@reliantlabs/forge-web-runtime 0.12.0 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -0
- package/dist/benign-errors.d.ts +33 -0
- package/dist/benign-errors.js +115 -0
- package/dist/benign-errors.js.map +1 -0
- package/dist/client-context.d.ts +35 -0
- package/dist/client-context.js +204 -0
- package/dist/client-context.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interceptors.js +10 -3
- package/dist/interceptors.js.map +1 -1
- package/dist/mock-transport.d.ts +41 -5
- package/dist/mock-transport.js +125 -7
- package/dist/mock-transport.js.map +1 -1
- package/dist/node.js +10 -0
- package/dist/node.js.map +1 -1
- package/dist/resource.d.ts +62 -3
- package/dist/resource.js +96 -17
- package/dist/resource.js.map +1 -1
- package/dist/service-hooks.js +8 -6
- package/dist/service-hooks.js.map +1 -1
- package/dist/session-cookie-fallback.d.ts +23 -0
- package/dist/session-cookie-fallback.js +203 -0
- package/dist/session-cookie-fallback.js.map +1 -0
- package/dist/telemetry-gate.d.ts +4 -0
- package/dist/telemetry-gate.js +25 -0
- package/dist/telemetry-gate.js.map +1 -1
- package/dist/telemetry.d.ts +29 -2
- package/dist/telemetry.js +370 -17
- package/dist/telemetry.js.map +1 -1
- package/dist/trace.d.ts +7 -0
- package/dist/trace.js +21 -2
- package/dist/trace.js.map +1 -1
- package/package.json +4 -2
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":"AAAA,sEAAsE;AACtE,EAAE;AACF,+EAA+E;AAC/E,4EAA4E;AAC5E,8EAA8E;AAC9E,+BAA+B;AAC/B,EAAE;AACF,4EAA4E;AAC5E,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,gDAAgD;AAChD,2EAA2E;AAC3E,uEAAuE;AACvE,+EAA+E;AAC/E,EAAE;AACF,2EAA2E;AAC3E,6EAA6E;AAC7E,0BAA0B;AAC1B,EAAE;AACF,4EAA4E;AAC5E,EAAE;AACF,wEAAwE;AACxE,8EAA8E;AAC9E,8EAA8E;AAC9E,8EAA8E;AAC9E,wEAAwE;AACxE,8EAA8E;AAC9E,sEAAsE;AACtE,gEAAgE;AAChE,6EAA6E;AAC7E,4EAA4E;AAC5E,6EAA6E;AAC7E,2DAA2D;AAC3D,EAAE;AACF,4EAA4E;AAC5E,EAAE;AACF,0EAA0E;AAC1E,2EAA2E;AAC3E,8EAA8E;AAC9E,6EAA6E;AAC7E,oEAAoE;AACpE,gBAAgB;AAChB,EAAE;AACF,4EAA4E;AAC5E,EAAE;AACF,6EAA6E;AAC7E,6EAA6E;AAC7E,6EAA6E;AAC7E,6EAA6E;AAC7E,8EAA8E;AAC9E,6EAA6E;AAC7E,eAAe;AACf,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EACL,OAAO,IAAI,UAAU,EACrB,WAAW,EACX,KAAK,GAEN,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EACL,KAAK,EACL,YAAY,EAEZ,mBAAmB,GAEpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAGL,UAAU,EACV,gBAAgB,EAChB,YAAY,EAKZ,WAAW,EACX,mBAAmB,EACnB,KAAK,EAEL,QAAQ,EAGR,aAAa,EACb,SAAS,GACV,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,wBAAwB,EAExB,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EACL,SAAS,EACT,aAAa,EACb,SAAS,EACT,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,cAAc,EACd,mBAAmB,GAEpB,MAAM,qBAAqB,CAAC;AAE7B,uEAAuE;AACvE,MAAM,CAAC,MAAM,eAAe,GAAG,qCAAqC,CAAC;AAErE,oDAAoD;AACpD,MAAM,CAAC,MAAM,uBAAuB,GAAG,oBAAoB,CAAC;AAC5D,qDAAqD;AACrD,MAAM,CAAC,MAAM,wBAAwB,GAAG,qBAAqB,CAAC;AAC9D,+EAA+E;AAC/E,MAAM,CAAC,MAAM,uBAAuB,GAAG,kBAAkB,CAAC;AAE1D,wBAAwB;AACxB,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC;AACnC,uDAAuD;AACvD,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAErC,MAAM,CAAC,MAAM,wBAAwB,GAAG,KAAK,CAAC;AAC9C,MAAM,CAAC,MAAM,sBAAsB,GAAG,KAAK,CAAC;AAC5C,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AACrC,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,CAAC;AACvC,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC;AAChD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC;AAmFxC,IAAI,OAAO,GAAyB,IAAI,CAAC;AAEzC,kEAAkE;AAClE,MAAM,UAAU,gBAAgB;IAC9B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,QAA4B;IAE5B,MAAM,KAAK,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACtC,IAAI,KAAK,KAAK,EAAE,IAAI,KAAK,CAAC,WAAW,EAAE,KAAK,KAAK,EAAE,CAAC;QAClD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;QAC3B,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAC1D,OAAO,IAAI,CAAC;QACd,CAAC;QACD,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC;QAChB,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC;QACd,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,UAAU;IACjB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3C,MAAM,CAAC,GAAI,GAAG,CAAC,iBAAiB,CAA2B,CAAC,OAAO,CAAC;QACpE,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,OAAO,GAA2B,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AAC/E,MAAM,SAAS,GAA2B;IACxC,GAAG,EAAE,OAAO;IACZ,IAAI,EAAE,KAAK;IACX,KAAK,EAAE,OAAO;CACf,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAQC,EACD,OAIC,EACD,OAAO,GAAG,UAAU,EAAE;IAEtB,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAC3C,MAAM,GAAG,GAA2B;QAClC,oBAAoB,EAAE,gCAAgC;QACtD,wBAAwB,EAAE,QAAQ;QAClC,sBAAsB,EAAE,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ;QACxD,yBAAyB,EAAE,CAAC,QAAQ;YAClC,OAAO,CAAC,QAAQ,CAAC,IAAI;YACrB,EAAE,CAAW;QACf,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,QAAQ;QACxD,WAAW,EAAE,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,IAAI;KACrD,CAAC;IACF,IAAI,OAAO;QAAE,GAAG,CAAC,uBAAuB,CAAC,GAAG,OAAO,CAAC;IACpD,IAAI,MAAM,CAAC,cAAc;QAAE,GAAG,CAAC,iBAAiB,CAAC,GAAG,MAAM,CAAC,cAAc,CAAC;IAC1E,IAAI,QAAQ,EAAE,CAAC;QACb,oEAAoE;QACpE,uEAAuE;QACvE,mEAAmE;QACnE,IAAI,MAAM,CAAC,cAAc;YACvB,GAAG,CAAC,qBAAqB,CAAC,GAAG,MAAM,CAAC,cAAc,CAAC;QACrD,GAAG,CAAC,8BAA8B,CAAC,GAAG,QAAQ,CAAC;QAC/C,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM;YACzB,GAAG,CAAC,8BAA8B,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAClE,CAAC;IACD,IAAI,MAAM,CAAC,gBAAgB;QACzB,GAAG,CAAC,mBAAmB,CAAC,GAAG,MAAM,CAAC,gBAAgB,CAAC;IACrD,IAAI,MAAM,CAAC,QAAQ;QAAE,GAAG,CAAC,gBAAgB,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC;IAC7D,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QACvB,GAAG,CAAC,6BAA6B,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC;QACxD,GAAG,CAAC,wBAAwB,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC;IACrD,CAAC;IACD,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,kBAAkB,IAAI,EAAE,CAAC,CAAC;IACpD,GAAG,CAAC,cAAc,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC;IACzC,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,WAAW,CAAC,KAAa,EAAE,GAAW;IAC7C,OAAO,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAC5D,CAAC;AAED;;;;GAIG;AACH,SAAS,kBAAkB,CAAC,KAAgC;IAI1D,MAAM,GAAG,GAAe,EAAE,CAAC;IAC3B,IAAI,KAAK,GAAY,SAAS,CAAC;IAC/B,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACxC,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;IACpC,CAAC;IACD,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7C,IAAI,CAAC,YAAY,KAAK,EAAE,CAAC;YACvB,IAAI,KAAK,KAAK,SAAS;gBAAE,KAAK,GAAG,CAAC,CAAC;YACnC,GAAG,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;QAC3C,CAAC;aAAM,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;YACjC,GAAG,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,CAAC,EAAE,mBAAmB,CAAC,CAAC;QACjD,CAAC;aAAM,IACL,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YAC7C,OAAO,CAAC,KAAK,SAAS,EACtB,CAAC;YACD,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACf,CAAC;aAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,CAAC;iBACX,MAAM,CACL,CAAC,CAAC,EAAE,EAAE,CACJ,OAAO,CAAC,KAAK,QAAQ;gBACrB,OAAO,CAAC,KAAK,SAAS;gBACtB,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAChD;iBACA,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACT,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,CAChE,CAAC;YACJ,GAAG,CAAC,GAAG,CAAC,GAAG,IAAsB,CAAC;QACpC,CAAC;IACH,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;AACpC,CAAC;AAED,SAAS,WAAW,CAAC,CAAmB,EAAE,EAAU;IAClD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,CAAC,GAAG,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QAC9C,CAA4B,CAAC,KAAK,EAAE,EAAE,CAAC;QACxC,KAAK,CAAC,CAAC,IAAI,CACT,GAAG,EAAE;YACH,YAAY,CAAC,CAAC,CAAC,CAAC;YAChB,OAAO,EAAE,CAAC;QACZ,CAAC,EACD,GAAG,EAAE;YACH,YAAY,CAAC,CAAC,CAAC,CAAC;YAChB,OAAO,EAAE,CAAC;QACZ,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,sBAAsB,CACpC,QAA2B,EAC3B,WAA+B;IAE/B,MAAM,IAAI,GAAG,CAAC,GAAG,QAAQ,EAAE,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IAChE,IAAI,IAAI,GAAG,OAAO,CAAC;IACnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACxB,IAAI,CAAC,CAAC,UAAU,CAAC,yBAAyB,CAAC,EAAE,CAAC;YAC5C,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,yBAAyB,CAAC,MAAM,CAAC,CAAC;QACnD,CAAC;aAAM,IAAI,CAAC,KAAK,wBAAwB,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACzD,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAW,CAAC;QAC/B,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,0EAA0E;AAC1E,SAAS,cAAc,CAAC,MAAe;IACrC,MAAM,GAAG,GAAG,IAAI,KAAK,CACnB,8FAA8F;QAC5F,sGAAsG;QACtG,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,CAC1B,CAAC;IACF,GAAG,CAAC,IAAI,GAAG,2BAA2B,CAAC;IACtC,GAAyB,CAAC,IAAI,GAAG,yBAAyB,CAAC;IAC5D,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAA2B;IAE3B,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;QAC/B,OAAO,GAAG,QAAQ,CAAC;QACnB,OAAO,QAAQ,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,sCAAsC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,KAAK,CAAC,MAA2B;IACxC,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC;QAC9D,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,QAAQ,GAAG,oBAAoB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACvD,MAAM,OAAO,GACX,MAAM,CAAC,KAAK;QACZ,CAAC,OAAO,UAAU,CAAC,KAAK,KAAK,UAAU;YACrC,CAAC,CAAE,UAAU,CAAC,KAA8B;YAC5C,CAAC,CAAC,SAAS,CAAC,CAAC;IACjB,IAAI,CAAC,QAAQ,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QACjD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACtD,MAAM,KAAK,GAAc;QACvB,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE;QACxC,OAAO,EAAE,WAAW,EAAE;QACtB,eAAe,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO;KAC9C,CAAC;IACF,MAAM,QAAQ,GAAe,gBAAgB,CAC3C,iBAAiB,CAAC,MAAM,EAAE;QACxB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,IAAI,EAAE,OAAO,CAAC,IAAI;KACnB,CAAC,CACH,CAAC;IACF,MAAM,OAAO,GAA2B,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC;IACtE,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACzD,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;IAC7B,CAAC;IACD,MAAM,QAAQ,GAAoB;QAChC,QAAQ;QACR,OAAO;QACP,KAAK,EAAE,OAAO;QACd,SAAS,EAAE,MAAM,CAAC,eAAe,IAAI,yBAAyB;QAC9D,OAAO,EAAE,CAAC;QACV,OAAO,EAAE,MAAM,CAAC,aAAa;KAC9B,CAAC;IACF,MAAM,KAAK,GAAG;QACZ,QAAQ,EAAE,MAAM,CAAC,YAAY,IAAI,iBAAiB;QAClD,QAAQ,EAAE,MAAM,CAAC,YAAY,IAAI,iBAAiB;QAClD,OAAO,EAAE,MAAM,CAAC,YAAY,IAAI,sBAAsB;KACvD,CAAC;IACF,MAAM,QAAQ,GAAU;QACtB,IAAI,EAAE,eAAe;QACrB,OAAO,EAAE,UAAU,EAAE,IAAI,SAAS;KACnC,CAAC;IACF,MAAM,IAAI,GAAG,IAAI,UAAU,CAAY;QACrC,GAAG,KAAK;QACR,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,CAChB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;KAC3E,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,IAAI,UAAU,CAAa;QACvC,GAAG,KAAK;QACR,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,CAChB,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;KACrE,CAAC,CAAC;IAEH,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,MAAM,GAAkB,IAAI,CAAC;IACjC,MAAM,OAAO,GAAG,GAAe,EAAE;QAC/B,MAAM,CAAC,GAAe,EAAE,CAAC;QACzB,IAAI,MAAM,CAAC,SAAS;YAAE,CAAC,CAAC,YAAY,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC;QACzD,IAAI,MAAM;YAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;QACjC,OAAO,CAAC,CAAC;IACX,CAAC,CAAC;IAEF,0EAA0E;IAC1E,0EAA0E;IAC1E,cAAc;IACd,MAAM,OAAO,GAAG,IAAI,wBAAwB,EAAE,CAAC;IAC/C,MAAM,cAAc,GAAG,MAAM,CAAC,cAAc,IAAI,IAAI,CAAC;IACrD,MAAM,WAAW,GACf,cAAc,IAAI,UAAU,CAAC,uBAAuB,CAAC,OAAO,CAAC,CAAC;IAChE,MAAM,MAAM,GAAe,cAAc;QACvC,CAAC,CAAC;YACE,MAAM,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE;YACjC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC;SACxC;QACH,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;IAC7E,MAAM,QAAQ,GAAG,IAAI,kBAAkB,CACrC,CAAC,IAAI,EAAE,EAAE;QACP,IAAI,CAAC,OAAO;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjC,CAAC,EACD,OAAO,EACP,MAAM,CACP,CAAC;IACF,MAAM,SAAS,GAAG,cAAc,IAAI,KAAK,CAAC,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IAC5E,MAAM,eAAe,GACnB,cAAc;QACd,WAAW,CAAC,mBAAmB,CAAC,IAAI,qBAAqB,EAAE,CAAC,CAAC;IAC/D,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,eAAe,EAAE,UAAU,EAAE,IAAI,SAAS,CAAC,CAAC;IAE9E,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC;IAE5E,SAAS,IAAI,CACX,QAAsB,EACtB,IAAY,EACZ,UAAsB;QAEtB,IAAI,OAAO,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,WAAW,EAAE,CAAC;YAChD,OAAO;QACT,CAAC;QACD,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC;QAC/B,MAAM,MAAM,GAAc;YACxB,YAAY,EAAE,GAAG;YACjB,oBAAoB,EAAE,GAAG;YACzB,cAAc,EAAE,QAAQ,CAAC,QAAQ,CAAC;YAClC,YAAY,EAAE,QAAQ;YACtB,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE;YACxD,UAAU,EAAE,gBAAgB,CAAC,EAAE,GAAG,OAAO,EAAE,EAAE,GAAG,UAAU,EAAE,CAAC;SAC9D,CAAC;QACF,MAAM,EAAE,GAAG,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACjD,IAAI,EAAE,IAAI,KAAK,CAAC,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAAC;YACvC,MAAM,CAAC,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC;YAC5B,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC;YAC1B,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC,UAAU,CAAC;QAC/B,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACpB,CAAC;IAED,SAAS,GAAG,CACV,QAAsB,EACtB,IAAY,EACZ,KAAqB;QAErB,IAAI,CAAC;YACH,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;YACxD,iEAAiE;YACjE,sDAAsD;YACtD,MAAM,OAAO,GACX,QAAQ,KAAK,OAAO,IAAI,KAAK,KAAK,SAAS;gBACzC,CAAC,CAAC,mBAAmB,CAAC,KAAK,EAAE,KAAK,CAAC;gBACnC,CAAC,CAAC,EAAE,CAAC;YACT,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,GAAG,OAAO,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC;QACtD,CAAC;QAAC,MAAM,CAAC;YACP,sCAAsC;QACxC,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAe;QACzB,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QACjC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QACjC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;KACpC,CAAC;IAEF,SAAS,WAAW,CAAC,KAAc,EAAE,KAAqB;QACxD,IAAI,CAAC;YACH,MAAM,EAAE,UAAU,EAAE,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;YACjD,IAAI,CAAC,OAAO,EAAE,uBAAuB,EAAE;gBACrC,GAAG,mBAAmB,CAAC,KAAK,EAAE,KAAK,CAAC;gBACpC,GAAG,UAAU;aACd,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,sCAAsC;QACxC,CAAC;IACH,CAAC;IAED,2EAA2E;IAC3E,MAAM,QAAQ,GAAG,IAAI,OAAO,EAAU,CAAC;IACvC,MAAM,OAAO,GAAG,IAAI,GAAG,EAA4C,CAAC;IACpE,IAAI,cAAc,GAAG,KAAK,CAAC;IAC3B,SAAS,WAAW,CAAC,KAAc,EAAE,IAAY;QAC/C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YAChD,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,OAAO;YAChC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;QACD,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,mBAAmB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YACrD,MAAM,GAAG,GAAG;gBACV,IAAI;gBACJ,UAAU,CAAC,gBAAgB,CAAC;gBAC5B,UAAU,CAAC,oBAAoB,CAAC;gBAChC,UAAU,CAAC,mBAAmB,CAAC;aAChC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACjB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,IAAI,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC5B,IAAI,CAAC,IAAI,IAAI,GAAG,GAAG,IAAI,CAAC,KAAK,IAAI,MAAM,EAAE,CAAC;gBACxC,IAAI,OAAO,CAAC,IAAI,IAAI,GAAG;oBAAE,OAAO,CAAC,KAAK,EAAE,CAAC;gBACzC,IAAI,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;gBAChC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACzB,CAAC;YACD,IAAI,EAAE,IAAI,CAAC,KAAK,GAAG,sBAAsB,EAAE,CAAC;gBAC1C,OAAO;YACT,CAAC;YACD,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;YAChC,yEAAyE;YACzE,sCAAsC;YACtC,IAAI,CAAC,cAAc,EAAE,CAAC;gBACpB,cAAc,GAAG,IAAI,CAAC;gBACtB,YAAY,CAAC,GAAG,EAAE;oBAChB,cAAc,GAAG,KAAK,CAAC;oBACvB,IAAI,CAAC,KAAK,EAAE,CAAC;gBACf,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;YACb,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,sCAAsC;QACxC,CAAC;IACH,CAAC;IACD,MAAM,YAAY,GAAG,MAAM,CAAC,mBAAmB,IAAI,wBAAwB,CAAC;IAE5E,MAAM,SAAS,GAAG,CAAC,GAAY,EAAE,MAAc,EAAQ,EAAE;QACvD,WAAW,CACT,GAAG,EACH,MAAM,KAAK,oBAAoB;YAC7B,CAAC,CAAC,wBAAwB;YAC1B,CAAC,CAAC,uBAAuB,CAC5B,CAAC;IACJ,CAAC,CAAC;IACF,MAAM,WAAW,GAAG,CAAC,MAAe,EAAQ,EAAE;QAC5C,WAAW,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;QAC9C,IAAI,OAAO,CAAC,aAAa,CAAC,oBAAoB,CAAC,GAAG,CAAC,EAAE,CAAC;YACpD,OAAO,CAAC,4DAA4D;QACtE,CAAC;QACD,MAAM,IAAI,GAAG,sBAAsB,CACjC,OAAO,CAAC,QAAQ,EAChB,OAAO,CAAC,GAAG,CAAC,YAAY,CACzB,CAAC;QACF,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;YACvC,OAAO;QACT,CAAC;QACD,IAAI,IAAI,KAAK,sBAAsB,EAAE,CAAC;YACpC,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QACD,0EAA0E;QAC1E,+CAA+C;QAC/C,MAAM,GAAG,GAAG,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QACtE,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClB,KAAK,WAAW,CAAC,QAAQ,EAAE,EAAE,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CACnD,OAAO,CAAC,QAAQ,CAAC,GAAG,EAAE;YACpB,MAAM,GAAG,CAAC;QACZ,CAAC,CAAC,CACH,CAAC;IACJ,CAAC,CAAC;IACF,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,UAAU,GAAG,CAAC,GAAY,EAAQ,EAAE;QACxC,IAAI,OAAO,CAAC,aAAa,CAAC,mBAAmB,CAAC,GAAG,CAAC,IAAI,OAAO,EAAE,CAAC;YAC9D,OAAO,CAAC,iDAAiD;QAC3D,CAAC;QACD,2DAA2D;QAC3D,OAAO,GAAG,IAAI,CAAC;QACf,KAAK,WAAW,CAAC,QAAQ,EAAE,EAAE,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;YACnD,IAAI,CAAC;gBACH,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,GAAG,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CACpE,CAAC;YACJ,CAAC;YAAC,MAAM,CAAC;gBACP,8DAA8D;YAChE,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IACF,MAAM,YAAY,GAAG,GAAS,EAAE;QAC9B,IAAI,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YACpC,KAAK,QAAQ,EAAE,CAAC;QAClB,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,aAAa,GAAG,MAAM,CAAC,oBAAoB,IAAI,IAAI,CAAC;IAC1D,IAAI,aAAa,EAAE,CAAC;QAClB,OAAO,CAAC,EAAE,CAAC,0BAA0B,EAAE,SAAS,CAAC,CAAC;QAClD,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,WAAW,CAAC,CAAC;QAC9C,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO,CAAC,EAAE,CAAC,mBAAmB,EAAE,UAAU,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IACD,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;IACvC,oBAAoB,CAAC,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC;IAE5E,SAAS,QAAQ;QACf,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC1E,CAAC;IAED,MAAM,QAAQ,GAAkB;QAC9B,MAAM;QACN,MAAM;QACN,WAAW;QACX,OAAO,CAAC,EAAE;YACR,MAAM;gBACJ,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,KAAK,EAAE;oBACjC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,CAAC;oBAChC,CAAC,CAAC,IAAI,CAAC;QACb,CAAC;QACD,KAAK,CAAC,SAAS,GAAG,wBAAwB;YACxC,OAAO,WAAW,CAAC,QAAQ,EAAE,EAAE,SAAS,CAAC,CAAC;QAC5C,CAAC;QACD,KAAK,CAAC,QAAQ,CAAC,SAAS,GAAG,wBAAwB;YACjD,IAAI,OAAO;gBAAE,OAAO;YACpB,MAAM,WAAW,CAAC,QAAQ,EAAE,EAAE,SAAS,CAAC,CAAC;YACzC,OAAO,GAAG,IAAI,CAAC;YACf,OAAO,CAAC,cAAc,CAAC,0BAA0B,EAAE,SAAS,CAAC,CAAC;YAC9D,OAAO,CAAC,cAAc,CAAC,oBAAoB,EAAE,WAAW,CAAC,CAAC;YAC1D,OAAO,CAAC,cAAc,CAAC,mBAAmB,EAAE,UAAU,CAAC,CAAC;YACxD,OAAO,CAAC,cAAc,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;YACnD,oBAAoB,CAAC,IAAI,CAAC,CAAC;YAC3B,IAAI,SAAS;gBAAE,KAAK,CAAC,OAAO,EAAE,CAAC;YAC/B,IAAI,eAAe;gBAAE,WAAW,CAAC,OAAO,EAAE,CAAC;YAC3C,IAAI,WAAW;gBAAE,UAAU,CAAC,OAAO,EAAE,CAAC;YACtC,IAAI,OAAO,KAAK,QAAQ;gBAAE,OAAO,GAAG,IAAI,CAAC;QAC3C,CAAC;QACD,IAAI,OAAO;YACT,OAAO,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;QACtC,CAAC;KACF,CAAC;IACF,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,WAAW;IAClB,IAAI,CAAC;QACH,OAAO,OAAO,EAAE,IAAI,SAAS,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAeD;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CACzB,GAAY,EACZ,UAAkC,EAAE;IAEpC,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,QAAQ,GAAG,CAAC,KAAiC,EAAQ,EAAE;QAC3D,MAAM,CAAC,GAAG,OAAO,CAAC;QAClB,IAAI,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QACD,OAAO,GAAG,IAAI,CAAC;QACf,KAAK,CAAC,cAAc,EAAE,CAAC;QACvB,KAAK,CAAC;aACH,KAAK,CAAC,OAAO,CAAC,SAAS,IAAI,wBAAwB,CAAC;aACpD,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAC/B,CAAC,CAAC;IACF,GAAG,CAAC,EAAE,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;IAChC,OAAO,GAAG,EAAE;QACV,GAAG,CAAC,cAAc,EAAE,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;IAChD,CAAC,CAAC;AACJ,CAAC","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"]}
|
package/dist/resource.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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.
|
package/dist/resource.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\n// Part of @reliantlabs/forge-web-runtime — the web twin of forge/pkg.\n//\n// <Resource> — the owned-once data-table container. It encapsulates the\n// loading / error / empty / data tristate ladder, a server-side (debounced)\n// filter, and cursor pagination, so list pages stop re-hand-rolling the\n// ladder and stop client-side-filtering a single page cap.\n//\n// Pairs with the app's useQueryResource hook: map a query result to\n// { status, data, error } and hand it here. Pagination is\n// cursor-based (page_token / next_page_token in Connect list RPCs): the page\n// owns the cursor stack and passes onNextPage/onPrevPage.\nimport { useEffect, useState, type ReactElement, type ReactNode } from \"react\";\n\nimport { userMessage } from \"./errors.js\";\n\nexport interface ResourceColumn<T> {\n header: string;\n cell: (row: T) => ReactNode;\n /** Extra classes for the cell + header (alignment, width). */\n className?: string;\n}\n\nexport type ResourceStatus = \"loading\" | \"error\" | \"success\";\n\nexport interface ResourceProps<T> {\n status: ResourceStatus;\n data: T[] | undefined;\n columns: ResourceColumn<T>[];\n rowKey: (row: T) => string;\n\n title?: string;\n /** Header-right slot, e.g. a \"New\" button. */\n actions?: ReactNode;\n error?: Error | null;\n onRetry?: () => void;\n onRowClick?: (row: T) => void;\n\n emptyTitle?: string;\n emptyMessage?: string;\n\n // Server-side filter (debounced). Omit onFilterChange to hide the search box.\n filter?: string;\n onFilterChange?: (value: string) => void;\n filterPlaceholder?: string;\n filterDebounceMs?: number;\n\n // Cursor pagination. Omit the handlers to hide the footer.\n onNextPage?: () => void;\n onPrevPage?: () => void;\n hasNextPage?: boolean;\n hasPrevPage?: boolean;\n /** True during a background refetch (page change / filter change). */\n isFetching?: boolean;\n}\n\nexport function Resource<T>({\n status,\n data,\n columns,\n rowKey,\n title,\n actions,\n error,\n onRetry,\n onRowClick,\n emptyTitle = \"Nothing here yet\",\n emptyMessage = \"No records match.\",\n filter,\n onFilterChange,\n filterPlaceholder = \"Search…\",\n filterDebounceMs = 300,\n onNextPage,\n onPrevPage,\n hasNextPage,\n hasPrevPage,\n isFetching,\n}: ResourceProps<T>): ReactElement {\n const [query, setQuery] = useState(filter ?? \"\");\n\n // Keep local input in sync when the parent resets the filter externally.\n useEffect(() => {\n setQuery(filter ?? \"\");\n }, [filter]);\n\n // Debounce filter changes so each keystroke doesn't fire an RPC.\n useEffect(() => {\n if (!onFilterChange || query === (filter ?? \"\")) {\n return;\n }\n const handle = setTimeout(() => onFilterChange(query), filterDebounceMs);\n return () => clearTimeout(handle);\n }, [query, filter, onFilterChange, filterDebounceMs]);\n\n const showFooter = Boolean(onNextPage || onPrevPage);\n const colCount = columns.length;\n const captionText = title ?? \"Results\";\n\n return (\n <section className=\"flex flex-col gap-4\">\n {(title || actions) && (\n <header className=\"flex items-center justify-between gap-4\">\n {title ? (\n <h1 className=\"text-xl font-semibold text-ink\">{title}</h1>\n ) : (\n <span />\n )}\n {actions ? (\n <div className=\"flex items-center gap-2\">{actions}</div>\n ) : null}\n </header>\n )}\n\n {onFilterChange ? (\n <input\n type=\"search\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={filterPlaceholder}\n aria-label={title ? `Search ${title.toLowerCase()}` : \"Search\"}\n 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\"\n />\n ) : null}\n\n <div className=\"overflow-x-auto rounded-lg border border-border bg-surface\">\n <table className=\"min-w-full divide-y divide-border text-sm\">\n <caption className=\"sr-only\">{captionText}</caption>\n <thead className=\"bg-surface-muted\">\n <tr>\n {columns.map((col, i) => (\n <th\n key={i}\n scope=\"col\"\n className={`px-4 py-2.5 text-left font-medium text-ink-muted ${\n col.className ?? \"\"\n }`}\n >\n {col.header}\n </th>\n ))}\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border\">\n {status === \"loading\" ? (\n <SkeletonRows rows={5} cols={colCount} />\n ) : status === \"error\" ? (\n <tr>\n <td colSpan={colCount} className=\"px-4 py-10\">\n <ErrorState error={error} onRetry={onRetry} />\n </td>\n </tr>\n ) : !data || data.length === 0 ? (\n <tr>\n <td colSpan={colCount} className=\"px-4 py-12\">\n <EmptyState title={emptyTitle} message={emptyMessage} />\n </td>\n </tr>\n ) : (\n data.map((row) => (\n <tr\n key={rowKey(row)}\n onClick={onRowClick ? () => onRowClick(row) : undefined}\n // Keyboard-operable when the row navigates: Enter/Space\n // activate it, and it takes focus in tab order.\n role={onRowClick ? \"button\" : undefined}\n tabIndex={onRowClick ? 0 : undefined}\n onKeyDown={\n onRowClick\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onRowClick(row);\n }\n }\n : undefined\n }\n className={\n onRowClick\n ? \"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\"\n : undefined\n }\n >\n {columns.map((col, i) => (\n <td\n key={i}\n className={`px-4 py-2.5 text-ink ${col.className ?? \"\"}`}\n >\n {col.cell(row)}\n </td>\n ))}\n </tr>\n ))\n )}\n </tbody>\n </table>\n </div>\n\n {showFooter ? (\n <footer className=\"flex items-center justify-end gap-2\">\n {isFetching ? (\n <span className=\"mr-auto text-xs text-ink-subtle\">Loading…</span>\n ) : null}\n <button\n type=\"button\"\n onClick={onPrevPage}\n disabled={!hasPrevPage}\n 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\"\n >\n Previous\n </button>\n <button\n type=\"button\"\n onClick={onNextPage}\n disabled={!hasNextPage}\n 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\"\n >\n Next\n </button>\n </footer>\n ) : null}\n </section>\n );\n}\n\n// SkeletonRows is the loading rung of the tristate ladder: a shimmer the\n// width of the table while the first page is in flight.\n//\n// It is ONE row with a colSpan cell, matching the error and empty rungs\n// above — not `rows` × `cols` real table cells. That shape is what makes it\n// accessible, and the reasoning is worth keeping:\n//\n// - The shimmer is pure decoration. It carries no data, so it is wrapped\n// in aria-hidden and the cell carries one sr-only \"Loading…\" instead.\n// Assistive tech hears the state once, not once per placeholder.\n// - A grid of empty <td>s announced as a table of blank cells, and read\n// as a real 5-row result set by anything counting rows. The placeholder\n// geometry is now plain <div>s inside the single cell, so the table has\n// exactly the structure it claims to have at every moment.\n// - Empty cells also tripped jsx-a11y/control-has-associated-label under\n// the eslint config forge scaffolds. That was the symptom that surfaced\n// this; the row-shape mismatch above is the actual defect.\n//\n// aria-hidden goes on the inner <div>, never on the <td> or <tr>: jsx-a11y\n// treats table elements as focusable, so hiding one is\n// no-aria-hidden-on-focusable — correctly, since a hidden focusable node is\n// a focus trap for screen-reader users.\nfunction SkeletonRows({ rows, cols }: { rows: number; cols: number }) {\n return (\n <tr>\n <td colSpan={cols} className=\"p-0\">\n <span className=\"sr-only\">Loading…</span>\n <div aria-hidden=\"true\" className=\"divide-y divide-border\">\n {Array.from({ length: rows }).map((_, r) => (\n <div key={r} className=\"flex items-center gap-4 px-4 py-3\">\n {Array.from({ length: cols }).map((__, c) => (\n <div\n key={c}\n className=\"h-4 flex-1 animate-pulse motion-reduce:animate-none rounded bg-surface-muted\"\n />\n ))}\n </div>\n ))}\n </div>\n </td>\n </tr>\n );\n}\n\n// ErrorState shows userMessage(error), never error.message: a ConnectError's\n// message carries the \"[code] \" transport framing, which is not copy a user\n// should read. It is body text in the empty rung's style, not a monospace\n// debug readout.\nfunction ErrorState({\n error,\n onRetry,\n}: {\n error?: Error | null;\n onRetry?: () => void;\n}) {\n return (\n <div role=\"alert\" className=\"flex flex-col items-center gap-2 text-center\">\n <p className=\"text-sm font-medium text-danger-ink\">\n Couldn't load data\n </p>\n {error ? (\n <p className=\"max-w-md text-sm text-ink-muted\">{userMessage(error)}</p>\n ) : null}\n {onRetry ? (\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"mt-1 rounded-md bg-danger px-3 py-1.5 text-sm font-medium text-on-danger hover:bg-danger-hover\"\n >\n Try again\n </button>\n ) : null}\n </div>\n );\n}\n\nfunction EmptyState({ title, message }: { title: string; message: string }) {\n return (\n <div className=\"flex flex-col items-center gap-1 text-center\">\n <p className=\"text-sm font-medium text-ink\">{title}</p>\n <p className=\"text-sm text-ink-muted\">{message}</p>\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\n// Part of @reliantlabs/forge-web-runtime — the web twin of forge/pkg.\n//\n// <Resource> — the owned-once data-table container. It encapsulates the\n// loading / error / empty / data tristate ladder, a server-side (debounced)\n// filter, and cursor pagination, so list pages stop re-hand-rolling the\n// ladder and stop client-side-filtering a single page cap.\n//\n// Pairs with the app's useQueryResource hook: map a query result to\n// { status, data, error } and hand it here. Pagination is\n// cursor-based (page_token / next_page_token in Connect list RPCs): the page\n// owns the cursor stack and passes onNextPage/onPrevPage.\n//\n// Layout, top to bottom: a header (title + primary action), a toolbar row\n// (search + the page's `toolbar` facets) that WRAPS, the table, and a footer\n// (the record count + the pager). Filters used to share the header row with\n// the title and the create button in one non-wrapping flex row, which pushed\n// the button off-canvas on a phone; they now have a row of their own.\nimport {\n useEffect,\n useState,\n type KeyboardEvent,\n type MouseEvent,\n type ReactElement,\n type ReactNode,\n} from \"react\";\n\nimport { userMessage } from \"./errors.js\";\n\nexport interface ResourceColumn<T> {\n header: string;\n cell: (row: T) => ReactNode;\n /** Extra classes for the cell + header (alignment, width). */\n className?: string;\n /**\n * Makes the column sortable: the server-side sort key (the List request's\n * `order_by` value, a column name) this header sorts by. The header renders\n * a button and `aria-sort`; the page owns the state through\n * `sort` / `onSortChange`.\n */\n sortKey?: string;\n}\n\n/** The active server-side sort: an `order_by` key and its direction. */\nexport interface ResourceSort {\n key: string;\n descending: boolean;\n}\n\nexport type ResourceStatus = \"loading\" | \"error\" | \"success\";\n\nexport interface ResourceProps<T> {\n status: ResourceStatus;\n data: T[] | undefined;\n columns: ResourceColumn<T>[];\n rowKey: (row: T) => string;\n\n title?: string;\n /** Header-right slot: the page's primary action, e.g. a \"New\" button. */\n actions?: ReactNode;\n /**\n * Filter controls, rendered in their own wrapping row beside the search\n * box — never in the header, so a page with several facets still fits a\n * phone. Pass a filter bar, selects, pickers.\n */\n toolbar?: ReactNode;\n error?: Error | null;\n onRetry?: () => void;\n /**\n * Clicking anywhere on a row calls it; a click that lands on a link or\n * another control inside the row is left to that control. Without\n * `rowsHaveLinks` the row is also the keyboard path to the record: it takes\n * a tab stop, `role=\"button\"`, and Enter/Space call this.\n */\n onRowClick?: (row: T) => void;\n /**\n * Declares that every row renders a real link to its record (in the\n * primary cell), so `onRowClick` is a POINTER convenience only: the row\n * keeps table semantics (no button role, no tab stop) and keyboard and\n * screen-reader users reach the record through the link. Set it only when\n * the link is there — without it, a row with `onRowClick` stays keyboard\n * operable itself, so a page that predates the link is never stranded.\n */\n rowsHaveLinks?: boolean;\n\n emptyTitle?: string;\n emptyMessage?: string;\n\n // Server-side filter (debounced). Omit onFilterChange to hide the search box.\n filter?: string;\n onFilterChange?: (value: string) => void;\n filterPlaceholder?: string;\n filterDebounceMs?: number;\n\n // Server-side sort, driven by columns that declare a sortKey. Each click\n // on a header cycles ascending → descending → unsorted.\n sort?: ResourceSort | null;\n onSortChange?: (sort: ResourceSort | null) => void;\n\n // Cursor pagination. Omit the handlers to hide the pager.\n onNextPage?: () => void;\n onPrevPage?: () => void;\n hasNextPage?: boolean;\n hasPrevPage?: boolean;\n /** True during a background refetch (page change / filter change). */\n isFetching?: boolean;\n /**\n * The total number of matching records (the List response's total_count),\n * shown in the footer: \"1–25 of 112\". Omit when the API does not report it.\n */\n totalCount?: number;\n /** Zero-based index of this page's first row, for the footer's range. */\n pageStart?: number;\n}\n\n// The controls a row click must not hijack: a click on any of these inside a\n// row belongs to that control (and a link inside the row already navigates).\nconst INTERACTIVE =\n \"a, button, input, select, textarea, label, [role='button'], [role='combobox']\";\n\nfunction nextSort(\n current: ResourceSort | null | undefined,\n key: string,\n): ResourceSort | null {\n if (current?.key !== key) return { key, descending: false };\n if (!current.descending) return { key, descending: true };\n return null;\n}\n\nexport function Resource<T>({\n status,\n data,\n columns,\n rowKey,\n title,\n actions,\n toolbar,\n error,\n onRetry,\n onRowClick,\n rowsHaveLinks = false,\n emptyTitle = \"Nothing here yet\",\n emptyMessage = \"No records match.\",\n filter,\n onFilterChange,\n filterPlaceholder = \"Search…\",\n filterDebounceMs = 300,\n sort,\n onSortChange,\n onNextPage,\n onPrevPage,\n hasNextPage,\n hasPrevPage,\n isFetching,\n totalCount,\n pageStart,\n}: ResourceProps<T>): ReactElement {\n const [query, setQuery] = useState(filter ?? \"\");\n\n // Keep local input in sync when the parent resets the filter externally.\n useEffect(() => {\n setQuery(filter ?? \"\");\n }, [filter]);\n\n // Debounce filter changes so each keystroke doesn't fire an RPC.\n useEffect(() => {\n if (!onFilterChange || query === (filter ?? \"\")) {\n return;\n }\n const handle = setTimeout(() => onFilterChange(query), filterDebounceMs);\n return () => clearTimeout(handle);\n }, [query, filter, onFilterChange, filterDebounceMs]);\n\n const colCount = columns.length;\n const captionText = title ?? \"Results\";\n const rows = status === \"success\" && data ? data.length : 0;\n const showPager =\n Boolean(onNextPage || onPrevPage) && Boolean(hasNextPage || hasPrevPage);\n const countText = describeCount(rows, totalCount, pageStart);\n const showFooter = showPager || countText !== \"\" || Boolean(isFetching);\n const showToolbar = Boolean(onFilterChange || toolbar);\n\n return (\n <section className=\"flex flex-col gap-4\">\n {(title || actions) && (\n <header className=\"flex flex-wrap items-center justify-between gap-3\">\n {title ? (\n <h1 className=\"text-xl font-semibold text-ink\">{title}</h1>\n ) : (\n <span />\n )}\n {actions ? (\n <div className=\"flex flex-wrap items-center gap-2\">{actions}</div>\n ) : null}\n </header>\n )}\n\n {showToolbar ? (\n <div className=\"flex flex-wrap items-start gap-2\">\n {onFilterChange ? (\n <input\n type=\"search\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={filterPlaceholder}\n aria-label={title ? `Search ${title.toLowerCase()}` : \"Search\"}\n 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\"\n />\n ) : null}\n {toolbar ? <div className=\"min-w-0 flex-1\">{toolbar}</div> : null}\n </div>\n ) : null}\n\n <div className=\"overflow-x-auto rounded-lg border border-border bg-surface\">\n <table className=\"min-w-full divide-y divide-border text-sm\">\n <caption className=\"sr-only\">{captionText}</caption>\n <thead className=\"bg-surface-muted\">\n <tr>\n {columns.map((col, i) => {\n const sortable = Boolean(col.sortKey && onSortChange);\n const active = sortable && sort?.key === col.sortKey;\n return (\n <th\n key={i}\n scope=\"col\"\n aria-sort={\n active\n ? sort?.descending\n ? \"descending\"\n : \"ascending\"\n : undefined\n }\n className={`px-4 py-2.5 text-left font-medium text-ink-muted ${\n col.className ?? \"\"\n }`}\n >\n {sortable ? (\n <button\n type=\"button\"\n onClick={() =>\n onSortChange?.(nextSort(sort, col.sortKey ?? \"\"))\n }\n 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\"\n >\n {col.header}\n <span\n aria-hidden=\"true\"\n className={active ? \"text-ink\" : \"text-ink-subtle\"}\n >\n {active ? (sort?.descending ? \"↓\" : \"↑\") : \"↕\"}\n </span>\n </button>\n ) : (\n col.header\n )}\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border\">\n {status === \"loading\" ? (\n <SkeletonRows rows={5} cols={colCount} />\n ) : status === \"error\" ? (\n <tr>\n <td colSpan={colCount} className=\"px-4 py-10\">\n <ErrorState error={error} onRetry={onRetry} />\n </td>\n </tr>\n ) : !data || data.length === 0 ? (\n <tr>\n <td colSpan={colCount} className=\"px-4 py-12\">\n <EmptyState title={emptyTitle} message={emptyMessage} />\n </td>\n </tr>\n ) : (\n data.map((row) => (\n <tr\n key={rowKey(row)}\n {...rowInteraction(row, onRowClick, rowsHaveLinks)}\n >\n {columns.map((col, i) => (\n <td\n key={i}\n className={`px-4 py-2.5 text-ink ${col.className ?? \"\"}`}\n >\n {col.cell(row)}\n </td>\n ))}\n </tr>\n ))\n )}\n </tbody>\n </table>\n </div>\n\n {showFooter ? (\n <footer className=\"flex flex-wrap items-center justify-end gap-2\">\n <span\n aria-live=\"polite\"\n className=\"mr-auto text-sm text-ink-muted tabular-nums\"\n >\n {isFetching ? \"Loading…\" : countText}\n </span>\n {showPager ? (\n <>\n <button\n type=\"button\"\n onClick={onPrevPage}\n disabled={!hasPrevPage}\n 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\"\n >\n Previous\n </button>\n <button\n type=\"button\"\n onClick={onNextPage}\n disabled={!hasNextPage}\n 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\"\n >\n Next\n </button>\n </>\n ) : null}\n </footer>\n ) : null}\n </section>\n );\n}\n\n/** The props rowInteraction puts on a body row. */\nexport interface RowInteraction {\n onClick?: (e: MouseEvent<HTMLTableRowElement>) => void;\n onKeyDown?: (e: KeyboardEvent<HTMLTableRowElement>) => void;\n role?: \"button\";\n tabIndex?: number;\n className?: string;\n}\n\n/**\n * rowInteraction is how a body row responds to its onRowClick. A row with\n * no onRowClick is inert data. A row whose cells link to the record\n * (`rowsHaveLinks`) gets a pointer-only click and keeps table semantics. A\n * row with onRowClick and no link is the only way to its record, so it is\n * keyboard operable itself: a tab stop, a button role, Enter/Space. Exported\n * for its tests; not part of the package's index.\n */\nexport function rowInteraction<T>(\n row: T,\n onRowClick: ((row: T) => void) | undefined,\n rowsHaveLinks: boolean,\n): RowInteraction {\n if (!onRowClick) return {};\n const onClick = (e: MouseEvent<HTMLTableRowElement>) => {\n // Leave clicks on the row's own controls (the primary-cell link, a\n // menu) to those controls, and never turn a text selection into a\n // navigation. Only a control INSIDE the row counts: a keyboard-operable\n // row is itself role=\"button\", and closest() matches the row too.\n const control = (e.target as Element).closest(INTERACTIVE);\n if (\n control &&\n control !== e.currentTarget &&\n e.currentTarget.contains(control)\n ) {\n return;\n }\n if (window.getSelection()?.toString()) return;\n onRowClick(row);\n };\n if (rowsHaveLinks) {\n return {\n onClick,\n className:\n \"cursor-pointer transition-colors hover:bg-surface-muted focus-within:bg-surface-muted\",\n };\n }\n return {\n onClick,\n role: \"button\",\n tabIndex: 0,\n onKeyDown: (e) => {\n // Only the row's own keys: Enter on a control inside the row is\n // that control's.\n if (e.target !== e.currentTarget) return;\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onRowClick(row);\n }\n },\n className:\n \"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\",\n };\n}\n\n/**\n * describeCount is the footer's record count: \"1–25 of 112\" with a known page\n * offset, \"112 results\" without one, and nothing while no row is shown.\n */\nfunction describeCount(\n rows: number,\n total?: number,\n pageStart?: number,\n): string {\n if (total === undefined || rows === 0) return \"\";\n if (pageStart === undefined) {\n return `${total.toLocaleString()} ${total === 1 ? \"result\" : \"results\"}`;\n }\n const first = (pageStart + 1).toLocaleString();\n const last = (pageStart + rows).toLocaleString();\n return `${first}–${last} of ${total.toLocaleString()}`;\n}\n\n// SkeletonRows is the loading rung of the tristate ladder: a shimmer the\n// width of the table while the first page is in flight.\n//\n// It is ONE row with a colSpan cell, matching the error and empty rungs\n// above — not `rows` × `cols` real table cells. That shape is what makes it\n// accessible, and the reasoning is worth keeping:\n//\n// - The shimmer is pure decoration. It carries no data, so it is wrapped\n// in aria-hidden and the cell carries one sr-only \"Loading…\" instead.\n// Assistive tech hears the state once, not once per placeholder.\n// - A grid of empty <td>s announced as a table of blank cells, and read\n// as a real 5-row result set by anything counting rows. The placeholder\n// geometry is now plain <div>s inside the single cell, so the table has\n// exactly the structure it claims to have at every moment.\n// - Empty cells also tripped jsx-a11y/control-has-associated-label under\n// the eslint config forge scaffolds. That was the symptom that surfaced\n// this; the row-shape mismatch above is the actual defect.\n//\n// aria-hidden goes on the inner <div>, never on the <td> or <tr>: jsx-a11y\n// treats table elements as focusable, so hiding one is\n// no-aria-hidden-on-focusable — correctly, since a hidden focusable node is\n// a focus trap for screen-reader users.\nfunction SkeletonRows({ rows, cols }: { rows: number; cols: number }) {\n return (\n <tr>\n <td colSpan={cols} className=\"p-0\">\n <span className=\"sr-only\">Loading…</span>\n <div aria-hidden=\"true\" className=\"divide-y divide-border\">\n {Array.from({ length: rows }).map((_, r) => (\n <div key={r} className=\"flex items-center gap-4 px-4 py-3\">\n {Array.from({ length: cols }).map((__, c) => (\n <div\n key={c}\n className=\"h-4 flex-1 animate-pulse motion-reduce:animate-none rounded bg-surface-muted\"\n />\n ))}\n </div>\n ))}\n </div>\n </td>\n </tr>\n );\n}\n\n// ErrorState shows userMessage(error), never error.message: a ConnectError's\n// message carries the \"[code] \" transport framing, which is not copy a user\n// should read. It is body text in the empty rung's style, not a monospace\n// debug readout.\nfunction ErrorState({\n error,\n onRetry,\n}: {\n error?: Error | null;\n onRetry?: () => void;\n}) {\n return (\n <div role=\"alert\" className=\"flex flex-col items-center gap-2 text-center\">\n <p className=\"text-sm font-medium text-danger-ink\">\n Couldn't load data\n </p>\n {error ? (\n <p className=\"max-w-md text-sm text-ink-muted\">{userMessage(error)}</p>\n ) : null}\n {onRetry ? (\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"mt-1 rounded-md bg-danger px-3 py-1.5 text-sm font-medium text-on-danger hover:bg-danger-hover\"\n >\n Try again\n </button>\n ) : null}\n </div>\n );\n}\n\nfunction EmptyState({ title, message }: { title: string; message: string }) {\n return (\n <div className=\"flex flex-col items-center gap-1 text-center\">\n <p className=\"text-sm font-medium text-ink\">{title}</p>\n <p className=\"text-sm text-ink-muted\">{message}</p>\n </div>\n );\n}\n"]}
|