@remit/data-ports 0.0.25 → 0.0.27
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/package.json +1 -1
- package/src/account-settings.ts +1 -0
- package/src/index.ts +8 -0
- package/src/interfaces/outbox-attachment.ts +133 -0
- package/src/types.ts +2 -0
package/package.json
CHANGED
package/src/account-settings.ts
CHANGED
|
@@ -66,6 +66,7 @@ export const accountSettingRegistry = {
|
|
|
66
66
|
[AccountSettingName.AccountSignatureHtml]: StringSettingSchema,
|
|
67
67
|
[AccountSettingName.AccountDisplayName]: StringSettingSchema,
|
|
68
68
|
[AccountSettingName.AccountMuted]: MutedFlagSettingSchema,
|
|
69
|
+
[AccountSettingName.AccountComposeLanguages]: StringListSettingSchema,
|
|
69
70
|
[AccountSettingName.MailboxDisplayName]: StringSettingSchema,
|
|
70
71
|
[AccountSettingName.MailboxMuted]: MutedFlagSettingSchema,
|
|
71
72
|
[AccountSettingName.FolderRoleAppointment]: StringSettingSchema,
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,13 @@ export type { IMessageFlagPushRepository } from "./interfaces/message-flag-push.
|
|
|
17
17
|
export type { IMessageLabelRepository } from "./interfaces/message-label.js";
|
|
18
18
|
export type { IMessagePlacementMoveRepository } from "./interfaces/message-placement-move.js";
|
|
19
19
|
export type { IOrganizeJobRequestRepository } from "./interfaces/organize-job-request.js";
|
|
20
|
+
export type {
|
|
21
|
+
CreateOutboxAttachmentInput,
|
|
22
|
+
IOutboxAttachmentRepository,
|
|
23
|
+
OutboxAttachmentCap,
|
|
24
|
+
ReserveOutboxAttachmentResult,
|
|
25
|
+
} from "./interfaces/outbox-attachment.js";
|
|
26
|
+
export { holdsRoom } from "./interfaces/outbox-attachment.js";
|
|
20
27
|
export type { IOutboxMessageRepository } from "./interfaces/outbox-message.js";
|
|
21
28
|
export type { IQuarantineRepository } from "./interfaces/quarantine.js";
|
|
22
29
|
export type { IThreadMessageRepository } from "./interfaces/thread-message.js";
|
|
@@ -79,6 +86,7 @@ export type {
|
|
|
79
86
|
MessagePlacementMoveItem,
|
|
80
87
|
MessageReferenceItem,
|
|
81
88
|
OrganizeJobRequestItem,
|
|
89
|
+
OutboxAttachmentItem,
|
|
82
90
|
OutboxMessageItem,
|
|
83
91
|
PutMessageFlagPushInput,
|
|
84
92
|
PutMessagePlacementMoveInput,
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { OutboxAttachmentItem } from "../types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether an attachment is spoken for: uploaded and confirmed, or reserved and
|
|
5
|
+
* not yet lapsed. The cap counts these and nothing else. One definition, because
|
|
6
|
+
* the repository and the service both have to agree on it exactly.
|
|
7
|
+
*/
|
|
8
|
+
export const holdsRoom = (
|
|
9
|
+
item: OutboxAttachmentItem,
|
|
10
|
+
nowSeconds: number,
|
|
11
|
+
): boolean =>
|
|
12
|
+
item.state === "Stored" || item.reservationExpiresAt >= nowSeconds;
|
|
13
|
+
|
|
14
|
+
export interface CreateOutboxAttachmentInput {
|
|
15
|
+
outboxAttachmentId: string;
|
|
16
|
+
outboxMessageId: string;
|
|
17
|
+
accountId: string;
|
|
18
|
+
accountConfigId: string;
|
|
19
|
+
filename: string;
|
|
20
|
+
contentType: string;
|
|
21
|
+
sizeBytes: number;
|
|
22
|
+
storageKey: string;
|
|
23
|
+
reservationExpiresAt: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The numbers the reservation must fit inside. They come from the caller — the
|
|
28
|
+
* cap is a business rule and belongs with the rest of them — but they are
|
|
29
|
+
* applied here, where the count and the insert can happen together.
|
|
30
|
+
*/
|
|
31
|
+
export interface OutboxAttachmentCap {
|
|
32
|
+
maxTotalBytes: number;
|
|
33
|
+
maxCount: number;
|
|
34
|
+
/** Unix seconds. A Pending row past its expiry stops holding room. */
|
|
35
|
+
nowSeconds: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type ReserveOutboxAttachmentResult =
|
|
39
|
+
| { outcome: "Reserved"; item: OutboxAttachmentItem }
|
|
40
|
+
| { outcome: "OverByteCap"; usedBytes: number }
|
|
41
|
+
| { outcome: "OverCountCap"; usedBytes: number };
|
|
42
|
+
|
|
43
|
+
export interface IOutboxAttachmentRepository {
|
|
44
|
+
/**
|
|
45
|
+
* Claim room on a draft, or report why there is none.
|
|
46
|
+
*
|
|
47
|
+
* **The invariant an implementation owes its callers:** of two reservations
|
|
48
|
+
* for the same draft running concurrently, at most one may observe a total
|
|
49
|
+
* that does not include the other. Either one of them sees the other's row,
|
|
50
|
+
* or one of them fails. Both being told there was room is a breach, and the
|
|
51
|
+
* consequence is a message that cannot be sent — phase 4 builds a MIME body
|
|
52
|
+
* from these rows and the receiving server rejects it.
|
|
53
|
+
*
|
|
54
|
+
* A count-then-insert only satisfies that under serializable isolation or an
|
|
55
|
+
* external lock. **The drizzle/SQLite implementation gets it from neither the
|
|
56
|
+
* schema nor the isolation level:** `runInTransaction` issues a SAVEPOINT,
|
|
57
|
+
* which begins a DEFERRED transaction, and what actually orders concurrent
|
|
58
|
+
* reservations is the module-level write queue in `tx.ts` — correct on a
|
|
59
|
+
* single-process deployment, which is what that backend is. Two writer
|
|
60
|
+
* processes against one SQLite file would fail loud
|
|
61
|
+
* (`SQLITE_BUSY_SNAPSHOT`), not silently over-admit.
|
|
62
|
+
*
|
|
63
|
+
* **An adapter without serializable isolation must not do a
|
|
64
|
+
* read-then-write.** On DynamoDB in particular, a Query followed by a
|
|
65
|
+
* PutItem breaches this silently and is the failure mode that matters: it is
|
|
66
|
+
* the deployment that actually scales horizontally. Use a conditional write
|
|
67
|
+
* on a per-draft aggregate instead — an `UpdateItem` that adds the size and
|
|
68
|
+
* carries a `ConditionExpression` bounding the running total, decided on
|
|
69
|
+
* whether the condition held — and derive `usedBytes` for the rejection from
|
|
70
|
+
* the same item.
|
|
71
|
+
*
|
|
72
|
+
* Either way the caller supplies the limits: the cap is a business rule and
|
|
73
|
+
* lives with the rest of them. What is delegated here is only the atomicity.
|
|
74
|
+
*/
|
|
75
|
+
reserve(
|
|
76
|
+
input: CreateOutboxAttachmentInput,
|
|
77
|
+
cap: OutboxAttachmentCap,
|
|
78
|
+
): Promise<ReserveOutboxAttachmentResult>;
|
|
79
|
+
|
|
80
|
+
/** One attachment, scoped to its tenant. Throws NotFound when it is gone. */
|
|
81
|
+
get(
|
|
82
|
+
accountConfigId: string,
|
|
83
|
+
outboxAttachmentId: string,
|
|
84
|
+
): Promise<OutboxAttachmentItem>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Everything a draft holds, lapsed reservations included. Callers that care
|
|
88
|
+
* about what is live filter on state and `reservationExpiresAt`.
|
|
89
|
+
*/
|
|
90
|
+
listByOutboxMessage(
|
|
91
|
+
accountConfigId: string,
|
|
92
|
+
outboxMessageId: string,
|
|
93
|
+
): Promise<OutboxAttachmentItem[]>;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Move a reservation to Stored at the size storage actually holds. Returns
|
|
97
|
+
* null when the row is gone or no longer Pending, so a caller can tell a
|
|
98
|
+
* lapsed reservation from a confirmed one without a second read.
|
|
99
|
+
*/
|
|
100
|
+
markStored(
|
|
101
|
+
accountConfigId: string,
|
|
102
|
+
outboxAttachmentId: string,
|
|
103
|
+
sizeBytes: number,
|
|
104
|
+
): Promise<OutboxAttachmentItem | null>;
|
|
105
|
+
|
|
106
|
+
/** Drop specific attachments from a draft. */
|
|
107
|
+
deleteMany(
|
|
108
|
+
accountConfigId: string,
|
|
109
|
+
outboxAttachmentIds: string[],
|
|
110
|
+
): Promise<void>;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Drop the draft's reservations that lapsed without ever being confirmed, and
|
|
114
|
+
* report which ids went.
|
|
115
|
+
*
|
|
116
|
+
* A `Pending` row past its expiry already stops holding room, but until it is
|
|
117
|
+
* gone it still names an object — which is enough for the sweep to treat that
|
|
118
|
+
* object as accounted for and leave it. So the row has to go, and the sweep
|
|
119
|
+
* collects the bytes on the same pass. `complete` refuses a lapsed row, so
|
|
120
|
+
* nothing is racing this.
|
|
121
|
+
*/
|
|
122
|
+
deleteLapsedReservations(
|
|
123
|
+
accountConfigId: string,
|
|
124
|
+
outboxMessageId: string,
|
|
125
|
+
nowSeconds: number,
|
|
126
|
+
): Promise<string[]>;
|
|
127
|
+
|
|
128
|
+
/** Drop everything a draft holds, as part of retiring the draft. */
|
|
129
|
+
deleteByOutboxMessage(
|
|
130
|
+
accountConfigId: string,
|
|
131
|
+
outboxMessageId: string,
|
|
132
|
+
): Promise<void>;
|
|
133
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
MessagePlacementMoveSchema,
|
|
22
22
|
MessageSchema,
|
|
23
23
|
OrganizeJobRequestSchema,
|
|
24
|
+
OutboxAttachmentSchema,
|
|
24
25
|
OutboxMessageSchema,
|
|
25
26
|
QuarantineSchema,
|
|
26
27
|
RawMessageStorageSchema,
|
|
@@ -54,6 +55,7 @@ export type AccountConfigItem = z.infer<typeof AccountConfigSchema>;
|
|
|
54
55
|
export type MailboxItem = z.infer<typeof MailboxSchema>;
|
|
55
56
|
export type ThreadMessageItem = z.infer<typeof ThreadMessageSchema>;
|
|
56
57
|
export type OutboxMessageItem = z.infer<typeof OutboxMessageSchema>;
|
|
58
|
+
export type OutboxAttachmentItem = z.infer<typeof OutboxAttachmentSchema>;
|
|
57
59
|
export type MessageItem = z.infer<typeof MessageSchema>;
|
|
58
60
|
export type MessageFlagItem = z.infer<typeof MessageFlagSchema>;
|
|
59
61
|
export type BodyPartItem = z.infer<typeof BodyPartSchema>;
|