@openlfcp/shared-objects 0.1.2 → 0.1.4
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 +2 -2
- package/dist/automerge-bytes.d.ts +40 -0
- package/dist/automerge-bytes.js +111 -0
- package/dist/canonical.d.ts +38 -0
- package/dist/canonical.js +613 -0
- package/dist/chunk-limits.d.ts +58 -0
- package/dist/chunk-limits.js +457 -0
- package/dist/data-profile.d.ts +170 -0
- package/dist/data-profile.js +349 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +15 -0
- package/dist/profile-invalid.d.ts +12 -0
- package/dist/profile-invalid.js +15 -0
- package/dist/references.d.ts +28 -0
- package/dist/references.js +220 -0
- package/dist/replica.d.ts +277 -0
- package/dist/replica.js +1066 -0
- package/dist/task.d.ts +159 -0
- package/dist/task.js +192 -0
- package/dist/validate.d.ts +70 -0
- package/dist/validate.js +196 -0
- package/dist/values.d.ts +52 -0
- package/dist/values.js +163 -0
- package/dist/wasm.d.ts +33 -0
- package/dist/wasm.js +58 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -20,9 +20,9 @@ reaches the replica (§95). The replica has no Markdown or editor concepts.
|
|
|
20
20
|
|
|
21
21
|
Part of the OpenLFCP TypeScript SDK, which implements the OpenLFCP MVP 0.1
|
|
22
22
|
subset of [LFCP-WIRE-01](https://github.com/openlfcp/spec/blob/main/wire/LFCP-WIRE-01.md)
|
|
23
|
-
at `mvp-0.1-baseline.
|
|
23
|
+
at `mvp-0.1-baseline.10`, not every deferred WIRE-01 feature. It does not
|
|
24
24
|
claim full LFCP-WIRE-01 conformance. This is the MVP 0.1 release
|
|
25
|
-
(`0.1.
|
|
25
|
+
(`0.1.4`, npm dist-tag `latest`). Until 1.0, minor versions may change APIs.
|
|
26
26
|
|
|
27
27
|
## Automerge and WebAssembly
|
|
28
28
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type ParsedChange } from "./canonical.js";
|
|
2
|
+
/** A checked Automerge change: its bytes and decoded header. */
|
|
3
|
+
export interface CheckedChange {
|
|
4
|
+
readonly bytes: Uint8Array;
|
|
5
|
+
/** Hex SHA-256 change hash (the Automerge change identity). */
|
|
6
|
+
readonly hash: string;
|
|
7
|
+
/** Hex actor ID. */
|
|
8
|
+
readonly actor: string;
|
|
9
|
+
readonly seq: number;
|
|
10
|
+
/** Hex hashes of the changes this one depends on. */
|
|
11
|
+
readonly deps: readonly string[];
|
|
12
|
+
/** Hex IDs of the other actors the change refers to (§11.1: each must be known to the document). */
|
|
13
|
+
readonly otherActors: readonly string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Checks that `bytes` is exactly one valid Automerge change (§11, §12) and
|
|
17
|
+
* returns it decoded. Rejects with PROFILE_INVALID / INVALID_AUTOMERGE_BYTES: a chunk that is not a
|
|
18
|
+
* change (a document chunk is a full save, §13), bytes Automerge cannot
|
|
19
|
+
* parse, trailing bytes such as a second concatenated change, and a
|
|
20
|
+
* checksum that does not match the change hash. Automerge JS 3.5.0 parses
|
|
21
|
+
* a change without verifying its checksum, so the check is done here.
|
|
22
|
+
*/
|
|
23
|
+
export declare function checkChange(bytes: Uint8Array): CheckedChange;
|
|
24
|
+
/** The change as its canonical bytes give it (§11.3), reusing the walk of checkChange. */
|
|
25
|
+
export declare function parsedOf(change: CheckedChange): ParsedChange;
|
|
26
|
+
/**
|
|
27
|
+
* Checks that `bytes` starts as an Automerge full save (a document chunk,
|
|
28
|
+
* §13). Whether the whole image loads, checksums included, is decided by
|
|
29
|
+
* Automerge's load.
|
|
30
|
+
*/
|
|
31
|
+
export declare function checkSaveHeader(bytes: Uint8Array): void;
|
|
32
|
+
/** §11: the Data Unit plaintext [1, change] of one checked Automerge change. */
|
|
33
|
+
export declare function frameChange(change: Uint8Array): Uint8Array;
|
|
34
|
+
/** §11: the one Automerge change a Data Unit plaintext carries, checked. Throws PROFILE_INVALID / INVALID_AUTOMERGE_BYTES. */
|
|
35
|
+
export declare function unframeChange(plaintext: Uint8Array): CheckedChange;
|
|
36
|
+
/** §13: the Snapshot plaintext [1, save] of an Automerge full save. */
|
|
37
|
+
export declare function frameSnapshot(save: Uint8Array): Uint8Array;
|
|
38
|
+
/** §13: the Automerge full save a Snapshot plaintext carries (header checked). Throws PROFILE_INVALID / INVALID_AUTOMERGE_BYTES. */
|
|
39
|
+
export declare function unframeSnapshot(plaintext: Uint8Array): Uint8Array;
|
|
40
|
+
//# sourceMappingURL=automerge-bytes.d.ts.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import * as A from "@automerge/automerge";
|
|
2
|
+
import { toHex } from "@openlfcp/core";
|
|
3
|
+
import { checkCanonicalChange } from "./canonical.js";
|
|
4
|
+
import { checkChangeExpansion } from "./chunk-limits.js";
|
|
5
|
+
import { ProfileInvalidError } from "./profile-invalid.js";
|
|
6
|
+
import { frameProfilePayload, unframeProfilePayload } from "./values.js";
|
|
7
|
+
/**
|
|
8
|
+
* Automerge byte checks of the SHARED-OBJECTS-PROFILE-01 payloads (LFCP-031):
|
|
9
|
+
* a Data Unit plaintext carries exactly one Automerge change (§11, §12), a
|
|
10
|
+
* Snapshot plaintext one Automerge full save (§13). The framing itself is
|
|
11
|
+
* frameProfilePayload / unframeProfilePayload (LFCP-030); this adds the
|
|
12
|
+
* check that the framed bytes are what they claim to be.
|
|
13
|
+
*
|
|
14
|
+
* Automerge storage chunks start with the magic bytes 85 6f 4a 83, a 4-byte
|
|
15
|
+
* checksum (the first 4 bytes of the chunk hash) and a chunk type: 0 for a
|
|
16
|
+
* document (a full save), 1 for a change, 2 for a compressed change.
|
|
17
|
+
*/
|
|
18
|
+
const MAGIC = [0x85, 0x6f, 0x4a, 0x83];
|
|
19
|
+
const CHUNK_DOCUMENT = 0;
|
|
20
|
+
const CHUNK_CHANGE = 1;
|
|
21
|
+
const CHUNK_COMPRESSED_CHANGE = 2;
|
|
22
|
+
const HEADER = 9; // magic, checksum, chunk type
|
|
23
|
+
/** §11, §13, §74.1: PROFILE_INVALID / INVALID_AUTOMERGE_BYTES. */
|
|
24
|
+
const reject = (why) => {
|
|
25
|
+
throw new ProfileInvalidError("INVALID_AUTOMERGE_BYTES", why);
|
|
26
|
+
};
|
|
27
|
+
function chunkType(bytes, what) {
|
|
28
|
+
if (bytes.length < HEADER || MAGIC.some((b, i) => bytes[i] !== b))
|
|
29
|
+
return reject(`${what} is not an Automerge chunk`);
|
|
30
|
+
return bytes[8];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Checks that `bytes` is exactly one valid Automerge change (§11, §12) and
|
|
34
|
+
* returns it decoded. Rejects with PROFILE_INVALID / INVALID_AUTOMERGE_BYTES: a chunk that is not a
|
|
35
|
+
* change (a document chunk is a full save, §13), bytes Automerge cannot
|
|
36
|
+
* parse, trailing bytes such as a second concatenated change, and a
|
|
37
|
+
* checksum that does not match the change hash. Automerge JS 3.5.0 parses
|
|
38
|
+
* a change without verifying its checksum, so the check is done here.
|
|
39
|
+
*/
|
|
40
|
+
export function checkChange(bytes) {
|
|
41
|
+
const type = chunkType(bytes, "the change");
|
|
42
|
+
if (type === CHUNK_COMPRESSED_CHANGE)
|
|
43
|
+
reject("a compressed Automerge change (chunk type 2): a change is uncompressed (§11)");
|
|
44
|
+
if (type !== CHUNK_CHANGE)
|
|
45
|
+
reject(`chunk type ${type} is not an Automerge change (§11)`);
|
|
46
|
+
// §11.1: what the change expands to and its structure, before Automerge decodes it.
|
|
47
|
+
const expansion = checkChangeExpansion(bytes);
|
|
48
|
+
// §11.3: the canonical encoding, by the format's properties, still before the engine.
|
|
49
|
+
const parsed = checkCanonicalChange(bytes);
|
|
50
|
+
let decoded;
|
|
51
|
+
try {
|
|
52
|
+
decoded = A.decodeChange(bytes);
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
return reject(`invalid Automerge change bytes (§11): ${e.message}`);
|
|
56
|
+
}
|
|
57
|
+
if (toHex(bytes.subarray(4, 8)) !== decoded.hash.slice(0, 8))
|
|
58
|
+
reject("the Automerge change checksum does not match its hash (§11)");
|
|
59
|
+
const checked = Object.freeze({
|
|
60
|
+
bytes: Uint8Array.from(bytes),
|
|
61
|
+
hash: decoded.hash,
|
|
62
|
+
actor: decoded.actor,
|
|
63
|
+
seq: decoded.seq,
|
|
64
|
+
deps: Object.freeze([...decoded.deps]),
|
|
65
|
+
otherActors: expansion.otherActors,
|
|
66
|
+
});
|
|
67
|
+
PARSED.set(checked, parsed);
|
|
68
|
+
return checked;
|
|
69
|
+
}
|
|
70
|
+
/** The operations checkChange read from the bytes (§11.3), for the references of §11.4. */
|
|
71
|
+
const PARSED = new WeakMap();
|
|
72
|
+
/** The change as its canonical bytes give it (§11.3), reusing the walk of checkChange. */
|
|
73
|
+
export function parsedOf(change) {
|
|
74
|
+
let p = PARSED.get(change);
|
|
75
|
+
if (p === undefined) {
|
|
76
|
+
p = checkCanonicalChange(change.bytes);
|
|
77
|
+
PARSED.set(change, p);
|
|
78
|
+
}
|
|
79
|
+
return p;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Checks that `bytes` starts as an Automerge full save (a document chunk,
|
|
83
|
+
* §13). Whether the whole image loads, checksums included, is decided by
|
|
84
|
+
* Automerge's load.
|
|
85
|
+
*/
|
|
86
|
+
export function checkSaveHeader(bytes) {
|
|
87
|
+
const type = chunkType(bytes, "the Snapshot image");
|
|
88
|
+
if (type !== CHUNK_DOCUMENT)
|
|
89
|
+
reject(`chunk type ${type} is not an Automerge document (full save, §13)`);
|
|
90
|
+
}
|
|
91
|
+
/** §11: the Data Unit plaintext [1, change] of one checked Automerge change. */
|
|
92
|
+
export function frameChange(change) {
|
|
93
|
+
checkChange(change);
|
|
94
|
+
return frameProfilePayload(change);
|
|
95
|
+
}
|
|
96
|
+
/** §11: the one Automerge change a Data Unit plaintext carries, checked. Throws PROFILE_INVALID / INVALID_AUTOMERGE_BYTES. */
|
|
97
|
+
export function unframeChange(plaintext) {
|
|
98
|
+
return checkChange(unframeProfilePayload(plaintext));
|
|
99
|
+
}
|
|
100
|
+
/** §13: the Snapshot plaintext [1, save] of an Automerge full save. */
|
|
101
|
+
export function frameSnapshot(save) {
|
|
102
|
+
checkSaveHeader(save);
|
|
103
|
+
return frameProfilePayload(save);
|
|
104
|
+
}
|
|
105
|
+
/** §13: the Automerge full save a Snapshot plaintext carries (header checked). Throws PROFILE_INVALID / INVALID_AUTOMERGE_BYTES. */
|
|
106
|
+
export function unframeSnapshot(plaintext) {
|
|
107
|
+
const save = unframeProfilePayload(plaintext);
|
|
108
|
+
checkSaveHeader(save);
|
|
109
|
+
return save;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=automerge-bytes.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** An operation of a change, as the references of §11.4 read it. */
|
|
2
|
+
export interface ParsedOp {
|
|
3
|
+
/** "_root", or the creating operation's ID "counter@actorhex". */
|
|
4
|
+
readonly obj: string;
|
|
5
|
+
/** A property name, the head of a sequence, or an element ID. */
|
|
6
|
+
readonly key: {
|
|
7
|
+
readonly kind: "prop";
|
|
8
|
+
readonly name: string;
|
|
9
|
+
} | {
|
|
10
|
+
readonly kind: "head";
|
|
11
|
+
} | {
|
|
12
|
+
readonly kind: "elem";
|
|
13
|
+
readonly id: string;
|
|
14
|
+
};
|
|
15
|
+
readonly insert: boolean;
|
|
16
|
+
/** 0 makeMap, 1 set, 2 makeList, 3 del, 4 makeText, 5 inc, 6 makeTable, 7 mark. */
|
|
17
|
+
readonly action: number;
|
|
18
|
+
/** Predecessor IDs "counter@actorhex", in order. */
|
|
19
|
+
readonly pred: readonly string[];
|
|
20
|
+
}
|
|
21
|
+
/** A change read from its bytes (§11.3), without the engine. */
|
|
22
|
+
export interface ParsedChange {
|
|
23
|
+
readonly actor: string;
|
|
24
|
+
readonly seq: number;
|
|
25
|
+
readonly startOp: number;
|
|
26
|
+
/** Hex dependency hashes, ascending. */
|
|
27
|
+
readonly deps: readonly string[];
|
|
28
|
+
/** Hex other actors, ascending: actor index i is otherActors[i - 1]. */
|
|
29
|
+
readonly otherActors: readonly string[];
|
|
30
|
+
readonly ops: readonly ParsedOp[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* §11.3: checks that `bytes` (one change chunk within the §11.1 limits) is
|
|
34
|
+
* the canonical encoding of its change, and returns the change's operations
|
|
35
|
+
* read from it. Throws PROFILE_INVALID / INVALID_AUTOMERGE_BYTES.
|
|
36
|
+
*/
|
|
37
|
+
export declare function checkCanonicalChange(bytes: Uint8Array): ParsedChange;
|
|
38
|
+
//# sourceMappingURL=canonical.d.ts.map
|