@homeflare/alchemy 0.35.0 → 0.36.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 (29) hide show
  1. package/dist/grafana/index.d.ts +5 -4
  2. package/dist/grafana/index.d.ts.map +1 -1
  3. package/dist/grafana/index.js +204 -90
  4. package/dist/grafana/index.js.map +7 -4
  5. package/dist/grafana/notification-policy-drop-warning.d.ts +27 -0
  6. package/dist/grafana/notification-policy-drop-warning.d.ts.map +1 -0
  7. package/dist/grafana/notification-policy-fixtures.d.ts +34 -0
  8. package/dist/grafana/notification-policy-fixtures.d.ts.map +1 -0
  9. package/dist/grafana/notification-policy-model.d.ts +53 -0
  10. package/dist/grafana/notification-policy-model.d.ts.map +1 -0
  11. package/dist/grafana/notification-policy.d.ts +57 -0
  12. package/dist/grafana/notification-policy.d.ts.map +1 -0
  13. package/dist/grafana/providers.d.ts +1 -1
  14. package/dist/grafana/providers.d.ts.map +1 -1
  15. package/docs/grafana-alerting.md +3 -2
  16. package/docs/grafana-notification-policy.md +127 -0
  17. package/docs/grafana.md +12 -8
  18. package/docs/upstream-conformance.md +19 -5
  19. package/package.json +1 -1
  20. package/src/grafana/index.ts +11 -4
  21. package/src/grafana/notification-policy-drop-warning.test.ts +163 -0
  22. package/src/grafana/notification-policy-drop-warning.ts +60 -0
  23. package/src/grafana/notification-policy-fixtures.ts +27 -0
  24. package/src/grafana/notification-policy-model.test.ts +104 -0
  25. package/src/grafana/notification-policy-model.ts +100 -0
  26. package/src/grafana/notification-policy-refusals.test.ts +119 -0
  27. package/src/grafana/notification-policy.test.ts +86 -0
  28. package/src/grafana/notification-policy.ts +191 -0
  29. package/src/grafana/providers.ts +2 -0
@@ -7,12 +7,13 @@
7
7
  * depend on.
8
8
  *
9
9
  * `Grafana.Datasource`, `Grafana.Folder`, `Grafana.Dashboard`, `Grafana.ContactPoint`,
10
- * `Grafana.MuteTiming`, `Grafana.MessageTemplate` and `Grafana.AlertRuleGroup` ship here.
11
- * `Grafana.NotificationPolicy` — the singleton policy tree — is a stacked follow-up PR on the same
12
- * SDK operations — see docs/grafana.md, docs/grafana-folder-dashboard.md and
13
- * docs/grafana-alerting.md for what shipped and what is still open.
10
+ * `Grafana.MuteTiming`, `Grafana.MessageTemplate`, `Grafana.AlertRuleGroup` and
11
+ * `Grafana.NotificationPolicy` all ship here — see docs/grafana.md, docs/grafana-folder-dashboard.md,
12
+ * docs/grafana-alerting.md, docs/grafana-alerting-rules.md and
13
+ * docs/grafana-notification-policy.md for what shipped.
14
14
  */
15
15
  export { type AlertRuleGroupAttributes, type AlertRuleGroupProps, type AlertRuleInput, GrafanaAlertRuleGroup, GrafanaAlertRuleGroupProvider, } from './alert-rule-group.ts';
16
+ export { type NotificationPolicyAttributes, type NotificationPolicyProps, type PolicyRoute, GrafanaNotificationPolicy, GrafanaNotificationPolicyProvider, } from './notification-policy.ts';
16
17
  export { type DatasourceAttributes, type DatasourceProps, GrafanaDatasource, GrafanaDatasourceProvider, } from './datasource.ts';
17
18
  export { type DashboardAttributes, type DashboardModel, type DashboardProps, GrafanaDashboard, GrafanaDashboardProvider, } from './dashboard.ts';
18
19
  export { type FolderAttributes, type FolderProps, GrafanaFolder, GrafanaFolderProvider, GrafanaFolderReparentError, } from './folder.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/grafana/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EACL,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,qBAAqB,EACrB,6BAA6B,GAC9B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,aAAa,EACb,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,mBAAmB,EACnB,2BAA2B,GAC5B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,EACzB,sBAAsB,EACtB,8BAA8B,GAC/B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,6BAA6B,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,KAAK,aAAa,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/grafana/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EACL,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,qBAAqB,EACrB,6BAA6B,GAC9B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,4BAA4B,EACjC,KAAK,uBAAuB,EAC5B,KAAK,WAAW,EAChB,yBAAyB,EACzB,iCAAiC,GAClC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,aAAa,EACb,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,mBAAmB,EACnB,2BAA2B,GAC5B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,EACzB,sBAAsB,EACtB,8BAA8B,GAC/B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,6BAA6B,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,KAAK,aAAa,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -190,26 +190,138 @@ var diff = ({
