alchemy 2.0.0-beta.17 → 2.0.0-beta.19

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 (182) hide show
  1. package/bin/alchemy.js +428 -80
  2. package/bin/alchemy.js.map +1 -1
  3. package/bin/alchemy.ts +2 -0
  4. package/bin/commands/_shared.ts +34 -0
  5. package/bin/commands/bootstrap.ts +10 -3
  6. package/bin/commands/deploy.ts +23 -3
  7. package/bin/commands/login.ts +18 -3
  8. package/bin/commands/logs.ts +11 -2
  9. package/bin/commands/profile.ts +11 -5
  10. package/bin/commands/state.ts +21 -13
  11. package/bin/commands/tail.ts +10 -2
  12. package/lib/AWS/CloudFront/CachePolicy.d.ts +106 -0
  13. package/lib/AWS/CloudFront/CachePolicy.d.ts.map +1 -0
  14. package/lib/AWS/CloudFront/CachePolicy.js +147 -0
  15. package/lib/AWS/CloudFront/CachePolicy.js.map +1 -0
  16. package/lib/AWS/CloudFront/KeyGroup.d.ts +72 -0
  17. package/lib/AWS/CloudFront/KeyGroup.d.ts.map +1 -0
  18. package/lib/AWS/CloudFront/KeyGroup.js +136 -0
  19. package/lib/AWS/CloudFront/KeyGroup.js.map +1 -0
  20. package/lib/AWS/CloudFront/OriginRequestPolicy.d.ts +84 -0
  21. package/lib/AWS/CloudFront/OriginRequestPolicy.d.ts.map +1 -0
  22. package/lib/AWS/CloudFront/OriginRequestPolicy.js +139 -0
  23. package/lib/AWS/CloudFront/OriginRequestPolicy.js.map +1 -0
  24. package/lib/AWS/CloudFront/PublicKey.d.ts +72 -0
  25. package/lib/AWS/CloudFront/PublicKey.d.ts.map +1 -0
  26. package/lib/AWS/CloudFront/PublicKey.js +137 -0
  27. package/lib/AWS/CloudFront/PublicKey.js.map +1 -0
  28. package/lib/AWS/CloudFront/ResponseHeadersPolicy.d.ts +114 -0
  29. package/lib/AWS/CloudFront/ResponseHeadersPolicy.d.ts.map +1 -0
  30. package/lib/AWS/CloudFront/ResponseHeadersPolicy.js +162 -0
  31. package/lib/AWS/CloudFront/ResponseHeadersPolicy.js.map +1 -0
  32. package/lib/AWS/CloudFront/index.d.ts +5 -0
  33. package/lib/AWS/CloudFront/index.d.ts.map +1 -1
  34. package/lib/AWS/CloudFront/index.js +5 -0
  35. package/lib/AWS/CloudFront/index.js.map +1 -1
  36. package/lib/AWS/ECS/CapacityProvider.d.ts +102 -0
  37. package/lib/AWS/ECS/CapacityProvider.d.ts.map +1 -0
  38. package/lib/AWS/ECS/CapacityProvider.js +217 -0
  39. package/lib/AWS/ECS/CapacityProvider.js.map +1 -0
  40. package/lib/AWS/ECS/index.d.ts +1 -0
  41. package/lib/AWS/ECS/index.d.ts.map +1 -1
  42. package/lib/AWS/ECS/index.js +1 -0
  43. package/lib/AWS/ECS/index.js.map +1 -1
  44. package/lib/AWS/Providers.d.ts.map +1 -1
  45. package/lib/AWS/Providers.js +7 -1
  46. package/lib/AWS/Providers.js.map +1 -1
  47. package/lib/Agent/lsp/client.d.ts +2 -2
  48. package/lib/Apply.d.ts.map +1 -1
  49. package/lib/Apply.js +41 -9
  50. package/lib/Apply.js.map +1 -1
  51. package/lib/Axiom/Annotation.d.ts +49 -0
  52. package/lib/Axiom/Annotation.d.ts.map +1 -0
  53. package/lib/Axiom/Annotation.js +74 -0
  54. package/lib/Axiom/Annotation.js.map +1 -0
  55. package/lib/Axiom/ApiToken.d.ts +62 -0
  56. package/lib/Axiom/ApiToken.d.ts.map +1 -0
  57. package/lib/Axiom/ApiToken.js +98 -0
  58. package/lib/Axiom/ApiToken.js.map +1 -0
  59. package/lib/Axiom/AuthProvider.d.ts +48 -0
  60. package/lib/Axiom/AuthProvider.d.ts.map +1 -0
  61. package/lib/Axiom/AuthProvider.js +177 -0
  62. package/lib/Axiom/AuthProvider.js.map +1 -0
  63. package/lib/Axiom/Chart.d.ts +72 -0
  64. package/lib/Axiom/Chart.d.ts.map +1 -0
  65. package/lib/Axiom/Chart.js +14 -0
  66. package/lib/Axiom/Chart.js.map +1 -0
  67. package/lib/Axiom/Credentials.d.ts +12 -0
  68. package/lib/Axiom/Credentials.d.ts.map +1 -0
  69. package/lib/Axiom/Credentials.js +34 -0
  70. package/lib/Axiom/Credentials.js.map +1 -0
  71. package/lib/Axiom/Dashboard.d.ts +124 -0
  72. package/lib/Axiom/Dashboard.d.ts.map +1 -0
  73. package/lib/Axiom/Dashboard.js +140 -0
  74. package/lib/Axiom/Dashboard.js.map +1 -0
  75. package/lib/Axiom/Dataset.d.ts +101 -0
  76. package/lib/Axiom/Dataset.d.ts.map +1 -0
  77. package/lib/Axiom/Dataset.js +183 -0
  78. package/lib/Axiom/Dataset.js.map +1 -0
  79. package/lib/Axiom/Monitor.d.ts +74 -0
  80. package/lib/Axiom/Monitor.d.ts.map +1 -0
  81. package/lib/Axiom/Monitor.js +103 -0
  82. package/lib/Axiom/Monitor.js.map +1 -0
  83. package/lib/Axiom/Notifier.d.ts +63 -0
  84. package/lib/Axiom/Notifier.d.ts.map +1 -0
  85. package/lib/Axiom/Notifier.js +83 -0
  86. package/lib/Axiom/Notifier.js.map +1 -0
  87. package/lib/Axiom/Providers.d.ts +15 -0
  88. package/lib/Axiom/Providers.d.ts.map +1 -0
  89. package/lib/Axiom/Providers.js +32 -0
  90. package/lib/Axiom/Providers.js.map +1 -0
  91. package/lib/Axiom/View.d.ts +55 -0
  92. package/lib/Axiom/View.d.ts.map +1 -0
  93. package/lib/Axiom/View.js +79 -0
  94. package/lib/Axiom/View.js.map +1 -0
  95. package/lib/Axiom/VirtualField.d.ts +42 -0
  96. package/lib/Axiom/VirtualField.d.ts.map +1 -0
  97. package/lib/Axiom/VirtualField.js +71 -0
  98. package/lib/Axiom/VirtualField.js.map +1 -0
  99. package/lib/Axiom/index.d.ts +13 -0
  100. package/lib/Axiom/index.d.ts.map +1 -0
  101. package/lib/Axiom/index.js +13 -0
  102. package/lib/Axiom/index.js.map +1 -0
  103. package/lib/Bundle/Bundle.d.ts +20 -2
  104. package/lib/Bundle/Bundle.d.ts.map +1 -1
  105. package/lib/Bundle/Bundle.js +18 -3
  106. package/lib/Bundle/Bundle.js.map +1 -1
  107. package/lib/Bundle/PurePlugin.d.ts +109 -0
  108. package/lib/Bundle/PurePlugin.d.ts.map +1 -0
  109. package/lib/Bundle/PurePlugin.js +469 -0
  110. package/lib/Bundle/PurePlugin.js.map +1 -0
  111. package/lib/Bundle/index.d.ts +1 -0
  112. package/lib/Bundle/index.d.ts.map +1 -1
  113. package/lib/Bundle/index.js +1 -0
  114. package/lib/Bundle/index.js.map +1 -1
  115. package/lib/Cloudflare/Workers/Worker.d.ts +12 -0
  116. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  117. package/lib/Cloudflare/Workers/Worker.js +19 -4
  118. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  119. package/lib/GitHub/Secrets.d.ts +45 -0
  120. package/lib/GitHub/Secrets.d.ts.map +1 -0
  121. package/lib/GitHub/Secrets.js +30 -0
  122. package/lib/GitHub/Secrets.js.map +1 -0
  123. package/lib/GitHub/index.d.ts +1 -0
  124. package/lib/GitHub/index.d.ts.map +1 -1
  125. package/lib/GitHub/index.js +1 -0
  126. package/lib/GitHub/index.js.map +1 -1
  127. package/lib/Output.d.ts.map +1 -1
  128. package/lib/Output.js +1 -1
  129. package/lib/Output.js.map +1 -1
  130. package/lib/Plan.d.ts.map +1 -1
  131. package/lib/Plan.js +10 -4
  132. package/lib/Plan.js.map +1 -1
  133. package/lib/Resource.d.ts +2 -2
  134. package/lib/Resource.d.ts.map +1 -1
  135. package/lib/Telemetry/Attributes.d.ts +43 -0
  136. package/lib/Telemetry/Attributes.d.ts.map +1 -0
  137. package/lib/Telemetry/Attributes.js +144 -0
  138. package/lib/Telemetry/Attributes.js.map +1 -0
  139. package/lib/Telemetry/Layer.d.ts +14 -0
  140. package/lib/Telemetry/Layer.d.ts.map +1 -0
  141. package/lib/Telemetry/Layer.js +50 -0
  142. package/lib/Telemetry/Layer.js.map +1 -0
  143. package/lib/Telemetry/Metrics.d.ts +34 -0
  144. package/lib/Telemetry/Metrics.d.ts.map +1 -0
  145. package/lib/Telemetry/Metrics.js +57 -0
  146. package/lib/Telemetry/Metrics.js.map +1 -0
  147. package/package.json +12 -1
  148. package/src/AWS/CloudFront/CachePolicy.ts +271 -0
  149. package/src/AWS/CloudFront/KeyGroup.ts +233 -0
  150. package/src/AWS/CloudFront/OriginRequestPolicy.ts +256 -0
  151. package/src/AWS/CloudFront/PublicKey.ts +245 -0
  152. package/src/AWS/CloudFront/ResponseHeadersPolicy.ts +308 -0
  153. package/src/AWS/CloudFront/index.ts +11 -0
  154. package/src/AWS/ECS/CapacityProvider.ts +336 -0
  155. package/src/AWS/ECS/index.ts +4 -0
  156. package/src/AWS/Providers.ts +12 -0
  157. package/src/Apply.ts +118 -9
  158. package/src/Axiom/Annotation.ts +95 -0
  159. package/src/Axiom/ApiToken.ts +125 -0
  160. package/src/Axiom/AuthProvider.ts +341 -0
  161. package/src/Axiom/Chart.ts +94 -0
  162. package/src/Axiom/Credentials.ts +70 -0
  163. package/src/Axiom/Dashboard.ts +192 -0
  164. package/src/Axiom/Dataset.ts +291 -0
  165. package/src/Axiom/Monitor.ts +122 -0
  166. package/src/Axiom/Notifier.ts +104 -0
  167. package/src/Axiom/Providers.ts +58 -0
  168. package/src/Axiom/View.ts +106 -0
  169. package/src/Axiom/VirtualField.ts +90 -0
  170. package/src/Axiom/index.ts +12 -0
  171. package/src/Bundle/Bundle.ts +39 -1
  172. package/src/Bundle/PurePlugin.ts +556 -0
  173. package/src/Bundle/index.ts +1 -0
  174. package/src/Cloudflare/Workers/Worker.ts +37 -6
  175. package/src/GitHub/Secrets.ts +66 -0
  176. package/src/GitHub/index.ts +1 -0
  177. package/src/Output.ts +2 -1
  178. package/src/Plan.ts +13 -4
  179. package/src/Resource.ts +2 -2
  180. package/src/Telemetry/Attributes.ts +216 -0
  181. package/src/Telemetry/Layer.ts +65 -0
  182. package/src/Telemetry/Metrics.ts +101 -0
