@docx-editor.dev/telemetry 0.0.0-stage → 2.29.0

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.
Files changed (48) hide show
  1. package/LICENSE.md +121 -0
  2. package/README.md +94 -2
  3. package/THIRD_PARTY_NOTICES.md +22 -0
  4. package/dist/chunk-73YEPWY5.js +1 -0
  5. package/dist/chunk-7ZXJ6HP2.cjs +1 -0
  6. package/dist/chunk-DT2KMPUJ.js +1 -0
  7. package/dist/chunk-DTEVX2XS.js +1 -0
  8. package/dist/chunk-LFQSY2TD.cjs +1 -0
  9. package/dist/chunk-LFX65QJG.js +4 -0
  10. package/dist/chunk-LGNT4I5W.cjs +1 -0
  11. package/dist/chunk-MJX3ECH3.cjs +4 -0
  12. package/dist/chunk-NUN46EXH.js +2 -0
  13. package/dist/chunk-RRYW7EAV.js +1 -0
  14. package/dist/chunk-S7KBMROA.js +1 -0
  15. package/dist/chunk-U6ERFNUM.cjs +1 -0
  16. package/dist/chunk-UK2MNJCG.cjs +2 -0
  17. package/dist/chunk-WGWKNKUT.cjs +1 -0
  18. package/dist/hocuspocus/index.cjs +1 -0
  19. package/dist/hocuspocus/index.d.cts +118 -0
  20. package/dist/hocuspocus/index.d.ts +118 -0
  21. package/dist/hocuspocus/index.js +1 -0
  22. package/dist/index.cjs +1 -0
  23. package/dist/index.d.cts +332 -0
  24. package/dist/index.d.ts +332 -0
  25. package/dist/index.js +1 -0
  26. package/dist/ingest/index.cjs +1 -0
  27. package/dist/ingest/index.d.cts +107 -0
  28. package/dist/ingest/index.d.ts +107 -0
  29. package/dist/ingest/index.js +1 -0
  30. package/dist/otel/index.cjs +1 -0
  31. package/dist/otel/index.d.cts +57 -0
  32. package/dist/otel/index.d.ts +57 -0
  33. package/dist/otel/index.js +1 -0
  34. package/dist/pipeline-DbVW7S-K.d.cts +223 -0
  35. package/dist/pipeline-DbVW7S-K.d.ts +223 -0
  36. package/dist/prometheus/index.cjs +1 -0
  37. package/dist/prometheus/index.d.cts +88 -0
  38. package/dist/prometheus/index.d.ts +88 -0
  39. package/dist/prometheus/index.js +1 -0
  40. package/dist/sentry/index.cjs +1 -0
  41. package/dist/sentry/index.d.cts +134 -0
  42. package/dist/sentry/index.d.ts +134 -0
  43. package/dist/sentry/index.js +1 -0
  44. package/dist/server/index.cjs +1 -0
  45. package/dist/server/index.d.cts +156 -0
  46. package/dist/server/index.d.ts +156 -0
  47. package/dist/server/index.js +1 -0
  48. package/package.json +114 -4
