@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 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
- # Temporary Holding Version
1
+ # @r_masseater/vitest-otlp
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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,6 @@
1
+ // src/context.ts
2
+ const SEMCONV_KEY = "vitest-otlp:semconv";
3
+ const FORWARD_KEY = "vitest-otlp:forward";
4
+ export { SEMCONV_KEY as n, FORWARD_KEY as t };
5
+
6
+ //# sourceMappingURL=context-COyJBDQM.mjs.map
@@ -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"}
@@ -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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"}
@@ -0,0 +1,4 @@
1
+ import { TestProject } from "vite-plus/test/node";
2
+ declare const setup: (project: TestProject) => Promise<() => Promise<void>>;
3
+ export { setup };
4
+ //# sourceMappingURL=semconv.d.mts.map