@syncular/testkit 0.7.0 → 0.9.0
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 +34 -9
- package/dist/create-test-sync.d.ts +3 -1
- package/dist/create-test-sync.js +3 -0
- package/dist/server.d.ts +3 -1
- package/dist/server.js +6 -0
- package/package.json +5 -5
- package/src/create-test-sync.ts +6 -1
- package/src/server.ts +9 -0
package/README.md
CHANGED
|
@@ -16,15 +16,10 @@ green test here is real sync behaviour, not a fake.
|
|
|
16
16
|
|
|
17
17
|
## Install
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
{
|
|
24
|
-
"devDependencies": {
|
|
25
|
-
"@syncular/testkit": "workspace:*"
|
|
26
|
-
}
|
|
27
|
-
}
|
|
19
|
+
Install it as a development dependency:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bun add --dev @syncular/testkit
|
|
28
23
|
```
|
|
29
24
|
|
|
30
25
|
Requires the Bun runtime (the in-memory client backend is `bun:sqlite`). The
|
|
@@ -71,6 +66,7 @@ adds the test-only controls below.
|
|
|
71
66
|
| `partition` | `"test"` | the §1.1 partition every client lives in |
|
|
72
67
|
| `actorId` | `"test-actor"` | default actor a client authenticates as |
|
|
73
68
|
| `resolveScopes` | grant-all | host authorization (§3.2); omit to grant `'*'` for every var |
|
|
69
|
+
| `validators` | off | real per-table write validators (§6.7), including structured rejection details |
|
|
74
70
|
| `startMs` | `1_750_000_000_000` | epoch ms the shared virtual clock starts at |
|
|
75
71
|
|
|
76
72
|
`TestSync`:
|
|
@@ -168,6 +164,35 @@ await a.sync(); // the hub fans the commit to b as a delta
|
|
|
168
164
|
|
|
169
165
|
`goOffline()` also drops the socket; reconnect with `connectRealtime()`.
|
|
170
166
|
|
|
167
|
+
### Multi-client conflicts and correction metadata
|
|
168
|
+
|
|
169
|
+
Create the common server row, let both clients pull it, then make their edits
|
|
170
|
+
from the same observed version. Push one client first so the second write is a
|
|
171
|
+
deterministic conflict—no timers or transport mocks are needed:
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
await sync.syncAll();
|
|
175
|
+
const [{ version }] = a.api.query(
|
|
176
|
+
'SELECT _sync_version AS version FROM notes WHERE id = ?',
|
|
177
|
+
['n1'],
|
|
178
|
+
) as Array<{ version: number }>;
|
|
179
|
+
|
|
180
|
+
a.api.patch('notes', 'n1', { body: 'A' }, { baseVersion: version });
|
|
181
|
+
b.api.patch('notes', 'n1', { body: 'B' }, { baseVersion: version });
|
|
182
|
+
|
|
183
|
+
await a.sync(); // wins version + 1
|
|
184
|
+
await b.sync(); // loses against the stale base
|
|
185
|
+
|
|
186
|
+
expect(b.api.conflicts[0]?.serverRow.body).toBe('A');
|
|
187
|
+
expect(b.api.conflicts[0]?.operation?.changedFields).toEqual(['body']);
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Pass `validators` to `createTestSync` to exercise the same business rules as
|
|
191
|
+
production. A `ValidationRejection` with structured details reaches
|
|
192
|
+
`client.api.rejections` through both loopback and socket sync rounds, so app
|
|
193
|
+
tests can assert field focus and correction routing without displaying server
|
|
194
|
+
diagnostics.
|
|
195
|
+
|
|
171
196
|
## React
|
|
172
197
|
|
|
173
198
|
`@syncular/react`'s `SyncProvider` already takes any `SyncClient`, so
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* await sync.dispose();
|
|
16
16
|
*/
|
|
17
17
|
import type { ClientSchema, SyncClientConfig } from '@syncular/client';
|
|
18
|
-
import type { ResolveScopes } from '@syncular/server';
|
|
18
|
+
import type { ResolveScopes, ValidatorRegistry } from '@syncular/server';
|
|
19
19
|
import { type TestClient } from './client.js';
|
|
20
20
|
import { type VirtualClock } from './clock.js';
|
|
21
21
|
import { type TestServer } from './server.js';
|
|
@@ -37,6 +37,8 @@ export interface CreateTestSyncOptions {
|
|
|
37
37
|
* in the server exactly as it would in production.
|
|
38
38
|
*/
|
|
39
39
|
readonly resolveScopes?: ResolveScopes;
|
|
40
|
+
/** Optional §6.7 write validators, executed by the real test server. */
|
|
41
|
+
readonly validators?: ValidatorRegistry;
|
|
40
42
|
/** Epoch ms the shared virtual clock starts at (default 1_750_000_000_000). */
|
|
41
43
|
readonly startMs?: number;
|
|
42
44
|
}
|
package/dist/create-test-sync.js
CHANGED
|
@@ -28,6 +28,9 @@ export async function createTestSync(options) {
|
|
|
28
28
|
...(options.resolveScopes !== undefined
|
|
29
29
|
? { resolveScopes: options.resolveScopes }
|
|
30
30
|
: {}),
|
|
31
|
+
...(options.validators !== undefined
|
|
32
|
+
? { validators: options.validators }
|
|
33
|
+
: {}),
|
|
31
34
|
});
|
|
32
35
|
const clients = [];
|
|
33
36
|
let autoId = 0;
|
package/dist/server.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* directly.
|
|
9
9
|
*/
|
|
10
10
|
import { type ClientSchema } from '@syncular/client';
|
|
11
|
-
import { MemorySegmentStore, type RealtimeHub, type ResolveScopes, SqliteServerStorage, type SyncRequestContext } from '@syncular/server';
|
|
11
|
+
import { MemorySegmentStore, type RealtimeHub, type ResolveScopes, SqliteServerStorage, type SyncRequestContext, type ValidatorRegistry } from '@syncular/server';
|
|
12
12
|
import type { VirtualClock } from './clock.js';
|
|
13
13
|
/** Default partition every actor lives in (apps override via options). */
|
|
14
14
|
export declare const DEFAULT_PARTITION = "test";
|
|
@@ -27,6 +27,8 @@ export interface TestServerOptions {
|
|
|
27
27
|
readonly partition: string;
|
|
28
28
|
/** Defaults to {@link allowAllScopes}. */
|
|
29
29
|
readonly resolveScopes?: ResolveScopes;
|
|
30
|
+
/** Optional §6.7 validators, shared by HTTP-like and socket rounds. */
|
|
31
|
+
readonly validators?: ValidatorRegistry;
|
|
30
32
|
}
|
|
31
33
|
/**
|
|
32
34
|
* The server half of a test sync. `ctxFor(actorId)` builds the per-request
|
package/dist/server.js
CHANGED
|
@@ -45,6 +45,9 @@ export function createTestServer(options) {
|
|
|
45
45
|
resolveScopes,
|
|
46
46
|
clock: clockFn,
|
|
47
47
|
segments,
|
|
48
|
+
...(options.validators !== undefined
|
|
49
|
+
? { validators: options.validators }
|
|
50
|
+
: {}),
|
|
48
51
|
});
|
|
49
52
|
return {
|
|
50
53
|
storage,
|
|
@@ -58,6 +61,9 @@ export function createTestServer(options) {
|
|
|
58
61
|
storage,
|
|
59
62
|
segments,
|
|
60
63
|
resolveScopes,
|
|
64
|
+
...(options.validators !== undefined
|
|
65
|
+
? { validators: options.validators }
|
|
66
|
+
: {}),
|
|
61
67
|
clock: clockFn,
|
|
62
68
|
realtime: hub,
|
|
63
69
|
}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncular/testkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Syncular test kit: in-process loopback server + clients for integration scenarios",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Benjamin Kniffler",
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"test": "bun test --preload ./test/setup.ts"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@syncular/server": "0.
|
|
68
|
-
"@syncular/client": "0.
|
|
67
|
+
"@syncular/server": "0.9.0",
|
|
68
|
+
"@syncular/client": "0.9.0"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"react": ">=18.0.0"
|
|
@@ -77,8 +77,8 @@
|
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@happy-dom/global-registrator": "^15.11.0",
|
|
80
|
-
"@syncular/core": "0.
|
|
81
|
-
"@syncular/react": "0.
|
|
80
|
+
"@syncular/core": "0.9.0",
|
|
81
|
+
"@syncular/react": "0.9.0",
|
|
82
82
|
"@testing-library/react": "^16.1.0",
|
|
83
83
|
"@types/react": "^18.3.0",
|
|
84
84
|
"react": "^18.3.1",
|
package/src/create-test-sync.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import type { ClientSchema, SyncClientConfig } from '@syncular/client';
|
|
19
|
-
import type { ResolveScopes } from '@syncular/server';
|
|
19
|
+
import type { ResolveScopes, ValidatorRegistry } from '@syncular/server';
|
|
20
20
|
import { buildTestClient, type TestClient } from './client';
|
|
21
21
|
import { createVirtualClock, type VirtualClock } from './clock';
|
|
22
22
|
import {
|
|
@@ -44,6 +44,8 @@ export interface CreateTestSyncOptions {
|
|
|
44
44
|
* in the server exactly as it would in production.
|
|
45
45
|
*/
|
|
46
46
|
readonly resolveScopes?: ResolveScopes;
|
|
47
|
+
/** Optional §6.7 write validators, executed by the real test server. */
|
|
48
|
+
readonly validators?: ValidatorRegistry;
|
|
47
49
|
/** Epoch ms the shared virtual clock starts at (default 1_750_000_000_000). */
|
|
48
50
|
readonly startMs?: number;
|
|
49
51
|
}
|
|
@@ -98,6 +100,9 @@ export async function createTestSync(
|
|
|
98
100
|
...(options.resolveScopes !== undefined
|
|
99
101
|
? { resolveScopes: options.resolveScopes }
|
|
100
102
|
: {}),
|
|
103
|
+
...(options.validators !== undefined
|
|
104
|
+
? { validators: options.validators }
|
|
105
|
+
: {}),
|
|
101
106
|
});
|
|
102
107
|
|
|
103
108
|
const clients: TestClient[] = [];
|
package/src/server.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type ServerSchema,
|
|
18
18
|
SqliteServerStorage,
|
|
19
19
|
type SyncRequestContext,
|
|
20
|
+
type ValidatorRegistry,
|
|
20
21
|
} from '@syncular/server';
|
|
21
22
|
import type { VirtualClock } from './clock';
|
|
22
23
|
|
|
@@ -50,6 +51,8 @@ export interface TestServerOptions {
|
|
|
50
51
|
readonly partition: string;
|
|
51
52
|
/** Defaults to {@link allowAllScopes}. */
|
|
52
53
|
readonly resolveScopes?: ResolveScopes;
|
|
54
|
+
/** Optional §6.7 validators, shared by HTTP-like and socket rounds. */
|
|
55
|
+
readonly validators?: ValidatorRegistry;
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
/**
|
|
@@ -79,6 +82,9 @@ export function createTestServer(options: TestServerOptions): TestServer {
|
|
|
79
82
|
resolveScopes,
|
|
80
83
|
clock: clockFn,
|
|
81
84
|
segments,
|
|
85
|
+
...(options.validators !== undefined
|
|
86
|
+
? { validators: options.validators }
|
|
87
|
+
: {}),
|
|
82
88
|
});
|
|
83
89
|
return {
|
|
84
90
|
storage,
|
|
@@ -92,6 +98,9 @@ export function createTestServer(options: TestServerOptions): TestServer {
|
|
|
92
98
|
storage,
|
|
93
99
|
segments,
|
|
94
100
|
resolveScopes,
|
|
101
|
+
...(options.validators !== undefined
|
|
102
|
+
? { validators: options.validators }
|
|
103
|
+
: {}),
|
|
95
104
|
clock: clockFn,
|
|
96
105
|
realtime: hub,
|
|
97
106
|
}),
|