@openlfcp/shared-objects 0.1.4 → 0.1.5
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/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/references.d.ts +1 -1
- package/dist/references.js +5 -0
- package/dist/replica.d.ts +6 -0
- package/dist/replica.js +31 -27
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export declare const PACKAGE = "@openlfcp/shared-objects";
|
|
|
7
7
|
export { type CheckedChange, checkChange, checkSaveHeader, frameChange, frameSnapshot, unframeChange, unframeSnapshot, } from "./automerge-bytes.js";
|
|
8
8
|
export { CHANGE_LIMITS, type ChunkExpansion, checkChangeExpansion, checkSnapshotExpansion, MAX_DOCUMENT_DEPTH, SNAPSHOT_LIMITS_FLOOR, type SnapshotLimits, } from "./chunk-limits.js";
|
|
9
9
|
export { type SharedObjectsApplyResult, type SharedObjectsBatchResult, type SharedObjectsCheckpoint, type SharedObjectsCodec, SharedObjectsDataProfile, type SharedObjectsDiagnostic, type SharedObjectsExcludeResult, type SharedObjectsSnapshotCodec, type SharedObjectsUnit, } from "./data-profile.js";
|
|
10
|
-
export { type BatchReceiveResult, type BuiltReplica, type LocalChange, type ObjectChange, ObjectIdCollisionError, type ObjectStatus, type ReceiveResult, type ReplicaIntent, type ReplicaOptions, type ReplicaValidation, type ResolveFieldConflict, resolveFieldConflict, SCALAR_FIELDS, type ScalarField, type ScalarView, SharedObjectsReplica, type TaskView, } from "./replica.js";
|
|
10
|
+
export { applyBatchChecked, applyChecked, type BatchReceiveResult, type BuiltReplica, isWasmTrap, type LocalChange, type ObjectChange, ObjectIdCollisionError, type ObjectStatus, type ReceiveResult, type ReplicaIntent, type ReplicaOptions, type ReplicaValidation, type ResolveFieldConflict, resolveFieldConflict, SCALAR_FIELDS, type ScalarField, type ScalarView, SharedObjectsReplica, type TaskView, } from "./replica.js";
|
|
11
11
|
export { addTag, assign, cancel, clearDue, clearScheduled, complete, createTask, deleteTask, type NewTask, type ParsedTask, ProfileError, parseTask, removeTag, reopen, restoreTask, setDue, setPriority, setScheduled, setStatus, setTitle, type Task, type TaskChange, type TaskIntent, type TaskPriority, type TaskStatus, taskToJson, unassign, } from "./task.js";
|
|
12
12
|
export { DIAGNOSTIC_ORDER, firstPerField, isMap, type Json, objectProblems, type ProfileDiagnostic, ProfileInvalidError, type ProfileProblem, pointerToken, type RootValidation, validateRoot, validateTransition, } from "./validate.js";
|
|
13
13
|
export { deriveActorId, FRAMING_VERSION, frameProfilePayload, isLocalDate, isNamespacedValue, isPrincipalRef, isReverseDomain, isUtcTimestamp, PROFILE_ID, type PrincipalRef, parsePrincipalRef, principalRef, unframeProfilePayload, } from "./values.js";
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ export const PACKAGE = "@openlfcp/shared-objects";
|
|
|
7
7
|
export { checkChange, checkSaveHeader, frameChange, frameSnapshot, unframeChange, unframeSnapshot, } from "./automerge-bytes.js";
|
|
8
8
|
export { CHANGE_LIMITS, checkChangeExpansion, checkSnapshotExpansion, MAX_DOCUMENT_DEPTH, SNAPSHOT_LIMITS_FLOOR, } from "./chunk-limits.js";
|
|
9
9
|
export { SharedObjectsDataProfile, } from "./data-profile.js";
|
|
10
|
-
export { ObjectIdCollisionError, resolveFieldConflict, SCALAR_FIELDS, SharedObjectsReplica, } from "./replica.js";
|
|
10
|
+
export { applyBatchChecked, applyChecked, isWasmTrap, ObjectIdCollisionError, resolveFieldConflict, SCALAR_FIELDS, SharedObjectsReplica, } from "./replica.js";
|
|
11
11
|
export { addTag, assign, cancel, clearDue, clearScheduled, complete, createTask, deleteTask, ProfileError, parseTask, removeTag, reopen, restoreTask, setDue, setPriority, setScheduled, setStatus, setTitle, taskToJson, unassign, } from "./task.js";
|
|
12
12
|
export { DIAGNOSTIC_ORDER, firstPerField, isMap, objectProblems, ProfileInvalidError, pointerToken, validateRoot, validateTransition, } from "./validate.js";
|
|
13
13
|
export { deriveActorId, FRAMING_VERSION, frameProfilePayload, isLocalDate, isNamespacedValue, isPrincipalRef, isReverseDomain, isUtcTimestamp, PROFILE_ID, parsePrincipalRef, principalRef, unframeProfilePayload, } from "./values.js";
|
package/dist/references.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as A from "@automerge/automerge";
|
|
2
2
|
import { type ParsedChange } from "./canonical.js";
|
|
3
3
|
/** A §11.4 rule a change breaks. */
|
|
4
|
-
export type ReferenceRule = "R1" | "R2" | "R3" | "R4" | "R5" | "R6" | "R7";
|
|
4
|
+
export type ReferenceRule = "R1" | "R2" | "R3" | "R4" | "R5" | "R6" | "R7" | "R10";
|
|
5
5
|
/**
|
|
6
6
|
* The changes of a document as §11.4 reads them. A replica keeps it current
|
|
7
7
|
* as changes are admitted and committed, or builds it again from its
|
package/dist/references.js
CHANGED
|
@@ -137,6 +137,11 @@ export class ReferenceHistory {
|
|
|
137
137
|
};
|
|
138
138
|
for (const [i, op] of change.ops.entries()) {
|
|
139
139
|
const id = `${change.startOp + i}@${change.actor}`;
|
|
140
|
+
// R10: no operation makes a table. Automerge JS 3.5.0 cannot write a
|
|
141
|
+
// table back out and aborts applying one (differential fuzzing D1); a
|
|
142
|
+
// writer of this profile never makes one.
|
|
143
|
+
if (op.action === 6)
|
|
144
|
+
return "R10";
|
|
140
145
|
// R3: the root, or an object an operation made; its key form.
|
|
141
146
|
let sequence;
|
|
142
147
|
if (op.obj === "_root")
|
package/dist/replica.d.ts
CHANGED
|
@@ -143,6 +143,12 @@ export interface BuiltReplica {
|
|
|
143
143
|
type Doc = A.Doc<Record<string, unknown>>;
|
|
144
144
|
/** §30: an object's maps and lists nest at most this many levels (the field's own map is 1). */
|
|
145
145
|
export declare const MAX_VALUE_DEPTH = 64;
|
|
146
|
+
/**
|
|
147
|
+
* Whether a thrown value is a wasm trap (the engine's module is dead, not
|
|
148
|
+
* the change refused): it is rethrown as it is, never turned into
|
|
149
|
+
* INVALID_AUTOMERGE_BYTES, so a crash-loop breaker sees it.
|
|
150
|
+
*/
|
|
151
|
+
export declare function isWasmTrap(e: unknown): boolean;
|
|
146
152
|
/**
|
|
147
153
|
* Applies one change that passed the receive checks. Automerge can throw
|
|
148
154
|
* after changing the document in place (a change that skips a sequence
|
package/dist/replica.js
CHANGED
|
@@ -314,7 +314,7 @@ const actorHex = (opts) => toHex(deriveActorId(opts.resource, opts.principal));
|
|
|
314
314
|
* the change refused): it is rethrown as it is, never turned into
|
|
315
315
|
* INVALID_AUTOMERGE_BYTES, so a crash-loop breaker sees it.
|
|
316
316
|
*/
|
|
317
|
-
function isWasmTrap(e) {
|
|
317
|
+
export function isWasmTrap(e) {
|
|
318
318
|
const trap = globalThis
|
|
319
319
|
.WebAssembly?.RuntimeError;
|
|
320
320
|
if (trap !== undefined && e instanceof trap)
|
|
@@ -330,7 +330,18 @@ function isWasmTrap(e) {
|
|
|
330
330
|
* is: the module is dead, and only a restart recovers. Exported for tests.
|
|
331
331
|
*/
|
|
332
332
|
export function applyChecked(doc, bytes) {
|
|
333
|
+
// Capture the document's changes BEFORE the apply: a change the engine
|
|
334
|
+
// aborts on (D1: a makeTable it cannot write back) stays in the document's
|
|
335
|
+
// graph or aborts the apply mid-way, so A.getAllChanges(doc) AFTER the
|
|
336
|
+
// error still holds the bad change and rebuilding from it re-aborts
|
|
337
|
+
// ("could not be restored"). Rebuilding from the changes it held before
|
|
338
|
+
// always gives its prior state.
|
|
339
|
+
//
|
|
340
|
+
// Do NOT "simplify" this by applying to A.clone(doc): A.clone gives the
|
|
341
|
+
// clone a new actor id, which breaks local writes (§8) and convergence.
|
|
333
342
|
const before = [...A.getHeads(doc)].sort().join();
|
|
343
|
+
const actor = A.getActorId(doc);
|
|
344
|
+
const good = A.getAllChanges(doc);
|
|
334
345
|
try {
|
|
335
346
|
return { next: A.applyChanges(doc, [bytes])[0] };
|
|
336
347
|
}
|
|
@@ -338,17 +349,21 @@ export function applyChecked(doc, bytes) {
|
|
|
338
349
|
if (isWasmTrap(e))
|
|
339
350
|
throw e;
|
|
340
351
|
const error = e instanceof Error ? e : new Error(String(e));
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
352
|
+
return { restored: restoreFrom(good, actor, before), error };
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/** Rebuilds a document from the changes it held before an aborted apply (never from the aborted one). */
|
|
356
|
+
function restoreFrom(good, actor, before) {
|
|
357
|
+
let restored;
|
|
358
|
+
try {
|
|
359
|
+
[restored] = A.applyChanges(A.init({ actor }), [...good]);
|
|
360
|
+
}
|
|
361
|
+
catch (r) {
|
|
362
|
+
throw new Error(`the replica could not be restored after an Automerge error: ${String(r)}`);
|
|
351
363
|
}
|
|
364
|
+
if ([...A.getHeads(restored)].sort().join() !== before)
|
|
365
|
+
throw new Error("the replica could not be restored after an Automerge error: heads differ");
|
|
366
|
+
return restored;
|
|
352
367
|
}
|
|
353
368
|
/**
|
|
354
369
|
* applyChecked for a batch of admitted changes, in one engine call. On any
|
|
@@ -357,7 +372,10 @@ export function applyChecked(doc, bytes) {
|
|
|
357
372
|
* heads it had. Exported for tests.
|
|
358
373
|
*/
|
|
359
374
|
export function applyBatchChecked(doc, batch) {
|
|
360
|
-
|
|
375
|
+
// See applyChecked: capture the changes before the apply, restore from them.
|
|
376
|
+
const before = [...A.getHeads(doc)].sort().join();
|
|
377
|
+
const actor = A.getActorId(doc);
|
|
378
|
+
const good = A.getAllChanges(doc);
|
|
361
379
|
try {
|
|
362
380
|
return {
|
|
363
381
|
next: A.applyChanges(doc, batch.map((c) => c.bytes))[0],
|
|
@@ -367,22 +385,8 @@ export function applyBatchChecked(doc, batch) {
|
|
|
367
385
|
if (isWasmTrap(e))
|
|
368
386
|
throw e;
|
|
369
387
|
const error = e instanceof Error ? e : new Error(String(e));
|
|
370
|
-
return { restored:
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
function restoreWithout(doc, batch, heads) {
|
|
374
|
-
const out = new Set(batch.map((c) => c.hash));
|
|
375
|
-
const kept = A.getAllChanges(doc).filter((c) => !out.has(A.decodeChange(c).hash));
|
|
376
|
-
let restored;
|
|
377
|
-
try {
|
|
378
|
-
[restored] = A.applyChanges(A.init({ actor: A.getActorId(doc) }), kept);
|
|
388
|
+
return { restored: restoreFrom(good, actor, before), error };
|
|
379
389
|
}
|
|
380
|
-
catch (r) {
|
|
381
|
-
throw new Error(`the replica could not be restored after an Automerge error: ${String(r)}`);
|
|
382
|
-
}
|
|
383
|
-
if ([...A.getHeads(restored)].sort().join() !== [...heads].sort().join())
|
|
384
|
-
throw new Error("the replica could not be restored after an Automerge error: heads differ");
|
|
385
|
-
return restored;
|
|
386
390
|
}
|
|
387
391
|
function loadFailure(e, what) {
|
|
388
392
|
if (e instanceof LfcpError)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openlfcp/shared-objects",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "SHARED-OBJECTS-PROFILE-01 (org.openlfcp.shared-objects.v1) implementation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openlfcp",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@automerge/automerge": "3.5.0",
|
|
47
47
|
"fflate": "0.8.3",
|
|
48
|
-
"@openlfcp/
|
|
49
|
-
"@openlfcp/
|
|
48
|
+
"@openlfcp/core": "^0.1.5",
|
|
49
|
+
"@openlfcp/crypto": "^0.1.5"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"fast-check": "4.10.2"
|