@remit/drizzle-service 0.0.101 → 0.0.102

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/drizzle-service",
3
- "version": "0.0.101",
3
+ "version": "0.0.102",
4
4
  "description": "Drizzle ORM service over SQLite",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -7,11 +7,12 @@ import type {
7
7
  } from "@remit/data-ports";
8
8
  import { deriveCalendarSuggestionId } from "@remit/data-ports/id";
9
9
  import { CalendarSuggestionState } from "@remit/domain-enums";
10
- import { and, asc, desc, eq, gte, inArray, lt, or } from "drizzle-orm";
10
+ import { and, asc, desc, eq, gte, lt, or } from "drizzle-orm";
11
11
  import type { Db } from "../db.js";
12
12
  import { NotFoundError } from "../error.js";
13
13
  import { decodeToken, resultList } from "../pagination.js";
14
14
  import { calendarSuggestionTable } from "../schema.js";
15
+ import { inList } from "./in-list.js";
15
16
 
16
17
  type DB = Db<Record<string, unknown>>;
17
18
 
@@ -111,7 +112,7 @@ export class CalendarSuggestionRepo implements ICalendarSuggestionRepository {
111
112
  .where(
112
113
  and(
113
114
  eq(calendarSuggestionTable.accountConfigId, accountConfigId),
114
- inArray(
115
+ inList(
115
116
  calendarSuggestionTable.acceptedCalendarObjectId,
116
117
  calendarObjectIds,
117
118
  ),
@@ -1,4 +1,3 @@
1
- import { and, eq, inArray, or, type SQL } from "drizzle-orm";
2
1
  import { runBatch, type SqliteBatchItem } from "../batch.js";
3
2
  import {
4
3
  openSqliteDatabases,
@@ -17,6 +16,7 @@ import { quarantineTable } from "../schema/quarantine.js";
17
16
  import { syncWarningTable } from "../schema/sync-warning.js";
18
17
  import { threadMessageTable } from "../schema/thread-message.js";
19
18
  import { SearchIndex } from "../search/thread-message-search.js";
19
+ import { inList, pairInList } from "./in-list.js";
20
20
  import { deleteMessageSubtree } from "./message.js";
21
21
 
22
22
  export interface CascadeEntity {
@@ -107,7 +107,7 @@ export const runDrizzleCascadeDelete = async (
107
107
  items.push(
108
108
  mail
109
109
  .delete(threadMessageTable)
110
- .where(inArray(threadMessageTable.threadMessageId, threadMessageIds)),
110
+ .where(inList(threadMessageTable.threadMessageId, threadMessageIds)),
111
111
  );
112
112
  }
113
113
 
@@ -116,12 +116,12 @@ export const runDrizzleCascadeDelete = async (
116
116
  items.push(
117
117
  mail
118
118
  .delete(syncWarningTable)
119
- .where(inArray(syncWarningTable.mailboxId, mailboxIds)),
119
+ .where(inList(syncWarningTable.mailboxId, mailboxIds)),
120
120
  );
121
121
  items.push(
122
122
  mail
123
123
  .delete(mailboxTable)
124
- .where(inArray(mailboxTable.mailboxId, mailboxIds)),
124
+ .where(inList(mailboxTable.mailboxId, mailboxIds)),
125
125
  );
126
126
  }
127
127
 
@@ -132,19 +132,21 @@ export const runDrizzleCascadeDelete = async (
132
132
  items.push(
133
133
  mail
134
134
  .delete(outboxMessageTable)
135
- .where(inArray(outboxMessageTable.outboxMessageId, outboxMessageIds)),
135
+ .where(inList(outboxMessageTable.outboxMessageId, outboxMessageIds)),
136
136
  );
137
137
  }
138
138
 
139
139
  const lockKeys = grouped.get("MailboxLock") ?? [];
140
140
  if (lockKeys.length > 0) {
141
- const conditions: SQL[] = lockKeys.map((k) =>
142
- and(
143
- eq(mailboxLockTable.mailboxId, k.mailboxId),
144
- eq(mailboxLockTable.eventName, k.eventName),
141
+ items.push(
142
+ mail.delete(mailboxLockTable).where(
143
+ pairInList(
144
+ mailboxLockTable.mailboxId,
145
+ mailboxLockTable.eventName,
146
+ lockKeys.map((k) => [k.mailboxId, k.eventName]),
147
+ ),
145
148
  ),
146
- ) as SQL[];
147
- items.push(mail.delete(mailboxLockTable).where(or(...conditions)));
149
+ );
148
150
  }
149
151
 
150
152
  const accountSettingIds = (grouped.get("AccountSetting") ?? []).map(
@@ -154,9 +156,7 @@ export const runDrizzleCascadeDelete = async (
154
156
  appItems.push(
155
157
  app
156
158
  .delete(accountSettingTable)
157
- .where(
158
- inArray(accountSettingTable.accountSettingId, accountSettingIds),
159
- ),
159
+ .where(inList(accountSettingTable.accountSettingId, accountSettingIds)),
160
160
  );
161
161
  }
162
162
 
@@ -165,7 +165,7 @@ export const runDrizzleCascadeDelete = async (
165
165
  items.push(
166
166
  mail
167
167
  .delete(addressTable)
168
- .where(inArray(addressTable.addressId, addressIds)),
168
+ .where(inList(addressTable.addressId, addressIds)),
169
169
  );
170
170
  }
171
171
 
@@ -178,17 +178,17 @@ export const runDrizzleCascadeDelete = async (
178
178
  items.push(
179
179
  mail
180
180
  .delete(quarantineTable)
181
- .where(inArray(quarantineTable.accountId, accountIds)),
181
+ .where(inList(quarantineTable.accountId, accountIds)),
182
182
  );
183
183
  items.push(
184
184
  mail
185
185
  .delete(syncWarningTable)
186
- .where(inArray(syncWarningTable.accountId, accountIds)),
186
+ .where(inList(syncWarningTable.accountId, accountIds)),
187
187
  );
188
188
  appItems.push(
189
189
  app
190
190
  .delete(accountTable)
191
- .where(inArray(accountTable.accountId, accountIds)),
191
+ .where(inList(accountTable.accountId, accountIds)),
192
192
  );
193
193
  }
194
194
 
@@ -200,23 +200,22 @@ export const runDrizzleCascadeDelete = async (
200
200
  mail
201
201
  .delete(messagePlacementMoveTable)
202
202
  .where(
203
- inArray(messagePlacementMoveTable.messageId, placementMoveMessageIds),
203
+ inList(messagePlacementMoveTable.messageId, placementMoveMessageIds),
204
204
  ),
205
205
  );
206
206
  }
207
207
 
208
- // Composite key (messageId, flagName) — a message can carry an
209
- // independent pending read-state marker and pending star marker at once
210
- // (issue #1273), so a single-column inArray cannot target one row.
211
208
  const flagPushKeys = grouped.get("MessageFlagPush") ?? [];
212
209
  if (flagPushKeys.length > 0) {
213
- const conditions: SQL[] = flagPushKeys.map((k) =>
214
- and(
215
- eq(messageFlagPushTable.messageId, k.messageId),
216
- eq(messageFlagPushTable.flagName, k.flagName),
210
+ items.push(
211
+ mail.delete(messageFlagPushTable).where(
212
+ pairInList(
213
+ messageFlagPushTable.messageId,
214
+ messageFlagPushTable.flagName,
215
+ flagPushKeys.map((k) => [k.messageId, k.flagName]),
216
+ ),
217
217
  ),
218
- ) as SQL[];
219
- items.push(mail.delete(messageFlagPushTable).where(or(...conditions)));
218
+ );
220
219
  }
221
220
  await runBatch(mail, items);
222
221
  await runBatch(app, appItems);
@@ -8,7 +8,7 @@ import type {
8
8
  MessageReferenceInput,
9
9
  UpdateEnvelopeInput,
10
10
  } from "@remit/data-ports";
11
- import { eq, inArray } from "drizzle-orm";
11
+ import { eq } from "drizzle-orm";
12
12
  import { runBatch } from "../batch.js";
13
13
  import type { Db } from "../db.js";
14
14
  import {
@@ -34,6 +34,7 @@ import {
34
34
  messageTable,
35
35
  rawMessageStorageTable,
36
36
  } from "../schema/message-data.js";
37
+ import { inList } from "./in-list.js";
37
38
  import {
38
39
  toBodyPartItem,
39
40
  toBodyPartParameterItem,
@@ -235,7 +236,7 @@ export class DrizzleEnvelopeRepository implements IEnvelopeRepository {
235
236
 
236
237
  await this.db
237
238
  .delete(envelopeTable)
238
- .where(inArray(envelopeTable.envelopeId, envelopeIds));
239
+ .where(inList(envelopeTable.envelopeId, envelopeIds));
239
240
  }
240
241
 
241
242
  async createEnvelope(input: CreateEnvelopeInput): Promise<EnvelopeItem> {
@@ -293,7 +294,7 @@ export class DrizzleEnvelopeRepository implements IEnvelopeRepository {
293
294
  const rows = await this.db
294
295
  .select()
295
296
  .from(envelopeTable)
296
- .where(inArray(envelopeTable.envelopeId, envelopeId));
297
+ .where(inList(envelopeTable.envelopeId, envelopeId));
297
298
  return rows.map(toEnvelopeItem);
298
299
  }
299
300
 
@@ -353,6 +354,6 @@ export class DrizzleEnvelopeRepository implements IEnvelopeRepository {
353
354
  if (messageIds.length === 0) return;
354
355
  await this.db
355
356
  .delete(bodyStructureRefetchTable)
356
- .where(inArray(bodyStructureRefetchTable.messageId, messageIds));
357
+ .where(inList(bodyStructureRefetchTable.messageId, messageIds));
357
358
  }
358
359
  }
@@ -6,7 +6,7 @@ import type {
6
6
  ResultList,
7
7
  UpdateAccountConfigInput,
8
8
  } from "@remit/data-ports";
9
- import { and, asc, eq, gt, inArray, or } from "drizzle-orm";
9
+ import { and, asc, eq, gt, or } from "drizzle-orm";
10
10
  import type { Db } from "../db.js";
11
11
  import { NotFoundError } from "../error.js";
12
12
  import { randomId } from "../id.js";
@@ -18,6 +18,7 @@ import {
18
18
  import { addressTable } from "../schema/i4-address.js";
19
19
  import { rowToAccount } from "./i4-account.js";
20
20
  import { rowToAddress } from "./i4-address.js";
21
+ import { inList } from "./in-list.js";
21
22
 
22
23
  type DB = Db<Record<string, unknown>>;
23
24
 
@@ -71,7 +72,7 @@ export class AccountConfigRepo implements IAccountConfigRepository {
71
72
  const rows = await this.db
72
73
  .select()
73
74
  .from(accountConfigTable)
74
- .where(inArray(accountConfigTable.accountConfigId, accountConfigId));
75
+ .where(inList(accountConfigTable.accountConfigId, accountConfigId));
75
76
  return rows.map(rowToAccountConfig);
76
77
  }
77
78
  const [row] = await this.db
@@ -116,7 +117,7 @@ export class AccountConfigRepo implements IAccountConfigRepository {
116
117
  if (accountConfigIds.length === 0) return;
117
118
  await this.db
118
119
  .delete(accountConfigTable)
119
- .where(inArray(accountConfigTable.accountConfigId, accountConfigIds));
120
+ .where(inList(accountConfigTable.accountConfigId, accountConfigIds));
120
121
  }
121
122
 
122
123
  async listByUser(
@@ -5,9 +5,10 @@ import type {
5
5
  IAccountSettingRepository,
6
6
  UpsertAccountSettingInput,
7
7
  } from "@remit/data-ports";
8
- import { and, eq, inArray } from "drizzle-orm";
8
+ import { and, eq } from "drizzle-orm";
9
9
  import type { Db } from "../db.js";
10
10
  import { accountSettingTable } from "../schema/i4-account-setting.js";
11
+ import { inList } from "./in-list.js";
11
12
 
12
13
  type DB = Db<Record<string, unknown>>;
13
14
 
@@ -63,8 +64,8 @@ export class AccountSettingRepo implements IAccountSettingRepository {
63
64
  .from(accountSettingTable)
64
65
  .where(
65
66
  and(
66
- inArray(accountSettingTable.accountConfigId, [...accountConfigIds]),
67
- inArray(accountSettingTable.name, [...names]),
67
+ inList(accountSettingTable.accountConfigId, accountConfigIds),
68
+ inList(accountSettingTable.name, names),
68
69
  ),
69
70
  );
70
71
  return rows.map(rowToAccountSetting);
@@ -9,7 +9,7 @@ import type {
9
9
  UpdateAccountInput,
10
10
  } from "@remit/data-ports";
11
11
  import { AccountService, SyncPhase } from "@remit/domain-enums";
12
- import { and, asc, eq, gt, inArray, like, or, sql } from "drizzle-orm";
12
+ import { and, asc, eq, gt, like, or, sql } from "drizzle-orm";
13
13
  import type { Db } from "../db.js";
14
14
  import { NotFoundError } from "../error.js";
15
15
  import { randomId } from "../id.js";
@@ -17,6 +17,7 @@ import { decodeToken, encodeToken, resultList } from "../pagination.js";
17
17
  import { accountTable } from "../schema/i4-account-config.js";
18
18
  import { mailboxTable } from "../schema/i4-mailbox.js";
19
19
  import { rowToMailbox } from "./i4-mailbox.js";
20
+ import { inList } from "./in-list.js";
20
21
 
21
22
  type DB = Db<Record<string, unknown>>;
22
23
 
@@ -129,7 +130,7 @@ export class AccountRepo implements IAccountRepository {
129
130
  const rows = await this.db
130
131
  .select()
131
132
  .from(accountTable)
132
- .where(inArray(accountTable.accountId, accountId));
133
+ .where(inList(accountTable.accountId, accountId));
133
134
  return rows.map(rowToAccount);
134
135
  }
135
136
  const [row] = await this.db
@@ -187,7 +188,7 @@ export class AccountRepo implements IAccountRepository {
187
188
  if (accountIds.length === 0) return;
188
189
  await this.db
189
190
  .delete(accountTable)
190
- .where(inArray(accountTable.accountId, accountIds));
191
+ .where(inList(accountTable.accountId, accountIds));
191
192
  }
192
193
 
193
194
  async list(
@@ -19,7 +19,6 @@ import {
19
19
  eq,
20
20
  getTableColumns,
21
21
  gt,
22
- inArray,
23
22
  type SQL,
24
23
  sql,
25
24
  } from "drizzle-orm";
@@ -43,6 +42,7 @@ import {
43
42
  addressRecency,
44
43
  addressSearchMatch,
45
44
  } from "./address-search-predicates.js";
45
+ import { inList } from "./in-list.js";
46
46
 
47
47
  type DB = Db<Record<string, unknown>>;
48
48
 
@@ -400,7 +400,7 @@ export class AddressRepo implements IAddressRepository {
400
400
  .where(
401
401
  and(
402
402
  eq(addressTable.accountConfigId, accountConfigId),
403
- inArray(addressTable.addressId, addressId),
403
+ inList(addressTable.addressId, addressId),
404
404
  ),
405
405
  );
406
406
  return rows.map(rowToAddress);
@@ -666,7 +666,7 @@ export class AddressRepo implements IAddressRepository {
666
666
  .where(
667
667
  and(
668
668
  eq(addressTable.accountConfigId, accountConfigId),
669
- inArray(addressTable.addressId, addressIds),
669
+ inList(addressTable.addressId, addressIds),
670
670
  ),
671
671
  );
672
672
  }
@@ -856,7 +856,7 @@ export class AddressRepo implements IAddressRepository {
856
856
  .select()
857
857
  .from(envelopeAddressTable)
858
858
  .where(
859
- inArray(envelopeAddressTable.envelopeAddressId, envelopeAddressId),
859
+ inList(envelopeAddressTable.envelopeAddressId, envelopeAddressId),
860
860
  );
861
861
  return rows.map(rowToEnvelopeAddress);
862
862
  }
@@ -884,7 +884,7 @@ export class AddressRepo implements IAddressRepository {
884
884
  await this.db
885
885
  .delete(envelopeAddressTable)
886
886
  .where(
887
- inArray(envelopeAddressTable.envelopeAddressId, envelopeAddressIds),
887
+ inList(envelopeAddressTable.envelopeAddressId, envelopeAddressIds),
888
888
  );
889
889
  }
890
890
  }
@@ -14,12 +14,13 @@ import {
14
14
  type UnappointedRoleResolution,
15
15
  } from "@remit/data-ports/folder-role";
16
16
  import { CanonicalMailboxRole } from "@remit/domain-enums";
17
- import { eq, inArray } from "drizzle-orm";
17
+ import { eq } from "drizzle-orm";
18
18
  import type { Db } from "../db.js";
19
19
  import { randomId } from "../id.js";
20
20
  import { accountTable } from "../schema/i4-account-config.js";
21
21
  import { mailboxSpecialUseTable, mailboxTable } from "../schema/i4-mailbox.js";
22
22
  import { AccountSettingRepo } from "./i4-account-setting.js";
23
+ import { inList } from "./in-list.js";
23
24
 
24
25
  type DB = Db<Record<string, unknown>>;
25
26
 
@@ -269,7 +270,7 @@ export class MailboxSpecialUseRepo implements IMailboxSpecialUseRepository {
269
270
  accountConfigId: accountTable.accountConfigId,
270
271
  })
271
272
  .from(accountTable)
272
- .where(inArray(accountTable.accountId, [...accountIds]));
273
+ .where(inList(accountTable.accountId, accountIds));
273
274
  if (accounts.length === 0) return new Map();
274
275
 
275
276
  const wanted = new Map<string, string>();
@@ -303,14 +304,14 @@ export class MailboxSpecialUseRepo implements IMailboxSpecialUseRepository {
303
304
  const rows = await this.db
304
305
  .select()
305
306
  .from(mailboxTable)
306
- .where(inArray(mailboxTable.accountId, [...accountIds]));
307
+ .where(inList(mailboxTable.accountId, accountIds));
307
308
  if (rows.length === 0) return new Map();
308
309
 
309
310
  const entries = await this.db
310
311
  .select()
311
312
  .from(mailboxSpecialUseTable)
312
313
  .where(
313
- inArray(
314
+ inList(
314
315
  mailboxSpecialUseTable.mailboxId,
315
316
  rows.map((row) => row.mailboxId),
316
317
  ),
@@ -397,7 +398,7 @@ export class MailboxSpecialUseRepo implements IMailboxSpecialUseRepository {
397
398
  .select()
398
399
  .from(mailboxSpecialUseTable)
399
400
  .where(
400
- inArray(
401
+ inList(
401
402
  mailboxSpecialUseTable.mailboxId,
402
403
  rows.map((row) => row.mailboxId),
403
404
  ),
@@ -44,6 +44,7 @@ import { messageTable } from "../schema/message-data.js";
44
44
  import { syncWarningTable } from "../schema/sync-warning.js";
45
45
  import { threadMessageTable } from "../schema/thread-message.js";
46
46
  import type { SearchIndex } from "../search/thread-message-search.js";
47
+ import { inList } from "./in-list.js";
47
48
  import { deleteMessageSubtree } from "./message.js";
48
49
 
49
50
  const base36Translator = shortUuid.createTranslator(
@@ -207,7 +208,7 @@ export class MailboxRepo implements IMailboxRepository {
207
208
  .where(
208
209
  and(
209
210
  eq(mailboxTable.accountId, accountId),
210
- inArray(mailboxTable.mailboxId, mailboxId),
211
+ inList(mailboxTable.mailboxId, mailboxId),
211
212
  ),
212
213
  );
213
214
  return rows.map(rowToMailbox);
@@ -384,7 +385,7 @@ export class MailboxRepo implements IMailboxRepository {
384
385
  .where(
385
386
  and(
386
387
  eq(syncWarningTable.accountId, accountId),
387
- inArray(syncWarningTable.mailboxId, mailboxIds),
388
+ inList(syncWarningTable.mailboxId, mailboxIds),
388
389
  ),
389
390
  ),
390
391
  this.db
@@ -392,7 +393,7 @@ export class MailboxRepo implements IMailboxRepository {
392
393
  .where(
393
394
  and(
394
395
  eq(mailboxTable.accountId, accountId),
395
- inArray(mailboxTable.mailboxId, mailboxIds),
396
+ inList(mailboxTable.mailboxId, mailboxIds),
396
397
  ),
397
398
  ),
398
399
  ]);
@@ -551,7 +552,7 @@ export class MailboxRepo implements IMailboxRepository {
551
552
  const listings = await this.db
552
553
  .select({ threadMessageId: threadMessageTable.threadMessageId })
553
554
  .from(threadMessageTable)
554
- .where(inArray(threadMessageTable.messageId, messageIds));
555
+ .where(inList(threadMessageTable.messageId, messageIds));
555
556
 
556
557
  // The primitive the rest of the codebase deletes mail with: nine
557
558
  // per-message child tables. A bespoke table list would orphan them.
@@ -559,7 +560,7 @@ export class MailboxRepo implements IMailboxRepository {
559
560
  ...deleteMessageSubtree(this.db, messageIds),
560
561
  this.db
561
562
  .delete(threadMessageTable)
562
- .where(inArray(threadMessageTable.messageId, messageIds)),
563
+ .where(inList(threadMessageTable.messageId, messageIds)),
563
564
  ]);
564
565
  await this.search.sync(listings.map((row) => row.threadMessageId));
565
566
  }
@@ -6,11 +6,12 @@ import type {
6
6
  ReserveOutboxAttachmentResult,
7
7
  } from "@remit/data-ports";
8
8
  import { holdsRoom } from "@remit/data-ports";
9
- import { and, eq, gt, gte, inArray, lt, or, sum } from "drizzle-orm";
9
+ import { and, eq, gt, gte, lt, or, sum } from "drizzle-orm";
10
10
  import { runBatch } from "../batch.js";
11
11
  import type { Db } from "../db.js";
12
12
  import { NotFoundError } from "../error.js";
13
13
  import { outboxAttachmentTable } from "../schema/i4-outbox-attachment.js";
14
+ import { inList } from "./in-list.js";
14
15
 
15
16
  type DB = Db<Record<string, unknown>>;
16
17
 
@@ -218,10 +219,7 @@ export class OutboxAttachmentRepo implements IOutboxAttachmentRepository {
218
219
  .where(
219
220
  and(
220
221
  eq(outboxAttachmentTable.accountConfigId, accountConfigId),
221
- inArray(
222
- outboxAttachmentTable.outboxAttachmentId,
223
- outboxAttachmentIds,
224
- ),
222
+ inList(outboxAttachmentTable.outboxAttachmentId, outboxAttachmentIds),
225
223
  ),
226
224
  );
227
225
  }
@@ -5,11 +5,12 @@ import type {
5
5
  ResultList,
6
6
  UpdateOutboxMessageInput,
7
7
  } from "@remit/data-ports";
8
- import { and, desc, eq, inArray, lt, or } from "drizzle-orm";
8
+ import { and, desc, eq, lt, or } from "drizzle-orm";
9
9
  import type { Db } from "../db.js";
10
10
  import { ForbiddenError, NotFoundError } from "../error.js";
11
11
  import { decodeToken, resultList } from "../pagination.js";
12
12
  import { outboxMessageTable } from "../schema/i4-outbox-message.js";
13
+ import { inList } from "./in-list.js";
13
14
 
14
15
  type DB = Db<Record<string, unknown>>;
15
16
 
@@ -99,7 +100,7 @@ export class OutboxMessageRepo implements IOutboxMessageRepository {
99
100
  .where(
100
101
  and(
101
102
  eq(outboxMessageTable.accountConfigId, accountConfigId),
102
- inArray(outboxMessageTable.outboxMessageId, outboxMessageId),
103
+ inList(outboxMessageTable.outboxMessageId, outboxMessageId),
103
104
  ),
104
105
  );
105
106
  return rows.map(rowToOutboxMessage);
@@ -264,7 +265,7 @@ export class OutboxMessageRepo implements IOutboxMessageRepository {
264
265
  .where(
265
266
  and(
266
267
  eq(outboxMessageTable.accountConfigId, accountConfigId),
267
- inArray(outboxMessageTable.outboxMessageId, outboxMessageIds),
268
+ inList(outboxMessageTable.outboxMessageId, outboxMessageIds),
268
269
  ),
269
270
  );
270
271
  }
@@ -298,7 +299,7 @@ export class OutboxMessageRepo implements IOutboxMessageRepository {
298
299
  .from(outboxMessageTable)
299
300
  .where(
300
301
  and(
301
- inArray(outboxMessageTable.accountId, accountIds),
302
+ inList(outboxMessageTable.accountId, accountIds),
302
303
  after
303
304
  ? or(
304
305
  lt(outboxMessageTable.createdAt, after.createdAt),
@@ -0,0 +1,61 @@
1
+ import assert from "node:assert/strict";
2
+ import { after, before, describe, test } from "node:test";
3
+ import { calendarTable } from "../schema.js";
4
+ import { createSqliteTestDb, type SqliteTestDb } from "../test-db-sqlite.js";
5
+ import { inList } from "./in-list.js";
6
+
7
+ const SCHEMA = { calendars: calendarTable };
8
+
9
+ const calendar = (calendarId: string): typeof calendarTable.$inferInsert => ({
10
+ calendarId,
11
+ accountConfigId: "account-config-in-list",
12
+ urlSegment: calendarId,
13
+ displayName: calendarId,
14
+ color: "Cal1",
15
+ componentSet: "VeventOnly",
16
+ source: "UserCreated",
17
+ timezone: "",
18
+ subscriptionUrl: "",
19
+ subscriptionEnabled: false,
20
+ syncSequence: 0,
21
+ createdAt: 0,
22
+ updatedAt: 0,
23
+ });
24
+
25
+ describe("inList on better-sqlite3", () => {
26
+ let db: SqliteTestDb<typeof SCHEMA>;
27
+ let close: () => Promise<void>;
28
+ const allIds = Array.from({ length: 150 }, (_, index) => `calendar-${index}`);
29
+
30
+ before(async () => {
31
+ ({ db, close } = await createSqliteTestDb(SCHEMA));
32
+ await db.insert(calendarTable).values(allIds.map(calendar));
33
+ });
34
+
35
+ after(() => close());
36
+
37
+ test("binds a single parameter for 150 ids", () => {
38
+ const query = db
39
+ .select({ calendarId: calendarTable.calendarId })
40
+ .from(calendarTable)
41
+ .where(inList(calendarTable.calendarId, allIds))
42
+ .toSQL();
43
+ assert.equal(query.params.length, 1);
44
+ });
45
+
46
+ test("matches only the listed ids", async () => {
47
+ const rows = await db
48
+ .select({ calendarId: calendarTable.calendarId })
49
+ .from(calendarTable)
50
+ .where(inList(calendarTable.calendarId, ["calendar-3", "missing"]));
51
+ assert.deepEqual(rows, [{ calendarId: "calendar-3" }]);
52
+ });
53
+
54
+ test("matches nothing for an empty list", async () => {
55
+ const rows = await db
56
+ .select({ calendarId: calendarTable.calendarId })
57
+ .from(calendarTable)
58
+ .where(inList(calendarTable.calendarId, []));
59
+ assert.deepEqual(rows, []);
60
+ });
61
+ });
@@ -0,0 +1,14 @@
1
+ import { type Column, type SQL, sql } from "drizzle-orm";
2
+
3
+ export const inList = <T extends Column>(
4
+ column: T,
5
+ values: readonly T["_"]["data"][],
6
+ ): SQL =>
7
+ sql`${column} in (select value from json_each(${JSON.stringify(values)}))`;
8
+
9
+ export const pairInList = <A extends Column, B extends Column>(
10
+ first: A,
11
+ second: B,
12
+ pairs: readonly (readonly [A["_"]["data"], B["_"]["data"]])[],
13
+ ): SQL =>
14
+ sql`(${first}, ${second}) in (select value->>0, value->>1 from json_each(${JSON.stringify(pairs)}))`;
@@ -4,13 +4,14 @@ import type {
4
4
  IMessageFlagRepository,
5
5
  MessageFlagItem,
6
6
  } from "@remit/data-ports";
7
- import { and, eq, inArray } from "drizzle-orm";
7
+ import { and, eq } from "drizzle-orm";
8
8
  import type { Db } from "../db.js";
9
9
  import { NotFoundError } from "../error.js";
10
10
  import {
11
11
  type MessageDataSchema,
12
12
  messageFlagTable,
13
13
  } from "../schema/message-data.js";
14
+ import { inList } from "./in-list.js";
14
15
 
15
16
  type DB = Db<MessageDataSchema>;
16
17
 
@@ -58,7 +59,7 @@ export class DrizzleMessageFlagRepository implements IMessageFlagRepository {
58
59
  const rows = await this.db
59
60
  .select()
60
61
  .from(messageFlagTable)
61
- .where(inArray(messageFlagTable.messageFlagId, idOrIds));
62
+ .where(inList(messageFlagTable.messageFlagId, idOrIds));
62
63
  return rows.map(rowToMessageFlag);
63
64
  }
64
65
  const [row] = await this.db
@@ -79,7 +80,7 @@ export class DrizzleMessageFlagRepository implements IMessageFlagRepository {
79
80
  if (messageFlagIds.length === 0) return;
80
81
  await this.db
81
82
  .delete(messageFlagTable)
82
- .where(inArray(messageFlagTable.messageFlagId, messageFlagIds));
83
+ .where(inList(messageFlagTable.messageFlagId, messageFlagIds));
83
84
  }
84
85
 
85
86
  async getFlags(messageId: string): Promise<MessageFlagItem[]> {
@@ -155,7 +156,7 @@ export class DrizzleMessageFlagRepository implements IMessageFlagRepository {
155
156
  .where(
156
157
  and(
157
158
  eq(messageFlagTable.messageId, messageId),
158
- inArray(messageFlagTable.flagName, flagNames),
159
+ inList(messageFlagTable.flagName, flagNames),
159
160
  ),
160
161
  );
161
162
  }
@@ -3,10 +3,11 @@ import type {
3
3
  IMessageLabelRepository,
4
4
  MessageLabelItem,
5
5
  } from "@remit/data-ports";
6
- import { and, desc, eq, inArray } from "drizzle-orm";
6
+ import { and, desc, eq } from "drizzle-orm";
7
7
  import type { Db } from "../db.js";
8
8
  import { deterministicBase36Id } from "../id.js";
9
9
  import { messageLabelTable } from "../schema.js";
10
+ import { inList } from "./in-list.js";
10
11
 
11
12
  type DB = Db<Record<string, unknown>>;
12
13
 
@@ -82,7 +83,7 @@ export class MessageLabelRepo implements IMessageLabelRepository {
82
83
  const rows = await this.db
83
84
  .select()
84
85
  .from(messageLabelTable)
85
- .where(inArray(messageLabelTable.messageId, messageIds));
86
+ .where(inList(messageLabelTable.messageId, messageIds));
86
87
  return rows.map(rowToMessageLabel);
87
88
  }
88
89
 
@@ -45,6 +45,7 @@ import {
45
45
  } from "../schema/message-data.js";
46
46
  import { threadMessageTable } from "../schema/thread-message.js";
47
47
  import type { SearchIndex } from "../search/thread-message-search.js";
48
+ import { inList } from "./in-list.js";
48
49
  import {
49
50
  toBodyPartItem,
50
51
  toBodyPartParameterItem,
@@ -129,36 +130,36 @@ export function deleteMessageSubtree(
129
130
  return [
130
131
  db
131
132
  .delete(messageFlagTable)
132
- .where(and(inArray(messageFlagTable.messageId, messageIds), guard)),
133
+ .where(and(inList(messageFlagTable.messageId, messageIds), guard)),
133
134
  db
134
135
  .delete(messageReferenceTable)
135
- .where(and(inArray(messageReferenceTable.messageId, messageIds), guard)),
136
+ .where(and(inList(messageReferenceTable.messageId, messageIds), guard)),
136
137
  db
137
138
  .delete(envelopeAddressTable)
138
- .where(and(inArray(envelopeAddressTable.messageId, messageIds), guard)),
139
+ .where(and(inList(envelopeAddressTable.messageId, messageIds), guard)),
139
140
  db
140
141
  .delete(bodyPartParameterTable)
141
- .where(and(inArray(bodyPartParameterTable.messageId, messageIds), guard)),
142
+ .where(and(inList(bodyPartParameterTable.messageId, messageIds), guard)),
142
143
  db
143
144
  .delete(bodyPartStorageTable)
144
- .where(and(inArray(bodyPartStorageTable.messageId, messageIds), guard)),
145
+ .where(and(inList(bodyPartStorageTable.messageId, messageIds), guard)),
145
146
  db
146
147
  .delete(rawMessageStorageTable)
147
- .where(and(inArray(rawMessageStorageTable.messageId, messageIds), guard)),
148
+ .where(and(inList(rawMessageStorageTable.messageId, messageIds), guard)),
148
149
  db
149
150
  .delete(bodyPartTable)
150
- .where(and(inArray(bodyPartTable.messageId, messageIds), guard)),
151
+ .where(and(inList(bodyPartTable.messageId, messageIds), guard)),
151
152
  db
152
153
  .delete(bodyStructureRefetchTable)
153
154
  .where(
154
- and(inArray(bodyStructureRefetchTable.messageId, messageIds), guard),
155
+ and(inList(bodyStructureRefetchTable.messageId, messageIds), guard),
155
156
  ),
156
157
  db
157
158
  .delete(envelopeTable)
158
- .where(and(inArray(envelopeTable.messageId, messageIds), guard)),
159
+ .where(and(inList(envelopeTable.messageId, messageIds), guard)),
159
160
  db
160
161
  .delete(messageTable)
161
- .where(and(inArray(messageTable.messageId, messageIds), guard))
162
+ .where(and(inList(messageTable.messageId, messageIds), guard))
162
163
  .returning({ messageId: messageTable.messageId }),
163
164
  ];
164
165
  }
@@ -275,7 +276,7 @@ export class DrizzleMessageRepository implements IMessageRepository {
275
276
  const rows = await this.db
276
277
  .select()
277
278
  .from(messageTable)
278
- .where(inArray(messageTable.messageId, messageId));
279
+ .where(inList(messageTable.messageId, messageId));
279
280
  return rows.map(toMessageItem);
280
281
  }
281
282
 
@@ -547,18 +548,18 @@ export class DrizzleMessageRepository implements IMessageRepository {
547
548
  or(
548
549
  and(
549
550
  eq(messageTable.status, "moving"),
550
- inArray(messageTable.originalMailboxId, ids),
551
+ inList(messageTable.originalMailboxId, ids),
551
552
  isNotNull(messageTable.originalUid),
552
553
  ),
553
554
  and(
554
555
  eq(messageTable.status, "deleting"),
555
- inArray(messageTable.mailboxId, ids),
556
+ inList(messageTable.mailboxId, ids),
556
557
  ),
557
558
  and(
558
559
  eq(messageTable.status, "moving"),
559
560
  eq(messageTable.uid, 0),
560
561
  isNull(messageTable.originalUid),
561
- inArray(messageTable.mailboxId, ids),
562
+ inList(messageTable.mailboxId, ids),
562
563
  ),
563
564
  ),
564
565
  ),
@@ -5,9 +5,10 @@ import type {
5
5
  SyncWarningKey,
6
6
  } from "@remit/data-ports";
7
7
  import { deriveSyncWarningId } from "@remit/data-ports/id";
8
- import { and, count, desc, eq, gte, inArray, sql } from "drizzle-orm";
8
+ import { and, count, desc, eq, gte, sql } from "drizzle-orm";
9
9
  import type { Db } from "../db.js";
10
10
  import { syncWarningTable } from "../schema/sync-warning.js";
11
+ import { inList } from "./in-list.js";
11
12
 
12
13
  type DB = Db<Record<string, unknown>>;
13
14
 
@@ -71,6 +72,6 @@ export class SyncWarningRepo implements ISyncWarningRepository {
71
72
  if (syncWarningIds.length === 0) return;
72
73
  await this.db
73
74
  .delete(syncWarningTable)
74
- .where(inArray(syncWarningTable.syncWarningId, syncWarningIds));
75
+ .where(inList(syncWarningTable.syncWarningId, syncWarningIds));
75
76
  };
76
77
  }
@@ -34,6 +34,7 @@ import type {
34
34
  SearchIndex,
35
35
  SearchPosition,
36
36
  } from "../search/thread-message-search.js";
37
+ import { inList } from "./in-list.js";
37
38
  import {
38
39
  bodyMatch,
39
40
  fromMatch,
@@ -579,7 +580,7 @@ export class DrizzleThreadMessageRepository
579
580
  .where(
580
581
  and(
581
582
  eq(threadMessageTable.accountConfigId, accountConfigId),
582
- inArray(threadMessageTable.threadMessageId, threadMessageId),
583
+ inList(threadMessageTable.threadMessageId, threadMessageId),
583
584
  ),
584
585
  );
585
586
  return rows.map(toItem);
@@ -678,7 +679,7 @@ export class DrizzleThreadMessageRepository
678
679
  .where(
679
680
  and(
680
681
  eq(threadMessageTable.accountConfigId, accountConfigId),
681
- inArray(threadMessageTable.threadMessageId, ids),
682
+ inList(threadMessageTable.threadMessageId, ids),
682
683
  ),
683
684
  );
684
685
  await this.search.sync(ids);