@serve.zone/coremail 1.0.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/.smartconfig.json +50 -0
- package/changelog.md +10 -0
- package/cli.js +4 -0
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/classes.auth.d.ts +45 -0
- package/dist_ts/classes.auth.js +204 -0
- package/dist_ts/classes.config.d.ts +2 -0
- package/dist_ts/classes.config.js +72 -0
- package/dist_ts/classes.coremail.d.ts +41 -0
- package/dist_ts/classes.coremail.js +155 -0
- package/dist_ts/classes.desiredstate.d.ts +40 -0
- package/dist_ts/classes.desiredstate.js +382 -0
- package/dist_ts/classes.gateway.d.ts +62 -0
- package/dist_ts/classes.gateway.js +669 -0
- package/dist_ts/classes.inbound.d.ts +49 -0
- package/dist_ts/classes.inbound.js +702 -0
- package/dist_ts/classes.maintenance.d.ts +16 -0
- package/dist_ts/classes.maintenance.js +339 -0
- package/dist_ts/classes.models.d.ts +161 -0
- package/dist_ts/classes.models.js +595 -0
- package/dist_ts/classes.server.d.ts +40 -0
- package/dist_ts/classes.server.js +309 -0
- package/dist_ts/classes.storage.d.ts +32 -0
- package/dist_ts/classes.storage.js +317 -0
- package/dist_ts/classes.submissions.d.ts +33 -0
- package/dist_ts/classes.submissions.js +385 -0
- package/dist_ts/classes.transfer.d.ts +41 -0
- package/dist_ts/classes.transfer.js +289 -0
- package/dist_ts/coremail.cursor.d.ts +20 -0
- package/dist_ts/coremail.cursor.js +191 -0
- package/dist_ts/coremail.log.d.ts +3 -0
- package/dist_ts/coremail.log.js +11 -0
- package/dist_ts/coremail.mime.d.ts +8 -0
- package/dist_ts/coremail.mime.js +63 -0
- package/dist_ts/coremail.persistence.d.ts +148 -0
- package/dist_ts/coremail.persistence.js +695 -0
- package/dist_ts/coremail.quota.d.ts +29 -0
- package/dist_ts/coremail.quota.js +149 -0
- package/dist_ts/coremail.selectors.d.ts +3 -0
- package/dist_ts/coremail.selectors.js +21 -0
- package/dist_ts/coremail.validation.d.ts +9 -0
- package/dist_ts/coremail.validation.js +325 -0
- package/dist_ts/index.d.ts +4 -0
- package/dist_ts/index.js +39 -0
- package/dist_ts/interfaces.d.ts +92 -0
- package/dist_ts/interfaces.js +2 -0
- package/dist_ts/plugins.d.ts +11 -0
- package/dist_ts/plugins.js +12 -0
- package/license.md +19 -0
- package/package.json +48 -0
- package/readme.md +199 -0
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/classes.auth.ts +321 -0
- package/ts/classes.config.ts +104 -0
- package/ts/classes.coremail.ts +249 -0
- package/ts/classes.desiredstate.ts +508 -0
- package/ts/classes.gateway.ts +811 -0
- package/ts/classes.inbound.ts +898 -0
- package/ts/classes.maintenance.ts +335 -0
- package/ts/classes.models.ts +530 -0
- package/ts/classes.server.ts +444 -0
- package/ts/classes.storage.ts +448 -0
- package/ts/classes.submissions.ts +554 -0
- package/ts/classes.transfer.ts +379 -0
- package/ts/coremail.cursor.ts +328 -0
- package/ts/coremail.log.ts +21 -0
- package/ts/coremail.mime.ts +94 -0
- package/ts/coremail.persistence.ts +1156 -0
- package/ts/coremail.quota.ts +214 -0
- package/ts/coremail.selectors.ts +35 -0
- package/ts/coremail.validation.ts +446 -0
- package/ts/index.ts +38 -0
- package/ts/interfaces.ts +109 -0
- package/ts/plugins.ts +11 -0
|
@@ -0,0 +1,1156 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import {
|
|
3
|
+
normalizeCoreMailConnectionInfo,
|
|
4
|
+
normalizeCoreMailEnvelope,
|
|
5
|
+
normalizeCoreMailGatewayMessage,
|
|
6
|
+
normalizeCoreMailOutboundMessage,
|
|
7
|
+
} from './coremail.validation.js';
|
|
8
|
+
import type {
|
|
9
|
+
ISubmissionPartRecord,
|
|
10
|
+
TTransferGrantPurpose,
|
|
11
|
+
} from './interfaces.js';
|
|
12
|
+
|
|
13
|
+
type TCoreMailSha256 = plugins.serveZoneInterfaces.data.TCoreMailSha256;
|
|
14
|
+
|
|
15
|
+
export interface ICoreMailStateRecord {
|
|
16
|
+
stateId: string;
|
|
17
|
+
appliedConfigEpoch: number;
|
|
18
|
+
appliedDesiredStateDigest: TCoreMailSha256;
|
|
19
|
+
snapshotId: string;
|
|
20
|
+
updatedAt: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ICoreMailDesiredSnapshotRecord {
|
|
24
|
+
snapshotId: string;
|
|
25
|
+
reconciliationId: string;
|
|
26
|
+
expectedAppliedConfigEpoch: number;
|
|
27
|
+
configEpoch: number;
|
|
28
|
+
desiredStateDigest: TCoreMailSha256;
|
|
29
|
+
lengthBytes: number;
|
|
30
|
+
objectKey: string;
|
|
31
|
+
state: 'staged' | 'active' | 'superseded' | 'failed' | 'retiring';
|
|
32
|
+
createdAt: number;
|
|
33
|
+
updatedAt: number;
|
|
34
|
+
activatedAt?: number;
|
|
35
|
+
retiringAt?: number;
|
|
36
|
+
errorCode?: plugins.serveZoneInterfaces.data.TCoreMailErrorCode;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ICoreMailQuotaCounterRecord {
|
|
40
|
+
counterId: TCoreMailSha256;
|
|
41
|
+
tenantId: string;
|
|
42
|
+
serviceId: string;
|
|
43
|
+
bindingId: string;
|
|
44
|
+
bindingRevision: number;
|
|
45
|
+
kind: 'outbound-minute' | 'outbound-day' | 'pending-inbound';
|
|
46
|
+
windowStartedAt?: number;
|
|
47
|
+
count: number;
|
|
48
|
+
createdAt: number;
|
|
49
|
+
updatedAt: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ICoreMailTransferGrantRecord {
|
|
53
|
+
grantId: string;
|
|
54
|
+
authorityKind: 'control' | 'gateway' | 'workload';
|
|
55
|
+
tenantId: string;
|
|
56
|
+
serviceId: string;
|
|
57
|
+
bindingId: string;
|
|
58
|
+
purpose: TTransferGrantPurpose;
|
|
59
|
+
method: 'PUT' | 'GET';
|
|
60
|
+
path: string;
|
|
61
|
+
bearerSha256: TCoreMailSha256;
|
|
62
|
+
expectedSha256: TCoreMailSha256;
|
|
63
|
+
lengthBytes: number;
|
|
64
|
+
contentType: string;
|
|
65
|
+
objectKey: string;
|
|
66
|
+
state: 'issued' | 'transferring' | 'consumed' | 'failed' | 'expired';
|
|
67
|
+
expiresAt: number;
|
|
68
|
+
createdAt: number;
|
|
69
|
+
consumedAt?: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ICoreMailSubmissionRecord {
|
|
73
|
+
submissionId: string;
|
|
74
|
+
tenantId: string;
|
|
75
|
+
serviceId: string;
|
|
76
|
+
bindingId: string;
|
|
77
|
+
bindingRevision: number;
|
|
78
|
+
revision: number;
|
|
79
|
+
idempotencyKey: string;
|
|
80
|
+
submissionDigest: TCoreMailSha256;
|
|
81
|
+
state: plugins.serveZoneInterfaces.data.TCoreMailSubmissionState;
|
|
82
|
+
message: plugins.serveZoneInterfaces.data.ICoreMailOutboundMessageDescriptor;
|
|
83
|
+
parts: ISubmissionPartRecord[];
|
|
84
|
+
mimeObjectKey?: string;
|
|
85
|
+
mimeSha256?: TCoreMailSha256;
|
|
86
|
+
mimeLengthBytes?: number;
|
|
87
|
+
mimePublishedAt?: number;
|
|
88
|
+
transportMessageId?: string;
|
|
89
|
+
transportGrantId?: string;
|
|
90
|
+
gatewayStatusUpdatedAt?: number;
|
|
91
|
+
attempts: number;
|
|
92
|
+
nextAttemptAt?: number;
|
|
93
|
+
workerRetryAt?: number;
|
|
94
|
+
retiringAt?: number;
|
|
95
|
+
leaseOwnerTaskId?: string;
|
|
96
|
+
leaseToken?: string;
|
|
97
|
+
leaseExpiresAt?: number;
|
|
98
|
+
acceptedAt?: number;
|
|
99
|
+
deliveredAt?: number;
|
|
100
|
+
terminalAt?: number;
|
|
101
|
+
error?: plugins.serveZoneInterfaces.data.ICoreMailErrorData;
|
|
102
|
+
createdAt: number;
|
|
103
|
+
updatedAt: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface ICoreMailInboundDeliveryRecord {
|
|
107
|
+
deliveryId: string;
|
|
108
|
+
tenantId: string;
|
|
109
|
+
serviceId: string;
|
|
110
|
+
bindingId: string;
|
|
111
|
+
bindingRevision: number;
|
|
112
|
+
revision: number;
|
|
113
|
+
transportDeliveryId: string;
|
|
114
|
+
state: plugins.serveZoneInterfaces.data.TCoreMailInboundDeliveryState;
|
|
115
|
+
envelope: plugins.serveZoneInterfaces.data.ICoreMailEnvelope;
|
|
116
|
+
rawMimeSha256: TCoreMailSha256;
|
|
117
|
+
rawMimeLengthBytes: number;
|
|
118
|
+
rawMimeObjectKey: string;
|
|
119
|
+
messageId?: string;
|
|
120
|
+
subject?: string;
|
|
121
|
+
receivedAt: number;
|
|
122
|
+
updatedAt: number;
|
|
123
|
+
fetchGrantId?: string;
|
|
124
|
+
acknowledgedOutcome?: plugins.serveZoneInterfaces.data.TCoreMailInboundAckOutcome;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface ICoreMailRecipientHandleRecord {
|
|
128
|
+
routingHandle: string;
|
|
129
|
+
tenantId: string;
|
|
130
|
+
serviceId: string;
|
|
131
|
+
bindingId: string;
|
|
132
|
+
bindingRevision: number;
|
|
133
|
+
maxPendingInbound: number;
|
|
134
|
+
recipient: string;
|
|
135
|
+
envelopeDigest: TCoreMailSha256;
|
|
136
|
+
state: 'issued' | 'consumed' | 'expired';
|
|
137
|
+
handoffId?: string;
|
|
138
|
+
expiresAt: number;
|
|
139
|
+
createdAt: number;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface ICoreMailInboundHandoffRecord {
|
|
143
|
+
handoffId: string;
|
|
144
|
+
revision: number;
|
|
145
|
+
transportDeliveryId: string;
|
|
146
|
+
routingHandles: string[];
|
|
147
|
+
message: plugins.serveZoneInterfaces.data.ICoreMailGatewayMessageDescriptor;
|
|
148
|
+
source: plugins.serveZoneInterfaces.data.IMailConnectionInfo;
|
|
149
|
+
state: 'prepared' | 'uploaded' | 'completed' | 'failed' | 'retiring';
|
|
150
|
+
objectKey: string;
|
|
151
|
+
grantId?: string;
|
|
152
|
+
deliveryIds: string[];
|
|
153
|
+
createdAt: number;
|
|
154
|
+
updatedAt: number;
|
|
155
|
+
completedAt?: number;
|
|
156
|
+
retiringAt?: number;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const identifierRegex =
|
|
160
|
+
/^[A-Za-z0-9](?:[A-Za-z0-9._:-]{0,126}[A-Za-z0-9])?$/;
|
|
161
|
+
const uuidRegex =
|
|
162
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
163
|
+
const objectKeyRegex = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,1023}$/;
|
|
164
|
+
const contentTypeRegex =
|
|
165
|
+
/^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+(?:; ?[a-z0-9!#$&^_.+-]+=(?:"[^"\r\n]{1,100}"|[a-z0-9!#$&^_.+-]+))*$/i;
|
|
166
|
+
|
|
167
|
+
const requireRecord = (
|
|
168
|
+
valueArg: unknown,
|
|
169
|
+
requiredArg: string[],
|
|
170
|
+
optionalArg: string[],
|
|
171
|
+
labelArg: string,
|
|
172
|
+
): Record<string, unknown> => {
|
|
173
|
+
if (
|
|
174
|
+
!valueArg
|
|
175
|
+
|| typeof valueArg !== 'object'
|
|
176
|
+
|| Array.isArray(valueArg)
|
|
177
|
+
|| (
|
|
178
|
+
Object.getPrototypeOf(valueArg) !== Object.prototype
|
|
179
|
+
&& Object.getPrototypeOf(valueArg) !== null
|
|
180
|
+
)
|
|
181
|
+
) {
|
|
182
|
+
throw new Error(`${labelArg} must be a plain record.`);
|
|
183
|
+
}
|
|
184
|
+
const value = valueArg as Record<string, unknown>;
|
|
185
|
+
const allowed = new Set([...requiredArg, ...optionalArg]);
|
|
186
|
+
if (
|
|
187
|
+
requiredArg.some((keyArg) => !Object.hasOwn(value, keyArg))
|
|
188
|
+
|| Object.keys(value).some((keyArg) => !allowed.has(keyArg))
|
|
189
|
+
|| optionalArg.some((keyArg) =>
|
|
190
|
+
Object.hasOwn(value, keyArg) && value[keyArg] === undefined
|
|
191
|
+
)
|
|
192
|
+
) {
|
|
193
|
+
throw new Error(`${labelArg} has missing, unknown, or undefined fields.`);
|
|
194
|
+
}
|
|
195
|
+
return value;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const requireString = (
|
|
199
|
+
valueArg: unknown,
|
|
200
|
+
labelArg: string,
|
|
201
|
+
maximumArg = 2_048,
|
|
202
|
+
): string => {
|
|
203
|
+
if (
|
|
204
|
+
typeof valueArg !== 'string'
|
|
205
|
+
|| valueArg.length === 0
|
|
206
|
+
|| valueArg.length > maximumArg
|
|
207
|
+
|| valueArg.trim() !== valueArg
|
|
208
|
+
) {
|
|
209
|
+
throw new Error(`${labelArg} must be a bounded canonical string.`);
|
|
210
|
+
}
|
|
211
|
+
return valueArg;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const requireIdentifier = (valueArg: unknown, labelArg: string): string => {
|
|
215
|
+
const value = requireString(valueArg, labelArg, 128);
|
|
216
|
+
if (!identifierRegex.test(value)) {
|
|
217
|
+
throw new Error(`${labelArg} must be a canonical identifier.`);
|
|
218
|
+
}
|
|
219
|
+
return value;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const requireUuid = (valueArg: unknown, labelArg: string): string => {
|
|
223
|
+
if (typeof valueArg !== 'string' || !uuidRegex.test(valueArg)) {
|
|
224
|
+
throw new Error(`${labelArg} must be a canonical UUID.`);
|
|
225
|
+
}
|
|
226
|
+
return valueArg;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const requireInteger = (
|
|
230
|
+
valueArg: unknown,
|
|
231
|
+
labelArg: string,
|
|
232
|
+
minimumArg = 0,
|
|
233
|
+
): number => {
|
|
234
|
+
if (!Number.isSafeInteger(valueArg) || Number(valueArg) < minimumArg) {
|
|
235
|
+
throw new Error(`${labelArg} must be a bounded safe integer.`);
|
|
236
|
+
}
|
|
237
|
+
return Number(valueArg);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const requireSha256 = (
|
|
241
|
+
valueArg: unknown,
|
|
242
|
+
labelArg: string,
|
|
243
|
+
): TCoreMailSha256 => {
|
|
244
|
+
try {
|
|
245
|
+
return plugins.serveZoneInterfaces.data.normalizeCoreMailSha256(valueArg);
|
|
246
|
+
} catch {
|
|
247
|
+
throw new Error(`${labelArg} must be a canonical SHA-256 digest.`);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const requireObjectKey = (valueArg: unknown, labelArg: string): string => {
|
|
252
|
+
const value = requireString(valueArg, labelArg, 1_024);
|
|
253
|
+
if (
|
|
254
|
+
!objectKeyRegex.test(value)
|
|
255
|
+
|| value.includes('//')
|
|
256
|
+
|| value.split('/').some((partArg) => partArg === '..')
|
|
257
|
+
) {
|
|
258
|
+
throw new Error(`${labelArg} must be a canonical object key.`);
|
|
259
|
+
}
|
|
260
|
+
return value;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const canonicalJson = (valueArg: unknown): string => {
|
|
264
|
+
const visit = (value: unknown): unknown => {
|
|
265
|
+
if (Array.isArray(value)) return value.map(visit);
|
|
266
|
+
if (
|
|
267
|
+
value
|
|
268
|
+
&& typeof value === 'object'
|
|
269
|
+
&& (
|
|
270
|
+
Object.getPrototypeOf(value) === Object.prototype
|
|
271
|
+
|| Object.getPrototypeOf(value) === null
|
|
272
|
+
)
|
|
273
|
+
) {
|
|
274
|
+
return Object.fromEntries(
|
|
275
|
+
Object.keys(value as Record<string, unknown>)
|
|
276
|
+
.sort()
|
|
277
|
+
.map((keyArg) => [
|
|
278
|
+
keyArg,
|
|
279
|
+
visit((value as Record<string, unknown>)[keyArg]),
|
|
280
|
+
]),
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
return value;
|
|
284
|
+
};
|
|
285
|
+
return JSON.stringify(visit(valueArg));
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const requireNormalized = (
|
|
289
|
+
actualArg: unknown,
|
|
290
|
+
normalizedArg: unknown,
|
|
291
|
+
labelArg: string,
|
|
292
|
+
): void => {
|
|
293
|
+
if (canonicalJson(actualArg) !== canonicalJson(normalizedArg)) {
|
|
294
|
+
throw new Error(`${labelArg} is not a strict normalized value.`);
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const assertPart = (
|
|
299
|
+
valueArg: unknown,
|
|
300
|
+
labelArg: string,
|
|
301
|
+
): asserts valueArg is ISubmissionPartRecord => {
|
|
302
|
+
const value = requireRecord(
|
|
303
|
+
valueArg,
|
|
304
|
+
[
|
|
305
|
+
'partId',
|
|
306
|
+
'kind',
|
|
307
|
+
'contentType',
|
|
308
|
+
'sha256',
|
|
309
|
+
'lengthBytes',
|
|
310
|
+
'objectKey',
|
|
311
|
+
'state',
|
|
312
|
+
],
|
|
313
|
+
['filename', 'contentId', 'grantId'],
|
|
314
|
+
labelArg,
|
|
315
|
+
);
|
|
316
|
+
requireIdentifier(value.partId, `${labelArg}.partId`);
|
|
317
|
+
if (
|
|
318
|
+
value.kind !== 'text'
|
|
319
|
+
&& value.kind !== 'html'
|
|
320
|
+
&& value.kind !== 'attachment'
|
|
321
|
+
) {
|
|
322
|
+
throw new Error(`${labelArg}.kind is invalid.`);
|
|
323
|
+
}
|
|
324
|
+
if (
|
|
325
|
+
typeof value.contentType !== 'string'
|
|
326
|
+
|| !contentTypeRegex.test(value.contentType)
|
|
327
|
+
) {
|
|
328
|
+
throw new Error(`${labelArg}.contentType is invalid.`);
|
|
329
|
+
}
|
|
330
|
+
requireSha256(value.sha256, `${labelArg}.sha256`);
|
|
331
|
+
requireInteger(value.lengthBytes, `${labelArg}.lengthBytes`);
|
|
332
|
+
requireObjectKey(value.objectKey, `${labelArg}.objectKey`);
|
|
333
|
+
if (
|
|
334
|
+
value.state !== 'missing'
|
|
335
|
+
&& value.state !== 'uploading'
|
|
336
|
+
&& value.state !== 'complete'
|
|
337
|
+
&& value.state !== 'failed'
|
|
338
|
+
) {
|
|
339
|
+
throw new Error(`${labelArg}.state is invalid.`);
|
|
340
|
+
}
|
|
341
|
+
if (value.filename !== undefined) {
|
|
342
|
+
requireString(value.filename, `${labelArg}.filename`, 512);
|
|
343
|
+
}
|
|
344
|
+
if (value.contentId !== undefined) {
|
|
345
|
+
requireString(value.contentId, `${labelArg}.contentId`, 256);
|
|
346
|
+
}
|
|
347
|
+
if (value.grantId !== undefined) {
|
|
348
|
+
requireUuid(value.grantId, `${labelArg}.grantId`);
|
|
349
|
+
}
|
|
350
|
+
if (
|
|
351
|
+
(value.state === 'uploading' || value.state === 'complete')
|
|
352
|
+
&& value.grantId === undefined
|
|
353
|
+
) {
|
|
354
|
+
throw new Error(`${labelArg} requires its exact upload grant.`);
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
export const assertCoreMailStateRecord = (
|
|
359
|
+
valueArg: unknown,
|
|
360
|
+
): asserts valueArg is ICoreMailStateRecord => {
|
|
361
|
+
const value = requireRecord(
|
|
362
|
+
valueArg,
|
|
363
|
+
[
|
|
364
|
+
'stateId',
|
|
365
|
+
'appliedConfigEpoch',
|
|
366
|
+
'appliedDesiredStateDigest',
|
|
367
|
+
'snapshotId',
|
|
368
|
+
'updatedAt',
|
|
369
|
+
],
|
|
370
|
+
[],
|
|
371
|
+
'CoreMailState',
|
|
372
|
+
);
|
|
373
|
+
if (value.stateId !== 'active') {
|
|
374
|
+
throw new Error('CoreMailState.stateId is invalid.');
|
|
375
|
+
}
|
|
376
|
+
const epoch = requireInteger(
|
|
377
|
+
value.appliedConfigEpoch,
|
|
378
|
+
'CoreMailState.appliedConfigEpoch',
|
|
379
|
+
);
|
|
380
|
+
requireSha256(
|
|
381
|
+
value.appliedDesiredStateDigest,
|
|
382
|
+
'CoreMailState.appliedDesiredStateDigest',
|
|
383
|
+
);
|
|
384
|
+
if (epoch === 0) {
|
|
385
|
+
if (value.snapshotId !== '-') {
|
|
386
|
+
throw new Error('CoreMailState empty pointer is invalid.');
|
|
387
|
+
}
|
|
388
|
+
} else {
|
|
389
|
+
requireUuid(value.snapshotId, 'CoreMailState.snapshotId');
|
|
390
|
+
}
|
|
391
|
+
requireInteger(value.updatedAt, 'CoreMailState.updatedAt');
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
export const assertCoreMailDesiredSnapshotRecord = (
|
|
395
|
+
valueArg: unknown,
|
|
396
|
+
): asserts valueArg is ICoreMailDesiredSnapshotRecord => {
|
|
397
|
+
const value = requireRecord(
|
|
398
|
+
valueArg,
|
|
399
|
+
[
|
|
400
|
+
'snapshotId',
|
|
401
|
+
'reconciliationId',
|
|
402
|
+
'expectedAppliedConfigEpoch',
|
|
403
|
+
'configEpoch',
|
|
404
|
+
'desiredStateDigest',
|
|
405
|
+
'lengthBytes',
|
|
406
|
+
'objectKey',
|
|
407
|
+
'state',
|
|
408
|
+
'createdAt',
|
|
409
|
+
'updatedAt',
|
|
410
|
+
],
|
|
411
|
+
['activatedAt', 'retiringAt', 'errorCode'],
|
|
412
|
+
'CoreMailDesiredSnapshot',
|
|
413
|
+
);
|
|
414
|
+
requireUuid(value.snapshotId, 'CoreMailDesiredSnapshot.snapshotId');
|
|
415
|
+
requireUuid(
|
|
416
|
+
value.reconciliationId,
|
|
417
|
+
'CoreMailDesiredSnapshot.reconciliationId',
|
|
418
|
+
);
|
|
419
|
+
const expectedEpoch = requireInteger(
|
|
420
|
+
value.expectedAppliedConfigEpoch,
|
|
421
|
+
'CoreMailDesiredSnapshot.expectedAppliedConfigEpoch',
|
|
422
|
+
);
|
|
423
|
+
const epoch = requireInteger(
|
|
424
|
+
value.configEpoch,
|
|
425
|
+
'CoreMailDesiredSnapshot.configEpoch',
|
|
426
|
+
1,
|
|
427
|
+
);
|
|
428
|
+
if (epoch <= expectedEpoch) {
|
|
429
|
+
throw new Error('CoreMailDesiredSnapshot epoch fence is invalid.');
|
|
430
|
+
}
|
|
431
|
+
requireSha256(
|
|
432
|
+
value.desiredStateDigest,
|
|
433
|
+
'CoreMailDesiredSnapshot.desiredStateDigest',
|
|
434
|
+
);
|
|
435
|
+
requireInteger(value.lengthBytes, 'CoreMailDesiredSnapshot.lengthBytes', 2);
|
|
436
|
+
requireObjectKey(value.objectKey, 'CoreMailDesiredSnapshot.objectKey');
|
|
437
|
+
if (
|
|
438
|
+
value.state !== 'staged'
|
|
439
|
+
&& value.state !== 'active'
|
|
440
|
+
&& value.state !== 'superseded'
|
|
441
|
+
&& value.state !== 'failed'
|
|
442
|
+
&& value.state !== 'retiring'
|
|
443
|
+
) {
|
|
444
|
+
throw new Error('CoreMailDesiredSnapshot.state is invalid.');
|
|
445
|
+
}
|
|
446
|
+
requireInteger(value.createdAt, 'CoreMailDesiredSnapshot.createdAt');
|
|
447
|
+
requireInteger(value.updatedAt, 'CoreMailDesiredSnapshot.updatedAt');
|
|
448
|
+
if (value.activatedAt !== undefined) {
|
|
449
|
+
requireInteger(value.activatedAt, 'CoreMailDesiredSnapshot.activatedAt');
|
|
450
|
+
}
|
|
451
|
+
if (value.retiringAt !== undefined) {
|
|
452
|
+
requireInteger(value.retiringAt, 'CoreMailDesiredSnapshot.retiringAt');
|
|
453
|
+
}
|
|
454
|
+
if ((value.state === 'retiring') !== (value.retiringAt !== undefined)) {
|
|
455
|
+
throw new Error('CoreMailDesiredSnapshot retirement tuple is invalid.');
|
|
456
|
+
}
|
|
457
|
+
if (
|
|
458
|
+
(value.state === 'active' || value.state === 'superseded')
|
|
459
|
+
&& value.activatedAt === undefined
|
|
460
|
+
) {
|
|
461
|
+
throw new Error('Activated CoreMailDesiredSnapshot has no activation time.');
|
|
462
|
+
}
|
|
463
|
+
if (value.errorCode !== undefined) {
|
|
464
|
+
requireIdentifier(value.errorCode, 'CoreMailDesiredSnapshot.errorCode');
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
export const assertCoreMailQuotaCounterRecord = (
|
|
469
|
+
valueArg: unknown,
|
|
470
|
+
): asserts valueArg is ICoreMailQuotaCounterRecord => {
|
|
471
|
+
const value = requireRecord(
|
|
472
|
+
valueArg,
|
|
473
|
+
[
|
|
474
|
+
'counterId',
|
|
475
|
+
'tenantId',
|
|
476
|
+
'serviceId',
|
|
477
|
+
'bindingId',
|
|
478
|
+
'bindingRevision',
|
|
479
|
+
'kind',
|
|
480
|
+
'count',
|
|
481
|
+
'createdAt',
|
|
482
|
+
'updatedAt',
|
|
483
|
+
],
|
|
484
|
+
['windowStartedAt'],
|
|
485
|
+
'CoreMailQuotaCounter',
|
|
486
|
+
);
|
|
487
|
+
requireSha256(value.counterId, 'CoreMailQuotaCounter.counterId');
|
|
488
|
+
requireIdentifier(value.tenantId, 'CoreMailQuotaCounter.tenantId');
|
|
489
|
+
requireIdentifier(value.serviceId, 'CoreMailQuotaCounter.serviceId');
|
|
490
|
+
requireIdentifier(value.bindingId, 'CoreMailQuotaCounter.bindingId');
|
|
491
|
+
requireInteger(
|
|
492
|
+
value.bindingRevision,
|
|
493
|
+
'CoreMailQuotaCounter.bindingRevision',
|
|
494
|
+
1,
|
|
495
|
+
);
|
|
496
|
+
if (
|
|
497
|
+
value.kind !== 'outbound-minute'
|
|
498
|
+
&& value.kind !== 'outbound-day'
|
|
499
|
+
&& value.kind !== 'pending-inbound'
|
|
500
|
+
) {
|
|
501
|
+
throw new Error('CoreMailQuotaCounter.kind is invalid.');
|
|
502
|
+
}
|
|
503
|
+
const windowStartedAt = value.windowStartedAt === undefined
|
|
504
|
+
? undefined
|
|
505
|
+
: requireInteger(
|
|
506
|
+
value.windowStartedAt,
|
|
507
|
+
'CoreMailQuotaCounter.windowStartedAt',
|
|
508
|
+
);
|
|
509
|
+
if ((value.kind === 'pending-inbound') === (windowStartedAt !== undefined)) {
|
|
510
|
+
throw new Error('CoreMailQuotaCounter window tuple is invalid.');
|
|
511
|
+
}
|
|
512
|
+
requireInteger(value.count, 'CoreMailQuotaCounter.count');
|
|
513
|
+
const createdAt = requireInteger(
|
|
514
|
+
value.createdAt,
|
|
515
|
+
'CoreMailQuotaCounter.createdAt',
|
|
516
|
+
);
|
|
517
|
+
const updatedAt = requireInteger(
|
|
518
|
+
value.updatedAt,
|
|
519
|
+
'CoreMailQuotaCounter.updatedAt',
|
|
520
|
+
);
|
|
521
|
+
if (updatedAt < createdAt) {
|
|
522
|
+
throw new Error('CoreMailQuotaCounter timestamps are invalid.');
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
export const assertCoreMailTransferGrantRecord = (
|
|
527
|
+
valueArg: unknown,
|
|
528
|
+
): asserts valueArg is ICoreMailTransferGrantRecord => {
|
|
529
|
+
const value = requireRecord(
|
|
530
|
+
valueArg,
|
|
531
|
+
[
|
|
532
|
+
'grantId',
|
|
533
|
+
'authorityKind',
|
|
534
|
+
'tenantId',
|
|
535
|
+
'serviceId',
|
|
536
|
+
'bindingId',
|
|
537
|
+
'purpose',
|
|
538
|
+
'method',
|
|
539
|
+
'path',
|
|
540
|
+
'bearerSha256',
|
|
541
|
+
'expectedSha256',
|
|
542
|
+
'lengthBytes',
|
|
543
|
+
'contentType',
|
|
544
|
+
'objectKey',
|
|
545
|
+
'state',
|
|
546
|
+
'expiresAt',
|
|
547
|
+
'createdAt',
|
|
548
|
+
],
|
|
549
|
+
['consumedAt'],
|
|
550
|
+
'CoreMailTransferGrant',
|
|
551
|
+
);
|
|
552
|
+
const grantId = requireUuid(value.grantId, 'CoreMailTransferGrant.grantId');
|
|
553
|
+
if (
|
|
554
|
+
value.authorityKind !== 'control'
|
|
555
|
+
&& value.authorityKind !== 'gateway'
|
|
556
|
+
&& value.authorityKind !== 'workload'
|
|
557
|
+
) {
|
|
558
|
+
throw new Error('CoreMailTransferGrant.authorityKind is invalid.');
|
|
559
|
+
}
|
|
560
|
+
if (value.tenantId !== '-') {
|
|
561
|
+
requireIdentifier(value.tenantId, 'CoreMailTransferGrant.tenantId');
|
|
562
|
+
}
|
|
563
|
+
requireIdentifier(value.serviceId, 'CoreMailTransferGrant.serviceId');
|
|
564
|
+
if (value.bindingId !== '-') {
|
|
565
|
+
requireIdentifier(value.bindingId, 'CoreMailTransferGrant.bindingId');
|
|
566
|
+
}
|
|
567
|
+
if (
|
|
568
|
+
value.purpose !== 'desired-state-upload'
|
|
569
|
+
&& value.purpose !== 'outbound-part-upload'
|
|
570
|
+
&& value.purpose !== 'outbound-mime-download'
|
|
571
|
+
&& value.purpose !== 'inbound-mime-upload'
|
|
572
|
+
&& value.purpose !== 'inbound-mime-download'
|
|
573
|
+
) {
|
|
574
|
+
throw new Error('CoreMailTransferGrant.purpose is invalid.');
|
|
575
|
+
}
|
|
576
|
+
if (value.method !== 'PUT' && value.method !== 'GET') {
|
|
577
|
+
throw new Error('CoreMailTransferGrant.method is invalid.');
|
|
578
|
+
}
|
|
579
|
+
if (value.path !== `/transfers/${grantId}`) {
|
|
580
|
+
throw new Error('CoreMailTransferGrant.path is invalid.');
|
|
581
|
+
}
|
|
582
|
+
requireSha256(value.bearerSha256, 'CoreMailTransferGrant.bearerSha256');
|
|
583
|
+
requireSha256(value.expectedSha256, 'CoreMailTransferGrant.expectedSha256');
|
|
584
|
+
requireInteger(value.lengthBytes, 'CoreMailTransferGrant.lengthBytes');
|
|
585
|
+
if (
|
|
586
|
+
typeof value.contentType !== 'string'
|
|
587
|
+
|| !contentTypeRegex.test(value.contentType)
|
|
588
|
+
) {
|
|
589
|
+
throw new Error('CoreMailTransferGrant.contentType is invalid.');
|
|
590
|
+
}
|
|
591
|
+
requireObjectKey(value.objectKey, 'CoreMailTransferGrant.objectKey');
|
|
592
|
+
if (
|
|
593
|
+
value.state !== 'issued'
|
|
594
|
+
&& value.state !== 'transferring'
|
|
595
|
+
&& value.state !== 'consumed'
|
|
596
|
+
&& value.state !== 'failed'
|
|
597
|
+
&& value.state !== 'expired'
|
|
598
|
+
) {
|
|
599
|
+
throw new Error('CoreMailTransferGrant.state is invalid.');
|
|
600
|
+
}
|
|
601
|
+
requireInteger(value.expiresAt, 'CoreMailTransferGrant.expiresAt');
|
|
602
|
+
requireInteger(value.createdAt, 'CoreMailTransferGrant.createdAt');
|
|
603
|
+
if (value.consumedAt !== undefined) {
|
|
604
|
+
requireInteger(value.consumedAt, 'CoreMailTransferGrant.consumedAt');
|
|
605
|
+
}
|
|
606
|
+
if (value.state === 'consumed' && value.consumedAt === undefined) {
|
|
607
|
+
throw new Error('Consumed CoreMailTransferGrant has no completion time.');
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
export const assertCoreMailSubmissionRecord = (
|
|
612
|
+
valueArg: unknown,
|
|
613
|
+
): asserts valueArg is ICoreMailSubmissionRecord => {
|
|
614
|
+
const value = requireRecord(
|
|
615
|
+
valueArg,
|
|
616
|
+
[
|
|
617
|
+
'submissionId',
|
|
618
|
+
'tenantId',
|
|
619
|
+
'serviceId',
|
|
620
|
+
'bindingId',
|
|
621
|
+
'bindingRevision',
|
|
622
|
+
'revision',
|
|
623
|
+
'idempotencyKey',
|
|
624
|
+
'submissionDigest',
|
|
625
|
+
'state',
|
|
626
|
+
'message',
|
|
627
|
+
'parts',
|
|
628
|
+
'attempts',
|
|
629
|
+
'createdAt',
|
|
630
|
+
'updatedAt',
|
|
631
|
+
],
|
|
632
|
+
[
|
|
633
|
+
'mimeObjectKey',
|
|
634
|
+
'mimeSha256',
|
|
635
|
+
'mimeLengthBytes',
|
|
636
|
+
'mimePublishedAt',
|
|
637
|
+
'transportMessageId',
|
|
638
|
+
'transportGrantId',
|
|
639
|
+
'gatewayStatusUpdatedAt',
|
|
640
|
+
'nextAttemptAt',
|
|
641
|
+
'workerRetryAt',
|
|
642
|
+
'retiringAt',
|
|
643
|
+
'leaseOwnerTaskId',
|
|
644
|
+
'leaseToken',
|
|
645
|
+
'leaseExpiresAt',
|
|
646
|
+
'acceptedAt',
|
|
647
|
+
'deliveredAt',
|
|
648
|
+
'terminalAt',
|
|
649
|
+
'error',
|
|
650
|
+
],
|
|
651
|
+
'CoreMailSubmission',
|
|
652
|
+
);
|
|
653
|
+
requireUuid(value.submissionId, 'CoreMailSubmission.submissionId');
|
|
654
|
+
requireIdentifier(value.tenantId, 'CoreMailSubmission.tenantId');
|
|
655
|
+
requireIdentifier(value.serviceId, 'CoreMailSubmission.serviceId');
|
|
656
|
+
requireIdentifier(value.bindingId, 'CoreMailSubmission.bindingId');
|
|
657
|
+
requireInteger(
|
|
658
|
+
value.bindingRevision,
|
|
659
|
+
'CoreMailSubmission.bindingRevision',
|
|
660
|
+
1,
|
|
661
|
+
);
|
|
662
|
+
requireInteger(value.revision, 'CoreMailSubmission.revision');
|
|
663
|
+
requireIdentifier(value.idempotencyKey, 'CoreMailSubmission.idempotencyKey');
|
|
664
|
+
requireSha256(value.submissionDigest, 'CoreMailSubmission.submissionDigest');
|
|
665
|
+
const submissionStates = new Set([
|
|
666
|
+
'preparing',
|
|
667
|
+
'uploading',
|
|
668
|
+
'ready',
|
|
669
|
+
'accepted',
|
|
670
|
+
'queued',
|
|
671
|
+
'delivering',
|
|
672
|
+
'delivered',
|
|
673
|
+
'deferred',
|
|
674
|
+
'failed',
|
|
675
|
+
'deadLettered',
|
|
676
|
+
]);
|
|
677
|
+
if (!submissionStates.has(String(value.state))) {
|
|
678
|
+
throw new Error('CoreMailSubmission.state is invalid.');
|
|
679
|
+
}
|
|
680
|
+
const messageRecord = requireRecord(
|
|
681
|
+
value.message,
|
|
682
|
+
['sender', 'recipients', 'subject', 'parts'],
|
|
683
|
+
['replyTo', 'headers'],
|
|
684
|
+
'CoreMailSubmission.message',
|
|
685
|
+
);
|
|
686
|
+
const sender = requireRecord(
|
|
687
|
+
messageRecord.sender,
|
|
688
|
+
['address'],
|
|
689
|
+
['displayName'],
|
|
690
|
+
'CoreMailSubmission.message.sender',
|
|
691
|
+
);
|
|
692
|
+
const normalizedMessage = normalizeCoreMailOutboundMessage(value.message, {
|
|
693
|
+
schemaVersion: 2,
|
|
694
|
+
bindingId: String(value.bindingId),
|
|
695
|
+
serviceId: String(value.serviceId),
|
|
696
|
+
tenantId: String(value.tenantId),
|
|
697
|
+
revision: Number(value.bindingRevision),
|
|
698
|
+
state: 'active',
|
|
699
|
+
capabilities: ['outbound'],
|
|
700
|
+
credentials: [],
|
|
701
|
+
allowedSenders: [String(sender.address)],
|
|
702
|
+
inboundRecipients: [],
|
|
703
|
+
limits: {
|
|
704
|
+
messagesPerMinute: 1,
|
|
705
|
+
messagesPerDay: 1,
|
|
706
|
+
maxPendingInbound: 1,
|
|
707
|
+
},
|
|
708
|
+
});
|
|
709
|
+
requireNormalized(
|
|
710
|
+
value.message,
|
|
711
|
+
normalizedMessage,
|
|
712
|
+
'CoreMailSubmission.message',
|
|
713
|
+
);
|
|
714
|
+
if (!Array.isArray(value.parts) || value.parts.length === 0) {
|
|
715
|
+
throw new Error('CoreMailSubmission.parts is invalid.');
|
|
716
|
+
}
|
|
717
|
+
value.parts.forEach((partArg, indexArg) =>
|
|
718
|
+
assertPart(partArg, `CoreMailSubmission.parts[${indexArg}]`)
|
|
719
|
+
);
|
|
720
|
+
const parts = value.parts as ISubmissionPartRecord[];
|
|
721
|
+
if (
|
|
722
|
+
new Set(parts.map((partArg) => partArg.partId)).size !== parts.length
|
|
723
|
+
|| canonicalJson(parts.map((partArg) => ({
|
|
724
|
+
partId: partArg.partId,
|
|
725
|
+
kind: partArg.kind,
|
|
726
|
+
contentType: partArg.contentType,
|
|
727
|
+
...(partArg.filename === undefined ? {} : { filename: partArg.filename }),
|
|
728
|
+
...(partArg.contentId === undefined
|
|
729
|
+
? {}
|
|
730
|
+
: { contentId: partArg.contentId }),
|
|
731
|
+
sha256: partArg.sha256,
|
|
732
|
+
lengthBytes: partArg.lengthBytes,
|
|
733
|
+
}))) !== canonicalJson(normalizedMessage.parts)
|
|
734
|
+
) {
|
|
735
|
+
throw new Error('CoreMailSubmission parts do not match message intent.');
|
|
736
|
+
}
|
|
737
|
+
requireInteger(value.attempts, 'CoreMailSubmission.attempts');
|
|
738
|
+
requireInteger(value.createdAt, 'CoreMailSubmission.createdAt');
|
|
739
|
+
requireInteger(value.updatedAt, 'CoreMailSubmission.updatedAt');
|
|
740
|
+
if (value.mimeObjectKey !== undefined) {
|
|
741
|
+
requireObjectKey(value.mimeObjectKey, 'CoreMailSubmission.mimeObjectKey');
|
|
742
|
+
}
|
|
743
|
+
if (value.mimeSha256 !== undefined) {
|
|
744
|
+
requireSha256(value.mimeSha256, 'CoreMailSubmission.mimeSha256');
|
|
745
|
+
}
|
|
746
|
+
if (value.mimeLengthBytes !== undefined) {
|
|
747
|
+
requireInteger(
|
|
748
|
+
value.mimeLengthBytes,
|
|
749
|
+
'CoreMailSubmission.mimeLengthBytes',
|
|
750
|
+
1,
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
const mimeFieldCount = [
|
|
754
|
+
value.mimeObjectKey,
|
|
755
|
+
value.mimeSha256,
|
|
756
|
+
value.mimeLengthBytes,
|
|
757
|
+
].filter((entryArg) => entryArg !== undefined).length;
|
|
758
|
+
if (mimeFieldCount !== 0 && mimeFieldCount !== 3) {
|
|
759
|
+
throw new Error('CoreMailSubmission immutable MIME tuple is incomplete.');
|
|
760
|
+
}
|
|
761
|
+
if (value.transportMessageId !== undefined) {
|
|
762
|
+
requireIdentifier(
|
|
763
|
+
value.transportMessageId,
|
|
764
|
+
'CoreMailSubmission.transportMessageId',
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
if (value.transportGrantId !== undefined) {
|
|
768
|
+
requireUuid(value.transportGrantId, 'CoreMailSubmission.transportGrantId');
|
|
769
|
+
}
|
|
770
|
+
for (const [key, entry] of [
|
|
771
|
+
['gatewayStatusUpdatedAt', value.gatewayStatusUpdatedAt],
|
|
772
|
+
['mimePublishedAt', value.mimePublishedAt],
|
|
773
|
+
['nextAttemptAt', value.nextAttemptAt],
|
|
774
|
+
['workerRetryAt', value.workerRetryAt],
|
|
775
|
+
['retiringAt', value.retiringAt],
|
|
776
|
+
['leaseExpiresAt', value.leaseExpiresAt],
|
|
777
|
+
['acceptedAt', value.acceptedAt],
|
|
778
|
+
['deliveredAt', value.deliveredAt],
|
|
779
|
+
['terminalAt', value.terminalAt],
|
|
780
|
+
] as const) {
|
|
781
|
+
if (entry !== undefined) requireInteger(entry, `CoreMailSubmission.${key}`);
|
|
782
|
+
}
|
|
783
|
+
if (value.leaseOwnerTaskId !== undefined) {
|
|
784
|
+
requireIdentifier(
|
|
785
|
+
value.leaseOwnerTaskId,
|
|
786
|
+
'CoreMailSubmission.leaseOwnerTaskId',
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
if (value.leaseToken !== undefined) {
|
|
790
|
+
requireUuid(value.leaseToken, 'CoreMailSubmission.leaseToken');
|
|
791
|
+
}
|
|
792
|
+
const leaseFieldCount = [
|
|
793
|
+
value.leaseOwnerTaskId,
|
|
794
|
+
value.leaseToken,
|
|
795
|
+
value.leaseExpiresAt,
|
|
796
|
+
].filter((entryArg) => entryArg !== undefined).length;
|
|
797
|
+
if (leaseFieldCount !== 0 && leaseFieldCount !== 3) {
|
|
798
|
+
throw new Error('CoreMailSubmission lease tuple is incomplete.');
|
|
799
|
+
}
|
|
800
|
+
const hasTransportIdentity = value.transportMessageId !== undefined;
|
|
801
|
+
const hasAcceptedAt = value.acceptedAt !== undefined;
|
|
802
|
+
if (hasTransportIdentity !== hasAcceptedAt) {
|
|
803
|
+
throw new Error('CoreMailSubmission transport acceptance tuple is incomplete.');
|
|
804
|
+
}
|
|
805
|
+
if (
|
|
806
|
+
['accepted', 'queued', 'delivering', 'delivered', 'deferred', 'deadLettered']
|
|
807
|
+
.includes(String(value.state))
|
|
808
|
+
&& (!hasTransportIdentity || mimeFieldCount !== 3)
|
|
809
|
+
) {
|
|
810
|
+
throw new Error('CoreMailSubmission accepted state has no transport or MIME.');
|
|
811
|
+
}
|
|
812
|
+
if (
|
|
813
|
+
['preparing', 'uploading', 'ready'].includes(String(value.state))
|
|
814
|
+
&& hasTransportIdentity
|
|
815
|
+
) {
|
|
816
|
+
throw new Error('CoreMailSubmission pre-acceptance state has transport identity.');
|
|
817
|
+
}
|
|
818
|
+
if (hasTransportIdentity && mimeFieldCount !== 3) {
|
|
819
|
+
throw new Error('CoreMailSubmission transport identity has no immutable MIME.');
|
|
820
|
+
}
|
|
821
|
+
if (value.mimePublishedAt !== undefined && mimeFieldCount !== 3) {
|
|
822
|
+
throw new Error('CoreMailSubmission published MIME has no ownership tuple.');
|
|
823
|
+
}
|
|
824
|
+
if (hasTransportIdentity && value.mimePublishedAt === undefined) {
|
|
825
|
+
throw new Error('CoreMailSubmission accepted transport has unpublished MIME.');
|
|
826
|
+
}
|
|
827
|
+
if (value.state === 'delivered' && value.deliveredAt === undefined) {
|
|
828
|
+
throw new Error('Delivered CoreMailSubmission has no delivery time.');
|
|
829
|
+
}
|
|
830
|
+
let normalizedError: plugins.serveZoneInterfaces.data.ICoreMailErrorData | undefined;
|
|
831
|
+
if (value.error !== undefined) {
|
|
832
|
+
normalizedError = plugins.serveZoneInterfaces.data.normalizeCoreMailErrorData(
|
|
833
|
+
value.error,
|
|
834
|
+
);
|
|
835
|
+
requireNormalized(
|
|
836
|
+
value.error,
|
|
837
|
+
normalizedError,
|
|
838
|
+
'CoreMailSubmission.error',
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
if (
|
|
842
|
+
value.state === 'deferred'
|
|
843
|
+
&& (
|
|
844
|
+
!normalizedError?.retryable
|
|
845
|
+
|| value.nextAttemptAt === undefined
|
|
846
|
+
|| value.terminalAt !== undefined
|
|
847
|
+
|| value.deliveredAt !== undefined
|
|
848
|
+
)
|
|
849
|
+
) {
|
|
850
|
+
throw new Error('Deferred CoreMailSubmission outcome is incomplete.');
|
|
851
|
+
}
|
|
852
|
+
if (
|
|
853
|
+
(value.state === 'failed' || value.state === 'deadLettered')
|
|
854
|
+
&& (
|
|
855
|
+
!normalizedError
|
|
856
|
+
|| normalizedError.retryable
|
|
857
|
+
|| value.terminalAt === undefined
|
|
858
|
+
|| value.deliveredAt !== undefined
|
|
859
|
+
)
|
|
860
|
+
) {
|
|
861
|
+
throw new Error('Failed CoreMailSubmission outcome is incomplete.');
|
|
862
|
+
}
|
|
863
|
+
if (
|
|
864
|
+
value.state === 'delivered'
|
|
865
|
+
&& (
|
|
866
|
+
value.terminalAt !== value.deliveredAt
|
|
867
|
+
|| value.error !== undefined
|
|
868
|
+
)
|
|
869
|
+
) {
|
|
870
|
+
throw new Error('Delivered CoreMailSubmission outcome is incomplete.');
|
|
871
|
+
}
|
|
872
|
+
if (
|
|
873
|
+
value.state !== 'deferred'
|
|
874
|
+
&& value.state !== 'failed'
|
|
875
|
+
&& value.state !== 'deadLettered'
|
|
876
|
+
&& value.state !== 'delivered'
|
|
877
|
+
&& (
|
|
878
|
+
value.nextAttemptAt !== undefined
|
|
879
|
+
|| value.error !== undefined
|
|
880
|
+
|| value.terminalAt !== undefined
|
|
881
|
+
|| value.deliveredAt !== undefined
|
|
882
|
+
)
|
|
883
|
+
) {
|
|
884
|
+
throw new Error('CoreMailSubmission non-outcome state has outcome fields.');
|
|
885
|
+
}
|
|
886
|
+
if (
|
|
887
|
+
value.workerRetryAt !== undefined
|
|
888
|
+
&& !['ready', 'accepted', 'queued', 'delivering'].includes(String(value.state))
|
|
889
|
+
) {
|
|
890
|
+
throw new Error('CoreMailSubmission worker retry state is invalid.');
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
export const assertCoreMailInboundDeliveryRecord = (
|
|
895
|
+
valueArg: unknown,
|
|
896
|
+
): asserts valueArg is ICoreMailInboundDeliveryRecord => {
|
|
897
|
+
const value = requireRecord(
|
|
898
|
+
valueArg,
|
|
899
|
+
[
|
|
900
|
+
'deliveryId',
|
|
901
|
+
'tenantId',
|
|
902
|
+
'serviceId',
|
|
903
|
+
'bindingId',
|
|
904
|
+
'bindingRevision',
|
|
905
|
+
'revision',
|
|
906
|
+
'transportDeliveryId',
|
|
907
|
+
'state',
|
|
908
|
+
'envelope',
|
|
909
|
+
'rawMimeSha256',
|
|
910
|
+
'rawMimeLengthBytes',
|
|
911
|
+
'rawMimeObjectKey',
|
|
912
|
+
'receivedAt',
|
|
913
|
+
'updatedAt',
|
|
914
|
+
],
|
|
915
|
+
['messageId', 'subject', 'fetchGrantId', 'acknowledgedOutcome'],
|
|
916
|
+
'CoreMailInboundDelivery',
|
|
917
|
+
);
|
|
918
|
+
requireUuid(value.deliveryId, 'CoreMailInboundDelivery.deliveryId');
|
|
919
|
+
requireIdentifier(value.tenantId, 'CoreMailInboundDelivery.tenantId');
|
|
920
|
+
requireIdentifier(value.serviceId, 'CoreMailInboundDelivery.serviceId');
|
|
921
|
+
requireIdentifier(value.bindingId, 'CoreMailInboundDelivery.bindingId');
|
|
922
|
+
requireInteger(
|
|
923
|
+
value.bindingRevision,
|
|
924
|
+
'CoreMailInboundDelivery.bindingRevision',
|
|
925
|
+
1,
|
|
926
|
+
);
|
|
927
|
+
requireInteger(value.revision, 'CoreMailInboundDelivery.revision');
|
|
928
|
+
requireIdentifier(
|
|
929
|
+
value.transportDeliveryId,
|
|
930
|
+
'CoreMailInboundDelivery.transportDeliveryId',
|
|
931
|
+
);
|
|
932
|
+
if (
|
|
933
|
+
value.state !== 'pending'
|
|
934
|
+
&& value.state !== 'fetching'
|
|
935
|
+
&& value.state !== 'fetched'
|
|
936
|
+
&& value.state !== 'acknowledged'
|
|
937
|
+
) {
|
|
938
|
+
throw new Error('CoreMailInboundDelivery.state is invalid.');
|
|
939
|
+
}
|
|
940
|
+
requireNormalized(
|
|
941
|
+
value.envelope,
|
|
942
|
+
normalizeCoreMailEnvelope(value.envelope),
|
|
943
|
+
'CoreMailInboundDelivery.envelope',
|
|
944
|
+
);
|
|
945
|
+
requireSha256(
|
|
946
|
+
value.rawMimeSha256,
|
|
947
|
+
'CoreMailInboundDelivery.rawMimeSha256',
|
|
948
|
+
);
|
|
949
|
+
requireInteger(
|
|
950
|
+
value.rawMimeLengthBytes,
|
|
951
|
+
'CoreMailInboundDelivery.rawMimeLengthBytes',
|
|
952
|
+
1,
|
|
953
|
+
);
|
|
954
|
+
requireObjectKey(
|
|
955
|
+
value.rawMimeObjectKey,
|
|
956
|
+
'CoreMailInboundDelivery.rawMimeObjectKey',
|
|
957
|
+
);
|
|
958
|
+
requireInteger(value.receivedAt, 'CoreMailInboundDelivery.receivedAt');
|
|
959
|
+
requireInteger(value.updatedAt, 'CoreMailInboundDelivery.updatedAt');
|
|
960
|
+
if (value.messageId !== undefined) {
|
|
961
|
+
requireString(value.messageId, 'CoreMailInboundDelivery.messageId', 998);
|
|
962
|
+
}
|
|
963
|
+
if (value.subject !== undefined) {
|
|
964
|
+
requireString(value.subject, 'CoreMailInboundDelivery.subject', 768);
|
|
965
|
+
}
|
|
966
|
+
if (value.fetchGrantId !== undefined) {
|
|
967
|
+
requireUuid(value.fetchGrantId, 'CoreMailInboundDelivery.fetchGrantId');
|
|
968
|
+
}
|
|
969
|
+
if (
|
|
970
|
+
(value.state === 'fetching'
|
|
971
|
+
|| value.state === 'fetched'
|
|
972
|
+
|| value.state === 'acknowledged')
|
|
973
|
+
&& value.fetchGrantId === undefined
|
|
974
|
+
) {
|
|
975
|
+
throw new Error('CoreMailInboundDelivery fetch state has no grant.');
|
|
976
|
+
}
|
|
977
|
+
if (
|
|
978
|
+
value.acknowledgedOutcome !== undefined
|
|
979
|
+
&& value.acknowledgedOutcome !== 'processed'
|
|
980
|
+
&& value.acknowledgedOutcome !== 'discarded'
|
|
981
|
+
) {
|
|
982
|
+
throw new Error('CoreMailInboundDelivery acknowledgement is invalid.');
|
|
983
|
+
}
|
|
984
|
+
if (
|
|
985
|
+
(value.state === 'acknowledged')
|
|
986
|
+
!== (value.acknowledgedOutcome !== undefined)
|
|
987
|
+
) {
|
|
988
|
+
throw new Error('CoreMailInboundDelivery acknowledgement tuple is invalid.');
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
|
|
992
|
+
export const assertCoreMailRecipientHandleRecord = (
|
|
993
|
+
valueArg: unknown,
|
|
994
|
+
): asserts valueArg is ICoreMailRecipientHandleRecord => {
|
|
995
|
+
const value = requireRecord(
|
|
996
|
+
valueArg,
|
|
997
|
+
[
|
|
998
|
+
'routingHandle',
|
|
999
|
+
'tenantId',
|
|
1000
|
+
'serviceId',
|
|
1001
|
+
'bindingId',
|
|
1002
|
+
'bindingRevision',
|
|
1003
|
+
'maxPendingInbound',
|
|
1004
|
+
'recipient',
|
|
1005
|
+
'envelopeDigest',
|
|
1006
|
+
'state',
|
|
1007
|
+
'expiresAt',
|
|
1008
|
+
'createdAt',
|
|
1009
|
+
],
|
|
1010
|
+
['handoffId'],
|
|
1011
|
+
'CoreMailRecipientHandle',
|
|
1012
|
+
);
|
|
1013
|
+
requireSha256(value.routingHandle, 'CoreMailRecipientHandle.routingHandle');
|
|
1014
|
+
requireIdentifier(value.tenantId, 'CoreMailRecipientHandle.tenantId');
|
|
1015
|
+
requireIdentifier(value.serviceId, 'CoreMailRecipientHandle.serviceId');
|
|
1016
|
+
requireIdentifier(value.bindingId, 'CoreMailRecipientHandle.bindingId');
|
|
1017
|
+
requireInteger(
|
|
1018
|
+
value.bindingRevision,
|
|
1019
|
+
'CoreMailRecipientHandle.bindingRevision',
|
|
1020
|
+
1,
|
|
1021
|
+
);
|
|
1022
|
+
requireInteger(
|
|
1023
|
+
value.maxPendingInbound,
|
|
1024
|
+
'CoreMailRecipientHandle.maxPendingInbound',
|
|
1025
|
+
1,
|
|
1026
|
+
);
|
|
1027
|
+
normalizeCoreMailEnvelope({
|
|
1028
|
+
mailFrom: '',
|
|
1029
|
+
rcptTo: [value.recipient],
|
|
1030
|
+
});
|
|
1031
|
+
requireSha256(
|
|
1032
|
+
value.envelopeDigest,
|
|
1033
|
+
'CoreMailRecipientHandle.envelopeDigest',
|
|
1034
|
+
);
|
|
1035
|
+
if (
|
|
1036
|
+
value.state !== 'issued'
|
|
1037
|
+
&& value.state !== 'consumed'
|
|
1038
|
+
&& value.state !== 'expired'
|
|
1039
|
+
) {
|
|
1040
|
+
throw new Error('CoreMailRecipientHandle.state is invalid.');
|
|
1041
|
+
}
|
|
1042
|
+
requireInteger(value.expiresAt, 'CoreMailRecipientHandle.expiresAt');
|
|
1043
|
+
requireInteger(value.createdAt, 'CoreMailRecipientHandle.createdAt');
|
|
1044
|
+
if (value.handoffId !== undefined) {
|
|
1045
|
+
requireUuid(value.handoffId, 'CoreMailRecipientHandle.handoffId');
|
|
1046
|
+
}
|
|
1047
|
+
if (
|
|
1048
|
+
(value.state === 'consumed')
|
|
1049
|
+
!== (value.handoffId !== undefined)
|
|
1050
|
+
) {
|
|
1051
|
+
throw new Error('CoreMailRecipientHandle handoff tuple is invalid.');
|
|
1052
|
+
}
|
|
1053
|
+
};
|
|
1054
|
+
|
|
1055
|
+
export const assertCoreMailInboundHandoffRecord = (
|
|
1056
|
+
valueArg: unknown,
|
|
1057
|
+
): asserts valueArg is ICoreMailInboundHandoffRecord => {
|
|
1058
|
+
const value = requireRecord(
|
|
1059
|
+
valueArg,
|
|
1060
|
+
[
|
|
1061
|
+
'handoffId',
|
|
1062
|
+
'revision',
|
|
1063
|
+
'transportDeliveryId',
|
|
1064
|
+
'routingHandles',
|
|
1065
|
+
'message',
|
|
1066
|
+
'source',
|
|
1067
|
+
'state',
|
|
1068
|
+
'objectKey',
|
|
1069
|
+
'deliveryIds',
|
|
1070
|
+
'createdAt',
|
|
1071
|
+
'updatedAt',
|
|
1072
|
+
],
|
|
1073
|
+
['grantId', 'completedAt', 'retiringAt'],
|
|
1074
|
+
'CoreMailInboundHandoff',
|
|
1075
|
+
);
|
|
1076
|
+
requireUuid(value.handoffId, 'CoreMailInboundHandoff.handoffId');
|
|
1077
|
+
requireInteger(value.revision, 'CoreMailInboundHandoff.revision');
|
|
1078
|
+
requireIdentifier(
|
|
1079
|
+
value.transportDeliveryId,
|
|
1080
|
+
'CoreMailInboundHandoff.transportDeliveryId',
|
|
1081
|
+
);
|
|
1082
|
+
if (
|
|
1083
|
+
!Array.isArray(value.routingHandles)
|
|
1084
|
+
|| value.routingHandles.length === 0
|
|
1085
|
+
|| value.routingHandles.length > 100
|
|
1086
|
+
) {
|
|
1087
|
+
throw new Error('CoreMailInboundHandoff.routingHandles is invalid.');
|
|
1088
|
+
}
|
|
1089
|
+
value.routingHandles.forEach((handleArg, indexArg) =>
|
|
1090
|
+
requireSha256(
|
|
1091
|
+
handleArg,
|
|
1092
|
+
`CoreMailInboundHandoff.routingHandles[${indexArg}]`,
|
|
1093
|
+
)
|
|
1094
|
+
);
|
|
1095
|
+
if (new Set(value.routingHandles).size !== value.routingHandles.length) {
|
|
1096
|
+
throw new Error('CoreMailInboundHandoff.routingHandles contains duplicates.');
|
|
1097
|
+
}
|
|
1098
|
+
requireNormalized(
|
|
1099
|
+
value.message,
|
|
1100
|
+
normalizeCoreMailGatewayMessage(value.message),
|
|
1101
|
+
'CoreMailInboundHandoff.message',
|
|
1102
|
+
);
|
|
1103
|
+
requireNormalized(
|
|
1104
|
+
value.source,
|
|
1105
|
+
normalizeCoreMailConnectionInfo(value.source),
|
|
1106
|
+
'CoreMailInboundHandoff.source',
|
|
1107
|
+
);
|
|
1108
|
+
if (
|
|
1109
|
+
value.state !== 'prepared'
|
|
1110
|
+
&& value.state !== 'uploaded'
|
|
1111
|
+
&& value.state !== 'completed'
|
|
1112
|
+
&& value.state !== 'failed'
|
|
1113
|
+
&& value.state !== 'retiring'
|
|
1114
|
+
) {
|
|
1115
|
+
throw new Error('CoreMailInboundHandoff.state is invalid.');
|
|
1116
|
+
}
|
|
1117
|
+
requireObjectKey(value.objectKey, 'CoreMailInboundHandoff.objectKey');
|
|
1118
|
+
if (value.grantId !== undefined) {
|
|
1119
|
+
requireUuid(value.grantId, 'CoreMailInboundHandoff.grantId');
|
|
1120
|
+
}
|
|
1121
|
+
if (
|
|
1122
|
+
(value.state === 'uploaded' || value.state === 'completed')
|
|
1123
|
+
&& value.grantId === undefined
|
|
1124
|
+
) {
|
|
1125
|
+
throw new Error('CoreMailInboundHandoff upload state has no grant.');
|
|
1126
|
+
}
|
|
1127
|
+
if (!Array.isArray(value.deliveryIds)) {
|
|
1128
|
+
throw new Error('CoreMailInboundHandoff.deliveryIds is invalid.');
|
|
1129
|
+
}
|
|
1130
|
+
value.deliveryIds.forEach((deliveryIdArg, indexArg) =>
|
|
1131
|
+
requireUuid(
|
|
1132
|
+
deliveryIdArg,
|
|
1133
|
+
`CoreMailInboundHandoff.deliveryIds[${indexArg}]`,
|
|
1134
|
+
)
|
|
1135
|
+
);
|
|
1136
|
+
if (new Set(value.deliveryIds).size !== value.deliveryIds.length) {
|
|
1137
|
+
throw new Error('CoreMailInboundHandoff.deliveryIds contains duplicates.');
|
|
1138
|
+
}
|
|
1139
|
+
requireInteger(value.createdAt, 'CoreMailInboundHandoff.createdAt');
|
|
1140
|
+
requireInteger(value.updatedAt, 'CoreMailInboundHandoff.updatedAt');
|
|
1141
|
+
if (value.completedAt !== undefined) {
|
|
1142
|
+
requireInteger(value.completedAt, 'CoreMailInboundHandoff.completedAt');
|
|
1143
|
+
}
|
|
1144
|
+
if (value.retiringAt !== undefined) {
|
|
1145
|
+
requireInteger(value.retiringAt, 'CoreMailInboundHandoff.retiringAt');
|
|
1146
|
+
}
|
|
1147
|
+
if ((value.state === 'retiring') !== (value.retiringAt !== undefined)) {
|
|
1148
|
+
throw new Error('CoreMailInboundHandoff retirement tuple is invalid.');
|
|
1149
|
+
}
|
|
1150
|
+
if (
|
|
1151
|
+
(value.state === 'completed')
|
|
1152
|
+
!== (value.completedAt !== undefined && value.deliveryIds.length > 0)
|
|
1153
|
+
) {
|
|
1154
|
+
throw new Error('CoreMailInboundHandoff completion tuple is invalid.');
|
|
1155
|
+
}
|
|
1156
|
+
};
|