@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 +21 -0
- package/dist/components/ui/data-table.d.ts +4 -0
- package/dist/components/ui/data-table.js +3 -1
- package/dist/services/opentelemetry/api.d.ts +5 -0
- package/dist/services/opentelemetry/api.js +44 -0
- package/dist/services/opentelemetry/browser.d.ts +10 -0
- package/dist/services/opentelemetry/browser.js +59 -0
- package/package.json +9 -1
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.
|
|
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"
|