@krak-stack/registry 0.1.31 → 0.1.32

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/README.md CHANGED
@@ -20,6 +20,8 @@ import {
20
20
  } from "@krak-stack/registry/httpapi-toolkit";
21
21
  import { ApiClient } from "@krak-stack/registry/httpapi/client";
22
22
  import { HttpApiSpec } from "@krak-stack/registry/httpapi/helpers";
23
+ import { HttpApiOtlp } from "@krak-stack/registry/opentelemetry/api";
24
+ import { BrowserOtlp } from "@krak-stack/registry/opentelemetry/browser";
23
25
  import { createDocsSource, makeDocs } from "@krak-stack/registry/docs";
24
26
  import { loadMdxDocsDirectory } from "@krak-stack/registry/docs/server";
25
27
  import {
@@ -42,6 +44,25 @@ import {
42
44
  import { NotificationService } from "@krak-stack/registry/service-notification";
43
45
  ```
44
46
 
47
+ Inject API telemetry and browser proxy routes into the application HTTP layer:
48
+
49
+ ```ts
50
+ const appLayer = appRoutes.pipe(Layer.provideMerge(HttpApiOtlp.layer));
51
+ ```
52
+
53
+ Install browser telemetry globally on the Atom runtime used by the API client:
54
+
55
+ ```ts
56
+ import { Atom } from "effect/unstable/reactivity";
57
+ import { BrowserOtlp } from "@krak-stack/registry/opentelemetry/browser";
58
+
59
+ const apiRuntime = Atom.context();
60
+
61
+ if (!import.meta.env.SSR) {
62
+ apiRuntime.addGlobalLayer(BrowserOtlp.layer({ serviceName: "my-app-web" }));
63
+ }
64
+ ```
65
+
45
66
  `@krak-stack/registry/service-notification` is the source-compatible direct
46
67
  dispatcher. Use the separate `@krak-stack/notifications` package for a durable
47
68
  inbox, package-owned migrations, persisted delivery jobs, retries, workers, and
@@ -0,0 +1,5 @@
1
+ import { Config, Layer } from "effect";
2
+ import { HttpRouter } from "effect/unstable/http";
3
+ export declare class HttpApiOtlp {
4
+ static readonly layer: Layer.Layer<import("effect/FileSystem").FileSystem | import("effect/unstable/http/Etag").Generator | import("effect/unstable/http/HttpPlatform").HttpPlatform | import("effect/Path").Path, Config.ConfigError, HttpRouter.HttpRouter>;
5
+ }
@@ -0,0 +1,44 @@
1
+ // ../../src/services/opentelemetry/api.ts
2
+ import { Config, Effect, Layer } from "effect";
3
+ import {
4
+ FetchHttpClient,
5
+ HttpClient,
6
+ HttpClientRequest,
7
+ HttpMiddleware,
8
+ HttpRouter,
9
+ HttpServer,
10
+ HttpServerRequest,
11
+ HttpServerResponse
12
+ } from "effect/unstable/http";
13
+ import { Otlp, OtlpSerialization } from "effect/unstable/observability";
14
+ var OpenTelemetryLive = Otlp.layerFromConfig().pipe(Layer.provide(OtlpSerialization.layerJson), Layer.provide(FetchHttpClient.layer));
15
+ var otlpProxyPaths = [
16
+ "/api/otel/v1/traces",
17
+ "/api/otel/v1/metrics",
18
+ "/api/otel/v1/logs"
19
+ ];
20
+ var proxyOtlp = (baseUrl, signal) => Effect.gen(function* () {
21
+ const incoming = yield* HttpServerRequest.HttpServerRequest;
22
+ const client = yield* HttpClient.HttpClient;
23
+ const contentType = incoming.headers["content-type"] ?? "application/json";
24
+ const body = new Uint8Array(yield* incoming.arrayBuffer);
25
+ const request = HttpClientRequest.get(baseUrl).pipe(HttpClientRequest.appendUrl(`/v1/${signal}`), HttpClientRequest.setMethod("POST"), HttpClientRequest.bodyUint8Array(body, contentType));
26
+ const upstream = yield* client.execute(request);
27
+ const responseBody = new Uint8Array(yield* upstream.arrayBuffer);
28
+ return HttpServerResponse.uint8Array(responseBody, {
29
+ status: upstream.status,
30
+ contentType: upstream.headers["content-type"],
31
+ headers: {
32
+ "retry-after": upstream.headers["retry-after"]
33
+ }
34
+ });
35
+ }).pipe(Effect.withTracerEnabled(false), HttpMiddleware.withLoggerDisabled, Effect.catch((error) => Effect.logError("Failed to proxy browser telemetry", error).pipe(Effect.as(HttpServerResponse.empty({ status: 502 })))), Effect.provide(FetchHttpClient.layer));
36
+ var otlpProxyLayer = Layer.unwrap(Effect.map(Config.url("OTEL_COLLECTOR_PROXY_URL").pipe(Config.withDefault(new URL("http://localhost:4318"))), (baseUrl) => Layer.mergeAll(HttpRouter.add("POST", otlpProxyPaths[0], proxyOtlp(baseUrl, "traces")), HttpRouter.add("POST", otlpProxyPaths[1], proxyOtlp(baseUrl, "metrics")), HttpRouter.add("POST", otlpProxyPaths[2], proxyOtlp(baseUrl, "logs")))));
37
+ var serverServices = HttpServer.layerServices.pipe(Layer.provideMerge(HttpMiddleware.layerTracerDisabledForUrls(otlpProxyPaths)));
38
+
39
+ class HttpApiOtlp {
40
+ static layer = Layer.mergeAll(OpenTelemetryLive, otlpProxyLayer, serverServices);
41
+ }
42
+ export {
43
+ HttpApiOtlp
44
+ };
@@ -0,0 +1,10 @@
1
+ import { Layer } from "effect";
2
+ import { Otlp } from "effect/unstable/observability";
3
+ export declare class BrowserOtlp {
4
+ static readonly layer: ({ attributes, baseUrl, serviceName, serviceVersion, ...options }: Omit<Parameters<typeof Otlp.layerJson>[0], "baseUrl" | "resource"> & {
5
+ readonly serviceName: string;
6
+ readonly serviceVersion?: string;
7
+ readonly attributes?: NonNullable<Parameters<typeof Otlp.layerJson>[0]["resource"]>["attributes"];
8
+ readonly baseUrl?: string;
9
+ }) => Layer.Layer<never, never, never>;
10
+ }
@@ -0,0 +1,59 @@
1
+ // ../../src/services/opentelemetry/browser.ts
2
+ import { Effect, Layer, Predicate, Schema, Stream } from "effect";
3
+ import { FetchHttpClient } from "effect/unstable/http";
4
+ import { Otlp } from "effect/unstable/observability";
5
+
6
+ class BrowserReportedError extends Schema.TaggedError()("BrowserReportedError", {
7
+ type: Schema.Literals(["window.error", "unhandledrejection"]),
8
+ message: Schema.String,
9
+ stack: Schema.optional(Schema.String)
10
+ }) {
11
+ }
12
+ var reportBrowserException = (exception) => Effect.fail(exception).pipe(Effect.withSpan("browser.exception", {
13
+ attributes: {
14
+ "exception.type": exception.type,
15
+ "exception.message": exception.message,
16
+ "exception.stacktrace": exception.stack ?? ""
17
+ }
18
+ }), Effect.ignoreCause);
19
+ var BrowserExceptionTrackingLive = Layer.effectDiscard(Stream.merge(Stream.fromEventListener(globalThis, "error", {
20
+ bufferSize: 100
21
+ }).pipe(Stream.map((event) => {
22
+ const error = event.error;
23
+ return new BrowserReportedError({
24
+ type: "window.error",
25
+ message: event.message,
26
+ stack: Predicate.isError(error) ? error.stack : undefined
27
+ });
28
+ })), Stream.fromEventListener(globalThis, "unhandledrejection", { bufferSize: 100 }).pipe(Stream.map((event) => {
29
+ const reason = event.reason;
30
+ return new BrowserReportedError(Predicate.isError(reason) ? {
31
+ type: "unhandledrejection",
32
+ message: reason.message,
33
+ stack: reason.stack
34
+ } : {
35
+ type: "unhandledrejection",
36
+ message: "Unhandled promise rejection"
37
+ });
38
+ }))).pipe(Stream.runForEach(reportBrowserException), Effect.forkScoped));
39
+
40
+ class BrowserOtlp {
41
+ static layer = ({
42
+ attributes,
43
+ baseUrl = "/api/otel",
44
+ serviceName,
45
+ serviceVersion,
46
+ ...options
47
+ }) => BrowserExceptionTrackingLive.pipe(Layer.provideMerge(Otlp.layerJson({
48
+ tracerExportInterval: "5 seconds",
49
+ metricsExportInterval: "30 seconds",
50
+ loggerExcludeLogSpans: true,
51
+ loggerMergeWithExisting: true,
52
+ ...options,
53
+ baseUrl,
54
+ resource: { attributes, serviceName, serviceVersion }
55
+ }).pipe(Layer.provide(FetchHttpClient.layer))));
56
+ }
57
+ export {
58
+ BrowserOtlp
59
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krak-stack/registry",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
4
4
  "description": "Tree-shakable KrakStack components and Effect services.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -48,6 +48,14 @@
48
48
  "types": "./dist/lib/httpapi-mcp.d.ts",
49
49
  "import": "./dist/lib/httpapi-mcp.js"
50
50
  },
51
+ "./opentelemetry/api": {
52
+ "types": "./dist/services/opentelemetry/api.d.ts",
53
+ "import": "./dist/services/opentelemetry/api.js"
54
+ },
55
+ "./opentelemetry/browser": {
56
+ "types": "./dist/services/opentelemetry/browser.d.ts",
57
+ "import": "./dist/services/opentelemetry/browser.js"
58
+ },
51
59
  "./docs": {
52
60
  "types": "./dist/lib/docs.d.ts",
53
61
  "import": "./dist/lib/docs.js"