@homeflare/alchemy 0.33.0 → 0.35.0

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 (86) hide show
  1. package/dist/grafana/alert-rule-group-drop-warning.d.ts +25 -0
  2. package/dist/grafana/alert-rule-group-drop-warning.d.ts.map +1 -0
  3. package/dist/grafana/alert-rule-group-fixtures.d.ts +73 -0
  4. package/dist/grafana/alert-rule-group-fixtures.d.ts.map +1 -0
  5. package/dist/grafana/alert-rule-group-model.d.ts +28 -0
  6. package/dist/grafana/alert-rule-group-model.d.ts.map +1 -0
  7. package/dist/grafana/alert-rule-group.d.ts +60 -0
  8. package/dist/grafana/alert-rule-group.d.ts.map +1 -0
  9. package/dist/grafana/alerting-provenance.d.ts +80 -0
  10. package/dist/grafana/alerting-provenance.d.ts.map +1 -0
  11. package/dist/grafana/contact-point-fixtures.d.ts +28 -0
  12. package/dist/grafana/contact-point-fixtures.d.ts.map +1 -0
  13. package/dist/grafana/contact-point.d.ts +99 -0
  14. package/dist/grafana/contact-point.d.ts.map +1 -0
  15. package/dist/grafana/dashboard-model.d.ts +8 -38
  16. package/dist/grafana/dashboard-model.d.ts.map +1 -1
  17. package/dist/grafana/datasource.d.ts +2 -9
  18. package/dist/grafana/datasource.d.ts.map +1 -1
  19. package/dist/grafana/index.d.ts +11 -4
  20. package/dist/grafana/index.d.ts.map +1 -1
  21. package/dist/grafana/index.js +339 -80
  22. package/dist/grafana/index.js.map +16 -7
  23. package/dist/grafana/message-template.d.ts +70 -0
  24. package/dist/grafana/message-template.d.ts.map +1 -0
  25. package/dist/grafana/mute-timing.d.ts +112 -0
  26. package/dist/grafana/mute-timing.d.ts.map +1 -0
  27. package/dist/grafana/providers.d.ts +1 -1
  28. package/dist/grafana/providers.d.ts.map +1 -1
  29. package/dist/grafana/secret-refs.d.ts +19 -0
  30. package/dist/grafana/secret-refs.d.ts.map +1 -0
  31. package/dist/grafana/subset-match.d.ts +2 -0
  32. package/dist/grafana/subset-match.d.ts.map +1 -0
  33. package/dist/linux/index.d.ts +1 -1
  34. package/dist/linux/index.d.ts.map +1 -1
  35. package/dist/linux/index.js +3 -1
  36. package/dist/linux/index.js.map +1 -1
  37. package/dist/proxmox/index.js +349 -287
  38. package/dist/proxmox/index.js.map +8 -7
  39. package/dist/proxmox/network-apply-read.d.ts +12 -0
  40. package/dist/proxmox/network-apply-read.d.ts.map +1 -1
  41. package/dist/proxmox/network-apply.d.ts +2 -0
  42. package/dist/proxmox/network-apply.d.ts.map +1 -1
  43. package/dist/proxmox/node-network-form.d.ts +86 -54
  44. package/dist/proxmox/node-network-form.d.ts.map +1 -1
  45. package/dist/proxmox/node-network-wire.d.ts +100 -0
  46. package/dist/proxmox/node-network-wire.d.ts.map +1 -0
  47. package/dist/proxmox/node-network.d.ts +21 -106
  48. package/dist/proxmox/node-network.d.ts.map +1 -1
  49. package/docs/grafana-alerting-rules.md +143 -0
  50. package/docs/grafana-alerting.md +147 -0
  51. package/docs/grafana.md +6 -3
  52. package/docs/upstream-conformance.md +38 -4
  53. package/package.json +1 -1
  54. package/src/grafana/alert-rule-group-drop-warning.test.ts +107 -0
  55. package/src/grafana/alert-rule-group-drop-warning.ts +41 -0
  56. package/src/grafana/alert-rule-group-fixtures.ts +46 -0
  57. package/src/grafana/alert-rule-group-model.test.ts +75 -0
  58. package/src/grafana/alert-rule-group-model.ts +66 -0
  59. package/src/grafana/alert-rule-group-provenance.test.ts +116 -0
  60. package/src/grafana/alert-rule-group.test.ts +164 -0
  61. package/src/grafana/alert-rule-group.ts +231 -0
  62. package/src/grafana/alerting-provenance.ts +109 -0
  63. package/src/grafana/contact-point-fixtures.ts +25 -0
  64. package/src/grafana/contact-point-provenance.test.ts +113 -0
  65. package/src/grafana/contact-point-secrets.test.ts +117 -0
  66. package/src/grafana/contact-point.test.ts +123 -0
  67. package/src/grafana/contact-point.ts +179 -0
  68. package/src/grafana/dashboard-model.ts +8 -57
  69. package/src/grafana/datasource.ts +5 -27
  70. package/src/grafana/index.ts +31 -4
  71. package/src/grafana/message-template.test.ts +212 -0
  72. package/src/grafana/message-template.ts +114 -0
  73. package/src/grafana/mute-timing.test.ts +227 -0
  74. package/src/grafana/mute-timing.ts +160 -0
  75. package/src/grafana/providers.ts +8 -0
  76. package/src/grafana/secret-refs.ts +48 -0
  77. package/src/grafana/subset-match.ts +71 -0
  78. package/src/linux/index-export.test.ts +25 -0
  79. package/src/linux/index.ts +15 -1
  80. package/src/proxmox/constraints-host-forms.test.ts +2 -2
  81. package/src/proxmox/network-apply-read.ts +12 -0
  82. package/src/proxmox/network-apply.ts +2 -0
  83. package/src/proxmox/node-network-form.ts +93 -99
  84. package/src/proxmox/node-network-read-failure.test.ts +206 -0
  85. package/src/proxmox/node-network-wire.ts +239 -0
  86. package/src/proxmox/node-network.ts +155 -167
