@serve.zone/dcrouter 32.1.1 → 32.1.3

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 (48) hide show
  1. package/deno.json +1 -1
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/classes.dcrouter.js +2 -2
  4. package/dist_ts/config/classes.gateway-client-manager.js +1 -1
  5. package/dist_ts/db/documents/classes.cached.email.d.ts +0 -1
  6. package/dist_ts/db/documents/classes.cached.email.js +45 -48
  7. package/dist_ts/db/documents/classes.email-traffic-bucket.doc.d.ts +0 -1
  8. package/dist_ts/db/documents/classes.email-traffic-bucket.doc.js +61 -50
  9. package/dist_ts/db/documents/classes.gateway-client.doc.d.ts +9 -2
  10. package/dist_ts/db/documents/classes.gateway-client.doc.js +9 -11
  11. package/dist_ts/db/documents/classes.ip-intelligence.doc.d.ts +4 -3
  12. package/dist_ts/db/documents/classes.ip-intelligence.doc.js +27 -31
  13. package/dist_ts/db/documents/classes.ops-config-event.doc.d.ts +0 -2
  14. package/dist_ts/db/documents/classes.ops-config-event.doc.js +70 -68
  15. package/dist_ts/db/documents/classes.smartmta-storage.doc.d.ts +14 -0
  16. package/dist_ts/db/documents/classes.smartmta-storage.doc.js +17 -3
  17. package/dist_ts/db/documents/classes.webpush-admission.doc.d.ts +0 -1
  18. package/dist_ts/db/documents/classes.webpush-admission.doc.js +14 -5
  19. package/dist_ts/db/documents/classes.webpush-binding.doc.d.ts +1 -1
  20. package/dist_ts/db/documents/classes.webpush-binding.doc.js +46 -8
  21. package/dist_ts/db/documents/classes.webpush-spool.doc.d.ts +0 -1
  22. package/dist_ts/db/documents/classes.webpush-spool.doc.js +34 -6
  23. package/dist_ts/dns/manager.dns.js +7 -3
  24. package/dist_ts/email/classes.smartmta-storage-manager.d.ts +15 -3
  25. package/dist_ts/email/classes.smartmta-storage-manager.js +62 -36
  26. package/dist_ts/security/classes.security-policy-manager.js +11 -9
  27. package/dist_ts/webpush/classes.webpush-manager.d.ts +5 -1
  28. package/dist_ts/webpush/classes.webpush-manager.js +162 -232
  29. package/dist_ts_web/00_commitinfo_data.js +1 -1
  30. package/package.json +3 -3
  31. package/readme.md +1 -0
  32. package/ts/00_commitinfo_data.ts +1 -1
  33. package/ts/classes.dcrouter.ts +1 -1
  34. package/ts/config/classes.gateway-client-manager.ts +4 -2
  35. package/ts/db/documents/classes.cached.email.ts +51 -66
  36. package/ts/db/documents/classes.email-traffic-bucket.doc.ts +65 -59
  37. package/ts/db/documents/classes.gateway-client.doc.ts +38 -18
  38. package/ts/db/documents/classes.ip-intelligence.doc.ts +37 -40
  39. package/ts/db/documents/classes.ops-config-event.doc.ts +80 -77
  40. package/ts/db/documents/classes.smartmta-storage.doc.ts +16 -1
  41. package/ts/db/documents/classes.webpush-admission.doc.ts +14 -5
  42. package/ts/db/documents/classes.webpush-binding.doc.ts +54 -8
  43. package/ts/db/documents/classes.webpush-spool.doc.ts +40 -6
  44. package/ts/dns/manager.dns.ts +6 -2
  45. package/ts/email/classes.smartmta-storage-manager.ts +66 -50
  46. package/ts/security/classes.security-policy-manager.ts +10 -8
  47. package/ts/webpush/classes.webpush-manager.ts +175 -280
  48. package/ts_web/00_commitinfo_data.ts +1 -1
@@ -1,9 +1,9 @@
1
- import * as plugins from '../plugins.js';
2
1
  import type {
3
2
  IStorageManager,
4
3
  IStorageListPageOptions,
5
4
  IStorageListPageResult,
6
5
  } from '@push.rocks/smartmta';
