veryfront 0.1.746 → 0.1.748

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/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.746",
3
+ "version": "0.1.748",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "minimumDependencyAge": "P2D",
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/src/cache/backends/api.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA0BhD,qBAAa,eAAgB,YAAW,YAAY;IAClD,QAAQ,CAAC,IAAI,EAAG,KAAK,CAAU;IAC/B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,cAAc,CAAC;gBAGrB,OAAO,GAAE;QACP,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;KACxB;IAgBR,OAAO,CAAC,SAAS;YAIH,OAAO;IA2Ff,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAQxC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;YAuBrD,eAAe;IAKvB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,SAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQhE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAYrF,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/B,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAMrD"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/src/cache/backends/api.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA2BhD,qBAAa,eAAgB,YAAW,YAAY;IAClD,QAAQ,CAAC,IAAI,EAAG,KAAK,CAAU;IAC/B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,cAAc,CAAC;gBAGrB,OAAO,GAAE;QACP,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;KACxB;IAgBR,OAAO,CAAC,SAAS;YAIH,OAAO;IA8Ff,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAQxC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;YAuBrD,eAAe;IAKvB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,SAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQhE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAYrF,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/B,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAMrD"}
@@ -7,6 +7,7 @@ import { CircuitBreakerOpen, getCircuitBreaker } from "../../utils/circuit-break
7
7
  import { getEnvValue } from "./helpers.js";
8
8
  import { buildBatchResults } from "../batch-results.js";
9
9
  import { REQUEST_ERROR } from "../../errors/index.js";
10
+ import { sanitizeUrlForSpan } from "../../utils/logger/redact.js";
10
11
  const logger = baseLogger.component("api-cache-backend");
11
12
  const DEFAULT_TIMEOUT_MS = 10_000;
12
13
  const CIRCUIT_BREAKER_RESET_TIMEOUT_MS = 15_000;
@@ -56,7 +57,10 @@ export class ApiCacheBackend {
56
57
  }