@@ -49,13 +49,18 @@ export const providers = () =>
49
49
  AutoScaling.AutoScalingGroup,
50
50
  AutoScaling.LaunchTemplate,
51
51
  AutoScaling.ScalingPolicy,
52
+ CloudFront.CachePolicy,
52
53
  CloudFront.Distribution,
53
54
  CloudFront.Function,
54
55
  CloudFront.Invalidation,
56
+ CloudFront.KeyGroup,
55
57
  CloudFront.KeyValueStore,
56
58
  CloudFront.KvEntries,
57
59
  CloudFront.KvRoutesUpdate,
58
60
  CloudFront.OriginAccessControl,
61
+ CloudFront.OriginRequestPolicy,
62
+ CloudFront.PublicKey,
63
+ CloudFront.ResponseHeadersPolicy,
59
64
  CloudWatch.Alarm,
60
65
  CloudWatch.AlarmMuteRule,
61
66
  CloudWatch.AnomalyDetector,
@@ -124,6 +129,7 @@ export const providers = () =>
124
129
  EC2.Vpc,
125
130
  EC2.VpcEndpoint,
126
131
  ECR.Repository,
132
+ ECS.CapacityProvider,
127
133
  ECS.Cluster,
128
134
  ECS.DescribeTasksPolicy,
