@remit/drizzle-service 0.0.35 → 0.0.36

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.
Files changed (60) hide show
  1. package/package.json +3 -10
  2. package/src/db.ts +6 -6
  3. package/src/error.ts +2 -5
  4. package/src/index.ts +0 -1
  5. package/src/repair/thread-message-category-contract.ts +2 -4
  6. package/src/repair/thread-message-category.sqlite.test.ts +1 -2
  7. package/src/repair/thread-message-category.ts +15 -25
  8. package/src/repos/cascade-delete.ts +8 -22
  9. package/src/repos/category-fixture.ts +2 -19
  10. package/src/repos/filter-anchor.ts +4 -4
  11. package/src/repos/filter.ts +3 -3
  12. package/src/repos/i4-account-config.ts +2 -2
  13. package/src/repos/i4-account-export-request.ts +2 -2
  14. package/src/repos/i4-account-setting.ts +2 -2
  15. package/src/repos/i4-account.ts +2 -2
  16. package/src/repos/i4-mailbox-lock.ts +2 -2
  17. package/src/repos/i4-mailbox-special-use.ts +2 -2
  18. package/src/repos/i4-mailbox.ts +2 -2
  19. package/src/repos/i4-message-flag-push.test.ts +1 -1
  20. package/src/repos/i4-message-flag-push.ts +3 -3
  21. package/src/repos/i4-message-placement-move.test.ts +1 -1
  22. package/src/repos/i4-message-placement-move.ts +4 -3
  23. package/src/repos/i4-organize-job-request.ts +2 -2
  24. package/src/repos/i4-outbox-message.ts +2 -2
  25. package/src/repos/label.ts +2 -2
  26. package/src/repos/message-flag.ts +2 -2
  27. package/src/repos/message-label.ts +2 -2
  28. package/src/repos/message.ts +8 -10
  29. package/src/repos/quarantine.ts +2 -2
  30. package/src/repos/test-helpers.ts +11 -213
  31. package/src/repos/thread-message.test.ts +35 -93
  32. package/src/repos/thread-message.ts +7 -19
  33. package/src/repos/thread-search-predicates.ts +13 -41
  34. package/src/repos/unit-of-work.ts +1 -1
  35. package/src/schema/i4-account-config.ts +1 -1
  36. package/src/schema/i4-account-export-request.ts +1 -1
  37. package/src/schema/i4-account-setting.ts +1 -1
  38. package/src/schema/i4-address.ts +1 -1
  39. package/src/schema/i4-mailbox-lock.ts +1 -1
  40. package/src/schema/i4-mailbox.ts +1 -1
  41. package/src/schema/i4-message-flag-push.ts +1 -1
  42. package/src/schema/i4-message-placement-move.ts +1 -1
  43. package/src/schema/i4-organize-job-request.ts +1 -1
  44. package/src/schema/i4-outbox-message.ts +1 -1
  45. package/src/schema/message-data.ts +1 -1
  46. package/src/schema/outbox.ts +13 -56
  47. package/src/schema/quarantine.ts +1 -1
  48. package/src/schema/thread-message.ts +1 -1
  49. package/src/schema-full-sqlite.ts +8 -6
  50. package/src/schema.ts +4 -4
  51. package/src/sqlite-client.ts +4 -5
  52. package/src/test-db-sqlite.ts +4 -9
  53. package/src/test-db.ts +11 -66
  54. package/src/tx.ts +6 -12
  55. package/drizzle.config.ts +0 -15
  56. package/src/dialect.ts +0 -13
  57. package/src/repair/thread-message-category.test.ts +0 -229
  58. package/src/repos/thread-message-category.test.ts +0 -156
  59. package/src/schema/active-entities.ts +0 -19
  60. package/src/schema-full.ts +0 -16
@@ -1,34 +1,14 @@
1
1
  import { type SQL, sql } from "drizzle-orm";
2
- import { isSqlite } from "../dialect.js";
3
2
 
4
3
  // Accent- and case-insensitive substring match over the whole mailbox, isolated
