@interfere/react 0.1.0-alpha.2 → 0.1.0-alpha.25

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 (205) hide show
  1. package/dist/error-boundary.d.mts +27 -0
  2. package/dist/error-boundary.d.mts.map +1 -0
  3. package/dist/error-boundary.mjs +42 -0
  4. package/dist/error-boundary.mjs.map +1 -0
  5. package/dist/internal/client.d.mts +13 -0
  6. package/dist/internal/client.d.mts.map +1 -0
  7. package/dist/internal/client.mjs +80 -0
  8. package/dist/internal/client.mjs.map +1 -0
  9. package/dist/internal/config.d.mts +9 -0
  10. package/dist/internal/config.d.mts.map +1 -0
  11. package/dist/internal/config.mjs +27 -0
  12. package/dist/internal/config.mjs.map +1 -0
  13. package/dist/internal/context.d.mts +6 -0
  14. package/dist/internal/context.d.mts.map +1 -0
  15. package/dist/internal/context.mjs +32 -0
  16. package/dist/internal/context.mjs.map +1 -0
  17. package/dist/internal/envelope.d.mts +14 -0
  18. package/dist/internal/envelope.d.mts.map +1 -0
  19. package/dist/internal/envelope.mjs +20 -0
  20. package/dist/internal/envelope.mjs.map +1 -0
  21. package/dist/internal/errors.d.mts +4 -0
  22. package/dist/internal/errors.d.mts.map +1 -0
  23. package/dist/internal/errors.mjs +4 -0
  24. package/dist/internal/errors.mjs.map +1 -0
  25. package/dist/internal/sw.d.mts +4 -0
  26. package/dist/internal/sw.d.mts.map +1 -0
  27. package/dist/internal/sw.mjs +10 -0
  28. package/dist/internal/sw.mjs.map +1 -0
  29. package/dist/plugins/errors.d.mts +6 -0
  30. package/dist/plugins/errors.d.mts.map +1 -0
  31. package/dist/plugins/errors.mjs +59 -0
  32. package/dist/plugins/errors.mjs.map +1 -0
  33. package/dist/plugins/lib/loader.d.mts +9 -0
  34. package/dist/plugins/lib/loader.d.mts.map +1 -0
  35. package/dist/plugins/lib/loader.mjs +47 -0
  36. package/dist/plugins/lib/loader.mjs.map +1 -0
  37. package/dist/plugins/lib/types.d.mts +14 -0
  38. package/dist/plugins/lib/types.d.mts.map +1 -0
  39. package/dist/plugins/lib/types.mjs +1 -0
  40. package/dist/plugins/pages.d.mts +6 -0
  41. package/dist/plugins/pages.d.mts.map +1 -0
  42. package/dist/plugins/pages.mjs +102 -0
  43. package/dist/plugins/pages.mjs.map +1 -0
  44. package/dist/plugins/rage-clicks.d.mts +6 -0
  45. package/dist/plugins/rage-clicks.d.mts.map +1 -0
  46. package/dist/plugins/rage-clicks.mjs +53 -0
  47. package/dist/plugins/rage-clicks.mjs.map +1 -0
  48. package/dist/plugins/replay.d.mts +6 -0
  49. package/dist/plugins/replay.d.mts.map +1 -0
  50. package/dist/plugins/replay.mjs +62 -0
  51. package/dist/plugins/replay.mjs.map +1 -0
  52. package/dist/provider.d.mts +17 -11
  53. package/dist/provider.d.mts.map +1 -1
  54. package/dist/provider.mjs +18 -17
  55. package/dist/provider.mjs.map +1 -1
  56. package/dist/tracking/api.d.mts +22 -0
  57. package/dist/tracking/api.d.mts.map +1 -0
  58. package/dist/tracking/api.mjs +88 -0
  59. package/dist/tracking/api.mjs.map +1 -0
  60. package/dist/tracking/session.d.mts +19 -0
  61. package/dist/tracking/session.d.mts.map +1 -0
  62. package/dist/tracking/session.mjs +92 -0
  63. package/dist/tracking/session.mjs.map +1 -0
  64. package/dist/tracking/visitor.d.mts +6 -0
  65. package/dist/tracking/visitor.d.mts.map +1 -0
  66. package/dist/tracking/visitor.mjs +35 -0
  67. package/dist/tracking/visitor.mjs.map +1 -0
  68. package/dist/transport/http.d.mts +15 -0
  69. package/dist/transport/http.d.mts.map +1 -0
  70. package/dist/transport/http.mjs +56 -0
  71. package/dist/transport/http.mjs.map +1 -0
  72. package/dist/transport/queue.d.mts +25 -0
  73. package/dist/transport/queue.d.mts.map +1 -0
  74. package/dist/transport/queue.mjs +60 -0
  75. package/dist/transport/queue.mjs.map +1 -0
  76. package/dist/util/log.d.mts +13 -0
  77. package/dist/util/log.d.mts.map +1 -0
  78. package/dist/util/log.mjs +37 -0
  79. package/dist/util/log.mjs.map +1 -0
  80. package/package.json +47 -70
  81. package/dist/client.d.mts +0 -15
  82. package/dist/client.d.mts.map +0 -1
  83. package/dist/client.mjs +0 -75
  84. package/dist/client.mjs.map +0 -1
  85. package/dist/core/events/event-registry.d.mts +0 -23
  86. package/dist/core/events/event-registry.d.mts.map +0 -1
  87. package/dist/core/events/event-registry.mjs +0 -32
  88. package/dist/core/events/event-registry.mjs.map +0 -1
  89. package/dist/core/events/plugin-event-types.d.mts +0 -92
  90. package/dist/core/events/plugin-event-types.d.mts.map +0 -1
  91. package/dist/core/events/plugin-event-types.mjs +0 -25
  92. package/dist/core/events/plugin-event-types.mjs.map +0 -1
  93. package/dist/core/plugins/dom-utils.d.mts +0 -9
  94. package/dist/core/plugins/dom-utils.d.mts.map +0 -1
  95. package/dist/core/plugins/dom-utils.mjs +0 -25
  96. package/dist/core/plugins/dom-utils.mjs.map +0 -1
  97. package/dist/core/plugins/impl/ai-summary.d.mts +0 -6
  98. package/dist/core/plugins/impl/ai-summary.d.mts.map +0 -1
  99. package/dist/core/plugins/impl/ai-summary.mjs +0 -122
  100. package/dist/core/plugins/impl/ai-summary.mjs.map +0 -1
  101. package/dist/core/plugins/impl/errors.d.mts +0 -9
  102. package/dist/core/plugins/impl/errors.d.mts.map +0 -1
  103. package/dist/core/plugins/impl/errors.mjs +0 -153
  104. package/dist/core/plugins/impl/errors.mjs.map +0 -1
  105. package/dist/core/plugins/impl/page-events.d.mts +0 -15
  106. package/dist/core/plugins/impl/page-events.d.mts.map +0 -1
  107. package/dist/core/plugins/impl/page-events.mjs +0 -131
  108. package/dist/core/plugins/impl/page-events.mjs.map +0 -1
  109. package/dist/core/plugins/impl/rage-click.d.mts +0 -6
  110. package/dist/core/plugins/impl/rage-click.d.mts.map +0 -1
  111. package/dist/core/plugins/impl/rage-click.mjs +0 -53
  112. package/dist/core/plugins/impl/rage-click.mjs.map +0 -1
  113. package/dist/core/plugins/impl/replay.d.mts +0 -9
  114. package/dist/core/plugins/impl/replay.d.mts.map +0 -1
  115. package/dist/core/plugins/impl/replay.mjs +0 -144
  116. package/dist/core/plugins/impl/replay.mjs.map +0 -1
  117. package/dist/core/plugins/impl/server-tracing.d.mts +0 -7
  118. package/dist/core/plugins/impl/server-tracing.d.mts.map +0 -1
  119. package/dist/core/plugins/impl/server-tracing.mjs +0 -160
  120. package/dist/core/plugins/impl/server-tracing.mjs.map +0 -1
  121. package/dist/core/plugins/plugin-event-system.d.mts +0 -47
  122. package/dist/core/plugins/plugin-event-system.d.mts.map +0 -1
  123. package/dist/core/plugins/plugin-event-system.mjs +0 -75
  124. package/dist/core/plugins/plugin-event-system.mjs.map +0 -1
  125. package/dist/core/plugins/plugin-loader.d.mts +0 -22
  126. package/dist/core/plugins/plugin-loader.d.mts.map +0 -1
  127. package/dist/core/plugins/plugin-loader.mjs +0 -142
  128. package/dist/core/plugins/plugin-loader.mjs.map +0 -1
  129. package/dist/core/runtime/config.d.mts +0 -14
  130. package/dist/core/runtime/config.d.mts.map +0 -1
  131. package/dist/core/runtime/config.mjs +0 -39
  132. package/dist/core/runtime/config.mjs.map +0 -1
  133. package/dist/core/runtime/context.d.mts +0 -25
  134. package/dist/core/runtime/context.d.mts.map +0 -1
  135. package/dist/core/runtime/context.mjs +0 -48
  136. package/dist/core/runtime/context.mjs.map +0 -1
  137. package/dist/core/runtime/ingest-target.d.mts +0 -10
  138. package/dist/core/runtime/ingest-target.d.mts.map +0 -1
  139. package/dist/core/runtime/ingest-target.mjs +0 -15
  140. package/dist/core/runtime/ingest-target.mjs.map +0 -1
  141. package/dist/core/schemas.d.mts +0 -85
  142. package/dist/core/schemas.d.mts.map +0 -1
  143. package/dist/core/schemas.mjs +0 -1
  144. package/dist/effect/build-envelope.d.mts +0 -9
  145. package/dist/effect/build-envelope.d.mts.map +0 -1
  146. package/dist/effect/build-envelope.mjs +0 -29
  147. package/dist/effect/build-envelope.mjs.map +0 -1
  148. package/dist/effect/errors.d.mts +0 -36
  149. package/dist/effect/errors.d.mts.map +0 -1
  150. package/dist/effect/errors.mjs +0 -10
  151. package/dist/effect/errors.mjs.map +0 -1
  152. package/dist/effect/layers/config.layer.d.mts +0 -13
  153. package/dist/effect/layers/config.layer.d.mts.map +0 -1
  154. package/dist/effect/layers/config.layer.mjs +0 -21
  155. package/dist/effect/layers/config.layer.mjs.map +0 -1
  156. package/dist/effect/layers/context.layer.d.mts +0 -12
  157. package/dist/effect/layers/context.layer.d.mts.map +0 -1
  158. package/dist/effect/layers/context.layer.mjs +0 -14
  159. package/dist/effect/layers/context.layer.mjs.map +0 -1
  160. package/dist/effect/layers/http.layer.d.mts +0 -21
  161. package/dist/effect/layers/http.layer.d.mts.map +0 -1
  162. package/dist/effect/layers/http.layer.mjs +0 -113
  163. package/dist/effect/layers/http.layer.mjs.map +0 -1
  164. package/dist/effect/layers/queue.layer.d.mts +0 -30
  165. package/dist/effect/layers/queue.layer.d.mts.map +0 -1
  166. package/dist/effect/layers/queue.layer.mjs +0 -232
  167. package/dist/effect/layers/queue.layer.mjs.map +0 -1
  168. package/dist/effect/layers/session.layer.d.mts +0 -26
  169. package/dist/effect/layers/session.layer.d.mts.map +0 -1
  170. package/dist/effect/layers/session.layer.mjs +0 -126
  171. package/dist/effect/layers/session.layer.mjs.map +0 -1
  172. package/dist/effect/layers/storage.layer.d.mts +0 -19
  173. package/dist/effect/layers/storage.layer.d.mts.map +0 -1
  174. package/dist/effect/layers/storage.layer.mjs +0 -200
  175. package/dist/effect/layers/storage.layer.mjs.map +0 -1
  176. package/dist/effect/layers/tracer.layer.d.mts +0 -9
  177. package/dist/effect/layers/tracer.layer.d.mts.map +0 -1
  178. package/dist/effect/layers/tracer.layer.mjs +0 -11
  179. package/dist/effect/layers/tracer.layer.mjs.map +0 -1
  180. package/dist/effect/runtime-services.d.mts +0 -22
  181. package/dist/effect/runtime-services.d.mts.map +0 -1
  182. package/dist/effect/runtime-services.mjs +0 -76
  183. package/dist/effect/runtime-services.mjs.map +0 -1
  184. package/dist/effect/tags.d.mts +0 -50
  185. package/dist/effect/tags.d.mts.map +0 -1
  186. package/dist/effect/tags.mjs +0 -7
  187. package/dist/effect/tags.mjs.map +0 -1
  188. package/dist/hooks/use-runtime-and-plugins.d.mts +0 -7
  189. package/dist/hooks/use-runtime-and-plugins.d.mts.map +0 -1
  190. package/dist/hooks/use-runtime-and-plugins.mjs +0 -153
  191. package/dist/hooks/use-runtime-and-plugins.mjs.map +0 -1
  192. package/dist/hooks/use-session.d.mts +0 -40
  193. package/dist/hooks/use-session.d.mts.map +0 -1
  194. package/dist/hooks/use-session.mjs +0 -96
  195. package/dist/hooks/use-session.mjs.map +0 -1
  196. package/dist/package.mjs +0 -100
  197. package/dist/package.mjs.map +0 -1
  198. package/dist/server/auth.d.mts +0 -11
  199. package/dist/server/auth.d.mts.map +0 -1
  200. package/dist/server/auth.mjs +0 -36
  201. package/dist/server/auth.mjs.map +0 -1
  202. package/dist/server/capture.d.mts +0 -18
  203. package/dist/server/capture.d.mts.map +0 -1
  204. package/dist/server/capture.mjs +0 -105
  205. package/dist/server/capture.mjs.map +0 -1
