@edgestore/react 0.0.0-alpha.13 → 0.0.1

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
@@ -1,86 +1,161 @@
1
- # Getting Started
1
+ # Quick Start
2
2
 
3
- ### Next.js Setup
3
+ ## Next.js Setup
4
4
 
5
- #### Install
5
+ ### Install
6
6
 
7
- ```bash
8
- npm install @edgestore/react
7
+ Let's start by installing the required packages.
8
+
9
+ ```shell
10
+ npm install @edgestore/server @edgestore/react zod
9
11
  ```
10
12
 
11
- #### Environment Variables
13
+ ### Environment Variables
14
+
15
+ Then go to your [Dashboard](https://dashboard.edgestore.dev), create a new project and copy the keys to your environment variables.
12
16
 
13
- ```bash
14
- # .env
17
+ ```shell title=".env"
15
18
  EDGE_STORE_ACCESS_KEY=your-access-key
16
19
  EDGE_STORE_SECRET_KEY=your-secret-key
17
20
  ```
18
21
 
19
- #### API Route
22
+ ### Backend
23
+
24
+ Now we can create the backend code for our Next.js app.<br/>
25
+ Edge Store is compatible with both types of Next.js apps (`pages router` and `app router`).
26
+
27
+ The example below is the simplest bucket you can create with Edge Store. Just a simple file bucket with no validation that will be accessible by anyone with the link.
28
+
29
+ You can have multiple buckets in your app, each with its own configuration.
30
+
31
+ ```ts title="src/app/api/edgestore/[...edgestore]/route.ts"
32
+ import { initEdgeStore } from '@edgestore/server';
33
+ import { createEdgeStoreNextHandler } from '@edgestore/server/adapters/next/app';
34
+
35
+ const es = initEdgeStore.create();
36
+
37
+ /**
38
+ * This is the main router for the Edge Store buckets.
39
+ */
40
+ const edgeStoreRouter = es.router({
41
+ publicFiles: es.fileBucket(),
42
+ });
43
+
44
+ const handler = createEdgeStoreNextHandler({
45
+ router: edgeStoreRouter,
46
+ });
47
+
48
+ export { handler as GET, handler as POST };
49
+
50
+ /**
51
+ * This type is used to create the type-safe client for the frontend.
52
+ */
53
+ export type EdgeStoreRouter = typeof edgeStoreRouter;
54
+ ```
55
+
56
+ ### Frontend
57
+
58
+ Now let's initiate our context provider.
20
59
 
21
- ```jsx
22
- // pages/api/edgestore/[...edgestore].js
23
- import EdgeStore from '@edgestore/react/next';
60
+ ```tsx title="src/lib/edgestore.ts"
61
+ 'use client';
24
62
 
25
- export default EdgeStore();
63
+ import { EdgeStoreRouter } from '../app/api/edgestore/[...edgestore]/route';
64
+ import { createEdgeStoreProvider } from '@edgestore/react';
65
+
66
+ const { EdgeStoreProvider, useEdgeStore } =
67
+ createEdgeStoreProvider<EdgeStoreRouter>();
68
+
69
+ export { EdgeStoreProvider, useEdgeStore };
26
70
  ```
27
71
 
28
- #### Provider
72
+ And then wrap our app with the provider.
29
73
 
30
- ```jsx
31
- // pages/_app.jsx
32
- import { EdgeStoreProvider } from '@edgestore/react';
74
+ ```tsx title="src/app/layout.tsx"
75
+ import { EdgeStoreProvider } from '../lib/edgestore';
76
+ import './globals.css';
33
77
 
34
- export default function App({ Component, pageProps }) {
78
+ // ...
79
+
80
+ export default function RootLayout({
81
+ children,
82
+ }: {
83
+ children: React.ReactNode;
84
+ }) {
35
85
  return (
36
- <EdgeStoreProvider>
37
- <Component {...pageProps} />
38
- </EdgeStoreProvider>
86
+ <html lang="en">
87
+ <body>
88
+ <EdgeStoreProvider>{children}</EdgeStoreProvider>
89
+ </body>
90
+ </html>
39
91
  );
40
92
  }
41
93
  ```
42
94
 
43
- ### Upload image
95
+ ### Upload file
96
+
97
+ You can use the `useEdgeStore` hook to access typesafe frontend client and use it to upload files.
44
98
 
