@alchemy.run/axiom 0.0.0 → 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.
Files changed (83) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +54 -4
  3. package/lib/Annotation.d.ts +51 -0
  4. package/lib/Annotation.d.ts.map +1 -0
  5. package/lib/Annotation.js +97 -0
  6. package/lib/Annotation.js.map +1 -0
  7. package/lib/ApiToken.d.ts +64 -0
  8. package/lib/ApiToken.d.ts.map +1 -0
  9. package/lib/ApiToken.js +123 -0
  10. package/lib/ApiToken.js.map +1 -0
  11. package/lib/AuthProvider.d.ts +40 -0
  12. package/lib/AuthProvider.d.ts.map +1 -0
  13. package/lib/AuthProvider.js +99 -0
  14. package/lib/AuthProvider.js.map +1 -0
  15. package/lib/Chart.d.ts +130 -0
  16. package/lib/Chart.d.ts.map +1 -0
  17. package/lib/Chart.js +14 -0
  18. package/lib/Chart.js.map +1 -0
  19. package/lib/Contributions.d.ts +7 -0
  20. package/lib/Contributions.d.ts.map +1 -0
  21. package/lib/Contributions.js +6 -0
  22. package/lib/Contributions.js.map +1 -0
  23. package/lib/Credentials.d.ts +10 -0
  24. package/lib/Credentials.d.ts.map +1 -0
  25. package/lib/Credentials.js +30 -0
  26. package/lib/Credentials.js.map +1 -0
  27. package/lib/Dashboard.d.ts +112 -0
  28. package/lib/Dashboard.d.ts.map +1 -0
  29. package/lib/Dashboard.js +157 -0
  30. package/lib/Dashboard.js.map +1 -0
  31. package/lib/Dataset.d.ts +120 -0
  32. package/lib/Dataset.d.ts.map +1 -0
  33. package/lib/Dataset.js +215 -0
  34. package/lib/Dataset.js.map +1 -0
  35. package/lib/Monitor.d.ts +76 -0
  36. package/lib/Monitor.d.ts.map +1 -0
  37. package/lib/Monitor.js +126 -0
  38. package/lib/Monitor.js.map +1 -0
  39. package/lib/Notifier.d.ts +65 -0
  40. package/lib/Notifier.d.ts.map +1 -0
  41. package/lib/Notifier.js +112 -0
  42. package/lib/Notifier.js.map +1 -0
  43. package/lib/OtelHeaders.d.ts +9 -0
  44. package/lib/OtelHeaders.d.ts.map +1 -0
  45. package/lib/OtelHeaders.js +9 -0
  46. package/lib/OtelHeaders.js.map +1 -0
  47. package/lib/Providers.d.ts +15 -0
  48. package/lib/Providers.d.ts.map +1 -0
  49. package/lib/Providers.js +34 -0
  50. package/lib/Providers.js.map +1 -0
  51. package/lib/Telemetry.d.ts +66 -0
  52. package/lib/Telemetry.d.ts.map +1 -0
  53. package/lib/Telemetry.js +68 -0
  54. package/lib/Telemetry.js.map +1 -0
  55. package/lib/View.d.ts +57 -0
  56. package/lib/View.d.ts.map +1 -0
  57. package/lib/View.js +100 -0
  58. package/lib/View.js.map +1 -0
  59. package/lib/VirtualField.d.ts +44 -0
  60. package/lib/VirtualField.d.ts.map +1 -0
  61. package/lib/VirtualField.js +97 -0
  62. package/lib/VirtualField.js.map +1 -0
  63. package/lib/index.d.ts +14 -0
  64. package/lib/index.d.ts.map +1 -0
  65. package/lib/index.js +14 -0
  66. package/lib/index.js.map +1 -0
  67. package/package.json +53 -3
  68. package/src/Annotation.ts +121 -0
  69. package/src/ApiToken.ts +148 -0
  70. package/src/AuthProvider.ts +135 -0
  71. package/src/Chart.ts +156 -0
  72. package/src/Contributions.ts +9 -0
  73. package/src/Credentials.ts +68 -0
  74. package/src/Dashboard.ts +192 -0
  75. package/src/Dataset.ts +326 -0
  76. package/src/Monitor.ts +148 -0
  77. package/src/Notifier.ts +136 -0
  78. package/src/OtelHeaders.ts +9 -0
  79. package/src/Providers.ts +60 -0
  80. package/src/Telemetry.ts +116 -0
  81. package/src/View.ts +130 -0
  82. package/src/VirtualField.ts +123 -0
  83. package/src/index.ts +13 -0
