@alchemy-unifi/network 0.0.1 → 0.1.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.
@@ -0,0 +1,929 @@
1
+ import * as Schema from "effect/Schema";
2
+ import * as Sdk from "@alchemy-unifi/network-sdk";
3
+ import * as Effect from "effect/Effect";
4
+ import * as HttpClient from "effect/http/HttpClient";
5
+ import * as Redacted from "effect/Redacted";
6
+ import * as Context from "effect/Context";
7
+ import * as Layer from "effect/Layer";
8
+ import "effect/Scope";
9
+ import * as Provider from "alchemy/Provider";
10
+ import { Resource } from "alchemy/Resource";
11
+ import * as Deferred from "effect/Deferred";
12
+ import { UserFacingError } from "alchemy/UserFacingError";
13
+ import { State, StateStoreError } from "alchemy/State/State";
14
+ export declare const UNIFI_NETWORK_AUTH_PROVIDER_NAME = "UnifiNetwork";
15
+ /** Values stored in a profile for the `UnifiNetwork` auth provider. */
16
+ export declare const UnifiNetworkAuthConfigSchema: Schema.Union<readonly [Schema.Struct<{
17
+ readonly method: Schema.Literal<"local">;
18
+ readonly url: Schema.String;
19
+ readonly apiKey: Schema.String;
20
+ readonly certFingerprint: Schema.optional<Schema.String>;
21
+ readonly insecure: Schema.optional<Schema.Boolean>;
22
+ readonly site: Schema.optional<Schema.String>;
23
+ }>, Schema.Struct<{
24
+ readonly method: Schema.Literal<"remote">;
25
+ readonly consoleId: Schema.String;
26
+ readonly apiKey: Schema.String;
27
+ readonly site: Schema.optional<Schema.String>;
28
+ }>]>;
29
+ export type UnifiNetworkAuthConfig = typeof UnifiNetworkAuthConfigSchema.Type;
30
+ export declare const UnifiNetworkAuth: import("effect/Layer").Layer<never, never, import("alchemy").AuthProviders | import("effect/FileSystem").FileSystem | import("effect/Path").Path>;
31
+ /** A console or self-hosted controller reached directly, e.g. `https://192.168.1.1`. */
32
+ export interface LocalConnection {
33
+ readonly type: "local";
34
+ /** Origin of the console, without the Integration API path. */
35
+ readonly url: string;
36
+ readonly apiKey: Redacted.Redacted<string>;
37
+ /**
38
+ * Accept the server only if a certificate in its chain has this SHA-256
39
+ * fingerprint (`AB:CD:…`), whether or not Node trusts it.
40
+ */
41
+ readonly certFingerprint?: string | undefined;
42
+ /** Skip TLS verification entirely. */
43
+ readonly insecure: boolean;
44
+ }
45
+ /** A console reached through the `api.ui.com` cloud connector. */
46
+ export interface RemoteConnection {
47
+ readonly type: "remote";
48
+ readonly consoleId: string;
49
+ readonly apiKey: Redacted.Redacted<string>;
50
+ }
51
+ export type Connection = LocalConnection | RemoteConnection;
52
+ /** Where credentials came from: a named Alchemy profile, or `UNIFI_*` environment variables. */
53
+ export type CredentialsSource = {
54
+ readonly type: "profile";
55
+ readonly profile: string;
56
+ } | {
57
+ readonly type: "env";
58
+ };
59
+ export interface ResolvedCredentials {
60
+ readonly connection: Connection;
61
+ /**
62
+ * Site used by resources that don't set `site`, by internal reference
63
+ * (e.g. `default`) or name. Unset means the console's only site; it's only
64
+ * needed on consoles hosting several.
65
+ */
66
+ readonly site?: string | undefined;
67
+ readonly source: CredentialsSource;
68
+ }
69
+ declare const UnifiNetworkCredentials_base: Context.ServiceClass<UnifiNetworkCredentials, "UnifiNetwork::Credentials", Effect.Effect<ResolvedCredentials, never, never>>;
70
+ /**
71
+ * Lazily-resolved credentials for the active profile (or the environment).
72
+ * The service value is an Effect so nothing resolves at Layer construction;
73
+ * handlers run `yield* yield* UnifiNetworkCredentials`.
74
+ */
75
+ export declare class UnifiNetworkCredentials extends UnifiNetworkCredentials_base {
76
+ /** Alchemy's tag for credentials services, which lets stack bodies require this one. */
77
+ readonly kind: "Credentials";
78
+ }
79
+ /**
80
+ * Resolves {@link UnifiNetworkCredentials} through the `UnifiNetwork` auth
81
+ * provider: environment variables when they're all set, otherwise the
82
+ * selected Alchemy profile. Resolution is deferred to first use and cached.
83
+ */
84
+ export declare const fromAuthProvider: () => Layer.Layer<UnifiNetworkCredentials, never, import("alchemy").AuthProviders | import("alchemy/Auth/Profile").ProfileStore>;
85
+ /** A connection whose base URL has been found and whose API key works. */
86
+ interface Established {
87
+ readonly baseUrl: string;
88
+ readonly applicationVersion: string;
89
+ /** What SDK operations need: credentials for {@link baseUrl} and a client with the connection's TLS settings. */
90
+ readonly services: Context.Context<Sdk.Credentials | HttpClient.HttpClient>;
91
+ }
92
+ declare const UnifiNetworkConnection_base: Context.ServiceClass<UnifiNetworkConnection, "UnifiNetwork::Connection", Effect.Effect<Established, Sdk.ConfigError, never>>;
93
+ /**
94
+ * The profile's established connection, created on first use and shared for
95
+ * the rest of the run. Provider-private: its `HttpClient` and SDK
96
+ * `Credentials` never enter the stack's context, so they can't replace other
97
+ * providers' clients.
98
+ */
99
+ export declare class UnifiNetworkConnection extends UnifiNetworkConnection_base {
100
+ /** Alchemy's tag for environment services, which lets stack bodies (and so lookups) require this one. */
101
+ readonly kind: "Environment";
102
+ }
103
+ /** Runs SDK operations against the profile's console, retrying the console's transient stale-network 422. */
104
+ export declare const withConnection: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E | Sdk.ConfigError, Exclude<R, Sdk.Credentials | HttpClient.HttpClient> | UnifiNetworkConnection>;
105
+ declare namespace ReadCache_d_exports {
106
+ export { Key, UnifiNetworkReadCache, cached, invalidate, invalidating, key, layer };
107
+ }
108
+ declare const UnifiNetworkReadCache_base: Context.ServiceClass<UnifiNetworkReadCache, "UnifiNetwork::ReadCache", {
109
+ readonly run: object;
110
+ }>;
111
+ /**
112
+ * Per-run memo of API reads, so a plan touching many resources lists each
113
+ * collection once instead of once per resource. One instance per
114
+ * `providers()` build, i.e. per CLI run or test file. The value is only an
115
+ * identity: entries live on each {@link Key}, keyed by it.
116
+ */
117
+ export declare class UnifiNetworkReadCache extends UnifiNetworkReadCache_base {
118
+ /** Alchemy's tag for environment services, which lets stack bodies (and so lookups) require this one. */
119
+ readonly kind: "Environment";
120
+ }
121
+ declare const layer: Layer.Layer<UnifiNetworkReadCache, never, never>;
122
+ /**
123
+ * One kind of cached read, e.g. a resource type's list. `scope` arguments
124
+ * separate its entries, e.g. by site ID.
125
+ */
126
+ interface Key<A, E> {
127
+ readonly name: string;
128
+ readonly entries: WeakMap<object, Map<string, Deferred.Deferred<A, E>>>;
129
+ }
130
+ declare const key: <A, E = never>(name: string) => Key<A, E>;
131
+ /**
132
+ * `load`'s result, shared by every caller in this run until invalidated.
133
+ * Concurrent callers wait for one load. Failures aren't cached.
134
+ */
135
+ declare const cached: <A, E, R>(cacheKey: Key<A, E>, scope: string, load: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R | UnifiNetworkReadCache>;
136
+ /** Drops an entry, so the next read loads afresh. */
137
+ declare const invalidate: <A, E>(cacheKey: Key<A, E>, scope: string) => Effect.Effect<void, never, UnifiNetworkReadCache>;
138
+ /** Runs a write, then invalidates the entry it affects, whether or not the write succeeded. */
139
+ declare const invalidating: <A, E>(cacheKey: Key<A, E>, scope: string) => <B, E2, R>(write: Effect.Effect<B, E2, R>) => Effect.Effect<B, E2, R | UnifiNetworkReadCache>;
140
+ declare const Providers_base: Provider.ProviderCollection<Providers, "UnifiNetwork">;
141
+ export declare class Providers extends Providers_base {}
142
+ /**
143
+ * Registers every UniFi Network resource provider, the `UnifiNetwork` auth
144
+ * provider, the lazily-resolved credentials and connection, and the per-run
145
+ * read cache. Merge
146
+ * alongside other cloud `providers()` layers in a Stack.
147
+ *
148
+ * Its requirements (`AuthProviders`, `ProfileStore`, `FileSystem`, `Path`)
149
+ * come from Alchemy's runtime; it provides only its own services.
150
+ *
151
+ * Unlike most providers this doesn't provide an `HttpClient`: ours carries
152
+ * the console's TLS settings, and lifecycle operations run in the stack's
153
+ * merged context, so it would replace other providers' clients. SDK calls
154
+ * get it through `UnifiNetworkConnection` instead.
155
+ */
156
+ export declare const providers: () => Layer.Layer<Providers | UnifiNetworkConnection | UnifiNetworkCredentials | UnifiNetworkReadCache, never, import("alchemy").AuthProviders | import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("alchemy/Auth/Profile").ProfileStore>;
157
+ /** A UniFi site: a namespace within one console. */
158
+ export interface Site {
159
+ /** The ID other Integration API calls take. */
160
+ readonly id: string;
161
+ /** Stable reference that survives renames: `default`, or a generated one like `x7k2m9q1`. */
162
+ readonly internalReference: string;
163
+ readonly name: string;
164
+ }
165
+ declare const SiteNotFound_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
166
+ readonly _tag: "SiteNotFound";
167
+ } & Readonly<A>;
168
+ export declare class SiteNotFound extends SiteNotFound_base<{
169
+ readonly site: string;
170
+ readonly available: ReadonlyArray<Site>;
171
+ }> {
172
+ readonly [UserFacingError]: true;
173
+ get message(): string;
174
+ }
175
+ declare const SiteRequired_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
176
+ readonly _tag: "SiteRequired";
177
+ } & Readonly<A>;
178
+ export declare class SiteRequired extends SiteRequired_base<{
179
+ readonly available: ReadonlyArray<Site>;
180
+ }> {
181
+ readonly [UserFacingError]: true;
182
+ get message(): string;
183
+ }
184
+ declare const AmbiguousSite_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
185
+ readonly _tag: "AmbiguousSite";
186
+ } & Readonly<A>;
187
+ export declare class AmbiguousSite extends AmbiguousSite_base<{
188
+ readonly site: string;
189
+ readonly matches: ReadonlyArray<Site>;
190
+ }> {
191
+ readonly [UserFacingError]: true;
192
+ get message(): string;
193
+ }
194
+ declare const CrossSiteReference_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
195
+ readonly _tag: "CrossSiteReference";
196
+ } & Readonly<A>;
197
+ /** A resource referenced another that lives in a different site. */
198
+ export declare class CrossSiteReference extends CrossSiteReference_base<{
199
+ readonly what: string;
200
+ readonly referencedSiteId: string;
201
+ readonly site: Site;
202
+ }> {
203
+ readonly [UserFacingError]: true;
204
+ get message(): string;
205
+ }
206
+ /** Fails if a reference carries a site ID (resources and lookups do) other than `site`'s. */
207
+ export declare const checkSameSite: (what: string, referencedSiteId: string | undefined, site: Site) => Effect.Effect<void, never, never> | Effect.Effect<never, CrossSiteReference, never>;
208
+ export declare const sitesKey: Key<readonly Site[], Sdk.UnifiNetworkOpError>;
209
+ /** The console's sites, listed once per run. */
210
+ export declare const listSites: Effect.Effect<readonly Site[], Sdk.UnifiNetworkOpError, UnifiNetworkConnection | UnifiNetworkReadCache>;
211
+ /**
212
+ * The site a resource belongs to: `site` (an ID, internal reference or
213
+ * name), else the profile's site, else the console's only site. Fails,
214
+ * listing the console's sites, when it doesn't exist or is ambiguous, so a
215
+ * plan stops before changing anything.
216
+ */
217
+ export declare const resolveSite: (site?: string | undefined) => Effect.Effect<Site, AmbiguousSite | SiteNotFound | SiteRequired | Sdk.UnifiNetworkOpError, UnifiNetworkConnection | UnifiNetworkCredentials | UnifiNetworkReadCache>;
218
+ export declare const DEFAULT_WARN_AT = 4000000;
219
+ interface UnifiStateOptions {
220
+ /**
221
+ * Warn once a stage's stored state passes this many bytes (compressed and
222
+ * base64-encoded), as a prompt to move to a store without the console's
223
+ * limits. A console stores up to about 16 MiB directly and 7.5 MiB through
224
+ * ui.com.
225
+ *
226
+ * @default 4_000_000
227
+ */
228
+ readonly warnAt?: number;
229
+ }
230
+ /**
231
+ * State store on the console the profile points at: each stack and stage
232
+ * is a disabled Gateway → Gateway firewall policy holding the stage's
233
+ * compressed state, locked with a traffic-matching list while a run uses
234
+ * it. Nothing connects until the first state operation.
235
+ *
236
+ * The state includes secrets in plaintext, as in every Alchemy store: a
237
+ * UniFi stack's are already on the console, but another provider's
238
+ * resources in the same stack would put theirs there too.
239
+ *
240
+ * @example
241
+ * ```ts
242
+ * import * as UnifiNetwork from "@alchemy-unifi/network";
243
+ * import * as Alchemy from "alchemy";
244
+ * import * as Effect from "effect/Effect";
245
+ *
246
+ * export default Alchemy.Stack(
247
+ * "HomeNetwork",
248
+ * { providers: UnifiNetwork.providers(), state: UnifiNetwork.state() },
249
+ * Effect.gen(function* () {
250
+ * yield* UnifiNetwork.FirewallZone("iot-zone", { name: "IoT" });
251
+ * }),
252
+ * );
253
+ * ```
254
+ */
255
+ export declare const state: (options?: UnifiStateOptions) => Layer.Layer<State, never, import("alchemy").AuthProviders | import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("alchemy/Auth/Profile").ProfileStore>;
256
+ /** What resource lifecycle handlers and lookups need. `providers()` provides all of them. */
257
+ type Services = UnifiNetworkCredentials | UnifiNetworkConnection | UnifiNetworkReadCache;
258
+ /**
259
+ * Comparison of request bodies with what the console reports. Props are
260
+ * authoritative: a resource's desired body is its props with explicit
261
+ * defaults for anything unset, sent whole on every update, and the console's
262
+ * object must equal it field for field.
263
+ */
264
+ /** `Omit` applied to each member of a union, keeping the discriminant. */
265
+ type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
266
+ /**
267
+ * Props are the Integration API's DNS policy body plus `site`. `type` picks
268
+ * the kind: a record (`"A_RECORD"`, `"AAAA_RECORD"`, `"CNAME_RECORD"`,
269
+ * `"MX_RECORD"`, `"SRV_RECORD"`, `"TXT_RECORD"`) or `"FORWARD_DOMAIN"`
270
+ * (send a domain's queries to another DNS server).
271
+ */
272
+ export type DnsPolicyProps = Sdk.CreateOrUpdateDNSPolicy & {
273
+ /** Site to create the policy in. @default the profile's site, else the console's only site */
274
+ site?: string;
275
+ };
276
+ export interface DnsPolicyAttributes {
277
+ siteId: string;
278
+ dnsPolicyId: string;
279
+ type: DnsPolicyProps["type"];
280
+ domain: string;
281
+ /** The whole policy as the console reports it, so `alchemy drift` sees any change. */
282
+ config: DistributiveOmit<Sdk.DNSPolicy, "id" | "metadata">;
283
+ }
284
+ export interface DnsPolicy extends Resource<"UnifiNetwork.DnsPolicy", DnsPolicyProps, DnsPolicyAttributes, never, Providers> {}
285
+ /** A site's DNS policies, listed once per run. */
286
+ export declare const listDnsPolicies: (siteId: string) => Effect.Effect<readonly Sdk.DNSPolicy[], Sdk.UnifiNetworkOpError, UnifiNetworkConnection | UnifiNetworkReadCache>;
287
+ /**
288
+ * A local DNS record or forwarding rule on the gateway. DNS policies have no
289
+ * name, so existing ones are adopted with `--adopt` by type and domain (and,
290
+ * when a domain has several records of that type, by their values).
291
+ * Changing `type` updates the policy in place.
292
+ *
293
+ * @example
294
+ * ```ts
295
+ * import * as UnifiNetwork from "@alchemy-unifi/network";
296
+ * import * as Alchemy from "alchemy";
297
+ * import * as Effect from "effect/Effect";
298
+ *
299
+ * export default Alchemy.Stack(
300
+ * "HomeNetwork",
301
+ * { providers: UnifiNetwork.providers(), state: UnifiNetwork.state() },
302
+ * Effect.gen(function* () {
303
+ * yield* UnifiNetwork.DnsPolicy("home-assistant", {
304
+ * type: "A_RECORD",
305
+ * enabled: true,
306
+ * domain: "ha.home.arpa",
307
+ * ipv4Address: "192.168.2.20",
308
+ * ttlSeconds: 300,
309
+ * });
310
+ * }),
311
+ * );
312
+ * ```
313
+ */
314
+ export declare const DnsPolicy: import("alchemy").ResourceClass<DnsPolicy>;
315
+ export declare const DnsPolicyProvider: () => import("effect/Layer").Layer<Provider.Provider<DnsPolicy>, never, Services>;
316
+ declare const LookupNotFound_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
317
+ readonly _tag: "LookupNotFound";
318
+ } & Readonly<A>;
319
+ /** A `lookup` found nothing matching its query. */
320
+ export declare class LookupNotFound extends LookupNotFound_base<{
321
+ readonly resource: string;
322
+ readonly query: string;
323
+ readonly site: Site;
324
+ }> {
325
+ readonly [UserFacingError]: true;
326
+ get message(): string;
327
+ }
328
+ declare const LookupAmbiguous_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
329
+ readonly _tag: "LookupAmbiguous";
330
+ } & Readonly<A>;
331
+ /** A `lookup` matched several objects, e.g. networks sharing a name. */
332
+ export declare class LookupAmbiguous extends LookupAmbiguous_base<{
333
+ readonly resource: string;
334
+ readonly query: string;
335
+ readonly site: Site;
336
+ readonly count: number;
337
+ }> {
338
+ readonly [UserFacingError]: true;
339
+ get message(): string;
340
+ }
341
+ export declare const describeQuery: (query: object) => string;
342
+ /** The zones every zone-based firewall has, by their API names. */
343
+ export type BuiltinFirewallZoneName = "Internal" | "External" | "Gateway" | "Vpn" | "Hotspot" | "Dmz";
344
+ /** A built-in zone, resolved by name in whichever site the referencing resource is in. */
345
+ export interface BuiltinFirewallZone {
346
+ readonly builtinFirewallZone: BuiltinFirewallZoneName;
347
+ }
348
+ /**
349
+ * A zone: a `FirewallZone` resource, a {@link FirewallZone.lookup} result,
350
+ * `{ firewallZoneId }`, or a built-in constant such as `FirewallZone.INTERNAL`.
351
+ */
352
+ export type FirewallZoneReference = {
353
+ readonly firewallZoneId: string;
354
+ readonly siteId?: string | undefined;
355
+ } | BuiltinFirewallZone;
356
+ export interface FirewallZoneProps {
357
+ /** Site to create the zone in. @default the profile's site, else the console's only site */
358
+ site?: string;
359
+ name: string;
360
+ }
361
+ export interface FirewallZoneAttributes {
362
+ siteId: string;
363
+ firewallZoneId: string;
364
+ name: string;
365
+ }
366
+ export interface FirewallZone extends Resource<"UnifiNetwork.FirewallZone", FirewallZoneProps, FirewallZoneAttributes, never, Providers> {}
367
+ /** A site's zones, built-in ones included, listed once per run. */
368
+ export declare const listFirewallZones: (siteId: string) => Effect.Effect<readonly Sdk.FirewallZone[], Sdk.UnifiNetworkOpError, UnifiNetworkConnection | UnifiNetworkReadCache>;
369
+ /** The zone ID a reference means in `site`. */
370
+ export declare const resolveFirewallZone: (reference: FirewallZoneReference, site: Site) => Effect.Effect<string, CrossSiteReference | LookupNotFound | Sdk.UnifiNetworkOpError, UnifiNetworkConnection | UnifiNetworkReadCache>;
371
+ /**
372
+ * A custom zone in the zone-based firewall. Networks join zones through
373
+ * `Network`'s `zone` prop; the zone doesn't list its networks, so the two
374
+ * resources never fight over membership.
375
+ *
376
+ * @example
377
+ * ```ts
378
+ * import * as UnifiNetwork from "@alchemy-unifi/network";
379
+ * import * as Alchemy from "alchemy";
380
+ * import * as Effect from "effect/Effect";
381
+ *
382
+ * export default Alchemy.Stack(
383
+ * "HomeNetwork",
384
+ * { providers: UnifiNetwork.providers(), state: UnifiNetwork.state() },
385
+ * Effect.gen(function* () {
386
+ * const cameras = yield* UnifiNetwork.FirewallZone("cameras", { name: "Cameras" });
387
+ * // Networks join zones from the network's side.
388
+ * yield* UnifiNetwork.Network("cctv", {
389
+ * management: "GATEWAY",
390
+ * name: "CCTV",
391
+ * vlanId: 30,
392
+ * enabled: true,
393
+ * cellularBackupEnabled: false,
394
+ * internetAccessEnabled: false,
395
+ * isolationEnabled: true,
396
+ * zone: cameras, // or a built-in zone, e.g. UnifiNetwork.FirewallZone.INTERNAL
397
+ * ipv4Configuration: { autoScaleEnabled: false, hostIpAddress: "192.168.30.1", prefixLength: 24 },
398
+ * });
399
+ * }),
400
+ * );
401
+ * ```
402
+ */
403
+ export declare const FirewallZone: import("alchemy").ResourceClassWithMethods<FirewallZone, {
404
+ readonly INTERNAL: BuiltinFirewallZone;
405
+ readonly EXTERNAL: BuiltinFirewallZone;
406
+ readonly GATEWAY: BuiltinFirewallZone;
407
+ readonly VPN: BuiltinFirewallZone;
408
+ readonly HOTSPOT: BuiltinFirewallZone;
409
+ readonly DMZ: BuiltinFirewallZone;
410
+ /**
411
+ * Any zone in the site, built-in or custom, by name. Dies with a
412
+ * user-facing `LookupNotFound` if there's none: stack bodies can't fail
413
+ * with typed errors.
414
+ */
415
+ readonly lookup: (query: {
416
+ readonly name: string;
417
+ readonly site?: string;
418
+ }) => Effect.Effect<FirewallZoneAttributes, never, UnifiNetworkConnection | UnifiNetworkCredentials | UnifiNetworkReadCache>;
419
+ }>;
420
+ export declare const FirewallZoneProvider: () => import("effect/Layer").Layer<Provider.Provider<FirewallZone>, never, Services>;
421
+ interface SiteProp {
422
+ /** Site to create the network in. @default the profile's site, else the console's only site */
423
+ site?: string;
424
+ }
425
+ /** A device, e.g. the switch routing a switch-managed network. */
426
+ export interface DeviceReference {
427
+ readonly deviceId: string;
428
+ }
429
+ /** A network routed by the gateway: the usual kind, with DHCP, zones and internet access. */
430
+ export interface GatewayNetworkProps extends Omit<Sdk.IntegrationGatewayManagedNetworkCreateUpdateDto, "zoneId">, SiteProp {
431
+ /**
432
+ * The network's firewall zone, e.g. `FirewallZone.INTERNAL` or a
433
+ * `FirewallZone` resource. Required once the site uses the zone-based
434
+ * firewall.
435
+ */
436
+ zone?: FirewallZoneReference;
437
+ }
438
+ /** A network routed by a layer-3 switch. */
439
+ export interface SwitchNetworkProps extends Omit<Sdk.IntegrationSwitchManagedNetworkCreateUpdateDto, "deviceId">, SiteProp {
440
+ /** The switch routing the network. */
441
+ device: DeviceReference;
442
+ }
443
+ /** A VLAN that UniFi carries but doesn't route; something else on it does. */
444
+ export interface UnmanagedNetworkProps extends Sdk.IntegrationUnmanagedNetworkCreateUpdateDto, SiteProp {}
445
+ /** Props are the Integration API's network body, plus `site` and references; `management` picks the kind. */
446
+ export type NetworkProps = GatewayNetworkProps | SwitchNetworkProps | UnmanagedNetworkProps;
447
+ /** A network: a `Network` resource, a {@link Network.lookup} result, or `{ networkId }`. */
448
+ export interface NetworkReference {
449
+ readonly networkId: string;
450
+ readonly siteId?: string | undefined;
451
+ /** Set by resources and lookups. Some objects must reference the default network differently. */
452
+ readonly default?: boolean | undefined;
453
+ }
454
+ declare const DefaultNetworkReplacement_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
455
+ readonly _tag: "DefaultNetworkReplacement";
456
+ } & Readonly<A>;
457
+ /** A change that would replace the site's default network, which can't be deleted. */
458
+ export declare class DefaultNetworkReplacement extends DefaultNetworkReplacement_base<{
459
+ readonly networkName: string;
460
+ readonly reason: string;
461
+ }> {
462
+ readonly [UserFacingError]: true;
463
+ get message(): string;
464
+ }
465
+ export interface NetworkAttributes {
466
+ siteId: string;
467
+ networkId: string;
468
+ name: string;
469
+ vlanId: number;
470
+ management: NetworkProps["management"];
471
+ /** The network's firewall zone (gateway networks). */
472
+ zoneId: string | undefined;
473
+ /** Whether this is the site's default network, which is never deleted. */
474
+ default: boolean;
475
+ /** The whole network as the console reports it, so `alchemy drift` sees any change. */
476
+ config: DistributiveOmit<Sdk.NetworkDetails, "id" | "metadata" | "default">;
477
+ }
478
+ export interface Network extends Resource<"UnifiNetwork.Network", NetworkProps, NetworkAttributes, never, Providers> {}
479
+ /** A site's networks, listed once per run. */
480
+ export declare const listNetworks: (siteId: string) => Effect.Effect<readonly Sdk.NetworkOverview[], Sdk.UnifiNetworkOpError, UnifiNetworkConnection | UnifiNetworkReadCache>;
481
+ /** The network ID a reference means in `site`. */
482
+ export declare const resolveNetwork: (reference: NetworkReference, site: Site) => Effect.Effect<string, CrossSiteReference, never>;
483
+ /**
484
+ * A network (VLAN). `management` picks the kind: `"GATEWAY"` (routed by the
485
+ * gateway), `"SWITCH"` (routed by a layer-3 switch) or `"UNMANAGED"`.
486
+ * Changing it, or the site, replaces the network: the old one is deleted
487
+ * first, since VLAN IDs must be unique.
488
+ *
489
+ * Props are authoritative: every update sends the whole network, with
490
+ * explicit defaults for unset fields (`mdnsForwardingEnabled: true`, and a
491
+ * DHCP server's `domainName: ""`), so changes made in the UI are put back.
492
+ * The `config` attribute mirrors the console's object, so `alchemy drift`
493
+ * sees them.
494
+ * Existing networks are adopted by VLAN ID
495
+ * with `--adopt` (and renamed to match), including the site's default
496
+ * network (VLAN 1), which is never deleted or replaced.
497
+ *
498
+ * @example
499
+ * ```ts
500
+ * import * as UnifiNetwork from "@alchemy-unifi/network";
501
+ * import * as Alchemy from "alchemy";
502
+ * import * as Effect from "effect/Effect";
503
+ *
504
+ * export default Alchemy.Stack(
505
+ * "HomeNetwork",
506
+ * { providers: UnifiNetwork.providers(), state: UnifiNetwork.state() },
507
+ * Effect.gen(function* () {
508
+ * yield* UnifiNetwork.Network("guest", {
509
+ * management: "GATEWAY",
510
+ * name: "Guest",
511
+ * vlanId: 12,
512
+ * enabled: true,
513
+ * cellularBackupEnabled: false,
514
+ * internetAccessEnabled: true,
515
+ * isolationEnabled: true,
516
+ * zone: UnifiNetwork.FirewallZone.HOTSPOT,
517
+ * ipv4Configuration: { autoScaleEnabled: false, hostIpAddress: "192.168.12.1", prefixLength: 24 },
518
+ * });
519
+ * }),
520
+ * );
521
+ * ```
522
+ */
523
+ export declare const Network: import("alchemy").ResourceClassWithMethods<Network, {
524
+ /**
525
+ * A network in the site by name or VLAN ID. Dies with a user-facing
526
+ * `LookupNotFound` or `LookupAmbiguous`: stack bodies can't fail with typed
527
+ * errors.
528
+ */
529
+ readonly lookup: (query: ({
530
+ readonly name: string;
531
+ } | {
532
+ readonly vlanId: number;
533
+ }) & {
534
+ readonly site?: string;
535
+ }) => Effect.Effect<NetworkAttributes, never, UnifiNetworkConnection | UnifiNetworkCredentials | UnifiNetworkReadCache>;
536
+ }>;
537
+ export declare const NetworkProvider: () => import("effect/Layer").Layer<Provider.Provider<Network>, never, Services>;
538
+ /**
539
+ * Props are the Integration API's list body plus `site`. `type` picks what
540
+ * it holds: `"IPV4_ADDRESSES"`, `"IPV6_ADDRESSES"` or `"PORTS"` (no MACs).
541
+ */
542
+ export type TrafficMatchingListProps = Sdk.CreateOrUpdateTrafficMatchingList & {
543
+ /** Site to create the list in. @default the profile's site, else the console's only site */
544
+ site?: string;
545
+ };
546
+ /** A list: a `TrafficMatchingList` resource, a {@link TrafficMatchingList.lookup} result, or `{ trafficMatchingListId }`. */
547
+ export interface TrafficMatchingListReference {
548
+ readonly trafficMatchingListId: string;
549
+ readonly siteId?: string | undefined;
550
+ }
551
+ export interface TrafficMatchingListAttributes {
552
+ siteId: string;
553
+ trafficMatchingListId: string;
554
+ name: string;
555
+ type: TrafficMatchingListProps["type"];
556
+ /** The whole list as the console reports it, so `alchemy drift` sees any change. */
557
+ config: DistributiveOmit<Sdk.TrafficMatchingList, "id">;
558
+ }
559
+ export interface TrafficMatchingList extends Resource<"UnifiNetwork.TrafficMatchingList", TrafficMatchingListProps, TrafficMatchingListAttributes, never, Providers> {}
560
+ /** A site's traffic-matching lists, listed once per run. */
561
+ export declare const listTrafficMatchingLists: (siteId: string) => Effect.Effect<readonly Sdk.TrafficMatchingList[], Sdk.UnifiNetworkOpError, UnifiNetworkConnection | UnifiNetworkReadCache>;
562
+ declare const TrafficMatchingListInUse_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
563
+ readonly _tag: "TrafficMatchingListInUse";
564
+ } & Readonly<A>;
565
+ /** A list can't be deleted while something references it. */
566
+ export declare class TrafficMatchingListInUse extends TrafficMatchingListInUse_base<{
567
+ readonly listName: string;
568
+ /** Names of the firewall policies referencing it, as far as they could be found. */
569
+ readonly referencedBy: ReadonlyArray<string>;
570
+ readonly detail: string;
571
+ }> {
572
+ readonly [UserFacingError]: true;
573
+ get message(): string;
574
+ }
575
+ /** The list ID a reference means in `site`. */
576
+ export declare const resolveTrafficMatchingList: (reference: TrafficMatchingListReference, site: Site) => Effect.Effect<string, CrossSiteReference, never>;
577
+ /**
578
+ * A named list of IPv4 addresses, IPv6 addresses or ports, which firewall
579
+ * policies reference instead of repeating values inline. Names are unique
580
+ * per site, so existing lists are adopted by name with `--adopt`. Changing
581
+ * `type` updates the list in place.
582
+ *
583
+ * @example
584
+ * ```ts
585
+ * import * as UnifiNetwork from "@alchemy-unifi/network";
586
+ * import * as Alchemy from "alchemy";
587
+ * import * as Effect from "effect/Effect";
588
+ *
589
+ * export default Alchemy.Stack(
590
+ * "HomeNetwork",
591
+ * { providers: UnifiNetwork.providers(), state: UnifiNetwork.state() },
592
+ * Effect.gen(function* () {
593
+ * const haHosts = yield* UnifiNetwork.TrafficMatchingList("ha-hosts", {
594
+ * type: "IPV4_ADDRESSES",
595
+ * name: "Home Assistant hosts",
596
+ * items: [{ type: "IP_ADDRESS", value: "192.168.2.20" }],
597
+ * });
598
+ * // Reference it from a policy's filter: { type: "TRAFFIC_MATCHING_LIST", matchOpposite: false, trafficMatchingList: haHosts }
599
+ * }),
600
+ * );
601
+ * ```
602
+ */
603
+ export declare const TrafficMatchingList: import("alchemy").ResourceClassWithMethods<TrafficMatchingList, {
604
+ /**
605
+ * A list in the site by name. Dies with a user-facing `LookupNotFound`:
606
+ * stack bodies can't fail with typed errors.
607
+ */
608
+ readonly lookup: (query: {
609
+ readonly name: string;
610
+ readonly site?: string;
611
+ }) => Effect.Effect<TrafficMatchingListAttributes, never, UnifiNetworkConnection | UnifiNetworkCredentials | UnifiNetworkReadCache>;
612
+ }>;
613
+ export declare const TrafficMatchingListProvider: () => import("effect/Layer").Layer<Provider.Provider<TrafficMatchingList>, never, Services>;
614
+ /** Match the addresses or ports in a traffic-matching list (a `TrafficMatchingList` resource or reference). */
615
+ export interface TrafficMatchingListFilter {
616
+ readonly type: "TRAFFIC_MATCHING_LIST";
617
+ /** Match everything except the list's entries. */
618
+ readonly matchOpposite: boolean;
619
+ readonly trafficMatchingList: TrafficMatchingListReference;
620
+ }
621
+ /** IP addresses, ranges or subnets to match: inline, or from a list. */
622
+ export type IpAddressFilter = Sdk.IntegrationFirewallPolicySpecificIpAddressFilterDto | TrafficMatchingListFilter;
623
+ /** Ports or port ranges to match: inline, or from a list. */
624
+ export type PortFilter = Sdk.IntegrationFirewallPolicyPortValueFilterDto | TrafficMatchingListFilter;
625
+ /** Networks to match (`Network` resources or references). */
626
+ export interface NetworkFilter {
627
+ /** Match every network except these. */
628
+ readonly matchOpposite: boolean;
629
+ readonly networks: ReadonlyArray<NetworkReference>;
630
+ }
631
+ /** An API traffic filter with its ID fields replaced by references. */
632
+ type WithReferences<F> = { [K in keyof F]: K extends "ipAddressFilter" ? IpAddressFilter : K extends "portFilter" ? PortFilter : K extends "networkFilter" ? NetworkFilter : F[K]; };
633
+ type Distribute<F> = F extends unknown ? WithReferences<F> : never;
634
+ /** What a policy matches at its source, besides the zone. `type` picks the kind. */
635
+ export type FirewallPolicySourceFilter = Distribute<Sdk.FirewallPolicySourceTrafficFilter>;
636
+ /** What a policy matches at its destination, besides the zone. `type` picks the kind. */
637
+ export type FirewallPolicyDestinationFilter = Distribute<Sdk.FirewallPolicyDestinationTrafficFilter>;
638
+ export interface FirewallPolicyEndpoint<Filter> {
639
+ /** A `FirewallZone` resource or lookup, `{ firewallZoneId }`, or a built-in constant such as `FirewallZone.INTERNAL`. */
640
+ readonly zone: FirewallZoneReference;
641
+ /** Narrows the zone's traffic. Unset matches all of it. */
642
+ readonly trafficFilter?: Filter;
643
+ }
644
+ /**
645
+ * Props are the Integration API's policy body, with zones, networks and
646
+ * traffic-matching lists as references, plus `site`.
647
+ *
648
+ * Props are authoritative, like every resource's: leaving out
649
+ * `trafficFilter`, `connectionStateFilter`, `ipsecFilter`, `schedule` or
650
+ * `description` means none, even if the policy has one in the console.
651
+ */
652
+ export interface FirewallPolicyProps extends Omit<Sdk.CreateFirewallPolicyRequest, "siteId" | "source" | "destination"> {
653
+ /** Site to create the policy in. @default the profile's site, else the console's only site */
654
+ site?: string;
655
+ source: FirewallPolicyEndpoint<FirewallPolicySourceFilter>;
656
+ destination: FirewallPolicyEndpoint<FirewallPolicyDestinationFilter>;
657
+ }
658
+ /** A policy: a `FirewallPolicy` resource, a {@link FirewallPolicy.lookup} result, or `{ firewallPolicyId }`. */
659
+ export interface FirewallPolicyReference {
660
+ readonly firewallPolicyId: string;
661
+ readonly siteId?: string | undefined;
662
+ }
663
+ export interface FirewallPolicyAttributes {
664
+ siteId: string;
665
+ firewallPolicyId: string;
666
+ name: string;
667
+ sourceFirewallZoneId: string;
668
+ destinationFirewallZoneId: string;
669
+ /**
670
+ * The whole policy as the console reports it, so `alchemy drift` sees any
671
+ * change; without its `index`, which moves whenever its ordering changes.
672
+ */
673
+ config: Omit<Sdk.FirewallPolicy, "id" | "metadata" | "index">;
674
+ }
675
+ export interface FirewallPolicy extends Resource<"UnifiNetwork.FirewallPolicy", FirewallPolicyProps, FirewallPolicyAttributes, never, Providers> {}
676
+ /**
677
+ * A site's firewall policies, listed once per run: all of them, including
678
+ * system-defined ones and ones UniFi derives from UI Objects.
679
+ */
680
+ export declare const listFirewallPolicies: (siteId: string) => Effect.Effect<readonly Sdk.FirewallPolicy[], Sdk.UnifiNetworkOpError, UnifiNetworkConnection | UnifiNetworkReadCache>;
681
+ /** Policies the user made, as opposed to system-defined ones and ones UniFi derives (from UI Objects, say), which the provider never touches. */
682
+ export declare const isUserDefined: (policy: Sdk.FirewallPolicy) => boolean;
683
+ /**
684
+ * A zone-based firewall policy: allow, block or reject traffic from one
685
+ * zone to another, optionally narrowed by traffic filters. Changing its
686
+ * zones updates it in place and moves it to the new zone pair's ordering
687
+ * (see `FirewallPolicyOrdering`).
688
+ *
689
+ * Existing user-defined policies are adopted with `--adopt` by name and zone
690
+ * pair (names aren't unique, so several matches adopt nothing). Policies
691
+ * UniFi derives, e.g. from UI Objects, are invisible: never listed, adopted
692
+ * or ordered.
693
+ *
694
+ * @example
695
+ * ```ts
696
+ * import * as UnifiNetwork from "@alchemy-unifi/network";
697
+ * import * as Alchemy from "alchemy";
698
+ * import * as Effect from "effect/Effect";
699
+ *
700
+ * export default Alchemy.Stack(
701
+ * "HomeNetwork",
702
+ * { providers: UnifiNetwork.providers(), state: UnifiNetwork.state() },
703
+ * Effect.gen(function* () {
704
+ * const iot = yield* UnifiNetwork.FirewallZone("iot-zone", { name: "IoT" });
705
+ * yield* UnifiNetwork.FirewallPolicy("block-iot-to-internal", {
706
+ * name: "Block IoT to Internal",
707
+ * enabled: true,
708
+ * action: { type: "BLOCK" },
709
+ * source: { zone: iot },
710
+ * destination: { zone: UnifiNetwork.FirewallZone.INTERNAL },
711
+ * ipProtocolScope: { ipVersion: "IPV4_AND_IPV6" },
712
+ * loggingEnabled: false,
713
+ * });
714
+ * }),
715
+ * );
716
+ * ```
717
+ */
718
+ export declare const FirewallPolicy: import("alchemy").ResourceClassWithMethods<FirewallPolicy, {
719
+ /**
720
+ * A user-defined policy by name, optionally narrowed to a zone pair. Dies
721
+ * with a user-facing `LookupNotFound` or `LookupAmbiguous`: stack bodies
722
+ * can't fail with typed errors.
723
+ */
724
+ readonly lookup: (query: {
725
+ readonly name: string;
726
+ readonly source?: FirewallZoneReference;
727
+ readonly destination?: FirewallZoneReference;
728
+ readonly site?: string;
729
+ }) => Effect.Effect<FirewallPolicyAttributes, never, UnifiNetworkConnection | UnifiNetworkCredentials | UnifiNetworkReadCache>;
730
+ }>;
731
+ export declare const FirewallPolicyProvider: () => import("effect/Layer").Layer<Provider.Provider<FirewallPolicy>, never, Services>;
732
+ export interface FirewallPolicyOrderingProps {
733
+ /** Site of the zones. @default the profile's site, else the console's only site */
734
+ site?: string;
735
+ source: FirewallZoneReference;
736
+ destination: FirewallZoneReference;
737
+ /** Policies evaluated before the system-defined ones, first match wins. */
738
+ beforeSystemDefined?: ReadonlyArray<FirewallPolicyReference>;
739
+ /** Policies evaluated after the system-defined ones. */
740
+ afterSystemDefined?: ReadonlyArray<FirewallPolicyReference>;
741
+ /**
742
+ * Fail instead of keeping policies in the zone pair that aren't listed
743
+ * (e.g. made in the UI). @default false
744
+ */
745
+ exclusive?: boolean;
746
+ }
747
+ export interface FirewallPolicyOrderingAttributes {
748
+ siteId: string;
749
+ sourceFirewallZoneId: string;
750
+ destinationFirewallZoneId: string;
751
+ beforeSystemDefined: ReadonlyArray<string>;
752
+ afterSystemDefined: ReadonlyArray<string>;
753
+ /** The policies the props list, as opposed to unlisted ones kept in the order. */
754
+ listedFirewallPolicyIds: ReadonlyArray<string>;
755
+ }
756
+ export interface FirewallPolicyOrdering extends Resource<"UnifiNetwork.FirewallPolicyOrdering", FirewallPolicyOrderingProps, FirewallPolicyOrderingAttributes, never, Providers> {}
757
+ declare const InvalidFirewallPolicyOrdering_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
758
+ readonly _tag: "InvalidFirewallPolicyOrdering";
759
+ } & Readonly<A>;
760
+ /** An ordering that can't be applied as given. */
761
+ export declare class InvalidFirewallPolicyOrdering extends InvalidFirewallPolicyOrdering_base<{
762
+ readonly reason: string;
763
+ }> {
764
+ readonly [UserFacingError]: true;
765
+ get message(): string;
766
+ }
767
+ /**
768
+ * The evaluation order of user-defined policies from one zone to another.
769
+ * Listing policies makes the ordering depend on them, so it's applied after
770
+ * they're created or moved, and re-applied when the list changes.
771
+ *
772
+ * UniFi rejects an ordering that leaves out any of the pair's policies, so
773
+ * unlisted ones (e.g. made in the UI) are kept after the listed ones in each
774
+ * bucket, in their current relative order, with a warning; `exclusive: true`
775
+ * fails instead. A policy the ordering listed before but no longer does is
776
+ * kept quietly, even when exclusive: it's usually being deleted in the same
777
+ * deploy, which runs after the ordering is applied. The state store's
778
+ * policies (`UnifiNetwork.state()`, Gateway → Gateway) are kept quietly too.
779
+ * System-defined policies sit between the two buckets and
780
+ * can't be reordered; policies UniFi derives from UI Objects aren't ordered
781
+ * at all.
782
+ *
783
+ * The zone pair is the identity: an existing ordering is adopted without
784
+ * `--adopt`, and deleting the resource leaves the order as it is.
785
+ *
786
+ * @example
787
+ * ```ts
788
+ * import * as UnifiNetwork from "@alchemy-unifi/network";
789
+ * import * as Alchemy from "alchemy";
790
+ * import * as Effect from "effect/Effect";
791
+ *
792
+ * export default Alchemy.Stack(
793
+ * "HomeNetwork",
794
+ * { providers: UnifiNetwork.providers(), state: UnifiNetwork.state() },
795
+ * Effect.gen(function* () {
796
+ * const { INTERNAL } = UnifiNetwork.FirewallZone;
797
+ * const iot = yield* UnifiNetwork.FirewallZone("iot-zone", { name: "IoT" });
798
+ * const policy = { enabled: true, source: { zone: INTERNAL }, destination: { zone: iot }, loggingEnabled: false } as const;
799
+ * const allowHomeAssistant = yield* UnifiNetwork.FirewallPolicy("allow-ha", {
800
+ * ...policy,
801
+ * name: "Allow Home Assistant",
802
+ * action: { type: "ALLOW", allowReturnTraffic: true },
803
+ * ipProtocolScope: { ipVersion: "IPV4_AND_IPV6" },
804
+ * });
805
+ * const blockTheRest = yield* UnifiNetwork.FirewallPolicy("block-rest", {
806
+ * ...policy,
807
+ * name: "Block the rest",
808
+ * action: { type: "BLOCK" },
809
+ * ipProtocolScope: { ipVersion: "IPV4_AND_IPV6" },
810
+ * });
811
+ * yield* UnifiNetwork.FirewallPolicyOrdering("internal-to-iot", {
812
+ * source: INTERNAL,
813
+ * destination: iot,
814
+ * beforeSystemDefined: [allowHomeAssistant, blockTheRest],
815
+ * });
816
+ * }),
817
+ * );
818
+ * ```
819
+ */
820
+ export declare const FirewallPolicyOrdering: import("alchemy").ResourceClass<FirewallPolicyOrdering>;
821
+ export declare const FirewallPolicyOrderingProvider: () => import("effect/Layer").Layer<Provider.Provider<FirewallPolicyOrdering>, never, Services>;
822
+ /** The site's native (untagged) network. */
823
+ export interface NativeNetwork {
824
+ readonly type: "NATIVE";
825
+ }
826
+ interface CommonProps {
827
+ /** Site to create the broadcast in. @default the profile's site, else the console's only site */
828
+ site?: string;
829
+ /**
830
+ * The network clients join: a network reference, or
831
+ * `WifiBroadcast.NATIVE_NETWORK` for the site's default network (a
832
+ * `Network` resource or lookup for the default network works too).
833
+ */
834
+ network?: NetworkReference | NativeNetwork;
835
+ }
836
+ /** WPA security on a standard broadcast must set `fastRoamingEnabled` (the console rejects it otherwise). */
837
+ type WithFastRoaming<S> = S extends {
838
+ fastRoamingEnabled?: boolean;
839
+ } ? S & {
840
+ fastRoamingEnabled: boolean;
841
+ } : S;
842
+ export type StandardWifiSecurityConfiguration = WithFastRoaming<Sdk.WifiSecurityConfigurationDetailObject>;
843
+ /**
844
+ * An ordinary SSID. Pass passphrases as `Redacted` (e.g. from
845
+ * `Config.Redacted`) to keep them out of logs.
846
+ */
847
+ export interface StandardWifiBroadcastProps extends Omit<Sdk.IntegrationStandardWifiBroadcastCreateUpdateDto, "network" | "securityConfiguration">, CommonProps {
848
+ securityConfiguration: StandardWifiSecurityConfiguration;
849
+ }
850
+ /** An SSID tuned for IoT devices (2.4 GHz only, longer DTIM). */
851
+ export interface IotWifiBroadcastProps extends Omit<Sdk.IntegrationIotOptimizedWifiBroadcastCreateUpdateDto, "network">, CommonProps {}
852
+ /** Props are the Integration API's Wi-Fi body, plus `site` and references; `type` picks the kind. */
853
+ export type WifiBroadcastProps = StandardWifiBroadcastProps | IotWifiBroadcastProps;
854
+ export interface WifiBroadcastAttributes {
855
+ siteId: string;
856
+ wifiBroadcastId: string;
857
+ name: string;
858
+ type: WifiBroadcastProps["type"];
859
+ /** The network clients join, when it isn't the native one. */
860
+ networkId: string | undefined;
861
+ /** The whole broadcast as the console reports it (passphrases `Redacted`), so `alchemy drift` sees any change. */
862
+ config: DistributiveOmit<Sdk.WifiBroadcastDetails, "id" | "metadata">;
863
+ }
864
+ export interface WifiBroadcast extends Resource<"UnifiNetwork.WifiBroadcast", WifiBroadcastProps, WifiBroadcastAttributes, never, Providers> {}
865
+ /** A site's Wi-Fi broadcasts, listed once per run. */
866
+ export declare const listWifiBroadcasts: (siteId: string) => Effect.Effect<readonly Sdk.WifiBroadcastOverview[], Sdk.UnifiNetworkOpError, UnifiNetworkConnection | UnifiNetworkReadCache>;
867
+ /**
868
+ * A Wi-Fi network (SSID). `type` picks a standard or an IoT-optimised
869
+ * broadcast; changing it, or the site, replaces the broadcast.
870
+ *
871
+ * Props are authoritative: every update sends the whole broadcast, with
872
+ * explicit defaults for unset fields (a standard broadcast's
873
+ * `bandSteeringEnabled: false`), so changes made in the UI, passphrases
874
+ * included, are put back. The `config` attribute mirrors the console's
875
+ * object, so `alchemy drift` sees them. Existing broadcasts are adopted by name with
876
+ * `--adopt`.
877
+ *
878
+ * @example
879
+ * ```ts
880
+ * import * as UnifiNetwork from "@alchemy-unifi/network";
881
+ * import * as Alchemy from "alchemy";
882
+ * import * as Config from "effect/Config";
883
+ * import * as Effect from "effect/Effect";
884
+ *
885
+ * export default Alchemy.Stack(
886
+ * "HomeNetwork",
887
+ * { providers: UnifiNetwork.providers(), state: UnifiNetwork.state() },
888
+ * Effect.gen(function* () {
889
+ * const lan = yield* UnifiNetwork.Network.lookup({ vlanId: 1 });
890
+ * yield* UnifiNetwork.WifiBroadcast("home", {
891
+ * type: "STANDARD",
892
+ * name: "Home",
893
+ * enabled: true,
894
+ * network: lan,
895
+ * securityConfiguration: {
896
+ * type: "WPA2_PERSONAL",
897
+ * passphrase: yield* Config.Redacted("WIFI_PASSPHRASE"),
898
+ * fastRoamingEnabled: false,
899
+ * },
900
+ * broadcastingFrequenciesGHz: [2.4, 5],
901
+ * hideName: false,
902
+ * clientIsolationEnabled: false,
903
+ * multicastToUnicastConversionEnabled: false,
904
+ * uapsdEnabled: false,
905
+ * channel2gLockedTo6: false,
906
+ * dtimPeriod2gLockedTo3: false,
907
+ * advertiseDeviceName: false,
908
+ * arpProxyEnabled: false,
909
+ * bssTransitionEnabled: true,
910
+ * });
911
+ * }),
912
+ * );
913
+ * ```
914
+ */
915
+ export declare const WifiBroadcast: import("alchemy").ResourceClassWithMethods<WifiBroadcast, {
916
+ readonly NATIVE_NETWORK: NativeNetwork;
917
+ /**
918
+ * A broadcast in the site by name. Dies with a user-facing
919
+ * `LookupNotFound` or `LookupAmbiguous`: stack bodies can't fail with typed
920
+ * errors.
921
+ */
922
+ readonly lookup: (query: {
923
+ readonly name: string;
924
+ readonly site?: string;
925
+ }) => Effect.Effect<WifiBroadcastAttributes, never, UnifiNetworkConnection | UnifiNetworkCredentials | UnifiNetworkReadCache>;
926
+ }>;
927
+ export declare const WifiBroadcastProvider: () => import("effect/Layer").Layer<Provider.Provider<WifiBroadcast>, never, Services>;
928
+ export { type Established, ReadCache_d_exports as ReadCache, type UnifiStateOptions };
929
+ //# sourceMappingURL=index.d.mts.map