@krak-stack/registry 0.1.39 → 0.1.41

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.
@@ -1,5 +1,5 @@
1
1
  // ../../src/components/ui/google-map.tsx
2
- import { Minus, Plus } from "lucide-react";
2
+ import { LocateFixed, Minus, Plus } from "lucide-react";
3
3
  import { useEffect, useEffectEvent, useRef, useState } from "react";
4
4
  import { Schema } from "effect";
5
5
 
@@ -8,7 +8,7 @@ import { cn } from "cn";
8
8
 
9
9
  // ../../src/components/ui/google-map.tsx
10
10
  import { useKrakstackLocale } from "@krak-stack/registry/krakstack-provider";
11
- import { jsx, jsxs } from "react/jsx-runtime";
11
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
12
12
  var googleMapsScriptId = "krak-stack-google-maps-script";
13
13
  var googleMapsCallbackName = "__krakStackGoogleMapsInit";
14
14
  var googleMapsPromise;
@@ -21,7 +21,8 @@ var defaultMessages = {
21
21
  errorTitle: "Map unavailable",
22
22
  errorBody: "Google Maps could not be loaded. Try again later.",
23
23
  zoomIn: "Zoom in",
24
- zoomOut: "Zoom out"
24
+ zoomOut: "Zoom out",
25
+ showArea: "Show selected area"
25
26
  },
26
27
  fr: {
27
28
  mapLabel: "Carte Google",
@@ -31,7 +32,8 @@ var defaultMessages = {
31
32
  errorTitle: "Carte indisponible",
32
33
  errorBody: "Impossible de charger Google Maps. Réessayez plus tard.",
33
34
  zoomIn: "Zoom avant",
34
- zoomOut: "Zoom arrière"
35
+ zoomOut: "Zoom arrière",
36
+ showArea: "Afficher la zone sélectionnée"
35
37
  }
36
38
  };
