cloudflare-ddns-sync 4.0.0-beta.2 → 4.0.0-rc.1
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.
- package/CHANGELOG.md +13 -0
- package/MIGRATION-V4.md +64 -0
- package/README.md +91 -117
- package/dist/index.d.ts +41 -16
- package/dist/index.js +286 -40
- package/dist/lib/cloudflare-client.d.ts +16 -10
- package/dist/lib/cloudflare-client.js +119 -161
- package/dist/lib/cron.d.ts +1 -1
- package/dist/lib/cron.js +1 -3
- package/dist/lib/ip-utils.d.ts +0 -5
- package/dist/lib/ip-utils.js +0 -26
- package/dist/lib/sync-error.d.ts +12 -0
- package/dist/lib/sync-error.js +10 -0
- package/dist/lib/sync-job.d.ts +44 -0
- package/dist/lib/sync-job.js +190 -0
- package/dist/types/Auth.d.ts +5 -2
- package/dist/types/Callbacks.d.ts +3 -2
- package/dist/types/Options.d.ts +48 -0
- package/dist/types/Record.d.ts +11 -2
- package/dist/types/cloudflare/RecordData.d.ts +3 -1
- package/dist/types/index.d.ts +4 -1
- package/dist/types/index.js +4 -1
- package/logo.png +0 -0
- package/package.json +19 -16
- package/dist/types/CloudflareOptions.d.ts +0 -2
- /package/dist/types/{CloudflareOptions.js → Options.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v4
|
|
4
|
+
|
|
5
|
+
### v4.0.0
|
|
6
|
+
|
|
7
|
+
- ⬆️ Update dependencies, including Cloudflare 2 → 7, node-cron 3 → 4 and public-ip 6 → 8.
|
|
8
|
+
- ♻️ Preserve public auth fields, key/token precedence, record types, sync methods and DNS defaults while adapting to the new SDK.
|
|
9
|
+
- ♻️ Fetch all zone and DNS record pages; report zone-loading failures through the awaited operation.
|
|
10
|
+
- 🚨 Add a DDNS-oriented API with `createDdns()`, `sync()`, `schedule()`, `watch()`, `list()`, `remove()` and `ip()`
|
|
11
|
+
- 🚨 Allow records to be configured as hostnames; A and AAAA records automatically resolve the matching public IP
|
|
12
|
+
- 🚨 Return a dedicated controllable job from schedules and watchers
|
|
13
|
+
- 🐛 Respect the requested DNS type when listing records with the same hostname
|
|
14
|
+
- ♻️ Validate configuration before network access, make jobs safe to stop and add `sync({ipv4, ipv6})` for configured records
|
|
15
|
+
|
|
3
16
|
## v3
|
|
4
17
|
|
|
5
18
|
### 3.0.2
|
package/MIGRATION-V4.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Migrating from v3 to v4
|
|
2
|
+
|
|
3
|
+
v4 replaces the class-based API with `createDdns()` and consolidates related methods. Node.js 20 remains the minimum requirement.
|
|
4
|
+
|
|
5
|
+
## Initialization
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
// v3
|
|
9
|
+
const ddns = new CloudflareDDNSSync({token});
|
|
10
|
+
|
|
11
|
+
// v4
|
|
12
|
+
const ddns = createDdns({token, records: ['home.example.com']});
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
If `token` is omitted, v4 reads `CLOUDFLARE_API_TOKEN`. `email`/`key` and user service keys remain supported.
|
|
16
|
+
|
|
17
|
+
## Synchronizing
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
// v3
|
|
21
|
+
await ddns.syncRecord({name: 'home.example.com'});
|
|
22
|
+
await ddns.syncRecords(records, '203.0.113.10');
|
|
23
|
+
|
|
24
|
+
// v4
|
|
25
|
+
await ddns.sync('home.example.com');
|
|
26
|
+
await ddns.sync(records, {ipv4: '203.0.113.10'});
|
|
27
|
+
await ddns.sync({ipv4: '203.0.113.10'}); // configured records
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`sync()` always returns an array. For a single record, use the first entry: `const [record] = await ddns.sync('home.example.com')`.
|
|
31
|
+
|
|
32
|
+
For configured records, pass fixed addresses directly as the first argument. The former placeholder form `sync(undefined, {ipv4})` remains supported for compatibility.
|
|
33
|
+
|
|
34
|
+
A and AAAA records automatically use the matching IP family. Non-IP records now require explicit `content`.
|
|
35
|
+
|
|
36
|
+
## Scheduling and watching
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
// v3
|
|
40
|
+
const task = ddns.syncByCronTime('*/5 * * * *', records, callback);
|
|
41
|
+
const id = await ddns.syncOnIpChange(records, callback);
|
|
42
|
+
ddns.stopSyncOnIpChange(id);
|
|
43
|
+
|
|
44
|
+
// v4
|
|
45
|
+
const task = ddns.schedule('*/5 * * * *', {records, onSync: callback});
|
|
46
|
+
const watcher = await ddns.watch({records, onSync: callback});
|
|
47
|
+
await watcher.stop();
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`schedule()` and `watch()` return a `SyncJob` with `run()`, `start()` and `stop()`. A v4 job replaces both the listener ID and the direct `node-cron` return value.
|
|
51
|
+
|
|
52
|
+
## Listing, removing and retrieving IPs
|
|
53
|
+
|
|
54
|
+
| v3 | v4 |
|
|
55
|
+
| --- | --- |
|
|
56
|
+
| `getRecordDataForRecord(record)` | `list({records: record})` |
|
|
57
|
+
| `getRecordDataForRecords(records)` | `list({records})` |
|
|
58
|
+
| `getRecordDataForDomain(domain)` | `list({domains: domain})` |
|
|
59
|
+
| `getRecordDataForDomains(domains)` | `list({domains, groupBy: 'domain'})` |
|
|
60
|
+
| `removeRecord(name, type?)` | `remove(name)` oder `remove({name, type})` |
|
|
61
|
+
| `getIp()` | `ip()` |
|
|
62
|
+
| `getIpv6()` | `ip(6)` |
|
|
63
|
+
|
|
64
|
+
`list()` without filters uses the configured records. Without that configuration, a filter is required.
|
package/README.md
CHANGED
|
@@ -1,160 +1,134 @@
|
|
|
1
1
|
<table>
|
|
2
2
|
<tr>
|
|
3
|
-
<td width="25%"><img src="./logo.png" alt="
|
|
3
|
+
<td width="25%"><img src="./logo.png" alt="Cloudflare DDNS Sync logo" width="100%"></td>
|
|
4
4
|
<td><h1>Cloudflare DDNS Sync</h1></td>
|
|
5
5
|
</tr>
|
|
6
6
|
</table>
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
[](https://www.npmjs.com/package/cloudflare-ddns-sync)
|
|
10
|
-
[](https://www.npmjs.com/package/cloudflare-ddns-sync)
|
|
11
|
-
[](https://www.npmjs.com/package/cloudflare-ddns-sync-cli)
|
|
8
|
+
Cloudflare DDNS Sync synchronizes DNS records with the current public IP address. Version 4 provides a small, DDNS-oriented API.
|
|
12
9
|
|
|
13
|
-
##
|
|
10
|
+
## Voraussetzungen
|
|
14
11
|
|
|
15
|
-
|
|
12
|
+
- Node.js 20 or newer
|
|
13
|
+
- A Cloudflare API token with permission to read zones and edit DNS records
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
```sh
|
|
16
|
+
npm install cloudflare-ddns-sync
|
|
17
|
+
```
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
## Einstieg
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Set `CLOUDFLARE_API_TOKEN` and configure the hostnames once:
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
```ts
|
|
24
|
+
import {createDdns} from 'cloudflare-ddns-sync';
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
const ddns = createDdns({
|
|
27
|
+
records: ['home.example.com', {name: 'app.example.com', proxied: true}],
|
|
28
|
+
});
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
await ddns.sync();
|
|
31
|
+
```
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
A string represents an A record. Missing A and AAAA records are created; existing records with the same name and type are updated. A records automatically receive the public IPv4 address, and AAAA records receive the IPv6 address.
|
|
31
34
|
|
|
32
|
-
```
|
|
33
|
-
|
|
35
|
+
```ts
|
|
36
|
+
const ddns = createDdns({
|
|
37
|
+
token: process.env.CLOUDFLARE_API_TOKEN,
|
|
38
|
+
records: [
|
|
39
|
+
'home.example.com',
|
|
40
|
+
{name: 'home.example.com', type: 'AAAA'},
|
|
41
|
+
{name: 'alias.example.com', type: 'CNAME', content: 'home.example.com'},
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
await ddns.sync();
|
|
34
46
|
```
|
|
35
47
|
|
|
36
|
-
|
|
48
|
+
Explicit `token`, `email`/`key` and user service keys remain supported. If no credentials are provided, only `CLOUDFLARE_API_TOKEN` is used.
|
|
37
49
|
|
|
38
|
-
##
|
|
50
|
+
## Synchronizing
|
|
39
51
|
|
|
40
|
-
|
|
41
|
-
|
|
52
|
+
```ts
|
|
53
|
+
await ddns.sync();
|
|
54
|
+
await ddns.sync('temporary.example.com');
|
|
55
|
+
await ddns.sync(['home.example.com', {name: 'vpn.example.com', proxied: true}]);
|
|
42
56
|
|
|
43
|
-
|
|
57
|
+
await ddns.sync({
|
|
58
|
+
ipv4: '203.0.113.10',
|
|
59
|
+
ipv6: '2001:db8::1',
|
|
60
|
+
});
|
|
61
|
+
```
|
|
44
62
|
|
|
45
|
-
|
|
46
|
-
const Cddnss = require('cloudflare-ddns-sync').default;
|
|
63
|
+
`sync()` always returns an array of updated DNS records. Explicit `content` on a record takes precedence. Record types other than A and AAAA require `content`.
|
|
47
64
|
|
|
48
|
-
|
|
49
|
-
const cddnss = new Cddnss({
|
|
50
|
-
email: 'your@email.com',
|
|
51
|
-
key: '<your-cloudflare-api-key>',
|
|
52
|
-
token: '<your-cloudflare-api-token>',
|
|
53
|
-
});
|
|
65
|
+
Use `plan()` to inspect the same operation without writing DNS records. Each entry has an `action` of `create`, `update` or `unchanged`. `sync()` leaves unchanged records untouched. If one or more writes fail, it throws `SyncError` with `succeeded` and `failed` entries.
|
|
54
66
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
name: 'test-1.domain.com',
|
|
58
|
-
type: 'A', // optional
|
|
59
|
-
proxied: true, // optional
|
|
60
|
-
ttl: 1, // optional
|
|
61
|
-
priority: 0, // optional
|
|
62
|
-
content: '1.2.3.4', // optional
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
name: 'test-2.domain.com',
|
|
66
|
-
},
|
|
67
|
-
];
|
|
68
|
-
|
|
69
|
-
cddnss.syncRecords(records).then((result) => {
|
|
70
|
-
console.log(result);
|
|
71
|
-
});
|
|
67
|
+
```ts
|
|
68
|
+
const changes = await ddns.plan();
|
|
72
69
|
```
|
|
73
70
|
|
|
74
|
-
|
|
71
|
+
## Scheduling and IP watching
|
|
75
72
|
|
|
76
|
-
```
|
|
77
|
-
|
|
73
|
+
```ts
|
|
74
|
+
const scheduled = ddns.schedule('*/5 * * * *');
|
|
75
|
+
await scheduled.stop();
|
|
78
76
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
email: 'your@email.com',
|
|
82
|
-
key: '<your-cloudflare-api-key>',
|
|
83
|
-
token: '<your-cloudflare-api-token>',
|
|
77
|
+
const watching = await ddns.watch({
|
|
78
|
+
onError: error => console.error(error),
|
|
84
79
|
});
|
|
85
80
|
|
|
86
|
-
|
|
87
|
-
{
|
|
88
|
-
name: 'test-1.yourdomain.com',
|
|
89
|
-
type: 'A', // optional
|
|
90
|
-
proxied: true, // optional
|
|
91
|
-
ttl: 1, // optional
|
|
92
|
-
priority: 0, // optional
|
|
93
|
-
content: '1.2.3.4', // optional
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
name: 'test-2.yourdomain.com',
|
|
97
|
-
},
|
|
98
|
-
];
|
|
99
|
-
|
|
100
|
-
cddnss.syncRecords(records).then((result: Array<RecordData>) => {
|
|
101
|
-
console.log(result);
|
|
102
|
-
});
|
|
81
|
+
await watching.stop();
|
|
103
82
|
```
|
|
104
83
|
|
|
105
|
-
|
|
84
|
+
`schedule()` starts immediately and synchronizes at the next cron interval. `watch()` synchronizes first and then watches only the required IP families; the default interval is ten seconds. Fixed addresses and records with explicit `content` are not polled. Both return a job with `run()`, `start()` and `stop()` methods. Errors from `run()` reject its promise; automatic runs call `onError` once.
|
|
106
85
|
|
|
107
|
-
|
|
86
|
+
Configuration errors are `DdnsError`s with `code: 'INVALID_CONFIG'`; errors while looking up a public address have `code: 'IP_UNAVAILABLE'`.
|
|
108
87
|
|
|
88
|
+
## Listing and removing records
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
const records = await ddns.list({records: 'home.example.com'});
|
|
92
|
+
const ipv6Records = await ddns.list({records: {name: 'home.example.com', type: 'AAAA'}});
|
|
93
|
+
const domainRecords = await ddns.list({domains: 'example.com'});
|
|
94
|
+
const grouped = await ddns.list({domains: ['example.com', 'example.org'], groupBy: 'domain'});
|
|
95
|
+
|
|
96
|
+
await ddns.remove('home.example.com');
|
|
97
|
+
await ddns.remove({name: 'home.example.com', type: 'AAAA'});
|
|
109
98
|
```
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
└────────────── second (0-59) [optional]
|
|
99
|
+
|
|
100
|
+
`remove('name')` removes an A record. Use a record object for other types. `list()` without filters uses the records configured when creating the DDNS instance. A record filter with `type` returns only that DNS type.
|
|
101
|
+
|
|
102
|
+
## Public IP
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
const ipv4 = await ddns.ip();
|
|
106
|
+
const ipv6 = await ddns.ip(6);
|
|
119
107
|
```
|
|
120
108
|
|
|
121
|
-
##
|
|
122
|
-
|
|
123
|
-
- getIp(): Promise\<string\>
|
|
124
|
-
- getIpv6(): Promise\<string\>
|
|
125
|
-
- getRecordDataForDomain(domain: string): Promise\<Array\<[RecordData](https://cddnss.knaup.pw/types/recorddata)\>\>
|
|
126
|
-
- getRecordDataForDomains(domains: Array\<string\>): Promise\<[DomainRecordList](https://cddnss.knaup.pw/types/domainrecordlist)\>
|
|
127
|
-
- getRecordDataForRecord(record: [Record](https://cddnss.knaup.pw/types/record)): Promise\<[RecordData](https://cddnss.knaup.pw/types/recorddata)\>
|
|
128
|
-
- getRecordDataForRecords(records: Array\<[Record](https://cddnss.knaup.pw/types/record)\>): Promise\<Array\<[RecordData](https://cddnss.knaup.pw/types/recorddata)\>\>
|
|
129
|
-
- removeRecord(recordName: string, recordType?: string): Promise\<void\>
|
|
130
|
-
- stopSyncOnIpChange(changeListenerId: string): void
|
|
131
|
-
- syncByCronTime(cronExpression: string, records: Array\<[Record](https://cddnss.knaup.pw/types/recorddata)\>, callback: [MultiSyncCallback](https://cddnss.knaup.pw/types/multisynccallback), ip?: string): [ScheduledTask](https://www.npmjs.com/package/node-cron#scheduledtask-methods)
|
|
132
|
-
- syncOnIpChange(records: Array\<[Record](https://cddnss.knaup.pw/types/record)\>, callback: multisynccallback): Promise\<string\>
|
|
133
|
-
- syncRecord(record: [Record](https://cddnss.knaup.pw/types/record), ip?: string): Promise\<[RecordData](https://cddnss.knaup.pw/types/recorddata)\>
|
|
134
|
-
- syncRecords(records: Array\<[Record](https://cddnss.knaup.pw/types/record)\>, ip?: string): Promise\<Array\<[RecordData](https://cddnss.knaup.pw/types/recorddata)\>\>
|
|
135
|
-
|
|
136
|
-
For a more detailed view, have a look at the [Documentation](https://cddnss.knaup.pw/)
|
|
137
|
-
|
|
138
|
-
## Get Your Cloudflare API Key
|
|
139
|
-
|
|
140
|
-
- Go to **[Cloudflare](https://www.cloudflare.com)**
|
|
141
|
-
- **Log In**
|
|
142
|
-
- In the upper right corner: **click on the user icon**
|
|
143
|
-
- Go to **"My Profile"**
|
|
144
|
-
- In the "API Tokens"-Section: **click on the "View"-Button of the Global Key**
|
|
145
|
-
- **Enter your password** and **fill the captcha**
|
|
146
|
-
- **Copy the API Key**
|
|
109
|
+
## Optional: eigene IP-Quelle und Zone
|
|
147
110
|
|
|
148
|
-
|
|
111
|
+
```ts
|
|
112
|
+
const ddns = createDdns({
|
|
113
|
+
zone: 'example.com',
|
|
114
|
+
records: ['@', 'home'],
|
|
115
|
+
resolveIp: family => readAddressFromRouter(family),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
await ddns.close();
|
|
119
|
+
```
|
|
149
120
|
|
|
150
|
-
|
|
121
|
+
With `zone`, `@` means the zone apex and simple labels are expanded. `resolveIp` is used by both `sync()` and `watch()`.
|
|
151
122
|
|
|
152
|
-
|
|
123
|
+
## Migrating from v3
|
|
153
124
|
|
|
154
|
-
|
|
155
|
-
- Configure the email, cloudflare api key and the domain
|
|
156
|
-
- Run `npm test`
|
|
125
|
+
v4 is a breaking release. See [MIGRATION-V4.md](./MIGRATION-V4.md) for the complete mapping of the previous API.
|
|
157
126
|
|
|
158
|
-
|
|
127
|
+
## Tests
|
|
128
|
+
|
|
129
|
+
The integration tests create and remove records in a Cloudflare test zone:
|
|
159
130
|
|
|
160
|
-
|
|
131
|
+
```sh
|
|
132
|
+
npm run build
|
|
133
|
+
npm test -- --token="$CLOUDFLARE_API_TOKEN" --domain="example.com"
|
|
134
|
+
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,43 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
getRecordDataForDomains(domains: Array<string>): Promise<{}>;
|
|
9
|
-
getRecordDataForRecord(record: Record): Promise<import("cloudflare/resources/dns/records.mjs").DNSRecord>;
|
|
10
|
-
getRecordDataForRecords(records: Array<Record>): Promise<any[]>;
|
|
11
|
-
removeRecord(recordName: string, recordType?: string): Promise<void>;
|
|
12
|
-
stopSyncOnIpChange(changeListenerId: string): void;
|
|
13
|
-
syncByCronTime(cronExpression: string, records: Array<Record>, callback: MultiSyncCallback, ip?: string): import("node-cron").ScheduledTask;
|
|
14
|
-
syncOnIpChange(records: Array<Record>, callback: MultiSyncCallback): Promise<string>;
|
|
15
|
-
syncRecord(record: Record, ip?: string): any;
|
|
16
|
-
syncRecords(records: Array<Record>, ip?: string): Promise<import("cloudflare/resources/dns/records.mjs").DNSRecord[]>;
|
|
1
|
+
import { SyncError } from './lib/sync-error.js';
|
|
2
|
+
import { DdnsOptions, DnsRecord, DomainListOptions, GroupedDomainListOptions, JobOptions, RecordListOptions, RecordSelection, SyncJob, SyncChange, SyncOptions, WatchOptions } from './types/index.js';
|
|
3
|
+
export type DdnsErrorCode = 'INVALID_CONFIG' | 'IP_UNAVAILABLE';
|
|
4
|
+
export declare class DdnsError extends Error {
|
|
5
|
+
readonly code: DdnsErrorCode;
|
|
6
|
+
readonly cause?: unknown;
|
|
7
|
+
constructor(code: DdnsErrorCode, message: string, cause?: unknown);
|
|
17
8
|
}
|
|
9
|
+
export { SyncError };
|
|
10
|
+
export declare class Ddns {
|
|
11
|
+
private readonly cloudflareClient;
|
|
12
|
+
private readonly configuredRecords?;
|
|
13
|
+
private readonly zone?;
|
|
14
|
+
private readonly resolveIp?;
|
|
15
|
+
private readonly jobs;
|
|
16
|
+
private closed;
|
|
17
|
+
constructor(options?: DdnsOptions);
|
|
18
|
+
sync(): Promise<Array<DnsRecord>>;
|
|
19
|
+
sync(options: SyncOptions): Promise<Array<DnsRecord>>;
|
|
20
|
+
sync(records: RecordSelection, options?: SyncOptions): Promise<Array<DnsRecord>>;
|
|
21
|
+
sync(records: undefined, options: SyncOptions): Promise<Array<DnsRecord>>;
|
|
22
|
+
plan(): Promise<Array<SyncChange>>;
|
|
23
|
+
plan(options: SyncOptions): Promise<Array<SyncChange>>;
|
|
24
|
+
plan(records: RecordSelection, options?: SyncOptions): Promise<Array<SyncChange>>;
|
|
25
|
+
schedule(expression: string, options?: JobOptions): SyncJob;
|
|
26
|
+
watch(options?: WatchOptions): Promise<SyncJob>;
|
|
27
|
+
list(): Promise<Array<DnsRecord>>;
|
|
28
|
+
list(filter: RecordListOptions): Promise<Array<DnsRecord>>;
|
|
29
|
+
list(filter: DomainListOptions): Promise<Array<DnsRecord>>;
|
|
30
|
+
list(filter: GroupedDomainListOptions): Promise<{
|
|
31
|
+
[domain: string]: Array<DnsRecord>;
|
|
32
|
+
}>;
|
|
33
|
+
remove(records: RecordSelection): Promise<void>;
|
|
34
|
+
ip(family?: 4 | 6): Promise<string>;
|
|
35
|
+
close(): Promise<void>;
|
|
36
|
+
private resolveRecords;
|
|
37
|
+
private ensureOpen;
|
|
38
|
+
private dynamicFamilies;
|
|
39
|
+
private withContent;
|
|
40
|
+
}
|
|
41
|
+
export declare function createDdns(options?: DdnsOptions): Ddns;
|
|
18
42
|
export * from './types/index.js';
|
|
43
|
+
export default createDdns;
|