@syncular/testkit 0.1.3 → 0.2.1
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/README.md +210 -0
- package/dist/client.d.ts +68 -0
- package/dist/client.js +145 -0
- package/dist/clock.d.ts +24 -0
- package/dist/clock.js +32 -0
- package/dist/create-test-sync.d.ts +67 -0
- package/dist/create-test-sync.js +90 -0
- package/dist/faults.d.ts +52 -50
- package/dist/faults.js +74 -192
- package/dist/index.d.ts +17 -11
- package/dist/index.js +18 -11
- package/dist/react.d.ts +10 -0
- package/dist/react.js +41 -0
- package/dist/server.d.ts +44 -4
- package/dist/server.js +68 -4
- package/package.json +41 -57
- package/src/client.ts +218 -0
- package/src/clock.ts +45 -0
- package/src/create-test-sync.ts +158 -0
- package/src/faults.ts +77 -338
- package/src/index.ts +44 -10
- package/src/react.ts +46 -0
- package/src/server.ts +102 -3
- package/dist/audit.d.ts +0 -22
- package/dist/audit.d.ts.map +0 -1
- package/dist/audit.js +0 -40
- package/dist/audit.js.map +0 -1
- package/dist/client-bridge.d.ts +0 -77
- package/dist/client-bridge.d.ts.map +0 -1
- package/dist/client-bridge.js +0 -540
- package/dist/client-bridge.js.map +0 -1
- package/dist/deterministic.d.ts +0 -31
- package/dist/deterministic.d.ts.map +0 -1
- package/dist/deterministic.js +0 -69
- package/dist/deterministic.js.map +0 -1
- package/dist/disposable.d.ts +0 -10
- package/dist/disposable.d.ts.map +0 -1
- package/dist/disposable.js +0 -28
- package/dist/disposable.js.map +0 -1
- package/dist/faults.d.ts.map +0 -1
- package/dist/faults.js.map +0 -1
- package/dist/hono-node-server.d.ts +0 -20
- package/dist/hono-node-server.d.ts.map +0 -1
- package/dist/hono-node-server.js +0 -90
- package/dist/hono-node-server.js.map +0 -1
- package/dist/http-fixtures.d.ts +0 -32
- package/dist/http-fixtures.d.ts.map +0 -1
- package/dist/http-fixtures.js +0 -117
- package/dist/http-fixtures.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/project-scoped-tasks.d.ts +0 -40
- package/dist/project-scoped-tasks.d.ts.map +0 -1
- package/dist/project-scoped-tasks.js +0 -247
- package/dist/project-scoped-tasks.js.map +0 -1
- package/dist/realtime-ws.d.ts +0 -48
- package/dist/realtime-ws.d.ts.map +0 -1
- package/dist/realtime-ws.js +0 -203
- package/dist/realtime-ws.js.map +0 -1
- package/dist/runtime-process.d.ts +0 -14
- package/dist/runtime-process.d.ts.map +0 -1
- package/dist/runtime-process.js +0 -99
- package/dist/runtime-process.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/sync-builders.d.ts +0 -48
- package/dist/sync-builders.d.ts.map +0 -1
- package/dist/sync-builders.js +0 -66
- package/dist/sync-builders.js.map +0 -1
- package/dist/sync-http.d.ts +0 -48
- package/dist/sync-http.d.ts.map +0 -1
- package/dist/sync-http.js +0 -43
- package/dist/sync-http.js.map +0 -1
- package/dist/sync-parse.d.ts +0 -8
- package/dist/sync-parse.d.ts.map +0 -1
- package/dist/sync-parse.js +0 -38
- package/dist/sync-parse.js.map +0 -1
- package/dist/sync-response.d.ts +0 -6
- package/dist/sync-response.d.ts.map +0 -1
- package/dist/sync-response.js +0 -19
- package/dist/sync-response.js.map +0 -1
- package/src/audit.ts +0 -74
- package/src/client-bridge.ts +0 -841
- package/src/deterministic.ts +0 -121
- package/src/disposable.ts +0 -50
- package/src/hono-node-server.ts +0 -144
- package/src/http-fixtures.ts +0 -187
- package/src/project-scoped-tasks.ts +0 -351
- package/src/realtime-ws.ts +0 -354
- package/src/runtime-process.ts +0 -159
- package/src/sync-builders.ts +0 -136
- package/src/sync-http.ts +0 -117
- package/src/sync-parse.ts +0 -86
- package/src/sync-response.ts +0 -43
package/src/deterministic.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
export interface CreateIdFactoryOptions {
|
|
2
|
-
prefix?: string;
|
|
3
|
-
separator?: string;
|
|
4
|
-
startAt?: number;
|
|
5
|
-
step?: number;
|
|
6
|
-
padLength?: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface IdFactory {
|
|
10
|
-
next: () => string;
|
|
11
|
-
peek: () => string;
|
|
12
|
-
current: () => number;
|
|
13
|
-
reset: (startAt?: number) => void;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function normalizeCounter(value: number, label: string): number {
|
|
17
|
-
if (!Number.isFinite(value)) {
|
|
18
|
-
throw new Error(`${label} must be a finite number`);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return Math.trunc(value);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function formatCounter(value: number, padLength: number): string {
|
|
25
|
-
const text = String(value);
|
|
26
|
-
return padLength > text.length ? text.padStart(padLength, '0') : text;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function createIdFactory(
|
|
30
|
-
options: CreateIdFactoryOptions = {}
|
|
31
|
-
): IdFactory {
|
|
32
|
-
const prefix = options.prefix ?? '';
|
|
33
|
-
const separator = options.separator ?? '-';
|
|
34
|
-
const step = normalizeCounter(options.step ?? 1, 'step');
|
|
35
|
-
const padLength = Math.max(
|
|
36
|
-
0,
|
|
37
|
-
normalizeCounter(options.padLength ?? 0, 'padLength')
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
if (step === 0) {
|
|
41
|
-
throw new Error('step must not be 0');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
let counter = normalizeCounter(options.startAt ?? 1, 'startAt');
|
|
45
|
-
|
|
46
|
-
const format = (value: number): string => {
|
|
47
|
-
const token = formatCounter(value, padLength);
|
|
48
|
-
if (prefix.length === 0) {
|
|
49
|
-
return token;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return `${prefix}${separator}${token}`;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
next: () => {
|
|
57
|
-
const value = format(counter);
|
|
58
|
-
counter += step;
|
|
59
|
-
return value;
|
|
60
|
-
},
|
|
61
|
-
peek: () => format(counter),
|
|
62
|
-
current: () => counter,
|
|
63
|
-
reset: (nextStartAt = options.startAt ?? 1) => {
|
|
64
|
-
counter = normalizeCounter(nextStartAt, 'startAt');
|
|
65
|
-
},
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface CreateCommitIdFactoryOptions
|
|
70
|
-
extends Omit<CreateIdFactoryOptions, 'prefix'> {
|
|
71
|
-
prefix?: string;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function createCommitIdFactory(
|
|
75
|
-
options: CreateCommitIdFactoryOptions = {}
|
|
76
|
-
): IdFactory {
|
|
77
|
-
return createIdFactory({
|
|
78
|
-
prefix: options.prefix ?? 'commit',
|
|
79
|
-
separator: options.separator,
|
|
80
|
-
startAt: options.startAt,
|
|
81
|
-
step: options.step,
|
|
82
|
-
padLength: options.padLength,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export interface CreateFakeClockOptions {
|
|
87
|
-
startMs?: number;
|
|
88
|
-
tickMs?: number;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export interface FakeClock {
|
|
92
|
-
now: () => number;
|
|
93
|
-
iso: () => string;
|
|
94
|
-
set: (nextMs: number) => number;
|
|
95
|
-
advance: (deltaMs: number) => number;
|
|
96
|
-
tick: (stepMs?: number) => number;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function createFakeClock(
|
|
100
|
-
options: CreateFakeClockOptions = {}
|
|
101
|
-
): FakeClock {
|
|
102
|
-
const defaultTickMs = normalizeCounter(options.tickMs ?? 1, 'tickMs');
|
|
103
|
-
let nowMs = normalizeCounter(options.startMs ?? Date.now(), 'startMs');
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
now: () => nowMs,
|
|
107
|
-
iso: () => new Date(nowMs).toISOString(),
|
|
108
|
-
set: (nextMs: number) => {
|
|
109
|
-
nowMs = normalizeCounter(nextMs, 'nextMs');
|
|
110
|
-
return nowMs;
|
|
111
|
-
},
|
|
112
|
-
advance: (deltaMs: number) => {
|
|
113
|
-
nowMs += normalizeCounter(deltaMs, 'deltaMs');
|
|
114
|
-
return nowMs;
|
|
115
|
-
},
|
|
116
|
-
tick: (stepMs = defaultTickMs) => {
|
|
117
|
-
nowMs += normalizeCounter(stepMs, 'stepMs');
|
|
118
|
-
return nowMs;
|
|
119
|
-
},
|
|
120
|
-
};
|
|
121
|
-
}
|
package/src/disposable.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
export interface AsyncDisposableResource<T> {
|
|
2
|
-
value: T;
|
|
3
|
-
dispose: () => Promise<void>;
|
|
4
|
-
[Symbol.asyncDispose]: () => Promise<void>;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export type ResourceRunner<T, TResult> =
|
|
8
|
-
| ((value: T) => Promise<TResult>)
|
|
9
|
-
| ((value: T) => TResult);
|
|
10
|
-
|
|
11
|
-
export function createAsyncDisposableResource<T>(
|
|
12
|
-
value: T,
|
|
13
|
-
dispose: () => Promise<void> | void
|
|
14
|
-
): AsyncDisposableResource<T> {
|
|
15
|
-
let disposed = false;
|
|
16
|
-
|
|
17
|
-
const disposeOnce = async () => {
|
|
18
|
-
if (disposed) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
disposed = true;
|
|
23
|
-
await dispose();
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
return {
|
|
27
|
-
value,
|
|
28
|
-
dispose: disposeOnce,
|
|
29
|
-
[Symbol.asyncDispose]: disposeOnce,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export async function withAsyncDisposableResource<T, TResult>(
|
|
34
|
-
resource: AsyncDisposableResource<T>,
|
|
35
|
-
run: ResourceRunner<T, TResult>
|
|
36
|
-
): Promise<TResult> {
|
|
37
|
-
try {
|
|
38
|
-
return await run(resource.value);
|
|
39
|
-
} finally {
|
|
40
|
-
await resource.dispose();
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export async function withAsyncDisposableFactory<T, TResult>(
|
|
45
|
-
create: () => Promise<AsyncDisposableResource<T>>,
|
|
46
|
-
run: ResourceRunner<T, TResult>
|
|
47
|
-
): Promise<TResult> {
|
|
48
|
-
const resource = await create();
|
|
49
|
-
return withAsyncDisposableResource(resource, run);
|
|
50
|
-
}
|
package/src/hono-node-server.ts
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { createServer, type Server as NodeServer } from 'node:http';
|
|
2
|
-
import type { Hono } from 'hono';
|
|
3
|
-
import {
|
|
4
|
-
type AsyncDisposableResource,
|
|
5
|
-
createAsyncDisposableResource,
|
|
6
|
-
type ResourceRunner,
|
|
7
|
-
withAsyncDisposableFactory,
|
|
8
|
-
} from './disposable';
|
|
9
|
-
|
|
10
|
-
export interface NodeHonoServerOptions {
|
|
11
|
-
cors?: boolean;
|
|
12
|
-
corsAllowMethods?: string;
|
|
13
|
-
corsAllowHeaders?: string;
|
|
14
|
-
corsMaxAgeSeconds?: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface CreateNodeHonoServerResourceOptions
|
|
18
|
-
extends NodeHonoServerOptions {
|
|
19
|
-
listen?: boolean;
|
|
20
|
-
host?: string;
|
|
21
|
-
port?: number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function createNodeHonoServer(
|
|
25
|
-
app: Hono,
|
|
26
|
-
options?: NodeHonoServerOptions
|
|
27
|
-
): NodeServer {
|
|
28
|
-
const corsEnabled = options?.cors ?? true;
|
|
29
|
-
const corsAllowMethods =
|
|
30
|
-
options?.corsAllowMethods ?? 'GET, POST, PUT, DELETE, OPTIONS';
|
|
31
|
-
const corsAllowHeaders =
|
|
32
|
-
options?.corsAllowHeaders ??
|
|
33
|
-
'authorization, content-type, x-actor-id, x-partition-id, x-syncular-schema-version, x-syncular-snapshot-scopes, x-syncular-transport-path, x-upload-token, x-user-id';
|
|
34
|
-
const corsMaxAgeSeconds = options?.corsMaxAgeSeconds ?? 86400;
|
|
35
|
-
|
|
36
|
-
return createServer(async (req, res) => {
|
|
37
|
-
const url = `http://localhost${req.url ?? '/'}`;
|
|
38
|
-
|
|
39
|
-
const headers = new Headers();
|
|
40
|
-
for (const [key, value] of Object.entries(req.headers)) {
|
|
41
|
-
if (!value) {
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
headers.set(key, Array.isArray(value) ? value.join(', ') : value);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (corsEnabled && req.method === 'OPTIONS') {
|
|
49
|
-
res.writeHead(204, {
|
|
50
|
-
'access-control-allow-origin': '*',
|
|
51
|
-
'access-control-allow-methods': corsAllowMethods,
|
|
52
|
-
'access-control-allow-headers': corsAllowHeaders,
|
|
53
|
-
'access-control-max-age': String(corsMaxAgeSeconds),
|
|
54
|
-
});
|
|
55
|
-
res.end();
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const hasBody = req.method !== 'GET' && req.method !== 'HEAD';
|
|
60
|
-
const body = hasBody
|
|
61
|
-
? await new Promise<Uint8Array>((resolve) => {
|
|
62
|
-
const chunks: Uint8Array[] = [];
|
|
63
|
-
req.on('data', (chunk: Uint8Array) => chunks.push(chunk));
|
|
64
|
-
req.on('end', () => {
|
|
65
|
-
const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
66
|
-
const merged = new Uint8Array(total);
|
|
67
|
-
let offset = 0;
|
|
68
|
-
for (const chunk of chunks) {
|
|
69
|
-
merged.set(chunk, offset);
|
|
70
|
-
offset += chunk.length;
|
|
71
|
-
}
|
|
72
|
-
resolve(merged);
|
|
73
|
-
});
|
|
74
|
-
})
|
|
75
|
-
: undefined;
|
|
76
|
-
|
|
77
|
-
const requestBody = body ? Uint8Array.from(body) : undefined;
|
|
78
|
-
|
|
79
|
-
const request = new Request(url, {
|
|
80
|
-
method: req.method,
|
|
81
|
-
headers,
|
|
82
|
-
body: requestBody,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
const response = await app.fetch(request);
|
|
86
|
-
const responseHeaders: Record<string, string> = {};
|
|
87
|
-
response.headers.forEach((value, key) => {
|
|
88
|
-
responseHeaders[key] = value;
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
if (corsEnabled) {
|
|
92
|
-
responseHeaders['access-control-allow-origin'] = '*';
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
res.writeHead(response.status, responseHeaders);
|
|
96
|
-
const bytes = Buffer.from(await response.arrayBuffer());
|
|
97
|
-
res.end(bytes);
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export async function closeNodeServer(server: NodeServer): Promise<void> {
|
|
102
|
-
await new Promise<void>((resolve, reject) => {
|
|
103
|
-
server.close((error) => {
|
|
104
|
-
if (error) {
|
|
105
|
-
reject(error);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
resolve();
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function createNodeServerResource(
|
|
115
|
-
server: NodeServer
|
|
116
|
-
): AsyncDisposableResource<NodeServer> {
|
|
117
|
-
return createAsyncDisposableResource(server, () => closeNodeServer(server));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export async function createNodeHonoServerResource(
|
|
121
|
-
app: Hono,
|
|
122
|
-
options: CreateNodeHonoServerResourceOptions = {}
|
|
123
|
-
): Promise<AsyncDisposableResource<NodeServer>> {
|
|
124
|
-
const server = createNodeHonoServer(app, options);
|
|
125
|
-
|
|
126
|
-
if (options.listen ?? true) {
|
|
127
|
-
await new Promise<void>((resolve) => {
|
|
128
|
-
server.listen(options.port ?? 0, options.host ?? '127.0.0.1', resolve);
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return createNodeServerResource(server);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export async function withNodeHonoServer<TResult>(
|
|
136
|
-
app: Hono,
|
|
137
|
-
run: ResourceRunner<NodeServer, TResult>,
|
|
138
|
-
options: CreateNodeHonoServerResourceOptions = {}
|
|
139
|
-
): Promise<TResult> {
|
|
140
|
-
return withAsyncDisposableFactory(
|
|
141
|
-
() => createNodeHonoServerResource(app, options),
|
|
142
|
-
run
|
|
143
|
-
);
|
|
144
|
-
}
|
package/src/http-fixtures.ts
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import { createDatabase } from '@syncular/core';
|
|
2
|
-
import {
|
|
3
|
-
ensureSyncSchema,
|
|
4
|
-
type ServerSyncDialect,
|
|
5
|
-
type ServerTableHandler,
|
|
6
|
-
type SyncCoreDb,
|
|
7
|
-
} from '@syncular/server';
|
|
8
|
-
import { createBunSqliteDialect } from '@syncular/server/bun-sqlite';
|
|
9
|
-
import {
|
|
10
|
-
type CreateSyncRoutesOptions,
|
|
11
|
-
createSyncRoutes,
|
|
12
|
-
} from '@syncular/server/hono';
|
|
13
|
-
import { createPgliteDialect } from '@syncular/server/pglite';
|
|
14
|
-
import { createPostgresServerDialect } from '@syncular/server/postgres';
|
|
15
|
-
import { createSqliteServerDialect } from '@syncular/server/sqlite';
|
|
16
|
-
import { Hono } from 'hono';
|
|
17
|
-
import type { Kysely } from 'kysely';
|
|
18
|
-
import { createNodeHonoServer } from './hono-node-server';
|
|
19
|
-
|
|
20
|
-
export type HttpServerDialect = 'sqlite' | 'pglite';
|
|
21
|
-
|
|
22
|
-
export interface HttpServerFixture<DB extends SyncCoreDb> {
|
|
23
|
-
db: Kysely<DB>;
|
|
24
|
-
dialect: ServerSyncDialect;
|
|
25
|
-
app: Hono;
|
|
26
|
-
httpServer: ReturnType<typeof createNodeHonoServer>;
|
|
27
|
-
baseUrl: string;
|
|
28
|
-
destroy: () => Promise<void>;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface CreateHttpServerFixtureOptions<DB extends SyncCoreDb> {
|
|
32
|
-
serverDialect: HttpServerDialect;
|
|
33
|
-
createTables: (db: Kysely<DB>) => Promise<void>;
|
|
34
|
-
handlers: ServerTableHandler<DB>[];
|
|
35
|
-
authenticate: CreateSyncRoutesOptions<
|
|
36
|
-
DB,
|
|
37
|
-
{ actorId: string }
|
|
38
|
-
>['authenticate'];
|
|
39
|
-
sync?: CreateSyncRoutesOptions<DB, { actorId: string }>['sync'];
|
|
40
|
-
routePath?: string;
|
|
41
|
-
cors?: boolean;
|
|
42
|
-
corsAllowMethods?: string;
|
|
43
|
-
corsAllowHeaders?: string;
|
|
44
|
-
corsMaxAgeSeconds?: number;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const PGLITE_INIT_ATTEMPTS = 3;
|
|
48
|
-
|
|
49
|
-
function isTransientPgliteInitError(error: Error): boolean {
|
|
50
|
-
return (
|
|
51
|
-
error.message.includes('access to a null reference') ||
|
|
52
|
-
error.message.includes('_pgl_initdb')
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function isServerNotRunningError(error: Error | null): boolean {
|
|
57
|
-
return (
|
|
58
|
-
error !== null && 'code' in error && error.code === 'ERR_SERVER_NOT_RUNNING'
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async function delay(ms: number): Promise<void> {
|
|
63
|
-
await new Promise<void>((resolve) => {
|
|
64
|
-
setTimeout(resolve, ms);
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async function withPgliteInitRetry<T>(
|
|
69
|
-
useRetries: boolean,
|
|
70
|
-
create: () => Promise<T>
|
|
71
|
-
): Promise<T> {
|
|
72
|
-
const maxAttempts = useRetries ? PGLITE_INIT_ATTEMPTS : 1;
|
|
73
|
-
let lastError: Error | null = null;
|
|
74
|
-
|
|
75
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
76
|
-
try {
|
|
77
|
-
return await create();
|
|
78
|
-
} catch (error) {
|
|
79
|
-
const candidate =
|
|
80
|
-
error instanceof Error ? error : new Error(String(error));
|
|
81
|
-
lastError = candidate;
|
|
82
|
-
|
|
83
|
-
const isRetryable =
|
|
84
|
-
useRetries &&
|
|
85
|
-
attempt < maxAttempts &&
|
|
86
|
-
isTransientPgliteInitError(candidate);
|
|
87
|
-
if (!isRetryable) {
|
|
88
|
-
throw candidate;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
await delay(25 * attempt);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
throw lastError ?? new Error('PGLite initialization failed');
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export async function createHttpServerFixture<DB extends SyncCoreDb>(
|
|
99
|
-
options: CreateHttpServerFixtureOptions<DB>
|
|
100
|
-
): Promise<HttpServerFixture<DB>> {
|
|
101
|
-
const dialect =
|
|
102
|
-
options.serverDialect === 'pglite'
|
|
103
|
-
? createPostgresServerDialect()
|
|
104
|
-
: createSqliteServerDialect();
|
|
105
|
-
|
|
106
|
-
const db = await withPgliteInitRetry(
|
|
107
|
-
options.serverDialect === 'pglite',
|
|
108
|
-
async () => {
|
|
109
|
-
const candidateDb =
|
|
110
|
-
options.serverDialect === 'pglite'
|
|
111
|
-
? createDatabase<DB>({
|
|
112
|
-
dialect: createPgliteDialect(),
|
|
113
|
-
family: 'postgres',
|
|
114
|
-
})
|
|
115
|
-
: createDatabase<DB>({
|
|
116
|
-
dialect: createBunSqliteDialect({ path: ':memory:' }),
|
|
117
|
-
family: 'sqlite',
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
await ensureSyncSchema(candidateDb, dialect);
|
|
122
|
-
if (dialect.ensureConsoleSchema) {
|
|
123
|
-
await dialect.ensureConsoleSchema(candidateDb);
|
|
124
|
-
}
|
|
125
|
-
await options.createTables(candidateDb);
|
|
126
|
-
return candidateDb;
|
|
127
|
-
} catch (error) {
|
|
128
|
-
await candidateDb.destroy();
|
|
129
|
-
throw error;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
const app = new Hono();
|
|
135
|
-
const routePath = options.routePath ?? '/sync';
|
|
136
|
-
|
|
137
|
-
const syncRoutes = createSyncRoutes<DB>({
|
|
138
|
-
db,
|
|
139
|
-
dialect,
|
|
140
|
-
handlers: options.handlers,
|
|
141
|
-
authenticate: options.authenticate,
|
|
142
|
-
sync: options.sync,
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
app.route(routePath, syncRoutes);
|
|
146
|
-
|
|
147
|
-
const httpServer = createNodeHonoServer(app, {
|
|
148
|
-
cors: options.cors,
|
|
149
|
-
corsAllowMethods: options.corsAllowMethods,
|
|
150
|
-
corsAllowHeaders: options.corsAllowHeaders,
|
|
151
|
-
corsMaxAgeSeconds: options.corsMaxAgeSeconds,
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
await new Promise<void>((resolve) => {
|
|
155
|
-
httpServer.listen(0, resolve);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
const address = httpServer.address();
|
|
159
|
-
const port = typeof address === 'object' && address ? address.port : 0;
|
|
160
|
-
|
|
161
|
-
let destroyed = false;
|
|
162
|
-
|
|
163
|
-
return {
|
|
164
|
-
db,
|
|
165
|
-
dialect,
|
|
166
|
-
app,
|
|
167
|
-
httpServer,
|
|
168
|
-
baseUrl: `http://localhost:${port}`,
|
|
169
|
-
destroy: async () => {
|
|
170
|
-
if (destroyed) {
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
destroyed = true;
|
|
174
|
-
|
|
175
|
-
await new Promise<void>((resolve, reject) => {
|
|
176
|
-
httpServer.close((err) => {
|
|
177
|
-
if (err && !isServerNotRunningError(err)) {
|
|
178
|
-
reject(err);
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
resolve();
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
await db.destroy();
|
|
185
|
-
},
|
|
186
|
-
};
|
|
187
|
-
}
|