@@ -1,18 +1,77 @@
1
- // src/grafana/datasource.ts
1
+ // src/grafana/alert-rule-group.ts
2
+ import { isResolved as isResolved2 } from "alchemy/Diff";
2
3
  import { Resource } from "alchemy";
3
4
  import * as Provider from "alchemy/Provider";
4
5
  import * as grafana from "@distilled.cloud/grafana";
6
+ import * as Effect5 from "effect/Effect";
7
+
8
+ // src/grafana/alert-rule-group-drop-warning.ts
9
+ import * as Effect from "effect/Effect";
10
+ var warnOnDroppedRules = (folderUid, group, declaredUids, liveRules) => Effect.gen(function* () {
11
+ const dropped = (liveRules ?? []).filter((rule) => typeof rule.uid === "string").filter((rule) => !declaredUids.has(rule.uid));
12
+ if (dropped.length === 0)
13
+ return;
14
+ const names = dropped.map((rule) => `${typeof rule.title === "string" ? rule.title : "(untitled)"} (${rule.uid})`).join(", ");
15
+ yield* Effect.logWarning(`Grafana.AlertRuleGroup ${folderUid}/${group}: this update REMOVES ${dropped.length} ` + `rule(s) not in the declaration: ${names}. Whole-group replacement is by design — add ` + `them to the declaration to keep them, or give them their own group.`);
16
+ });
17
+
18
+ // src/grafana/alert-rule-group-model.ts
19
+ var RULE_VOLATILE_FIELDS = ["id", "updated", "provenance"];
20
+ var normalizeRule = (rule, folderUid, group) => {
21
+ const out = {};
22
+ for (const [key, value] of Object.entries(rule)) {
23
+ if (key === "folderUID" || key === "ruleGroup" || key === "orgID")
24
+ continue;
25
+ if (RULE_VOLATILE_FIELDS.includes(key))
26
+ continue;
27
+ out[key] = value;
28
+ }
29
+ out.folderUID = folderUid;
30
+ out.ruleGroup = group;
31
+ out.orgID = 1;
32
+ return out;
33
+ };
34
+ var toWireRule = (rule, folderUid, group) => ({
35
+ ...rule,
36
+ folderUID: folderUid,
37
+ orgID: 1,
38
+ ruleGroup: group
39
+ });
40
+
41
+ // src/grafana/alerting-provenance.ts
42
+ import * as Effect3 from "effect/Effect";
43
+
44
+ // src/grafana/provisioned.ts
5
45
  import * as Data from "effect/Data";
6
46
  import * as Effect2 from "effect/Effect";
7
47
 
48
+ class GrafanaProvisionedObjectError extends Data.TaggedError("GrafanaProvisionedObjectError") {
49
+ }
50
+ var refuseIfProvisioned = (kind, uid, owner) => {
51
+ const ownerClause = owner === undefined || owner === "" ? `(this Grafana instance did not report an owning file)` : `by ${owner}`;
52
+ return Effect2.fail(new GrafanaProvisionedObjectError({
53
+ message: `${kind} '${uid}' is file-provisioned ${ownerClause} and read-only through the classic ` + `write API this family calls. Edit the provisioning file instead — declaring this ` + `object read-only (a declaration matching what is live) is safe and will never reach ` + `this refusal.`
54
+ }));
55
+ };
56
+
57
+ // src/grafana/alerting-provenance.ts
58
+ var WRITABLE_PROVENANCES = new Set(["", "api"]);
59
+ var isForeignProvenance = (provenance) => provenance === undefined || !WRITABLE_PROVENANCES.has(provenance);
60
+ var refuseIfForeignProvenance = (kind, id, provenance) => {
61
+ const reason = provenance === undefined ? `did not report a provenance at all — this family cannot tell whether it is safe to own` : `has provenance '${provenance}', not '(none)' or 'api'`;
62
+ return Effect3.fail(new GrafanaProvisionedObjectError({
63
+ message: `${kind} '${id}' ${reason} — something other than this API may own it (file ` + `provisioning, the Prometheus-rules importer, or similar), and writing here could take ` + `over or corrupt an object this family does not actually control. Edit it at its actual ` + `source instead — declaring this object read-only (a declaration matching what is live) ` + `is safe and will never reach this refusal.`
64
+ }));
65
+ };
66
+
8
67
  // src/grafana/resource.ts
9
68
  import { isResolved } from "alchemy/Diff";
