@uploadista/observability 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.
Files changed (118) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/.turbo/turbo-check.log +0 -0
  3. package/LICENSE +21 -0
  4. package/dist/core/errors.d.ts +8 -0
  5. package/dist/core/errors.d.ts.map +1 -0
  6. package/dist/core/errors.js +108 -0
  7. package/dist/core/index.d.ts +8 -0
  8. package/dist/core/index.d.ts.map +1 -0
  9. package/dist/core/index.js +8 -0
  10. package/dist/core/layers.d.ts +104 -0
  11. package/dist/core/layers.d.ts.map +1 -0
  12. package/dist/core/layers.js +110 -0
  13. package/dist/core/logging.d.ts +18 -0
  14. package/dist/core/logging.d.ts.map +1 -0
  15. package/dist/core/logging.js +41 -0
  16. package/dist/core/metrics.d.ts +37 -0
  17. package/dist/core/metrics.d.ts.map +1 -0
  18. package/dist/core/metrics.js +72 -0
  19. package/dist/core/testing.d.ts +43 -0
  20. package/dist/core/testing.d.ts.map +1 -0
  21. package/dist/core/testing.js +93 -0
  22. package/dist/core/tracing.d.ts +19 -0
  23. package/dist/core/tracing.d.ts.map +1 -0
  24. package/dist/core/tracing.js +43 -0
  25. package/dist/core/utilities.d.ts +11 -0
  26. package/dist/core/utilities.d.ts.map +1 -0
  27. package/dist/core/utilities.js +41 -0
  28. package/dist/flow/errors.d.ts +15 -0
  29. package/dist/flow/errors.d.ts.map +1 -0
  30. package/dist/flow/errors.js +66 -0
  31. package/dist/flow/index.d.ts +6 -0
  32. package/dist/flow/index.d.ts.map +1 -0
  33. package/dist/flow/index.js +6 -0
  34. package/dist/flow/layers.d.ts +40 -0
  35. package/dist/flow/layers.d.ts.map +1 -0
  36. package/dist/flow/layers.js +94 -0
  37. package/dist/flow/metrics.d.ts +52 -0
  38. package/dist/flow/metrics.d.ts.map +1 -0
  39. package/dist/flow/metrics.js +89 -0
  40. package/dist/flow/testing.d.ts +11 -0
  41. package/dist/flow/testing.d.ts.map +1 -0
  42. package/dist/flow/testing.js +27 -0
  43. package/dist/flow/tracing.d.ts +35 -0
  44. package/dist/flow/tracing.d.ts.map +1 -0
  45. package/dist/flow/tracing.js +42 -0
  46. package/dist/index.d.ts +8 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +14 -0
  49. package/dist/service/metrics.d.ts +23 -0
  50. package/dist/service/metrics.d.ts.map +1 -0
  51. package/dist/service/metrics.js +17 -0
  52. package/dist/storage/azure.d.ts +47 -0
  53. package/dist/storage/azure.d.ts.map +1 -0
  54. package/dist/storage/azure.js +89 -0
  55. package/dist/storage/filesystem.d.ts +47 -0
  56. package/dist/storage/filesystem.d.ts.map +1 -0
  57. package/dist/storage/filesystem.js +70 -0
  58. package/dist/storage/gcs.d.ts +47 -0
  59. package/dist/storage/gcs.d.ts.map +1 -0
  60. package/dist/storage/gcs.js +90 -0
  61. package/dist/storage/index.d.ts +5 -0
  62. package/dist/storage/index.d.ts.map +1 -0
  63. package/dist/storage/index.js +5 -0
  64. package/dist/storage/s3.d.ts +47 -0
  65. package/dist/storage/s3.d.ts.map +1 -0
  66. package/dist/storage/s3.js +67 -0
  67. package/dist/test-observability.d.ts +12 -0
  68. package/dist/test-observability.d.ts.map +1 -0
  69. package/dist/test-observability.js +153 -0
  70. package/dist/upload/errors.d.ts +16 -0
  71. package/dist/upload/errors.d.ts.map +1 -0
  72. package/dist/upload/errors.js +107 -0
  73. package/dist/upload/index.d.ts +6 -0
  74. package/dist/upload/index.d.ts.map +1 -0
  75. package/dist/upload/index.js +6 -0
  76. package/dist/upload/layers.d.ts +32 -0
  77. package/dist/upload/layers.d.ts.map +1 -0
  78. package/dist/upload/layers.js +63 -0
  79. package/dist/upload/metrics.d.ts +46 -0
  80. package/dist/upload/metrics.d.ts.map +1 -0
  81. package/dist/upload/metrics.js +80 -0
  82. package/dist/upload/testing.d.ts +32 -0
  83. package/dist/upload/testing.d.ts.map +1 -0
  84. package/dist/upload/testing.js +52 -0
  85. package/dist/upload/tracing.d.ts +25 -0
  86. package/dist/upload/tracing.d.ts.map +1 -0
  87. package/dist/upload/tracing.js +35 -0
  88. package/package.json +37 -0
  89. package/src/core/errors.ts +187 -0
  90. package/src/core/index.ts +9 -0
  91. package/src/core/layers.ts +205 -0
  92. package/src/core/logging.ts +81 -0
  93. package/src/core/metrics.ts +108 -0
  94. package/src/core/testing.ts +142 -0
  95. package/src/core/tracing.ts +67 -0
  96. package/src/core/utilities.ts +133 -0
  97. package/src/flow/errors.ts +95 -0
  98. package/src/flow/index.ts +17 -0
  99. package/src/flow/layers.ts +131 -0
  100. package/src/flow/metrics.ts +130 -0
  101. package/src/flow/testing.ts +33 -0
  102. package/src/flow/tracing.ts +72 -0
  103. package/src/index.ts +31 -0
  104. package/src/service/metrics.ts +31 -0
  105. package/src/storage/azure.ts +163 -0
  106. package/src/storage/filesystem.ts +153 -0
  107. package/src/storage/gcs.ts +161 -0
  108. package/src/storage/index.ts +5 -0
  109. package/src/storage/s3.ts +136 -0
  110. package/src/test-observability.ts +234 -0
  111. package/src/upload/errors.ts +166 -0
  112. package/src/upload/index.ts +12 -0
  113. package/src/upload/layers.ts +88 -0
  114. package/src/upload/metrics.ts +118 -0
  115. package/src/upload/testing.ts +60 -0
  116. package/src/upload/tracing.ts +58 -0
  117. package/tsconfig.json +10 -0
  118. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,47 @@
