@remit/drizzle-service 0.0.71 → 0.0.73

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.71",
3
+ "version": "0.0.73",
4
4
  "description": "Drizzle ORM service over SQLite",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -18,6 +18,7 @@ import {
18
18
  desc,
19
19
  eq,
20
20
  getTableColumns,
21
+ gt,
21
22
  inArray,
22
23
  type SQL,
23
24
  sql,
@@ -748,6 +749,40 @@ export class AddressRepo implements IAddressRepository {
748
749
  );
749
750
  }
750
751
 
752
+ async listAllByAccountConfigPage(input: {
753
+ accountConfigId: string;
754
+ cursor?: string;
755
+ limit?: number;
756
+ }): Promise<ResultList<AddressItem>> {
757
+ const { accountConfigId, cursor, limit = 100 } = input;
758
+ const afterAddressId = cursor
759
+ ? (decodeToken(cursor).addressId as string)
760
+ : undefined;
761
+
762
+ const rows = await this.db
763
+ .select()
764
+ .from(addressTable)
765
+ .where(
766
+ and(
767
+ eq(addressTable.accountConfigId, accountConfigId),
768
+ afterAddressId
769
+ ? gt(addressTable.addressId, afterAddressId)
770
+ : undefined,
771
+ ),
772
+ )
773
+ .orderBy(asc(addressTable.addressId))
774
+ .limit(limit + 1);
775
+
776
+ const hasMore = rows.length > limit;
777
+ const items = rows.slice(0, limit).map(rowToAddress);
778
+ const lastItem = items[items.length - 1];
779
+ return resultList(
780
+ items,
781
+ limit,
782
+ hasMore && lastItem ? { addressId: lastItem.addressId } : undefined,
783
+ );
784
+ }
785
+
751
786
  async createEnvelopeAddress(
752
787
  input: CreateEnvelopeAddressInput,
753
788
  ): Promise<EnvelopeAddressItem> {
@@ -224,6 +224,59 @@ describe("OutboxMessageRepo", () => {
224
224
  await repo.deleteMany(accountConfigId, created);
225
225
  });
226
226
 
227
+ test("listByAccounts pages one ordering over every account (#1013)", async () => {
228
+ const accountConfigId = randomId();
229
+ const first = randomId();
230
+ const second = randomId();
231
+ const created: string[] = [];
232
+ const byAccount = new Map<string, string[]>([
233
+ [first, []],
234
+ [second, []],
235
+ ]);
236
+ for (let i = 0; i < 6; i++) {
237
+ const accountId = i % 2 === 0 ? first : second;
238
+ const msg = await repo.create(
239
+ makeOutboxInput(accountId, accountConfigId),
240
+ );
241
+ created.push(msg.outboxMessageId);
242
+ byAccount.get(accountId)?.push(msg.outboxMessageId);
243
+ }
244
+
245
+ const wholeList = await repo.listByAccounts([first, second]);
246
+ const wholeIds = wholeList.items.map((m) => m.outboxMessageId);
247
+ for (const ids of byAccount.values()) {
248
+ for (const id of ids) {
249
+ assert.ok(wholeIds.includes(id), "every account's rows come back");
250
+ }
251
+ }
252
+
253
+ const seen: string[] = [];
254
+ let continuationToken: string | undefined;
255
+ let pages = 0;
256
+ do {
257
+ const page = await repo.listByAccounts([first, second], {
258
+ limit: 2,
259
+ continuationToken,
260
+ });
261
+ seen.push(...page.items.map((m) => m.outboxMessageId));
262
+ continuationToken = page.continuationToken;
263
+ pages++;
264
+ assert.ok(pages < 10, "pagination must terminate");
265
+ } while (continuationToken);
266
+
267
+ assert.equal(seen.length, 6, "every row returned exactly once");
268
+ assert.equal(new Set(seen).size, 6, "no duplicates across pages");
269
+ assert.deepEqual([...seen].sort(), [...created].sort(), "no gaps");
270
+
271
+ await repo.deleteMany(accountConfigId, created);
272
+ });
273
+
274
+ test("listByAccounts answers no accounts with no rows", async () => {
275
+ const list = await repo.listByAccounts([]);
276
+ assert.deepEqual(list.items, []);
277
+ assert.equal(list.continuationToken, undefined);
278
+ });
279
+
227
280
  test("listQueued returns only queued messages", async () => {
228
281
  const accountId = randomId();
229
282
  const accountConfigId = randomId();
@@ -272,8 +272,17 @@ export class OutboxMessageRepo implements IOutboxMessageRepository {
272
272
  async listByAccount(
273
273
  accountId: string,
274
274
  options?: { limit?: number; continuationToken?: string },
275
+ ): Promise<ResultList<OutboxMessageItem>> {
276
+ return this.listByAccounts([accountId], options);
277
+ }
278
+
279
+ async listByAccounts(
280
+ accountIds: string[],
281
+ options?: { limit?: number; continuationToken?: string },
275
282
  ): Promise<ResultList<OutboxMessageItem>> {
276
283
  const limit = options?.limit ?? 100;
284
+ if (accountIds.length === 0) return resultList([], limit);
285
+
277
286
  const cursor = options?.continuationToken
278
287
  ? decodeToken(options.continuationToken)
279
288
  : undefined;
@@ -289,7 +298,7 @@ export class OutboxMessageRepo implements IOutboxMessageRepository {
289
298
  .from(outboxMessageTable)
290
299
  .where(
291
300
  and(
292
- eq(outboxMessageTable.accountId, accountId),
301
+ inArray(outboxMessageTable.accountId, accountIds),
293
302
  after
294
303
  ? or(
295
304
  lt(outboxMessageTable.createdAt, after.createdAt),
@@ -148,6 +148,27 @@ describe("DrizzleMessageRepository (sqlite)", () => {
148
148
  assert.equal(result.item.messageId, MESSAGE_ID);
149
149
  });
150
150
 
151
+ // The state every inbound sync row is born in, and the one the re-point gate
152
+ // in mailbox-service has to accept (#1096): the sync path supplies no
153
+ // syncStatus and nothing later promotes what the repository writes here.
154
+ test("upsertWithStatus writes pending when the caller names no syncStatus", async () => {
155
+ const inboundId = "00000000-0000-0000-2222-000000000009";
156
+ const { item, created } = await repo.upsertWithStatus({
157
+ messageId: inboundId,
158
+ mailboxId: MAILBOX_ID,
159
+ uid: 43,
160
+ sequenceNumber: 2,
161
+ rfc822Size: 1024,
162
+ internalDate: NOW,
163
+ envelopeId: deriveEnvelopeId(inboundId),
164
+ rootBodyPartId: deriveRootBodyPartId(inboundId),
165
+ });
166
+
167
+ assert.equal(created, true);
168
+ assert.equal(item.syncStatus, "pending");
169
+ assert.equal(item.status, "active");
170
+ });
171
+
151
172
  test("delete removes the message and appends a removal outbox row", async () => {
152
173
  await repo.delete(MESSAGE_ID);
153
174
  await assert.rejects(() => repo.get(MESSAGE_ID));