@omote/core 0.9.1 → 0.9.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/dist/ErrorCodes-AX3ADZri.d.mts +266 -0
- package/dist/ErrorCodes-AX3ADZri.d.ts +266 -0
- package/dist/chunk-CYBTTLG7.mjs +927 -0
- package/dist/chunk-CYBTTLG7.mjs.map +1 -0
- package/dist/chunk-X5OTUOE6.mjs +927 -0
- package/dist/chunk-X5OTUOE6.mjs.map +1 -0
- package/dist/chunk-Y3DTP5P3.mjs +927 -0
- package/dist/chunk-Y3DTP5P3.mjs.map +1 -0
- package/dist/index.d.mts +214 -3
- package/dist/index.d.ts +214 -3
- package/dist/index.js +711 -231
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +636 -223
- package/dist/index.mjs.map +1 -1
- package/dist/logging/index.d.mts +2 -2
- package/dist/logging/index.d.ts +2 -2
- package/dist/logging/index.js +75 -1
- package/dist/logging/index.js.map +1 -1
- package/dist/logging/index.mjs +9 -1
- package/package.json +3 -1
|
@@ -0,0 +1,927 @@
|
|
|
1
|
+
// src/logging/types.ts
|
|
2
|
+
var LOG_LEVEL_PRIORITY = {
|
|
3
|
+
error: 0,
|
|
4
|
+
warn: 1,
|
|
5
|
+
info: 2,
|
|
6
|
+
debug: 3,
|
|
7
|
+
trace: 4,
|
|
8
|
+
verbose: 5
|
|
9
|
+
};
|
|
10
|
+
var DEFAULT_LOGGING_CONFIG = {
|
|
11
|
+
level: "info",
|
|
12
|
+
enabled: true,
|
|
13
|
+
format: "pretty",
|
|
14
|
+
timestamps: true,
|
|
15
|
+
includeModule: true
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// src/logging/formatters.ts
|
|
19
|
+
var COLORS = {
|
|
20
|
+
reset: "\x1B[0m",
|
|
21
|
+
red: "\x1B[31m",
|
|
22
|
+
yellow: "\x1B[33m",
|
|
23
|
+
blue: "\x1B[34m",
|
|
24
|
+
cyan: "\x1B[36m",
|
|
25
|
+
gray: "\x1B[90m",
|
|
26
|
+
white: "\x1B[37m",
|
|
27
|
+
magenta: "\x1B[35m"
|
|
28
|
+
};
|
|
29
|
+
var LEVEL_COLORS = {
|
|
30
|
+
error: COLORS.red,
|
|
31
|
+
warn: COLORS.yellow,
|
|
32
|
+
info: COLORS.blue,
|
|
33
|
+
debug: COLORS.cyan,
|
|
34
|
+
trace: COLORS.magenta,
|
|
35
|
+
verbose: COLORS.gray
|
|
36
|
+
};
|
|
37
|
+
var LEVEL_NAMES = {
|
|
38
|
+
error: "ERROR ",
|
|
39
|
+
warn: "WARN ",
|
|
40
|
+
info: "INFO ",
|
|
41
|
+
debug: "DEBUG ",
|
|
42
|
+
trace: "TRACE ",
|
|
43
|
+
verbose: "VERBOSE"
|
|
44
|
+
};
|
|
45
|
+
var isBrowser = typeof window !== "undefined";
|
|
46
|
+
function formatTimestamp(timestamp) {
|
|
47
|
+
const date = new Date(timestamp);
|
|
48
|
+
return date.toISOString().substring(11, 23);
|
|
49
|
+
}
|
|
50
|
+
function safeStringify(data) {
|
|
51
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
52
|
+
return JSON.stringify(data, (key, value) => {
|
|
53
|
+
if (typeof value === "object" && value !== null) {
|
|
54
|
+
if (seen.has(value)) {
|
|
55
|
+
return "[Circular]";
|
|
56
|
+
}
|
|
57
|
+
seen.add(value);
|
|
58
|
+
}
|
|
59
|
+
if (value instanceof Error) {
|
|
60
|
+
return {
|
|
61
|
+
name: value.name,
|
|
62
|
+
message: value.message,
|
|
63
|
+
stack: value.stack
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (value instanceof Float32Array || value instanceof Int16Array) {
|
|
67
|
+
return `${value.constructor.name}(${value.length})`;
|
|
68
|
+
}
|
|
69
|
+
if (ArrayBuffer.isView(value)) {
|
|
70
|
+
return `${value.constructor.name}(${value.byteLength})`;
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
var jsonFormatter = (entry) => {
|
|
76
|
+
const output = {
|
|
77
|
+
timestamp: entry.timestamp,
|
|
78
|
+
level: entry.level,
|
|
79
|
+
module: entry.module,
|
|
80
|
+
message: entry.message
|
|
81
|
+
};
|
|
82
|
+
if (entry.data && Object.keys(entry.data).length > 0) {
|
|
83
|
+
output.data = entry.data;
|
|
84
|
+
}
|
|
85
|
+
if (entry.error) {
|
|
86
|
+
output.error = {
|
|
87
|
+
name: entry.error.name,
|
|
88
|
+
message: entry.error.message,
|
|
89
|
+
stack: entry.error.stack
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return safeStringify(output);
|
|
93
|
+
};
|
|
94
|
+
var prettyFormatter = (entry) => {
|
|
95
|
+
const time = formatTimestamp(entry.timestamp);
|
|
96
|
+
const level = LEVEL_NAMES[entry.level];
|
|
97
|
+
const module = entry.module;
|
|
98
|
+
const message = entry.message;
|
|
99
|
+
let output;
|
|
100
|
+
if (isBrowser) {
|
|
101
|
+
output = `${time} ${level} [${module}] ${message}`;
|
|
102
|
+
} else {
|
|
103
|
+
const color = LEVEL_COLORS[entry.level];
|
|
104
|
+
output = `${COLORS.gray}${time}${COLORS.reset} ${color}${level}${COLORS.reset} ${COLORS.cyan}[${module}]${COLORS.reset} ${message}`;
|
|
105
|
+
}
|
|
106
|
+
if (entry.data && Object.keys(entry.data).length > 0) {
|
|
107
|
+
const dataStr = safeStringify(entry.data);
|
|
108
|
+
if (dataStr.length > 80) {
|
|
109
|
+
output += "\n " + JSON.stringify(entry.data, null, 2).replace(/\n/g, "\n ");
|
|
110
|
+
} else {
|
|
111
|
+
output += " " + dataStr;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (entry.error) {
|
|
115
|
+
output += `
|
|
116
|
+
${entry.error.name}: ${entry.error.message}`;
|
|
117
|
+
if (entry.error.stack) {
|
|
118
|
+
const stackLines = entry.error.stack.split("\n").slice(1, 4);
|
|
119
|
+
output += "\n " + stackLines.join("\n ");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return output;
|
|
123
|
+
};
|
|
124
|
+
function getFormatter(format) {
|
|
125
|
+
return format === "json" ? jsonFormatter : prettyFormatter;
|
|
126
|
+
}
|
|
127
|
+
function createBrowserConsoleArgs(entry) {
|
|
128
|
+
const time = formatTimestamp(entry.timestamp);
|
|
129
|
+
const level = entry.level.toUpperCase().padEnd(7);
|
|
130
|
+
const module = entry.module;
|
|
131
|
+
const message = entry.message;
|
|
132
|
+
const styles = {
|
|
133
|
+
time: "color: gray;",
|
|
134
|
+
error: "color: red; font-weight: bold;",
|
|
135
|
+
warn: "color: orange; font-weight: bold;",
|
|
136
|
+
info: "color: blue;",
|
|
137
|
+
debug: "color: cyan;",
|
|
138
|
+
trace: "color: magenta;",
|
|
139
|
+
verbose: "color: gray;",
|
|
140
|
+
module: "color: teal; font-weight: bold;",
|
|
141
|
+
message: "color: inherit;"
|
|
142
|
+
};
|
|
143
|
+
let formatStr = "%c%s %c%s %c[%s]%c %s";
|
|
144
|
+
const args = [
|
|
145
|
+
styles.time,
|
|
146
|
+
time,
|
|
147
|
+
styles[entry.level],
|
|
148
|
+
level,
|
|
149
|
+
styles.module,
|
|
150
|
+
module,
|
|
151
|
+
styles.message,
|
|
152
|
+
message
|
|
153
|
+
];
|
|
154
|
+
if (entry.data && Object.keys(entry.data).length > 0) {
|
|
155
|
+
formatStr += " %o";
|
|
156
|
+
args.push(entry.data);
|
|
157
|
+
}
|
|
158
|
+
return [formatStr, ...args];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/telemetry/exporters/console.ts
|
|
162
|
+
var ConsoleExporter = class {
|
|
163
|
+
constructor(options = {}) {
|
|
164
|
+
this.enabled = options.enabled ?? true;
|
|
165
|
+
this.prefix = options.prefix ?? "[Omote Telemetry]";
|
|
166
|
+
}
|
|
167
|
+
exportSpan(span) {
|
|
168
|
+
if (!this.enabled) return;
|
|
169
|
+
const statusIcon = span.status === "ok" ? "\u2713" : "\u2717";
|
|
170
|
+
const statusColor = span.status === "ok" ? "color: green" : "color: red";
|
|
171
|
+
console.groupCollapsed(
|
|
172
|
+
`%c${this.prefix} %c${statusIcon} ${span.name} %c(${span.durationMs.toFixed(2)}ms)`,
|
|
173
|
+
"color: gray",
|
|
174
|
+
statusColor,
|
|
175
|
+
"color: gray"
|
|
176
|
+
);
|
|
177
|
+
console.log("Trace ID:", span.traceId);
|
|
178
|
+
console.log("Span ID:", span.spanId);
|
|
179
|
+
if (span.parentSpanId) {
|
|
180
|
+
console.log("Parent Span ID:", span.parentSpanId);
|
|
181
|
+
}
|
|
182
|
+
console.log("Duration:", `${span.durationMs.toFixed(2)}ms`);
|
|
183
|
+
console.log("Status:", span.status);
|
|
184
|
+
if (Object.keys(span.attributes).length > 0) {
|
|
185
|
+
console.log("Attributes:", span.attributes);
|
|
186
|
+
}
|
|
187
|
+
if (span.error) {
|
|
188
|
+
console.error("Error:", span.error);
|
|
189
|
+
}
|
|
190
|
+
console.groupEnd();
|
|
191
|
+
}
|
|
192
|
+
exportMetric(metric) {
|
|
193
|
+
if (!this.enabled) return;
|
|
194
|
+
const typeIcon = metric.type === "counter" ? "\u2191" : "\u{1F4CA}";
|
|
195
|
+
console.log(
|
|
196
|
+
`%c${this.prefix} %c${typeIcon} ${metric.name}: %c${metric.value}`,
|
|
197
|
+
"color: gray",
|
|
198
|
+
"color: blue",
|
|
199
|
+
"color: black; font-weight: bold",
|
|
200
|
+
metric.attributes
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
async flush() {
|
|
204
|
+
}
|
|
205
|
+
async shutdown() {
|
|
206
|
+
this.enabled = false;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// src/telemetry/exporters/otlp.ts
|
|
211
|
+
var StatusCode = {
|
|
212
|
+
UNSET: 0,
|
|
213
|
+
OK: 1,
|
|
214
|
+
ERROR: 2
|
|
215
|
+
};
|
|
216
|
+
function spanToOTLP(span, serviceName, serviceVersion) {
|
|
217
|
+
const attributes = Object.entries(span.attributes).filter(([, v]) => v !== void 0).map(([key, value]) => ({
|
|
218
|
+
key,
|
|
219
|
+
value: typeof value === "string" ? { stringValue: value } : typeof value === "number" ? Number.isInteger(value) ? { intValue: value } : { doubleValue: value } : { boolValue: value }
|
|
220
|
+
}));
|
|
221
|
+
return {
|
|
222
|
+
resourceSpans: [{
|
|
223
|
+
resource: {
|
|
224
|
+
attributes: [
|
|
225
|
+
{ key: "service.name", value: { stringValue: serviceName } },
|
|
226
|
+
{ key: "service.version", value: { stringValue: serviceVersion } },
|
|
227
|
+
{ key: "telemetry.sdk.name", value: { stringValue: "omote-sdk" } },
|
|
228
|
+
{ key: "telemetry.sdk.language", value: { stringValue: "javascript" } }
|
|
229
|
+
]
|
|
230
|
+
},
|
|
231
|
+
scopeSpans: [{
|
|
232
|
+
scope: {
|
|
233
|
+
name: "omote-sdk",
|
|
234
|
+
version: serviceVersion
|
|
235
|
+
},
|
|
236
|
+
spans: [{
|
|
237
|
+
traceId: span.traceId,
|
|
238
|
+
spanId: span.spanId,
|
|
239
|
+
parentSpanId: span.parentSpanId || "",
|
|
240
|
+
name: span.name,
|
|
241
|
+
kind: 1,
|
|
242
|
+
// INTERNAL
|
|
243
|
+
startTimeUnixNano: String(span.startTime * 1e6),
|
|
244
|
+
endTimeUnixNano: String(span.endTime * 1e6),
|
|
245
|
+
attributes,
|
|
246
|
+
status: {
|
|
247
|
+
code: span.status === "ok" ? StatusCode.OK : StatusCode.ERROR,
|
|
248
|
+
message: span.error?.message || ""
|
|
249
|
+
}
|
|
250
|
+
}]
|
|
251
|
+
}]
|
|
252
|
+
}]
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function metricToOTLP(metric, serviceName, serviceVersion) {
|
|
256
|
+
const attributes = Object.entries(metric.attributes).filter(([, v]) => v !== void 0).map(([key, value]) => ({
|
|
257
|
+
key,
|
|
258
|
+
value: typeof value === "string" ? { stringValue: value } : typeof value === "number" ? Number.isInteger(value) ? { intValue: value } : { doubleValue: value } : { boolValue: value }
|
|
259
|
+
}));
|
|
260
|
+
const dataPoint = {
|
|
261
|
+
attributes,
|
|
262
|
+
timeUnixNano: String(metric.timestamp * 1e6),
|
|
263
|
+
...metric.type === "counter" ? { asInt: metric.value } : { asDouble: metric.value }
|
|
264
|
+
};
|
|
265
|
+
return {
|
|
266
|
+
resourceMetrics: [{
|
|
267
|
+
resource: {
|
|
268
|
+
attributes: [
|
|
269
|
+
{ key: "service.name", value: { stringValue: serviceName } },
|
|
270
|
+
{ key: "service.version", value: { stringValue: serviceVersion } }
|
|
271
|
+
]
|
|
272
|
+
},
|
|
273
|
+
scopeMetrics: [{
|
|
274
|
+
scope: {
|
|
275
|
+
name: "omote-sdk",
|
|
276
|
+
version: serviceVersion
|
|
277
|
+
},
|
|
278
|
+
metrics: [{
|
|
279
|
+
name: metric.name,
|
|
280
|
+
...metric.type === "counter" ? {
|
|
281
|
+
sum: {
|
|
282
|
+
dataPoints: [dataPoint],
|
|
283
|
+
aggregationTemporality: 2,
|
|
284
|
+
// CUMULATIVE
|
|
285
|
+
isMonotonic: true
|
|
286
|
+
}
|
|
287
|
+
} : {
|
|
288
|
+
gauge: {
|
|
289
|
+
dataPoints: [dataPoint]
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}]
|
|
293
|
+
}]
|
|
294
|
+
}]
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
var OTLPExporter = class {
|
|
298
|
+
constructor(config, serviceName = "omote-sdk", serviceVersion = "0.1.0") {
|
|
299
|
+
this.spanBuffer = [];
|
|
300
|
+
this.metricBuffer = [];
|
|
301
|
+
this.flushIntervalId = null;
|
|
302
|
+
this.BUFFER_SIZE = 100;
|
|
303
|
+
this.FLUSH_INTERVAL_MS = 5e3;
|
|
304
|
+
this.isShutdown = false;
|
|
305
|
+
const parsed = new URL(config.endpoint);
|
|
306
|
+
if (parsed.protocol !== "https:") {
|
|
307
|
+
const isLocalhost = parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]";
|
|
308
|
+
if (!isLocalhost) {
|
|
309
|
+
throw new Error("OTLP endpoint must use HTTPS (or localhost for development)");
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
this.config = {
|
|
313
|
+
endpoint: config.endpoint,
|
|
314
|
+
timeoutMs: config.timeoutMs ?? 1e4,
|
|
315
|
+
headers: config.headers ? { ...config.headers } : {}
|
|
316
|
+
};
|
|
317
|
+
this.serviceName = serviceName;
|
|
318
|
+
this.serviceVersion = serviceVersion;
|
|
319
|
+
this.flushIntervalId = setInterval(() => {
|
|
320
|
+
this.flush().catch(console.error);
|
|
321
|
+
}, this.FLUSH_INTERVAL_MS);
|
|
322
|
+
}
|
|
323
|
+
exportSpan(span) {
|
|
324
|
+
if (this.isShutdown) return;
|
|
325
|
+
this.spanBuffer.push(span);
|
|
326
|
+
if (this.spanBuffer.length >= this.BUFFER_SIZE) {
|
|
327
|
+
this.flush().catch(console.error);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
exportMetric(metric) {
|
|
331
|
+
if (this.isShutdown) return;
|
|
332
|
+
this.metricBuffer.push(metric);
|
|
333
|
+
if (this.metricBuffer.length >= this.BUFFER_SIZE) {
|
|
334
|
+
this.flush().catch(console.error);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
async flush() {
|
|
338
|
+
if (this.isShutdown) return;
|
|
339
|
+
const spans = this.spanBuffer.splice(0);
|
|
340
|
+
const metrics = this.metricBuffer.splice(0);
|
|
341
|
+
const promises = [];
|
|
342
|
+
if (spans.length > 0) {
|
|
343
|
+
promises.push(this.exportSpans(spans));
|
|
344
|
+
}
|
|
345
|
+
if (metrics.length > 0) {
|
|
346
|
+
promises.push(this.exportMetrics(metrics));
|
|
347
|
+
}
|
|
348
|
+
await Promise.all(promises);
|
|
349
|
+
}
|
|
350
|
+
async shutdown() {
|
|
351
|
+
if (this.flushIntervalId) {
|
|
352
|
+
clearInterval(this.flushIntervalId);
|
|
353
|
+
this.flushIntervalId = null;
|
|
354
|
+
}
|
|
355
|
+
await this.flush();
|
|
356
|
+
this.isShutdown = true;
|
|
357
|
+
}
|
|
358
|
+
async exportSpans(spans) {
|
|
359
|
+
const resourceSpans = spans.map(
|
|
360
|
+
(span) => spanToOTLP(span, this.serviceName, this.serviceVersion).resourceSpans[0]
|
|
361
|
+
);
|
|
362
|
+
const body = { resourceSpans };
|
|
363
|
+
const endpoint = this.config.endpoint.replace(/\/$/, "") + "/v1/traces";
|
|
364
|
+
await this.sendRequest(endpoint, body);
|
|
365
|
+
}
|
|
366
|
+
async exportMetrics(metrics) {
|
|
367
|
+
const resourceMetrics = metrics.map(
|
|
368
|
+
(metric) => metricToOTLP(metric, this.serviceName, this.serviceVersion).resourceMetrics[0]
|
|
369
|
+
);
|
|
370
|
+
const body = { resourceMetrics };
|
|
371
|
+
const endpoint = this.config.endpoint.replace(/\/$/, "") + "/v1/metrics";
|
|
372
|
+
await this.sendRequest(endpoint, body);
|
|
373
|
+
}
|
|
374
|
+
async sendRequest(endpoint, body) {
|
|
375
|
+
const controller = new AbortController();
|
|
376
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeoutMs);
|
|
377
|
+
try {
|
|
378
|
+
const response = await fetch(endpoint, {
|
|
379
|
+
method: "POST",
|
|
380
|
+
headers: {
|
|
381
|
+
"Content-Type": "application/json",
|
|
382
|
+
...this.config.headers
|
|
383
|
+
},
|
|
384
|
+
body: JSON.stringify(body),
|
|
385
|
+
signal: controller.signal
|
|
386
|
+
});
|
|
387
|
+
if (!response.ok) {
|
|
388
|
+
console.warn(`[OTLP] Export failed: ${response.status} ${response.statusText}`);
|
|
389
|
+
}
|
|
390
|
+
} catch (error) {
|
|
391
|
+
if (error.name === "AbortError") {
|
|
392
|
+
console.warn("[OTLP] Export timed out");
|
|
393
|
+
} else {
|
|
394
|
+
console.warn("[OTLP] Export error:", error);
|
|
395
|
+
}
|
|
396
|
+
} finally {
|
|
397
|
+
clearTimeout(timeoutId);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
// src/logging/Clock.ts
|
|
403
|
+
var defaultClock = {
|
|
404
|
+
now: () => performance.now(),
|
|
405
|
+
timestamp: () => Date.now()
|
|
406
|
+
};
|
|
407
|
+
var activeClock = defaultClock;
|
|
408
|
+
function configureClock(clock) {
|
|
409
|
+
activeClock = clock;
|
|
410
|
+
}
|
|
411
|
+
function getClock() {
|
|
412
|
+
return activeClock;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// src/telemetry/OmoteTelemetry.ts
|
|
416
|
+
function generateId(length = 16) {
|
|
417
|
+
const bytes = new Uint8Array(length);
|
|
418
|
+
crypto.getRandomValues(bytes);
|
|
419
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
420
|
+
}
|
|
421
|
+
var globalTelemetry = null;
|
|
422
|
+
function configureTelemetry(config) {
|
|
423
|
+
if (globalTelemetry) {
|
|
424
|
+
globalTelemetry.shutdown();
|
|
425
|
+
}
|
|
426
|
+
globalTelemetry = new OmoteTelemetry(config);
|
|
427
|
+
return globalTelemetry;
|
|
428
|
+
}
|
|
429
|
+
function getTelemetry() {
|
|
430
|
+
return globalTelemetry;
|
|
431
|
+
}
|
|
432
|
+
var OmoteTelemetry = class {
|
|
433
|
+
constructor(config) {
|
|
434
|
+
this.exporter = null;
|
|
435
|
+
this.activeTraceId = null;
|
|
436
|
+
this.metricsIntervalId = null;
|
|
437
|
+
// Span stack for log-to-span correlation
|
|
438
|
+
this.spanStack = [];
|
|
439
|
+
// Metric accumulators
|
|
440
|
+
this.counters = /* @__PURE__ */ new Map();
|
|
441
|
+
this.histograms = /* @__PURE__ */ new Map();
|
|
442
|
+
this.config = {
|
|
443
|
+
enabled: config.enabled ?? false,
|
|
444
|
+
serviceName: config.serviceName ?? "omote-sdk",
|
|
445
|
+
serviceVersion: config.serviceVersion ?? "0.1.0",
|
|
446
|
+
exporter: config.exporter ?? "none",
|
|
447
|
+
exporterConfig: config.exporterConfig,
|
|
448
|
+
sampling: config.sampling ?? { ratio: 1, alwaysSampleErrors: true },
|
|
449
|
+
metricsEnabled: config.metricsEnabled ?? true,
|
|
450
|
+
metricsIntervalMs: config.metricsIntervalMs ?? 6e4
|
|
451
|
+
};
|
|
452
|
+
if (this.config.enabled) {
|
|
453
|
+
this.initExporter();
|
|
454
|
+
this.startMetricsCollection();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Initialize the configured exporter
|
|
459
|
+
*/
|
|
460
|
+
initExporter() {
|
|
461
|
+
switch (this.config.exporter) {
|
|
462
|
+
case "console":
|
|
463
|
+
this.exporter = new ConsoleExporter({ enabled: true });
|
|
464
|
+
break;
|
|
465
|
+
case "otlp":
|
|
466
|
+
if (!this.config.exporterConfig) {
|
|
467
|
+
console.warn("[Telemetry] OTLP exporter requires exporterConfig with endpoint");
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
this.exporter = new OTLPExporter(
|
|
471
|
+
this.config.exporterConfig,
|
|
472
|
+
this.config.serviceName,
|
|
473
|
+
this.config.serviceVersion
|
|
474
|
+
);
|
|
475
|
+
break;
|
|
476
|
+
case "none":
|
|
477
|
+
default:
|
|
478
|
+
this.exporter = null;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Start periodic metrics collection
|
|
483
|
+
*/
|
|
484
|
+
startMetricsCollection() {
|
|
485
|
+
if (!this.config.metricsEnabled || !this.exporter) return;
|
|
486
|
+
this.metricsIntervalId = setInterval(() => {
|
|
487
|
+
this.flushMetrics();
|
|
488
|
+
}, this.config.metricsIntervalMs);
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Check if this operation should be sampled
|
|
492
|
+
*/
|
|
493
|
+
shouldSample(isError = false) {
|
|
494
|
+
if (!this.config.enabled) return false;
|
|
495
|
+
const sampling = this.config.sampling;
|
|
496
|
+
if (isError && sampling.alwaysSampleErrors) return true;
|
|
497
|
+
const ratio = sampling.ratio ?? 1;
|
|
498
|
+
return Math.random() < ratio;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Start a new span
|
|
502
|
+
*
|
|
503
|
+
* @example
|
|
504
|
+
* ```typescript
|
|
505
|
+
* const span = telemetry.startSpan('Wav2Vec2.infer', {
|
|
506
|
+
* 'inference.input_samples': samples.length,
|
|
507
|
+
* 'model.backend': 'webgpu',
|
|
508
|
+
* });
|
|
509
|
+
*
|
|
510
|
+
* try {
|
|
511
|
+
* const result = await doInference();
|
|
512
|
+
* span.setAttributes({ 'inference.output_frames': result.frames });
|
|
513
|
+
* span.end();
|
|
514
|
+
* } catch (error) {
|
|
515
|
+
* span.endWithError(error);
|
|
516
|
+
* }
|
|
517
|
+
* ```
|
|
518
|
+
*/
|
|
519
|
+
startSpan(name, attributes = {}, parentContext) {
|
|
520
|
+
const traceId = parentContext?.traceId ?? this.activeTraceId ?? generateId(16);
|
|
521
|
+
const spanId = generateId(8);
|
|
522
|
+
const parentSpanId = parentContext?.spanId;
|
|
523
|
+
const startTime = getClock().now();
|
|
524
|
+
if (!parentContext && !this.activeTraceId) {
|
|
525
|
+
this.activeTraceId = traceId;
|
|
526
|
+
}
|
|
527
|
+
let spanAttributes = { ...attributes };
|
|
528
|
+
let ended = false;
|
|
529
|
+
let sampled = this.shouldSample();
|
|
530
|
+
const context = { traceId, spanId, parentSpanId };
|
|
531
|
+
this.spanStack.push({ traceId, spanId });
|
|
532
|
+
const endSpan = (status, error) => {
|
|
533
|
+
if (ended) return;
|
|
534
|
+
ended = true;
|
|
535
|
+
const idx = this.spanStack.findIndex((s) => s.spanId === spanId);
|
|
536
|
+
if (idx !== -1) this.spanStack.splice(idx, 1);
|
|
537
|
+
const endTime = getClock().now();
|
|
538
|
+
const durationMs = endTime - startTime;
|
|
539
|
+
if (status === "error" && !sampled) {
|
|
540
|
+
sampled = this.shouldSample(true);
|
|
541
|
+
}
|
|
542
|
+
if (!sampled || !this.exporter) return;
|
|
543
|
+
const spanData = {
|
|
544
|
+
name,
|
|
545
|
+
traceId,
|
|
546
|
+
spanId,
|
|
547
|
+
parentSpanId,
|
|
548
|
+
startTime,
|
|
549
|
+
endTime,
|
|
550
|
+
durationMs,
|
|
551
|
+
status,
|
|
552
|
+
attributes: spanAttributes,
|
|
553
|
+
error
|
|
554
|
+
};
|
|
555
|
+
this.exporter.exportSpan(spanData);
|
|
556
|
+
if (!parentSpanId && this.activeTraceId === traceId) {
|
|
557
|
+
this.activeTraceId = null;
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
return {
|
|
561
|
+
end: () => endSpan("ok"),
|
|
562
|
+
endWithError: (error) => endSpan("error", error),
|
|
563
|
+
setAttributes: (attrs) => {
|
|
564
|
+
spanAttributes = { ...spanAttributes, ...attrs };
|
|
565
|
+
},
|
|
566
|
+
getContext: () => context
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Wrap an async function with a span
|
|
571
|
+
*
|
|
572
|
+
* @example
|
|
573
|
+
* ```typescript
|
|
574
|
+
* const result = await telemetry.withSpan('Model.load', async (span) => {
|
|
575
|
+
* const model = await loadModel();
|
|
576
|
+
* span.setAttributes({ 'model.size_bytes': model.size });
|
|
577
|
+
* return model;
|
|
578
|
+
* });
|
|
579
|
+
* ```
|
|
580
|
+
*/
|
|
581
|
+
async withSpan(name, fn, attributes = {}, parentContext) {
|
|
582
|
+
const span = this.startSpan(name, attributes, parentContext);
|
|
583
|
+
try {
|
|
584
|
+
const result = await fn(span);
|
|
585
|
+
span.end();
|
|
586
|
+
return result;
|
|
587
|
+
} catch (error) {
|
|
588
|
+
span.endWithError(error);
|
|
589
|
+
throw error;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Increment a counter metric
|
|
594
|
+
*
|
|
595
|
+
* @example
|
|
596
|
+
* ```typescript
|
|
597
|
+
* telemetry.incrementCounter('omote.inference.total', 1, {
|
|
598
|
+
* model: 'wav2vec2',
|
|
599
|
+
* backend: 'webgpu',
|
|
600
|
+
* status: 'success',
|
|
601
|
+
* });
|
|
602
|
+
* ```
|
|
603
|
+
*/
|
|
604
|
+
incrementCounter(name, value = 1, attributes = {}) {
|
|
605
|
+
if (!this.config.enabled || !this.config.metricsEnabled) return;
|
|
606
|
+
const key = this.getMetricKey(name, attributes);
|
|
607
|
+
const existing = this.counters.get(key);
|
|
608
|
+
if (existing) {
|
|
609
|
+
existing.value += value;
|
|
610
|
+
} else {
|
|
611
|
+
this.counters.set(key, { value, attributes });
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Record a histogram value
|
|
616
|
+
*
|
|
617
|
+
* @example
|
|
618
|
+
* ```typescript
|
|
619
|
+
* telemetry.recordHistogram('omote.inference.latency', durationMs, {
|
|
620
|
+
* model: 'wav2vec2',
|
|
621
|
+
* backend: 'webgpu',
|
|
622
|
+
* });
|
|
623
|
+
* ```
|
|
624
|
+
*/
|
|
625
|
+
recordHistogram(name, value, attributes = {}) {
|
|
626
|
+
if (!this.config.enabled || !this.config.metricsEnabled) return;
|
|
627
|
+
const key = this.getMetricKey(name, attributes);
|
|
628
|
+
const existing = this.histograms.get(key);
|
|
629
|
+
if (existing) {
|
|
630
|
+
existing.count++;
|
|
631
|
+
existing.sum += value;
|
|
632
|
+
if (value < existing.min) existing.min = value;
|
|
633
|
+
if (value > existing.max) existing.max = value;
|
|
634
|
+
} else {
|
|
635
|
+
this.histograms.set(key, { count: 1, sum: value, min: value, max: value, attributes });
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Generate unique key for metric with attributes
|
|
640
|
+
*/
|
|
641
|
+
getMetricKey(name, attributes) {
|
|
642
|
+
const keys = Object.keys(attributes);
|
|
643
|
+
if (keys.length === 0) return name;
|
|
644
|
+
const sortedAttrs = Object.entries(attributes).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${v}`).join(",");
|
|
645
|
+
return `${name}|${sortedAttrs}`;
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Flush accumulated metrics to exporter
|
|
649
|
+
*/
|
|
650
|
+
flushMetrics() {
|
|
651
|
+
if (!this.exporter) return;
|
|
652
|
+
const timestamp = getClock().now();
|
|
653
|
+
for (const [key, data] of this.counters) {
|
|
654
|
+
if (data.value === 0) continue;
|
|
655
|
+
const name = key.split("|")[0];
|
|
656
|
+
const metric = {
|
|
657
|
+
name,
|
|
658
|
+
type: "counter",
|
|
659
|
+
value: data.value,
|
|
660
|
+
attributes: data.attributes,
|
|
661
|
+
timestamp
|
|
662
|
+
};
|
|
663
|
+
this.exporter.exportMetric(metric);
|
|
664
|
+
data.value = 0;
|
|
665
|
+
}
|
|
666
|
+
for (const [key, data] of this.histograms) {
|
|
667
|
+
const name = key.split("|")[0];
|
|
668
|
+
if (data.count === 0) continue;
|
|
669
|
+
const avg = data.sum / data.count;
|
|
670
|
+
const metric = {
|
|
671
|
+
name,
|
|
672
|
+
type: "histogram",
|
|
673
|
+
value: avg,
|
|
674
|
+
attributes: {
|
|
675
|
+
...data.attributes,
|
|
676
|
+
count: data.count,
|
|
677
|
+
sum: data.sum,
|
|
678
|
+
min: data.min,
|
|
679
|
+
max: data.max
|
|
680
|
+
},
|
|
681
|
+
timestamp
|
|
682
|
+
};
|
|
683
|
+
this.exporter.exportMetric(metric);
|
|
684
|
+
data.count = 0;
|
|
685
|
+
data.sum = 0;
|
|
686
|
+
data.min = Infinity;
|
|
687
|
+
data.max = -Infinity;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Force flush all pending data
|
|
692
|
+
*/
|
|
693
|
+
async flush() {
|
|
694
|
+
this.flushMetrics();
|
|
695
|
+
await this.exporter?.flush();
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Shutdown telemetry
|
|
699
|
+
*/
|
|
700
|
+
async shutdown() {
|
|
701
|
+
if (this.metricsIntervalId) {
|
|
702
|
+
clearInterval(this.metricsIntervalId);
|
|
703
|
+
this.metricsIntervalId = null;
|
|
704
|
+
}
|
|
705
|
+
await this.flush();
|
|
706
|
+
await this.exporter?.shutdown();
|
|
707
|
+
this.exporter = null;
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Check if telemetry is enabled
|
|
711
|
+
*/
|
|
712
|
+
isEnabled() {
|
|
713
|
+
return this.config.enabled;
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Get current configuration
|
|
717
|
+
*/
|
|
718
|
+
getConfig() {
|
|
719
|
+
return { ...this.config };
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Get the active span context for log-to-span correlation.
|
|
723
|
+
* Returns the most recent (top of stack) active span, or null if none.
|
|
724
|
+
*/
|
|
725
|
+
getActiveContext() {
|
|
726
|
+
return this.spanStack.length > 0 ? this.spanStack[this.spanStack.length - 1] : null;
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
// src/logging/Logger.ts
|
|
731
|
+
var isBrowser2 = typeof window !== "undefined";
|
|
732
|
+
var globalConfig = { ...DEFAULT_LOGGING_CONFIG };
|
|
733
|
+
function configureLogging(config) {
|
|
734
|
+
globalConfig = { ...globalConfig, ...config };
|
|
735
|
+
}
|
|
736
|
+
function getLoggingConfig() {
|
|
737
|
+
return { ...globalConfig };
|
|
738
|
+
}
|
|
739
|
+
function resetLoggingConfig() {
|
|
740
|
+
globalConfig = { ...DEFAULT_LOGGING_CONFIG };
|
|
741
|
+
}
|
|
742
|
+
function setLogLevel(level) {
|
|
743
|
+
globalConfig.level = level;
|
|
744
|
+
}
|
|
745
|
+
function setLoggingEnabled(enabled) {
|
|
746
|
+
globalConfig.enabled = enabled;
|
|
747
|
+
}
|
|
748
|
+
var consoleSink = (entry) => {
|
|
749
|
+
const consoleMethod = entry.level === "error" ? "error" : entry.level === "warn" ? "warn" : "log";
|
|
750
|
+
if (globalConfig.format === "pretty" && isBrowser2) {
|
|
751
|
+
const args = createBrowserConsoleArgs(entry);
|
|
752
|
+
console[consoleMethod](...args);
|
|
753
|
+
} else {
|
|
754
|
+
const formatter = getFormatter(globalConfig.format);
|
|
755
|
+
const formatted = formatter(entry);
|
|
756
|
+
console[consoleMethod](formatted);
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
function getActiveSink() {
|
|
760
|
+
return globalConfig.sink || consoleSink;
|
|
761
|
+
}
|
|
762
|
+
function shouldLog(level) {
|
|
763
|
+
if (!globalConfig.enabled) return false;
|
|
764
|
+
return LOG_LEVEL_PRIORITY[level] <= LOG_LEVEL_PRIORITY[globalConfig.level];
|
|
765
|
+
}
|
|
766
|
+
var Logger = class _Logger {
|
|
767
|
+
constructor(module) {
|
|
768
|
+
this.module = module;
|
|
769
|
+
}
|
|
770
|
+
log(level, message, data) {
|
|
771
|
+
if (!shouldLog(level)) return;
|
|
772
|
+
const entry = {
|
|
773
|
+
timestamp: getClock().timestamp(),
|
|
774
|
+
level,
|
|
775
|
+
module: this.module,
|
|
776
|
+
message,
|
|
777
|
+
data
|
|
778
|
+
};
|
|
779
|
+
const ctx = getTelemetry()?.getActiveContext();
|
|
780
|
+
if (ctx) {
|
|
781
|
+
entry.traceId = ctx.traceId;
|
|
782
|
+
entry.spanId = ctx.spanId;
|
|
783
|
+
}
|
|
784
|
+
if (data?.error instanceof Error) {
|
|
785
|
+
entry.error = data.error;
|
|
786
|
+
const { error, ...rest } = data;
|
|
787
|
+
entry.data = Object.keys(rest).length > 0 ? rest : void 0;
|
|
788
|
+
}
|
|
789
|
+
getActiveSink()(entry);
|
|
790
|
+
}
|
|
791
|
+
error(message, data) {
|
|
792
|
+
this.log("error", message, data);
|
|
793
|
+
}
|
|
794
|
+
warn(message, data) {
|
|
795
|
+
this.log("warn", message, data);
|
|
796
|
+
}
|
|
797
|
+
info(message, data) {
|
|
798
|
+
this.log("info", message, data);
|
|
799
|
+
}
|
|
800
|
+
debug(message, data) {
|
|
801
|
+
this.log("debug", message, data);
|
|
802
|
+
}
|
|
803
|
+
trace(message, data) {
|
|
804
|
+
this.log("trace", message, data);
|
|
805
|
+
}
|
|
806
|
+
verbose(message, data) {
|
|
807
|
+
this.log("verbose", message, data);
|
|
808
|
+
}
|
|
809
|
+
child(subModule) {
|
|
810
|
+
return new _Logger(`${this.module}.${subModule}`);
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
var loggerCache = /* @__PURE__ */ new Map();
|
|
814
|
+
function createLogger(module) {
|
|
815
|
+
let logger = loggerCache.get(module);
|
|
816
|
+
if (!logger) {
|
|
817
|
+
logger = new Logger(module);
|
|
818
|
+
loggerCache.set(module, logger);
|
|
819
|
+
}
|
|
820
|
+
return logger;
|
|
821
|
+
}
|
|
822
|
+
function clearLoggerCache() {
|
|
823
|
+
loggerCache.clear();
|
|
824
|
+
}
|
|
825
|
+
var noopLogger = {
|
|
826
|
+
module: "noop",
|
|
827
|
+
error: () => {
|
|
828
|
+
},
|
|
829
|
+
warn: () => {
|
|
830
|
+
},
|
|
831
|
+
info: () => {
|
|
832
|
+
},
|
|
833
|
+
debug: () => {
|
|
834
|
+
},
|
|
835
|
+
trace: () => {
|
|
836
|
+
},
|
|
837
|
+
verbose: () => {
|
|
838
|
+
},
|
|
839
|
+
child: () => noopLogger
|
|
840
|
+
};
|
|
841
|
+
function getNoopLogger() {
|
|
842
|
+
return noopLogger;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// src/logging/ErrorCodes.ts
|
|
846
|
+
var ErrorCodes = {
|
|
847
|
+
// ── Inference ──────────────────────────────────────────────────────────
|
|
848
|
+
/** Model failed to load (file not found, corrupted, unsupported format) */
|
|
849
|
+
INF_LOAD_FAILED: "OMOTE_INF_001",
|
|
850
|
+
/** ORT session poisoned after WebGPU device loss — must reload tab */
|
|
851
|
+
INF_SESSION_POISON: "OMOTE_INF_002",
|
|
852
|
+
/** Inference exceeded timeout threshold */
|
|
853
|
+
INF_TIMEOUT: "OMOTE_INF_003",
|
|
854
|
+
/** Out-of-memory during inference or model loading */
|
|
855
|
+
INF_OOM: "OMOTE_INF_004",
|
|
856
|
+
/** WebGPU unavailable, fell back to WASM */
|
|
857
|
+
INF_WEBGPU_FALLBACK: "OMOTE_INF_005",
|
|
858
|
+
/** Input tensor shape does not match model expectations */
|
|
859
|
+
INF_SHAPE_MISMATCH: "OMOTE_INF_006",
|
|
860
|
+
// ── Audio ──────────────────────────────────────────────────────────────
|
|
861
|
+
/** AudioContext creation or resume failed */
|
|
862
|
+
AUD_CONTEXT_FAILED: "OMOTE_AUD_001",
|
|
863
|
+
/** Gap detected in audio scheduling (buffer underrun) */
|
|
864
|
+
AUD_SCHEDULE_GAP: "OMOTE_AUD_002",
|
|
865
|
+
/** Audio buffer decoding failed */
|
|
866
|
+
AUD_DECODE_FAILED: "OMOTE_AUD_003",
|
|
867
|
+
// ── Speech ─────────────────────────────────────────────────────────────
|
|
868
|
+
/** Voice activity detection error */
|
|
869
|
+
SPH_VAD_ERROR: "OMOTE_SPH_001",
|
|
870
|
+
/** Automatic speech recognition error */
|
|
871
|
+
SPH_ASR_ERROR: "OMOTE_SPH_002",
|
|
872
|
+
/** Microphone access denied or unavailable */
|
|
873
|
+
SPH_MIC_DENIED: "OMOTE_SPH_003",
|
|
874
|
+
// ── TTS ────────────────────────────────────────────────────────────────
|
|
875
|
+
/** TTS synthesis failed */
|
|
876
|
+
TTS_SYNTH_FAILED: "OMOTE_TTS_001",
|
|
877
|
+
/** TTS streaming error (chunk delivery failure) */
|
|
878
|
+
TTS_STREAM_ERROR: "OMOTE_TTS_002",
|
|
879
|
+
/** Phonemizer (eSpeak-NG WASM) ran out of memory */
|
|
880
|
+
TTS_PHONEMIZER_OOM: "OMOTE_TTS_003",
|
|
881
|
+
// ── Pipeline ───────────────────────────────────────────────────────────
|
|
882
|
+
/** Invalid state transition in pipeline state machine */
|
|
883
|
+
PIP_STATE_ERROR: "OMOTE_PIP_001",
|
|
884
|
+
/** Pipeline operation aborted (user interrupt or signal) */
|
|
885
|
+
PIP_ABORT: "OMOTE_PIP_002",
|
|
886
|
+
// ── Cache ──────────────────────────────────────────────────────────────
|
|
887
|
+
/** IndexedDB storage quota exceeded */
|
|
888
|
+
CAC_QUOTA_EXCEEDED: "OMOTE_CAC_001",
|
|
889
|
+
/** Cache entry evicted (LRU or manual) */
|
|
890
|
+
CAC_EVICTION: "OMOTE_CAC_002",
|
|
891
|
+
/** Cached model is stale (version mismatch) */
|
|
892
|
+
CAC_STALE: "OMOTE_CAC_003",
|
|
893
|
+
// ── Network ────────────────────────────────────────────────────────────
|
|
894
|
+
/** HTTP fetch failed (model download, CDN) */
|
|
895
|
+
NET_FETCH_FAILED: "OMOTE_NET_001",
|
|
896
|
+
/** Network request timed out */
|
|
897
|
+
NET_TIMEOUT: "OMOTE_NET_002",
|
|
898
|
+
/** WebSocket connection error */
|
|
899
|
+
NET_WEBSOCKET_ERROR: "OMOTE_NET_003"
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
export {
|
|
903
|
+
LOG_LEVEL_PRIORITY,
|
|
904
|
+
DEFAULT_LOGGING_CONFIG,
|
|
905
|
+
jsonFormatter,
|
|
906
|
+
prettyFormatter,
|
|
907
|
+
getFormatter,
|
|
908
|
+
ConsoleExporter,
|
|
909
|
+
OTLPExporter,
|
|
910
|
+
defaultClock,
|
|
911
|
+
configureClock,
|
|
912
|
+
getClock,
|
|
913
|
+
configureTelemetry,
|
|
914
|
+
getTelemetry,
|
|
915
|
+
OmoteTelemetry,
|
|
916
|
+
configureLogging,
|
|
917
|
+
getLoggingConfig,
|
|
918
|
+
resetLoggingConfig,
|
|
919
|
+
setLogLevel,
|
|
920
|
+
setLoggingEnabled,
|
|
921
|
+
createLogger,
|
|
922
|
+
clearLoggerCache,
|
|
923
|
+
noopLogger,
|
|
924
|
+
getNoopLogger,
|
|
925
|
+
ErrorCodes
|
|
926
|
+
};
|
|
927
|
+
//# sourceMappingURL=chunk-Y3DTP5P3.mjs.map
|