@effect-app/infra 4.0.0-beta.302 → 4.0.0-beta.303

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @effect-app/infra
2
2
 
3
+ ## 4.0.0-beta.303
4
+
5
+ ### Patch Changes
6
+
7
+ - 33b0544: Replace repository codec child spans with parent-span timing attributes and bounded metrics, and record database operation timing for instrumented stores.
8
+ - Updated dependencies [33b0544]
9
+ - effect-app@4.0.0-beta.303
10
+
3
11
  ## 4.0.0-beta.302
4
12
 
5
13
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"otel.d.ts","sourceRoot":"","sources":["../src/otel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAA;AAE3C,MAAM,MAAM,QAAQ,GAChB,YAAY,GACZ,QAAQ,GACR,UAAU,GACV,SAAS,GACT,OAAO,GACP,WAAW,GACX,QAAQ,GACR,MAAM,CAAA;AAEV,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAA;IACzB,mDAAmD;IACnD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,0CAA0C;IAC1C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,oEAAoE;IACpE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACpC,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;CACrD;AAYD;;;;GAIG;AACH,eAAO,MAAM,UAAU,MAAO,aAAa,4IAKxC,CAAA;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,MAAO,aAAa,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAG,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CACrC,CAAA;AAEzE,sEAAsE;AACtE,eAAO,MAAM,kBAAkB,MAAO;IACpC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC5C,sCAIG,CAAA;AAEJ,4CAA4C;AAC5C,eAAO,MAAM,sBAAsB,MAAO;IACxC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3C,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACzC,sCAMG,CAAA;AAEJ,MAAM,MAAM,eAAe,GACvB,YAAY,GACZ,UAAU,GACV,OAAO,GACP,QAAQ,GACR,KAAK,CAAA;AAET,MAAM,MAAM,kBAAkB,GAC1B,SAAS,GACT,QAAQ,GACR,SAAS,GACT,SAAS,GACT,QAAQ,CAAA;AAEZ,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAA;IACtC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;IAChC,0BAA0B;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;CACrD;AAYD,0DAA0D;AAC1D,eAAO,MAAM,iBAAiB,MACzB,oBAAoB,QACjB,UAAU,GAAG,UAAU,4IAM5B,CAAA;AAEH,yGAAyG;AACzG,eAAO,MAAM,iBAAiB,MACzB,oBAAoB,QACjB,UAAU,GAAG,UAAU;;;;CAMlB,CAAA"}
1
+ {"version":3,"file":"otel.d.ts","sourceRoot":"","sources":["../src/otel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAA;AAG3C,MAAM,MAAM,QAAQ,GAChB,YAAY,GACZ,QAAQ,GACR,UAAU,GACV,SAAS,GACT,OAAO,GACP,WAAW,GACX,QAAQ,GACR,MAAM,CAAA;AAEV,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAA;IACzB,mDAAmD;IACnD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,0CAA0C;IAC1C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,oEAAoE;IACpE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACpC,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;CACrD;AAoCD;;;;GAIG;AACH,eAAO,MAAM,UAAU,MAAO,aAAa,4IAKxC,CAAA;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,MAAO,aAAa,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAG,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CACZ,CAAA;AAElG,sEAAsE;AACtE,eAAO,MAAM,kBAAkB,MAAO;IACpC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC5C,sCAIG,CAAA;AAEJ,4CAA4C;AAC5C,eAAO,MAAM,sBAAsB,MAAO;IACxC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3C,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACzC,sCAMG,CAAA;AAEJ,MAAM,MAAM,eAAe,GACvB,YAAY,GACZ,UAAU,GACV,OAAO,GACP,QAAQ,GACR,KAAK,CAAA;AAET,MAAM,MAAM,kBAAkB,GAC1B,SAAS,GACT,QAAQ,GACR,SAAS,GACT,SAAS,GACT,QAAQ,CAAA;AAEZ,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAA;IACtC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;IAChC,0BAA0B;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;CACrD;AAYD,0DAA0D;AAC1D,eAAO,MAAM,iBAAiB,MACzB,oBAAoB,QACjB,UAAU,GAAG,UAAU,4IAM5B,CAAA;AAEH,yGAAyG;AACzG,eAAO,MAAM,iBAAiB,MACzB,oBAAoB,QACjB,UAAU,GAAG,UAAU;;;;CAMlB,CAAA"}
package/dist/otel.js CHANGED
@@ -9,6 +9,7 @@
9
9
  * - Cosmos DB: https://opentelemetry.io/docs/specs/semconv/database/cosmosdb/
10
10
  */
11
11
  import * as Effect from "effect-app/Effect";
12
+ import * as Metric from "effect/Metric";
12
13
  const dbAttributes = (a) => ({
13
14
  "db.system.name": a.system,
14
15
  "db.operation.name": a.operation,
@@ -18,6 +19,24 @@ const dbAttributes = (a) => ({
18
19
  ...(a.entity !== undefined && { "app.entity": a.entity }),
19
20
  ...a.extra
20
21
  });
22
+ const dbOperationDuration = Metric.histogram("db.client.operation.duration", {
23
+ boundaries: [0.1, 0.5, 1, 2, 5, 10, 25, 50, 100, 250, 500, 1_000, 2_500, 5_000, 10_000],
24
+ description: "Caller-observed database operation duration in milliseconds"
25
+ });
26
+ const timeDbOperation = (a) => (self) => Effect.clockWith((clock) => {
27
+ const startedAt = clock.currentTimeNanosUnsafe();
28
+ return Effect.onExit(self, () => {
29
+ const durationMs = Number(clock.currentTimeNanosUnsafe() - startedAt) / 1_000_000;
30
+ return Effect.all([
31
+ Effect.annotateCurrentSpan({ "db.operation.duration_ms": durationMs }),
32
+ Metric.update(Metric.withAttributes(dbOperationDuration, {
33
+ "db.system.name": a.system,
34
+ "db.operation.name": a.operation,
35
+ ...(a.entity !== undefined && { "app.entity": a.entity })
36
+ }), durationMs)
37
+ ], { discard: true });
38
+ });
39
+ });
21
40
  /**
22
41
  * Wrap an effect with an OTel-semconv database span.
23
42
  *
@@ -32,7 +51,7 @@ export const withDbSpan = (a) => Effect.withSpan(`${a.operation} ${a.collection}
32
51
  * Annotates before running so attrs persist even on failure.
33
52
  * No-op if there is no current span.
34
53
  */
35
- export const annotateDb = (a) => (self) => Effect.flatMap(Effect.annotateCurrentSpan(dbAttributes(a)), () => self);
54
+ export const annotateDb = (a) => (self) => Effect.flatMap(Effect.annotateCurrentSpan(dbAttributes(a)), () => self.pipe(timeDbOperation(a)));
36
55
  /** Annotate the current span with response metrics from a DB call. */
37
56
  export const annotateDbResponse = (m) => Effect.annotateCurrentSpan({
38
57
  ...(m.returnedRows !== undefined && { "db.response.returned_rows": m.returnedRows }),
@@ -62,4 +81,4 @@ export const messagingSpanArgs = (a, kind) => ({
62
81
  kind,
63
82
  attributes: messagingAttributes(a)
64
83
  });
65
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3RlbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9vdGVsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7R0FTRztBQUVILE9BQU8sS0FBSyxNQUFNLE1BQU0sbUJBQW1CLENBQUE7QUE0QjNDLE1BQU0sWUFBWSxHQUFHLENBQUMsQ0FBZ0IsRUFBMkIsRUFBRSxDQUFDLENBQUM7SUFDbkUsZ0JBQWdCLEVBQUUsQ0FBQyxDQUFDLE1BQU07SUFDMUIsbUJBQW1CLEVBQUUsQ0FBQyxDQUFDLFNBQVM7SUFDaEMsb0JBQW9CLEVBQUUsQ0FBQyxDQUFDLFVBQVU7SUFDbEMsR0FBRyxDQUFDLENBQUMsQ0FBQyxTQUFTLEtBQUssU0FBUyxJQUFJLEVBQUUsY0FBYyxFQUFFLENBQUMsQ0FBQyxTQUFTLEVBQUUsQ0FBQztJQUNqRSxHQUFHLENBQUMsQ0FBQyxDQUFDLEtBQUssS0FBSyxTQUFTLElBQUksRUFBRSxlQUFlLEVBQUUsQ0FBQyxDQUFDLEtBQUssRUFBRSxDQUFDO0lBQzFELEdBQUcsQ0FBQyxDQUFDLENBQUMsTUFBTSxLQUFLLFNBQVMsSUFBSSxFQUFFLFlBQVksRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUM7SUFDekQsR0FBRyxDQUFDLENBQUMsS0FBSztDQUNYLENBQUMsQ0FBQTtBQUVGOzs7O0dBSUc7QUFDSCxNQUFNLENBQUMsTUFBTSxVQUFVLEdBQUcsQ0FBQyxDQUFnQixFQUFFLEVBQUUsQ0FDN0MsTUFBTSxDQUFDLFFBQVEsQ0FDYixHQUFHLENBQUMsQ0FBQyxTQUFTLElBQUksQ0FBQyxDQUFDLFVBQVUsRUFBRSxFQUNoQyxFQUFFLFVBQVUsRUFBRSxZQUFZLENBQUMsQ0FBQyxDQUFDLEVBQUUsSUFBSSxFQUFFLFFBQWlCLEVBQUUsRUFDeEQsRUFBRSxpQkFBaUIsRUFBRSxLQUFLLEVBQUUsQ0FDN0IsQ0FBQTtBQUVIOzs7Ozs7O0dBT0c7QUFDSCxNQUFNLENBQUMsTUFBTSxVQUFVLEdBQUcsQ0FB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
84
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3RlbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9vdGVsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7R0FTRztBQUVILE9BQU8sS0FBSyxNQUFNLE1BQU0sbUJBQW1CLENBQUE7QUFDM0MsT0FBTyxLQUFLLE1BQU0sTUFBTSxlQUFlLENBQUE7QUE0QnZDLE1BQU0sWUFBWSxHQUFHLENBQUMsQ0FBZ0IsRUFBMkIsRUFBRSxDQUFDLENBQUM7SUFDbkUsZ0JBQWdCLEVBQUUsQ0FBQyxDQUFDLE1BQU07SUFDMUIsbUJBQW1CLEVBQUUsQ0FBQyxDQUFDLFNBQVM7SUFDaEMsb0JBQW9CLEVBQUUsQ0FBQyxDQUFDLFVBQVU7SUFDbEMsR0FBRyxDQUFDLENBQUMsQ0FBQyxTQUFTLEtBQUssU0FBUyxJQUFJLEVBQUUsY0FBYyxFQUFFLENBQUMsQ0FBQyxTQUFTLEVBQUUsQ0FBQztJQUNqRSxHQUFHLENBQUMsQ0FBQyxDQUFDLEtBQUssS0FBSyxTQUFTLElBQUksRUFBRSxlQUFlLEVBQUUsQ0FBQyxDQUFDLEtBQUssRUFBRSxDQUFDO0lBQzFELEdBQUcsQ0FBQyxDQUFDLENBQUMsTUFBTSxLQUFLLFNBQVMsSUFBSSxFQUFFLFlBQVksRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUM7SUFDekQsR0FBRyxDQUFDLENBQUMsS0FBSztDQUNYLENBQUMsQ0FBQTtBQUVGLE1BQU0sbUJBQW1CLEdBQUcsTUFBTSxDQUFDLFNBQVMsQ0FBQyw4QkFBOEIsRUFBRTtJQUMzRSxVQUFVLEVBQUUsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLE1BQU0sQ0FBQztJQUN2RixXQUFXLEVBQUUsNkRBQTZEO0NBQzNFLENBQUMs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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-app/infra",
3
- "version": "4.0.0-beta.302",
3
+ "version": "4.0.0-beta.303",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -15,7 +15,7 @@
15
15
  "proper-lockfile": "^4.1.2",
16
16
  "pure-rand": "8.4.0",
17
17
  "query-string": "^9.4.0",
18
- "effect-app": "4.0.0-beta.302"
18
+ "effect-app": "4.0.0-beta.303"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@azure/cosmos": "^4.9.3",
package/src/otel.ts CHANGED
@@ -10,6 +10,7 @@
10
10
  */
11
11
 
12
12
  import * as Effect from "effect-app/Effect"
13
+ import * as Metric from "effect/Metric"
13
14
 
14
15
  export type DbSystem =
15
16
  | "postgresql"
@@ -47,6 +48,30 @@ const dbAttributes = (a: DbSpanOptions): Record<string, unknown> => ({
47
48
  ...a.extra
48
49
  })
49
50
 
51
+ const dbOperationDuration = Metric.histogram("db.client.operation.duration", {
52
+ boundaries: [0.1, 0.5, 1, 2, 5, 10, 25, 50, 100, 250, 500, 1_000, 2_500, 5_000, 10_000],
53
+ description: "Caller-observed database operation duration in milliseconds"
54
+ })
55
+
56
+ const timeDbOperation = (a: DbSpanOptions) => <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
57
+ Effect.clockWith((clock) => {
58
+ const startedAt = clock.currentTimeNanosUnsafe()
59
+ return Effect.onExit(self, () => {
60
+ const durationMs = Number(clock.currentTimeNanosUnsafe() - startedAt) / 1_000_000
61
+ return Effect.all([
62
+ Effect.annotateCurrentSpan({ "db.operation.duration_ms": durationMs }),
63
+ Metric.update(
64
+ Metric.withAttributes(dbOperationDuration, {
65
+ "db.system.name": a.system,
66
+ "db.operation.name": a.operation,
67
+ ...(a.entity !== undefined && { "app.entity": a.entity })
68
+ }),
69
+ durationMs
70
+ )
71
+ ], { discard: true })
72
+ })
73
+ })
74
+
50
75
  /**
51
76
  * Wrap an effect with an OTel-semconv database span.
52
77
  *
@@ -68,7 +93,7 @@ export const withDbSpan = (a: DbSpanOptions) =>
68
93
  * No-op if there is no current span.
69
94
  */
70
95
  export const annotateDb = (a: DbSpanOptions) => <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
71
- Effect.flatMap(Effect.annotateCurrentSpan(dbAttributes(a)), () => self)
96
+ Effect.flatMap(Effect.annotateCurrentSpan(dbAttributes(a)), () => self.pipe(timeDbOperation(a)))
72
97
 
73
98
  /** Annotate the current span with response metrics from a DB call. */
74
99
  export const annotateDbResponse = (m: {
@@ -8,6 +8,7 @@ import { RepositoryRegistryLive } from "effect-app/Model/Repository/Registry"
8
8
  import * as S from "effect-app/Schema"
9
9
  import { setupRequestContextFromCurrent } from "effect-app/setupRequest"
10
10
  import * as Ref from "effect/Ref"
11
+ import * as Tracer from "effect/Tracer"
11
12
  import { MemoryStoreLive } from "../src/Store/Memory.js"
12
13
 
13
14
  class BatchItem extends S.Class<BatchItem>("BatchItem")({
@@ -175,4 +176,41 @@ describe("repository ext save/remove batching", () => {
175
176
  setupRequestContextFromCurrent(),
176
177
  Effect.provide(TestStoreLive)
177
178
  ))
179
+
180
+ it.effect("records schema timing on repository spans without codec child spans", () =>
181
+ Effect
182
+ .gen(function*() {
183
+ const spans: Tracer.NativeSpan[] = []
184
+ const tracer = Tracer.make({
185
+ span(options) {
186
+ const span = new Tracer.NativeSpan(options)
187
+ spans.push(span)
188
+ return span
189
+ }
190
+ })
191
+
192
+ yield* Effect
193
+ .gen(function*() {
194
+ const repo = yield* makeRepo("TelemetryItem", BatchItem, {})
195
+ yield* repo.save(new BatchItem({ id: "1", label: "one" }))
196
+ yield* repo.all
197
+ })
198
+ .pipe(Effect.provideService(Tracer.Tracer, tracer))
199
+
200
+ expect(spans.map((_) => _.name)).not.toContain("parseMany")
201
+ expect(spans.map((_) => _.name)).not.toContain("encodeMany")
202
+
203
+ const saveSpan = spans.find((_) => _.name === "Repository.saveAndPublish")
204
+ const allSpan = spans.find((_) => _.name === "Repository.all")
205
+ expect(saveSpan?.attributes.get("app.schema.encode.duration_ms")).toEqual(expect.any(Number))
206
+ expect(saveSpan?.attributes.get("app.schema.item_count")).toBe(1)
207
+ expect(saveSpan?.attributes.get("db.operation.duration_ms")).toEqual(expect.any(Number))
208
+ expect(allSpan?.attributes.get("app.schema.decode.duration_ms")).toEqual(expect.any(Number))
209
+ expect(allSpan?.attributes.get("app.schema.item_count")).toBe(1)
210
+ expect(allSpan?.attributes.get("db.operation.duration_ms")).toEqual(expect.any(Number))
211
+ })
212
+ .pipe(
213
+ setupRequestContextFromCurrent(),
214
+ Effect.provide(TestStoreLive)
215
+ ))
178
216
  })