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