package/src/Chart.ts ADDED
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Typed view of the Axiom dashboard chart-JSON subset.
3
+ *
4
+ * Axiom's `CreateDashboardInput` declares
5
+ * `dashboard.charts: ReadonlyArray<unknown>` (see
6
+ * `@distilled.cloud/axiom/.../v2/createDashboard.ts`); these
7
+ * interfaces give callers compile-time safety when building the
8
+ * payload by hand.
9
+ *
10
+ * `chart.id` is a free-form string the author picks — Axiom
11
+ * doesn't validate the format. It joins to the matching
12
+ * `LayoutCell.i` in `dashboard.layout`.
13
+ */
14
+
15
+ /**
16
+ * Axiom-validated chart kinds. Discovered by probing
17
+ * `POST /v2/dashboards`: anything else returns
18
+ * `dashboard validation failed at [charts N type]: Invalid input`.
19
+ */
20
+ export type ChartKind =
21
+ | "TimeSeries"
22
+ | "Table"
23
+ | "Pie"
24
+ | "Statistic"
25
+ | "Heatmap"
26
+ | "LogStream"
27
+ | "Note"
28
+ | "SmartFilter";
29
+
30
+ /**
31
+ * Minimum chart payload Axiom's `POST /v2/dashboards` accepts.
32
+ * Any unknown keys (e.g. `dataset`, `description`) trigger
33
+ * `Unrecognized keys: "<name>"`, so this struct is intentionally
34
+ * narrow. The dataset is implicit via the APL query.
35
+ */
36
+ export interface BaseChart {
37
+ readonly id: string;
38
+ readonly name: string;
39
+ readonly type: ChartKind;
40
+ readonly query: { readonly apl: string };
41
+ }
42
+
43
+ export interface TimeSeriesChart extends BaseChart {
44
+ readonly type: "TimeSeries";
45
+ }
46
+
47
+ export interface TableChart extends BaseChart {
48
+ readonly type: "Table";
49
+ }
50
+
51
+ export interface PieChart extends BaseChart {
52
+ readonly type: "Pie";
53
+ }
54
+
55
+ export interface StatisticChart extends BaseChart {
56
+ readonly type: "Statistic";
57
+ }
58
+
59
+ export interface HeatmapChart extends BaseChart {
60
+ readonly type: "Heatmap";
61
+ }
62
+
63
+ export interface LogStreamChart extends BaseChart {
64
+ readonly type: "LogStream";
65
+ }
66
+
67
+ /**
68
+ * A free-form markdown note. Unlike data charts, `Note` has no `query`
69
+ * or `name` — Axiom's payload is strictly `{ id, type, text, variant? }`.
70
+ */
71
+ export interface NoteChart {
72
+ readonly id: string;
73
+ readonly type: "Note";
74
+ /** Markdown body rendered in the note cell. */
75
+ readonly text: string;
76
+ readonly variant?: "default";
77
+ }
78
+
79
+ /**
80
+ * A dashboard filter bar (Axiom calls it `SmartFilter` on the wire,
81
+ * `Filter bar` in the UI). Holds one or more filters whose IDs can
82
+ * be referenced from other charts' APL via
83
+ * `declare query_parameters (<filter-id>:string = "")`.
84
+ *
85
+ * Unlike data charts, `SmartFilter` has no top-level `query` field —
86
+ * each filter inside `filters` may carry its own option-source APL.
87
+ */
88
+ export interface SmartFilterChart {
89
+ readonly id: string;
90
+ readonly type: "SmartFilter";
91
+ /** Optional display name for the filter bar element. */
92
+ readonly name?: string;
93
+ readonly filters: readonly SmartFilter[];
94
+ }
95
+
96
+ /**
97
+ * One filter inside a {@link SmartFilterChart}. Either a free-text
98
+ * `search` filter or a dropdown `select` filter. Select filters draw
99
+ * their options from either an inline `list` or an APL `query`.
100
+ */
101
+ export type SmartFilter = SmartFilterSearch | SmartFilterSelect;
102
+
103
+ export interface SmartFilterSearch {
104
+ readonly id: string;
105
+ readonly type: "search";
106
+ readonly name?: string;
107
+ readonly active?: boolean;
108
+ }
109
+
110
+ export interface SmartFilterSelect {
111
+ readonly id: string;
112
+ readonly type: "select";
113
+ readonly name?: string;
114
+ readonly active?: boolean;
115
+ /** `"list"` for inline `options`, `"apl"` for a query-driven dropdown. */
116
+ readonly selectType?: "list" | "apl";
117
+ /**
118
+ * APL query that returns rows shaped `{ key, value }`. Axiom shows
119
+ * `key` in the dropdown and substitutes `value` into chart queries.
120
+ */
121
+ readonly apl?: { readonly apl: string };
122
+ /** Inline key/value options when `selectType === "list"`. */
123
+ readonly options?: readonly {
124
+ readonly id?: string;
125
+ readonly key: string;
126
+ readonly value: string;
127
+ readonly default?: boolean;
128
+ }[];
129
+ }
130
+
131
+ export type Chart =
132
+ | TimeSeriesChart
133
+ | TableChart
134
+ | PieChart
135
+ | StatisticChart
136
+ | HeatmapChart
137
+ | LogStreamChart
138
+ | NoteChart
139
+ | SmartFilterChart;
140
+
141
+ /**
142
+ * Mirrors Axiom's `CreateDashboardInput.dashboard.layout` cell schema.
143
+ * `i` references a chart by its `id`.
144
+ */
145
+ export interface LayoutCell {
146
+ readonly i: string;
147
+ readonly x: number;
148
+ readonly y: number | null;
149
+ readonly w: number;
150
+ readonly h: number;
151
+ readonly minW?: number;
152
+ readonly minH?: number;
153
+ readonly maxW?: number;
154
+ readonly maxH?: number;
155
+ readonly static?: boolean;
156
+ }
@@ -0,0 +1,9 @@
1
+ import * as Contributions from "alchemy/Contributions";
2
+
3
+ export default Contributions.make({
4
+ name: "axiom",
5
+ auth: Contributions.lazy(
6
+ () => import("./AuthProvider.ts"),
7
+ (module) => module.AxiomAuth,
8
+ ),
9
+ });
@@ -0,0 +1,68 @@
1
+ import { Credentials } from "@distilled.cloud/axiom/Credentials";
2
+ import { ConfigError } from "@distilled.cloud/core/errors";
3
+ import {
4
+ deferUntilFirstUse,
5
+ orDieCredentialsUnavailable,
6
+ resolveProviderConfig,
7
+ } from "alchemy/Auth/Resolve";
8
+ import * as Effect from "effect/Effect";
9
+ import * as Layer from "effect/Layer";
10
+ import * as Match from "effect/Match";
11
+ import {
12
+ AXIOM_AUTH_PROVIDER_NAME,
13
+ type AxiomAuthConfig,
14
+ type AxiomResolvedCredentials,
15
+ } from "./AuthProvider.ts";
16
+
17
+ export {
18
+ Credentials,
19
+ CredentialsFromEnv,
20
+ DEFAULT_API_BASE_URL,
21
+ } from "@distilled.cloud/axiom/Credentials";
22
+
23
+ /**
24
+ * Build a `Credentials` layer that resolves Axiom credentials via the Alchemy
25
+ * AuthProvider using the configured profile (defaults to "default", overridable
26
+ * with the current Alchemy profile).
27
+ */
28
+ export const fromAuthProvider = () =>
29
+ Layer.effect(
30
+ Credentials,
31
+ Effect.gen(function* () {
32
+ // Defer profile lookup and credential resolution until first use, so
33
+ // building the provider layers never requires a configured profile.
34
+ const resolve = yield* resolveProviderConfig<AxiomAuthConfig, AxiomResolvedCredentials>(
35
+ AXIOM_AUTH_PROVIDER_NAME,
36
+ ).pipe(
37
+ Effect.flatMap(({ profileName, resolve }) =>
38
+ resolve.pipe(
39
+ Effect.map((creds) =>
40
+ Match.value(creds).pipe(
41
+ Match.when({ type: "apiToken" }, (c) => ({
42
+ apiKey: c.apiToken,
43
+ apiBaseUrl: c.apiBaseUrl,
44
+ orgId: c.orgId,
45
+ })),
46
+ Match.when({ type: "pat" }, (c) => ({
47
+ apiKey: c.apiToken,
48
+ apiBaseUrl: c.apiBaseUrl,
49
+ orgId: c.orgId,
50
+ })),
51
+ Match.exhaustive,
52
+ ),
53
+ ),
54
+ Effect.mapError((e) =>
55
+ ConfigError.make({
56
+ message: `Failed to resolve Axiom credentials from ${profileName === undefined ? "the CI environment" : `profile '${profileName}'`}: ${(e as { message?: string }).message ?? String(e)}`,
57
+ }),
58
+ ),
59
+ ),
60
+ ),
61
+ deferUntilFirstUse,
62
+ );
63
+ return yield* resolve.pipe(
64
+ orDieCredentialsUnavailable(AXIOM_AUTH_PROVIDER_NAME),
65
+ Effect.cached,
66
+ );
67
+ }),
68
+ );
@@ -0,0 +1,192 @@
1
+ import * as Axiom from "@distilled.cloud/axiom";
2
+ import * as Provider from "alchemy/Provider";
3
+ import { Resource } from "alchemy/Resource";
4
+ import * as Effect from "effect/Effect";
5
+ import type { Providers } from "./Providers.ts";
6
+
7
+ /** Dashboard input. */
8
+ export type DashboardProps = Axiom.CreateDashboardRequest;
9
+
10
+ export type Dashboard = Resource<
11
+ "Axiom.Dashboard",
12
+ DashboardProps,
13
+ {
14
+ /** Stable Axiom dashboard `uid` (used as the path identifier). */
15
+ uid: string;
16
+ id: string;
17
+ createdAt: string;
18
+ createdBy: string;
19
+ updatedAt: string;
20
+ updatedBy: string;
21
+ /** The full dashboard document as returned by Axiom. */
22
+ dashboard: Axiom.DashboardWriteResponse["dashboard"]["dashboard"];
23
+ },
24
+ never,
25
+ Providers
26
+ >;
27
+
28
+ /**
29
+ * An Axiom dashboard — a named, layout-driven collection of charts. Each
30
+ * dashboard takes a full document (`charts` + `layout` array of grid cells +
31
+ * `timeWindow` + `refreshTime`) at version `schemaVersion: 2`.
32
+ *
33
+ * Charts are typed via {@link Chart} (a discriminated union over the
34
+ * Axiom-validated chart kinds — `TimeSeries`, `Table`, `Pie`, `Statistic`,
35
+ * `Heatmap`, `LogStream`, `Note`). The `id` on each chart is a free-form
36
+ * string the author picks; layout cells join via `LayoutCell.i`.
37
+ *
38
+ * The path identifier is `uid` (auto-assigned by Axiom). `id` is also
39
+ * exposed as an output but the API uses `uid` everywhere.
40
+ *
41
+ * Notes from probing `POST /v2/dashboards`:
42
+ *
43
+ * - Relative time windows must use the `qr-now-{duration}` form (e.g.
44
+ * `"qr-now-7d"` / `"qr-now"`); plain `"now-7d"` is rejected.
45
+ * - When authenticating with an API token, `dashboard.owner` must be `""`
46
+ * (Axiom rewrites this to the org-shared `X-AXIOM-EVERYONE`); per-user
47
+ * "private" dashboards aren't allowed for tokens.
48
+ * - The chart payload is strict: only `id`, `name`, `type`, `query`. Extra
49
+ * keys (e.g. `dataset`, `description`) trigger
50
+ * `Unrecognized keys: "<name>"`.
51
+ * @see https://axiom.co/docs/query-data/dashboards
52
+ *
53
+ * ### Creating a Dashboard
54
+ * **Example:** Minimal empty dashboard
55
+ * ```typescript
56
+ * yield* Axiom.Dashboard("ops", {
57
+ * dashboard: {
58
+ * name: "Ops Overview",
59
+ * owner: "", // org-shared (required for API tokens)
60
+ * description: "Top-level service health",
61
+ * charts: [],
62
+ * layout: [],
63
+ * refreshTime: 60, // seconds: 15 | 60 | 300
64
+ * schemaVersion: 2,
65
+ * timeWindowStart: "qr-now-1h",
66
+ * timeWindowEnd: "qr-now",
67
+ * },
68
+ * });
69
+ * ```
70
+ *
71
+ * **Example:** One-chart dashboard
72
+ * ```typescript
73
+ * import type { Chart, LayoutCell } from "@alchemy.run/axiom";
74
+ *
75
+ * const errors: Chart = {
76
+ * id: "errors-5m",
77
+ * name: "5xx errors / 5m",
78
+ * type: "TimeSeries",
79
+ * query: {
80
+ * apl: `['my-app-traces']
81
+ * | where status >= 500
82
+ * | summarize count() by bin_auto(_time)`,
83
+ * },
84
+ * };
85
+ *
86
+ * yield* Axiom.Dashboard("errors", {
87
+ * dashboard: {
88
+ * name: "Errors",
89
+ * owner: "",
90
+ * refreshTime: 60,
91
+ * schemaVersion: 2,
92
+ * timeWindowStart: "qr-now-24h",
93
+ * timeWindowEnd: "qr-now",
94
+ * charts: [errors],
95
+ * layout: [{ i: errors.id, x: 0, y: 0, w: 12, h: 6 } satisfies LayoutCell],
96
+ * },
97
+ * });
98
+ * ```
99
+ *
100
+ * **Example:** Compare to last 24h
101
+ * ```typescript
102
+ * yield* Axiom.Dashboard("compare", {
103
+ * dashboard: {
104
+ * name: "Compare vs yesterday",
105
+ * owner: "",
106
+ * refreshTime: 300,
107
+ * schemaVersion: 2,
108
+ * timeWindowStart: "qr-now-1h",
109
+ * timeWindowEnd: "qr-now",
110
+ * against: "-1d", // overlay the same window from 24h ago
111
+ * charts: [],
112
+ * layout: [],
113
+ * },
114
+ * });
115
+ * ```
116
+ *
117
+ * @resource
118
+ * @product Dashboard
119
+ */
120
+ export const Dashboard = Resource<Dashboard>("Axiom.Dashboard");
121
+
122
+ export const DashboardProvider = () =>
123
+ Provider.effect(
124
+ Dashboard,
125
+ Effect.gen(function* () {
126
+ const create = yield* Axiom.createDashboard;
127
+ const update = yield* Axiom.updateDashboard;
128
+ const get = yield* Axiom.getDashboard;
129
+ const del = yield* Axiom.deleteDashboard;
130
+ const listAll = yield* Axiom.listDashboards;
131
+
132
+ const toAttrsFromCreate = (envelope: Axiom.DashboardWriteResponse) => ({
133
+ uid: envelope.dashboard.uid,
134
+ id: envelope.dashboard.id,
135
+ createdAt: envelope.dashboard.createdAt,
136
+ createdBy: envelope.dashboard.createdBy,
137
+ updatedAt: envelope.dashboard.updatedAt,
138
+ updatedBy: envelope.dashboard.updatedBy,
139
+ dashboard: envelope.dashboard.dashboard,
140
+ });
141
+ const toAttrsFromGet = (current: Axiom.DashboardResource) => ({
142
+ uid: current.uid,
143
+ id: current.id,
144
+ createdAt: current.createdAt,
145
+ createdBy: current.createdBy,
146
+ updatedAt: current.updatedAt,
147
+ updatedBy: current.updatedBy,
148
+ dashboard: current.dashboard,
149
+ });
150
+
151
+ return {
152
+ stables: ["uid", "id", "createdAt", "createdBy"],
153
+ reconcile: Effect.fn(function* ({ news, output }) {
154
+ // Observe — `uid` is server-assigned at create time. We probe
155
+ // Axiom for the dashboard via the cached uid; treat NotFound
156
+ // (deleted out-of-band) as "no observed state" so we converge
157
+ // by re-creating.
158
+ const observed = output?.uid
159
+ ? yield* get({ uid: output.uid }).pipe(
160
+ Effect.catchTag("NotFound", () => Effect.succeed(undefined)),
161
+ )
162
+ : undefined;
163
+
164
+ // Ensure — POST mints a new dashboard with a fresh uid.
165
+ if (observed === undefined) {
166
+ return toAttrsFromCreate(yield* create(news));
167
+ }
168
+
169
+ // Sync — `overwrite: true` short-circuits Axiom's optimistic-
170
+ // concurrency check (otherwise the API requires the caller to
171
+ // echo back the server-side `version`, which we don't track).
172
+ return toAttrsFromCreate(yield* update({ ...news, uid: observed.uid, overwrite: true }));
173
+ }),
174
+ delete: Effect.fn(function* ({ output }) {
175
+ yield* del({ uid: output.uid }).pipe(Effect.catchTag("NotFound", () => Effect.void));
176
+ }),
177
+ read: Effect.fn(function* ({ output }) {
178
+ if (!output?.uid) return undefined;
179
+ return yield* get({ uid: output.uid }).pipe(
180
+ Effect.map(toAttrsFromGet),
181
+ Effect.catchTag("NotFound", () => Effect.succeed(undefined)),
182
+ );
183
+ }),
184
+ // Enumerate every dashboard visible to the caller. `GET /v2/dashboards`
185
+ // returns the full collection in one response (no pagination wrapper);
186
+ // each item carries the same shape as `getDashboard`, so we map through
187
+ // the shared `toAttrsFromGet` hydrator to produce the exact `read`
188
+ // Attributes shape.
189
+ list: () => listAll({}).pipe(Effect.map((dashboards) => dashboards.map(toAttrsFromGet))),
190
+ };
191
+ }),
192
+ );