@uploadista/server 0.0.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.
- package/.turbo/turbo-build.log +5 -0
- package/.turbo/turbo-check.log +34 -0
- package/LICENSE +21 -0
- package/README.md +503 -0
- package/dist/auth/cache.d.ts +87 -0
- package/dist/auth/cache.d.ts.map +1 -0
- package/dist/auth/cache.js +121 -0
- package/dist/auth/cache.test.d.ts +2 -0
- package/dist/auth/cache.test.d.ts.map +1 -0
- package/dist/auth/cache.test.js +209 -0
- package/dist/auth/get-auth-credentials.d.ts +73 -0
- package/dist/auth/get-auth-credentials.d.ts.map +1 -0
- package/dist/auth/get-auth-credentials.js +55 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +1 -0
- package/dist/auth/jwt/index.d.ts +38 -0
- package/dist/auth/jwt/index.d.ts.map +1 -0
- package/dist/auth/jwt/index.js +36 -0
- package/dist/auth/jwt/types.d.ts +77 -0
- package/dist/auth/jwt/types.d.ts.map +1 -0
- package/dist/auth/jwt/types.js +1 -0
- package/dist/auth/jwt/validate.d.ts +58 -0
- package/dist/auth/jwt/validate.d.ts.map +1 -0
- package/dist/auth/jwt/validate.js +226 -0
- package/dist/auth/jwt/validate.test.d.ts +2 -0
- package/dist/auth/jwt/validate.test.d.ts.map +1 -0
- package/dist/auth/jwt/validate.test.js +492 -0
- package/dist/auth/service.d.ts +63 -0
- package/dist/auth/service.d.ts.map +1 -0
- package/dist/auth/service.js +43 -0
- package/dist/auth/service.test.d.ts +2 -0
- package/dist/auth/service.test.d.ts.map +1 -0
- package/dist/auth/service.test.js +195 -0
- package/dist/auth/types.d.ts +38 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +1 -0
- package/dist/cache.d.ts +87 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +121 -0
- package/dist/cache.test.d.ts +2 -0
- package/dist/cache.test.d.ts.map +1 -0
- package/dist/cache.test.js +209 -0
- package/dist/cloudflare-config.d.ts +72 -0
- package/dist/cloudflare-config.d.ts.map +1 -0
- package/dist/cloudflare-config.js +67 -0
- package/dist/error-types.d.ts +138 -0
- package/dist/error-types.d.ts.map +1 -0
- package/dist/error-types.js +155 -0
- package/dist/hono-adapter.d.ts +48 -0
- package/dist/hono-adapter.d.ts.map +1 -0
- package/dist/hono-adapter.js +58 -0
- package/dist/http-utils.d.ts +148 -0
- package/dist/http-utils.d.ts.map +1 -0
- package/dist/http-utils.js +233 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/layer-utils.d.ts +121 -0
- package/dist/layer-utils.d.ts.map +1 -0
- package/dist/layer-utils.js +80 -0
- package/dist/metrics/service.d.ts +26 -0
- package/dist/metrics/service.d.ts.map +1 -0
- package/dist/metrics/service.js +20 -0
- package/dist/plugins-typing.d.ts +11 -0
- package/dist/plugins-typing.d.ts.map +1 -0
- package/dist/plugins-typing.js +1 -0
- package/dist/service.d.ts +63 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +43 -0
- package/dist/service.test.d.ts +2 -0
- package/dist/service.test.d.ts.map +1 -0
- package/dist/service.test.js +195 -0
- package/dist/types.d.ts +38 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +47 -0
- package/src/auth/get-auth-credentials.ts +97 -0
- package/src/auth/index.ts +1 -0
- package/src/cache.test.ts +306 -0
- package/src/cache.ts +204 -0
- package/src/error-types.ts +172 -0
- package/src/http-utils.ts +264 -0
- package/src/index.ts +8 -0
- package/src/layer-utils.ts +184 -0
- package/src/plugins-typing.ts +57 -0
- package/src/service.test.ts +275 -0
- package/src/service.ts +78 -0
- package/src/types.ts +40 -0
- package/tsconfig.json +13 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
|
|
2
|
+
> @uploadista/server@ check /Users/denislaboureyras/Documents/uploadista/dev/uploadista/packages/uploadista/servers/server
|
|
3
|
+
> biome check --write ./src
|
|
4
|
+
|
|
5
|
+
src/cloudflare-config.ts:15:55 lint/suspicious/noExplicitAny ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6
|
+
|
|
7
|
+
! Unexpected any. Specify a different type.
|
|
8
|
+
|
|
9
|
+
13 │ STORAGE_ENCRYPTION_KEY: string;
|
|
10
|
+
14 │ STORAGE_ENCRYPTION_IV_LENGTH: string;
|
|
11
|
+
> 15 │ UPLOAD_EVENT_DURABLE_OBJECT: DurableObjectNamespace<any>;
|
|
12
|
+
│ ^^^
|
|
13
|
+
16 │ };
|
|
14
|
+
17 │
|
|
15
|
+
|
|
16
|
+
i any disables many type checking rules. Its use should be avoided.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
src/cloudflare-config.ts:31:16 lint/suspicious/noExplicitAny ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
20
|
+
|
|
21
|
+
! Unexpected any. Specify a different type.
|
|
22
|
+
|
|
23
|
+
29 │ storageIvLength?: string;
|
|
24
|
+
30 │ },
|
|
25
|
+
> 31 │ ) => Promise<any>;
|
|
26
|
+
│ ^^^
|
|
27
|
+
32 │ };
|
|
28
|
+
33 │
|
|
29
|
+
|
|
30
|
+
Checked 2 files in 36ms. No fixes applied.
|
|
31
|
+
Found 2 warnings.
|
|
32
|
+
i any disables many type checking rules. Its use should be avoided.
|
|
33
|
+
|
|
34
|
+
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 uploadista
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
# @uploadista/server
|
|
2
|
+
|
|
3
|
+
Core server utilities and authentication for Uploadista file upload and flow processing.
|
|
4
|
+
|
|
5
|
+
This package provides framework-agnostic server components including authentication context management, caching utilities, and layer composition helpers. Use this with adapter packages (`@uploadista/adapters-hono`, `@uploadista/adapters-express`, `@uploadista/adapters-fastify`) to set up complete upload servers.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Authentication Context** - User identity and metadata management
|
|
10
|
+
- **Auth Caching** - LRU cache for auth contexts with TTL support
|
|
11
|
+
- **Effect Layers** - Dependency injection for upload and flow servers
|
|
12
|
+
- **Error Handling** - Standardized error responses with HTTP status codes
|
|
13
|
+
- **HTTP Utilities** - Route parsing and error mapping helpers
|
|
14
|
+
- **TypeScript** - Full type safety with comprehensive JSDoc
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @uploadista/server @uploadista/core
|
|
20
|
+
# or
|
|
21
|
+
pnpm add @uploadista/server @uploadista/core
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Requirements
|
|
25
|
+
|
|
26
|
+
- Node.js 18+
|
|
27
|
+
- TypeScript 5.0+ (optional but recommended)
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### 1. Set Up Authentication
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { AuthContextServiceLive } from "@uploadista/server";
|
|
35
|
+
import { Effect } from "effect";
|
|
36
|
+
|
|
37
|
+
// Create auth context for a request
|
|
38
|
+
const authContext = {
|
|
39
|
+
clientId: "user-123",
|
|
40
|
+
metadata: {
|
|
41
|
+
permissions: ["upload:create", "flow:execute"],
|
|
42
|
+
quota: { storage: 1000000000 }, // 1GB
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Provide auth context to your effects
|
|
47
|
+
const effect = Effect.service(AuthContextService).pipe(
|
|
48
|
+
Effect.andThen((authService) => authService.getClientId()),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const result = await Effect.runPromise(
|
|
52
|
+
effect.pipe(Effect.provide(AuthContextServiceLive(authContext))),
|
|
53
|
+
);
|
|
54
|
+
console.log(result); // "user-123"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 2. Get JWT Credentials
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { getAuthCredentials } from "@uploadista/server";
|
|
61
|
+
|
|
62
|
+
// Exchange credentials for JWT token
|
|
63
|
+
const response = await getAuthCredentials({
|
|
64
|
+
uploadistaClientId: process.env.UPLOADISTA_CLIENT_ID,
|
|
65
|
+
uploadistaApiKey: process.env.UPLOADISTA_API_KEY,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (response.isValid) {
|
|
69
|
+
console.log(`Token: ${response.data.token}`);
|
|
70
|
+
console.log(`Expires in: ${response.data.expiresIn}s`);
|
|
71
|
+
} else {
|
|
72
|
+
console.error(`Auth failed: ${response.error}`);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 3. Create Upload Server Layer
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { createUploadServerLayer } from "@uploadista/server";
|
|
80
|
+
import { redisKvStore } from "@uploadista/kv-store-redis";
|
|
81
|
+
import { s3DataStore } from "@uploadista/data-store-s3";
|
|
82
|
+
import { webSocketEventEmitter } from "@uploadista/event-emitter-websocket";
|
|
83
|
+
import { memoryEventBroadcaster } from "@uploadista/event-broadcaster-memory";
|
|
84
|
+
|
|
85
|
+
const uploadServerLayer = createUploadServerLayer({
|
|
86
|
+
kvStore: redisKvStore,
|
|
87
|
+
eventEmitter: webSocketEventEmitter,
|
|
88
|
+
dataStore: s3DataStore,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Use in your framework adapter...
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 4. Create Flow Server Layer
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { createFlowServerLayer } from "@uploadista/server";
|
|
98
|
+
|
|
99
|
+
const flowServerLayer = createFlowServerLayer({
|
|
100
|
+
kvStore: redisKvStore,
|
|
101
|
+
eventEmitter: webSocketEventEmitter,
|
|
102
|
+
flowProvider: createFlowsEffect,
|
|
103
|
+
uploadServer: uploadServerLayer,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Use in your framework adapter...
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## API Reference
|
|
110
|
+
|
|
111
|
+
### Authentication
|
|
112
|
+
|
|
113
|
+
#### `AuthContext`
|
|
114
|
+
|
|
115
|
+
User identity and authorization metadata.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
type AuthContext = {
|
|
119
|
+
clientId: string;
|
|
120
|
+
metadata?: Record<string, unknown>;
|
|
121
|
+
permissions?: string[];
|
|
122
|
+
};
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Properties**:
|
|
126
|
+
- `clientId` - Unique user identifier
|
|
127
|
+
- `metadata` - Custom authorization metadata (permissions, quotas, etc.)
|
|
128
|
+
- `permissions` - Array of permission strings for authorization
|
|
129
|
+
|
|
130
|
+
#### `AuthContextService`
|
|
131
|
+
|
|
132
|
+
Effect service for accessing auth context throughout request processing.
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
export class AuthContextService extends Context.Tag("AuthContextService")<
|
|
136
|
+
AuthContextService,
|
|
137
|
+
{
|
|
138
|
+
readonly getClientId: () => Effect.Effect<string | null>;
|
|
139
|
+
readonly getMetadata: () => Effect.Effect<Record<string, unknown>>;
|
|
140
|
+
readonly hasPermission: (permission: string) => Effect.Effect<boolean>;
|
|
141
|
+
readonly getAuthContext: () => Effect.Effect<AuthContext | null>;
|
|
142
|
+
}
|
|
143
|
+
>() {}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Methods**:
|
|
147
|
+
- `getClientId()` - Get current client ID
|
|
148
|
+
- `getMetadata()` - Get auth metadata object
|
|
149
|
+
- `hasPermission(permission)` - Check if user has permission
|
|
150
|
+
- `getAuthContext()` - Get full auth context
|
|
151
|
+
|
|
152
|
+
#### `AuthContextServiceLive(authContext)`
|
|
153
|
+
|
|
154
|
+
Factory for creating AuthContextService layer.
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { AuthContextServiceLive } from "@uploadista/server";
|
|
158
|
+
|
|
159
|
+
const authLayer = AuthContextServiceLive({
|
|
160
|
+
clientId: "user-123",
|
|
161
|
+
permissions: ["upload:create"],
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### `getAuthCredentials(params)`
|
|
166
|
+
|
|
167
|
+
Exchange client credentials for JWT token.
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { getAuthCredentials } from "@uploadista/server";
|
|
171
|
+
|
|
172
|
+
const response = await getAuthCredentials({
|
|
173
|
+
uploadistaClientId: "my-client",
|
|
174
|
+
uploadistaApiKey: "sk_...",
|
|
175
|
+
baseUrl: "https://api.uploadista.com", // optional
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
if (response.isValid) {
|
|
179
|
+
// response.data.token - JWT token
|
|
180
|
+
// response.data.expiresIn - Seconds until expiration
|
|
181
|
+
} else {
|
|
182
|
+
// response.error - Error message
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Caching
|
|
187
|
+
|
|
188
|
+
#### `AuthCacheConfig`
|
|
189
|
+
|
|
190
|
+
Configuration for auth context caching.
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
type AuthCacheConfig = {
|
|
194
|
+
maxSize?: number; // Default: 10000
|
|
195
|
+
ttl?: number; // Default: 3600000 (1 hour)
|
|
196
|
+
};
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### `AuthCacheService`
|
|
200
|
+
|
|
201
|
+
Effect service for storing and retrieving cached auth contexts.
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
export class AuthCacheService extends Context.Tag("AuthCacheService")<
|
|
205
|
+
AuthCacheService,
|
|
206
|
+
{
|
|
207
|
+
readonly set: (
|
|
208
|
+
jobId: string,
|
|
209
|
+
authContext: AuthContext,
|
|
210
|
+
) => Effect.Effect<void>;
|
|
211
|
+
readonly get: (jobId: string) => Effect.Effect<AuthContext | null>;
|
|
212
|
+
readonly delete: (jobId: string) => Effect.Effect<void>;
|
|
213
|
+
readonly clear: () => Effect.Effect<void>;
|
|
214
|
+
readonly size: () => Effect.Effect<number>;
|
|
215
|
+
}
|
|
216
|
+
>() {}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Methods**:
|
|
220
|
+
- `set(jobId, authContext)` - Cache auth for a job
|
|
221
|
+
- `get(jobId)` - Retrieve cached auth
|
|
222
|
+
- `delete(jobId)` - Remove specific cache entry
|
|
223
|
+
- `clear()` - Clear all cached entries
|
|
224
|
+
- `size()` - Get number of cached entries
|
|
225
|
+
|
|
226
|
+
#### `AuthCacheServiceLive(config?)`
|
|
227
|
+
|
|
228
|
+
Create in-memory auth cache layer.
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
import { AuthCacheServiceLive } from "@uploadista/server";
|
|
232
|
+
|
|
233
|
+
const cacheLayer = AuthCacheServiceLive({
|
|
234
|
+
maxSize: 5000,
|
|
235
|
+
ttl: 1800000, // 30 minutes
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Layer Composition
|
|
240
|
+
|
|
241
|
+
#### `UploadServerLayerConfig`
|
|
242
|
+
|
|
243
|
+
Configuration for creating upload server layer.
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
interface UploadServerLayerConfig {
|
|
247
|
+
kvStore: Layer.Layer<BaseKvStoreService>;
|
|
248
|
+
eventEmitter: Layer.Layer<BaseEventEmitterService>;
|
|
249
|
+
dataStore: Layer.Layer<UploadFileDataStores, never, UploadFileKVStore>;
|
|
250
|
+
bufferedDataStore?: Layer.Layer<UploadFileDataStore>;
|
|
251
|
+
generateId?: Layer.Layer<GenerateId>;
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### `createUploadServerLayer(config)`
|
|
256
|
+
|
|
257
|
+
Compose upload server with all dependencies.
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
import { createUploadServerLayer } from "@uploadista/server";
|
|
261
|
+
|
|
262
|
+
const uploadLayer = createUploadServerLayer({
|
|
263
|
+
kvStore: redisKvStore,
|
|
264
|
+
eventEmitter: webSocketEventEmitter,
|
|
265
|
+
dataStore: s3DataStore,
|
|
266
|
+
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
#### `FlowServerLayerConfig`
|
|
270
|
+
|
|
271
|
+
Configuration for creating flow server layer.
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
interface FlowServerLayerConfig {
|
|
275
|
+
kvStore: Layer.Layer<BaseKvStoreService>;
|
|
276
|
+
eventEmitter: Layer.Layer<BaseEventEmitterService>;
|
|
277
|
+
flowProvider: Layer.Layer<FlowProvider>;
|
|
278
|
+
uploadServer: Layer.Layer<UploadServer>;
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
#### `createFlowServerLayer(config)`
|
|
283
|
+
|
|
284
|
+
Compose flow server with all dependencies.
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
import { createFlowServerLayer } from "@uploadista/server";
|
|
288
|
+
|
|
289
|
+
const flowLayer = createFlowServerLayer({
|
|
290
|
+
kvStore: redisKvStore,
|
|
291
|
+
eventEmitter: webSocketEventEmitter,
|
|
292
|
+
flowProvider: createFlowsEffect,
|
|
293
|
+
uploadServer: uploadLayer,
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Error Handling
|
|
298
|
+
|
|
299
|
+
#### `AdapterError`
|
|
300
|
+
|
|
301
|
+
Base error class for adapter errors.
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
class AdapterError extends Error {
|
|
305
|
+
constructor(
|
|
306
|
+
message: string,
|
|
307
|
+
statusCode?: number,
|
|
308
|
+
errorCode?: string,
|
|
309
|
+
) {}
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Properties**:
|
|
314
|
+
- `statusCode` - HTTP status code (default: 500)
|
|
315
|
+
- `errorCode` - Machine-readable error code
|
|
316
|
+
|
|
317
|
+
#### `ValidationError`, `NotFoundError`, `BadRequestError`
|
|
318
|
+
|
|
319
|
+
Pre-configured error classes:
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
import {
|
|
323
|
+
ValidationError,
|
|
324
|
+
NotFoundError,
|
|
325
|
+
BadRequestError,
|
|
326
|
+
} from "@uploadista/server";
|
|
327
|
+
|
|
328
|
+
// Validation error (400)
|
|
329
|
+
throw new ValidationError("Invalid upload ID format");
|
|
330
|
+
|
|
331
|
+
// Not found (404)
|
|
332
|
+
throw new NotFoundError("Upload");
|
|
333
|
+
|
|
334
|
+
// Bad request (400)
|
|
335
|
+
throw new BadRequestError("Invalid JSON body");
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
#### Error Response Factories
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
import {
|
|
342
|
+
createErrorResponseBody,
|
|
343
|
+
createUploadistaErrorResponseBody,
|
|
344
|
+
createGenericErrorResponseBody,
|
|
345
|
+
} from "@uploadista/server";
|
|
346
|
+
|
|
347
|
+
// For adapter errors
|
|
348
|
+
const errorResponse = createErrorResponseBody(
|
|
349
|
+
new ValidationError("Invalid data"),
|
|
350
|
+
);
|
|
351
|
+
// => { error: "Invalid data", code: "VALIDATION_ERROR", timestamp: "..." }
|
|
352
|
+
|
|
353
|
+
// For core library errors
|
|
354
|
+
const uploadistaErrorResponse = createUploadistaErrorResponseBody(error);
|
|
355
|
+
|
|
356
|
+
// For unknown errors
|
|
357
|
+
const genericErrorResponse = createGenericErrorResponseBody("Something went wrong");
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### HTTP Utilities
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
import {
|
|
364
|
+
parseUrlSegments,
|
|
365
|
+
getLastSegment,
|
|
366
|
+
hasBasePath,
|
|
367
|
+
getRouteSegments,
|
|
368
|
+
handleFlowError,
|
|
369
|
+
extractJobIdFromStatus,
|
|
370
|
+
extractJobAndNodeId,
|
|
371
|
+
extractFlowAndStorageId,
|
|
372
|
+
} from "@uploadista/server";
|
|
373
|
+
|
|
374
|
+
// Parse route
|
|
375
|
+
const segments = parseUrlSegments("/uploadista/api/upload/abc");
|
|
376
|
+
// => ["uploadista", "api", "upload", "abc"]
|
|
377
|
+
|
|
378
|
+
// Check if request is for uploadista
|
|
379
|
+
const isUploadistaRequest = hasBasePath("/uploadista/api/upload", "uploadista");
|
|
380
|
+
// => true
|
|
381
|
+
|
|
382
|
+
// Extract parameters from URL
|
|
383
|
+
const jobId = extractJobIdFromStatus(["jobs", "job-123", "status"]);
|
|
384
|
+
// => "job-123"
|
|
385
|
+
|
|
386
|
+
// Handle errors consistently
|
|
387
|
+
const errorInfo = handleFlowError({
|
|
388
|
+
code: "FILE_NOT_FOUND",
|
|
389
|
+
message: "File not found",
|
|
390
|
+
});
|
|
391
|
+
// => { status: 404, code: "FILE_NOT_FOUND", message: "File not found" }
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
## Framework Integration
|
|
395
|
+
|
|
396
|
+
This package is used by framework adapters:
|
|
397
|
+
|
|
398
|
+
- **[@uploadista/adapters-hono](../adapters-hono/)** - For Cloudflare Workers
|
|
399
|
+
- **[@uploadista/adapters-express](../adapters-express/)** - For Node.js Express
|
|
400
|
+
- **[@uploadista/adapters-fastify](../adapters-fastify/)** - For Node.js Fastify
|
|
401
|
+
|
|
402
|
+
## Complete Server Example
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
import { Effect, Layer } from "effect";
|
|
406
|
+
import {
|
|
407
|
+
createUploadServerLayer,
|
|
408
|
+
createFlowServerLayer,
|
|
409
|
+
AuthContextServiceLive,
|
|
410
|
+
} from "@uploadista/server";
|
|
411
|
+
import { redisKvStore } from "@uploadista/kv-store-redis";
|
|
412
|
+
import { s3DataStore } from "@uploadista/data-store-s3";
|
|
413
|
+
import { webSocketEventEmitter } from "@uploadista/event-emitter-websocket";
|
|
414
|
+
|
|
415
|
+
// Configure servers
|
|
416
|
+
const uploadLayer = createUploadServerLayer({
|
|
417
|
+
kvStore: redisKvStore,
|
|
418
|
+
eventEmitter: webSocketEventEmitter,
|
|
419
|
+
dataStore: s3DataStore,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
const flowLayer = createFlowServerLayer({
|
|
423
|
+
kvStore: redisKvStore,
|
|
424
|
+
eventEmitter: webSocketEventEmitter,
|
|
425
|
+
flowProvider: createFlowsEffect,
|
|
426
|
+
uploadServer: uploadLayer,
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// Set up authentication for a request
|
|
430
|
+
const authContext = {
|
|
431
|
+
clientId: "user-123",
|
|
432
|
+
permissions: ["upload:create", "flow:execute"],
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
// Compose all layers
|
|
436
|
+
const appLayer = Layer.provide(flowLayer, uploadLayer).pipe(
|
|
437
|
+
Layer.provide(AuthContextServiceLive(authContext)),
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
// Run effects
|
|
441
|
+
const myEffect = Effect.gen(function* () {
|
|
442
|
+
const uploadServer = yield* UploadServer;
|
|
443
|
+
const flowServer = yield* FlowServer;
|
|
444
|
+
// ... use uploadServer and flowServer
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
Effect.runPromise(myEffect.pipe(Effect.provide(appLayer)));
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
## TypeScript Support
|
|
451
|
+
|
|
452
|
+
Full TypeScript support with comprehensive types:
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
import type {
|
|
456
|
+
AuthContext,
|
|
457
|
+
AuthResult,
|
|
458
|
+
AuthCacheConfig,
|
|
459
|
+
UploadServerLayerConfig,
|
|
460
|
+
FlowServerLayerConfig,
|
|
461
|
+
} from "@uploadista/server";
|
|
462
|
+
import type { UploadServer, FlowServer } from "@uploadista/core";
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
## Architecture Notes
|
|
466
|
+
|
|
467
|
+
### Authentication Flow
|
|
468
|
+
|
|
469
|
+
1. Client authenticates with credentials (ID + API key)
|
|
470
|
+
2. Server validates and issues JWT token
|
|
471
|
+
3. Token includes user identity and permissions
|
|
472
|
+
4. Subsequent requests include token in Authorization header
|
|
473
|
+
5. Auth context created from token claims
|
|
474
|
+
6. Auth context passed through Effect layers to handlers
|
|
475
|
+
7. Handlers check permissions before processing
|
|
476
|
+
|
|
477
|
+
### Effect Layer Pattern
|
|
478
|
+
|
|
479
|
+
- Use `Layer.provide()` to compose dependencies
|
|
480
|
+
- Each layer provides one service (UploadServer, FlowServer, etc.)
|
|
481
|
+
- Auth context automatically available via AuthContextService
|
|
482
|
+
- Cache automatically handles auth context persistence across requests
|
|
483
|
+
|
|
484
|
+
### Error Handling Strategy
|
|
485
|
+
|
|
486
|
+
1. Catch domain errors in handlers
|
|
487
|
+
2. Map to AdapterError with appropriate HTTP status
|
|
488
|
+
3. Format using createErrorResponseBody
|
|
489
|
+
4. Return JSON error response with timestamp
|
|
490
|
+
5. Log errors for monitoring
|
|
491
|
+
|
|
492
|
+
## Related Packages
|
|
493
|
+
|
|
494
|
+
- **[@uploadista/core](../../core/)** - Core upload and flow engine
|
|
495
|
+
- **[@uploadista/adapters-hono](../adapters-hono/)** - Hono adapter
|
|
496
|
+
- **[@uploadista/adapters-express](../adapters-express/)** - Express adapter
|
|
497
|
+
- **[@uploadista/adapters-fastify](../adapters-fastify/)** - Fastify adapter
|
|
498
|
+
- **[@uploadista/kv-store-redis](../../kv-stores/redis/)** - Redis KV store
|
|
499
|
+
- **[@uploadista/data-store-s3](../../data-stores/s3/)** - AWS S3 storage
|
|
500
|
+
|
|
501
|
+
## License
|
|
502
|
+
|
|
503
|
+
MIT
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Context, Effect, Layer } from "effect";
|
|
2
|
+
import type { AuthContext } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for the auth cache.
|
|
5
|
+
*/
|
|
6
|
+
export type AuthCacheConfig = {
|
|
7
|
+
/**
|
|
8
|
+
* Maximum number of entries in the cache.
|
|
9
|
+
* When exceeded, oldest entries are removed (LRU eviction).
|
|
10
|
+
* @default 10000
|
|
11
|
+
*/
|
|
12
|
+
maxSize?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Time-to-live for cache entries in milliseconds.
|
|
15
|
+
* Entries older than this will be automatically evicted.
|
|
16
|
+
* @default 3600000 (1 hour)
|
|
17
|
+
*/
|
|
18
|
+
ttl?: number;
|
|
19
|
+
};
|
|
20
|
+
declare const AuthCacheService_base: Context.TagClass<AuthCacheService, "AuthCacheService", {
|
|
21
|
+
/**
|
|
22
|
+
* Store an auth context for a job ID.
|
|
23
|
+
*/
|
|
24
|
+
readonly set: (jobId: string, authContext: AuthContext) => Effect.Effect<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Retrieve a cached auth context by job ID.
|
|
27
|
+
* Returns null if not found or expired.
|
|
28
|
+
*/
|
|
29
|
+
readonly get: (jobId: string) => Effect.Effect<AuthContext | null>;
|
|
30
|
+
/**
|
|
31
|
+
* Delete a cached auth context by job ID.
|
|
32
|
+
*/
|
|
33
|
+
readonly delete: (jobId: string) => Effect.Effect<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Clear all cached auth contexts.
|
|
36
|
+
*/
|
|
37
|
+
readonly clear: () => Effect.Effect<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Get the current number of cached entries.
|
|
40
|
+
*/
|
|
41
|
+
readonly size: () => Effect.Effect<number>;
|
|
42
|
+
}>;
|
|
43
|
+
/**
|
|
44
|
+
* Auth Cache Service
|
|
45
|
+
*
|
|
46
|
+
* Provides caching of authentication contexts for upload and flow jobs.
|
|
47
|
+
* This allows subsequent operations (chunk uploads, flow continuations)
|
|
48
|
+
* to reuse the auth context from the initial request without re-authenticating.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* import { Effect } from "effect";
|
|
53
|
+
* import { AuthCacheService } from "@uploadista/server";
|
|
54
|
+
*
|
|
55
|
+
* const handler = Effect.gen(function* () {
|
|
56
|
+
* const authCache = yield* AuthCacheService;
|
|
57
|
+
* const authContext = { userId: "user-123" };
|
|
58
|
+
*
|
|
59
|
+
* // Cache auth for upload
|
|
60
|
+
* yield* authCache.set("upload-abc", authContext);
|
|
61
|
+
*
|
|
62
|
+
* // Retrieve cached auth later
|
|
63
|
+
* const cached = yield* authCache.get("upload-abc");
|
|
64
|
+
* console.log(cached?.userId); // "user-123"
|
|
65
|
+
*
|
|
66
|
+
* // Clear when done
|
|
67
|
+
* yield* authCache.delete("upload-abc");
|
|
68
|
+
* });
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export declare class AuthCacheService extends AuthCacheService_base {
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Creates an AuthCacheService Layer with in-memory storage.
|
|
75
|
+
*
|
|
76
|
+
* @param config - Optional configuration for cache behavior
|
|
77
|
+
* @returns Effect Layer providing AuthCacheService
|
|
78
|
+
*/
|
|
79
|
+
export declare const AuthCacheServiceLive: (config?: AuthCacheConfig) => Layer.Layer<AuthCacheService>;
|
|
80
|
+
/**
|
|
81
|
+
* No-op implementation of AuthCacheService.
|
|
82
|
+
* Does not cache anything - all operations are no-ops.
|
|
83
|
+
* Used when caching is disabled or not needed.
|
|
84
|
+
*/
|
|
85
|
+
export declare const NoAuthCacheServiceLive: Layer.Layer<AuthCacheService>;
|
|
86
|
+
export {};
|
|
87
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/auth/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;;IAyCE;;OAEG;kBACW,CACZ,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,WAAW,KACrB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;IAExB;;;OAGG;kBACW,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,WAAW,GAAG,IAAI,CAAC;IAElE;;OAEG;qBACc,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;IAEvD;;OAEG;oBACa,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;IAEzC;;OAEG;mBACY,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;;AA1D9C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,gBAAiB,SAAQ,qBAgCnC;CAAG;AAEN;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAC/B,SAAQ,eAAoB,KAC3B,KAAK,CAAC,KAAK,CAAC,gBAAgB,CAyF9B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,KAAK,CAAC,KAAK,CAAC,gBAAgB,CAO7D,CAAC"}
|