45
- ```jsx
46
- import { useEdgeStore } from '@edgestore/react';
99
+ ```tsx {1, 6, 19-28}
100
+ import { useEdgeStore } from '../lib/edgestore';
101
+ import * as React from 'react';
47
102
 
48
- const Page = () => {
49
- const [file, setFile] = useState(null);
50
- const { upload } = useEdgeStore();
103
+ export default function Page() {
104
+ const [file, setFile] = React.useState<File | null>(null);
105
+ const { edgestore } = useEdgeStore();
51
106
 
52
107
  return (
53
108
  <div>
54
- <input type="file" onChange={(e) => setFile(e.target.files[0])} />
109
+ <input
110
+ type="file"
111
+ onChange={(e) => {
112
+ setFile(e.target.files?.[0] ?? null);
113
+ }}
114
+ />
55
115
  <button
56
116
  onClick={async () => {
57
- await upload({
58
- file,
59
- key: 'path/to/image.jpg',
60
- });
117
+ if (file) {
118
+ const res = await edgestore.publicFiles.upload({
119
+ file,
120
+ onProgressChange: (progress) => {
121
+ // you can use this to show a progress bar
122
+ console.log(progress);
123
+ },
124
+ });
125
+ // you can run some server action or api here
126
+ // to add the necessary data to your database
127
+ console.log(res);
128
+ }
61
129
  }}
62
130
  >
63
131
  Upload
64
132
  </button>
65
133
  </div>
66
134
  );
67
- };
68
-
69
- export default Page;
135
+ }
70
136
  ```
71
137
 
72
- ### Show image
138
+ ### Replace file
73
139
 
74
- ```jsx
75
- import { useEdgeStore } from '@edgestore/react';
140
+ By passing the `replaceTargetUrl` option, you can replace an existing file with a new one.
141
+ It will automatically delete the old file after the upload is complete.
76
142
 
77
- const Page = () => {
78
- const { getImgSrc } = useEdgeStore();
143
+ You can also just upload the file using the same file name, but in that case, you might still see the old file for a while becasue of the CDN cache.
79
144
 
80
- return (
81
- <div>
82
- <img src={getImgSrc('path/to/image.jpg')} />
83
- </div>
84
- );
85
- };
145
+ ```tsx
146
+ const res = await edgestore.publicFiles.upload({
147
+ file,
148
+ options: {
149
+ replaceTargetUrl: oldFileUrl,
150
+ },
151
+ // ...
152
+ });
153
+ ```
154
+
155
+ ### Delete file
156
+
157
+ ```tsx
158
+ await edgestore.publicFiles.delete({
159
+ url: urlToDelete,
160
+ })
86
161
  ```
@@ -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,5 +1,5 @@
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']]: {
@@ -18,17 +18,13 @@ export type BucketFunctions<TRouter extends AnyRouter> = {
18
18
  size: number;
19
19
  uploadedAt: Date;
20
20
  metadata: InferMetadataObject<TRouter['buckets'][K]>;
21
- path: InferBucketPathKeys<TRouter['buckets'][K]> extends never ? Record<string, never> : {
22
- [TKey in InferBucketPathKeys<TRouter['buckets'][K]>]: string;
23
- };
21
+ path: InferBucketPathObject<TRouter['buckets'][K]>;
24
22
  } : {
25
23
  url: string;
26
24
  size: number;
27
25
  uploadedAt: Date;
28
26
  metadata: InferMetadataObject<TRouter['buckets'][K]>;
29
- path: InferBucketPathKeys<TRouter['buckets'][K]> extends never ? Record<string, never> : {
30
- [TKey in InferBucketPathKeys<TRouter['buckets'][K]>]: string;
31
- };
27
+ path: InferBucketPathObject<TRouter['buckets'][K]>;
32
28
  }>;
