@effect-app/infra 4.0.0-beta.302 → 4.0.0-beta.304
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 +15 -0
- package/dist/otel.d.ts.map +1 -1
- package/dist/otel.js +21 -2
- package/package.json +2 -2
- package/src/otel.ts +26 -1
- package/test/repository-ext.test.ts +107 -1
- package/test/rpc-e2e-invalidation.test.ts +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @effect-app/infra
|
|
2
2
|
|
|
3
|
+
## 4.0.0-beta.304
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [412f08b]
|
|
8
|
+
- effect-app@4.0.0-beta.304
|
|
9
|
+
|
|
10
|
+
## 4.0.0-beta.303
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- 33b0544: Replace repository codec child spans with parent-span timing attributes and bounded metrics, and record database operation timing for instrumented stores.
|
|
15
|
+
- Updated dependencies [33b0544]
|
|
16
|
+
- effect-app@4.0.0-beta.303
|
|
17
|
+
|
|
3
18
|
## 4.0.0-beta.302
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/dist/otel.d.ts.map
CHANGED
|
@@ -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;
|
|
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,
|
|
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.
|
|
3
|
+
"version": "4.0.0-beta.304",
|
|
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.
|
|
18
|
+
"effect-app": "4.0.0-beta.304"
|
|
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")({
|
|
@@ -169,7 +170,112 @@ describe("repository ext save/remove batching", () => {
|
|
|
169
170
|
.pipe(Effect.provideService(DataDependencies.DataDependencyRecorder, recorder))
|
|
170
171
|
|
|
171
172
|
expect(yield* Ref.get(readsRef)).toEqual(new Set([DataDependencies.repo("DependencyItem")]))
|
|
172
|
-
expect(yield* Ref.get(writesRef)).toEqual(new Set([DataDependencies.repo("DependencyItem")]))
|
|
173
|
+
expect(yield* Ref.get(writesRef)).toEqual(new Set([DataDependencies.repo("DependencyItem", ["1"])]))
|
|
174
|
+
})
|
|
175
|
+
.pipe(
|
|
176
|
+
setupRequestContextFromCurrent(),
|
|
177
|
+
Effect.provide(TestStoreLive)
|
|
178
|
+
))
|
|
179
|
+
|
|
180
|
+
it.effect("narrows direct repository reads to the requested entity", () =>
|
|
181
|
+
Effect
|
|
182
|
+
.gen(function*() {
|
|
183
|
+
const readsRef = yield* Ref.make(DataDependencies.empty())
|
|
184
|
+
const writesRef = yield* Ref.make(DataDependencies.empty())
|
|
185
|
+
const recorder = DataDependencies.makeDataDependencyRecorder(readsRef, writesRef)
|
|
186
|
+
|
|
187
|
+
yield* Effect
|
|
188
|
+
.gen(function*() {
|
|
189
|
+
const repo = yield* makeRepo("DependencyItem", BatchItem, {})
|
|
190
|
+
yield* repo.find("1")
|
|
191
|
+
})
|
|
192
|
+
.pipe(Effect.provideService(DataDependencies.DataDependencyRecorder, recorder))
|
|
193
|
+
|
|
194
|
+
expect(yield* Ref.get(readsRef)).toEqual(new Set([DataDependencies.repo("DependencyItem", ["1"])]))
|
|
195
|
+
})
|
|
196
|
+
.pipe(
|
|
197
|
+
setupRequestContextFromCurrent(),
|
|
198
|
+
Effect.provide(TestStoreLive)
|
|
199
|
+
))
|
|
200
|
+
|
|
201
|
+
it("matches entity dependencies by overlapping ids with a coarse fallback", () => {
|
|
202
|
+
const item1 = new Set([DataDependencies.repo("DependencyItem", ["1"])])
|
|
203
|
+
const item2 = new Set([DataDependencies.repo("DependencyItem", ["2"])])
|
|
204
|
+
const collection = new Set([DataDependencies.repo("DependencyItem")])
|
|
205
|
+
|
|
206
|
+
expect(DataDependencies.intersects(item1, item2)).toBe(false)
|
|
207
|
+
expect(DataDependencies.intersects(item1, item1)).toBe(true)
|
|
208
|
+
expect(DataDependencies.intersects(collection, item2)).toBe(true)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it("decodes repository dependencies produced before entity ids were added", () => {
|
|
212
|
+
expect(
|
|
213
|
+
S.decodeUnknownSync(DataDependencies.DataDependency)({
|
|
214
|
+
type: "repo",
|
|
215
|
+
name: "DependencyItem"
|
|
216
|
+
})
|
|
217
|
+
)
|
|
218
|
+
.toEqual(DataDependencies.repo("DependencyItem"))
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it.effect("matches an explicit query scope to a write alias", () =>
|
|
222
|
+
Effect
|
|
223
|
+
.gen(function*() {
|
|
224
|
+
const readsRef = yield* Ref.make(DataDependencies.empty())
|
|
225
|
+
const writesRef = yield* Ref.make(DataDependencies.empty())
|
|
226
|
+
const recorder = DataDependencies.makeDataDependencyRecorder(readsRef, writesRef)
|
|
227
|
+
|
|
228
|
+
yield* Effect
|
|
229
|
+
.gen(function*() {
|
|
230
|
+
const repo = yield* makeRepo("DependencyItem", BatchItem, {
|
|
231
|
+
dependencyIds: (item) => [item.id, `alias-${item.id}`]
|
|
232
|
+
})
|
|
233
|
+
yield* repo.save(new BatchItem({ id: "1", label: "one" }))
|
|
234
|
+
yield* repo.all.pipe(DataDependencies.withRepoReadScope("DependencyItem", ["alias-1"]))
|
|
235
|
+
})
|
|
236
|
+
.pipe(Effect.provideService(DataDependencies.DataDependencyRecorder, recorder))
|
|
237
|
+
|
|
238
|
+
expect(yield* Ref.get(readsRef)).toEqual(new Set([DataDependencies.repo("DependencyItem", ["alias-1"])]))
|
|
239
|
+
expect(yield* Ref.get(writesRef)).toEqual(
|
|
240
|
+
new Set([DataDependencies.repo("DependencyItem", ["1", "alias-1"])])
|
|
241
|
+
)
|
|
242
|
+
})
|
|
243
|
+
.pipe(
|
|
244
|
+
setupRequestContextFromCurrent(),
|
|
245
|
+
Effect.provide(TestStoreLive)
|
|
246
|
+
))
|
|
247
|
+
|
|
248
|
+
it.effect("records schema timing on repository spans without codec child spans", () =>
|
|
249
|
+
Effect
|
|
250
|
+
.gen(function*() {
|
|
251
|
+
const spans: Tracer.NativeSpan[] = []
|
|
252
|
+
const tracer = Tracer.make({
|
|
253
|
+
span(options) {
|
|
254
|
+
const span = new Tracer.NativeSpan(options)
|
|
255
|
+
spans.push(span)
|
|
256
|
+
return span
|
|
257
|
+
}
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
yield* Effect
|
|
261
|
+
.gen(function*() {
|
|
262
|
+
const repo = yield* makeRepo("TelemetryItem", BatchItem, {})
|
|
263
|
+
yield* repo.save(new BatchItem({ id: "1", label: "one" }))
|
|
264
|
+
yield* repo.all
|
|
265
|
+
})
|
|
266
|
+
.pipe(Effect.provideService(Tracer.Tracer, tracer))
|
|
267
|
+
|
|
268
|
+
expect(spans.map((_) => _.name)).not.toContain("parseMany")
|
|
269
|
+
expect(spans.map((_) => _.name)).not.toContain("encodeMany")
|
|
270
|
+
|
|
271
|
+
const saveSpan = spans.find((_) => _.name === "Repository.saveAndPublish")
|
|
272
|
+
const allSpan = spans.find((_) => _.name === "Repository.all")
|
|
273
|
+
expect(saveSpan?.attributes.get("app.schema.encode.duration_ms")).toEqual(expect.any(Number))
|
|
274
|
+
expect(saveSpan?.attributes.get("app.schema.item_count")).toBe(1)
|
|
275
|
+
expect(saveSpan?.attributes.get("db.operation.duration_ms")).toEqual(expect.any(Number))
|
|
276
|
+
expect(allSpan?.attributes.get("app.schema.decode.duration_ms")).toEqual(expect.any(Number))
|
|
277
|
+
expect(allSpan?.attributes.get("app.schema.item_count")).toBe(1)
|
|
278
|
+
expect(allSpan?.attributes.get("db.operation.duration_ms")).toEqual(expect.any(Number))
|
|
173
279
|
})
|
|
174
280
|
.pipe(
|
|
175
281
|
setupRequestContextFromCurrent(),
|
|
@@ -358,8 +358,8 @@ it.live(
|
|
|
358
358
|
const writes = yield* Ref.get(writesRef)
|
|
359
359
|
expect(values).toStrictEqual([1, 2, 3])
|
|
360
360
|
// Each emitted value writes to RepoItem; routing drains the writes per chunk and the client
|
|
361
|
-
// accumulates
|
|
362
|
-
expect(writes).toStrictEqual(new Set([DataDependencies.repo("RepoItem")]))
|
|
361
|
+
// accumulates their entity ids into one dependency.
|
|
362
|
+
expect(writes).toStrictEqual(new Set([DataDependencies.repo("RepoItem", ["1", "2", "3"])]))
|
|
363
363
|
}, Effect.provide(TestLayer)),
|
|
364
364
|
{ timeout: 10_000 }
|
|
365
365
|
)
|
|
@@ -377,7 +377,7 @@ it.live(
|
|
|
377
377
|
const command = yield* withDependencyCapture(client.SaveRepoItem.handler({ id: "1", label: "one" }))
|
|
378
378
|
expect(Exit.isSuccess(command.result)).toBe(true)
|
|
379
379
|
expect(command.dependencies.reads).toStrictEqual(DataDependencies.empty())
|
|
380
|
-
expect(command.dependencies.writes).toStrictEqual(new Set([DataDependencies.repo("RepoItem")]))
|
|
380
|
+
expect(command.dependencies.writes).toStrictEqual(new Set([DataDependencies.repo("RepoItem", ["1"])]))
|
|
381
381
|
}, Effect.provide(TestLayer)),
|
|
382
382
|
{ timeout: 10_000 }
|
|
383
383
|
)
|
|
@@ -411,7 +411,7 @@ it.live(
|
|
|
411
411
|
// A command writing an UNRELATED repo => the query is NOT invalidated (negative control).
|
|
412
412
|
const saveOther = yield* withDependencyCapture(client.SaveOtherItem.handler({ id: "2", label: "two" }))
|
|
413
413
|
expect(Exit.isSuccess(saveOther.result)).toBe(true)
|
|
414
|
-
expect(saveOther.dependencies.writes).toStrictEqual(new Set([DataDependencies.repo("OtherItem")]))
|
|
414
|
+
expect(saveOther.dependencies.writes).toStrictEqual(new Set([DataDependencies.repo("OtherItem", ["2"])]))
|
|
415
415
|
expect(invalidatedBy(saveOther.dependencies.writes)).toStrictEqual([])
|
|
416
416
|
}, Effect.provide(TestLayer)),
|
|
417
417
|
{ timeout: 10_000 }
|