@serve.zone/dcrouter 16.4.0 → 16.5.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.
- package/deno.json +1 -1
- package/dist_serve/bundle.js +852 -852
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.d.ts +2 -1
- package/dist_ts/classes.dcrouter.js +20 -4
- package/dist_ts/db/documents/classes.cached.email.js +2 -2
- package/dist_ts/db/documents/classes.dns-record.doc.d.ts +4 -0
- package/dist_ts/db/documents/classes.dns-record.doc.js +33 -2
- package/dist_ts/db/documents/classes.email-domain.doc.d.ts +5 -1
- package/dist_ts/db/documents/classes.email-domain.doc.js +26 -2
- package/dist_ts/dns/manager.dns.d.ts +6 -0
- package/dist_ts/dns/manager.dns.js +81 -32
- package/dist_ts/dns/providers/cloudflare.provider.d.ts +2 -0
- package/dist_ts/dns/providers/cloudflare.provider.js +45 -28
- package/dist_ts/email/classes.email-domain.manager.d.ts +43 -38
- package/dist_ts/email/classes.email-domain.manager.js +391 -324
- package/dist_ts/email/classes.mail-dns-sync.d.ts +58 -17
- package/dist_ts/email/classes.mail-dns-sync.js +857 -141
- package/dist_ts/email/classes.mail-edge-eligibility.d.ts +55 -0
- package/dist_ts/email/classes.mail-edge-eligibility.js +168 -0
- package/dist_ts/email/classes.mail-egress-coordinator.d.ts +3 -1
- package/dist_ts/email/classes.mail-egress-coordinator.js +43 -22
- package/dist_ts/email/classes.workapp-mail-manager.d.ts +3 -0
- package/dist_ts/email/classes.workapp-mail-manager.js +58 -12
- package/dist_ts/email/index.d.ts +1 -0
- package/dist_ts/email/index.js +2 -1
- package/dist_ts/opsserver/handlers/email-domain.handler.d.ts +1 -0
- package/dist_ts/opsserver/handlers/email-domain.handler.js +138 -22
- package/dist_ts/remoteingress/classes.hub-lifecycle.js +2 -1
- package/dist_ts/remoteingress/classes.tunnel-manager.d.ts +6 -0
- package/dist_ts/remoteingress/classes.tunnel-manager.js +40 -2
- package/dist_ts_interfaces/data/dns-record.d.ts +6 -0
- package/dist_ts_interfaces/data/email-domain.d.ts +81 -0
- package/dist_ts_interfaces/requests/email-domains.d.ts +25 -17
- package/dist_ts_migrations/index.js +185 -9
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate/email-domains.d.ts +16 -0
- package/dist_ts_web/appstate/email-domains.js +140 -19
- package/dist_ts_web/elements/email/ops-view-email-domains.d.ts +1 -0
- package/dist_ts_web/elements/email/ops-view-email-domains.js +36 -13
- package/package.json +7 -7
- package/readme.md +13 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +22 -3
- package/ts/db/documents/classes.cached.email.ts +1 -0
- package/ts/db/documents/classes.dns-record.doc.ts +20 -0
- package/ts/db/documents/classes.email-domain.doc.ts +14 -0
- package/ts/dns/manager.dns.ts +89 -29
- package/ts/dns/providers/cloudflare.provider.ts +46 -27
- package/ts/email/classes.email-domain.manager.ts +416 -368
- package/ts/email/classes.mail-dns-sync.ts +913 -146
- package/ts/email/classes.mail-edge-eligibility.ts +205 -0
- package/ts/email/classes.mail-egress-coordinator.ts +46 -23
- package/ts/email/classes.workapp-mail-manager.ts +69 -13
- package/ts/email/index.ts +1 -0
- package/ts/opsserver/handlers/email-domain.handler.ts +154 -21
- package/ts/remoteingress/classes.hub-lifecycle.ts +1 -0
- package/ts/remoteingress/classes.tunnel-manager.ts +53 -7
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate/email-domains.ts +187 -18
- package/ts_web/elements/email/ops-view-email-domains.ts +44 -12
|
@@ -44,6 +44,26 @@ export class DnsRecordDoc extends plugins.smartdata.SmartDataDbDoc<DnsRecordDoc,
|
|
|
44
44
|
@plugins.smartdata.svDb()
|
|
45
45
|
public createdBy!: string;
|
|
46
46
|
|
|
47
|
+
@plugins.smartdata.svDb()
|
|
48
|
+
public managedBy?: string;
|
|
49
|
+
|
|
50
|
+
@plugins.smartdata.svDb()
|
|
51
|
+
public managedOwnerId?: string;
|
|
52
|
+
|
|
53
|
+
@plugins.smartdata.svDb()
|
|
54
|
+
public managedRecordKey?: string;
|
|
55
|
+
|
|
56
|
+
public async createSavableObject(): Promise<DnsRecordDoc> {
|
|
57
|
+
const savable = await super.createSavableObject() as DnsRecordDoc;
|
|
58
|
+
// SmartData persists decorated `undefined` values through `$set`. Managed
|
|
59
|
+
// metadata must be truly absent so partial indexes and ownership checks do
|
|
60
|
+
// not accidentally classify operator/provider records as reconciler-owned.
|
|
61
|
+
if (!this.managedBy) delete (savable as any).managedBy;
|
|
62
|
+
if (!this.managedOwnerId) delete (savable as any).managedOwnerId;
|
|
63
|
+
if (!this.managedRecordKey) delete (savable as any).managedRecordKey;
|
|
64
|
+
return savable;
|
|
65
|
+
}
|
|
66
|
+
|
|
47
67
|
constructor() {
|
|
48
68
|
super();
|
|
49
69
|
}
|
|
@@ -5,6 +5,8 @@ import type {
|
|
|
5
5
|
IEmailDomainRateLimits,
|
|
6
6
|
IEmailDomainDnsStatus,
|
|
7
7
|
IEmailDomainRemoteIngress,
|
|
8
|
+
IEmailDomainReconciliation,
|
|
9
|
+
IEmailDkimMaterial,
|
|
8
10
|
} from '../../../ts_interfaces/data/email-domain.js';
|
|
9
11
|
|
|
10
12
|
const getDb = () => DcRouterDb.getInstance().getDb();
|
|
@@ -36,6 +38,18 @@ export class EmailDomainDoc extends plugins.smartdata.SmartDataDbDoc<EmailDomain
|
|
|
36
38
|
@plugins.smartdata.svDb()
|
|
37
39
|
public dnsStatus!: IEmailDomainDnsStatus;
|
|
38
40
|
|
|
41
|
+
@plugins.smartdata.svDb()
|
|
42
|
+
public reconciliation?: IEmailDomainReconciliation;
|
|
43
|
+
|
|
44
|
+
@plugins.smartdata.svDb()
|
|
45
|
+
public activeDkim?: IEmailDkimMaterial;
|
|
46
|
+
|
|
47
|
+
@plugins.smartdata.svDb()
|
|
48
|
+
public pendingDkim?: IEmailDkimMaterial;
|
|
49
|
+
|
|
50
|
+
@plugins.smartdata.svDb()
|
|
51
|
+
public retiringDkim?: IEmailDkimMaterial[];
|
|
52
|
+
|
|
39
53
|
@plugins.smartdata.svDb()
|
|
40
54
|
public createdAt!: string;
|
|
41
55
|
|
package/ts/dns/manager.dns.ts
CHANGED
|
@@ -88,10 +88,12 @@ export class DnsManager {
|
|
|
88
88
|
let registered = 0;
|
|
89
89
|
for (const domain of dcrouterDomains) {
|
|
90
90
|
const records = await DnsRecordDoc.findByDomainId(domain.id);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
const rrsets = new Set(records.map((record) => this.rrsetKey(record.name, record.type)));
|
|
92
|
+
for (const rrset of rrsets) {
|
|
93
|
+
const [name, type] = rrset.split('|') as [string, TDnsRecordType];
|
|
94
|
+
await this.refreshLocalRrset(domain.id, name, type);
|
|
94
95
|
}
|
|
96
|
+
registered += records.length;
|
|
95
97
|
await this.registerAuthoritativeZoneDefaults(domain);
|
|
96
98
|
}
|
|
97
99
|
logger.log(
|
|
@@ -167,6 +169,26 @@ export class DnsManager {
|
|
|
167
169
|
});
|
|
168
170
|
}
|
|
169
171
|
|
|
172
|
+
private rrsetKey(name: string, type: TDnsRecordType): string {
|
|
173
|
+
return `${name.toLowerCase()}|${type}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Rebuild one authoritative RRset atomically from persisted rows. */
|
|
177
|
+
private async refreshLocalRrset(
|
|
178
|
+
domainId: string,
|
|
179
|
+
name: string,
|
|
180
|
+
type: TDnsRecordType,
|
|
181
|
+
): Promise<void> {
|
|
182
|
+
if (!this.dnsServer) return;
|
|
183
|
+
this.dnsServer.unregisterHandler(name, [type]);
|
|
184
|
+
const records = await DnsRecordDoc.findByDomainId(domainId);
|
|
185
|
+
for (const record of records) {
|
|
186
|
+
if (record.name.toLowerCase() === name.toLowerCase() && record.type === type) {
|
|
187
|
+
this.registerRecordWithDnsServer(record);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
170
192
|
private parseRecordData(type: TDnsRecordType, value: string): any {
|
|
171
193
|
switch (type) {
|
|
172
194
|
case 'A':
|
|
@@ -608,9 +630,16 @@ export class DnsManager {
|
|
|
608
630
|
const doc = await DomainDoc.findById(id);
|
|
609
631
|
if (!doc) return false;
|
|
610
632
|
const records = await DnsRecordDoc.findByDomainId(id);
|
|
633
|
+
const localRrsets = new Set(records.map((record) => this.rrsetKey(record.name, record.type)));
|
|
611
634
|
for (const r of records) {
|
|
612
635
|
await r.delete();
|
|
613
636
|
}
|
|
637
|
+
if (doc.source === 'dcrouter' && this.dnsServer) {
|
|
638
|
+
for (const rrset of localRrsets) {
|
|
639
|
+
const [name, type] = rrset.split('|') as [string, TDnsRecordType];
|
|
640
|
+
this.dnsServer.unregisterHandler(name, [type]);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
614
643
|
await doc.delete();
|
|
615
644
|
return true;
|
|
616
645
|
}
|
|
@@ -631,13 +660,34 @@ export class DnsManager {
|
|
|
631
660
|
}
|
|
632
661
|
const providerRecords = await client.listRecords(doc.name);
|
|
633
662
|
|
|
634
|
-
//
|
|
663
|
+
// Merge by provider id, then exact tuple. Replacing the collection would
|
|
664
|
+
// erase explicit ownership metadata from reconciler-managed rows.
|
|
635
665
|
const existing = await DnsRecordDoc.findByDomainId(id);
|
|
636
|
-
|
|
637
|
-
await r.delete();
|
|
638
|
-
}
|
|
666
|
+
const remaining = new Set(existing);
|
|
639
667
|
for (const pr of providerRecords) {
|
|
640
|
-
|
|
668
|
+
const match = existing.find((record) => record.providerRecordId === pr.providerRecordId)
|
|
669
|
+
|| existing.find((record) => (
|
|
670
|
+
record.name.toLowerCase() === pr.name.toLowerCase()
|
|
671
|
+
&& record.type === pr.type
|
|
672
|
+
&& record.value.trim() === pr.value.trim()
|
|
673
|
+
));
|
|
674
|
+
if (!match) {
|
|
675
|
+
await this.createSyncedRecord(id, pr, doc.createdBy);
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
remaining.delete(match);
|
|
679
|
+
match.name = pr.name.toLowerCase();
|
|
680
|
+
match.type = pr.type;
|
|
681
|
+
match.value = pr.value;
|
|
682
|
+
match.ttl = pr.ttl;
|
|
683
|
+
match.proxied = pr.proxied;
|
|
684
|
+
match.source = 'synced';
|
|
685
|
+
match.providerRecordId = pr.providerRecordId;
|
|
686
|
+
match.updatedAt = Date.now();
|
|
687
|
+
await match.save();
|
|
688
|
+
}
|
|
689
|
+
for (const staleMirror of remaining) {
|
|
690
|
+
await staleMirror.delete();
|
|
641
691
|
}
|
|
642
692
|
doc.lastSyncedAt = Date.now();
|
|
643
693
|
doc.updatedAt = doc.lastSyncedAt;
|
|
@@ -811,6 +861,9 @@ export class DnsManager {
|
|
|
811
861
|
ttl?: number;
|
|
812
862
|
proxied?: boolean;
|
|
813
863
|
createdBy: string;
|
|
864
|
+
managedBy?: string;
|
|
865
|
+
managedOwnerId?: string;
|
|
866
|
+
managedRecordKey?: string;
|
|
814
867
|
}): Promise<{ success: boolean; id?: string; message?: string }> {
|
|
815
868
|
const domain = await DomainDoc.findById(args.domainId);
|
|
816
869
|
if (!domain) return { success: false, message: 'Domain not found' };
|
|
@@ -828,6 +881,9 @@ export class DnsManager {
|
|
|
828
881
|
doc.createdAt = now;
|
|
829
882
|
doc.updatedAt = now;
|
|
830
883
|
doc.createdBy = args.createdBy;
|
|
884
|
+
doc.managedBy = args.managedBy;
|
|
885
|
+
doc.managedOwnerId = args.managedOwnerId;
|
|
886
|
+
doc.managedRecordKey = args.managedRecordKey;
|
|
831
887
|
|
|
832
888
|
if (domain.source === 'provider') {
|
|
833
889
|
// Push to provider first; only persist locally on success
|
|
@@ -849,12 +905,12 @@ export class DnsManager {
|
|
|
849
905
|
} catch (err: unknown) {
|
|
850
906
|
return { success: false, message: `Provider rejected record: ${(err as Error).message}` };
|
|
851
907
|
}
|
|
852
|
-
} else {
|
|
853
|
-
// dcrouter-hosted / authoritative — register with embedded DnsServer immediately
|
|
854
|
-
this.registerRecordWithDnsServer(doc);
|
|
855
908
|
}
|
|
856
909
|
|
|
857
910
|
await doc.save();
|
|
911
|
+
if (domain.source === 'dcrouter') {
|
|
912
|
+
await this.refreshLocalRrset(doc.domainId, doc.name, doc.type);
|
|
913
|
+
}
|
|
858
914
|
return { success: true, id: doc.id };
|
|
859
915
|
}
|
|
860
916
|
|
|
@@ -870,6 +926,8 @@ export class DnsManager {
|
|
|
870
926
|
const domain = await DomainDoc.findById(doc.domainId);
|
|
871
927
|
if (!domain) return { success: false, message: 'Parent domain not found' };
|
|
872
928
|
|
|
929
|
+
const previousName = doc.name;
|
|
930
|
+
const previousType = doc.type;
|
|
873
931
|
if (args.name !== undefined) doc.name = args.name.toLowerCase();
|
|
874
932
|
if (args.value !== undefined) doc.value = args.value;
|
|
875
933
|
if (args.ttl !== undefined) doc.ttl = args.ttl;
|
|
@@ -893,12 +951,15 @@ export class DnsManager {
|
|
|
893
951
|
} catch (err: unknown) {
|
|
894
952
|
return { success: false, message: `Provider rejected update: ${(err as Error).message}` };
|
|
895
953
|
}
|
|
896
|
-
} else {
|
|
897
|
-
// Re-register the local record so the new closure picks up the updated fields
|
|
898
|
-
this.registerRecordWithDnsServer(doc);
|
|
899
954
|
}
|
|
900
955
|
|
|
901
956
|
await doc.save();
|
|
957
|
+
if (domain.source === 'dcrouter') {
|
|
958
|
+
await this.refreshLocalRrset(doc.domainId, previousName, previousType);
|
|
959
|
+
if (previousName.toLowerCase() !== doc.name.toLowerCase()) {
|
|
960
|
+
await this.refreshLocalRrset(doc.domainId, doc.name, doc.type);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
902
963
|
return { success: true };
|
|
903
964
|
}
|
|
904
965
|
|
|
@@ -909,24 +970,23 @@ export class DnsManager {
|
|
|
909
970
|
if (!domain) return { success: false, message: 'Parent domain not found' };
|
|
910
971
|
|
|
911
972
|
if (domain.source === 'provider') {
|
|
912
|
-
if (domain.providerId
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
973
|
+
if (!domain.providerId || !doc.providerRecordId) {
|
|
974
|
+
return { success: false, message: 'Provider record metadata missing' };
|
|
975
|
+
}
|
|
976
|
+
const client = await this.getProviderClientById(domain.providerId);
|
|
977
|
+
if (!client) {
|
|
978
|
+
return { success: false, message: 'Provider client unavailable' };
|
|
979
|
+
}
|
|
980
|
+
try {
|
|
981
|
+
await client.deleteRecord(domain.name, doc.providerRecordId);
|
|
982
|
+
} catch (err: unknown) {
|
|
983
|
+
return { success: false, message: `Provider rejected delete: ${(err as Error).message}` };
|
|
921
984
|
}
|
|
922
985
|
}
|
|
923
|
-
// For dcrouter-hosted records: unregister the handler from the embedded DnsServer
|
|
924
|
-
// so the record stops being served immediately (not just after restart).
|
|
925
|
-
if (domain.source === 'dcrouter' && this.dnsServer) {
|
|
926
|
-
this.unregisterRecordFromDnsServer(doc);
|
|
927
|
-
}
|
|
928
|
-
|
|
929
986
|
await doc.delete();
|
|
987
|
+
if (domain.source === 'dcrouter') {
|
|
988
|
+
await this.refreshLocalRrset(doc.domainId, doc.name, doc.type);
|
|
989
|
+
}
|
|
930
990
|
return { success: true };
|
|
931
991
|
}
|
|
932
992
|
|
|
@@ -49,17 +49,21 @@ export class CloudflareDnsProvider implements IDnsProviderClient {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
public async listRecords(domain: string): Promise<IProviderRecord[]> {
|
|
52
|
-
const
|
|
52
|
+
const zoneId = await this.cfAccount.zoneManager.getZoneId(domain);
|
|
53
|
+
const records: any[] = [];
|
|
54
|
+
// RecordManager.listRecords() intentionally converts provider failures to
|
|
55
|
+
// an empty array. DNS reconciliation must distinguish an empty zone from
|
|
56
|
+
// an unavailable provider, so use the raw paginated iterator and let
|
|
57
|
+
// errors propagate to the durable reconciliation outcome.
|
|
58
|
+
for await (const record of (this.cfAccount as any).apiAccount.dns.records.list({
|
|
59
|
+
zone_id: zoneId,
|
|
60
|
+
per_page: 100,
|
|
61
|
+
})) {
|
|
62
|
+
records.push(record);
|
|
63
|
+
}
|
|
53
64
|
return records
|
|
54
65
|
.filter((r) => this.isSupportedType(r.type))
|
|
55
|
-
.map((r) => (
|
|
56
|
-
providerRecordId: r.id,
|
|
57
|
-
name: r.name,
|
|
58
|
-
type: r.type as TDnsRecordType,
|
|
59
|
-
value: r.content,
|
|
60
|
-
ttl: r.ttl,
|
|
61
|
-
proxied: r.proxied,
|
|
62
|
-
}));
|
|
66
|
+
.map((r) => this.fromApiRecord(r));
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
public async createRecord(
|
|
@@ -71,21 +75,14 @@ export class CloudflareDnsProvider implements IDnsProviderClient {
|
|
|
71
75
|
zone_id: zoneId,
|
|
72
76
|
type: record.type,
|
|
73
77
|
name: record.name,
|
|
74
|
-
content: record.value,
|
|
75
78
|
ttl: record.ttl ?? 1, // 1 = automatic
|
|
79
|
+
...this.toApiRecordValue(record),
|
|
76
80
|
};
|
|
77
81
|
if (record.proxied !== undefined) {
|
|
78
82
|
apiRecord.proxied = record.proxied;
|
|
79
83
|
}
|
|
80
84
|
const created = await (this.cfAccount as any).apiAccount.dns.records.create(apiRecord);
|
|
81
|
-
return
|
|
82
|
-
providerRecordId: created.id,
|
|
83
|
-
name: created.name,
|
|
84
|
-
type: created.type as TDnsRecordType,
|
|
85
|
-
value: created.content,
|
|
86
|
-
ttl: created.ttl,
|
|
87
|
-
proxied: created.proxied,
|
|
88
|
-
};
|
|
85
|
+
return this.fromApiRecord(created);
|
|
89
86
|
}
|
|
90
87
|
|
|
91
88
|
public async updateRecord(
|
|
@@ -98,8 +95,8 @@ export class CloudflareDnsProvider implements IDnsProviderClient {
|
|
|
98
95
|
zone_id: zoneId,
|
|
99
96
|
type: record.type,
|
|
100
97
|
name: record.name,
|
|
101
|
-
content: record.value,
|
|
102
98
|
ttl: record.ttl ?? 1,
|
|
99
|
+
...this.toApiRecordValue(record),
|
|
103
100
|
};
|
|
104
101
|
if (record.proxied !== undefined) {
|
|
105
102
|
apiRecord.proxied = record.proxied;
|
|
@@ -108,14 +105,7 @@ export class CloudflareDnsProvider implements IDnsProviderClient {
|
|
|
108
105
|
providerRecordId,
|
|
109
106
|
apiRecord,
|
|
110
107
|
);
|
|
111
|
-
return
|
|
112
|
-
providerRecordId: updated.id,
|
|
113
|
-
name: updated.name,
|
|
114
|
-
type: updated.type as TDnsRecordType,
|
|
115
|
-
value: updated.content,
|
|
116
|
-
ttl: updated.ttl,
|
|
117
|
-
proxied: updated.proxied,
|
|
118
|
-
};
|
|
108
|
+
return this.fromApiRecord(updated);
|
|
119
109
|
}
|
|
120
110
|
|
|
121
111
|
public async deleteRecord(domain: string, providerRecordId: string): Promise<void> {
|
|
@@ -128,4 +118,33 @@ export class CloudflareDnsProvider implements IDnsProviderClient {
|
|
|
128
118
|
private isSupportedType(type: string): boolean {
|
|
129
119
|
return ['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'NS', 'SOA', 'CAA'].includes(type);
|
|
130
120
|
}
|
|
121
|
+
|
|
122
|
+
private toApiRecordValue(record: IProviderRecordInput): { content: string; priority?: number } {
|
|
123
|
+
if (record.type !== 'MX') {
|
|
124
|
+
return { content: record.value };
|
|
125
|
+
}
|
|
126
|
+
const match = record.value.trim().match(/^(\d+)\s+(.+)$/);
|
|
127
|
+
if (!match) {
|
|
128
|
+
throw new Error(`CloudflareDnsProvider: invalid MX value "${record.value}"; expected "<priority> <exchange>"`);
|
|
129
|
+
}
|
|
130
|
+
const priority = Number.parseInt(match[1], 10);
|
|
131
|
+
if (!Number.isInteger(priority) || priority < 0 || priority > 65535) {
|
|
132
|
+
throw new Error(`CloudflareDnsProvider: invalid MX priority in "${record.value}"`);
|
|
133
|
+
}
|
|
134
|
+
return { content: match[2], priority };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private fromApiRecord(record: any): IProviderRecord {
|
|
138
|
+
const value = record.type === 'MX'
|
|
139
|
+
? `${Number(record.priority)} ${record.content}`
|
|
140
|
+
: String(record.content);
|
|
141
|
+
return {
|
|
142
|
+
providerRecordId: String(record.id),
|
|
143
|
+
name: String(record.name),
|
|
144
|
+
type: record.type as TDnsRecordType,
|
|
145
|
+
value,
|
|
146
|
+
ttl: Number(record.ttl),
|
|
147
|
+
...(record.proxied !== undefined ? { proxied: Boolean(record.proxied) } : {}),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
131
150
|
}
|