@reliantlabs/forge-web-runtime 0.12.0 → 0.13.1

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 +1 @@
1
- {"version":3,"file":"interceptors.js","sourceRoot":"","sources":["../src/interceptors.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,EAAE;AACF,qEAAqE;AACrE,gFAAgF;AAChF,0EAA0E;AAC1E,wEAAwE;AACxE,2EAA2E;AAC3E,0EAA0E;AAC1E,4EAA4E;AAC5E,yEAAyE;AACzE,6DAA6D;AAC7D,+BAA+B;AAC/B,EAAE;AACF,uEAAuE;AACvE,mEAAmE;AACnE,2EAA2E;AAC3E,UAAU;AACV,EAAE;AACF,yDAAyD;AACzD,EAAE;AACF,4DAA4D;AAC5D,oDAAoD;AACpD,EAAE;AACF,sDAAsD;AACtD,6DAA6D;AAC7D,qEAAqE;AACrE,0EAA0E;AAC1E,wEAAwE;AACxE,wEAAwE;AACxE,2EAA2E;AAC3E,sEAAsE;AACtE,4EAA4E;AAC5E,2EAA2E;AAC3E,eAAe;AACf,OAAO,EAAE,8BAA8B,EAAE,MAAM,wBAAwB,CAAC;AAExE,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA0B7D,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAE1E,8DAA8D;AAC9D,MAAM,UAAU,eAAe,CAAC,GAA6B;IAC3D,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACzD,IAAI,KAAK,EAAE,CAAC;YACV,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,KAAK,EAAE,CAAC,CAAC;QACrD,CAAC;QACD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC,CAAC;AACJ,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,iBAAiB,CAAC,GAA6B;IAC7D,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC;QAC/B,IAAI,KAAK,EAAE,CAAC;YACV,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QAC7B,MAAM,cAAc,EAAE,CAAC;QACvB,kBAAkB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC/B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC,CAAC;AACJ,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,yBAAyB,CACvC,GAA6B;IAE7B,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QAC7B,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;QACzB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,qEAAqE;YACrE,4DAA4D;YAC5D,iBAAiB;YACjB,MAAM,UAAU,GAAG,cAAc,CAC/B,GAAG,EACH,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAC3C,CAAC;YACF,GAAG,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC,CAAC;YAC1B,MAAM,UAAU,CAAC;QACnB,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,SAAS,aAAa,CAAC,GAAiC;IACtD,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC;IACrC,IACE,KAAK,KAAK,8BAA8B,CAAC,eAAe;QACxD,KAAK,KAAK,8BAA8B,CAAC,UAAU,EACnD,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAA6B;IAC5D,MAAM,GAAG,GAAG,GAAG,CAAC,UAAU,IAAI,CAAC,CAAC;IAChC,MAAM,IAAI,GAAG,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC;IACpC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QAC7B,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;YACtC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;QACD,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,SAAS,CAAC;YACR,IAAI,CAAC;gBACH,OAAO,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;YACzB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,UAAU,GACd,GAAG,YAAY,kBAAkB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;gBAChE,IAAI,OAAO,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC;oBAC5C,MAAM,UAAU,CAAC;gBACnB,CAAC;gBACD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC;gBAC3D,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;gBACrB,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CACtC,MAAgC,EAAE;IAElC,OAAO;QACL,yBAAyB,CAAC,GAAG,CAAC;QAC9B,eAAe,CAAC,GAAG,CAAC;QACpB,iBAAiB,CAAC,GAAG,CAAC;QACtB,gBAAgB,EAAE;KACnB,CAAC;AACJ,CAAC;AAED,4EAA4E;AAC5E,EAAE;AACF,4EAA4E;AAC5E,4EAA4E;AAC5E,0EAA0E;AAC1E,0EAA0E;AAC1E,8EAA8E;AAC9E,yEAAyE;AAEzE,OAAO,EACL,QAAQ,EACR,kBAAkB,EAClB,yBAAyB,EACzB,eAAe,EACf,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,WAAW,EACX,cAAc,EACd,oBAAoB,GACrB,MAAM,YAAY,CAAC","sourcesContent":["// Part of @reliantlabs/forge-web-runtime — the web twin of forge/pkg.\n//\n// This module is also the package's SECOND ENTRY POINT, published as\n// \"@reliantlabs/forge-web-runtime/interceptors\". It is the DOM-free, React-free\n// half of the runtime: the transport layer and nothing else. Importing it\n// pulls in exactly ./errors and ./trace — no React, no @types/react, no\n// window/document. That matters for React Native, where Expo pins React 18\n// while the package's components are typed against React 19: a mobile app\n// importing the full barrel would typecheck the components it never renders\n// and fail. Everything re-exported at the bottom of this file is part of\n// this entry point's surface; keep it React-free (guarded by\n// interceptors-entry.test.ts).\n//\n// The Connect transport interceptor stack. This is the one place every\n// frontend→backend call flows through, composed once and handed to\n// createConnectTransport by the app's own connect module. Order (outermost\n// first):\n//\n// error-normalize → auth → brand headers → traceparent\n//\n// error-normalize is outermost so every caller sees a typed\n// ConnectClientError instead of a raw ConnectError.\n//\n// RETRY OWNERSHIP — read before adding a retry layer.\n// The single retry owner is the app's React Query client: it\n// structurally knows reads from writes, so it retries transient READ\n// failures and NEVER retries a mutation — no duplicate writes. Each React\n// Query retry re-runs the queryFn, which re-runs this whole interceptor\n// chain (fresh token + traceparent), so nothing is lost by not retrying\n// here. `retryInterceptor` below is therefore NOT in the default chain; it\n// stays exported (idempotency-gated) only for callers who use the raw\n// transport OUTSIDE React Query. Putting it back in the default chain would\n// stack two retry layers AND retry non-idempotent mutations — the bug this\n// split fixed.\nimport { MethodOptions_IdempotencyLevel } from \"@bufbuild/protobuf/wkt\";\n\nimport { ConnectClientError, normalizeError } from \"./errors.js\";\nimport { telemetryReady } from \"./telemetry-gate.js\";\nimport { injectTraceContext, traceIdFrom } from \"./trace.js\";\n\nimport type {\n Interceptor,\n UnaryRequest,\n StreamRequest,\n} from \"@connectrpc/connect\";\n\n/**\n * RuntimeInterceptorConfig is the seam the app wires its runtime dependencies\n * through. Everything is optional: an unconfigured chain still propagates\n * traceparent and normalizes errors.\n */\nexport interface RuntimeInterceptorConfig {\n /** Returns the current bearer token, or null when unauthenticated. */\n getToken?: () => Promise<string | null> | string | null;\n /** Returns the active brand, attached as the X-Brand header. */\n getBrand?: () => string | null | undefined;\n /** Invoked with the typed error on every failed RPC (telemetry sink). */\n onError?: (err: ConnectClientError) => void;\n /** Max transparent retries for retryable unary failures. Default 2. */\n maxRetries?: number;\n /** Base backoff in ms; grows exponentially with jitter. Default 200. */\n retryBaseMs?: number;\n}\n\nconst sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));\n\n/** Attaches the bearer token from the app's auth provider. */\nexport function authInterceptor(cfg: RuntimeInterceptorConfig): Interceptor {\n return (next) => async (req) => {\n const token = cfg.getToken ? await cfg.getToken() : null;\n if (token) {\n req.header.set(\"Authorization\", `Bearer ${token}`);\n }\n return next(req);\n };\n}\n\n/** Attaches brand context headers when the app supplies them. */\nexport function headerInterceptor(cfg: RuntimeInterceptorConfig): Interceptor {\n return (next) => async (req) => {\n const brand = cfg.getBrand?.();\n if (brand) {\n req.header.set(\"X-Brand\", brand);\n }\n return next(req);\n };\n}\n\n/**\n * Propagates a W3C traceparent so the backend (otelconnect + otelhttp) joins\n * the same distributed trace. Always emits a valid header — see trace.ts.\n *\n * While browser telemetry is still starting it waits (bounded; see\n * telemetry-gate.ts) so the call is made through the SDK's patched fetch and\n * its traceparent names a span the browser really exports.\n */\nexport function traceInterceptor(): Interceptor {\n return (next) => async (req) => {\n await telemetryReady();\n injectTraceContext(req.header);\n return next(req);\n };\n}\n\n/** Converts any thrown failure into a typed ConnectClientError. */\nexport function errorNormalizeInterceptor(\n cfg: RuntimeInterceptorConfig,\n): Interceptor {\n return (next) => async (req) => {\n try {\n return await next(req);\n } catch (err) {\n // traceInterceptor (innermost) has written the traceparent onto this\n // same request by now: the fallback when the server sent no\n // traceresponse.\n const normalized = normalizeError(\n err,\n traceIdFrom(req.header.get(\"traceparent\")),\n );\n cfg.onError?.(normalized);\n throw normalized;\n }\n };\n}\n\n/**\n * retryEligible reports whether a request is SAFE to retry transparently:\n * a no-side-effects / idempotent method (declared via proto\n * `idempotency_level`), or one carrying an explicit Idempotency-Key header.\n * A plain mutation (the default IDEMPOTENCY_UNKNOWN) is never retried, so a\n * transparent retry can't produce a duplicate write.\n */\nfunction retryEligible(req: UnaryRequest | StreamRequest): boolean {\n const level = req.method.idempotency;\n if (\n level === MethodOptions_IdempotencyLevel.NO_SIDE_EFFECTS ||\n level === MethodOptions_IdempotencyLevel.IDEMPOTENT\n ) {\n return true;\n }\n return req.header.has(\"Idempotency-Key\");\n}\n\n/**\n * Transparently retries retryable failures with exponential backoff +\n * jitter — for IDEMPOTENT requests ONLY (see retryEligible). NOT part of the\n * default chain: React Query owns retries (see the retry-ownership note at\n * the top of this file). Exported for callers using the raw transport\n * outside React Query. Streaming RPCs are never retried (a partially-\n * consumed stream can't be safely replayed).\n */\nexport function retryInterceptor(cfg: RuntimeInterceptorConfig): Interceptor {\n const max = cfg.maxRetries ?? 2;\n const base = cfg.retryBaseMs ?? 200;\n return (next) => async (req) => {\n if (req.stream || !retryEligible(req)) {\n return next(req);\n }\n let attempt = 0;\n for (;;) {\n try {\n return await next(req);\n } catch (err) {\n const normalized =\n err instanceof ConnectClientError ? err : normalizeError(err);\n if (attempt >= max || !normalized.retryable) {\n throw normalized;\n }\n const backoff = base * 2 ** attempt + Math.random() * base;\n await sleep(backoff);\n attempt++;\n }\n }\n };\n}\n\n/**\n * buildRuntimeInterceptors composes the full stack in the correct order.\n * Pass the result to createConnectTransport's `interceptors` option.\n *\n * retryInterceptor is deliberately NOT included — React Query is the single\n * retry owner (see the retry-ownership note at the top of this file).\n */\nexport function buildRuntimeInterceptors(\n cfg: RuntimeInterceptorConfig = {},\n): Interceptor[] {\n return [\n errorNormalizeInterceptor(cfg),\n authInterceptor(cfg),\n headerInterceptor(cfg),\n traceInterceptor(),\n ];\n}\n\n// ── The rest of the transport-layer surface ──────────────────────────────\n//\n// These belong to this entry point because the interceptor stack's contract\n// is expressed in them: errorNormalizeInterceptor THROWS ConnectClientError\n// and RuntimeInterceptorConfig.onError RECEIVES one, so a consumer cannot\n// write a handler without the type; setTraceSampled is traceInterceptor's\n// only knob. A consumer of \"@reliantlabs/forge-web-runtime/interceptors\" must\n// never have to fall back to the React-typed barrel to complete the job.\n\nexport {\n codeName,\n ConnectClientError,\n FORGE_ERROR_REASON_HEADER,\n isRetryableCode,\n normalizeError,\n} from \"./errors.js\";\n\nexport {\n freshTraceparent,\n injectTraceContext,\n setTraceSampled,\n traceIdFrom,\n TRACEPARENT_RE,\n TRACERESPONSE_HEADER,\n} from \"./trace.js\";\n"]}
1
+ {"version":3,"file":"interceptors.js","sourceRoot":"","sources":["../src/interceptors.ts"],"names":[],"mappings":"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","sourcesContent":["// Part of @reliantlabs/forge-web-runtime — the web twin of forge/pkg.\n//\n// This module is also the package's SECOND ENTRY POINT, published as\n// \"@reliantlabs/forge-web-runtime/interceptors\". It is the DOM-free, React-free\n// half of the runtime: the transport layer and nothing else. Importing it\n// pulls in exactly ./errors and ./trace — no React, no @types/react, no\n// window/document. That matters for React Native, where Expo pins React 18\n// while the package's components are typed against React 19: a mobile app\n// importing the full barrel would typecheck the components it never renders\n// and fail. Everything re-exported at the bottom of this file is part of\n// this entry point's surface; keep it React-free (guarded by\n// interceptors-entry.test.ts).\n//\n// The Connect transport interceptor stack. This is the one place every\n// frontend→backend call flows through, composed once and handed to\n// createConnectTransport by the app's own connect module. Order (outermost\n// first):\n//\n// error-normalize → auth → brand headers → traceparent\n//\n// error-normalize is outermost so every caller sees a typed\n// ConnectClientError instead of a raw ConnectError.\n//\n// RETRY OWNERSHIP — read before adding a retry layer.\n// The single retry owner is the app's React Query client: it\n// structurally knows reads from writes, so it retries transient READ\n// failures and NEVER retries a mutation — no duplicate writes. Each React\n// Query retry re-runs the queryFn, which re-runs this whole interceptor\n// chain (fresh token + traceparent), so nothing is lost by not retrying\n// here. `retryInterceptor` below is therefore NOT in the default chain; it\n// stays exported (idempotency-gated) only for callers who use the raw\n// transport OUTSIDE React Query. Putting it back in the default chain would\n// stack two retry layers AND retry non-idempotent mutations — the bug this\n// split fixed.\nimport { MethodOptions_IdempotencyLevel } from \"@bufbuild/protobuf/wkt\";\nimport { context } from \"@opentelemetry/api\";\n\nimport { ConnectClientError, normalizeError } from \"./errors.js\";\nimport { telemetryReady } from \"./telemetry-gate.js\";\nimport { injectTraceContext, traceIdFrom } from \"./trace.js\";\n\nimport type {\n Interceptor,\n UnaryRequest,\n StreamRequest,\n} from \"@connectrpc/connect\";\n\n/**\n * RuntimeInterceptorConfig is the seam the app wires its runtime dependencies\n * through. Everything is optional: an unconfigured chain still propagates\n * traceparent and normalizes errors.\n */\nexport interface RuntimeInterceptorConfig {\n /** Returns the current bearer token, or null when unauthenticated. */\n getToken?: () => Promise<string | null> | string | null;\n /** Returns the active brand, attached as the X-Brand header. */\n getBrand?: () => string | null | undefined;\n /** Invoked with the typed error on every failed RPC (telemetry sink). */\n onError?: (err: ConnectClientError) => void;\n /** Max transparent retries for retryable unary failures. Default 2. */\n maxRetries?: number;\n /** Base backoff in ms; grows exponentially with jitter. Default 200. */\n retryBaseMs?: number;\n}\n\nconst sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));\n\n/** Attaches the bearer token from the app's auth provider. */\nexport function authInterceptor(cfg: RuntimeInterceptorConfig): Interceptor {\n return (next) => async (req) => {\n const parent = context.active();\n const token = cfg.getToken ? await cfg.getToken() : null;\n if (token) {\n req.header.set(\"Authorization\", `Bearer ${token}`);\n }\n // Browser stack context managers cannot carry context through native\n // await. Restore the caller's context before entering the next boundary.\n return context.with(parent, () => next(req));\n };\n}\n\n/** Attaches brand context headers when the app supplies them. */\nexport function headerInterceptor(cfg: RuntimeInterceptorConfig): Interceptor {\n return (next) => async (req) => {\n const brand = cfg.getBrand?.();\n if (brand) {\n req.header.set(\"X-Brand\", brand);\n }\n return next(req);\n };\n}\n\n/**\n * Propagates a W3C traceparent so the backend (otelconnect + otelhttp) joins\n * the same distributed trace. Always emits a valid header — see trace.ts.\n *\n * While browser telemetry is still starting it waits (bounded; see\n * telemetry-gate.ts) so the call is made through the SDK's patched fetch and\n * its traceparent names a span the browser really exports.\n */\nexport function traceInterceptor(): Interceptor {\n return (next) => async (req) => {\n const parent = context.active();\n await telemetryReady();\n return context.with(parent, () => {\n injectTraceContext(req.header);\n return next(req);\n });\n };\n}\n\n/** Converts any thrown failure into a typed ConnectClientError. */\nexport function errorNormalizeInterceptor(\n cfg: RuntimeInterceptorConfig,\n): Interceptor {\n return (next) => async (req) => {\n try {\n return await next(req);\n } catch (err) {\n // traceInterceptor (innermost) has written the traceparent onto this\n // same request by now: the fallback when the server sent no\n // traceresponse.\n const normalized = normalizeError(\n err,\n traceIdFrom(req.header.get(\"traceparent\")),\n );\n cfg.onError?.(normalized);\n throw normalized;\n }\n };\n}\n\n/**\n * retryEligible reports whether a request is SAFE to retry transparently:\n * a no-side-effects / idempotent method (declared via proto\n * `idempotency_level`), or one carrying an explicit Idempotency-Key header.\n * A plain mutation (the default IDEMPOTENCY_UNKNOWN) is never retried, so a\n * transparent retry can't produce a duplicate write.\n */\nfunction retryEligible(req: UnaryRequest | StreamRequest): boolean {\n const level = req.method.idempotency;\n if (\n level === MethodOptions_IdempotencyLevel.NO_SIDE_EFFECTS ||\n level === MethodOptions_IdempotencyLevel.IDEMPOTENT\n ) {\n return true;\n }\n return req.header.has(\"Idempotency-Key\");\n}\n\n/**\n * Transparently retries retryable failures with exponential backoff +\n * jitter — for IDEMPOTENT requests ONLY (see retryEligible). NOT part of the\n * default chain: React Query owns retries (see the retry-ownership note at\n * the top of this file). Exported for callers using the raw transport\n * outside React Query. Streaming RPCs are never retried (a partially-\n * consumed stream can't be safely replayed).\n */\nexport function retryInterceptor(cfg: RuntimeInterceptorConfig): Interceptor {\n const max = cfg.maxRetries ?? 2;\n const base = cfg.retryBaseMs ?? 200;\n return (next) => async (req) => {\n if (req.stream || !retryEligible(req)) {\n return next(req);\n }\n let attempt = 0;\n for (;;) {\n try {\n return await next(req);\n } catch (err) {\n const normalized =\n err instanceof ConnectClientError ? err : normalizeError(err);\n if (attempt >= max || !normalized.retryable) {\n throw normalized;\n }\n const backoff = base * 2 ** attempt + Math.random() * base;\n await sleep(backoff);\n attempt++;\n }\n }\n };\n}\n\n/**\n * buildRuntimeInterceptors composes the full stack in the correct order.\n * Pass the result to createConnectTransport's `interceptors` option.\n *\n * retryInterceptor is deliberately NOT included — React Query is the single\n * retry owner (see the retry-ownership note at the top of this file).\n */\nexport function buildRuntimeInterceptors(\n cfg: RuntimeInterceptorConfig = {},\n): Interceptor[] {\n return [\n errorNormalizeInterceptor(cfg),\n authInterceptor(cfg),\n headerInterceptor(cfg),\n traceInterceptor(),\n ];\n}\n\n// ── The rest of the transport-layer surface ──────────────────────────────\n//\n// These belong to this entry point because the interceptor stack's contract\n// is expressed in them: errorNormalizeInterceptor THROWS ConnectClientError\n// and RuntimeInterceptorConfig.onError RECEIVES one, so a consumer cannot\n// write a handler without the type; setTraceSampled is traceInterceptor's\n// only knob. A consumer of \"@reliantlabs/forge-web-runtime/interceptors\" must\n// never have to fall back to the React-typed barrel to complete the job.\n\nexport {\n codeName,\n ConnectClientError,\n FORGE_ERROR_REASON_HEADER,\n isRetryableCode,\n normalizeError,\n} from \"./errors.js\";\n\nexport {\n freshTraceparent,\n injectTraceContext,\n setTraceSampled,\n traceIdFrom,\n TRACEPARENT_RE,\n TRACERESPONSE_HEADER,\n} from \"./trace.js\";\n"]}
package/dist/node.js CHANGED
@@ -149,6 +149,16 @@ export function buildNodeResource(config, runtime, version = sdkVersion()) {
149
149
  out["telemetry.sdk.version"] = version;
150
150
  if (config.serviceVersion)
151
151
  out["service.version"] = config.serviceVersion;
152
+ if (electron) {
153
+ // A desktop app's main process: the same app.desktop.* its renderer
154
+ // reports from the preload bridge (client-context.ts), so main-process
155
+ // errors name the app, Electron and Chromium versions they ran on.
156
+ if (config.serviceVersion)
157
+ out["app.desktop.version"] = config.serviceVersion;
158
+ out["app.desktop.electron.version"] = electron;
159
+ if (runtime.versions.chrome)
160
+ out["app.desktop.chromium.version"] = runtime.versions.chrome;
161
+ }
152
162
  if (config.serviceNamespace)
153
163
  out["service.namespace"] = config.serviceNamespace;
154
164
  if (config.frontend)
package/dist/node.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"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","sourcesContent":["// Part of @reliantlabs/forge-web-runtime — the web twin of forge/pkg.\n//\n// Node / Electron-main telemetry: logs, errors and spans over OTLP/HTTP to the\n// same ingest the browser entry (`/telemetry`) ships to — usually the KEYED\n// ingest, `https://<ingest host>/k/<key>`, since a desktop app's main process\n// has no same-origin `/_otel`.\n//\n// ── What it records ──────────────────────────────────────────────────────\n//\n// - uncaught exceptions and unhandled rejections, as ERROR log records in\n// the shape forge/pkg/observe gives a server error (node-exception.ts), so\n// `forge obs issues` groups them with sfp1;\n// - what the app logs through `telemetry.logger`, and errors it hands to\n// `telemetry.reportError` (and to the barrel's `reportException`);\n// - spans: the global @opentelemetry/api tracer is this one (node-trace.ts).\n//\n// Records carry no `rum.sessionId`. That is what makes the issues pipeline\n// treat them as server-side errors (obsquery.ServerErrorFilterSQL), which is\n// what a main process is.\n//\n// ── What it changes about the process ────────────────────────────────────\n//\n// Nothing an app can observe, by design. Exceptions are watched through\n// `uncaughtExceptionMonitor`, which leaves the process's fate to whoever owns\n// it (the app's own handler, Electron's dialog, Node's exit). Rejections have\n// no monitor event, and any `unhandledRejection` listener — this one included\n// — switches off Node's default of raising the rejection as an uncaught\n// exception. So when this listener is the only one, it puts the default back:\n// it flushes the record, then raises the rejection as Node would have\n// (`--unhandled-rejections` is honoured). In plain Node with no\n// `uncaughtException` handler of the app's own, the process would die before\n// an async export could finish, so the runtime flushes first and then exits\n// as Node does (stderr, code 1). Under Electron the main process outlives an\n// uncaught exception by default, and nothing is installed.\n//\n// ── Batching and quitting ────────────────────────────────────────────────\n//\n// Records are queued and exported in batches (delay and size below), with\n// unref'd timers, so telemetry never keeps a process alive. A fatal record\n// starts its export at once. `flush(timeoutMs)` exports everything queued and\n// resolves by the timeout whatever the network does; `flushOnQuit(app)` runs\n// it from Electron's `before-quit`. An idle Node process flushes on\n// `beforeExit`.\n//\n// ── Privacy ──────────────────────────────────────────────────────────────\n//\n// Nothing about the user beyond what the browser entry sends: an opaque user\n// id when the app sets one, the app client, and the record itself. Messages,\n// bodies and string attributes have credential shapes redacted with the same\n// patterns `track()` uses; an exception keeps only its message's first line;\n// stack paths lose the home directory and the install location; object-valued\n// attributes are dropped, never serialized. No host name, no IP, no argv, no\n// environment.\nimport { createRequire } from \"node:module\";\nimport { homedir } from \"node:os\";\nimport { inspect } from \"node:util\";\n\nimport {\n context as contextApi,\n propagation,\n trace,\n type Tracer,\n} from \"@opentelemetry/api\";\n\nimport { setExceptionReporter } from \"./error-reporter.js\";\nimport {\n bound,\n boundMessage,\n ERROR_GROUP,\n exceptionAttributes,\n type PathScrub,\n} from \"./node-exception.js\";\nimport {\n type Attributes,\n type AttributeValue,\n BatchQueue,\n encodeAttributes,\n exportSignal,\n type ExporterOptions,\n type FetchLike,\n type KeyValue,\n type LogRecord,\n logsPayload,\n MAX_ATTRIBUTE_CHARS,\n nowMs,\n type Scope,\n SEVERITY,\n type SeverityText,\n type SpanRecord,\n tracesPayload,\n unixNanos,\n} from \"./node-otlp.js\";\nimport {\n AsyncLocalContextManager,\n type ContextOps,\n NodeTracerProvider,\n TraceparentPropagator,\n} from \"./node-trace.js\";\nimport { redactSecrets } from \"./track.js\";\n\nexport { frontendServiceName } from \"./service-name.js\";\nexport {\n CODE_FILE,\n CODE_FUNCTION,\n CODE_LINE,\n ERROR_GROUP,\n EXCEPTION_MESSAGE,\n EXCEPTION_STACKTRACE,\n EXCEPTION_TYPE,\n exceptionAttributes,\n type PathScrub,\n} from \"./node-exception.js\";\n\n/** The instrumentation scope of every log record this entry writes. */\nexport const NODE_SCOPE_NAME = \"@reliantlabs/forge-web-runtime/node\";\n\n/** Body of the record for an uncaught exception. */\nexport const UNCAUGHT_EXCEPTION_BODY = \"uncaught exception\";\n/** Body of the record for an unhandled rejection. */\nexport const UNHANDLED_REJECTION_BODY = \"unhandled rejection\";\n/** Body of the record for an error passed to reportError / reportException. */\nexport const REPORTED_EXCEPTION_BODY = \"caught exception\";\n\n/** Longest log body. */\nexport const MAX_BODY_CHARS = 4096;\n/** Longest user id, as the browser entry bounds it. */\nexport const MAX_USER_ID_CHARS = 128;\n\nexport const DEFAULT_FLUSH_TIMEOUT_MS = 2_000;\nexport const DEFAULT_BATCH_DELAY_MS = 5_000;\nexport const DEFAULT_MAX_BATCH = 256;\nexport const DEFAULT_MAX_QUEUE = 2_048;\nexport const DEFAULT_EXPORT_TIMEOUT_MS = 10_000;\n/**\n * Most records of ONE fatal error (same type, message and frame) per minute.\n * A handler that throws while handling — an EPIPE logger writing to the pipe\n * that just broke — raises the same exception in a tight loop, and each one\n * would otherwise be a record and a request.\n */\nexport const MAX_REPEATS_PER_MINUTE = 5;\n\n/** A value a caller may attach to a log record; anything else is dropped. */\nexport type LogAttributes = Record<string, unknown>;\n\nexport interface NodeTelemetryConfig {\n /** `service.name`. For a forge frontend: frontendServiceName(project, frontend). */\n serviceName: string;\n /** `service.version` — the release. */\n serviceVersion?: string;\n /** `service.namespace`, conventionally the project name. */\n serviceNamespace?: string;\n /** Emitted as `deployment.environment.name` and the legacy `deployment.environment`. */\n environment?: string;\n /** The forge frontend this process belongs to, as `forge.frontend`. */\n frontend?: string;\n /** `app.client` on every record and span (`electron`, `cli`, ...). */\n appClient?: string;\n /**\n * The OTLP base URL: `${endpoint}/v1/logs` and `/v1/traces` are posted to.\n * The keyed ingest is `https://<ingest host>/k/<key>`. Unset, empty, `off`\n * or not an absolute http(s) URL disables telemetry: init returns null and\n * installs nothing.\n */\n endpoint?: string;\n /**\n * The `Origin` header sent with every export. The keyed ingest admits a\n * request only when its Origin is EXACTLY one of the key's allowed origins,\n * so a desktop app sends its renderer's origin (`app://bundle`).\n */\n origin?: string;\n /** Extra request headers. */\n headers?: Record<string, string>;\n /** Files below it are reported relative to it. Electron: app.getAppPath(). Default process.cwd(). */\n appRoot?: string;\n /** Extra resource attributes. They cannot override `service.name`. */\n resourceAttributes?: Record<string, string>;\n /** Record uncaught exceptions and unhandled rejections. Default true. */\n captureProcessErrors?: boolean;\n /** Register the tracer, context manager and propagator globally. Default true. */\n registerGlobal?: boolean;\n /** Least severe level the logger exports. Default `INFO`. */\n minSeverity?: SeverityText;\n /** How long a record may wait for its batch. Default 5s. */\n batchDelayMs?: number;\n /** Most records in one request. Default 256. */\n maxBatchSize?: number;\n /** Most records held per signal; more are dropped and counted. Default 2048. */\n maxQueueSize?: number;\n /** One request's budget. Default 10s. */\n exportTimeoutMs?: number;\n /** How long a fatal error's record may delay the process's default exit. Default 2s. */\n fatalFlushTimeoutMs?: number;\n /** Told when a batch is dropped (the ingest refused it or was unreachable). */\n onExportError?: (error: Error) => void;\n /** Test seam: the fetch exports go through. Default globalThis.fetch. */\n fetch?: FetchLike;\n}\n\nexport interface NodeLogger {\n debug(body: string, attributes?: LogAttributes): void;\n info(body: string, attributes?: LogAttributes): void;\n warn(body: string, attributes?: LogAttributes): void;\n /** An ERROR record. An Error among the attributes supplies its exception.* keys. */\n error(body: string, attributes?: LogAttributes): void;\n}\n\nexport interface NodeTelemetry {\n readonly logger: NodeLogger;\n /** The tracer to open spans on (also the global one, unless registerGlobal is false). */\n readonly tracer: Tracer;\n /** One ERROR record for an error the app caught. */\n reportError(error: unknown, attributes?: LogAttributes): void;\n /** The opaque `userId` on every later record and span; null clears it. Never an email. */\n setUser(id: string | null): void;\n /** Export everything queued; resolves by `timeoutMs` (default 2s) whatever the network does. */\n flush(timeoutMs?: number): Promise<void>;\n /** Flush, then stop: every later call is a no-op. init may run again afterwards. */\n shutdown(timeoutMs?: number): Promise<void>;\n /** Records refused because a queue was full. */\n readonly dropped: number;\n}\n\nlet current: NodeTelemetry | null = null;\n\n/** The running instance, or null before init / after shutdown. */\nexport function getNodeTelemetry(): NodeTelemetry | null {\n return current;\n}\n\n/**\n * The absolute OTLP base for `endpoint`, or null when telemetry is off: unset,\n * empty, `off`, or not an absolute http(s) URL. Trailing slashes are dropped.\n */\nexport function resolveNodeIngestUrl(\n endpoint: string | undefined,\n): string | null {\n const value = (endpoint ?? \"\").trim();\n if (value === \"\" || value.toLowerCase() === \"off\") {\n return null;\n }\n try {\n const url = new URL(value);\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n return null;\n }\n url.search = \"\";\n url.hash = \"\";\n return url.toString().replace(/\\/+$/, \"\");\n } catch {\n return null;\n }\n}\n\nfunction sdkVersion(): string {\n try {\n const req = createRequire(import.meta.url);\n const v = (req(\"../package.json\") as { version?: unknown }).version;\n return typeof v === \"string\" ? v : \"\";\n } catch {\n return \"\";\n }\n}\n\nconst OS_TYPE: Record<string, string> = { win32: \"windows\", sunos: \"solaris\" };\nconst HOST_ARCH: Record<string, string> = {\n x64: \"amd64\",\n ia32: \"x86\",\n ppc64: \"ppc64\",\n};\n\n/**\n * The resource every record and span carries. Pure given its inputs;\n * exported for tests and for an app that wants to see exactly what is sent.\n */\nexport function buildNodeResource(\n config: Pick<\n NodeTelemetryConfig,\n | \"serviceName\"\n | \"serviceVersion\"\n | \"serviceNamespace\"\n | \"environment\"\n | \"frontend\"\n | \"resourceAttributes\"\n >,\n runtime: {\n versions: Record<string, string | undefined>;\n platform: string;\n arch: string;\n },\n version = sdkVersion(),\n): Record<string, string> {\n const electron = runtime.versions.electron;\n const out: Record<string, string> = {\n \"telemetry.sdk.name\": \"@reliantlabs/forge-web-runtime\",\n \"telemetry.sdk.language\": \"nodejs\",\n \"process.runtime.name\": electron ? \"electron\" : \"nodejs\",\n \"process.runtime.version\": (electron ??\n runtime.versions.node ??\n \"\") as string,\n \"os.type\": OS_TYPE[runtime.platform] ?? runtime.platform,\n \"host.arch\": HOST_ARCH[runtime.arch] ?? runtime.arch,\n };\n if (version) out[\"telemetry.sdk.version\"] = version;\n if (config.serviceVersion) out[\"service.version\"] = config.serviceVersion;\n if (config.serviceNamespace)\n out[\"service.namespace\"] = config.serviceNamespace;\n if (config.frontend) out[\"forge.frontend\"] = config.frontend;\n if (config.environment) {\n out[\"deployment.environment.name\"] = config.environment;\n out[\"deployment.environment\"] = config.environment;\n }\n Object.assign(out, config.resourceAttributes ?? {});\n out[\"service.name\"] = config.serviceName;\n return out;\n}\n\nfunction scrubString(value: string, max: number): string {\n return bound(redactSecrets(value.slice(0, max * 2)), max);\n}\n\n/**\n * Reduce caller attributes to what a record may carry: strings redacted and\n * bounded, finite numbers, booleans, arrays of those; an Error becomes its\n * bounded message (its stack goes in exception.*). Objects are dropped.\n */\nfunction sanitizeAttributes(attrs: LogAttributes | undefined): {\n attributes: Attributes;\n error: unknown;\n} {\n const out: Attributes = {};\n let error: unknown = undefined;\n if (!attrs || typeof attrs !== \"object\") {\n return { attributes: out, error };\n }\n for (const [key, v] of Object.entries(attrs)) {\n if (v instanceof Error) {\n if (error === undefined) error = v;\n out[key] = boundMessage(v.message ?? \"\");\n } else if (typeof v === \"string\") {\n out[key] = scrubString(v, MAX_ATTRIBUTE_CHARS);\n } else if (\n (typeof v === \"number\" && Number.isFinite(v)) ||\n typeof v === \"boolean\"\n ) {\n out[key] = v;\n } else if (Array.isArray(v)) {\n const kept = v\n .filter(\n (x) =>\n typeof x === \"string\" ||\n typeof x === \"boolean\" ||\n (typeof x === \"number\" && Number.isFinite(x)),\n )\n .map((x) =>\n typeof x === \"string\" ? scrubString(x, MAX_ATTRIBUTE_CHARS) : x,\n );\n out[key] = kept as AttributeValue;\n }\n }\n return { attributes: out, error };\n}\n\nfunction withTimeout(p: Promise<unknown>, ms: number): Promise<void> {\n return new Promise((resolve) => {\n const t = setTimeout(resolve, Math.max(0, ms));\n (t as { unref?: () => void }).unref?.();\n void p.then(\n () => {\n clearTimeout(t);\n resolve();\n },\n () => {\n clearTimeout(t);\n resolve();\n },\n );\n });\n}\n\n/** What `--unhandled-rejections` says Node does with a rejection nobody handles. */\nexport function unhandledRejectionMode(\n execArgv: readonly string[],\n nodeOptions: string | undefined,\n): string {\n const args = [...execArgv, ...(nodeOptions ?? \"\").split(/\\s+/)];\n let mode = \"throw\";\n for (let i = 0; i < args.length; i++) {\n const a = args[i] ?? \"\";\n if (a.startsWith(\"--unhandled-rejections=\")) {\n mode = a.slice(\"--unhandled-rejections=\".length);\n } else if (a === \"--unhandled-rejections\" && args[i + 1]) {\n mode = args[i + 1] as string;\n }\n }\n return mode;\n}\n\n/** The error Node raises for a rejection whose reason is not an Error. */\nfunction rejectionError(reason: unknown): Error {\n const err = new Error(\n \"This error originated either by throwing inside of an async function without a catch block, \" +\n \"or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason \" +\n `\"${inspect(reason)}\".`,\n );\n err.name = \"UnhandledPromiseRejection\";\n (err as { code?: string }).code = \"ERR_UNHANDLED_REJECTION\";\n return err;\n}\n\n/**\n * initNodeTelemetry starts telemetry for this process and returns it, or null\n * when it is off (no usable endpoint, no fetch, not Node). Idempotent: a\n * second call returns the running instance and ignores its config. Never\n * throws.\n */\nexport function initNodeTelemetry(\n config: NodeTelemetryConfig,\n): NodeTelemetry | null {\n if (current) {\n return current;\n }\n try {\n const instance = start(config);\n current = instance;\n return instance;\n } catch {\n // Telemetry must never break the app.\n return null;\n }\n}\n\nfunction start(config: NodeTelemetryConfig): NodeTelemetry | null {\n if (typeof process === \"undefined\" || !process.versions?.node) {\n return null;\n }\n const endpoint = resolveNodeIngestUrl(config.endpoint);\n const fetchFn: FetchLike | undefined =\n config.fetch ??\n (typeof globalThis.fetch === \"function\"\n ? (globalThis.fetch as unknown as FetchLike)\n : undefined);\n if (!endpoint || !fetchFn || !config.serviceName) {\n return null;\n }\n\n const isElectron = Boolean(process.versions.electron);\n const scrub: PathScrub = {\n appRoot: config.appRoot ?? process.cwd(),\n homeDir: safeHomedir(),\n caseInsensitive: process.platform === \"win32\",\n };\n const resource: KeyValue[] = encodeAttributes(\n buildNodeResource(config, {\n versions: process.versions,\n platform: process.platform,\n arch: process.arch,\n }),\n );\n const headers: Record<string, string> = { ...(config.headers ?? {}) };\n const origin = config.origin?.trim().replace(/\\/+$/, \"\");\n if (origin) {\n headers[\"origin\"] = origin;\n }\n const exporter: ExporterOptions = {\n endpoint,\n headers,\n fetch: fetchFn,\n timeoutMs: config.exportTimeoutMs ?? DEFAULT_EXPORT_TIMEOUT_MS,\n retries: 2,\n onError: config.onExportError,\n };\n const batch = {\n maxQueue: config.maxQueueSize ?? DEFAULT_MAX_QUEUE,\n maxBatch: config.maxBatchSize ?? DEFAULT_MAX_BATCH,\n delayMs: config.batchDelayMs ?? DEFAULT_BATCH_DELAY_MS,\n };\n const logScope: Scope = {\n name: NODE_SCOPE_NAME,\n version: sdkVersion() || undefined,\n };\n const logs = new BatchQueue<LogRecord>({\n ...batch,\n send: (records) =>\n exportSignal(exporter, \"logs\", logsPayload(resource, logScope, records)),\n });\n const spans = new BatchQueue<SpanRecord>({\n ...batch,\n send: (records) =>\n exportSignal(exporter, \"traces\", tracesPayload(resource, records)),\n });\n\n let stopped = false;\n let userId: string | null = null;\n const globals = (): Attributes => {\n const g: Attributes = {};\n if (config.appClient) g[\"app.client\"] = config.appClient;\n if (userId) g[\"userId\"] = userId;\n return g;\n };\n\n // Context: ours, registered globally when allowed. If another manager got\n // there first, spans follow ITS context so the app's context.active() and\n // ours agree.\n const manager = new AsyncLocalContextManager();\n const registerGlobal = config.registerGlobal ?? true;\n const ownsContext =\n registerGlobal && contextApi.setGlobalContextManager(manager);\n const ctxOps: ContextOps = registerGlobal\n ? {\n active: () => contextApi.active(),\n with: (c, fn) => contextApi.with(c, fn),\n }\n : { active: () => manager.active(), with: (c, fn) => manager.with(c, fn) };\n const provider = new NodeTracerProvider(\n (span) => {\n if (!stopped) spans.push(span);\n },\n globals,\n ctxOps,\n );\n const ownsTrace = registerGlobal && trace.setGlobalTracerProvider(provider);\n const ownsPropagation =\n registerGlobal &&\n propagation.setGlobalPropagator(new TraceparentPropagator());\n const tracer = provider.getTracer(NODE_SCOPE_NAME, sdkVersion() || undefined);\n\n const minSeverity = SEVERITY[config.minSeverity ?? \"INFO\"] ?? SEVERITY.INFO;\n\n function emit(\n severity: SeverityText,\n body: string,\n attributes: Attributes,\n ): void {\n if (stopped || SEVERITY[severity] < minSeverity) {\n return;\n }\n const now = unixNanos(nowMs());\n const record: LogRecord = {\n timeUnixNano: now,\n observedTimeUnixNano: now,\n severityNumber: SEVERITY[severity],\n severityText: severity,\n body: { stringValue: scrubString(body, MAX_BODY_CHARS) },\n attributes: encodeAttributes({ ...globals(), ...attributes }),\n };\n const sc = trace.getSpanContext(ctxOps.active());\n if (sc && trace.isSpanContextValid(sc)) {\n record.traceId = sc.traceId;\n record.spanId = sc.spanId;\n record.flags = sc.traceFlags;\n }\n logs.push(record);\n }\n\n function log(\n severity: SeverityText,\n body: string,\n attrs?: LogAttributes,\n ): void {\n try {\n const { attributes, error } = sanitizeAttributes(attrs);\n // As observe: only an ERROR record gets exception inputs, and an\n // attribute the caller set wins over the derived one.\n const derived =\n severity === \"ERROR\" && error !== undefined\n ? exceptionAttributes(error, scrub)\n : {};\n emit(severity, body, { ...derived, ...attributes });\n } catch {\n // Telemetry must never break the app.\n }\n }\n\n const logger: NodeLogger = {\n debug: (b, a) => log(\"DEBUG\", b, a),\n info: (b, a) => log(\"INFO\", b, a),\n warn: (b, a) => log(\"WARN\", b, a),\n error: (b, a) => log(\"ERROR\", b, a),\n };\n\n function reportError(error: unknown, attrs?: LogAttributes): void {\n try {\n const { attributes } = sanitizeAttributes(attrs);\n emit(\"ERROR\", REPORTED_EXCEPTION_BODY, {\n ...exceptionAttributes(error, scrub),\n ...attributes,\n });\n } catch {\n // Telemetry must never break the app.\n }\n }\n\n // ── Process errors ──────────────────────────────────────────────────────\n const recorded = new WeakSet<object>();\n const repeats = new Map<string, { since: number; count: number }>();\n let drainScheduled = false;\n function recordFatal(value: unknown, body: string): void {\n if (typeof value === \"object\" && value !== null) {\n if (recorded.has(value)) return;\n recorded.add(value);\n }\n try {\n const attributes = exceptionAttributes(value, scrub);\n const key = [\n body,\n attributes[\"exception.type\"],\n attributes[\"code.function.name\"],\n attributes[\"exception.message\"],\n ].join(\"\\u001f\");\n const now = Date.now();\n let seen = repeats.get(key);\n if (!seen || now - seen.since >= 60_000) {\n if (repeats.size >= 256) repeats.clear();\n seen = { since: now, count: 0 };\n repeats.set(key, seen);\n }\n if (++seen.count > MAX_REPEATS_PER_MINUTE) {\n return;\n }\n emit(\"ERROR\", body, attributes);\n // A fatal record does not wait for its batch; a burst in one turn of the\n // event loop goes out as one request.\n if (!drainScheduled) {\n drainScheduled = true;\n setImmediate(() => {\n drainScheduled = false;\n logs.drain();\n }).unref();\n }\n } catch {\n // Telemetry must never break the app.\n }\n }\n const fatalFlushMs = config.fatalFlushTimeoutMs ?? DEFAULT_FLUSH_TIMEOUT_MS;\n\n const onMonitor = (err: unknown, origin: string): void => {\n recordFatal(\n err,\n origin === \"unhandledRejection\"\n ? UNHANDLED_REJECTION_BODY\n : UNCAUGHT_EXCEPTION_BODY,\n );\n };\n const onRejection = (reason: unknown): void => {\n recordFatal(reason, UNHANDLED_REJECTION_BODY);\n if (process.listenerCount(\"unhandledRejection\") > 1) {\n return; // The app handles rejections; it decides what happens next.\n }\n const mode = unhandledRejectionMode(\n process.execArgv,\n process.env.NODE_OPTIONS,\n );\n if (mode === \"warn\" || mode === \"none\") {\n return;\n }\n if (mode === \"warn-with-error-code\") {\n process.exitCode = 1;\n return;\n }\n // throw / strict: what Node does with no listener at all — raise it as an\n // uncaught exception — once the record is out.\n const err = reason instanceof Error ? reason : rejectionError(reason);\n recorded.add(err);\n void withTimeout(flushAll(), fatalFlushMs).then(() =>\n process.nextTick(() => {\n throw err;\n }),\n );\n };\n let exiting = false;\n const onUncaught = (err: unknown): void => {\n if (process.listenerCount(\"uncaughtException\") > 1 || exiting) {\n return; // The app's own handler owns the process's fate.\n }\n // Node's default (print, exit 1), after the record is out.\n exiting = true;\n void withTimeout(flushAll(), fatalFlushMs).then(() => {\n try {\n process.stderr.write(\n `${err instanceof Error && err.stack ? err.stack : inspect(err)}\\n`,\n );\n } catch {\n // stderr may be gone; the exit code still says what happened.\n }\n process.exit(1);\n });\n };\n const onBeforeExit = (): void => {\n if (logs.size > 0 || spans.size > 0) {\n void flushAll();\n }\n };\n\n const captureErrors = config.captureProcessErrors ?? true;\n if (captureErrors) {\n process.on(\"uncaughtExceptionMonitor\", onMonitor);\n process.on(\"unhandledRejection\", onRejection);\n if (!isElectron) {\n process.on(\"uncaughtException\", onUncaught);\n }\n }\n process.on(\"beforeExit\", onBeforeExit);\n setExceptionReporter((error, attributes) => reportError(error, attributes));\n\n function flushAll(): Promise<void> {\n return Promise.all([logs.flush(), spans.flush()]).then(() => undefined);\n }\n\n const instance: NodeTelemetry = {\n logger,\n tracer,\n reportError,\n setUser(id) {\n userId =\n typeof id === \"string\" && id !== \"\"\n ? id.slice(0, MAX_USER_ID_CHARS)\n : null;\n },\n flush(timeoutMs = DEFAULT_FLUSH_TIMEOUT_MS) {\n return withTimeout(flushAll(), timeoutMs);\n },\n async shutdown(timeoutMs = DEFAULT_FLUSH_TIMEOUT_MS) {\n if (stopped) return;\n await withTimeout(flushAll(), timeoutMs);\n stopped = true;\n process.removeListener(\"uncaughtExceptionMonitor\", onMonitor);\n process.removeListener(\"unhandledRejection\", onRejection);\n process.removeListener(\"uncaughtException\", onUncaught);\n process.removeListener(\"beforeExit\", onBeforeExit);\n setExceptionReporter(null);\n if (ownsTrace) trace.disable();\n if (ownsPropagation) propagation.disable();\n if (ownsContext) contextApi.disable();\n if (current === instance) current = null;\n },\n get dropped() {\n return logs.dropped + spans.dropped;\n },\n };\n return instance;\n}\n\nfunction safeHomedir(): string | undefined {\n try {\n return homedir() || undefined;\n } catch {\n return undefined;\n }\n}\n\n/** The slice of Electron's `app` flushOnQuit needs. */\nexport interface QuitApp {\n on(\n event: \"before-quit\",\n listener: (event: { preventDefault(): void }) => void,\n ): unknown;\n removeListener?(\n event: \"before-quit\",\n listener: (event: { preventDefault(): void }) => void,\n ): unknown;\n quit(): void;\n}\n\n/**\n * Hold Electron's quit until the running telemetry has flushed (bounded by\n * `timeoutMs`, default 2s), then quit. Returns a function that removes the\n * hook.\n *\n * It defers the FIRST quit and calls app.quit() again when the flush is done.\n * An app that already defers quit for its own shutdown (stops a backend, then\n * exits) should instead `await telemetry.flush()` as the last step of that\n * sequence: two listeners that each defer and re-quit cut each other short.\n */\nexport function flushOnQuit(\n app: QuitApp,\n options: { timeoutMs?: number } = {},\n): () => void {\n let flushed = false;\n const listener = (event: { preventDefault(): void }): void => {\n const t = current;\n if (flushed || !t) {\n return;\n }\n flushed = true;\n event.preventDefault();\n void t\n .flush(options.timeoutMs ?? DEFAULT_FLUSH_TIMEOUT_MS)\n .finally(() => app.quit());\n };\n app.on(\"before-quit\", listener);\n return () => {\n app.removeListener?.(\"before-quit\", listener);\n };\n}\n"]}
1
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"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","sourcesContent":["// Part of @reliantlabs/forge-web-runtime — the web twin of forge/pkg.\n//\n// Node / Electron-main telemetry: logs, errors and spans over OTLP/HTTP to the\n// same ingest the browser entry (`/telemetry`) ships to — usually the KEYED\n// ingest, `https://<ingest host>/k/<key>`, since a desktop app's main process\n// has no same-origin `/_otel`.\n//\n// ── What it records ──────────────────────────────────────────────────────\n//\n// - uncaught exceptions and unhandled rejections, as ERROR log records in\n// the shape forge/pkg/observe gives a server error (node-exception.ts), so\n// `forge obs issues` groups them with sfp1;\n// - what the app logs through `telemetry.logger`, and errors it hands to\n// `telemetry.reportError` (and to the barrel's `reportException`);\n// - spans: the global @opentelemetry/api tracer is this one (node-trace.ts).\n//\n// Records carry no `rum.sessionId`. That is what makes the issues pipeline\n// treat them as server-side errors (obsquery.ServerErrorFilterSQL), which is\n// what a main process is.\n//\n// ── What it changes about the process ────────────────────────────────────\n//\n// Nothing an app can observe, by design. Exceptions are watched through\n// `uncaughtExceptionMonitor`, which leaves the process's fate to whoever owns\n// it (the app's own handler, Electron's dialog, Node's exit). Rejections have\n// no monitor event, and any `unhandledRejection` listener — this one included\n// — switches off Node's default of raising the rejection as an uncaught\n// exception. So when this listener is the only one, it puts the default back:\n// it flushes the record, then raises the rejection as Node would have\n// (`--unhandled-rejections` is honoured). In plain Node with no\n// `uncaughtException` handler of the app's own, the process would die before\n// an async export could finish, so the runtime flushes first and then exits\n// as Node does (stderr, code 1). Under Electron the main process outlives an\n// uncaught exception by default, and nothing is installed.\n//\n// ── Batching and quitting ────────────────────────────────────────────────\n//\n// Records are queued and exported in batches (delay and size below), with\n// unref'd timers, so telemetry never keeps a process alive. A fatal record\n// starts its export at once. `flush(timeoutMs)` exports everything queued and\n// resolves by the timeout whatever the network does; `flushOnQuit(app)` runs\n// it from Electron's `before-quit`. An idle Node process flushes on\n// `beforeExit`.\n//\n// ── Privacy ──────────────────────────────────────────────────────────────\n//\n// Nothing about the user beyond what the browser entry sends: an opaque user\n// id when the app sets one, the app client, and the record itself. Messages,\n// bodies and string attributes have credential shapes redacted with the same\n// patterns `track()` uses; an exception keeps only its message's first line;\n// stack paths lose the home directory and the install location; object-valued\n// attributes are dropped, never serialized. No host name, no IP, no argv, no\n// environment.\nimport { createRequire } from \"node:module\";\nimport { homedir } from \"node:os\";\nimport { inspect } from \"node:util\";\n\nimport {\n context as contextApi,\n propagation,\n trace,\n type Tracer,\n} from \"@opentelemetry/api\";\n\nimport { setExceptionReporter } from \"./error-reporter.js\";\nimport {\n bound,\n boundMessage,\n ERROR_GROUP,\n exceptionAttributes,\n type PathScrub,\n} from \"./node-exception.js\";\nimport {\n type Attributes,\n type AttributeValue,\n BatchQueue,\n encodeAttributes,\n exportSignal,\n type ExporterOptions,\n type FetchLike,\n type KeyValue,\n type LogRecord,\n logsPayload,\n MAX_ATTRIBUTE_CHARS,\n nowMs,\n type Scope,\n SEVERITY,\n type SeverityText,\n type SpanRecord,\n tracesPayload,\n unixNanos,\n} from \"./node-otlp.js\";\nimport {\n AsyncLocalContextManager,\n type ContextOps,\n NodeTracerProvider,\n TraceparentPropagator,\n} from \"./node-trace.js\";\nimport { redactSecrets } from \"./track.js\";\n\nexport { frontendServiceName } from \"./service-name.js\";\nexport {\n CODE_FILE,\n CODE_FUNCTION,\n CODE_LINE,\n ERROR_GROUP,\n EXCEPTION_MESSAGE,\n EXCEPTION_STACKTRACE,\n EXCEPTION_TYPE,\n exceptionAttributes,\n type PathScrub,\n} from \"./node-exception.js\";\n\n/** The instrumentation scope of every log record this entry writes. */\nexport const NODE_SCOPE_NAME = \"@reliantlabs/forge-web-runtime/node\";\n\n/** Body of the record for an uncaught exception. */\nexport const UNCAUGHT_EXCEPTION_BODY = \"uncaught exception\";\n/** Body of the record for an unhandled rejection. */\nexport const UNHANDLED_REJECTION_BODY = \"unhandled rejection\";\n/** Body of the record for an error passed to reportError / reportException. */\nexport const REPORTED_EXCEPTION_BODY = \"caught exception\";\n\n/** Longest log body. */\nexport const MAX_BODY_CHARS = 4096;\n/** Longest user id, as the browser entry bounds it. */\nexport const MAX_USER_ID_CHARS = 128;\n\nexport const DEFAULT_FLUSH_TIMEOUT_MS = 2_000;\nexport const DEFAULT_BATCH_DELAY_MS = 5_000;\nexport const DEFAULT_MAX_BATCH = 256;\nexport const DEFAULT_MAX_QUEUE = 2_048;\nexport const DEFAULT_EXPORT_TIMEOUT_MS = 10_000;\n/**\n * Most records of ONE fatal error (same type, message and frame) per minute.\n * A handler that throws while handling — an EPIPE logger writing to the pipe\n * that just broke — raises the same exception in a tight loop, and each one\n * would otherwise be a record and a request.\n */\nexport const MAX_REPEATS_PER_MINUTE = 5;\n\n/** A value a caller may attach to a log record; anything else is dropped. */\nexport type LogAttributes = Record<string, unknown>;\n\nexport interface NodeTelemetryConfig {\n /** `service.name`. For a forge frontend: frontendServiceName(project, frontend). */\n serviceName: string;\n /** `service.version` — the release. */\n serviceVersion?: string;\n /** `service.namespace`, conventionally the project name. */\n serviceNamespace?: string;\n /** Emitted as `deployment.environment.name` and the legacy `deployment.environment`. */\n environment?: string;\n /** The forge frontend this process belongs to, as `forge.frontend`. */\n frontend?: string;\n /** `app.client` on every record and span (`electron`, `cli`, ...). */\n appClient?: string;\n /**\n * The OTLP base URL: `${endpoint}/v1/logs` and `/v1/traces` are posted to.\n * The keyed ingest is `https://<ingest host>/k/<key>`. Unset, empty, `off`\n * or not an absolute http(s) URL disables telemetry: init returns null and\n * installs nothing.\n */\n endpoint?: string;\n /**\n * The `Origin` header sent with every export. The keyed ingest admits a\n * request only when its Origin is EXACTLY one of the key's allowed origins,\n * so a desktop app sends its renderer's origin (`app://bundle`).\n */\n origin?: string;\n /** Extra request headers. */\n headers?: Record<string, string>;\n /** Files below it are reported relative to it. Electron: app.getAppPath(). Default process.cwd(). */\n appRoot?: string;\n /** Extra resource attributes. They cannot override `service.name`. */\n resourceAttributes?: Record<string, string>;\n /** Record uncaught exceptions and unhandled rejections. Default true. */\n captureProcessErrors?: boolean;\n /** Register the tracer, context manager and propagator globally. Default true. */\n registerGlobal?: boolean;\n /** Least severe level the logger exports. Default `INFO`. */\n minSeverity?: SeverityText;\n /** How long a record may wait for its batch. Default 5s. */\n batchDelayMs?: number;\n /** Most records in one request. Default 256. */\n maxBatchSize?: number;\n /** Most records held per signal; more are dropped and counted. Default 2048. */\n maxQueueSize?: number;\n /** One request's budget. Default 10s. */\n exportTimeoutMs?: number;\n /** How long a fatal error's record may delay the process's default exit. Default 2s. */\n fatalFlushTimeoutMs?: number;\n /** Told when a batch is dropped (the ingest refused it or was unreachable). */\n onExportError?: (error: Error) => void;\n /** Test seam: the fetch exports go through. Default globalThis.fetch. */\n fetch?: FetchLike;\n}\n\nexport interface NodeLogger {\n debug(body: string, attributes?: LogAttributes): void;\n info(body: string, attributes?: LogAttributes): void;\n warn(body: string, attributes?: LogAttributes): void;\n /** An ERROR record. An Error among the attributes supplies its exception.* keys. */\n error(body: string, attributes?: LogAttributes): void;\n}\n\nexport interface NodeTelemetry {\n readonly logger: NodeLogger;\n /** The tracer to open spans on (also the global one, unless registerGlobal is false). */\n readonly tracer: Tracer;\n /** One ERROR record for an error the app caught. */\n reportError(error: unknown, attributes?: LogAttributes): void;\n /** The opaque `userId` on every later record and span; null clears it. Never an email. */\n setUser(id: string | null): void;\n /** Export everything queued; resolves by `timeoutMs` (default 2s) whatever the network does. */\n flush(timeoutMs?: number): Promise<void>;\n /** Flush, then stop: every later call is a no-op. init may run again afterwards. */\n shutdown(timeoutMs?: number): Promise<void>;\n /** Records refused because a queue was full. */\n readonly dropped: number;\n}\n\nlet current: NodeTelemetry | null = null;\n\n/** The running instance, or null before init / after shutdown. */\nexport function getNodeTelemetry(): NodeTelemetry | null {\n return current;\n}\n\n/**\n * The absolute OTLP base for `endpoint`, or null when telemetry is off: unset,\n * empty, `off`, or not an absolute http(s) URL. Trailing slashes are dropped.\n */\nexport function resolveNodeIngestUrl(\n endpoint: string | undefined,\n): string | null {\n const value = (endpoint ?? \"\").trim();\n if (value === \"\" || value.toLowerCase() === \"off\") {\n return null;\n }\n try {\n const url = new URL(value);\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n return null;\n }\n url.search = \"\";\n url.hash = \"\";\n return url.toString().replace(/\\/+$/, \"\");\n } catch {\n return null;\n }\n}\n\nfunction sdkVersion(): string {\n try {\n const req = createRequire(import.meta.url);\n const v = (req(\"../package.json\") as { version?: unknown }).version;\n return typeof v === \"string\" ? v : \"\";\n } catch {\n return \"\";\n }\n}\n\nconst OS_TYPE: Record<string, string> = { win32: \"windows\", sunos: \"solaris\" };\nconst HOST_ARCH: Record<string, string> = {\n x64: \"amd64\",\n ia32: \"x86\",\n ppc64: \"ppc64\",\n};\n\n/**\n * The resource every record and span carries. Pure given its inputs;\n * exported for tests and for an app that wants to see exactly what is sent.\n */\nexport function buildNodeResource(\n config: Pick<\n NodeTelemetryConfig,\n | \"serviceName\"\n | \"serviceVersion\"\n | \"serviceNamespace\"\n | \"environment\"\n | \"frontend\"\n | \"resourceAttributes\"\n >,\n runtime: {\n versions: Record<string, string | undefined>;\n platform: string;\n arch: string;\n },\n version = sdkVersion(),\n): Record<string, string> {\n const electron = runtime.versions.electron;\n const out: Record<string, string> = {\n \"telemetry.sdk.name\": \"@reliantlabs/forge-web-runtime\",\n \"telemetry.sdk.language\": \"nodejs\",\n \"process.runtime.name\": electron ? \"electron\" : \"nodejs\",\n \"process.runtime.version\": (electron ??\n runtime.versions.node ??\n \"\") as string,\n \"os.type\": OS_TYPE[runtime.platform] ?? runtime.platform,\n \"host.arch\": HOST_ARCH[runtime.arch] ?? runtime.arch,\n };\n if (version) out[\"telemetry.sdk.version\"] = version;\n if (config.serviceVersion) out[\"service.version\"] = config.serviceVersion;\n if (electron) {\n // A desktop app's main process: the same app.desktop.* its renderer\n // reports from the preload bridge (client-context.ts), so main-process\n // errors name the app, Electron and Chromium versions they ran on.\n if (config.serviceVersion)\n out[\"app.desktop.version\"] = config.serviceVersion;\n out[\"app.desktop.electron.version\"] = electron;\n if (runtime.versions.chrome)\n out[\"app.desktop.chromium.version\"] = runtime.versions.chrome;\n }\n if (config.serviceNamespace)\n out[\"service.namespace\"] = config.serviceNamespace;\n if (config.frontend) out[\"forge.frontend\"] = config.frontend;\n if (config.environment) {\n out[\"deployment.environment.name\"] = config.environment;\n out[\"deployment.environment\"] = config.environment;\n }\n Object.assign(out, config.resourceAttributes ?? {});\n out[\"service.name\"] = config.serviceName;\n return out;\n}\n\nfunction scrubString(value: string, max: number): string {\n return bound(redactSecrets(value.slice(0, max * 2)), max);\n}\n\n/**\n * Reduce caller attributes to what a record may carry: strings redacted and\n * bounded, finite numbers, booleans, arrays of those; an Error becomes its\n * bounded message (its stack goes in exception.*). Objects are dropped.\n */\nfunction sanitizeAttributes(attrs: LogAttributes | undefined): {\n attributes: Attributes;\n error: unknown;\n} {\n const out: Attributes = {};\n let error: unknown = undefined;\n if (!attrs || typeof attrs !== \"object\") {\n return { attributes: out, error };\n }\n for (const [key, v] of Object.entries(attrs)) {\n if (v instanceof Error) {\n if (error === undefined) error = v;\n out[key] = boundMessage(v.message ?? \"\");\n } else if (typeof v === \"string\") {\n out[key] = scrubString(v, MAX_ATTRIBUTE_CHARS);\n } else if (\n (typeof v === \"number\" && Number.isFinite(v)) ||\n typeof v === \"boolean\"\n ) {\n out[key] = v;\n } else if (Array.isArray(v)) {\n const kept = v\n .filter(\n (x) =>\n typeof x === \"string\" ||\n typeof x === \"boolean\" ||\n (typeof x === \"number\" && Number.isFinite(x)),\n )\n .map((x) =>\n typeof x === \"string\" ? scrubString(x, MAX_ATTRIBUTE_CHARS) : x,\n );\n out[key] = kept as AttributeValue;\n }\n }\n return { attributes: out, error };\n}\n\nfunction withTimeout(p: Promise<unknown>, ms: number): Promise<void> {\n return new Promise((resolve) => {\n const t = setTimeout(resolve, Math.max(0, ms));\n (t as { unref?: () => void }).unref?.();\n void p.then(\n () => {\n clearTimeout(t);\n resolve();\n },\n () => {\n clearTimeout(t);\n resolve();\n },\n );\n });\n}\n\n/** What `--unhandled-rejections` says Node does with a rejection nobody handles. */\nexport function unhandledRejectionMode(\n execArgv: readonly string[],\n nodeOptions: string | undefined,\n): string {\n const args = [...execArgv, ...(nodeOptions ?? \"\").split(/\\s+/)];\n let mode = \"throw\";\n for (let i = 0; i < args.length; i++) {\n const a = args[i] ?? \"\";\n if (a.startsWith(\"--unhandled-rejections=\")) {\n mode = a.slice(\"--unhandled-rejections=\".length);\n } else if (a === \"--unhandled-rejections\" && args[i + 1]) {\n mode = args[i + 1] as string;\n }\n }\n return mode;\n}\n\n/** The error Node raises for a rejection whose reason is not an Error. */\nfunction rejectionError(reason: unknown): Error {\n const err = new Error(\n \"This error originated either by throwing inside of an async function without a catch block, \" +\n \"or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason \" +\n `\"${inspect(reason)}\".`,\n );\n err.name = \"UnhandledPromiseRejection\";\n (err as { code?: string }).code = \"ERR_UNHANDLED_REJECTION\";\n return err;\n}\n\n/**\n * initNodeTelemetry starts telemetry for this process and returns it, or null\n * when it is off (no usable endpoint, no fetch, not Node). Idempotent: a\n * second call returns the running instance and ignores its config. Never\n * throws.\n */\nexport function initNodeTelemetry(\n config: NodeTelemetryConfig,\n): NodeTelemetry | null {\n if (current) {\n return current;\n }\n try {\n const instance = start(config);\n current = instance;\n return instance;\n } catch {\n // Telemetry must never break the app.\n return null;\n }\n}\n\nfunction start(config: NodeTelemetryConfig): NodeTelemetry | null {\n if (typeof process === \"undefined\" || !process.versions?.node) {\n return null;\n }\n const endpoint = resolveNodeIngestUrl(config.endpoint);\n const fetchFn: FetchLike | undefined =\n config.fetch ??\n (typeof globalThis.fetch === \"function\"\n ? (globalThis.fetch as unknown as FetchLike)\n : undefined);\n if (!endpoint || !fetchFn || !config.serviceName) {\n return null;\n }\n\n const isElectron = Boolean(process.versions.electron);\n const scrub: PathScrub = {\n appRoot: config.appRoot ?? process.cwd(),\n homeDir: safeHomedir(),\n caseInsensitive: process.platform === \"win32\",\n };\n const resource: KeyValue[] = encodeAttributes(\n buildNodeResource(config, {\n versions: process.versions,\n platform: process.platform,\n arch: process.arch,\n }),\n );\n const headers: Record<string, string> = { ...(config.headers ?? {}) };\n const origin = config.origin?.trim().replace(/\\/+$/, \"\");\n if (origin) {\n headers[\"origin\"] = origin;\n }\n const exporter: ExporterOptions = {\n endpoint,\n headers,\n fetch: fetchFn,\n timeoutMs: config.exportTimeoutMs ?? DEFAULT_EXPORT_TIMEOUT_MS,\n retries: 2,\n onError: config.onExportError,\n };\n const batch = {\n maxQueue: config.maxQueueSize ?? DEFAULT_MAX_QUEUE,\n maxBatch: config.maxBatchSize ?? DEFAULT_MAX_BATCH,\n delayMs: config.batchDelayMs ?? DEFAULT_BATCH_DELAY_MS,\n };\n const logScope: Scope = {\n name: NODE_SCOPE_NAME,\n version: sdkVersion() || undefined,\n };\n const logs = new BatchQueue<LogRecord>({\n ...batch,\n send: (records) =>\n exportSignal(exporter, \"logs\", logsPayload(resource, logScope, records)),\n });\n const spans = new BatchQueue<SpanRecord>({\n ...batch,\n send: (records) =>\n exportSignal(exporter, \"traces\", tracesPayload(resource, records)),\n });\n\n let stopped = false;\n let userId: string | null = null;\n const globals = (): Attributes => {\n const g: Attributes = {};\n if (config.appClient) g[\"app.client\"] = config.appClient;\n if (userId) g[\"userId\"] = userId;\n return g;\n };\n\n // Context: ours, registered globally when allowed. If another manager got\n // there first, spans follow ITS context so the app's context.active() and\n // ours agree.\n const manager = new AsyncLocalContextManager();\n const registerGlobal = config.registerGlobal ?? true;\n const ownsContext =\n registerGlobal && contextApi.setGlobalContextManager(manager);\n const ctxOps: ContextOps = registerGlobal\n ? {\n active: () => contextApi.active(),\n with: (c, fn) => contextApi.with(c, fn),\n }\n : { active: () => manager.active(), with: (c, fn) => manager.with(c, fn) };\n const provider = new NodeTracerProvider(\n (span) => {\n if (!stopped) spans.push(span);\n },\n globals,\n ctxOps,\n );\n const ownsTrace = registerGlobal && trace.setGlobalTracerProvider(provider);\n const ownsPropagation =\n registerGlobal &&\n propagation.setGlobalPropagator(new TraceparentPropagator());\n const tracer = provider.getTracer(NODE_SCOPE_NAME, sdkVersion() || undefined);\n\n const minSeverity = SEVERITY[config.minSeverity ?? \"INFO\"] ?? SEVERITY.INFO;\n\n function emit(\n severity: SeverityText,\n body: string,\n attributes: Attributes,\n ): void {\n if (stopped || SEVERITY[severity] < minSeverity) {\n return;\n }\n const now = unixNanos(nowMs());\n const record: LogRecord = {\n timeUnixNano: now,\n observedTimeUnixNano: now,\n severityNumber: SEVERITY[severity],\n severityText: severity,\n body: { stringValue: scrubString(body, MAX_BODY_CHARS) },\n attributes: encodeAttributes({ ...globals(), ...attributes }),\n };\n const sc = trace.getSpanContext(ctxOps.active());\n if (sc && trace.isSpanContextValid(sc)) {\n record.traceId = sc.traceId;\n record.spanId = sc.spanId;\n record.flags = sc.traceFlags;\n }\n logs.push(record);\n }\n\n function log(\n severity: SeverityText,\n body: string,\n attrs?: LogAttributes,\n ): void {\n try {\n const { attributes, error } = sanitizeAttributes(attrs);\n // As observe: only an ERROR record gets exception inputs, and an\n // attribute the caller set wins over the derived one.\n const derived =\n severity === \"ERROR\" && error !== undefined\n ? exceptionAttributes(error, scrub)\n : {};\n emit(severity, body, { ...derived, ...attributes });\n } catch {\n // Telemetry must never break the app.\n }\n }\n\n const logger: NodeLogger = {\n debug: (b, a) => log(\"DEBUG\", b, a),\n info: (b, a) => log(\"INFO\", b, a),\n warn: (b, a) => log(\"WARN\", b, a),\n error: (b, a) => log(\"ERROR\", b, a),\n };\n\n function reportError(error: unknown, attrs?: LogAttributes): void {\n try {\n const { attributes } = sanitizeAttributes(attrs);\n emit(\"ERROR\", REPORTED_EXCEPTION_BODY, {\n ...exceptionAttributes(error, scrub),\n ...attributes,\n });\n } catch {\n // Telemetry must never break the app.\n }\n }\n\n // ── Process errors ──────────────────────────────────────────────────────\n const recorded = new WeakSet<object>();\n const repeats = new Map<string, { since: number; count: number }>();\n let drainScheduled = false;\n function recordFatal(value: unknown, body: string): void {\n if (typeof value === \"object\" && value !== null) {\n if (recorded.has(value)) return;\n recorded.add(value);\n }\n try {\n const attributes = exceptionAttributes(value, scrub);\n const key = [\n body,\n attributes[\"exception.type\"],\n attributes[\"code.function.name\"],\n attributes[\"exception.message\"],\n ].join(\"\\u001f\");\n const now = Date.now();\n let seen = repeats.get(key);\n if (!seen || now - seen.since >= 60_000) {\n if (repeats.size >= 256) repeats.clear();\n seen = { since: now, count: 0 };\n repeats.set(key, seen);\n }\n if (++seen.count > MAX_REPEATS_PER_MINUTE) {\n return;\n }\n emit(\"ERROR\", body, attributes);\n // A fatal record does not wait for its batch; a burst in one turn of the\n // event loop goes out as one request.\n if (!drainScheduled) {\n drainScheduled = true;\n setImmediate(() => {\n drainScheduled = false;\n logs.drain();\n }).unref();\n }\n } catch {\n // Telemetry must never break the app.\n }\n }\n const fatalFlushMs = config.fatalFlushTimeoutMs ?? DEFAULT_FLUSH_TIMEOUT_MS;\n\n const onMonitor = (err: unknown, origin: string): void => {\n recordFatal(\n err,\n origin === \"unhandledRejection\"\n ? UNHANDLED_REJECTION_BODY\n : UNCAUGHT_EXCEPTION_BODY,\n );\n };\n const onRejection = (reason: unknown): void => {\n recordFatal(reason, UNHANDLED_REJECTION_BODY);\n if (process.listenerCount(\"unhandledRejection\") > 1) {\n return; // The app handles rejections; it decides what happens next.\n }\n const mode = unhandledRejectionMode(\n process.execArgv,\n process.env.NODE_OPTIONS,\n );\n if (mode === \"warn\" || mode === \"none\") {\n return;\n }\n if (mode === \"warn-with-error-code\") {\n process.exitCode = 1;\n return;\n }\n // throw / strict: what Node does with no listener at all — raise it as an\n // uncaught exception — once the record is out.\n const err = reason instanceof Error ? reason : rejectionError(reason);\n recorded.add(err);\n void withTimeout(flushAll(), fatalFlushMs).then(() =>\n process.nextTick(() => {\n throw err;\n }),\n );\n };\n let exiting = false;\n const onUncaught = (err: unknown): void => {\n if (process.listenerCount(\"uncaughtException\") > 1 || exiting) {\n return; // The app's own handler owns the process's fate.\n }\n // Node's default (print, exit 1), after the record is out.\n exiting = true;\n void withTimeout(flushAll(), fatalFlushMs).then(() => {\n try {\n process.stderr.write(\n `${err instanceof Error && err.stack ? err.stack : inspect(err)}\\n`,\n );\n } catch {\n // stderr may be gone; the exit code still says what happened.\n }\n process.exit(1);\n });\n };\n const onBeforeExit = (): void => {\n if (logs.size > 0 || spans.size > 0) {\n void flushAll();\n }\n };\n\n const captureErrors = config.captureProcessErrors ?? true;\n if (captureErrors) {\n process.on(\"uncaughtExceptionMonitor\", onMonitor);\n process.on(\"unhandledRejection\", onRejection);\n if (!isElectron) {\n process.on(\"uncaughtException\", onUncaught);\n }\n }\n process.on(\"beforeExit\", onBeforeExit);\n setExceptionReporter((error, attributes) => reportError(error, attributes));\n\n function flushAll(): Promise<void> {\n return Promise.all([logs.flush(), spans.flush()]).then(() => undefined);\n }\n\n const instance: NodeTelemetry = {\n logger,\n tracer,\n reportError,\n setUser(id) {\n userId =\n typeof id === \"string\" && id !== \"\"\n ? id.slice(0, MAX_USER_ID_CHARS)\n : null;\n },\n flush(timeoutMs = DEFAULT_FLUSH_TIMEOUT_MS) {\n return withTimeout(flushAll(), timeoutMs);\n },\n async shutdown(timeoutMs = DEFAULT_FLUSH_TIMEOUT_MS) {\n if (stopped) return;\n await withTimeout(flushAll(), timeoutMs);\n stopped = true;\n process.removeListener(\"uncaughtExceptionMonitor\", onMonitor);\n process.removeListener(\"unhandledRejection\", onRejection);\n process.removeListener(\"uncaughtException\", onUncaught);\n process.removeListener(\"beforeExit\", onBeforeExit);\n setExceptionReporter(null);\n if (ownsTrace) trace.disable();\n if (ownsPropagation) propagation.disable();\n if (ownsContext) contextApi.disable();\n if (current === instance) current = null;\n },\n get dropped() {\n return logs.dropped + spans.dropped;\n },\n };\n return instance;\n}\n\nfunction safeHomedir(): string | undefined {\n try {\n return homedir() || undefined;\n } catch {\n return undefined;\n }\n}\n\n/** The slice of Electron's `app` flushOnQuit needs. */\nexport interface QuitApp {\n on(\n event: \"before-quit\",\n listener: (event: { preventDefault(): void }) => void,\n ): unknown;\n removeListener?(\n event: \"before-quit\",\n listener: (event: { preventDefault(): void }) => void,\n ): unknown;\n quit(): void;\n}\n\n/**\n * Hold Electron's quit until the running telemetry has flushed (bounded by\n * `timeoutMs`, default 2s), then quit. Returns a function that removes the\n * hook.\n *\n * It defers the FIRST quit and calls app.quit() again when the flush is done.\n * An app that already defers quit for its own shutdown (stops a backend, then\n * exits) should instead `await telemetry.flush()` as the last step of that\n * sequence: two listeners that each defer and re-quit cut each other short.\n */\nexport function flushOnQuit(\n app: QuitApp,\n options: { timeoutMs?: number } = {},\n): () => void {\n let flushed = false;\n const listener = (event: { preventDefault(): void }): void => {\n const t = current;\n if (flushed || !t) {\n return;\n }\n flushed = true;\n event.preventDefault();\n void t\n .flush(options.timeoutMs ?? DEFAULT_FLUSH_TIMEOUT_MS)\n .finally(() => app.quit());\n };\n app.on(\"before-quit\", listener);\n return () => {\n app.removeListener?.(\"before-quit\", listener);\n };\n}\n"]}
@@ -1,9 +1,21 @@
1
- import { type ReactElement, type ReactNode } from "react";
1
+ import { type KeyboardEvent, type MouseEvent, type ReactElement, type ReactNode } from "react";
2
2
  export interface ResourceColumn<T> {
3
3
  header: string;
4
4
  cell: (row: T) => ReactNode;
5
5
  /** Extra classes for the cell + header (alignment, width). */
6
6
  className?: string;
7
+ /**
8
+ * Makes the column sortable: the server-side sort key (the List request's
9
+ * `order_by` value, a column name) this header sorts by. The header renders
10
+ * a button and `aria-sort`; the page owns the state through
11
+ * `sort` / `onSortChange`.
12
+ */
13
+ sortKey?: string;
14
+ }
15
+ /** The active server-side sort: an `order_by` key and its direction. */
16
+ export interface ResourceSort {
17
+ key: string;
18
+ descending: boolean;
7
19
  }
