@shivaedev/platform 0.14.1 → 0.14.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.14.3 - 2026-10-18
4
+
5
+ ### Fixed
6
+
7
+ - Bound OTLP HTTP export attempts to 10 seconds by default, honor standard general and traces-specific timeout variables, and read the batch interval, size and shutdown timeout through Effect Config.
8
+ - Redact bare `sig` assignments, including short errors such as `error: sig=...`, and PEM private-key blocks from exported trace values in both JSON and protobuf.
9
+ - SQL spans from `appDatabase` and `appHost` report the host and port from `DATABASE_URL`, including address query parameters, while keeping the connection URL redacted.
10
+
11
+ ## 0.14.2 - 2026-10-17
12
+
13
+ ### Fixed
14
+
15
+ - Align published framework dependencies with the current releases.
16
+
3
17
  ## 0.14.1 - 2026-10-17
4
18
 
5
19
  ### Added
package/README.md CHANGED
@@ -86,11 +86,11 @@ RPC uses the existing `localRpcGuard`: it requires a loopback peer and Host, sam
86
86
 
87
87
  For local tools, use `policy: "loopback"` with contracts that do not use session middleware to serve RPC without a session provider. The guard requires a loopback peer and Host, absent or same-origin Origin, and NDJSON content type. This policy does not use `TRUSTED_PROXY` or `config.trustedProxy`. Set `rpcPath` to choose another native RPC route; `/rpc` remains the default.
88
88
 
89
- Omit `migrations` to host an app without a database. This form creates no SQL pool and reads no `DATABASE_URL`; `AppHttpHostOptions` describes it. `AppHostOptions` retains its required migrations for existing typed callers. A service that needs SQL still requires a SQL Layer. Provide `appDatabase` to the application’s service Layer when it owns database composition, as Fleet's daemon does with its migration loader and namespace ledger. Real HTTP tests cover the custom route, session-free loopback guard and database-free startup; Fleet's PostgreSQL daemon tests cover the adopted composition.
89
+ Omit `migrations` to host an app without a database. This form creates no SQL pool and reads no `DATABASE_URL`; `AppHttpHostOptions` describes it. `AppHostOptions` requires migrations for database-backed hosts. A service that needs SQL still requires a SQL Layer. Provide `appDatabase` to the application’s service Layer when it owns database composition, as Fleet's daemon does with its migration loader and namespace ledger. Real HTTP tests cover the custom route, session-free loopback guard and database-free startup; Fleet's PostgreSQL daemon tests cover the adopted composition.
90
90
 
91
91
  `appHost` serves unauthenticated `GET /health`. It queries its SQL client on each request and returns `200` only when the query succeeds within two seconds; a missing SQL client, query failure or timeout returns `503`. Both responses have an empty body and `Cache-Control: no-store`. This route also works when the SQL client comes from the application’s `services` Layer. Use it for database readiness checks.
92
92
 
93
- Set `migrations.mode: "check"` on `appDatabase` or a database-backed `appHost` when a separate pre-deploy command owns migrations. Startup reads the ledger and fails with native `MigrationError` (`kind: "BadState"`) if any required migration ID or name is missing; it does not run migrations or create a ledger. Later ledger entries are allowed for rolling deployments. With `mode` omitted or `"migrate"`, the existing migration behavior remains: startup applies pending migrations with a five-second lock timeout unless `migrations.lockTimeout` overrides it.
93
+ Set `migrations.mode: "check"` on `appDatabase` or a database-backed `appHost` when a separate pre-deploy command owns migrations. Startup reads the ledger and fails with native `MigrationError` (`kind: "BadState"`) if any required migration ID or name is missing; it does not run migrations or create a ledger. Later ledger entries are allowed for rolling deployments. With `mode` omitted or `"migrate"`, startup applies pending migrations with a five-second lock timeout unless `migrations.lockTimeout` overrides it.
94
94
 
95
95
  When construction itself touches the schema, use `appHost({ migrations, config, application })`. `application` is a native Effect that returns the HTTP host options (`group`, handler and service Layers, session policy, and optional HTTP config). The host first builds `appDatabase`, then evaluates this Effect in the host scope with SQL, Reactivity and `AppChanges` available. A failed migration or schema-current check prevents construction. Initialization requirements and errors stay in the returned Layer's types, and acquired resources close with the host scope.
96
96
 
@@ -109,11 +109,31 @@ Construct Better Auth inside `application`, since its constructor starts schema
109
109
 
110
110
  Set `OTEL_EXPORTER_OTLP_ENDPOINT` (the collector base URL; `/v1/traces` is appended) or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` (the complete traces URL, which takes precedence) to export `appHost`'s Effect spans over OTLP/HTTP. `OTEL_EXPORTER_OTLP_PROTOCOL` selects `http/protobuf` or `http/json`; `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL` takes precedence for traces. When neither protocol variable is set, the default is `http/protobuf`, following the [OpenTelemetry recommendation](https://opentelemetry.io/docs/specs/otel/protocol/exporter/#specify-protocol). Unsupported protocols, including `grpc`, fail startup with a `ConfigError` naming the value. `OTEL_EXPORTER_OTLP_HEADERS` supplies comma-separated `key=value` exporter headers with URL-encoded values. `OTEL_SERVICE_NAME` sets the service name and `OTEL_SERVICE_VERSION` sets `service.version`; `OTEL_RESOURCE_ATTRIBUTES` adds comma-separated, URL-encoded resource attributes and can supply `service.name` when no service name is set. The fallback name is `unknown_service`. Configuration is read through Effect Config. Without an endpoint, no exporter or export loop is built, even when service metadata is set.
111
111
 
112
- On Railway, enable the per-service tracing setting separately for the web service and worker, and leave both exporter endpoint variables unset in your app's configuration. Railway injects the `OTEL_*` variables, including `OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf`, only when the app sets no endpoint of its own. The framework exports through Effect's own `effect/observability` OTLP tracer; eBPF auto-instrumentation is outside this path. For a worker, provide `otlpTracing` from `@shivaedev/platform/otlpTracing.ts` to its application Layer. It batches ended Effect spans and flushes during scoped shutdown with a three-second bound. The first failed export logs a warning with the collector status and redacted response body, or the transport failure kind when no response arrives. Repeated failures stay quiet until a successful export. Collector connection failures and HTTP errors leave requests, jobs and shutdown successful. HTTP receiver tests cover the host and the starter's PostgreSQL word-count worker; they do not establish delivery in a deployed collector.
112
+ The supported tracing variables and defaults are:
113
+
114
+ | Variable | Default | Meaning |
115
+ | --- | --- | --- |
116
+ | `OTEL_EXPORTER_OTLP_ENDPOINT` | Unset | Collector base URL. |
117
+ | `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | Base URL plus `/v1/traces` | Complete traces URL; takes precedence. |
118
+ | `OTEL_EXPORTER_OTLP_PROTOCOL` | `http/protobuf` | `http/protobuf` or `http/json`. |
119
+ | `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL` | General protocol | Overrides the protocol for traces. |
120
+ | `OTEL_EXPORTER_OTLP_HEADERS` | Empty | Comma-separated exporter headers. |
121
+ | `OTEL_SERVICE_NAME` | Resource `service.name`, then `unknown_service` | Service name. |
122
+ | `OTEL_SERVICE_VERSION` | Resource `service.version`, otherwise unset | Service version. |
123
+ | `OTEL_RESOURCE_ATTRIBUTES` | Empty | Comma-separated resource attributes. |
124
+ | `OTEL_EXPORTER_OTLP_TIMEOUT` | `10000` ms | Timeout for each HTTP export attempt, including its response body. |
125
+ | `OTEL_EXPORTER_OTLP_TRACES_TIMEOUT` | General timeout | Overrides the request timeout for traces. |
126
+ | `OTEL_BSP_SCHEDULE_DELAY` | `5000` ms | Effect tracer's batch export interval. |
127
+ | `OTEL_BSP_MAX_EXPORT_BATCH_SIZE` | `1000` spans | Effect tracer's batch-size trigger. |
128
+ | `OTEL_BSP_EXPORT_TIMEOUT` | `3000` ms | Effect tracer's shutdown flush bound, capped at the request timeout. |
129
+
130
+ Timeouts fail through the HTTP client's typed transport-error channel and use the same warning path as other transport failures. Effect retains its native retry policy; the request timeout applies to each attempt. The batch defaults follow Effect's `OtlpTracer.layer`. Its options cannot express a separate batch-operation deadline or a queue capacity, so `OTEL_BSP_EXPORT_TIMEOUT` controls shutdown only and `OTEL_BSP_MAX_QUEUE_SIZE` is unsupported. Other `OTEL_BSP_*` variables are unsupported.
131
+
132
+ On Railway, enable the per-service tracing setting separately for the web service and worker, and leave both exporter endpoint variables unset in your app's configuration. Railway injects the `OTEL_*` variables, including `OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf`, only when the app sets no endpoint of its own. The framework exports through Effect's own `effect/observability` OTLP tracer; eBPF auto-instrumentation is outside this path. For a worker, provide `otlpTracing` from `@shivaedev/platform/otlpTracing.ts` to its application Layer. It batches ended Effect spans and flushes during scoped shutdown with a three-second bound by default. The first failed export logs a warning with the collector status and redacted response body, or the transport failure kind when no response arrives. Repeated failures stay quiet until a successful export. Collector connection failures and HTTP errors leave requests, jobs and shutdown successful. HTTP receiver tests cover the host and the starter's PostgreSQL word-count worker; they do not establish delivery in a deployed collector.
113
133
 
