@homeflare/alchemy 0.50.0 → 0.51.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 (58) hide show
  1. package/dist/unifi/acl-rule-ordering.d.ts +6 -1
  2. package/dist/unifi/acl-rule-ordering.d.ts.map +1 -1
  3. package/dist/unifi/acl-rule.d.ts +6 -1
  4. package/dist/unifi/acl-rule.d.ts.map +1 -1
  5. package/dist/unifi/dns-policy.d.ts +6 -1
  6. package/dist/unifi/dns-policy.d.ts.map +1 -1
  7. package/dist/unifi/fake-unifi.d.ts +4 -0
  8. package/dist/unifi/fake-unifi.d.ts.map +1 -1
  9. package/dist/unifi/firewall-policy-ordering.d.ts +6 -1
  10. package/dist/unifi/firewall-policy-ordering.d.ts.map +1 -1
  11. package/dist/unifi/firewall-policy.d.ts +6 -1
  12. package/dist/unifi/firewall-policy.d.ts.map +1 -1
  13. package/dist/unifi/firewall-zone.d.ts +6 -1
  14. package/dist/unifi/firewall-zone.d.ts.map +1 -1
  15. package/dist/unifi/index.d.ts +5 -3
  16. package/dist/unifi/index.d.ts.map +1 -1
  17. package/dist/unifi/index.js +339 -86
  18. package/dist/unifi/index.js.map +10 -6
  19. package/dist/unifi/network-scope.d.ts +31 -0
  20. package/dist/unifi/network-scope.d.ts.map +1 -0
  21. package/dist/unifi/network-update.d.ts +44 -0
  22. package/dist/unifi/network-update.d.ts.map +1 -0
  23. package/dist/unifi/network.d.ts +32 -12
  24. package/dist/unifi/network.d.ts.map +1 -1
  25. package/dist/unifi/policy.d.ts +89 -1
  26. package/dist/unifi/policy.d.ts.map +1 -1
  27. package/dist/unifi/resource.d.ts +17 -43
  28. package/dist/unifi/resource.d.ts.map +1 -1
  29. package/dist/unifi/traffic-matching-list.d.ts +6 -1
  30. package/dist/unifi/traffic-matching-list.d.ts.map +1 -1
  31. package/dist/unifi/update-reconcile.d.ts +31 -0
  32. package/dist/unifi/update-reconcile.d.ts.map +1 -0
  33. package/dist/unifi/wifi-broadcast.d.ts +6 -1
  34. package/dist/unifi/wifi-broadcast.d.ts.map +1 -1
  35. package/dist/unifi/wire-guard.d.ts +33 -0
  36. package/dist/unifi/wire-guard.d.ts.map +1 -0
  37. package/docs/unifi-network-write.md +90 -0
  38. package/docs/unifi.md +36 -36
  39. package/package.json +1 -1
  40. package/src/unifi/fake-unifi.ts +15 -2
  41. package/src/unifi/handler-allow-news.test.ts +80 -0
  42. package/src/unifi/index.ts +21 -5
  43. package/src/unifi/network-scope.test.ts +203 -0
  44. package/src/unifi/network-scope.ts +75 -0
  45. package/src/unifi/network-update.test.ts +250 -0
  46. package/src/unifi/network-update.ts +95 -0
  47. package/src/unifi/network.test.ts +27 -1
  48. package/src/unifi/network.ts +56 -12
  49. package/src/unifi/policy.ts +128 -10
  50. package/src/unifi/resource.test.ts +15 -3
  51. package/src/unifi/resource.ts +64 -105
  52. package/src/unifi/update-reconcile.ts +126 -0
  53. package/src/unifi/wire-guard-anchor.test.ts +198 -0
  54. package/src/unifi/wire-guard-round2.test.ts +120 -0
  55. package/src/unifi/wire-guard.test.ts +218 -0
  56. package/src/unifi/wire-guard.ts +192 -0
  57. package/src/unifi/write-op-reference.test.ts +38 -2
  58. package/src/unifi/get-only-guard.test.ts +0 -165
@@ -1,87 +1,124 @@
1
1
  // src/unifi/policy.ts
2
2
  import * as Data from "effect/Data";
3
3
  import * as Effect from "effect/Effect";
4
- var UNIFI_READ_ONLY_POLICY = "read-only by Tim's rule, 2026-09-24; lifting it is a kit change";
4
+ var UNIFI_WRITE_POLICY = "UniFi is adopt-only except Unifi.Network update: one whole-object PUT built from the live " + "object plus the declared field changes, only on a row with prior Alchemy state, only through " + "deploy-approve; create/delete and every other family stay refused -- widening is a kit change " + "(Tim 2026-10-10 lifts the 2026-09-24 rule for this one case)";
5
+ var UNIFI_READ_ONLY_POLICY = UNIFI_WRITE_POLICY;
5
6
 
6
7
  class UnifiWriteRefused extends Data.TaggedError("UnifiWriteRefused") {
7
8
  get message() {
8
- return `${this.type} ${this.identity}: refusing to ${this.action} -- ${UNIFI_READ_ONLY_POLICY}.`;
9
+ return `${this.type} ${this.identity}: refusing to ${this.action} -- ${UNIFI_WRITE_POLICY}.`;
9
10
  }
10
11
  }
11
12
  var refuseWrite = (type, identity, action) => Effect.fail(new UnifiWriteRefused({ type, identity, action }));