5
- // here as the one text-search seam that genuinely differs by dialect (RFC 036
6
- // D1). The subject and sender predicates match the DynamoDB `contains()`
7
- // substring contract: LIKE metacharacters (`\`, `%`, `_`) are escaped in JS so
8
- // the needle arrives as bind-parameter text, and the escaped form is treated
9
- // literally.
4
+ // here as the one text-search seam whose behaviour is engine-specific. The
5
+ // subject and sender predicates match the `contains()` substring contract:
6
+ // LIKE metacharacters (`\`, `%`, `_`) are escaped in JS so the needle arrives as
7
+ // bind-parameter text, and the escaped form is treated literally.
10
8
 
11
9
  const escapeLike = (term: string): string => term.replace(/[\\%_]/g, "\\$&");
12
10
 
13
- // ─── Postgres ────────────────────────────────────────────────────────────────
14
- // The folded expressions must reproduce the indexed expressions in
15
- // npm-scripts/pg-search-index.sql exactly (immutable unaccent + lower over the
16
- // coalesced text) so the planner uses the trigram GIN indexes. Matching runs
17
- // over the whole mailbox — Postgres indexes the text, so there is no
18
- // recent-window read bound.
19
- const PG_SUBJECT_FOLDED = sql`remit_immutable_unaccent(lower(coalesce(subject, '')))`;
20
- const PG_FROM_FOLDED = sql`remit_immutable_unaccent(lower(coalesce(from_name, '') || ' ' || coalesce(from_email, '')))`;
21
-
22
- const pgLikePattern = (term: string): SQL =>
23
- sql`'%' || remit_immutable_unaccent(lower(${escapeLike(term)})) || '%'`;
24
-
25
- const pgSubjectMatch = (term: string): SQL =>
26
- sql`${PG_SUBJECT_FOLDED} like ${pgLikePattern(term)}`;
27
- const pgFromMatch = (term: string): SQL =>
28
- sql`${PG_FROM_FOLDED} like ${pgLikePattern(term)}`;
29
-
30
- // ─── SQLite ──────────────────────────────────────────────────────────────────
31
- // Text search on SQLite is the external-content FTS5 trigram index that
11
+ // Text search is the external-content FTS5 trigram index that
32
12
  // npm-scripts/sqlite-search-index.sql installs (RFC 036 D4): `thread_message_fts`
33
13
  // indexes the folded subject and sender, and MATCH is an accent- and
34
14
  // case-insensitive substring search (the tokenizer folds both sides, so the
@@ -39,7 +19,7 @@ const pgFromMatch = (term: string): SQL =>
39
19
  // falls back to the unindexed folded LIKE scan D4 names — lower() both sides,
40
20
  // substring-match, `escape '\'` making the JS-escaped metacharacters literal.
41
21
  // It is case-insensitive for ASCII and does not fold diacritics; the accepted
42
- // per-target difference from Postgres `unaccent`.
22
+ // difference between a short term and an indexed one (contract C10).
43
23
 
44
24
  // FTS5 treats bare query text as its match grammar (AND/OR/NEAR/`*`/`-`/`:`), so
45
25
  // wrap the term as a double-quoted string literal — doubling embedded quotes —
@@ -54,26 +34,18 @@ const isTrigramIndexable = (term: string): boolean => [...term].length >= 3;
54
34
  const ftsRowidMatch = (matchExpr: string): SQL =>
55
35
  sql`"thread_message"."rowid" in (select "rowid" from "thread_message_fts" where "thread_message_fts" match ${matchExpr})`;
56
36
 
57
- const SQLITE_SUBJECT_FOLDED = sql`lower(coalesce(subject, ''))`;
58
- const SQLITE_FROM_FOLDED = sql`lower(coalesce(from_name, '') || ' ' || coalesce(from_email, ''))`;
37
+ const SUBJECT_FOLDED = sql`lower(coalesce(subject, ''))`;
38
+ const FROM_FOLDED = sql`lower(coalesce(from_name, '') || ' ' || coalesce(from_email, ''))`;
59
39
 
60
- const sqliteLikePattern = (term: string): SQL =>
40
+ const likePattern = (term: string): SQL =>
61
41
  sql`'%' || lower(${escapeLike(term)}) || '%'`;
62
42
 
63
- const sqliteSubjectMatch = (term: string): SQL =>
43
+ export const subjectMatch = (term: string): SQL =>
64
44
  isTrigramIndexable(term)
65
45
  ? ftsRowidMatch(`subject : ${ftsPhrase(term)}`)
