@safaricom-mxl/effect-sdk 0.0.23 → 0.0.24

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.
@@ -0,0 +1,495 @@
1
+ import { t as __exportAll } from "../rolldown-runtime-D8jQpDG7.js";
2
+ import { _ as scrubUrl, n as make$1 } from "../flushable-CXe-q-GF.js";
3
+ import { Context, Effect, Exit, Layer, Option, Predicate, Tracer } from "effect";
4
+ // src/foldkit/args.ts
5
+ const REDACTED = "[redacted]";
6
+ const MAX_STRING = 256;
7
+ const MAX_ARRAY = 20;
8
+ const ID_KEY = /(?:^id|Id|Ids|_id|_ids)$/;
9
+ const capString = (value) => value.length > MAX_STRING ? `${value.slice(0, MAX_STRING)}…` : value;
10
+ /** Primitives and arrays of primitives, size-capped. Anything else is not an attribute. */
11
+ const toAttributeValue = (value) => {
12
+ if (value === null) return null;
13
+ if (typeof value === "string") return capString(value);
14
+ if (typeof value === "number" || typeof value === "boolean") return value;
15
+ if (Array.isArray(value)) {
16
+ const items = [];
17
+ for (const item of value.slice(0, MAX_ARRAY)) if (typeof item === "string") items.push(capString(item));
18
+ else if (typeof item === "number" || typeof item === "boolean") items.push(item);
19
+ else return void 0;
20
+ return items;
21
+ }
22
+ };
23
+ const makeArgsScrubber = (policy = "ids") => {
24
+ if (policy === "none") return () => ({});
25
+ const allow = new Set(typeof policy === "object" ? policy.allow ?? [] : []);
26
+ const keepsAll = policy === "all";
27
+ return (args) => {
28
+ const out = {};
29
+ if (args === void 0) return out;
30
+ for (const key of Object.keys(args)) {
31
+ const value = toAttributeValue(args[key]);
32
+ if (value === void 0) continue;
33
+ out[key] = keepsAll || allow.has(key) || typeof value === "boolean" || value === null || ID_KEY.test(key) ? value : REDACTED;
34
+ }
35
+ return out;
36
+ };
37
+ };
38
+ // src/foldkit/message.ts
39
+ const MAX_DEPTH = 4;
40
+ /**
41
+ * `GotChat > SubmittedComposer` for a Submodel Message wrapped by its parent.
42
+ * Only `_tag`s: a Message's fields can hold anything the user typed.
43
+ */
44
+ const messageTag = (message) => {
45
+ const tags = [];
46
+ let current = message;
47
+ while (tags.length < MAX_DEPTH && Predicate.hasProperty(current, "_tag") && typeof current._tag === "string") {
48
+ tags.push(current._tag);
49
+ current = Predicate.hasProperty(current, "message") ? current.message : void 0;
50
+ }
51
+ return tags.length === 0 ? "unknown" : tags.join(" > ");
52
+ };
53
+ /** The last `size` Message tags, oldest first, for crash reports. */
54
+ const makeRecentMessages = (size) => {
55
+ const items = [];
56
+ return {
57
+ push: (tag) => {
58
+ items.push(tag);
59
+ if (items.length > size) items.shift();
60
+ },
61
+ read: () => items.slice()
62
+ };
63
+ };
64
+ // src/foldkit/tracer.ts
65
+ /** Internal arg carrying a navigation id from `update` to the tracer. Never exported. */
66
+ const NAVIGATION_ARG = "mxl.foldkit.navigation_id";
67
+ const MAX_EARLY_PHASES = 50;
68
+ const nowNanos = () => BigInt(Math.round((performance.timeOrigin + performance.now()) * 1e6));
69
+ const makeFoldkitTracing = (options) => {
70
+ const commandNames = /* @__PURE__ */ new Set();
71
+ const navigations = /* @__PURE__ */ new Map();
72
+ let nextNavigationId = 1;
73
+ let latestNavigation;
74
+ let inner;
75
+ const earlyPhases = [];
76
+ const navigationSpan = (navigation, tracer) => {
77
+ if (navigation.span !== void 0) return navigation.span;
78
+ const span = tracer.span({
79
+ name: `${navigation.kind} ${navigation.route}`,
80
+ parent: Option.none(),
81
+ annotations: Context.empty(),
82
+ links: [],
83
+ startTime: navigation.startTime,
84
+ kind: "internal",
85
+ root: true,
86
+ sampled: navigation.sampled
87
+ });
88
+ span.attribute("url.path", navigation.path);
89
+ navigation.span = span;
90
+ return span;
91
+ };
92
+ /**
93
+ * The span to parent under: the navigation's own while it is open, or the one it ended with.
94
+ * A navigation that closed before any child needed a span has none to end, so none is made.
95
+ */
96
+ const parentSpanOf = (navigation, tracer) => {
97
+ if (navigation === void 0) return Option.none();
98
+ if (navigation.span !== void 0) return Option.some(navigation.span);
99
+ return navigations.has(navigation.id) ? Option.some(navigationSpan(navigation, tracer)) : Option.none();
100
+ };
101
+ const closeNavigation = (navigation, endTime, interrupted) => {
102
+ if (!navigations.has(navigation.id)) return;
103
+ navigations.delete(navigation.id);
104
+ clearTimeout(navigation.capTimer);
105
+ if (navigation.quietTimer !== void 0) clearTimeout(navigation.quietTimer);
106
+ if (latestNavigation === navigation) latestNavigation = void 0;
107
+ if (navigation.span === void 0) return;
108
+ if (interrupted) navigation.span.attribute("mxl.navigation.interrupted", true);
109
+ navigation.span.end(endTime, Exit.void);
110
+ };
111
+ const armQuietTimer = (navigation, delayMs) => {
112
+ if (navigation.quietTimer !== void 0) clearTimeout(navigation.quietTimer);
113
+ navigation.quietTimer = setTimeout(() => closeNavigation(navigation, navigation.lastEnd, false), delayMs);
114
+ };
115
+ const startNavigation = (input) => {
116
+ const id = nextNavigationId++;
117
+ const navigation = {
118
+ ...input,
119
+ id,
120
+ sampled: Math.random() < options.navigationSampleRate,
121
+ span: void 0,
122
+ pending: 0,
123
+ hasCommand: false,
124
+ lastEnd: input.startTime,
125
+ quietTimer: void 0,
126
+ capTimer: setTimeout(() => closeNavigation(navigation, nowNanos(), true), options.navigationMaxMs)
127
+ };
128
+ navigations.set(id, navigation);
129
+ latestNavigation = navigation;
130
+ armQuietTimer(navigation, options.navigationStartGraceMs);
131
+ return id;
132
+ };
133
+ const commandStarted = (navigation) => {
134
+ navigation.pending += 1;
135
+ navigation.hasCommand = true;
136
+ if (navigation.quietTimer !== void 0) clearTimeout(navigation.quietTimer);
137
+ navigation.quietTimer = void 0;
138
+ };
139
+ const commandEnded = (navigation, endTime) => {
140
+ navigation.pending -= 1;
141
+ if (endTime > navigation.lastEnd) navigation.lastEnd = endTime;
142
+ if (navigation.pending > 0) return;
143
+ armQuietTimer(navigation, options.navigationQuietMs);
144
+ };
145
+ class CommandSpan {
146
+ tracer;
147
+ options;
148
+ _tag = "Span";
149
+ name;
150
+ annotations;
151
+ kind;
152
+ real;
153
+ navigation;
154
+ hasChildren = false;
155
+ buffered = [];
156
+ constructor(tracer, options) {
157
+ this.tracer = tracer;
158
+ this.options = options;
159
+ this.name = options.name;
160
+ this.annotations = options.annotations;
161
+ this.kind = options.kind;
162
+ }
163
+ materialize() {
164
+ if (this.real !== void 0) return this.real;
165
+ const navigation = this.navigation;
166
+ const parent = navigation === void 0 ? this.options.parent : parentSpanOf(navigation, this.tracer);
167
+ const rate = options.sampleRates[this.name];
168
+ const sampled = navigation === void 0 ? this.options.sampled && (rate === void 0 || Math.random() < rate) : navigation.sampled;
169
+ const real = this.tracer.span({
170
+ ...this.options,
171
+ parent,
172
+ root: navigation === void 0 || Option.isNone(parent),
173
+ sampled
174
+ });
175
+ for (const [key, value] of this.buffered) real.attribute(key, value);
176
+ this.buffered.length = 0;
177
+ this.real = real;
178
+ return real;
179
+ }
180
+ childStarted() {
181
+ this.hasChildren = true;
182
+ }
183
+ get spanId() {
184
+ return this.materialize().spanId;
185
+ }
186
+ get traceId() {
187
+ return this.materialize().traceId;
188
+ }
189
+ get sampled() {
190
+ return this.materialize().sampled;
191
+ }
192
+ get parent() {
193
+ return this.materialize().parent;
194
+ }
195
+ get status() {
196
+ return this.real?.status ?? {
197
+ _tag: "Started",
198
+ startTime: this.options.startTime
199
+ };
200
+ }
201
+ get attributes() {
202
+ return this.real?.attributes ?? new Map(this.buffered);
203
+ }
204
+ get links() {
205
+ return this.real?.links ?? this.options.links;
206
+ }
207
+ attribute(key, value) {
208
+ if (key === "mxl.foldkit.navigation_id") {
209
+ const navigation = typeof value === "number" ? navigations.get(value) : void 0;
210
+ if (navigation !== void 0 && this.real === void 0) {
211
+ this.navigation = navigation;
212
+ commandStarted(navigation);
213
+ }
214
+ return;
215
+ }
216
+ if (this.real !== void 0) this.real.attribute(key, value);
217
+ else this.buffered.push([key, value]);
218
+ }
219
+ event(name, startTime, attributes) {
220
+ this.materialize().event(name, startTime, attributes);
221
+ }
222
+ addLinks(links) {
223
+ this.materialize().addLinks(links);
224
+ }
225
+ end(endTime, exit) {
226
+ if (this.navigation !== void 0) commandEnded(this.navigation, endTime);
227
+ if (this.real === void 0 && !this.hasChildren && Exit.isSuccess(exit) && !options.keepCommands.has(this.name)) return;
228
+ this.materialize().end(endTime, exit);
229
+ }
230
+ }
231
+ const wrap = (tracer) => {
232
+ inner = tracer;
233
+ for (const record of earlyPhases.splice(0)) record(tracer);
234
+ return Tracer.make({
235
+ context: tracer.context,
236
+ span(spanOptions) {
237
+ if (Option.isSome(spanOptions.parent)) {
238
+ const parent = spanOptions.parent.value;
239
+ if (parent instanceof CommandSpan) parent.childStarted();
240
+ return tracer.span(spanOptions);
241
+ }
242
+ if (!commandNames.has(spanOptions.name)) return tracer.span(spanOptions);
243
+ return new CommandSpan(tracer, spanOptions);
244
+ }
245
+ });
246
+ };
247
+ /** A finished phase span (slow `update`/`view`/patch), under the open navigation. */
248
+ const recordPhase = (name, startTime, endTime, attributes) => {
249
+ const navigation = latestNavigation;
250
+ if (navigation !== void 0 && endTime > navigation.lastEnd) {
251
+ navigation.lastEnd = endTime;
252
+ if (navigation.hasCommand && navigation.pending === 0) armQuietTimer(navigation, options.navigationQuietMs);
253
+ }
254
+ const record = (tracer) => {
255
+ const parent = parentSpanOf(navigation, tracer);
256
+ const span = tracer.span({
257
+ name,
258
+ parent,
259
+ annotations: Context.empty(),
260
+ links: [],
261
+ startTime,
262
+ kind: "internal",
263
+ root: Option.isNone(parent),
264
+ sampled: navigation?.sampled ?? true
265
+ });
266
+ for (const key of Object.keys(attributes)) span.attribute(key, attributes[key]);
267
+ span.end(endTime, Exit.void);
268
+ };
269
+ if (inner !== void 0) {
270
+ record(inner);
271
+ return;
272
+ }
273
+ if (earlyPhases.length < MAX_EARLY_PHASES) earlyPhases.push(record);
274
+ };
275
+ /** Ends every open navigation, e.g. when the page goes away. */
276
+ const interruptAll = () => {
277
+ for (const navigation of Array.from(navigations.values())) closeNavigation(navigation, nowNanos(), true);
278
+ };
279
+ return {
280
+ registerCommand: (name) => {
281
+ commandNames.add(name);
282
+ },
283
+ startNavigation,
284
+ wrap,
285
+ recordPhase,
286
+ interruptAll
287
+ };
288
+ };
289
+ // src/foldkit/make.ts
290
+ var make_exports = /* @__PURE__ */ __exportAll({ make: () => make });
291
+ const isFn = (value) => typeof value === "function";
292
+ const field = (owner, key) => Predicate.hasProperty(owner, key) ? owner[key] : void 0;
293
+ const fnField = (owner, key) => {
294
+ const value = field(owner, key);
295
+ return isFn(value) ? value : void 0;
296
+ };
297
+ const objectField = (owner, key) => {
298
+ const value = field(owner, key);
299
+ return Predicate.isObject(value) ? value : void 0;
300
+ };
301
+ const isThresholds = (value) => Predicate.isObject(value);
302
+ const parseAppConfig = (app) => {
303
+ const resources = field(app, "resources");
304
+ const routing = objectField(app, "routing");
305
+ const crash = objectField(app, "crash");
306
+ const slow = field(app, "slow");
307
+ const slowThresholds = field(slow, "thresholdOverrides");
308
+ return {
309
+ resources: Layer.isLayer(resources) ? resources : void 0,
310
+ init: fnField(app, "init"),
311
+ update: fnField(app, "update"),
312
+ routing,
313
+ onUrlChange: fnField(routing, "onUrlChange"),
314
+ crash,
315
+ crashReport: fnField(crash, "report"),
316
+ slow: slow === false ? false : Predicate.isObject(slow) ? slow : void 0,
317
+ onSlow: fnField(slow, "onSlow"),
318
+ slowThresholds: isThresholds(slowThresholds) ? slowThresholds : void 0
319
+ };
320
+ };
321
+ /** Command args export as `mxl.foldkit.arg.<key>`. */
322
+ const ARG_PREFIX = "mxl.foldkit.arg.";
323
+ const DEFAULT_SLOW = {
324
+ Update: 16,
325
+ View: 50,
326
+ Patch: 50
327
+ };
328
+ /** A Command's result Message remembers the navigation that caused it, so its follow-ups join it. */
329
+ const makeNavigationMarks = () => {
330
+ const marks = /* @__PURE__ */ new WeakMap();
331
+ return {
332
+ get: (message) => Predicate.isObject(message) ? marks.get(message) : void 0,
333
+ set: (message, id) => {
334
+ if (Predicate.isObject(message)) marks.set(message, id);
335
+ }
336
+ };
337
+ };
338
+ const isUpdateReturn = (value) => Predicate.hasProperty(value, "model");
339
+ /**
340
+ * MXL telemetry for a Foldkit app: Command spans with navigation parents,
341
+ * scrubbed args, crash reports and slow-phase spans, on top of the buffered
342
+ * browser tracer (`MxlFlush.make`) with session replay.
343
+ *
344
+ * @example
345
+ * ```ts
346
+ * import { MxlFoldkit } from "@safaricom-mxl/effect-sdk/foldkit"
347
+ *
348
+ * const telemetry = MxlFoldkit.make({ serviceName: "acme-web", endpoint: "http://127.0.0.1:4318" })
349
+ *
350
+ * Runtime.run(Runtime.makeApplication(telemetry.instrument({ Model, init, update, view, resources, routing })))
351
+ * ```
352
+ */
353
+ const make = (config) => {
354
+ const tracing = makeFoldkitTracing({
355
+ keepCommands: new Set(config.keepCommands ?? []),
356
+ sampleRates: config.sampleCommands ?? {},
357
+ navigationSampleRate: config.navigationSampleRate ?? 1,
358
+ navigationQuietMs: 150,
359
+ navigationStartGraceMs: 1e3,
360
+ navigationMaxMs: 3e4
361
+ });
362
+ const canListen = typeof globalThis.addEventListener === "function";
363
+ if (canListen) globalThis.addEventListener("pagehide", tracing.interruptAll, { capture: true });
364
+ const telemetry = make$1(config);
365
+ const scrubArgs = makeArgsScrubber(config.commandArgs);
366
+ const recent = makeRecentMessages(20);
367
+ const marks = makeNavigationMarks();
368
+ const routeOf = (url) => scrubUrl(config.routeName?.(url.pathname) ?? url.pathname);
369
+ const layer = Layer.effect(Tracer.Tracer, Effect.map(Effect.tracer, tracing.wrap)).pipe(Layer.provideMerge(telemetry.layer));
370
+ const instrumentCommand = (command, tag, navigationId) => {
371
+ tracing.registerCommand(command.name);
372
+ const args = {
373
+ ...Object.fromEntries(Object.entries(scrubArgs(command.args)).map(([key, value]) => [`${ARG_PREFIX}${key}`, value])),
374
+ "mxl.foldkit.message": tag
375
+ };
376
+ if (navigationId === void 0) return {
377
+ ...command,
378
+ args
379
+ };
380
+ return {
381
+ ...command,
382
+ args: {
383
+ ...args,
384
+ [NAVIGATION_ARG]: navigationId
385
+ },
386
+ effect: Effect.tap(command.effect, (message) => Effect.sync(() => marks.set(message, navigationId)))
387
+ };
388
+ };
389
+ const withCommands = (result, tag, navigationId) => {
390
+ if (!isUpdateReturn(result) || result.commands === void 0 || result.commands.length === 0) return result;
391
+ return {
392
+ ...result,
393
+ commands: result.commands.map((command) => instrumentCommand(command, tag, navigationId))
394
+ };
395
+ };
396
+ const instrumentUpdate = (update) => (model, message) => {
397
+ const tag = messageTag(message);
398
+ recent.push(tag);
399
+ return withCommands(update(model, message), tag, marks.get(message));
400
+ };
401
+ const instrumentInit = (init) => (flags, url) => {
402
+ const navigationId = url === void 0 ? void 0 : tracing.startNavigation({
403
+ kind: "pageload",
404
+ route: routeOf(url),
405
+ path: scrubUrl(url.pathname),
406
+ startTime: BigInt(Math.round(performance.timeOrigin * 1e6))
407
+ });
408
+ return withCommands(init(flags, url), "init", navigationId);
409
+ };
410
+ const instrumentOnUrlChange = (onUrlChange) => (url) => {
411
+ const message = onUrlChange(url);
412
+ const navigationId = tracing.startNavigation({
413
+ kind: "navigate",
414
+ route: routeOf(url),
415
+ path: scrubUrl(url.pathname),
416
+ startTime: nowNanos()
417
+ });
418
+ marks.set(message, navigationId);
419
+ return message;
420
+ };
421
+ const reportCrash = (context) => {
422
+ telemetry.captureException(context.error, {
423
+ name: "foldkit.crash",
424
+ attributes: {
425
+ "mxl.exception.source": "foldkit.crash",
426
+ "mxl.foldkit.message": Predicate.hasProperty(context.message, "value") ? messageTag(context.message.value) : "init",
427
+ "mxl.foldkit.recent_messages": recent.read()
428
+ }
429
+ });
430
+ telemetry.flush();
431
+ };
432
+ const thresholds = config.slowPhases === false ? void 0 : {
433
+ ...DEFAULT_SLOW,
434
+ ...config.slowPhases
435
+ };
436
+ const recordSlow = (context) => {
437
+ if (context._tag === "SubscriptionDependencies") return;
438
+ const endTime = nowNanos();
439
+ const startTime = endTime - BigInt(Math.round(context.durationMs * 1e6));
440
+ const message = context._tag === "Update" ? context.message : Predicate.hasProperty(context.message, "value") ? context.message.value : void 0;
441
+ tracing.recordPhase(`foldkit.${context._tag.toLowerCase()}`, startTime, endTime, {
442
+ "mxl.foldkit.phase": context._tag,
443
+ "mxl.foldkit.duration_ms": context.durationMs,
444
+ "mxl.foldkit.threshold_ms": context.thresholdMs,
445
+ ...message === void 0 ? void 0 : { "mxl.foldkit.message": messageTag(message) }
446
+ });
447
+ };
448
+ const instrument = (app) => {
449
+ const parsed = parseAppConfig(app);
450
+ const { init, update, onUrlChange, crashReport, onSlow } = parsed;
451
+ return {
452
+ ...app,
453
+ resources: parsed.resources === void 0 ? layer : Layer.merge(parsed.resources, layer),
454
+ crash: {
455
+ ...parsed.crash,
456
+ report: (context) => {
457
+ reportCrash(context);
458
+ crashReport?.(context);
459
+ }
460
+ },
461
+ ...update === void 0 ? void 0 : { update: instrumentUpdate(update) },
462
+ ...init === void 0 ? void 0 : { init: instrumentInit(init) },
463
+ ...onUrlChange === void 0 ? void 0 : { routing: {
464
+ ...parsed.routing,
465
+ onUrlChange: instrumentOnUrlChange(onUrlChange)
466
+ } },
467
+ ...thresholds === void 0 || parsed.slow === false ? void 0 : { slow: {
468
+ ...parsed.slow,
469
+ show: "Always",
470
+ thresholdOverrides: {
471
+ ...thresholds,
472
+ ...parsed.slowThresholds
473
+ },
474
+ onSlow: (context) => {
475
+ recordSlow(context);
476
+ onSlow?.(context);
477
+ }
478
+ } }
479
+ };
480
+ };
481
+ const dispose = async () => {
482
+ if (canListen) globalThis.removeEventListener("pagehide", tracing.interruptAll, { capture: true });
483
+ tracing.interruptAll();
484
+ await telemetry.dispose();
485
+ };
486
+ return {
487
+ ...telemetry,
488
+ layer,
489
+ instrument,
490
+ dispose
491
+ };
492
+ };
493
+ export { make_exports as MapleFoldkit, make_exports as MxlFoldkit };
494
+
495
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["makeFlushable"],"sources":["../../src/foldkit/args.ts","../../src/foldkit/message.ts","../../src/foldkit/tracer.ts","../../src/foldkit/make.ts"],"sourcesContent":["// Command args become span attributes verbatim in Foldkit's runtime, and apps\n// put message text, invite tokens and API keys in them. Default-deny: only ids\n// and flags survive unless the app allows a key by name.\n\n/** How Command args reach span attributes. */\nexport type CommandArgsPolicy =\n | 'ids'\n | 'all'\n | 'none'\n | {\n /** Arg keys exported as-is, on top of ids and flags. */\n readonly allow?: ReadonlyArray<string> | undefined;\n };\n\nexport const REDACTED = '[redacted]';\nconst MAX_STRING = 256;\nconst MAX_ARRAY = 20;\nconst ID_KEY = /(?:^id|Id|Ids|_id|_ids)$/;\n\ntype Primitive = string | number | boolean;\n\nconst capString = (value: string): string =>\n value.length > MAX_STRING ? `${value.slice(0, MAX_STRING)}…` : value;\n\n/** Primitives and arrays of primitives, size-capped. Anything else is not an attribute. */\nconst toAttributeValue = (\n value: unknown,\n): Primitive | ReadonlyArray<Primitive> | null | undefined => {\n if (value === null) return null;\n if (typeof value === 'string') return capString(value);\n if (typeof value === 'number' || typeof value === 'boolean') return value;\n if (Array.isArray(value)) {\n const items: Array<Primitive> = [];\n for (const item of value.slice(0, MAX_ARRAY)) {\n if (typeof item === 'string') items.push(capString(item));\n else if (typeof item === 'number' || typeof item === 'boolean')\n items.push(item);\n else return undefined;\n }\n return items;\n }\n return undefined;\n};\n\nexport const makeArgsScrubber = (\n policy: CommandArgsPolicy = 'ids',\n): ((\n args: Readonly<Record<string, unknown>> | undefined,\n) => Record<string, unknown>) => {\n if (policy === 'none') return () => ({});\n const allow = new Set(typeof policy === 'object' ? (policy.allow ?? []) : []);\n const keepsAll = policy === 'all';\n return (args) => {\n const out: Record<string, unknown> = {};\n if (args === undefined) return out;\n for (const key of Object.keys(args)) {\n const value = toAttributeValue(args[key]);\n if (value === undefined) continue;\n const kept =\n keepsAll ||\n allow.has(key) ||\n typeof value === 'boolean' ||\n value === null ||\n ID_KEY.test(key);\n out[key] = kept ? value : REDACTED;\n }\n return out;\n };\n};\n","import { Predicate } from 'effect';\n\nconst MAX_DEPTH = 4;\n\n/**\n * `GotChat > SubmittedComposer` for a Submodel Message wrapped by its parent.\n * Only `_tag`s: a Message's fields can hold anything the user typed.\n */\nexport const messageTag = (message: unknown): string => {\n const tags: Array<string> = [];\n let current = message;\n while (\n tags.length < MAX_DEPTH &&\n Predicate.hasProperty(current, '_tag') &&\n typeof current._tag === 'string'\n ) {\n tags.push(current._tag);\n current = Predicate.hasProperty(current, 'message')\n ? current.message\n : undefined;\n }\n return tags.length === 0 ? 'unknown' : tags.join(' > ');\n};\n\n/** The last `size` Message tags, oldest first, for crash reports. */\nexport const makeRecentMessages = (size: number) => {\n const items: Array<string> = [];\n return {\n push: (tag: string): void => {\n items.push(tag);\n if (items.length > size) items.shift();\n },\n read: (): ReadonlyArray<string> => items.slice(),\n };\n};\n","// The tracer the Foldkit runtime's Command spans go through. Foldkit wraps each\n// Command in `Effect.withSpan(command.name, { attributes: command.args })` at\n// the root of the runtime, so on their own Commands are disconnected root\n// traces, one per focus move. This tracer delays creating a Command's real\n// span until something needs its identity, which lets it\n// - parent the Command under the navigation that caused it (the instrumented\n// `update` passes the navigation id in through the args), and\n// - drop a Command that finished without starting a child span: the UI work\n// (focus, scroll, hover delays) that would otherwise flood the traces.\nimport { Context, Exit, Option, Tracer } from 'effect';\n\n/** Internal arg carrying a navigation id from `update` to the tracer. Never exported. */\nexport const NAVIGATION_ARG = 'mxl.foldkit.navigation_id';\n\ntype SpanOptions = Parameters<Tracer.Tracer['span']>[0];\n\nconst MAX_EARLY_PHASES = 50;\n\nexport const nowNanos = (): bigint =>\n BigInt(Math.round((performance.timeOrigin + performance.now()) * 1_000_000));\n\ninterface Navigation {\n readonly id: number;\n readonly kind: 'pageload' | 'navigate';\n readonly route: string;\n readonly path: string;\n readonly startTime: bigint;\n readonly sampled: boolean;\n span: Tracer.Span | undefined;\n pending: number;\n /** Until its first Command starts, the navigation waits on the start grace, not the quiet period. */\n hasCommand: boolean;\n lastEnd: bigint;\n quietTimer: ReturnType<typeof setTimeout> | undefined;\n readonly capTimer: ReturnType<typeof setTimeout>;\n}\n\nexport interface CommandTracingOptions {\n /** Commands that always export, even without child spans. */\n readonly keepCommands: ReadonlySet<string>;\n /** Per-Command sample rates for chatty Commands outside a navigation. */\n readonly sampleRates: Readonly<Record<string, number>>;\n /** Navigations sample once; every Command under one follows its decision. */\n readonly navigationSampleRate: number;\n /** A navigation ends this long after its last Command, unless another starts. */\n readonly navigationQuietMs: number;\n /** How long a new navigation waits for its first Command before it ends with none. */\n readonly navigationStartGraceMs: number;\n readonly navigationMaxMs: number;\n}\n\nexport const makeFoldkitTracing = (options: CommandTracingOptions) => {\n const commandNames = new Set<string>();\n const navigations = new Map<number, Navigation>();\n let nextNavigationId = 1;\n let latestNavigation: Navigation | undefined;\n let inner: Tracer.Tracer | undefined;\n // Phase spans reported before Foldkit builds `resources` (the first Command or Subscription).\n const earlyPhases: Array<(tracer: Tracer.Tracer) => void> = [];\n\n const navigationSpan = (\n navigation: Navigation,\n tracer: Tracer.Tracer,\n ): Tracer.Span => {\n if (navigation.span !== undefined) return navigation.span;\n const span = tracer.span({\n name: `${navigation.kind} ${navigation.route}`,\n parent: Option.none(),\n annotations: Context.empty(),\n links: [],\n startTime: navigation.startTime,\n kind: 'internal',\n root: true,\n sampled: navigation.sampled,\n });\n span.attribute('url.path', navigation.path);\n navigation.span = span;\n return span;\n };\n\n /**\n * The span to parent under: the navigation's own while it is open, or the one it ended with.\n * A navigation that closed before any child needed a span has none to end, so none is made.\n */\n const parentSpanOf = (\n navigation: Navigation | undefined,\n tracer: Tracer.Tracer,\n ): Option.Option<Tracer.AnySpan> => {\n if (navigation === undefined) return Option.none();\n if (navigation.span !== undefined) return Option.some(navigation.span);\n return navigations.has(navigation.id)\n ? Option.some(navigationSpan(navigation, tracer))\n : Option.none();\n };\n\n const closeNavigation = (\n navigation: Navigation,\n endTime: bigint,\n interrupted: boolean,\n ): void => {\n if (!navigations.has(navigation.id)) return;\n navigations.delete(navigation.id);\n clearTimeout(navigation.capTimer);\n if (navigation.quietTimer !== undefined)\n clearTimeout(navigation.quietTimer);\n if (latestNavigation === navigation) latestNavigation = undefined;\n if (navigation.span === undefined) return;\n if (interrupted)\n navigation.span.attribute('mxl.navigation.interrupted', true);\n navigation.span.end(endTime, Exit.void);\n };\n\n const armQuietTimer = (navigation: Navigation, delayMs: number): void => {\n if (navigation.quietTimer !== undefined)\n clearTimeout(navigation.quietTimer);\n navigation.quietTimer = setTimeout(\n () => closeNavigation(navigation, navigation.lastEnd, false),\n delayMs,\n );\n };\n\n const startNavigation = (input: {\n readonly kind: 'pageload' | 'navigate';\n readonly route: string;\n readonly path: string;\n readonly startTime: bigint;\n }): number => {\n const id = nextNavigationId++;\n const navigation: Navigation = {\n ...input,\n id,\n sampled: Math.random() < options.navigationSampleRate,\n span: undefined,\n pending: 0,\n hasCommand: false,\n lastEnd: input.startTime,\n quietTimer: undefined,\n capTimer: setTimeout(\n () => closeNavigation(navigation, nowNanos(), true),\n options.navigationMaxMs,\n ),\n };\n navigations.set(id, navigation);\n latestNavigation = navigation;\n // A navigation that starts no Command ends at its last phase span, not at the cap.\n armQuietTimer(navigation, options.navigationStartGraceMs);\n return id;\n };\n\n const commandStarted = (navigation: Navigation): void => {\n navigation.pending += 1;\n navigation.hasCommand = true;\n if (navigation.quietTimer !== undefined)\n clearTimeout(navigation.quietTimer);\n navigation.quietTimer = undefined;\n };\n\n const commandEnded = (navigation: Navigation, endTime: bigint): void => {\n navigation.pending -= 1;\n if (endTime > navigation.lastEnd) navigation.lastEnd = endTime;\n if (navigation.pending > 0) return;\n // A Command's result Message forks its follow-up Commands right after it ends.\n armQuietTimer(navigation, options.navigationQuietMs);\n };\n\n class CommandSpan implements Tracer.Span {\n readonly _tag = 'Span';\n readonly name: string;\n readonly annotations: Context.Context<never>;\n readonly kind: Tracer.SpanKind;\n private real: Tracer.Span | undefined;\n private navigation: Navigation | undefined;\n private hasChildren = false;\n private readonly buffered: Array<readonly [string, unknown]> = [];\n\n constructor(\n private readonly tracer: Tracer.Tracer,\n private readonly options: SpanOptions,\n ) {\n this.name = options.name;\n this.annotations = options.annotations;\n this.kind = options.kind;\n }\n\n private materialize(): Tracer.Span {\n if (this.real !== undefined) return this.real;\n const navigation = this.navigation;\n const parent =\n navigation === undefined\n ? this.options.parent\n : parentSpanOf(navigation, this.tracer);\n const rate = options.sampleRates[this.name];\n const sampled =\n navigation === undefined\n ? this.options.sampled && (rate === undefined || Math.random() < rate)\n : navigation.sampled;\n const real = this.tracer.span({\n ...this.options,\n parent,\n root: navigation === undefined || Option.isNone(parent),\n sampled,\n });\n for (const [key, value] of this.buffered) real.attribute(key, value);\n this.buffered.length = 0;\n this.real = real;\n return real;\n }\n\n childStarted(): void {\n this.hasChildren = true;\n }\n\n get spanId(): string {\n return this.materialize().spanId;\n }\n get traceId(): string {\n return this.materialize().traceId;\n }\n get sampled(): boolean {\n return this.materialize().sampled;\n }\n get parent(): Option.Option<Tracer.AnySpan> {\n return this.materialize().parent;\n }\n get status(): Tracer.SpanStatus {\n return (\n this.real?.status ?? {\n _tag: 'Started',\n startTime: this.options.startTime,\n }\n );\n }\n get attributes(): ReadonlyMap<string, unknown> {\n return this.real?.attributes ?? new Map(this.buffered);\n }\n get links(): ReadonlyArray<Tracer.SpanLink> {\n return this.real?.links ?? this.options.links;\n }\n\n attribute(key: string, value: unknown): void {\n if (key === NAVIGATION_ARG) {\n const navigation =\n typeof value === 'number' ? navigations.get(value) : undefined;\n if (navigation !== undefined && this.real === undefined) {\n this.navigation = navigation;\n commandStarted(navigation);\n }\n return;\n }\n if (this.real !== undefined) this.real.attribute(key, value);\n else this.buffered.push([key, value]);\n }\n event(\n name: string,\n startTime: bigint,\n attributes?: Record<string, unknown>,\n ): void {\n this.materialize().event(name, startTime, attributes);\n }\n addLinks(links: ReadonlyArray<Tracer.SpanLink>): void {\n this.materialize().addLinks(links);\n }\n end(endTime: bigint, exit: Exit.Exit<unknown, unknown>): void {\n if (this.navigation !== undefined) commandEnded(this.navigation, endTime);\n const isUiOnly =\n this.real === undefined &&\n !this.hasChildren &&\n Exit.isSuccess(exit) &&\n !options.keepCommands.has(this.name);\n if (isUiOnly) return;\n this.materialize().end(endTime, exit);\n }\n }\n\n const wrap = (tracer: Tracer.Tracer): Tracer.Tracer => {\n inner = tracer;\n for (const record of earlyPhases.splice(0)) record(tracer);\n return Tracer.make({\n context: tracer.context,\n span(spanOptions) {\n if (Option.isSome(spanOptions.parent)) {\n const parent = spanOptions.parent.value;\n if (parent instanceof CommandSpan) parent.childStarted();\n return tracer.span(spanOptions);\n }\n if (!commandNames.has(spanOptions.name))\n return tracer.span(spanOptions);\n return new CommandSpan(tracer, spanOptions);\n },\n });\n };\n\n /** A finished phase span (slow `update`/`view`/patch), under the open navigation. */\n const recordPhase = (\n name: string,\n startTime: bigint,\n endTime: bigint,\n attributes: Record<string, unknown>,\n ): void => {\n const navigation = latestNavigation;\n if (navigation !== undefined && endTime > navigation.lastEnd) {\n navigation.lastEnd = endTime;\n if (navigation.hasCommand && navigation.pending === 0)\n armQuietTimer(navigation, options.navigationQuietMs);\n }\n const record = (tracer: Tracer.Tracer): void => {\n const parent = parentSpanOf(navigation, tracer);\n const span = tracer.span({\n name,\n parent,\n annotations: Context.empty(),\n links: [],\n startTime,\n kind: 'internal',\n root: Option.isNone(parent),\n // A navigation sampled out keeps its phases out even once it has closed.\n sampled: navigation?.sampled ?? true,\n });\n for (const key of Object.keys(attributes))\n span.attribute(key, attributes[key]);\n span.end(endTime, Exit.void);\n };\n if (inner !== undefined) {\n record(inner);\n return;\n }\n if (earlyPhases.length < MAX_EARLY_PHASES) earlyPhases.push(record);\n };\n\n /** Ends every open navigation, e.g. when the page goes away. */\n const interruptAll = (): void => {\n for (const navigation of Array.from(navigations.values()))\n closeNavigation(navigation, nowNanos(), true);\n };\n\n return {\n registerCommand: (name: string): void => {\n commandNames.add(name);\n },\n startNavigation,\n wrap,\n recordPhase,\n interruptAll,\n };\n};\n\nexport type FoldkitTracing = ReturnType<typeof makeFoldkitTracing>;\n","import { Effect, Layer, Predicate, Tracer } from 'effect';\nimport type {\n FlushableTelemetry,\n MxlBrowserFlushableConfig,\n} from '../browser/flushable.js';\nimport { make as makeFlushable } from '../browser/flushable.js';\nimport { scrubUrl } from '../shared/url-privacy.js';\nimport type { CommandArgsPolicy } from './args.js';\nimport { makeArgsScrubber } from './args.js';\nimport { makeRecentMessages, messageTag } from './message.js';\nimport type { FoldkitTracing } from './tracer.js';\nimport { makeFoldkitTracing, NAVIGATION_ARG, nowNanos } from './tracer.js';\n\n// Foldkit is typed structurally so the SDK has no dependency on it: these are\n// the parts of `Runtime.makeApplication`'s config the instrumentation reads.\n\ninterface FoldkitCommand {\n readonly name: string;\n readonly args?: Readonly<Record<string, unknown>> | undefined;\n readonly effect: Effect.Effect<unknown, unknown, unknown>;\n}\n\ninterface FoldkitUpdateReturn {\n readonly model: unknown;\n readonly commands?: ReadonlyArray<FoldkitCommand> | undefined;\n}\n\ninterface FoldkitMessage {\n readonly _tag: string;\n}\n\ninterface FoldkitUrl {\n readonly pathname: string;\n}\n\ntype SlowPhase = 'Update' | 'View' | 'Patch' | 'SubscriptionDependencies';\n\ninterface FoldkitSlowContext {\n readonly _tag: SlowPhase;\n readonly durationMs: number;\n readonly thresholdMs: number;\n readonly message?: unknown;\n}\n\ninterface FoldkitCrashContext {\n readonly error: Error;\n readonly message: unknown;\n}\n\ntype SlowThresholds = Readonly<Partial<Record<SlowPhase, number>>>;\n\ntype UpdateFn = (model: never, message: never) => FoldkitUpdateReturn;\ntype InitFn = (flags: never, url?: FoldkitUrl) => FoldkitUpdateReturn;\ntype OnUrlChangeFn = (url: FoldkitUrl) => FoldkitMessage;\ntype CrashReportFn = (context: FoldkitCrashContext) => void;\ntype OnSlowFn = (context: FoldkitSlowContext) => void;\n\n/** The parts of a `Runtime.makeApplication` / `makeElement` config that `instrument` wraps. */\ninterface FoldkitAppConfig {\n readonly resources: Layer.Layer<unknown, unknown, unknown> | undefined;\n readonly init: InitFn | undefined;\n readonly update: UpdateFn | undefined;\n readonly routing: object | undefined;\n readonly onUrlChange: OnUrlChangeFn | undefined;\n readonly crash: object | undefined;\n readonly crashReport: CrashReportFn | undefined;\n readonly slow: false | object | undefined;\n readonly onSlow: OnSlowFn | undefined;\n readonly slowThresholds: SlowThresholds | undefined;\n}\n\n// BOUNDARY: the config carries the app's own Model and Message types, which the\n// SDK cannot name. Only the shape Foldkit guarantees is checked here; the\n// wrappers pass every value through untouched.\nconst isFn = <Fn>(value: unknown): value is Fn => typeof value === 'function';\nconst field = (owner: unknown, key: string): unknown =>\n Predicate.hasProperty(owner, key) ? owner[key] : undefined;\nconst fnField = <Fn>(owner: unknown, key: string): Fn | undefined => {\n const value = field(owner, key);\n return isFn<Fn>(value) ? value : undefined;\n};\nconst objectField = (owner: unknown, key: string): object | undefined => {\n const value = field(owner, key);\n return Predicate.isObject(value) ? value : undefined;\n};\nconst isThresholds = (value: unknown): value is SlowThresholds =>\n Predicate.isObject(value);\n\nconst parseAppConfig = <Config extends object>(\n app: Config,\n): FoldkitAppConfig => {\n const resources = field(app, 'resources');\n const routing = objectField(app, 'routing');\n const crash = objectField(app, 'crash');\n const slow = field(app, 'slow');\n const slowThresholds = field(slow, 'thresholdOverrides');\n return {\n resources: Layer.isLayer(resources) ? resources : undefined,\n init: fnField<InitFn>(app, 'init'),\n update: fnField<UpdateFn>(app, 'update'),\n routing,\n onUrlChange: fnField<OnUrlChangeFn>(routing, 'onUrlChange'),\n crash,\n crashReport: fnField<CrashReportFn>(crash, 'report'),\n slow: slow === false ? false : Predicate.isObject(slow) ? slow : undefined,\n onSlow: fnField<OnSlowFn>(slow, 'onSlow'),\n slowThresholds: isThresholds(slowThresholds) ? slowThresholds : undefined,\n };\n};\n\nexport interface MxlFoldkitConfig extends MxlBrowserFlushableConfig {\n /**\n * Which Command args become span attributes, as `mxl.foldkit.arg.<key>`. Default\n * `\"ids\"`: args whose key ends in `Id`/`Ids`, booleans and nulls export, every\n * other value exports as `[redacted]`. `{ allow: [\"channelId\", \"provider\"] }` adds keys by name.\n */\n readonly commandArgs?: CommandArgsPolicy | undefined;\n /**\n * Commands exported even when they finish without starting a child span.\n * By default such a Command is UI work (focus, scroll, a hover delay) and is\n * dropped, so only Commands that called an API, RPC or traced Effect export.\n * Failed Commands always export.\n */\n readonly keepCommands?: ReadonlyArray<string> | undefined;\n /** Sample rates for chatty Commands outside a navigation, e.g. `{ SendTypingHeartbeat: 0.01 }`. */\n readonly sampleCommands?: Readonly<Record<string, number>> | undefined;\n /** Share of navigations traced. Every Command under a navigation follows its decision. Default 1. */\n readonly navigationSampleRate?: number | undefined;\n /**\n * Span name suffix for a navigation, from the URL path: a route template like\n * `/chat/:channelId` or a route name. Default: the path itself.\n */\n readonly routeName?: ((pathname: string) => string) | undefined;\n /**\n * Report slow `update`, `view` and DOM patch phases as spans. Default\n * thresholds: update 16ms, view 50ms, patch 50ms. `false` turns it off.\n */\n readonly slowPhases?:\n | false\n | Partial<Record<'Update' | 'View' | 'Patch', number>>\n | undefined;\n}\n\nexport interface MxlFoldkit extends FlushableTelemetry {\n /**\n * Wrap a `Runtime.makeApplication` (or `makeElement`) config: merges the\n * telemetry into `resources`, and instruments `init`, `update`, `routing`,\n * `crash` and `slow`. Everything else passes through.\n */\n readonly instrument: <Config extends object>(config: Config) => Config;\n}\n\nexport type MapleFoldkit = MxlFoldkit;\nexport type MapleFoldkitConfig = MxlFoldkitConfig;\n\n/** Command args export as `mxl.foldkit.arg.<key>`. */\nconst ARG_PREFIX = 'mxl.foldkit.arg.';\n\nconst DEFAULT_SLOW = {\n Update: 16,\n View: 50,\n Patch: 50,\n} satisfies SlowThresholds;\n\n/** A Command's result Message remembers the navigation that caused it, so its follow-ups join it. */\nconst makeNavigationMarks = () => {\n const marks = new WeakMap<object, number>();\n return {\n get: (message: unknown): number | undefined =>\n Predicate.isObject(message) ? marks.get(message) : undefined,\n set: (message: unknown, id: number): void => {\n if (Predicate.isObject(message)) marks.set(message, id);\n },\n };\n};\n\nconst isUpdateReturn = (value: unknown): value is FoldkitUpdateReturn =>\n Predicate.hasProperty(value, 'model');\n\n/**\n * MXL telemetry for a Foldkit app: Command spans with navigation parents,\n * scrubbed args, crash reports and slow-phase spans, on top of the buffered\n * browser tracer (`MxlFlush.make`) with session replay.\n *\n * @example\n * ```ts\n * import { MxlFoldkit } from \"@safaricom-mxl/effect-sdk/foldkit\"\n *\n * const telemetry = MxlFoldkit.make({ serviceName: \"acme-web\", endpoint: \"http://127.0.0.1:4318\" })\n *\n * Runtime.run(Runtime.makeApplication(telemetry.instrument({ Model, init, update, view, resources, routing })))\n * ```\n */\nexport const make = (config: MxlFoldkitConfig): MxlFoldkit => {\n const tracing: FoldkitTracing = makeFoldkitTracing({\n keepCommands: new Set(config.keepCommands ?? []),\n sampleRates: config.sampleCommands ?? {},\n navigationSampleRate: config.navigationSampleRate ?? 1,\n navigationQuietMs: 150,\n navigationStartGraceMs: 1_000,\n navigationMaxMs: 30_000,\n });\n // Registered before `makeFlushable` adds its own `pagehide` flush, and in the capture phase:\n // either way it runs first, so the open navigation ends while that flush can still send it.\n const canListen = typeof globalThis.addEventListener === 'function';\n if (canListen)\n globalThis.addEventListener('pagehide', tracing.interruptAll, {\n capture: true,\n });\n const telemetry = makeFlushable(config);\n const scrubArgs = makeArgsScrubber(config.commandArgs);\n const recent = makeRecentMessages(20);\n const marks = makeNavigationMarks();\n const routeOf = (url: FoldkitUrl): string =>\n scrubUrl(config.routeName?.(url.pathname) ?? url.pathname);\n\n const layer = Layer.effect(\n Tracer.Tracer,\n Effect.map(Effect.tracer, tracing.wrap),\n ).pipe(Layer.provideMerge(telemetry.layer));\n\n const instrumentCommand = (\n command: FoldkitCommand,\n tag: string,\n navigationId: number | undefined,\n ): FoldkitCommand => {\n tracing.registerCommand(command.name);\n // Namespaced, so an app's `orgId` or `limit` arg never collides with a semconv or backend key.\n const args = {\n ...Object.fromEntries(\n Object.entries(scrubArgs(command.args)).map(([key, value]) => [\n `${ARG_PREFIX}${key}`,\n value,\n ]),\n ),\n 'mxl.foldkit.message': tag,\n };\n if (navigationId === undefined) return { ...command, args };\n return {\n ...command,\n args: { ...args, [NAVIGATION_ARG]: navigationId },\n effect: Effect.tap(command.effect, (message) =>\n Effect.sync(() => marks.set(message, navigationId)),\n ),\n };\n };\n\n const withCommands = <Return>(\n result: Return,\n tag: string,\n navigationId: number | undefined,\n ): Return => {\n if (\n !isUpdateReturn(result) ||\n result.commands === undefined ||\n result.commands.length === 0\n )\n return result;\n return {\n ...result,\n commands: result.commands.map((command) =>\n instrumentCommand(command, tag, navigationId),\n ),\n };\n };\n\n const instrumentUpdate =\n <Model, Message, Return>(\n update: (model: Model, message: Message) => Return,\n ) =>\n (model: Model, message: Message): Return => {\n const tag = messageTag(message);\n recent.push(tag);\n return withCommands(update(model, message), tag, marks.get(message));\n };\n\n const instrumentInit =\n <Flags, Return>(init: (flags: Flags, url?: FoldkitUrl) => Return) =>\n (flags: Flags, url?: FoldkitUrl): Return => {\n const navigationId =\n url === undefined\n ? undefined\n : tracing.startNavigation({\n kind: 'pageload',\n route: routeOf(url),\n path: scrubUrl(url.pathname),\n // The page load began at navigation start, not when the app's JS got here.\n startTime: BigInt(Math.round(performance.timeOrigin * 1_000_000)),\n });\n return withCommands(init(flags, url), 'init', navigationId);\n };\n\n const instrumentOnUrlChange =\n <Message>(onUrlChange: (url: FoldkitUrl) => Message) =>\n (url: FoldkitUrl): Message => {\n const message = onUrlChange(url);\n const navigationId = tracing.startNavigation({\n kind: 'navigate',\n route: routeOf(url),\n path: scrubUrl(url.pathname),\n startTime: nowNanos(),\n });\n marks.set(message, navigationId);\n return message;\n };\n\n const reportCrash = (context: FoldkitCrashContext): void => {\n telemetry.captureException(context.error, {\n name: 'foldkit.crash',\n attributes: {\n 'mxl.exception.source': 'foldkit.crash',\n 'mxl.foldkit.message': Predicate.hasProperty(context.message, 'value')\n ? messageTag(context.message.value)\n : 'init',\n 'mxl.foldkit.recent_messages': recent.read(),\n },\n });\n // The runtime has stopped; nothing else will trigger a flush.\n void telemetry.flush();\n };\n\n const thresholds =\n config.slowPhases === false\n ? undefined\n : { ...DEFAULT_SLOW, ...config.slowPhases };\n\n const recordSlow = (context: FoldkitSlowContext): void => {\n if (context._tag === 'SubscriptionDependencies') return;\n const endTime = nowNanos();\n const startTime =\n endTime - BigInt(Math.round(context.durationMs * 1_000_000));\n const message =\n context._tag === 'Update'\n ? context.message\n : Predicate.hasProperty(context.message, 'value')\n ? context.message.value\n : undefined;\n tracing.recordPhase(\n `foldkit.${context._tag.toLowerCase()}`,\n startTime,\n endTime,\n {\n 'mxl.foldkit.phase': context._tag,\n 'mxl.foldkit.duration_ms': context.durationMs,\n 'mxl.foldkit.threshold_ms': context.thresholdMs,\n ...(message === undefined\n ? undefined\n : { 'mxl.foldkit.message': messageTag(message) }),\n },\n );\n };\n\n const instrument = <Config extends object>(app: Config): Config => {\n const parsed = parseAppConfig(app);\n const { init, update, onUrlChange, crashReport, onSlow } = parsed;\n return {\n ...app,\n resources:\n parsed.resources === undefined\n ? layer\n : Layer.merge(parsed.resources, layer),\n crash: {\n ...parsed.crash,\n report: (context: FoldkitCrashContext) => {\n reportCrash(context);\n crashReport?.(context);\n },\n },\n ...(update === undefined\n ? undefined\n : { update: instrumentUpdate(update) }),\n ...(init === undefined ? undefined : { init: instrumentInit(init) }),\n ...(onUrlChange === undefined\n ? undefined\n : {\n routing: {\n ...parsed.routing,\n onUrlChange: instrumentOnUrlChange(onUrlChange),\n },\n }),\n ...(thresholds === undefined || parsed.slow === false\n ? undefined\n : {\n slow: {\n ...parsed.slow,\n show: 'Always',\n thresholdOverrides: { ...thresholds, ...parsed.slowThresholds },\n onSlow: (context: FoldkitSlowContext) => {\n recordSlow(context);\n onSlow?.(context);\n },\n },\n }),\n };\n };\n\n const dispose = async (): Promise<void> => {\n if (canListen)\n globalThis.removeEventListener('pagehide', tracing.interruptAll, {\n capture: true,\n });\n // Open navigations go out with the final flush rather than ending after it.\n tracing.interruptAll();\n await telemetry.dispose();\n };\n\n return { ...telemetry, layer, instrument, dispose };\n};\n"],"mappings":";;;;AAcA,MAAa,WAAW;AACxB,MAAM,aAAa;AACnB,MAAM,YAAY;AAClB,MAAM,SAAS;AAIf,MAAM,aAAa,UACjB,MAAM,SAAS,aAAa,GAAG,MAAM,MAAM,GAAG,UAAU,EAAE,KAAK;;AAGjE,MAAM,oBACJ,UAC4D;CAC5D,IAAI,UAAU,MAAM,OAAO;CAC3B,IAAI,OAAO,UAAU,UAAU,OAAO,UAAU,KAAK;CACrD,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,WAAW,OAAO;CACpE,IAAI,MAAM,QAAQ,KAAK,GAAG;EACxB,MAAM,QAA0B,CAAC;EACjC,KAAK,MAAM,QAAQ,MAAM,MAAM,GAAG,SAAS,GACzC,IAAI,OAAO,SAAS,UAAU,MAAM,KAAK,UAAU,IAAI,CAAC;OACnD,IAAI,OAAO,SAAS,YAAY,OAAO,SAAS,WACnD,MAAM,KAAK,IAAI;OACZ,OAAO,KAAA;EAEd,OAAO;CACT;AAEF;AAEA,MAAa,oBACX,SAA4B,UAGG;CAC/B,IAAI,WAAW,QAAQ,cAAc,CAAC;CACtC,MAAM,QAAQ,IAAI,IAAI,OAAO,WAAW,WAAY,OAAO,SAAS,CAAC,IAAK,CAAC,CAAC;CAC5E,MAAM,WAAW,WAAW;CAC5B,QAAQ,SAAS;EACf,MAAM,MAA+B,CAAC;EACtC,IAAI,SAAS,KAAA,GAAW,OAAO;EAC/B,KAAK,MAAM,OAAO,OAAO,KAAK,IAAI,GAAG;GACnC,MAAM,QAAQ,iBAAiB,KAAK,IAAI;GACxC,IAAI,UAAU,KAAA,GAAW;GAOzB,IAAI,OALF,YACA,MAAM,IAAI,GAAG,KACb,OAAO,UAAU,aACjB,UAAU,QACV,OAAO,KAAK,GAAG,IACC,QAAQ;EAC5B;EACA,OAAO;CACT;AACF;;AClEA,MAAM,YAAY;;;;;AAMlB,MAAa,cAAc,YAA6B;CACtD,MAAM,OAAsB,CAAC;CAC7B,IAAI,UAAU;CACd,OACE,KAAK,SAAS,aACd,UAAU,YAAY,SAAS,MAAM,KACrC,OAAO,QAAQ,SAAS,UACxB;EACA,KAAK,KAAK,QAAQ,IAAI;EACtB,UAAU,UAAU,YAAY,SAAS,SAAS,IAC9C,QAAQ,UACR,KAAA;CACN;CACA,OAAO,KAAK,WAAW,IAAI,YAAY,KAAK,KAAK,KAAK;AACxD;;AAGA,MAAa,sBAAsB,SAAiB;CAClD,MAAM,QAAuB,CAAC;CAC9B,OAAO;EACL,OAAO,QAAsB;GAC3B,MAAM,KAAK,GAAG;GACd,IAAI,MAAM,SAAS,MAAM,MAAM,MAAM;EACvC;EACA,YAAmC,MAAM,MAAM;CACjD;AACF;;;ACtBA,MAAa,iBAAiB;AAI9B,MAAM,mBAAmB;AAEzB,MAAa,iBACX,OAAO,KAAK,OAAO,YAAY,aAAa,YAAY,IAAI,KAAK,GAAS,CAAC;AAgC7E,MAAa,sBAAsB,YAAmC;CACpE,MAAM,+BAAe,IAAI,IAAY;CACrC,MAAM,8BAAc,IAAI,IAAwB;CAChD,IAAI,mBAAmB;CACvB,IAAI;CACJ,IAAI;CAEJ,MAAM,cAAsD,CAAC;CAE7D,MAAM,kBACJ,YACA,WACgB;EAChB,IAAI,WAAW,SAAS,KAAA,GAAW,OAAO,WAAW;EACrD,MAAM,OAAO,OAAO,KAAK;GACvB,MAAM,GAAG,WAAW,KAAK,GAAG,WAAW;GACvC,QAAQ,OAAO,KAAK;GACpB,aAAa,QAAQ,MAAM;GAC3B,OAAO,CAAC;GACR,WAAW,WAAW;GACtB,MAAM;GACN,MAAM;GACN,SAAS,WAAW;EACtB,CAAC;EACD,KAAK,UAAU,YAAY,WAAW,IAAI;EAC1C,WAAW,OAAO;EAClB,OAAO;CACT;;;;;CAMA,MAAM,gBACJ,YACA,WACkC;EAClC,IAAI,eAAe,KAAA,GAAW,OAAO,OAAO,KAAK;EACjD,IAAI,WAAW,SAAS,KAAA,GAAW,OAAO,OAAO,KAAK,WAAW,IAAI;EACrE,OAAO,YAAY,IAAI,WAAW,EAAE,IAChC,OAAO,KAAK,eAAe,YAAY,MAAM,CAAC,IAC9C,OAAO,KAAK;CAClB;CAEA,MAAM,mBACJ,YACA,SACA,gBACS;EACT,IAAI,CAAC,YAAY,IAAI,WAAW,EAAE,GAAG;EACrC,YAAY,OAAO,WAAW,EAAE;EAChC,aAAa,WAAW,QAAQ;EAChC,IAAI,WAAW,eAAe,KAAA,GAC5B,aAAa,WAAW,UAAU;EACpC,IAAI,qBAAqB,YAAY,mBAAmB,KAAA;EACxD,IAAI,WAAW,SAAS,KAAA,GAAW;EACnC,IAAI,aACF,WAAW,KAAK,UAAU,8BAA8B,IAAI;EAC9D,WAAW,KAAK,IAAI,SAAS,KAAK,IAAI;CACxC;CAEA,MAAM,iBAAiB,YAAwB,YAA0B;EACvE,IAAI,WAAW,eAAe,KAAA,GAC5B,aAAa,WAAW,UAAU;EACpC,WAAW,aAAa,iBAChB,gBAAgB,YAAY,WAAW,SAAS,KAAK,GAC3D,OACF;CACF;CAEA,MAAM,mBAAmB,UAKX;EACZ,MAAM,KAAK;EACX,MAAM,aAAyB;GAC7B,GAAG;GACH;GACA,SAAS,KAAK,OAAO,IAAI,QAAQ;GACjC,MAAM,KAAA;GACN,SAAS;GACT,YAAY;GACZ,SAAS,MAAM;GACf,YAAY,KAAA;GACZ,UAAU,iBACF,gBAAgB,YAAY,SAAS,GAAG,IAAI,GAClD,QAAQ,eACV;EACF;EACA,YAAY,IAAI,IAAI,UAAU;EAC9B,mBAAmB;EAEnB,cAAc,YAAY,QAAQ,sBAAsB;EACxD,OAAO;CACT;CAEA,MAAM,kBAAkB,eAAiC;EACvD,WAAW,WAAW;EACtB,WAAW,aAAa;EACxB,IAAI,WAAW,eAAe,KAAA,GAC5B,aAAa,WAAW,UAAU;EACpC,WAAW,aAAa,KAAA;CAC1B;CAEA,MAAM,gBAAgB,YAAwB,YAA0B;EACtE,WAAW,WAAW;EACtB,IAAI,UAAU,WAAW,SAAS,WAAW,UAAU;EACvD,IAAI,WAAW,UAAU,GAAG;EAE5B,cAAc,YAAY,QAAQ,iBAAiB;CACrD;CAEA,MAAM,YAAmC;EAWpB;EACA;EAXnB,OAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,cAAsB;EACtB,WAA+D,CAAC;EAEhE,YACE,QACA,SACA;GAFiB,KAAA,SAAA;GACA,KAAA,UAAA;GAEjB,KAAK,OAAO,QAAQ;GACpB,KAAK,cAAc,QAAQ;GAC3B,KAAK,OAAO,QAAQ;EACtB;EAEA,cAAmC;GACjC,IAAI,KAAK,SAAS,KAAA,GAAW,OAAO,KAAK;GACzC,MAAM,aAAa,KAAK;GACxB,MAAM,SACJ,eAAe,KAAA,IACX,KAAK,QAAQ,SACb,aAAa,YAAY,KAAK,MAAM;GAC1C,MAAM,OAAO,QAAQ,YAAY,KAAK;GACtC,MAAM,UACJ,eAAe,KAAA,IACX,KAAK,QAAQ,YAAY,SAAS,KAAA,KAAa,KAAK,OAAO,IAAI,QAC/D,WAAW;GACjB,MAAM,OAAO,KAAK,OAAO,KAAK;IAC5B,GAAG,KAAK;IACR;IACA,MAAM,eAAe,KAAA,KAAa,OAAO,OAAO,MAAM;IACtD;GACF,CAAC;GACD,KAAK,MAAM,CAAC,KAAK,UAAU,KAAK,UAAU,KAAK,UAAU,KAAK,KAAK;GACnE,KAAK,SAAS,SAAS;GACvB,KAAK,OAAO;GACZ,OAAO;EACT;EAEA,eAAqB;GACnB,KAAK,cAAc;EACrB;EAEA,IAAI,SAAiB;GACnB,OAAO,KAAK,YAAY,CAAC,CAAC;EAC5B;EACA,IAAI,UAAkB;GACpB,OAAO,KAAK,YAAY,CAAC,CAAC;EAC5B;EACA,IAAI,UAAmB;GACrB,OAAO,KAAK,YAAY,CAAC,CAAC;EAC5B;EACA,IAAI,SAAwC;GAC1C,OAAO,KAAK,YAAY,CAAC,CAAC;EAC5B;EACA,IAAI,SAA4B;GAC9B,OACE,KAAK,MAAM,UAAU;IACnB,MAAM;IACN,WAAW,KAAK,QAAQ;GAC1B;EAEJ;EACA,IAAI,aAA2C;GAC7C,OAAO,KAAK,MAAM,cAAc,IAAI,IAAI,KAAK,QAAQ;EACvD;EACA,IAAI,QAAwC;GAC1C,OAAO,KAAK,MAAM,SAAS,KAAK,QAAQ;EAC1C;EAEA,UAAU,KAAa,OAAsB;GAC3C,IAAI,QAAA,6BAAwB;IAC1B,MAAM,aACJ,OAAO,UAAU,WAAW,YAAY,IAAI,KAAK,IAAI,KAAA;IACvD,IAAI,eAAe,KAAA,KAAa,KAAK,SAAS,KAAA,GAAW;KACvD,KAAK,aAAa;KAClB,eAAe,UAAU;IAC3B;IACA;GACF;GACA,IAAI,KAAK,SAAS,KAAA,GAAW,KAAK,KAAK,UAAU,KAAK,KAAK;QACtD,KAAK,SAAS,KAAK,CAAC,KAAK,KAAK,CAAC;EACtC;EACA,MACE,MACA,WACA,YACM;GACN,KAAK,YAAY,CAAC,CAAC,MAAM,MAAM,WAAW,UAAU;EACtD;EACA,SAAS,OAA6C;GACpD,KAAK,YAAY,CAAC,CAAC,SAAS,KAAK;EACnC;EACA,IAAI,SAAiB,MAAyC;GAC5D,IAAI,KAAK,eAAe,KAAA,GAAW,aAAa,KAAK,YAAY,OAAO;GAMxE,IAJE,KAAK,SAAS,KAAA,KACd,CAAC,KAAK,eACN,KAAK,UAAU,IAAI,KACnB,CAAC,QAAQ,aAAa,IAAI,KAAK,IAAI,GACvB;GACd,KAAK,YAAY,CAAC,CAAC,IAAI,SAAS,IAAI;EACtC;CACF;CAEA,MAAM,QAAQ,WAAyC;EACrD,QAAQ;EACR,KAAK,MAAM,UAAU,YAAY,OAAO,CAAC,GAAG,OAAO,MAAM;EACzD,OAAO,OAAO,KAAK;GACjB,SAAS,OAAO;GAChB,KAAK,aAAa;IAChB,IAAI,OAAO,OAAO,YAAY,MAAM,GAAG;KACrC,MAAM,SAAS,YAAY,OAAO;KAClC,IAAI,kBAAkB,aAAa,OAAO,aAAa;KACvD,OAAO,OAAO,KAAK,WAAW;IAChC;IACA,IAAI,CAAC,aAAa,IAAI,YAAY,IAAI,GACpC,OAAO,OAAO,KAAK,WAAW;IAChC,OAAO,IAAI,YAAY,QAAQ,WAAW;GAC5C;EACF,CAAC;CACH;;CAGA,MAAM,eACJ,MACA,WACA,SACA,eACS;EACT,MAAM,aAAa;EACnB,IAAI,eAAe,KAAA,KAAa,UAAU,WAAW,SAAS;GAC5D,WAAW,UAAU;GACrB,IAAI,WAAW,cAAc,WAAW,YAAY,GAClD,cAAc,YAAY,QAAQ,iBAAiB;EACvD;EACA,MAAM,UAAU,WAAgC;GAC9C,MAAM,SAAS,aAAa,YAAY,MAAM;GAC9C,MAAM,OAAO,OAAO,KAAK;IACvB;IACA;IACA,aAAa,QAAQ,MAAM;IAC3B,OAAO,CAAC;IACR;IACA,MAAM;IACN,MAAM,OAAO,OAAO,MAAM;IAE1B,SAAS,YAAY,WAAW;GAClC,CAAC;GACD,KAAK,MAAM,OAAO,OAAO,KAAK,UAAU,GACtC,KAAK,UAAU,KAAK,WAAW,IAAI;GACrC,KAAK,IAAI,SAAS,KAAK,IAAI;EAC7B;EACA,IAAI,UAAU,KAAA,GAAW;GACvB,OAAO,KAAK;GACZ;EACF;EACA,IAAI,YAAY,SAAS,kBAAkB,YAAY,KAAK,MAAM;CACpE;;CAGA,MAAM,qBAA2B;EAC/B,KAAK,MAAM,cAAc,MAAM,KAAK,YAAY,OAAO,CAAC,GACtD,gBAAgB,YAAY,SAAS,GAAG,IAAI;CAChD;CAEA,OAAO;EACL,kBAAkB,SAAuB;GACvC,aAAa,IAAI,IAAI;EACvB;EACA;EACA;EACA;EACA;CACF;AACF;;;AC9QA,MAAM,QAAY,UAAgC,OAAO,UAAU;AACnE,MAAM,SAAS,OAAgB,QAC7B,UAAU,YAAY,OAAO,GAAG,IAAI,MAAM,OAAO,KAAA;AACnD,MAAM,WAAe,OAAgB,QAAgC;CACnE,MAAM,QAAQ,MAAM,OAAO,GAAG;CAC9B,OAAO,KAAS,KAAK,IAAI,QAAQ,KAAA;AACnC;AACA,MAAM,eAAe,OAAgB,QAAoC;CACvE,MAAM,QAAQ,MAAM,OAAO,GAAG;CAC9B,OAAO,UAAU,SAAS,KAAK,IAAI,QAAQ,KAAA;AAC7C;AACA,MAAM,gBAAgB,UACpB,UAAU,SAAS,KAAK;AAE1B,MAAM,kBACJ,QACqB;CACrB,MAAM,YAAY,MAAM,KAAK,WAAW;CACxC,MAAM,UAAU,YAAY,KAAK,SAAS;CAC1C,MAAM,QAAQ,YAAY,KAAK,OAAO;CACtC,MAAM,OAAO,MAAM,KAAK,MAAM;CAC9B,MAAM,iBAAiB,MAAM,MAAM,oBAAoB;CACvD,OAAO;EACL,WAAW,MAAM,QAAQ,SAAS,IAAI,YAAY,KAAA;EAClD,MAAM,QAAgB,KAAK,MAAM;EACjC,QAAQ,QAAkB,KAAK,QAAQ;EACvC;EACA,aAAa,QAAuB,SAAS,aAAa;EAC1D;EACA,aAAa,QAAuB,OAAO,QAAQ;EACnD,MAAM,SAAS,QAAQ,QAAQ,UAAU,SAAS,IAAI,IAAI,OAAO,KAAA;EACjE,QAAQ,QAAkB,MAAM,QAAQ;EACxC,gBAAgB,aAAa,cAAc,IAAI,iBAAiB,KAAA;CAClE;AACF;;AAgDA,MAAM,aAAa;AAEnB,MAAM,eAAe;CACnB,QAAQ;CACR,MAAM;CACN,OAAO;AACT;;AAGA,MAAM,4BAA4B;CAChC,MAAM,wBAAQ,IAAI,QAAwB;CAC1C,OAAO;EACL,MAAM,YACJ,UAAU,SAAS,OAAO,IAAI,MAAM,IAAI,OAAO,IAAI,KAAA;EACrD,MAAM,SAAkB,OAAqB;GAC3C,IAAI,UAAU,SAAS,OAAO,GAAG,MAAM,IAAI,SAAS,EAAE;EACxD;CACF;AACF;AAEA,MAAM,kBAAkB,UACtB,UAAU,YAAY,OAAO,OAAO;;;;;;;;;;;;;;;AAgBtC,MAAa,QAAQ,WAAyC;CAC5D,MAAM,UAA0B,mBAAmB;EACjD,cAAc,IAAI,IAAI,OAAO,gBAAgB,CAAC,CAAC;EAC/C,aAAa,OAAO,kBAAkB,CAAC;EACvC,sBAAsB,OAAO,wBAAwB;EACrD,mBAAmB;EACnB,wBAAwB;EACxB,iBAAiB;CACnB,CAAC;CAGD,MAAM,YAAY,OAAO,WAAW,qBAAqB;CACzD,IAAI,WACF,WAAW,iBAAiB,YAAY,QAAQ,cAAc,EAC5D,SAAS,KACX,CAAC;CACH,MAAM,YAAYA,OAAc,MAAM;CACtC,MAAM,YAAY,iBAAiB,OAAO,WAAW;CACrD,MAAM,SAAS,mBAAmB,EAAE;CACpC,MAAM,QAAQ,oBAAoB;CAClC,MAAM,WAAW,QACf,SAAS,OAAO,YAAY,IAAI,QAAQ,KAAK,IAAI,QAAQ;CAE3D,MAAM,QAAQ,MAAM,OAClB,OAAO,QACP,OAAO,IAAI,OAAO,QAAQ,QAAQ,IAAI,CACxC,CAAC,CAAC,KAAK,MAAM,aAAa,UAAU,KAAK,CAAC;CAE1C,MAAM,qBACJ,SACA,KACA,iBACmB;EACnB,QAAQ,gBAAgB,QAAQ,IAAI;EAEpC,MAAM,OAAO;GACX,GAAG,OAAO,YACR,OAAO,QAAQ,UAAU,QAAQ,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,WAAW,CAC5D,GAAG,aAAa,OAChB,KACF,CAAC,CACH;GACA,uBAAuB;EACzB;EACA,IAAI,iBAAiB,KAAA,GAAW,OAAO;GAAE,GAAG;GAAS;EAAK;EAC1D,OAAO;GACL,GAAG;GACH,MAAM;IAAE,GAAG;KAAO,iBAAiB;GAAa;GAChD,QAAQ,OAAO,IAAI,QAAQ,SAAS,YAClC,OAAO,WAAW,MAAM,IAAI,SAAS,YAAY,CAAC,CACpD;EACF;CACF;CAEA,MAAM,gBACJ,QACA,KACA,iBACW;EACX,IACE,CAAC,eAAe,MAAM,KACtB,OAAO,aAAa,KAAA,KACpB,OAAO,SAAS,WAAW,GAE3B,OAAO;EACT,OAAO;GACL,GAAG;GACH,UAAU,OAAO,SAAS,KAAK,YAC7B,kBAAkB,SAAS,KAAK,YAAY,CAC9C;EACF;CACF;CAEA,MAAM,oBAEF,YAED,OAAc,YAA6B;EAC1C,MAAM,MAAM,WAAW,OAAO;EAC9B,OAAO,KAAK,GAAG;EACf,OAAO,aAAa,OAAO,OAAO,OAAO,GAAG,KAAK,MAAM,IAAI,OAAO,CAAC;CACrE;CAEF,MAAM,kBACY,UACf,OAAc,QAA6B;EAC1C,MAAM,eACJ,QAAQ,KAAA,IACJ,KAAA,IACA,QAAQ,gBAAgB;GACtB,MAAM;GACN,OAAO,QAAQ,GAAG;GAClB,MAAM,SAAS,IAAI,QAAQ;GAE3B,WAAW,OAAO,KAAK,MAAM,YAAY,aAAa,GAAS,CAAC;EAClE,CAAC;EACP,OAAO,aAAa,KAAK,OAAO,GAAG,GAAG,QAAQ,YAAY;CAC5D;CAEF,MAAM,yBACM,iBACT,QAA6B;EAC5B,MAAM,UAAU,YAAY,GAAG;EAC/B,MAAM,eAAe,QAAQ,gBAAgB;GAC3C,MAAM;GACN,OAAO,QAAQ,GAAG;GAClB,MAAM,SAAS,IAAI,QAAQ;GAC3B,WAAW,SAAS;EACtB,CAAC;EACD,MAAM,IAAI,SAAS,YAAY;EAC/B,OAAO;CACT;CAEF,MAAM,eAAe,YAAuC;EAC1D,UAAU,iBAAiB,QAAQ,OAAO;GACxC,MAAM;GACN,YAAY;IACV,wBAAwB;IACxB,uBAAuB,UAAU,YAAY,QAAQ,SAAS,OAAO,IACjE,WAAW,QAAQ,QAAQ,KAAK,IAChC;IACJ,+BAA+B,OAAO,KAAK;GAC7C;EACF,CAAC;EAED,UAAe,MAAM;CACvB;CAEA,MAAM,aACJ,OAAO,eAAe,QAClB,KAAA,IACA;EAAE,GAAG;EAAc,GAAG,OAAO;CAAW;CAE9C,MAAM,cAAc,YAAsC;EACxD,IAAI,QAAQ,SAAS,4BAA4B;EACjD,MAAM,UAAU,SAAS;EACzB,MAAM,YACJ,UAAU,OAAO,KAAK,MAAM,QAAQ,aAAa,GAAS,CAAC;EAC7D,MAAM,UACJ,QAAQ,SAAS,WACb,QAAQ,UACR,UAAU,YAAY,QAAQ,SAAS,OAAO,IAC5C,QAAQ,QAAQ,QAChB,KAAA;EACR,QAAQ,YACN,WAAW,QAAQ,KAAK,YAAY,KACpC,WACA,SACA;GACE,qBAAqB,QAAQ;GAC7B,2BAA2B,QAAQ;GACnC,4BAA4B,QAAQ;GACpC,GAAI,YAAY,KAAA,IACZ,KAAA,IACA,EAAE,uBAAuB,WAAW,OAAO,EAAE;EACnD,CACF;CACF;CAEA,MAAM,cAAqC,QAAwB;EACjE,MAAM,SAAS,eAAe,GAAG;EACjC,MAAM,EAAE,MAAM,QAAQ,aAAa,aAAa,WAAW;EAC3D,OAAO;GACL,GAAG;GACH,WACE,OAAO,cAAc,KAAA,IACjB,QACA,MAAM,MAAM,OAAO,WAAW,KAAK;GACzC,OAAO;IACL,GAAG,OAAO;IACV,SAAS,YAAiC;KACxC,YAAY,OAAO;KACnB,cAAc,OAAO;IACvB;GACF;GACA,GAAI,WAAW,KAAA,IACX,KAAA,IACA,EAAE,QAAQ,iBAAiB,MAAM,EAAE;GACvC,GAAI,SAAS,KAAA,IAAY,KAAA,IAAY,EAAE,MAAM,eAAe,IAAI,EAAE;GAClE,GAAI,gBAAgB,KAAA,IAChB,KAAA,IACA,EACE,SAAS;IACP,GAAG,OAAO;IACV,aAAa,sBAAsB,WAAW;GAChD,EACF;GACJ,GAAI,eAAe,KAAA,KAAa,OAAO,SAAS,QAC5C,KAAA,IACA,EACE,MAAM;IACJ,GAAG,OAAO;IACV,MAAM;IACN,oBAAoB;KAAE,GAAG;KAAY,GAAG,OAAO;IAAe;IAC9D,SAAS,YAAgC;KACvC,WAAW,OAAO;KAClB,SAAS,OAAO;IAClB;GACF,EACF;EACN;CACF;CAEA,MAAM,UAAU,YAA2B;EACzC,IAAI,WACF,WAAW,oBAAoB,YAAY,QAAQ,cAAc,EAC/D,SAAS,KACX,CAAC;EAEH,QAAQ,aAAa;EACrB,MAAM,UAAU,QAAQ;CAC1B;CAEA,OAAO;EAAE,GAAG;EAAW;EAAO;EAAY;CAAQ;AACpD"}