8
20
  export type ResourceStatus = "loading" | "error" | "success";
9
21
  export interface ResourceProps<T> {
@@ -12,22 +24,69 @@ export interface ResourceProps<T> {
12
24
  columns: ResourceColumn<T>[];
13
25
  rowKey: (row: T) => string;
14
26
  title?: string;
15
- /** Header-right slot, e.g. a "New" button. */
27
+ /** Header-right slot: the page's primary action, e.g. a "New" button. */
16
28
  actions?: ReactNode;
29
+ /**
30
+ * Filter controls, rendered in their own wrapping row beside the search
31
+ * box — never in the header, so a page with several facets still fits a
32
+ * phone. Pass a filter bar, selects, pickers.
33
+ */
34
+ toolbar?: ReactNode;
17
35
  error?: Error | null;
18
36
  onRetry?: () => void;
37
+ /**
38
+ * Clicking anywhere on a row calls it; a click that lands on a link or
39
+ * another control inside the row is left to that control. Without
40
+ * `rowsHaveLinks` the row is also the keyboard path to the record: it takes
41
+ * a tab stop, `role="button"`, and Enter/Space call this.
42
+ */
19
43
  onRowClick?: (row: T) => void;
44
+ /**
45
+ * Declares that every row renders a real link to its record (in the
46
+ * primary cell), so `onRowClick` is a POINTER convenience only: the row
47
+ * keeps table semantics (no button role, no tab stop) and keyboard and
48
+ * screen-reader users reach the record through the link. Set it only when
49
+ * the link is there — without it, a row with `onRowClick` stays keyboard
50
+ * operable itself, so a page that predates the link is never stranded.
51
+ */
52
+ rowsHaveLinks?: boolean;
20
53
  emptyTitle?: string;
21
54
  emptyMessage?: string;
22
55
  filter?: string;
23
56
  onFilterChange?: (value: string) => void;
24
57
  filterPlaceholder?: string;
25
58
  filterDebounceMs?: number;
59
+ sort?: ResourceSort | null;
60
+ onSortChange?: (sort: ResourceSort | null) => void;
26
61
  onNextPage?: () => void;
27
62
  onPrevPage?: () => void;
28
63
  hasNextPage?: boolean;
29
64
  hasPrevPage?: boolean;
30
65
  /** True during a background refetch (page change / filter change). */
31
66
  isFetching?: boolean;
67
+ /**
68
+ * The total number of matching records (the List response's total_count),
69
+ * shown in the footer: "1–25 of 112". Omit when the API does not report it.
70
+ */
71
+ totalCount?: number;
72
+ /** Zero-based index of this page's first row, for the footer's range. */
73
+ pageStart?: number;
74
+ }
75
+ export declare function Resource<T>({ status, data, columns, rowKey, title, actions, toolbar, error, onRetry, onRowClick, rowsHaveLinks, emptyTitle, emptyMessage, filter, onFilterChange, filterPlaceholder, filterDebounceMs, sort, onSortChange, onNextPage, onPrevPage, hasNextPage, hasPrevPage, isFetching, totalCount, pageStart, }: ResourceProps<T>): ReactElement;
76
+ /** The props rowInteraction puts on a body row. */
77
+ export interface RowInteraction {
78
+ onClick?: (e: MouseEvent<HTMLTableRowElement>) => void;
79
+ onKeyDown?: (e: KeyboardEvent<HTMLTableRowElement>) => void;
80
+ role?: "button";
81
+ tabIndex?: number;
82
+ className?: string;
32
83
  }
33
- export declare function Resource<T>({ status, data, columns, rowKey, title, actions, error, onRetry, onRowClick, emptyTitle, emptyMessage, filter, onFilterChange, filterPlaceholder, filterDebounceMs, onNextPage, onPrevPage, hasNextPage, hasPrevPage, isFetching, }: ResourceProps<T>): ReactElement;
84
+ /**
85
+ * rowInteraction is how a body row responds to its onRowClick. A row with
86
+ * no onRowClick is inert data. A row whose cells link to the record
87
+ * (`rowsHaveLinks`) gets a pointer-only click and keeps table semantics. A
88
+ * row with onRowClick and no link is the only way to its record, so it is
89
+ * keyboard operable itself: a tab stop, a button role, Enter/Space. Exported
90
+ * for its tests; not part of the package's index.
91
+ */
92
+ export declare function rowInteraction<T>(row: T, onRowClick: ((row: T) => void) | undefined, rowsHaveLinks: boolean): RowInteraction;
package/dist/resource.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  // Part of @reliantlabs/forge-web-runtime — the web twin of forge/pkg.
4
4
  //
5
5
  // <Resource> — the owned-once data-table container. It encapsulates the
@@ -11,9 +11,25 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  // { status, data, error } and hand it here. Pagination is
12
12
  // cursor-based (page_token / next_page_token in Connect list RPCs): the page
13
13
  // owns the cursor stack and passes onNextPage/onPrevPage.
14
- import { useEffect, useState } from "react";
14
+ //
15
+ // Layout, top to bottom: a header (title + primary action), a toolbar row
16
+ // (search + the page's `toolbar` facets) that WRAPS, the table, and a footer
17
+ // (the record count + the pager). Filters used to share the header row with
18
+ // the title and the create button in one non-wrapping flex row, which pushed
19
+ // the button off-canvas on a phone; they now have a row of their own.
20
+ import { useEffect, useState, } from "react";
15
21
  import { userMessage } from "./errors.js";
16
- export function Resource({ status, data, columns, rowKey, title, actions, error, onRetry, onRowClick, emptyTitle = "Nothing here yet", emptyMessage = "No records match.", filter, onFilterChange, filterPlaceholder = "Search…", filterDebounceMs = 300, onNextPage, onPrevPage, hasNextPage, hasPrevPage, isFetching, }) {
22
+ // The controls a row click must not hijack: a click on any of these inside a
23
+ // row belongs to that control (and a link inside the row already navigates).
24
+ const INTERACTIVE = "a, button, input, select, textarea, label, [role='button'], [role='combobox']";
25
+ function nextSort(current, key) {
26
+ if (current?.key !== key)
27
+ return { key, descending: false };
28
+ if (!current.descending)
29
+ return { key, descending: true };
30
+ return null;
31
+ }
32
+ export function Resource({ status, data, columns, rowKey, title, actions, toolbar, error, onRetry, onRowClick, rowsHaveLinks = false, emptyTitle = "Nothing here yet", emptyMessage = "No records match.", filter, onFilterChange, filterPlaceholder = "Search…", filterDebounceMs = 300, sort, onSortChange, onNextPage, onPrevPage, hasNextPage, hasPrevPage, isFetching, totalCount, pageStart, }) {
17
33
  const [query, setQuery] = useState(filter ?? "");
18
34
  // Keep local input in sync when the parent resets the filter externally.
19
35
  useEffect(() => {
@@ -27,22 +43,85 @@ export function Resource({ status, data, columns, rowKey, title, actions, error,
27
43
  const handle = setTimeout(() => onFilterChange(query), filterDebounceMs);
28
44
  return () => clearTimeout(handle);
29
45
  }, [query, filter, onFilterChange, filterDebounceMs]);
30
- const showFooter = Boolean(onNextPage || onPrevPage);
31
46
  const colCount = columns.length;
32
47
  const captionText = title ?? "Results";
33
- return (_jsxs("section", { className: "flex flex-col gap-4", children: [(title || actions) && (_jsxs("header", { className: "flex items-center justify-between gap-4", children: [title ? (_jsx("h1", { className: "text-xl font-semibold text-ink", children: title })) : (_jsx("span", {})), actions ? (_jsx("div", { className: "flex items-center gap-2", children: actions })) : null] })), onFilterChange ? (_jsx("input", { type: "search", value: query, onChange: (e) => setQuery(e.target.value), placeholder: filterPlaceholder, "aria-label": title ? `Search ${title.toLowerCase()}` : "Search", className: "w-full max-w-sm rounded-md border border-border bg-surface px-3 py-2 text-sm text-ink shadow-sm focus:border-accent focus:outline-none focus:ring-1 focus:ring-accent" })) : null, _jsx("div", { className: "overflow-x-auto rounded-lg border border-border bg-surface", children: _jsxs("table", { className: "min-w-full divide-y divide-border text-sm", children: [_jsx("caption", { className: "sr-only", children: captionText }), _jsx("thead", { className: "bg-surface-muted", children: _jsx("tr", { children: columns.map((col, i) => (_jsx("th", { scope: "col", className: `px-4 py-2.5 text-left font-medium text-ink-muted ${col.className ?? ""}`, children: col.header }, i))) }) }), _jsx("tbody", { className: "divide-y divide-border", children: status === "loading" ? (_jsx(SkeletonRows, { rows: 5, cols: colCount })) : status === "error" ? (_jsx("tr", { children: _jsx("td", { colSpan: colCount, className: "px-4 py-10", children: _jsx(ErrorState, { error: error, onRetry: onRetry }) }) })) : !data || data.length === 0 ? (_jsx("tr", { children: _jsx("td", { colSpan: colCount, className: "px-4 py-12", children: _jsx(EmptyState, { title: emptyTitle, message: emptyMessage }) }) })) : (data.map((row) => (_jsx("tr", { onClick: onRowClick ? () => onRowClick(row) : undefined,
34
- // Keyboard-operable when the row navigates: Enter/Space
35
- // activate it, and it takes focus in tab order.
36
- role: onRowClick ? "button" : undefined, tabIndex: onRowClick ? 0 : undefined, onKeyDown: onRowClick
37
- ? (e) => {
38
- if (e.key === "Enter" || e.key === " ") {
39
- e.preventDefault();
40
- onRowClick(row);
41
- }
42
- }
43
- : undefined, className: onRowClick
44
- ? "cursor-pointer transition-colors hover:bg-surface-muted focus:bg-surface-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-accent"
45
- : undefined, children: columns.map((col, i) => (_jsx("td", { className: `px-4 py-2.5 text-ink ${col.className ?? ""}`, children: col.cell(row) }, i))) }, rowKey(row))))) })] }) }), showFooter ? (_jsxs("footer", { className: "flex items-center justify-end gap-2", children: [isFetching ? (_jsx("span", { className: "mr-auto text-xs text-ink-subtle", children: "Loading\u2026" })) : null, _jsx("button", { type: "button", onClick: onPrevPage, disabled: !hasPrevPage, className: "rounded-md border border-border px-3 py-1.5 text-sm font-medium text-ink-muted hover:bg-surface-muted disabled:cursor-not-allowed disabled:opacity-40", children: "Previous" }), _jsx("button", { type: "button", onClick: onNextPage, disabled: !hasNextPage, className: "rounded-md border border-border px-3 py-1.5 text-sm font-medium text-ink-muted hover:bg-surface-muted disabled:cursor-not-allowed disabled:opacity-40", children: "Next" })] })) : null] }));
48
+ const rows = status === "success" && data ? data.length : 0;
49
+ const showPager = Boolean(onNextPage || onPrevPage) && Boolean(hasNextPage || hasPrevPage);
50
+ const countText = describeCount(rows, totalCount, pageStart);
51
+ const showFooter = showPager || countText !== "" || Boolean(isFetching);
52
+ const showToolbar = Boolean(onFilterChange || toolbar);
53
+ return (_jsxs("section", { className: "flex flex-col gap-4", children: [(title || actions) && (_jsxs("header", { className: "flex flex-wrap items-center justify-between gap-3", children: [title ? (_jsx("h1", { className: "text-xl font-semibold text-ink", children: title })) : (_jsx("span", {})), actions ? (_jsx("div", { className: "flex flex-wrap items-center gap-2", children: actions })) : null] })), showToolbar ? (_jsxs("div", { className: "flex flex-wrap items-start gap-2", children: [onFilterChange ? (_jsx("input", { type: "search", value: query, onChange: (e) => setQuery(e.target.value), placeholder: filterPlaceholder, "aria-label": title ? `Search ${title.toLowerCase()}` : "Search", className: "h-9 w-full min-w-0 rounded-md border border-border-strong bg-surface px-3 text-sm text-ink placeholder:text-ink-subtle focus:border-accent focus:outline-none focus:ring-1 focus:ring-accent sm:w-64" })) : null, toolbar ? _jsx("div", { className: "min-w-0 flex-1", children: toolbar }) : null] })) : null, _jsx("div", { className: "overflow-x-auto rounded-lg border border-border bg-surface", children: _jsxs("table", { className: "min-w-full divide-y divide-border text-sm", children: [_jsx("caption", { className: "sr-only", children: captionText }), _jsx("thead", { className: "bg-surface-muted", children: _jsx("tr", { children: columns.map((col, i) => {
54
+ const sortable = Boolean(col.sortKey && onSortChange);
55
+ const active = sortable && sort?.key === col.sortKey;
56
+ return (_jsx("th", { scope: "col", "aria-sort": active
57
+ ? sort?.descending
58
+ ? "descending"
59
+ : "ascending"
60
+ : undefined, className: `px-4 py-2.5 text-left font-medium text-ink-muted ${col.className ?? ""}`, children: sortable ? (_jsxs("button", { type: "button", onClick: () => onSortChange?.(nextSort(sort, col.sortKey ?? "")), className: "-mx-1 inline-flex items-center gap-1 rounded px-1 hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent", children: [col.header, _jsx("span", { "aria-hidden": "true", className: active ? "text-ink" : "text-ink-subtle", children: active ? (sort?.descending ? "↓" : "↑") : "↕" })] })) : (col.header) }, i));
61
+ }) }) }), _jsx("tbody", { className: "divide-y divide-border", children: status === "loading" ? (_jsx(SkeletonRows, { rows: 5, cols: colCount })) : status === "error" ? (_jsx("tr", { children: _jsx("td", { colSpan: colCount, className: "px-4 py-10", children: _jsx(ErrorState, { error: error, onRetry: onRetry }) }) })) : !data || data.length === 0 ? (_jsx("tr", { children: _jsx("td", { colSpan: colCount, className: "px-4 py-12", children: _jsx(EmptyState, { title: emptyTitle, message: emptyMessage }) }) })) : (data.map((row) => (_jsx("tr", { ...rowInteraction(row, onRowClick, rowsHaveLinks), children: columns.map((col, i) => (_jsx("td", { className: `px-4 py-2.5 text-ink ${col.className ?? ""}`, children: col.cell(row) }, i))) }, rowKey(row))))) })] }) }), showFooter ? (_jsxs("footer", { className: "flex flex-wrap items-center justify-end gap-2", children: [_jsx("span", { "aria-live": "polite", className: "mr-auto text-sm text-ink-muted tabular-nums", children: isFetching ? "Loading…" : countText }), showPager ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", onClick: onPrevPage, disabled: !hasPrevPage, className: "h-9 rounded-md border border-border-strong px-3 text-sm font-medium text-ink hover:bg-surface-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent disabled:cursor-not-allowed disabled:opacity-40", children: "Previous" }), _jsx("button", { type: "button", onClick: onNextPage, disabled: !hasNextPage, className: "h-9 rounded-md border border-border-strong px-3 text-sm font-medium text-ink hover:bg-surface-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent disabled:cursor-not-allowed disabled:opacity-40", children: "Next" })] })) : null] })) : null] }));
62
+ }
63
+ /**
64
+ * rowInteraction is how a body row responds to its onRowClick. A row with
65
+ * no onRowClick is inert data. A row whose cells link to the record
66
+ * (`rowsHaveLinks`) gets a pointer-only click and keeps table semantics. A
67
+ * row with onRowClick and no link is the only way to its record, so it is
68
+ * keyboard operable itself: a tab stop, a button role, Enter/Space. Exported
69
+ * for its tests; not part of the package's index.
70
+ */
71
+ export function rowInteraction(row, onRowClick, rowsHaveLinks) {
72
+ if (!onRowClick)
73
+ return {};
74
+ const onClick = (e) => {
75
+ // Leave clicks on the row's own controls (the primary-cell link, a
76
+ // menu) to those controls, and never turn a text selection into a
77
+ // navigation. Only a control INSIDE the row counts: a keyboard-operable
78
+ // row is itself role="button", and closest() matches the row too.
79
+ const control = e.target.closest(INTERACTIVE);
80
+ if (control &&
81
+ control !== e.currentTarget &&
82
+ e.currentTarget.contains(control)) {
83
+ return;
84
+ }
85
+ if (window.getSelection()?.toString())
86
+ return;
87
+ onRowClick(row);
88
+ };
89
+ if (rowsHaveLinks) {
90
+ return {
91
+ onClick,
92
+ className: "cursor-pointer transition-colors hover:bg-surface-muted focus-within:bg-surface-muted",
93
+ };
94
+ }
95
+ return {
96
+ onClick,
97
+ role: "button",
98
+ tabIndex: 0,
99
+ onKeyDown: (e) => {
100
+ // Only the row's own keys: Enter on a control inside the row is
101
+ // that control's.
102
+ if (e.target !== e.currentTarget)
103
+ return;
104
+ if (e.key === "Enter" || e.key === " ") {
105
+ e.preventDefault();
106
+ onRowClick(row);
107
+ }
108
+ },
109
+ className: "cursor-pointer transition-colors hover:bg-surface-muted focus:bg-surface-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-accent",
110
+ };
111
+ }
112
+ /**
113
+ * describeCount is the footer's record count: "1–25 of 112" with a known page
114
+ * offset, "112 results" without one, and nothing while no row is shown.
115
+ */
116
+ function describeCount(rows, total, pageStart) {
117
+ if (total === undefined || rows === 0)
118
+ return "";
119
+ if (pageStart === undefined) {
120
+ return `${total.toLocaleString()} ${total === 1 ? "result" : "results"}`;
121
+ }
122
+ const first = (pageStart + 1).toLocaleString();
123
+ const last = (pageStart + rows).toLocaleString();
124
+ return `${first}–${last} of ${total.toLocaleString()}`;
46
125
  }
47
126
  // SkeletonRows is the loading rung of the tristate ladder: a shimmer the
48
127
  // width of the table while the first page is in flight.