190
190
  });
191
191
  var handlers = { ...grafanaHandlers(spec), diff };
192
192
  var GrafanaAlertRuleGroupProvider = () => Provider.effect(GrafanaAlertRuleGroup, Effect5.succeed(GrafanaAlertRuleGroup.Provider.of(handlers)));
193
- // src/grafana/datasource.ts
193
+ // src/grafana/notification-policy.ts
194
+ import { isResolved as isResolved3 } from "alchemy/Diff";
194
195
  import { Resource as Resource2 } from "alchemy";
195
196
  import * as Provider2 from "alchemy/Provider";
196
197
  import * as grafana2 from "@distilled.cloud/grafana";
197
198
  import * as Effect7 from "effect/Effect";
198
199
 
200
+ // src/grafana/notification-policy-model.ts
201
+ var ROUTE_VOLATILE_FIELDS = ["provenance"];
202
+ var normalizeRoute = (route) => {
203
+ const out = {};
204
+ for (const [key, value] of Object.entries(route)) {
205
+ if (ROUTE_VOLATILE_FIELDS.includes(key))
206
+ continue;
207
+ if (key === "routes" && Array.isArray(value)) {
208
+ out.routes = value.map((child) => normalizeRoute(child));
209
+ continue;
210
+ }
211
+ out[key] = value;
212
+ }
213
+ return out;
214
+ };
215
+ var ownFieldsJson = (route) => {
216
+ const { routes: _routes, ...ownFields } = route;
217
+ return JSON.stringify(ownFields);
218
+ };
219
+ var flattenRoutes = (route, path = "root", ancestorKey = "") => {
220
+ const children = Array.isArray(route.routes) ? route.routes : [];
221
+ const ownJson = ownFieldsJson(route);
222
+ const key = ancestorKey === "" ? ownJson : `${ancestorKey}\x00${ownJson}`;
223
+ const self = {
224
+ key,
225
+ path,
226
+ receiver: typeof route.receiver === "string" ? route.receiver : "(inherited)"
227
+ };
228
+ return [
229
+ self,
230
+ ...children.flatMap((child, index) => flattenRoutes(child, `${path}.${index}`, key))
231
+ ];
232
+ };
233
+
234
+ // src/grafana/notification-policy-drop-warning.ts
235
+ import * as Effect6 from "effect/Effect";
236
+ var countByKey = (routes) => {
237
+ const counts = new Map;
238
+ for (const route of routes)
239
+ counts.set(route.key, (counts.get(route.key) ?? 0) + 1);
240
+ return counts;
241
+ };
242
+ var warnOnDroppedRoutes = (declared, live) => Effect6.gen(function* () {
243
+ const declaredNodes = flattenRoutes(normalizeRoute(declared)).filter((r) => r.path !== "root");
244
+ const liveNodes = flattenRoutes(normalizeRoute(live)).filter((r) => r.path !== "root");
245
+ const declaredCounts = countByKey(declaredNodes);
246
+ const liveCounts = countByKey(liveNodes);
247
+ const dropped = [];
248
+ for (const [key, liveCount] of liveCounts) {
249
+ const extra = liveCount - (declaredCounts.get(key) ?? 0);
250
+ if (extra <= 0)
251
+ continue;
252
+ const occurrences = liveNodes.filter((route) => route.key === key);
253
+ dropped.push(...occurrences.slice(-extra));
254
+ }
255
+ if (dropped.length === 0)
256
+ return;
257
+ const names = dropped.map((route) => `${route.receiver} (${route.path})`).join(", ");
258
+ yield* Effect6.logWarning(`Grafana.NotificationPolicy: this update REMOVES ${dropped.length} route(s)/matcher(s) not ` + `in the declaration: ${names}. Whole-tree replacement is by design — add them to the ` + `declared tree to keep them.`);
259
+ });
260
+
261
+ // src/grafana/notification-policy.ts
262
+ var GrafanaNotificationPolicy = Resource2("Grafana.NotificationPolicy", { defaultRemovalPolicy: "retain" });
263
+ var body2 = (props) => props.route;
264
+ var spec2 = {
265
+ attributes: (live) => ({
266
+ provenance: live.provenance,
267
+ route: normalizeRoute(live)
268
+ }),
269
+ create: (props) => grafana2.routePutPolicyTree(body2(props)),
270
+ destroy: (props, live) => Effect7.gen(function* () {
271
+ if (isForeignProvenance(live.provenance)) {
272
+ return yield* refuseIfForeignProvenance("Grafana.NotificationPolicy", "default", live.provenance);
273
+ }
274
+ if (props.allowReset !== true) {
275
+ yield* Effect7.logWarning("Grafana.NotificationPolicy: destroy requested but allowReset is not set -- the policy " + "tree is RETAINED, not reset. Grafana always needs a root route; resetting wipes " + "every declared route/receiver/matcher back to Grafana's own default for the WHOLE " + "instance. Set allowReset: true to actually call routeResetPolicyTree.");
276
+ return;
277
+ }
278
+ yield* grafana2.routeResetPolicyTree({});
279
+ }),
280
+ fetchLive: (_props) => grafana2.routeGetPolicyTree({}),
281
+ matches: (attributes, props) => declaredContentMatches(normalizeRoute(props.route), attributes.route),
282
+ update: (props, live) => Effect7.gen(function* () {
283
+ if (isForeignProvenance(live.provenance)) {
284
+ return yield* refuseIfForeignProvenance("Grafana.NotificationPolicy", "default", live.provenance);
285
+ }
286
+ yield* warnOnDroppedRoutes(props.route, live);
287
+ yield* grafana2.routePutPolicyTree(body2(props));
288
+ })
289
+ };
290
+ var diff2 = ({
291
+ news,
292
+ output
293
+ }) => Effect7.gen(function* () {
294
+ if (output === undefined || !isResolved3(news))
295
+ return;
296
+ const live = yield* spec2.fetchLive(news);
297
+ if (live === undefined)
298
+ return { action: "update" };
299
+ yield* warnOnDroppedRoutes(news.route, live);
300
+ const mapped = spec2.attributes(live, news);
301
+ return mapped !== undefined && spec2.matches(mapped, news) ? { action: "noop" } : { action: "update" };
302
+ });
303
+ var handlers2 = { ...grafanaHandlers(spec2), diff: diff2 };
304
+ var GrafanaNotificationPolicyProvider = () => Provider2.effect(GrafanaNotificationPolicy, Effect7.succeed(GrafanaNotificationPolicy.Provider.of(handlers2)));
305
+ // src/grafana/datasource.ts
306
+ import { Resource as Resource3 } from "alchemy";
307
+ import * as Provider3 from "alchemy/Provider";
308
+ import * as grafana3 from "@distilled.cloud/grafana";
309
+ import * as Effect9 from "effect/Effect";
310
+
199
311
  // src/grafana/secret-refs.ts
