@remit/data-ports 0.0.19 → 0.0.21

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.19",
3
+ "version": "0.0.21",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -85,15 +85,20 @@ export function labelRepositoryConformance(
85
85
  const accountConfigId = harness.makeId();
86
86
  const other = harness.makeId();
87
87
 
88
- const first = await repo.create({ accountConfigId, name: "First" });
89
- const second = await repo.create({ accountConfigId, name: "Second" });
88
+ // Created concurrently so their `createdAt` millisecond can tie (as it
89
+ // reliably does against the synchronous sqlite driver): the keyset's
90
+ // `labelId` tiebreak then decides order, so this asserts pagination is
91
+ // exhaustive and duplicate-free rather than a specific creation order.
92
+ const created = await Promise.all([
93
+ repo.create({ accountConfigId, name: "First" }),
94
+ repo.create({ accountConfigId, name: "Second" }),
95
+ ]);
90
96
  await repo.create({ accountConfigId: other, name: "Foreign" });
91
97
 
92
98
  const page1 = await repo.listPageByAccountConfig(accountConfigId, {
93
99
  limit: 1,
94
100
  });
95
101
  assert.equal(page1.items.length, 1);
96
- assert.equal(page1.items[0]?.labelId, first.labelId);
97
102
  assert.ok(page1.continuationToken);
98
103
 
99
104
  const page2 = await repo.listPageByAccountConfig(accountConfigId, {
@@ -101,8 +106,20 @@ export function labelRepositoryConformance(
101
106
  continuationToken: page1.continuationToken,
102
107
  });
103
108
  assert.equal(page2.items.length, 1);
104
- assert.equal(page2.items[0]?.labelId, second.labelId);
105
109
  assert.equal(page2.continuationToken, undefined);
110
+
111
+ const seen = new Set([
112
+ ...page1.items.map((label) => label.labelId),
113
+ ...page2.items.map((label) => label.labelId),
114
+ ]);
115
+ assert.equal(
116
+ seen.size,
117
+ created.length,
118
+ "every label is paged exactly once",
119
+ );
120
+ for (const label of created) {
121
+ assert.ok(seen.has(label.labelId));
122
+ }
106
123
  });
107
124
 
108
125
  test("delete removes the row", async () => {
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ export type { IAddressRepository } from "./interfaces/address.js";
6
6
  export type { IEnvelopeRepository } from "./interfaces/envelope.js";
7
7
  export type { IFilterRepository } from "./interfaces/filter.js";
8
8
  export type { IFilterAnchorRepository } from "./interfaces/filter-anchor.js";
9
+ export type { IFilterAnchorTransaction } from "./interfaces/filter-anchor-transaction.js";
9
10
  export type { ILabelRepository } from "./interfaces/label.js";
10
11
  export type { IMailboxRepository } from "./interfaces/mailbox.js";
11
12
  export type { IMailboxLockRepository } from "./interfaces/mailbox-lock.js";
@@ -0,0 +1,23 @@
1
+ import type {
2
+ CreateFilterAnchorInput,
3
+ CreateFilterInput,
4
+ FilterItem,
5
+ } from "../types.js";
6
+
7
+ /**
8
+ * Atomically creates a `Filter` row and its optional sibling `FilterAnchor`
9
+ * row (#351). Without this, the two writes ran sequentially: a failure on the
10
+ * second left a `Filter` durably marked `hasAnchor: true` with no matching
11
+ * `FilterAnchor` row — a filter that looks normal but silently matches
12
+ * nothing, forever, with no repair path (the anchor message id is never
13
+ * retained once `createFilterWithAnchor` returns).
14
+ *
15
+ * `anchor` omits `filterId` — it is not known until the `Filter` row is
16
+ * created inside the same transaction.
17
+ */
18
+ export interface IFilterAnchorTransaction {
19
+ createWithAnchor(
20
+ filter: CreateFilterInput,
21
+ anchor: Omit<CreateFilterAnchorInput, "filterId"> | null,
22
+ ): Promise<FilterItem>;
23
+ }