66
- : sql`${SQLITE_SUBJECT_FOLDED} like ${sqliteLikePattern(term)} escape '\\'`;
46
+ : sql`${SUBJECT_FOLDED} like ${likePattern(term)} escape '\\'`;
67
47
 
68
- const sqliteFromMatch = (term: string): SQL =>
48
+ export const fromMatch = (term: string): SQL =>
69
49
  isTrigramIndexable(term)
70
50
  ? ftsRowidMatch(`sender : ${ftsPhrase(term)}`)
71
- : sql`${SQLITE_FROM_FOLDED} like ${sqliteLikePattern(term)} escape '\\'`;
72
-
73
- // ─── Dialect selection ───────────────────────────────────────────────────────
74
-
75
- export const subjectMatch = (term: string): SQL =>
76
- isSqlite() ? sqliteSubjectMatch(term) : pgSubjectMatch(term);
77
-
78
- export const fromMatch = (term: string): SQL =>
79
- isSqlite() ? sqliteFromMatch(term) : pgFromMatch(term);
51
+ : sql`${FROM_FOLDED} like ${likePattern(term)} escape '\\'`;
@@ -8,7 +8,7 @@ import { DrizzleMessageRepository } from "./message.js";
8
8
  import { DrizzleThreadMessageRepository } from "./thread-message.js";
9
9
 
10
10
  /**
11
- * Runs a write set inside a single Postgres transaction. The repositories handed
11
+ * Runs a write set inside a single transaction. The repositories handed
12
12
  * to the callback are bound to that transaction, so the data rows and the
13
13
  * transactional-outbox rows the message write appends commit atomically — a
14
14
  * throw anywhere rolls the whole set back, outbox included.
@@ -1,4 +1,4 @@
1
- import { entities } from "./active-entities.js";
1
+ import * as entities from "@remit/drizzle-sqlite-schema";
2
2
 
3
3
  export const accountConfigTable = entities.accountConfigs;
4
4
  export const accountTable = entities.accounts;
@@ -1,3 +1,3 @@
1
- import { entities } from "./active-entities.js";
1
+ import * as entities from "@remit/drizzle-sqlite-schema";
2
2
 
3
3
  export const accountExportRequestTable = entities.accountExportRequests;
@@ -1,3 +1,3 @@
1
- import { entities } from "./active-entities.js";
1
+ import * as entities from "@remit/drizzle-sqlite-schema";
2
2
 
3
3
  export const accountSettingTable = entities.accountSettings;
@@ -1,3 +1,3 @@
1
- import { entities } from "./active-entities.js";
1
+ import * as entities from "@remit/drizzle-sqlite-schema";
2
2
 
3
3
  export const addressTable = entities.addresses;
@@ -1,3 +1,3 @@
1
- import { entities } from "./active-entities.js";
1
+ import * as entities from "@remit/drizzle-sqlite-schema";
2
2
 
3
3
  export const mailboxLockTable = entities.mailboxLocks;
@@ -1,4 +1,4 @@
1
- import { entities } from "./active-entities.js";
1
+ import * as entities from "@remit/drizzle-sqlite-schema";
2
2
 
3
3
  export const mailboxTable = entities.mailboxes;
4
4
  export const mailboxSpecialUseTable = entities.mailboxSpecialUseEntries;
@@ -1,3 +1,3 @@
1
- import { entities } from "./active-entities.js";
1
+ import * as entities from "@remit/drizzle-sqlite-schema";
2
2
 
3
3
  export const messageFlagPushTable = entities.messageFlagPushes;
@@ -1,3 +1,3 @@
1
- import { entities } from "./active-entities.js";
1
+ import * as entities from "@remit/drizzle-sqlite-schema";
2
2
 
3
3
  export const messagePlacementMoveTable = entities.messagePlacementMoves;
@@ -1 +1 @@
1
- export { organizeJobRequests as organizeJobRequestTable } from "@remit/drizzle-pg-schema";
1
+ export { organizeJobRequests as organizeJobRequestTable } from "@remit/drizzle-sqlite-schema";
@@ -1,3 +1,3 @@
1
- import { entities } from "./active-entities.js";
1
+ import * as entities from "@remit/drizzle-sqlite-schema";
2
2
 
3
3
  export const outboxMessageTable = entities.outboxMessages;
@@ -1,4 +1,4 @@
1
- import { entities } from "./active-entities.js";
1
+ import * as entities from "@remit/drizzle-sqlite-schema";
2
2
  import { outboxTable } from "./outbox.js";
3
3
 
4
4
  export { outboxTable };
@@ -1,74 +1,31 @@
1
1
  import { sql } from "drizzle-orm";
2
- import {
3
- bigint,
4
- jsonb,
5
- index as pgIndex,
6
- pgTable,
7
- text as pgText,
8
- timestamp,
9
- uuid,
10
- } from "drizzle-orm/pg-core";
11
- import {
12
- index as sqliteIndex,
13
- integer as sqliteInteger,
14
- sqliteTable,
15
- text as sqliteText,
16
- } from "drizzle-orm/sqlite-core";
17
- import { isSqlite } from "../dialect.js";
2
+ import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
18
3
 
19
4
  /**
20
5
  * Transactional outbox. It has no TypeSpec entity — it is infrastructure for
21
6
  * the search-index worker (append a row per body change / move, drain by id,
22
7
  * mark `processed_at`). The partial index selects unprocessed rows for the
23
- * boot-time backstop scan (Postgres) and the short-cadence poll (SQLite,
24
- * RFC 036 D2). It is hand-written per dialect because the two column-builder
25
- * sets share no surface; both keep identical column names so the repos and the
26
- * drain logic read the same rows on either backend.
8
+ * short-cadence poll (RFC 036 D2).
27
9
  *
28
- * Both raw tables are exported for committed-migration generation (schema-full
29
- * per dialect). The runtime `outboxTable` is the dialect-selected one, cast to
30
- * the Postgres type so the repos keep one static shape (RFC 036 D1).
10
+ * Exported raw for committed-migration generation (schema-full-sqlite) as well
11
+ * as for the repos and the drain logic.
31
12
  */