37
39
  var googleMapMessages = (locale, translations) => ({
@@ -104,6 +106,8 @@ function GoogleMap({
104
106
  radius,
105
107
  interactive = true,
106
108
  zoomControls = true,
109
+ fitRadius = false,
110
+ fitPadding = 32,
107
111
  minZoom = 3,
108
112
  maxZoom = 20,
109
113
  className,
@@ -135,6 +139,18 @@ function GoogleMap({
135
139
  const radiusStrokeColor = radius?.strokeColor;
136
140
  const radiusStrokeOpacity = radius?.strokeOpacity;
137
141
  const radiusStrokeWeight = radius?.strokeWeight;
142
+ const showSelectedArea = () => {
143
+ try {
144
+ const element = mapElementRef.current;
145
+ const bounds = circleRef.current?.getBounds()?.toJSON();
146
+ if (!element?.clientWidth || !element.clientHeight || !bounds)
147
+ return;
148
+ mapRef.current?.fitBounds(bounds, Math.max(0, fitPadding));
149
+ } catch {
150
+ setLoadState("error");
151
+ }
152
+ };
153
+ const fitSelection = useEffectEvent(showSelectedArea);
138
154
  const initializeMap = useEffectEvent((api, element) => {
139
155
  const map = new api.maps.Map(element, mapOptions(center, zoom, interactive, normalizedMinZoom, normalizedMaxZoom));
140
156
  apiRef.current = api;
@@ -182,17 +198,36 @@ function GoogleMap({
182
198
  };
183
199
  }, [normalizedApiKey]);
184
200
  useEffect(() => {
185
- const api = apiRef.current;
186
201
  const map = mapRef.current;
187
- if (loadState !== "ready" || !api || !map)
202
+ if (loadState !== "ready" || !map)
188
203
  return;
204
+ const nextCenter = { lat: centerLat, lng: centerLng };
205
+ const nextZoom = clampZoom(zoom, normalizedMinZoom, normalizedMaxZoom);
189
206
  try {
190
- const nextCenter = { lat: centerLat, lng: centerLng };
191
- const nextZoom = clampZoom(zoom, normalizedMinZoom, normalizedMaxZoom);
207
+ map.setOptions(mapOptions(nextCenter, nextZoom, interactive, normalizedMinZoom, normalizedMaxZoom));
192
208
  map.setCenter(nextCenter);
193
209
  map.setZoom(nextZoom);
194
- map.setOptions(mapOptions(nextCenter, nextZoom, interactive, normalizedMinZoom, normalizedMaxZoom));
195
210
  setCurrentZoom(nextZoom);
211
+ } catch {
212
+ setLoadState("error");
213
+ }
214
+ }, [
215
+ centerLat,
216
+ centerLng,
217
+ zoom,
218
+ interactive,
219
+ fitRadius,
220
+ normalizedMinZoom,
221
+ normalizedMaxZoom,
222
+ loadState
223
+ ]);
224
+ useEffect(() => {
225
+ const api = apiRef.current;
226
+ const map = mapRef.current;
227
+ if (loadState !== "ready" || !api || !map)
228
+ return;
229
+ try {
230
+ const nextCenter = { lat: centerLat, lng: centerLng };
196
231
  if (markerEnabled) {
197
232
  if (markerRef.current) {
198
233
  markerRef.current.setMap(map);
@@ -241,19 +276,46 @@ function GoogleMap({
241
276
  }, [
242
277
  centerLat,
243
278
  centerLng,
244
- interactive,
245
279
  loadState,
246
280
  markerEnabled,
247
281
  markerIcon,
248
- normalizedMaxZoom,
249
- normalizedMinZoom,
250
282
  radiusFillColor,
251
283
  radiusFillOpacity,
252
284
  radiusMeters,
253
285
  radiusStrokeColor,
254
286
  radiusStrokeOpacity,
255
- radiusStrokeWeight,
256
- zoom
287
+ radiusStrokeWeight
288
+ ]);
289
+ useEffect(() => {
290
+ const element = mapElementRef.current;
291
+ if (loadState !== "ready" || !fitRadius || radiusMeters === undefined || !element)
292
+ return;
293
+ fitSelection();
294
+ if (!globalThis.ResizeObserver)
295
+ return;
296
+ let width = element.clientWidth;
297
+ let height = element.clientHeight;
298
+ const observer = new ResizeObserver(() => {
299
+ const nextWidth = element.clientWidth;
300
+ const nextHeight = element.clientHeight;
301
+ if (nextWidth === width && nextHeight === height)
302
+ return;
303
+ width = nextWidth;
304
+ height = nextHeight;
305
+ fitSelection();
306
+ });
307
+ observer.observe(element);
308
+ return () => observer.disconnect();
309
+ }, [
310
+ loadState,
311
+ fitRadius,
312
+ fitPadding,
313
+ interactive,
314
+ centerLat,
315
+ centerLng,
316
+ radiusMeters,
317
+ normalizedMinZoom,
318
+ normalizedMaxZoom
257
319
  ]);
258
320
  const changeZoom = (delta) => {
259
321
  const map = mapRef.current;
@@ -264,7 +326,7 @@ function GoogleMap({
264
326
  setCurrentZoom(nextZoom);
265
327
  };
266
328
  return /* @__PURE__ */ jsxs("div", {
267
- className: cn("bg-muted relative min-h-64 overflow-hidden rounded-lg border", className),
329
+ className: cn("bg-muted relative isolate min-h-64 min-w-0 overflow-hidden rounded-lg border", className),
268
330
  children: [
269
331
  /* @__PURE__ */ jsx("div", {
270
332
  ref: mapElementRef,
@@ -273,11 +335,28 @@ function GoogleMap({
273
335
  "aria-label": labels.mapLabel
274
336
  }),
275
337
  loadState === "ready" && zoomControls ? /* @__PURE__ */ jsxs("div", {
276
- className: "bg-background/95 absolute right-4 bottom-4 z-10 overflow-hidden rounded-md border shadow-sm backdrop-blur",
338
+ className: "bg-background/95 absolute top-3 right-3 z-10 overflow-hidden rounded-md border shadow-sm backdrop-blur",
277
339
  children: [
340
+ fitRadius && radiusMeters !== undefined ? /* @__PURE__ */ jsxs(Fragment, {
341
+ children: [
342
+ /* @__PURE__ */ jsx("button", {
343
+ type: "button",
344
+ onClick: showSelectedArea,
345
+ "aria-label": labels.showArea,
346
+ title: labels.showArea,
347
+ className: "text-foreground hover:bg-accent focus-visible:ring-ring relative flex size-11 items-center justify-center focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-inset",
348
+ children: /* @__PURE__ */ jsx(LocateFixed, {
349
+ className: "size-4"
350
+ })
351
+ }),
352
+ /* @__PURE__ */ jsx("div", {
353
+ className: "bg-border h-px"
354
+ })
355
+ ]
356
+ }) : null,
278
357
  /* @__PURE__ */ jsx("button", {
279
358
  type: "button",
280
- className: "text-muted-foreground hover:bg-accent hover:text-accent-foreground flex size-9 items-center justify-center transition-colors disabled:pointer-events-none disabled:opacity-50",
359
+ className: "text-foreground hover:bg-accent focus-visible:ring-ring relative flex size-11 items-center justify-center transition-colors focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-inset disabled:pointer-events-none disabled:opacity-50",
281
360
  disabled: currentZoom >= normalizedMaxZoom,
282
361
  onClick: () => changeZoom(1),
283
362
  "aria-label": labels.zoomIn,
@@ -290,7 +369,7 @@ function GoogleMap({
290
369
  }),
291
370
  /* @__PURE__ */ jsx("button", {
292
371
  type: "button",
293
- className: "text-muted-foreground hover:bg-accent hover:text-accent-foreground flex size-9 items-center justify-center transition-colors disabled:pointer-events-none disabled:opacity-50",
372
+ className: "text-foreground hover:bg-accent focus-visible:ring-ring relative flex size-11 items-center justify-center transition-colors focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-inset disabled:pointer-events-none disabled:opacity-50",
294
373
  disabled: currentZoom <= normalizedMinZoom,
295
374
  onClick: () => changeZoom(-1),
296
375
  "aria-label": labels.zoomOut,
@@ -29,8 +29,8 @@ export declare const SortParams: Schema.declare<readonly {
29
29
  readonly direction: "asc" | "desc";
30
30
  }[]>;
31
31
  export declare const Query: Schema.Struct<{
32
- readonly page: Schema.decodeTo<Schema.Int, Schema.optionalKey<Schema.Union<readonly [Schema.Number, Schema.NumberFromString]>>, never, never>;
33
- readonly pageSize: Schema.decodeTo<Schema.Int, Schema.optionalKey<Schema.Union<readonly [Schema.Number, Schema.NumberFromString]>>, never, never>;
32
+ readonly page: Schema.decodeTo<Schema.Int, Schema.optional<Schema.Union<readonly [Schema.Number, Schema.NumberFromString]>>, never, never>;
33
+ readonly pageSize: Schema.decodeTo<Schema.Int, Schema.optional<Schema.Union<readonly [Schema.Number, Schema.NumberFromString]>>, never, never>;
34
34
  readonly globalFilter: Schema.optional<Schema.String>;
35
35
  readonly sort: Schema.optional<Schema.declare<readonly {
36
36
  readonly id: string;
@@ -42,7 +42,34 @@ export declare const Query: Schema.Struct<{
42
42
  }>;
43
43
  export type QueryType = typeof Query.Type;
44
44
  export type QueryEncoded = typeof Query.Encoded;
45
- export declare const QueryStandard: ReturnType<typeof Schema.toStandardSchemaV1<typeof Query>>;
45
+ export declare const QueryStandard: import("effect/StandardSchema").StandardSchemaV1<{
46
+ readonly globalFilter?: string | undefined;
47
+ readonly sort?: readonly {
48
+ readonly id: string;
49
+ readonly direction: "asc" | "desc";
50
+ }[] | undefined;
51
+ readonly page?: string | number | undefined;
52
+ readonly pageSize?: string | number | undefined;
53
+ }, {
54
+ readonly globalFilter?: string | undefined;
55
+ readonly sort?: readonly {
56
+ readonly id: string;
57
+ readonly direction: "asc" | "desc";
58
+ }[] | undefined;
59
+ readonly page?: number | undefined;
60
+ readonly pageSize?: number | undefined;
61
+ }> & Schema.Struct<{
62
+ readonly globalFilter: Schema.optional<Schema.String>;
63
+ readonly sort: Schema.optional<Schema.declare<readonly {
64
+ readonly id: string;
65
+ readonly direction: "asc" | "desc";
66
+ }[], readonly {
67
+ readonly id: string;
68
+ readonly direction: "asc" | "desc";
69
+ }[]>>;
70
+ readonly page: Schema.decodeTo<Schema.optional<Schema.Int>, Schema.decodeTo<Schema.Int, Schema.optional<Schema.Union<readonly [Schema.Number, Schema.NumberFromString]>>, never, never>, never, never>;
71
+ readonly pageSize: Schema.decodeTo<Schema.optional<Schema.Int>, Schema.decodeTo<Schema.Int, Schema.optional<Schema.Union<readonly [Schema.Number, Schema.NumberFromString]>>, never, never>, never, never>;
72
+ }>;
46
73
  export declare const PaginationMeta: Schema.Struct<{
47
74
  readonly page: Schema.Int;
48
75
  readonly pageSize: Schema.Int;
package/dist/lib/query.js CHANGED
@@ -81,11 +81,11 @@ var SortParams = Schema.declare(Schema.is(SortParamsArray), {
81
81
  toCodecStringTree: () => Schema.link()(SortParamsFromString, SchemaTransformation.passthrough())
82
82
  });
83
83
  var Query = Schema.Struct({
84
- page: Schema.optionalKey(Schema.Union([Schema.Number, Schema.NumberFromString]).check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0))).pipe(Schema.decodeTo(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)), {
84
+ page: Schema.optional(Schema.Union([Schema.Number, Schema.NumberFromString]).check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0))).pipe(Schema.decodeTo(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)), {
85
85
  decode: SchemaGetter.withDefault(Effect.succeed(0)),
86
86
  encode: SchemaGetter.transformOptional(Option.filter((page) => page !== 0))
87
87
  })),
88
- pageSize: Schema.optionalKey(Schema.Union([Schema.Number, Schema.NumberFromString]).check(Schema.isInt(), Schema.isBetween({ minimum: 1, maximum: 100 }))).pipe(Schema.decodeTo(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 100 })), {
88
+ pageSize: Schema.optional(Schema.Union([Schema.Number, Schema.NumberFromString]).check(Schema.isInt(), Schema.isBetween({ minimum: 1, maximum: 100 }))).pipe(Schema.decodeTo(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 100 })), {
89
89
  decode: SchemaGetter.withDefault(Effect.succeed(20)),
90
90
  encode: SchemaGetter.transformOptional(Option.filter((pageSize) => pageSize !== 20))
91
91
  })),
@@ -107,7 +107,17 @@ var Query = Schema.Struct({
107
107
  }
108
108
  ]
109
109
  });
110
- var QueryStandard = Schema.toStandardSchemaV1(Query);
110
+ var QueryStandard = Schema.toStandardSchemaV1(Schema.Struct({
111
+ ...Query.fields,
112
+ page: Query.fields.page.pipe(Schema.decodeTo(Schema.optional(Schema.Int), {
113
+ decode: SchemaGetter.transform((page) => page === 0 ? undefined : page),
114
+ encode: SchemaGetter.transform((page) => page ?? 0)
115
+ })),
116
+ pageSize: Query.fields.pageSize.pipe(Schema.decodeTo(Schema.optional(Schema.Int), {
117
+ decode: SchemaGetter.transform((pageSize) => pageSize === 20 ? undefined : pageSize),
118
+ encode: SchemaGetter.transform((pageSize) => pageSize ?? 20)
119
+ }))
120
+ }).annotate({ identifier: "QuerySearch" }));
111
121
  var PaginationMeta = Schema.Struct({
112
122
  page: Schema.Int,
113
123
  pageSize: Schema.Int,
@@ -1,5 +1,5 @@
1
1
  import { Config, Layer } from "effect";
2
2
  import { HttpRouter } from "effect/http";
3
3
  export declare class HttpApiOtlp {
4
- static readonly layer: Layer.Layer<import("effect/FileSystem").FileSystem | import("effect/http/Etag").Generator | import("effect/http/HttpPlatform").HttpPlatform | import("effect/Path").Path, Config.ConfigError, HttpRouter.HttpRouter>;
4
+ static readonly layer: Layer.Layer<import("effect/FileSystem").FileSystem | import("effect/observability/OtlpExporter").Flusher | import("effect/http/Etag").Generator | import("effect/http/HttpPlatform").HttpPlatform | import("./events.js").OtlpEvents | import("effect/Path").Path, Config.ConfigError, HttpRouter.HttpRouter>;
5
5
  }
@@ -1,7 +1,7 @@
1
1
  // ../../src/services/opentelemetry/api.ts
2
- import { Config, Effect, Layer, Schema } from "effect";
2
+ import { Config as Config2, Effect as Effect3, Layer as Layer3, Schema as Schema4 } from "effect";
3
3
  import {
4
- FetchHttpClient,
4
+ FetchHttpClient as FetchHttpClient2,
5
5
  HttpClient,
6
6
  HttpClientRequest,
7
7
  HttpMiddleware,
@@ -10,15 +10,167 @@ import {
10
10
  HttpServerRequest,
11
11
  HttpServerResponse
12
12
  } from "effect/http";
13
- import { Otlp, OtlpSerialization } from "effect/observability";
14
- var OpenTelemetryLive = Otlp.layerFromConfig().pipe(Layer.provide(OtlpSerialization.layerJson), Layer.provide(FetchHttpClient.layer));
13
+
14
+ // ../../src/services/opentelemetry/server.ts
15
+ import { Config, Effect as Effect2, Layer as Layer2, Schema as Schema3 } from "effect";
16
+ import { FetchHttpClient } from "effect/http";
17
+ import { Otlp, OtlpExporter as OtlpExporter2, OtlpSerialization as OtlpSerialization2 } from "effect/observability";
18
+
19
+ // ../../src/services/opentelemetry/events.ts
20
+ import {
21
+ Clock,
22
+ Context,
23
+ DateTime,
24
+ Effect,
25
+ Layer,
26
+ Option,
27
+ References,
28
+ Schema as Schema2
29
+ } from "effect";
30
+ import {
31
+ OtlpExporter,
32
+ OtlpResource,
33
+ OtlpSerialization
34
+ } from "effect/observability";
35
+
36
+ // ../../src/services/opentelemetry/schema.ts
37
+ import { Schema } from "effect";
38
+ var EventAttributeValue = Schema.Union([
39
+ Schema.String,
40
+ Schema.Finite,
41
+ Schema.Boolean,
42
+ Schema.Array(Schema.String),
43
+ Schema.Array(Schema.Finite),
44
+ Schema.Array(Schema.Boolean)
45
+ ]);
46
+ var EventAttributesSchema = Schema.Record(Schema.String, EventAttributeValue).annotate({ identifier: "EventAttributes" });
47
+ var EventOptionsSchema = Schema.Struct({
48
+ userId: Schema.optional(Schema.NonEmptyString),
49
+ anonymousId: Schema.optional(Schema.NonEmptyString),
50
+ sessionId: Schema.optional(Schema.NonEmptyString),
51
+ messageId: Schema.optional(Schema.NonEmptyString),
52
+ timestamp: Schema.optional(Schema.DateTimeUtc),
53
+ context: Schema.optional(EventAttributesSchema)
54
+ }).annotate({ identifier: "EventOptions" });
55
+ var TelemetryEvent = Schema.Struct({
56
+ name: Schema.NonEmptyString,
57
+ properties: Schema.optional(EventAttributesSchema),
58
+ options: Schema.optional(EventOptionsSchema)
59
+ }).annotate({ identifier: "TelemetryEvent" });
60
+ var PageEvent = Schema.Struct({
61
+ name: Schema.optional(Schema.NonEmptyString),
62
+ category: Schema.optional(Schema.NonEmptyString),
63
+ path: Schema.optional(Schema.NonEmptyString),
64
+ properties: Schema.optional(EventAttributesSchema),
65
+ options: Schema.optional(EventOptionsSchema)
66
+ }).annotate({ identifier: "PageEvent" });
67
+ var BrowserIdentity = Schema.Struct({
68
+ userId: Schema.optional(Schema.NonEmptyString),
69
+ organizationId: Schema.optional(Schema.NonEmptyString)
70
+ }).annotate({ identifier: "BrowserTelemetryIdentity" });
71
+
72
+ // ../../src/services/opentelemetry/events.ts
73
+ class OtlpEvents extends Context.Service()("krakstack/opentelemetry/Events") {
74
+ static disabledLayer = Layer.succeed(this, { push: () => {} });
75
+ static layer = (options) => Layer.effect(this, Effect.gen(function* () {
76
+ const serialization = yield* OtlpSerialization.OtlpSerialization;
77
+ const resource = yield* OtlpResource.fromConfig(options.resource);
78
+ return yield* OtlpExporter.make({
79
+ ...options,
80
+ label: "OtlpEvents",
81
+ body: (logRecords) => [
82
+ serialization.logs({
83
+ resourceLogs: [
84
+ {
85
+ resource,
86
+ scopeLogs: [
87
+ {
88
+ scope: { name: "krakstack.events" },
89
+ logRecords
90
+ }
91
+ ]
92
+ }
93
+ ]
94
+ }),
95
+ Effect.void
96
+ ]
97
+ });
98
+ }));
99
+ }
100
+ var track = Effect.fnUntraced(function* (name, properties, options) {
101
+ const event = yield* Schema2.decodeUnknownEffect(TelemetryEvent)({
102
+ name,
103
+ properties,
104
+ options
105
+ });
106
+ const events = yield* OtlpEvents;
107
+ const clock = yield* Clock.Clock;
108
+ const observedTime = clock.currentTimeNanosUnsafe().toString();
109
+ const timestamp = event.options?.timestamp ? (BigInt(DateTime.toEpochMillis(event.options.timestamp)) * 1000000n).toString() : observedTime;
110
+ const span = yield* Effect.currentSpan.pipe(Effect.option);
111
+ const attributes = { ...event.options?.context, ...event.properties };
112
+ if (event.options?.userId)
113
+ attributes["user.id"] = event.options.userId;
114
+ if (event.options?.anonymousId)
115
+ attributes["analytics.anonymous_id"] = event.options.anonymousId;
116
+ if (event.options?.sessionId)
117
+ attributes["session.id"] = event.options.sessionId;
118
+ if (event.options?.messageId)
119
+ attributes["log.record.uid"] = event.options.messageId;
120
+ const annotations = yield* References.CurrentLogAnnotations;
121
+ events.push({
122
+ eventName: event.name,
123
+ timeUnixNano: timestamp,
124
+ observedTimeUnixNano: observedTime,
125
+ severityNumber: 9,
126
+ attributes: OtlpResource.entriesToAttributes(Object.entries({
127
+ ...attributes,
128
+ ...annotations
129
+ })),
130
+ droppedAttributesCount: 0,
131
+ traceId: Option.isSome(span) ? span.value.traceId : undefined,
132
+ spanId: Option.isSome(span) ? span.value.spanId : undefined
133
+ });
134
+ });
135
+
136
+ // ../../src/services/opentelemetry/server.ts
137
+ var ServerEventsLive = Layer2.unwrap(Effect2.gen(function* () {
138
+ const disabled = yield* Config.Boolean("OTEL_SDK_DISABLED").pipe(Config.withDefault(false));
139
+ const exporters = yield* Config.Array(Schema3.String, "OTEL_LOGS_EXPORTER").pipe(Config.withDefault([]));
140
+ if (disabled || !exporters.some((value) => value.trim().toLowerCase() === "otlp")) {
141
+ return OtlpEvents.disabledLayer;
142
+ }
143
+ const endpoint = yield* Config.URL("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT").pipe(Config.orElse(() => Config.URL("OTEL_EXPORTER_OTLP_ENDPOINT").pipe(Config.map((url) => {
144
+ url.pathname += `${url.pathname.endsWith("/") ? "" : "/"}v1/logs`;
145
+ return url;
146
+ }))), Config.withDefault(undefined));
147
+ if (!endpoint) {
148
+ return OtlpEvents.disabledLayer;
149
+ }
150
+ const headersConfig = (name) => Config.Record(Schema3.String, Schema3.StringFromUriComponent, name);
151
+ const headers = yield* headersConfig("OTEL_EXPORTER_OTLP_LOGS_HEADERS").pipe(Config.orElse(() => headersConfig("OTEL_EXPORTER_OTLP_HEADERS")), Config.withDefault(undefined));
152
+ return OtlpEvents.layer({
153
+ url: endpoint.toString(),
154
+ headers,
155
+ exportInterval: "1 second",
156
+ maxBatchSize: 1000,
157
+ shutdownTimeout: "3 seconds"
158
+ });
159
+ }).pipe(Effect2.orDie));
160
+
161
+ class ServerOtlp {
162
+ static track = track;
163
+ static layer = Layer2.mergeAll(Otlp.layerFromConfig(), ServerEventsLive).pipe(Layer2.provideMerge(OtlpExporter2.layerFlusher), Layer2.provide(OtlpSerialization2.layerJson), Layer2.provide(FetchHttpClient.layer));
164
+ }
165
+
166
+ // ../../src/services/opentelemetry/api.ts
15
167
  var otlpProxyPaths = [
16
168
  "/api/otel/v1/traces",
17
169
  "/api/otel/v1/metrics",
18
170
  "/api/otel/v1/logs"
19
171
  ];
20
- var exporterConfig = (signal) => Config.Array(Schema.String, `OTEL_${signal}_EXPORTER`).pipe(Config.map((exporters) => exporters.map((exporter) => exporter.toLowerCase().trim())), Config.withDefault([]));
21
- var proxyOtlp = (baseUrl, signal) => Effect.gen(function* () {
172
+ var exporterConfig = (signal) => Config2.Array(Schema4.String, `OTEL_${signal}_EXPORTER`).pipe(Config2.map((exporters) => exporters.map((exporter) => exporter.toLowerCase().trim())), Config2.withDefault([]));
173
+ var proxyOtlp = (baseUrl, signal) => Effect3.gen(function* () {
22
174
  const incoming = yield* HttpServerRequest.HttpServerRequest;
23
175
  const client = yield* HttpClient.HttpClient;
24
176
  const contentType = incoming.headers["content-type"] ?? "application/json";
@@ -33,20 +185,20 @@ var proxyOtlp = (baseUrl, signal) => Effect.gen(function* () {
33
185
  "retry-after": upstream.headers["retry-after"]
34
186
  }
35
187
  });
36
- }).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));
37
- var otlpProxyLayer = Layer.unwrap(Effect.gen(function* () {
38
- const baseUrl = yield* Config.URL("OTEL_EXPORTER_OTLP_ENDPOINT");
39
- const exporters = yield* Config.all({
188
+ }).pipe(Effect3.withTracerEnabled(false), HttpMiddleware.withLoggerDisabled, Effect3.catch((error) => Effect3.logError("Failed to proxy browser telemetry", error).pipe(Effect3.as(HttpServerResponse.empty({ status: 502 })))), Effect3.provide(FetchHttpClient2.layer));
189
+ var otlpProxyLayer = Layer3.unwrap(Effect3.gen(function* () {
190
+ const baseUrl = yield* Config2.URL("OTEL_EXPORTER_OTLP_ENDPOINT");
191
+ const exporters = yield* Config2.all({
40
192
  logs: exporterConfig("LOGS"),
41
193
  metrics: exporterConfig("METRICS"),
42
194
  traces: exporterConfig("TRACES")
43
195
  });
44
- return Layer.mergeAll(exporters.traces.includes("otlp") ? HttpRouter.add("POST", otlpProxyPaths[0], proxyOtlp(baseUrl, "traces")) : Layer.empty, exporters.metrics.includes("otlp") ? HttpRouter.add("POST", otlpProxyPaths[1], proxyOtlp(baseUrl, "metrics")) : Layer.empty, exporters.logs.includes("otlp") ? HttpRouter.add("POST", otlpProxyPaths[2], proxyOtlp(baseUrl, "logs")) : Layer.empty);
196
+ return Layer3.mergeAll(exporters.traces.includes("otlp") ? HttpRouter.add("POST", otlpProxyPaths[0], proxyOtlp(baseUrl, "traces")) : Layer3.empty, exporters.metrics.includes("otlp") ? HttpRouter.add("POST", otlpProxyPaths[1], proxyOtlp(baseUrl, "metrics")) : Layer3.empty, exporters.logs.includes("otlp") ? HttpRouter.add("POST", otlpProxyPaths[2], proxyOtlp(baseUrl, "logs")) : Layer3.empty);
45
197
  }));
46
- var serverServices = HttpServer.layerServices.pipe(Layer.provideMerge(HttpMiddleware.layerTracerDisabledForUrls(otlpProxyPaths)));
198
+ var serverServices = HttpServer.layerServices.pipe(Layer3.provideMerge(HttpMiddleware.layerTracerDisabledForUrls(otlpProxyPaths)));
47
199
 
48
200
  class HttpApiOtlp {
49
- static layer = Layer.mergeAll(OpenTelemetryLive, otlpProxyLayer, serverServices);
201
+ static layer = Layer3.mergeAll(ServerOtlp.layer, otlpProxyLayer, serverServices);
50
202
  }
51
203
  export {
52
204
  HttpApiOtlp
@@ -1,5 +1,15 @@
1
- import { Layer } from "effect";
1
+ import { Context, Effect, Layer, Ref, Schema } from "effect";
2
2
  import { type Otlp, OtlpExporter, OtlpLogger, OtlpMetrics, OtlpTracer } from "effect/observability";
3
+ import { OtlpEvents, type EventExportOptions } from "./events.js";
4
+ import { BrowserIdentity, type EventAttributes, type EventOptions, type PageOptions } from "./schema.js";
5
+ export type { BrowserIdentity, EventAttributes, EventOptions, PageOptions, } from "./schema.js";
6
+ declare const BrowserIdentityState_base: Context.ServiceClass<BrowserIdentityState, "krakstack/opentelemetry/BrowserIdentity", Ref.Ref<{
7
+ readonly userId?: string | undefined;
8
+ readonly organizationId?: string | undefined;
9
+ }>>;
10
+ declare class BrowserIdentityState extends BrowserIdentityState_base {
11
+ static readonly layer: Layer.Layer<BrowserIdentityState, never, never>;
12
+ }
3
13
  type OtlpOptions = Parameters<typeof Otlp.layerJson>[0];
4
14
  type SignalOptions<Options> = Omit<Options, "url" | "resource" | "headers">;
5
15
  export interface BrowserOtlpOptions {
@@ -11,8 +21,16 @@ export interface BrowserOtlpOptions {
11
21
  readonly traces?: false | SignalOptions<Parameters<typeof OtlpTracer.layer>[0]>;
12
22
  readonly metrics?: false | SignalOptions<Parameters<typeof OtlpMetrics.layer>[0]>;
13
23
  readonly logs?: false | SignalOptions<Parameters<typeof OtlpLogger.layer>[0]>;
24
+ readonly events?: false | (EventExportOptions & {
25
+ /** Initial page and pathname changes; enabled by default. */
26
+ readonly pageViews?: boolean;
27
+ });
14
28
  }
15
29
  export declare class BrowserOtlp {
16
- static readonly layer: ({ attributes, baseUrl, logs, metrics, serviceName, serviceVersion, traces, headers, }: BrowserOtlpOptions) => Layer.Layer<OtlpExporter.Flusher, never, never>;
30
+ /** Uses the active browser layer; invalid identity warns without failing or changing state. */
31
+ static readonly identify: (identity: BrowserIdentity | null) => Effect.Effect<void, never, never>;
32
+ static readonly track: (name: string, properties?: EventAttributes, options?: EventOptions) => Effect.Effect<void, Schema.SchemaError, never>;
33
+ static readonly page: (options?: PageOptions) => Effect.Effect<void, Schema.SchemaError, never>;
34
+ static readonly pageView: (options?: PageOptions) => Effect.Effect<void, Schema.SchemaError, never>;
35
+ static readonly layer: ({ attributes, baseUrl, logs, events, metrics, serviceName, serviceVersion, traces, headers, }: BrowserOtlpOptions) => Layer.Layer<BrowserIdentityState | OtlpExporter.Flusher | OtlpEvents, never, never>;
17
36
  }
18
- export {};