129
135
  ECS.ListTasksPolicy,
@@ -282,13 +288,18 @@ export const providers = () =>
282
288
  AutoScaling.AutoScalingGroupProvider(),
283
289
  AutoScaling.LaunchTemplateProvider(),
284
290
  AutoScaling.ScalingPolicyProvider(),
291
+ CloudFront.CachePolicyProvider(),
285
292
  CloudFront.DistributionProvider(),
286
293
  CloudFront.FunctionProvider(),
287
294
  CloudFront.InvalidationProvider(),
295
+ CloudFront.KeyGroupProvider(),
288
296
  CloudFront.KeyValueStoreProvider(),
289
297
  CloudFront.KvEntriesProvider(),
290
298
  CloudFront.KvRoutesUpdateProvider(),
291
299
  CloudFront.OriginAccessControlProvider(),
300
+ CloudFront.OriginRequestPolicyProvider(),
301
+ CloudFront.PublicKeyProvider(),
302
+ CloudFront.ResponseHeadersPolicyProvider(),
292
303
  CloudWatch.AlarmMuteRuleProvider(),
293
304
  CloudWatch.AlarmProvider(),
294
305
  CloudWatch.AnomalyDetectorProvider(),
@@ -357,6 +368,7 @@ export const providers = () =>
357
368
  EC2.VpcEndpointProvider(),
