@remit/account-worker 0.0.14 → 0.0.16
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 +1 -1
- package/src/compose-relational.ts +7 -15
- package/src/config.ts +4 -4
- package/src/deletion-capabilities.ts +8 -17
- package/src/handlers/account-purge.test.ts +3 -3
- package/src/handlers/account-purge.ts +5 -5
- package/src/handlers/organize-job.ts +7 -2
- package/src/poller.ts +1 -1
package/package.json
CHANGED
|
@@ -2,7 +2,6 @@ import { rm } from "node:fs/promises";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import {
|
|
4
4
|
type CascadeDeleter,
|
|
5
|
-
createCascadeDeleter,
|
|
6
5
|
createSqliteCascadeDeleter,
|
|
7
6
|
} from "@remit/drizzle-service";
|
|
8
7
|
import type { Logger } from "@remit/logger-lambda";
|
|
@@ -30,22 +29,15 @@ const deleteStoragePrefix = async (
|
|
|
30
29
|
log.info({ keyPrefix, target }, "Filesystem storage prefix cleanup complete");
|
|
31
30
|
};
|
|
32
31
|
|
|
33
|
-
// The Drizzle cascade deleter is opened once, on first use, and reused
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
// does not open a database handle it will not use.
|
|
32
|
+
// The Drizzle cascade deleter is opened once, on first use, and reused: it
|
|
33
|
+
// opens the shared file at `SQLITE_DB_PATH` (the native binding loads there,
|
|
34
|
+
// hence async). Built lazily so the fanout worker — which signs out but never
|
|
35
|
+
// cascade-deletes — does not open a database handle it will not use.
|
|
38
36
|
let deleterPromise: Promise<CascadeDeleter> | undefined;
|
|
39
37
|
|
|
40
|
-
const buildDeleter = async (): Promise<CascadeDeleter> => {
|
|
41
|
-
if (process.env.DATA_BACKEND === "sqlite") {
|
|
42
|
-
return createSqliteCascadeDeleter(env.SQLITE_DB_PATH);
|
|
43
|
-
}
|
|
44
|
-
return createCascadeDeleter(env.PG_CONNECTION_URL);
|
|
45
|
-
};
|
|
46
|
-
|
|
47
38
|
const getDeleter = (): Promise<CascadeDeleter> => {
|
|
48
|
-
if (!deleterPromise)
|
|
39
|
+
if (!deleterPromise)
|
|
40
|
+
deleterPromise = createSqliteCascadeDeleter(env.SQLITE_DB_PATH);
|
|
49
41
|
return deleterPromise;
|
|
50
42
|
};
|
|
51
43
|
|
|
@@ -60,7 +52,7 @@ const getDeleter = (): Promise<CascadeDeleter> => {
|
|
|
60
52
|
* backend with no response cache (`deploy/vps/caddy/routes.caddy`), so there
|
|
61
53
|
* is nothing to invalidate.
|
|
62
54
|
* - `deleteStoragePrefix`: recursive filesystem removal.
|
|
63
|
-
* - `cascadeDelete`: the Drizzle cascade over
|
|
55
|
+
* - `cascadeDelete`: the Drizzle cascade over SQLite. Message
|
|
64
56
|
* subtrees emit `message.removed` outbox rows the search-index worker relays.
|
|
65
57
|
*/
|
|
66
58
|
export const buildRelationalDeletionCapabilities =
|
package/src/config.ts
CHANGED
|
@@ -52,10 +52,10 @@ export const graceSeconds = graceSecondsRaw
|
|
|
52
52
|
let cascadeServicesPromise: Promise<CascadeServices> | null = null;
|
|
53
53
|
|
|
54
54
|
// Every cascade service is a `RemitClient` repository, so the whole enumeration
|
|
55
|
-
// runs on whatever backend `getClient()`
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
55
|
+
// runs on whatever backend `getClient()` resolved — DynamoDB (ElectroDB) or
|
|
56
|
+
// SQLite (Drizzle). Filter/FilterAnchor/Label/MessageLabel (Smart Organize,
|
|
57
|
+
// RFC 034) are present on both via the client, so they need no
|
|
58
|
+
// backend-specific wiring here.
|
|
59
59
|
export const getCascadeServices = (): Promise<CascadeServices> => {
|
|
60
60
|
if (!cascadeServicesPromise) {
|
|
61
61
|
cascadeServicesPromise = getClient().then((remitClient) => ({
|
|
@@ -14,12 +14,11 @@ import type { CascadeEntity } from "./cascade.js";
|
|
|
14
14
|
* ElectroDB graph and lives outside this shared, open-core module; it is
|
|
15
15
|
* injected here through `setDeletionCapabilities` so that graph never reaches
|
|
16
16
|
* the relational tree.
|
|
17
|
-
* - `compose-relational.ts` (self-host stack, RFC 035/036)
|
|
18
|
-
* `DATA_BACKEND`: no-op sign-out
|
|
17
|
+
* - `compose-relational.ts` (self-host stack, RFC 035/036): no-op sign-out
|
|
19
18
|
* (deleting the AccountConfig severs the session's data resolution; there is
|
|
20
19
|
* no CDN to sign out of), no-op invalidation (Caddy proxies `/content/*`
|
|
21
20
|
* straight to the backend with no cache), filesystem prefix delete, and the
|
|
22
|
-
* Drizzle cascade over
|
|
21
|
+
* Drizzle cascade over SQLite.
|
|
23
22
|
*/
|
|
24
23
|
export interface DeletionCapabilities {
|
|
25
24
|
/**
|
|
@@ -49,18 +48,12 @@ export interface DeletionCapabilities {
|
|
|
49
48
|
deleteStoragePrefix(keyPrefix: string, log: Logger): Promise<void>;
|
|
50
49
|
/**
|
|
51
50
|
* Remove the enumerated rows in dependency order (children → parents). AWS:
|
|
52
|
-
* DynamoDB `BatchWriteItem`. Relational: Drizzle transaction
|
|
53
|
-
* SQLite). Excludes the AccountConfig row, which the caller deletes last
|
|
51
|
+
* DynamoDB `BatchWriteItem`. Relational: a Drizzle transaction. Excludes the AccountConfig row, which the caller deletes last
|
|
54
52
|
* through its repository as the cascade-in-progress marker.
|
|
55
53
|
*/
|
|
56
54
|
cascadeDelete(entities: CascadeEntity[], log: Logger): Promise<void>;
|
|
57
55
|
}
|
|
58
56
|
|
|
59
|
-
const isRelationalBackend = (): boolean => {
|
|
60
|
-
const backend = process.env.DATA_BACKEND;
|
|
61
|
-
return backend === "postgres" || backend === "sqlite";
|
|
62
|
-
};
|
|
63
|
-
|
|
64
57
|
let injectedCapabilities: DeletionCapabilities | null = null;
|
|
65
58
|
|
|
66
59
|
/**
|
|
@@ -77,18 +70,16 @@ export const setDeletionCapabilities = (
|
|
|
77
70
|
|
|
78
71
|
const buildDeletionCapabilitiesFromEnv =
|
|
79
72
|
async (): Promise<DeletionCapabilities> => {
|
|
80
|
-
if (
|
|
73
|
+
if (injectedCapabilities) return injectedCapabilities;
|
|
74
|
+
if (process.env.SQLITE_DB_PATH) {
|
|
81
75
|
const { buildRelationalDeletionCapabilities } = await import(
|
|
82
76
|
"./compose-relational.js"
|
|
83
77
|
);
|
|
84
78
|
return buildRelationalDeletionCapabilities();
|
|
85
79
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
return injectedCapabilities;
|
|
80
|
+
throw new Error(
|
|
81
|
+
"no deletion capabilities registered — register them with setDeletionCapabilities() from your composition root",
|
|
82
|
+
);
|
|
92
83
|
};
|
|
93
84
|
|
|
94
85
|
let capabilitiesPromise: Promise<DeletionCapabilities> | undefined;
|
|
@@ -106,18 +106,18 @@ describe("processAccountDataPurge", () => {
|
|
|
106
106
|
);
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
-
it("skips vector deletes on the
|
|
109
|
+
it("skips vector deletes on the relational backend, still enqueuing finalize", async () => {
|
|
110
110
|
const sent: Sent[] = [];
|
|
111
111
|
await processAccountDataPurge(
|
|
112
112
|
event,
|
|
113
113
|
noopLog,
|
|
114
|
-
baseDeps(sent, { dataBackend: "
|
|
114
|
+
baseDeps(sent, { dataBackend: "sqlite" }),
|
|
115
115
|
);
|
|
116
116
|
|
|
117
117
|
assert.equal(
|
|
118
118
|
sent.some((m) => m.queueUrl === "http://queue/search"),
|
|
119
119
|
false,
|
|
120
|
-
"no search-index enqueue on
|
|
120
|
+
"no search-index enqueue on the relational backend",
|
|
121
121
|
);
|
|
122
122
|
assert.deepEqual(
|
|
123
123
|
sent
|
|
@@ -196,11 +196,11 @@ export const processAccountDataPurge = async (
|
|
|
196
196
|
|
|
197
197
|
const messageIds = [...new Set(items.map((i) => i.messageId))];
|
|
198
198
|
|
|
199
|
-
// On
|
|
200
|
-
// message, which the
|
|
201
|
-
// here too would double the removal. On
|
|
202
|
-
// producer of the vector deletes (#457).
|
|
203
|
-
if (dataBackend !== "
|
|
199
|
+
// On the relational backend the destructive delete emits a `message.removed`
|
|
200
|
+
// outbox row per message, which the search-index worker relays as a
|
|
201
|
+
// search-index REMOVE; enqueuing here too would double the removal. On
|
|
202
|
+
// DynamoDB this fanout is the sole producer of the vector deletes (#457).
|
|
203
|
+
if (dataBackend !== "sqlite") {
|
|
204
204
|
await enqueueVectorDeletes(
|
|
205
205
|
sqsFor(searchIndexQueueUrl),
|
|
206
206
|
searchIndexQueueUrl,
|
|
@@ -39,14 +39,19 @@ export const processOrganizeJob = async (
|
|
|
39
39
|
|
|
40
40
|
try {
|
|
41
41
|
const predicate = predicateFromJob(job);
|
|
42
|
+
const matchDeps = buildOrganizeMatchDeps(client);
|
|
42
43
|
const { messageIds, semanticUnavailable } = await matchOrganize(
|
|
43
|
-
|
|
44
|
+
matchDeps,
|
|
44
45
|
accountConfigId,
|
|
45
46
|
predicate,
|
|
46
47
|
ORGANIZE_MATCH_LIMIT,
|
|
47
48
|
);
|
|
48
49
|
const { applied, failed } = await applyOrganize(
|
|
49
|
-
{
|
|
50
|
+
{
|
|
51
|
+
client,
|
|
52
|
+
moveService: buildOrganizeMoveService(client),
|
|
53
|
+
match: matchDeps,
|
|
54
|
+
},
|
|
50
55
|
accountConfigId,
|
|
51
56
|
messageIds,
|
|
52
57
|
predicate,
|
package/src/poller.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { fanoutHandler, finalizeHandler } from "./index.js";
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Production queue poller. No e2e shim exists for account-worker today —
|
|
8
|
-
* the deletion cascade is not exercised on the
|
|
8
|
+
* the deletion cascade is not exercised on the compose stack in
|
|
9
9
|
* CI (see AGENTS.md worker roster notes). This is the standalone
|
|
10
10
|
* production entrypoint for the dedicated image.
|
|
11
11
|
*
|