12
- // src/unifi/resource.ts
13
- import { Unowned } from "alchemy/AdoptPolicy";
14
- import { isResolved } from "alchemy/Diff";
15
- import { CredentialsFromEnv } from "@distilled.cloud/unifi-network/Credentials";
13
+ var fieldList = (fields) => `[${fields.join(", ")}]`;
14
+
15
+ class UnifiLiveDriftedSinceDeploy extends Data.TaggedError("UnifiLiveDriftedSinceDeploy") {
16
+ get message() {
17
+ return `${this.type} ${this.identity}: live differs from the last deployed state on ` + `${fieldList(this.fields)}; refusing to PUT over it. Two causes look identical from here: a ` + "hand edit on the console, or an earlier PUT that landed but did not converge " + "(UnifiUpdateDidNotConverge). Re-import the live object first.";
18
+ }
19
+ }
20
+
21
+ class UnifiUpdateWouldBeNoop extends Data.TaggedError("UnifiUpdateWouldBeNoop") {
22
+ get message() {
23
+ return `${this.type} ${this.identity}: the merged update body equals live; refusing a no-op PUT.`;
24
+ }
25
+ }
26
+
27
+ class UnifiUpdateDidNotConverge extends Data.TaggedError("UnifiUpdateDidNotConverge") {
28
+ get message() {
29
+ return `${this.type} ${this.identity}: the PUT LANDED, but live now differs from the declaration on ` + `${fieldList(this.fields)}. The change is on the console and is not rolled back; the next ` + "deploy will see this as drift. Recovery is a re-import of the live object.";
30
+ }
31
+ }
32
+
33
+ class UnifiFieldNotRemovable extends Data.TaggedError("UnifiFieldNotRemovable") {
34
+ get message() {
35
+ return `${this.type} ${this.identity}: refusing to remove ${fieldList(this.fields)}; a whole-object ` + "PUT turns an omitted block off, and only the removable allowlist may be dropped.";
36
+ }
37
+ }
38
+
39
+ class UnifiFieldNotSettable extends Data.TaggedError("UnifiFieldNotSettable") {
40
+ get message() {
41
+ return `${this.type} ${this.identity}: refusing to set ${fieldList(this.fields)}; this write path ` + "only sets the settable allowlist (ipv6Configuration).";
42
+ }
43
+ }
44
+
45
+ class UnifiManagementNetworkRefused extends Data.TaggedError("UnifiManagementNetworkRefused") {
46
+ get message() {
47
+ return `${this.type} ${this.identity}: live is the default (management) network; refusing any write to it.`;
48
+ }
49
+ }
50
+
51
+ class UnifiImmutableFieldChanged extends Data.TaggedError("UnifiImmutableFieldChanged") {
52
+ get message() {
53
+ return `${this.type} ${this.identity}: refusing to change ${fieldList(this.fields)}; those fields are never written.`;
54
+ }
55
+ }
56
+
57
+ class UnifiIdentityChanged extends Data.TaggedError("UnifiIdentityChanged") {
58
+ get message() {
59
+ return `${this.type} ${this.identity}: the last deploy named ${this.previous}; refusing to PUT against another object.`;
60
+ }
61
+ }
62
+ // src/unifi/wire-guard.ts
16
63
  import * as Effect2 from "effect/Effect";
64
+ import * as Option from "effect/Option";
65
+ import * as FetchHttpClient from "effect/http/FetchHttpClient";
17
66
  import * as HttpClient from "effect/http/HttpClient";
