@syncular/server-dialect-postgres 0.0.1-100
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/dist/index.d.ts +57 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +479 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
- package/src/index.test.ts +170 -0
- package/src/index.ts +688 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import Database from 'bun:sqlite';
|
|
2
|
+
import { afterEach, describe, expect, it } from 'bun:test';
|
|
3
|
+
import type { ScopeValues, StoredScopes, SyncOp } from '@syncular/core';
|
|
4
|
+
import type { DbExecutor } from '@syncular/server';
|
|
5
|
+
import type { SyncCoreDb } from '@syncular/server/schema';
|
|
6
|
+
import type { Dialect, QueryResult } from 'kysely';
|
|
7
|
+
import {
|
|
8
|
+
Kysely,
|
|
9
|
+
SqliteAdapter,
|
|
10
|
+
SqliteIntrospector,
|
|
11
|
+
SqliteQueryCompiler,
|
|
12
|
+
} from 'kysely';
|
|
13
|
+
import { PostgresServerSyncDialect } from './index';
|
|
14
|
+
|
|
15
|
+
class TestPostgresServerSyncDialect extends PostgresServerSyncDialect {
|
|
16
|
+
readonly calls: Array<{ cursor: number; limitCommits: number }> = [];
|
|
17
|
+
|
|
18
|
+
protected override async readIncrementalPullRowsBatch<DB extends SyncCoreDb>(
|
|
19
|
+
_db: DbExecutor<DB>,
|
|
20
|
+
args: {
|
|
21
|
+
table: string;
|
|
22
|
+
scopes: ScopeValues;
|
|
23
|
+
cursor: number;
|
|
24
|
+
limitCommits: number;
|
|
25
|
+
partitionId?: string;
|
|
26
|
+
}
|
|
27
|
+
): Promise<
|
|
28
|
+
Array<{
|
|
29
|
+
commit_seq: number;
|
|
30
|
+
actor_id: string;
|
|
31
|
+
created_at: string;
|
|
32
|
+
change_id: number;
|
|
33
|
+
table: string;
|
|
34
|
+
row_id: string;
|
|
35
|
+
op: SyncOp;
|
|
36
|
+
row_json: unknown | null;
|
|
37
|
+
row_version: number | null;
|
|
38
|
+
scopes: StoredScopes;
|
|
39
|
+
}>
|
|
40
|
+
> {
|
|
41
|
+
this.calls.push({ cursor: args.cursor, limitCommits: args.limitCommits });
|
|
42
|
+
|
|
43
|
+
const startCommit = args.cursor + 1;
|
|
44
|
+
const endCommit = Math.min(args.cursor + args.limitCommits, 120);
|
|
45
|
+
if (startCommit > endCommit) return [];
|
|
46
|
+
|
|
47
|
+
const rows: Array<{
|
|
48
|
+
commit_seq: number;
|
|
49
|
+
actor_id: string;
|
|
50
|
+
created_at: string;
|
|
51
|
+
change_id: number;
|
|
52
|
+
table: string;
|
|
53
|
+
row_id: string;
|
|
54
|
+
op: SyncOp;
|
|
55
|
+
row_json: unknown | null;
|
|
56
|
+
row_version: number | null;
|
|
57
|
+
scopes: StoredScopes;
|
|
58
|
+
}> = [];
|
|
59
|
+
|
|
60
|
+
let changeId = 0;
|
|
61
|
+
for (let commitSeq = startCommit; commitSeq <= endCommit; commitSeq += 1) {
|
|
62
|
+
for (let i = 0; i < 3; i += 1) {
|
|
63
|
+
changeId += 1;
|
|
64
|
+
rows.push({
|
|
65
|
+
commit_seq: commitSeq,
|
|
66
|
+
actor_id: 'actor-1',
|
|
67
|
+
created_at: '2026-01-01T00:00:00.000Z',
|
|
68
|
+
change_id: changeId,
|
|
69
|
+
table: args.table,
|
|
70
|
+
row_id: `row-${commitSeq}-${i}`,
|
|
71
|
+
op: 'upsert',
|
|
72
|
+
row_json: { id: `row-${commitSeq}-${i}` },
|
|
73
|
+
row_version: commitSeq,
|
|
74
|
+
scopes: { actor_id: 'actor-1' },
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return rows;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
describe('PostgresServerSyncDialect.iterateIncrementalPullRows', () => {
|
|
84
|
+
let db: Kysely<SyncCoreDb>;
|
|
85
|
+
|
|
86
|
+
function createTestDb(): Kysely<SyncCoreDb> {
|
|
87
|
+
const sqlite = new Database(':memory:');
|
|
88
|
+
const dialect: Dialect = {
|
|
89
|
+
createAdapter: () => new SqliteAdapter(),
|
|
90
|
+
createDriver: () => ({
|
|
91
|
+
init: async () => {},
|
|
92
|
+
acquireConnection: async () => ({
|
|
93
|
+
executeQuery: async <R>(compiledQuery: {
|
|
94
|
+
sql: string;
|
|
95
|
+
parameters: readonly unknown[];
|
|
96
|
+
}): Promise<QueryResult<R>> => {
|
|
97
|
+
const normalizedSql = compiledQuery.sql.trimStart().toLowerCase();
|
|
98
|
+
if (
|
|
99
|
+
normalizedSql.startsWith('select') ||
|
|
100
|
+
normalizedSql.startsWith('with') ||
|
|
101
|
+
normalizedSql.startsWith('pragma')
|
|
102
|
+
) {
|
|
103
|
+
const rows = sqlite
|
|
104
|
+
.prepare(compiledQuery.sql)
|
|
105
|
+
.all(...(compiledQuery.parameters ?? [])) as R[];
|
|
106
|
+
return { rows };
|
|
107
|
+
}
|
|
108
|
+
const result = sqlite
|
|
109
|
+
.prepare(compiledQuery.sql)
|
|
110
|
+
.run(...(compiledQuery.parameters ?? []));
|
|
111
|
+
return {
|
|
112
|
+
rows: [] as R[],
|
|
113
|
+
numAffectedRows: BigInt(result.changes),
|
|
114
|
+
insertId:
|
|
115
|
+
result.lastInsertRowid != null
|
|
116
|
+
? BigInt(result.lastInsertRowid)
|
|
117
|
+
: undefined,
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
streamQuery: <R>(): AsyncIterableIterator<QueryResult<R>> => {
|
|
121
|
+
throw new Error('Not implemented in test driver');
|
|
122
|
+
},
|
|
123
|
+
}),
|
|
124
|
+
beginTransaction: async () => {},
|
|
125
|
+
commitTransaction: async () => {},
|
|
126
|
+
rollbackTransaction: async () => {},
|
|
127
|
+
releaseConnection: async () => {},
|
|
128
|
+
destroy: async () => {
|
|
129
|
+
sqlite.close();
|
|
130
|
+
},
|
|
131
|
+
}),
|
|
132
|
+
createIntrospector: (innerDb) => new SqliteIntrospector(innerDb),
|
|
133
|
+
createQueryCompiler: () => new SqliteQueryCompiler(),
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
return new Kysely<SyncCoreDb>({ dialect });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
afterEach(async () => {
|
|
140
|
+
if (db) {
|
|
141
|
+
await db.destroy();
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('advances the batch cursor by commit sequence, not by row count', async () => {
|
|
146
|
+
db = createTestDb();
|
|
147
|
+
const dialect = new TestPostgresServerSyncDialect();
|
|
148
|
+
|
|
149
|
+
const rows = [];
|
|
150
|
+
for await (const row of dialect.iterateIncrementalPullRows(db, {
|
|
151
|
+
table: 'tasks',
|
|
152
|
+
scopes: { actor_id: 'actor-1' },
|
|
153
|
+
cursor: 0,
|
|
154
|
+
limitCommits: 120,
|
|
155
|
+
batchSize: 100,
|
|
156
|
+
})) {
|
|
157
|
+
rows.push(row);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const commitSeqs = new Set(rows.map((row) => row.commit_seq));
|
|
161
|
+
const maxCommitSeq = Math.max(...commitSeqs.values());
|
|
162
|
+
|
|
163
|
+
expect(commitSeqs.size).toBe(120);
|
|
164
|
+
expect(maxCommitSeq).toBe(120);
|
|
165
|
+
expect(dialect.calls).toEqual([
|
|
166
|
+
{ cursor: 0, limitCommits: 100 },
|
|
167
|
+
{ cursor: 100, limitCommits: 20 },
|
|
168
|
+
]);
|
|
169
|
+
});
|
|
170
|
+
});
|