@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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@uploadista/server",
3
3
  "type": "module",
4
- "version": "0.0.18-beta.9",
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.18-beta.9",
24
- "@uploadista/observability": "0.0.18-beta.9",
25
- "@uploadista/event-emitter-websocket": "0.0.18-beta.9",
26
- "@uploadista/event-broadcaster-memory": "0.0.18-beta.9"
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.14",
37
+ "vitest": "4.0.15",
38
38
  "zod": "4.1.13",
39
- "@uploadista/typescript-config": "0.0.18-beta.9"
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 {
@@ -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 serverLayer = serverLayerRaw as unknown as Layer.Layer<
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
- const authContextLayer = AuthContextServiceLive(authContext);
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 instead of creating a new one per request
563
- if (withTracing) {
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
- Effect.succeed(matchHasPermission(permissions, permission)),
150
+ bypassAuth
151
+ ? Effect.succeed(true)
152
+ : Effect.succeed(matchHasPermission(permissions, permission)),
136
153
 
137
154
  hasAnyPermission: (requiredPermissions: readonly string[]) =>
138
- Effect.succeed(matchHasAnyPermission(permissions, requiredPermissions)),
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}'`,