@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,5 @@
1
+
2
+ 
3
+ > @uploadista/observability@0.0.2 build /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/observability
4
+ > tsc -b
5
+
File without changes
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.
@@ -0,0 +1,8 @@
1
+ import { Effect } from "effect";
2
+ import type { StorageMetrics } from "./metrics.js";
3
+ export type StorageErrorCategory = "network_error" | "authentication_error" | "authorization_error" | "throttling_error" | "server_error" | "client_error" | "unknown_error";
4
+ export declare const classifyStorageError: (error: unknown) => StorageErrorCategory;
5
+ export declare const createStorageErrorClassifier: (storageType: string, customErrorMapping?: (error: unknown) => StorageErrorCategory | null) => (error: unknown) => StorageErrorCategory;
6
+ export declare const trackStorageError: (storageType: string, metrics: StorageMetrics, operation: string, error: unknown, context?: Record<string, unknown>, errorClassifier?: (error: unknown) => StorageErrorCategory) => Effect.Effect<void, never, never>;
7
+ export declare const createStorageErrorTracker: (storageType: string, metrics: StorageMetrics, customErrorClassifier?: (error: unknown) => StorageErrorCategory | null) => (operation: string, error: unknown, context?: Record<string, unknown>) => Effect.Effect<void, never, never>;
8
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAMnD,MAAM,MAAM,oBAAoB,GAC5B,eAAe,GACf,sBAAsB,GACtB,qBAAqB,GACrB,kBAAkB,GAClB,cAAc,GACd,cAAc,GACd,eAAe,CAAC;AAGpB,eAAO,MAAM,oBAAoB,GAAI,OAAO,OAAO,KAAG,oBAmFrD,CAAC;AAGF,eAAO,MAAM,4BAA4B,GACvC,aAAa,MAAM,EACnB,qBAAqB,CAAC,KAAK,EAAE,OAAO,KAAK,oBAAoB,GAAG,IAAI,MAE5D,OAAO,OAAO,KAAG,oBAU1B,CAAC;AAGF,eAAO,MAAM,iBAAiB,GAC5B,aAAa,MAAM,EACnB,SAAS,cAAc,EACvB,WAAW,MAAM,EACjB,OAAO,OAAO,EACd,UAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACrC,0BA7G0C,OAAO,KAAG,oBA6Gd,sCAkCpC,CAAC;AAGL,eAAO,MAAM,yBAAyB,GACpC,aAAa,MAAM,EACnB,SAAS,cAAc,EACvB,wBAAwB,CAAC,KAAK,EAAE,OAAO,KAAK,oBAAoB,GAAG,IAAI,MAQrE,WAAW,MAAM,EACjB,OAAO,OAAO,EACd,UAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,sCAUxC,CAAC"}
@@ -0,0 +1,108 @@
1
+ import { Effect, Metric } from "effect";
2
+ // Generic error classifier - can be extended per storage type
3
+ export const classifyStorageError = (error) => {
4
+ if (!error || typeof error !== "object")
5
+ return "unknown_error";
6
+ const errorCode = "code" in error ? error.code : undefined;
7
+ const errorName = "name" in error ? error.name : undefined;
8
+ const errorMessage = error instanceof Error ? error.message.toLowerCase() : "";
9
+ // Network errors (common across all storage types)
10
+ if (errorCode === "NetworkError" ||
11
+ errorCode === "ECONNRESET" ||
12
+ errorCode === "ENOTFOUND" ||
13
+ errorCode === "ETIMEDOUT" ||
14
+ errorMessage.indexOf("network") >= 0 ||
15
+ errorMessage.indexOf("timeout") >= 0) {
16
+ return "network_error";
17
+ }
18
+ // Authentication errors (common patterns)
19
+ if (errorCode === "InvalidAccessKeyId" ||
20
+ errorCode === "SignatureDoesNotMatch" ||
21
+ errorCode === "TokenRefreshRequired" ||
22
+ errorCode === "AuthenticationFailed" ||
23
+ errorName === "AuthenticationError" ||
24
+ errorMessage.indexOf("authentication") >= 0 ||
25
+ errorMessage.indexOf("unauthorized") >= 0) {
26
+ return "authentication_error";
27
+ }
28
+ // Authorization errors
29
+ if (errorCode === "AccessDenied" ||
30
+ errorCode === "AccountProblem" ||
31
+ errorCode === "Forbidden" ||
32
+ errorName === "AuthorizationError" ||
33
+ errorMessage.indexOf("forbidden") >= 0 ||
34
+ errorMessage.indexOf("permission") >= 0) {
35
+ return "authorization_error";
36
+ }
37
+ // Throttling errors
38
+ if (errorCode === "SlowDown" ||
39
+ errorCode === "RequestTimeTooSkewed" ||
40
+ errorCode === "TooManyRequests" ||
41
+ errorName === "ThrottlingError" ||
42
+ errorMessage.indexOf("throttl") >= 0 ||
43
+ errorMessage.indexOf("rate limit") >= 0) {
44
+ return "throttling_error";
45
+ }
46
+ // Server errors
47
+ if (errorCode === "InternalError" ||
48
+ errorCode === "ServiceUnavailable" ||
49
+ errorCode === "InternalServerError" ||
50
+ errorName === "ServerError" ||
51
+ errorMessage.indexOf("server error") >= 0 ||
52
+ errorMessage.indexOf("service unavailable") >= 0) {
53
+ return "server_error";
54
+ }
55
+ // Client errors
56
+ if (errorCode === "InvalidRequest" ||
57
+ errorCode === "MalformedXML" ||
58
+ errorCode === "RequestEntityTooLarge" ||
59
+ errorCode === "BadRequest" ||
60
+ errorName === "ClientError" ||
61
+ errorMessage.indexOf("bad request") >= 0 ||
62
+ errorMessage.indexOf("invalid") >= 0) {
63
+ return "client_error";
64
+ }
65
+ return "unknown_error";
66
+ };
67
+ // Storage-specific error classifier factory
68
+ export const createStorageErrorClassifier = (storageType, customErrorMapping) => {
69
+ return (error) => {
70
+ // Try custom mapping first
71
+ if (customErrorMapping) {
72
+ const customResult = customErrorMapping(error);
73
+ if (customResult !== null)
74
+ return customResult;
75
+ }
76
+ // Fall back to generic classification
77
+ return classifyStorageError(error);
78
+ };
79
+ };
80
+ // Generic error tracking function
81
+ export const trackStorageError = (storageType, metrics, operation, error, context = {}, errorClassifier = classifyStorageError) => Effect.gen(function* () {
82
+ const errorCategory = errorClassifier(error);
83
+ // Record error metrics
84
+ const errorMetric = metrics.uploadErrorsTotal.pipe(Metric.tagged("operation", operation), Metric.tagged("error_category", errorCategory));
85
+ yield* errorMetric(Effect.succeed(1));
86
+ // Create detailed error context
87
+ const errorDetails = {
88
+ storage_type: storageType,
89
+ operation,
90
+ error_category: errorCategory,
91
+ error_type: typeof error,
92
+ error_message: error instanceof Error ? error.message : String(error),
93
+ error_code: error && typeof error === "object" && "code" in error
94
+ ? error.code
95
+ : undefined,
96
+ error_name: error && typeof error === "object" && "name" in error
97
+ ? error.name
98
+ : undefined,
99
+ ...context,
100
+ };
101
+ // Log structured error
102
+ yield* Effect.logError(`${storageType.toUpperCase()} ${operation} failed`).pipe(Effect.annotateLogs(errorDetails));
103
+ });
104
+ // Factory for storage-specific error tracking
105
+ export const createStorageErrorTracker = (storageType, metrics, customErrorClassifier) => {
106
+ const errorClassifier = createStorageErrorClassifier(storageType, customErrorClassifier);
107
+ return (operation, error, context = {}) => trackStorageError(storageType, metrics, operation, error, context, errorClassifier);
108
+ };
@@ -0,0 +1,8 @@
1
+ export * from "./errors.js";
2
+ export * from "./layers.js";
3
+ export * from "./logging.js";
4
+ export * from "./metrics.js";
5
+ export * from "./testing.js";
6
+ export * from "./tracing.js";
7
+ export * from "./utilities.js";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAEA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,8 @@
1
+ // Core observability exports
2
+ export * from "./errors.js";
3
+ export * from "./layers.js";
4
+ export * from "./logging.js";
5
+ export * from "./metrics.js";
6
+ export * from "./testing.js";
7
+ export * from "./tracing.js";
8
+ export * from "./utilities.js";
@@ -0,0 +1,104 @@
1
+ import { Context, Effect, Layer, Option } from "effect";
2
+ import type { StorageMetrics } from "./metrics.js";
3
+ /**
4
+ * Core observability service providing tracing, metrics, and logging capabilities
5
+ */
6
+ export interface ObservabilityService {
7
+ readonly serviceName: string;
8
+ readonly enabled: boolean;
9
+ }
10
+ declare const Observability_base: Context.TagClass<Observability, "Observability", ObservabilityService>;
11
+ /**
12
+ * Observability service tag for Effect Context
13
+ */
14
+ export declare class Observability extends Observability_base {
15
+ }
16
+ /**
17
+ * Storage observability service extending base observability with storage-specific metrics
18
+ */
19
+ export interface StorageObservabilityService extends ObservabilityService {
20
+ readonly storageType: string;
21
+ readonly metrics: StorageMetrics;
22
+ }
23
+ declare const StorageObservability_base: Context.TagClass<StorageObservability, "StorageObservability", StorageObservabilityService>;
24
+ /**
25
+ * Storage observability service tag
26
+ */
27
+ export declare class StorageObservability extends StorageObservability_base {
28
+ }
29
+ /**
30
+ * Upload observability service for upload-specific operations
31
+ */
32
+ export interface UploadObservabilityService extends ObservabilityService {
33
+ readonly metrics: {
34
+ uploadCreated: Effect.Effect<void>;
35
+ uploadCompleted: Effect.Effect<void>;
36
+ uploadFailed: Effect.Effect<void>;
37
+ chunkUploaded: Effect.Effect<void>;
38
+ };
39
+ }
40
+ declare const UploadObservability_base: Context.TagClass<UploadObservability, "UploadObservability", UploadObservabilityService>;
41
+ /**
42
+ * Upload observability service tag
43
+ */
44
+ export declare class UploadObservability extends UploadObservability_base {
45
+ }
46
+ /**
47
+ * Flow observability service for flow execution operations
48
+ */
49
+ export interface FlowObservabilityService extends ObservabilityService {
50
+ readonly metrics: {
51
+ flowStarted: Effect.Effect<void>;
52
+ flowCompleted: Effect.Effect<void>;
53
+ flowFailed: Effect.Effect<void>;
54
+ nodeExecuted: Effect.Effect<void>;
55
+ };
56
+ }
57
+ declare const FlowObservability_base: Context.TagClass<FlowObservability, "FlowObservability", FlowObservabilityService>;
58
+ /**
59
+ * Flow observability service tag
60
+ */
61
+ export declare class FlowObservability extends FlowObservability_base {
62
+ }
63
+ /**
64
+ * Create a base observability layer
65
+ */
66
+ export declare const makeObservabilityLayer: (serviceName: string, enabled?: boolean) => Layer.Layer<Observability>;
67
+ /**
68
+ * Create a storage observability layer
69
+ */
70
+ export declare const makeStorageObservabilityLayer: (storageType: string, metrics: StorageMetrics, enabled?: boolean) => Layer.Layer<StorageObservability>;
71
+ /**
72
+ * Create an upload observability layer
73
+ */
74
+ export declare const makeUploadObservabilityLayer: (enabled?: boolean) => Layer.Layer<UploadObservability>;
75
+ /**
76
+ * Create a flow observability layer
77
+ */
78
+ export declare const makeFlowObservabilityLayer: (enabled?: boolean) => Layer.Layer<FlowObservability>;
79
+ /**
80
+ * No-op observability layer (disabled)
81
+ */
82
+ export declare const ObservabilityDisabled: Layer.Layer<Observability, never, never>;
83
+ /**
84
+ * No-op storage observability layer
85
+ */
86
+ export declare const StorageObservabilityDisabled: (storageType: string) => Layer.Layer<StorageObservability, never, never>;
87
+ /**
88
+ * No-op upload observability layer
89
+ */
90
+ export declare const UploadObservabilityDisabled: Layer.Layer<UploadObservability, never, never>;
91
+ /**
92
+ * No-op flow observability layer
93
+ */
94
+ export declare const FlowObservabilityDisabled: Layer.Layer<FlowObservability, never, never>;
95
+ /**
96
+ * Check if observability is enabled in the current context
97
+ */
98
+ export declare const isObservabilityEnabled: Effect.Effect<boolean, never, never>;
99
+ /**
100
+ * Execute an effect only if observability is enabled
101
+ */
102
+ export declare const whenObservabilityEnabled: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<Option.Option<A>, E, R | Observability>;
103
+ export {};
104
+ //# sourceMappingURL=layers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layers.d.ts","sourceRoot":"","sources":["../../src/core/layers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAMnD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;;AAED;;GAEG;AACH,qBAAa,aAAc,SAAQ,kBAGhC;CAAG;AAEN;;GAEG;AACH,MAAM,WAAW,2BAA4B,SAAQ,oBAAoB;IACvE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;CAClC;;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,yBAGvC;CAAG;AAEN;;GAEG;AACH,MAAM,WAAW,0BAA2B,SAAQ,oBAAoB;IACtE,QAAQ,CAAC,OAAO,EAAE;QAChB,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,eAAe,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACrC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAClC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;KACpC,CAAC;CACH;;AAED;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,wBAGtC;CAAG;AAEN;;GAEG;AACH,MAAM,WAAW,wBAAyB,SAAQ,oBAAoB;IACpE,QAAQ,CAAC,OAAO,EAAE;QAChB,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACjC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAChC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;KACnC,CAAC;CACH;;AAED;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,sBAGpC;CAAG;AAMN;;GAEG;AACH,eAAO,MAAM,sBAAsB,GACjC,aAAa,MAAM,EACnB,iBAAc,KACb,KAAK,CAAC,KAAK,CAAC,aAAa,CAIxB,CAAC;AAEL;;GAEG;AACH,eAAO,MAAM,6BAA6B,GACxC,aAAa,MAAM,EACnB,SAAS,cAAc,EACvB,iBAAc,KACb,KAAK,CAAC,KAAK,CAAC,oBAAoB,CAM/B,CAAC;AAEL;;GAEG;AACH,eAAO,MAAM,4BAA4B,GACvC,iBAAc,KACb,KAAK,CAAC,KAAK,CAAC,mBAAmB,CAU9B,CAAC;AAEL;;GAEG;AACH,eAAO,MAAM,0BAA0B,GACrC,iBAAc,KACb,KAAK,CAAC,KAAK,CAAC,iBAAiB,CAU5B,CAAC;AAML;;GAEG;AACH,eAAO,MAAM,qBAAqB,0CAGjC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,4BAA4B,GAAI,aAAa,MAAM,oDAK7D,CAAC;AAEJ;;GAEG;AACH,eAAO,MAAM,2BAA2B,gDAAsC,CAAC;AAE/E;;GAEG;AACH,eAAO,MAAM,yBAAyB,8CAAoC,CAAC;AAM3E;;GAEG;AACH,eAAO,MAAM,sBAAsB,sCAMjC,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,wBAAwB,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAC9C,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC7B,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,aAAa,CAQnD,CAAC"}
@@ -0,0 +1,110 @@
1
+ import { Context, Effect, Layer, Option } from "effect";
2
+ /**
3
+ * Observability service tag for Effect Context
4
+ */
5
+ export class Observability extends Context.Tag("Observability")() {
6
+ }
7
+ /**
8
+ * Storage observability service tag
9
+ */
10
+ export class StorageObservability extends Context.Tag("StorageObservability")() {
11
+ }
12
+ /**
13
+ * Upload observability service tag
14
+ */
15
+ export class UploadObservability extends Context.Tag("UploadObservability")() {
16
+ }
17
+ /**
18
+ * Flow observability service tag
19
+ */
20
+ export class FlowObservability extends Context.Tag("FlowObservability")() {
21
+ }
22
+ // ============================================================================
23
+ // Layer Factories
24
+ // ============================================================================
25
+ /**
26
+ * Create a base observability layer
27
+ */
28
+ export const makeObservabilityLayer = (serviceName, enabled = true) => Layer.succeed(Observability, {
29
+ serviceName,
30
+ enabled,
31
+ });
32
+ /**
33
+ * Create a storage observability layer
34
+ */
35
+ export const makeStorageObservabilityLayer = (storageType, metrics, enabled = true) => Layer.succeed(StorageObservability, {
36
+ serviceName: `uploadista-${storageType}-store`,
37
+ storageType,
38
+ metrics,
39
+ enabled,
40
+ });
41
+ /**
42
+ * Create an upload observability layer
43
+ */
44
+ export const makeUploadObservabilityLayer = (enabled = true) => Layer.succeed(UploadObservability, {
45
+ serviceName: "uploadista-upload-server",
46
+ enabled,
47
+ metrics: {
48
+ uploadCreated: Effect.void,
49
+ uploadCompleted: Effect.void,
50
+ uploadFailed: Effect.void,
51
+ chunkUploaded: Effect.void,
52
+ },
53
+ });
54
+ /**
55
+ * Create a flow observability layer
56
+ */
57
+ export const makeFlowObservabilityLayer = (enabled = true) => Layer.succeed(FlowObservability, {
58
+ serviceName: "uploadista-flow-engine",
59
+ enabled,
60
+ metrics: {
61
+ flowStarted: Effect.void,
62
+ flowCompleted: Effect.void,
63
+ flowFailed: Effect.void,
64
+ nodeExecuted: Effect.void,
65
+ },
66
+ });
67
+ // ============================================================================
68
+ // No-op Layers (for testing and opt-out)
69
+ // ============================================================================
70
+ /**
71
+ * No-op observability layer (disabled)
72
+ */
73
+ export const ObservabilityDisabled = makeObservabilityLayer("uploadista-disabled", false);
74
+ /**
75
+ * No-op storage observability layer
76
+ */
77
+ export const StorageObservabilityDisabled = (storageType) => makeStorageObservabilityLayer(storageType, {}, // No-op metrics
78
+ false);
79
+ /**
80
+ * No-op upload observability layer
81
+ */
82
+ export const UploadObservabilityDisabled = makeUploadObservabilityLayer(false);
83
+ /**
84
+ * No-op flow observability layer
85
+ */
86
+ export const FlowObservabilityDisabled = makeFlowObservabilityLayer(false);
87
+ // ============================================================================
88
+ // Helper Functions
89
+ // ============================================================================
90
+ /**
91
+ * Check if observability is enabled in the current context
92
+ */
93
+ export const isObservabilityEnabled = Effect.gen(function* () {
94
+ const observability = yield* Effect.serviceOption(Observability);
95
+ return Option.match(observability, {
96
+ onNone: () => false,
97
+ onSome: (obs) => obs.enabled,
98
+ });
99
+ });
100
+ /**
101
+ * Execute an effect only if observability is enabled
102
+ */
103
+ export const whenObservabilityEnabled = (effect) => Effect.gen(function* () {
104
+ const enabled = yield* isObservabilityEnabled;
105
+ if (enabled) {
106
+ const result = yield* effect;
107
+ return Option.some(result);
108
+ }
109
+ return Option.none();
110
+ });
@@ -0,0 +1,18 @@
1
+ import { Effect } from "effect";
2
+ export declare const logWithContext: (message: string, context: Record<string, unknown>) => Effect.Effect<void, never, never>;
3
+ export declare const logUploadProgress: (storageType: string, uploadId: string, progress: {
4
+ uploadedBytes: number;
5
+ totalBytes: number;
6
+ partNumber?: number;
7
+ speed?: number;
8
+ }) => Effect.Effect<void, never, never>;
9
+ export declare const logStorageOperation: (storageType: string, operation: string, uploadId: string, metadata?: Record<string, unknown>) => Effect.Effect<void, never, never>;
10
+ export declare const logUploadCompletion: (storageType: string, uploadId: string, metrics: {
11
+ fileSize: number;
12
+ totalDurationMs: number;
13
+ partsCount?: number;
14
+ averagePartSize?: number;
15
+ throughputBps?: number;
16
+ retryCount?: number;
17
+ }) => Effect.Effect<void, never, never>;
18
+ //# sourceMappingURL=logging.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../../src/core/logging.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAMhC,eAAO,MAAM,cAAc,GACzB,SAAS,MAAM,EACf,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,sCACyB,CAAC;AAE5D,eAAO,MAAM,iBAAiB,GAC5B,aAAa,MAAM,EACnB,UAAU,MAAM,EAChB,UAAU;IACR,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,sCAYC,CAAC;AAEL,eAAO,MAAM,mBAAmB,GAC9B,aAAa,MAAM,EACnB,WAAW,MAAM,EACjB,UAAU,MAAM,EAChB,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,sCAOhC,CAAC;AAEL,eAAO,MAAM,mBAAmB,GAC9B,aAAa,MAAM,EACnB,UAAU,MAAM,EAChB,SAAS;IACP,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,sCAwBF,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { Effect } from "effect";
2
+ // ============================================================================
3
+ // Enhanced Logging Helpers (Storage-agnostic)
4
+ // ============================================================================
5
+ export const logWithContext = (message, context) => Effect.log(message).pipe(Effect.annotateLogs(context));
6
+ export const logUploadProgress = (storageType, uploadId, progress) => logWithContext("Upload progress", {
7
+ storage_type: storageType,
8
+ upload_id: uploadId,
9
+ uploaded_bytes: progress.uploadedBytes,
10
+ total_bytes: progress.totalBytes,
11
+ progress_percentage: Math.round((progress.uploadedBytes / progress.totalBytes) * 100),
12
+ ...(progress.partNumber && { part_number: progress.partNumber }),
13
+ ...(progress.speed && { upload_speed_bps: progress.speed }),
14
+ });
15
+ export const logStorageOperation = (storageType, operation, uploadId, metadata) => logWithContext(`${storageType.toUpperCase()} ${operation}`, {
16
+ storage_type: storageType,
17
+ operation,
18
+ upload_id: uploadId,
19
+ ...metadata,
20
+ });
21
+ export const logUploadCompletion = (storageType, uploadId, metrics) => {
22
+ const throughputMBps = metrics.throughputBps
23
+ ? metrics.throughputBps / (1024 * 1024)
24
+ : 0;
25
+ return logWithContext(`${storageType.toUpperCase()} upload completed`, {
26
+ storage_type: storageType,
27
+ upload_id: uploadId,
28
+ file_size_bytes: metrics.fileSize,
29
+ file_size_mb: Math.round((metrics.fileSize / (1024 * 1024)) * 100) / 100,
30
+ total_duration_ms: metrics.totalDurationMs,
31
+ total_duration_seconds: Math.round((metrics.totalDurationMs / 1000) * 100) / 100,
32
+ throughput_bps: metrics.throughputBps,
33
+ throughput_mbps: Math.round(throughputMBps * 100) / 100,
34
+ ...(metrics.partsCount && { parts_count: metrics.partsCount }),
35
+ ...(metrics.averagePartSize && {
36
+ average_part_size_bytes: metrics.averagePartSize,
37
+ average_part_size_mb: Math.round((metrics.averagePartSize / (1024 * 1024)) * 100) / 100,
38
+ }),
39
+ ...(metrics.retryCount && { retry_count: metrics.retryCount }),
40
+ });
41
+ };
@@ -0,0 +1,37 @@
1
+ import { Metric } from "effect";
2
+ export declare const createUploadMetrics: (storageType: string) => {
3
+ uploadRequestsTotal: Metric.Metric.Counter<number>;
4
+ uploadPartsTotal: Metric.Metric.Counter<number>;
5
+ uploadSuccessTotal: Metric.Metric.Counter<number>;
6
+ uploadErrorsTotal: Metric.Metric.Counter<number>;
7
+ apiCallsTotal: Metric.Metric.Counter<number>;
8
+ };
9
+ export declare const createUploadHistograms: (storageType: string) => {
10
+ uploadDurationHistogram: Metric.Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>;
11
+ partUploadDurationHistogram: Metric.Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>;
12
+ fileSizeHistogram: Metric.Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>;
13
+ partSizeHistogram: Metric.Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>;
14
+ };
15
+ export declare const createUploadGauges: (storageType: string) => {
16
+ activeUploadsGauge: Metric.Metric.Gauge<number>;
17
+ uploadThroughputGauge: Metric.Metric.Gauge<number>;
18
+ };
19
+ export declare const createUploadSummaries: (storageType: string) => {
20
+ uploadLatencySummary: Metric.Metric.Summary<number>;
21
+ };
22
+ export declare const createStorageMetrics: (storageType: string) => {
23
+ uploadLatencySummary: Metric.Metric.Summary<number>;
24
+ activeUploadsGauge: Metric.Metric.Gauge<number>;
25
+ uploadThroughputGauge: Metric.Metric.Gauge<number>;
26
+ uploadDurationHistogram: Metric.Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>;
27
+ partUploadDurationHistogram: Metric.Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>;
28
+ fileSizeHistogram: Metric.Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>;
29
+ partSizeHistogram: Metric.Metric<import("effect/MetricKeyType").MetricKeyType.Histogram, number, import("effect/MetricState").MetricState.Histogram>;
30
+ uploadRequestsTotal: Metric.Metric.Counter<number>;
31
+ uploadPartsTotal: Metric.Metric.Counter<number>;
32
+ uploadSuccessTotal: Metric.Metric.Counter<number>;
33
+ uploadErrorsTotal: Metric.Metric.Counter<number>;
34
+ apiCallsTotal: Metric.Metric.Counter<number>;
35
+ };
36
+ export type StorageMetrics = ReturnType<typeof createStorageMetrics>;
37
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/core/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAoB,MAAM,QAAQ,CAAC;AAOlD,eAAO,MAAM,mBAAmB,GAAI,aAAa,MAAM;;;;;;CAoBrD,CAAC;AAGH,eAAO,MAAM,sBAAsB,GAAI,aAAa,MAAM;;;;;CAwCxD,CAAC;AAGH,eAAO,MAAM,kBAAkB,GAAI,aAAa,MAAM;;;CAWpD,CAAC;AAGH,eAAO,MAAM,qBAAqB,GAAI,aAAa,MAAM;;CASvD,CAAC;AAGH,eAAO,MAAM,oBAAoB,GAAI,aAAa,MAAM;;;;;;;;;;;;;CAKtD,CAAC;AAGH,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
@@ -0,0 +1,72 @@
1
+ import { Metric, MetricBoundaries } from "effect";
2
+ // ============================================================================
3
+ // Core Storage Metrics (reusable across all storage types)
4
+ // ============================================================================
5
+ // Counter metrics
6
+ export const createUploadMetrics = (storageType) => ({
7
+ uploadRequestsTotal: Metric.counter(`${storageType}_upload_requests_total`, {
8
+ description: `Total number of upload requests for ${storageType}`,
9
+ }),
10
+ uploadPartsTotal: Metric.counter(`${storageType}_upload_parts_total`, {
11
+ description: `Total number of individual parts uploaded for ${storageType}`,
12
+ }),
13
+ uploadSuccessTotal: Metric.counter(`${storageType}_upload_success_total`, {
14
+ description: `Total number of successful uploads for ${storageType}`,
15
+ }),
16
+ uploadErrorsTotal: Metric.counter(`${storageType}_upload_errors_total`, {
17
+ description: `Total number of upload errors for ${storageType}`,
18
+ }),
19
+ apiCallsTotal: Metric.counter(`${storageType}_api_calls_total`, {
20
+ description: `Total number of API calls for ${storageType}`,
21
+ }),
22
+ });
23
+ // Histogram metrics for timing and sizes (reusable)
24
+ export const createUploadHistograms = (storageType) => ({
25
+ uploadDurationHistogram: Metric.histogram(`${storageType}_upload_duration_seconds`, MetricBoundaries.exponential({
26
+ start: 0.01, // 10ms
27
+ factor: 2,
28
+ count: 20, // Up to ~10 seconds
29
+ }), `Duration of upload operations in seconds for ${storageType}`),
30
+ partUploadDurationHistogram: Metric.histogram(`${storageType}_part_upload_duration_seconds`, MetricBoundaries.exponential({
31
+ start: 0.001, // 1ms
32
+ factor: 2,
33
+ count: 15, // Up to ~32 seconds
34
+ }), `Duration of individual part uploads in seconds for ${storageType}`),
35
+ fileSizeHistogram: Metric.histogram(`${storageType}_file_size_bytes`, MetricBoundaries.exponential({
36
+ start: 1024, // 1KB
37
+ factor: 2,
38
+ count: 25, // Up to ~33GB
39
+ }), `Size of uploaded files in bytes for ${storageType}`),
40
+ partSizeHistogram: Metric.histogram(`${storageType}_part_size_bytes`, MetricBoundaries.linear({
41
+ start: 5_242_880, // 5MB (minimum part size)
42
+ width: 1_048_576, // 1MB increments
43
+ count: 20, // Up to ~25MB
44
+ }), `Size of upload parts in bytes for ${storageType}`),
45
+ });
46
+ // Gauge metrics for current state (reusable)
47
+ export const createUploadGauges = (storageType) => ({
48
+ activeUploadsGauge: Metric.gauge(`${storageType}_active_uploads`, {
49
+ description: `Number of currently active uploads for ${storageType}`,
50
+ }),
51
+ uploadThroughputGauge: Metric.gauge(`${storageType}_upload_throughput_bytes_per_second`, {
52
+ description: `Current upload throughput in bytes per second for ${storageType}`,
53
+ }),
54
+ });
55
+ // Summary metrics for percentiles (reusable)
56
+ export const createUploadSummaries = (storageType) => ({
57
+ uploadLatencySummary: Metric.summary({
58
+ name: `${storageType}_upload_latency_seconds`,
59
+ maxAge: "10 minutes",
60
+ maxSize: 1000,
61
+ error: 0.01,
62
+ quantiles: [0.5, 0.9, 0.95, 0.99],
63
+ description: `Upload latency percentiles for ${storageType}`,
64
+ }),
65
+ });
66
+ // Combined metrics factory
67
+ export const createStorageMetrics = (storageType) => ({
68
+ ...createUploadMetrics(storageType),
69
+ ...createUploadHistograms(storageType),
70
+ ...createUploadGauges(storageType),
71
+ ...createUploadSummaries(storageType),
72
+ });
@@ -0,0 +1,43 @@
1
+ import { Effect, Layer } from "effect";
2
+ import { FlowObservability, StorageObservability, UploadObservability } from "./layers.js";
3
+ /**
4
+ * Mock storage observability for testing
5
+ */
6
+ export declare const makeTestStorageObservability: (storageType: string) => Layer.Layer<StorageObservability>;
7
+ /**
8
+ * Mock upload observability for testing
9
+ */
10
+ export declare const makeTestUploadObservability: () => Layer.Layer<UploadObservability>;
11
+ /**
12
+ * Mock flow observability for testing
13
+ */
14
+ export declare const makeTestFlowObservability: () => Layer.Layer<FlowObservability>;
15
+ /**
16
+ * Capture metrics snapshot from an effect for testing
17
+ * Note: Metric snapshots are simplified - for full metric testing,
18
+ * use Effect's built-in metric testing utilities
19
+ */
20
+ export declare const captureMetrics: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
21
+ /**
22
+ * Test helper to capture metrics around effect execution
23
+ * This is a simplified version - for production testing,
24
+ * use Effect's metric testing utilities
25
+ */
26
+ export declare const withMetricTracking: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
27
+ /**
28
+ * Test fixture for observability testing
29
+ */
30
+ export interface ObservabilityTestFixture {
31
+ readonly storageObservability: Layer.Layer<StorageObservability>;
32
+ readonly uploadObservability: Layer.Layer<UploadObservability>;
33
+ readonly flowObservability: Layer.Layer<FlowObservability>;
34
+ }
35
+ /**
36
+ * Create a complete test fixture with all observability layers
37
+ */
38
+ export declare const createTestFixture: (storageType?: string) => ObservabilityTestFixture;
39
+ /**
40
+ * Run an effect with test observability layers
41
+ */
42
+ export declare const runWithTestObservability: <A, E>(effect: Effect.Effect<A, E, StorageObservability | UploadObservability | FlowObservability>, storageType?: string) => Effect.Effect<A, E>;
43
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../../src/core/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAU,MAAM,QAAQ,CAAC;AAM/C,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,aAAa,CAAC;AAOrB;;GAEG;AACH,eAAO,MAAM,4BAA4B,GACvC,aAAa,MAAM,KAClB,KAAK,CAAC,KAAK,CAAC,oBAAoB,CASlC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,2BAA2B,QAClC,KAAK,CAAC,KAAK,CAAC,mBAAmB,CAYlC,CAAC;AAEJ;;GAEG;AACH,eAAO,MAAM,yBAAyB,QAAO,KAAK,CAAC,KAAK,CAAC,iBAAiB,CAYzE,CAAC;AAMF;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EACpC,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAMpB,CAAC;AAEL;;;;GAIG;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,CAQpB,CAAC;AAEL;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,oBAAoB,EAAE,KAAK,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;IACjE,QAAQ,CAAC,mBAAmB,EAAE,KAAK,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;IAC/D,QAAQ,CAAC,iBAAiB,EAAE,KAAK,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;CAC5D;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAC5B,oBAA4B,KAC3B,wBAID,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,wBAAwB,GAAI,CAAC,EAAE,CAAC,EAC3C,QAAQ,MAAM,CAAC,MAAM,CACnB,CAAC,EACD,CAAC,EACD,oBAAoB,GAAG,mBAAmB,GAAG,iBAAiB,CAC/D,EACD,oBAA4B,KAC3B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAOpB,CAAC"}