358
369
  EC2.VpcProvider(),
359
370
  ECR.RepositoryProvider(),
371
+ ECS.CapacityProviderProvider(),
360
372
  ECS.ClusterProvider(),
361
373
  ECS.DescribeTasksPolicyLive,
362
374
  ECS.ListTasksPolicyLive,
package/src/Apply.ts CHANGED
@@ -25,6 +25,7 @@ import { findProviderByType } from "./Provider.ts";
25
25
  import type { ResourceBinding } from "./Resource.ts";
26
26
  import { Stack } from "./Stack.ts";
27
27
  import { Stage } from "./Stage.ts";
28
+ import { recordResourceOp, type ResourceOp } from "./Telemetry/Metrics.ts";
28
29
  import {
29
30
  type CreatedResourceState,
30
31
  type CreatingResourceState,
@@ -81,6 +82,41 @@ const provideLifecycleScope =
81
82
  ),
82
83
  ) as Effect.Effect<A, E, Exclude<R, InstanceId | Artifacts>>;
83
84
 
85
+ /**
86
+ * Instruments a single provider lifecycle call with an OTel span
87
+ * (`provider.<op>`), the resource counter / duration histogram, and the
88
+ * scoped artifacts/instance services normally supplied by
89
+ * {@link provideLifecycleScope}.
90
+ *
91
+ * This is the only call site through which provider lifecycle methods
92
+ * are dispatched, so wrapping it here gives us a fully-instrumented
93
+ * toolchain without touching any individual provider implementation.
94
+ */
95
+ const instrumentLifecycle =
96
+ (
97
+ op: ResourceOp,
98
+ fqn: string,
99
+ resourceType: string,
100
+ logicalId: string,
101
+ instanceId: string,
102
+ ) =>
103
+ <A, E, R>(
104
+ effect: Effect.Effect<A, E, R>,
105
+ ): Effect.Effect<A, E, Exclude<R, InstanceId | Artifacts>> =>
106
+ effect.pipe(
107
+ provideLifecycleScope(fqn, instanceId),
108
+ recordResourceOp(resourceType, op),
109
+ Effect.withSpan(`provider.${op}`, {
110
+ attributes: {
111
+ "alchemy.resource.fqn": fqn,
112
+ "alchemy.resource.type": resourceType,
113
+ "alchemy.resource.logical_id": logicalId,
114
+ "alchemy.resource.instance_id": instanceId,
115
+ "alchemy.resource.op": op,
116
+ },
117
+ }),
118
+ );
119
+
84
120
  export const apply = <P extends Plan>(
85
121
  plan: P,
86
122
  ): Effect.Effect<
