@remit/drizzle-service 0.0.1

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 (82) hide show
  1. package/drizzle.config.ts +15 -0
  2. package/package.json +52 -0
  3. package/src/db.ts +13 -0
  4. package/src/dialect.ts +13 -0
  5. package/src/error.ts +37 -0
  6. package/src/id.ts +50 -0
  7. package/src/index.ts +62 -0
  8. package/src/pagination.ts +29 -0
  9. package/src/repos/cascade-delete.sqlite.test.ts +159 -0
  10. package/src/repos/cascade-delete.test.ts +423 -0
  11. package/src/repos/cascade-delete.ts +219 -0
  12. package/src/repos/envelope.sqlite.test.ts +94 -0
  13. package/src/repos/envelope.test.ts +225 -0
  14. package/src/repos/envelope.ts +342 -0
  15. package/src/repos/filter-anchor.test.ts +131 -0
  16. package/src/repos/filter-anchor.ts +105 -0
  17. package/src/repos/filter.test.ts +279 -0
  18. package/src/repos/filter.ts +253 -0
  19. package/src/repos/i4-account-config.test.ts +105 -0
  20. package/src/repos/i4-account-config.ts +257 -0
  21. package/src/repos/i4-account-export-request.ts +141 -0
  22. package/src/repos/i4-account-setting.test.ts +94 -0
  23. package/src/repos/i4-account-setting.ts +92 -0
  24. package/src/repos/i4-account.test.ts +223 -0
  25. package/src/repos/i4-account.ts +380 -0
  26. package/src/repos/i4-address-wellknown.ts +31 -0
  27. package/src/repos/i4-address.test.ts +358 -0
  28. package/src/repos/i4-address.ts +613 -0
  29. package/src/repos/i4-mailbox-lock.test.ts +368 -0
  30. package/src/repos/i4-mailbox-lock.ts +140 -0
  31. package/src/repos/i4-mailbox-special-use.ts +165 -0
  32. package/src/repos/i4-mailbox.test.ts +188 -0
  33. package/src/repos/i4-mailbox.ts +347 -0
  34. package/src/repos/i4-message-flag-push.test.ts +189 -0
  35. package/src/repos/i4-message-flag-push.ts +173 -0
  36. package/src/repos/i4-message-placement-move.test.ts +135 -0
  37. package/src/repos/i4-message-placement-move.ts +144 -0
  38. package/src/repos/i4-organize-job-request.ts +142 -0
  39. package/src/repos/i4-outbox-message.test.ts +298 -0
  40. package/src/repos/i4-outbox-message.ts +299 -0
  41. package/src/repos/label.conformance.sqlite.test.ts +23 -0
  42. package/src/repos/label.conformance.test.ts +19 -0
  43. package/src/repos/label.ts +125 -0
  44. package/src/repos/mappers.ts +171 -0
  45. package/src/repos/message-flag.ts +162 -0
  46. package/src/repos/message-label.test.ts +110 -0
  47. package/src/repos/message-label.ts +96 -0
  48. package/src/repos/message.sqlite.test.ts +118 -0
  49. package/src/repos/message.test.ts +488 -0
  50. package/src/repos/message.ts +558 -0
  51. package/src/repos/serialized-writes.sqlite.test.ts +199 -0
  52. package/src/repos/test-helpers.ts +222 -0
  53. package/src/repos/thread-message.sqlite.test.ts +198 -0
  54. package/src/repos/thread-message.test.ts +744 -0
  55. package/src/repos/thread-message.ts +832 -0
  56. package/src/repos/thread-search-predicates.ts +79 -0
  57. package/src/repos/unit-of-work.sqlite.test.ts +138 -0
  58. package/src/repos/unit-of-work.test.ts +105 -0
  59. package/src/repos/unit-of-work.ts +31 -0
  60. package/src/schema/active-entities.ts +19 -0
  61. package/src/schema/i4-account-config.ts +4 -0
  62. package/src/schema/i4-account-export-request.ts +3 -0
  63. package/src/schema/i4-account-setting.ts +3 -0
  64. package/src/schema/i4-address.ts +3 -0
  65. package/src/schema/i4-mailbox-lock.ts +3 -0
  66. package/src/schema/i4-mailbox.ts +4 -0
  67. package/src/schema/i4-message-flag-push.ts +3 -0
  68. package/src/schema/i4-message-placement-move.ts +3 -0
  69. package/src/schema/i4-organize-job-request.ts +1 -0
  70. package/src/schema/i4-outbox-message.ts +3 -0
  71. package/src/schema/message-data.ts +44 -0
  72. package/src/schema/outbox.ts +74 -0
  73. package/src/schema/thread-message.ts +3 -0
  74. package/src/schema-full-sqlite.ts +11 -0
  75. package/src/schema-full.ts +16 -0
  76. package/src/schema.ts +28 -0
  77. package/src/sqlite-client.ts +52 -0
  78. package/src/test-db-sqlite.ts +75 -0
  79. package/src/test-db.ts +76 -0
  80. package/src/tx.ts +208 -0
  81. package/src/vps-migrations-drift.test.ts +34 -0
  82. package/tsconfig.json +8 -0
