@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 +121 -46
- package/dist/contextProvider.d.ts.map +1 -1
- package/dist/createNextProxy.d.ts +5 -11
- package/dist/createNextProxy.d.ts.map +1 -1
- package/dist/index.js +19 -5
- package/dist/index.mjs +19 -5
- package/package.json +8 -6
- package/src/contextProvider.tsx +1 -2
- package/src/createNextProxy.ts +26 -17
package/README.md
CHANGED
|
@@ -1,86 +1,161 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Quick Start
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Next.js Setup
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
### Install
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
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
|
-
|
|
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
|
-
```
|
|
22
|
-
|
|
23
|
-
import EdgeStore from '@edgestore/react/next';
|
|
60
|
+
```tsx title="src/lib/edgestore.ts"
|
|
61
|
+
'use client';
|
|
24
62
|
|
|
25
|
-
|
|
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
|
-
|
|
72
|
+
And then wrap our app with the provider.
|
|
29
73
|
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
import
|
|
74
|
+
```tsx title="src/app/layout.tsx"
|
|
75
|
+
import { EdgeStoreProvider } from '../lib/edgestore';
|
|
76
|
+
import './globals.css';
|
|
33
77
|
|
|
34
|
-
|
|
78
|
+
// ...
|
|
79
|
+
|
|
80
|
+
export default function RootLayout({
|
|
81
|
+
children,
|
|
82
|
+
}: {
|
|
83
|
+
children: React.ReactNode;
|
|
84
|
+
}) {
|
|
35
85
|
return (
|
|
36
|
-
<
|
|
37
|
-
<
|
|
38
|
-
|
|
86
|
+
<html lang="en">
|
|
87
|
+
<body>
|
|
88
|
+
<EdgeStoreProvider>{children}</EdgeStoreProvider>
|
|
89
|
+
</body>
|
|
90
|
+
</html>
|
|
39
91
|
);
|
|
40
92
|
}
|
|
41
93
|
```
|
|
42
94
|
|
|
43
|
-
### Upload
|
|
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
|
-
```
|
|
46
|
-
import { useEdgeStore } from '
|
|
99
|
+
```tsx {1, 6, 19-28}
|
|
100
|
+
import { useEdgeStore } from '../lib/edgestore';
|
|
101
|
+
import * as React from 'react';
|
|
47
102
|
|
|
48
|
-
|
|
49
|
-
const [file, setFile] = useState(null);
|
|
50
|
-
const {
|
|
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
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
###
|
|
138
|
+
### Replace file
|
|
73
139
|
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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;;
|
|
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,
|
|
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:
|
|
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:
|
|
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
|
|
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":";AAAA,OAAO,EACL,SAAS,EACT,
|
|
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 = ({
|
|
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('[
|
|
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 = ({
|
|
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('[
|
|
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.
|
|
4
|
-
"description": "
|
|
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/
|
|
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.
|
|
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.
|
|
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": "
|
|
74
|
+
"gitHead": "f57bc36304ce6bd97f5b85e33a2b1eeeaf7633d2"
|
|
73
75
|
}
|
package/src/contextProvider.tsx
CHANGED
|
@@ -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
|
-
'[
|
|
63
|
+
'[edgestore]: `useEdgeStore` must be wrapped in a <EdgeStoreProvider />',
|
|
65
64
|
);
|
|
66
65
|
}
|
|
67
66
|
|
package/src/createNextProxy.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AnyRouter,
|
|
3
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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,
|