33
29
  delete: (params: {
34
30
  url: string;
@@ -42,13 +38,11 @@ type UploadOptions = {
42
38
  /**
43
39
  * e.g. 'my-file-name.jpg'
44
40
  *
45
- * * Not Recommended *
46
- *
47
41
  * By default, a unique file name will be generated for each upload.
48
42
  * If you want to use a custom file name, you can use this option.
49
43
  * If you use the same file name for multiple uploads, the previous file will be overwritten.
50
- * But it will take some time for the cache to be cleared.
51
- * So you will keep seeing the old file for a while.
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.
52
46
  *
53
47
  * If you want to replace an existing file immediately leave the `manualFileName` option empty and use the `replaceTargetUrl` option.
54
48
  */
@@ -1 +1 @@
1
- {"version":3,"file":"createNextProxy.d.ts","sourceRoot":"","sources":["../src/createNextProxy.ts"],"names":[],"mappings":"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"}
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
@@ -89,8 +89,8 @@ async function uploadFile({ file, input, onProgressChange, options }, { apiPath,
89
89
  // Upload the file to the signed URL and get the progress
90
90
  await uploadFileInner(file, json.uploadUrl, onProgressChange);
91
91
  return {
92
- url: json.accessUrl,
93
- thumbnailUrl: json.thumbnailUrl,
92
+ url: getUrl(json.accessUrl, apiPath),
93
+ thumbnailUrl: json.thumbnailUrl ? getUrl(json.thumbnailUrl, apiPath) : null,
94
94
  size: json.size,
95
95
  uploadedAt: new Date(json.uploadedAt),
96
96
  path: json.path,
@@ -101,6 +101,21 @@ async function uploadFile({ file, input, onProgressChange, options }, { apiPath,
101
101
  throw e;
102
102
  }
103
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
+ }
104
119
  const uploadFileInner = async (file, uploadUrl, onProgressChange)=>{
105
120
  const promise = new Promise((resolve, reject)=>{
106
121
  const request = new XMLHttpRequest();
@@ -150,8 +165,7 @@ async function deleteFile({ url }, { apiPath, bucketName }) {
150
165
  const DEFAULT_BASE_URL = process.env.NEXT_PUBLIC_EDGE_STORE_BASE_URL ?? 'https://files.edgestore.dev';
151
166
  function createEdgeStoreProvider(opts) {
152
167
  const EdgeStoreContext = /*#__PURE__*/ React__namespace.createContext(undefined);
153
- const EdgeStoreProvider = ({ // TODO: Add basePath when custom domain is supported
154
- children, basePath })=>{
168
+ const EdgeStoreProvider = ({ children, basePath })=>{
155
169
  return EdgeStoreProviderInner({
156
170
  children,
157
171
  context: EdgeStoreContext,
@@ -166,7 +180,7 @@ function createEdgeStoreProvider(opts) {
166
180
  // @ts-expect-error - We know that the context value should not be undefined
167
181
  const value = React__namespace.useContext(EdgeStoreContext);
168
182
  if (!value && process.env.NODE_ENV !== 'production') {
169
- throw new Error('[edge-store]: `useEdgeStore` must be wrapped in a <EdgeStoreProvider />');
183
+ throw new Error('[edgestore]: `useEdgeStore` must be wrapped in a <EdgeStoreProvider />');
170
184
  }
171
185
  return value;
172
186
  }
package/dist/index.mjs CHANGED
@@ -65,8 +65,8 @@ async function uploadFile({ file, input, onProgressChange, options }, { apiPath,
65
65
  // Upload the file to the signed URL and get the progress
66
66
  await uploadFileInner(file, json.uploadUrl, onProgressChange);
67
67
  return {
68
- url: json.accessUrl,
69
- thumbnailUrl: json.thumbnailUrl,
68
+ url: getUrl(json.accessUrl, apiPath),
69
+ thumbnailUrl: json.thumbnailUrl ? getUrl(json.thumbnailUrl, apiPath) : null,
70
70
  size: json.size,
71
71
  uploadedAt: new Date(json.uploadedAt),
72
72
  path: json.path,
@@ -77,6 +77,21 @@ async function uploadFile({ file, input, onProgressChange, options }, { apiPath,
77
77
  throw e;
78
78
  }
79
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
+ }
80
95
  const uploadFileInner = async (file, uploadUrl, onProgressChange)=>{
81
96
  const promise = new Promise((resolve, reject)=>{
82
97
  const request = new XMLHttpRequest();
@@ -126,8 +141,7 @@ async function deleteFile({ url }, { apiPath, bucketName }) {
126
141
  const DEFAULT_BASE_URL = process.env.NEXT_PUBLIC_EDGE_STORE_BASE_URL ?? 'https://files.edgestore.dev';
127
142
  function createEdgeStoreProvider(opts) {
128
143
  const EdgeStoreContext = /*#__PURE__*/ React.createContext(undefined);
129
- const EdgeStoreProvider = ({ // TODO: Add basePath when custom domain is supported
130
- children, basePath })=>{
144
+ const EdgeStoreProvider = ({ children, basePath })=>{
131
145
  return EdgeStoreProviderInner({
132
146
  children,
133
147
  context: EdgeStoreContext,
@@ -142,7 +156,7 @@ function createEdgeStoreProvider(opts) {
142
156
  // @ts-expect-error - We know that the context value should not be undefined
143
157
  const value = React.useContext(EdgeStoreContext);
144
158
  if (!value && process.env.NODE_ENV !== 'production') {
145
- throw new Error('[edge-store]: `useEdgeStore` must be wrapped in a <EdgeStoreProvider />');
159
+ throw new Error('[edgestore]: `useEdgeStore` must be wrapped in a <EdgeStoreProvider />');
146
160
  }
147
161
  return value;
148
162
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@edgestore/react",
3
- "version": "0.0.0-alpha.13",
4
- "description": "Image Handling for React/Next.js",
3
+ "version": "0.0.1",
4
+ "description": "The best DX for uploading files from your Next.js app",
5
5
  "homepage": "https://edgestore.dev",
6
- "repository": "https://github.com/edgestorejs/edge-store.git",
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.13",
57
+ "@edgestore/server": "0.0.1",
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.13",
64
+ "@edgestore/server": "0.0.1",
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": "f89bcb0360feec96fb842ad447d86a0e5c58900d"
74
+ "gitHead": "f57bc36304ce6bd97f5b85e33a2b1eeeaf7633d2"
73
75
  }
@@ -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
 
@@ -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';
@@ -29,22 +29,14 @@ export type BucketFunctions<TRouter extends AnyRouter> = {
29
29
  size: number;
30
30
  uploadedAt: Date;
31
31
  metadata: InferMetadataObject<TRouter['buckets'][K]>;
32
- path: InferBucketPathKeys<TRouter['buckets'][K]> extends never
33
- ? Record<string, never>
34
- : {
35
- [TKey in InferBucketPathKeys<TRouter['buckets'][K]>]: string;
36
- };
32
+ path: InferBucketPathObject<TRouter['buckets'][K]>;
37
33
  }
38
34
  : {
39
35
  url: string;
40
36
  size: number;
41
37
  uploadedAt: Date;
42
38
  metadata: InferMetadataObject<TRouter['buckets'][K]>;
43
- path: InferBucketPathKeys<TRouter['buckets'][K]> extends never
44
- ? Record<string, never>
45
- : {
46
- [TKey in InferBucketPathKeys<TRouter['buckets'][K]>]: string;
47
- };
39
+ path: InferBucketPathObject<TRouter['buckets'][K]>;
48
40
  }
49
41
  >;
50
42
  delete: (params: { url: string }) => Promise<{
@@ -59,13 +51,11 @@ type UploadOptions = {
59
51
  /**
60
52
  * e.g. 'my-file-name.jpg'
61
53
  *
62
- * * Not Recommended *
63
- *
64
54
  * By default, a unique file name will be generated for each upload.
65
55
  * If you want to use a custom file name, you can use this option.
66
56
  * If you use the same file name for multiple uploads, the previous file will be overwritten.
67
- * But it will take some time for the cache to be cleared.
68
- * So you will keep seeing the old file for a while.
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.
69
59
  *
70
60
  * If you want to replace an existing file immediately leave the `manualFileName` option empty and use the `replaceTargetUrl` option.
71
61
  */
@@ -166,8 +156,10 @@ async function uploadFile(
166
156
  // Upload the file to the signed URL and get the progress
167
157
  await uploadFileInner(file, json.uploadUrl, onProgressChange);
168
158
  return {
169
- url: json.accessUrl,
170
- thumbnailUrl: json.thumbnailUrl,
159
+ url: getUrl(json.accessUrl, apiPath),
160
+ thumbnailUrl: json.thumbnailUrl
161
+ ? getUrl(json.thumbnailUrl, apiPath)
162
+ : null,
171
163
  size: json.size,
172
164
  uploadedAt: new Date(json.uploadedAt),
173
165
  path: json.path,
@@ -179,6 +171,23 @@ async function uploadFile(
179
171
  }
180
172
  }
181
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
+
182
191
  const uploadFileInner = async (
183
192
  file: File,
184
193
  uploadUrl: string,