@edgestore/server 0.3.1 → 0.3.3

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.
@@ -0,0 +1,18 @@
1
+ import { type EdgeStoreRouter, type MaybePromise, type Provider } from '@edgestore/shared';
2
+ import { type LogLevel } from '../../libs/logger';
3
+ export type CreateContextOptions = {
4
+ req: Request;
5
+ };
6
+ export type Config<TCtx> = {
7
+ provider?: Provider;
8
+ router: EdgeStoreRouter<TCtx>;
9
+ logLevel?: LogLevel;
10
+ } & (TCtx extends Record<string, never> ? object : {
11
+ provider?: Provider;
12
+ router: EdgeStoreRouter<TCtx>;
13
+ createContext: (opts: CreateContextOptions) => MaybePromise<TCtx>;
14
+ });
15
+ export declare function createEdgeStoreStartHandler<TCtx>(config: Config<TCtx>): ({ request }: {
16
+ request: Request;
17
+ }) => Promise<Response>;
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapters/start/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,QAAQ,EACd,MAAM,mBAAmB,CAAC;AAC3B,OAAe,EAAE,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAiB1D,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,EAAE,OAAO,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,MAAM,CAAC,IAAI,IAAI;IACzB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,MAAM,EAAE,eAAe,CAAC,IAAI,CAAC,CAAC;IAC9B,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB,GAAG,CAAC,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GACnC,MAAM,GACN;IACE,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,MAAM,EAAE,eAAe,CAAC,IAAI,CAAC,CAAC;IAC9B,aAAa,EAAE,CAAC,IAAI,EAAE,oBAAoB,KAAK,YAAY,CAAC,IAAI,CAAC,CAAC;CACnE,CAAC,CAAC;AAsBP,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC;aAM9B,OAAO;wBA4I9C"}
@@ -0,0 +1,190 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var shared = require('@edgestore/shared');
6
+ var utils = require('../../utils-5819d5e1.js');
7
+ var providers_edgestore_index = require('../../providers/edgestore/index.js');
8
+ var shared$1 = require('../../shared-7c700083.js');
9
+ require('../../index-4491caf0.js');
10
+ require('@panva/hkdf');
11
+ require('cookie');
12
+ require('jose');
13
+ require('uuid');
14
+
15
+ // Helper to extract a cookie from the request's cookie header
16
+ function getCookie(req, cookieName) {
17
+ const cookieHeader = req.headers.get('cookie');
18
+ if (!cookieHeader) return undefined;
19
+ return cookieHeader.split(';').map((cookieStr)=>cookieStr.trim()).reduce((acc, cookieStr)=>{
20
+ const [name, ...rest] = cookieStr.split('=');
21
+ if (name && rest.length > 0) {
22
+ acc[name] = rest.join('=');
23
+ }
24
+ return acc;
25
+ }, {})[cookieName];
26
+ }
27
+ function createEdgeStoreStartHandler(config) {
28
+ const { provider = providers_edgestore_index.EdgeStoreProvider() } = config;
29
+ const log = new utils.Logger(config.logLevel);
30
+ globalThis._EDGE_STORE_LOGGER = log;
31
+ log.debug('Creating Edge Store TanStack Start handler');
32
+ return async ({ request })=>{
33
+ try {
34
+ const { pathname } = new URL(request.url);
35
+ if (utils.matchPath(pathname, '/health')) {
36
+ return new Response('OK', {
37
+ status: 200
38
+ });
39
+ } else if (utils.matchPath(pathname, '/init')) {
40
+ let ctx = {};
41
+ try {
42
+ ctx = 'createContext' in config ? await config.createContext({
43
+ req: request
44
+ }) : {};
45
+ } catch (err) {
46
+ throw new shared.EdgeStoreError({
47
+ message: 'Error creating context',
48
+ code: 'CREATE_CONTEXT_ERROR',
49
+ cause: err instanceof Error ? err : undefined
50
+ });
51
+ }
52
+ const { newCookies, token, baseUrl } = await shared$1.init({
53
+ ctx,
54
+ provider,
55
+ router: config.router
56
+ });
57
+ const headers = new Headers();
58
+ newCookies.forEach((cookie)=>headers.append('Set-Cookie', cookie));
59
+ headers.set('Content-Type', 'application/json');
60
+ return new Response(JSON.stringify({
61
+ token,
62
+ baseUrl
63
+ }), {
64
+ status: 200,
65
+ headers
66
+ });
67
+ } else if (utils.matchPath(pathname, '/request-upload')) {
68
+ const body = await request.json();
69
+ const ctxToken = getCookie(request, 'edgestore-ctx');
70
+ const result = await shared$1.requestUpload({
71
+ provider,
72
+ router: config.router,
73
+ body: body,
74
+ ctxToken
75
+ });
76
+ return new Response(JSON.stringify(result), {
77
+ status: 200,
78
+ headers: {
79
+ 'Content-Type': 'application/json'
80
+ }
81
+ });
82
+ } else if (utils.matchPath(pathname, '/request-upload-parts')) {
83
+ const body = await request.json();
84
+ const ctxToken = getCookie(request, 'edgestore-ctx');
85
+ const result = await shared$1.requestUploadParts({
86
+ provider,
87
+ router: config.router,
88
+ body: body,
89
+ ctxToken
90
+ });
91
+ return new Response(JSON.stringify(result), {
92
+ status: 200,
93
+ headers: {
94
+ 'Content-Type': 'application/json'
95
+ }
96
+ });
97
+ } else if (utils.matchPath(pathname, '/complete-multipart-upload')) {
98
+ const body = await request.json();
99
+ const ctxToken = getCookie(request, 'edgestore-ctx');
100
+ await shared$1.completeMultipartUpload({
101
+ provider,
102
+ router: config.router,
103
+ body: body,
104
+ ctxToken
105
+ });
106
+ return new Response(null, {
107
+ status: 200
108
+ });
109
+ } else if (utils.matchPath(pathname, '/confirm-upload')) {
110
+ const body = await request.json();
111
+ const ctxToken = getCookie(request, 'edgestore-ctx');
112
+ const result = await shared$1.confirmUpload({
113
+ provider,
114
+ router: config.router,
115
+ body: body,
116
+ ctxToken
117
+ });
118
+ return new Response(JSON.stringify(result), {
119
+ status: 200,
120
+ headers: {
121
+ 'Content-Type': 'application/json'
122
+ }
123
+ });
124
+ } else if (utils.matchPath(pathname, '/delete-file')) {
125
+ const body = await request.json();
126
+ const ctxToken = getCookie(request, 'edgestore-ctx');
127
+ const result = await shared$1.deleteFile({
128
+ provider,
129
+ router: config.router,
130
+ body: body,
131
+ ctxToken
132
+ });
133
+ return new Response(JSON.stringify(result), {
134
+ status: 200,
135
+ headers: {
136
+ 'Content-Type': 'application/json'
137
+ }
138
+ });
139
+ } else if (utils.matchPath(pathname, '/proxy-file')) {
140
+ const urlParam = new URL(request.url).searchParams.get('url');
141
+ if (typeof urlParam === 'string') {
142
+ const proxyRes = await fetch(urlParam, {
143
+ headers: {
144
+ cookie: request.headers.get('cookie') ?? ''
145
+ }
146
+ });
147
+ const data = await proxyRes.arrayBuffer();
148
+ const headers = new Headers();
149
+ headers.set('Content-Type', proxyRes.headers.get('Content-Type') ?? 'application/octet-stream');
150
+ return new Response(data, {
151
+ status: proxyRes.status,
152
+ headers
153
+ });
154
+ } else {
155
+ return new Response(null, {
156
+ status: 400
157
+ });
158
+ }
159
+ } else {
160
+ return new Response(null, {
161
+ status: 404
162
+ });
163
+ }
164
+ } catch (err) {
165
+ if (err instanceof shared.EdgeStoreError) {
166
+ log[err.level](err.formattedMessage());
167
+ if (err.cause) log[err.level](err.cause);
168
+ const status = shared.EDGE_STORE_ERROR_CODES[err.code] || 500;
169
+ return new Response(JSON.stringify(err.formattedJson()), {
170
+ status,
171
+ headers: {
172
+ 'Content-Type': 'application/json'
173
+ }
174
+ });
175
+ }
176
+ log.error(err);
177
+ return new Response(JSON.stringify(new shared.EdgeStoreError({
178
+ message: 'Internal server error',
179
+ code: 'SERVER_ERROR'
180
+ }).formattedJson()), {
181
+ status: 500,
182
+ headers: {
183
+ 'Content-Type': 'application/json'
184
+ }
185
+ });
186
+ }
187
+ };
188
+ }
189
+
190
+ exports.createEdgeStoreStartHandler = createEdgeStoreStartHandler;
@@ -0,0 +1,186 @@
1
+ import { EdgeStoreError, EDGE_STORE_ERROR_CODES } from '@edgestore/shared';
2
+ import { L as Logger, m as matchPath } from '../../utils-f6f56d38.mjs';
3
+ import { EdgeStoreProvider } from '../../providers/edgestore/index.mjs';
4
+ import { i as init, r as requestUpload, a as requestUploadParts, c as completeMultipartUpload, d as confirmUpload, e as deleteFile } from '../../shared-039276af.mjs';
5
+ import '../../index-28efdacf.mjs';
6
+ import '@panva/hkdf';
7
+ import 'cookie';
8
+ import 'jose';
9
+ import 'uuid';
10
+
11
+ // Helper to extract a cookie from the request's cookie header
12
+ function getCookie(req, cookieName) {
13
+ const cookieHeader = req.headers.get('cookie');
14
+ if (!cookieHeader) return undefined;
15
+ return cookieHeader.split(';').map((cookieStr)=>cookieStr.trim()).reduce((acc, cookieStr)=>{
16
+ const [name, ...rest] = cookieStr.split('=');
17
+ if (name && rest.length > 0) {
18
+ acc[name] = rest.join('=');
19
+ }
20
+ return acc;
21
+ }, {})[cookieName];
22
+ }
23
+ function createEdgeStoreStartHandler(config) {
24
+ const { provider = EdgeStoreProvider() } = config;
25
+ const log = new Logger(config.logLevel);
26
+ globalThis._EDGE_STORE_LOGGER = log;
27
+ log.debug('Creating Edge Store TanStack Start handler');
28
+ return async ({ request })=>{
29
+ try {
30
+ const { pathname } = new URL(request.url);
31
+ if (matchPath(pathname, '/health')) {
32
+ return new Response('OK', {
33
+ status: 200
34
+ });
35
+ } else if (matchPath(pathname, '/init')) {
36
+ let ctx = {};
37
+ try {
38
+ ctx = 'createContext' in config ? await config.createContext({
39
+ req: request
40
+ }) : {};
41
+ } catch (err) {
42
+ throw new EdgeStoreError({
43
+ message: 'Error creating context',
44
+ code: 'CREATE_CONTEXT_ERROR',
45
+ cause: err instanceof Error ? err : undefined
46
+ });
47
+ }
48
+ const { newCookies, token, baseUrl } = await init({
49
+ ctx,
50
+ provider,
51
+ router: config.router
52
+ });
53
+ const headers = new Headers();
54
+ newCookies.forEach((cookie)=>headers.append('Set-Cookie', cookie));
55
+ headers.set('Content-Type', 'application/json');
56
+ return new Response(JSON.stringify({
57
+ token,
58
+ baseUrl
59
+ }), {
60
+ status: 200,
61
+ headers
62
+ });
63
+ } else if (matchPath(pathname, '/request-upload')) {
64
+ const body = await request.json();
65
+ const ctxToken = getCookie(request, 'edgestore-ctx');
66
+ const result = await requestUpload({
67
+ provider,
68
+ router: config.router,
69
+ body: body,
70
+ ctxToken
71
+ });
72
+ return new Response(JSON.stringify(result), {
73
+ status: 200,
74
+ headers: {
75
+ 'Content-Type': 'application/json'
76
+ }
77
+ });
78
+ } else if (matchPath(pathname, '/request-upload-parts')) {
79
+ const body = await request.json();
80
+ const ctxToken = getCookie(request, 'edgestore-ctx');
81
+ const result = await requestUploadParts({
82
+ provider,
83
+ router: config.router,
84
+ body: body,
85
+ ctxToken
86
+ });
87
+ return new Response(JSON.stringify(result), {
88
+ status: 200,
89
+ headers: {
90
+ 'Content-Type': 'application/json'
91
+ }
92
+ });
93
+ } else if (matchPath(pathname, '/complete-multipart-upload')) {
94
+ const body = await request.json();
95
+ const ctxToken = getCookie(request, 'edgestore-ctx');
96
+ await completeMultipartUpload({
97
+ provider,
98
+ router: config.router,
99
+ body: body,
100
+ ctxToken
101
+ });
102
+ return new Response(null, {
103
+ status: 200
104
+ });
105
+ } else if (matchPath(pathname, '/confirm-upload')) {
106
+ const body = await request.json();
107
+ const ctxToken = getCookie(request, 'edgestore-ctx');
108
+ const result = await confirmUpload({
109
+ provider,
110
+ router: config.router,
111
+ body: body,
112
+ ctxToken
113
+ });
114
+ return new Response(JSON.stringify(result), {
115
+ status: 200,
116
+ headers: {
117
+ 'Content-Type': 'application/json'
118
+ }
119
+ });
120
+ } else if (matchPath(pathname, '/delete-file')) {
121
+ const body = await request.json();
122
+ const ctxToken = getCookie(request, 'edgestore-ctx');
123
+ const result = await deleteFile({
124
+ provider,
125
+ router: config.router,
126
+ body: body,
127
+ ctxToken
128
+ });
129
+ return new Response(JSON.stringify(result), {
130
+ status: 200,
131
+ headers: {
132
+ 'Content-Type': 'application/json'
133
+ }
134
+ });
135
+ } else if (matchPath(pathname, '/proxy-file')) {
136
+ const urlParam = new URL(request.url).searchParams.get('url');
137
+ if (typeof urlParam === 'string') {
138
+ const proxyRes = await fetch(urlParam, {
139
+ headers: {
140
+ cookie: request.headers.get('cookie') ?? ''
141
+ }
142
+ });
143
+ const data = await proxyRes.arrayBuffer();
144
+ const headers = new Headers();
145
+ headers.set('Content-Type', proxyRes.headers.get('Content-Type') ?? 'application/octet-stream');
146
+ return new Response(data, {
147
+ status: proxyRes.status,
148
+ headers
149
+ });
150
+ } else {
151
+ return new Response(null, {
152
+ status: 400
153
+ });
154
+ }
155
+ } else {
156
+ return new Response(null, {
157
+ status: 404
158
+ });
159
+ }
160
+ } catch (err) {
161
+ if (err instanceof EdgeStoreError) {
162
+ log[err.level](err.formattedMessage());
163
+ if (err.cause) log[err.level](err.cause);
164
+ const status = EDGE_STORE_ERROR_CODES[err.code] || 500;
165
+ return new Response(JSON.stringify(err.formattedJson()), {
166
+ status,
167
+ headers: {
168
+ 'Content-Type': 'application/json'
169
+ }
170
+ });
171
+ }
172
+ log.error(err);
173
+ return new Response(JSON.stringify(new EdgeStoreError({
174
+ message: 'Internal server error',
175
+ code: 'SERVER_ERROR'
176
+ }).formattedJson()), {
177
+ status: 500,
178
+ headers: {
179
+ 'Content-Type': 'application/json'
180
+ }
181
+ });
182
+ }
183
+ };
184
+ }
185
+
186
+ export { createEdgeStoreStartHandler };
@@ -24,6 +24,17 @@ export type AWSProviderOptions = {
24
24
  * Can also be set via the `ES_AWS_BUCKET_NAME` environment variable.
25
25
  */
26
26
  bucketName?: string;
27
+ /**
28
+ * Custom endpoint for S3-compatible storage providers (e.g., MinIO).
29
+ * Can also be set via the `ES_AWS_ENDPOINT` environment variable.
30
+ */
31
+ endpoint?: string;
32
+ /**
33
+ * Force path style for S3-compatible storage providers.
34
+ * Can also be set via the `ES_AWS_FORCE_PATH_STYLE` environment variable.
35
+ * Defaults to false for AWS S3, but should be true for most S3-compatible providers.
36
+ */
37
+ forcePathStyle?: boolean;
27
38
  /**
28
39
  * Base URL to use for accessing files.
29
40
  * Only needed if you are using a custom domain or cloudfront.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/aws/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAGlD,MAAM,MAAM,kBAAkB,GAAG;IAC/B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,QAAQ,CAqGlE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/aws/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAGlD,MAAM,MAAM,kBAAkB,GAAG;IAC/B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,QAAQ,CA8GlE"}
@@ -7,15 +7,17 @@ var s3RequestPresigner = require('@aws-sdk/s3-request-presigner');
7
7
  var uuid = require('uuid');
8
8
 
9
9
  function AWSProvider(options) {
10
- const { accessKeyId = process.env.ES_AWS_ACCESS_KEY_ID, secretAccessKey = process.env.ES_AWS_SECRET_ACCESS_KEY, region = process.env.ES_AWS_REGION, bucketName = process.env.ES_AWS_BUCKET_NAME } = options ?? {};
11
- const baseUrl = options?.baseUrl ?? process.env.EDGE_STORE_BASE_URL ?? `https://${bucketName}.s3.${region}.amazonaws.com`;
10
+ const { accessKeyId = process.env.ES_AWS_ACCESS_KEY_ID, secretAccessKey = process.env.ES_AWS_SECRET_ACCESS_KEY, region = process.env.ES_AWS_REGION, bucketName = process.env.ES_AWS_BUCKET_NAME, endpoint = process.env.ES_AWS_ENDPOINT, forcePathStyle = process.env.ES_AWS_FORCE_PATH_STYLE === 'true' } = options ?? {};
11
+ const baseUrl = options?.baseUrl ?? process.env.EDGE_STORE_BASE_URL ?? (endpoint ? `${endpoint}/${bucketName}` : `https://${bucketName}.s3.${region}.amazonaws.com`);
12
12
  const credentials = accessKeyId && secretAccessKey ? {
13
13
  accessKeyId,
14
14
  secretAccessKey
15
15
  } : undefined;
16
16
  const s3Client = new clientS3.S3Client({
17
17
  region,
18
- credentials
18
+ credentials,
19
+ endpoint,
20
+ forcePathStyle
19
21
  });
20
22
  return {
21
23
  async init () {
@@ -3,15 +3,17 @@ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
3
3
  import { v4 } from 'uuid';
4
4
 
5
5
  function AWSProvider(options) {
6
- const { accessKeyId = process.env.ES_AWS_ACCESS_KEY_ID, secretAccessKey = process.env.ES_AWS_SECRET_ACCESS_KEY, region = process.env.ES_AWS_REGION, bucketName = process.env.ES_AWS_BUCKET_NAME } = options ?? {};
7
- const baseUrl = options?.baseUrl ?? process.env.EDGE_STORE_BASE_URL ?? `https://${bucketName}.s3.${region}.amazonaws.com`;
6
+ const { accessKeyId = process.env.ES_AWS_ACCESS_KEY_ID, secretAccessKey = process.env.ES_AWS_SECRET_ACCESS_KEY, region = process.env.ES_AWS_REGION, bucketName = process.env.ES_AWS_BUCKET_NAME, endpoint = process.env.ES_AWS_ENDPOINT, forcePathStyle = process.env.ES_AWS_FORCE_PATH_STYLE === 'true' } = options ?? {};
7
+ const baseUrl = options?.baseUrl ?? process.env.EDGE_STORE_BASE_URL ?? (endpoint ? `${endpoint}/${bucketName}` : `https://${bucketName}.s3.${region}.amazonaws.com`);
8
8
  const credentials = accessKeyId && secretAccessKey ? {
9
9
  accessKeyId,
10
10
  secretAccessKey
11
11
  } : undefined;
12
12
  const s3Client = new S3Client({
13
13
  region,
14
- credentials
14
+ credentials,
15
+ endpoint,
16
+ forcePathStyle
15
17
  });
16
18
  return {
17
19
  async init () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edgestore/server",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Upload files with ease from React/Next.js",
5
5
  "homepage": "https://edgestore.dev",
6
6
  "repository": "https://github.com/edgestorejs/edgestore.git",
@@ -52,6 +52,11 @@
52
52
  "require": "./dist/adapters/next/app/index.js",
53
53
  "default": "./dist/adapters/next/app/index.js"
54
54
  },
55
+ "./adapters/start": {
56
+ "import": "./dist/adapters/start/index.mjs",
57
+ "require": "./dist/adapters/start/index.js",
58
+ "default": "./dist/adapters/start/index.js"
59
+ },
55
60
  "./providers/aws": {
56
61
  "import": "./dist/providers/aws/index.mjs",
57
62
  "require": "./dist/providers/aws/index.js",
@@ -84,7 +89,7 @@
84
89
  },
85
90
  "license": "MIT",
86
91
  "dependencies": {
87
- "@edgestore/shared": "0.3.1",
92
+ "@edgestore/shared": "0.3.3",
88
93
  "@panva/hkdf": "^1.0.4",
89
94
  "cookie": "^0.5.0",
90
95
  "jose": "^4.13.1",
@@ -123,5 +128,5 @@
123
128
  "typescript": "^5.1.6",
124
129
  "zod": "3.21.4"
125
130
  },
126
- "gitHead": "26e3c5e08530d1b6b6363099ddf3062278673931"
131
+ "gitHead": "9bc2c36ee2463f79c26cb39cfc69dc77b7569a21"
127
132
  }
@@ -0,0 +1,208 @@
1
+ import {
2
+ EDGE_STORE_ERROR_CODES,
3
+ EdgeStoreError,
4
+ type EdgeStoreErrorCodeKey,
5
+ type EdgeStoreRouter,
6
+ type MaybePromise,
7
+ type Provider,
8
+ } from '@edgestore/shared';
9
+ import Logger, { type LogLevel } from '../../libs/logger';
10
+ import { matchPath } from '../../libs/utils';
11
+ import { EdgeStoreProvider } from '../../providers/edgestore';
12
+ import {
13
+ completeMultipartUpload,
14
+ confirmUpload,
15
+ deleteFile,
16
+ init,
17
+ requestUpload,
18
+ requestUploadParts,
19
+ type CompleteMultipartUploadBody,
20
+ type ConfirmUploadBody,
21
+ type DeleteFileBody,
22
+ type RequestUploadBody,
23
+ type RequestUploadPartsParams,
24
+ } from '../shared';
25
+
26
+ export type CreateContextOptions = {
27
+ req: Request;
28
+ };
29
+
30
+ export type Config<TCtx> = {
31
+ provider?: Provider;
32
+ router: EdgeStoreRouter<TCtx>;
33
+ logLevel?: LogLevel;
34
+ } & (TCtx extends Record<string, never>
35
+ ? object
36
+ : {
37
+ provider?: Provider;
38
+ router: EdgeStoreRouter<TCtx>;
39
+ createContext: (opts: CreateContextOptions) => MaybePromise<TCtx>;
40
+ });
41
+
42
+ declare const globalThis: {
43
+ _EDGE_STORE_LOGGER: Logger;
44
+ };
45
+
46
+ // Helper to extract a cookie from the request's cookie header
47
+ function getCookie(req: Request, cookieName: string): string | undefined {
48
+ const cookieHeader = req.headers.get('cookie');
49
+ if (!cookieHeader) return undefined;
50
+ return cookieHeader
51
+ .split(';')
52
+ .map((cookieStr) => cookieStr.trim())
53
+ .reduce((acc: Record<string, string>, cookieStr) => {
54
+ const [name, ...rest] = cookieStr.split('=');
55
+ if (name && rest.length > 0) {
56
+ acc[name] = rest.join('=');
57
+ }
58
+ return acc;
59
+ }, {})[cookieName];
60
+ }
61
+
62
+ export function createEdgeStoreStartHandler<TCtx>(config: Config<TCtx>) {
63
+ const { provider = EdgeStoreProvider() } = config;
64
+ const log = new Logger(config.logLevel);
65
+ globalThis._EDGE_STORE_LOGGER = log;
66
+ log.debug('Creating Edge Store TanStack Start handler');
67
+
68
+ return async ({ request }: { request: Request }) => {
69
+ try {
70
+ const { pathname } = new URL(request.url);
71
+ if (matchPath(pathname, '/health')) {
72
+ return new Response('OK', { status: 200 });
73
+ } else if (matchPath(pathname, '/init')) {
74
+ let ctx = {} as TCtx;
75
+ try {
76
+ ctx =
77
+ 'createContext' in config
78
+ ? await config.createContext({ req: request })
79
+ : ({} as TCtx);
80
+ } catch (err) {
81
+ throw new EdgeStoreError({
82
+ message: 'Error creating context',
83
+ code: 'CREATE_CONTEXT_ERROR',
84
+ cause: err instanceof Error ? err : undefined,
85
+ });
86
+ }
87
+ const { newCookies, token, baseUrl } = await init({
88
+ ctx,
89
+ provider,
90
+ router: config.router,
91
+ });
92
+ const headers = new Headers();
93
+ newCookies.forEach((cookie) => headers.append('Set-Cookie', cookie));
94
+ headers.set('Content-Type', 'application/json');
95
+ return new Response(JSON.stringify({ token, baseUrl }), {
96
+ status: 200,
97
+ headers,
98
+ });
99
+ } else if (matchPath(pathname, '/request-upload')) {
100
+ const body = await request.json();
101
+ const ctxToken = getCookie(request, 'edgestore-ctx');
102
+ const result = await requestUpload({
103
+ provider,
104
+ router: config.router,
105
+ body: body as RequestUploadBody,
106
+ ctxToken,
107
+ });
108
+ return new Response(JSON.stringify(result), {
109
+ status: 200,
110
+ headers: { 'Content-Type': 'application/json' },
111
+ });
112
+ } else if (matchPath(pathname, '/request-upload-parts')) {
113
+ const body = await request.json();
114
+ const ctxToken = getCookie(request, 'edgestore-ctx');
115
+ const result = await requestUploadParts({
116
+ provider,
117
+ router: config.router,
118
+ body: body as RequestUploadPartsParams,
119
+ ctxToken,
120
+ });
121
+ return new Response(JSON.stringify(result), {
122
+ status: 200,
123
+ headers: { 'Content-Type': 'application/json' },
124
+ });
125
+ } else if (matchPath(pathname, '/complete-multipart-upload')) {
126
+ const body = await request.json();
127
+ const ctxToken = getCookie(request, 'edgestore-ctx');
128
+ await completeMultipartUpload({
129
+ provider,
130
+ router: config.router,
131
+ body: body as CompleteMultipartUploadBody,
132
+ ctxToken,
133
+ });
134
+ return new Response(null, { status: 200 });
135
+ } else if (matchPath(pathname, '/confirm-upload')) {
136
+ const body = await request.json();
137
+ const ctxToken = getCookie(request, 'edgestore-ctx');
138
+ const result = await confirmUpload({
139
+ provider,
140
+ router: config.router,
141
+ body: body as ConfirmUploadBody,
142
+ ctxToken,
143
+ });
144
+ return new Response(JSON.stringify(result), {
145
+ status: 200,
146
+ headers: { 'Content-Type': 'application/json' },
147
+ });
148
+ } else if (matchPath(pathname, '/delete-file')) {
149
+ const body = await request.json();
150
+ const ctxToken = getCookie(request, 'edgestore-ctx');
151
+ const result = await deleteFile({
152
+ provider,
153
+ router: config.router,
154
+ body: body as DeleteFileBody,
155
+ ctxToken,
156
+ });
157
+ return new Response(JSON.stringify(result), {
158
+ status: 200,
159
+ headers: { 'Content-Type': 'application/json' },
160
+ });
161
+ } else if (matchPath(pathname, '/proxy-file')) {
162
+ const urlParam = new URL(request.url).searchParams.get('url');
163
+ if (typeof urlParam === 'string') {
164
+ const proxyRes = await fetch(urlParam, {
165
+ headers: {
166
+ cookie: request.headers.get('cookie') ?? '',
167
+ },
168
+ });
169
+ const data = await proxyRes.arrayBuffer();
170
+ const headers = new Headers();
171
+ headers.set(
172
+ 'Content-Type',
173
+ proxyRes.headers.get('Content-Type') ?? 'application/octet-stream',
174
+ );
175
+ return new Response(data, { status: proxyRes.status, headers });
176
+ } else {
177
+ return new Response(null, { status: 400 });
178
+ }
179
+ } else {
180
+ return new Response(null, { status: 404 });
181
+ }
182
+ } catch (err) {
183
+ if (err instanceof EdgeStoreError) {
184
+ log[err.level](err.formattedMessage());
185
+ if (err.cause) log[err.level](err.cause);
186
+ const status =
187
+ EDGE_STORE_ERROR_CODES[err.code as EdgeStoreErrorCodeKey] || 500;
188
+ return new Response(JSON.stringify(err.formattedJson()), {
189
+ status,
190
+ headers: { 'Content-Type': 'application/json' },
191
+ });
192
+ }
193
+ log.error(err);
194
+ return new Response(
195
+ JSON.stringify(
196
+ new EdgeStoreError({
197
+ message: 'Internal server error',
198
+ code: 'SERVER_ERROR',
199
+ }).formattedJson(),
200
+ ),
201
+ {
202
+ status: 500,
203
+ headers: { 'Content-Type': 'application/json' },
204
+ },
205
+ );
206
+ }
207
+ };
208
+ }
@@ -33,6 +33,17 @@ export type AWSProviderOptions = {
33
33
  * Can also be set via the `ES_AWS_BUCKET_NAME` environment variable.
34
34
  */
35
35
  bucketName?: string;
36
+ /**
37
+ * Custom endpoint for S3-compatible storage providers (e.g., MinIO).
38
+ * Can also be set via the `ES_AWS_ENDPOINT` environment variable.
39
+ */
40
+ endpoint?: string;
41
+ /**
42
+ * Force path style for S3-compatible storage providers.
43
+ * Can also be set via the `ES_AWS_FORCE_PATH_STYLE` environment variable.
44
+ * Defaults to false for AWS S3, but should be true for most S3-compatible providers.
45
+ */
46
+ forcePathStyle?: boolean;
36
47
  /**
37
48
  * Base URL to use for accessing files.
38
49
  * Only needed if you are using a custom domain or cloudfront.
@@ -55,12 +66,16 @@ export function AWSProvider(options?: AWSProviderOptions): Provider {
55
66
  secretAccessKey = process.env.ES_AWS_SECRET_ACCESS_KEY,
56
67
  region = process.env.ES_AWS_REGION,
57
68
  bucketName = process.env.ES_AWS_BUCKET_NAME,
69
+ endpoint = process.env.ES_AWS_ENDPOINT,
70
+ forcePathStyle = process.env.ES_AWS_FORCE_PATH_STYLE === 'true',
58
71
  } = options ?? {};
59
72
 
60
73
  const baseUrl =
61
74
  options?.baseUrl ??
62
75
  process.env.EDGE_STORE_BASE_URL ??
63
- `https://${bucketName}.s3.${region}.amazonaws.com`;
76
+ (endpoint
77
+ ? `${endpoint}/${bucketName}`
78
+ : `https://${bucketName}.s3.${region}.amazonaws.com`);
64
79
 
65
80
  const credentials =
66
81
  accessKeyId && secretAccessKey
@@ -69,7 +84,12 @@ export function AWSProvider(options?: AWSProviderOptions): Provider {
69
84
  secretAccessKey,
70
85
  }
71
86
  : undefined;
72
- const s3Client = new S3Client({ region, credentials });
87
+ const s3Client = new S3Client({
88
+ region,
89
+ credentials,
90
+ endpoint,
91
+ forcePathStyle,
92
+ });
73
93
 
74
94
  return {
75
95
  async init() {