@uploadista/server 0.0.18-beta.9 → 0.0.19
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/dist/index.cjs +2 -2
- package/dist/index.d.cts +49 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +49 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
- package/src/core/http-handlers/upload-http-handlers.ts +1 -1
- package/src/core/server.ts +30 -8
- package/src/core/types.ts +36 -0
- package/src/service.ts +28 -2
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uploadista/server",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.19",
|
|
5
5
|
"description": "Core Server package for Uploadista",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Uploadista",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@uploadista/core": "0.0.
|
|
24
|
-
"@uploadista/
|
|
25
|
-
"@uploadista/
|
|
26
|
-
"@uploadista/event-broadcaster-memory": "0.0.
|
|
23
|
+
"@uploadista/core": "0.0.19",
|
|
24
|
+
"@uploadista/event-emitter-websocket": "0.0.19",
|
|
25
|
+
"@uploadista/observability": "0.0.19",
|
|
26
|
+
"@uploadista/event-broadcaster-memory": "0.0.19"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@cloudflare/workers-types": "4.20251202.0",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"tsd": "0.33.0",
|
|
35
35
|
"tsdown": "0.16.8",
|
|
36
36
|
"typescript": "5.9.3",
|
|
37
|
-
"vitest": "4.0.
|
|
37
|
+
"vitest": "4.0.15",
|
|
38
38
|
"zod": "4.1.13",
|
|
39
|
-
"@uploadista/typescript-config": "0.0.
|
|
39
|
+
"@uploadista/typescript-config": "0.0.19"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"effect": "^3.0.0",
|
|
@@ -5,8 +5,8 @@ import { MetricsService } from "@uploadista/observability";
|
|
|
5
5
|
import { Effect } from "effect";
|
|
6
6
|
import { AuthCacheService } from "../../cache";
|
|
7
7
|
import { ValidationError } from "../../error-types";
|
|
8
|
-
import { PERMISSIONS } from "../../permissions/types";
|
|
9
8
|
import { QuotaExceededError } from "../../permissions/errors";
|
|
9
|
+
import { PERMISSIONS } from "../../permissions/types";
|
|
10
10
|
import { AuthContextService } from "../../service";
|
|
11
11
|
import { UsageHookService } from "../../usage-hooks/service";
|
|
12
12
|
import type {
|
package/src/core/server.ts
CHANGED
|
@@ -193,6 +193,7 @@ export const createUploadistaServer = async <
|
|
|
193
193
|
eventEmitter,
|
|
194
194
|
eventBroadcaster = memoryEventBroadcaster,
|
|
195
195
|
withTracing = false,
|
|
196
|
+
observabilityLayer,
|
|
196
197
|
baseUrl: configBaseUrl = "uploadista",
|
|
197
198
|
generateId = GenerateIdLive,
|
|
198
199
|
metricsLayer,
|
|
@@ -308,6 +309,15 @@ export const createUploadistaServer = async <
|
|
|
308
309
|
...(dlqLayer ? [dlqLayer] : []),
|
|
309
310
|
);
|
|
310
311
|
|
|
312
|
+
/**
|
|
313
|
+
* Determine the tracing layer to use.
|
|
314
|
+
* This must be included in the runtime layer (not per-request) so that the
|
|
315
|
+
* BatchSpanProcessor can aggregate spans across requests and flush them properly.
|
|
316
|
+
*/
|
|
317
|
+
const tracingLayer = withTracing
|
|
318
|
+
? observabilityLayer ?? NodeSdkLive
|
|
319
|
+
: null;
|
|
320
|
+
|
|
311
321
|
/**
|
|
312
322
|
* Type Casting Rationale for Plugin System
|
|
313
323
|
*
|
|
@@ -385,16 +395,29 @@ export const createUploadistaServer = async <
|
|
|
385
395
|
* @see validatePluginRequirements - Runtime validation helper
|
|
386
396
|
* @see ValidatePlugins - Compile-time validation type utility
|
|
387
397
|
*/
|
|
388
|
-
const
|
|
398
|
+
const serverLayerTyped = serverLayerRaw as unknown as Layer.Layer<
|
|
389
399
|
// biome-ignore lint/suspicious/noExplicitAny: Dynamic plugin requirements require any - see comprehensive explanation above
|
|
390
400
|
any,
|
|
391
401
|
never,
|
|
392
402
|
never
|
|
393
403
|
>;
|
|
394
404
|
|
|
405
|
+
/**
|
|
406
|
+
* Final server layer with optional tracing.
|
|
407
|
+
* The tracing layer is merged at runtime level (not per-request) so that:
|
|
408
|
+
* 1. The OpenTelemetry SDK is initialized once for the server
|
|
409
|
+
* 2. The BatchSpanProcessor can aggregate spans across requests
|
|
410
|
+
* 3. Spans are properly flushed when the runtime is disposed
|
|
411
|
+
*/
|
|
412
|
+
const serverLayer = tracingLayer
|
|
413
|
+
? Layer.merge(serverLayerTyped, tracingLayer)
|
|
414
|
+
: serverLayerTyped;
|
|
415
|
+
|
|
395
416
|
// Create a shared managed runtime from the server layer
|
|
396
417
|
// This ensures all requests use the same layer instances (including event broadcaster)
|
|
397
418
|
// ManagedRuntime properly handles scoped resources and provides convenient run methods
|
|
419
|
+
// When tracing is enabled, the OpenTelemetry SDK is part of this runtime and will be
|
|
420
|
+
// properly shut down (flushing all pending spans) when dispose() is called
|
|
398
421
|
|
|
399
422
|
const managedRuntime = ManagedRuntime.make(serverLayer);
|
|
400
423
|
|
|
@@ -491,7 +514,10 @@ export const createUploadistaServer = async <
|
|
|
491
514
|
}
|
|
492
515
|
|
|
493
516
|
// Create auth context layer for this request
|
|
494
|
-
|
|
517
|
+
// If no auth middleware is configured, bypass permission checks (backward compatibility)
|
|
518
|
+
const authContextLayer = AuthContextServiceLive(authContext, {
|
|
519
|
+
bypassAuth: !adapter.runAuthMiddleware,
|
|
520
|
+
});
|
|
495
521
|
|
|
496
522
|
// Extract waitUntil callback if available (for Cloudflare Workers)
|
|
497
523
|
// This must be extracted per-request since it comes from the framework context
|
|
@@ -559,12 +585,8 @@ export const createUploadistaServer = async <
|
|
|
559
585
|
}),
|
|
560
586
|
);
|
|
561
587
|
|
|
562
|
-
// Use the shared managed runtime
|
|
563
|
-
|
|
564
|
-
return managedRuntime.runPromise(
|
|
565
|
-
program.pipe(Effect.provide(NodeSdkLive)),
|
|
566
|
-
);
|
|
567
|
-
}
|
|
588
|
+
// Use the shared managed runtime which includes all layers (including tracing if enabled)
|
|
589
|
+
// Tracing is now part of the runtime layer, so spans are properly aggregated and flushed
|
|
568
590
|
return managedRuntime.runPromise(program);
|
|
569
591
|
};
|
|
570
592
|
|
package/src/core/types.ts
CHANGED
|
@@ -230,6 +230,42 @@ export interface UploadistaServerConfig<
|
|
|
230
230
|
*/
|
|
231
231
|
withTracing?: boolean;
|
|
232
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Optional: Custom observability layer for distributed tracing.
|
|
235
|
+
*
|
|
236
|
+
* When provided, this layer will be used instead of the default NodeSdkLive.
|
|
237
|
+
* This allows you to configure custom OTLP exporters (e.g., for Grafana Cloud,
|
|
238
|
+
* Jaeger, or other OpenTelemetry-compatible backends).
|
|
239
|
+
*
|
|
240
|
+
* Requires `withTracing: true` to be effective.
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```typescript
|
|
244
|
+
* import { OtlpNodeSdkLive, createOtlpNodeSdkLayer } from "@uploadista/observability";
|
|
245
|
+
*
|
|
246
|
+
* // Option 1: Use default OTLP layer (reads from env vars)
|
|
247
|
+
* const server = await createUploadistaServer({
|
|
248
|
+
* withTracing: true,
|
|
249
|
+
* observabilityLayer: OtlpNodeSdkLive,
|
|
250
|
+
* // ...
|
|
251
|
+
* });
|
|
252
|
+
*
|
|
253
|
+
* // Option 2: Custom configuration with tenant attributes
|
|
254
|
+
* const server = await createUploadistaServer({
|
|
255
|
+
* withTracing: true,
|
|
256
|
+
* observabilityLayer: createOtlpNodeSdkLayer({
|
|
257
|
+
* serviceName: "uploadista-cloud-api",
|
|
258
|
+
* resourceAttributes: {
|
|
259
|
+
* "deployment.environment": "production",
|
|
260
|
+
* },
|
|
261
|
+
* }),
|
|
262
|
+
* // ...
|
|
263
|
+
* });
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
// biome-ignore lint/suspicious/noExplicitAny: Observability layers from @effect/opentelemetry provide different services
|
|
267
|
+
observabilityLayer?: Layer.Layer<any, never, never>;
|
|
268
|
+
|
|
233
269
|
/**
|
|
234
270
|
* Optional: Metrics layer for observability.
|
|
235
271
|
*
|
package/src/service.ts
CHANGED
|
@@ -114,17 +114,32 @@ export class AuthContextService extends Context.Tag("AuthContextService")<
|
|
|
114
114
|
}
|
|
115
115
|
>() {}
|
|
116
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Options for creating an AuthContextService Layer.
|
|
119
|
+
*/
|
|
120
|
+
export interface AuthContextServiceOptions {
|
|
121
|
+
/**
|
|
122
|
+
* When true, bypasses all permission checks (grants all permissions).
|
|
123
|
+
* Used when no auth middleware is configured (backward compatibility).
|
|
124
|
+
* @default false
|
|
125
|
+
*/
|
|
126
|
+
bypassAuth?: boolean;
|
|
127
|
+
}
|
|
128
|
+
|
|
117
129
|
/**
|
|
118
130
|
* Creates an AuthContextService Layer from an AuthContext.
|
|
119
131
|
* This is typically called by adapters after successful authentication.
|
|
120
132
|
*
|
|
121
133
|
* @param authContext - The authentication context from middleware
|
|
134
|
+
* @param options - Optional configuration for auth behavior
|
|
122
135
|
* @returns Effect Layer providing AuthContextService
|
|
123
136
|
*/
|
|
124
137
|
export const AuthContextServiceLive = (
|
|
125
138
|
authContext: AuthContext | null,
|
|
139
|
+
options?: AuthContextServiceOptions,
|
|
126
140
|
): Layer.Layer<AuthContextService> => {
|
|
127
141
|
const permissions = authContext?.permissions ?? [];
|
|
142
|
+
const bypassAuth = options?.bypassAuth ?? false;
|
|
128
143
|
|
|
129
144
|
return Layer.succeed(AuthContextService, {
|
|
130
145
|
getClientId: () => Effect.succeed(authContext?.clientId ?? null),
|
|
@@ -132,13 +147,24 @@ export const AuthContextServiceLive = (
|
|
|
132
147
|
getMetadata: () => Effect.succeed(authContext?.metadata ?? {}),
|
|
133
148
|
|
|
134
149
|
hasPermission: (permission: string) =>
|
|
135
|
-
|
|
150
|
+
bypassAuth
|
|
151
|
+
? Effect.succeed(true)
|
|
152
|
+
: Effect.succeed(matchHasPermission(permissions, permission)),
|
|
136
153
|
|
|
137
154
|
hasAnyPermission: (requiredPermissions: readonly string[]) =>
|
|
138
|
-
|
|
155
|
+
bypassAuth
|
|
156
|
+
? Effect.succeed(true)
|
|
157
|
+
: Effect.succeed(matchHasAnyPermission(permissions, requiredPermissions)),
|
|
139
158
|
|
|
140
159
|
requirePermission: (permission: string) =>
|
|
141
160
|
Effect.gen(function* () {
|
|
161
|
+
// If bypass mode is enabled, grant all permissions
|
|
162
|
+
if (bypassAuth) {
|
|
163
|
+
yield* Effect.logDebug(
|
|
164
|
+
`[Auth] Bypass mode: permission '${permission}' auto-granted`,
|
|
165
|
+
);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
142
168
|
if (!authContext) {
|
|
143
169
|
yield* Effect.logDebug(
|
|
144
170
|
`[Auth] Permission check failed: authentication required for '${permission}'`,
|