@edgestore/react 0.0.0-alpha.12 → 0.0.0-alpha.14

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.
@@ -1 +1 @@
1
- {"version":3,"file":"contextProvider.d.ts","sourceRoot":"","sources":["../src/contextProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAmB,MAAM,mBAAmB,CAAC;AAKrE,KAAK,qBAAqB,CAAC,OAAO,SAAS,SAAS,IAAI;IACtD,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;IACpC;;;OAGG;IACH,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;CACjC,CAAC;AAEF,wBAAgB,uBAAuB,CAAC,OAAO,SAAS,SAAS,EAAE,IAAI,CAAC,EAAE;IACxE;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;;kBAUa,MAAM,SAAS;QACzB;;;;;;WAMG;;;;EAgCN"}
1
+ {"version":3,"file":"contextProvider.d.ts","sourceRoot":"","sources":["../src/contextProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAmB,MAAM,mBAAmB,CAAC;AAKrE,KAAK,qBAAqB,CAAC,OAAO,SAAS,SAAS,IAAI;IACtD,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;IACpC;;;OAGG;IACH,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;CACjC,CAAC;AAEF,wBAAgB,uBAAuB,CAAC,OAAO,SAAS,SAAS,EAAE,IAAI,CAAC,EAAE;IACxE;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;;kBASa,MAAM,SAAS;QACzB;;;;;;WAMG;;;;EAgCN"}
@@ -1,26 +1,30 @@
1
1
  /// <reference types="react" />
2
- import { AnyRouter, InferBucketPathKeys, InferMetadataObject } from '@edgestore/server/core';
2
+ import { AnyRouter, InferBucketPathObject, InferMetadataObject } from '@edgestore/server/core';
3
3
  import { z } from 'zod';
4
4
  export type BucketFunctions<TRouter extends AnyRouter> = {
5
5
  [K in keyof TRouter['buckets']]: {
6
- upload: (params: z.infer<TRouter['buckets'][K]['_def']['input']> extends object ? {
6
+ upload: (params: z.infer<TRouter['buckets'][K]['_def']['input']> extends never ? {
7
7
  file: File;
8
- input: z.infer<TRouter['buckets'][K]['_def']['input']>;
9
8
  onProgressChange?: OnProgressChangeHandler;
10
9
  options?: UploadOptions;
11
10
  } : {
12
11
  file: File;
12
+ input: z.infer<TRouter['buckets'][K]['_def']['input']>;
13
13
  onProgressChange?: OnProgressChangeHandler;
14
14
  options?: UploadOptions;
15
- }) => Promise<{
15
+ }) => Promise<TRouter['buckets'][K]['_def']['type'] extends 'IMAGE' ? {
16
+ url: string;
17
+ thumbnailUrl: string | null;
18
+ size: number;
19
+ uploadedAt: Date;
20
+ metadata: InferMetadataObject<TRouter['buckets'][K]>;
21
+ path: InferBucketPathObject<TRouter['buckets'][K]>;
22
+ } : {
16
23
  url: string;
17
- thumbnailUrl: TRouter['buckets'][K]['_def']['type'] extends 'IMAGE' ? string | null : never;
18
24
  size: number;
19
25
  uploadedAt: Date;
20
26
  metadata: InferMetadataObject<TRouter['buckets'][K]>;
21
- path: {
22
- [TKey in InferBucketPathKeys<TRouter['buckets'][K]>]: string;
23
- };
27
+ path: InferBucketPathObject<TRouter['buckets'][K]>;
24
28
  }>;
25
29
  delete: (params: {
26
30
  url: string;
@@ -31,6 +35,22 @@ export type BucketFunctions<TRouter extends AnyRouter> = {
31
35
  };
32
36
  type OnProgressChangeHandler = (progress: number) => void;
33
37
  type UploadOptions = {
38
+ /**
39
+ * e.g. 'my-file-name.jpg'
40
+ *
41
+ * By default, a unique file name will be generated for each upload.
42
+ * If you want to use a custom file name, you can use this option.
43
+ * If you use the same file name for multiple uploads, the previous file will be overwritten.
44
+ * But it might take some time for the CDN cache to be cleared.
45
+ * So maybe you will keep seeing the old file for a while.
46
+ *
47
+ * If you want to replace an existing file immediately leave the `manualFileName` option empty and use the `replaceTargetUrl` option.
48
+ */
49
+ manualFileName?: string;
50
+ /**
51
+ * Use this to replace an existing file.
52
+ * It will automatically delete the existing file when the upload is complete.
53
+ */
34
54
  replaceTargetUrl?: string;
35
55
  };
36
56
  export declare function createNextProxy<TRouter extends AnyRouter>({ apiPath, uploadingCountRef, maxConcurrentUploads, }: {
@@ -1 +1 @@
1
- {"version":3,"file":"createNextProxy.d.ts","sourceRoot":"","sources":["../src/createNextProxy.ts"],"names":[],"mappings":";AAAA,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,MAAM,eAAe,CAAC,OAAO,SAAS,SAAS,IAAI;KACtD,CAAC,IAAI,MAAM,OAAO,CAAC,SAAS,CAAC,GAAG;QAC/B,MAAM,EAAE,CACN,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,MAAM,GAClE;YACE,IAAI,EAAE,IAAI,CAAC;YACX,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;YACvD,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;YAC3C,OAAO,CAAC,EAAE,aAAa,CAAC;SACzB,GACD;YACE,IAAI,EAAE,IAAI,CAAC;YACX,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;YAC3C,OAAO,CAAC,EAAE,aAAa,CAAC;SACzB,KACF,OAAO,CAAC;YACX,GAAG,EAAE,MAAM,CAAC;YACZ,YAAY,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,SAAS,OAAO,GAC/D,MAAM,GAAG,IAAI,GACb,KAAK,CAAC;YACV,IAAI,EAAE,MAAM,CAAC;YACb,UAAU,EAAE,IAAI,CAAC;YACjB,QAAQ,EAAE,mBAAmB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACrD,IAAI,EAAE;iBACH,IAAI,IAAI,mBAAmB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM;aAC7D,CAAC;SACH,CAAC,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC;YAC3C,OAAO,EAAE,OAAO,CAAC;SAClB,CAAC,CAAC;KACJ;CACF,CAAC;AAEF,KAAK,uBAAuB,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;AAE1D,KAAK,aAAa,GAAG;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,wBAAgB,eAAe,CAAC,OAAO,SAAS,SAAS,EAAE,EACzD,OAAO,EACP,iBAAiB,EACjB,oBAAwB,GACzB,EAAE;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAClD,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,4BAiCA"}
1
+ {"version":3,"file":"createNextProxy.d.ts","sourceRoot":"","sources":["../src/createNextProxy.ts"],"names":[],"mappings":";AAAA,OAAO,EACL,SAAS,EACT,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,MAAM,eAAe,CAAC,OAAO,SAAS,SAAS,IAAI;KACtD,CAAC,IAAI,MAAM,OAAO,CAAC,SAAS,CAAC,GAAG;QAC/B,MAAM,EAAE,CACN,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,KAAK,GACjE;YACE,IAAI,EAAE,IAAI,CAAC;YACX,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;YAC3C,OAAO,CAAC,EAAE,aAAa,CAAC;SACzB,GACD;YACE,IAAI,EAAE,IAAI,CAAC;YACX,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;YACvD,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;YAC3C,OAAO,CAAC,EAAE,aAAa,CAAC;SACzB,KACF,OAAO,CACV,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,SAAS,OAAO,GACjD;YACE,GAAG,EAAE,MAAM,CAAC;YACZ,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;YAC5B,IAAI,EAAE,MAAM,CAAC;YACb,UAAU,EAAE,IAAI,CAAC;YACjB,QAAQ,EAAE,mBAAmB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACrD,IAAI,EAAE,qBAAqB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SACpD,GACD;YACE,GAAG,EAAE,MAAM,CAAC;YACZ,IAAI,EAAE,MAAM,CAAC;YACb,UAAU,EAAE,IAAI,CAAC;YACjB,QAAQ,EAAE,mBAAmB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACrD,IAAI,EAAE,qBAAqB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SACpD,CACN,CAAC;QACF,MAAM,EAAE,CAAC,MAAM,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC;YAC3C,OAAO,EAAE,OAAO,CAAC;SAClB,CAAC,CAAC;KACJ;CACF,CAAC;AAEF,KAAK,uBAAuB,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;AAE1D,KAAK,aAAa,GAAG;IACnB;;;;;;;;;;OAUG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,wBAAgB,eAAe,CAAC,OAAO,SAAS,SAAS,EAAE,EACzD,OAAO,EACP,iBAAiB,EACjB,oBAAwB,GACzB,EAAE;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAClD,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,4BAiCA"}
package/dist/index.js CHANGED
@@ -74,6 +74,7 @@ async function uploadFile({ file, input, onProgressChange, options }, { apiPath,
74
74
  extension: file.name.split('.').pop(),
75
75
  type: file.type,
76
76
  size: file.size,
77
+ fileName: options?.manualFileName,
77
78
  replaceTargetUrl: options?.replaceTargetUrl
78
79
  }
79
80
  }),
@@ -88,8 +89,8 @@ async function uploadFile({ file, input, onProgressChange, options }, { apiPath,
88
89
  // Upload the file to the signed URL and get the progress
89
90
  await uploadFileInner(file, json.uploadUrl, onProgressChange);
90
91
  return {
91
- url: json.accessUrl,
92
- thumbnailUrl: json.thumbnailUrl,
92
+ url: getUrl(json.accessUrl, apiPath),
93
+ thumbnailUrl: json.thumbnailUrl ? getUrl(json.thumbnailUrl, apiPath) : null,
93
94
  size: json.size,
94
95
  uploadedAt: new Date(json.uploadedAt),
95
96
  path: json.path,
@@ -100,6 +101,21 @@ async function uploadFile({ file, input, onProgressChange, options }, { apiPath,
100
101
  throw e;
101
102
  }
102
103
  }
104
+ /**
105
+ * Protected files need third-party cookies to work.
106
+ * Since third party cookies doesn't work on localhost,
107
+ * we need to proxy the file through the server.
108
+ */ function getUrl(url, apiPath) {
109
+ if (process.env.NODE_ENV === 'development' && !url.includes('/_public/')) {
110
+ const proxyUrl = new URL(window.location.origin);
111
+ proxyUrl.pathname = `${apiPath}/proxy-file`;
112
+ proxyUrl.search = new URLSearchParams({
113
+ url
114
+ }).toString();
115
+ return proxyUrl.toString();
116
+ }
117
+ return url;
118
+ }
103
119
  const uploadFileInner = async (file, uploadUrl, onProgressChange)=>{
104
120
  const promise = new Promise((resolve, reject)=>{
105
121
  const request = new XMLHttpRequest();
@@ -146,11 +162,10 @@ async function deleteFile({ url }, { apiPath, bucketName }) {
146
162
  };
147
163
  }
148
164
 
149
- const DEFAULT_BASE_URL = process.env.NEXT_PUBLIC_EDGE_STORE_BASE_URL ?? 'https://files.edge-store.com';
165
+ const DEFAULT_BASE_URL = process.env.NEXT_PUBLIC_EDGE_STORE_BASE_URL ?? 'https://files.edgestore.dev';
150
166
  function createEdgeStoreProvider(opts) {
151
167
  const EdgeStoreContext = /*#__PURE__*/ React__namespace.createContext(undefined);
152
- const EdgeStoreProvider = ({ // TODO: Add basePath when custom domain is supported
153
- children, basePath })=>{
168
+ const EdgeStoreProvider = ({ children, basePath })=>{
154
169
  return EdgeStoreProviderInner({
155
170
  children,
156
171
  context: EdgeStoreContext,
@@ -165,7 +180,7 @@ function createEdgeStoreProvider(opts) {
165
180
  // @ts-expect-error - We know that the context value should not be undefined
166
181
  const value = React__namespace.useContext(EdgeStoreContext);
167
182
  if (!value && process.env.NODE_ENV !== 'production') {
168
- throw new Error('[edge-store]: `useEdgeStore` must be wrapped in a <EdgeStoreProvider />');
183
+ throw new Error('[edgestore]: `useEdgeStore` must be wrapped in a <EdgeStoreProvider />');
169
184
  }
170
185
  return value;
171
186
  }
@@ -193,11 +208,12 @@ function EdgeStoreProviderInner({ children, context, basePath, maxConcurrentUplo
193
208
  });
194
209
  }
195
210
  });
211
+ // eslint-disable-next-line react-hooks/exhaustive-deps
196
212
  }, []);
197
213
  function getSrc(url) {
198
214
  if (// in production we use cookies, so we don't need a token
199
215
  process.env.NODE_ENV === 'production' || // public urls don't need a token
200
- // e.g. https://files.edge-store.com/project/bucket/_public/...
216
+ // e.g. https://files.edgestore.dev/project/bucket/_public/...
201
217
  url.match(/^https?:\/\/[^\/]+\/[^\/]+\/[^\/]+\/_public\/.+/)) {
202
218
  return `${url}`;
203
219
  } else {
package/dist/index.mjs CHANGED
@@ -50,6 +50,7 @@ async function uploadFile({ file, input, onProgressChange, options }, { apiPath,
50
50
  extension: file.name.split('.').pop(),
51
51
  type: file.type,
52
52
  size: file.size,
53
+ fileName: options?.manualFileName,
53
54
  replaceTargetUrl: options?.replaceTargetUrl
54
55
  }
55
56
  }),
@@ -64,8 +65,8 @@ async function uploadFile({ file, input, onProgressChange, options }, { apiPath,
64
65
  // Upload the file to the signed URL and get the progress
65
66
  await uploadFileInner(file, json.uploadUrl, onProgressChange);
66
67
  return {
67
- url: json.accessUrl,
68
- thumbnailUrl: json.thumbnailUrl,
68
+ url: getUrl(json.accessUrl, apiPath),
69
+ thumbnailUrl: json.thumbnailUrl ? getUrl(json.thumbnailUrl, apiPath) : null,
69
70
  size: json.size,
70
71
  uploadedAt: new Date(json.uploadedAt),
71
72
  path: json.path,
@@ -76,6 +77,21 @@ async function uploadFile({ file, input, onProgressChange, options }, { apiPath,
76
77
  throw e;
77
78
  }
78
79
  }
80
+ /**
81
+ * Protected files need third-party cookies to work.
82
+ * Since third party cookies doesn't work on localhost,
83
+ * we need to proxy the file through the server.
84
+ */ function getUrl(url, apiPath) {
85
+ if (process.env.NODE_ENV === 'development' && !url.includes('/_public/')) {
86
+ const proxyUrl = new URL(window.location.origin);
87
+ proxyUrl.pathname = `${apiPath}/proxy-file`;
88
+ proxyUrl.search = new URLSearchParams({
89
+ url
90
+ }).toString();
91
+ return proxyUrl.toString();
92
+ }
93
+ return url;
94
+ }
79
95
  const uploadFileInner = async (file, uploadUrl, onProgressChange)=>{
80
96
  const promise = new Promise((resolve, reject)=>{
81
97
  const request = new XMLHttpRequest();
@@ -122,11 +138,10 @@ async function deleteFile({ url }, { apiPath, bucketName }) {
122
138
  };
123
139
  }
124
140
 
125
- const DEFAULT_BASE_URL = process.env.NEXT_PUBLIC_EDGE_STORE_BASE_URL ?? 'https://files.edge-store.com';
141
+ const DEFAULT_BASE_URL = process.env.NEXT_PUBLIC_EDGE_STORE_BASE_URL ?? 'https://files.edgestore.dev';
126
142
  function createEdgeStoreProvider(opts) {
127
143
  const EdgeStoreContext = /*#__PURE__*/ React.createContext(undefined);
128
- const EdgeStoreProvider = ({ // TODO: Add basePath when custom domain is supported
129
- children, basePath })=>{
144
+ const EdgeStoreProvider = ({ children, basePath })=>{
130
145
  return EdgeStoreProviderInner({
131
146
  children,
132
147
  context: EdgeStoreContext,
@@ -141,7 +156,7 @@ function createEdgeStoreProvider(opts) {
141
156
  // @ts-expect-error - We know that the context value should not be undefined
142
157
  const value = React.useContext(EdgeStoreContext);
143
158
  if (!value && process.env.NODE_ENV !== 'production') {
144
- throw new Error('[edge-store]: `useEdgeStore` must be wrapped in a <EdgeStoreProvider />');
159
+ throw new Error('[edgestore]: `useEdgeStore` must be wrapped in a <EdgeStoreProvider />');
145
160
  }
146
161
  return value;
147
162
  }
@@ -169,11 +184,12 @@ function EdgeStoreProviderInner({ children, context, basePath, maxConcurrentUplo
169
184
  });
170
185
  }
171
186
  });
187
+ // eslint-disable-next-line react-hooks/exhaustive-deps
172
188
  }, []);
173
189
  function getSrc(url) {
174
190
  if (// in production we use cookies, so we don't need a token
175
191
  process.env.NODE_ENV === 'production' || // public urls don't need a token
176
- // e.g. https://files.edge-store.com/project/bucket/_public/...
192
+ // e.g. https://files.edgestore.dev/project/bucket/_public/...
177
193
  url.match(/^https?:\/\/[^\/]+\/[^\/]+\/[^\/]+\/_public\/.+/)) {
178
194
  return `${url}`;
179
195
  } else {
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@edgestore/react",
3
- "version": "0.0.0-alpha.12",
4
- "description": "Image Handling for React/Next.js",
5
- "homepage": "https://edge-store.com",
6
- "repository": "https://github.com/edgestorejs/edge-store.git",
3
+ "version": "0.0.0-alpha.14",
4
+ "description": "The best DX for uploading files from your Next.js app",
5
+ "homepage": "https://edgestore.dev",
6
+ "repository": "https://github.com/edgestorejs/edgestore.git",
7
7
  "author": "Ravi <me@ravi.com>",
8
8
  "main": "dist/index.js",
9
9
  "module": "dist/index.mjs",
@@ -12,6 +12,8 @@
12
12
  "react",
13
13
  "nodejs",
14
14
  "nextjs",
15
+ "upload",
16
+ "file",
15
17
  "image",
16
18
  "cdn",
17
19
  "edgestore",
@@ -52,14 +54,14 @@
52
54
  "uuid": "^9.0.0"
53
55
  },
54
56
  "peerDependencies": {
55
- "@edgestore/server": "0.0.0-alpha.12",
57
+ "@edgestore/server": "0.0.0-alpha.14",
56
58
  "next": "*",
57
59
  "react": ">=16.8.0",
58
60
  "react-dom": ">=16.8.0",
59
61
  "zod": ">=3.0.0"
60
62
  },
61
63
  "devDependencies": {
62
- "@edgestore/server": "0.0.0-alpha.12",
64
+ "@edgestore/server": "0.0.0-alpha.14",
63
65
  "@types/cookie": "^0.5.1",
64
66
  "@types/node": "^18.11.18",
65
67
  "@types/uuid": "^9.0.1",
@@ -69,5 +71,5 @@
69
71
  "typescript": "^5.1.6",
70
72
  "zod": "^3.21.4"
71
73
  },
72
- "gitHead": "bec47f77e223a231f5e04070aa8da6a609838e6b"
74
+ "gitHead": "6e0044e7fcb252014e5a4fc9257d54e897f42b84"
73
75
  }
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import { BucketFunctions, createNextProxy } from './createNextProxy';
4
4
 
5
5
  const DEFAULT_BASE_URL =
6
- process.env.NEXT_PUBLIC_EDGE_STORE_BASE_URL ?? 'https://files.edge-store.com';
6
+ process.env.NEXT_PUBLIC_EDGE_STORE_BASE_URL ?? 'https://files.edgestore.dev';
7
7
 
8
8
  type EdgeStoreContextValue<TRouter extends AnyRouter> = {
9
9
  edgestore: BucketFunctions<TRouter>;
@@ -29,7 +29,6 @@ export function createEdgeStoreProvider<TRouter extends AnyRouter>(opts?: {
29
29
  >(undefined);
30
30
 
31
31
  const EdgeStoreProvider = ({
32
- // TODO: Add basePath when custom domain is supported
33
32
  children,
34
33
  basePath,
35
34
  }: {
@@ -61,7 +60,7 @@ export function createEdgeStoreProvider<TRouter extends AnyRouter>(opts?: {
61
60
  React.useContext(EdgeStoreContext);
62
61
  if (!value && process.env.NODE_ENV !== 'production') {
63
62
  throw new Error(
64
- '[edge-store]: `useEdgeStore` must be wrapped in a <EdgeStoreProvider />',
63
+ '[edgestore]: `useEdgeStore` must be wrapped in a <EdgeStoreProvider />',
65
64
  );
66
65
  }
67
66
 
@@ -103,6 +102,7 @@ function EdgeStoreProviderInner<TRouter extends AnyRouter>({
103
102
  });
104
103
  }
105
104
  });
105
+ // eslint-disable-next-line react-hooks/exhaustive-deps
106
106
  }, []);
107
107
 
108
108
  function getSrc(url: string) {
@@ -110,7 +110,7 @@ function EdgeStoreProviderInner<TRouter extends AnyRouter>({
110
110
  // in production we use cookies, so we don't need a token
111
111
  process.env.NODE_ENV === 'production' ||
112
112
  // public urls don't need a token
113
- // e.g. https://files.edge-store.com/project/bucket/_public/...
113
+ // e.g. https://files.edgestore.dev/project/bucket/_public/...
114
114
  url.match(/^https?:\/\/[^\/]+\/[^\/]+\/[^\/]+\/_public\/.+/)
115
115
  ) {
116
116
  return `${url}`;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  AnyRouter,
3
- InferBucketPathKeys,
3
+ InferBucketPathObject,
4
4
  InferMetadataObject,
5
5
  } from '@edgestore/server/core';
6
6
  import { z } from 'zod';
@@ -9,30 +9,36 @@ import EdgeStoreError from './libs/errors/EdgeStoreError';
9
9
  export type BucketFunctions<TRouter extends AnyRouter> = {
10
10
  [K in keyof TRouter['buckets']]: {
11
11
  upload: (
12
- params: z.infer<TRouter['buckets'][K]['_def']['input']> extends object
12
+ params: z.infer<TRouter['buckets'][K]['_def']['input']> extends never
13
13
  ? {
14
14
  file: File;
15
- input: z.infer<TRouter['buckets'][K]['_def']['input']>;
16
15
  onProgressChange?: OnProgressChangeHandler;
17
16
  options?: UploadOptions;
18
17
  }
19
18
  : {
20
19
  file: File;
20
+ input: z.infer<TRouter['buckets'][K]['_def']['input']>;
21
21
  onProgressChange?: OnProgressChangeHandler;
22
22
  options?: UploadOptions;
23
23
  },
24
- ) => Promise<{
25
- url: string;
26
- thumbnailUrl: TRouter['buckets'][K]['_def']['type'] extends 'IMAGE'
27
- ? string | null
28
- : never;
29
- size: number;
30
- uploadedAt: Date;
31
- metadata: InferMetadataObject<TRouter['buckets'][K]>;
32
- path: {
33
- [TKey in InferBucketPathKeys<TRouter['buckets'][K]>]: string;
34
- };
35
- }>;
24
+ ) => Promise<
25
+ TRouter['buckets'][K]['_def']['type'] extends 'IMAGE'
26
+ ? {
27
+ url: string;
28
+ thumbnailUrl: string | null;
29
+ size: number;
30
+ uploadedAt: Date;
31
+ metadata: InferMetadataObject<TRouter['buckets'][K]>;
32
+ path: InferBucketPathObject<TRouter['buckets'][K]>;
33
+ }
34
+ : {
35
+ url: string;
36
+ size: number;
37
+ uploadedAt: Date;
38
+ metadata: InferMetadataObject<TRouter['buckets'][K]>;
39
+ path: InferBucketPathObject<TRouter['buckets'][K]>;
40
+ }
41
+ >;
36
42
  delete: (params: { url: string }) => Promise<{
37
43
  success: boolean;
38
44
  }>;
@@ -42,6 +48,22 @@ export type BucketFunctions<TRouter extends AnyRouter> = {
42
48
  type OnProgressChangeHandler = (progress: number) => void;
43
49
 
44
50
  type UploadOptions = {
51
+ /**
52
+ * e.g. 'my-file-name.jpg'
53
+ *
54
+ * By default, a unique file name will be generated for each upload.
55
+ * If you want to use a custom file name, you can use this option.
56
+ * If you use the same file name for multiple uploads, the previous file will be overwritten.
57
+ * But it might take some time for the CDN cache to be cleared.
58
+ * So maybe you will keep seeing the old file for a while.
59
+ *
60
+ * If you want to replace an existing file immediately leave the `manualFileName` option empty and use the `replaceTargetUrl` option.
61
+ */
62
+ manualFileName?: string;
63
+ /**
64
+ * Use this to replace an existing file.
65
+ * It will automatically delete the existing file when the upload is complete.
66
+ */
45
67
  replaceTargetUrl?: string;
46
68
  };
47
69
 
@@ -119,6 +141,7 @@ async function uploadFile(
119
141
  extension: file.name.split('.').pop(),
120
142
  type: file.type,
121
143
  size: file.size,
144
+ fileName: options?.manualFileName,
122
145
  replaceTargetUrl: options?.replaceTargetUrl,
123
146
  },
124
147
  }),
@@ -133,8 +156,10 @@ async function uploadFile(
133
156
  // Upload the file to the signed URL and get the progress
134
157
  await uploadFileInner(file, json.uploadUrl, onProgressChange);
135
158
  return {
136
- url: json.accessUrl,
137
- thumbnailUrl: json.thumbnailUrl,
159
+ url: getUrl(json.accessUrl, apiPath),
160
+ thumbnailUrl: json.thumbnailUrl
161
+ ? getUrl(json.thumbnailUrl, apiPath)
162
+ : null,
138
163
  size: json.size,
139
164
  uploadedAt: new Date(json.uploadedAt),
140
165
  path: json.path,
@@ -146,6 +171,23 @@ async function uploadFile(
146
171
  }
147
172
  }
148
173
 
174
+ /**
175
+ * Protected files need third-party cookies to work.
176
+ * Since third party cookies doesn't work on localhost,
177
+ * we need to proxy the file through the server.
178
+ */
179
+ function getUrl(url: string, apiPath: string) {
180
+ if (process.env.NODE_ENV === 'development' && !url.includes('/_public/')) {
181
+ const proxyUrl = new URL(window.location.origin);
182
+ proxyUrl.pathname = `${apiPath}/proxy-file`;
183
+ proxyUrl.search = new URLSearchParams({
184
+ url,
185
+ }).toString();
186
+ return proxyUrl.toString();
187
+ }
188
+ return url;
189
+ }
190
+
149
191
  const uploadFileInner = async (
150
192
  file: File,
151
193
  uploadUrl: string,