32
- export const pgOutboxTable = pgTable(
13
+ export const outboxTable = sqliteTable(
33
14
  "outbox",
34
15
  {
35
- id: uuid("id").primaryKey(),
36
- messageId: pgText("message_id").notNull(),
37
- event: pgText("event").notNull(),
38
- payload: jsonb("payload").notNull(),
39
- createdAt: timestamp("created_at", { withTimezone: true })
40
- .defaultNow()
41
- .notNull(),
42
- processedAt: bigint("processed_at", { mode: "number" }),
43
- },
44
- (t) => [
45
- pgIndex("outbox_message_id_idx").on(t.messageId),
46
- pgIndex("outbox_unprocessed_idx")
47
- .on(t.createdAt)
48
- .where(sql`${t.processedAt} IS NULL`),
49
- ],
50
- );
51
-
52
- export const sqliteOutboxTable = sqliteTable(
53
- "outbox",
54
- {
55
- id: sqliteText("id").primaryKey(),
56
- messageId: sqliteText("message_id").notNull(),
57
- event: sqliteText("event").notNull(),
58
- payload: sqliteText("payload", { mode: "json" }).notNull(),
59
- createdAt: sqliteInteger("created_at", { mode: "timestamp_ms" })
16
+ id: text("id").primaryKey(),
17
+ messageId: text("message_id").notNull(),
18
+ event: text("event").notNull(),
19
+ payload: text("payload", { mode: "json" }).notNull(),
20
+ createdAt: integer("created_at", { mode: "timestamp_ms" })
60
21
  .$defaultFn(() => new Date())
61
22
  .notNull(),
62
- processedAt: sqliteInteger("processed_at", { mode: "number" }),
23
+ processedAt: integer("processed_at", { mode: "number" }),
63
24
  },
64
25
  (t) => [
65
- sqliteIndex("outbox_message_id_idx").on(t.messageId),
66
- sqliteIndex("outbox_unprocessed_idx")
26
+ index("outbox_message_id_idx").on(t.messageId),
27
+ index("outbox_unprocessed_idx")
67
28
  .on(t.createdAt)
68
29
  .where(sql`${t.processedAt} IS NULL`),
69
30
  ],
70
31
  );
71
-
72
- export const outboxTable: typeof pgOutboxTable = isSqlite()
73
- ? (sqliteOutboxTable as unknown as typeof pgOutboxTable)
74
- : pgOutboxTable;
@@ -1,3 +1,3 @@
1
- import { entities } from "./active-entities.js";
1
+ import * as entities from "@remit/drizzle-sqlite-schema";
2
2
 
3
3
  export const quarantineTable = entities.quarantines;
@@ -1,3 +1,3 @@
1
- import { entities } from "./active-entities.js";
1
+ import * as entities from "@remit/drizzle-sqlite-schema";
2
2
 
3
3
  export const threadMessageTable = entities.threadMessages;
@@ -2,10 +2,12 @@
2
2
  // (RFC 036 D5) — consumed by packages/migrate/drizzle.entities.sqlite.config.ts
3
3
  // and the drift guard (npm-scripts/check-vps-migrations.mjs), never at runtime.
4
4
  //
5
- // The SQLite twin of schema-full.ts: the sqlite-dialect entity package
6
- // wholesale (`sqliteTable`/`text(json)`/`integer`) plus the raw sqlite outbox
7
- // infra table. Kept separate from the runtime facade (../schema/active-entities)
8
- // because drizzle-kit's `generate --dialect sqlite` needs the real sqlite table
9
- // objects, not the pg-cast the repos consume.
5
+ // It pulls the generated entity package in wholesale, so a new TypeSpec entity
6
+ // flows into the committed migration with nothing to hand-maintain. The only
7
+ // addition is the `outbox` infra table, which has no entity.
8
+ //
9
+ // schema.ts stays the app/dev surface (single `*Table` alias per table, what
10
+ // the repos and `pushSchema` need); this file exposes canonical names, so it
11
+ // must not be fed to `pushSchema` alongside schema.ts (duplicate index names).
10
12
  export * from "@remit/drizzle-sqlite-schema";
11
- export { sqliteOutboxTable as outboxTable } from "./schema/outbox.js";
13
+ export { outboxTable } from "./schema/outbox.js";
package/src/schema.ts CHANGED
@@ -2,11 +2,11 @@
2
2
  // the names the repos and `pushSchema` (test-db.ts) consume. Each table appears
3
3
  // exactly once here — `pushSchema` registers a table's indexes per exported
4
4
  // binding, so exposing one table under two names creates duplicate index names
5
- // and breaks `apply()`. The committed-migration `generate` reads schema-full.ts
6
- // instead (the entity package wholesale), so the migration is driven by the
7
- // entities, not by this hand-maintained alias list.
5
+ // and breaks `apply()`. The committed-migration `generate` reads
6
+ // schema-full-sqlite.ts instead (the entity package wholesale), so the
7
+ // migration is driven by the entities, not by this hand-maintained alias list.
8
8
 
9
- import { entities } from "./schema/active-entities.js";
9
+ import * as entities from "@remit/drizzle-sqlite-schema";
10
10
 
11
11
  export const filterAnchorTable = entities.filterAnchors;
12
12
  export const filterTable = entities.filters;
@@ -12,10 +12,9 @@ import { serializeSqliteWrites } from "./tx.js";
12
12
  // serialization (RFC 036 D3). `run`/`transaction`/reads pass through by design —
13
13
  // see the wrapper's comment.
14
14
  //
15
- // better-sqlite3 and its drizzle driver are imported dynamically so the Postgres
16
- // path never loads the native binding, and so the whole module stays out of the
17
- // DynamoDB Lambda bundle (this package is `external` there — see
18
- // remit-backend/src/service/dynamodb.ts).
15
+ // better-sqlite3 and its drizzle driver are imported dynamically so the whole
16
+ // module stays out of the DynamoDB Lambda bundle (this package is `external`
17
+ // there — see remit-backend/src/service/data-client.ts).
19
18
 
20
19
  export interface SqliteClientOptions {
21
20
  filename: string;
@@ -41,7 +40,7 @@ export async function createSqliteDatabase<
41
40
  sqlite.pragma("synchronous = NORMAL");
42
41
  sqlite.pragma("foreign_keys = ON");
43
42
 
44
- const base = drizzle(sqlite, { schema }) as unknown as Db<TSchema>;
43
+ const base: Db<TSchema> = drizzle(sqlite, { schema });
45
44
 
46
45
  return {
47
46
  db: serializeSqliteWrites(base),
@@ -12,14 +12,9 @@ const searchIndexDdl = (): string =>
12
12
  "utf8",
13
13
  );
14
14
 
15
- // SQLite counterpart of repos/test-helpers.ts's embedded-Postgres harness
16
- // (RFC 036 D1). A real better-sqlite3 database (in-memory by default) with the
17
- // schema pushed from the drizzle table objects the sqlite `pushSchema` — so a
18
- // repo runs against the exact dialect it ships on, no hand-maintained DDL.
19
- //
20
- // The tests that use it run in a `DATA_BACKEND=sqlite` process (see
21
- // test:run:sqlite), so the schema facades resolve to the sqlite tables and the
22
- // repos take the sqlite transaction / predicate paths.
15
+ // A real better-sqlite3 database (in-memory by default) with the schema pushed
16
+ // from the drizzle table objects the sqlite `pushSchema` so a repo runs
17
+ // against the exact engine it ships on, no hand-maintained DDL.
23
18
 
24
19
  export type SqliteTestDb<TSchema extends Record<string, unknown>> = Db<TSchema>;
25
20
 
@@ -36,7 +31,7 @@ export async function createSqliteTestDb<
36
31
  const sqlite = new Database(options?.filename ?? ":memory:");
37
32
  sqlite.pragma("foreign_keys = ON");
38
33
 
39
- const db = drizzle(sqlite, { schema }) as unknown as SqliteTestDb<TSchema>;
34
+ const db: SqliteTestDb<TSchema> = drizzle(sqlite, { schema });
40
35
 
41
36
  // pushSQLiteSchema derives the CREATE statements from the table objects;
42
37
  // better-sqlite3 rejects its own `apply()` (it issues the DDL through a
package/src/test-db.ts CHANGED
@@ -1,76 +1,21 @@
1
- import { pushSchema } from "drizzle-kit/api";
2
- import { drizzle } from "drizzle-orm/node-postgres";
3
- import EmbeddedPostgres from "embedded-postgres";
4
- import pg from "pg";
1
+ import type Database from "better-sqlite3";
2
+ import type { Db } from "./db.js";
5
3
  import * as schema from "./schema.js";
4
+ import { createSqliteTestDb } from "./test-db-sqlite.js";
6
5
 
7
- const { Pool } = pg;
8
-
9
- export type TestDb = ReturnType<typeof drizzle<typeof schema>>;
10
-
11
- let _instance: EmbeddedPostgres | null = null;
12
- let _port = 0;
13
-
14
- async function ensureStarted(): Promise<{ port: number }> {
15
- if (_instance) return { port: _port };
16
-
17
- for (let attempt = 0; attempt < 10; attempt++) {
18
- const port = 15000 + Math.floor(Math.random() * 5000);
19
- const instance = new EmbeddedPostgres({
20
- databaseDir: `/tmp/remit-test-pg-${process.pid}-${port}-${attempt}`,
21
- port,
22
- persistent: false,
23
- });
24
- try {
25
- await instance.initialise();
26
- await instance.start();
27
- } catch (err) {
28
- await instance.stop().catch(() => undefined);
29
- if (attempt === 9) throw err;
30
- continue;
31
- }
32
- _instance = instance;
33
- _port = port;
34
- process.on("exit", () => {
35
- instance.stop().catch(() => undefined);
36
- });
37
- return { port };
38
- }
39
- throw new Error("Failed to start embedded postgres");
40
- }
6
+ export type TestDb = Db<typeof schema>;
41
7
 
8
+ /**
9
+ * The whole app schema on a throwaway in-memory SQLite database, with the FTS5
10
+ * search objects installed on top — the harness a repo test takes when it needs
11
+ * more tables than its own.
12
+ */
42
13
  export async function createTestDb(): Promise<{
43
14
  db: TestDb;
44
- pool: pg.Pool;
15
+ sqlite: Database.Database;
45
16
  close: () => Promise<void>;
46
17
  }> {
47
- const { port } = await ensureStarted();
48
-
49
- const pool = new Pool({
50
- host: "localhost",
51
- port,
52
- user: "postgres",
53
- password: "password",
54
- database: "postgres",
55
- });
56
-
57
- const db = drizzle(pool, { schema }) as TestDb;
58
-
59
- const { apply } = await pushSchema(schema, drizzle(pool));
60
- await apply();
61
-
62
- return {
63
- db,
64
- pool,
65
- close: async () => {
66
- await pool.end();
67
- if (_instance) {
68
- const inst = _instance;
69
- _instance = null;
70
- await inst.stop();
71
- }
72
- },
73
- };
18
+ return createSqliteTestDb(schema, { searchIndex: true });
74
19
  }
75
20
 
76
21
  export { randomId } from "./id.js";
package/src/tx.ts CHANGED
@@ -2,14 +2,13 @@ import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { type SQL, sql } from "drizzle-orm";
4
4
  import type { Db } from "./db.js";
5
- import { isSqlite } from "./dialect.js";
6
5
 
7
- // Runs a write set in one transaction, on either dialect (RFC 036 D1).
6
+ // Runs a write set in one transaction.
8
7
  //
9
- // Postgres uses drizzle's own `db.transaction()`. better-sqlite3 cannot: its
10
- // native transaction runner rejects a callback that returns a promise, and the
11
- // repos' write sets are async. SQLite instead brackets the callback with a
12
- // SAVEPOINT — a savepoint opens a transaction when none is active and commits
8
+ // better-sqlite3's native transaction runner rejects a callback that returns a
9
+ // promise, and the repos' write sets are async, so drizzle's own
10
+ // `db.transaction()` cannot be used. The callback is bracketed with a SAVEPOINT
11
+ // instead — a savepoint opens a transaction when none is active and commits
13
12
  // when the outermost one is released.
14
13
  //
15
14
  // All writers on this backend share one better-sqlite3 connection (RFC 036 D3),
@@ -66,10 +65,6 @@ export async function runInTransaction<
66
65
  TSchema extends Record<string, unknown>,
67
66
  T,
68
67
  >(db: Db<TSchema>, fn: (tx: Db<TSchema>) => Promise<T>): Promise<T> {
69
- if (!isSqlite()) {
70
- return db.transaction(fn);
71
- }
72
-
73
68
  if (inSqliteTx.getStore()) {
74
69
  // Already inside a top-level transaction on this connection — nest with a
75
70
  // savepoint, do not re-queue.
@@ -184,8 +179,7 @@ function wrapWriteBuilder<B extends object>(builder: B): B {
184
179
  // savepoint DDL, already inside a serialized unit), `transaction`, and reads —
185
180
  // reads are not serialized, so a read issued during another unit's open
186
181
  // transaction can still observe uncommitted rows; the wrapper closes the
187
- // write-side rollback hazard, not read isolation. On Postgres this is never
188
- // applied.
182
+ // write-side rollback hazard, not read isolation.
189
183
  export function serializeSqliteWrites<TDb extends Db<Record<string, unknown>>>(
190
184
  db: TDb,
191
185
  ): TDb {
package/drizzle.config.ts DELETED
@@ -1,15 +0,0 @@
1
- const url =
2
- process.env.DATABASE_URL ??
3
- process.env.PG_CONNECTION_URL ??
4
- "postgresql://remit:remit@localhost:5432/remit_dev";
5
-
6
- export default {
7
- dialect: "postgresql",
8
- schema: "./src/schema.ts",
9
- out: "./.drizzle",
10
- dbCredentials: { url },
11
- // The better-auth identity tables (auth_*) share this database but are owned
12
- // by remit-auth-service's own drizzle config. Exclude them here so an entity
13
- // push never proposes dropping tables it does not manage.
14
- tablesFilter: ["!auth_*"],
15
- };
package/src/dialect.ts DELETED
@@ -1,13 +0,0 @@
1
- // The SQL dialect this process runs against (RFC 036 D1). One deployment is
2
- // one backend, chosen once at startup by `DATA_BACKEND`: `sqlite` selects the
3
- // SQLite entity tables, outbox table, transaction strategy, and search
4
- // predicates; anything else keeps the Postgres behavior that predates this
5
- // switch. Read once at module load — the repos and schema facade branch on it,
6
- // and a single process never mixes dialects.
7
-
8
- export type SqlDialect = "postgres" | "sqlite";
9
-
10
- export const SQL_DIALECT: SqlDialect =
11
- process.env.DATA_BACKEND === "sqlite" ? "sqlite" : "postgres";
12
-
13
- export const isSqlite = (): boolean => SQL_DIALECT === "sqlite";