114
134
  Exported HTTP server spans use `{method} {route}` with `http.route` for matched RPC and router paths, including parameter templates; unmatched requests use the method alone. The production host continues incoming W3C `traceparent` context, including one sent by an edge proxy.
115
135
 
116
- Both JSON and protobuf export serialization apply the existing sensitive-key and credential-text redaction to span attributes, events, links, error details and resource attributes. Exporter headers stay in the collector request, outside the spans. Applications must still avoid placing secrets in span names, free text or custom attributes; a comprehensive application redaction policy and request/action correlation remain open.
136
+ Both JSON and protobuf export serialization apply sensitive-key and credential-text redaction to span attributes, events, links, error details and resource attributes. Credential text includes JWTs, Bearer/Basic credentials, sensitive assignments including bare `sig=...` anywhere in text (with or without `?`), and complete PEM private-key blocks through their matching END marker, including RSA, EC, OPENSSH and encrypted private keys. Matches become `<redacted>`. `sig` uses the shared sensitive-key policy for attributes and text. URL query values, fragments and user information are also redacted. Exporter headers stay in the collector request, outside the spans. Applications must still avoid placing secrets in span names, free text or custom attributes.
117
137
 
118
138
  Set `config.maxConnections` on `appDatabase` or a migrated `appHost` to bound the pool. A daemon with long-lived RPC streams can use `config.disablePreemptiveShutdown: true` to let scope closure interrupt those streams before closing the listener; omitting it keeps Node's native graceful-shutdown default. Fleet's daemon tests cover one bounded pool and scoped provider and listener cleanup, including follower reconnection after shutdown.
119
139
 
@@ -337,9 +357,7 @@ await runtime.dispose();
337
357
 
338
358
  `base` is `"application"` and `request` is `"request"`. The application's resource Layer is acquired once across calls. An explicit `developmentCacheKey` shares a runtime between calls with that key during development. Each key belongs to one application runtime definition.
339
359
 
340
- A nested `runWithServices` call replaces the outer invocation's overrides for
341
- its callback. Merge the contexts explicitly when the nested call must retain
342
- those overrides.
360
+ A nested `runWithServices` call replaces the outer invocation's overrides for its callback. Merge the contexts explicitly when the nested call must retain those overrides.
343
361
 
344
362
  ### Node subscriptions: combine cancellation and clean up
345
363
 
@@ -442,6 +460,7 @@ Import the module that defines the name; there is no package-root entry. Paths b
442
460
  | Module | Names and purpose |
443
461
  | --- | --- |
444
462
  | `otlpTracing.ts` | `otlpTracing`: scoped, opt-in Effect OTLP trace export configured through standard exporter variables; provide it to worker Layers. |
463
+ | `boundedBody.ts` | `readBoundedBody`, `RequestBodyTooLarge`: bounded body bytes and a typed HTTP 413 failure. |
445
464
  | `appHost.ts` | `appHost({ group, handlers, services, migrations?, sessions?, policy?, rpcPath?, assets?, routes?, config? })` or `appHost({ application, migrations, config? })`, `appDatabase({ migrations, config? })`, `AppChanges`, `AppHostOptions`, `AppHttpHostOptions`, `AppLocalHostOptions`, `AppDatabaseOptions`: scoped HTTP and PostgreSQL composition with loopback defaults and optional proxy trust. |
446
465
  | `errors/taxonomy.ts` | `NotFound`, `Unauthorized`, `Forbidden`, `BadRequest`, `Conflict`, `PreconditionFailed`, `TooManyRequests`, `AuthUnavailable`, `InternalServerError`, `ServiceUnavailable`: Schema tagged error classes with a `message`; `BadRequest` and `Conflict` also accept an optional `field`. |
447
466
  | `errors/rejected-field.ts` | `rejectedField(unknown): Option<RejectedField>`; `RejectedField` is `{ field: string, message: string }`. |
@@ -480,6 +499,7 @@ Install the optional peers for the selected integration:
480
499
 
481
500
  | Integration | Additional peers |
482
501
  | --- | --- |
502
+ | `boundedBody.ts` | `@effect/platform-node` |
483
503
  | `appHost.ts` | `@effect/platform-node`, `@effect/sql-pg` |
484
504
  | `better-auth/adapter.ts` | `better-auth`, `@shivaedev/effect-prisma`, `@prisma-next/sql-orm-client` |
485
505
  | `testing/vitest.ts` | `@effect/vitest`, `@shivaedev/effect-prisma`, `@shivaedev/effect-trpc`, `@trpc/server` |