18
- var unifiOperations = (spec) => {
19
- const read = (props) => spec.fetchLive(props).pipe(Effect2.map((live) => live === undefined ? undefined : spec.attributes(live, props)));
20
- return {
21
- read,
22
- readHandler: ({ olds, output }) => Effect2.gen(function* () {
23
- const attrs = yield* read(olds);
24
- if (attrs === undefined)
25
- return;
26
- return output !== undefined ? attrs : Unowned(attrs);
27
- }),
28
- diff: (news, output) => Effect2.gen(function* () {
29
- if (output === undefined || !isResolved(news))
30
- return;
31
- const live = yield* read(news);
32
- if (live === undefined)
33
- return { action: "update" };
34
- return spec.matches(live, news) ? { action: "noop" } : { action: "update" };
35
- }),
36
- reconcile: (news) => Effect2.gen(function* () {
37
- const live = yield* read(news);
38
- if (live === undefined)
39
- return yield* refuseWrite(spec.type, spec.describe(news), "create");
40
- if (!spec.matches(live, news)) {
41
- return yield* refuseWrite(spec.type, spec.describe(news), "update");
42
- }
43
- return live;
44
- }),
45
- destroy: (olds) => refuseWrite(spec.type, spec.describe(olds), "delete")
46
- };
47
- };
48
-
49
- class UnifiNonGetRequest extends Error {
67
+ import * as HttpClientRequest from "effect/http/HttpClientRequest";
68
+ class UnifiRefusedRequest extends Error {
50
69
  method;
51
70
  path;
52
- constructor(method, path) {
53
- super(`UniFi HttpClient guard: refused ${method} ${path} -- ${UNIFI_READ_ONLY_POLICY}. This is a ` + "defect, not a recoverable condition: something under src/unifi tried to send a non-GET " + "request, which policy.ts's per-operation refusal should already have made impossible. " + "Fix the kit code that produced this request.");
71
+ constructor(method, path, detail = "not on this row's allow list") {
72
+ super(`UniFi HttpClient guard: refused ${method} ${path} (${detail}) -- ${UNIFI_WRITE_POLICY}. ` + "This is a defect, not a recoverable condition: something under src/unifi sent a request " + "policy.ts's per-operation refusal should already have made impossible. Fix the kit " + "code that produced this request.");
54
73
  this.method = method;
55
74
  this.path = path;
56
- this.name = "UnifiNonGetRequest";
75
+ this.name = "UnifiRefusedRequest";
57
76
  }
58
77
  }
78
+ var UnifiNonGetRequest = UnifiRefusedRequest;
59
79
  var requestPath = (url) => {
60
80
  try {
61
- return new URL(url).pathname;
81
+ return new URL(url).pathname.replace(/(\/consoles\/)[^/]*/g, "$1<redacted>");
62
82
  } catch {
63
83
  return "<unparseable request url>";
64
84
  }
65
85
  };
66
- var guardGetOnly = (client) => client.pipe(HttpClient.mapRequestEffect((request) => request.method === "GET" ? Effect2.succeed(request) : Effect2.die(new UnifiNonGetRequest(request.method, requestPath(request.url)))));
67
- var GetOnlyHttpClient = HttpClient.layerMergedContext(Effect2.map(HttpClient.HttpClient, guardGetOnly));
68
- var unifiHandlers = (spec) => {
69
- const ops = unifiOperations(spec);
70
- const withCredentials = (effect) => effect.pipe(Effect2.provide(GetOnlyHttpClient), Effect2.provide(CredentialsFromEnv));
71
- return {
72
- list: () => Effect2.succeed([]),
73
- read: (args) => withCredentials(ops.readHandler(args)),
74
- diff: (args) => withCredentials(ops.diff(args.news, args.output)),
75
- reconcile: (args) => withCredentials(ops.reconcile(args.news)),
76
- delete: (args) => withCredentials(ops.destroy(args.olds))
77
- };
86
+ var isAllowed = (allow, baseUrl, method, request) => {
87
+ if (baseUrl === undefined)
88
+ return false;
89
+ try {
90
+ const base = new URL(baseUrl);
91
+ const resolved = HttpClientRequest.toUrl(request);
92
+ if (Option.isNone(resolved))
93
+ return false;
94
+ const target = resolved.value;
95
+ if (target.origin !== base.origin || target.search !== "" || target.hash !== "")
96
+ return false;
97
+ const basePath = base.pathname.replace(/\/+$/, "");
98
+ return allow.some((entry) => entry.method === method && target.pathname === `${basePath}${entry.tail}`);
99
+ } catch {
100
+ return false;
101
+ }
102
+ };
103
+ var followedElsewhere = (res) => {
104
+ const sent = HttpClientRequest.toUrl(res.request);
105
+ return res.url !== "" && Option.isSome(sent) && res.url !== sent.value.href.split("#")[0];
78
106
  };
107
+ var guard = (client, allow, baseUrl) => client.pipe(HttpClient.mapRequestEffect((request) => request.method === "GET" || isAllowed(allow, baseUrl, request.method, request) ? Effect2.succeed(request) : Effect2.die(new UnifiRefusedRequest(request.method, requestPath(request.url)))), HttpClient.transformResponse((response) => Effect2.flatMap(response, (res) => res.status >= 300 && res.status < 400 ? Effect2.die(new UnifiRefusedRequest(res.request.method, requestPath(res.request.url), `answered a ${res.status} redirect`)) : followedElsewhere(res) ? Effect2.die(new UnifiRefusedRequest(res.request.method, requestPath(res.request.url), "the response URL differs from the request URL (a redirect was followed)")) : Effect2.succeed(res))));
108
+ var guardedHttpClient = (allow, baseUrl) => {
109
+ return HttpClient.layerMergedContext(Effect2.map(HttpClient.HttpClient, (client) => HttpClient.transform(guard(client, allow, baseUrl), manualRedirect)));
110
+ };
111
+ var manualRedirect = (effect) => Effect2.flatMap(Effect2.serviceOption(FetchHttpClient.RequestInit), (existing) => Effect2.provideService(effect, FetchHttpClient.RequestInit, {
112
+ ...Option.getOrElse(existing, () => ({})),
113
+ redirect: "manual"
114
+ }));
115
+ var GetOnlyHttpClient = guardedHttpClient([]);
79
116
  // src/unifi/network.ts
80
117
  import { Resource } from "alchemy";
81
118
  import { adopt } from "alchemy/AdoptPolicy";
82
119
  import * as Provider from "alchemy/Provider";
83
- import * as networks from "@distilled.cloud/unifi-network/networks";
84
- import * as Effect3 from "effect/Effect";
120
+ import * as networks2 from "@distilled.cloud/unifi-network/networks";
121
+ import * as Effect7 from "effect/Effect";
85
122
 
86
123
  // src/unifi/network-form.ts
87
124
  var sortedSet = (values) => [...new Set(values)].sort();
@@ -183,26 +220,231 @@ var fieldDrift = makeDriftOf([
183
220
  var matches = (attributes, props) => fieldDrift(attributes, props).length === 0;
184
221
  var driftOf = (live, props) => fieldDrift(attributesOf(live, props), props);
185
222
 
223
+ // src/unifi/network-scope.ts
224
+ import * as Effect3 from "effect/Effect";
225
+ var SETTABLE_KEYS = ["ipv6Configuration"];
226
+ var REMOVABLE_KEYS = ["ipv6Configuration"];
227
+ var IMMUTABLE_KEYS = [
228
+ "zoneId",
229
+ "vlanId",
230
+ "management",
231
+ "deviceId"
232
+ ];
233
+ var isIn = (list) => (key) => list.includes(key);
234
+ var checkNetworkWriteScope = (live, patch, props) => {
235
+ const type = "Unifi.Network";
236
+ const identity = `sites/${props.siteId}/networks/${props.networkId}`;
237
+ if (live.default === true) {
238
+ return Effect3.fail(new UnifiManagementNetworkRefused({ type, identity }));
239
+ }
240
+ const touched = [...Object.keys(patch.set), ...patch.unset.map(String)];
241
+ const immutable = touched.filter(isIn(IMMUTABLE_KEYS));
242
+ if (immutable.length > 0) {
243
+ return Effect3.fail(new UnifiImmutableFieldChanged({ type, identity, fields: immutable }));
244
+ }
245
+ const removable = isIn(REMOVABLE_KEYS);
246
+ const notRemovable = patch.unset.map(String).filter((k) => !removable(k));
247
+ if (notRemovable.length > 0) {
248
+ return Effect3.fail(new UnifiFieldNotRemovable({ type, identity, fields: notRemovable }));
249
+ }
250
+ const settable = isIn(SETTABLE_KEYS);
251
+ const notSettable = Object.keys(patch.set).filter((k) => !settable(k));
252
+ if (notSettable.length > 0) {
253
+ return Effect3.fail(new UnifiFieldNotSettable({ type, identity, fields: notSettable }));
254
+ }
255
+ return Effect3.void;
256
+ };
257
+
258
+ // src/unifi/network-update.ts
259
+ import * as networks from "@distilled.cloud/unifi-network/networks";
260
+ import * as Retry from "@distilled.cloud/unifi-network/Retry";
261
+ import { deepEqual as deepEqual2 } from "alchemy/Diff";
262
+ import * as Effect4 from "effect/Effect";
263
+ var SERVER_FIELDS = ["id", "default", "metadata"];
264
+ var withoutServerFields = (live) => {
265
+ const server = new Set(SERVER_FIELDS);
266
+ const rest = {};
267
+ for (const [key, value] of Object.entries(live)) {
268
+ if (!server.has(key))
269
+ rest[key] = value;
270
+ }
271
+ return rest;
272
+ };
273
+ var mergeForUpdate = (live, patch, props) => {
274
+ const rest = withoutServerFields(live);
275
+ const body = { ...rest, ...patch.set };
276
+ for (const key of patch.unset)
277
+ delete body[key];
278
+ if (deepEqual2(body, rest, { stripNullish: true })) {
279
+ return Effect4.fail(new UnifiUpdateWouldBeNoop({
280
+ type: "Unifi.Network",
281
+ identity: `sites/${props.siteId}/networks/${props.networkId}`
282
+ }));
283
+ }
284
+ return Effect4.succeed({
285
+ ...body,
286
+ siteId: props.siteId,
287
+ networkId: live.id
288
+ });
289
+ };
290
+ var writeNetwork = (live, patch, props) => checkNetworkWriteScope(live, patch, props).pipe(Effect4.andThen(mergeForUpdate(live, patch, props)), Effect4.flatMap((body) => networks.updateNetwork(body).pipe(Retry.none)));
291
+
292
+ // src/unifi/resource.ts
293
+ import { Unowned } from "alchemy/AdoptPolicy";
294
+ import { isResolved } from "alchemy/Diff";
295
+ import { Credentials, CredentialsFromEnv } from "@distilled.cloud/unifi-network/Credentials";
296
+ import * as Effect6 from "effect/Effect";
297
+
298
+ // src/unifi/update-reconcile.ts
299
+ import { deepEqual as deepEqual3 } from "alchemy/Diff";
300
+ import * as Effect5 from "effect/Effect";
301
+ var computePatch = (news, olds, patchKeys) => {
302
+ const set = {};
303
+ const unset = [];
304
+ for (const key of patchKeys) {
305
+ if (deepEqual3(news[key], olds[key], { stripNullish: true }))
306
+ continue;
307
+ if (news[key] == null)
308
+ unset.push(key);
309
+ else
310
+ set[key] = news[key];
311
+ }
312
+ return { set, unset };
313
+ };
314
+ var updateReconcile = (spec, { news, olds, output, note }) => Effect5.gen(function* () {
315
+ const identity = spec.describe(news);
316
+ const live = yield* spec.fetchLive(news);
317
+ if (live === undefined)
318
+ return yield* refuseWrite(spec.type, identity, "create");
319
+ const attrs = spec.attributes(live, news);
320
+ if (spec.matches(attrs, news))
321
+ return attrs;
322
+ const update = spec.update;
323
+ if (update === undefined || olds === undefined || output === undefined) {
324
+ return yield* refuseWrite(spec.type, identity, "update");
325
+ }
326
+ const previous = spec.describe(olds);
327
+ if (previous !== identity) {
328
+ return yield* new UnifiIdentityChanged({ type: spec.type, identity, previous });
329
+ }
330
+ const drifted = update.driftOf(live, olds).map((d) => d.field);
331
+ if (drifted.length > 0) {
332
+ return yield* new UnifiLiveDriftedSinceDeploy({
333
+ type: spec.type,
334
+ identity,
335
+ fields: drifted
336
+ });
337
+ }
338
+ const patch = computePatch(news, olds, update.patchKeys);
339
+ if (Object.keys(patch.set).length === 0 && patch.unset.length === 0) {
340
+ return yield* refuseWrite(spec.type, identity, "update");
341
+ }
342
+ yield* update.checkScope(live, patch, news);
343
+ yield* note(`${spec.type} ${identity}: PUT changes [${Object.keys(patch.set).join(", ")}] ` + `removes [${patch.unset.map(String).join(", ")}]`);
344
+ const written = yield* update.write(live, patch, news);
345
+ const writtenAttrs = spec.attributes(written, news);
346
+ if (spec.matches(writtenAttrs, news))
347
+ return writtenAttrs;
348
+ return yield* new UnifiUpdateDidNotConverge({
349
+ type: spec.type,
350
+ identity,
351
+ fields: update.driftOf(written, news).map((d) => d.field)
352
+ });
353
+ });
354
+
355
+ // src/unifi/resource.ts
356
+ var unifiOperations = (spec) => {
357
+ const read = (props) => spec.fetchLive(props).pipe(Effect6.map((live) => live === undefined ? undefined : spec.attributes(live, props)));
358
+ return {
359
+ read,
360
+ readHandler: ({ olds, output }) => Effect6.gen(function* () {
361
+ const attrs = yield* read(olds);
362
+ if (attrs === undefined)
363
+ return;
364
+ return output !== undefined ? attrs : Unowned(attrs);
365
+ }),
366
+ diff: (news, output) => Effect6.gen(function* () {
367
+ if (output === undefined || !isResolved(news))
368
+ return;
369
+ const live = yield* read(news);
370
+ if (live === undefined)
371
+ return { action: "update" };
372
+ return spec.matches(live, news) ? { action: "noop" } : { action: "update" };
373
+ }),
374
+ reconcile: (news, rest = {}) => updateReconcile(spec, {
375
+ news,
376
+ olds: rest.olds,
377
+ output: rest.output,
378
+ note: rest.note ?? (() => Effect6.void)
379
+ }),
380
+ destroy: (olds) => refuseWrite(spec.type, spec.describe(olds), "delete")
381
+ };
382
+ };
383
+ var unifiHandlers = (spec) => {
384
+ const ops = unifiOperations(spec);
385
+ const withCredentials = (effect, allow = []) => Effect6.gen(function* () {
386
+ const { apiBaseUrl } = yield* yield* Credentials;
387
+ return yield* effect.pipe(Effect6.provide(guardedHttpClient(allow, apiBaseUrl)));
388
+ }).pipe(Effect6.provide(CredentialsFromEnv));
389
+ return {
390
+ list: () => Effect6.succeed([]),
391
+ read: (args) => withCredentials(ops.readHandler(args)),
392
+ diff: (args) => withCredentials(ops.diff(args.news, args.output)),
393
+ reconcile: (args) => withCredentials(ops.reconcile(args.news, {
394
+ olds: args.olds,
395
+ output: args.output,
396
+ note: (message) => args.session.note(message)
397
+ }), spec.update ? [spec.update.allowedWrite(args.news)] : []),
398
+ delete: (args) => withCredentials(ops.destroy(args.olds))
399
+ };
400
+ };
401
+
186
402
  // src/unifi/network.ts
187
403
  var UnifiNetwork = Resource("Unifi.Network", {
188
404
  defaultRemovalPolicy: "retain"
189
405
  });
190
406
  var network = (id, props) => UnifiNetwork(id, props).pipe(adopt(true));
407
+ var NETWORK_PATCH_KEYS = [
408
+ "name",
409
+ "enabled",
410
+ "management",
411
+ "vlanId",
412
+ "dhcpGuarding",
413
+ "cellularBackupEnabled",
414
+ "internetAccessEnabled",
415
+ "ipv4Configuration",
416
+ "ipv6Configuration",
417
+ "isolationEnabled",
418
+ "mdnsForwardingEnabled",
419
+ "zoneId",
420
+ "deviceId"
421
+ ];
422
+ var networkAllowedWrite = (props) => ({
423
+ method: "PUT",
424
+ tail: `/v1/sites/${props.siteId}/networks/${props.networkId}`
425
+ });
191
426
  var spec = {
192
427
  type: "Unifi.Network",
193
428
  describe: (props) => `sites/${props.siteId}/networks/${props.networkId}`,
194
- fetchLive: (props) => networks.getNetworkDetails({ siteId: props.siteId, networkId: props.networkId }).pipe(Effect3.catchTag("NotFound", () => Effect3.succeed(undefined))),
429
+ fetchLive: (props) => networks2.getNetworkDetails({ siteId: props.siteId, networkId: props.networkId }).pipe(Effect7.catchTag("NotFound", () => Effect7.succeed(undefined))),
195
430
  attributes: attributesOf,
196
- matches
431
+ matches,
432
+ update: {
433
+ allowedWrite: networkAllowedWrite,
434
+ patchKeys: NETWORK_PATCH_KEYS,
435
+ driftOf,
436
+ checkScope: checkNetworkWriteScope,
437
+ write: writeNetwork
438
+ }
197
439
  };
198
440
  var handlers = unifiHandlers(spec);
199
- var UnifiNetworkProvider = () => Provider.effect(UnifiNetwork, Effect3.succeed(UnifiNetwork.Provider.of(handlers)));
441
+ var UnifiNetworkProvider = () => Provider.effect(UnifiNetwork, Effect7.succeed(UnifiNetwork.Provider.of(handlers)));
200
442
  // src/unifi/firewall-zone.ts
201
443
  import { Resource as Resource2 } from "alchemy";
202
444
  import { adopt as adopt2 } from "alchemy/AdoptPolicy";
203
445
  import * as Provider2 from "alchemy/Provider";
204
446
  import * as firewall from "@distilled.cloud/unifi-network/firewall";
205
- import * as Effect4 from "effect/Effect";
447
+ import * as Effect8 from "effect/Effect";
206
448
 
207
449
  // src/unifi/firewall-zone-form.ts
208
450
  var sortedSet2 = (values) => [...new Set(values)].sort();
@@ -239,18 +481,18 @@ var firewallZone = (id, props) => UnifiFirewallZone(id, props).pipe(adopt2(true)
239
481
  var spec2 = {
240
482
  type: "Unifi.FirewallZone",
241
483
  describe: (props) => `sites/${props.siteId}/firewall/zones/${props.firewallZoneId}`,
242
- fetchLive: (props) => firewall.getFirewallZone({ siteId: props.siteId, firewallZoneId: props.firewallZoneId }).pipe(Effect4.catchTag("NotFound", () => Effect4.succeed(undefined))),
484
+ fetchLive: (props) => firewall.getFirewallZone({ siteId: props.siteId, firewallZoneId: props.firewallZoneId }).pipe(Effect8.catchTag("NotFound", () => Effect8.succeed(undefined))),
243
485
  attributes: attributesOf2,
244
486
  matches: matches2
245
487
  };
246
488
  var handlers2 = unifiHandlers(spec2);
247
- var UnifiFirewallZoneProvider = () => Provider2.effect(UnifiFirewallZone, Effect4.succeed(UnifiFirewallZone.Provider.of(handlers2)));
489
+ var UnifiFirewallZoneProvider = () => Provider2.effect(UnifiFirewallZone, Effect8.succeed(UnifiFirewallZone.Provider.of(handlers2)));
248
490
  // src/unifi/dns-policy.ts
249
491
  import { Resource as Resource3 } from "alchemy";
250
492
  import { adopt as adopt3 } from "alchemy/AdoptPolicy";
251
493
  import * as Provider3 from "alchemy/Provider";
252
494
  import * as dnsPolicies from "@distilled.cloud/unifi-network/dns_policies";
253
- import * as Effect5 from "effect/Effect";
495
+ import * as Effect9 from "effect/Effect";
254
496
 
255
497
  // src/unifi/dns-policy-form.ts
256
498
  var attributesOf3 = (live, props) => ({
@@ -329,18 +571,18 @@ var dnsPolicy = (id, props) => UnifiDnsPolicy(id, props).pipe(adopt3(true));
329
571
  var spec3 = {
330
572
  type: "Unifi.DnsPolicy",
331
573
  describe: (props) => `sites/${props.siteId}/dns/policies/${props.dnsPolicyId}`,
332
- fetchLive: (props) => dnsPolicies.getDnsPolicy({ siteId: props.siteId, dnsPolicyId: props.dnsPolicyId }).pipe(Effect5.catchTag("NotFound", () => Effect5.succeed(undefined))),
574
+ fetchLive: (props) => dnsPolicies.getDnsPolicy({ siteId: props.siteId, dnsPolicyId: props.dnsPolicyId }).pipe(Effect9.catchTag("NotFound", () => Effect9.succeed(undefined))),
333
575
  attributes: attributesOf3,
334
576
  matches: matches3
335
577
  };
336
578
  var handlers3 = unifiHandlers(spec3);
337
- var UnifiDnsPolicyProvider = () => Provider3.effect(UnifiDnsPolicy, Effect5.succeed(UnifiDnsPolicy.Provider.of(handlers3)));
579
+ var UnifiDnsPolicyProvider = () => Provider3.effect(UnifiDnsPolicy, Effect9.succeed(UnifiDnsPolicy.Provider.of(handlers3)));
338
580
  // src/unifi/acl-rule.ts
339
581
  import { Resource as Resource4 } from "alchemy";
340
582
  import { adopt as adopt4 } from "alchemy/AdoptPolicy";
341
583
  import * as Provider4 from "alchemy/Provider";
342
584
  import * as aclRules from "@distilled.cloud/unifi-network/access_control_acl_rules";
343
- import * as Effect6 from "effect/Effect";
585
+ import * as Effect10 from "effect/Effect";
344
586
 
345
587
  // src/unifi/acl-rule-form.ts
346
588
  var sortedSet3 = (values) => [...new Set(values)].sort();
@@ -416,18 +658,18 @@ var aclRule = (id, props) => UnifiAclRule(id, props).pipe(adopt4(true));
416
658
  var spec4 = {
417
659
  type: "Unifi.AclRule",
418
660
  describe: (props) => `sites/${props.siteId}/acl-rules/${props.aclRuleId}`,
419
- fetchLive: (props) => aclRules.getAclRule({ siteId: props.siteId, aclRuleId: props.aclRuleId }).pipe(Effect6.catchTag("NotFound", () => Effect6.succeed(undefined))),
661
+ fetchLive: (props) => aclRules.getAclRule({ siteId: props.siteId, aclRuleId: props.aclRuleId }).pipe(Effect10.catchTag("NotFound", () => Effect10.succeed(undefined))),
420
662
  attributes: attributesOf4,
421
663
  matches: matches4
422
664
  };
423
665
  var handlers4 = unifiHandlers(spec4);
424
- var UnifiAclRuleProvider = () => Provider4.effect(UnifiAclRule, Effect6.succeed(UnifiAclRule.Provider.of(handlers4)));
666
+ var UnifiAclRuleProvider = () => Provider4.effect(UnifiAclRule, Effect10.succeed(UnifiAclRule.Provider.of(handlers4)));
425
667
  // src/unifi/acl-rule-ordering.ts
426
668
  import { Resource as Resource5 } from "alchemy";
427
669
  import { adopt as adopt5 } from "alchemy/AdoptPolicy";
428
670
  import * as Provider5 from "alchemy/Provider";
429
671
  import * as aclRules2 from "@distilled.cloud/unifi-network/access_control_acl_rules";
430
- import * as Effect7 from "effect/Effect";
672
+ import * as Effect11 from "effect/Effect";
431
673
 
432
674
  // src/unifi/acl-rule-ordering-form.ts
433
675
  var attributesOf5 = (live, props) => ({
@@ -456,18 +698,18 @@ var aclRuleOrdering = (id, props) => UnifiAclRuleOrdering(id, props).pipe(adopt5
456
698
  var spec5 = {
457
699
  type: "Unifi.AclRuleOrdering",
458
700
  describe: (props) => `sites/${props.siteId}/acl-rules/ordering`,
459
- fetchLive: (props) => aclRules2.getAclRuleOrdering({ siteId: props.siteId }).pipe(Effect7.catchTag("NotFound", () => Effect7.succeed(undefined))),
701
+ fetchLive: (props) => aclRules2.getAclRuleOrdering({ siteId: props.siteId }).pipe(Effect11.catchTag("NotFound", () => Effect11.succeed(undefined))),
460
702
  attributes: attributesOf5,
461
703
  matches: matches5
462
704
  };
463
705
  var handlers5 = unifiHandlers(spec5);
464
- var UnifiAclRuleOrderingProvider = () => Provider5.effect(UnifiAclRuleOrdering, Effect7.succeed(UnifiAclRuleOrdering.Provider.of(handlers5)));
706
+ var UnifiAclRuleOrderingProvider = () => Provider5.effect(UnifiAclRuleOrdering, Effect11.succeed(UnifiAclRuleOrdering.Provider.of(handlers5)));
465
707
  // src/unifi/wifi-broadcast.ts
466
708
  import { Resource as Resource6 } from "alchemy";
467
709
  import { adopt as adopt6 } from "alchemy/AdoptPolicy";
468
710
  import * as Provider6 from "alchemy/Provider";
469
711
  import * as wifiBroadcasts from "@distilled.cloud/unifi-network/wifi_broadcasts";
470
- import * as Effect9 from "effect/Effect";
712
+ import * as Effect13 from "effect/Effect";
471
713
 
472
714
  // src/unifi/wifi-broadcast-form.ts
473
715
  var sortedStrings = (values) => [...new Set(values)].sort();
@@ -551,7 +793,7 @@ var driftOf6 = (live, props) => fieldDrift6(attributesOf6(live, props), props);
551
793
 
552
794
  // src/unifi/paginate.ts
553
795
  import * as Data2 from "effect/Data";
554
- import * as Effect8 from "effect/Effect";
796
+ import * as Effect12 from "effect/Effect";
555
797
  var MAX_PAGES = 1000;
556
798
 
557
799
  class UnifiPaginationInconsistent extends Data2.TaggedError("UnifiPaginationInconsistent") {
@@ -560,7 +802,7 @@ class UnifiPaginationInconsistent extends Data2.TaggedError("UnifiPaginationInco
560
802
  }
561
803
  }
562
804
  var numOrPlaceholder = (value) => typeof value === "number" && Number.isFinite(value) ? String(value) : "<non-numeric>";
563
- var pageAll = (fetchPage, request) => Effect8.gen(function* () {
805
+ var pageAll = (fetchPage, request) => Effect12.gen(function* () {
564
806
  const rows = [];
565
807
  const startOffset = request.offset ?? 0;
566
808
  let offset = startOffset;
@@ -611,18 +853,18 @@ var wifiBroadcast = (id, props) => UnifiWifiBroadcast(id, props).pipe(adopt6(tru
611
853
  var spec6 = {
612
854
  type: "Unifi.WifiBroadcast",
613
855
  describe: (props) => `sites/${props.siteId}/wifi/broadcasts/${props.wifiBroadcastId}`,
614
- fetchLive: (props) => pageAll(wifiBroadcasts.getWifiBroadcastPage, { siteId: props.siteId }).pipe(Effect9.catchTag("NotFound", () => Effect9.succeed(undefined)), Effect9.map((rows) => rows?.find((row) => row.id === props.wifiBroadcastId))),
856
+ fetchLive: (props) => pageAll(wifiBroadcasts.getWifiBroadcastPage, { siteId: props.siteId }).pipe(Effect13.catchTag("NotFound", () => Effect13.succeed(undefined)), Effect13.map((rows) => rows?.find((row) => row.id === props.wifiBroadcastId))),
615
857
  attributes: attributesOf6,
616
858
  matches: matches6
617
859
  };
618
860
  var handlers6 = unifiHandlers(spec6);
619
- var UnifiWifiBroadcastProvider = () => Provider6.effect(UnifiWifiBroadcast, Effect9.succeed(UnifiWifiBroadcast.Provider.of(handlers6)));
861
+ var UnifiWifiBroadcastProvider = () => Provider6.effect(UnifiWifiBroadcast, Effect13.succeed(UnifiWifiBroadcast.Provider.of(handlers6)));
620
862
  // src/unifi/firewall-policy.ts
621
863
  import { Resource as Resource7 } from "alchemy";
622
864
  import { adopt as adopt7 } from "alchemy/AdoptPolicy";
623
865
  import * as Provider7 from "alchemy/Provider";
624
866
  import * as firewall2 from "@distilled.cloud/unifi-network/firewall";
625
- import * as Effect10 from "effect/Effect";
867
+ import * as Effect14 from "effect/Effect";
626
868
 
627
869
  // src/unifi/firewall-policy-form.ts
628
870
  var sortedSet4 = (values) => [...new Set(values)].sort();
@@ -689,18 +931,18 @@ var firewallPolicy = (id, props) => UnifiFirewallPolicy(id, props).pipe(adopt7(t
689
931
  var spec7 = {
690
932
  type: "Unifi.FirewallPolicy",
691
933
  describe: (props) => `sites/${props.siteId}/firewall/policies/${props.firewallPolicyId}`,
692
- fetchLive: (props) => firewall2.getFirewallPolicy({ siteId: props.siteId, firewallPolicyId: props.firewallPolicyId }).pipe(Effect10.catchTag("NotFound", () => Effect10.succeed(undefined))),
934
+ fetchLive: (props) => firewall2.getFirewallPolicy({ siteId: props.siteId, firewallPolicyId: props.firewallPolicyId }).pipe(Effect14.catchTag("NotFound", () => Effect14.succeed(undefined))),
693
935
  attributes: attributesOf7,
694
936
  matches: matches7
695
937
  };
696
938
  var handlers7 = unifiHandlers(spec7);
697
- var UnifiFirewallPolicyProvider = () => Provider7.effect(UnifiFirewallPolicy, Effect10.succeed(UnifiFirewallPolicy.Provider.of(handlers7)));
939
+ var UnifiFirewallPolicyProvider = () => Provider7.effect(UnifiFirewallPolicy, Effect14.succeed(UnifiFirewallPolicy.Provider.of(handlers7)));
698
940
  // src/unifi/firewall-policy-ordering.ts
699
941
  import { Resource as Resource8 } from "alchemy";
700
942
  import { adopt as adopt8 } from "alchemy/AdoptPolicy";
701
943
  import * as Provider8 from "alchemy/Provider";
702
944
  import * as firewall3 from "@distilled.cloud/unifi-network/firewall";
703
- import * as Effect11 from "effect/Effect";
945
+ import * as Effect15 from "effect/Effect";
704
946
 
705
947
  // src/unifi/firewall-policy-ordering-form.ts
706
948
  var attributesOf8 = (live, props) => ({
@@ -742,18 +984,18 @@ var spec8 = {
742
984
  siteId: props.siteId,
743
985
  sourceFirewallZoneId: props.sourceFirewallZoneId,
744
986
  destinationFirewallZoneId: props.destinationFirewallZoneId
745
- }).pipe(Effect11.catchTag("NotFound", () => Effect11.succeed(undefined))),
987
+ }).pipe(Effect15.catchTag("NotFound", () => Effect15.succeed(undefined))),
746
988
  attributes: attributesOf8,
747
989
  matches: matches8
748
990
  };
749
991
  var handlers8 = unifiHandlers(spec8);
750
- var UnifiFirewallPolicyOrderingProvider = () => Provider8.effect(UnifiFirewallPolicyOrdering, Effect11.succeed(UnifiFirewallPolicyOrdering.Provider.of(handlers8)));
992
+ var UnifiFirewallPolicyOrderingProvider = () => Provider8.effect(UnifiFirewallPolicyOrdering, Effect15.succeed(UnifiFirewallPolicyOrdering.Provider.of(handlers8)));
751
993
  // src/unifi/traffic-matching-list.ts
752
994
  import { Resource as Resource9 } from "alchemy";
753
995
  import { adopt as adopt9 } from "alchemy/AdoptPolicy";
754
996
  import * as Provider9 from "alchemy/Provider";
755
997
  import * as trafficMatchingLists from "@distilled.cloud/unifi-network/traffic_matching_lists";
756
- import * as Effect12 from "effect/Effect";
998
+ import * as Effect16 from "effect/Effect";
757
999
 
758
1000
  // src/unifi/traffic-matching-list-form.ts
759
1001
  var attributesOf9 = (live, props) => ({
@@ -789,35 +1031,45 @@ var spec9 = {
789
1031
  fetchLive: (props) => trafficMatchingLists.getTrafficMatchingList({
790
1032
  siteId: props.siteId,
791
1033
  trafficMatchingListId: props.trafficMatchingListId
792
- }).pipe(Effect12.catchTag("NotFound", () => Effect12.succeed(undefined))),
1034
+ }).pipe(Effect16.catchTag("NotFound", () => Effect16.succeed(undefined))),
793
1035
  attributes: attributesOf9,
794
1036
  matches: matches9
795
1037
  };
796
1038
  var handlers9 = unifiHandlers(spec9);
797
- var UnifiTrafficMatchingListProvider = () => Provider9.effect(UnifiTrafficMatchingList, Effect12.succeed(UnifiTrafficMatchingList.Provider.of(handlers9)));
1039
+ var UnifiTrafficMatchingListProvider = () => Provider9.effect(UnifiTrafficMatchingList, Effect16.succeed(UnifiTrafficMatchingList.Provider.of(handlers9)));
798
1040
  // src/unifi/providers.ts
799
1041
  import * as Layer from "effect/Layer";
800
1042
  var providers = () => Layer.mergeAll(UnifiNetworkProvider(), UnifiFirewallZoneProvider(), UnifiDnsPolicyProvider(), UnifiAclRuleProvider(), UnifiAclRuleOrderingProvider(), UnifiWifiBroadcastProvider(), UnifiFirewallPolicyProvider(), UnifiFirewallPolicyOrderingProvider(), UnifiTrafficMatchingListProvider());
801
1043
  export {
802
1044
  GetOnlyHttpClient,
803
1045
  UNIFI_READ_ONLY_POLICY,
1046
+ UNIFI_WRITE_POLICY,
804
1047
  UnifiAclRule,
805
1048
  UnifiAclRuleOrdering,
806
1049
  UnifiAclRuleOrderingProvider,
807
1050
  UnifiAclRuleProvider,
808
1051
  UnifiDnsPolicy,
809
1052
  UnifiDnsPolicyProvider,
1053
+ UnifiFieldNotRemovable,
1054
+ UnifiFieldNotSettable,
810
1055
  UnifiFirewallPolicy,
811
1056
  UnifiFirewallPolicyOrdering,
812
1057
  UnifiFirewallPolicyOrderingProvider,
813
1058
  UnifiFirewallPolicyProvider,
814
1059
  UnifiFirewallZone,
815
1060
  UnifiFirewallZoneProvider,
1061
+ UnifiIdentityChanged,
1062
+ UnifiImmutableFieldChanged,
1063
+ UnifiLiveDriftedSinceDeploy,
1064
+ UnifiManagementNetworkRefused,
816
1065
  UnifiNetwork,
817
1066
  UnifiNetworkProvider,
818
1067
  UnifiNonGetRequest,
1068
+ UnifiRefusedRequest,
819
1069
  UnifiTrafficMatchingList,
820
1070
  UnifiTrafficMatchingListProvider,
1071
+ UnifiUpdateDidNotConverge,
1072
+ UnifiUpdateWouldBeNoop,
821
1073
  UnifiWifiBroadcast,
822
1074
  UnifiWifiBroadcastProvider,
823
1075
  UnifiWriteRefused,
@@ -842,6 +1094,7 @@ export {
842
1094
  driftOf8 as firewallPolicyOrderingDriftOf,
843
1095
  firewallZone,
844
1096
  driftOf2 as firewallZoneDriftOf,
1097
+ guardedHttpClient,
845
1098
  network,
846
1099
  driftOf as networkDriftOf,
847
1100
  providers,
@@ -851,5 +1104,5 @@ export {
851
1104
  driftOf6 as wifiBroadcastDriftOf
852
1105
  };
853
1106
 
854
- //# debugId=5A3AB5CE8957273E64756E2164756E21
1107
+ //# debugId=04975D29327C722564756E2164756E21
855
1108
  //# sourceMappingURL=index.js.map