@krak-stack/registry 0.1.30 → 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
@@ -205,6 +205,10 @@ export type DataTablePaginationFeature = false | {
205
205
  pageSizes?: readonly number[];
206
206
  };
207
207
  export interface DataTableFeatures<TData> {
208
+ toolbar?: false | {
209
+ /** Additional `MenubarMenu` controls rendered at the start of the toolbar. */
210
+ items: ReactNode;
211
+ };
208
212
  search?: boolean;
209
213
  sorting?: boolean;
210
214
  pagination?: DataTablePaginationFeature;
@@ -2068,8 +2068,9 @@ var DataTableToolbar = ({
2068
2068
  const model = deriveModel(store);
2069
2069
  const selectedRows = getSelectedRows(store, model);
2070
2070
  const grouping = features.grouping;
2071
+ const toolbar = features.toolbar;
2071
2072
  const bulkActions = features.selection && features.selection.bulkActions;
2072
- const hasToolbar = features.search !== false || features.sorting !== false || features.columnVisibility !== false || !!features.export || !!features.gallery || !!grouping || !!features.refresh || !!bulkActions && selectedRows.length > 0;
2073
+ const hasToolbar = !!toolbar || features.search !== false || features.sorting !== false || features.columnVisibility !== false || !!features.export || !!features.gallery || !!grouping || !!features.refresh || !!bulkActions && selectedRows.length > 0;
2073
2074
  if (!hasToolbar)
2074
2075
  return null;
2075
2076
  const update = (next) => changeState(setStore, next);
@@ -2113,6 +2114,7 @@ var DataTableToolbar = ({
2113
2114
  children: /* @__PURE__ */ jsxs8(Menubar, {
2114
2115
  className: "h-auto w-max min-w-full justify-start bg-transparent shadow-none [&_[data-slot=menubar-trigger]]:gap-2 [&_[data-slot=menubar-trigger]_svg]:size-4",
2115
2116
  children: [
2117
+ toolbar && toolbar.items,
2116
2118
  visibleBulkActions.length ? /* @__PURE__ */ jsxs8(MenubarMenu, {
2117
2119
  children: [
2118
2120
  /* @__PURE__ */ jsxs8(MenubarTrigger, {
@@ -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.30",
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"