@korajs/sync 1.0.0-beta.11 → 1.0.0-beta.13
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 +30 -40
- package/dist/chunk-5S6U7EVU.js +344 -0
- package/dist/chunk-5S6U7EVU.js.map +1 -0
- package/dist/index.cjs +6671 -1082
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1625 -96
- package/dist/index.d.ts +1625 -96
- package/dist/index.js +6382 -1100
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +313 -2
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +117 -3
- package/dist/internal.d.ts +117 -3
- package/dist/internal.js +17 -3
- package/dist/internal.js.map +1 -1
- package/dist/transport-Bemtqwep.d.cts +1448 -0
- package/dist/transport-Bemtqwep.d.ts +1448 -0
- package/package.json +3 -3
- package/dist/chunk-TU345YUC.js +0 -23
- package/dist/chunk-TU345YUC.js.map +0 -1
- package/dist/transport-iXX0KA_W.d.cts +0 -727
- package/dist/transport-iXX0KA_W.d.ts +0 -727
package/dist/internal.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { d as SyncTransport, T as TransportOptions, b as SyncMessage, e as TransportMessageHandler, f as TransportCloseHandler, g as TransportErrorHandler, Q as QueueStorage, M as INVALID_OPERATION_ID } from './transport-Bemtqwep.js';
|
|
2
|
+
export { aF as buildScopeSnapshot, aG as matchesScopePredicate, aH as recordMatchesScopePredicates } from './transport-Bemtqwep.js';
|
|
3
|
+
import { Operation, SchemaDefinition } from '@korajs/core';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* In-memory transport for testing. Provides instant, synchronous message delivery
|
|
@@ -59,10 +60,123 @@ declare function createMemoryTransportPair(): {
|
|
|
59
60
|
*/
|
|
60
61
|
declare class MemoryQueueStorage implements QueueStorage {
|
|
61
62
|
private operations;
|
|
63
|
+
private readonly sent;
|
|
62
64
|
load(): Promise<Operation[]>;
|
|
63
65
|
enqueue(op: Operation): Promise<void>;
|
|
64
66
|
dequeue(ids: string[]): Promise<void>;
|
|
67
|
+
markSent(ops: Operation[]): Promise<void>;
|
|
68
|
+
loadSentIds(): Promise<string[]>;
|
|
65
69
|
count(): Promise<number>;
|
|
66
70
|
}
|
|
67
71
|
|
|
68
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Canonical key of a downlink scope map (SYNC-11). The client keys its per-view
|
|
74
|
+
* delivery watermark by the scope the server ACCEPTED and reports that scope's key at
|
|
75
|
+
* the next handshake; the server computes the key of the scope it resolves now and
|
|
76
|
+
* resumes the delivery stream from the reported watermark only when the two match.
|
|
77
|
+
* Both sides must therefore compute it the same way, from scopes that may have been
|
|
78
|
+
* re-ordered or round-tripped through JSON: object keys are sorted, `undefined` entries
|
|
79
|
+
* dropped, and `$in` lists de-duplicated and sorted. No scope (undefined or null) is
|
|
80
|
+
* the empty string.
|
|
81
|
+
*/
|
|
82
|
+
declare function scopeViewKey(scope: Record<string, Record<string, unknown>> | null | undefined): string;
|
|
83
|
+
|
|
84
|
+
/** Outcome of {@link verifyInboundOperation}. */
|
|
85
|
+
type InboundVerification = {
|
|
86
|
+
ok: true;
|
|
87
|
+
/** Whether the id was checked (false: not content-addressed, or not declared). */
|
|
88
|
+
verified: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* The hash version the id matched, when it declared none (`'verify-ids'`): 1, or 2
|
|
91
|
+
* for a version-2 id whose declaration was lost on the way.
|
|
92
|
+
*/
|
|
93
|
+
matchedVersion?: 1 | 2;
|
|
94
|
+
/**
|
|
95
|
+
* False when the id matched only through the schema-dependent rebuild of a
|
|
96
|
+
* beta.12 hash (declared nested object members that were `undefined`): a
|
|
97
|
+
* receiver without the schema cannot repeat that check, so the server must not
|
|
98
|
+
* declare the matched version on the stored copy. Absent: true.
|
|
99
|
+
*/
|
|
100
|
+
declarable?: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* The update's data with each top-level member beta.12 hashed as `undefined`
|
|
103
|
+
* restored as `null` (RT-71): beta.12 cleared those fields locally, and `null`
|
|
104
|
+
* hashes identically under version 1, so the stored copy carries the write the
|
|
105
|
+
* beta.12 client made, under the same verified id. Present only when the id
|
|
106
|
+
* matched through that rebuild.
|
|
107
|
+
*/
|
|
108
|
+
restoredData?: Operation['data'];
|
|
109
|
+
} | {
|
|
110
|
+
ok: false;
|
|
111
|
+
code: typeof INVALID_OPERATION_ID;
|
|
112
|
+
message: string;
|
|
113
|
+
};
|
|
114
|
+
/** How {@link verifyInboundOperation} treats an operation that declares no hash version. */
|
|
115
|
+
type AbsentHashVersionPolicy =
|
|
116
|
+
/**
|
|
117
|
+
* Not checked (clients). The server declares the version of every id it verified
|
|
118
|
+
* (1 or 2) when it stores the operation; an absent version marks an id the server
|
|
119
|
+
* could not verify: a log row from before beta.13, a server-side schema transform
|
|
120
|
+
* (which rewrites data under the original id), or a protocol-1 encrypted payload.
|
|
121
|
+
*/
|
|
122
|
+
'skip'
|
|
123
|
+
/**
|
|
124
|
+
* Checked (the server, on upload: omitting the version skips nothing): the id must be
|
|
125
|
+
* the operation's version-1 or version-2 content hash.
|
|
126
|
+
*/
|
|
127
|
+
| 'verify-ids';
|
|
128
|
+
/**
|
|
129
|
+
* Verify a delivered (or uploaded) operation's content-addressed id (CORE-1, RT-64).
|
|
130
|
+
* Runs AFTER decryption, on the plaintext the id was computed over, and before any
|
|
131
|
+
* transform or apply. A mismatch means the operation was altered after it was created
|
|
132
|
+
* (in transit, by a relay, or a ciphertext swap that kept its id), or that its id was
|
|
133
|
+
* chosen rather than computed (squatting an id the server derives).
|
|
134
|
+
*
|
|
135
|
+
* Rules:
|
|
136
|
+
* - Scope entries and server-derived operations (reserved `kora:` nodes, no declared hash
|
|
137
|
+
* version: keyed HMAC ids) are not checked. A reserved-node operation that declares a
|
|
138
|
+
* hash version (a server route write) is checked like any other.
|
|
139
|
+
* - An operation that arrived in an encryption envelope is always checked, against
|
|
140
|
+
* the hash version it declares (bound into the envelope's authenticated data).
|
|
141
|
+
* - A plaintext operation declaring hash version 2 or 1 is checked against that version.
|
|
142
|
+
* Version 1 covers type, collection, recordId, data, timestamp, nodeId and atomicOps;
|
|
143
|
+
* binary values are accepted in either of their two forms (bytes, or the canonical
|
|
144
|
+
* `{ $koraBytes }` form a JSON round trip produces), since a version-1 hash was
|
|
145
|
+
* computed over whichever form the writer held.
|
|
146
|
+
* - A plaintext operation declaring no version follows `absentVersion`: skipped, or
|
|
147
|
+
* accepted when its id is its version-1 or version-2 content hash.
|
|
148
|
+
* - An unknown declared version fails closed.
|
|
149
|
+
*
|
|
150
|
+
* @param op - The operation, decrypted
|
|
151
|
+
* @param context - Whether it arrived in an encryption envelope, and the policy for an
|
|
152
|
+
* operation that declares no hash version (default `'skip'`)
|
|
153
|
+
* @returns ok, or the refusal with a code and an explanation
|
|
154
|
+
*/
|
|
155
|
+
declare function verifyInboundOperation(op: Operation, context: {
|
|
156
|
+
encrypted: boolean;
|
|
157
|
+
absentVersion?: AbsentHashVersionPolicy;
|
|
158
|
+
/** The schema, for rebuilding beta.12 hashes of `undefined` nested members (RT-71). */
|
|
159
|
+
schema?: SchemaDefinition | null;
|
|
160
|
+
}): Promise<InboundVerification>;
|
|
161
|
+
/**
|
|
162
|
+
* Whether the operation's id is its content hash under its declared version (absent:
|
|
163
|
+
* 1). A version-1 id is also accepted over the other form of its binary values, and
|
|
164
|
+
* over the beta.12 form of `undefined` members (see {@link matchOperationId}).
|
|
165
|
+
*
|
|
166
|
+
* @param op - The operation (plaintext)
|
|
167
|
+
* @param schema - Optional schema, to rebuild declared nested members
|
|
168
|
+
* @returns true when the id is the content hash
|
|
169
|
+
*/
|
|
170
|
+
declare function operationIdMatches(op: Operation, schema?: SchemaDefinition | null): Promise<boolean>;
|
|
171
|
+
/**
|
|
172
|
+
* A beta.12 update's data with every `previousData` key it lacks restored as `null`
|
|
173
|
+
* (RT-71). beta.12 writes `previousData[key]` for every key it validated, so such a key
|
|
174
|
+
* held `undefined` in `data`: hashed as `null`, applied locally as a cleared field, and
|
|
175
|
+
* dropped by the JSON upload. Returns `op.data` itself when there is none to restore.
|
|
176
|
+
*
|
|
177
|
+
* @param op - A plaintext operation
|
|
178
|
+
* @returns The restored data, or `op.data` unchanged
|
|
179
|
+
*/
|
|
180
|
+
declare function restoreUndefinedFromPrevious(op: Operation): Operation['data'];
|
|
181
|
+
|
|
182
|
+
export { type InboundVerification, MemoryQueueStorage, MemoryTransport, createMemoryTransportPair, operationIdMatches, restoreUndefinedFromPrevious, scopeViewKey, verifyInboundOperation };
|
package/dist/internal.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
-
MemoryQueueStorage
|
|
3
|
-
|
|
2
|
+
MemoryQueueStorage,
|
|
3
|
+
buildScopeSnapshot,
|
|
4
|
+
matchesScopePredicate,
|
|
5
|
+
operationIdMatches,
|
|
6
|
+
recordMatchesScopePredicates,
|
|
7
|
+
restoreUndefinedFromPrevious,
|
|
8
|
+
scopeViewKey,
|
|
9
|
+
verifyInboundOperation
|
|
10
|
+
} from "./chunk-5S6U7EVU.js";
|
|
4
11
|
|
|
5
12
|
// src/transport/memory-transport.ts
|
|
6
13
|
import { SyncError } from "@korajs/core";
|
|
@@ -97,6 +104,13 @@ function createMemoryTransportPair() {
|
|
|
97
104
|
export {
|
|
98
105
|
MemoryQueueStorage,
|
|
99
106
|
MemoryTransport,
|
|
100
|
-
|
|
107
|
+
buildScopeSnapshot,
|
|
108
|
+
createMemoryTransportPair,
|
|
109
|
+
matchesScopePredicate,
|
|
110
|
+
operationIdMatches,
|
|
111
|
+
recordMatchesScopePredicates,
|
|
112
|
+
restoreUndefinedFromPrevious,
|
|
113
|
+
scopeViewKey,
|
|
114
|
+
verifyInboundOperation
|
|
101
115
|
};
|
|
102
116
|
//# sourceMappingURL=internal.js.map
|
package/dist/internal.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/transport/memory-transport.ts"],"sourcesContent":["import { SyncError } from '@korajs/core'\nimport type { SyncMessage } from '../protocol/messages'\nimport type {\n\tSyncTransport,\n\tTransportCloseHandler,\n\tTransportErrorHandler,\n\tTransportMessageHandler,\n\tTransportOptions,\n} from './transport'\n\n/**\n * In-memory transport for testing. Provides instant, synchronous message delivery\n * between a linked pair of transports (simulating client ↔ server).\n */\nexport class MemoryTransport implements SyncTransport {\n\tprivate connected = false\n\tprivate messageHandler: TransportMessageHandler | null = null\n\tprivate closeHandler: TransportCloseHandler | null = null\n\tprivate errorHandler: TransportErrorHandler | null = null\n\tprivate peer: MemoryTransport | null = null\n\tprivate readonly sentMessages: SyncMessage[] = []\n\n\t/** Link this transport to its peer (the other end of the connection) */\n\tlinkPeer(peer: MemoryTransport): void {\n\t\tthis.peer = peer\n\t}\n\n\tasync connect(_url: string, _options?: TransportOptions): Promise<void> {\n\t\tif (!this.peer) {\n\t\t\tthrow new SyncError('MemoryTransport has no linked peer', {\n\t\t\t\treason: 'not-linked',\n\t\t\t})\n\t\t}\n\t\tthis.connected = true\n\t\tthis.peer.connected = true\n\t}\n\n\tasync disconnect(): Promise<void> {\n\t\tif (!this.connected) return\n\t\tthis.connected = false\n\t\t// Notify peer that we disconnected\n\t\tif (this.peer?.connected) {\n\t\t\tthis.peer.connected = false\n\t\t\tthis.peer.closeHandler?.('peer disconnected')\n\t\t}\n\t}\n\n\tsend(message: SyncMessage): void {\n\t\tif (!this.connected) {\n\t\t\tthrow new SyncError('Cannot send message: transport is not connected', {\n\t\t\t\tmessageType: message.type,\n\t\t\t})\n\t\t}\n\t\tthis.sentMessages.push(message)\n\t\t// Deliver to peer's message handler\n\t\tthis.peer?.messageHandler?.(message)\n\t}\n\n\tonMessage(handler: TransportMessageHandler): void {\n\t\tthis.messageHandler = handler\n\t}\n\n\tonClose(handler: TransportCloseHandler): void {\n\t\tthis.closeHandler = handler\n\t}\n\n\tonError(handler: TransportErrorHandler): void {\n\t\tthis.errorHandler = handler\n\t}\n\n\tisConnected(): boolean {\n\t\treturn this.connected\n\t}\n\n\t// --- Testing helpers ---\n\n\t/**\n\t * Simulate an incoming message (as if received from peer).\n\t * Useful for testing without a linked peer.\n\t */\n\tsimulateIncoming(message: SyncMessage): void {\n\t\tthis.messageHandler?.(message)\n\t}\n\n\t/**\n\t * Simulate a disconnect from the remote side.\n\t */\n\tsimulateDisconnect(reason: string): void {\n\t\tthis.connected = false\n\t\tthis.closeHandler?.(reason)\n\t}\n\n\t/**\n\t * Simulate a transport error.\n\t */\n\tsimulateError(error: Error): void {\n\t\tthis.errorHandler?.(error)\n\t}\n\n\t/**\n\t * Get all messages sent through this transport.\n\t */\n\tgetSentMessages(): SyncMessage[] {\n\t\treturn [...this.sentMessages]\n\t}\n\n\t/**\n\t * Clear the sent messages history.\n\t */\n\tclearSentMessages(): void {\n\t\tthis.sentMessages.length = 0\n\t}\n}\n\n/**\n * Create a linked pair of memory transports for testing.\n * Messages sent on `client` arrive at `server` and vice versa.\n */\nexport function createMemoryTransportPair(): { client: MemoryTransport; server: MemoryTransport } {\n\tconst client = new MemoryTransport()\n\tconst server = new MemoryTransport()\n\tclient.linkPeer(server)\n\tserver.linkPeer(client)\n\treturn { client, server }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/transport/memory-transport.ts"],"sourcesContent":["import { SyncError } from '@korajs/core'\nimport type { SyncMessage } from '../protocol/messages'\nimport type {\n\tSyncTransport,\n\tTransportCloseHandler,\n\tTransportErrorHandler,\n\tTransportMessageHandler,\n\tTransportOptions,\n} from './transport'\n\n/**\n * In-memory transport for testing. Provides instant, synchronous message delivery\n * between a linked pair of transports (simulating client ↔ server).\n */\nexport class MemoryTransport implements SyncTransport {\n\tprivate connected = false\n\tprivate messageHandler: TransportMessageHandler | null = null\n\tprivate closeHandler: TransportCloseHandler | null = null\n\tprivate errorHandler: TransportErrorHandler | null = null\n\tprivate peer: MemoryTransport | null = null\n\tprivate readonly sentMessages: SyncMessage[] = []\n\n\t/** Link this transport to its peer (the other end of the connection) */\n\tlinkPeer(peer: MemoryTransport): void {\n\t\tthis.peer = peer\n\t}\n\n\tasync connect(_url: string, _options?: TransportOptions): Promise<void> {\n\t\tif (!this.peer) {\n\t\t\tthrow new SyncError('MemoryTransport has no linked peer', {\n\t\t\t\treason: 'not-linked',\n\t\t\t})\n\t\t}\n\t\tthis.connected = true\n\t\tthis.peer.connected = true\n\t}\n\n\tasync disconnect(): Promise<void> {\n\t\tif (!this.connected) return\n\t\tthis.connected = false\n\t\t// Notify peer that we disconnected\n\t\tif (this.peer?.connected) {\n\t\t\tthis.peer.connected = false\n\t\t\tthis.peer.closeHandler?.('peer disconnected')\n\t\t}\n\t}\n\n\tsend(message: SyncMessage): void {\n\t\tif (!this.connected) {\n\t\t\tthrow new SyncError('Cannot send message: transport is not connected', {\n\t\t\t\tmessageType: message.type,\n\t\t\t})\n\t\t}\n\t\tthis.sentMessages.push(message)\n\t\t// Deliver to peer's message handler\n\t\tthis.peer?.messageHandler?.(message)\n\t}\n\n\tonMessage(handler: TransportMessageHandler): void {\n\t\tthis.messageHandler = handler\n\t}\n\n\tonClose(handler: TransportCloseHandler): void {\n\t\tthis.closeHandler = handler\n\t}\n\n\tonError(handler: TransportErrorHandler): void {\n\t\tthis.errorHandler = handler\n\t}\n\n\tisConnected(): boolean {\n\t\treturn this.connected\n\t}\n\n\t// --- Testing helpers ---\n\n\t/**\n\t * Simulate an incoming message (as if received from peer).\n\t * Useful for testing without a linked peer.\n\t */\n\tsimulateIncoming(message: SyncMessage): void {\n\t\tthis.messageHandler?.(message)\n\t}\n\n\t/**\n\t * Simulate a disconnect from the remote side.\n\t */\n\tsimulateDisconnect(reason: string): void {\n\t\tthis.connected = false\n\t\tthis.closeHandler?.(reason)\n\t}\n\n\t/**\n\t * Simulate a transport error.\n\t */\n\tsimulateError(error: Error): void {\n\t\tthis.errorHandler?.(error)\n\t}\n\n\t/**\n\t * Get all messages sent through this transport.\n\t */\n\tgetSentMessages(): SyncMessage[] {\n\t\treturn [...this.sentMessages]\n\t}\n\n\t/**\n\t * Clear the sent messages history.\n\t */\n\tclearSentMessages(): void {\n\t\tthis.sentMessages.length = 0\n\t}\n}\n\n/**\n * Create a linked pair of memory transports for testing.\n * Messages sent on `client` arrive at `server` and vice versa.\n */\nexport function createMemoryTransportPair(): { client: MemoryTransport; server: MemoryTransport } {\n\tconst client = new MemoryTransport()\n\tconst server = new MemoryTransport()\n\tclient.linkPeer(server)\n\tserver.linkPeer(client)\n\treturn { client, server }\n}\n"],"mappings":";;;;;;;;;;;;AAAA,SAAS,iBAAiB;AAcnB,IAAM,kBAAN,MAA+C;AAAA,EAC7C,YAAY;AAAA,EACZ,iBAAiD;AAAA,EACjD,eAA6C;AAAA,EAC7C,eAA6C;AAAA,EAC7C,OAA+B;AAAA,EACtB,eAA8B,CAAC;AAAA;AAAA,EAGhD,SAAS,MAA6B;AACrC,SAAK,OAAO;AAAA,EACb;AAAA,EAEA,MAAM,QAAQ,MAAc,UAA4C;AACvE,QAAI,CAAC,KAAK,MAAM;AACf,YAAM,IAAI,UAAU,sCAAsC;AAAA,QACzD,QAAQ;AAAA,MACT,CAAC;AAAA,IACF;AACA,SAAK,YAAY;AACjB,SAAK,KAAK,YAAY;AAAA,EACvB;AAAA,EAEA,MAAM,aAA4B;AACjC,QAAI,CAAC,KAAK,UAAW;AACrB,SAAK,YAAY;AAEjB,QAAI,KAAK,MAAM,WAAW;AACzB,WAAK,KAAK,YAAY;AACtB,WAAK,KAAK,eAAe,mBAAmB;AAAA,IAC7C;AAAA,EACD;AAAA,EAEA,KAAK,SAA4B;AAChC,QAAI,CAAC,KAAK,WAAW;AACpB,YAAM,IAAI,UAAU,mDAAmD;AAAA,QACtE,aAAa,QAAQ;AAAA,MACtB,CAAC;AAAA,IACF;AACA,SAAK,aAAa,KAAK,OAAO;AAE9B,SAAK,MAAM,iBAAiB,OAAO;AAAA,EACpC;AAAA,EAEA,UAAU,SAAwC;AACjD,SAAK,iBAAiB;AAAA,EACvB;AAAA,EAEA,QAAQ,SAAsC;AAC7C,SAAK,eAAe;AAAA,EACrB;AAAA,EAEA,QAAQ,SAAsC;AAC7C,SAAK,eAAe;AAAA,EACrB;AAAA,EAEA,cAAuB;AACtB,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,iBAAiB,SAA4B;AAC5C,SAAK,iBAAiB,OAAO;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmB,QAAsB;AACxC,SAAK,YAAY;AACjB,SAAK,eAAe,MAAM;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,OAAoB;AACjC,SAAK,eAAe,KAAK;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAiC;AAChC,WAAO,CAAC,GAAG,KAAK,YAAY;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA,EAKA,oBAA0B;AACzB,SAAK,aAAa,SAAS;AAAA,EAC5B;AACD;AAMO,SAAS,4BAAkF;AACjG,QAAM,SAAS,IAAI,gBAAgB;AACnC,QAAM,SAAS,IAAI,gBAAgB;AACnC,SAAO,SAAS,MAAM;AACtB,SAAO,SAAS,MAAM;AACtB,SAAO,EAAE,QAAQ,OAAO;AACzB;","names":[]}
|