@@ -0,0 +1,332 @@
1
+ /**
2
+ * Anonymous diagnostics, metrics, and error reporting for editors and collaboration servers.
3
+ *
4
+ * - `@docx-editor.dev/telemetry`: {@link createTelemetry} for editors, the console, callback,
5
+ * memory, and HTTP exporters, and {@link browserPrivacySignals}.
6
+ * - `@docx-editor.dev/telemetry/ingest`: a route that receives telemetry from `httpExporter`
7
+ * and forwards it to a log service.
8
+ * - `@docx-editor.dev/telemetry/otel`: OpenTelemetry metrics and traces through your SDK.
9
+ * - `@docx-editor.dev/telemetry/sentry`: Sentry issues and breadcrumbs.
10
+ * - `@docx-editor.dev/telemetry/prometheus`: a `/metrics` endpoint.
11
+ * - `@docx-editor.dev/telemetry/server`: room telemetry for any Yjs server.
12
+ * - `@docx-editor.dev/telemetry/hocuspocus`: room telemetry for Hocuspocus in one call.
13
+ *
14
+ * @packageDocumentation
15
+ */
16
+ import { EditorModule, EditorDiagnostic } from '@docx-editor.dev/core';
17
+ export { ByteSizeBucket, CollaborationDiagnosticCode, CollaborationParticipantsDiagnostic, CollaborationStatusDiagnostic, CountBucket, CustomNodeDiagnosticCode, CustomNodeDiagnosticEvent, DiagnosticBase, DiagnosticCode, DiagnosticErrorName, DiagnosticExplanation, DiagnosticFailureDetail, DocumentOpenDiagnostic, DocumentOpenDiagnosticCode, DocumentPartKind, DocumentShape, EditDiagnosticCode, EditRejectedDiagnostic, EditRejectionOrigin, EditorDiagnostic, EditorDiagnosticKind, FailureStage, FontDiagnosticCode, FontFailedDiagnostic, FormattingChangeKind, HistoryDiagnosticCode, HistoryDiagnosticEvent, PerformanceDiagnostic, PerformanceDiagnosticCode, ReviewChangeKind, ReviewDiagnosticCode, ReviewOperation, ReviewSkippedDiagnostic, TimingSummary, explainDiagnostic } from '@docx-editor.dev/core';
18
+ import { A as Attributes, T as TelemetryPipelineOptions, a as TelemetryEvent, b as TelemetryExporter, M as MetricRecord } from './pipeline-DbVW7S-K.js';
19
+ export { c as AttributeValue, E as ENGINE_VERSION, d as MAX_METRIC_LABEL_VALUES, e as METRICS, f as MetricDefinition, g as MetricId, h as MetricKind, i as TelemetryEventContext, j as TelemetryExporterContext, p as prometheusName } from './pipeline-DbVW7S-K.js';
20
+
21
+ /**
22
+ * The areas you can switch on. Each one selects diagnostic kinds:
23
+ *
24
+ * - `documentOpen`: documents opened and refused (`document.open`).
25
+ * - `editing`: edits and commands refused while changes are not tracked (`edit.rejected`).
26
+ * - `trackedChanges`: refusals while suggesting, refusals about tracking itself, and skipped
27
+ * tracked changes
28
+ * (`edit.rejected` with `tracked: true`, `review.skipped`).
29
+ * - `fonts`: fonts that failed and were substituted (`font.failed`).
30
+ * - `history`: undo steps that did not group as the gesture intended (`history`).
31
+ * - `customNodes`: custom node payloads that could not be read (`custom-node`).
32
+ * - `collaborationClient`: collaboration status changes and room size (`collab.*`).
33
+ * - `performance`: how fast the editor responds: key presses and clicks to the next frame,
34
+ * and engine layout and paint times, as one summary per minute of use (`performance`).
35
+ *
36
+ * @public
37
+ */
38
+ type TelemetryArea = 'documentOpen' | 'editing' | 'trackedChanges' | 'fonts' | 'history' | 'customNodes' | 'collaborationClient' | 'performance';
39
+ /** Options for one area. @public */
40
+ interface TelemetryAreaOptions {
41
+ /**
42
+ * Share of events to send, from 0 to 1. Metrics count every event, except that a
43
+ * `performance` rate of 0 turns performance off, metrics included. Defaults to 1.
44
+ * Each event is sampled on its own, except in `performance`: there the choice is made once
45
+ * for each editor, which sends all of its summaries or none.
46
+ */
47
+ readonly sampleRate?: number;
48
+ }
49
+ /** Which areas to record. An area that is absent or `false` records nothing. @public */
50
+ type TelemetryInclude = {
51
+ readonly [A in TelemetryArea]?: boolean | TelemetryAreaOptions;
52
+ };
53
+ /** Options for {@link createTelemetry}. @public */
54
+ interface TelemetryOptions extends TelemetryPipelineOptions {
55
+ /**
56
+ * The areas to record. Omitted, every area is on.
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * include: { documentOpen: true, trackedChanges: true, editing: { sampleRate: 0.2 } }
61
+ * ```
62
+ */
63
+ readonly include?: TelemetryInclude;
64
+ /**
65
+ * Identical refusals inside this window are sent once, then summarized with `repeated`.
66
+ * Holding Backspace on a locked drawing is one event, not hundreds. Defaults to 10 000 ms.
67
+ * Set 0 to send every event.
68
+ */
69
+ readonly dedupeWindowMs?: number;
70
+ /**
71
+ * How much of an opened document's shape to send. `'basic'`, the default, sends the size and
72
+ * page buckets. `'detailed'` also sends part, paragraph, review item, and embedded font
73
+ * buckets: useful to group open times, but several counts together describe a document more
74
+ * closely, so ask for it only when you need it.
75
+ */
76
+ readonly documentShape?: 'basic' | 'detailed';
77
+ /**
78
+ * Your identifiers on every event, such as a tenant or a pseudonymous user id. A function is
79
+ * read at each event, so it can follow sign-in and tenant switches. Events only: to label
80
+ * metrics with a key, list it in `metricLabels`. For values that belong to one editor, such as
81
+ * its room or document, use {@link Telemetry.moduleFor}.
82
+ *
83
+ * These values are yours and go only to your exporters. Keys follow attribute rules: letters,
84
+ * digits, `_`, `.`, and `-`, up to 64 characters. Values are strings, numbers, or booleans;
85
+ * strings are cut at 256 characters.
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * attributes: () => ({ 'tenant.id': session.tenantId, 'user.id': session.pseudonymousId })
90
+ * ```
91
+ */
92
+ readonly attributes?: Attributes | (() => Attributes);
93
+ /**
94
+ * Which events go to your exporters, after the privacy check and sampling. Metrics still count
95
+ * every event. A filter that throws drops the event.
96
+ *
97
+ * @example
98
+ * ```ts
99
+ * // Failures and performance summaries only: no event for an open that succeeded.
100
+ * filter: (event) => isFailure(event) || event.kind === 'performance'
101
+ * ```
102
+ */
103
+ readonly filter?: (event: TelemetryEvent) => boolean;
104
+ }
105
+ /** A configured telemetry client. Create it once, outside any component. @public */
106
+ interface Telemetry {
107
+ /**
108
+ * Register this in the editor's `modules`. It observes every editor created with it, from
109
+ * the first open on.
110
+ *
111
+ * @example
112
+ * ```tsx
113
+ * const MODULES = [reviewModule(), telemetry.module];
114
+ * <DocxEditor document={bytes} modules={MODULES} />;
115
+ * ```
116
+ */
117
+ readonly module: EditorModule;
118
+ /**
119
+ * A module for one editor, whose events also carry `attributes`, such as its collaboration
120
+ * room, document, or workspace. Create one for each editor, and keep it stable across renders.
121
+ * Use it in place of `module`, not next to it.
122
+ *
123
+ * @example
124
+ * ```tsx
125
+ * const modules = useMemo(
126
+ * () => [reviewModule(), telemetry.moduleFor({ 'room.id': roomId, 'document.id': documentId })],
127
+ * [roomId, documentId]
128
+ * );
129
+ * ```
130
+ */
131
+ moduleFor(attributes: Attributes): EditorModule;
132
+ /**
133
+ * Record one diagnostic yourself, for example from a runtime you created without `module`.
134
+ * Use `module` or `record`, not both, or every event is recorded twice. `attributes` are added
135
+ * to this event, as `moduleFor` adds them.
136
+ */
137
+ record(event: EditorDiagnostic, attributes?: Attributes): void;
138
+ /** Send summaries of open deduplication windows, then flush every exporter. */
139
+ flush(): Promise<void>;
140
+ /** Flush, then stop. Later events are dropped. */
141
+ shutdown(): Promise<void>;
142
+ }
143
+ /**
144
+ * Create a telemetry client for editors.
145
+ *
146
+ * Nothing is sent unless you pass an exporter. With `consent`, nothing is sent while it is
147
+ * false; without it, events are sent. Every event is anonymous: codes, enums, and bucketed
148
+ * numbers only.
149
+ *
150
+ * @example
151
+ * ```ts
152
+ * import { createTelemetry, consoleExporter } from '@docx-editor.dev/telemetry';
153
+ *
154
+ * export const telemetry = createTelemetry({
155
+ * exporters: [consoleExporter()],
156
+ * include: { documentOpen: true, trackedChanges: true },
157
+ * });
158
+ * ```
159
+ *
160
+ * @public
161
+ */
162
+ declare function createTelemetry(options: TelemetryOptions): Telemetry;
163
+
164
+ /**
165
+ * Sending telemetry to your own HTTP endpoint from a browser, and reading the browser's privacy
166
+ * signals. No dependency: `fetch`, `sendBeacon`, and `localStorage` only.
167
+ */
168
+
169
+ /** Options for {@link httpExporter}. @public */
170
+ interface HttpExporterOptions {
171
+ /**
172
+ * Where to POST. Use a route on your own origin, which then forwards to your log service, so
173
+ * the service's token never reaches the browser. See `@docx-editor.dev/telemetry/ingest`.
174
+ */
175
+ readonly url: string;
176
+ /**
177
+ * Events per request, from 1 to 50. Defaults to 20. A full batch is sent at once. The limit
178
+ * matches the default `maxEvents` of `telemetryRoute`, which refuses a longer list.
179
+ */
180
+ readonly batchSize?: number;
181
+ /** How long an event waits for its batch to fill, in milliseconds. Defaults to 10 000. */
182
+ readonly flushIntervalMs?: number;
183
+ /**
184
+ * Extra request headers. A browser cannot set headers on `sendBeacon`, so with headers, the
185
+ * batches sent while the page closes use `fetch` with `keepalive` instead.
186
+ */
187
+ readonly headers?: Readonly<Record<string, string>>;
188
+ /**
189
+ * Called when a request fails, or when batches are dropped because the page closed with more
190
+ * than the browser lets it send. Telemetry never throws into your app.
191
+ */
192
+ readonly onError?: (error: unknown) => void;
193
+ }
194
+ /**
195
+ * Batch events and POST them as a JSON array to your own endpoint.
196
+ *
197
+ * While the page is open, events wait up to `flushIntervalMs` for a batch of `batchSize`, and
198
+ * each batch goes out with an ordinary `fetch`; so does a `telemetry.flush()` you call. When the
199
+ * page is hidden or closes, everything waiting goes out with `sendBeacon`. A closing page may
200
+ * send only about 64 KiB in all, so delivery while closing is best effort: batches beyond that
201
+ * are dropped and reported to `onError`. If consent is withdrawn, events still waiting are
202
+ * discarded.
203
+ *
204
+ * @example
205
+ * ```ts
206
+ * import { createTelemetry, httpExporter } from '@docx-editor.dev/telemetry';
207
+ *
208
+ * export const telemetry = createTelemetry({
209
+ * exporters: [httpExporter({ url: '/api/telemetry' })],
210
+ * include: { documentOpen: true, editing: true, trackedChanges: true, performance: true },
211
+ * });
212
+ * ```
213
+ *
214
+ * @public
215
+ */
216
+ declare function httpExporter(options: HttpExporterOptions): TelemetryExporter;
217
+ /** Options for {@link browserPrivacySignals}. @public */
218
+ interface BrowserPrivacySignalsOptions {
219
+ /**
220
+ * A `localStorage` key that opts the visitor out when its value is `off`, such as one your
221
+ * privacy settings page writes. Omit it to read only the browser signals.
222
+ */
223
+ readonly optOutKey?: string;
224
+ }
225
+ /**
226
+ * A `consent` function that follows the browser's privacy signals: it returns `false` when the
227
+ * visitor sends Global Privacy Control or Do Not Track, or when `optOutKey` is set to `off`.
228
+ * Outside a browser page, such as during server rendering, it returns `false`. Combine it with
229
+ * your consent manager when you have one.
230
+ *
231
+ * @example
232
+ * ```ts
233
+ * import { browserPrivacySignals, createTelemetry } from '@docx-editor.dev/telemetry';
234
+ *
235
+ * const privacy = browserPrivacySignals({ optOutKey: 'my-app:telemetry' });
236
+ * export const telemetry = createTelemetry({
237
+ * exporters: [],
238
+ * consent: () => privacy() && consentManager.analytics,
239
+ * });
240
+ * ```
241
+ *
242
+ * @public
243
+ */
244
+ declare function browserPrivacySignals(options?: BrowserPrivacySignalsOptions): () => boolean;
245
+
246
+ /**
247
+ * Send each event to your own function: Datadog RUM, PostHog, a logger, or an API route.
248
+ *
249
+ * `onMetric` receives every measurement. A performance summary arrives as one histogram sample
250
+ * for each timing in it, at the middle of the timing's band, so the bucket counts are exact.
251
+ * To chart percentiles, read `p50Ms` and `p95Ms` from the `performance` event instead.
252
+ *
253
+ * @example
254
+ * ```ts
255
+ * callbackExporter((event) =>
256
+ * isFailure(event)
257
+ * ? datadogRum.addError(new Error(event.kind), event)
258
+ * : datadogRum.addAction(event.kind, event)
259
+ * );
260
+ * ```
261
+ *
262
+ * @public
263
+ */
264
+ declare function callbackExporter(onEvent: (event: TelemetryEvent) => void, onMetric?: (record: MetricRecord) => void): TelemetryExporter;
265
+ /** Options for {@link consoleExporter}. @public */
266
+ interface ConsoleExporterOptions {
267
+ /** Also print every metric measurement. Defaults to `false`. */
268
+ readonly metrics?: boolean;
269
+ /** Where to print. Defaults to the global `console`. */
270
+ readonly console?: Pick<Console, 'info' | 'warn'>;
271
+ }
272
+ /**
273
+ * Whether an event is a failure: a refused open, a refused edit other than an expected one
274
+ * (a stale range, a read-only document, protected content, a session or refresh not ready
275
+ * yet), an unreadable payload, a font that did not load, or a collaboration error. A missing
276
+ * font that was substituted and a tracked change someone else already reviewed are not. Use it
277
+ * in a callback to send failures as errors and the rest as actions or logs.
278
+ *
279
+ * @example
280
+ * ```ts
281
+ * callbackExporter((event) => {
282
+ * const code = 'code' in event ? event.code : undefined;
283
+ * if (isFailure(event)) myLogger.error(event.kind, { code, hint: event.hint });
284
+ * else myLogger.info(event.kind);
285
+ * });
286
+ * ```
287
+ *
288
+ * @public
289
+ */
290
+ declare function isFailure(event: TelemetryEvent): boolean;
291
+ /**
292
+ * Print each event with its hint and docs link. Use it during development to see why a
293
+ * document did not open or why a tracked change was refused.
294
+ *
295
+ * @example
296
+ * ```ts
297
+ * createTelemetry({ exporters: [consoleExporter()] });
298
+ * // An agent deletes a picture while changes are tracked:
299
+ * // [docx-editor] edit refused: edit.tracked-operation-unsupported (automation, tracked)
300
+ * // This editing API operation cannot be recorded as a tracked change, so nothing was ...
301
+ * // https://docx-editor.dev/docs/2.x/guides/diagnostics#edit-tracked-operation-unsupported
302
+ * ```
303
+ *
304
+ * @public
305
+ */
306
+ declare function consoleExporter(options?: ConsoleExporterOptions): TelemetryExporter;
307
+ /** An exporter that keeps what it receives. Returned by {@link memoryExporter}. @public */
308
+ interface MemoryExporter extends TelemetryExporter {
309
+ readonly events: readonly TelemetryEvent[];
310
+ readonly metrics: readonly MetricRecord[];
311
+ /** Forget everything received so far. */
312
+ clear(): void;
313
+ }
314
+ /**
315
+ * Keep events and metrics in memory, for tests in your own suite.
316
+ *
317
+ * @example
318
+ * ```ts
319
+ * const memory = memoryExporter();
320
+ * const telemetry = createTelemetry({ exporters: [memory] });
321
+ * // ...open a file that fails...
322
+ * expect(memory.events).toContainEqual(
323
+ * expect.objectContaining({ code: 'open.missing-main-document' })
324
+ * );
325
+ * ```
326
+ *
327
+ * @public
328
+ */
329
+ declare function memoryExporter(): MemoryExporter;
330
+
331
+ export { Attributes, MetricRecord, TelemetryEvent, TelemetryExporter, TelemetryPipelineOptions, browserPrivacySignals, callbackExporter, consoleExporter, createTelemetry, httpExporter, isFailure, memoryExporter };
332
+ export type { BrowserPrivacySignalsOptions, ConsoleExporterOptions, HttpExporterOptions, MemoryExporter, Telemetry, TelemetryArea, TelemetryAreaOptions, TelemetryInclude, TelemetryOptions };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export{a as callbackExporter,d as consoleExporter,c as isFailure,e as memoryExporter}from'./chunk-NUN46EXH.js';import {c,b as b$1}from'./chunk-S7KBMROA.js';export{b as ENGINE_VERSION,a as MAX_METRIC_LABEL_VALUES}from'./chunk-S7KBMROA.js';import {a as a$1}from'./chunk-73YEPWY5.js';export{a as METRICS,b as prometheusName}from'./chunk-73YEPWY5.js';import {b,a}from'./chunk-DTEVX2XS.js';import'./chunk-DT2KMPUJ.js';import {explainDiagnostic}from'@docx-editor.dev/core';export{explainDiagnostic}from'@docx-editor.dev/core';var W={documentOpen:true,editing:true,trackedChanges:true,fonts:true,history:true,customNodes:true,collaborationClient:true,performance:true},q=new Set(["edit.tracking-required","edit.suggesting-needs-author","edit.tracking-mode-unsupported","edit.tracking-author-required","edit.tracking-unsupported-host"]);function U(e){switch(e.kind){case "document.open":return "documentOpen";case "edit.rejected":return e.tracked||q.has(e.code)?"trackedChanges":"editing";case "review.skipped":return "trackedChanges";case "font.failed":return "fonts";case "history":return "history";case "custom-node":return "customNodes";case "collab.status":case "collab.participants":return "collaborationClient";case "performance":return "performance";default:return null}}function X(e){return e.kind==="edit.rejected"?[e.editorSessionId,e.code,e.cause,e.errorName,e.stage,e.origin,e.tracked,e.command].join("|"):e.kind==="font.failed"?`${e.editorSessionId}|font|${e.code}`:e.kind==="history"||e.kind==="custom-node"?`${e.editorSessionId}|${e.kind}|${e.code}`:e.kind==="collab.participants"?`${e.editorSessionId}|participants`:e.kind==="collab.status"?`${e.editorSessionId}|status|${e.status}|${e.code??""}|${e.recovering}`:null}function Y(e){let t=c(e),m=e.metricLabels??[];function n(r){let o={};try{let a=typeof e.attributes=="function"?e.attributes():e.attributes;o=b(a??{},"attributes");}catch{}return Object.freeze({...o,...r})}function s(r){let o={};for(let a of m)r[a]!==void 0&&(o[a]=r[a]);return o}let f=e.include??W,l=e.dedupeWindowMs??1e4,g=r=>{try{if(e.filter&&!e.filter(r))return}catch{return}t.event(r);},h=e.documentShape==="detailed",y=new Map,T=false,w=()=>{},E=false;function D(r){let o=f[r];return o?o===true?1:Math.min(1,Math.max(0,o.sampleRate??1)):null}let b$2=new Map;function k(r,o,a){if(a>=1)return true;if(o!=="performance")return Math.random()<a;let p=b$2.get(r.editorSessionId);return p===void 0&&(b$2.size>=1e3&&b$2.clear(),p=Math.random()<a,b$2.set(r.editorSessionId,p)),p}function v(){if(T)return;T=true;let r=globalThis;if(typeof r.addEventListener!="function")return;let o=()=>{t.setPageClosing(true),u();},a=()=>{let p=globalThis.document?.visibilityState==="hidden";t.setPageClosing(p),p&&u();};r.addEventListener("pagehide",o),r.addEventListener("visibilitychange",a),w=()=>{r.removeEventListener?.("pagehide",o),r.removeEventListener?.("visibilitychange",a);};}function x(r){let o=y.get(r);if(o)if(y.delete(r),clearTimeout(o.timer),o.latest){let a=o.event;o.latest.count!==a.count&&g(I(F(o.latest,h),t,o.event.attributes));}else o.repeated>0&&g({...o.event,repeated:o.repeated});}function i(r,o={}){if(E||!t.consented())return;let a$2=n(o),p=a(I(r,t,a$2));if("dropped"in p){t.metric(a$1.droppedEvents,1,{...s(a$2),reason:p.dropped,"docx_editor.version":b$1});return}let A=U(r),M=A?D(A):null;if(M===null||A==="performance"&&M===0)return;v(),Z(t,p.event,s(a$2));let S=l>0?X(r):null,O=S?y.get(S):void 0;if(O){r.kind==="collab.participants"?O.latest=r:O.repeated+=1;return}if(!k(r,A,M))return;let N=I(F(r,h),t,a$2);if(g(N),S){let R=setTimeout(()=>x(S),l);R.unref?.(),y.set(S,{event:N,repeated:0,timer:R});}}function c$1(r){return Object.freeze({id:"telemetry",onDiagnostic:o=>i(o,r),wantsDiagnostic:o=>!E&&(o!=="performance"||(D("performance")??0)>0&&t.consented())})}async function u(){for(let r of [...y.keys()])x(r);await t.flush();}return {module:c$1({}),moduleFor(r){return c$1(b(r,"moduleFor"))},record:i,flush:u,async shutdown(){for(let r of [...y.keys()])x(r);E=true,w(),await t.shutdown();}}}function F(e,t){if(t||e.kind!=="document.open"||e.outcome!=="opened"||!("shape"in e))return e;let{shape:m,...n}=e;return n}function I(e,t,m){let n="code"in e?e.code:void 0;return {...e,...n?explainDiagnostic(n):{},engineVersion:b$1,resource:t.resource,attributes:m}}function Z(e,t,m){let n={metric:(s,f,l={})=>e.metric(s,f,{...l,...m,"docx_editor.version":b$1})};switch(t.kind){case "document.open":t.outcome==="opened"?(n.metric(a$1.documentOpens,1,{outcome:"opened",code:"none",cause:"none",part:"none"}),n.metric(a$1.documentOpenDuration,t.durationMs/1e3)):n.metric(a$1.documentOpens,1,{outcome:"rejected",code:t.code,cause:t.cause??"none",part:t.part??"none"});return;case "edit.rejected":n.metric(a$1.editRejections,1,{code:t.code,cause:t.cause??"none",origin:t.origin,tracked:t.tracked});return;case "review.skipped":n.metric(a$1.reviewSkipped,t.count,{code:t.code,cause:t.cause??"none",operation:t.operation??"none",change_kind:t.changeKind??"none"});return;case "font.failed":n.metric(a$1.fontFailures,1,{code:t.code});return;case "history":n.metric(a$1.historyDiagnostics,1,{code:t.code});return;case "custom-node":n.metric(a$1.customNodeDiagnostics,1,{code:t.code});return;case "collab.status":n.metric(a$1.collabClientStatusTransitions,1,{status:t.status,code:t.code??"none"}),t.connectMs!==void 0&&n.metric(a$1.collabClientConnectDuration,t.connectMs/1e3);return;case "collab.participants":n.metric(a$1.collabClientParticipants,t.count);return;case "performance":{let s=t.pagesBucket??"unknown";L(n,a$1.editorInteractionDuration,s,t.interaction),L(n,a$1.editorLayoutDuration,s,t.layout),L(n,a$1.editorPaintDuration,s,t.paint);}}}function L(e,t,m,n){if(!n)return;let s=t.buckets??[];n.bands.forEach((f,l)=>{let g=s[l],h=l===0?0:s[l-1],y=g===void 0?Math.max(n.maxMs/1e3,h+1e-6):(h+g)/2;for(let T=0;T<f;T+=1)e.metric(t,y,{pages:m});});}var J=new TextEncoder,Q=e=>J.encode(e).byteLength;function ee(e){let t=Math.min(50,Math.max(1,Math.floor(e.batchSize??20))),m=e.flushIntervalMs??1e4,n=[],s,f,l=new Set,g=i=>{try{e.onError?.(i);}catch{}},h=(i,c)=>{if(typeof fetch!="function")return;let u=fetch(e.url,{method:"POST",headers:{"Content-Type":"application/json",...e.headers},body:i,keepalive:c}).then(r=>{if(!r.ok)throw new Error(`telemetry endpoint answered ${r.status}`)}).catch(g).finally(()=>l.delete(u));l.add(u);},y=i=>{let c=globalThis.navigator;if(!e.headers&&typeof c?.sendBeacon=="function")try{if(c.sendBeacon(e.url,new Blob([i],{type:"application/json"})))return}catch{}h(i,true);},T=i=>{let c=Math.min(t,n.length);for(;c>0;){let u=JSON.stringify(n.slice(0,c)),r=Q(u);if(r<=i)return {body:u,bytes:r,count:c};if(c===1)return null;c=Math.ceil(c/2);}return null},w=0,E=false,D=i=>{if(s!==void 0&&clearTimeout(s),s=void 0,f&&!f.consented()){n=[];return}(!i||!E)&&(w=0),E=i;let c=0;for(;n.length;){let u=T(61440-(i?w:0));if(!u){c+=i?n.length:1,n=i?[]:n.slice(1);continue}n=n.slice(u.count),i?(w+=u.bytes,y(u.body)):h(u.body,false);}c&&g(new Error(`telemetry dropped ${c} events that did not fit`));},b=()=>{let i=f?.pageClosing()??false;return i||(E=false),i},k=globalThis.document,v=()=>{k?.visibilityState==="visible"&&(E=false);},x=()=>k?.removeEventListener?.("visibilitychange",v);return {name:"http",attach(i){f=i,k?.addEventListener?.("visibilitychange",v);},event(i){n.push(i),n.length>=t?D(b()):s===void 0&&(s=setTimeout(()=>D(b()),m),s.unref?.());},async flush(){D(b()),await Promise.all(l);},async shutdown(){x(),D(b()),await Promise.all(l);}}}function te(e={}){return ()=>{let t=globalThis;if(!t.window||!t.document||!t.navigator||t.navigator.globalPrivacyControl===true||t.navigator.doNotTrack==="1")return false;if(!e.optOutKey)return true;try{return globalThis.localStorage?.getItem(e.optOutKey)!=="off"}catch{return true}}}export{te as browserPrivacySignals,Y as createTelemetry,ee as httpExporter};
@@ -0,0 +1 @@
1
+ 'use strict';var chunkLFQSY2TD_cjs=require('../chunk-LFQSY2TD.cjs');require('../chunk-WGWKNKUT.cjs');var b=50,i=e=>typeof e=="string",l=e=>typeof e=="number"&&Number.isFinite(e),m=e=>typeof e=="boolean",k={"edit.rejected":[["code",i],["origin",i],["tracked",m]],"review.skipped":[["code",i],["count",l]],"font.failed":[["code",i]],history:[["code",i]],"custom-node":[["code",i]],"collab.status":[["status",i],["previous",i],["recovering",m]],"collab.participants":[["count",l]],performance:[["windowMs",l]]};function h(e){if(!l(e.schemaVersion)||!l(e.time)||!i(e.editorSessionId)||!i(e.engineVersion))return false;if(e.kind==="document.open")return e.outcome==="opened"?i(e.sizeBucket)&&l(e.durationMs):e.outcome==="rejected"&&i(e.code);let r=k[e.kind];return !!r&&r.every(([t,o])=>o(e[t]))}function w(e,r){if(r.size===0)return {};let t=chunkLFQSY2TD_cjs.c(e)??{};return Object.fromEntries(Object.entries(t).filter(([o])=>r.has(o)))}function E(e,r={}){let t=r.maxEvents??b,o=new Set(r.attributeKeys??[]);return !Array.isArray(e)||e.length>t?[]:e.flatMap(n=>{if(!n||typeof n!="object"||Array.isArray(n))return [];let c=n,{resource:d,attributes:u,...s}=c,a=chunkLFQSY2TD_cjs.a(s);if("dropped"in a||!h(a.event))return [];let g=w(c.attributes,o),{attributes:x,...p}=a.event;return [Object.freeze({...p,resource:w(c.resource,o),attributes:g})]})}function O(e){let{hint:r,docs:t,...o}=e,n="code"in e&&e.code?` ${e.code}`:"";return {...o,dt:e.time,message:`${e.kind}${n}`}}var T=64*1024;async function R(e,r){if(Number(e.headers.get("content-length")??0)>r)return null;let t=e.body;if(!t)return "";let o=t.getReader(),n=[],c=0;for(;;){let{done:s,value:a}=await o.read();if(s)break;if(c+=a.byteLength,c>r)return await o.cancel().catch(()=>{}),null;n.push(a);}let d=new Uint8Array(c),u=0;for(let s of n)d.set(s,u),u+=s.byteLength;return new TextDecoder().decode(d)}function v(e){let r=e.maxBodyBytes??T,t=()=>new Response(null,{status:204}),o=n=>{try{e.onError?.(n);}catch{}};return async n=>{if(n.method!=="POST")return t();if(e.allowedOrigins){let s=n.headers.get("origin");if(!s||!e.allowedOrigins.includes(s))return t()}let c;try{c=await R(n,r);}catch{return t()}if(c===null)return t();let d;try{d=JSON.parse(c);}catch{return t()}let u=E(d,e);if(!u.length)return t();try{let a=await e.forward(u);if(a&&a.ok===!1)throw new Error(`the log service answered ${String(a.status)}`)}catch(s){o(s);}return t()}}exports.telemetryRoute=v;exports.toLogRecord=O;exports.validateTelemetryEvents=E;
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Receive editor telemetry on your server and forward it to a log service, such as Better Stack,
3
+ * Axiom, or Datadog Logs. Pair it with `httpExporter` in the browser: the browser posts to your
4
+ * route, and only your server holds the log service's token.
5
+ *
6
+ * Anyone can POST to a public route, so every event is checked again with the same closed lists
7
+ * the browser used, and fields the client chose itself are dropped unless you allow them. No
8
+ * dependency: Fetch API `Request` and `Response` only, so it runs in Next.js route handlers,
9
+ * Hono, Bun, Deno, and Cloudflare Workers.
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+ import { a as TelemetryEvent } from '../pipeline-DbVW7S-K.cjs';
14
+ import '@docx-editor.dev/core';
15
+
16
+ /** Options for {@link validateTelemetryEvents}. @public */
17
+ interface ValidateTelemetryEventsOptions {
18
+ /** The most events one request may carry. Defaults to 50; a longer list is refused whole. */
19
+ readonly maxEvents?: number;
20
+ /**
21
+ * Keys of the browser's `resource` and `attributes` to keep, such as `['app.version']`. Each
22
+ * kept value must be a number, a boolean, or an identifier string. Defaults to none: a value
23
+ * the client chose cannot be checked for meaning, so add your fixed fields on the server.
24
+ */
25
+ readonly attributeKeys?: readonly string[];
26
+ }
27
+ /**
28
+ * The telemetry events in an untrusted request body, checked again with the package's own
29
+ * closed lists. An event with an unknown kind or code, or without the fields its kind needs, is
30
+ * dropped. A field outside its list is removed. The client's `resource` and `attributes` keep
31
+ * only the keys in `attributeKeys`. Returns an empty list for a body that is not an array or is
32
+ * longer than `maxEvents`.
33
+ *
34
+ * The check proves that every value comes from the package's lists or is a number. It cannot
35
+ * prove that a client told the truth: a script can send a well-formed event that never
36
+ * happened. It can never send text.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * const events = validateTelemetryEvents(await request.json(), { attributeKeys: ['app.version'] });
41
+ * ```
42
+ *
43
+ * @public
44
+ */
45
+ declare function validateTelemetryEvents(body: unknown, options?: ValidateTelemetryEventsOptions): TelemetryEvent[];
46
+ /** A log record with the fields log services read for time and summary. @public */
47
+ type TelemetryLogRecord = Record<string, unknown> & {
48
+ /** The event time in milliseconds since the epoch, which Better Stack and others read. */
49
+ readonly dt: number;
50
+ /** A one-line summary, such as `edit.rejected edit.picture-selected`. */
51
+ readonly message: string;
52
+ };
53
+ /**
54
+ * An event as a log record: `dt` for the time, `message` for the live tail, and every field of
55
+ * the event except the fixed `hint` and `docs` text, which the code already identifies. Add
56
+ * your own fixed fields, such as a service name, to the result.
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * body: JSON.stringify(events.map((event) => ({ ...toLogRecord(event), service: 'my-app' })))
61
+ * ```
62
+ *
63
+ * @public
64
+ */
65
+ declare function toLogRecord(event: TelemetryEvent): TelemetryLogRecord;
66
+ /** Options for {@link telemetryRoute}. @public */
67
+ interface TelemetryRouteOptions extends ValidateTelemetryEventsOptions {
68
+ /** Send the checked events on, such as to your log service. Errors are caught and reported. */
69
+ forward(events: readonly TelemetryEvent[]): unknown;
70
+ /** Origins allowed to post, such as `['https://app.example.com']`. Omit to allow any. */
71
+ readonly allowedOrigins?: readonly string[];
72
+ /** The largest body accepted, in bytes. Defaults to 64 KiB. */
73
+ readonly maxBodyBytes?: number;
74
+ /** Called when `forward` throws or rejects, or the log service refuses. Defaults to nothing. */
75
+ readonly onError?: (error: unknown) => void;
76
+ }
77
+ /**
78
+ * A Fetch-style handler for telemetry from `httpExporter`: it accepts POST only, checks the
79
+ * origin, reads at most `maxBodyBytes`, validates every event, and calls `forward` with what
80
+ * passes. It answers 204 to every request, so a telemetry problem never surfaces in the
81
+ * editor; use `onError` to see failures. It does not rate-limit; put it behind your platform's
82
+ * limits.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * // Next.js: app/api/telemetry/route.ts
87
+ * import { telemetryRoute, toLogRecord } from '@docx-editor.dev/telemetry/ingest';
88
+ *
89
+ * export const POST = telemetryRoute({
90
+ * forward: (events) =>
91
+ * fetch(`https://${process.env.BETTERSTACK_INGESTING_HOST}`, {
92
+ * method: 'POST',
93
+ * headers: {
94
+ * 'Content-Type': 'application/json',
95
+ * Authorization: `Bearer ${process.env.BETTERSTACK_SOURCE_TOKEN}`,
96
+ * },
97
+ * body: JSON.stringify(events.map(toLogRecord)),
98
+ * }),
99
+ * });
100
+ * ```
101
+ *
102
+ * @public
103
+ */
104
+ declare function telemetryRoute(options: TelemetryRouteOptions): (request: Request) => Promise<Response>;
105
+
106
+ export { TelemetryEvent, telemetryRoute, toLogRecord, validateTelemetryEvents };
107
+ export type { TelemetryLogRecord, TelemetryRouteOptions, ValidateTelemetryEventsOptions };
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Receive editor telemetry on your server and forward it to a log service, such as Better Stack,
3
+ * Axiom, or Datadog Logs. Pair it with `httpExporter` in the browser: the browser posts to your
4
+ * route, and only your server holds the log service's token.
5
+ *
6
+ * Anyone can POST to a public route, so every event is checked again with the same closed lists
7
+ * the browser used, and fields the client chose itself are dropped unless you allow them. No
8
+ * dependency: Fetch API `Request` and `Response` only, so it runs in Next.js route handlers,
9
+ * Hono, Bun, Deno, and Cloudflare Workers.
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+ import { a as TelemetryEvent } from '../pipeline-DbVW7S-K.js';
14
+ import '@docx-editor.dev/core';
15
+
16
+ /** Options for {@link validateTelemetryEvents}. @public */
17
+ interface ValidateTelemetryEventsOptions {
18
+ /** The most events one request may carry. Defaults to 50; a longer list is refused whole. */
19
+ readonly maxEvents?: number;
20
+ /**
21
+ * Keys of the browser's `resource` and `attributes` to keep, such as `['app.version']`. Each
22
+ * kept value must be a number, a boolean, or an identifier string. Defaults to none: a value
23
+ * the client chose cannot be checked for meaning, so add your fixed fields on the server.
24
+ */
25
+ readonly attributeKeys?: readonly string[];
26
+ }
27
+ /**
28
+ * The telemetry events in an untrusted request body, checked again with the package's own
29
+ * closed lists. An event with an unknown kind or code, or without the fields its kind needs, is
30
+ * dropped. A field outside its list is removed. The client's `resource` and `attributes` keep
31
+ * only the keys in `attributeKeys`. Returns an empty list for a body that is not an array or is
32
+ * longer than `maxEvents`.
33
+ *
34
+ * The check proves that every value comes from the package's lists or is a number. It cannot
35
+ * prove that a client told the truth: a script can send a well-formed event that never
36
+ * happened. It can never send text.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * const events = validateTelemetryEvents(await request.json(), { attributeKeys: ['app.version'] });
41
+ * ```
42
+ *
43
+ * @public
44
+ */
45
+ declare function validateTelemetryEvents(body: unknown, options?: ValidateTelemetryEventsOptions): TelemetryEvent[];
46
+ /** A log record with the fields log services read for time and summary. @public */
47
+ type TelemetryLogRecord = Record<string, unknown> & {
48
+ /** The event time in milliseconds since the epoch, which Better Stack and others read. */
49
+ readonly dt: number;
50
+ /** A one-line summary, such as `edit.rejected edit.picture-selected`. */
51
+ readonly message: string;
52
+ };
53
+ /**
54
+ * An event as a log record: `dt` for the time, `message` for the live tail, and every field of
55
+ * the event except the fixed `hint` and `docs` text, which the code already identifies. Add
56
+ * your own fixed fields, such as a service name, to the result.
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * body: JSON.stringify(events.map((event) => ({ ...toLogRecord(event), service: 'my-app' })))
61
+ * ```
62
+ *
63
+ * @public
64
+ */
65
+ declare function toLogRecord(event: TelemetryEvent): TelemetryLogRecord;
66
+ /** Options for {@link telemetryRoute}. @public */
67
+ interface TelemetryRouteOptions extends ValidateTelemetryEventsOptions {
68
+ /** Send the checked events on, such as to your log service. Errors are caught and reported. */
69
+ forward(events: readonly TelemetryEvent[]): unknown;
70
+ /** Origins allowed to post, such as `['https://app.example.com']`. Omit to allow any. */
71
+ readonly allowedOrigins?: readonly string[];
72
+ /** The largest body accepted, in bytes. Defaults to 64 KiB. */
73
+ readonly maxBodyBytes?: number;
74
+ /** Called when `forward` throws or rejects, or the log service refuses. Defaults to nothing. */
75
+ readonly onError?: (error: unknown) => void;
76
+ }
77
+ /**
78
+ * A Fetch-style handler for telemetry from `httpExporter`: it accepts POST only, checks the
79
+ * origin, reads at most `maxBodyBytes`, validates every event, and calls `forward` with what
80
+ * passes. It answers 204 to every request, so a telemetry problem never surfaces in the
81
+ * editor; use `onError` to see failures. It does not rate-limit; put it behind your platform's
82
+ * limits.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * // Next.js: app/api/telemetry/route.ts
87
+ * import { telemetryRoute, toLogRecord } from '@docx-editor.dev/telemetry/ingest';
88
+ *
89
+ * export const POST = telemetryRoute({
90
+ * forward: (events) =>
91
+ * fetch(`https://${process.env.BETTERSTACK_INGESTING_HOST}`, {
92
+ * method: 'POST',
93
+ * headers: {
94
+ * 'Content-Type': 'application/json',
95
+ * Authorization: `Bearer ${process.env.BETTERSTACK_SOURCE_TOKEN}`,
96
+ * },
97
+ * body: JSON.stringify(events.map(toLogRecord)),
98
+ * }),
99
+ * });
100
+ * ```
101
+ *
102
+ * @public
103
+ */
104
+ declare function telemetryRoute(options: TelemetryRouteOptions): (request: Request) => Promise<Response>;
105
+
106
+ export { TelemetryEvent, telemetryRoute, toLogRecord, validateTelemetryEvents };
107
+ export type { TelemetryLogRecord, TelemetryRouteOptions, ValidateTelemetryEventsOptions };
@@ -0,0 +1 @@
1
+ import {a,c}from'../chunk-DTEVX2XS.js';import'../chunk-DT2KMPUJ.js';var b=50,i=e=>typeof e=="string",l=e=>typeof e=="number"&&Number.isFinite(e),m=e=>typeof e=="boolean",k={"edit.rejected":[["code",i],["origin",i],["tracked",m]],"review.skipped":[["code",i],["count",l]],"font.failed":[["code",i]],history:[["code",i]],"custom-node":[["code",i]],"collab.status":[["status",i],["previous",i],["recovering",m]],"collab.participants":[["count",l]],performance:[["windowMs",l]]};function h(e){if(!l(e.schemaVersion)||!l(e.time)||!i(e.editorSessionId)||!i(e.engineVersion))return false;if(e.kind==="document.open")return e.outcome==="opened"?i(e.sizeBucket)&&l(e.durationMs):e.outcome==="rejected"&&i(e.code);let r=k[e.kind];return !!r&&r.every(([t,o])=>o(e[t]))}function w(e,r){if(r.size===0)return {};let t=c(e)??{};return Object.fromEntries(Object.entries(t).filter(([o])=>r.has(o)))}function E(e,r={}){let t=r.maxEvents??b,o=new Set(r.attributeKeys??[]);return !Array.isArray(e)||e.length>t?[]:e.flatMap(n=>{if(!n||typeof n!="object"||Array.isArray(n))return [];let c=n,{resource:d,attributes:u,...s}=c,a$1=a(s);if("dropped"in a$1||!h(a$1.event))return [];let g=w(c.attributes,o),{attributes:x,...p}=a$1.event;return [Object.freeze({...p,resource:w(c.resource,o),attributes:g})]})}function O(e){let{hint:r,docs:t,...o}=e,n="code"in e&&e.code?` ${e.code}`:"";return {...o,dt:e.time,message:`${e.kind}${n}`}}var T=64*1024;async function R(e,r){if(Number(e.headers.get("content-length")??0)>r)return null;let t=e.body;if(!t)return "";let o=t.getReader(),n=[],c=0;for(;;){let{done:s,value:a}=await o.read();if(s)break;if(c+=a.byteLength,c>r)return await o.cancel().catch(()=>{}),null;n.push(a);}let d=new Uint8Array(c),u=0;for(let s of n)d.set(s,u),u+=s.byteLength;return new TextDecoder().decode(d)}function v(e){let r=e.maxBodyBytes??T,t=()=>new Response(null,{status:204}),o=n=>{try{e.onError?.(n);}catch{}};return async n=>{if(n.method!=="POST")return t();if(e.allowedOrigins){let s=n.headers.get("origin");if(!s||!e.allowedOrigins.includes(s))return t()}let c;try{c=await R(n,r);}catch{return t()}if(c===null)return t();let d;try{d=JSON.parse(c);}catch{return t()}let u=E(d,e);if(!u.length)return t();try{let a=await e.forward(u);if(a&&a.ok===!1)throw new Error(`the log service answered ${String(a.status)}`)}catch(s){o(s);}return t()}}export{v as telemetryRoute,O as toLogRecord,E as validateTelemetryEvents};
@@ -0,0 +1 @@
1
+ 'use strict';var chunkUK2MNJCG_cjs=require('../chunk-UK2MNJCG.cjs'),chunkWGWKNKUT_cjs=require('../chunk-WGWKNKUT.cjs'),api=require('@opentelemetry/api');var w="@docx-editor.dev/telemetry";function O(o){let n=o.getDelegate?.()??o;return /^Noop/.test(n?.constructor?.name??"")}var M=false;function E(o,n){let s=chunkWGWKNKUT_cjs.a();M||s==="production"||!O(o)||!O(n)||(M=true,console.warn("[docx-editor telemetry] otelExporter found no OpenTelemetry SDK. Register a MeterProvider and a TracerProvider before creating the exporter, or pass them as options. Until then every measurement is dropped."));}function h(o,n,s){return o.createObservableGauge(n.name,{unit:n.unit,description:n.description}).addCallback(t=>{for(let{value:i,attributes:a}of s.values())t.observe(i,a);}),{record(t,i={}){s.set(f(i),{value:t,attributes:i});},forget(t){s.delete(f(t));}}}function f(o){return JSON.stringify(Object.keys(o).sort().map(n=>[n,o[n]]))}function m(o){let n={...o.resource,...o.attributes};for(let[s,t]of Object.entries(o))if(!(s==="resource"||s==="attributes"||s==="hint"||s==="docs")){if(typeof t=="string"||typeof t=="number"||typeof t=="boolean")n[`docx_editor.${s}`]=t;else if(s==="shape"&&t&&typeof t=="object")for(let[i,a]of Object.entries(t))typeof a=="string"&&(n[`docx_editor.shape.${i}`]=a);else if(t&&typeof t=="object"&&!Array.isArray(t))for(let[i,a]of Object.entries(t))typeof a=="number"&&(n[`docx_editor.${s}.${i}`]=a);}return n}function j(o={}){let n=o.scope??w,s=()=>o.meterProvider??api.metrics.getMeterProvider(),t=()=>o.tracerProvider??api.trace.getTracerProvider(),i,a,l=new Map,p=new Map,g=false;function P(e){let r=p.get(e);return r||(r=new Map,p.set(e,r)),r}function k(){let e=s();return (e!==i||!a)&&(i=e,a=e.getMeter(n),l.clear()),a}function T(e){let r=k();g||(g=true,setTimeout(()=>E(s(),t()),5e3).unref?.());let c=l.get(e.name);if(c)return c;let d={unit:e.unit,description:e.description},u=e.kind==="counter"?r.createCounter(e.name,d):e.kind==="histogram"?r.createHistogram(e.name,{...d,...e.buckets?{advice:{explicitBucketBoundaries:[...e.buckets]}}:{}}):h(r,e,P(e.name));return l.set(e.name,u),u}return {name:"otel",metric(e){let r=T(e.definition),c=e.attributes;"add"in r?r.add(e.value,c):r.record(e.value,c);},event(e){if(e.kind==="document.open"){let d=e.time-(e.outcome==="opened"?e.durationMs:0),u=t().getTracer(n).startSpan("docx_editor.document.open",{startTime:d,attributes:m(e)},o.parentContext?.());e.outcome==="rejected"&&u.setStatus({code:api.SpanStatusCode.ERROR,message:e.code}),u.end(e.time);return}let r=api.trace.getActiveSpan();if(r){r.addEvent(`docx_editor.${e.kind}`,m(e));return}if(!chunkUK2MNJCG_cjs.c(e))return;let c=t().getTracer(n).startSpan(`docx_editor.${e.kind}`,{startTime:e.time,attributes:m(e)},o.parentContext?.());c.setStatus({code:api.SpanStatusCode.ERROR,message:"code"in e?e.code:e.kind}),c.end(e.time);},forget(e,r){p.get(e.name)?.delete(f(r));},async flush(){let e=r=>(r.getDelegate?.()??r).forceFlush?.();await Promise.all([e(s()),e(t())]);}}}exports.otelExporter=j;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * OpenTelemetry: metrics and traces through the OpenTelemetry SDK you already run. This entry
3
+ * depends only on `@opentelemetry/api`; your SDK decides where data goes (OTLP to a
4
+ * collector, Datadog, Grafana, Honeycomb, New Relic, and others).
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ import { MeterProvider, TracerProvider, Context } from '@opentelemetry/api';
9
+ import { b as TelemetryExporter } from '../pipeline-DbVW7S-K.cjs';
10
+ import '@docx-editor.dev/core';
11
+
12
+ /**
13
+ * OpenTelemetry: metrics and traces through the OpenTelemetry SDK you already run. This entry
14
+ * depends only on `@opentelemetry/api`; your SDK decides where data goes (OTLP to a
15
+ * collector, Datadog, Grafana, Honeycomb, New Relic, and others).
16
+ *
17
+ * @packageDocumentation
18
+ */
19
+
20
+ /** Options for {@link otelExporter}. @public */
21
+ interface OtelExporterOptions {
22
+ /** Defaults to the global provider your SDK registered. */
23
+ readonly meterProvider?: MeterProvider;
24
+ /** Defaults to the global provider your SDK registered. */
25
+ readonly tracerProvider?: TracerProvider;
26
+ /**
27
+ * The parent of each `docx_editor.document.open` span. Browsers have no async context, so
28
+ * without this the span is a root span. Return your page-load or route span's context.
29
+ */
30
+ readonly parentContext?: () => Context | undefined;
31
+ /** Instrumentation scope name. Defaults to `@docx-editor.dev/telemetry`. */
32
+ readonly scope?: string;
33
+ }
34
+ /**
35
+ * Record metrics and traces with OpenTelemetry. The exporter reads the global providers at
36
+ * each use, so it can be created before your SDK starts.
37
+ *
38
+ * Metrics use the names in `METRICS`. Each document open becomes a
39
+ * `docx_editor.document.open` span, with an error status when it is refused. Other events
40
+ * become events on the active span; without one, a failure gets a short span of its own.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * import { createTelemetry } from '@docx-editor.dev/telemetry';
45
+ * import { otelExporter } from '@docx-editor.dev/telemetry/otel';
46
+ *
47
+ * // Before or after your OpenTelemetry SDK starts: the exporter reads the global providers at
48
+ * // each use.
49
+ * export const telemetry = createTelemetry({ exporters: [otelExporter()] });
50
+ * ```
51
+ *
52
+ * @public
53
+ */
54
+ declare function otelExporter(options?: OtelExporterOptions): TelemetryExporter;
55
+
56
+ export { TelemetryExporter, otelExporter };
57
+ export type { OtelExporterOptions };