@dxos/observability 0.3.11-main.2f1331c → 0.3.11-main.463e5cd
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -4
- package/dist/lib/browser/{chunk-2J5YS65Q.mjs → chunk-6ZTMPKEG.mjs} +3 -3
- package/dist/lib/browser/{chunk-2J5YS65Q.mjs.map → chunk-6ZTMPKEG.mjs.map} +3 -3
- package/dist/lib/browser/chunk-HPGC4HC7.mjs +83 -0
- package/dist/lib/browser/chunk-HPGC4HC7.mjs.map +7 -0
- package/dist/lib/browser/chunk-OCQVYNE3.mjs +842 -0
- package/dist/lib/browser/chunk-OCQVYNE3.mjs.map +7 -0
- package/dist/lib/browser/datadog/index.mjs +1 -1
- package/dist/lib/browser/index.mjs +32 -553
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/observability-QNG5QON4.mjs +10 -0
- package/dist/lib/browser/observability-QNG5QON4.mjs.map +7 -0
- package/dist/lib/browser/segment/index.mjs +8 -0
- package/dist/lib/browser/segment/index.mjs.map +7 -0
- package/dist/lib/node/chunk-BHF4PKAP.cjs +170 -0
- package/dist/lib/node/chunk-BHF4PKAP.cjs.map +7 -0
- package/dist/lib/node/chunk-CBMRJXSL.cjs +861 -0
- package/dist/lib/node/chunk-CBMRJXSL.cjs.map +7 -0
- package/dist/lib/node/{chunk-VDBBIYH6.cjs → chunk-ZJRKFREX.cjs} +6 -6
- package/dist/lib/node/{chunk-VDBBIYH6.cjs.map → chunk-ZJRKFREX.cjs.map} +3 -3
- package/dist/lib/node/datadog/index.cjs +3 -3
- package/dist/lib/node/datadog/index.cjs.map +1 -1
- package/dist/lib/node/index.cjs +31 -603
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/observability-JMKBRR4L.cjs +32 -0
- package/dist/lib/node/observability-JMKBRR4L.cjs.map +7 -0
- package/dist/lib/node/segment/index.cjs +30 -0
- package/dist/lib/node/segment/index.cjs.map +7 -0
- package/dist/types/src/datadog/metrics.d.ts +3 -1
- package/dist/types/src/datadog/metrics.d.ts.map +1 -1
- package/dist/types/src/datadog/metrics.js +1 -1
- package/dist/types/src/datadog/metrics.js.map +1 -1
- package/dist/types/src/helpers/browser-observability.d.ts +17 -0
- package/dist/types/src/helpers/browser-observability.d.ts.map +1 -0
- package/dist/types/src/helpers/browser-observability.js +166 -0
- package/dist/types/src/helpers/browser-observability.js.map +1 -0
- package/dist/types/src/helpers/common.d.ts +10 -0
- package/dist/types/src/helpers/common.d.ts.map +1 -0
- package/dist/types/src/helpers/common.js +23 -0
- package/dist/types/src/helpers/common.js.map +1 -0
- package/dist/types/src/helpers/index.d.ts +6 -0
- package/dist/types/src/helpers/index.d.ts.map +1 -0
- package/dist/types/src/helpers/index.js +25 -0
- package/dist/types/src/helpers/index.js.map +1 -0
- package/dist/types/src/{util.d.ts → helpers/map-spaces.d.ts} +2 -3
- package/dist/types/src/helpers/map-spaces.d.ts.map +1 -0
- package/dist/types/src/{util.js → helpers/map-spaces.js} +1 -2
- package/dist/types/src/helpers/map-spaces.js.map +1 -0
- package/dist/types/src/helpers/node-observability.d.ts +34 -0
- package/dist/types/src/helpers/node-observability.d.ts.map +1 -0
- package/dist/types/src/helpers/node-observability.js +106 -0
- package/dist/types/src/helpers/node-observability.js.map +1 -0
- package/dist/types/src/helpers/setup-telemetry-listeners.d.ts +4 -0
- package/dist/types/src/helpers/setup-telemetry-listeners.d.ts.map +1 -0
- package/dist/types/src/helpers/setup-telemetry-listeners.js +91 -0
- package/dist/types/src/helpers/setup-telemetry-listeners.js.map +1 -0
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/index.js +1 -0
- package/dist/types/src/index.js.map +1 -1
- package/dist/types/src/observability.d.ts +8 -8
- package/dist/types/src/observability.d.ts.map +1 -1
- package/dist/types/src/observability.js +51 -37
- package/dist/types/src/observability.js.map +1 -1
- package/dist/types/src/segment/browser.d.ts.map +1 -1
- package/dist/types/src/segment/browser.js +2 -2
- package/dist/types/src/segment/browser.js.map +1 -1
- package/dist/types/src/segment/node.d.ts.map +1 -1
- package/dist/types/src/segment/node.js +2 -2
- package/dist/types/src/segment/node.js.map +1 -1
- package/dist/types/src/segment/types.d.ts +3 -1
- package/dist/types/src/segment/types.d.ts.map +1 -1
- package/package.json +18 -14
- package/src/datadog/metrics.ts +3 -5
- package/src/helpers/browser-observability.ts +177 -0
- package/src/helpers/common.ts +25 -0
- package/src/helpers/index.ts +9 -0
- package/src/{util.ts → helpers/map-spaces.ts} +1 -2
- package/src/helpers/node-observability.ts +151 -0
- package/src/helpers/setup-telemetry-listeners.ts +110 -0
- package/src/index.ts +1 -0
- package/src/observability.ts +61 -39
- package/src/segment/browser.ts +3 -3
- package/src/segment/node.ts +3 -3
- package/src/segment/types.ts +1 -1
- package/dist/types/src/util.d.ts.map +0 -1
- package/dist/types/src/util.js.map +0 -1
package/src/observability.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { isNode } from '@dxos/util';
|
|
|
14
14
|
|
|
15
15
|
import buildSecrets from './cli-observability-secrets.json';
|
|
16
16
|
import { DatadogMetrics } from './datadog';
|
|
17
|
+
import { type IPData, getTelemetryIdentifier, mapSpaces } from './helpers';
|
|
17
18
|
import { SegmentTelemetry, type EventOptions, type PageOptions } from './segment';
|
|
18
19
|
import {
|
|
19
20
|
captureException as sentryCaptureException,
|
|
@@ -23,15 +24,11 @@ import {
|
|
|
23
24
|
type InitOptions,
|
|
24
25
|
setTag as sentrySetTag,
|
|
25
26
|
} from './sentry';
|
|
26
|
-
import { mapSpaces } from './util';
|
|
27
27
|
|
|
28
28
|
const SPACE_METRICS_MIN_INTERVAL = 1000 * 60;
|
|
29
29
|
// const DATADOG_IDLE_INTERVAL = 1000 * 60 * 5;
|
|
30
30
|
const DATADOG_IDLE_INTERVAL = 1000 * 60 * 1;
|
|
31
31
|
|
|
32
|
-
// TODO(nf): add allowlist for telemetry tags?
|
|
33
|
-
const ERROR_TAGS = ['identityKey', 'username', 'deviceKey', 'group'];
|
|
34
|
-
|
|
35
32
|
// Secrets? EnvironmentConfig?
|
|
36
33
|
|
|
37
34
|
export type ObservabilitySecrets = {
|
|
@@ -45,6 +42,7 @@ export type ObservabilitySecrets = {
|
|
|
45
42
|
};
|
|
46
43
|
|
|
47
44
|
export type Mode = 'basic' | 'full' | 'disabled';
|
|
45
|
+
export type TagScope = 'errors' | 'telemetry' | 'metrics' | 'all';
|
|
48
46
|
|
|
49
47
|
export type ObservabilityOptions = {
|
|
50
48
|
/// The webapp (e.g. 'composer.dxos.org'), 'cli', or 'agent'.
|
|
@@ -62,8 +60,8 @@ export type ObservabilityOptions = {
|
|
|
62
60
|
|
|
63
61
|
errorLog?: {
|
|
64
62
|
sentryInitOptions?: InitOptions;
|
|
63
|
+
logProcessor?: boolean;
|
|
65
64
|
};
|
|
66
|
-
logProcessor?: boolean;
|
|
67
65
|
};
|
|
68
66
|
|
|
69
67
|
/*
|
|
@@ -87,10 +85,10 @@ export class Observability {
|
|
|
87
85
|
private _group?: string;
|
|
88
86
|
// TODO(nf): accept upstream context?
|
|
89
87
|
private _ctx = new Context();
|
|
90
|
-
private _tags = new Map<string, string>();
|
|
88
|
+
private _tags = new Map<string, { value: string; scope: TagScope }>();
|
|
91
89
|
|
|
92
90
|
// TODO(nf): make platform a required extension?
|
|
93
|
-
constructor({ namespace, config, secrets, group, mode, telemetry, errorLog
|
|
91
|
+
constructor({ namespace, config, secrets, group, mode, telemetry, errorLog }: ObservabilityOptions) {
|
|
94
92
|
this._namespace = namespace;
|
|
95
93
|
this._config = config;
|
|
96
94
|
this._mode = mode ?? 'disabled';
|
|
@@ -98,7 +96,7 @@ export class Observability {
|
|
|
98
96
|
this._secrets = this._loadSecrets(config, secrets);
|
|
99
97
|
this._telemetryBatchSize = telemetry?.batchSize ?? 30;
|
|
100
98
|
this._errorReportingOptions = errorLog?.sentryInitOptions;
|
|
101
|
-
this._errorLogProcessor = logProcessor ?? false;
|
|
99
|
+
this._errorLogProcessor = errorLog?.logProcessor ?? false;
|
|
102
100
|
|
|
103
101
|
if (this._group) {
|
|
104
102
|
this.setTag('group', this._group);
|
|
@@ -149,6 +147,10 @@ export class Observability {
|
|
|
149
147
|
return this._mode;
|
|
150
148
|
}
|
|
151
149
|
|
|
150
|
+
get group() {
|
|
151
|
+
return this._group;
|
|
152
|
+
}
|
|
153
|
+
|
|
152
154
|
get enabled() {
|
|
153
155
|
return this._mode !== 'disabled';
|
|
154
156
|
}
|
|
@@ -157,18 +159,25 @@ export class Observability {
|
|
|
157
159
|
// Tags
|
|
158
160
|
//
|
|
159
161
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
*/
|
|
163
|
-
setTag(key: string, value: string) {
|
|
164
|
-
if (this.enabled && ERROR_TAGS.includes(key)) {
|
|
162
|
+
setTag(key: string, value: string, scope?: TagScope) {
|
|
163
|
+
if (this.enabled && (scope === undefined || scope === 'all' || scope === 'errors')) {
|
|
165
164
|
sentrySetTag(key, value);
|
|
166
165
|
}
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
if (!scope) {
|
|
167
|
+
scope = 'all';
|
|
168
|
+
}
|
|
169
|
+
this._tags.set(key, { value, scope });
|
|
169
170
|
}
|
|
170
171
|
|
|
171
|
-
|
|
172
|
+
addIPDataTelemetryTags = (ipData: IPData) => {
|
|
173
|
+
this.setTag('city', ipData.city, 'telemetry');
|
|
174
|
+
this.setTag('region', ipData.region, 'telemetry');
|
|
175
|
+
this.setTag('country', ipData.country, 'telemetry');
|
|
176
|
+
ipData.latitude && this.setTag('latitude', ipData.latitude.toString(), 'telemetry');
|
|
177
|
+
ipData.longitude && this.setTag('longitude', ipData.longitude.toString(), 'telemetry');
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// TODO(wittjosiah): Improve privacy of telemetry identifiers. See `getTelemetryIdentifier`.
|
|
172
181
|
async setIdentityTags(client: Client) {
|
|
173
182
|
client.services.services.IdentityService!.queryIdentity().subscribe((idqr) => {
|
|
174
183
|
if (!idqr?.identity?.identityKey) {
|
|
@@ -176,16 +185,9 @@ export class Observability {
|
|
|
176
185
|
return;
|
|
177
186
|
}
|
|
178
187
|
|
|
179
|
-
|
|
180
|
-
// TODO(nf): cardinality
|
|
181
|
-
this.setTag('identityKey', idqr?.identity?.identityKey.truncate());
|
|
182
|
-
if (idqr?.identity?.profile?.displayName) {
|
|
183
|
-
this.setTag('username', idqr?.identity?.profile?.displayName);
|
|
184
|
-
}
|
|
188
|
+
this.setTag('identityKey', idqr.identity.identityKey.truncate());
|
|
185
189
|
});
|
|
186
|
-
}
|
|
187
190
|
|
|
188
|
-
async setDeviceTags(client: Client) {
|
|
189
191
|
client.services.services.DevicesService!.queryDevices().subscribe((dqr) => {
|
|
190
192
|
if (!dqr || !dqr.devices || dqr.devices.length === 0) {
|
|
191
193
|
log('empty response from device service', { device: dqr });
|
|
@@ -213,7 +215,14 @@ export class Observability {
|
|
|
213
215
|
if (this.enabled && this._secrets.DATADOG_API_KEY) {
|
|
214
216
|
this._metrics = new DatadogMetrics({
|
|
215
217
|
apiKey: this._secrets.DATADOG_API_KEY,
|
|
216
|
-
getTags: () =>
|
|
218
|
+
getTags: () =>
|
|
219
|
+
Object.fromEntries(
|
|
220
|
+
Array.from(this._tags)
|
|
221
|
+
.filter(([key, value]) => {
|
|
222
|
+
return value.scope === 'all' || value.scope === 'metrics';
|
|
223
|
+
})
|
|
224
|
+
.map(([key, value]) => [key, value.value]),
|
|
225
|
+
),
|
|
217
226
|
// TODO(nf): move/refactor from telementryContext, needed to read CORS proxy
|
|
218
227
|
config: this._config!,
|
|
219
228
|
});
|
|
@@ -283,19 +292,27 @@ export class Observability {
|
|
|
283
292
|
// scheduleTaskInterval(ctx, async () => updateSignalMetrics.emit(), DATADOG_IDLE_INTERVAL);
|
|
284
293
|
}
|
|
285
294
|
|
|
286
|
-
startSpacesMetrics(client: Client) {
|
|
287
|
-
|
|
295
|
+
startSpacesMetrics(client: Client, namespace: string) {
|
|
296
|
+
const spaces = client.spaces.get();
|
|
288
297
|
const subscriptions = new Map<string, { unsubscribe: () => void }>();
|
|
289
298
|
this._ctx.onDispose(() => subscriptions.forEach((subscription) => subscription.unsubscribe()));
|
|
290
299
|
|
|
291
300
|
const updateSpaceMetrics = new Event<Space>().debounce(SPACE_METRICS_MIN_INTERVAL);
|
|
292
|
-
updateSpaceMetrics.on(this._ctx, async (
|
|
301
|
+
updateSpaceMetrics.on(this._ctx, async () => {
|
|
293
302
|
log('send space update');
|
|
294
|
-
for (const
|
|
295
|
-
|
|
296
|
-
this.gauge('dxos.client.space.
|
|
297
|
-
this.gauge('dxos.client.space.
|
|
298
|
-
this.gauge('dxos.client.space.
|
|
303
|
+
for (const data of mapSpaces(spaces, { truncateKeys: true })) {
|
|
304
|
+
// Metrics
|
|
305
|
+
this.gauge('dxos.client.space.members', data.members, { key: data.key });
|
|
306
|
+
this.gauge('dxos.client.space.objects', data.objects, { key: data.key });
|
|
307
|
+
this.gauge('dxos.client.space.epoch', data.epoch, { key: data.key });
|
|
308
|
+
this.gauge('dxos.client.space.currentDataMutations', data.currentDataMutations, { key: data.key });
|
|
309
|
+
|
|
310
|
+
// Telemetry
|
|
311
|
+
this.event({
|
|
312
|
+
identityId: getTelemetryIdentifier(client),
|
|
313
|
+
name: `${namespace}.space.update`,
|
|
314
|
+
properties: data,
|
|
315
|
+
});
|
|
299
316
|
}
|
|
300
317
|
});
|
|
301
318
|
|
|
@@ -311,9 +328,7 @@ export class Observability {
|
|
|
311
328
|
});
|
|
312
329
|
|
|
313
330
|
client.spaces.subscribe({
|
|
314
|
-
next: async () => {
|
|
315
|
-
spaces = client.spaces.get();
|
|
316
|
-
// spaces = await this.getSpaces(this._agent.client);
|
|
331
|
+
next: async (spaces) => {
|
|
317
332
|
spaces
|
|
318
333
|
.filter((space) => !subscriptions.has(space.key.toHex()))
|
|
319
334
|
.forEach((space) => {
|
|
@@ -370,7 +385,14 @@ export class Observability {
|
|
|
370
385
|
this._telemetry = new SegmentTelemetry({
|
|
371
386
|
apiKey: this._secrets.TELEMETRY_API_KEY,
|
|
372
387
|
batchSize: this._telemetryBatchSize,
|
|
373
|
-
getTags: () =>
|
|
388
|
+
getTags: () =>
|
|
389
|
+
Object.fromEntries(
|
|
390
|
+
Array.from(this._tags)
|
|
391
|
+
.filter(([key, value]) => {
|
|
392
|
+
return value.scope === 'all' || value.scope === 'telemetry';
|
|
393
|
+
})
|
|
394
|
+
.map(([key, value]) => [key, value.value]),
|
|
395
|
+
),
|
|
374
396
|
});
|
|
375
397
|
} else {
|
|
376
398
|
log('segment disabled');
|
|
@@ -420,8 +442,8 @@ export class Observability {
|
|
|
420
442
|
}
|
|
421
443
|
// TODO(nf): is this different than passing as properties in options?
|
|
422
444
|
this._tags.forEach((v, k) => {
|
|
423
|
-
if (
|
|
424
|
-
sentrySetTag(k, v);
|
|
445
|
+
if (v.scope === 'all' || v.scope === 'errors') {
|
|
446
|
+
sentrySetTag(k, v.value);
|
|
425
447
|
}
|
|
426
448
|
});
|
|
427
449
|
} else {
|
package/src/segment/browser.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { EventOptions, SegmentTelemetryOptions, PageOptions } from './types
|
|
|
10
10
|
import { captureException } from '../sentry';
|
|
11
11
|
|
|
12
12
|
export class SegmentTelemetry {
|
|
13
|
-
private _getTags: () =>
|
|
13
|
+
private _getTags: () => { [key: string]: string };
|
|
14
14
|
|
|
15
15
|
constructor({ apiKey, batchSize, getTags }: SegmentTelemetryOptions) {
|
|
16
16
|
this._getTags = getTags;
|
|
@@ -31,7 +31,7 @@ export class SegmentTelemetry {
|
|
|
31
31
|
page({ identityId: userId, ...options }: PageOptions = {}) {
|
|
32
32
|
try {
|
|
33
33
|
(window as any).analytics?.page({
|
|
34
|
-
context:
|
|
34
|
+
context: this._getTags(),
|
|
35
35
|
...options,
|
|
36
36
|
userId,
|
|
37
37
|
});
|
|
@@ -44,7 +44,7 @@ export class SegmentTelemetry {
|
|
|
44
44
|
try {
|
|
45
45
|
options.properties = {
|
|
46
46
|
...options.properties,
|
|
47
|
-
...
|
|
47
|
+
...this._getTags(),
|
|
48
48
|
};
|
|
49
49
|
(window as any).analytics?.track({
|
|
50
50
|
...options,
|
package/src/segment/node.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { captureException } from '../sentry';
|
|
|
12
12
|
|
|
13
13
|
export class SegmentTelemetry {
|
|
14
14
|
private _analytics?: Analytics;
|
|
15
|
-
private _getTags: () =>
|
|
15
|
+
private _getTags: () => { [key: string]: string };
|
|
16
16
|
|
|
17
17
|
constructor({ apiKey, batchSize, getTags }: SegmentTelemetryOptions) {
|
|
18
18
|
this._getTags = getTags;
|
|
@@ -52,7 +52,7 @@ export class SegmentTelemetry {
|
|
|
52
52
|
* Track an event.
|
|
53
53
|
*/
|
|
54
54
|
event({ installationId: anonymousId, identityId: userId, name: event, ...options }: EventOptions) {
|
|
55
|
-
log('sending event to telemetry', { event, options, tags:
|
|
55
|
+
log('sending event to telemetry', { event, options, tags: this._getTags() });
|
|
56
56
|
if (!this._analytics) {
|
|
57
57
|
log('Analytics not initialized', { action: 'event' });
|
|
58
58
|
return;
|
|
@@ -61,7 +61,7 @@ export class SegmentTelemetry {
|
|
|
61
61
|
try {
|
|
62
62
|
this._analytics.track({
|
|
63
63
|
...options,
|
|
64
|
-
context:
|
|
64
|
+
context: this._getTags(),
|
|
65
65
|
userId,
|
|
66
66
|
anonymousId: anonymousId!,
|
|
67
67
|
event,
|
package/src/segment/types.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../src/util.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAEnD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,eAAO,MAAM,SAAS,WAAY,KAAK,EAAE,YAAW,gBAAgB;;;;;;;;;;;;GAqCnE,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"util.js","sourceRoot":"","sources":["../../../src/util.ts"],"names":[],"mappings":";AAAA,EAAE;AACF,0BAA0B;AAC1B,EAAE;;;AASK,MAAM,SAAS,GAAG,CAAC,MAAe,EAAE,UAA4B,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,EAAE,EAAE;IAChH,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;;QAC1B,4BAA4B;QAC5B,oEAAoE;QACpE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAA,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,mCAAI,EAAE,CAAC;QAC1D,MAAM,OAAO,GAAG,IAAI,IAAI,KAAK,IAAI,KAAK,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAElE,kGAAkG;QAClG,uDAAuD;QACvD,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC;QAC9C,MAAM,kBAAkB,GAAG,MAAA,MAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,YAAY,0CAAE,OAAO,CAAC,SAAS,CAAC,SAAS,CAAC,aAAa,EAAE,mCAAI,CAAC,CAAC;QACpG,MAAM,KAAK,GAAG,MAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,YAAY,0CAAE,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC;QAC/D,yEAAyE;QACzE,MAAM,oBAAoB,GAAG,MAAA,MAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,oBAAoB,0CAAE,aAAa,EAAE,mCAAI,CAAC,CAAC;QAClF,MAAM,kBAAkB,GAAG,MAAA,MAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,mBAAmB,0CAAE,aAAa,EAAE,mCAAI,CAAC,CAAC;QAE/E,OAAO;YACL,kCAAkC;YAClC,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE;YACzB,IAAI,EAAE,KAAK,CAAC,MAAM;YAClB,IAAI,EAAE,KAAK,CAAC,UAAU,CAAC,IAAI;YAC3B,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM;YACnC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,MAAM;YACxC,OAAO;YACP,KAAK;YACL,gBAAgB;YAChB,kBAAkB;YAClB,oBAAoB;YACpB,kBAAkB;YAElB,0BAA0B;YAC1B,QAAQ,EAAE,CACR,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,oBAAoB,GAAG,kBAAkB,CAAC,GAAG,CAAC,kBAAkB,GAAG,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC;gBAC9G,GAAG,CACJ,CAAC,OAAO,CAAC,CAAC,CAAC;SACb,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AArCW,QAAA,SAAS,aAqCpB"}
|