@remit/backend 0.0.52 → 0.0.54
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,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
4
|
import { logger } from "@remit/logger-lambda";
|
|
5
5
|
import {
|
|
@@ -29,15 +29,29 @@ const CHECKPOINT_PATH =
|
|
|
29
29
|
process.env.LIST_ID_BACKFILL_CHECKPOINT_PATH ??
|
|
30
30
|
"/data/sqlite/list-id-backfill-checkpoint.json";
|
|
31
31
|
|
|
32
|
+
const isCheckpoint = (value: unknown): value is ListIdBackfillCheckpoint => {
|
|
33
|
+
if (typeof value !== "object" || value === null) return false;
|
|
34
|
+
if (!("accountConfigId" in value)) return false;
|
|
35
|
+
const candidate = value as Record<string, unknown>;
|
|
36
|
+
return (
|
|
37
|
+
typeof candidate.accountConfigId === "string" &&
|
|
38
|
+
(candidate.continuationToken === undefined ||
|
|
39
|
+
typeof candidate.continuationToken === "string")
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
32
43
|
const fileCheckpointStore: ListIdBackfillCheckpointStore = {
|
|
33
44
|
load: async () => {
|
|
34
45
|
if (!existsSync(CHECKPOINT_PATH)) return undefined;
|
|
35
46
|
const raw = await readFile(CHECKPOINT_PATH, "utf8");
|
|
36
|
-
|
|
47
|
+
const parsed: unknown = JSON.parse(raw);
|
|
48
|
+
return isCheckpoint(parsed) ? parsed : undefined;
|
|
37
49
|
},
|
|
38
50
|
save: async (checkpoint) => {
|
|
39
51
|
await mkdir(dirname(CHECKPOINT_PATH), { recursive: true });
|
|
40
|
-
|
|
52
|
+
const staging = `${CHECKPOINT_PATH}.writing`;
|
|
53
|
+
await writeFile(staging, JSON.stringify(checkpoint));
|
|
54
|
+
await rename(staging, CHECKPOINT_PATH);
|
|
41
55
|
},
|
|
42
56
|
clear: async () => {
|
|
43
57
|
await rm(CHECKPOINT_PATH, { force: true });
|
|
@@ -241,6 +241,41 @@ describe("executeThreadSearch", () => {
|
|
|
241
241
|
);
|
|
242
242
|
});
|
|
243
243
|
|
|
244
|
+
// #509: `limit` is a page size. A response that carries a page of rows and a
|
|
245
|
+
// count has to state how many messages match, not how many it handed back.
|
|
246
|
+
it("counts the whole match when rows are asked for as well", async () => {
|
|
247
|
+
const pageSize = 2;
|
|
248
|
+
const matches = 4753;
|
|
249
|
+
const windowCalls: number[] = [];
|
|
250
|
+
const client: ThreadSearchClient = {
|
|
251
|
+
threadMessage: {
|
|
252
|
+
async searchByMailboxWindow(_account, _mailbox, _search, options) {
|
|
253
|
+
windowCalls.push(options?.limit ?? 0);
|
|
254
|
+
return {
|
|
255
|
+
items: ACCOUNT_ROWS.slice(0, pageSize),
|
|
256
|
+
continuationToken: "next",
|
|
257
|
+
};
|
|
258
|
+
},
|
|
259
|
+
async countByMailbox() {
|
|
260
|
+
return matches;
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
message: { get: async () => [] },
|
|
264
|
+
address: { getAddress: async () => [] },
|
|
265
|
+
messageLabel: { listByMessageIds: async () => [] },
|
|
266
|
+
label: { listByAccountConfig: async () => [] },
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const response = await executeThreadSearch(client, ACCOUNT, MAILBOX, {
|
|
270
|
+
count: true,
|
|
271
|
+
limit: pageSize,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
assert.deepEqual(windowCalls, [pageSize]);
|
|
275
|
+
assert.equal(response.items?.length, pageSize);
|
|
276
|
+
assert.equal(response.count, matches);
|
|
277
|
+
});
|
|
278
|
+
|
|
244
279
|
it("projects category, so the DynamoDB port reads it with the row", () => {
|
|
245
280
|
assert.ok(buildSearchThreadsOptions({}).attributes.includes("category"));
|
|
246
281
|
assert.ok(buildListThreadsOptions({}).attributes.includes("category"));
|
package/src/handlers/thread.ts
CHANGED
|
@@ -131,7 +131,6 @@ export interface ThreadSearchClient extends EnrichClient {
|
|
|
131
131
|
mailboxId: string,
|
|
132
132
|
search: ThreadSearch,
|
|
133
133
|
options?: {
|
|
134
|
-
limit?: number;
|
|
135
134
|
excludeDeleted?: boolean;
|
|
136
135
|
order?: "asc" | "desc";
|
|
137
136
|
},
|
|
@@ -160,8 +159,12 @@ export type ThreadSearchParams = {
|
|
|
160
159
|
* Run a per-mailbox thread search: select the index/window read, optionally
|
|
161
160
|
* count, and resolve off-row criteria by enriching+filtering the window. The
|
|
162
161
|
* `results` toggle omits `items` (count-only) and `count` adds the match count.
|
|
163
|
-
*
|
|
164
|
-
*
|
|
162
|
+
*
|
|
163
|
+
* `count` names the whole match, never the page: a page size bounds the rows a
|
|
164
|
+
* response carries and has no bearing on how many messages match (#509). The
|
|
165
|
+
* one exception is an off-row criterion, which no Select:COUNT can see — those
|
|
166
|
+
* force a window read+enrich even in count-only mode, and the count that comes
|
|
167
|
+
* back is over the enriched window, which the API documents as such.
|
|
165
168
|
*/
|
|
166
169
|
export const executeThreadSearch = async (
|
|
167
170
|
client: ThreadSearchClient,
|
|
@@ -226,11 +229,6 @@ export const executeThreadSearch = async (
|
|
|
226
229
|
accountConfigId,
|
|
227
230
|
);
|
|
228
231
|
response.continuationToken = window.continuationToken;
|
|
229
|
-
// The window read already yielded the exact match set, so the count is
|
|
230
|
-
// its length — no separate Select:COUNT, and count == items.length by
|
|
231
|
-
// construction.
|
|
232
|
-
if (wantCount) response.count = response.items.length;
|
|
233
|
-
return response;
|
|
234
232
|
}
|
|
235
233
|
|
|
236
234
|
if (wantCount) {
|
|
@@ -238,7 +236,7 @@ export const executeThreadSearch = async (
|
|
|
238
236
|
accountConfigId,
|
|
239
237
|
mailboxId,
|
|
240
238
|
search,
|
|
241
|
-
{
|
|
239
|
+
{ excludeDeleted: true, order: params.order },
|
|
242
240
|
);
|
|
243
241
|
}
|
|
244
242
|
|