6
+ import { SmartMtaStorageDoc } from '../db/documents/classes.smartmta-storage.doc.js';
7
7
  import {
8
8
  getSmartMtaTextNamespace,
9
9
  hashSmartMtaStorageKey,
@@ -11,46 +11,45 @@ import {
11
11
  validateSmartMtaTextValue,
12
12
  } from './smartmta-storage-policy.js';
13
13
 
14
- interface ISmartMtaStorageDocument {
15
- _id: string;
16
- id: string;
17
- key: string;
18
- value: string;
19
- createdAt: string;
20
- updatedAt: string;
21
- }
22
-
14
+ /**
15
+ * SmartMTA's key/value storage, backed by the content-addressed `SmartMtaStorageDoc`.
16
+ *
17
+ * The document model declares `identityAsDocumentId: 'id'`, so the `sha256(normalizedKey)`
18
+ * identity is the stored `_id`: every read and write below pins `id` by equality and is
19
+ * answered through the primary key, which is what makes `set()` one race-free upsert
20
+ * without a secondary unique index. The model owns its database binding, so this manager
21
+ * holds no connection and no collection handle of its own.
22
+ */
23
23
  export class SmartMtaStorageManager implements IStorageManager {
24
- private readonly collection: any;
25
-
26
- constructor(smartdataDb: plugins.smartdata.SmartdataDb) {
27
- this.collection = smartdataDb.mongoDb.collection('SmartMtaStorageDoc');
28
- }
29
-
24
+ /**
25
+ * A stored document is only usable when it still carries the content address it was
26
+ * written under. `_id` is not re-read here: it equals `id` by construction — an
27
+ * identity-pinned upsert derives the inserted `_id` from its own filter, and the client
28
+ * refuses to store a document whose `_id` diverges from its declared identity.
29
+ */
30
30
  private validateDocument(
31
- document: ISmartMtaStorageDocument,
32
- expectedKey?: string,
33
- ): ISmartMtaStorageDocument {
34
- const normalizedKey = normalizeSmartMtaStorageKey(document.key);
31
+ documentArg: SmartMtaStorageDoc,
32
+ expectedKeyArg?: string,
33
+ ): SmartMtaStorageDoc {
34
+ const normalizedKey = normalizeSmartMtaStorageKey(documentArg.key);
35
35
  const expectedId = hashSmartMtaStorageKey(normalizedKey);
36
36
  if (
37
- document._id !== expectedId
38
- || document.id !== expectedId
39
- || document.key !== normalizedKey
40
- || (expectedKey && document.key !== expectedKey)
41
- || typeof document.value !== 'string'
37
+ documentArg.id !== expectedId
38
+ || documentArg.key !== normalizedKey
39
+ || (expectedKeyArg && documentArg.key !== expectedKeyArg)
40
+ || typeof documentArg.value !== 'string'
42
41
  ) {
43
- throw new Error(`Corrupt SmartMTA storage document: ${document._id}`);
42
+ throw new Error(`Corrupt SmartMTA storage document: ${documentArg.id}`);
44
43
  }
45
- validateSmartMtaTextValue(document.key, document.value);
46
- return document;
44
+ validateSmartMtaTextValue(documentArg.key, documentArg.value);
45
+ return documentArg;
47
46
  }
48
47
 
49
48
  public async get(key: string): Promise<string | null> {
50
49
  const normalizedKey = normalizeSmartMtaStorageKey(key);
51
50
  getSmartMtaTextNamespace(normalizedKey);
52
51
  const id = hashSmartMtaStorageKey(normalizedKey);
53
- const document = await this.collection.findOne({ _id: id });
52
+ const document = await SmartMtaStorageDoc.getInstance<SmartMtaStorageDoc>({ id });
54
53
  return document ? this.validateDocument(document, normalizedKey).value : null;
55
54
  }
56
55
 
@@ -58,17 +57,21 @@ export class SmartMtaStorageManager implements IStorageManager {
58
57
  const normalizedKey = normalizeSmartMtaStorageKey(key);
59
58
  validateSmartMtaTextValue(normalizedKey, value);
60
59
  const id = hashSmartMtaStorageKey(normalizedKey);
61
- const existing = await this.collection.findOne({ _id: id });
60
+ const existing = await SmartMtaStorageDoc.getInstance<SmartMtaStorageDoc>({ id });
62
61
  if (existing) {
63
62
  this.validateDocument(existing, normalizedKey);
64
63
  }
65
64
 
66
65
  const now = new Date().toISOString();
67
- await this.collection.updateOne(
68
- { _id: id },
66
+ // The equality anchor on `id` is mandatory for an upsert on this model: MongoDB derives
67
+ // the inserted primary key from the filter, and the client refuses an unpinned upsert
68
+ // rather than let a generated `_id` contradict the declared identity. It seeds `id`
69
+ // itself from that anchor, so the value never has to be repeated in `$setOnInsert` —
70
+ // and may not be, because `$set` can never modify an immutable `@unI()` field.
71
+ await SmartMtaStorageDoc.atomicUpdate<SmartMtaStorageDoc>(
72
+ { id },
69
73
  {
70
74
  $set: {
71
- id,
72
75
  key: normalizedKey,
73
76
  value,
74
77
  updatedAt: now,
@@ -80,7 +83,7 @@ export class SmartMtaStorageManager implements IStorageManager {
80
83
  { upsert: true },
81
84
  );
82
85
 
83
- const stored = await this.collection.findOne({ _id: id });
86
+ const stored = await SmartMtaStorageDoc.getInstance<SmartMtaStorageDoc>({ id });
84
87
  if (!stored || this.validateDocument(stored, normalizedKey).value !== value) {
85
88
  throw new Error(`SmartMTA storage write verification failed: ${normalizedKey}`);
86
89
  }
@@ -89,16 +92,22 @@ export class SmartMtaStorageManager implements IStorageManager {
89
92
  public async list(prefix: string): Promise<string[]> {
90
93
  const normalizedPrefix = normalizeSmartMtaStorageKey(prefix);
91
94
  getSmartMtaTextNamespace(normalizedPrefix);
92
- const documents = await this.collection.find({
95
+ const cursor = await SmartMtaStorageDoc.getCursor<SmartMtaStorageDoc>({
93
96
  key: {
94
97
  $gte: normalizedPrefix,
95
- $lt: `${normalizedPrefix}\uffff`,
98
+ $lt: `${normalizedPrefix}￿`,
96
99
  },
97
- }).toArray();
100
+ });
101
+ let documents: SmartMtaStorageDoc[];
102
+ try {
103
+ documents = await cursor.toArray();
104
+ } finally {
105
+ await cursor.close();
106
+ }
98
107
 
99
108
  return documents
100
- .map((document: ISmartMtaStorageDocument) => this.validateDocument(document).key)
101
- .filter((key: string) => key.startsWith(normalizedPrefix))
109
+ .map((documentArg) => this.validateDocument(documentArg).key)
110
+ .filter((keyArg) => keyArg.startsWith(normalizedPrefix))
102
111
  .sort();
103
112
  }
104
113
 
@@ -126,24 +135,29 @@ export class SmartMtaStorageManager implements IStorageManager {
126
135
  seekAfter = decoded;
127
136
  }
128
137
 
129
- const documents = await this.collection
130
- .find({
138
+ const cursor = await SmartMtaStorageDoc.getCursor<SmartMtaStorageDoc>(
139
+ {
131
140
  key: {
132
141
  ...(seekAfter ? { $gt: seekAfter } : { $gte: normalizedPrefix }),
133
142
  $lt: `${normalizedPrefix}￿`,
134
143
  },
135
- })
136
- .sort({ key: 1 })
137
- .limit(limit + 1)
138
- .toArray();
144
+ },
145
+ { sort: { key: 1 }, limit: limit + 1 },
146
+ );
147
+ let documents: SmartMtaStorageDoc[];
148
+ try {
149
+ documents = await cursor.toArray();
150
+ } finally {
151
+ await cursor.close();
152
+ }
139
153
 
140
154
  const hasMore = documents.length > limit;
141
155
  const pageDocuments = documents.slice(0, limit);
142
156
  const keys = pageDocuments
143
- .map((document: ISmartMtaStorageDocument) => this.validateDocument(document).key)
144
- .filter((key: string) => key.startsWith(normalizedPrefix));
157
+ .map((documentArg) => this.validateDocument(documentArg).key)
158
+ .filter((keyArg) => keyArg.startsWith(normalizedPrefix));
145
159
  const lastKey = pageDocuments.length > 0
146
- ? (pageDocuments[pageDocuments.length - 1] as ISmartMtaStorageDocument).key
160
+ ? pageDocuments[pageDocuments.length - 1].key
147
161
  : undefined;
148
162
 
149
163
  return {
@@ -158,10 +172,12 @@ export class SmartMtaStorageManager implements IStorageManager {
158
172
  const normalizedKey = normalizeSmartMtaStorageKey(key);
159
173
  getSmartMtaTextNamespace(normalizedKey);
160
174
  const id = hashSmartMtaStorageKey(normalizedKey);
161
- const existing = await this.collection.findOne({ _id: id });
175
+ const existing = await SmartMtaStorageDoc.getInstance<SmartMtaStorageDoc>({ id });
162
176
  if (existing) {
163
177
  this.validateDocument(existing, normalizedKey);
164
- await this.collection.deleteOne({ _id: id });
178
+ // The identity pins a single document, which is what the client requires of a
179
+ // singular delete; the primary key answers it.
180
+ await SmartMtaStorageDoc.atomicDelete<SmartMtaStorageDoc>({ id });
165
181
  }
166
182
  }
167
183
  }
@@ -296,13 +296,13 @@ export class SecurityPolicyManager {
296
296
  const expiryCutoff = this.now() - IP_INTELLIGENCE_RETENTION_MAX_AGE_MS;
297
297
 
298
298
  while (!this.isStopping) {
299
- const expiredIds = await IpIntelligenceDoc.findOldestIds({
299
+ const expiredIpAddresses = await IpIntelligenceDoc.findOldestIpAddresses({
300
300
  lastSeenBefore: expiryCutoff,
301
301
  limit: IP_INTELLIGENCE_RETENTION_BATCH_SIZE,
302
302
  });
303
- if (expiredIds.length === 0) break;
304
- deletedCount += await IpIntelligenceDoc.deleteByDocumentIds(expiredIds);
305
- if (expiredIds.length < IP_INTELLIGENCE_RETENTION_BATCH_SIZE) break;
303
+ if (expiredIpAddresses.length === 0) break;
304
+ deletedCount += await IpIntelligenceDoc.deleteByIpAddresses(expiredIpAddresses);
305
+ if (expiredIpAddresses.length < IP_INTELLIGENCE_RETENTION_BATCH_SIZE) break;
306
306
  }
307
307
 
308
308
  let currentCount = await IpIntelligenceDoc.countAll();
@@ -315,13 +315,15 @@ export class SecurityPolicyManager {
315
315
  remainingToDelete,
316
316
  IP_INTELLIGENCE_RETENTION_BATCH_SIZE,
317
317
  );
318
- const oldestIds = await IpIntelligenceDoc.findOldestIds({ limit: batchLimit });
319
- if (oldestIds.length === 0) break;
320
- const deletedThisBatch = await IpIntelligenceDoc.deleteByDocumentIds(oldestIds);
318
+ const oldestIpAddresses = await IpIntelligenceDoc.findOldestIpAddresses({
319
+ limit: batchLimit,
320
+ });
321
+ if (oldestIpAddresses.length === 0) break;
322
+ const deletedThisBatch = await IpIntelligenceDoc.deleteByIpAddresses(oldestIpAddresses);
321
323
  deletedCount += deletedThisBatch;
322
324
  currentCount -= deletedThisBatch;
323
325
  remainingToDelete -= deletedThisBatch;
324
- if (deletedThisBatch === 0 || oldestIds.length < batchLimit) break;
326
+ if (deletedThisBatch === 0 || oldestIpAddresses.length < batchLimit) break;
325
327
  }
326
328
  }
327
329