@@ -0,0 +1,299 @@
1
+ import type {
2
+ CreateOutboxMessageInput,
3
+ IOutboxMessageRepository,
4
+ OutboxMessageItem,
5
+ ResultList,
6
+ UpdateOutboxMessageInput,
7
+ } from "@remit/data-ports";
8
+ import { and, desc, eq, inArray, lt, or } from "drizzle-orm";
9
+ import type { NodePgDatabase } from "drizzle-orm/node-postgres";
10
+ import { ForbiddenError, NotFoundError } from "../error.js";
11
+ import { decodeToken, resultList } from "../pagination.js";
12
+ import { outboxMessageTable } from "../schema/i4-outbox-message.js";
13
+
14
+ type DB = NodePgDatabase<Record<string, unknown>>;
15
+
16
+ function rowToOutboxMessage(
17
+ row: typeof outboxMessageTable.$inferSelect,
18
+ ): OutboxMessageItem {
19
+ return {
20
+ outboxMessageId: row.outboxMessageId,
21
+ accountId: row.accountId,
22
+ accountConfigId: row.accountConfigId,
23
+ fromAddress: row.fromAddress,
24
+ fromName: row.fromName ?? undefined,
25
+ toAddresses: (row.toAddresses as string[]) ?? [],
26
+ ccAddresses: (row.ccAddresses as string[]) ?? [],
27
+ bccAddresses: (row.bccAddresses as string[]) ?? [],
28
+ replyToAddress: row.replyToAddress ?? undefined,
29
+ subject: row.subject ?? undefined,
30
+ messageIdValue: row.messageIdValue,
31
+ inReplyTo: row.inReplyTo ?? undefined,
32
+ references: (row.references as string[]) ?? [],
33
+ textBody: row.textBody ?? undefined,
34
+ htmlBody: row.htmlBody ?? undefined,
35
+ status: row.status as OutboxMessageItem["status"],
36
+ lastError: row.lastError ?? undefined,
37
+ lastSmtpCode: row.lastSmtpCode ?? undefined,
38
+ sentAt: row.sentAt ?? undefined,
39
+ smtpMessageId: row.smtpMessageId ?? undefined,
40
+ createdAt: row.createdAt,
41
+ updatedAt: row.updatedAt,
42
+ };
43
+ }
44
+
45
+ export class OutboxMessageRepo implements IOutboxMessageRepository {
46
+ constructor(private db: DB) {}
47
+
48
+ async create(input: CreateOutboxMessageInput): Promise<OutboxMessageItem> {
49
+ const now = Date.now();
50
+ const [row] = await this.db
51
+ .insert(outboxMessageTable)
52
+ .values({
53
+ accountId: input.accountId,
54
+ accountConfigId: input.accountConfigId,
55
+ fromAddress: input.fromAddress,
56
+ fromName: input.fromName,
57
+ toAddresses: input.toAddresses ?? [],
58
+ ccAddresses: input.ccAddresses ?? [],
59
+ bccAddresses: input.bccAddresses ?? [],
60
+ replyToAddress: input.replyToAddress,
61
+ subject: input.subject,
62
+ messageIdValue: input.messageIdValue,
63
+ inReplyTo: input.inReplyTo,
64
+ references: input.references ?? [],
65
+ textBody: input.textBody,
66
+ htmlBody: input.htmlBody,
67
+ status: input.status,
68
+ lastError: input.lastError,
69
+ lastSmtpCode: input.lastSmtpCode,
70
+ sentAt: input.sentAt,
71
+ smtpMessageId: input.smtpMessageId,
72
+ createdAt: now,
73
+ updatedAt: now,
74
+ })
75
+ .returning();
76
+ return rowToOutboxMessage(row);
77
+ }
78
+
79
+ async get(
80
+ accountConfigId: string,
81
+ outboxMessageId: string,
82
+ mode?: "read" | "act",
83
+ ): Promise<OutboxMessageItem>;
84
+ async get(
85
+ accountConfigId: string,
86
+ outboxMessageIds: string[],
87
+ ): Promise<OutboxMessageItem[]>;
88
+ async get(
89
+ accountConfigId: string,
90
+ outboxMessageId: string | string[],
91
+ mode: "read" | "act" = "read",
92
+ ): Promise<OutboxMessageItem | OutboxMessageItem[]> {
93
+ if (Array.isArray(outboxMessageId)) {
94
+ if (outboxMessageId.length === 0) return [];
95
+ const rows = await this.db
96
+ .select()
97
+ .from(outboxMessageTable)
98
+ .where(
99
+ and(
100
+ eq(outboxMessageTable.accountConfigId, accountConfigId),
101
+ inArray(outboxMessageTable.outboxMessageId, outboxMessageId),
102
+ ),
103
+ );
104
+ return rows.map(rowToOutboxMessage);
105
+ }
106
+ // Fetch unscoped by id so a foreign row can be distinguished from an
107
+ // absent one — mode="act" reports the former as 403, not 404.
108
+ const [row] = await this.db
109
+ .select()
110
+ .from(outboxMessageTable)
111
+ .where(eq(outboxMessageTable.outboxMessageId, outboxMessageId));
112
+ if (!row) {
113
+ throw new NotFoundError(`OutboxMessage not found: ${outboxMessageId}`);
114
+ }
115
+ if (row.accountConfigId !== accountConfigId) {
116
+ if (mode === "act") {
117
+ throw new ForbiddenError(
118
+ `OutboxMessage ${outboxMessageId} not in account config`,
119
+ );
120
+ }
121
+ throw new NotFoundError(`OutboxMessage not found: ${outboxMessageId}`);
122
+ }
123
+ return rowToOutboxMessage(row);
124
+ }
125
+
126
+ async update(
127
+ accountConfigId: string,
128
+ outboxMessageId: string,
129
+ input: UpdateOutboxMessageInput,
130
+ ): Promise<OutboxMessageItem> {
131
+ const now = Date.now();
132
+ const updates: Partial<typeof outboxMessageTable.$inferInsert> = {
133
+ updatedAt: now,
134
+ };
135
+ if (input.status !== undefined) updates.status = input.status;
136
+ if (input.lastError !== undefined) updates.lastError = input.lastError;
137
+ if (input.lastSmtpCode !== undefined)
138
+ updates.lastSmtpCode = input.lastSmtpCode;
139
+ if (input.sentAt !== undefined) updates.sentAt = input.sentAt;
140
+ if (input.smtpMessageId !== undefined)
141
+ updates.smtpMessageId = input.smtpMessageId;
142
+ if (input.toAddresses !== undefined)
143
+ updates.toAddresses = input.toAddresses;
144
+ if (input.ccAddresses !== undefined)
145
+ updates.ccAddresses = input.ccAddresses;
146
+ if (input.bccAddresses !== undefined)
147
+ updates.bccAddresses = input.bccAddresses;
148
+ if (input.subject !== undefined) updates.subject = input.subject;
149
+ if (input.textBody !== undefined) updates.textBody = input.textBody;
150
+ if (input.htmlBody !== undefined) updates.htmlBody = input.htmlBody;
151
+ if (input.inReplyTo !== undefined) updates.inReplyTo = input.inReplyTo;
152
+ if (input.references !== undefined) updates.references = input.references;
153
+
154
+ const [row] = await this.db
155
+ .update(outboxMessageTable)
156
+ .set(updates)
157
+ .where(
158
+ and(
159
+ eq(outboxMessageTable.accountConfigId, accountConfigId),
160
+ eq(outboxMessageTable.outboxMessageId, outboxMessageId),
161
+ ),
162
+ )
163
+ .returning();
164
+ if (!row)
165
+ throw new NotFoundError(`OutboxMessage not found: ${outboxMessageId}`);
166
+ return rowToOutboxMessage(row);
167
+ }
168
+
169
+ async updateStatus(
170
+ accountConfigId: string,
171
+ outboxMessageId: string,
172
+ status: OutboxMessageItem["status"],
173
+ ): Promise<OutboxMessageItem> {
174
+ return this.update(accountConfigId, outboxMessageId, { status });
175
+ }
176
+
177
+ async markSent(
178
+ accountConfigId: string,
179
+ outboxMessageId: string,
180
+ fields: { sentAt: number; smtpMessageId?: string },
181
+ ): Promise<OutboxMessageItem> {
182
+ const now = Date.now();
183
+ const [row] = await this.db
184
+ .update(outboxMessageTable)
185
+ .set({
186
+ status: "sent",
187
+ sentAt: fields.sentAt,
188
+ smtpMessageId: fields.smtpMessageId,
189
+ lastError: null,
190
+ lastSmtpCode: null,
191
+ updatedAt: now,
192
+ })
193
+ .where(
194
+ and(
195
+ eq(outboxMessageTable.accountConfigId, accountConfigId),
196
+ eq(outboxMessageTable.outboxMessageId, outboxMessageId),
197
+ ),
198
+ )
199
+ .returning();
200
+ if (!row)
201
+ throw new NotFoundError(`OutboxMessage not found: ${outboxMessageId}`);
202
+ return rowToOutboxMessage(row);
203
+ }
204
+
205
+ async delete(
206
+ accountConfigId: string,
207
+ outboxMessageId: string,
208
+ ): Promise<void> {
209
+ await this.db
210
+ .delete(outboxMessageTable)
211
+ .where(
212
+ and(
213
+ eq(outboxMessageTable.accountConfigId, accountConfigId),
214
+ eq(outboxMessageTable.outboxMessageId, outboxMessageId),
215
+ ),
216
+ );
217
+ }
218
+
219
+ async deleteMany(
220
+ accountConfigId: string,
221
+ outboxMessageIds: string[],
222
+ ): Promise<void> {
223
+ if (outboxMessageIds.length === 0) return;
224
+ await this.db
225
+ .delete(outboxMessageTable)
226
+ .where(
227
+ and(
228
+ eq(outboxMessageTable.accountConfigId, accountConfigId),
229
+ inArray(outboxMessageTable.outboxMessageId, outboxMessageIds),
230
+ ),
231
+ );
232
+ }
233
+
234
+ async listByAccount(
235
+ accountId: string,
236
+ options?: { limit?: number; continuationToken?: string },
237
+ ): Promise<ResultList<OutboxMessageItem>> {
238
+ const limit = options?.limit ?? 100;
239
+ const cursor = options?.continuationToken
240
+ ? decodeToken(options.continuationToken)
241
+ : undefined;
242
+ const after = cursor
243
+ ? {
244
+ createdAt: cursor.createdAt as number,
245
+ outboxMessageId: cursor.outboxMessageId as string,
246
+ }
247
+ : undefined;
248
+
249
+ const rows = await this.db
250
+ .select()
251
+ .from(outboxMessageTable)
252
+ .where(
253
+ and(
254
+ eq(outboxMessageTable.accountId, accountId),
255
+ after
256
+ ? or(
257
+ lt(outboxMessageTable.createdAt, after.createdAt),
258
+ and(
259
+ eq(outboxMessageTable.createdAt, after.createdAt),
260
+ lt(outboxMessageTable.outboxMessageId, after.outboxMessageId),
261
+ ),
262
+ )
263
+ : undefined,
264
+ ),
265
+ )
266
+ .orderBy(
267
+ desc(outboxMessageTable.createdAt),
268
+ desc(outboxMessageTable.outboxMessageId),
269
+ )
270
+ .limit(limit + 1);
271
+
272
+ const hasMore = rows.length > limit;
273
+ const items = rows.slice(0, limit).map(rowToOutboxMessage);
274
+ const lastItem = items[items.length - 1];
275
+ return resultList(
276
+ items,
277
+ limit,
278
+ hasMore && lastItem
279
+ ? {
280
+ createdAt: lastItem.createdAt,
281
+ outboxMessageId: lastItem.outboxMessageId,
282
+ }
283
+ : undefined,
284
+ );
285
+ }
286
+
287
+ async listQueued(accountId: string): Promise<OutboxMessageItem[]> {
288
+ const rows = await this.db
289
+ .select()
290
+ .from(outboxMessageTable)
291
+ .where(
292
+ and(
293
+ eq(outboxMessageTable.accountId, accountId),
294
+ eq(outboxMessageTable.status, "queued"),
295
+ ),
296
+ );
297
+ return rows.map(rowToOutboxMessage);
298
+ }
299
+ }
@@ -0,0 +1,23 @@
1
+ import { labelRepositoryConformance } from "@remit/data-ports/conformance";
2
+ import { NotFoundError } from "../error.js";
3
+ import { randomId } from "../id.js";
4
+ import { labelTable } from "../schema.js";
5
+ import { createSqliteTestDb } from "../test-db-sqlite.js";
6
+ import { LabelRepo } from "./label.js";
7
+
8
+ let close: (() => Promise<void>) | undefined;
9
+
10
+ labelRepositoryConformance({
11
+ async createRepository() {
12
+ const { db, close: closeDb } = await createSqliteTestDb({
13
+ labels: labelTable,
14
+ });
15
+ close = closeDb;
16
+ return new LabelRepo(db as never);
17
+ },
18
+ teardown: async () => {
19
+ await close?.();
20
+ },
21
+ makeId: () => randomId(),
22
+ isNotFoundError: (error) => error instanceof NotFoundError,
23
+ });
@@ -0,0 +1,19 @@
1
+ import { labelRepositoryConformance } from "@remit/data-ports/conformance";
2
+ import { NotFoundError } from "../error.js";
3
+ import { createTestDb, randomId } from "../test-db.js";
4
+ import { LabelRepo } from "./label.js";
5
+
6
+ let close: (() => Promise<void>) | undefined;
7
+
8
+ labelRepositoryConformance({
9
+ async createRepository() {
10
+ const { db, close: closeDb } = await createTestDb();
11
+ close = closeDb;
12
+ return new LabelRepo(db as never);
13
+ },
14
+ teardown: async () => {
15
+ await close?.();
16
+ },
17
+ makeId: () => randomId(),
18
+ isNotFoundError: (error) => error instanceof NotFoundError,
19
+ });
@@ -0,0 +1,125 @@
1
+ import type {
2
+ CreateLabelInput,
3
+ ILabelRepository,
4
+ LabelItem,
5
+ UpdateLabelInput,
6
+ } from "@remit/data-ports";
7
+ import { and, eq } from "drizzle-orm";
8
+ import type { NodePgDatabase } from "drizzle-orm/node-postgres";
9
+ import { NotFoundError } from "../error.js";
10
+ import { randomId } from "../id.js";
11
+ import { labelTable } from "../schema.js";
12
+
13
+ type DB = NodePgDatabase<Record<string, unknown>>;
14
+
15
+ const normalize = (name: string): string => name.trim().toLowerCase();
16
+
17
+ function rowToLabel(row: typeof labelTable.$inferSelect): LabelItem {
18
+ return {
19
+ labelId: row.labelId,
20
+ accountConfigId: row.accountConfigId,
21
+ name: row.name,
22
+ normalizedName: row.normalizedName,
23
+ color: row.color,
24
+ createdAt: row.createdAt,
25
+ updatedAt: row.updatedAt,
26
+ };
27
+ }
28
+
29
+ export class LabelRepo implements ILabelRepository {
30
+ constructor(private db: DB) {}
31
+
32
+ async create(input: CreateLabelInput): Promise<LabelItem> {
33
+ const now = Date.now();
34
+ const [row] = await this.db
35
+ .insert(labelTable)
36
+ .values({
37
+ labelId: randomId(),
38
+ accountConfigId: input.accountConfigId,
39
+ name: input.name,
40
+ normalizedName: normalize(input.name),
41
+ color: input.color ?? "Default",
42
+ createdAt: now,
43
+ updatedAt: now,
44
+ })
45
+ .returning();
46
+ return rowToLabel(row);
47
+ }
48
+
49
+ async get(accountConfigId: string, labelId: string): Promise<LabelItem> {
50
+ const [row] = await this.db
51
+ .select()
52
+ .from(labelTable)
53
+ .where(
54
+ and(
55
+ eq(labelTable.accountConfigId, accountConfigId),
56
+ eq(labelTable.labelId, labelId),
57
+ ),
58
+ );
59
+ if (!row) {
60
+ throw new NotFoundError(`Label not found: ${labelId}`);
61
+ }
62
+ return rowToLabel(row);
63
+ }
64
+
65
+ async update(
66
+ accountConfigId: string,
67
+ labelId: string,
68
+ input: UpdateLabelInput,
69
+ ): Promise<LabelItem> {
70
+ const patch =
71
+ input.name !== undefined
72
+ ? { ...input, normalizedName: normalize(input.name) }
73
+ : input;
74
+ const [row] = await this.db
75
+ .update(labelTable)
76
+ .set({ ...patch, updatedAt: Date.now() })
77
+ .where(
78
+ and(
79
+ eq(labelTable.accountConfigId, accountConfigId),
80
+ eq(labelTable.labelId, labelId),
81
+ ),
82
+ )
83
+ .returning();
84
+ if (!row) {
85
+ throw new NotFoundError(`Label not found: ${labelId}`);
86
+ }
87
+ return rowToLabel(row);
88
+ }
89
+
90
+ async delete(accountConfigId: string, labelId: string): Promise<void> {
91
+ await this.db
92
+ .delete(labelTable)
93
+ .where(
94
+ and(
95
+ eq(labelTable.accountConfigId, accountConfigId),
96
+ eq(labelTable.labelId, labelId),
97
+ ),
98
+ );
99
+ }
100
+
101
+ async listByAccountConfig(accountConfigId: string): Promise<LabelItem[]> {
102
+ const rows = await this.db
103
+ .select()
104
+ .from(labelTable)
105
+ .where(eq(labelTable.accountConfigId, accountConfigId))
106
+ .orderBy(labelTable.createdAt);
107
+ return rows.map(rowToLabel);
108
+ }
109
+
110
+ async findByNormalizedName(
111
+ accountConfigId: string,
112
+ normalizedName: string,
113
+ ): Promise<LabelItem | null> {
114
+ const [row] = await this.db
115
+ .select()
116
+ .from(labelTable)
117
+ .where(
118
+ and(
119
+ eq(labelTable.accountConfigId, accountConfigId),
120
+ eq(labelTable.normalizedName, normalize(normalizedName)),
121
+ ),
122
+ );
123
+ return row ? rowToLabel(row) : null;
124
+ }
125
+ }
@@ -0,0 +1,171 @@
1
+ import type {
2
+ BodyPartContentItem,
3
+ BodyPartItem,
4
+ BodyPartParameterItem,
5
+ BodyPartStorageItem,
6
+ EnvelopeAddressItem,
7
+ EnvelopeItem,
8
+ MessageReferenceItem,
9
+ RawMessageStorageItem,
10
+ } from "@remit/data-ports";
11
+ import type {
12
+ bodyPartContentTable,
13
+ bodyPartParameterTable,
14
+ bodyPartStorageTable,
15
+ bodyPartTable,
16
+ envelopeAddressTable,
17
+ envelopeTable,
18
+ messageReferenceTable,
19
+ rawMessageStorageTable,
20
+ } from "../schema/message-data.js";
21
+
22
+ export function toEnvelopeItem(
23
+ row: typeof envelopeTable.$inferSelect,
24
+ ): EnvelopeItem {
25
+ return {
26
+ envelopeId: row.envelopeId,
27
+ messageId: row.messageId,
28
+ dateValue: row.dateValue,
29
+ dateRaw: row.dateRaw,
30
+ createdAt: row.createdAt,
31
+ updatedAt: row.updatedAt,
32
+ ...(row.subject !== null ? { subject: row.subject } : {}),
33
+ ...(row.messageIdValue !== null
34
+ ? { messageIdValue: row.messageIdValue }
35
+ : {}),
36
+ };
37
+ }
38
+
39
+ export function toMessageReferenceItem(
40
+ row: typeof messageReferenceTable.$inferSelect,
41
+ ): MessageReferenceItem {
42
+ return {
43
+ messageReferenceId: row.messageReferenceId,
44
+ messageId: row.messageId,
45
+ envelopeId: row.envelopeId,
46
+ messageIdValue: row.messageIdValue,
47
+ referenceType: row.referenceType,
48
+ referenceOrder: row.referenceOrder,
49
+ createdAt: row.createdAt,
50
+ updatedAt: row.updatedAt,
51
+ };
52
+ }
53
+
54
+ export function toEnvelopeAddressItem(
55
+ row: typeof envelopeAddressTable.$inferSelect,
56
+ ): EnvelopeAddressItem {
57
+ return {
58
+ envelopeAddressId: row.envelopeAddressId,
59
+ messageId: row.messageId,
60
+ addressId: row.addressId,
61
+ normalizedEmail: row.normalizedEmail,
62
+ addressRole: row.addressRole,
63
+ addressOrder: row.addressOrder,
64
+ createdAt: row.createdAt,
65
+ updatedAt: row.updatedAt,
66
+ ...(row.displayName !== null ? { displayName: row.displayName } : {}),
67
+ };
68
+ }
69
+
70
+ export function toBodyPartItem(
71
+ row: typeof bodyPartTable.$inferSelect,
72
+ ): BodyPartItem {
73
+ return {
74
+ bodyPartId: row.bodyPartId,
75
+ messageId: row.messageId,
76
+ partPath: row.partPath,
77
+ mediaType: row.mediaType,
78
+ mediaSubtype: row.mediaSubtype,
79
+ transferEncoding: row.transferEncoding,
80
+ sizeOctets: row.sizeOctets,
81
+ isMultipart: row.isMultipart,
82
+ createdAt: row.createdAt,
83
+ updatedAt: row.updatedAt,
84
+ ...(row.parentBodyPartId !== null
85
+ ? { parentBodyPartId: row.parentBodyPartId }
86
+ : {}),
87
+ ...(row.contentId !== null ? { contentId: row.contentId } : {}),
88
+ ...(row.contentDescription !== null
89
+ ? { contentDescription: row.contentDescription }
90
+ : {}),
91
+ ...(row.lineCount !== null ? { lineCount: row.lineCount } : {}),
92
+ ...(row.md5Hash !== null ? { md5Hash: row.md5Hash } : {}),
93
+ ...(row.disposition !== null ? { disposition: row.disposition } : {}),
94
+ ...(row.dispositionFilename !== null
95
+ ? { dispositionFilename: row.dispositionFilename }
96
+ : {}),
97
+ ...(row.language !== null ? { language: row.language } : {}),
98
+ ...(row.location !== null ? { location: row.location } : {}),
99
+ ...(row.multipartSubtype !== null
100
+ ? { multipartSubtype: row.multipartSubtype }
101
+ : {}),
102
+ };
103
+ }
104
+
105
+ export function toBodyPartParameterItem(
106
+ row: typeof bodyPartParameterTable.$inferSelect,
107
+ ): BodyPartParameterItem {
108
+ return {
109
+ bodyPartParameterId: row.bodyPartParameterId,
110
+ messageId: row.messageId,
111
+ bodyPartId: row.bodyPartId,
112
+ parameterName: row.parameterName,
113
+ parameterValue: row.parameterValue,
114
+ createdAt: row.createdAt,
115
+ updatedAt: row.updatedAt,
116
+ };
117
+ }
118
+
119
+ export function toRawMessageStorageItem(
120
+ row: typeof rawMessageStorageTable.$inferSelect,
121
+ ): RawMessageStorageItem {
122
+ return {
123
+ rawStorageId: row.rawStorageId,
124
+ messageId: row.messageId,
125
+ storageType: row.storageType,
126
+ storageLocation: row.storageLocation,
127
+ storageKey: row.storageKey,
128
+ sizeBytes: row.sizeBytes,
129
+ checksumSha256: row.checksumSha256,
130
+ contentEncoding: row.contentEncoding,
131
+ storedAt: row.storedAt,
132
+ createdAt: row.createdAt,
133
+ updatedAt: row.updatedAt,
134
+ ...(row.expiresAt !== null ? { expiresAt: row.expiresAt } : {}),
135
+ };
136
+ }
137
+
138
+ export function toBodyPartStorageItem(
139
+ row: typeof bodyPartStorageTable.$inferSelect,
140
+ ): BodyPartStorageItem {
141
+ return {
142
+ bodyPartStorageId: row.bodyPartStorageId,
143
+ messageId: row.messageId,
144
+ bodyPartId: row.bodyPartId,
145
+ storageType: row.storageType,
146
+ storageLocation: row.storageLocation,
147
+ storageKey: row.storageKey,
148
+ decodedSizeBytes: row.decodedSizeBytes,
149
+ checksumSha256: row.checksumSha256,
150
+ contentEncoding: row.contentEncoding,
151
+ isDeduped: row.isDeduped,
152
+ storedAt: row.storedAt,
153
+ createdAt: row.createdAt,
154
+ updatedAt: row.updatedAt,
155
+ ...(row.dedupHash !== null ? { dedupHash: row.dedupHash } : {}),
156
+ };
157
+ }
158
+
159
+ export function toBodyPartContentItem(
160
+ row: typeof bodyPartContentTable.$inferSelect,
161
+ ): BodyPartContentItem {
162
+ return {
163
+ bodyPartContentId: row.bodyPartContentId,
164
+ messageId: row.messageId,
165
+ bodyPartId: row.bodyPartId,
166
+ content: row.content,
167
+ contentLength: row.contentLength,
168
+ createdAt: row.createdAt,
169
+ updatedAt: row.updatedAt,
170
+ };
171
+ }