@r_masseater/vitest-otlp 0.0.0-stage → 0.0.0
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/LICENSE +87 -0
- package/README.md +170 -2
- package/dist/context-COyJBDQM.mjs +6 -0
- package/dist/context-COyJBDQM.mjs.map +1 -0
- package/dist/index.d.mts +98 -0
- package/dist/index.mjs +186 -0
- package/dist/index.mjs.map +1 -0
- package/dist/semconv.d.mts +4 -0
- package/dist/semconv.mjs +92 -0
- package/dist/semconv.mjs.map +1 -0
- package/dist/setup.d.mts +1 -0
- package/dist/setup.mjs +337 -0
- package/dist/setup.mjs.map +1 -0
- package/dist/store-8DpTwYxj.d.mts +9 -0
- package/dist/store-DSDf4Wia.mjs +13 -0
- package/dist/store-DSDf4Wia.mjs.map +1 -0
- package/dist/tail.worker.js +256 -0
- package/dist/tail.worker.js.map +1 -0
- package/dist/workers.d.mts +26 -0
- package/dist/workers.mjs +54 -0
- package/dist/workers.mjs.map +1 -0
- package/package.json +75 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 masseater
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
@tanstack/eslint-plugin-start
|
|
26
|
+
|
|
27
|
+
MIT License
|
|
28
|
+
|
|
29
|
+
Copyright (c) 2021-present Tanner Linsley
|
|
30
|
+
|
|
31
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
32
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
33
|
+
in the Software without restriction, including without limitation the rights
|
|
34
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
35
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
36
|
+
furnished to do so, subject to the following conditions:
|
|
37
|
+
|
|
38
|
+
The above copyright notice and this permission notice shall be included in all
|
|
39
|
+
copies or substantial portions of the Software.
|
|
40
|
+
|
|
41
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
42
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
43
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
44
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
45
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
46
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
47
|
+
SOFTWARE.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
@typescript-eslint/utils
|
|
52
|
+
|
|
53
|
+
MIT License
|
|
54
|
+
|
|
55
|
+
Copyright (c) 2019 typescript-eslint and other contributors
|
|
56
|
+
|
|
57
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
58
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
59
|
+
in the Software without restriction, including without limitation the rights
|
|
60
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
61
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
62
|
+
furnished to do so, subject to the following conditions:
|
|
63
|
+
|
|
64
|
+
The above copyright notice and this permission notice shall be included in all
|
|
65
|
+
copies or substantial portions of the Software.
|
|
66
|
+
|
|
67
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
68
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
69
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
70
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
71
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
72
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
73
|
+
SOFTWARE.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
globals
|
|
78
|
+
|
|
79
|
+
MIT License
|
|
80
|
+
|
|
81
|
+
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
|
82
|
+
|
|
83
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
84
|
+
|
|
85
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
86
|
+
|
|
87
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,171 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @r_masseater/vitest-otlp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A Vitest plugin that receives OTLP during tests. A test can then assert the
|
|
4
|
+
spans, metrics and logs that the code under test really exported. It shows that
|
|
5
|
+
auto-instrumentation and `OTEL_*` settings work. It also shows that telemetry
|
|
6
|
+
actually leaves the process.
|
|
7
|
+
|
|
8
|
+
It requires [Vite+](https://viteplus.dev/) and Node.js 26 or later.
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
vp add -D @r_masseater/vitest-otlp
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { otlp } from "@r_masseater/vitest-otlp";
|
|
16
|
+
import { defineConfig } from "vite-plus";
|
|
17
|
+
|
|
18
|
+
export default defineConfig({
|
|
19
|
+
plugins: [otlp()],
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
25
|
+
import { waitForSpans } from "@r_masseater/vitest-otlp";
|
|
26
|
+
import { Effect } from "effect";
|
|
27
|
+
import { expect, test } from "vite-plus/test";
|
|
28
|
+
|
|
29
|
+
test("checkout is traced", () =>
|
|
30
|
+
Effect.gen(function* () {
|
|
31
|
+
const sdk = new NodeSDK();
|
|
32
|
+
sdk.start();
|
|
33
|
+
yield* checkout;
|
|
34
|
+
yield* Effect.promise(() => sdk.shutdown());
|
|
35
|
+
|
|
36
|
+
const spans = yield* waitForSpans({
|
|
37
|
+
where: (span) => span.name === "checkout",
|
|
38
|
+
});
|
|
39
|
+
expect(spans).toMatchObject([{ attributes: { "order.id": "o-1" } }]);
|
|
40
|
+
}).pipe(Effect.runPromise));
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## How it works
|
|
44
|
+
|
|
45
|
+
Each test file gets its own OTLP receiver on free loopback ports. It serves
|
|
46
|
+
OTLP/HTTP with protobuf or JSON, and OTLP/gRPC. Both accept gzip. Messages are
|
|
47
|
+
decoded with code that [protobuf-es](https://github.com/bufbuild/protobuf-es)
|
|
48
|
+
generated from
|
|
49
|
+
[opentelemetry-proto](https://github.com/open-telemetry/opentelemetry-proto)
|
|
50
|
+
v1.11.1.
|
|
51
|
+
|
|
52
|
+
The test learns the receiver through the standard environment variables. An SDK
|
|
53
|
+
that reads its configuration from the environment sends to it without code
|
|
54
|
+
changes. Child processes of the test inherit the variables. They send to the
|
|
55
|
+
same receiver.
|
|
56
|
+
|
|
57
|
+
An in-memory exporter skips the real export path. This plugin instead sends
|
|
58
|
+
over OTLP and reads the environment, just as production does. A Collector or
|
|
59
|
+
grafana/otel-lgtm in a container needs Docker. This plugin needs none, and the
|
|
60
|
+
test reads what arrived directly.
|
|
61
|
+
|
|
62
|
+
## API
|
|
63
|
+
|
|
64
|
+
The read API is written with Effect.
|
|
65
|
+
|
|
66
|
+
`waitForSpans`, `waitForLogs` and `waitForMetrics` wait for items that match
|
|
67
|
+
`where`. They reread every `interval` until `count` items arrive, and return all
|
|
68
|
+
matches. The defaults are 20 ms and 1. After `timeout` they fail with
|
|
69
|
+
`TelemetryNotReceived`, which carries how many arrived. The default timeout is
|
|
70
|
+
5 seconds.
|
|
71
|
+
|
|
72
|
+
`receivedSpans`, `receivedLogs` and `receivedMetrics` return everything that has
|
|
73
|
+
arrived in the test file so far. `clearTelemetry` discards what has arrived.
|
|
74
|
+
`receiverAddress` returns the `httpEndpoint` and `grpcEndpoint` of the receiver.
|
|
75
|
+
|
|
76
|
+
Attributes become plain objects such as `{ "order.id": "o-1" }`. IDs become hex
|
|
77
|
+
strings. Spans and logs carry `resource` and `scope`. They hold the resource
|
|
78
|
+
attributes and the instrumentation scope.
|
|
79
|
+
|
|
80
|
+
## Environment
|
|
81
|
+
|
|
82
|
+
`OTEL_EXPORTER_OTLP_ENDPOINT` is the HTTP URL of the receiver. When
|
|
83
|
+
`OTEL_EXPORTER_OTLP_PROTOCOL` is `grpc`, it is the gRPC URL instead.
|
|
84
|
+
`OTEL_TRACES_EXPORTER`, `OTEL_METRICS_EXPORTER` and `OTEL_LOGS_EXPORTER` are
|
|
85
|
+
`otlp`.
|
|
86
|
+
|
|
87
|
+
Export delays are shortened, but only when they are unset.
|
|
88
|
+
|
|
89
|
+
| Variable | Value |
|
|
90
|
+
| ----------------------------- | ----- |
|
|
91
|
+
| `OTEL_BSP_SCHEDULE_DELAY` | 50 |
|
|
92
|
+
| `OTEL_BLRP_SCHEDULE_DELAY` | 50 |
|
|
93
|
+
| `OTEL_METRIC_EXPORT_INTERVAL` | 100 |
|
|
94
|
+
| `OTEL_METRIC_EXPORT_TIMEOUT` | 100 |
|
|
95
|
+
|
|
96
|
+
The per-signal variables `OTEL_EXPORTER_OTLP_<SIGNAL>_ENDPOINT` are emptied.
|
|
97
|
+
Test telemetry therefore never leaks to an outside Collector.
|
|
98
|
+
|
|
99
|
+
The receiver closes when the test file ends. With `isolate: false`, files may
|
|
100
|
+
share one SDK. Then telemetry from the second file on is lost.
|
|
101
|
+
|
|
102
|
+
## Semantic conventions
|
|
103
|
+
|
|
104
|
+
`otlp({ semconv: true })` also checks every export against the OpenTelemetry
|
|
105
|
+
semantic conventions. It starts the live-check of
|
|
106
|
+
[OpenTelemetry Weaver](https://github.com/open-telemetry/weaver) once for the
|
|
107
|
+
run. Each receiver forwards what it gets to Weaver before storing it. When the
|
|
108
|
+
run ends, the plugin reads the report of Weaver. The run then fails and lists
|
|
109
|
+
every finding at or above `failOn`.
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
otlp({
|
|
113
|
+
semconv: {
|
|
114
|
+
registry: "https://github.com/open-telemetry/semantic-conventions.git@v1.45.0[model]",
|
|
115
|
+
weaver: "weaver",
|
|
116
|
+
failOn: "violation",
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The values above are the defaults. `failOn` is `violation`, `improvement` or
|
|
122
|
+
`information`. A custom attribute is a violation unless `registry` points to a
|
|
123
|
+
registry that defines it. The `weaver` binary must be on `PATH`. In GitHub
|
|
124
|
+
Actions, the
|
|
125
|
+
[setup-weaver](https://github.com/open-telemetry/weaver/tree/main/.github/actions/setup-weaver)
|
|
126
|
+
action installs it.
|
|
127
|
+
|
|
128
|
+
## Cloudflare Workers
|
|
129
|
+
|
|
130
|
+
Workers trace KV, D1, R2 and `fetch` calls without code changes, and hand the
|
|
131
|
+
spans and logs to a
|
|
132
|
+
[streaming tail worker](https://developers.cloudflare.com/workers/observability/logs/tail-workers/).
|
|
133
|
+
`tailWorker()` from `@r_masseater/vitest-otlp/workers` builds a tail worker for
|
|
134
|
+
[Miniflare](https://developers.cloudflare.com/workers/testing/miniflare/). It
|
|
135
|
+
turns each invocation into OTLP and sends it to the receiver of the test file.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import { waitForSpans } from "@r_masseater/vitest-otlp";
|
|
139
|
+
import { tailWorker } from "@r_masseater/vitest-otlp/workers";
|
|
140
|
+
import { Effect } from "effect";
|
|
141
|
+
import { Miniflare } from "miniflare";
|
|
142
|
+
|
|
143
|
+
test("caching writes to KV", () =>
|
|
144
|
+
Effect.gen(function* () {
|
|
145
|
+
const tail = yield* tailWorker();
|
|
146
|
+
const miniflare = new Miniflare({
|
|
147
|
+
workers: [
|
|
148
|
+
{
|
|
149
|
+
name: "app",
|
|
150
|
+
scriptPath: "worker.js",
|
|
151
|
+
kvNamespaces: ["CACHE"],
|
|
152
|
+
streamingTails: [tail.name],
|
|
153
|
+
},
|
|
154
|
+
tail,
|
|
155
|
+
],
|
|
156
|
+
});
|
|
157
|
+
yield* Effect.promise(() => miniflare.dispatchFetch("http://localhost/"));
|
|
158
|
+
yield* waitForSpans({ where: (span) => span.name === "kv_put" });
|
|
159
|
+
yield* Effect.promise(() => miniflare.dispose());
|
|
160
|
+
}).pipe(Effect.runPromise));
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The invocation becomes a server span named after its trigger, such as `fetch`.
|
|
164
|
+
Each traced call becomes a client span under it, and `console` calls become log
|
|
165
|
+
records in the same trace. The resource `service.name` is the script name of
|
|
166
|
+
the Worker. Miniflare reports no script name, so pass `serviceName` to
|
|
167
|
+
`tailWorker()` instead.
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-COyJBDQM.mjs","names":[],"sources":["../src/context.ts"],"sourcesContent":["type SemconvLevel = \"violation\" | \"improvement\" | \"information\";\n\ninterface SemconvOptions {\n readonly registry?: string;\n readonly weaver?: string;\n readonly failOn?: SemconvLevel;\n}\n\ninterface ResolvedSemconv {\n readonly registry: string;\n readonly weaver: string;\n readonly failOn: SemconvLevel;\n}\n\nconst SEMCONV_KEY = \"vitest-otlp:semconv\";\nconst FORWARD_KEY = \"vitest-otlp:forward\";\n\ndeclare module \"vite-plus/test\" {\n interface ProvidedContext {\n readonly [SEMCONV_KEY]?: ResolvedSemconv;\n readonly [FORWARD_KEY]?: readonly string[];\n }\n}\n\nexport { FORWARD_KEY, SEMCONV_KEY };\nexport type { ResolvedSemconv, SemconvLevel, SemconvOptions };\n"],"mappings":";AAcA,MAAM,cAAc;AACpB,MAAM,cAAc"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { t as ReceiverNotRunning } from "./store-8DpTwYxj.mjs";
|
|
2
|
+
import { Duration, Effect, Schema } from "effect";
|
|
3
|
+
import { Plugin } from "vite-plus";
|
|
4
|
+
type SemconvLevel = "violation" | "improvement" | "information";
|
|
5
|
+
interface SemconvOptions {
|
|
6
|
+
readonly registry?: string;
|
|
7
|
+
readonly weaver?: string;
|
|
8
|
+
readonly failOn?: SemconvLevel;
|
|
9
|
+
}
|
|
10
|
+
interface ResolvedSemconv {
|
|
11
|
+
readonly registry: string;
|
|
12
|
+
readonly weaver: string;
|
|
13
|
+
readonly failOn: SemconvLevel;
|
|
14
|
+
}
|
|
15
|
+
declare const SEMCONV_KEY = "vitest-otlp:semconv";
|
|
16
|
+
declare const FORWARD_KEY = "vitest-otlp:forward";
|
|
17
|
+
declare module "vite-plus/test" {
|
|
18
|
+
interface ProvidedContext {
|
|
19
|
+
readonly [SEMCONV_KEY]?: ResolvedSemconv;
|
|
20
|
+
readonly [FORWARD_KEY]?: readonly string[];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
interface ReceiverAddress {
|
|
24
|
+
readonly httpEndpoint: string;
|
|
25
|
+
readonly grpcEndpoint: string;
|
|
26
|
+
}
|
|
27
|
+
type Attributes = Readonly<Record<string, unknown>>;
|
|
28
|
+
interface InstrumentationScope {
|
|
29
|
+
readonly name: string;
|
|
30
|
+
readonly version?: string;
|
|
31
|
+
readonly attributes?: Attributes;
|
|
32
|
+
}
|
|
33
|
+
interface ReceivedSpan {
|
|
34
|
+
readonly traceId: string;
|
|
35
|
+
readonly spanId: string;
|
|
36
|
+
readonly parentSpanId?: string;
|
|
37
|
+
readonly name: string;
|
|
38
|
+
readonly kind: string;
|
|
39
|
+
readonly startTimeUnixNano: string;
|
|
40
|
+
readonly endTimeUnixNano: string;
|
|
41
|
+
readonly attributes: Attributes;
|
|
42
|
+
readonly events: readonly unknown[];
|
|
43
|
+
readonly links: readonly unknown[];
|
|
44
|
+
readonly status: {
|
|
45
|
+
readonly code?: string;
|
|
46
|
+
readonly message?: string;
|
|
47
|
+
};
|
|
48
|
+
readonly resource: Attributes;
|
|
49
|
+
readonly scope: InstrumentationScope;
|
|
50
|
+
}
|
|
51
|
+
interface ReceivedLogRecord {
|
|
52
|
+
readonly timeUnixNano?: string;
|
|
53
|
+
readonly observedTimeUnixNano?: string;
|
|
54
|
+
readonly severityNumber?: number | string;
|
|
55
|
+
readonly severityText?: string;
|
|
56
|
+
readonly body?: unknown;
|
|
57
|
+
readonly attributes: Attributes;
|
|
58
|
+
readonly traceId?: string;
|
|
59
|
+
readonly spanId?: string;
|
|
60
|
+
readonly resource: Attributes;
|
|
61
|
+
readonly scope: InstrumentationScope;
|
|
62
|
+
}
|
|
63
|
+
interface ReceivedMetric {
|
|
64
|
+
readonly name: string;
|
|
65
|
+
readonly description?: string;
|
|
66
|
+
readonly unit?: string;
|
|
67
|
+
readonly type: string;
|
|
68
|
+
readonly dataPoints: readonly unknown[];
|
|
69
|
+
readonly resource: Attributes;
|
|
70
|
+
readonly scope: InstrumentationScope;
|
|
71
|
+
}
|
|
72
|
+
declare const TelemetryNotReceived_base: Schema.Class<TelemetryNotReceived, Schema.TaggedStruct<"TelemetryNotReceived", {
|
|
73
|
+
readonly message: Schema.String;
|
|
74
|
+
}>, import("effect/Cause").YieldableError & {
|
|
75
|
+
readonly brand: unique symbol;
|
|
76
|
+
}>;
|
|
77
|
+
declare class TelemetryNotReceived extends TelemetryNotReceived_base {}
|
|
78
|
+
type ReadError = ReceiverNotRunning | Schema.SchemaError;
|
|
79
|
+
interface WaitOptions<Item> {
|
|
80
|
+
readonly where?: (item: Item) => boolean;
|
|
81
|
+
readonly count?: number;
|
|
82
|
+
readonly timeout?: Duration.Duration;
|
|
83
|
+
readonly interval?: Duration.Duration;
|
|
84
|
+
}
|
|
85
|
+
interface OtlpOptions {
|
|
86
|
+
readonly semconv?: SemconvOptions | true;
|
|
87
|
+
}
|
|
88
|
+
declare const otlp: (options?: OtlpOptions) => Plugin;
|
|
89
|
+
declare const receiverAddress: Effect.Effect<ReceiverAddress, ReceiverNotRunning>;
|
|
90
|
+
declare const receivedSpans: Effect.Effect<readonly ReceivedSpan[], ReadError, never>;
|
|
91
|
+
declare const receivedLogs: Effect.Effect<readonly ReceivedLogRecord[], ReadError, never>;
|
|
92
|
+
declare const receivedMetrics: Effect.Effect<readonly ReceivedMetric[], ReadError, never>;
|
|
93
|
+
declare const waitForSpans: ({ where, count, timeout, interval }?: WaitOptions<ReceivedSpan>) => Effect.Effect<readonly ReceivedSpan[], TelemetryNotReceived | ReadError, never>;
|
|
94
|
+
declare const waitForLogs: ({ where, count, timeout, interval }?: WaitOptions<ReceivedLogRecord>) => Effect.Effect<readonly ReceivedLogRecord[], TelemetryNotReceived | ReadError, never>;
|
|
95
|
+
declare const waitForMetrics: ({ where, count, timeout, interval }?: WaitOptions<ReceivedMetric>) => Effect.Effect<readonly ReceivedMetric[], TelemetryNotReceived | ReadError, never>;
|
|
96
|
+
declare const clearTelemetry: Effect.Effect<void, ReceiverNotRunning>;
|
|
97
|
+
export { type Attributes, type InstrumentationScope, type OtlpOptions, type ReadError, type ReceivedLogRecord, type ReceivedMetric, type ReceivedSpan, type ReceiverAddress, ReceiverNotRunning, type SemconvLevel, type SemconvOptions, TelemetryNotReceived, type WaitOptions, clearTelemetry, otlp, receivedLogs, receivedMetrics, receivedSpans, receiverAddress, waitForLogs, waitForMetrics, waitForSpans };
|
|
98
|
+
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { n as SEMCONV_KEY } from "./context-COyJBDQM.mjs";
|
|
2
|
+
import { n as currentReceiver, t as ReceiverNotRunning } from "./store-DSDf4Wia.mjs";
|
|
3
|
+
import { Array, Duration, Effect, Option, Predicate, Schedule, Schema, pipe } from "effect";
|
|
4
|
+
// src/otlp-schema.ts
|
|
5
|
+
const orEmpty = (schema, empty) => schema.pipe(Schema.withDecodingDefault(Effect.succeed(empty)));
|
|
6
|
+
const AttributesSchema = Schema.Record(Schema.String, Schema.Unknown);
|
|
7
|
+
const OptionalString = Schema.optionalKey(Schema.String);
|
|
8
|
+
const ResourceSchema = orEmpty(Schema.Struct({ attributes: orEmpty(AttributesSchema, {}) }), {});
|
|
9
|
+
const ScopeSchema = orEmpty(Schema.Struct({
|
|
10
|
+
name: orEmpty(Schema.String, ""),
|
|
11
|
+
version: OptionalString,
|
|
12
|
+
attributes: Schema.optionalKey(AttributesSchema)
|
|
13
|
+
}), {});
|
|
14
|
+
const UnknownArray = orEmpty(Schema.Array(Schema.Unknown), []);
|
|
15
|
+
const StatusSchema = Schema.Struct({
|
|
16
|
+
code: OptionalString,
|
|
17
|
+
message: OptionalString
|
|
18
|
+
});
|
|
19
|
+
const SpanSchema = Schema.Struct({
|
|
20
|
+
traceId: Schema.String,
|
|
21
|
+
spanId: Schema.String,
|
|
22
|
+
parentSpanId: OptionalString,
|
|
23
|
+
name: Schema.String,
|
|
24
|
+
kind: orEmpty(Schema.String, "SPAN_KIND_UNSPECIFIED"),
|
|
25
|
+
startTimeUnixNano: orEmpty(Schema.String, "0"),
|
|
26
|
+
endTimeUnixNano: orEmpty(Schema.String, "0"),
|
|
27
|
+
attributes: orEmpty(AttributesSchema, {}),
|
|
28
|
+
events: UnknownArray,
|
|
29
|
+
links: UnknownArray,
|
|
30
|
+
status: orEmpty(StatusSchema, {})
|
|
31
|
+
});
|
|
32
|
+
const LogRecordSchema = Schema.Struct({
|
|
33
|
+
timeUnixNano: OptionalString,
|
|
34
|
+
observedTimeUnixNano: OptionalString,
|
|
35
|
+
severityNumber: Schema.optionalKey(Schema.Union([Schema.Finite, Schema.String])),
|
|
36
|
+
severityText: OptionalString,
|
|
37
|
+
body: Schema.optionalKey(Schema.Unknown),
|
|
38
|
+
attributes: orEmpty(AttributesSchema, {}),
|
|
39
|
+
traceId: OptionalString,
|
|
40
|
+
spanId: OptionalString
|
|
41
|
+
});
|
|
42
|
+
const DataSchema = Schema.optionalKey(Schema.Struct({ dataPoints: UnknownArray }));
|
|
43
|
+
const MetricSchema = Schema.Struct({
|
|
44
|
+
name: Schema.String,
|
|
45
|
+
description: OptionalString,
|
|
46
|
+
unit: OptionalString,
|
|
47
|
+
sum: DataSchema,
|
|
48
|
+
gauge: DataSchema,
|
|
49
|
+
histogram: DataSchema,
|
|
50
|
+
exponentialHistogram: DataSchema,
|
|
51
|
+
summary: DataSchema
|
|
52
|
+
});
|
|
53
|
+
const ScopeSpansSchema = Schema.Struct({
|
|
54
|
+
scope: ScopeSchema,
|
|
55
|
+
spans: orEmpty(Schema.Array(SpanSchema), [])
|
|
56
|
+
});
|
|
57
|
+
const ResourceSpansSchema = Schema.Struct({
|
|
58
|
+
resource: ResourceSchema,
|
|
59
|
+
scopeSpans: orEmpty(Schema.Array(ScopeSpansSchema), [])
|
|
60
|
+
});
|
|
61
|
+
const TraceRequestSchema = Schema.Struct({ resourceSpans: orEmpty(Schema.Array(ResourceSpansSchema), []) });
|
|
62
|
+
const ScopeLogsSchema = Schema.Struct({
|
|
63
|
+
scope: ScopeSchema,
|
|
64
|
+
logRecords: orEmpty(Schema.Array(LogRecordSchema), [])
|
|
65
|
+
});
|
|
66
|
+
const ResourceLogsSchema = Schema.Struct({
|
|
67
|
+
resource: ResourceSchema,
|
|
68
|
+
scopeLogs: orEmpty(Schema.Array(ScopeLogsSchema), [])
|
|
69
|
+
});
|
|
70
|
+
const LogsRequestSchema = Schema.Struct({ resourceLogs: orEmpty(Schema.Array(ResourceLogsSchema), []) });
|
|
71
|
+
const ScopeMetricsSchema = Schema.Struct({
|
|
72
|
+
scope: ScopeSchema,
|
|
73
|
+
metrics: orEmpty(Schema.Array(MetricSchema), [])
|
|
74
|
+
});
|
|
75
|
+
const ResourceMetricsSchema = Schema.Struct({
|
|
76
|
+
resource: ResourceSchema,
|
|
77
|
+
scopeMetrics: orEmpty(Schema.Array(ScopeMetricsSchema), [])
|
|
78
|
+
});
|
|
79
|
+
const MetricsRequestSchema = Schema.Struct({ resourceMetrics: orEmpty(Schema.Array(ResourceMetricsSchema), []) });
|
|
80
|
+
const METRIC_TYPES = [
|
|
81
|
+
"sum",
|
|
82
|
+
"gauge",
|
|
83
|
+
"histogram",
|
|
84
|
+
"exponentialHistogram",
|
|
85
|
+
"summary"
|
|
86
|
+
];
|
|
87
|
+
const toMetric = (context) => ({ sum, gauge, histogram, exponentialHistogram, summary, ...metric }) => {
|
|
88
|
+
const data = {
|
|
89
|
+
sum,
|
|
90
|
+
gauge,
|
|
91
|
+
histogram,
|
|
92
|
+
exponentialHistogram,
|
|
93
|
+
summary
|
|
94
|
+
};
|
|
95
|
+
const found = pipe(METRIC_TYPES, Array.findFirst((type) => pipe(Option.fromNullishOr(data[type]), Option.map(({ dataPoints }) => ({
|
|
96
|
+
type,
|
|
97
|
+
dataPoints
|
|
98
|
+
})))));
|
|
99
|
+
return {
|
|
100
|
+
...metric,
|
|
101
|
+
...Option.getOrElse(found, () => ({
|
|
102
|
+
type: "unknown",
|
|
103
|
+
dataPoints: []
|
|
104
|
+
})),
|
|
105
|
+
...context
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
const spansOf = ({ resource, scopeSpans }) => pipe(scopeSpans, Array.flatMap(({ scope, spans }) => pipe(spans, Array.map((span) => ({
|
|
109
|
+
...span,
|
|
110
|
+
resource: resource.attributes,
|
|
111
|
+
scope
|
|
112
|
+
})))));
|
|
113
|
+
const logsOf = ({ resource, scopeLogs }) => pipe(scopeLogs, Array.flatMap(({ scope, logRecords }) => pipe(logRecords, Array.map((logRecord) => ({
|
|
114
|
+
...logRecord,
|
|
115
|
+
resource: resource.attributes,
|
|
116
|
+
scope
|
|
117
|
+
})))));
|
|
118
|
+
const metricsOf = ({ resource, scopeMetrics }) => pipe(scopeMetrics, Array.flatMap(({ scope, metrics }) => pipe(metrics, Array.map(toMetric({
|
|
119
|
+
resource: resource.attributes,
|
|
120
|
+
scope
|
|
121
|
+
})))));
|
|
122
|
+
const decodeSpans = (normalized) => Schema.decodeUnknownEffect(TraceRequestSchema)(normalized).pipe(Effect.map(({ resourceSpans }) => pipe(resourceSpans, Array.flatMap(spansOf))));
|
|
123
|
+
const decodeLogs = (normalized) => Schema.decodeUnknownEffect(LogsRequestSchema)(normalized).pipe(Effect.map(({ resourceLogs }) => pipe(resourceLogs, Array.flatMap(logsOf))));
|
|
124
|
+
const decodeMetrics = (normalized) => Schema.decodeUnknownEffect(MetricsRequestSchema)(normalized).pipe(Effect.map(({ resourceMetrics }) => pipe(resourceMetrics, Array.flatMap(metricsOf))));
|
|
125
|
+
// src/index.ts
|
|
126
|
+
const taggedError = Schema.TaggedError;
|
|
127
|
+
var TelemetryNotReceived = class extends taggedError()("TelemetryNotReceived", { message: Schema.String }) {};
|
|
128
|
+
const DEFAULT_TIMEOUT_SECONDS = 5;
|
|
129
|
+
const DEFAULT_INTERVAL_MILLIS = 20;
|
|
130
|
+
const DEFAULT_TIMEOUT = Duration.seconds(DEFAULT_TIMEOUT_SECONDS);
|
|
131
|
+
const DEFAULT_INTERVAL = Duration.millis(DEFAULT_INTERVAL_MILLIS);
|
|
132
|
+
const ONE = 1;
|
|
133
|
+
const DEFAULT_SEMCONV = {
|
|
134
|
+
registry: "https://github.com/open-telemetry/semantic-conventions.git@v1.45.0[model]",
|
|
135
|
+
weaver: "weaver",
|
|
136
|
+
failOn: "violation"
|
|
137
|
+
};
|
|
138
|
+
const semconvConfig = (semconv) => ({
|
|
139
|
+
...DEFAULT_SEMCONV,
|
|
140
|
+
...Option.getOrElse(Option.liftPredicate(semconv, (options) => options !== true), () => ({}))
|
|
141
|
+
});
|
|
142
|
+
const otlp = (options = {}) => ({
|
|
143
|
+
name: "@r_masseater/vitest-otlp",
|
|
144
|
+
config: () => ({ test: {
|
|
145
|
+
setupFiles: ["@r_masseater/vitest-otlp/setup"],
|
|
146
|
+
...Option.match(Option.fromNullishOr(options.semconv), {
|
|
147
|
+
onNone: () => ({}),
|
|
148
|
+
onSome: (semconv) => ({
|
|
149
|
+
globalSetup: ["@r_masseater/vitest-otlp/semconv"],
|
|
150
|
+
provide: { [SEMCONV_KEY]: semconvConfig(semconv) }
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
} })
|
|
154
|
+
});
|
|
155
|
+
const signal = (label, key, decode) => ({
|
|
156
|
+
label,
|
|
157
|
+
received: currentReceiver.pipe(Effect.flatMap((receiver) => receiver.requests), Effect.flatMap((requests) => pipe(requests[key], Array.map(decode), Effect.all)), Effect.map(Array.flatten))
|
|
158
|
+
});
|
|
159
|
+
const spans = signal("spans", "traces", decodeSpans);
|
|
160
|
+
const logs = signal("log records", "logs", decodeLogs);
|
|
161
|
+
const metrics = signal("metrics", "metrics", decodeMetrics);
|
|
162
|
+
const everything = () => true;
|
|
163
|
+
const isNotReceived = Predicate.isTagged("TelemetryNotReceived");
|
|
164
|
+
const waitFor = ({ label, received }) => ({ where = everything, count = ONE, timeout = DEFAULT_TIMEOUT, interval = DEFAULT_INTERVAL } = {}) => {
|
|
165
|
+
const check = (all) => {
|
|
166
|
+
const matched = pipe(all, Array.filter(where));
|
|
167
|
+
const notYet = TelemetryNotReceived.make({ message: `Expected ${count} ${label} within ${Duration.format(timeout)}, received ${matched.length} matching of ${all.length}.` });
|
|
168
|
+
return Effect.succeed(matched).pipe(Effect.filterOrFail(() => matched.length >= count, () => notYet));
|
|
169
|
+
};
|
|
170
|
+
const schedule = pipe(Schedule.spaced(interval), Schedule.upTo({ duration: timeout }));
|
|
171
|
+
return received.pipe(Effect.flatMap(check), Effect.retry({
|
|
172
|
+
while: isNotReceived,
|
|
173
|
+
schedule
|
|
174
|
+
}));
|
|
175
|
+
};
|
|
176
|
+
const receiverAddress = currentReceiver.pipe(Effect.map((receiver) => receiver.address));
|
|
177
|
+
const receivedSpans = spans.received;
|
|
178
|
+
const receivedLogs = logs.received;
|
|
179
|
+
const receivedMetrics = metrics.received;
|
|
180
|
+
const waitForSpans = waitFor(spans);
|
|
181
|
+
const waitForLogs = waitFor(logs);
|
|
182
|
+
const waitForMetrics = waitFor(metrics);
|
|
183
|
+
const clearTelemetry = currentReceiver.pipe(Effect.flatMap((receiver) => receiver.clear));
|
|
184
|
+
export { ReceiverNotRunning, TelemetryNotReceived, clearTelemetry, otlp, receivedLogs, receivedMetrics, receivedSpans, receiverAddress, waitForLogs, waitForMetrics, waitForSpans };
|
|
185
|
+
|
|
186
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/otlp-schema.ts","../src/index.ts"],"sourcesContent":["import { Array, Effect, Option, Schema, pipe } from \"effect\";\n\nimport type {\n Attributes,\n InstrumentationScope,\n ReceivedLogRecord,\n ReceivedMetric,\n ReceivedSpan,\n} from \"./telemetry.ts\";\n\nconst orEmpty = <Self extends Schema.Top>(\n schema: Self,\n empty: Self[\"Encoded\"],\n): Schema.withDecodingDefault<Self> =>\n schema.pipe(Schema.withDecodingDefault(Effect.succeed(empty)));\n\nconst AttributesSchema = Schema.Record(Schema.String, Schema.Unknown);\n\nconst OptionalString = Schema.optionalKey(Schema.String);\n\nconst ResourceSchema = orEmpty(Schema.Struct({ attributes: orEmpty(AttributesSchema, {}) }), {});\n\nconst ScopeSchema = orEmpty(\n Schema.Struct({\n name: orEmpty(Schema.String, \"\"),\n version: OptionalString,\n attributes: Schema.optionalKey(AttributesSchema),\n }),\n {},\n);\n\nconst UnknownArray = orEmpty(Schema.Array(Schema.Unknown), []);\n\nconst StatusSchema = Schema.Struct({ code: OptionalString, message: OptionalString });\n\nconst SpanSchema = Schema.Struct({\n traceId: Schema.String,\n spanId: Schema.String,\n parentSpanId: OptionalString,\n name: Schema.String,\n kind: orEmpty(Schema.String, \"SPAN_KIND_UNSPECIFIED\"),\n startTimeUnixNano: orEmpty(Schema.String, \"0\"),\n endTimeUnixNano: orEmpty(Schema.String, \"0\"),\n attributes: orEmpty(AttributesSchema, {}),\n events: UnknownArray,\n links: UnknownArray,\n status: orEmpty(StatusSchema, {}),\n});\n\nconst LogRecordSchema = Schema.Struct({\n timeUnixNano: OptionalString,\n observedTimeUnixNano: OptionalString,\n severityNumber: Schema.optionalKey(Schema.Union([Schema.Finite, Schema.String])),\n severityText: OptionalString,\n body: Schema.optionalKey(Schema.Unknown),\n attributes: orEmpty(AttributesSchema, {}),\n traceId: OptionalString,\n spanId: OptionalString,\n});\n\nconst DataSchema = Schema.optionalKey(Schema.Struct({ dataPoints: UnknownArray }));\n\nconst MetricSchema = Schema.Struct({\n name: Schema.String,\n description: OptionalString,\n unit: OptionalString,\n sum: DataSchema,\n gauge: DataSchema,\n histogram: DataSchema,\n exponentialHistogram: DataSchema,\n summary: DataSchema,\n});\n\nconst ScopeSpansSchema = Schema.Struct({\n scope: ScopeSchema,\n spans: orEmpty(Schema.Array(SpanSchema), []),\n});\nconst ResourceSpansSchema = Schema.Struct({\n resource: ResourceSchema,\n scopeSpans: orEmpty(Schema.Array(ScopeSpansSchema), []),\n});\nconst TraceRequestSchema = Schema.Struct({\n resourceSpans: orEmpty(Schema.Array(ResourceSpansSchema), []),\n});\n\nconst ScopeLogsSchema = Schema.Struct({\n scope: ScopeSchema,\n logRecords: orEmpty(Schema.Array(LogRecordSchema), []),\n});\nconst ResourceLogsSchema = Schema.Struct({\n resource: ResourceSchema,\n scopeLogs: orEmpty(Schema.Array(ScopeLogsSchema), []),\n});\nconst LogsRequestSchema = Schema.Struct({\n resourceLogs: orEmpty(Schema.Array(ResourceLogsSchema), []),\n});\n\nconst ScopeMetricsSchema = Schema.Struct({\n scope: ScopeSchema,\n metrics: orEmpty(Schema.Array(MetricSchema), []),\n});\nconst ResourceMetricsSchema = Schema.Struct({\n resource: ResourceSchema,\n scopeMetrics: orEmpty(Schema.Array(ScopeMetricsSchema), []),\n});\nconst MetricsRequestSchema = Schema.Struct({\n resourceMetrics: orEmpty(Schema.Array(ResourceMetricsSchema), []),\n});\n\ninterface Context {\n readonly resource: Attributes;\n readonly scope: InstrumentationScope;\n}\n\nconst METRIC_TYPES = [\"sum\", \"gauge\", \"histogram\", \"exponentialHistogram\", \"summary\"] as const;\n\nconst toMetric =\n (context: Context) =>\n ({\n sum,\n gauge,\n histogram,\n exponentialHistogram,\n summary,\n ...metric\n }: typeof MetricSchema.Type): ReceivedMetric => {\n const data = { sum, gauge, histogram, exponentialHistogram, summary };\n const found = pipe(\n METRIC_TYPES,\n Array.findFirst((type) =>\n pipe(\n Option.fromNullishOr(data[type]),\n Option.map(({ dataPoints }) => ({ type, dataPoints })),\n ),\n ),\n );\n return {\n ...metric,\n ...Option.getOrElse(found, () => ({ type: \"unknown\", dataPoints: [] })),\n ...context,\n };\n };\n\nconst spansOf = ({\n resource,\n scopeSpans,\n}: typeof ResourceSpansSchema.Type): readonly ReceivedSpan[] =>\n pipe(\n scopeSpans,\n Array.flatMap(({ scope, spans }) =>\n pipe(\n spans,\n Array.map((span) => ({ ...span, resource: resource.attributes, scope })),\n ),\n ),\n );\n\nconst logsOf = ({\n resource,\n scopeLogs,\n}: typeof ResourceLogsSchema.Type): readonly ReceivedLogRecord[] =>\n pipe(\n scopeLogs,\n Array.flatMap(({ scope, logRecords }) =>\n pipe(\n logRecords,\n Array.map((logRecord) => ({ ...logRecord, resource: resource.attributes, scope })),\n ),\n ),\n );\n\nconst metricsOf = ({\n resource,\n scopeMetrics,\n}: typeof ResourceMetricsSchema.Type): readonly ReceivedMetric[] =>\n pipe(\n scopeMetrics,\n Array.flatMap(({ scope, metrics }) =>\n pipe(metrics, Array.map(toMetric({ resource: resource.attributes, scope }))),\n ),\n );\n\nconst decodeSpans = (\n normalized: unknown,\n): Effect.Effect<readonly ReceivedSpan[], Schema.SchemaError> =>\n Schema.decodeUnknownEffect(TraceRequestSchema)(normalized).pipe(\n Effect.map(({ resourceSpans }) => pipe(resourceSpans, Array.flatMap(spansOf))),\n );\n\nconst decodeLogs = (\n normalized: unknown,\n): Effect.Effect<readonly ReceivedLogRecord[], Schema.SchemaError> =>\n Schema.decodeUnknownEffect(LogsRequestSchema)(normalized).pipe(\n Effect.map(({ resourceLogs }) => pipe(resourceLogs, Array.flatMap(logsOf))),\n );\n\nconst decodeMetrics = (\n normalized: unknown,\n): Effect.Effect<readonly ReceivedMetric[], Schema.SchemaError> =>\n Schema.decodeUnknownEffect(MetricsRequestSchema)(normalized).pipe(\n Effect.map(({ resourceMetrics }) => pipe(resourceMetrics, Array.flatMap(metricsOf))),\n );\n\nexport { decodeLogs, decodeMetrics, decodeSpans };\n","import { Array, Duration, Effect, Option, Predicate, Schedule, Schema, pipe } from \"effect\";\nimport type { Plugin } from \"vite-plus\";\n\nimport { SEMCONV_KEY } from \"./context.ts\";\nimport type { ResolvedSemconv, SemconvOptions } from \"./context.ts\";\nimport { decodeLogs, decodeMetrics, decodeSpans } from \"./otlp-schema.ts\";\nimport type { SignalKey } from \"./requests.ts\";\nimport type { ReceiverNotRunning } from \"./store.ts\";\nimport { currentReceiver } from \"./store.ts\";\nimport type {\n ReceivedLogRecord,\n ReceivedMetric,\n ReceivedSpan,\n ReceiverAddress,\n} from \"./telemetry.ts\";\n\nconst taggedError = Schema.TaggedError;\n\nclass TelemetryNotReceived extends taggedError<\n TelemetryNotReceived,\n { readonly brand: unique symbol }\n>()(\"TelemetryNotReceived\", { message: Schema.String }) {}\n\ntype ReadError = ReceiverNotRunning | Schema.SchemaError;\n\ninterface WaitOptions<Item> {\n readonly where?: (item: Item) => boolean;\n readonly count?: number;\n readonly timeout?: Duration.Duration;\n readonly interval?: Duration.Duration;\n}\n\ninterface Signal<Item> {\n readonly label: string;\n readonly received: Effect.Effect<readonly Item[], ReadError>;\n}\n\nconst DEFAULT_TIMEOUT_SECONDS = 5;\nconst DEFAULT_INTERVAL_MILLIS = 20;\nconst DEFAULT_TIMEOUT = Duration.seconds(DEFAULT_TIMEOUT_SECONDS);\nconst DEFAULT_INTERVAL = Duration.millis(DEFAULT_INTERVAL_MILLIS);\nconst ONE = 1;\n\nconst DEFAULT_SEMCONV: ResolvedSemconv = {\n registry: \"https://github.com/open-telemetry/semantic-conventions.git@v1.45.0[model]\",\n weaver: \"weaver\",\n failOn: \"violation\",\n};\n\ninterface OtlpOptions {\n readonly semconv?: SemconvOptions | true;\n}\n\nconst semconvConfig = (semconv: SemconvOptions | true): ResolvedSemconv => ({\n ...DEFAULT_SEMCONV,\n ...Option.getOrElse(\n Option.liftPredicate(semconv, (options) => options !== true),\n () => ({}),\n ),\n});\n\nconst otlp = (options: OtlpOptions = {}): Plugin => ({\n name: \"@r_masseater/vitest-otlp\",\n config: () => ({\n test: {\n setupFiles: [\"@r_masseater/vitest-otlp/setup\"],\n ...Option.match(Option.fromNullishOr(options.semconv), {\n onNone: () => ({}),\n onSome: (semconv) => ({\n globalSetup: [\"@r_masseater/vitest-otlp/semconv\"],\n provide: { [SEMCONV_KEY]: semconvConfig(semconv) },\n }),\n }),\n },\n }),\n});\n\nconst signal = <Item>(\n label: string,\n key: SignalKey,\n decode: (normalized: unknown) => Effect.Effect<readonly Item[], Schema.SchemaError>,\n): Signal<Item> => ({\n label,\n received: currentReceiver.pipe(\n Effect.flatMap((receiver) => receiver.requests),\n Effect.flatMap((requests) => pipe(requests[key], Array.map(decode), Effect.all)),\n Effect.map(Array.flatten),\n ),\n});\n\nconst spans = signal<ReceivedSpan>(\"spans\", \"traces\", decodeSpans);\nconst logs = signal<ReceivedLogRecord>(\"log records\", \"logs\", decodeLogs);\nconst metrics = signal<ReceivedMetric>(\"metrics\", \"metrics\", decodeMetrics);\n\nconst everything = (): boolean => true;\n\nconst isNotReceived = Predicate.isTagged(\"TelemetryNotReceived\");\n\nconst waitFor =\n <Item>({ label, received }: Signal<Item>) =>\n ({\n where = everything,\n count = ONE,\n timeout = DEFAULT_TIMEOUT,\n interval = DEFAULT_INTERVAL,\n }: WaitOptions<Item> = {}): Effect.Effect<readonly Item[], TelemetryNotReceived | ReadError> => {\n const check = (all: readonly Item[]): Effect.Effect<readonly Item[], TelemetryNotReceived> => {\n const matched = pipe(all, Array.filter(where));\n const notYet = TelemetryNotReceived.make({\n message: `Expected ${count} ${label} within ${Duration.format(timeout)}, received ${matched.length} matching of ${all.length}.`,\n });\n return Effect.succeed(matched).pipe(\n Effect.filterOrFail(\n () => matched.length >= count,\n () => notYet,\n ),\n );\n };\n const schedule = pipe(Schedule.spaced(interval), Schedule.upTo({ duration: timeout }));\n return received.pipe(Effect.flatMap(check), Effect.retry({ while: isNotReceived, schedule }));\n };\n\nconst receiverAddress: Effect.Effect<ReceiverAddress, ReceiverNotRunning> = currentReceiver.pipe(\n Effect.map((receiver) => receiver.address),\n);\n\nconst receivedSpans = spans.received;\nconst receivedLogs = logs.received;\nconst receivedMetrics = metrics.received;\n\nconst waitForSpans = waitFor(spans);\nconst waitForLogs = waitFor(logs);\nconst waitForMetrics = waitFor(metrics);\n\nconst clearTelemetry: Effect.Effect<void, ReceiverNotRunning> = currentReceiver.pipe(\n Effect.flatMap((receiver) => receiver.clear),\n);\n\nexport {\n TelemetryNotReceived,\n clearTelemetry,\n otlp,\n receiverAddress,\n receivedLogs,\n receivedMetrics,\n receivedSpans,\n waitForLogs,\n waitForMetrics,\n waitForSpans,\n};\nexport { ReceiverNotRunning } from \"./store.ts\";\nexport type {\n Attributes,\n InstrumentationScope,\n ReceivedLogRecord,\n ReceivedMetric,\n ReceivedSpan,\n ReceiverAddress,\n} from \"./telemetry.ts\";\nexport type { OtlpOptions, ReadError, WaitOptions };\nexport type { SemconvLevel, SemconvOptions } from \"./context.ts\";\n"],"mappings":"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"}
|