57
58
  try {
58
59
  return await this.circuitBreaker.execute(async () => {
59
- const url = `${this.apiBaseUrl}/projects/${projectRef}/cache${path}`;
60
+ const encodedProjectRef = encodeURIComponent(projectRef);
61
+ const url = `${this.apiBaseUrl}/projects/${encodedProjectRef}/cache${path}`;
62
+ const spanUrl = sanitizeUrlForSpan(url);
63
+ const cacheOperation = sanitizeUrlForSpan(path);
60
64
  const controller = new AbortController();
61
65
  const timeoutId = dntShim.setTimeout(() => controller.abort(), this.timeoutMs);
62
66
  try {
@@ -70,9 +74,9 @@ export class ApiCacheBackend {
70
74
  signal: controller.signal,
71
75
  }), {
72
76
  "http.method": method,
73
- "http.url": url,
77
+ "http.url": spanUrl,
74
78
  "http.host": new URL(this.apiBaseUrl).host,
75
- "cache.operation": path,
79
+ "cache.operation": cacheOperation,
76
80
  "cache.project_slug": projectRef,
77
81
  });
78
82
  if (!response.ok) {
@@ -100,7 +104,7 @@ export class ApiCacheBackend {
100
104
  catch (error) {
101
105
  if (error instanceof CircuitBreakerOpen) {
102
106
  logger.info("Circuit breaker open, failing fast", {
103
- path,
107
+ path: sanitizeUrlForSpan(path),
104
108
  nextAttemptMs: error.nextAttemptMs,
105
109
  });
106
110
  return null;
@@ -108,7 +112,7 @@ export class ApiCacheBackend {
108
112
  const isTimeout = error instanceof Error && error.name === "AbortError";
109
113
  const errorMsg = error instanceof Error ? error.message : String(error);
110
114
  logger.info(`Request ${isTimeout ? "timeout" : "error"}`, {
111
- path,
115
+ path: sanitizeUrlForSpan(path),
112
116
  error: errorMsg,
113
117
  isTimeout,
114
118
  tokenSource,
@@ -12,6 +12,8 @@ export declare class RedisRateLimitStore implements RateLimitStore {
12
12
  private readonly keyPrefix;
13
13
  constructor(options?: RedisRateLimitOptions);
14
14
  private ensureClient;
15
+ private clearCachedClient;
16
+ private attachClientLifecycleHandlers;
15
17
  private connectClient;
16
18
  private storageKey;
17
19
  increment(key: string, windowMs: number): Promise<RateLimitEntry>;
@@ -1 +1 @@
1
- {"version":3,"file":"redis-rate-limit.d.ts","sourceRoot":"","sources":["../../../../../src/src/middleware/builtin/security/redis-rate-limit.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAcjE,4CAA4C;AAC5C,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wCAAwC;AACxC,qBAAa,mBAAoB,YAAW,cAAc;IACxD,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,aAAa,CAAqC;IAC1D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,OAAO,GAAE,qBAA0B;IAK/C,OAAO,CAAC,YAAY;YAMN,aAAa;IAmC3B,OAAO,CAAC,UAAU;IAIZ,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAqBjE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAK/B"}
1
+ {"version":3,"file":"redis-rate-limit.d.ts","sourceRoot":"","sources":["../../../../../src/src/middleware/builtin/security/redis-rate-limit.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AA4BjE,4CAA4C;AAC5C,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wCAAwC;AACxC,qBAAa,mBAAoB,YAAW,cAAc;IACxD,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,aAAa,CAAqC;IAC1D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,OAAO,GAAE,qBAA0B;IAK/C,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,6BAA6B;YAWvB,aAAa;IAiC3B,OAAO,CAAC,UAAU;IAIZ,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAcjE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAK/B"}
@@ -1,6 +1,15 @@
1
1
  import { createError, toError } from "../../../errors/veryfront-error.js";
2
2
  import { serverLogger } from "../../../utils/index.js";
3
3
  const logger = serverLogger.component("redis-ratelimit");
4
+ const INCREMENT_WITH_TTL_SCRIPT = `
5
+ local count = redis.call("INCR", KEYS[1])
6
+ local ttl = redis.call("PTTL", KEYS[1])
7
+ if ttl < 0 then
8
+ redis.call("PEXPIRE", KEYS[1], ARGV[1])
9
+ ttl = tonumber(ARGV[1])
10
+ end
11
+ return { count, ttl }
12
+ `;
4
13
  /** Implement redis rate limit store. */
5
14
  export class RedisRateLimitStore {
6
15
  client = null;
@@ -17,6 +26,19 @@ export class RedisRateLimitStore {
17
26
  this.clientPromise ??= this.connectClient();
18
27
  return this.clientPromise;
19
28
  }
29
+ clearCachedClient() {
30
+ this.client = null;
31
+ this.clientPromise = null;
32
+ }
33
+ attachClientLifecycleHandlers(client) {
34
+ client.on?.("error", (err) => {
35
+ logger.error("client error", err);
36
+ this.clearCachedClient();
37
+ });
38
+ client.on?.("end", () => {
39
+ this.clearCachedClient();
40
+ });
41
+ }
20
42
  async connectClient() {
21
43
  let createClient;
22
44
  try {
@@ -34,11 +56,10 @@ export class RedisRateLimitStore {
34
56
  }
35
57
  try {
36
58
  const client = createClient({ url: this.url });
37
- client.on?.("error", (err) => {
38
- logger.error("client error", err);
39
- });
59
+ this.attachClientLifecycleHandlers(client);
40
60
  await client.connect();
41
61
  this.client = client;
62
+ this.clientPromise = null;
42
63
  return client;
43
64
  }
44
65
  catch (error) {
@@ -52,15 +73,10 @@ export class RedisRateLimitStore {
52
73
  async increment(key, windowMs) {
53
74
  const client = await this.ensureClient();
54
75
  const redisKey = this.storageKey(key);
55
- const count = await client.incr(redisKey);
56
- if (count === 1) {
57
- await client.pExpire(redisKey, windowMs);
58
- }
59
- const pttl = await client.pTTL(redisKey);
60
- if (pttl === -1) {
61
- await client.pExpire(redisKey, windowMs);
62
- return { count, resetAt: Date.now() + windowMs };
63
- }
76
+ const [count, pttl] = parseIncrementResult(await client.eval(INCREMENT_WITH_TTL_SCRIPT, {
77
+ keys: [redisKey],
78
+ arguments: [String(windowMs)],
79
+ }));
64
80
  const ttl = pttl > 0 ? pttl : windowMs;
65
81
  return { count, resetAt: Date.now() + ttl };
66
82
  }
@@ -72,6 +88,23 @@ export class RedisRateLimitStore {
72
88
  if (!this.client)
73
89
  return;
74
90
  await this.client.disconnect();
75
- this.client = null;
91
+ this.clearCachedClient();
92
+ }
93
+ }
94
+ function parseIncrementResult(result) {
95
+ if (!Array.isArray(result) || result.length < 2) {
96
+ throw toError(createError({
97
+ type: "config",
98
+ message: "Redis rate limit eval returned an invalid result.",
99
+ }));
100
+ }
101
+ const count = Number(result[0]);
102
+ const ttl = Number(result[1]);
103
+ if (!Number.isFinite(count) || !Number.isFinite(ttl)) {
104
+ throw toError(createError({
105
+ type: "config",
106
+ message: "Redis rate limit eval returned non-numeric values.",
107
+ }));
76
108
  }
109
+ return [count, ttl];
77
110
  }
@@ -1 +1 @@
1
- {"version":3,"file":"http-instrumentation.d.ts","sourceRoot":"","sources":["../../../../src/src/observability/auto-instrument/http-instrumentation.ts"],"names":[],"mappings":"AAiCA,mCAAmC;AACnC,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,GAC1D,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAiCzC;AACD,2EAA2E;AAC3E,wBAAgB,uBAAuB,CACrC,SAAS,GAAE,OAAO,KAAwB,GACzC,OAAO,KAAK,CAqDd"}
1
+ {"version":3,"file":"http-instrumentation.d.ts","sourceRoot":"","sources":["../../../../src/src/observability/auto-instrument/http-instrumentation.ts"],"names":[],"mappings":"AAoCA,mCAAmC;AACnC,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,GAC1D,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAiCzC;AACD,2EAA2E;AAC3E,wBAAgB,uBAAuB,CACrC,SAAS,GAAE,OAAO,KAAwB,GACzC,OAAO,KAAK,CAsDd"}
@@ -1,7 +1,10 @@
1
1
  import { serverLogger } from "../../utils/index.js";
2
+ import { sanitizeUrlForSpan } from "../../utils/logger/redact.js";
2
3
  import { context as otContext, propagation, SpanKind, SpanStatusCode, trace, } from "../tracing/api-shim.js";
3
4
  const logger = serverLogger.component("auto-instrument");
4
- const tracer = trace.getTracer("veryfront-http");
5
+ function getHttpTracer() {
6
+ return trace.getTracer("veryfront-http");
7
+ }
5
8
  const headersGetter = {
6
9
  keys(carrier) {
7
10
  return [...carrier.keys()];
@@ -27,7 +30,7 @@ export function instrumentHttpHandler(handler) {
27
30
  const httpAttrs = buildHttpAttributes(request, url);
28
31
  const parentContext = extractParentContext(request.headers);
29
32
  try {
30
- return await tracer.startActiveSpan("http.server.request", { kind: SpanKind.SERVER, attributes: httpAttrs }, parentContext, async (span) => {
33
+ return await getHttpTracer().startActiveSpan("http.server.request", { kind: SpanKind.SERVER, attributes: httpAttrs }, parentContext, async (span) => {
31
34
  try {
32
35
  const response = await handler(request);
33
36
  recordResponseSuccess(span, response, performance.now() - startTime, httpAttrs);
@@ -54,10 +57,11 @@ export function createInstrumentedFetch(baseFetch = globalThis.fetch) {
54
57
  const startTime = performance.now();
55
58
  const urlString = extractFetchUrl(input);
56
59
  const method = init?.method ?? "GET";
60
+ const spanUrl = sanitizeUrlForSpan(urlString);
57
61
  const fetchAttrs = {
58
62
  "http.method": method,
59
- "http.url": urlString,
60
- "http.target": urlString,
63
+ "http.url": spanUrl,
64
+ "http.target": spanUrl,
61
65
  "http.host": "",
62
66
  "http.scheme": "",
63
67
  };
@@ -71,7 +75,7 @@ export function createInstrumentedFetch(baseFetch = globalThis.fetch) {
71
75
  /* expected: relative URLs cannot be parsed, leave defaults */
72
76
  }
73
77
  try {
74
- return await tracer.startActiveSpan("http.client.fetch", { kind: SpanKind.CLIENT, attributes: fetchAttrs }, async (span) => {
78
+ return await getHttpTracer().startActiveSpan("http.client.fetch", { kind: SpanKind.CLIENT, attributes: fetchAttrs }, async (span) => {
75
79
  try {
76
80
  const headers = new Headers(init?.headers);
77
81
  propagation.inject(otContext.active(), headers, {
@@ -99,7 +103,7 @@ export function createInstrumentedFetch(baseFetch = globalThis.fetch) {
99
103
  function buildHttpAttributes(request, url) {
100
104
  return {
101
105
  "http.method": request.method,
102
- "http.url": request.url,
106
+ "http.url": sanitizeUrlForSpan(request.url),
103
107
  "http.target": url.pathname,
104
108
  "http.host": url.host,
105
109
  "http.scheme": url.protocol.replace(":", ""),
@@ -50,6 +50,16 @@ export declare function redactSensitive<T>(context: T): T;
50
50
  * Non-URL strings pass through unchanged.
51
51
  */
52
52
  export declare function sanitizeUrlCredentials(input: string): string;
53
+ /**
54
+ * Return the URL form safe to attach to observability span attributes.
55
+ *
56
+ * Span attributes bypass the logger's structured redaction pass, so `http.url`
57
+ * must not include query strings, fragments, or URL userinfo. This intentionally
58
+ * strips every query parameter instead of selectively redacting credential-like
59
+ * names because cache keys and callback state can be sensitive even when the
60
+ * parameter name is not obviously a credential.
61
+ */
62
+ export declare function sanitizeUrlForSpan(input: string): string;
53
63
  /**
54
64
  * Apply {@link sanitizeUrlCredentials} to the `message` and `stack` of a
55
65
  * serialized-error-shaped object, returning a new object. Used by the logger's
@@ -1 +1 @@
1
- {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../../../src/src/utils/logger/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,6DAA6D;AAC7D,eAAO,MAAM,QAAQ,eAAe,CAAC;AA2CrC;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGnD;AA6ED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAEhD;AA2BD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CA4B5D;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,CAAC,SAAS;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,EAC5D,KAAK,EAAE,CAAC,GAAG,CAAC,CAMb"}
1
+ {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../../../src/src/utils/logger/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,6DAA6D;AAC7D,eAAO,MAAM,QAAQ,eAAe,CAAC;AA2CrC;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGnD;AA6ED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAEhD;AA2BD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CA4B5D;AAqBD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAgBxD;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,CAAC,SAAS;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,EAC5D,KAAK,EAAE,CAAC,GAAG,CAAC,CAMb"}
@@ -216,6 +216,53 @@ export function sanitizeUrlCredentials(input) {
216
216
  });
217
217
  return out;
218
218
  }
219
+ function firstUrlDelimiterIndex(input) {
220
+ const queryIndex = input.indexOf("?");
221
+ const hashIndex = input.indexOf("#");
222
+ if (queryIndex === -1)
223
+ return hashIndex;
224
+ if (hashIndex === -1)
225
+ return queryIndex;
226
+ return Math.min(queryIndex, hashIndex);
227
+ }
228
+ function sanitizeProtocolRelativeUrlForSpan(input) {
229
+ if (!input.startsWith("//"))
230
+ return null;
231
+ try {
232
+ const url = new URL(`https:${input}`);
233
+ return `//${url.host}${url.pathname}`;
234
+ }
235
+ catch (_) {
236
+ return null;
237
+ }
238
+ }
239
+ /**
240
+ * Return the URL form safe to attach to observability span attributes.
241
+ *
242
+ * Span attributes bypass the logger's structured redaction pass, so `http.url`
243
+ * must not include query strings, fragments, or URL userinfo. This intentionally
244
+ * strips every query parameter instead of selectively redacting credential-like
245
+ * names because cache keys and callback state can be sensitive even when the
246
+ * parameter name is not obviously a credential.
247
+ */
248
+ export function sanitizeUrlForSpan(input) {
249
+ if (typeof input !== "string" || input.length === 0)
250
+ return input;
251
+ try {
252
+ const url = new URL(input);
253
+ if (url.origin !== "null")
254
+ return `${url.origin}${url.pathname}`;
255
+ }
256
+ catch (_) {
257
+ // Relative or malformed URL-shaped strings are handled by the fallback.
258
+ }
259
+ const delimiterIndex = firstUrlDelimiterIndex(input);
260
+ const withoutQueryOrFragment = delimiterIndex === -1 ? input : input.slice(0, delimiterIndex);
261
+ const protocolRelativeUrl = sanitizeProtocolRelativeUrlForSpan(withoutQueryOrFragment);
262
+ if (protocolRelativeUrl)
263
+ return protocolRelativeUrl;
264
+ return sanitizeUrlCredentials(withoutQueryOrFragment);
265
+ }
219
266
  /**
220
267
  * Apply {@link sanitizeUrlCredentials} to the `message` and `stack` of a
221
268
  * serialized-error-shaped object, returning a new object. Used by the logger's
@@ -1,3 +1,3 @@
1
1
  /** Shared version value. */
2
- export declare const VERSION = "0.1.746";
2
+ export declare const VERSION = "0.1.748";
3
3
  //# sourceMappingURL=version-constant.d.ts.map
@@ -1,4 +1,4 @@
1
1
  // Keep in sync with deno.json version.
2
2
  // scripts/release.ts updates this constant during releases.
3
3
  /** Shared version value. */
4
- export const VERSION = "0.1.746";
4
+ export const VERSION = "0.1.748";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.746",
3
+ "version": "0.1.748",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",