@syncular/server 0.0.1 → 0.0.2-127
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
|
@@ -4,31 +4,33 @@
|
|
|
4
4
|
* Separates chunk metadata (in database) from chunk body (in blob storage).
|
|
5
5
|
* Enables flexible storage backends (database, S3, R2, etc.)
|
|
6
6
|
*/
|
|
7
|
-
import type { SyncSnapshotChunkRef } from '@syncular/core';
|
|
7
|
+
import type { SyncSnapshotChunkCompression, SyncSnapshotChunkEncoding, SyncSnapshotChunkRef } from '@syncular/core';
|
|
8
8
|
/**
|
|
9
9
|
* Page key for identifying a specific chunk
|
|
10
10
|
*/
|
|
11
11
|
export interface SnapshotChunkPageKey {
|
|
12
|
+
partitionId: string;
|
|
12
13
|
scopeKey: string;
|
|
13
14
|
scope: string;
|
|
14
15
|
asOfCommitSeq: number;
|
|
15
16
|
rowCursor: string | null;
|
|
16
17
|
rowLimit: number;
|
|
17
|
-
encoding:
|
|
18
|
-
compression:
|
|
18
|
+
encoding: SyncSnapshotChunkEncoding;
|
|
19
|
+
compression: SyncSnapshotChunkCompression;
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
21
22
|
* Metadata stored in the database for each chunk
|
|
22
23
|
*/
|
|
23
24
|
export interface SnapshotChunkMetadata {
|
|
24
25
|
chunkId: string;
|
|
26
|
+
partitionId: string;
|
|
25
27
|
scopeKey: string;
|
|
26
28
|
scope: string;
|
|
27
29
|
asOfCommitSeq: number;
|
|
28
30
|
rowCursor: string | null;
|
|
29
31
|
rowLimit: number;
|
|
30
|
-
encoding:
|
|
31
|
-
compression:
|
|
32
|
+
encoding: SyncSnapshotChunkEncoding;
|
|
33
|
+
compression: SyncSnapshotChunkCompression;
|
|
32
34
|
sha256: string;
|
|
33
35
|
byteLength: number;
|
|
34
36
|
blobHash: string;
|
|
@@ -47,10 +49,23 @@ export interface SnapshotChunkStorage {
|
|
|
47
49
|
storeChunk(metadata: Omit<SnapshotChunkMetadata, 'chunkId' | 'byteLength' | 'blobHash'> & {
|
|
48
50
|
body: Uint8Array;
|
|
49
51
|
}): Promise<SyncSnapshotChunkRef>;
|
|
52
|
+
/**
|
|
53
|
+
* Store a chunk from a stream.
|
|
54
|
+
* Preferred for large payloads to avoid full buffering in memory.
|
|
55
|
+
*/
|
|
56
|
+
storeChunkStream?(metadata: Omit<SnapshotChunkMetadata, 'chunkId' | 'byteLength' | 'blobHash'> & {
|
|
57
|
+
bodyStream: ReadableStream<Uint8Array>;
|
|
58
|
+
byteLength?: number;
|
|
59
|
+
}): Promise<SyncSnapshotChunkRef>;
|
|
50
60
|
/**
|
|
51
61
|
* Read chunk body by chunk ID
|
|
52
62
|
*/
|
|
53
63
|
readChunk(chunkId: string): Promise<Uint8Array | null>;
|
|
64
|
+
/**
|
|
65
|
+
* Read chunk body as a stream.
|
|
66
|
+
* Preferred for large payloads to avoid full buffering in memory.
|
|
67
|
+
*/
|
|
68
|
+
readChunkStream?(chunkId: string): Promise<ReadableStream<Uint8Array> | null>;
|
|
54
69
|
/**
|
|
55
70
|
* Find existing chunk by page key
|
|
56
71
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/snapshot-chunks/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/snapshot-chunks/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,4BAA4B,EAC5B,yBAAyB,EACzB,oBAAoB,EACrB,MAAM,gBAAgB,CAAC;AAExB;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,yBAAyB,CAAC;IACpC,WAAW,EAAE,4BAA4B,CAAC;CAC3C;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,yBAAyB,CAAC;IACpC,WAAW,EAAE,4BAA4B,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,2BAA2B;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,UAAU,CACR,QAAQ,EAAE,IAAI,CACZ,qBAAqB,EACrB,SAAS,GAAG,YAAY,GAAG,UAAU,CACtC,GAAG;QACF,IAAI,EAAE,UAAU,CAAC;KAClB,GACA,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAEjC;;;OAGG;IACH,gBAAgB,CAAC,CACf,QAAQ,EAAE,IAAI,CACZ,qBAAqB,EACrB,SAAS,GAAG,YAAY,GAAG,UAAU,CACtC,GAAG;QACF,UAAU,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;QACvC,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GACA,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAEjC;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAEvD;;;OAGG;IACH,eAAe,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC;IAE9E;;OAEG;IACH,SAAS,CACP,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IAExC;;OAEG;IACH,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACpD"}
|
|
@@ -4,30 +4,32 @@
|
|
|
4
4
|
* Used for efficiently serving large bootstrap snapshots (e.g. catalogs)
|
|
5
5
|
* without embedding huge JSON payloads into pull responses.
|
|
6
6
|
*/
|
|
7
|
-
import type
|
|
7
|
+
import { type SyncSnapshotChunkCompression, type SyncSnapshotChunkEncoding, type SyncSnapshotChunkRef } from '@syncular/core';
|
|
8
8
|
import { type Kysely } from 'kysely';
|
|
9
9
|
import type { SyncCoreDb } from './schema';
|
|
10
10
|
export interface SnapshotChunkPageKey {
|
|
11
|
+
partitionId: string;
|
|
11
12
|
scopeKey: string;
|
|
12
13
|
scope: string;
|
|
13
14
|
asOfCommitSeq: number;
|
|
14
15
|
rowCursor: string | null;
|
|
15
16
|
rowLimit: number;
|
|
16
|
-
encoding:
|
|
17
|
-
compression:
|
|
17
|
+
encoding: SyncSnapshotChunkEncoding;
|
|
18
|
+
compression: SyncSnapshotChunkCompression;
|
|
18
19
|
}
|
|
19
20
|
export interface SnapshotChunkRow {
|
|
20
21
|
chunkId: string;
|
|
22
|
+
partitionId: string;
|
|
21
23
|
scopeKey: string;
|
|
22
24
|
scope: string;
|
|
23
25
|
asOfCommitSeq: number;
|
|
24
26
|
rowCursor: string;
|
|
25
27
|
rowLimit: number;
|
|
26
|
-
encoding:
|
|
27
|
-
compression:
|
|
28
|
+
encoding: SyncSnapshotChunkEncoding;
|
|
29
|
+
compression: SyncSnapshotChunkCompression;
|
|
28
30
|
sha256: string;
|
|
29
31
|
byteLength: number;
|
|
30
|
-
body: Uint8Array
|
|
32
|
+
body: Uint8Array | ReadableStream<Uint8Array>;
|
|
31
33
|
expiresAt: string;
|
|
32
34
|
}
|
|
33
35
|
export declare function readSnapshotChunkRefByPageKey<DB extends SyncCoreDb>(db: Kysely<DB>, args: SnapshotChunkPageKey & {
|
|
@@ -35,13 +37,14 @@ export declare function readSnapshotChunkRefByPageKey<DB extends SyncCoreDb>(db:
|
|
|
35
37
|
}): Promise<SyncSnapshotChunkRef | null>;
|
|
36
38
|
export declare function insertSnapshotChunk<DB extends SyncCoreDb>(db: Kysely<DB>, args: {
|
|
37
39
|
chunkId: string;
|
|
40
|
+
partitionId: string;
|
|
38
41
|
scopeKey: string;
|
|
39
42
|
scope: string;
|
|
40
43
|
asOfCommitSeq: number;
|
|
41
44
|
rowCursor: string | null;
|
|
42
45
|
rowLimit: number;
|
|
43
|
-
encoding:
|
|
44
|
-
compression:
|
|
46
|
+
encoding: SyncSnapshotChunkEncoding;
|
|
47
|
+
compression: SyncSnapshotChunkCompression;
|
|
45
48
|
sha256: string;
|
|
46
49
|
body: Uint8Array;
|
|
47
50
|
expiresAt: string;
|
|
@@ -50,6 +53,7 @@ export declare function readSnapshotChunk<DB extends SyncCoreDb>(db: Kysely<DB>,
|
|
|
50
53
|
/** External chunk storage for reading from S3/R2/etc */
|
|
51
54
|
chunkStorage?: {
|
|
52
55
|
readChunk(chunkId: string): Promise<Uint8Array | null>;
|
|
56
|
+
readChunkStream?(chunkId: string): Promise<ReadableStream<Uint8Array> | null>;
|
|
53
57
|
};
|
|
54
58
|
}): Promise<SnapshotChunkRow | null>;
|
|
55
59
|
export declare function deleteExpiredSnapshotChunks<DB extends SyncCoreDb>(db: Kysely<DB>, nowIso?: string): Promise<number>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot-chunks.d.ts","sourceRoot":"","sources":["../src/snapshot-chunks.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"snapshot-chunks.d.ts","sourceRoot":"","sources":["../src/snapshot-chunks.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAGL,KAAK,4BAA4B,EACjC,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,EAC1B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,MAAM,EAAO,MAAM,QAAQ,CAAC;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,yBAAyB,CAAC;IACpC,WAAW,EAAE,4BAA4B,CAAC;CAC3C;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,yBAAyB,CAAC;IACpC,WAAW,EAAE,4BAA4B,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,UAAU,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;IAC9C,SAAS,EAAE,MAAM,CAAC;CACnB;AAqBD,wBAAsB,6BAA6B,CAAC,EAAE,SAAS,UAAU,EACvE,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,EACd,IAAI,EAAE,oBAAoB,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/C,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CA+CtC;AAED,wBAAsB,mBAAmB,CAAC,EAAE,SAAS,UAAU,EAC7D,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,EACd,IAAI,EAAE;IACJ,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,yBAAyB,CAAC;IACpC,WAAW,EAAE,4BAA4B,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,UAAU,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,GACA,OAAO,CAAC,oBAAoB,CAAC,CAyE/B;AAED,wBAAsB,iBAAiB,CAAC,EAAE,SAAS,UAAU,EAC3D,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,EACd,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;IACR,wDAAwD;IACxD,YAAY,CAAC,EAAE;QACb,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;QACvD,eAAe,CAAC,CACd,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC;KAC/C,CAAC;CACH,GACA,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAkGlC;AAED,wBAAsB,2BAA2B,CAAC,EAAE,SAAS,UAAU,EACrE,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,EACd,MAAM,SAA2B,GAChC,OAAO,CAAC,MAAM,CAAC,CAOjB"}
|
package/dist/snapshot-chunks.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Used for efficiently serving large bootstrap snapshots (e.g. catalogs)
|
|
5
5
|
* without embedding huge JSON payloads into pull responses.
|
|
6
6
|
*/
|
|
7
|
+
import { SYNC_SNAPSHOT_CHUNK_COMPRESSION, SYNC_SNAPSHOT_CHUNK_ENCODING, } from '@syncular/core';
|
|
7
8
|
import { sql } from 'kysely';
|
|
8
9
|
function coerceChunkRow(value) {
|
|
9
10
|
// pg returns Buffer (subclass of Uint8Array); sqlite returns Uint8Array
|
|
@@ -32,7 +33,8 @@ export async function readSnapshotChunkRefByPageKey(db, args) {
|
|
|
32
33
|
select chunk_id, sha256, byte_length, encoding, compression
|
|
33
34
|
from ${sql.table('sync_snapshot_chunks')}
|
|
34
35
|
where
|
|
35
|
-
|
|
36
|
+
partition_id = ${args.partitionId}
|
|
37
|
+
and scope_key = ${args.scopeKey}
|
|
36
38
|
and scope = ${args.scope}
|
|
37
39
|
and as_of_commit_seq = ${args.asOfCommitSeq}
|
|
38
40
|
and row_cursor = ${rowCursorKey}
|
|
@@ -45,10 +47,10 @@ export async function readSnapshotChunkRefByPageKey(db, args) {
|
|
|
45
47
|
const row = rowResult.rows[0];
|
|
46
48
|
if (!row)
|
|
47
49
|
return null;
|
|
48
|
-
if (row.encoding !==
|
|
50
|
+
if (row.encoding !== SYNC_SNAPSHOT_CHUNK_ENCODING) {
|
|
49
51
|
throw new Error(`Unexpected snapshot chunk encoding: ${String(row.encoding)}`);
|
|
50
52
|
}
|
|
51
|
-
if (row.compression !==
|
|
53
|
+
if (row.compression !== SYNC_SNAPSHOT_CHUNK_COMPRESSION) {
|
|
52
54
|
throw new Error(`Unexpected snapshot chunk compression: ${String(row.compression)}`);
|
|
53
55
|
}
|
|
54
56
|
return {
|
|
@@ -67,6 +69,7 @@ export async function insertSnapshotChunk(db, args) {
|
|
|
67
69
|
await sql `
|
|
68
70
|
insert into ${sql.table('sync_snapshot_chunks')} (
|
|
69
71
|
chunk_id,
|
|
72
|
+
partition_id,
|
|
70
73
|
scope_key,
|
|
71
74
|
scope,
|
|
72
75
|
as_of_commit_seq,
|
|
@@ -83,6 +86,7 @@ export async function insertSnapshotChunk(db, args) {
|
|
|
83
86
|
)
|
|
84
87
|
values (
|
|
85
88
|
${args.chunkId},
|
|
89
|
+
${args.partitionId},
|
|
86
90
|
${args.scopeKey},
|
|
87
91
|
${args.scope},
|
|
88
92
|
${args.asOfCommitSeq},
|
|
@@ -98,6 +102,7 @@ export async function insertSnapshotChunk(db, args) {
|
|
|
98
102
|
${args.expiresAt}
|
|
99
103
|
)
|
|
100
104
|
on conflict (
|
|
105
|
+
partition_id,
|
|
101
106
|
scope_key,
|
|
102
107
|
scope,
|
|
103
108
|
as_of_commit_seq,
|
|
@@ -111,6 +116,7 @@ export async function insertSnapshotChunk(db, args) {
|
|
|
111
116
|
blob_hash = ${blobHash}
|
|
112
117
|
`.execute(db);
|
|
113
118
|
const ref = await readSnapshotChunkRefByPageKey(db, {
|
|
119
|
+
partitionId: args.partitionId,
|
|
114
120
|
scopeKey: args.scopeKey,
|
|
115
121
|
scope: args.scope,
|
|
116
122
|
asOfCommitSeq: args.asOfCommitSeq,
|
|
@@ -128,6 +134,7 @@ export async function readSnapshotChunk(db, chunkId, options) {
|
|
|
128
134
|
const rowResult = await sql `
|
|
129
135
|
select
|
|
130
136
|
chunk_id,
|
|
137
|
+
partition_id,
|
|
131
138
|
scope_key,
|
|
132
139
|
scope,
|
|
133
140
|
as_of_commit_seq,
|
|
@@ -147,24 +154,44 @@ export async function readSnapshotChunk(db, chunkId, options) {
|
|
|
147
154
|
const row = rowResult.rows[0];
|
|
148
155
|
if (!row)
|
|
149
156
|
return null;
|
|
150
|
-
if (row.encoding !==
|
|
157
|
+
if (row.encoding !== SYNC_SNAPSHOT_CHUNK_ENCODING) {
|
|
151
158
|
throw new Error(`Unexpected snapshot chunk encoding: ${String(row.encoding)}`);
|
|
152
159
|
}
|
|
153
|
-
if (row.compression !==
|
|
160
|
+
if (row.compression !== SYNC_SNAPSHOT_CHUNK_COMPRESSION) {
|
|
154
161
|
throw new Error(`Unexpected snapshot chunk compression: ${String(row.compression)}`);
|
|
155
162
|
}
|
|
156
163
|
// Read body from external storage if available, otherwise use inline body
|
|
157
164
|
let body;
|
|
158
165
|
if (options?.chunkStorage) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
166
|
+
if (options.chunkStorage.readChunkStream) {
|
|
167
|
+
const externalBodyStream = await options.chunkStorage.readChunkStream(chunkId);
|
|
168
|
+
if (externalBodyStream) {
|
|
169
|
+
body = externalBodyStream;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
const externalBody = await options.chunkStorage.readChunk(chunkId);
|
|
173
|
+
if (externalBody) {
|
|
174
|
+
body = externalBody;
|
|
175
|
+
}
|
|
176
|
+
else if (row.body) {
|
|
177
|
+
body = coerceChunkRow(row.body);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
throw new Error(`Snapshot chunk body missing for chunk ${chunkId}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
165
183
|
}
|
|
166
184
|
else {
|
|
167
|
-
|
|
185
|
+
const externalBody = await options.chunkStorage.readChunk(chunkId);
|
|
186
|
+
if (externalBody) {
|
|
187
|
+
body = externalBody;
|
|
188
|
+
}
|
|
189
|
+
else if (row.body) {
|
|
190
|
+
body = coerceChunkRow(row.body);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
throw new Error(`Snapshot chunk body missing for chunk ${chunkId}`);
|
|
194
|
+
}
|
|
168
195
|
}
|
|
169
196
|
}
|
|
170
197
|
else {
|
|
@@ -172,6 +199,7 @@ export async function readSnapshotChunk(db, chunkId, options) {
|
|
|
172
199
|
}
|
|
173
200
|
return {
|
|
174
201
|
chunkId: row.chunk_id,
|
|
202
|
+
partitionId: row.partition_id,
|
|
175
203
|
scopeKey: row.scope_key,
|
|
176
204
|
scope: row.scope,
|
|
177
205
|
asOfCommitSeq: Number(row.as_of_commit_seq ?? 0),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot-chunks.js","sourceRoot":"","sources":["../src/snapshot-chunks.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"snapshot-chunks.js","sourceRoot":"","sources":["../src/snapshot-chunks.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from './resolve';
|
|
1
|
+
export * from './resolve.js';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ScopeValues, type SyncSubscriptionRequest } from '@syncular/core';
|
|
2
2
|
import type { Kysely } from 'kysely';
|
|
3
|
+
import type { TableRegistry } from '../handlers/registry';
|
|
3
4
|
import type { SyncCoreDb } from '../schema';
|
|
4
|
-
import type { TableRegistry } from '../shapes/registry';
|
|
5
5
|
export declare class InvalidSubscriptionScopeError extends Error {
|
|
6
6
|
constructor(message: string);
|
|
7
7
|
}
|
|
@@ -10,7 +10,7 @@ export declare class InvalidSubscriptionScopeError extends Error {
|
|
|
10
10
|
*/
|
|
11
11
|
export interface ResolvedSubscription {
|
|
12
12
|
id: string;
|
|
13
|
-
|
|
13
|
+
table: string;
|
|
14
14
|
scopes: ScopeValues;
|
|
15
15
|
params: Record<string, unknown> | undefined;
|
|
16
16
|
cursor: number;
|
|
@@ -21,8 +21,8 @@ export interface ResolvedSubscription {
|
|
|
21
21
|
* Resolve effective scopes for subscriptions.
|
|
22
22
|
*
|
|
23
23
|
* For each subscription:
|
|
24
|
-
* 1. Look up the
|
|
25
|
-
* 2. Call
|
|
24
|
+
* 1. Look up the table handler by subscription.table
|
|
25
|
+
* 2. Call handler.resolveScopes() to get allowed scopes for this actor
|
|
26
26
|
* 3. Intersect requested scopes with allowed scopes
|
|
27
27
|
* 4. Mark as revoked if no effective scopes
|
|
28
28
|
*/
|
|
@@ -30,6 +30,6 @@ export declare function resolveEffectiveScopesForSubscriptions<DB extends SyncCo
|
|
|
30
30
|
db: Kysely<DB>;
|
|
31
31
|
actorId: string;
|
|
32
32
|
subscriptions: SyncSubscriptionRequest[];
|
|
33
|
-
|
|
33
|
+
handlers: TableRegistry<DB>;
|
|
34
34
|
}): Promise<ResolvedSubscription[]>;
|
|
35
35
|
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/subscriptions/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/subscriptions/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,uBAAuB,EAC7B,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,qBAAa,6BAA8B,SAAQ,KAAK;IACtD,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,uBAAuB,CAAC,gBAAgB,CAAC,CAAC;IAC3D,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAC;CAC9B;AAgGD;;;;;;;;GAQG;AACH,wBAAsB,sCAAsC,CAC1D,EAAE,SAAS,UAAU,EACrB,IAAI,EAAE;IACN,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,uBAAuB,EAAE,CAAC;IACzC,QAAQ,EAAE,aAAa,CAAC,EAAE,CAAC,CAAC;CAC7B,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAoGlC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { extractScopeVars, } from '@syncular/core';
|
|
1
2
|
export class InvalidSubscriptionScopeError extends Error {
|
|
2
3
|
constructor(message) {
|
|
3
4
|
super(message);
|
|
@@ -54,12 +55,35 @@ function scopesEmpty(scopes) {
|
|
|
54
55
|
}
|
|
55
56
|
return true;
|
|
56
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Collect valid scope keys from handler scope patterns.
|
|
60
|
+
*/
|
|
61
|
+
function collectScopeKeys(scopePatterns) {
|
|
62
|
+
const keys = new Set();
|
|
63
|
+
for (const pattern of scopePatterns) {
|
|
64
|
+
for (const key of extractScopeVars(pattern)) {
|
|
65
|
+
keys.add(key);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return keys;
|
|
69
|
+
}
|
|
70
|
+
function validateScopeKeys(args) {
|
|
71
|
+
for (const scopeKey of Object.keys(args.scopeValues)) {
|
|
72
|
+
if (args.validScopeKeys.has(scopeKey)) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const expectedKeys = args.validScopeKeys.size > 0
|
|
76
|
+
? Array.from(args.validScopeKeys).sort().join(', ')
|
|
77
|
+
: '(none)';
|
|
78
|
+
throw new InvalidSubscriptionScopeError(`Invalid scope key "${scopeKey}" in ${args.source} for subscription "${args.subscriptionId}" on table "${args.table}". Expected keys: ${expectedKeys}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
57
81
|
/**
|
|
58
82
|
* Resolve effective scopes for subscriptions.
|
|
59
83
|
*
|
|
60
84
|
* For each subscription:
|
|
61
|
-
* 1. Look up the
|
|
62
|
-
* 2. Call
|
|
85
|
+
* 1. Look up the table handler by subscription.table
|
|
86
|
+
* 2. Call handler.resolveScopes() to get allowed scopes for this actor
|
|
63
87
|
* 3. Intersect requested scopes with allowed scopes
|
|
64
88
|
* 4. Mark as revoked if no effective scopes
|
|
65
89
|
*/
|
|
@@ -74,17 +98,26 @@ export async function resolveEffectiveScopesForSubscriptions(args) {
|
|
|
74
98
|
throw new InvalidSubscriptionScopeError(`Duplicate subscription id: ${sub.id}`);
|
|
75
99
|
}
|
|
76
100
|
seenIds.add(sub.id);
|
|
77
|
-
if (!sub.
|
|
78
|
-
throw new InvalidSubscriptionScopeError(`Subscription ${sub.id} requires a
|
|
101
|
+
if (!sub.table || typeof sub.table !== 'string') {
|
|
102
|
+
throw new InvalidSubscriptionScopeError(`Subscription ${sub.id} requires a table name`);
|
|
79
103
|
}
|
|
80
|
-
const
|
|
81
|
-
if (!
|
|
82
|
-
throw new InvalidSubscriptionScopeError(`Unknown
|
|
104
|
+
const handler = args.handlers.get(sub.table);
|
|
105
|
+
if (!handler) {
|
|
106
|
+
throw new InvalidSubscriptionScopeError(`Unknown table: ${sub.table} for subscription ${sub.id}`);
|
|
83
107
|
}
|
|
84
|
-
|
|
108
|
+
const validScopeKeys = collectScopeKeys(handler.scopePatterns);
|
|
109
|
+
const requested = sub.scopes ?? {};
|
|
110
|
+
validateScopeKeys({
|
|
111
|
+
scopeValues: requested,
|
|
112
|
+
validScopeKeys,
|
|
113
|
+
source: 'requested scopes',
|
|
114
|
+
subscriptionId: sub.id,
|
|
115
|
+
table: sub.table,
|
|
116
|
+
});
|
|
117
|
+
// Get allowed scopes from the handler
|
|
85
118
|
let allowed;
|
|
86
119
|
try {
|
|
87
|
-
allowed = await
|
|
120
|
+
allowed = await handler.resolveScopes({
|
|
88
121
|
db: args.db,
|
|
89
122
|
actorId: args.actorId,
|
|
90
123
|
});
|
|
@@ -92,10 +125,10 @@ export async function resolveEffectiveScopesForSubscriptions(args) {
|
|
|
92
125
|
catch (resolveErr) {
|
|
93
126
|
// Scope resolution failed - mark subscription as revoked
|
|
94
127
|
// rather than failing the entire pull
|
|
95
|
-
console.error(`[resolveScopes] Failed for
|
|
128
|
+
console.error(`[resolveScopes] Failed for table ${sub.table}, subscription ${sub.id}:`, resolveErr);
|
|
96
129
|
out.push({
|
|
97
130
|
id: sub.id,
|
|
98
|
-
|
|
131
|
+
table: sub.table,
|
|
99
132
|
scopes: {},
|
|
100
133
|
params: sub.params,
|
|
101
134
|
cursor: sub.cursor,
|
|
@@ -104,13 +137,19 @@ export async function resolveEffectiveScopesForSubscriptions(args) {
|
|
|
104
137
|
});
|
|
105
138
|
continue;
|
|
106
139
|
}
|
|
140
|
+
validateScopeKeys({
|
|
141
|
+
scopeValues: allowed,
|
|
142
|
+
validScopeKeys,
|
|
143
|
+
source: 'resolveScopes() result',
|
|
144
|
+
subscriptionId: sub.id,
|
|
145
|
+
table: sub.table,
|
|
146
|
+
});
|
|
107
147
|
// Intersect with requested scopes
|
|
108
|
-
const requested = sub.scopes ?? {};
|
|
109
148
|
const effective = intersectScopes(requested, allowed);
|
|
110
149
|
if (scopesEmpty(effective)) {
|
|
111
150
|
out.push({
|
|
112
151
|
id: sub.id,
|
|
113
|
-
|
|
152
|
+
table: sub.table,
|
|
114
153
|
scopes: {},
|
|
115
154
|
params: sub.params,
|
|
116
155
|
cursor: sub.cursor,
|
|
@@ -121,7 +160,7 @@ export async function resolveEffectiveScopesForSubscriptions(args) {
|
|
|
121
160
|
}
|
|
122
161
|
out.push({
|
|
123
162
|
id: sub.id,
|
|
124
|
-
|
|
163
|
+
table: sub.table,
|
|
125
164
|
scopes: effective,
|
|
126
165
|
params: sub.params,
|
|
127
166
|
cursor: sub.cursor,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/subscriptions/resolve.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/subscriptions/resolve.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncular/server",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2-127",
|
|
4
|
+
"description": "Server-side sync engine with push/pull, pruning, and snapshot support",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Benjamin Kniffler",
|
|
7
|
+
"homepage": "https://syncular.dev",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/syncular/syncular.git",
|
|
11
|
+
"directory": "packages/server"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/syncular/syncular/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"sync",
|
|
18
|
+
"offline-first",
|
|
19
|
+
"realtime",
|
|
20
|
+
"database",
|
|
21
|
+
"typescript"
|
|
22
|
+
],
|
|
4
23
|
"private": false,
|
|
5
24
|
"publishConfig": {
|
|
6
25
|
"access": "public"
|
|
@@ -39,18 +58,20 @@
|
|
|
39
58
|
"scripts": {
|
|
40
59
|
"test": "bun test --pass-with-no-tests",
|
|
41
60
|
"tsgo": "tsgo --noEmit",
|
|
42
|
-
"build": "
|
|
61
|
+
"build": "tsgo",
|
|
62
|
+
"release": "bunx syncular-publish"
|
|
43
63
|
},
|
|
44
64
|
"dependencies": {
|
|
45
|
-
"@syncular/core": "
|
|
46
|
-
"zod": "^4.3.6"
|
|
65
|
+
"@syncular/core": "0.0.2-127"
|
|
47
66
|
},
|
|
48
67
|
"peerDependencies": {
|
|
49
|
-
"kysely": "^0.28.0"
|
|
68
|
+
"kysely": "^0.28.0",
|
|
69
|
+
"zod": "^4.0.0"
|
|
50
70
|
},
|
|
51
71
|
"devDependencies": {
|
|
52
|
-
"@syncular/config": "
|
|
53
|
-
"kysely": "*"
|
|
72
|
+
"@syncular/config": "0.0.0",
|
|
73
|
+
"kysely": "*",
|
|
74
|
+
"zod": "*"
|
|
54
75
|
},
|
|
55
76
|
"files": [
|
|
56
77
|
"dist",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { createHmacTokenSigner } from './database';
|
|
3
|
+
|
|
4
|
+
describe('createHmacTokenSigner', () => {
|
|
5
|
+
test('verifies valid signed tokens', async () => {
|
|
6
|
+
const signer = createHmacTokenSigner('test-secret');
|
|
7
|
+
const payload = {
|
|
8
|
+
hash: 'sha256:abc',
|
|
9
|
+
action: 'upload' as const,
|
|
10
|
+
expiresAt: Date.now() + 60_000,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const token = await signer.sign(payload, 60);
|
|
14
|
+
const decoded = await signer.verify(token);
|
|
15
|
+
|
|
16
|
+
expect(decoded).toEqual(payload);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('rejects tampered signatures', async () => {
|
|
20
|
+
const signer = createHmacTokenSigner('test-secret');
|
|
21
|
+
const payload = {
|
|
22
|
+
hash: 'sha256:def',
|
|
23
|
+
action: 'download' as const,
|
|
24
|
+
expiresAt: Date.now() + 60_000,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const token = await signer.sign(payload, 60);
|
|
28
|
+
const [data, sig] = token.split('.');
|
|
29
|
+
if (!data || !sig) {
|
|
30
|
+
throw new Error('Expected signed token with payload and signature');
|
|
31
|
+
}
|
|
32
|
+
const replacement = sig.endsWith('0') ? '1' : '0';
|
|
33
|
+
const tamperedSig = `${sig.slice(0, -1)}${replacement}`;
|
|
34
|
+
const tamperedToken = `${data}.${tamperedSig}`;
|
|
35
|
+
|
|
36
|
+
expect(await signer.verify(tamperedToken)).toBeNull();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('rejects malformed hex signatures', async () => {
|
|
40
|
+
const signer = createHmacTokenSigner('test-secret');
|
|
41
|
+
const payload = {
|
|
42
|
+
hash: 'sha256:ghi',
|
|
43
|
+
action: 'upload' as const,
|
|
44
|
+
expiresAt: Date.now() + 60_000,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const token = await signer.sign(payload, 60);
|
|
48
|
+
const [data] = token.split('.');
|
|
49
|
+
if (!data) {
|
|
50
|
+
throw new Error('Expected signed token payload segment');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
expect(await signer.verify(`${data}.not-hex-signature`)).toBeNull();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('rejects expired tokens', async () => {
|
|
57
|
+
const signer = createHmacTokenSigner('test-secret');
|
|
58
|
+
const payload = {
|
|
59
|
+
hash: 'sha256:jkl',
|
|
60
|
+
action: 'upload' as const,
|
|
61
|
+
expiresAt: Date.now() - 1,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const token = await signer.sign(payload, 60);
|
|
65
|
+
expect(await signer.verify(token)).toBeNull();
|
|
66
|
+
});
|
|
67
|
+
});
|