@@ -0,0 +1,4 @@
1
+ import type { PgPoolConfig } from "@effect/sql-pg/PgClient";
2
+ import { Redacted } from "effect";
3
+ export declare function databaseAddress(value: Redacted.Redacted<string>): Pick<PgPoolConfig, "host" | "port">;
4
+ //# sourceMappingURL=databaseAddress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"databaseAddress.d.ts","sourceRoot":"","sources":["../../src/server/databaseAddress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAElC,wBAAgB,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC,CAqBrG"}
@@ -0,0 +1,25 @@
1
+ import { Redacted } from "effect";
2
+ export function databaseAddress(value) {
3
+ try {
4
+ const url = new URL(Redacted.value(value));
5
+ if (url.protocol !== "postgres:" && url.protocol !== "postgresql:") {
6
+ return {};
7
+ }
8
+ const hostname = url.hostname.startsWith("[") ? url.hostname.slice(1, -1) : decodeURIComponent(url.hostname);
9
+ // PgConnection applies query parameters in order, after the authority.
10
+ const host = url.searchParams.getAll("host").at(-1) ?? hostname;
11
+ const rawPort = url.searchParams.getAll("port").at(-1) ?? url.port;
12
+ const port = rawPort === "" ? undefined : Number(rawPort);
13
+ if (host.includes(",") || (port !== undefined && (!Number.isInteger(port) || port < 1 || port > 65_535))) {
14
+ return {};
15
+ }
16
+ return {
17
+ ...(host === "" && !url.searchParams.has("host") ? {} : { host }),
18
+ ...(port === undefined ? {} : { port }),
19
+ };
20
+ }
21
+ catch {
22
+ return {};
23
+ }
24
+ }
25
+ //# sourceMappingURL=databaseAddress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"databaseAddress.js","sourceRoot":"","sources":["../../src/server/databaseAddress.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAElC,MAAM,UAAU,eAAe,CAAC,KAAgC;IAC/D,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3C,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,IAAI,GAAG,CAAC,QAAQ,KAAK,aAAa,EAAE,CAAC;YACpE,OAAO,EAAE,CAAC;QACX,CAAC;QACD,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7G,uEAAuE;QACvE,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC;QAChE,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC;QACnE,MAAM,IAAI,GAAG,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC1D,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC;YAC1G,OAAO,EAAE,CAAC;QACX,CAAC;QACD,OAAO;YACN,GAAG,CAAC,IAAI,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;YACjE,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;SACvC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC;IACX,CAAC;AACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"databaseLayer.d.ts","sourceRoot":"","sources":["../../src/server/databaseLayer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAU,KAAK,EAAE,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAC9D,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAI3D,OAAO,EAAE,KAAK,sBAAsB,EAAmB,MAAM,0CAA0C,CAAC;AACxG,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAEzE,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,sBAAsB,EAAE,QAAQ,GAAG,QAAQ,CAAC,CAAC;IACrE,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACjD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,OAAO,UAAU,+UA6BjG"}
1
+ {"version":3,"file":"databaseLayer.d.ts","sourceRoot":"","sources":["../../src/server/databaseLayer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAU,KAAK,EAAE,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAC9D,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAI3D,OAAO,EAAE,KAAK,sBAAsB,EAAmB,MAAM,0CAA0C,CAAC;AACxG,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAGzE,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,sBAAsB,EAAE,QAAQ,GAAG,QAAQ,CAAC,CAAC;IACrE,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACjD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,OAAO,UAAU,+UA8BjG"}
@@ -5,11 +5,13 @@ import { makeLiveServer } from "@shivaedev/effect-contract/makeLiveServer.ts";
5
5
  import { assertPostgresMigrations } from "@shivaedev/effect-sql/assertPostgresMigrations.ts";
6
6
  import { migratePostgres } from "@shivaedev/effect-sql/migrations.ts";
7
7
  import { postgresChanges } from "@shivaedev/effect-sql/postgresChanges.ts";
8
+ import { databaseAddress } from "./databaseAddress.js";
8
9
  export function databaseLayer(options, changesService) {
9
10
  const pool = Layer.unwrap(Effect.gen(function* () {
10
11
  const url = options.config?.databaseUrl ?? (yield* Config.Redacted("DATABASE_URL"));
11
12
  return PgClient.layer({
12
13
  url,
14
+ ...databaseAddress(url),
13
15
  ...(options.config?.maxConnections === undefined ? {} : { maxConnections: options.config.maxConnections }),
14
16
  ...(options.config?.applicationName === undefined ? {} : { applicationName: options.config.applicationName }),
15
17
  });
@@ -1 +1 @@
1
- {"version":3,"file":"databaseLayer.js","sourceRoot":"","sources":["../../src/server/databaseLayer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAiB,MAAM,QAAQ,CAAC;AAC9D,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,8CAA8C,CAAC;AAC9E,OAAO,EAAE,wBAAwB,EAAE,MAAM,mDAAmD,CAAC;AAC7F,OAAO,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AACtE,OAAO,EAA+B,eAAe,EAAE,MAAM,0CAA0C,CAAC;AAUxG,MAAM,UAAU,aAAa,CAAI,OAA8B,EAAE,cAAiC;IACjG,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CACxB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,EAAE,WAAW,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC;QACpF,OAAO,QAAQ,CAAC,KAAK,CAAC;YACrB,GAAG;YACH,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;YAC1G,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC;SAC7G,CAAC,CAAC;IACJ,CAAC,CAAC,CACF,CAAC;IACF,MAAM,QAAQ,GAAG,KAAK,CAAC,aAAa,CACnC,OAAO,CAAC,UAAU,CAAC,IAAI,KAAK,OAAO;QAClC,CAAC,CAAC,wBAAwB,CAAC,OAAO,CAAC,UAAU,CAAC;QAC9C,CAAC,CAAC,eAAe,CAAC,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,WAAW,EAAE,OAAO,CAAC,UAAU,CAAC,WAAW,IAAI,WAAW,EAAE,CAAC,CACzG,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;IACjC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;IACrG,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC;IACxC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC;IACb,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,CAClB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACxC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,cAAc,CAAC;QACpC,OAAO,eAAe,CAAC,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACtE,CAAC,CAAC,CACF,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;AAClC,CAAC"}
1
+ {"version":3,"file":"databaseLayer.js","sourceRoot":"","sources":["../../src/server/databaseLayer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAiB,MAAM,QAAQ,CAAC;AAC9D,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,8CAA8C,CAAC;AAC9E,OAAO,EAAE,wBAAwB,EAAE,MAAM,mDAAmD,CAAC;AAC7F,OAAO,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AACtE,OAAO,EAA+B,eAAe,EAAE,MAAM,0CAA0C,CAAC;AAExG,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AASvD,MAAM,UAAU,aAAa,CAAI,OAA8B,EAAE,cAAiC;IACjG,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CACxB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,EAAE,WAAW,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC;QACpF,OAAO,QAAQ,CAAC,KAAK,CAAC;YACrB,GAAG;YACH,GAAG,eAAe,CAAC,GAAG,CAAC;YACvB,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;YAC1G,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC;SAC7G,CAAC,CAAC;IACJ,CAAC,CAAC,CACF,CAAC;IACF,MAAM,QAAQ,GAAG,KAAK,CAAC,aAAa,CACnC,OAAO,CAAC,UAAU,CAAC,IAAI,KAAK,OAAO;QAClC,CAAC,CAAC,wBAAwB,CAAC,OAAO,CAAC,UAAU,CAAC;QAC9C,CAAC,CAAC,eAAe,CAAC,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,WAAW,EAAE,OAAO,CAAC,UAAU,CAAC,WAAW,IAAI,WAAW,EAAE,CAAC,CACzG,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;IACjC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;IACrG,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC;IACxC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC;IACb,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,CAClB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACxC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,cAAc,CAAC;QACpC,OAAO,eAAe,CAAC,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACtE,CAAC,CAAC,CACF,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;AAClC,CAAC"}
@@ -1,4 +1,4 @@
1
1
  import { Layer } from "effect";
2
2
  import { HttpClient } from "effect/http";
3
- export declare const otlpHttpClient: Layer.Layer<HttpClient.HttpClient, never, never>;
3
+ export declare function otlpHttpClient(timeout?: number): Layer.Layer<HttpClient.HttpClient, never, never>;
4
4
  //# sourceMappingURL=otlpHttpClient.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"otlpHttpClient.d.ts","sourceRoot":"","sources":["../../src/server/otlpHttpClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAmB,UAAU,EAAE,MAAM,aAAa,CAAC;AAG1D,eAAO,MAAM,cAAc,kDA8BiB,CAAC"}
1
+ {"version":3,"file":"otlpHttpClient.d.ts","sourceRoot":"","sources":["../../src/server/otlpHttpClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAmB,UAAU,EAA2C,MAAM,aAAa,CAAC;AASnG,wBAAgB,cAAc,CAAC,OAAO,SAAS,oDAyC9C"}
@@ -1,24 +1,34 @@
1
1
  import { Effect, Layer } from "effect";
2
- import { FetchHttpClient, HttpClient } from "effect/http";
2
+ import { FetchHttpClient, HttpClient, HttpClientError } from "effect/http";
3
3
  import { redactOtlpText } from "#safeOtlpSerialization.ts";
4
- export const otlpHttpClient = Layer.effect(HttpClient.HttpClient, Effect.gen(function* () {
5
- const client = yield* HttpClient.HttpClient;
6
- let warned = false;
7
- function warnOnce(diagnostic) {
8
- return Effect.suspend(() => {
9
- if (warned) {
4
+ function timeoutError(request) {
5
+ return new HttpClientError.HttpClientError({
6
+ reason: new HttpClientError.TransportError({ description: "OTLP export request timed out", request }),
7
+ });
8
+ }
9
+ export function otlpHttpClient(timeout = 10_000) {
10
+ return Layer.effect(HttpClient.HttpClient, Effect.gen(function* () {
11
+ const client = yield* HttpClient.HttpClient;
12
+ let warned = false;
13
+ function warnOnce(diagnostic) {
14
+ return Effect.suspend(() => {
15
+ if (warned) {
16
+ return Effect.void;
17
+ }
18
+ warned = true;
19
+ return Effect.flatMap(diagnostic, (annotations) => Effect.logWarning("OTLP trace export failed").pipe(Effect.annotateLogs(annotations)));
20
+ });
21
+ }
22
+ return client.pipe(HttpClient.tap((response) => response.arrayBuffer), HttpClient.transform((requestEffect, request) => requestEffect.pipe(Effect.timeoutOrElse({
23
+ duration: timeout,
24
+ orElse: () => Effect.fail(timeoutError(request)),
25
+ }))), HttpClient.tap((response) => {
26
+ if (response.status >= 200 && response.status < 300) {
27
+ warned = false;
10
28
  return Effect.void;
11
29
  }
12
- warned = true;
13
- return Effect.flatMap(diagnostic, (annotations) => Effect.logWarning("OTLP trace export failed").pipe(Effect.annotateLogs(annotations)));
14
- });
15
- }
16
- return client.pipe(HttpClient.tap((response) => {
17
- if (response.status >= 200 && response.status < 300) {
18
- warned = false;
19
- return Effect.void;
20
- }
21
- return warnOnce(response.text.pipe(Effect.orElseSucceed(() => "Unable to read collector response"), Effect.map((body) => ({ "otlp.response": redactOtlpText(body), "otlp.status": response.status }))));
22
- }), HttpClient.tapError((error) => warnOnce(Effect.succeed({ "otlp.failure": error.reason._tag }))));
23
- })).pipe(Layer.provide(FetchHttpClient.layer));
30
+ return warnOnce(response.text.pipe(Effect.orElseSucceed(() => "Unable to read collector response"), Effect.map((body) => ({ "otlp.response": redactOtlpText(body), "otlp.status": response.status }))));
31
+ }), HttpClient.tapError((error) => warnOnce(Effect.succeed({ "otlp.failure": error.reason._tag }))));
32
+ })).pipe(Layer.provide(FetchHttpClient.layer));
33
+ }
24
34
  //# sourceMappingURL=otlpHttpClient.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"otlpHttpClient.js","sourceRoot":"","sources":["../../src/server/otlpHttpClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3D,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,CAAC,MAAM,CACzC,UAAU,CAAC,UAAU,EACrB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;IAC5C,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,SAAS,QAAQ,CAAC,UAAkD;QACnE,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;YAC1B,IAAI,MAAM,EAAE,CAAC;gBACZ,OAAO,MAAM,CAAC,IAAI,CAAC;YACpB,CAAC;YACD,MAAM,GAAG,IAAI,CAAC;YACd,OAAO,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,0BAA0B,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QAC1I,CAAC,CAAC,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CACjB,UAAU,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;QAC3B,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;YACrD,MAAM,GAAG,KAAK,CAAC;YACf,OAAO,MAAM,CAAC,IAAI,CAAC;QACpB,CAAC;QACD,OAAO,QAAQ,CACd,QAAQ,CAAC,IAAI,CAAC,IAAI,CACjB,MAAM,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC,mCAAmC,CAAC,EAC/D,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,eAAe,EAAE,cAAc,CAAC,IAAI,CAAC,EAAE,aAAa,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CACjG,CACD,CAAC;IACH,CAAC,CAAC,EACF,UAAU,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,cAAc,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAC/F,CAAC;AACH,CAAC,CAAC,CACF,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"otlpHttpClient.js","sourceRoot":"","sources":["../../src/server/otlpHttpClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,eAAe,EAA0B,MAAM,aAAa,CAAC;AACnG,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3D,SAAS,YAAY,CAAC,OAA4C;IACjE,OAAO,IAAI,eAAe,CAAC,eAAe,CAAC;QAC1C,MAAM,EAAE,IAAI,eAAe,CAAC,cAAc,CAAC,EAAE,WAAW,EAAE,+BAA+B,EAAE,OAAO,EAAE,CAAC;KACrG,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAO,GAAG,MAAM;IAC9C,OAAO,KAAK,CAAC,MAAM,CAClB,UAAU,CAAC,UAAU,EACrB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;QAC5C,IAAI,MAAM,GAAG,KAAK,CAAC;QACnB,SAAS,QAAQ,CAAC,UAAkD;YACnE,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;gBAC1B,IAAI,MAAM,EAAE,CAAC;oBACZ,OAAO,MAAM,CAAC,IAAI,CAAC;gBACpB,CAAC;gBACD,MAAM,GAAG,IAAI,CAAC;gBACd,OAAO,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,0BAA0B,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;YAC1I,CAAC,CAAC,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CACjB,UAAU,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,EAClD,UAAU,CAAC,SAAS,CAAC,CAAC,aAAa,EAAE,OAAO,EAAE,EAAE,CAC/C,aAAa,CAAC,IAAI,CACjB,MAAM,CAAC,aAAa,CAAC;YACpB,QAAQ,EAAE,OAAO;YACjB,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;SAChD,CAAC,CACF,CACD,EACD,UAAU,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;YAC3B,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;gBACrD,MAAM,GAAG,KAAK,CAAC;gBACf,OAAO,MAAM,CAAC,IAAI,CAAC;YACpB,CAAC;YACD,OAAO,QAAQ,CACd,QAAQ,CAAC,IAAI,CAAC,IAAI,CACjB,MAAM,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC,mCAAmC,CAAC,EAC/D,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,eAAe,EAAE,cAAc,CAAC,IAAI,CAAC,EAAE,aAAa,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CACjG,CACD,CAAC;QACH,CAAC,CAAC,EACF,UAAU,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,cAAc,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAC/F,CAAC;IACH,CAAC,CAAC,CACF,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC;AAC9C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"otlpTracing.d.ts","sourceRoot":"","sources":["../../src/server/otlpTracing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,KAAK,EAAU,MAAM,QAAQ,CAAC;AAKvD,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,WAAW,CAsC9D,CAAC"}
1
+ {"version":3,"file":"otlpTracing.d.ts","sourceRoot":"","sources":["../../src/server/otlpTracing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,KAAK,EAAU,MAAM,QAAQ,CAAC;AAKvD,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,WAAW,CAoD9D,CAAC"}
@@ -16,6 +16,17 @@ export const otlpTracing = Layer.unwrap(Effect.gen(function* () {
16
16
  const serviceName = yield* Config.String("OTEL_SERVICE_NAME").pipe(Config.withDefault(attributes["service.name"] ?? "unknown_service"));
17
17
  const serviceVersion = yield* Config.String("OTEL_SERVICE_VERSION").pipe(Config.withDefault(undefined));
18
18
  const headers = yield* Config.Record(Schema.String, Schema.StringFromUriComponent, "OTEL_EXPORTER_OTLP_HEADERS").pipe(Config.withDefault({}));
19
- return OtlpTracer.layer({ headers, resource: { attributes, serviceName, serviceVersion }, url: endpoint.toString() }).pipe(Layer.provide(safeOtlpSerialization.pipe(Layer.provide(protocol === "http/protobuf" ? OtlpSerialization.layerProtobuf : OtlpSerialization.layerJson))), Layer.provide(otlpHttpClient));
19
+ const requestTimeout = yield* Config.Int("OTEL_EXPORTER_OTLP_TRACES_TIMEOUT").pipe(Config.orElse(() => Config.Int("OTEL_EXPORTER_OTLP_TIMEOUT")), Config.withDefault(10_000));
20
+ const exportInterval = yield* Config.Int("OTEL_BSP_SCHEDULE_DELAY").pipe(Config.withDefault(5000));
21
+ const maxBatchSize = yield* Config.Int("OTEL_BSP_MAX_EXPORT_BATCH_SIZE").pipe(Config.withDefault(1000));
22
+ const exportTimeout = yield* Config.Int("OTEL_BSP_EXPORT_TIMEOUT").pipe(Config.withDefault(3000));
23
+ return OtlpTracer.layer({
24
+ exportInterval,
25
+ headers,
26
+ maxBatchSize,
27
+ resource: { attributes, serviceName, serviceVersion },
28
+ shutdownTimeout: Math.min(exportTimeout, requestTimeout),
29
+ url: endpoint.toString(),
30
+ }).pipe(Layer.provide(safeOtlpSerialization.pipe(Layer.provide(protocol === "http/protobuf" ? OtlpSerialization.layerProtobuf : OtlpSerialization.layerJson))), Layer.provide(otlpHttpClient(requestTimeout)));
20
31
  }));
21
32
  //# sourceMappingURL=otlpTracing.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"otlpTracing.js","sourceRoot":"","sources":["../../src/server/otlpTracing.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"otlpTracing.js","sourceRoot":"","sources":["../../src/server/otlpTracing.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sensitive.d.ts","sourceRoot":"","sources":["../../../src/server/rpc-server/sensitive.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,QAAQ,eAAe,CAAC;AAwBrC,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;AAQpD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOnD;AAYD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,GAAG,MAAM,CAGxE"}
1
+ {"version":3,"file":"sensitive.d.ts","sourceRoot":"","sources":["../../../src/server/rpc-server/sensitive.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,QAAQ,eAAe,CAAC;AAwBrC,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;AAQpD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOnD;AA8BD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,GAAG,MAAM,CAGxE"}
@@ -21,7 +21,7 @@ const SENSITIVE_PARTS = [
21
21
  "xsrf",
22
22
  ];
23
23
  const SENSITIVE_WORDS = ["otp", "pin", "cvv", "cvc", "ssn", "iban"];
24
- const SENSITIVE_NAMES = ["session", "pan"];
24
+ const SENSITIVE_NAMES = ["session", "pan", "sig"];
25
25
  function wordsOf(key) {
26
26
  return key
27
27
  .replaceAll(/(?<lower>[a-z0-9])(?<upper>[A-Z])/gu, "$<lower> $<upper>")
@@ -37,14 +37,33 @@ export function isSensitiveKey(key) {
37
37
  }
38
38
  const AUTH_SCHEME = /\b(?<scheme>Bearer|Basic)\s+[A-Za-z0-9._~+/-]+=*/giu;
39
39
  const JWT = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*/gu;
40
+ const PRIVATE_KEY = /-----BEGIN (?<kind>(?:[A-Z0-9]+ )*PRIVATE KEY)-----[\s\S]*?-----END \k<kind>-----/gu;
40
41
  const URL_PASSWORD = /(?<authority>\/\/[^:/\s@]+:)[^@/\s]+@/gu;
41
- const ASSIGNMENT = /(?<key>[A-Za-z][\w.-]*)(?<separator>["']?\s*[=:]\s*["']?)(?<value>[^\s"'&,;]+)/gu;
42
+ function redactAssignments(text, sensitive) {
43
+ const matcher = /(?<key>[A-Za-z][\w.-]*)(?<separator>["']?\s*[=:]\s*["']?)(?<value>[^\s"'&,;]+)/gu;
44
+ let result = "";
45
+ let cursor = 0;
46
+ for (let match = matcher.exec(text); match !== null; match = matcher.exec(text)) {
47
+ const key = match[1] ?? "";
48
+ const separator = match[2] ?? "";
49
+ if (sensitive(key)) {
50
+ result += `${text.slice(cursor, match.index)}${key}${separator}${REDACTED}`;
51
+ cursor = matcher.lastIndex;
52
+ }
53
+ else if (match[3]?.includes("=")) {
54
+ // Revisit embedded parameters without parsing URL and source-location colons as assignments.
55
+ matcher.lastIndex = match.index + key.length + separator.length;
56
+ }
57
+ }
58
+ return result + text.slice(cursor);
59
+ }
42
60
  function scrub(text, sensitive) {
43
- return text
61
+ const credentials = text
62
+ .replaceAll(PRIVATE_KEY, REDACTED)
44
63
  .replaceAll(AUTH_SCHEME, `$<scheme> ${REDACTED}`)
45
64
  .replaceAll(JWT, REDACTED)
46
- .replaceAll(URL_PASSWORD, `$<authority>${REDACTED}@`)
47
- .replaceAll(ASSIGNMENT, (match, key, separator) => (sensitive(key) ? `${key}${separator}${REDACTED}` : match));
65
+ .replaceAll(URL_PASSWORD, `$<authority>${REDACTED}@`);
66
+ return redactAssignments(credentials, sensitive);
48
67
  }
49
68
  export function redactText(text, sensitive) {
50
69
  const scrubbed = scrub(text, sensitive);
@@ -1 +1 @@
1
- {"version":3,"file":"sensitive.js","sourceRoot":"","sources":["../../../src/server/rpc-server/sensitive.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,QAAQ,GAAG,YAAY,CAAC;AACrC,MAAM,UAAU,GAAG,IAAI,CAAC;AACxB,MAAM,eAAe,GAAG;IACvB,UAAU;IACV,YAAY;IACZ,QAAQ;IACR,OAAO;IACP,eAAe;IACf,QAAQ;IACR,QAAQ;IACR,WAAW;IACX,YAAY;IACZ,YAAY;IACZ,KAAK;IACL,QAAQ;IACR,WAAW;IACX,WAAW;IACX,YAAY;IACZ,eAAe;IACf,MAAM;IACN,MAAM;CACN,CAAC;AACF,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;AACpE,MAAM,eAAe,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;AAE3C,SAAS,OAAO,CAAC,GAAW;IAC3B,OAAO,GAAG;SACR,UAAU,CAAC,qCAAqC,EAAE,mBAAmB,CAAC;SACtE,UAAU,CAAC,wCAAwC,EAAE,oBAAoB,CAAC;SAC1E,WAAW,EAAE;SACb,KAAK,CAAC,UAAU,CAAC,CAAC;AACrB,CAAC;AACD,MAAM,UAAU,cAAc,CAAC,GAAW;IACzC,MAAM,UAAU,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC9D,OAAO,CACN,eAAe,CAAC,QAAQ,CAAC,UAAU,CAAC;WACjC,eAAe,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;WACzD,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAC9D,CAAC;AACH,CAAC;AACD,MAAM,WAAW,GAAG,qDAAqD,CAAC;AAC1E,MAAM,GAAG,GAAG,uDAAuD,CAAC;AACpE,MAAM,YAAY,GAAG,yCAAyC,CAAC;AAC/D,MAAM,UAAU,GAAG,kFAAkF,CAAC;AACtG,SAAS,KAAK,CAAC,IAAY,EAAE,SAAuB;IACnD,OAAO,IAAI;SACT,UAAU,CAAC,WAAW,EAAE,aAAa,QAAQ,EAAE,CAAC;SAChD,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC;SACzB,UAAU,CAAC,YAAY,EAAE,eAAe,QAAQ,GAAG,CAAC;SACpD,UAAU,CAAC,UAAU,EAAE,CAAC,KAAa,EAAE,GAAW,EAAE,SAAiB,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,SAAS,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;AACzI,CAAC;AACD,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,SAAuB;IAC/D,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACxC,OAAO,QAAQ,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,KAAK,QAAQ,CAAC,MAAM,GAAG,UAAU,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC;AAClI,CAAC"}
1
+ {"version":3,"file":"sensitive.js","sourceRoot":"","sources":["../../../src/server/rpc-server/sensitive.ts"],"names":[],"mappings":"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"}
package/docs/roadmap.md CHANGED
@@ -9,22 +9,23 @@ This file owns Platform's module implementation, package tests and scope questio
9
9
  - [x] Shared Schema tagged errors and field-rejection extraction, with native RPC JSON and tRPC rejection tests.
10
10
  - [x] Browser-safe native RPC middleware declarations and request-id, identity and optional-identity services, with compile-time checks on guarded handlers.
11
11
  - [x] Request-local session middleware, a Better Auth session-provider boundary and an explicit Origin policy. Tests cover concurrent identities, anonymous callers, provider failures and rejection before provider lookup.
12
- - [x] HTTP transport-header authentication and a WebSocket protocol fixture that uses upgrade-request headers instead of RPC message credentials.
13
- - [x] Request-id log/span annotations and redaction of failure payloads, defects, reported causes and credential-shaped strings, with bounded collection and string output.
14
- - [x] An application runtime that acquires its Layer once, shares an explicit development cache key and carries service overrides across Promise calls.
12
+ - [x] HTTP transport-header authentication, direct tests of the RPC-header fallback and HTTP-header precedence, and a WebSocket protocol fixture that uses upgrade-request headers instead of RPC message credentials. [Header selection evidence](https://github.com/ShivaeDev/platform/blob/main/packages/platform/src/server/rpc-server/transportHeaders.test.ts).
13
+ - [x] Request-id log/span annotations and redaction of failure payloads, defects, reported causes and credential-shaped strings, with bounded collection and string output. [Tracing tests](https://github.com/ShivaeDev/platform/blob/main/packages/platform/src/server/rpc-server/tracing.test.ts) cover successful requests and interruption without failure logs.
14
+ - [x] An application runtime that acquires its Layer once, releases it exactly once, shares an explicit development cache key and recreates disposed cache entries, bypasses caching in production, and carries service overrides across Promise calls. [Runtime tests](https://github.com/ShivaeDev/platform/blob/main/packages/platform/src/server/runtime/makePlatform.test.ts) cover typed Layer build failures, Promise rejection, signal interruption with finalization, and nested override replacement and restoration.
15
15
  - [x] Node subscription cancellation from Web/procedure signals and simulated request/socket close events, with listener cleanup.
16
16
  - [x] A Better Auth Effect Prisma database adapter and Prisma/tRPC rollback harness. Real PostgreSQL tests cover shared transactions, failed transaction rollback, selected row operations and harness types; they require `PLATFORM_EFFECT_PRISMA_TEST_DATABASE_URL`.
17
+ - [x] Better Auth adapter filters preserve PostgreSQL null, membership, comparison and literal-pattern semantics, including nonunique updates, OR-filter updates and deletes, and rejection of nonstring patterns. Configured model names map by default. Reads retain their filters through selection, ascending/descending ordering and limit/offset pagination; experimental native joins are rejected for both single and collection reads. [Adapter boundary tests](../src/server/better-auth/filterBoundaries.spec.ts) assert these paths against PostgreSQL.
17
18
  - [x] Packed-consumer checks for defining-module imports, types and core entries installed without optional peers.
18
19
  - [x] A local app host Layer and reusable database Layer, extracted from the PostgreSQL composition: migrations before services, guarded NDJSON RPC, session policy, static assets, scoped shutdown and commit-bound live delivery. The composed fixture covers two HTTP server processes and a worker. Fleet’s daemon adopts `appHost` and `appDatabase` with a custom RPC route, session-free loopback policy and its existing migration ledger. Platform’s real HTTP option tests cover hosting without a database, while Fleet’s PostgreSQL daemon tests cover pool limits, migration upgrades and existing clients.
19
20
 
20
21
  ## Next
21
22
 
22
- - [ ] Add focused runtime tests for production cache bypass, disposal and recreation, build failures, exit results and cancellation before documenting stronger lifecycle guarantees.
23
- - [ ] Extend adapter tests to the remaining public options and filter, selection, ordering and pagination paths. Add a regression test for the rejection of experimental native joins.
24
- - [ ] Exercise the no-HTTP-request `transportHeaders` fallback directly and the tracing boundary's successful and interrupted paths.
23
+ - [ ] Add focused adapter tests for the remaining public options, `debugLogs` and `usePlural`.
25
24
 
26
25
  ## Open questions
27
26
 
27
+ - What comprehensive application redaction policy should cover span names, free text and custom attributes, and how should request/action correlation work across boundaries?
28
+
28
29
  - Should the package keep the optional Better Auth storage adapter and Prisma/tRPC harness beside its native request-context modules, or should those compositions live with the integration packages? Both serve real boundary work, but they give this package a wider surface than its request-identity job alone.
29
30
  - If an application needs Better Auth storage on native Effect SQL, should Platform own that adapter or should the provider retain separate storage? The request middleware needs a session provider, not a particular database.
30
31
  - The exports make Better Auth translation helpers importable beside the adapter. Are these helpers intended as supported consumer APIs, or should a future public API change narrow the surface to the adapter?
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaedev/platform",
3
- "version": "0.14.1",
3
+ "version": "0.14.3",
4
4
  "description": "Opinionated Effect application infrastructure for ShivaeDev services",
5
5
  "homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/platform#readme",
6
6
  "bugs": {
@@ -386,12 +386,13 @@
386
386
  "@prisma-next/sql-contract": "0.16.0",
387
387
  "@prisma-next/sql-orm-client": "0.16.0",
388
388
  "@prisma-next/target-postgres": "0.16.0",
389
- "@shivaedev/effect-prisma": "0.9.1",
389
+ "@shivaedev/effect-prisma": "0.9.2",
390
390
  "@shivaedev/effect-trpc": "0.7.1",
391
- "@shivaedev/quality": "0.22.0",
391
+ "@shivaedev/quality": "0.23.0",
392
+ "@shivaedev/test-story": "0.6.0",
392
393
  "@trpc/client": "11.19.0",
393
394
  "@trpc/server": "11.19.0",
394
- "@types/node": "24.19.1",
395
+ "@types/node": "24.19.2",
395
396
  "@typescript/native": "npm:typescript@7.0.2",
396
397
  "better-auth": "1.7.7",
397
398
  "effect": "4.0.4",
@@ -404,7 +405,7 @@
404
405
  "@effect/sql-pg": "4.0.4",
405
406
  "@effect/vitest": "4.0.4",
406
407
  "@prisma-next/sql-orm-client": "0.16.0",
407
- "@shivaedev/effect-prisma": "0.9.1",
408
+ "@shivaedev/effect-prisma": "0.9.2",
408
409
  "@shivaedev/effect-trpc": "0.7.1",
409
410
  "@trpc/server": "11.19.0",
410
411
  "better-auth": "1.7.7",
@@ -0,0 +1,25 @@
1
+ import type { PgPoolConfig } from "@effect/sql-pg/PgClient";
2
+ import { Redacted } from "effect";
3
+
4
+ export function databaseAddress(value: Redacted.Redacted<string>): Pick<PgPoolConfig, "host" | "port"> {
5
+ try {
6
+ const url = new URL(Redacted.value(value));
7
+ if (url.protocol !== "postgres:" && url.protocol !== "postgresql:") {
8
+ return {};
9
+ }
10
+ const hostname = url.hostname.startsWith("[") ? url.hostname.slice(1, -1) : decodeURIComponent(url.hostname);
11
+ // PgConnection applies query parameters in order, after the authority.
12
+ const host = url.searchParams.getAll("host").at(-1) ?? hostname;
13
+ const rawPort = url.searchParams.getAll("port").at(-1) ?? url.port;
14
+ const port = rawPort === "" ? undefined : Number(rawPort);
15
+ if (host.includes(",") || (port !== undefined && (!Number.isInteger(port) || port < 1 || port > 65_535))) {
16
+ return {};
17
+ }
18
+ return {
19
+ ...(host === "" && !url.searchParams.has("host") ? {} : { host }),
20
+ ...(port === undefined ? {} : { port }),
21
+ };
22
+ } catch {
23
+ return {};
24
+ }
25
+ }
@@ -6,6 +6,7 @@ import { assertPostgresMigrations } from "@shivaedev/effect-sql/assertPostgresMi
6
6
  import { migratePostgres } from "@shivaedev/effect-sql/migrations.ts";
7
7
  import { type PostgresChangesOptions, postgresChanges } from "@shivaedev/effect-sql/postgresChanges.ts";
8
8
  import type { AppChanges, AppDatabaseOptions } from "#server/appHost.ts";
9
+ import { databaseAddress } from "./databaseAddress.ts";
9
10
 
10
11
  export interface DatabaseConfig {
11
12
  readonly applicationName?: string;
@@ -20,6 +21,7 @@ export function databaseLayer<R>(options: AppDatabaseOptions<R>, changesService:
20
21
  const url = options.config?.databaseUrl ?? (yield* Config.Redacted("DATABASE_URL"));
21
22
  return PgClient.layer({
22
23
  url,
24
+ ...databaseAddress(url),
23
25
  ...(options.config?.maxConnections === undefined ? {} : { maxConnections: options.config.maxConnections }),
24
26
  ...(options.config?.applicationName === undefined ? {} : { applicationName: options.config.applicationName }),
25
27
  });
@@ -1,35 +1,52 @@
1
1
  import { Effect, Layer } from "effect";
2
- import { FetchHttpClient, HttpClient } from "effect/http";
2
+ import { FetchHttpClient, HttpClient, HttpClientError, type HttpClientRequest } from "effect/http";
3
3
  import { redactOtlpText } from "#safeOtlpSerialization.ts";
4
4
 
5
- export const otlpHttpClient = Layer.effect(
6
- HttpClient.HttpClient,
7
- Effect.gen(function* () {
8
- const client = yield* HttpClient.HttpClient;
9
- let warned = false;
10
- function warnOnce(diagnostic: Effect.Effect<Record<string, unknown>>) {
11
- return Effect.suspend(() => {
12
- if (warned) {
13
- return Effect.void;
14
- }
15
- warned = true;
16
- return Effect.flatMap(diagnostic, (annotations) => Effect.logWarning("OTLP trace export failed").pipe(Effect.annotateLogs(annotations)));
17
- });
18
- }
19
- return client.pipe(
20
- HttpClient.tap((response) => {
21
- if (response.status >= 200 && response.status < 300) {
22
- warned = false;
23
- return Effect.void;
24
- }
25
- return warnOnce(
26
- response.text.pipe(
27
- Effect.orElseSucceed(() => "Unable to read collector response"),
28
- Effect.map((body) => ({ "otlp.response": redactOtlpText(body), "otlp.status": response.status })),
5
+ function timeoutError(request: HttpClientRequest.HttpClientRequest) {
6
+ return new HttpClientError.HttpClientError({
7
+ reason: new HttpClientError.TransportError({ description: "OTLP export request timed out", request }),
8
+ });
9
+ }
10
+
11
+ export function otlpHttpClient(timeout = 10_000) {
12
+ return Layer.effect(
13
+ HttpClient.HttpClient,
14
+ Effect.gen(function* () {
15
+ const client = yield* HttpClient.HttpClient;
16
+ let warned = false;
17
+ function warnOnce(diagnostic: Effect.Effect<Record<string, unknown>>) {
18
+ return Effect.suspend(() => {
19
+ if (warned) {
20
+ return Effect.void;
21
+ }
22
+ warned = true;
23
+ return Effect.flatMap(diagnostic, (annotations) => Effect.logWarning("OTLP trace export failed").pipe(Effect.annotateLogs(annotations)));
24
+ });
25
+ }
26
+ return client.pipe(
27
+ HttpClient.tap((response) => response.arrayBuffer),
28
+ HttpClient.transform((requestEffect, request) =>
29
+ requestEffect.pipe(
30
+ Effect.timeoutOrElse({
31
+ duration: timeout,
32
+ orElse: () => Effect.fail(timeoutError(request)),
33
+ }),
29
34
  ),
30
- );
31
- }),
32
- HttpClient.tapError((error) => warnOnce(Effect.succeed({ "otlp.failure": error.reason._tag }))),
33
- );
34
- }),
35
- ).pipe(Layer.provide(FetchHttpClient.layer));
35
+ ),
36
+ HttpClient.tap((response) => {
37
+ if (response.status >= 200 && response.status < 300) {
38
+ warned = false;
39
+ return Effect.void;
40
+ }
41
+ return warnOnce(
42
+ response.text.pipe(
43
+ Effect.orElseSucceed(() => "Unable to read collector response"),
44
+ Effect.map((body) => ({ "otlp.response": redactOtlpText(body), "otlp.status": response.status })),
45
+ ),
46
+ );
47
+ }),
48
+ HttpClient.tapError((error) => warnOnce(Effect.succeed({ "otlp.failure": error.reason._tag }))),
49
+ );
50
+ }),
51
+ ).pipe(Layer.provide(FetchHttpClient.layer));
52
+ }
@@ -34,11 +34,25 @@ export const otlpTracing: Layer.Layer<never, Config.ConfigError> = Layer.unwrap(
34
34
  const serviceName = yield* Config.String("OTEL_SERVICE_NAME").pipe(Config.withDefault(attributes["service.name"] ?? "unknown_service"));
35
35
  const serviceVersion = yield* Config.String("OTEL_SERVICE_VERSION").pipe(Config.withDefault(undefined));
36
36
  const headers = yield* Config.Record(Schema.String, Schema.StringFromUriComponent, "OTEL_EXPORTER_OTLP_HEADERS").pipe(Config.withDefault({}));
37
- return OtlpTracer.layer({ headers, resource: { attributes, serviceName, serviceVersion }, url: endpoint.toString() }).pipe(
37
+ const requestTimeout = yield* Config.Int("OTEL_EXPORTER_OTLP_TRACES_TIMEOUT").pipe(
38
+ Config.orElse(() => Config.Int("OTEL_EXPORTER_OTLP_TIMEOUT")),
39
+ Config.withDefault(10_000),
40
+ );
41
+ const exportInterval = yield* Config.Int("OTEL_BSP_SCHEDULE_DELAY").pipe(Config.withDefault(5000));
42
+ const maxBatchSize = yield* Config.Int("OTEL_BSP_MAX_EXPORT_BATCH_SIZE").pipe(Config.withDefault(1000));
43
+ const exportTimeout = yield* Config.Int("OTEL_BSP_EXPORT_TIMEOUT").pipe(Config.withDefault(3000));
44
+ return OtlpTracer.layer({
45
+ exportInterval,
46
+ headers,
47
+ maxBatchSize,
48
+ resource: { attributes, serviceName, serviceVersion },
49
+ shutdownTimeout: Math.min(exportTimeout, requestTimeout),
50
+ url: endpoint.toString(),
51
+ }).pipe(
38
52
  Layer.provide(
39
53
  safeOtlpSerialization.pipe(Layer.provide(protocol === "http/protobuf" ? OtlpSerialization.layerProtobuf : OtlpSerialization.layerJson)),
40
54
  ),
41
- Layer.provide(otlpHttpClient),
55
+ Layer.provide(otlpHttpClient(requestTimeout)),
42
56
  );
43
57
  }),
44
58
  );
@@ -21,7 +21,7 @@ const SENSITIVE_PARTS = [
21
21
  "xsrf",
22
22
  ];
23
23
  const SENSITIVE_WORDS = ["otp", "pin", "cvv", "cvc", "ssn", "iban"];
24
- const SENSITIVE_NAMES = ["session", "pan"];
24
+ const SENSITIVE_NAMES = ["session", "pan", "sig"];
25
25
  export type SensitiveKey = (key: string) => boolean;
26
26
  function wordsOf(key: string): string[] {
27
27
  return key
@@ -40,14 +40,32 @@ export function isSensitiveKey(key: string): boolean {
40
40
  }
41
41
  const AUTH_SCHEME = /\b(?<scheme>Bearer|Basic)\s+[A-Za-z0-9._~+/-]+=*/giu;
42
42
  const JWT = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*/gu;
43
+ const PRIVATE_KEY = /-----BEGIN (?<kind>(?:[A-Z0-9]+ )*PRIVATE KEY)-----[\s\S]*?-----END \k<kind>-----/gu;
43
44
  const URL_PASSWORD = /(?<authority>\/\/[^:/\s@]+:)[^@/\s]+@/gu;
44
- const ASSIGNMENT = /(?<key>[A-Za-z][\w.-]*)(?<separator>["']?\s*[=:]\s*["']?)(?<value>[^\s"'&,;]+)/gu;
45
+ function redactAssignments(text: string, sensitive: SensitiveKey): string {
46
+ const matcher = /(?<key>[A-Za-z][\w.-]*)(?<separator>["']?\s*[=:]\s*["']?)(?<value>[^\s"'&,;]+)/gu;
47
+ let result = "";
48
+ let cursor = 0;
49
+ for (let match = matcher.exec(text); match !== null; match = matcher.exec(text)) {
50
+ const key = match[1] ?? "";
51
+ const separator = match[2] ?? "";
52
+ if (sensitive(key)) {
53
+ result += `${text.slice(cursor, match.index)}${key}${separator}${REDACTED}`;
54
+ cursor = matcher.lastIndex;
55
+ } else if (match[3]?.includes("=")) {
56
+ // Revisit embedded parameters without parsing URL and source-location colons as assignments.
57
+ matcher.lastIndex = match.index + key.length + separator.length;
58
+ }
59
+ }
60
+ return result + text.slice(cursor);
61
+ }
45
62
  function scrub(text: string, sensitive: SensitiveKey): string {
46
- return text
63
+ const credentials = text
64
+ .replaceAll(PRIVATE_KEY, REDACTED)
47
65
  .replaceAll(AUTH_SCHEME, `$<scheme> ${REDACTED}`)
48
66
  .replaceAll(JWT, REDACTED)
49
- .replaceAll(URL_PASSWORD, `$<authority>${REDACTED}@`)
50
- .replaceAll(ASSIGNMENT, (match: string, key: string, separator: string) => (sensitive(key) ? `${key}${separator}${REDACTED}` : match));
67
+ .replaceAll(URL_PASSWORD, `$<authority>${REDACTED}@`);
68
+ return redactAssignments(credentials, sensitive);
51
69
  }
52
70
  export function redactText(text: string, sensitive: SensitiveKey): string {
53
71
  const scrubbed = scrub(text, sensitive);