@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/data-ports",
3
- "version": "0.0.25",
3
+ "version": "0.0.27",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -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>;