200
312
  import * as Data2 from "effect/Data";
201
- import * as Effect6 from "effect/Effect";
313
+ import * as Effect8 from "effect/Effect";
202
314
 
203
315
  class GrafanaSecretRefUnsetError extends Data2.TaggedError("GrafanaSecretRefUnsetError") {
204
316
  }
205
- var resolveSecretRefs = (refs, describeField) => Effect6.gen(function* () {
317
+ var resolveSecretRefs = (refs, describeField) => Effect8.gen(function* () {
206
318
  if (refs === undefined)
207
319
  return;
208
320
  const out = {};
209
321
  for (const [field, envVar] of Object.entries(refs)) {
210
322
  const raw = process.env[envVar];
211
323
  if (raw === undefined || raw.trim() === "") {
212
- return yield* Effect6.fail(new GrafanaSecretRefUnsetError({
324
+ return yield* Effect8.fail(new GrafanaSecretRefUnsetError({
213
325
  message: `${envVar} is unset. Export the value of ${describeField(field)} there — ` + "never as an Alchemy prop."
214
326
  }));
215
327
  }
@@ -219,7 +331,7 @@ var resolveSecretRefs = (refs, describeField) => Effect6.gen(function* () {
219
331
  });
220
332
 
221
333
  // src/grafana/datasource.ts
222
- var GrafanaDatasource = Resource2("Grafana.Datasource");
334
+ var GrafanaDatasource = Resource3("Grafana.Datasource");
223
335
  var resolveSecureJsonData = (refs) => resolveSecretRefs(refs, (field) => `secureJsonData.${field}`);
224
336
  var fields = (props) => ({
225
337
  name: props.name,
@@ -234,7 +346,7 @@ var fields = (props) => ({
234
346
  ...props.user === undefined ? {} : { user: props.user },
235
347
  ...props.withCredentials === undefined ? {} : { withCredentials: props.withCredentials }
236
348
  });
237
- var spec2 = {
349
+ var spec3 = {
238
350
  attributes: (live) => ({
239
351
  access: live.access ?? "",
240
352
  id: live.id ?? 0,
@@ -247,28 +359,28 @@ var spec2 = {
247
359
  url: live.url ?? "",
248
360
  version: live.version ?? 0
249
361
  }),
250
- create: (props) => Effect7.flatMap(resolveSecureJsonData(props.secureJsonDataRefs), (secureJsonData) => grafana2.addDataSource({
362
+ create: (props) => Effect9.flatMap(resolveSecureJsonData(props.secureJsonDataRefs), (secureJsonData) => grafana3.addDataSource({
251
363
  ...fields(props),
252
364
  ...secureJsonData === undefined ? {} : { secureJsonData },
253
365
  uid: props.uid
254
366
  })),
255
- destroy: (props) => grafana2.deleteDataSourceByUID({ uid: props.uid }),
256
- fetchLive: (props) => grafana2.getDataSourceByUID({ uid: props.uid }).pipe(Effect7.catchTag("NotFound", () => Effect7.succeed(undefined))),
367
+ destroy: (props) => grafana3.deleteDataSourceByUID({ uid: props.uid }),
368
+ fetchLive: (props) => grafana3.getDataSourceByUID({ uid: props.uid }).pipe(Effect9.catchTag("NotFound", () => Effect9.succeed(undefined))),
257
369
  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),
258
- update: (props, live) => Effect7.flatMap(resolveSecureJsonData(props.secureJsonDataRefs), (secureJsonData) => grafana2.updateDataSourceByUID({
370
+ update: (props, live) => Effect9.flatMap(resolveSecureJsonData(props.secureJsonDataRefs), (secureJsonData) => grafana3.updateDataSourceByUID({
259
371
  ...fields(props),
260
372
  ...secureJsonData === undefined ? {} : { secureJsonData },
261
373
  ...live.version === undefined ? {} : { version: live.version },
262
374
  uid: props.uid
263
375
  }))
264
376
  };
265
- var handlers2 = grafanaHandlers(spec2);
266
- var GrafanaDatasourceProvider = () => Provider2.effect(GrafanaDatasource, Effect7.succeed(GrafanaDatasource.Provider.of(handlers2)));
377
+ var handlers3 = grafanaHandlers(spec3);
378
+ var GrafanaDatasourceProvider = () => Provider3.effect(GrafanaDatasource, Effect9.succeed(GrafanaDatasource.Provider.of(handlers3)));
267
379
  // src/grafana/dashboard.ts
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";
380
+ import { Resource as Resource4 } from "alchemy";
381
+ import * as Provider4 from "alchemy/Provider";
382
+ import * as grafana4 from "@distilled.cloud/grafana";
383
+ import * as Effect10 from "effect/Effect";
272
384
 
273
385
  // src/grafana/dashboard-model.ts
274
386
  var VOLATILE_FIELDS = ["id", "version", "iteration"];
@@ -287,9 +399,9 @@ var normalizeModel = (model, uid) => {
287
399
  var modelTitle = (model) => typeof model.title === "string" ? model.title : "";
288
400
 
289
401
  // src/grafana/dashboard.ts
290
- var GrafanaDashboard = Resource3("Grafana.Dashboard");
402
+ var GrafanaDashboard = Resource4("Grafana.Dashboard");
291
403
  var modelOf = (live) => live.dashboard !== null && typeof live.dashboard === "object" ? live.dashboard : {};
292
- var spec3 = {
404
+ var spec4 = {
293
405
  attributes: (live, props) => {
294
406
  const model = normalizeModel(modelOf(live), props.uid);
295
407
  return {
@@ -303,23 +415,23 @@ var spec3 = {
303
415
  version: live.meta?.version ?? 0
304
416
  };
305
417
  },
306
- create: (props) => grafana3.postDashboard({
418
+ create: (props) => grafana4.postDashboard({
307
419
  dashboard: normalizeModel(props.dashboard, props.uid),
308
420
  ...props.folderUid === undefined ? {} : { folderUid: props.folderUid }
309
421
  }),
310
- destroy: (props, live) => Effect8.gen(function* () {
422
+ destroy: (props, live) => Effect10.gen(function* () {
311
423
  if (live.meta?.provisioned) {
312
424
  return yield* refuseIfProvisioned("Grafana.Dashboard", props.uid, live.meta.provisionedExternalId);
313
425
  }
314
- yield* grafana3.deleteDashboard({ uid: props.uid });
426
+ yield* grafana4.deleteDashboard({ uid: props.uid });
315
427
  }),
316
- fetchLive: (props) => grafana3.getDashboard({ uid: props.uid }).pipe(Effect8.catchTag("NotFound", () => Effect8.succeed(undefined))),
428
+ fetchLive: (props) => grafana4.getDashboard({ uid: props.uid }).pipe(Effect10.catchTag("NotFound", () => Effect10.succeed(undefined))),
317
429
  matches: (attributes, props) => (props.folderUid === undefined || attributes.folderUid === props.folderUid) && declaredContentMatches(normalizeModel(props.dashboard, props.uid), attributes.dashboard),
318
- update: (props, live) => Effect8.gen(function* () {
430
+ update: (props, live) => Effect10.gen(function* () {
319
431
  if (live.meta?.provisioned) {
320
432
  return yield* refuseIfProvisioned("Grafana.Dashboard", props.uid, live.meta.provisionedExternalId);
321
433
  }
322
- return yield* grafana3.postDashboard({
434
+ return yield* grafana4.postDashboard({
323
435
  dashboard: {
324
436
  ...normalizeModel(props.dashboard, props.uid),
325
437
  ...live.meta?.version === undefined ? {} : { version: live.meta.version }
@@ -328,21 +440,21 @@ var spec3 = {
328
440
  });
329
441
  })
330
442
  };
331
- var handlers3 = grafanaHandlers(spec3);
332
- var GrafanaDashboardProvider = () => Provider3.effect(GrafanaDashboard, Effect8.succeed(GrafanaDashboard.Provider.of(handlers3)));
443
+ var handlers4 = grafanaHandlers(spec4);
444
+ var GrafanaDashboardProvider = () => Provider4.effect(GrafanaDashboard, Effect10.succeed(GrafanaDashboard.Provider.of(handlers4)));
333
445
  // src/grafana/folder.ts
334
- import { Resource as Resource4 } from "alchemy";
335
- import * as Provider4 from "alchemy/Provider";
336
- import * as grafana4 from "@distilled.cloud/grafana";
446
+ import { Resource as Resource5 } from "alchemy";
447
+ import * as Provider5 from "alchemy/Provider";
448
+ import * as grafana5 from "@distilled.cloud/grafana";
337
449
  import * as Data3 from "effect/Data";
338
- import * as Effect9 from "effect/Effect";
339
- var GrafanaFolder = Resource4("Grafana.Folder", {
450
+ import * as Effect11 from "effect/Effect";
451
+ var GrafanaFolder = Resource5("Grafana.Folder", {
340
452
  defaultRemovalPolicy: "retain"
341
453
  });
342
454
 
343
455
  class GrafanaFolderReparentError extends Data3.TaggedError("GrafanaFolderReparentError") {
344
456
  }
345
- var spec4 = {
457
+ var spec5 = {
346
458
  attributes: (live) => ({
347
459
  id: live.id ?? 0,
348
460
  managedBy: live.managedBy ?? "",
@@ -352,30 +464,30 @@ var spec4 = {
352
464
  url: live.url ?? "",
353
465
  version: live.version ?? 0
354
466
  }),
355
- create: (props) => grafana4.createFolder({
467
+ create: (props) => grafana5.createFolder({
356
468
  uid: props.uid,
357
469
  title: props.title,
358
470
  ...props.description === undefined ? {} : { description: props.description },
359
471
  ...props.parentUid === undefined ? {} : { parentUid: props.parentUid }
360
472
  }),
361
- destroy: (props, live) => Effect9.gen(function* () {
473
+ destroy: (props, live) => Effect11.gen(function* () {
362
474
  if (live.managedBy) {
363
475
  return yield* refuseIfProvisioned("Grafana.Folder", props.uid, live.managedBy);
364
476
  }
365
- yield* grafana4.deleteFolder({ folder_uid: props.uid });
477
+ yield* grafana5.deleteFolder({ folder_uid: props.uid });
366
478
  }),
367
- fetchLive: (props) => grafana4.getFolderByUID({ folder_uid: props.uid }).pipe(Effect9.catchTag("NotFound", () => Effect9.succeed(undefined))),
479
+ fetchLive: (props) => grafana5.getFolderByUID({ folder_uid: props.uid }).pipe(Effect11.catchTag("NotFound", () => Effect11.succeed(undefined))),
368
480
  matches: (attributes, props) => attributes.title === props.title && (props.parentUid === undefined || attributes.parentUid === props.parentUid),
369
- update: (props, live) => Effect9.gen(function* () {
481
+ update: (props, live) => Effect11.gen(function* () {
370
482
  if (live.managedBy) {
371
483
  return yield* refuseIfProvisioned("Grafana.Folder", props.uid, live.managedBy);
372
484
  }
373
485
  if (props.parentUid !== undefined && props.parentUid !== (live.parentUid ?? undefined)) {
374
- return yield* Effect9.fail(new GrafanaFolderReparentError({
486
+ return yield* Effect11.fail(new GrafanaFolderReparentError({
375
487
  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.`
376
488
  }));
377
489
  }
378
- return yield* grafana4.updateFolder({
490
+ return yield* grafana5.updateFolder({
379
491
  folder_uid: props.uid,
380
492
  title: props.title,
381
493
  ...props.description === undefined ? {} : { description: props.description },
@@ -383,14 +495,14 @@ var spec4 = {
383
495
  });
384
496
  })
385
497
  };
386
- var handlers4 = grafanaHandlers(spec4);
387
- var GrafanaFolderProvider = () => Provider4.effect(GrafanaFolder, Effect9.succeed(GrafanaFolder.Provider.of(handlers4)));
498
+ var handlers5 = grafanaHandlers(spec5);
499
+ var GrafanaFolderProvider = () => Provider5.effect(GrafanaFolder, Effect11.succeed(GrafanaFolder.Provider.of(handlers5)));
388
500
  // 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");
501
+ import { Resource as Resource6 } from "alchemy";
502
+ import * as Provider6 from "alchemy/Provider";
503
+ import * as grafana6 from "@distilled.cloud/grafana";
504
+ import * as Effect12 from "effect/Effect";
505
+ var GrafanaContactPoint = Resource6("Grafana.ContactPoint");
394
506
  var nonSecretSettings = (props) => {
395
507
  const refs = props.secureSettingsRefs;
396
508
  if (refs === undefined)
@@ -402,8 +514,8 @@ var nonSecretSettings = (props) => {
402
514
  }
403
515
  return out;
404
516
  };
405
- var resolveSettings = (props) => Effect10.map(resolveSecretRefs(props.secureSettingsRefs, (field) => `settings.${field}`), (secrets) => secrets === undefined ? props.settings : { ...props.settings, ...secrets });
406
- var spec5 = {
517
+ var resolveSettings = (props) => Effect12.map(resolveSecretRefs(props.secureSettingsRefs, (field) => `settings.${field}`), (secrets) => secrets === undefined ? props.settings : { ...props.settings, ...secrets });
518
+ var spec6 = {
407
519
  attributes: (live) => ({
408
520
  disableResolveMessage: live.disableResolveMessage ?? false,
409
521
  name: live.name ?? "",
@@ -412,27 +524,27 @@ var spec5 = {
412
524
  type: live.type,
413
525
  uid: live.uid ?? ""
414
526
  }),
415
- create: (props) => Effect10.flatMap(resolveSettings(props), (settings) => grafana5.routePostContactpoints({
527
+ create: (props) => Effect12.flatMap(resolveSettings(props), (settings) => grafana6.routePostContactpoints({
416
528
  name: props.name,
417
529
  settings,
418
530
  type: props.type,
419
531
  uid: props.uid,
420
532
  ...props.disableResolveMessage === undefined ? {} : { disableResolveMessage: props.disableResolveMessage }
421
533
  })),
422
- destroy: (props, live) => Effect10.gen(function* () {
534
+ destroy: (props, live) => Effect12.gen(function* () {
423
535
  if (isForeignProvenance(live.provenance)) {
424
536
  return yield* refuseIfForeignProvenance("Grafana.ContactPoint", props.uid, live.provenance);
425
537
  }
426
- yield* grafana5.routeDeleteContactpoints({ UID: props.uid });
538
+ yield* grafana6.routeDeleteContactpoints({ UID: props.uid });
427
539
  }),
428
- fetchLive: (props) => grafana5.routeGetContactpoints({}).pipe(Effect10.map((points) => points.find((point) => point.uid === props.uid))),
540
+ fetchLive: (props) => grafana6.routeGetContactpoints({}).pipe(Effect12.map((points) => points.find((point) => point.uid === props.uid))),
429
541
  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* () {
542
+ update: (props, live) => Effect12.gen(function* () {
431
543
  if (isForeignProvenance(live.provenance)) {
432
544
  return yield* refuseIfForeignProvenance("Grafana.ContactPoint", props.uid, live.provenance);
433
545
  }
434
546
  const settings = yield* resolveSettings(props);
435
- yield* grafana5.routePutContactpoint({
547
+ yield* grafana6.routePutContactpoint({
436
548
  UID: props.uid,
437
549
  name: props.name,
438
550
  settings,
@@ -442,98 +554,98 @@ var spec5 = {
442
554
  });
443
555
  })
444
556
  };
445
- var handlers5 = grafanaHandlers(spec5);
446
- var GrafanaContactPointProvider = () => Provider5.effect(GrafanaContactPoint, Effect10.succeed(GrafanaContactPoint.Provider.of(handlers5)));
557
+ var handlers6 = grafanaHandlers(spec6);
558
+ var GrafanaContactPointProvider = () => Provider6.effect(GrafanaContactPoint, Effect12.succeed(GrafanaContactPoint.Provider.of(handlers6)));
447
559
  // 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 = {
560
+ import { Resource as Resource7 } from "alchemy";
561
+ import * as Provider7 from "alchemy/Provider";
562
+ import * as grafana7 from "@distilled.cloud/grafana";
563
+ import * as Effect13 from "effect/Effect";
564
+ var GrafanaMessageTemplate = Resource7("Grafana.MessageTemplate");
565
+ var spec7 = {
454
566
  attributes: (live) => ({
455
567
  name: live.name ?? "",
456
568
  provenance: live.provenance,
457
569
  template: live.template ?? ""
458
570
  }),
459
- create: (props) => grafana6.routePutTemplate({ name: props.name, template: props.template }),
460
- destroy: (props, live) => Effect11.gen(function* () {
571
+ create: (props) => grafana7.routePutTemplate({ name: props.name, template: props.template }),
572
+ destroy: (props, live) => Effect13.gen(function* () {
461
573
  if (isForeignProvenance(live.provenance)) {
462
574
  return yield* refuseIfForeignProvenance("Grafana.MessageTemplate", props.name, live.provenance);
463
575
  }
464
- yield* grafana6.routeDeleteTemplate({ name: props.name });
576
+ yield* grafana7.routeDeleteTemplate({ name: props.name });
465
577
  }),
466
- fetchLive: (props) => grafana6.routeGetTemplate({ name: props.name }).pipe(Effect11.catchTag("NotFound", () => Effect11.succeed(undefined))),
578
+ fetchLive: (props) => grafana7.routeGetTemplate({ name: props.name }).pipe(Effect13.catchTag("NotFound", () => Effect13.succeed(undefined))),
467
579
  matches: (attributes, props) => attributes.template === props.template,
468
- update: (props, live) => Effect11.gen(function* () {
580
+ update: (props, live) => Effect13.gen(function* () {
469
581
  if (isForeignProvenance(live.provenance)) {
470
582
  return yield* refuseIfForeignProvenance("Grafana.MessageTemplate", props.name, live.provenance);
471
583
  }
472
- yield* grafana6.routePutTemplate({
584
+ yield* grafana7.routePutTemplate({
473
585
  name: props.name,
474
586
  template: props.template,
475
587
  ...live.version === undefined ? {} : { version: live.version }
476
588
  });
477
589
  })
478
590
  };
479
- var handlers6 = grafanaHandlers(spec6);
480
- var GrafanaMessageTemplateProvider = () => Provider6.effect(GrafanaMessageTemplate, Effect11.succeed(GrafanaMessageTemplate.Provider.of(handlers6)));
591
+ var handlers7 = grafanaHandlers(spec7);
592
+ var GrafanaMessageTemplateProvider = () => Provider7.effect(GrafanaMessageTemplate, Effect13.succeed(GrafanaMessageTemplate.Provider.of(handlers7)));
481
593
  // 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 = {
594
+ import { Resource as Resource8 } from "alchemy";
595
+ import * as Provider8 from "alchemy/Provider";
596
+ import * as grafana8 from "@distilled.cloud/grafana";
597
+ import * as Effect14 from "effect/Effect";
598
+ var GrafanaMuteTiming = Resource8("Grafana.MuteTiming");
599
+ var spec8 = {
488
600
  attributes: (live) => ({
489
601
  name: live.name ?? "",
490
602
  provenance: live.provenance,
491
603
  timeIntervals: live.time_intervals ?? []
492
604
  }),
493
- create: (props) => grafana7.routePostMuteTiming({
605
+ create: (props) => grafana8.routePostMuteTiming({
494
606
  name: props.name,
495
607
  time_intervals: props.timeIntervals
496
608
  }),
497
- destroy: (props, live) => Effect12.gen(function* () {
609
+ destroy: (props, live) => Effect14.gen(function* () {
498
610
  if (isForeignProvenance(live.provenance)) {
499
611
  return yield* refuseIfForeignProvenance("Grafana.MuteTiming", props.name, live.provenance);
500
612
  }
501
- yield* grafana7.routeDeleteMuteTiming({
613
+ yield* grafana8.routeDeleteMuteTiming({
502
614
  name: props.name,
503
615
  ...live.version === undefined ? {} : { version: live.version }
504
616
  });
505
617
  }),
506
- fetchLive: (props) => grafana7.routeGetMuteTiming({ name: props.name }).pipe(Effect12.map((live) => live), Effect12.catchTag("NotFound", () => Effect12.succeed(undefined))),
618
+ fetchLive: (props) => grafana8.routeGetMuteTiming({ name: props.name }).pipe(Effect14.map((live) => live), Effect14.catchTag("NotFound", () => Effect14.succeed(undefined))),
507
619
  matches: (attributes, props) => declaredContentMatches(props.timeIntervals, attributes.timeIntervals),
508
- update: (props, live) => Effect12.gen(function* () {
620
+ update: (props, live) => Effect14.gen(function* () {
509
621
  if (isForeignProvenance(live.provenance)) {
510
622
  return yield* refuseIfForeignProvenance("Grafana.MuteTiming", props.name, live.provenance);
511
623
  }
512
- yield* grafana7.routePutMuteTiming({
624
+ yield* grafana8.routePutMuteTiming({
513
625
  name: props.name,
514
626
  time_intervals: props.timeIntervals
515
627
  });
516
628
  })
517
629
  };
518
- var handlers7 = grafanaHandlers(spec7);
519
- var GrafanaMuteTimingProvider = () => Provider7.effect(GrafanaMuteTiming, Effect12.succeed(GrafanaMuteTiming.Provider.of(handlers7)));
630
+ var handlers8 = grafanaHandlers(spec8);
631
+ var GrafanaMuteTimingProvider = () => Provider8.effect(GrafanaMuteTiming, Effect14.succeed(GrafanaMuteTiming.Provider.of(handlers8)));
520
632
  // src/grafana/credentials.ts
521
633
  import { Credentials } from "@distilled.cloud/grafana/Credentials";
522
634
  import { ConfigError } from "@distilled.cloud/grafana/Errors";
523
- import * as Effect13 from "effect/Effect";
635
+ import * as Effect15 from "effect/Effect";
524
636
  import * as Layer from "effect/Layer";
525
637
  import * as Redacted from "effect/Redacted";
526
- var resolve = (target) => Effect13.sync(() => process.env[target.tokenEnv]).pipe(Effect13.flatMap((raw) => raw === undefined || raw.trim() === "" ? Effect13.fail(new ConfigError({
638
+ var resolve = (target) => Effect15.sync(() => process.env[target.tokenEnv]).pipe(Effect15.flatMap((raw) => raw === undefined || raw.trim() === "" ? Effect15.fail(new ConfigError({
527
639
  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}.`
528
- })) : Effect13.succeed({
640
+ })) : Effect15.succeed({
529
641
  apiBaseUrl: target.baseUrl.replace(/\/+$/, ""),
530
642
  token: Redacted.make(raw.trim()),
531
643
  ...target.orgId === undefined ? {} : { orgId: target.orgId }
532
- })), Effect13.orDie);
644
+ })), Effect15.orDie);
533
645
  var grafanaCredentials = (target) => Layer.succeed(Credentials, resolve(target));
534
646
  // src/grafana/providers.ts
535
647
  import * as Layer2 from "effect/Layer";
536
- var grafanaProviders = (target) => Layer2.mergeAll(GrafanaDatasourceProvider(), GrafanaFolderProvider(), GrafanaDashboardProvider(), GrafanaContactPointProvider(), GrafanaMuteTimingProvider(), GrafanaMessageTemplateProvider(), GrafanaAlertRuleGroupProvider()).pipe(Layer2.provide(grafanaCredentials(target)));
648
+ var grafanaProviders = (target) => Layer2.mergeAll(GrafanaDatasourceProvider(), GrafanaFolderProvider(), GrafanaDashboardProvider(), GrafanaContactPointProvider(), GrafanaMuteTimingProvider(), GrafanaMessageTemplateProvider(), GrafanaAlertRuleGroupProvider(), GrafanaNotificationPolicyProvider()).pipe(Layer2.provide(grafanaCredentials(target)));
537
649
  export {
538
650
  GrafanaAlertRuleGroup,
539
651
  GrafanaAlertRuleGroupProvider,
@@ -550,11 +662,13 @@ export {
550
662
  GrafanaMessageTemplateProvider,
551
663
  GrafanaMuteTiming,
552
664
  GrafanaMuteTimingProvider,
665
+ GrafanaNotificationPolicy,
666
+ GrafanaNotificationPolicyProvider,
553
667
  GrafanaProvisionedObjectError,
554
668
  GrafanaSecretRefUnsetError,
555
669
  grafanaCredentials,
556
670
  grafanaProviders
557
671
  };
558
672
 
559
- //# debugId=2878C6B6E21D1CEC64756E2164756E21
673
+ //# debugId=5578DCEDF956037C64756E2164756E21
560
674
  //# sourceMappingURL=index.js.map