1
+ import { type Effect, Layer } from "effect";
2
+ import { type StorageMetrics } from "../core/metrics.js";
3
+ export declare const s3Metrics: {
4
+ uploadLatencySummary: import("effect/Metric").Metric.Summary<number>;
5
+ activeUploadsGauge: import("effect/Metric").Metric.Gauge<number>;
6
+ uploadThroughputGauge: import("effect/Metric").Metric.Gauge<number>;
7
+ uploadDurationHistogram: import("effect/Metric").Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>;
8
+ partUploadDurationHistogram: import("effect/Metric").Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>;
9
+ fileSizeHistogram: import("effect/Metric").Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>;
10
+ partSizeHistogram: import("effect/Metric").Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>;
11
+ uploadRequestsTotal: import("effect/Metric").Metric.Counter<number>;
12
+ uploadPartsTotal: import("effect/Metric").Metric.Counter<number>;
13
+ uploadSuccessTotal: import("effect/Metric").Metric.Counter<number>;
14
+ uploadErrorsTotal: import("effect/Metric").Metric.Counter<number>;
15
+ apiCallsTotal: import("effect/Metric").Metric.Counter<number>;
16
+ };
17
+ export declare const S3TracingLayer: Layer.Layer<{
18
+ serviceName: string;
19
+ }, never, never>;
20
+ export declare const trackS3Error: (operation: string, error: unknown, context?: Record<string, unknown>) => Effect.Effect<void, never, never>;
21
+ export declare const S3ObservabilityLayer: Layer.Layer<{
22
+ serviceName: string;
23
+ }, never, never>;
24
+ export declare const withS3UploadMetrics: <A, E, R>(uploadId: string, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
25
+ export declare const withS3ApiMetrics: <A, E, R>(operation: string, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
26
+ export declare const withS3TimingMetrics: <A, E, R>(metric: import("effect/Metric").Metric.Histogram<number>, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
27
+ export declare const withS3OperationMetrics: <A, E, R>(operation: string, uploadId: string, effect: Effect.Effect<A, E, R>, fileSize?: number) => Effect.Effect<A, E, R>;
28
+ export declare const withS3Span: <A, E, R>(operation: string, attributes?: Record<string, unknown>) => (effect: Effect.Effect<A, E, R>) => Effect.Effect<unknown, unknown, unknown>;
29
+ export declare const logS3Operation: (operation: string, uploadId: string, metadata?: Record<string, unknown> | undefined) => Effect.Effect<void, never, never>;
30
+ export declare const logS3UploadProgress: (uploadId: string, progress: {
31
+ uploadedBytes: number;
32
+ totalBytes: number;
33
+ partNumber?: number;
34
+ speed?: number;
35
+ }) => Effect.Effect<void, never, never>;
36
+ export declare const logS3UploadCompletion: (uploadId: string, metrics: {
37
+ fileSize: number;
38
+ totalDurationMs: number;
39
+ partsCount?: number;
40
+ averagePartSize?: number;
41
+ throughputBps?: number;
42
+ retryCount?: number;
43
+ }) => Effect.Effect<void, never, never>;
44
+ export declare const logS3Context: (message: string, context: Record<string, unknown>) => Effect.Effect<void, never, never>;
45
+ export declare const s3UploadRequestsTotal: import("effect/Metric").Metric.Counter<number>, s3UploadPartsTotal: import("effect/Metric").Metric.Counter<number>, s3UploadSuccessTotal: import("effect/Metric").Metric.Counter<number>, s3UploadErrorsTotal: import("effect/Metric").Metric.Counter<number>, s3ApiCallsTotal: import("effect/Metric").Metric.Counter<number>, s3UploadDurationHistogram: import("effect/Metric").Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>, s3PartUploadDurationHistogram: import("effect/Metric").Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>, s3FileSizeHistogram: import("effect/Metric").Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>, s3PartSizeHistogram: import("effect/Metric").Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>, s3ActiveUploadsGauge: import("effect/Metric").Metric.Gauge<number>, s3UploadThroughputGauge: import("effect/Metric").Metric.Gauge<number>, s3UploadLatencySummary: import("effect/Metric").Metric.Summary<number>;
46
+ export type S3Metrics = StorageMetrics;
47
+ //# sourceMappingURL=s3.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../../src/storage/s3.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAW5C,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAgB/E,eAAO,MAAM,SAAS;;;;;;;;;;;;;CAAqC,CAAC;AAG5D,eAAO,MAAM,cAAc;;gBAA0C,CAAC;AAqCtE,eAAO,MAAM,YAAY,6GAIxB,CAAC;AAGF,eAAO,MAAM,oBAAoB;;gBAGhC,CAAC;AAMF,eAAO,MAAM,mBAAmB,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EACzC,UAAU,MAAM,EAChB,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,2BACmB,CAAC;AAEpD,eAAO,MAAM,gBAAgB,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EACtC,WAAW,MAAM,EACjB,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,2BACiB,CAAC;AAElD,eAAO,MAAM,mBAAmB,+HAAoB,CAAC;AAErD,eAAO,MAAM,sBAAsB,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAC5C,WAAW,MAAM,EACjB,UAAU,MAAM,EAChB,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAC9B,WAAW,MAAM,2BAE4D,CAAC;AAGhF,eAAO,MAAM,UAAU,GACpB,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,WAAW,MAAM,EAAE,aAAa,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,MAChE,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,6CAC+B,CAAC;AAGjE,eAAO,MAAM,cAAc,4HAA+C,CAAC;AAC3E,eAAO,MAAM,mBAAmB;;;;;uCAA6C,CAAC;AAC9E,eAAO,MAAM,qBAAqB;;;;;;;uCAGjC,CAAC;AACF,eAAO,MAAM,YAAY,0FAAiB,CAAC;AAG3C,eAAO,MACgB,qBAAqB,kDACxB,kBAAkB,kDAChB,oBAAoB,kDACrB,mBAAmB,kDACvB,eAAe,kDACL,yBAAyB,sJACrB,6BAA6B,sJACvC,mBAAmB,sJACnB,mBAAmB,sJAClB,oBAAoB,gDACjB,uBAAuB,gDACxB,sBAAsB,gDACjC,CAAC;AAGd,MAAM,MAAM,SAAS,GAAG,cAAc,CAAC"}
@@ -0,0 +1,67 @@
1
+ import { Layer } from "effect";
2
+ import { createStorageErrorTracker, } from "../core/errors.js";
3
+ import { logStorageOperation, logUploadCompletion, logUploadProgress, logWithContext, } from "../core/logging.js";
4
+ import { createStorageMetrics } from "../core/metrics.js";
5
+ import { createStorageTracingLayer, withStorageSpan } from "../core/tracing.js";
6
+ import { withApiMetrics, withStorageOperationMetrics, withTimingMetrics, withUploadMetrics, } from "../core/utilities.js";
7
+ // ============================================================================
8
+ // S3-Specific Observability
9
+ // ============================================================================
10
+ const STORAGE_TYPE = "s3";
11
+ // S3-specific metrics
12
+ export const s3Metrics = createStorageMetrics(STORAGE_TYPE);
13
+ // S3-specific tracing layer
14
+ export const S3TracingLayer = createStorageTracingLayer(STORAGE_TYPE);
15
+ // S3-specific error classification
16
+ const classifyS3Error = (error) => {
17
+ if (!error || typeof error !== "object")
18
+ return null;
19
+ const errorCode = "code" in error ? error.code : undefined;
20
+ if (!errorCode)
21
+ return null;
22
+ // S3-specific error codes
23
+ switch (errorCode) {
24
+ case "NoSuchKey":
25
+ case "NoSuchBucket":
26
+ case "NoSuchUpload":
27
+ return "client_error";
28
+ case "BucketAlreadyExists":
29
+ case "BucketNotEmpty":
30
+ return "client_error";
31
+ case "InvalidBucketName":
32
+ case "InvalidPart":
33
+ case "InvalidPartOrder":
34
+ return "client_error";
35
+ case "EntityTooSmall":
36
+ case "EntityTooLarge":
37
+ return "client_error";
38
+ case "ExpiredToken":
39
+ case "TokenRefreshRequired":
40
+ return "authentication_error";
41
+ case "RequestTimeTooSkewed":
42
+ case "SlowDown":
43
+ return "throttling_error";
44
+ default:
45
+ return null; // Fall back to generic classification
46
+ }
47
+ };
48
+ // S3-specific error tracker
49
+ export const trackS3Error = createStorageErrorTracker(STORAGE_TYPE, s3Metrics, classifyS3Error);
50
+ // S3-specific observability layer
51
+ export const S3ObservabilityLayer = Layer.mergeAll(S3TracingLayer);
52
+ // ============================================================================
53
+ // S3 Utility Functions
54
+ // ============================================================================
55
+ export const withS3UploadMetrics = (uploadId, effect) => withUploadMetrics(s3Metrics, uploadId, effect);
56
+ export const withS3ApiMetrics = (operation, effect) => withApiMetrics(s3Metrics, operation, effect);
57
+ export const withS3TimingMetrics = withTimingMetrics;
58
+ export const withS3OperationMetrics = (operation, uploadId, effect, fileSize) => withStorageOperationMetrics(s3Metrics, operation, uploadId, effect, fileSize);
59
+ // S3-specific span wrapper
60
+ export const withS3Span = (operation, attributes) => (effect) => withStorageSpan(operation, STORAGE_TYPE, attributes)(effect);
61
+ // S3-specific logging functions
62
+ export const logS3Operation = logStorageOperation.bind(null, STORAGE_TYPE);
63
+ export const logS3UploadProgress = logUploadProgress.bind(null, STORAGE_TYPE);
64
+ export const logS3UploadCompletion = logUploadCompletion.bind(null, STORAGE_TYPE);
65
+ export const logS3Context = logWithContext;
66
+ // Export metrics for external access
67
+ export const { uploadRequestsTotal: s3UploadRequestsTotal, uploadPartsTotal: s3UploadPartsTotal, uploadSuccessTotal: s3UploadSuccessTotal, uploadErrorsTotal: s3UploadErrorsTotal, apiCallsTotal: s3ApiCallsTotal, uploadDurationHistogram: s3UploadDurationHistogram, partUploadDurationHistogram: s3PartUploadDurationHistogram, fileSizeHistogram: s3FileSizeHistogram, partSizeHistogram: s3PartSizeHistogram, activeUploadsGauge: s3ActiveUploadsGauge, uploadThroughputGauge: s3UploadThroughputGauge, uploadLatencySummary: s3UploadLatencySummary, } = s3Metrics;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Validation test for core observability infrastructure
3
+ *
4
+ * This test validates:
5
+ * 1. Layer creation and composition
6
+ * 2. Storage observability with metrics
7
+ * 3. Error classification
8
+ * 4. Tracing with spans
9
+ * 5. Structured logging
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=test-observability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-observability.d.ts","sourceRoot":"","sources":["../src/test-observability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Validation test for core observability infrastructure
3
+ *
4
+ * This test validates:
5
+ * 1. Layer creation and composition
6
+ * 2. Storage observability with metrics
7
+ * 3. Error classification
8
+ * 4. Tracing with spans
9
+ * 5. Structured logging
10
+ */
11
+ import { Effect, Metric } from "effect";
12
+ import { classifyStorageError, createStorageMetrics, makeStorageObservabilityLayer, StorageObservability, trackStorageError, withStorageSpan, logStorageOperation, logUploadCompletion, } from "./index.js";
13
+ // ============================================================================
14
+ // Test: Create Storage Observability Layer
15
+ // ============================================================================
16
+ const testStorageType = "test-s3";
17
+ const testMetrics = createStorageMetrics(testStorageType);
18
+ const TestStorageObservabilityLayer = makeStorageObservabilityLayer(testStorageType, testMetrics, true);
19
+ // ============================================================================
20
+ // Test: Simulate Upload Operation with Full Observability
21
+ // ============================================================================
22
+ const simulateUpload = (fileSize, shouldFail) => Effect.gen(function* () {
23
+ const obs = yield* StorageObservability;
24
+ console.log(`\n📊 Testing with storage type: ${obs.storageType}`);
25
+ console.log(`Service: ${obs.serviceName}`);
26
+ console.log(`Enabled: ${obs.enabled}`);
27
+ // Log operation start
28
+ yield* logStorageOperation(testStorageType, "uploadFile", "test-upload-123", { file_size: fileSize });
29
+ // Simulate upload with tracing
30
+ const uploadEffect = Effect.gen(function* () {
31
+ // Track upload request
32
+ yield* obs.metrics.uploadRequestsTotal.pipe(Metric.tagged("upload_id", "test-upload-123"))(Effect.succeed(1));
33
+ // Track file size
34
+ yield* obs.metrics.fileSizeHistogram(Effect.succeed(fileSize));
35
+ // Simulate upload work
36
+ yield* Effect.sleep("100 millis");
37
+ if (shouldFail) {
38
+ return yield* Effect.fail(new Error("NetworkError: Connection timeout"));
39
+ }
40
+ // Track success
41
+ yield* obs.metrics.uploadSuccessTotal.pipe(Metric.tagged("upload_id", "test-upload-123"))(Effect.succeed(1));
42
+ return { uploadId: "test-upload-123", key: "test-file.jpg" };
43
+ });
44
+ // Wrap with span
45
+ const result = yield* uploadEffect.pipe(withStorageSpan("uploadFile", testStorageType, {
46
+ "file.size": fileSize,
47
+ "upload.id": "test-upload-123",
48
+ }));
49
+ // Log completion
50
+ yield* logUploadCompletion(testStorageType, "test-upload-123", {
51
+ fileSize,
52
+ totalDurationMs: 100,
53
+ throughputBps: (fileSize / 100) * 1000,
54
+ });
55
+ return result;
56
+ });
57
+ // ============================================================================
58
+ // Test: Error Classification
59
+ // ============================================================================
60
+ const testErrorClassification = Effect.gen(function* () {
61
+ console.log("\n🔍 Testing Error Classification:");
62
+ const testErrors = [
63
+ { error: { code: "NetworkError" }, expected: "network_error" },
64
+ { error: { code: "InvalidAccessKeyId" }, expected: "authentication_error" },
65
+ { error: { code: "AccessDenied" }, expected: "authorization_error" },
66
+ { error: { code: "SlowDown" }, expected: "throttling_error" },
67
+ { error: { code: "InternalError" }, expected: "server_error" },
68
+ { error: { code: "InvalidRequest" }, expected: "client_error" },
69
+ { error: { code: "UnknownError" }, expected: "unknown_error" },
70
+ ];
71
+ for (const { error, expected } of testErrors) {
72
+ const category = classifyStorageError(error);
73
+ const status = category === expected ? "✅" : "❌";
74
+ console.log(` ${status} ${error.code} -> ${category} (expected: ${expected})`);
75
+ }
76
+ });
77
+ // ============================================================================
78
+ // Test: Error Tracking with Metrics
79
+ // ============================================================================
80
+ const testErrorTracking = Effect.gen(function* () {
81
+ const obs = yield* StorageObservability;
82
+ console.log("\n⚠️ Testing Error Tracking:");
83
+ const error = new Error("ECONNRESET: Connection reset by peer");
84
+ error.code = "ECONNRESET";
85
+ yield* trackStorageError(testStorageType, obs.metrics, "uploadPart", error, { upload_id: "test-upload-456", part_number: 1 });
86
+ console.log(" ✅ Error tracked with metrics and logs");
87
+ });
88
+ // ============================================================================
89
+ // Test: Successful Upload Flow
90
+ // ============================================================================
91
+ const testSuccessfulUpload = Effect.gen(function* () {
92
+ console.log("\n✅ Testing Successful Upload:");
93
+ const result = yield* simulateUpload(1024 * 1024 * 10, false); // 10MB file
94
+ console.log(` ✅ Upload completed: ${result.uploadId} -> ${result.key}`);
95
+ });
96
+ // ============================================================================
97
+ // Test: Failed Upload Flow
98
+ // ============================================================================
99
+ const testFailedUpload = Effect.gen(function* () {
100
+ console.log("\n❌ Testing Failed Upload:");
101
+ const obs = yield* StorageObservability;
102
+ // Use either to handle success and failure cases
103
+ const result = yield* Effect.either(simulateUpload(1024 * 1024 * 5, true));
104
+ if (result._tag === "Left") {
105
+ const error = result.left;
106
+ const errorMsg = error instanceof Error ? error.message : String(error);
107
+ console.log(` ❌ Upload failed as expected: ${errorMsg}`);
108
+ // Track the error
109
+ yield* trackStorageError(testStorageType, obs.metrics, "uploadFile", error, { upload_id: "test-upload-789" });
110
+ console.log(" ✅ Error handled and tracked successfully");
111
+ }
112
+ else {
113
+ console.log(" ❌ Upload should have failed but succeeded");
114
+ }
115
+ });
116
+ // ============================================================================
117
+ // Test: Metrics Snapshot
118
+ // ============================================================================
119
+ const testMetricsSnapshot = Effect.gen(function* () {
120
+ console.log("\n📈 Testing Metrics Snapshot:");
121
+ const snapshot = yield* Metric.snapshot;
122
+ console.log(` ✅ Captured ${snapshot.length} metric(s)`);
123
+ // Display some metrics
124
+ for (const metric of snapshot.slice(0, 5)) {
125
+ console.log(` - ${metric.metricKey.name}: ${JSON.stringify(metric.metricState)}`);
126
+ }
127
+ });
128
+ // ============================================================================
129
+ // Run All Tests
130
+ // ============================================================================
131
+ const runTests = Effect.gen(function* () {
132
+ console.log("🧪 Observability Infrastructure Validation Test");
133
+ console.log("=".repeat(50));
134
+ // Run all tests
135
+ yield* testErrorClassification;
136
+ yield* testSuccessfulUpload;
137
+ yield* testFailedUpload;
138
+ yield* testErrorTracking;
139
+ yield* testMetricsSnapshot;
140
+ console.log("\n" + "=".repeat(50));
141
+ console.log("✅ All tests completed successfully!");
142
+ console.log("\nCore observability infrastructure is working correctly:");
143
+ console.log(" ✅ Layer creation and composition");
144
+ console.log(" ✅ Metrics tracking (counters, histograms, gauges)");
145
+ console.log(" ✅ Error classification and tracking");
146
+ console.log(" ✅ Tracing with spans");
147
+ console.log(" ✅ Structured logging");
148
+ }).pipe(Effect.provide(TestStorageObservabilityLayer));
149
+ // Run the tests
150
+ Effect.runPromise(runTests).catch((error) => {
151
+ console.error("\n❌ Test failed:", error);
152
+ process.exit(1);
153
+ });
@@ -0,0 +1,16 @@
1
+ import { Effect } from "effect";
2
+ import type { UploadServerMetrics } from "./metrics.js";
3
+ export type UploadErrorCategory = "network_error" | "authentication_error" | "authorization_error" | "validation_error" | "size_limit_error" | "storage_error" | "abort_error" | "unknown_error";
4
+ /**
5
+ * Classify upload errors into standard categories
6
+ */
7
+ export declare const classifyUploadError: (error: unknown) => UploadErrorCategory;
8
+ /**
9
+ * Track upload errors with metrics and structured logging
10
+ */
11
+ export declare const trackUploadError: (metrics: UploadServerMetrics, operation: string, error: unknown, context?: Record<string, unknown>) => Effect.Effect<void, never, never>;
12
+ /**
13
+ * Create a custom error classifier for upload operations
14
+ */
15
+ export declare const createUploadErrorClassifier: (customErrorMapping?: (error: unknown) => UploadErrorCategory | null) => (error: unknown) => UploadErrorCategory;
16
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/upload/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAMxD,MAAM,MAAM,mBAAmB,GAC3B,eAAe,GACf,sBAAsB,GACtB,qBAAqB,GACrB,kBAAkB,GAClB,kBAAkB,GAClB,eAAe,GACf,aAAa,GACb,eAAe,CAAC;AAEpB;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAO,OAAO,KAAG,mBAqFpD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAC3B,SAAS,mBAAmB,EAC5B,WAAW,MAAM,EACjB,OAAO,OAAO,EACd,UAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,sCAiCnC,CAAC;AAEL;;GAEG;AACH,eAAO,MAAM,2BAA2B,GACtC,qBAAqB,CAAC,KAAK,EAAE,OAAO,KAAK,mBAAmB,GAAG,IAAI,MAE3D,OAAO,OAAO,KAAG,mBAU1B,CAAC"}
@@ -0,0 +1,107 @@
1
+ import { Effect, Metric } from "effect";
2
+ /**
3
+ * Classify upload errors into standard categories
4
+ */
5
+ export const classifyUploadError = (error) => {
6
+ if (!error || typeof error !== "object")
7
+ return "unknown_error";
8
+ const errorCode = "code" in error ? error.code : undefined;
9
+ const errorName = "name" in error ? error.name : undefined;
10
+ const errorMessage = error instanceof Error ? error.message.toLowerCase() : "";
11
+ // Abort errors
12
+ if (errorCode === "ABORTED" ||
13
+ errorName === "AbortError" ||
14
+ errorMessage.includes("abort")) {
15
+ return "abort_error";
16
+ }
17
+ // Size limit errors
18
+ if (errorCode === "FILE_TOO_LARGE" ||
19
+ errorCode === "LIMIT_FILE_SIZE" ||
20
+ errorCode === "RequestEntityTooLarge" ||
21
+ errorMessage.includes("too large") ||
22
+ errorMessage.includes("size limit") ||
23
+ errorMessage.includes("max size")) {
24
+ return "size_limit_error";
25
+ }
26
+ // Validation errors
27
+ if (errorCode === "INVALID_FILE" ||
28
+ errorCode === "INVALID_METADATA" ||
29
+ errorCode === "VALIDATION_ERROR" ||
30
+ errorMessage.includes("validation") ||
31
+ errorMessage.includes("invalid")) {
32
+ return "validation_error";
33
+ }
34
+ // Network errors
35
+ if (errorCode === "NetworkError" ||
36
+ errorCode === "ECONNRESET" ||
37
+ errorCode === "ENOTFOUND" ||
38
+ errorCode === "ETIMEDOUT" ||
39
+ errorMessage.includes("network") ||
40
+ errorMessage.includes("timeout")) {
41
+ return "network_error";
42
+ }
43
+ // Authentication errors
44
+ if (errorCode === "UNAUTHORIZED" ||
45
+ errorCode === "AuthenticationFailed" ||
46
+ errorName === "AuthenticationError" ||
47
+ errorMessage.includes("authentication") ||
48
+ errorMessage.includes("unauthorized")) {
49
+ return "authentication_error";
50
+ }
51
+ // Authorization errors
52
+ if (errorCode === "FORBIDDEN" ||
53
+ errorCode === "AccessDenied" ||
54
+ errorName === "AuthorizationError" ||
55
+ errorMessage.includes("forbidden") ||
56
+ errorMessage.includes("permission")) {
57
+ return "authorization_error";
58
+ }
59
+ // Storage errors
60
+ if (errorCode === "FILE_WRITE_ERROR" ||
61
+ errorCode === "STORAGE_ERROR" ||
62
+ errorMessage.includes("storage") ||
63
+ errorMessage.includes("write error")) {
64
+ return "storage_error";
65
+ }
66
+ return "unknown_error";
67
+ };
68
+ /**
69
+ * Track upload errors with metrics and structured logging
70
+ */
71
+ export const trackUploadError = (metrics, operation, error, context = {}) => Effect.gen(function* () {
72
+ const errorCategory = classifyUploadError(error);
73
+ // Record error metrics
74
+ const errorMetric = metrics.uploadFailedTotal.pipe(Metric.tagged("operation", operation), Metric.tagged("error_category", errorCategory));
75
+ yield* errorMetric(Effect.succeed(1));
76
+ // Create detailed error context
77
+ const errorDetails = {
78
+ operation,
79
+ error_category: errorCategory,
80
+ error_type: typeof error,
81
+ error_message: error instanceof Error ? error.message : String(error),
82
+ error_code: error && typeof error === "object" && "code" in error
83
+ ? String(error.code)
84
+ : undefined,
85
+ error_name: error && typeof error === "object" && "name" in error
86
+ ? String(error.name)
87
+ : undefined,
88
+ ...context,
89
+ };
90
+ // Log structured error
91
+ yield* Effect.logError(`Upload ${operation} failed`).pipe(Effect.annotateLogs(errorDetails));
92
+ });
93
+ /**
94
+ * Create a custom error classifier for upload operations
95
+ */
96
+ export const createUploadErrorClassifier = (customErrorMapping) => {
97
+ return (error) => {
98
+ // Try custom mapping first
99
+ if (customErrorMapping) {
100
+ const customResult = customErrorMapping(error);
101
+ if (customResult !== null)
102
+ return customResult;
103
+ }
104
+ // Fall back to generic classification
105
+ return classifyUploadError(error);
106
+ };
107
+ };
@@ -0,0 +1,6 @@
1
+ export * from "./metrics.js";
2
+ export * from "./tracing.js";
3
+ export { makeUploadObservabilityLive, UploadObservabilityLive, getUploadMetrics, withUploadDuration, withChunkDuration, } from "./layers.js";
4
+ export * from "./errors.js";
5
+ export * from "./testing.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/upload/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,EACvB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
@@ -0,0 +1,6 @@
1
+ // Upload observability exports
2
+ export * from "./metrics.js";
3
+ export * from "./tracing.js";
4
+ export { makeUploadObservabilityLive, UploadObservabilityLive, getUploadMetrics, withUploadDuration, withChunkDuration, } from "./layers.js";
5
+ export * from "./errors.js";
6
+ export * from "./testing.js";
@@ -0,0 +1,32 @@
1
+ import { Effect, Layer } from "effect";
2
+ import { UploadObservability } from "../core/layers.js";
3
+ /**
4
+ * Create a live upload observability layer with full metrics
5
+ */
6
+ export declare const makeUploadObservabilityLive: (serviceName?: string) => Layer.Layer<UploadObservability>;
7
+ /**
8
+ * Default live upload observability layer
9
+ */
10
+ export declare const UploadObservabilityLive: Layer.Layer<UploadObservability, never, never>;
11
+ /**
12
+ * No-op upload observability layer (for testing or disabled observability)
13
+ */
14
+ export declare const UploadObservabilityDisabled: Layer.Layer<UploadObservability, never, never>;
15
+ /**
16
+ * Helper to get upload metrics from context
17
+ */
18
+ export declare const getUploadMetrics: Effect.Effect<{
19
+ uploadCreated: Effect.Effect<void>;
20
+ uploadCompleted: Effect.Effect<void>;
21
+ uploadFailed: Effect.Effect<void>;
22
+ chunkUploaded: Effect.Effect<void>;
23
+ }, never, UploadObservability>;
24
+ /**
25
+ * Helper to track upload duration
26
+ */
27
+ export declare const withUploadDuration: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R | UploadObservability>;
28
+ /**
29
+ * Helper to track chunk upload duration
30
+ */
31
+ export declare const withChunkDuration: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
32
+ //# sourceMappingURL=layers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layers.d.ts","sourceRoot":"","sources":["../../src/upload/layers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAU,MAAM,QAAQ,CAAC;AAC/C,OAAO,EAEL,mBAAmB,EACpB,MAAM,mBAAmB,CAAC;AAO3B;;GAEG;AACH,eAAO,MAAM,2BAA2B,GACtC,oBAAwC,KACvC,KAAK,CAAC,KAAK,CAAC,mBAAmB,CAqBjC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uBAAuB,gDAAgC,CAAC;AAErE;;GAEG;AACH,eAAO,MAAM,2BAA2B,gDAAsC,CAAC;AAE/E;;GAEG;AACH,eAAO,MAAM,gBAAgB;;;;;8BAG3B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EACxC,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,mBAAmB,CAS7C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EACvC,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CASvB,CAAC"}
@@ -0,0 +1,63 @@
1
+ import { Effect, Layer, Metric } from "effect";
2
+ import { makeUploadObservabilityLayer, UploadObservability, } from "../core/layers.js";
3
+ import { createUploadServerMetrics } from "./metrics.js";
4
+ // ============================================================================
5
+ // Upload Observability Layer Implementation
6
+ // ============================================================================
7
+ /**
8
+ * Create a live upload observability layer with full metrics
9
+ */
10
+ export const makeUploadObservabilityLive = (serviceName = "uploadista-upload-server") => {
11
+ const metrics = createUploadServerMetrics();
12
+ return Layer.succeed(UploadObservability, {
13
+ serviceName,
14
+ enabled: true,
15
+ metrics: {
16
+ uploadCreated: Effect.succeed(metrics.uploadCreatedTotal).pipe(Effect.flatMap((metric) => Metric.increment(metric))),
17
+ uploadCompleted: Effect.succeed(metrics.uploadCompletedTotal).pipe(Effect.flatMap((metric) => Metric.increment(metric))),
18
+ uploadFailed: Effect.succeed(metrics.uploadFailedTotal).pipe(Effect.flatMap((metric) => Metric.increment(metric))),
19
+ chunkUploaded: Effect.succeed(metrics.chunkUploadedTotal).pipe(Effect.flatMap((metric) => Metric.increment(metric))),
20
+ },
21
+ });
22
+ };
23
+ /**
24
+ * Default live upload observability layer
25
+ */
26
+ export const UploadObservabilityLive = makeUploadObservabilityLive();
27
+ /**
28
+ * No-op upload observability layer (for testing or disabled observability)
29
+ */
30
+ export const UploadObservabilityDisabled = makeUploadObservabilityLayer(false);
31
+ /**
32
+ * Helper to get upload metrics from context
33
+ */
34
+ export const getUploadMetrics = Effect.gen(function* () {
35
+ const obs = yield* UploadObservability;
36
+ return obs.metrics;
37
+ });
38
+ /**
39
+ * Helper to track upload duration
40
+ */
41
+ export const withUploadDuration = (effect) => {
42
+ const metrics = createUploadServerMetrics();
43
+ return Effect.gen(function* () {
44
+ const startTime = Date.now();
45
+ const result = yield* effect;
46
+ const duration = (Date.now() - startTime) / 1000; // Convert to seconds
47
+ yield* Metric.update(metrics.uploadDurationHistogram, duration);
48
+ return result;
49
+ }).pipe(Effect.withSpan("upload-operation"));
50
+ };
51
+ /**
52
+ * Helper to track chunk upload duration
53
+ */
54
+ export const withChunkDuration = (effect) => {
55
+ const metrics = createUploadServerMetrics();
56
+ return Effect.gen(function* () {
57
+ const startTime = Date.now();
58
+ const result = yield* effect;
59
+ const duration = (Date.now() - startTime) / 1000; // Convert to seconds
60
+ yield* Metric.update(metrics.chunkUploadDurationHistogram, duration);
61
+ return result;
62
+ }).pipe(Effect.withSpan("chunk-upload"));
63
+ };
@@ -0,0 +1,46 @@
1
+ import { Metric } from "effect";
2
+ /**
3
+ * Upload server metrics for tracking upload operations
4
+ */
5
+ export declare const createUploadServerMetrics: () => {
6
+ uploadCreatedTotal: Metric.Metric.Counter<number>;
7
+ uploadCompletedTotal: Metric.Metric.Counter<number>;
8
+ uploadFailedTotal: Metric.Metric.Counter<number>;
9
+ chunkUploadedTotal: Metric.Metric.Counter<number>;
10
+ uploadFromUrlTotal: Metric.Metric.Counter<number>;
11
+ uploadFromUrlSuccessTotal: Metric.Metric.Counter<number>;
12
+ uploadFromUrlFailedTotal: Metric.Metric.Counter<number>;
13
+ uploadDurationHistogram: Metric.Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>;
14
+ chunkUploadDurationHistogram: Metric.Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>;
15
+ uploadFileSizeHistogram: Metric.Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>;
16
+ chunkSizeHistogram: Metric.Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>;
17
+ activeUploadsGauge: Metric.Metric.Gauge<number>;
18
+ uploadThroughputGauge: Metric.Metric.Gauge<number>;
19
+ uploadLatencySummary: Metric.Metric.Summary<number>;
20
+ chunkLatencySummary: Metric.Metric.Summary<number>;
21
+ };
22
+ /**
23
+ * Type for upload server metrics
24
+ */
25
+ export type UploadServerMetrics = ReturnType<typeof createUploadServerMetrics>;
26
+ /**
27
+ * Default upload server metrics instance
28
+ */
29
+ export declare const uploadServerMetrics: {
30
+ uploadCreatedTotal: Metric.Metric.Counter<number>;
31
+ uploadCompletedTotal: Metric.Metric.Counter<number>;
32
+ uploadFailedTotal: Metric.Metric.Counter<number>;
33
+ chunkUploadedTotal: Metric.Metric.Counter<number>;
34
+ uploadFromUrlTotal: Metric.Metric.Counter<number>;
35
+ uploadFromUrlSuccessTotal: Metric.Metric.Counter<number>;
36
+ uploadFromUrlFailedTotal: Metric.Metric.Counter<number>;
37
+ uploadDurationHistogram: Metric.Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>;
38
+ chunkUploadDurationHistogram: Metric.Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>;
39
+ uploadFileSizeHistogram: Metric.Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>;
40
+ chunkSizeHistogram: Metric.Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>;
41
+ activeUploadsGauge: Metric.Metric.Gauge<number>;
42
+ uploadThroughputGauge: Metric.Metric.Gauge<number>;
43
+ uploadLatencySummary: Metric.Metric.Summary<number>;
44
+ chunkLatencySummary: Metric.Metric.Summary<number>;
45
+ };
46
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/upload/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAoB,MAAM,QAAQ,CAAC;AAMlD;;GAEG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;CAkGpC,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,UAAU,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE/E;;GAEG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;CAA8B,CAAC"}