@alchemy.run/axiom 0.0.0-stage → 2.0.0-beta.84
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/LICENSE +201 -0
- package/README.md +58 -2
- package/lib/Annotation.d.ts +51 -0
- package/lib/Annotation.d.ts.map +1 -0
- package/lib/Annotation.js +97 -0
- package/lib/Annotation.js.map +1 -0
- package/lib/ApiToken.d.ts +64 -0
- package/lib/ApiToken.d.ts.map +1 -0
- package/lib/ApiToken.js +123 -0
- package/lib/ApiToken.js.map +1 -0
- package/lib/AuthProvider.d.ts +40 -0
- package/lib/AuthProvider.d.ts.map +1 -0
- package/lib/AuthProvider.js +99 -0
- package/lib/AuthProvider.js.map +1 -0
- package/lib/Chart.d.ts +130 -0
- package/lib/Chart.d.ts.map +1 -0
- package/lib/Chart.js +14 -0
- package/lib/Chart.js.map +1 -0
- package/lib/Contributions.d.ts +7 -0
- package/lib/Contributions.d.ts.map +1 -0
- package/lib/Contributions.js +6 -0
- package/lib/Contributions.js.map +1 -0
- package/lib/Credentials.d.ts +10 -0
- package/lib/Credentials.d.ts.map +1 -0
- package/lib/Credentials.js +30 -0
- package/lib/Credentials.js.map +1 -0
- package/lib/Dashboard.d.ts +112 -0
- package/lib/Dashboard.d.ts.map +1 -0
- package/lib/Dashboard.js +157 -0
- package/lib/Dashboard.js.map +1 -0
- package/lib/Dataset.d.ts +120 -0
- package/lib/Dataset.d.ts.map +1 -0
- package/lib/Dataset.js +215 -0
- package/lib/Dataset.js.map +1 -0
- package/lib/Monitor.d.ts +76 -0
- package/lib/Monitor.d.ts.map +1 -0
- package/lib/Monitor.js +126 -0
- package/lib/Monitor.js.map +1 -0
- package/lib/Notifier.d.ts +65 -0
- package/lib/Notifier.d.ts.map +1 -0
- package/lib/Notifier.js +112 -0
- package/lib/Notifier.js.map +1 -0
- package/lib/OtelHeaders.d.ts +9 -0
- package/lib/OtelHeaders.d.ts.map +1 -0
- package/lib/OtelHeaders.js +9 -0
- package/lib/OtelHeaders.js.map +1 -0
- package/lib/Providers.d.ts +15 -0
- package/lib/Providers.d.ts.map +1 -0
- package/lib/Providers.js +34 -0
- package/lib/Providers.js.map +1 -0
- package/lib/Telemetry.d.ts +66 -0
- package/lib/Telemetry.d.ts.map +1 -0
- package/lib/Telemetry.js +68 -0
- package/lib/Telemetry.js.map +1 -0
- package/lib/View.d.ts +57 -0
- package/lib/View.d.ts.map +1 -0
- package/lib/View.js +100 -0
- package/lib/View.js.map +1 -0
- package/lib/VirtualField.d.ts +44 -0
- package/lib/VirtualField.d.ts.map +1 -0
- package/lib/VirtualField.js +97 -0
- package/lib/VirtualField.js.map +1 -0
- package/lib/index.d.ts +14 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +14 -0
- package/lib/index.js.map +1 -0
- package/package.json +68 -3
- package/src/Annotation.ts +121 -0
- package/src/ApiToken.ts +148 -0
- package/src/AuthProvider.ts +135 -0
- package/src/Chart.ts +156 -0
- package/src/Contributions.ts +9 -0
- package/src/Credentials.ts +68 -0
- package/src/Dashboard.ts +192 -0
- package/src/Dataset.ts +326 -0
- package/src/Monitor.ts +148 -0
- package/src/Notifier.ts +136 -0
- package/src/OtelHeaders.ts +9 -0
- package/src/Providers.ts +60 -0
- package/src/Telemetry.ts +116 -0
- package/src/View.ts +130 -0
- package/src/VirtualField.ts +123 -0
- package/src/index.ts +13 -0
package/lib/Dataset.d.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import * as Axiom from "@distilled.cloud/axiom";
|
|
2
|
+
import * as Provider from "alchemy/Provider";
|
|
3
|
+
import { Resource } from "alchemy/Resource";
|
|
4
|
+
import { Stack } from "alchemy/Stack";
|
|
5
|
+
import { Stage } from "alchemy/Stage";
|
|
6
|
+
import type { Providers } from "./Providers.ts";
|
|
7
|
+
export type DatasetKind = "otel:metrics:v1" | "otel:traces:v1" | "otel:logs:v1" | "axiom:events:v1" | (string & {});
|
|
8
|
+
export type DatasetProps = {
|
|
9
|
+
/**
|
|
10
|
+
* Dataset name. Used as the dataset's stable identifier in Axiom — changing
|
|
11
|
+
* this triggers a replacement.
|
|
12
|
+
*/
|
|
13
|
+
name: string;
|
|
14
|
+
/** Free-form description shown in the Axiom UI. */
|
|
15
|
+
description?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Edge deployment where Axiom stores and processes the dataset's data.
|
|
18
|
+
* Defaults to the organization's default edge deployment.
|
|
19
|
+
*
|
|
20
|
+
* Cannot be changed after creation — triggers a replacement.
|
|
21
|
+
*/
|
|
22
|
+
edgeDeployment?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Dataset kind. Defaults to `axiom:events:v1`.
|
|
25
|
+
*
|
|
26
|
+
* For OTEL pipelines, choose:
|
|
27
|
+
* - `otel:traces:v1` for traces
|
|
28
|
+
* - `otel:logs:v1` for logs
|
|
29
|
+
* - `otel:metrics:v1` for metrics
|
|
30
|
+
*
|
|
31
|
+
* Cannot be changed after creation — triggers a replacement.
|
|
32
|
+
*/
|
|
33
|
+
kind?: DatasetKind;
|
|
34
|
+
/** Retention in days. Plan-dependent — see Axiom docs. */
|
|
35
|
+
retentionDays?: number;
|
|
36
|
+
/** Whether to enforce the configured retention period. */
|
|
37
|
+
useRetentionPeriod?: boolean;
|
|
38
|
+
};
|
|
39
|
+
export type Dataset = Resource<"Axiom.Dataset", DatasetProps, {
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
kind: DatasetKind;
|
|
43
|
+
description: string;
|
|
44
|
+
created: string;
|
|
45
|
+
/** Edge deployment where the dataset's data is stored and processed. */
|
|
46
|
+
edgeDeployment: string;
|
|
47
|
+
/** Base URL for ingesting and querying data in the dataset's edge deployment. */
|
|
48
|
+
edgeDeploymentUrl: string;
|
|
49
|
+
/** Base URL for Axiom's centralized management API. */
|
|
50
|
+
apiBaseUrl: string;
|
|
51
|
+
/** Root OTLP endpoint. Most exporters auto-append the signal path. */
|
|
52
|
+
otelEndpoint: string;
|
|
53
|
+
/** OTLP/HTTP traces endpoint. */
|
|
54
|
+
otelTracesEndpoint: string;
|
|
55
|
+
/** OTLP/HTTP logs endpoint. */
|
|
56
|
+
otelLogsEndpoint: string;
|
|
57
|
+
/** OTLP/HTTP metrics endpoint. */
|
|
58
|
+
otelMetricsEndpoint: string;
|
|
59
|
+
/**
|
|
60
|
+
* Headers required for OTLP shipping aside from the bearer token:
|
|
61
|
+
* `X-Axiom-Dataset`, or `X-Axiom-Metrics-Dataset` for an
|
|
62
|
+
* `otel:metrics:v1` dataset.
|
|
63
|
+
* Add `Authorization: Bearer <AXIOM_TOKEN>` separately at runtime so the
|
|
64
|
+
* secret is never persisted in resource state.
|
|
65
|
+
*/
|
|
66
|
+
otelHeaders: Record<string, string>;
|
|
67
|
+
}, never, Providers>;
|
|
68
|
+
/**
|
|
69
|
+
* An Axiom dataset — the top-level container that stores events, logs,
|
|
70
|
+
* traces, or metrics. Pick a `kind` up-front: it determines schema and how
|
|
71
|
+
* the data is shown in the UI, and **cannot be changed** after creation
|
|
72
|
+
* (changing it triggers a replacement, which deletes the data).
|
|
73
|
+
*
|
|
74
|
+
* Datasets expose their edge deployment and Axiom's OTLP/HTTP endpoints
|
|
75
|
+
* (`otelTracesEndpoint`, `otelLogsEndpoint`, `otelMetricsEndpoint`) as output
|
|
76
|
+
* attributes so you can inject them into a Worker / Lambda's env vars for
|
|
77
|
+
* OpenTelemetry shipping. The bearer token is **not** stored in resource state
|
|
78
|
+
* — supply `Authorization: Bearer <AXIOM_TOKEN>` separately at runtime.
|
|
79
|
+
* @see https://axiom.co/docs/reference/datasets
|
|
80
|
+
* @see https://axiom.co/docs/send-data/opentelemetry — OTLP endpoint reference
|
|
81
|
+
*
|
|
82
|
+
* ### Creating a Dataset
|
|
83
|
+
* **Example:** Logs dataset with 30-day retention
|
|
84
|
+
* ```typescript
|
|
85
|
+
* const logs = yield* Axiom.Dataset("app-logs", {
|
|
86
|
+
* name: "my-app-logs",
|
|
87
|
+
* kind: "otel:logs:v1",
|
|
88
|
+
* description: "Application logs from prod workers",
|
|
89
|
+
* retentionDays: 30,
|
|
90
|
+
* useRetentionPeriod: true,
|
|
91
|
+
* });
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* **Example:** Separate datasets per OTEL signal
|
|
95
|
+
* ```typescript
|
|
96
|
+
* const traces = yield* Axiom.Dataset("traces", { name: "app-traces", kind: "otel:traces:v1" });
|
|
97
|
+
* const logs = yield* Axiom.Dataset("logs", { name: "app-logs", kind: "otel:logs:v1" });
|
|
98
|
+
* const metrics = yield* Axiom.Dataset("metrics", { name: "app-metrics", kind: "otel:metrics:v1" });
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* ### Shipping OTEL data
|
|
102
|
+
* **Example:** Wire OTEL env vars into a Cloudflare Worker
|
|
103
|
+
* ```typescript
|
|
104
|
+
* yield* Cloudflare.Worker("api", {
|
|
105
|
+
* vars: {
|
|
106
|
+
* OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: traces.otelTracesEndpoint,
|
|
107
|
+
* OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: logs.otelLogsEndpoint,
|
|
108
|
+
* // Bearer token must come from a secret store, not the dataset state.
|
|
109
|
+
* OTEL_EXPORTER_OTLP_HEADERS:
|
|
110
|
+
* `Authorization=Bearer ${env.AXIOM_TOKEN},X-Axiom-Dataset=${traces.name}`,
|
|
111
|
+
* },
|
|
112
|
+
* });
|
|
113
|
+
* ```
|
|
114
|
+
*
|
|
115
|
+
* @resource
|
|
116
|
+
* @product Dataset
|
|
117
|
+
*/
|
|
118
|
+
export declare const Dataset: import("alchemy/Resource").ResourceClass<Dataset>;
|
|
119
|
+
export declare const DatasetProvider: () => import("effect/Layer").Layer<Provider.Provider<Dataset>, never, Stack | Stage | Axiom.AxiomOpContext>;
|
|
120
|
+
//# sourceMappingURL=Dataset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Dataset.d.ts","sourceRoot":"","sources":["../src/Dataset.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,wBAAwB,CAAC;AAIhD,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAGtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,MAAM,WAAW,GACnB,iBAAiB,GACjB,gBAAgB,GAChB,cAAc,GACd,iBAAiB,GACjB,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAElB,MAAM,MAAM,YAAY,GAAG;IACzB;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0DAA0D;IAC1D,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG,QAAQ,CAC5B,eAAe,EACf,YAAY,EACZ;IACE,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,WAAW,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,cAAc,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,YAAY,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,+BAA+B;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,kCAAkC;IAClC,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;;OAMG;IACH,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC,EACD,KAAK,EACL,SAAS,CACV,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,eAAO,MAAM,OAAO,mDAAqC,CAAC;AA8B1D,eAAO,MAAM,eAAe,6GA8JzB,CAAC"}
|
package/lib/Dataset.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import * as Axiom from "@distilled.cloud/axiom";
|
|
2
|
+
import { Credentials } from "@distilled.cloud/axiom/Credentials";
|
|
3
|
+
import { Unowned } from "alchemy/AdoptPolicy";
|
|
4
|
+
import { isResolved } from "alchemy/Diff";
|
|
5
|
+
import * as Provider from "alchemy/Provider";
|
|
6
|
+
import { Resource } from "alchemy/Resource";
|
|
7
|
+
import { Stack } from "alchemy/Stack";
|
|
8
|
+
import { Stage } from "alchemy/Stage";
|
|
9
|
+
import * as Effect from "effect/Effect";
|
|
10
|
+
import { otelDatasetHeader } from "./OtelHeaders.js";
|
|
11
|
+
/**
|
|
12
|
+
* An Axiom dataset — the top-level container that stores events, logs,
|
|
13
|
+
* traces, or metrics. Pick a `kind` up-front: it determines schema and how
|
|
14
|
+
* the data is shown in the UI, and **cannot be changed** after creation
|
|
15
|
+
* (changing it triggers a replacement, which deletes the data).
|
|
16
|
+
*
|
|
17
|
+
* Datasets expose their edge deployment and Axiom's OTLP/HTTP endpoints
|
|
18
|
+
* (`otelTracesEndpoint`, `otelLogsEndpoint`, `otelMetricsEndpoint`) as output
|
|
19
|
+
* attributes so you can inject them into a Worker / Lambda's env vars for
|
|
20
|
+
* OpenTelemetry shipping. The bearer token is **not** stored in resource state
|
|
21
|
+
* — supply `Authorization: Bearer <AXIOM_TOKEN>` separately at runtime.
|
|
22
|
+
* @see https://axiom.co/docs/reference/datasets
|
|
23
|
+
* @see https://axiom.co/docs/send-data/opentelemetry — OTLP endpoint reference
|
|
24
|
+
*
|
|
25
|
+
* ### Creating a Dataset
|
|
26
|
+
* **Example:** Logs dataset with 30-day retention
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const logs = yield* Axiom.Dataset("app-logs", {
|
|
29
|
+
* name: "my-app-logs",
|
|
30
|
+
* kind: "otel:logs:v1",
|
|
31
|
+
* description: "Application logs from prod workers",
|
|
32
|
+
* retentionDays: 30,
|
|
33
|
+
* useRetentionPeriod: true,
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* **Example:** Separate datasets per OTEL signal
|
|
38
|
+
* ```typescript
|
|
39
|
+
* const traces = yield* Axiom.Dataset("traces", { name: "app-traces", kind: "otel:traces:v1" });
|
|
40
|
+
* const logs = yield* Axiom.Dataset("logs", { name: "app-logs", kind: "otel:logs:v1" });
|
|
41
|
+
* const metrics = yield* Axiom.Dataset("metrics", { name: "app-metrics", kind: "otel:metrics:v1" });
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* ### Shipping OTEL data
|
|
45
|
+
* **Example:** Wire OTEL env vars into a Cloudflare Worker
|
|
46
|
+
* ```typescript
|
|
47
|
+
* yield* Cloudflare.Worker("api", {
|
|
48
|
+
* vars: {
|
|
49
|
+
* OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: traces.otelTracesEndpoint,
|
|
50
|
+
* OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: logs.otelLogsEndpoint,
|
|
51
|
+
* // Bearer token must come from a secret store, not the dataset state.
|
|
52
|
+
* OTEL_EXPORTER_OTLP_HEADERS:
|
|
53
|
+
* `Authorization=Bearer ${env.AXIOM_TOKEN},X-Axiom-Dataset=${traces.name}`,
|
|
54
|
+
* },
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @resource
|
|
59
|
+
* @product Dataset
|
|
60
|
+
*/
|
|
61
|
+
export const Dataset = Resource("Axiom.Dataset");
|
|
62
|
+
/**
|
|
63
|
+
* Axiom has no tags/labels API. The only writable field we can use to mark
|
|
64
|
+
* ownership is `description`. We append a deterministic marker on create so
|
|
65
|
+
* that on a re-apply (e.g. state was wiped) we can safely identify a dataset
|
|
66
|
+
* that **we** previously created and adopt it idempotently — without
|
|
67
|
+
* accidentally hijacking a dataset created by someone else with the same name.
|
|
68
|
+
*/
|
|
69
|
+
const MARKER_RE = /\s*\[alchemy:stack=([^;]+);stage=([^;]+);id=([^\]]+)\]\s*$/;
|
|
70
|
+
const buildMarker = (stack, stage, id) => `[alchemy:stack=${stack};stage=${stage};id=${id}]`;
|
|
71
|
+
const augmentDescription = (description, marker) => {
|
|
72
|
+
const base = stripMarker(description ?? "");
|
|
73
|
+
return base.length > 0 ? `${base}\n${marker}` : marker;
|
|
74
|
+
};
|
|
75
|
+
const stripMarker = (description) => description.replace(MARKER_RE, "").trimEnd();
|
|
76
|
+
const parseMarker = (description) => {
|
|
77
|
+
if (!description)
|
|
78
|
+
return undefined;
|
|
79
|
+
const m = description.match(MARKER_RE);
|
|
80
|
+
if (!m)
|
|
81
|
+
return undefined;
|
|
82
|
+
return { stack: m[1], stage: m[2], id: m[3] };
|
|
83
|
+
};
|
|
84
|
+
export const DatasetProvider = () => Provider.effect(Dataset, Effect.gen(function* () {
|
|
85
|
+
const { apiBaseUrl } = yield* yield* Credentials;
|
|
86
|
+
const create = yield* Axiom.createDataset;
|
|
87
|
+
const update = yield* Axiom.updateDataset;
|
|
88
|
+
const get = yield* Axiom.getDataset;
|
|
89
|
+
const listDatasets = yield* Axiom.getDatasets;
|
|
90
|
+
const del = yield* Axiom.deleteDataset;
|
|
91
|
+
const toAttrs = Effect.fn(function* (dataset) {
|
|
92
|
+
if (!dataset.edgeDeployment || !dataset.edgeDeploymentUrl) {
|
|
93
|
+
return yield* Effect.fail(new Error(`Axiom dataset "${dataset.name}" is missing its edge deployment metadata`));
|
|
94
|
+
}
|
|
95
|
+
const apiRoot = apiBaseUrl.replace(/\/$/, "");
|
|
96
|
+
const otelRoot = dataset.edgeDeploymentUrl.replace(/\/$/, "");
|
|
97
|
+
return {
|
|
98
|
+
id: dataset.id,
|
|
99
|
+
name: dataset.name,
|
|
100
|
+
kind: dataset.kind,
|
|
101
|
+
description: stripMarker(dataset.description),
|
|
102
|
+
created: dataset.created,
|
|
103
|
+
edgeDeployment: dataset.edgeDeployment,
|
|
104
|
+
edgeDeploymentUrl: dataset.edgeDeploymentUrl,
|
|
105
|
+
apiBaseUrl: apiRoot,
|
|
106
|
+
otelEndpoint: otelRoot,
|
|
107
|
+
otelTracesEndpoint: `${otelRoot}/v1/traces`,
|
|
108
|
+
otelLogsEndpoint: `${otelRoot}/v1/logs`,
|
|
109
|
+
otelMetricsEndpoint: `${otelRoot}/v1/metrics`,
|
|
110
|
+
otelHeaders: {
|
|
111
|
+
[otelDatasetHeader(dataset.kind === "otel:metrics:v1")]: dataset.name,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
return {
|
|
116
|
+
stables: ["id", "name", "kind", "edgeDeployment", "edgeDeploymentUrl"],
|
|
117
|
+
// Enumerate every dataset in the org. Axiom exposes a single
|
|
118
|
+
// account-wide `GET /v2/datasets` collection op (no pagination), so we
|
|
119
|
+
// fetch it once and hydrate each row into the exact `read`/`toAttrs`
|
|
120
|
+
// Attributes shape — directly usable by `delete` with no follow-up get.
|
|
121
|
+
list: () => Effect.gen(function* () {
|
|
122
|
+
const datasets = yield* listDatasets({});
|
|
123
|
+
return yield* Effect.forEach(datasets, toAttrs);
|
|
124
|
+
}),
|
|
125
|
+
diff: Effect.fn(function* ({ olds, news, output }) {
|
|
126
|
+
if (!isResolved(news))
|
|
127
|
+
return undefined;
|
|
128
|
+
if (output && news.name !== output.name) {
|
|
129
|
+
return { action: "replace" };
|
|
130
|
+
}
|
|
131
|
+
if (news.kind && output && news.kind !== output.kind) {
|
|
132
|
+
return { action: "replace" };
|
|
133
|
+
}
|
|
134
|
+
if (news.edgeDeployment && output && news.edgeDeployment !== output.edgeDeployment) {
|
|
135
|
+
return { action: "replace" };
|
|
136
|
+
}
|
|
137
|
+
if (news.description !== olds?.description ||
|
|
138
|
+
news.retentionDays !== olds?.retentionDays ||
|
|
139
|
+
news.useRetentionPeriod !== olds?.useRetentionPeriod) {
|
|
140
|
+
return { action: "update" };
|
|
141
|
+
}
|
|
142
|
+
return undefined;
|
|
143
|
+
}),
|
|
144
|
+
reconcile: Effect.fn(function* ({ id, news, output }) {
|
|
145
|
+
const stack = yield* Stack;
|
|
146
|
+
const stage = yield* Stage;
|
|
147
|
+
const marker = buildMarker(stack.name, stage, id);
|
|
148
|
+
// Observe — Axiom's dataset path identifier is `name`, which is
|
|
149
|
+
// also stable input. Probe for live state by name (preferring the
|
|
150
|
+
// cached `output.id` when present). `read` upstream has already
|
|
151
|
+
// surfaced foreign datasets as `Unowned`, so by the time we land
|
|
152
|
+
// here mutation is safe.
|
|
153
|
+
const datasetId = output?.id ?? news.name;
|
|
154
|
+
const observed = yield* get({ dataset_id: datasetId }).pipe(Effect.catchTag("NotFound", () => Effect.succeed(undefined)));
|
|
155
|
+
// Ensure — POST creates the dataset. Tolerate Conflict/
|
|
156
|
+
// UnprocessableEntity as a race with a peer reconciler (or with
|
|
157
|
+
// upstream read↔create), falling through to the sync path.
|
|
158
|
+
let current = observed;
|
|
159
|
+
if (current === undefined) {
|
|
160
|
+
current = yield* create({
|
|
161
|
+
name: news.name,
|
|
162
|
+
description: augmentDescription(news.description, marker),
|
|
163
|
+
edgeDeployment: news.edgeDeployment,
|
|
164
|
+
kind: news.kind,
|
|
165
|
+
retentionDays: news.retentionDays,
|
|
166
|
+
useRetentionPeriod: news.useRetentionPeriod,
|
|
167
|
+
}).pipe(Effect.catchIf((e) => e._tag === "Conflict" || e._tag === "UnprocessableEntity", () => update({
|
|
168
|
+
dataset_id: news.name,
|
|
169
|
+
description: augmentDescription(news.description, marker),
|
|
170
|
+
retentionDays: news.retentionDays,
|
|
171
|
+
useRetentionPeriod: news.useRetentionPeriod,
|
|
172
|
+
})));
|
|
173
|
+
return yield* toAttrs(current);
|
|
174
|
+
}
|
|
175
|
+
// Sync — the dataset exists. Apply mutable aspects (description,
|
|
176
|
+
// retentionDays, useRetentionPeriod) via PATCH. `kind` and `name`
|
|
177
|
+
// are stable and replacement-only via diff above.
|
|
178
|
+
const desiredDescription = augmentDescription(news.description, marker);
|
|
179
|
+
const needsSync = current.description !== desiredDescription ||
|
|
180
|
+
current.retentionDays !== news.retentionDays ||
|
|
181
|
+
current.useRetentionPeriod !== news.useRetentionPeriod;
|
|
182
|
+
if (!needsSync) {
|
|
183
|
+
return yield* toAttrs(current);
|
|
184
|
+
}
|
|
185
|
+
const updated = yield* update({
|
|
186
|
+
dataset_id: current.id,
|
|
187
|
+
description: desiredDescription,
|
|
188
|
+
retentionDays: news.retentionDays,
|
|
189
|
+
useRetentionPeriod: news.useRetentionPeriod,
|
|
190
|
+
});
|
|
191
|
+
return yield* toAttrs(updated);
|
|
192
|
+
}),
|
|
193
|
+
delete: Effect.fn(function* ({ output }) {
|
|
194
|
+
yield* del({ dataset_id: output.id }).pipe(Effect.catchTag("NotFound", () => Effect.void));
|
|
195
|
+
}),
|
|
196
|
+
read: Effect.fn(function* ({ id, olds, output }) {
|
|
197
|
+
const stack = yield* Stack;
|
|
198
|
+
const stage = yield* Stage;
|
|
199
|
+
const datasetId = output?.id ?? olds?.name;
|
|
200
|
+
if (!datasetId)
|
|
201
|
+
return undefined;
|
|
202
|
+
const existing = yield* get({ dataset_id: datasetId }).pipe(Effect.catchTag("NotFound", () => Effect.succeed(undefined)));
|
|
203
|
+
if (!existing)
|
|
204
|
+
return undefined;
|
|
205
|
+
const ownership = parseMarker(existing.description);
|
|
206
|
+
const isOurs = ownership !== undefined &&
|
|
207
|
+
ownership.stack === stack.name &&
|
|
208
|
+
ownership.stage === stage &&
|
|
209
|
+
ownership.id === id;
|
|
210
|
+
const attrs = yield* toAttrs(existing);
|
|
211
|
+
return isOurs ? attrs : Unowned(attrs);
|
|
212
|
+
}),
|
|
213
|
+
};
|
|
214
|
+
}));
|
|
215
|
+
//# sourceMappingURL=Dataset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Dataset.js","sourceRoot":"","sources":["../src/Dataset.ts"],"names":[],"mappings":"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"}
|
package/lib/Monitor.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as Axiom from "@distilled.cloud/axiom";
|
|
2
|
+
import * as Provider from "alchemy/Provider";
|
|
3
|
+
import { Resource } from "alchemy/Resource";
|
|
4
|
+
import type { Providers } from "./Providers.ts";
|
|
5
|
+
export type MonitorProps = Axiom.CreateMonitorRequest;
|
|
6
|
+
export type Monitor = Resource<"Axiom.Monitor", MonitorProps, Axiom.CreateMonitorResponse, never, Providers>;
|
|
7
|
+
/**
|
|
8
|
+
* An Axiom monitor — a scheduled APL/MPL query that evaluates on a fixed
|
|
9
|
+
* cadence and fires alerts via {@link Notifier notifiers} when its
|
|
10
|
+
* condition is met.
|
|
11
|
+
*
|
|
12
|
+
* Three monitor `type`s are supported:
|
|
13
|
+
*
|
|
14
|
+
* - **`Threshold`** — fires when an aggregate result crosses a static
|
|
15
|
+
* `threshold` (compared with `operator`).
|
|
16
|
+
* - **`MatchEvent`** — fires for every event matching the query.
|
|
17
|
+
* - **`AnomalyDetection`** — fires when results deviate from a learned
|
|
18
|
+
* baseline by more than `tolerance` over `compareDays`.
|
|
19
|
+
*
|
|
20
|
+
* Changing `type` triggers a replacement; everything else updates in place.
|
|
21
|
+
* @see https://axiom.co/docs/monitor-data/monitors
|
|
22
|
+
*
|
|
23
|
+
* ### Creating a Monitor
|
|
24
|
+
* **Example:** Threshold: alert on >100 errors per 5m
|
|
25
|
+
* ```typescript
|
|
26
|
+
* yield* Axiom.Monitor("error-rate", {
|
|
27
|
+
* name: "High error rate",
|
|
28
|
+
* description: "Fires when error count exceeds 100/5m",
|
|
29
|
+
* type: "Threshold",
|
|
30
|
+
* aplQuery: `
|
|
31
|
+
* ['my-app-traces']
|
|
32
|
+
* | where status >= 500
|
|
33
|
+
* | summarize count() by bin_auto(_time)
|
|
34
|
+
* `,
|
|
35
|
+
* operator: "Above",
|
|
36
|
+
* threshold: 100,
|
|
37
|
+
* intervalMinutes: 5,
|
|
38
|
+
* rangeMinutes: 5,
|
|
39
|
+
* alertOnNoData: false,
|
|
40
|
+
* resolvable: true,
|
|
41
|
+
* notifierIds: [slack.id, pagerduty.id],
|
|
42
|
+
* });
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* **Example:** MatchEvent: alert on every panic
|
|
46
|
+
* ```typescript
|
|
47
|
+
* yield* Axiom.Monitor("panics", {
|
|
48
|
+
* name: "Service panic",
|
|
49
|
+
* type: "MatchEvent",
|
|
50
|
+
* aplQuery: `['my-app-logs'] | where message contains "panic:"`,
|
|
51
|
+
* intervalMinutes: 1,
|
|
52
|
+
* rangeMinutes: 1,
|
|
53
|
+
* notifierIds: [pagerduty.id],
|
|
54
|
+
* });
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* **Example:** AnomalyDetection: deviation vs. last 7 days
|
|
58
|
+
* ```typescript
|
|
59
|
+
* yield* Axiom.Monitor("traffic-anomaly", {
|
|
60
|
+
* name: "Traffic anomaly",
|
|
61
|
+
* type: "AnomalyDetection",
|
|
62
|
+
* aplQuery: `['my-app-traces'] | summarize count() by bin_auto(_time)`,
|
|
63
|
+
* compareDays: 7,
|
|
64
|
+
* tolerance: 25, // %
|
|
65
|
+
* intervalMinutes: 15,
|
|
66
|
+
* rangeMinutes: 15,
|
|
67
|
+
* notifierIds: [slack.id],
|
|
68
|
+
* });
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @resource
|
|
72
|
+
* @product Monitor
|
|
73
|
+
*/
|
|
74
|
+
export declare const Monitor: import("alchemy/Resource").ResourceClass<Monitor>;
|
|
75
|
+
export declare const MonitorProvider: () => import("effect/Layer").Layer<Provider.Provider<Monitor>, never, Axiom.AxiomOpContext>;
|
|
76
|
+
//# sourceMappingURL=Monitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Monitor.d.ts","sourceRoot":"","sources":["../src/Monitor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,wBAAwB,CAAC;AAEhD,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,MAAM,YAAY,GAAG,KAAK,CAAC,oBAAoB,CAAC;AAEtD,MAAM,MAAM,OAAO,GAAG,QAAQ,CAC5B,eAAe,EACf,YAAY,EACZ,KAAK,CAAC,qBAAqB,EAC3B,KAAK,EACL,SAAS,CACV,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AACH,eAAO,MAAM,OAAO,mDAAqC,CAAC;AAE1D,eAAO,MAAM,eAAe,6FA6DzB,CAAC"}
|
package/lib/Monitor.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import * as Axiom from "@distilled.cloud/axiom";
|
|
2
|
+
import { isResolved } from "alchemy/Diff";
|
|
3
|
+
import * as Provider from "alchemy/Provider";
|
|
4
|
+
import { Resource } from "alchemy/Resource";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
/**
|
|
7
|
+
* An Axiom monitor — a scheduled APL/MPL query that evaluates on a fixed
|
|
8
|
+
* cadence and fires alerts via {@link Notifier notifiers} when its
|
|
9
|
+
* condition is met.
|
|
10
|
+
*
|
|
11
|
+
* Three monitor `type`s are supported:
|
|
12
|
+
*
|
|
13
|
+
* - **`Threshold`** — fires when an aggregate result crosses a static
|
|
14
|
+
* `threshold` (compared with `operator`).
|
|
15
|
+
* - **`MatchEvent`** — fires for every event matching the query.
|
|
16
|
+
* - **`AnomalyDetection`** — fires when results deviate from a learned
|
|
17
|
+
* baseline by more than `tolerance` over `compareDays`.
|
|
18
|
+
*
|
|
19
|
+
* Changing `type` triggers a replacement; everything else updates in place.
|
|
20
|
+
* @see https://axiom.co/docs/monitor-data/monitors
|
|
21
|
+
*
|
|
22
|
+
* ### Creating a Monitor
|
|
23
|
+
* **Example:** Threshold: alert on >100 errors per 5m
|
|
24
|
+
* ```typescript
|
|
25
|
+
* yield* Axiom.Monitor("error-rate", {
|
|
26
|
+
* name: "High error rate",
|
|
27
|
+
* description: "Fires when error count exceeds 100/5m",
|
|
28
|
+
* type: "Threshold",
|
|
29
|
+
* aplQuery: `
|
|
30
|
+
* ['my-app-traces']
|
|
31
|
+
* | where status >= 500
|
|
32
|
+
* | summarize count() by bin_auto(_time)
|
|
33
|
+
* `,
|
|
34
|
+
* operator: "Above",
|
|
35
|
+
* threshold: 100,
|
|
36
|
+
* intervalMinutes: 5,
|
|
37
|
+
* rangeMinutes: 5,
|
|
38
|
+
* alertOnNoData: false,
|
|
39
|
+
* resolvable: true,
|
|
40
|
+
* notifierIds: [slack.id, pagerduty.id],
|
|
41
|
+
* });
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* **Example:** MatchEvent: alert on every panic
|
|
45
|
+
* ```typescript
|
|
46
|
+
* yield* Axiom.Monitor("panics", {
|
|
47
|
+
* name: "Service panic",
|
|
48
|
+
* type: "MatchEvent",
|
|
49
|
+
* aplQuery: `['my-app-logs'] | where message contains "panic:"`,
|
|
50
|
+
* intervalMinutes: 1,
|
|
51
|
+
* rangeMinutes: 1,
|
|
52
|
+
* notifierIds: [pagerduty.id],
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* **Example:** AnomalyDetection: deviation vs. last 7 days
|
|
57
|
+
* ```typescript
|
|
58
|
+
* yield* Axiom.Monitor("traffic-anomaly", {
|
|
59
|
+
* name: "Traffic anomaly",
|
|
60
|
+
* type: "AnomalyDetection",
|
|
61
|
+
* aplQuery: `['my-app-traces'] | summarize count() by bin_auto(_time)`,
|
|
62
|
+
* compareDays: 7,
|
|
63
|
+
* tolerance: 25, // %
|
|
64
|
+
* intervalMinutes: 15,
|
|
65
|
+
* rangeMinutes: 15,
|
|
66
|
+
* notifierIds: [slack.id],
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @resource
|
|
71
|
+
* @product Monitor
|
|
72
|
+
*/
|
|
73
|
+
export const Monitor = Resource("Axiom.Monitor");
|
|
74
|
+
export const MonitorProvider = () => Provider.effect(Monitor, Effect.gen(function* () {
|
|
75
|
+
const create = yield* Axiom.createMonitor;
|
|
76
|
+
const update = yield* Axiom.updateMonitor;
|
|
77
|
+
const get = yield* Axiom.getMonitor;
|
|
78
|
+
const list = yield* Axiom.getMonitors;
|
|
79
|
+
const del = yield* Axiom.deleteMonitor;
|
|
80
|
+
return {
|
|
81
|
+
stables: ["id"],
|
|
82
|
+
// Enumerate every monitor in the org. Axiom exposes a single
|
|
83
|
+
// account-wide `GET /v2/monitors` collection op (no pagination) whose
|
|
84
|
+
// item schema is identical to `getMonitor`/`createMonitor`'s output, so
|
|
85
|
+
// we fetch it once and each row already carries the exact `read`
|
|
86
|
+
// Attributes shape — directly usable by `delete` with no follow-up get.
|
|
87
|
+
list: () => Effect.gen(function* () {
|
|
88
|
+
const monitors = yield* list({});
|
|
89
|
+
return monitors.map((m) => m);
|
|
90
|
+
}),
|
|
91
|
+
diff: Effect.fn(function* ({ news, output }) {
|
|
92
|
+
if (!isResolved(news))
|
|
93
|
+
return undefined;
|
|
94
|
+
if (output && news.type !== output.type) {
|
|
95
|
+
return { action: "replace" };
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
}),
|
|
99
|
+
reconcile: Effect.fn(function* ({ news, output }) {
|
|
100
|
+
// Observe — Axiom assigns the monitor id server-side, so the only
|
|
101
|
+
// handle to a previously-created monitor is the cached
|
|
102
|
+
// `output.id`. Probe for live state with that id; treat NotFound
|
|
103
|
+
// (deleted out-of-band) as "no observed state" so we converge by
|
|
104
|
+
// re-creating.
|
|
105
|
+
const observed = output?.id
|
|
106
|
+
? yield* get({ id: output.id }).pipe(Effect.catchTag("NotFound", () => Effect.succeed(undefined)))
|
|
107
|
+
: undefined;
|
|
108
|
+
// Ensure — POST mints a new monitor with a fresh id.
|
|
109
|
+
if (observed === undefined) {
|
|
110
|
+
return yield* create(news);
|
|
111
|
+
}
|
|
112
|
+
// Sync — the monitor exists; PATCH against its id with the
|
|
113
|
+
// desired props. `type` is replacement-only (handled in diff).
|
|
114
|
+
return yield* update({ ...news, id: observed.id });
|
|
115
|
+
}),
|
|
116
|
+
delete: Effect.fn(function* ({ output }) {
|
|
117
|
+
yield* del({ id: output.id }).pipe(Effect.catchTag("NotFound", () => Effect.void));
|
|
118
|
+
}),
|
|
119
|
+
read: Effect.fn(function* ({ output }) {
|
|
120
|
+
if (!output?.id)
|
|
121
|
+
return undefined;
|
|
122
|
+
return yield* get({ id: output.id }).pipe(Effect.catchTag("NotFound", () => Effect.succeed(undefined)));
|
|
123
|
+
}),
|
|
124
|
+
};
|
|
125
|
+
}));
|
|
126
|
+
//# sourceMappingURL=Monitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Monitor.js","sourceRoot":"","sources":["../src/Monitor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as Axiom from "@distilled.cloud/axiom";
|
|
2
|
+
import * as Provider from "alchemy/Provider";
|
|
3
|
+
import { Resource } from "alchemy/Resource";
|
|
4
|
+
import type { Providers } from "./Providers.ts";
|
|
5
|
+
export type NotifierProps = Axiom.CreateNotifierRequest;
|
|
6
|
+
export type Notifier = Resource<"Axiom.Notifier", NotifierProps, Axiom.CreateNotifierResponse & {
|
|
7
|
+
id: string;
|
|
8
|
+
}, never, Providers>;
|
|
9
|
+
/**
|
|
10
|
+
* An Axiom notifier — an alert destination (Slack, email, PagerDuty,
|
|
11
|
+
* Opsgenie, Discord, Microsoft Teams, generic webhook, or a fully custom
|
|
12
|
+
* webhook with templated body/headers) that {@link Monitor monitors} target
|
|
13
|
+
* via `notifierIds`. Exactly one channel under `properties` should be set.
|
|
14
|
+
* @see https://axiom.co/docs/monitor-data/notifiers
|
|
15
|
+
*
|
|
16
|
+
* ### Creating a Notifier
|
|
17
|
+
* **Example:** Slack incoming webhook
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const slack = yield* Axiom.Notifier("ops-slack", {
|
|
20
|
+
* name: "ops-channel",
|
|
21
|
+
* properties: {
|
|
22
|
+
* slack: { slackUrl: process.env.SLACK_WEBHOOK_URL! },
|
|
23
|
+
* },
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* **Example:** Email distribution list
|
|
28
|
+
* ```typescript
|
|
29
|
+
* yield* Axiom.Notifier("ops-email", {
|
|
30
|
+
* name: "ops-team",
|
|
31
|
+
* properties: { email: { emails: ["sre@example.com", "oncall@example.com"] } },
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* **Example:** PagerDuty integration
|
|
36
|
+
* ```typescript
|
|
37
|
+
* yield* Axiom.Notifier("pagerduty", {
|
|
38
|
+
* name: "primary-oncall",
|
|
39
|
+
* properties: {
|
|
40
|
+
* pagerduty: { routingKey: process.env.PAGERDUTY_ROUTING_KEY!, token: "" },
|
|
41
|
+
* },
|
|
42
|
+
* });
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* **Example:** Custom webhook with templated body
|
|
46
|
+
* ```typescript
|
|
47
|
+
* yield* Axiom.Notifier("incident-webhook", {
|
|
48
|
+
* name: "incident.io",
|
|
49
|
+
* properties: {
|
|
50
|
+
* customWebhook: {
|
|
51
|
+
* url: "https://api.incident.io/v2/alert_events",
|
|
52
|
+
* headers: { "Content-Type": "application/json" },
|
|
53
|
+
* secretHeaders: { Authorization: `Bearer ${process.env.INCIDENT_TOKEN}` },
|
|
54
|
+
* body: '{"title": "{{.Monitor.Name}}", "status": "firing"}',
|
|
55
|
+
* },
|
|
56
|
+
* },
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @resource
|
|
61
|
+
* @product Monitor
|
|
62
|
+
*/
|
|
63
|
+
export declare const Notifier: import("alchemy/Resource").ResourceClass<Notifier>;
|
|
64
|
+
export declare const NotifierProvider: () => import("effect/Layer").Layer<Provider.Provider<Notifier>, never, Axiom.AxiomOpContext>;
|
|
65
|
+
//# sourceMappingURL=Notifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Notifier.d.ts","sourceRoot":"","sources":["../src/Notifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,wBAAwB,CAAC;AAChD,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,MAAM,aAAa,GAAG,KAAK,CAAC,qBAAqB,CAAC;AAExD,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAC7B,gBAAgB,EAChB,aAAa,EACb,KAAK,CAAC,sBAAsB,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAC7C,KAAK,EACL,SAAS,CACV,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,eAAO,MAAM,QAAQ,oDAAuC,CAAC;AAE7D,eAAO,MAAM,gBAAgB,8FA+D1B,CAAC"}
|