10
- import * as Effect from "effect/Effect";
69
+ import * as Effect4 from "effect/Effect";
11
70
  var grafanaOperations = (spec) => {
12
- const read = (props) => spec.fetchLive(props).pipe(Effect.map((live) => live === undefined ? undefined : spec.attributes(live, props)));
71
+ const read = (props) => spec.fetchLive(props).pipe(Effect4.map((live) => live === undefined ? undefined : spec.attributes(live, props)));
13
72
  return {
14
73
  read,
15
- diff: (news, output) => Effect.gen(function* () {
74
+ diff: (news, output) => Effect4.gen(function* () {
16
75
  if (output === undefined || !isResolved(news))
17
76
  return;
18
77
  const live = yield* read(news);
@@ -22,7 +81,7 @@ var grafanaOperations = (spec) => {
22
81
  return { action: "noop" };
23
82
  return spec.update === undefined ? { action: "replace" } : { action: "update" };
24
83
  }),
25
- reconcile: (news) => Effect.gen(function* () {
84
+ reconcile: (news) => Effect4.gen(function* () {
26
85
  const before = yield* spec.fetchLive(news);
27
86
  if (before === undefined) {
28
87
  yield* spec.create(news);
@@ -34,11 +93,11 @@ var grafanaOperations = (spec) => {
34
93
  }
35
94
  const after = yield* read(news);
36
95
  if (after === undefined) {
37
- return yield* Effect.die(new Error("grafanaOperations: the write returned no error but the object is still absent. " + "Read back rather than trusting the response status."));
96
+ return yield* Effect4.die(new Error("grafanaOperations: the write returned no error but the object is still absent. " + "Read back rather than trusting the response status."));
38
97
  }
39
98
  return after;
40
99
  }),
41
- destroy: (olds) => Effect.gen(function* () {
100
+ destroy: (olds) => Effect4.gen(function* () {
42
101
  const live = yield* spec.fetchLive(olds);
43
102
  if (live === undefined)
44
103
  return;
@@ -49,7 +108,7 @@ var grafanaOperations = (spec) => {
49
108
  var grafanaHandlers = (spec) => {
50
109
  const ops = grafanaOperations(spec);
51
110
  return {
52
- list: () => Effect.succeed([]),
111
+ list: () => Effect4.succeed([]),
53
112
  read: ({ olds }) => ops.read(olds),
54
113
  diff: ({ news, output }) => ops.diff(news, output),
55
114
  reconcile: ({ news }) => ops.reconcile(news),
@@ -57,26 +116,111 @@ var grafanaHandlers = (spec) => {
57
116
  };
58
117
  };
59
118
 
119
+ // src/grafana/subset-match.ts
120
+ var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
121
+ var declaredContentMatches = (declared, live) => {
122
+ if (declared == null)
123
+ return true;
124
+ if (Array.isArray(declared)) {
125
+ return Array.isArray(live) && declared.length === live.length && declared.every((item, index) => declaredContentMatches(item, live[index]));
126
+ }
127
+ if (isPlainObject(declared)) {
128
+ return isPlainObject(live) && Object.entries(declared).every(([key, value]) => declaredContentMatches(value, live[key]));
129
+ }
130
+ return declared === live;
131
+ };
132
+
133
+ // src/grafana/alert-rule-group.ts
134
+ var GrafanaAlertRuleGroup = Resource("Grafana.AlertRuleGroup", {
135
+ defaultRemovalPolicy: "retain"
136
+ });
137
+ var findForeignRule = (rules) => (rules ?? []).find((rule) => isForeignProvenance(rule.provenance));
138
+ var body = (props) => ({
139
+ folderUid: props.folderUid,
140
+ interval: props.interval,
141
+ rules: props.rules.map((rule) => toWireRule(rule, props.folderUid, props.group)),
142
+ title: props.group
143
+ });
144
+ var refuseForForeignRule = (props, foreign) => refuseIfForeignProvenance("Grafana.AlertRuleGroup", `${props.folderUid}/${props.group}`, foreign.provenance);
145
+ var declaredUids = (props) => new Set(props.rules.map((rule) => rule.uid));
146
+ var spec = {
147
+ attributes: (live, props) => ({
148
+ folderUid: live.folderUid ?? props.folderUid,
149
+ group: props.group,
150
+ interval: live.interval ?? 0,
151
+ rules: (live.rules ?? []).map((rule) => normalizeRule(rule, props.folderUid, props.group))
152
+ }),
153
+ create: (props) => grafana.routePutAlertRuleGroup({
154
+ FolderUID: props.folderUid,
155
+ Group: props.group,
156
+ ...body(props)
157
+ }),
158
+ destroy: (props, live) => Effect5.gen(function* () {
159
+ const foreign = findForeignRule(live.rules);
160
+ if (foreign !== undefined)
161
+ return yield* refuseForForeignRule(props, foreign);
162
+ yield* grafana.routeDeleteAlertRuleGroup({ FolderUID: props.folderUid, Group: props.group });
163
+ }),
164
+ fetchLive: (props) => grafana.routeGetAlertRuleGroup({ FolderUID: props.folderUid, Group: props.group }).pipe(Effect5.catchTag("NotFound", () => Effect5.succeed(undefined))),
165
+ matches: (attributes, props) => attributes.interval === props.interval && declaredContentMatches(props.rules.map((rule) => normalizeRule(rule, props.folderUid, props.group)), attributes.rules),
166
+ update: (props, live) => Effect5.gen(function* () {
167
+ const foreign = findForeignRule(live.rules);
168
+ if (foreign !== undefined)
169
+ return yield* refuseForForeignRule(props, foreign);
170
+ yield* warnOnDroppedRules(props.folderUid, props.group, declaredUids(props), live.rules);
171
+ yield* grafana.routePutAlertRuleGroup({
172
+ FolderUID: props.folderUid,
173
+ Group: props.group,
174
+ ...body(props)
175
+ });
176
+ })
177
+ };
178
+ var diff = ({
179
+ news,
180
+ output
181
+ }) => Effect5.gen(function* () {
182
+ if (output === undefined || !isResolved2(news))
183
+ return;
184
+ const live = yield* spec.fetchLive(news);
185
+ if (live === undefined)
186
+ return { action: "update" };
187
+ yield* warnOnDroppedRules(news.folderUid, news.group, declaredUids(news), live.rules);
188
+ const mapped = spec.attributes(live, news);
189
+ return mapped !== undefined && spec.matches(mapped, news) ? { action: "noop" } : { action: "update" };
190
+ });
191
+ var handlers = { ...grafanaHandlers(spec), diff };
192
+ var GrafanaAlertRuleGroupProvider = () => Provider.effect(GrafanaAlertRuleGroup, Effect5.succeed(GrafanaAlertRuleGroup.Provider.of(handlers)));
60
193
  // src/grafana/datasource.ts
61
- var GrafanaDatasource = Resource("Grafana.Datasource");
194
+ import { Resource as Resource2 } from "alchemy";
195
+ import * as Provider2 from "alchemy/Provider";
196
+ import * as grafana2 from "@distilled.cloud/grafana";
197
+ import * as Effect7 from "effect/Effect";
198
+
199
+ // src/grafana/secret-refs.ts
200
+ import * as Data2 from "effect/Data";
201
+ import * as Effect6 from "effect/Effect";
62
202
 
63
- class GrafanaSecretRefUnsetError extends Data.TaggedError("GrafanaSecretRefUnsetError") {
203
+ class GrafanaSecretRefUnsetError extends Data2.TaggedError("GrafanaSecretRefUnsetError") {
64
204
  }
65
- var resolveSecureJsonData = (refs) => Effect2.gen(function* () {
205
+ var resolveSecretRefs = (refs, describeField) => Effect6.gen(function* () {
66
206
  if (refs === undefined)
67
207
  return;
68
208
  const out = {};
69
209
  for (const [field, envVar] of Object.entries(refs)) {
70
210
  const raw = process.env[envVar];
71
211
  if (raw === undefined || raw.trim() === "") {
72
- return yield* Effect2.fail(new GrafanaSecretRefUnsetError({
73
- message: `${envVar} is unset. Export the value of secureJsonData.${field} there — ` + "never as an Alchemy prop."
212
+ return yield* Effect6.fail(new GrafanaSecretRefUnsetError({
213
+ message: `${envVar} is unset. Export the value of ${describeField(field)} there — ` + "never as an Alchemy prop."
74
214
  }));
75
215
  }
76
216
  out[field] = raw.trim();
77
217
  }
78
218
  return out;
79
219
  });
220
+
221
+ // src/grafana/datasource.ts
222
+ var GrafanaDatasource = Resource2("Grafana.Datasource");
223
+ var resolveSecureJsonData = (refs) => resolveSecretRefs(refs, (field) => `secureJsonData.${field}`);
80
224
  var fields = (props) => ({
81
225
  name: props.name,
82
226
  type: props.type,
@@ -90,7 +234,7 @@ var fields = (props) => ({
90
234
  ...props.user === undefined ? {} : { user: props.user },
91
235
  ...props.withCredentials === undefined ? {} : { withCredentials: props.withCredentials }
92
236
  });
93
- var spec = {
237
+ var spec2 = {
94
238
  attributes: (live) => ({
95
239
  access: live.access ?? "",
96
240
  id: live.id ?? 0,
@@ -103,28 +247,28 @@ var spec = {
103
247
  url: live.url ?? "",
104
248
  version: live.version ?? 0
105
249
  }),
106
- create: (props) => Effect2.flatMap(resolveSecureJsonData(props.secureJsonDataRefs), (secureJsonData) => grafana.addDataSource({
250
+ create: (props) => Effect7.flatMap(resolveSecureJsonData(props.secureJsonDataRefs), (secureJsonData) => grafana2.addDataSource({
107
251
  ...fields(props),
108
252
  ...secureJsonData === undefined ? {} : { secureJsonData },
109
253
  uid: props.uid
110
254
  })),
111
- destroy: (props) => grafana.deleteDataSourceByUID({ uid: props.uid }),
112
- fetchLive: (props) => grafana.getDataSourceByUID({ uid: props.uid }).pipe(Effect2.catchTag("NotFound", () => Effect2.succeed(undefined))),
255
+ destroy: (props) => grafana2.deleteDataSourceByUID({ uid: props.uid }),
256
+ fetchLive: (props) => grafana2.getDataSourceByUID({ uid: props.uid }).pipe(Effect7.catchTag("NotFound", () => Effect7.succeed(undefined))),
113
257
  matches: (attributes, props) => (props.name === undefined || attributes.name === props.name) && (props.type === undefined || attributes.type === props.type) && (props.url === undefined || attributes.url === props.url) && (props.access === undefined || attributes.access === props.access) && (props.isDefault === undefined || attributes.isDefault === props.isDefault),
114
- update: (props, live) => Effect2.flatMap(resolveSecureJsonData(props.secureJsonDataRefs), (secureJsonData) => grafana.updateDataSourceByUID({
258
+ update: (props, live) => Effect7.flatMap(resolveSecureJsonData(props.secureJsonDataRefs), (secureJsonData) => grafana2.updateDataSourceByUID({
115
259
  ...fields(props),
116
260
  ...secureJsonData === undefined ? {} : { secureJsonData },
117
261
  ...live.version === undefined ? {} : { version: live.version },
118
262
  uid: props.uid
119
263
  }))
120
264
  };
121
- var handlers = grafanaHandlers(spec);
122
- var GrafanaDatasourceProvider = () => Provider.effect(GrafanaDatasource, Effect2.succeed(GrafanaDatasource.Provider.of(handlers)));
265
+ var handlers2 = grafanaHandlers(spec2);
266
+ var GrafanaDatasourceProvider = () => Provider2.effect(GrafanaDatasource, Effect7.succeed(GrafanaDatasource.Provider.of(handlers2)));
123
267
  // src/grafana/dashboard.ts
124
- import { Resource as Resource2 } from "alchemy";
125
- import * as Provider2 from "alchemy/Provider";
126
- import * as grafana2 from "@distilled.cloud/grafana";
127
- import * as Effect4 from "effect/Effect";
268
+ import { Resource as Resource3 } from "alchemy";
269
+ import * as Provider3 from "alchemy/Provider";
270
+ import * as grafana3 from "@distilled.cloud/grafana";
271
+ import * as Effect8 from "effect/Effect";
128
272
 
129
273
  // src/grafana/dashboard-model.ts
130
274
  var VOLATILE_FIELDS = ["id", "version", "iteration"];
@@ -141,36 +285,11 @@ var normalizeModel = (model, uid) => {
141
285
  return normalized;
142
286
  };
143
287
  var modelTitle = (model) => typeof model.title === "string" ? model.title : "";
144
- var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
145
- var declaredContentMatches = (declared, live) => {
146
- if (declared == null)
147
- return true;
148
- if (Array.isArray(declared)) {
149
- return Array.isArray(live) && declared.length === live.length && declared.every((item, index) => declaredContentMatches(item, live[index]));
150
- }
151
- if (isPlainObject(declared)) {
152
- return isPlainObject(live) && Object.entries(declared).every(([key, value]) => declaredContentMatches(value, live[key]));
153
- }
154
- return declared === live;
155
- };
156
-
157
- // src/grafana/provisioned.ts
158
- import * as Data2 from "effect/Data";
159
- import * as Effect3 from "effect/Effect";
160
-
161
- class GrafanaProvisionedObjectError extends Data2.TaggedError("GrafanaProvisionedObjectError") {
162
- }
163
- var refuseIfProvisioned = (kind, uid, owner) => {
164
- const ownerClause = owner === undefined || owner === "" ? `(this Grafana instance did not report an owning file)` : `by ${owner}`;
165
- return Effect3.fail(new GrafanaProvisionedObjectError({
166
- message: `${kind} '${uid}' is file-provisioned ${ownerClause} and read-only through the classic ` + `write API this family calls. Edit the provisioning file instead — declaring this ` + `object read-only (a declaration matching what is live) is safe and will never reach ` + `this refusal.`
167
- }));
168
- };
169
288
 
170
289
  // src/grafana/dashboard.ts
171
- var GrafanaDashboard = Resource2("Grafana.Dashboard");
290
+ var GrafanaDashboard = Resource3("Grafana.Dashboard");
172
291
  var modelOf = (live) => live.dashboard !== null && typeof live.dashboard === "object" ? live.dashboard : {};
173
- var spec2 = {
292
+ var spec3 = {
174
293
  attributes: (live, props) => {
175
294
  const model = normalizeModel(modelOf(live), props.uid);
176
295
  return {
@@ -184,23 +303,23 @@ var spec2 = {
184
303
  version: live.meta?.version ?? 0
185
304
  };
186
305
  },
187
- create: (props) => grafana2.postDashboard({
306
+ create: (props) => grafana3.postDashboard({
188
307
  dashboard: normalizeModel(props.dashboard, props.uid),
189
308
  ...props.folderUid === undefined ? {} : { folderUid: props.folderUid }
190
309
  }),
191
- destroy: (props, live) => Effect4.gen(function* () {
310
+ destroy: (props, live) => Effect8.gen(function* () {
192
311
  if (live.meta?.provisioned) {
193
312
  return yield* refuseIfProvisioned("Grafana.Dashboard", props.uid, live.meta.provisionedExternalId);
194
313
  }
195
- yield* grafana2.deleteDashboard({ uid: props.uid });
314
+ yield* grafana3.deleteDashboard({ uid: props.uid });
196
315
  }),
197
- fetchLive: (props) => grafana2.getDashboard({ uid: props.uid }).pipe(Effect4.catchTag("NotFound", () => Effect4.succeed(undefined))),
316
+ fetchLive: (props) => grafana3.getDashboard({ uid: props.uid }).pipe(Effect8.catchTag("NotFound", () => Effect8.succeed(undefined))),
198
317
  matches: (attributes, props) => (props.folderUid === undefined || attributes.folderUid === props.folderUid) && declaredContentMatches(normalizeModel(props.dashboard, props.uid), attributes.dashboard),
199
- update: (props, live) => Effect4.gen(function* () {
318
+ update: (props, live) => Effect8.gen(function* () {
200
319
  if (live.meta?.provisioned) {
201
320
  return yield* refuseIfProvisioned("Grafana.Dashboard", props.uid, live.meta.provisionedExternalId);
202
321
  }
203
- return yield* grafana2.postDashboard({
322
+ return yield* grafana3.postDashboard({
204
323
  dashboard: {
205
324
  ...normalizeModel(props.dashboard, props.uid),
206
325
  ...live.meta?.version === undefined ? {} : { version: live.meta.version }
@@ -209,21 +328,21 @@ var spec2 = {
209
328
  });
210
329
  })
211
330
  };
212
- var handlers2 = grafanaHandlers(spec2);
213
- var GrafanaDashboardProvider = () => Provider2.effect(GrafanaDashboard, Effect4.succeed(GrafanaDashboard.Provider.of(handlers2)));
331
+ var handlers3 = grafanaHandlers(spec3);
332
+ var GrafanaDashboardProvider = () => Provider3.effect(GrafanaDashboard, Effect8.succeed(GrafanaDashboard.Provider.of(handlers3)));
214
333
  // src/grafana/folder.ts
215
- import { Resource as Resource3 } from "alchemy";
216
- import * as Provider3 from "alchemy/Provider";
217
- import * as grafana3 from "@distilled.cloud/grafana";
334
+ import { Resource as Resource4 } from "alchemy";
335
+ import * as Provider4 from "alchemy/Provider";
336
+ import * as grafana4 from "@distilled.cloud/grafana";
218
337
  import * as Data3 from "effect/Data";
219
- import * as Effect5 from "effect/Effect";
220
- var GrafanaFolder = Resource3("Grafana.Folder", {
338
+ import * as Effect9 from "effect/Effect";
339
+ var GrafanaFolder = Resource4("Grafana.Folder", {
221
340
  defaultRemovalPolicy: "retain"
222
341
  });
223
342
 
224
343
  class GrafanaFolderReparentError extends Data3.TaggedError("GrafanaFolderReparentError") {
225
344
  }
226
- var spec3 = {
345
+ var spec4 = {
227
346
  attributes: (live) => ({
228
347
  id: live.id ?? 0,
229
348
  managedBy: live.managedBy ?? "",
@@ -233,30 +352,30 @@ var spec3 = {
233
352
  url: live.url ?? "",
234
353
  version: live.version ?? 0
235
354
  }),
236
- create: (props) => grafana3.createFolder({
355
+ create: (props) => grafana4.createFolder({
237
356
  uid: props.uid,
238
357
  title: props.title,
239
358
  ...props.description === undefined ? {} : { description: props.description },
240
359
  ...props.parentUid === undefined ? {} : { parentUid: props.parentUid }
241
360
  }),
242
- destroy: (props, live) => Effect5.gen(function* () {
361
+ destroy: (props, live) => Effect9.gen(function* () {
243
362
  if (live.managedBy) {
244
363
  return yield* refuseIfProvisioned("Grafana.Folder", props.uid, live.managedBy);
245
364
  }
246
- yield* grafana3.deleteFolder({ folder_uid: props.uid });
365
+ yield* grafana4.deleteFolder({ folder_uid: props.uid });
247
366
  }),
248
- fetchLive: (props) => grafana3.getFolderByUID({ folder_uid: props.uid }).pipe(Effect5.catchTag("NotFound", () => Effect5.succeed(undefined))),
367
+ fetchLive: (props) => grafana4.getFolderByUID({ folder_uid: props.uid }).pipe(Effect9.catchTag("NotFound", () => Effect9.succeed(undefined))),
249
368
  matches: (attributes, props) => attributes.title === props.title && (props.parentUid === undefined || attributes.parentUid === props.parentUid),
250
- update: (props, live) => Effect5.gen(function* () {
369
+ update: (props, live) => Effect9.gen(function* () {
251
370
  if (live.managedBy) {
252
371
  return yield* refuseIfProvisioned("Grafana.Folder", props.uid, live.managedBy);
253
372
  }
254
373
  if (props.parentUid !== undefined && props.parentUid !== (live.parentUid ?? undefined)) {
255
- return yield* Effect5.fail(new GrafanaFolderReparentError({
374
+ return yield* Effect9.fail(new GrafanaFolderReparentError({
256
375
  message: `Grafana.Folder '${props.uid}' declares parentUid '${props.parentUid}' but is ` + `live under '${live.parentUid ?? "(root)"}'. PUT /folders/{uid} cannot reparent a ` + `folder — move it by hand in Grafana, then update the declaration to match.`
257
376
  }));
258
377
  }
259
- return yield* grafana3.updateFolder({
378
+ return yield* grafana4.updateFolder({
260
379
  folder_uid: props.uid,
261
380
  title: props.title,
262
381
  ...props.description === undefined ? {} : { description: props.description },
@@ -264,26 +383,162 @@ var spec3 = {
264
383
  });
265
384
  })
266
385
  };
267
- var handlers3 = grafanaHandlers(spec3);
268
- var GrafanaFolderProvider = () => Provider3.effect(GrafanaFolder, Effect5.succeed(GrafanaFolder.Provider.of(handlers3)));
386
+ var handlers4 = grafanaHandlers(spec4);
387
+ var GrafanaFolderProvider = () => Provider4.effect(GrafanaFolder, Effect9.succeed(GrafanaFolder.Provider.of(handlers4)));
388
+ // src/grafana/contact-point.ts
389
+ import { Resource as Resource5 } from "alchemy";
390
+ import * as Provider5 from "alchemy/Provider";
391
+ import * as grafana5 from "@distilled.cloud/grafana";
392
+ import * as Effect10 from "effect/Effect";
393
+ var GrafanaContactPoint = Resource5("Grafana.ContactPoint");
394
+ var nonSecretSettings = (props) => {
395
+ const refs = props.secureSettingsRefs;
396
+ if (refs === undefined)
397
+ return props.settings;
398
+ const out = {};
399
+ for (const [key, value] of Object.entries(props.settings)) {
400
+ if (!(key in refs))
401
+ out[key] = value;
402
+ }
403
+ return out;
404
+ };
405
+ var resolveSettings = (props) => Effect10.map(resolveSecretRefs(props.secureSettingsRefs, (field) => `settings.${field}`), (secrets) => secrets === undefined ? props.settings : { ...props.settings, ...secrets });
406
+ var spec5 = {
407
+ attributes: (live) => ({
408
+ disableResolveMessage: live.disableResolveMessage ?? false,
409
+ name: live.name ?? "",
410
+ provenance: live.provenance,
411
+ settings: live.settings ?? {},
412
+ type: live.type,
413
+ uid: live.uid ?? ""
414
+ }),
415
+ create: (props) => Effect10.flatMap(resolveSettings(props), (settings) => grafana5.routePostContactpoints({
416
+ name: props.name,
417
+ settings,
418
+ type: props.type,
419
+ uid: props.uid,
420
+ ...props.disableResolveMessage === undefined ? {} : { disableResolveMessage: props.disableResolveMessage }
421
+ })),
422
+ destroy: (props, live) => Effect10.gen(function* () {
423
+ if (isForeignProvenance(live.provenance)) {
424
+ return yield* refuseIfForeignProvenance("Grafana.ContactPoint", props.uid, live.provenance);
425
+ }
426
+ yield* grafana5.routeDeleteContactpoints({ UID: props.uid });
427
+ }),
428
+ fetchLive: (props) => grafana5.routeGetContactpoints({}).pipe(Effect10.map((points) => points.find((point) => point.uid === props.uid))),
429
+ matches: (attributes, props) => attributes.name === props.name && attributes.type === props.type && attributes.disableResolveMessage === (props.disableResolveMessage ?? false) && declaredContentMatches(nonSecretSettings(props), attributes.settings),
430
+ update: (props, live) => Effect10.gen(function* () {
431
+ if (isForeignProvenance(live.provenance)) {
432
+ return yield* refuseIfForeignProvenance("Grafana.ContactPoint", props.uid, live.provenance);
433
+ }
434
+ const settings = yield* resolveSettings(props);
435
+ yield* grafana5.routePutContactpoint({
436
+ UID: props.uid,
437
+ name: props.name,
438
+ settings,
439
+ type: props.type,
440
+ uid: props.uid,
441
+ ...props.disableResolveMessage === undefined ? {} : { disableResolveMessage: props.disableResolveMessage }
442
+ });
443
+ })
444
+ };
445
+ var handlers5 = grafanaHandlers(spec5);
446
+ var GrafanaContactPointProvider = () => Provider5.effect(GrafanaContactPoint, Effect10.succeed(GrafanaContactPoint.Provider.of(handlers5)));
447
+ // src/grafana/message-template.ts
448
+ import { Resource as Resource6 } from "alchemy";
449
+ import * as Provider6 from "alchemy/Provider";
450
+ import * as grafana6 from "@distilled.cloud/grafana";
451
+ import * as Effect11 from "effect/Effect";
452
+ var GrafanaMessageTemplate = Resource6("Grafana.MessageTemplate");
453
+ var spec6 = {
454
+ attributes: (live) => ({
455
+ name: live.name ?? "",
456
+ provenance: live.provenance,
457
+ template: live.template ?? ""
458
+ }),
459
+ create: (props) => grafana6.routePutTemplate({ name: props.name, template: props.template }),
460
+ destroy: (props, live) => Effect11.gen(function* () {
461
+ if (isForeignProvenance(live.provenance)) {
462
+ return yield* refuseIfForeignProvenance("Grafana.MessageTemplate", props.name, live.provenance);
463
+ }
464
+ yield* grafana6.routeDeleteTemplate({ name: props.name });
465
+ }),
466
+ fetchLive: (props) => grafana6.routeGetTemplate({ name: props.name }).pipe(Effect11.catchTag("NotFound", () => Effect11.succeed(undefined))),
467
+ matches: (attributes, props) => attributes.template === props.template,
468
+ update: (props, live) => Effect11.gen(function* () {
469
+ if (isForeignProvenance(live.provenance)) {
470
+ return yield* refuseIfForeignProvenance("Grafana.MessageTemplate", props.name, live.provenance);
471
+ }
472
+ yield* grafana6.routePutTemplate({
473
+ name: props.name,
474
+ template: props.template,
475
+ ...live.version === undefined ? {} : { version: live.version }
476
+ });
477
+ })
478
+ };
479
+ var handlers6 = grafanaHandlers(spec6);
480
+ var GrafanaMessageTemplateProvider = () => Provider6.effect(GrafanaMessageTemplate, Effect11.succeed(GrafanaMessageTemplate.Provider.of(handlers6)));
481
+ // src/grafana/mute-timing.ts
482
+ import { Resource as Resource7 } from "alchemy";
483
+ import * as Provider7 from "alchemy/Provider";
484
+ import * as grafana7 from "@distilled.cloud/grafana";
485
+ import * as Effect12 from "effect/Effect";
486
+ var GrafanaMuteTiming = Resource7("Grafana.MuteTiming");
487
+ var spec7 = {
488
+ attributes: (live) => ({
489
+ name: live.name ?? "",
490
+ provenance: live.provenance,
491
+ timeIntervals: live.time_intervals ?? []
492
+ }),
493
+ create: (props) => grafana7.routePostMuteTiming({
494
+ name: props.name,
495
+ time_intervals: props.timeIntervals
496
+ }),
497
+ destroy: (props, live) => Effect12.gen(function* () {
498
+ if (isForeignProvenance(live.provenance)) {
499
+ return yield* refuseIfForeignProvenance("Grafana.MuteTiming", props.name, live.provenance);
500
+ }
501
+ yield* grafana7.routeDeleteMuteTiming({
502
+ name: props.name,
503
+ ...live.version === undefined ? {} : { version: live.version }
504
+ });
505
+ }),
506
+ fetchLive: (props) => grafana7.routeGetMuteTiming({ name: props.name }).pipe(Effect12.map((live) => live), Effect12.catchTag("NotFound", () => Effect12.succeed(undefined))),
507
+ matches: (attributes, props) => declaredContentMatches(props.timeIntervals, attributes.timeIntervals),
508
+ update: (props, live) => Effect12.gen(function* () {
509
+ if (isForeignProvenance(live.provenance)) {
510
+ return yield* refuseIfForeignProvenance("Grafana.MuteTiming", props.name, live.provenance);
511
+ }
512
+ yield* grafana7.routePutMuteTiming({
513
+ name: props.name,
514
+ time_intervals: props.timeIntervals
515
+ });
516
+ })
517
+ };
518
+ var handlers7 = grafanaHandlers(spec7);
519
+ var GrafanaMuteTimingProvider = () => Provider7.effect(GrafanaMuteTiming, Effect12.succeed(GrafanaMuteTiming.Provider.of(handlers7)));
269
520
  // src/grafana/credentials.ts
270
521
  import { Credentials } from "@distilled.cloud/grafana/Credentials";
271
522
  import { ConfigError } from "@distilled.cloud/grafana/Errors";
272
- import * as Effect6 from "effect/Effect";
523
+ import * as Effect13 from "effect/Effect";
273
524
  import * as Layer from "effect/Layer";
274
525
  import * as Redacted from "effect/Redacted";
275
- var resolve = (target) => Effect6.sync(() => process.env[target.tokenEnv]).pipe(Effect6.flatMap((raw) => raw === undefined || raw.trim() === "" ? Effect6.fail(new ConfigError({
526
+ var resolve = (target) => Effect13.sync(() => process.env[target.tokenEnv]).pipe(Effect13.flatMap((raw) => raw === undefined || raw.trim() === "" ? Effect13.fail(new ConfigError({
276
527
  message: `${target.tokenEnv} is not set. Export the Grafana service-account token there ` + `— never as an Alchemy prop — before running a stack that declares a resource ` + `against ${target.baseUrl}.`
277
- })) : Effect6.succeed({
528
+ })) : Effect13.succeed({
278
529
  apiBaseUrl: target.baseUrl.replace(/\/+$/, ""),
279
530
  token: Redacted.make(raw.trim()),
280
531
  ...target.orgId === undefined ? {} : { orgId: target.orgId }
281
- })), Effect6.orDie);
532
+ })), Effect13.orDie);
282
533
  var grafanaCredentials = (target) => Layer.succeed(Credentials, resolve(target));
283
534
  // src/grafana/providers.ts
284
535
  import * as Layer2 from "effect/Layer";
285
- var grafanaProviders = (target) => Layer2.mergeAll(GrafanaDatasourceProvider(), GrafanaFolderProvider(), GrafanaDashboardProvider()).pipe(Layer2.provide(grafanaCredentials(target)));
536
+ var grafanaProviders = (target) => Layer2.mergeAll(GrafanaDatasourceProvider(), GrafanaFolderProvider(), GrafanaDashboardProvider(), GrafanaContactPointProvider(), GrafanaMuteTimingProvider(), GrafanaMessageTemplateProvider(), GrafanaAlertRuleGroupProvider()).pipe(Layer2.provide(grafanaCredentials(target)));
286
537
  export {
538
+ GrafanaAlertRuleGroup,
539
+ GrafanaAlertRuleGroupProvider,
540
+ GrafanaContactPoint,
541
+ GrafanaContactPointProvider,
287
542
  GrafanaDashboard,
288
543
  GrafanaDashboardProvider,
289
544
  GrafanaDatasource,
@@ -291,11 +546,15 @@ export {
291
546
  GrafanaFolder,
292
547
  GrafanaFolderProvider,
293
548
  GrafanaFolderReparentError,
549
+ GrafanaMessageTemplate,
550
+ GrafanaMessageTemplateProvider,
551
+ GrafanaMuteTiming,
552
+ GrafanaMuteTimingProvider,
294
553
  GrafanaProvisionedObjectError,
295
554
  GrafanaSecretRefUnsetError,
296
555
  grafanaCredentials,
297
556
  grafanaProviders
298
557
  };
299
558
 
300
- //# debugId=D031E5F2BA23E8BC64756E2164756E21
559
+ //# debugId=2878C6B6E21D1CEC64756E2164756E21
301
560
  //# sourceMappingURL=index.js.map