@syncular/server 0.0.1 → 0.0.2-126
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 +25 -0
- package/dist/blobs/adapters/database.d.ts.map +1 -1
- package/dist/blobs/adapters/database.js +25 -3
- package/dist/blobs/adapters/database.js.map +1 -1
- package/dist/blobs/adapters/filesystem.d.ts +31 -0
- package/dist/blobs/adapters/filesystem.d.ts.map +1 -0
- package/dist/blobs/adapters/filesystem.js +140 -0
- package/dist/blobs/adapters/filesystem.js.map +1 -0
- package/dist/blobs/adapters/s3.d.ts +3 -2
- package/dist/blobs/adapters/s3.d.ts.map +1 -1
- package/dist/blobs/adapters/s3.js +49 -0
- package/dist/blobs/adapters/s3.js.map +1 -1
- package/dist/blobs/index.d.ts +1 -0
- package/dist/blobs/index.d.ts.map +1 -1
- package/dist/blobs/index.js +6 -5
- package/dist/blobs/index.js.map +1 -1
- package/dist/clients.d.ts +1 -0
- package/dist/clients.d.ts.map +1 -1
- package/dist/clients.js.map +1 -1
- package/dist/compaction.d.ts +1 -1
- package/dist/compaction.js +1 -1
- package/dist/dialect/base.d.ts +83 -0
- package/dist/dialect/base.d.ts.map +1 -0
- package/dist/dialect/base.js +144 -0
- package/dist/dialect/base.js.map +1 -0
- package/dist/dialect/helpers.d.ts +10 -0
- package/dist/dialect/helpers.d.ts.map +1 -0
- package/dist/dialect/helpers.js +59 -0
- package/dist/dialect/helpers.js.map +1 -0
- package/dist/dialect/index.d.ts +2 -0
- package/dist/dialect/index.d.ts.map +1 -1
- package/dist/dialect/index.js +3 -1
- package/dist/dialect/index.js.map +1 -1
- package/dist/dialect/types.d.ts +38 -46
- package/dist/dialect/types.d.ts.map +1 -1
- package/dist/{shapes → handlers}/create-handler.d.ts +18 -5
- package/dist/handlers/create-handler.d.ts.map +1 -0
- package/dist/{shapes → handlers}/create-handler.js +140 -43
- package/dist/handlers/create-handler.js.map +1 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/index.js +4 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/handlers/registry.d.ts.map +1 -0
- package/dist/handlers/registry.js.map +1 -0
- package/dist/{shapes → handlers}/types.d.ts +7 -7
- package/dist/{shapes → handlers}/types.d.ts.map +1 -1
- package/dist/{shapes → handlers}/types.js.map +1 -1
- package/dist/helpers/conflict.d.ts +1 -1
- package/dist/helpers/conflict.d.ts.map +1 -1
- package/dist/helpers/emitted-change.d.ts +1 -1
- package/dist/helpers/emitted-change.d.ts.map +1 -1
- package/dist/helpers/index.js +4 -4
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -16
- package/dist/index.js.map +1 -1
- package/dist/notify.d.ts +47 -0
- package/dist/notify.d.ts.map +1 -0
- package/dist/notify.js +85 -0
- package/dist/notify.js.map +1 -0
- package/dist/proxy/handler.d.ts +1 -1
- package/dist/proxy/handler.d.ts.map +1 -1
- package/dist/proxy/handler.js +15 -11
- package/dist/proxy/handler.js.map +1 -1
- package/dist/proxy/index.d.ts +2 -2
- package/dist/proxy/index.d.ts.map +1 -1
- package/dist/proxy/index.js +3 -3
- package/dist/proxy/index.js.map +1 -1
- package/dist/proxy/mutation-detector.d.ts +4 -0
- package/dist/proxy/mutation-detector.d.ts.map +1 -1
- package/dist/proxy/mutation-detector.js +209 -24
- package/dist/proxy/mutation-detector.js.map +1 -1
- package/dist/proxy/oplog.d.ts +2 -1
- package/dist/proxy/oplog.d.ts.map +1 -1
- package/dist/proxy/oplog.js +15 -9
- package/dist/proxy/oplog.js.map +1 -1
- package/dist/proxy/registry.d.ts +0 -11
- package/dist/proxy/registry.d.ts.map +1 -1
- package/dist/proxy/registry.js +0 -24
- package/dist/proxy/registry.js.map +1 -1
- package/dist/proxy/types.d.ts +2 -0
- package/dist/proxy/types.d.ts.map +1 -1
- package/dist/pull.d.ts +4 -3
- package/dist/pull.d.ts.map +1 -1
- package/dist/pull.js +565 -314
- package/dist/pull.js.map +1 -1
- package/dist/push.d.ts +15 -3
- package/dist/push.d.ts.map +1 -1
- package/dist/push.js +359 -229
- package/dist/push.js.map +1 -1
- package/dist/realtime/index.js +1 -1
- package/dist/realtime/types.d.ts +2 -0
- package/dist/realtime/types.d.ts.map +1 -1
- package/dist/schema.d.ts +11 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/snapshot-chunks/db-metadata.d.ts +6 -1
- package/dist/snapshot-chunks/db-metadata.d.ts.map +1 -1
- package/dist/snapshot-chunks/db-metadata.js +261 -92
- package/dist/snapshot-chunks/db-metadata.js.map +1 -1
- package/dist/snapshot-chunks/index.d.ts +0 -1
- package/dist/snapshot-chunks/index.d.ts.map +1 -1
- package/dist/snapshot-chunks/index.js +2 -3
- package/dist/snapshot-chunks/index.js.map +1 -1
- package/dist/snapshot-chunks/types.d.ts +20 -5
- package/dist/snapshot-chunks/types.d.ts.map +1 -1
- package/dist/snapshot-chunks.d.ts +12 -8
- package/dist/snapshot-chunks.d.ts.map +1 -1
- package/dist/snapshot-chunks.js +40 -12
- package/dist/snapshot-chunks.js.map +1 -1
- package/dist/subscriptions/index.js +1 -1
- package/dist/subscriptions/resolve.d.ts +6 -6
- package/dist/subscriptions/resolve.d.ts.map +1 -1
- package/dist/subscriptions/resolve.js +53 -14
- package/dist/subscriptions/resolve.js.map +1 -1
- package/package.json +28 -7
- package/src/blobs/adapters/database.test.ts +67 -0
- package/src/blobs/adapters/database.ts +34 -9
- package/src/blobs/adapters/filesystem.test.ts +132 -0
- package/src/blobs/adapters/filesystem.ts +189 -0
- package/src/blobs/adapters/s3.test.ts +522 -0
- package/src/blobs/adapters/s3.ts +55 -2
- package/src/blobs/index.ts +1 -0
- package/src/clients.ts +1 -0
- package/src/compaction.ts +1 -1
- package/src/dialect/base.ts +292 -0
- package/src/dialect/helpers.ts +61 -0
- package/src/dialect/index.ts +2 -0
- package/src/dialect/types.ts +50 -54
- package/src/{shapes → handlers}/create-handler.ts +219 -64
- package/src/{shapes → handlers}/types.ts +10 -7
- package/src/helpers/conflict.ts +1 -1
- package/src/helpers/emitted-change.ts +1 -1
- package/src/index.ts +2 -1
- package/src/notify.test.ts +516 -0
- package/src/notify.ts +131 -0
- package/src/proxy/handler.test.ts +120 -0
- package/src/proxy/handler.ts +18 -10
- package/src/proxy/index.ts +2 -1
- package/src/proxy/mutation-detector.test.ts +71 -0
- package/src/proxy/mutation-detector.ts +227 -29
- package/src/proxy/oplog.ts +19 -10
- package/src/proxy/registry.ts +0 -33
- package/src/proxy/types.ts +2 -0
- package/src/pull.ts +788 -405
- package/src/push.ts +507 -312
- package/src/realtime/types.ts +2 -0
- package/src/schema.ts +11 -1
- package/src/snapshot-chunks/db-metadata.test.ts +169 -0
- package/src/snapshot-chunks/db-metadata.ts +347 -105
- package/src/snapshot-chunks/index.ts +0 -1
- package/src/snapshot-chunks/types.ts +31 -5
- package/src/snapshot-chunks.ts +60 -21
- package/src/subscriptions/resolve.ts +73 -18
- package/dist/shapes/create-handler.d.ts.map +0 -1
- package/dist/shapes/create-handler.js.map +0 -1
- package/dist/shapes/index.d.ts.map +0 -1
- package/dist/shapes/index.js +0 -4
- package/dist/shapes/index.js.map +0 -1
- package/dist/shapes/registry.d.ts.map +0 -1
- package/dist/shapes/registry.js.map +0 -1
- package/dist/snapshot-chunks/adapters/s3.d.ts +0 -63
- package/dist/snapshot-chunks/adapters/s3.d.ts.map +0 -1
- package/dist/snapshot-chunks/adapters/s3.js +0 -50
- package/dist/snapshot-chunks/adapters/s3.js.map +0 -1
- package/src/snapshot-chunks/adapters/s3.ts +0 -68
- /package/dist/{shapes → handlers}/index.d.ts +0 -0
- /package/dist/{shapes → handlers}/registry.d.ts +0 -0
- /package/dist/{shapes → handlers}/registry.js +0 -0
- /package/dist/{shapes → handlers}/types.js +0 -0
- /package/src/{shapes → handlers}/index.ts +0 -0
- /package/src/{shapes → handlers}/registry.ts +0 -0
package/src/realtime/types.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
export interface SyncRealtimeCommitEvent {
|
|
8
8
|
type: 'commit';
|
|
9
9
|
commitSeq: number;
|
|
10
|
+
/** Logical partition key (tenant / demo / workspace). */
|
|
11
|
+
partitionId?: string;
|
|
10
12
|
/**
|
|
11
13
|
* Optional scopes affected by the commit.
|
|
12
14
|
* Broadcasters may omit this to keep payloads small (listeners can look up in DB).
|
package/src/schema.ts
CHANGED
|
@@ -16,6 +16,8 @@ import type { Generated } from 'kysely';
|
|
|
16
16
|
export interface SyncCommitsTable {
|
|
17
17
|
/** Monotonic commit sequence (server-assigned) */
|
|
18
18
|
commit_seq: Generated<number>;
|
|
19
|
+
/** Logical partition key (tenant / demo / workspace) */
|
|
20
|
+
partition_id: string;
|
|
19
21
|
/** Actor who produced the commit */
|
|
20
22
|
actor_id: string;
|
|
21
23
|
/** Client/device identifier */
|
|
@@ -43,6 +45,8 @@ export interface SyncCommitsTable {
|
|
|
43
45
|
export interface SyncChangesTable {
|
|
44
46
|
/** Monotonic change id */
|
|
45
47
|
change_id: Generated<number>;
|
|
48
|
+
/** Logical partition key (tenant / demo / workspace) */
|
|
49
|
+
partition_id: string;
|
|
46
50
|
/** Commit sequence this change belongs to */
|
|
47
51
|
commit_seq: number;
|
|
48
52
|
/** Table name being changed */
|
|
@@ -66,6 +70,8 @@ export interface SyncChangesTable {
|
|
|
66
70
|
* Per-client cursor tracking (for pruning + observability).
|
|
67
71
|
*/
|
|
68
72
|
export interface SyncClientCursorsTable {
|
|
73
|
+
/** Logical partition key (tenant / demo / workspace) */
|
|
74
|
+
partition_id: string;
|
|
69
75
|
/** Client/device identifier */
|
|
70
76
|
client_id: string;
|
|
71
77
|
/** Actor currently associated with the client */
|
|
@@ -87,6 +93,8 @@ export interface SyncClientCursorsTable {
|
|
|
87
93
|
export interface SyncSnapshotChunksTable {
|
|
88
94
|
/** Opaque chunk id */
|
|
89
95
|
chunk_id: string;
|
|
96
|
+
/** Logical partition key (tenant / demo / workspace) */
|
|
97
|
+
partition_id: string;
|
|
90
98
|
/** Effective scope key this chunk belongs to */
|
|
91
99
|
scope_key: string;
|
|
92
100
|
/** Scope identifier */
|
|
@@ -97,7 +105,7 @@ export interface SyncSnapshotChunksTable {
|
|
|
97
105
|
row_cursor: string;
|
|
98
106
|
/** Snapshot row limit used to produce this chunk */
|
|
99
107
|
row_limit: number;
|
|
100
|
-
/** Row encoding (e.g. '
|
|
108
|
+
/** Row encoding (e.g. 'json-row-frame-v1') */
|
|
101
109
|
encoding: string;
|
|
102
110
|
/** Compression algorithm (e.g. 'gzip') */
|
|
103
111
|
compression: string;
|
|
@@ -122,6 +130,8 @@ export interface SyncSnapshotChunksTable {
|
|
|
122
130
|
* the (much larger) change log.
|
|
123
131
|
*/
|
|
124
132
|
export interface SyncTableCommitsTable {
|
|
133
|
+
/** Logical partition key (tenant / demo / workspace) */
|
|
134
|
+
partition_id: string;
|
|
125
135
|
table: string;
|
|
126
136
|
commit_seq: number;
|
|
127
137
|
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
|
2
|
+
import type { BlobStorageAdapter } from '@syncular/core';
|
|
3
|
+
import { type Kysely, sql } from 'kysely';
|
|
4
|
+
import { createBunSqliteDb } from '../../../dialect-bun-sqlite/src';
|
|
5
|
+
import { createSqliteServerDialect } from '../../../server-dialect-sqlite/src';
|
|
6
|
+
import { ensureSyncSchema } from '../migrate';
|
|
7
|
+
import type { SyncCoreDb } from '../schema';
|
|
8
|
+
import { createDbMetadataChunkStorage } from './db-metadata';
|
|
9
|
+
|
|
10
|
+
interface TestDb extends SyncCoreDb {}
|
|
11
|
+
|
|
12
|
+
function createInMemoryBlobAdapter(): BlobStorageAdapter {
|
|
13
|
+
const store = new Map<string, Uint8Array>();
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
name: 'memory',
|
|
17
|
+
async signUpload() {
|
|
18
|
+
return { url: 'https://example.test/upload', method: 'PUT' };
|
|
19
|
+
},
|
|
20
|
+
async signDownload() {
|
|
21
|
+
return 'https://example.test/download';
|
|
22
|
+
},
|
|
23
|
+
async exists(hash) {
|
|
24
|
+
return store.has(hash);
|
|
25
|
+
},
|
|
26
|
+
async delete(hash) {
|
|
27
|
+
store.delete(hash);
|
|
28
|
+
},
|
|
29
|
+
async put(hash, data) {
|
|
30
|
+
store.set(hash, new Uint8Array(data));
|
|
31
|
+
},
|
|
32
|
+
async get(hash) {
|
|
33
|
+
const value = store.get(hash);
|
|
34
|
+
return value ? new Uint8Array(value) : null;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function createBodyStream(
|
|
40
|
+
chunks: readonly Uint8Array[]
|
|
41
|
+
): ReadableStream<Uint8Array> {
|
|
42
|
+
return new ReadableStream<Uint8Array>({
|
|
43
|
+
start(controller) {
|
|
44
|
+
for (const chunk of chunks) {
|
|
45
|
+
controller.enqueue(chunk);
|
|
46
|
+
}
|
|
47
|
+
controller.close();
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('createDbMetadataChunkStorage', () => {
|
|
53
|
+
let db: Kysely<TestDb>;
|
|
54
|
+
|
|
55
|
+
beforeEach(async () => {
|
|
56
|
+
db = createBunSqliteDb<TestDb>({ path: ':memory:' });
|
|
57
|
+
await ensureSyncSchema(db, createSqliteServerDialect());
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
afterEach(async () => {
|
|
61
|
+
await db.destroy();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('returns the persisted chunk id on page-key conflicts', async () => {
|
|
65
|
+
const storage = createDbMetadataChunkStorage({
|
|
66
|
+
db,
|
|
67
|
+
blobAdapter: createInMemoryBlobAdapter(),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const body = new Uint8Array([1, 2, 3, 4]);
|
|
71
|
+
const expiresAt = new Date(Date.now() + 60_000).toISOString();
|
|
72
|
+
|
|
73
|
+
const first = await storage.storeChunk({
|
|
74
|
+
partitionId: 'p1',
|
|
75
|
+
scopeKey: 'scope-key',
|
|
76
|
+
scope: 'tasks',
|
|
77
|
+
asOfCommitSeq: 1,
|
|
78
|
+
rowCursor: null,
|
|
79
|
+
rowLimit: 100,
|
|
80
|
+
encoding: 'json-row-frame-v1',
|
|
81
|
+
compression: 'gzip',
|
|
82
|
+
sha256: 'chunk-sha',
|
|
83
|
+
expiresAt,
|
|
84
|
+
body,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const second = await storage.storeChunk({
|
|
88
|
+
partitionId: 'p1',
|
|
89
|
+
scopeKey: 'scope-key',
|
|
90
|
+
scope: 'tasks',
|
|
91
|
+
asOfCommitSeq: 1,
|
|
92
|
+
rowCursor: null,
|
|
93
|
+
rowLimit: 100,
|
|
94
|
+
encoding: 'json-row-frame-v1',
|
|
95
|
+
compression: 'gzip',
|
|
96
|
+
sha256: 'chunk-sha',
|
|
97
|
+
expiresAt: new Date(Date.now() + 120_000).toISOString(),
|
|
98
|
+
body,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(second.id).toBe(first.id);
|
|
102
|
+
|
|
103
|
+
const chunkBody = await storage.readChunk(second.id);
|
|
104
|
+
expect(chunkBody).toEqual(body);
|
|
105
|
+
|
|
106
|
+
const countResult = await sql<{ count: number }>`
|
|
107
|
+
select count(*) as count
|
|
108
|
+
from ${sql.table('sync_snapshot_chunks')}
|
|
109
|
+
`.execute(db);
|
|
110
|
+
|
|
111
|
+
expect(Number(countResult.rows[0]?.count ?? 0)).toBe(1);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('disables blob checksums for chunk stream writes', async () => {
|
|
115
|
+
const putStreamMetadata: Array<Record<string, unknown> | undefined> = [];
|
|
116
|
+
const blobAdapter: BlobStorageAdapter = {
|
|
117
|
+
name: 'memory-stream',
|
|
118
|
+
async signUpload() {
|
|
119
|
+
return { url: 'https://example.test/upload', method: 'PUT' };
|
|
120
|
+
},
|
|
121
|
+
async signDownload() {
|
|
122
|
+
return 'https://example.test/download';
|
|
123
|
+
},
|
|
124
|
+
async exists() {
|
|
125
|
+
return false;
|
|
126
|
+
},
|
|
127
|
+
async delete() {},
|
|
128
|
+
async put() {},
|
|
129
|
+
async get() {
|
|
130
|
+
return null;
|
|
131
|
+
},
|
|
132
|
+
async putStream(_hash, stream, metadata) {
|
|
133
|
+
putStreamMetadata.push(metadata);
|
|
134
|
+
const reader = stream.getReader();
|
|
135
|
+
try {
|
|
136
|
+
while (true) {
|
|
137
|
+
const { done } = await reader.read();
|
|
138
|
+
if (done) break;
|
|
139
|
+
}
|
|
140
|
+
} finally {
|
|
141
|
+
reader.releaseLock();
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const storage = createDbMetadataChunkStorage({
|
|
147
|
+
db,
|
|
148
|
+
blobAdapter,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
await storage.storeChunkStream({
|
|
152
|
+
partitionId: 'p1',
|
|
153
|
+
scopeKey: 'scope-key',
|
|
154
|
+
scope: 'tasks',
|
|
155
|
+
asOfCommitSeq: 1,
|
|
156
|
+
rowCursor: null,
|
|
157
|
+
rowLimit: 100,
|
|
158
|
+
encoding: 'json-row-frame-v1',
|
|
159
|
+
compression: 'gzip',
|
|
160
|
+
sha256: 'chunk-sha',
|
|
161
|
+
expiresAt: new Date(Date.now() + 60_000).toISOString(),
|
|
162
|
+
byteLength: 4,
|
|
163
|
+
bodyStream: createBodyStream([new Uint8Array([1, 2, 3, 4])]),
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(putStreamMetadata).toHaveLength(1);
|
|
167
|
+
expect(putStreamMetadata[0]?.disableChecksum).toBe(true);
|
|
168
|
+
});
|
|
169
|
+
});
|