@@ -1,36 +0,0 @@
1
- import * as effect_Types0 from "effect/Types";
2
- import * as effect_Cause0 from "effect/Cause";
3
-
4
- //#region src/effect/errors.d.ts
5
- declare const HttpError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
6
- readonly _tag: "HttpError";
7
- } & Readonly<A>;
8
- declare class HttpError extends HttpError_base<{
9
- readonly status: number;
10
- readonly message: string;
11
- readonly url?: string;
12
- readonly body?: string;
13
- }> {}
14
- declare const ValidationError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
15
- readonly _tag: "ValidationError";
16
- } & Readonly<A>;
17
- declare class ValidationError extends ValidationError_base<{
18
- readonly message: string;
19
- readonly payload: unknown;
20
- readonly issues?: unknown;
21
- }> {}
22
- declare const ConfigError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
23
- readonly _tag: "ConfigError";
24
- } & Readonly<A>;
25
- declare class ConfigError extends ConfigError_base<{
26
- readonly message: string;
27
- }> {}
28
- declare const StorageError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
29
- readonly _tag: "StorageError";
30
- } & Readonly<A>;
31
- declare class StorageError extends StorageError_base<{
32
- readonly message: string;
33
- readonly operation: "load" | "save" | "append" | "remove" | "drop";
34
- }> {}
35
- //#endregion
36
- export { ConfigError, HttpError, StorageError, ValidationError };
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors.d.mts","names":[],"sources":["../../src/effect/errors.ts"],"sourcesContent":[],"mappings":";;;;;;;cAEa,SAAA,SAAkB;;;;;;cAK1B;;;cAEQ,eAAA,SAAwB;;;;AAPrC,CAAA,CAAA,CAAA;cAWK;;;cAEQ,WAAA,SAAoB;;;cAE5B;;;cAEQ,YAAA,SAAqB;;EAVrB,SAAA,SAAgB,EAAA,MAAA,GAAA,MAAQ,GAAA,QAAA,GAAA,QAAA,GAAA,MAAA;AAIhC,CAAA,CAAA,CAAA"}
@@ -1,10 +0,0 @@
1
- import { Data } from "effect";
2
-
3
- //#region src/effect/errors.ts
4
- var HttpError = class extends Data.TaggedError("HttpError") {};
5
- var ValidationError = class extends Data.TaggedError("ValidationError") {};
6
- var ConfigError = class extends Data.TaggedError("ConfigError") {};
7
- var StorageError = class extends Data.TaggedError("StorageError") {};
8
-
9
- //#endregion
10
- export { ConfigError, HttpError, StorageError, ValidationError };
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors.mjs","names":[],"sources":["../../src/effect/errors.ts"],"sourcesContent":["import { Data } from \"effect\";\n\nexport class HttpError extends Data.TaggedError(\"HttpError\")<{\n readonly status: number;\n readonly message: string;\n readonly url?: string;\n readonly body?: string;\n}> {}\n\nexport class ValidationError extends Data.TaggedError(\"ValidationError\")<{\n readonly message: string;\n readonly payload: unknown;\n readonly issues?: unknown;\n}> {}\n\nexport class ConfigError extends Data.TaggedError(\"ConfigError\")<{\n readonly message: string;\n}> {}\n\nexport class StorageError extends Data.TaggedError(\"StorageError\")<{\n readonly message: string;\n readonly operation: \"load\" | \"save\" | \"append\" | \"remove\" | \"drop\";\n}> {}\n"],"mappings":";;;AAEA,IAAa,YAAb,cAA+B,KAAK,YAAY,YAAY,CAKzD;AAEH,IAAa,kBAAb,cAAqC,KAAK,YAAY,kBAAkB,CAIrE;AAEH,IAAa,cAAb,cAAiC,KAAK,YAAY,cAAc,CAE7D;AAEH,IAAa,eAAb,cAAkC,KAAK,YAAY,eAAe,CAG/D"}
@@ -1,13 +0,0 @@
1
- import { Config } from "@interfere/types/sdk/config";
2
- import { Runtime } from "@interfere/types/sdk/runtime";
3
-
4
- //#region src/effect/layers/config.layer.d.ts
5
- type SdkConfig = Config & {
6
- runtime: Runtime;
7
- };
8
- interface ConfigService {
9
- get: () => SdkConfig;
10
- }
11
- declare function createConfigService(input: unknown): ConfigService;
12
- //#endregion
13
- export { ConfigService, SdkConfig, createConfigService };
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.layer.d.mts","names":[],"sources":["../../../src/effect/layers/config.layer.ts"],"sourcesContent":[],"mappings":";;;;KAMY,SAAA,GAAY;WAAoB;AAA5C,CAAA;AAEiB,UAAA,aAAA,CAAa;EASd,GAAA,EAAA,GAAA,GARH,SAQG;;iBAAA,mBAAA,kBAAqC"}
@@ -1,21 +0,0 @@
1
- import { getRuntime } from "../../core/runtime/config.mjs";
2
- import { configSchema } from "@interfere/types/sdk/config";
3
-
4
- //#region src/effect/layers/config.layer.ts
5
- const MAX_BATCH_SIZE = 1e3;
6
- const MIN_BATCH_SIZE = 1;
7
- const MAX_BATCH_MS = 6e4;
8
- const MIN_BATCH_MS = 100;
9
- function createConfigService(input) {
10
- const parsed = configSchema.parse(input);
11
- if (parsed.batch.size < MIN_BATCH_SIZE || parsed.batch.size > MAX_BATCH_SIZE) throw new Error(`Invalid batch.size: ${parsed.batch.size}. Must be between ${MIN_BATCH_SIZE} and ${MAX_BATCH_SIZE}`);
12
- if (parsed.batch.ms < MIN_BATCH_MS || parsed.batch.ms > MAX_BATCH_MS) throw new Error(`Invalid batch.ms: ${parsed.batch.ms}. Must be between ${MIN_BATCH_MS}ms and ${MAX_BATCH_MS}ms`);
13
- const current = {
14
- ...parsed,
15
- runtime: getRuntime()
16
- };
17
- return { get: () => current };
18
- }
19
-
20
- //#endregion
21
- export { createConfigService };
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.layer.mjs","names":[],"sources":["../../../src/effect/layers/config.layer.ts"],"sourcesContent":["import type { Config } from \"@interfere/types/sdk/config\";\nimport { configSchema } from \"@interfere/types/sdk/config\";\nimport type { Runtime } from \"@interfere/types/sdk/runtime\";\n\nimport { getRuntime } from \"../../core/runtime/config.js\";\n\nexport type SdkConfig = Config & { runtime: Runtime };\n\nexport interface ConfigService {\n get: () => SdkConfig;\n}\n\nconst MAX_BATCH_SIZE = 1000;\nconst MIN_BATCH_SIZE = 1;\nconst MAX_BATCH_MS = 60_000;\nconst MIN_BATCH_MS = 100;\n\nexport function createConfigService(input: unknown): ConfigService {\n const parsed = configSchema.parse(input) as Required<Config>;\n\n // Validate batch configuration\n if (\n parsed.batch.size < MIN_BATCH_SIZE ||\n parsed.batch.size > MAX_BATCH_SIZE\n ) {\n throw new Error(\n `Invalid batch.size: ${parsed.batch.size}. Must be between ${MIN_BATCH_SIZE} and ${MAX_BATCH_SIZE}`\n );\n }\n\n if (parsed.batch.ms < MIN_BATCH_MS || parsed.batch.ms > MAX_BATCH_MS) {\n throw new Error(\n `Invalid batch.ms: ${parsed.batch.ms}. Must be between ${MIN_BATCH_MS}ms and ${MAX_BATCH_MS}ms`\n );\n }\n\n const current = { ...parsed, runtime: getRuntime() } as Config & {\n runtime: Runtime;\n };\n return {\n get: () => current,\n };\n}\n"],"mappings":";;;;AAYA,MAAM,iBAAiB;AACvB,MAAM,iBAAiB;AACvB,MAAM,eAAe;AACrB,MAAM,eAAe;AAErB,SAAgB,oBAAoB,OAA+B;CACjE,MAAM,SAAS,aAAa,MAAM,MAAM;AAGxC,KACE,OAAO,MAAM,OAAO,kBACpB,OAAO,MAAM,OAAO,eAEpB,OAAM,IAAI,MACR,uBAAuB,OAAO,MAAM,KAAK,oBAAoB,eAAe,OAAO,iBACpF;AAGH,KAAI,OAAO,MAAM,KAAK,gBAAgB,OAAO,MAAM,KAAK,aACtD,OAAM,IAAI,MACR,qBAAqB,OAAO,MAAM,GAAG,oBAAoB,aAAa,SAAS,aAAa,IAC7F;CAGH,MAAM,UAAU;EAAE,GAAG;EAAQ,SAAS,YAAY;EAAE;AAGpD,QAAO,EACL,WAAW,SACZ"}
@@ -1,12 +0,0 @@
1
- import { ContextProperties } from "../../core/runtime/context.mjs";
2
- import { Context, Effect, Layer } from "effect";
3
-
4
- //#region src/effect/layers/context.layer.d.ts
5
- interface ContextService {
6
- readonly getOnce: () => Effect.Effect<ContextProperties>;
7
- }
8
- declare const ContextServiceTag_base: Context.TagClass<ContextServiceTag, "@interfere/react/Context", ContextService>;
9
- declare class ContextServiceTag extends ContextServiceTag_base {}
10
- declare const ContextServiceLive: Layer.Layer<ContextServiceTag, never, never>;
11
- //#endregion
12
- export { ContextService, ContextServiceLive, ContextServiceTag };
@@ -1 +0,0 @@
1
- {"version":3,"file":"context.layer.d.mts","names":[],"sources":["../../../src/effect/layers/context.layer.ts"],"sourcesContent":[],"mappings":";;;;UAKiB,cAAA;0BACS,MAAA,CAAO,OAAO;AADxC;AAEC,cAAA,sBAAA,kBAAA,kBAAA,EAAA,0BAAA,gBAAA,CAAA;cAEY,iBAAA,SAA0B,sBAAA;cAK1B,oBAAkB,KAAA,CAAA,MAAA"}
@@ -1,14 +0,0 @@
1
- import { getContextProperties } from "../../core/runtime/context.mjs";
2
- import { Context, Effect, Layer } from "effect";
3
-
4
- //#region src/effect/layers/context.layer.ts
5
- var ContextServiceTag = class extends Context.Tag("@interfere/react/Context")() {};
6
- const ContextServiceLive = Layer.succeed(ContextServiceTag, (() => {
7
- let cached = null;
8
- return { getOnce: () => (cached ? Effect.succeed(cached) : getContextProperties().pipe(Effect.tap((ctx) => Effect.sync(() => {
9
- cached = ctx;
10
- })))).pipe(Effect.withSpan("context.getOnce")) };
11
- })());
12
-
13
- //#endregion
14
- export { ContextServiceLive, ContextServiceTag };
@@ -1 +0,0 @@
1
- {"version":3,"file":"context.layer.mjs","names":["cached: ContextProperties | null"],"sources":["../../../src/effect/layers/context.layer.ts"],"sourcesContent":["import { Context, Effect, Layer } from \"effect\";\n\nimport type { ContextProperties } from \"../../core/runtime/context.js\";\nimport { getContextProperties } from \"../../core/runtime/context.js\";\n\nexport interface ContextService {\n readonly getOnce: () => Effect.Effect<ContextProperties>;\n}\n\nexport class ContextServiceTag extends Context.Tag(\"@interfere/react/Context\")<\n ContextServiceTag,\n ContextService\n>() {}\n\nexport const ContextServiceLive = Layer.succeed(\n ContextServiceTag,\n (() => {\n let cached: ContextProperties | null = null;\n\n return {\n getOnce: () =>\n (cached\n ? Effect.succeed(cached)\n : getContextProperties().pipe(\n Effect.tap((ctx) =>\n Effect.sync(() => {\n cached = ctx;\n })\n )\n )\n ).pipe(Effect.withSpan(\"context.getOnce\")),\n } satisfies ContextService;\n })()\n);\n"],"mappings":";;;;AASA,IAAa,oBAAb,cAAuC,QAAQ,IAAI,2BAA2B,EAG3E,CAAC;AAEJ,MAAa,qBAAqB,MAAM,QACtC,0BACO;CACL,IAAIA,SAAmC;AAEvC,QAAO,EACL,gBACG,SACG,OAAO,QAAQ,OAAO,GACtB,sBAAsB,CAAC,KACrB,OAAO,KAAK,QACV,OAAO,WAAW;AAChB,WAAS;GACT,CACH,CACF,EACH,KAAK,OAAO,SAAS,kBAAkB,CAAC,EAC7C;IACC,CACL"}
@@ -1,21 +0,0 @@
1
- import { HttpError } from "../errors.mjs";
2
- import { SdkConfig } from "./config.layer.mjs";
3
- import { Context, Effect, Layer } from "effect";
4
- import { Envelope } from "@interfere/types/sdk/envelope";
5
-
6
- //#region src/effect/layers/http.layer.d.ts
7
- interface HttpService {
8
- readonly postEnvelopes: (envelopes: Envelope[]) => Effect.Effect<void, HttpError>;
9
- }
10
- declare const HttpServiceTag_base: Context.TagClass<HttpServiceTag, "@interfere/react/Http", HttpService>;
11
- declare class HttpServiceTag extends HttpServiceTag_base {}
12
- /**
13
- * Creates an HTTP service layer for posting envelopes
14
- */
15
- interface HttpDeps {
16
- readonly fetch?: typeof globalThis.fetch;
17
- readonly timeoutMs?: number;
18
- }
19
- declare const HttpServiceLive: (config: SdkConfig, deps?: HttpDeps) => Layer.Layer<HttpServiceTag, never, never>;
20
- //#endregion
21
- export { HttpService, HttpServiceLive, HttpServiceTag };
@@ -1 +0,0 @@
1
- {"version":3,"file":"http.layer.d.mts","names":[],"sources":["../../../src/effect/layers/http.layer.ts"],"sourcesContent":[],"mappings":";;;;;;UAYiB,WAAA;sCAEF,eACR,MAAA,CAAO,aAAa;AAH3B;cAIC,mBAFc,kBAAA,eAAA,EAAA,uBAAA,aAAA,CAAA;AACY,cAGd,cAAA,SAAuB,mBAAA,CAHT;;AAC1B;;UAsDS,QAAA;0BACgB,UAAA,CAAW;;AArDrC;AAoDU,cAKG,eAJa,EAAA,CAAW,MAAK,EAIF,SAJE,EAAA,IAAA,CAAA,EAIgB,QAJhB,EAAA,GAIwB,KAAA,CAAA,KAJxB,CAIwB,cAJxB,EAAA,KAAA,EAAA,KAAA,CAAA"}
@@ -1,113 +0,0 @@
1
- import { HttpError } from "../errors.mjs";
2
- import { deriveIngestTarget } from "../../core/runtime/ingest-target.mjs";
3
- import { Context, Effect, Layer } from "effect";
4
- import { DEFAULT_TIMEOUT_MS, withTimeoutFail } from "@interfere/effect-utils/retry";
5
-
6
- //#region src/effect/layers/http.layer.ts
7
- var HttpServiceTag = class extends Context.Tag("@interfere/react/Http")() {};
8
- /**
9
- * Generates a W3C traceparent header
10
- */
11
- const generateTraceparent = () => {
12
- return `00-${randomHex(16)}-${randomHex(8)}-01`;
13
- };
14
- function randomHex(bytes) {
15
- const RANDOM_MAX = 256;
16
- const HEX_BYTE_WIDTH = 2;
17
- const buf = new Uint8Array(bytes);
18
- if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") crypto.getRandomValues(buf);
19
- else for (let i = 0; i < bytes; i += 1) buf[i] = Math.floor(Math.random() * RANDOM_MAX);
20
- const HEX_BASE = 16;
21
- return Array.from(buf).map((b) => b.toString(HEX_BASE).padStart(HEX_BYTE_WIDTH, "0")).join("");
22
- }
23
- /**
24
- * Keepalive limits per Fetch spec (https://fetch.spec.whatwg.org/#http-network-or-cache-fetch):
25
- * - If sum of body + inflight keepalive bytes > 64KB, returns network error
26
- * - We use 60KB threshold to leave room for headers/overhead
27
- * - Also limit concurrent keepalive requests to 15 (browser implementation detail)
28
- */
29
- const KEEPALIVE_BYTE_LIMIT = 6e4;
30
- const KEEPALIVE_REQUEST_LIMIT = 15;
31
- const HttpServiceLive = (config, deps) => {
32
- let pendingBodySize = 0;
33
- let pendingCount = 0;
34
- return Layer.succeed(HttpServiceTag, { postEnvelopes: (envelopes) => {
35
- const spanUrl = (() => {
36
- try {
37
- return deriveIngestTarget(config).url;
38
- } catch {
39
- return "<config-error>";
40
- }
41
- })();
42
- return Effect.gen(function* () {
43
- const target = yield* Effect.try({
44
- try: () => deriveIngestTarget(config),
45
- catch: (err) => new HttpError({
46
- status: 0,
47
- message: String(err instanceof Error ? err.message : err),
48
- url: "<config-error>"
49
- })
50
- });
51
- yield* Effect.logDebug("Sending batch to ingest").pipe(Effect.annotateLogs({
52
- url: target.url,
53
- envelopeCount: envelopes.length
54
- }));
55
- const headers = new Headers(target.headers);
56
- const url = target.url;
57
- headers.set("traceparent", generateTraceparent());
58
- const TIMEOUT_MS = deps?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
59
- const doFetch = deps?.fetch ?? fetch;
60
- const HTTP_METHOD_POST = "POST";
61
- const body = JSON.stringify(envelopes);
62
- const requestSize = body.length;
63
- pendingBodySize += requestSize;
64
- pendingCount += 1;
65
- const useKeepalive = pendingBodySize <= KEEPALIVE_BYTE_LIMIT && pendingCount <= KEEPALIVE_REQUEST_LIMIT;
66
- const response = yield* withTimeoutFail(Effect.tryPromise({
67
- try: async () => {
68
- try {
69
- return await doFetch(url, {
70
- method: HTTP_METHOD_POST,
71
- headers,
72
- body,
73
- keepalive: useKeepalive
74
- });
75
- } finally {
76
- pendingBodySize -= requestSize;
77
- pendingCount -= 1;
78
- }
79
- },
80
- catch: (error) => new HttpError({
81
- status: 0,
82
- message: `Network error: ${String(error)}`,
83
- url
84
- })
85
- }), TIMEOUT_MS, () => new HttpError({
86
- status: 0,
87
- message: "Network timeout",
88
- url
89
- }));
90
- if (!response.ok) {
91
- const body$1 = yield* Effect.promise(() => response.text()).pipe(Effect.orElse(() => Effect.succeed("")));
92
- if (response.status === 422) return yield* Effect.fail(new HttpError({
93
- status: response.status,
94
- message: "Invalid envelopes (schema mismatch)",
95
- url,
96
- body: body$1
97
- }));
98
- return yield* Effect.fail(new HttpError({
99
- status: response.status,
100
- message: `Ingest failed: ${response.status} ${response.statusText}`,
101
- url,
102
- body: body$1
103
- }));
104
- }
105
- }).pipe(Effect.withSpan("http.postEnvelopes", { attributes: {
106
- envelopeCount: envelopes.length,
107
- url: spanUrl
108
- } }));
109
- } });
110
- };
111
-
112
- //#endregion
113
- export { HttpServiceLive, HttpServiceTag };
@@ -1 +0,0 @@
1
- {"version":3,"file":"http.layer.mjs","names":["body"],"sources":["../../../src/effect/layers/http.layer.ts"],"sourcesContent":["import {\n DEFAULT_TIMEOUT_MS,\n withTimeoutFail,\n} from \"@interfere/effect-utils/retry\";\nimport type { Envelope } from \"@interfere/types/sdk/envelope\";\n\nimport { Context, Effect, Layer } from \"effect\";\n\nimport { deriveIngestTarget } from \"../../core/runtime/ingest-target.js\";\nimport { HttpError } from \"../errors.js\";\nimport type { SdkConfig } from \"./config.layer.js\";\n\nexport interface HttpService {\n readonly postEnvelopes: (\n envelopes: Envelope[]\n ) => Effect.Effect<void, HttpError>;\n}\n\nexport class HttpServiceTag extends Context.Tag(\"@interfere/react/Http\")<\n HttpServiceTag,\n HttpService\n>() {}\n\n/**\n * Generates a W3C traceparent header\n */\nconst generateTraceparent = (): string => {\n const version = \"00\";\n const TRACE_ID_BYTES = 16;\n const SPAN_ID_BYTES = 8;\n const traceId = randomHex(TRACE_ID_BYTES);\n const spanId = randomHex(SPAN_ID_BYTES);\n const flags = \"01\";\n return `${version}-${traceId}-${spanId}-${flags}`;\n};\n\nfunction randomHex(bytes: number): string {\n const RANDOM_MAX = 256;\n const HEX_BYTE_WIDTH = 2;\n const buf = new Uint8Array(bytes);\n\n if (\n typeof crypto !== \"undefined\" &&\n typeof crypto.getRandomValues === \"function\"\n ) {\n crypto.getRandomValues(buf);\n } else {\n for (let i = 0; i < bytes; i += 1) {\n buf[i] = Math.floor(Math.random() * RANDOM_MAX);\n }\n }\n\n const HEX_BASE = 16;\n return Array.from(buf)\n .map((b) => b.toString(HEX_BASE).padStart(HEX_BYTE_WIDTH, \"0\"))\n .join(\"\");\n}\n\n/**\n * Keepalive limits per Fetch spec (https://fetch.spec.whatwg.org/#http-network-or-cache-fetch):\n * - If sum of body + inflight keepalive bytes > 64KB, returns network error\n * - We use 60KB threshold to leave room for headers/overhead\n * - Also limit concurrent keepalive requests to 15 (browser implementation detail)\n */\nconst KEEPALIVE_BYTE_LIMIT = 60_000;\nconst KEEPALIVE_REQUEST_LIMIT = 15; // Arbitrary limit\n\n/**\n * Creates an HTTP service layer for posting envelopes\n */\ninterface HttpDeps {\n readonly fetch?: typeof globalThis.fetch;\n readonly timeoutMs?: number;\n}\n\nexport const HttpServiceLive = (config: SdkConfig, deps?: HttpDeps) => {\n // Track pending keepalive request state across all concurrent requests.\n // These are closure variables that persist for the lifetime of the layer.\n let pendingBodySize = 0;\n let pendingCount = 0;\n\n return Layer.succeed(HttpServiceTag, {\n postEnvelopes: (envelopes: Envelope[]) => {\n const spanUrl = (() => {\n try {\n return deriveIngestTarget(config).url;\n } catch {\n return \"<config-error>\";\n }\n })();\n\n return Effect.gen(function* () {\n const target = yield* Effect.try({\n try: () => deriveIngestTarget(config),\n catch: (err) =>\n new HttpError({\n status: 0,\n message: String(err instanceof Error ? err.message : err),\n url: \"<config-error>\",\n }),\n });\n\n yield* Effect.logDebug(\"Sending batch to ingest\").pipe(\n Effect.annotateLogs({\n url: target.url,\n envelopeCount: envelopes.length,\n })\n );\n\n const headers = new Headers(target.headers);\n const url = target.url;\n\n // Add traceparent header\n headers.set(\"traceparent\", generateTraceparent());\n\n // Send the request with timeout\n const TIMEOUT_MS = deps?.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const doFetch = deps?.fetch ?? fetch;\n const HTTP_METHOD_POST = \"POST\" as const;\n\n const body = JSON.stringify(envelopes);\n const requestSize = body.length;\n\n // Track this request's size for keepalive limit calculation\n pendingBodySize += requestSize;\n pendingCount += 1;\n\n // Keepalive allows requests to complete even when navigating away, but has limits:\n // - Per Fetch spec, if sum of inflight keepalive bytes > 64KB, it fails with network error\n // - The limit is aggregate across ALL concurrent keepalive requests, not per-request\n // - Caused \"Failed to fetch\" (status 0) errors in production when batches were large\n // - We only enable keepalive when under both size and count limits\n // See: https://fetch.spec.whatwg.org/#http-network-or-cache-fetch (step 8.10.5)\n const useKeepalive =\n pendingBodySize <= KEEPALIVE_BYTE_LIMIT &&\n pendingCount <= KEEPALIVE_REQUEST_LIMIT;\n\n const response = yield* withTimeoutFail(\n Effect.tryPromise({\n try: async () => {\n try {\n return await doFetch(url, {\n method: HTTP_METHOD_POST,\n headers,\n body,\n keepalive: useKeepalive,\n });\n } finally {\n // Always decrement counters when request completes (success or failure)\n pendingBodySize -= requestSize;\n pendingCount -= 1;\n }\n },\n catch: (error) =>\n new HttpError({\n status: 0,\n message: `Network error: ${String(error)}`,\n url,\n }),\n }),\n TIMEOUT_MS,\n () =>\n new HttpError({\n status: 0,\n message: \"Network timeout\",\n url,\n })\n );\n\n if (!response.ok) {\n const body = yield* Effect.promise(() => response.text()).pipe(\n Effect.orElse(() => Effect.succeed(\"\"))\n );\n\n // If the server tells us the envelopes are malformed, treat this as a\n // permanent failure so the queue layer can drop them instead of retrying.\n if (response.status === 422) {\n return yield* Effect.fail(\n new HttpError({\n status: response.status,\n message: \"Invalid envelopes (schema mismatch)\",\n url,\n body,\n })\n );\n }\n\n return yield* Effect.fail(\n new HttpError({\n status: response.status,\n message: `Ingest failed: ${response.status} ${response.statusText}`,\n url,\n body,\n })\n );\n }\n }).pipe(\n Effect.withSpan(\"http.postEnvelopes\", {\n attributes: {\n envelopeCount: envelopes.length,\n url: spanUrl,\n },\n })\n );\n },\n });\n};\n"],"mappings":"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"}
@@ -1,30 +0,0 @@
1
- import { ConfigTag } from "../tags.mjs";
2
- import { HttpServiceTag } from "./http.layer.mjs";
3
- import { Context, Effect, Layer, Scope } from "effect";
4
- import { CircuitBreakerState } from "@interfere/effect-utils/circuit-breaker";
5
- import { Envelope } from "@interfere/types/sdk/envelope";
6
-
7
- //#region src/effect/layers/queue.layer.d.ts
8
- interface QueueMetrics {
9
- readonly queueDepth: number;
10
- readonly successCount: number;
11
- readonly failureCount: number;
12
- readonly circuitBreakerState: CircuitBreakerState;
13
- }
14
- interface QueueService {
15
- readonly add: (envelope: Envelope) => Effect.Effect<void>;
16
- readonly size: () => Effect.Effect<number>;
17
- readonly flush: () => Effect.Effect<void>;
18
- readonly boundedFlush: (timeoutMs: number) => Effect.Effect<void>;
19
- readonly startBatchProcessor: () => Effect.Effect<void, never, Scope.Scope>;
20
- readonly setReady: (ready: boolean) => Effect.Effect<void>;
21
- readonly getMetrics: () => Effect.Effect<QueueMetrics>;
22
- }
23
- declare const QueueServiceTag_base: Context.TagClass<QueueServiceTag, "@interfere/react/Queue", QueueService>;
24
- declare class QueueServiceTag extends QueueServiceTag_base {}
25
- /**
26
- * Creates the queue service layer
27
- */
28
- declare const QueueServiceLive: Layer.Layer<QueueServiceTag, never, ConfigTag | HttpServiceTag>;
29
- //#endregion
30
- export { QueueMetrics, QueueService, QueueServiceLive, QueueServiceTag };
@@ -1 +0,0 @@
1
- {"version":3,"file":"queue.layer.d.mts","names":[],"sources":["../../../src/effect/layers/queue.layer.ts"],"sourcesContent":[],"mappings":";;;;;;;UA4CiB,YAAA;;EAAA,SAAA,YAAY,EAAA,MAIG;EAGf,SAAA,YAAY,EAAA,MAAA;EACF,SAAA,mBAAA,EAJK,mBAIL;;AACJ,UAFN,YAAA,CAEa;EACN,SAAO,GAAA,EAAA,CAAA,QAAA,EAFJ,QAEI,EAAA,GAFS,MAAA,CAAO,MAEhB,CAAA,IAAA,CAAA;EACiB,SAAO,IAAA,EAAA,GAAA,GAFhC,MAAA,CAAO,MAEyB,CAAA,MAAA,CAAA;EACU,SAAM,KAAA,EAAA,GAAA,GAF/C,MAAA,CAAO,MAEwC,CAAA,IAAA,CAAA;EAAjC,SAAO,YAAA,EAAA,CAAA,SAAA,EAAA,MAAA,EAAA,GADG,MAAA,CAAO,MACV,CAAA,IAAA,CAAA;EACJ,SAAO,mBAAA,EAAA,GAAA,GADV,MAAA,CAAO,MACG,CAAA,IAAA,EAAA,KAAA,EADiB,KAAA,CAAM,KACvB,CAAA;EACL,SAAA,QAAA,EAAA,CAAA,KAAA,EAAA,OAAA,EAAA,GADF,MAAA,CAAO,MACL,CAAA,IAAA,CAAA;EAAd,SAAO,UAAA,EAAA,GAAA,GAAP,MAAA,CAAO,MAAA,CAAO,YAAP,CAAA;;AACnC,cAAA,oBAAA,kBAAA,gBAAA,EAAA,wBAAA,cAAA,CAAA;cAEY,eAAA,SAAwB,oBAAA;;;AAArC;AA0Ba,cAAA,gBA8aZ,EA9a4B,KAAA,CAAA,KA8a5B,CA9a4B,eA8a5B,EAAA,KAAA,EA9a4B,SA8a5B,GA9a4B,cA8a5B,CAAA"}
@@ -1,232 +0,0 @@
1
- import { ConfigTag } from "../tags.mjs";
2
- import { HttpServiceTag } from "./http.layer.mjs";
3
- import { createStorageAdapter } from "./storage.layer.mjs";
4
- import { Chunk, Context, Duration, Effect, Layer, Queue, Ref, Schedule } from "effect";
5
- import { CircuitBreakerOpen, DEFAULT_CIRCUIT_BREAKER_CONFIG, createCircuitBreaker } from "@interfere/effect-utils/circuit-breaker";
6
- import { InterfereLogger } from "@interfere/effect-utils/observability";
7
- import { DEFAULT_RETRY_INITIAL_MS, DEFAULT_RETRY_MAX_MS, DEFAULT_RETRY_RECURS, createBackoffSchedule } from "@interfere/effect-utils/retry";
8
-
9
- //#region src/effect/layers/queue.layer.ts
10
- const MAX_RETRY_ATTEMPTS = 3;
11
- const DEFER_SEND_DELAY_MS = 250;
12
- const BREAKER_RETRY_BASE_MS = 5e3;
13
- const BREAKER_RETRY_MAX_MS = 3e4;
14
- const BREAKER_JITTER_FACTOR = .3;
15
- const BREAKER_COALESCE_REMAINING_MS = 250;
16
- const TEST_OFFLINE_REQUEUE_LIMIT = 3;
17
- const OFFLINE_RETRY_BASE_MS = DEFAULT_RETRY_INITIAL_MS;
18
- const OFFLINE_RETRY_MAX_MS = DEFAULT_RETRY_MAX_MS;
19
- const OFFLINE_JITTER_FACTOR = .3;
20
- var QueueServiceTag = class extends Context.Tag("@interfere/react/Queue")() {};
21
- /**
22
- * Creates the retry schedule for failed batches
23
- */
24
- const createRetrySchedule = () => createBackoffSchedule(DEFAULT_RETRY_INITIAL_MS, DEFAULT_RETRY_MAX_MS, MAX_RETRY_ATTEMPTS ?? DEFAULT_RETRY_RECURS);
25
- /**
26
- * Checks if the browser is online
27
- */
28
- const isOnline = () => Effect.sync(() => typeof navigator !== "undefined" ? navigator.onLine : true);
29
- /**
30
- * Creates the queue service layer
31
- */
32
- const QueueServiceLive = Layer.scoped(QueueServiceTag, Effect.gen(function* () {
33
- const config = yield* ConfigTag;
34
- const http = yield* HttpServiceTag;
35
- const storage = createStorageAdapter();
36
- const capacity = storage.capacity();
37
- const circuitBreaker = yield* createCircuitBreaker(DEFAULT_CIRCUIT_BREAKER_CONFIG);
38
- const metrics = yield* Ref.make({
39
- queueDepth: 0,
40
- successCount: 0,
41
- failureCount: 0,
42
- circuitBreakerState: "closed"
43
- });
44
- const envelopeQueue = yield* Queue.sliding(capacity);
45
- const readyRef = yield* Ref.make(false);
46
- const breakerRetryCount = yield* Ref.make(0);
47
- yield* Effect.gen(function* () {
48
- const initial = yield* storage.load().pipe(Effect.catchAll(() => Effect.succeed([])));
49
- const filtered = initial.filter((e) => typeof e.uuid === "string" && e.uuid.length > 0);
50
- if (filtered.length !== initial.length) {
51
- yield* storage.save(filtered).pipe(Effect.ignore);
52
- yield* Effect.logWarning("Purged persisted envelopes missing UUID").pipe(Effect.annotateLogs({
53
- before: initial.length,
54
- after: filtered.length,
55
- removed: initial.length - filtered.length
56
- }));
57
- }
58
- if (filtered.length > 0) {
59
- const seed = filtered.length > capacity ? filtered.slice(filtered.length - capacity) : filtered;
60
- yield* Queue.offerAll(envelopeQueue, Chunk.fromIterable(seed));
61
- if (seed.length !== filtered.length) yield* storage.save(seed).pipe(Effect.ignore);
62
- yield* Ref.update(metrics, (m) => ({
63
- ...m,
64
- queueDepth: seed.length
65
- }));
66
- yield* Effect.logDebug("Loaded persisted envelopes").pipe(Effect.annotateLogs({
67
- count: seed.length,
68
- truncated: seed.length !== filtered.length
69
- }));
70
- }
71
- });
72
- const requeueWithLog = (batch, message, extra) => Effect.gen(function* () {
73
- yield* Queue.offerAll(envelopeQueue, batch);
74
- yield* Ref.update(metrics, (m) => ({
75
- ...m,
76
- queueDepth: m.queueDepth + Chunk.size(batch)
77
- }));
78
- yield* Effect.logWarning(message).pipe(Effect.annotateLogs({
79
- batchSize: Chunk.size(batch),
80
- ...extra ?? {}
81
- }));
82
- });
83
- const performHttpSend = (envelopes, ids) => http.postEnvelopes(envelopes).pipe(Effect.tap(() => storage.removeByIds(ids).pipe(Effect.ignore)), Effect.tap(() => Ref.update(metrics, (m) => ({
84
- ...m,
85
- successCount: m.successCount + envelopes.length
86
- }))), Effect.tap(() => Effect.logDebug("Batch sent successfully").pipe(Effect.annotateLogs({ batchSize: envelopes.length }))), Effect.catchTag("HttpError", (error) => Effect.gen(function* () {
87
- if (error.status === 422) {
88
- yield* storage.removeByIds(ids).pipe(Effect.ignore);
89
- yield* Ref.update(metrics, (m) => ({
90
- ...m,
91
- failureCount: m.failureCount + envelopes.length
92
- }));
93
- yield* Effect.logWarning("Dropping invalid envelopes after 422 response").pipe(Effect.annotateLogs({
94
- batchSize: envelopes.length,
95
- status: error.status,
96
- url: error.url,
97
- body: error.body
98
- }));
99
- return;
100
- }
101
- yield* Effect.all([Effect.logWarning("Failed to send batch").pipe(Effect.annotateLogs({
102
- batchSize: envelopes.length,
103
- status: error.status,
104
- url: error.url,
105
- body: error.body,
106
- error: String(error)
107
- })), Ref.update(metrics, (m) => ({
108
- ...m,
109
- failureCount: m.failureCount + 1
110
- }))]);
111
- return yield* Effect.fail(error);
112
- })), Effect.retry(createRetrySchedule()));
113
- const sendWithBreaker = (envelopes, ids, batch) => circuitBreaker.protect(performHttpSend(envelopes, ids)).pipe(Effect.catchIf((error) => error instanceof CircuitBreakerOpen, () => Effect.gen(function* () {
114
- const retryCount = yield* Ref.getAndUpdate(breakerRetryCount, (n) => n + 1);
115
- const backoffMs = Math.min(BREAKER_RETRY_BASE_MS * 2 ** retryCount, BREAKER_RETRY_MAX_MS);
116
- const jitter = Math.random() * BREAKER_JITTER_FACTOR * backoffMs;
117
- const sleepMs = Math.floor(backoffMs + jitter);
118
- const data = yield* circuitBreaker.breakerData.get;
119
- const now = Date.now();
120
- const since = data.lastFailureTime ? now - data.lastFailureTime : 0;
121
- yield* requeueWithLog(batch, "Circuit breaker prevented batch send - requeueing", {
122
- remainingMs: Math.max(0, DEFAULT_CIRCUIT_BREAKER_CONFIG.resetMs - since),
123
- retryCount,
124
- nextRetryMs: sleepMs
125
- });
126
- yield* Effect.sleep(Duration.millis(sleepMs));
127
- })), Effect.tap(() => Ref.set(breakerRetryCount, 0)), Effect.catchAll((error) => requeueWithLog(batch, "Failed to send batch after all retries", { error: String(error) })), Effect.tap(() => Effect.gen(function* () {
128
- const state = yield* circuitBreaker.getState;
129
- yield* Ref.update(metrics, (m) => ({
130
- ...m,
131
- circuitBreakerState: state
132
- }));
133
- })), Effect.withSpan("queue.sendBatch", { attributes: { batchSize: envelopes.length } }));
134
- const offlineAttemptsRef = yield* Ref.make(0);
135
- const handleOffline = (batch) => Effect.gen(function* () {
136
- const attempts = yield* Ref.getAndUpdate(offlineAttemptsRef, (n) => n + 1);
137
- if (typeof process !== "undefined" && process.env?.VITEST && attempts >= TEST_OFFLINE_REQUEUE_LIMIT) {
138
- yield* Effect.logWarning(`Offline - dropping batch after ${TEST_OFFLINE_REQUEUE_LIMIT} attempts (test mode)`);
139
- return;
140
- }
141
- yield* requeueWithLog(batch, "Offline - requeueing batch");
142
- const base = OFFLINE_RETRY_BASE_MS;
143
- const capped = Math.min(base * 2 ** attempts, OFFLINE_RETRY_MAX_MS);
144
- const jitter = Math.random() * OFFLINE_JITTER_FACTOR * capped;
145
- const sleepMs = Math.floor(capped + jitter);
146
- yield* Effect.sleep(Duration.millis(sleepMs));
147
- });
148
- const sendBatch = (batch) => Effect.gen(function* () {
149
- if (Chunk.isEmpty(batch)) return;
150
- if (!(yield* isOnline())) {
151
- yield* handleOffline(batch);
152
- return;
153
- }
154
- const envelopes = Chunk.toArray(batch);
155
- yield* sendWithBreaker(envelopes, envelopes.map((e) => e.uuid).filter((v) => typeof v === "string"), batch);
156
- });
157
- const flush = () => Effect.gen(function* () {
158
- const items = yield* Queue.takeAll(envelopeQueue);
159
- yield* Ref.update(metrics, (m) => ({
160
- ...m,
161
- queueDepth: 0
162
- }));
163
- if (Chunk.isNonEmpty(items)) yield* sendBatch(items).pipe(Effect.catchAll(() => Effect.void));
164
- }).pipe(Effect.withSpan("queue.flush"));
165
- const processBatch = Effect.gen(function* () {
166
- if (!(yield* Ref.get(readyRef))) {
167
- yield* Effect.sleep(Duration.millis(DEFER_SEND_DELAY_MS));
168
- return;
169
- }
170
- const breakerState = yield* circuitBreaker.getState;
171
- if (breakerState === "open") {
172
- const data = yield* circuitBreaker.breakerData.get;
173
- const now = Date.now();
174
- const since = data.lastFailureTime ? now - data.lastFailureTime : 0;
175
- const remainingMs = Math.max(0, DEFAULT_CIRCUIT_BREAKER_CONFIG.resetMs - since);
176
- yield* Ref.update(metrics, (m) => ({
177
- ...m,
178
- circuitBreakerState: breakerState
179
- }));
180
- if (remainingMs > BREAKER_COALESCE_REMAINING_MS) {
181
- yield* Effect.logDebug("Circuit breaker open - coalescing batches until cooldown is small").pipe(Effect.annotateLogs({ remainingMs }));
182
- yield* Effect.sleep(Duration.millis(remainingMs - BREAKER_COALESCE_REMAINING_MS));
183
- return;
184
- }
185
- }
186
- const batchSize = config.batch.size;
187
- const batchMs = config.batch.ms;
188
- const pending = yield* Queue.takeBetween(envelopeQueue, 1, batchSize).pipe(Effect.timeout(Duration.millis(batchMs)), Effect.catchTag("TimeoutException", () => Queue.takeAll(envelopeQueue).pipe(Effect.map((items) => Chunk.isEmpty(items) ? Chunk.empty() : items))));
189
- yield* Ref.update(metrics, (m) => ({
190
- ...m,
191
- queueDepth: Math.max(0, m.queueDepth - Chunk.size(pending))
192
- }));
193
- if (Chunk.isNonEmpty(pending)) {
194
- yield* Effect.logDebug("Processing batch").pipe(Effect.annotateLogs({ batchSize: Chunk.size(pending) }));
195
- yield* sendBatch(pending);
196
- }
197
- });
198
- const startBatchProcessor = () => processBatch.pipe(Effect.catchAllCause((cause) => Effect.logError("Batch processor error", { cause: String(cause) })), Effect.repeat(Schedule.forever), Effect.provide(InterfereLogger.layer()), Effect.forkScoped, Effect.as(Effect.void));
199
- const add = (envelope) => Effect.gen(function* () {
200
- const size$1 = yield* Queue.size(envelopeQueue);
201
- if (size$1 >= capacity) {
202
- const toDrop = Math.max(1, size$1 - capacity + 1);
203
- yield* storage.dropOldest(toDrop).pipe(Effect.ignore);
204
- }
205
- yield* storage.append([envelope]).pipe(Effect.ignore);
206
- yield* Queue.offer(envelopeQueue, envelope);
207
- yield* Ref.update(metrics, (m) => ({
208
- ...m,
209
- queueDepth: m.queueDepth + 1
210
- }));
211
- yield* Effect.logDebug(`[${envelope.type}] Envelope '${envelope.uuid}' added to queue`).pipe(Effect.annotateLogs({
212
- envelopeId: envelope.uuid,
213
- queueSize: size$1 + 1
214
- }));
215
- }).pipe(Effect.withSpan("queue.add"));
216
- const size = () => Ref.get(metrics).pipe(Effect.map((m) => m.queueDepth));
217
- const boundedFlush = (timeoutMs) => Effect.race(flush(), Effect.sleep(Duration.millis(timeoutMs))).pipe(Effect.asVoid);
218
- const setReady = (ready) => Ref.set(readyRef, ready).pipe(Effect.zipRight(ready ? Effect.logDebug("Queue ready - enabling batch sends") : Effect.logDebug("Queue not ready - deferring batch sends")));
219
- const getMetrics = () => Ref.get(metrics);
220
- return {
221
- add,
222
- size,
223
- flush,
224
- boundedFlush,
225
- startBatchProcessor,
226
- setReady,
227
- getMetrics
228
- };
229
- }));
230
-
231
- //#endregion
232
- export { QueueServiceLive, QueueServiceTag };
@@ -1 +0,0 @@
1
- {"version":3,"file":"queue.layer.mjs","names":["storage: StorageAdapter","size"],"sources":["../../../src/effect/layers/queue.layer.ts"],"sourcesContent":["import {\n CircuitBreakerOpen,\n type CircuitBreakerState,\n createCircuitBreaker,\n DEFAULT_CIRCUIT_BREAKER_CONFIG,\n} from \"@interfere/effect-utils/circuit-breaker\";\nimport { InterfereLogger } from \"@interfere/effect-utils/observability\";\nimport {\n createBackoffSchedule,\n DEFAULT_RETRY_INITIAL_MS,\n DEFAULT_RETRY_MAX_MS,\n DEFAULT_RETRY_RECURS,\n} from \"@interfere/effect-utils/retry\";\nimport type { Envelope } from \"@interfere/types/sdk/envelope\";\n\nimport {\n Chunk,\n Context,\n Duration,\n Effect,\n Layer,\n Queue,\n Ref,\n Schedule,\n type Scope,\n} from \"effect\";\n\nimport type { HttpError } from \"../errors.js\";\nimport { ConfigTag } from \"../tags.js\";\nimport { HttpServiceTag } from \"./http.layer.js\";\nimport { createStorageAdapter, type StorageAdapter } from \"./storage.layer.js\";\n\n// Configuration constants\nconst MAX_RETRY_ATTEMPTS = 3; // Short-term retries for transient failures\nconst DEFER_SEND_DELAY_MS = 250; // Delay before sending when not ready\nconst BREAKER_RETRY_BASE_MS = 5000; // Base retry delay when breaker is open\nconst BREAKER_RETRY_MAX_MS = 30_000; // Max retry delay when breaker is open\nconst BREAKER_JITTER_FACTOR = 0.3; // 30% jitter for exponential backoff\nconst BREAKER_COALESCE_REMAINING_MS = 250; // Skip sending while breaker has more than this remaining\nconst TEST_OFFLINE_REQUEUE_LIMIT = 3; // Test-only cap to avoid infinite loops when offline\nconst OFFLINE_RETRY_BASE_MS = DEFAULT_RETRY_INITIAL_MS; // Base offline backoff\nconst OFFLINE_RETRY_MAX_MS = DEFAULT_RETRY_MAX_MS; // Cap offline backoff\nconst OFFLINE_JITTER_FACTOR = 0.3; // 30% jitter for offline backoff\n\nexport interface QueueMetrics {\n readonly queueDepth: number;\n readonly successCount: number;\n readonly failureCount: number;\n readonly circuitBreakerState: CircuitBreakerState;\n}\n\nexport interface QueueService {\n readonly add: (envelope: Envelope) => Effect.Effect<void>;\n readonly size: () => Effect.Effect<number>;\n readonly flush: () => Effect.Effect<void>;\n readonly boundedFlush: (timeoutMs: number) => Effect.Effect<void>;\n readonly startBatchProcessor: () => Effect.Effect<void, never, Scope.Scope>;\n readonly setReady: (ready: boolean) => Effect.Effect<void>;\n readonly getMetrics: () => Effect.Effect<QueueMetrics>;\n}\n\nexport class QueueServiceTag extends Context.Tag(\"@interfere/react/Queue\")<\n QueueServiceTag,\n QueueService\n>() {}\n\n/**\n * Creates the retry schedule for failed batches\n */\nconst createRetrySchedule = () =>\n createBackoffSchedule(\n DEFAULT_RETRY_INITIAL_MS,\n DEFAULT_RETRY_MAX_MS,\n MAX_RETRY_ATTEMPTS ?? DEFAULT_RETRY_RECURS\n );\n\n/**\n * Checks if the browser is online\n */\nconst isOnline = () =>\n Effect.sync(() =>\n typeof navigator !== \"undefined\" ? navigator.onLine : true\n );\n\n/**\n * Creates the queue service layer\n */\nexport const QueueServiceLive = Layer.scoped(\n QueueServiceTag,\n Effect.gen(function* () {\n const config = yield* ConfigTag;\n const http = yield* HttpServiceTag;\n\n // Create storage first to get its capacity\n const storage: StorageAdapter = createStorageAdapter();\n const capacity = storage.capacity();\n\n // Create the circuit breaker with default config\n const circuitBreaker = yield* createCircuitBreaker(\n DEFAULT_CIRCUIT_BREAKER_CONFIG\n );\n\n const metrics = yield* Ref.make<QueueMetrics>({\n queueDepth: 0,\n successCount: 0,\n failureCount: 0,\n circuitBreakerState: \"closed\",\n });\n\n const envelopeQueue = yield* Queue.sliding<Envelope>(capacity);\n const readyRef = yield* Ref.make<boolean>(false);\n const breakerRetryCount = yield* Ref.make<number>(0);\n\n yield* Effect.gen(function* () {\n const initial = yield* storage\n .load()\n .pipe(Effect.catchAll(() => Effect.succeed<Envelope[]>([])));\n\n // Filter out any entries missing required identifiers\n const filtered = initial.filter(\n (e) => typeof e.uuid === \"string\" && e.uuid.length > 0\n );\n\n if (filtered.length !== initial.length) {\n // Persist the cleaned set best-effort\n yield* storage.save(filtered).pipe(Effect.ignore);\n yield* Effect.logWarning(\n \"Purged persisted envelopes missing UUID\"\n ).pipe(\n Effect.annotateLogs({\n before: initial.length,\n after: filtered.length,\n removed: initial.length - filtered.length,\n })\n );\n }\n\n if (filtered.length > 0) {\n const seed =\n filtered.length > capacity\n ? filtered.slice(filtered.length - capacity)\n : filtered;\n\n yield* Queue.offerAll(envelopeQueue, Chunk.fromIterable(seed));\n\n // Ensure storage mirrors in-memory when trimming\n if (seed.length !== filtered.length) {\n yield* storage.save(seed).pipe(Effect.ignore);\n }\n\n yield* Ref.update(metrics, (m) => ({ ...m, queueDepth: seed.length }));\n\n yield* Effect.logDebug(\"Loaded persisted envelopes\").pipe(\n Effect.annotateLogs({\n count: seed.length,\n truncated: seed.length !== filtered.length,\n })\n );\n }\n });\n\n const requeueWithLog = (\n batch: Chunk.Chunk<Envelope>,\n message: string,\n extra?: Record<string, unknown>\n ) =>\n Effect.gen(function* () {\n yield* Queue.offerAll(envelopeQueue, batch);\n yield* Ref.update(metrics, (m) => ({\n ...m,\n queueDepth: m.queueDepth + Chunk.size(batch),\n }));\n yield* Effect.logWarning(message).pipe(\n Effect.annotateLogs({\n batchSize: Chunk.size(batch),\n ...(extra ?? {}),\n })\n );\n });\n\n const performHttpSend = (envelopes: Envelope[], ids: string[]) =>\n http.postEnvelopes(envelopes).pipe(\n Effect.tap(() => storage.removeByIds(ids).pipe(Effect.ignore)),\n Effect.tap(() =>\n Ref.update(metrics, (m) => ({\n ...m,\n successCount: m.successCount + envelopes.length,\n }))\n ),\n Effect.tap(() =>\n Effect.logDebug(\"Batch sent successfully\").pipe(\n Effect.annotateLogs({\n batchSize: envelopes.length,\n })\n )\n ),\n Effect.catchTag(\"HttpError\", (error: HttpError) =>\n Effect.gen(function* () {\n // If the server says the envelopes are invalid (schema mismatch),\n // drop this batch permanently instead of retrying.\n if (error.status === 422) {\n yield* storage.removeByIds(ids).pipe(Effect.ignore);\n yield* Ref.update(metrics, (m) => ({\n ...m,\n failureCount: m.failureCount + envelopes.length,\n }));\n yield* Effect.logWarning(\n \"Dropping invalid envelopes after 422 response\"\n ).pipe(\n Effect.annotateLogs({\n batchSize: envelopes.length,\n status: error.status,\n url: error.url,\n body: error.body,\n })\n );\n return;\n }\n\n // For all other HTTP errors, log and retry as before\n yield* Effect.all([\n Effect.logWarning(\"Failed to send batch\").pipe(\n Effect.annotateLogs({\n batchSize: envelopes.length,\n status: error.status,\n url: error.url,\n body: error.body,\n error: String(error),\n })\n ),\n Ref.update(metrics, (m) => ({\n ...m,\n failureCount: m.failureCount + 1,\n })),\n ]);\n\n // Re‑fail so the retry logic still kicks in\n return yield* Effect.fail(error);\n })\n ),\n Effect.retry(createRetrySchedule())\n );\n\n const sendWithBreaker = (\n envelopes: Envelope[],\n ids: string[],\n batch: Chunk.Chunk<Envelope>\n ) =>\n circuitBreaker.protect(performHttpSend(envelopes, ids)).pipe(\n Effect.catchIf(\n (error): error is CircuitBreakerOpen =>\n error instanceof CircuitBreakerOpen,\n () =>\n Effect.gen(function* () {\n const retryCount = yield* Ref.getAndUpdate(\n breakerRetryCount,\n (n) => n + 1\n );\n\n // Calculate exponential backoff with jitter\n const backoffMs = Math.min(\n BREAKER_RETRY_BASE_MS * 2 ** retryCount,\n BREAKER_RETRY_MAX_MS\n );\n const jitter = Math.random() * BREAKER_JITTER_FACTOR * backoffMs;\n const sleepMs = Math.floor(backoffMs + jitter);\n\n // Get remaining time until breaker might reset\n const data = yield* circuitBreaker.breakerData.get;\n const now = Date.now();\n const since = data.lastFailureTime\n ? now - data.lastFailureTime\n : 0;\n const remainingMs = Math.max(\n 0,\n DEFAULT_CIRCUIT_BREAKER_CONFIG.resetMs - since\n );\n\n yield* requeueWithLog(\n batch,\n \"Circuit breaker prevented batch send - requeueing\",\n {\n remainingMs,\n retryCount,\n nextRetryMs: sleepMs,\n }\n );\n\n yield* Effect.sleep(Duration.millis(sleepMs));\n })\n ),\n Effect.tap(() =>\n // Reset retry count on successful send or after final failure\n Ref.set(breakerRetryCount, 0)\n ),\n Effect.catchAll((error) =>\n requeueWithLog(batch, \"Failed to send batch after all retries\", {\n error: String(error),\n })\n ),\n // Always update circuit breaker state in metrics\n Effect.tap(() =>\n Effect.gen(function* () {\n const state = yield* circuitBreaker.getState;\n yield* Ref.update(metrics, (m) => ({\n ...m,\n circuitBreakerState: state,\n }));\n })\n ),\n Effect.withSpan(\"queue.sendBatch\", {\n attributes: { batchSize: envelopes.length },\n })\n );\n\n // Track offline requeue attempts in test environments\n const offlineAttemptsRef = yield* Ref.make(0);\n\n const handleOffline = (batch: Chunk.Chunk<Envelope>) =>\n Effect.gen(function* () {\n // Increment attempts and compute backoff\n const attempts = yield* Ref.getAndUpdate(\n offlineAttemptsRef,\n (n) => n + 1\n );\n if (\n typeof process !== \"undefined\" &&\n process.env?.VITEST &&\n attempts >= TEST_OFFLINE_REQUEUE_LIMIT\n ) {\n yield* Effect.logWarning(\n `Offline - dropping batch after ${TEST_OFFLINE_REQUEUE_LIMIT} attempts (test mode)`\n );\n return;\n }\n\n yield* requeueWithLog(batch, \"Offline - requeueing batch\");\n\n const base = OFFLINE_RETRY_BASE_MS;\n\n const capped = Math.min(base * 2 ** attempts, OFFLINE_RETRY_MAX_MS);\n const jitter = Math.random() * OFFLINE_JITTER_FACTOR * capped;\n const sleepMs = Math.floor(capped + jitter);\n\n yield* Effect.sleep(Duration.millis(sleepMs));\n });\n\n const sendBatch = (batch: Chunk.Chunk<Envelope>) =>\n Effect.gen(function* () {\n if (Chunk.isEmpty(batch)) {\n return;\n }\n\n const online = yield* isOnline();\n if (!online) {\n yield* handleOffline(batch);\n return;\n }\n\n const envelopes = Chunk.toArray(batch);\n const ids = envelopes\n .map((e) => e.uuid)\n .filter((v): v is string => typeof v === \"string\");\n\n yield* sendWithBreaker(envelopes, ids, batch);\n });\n\n const flush = () =>\n Effect.gen(function* () {\n const items = yield* Queue.takeAll(envelopeQueue);\n\n yield* Ref.update(metrics, (m) => ({\n ...m,\n queueDepth: 0,\n }));\n\n if (Chunk.isNonEmpty(items)) {\n yield* sendBatch(items).pipe(Effect.catchAll(() => Effect.void));\n }\n }).pipe(Effect.withSpan(\"queue.flush\"));\n\n const processBatch = Effect.gen(function* () {\n const isReady = yield* Ref.get(readyRef);\n\n if (!isReady) {\n yield* Effect.sleep(Duration.millis(DEFER_SEND_DELAY_MS));\n\n return;\n }\n\n // If the circuit breaker is open, coalesce batches by skipping sends\n // until the remaining cooldown is small. This prevents a retry storm.\n const breakerState = yield* circuitBreaker.getState;\n if (breakerState === \"open\") {\n const data = yield* circuitBreaker.breakerData.get;\n const now = Date.now();\n const since = data.lastFailureTime ? now - data.lastFailureTime : 0;\n const remainingMs = Math.max(\n 0,\n DEFAULT_CIRCUIT_BREAKER_CONFIG.resetMs - since\n );\n\n // Reflect breaker state in metrics\n yield* Ref.update(metrics, (m) => ({\n ...m,\n circuitBreakerState: breakerState,\n }));\n\n if (remainingMs > BREAKER_COALESCE_REMAINING_MS) {\n yield* Effect.logDebug(\n \"Circuit breaker open - coalescing batches until cooldown is small\"\n ).pipe(Effect.annotateLogs({ remainingMs }));\n // Sleep until near the reset time to allow one consolidated send attempt\n yield* Effect.sleep(\n Duration.millis(remainingMs - BREAKER_COALESCE_REMAINING_MS)\n );\n return;\n }\n }\n\n const batchSize = config.batch.size;\n const batchMs = config.batch.ms;\n\n const pending = yield* Queue.takeBetween(\n envelopeQueue,\n 1,\n batchSize\n ).pipe(\n Effect.timeout(Duration.millis(batchMs)),\n Effect.catchTag(\"TimeoutException\", () =>\n Queue.takeAll(envelopeQueue).pipe(\n Effect.map((items) =>\n Chunk.isEmpty(items) ? Chunk.empty<Envelope>() : items\n )\n )\n )\n );\n\n yield* Ref.update(metrics, (m) => ({\n ...m,\n queueDepth: Math.max(0, m.queueDepth - Chunk.size(pending)),\n }));\n\n if (Chunk.isNonEmpty(pending)) {\n yield* Effect.logDebug(\"Processing batch\").pipe(\n Effect.annotateLogs({ batchSize: Chunk.size(pending) })\n );\n yield* sendBatch(pending);\n }\n });\n\n const startBatchProcessor = () =>\n processBatch.pipe(\n Effect.catchAllCause((cause) =>\n Effect.logError(\"Batch processor error\", { cause: String(cause) })\n ),\n Effect.repeat(Schedule.forever),\n // Ensure the forked fiber uses the same logger configuration\n Effect.provide(InterfereLogger.layer()),\n Effect.forkScoped,\n Effect.as(Effect.void)\n );\n\n const add = (envelope: Envelope) =>\n Effect.gen(function* () {\n const size = yield* Queue.size(envelopeQueue);\n\n if (size >= capacity) {\n const toDrop = Math.max(1, size - capacity + 1);\n\n yield* storage.dropOldest(toDrop).pipe(Effect.ignore);\n }\n\n yield* storage.append([envelope]).pipe(Effect.ignore);\n yield* Queue.offer(envelopeQueue, envelope);\n yield* Ref.update(metrics, (m) => ({\n ...m,\n queueDepth: m.queueDepth + 1,\n }));\n yield* Effect.logDebug(\n `[${envelope.type}] Envelope '${envelope.uuid}' added to queue`\n ).pipe(\n Effect.annotateLogs({\n envelopeId: envelope.uuid,\n queueSize: size + 1,\n })\n );\n }).pipe(Effect.withSpan(\"queue.add\"));\n\n const size = () => Ref.get(metrics).pipe(Effect.map((m) => m.queueDepth));\n\n const boundedFlush = (timeoutMs: number) =>\n Effect.race(flush(), Effect.sleep(Duration.millis(timeoutMs))).pipe(\n Effect.asVoid\n );\n\n const setReady = (ready: boolean) =>\n Ref.set(readyRef, ready).pipe(\n Effect.zipRight(\n ready\n ? Effect.logDebug(\"Queue ready - enabling batch sends\")\n : Effect.logDebug(\"Queue not ready - deferring batch sends\")\n )\n );\n\n const getMetrics = () => Ref.get(metrics);\n\n return {\n add,\n size,\n flush,\n boundedFlush,\n startBatchProcessor,\n setReady,\n getMetrics,\n } satisfies QueueService;\n })\n);\n"],"mappings":"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"}