@@ -149,7 +185,15 @@ export const apply = <P extends Plan>(
149
185
  Object.entries(tracker).map(([fqn, t]) => [fqn, t.output]),
150
186
  );
151
187
  return yield* Output.evaluate(plan.output, outputs);
152
- }).pipe(ensureArtifactStore);
188
+ }).pipe(
189
+ ensureArtifactStore,
190
+ Effect.withSpan("apply", {
191
+ attributes: {
192
+ "alchemy.resources.count": Object.keys(plan.resources).length,
193
+ "alchemy.deletions.count": Object.keys(plan.deletions).length,
194
+ },
195
+ }),
196
+ );
153
197
 
154
198
  // ── Phase 1: concurrent initial execution ──────────────────────────────────
155
199
  //
@@ -411,7 +455,15 @@ const executeNode = (
411
455
  instanceId,
412
456
  bindings: excludeDeletedBindings(node.bindings),
413
457
  })
414
- .pipe(provideLifecycleScope(fqn, instanceId));
458
+ .pipe(
459
+ instrumentLifecycle(
460
+ "precreate",
461
+ fqn,
462
+ node.resource.Type,
463
+ logicalId,
464
+ instanceId,
465
+ ),
466
+ );
415
467
  yield* commit<CreatingResourceState>({
416
468
  status: "creating",
417
469
  fqn,
@@ -458,7 +510,15 @@ const executeNode = (
458
510
  session: scopedSession,
459
511
  output: attr,
460
512
  })
461
- .pipe(provideLifecycleScope(fqn, instanceId));
513
+ .pipe(
514
+ instrumentLifecycle(
515
+ "create",
516
+ fqn,
517
+ node.resource.Type,
518
+ logicalId,
519
+ instanceId,
520
+ ),
521
+ );
462
522
 
463
523
  yield* commit<CreatedResourceState>({
464
524
  status: "created",
@@ -557,7 +617,15 @@ const executeNode = (
557
617
  // @ts-expect-error - type system says this can be undefined, can it be?
558
618
  output: node.state.attr,
559
619
  })
560
- .pipe(provideLifecycleScope(fqn, instanceId));
620
+ .pipe(
621
+ instrumentLifecycle(
622
+ "update",
623
+ fqn,
624
+ node.resource.Type,
625
+ logicalId,
626
+ instanceId,
627
+ ),
628
+ );
561
629
 
562
630
  if (node.state.status === "replaced") {
563
631
  yield* commit<ReplacedResourceState>({
@@ -657,7 +725,15 @@ const executeNode = (
657
725
  instanceId,
658
726
  bindings: excludeDeletedBindings(node.bindings),
659
727
  })
660
- .pipe(provideLifecycleScope(fqn, instanceId));
728
+ .pipe(
729
+ instrumentLifecycle(
730
+ "precreate",
731
+ fqn,
732
+ node.resource.Type,
733
+ logicalId,
734
+ instanceId,
735
+ ),
736
+ );
661
737
  yield* commit<ReplacingResourceState>({
662
738
  status: "replacing",
663
739
  fqn,
@@ -706,7 +782,15 @@ const executeNode = (
706
782
  session: scopedSession,
707
783
  output: attr,
708
784
  })
709
- .pipe(provideLifecycleScope(fqn, instanceId));
785
+ .pipe(
786
+ instrumentLifecycle(
787
+ "create",
788
+ fqn,
789
+ node.resource.Type,
790
+ logicalId,
791
+ instanceId,
792
+ ),
793
+ );
710
794
 
711
795
  yield* commit<ReplacedResourceState>({
712
796
  // Creation of the new generation succeeded; from here on the only remaining
@@ -745,7 +829,16 @@ const executeNode = (
745
829
  // @ts-expect-error - node is never, this should be unreachable
746
830
  return yield* Effect.die(`Unknown action: ${node.action}`);
747
831
  });
748
- }) as Effect.Effect<void, never, never>;
832
+ }).pipe(
833
+ Effect.withSpan("apply.resource", {
834
+ attributes: {
835
+ "alchemy.resource.fqn": fqn,
836
+ "alchemy.resource.type": node.resource.Type,
837
+ "alchemy.resource.logical_id": node.resource.LogicalId,
838
+ "alchemy.resource.action": node.action,
839
+ },
840
+ }),
841
+ ) as Effect.Effect<void, never, never>;
749
842
 
750
843
  // ── Phase 3: imperative convergence loop ───────────────────────────────────
751
844
  //
@@ -830,7 +923,15 @@ const converge = Effect.fnUntraced(function* (
830
923
  olds: oldProps,
831
924
  output: tracker[fqn].output,
832
925
  })
833
- .pipe(provideLifecycleScope(fqn, instanceId));
926
+ .pipe(
927
+ instrumentLifecycle(
928
+ "update",
929
+ fqn,
930
+ node.resource.Type,
931
+ logicalId,
932
+ instanceId,
933
+ ),
934
+ );
834
935
 
835
936
  tracker[fqn] = {
836
937
  output: attr,
@@ -1007,7 +1108,15 @@ const collectGarbage = Effect.fnUntraced(function* (
1007
1108
  session: scopedSession,
1008
1109
  bindings: [],
1009
1110
  })
1010
- .pipe(provideLifecycleScope(fqn, instanceId));
1111
+ .pipe(
1112
+ instrumentLifecycle(
1113
+ "delete",
1114
+ fqn,
1115
+ resourceType,
1116
+ logicalId,
1117
+ instanceId,
1118
+ ),
1119
+ );
1011
1120
  }
1012
1121
 
1013
1122
  if (isDeleteNode(node)) {
@@ -0,0 +1,95 @@
1
+ import * as Operations from "@distilled.cloud/axiom/Operations";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Provider from "../Provider.ts";
4
+ import { Resource } from "../Resource.ts";
5
+ import type { Providers } from "./Providers.ts";
6
+
7
+ export type AnnotationProps = Operations.CreateAnnotationInput;
8
+
9
+ export type Annotation = Resource<
10
+ "Axiom.Annotation",
11
+ AnnotationProps,
12
+ Operations.CreateAnnotationOutput,
13
+ never,
14
+ Providers
15
+ >;
16
+
17
+ /**
18
+ * An Axiom annotation — a vertical marker overlaid on charts to flag a
19
+ * deploy, incident, feature flag flip, or any other point/range event you
20
+ * want correlated with telemetry.
21
+ *
22
+ * Annotations are scoped to one or more `datasets`. Use a single `time` for
23
+ * a point marker or `time` + `endTime` for a range. The `type` (e.g.
24
+ * `"deploy"`, `"incident"`) groups markers visually in the UI.
25
+ *
26
+ * Although typically created at deploy/release time (out-of-band of
27
+ * regular IaC), modelling them as resources makes per-environment
28
+ * annotation history reproducible.
29
+ *
30
+ * @see https://axiom.co/docs/query-data/annotate-charts
31
+ *
32
+ * @section Creating an Annotation
33
+ * @example Point-in-time deploy marker
34
+ * ```typescript
35
+ * yield* Axiom.Annotation("deploy-1.2.3", {
36
+ * type: "deploy",
37
+ * title: "Release 1.2.3",
38
+ * description: "https://github.com/acme/app/releases/tag/v1.2.3",
39
+ * datasets: ["my-app-traces", "my-app-logs"],
40
+ * time: new Date().toISOString(),
41
+ * url: "https://github.com/acme/app/releases/tag/v1.2.3",
42
+ * });
43
+ * ```
44
+ *
45
+ * @example Incident time-range
46
+ * ```typescript
47
+ * yield* Axiom.Annotation("inc-2026-04-27", {
48
+ * type: "incident",
49
+ * title: "Database failover",
50
+ * datasets: ["my-app-traces"],
51
+ * time: "2026-04-27T18:05:00Z",
52
+ * endTime: "2026-04-27T18:32:00Z",
53
+ * url: "https://incident.io/incidents/abc123",
54
+ * });
55
+ * ```
56
+ */
57
+ export const Annotation = Resource<Annotation>("Axiom.Annotation");
58
+
59
+ export const AnnotationProvider = () =>
60
+ Provider.effect(
61
+ Annotation,
62
+ Effect.gen(function* () {
63
+ const create = yield* Operations.createAnnotation;
64
+ const update = yield* Operations.updateAnnotation;
65
+ const get = yield* Operations.getAnnotation;
66
+ const del = yield* Operations.deleteAnnotation;
67
+
68
+ return {
69
+ stables: ["id"],
70
+ create: Effect.fn(function* ({ news }) {
71
+ return yield* create(news);
72
+ }),
73
+ update: Effect.fn(function* ({ news, output }) {
74
+ const result = yield* update({ ...news, id: output.id });
75
+ return { ...result, id: output.id, time: result.time ?? output.time };
76
+ }),
77
+ delete: Effect.fn(function* ({ output }) {
78
+ yield* del({ id: output.id }).pipe(
79
+ Effect.catchTag("NotFound", () => Effect.void),
80
+ );
81
+ }),
82
+ read: Effect.fn(function* ({ output }) {
83
+ if (!output?.id) return undefined;
84
+ return yield* get({ id: output.id }).pipe(
85
+ Effect.map((current) => ({
86
+ ...current,
87
+ id: current.id ?? output.id,
88
+ time: current.time ?? output.time,
89
+ })),
90
+ Effect.catchTag("NotFound", () => Effect.succeed(undefined)),
91
+ );
92
+ }),
93
+ };
94
+ }),
95
+ );
@@ -0,0 +1,125 @@
1
+ import * as Operations from "@distilled.cloud/axiom/Operations";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Redacted from "effect/Redacted";
4
+ import { deepEqual, isResolved } from "../Diff.ts";
5
+ import * as Provider from "../Provider.ts";
6
+ import { Resource } from "../Resource.ts";
7
+ import type { Providers } from "./Providers.ts";
8
+
9
+ export type ApiTokenProps = Omit<Operations.CreateAPITokenInput, never>;
10
+
11
+ export type ApiToken = Resource<
12
+ "Axiom.ApiToken",
13
+ ApiTokenProps,
14
+ Omit<Operations.CreateAPITokenOutput, "token"> & {
15
+ /**
16
+ * The bearer token. Returned only by `create` (and `regenerate`); Axiom
17
+ * does not return it on subsequent reads. Persisted in resource state via
18
+ * `Redacted` — handle with care.
19
+ */
20
+ token: Redacted.Redacted<string>;
21
+ },
22
+ never,
23
+ Providers
24
+ >;
25
+
26
+ /**
27
+ * An Axiom API token — a scoped bearer token used to authenticate API
28
+ * requests (ingest, query, admin). Capabilities are pinned at creation time;
29
+ * changing any field triggers a **replacement** because Axiom does not
30
+ * expose an update endpoint.
31
+ *
32
+ * The raw token value is returned only by `create`. After that, Axiom
33
+ * never echoes it back, so it is captured into `output.token` (as a
34
+ * {@link Redacted}) on initial create and persisted in resource state.
35
+ * Treat resource state as sensitive — anyone with read access can recover
36
+ * the token. Pair with a secret store for downstream consumption.
37
+ *
38
+ * @see https://axiom.co/docs/reference/tokens
39
+ *
40
+ * @section Creating an API Token
41
+ * @example Ingest-only token scoped to one dataset
42
+ * ```typescript
43
+ * const ingest = yield* Axiom.ApiToken("ingest", {
44
+ * name: "prod-ingest",
45
+ * description: "OTEL collector ingest",
46
+ * datasetCapabilities: {
47
+ * "my-app-traces": { ingest: ["create"] },
48
+ * },
49
+ * });
50
+ * ```
51
+ *
52
+ * @example Read-only query token
53
+ * ```typescript
54
+ * yield* Axiom.ApiToken("query", {
55
+ * name: "grafana-reader",
56
+ * datasetCapabilities: {
57
+ * "my-app-traces": { query: ["read"] },
58
+ * "my-app-logs": { query: ["read"] },
59
+ * },
60
+ * });
61
+ * ```
62
+ *
63
+ * @section Consuming the Token
64
+ * @example Forward the token via Cloudflare Secrets
65
+ * ```typescript
66
+ * const secret = yield* Cloudflare.Secret("axiom-token", {
67
+ * value: ingest.token,
68
+ * });
69
+ * ```
70
+ */
71
+ export const ApiToken = Resource<ApiToken>("Axiom.ApiToken");
72
+
73
+ export const ApiTokenProvider = () =>
74
+ Provider.effect(
75
+ ApiToken,
76
+ Effect.gen(function* () {
77
+ const create = yield* Operations.createAPIToken;
78
+ const get = yield* Operations.getAPIToken;
79
+ const del = yield* Operations.deleteAPIToken;
80
+
81
+ return {
82
+ stables: ["id", "token"],
83
+ diff: Effect.fn(function* ({ olds, news, output }) {
84
+ if (!isResolved(news)) return undefined;
85
+ // First create — let the engine create normally.
86
+ if (output == null) return undefined;
87
+ // Axiom has no update endpoint for tokens, so any actual change
88
+ // to the inputs forces a replacement (which mints a new bearer
89
+ // value). If nothing changed, fall through to `noop` so we don't
90
+ // needlessly rotate the token on every deploy.
91
+ if (deepEqual(olds, news)) return undefined;
92
+ return { action: "replace" } as const;
93
+ }),
94
+ create: Effect.fn(function* ({ news }) {
95
+ const result = yield* create(news);
96
+ if (!result.token) {
97
+ return yield* Effect.die(
98
+ new Error("Axiom did not return a token on create"),
99
+ );
100
+ }
101
+ return {
102
+ ...result,
103
+ token: Redacted.make(result.token),
104
+ };
105
+ }),
106
+ update: Effect.fn(function* ({ output }) {
107
+ // Diff returns "replace" for every change, so update should never
108
+ // run. If invoked, just return current state unchanged.
109
+ return output;
110
+ }),
111
+ delete: Effect.fn(function* ({ output }) {
112
+ yield* del({ id: output.id }).pipe(
113
+ Effect.catchTag("NotFound", () => Effect.void),
114
+ );
115
+ }),
116
+ read: Effect.fn(function* ({ output }) {
117
+ if (!output?.id) return undefined;
118
+ return yield* get({ id: output.id }).pipe(
119
+ Effect.map((current) => ({ ...current, token: output.token })),
120
+ Effect.catchTag("NotFound", () => Effect.succeed(undefined)),
121
+ );
122
+ }),
123
+ };
124
+ }),
125
+ );