Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@automerge/automerge

Package Overview
Dependencies
Maintainers
4
Versions
82
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@automerge/automerge - npm Package Compare versions

Comparing version 2.0.1-alpha.2 to 2.0.1-alpha.3

dist/cjs/internal_state.js

32

dist/cjs/constants.js
"use strict";
// Properties of the document root object
Object.defineProperty(exports, "__esModule", { value: true });
exports.TEXT = exports.COUNTER = exports.F64 = exports.INT = exports.UINT = exports.FROZEN = exports.READ_ONLY = exports.OBJECT_ID = exports.TRACE = exports.HEADS = exports.STATE = void 0;
// Properties of the document root object
//const OPTIONS = Symbol('_options') // object containing options passed to init()
//const CACHE = Symbol('_cache') // map from objectId to immutable object
//export const STATE = Symbol.for('_am_state') // object containing metadata about current state (e.g. sequence numbers)
exports.STATE = Symbol.for('_am_meta'); // object containing metadata about current state (e.g. sequence numbers)
exports.HEADS = Symbol.for('_am_heads'); // object containing metadata about current state (e.g. sequence numbers)
exports.TRACE = Symbol.for('_am_trace'); // object containing metadata about current state (e.g. sequence numbers)
exports.OBJECT_ID = Symbol.for('_am_objectId'); // object containing metadata about current state (e.g. sequence numbers)
exports.READ_ONLY = Symbol.for('_am_readOnly'); // object containing metadata about current state (e.g. sequence numbers)
exports.FROZEN = Symbol.for('_am_frozen'); // object containing metadata about current state (e.g. sequence numbers)
exports.UINT = Symbol.for('_am_uint');
exports.INT = Symbol.for('_am_int');
exports.F64 = Symbol.for('_am_f64');
exports.COUNTER = Symbol.for('_am_counter');
exports.TEXT = Symbol.for('_am_text');
// Properties of all Automerge objects
//const OBJECT_ID = Symbol('_objectId') // the object ID of the current object (string)
//const CONFLICTS = Symbol('_conflicts') // map or list (depending on object type) of conflicts
//const CHANGE = Symbol('_change') // the context object on proxy objects used in change callback
//const ELEM_IDS = Symbol('_elemIds') // list containing the element ID of each list element
exports.TEXT = exports.COUNTER = exports.F64 = exports.INT = exports.UINT = exports.IS_PROXY = exports.OBJECT_ID = exports.TRACE = exports.STATE = void 0;
exports.STATE = Symbol.for("_am_meta"); // symbol used to hide application metadata on automerge objects
exports.TRACE = Symbol.for("_am_trace"); // used for debugging
exports.OBJECT_ID = Symbol.for("_am_objectId"); // synbol used to hide the object id on automerge objects
exports.IS_PROXY = Symbol.for("_am_isProxy"); // symbol used to test if the document is a proxy object
exports.UINT = Symbol.for("_am_uint");
exports.INT = Symbol.for("_am_int");
exports.F64 = Symbol.for("_am_f64");
exports.COUNTER = Symbol.for("_am_counter");
exports.TEXT = Symbol.for("_am_text");

@@ -60,3 +60,3 @@ "use strict";

increment(delta) {
delta = typeof delta === 'number' ? delta : 1;
delta = typeof delta === "number" ? delta : 1;
this.context.increment(this.objectId, this.key, delta);

@@ -71,3 +71,3 @@ this.value += delta;

decrement(delta) {
return this.increment(typeof delta === 'number' ? -delta : -1);
return this.increment(typeof delta === "number" ? -delta : -1);
}

@@ -81,3 +81,3 @@ }

* located.
*/
*/
function getWriteableCounter(value, context, path, objectId, key) {

@@ -84,0 +84,0 @@ return new WriteableCounter(value, context, path, objectId, key);

"use strict";
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isAutomerge = exports.toJS = exports.dump = exports.getHeads = exports.getMissingDeps = exports.decodeSyncMessage = exports.encodeSyncMessage = exports.decodeChange = exports.encodeChange = exports.initSyncState = exports.receiveSyncMessage = exports.generateSyncMessage = exports.decodeSyncState = exports.encodeSyncState = exports.equals = exports.getHistory = exports.applyChanges = exports.getAllChanges = exports.getChanges = exports.getObjectId = exports.getLastLocalChange = exports.getConflicts = exports.getActorId = exports.merge = exports.save = exports.loadIncremental = exports.load = exports.emptyChange = exports.change = exports.from = exports.free = exports.clone = exports.view = exports.init = exports.getBackend = exports.use = exports.Float64 = exports.Uint = exports.Int = exports.Counter = exports.Text = exports.uuid = void 0;
/** @hidden **/
var uuid_1 = require("./uuid");
Object.defineProperty(exports, "uuid", { enumerable: true, get: function () { return uuid_1.uuid; } });
const proxies_1 = require("./proxies");
const constants_1 = require("./constants");
const types_1 = require("./types");
var types_2 = require("./types");
Object.defineProperty(exports, "Text", { enumerable: true, get: function () { return types_2.Text; } });
Object.defineProperty(exports, "Counter", { enumerable: true, get: function () { return types_2.Counter; } });
Object.defineProperty(exports, "Int", { enumerable: true, get: function () { return types_2.Int; } });
Object.defineProperty(exports, "Uint", { enumerable: true, get: function () { return types_2.Uint; } });
Object.defineProperty(exports, "Float64", { enumerable: true, get: function () { return types_2.Float64; } });
const low_level_1 = require("./low_level");
/** @hidden **/
function use(api) {
(0, low_level_1.UseApi)(api);
}
exports.use = use;
const wasm = require("@automerge/automerge-wasm");
use(wasm);
/** @hidden */
function getBackend(doc) {
return _state(doc).handle;
}
exports.getBackend = getBackend;
function _state(doc, checkroot = true) {
if (typeof doc !== 'object') {
throw new RangeError("must be the document root");
}
const state = Reflect.get(doc, constants_1.STATE);
if (state === undefined || state == null || (checkroot && _obj(doc) !== "_root")) {
throw new RangeError("must be the document root");
}
return state;
}
function _frozen(doc) {
return Reflect.get(doc, constants_1.FROZEN) === true;
}
function _trace(doc) {
return Reflect.get(doc, constants_1.TRACE);
}
function _set_heads(doc, heads) {
_state(doc).heads = heads;
}
function _clear_heads(doc) {
Reflect.set(doc, constants_1.HEADS, undefined);
Reflect.set(doc, constants_1.TRACE, undefined);
}
function _obj(doc) {
if (!(typeof doc === 'object') || doc === null) {
return null;
}
return Reflect.get(doc, constants_1.OBJECT_ID);
}
function _readonly(doc) {
return Reflect.get(doc, constants_1.READ_ONLY) !== false;
}
function importOpts(_actor) {
if (typeof _actor === 'object') {
return _actor;
}
else {
return { actor: _actor };
}
}
exports.unstable = void 0;
/**
* Create a new automerge document
* # Automerge
*
* @typeParam T - The type of value contained in the document. This will be the
* type that is passed to the change closure in {@link change}
* @param _opts - Either an actorId or an {@link InitOptions} (which may
* contain an actorId). If this is null the document will be initialised with a
* random actor ID
*/
function init(_opts) {
let opts = importOpts(_opts);
let freeze = !!opts.freeze;
let patchCallback = opts.patchCallback;
const handle = low_level_1.ApiHandler.create(opts.actor);
handle.enablePatches(true);
handle.enableFreeze(!!opts.freeze);
handle.registerDatatype("counter", (n) => new types_1.Counter(n));
handle.registerDatatype("text", (n) => new types_1.Text(n));
const doc = handle.materialize("/", undefined, { handle, heads: undefined, freeze, patchCallback });
return doc;
}
exports.init = init;
/**
* Make an immutable view of an automerge document as at `heads`
* This library provides the core automerge data structure and sync algorithms.
* Other libraries can be built on top of this one which provide IO and
* persistence.
*
* @remarks
* The document returned from this function cannot be passed to {@link change}.
* This is because it shares the same underlying memory as `doc`, but it is
* consequently a very cheap copy.
* An automerge document can be though of an immutable POJO (plain old javascript
* object) which `automerge` tracks the history of, allowing it to be merged with
* any other automerge document.
*
* Note that this function will throw an error if any of the hashes in `heads`
* are not in the document.
* ## Creating and modifying a document
*
* @typeParam T - The type of the value contained in the document
* @param doc - The document to create a view of
* @param heads - The hashes of the heads to create a view at
*/
function view(doc, heads) {
const state = _state(doc);
const handle = state.handle;
return state.handle.materialize("/", heads, Object.assign(Object.assign({}, state), { handle, heads }));
}
exports.view = view;
/**
* Make a full writable copy of an automerge document
* You can create a document with {@link init} or {@link from} and then make
* changes to it with {@link change}, you can merge two documents with {@link
* merge}.
*
* @remarks
* Unlike {@link view} this function makes a full copy of the memory backing
* the document and can thus be passed to {@link change}. It also generates a
* new actor ID so that changes made in the new document do not create duplicate
* sequence numbers with respect to the old document. If you need control over
* the actor ID which is generated you can pass the actor ID as the second
* argument
* ```ts
* import * as automerge from "@automerge/automerge"
*
* @typeParam T - The type of the value contained in the document
* @param doc - The document to clone
* @param _opts - Either an actor ID to use for the new doc or an {@link InitOptions}
*/
function clone(doc, _opts) {
const state = _state(doc);
const heads = state.heads;
const opts = importOpts(_opts);
const handle = state.handle.fork(opts.actor, heads);
// `change` uses the presence of state.heads to determine if we are in a view
// set it to undefined to indicate that this is a full fat document
const { heads: oldHeads } = state, stateSansHeads = __rest(state, ["heads"]);
return handle.applyPatches(doc, Object.assign(Object.assign({}, stateSansHeads), { handle }));
}
exports.clone = clone;
/** Explicity free the memory backing a document. Note that this is note
* necessary in environments which support
* [`FinalizationRegistry`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry)
*/
function free(doc) {
return _state(doc).handle.free();
}
exports.free = free;
/**
* Create an automerge document from a POJO
* type DocType = {ideas: Array<automerge.Text>}
*
* @param initialState - The initial state which will be copied into the document
* @typeParam T - The type of the value passed to `from` _and_ the type the resulting document will contain
* @typeParam actor - The actor ID of the resulting document, if this is null a random actor ID will be used
* let doc1 = automerge.init<DocType>()
* doc1 = automerge.change(doc1, d => {
* d.ideas = [new automerge.Text("an immutable document")]
* })
*
* @example
* ```
* const doc = automerge.from({
* tasks: [
* {description: "feed dogs", done: false}
* ]
* let doc2 = automerge.init<DocType>()
* doc2 = automerge.merge(doc2, automerge.clone(doc1))
* doc2 = automerge.change<DocType>(doc2, d => {
* d.ideas.push(new automerge.Text("which records it's history"))
* })
* ```
*/
function from(initialState, actor) {
return change(init(actor), (d) => Object.assign(d, initialState));
}
exports.from = from;
/**
* Update the contents of an automerge document
* @typeParam T - The type of the value contained in the document
* @param doc - The document to update
* @param options - Either a message, an {@link ChangeOptions}, or a {@link ChangeFn}
* @param callback - A `ChangeFn` to be used if `options` was a `string`
*
* Note that if the second argument is a function it will be used as the `ChangeFn` regardless of what the third argument is.
*
* @example A simple change
* ```
* let doc1 = automerge.init()
* // Note the `automerge.clone` call, see the "cloning" section of this readme for
* // more detail
* doc1 = automerge.merge(doc1, automerge.clone(doc2))
* doc1 = automerge.change(doc1, d => {
* d.key = "value"
* d.ideas[0].deleteAt(13, 8)
* d.ideas[0].insertAt(13, "object")
* })
* assert.equal(doc1.key, "value")
*
* let doc3 = automerge.merge(doc1, doc2)
* // doc3 is now {ideas: ["an immutable object", "which records it's history"]}
* ```
*
* @example A change with a message
* ## Applying changes from another document
*
* ```
* doc1 = automerge.change(doc1, "add another value", d => {
* d.key2 = "value2"
* })
* ```
* You can get a representation of the result of the last {@link change} you made
* to a document with {@link getLastLocalChange} and you can apply that change to
* another document using {@link applyChanges}.
*
* @example A change with a message and a timestamp
* If you need to get just the changes which are in one document but not in another
* you can use {@link getHeads} to get the heads of the document without the
* changes and then {@link getMissingDeps}, passing the result of {@link getHeads}
* on the document with the changes.
*
* ```
* doc1 = automerge.change(doc1, {message: "add another value", timestamp: 1640995200}, d => {
* d.key2 = "value2"
* ## Saving and loading documents
*
* You can {@link save} a document to generate a compresed binary representation of
* the document which can be loaded with {@link load}. If you have a document which
* you have recently made changes to you can generate recent changes with {@link
* saveIncremental}, this will generate all the changes since you last called
* `saveIncremental`, the changes generated can be applied to another document with
* {@link loadIncremental}.
*
* ## Viewing different versions of a document
*
* Occasionally you may wish to explicitly step to a different point in a document
* history. One common reason to do this is if you need to obtain a set of changes
* which take the document from one state to another in order to send those changes
* to another peer (or to save them somewhere). You can use {@link view} to do this.
*
* ```ts
* import * as automerge from "@automerge/automerge"
* import * as assert from "assert"
*
* let doc = automerge.from({
* key1: "value1",
* })
* ```
*
* @example responding to a patch callback
* ```
* let patchedPath
* let patchCallback = patch => {
* patchedPath = patch.path
* }
* doc1 = automerge.change(doc1, {message, "add another value", timestamp: 1640995200, patchCallback}, d => {
* // Make a clone of the document at this point, maybe this is actually on another
* // peer.
* let doc2 = automerge.clone < any > doc
*
* let heads = automerge.getHeads(doc)
*
* doc =
* automerge.change <
* any >
* (doc,
* d => {
* d.key2 = "value2"
* })
* assert.equal(patchedPath, ["key2"])
* ```
*/
function change(doc, options, callback) {
if (typeof options === 'function') {
return _change(doc, {}, options);
}
else if (typeof callback === 'function') {
if (typeof options === "string") {
options = { message: options };
}
return _change(doc, options, callback);
}
else {
throw RangeError("Invalid args for change");
}
}
exports.change = change;
function progressDocument(doc, heads, callback) {
if (heads == null) {
return doc;
}
let state = _state(doc);
let nextState = Object.assign(Object.assign({}, state), { heads: undefined });
let nextDoc = state.handle.applyPatches(doc, nextState, callback);
state.heads = heads;
return nextDoc;
}
function _change(doc, options, callback) {
if (typeof callback !== "function") {
throw new RangeError("invalid change function");
}
const state = _state(doc);
if (doc === undefined || state === undefined) {
throw new RangeError("must be the document root");
}
if (state.heads) {
throw new RangeError("Attempting to change an outdated document. Use Automerge.clone() if you wish to make a writable copy.");
}
if (_readonly(doc) === false) {
throw new RangeError("Calls to Automerge.change cannot be nested");
}
const heads = state.handle.getHeads();
try {
state.heads = heads;
const root = (0, proxies_1.rootProxy)(state.handle);
callback(root);
if (state.handle.pendingOps() === 0) {
state.heads = undefined;
return doc;
}
else {
state.handle.commit(options.message, options.time);
return progressDocument(doc, heads, options.patchCallback || state.patchCallback);
}
}
catch (e) {
//console.log("ERROR: ",e)
state.heads = undefined;
state.handle.rollback();
throw e;
}
}
/**
* Make a change to a document which does not modify the document
* })
*
* @param doc - The doc to add the empty change to
* @param options - Either a message or a {@link ChangeOptions} for the new change
* doc =
* automerge.change <
* any >
* (doc,
* d => {
* d.key3 = "value3"
* })
*
* Why would you want to do this? One reason might be that you have merged
* changes from some other peers and you want to generate a change which
* depends on those merged changes so that you can sign the new change with all
* of the merged changes as part of the new change.
*/
function emptyChange(doc, options) {
if (options === undefined) {
options = {};
}
if (typeof options === "string") {
options = { message: options };
}
const state = _state(doc);
if (state.heads) {
throw new RangeError("Attempting to change an outdated document. Use Automerge.clone() if you wish to make a writable copy.");
}
if (_readonly(doc) === false) {
throw new RangeError("Calls to Automerge.change cannot be nested");
}
const heads = state.handle.getHeads();
state.handle.emptyChange(options.message, options.time);
return progressDocument(doc, heads);
}
exports.emptyChange = emptyChange;
/**
* Load an automerge document from a compressed document produce by {@link save}
* // At this point we've generated two separate changes, now we want to send
* // just those changes to someone else
*
* @typeParam T - The type of the value which is contained in the document.
* Note that no validation is done to make sure this type is in
* fact the type of the contained value so be a bit careful
* @param data - The compressed document
* @param _opts - Either an actor ID or some {@link InitOptions}, if the actor
* ID is null a random actor ID will be created
* // view is a cheap reference based copy of a document at a given set of heads
* let before = automerge.view(doc, heads)
*
* Note that `load` will throw an error if passed incomplete content (for
* example if you are receiving content over the network and don't know if you
* have the complete document yet). If you need to handle incomplete content use
* {@link init} followed by {@link loadIncremental}.
*/
function load(data, _opts) {
const opts = importOpts(_opts);
const actor = opts.actor;
const patchCallback = opts.patchCallback;
const handle = low_level_1.ApiHandler.load(data, actor);
handle.enablePatches(true);
handle.enableFreeze(!!opts.freeze);
handle.registerDatatype("counter", (n) => new types_1.Counter(n));
handle.registerDatatype("text", (n) => new types_1.Text(n));
const doc = handle.materialize("/", undefined, { handle, heads: undefined, patchCallback });
return doc;
}
exports.load = load;
/**
* Load changes produced by {@link saveIncremental}, or partial changes
* // This view doesn't show the last two changes in the document state
* assert.deepEqual(before, {
* key1: "value1",
* })
*
* @typeParam T - The type of the value which is contained in the document.
* Note that no validation is done to make sure this type is in
* fact the type of the contained value so be a bit careful
* @param data - The compressedchanges
* @param opts - an {@link ApplyOptions}
* // Get the changes to send to doc2
* let changes = automerge.getChanges(before, doc)
*
* This function is useful when staying up to date with a connected peer.
* Perhaps the other end sent you a full compresed document which you loaded
* with {@link load} and they're sending you the result of
* {@link getLastLocalChange} every time they make a change.
* // Apply the changes at doc2
* doc2 = automerge.applyChanges < any > (doc2, changes)[0]
* assert.deepEqual(doc2, {
* key1: "value1",
* key2: "value2",
* key3: "value3",
* })
* ```
*
* Note that this function will succesfully load the results of {@link save} as
* well as {@link getLastLocalChange} or any other incremental change.
*/
function loadIncremental(doc, data, opts) {
if (!opts) {
opts = {};
}
const state = _state(doc);
if (state.heads) {
throw new RangeError("Attempting to change an out of date document - set at: " + _trace(doc));
}
if (_readonly(doc) === false) {
throw new RangeError("Calls to Automerge.change cannot be nested");
}
const heads = state.handle.getHeads();
state.handle.loadIncremental(data);
return progressDocument(doc, heads, opts.patchCallback || state.patchCallback);
}
exports.loadIncremental = loadIncremental;
/**
* Export the contents of a document to a compressed format
* If you have a {@link view} of a document which you want to make changes to you
* can {@link clone} the viewed document.
*
* @param doc - The doc to save
* ## Syncing
*
* The returned bytes can be passed to {@link load} or {@link loadIncremental}
*/
function save(doc) {
return _state(doc).handle.save();
}
exports.save = save;
/**
* Merge `local` into `remote`
* @typeParam T - The type of values contained in each document
* @param local - The document to merge changes into
* @param remote - The document to merge changes from
* The sync protocol is stateful. This means that we start by creating a {@link
* SyncState} for each peer we are communicating with using {@link initSyncState}.
* Then we generate a message to send to the peer by calling {@link
* generateSyncMessage}. When we receive a message from the peer we call {@link
* receiveSyncMessage}. Here's a simple example of a loop which just keeps two
* peers in sync.
*
* @returns - The merged document
* ```ts
* let sync1 = automerge.initSyncState()
* let msg: Uint8Array | null
* ;[sync1, msg] = automerge.generateSyncMessage(doc1, sync1)
*
* Often when you are merging documents you will also need to clone them. Both
* arguments to `merge` are frozen after the call so you can no longer call
* mutating methods (such as {@link change}) on them. The symtom of this will be
* an error which says "Attempting to change an out of date document". To
* overcome this call {@link clone} on the argument before passing it to {@link
* merge}.
*/
function merge(local, remote) {
const localState = _state(local);
if (localState.heads) {
throw new RangeError("Attempting to change an out of date document - set at: " + _trace(local));
}
const heads = localState.handle.getHeads();
const remoteState = _state(remote);
const changes = localState.handle.getChangesAdded(remoteState.handle);
localState.handle.applyChanges(changes);
return progressDocument(local, heads, localState.patchCallback);
}
exports.merge = merge;
/**
* Get the actor ID associated with the document
*/
function getActorId(doc) {
const state = _state(doc);
return state.handle.getActorId();
}
exports.getActorId = getActorId;
function conflictAt(context, objectId, prop) {
const values = context.getAll(objectId, prop);
if (values.length <= 1) {
return;
}
const result = {};
for (const fullVal of values) {
switch (fullVal[0]) {
case "map":
result[fullVal[1]] = (0, proxies_1.mapProxy)(context, fullVal[1], [prop], true);
break;
case "list":
result[fullVal[1]] = (0, proxies_1.listProxy)(context, fullVal[1], [prop], true);
break;
case "text":
result[fullVal[1]] = (0, proxies_1.textProxy)(context, fullVal[1], [prop], true);
break;
//case "table":
//case "cursor":
case "str":
case "uint":
case "int":
case "f64":
case "boolean":
case "bytes":
case "null":
result[fullVal[2]] = fullVal[1];
break;
case "counter":
result[fullVal[2]] = new types_1.Counter(fullVal[1]);
break;
case "timestamp":
result[fullVal[2]] = new Date(fullVal[1]);
break;
default:
throw RangeError(`datatype ${fullVal[0]} unimplemented`);
}
}
return result;
}
/**
* Get the conflicts associated with a property
* while (true) {
* if (msg != null) {
* network.send(msg)
* }
* let resp: Uint8Array =
* (network.receive()[(doc1, sync1, _ignore)] =
* automerge.receiveSyncMessage(doc1, sync1, resp)[(sync1, msg)] =
* automerge.generateSyncMessage(doc1, sync1))
* }
* ```
*
* The values of properties in a map in automerge can be conflicted if there
* are concurrent "put" operations to the same key. Automerge chooses one value
* arbitrarily (but deterministically, any two nodes who have the same set of
* changes will choose the same value) from the set of conflicting values to
* present as the value of the key.
* ## Conflicts
*
* Sometimes you may want to examine these conflicts, in this case you can use
* {@link getConflicts} to get the conflicts for the key.
* The only time conflicts occur in automerge documents is in concurrent
* assignments to the same key in an object. In this case automerge
* deterministically chooses an arbitrary value to present to the application but
* you can examine the conflicts using {@link getConflicts}.
*
* @example
* ```

@@ -503,267 +208,53 @@ * import * as automerge from "@automerge/automerge"

* ```
*/
function getConflicts(doc, prop) {
const state = _state(doc, false);
const objectId = _obj(doc);
if (objectId != null) {
return conflictAt(state.handle, objectId, prop);
}
else {
return undefined;
}
}
exports.getConflicts = getConflicts;
/**
* Get the binary representation of the last change which was made to this doc
*
* This is most useful when staying in sync with other peers, every time you
* make a change locally via {@link change} you immediately call {@link
* getLastLocalChange} and send the result over the network to other peers.
*/
function getLastLocalChange(doc) {
const state = _state(doc);
return state.handle.getLastLocalChange() || undefined;
}
exports.getLastLocalChange = getLastLocalChange;
/**
* Return the object ID of an arbitrary javascript value
* ## Actor IDs
*
* This is useful to determine if something is actually an automerge document,
* if `doc` is not an automerge document this will return null.
*/
function getObjectId(doc) {
return _obj(doc);
}
exports.getObjectId = getObjectId;
/**
* Get the changes which are in `newState` but not in `oldState`. The returned
* changes can be loaded in `oldState` via {@link applyChanges}.
* By default automerge will generate a random actor ID for you, but most methods
* for creating a document allow you to set the actor ID. You can get the actor ID
* associated with the document by calling {@link getActorId}. Actor IDs must not
* be used in concurrent threads of executiong - all changes by a given actor ID
* are expected to be sequential.
*
* Note that this will crash if there are changes in `oldState` which are not in `newState`.
*/
function getChanges(oldState, newState) {
const o = _state(oldState);
const n = _state(newState);
return n.handle.getChanges(getHeads(oldState));
}
exports.getChanges = getChanges;
/**
* Get all the changes in a document
* ## Listening to patches
*
* This is different to {@link save} because the output is an array of changes
* which can be individually applied via {@link applyChanges}`
* Sometimes you want to respond to changes made to an automerge document. In this
* case you can use the {@link PatchCallback} type to receive notifications when
* changes have been made.
*
*/
function getAllChanges(doc) {
const state = _state(doc);
return state.handle.getChanges([]);
}
exports.getAllChanges = getAllChanges;
/**
* Apply changes received from another document
* ## Cloning
*
* `doc` will be updated to reflect the `changes`. If there are changes which
* we do not have dependencies for yet those will be stored in the document and
* applied when the depended on changes arrive.
* Currently you cannot make mutating changes (i.e. call {@link change}) to a
* document which you have two pointers to. For example, in this code:
*
* You can use the {@link ApplyOptions} to pass a patchcallback which will be
* informed of any changes which occur as a result of applying the changes
* ```javascript
* let doc1 = automerge.init()
* let doc2 = automerge.change(doc1, d => (d.key = "value"))
* ```
*
*/
function applyChanges(doc, changes, opts) {
const state = _state(doc);
if (!opts) {
opts = {};
}
if (state.heads) {
throw new RangeError("Attempting to change an outdated document. Use Automerge.clone() if you wish to make a writable copy.");
}
if (_readonly(doc) === false) {
throw new RangeError("Calls to Automerge.change cannot be nested");
}
const heads = state.handle.getHeads();
state.handle.applyChanges(changes);
state.heads = heads;
return [progressDocument(doc, heads, opts.patchCallback || state.patchCallback)];
}
exports.applyChanges = applyChanges;
/** @hidden */
function getHistory(doc) {
const history = getAllChanges(doc);
return history.map((change, index) => ({
get change() {
return decodeChange(change);
},
get snapshot() {
const [state] = applyChanges(init(), history.slice(0, index + 1));
return state;
}
}));
}
exports.getHistory = getHistory;
/** @hidden */
// FIXME : no tests
// FIXME can we just use deep equals now?
function equals(val1, val2) {
if (!isObject(val1) || !isObject(val2))
return val1 === val2;
const keys1 = Object.keys(val1).sort(), keys2 = Object.keys(val2).sort();
if (keys1.length !== keys2.length)
return false;
for (let i = 0; i < keys1.length; i++) {
if (keys1[i] !== keys2[i])
return false;
if (!equals(val1[keys1[i]], val2[keys2[i]]))
return false;
}
return true;
}
exports.equals = equals;
/**
* encode a {@link SyncState} into binary to send over the network
* `doc1` and `doc2` are both pointers to the same state. Any attempt to call
* mutating methods on `doc1` will now result in an error like
*
* @group sync
* */
function encodeSyncState(state) {
const sync = low_level_1.ApiHandler.importSyncState(state);
const result = low_level_1.ApiHandler.encodeSyncState(sync);
sync.free();
return result;
}
exports.encodeSyncState = encodeSyncState;
/**
* Decode some binary data into a {@link SyncState}
* Attempting to change an out of date document
*
* @group sync
*/
function decodeSyncState(state) {
let sync = low_level_1.ApiHandler.decodeSyncState(state);
let result = low_level_1.ApiHandler.exportSyncState(sync);
sync.free();
return result;
}
exports.decodeSyncState = decodeSyncState;
/**
* Generate a sync message to send to the peer represented by `inState`
* @param doc - The doc to generate messages about
* @param inState - The {@link SyncState} representing the peer we are talking to
* If you encounter this you need to clone the original document, the above sample
* would work as:
*
* @group sync
* ```javascript
* let doc1 = automerge.init()
* let doc2 = automerge.change(automerge.clone(doc1), d => (d.key = "value"))
* ```
* @packageDocumentation
*
* @returns An array of `[newSyncState, syncMessage | null]` where
* `newSyncState` should replace `inState` and `syncMessage` should be sent to
* the peer if it is not null. If `syncMessage` is null then we are up to date.
*/
function generateSyncMessage(doc, inState) {
const state = _state(doc);
const syncState = low_level_1.ApiHandler.importSyncState(inState);
const message = state.handle.generateSyncMessage(syncState);
const outState = low_level_1.ApiHandler.exportSyncState(syncState);
return [outState, message];
}
exports.generateSyncMessage = generateSyncMessage;
/**
* Update a document and our sync state on receiving a sync message
* ## The {@link unstable} module
*
* @group sync
*
* @param doc - The doc the sync message is about
* @param inState - The {@link SyncState} for the peer we are communicating with
* @param message - The message which was received
* @param opts - Any {@link ApplyOption}s, used for passing a
* {@link PatchCallback} which will be informed of any changes
* in `doc` which occur because of the received sync message.
*
* @returns An array of `[newDoc, newSyncState, syncMessage | null]` where
* `newDoc` is the updated state of `doc`, `newSyncState` should replace
* `inState` and `syncMessage` should be sent to the peer if it is not null. If
* `syncMessage` is null then we are up to date.
* We are working on some changes to automerge which are not yet complete and
* will result in backwards incompatible API changes. Once these changes are
* ready for production use we will release a new major version of automerge.
* However, until that point you can use the {@link unstable} module to try out
* the new features, documents from the {@link unstable} module are
* interoperable with documents from the main module. Please see the docs for
* the {@link unstable} module for more details.
*/
function receiveSyncMessage(doc, inState, message, opts) {
const syncState = low_level_1.ApiHandler.importSyncState(inState);
if (!opts) {
opts = {};
}
const state = _state(doc);
if (state.heads) {
throw new RangeError("Attempting to change an outdated document. Use Automerge.clone() if you wish to make a writable copy.");
}
if (_readonly(doc) === false) {
throw new RangeError("Calls to Automerge.change cannot be nested");
}
const heads = state.handle.getHeads();
state.handle.receiveSyncMessage(syncState, message);
const outSyncState = low_level_1.ApiHandler.exportSyncState(syncState);
return [progressDocument(doc, heads, opts.patchCallback || state.patchCallback), outSyncState, null];
}
exports.receiveSyncMessage = receiveSyncMessage;
/**
* Create a new, blank {@link SyncState}
*
* When communicating with a peer for the first time use this to generate a new
* {@link SyncState} for them
*
* @group sync
*/
function initSyncState() {
return low_level_1.ApiHandler.exportSyncState(low_level_1.ApiHandler.initSyncState());
}
exports.initSyncState = initSyncState;
/** @hidden */
function encodeChange(change) {
return low_level_1.ApiHandler.encodeChange(change);
}
exports.encodeChange = encodeChange;
/** @hidden */
function decodeChange(data) {
return low_level_1.ApiHandler.decodeChange(data);
}
exports.decodeChange = decodeChange;
/** @hidden */
function encodeSyncMessage(message) {
return low_level_1.ApiHandler.encodeSyncMessage(message);
}
exports.encodeSyncMessage = encodeSyncMessage;
/** @hidden */
function decodeSyncMessage(message) {
return low_level_1.ApiHandler.decodeSyncMessage(message);
}
exports.decodeSyncMessage = decodeSyncMessage;
/**
* Get any changes in `doc` which are not dependencies of `heads`
*/
function getMissingDeps(doc, heads) {
const state = _state(doc);
return state.handle.getMissingDeps(heads);
}
exports.getMissingDeps = getMissingDeps;
/**
* Get the hashes of the heads of this document
*/
function getHeads(doc) {
const state = _state(doc);
return state.heads || state.handle.getHeads();
}
exports.getHeads = getHeads;
/** @hidden */
function dump(doc) {
const state = _state(doc);
state.handle.dump();
}
exports.dump = dump;
/** @hidden */
function toJS(doc) {
const state = _state(doc);
const enabled = state.handle.enableFreeze(false);
const result = state.handle.materialize();
state.handle.enableFreeze(enabled);
return result;
}
exports.toJS = toJS;
function isAutomerge(doc) {
return getObjectId(doc) === "_root" && !!Reflect.get(doc, constants_1.STATE);
}
exports.isAutomerge = isAutomerge;
function isObject(obj) {
return typeof obj === 'object' && obj !== null;
}
__exportStar(require("./stable"), exports);
const unstable = require("./unstable");
exports.unstable = unstable;

@@ -12,14 +12,36 @@ "use strict";

exports.ApiHandler = {
create(actor) { throw new RangeError("Automerge.use() not called"); },
load(data, actor) { throw new RangeError("Automerge.use() not called (load)"); },
encodeChange(change) { throw new RangeError("Automerge.use() not called (encodeChange)"); },
decodeChange(change) { throw new RangeError("Automerge.use() not called (decodeChange)"); },
initSyncState() { throw new RangeError("Automerge.use() not called (initSyncState)"); },
encodeSyncMessage(message) { throw new RangeError("Automerge.use() not called (encodeSyncMessage)"); },
decodeSyncMessage(msg) { throw new RangeError("Automerge.use() not called (decodeSyncMessage)"); },
encodeSyncState(state) { throw new RangeError("Automerge.use() not called (encodeSyncState)"); },
decodeSyncState(data) { throw new RangeError("Automerge.use() not called (decodeSyncState)"); },
exportSyncState(state) { throw new RangeError("Automerge.use() not called (exportSyncState)"); },
importSyncState(state) { throw new RangeError("Automerge.use() not called (importSyncState)"); },
create(textV2, actor) {
throw new RangeError("Automerge.use() not called");
},
load(data, textV2, actor) {
throw new RangeError("Automerge.use() not called (load)");
},
encodeChange(change) {
throw new RangeError("Automerge.use() not called (encodeChange)");
},
decodeChange(change) {
throw new RangeError("Automerge.use() not called (decodeChange)");
},
initSyncState() {
throw new RangeError("Automerge.use() not called (initSyncState)");
},
encodeSyncMessage(message) {
throw new RangeError("Automerge.use() not called (encodeSyncMessage)");
},
decodeSyncMessage(msg) {
throw new RangeError("Automerge.use() not called (decodeSyncMessage)");
},
encodeSyncState(state) {
throw new RangeError("Automerge.use() not called (encodeSyncState)");
},
decodeSyncState(data) {
throw new RangeError("Automerge.use() not called (decodeSyncState)");
},
exportSyncState(state) {
throw new RangeError("Automerge.use() not called (exportSyncState)");
},
importSyncState(state) {
throw new RangeError("Automerge.use() not called (importSyncState)");
},
};
/* eslint-enable */

@@ -8,3 +8,5 @@ "use strict";

constructor(value) {
if (!(Number.isInteger(value) && value <= Number.MAX_SAFE_INTEGER && value >= Number.MIN_SAFE_INTEGER)) {
if (!(Number.isInteger(value) &&
value <= Number.MAX_SAFE_INTEGER &&
value >= Number.MIN_SAFE_INTEGER)) {
throw new RangeError(`Value ${value} cannot be a uint`);

@@ -20,3 +22,5 @@ }

constructor(value) {
if (!(Number.isInteger(value) && value <= Number.MAX_SAFE_INTEGER && value >= 0)) {
if (!(Number.isInteger(value) &&
value <= Number.MAX_SAFE_INTEGER &&
value >= 0)) {
throw new RangeError(`Value ${value} cannot be a uint`);

@@ -32,3 +36,3 @@ }

constructor(value) {
if (typeof value !== 'number') {
if (typeof value !== "number") {
throw new RangeError(`Value ${value} cannot be a float64`);

@@ -35,0 +39,0 @@ }

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.rootProxy = exports.textProxy = exports.listProxy = exports.mapProxy = void 0;
const text_1 = require("./text");
const counter_1 = require("./counter");
const text_1 = require("./text");
const constants_1 = require("./constants");
const raw_string_1 = require("./raw_string");
function parseListIndex(key) {
if (typeof key === 'string' && /^[0-9]+$/.test(key))
if (typeof key === "string" && /^[0-9]+$/.test(key))
key = parseInt(key, 10);
if (typeof key !== 'number') {
// throw new TypeError('A list index must be a number, but you passed ' + JSON.stringify(key))
if (typeof key !== "number") {
return key;
}
if (key < 0 || isNaN(key) || key === Infinity || key === -Infinity) {
throw new RangeError('A list index must be positive, but you passed ' + key);
throw new RangeError("A list index must be positive, but you passed " + key);
}

@@ -20,3 +20,3 @@ return key;

function valueAt(target, prop) {
const { context, objectId, path, readonly, heads } = target;
const { context, objectId, path, readonly, heads, textV2 } = target;
const value = context.getWithType(objectId, prop, heads);

@@ -29,16 +29,31 @@ if (value === null) {

switch (datatype) {
case undefined: return;
case "map": return mapProxy(context, val, [...path, prop], readonly, heads);
case "list": return listProxy(context, val, [...path, prop], readonly, heads);
case "text": return textProxy(context, val, [...path, prop], readonly, heads);
//case "table":
//case "cursor":
case "str": return val;
case "uint": return val;
case "int": return val;
case "f64": return val;
case "boolean": return val;
case "null": return null;
case "bytes": return val;
case "timestamp": return val;
case undefined:
return;
case "map":
return mapProxy(context, val, textV2, [...path, prop], readonly, heads);
case "list":
return listProxy(context, val, textV2, [...path, prop], readonly, heads);
case "text":
if (textV2) {
return context.text(val, heads);
}
else {
return textProxy(context, val, [...path, prop], readonly, heads);
}
case "str":
return val;
case "uint":
return val;
case "int":
return val;
case "f64":
return val;
case "boolean":
return val;
case "null":
return null;
case "bytes":
return val;
case "timestamp":
return val;
case "counter": {

@@ -56,5 +71,5 @@ if (readonly) {

}
function import_value(value) {
function import_value(value, textV2) {
switch (typeof value) {
case 'object':
case "object":
if (value == null) {

@@ -75,8 +90,11 @@ return [null, "null"];

}
else if (value[constants_1.TEXT]) {
return [value, "text"];
}
else if (value instanceof Date) {
return [value.getTime(), "timestamp"];
}
else if (value instanceof raw_string_1.RawString) {
return [value.val, "str"];
}
else if (value instanceof text_1.Text) {
return [value, "text"];
}
else if (value instanceof Uint8Array) {

@@ -92,3 +110,3 @@ return [value, "bytes"];

else if (value[constants_1.OBJECT_ID]) {
throw new RangeError('Cannot create a reference to an existing document object');
throw new RangeError("Cannot create a reference to an existing document object");
}

@@ -98,6 +116,5 @@ else {

}
break;
case 'boolean':
case "boolean":
return [value, "boolean"];
case 'number':
case "number":
if (Number.isInteger(value)) {

@@ -109,6 +126,9 @@ return [value, "int"];

}
break;
case 'string':
return [value];
break;
case "string":
if (textV2) {
return [value, "text"];
}
else {
return [value, "str"];
}
default:

@@ -120,3 +140,3 @@ throw new RangeError(`Unsupported type of value: ${typeof value}`);

get(target, key) {
const { context, objectId, readonly, frozen, heads, cache } = target;
const { context, objectId, cache } = target;
if (key === Symbol.toStringTag) {

@@ -127,12 +147,8 @@ return target[Symbol.toStringTag];

return objectId;
if (key === constants_1.READ_ONLY)
return readonly;
if (key === constants_1.FROZEN)
return frozen;
if (key === constants_1.HEADS)
return heads;
if (key === constants_1.IS_PROXY)
return true;
if (key === constants_1.TRACE)
return target.trace;
if (key === constants_1.STATE)
return context;
return { handle: context };
if (!cache[key]) {

@@ -144,15 +160,7 @@ cache[key] = valueAt(target, key);

set(target, key, val) {
const { context, objectId, path, readonly, frozen } = target;
const { context, objectId, path, readonly, frozen, textV2 } = target;
target.cache = {}; // reset cache on set
if (val && val[constants_1.OBJECT_ID]) {
throw new RangeError('Cannot create a reference to an existing document object');
throw new RangeError("Cannot create a reference to an existing document object");
}
if (key === constants_1.FROZEN) {
target.frozen = val;
return true;
}
if (key === constants_1.HEADS) {
target.heads = val;
return true;
}
if (key === constants_1.TRACE) {

@@ -162,3 +170,3 @@ target.trace = val;

}
const [value, datatype] = import_value(val);
const [value, datatype] = import_value(val, textV2);
if (frozen) {

@@ -173,3 +181,3 @@ throw new RangeError("Attempting to use an outdated Automerge document");

const list = context.putObject(objectId, key, []);
const proxyList = listProxy(context, list, [...path, key], readonly);
const proxyList = listProxy(context, list, textV2, [...path, key], readonly);
for (let i = 0; i < value.length; i++) {

@@ -181,7 +189,12 @@ proxyList[i] = value[i];

case "text": {
const text = context.putObject(objectId, key, "", "text");
const proxyText = textProxy(context, text, [...path, key], readonly);
for (let i = 0; i < value.length; i++) {
proxyText[i] = value.get(i);
if (textV2) {
context.putObject(objectId, key, value);
}
else {
const text = context.putObject(objectId, key, "");
const proxyText = textProxy(context, text, [...path, key], readonly);
for (let i = 0; i < value.length; i++) {
proxyText[i] = value.get(i);
}
}
break;

@@ -191,3 +204,3 @@ }

const map = context.putObject(objectId, key, {});
const proxyMap = mapProxy(context, map, [...path, key], readonly);
const proxyMap = mapProxy(context, map, textV2, [...path, key], readonly);
for (const key in value) {

@@ -219,5 +232,7 @@ proxyMap[key] = value[key];

const value = this.get(target, key);
if (typeof value !== 'undefined') {
if (typeof value !== "undefined") {
return {
configurable: true, enumerable: true, value
configurable: true,
enumerable: true,
value,
};

@@ -235,6 +250,8 @@ }

get(target, index) {
const { context, objectId, readonly, frozen, heads } = target;
const { context, objectId, heads } = target;
index = parseListIndex(index);
if (index === Symbol.hasInstance) {
return (instance) => { return Array.isArray(instance); };
return instance => {
return Array.isArray(instance);
};
}

@@ -246,15 +263,11 @@ if (index === Symbol.toStringTag) {

return objectId;
if (index === constants_1.READ_ONLY)
return readonly;
if (index === constants_1.FROZEN)
return frozen;
if (index === constants_1.HEADS)
return heads;
if (index === constants_1.IS_PROXY)
return true;
if (index === constants_1.TRACE)
return target.trace;
if (index === constants_1.STATE)
return context;
if (index === 'length')
return { handle: context };
if (index === "length")
return context.length(objectId, heads);
if (typeof index === 'number') {
if (typeof index === "number") {
return valueAt(target, index);

@@ -267,15 +280,7 @@ }

set(target, index, val) {
const { context, objectId, path, readonly, frozen } = target;
const { context, objectId, path, readonly, frozen, textV2 } = target;
index = parseListIndex(index);
if (val && val[constants_1.OBJECT_ID]) {
throw new RangeError('Cannot create a reference to an existing document object');
throw new RangeError("Cannot create a reference to an existing document object");
}
if (index === constants_1.FROZEN) {
target.frozen = val;
return true;
}
if (index === constants_1.HEADS) {
target.heads = val;
return true;
}
if (index === constants_1.TRACE) {

@@ -286,5 +291,5 @@ target.trace = val;

if (typeof index == "string") {
throw new RangeError('list index must be a number');
throw new RangeError("list index must be a number");
}
const [value, datatype] = import_value(val);
const [value, datatype] = import_value(val, textV2);
if (frozen) {

@@ -305,3 +310,3 @@ throw new RangeError("Attempting to use an outdated Automerge document");

}
const proxyList = listProxy(context, list, [...path, index], readonly);
const proxyList = listProxy(context, list, textV2, [...path, index], readonly);
proxyList.splice(0, 0, ...value);

@@ -311,11 +316,21 @@ break;

case "text": {
let text;
if (index >= context.length(objectId)) {
text = context.insertObject(objectId, index, "", "text");
if (textV2) {
if (index >= context.length(objectId)) {
context.insertObject(objectId, index, value);
}
else {
context.putObject(objectId, index, value);
}
}
else {
text = context.putObject(objectId, index, "", "text");
let text;
if (index >= context.length(objectId)) {
text = context.insertObject(objectId, index, "");
}
else {
text = context.putObject(objectId, index, "");
}
const proxyText = textProxy(context, text, [...path, index], readonly);
proxyText.splice(0, 0, ...value);
}
const proxyText = textProxy(context, text, [...path, index], readonly);
proxyText.splice(0, 0, ...value);
break;

@@ -331,3 +346,3 @@ }

}
const proxyMap = mapProxy(context, map, [...path, index], readonly);
const proxyMap = mapProxy(context, map, textV2, [...path, index], readonly);
for (const key in value) {

@@ -351,4 +366,5 @@ proxyMap[key] = value[key];

index = parseListIndex(index);
if (context.get(objectId, index)[0] == "counter") {
throw new TypeError('Unsupported operation: deleting a counter from a list');
const elem = context.get(objectId, index);
if (elem != null && elem[0] == "counter") {
throw new TypeError("Unsupported operation: deleting a counter from a list");
}

@@ -361,10 +377,10 @@ context.delete(objectId, index);

index = parseListIndex(index);
if (typeof index === 'number') {
if (typeof index === "number") {
return index < context.length(objectId, heads);
}
return index === 'length';
return index === "length";
},
getOwnPropertyDescriptor(target, index) {
const { context, objectId, heads } = target;
if (index === 'length')
if (index === "length")
return { writable: true, value: context.length(objectId, heads) };

@@ -377,3 +393,5 @@ if (index === constants_1.OBJECT_ID)

},
getPrototypeOf(target) { return Object.getPrototypeOf(target); },
getPrototypeOf(target) {
return Object.getPrototypeOf(target);
},
ownKeys( /*target*/) {

@@ -387,30 +405,27 @@ const keys = [];

return keys;
}
},
};
const TextHandler = Object.assign({}, ListHandler, {
get(target, index) {
// FIXME this is a one line change from ListHandler.get()
const { context, objectId, readonly, frozen, heads } = target;
const { context, objectId, heads } = target;
index = parseListIndex(index);
if (index === Symbol.hasInstance) {
return (instance) => {
return Array.isArray(instance);
};
}
if (index === Symbol.toStringTag) {
return target[Symbol.toStringTag];
}
if (index === Symbol.hasInstance) {
return (instance) => { return Array.isArray(instance); };
}
if (index === constants_1.OBJECT_ID)
return objectId;
if (index === constants_1.READ_ONLY)
return readonly;
if (index === constants_1.FROZEN)
return frozen;
if (index === constants_1.HEADS)
return heads;
if (index === constants_1.IS_PROXY)
return true;
if (index === constants_1.TRACE)
return target.trace;
if (index === constants_1.STATE)
return context;
if (index === 'length')
return { handle: context };
if (index === "length")
return context.length(objectId, heads);
if (typeof index === 'number') {
if (typeof index === "number") {
return valueAt(target, index);

@@ -426,28 +441,61 @@ }

});
function mapProxy(context, objectId, path, readonly, heads) {
return new Proxy({ context, objectId, path, readonly: !!readonly, frozen: false, heads, cache: {} }, MapHandler);
function mapProxy(context, objectId, textV2, path, readonly, heads) {
const target = {
context,
objectId,
path: path || [],
readonly: !!readonly,
frozen: false,
heads,
cache: {},
textV2,
};
const proxied = {};
Object.assign(proxied, target);
let result = new Proxy(proxied, MapHandler);
// conversion through unknown is necessary because the types are so different
return result;
}
exports.mapProxy = mapProxy;
function listProxy(context, objectId, path, readonly, heads) {
const target = [];
Object.assign(target, { context, objectId, path, readonly: !!readonly, frozen: false, heads, cache: {} });
return new Proxy(target, ListHandler);
function listProxy(context, objectId, textV2, path, readonly, heads) {
const target = {
context,
objectId,
path: path || [],
readonly: !!readonly,
frozen: false,
heads,
cache: {},
textV2,
};
const proxied = [];
Object.assign(proxied, target);
// @ts-ignore
return new Proxy(proxied, ListHandler);
}
exports.listProxy = listProxy;
function textProxy(context, objectId, path, readonly, heads) {
const target = [];
Object.assign(target, { context, objectId, path, readonly: !!readonly, frozen: false, heads, cache: {} });
const target = {
context,
objectId,
path: path || [],
readonly: !!readonly,
frozen: false,
heads,
cache: {},
textV2: false,
};
return new Proxy(target, TextHandler);
}
exports.textProxy = textProxy;
function rootProxy(context, readonly) {
function rootProxy(context, textV2, readonly) {
/* eslint-disable-next-line */
return mapProxy(context, "_root", [], !!readonly);
return mapProxy(context, "_root", textV2, [], !!readonly);
}
exports.rootProxy = rootProxy;
function listMethods(target) {
const { context, objectId, path, readonly, frozen, heads } = target;
const { context, objectId, path, readonly, frozen, heads, textV2 } = target;
const methods = {
deleteAt(index, numDelete) {
if (typeof numDelete === 'number') {
if (typeof numDelete === "number") {
context.splice(objectId, index, numDelete);

@@ -461,3 +509,3 @@ }

fill(val, start, end) {
const [value, datatype] = import_value(val);
const [value, datatype] = import_value(val, textV2);
const length = context.length(objectId);

@@ -467,3 +515,8 @@ start = parseListIndex(start || 0);

for (let i = start; i < Math.min(end, length); i++) {
context.put(objectId, i, value, datatype);
if (datatype === "text" || datatype === "list" || datatype === "map") {
context.putObject(objectId, i, value);
}
else {
context.put(objectId, i, value, datatype);
}
}

@@ -476,3 +529,3 @@ return this;

const value = context.getWithType(objectId, i, heads);
if (value && value[1] === o[constants_1.OBJECT_ID] || value[1] === o) {
if (value && (value[1] === o[constants_1.OBJECT_ID] || value[1] === o)) {
return i;

@@ -513,3 +566,3 @@ }

if (val && val[constants_1.OBJECT_ID]) {
throw new RangeError('Cannot create a reference to an existing document object');
throw new RangeError("Cannot create a reference to an existing document object");
}

@@ -531,3 +584,3 @@ }

}
const values = vals.map((val) => import_value(val));
const values = vals.map(val => import_value(val, textV2));
for (const [value, datatype] of values) {

@@ -537,3 +590,3 @@ switch (datatype) {

const list = context.insertObject(objectId, index, []);
const proxyList = listProxy(context, list, [...path, index], readonly);
const proxyList = listProxy(context, list, textV2, [...path, index], readonly);
proxyList.splice(0, 0, ...value);

@@ -543,5 +596,10 @@ break;

case "text": {
const text = context.insertObject(objectId, index, "", "text");
const proxyText = textProxy(context, text, [...path, index], readonly);
proxyText.splice(0, 0, ...value);
if (textV2) {
context.insertObject(objectId, index, value);
}
else {
const text = context.insertObject(objectId, index, "");
const proxyText = textProxy(context, text, [...path, index], readonly);
proxyText.splice(0, 0, ...value);
}
break;

@@ -551,3 +609,3 @@ }

const map = context.insertObject(objectId, index, {});
const proxyMap = mapProxy(context, map, [...path, index], readonly);
const proxyMap = mapProxy(context, map, textV2, [...path, index], readonly);
for (const key in value) {

@@ -580,3 +638,3 @@ proxyMap[key] = value[key];

}
}
},
};

@@ -596,3 +654,3 @@ return iterator;

return { value, done: true };
}
},
};

@@ -612,3 +670,3 @@ return iterator;

}
}
},
};

@@ -652,3 +710,3 @@ return iterator;

let index = 0;
for (let v of this) {
for (const v of this) {
if (f(v, index)) {

@@ -662,3 +720,3 @@ return v;

let index = 0;
for (let v of this) {
for (const v of this) {
if (f(v, index)) {

@@ -672,3 +730,3 @@ return index;

includes(elem) {
return this.find((e) => e === elem) !== undefined;
return this.find(e => e === elem) !== undefined;
},

@@ -695,3 +753,3 @@ join(sep) {

let index = 0;
for (let v of this) {
for (const v of this) {
if (f(v, index)) {

@@ -712,3 +770,3 @@ return true;

}
}
},
};

@@ -721,3 +779,3 @@ return methods;

set(index, value) {
return this[index] = value;
return (this[index] = value);
},

@@ -728,11 +786,11 @@ get(index) {

toString() {
return context.text(objectId, heads).replace(//g, '');
return context.text(objectId, heads).replace(//g, "");
},
toSpans() {
const spans = [];
let chars = '';
let chars = "";
const length = context.length(objectId);
for (let i = 0; i < length; i++) {
const value = this[i];
if (typeof value === 'string') {
if (typeof value === "string") {
chars += value;

@@ -743,3 +801,3 @@ }

spans.push(chars);
chars = '';
chars = "";
}

@@ -760,5 +818,5 @@ spans.push(value);

return text.indexOf(o, start);
}
},
};
return methods;
}

@@ -7,3 +7,3 @@ "use strict";

constructor(text) {
if (typeof text === 'string') {
if (typeof text === "string") {
this.elems = [...text];

@@ -44,3 +44,3 @@ }

}
}
},
};

@@ -57,8 +57,8 @@ }

// https://jsperf.com/join-vs-loop-w-type-test
this.str = '';
this.str = "";
for (const elem of this.elems) {
if (typeof elem === 'string')
if (typeof elem === "string")
this.str += elem;
else
this.str += '\uFFFC';
this.str += "\uFFFC";
}

@@ -72,4 +72,4 @@ }

*
* For example, the value ['a', 'b', {x: 3}, 'c', 'd'] has spans:
* => ['ab', {x: 3}, 'cd']
* For example, the value `['a', 'b', {x: 3}, 'c', 'd']` has spans:
* `=> ['ab', {x: 3}, 'cd']`
*/

@@ -79,5 +79,5 @@ toSpans() {

this.spans = [];
let chars = '';
let chars = "";
for (const elem of this.elems) {
if (typeof elem === 'string') {
if (typeof elem === "string") {
chars += elem;

@@ -88,3 +88,3 @@ }

this.spans.push(chars);
chars = '';
chars = "";
}

@@ -91,0 +91,0 @@ this.spans.push(elem);

@@ -6,3 +6,3 @@ "use strict";

function defaultFactory() {
return (0, uuid_1.v4)().replace(/-/g, '');
return (0, uuid_1.v4)().replace(/-/g, "");
}

@@ -14,3 +14,7 @@ let factory = defaultFactory;

exports.uuid = uuid;
exports.uuid.setFactory = newFactory => { factory = newFactory; };
exports.uuid.reset = () => { factory = defaultFactory; };
exports.uuid.setFactory = newFactory => {
factory = newFactory;
};
exports.uuid.reset = () => {
factory = defaultFactory;
};
export declare const STATE: unique symbol;
export declare const HEADS: unique symbol;
export declare const TRACE: unique symbol;
export declare const OBJECT_ID: unique symbol;
export declare const READ_ONLY: unique symbol;
export declare const FROZEN: unique symbol;
export declare const IS_PROXY: unique symbol;
export declare const UINT: unique symbol;

@@ -8,0 +6,0 @@ export declare const INT: unique symbol;

@@ -37,6 +37,6 @@ import { Automerge, ObjID, Prop } from "@automerge/automerge-wasm";

context: Automerge;
path: string[];
path: Prop[];
objectId: ObjID;
key: Prop;
constructor(value: number, context: Automerge, path: string[], objectId: ObjID, key: Prop);
constructor(value: number, context: Automerge, path: Prop[], objectId: ObjID, key: Prop);
/**

@@ -59,4 +59,4 @@ * Increases the value of the counter by `delta`. If `delta` is not given,

* located.
*/
export declare function getWriteableCounter(value: number, context: Automerge, path: string[], objectId: ObjID, key: Prop): WriteableCounter;
*/
export declare function getWriteableCounter(value: number, context: Automerge, path: Prop[], objectId: ObjID, key: Prop): WriteableCounter;
export {};

@@ -1,282 +0,161 @@

/** @hidden **/
export { /** @hidden */ uuid } from './uuid';
import { AutomergeValue } from "./types";
export { AutomergeValue, Text, Counter, Int, Uint, Float64, ScalarValue } from "./types";
import { type API, type Patch } from "@automerge/automerge-wasm";
export { type Patch, PutPatch, DelPatch, SplicePatch, IncPatch, SyncMessage, } from "@automerge/automerge-wasm";
import { Actor as ActorId, Prop, ObjID, Change, DecodedChange, Heads, Automerge, MaterializeValue } from "@automerge/automerge-wasm";
import { JsSyncState as SyncState, SyncMessage, DecodedSyncMessage } from "@automerge/automerge-wasm";
/** Options passed to {@link change}, and {@link emptyChange}
* @typeParam T - The type of value contained in the document
*/
export type ChangeOptions<T> = {
/** A message which describes the changes */
message?: string;
/** The unix timestamp of the change (purely advisory, not used in conflict resolution) */
time?: number;
/** A callback which will be called to notify the caller of any changes to the document */
patchCallback?: PatchCallback<T>;
};
/** Options passed to {@link loadIncremental}, {@link applyChanges}, and {@link receiveSyncMessage}
* @typeParam T - The type of value contained in the document
*/
export type ApplyOptions<T> = {
patchCallback?: PatchCallback<T>;
};
/**
* An automerge document.
* @typeParam T - The type of the value contained in this document
* # Automerge
*
* Note that this provides read only access to the fields of the value. To
* modify the value use {@link change}
*/
export type Doc<T> = {
readonly [P in keyof T]: T[P];
};
/**
* Function which is called by {@link change} when making changes to a `Doc<T>`
* @typeParam T - The type of value contained in the document
* This library provides the core automerge data structure and sync algorithms.
* Other libraries can be built on top of this one which provide IO and
* persistence.
*
* This function may mutate `doc`
*/
export type ChangeFn<T> = (doc: T) => void;
/**
* Callback which is called by various methods in this library to notify the
* user of what changes have been made.
* @param patch - A description of the changes made
* @param before - The document before the change was made
* @param after - The document after the change was made
*/
export type PatchCallback<T> = (patch: Patch, before: Doc<T>, after: Doc<T>) => void;
/** @hidden **/
export interface State<T> {
change: DecodedChange;
snapshot: T;
}
/** @hidden **/
export declare function use(api: API): void;
/**
* Options to be passed to {@link init} or {@link load}
* @typeParam T - The type of the value the document contains
*/
export type InitOptions<T> = {
/** The actor ID to use for this document, a random one will be generated if `null` is passed */
actor?: ActorId;
freeze?: boolean;
/** A callback which will be called with the initial patch once the document has finished loading */
patchCallback?: PatchCallback<T>;
};
/** @hidden */
export declare function getBackend<T>(doc: Doc<T>): Automerge;
/**
* Create a new automerge document
* An automerge document can be though of an immutable POJO (plain old javascript
* object) which `automerge` tracks the history of, allowing it to be merged with
* any other automerge document.
*
* @typeParam T - The type of value contained in the document. This will be the
* type that is passed to the change closure in {@link change}
* @param _opts - Either an actorId or an {@link InitOptions} (which may
* contain an actorId). If this is null the document will be initialised with a
* random actor ID
*/
export declare function init<T>(_opts?: ActorId | InitOptions<T>): Doc<T>;
/**
* Make an immutable view of an automerge document as at `heads`
* ## Creating and modifying a document
*
* @remarks
* The document returned from this function cannot be passed to {@link change}.
* This is because it shares the same underlying memory as `doc`, but it is
* consequently a very cheap copy.
* You can create a document with {@link init} or {@link from} and then make
* changes to it with {@link change}, you can merge two documents with {@link
* merge}.
*
* Note that this function will throw an error if any of the hashes in `heads`
* are not in the document.
* ```ts
* import * as automerge from "@automerge/automerge"
*
* @typeParam T - The type of the value contained in the document
* @param doc - The document to create a view of
* @param heads - The hashes of the heads to create a view at
*/
export declare function view<T>(doc: Doc<T>, heads: Heads): Doc<T>;
/**
* Make a full writable copy of an automerge document
* type DocType = {ideas: Array<automerge.Text>}
*
* @remarks
* Unlike {@link view} this function makes a full copy of the memory backing
* the document and can thus be passed to {@link change}. It also generates a
* new actor ID so that changes made in the new document do not create duplicate
* sequence numbers with respect to the old document. If you need control over
* the actor ID which is generated you can pass the actor ID as the second
* argument
* let doc1 = automerge.init<DocType>()
* doc1 = automerge.change(doc1, d => {
* d.ideas = [new automerge.Text("an immutable document")]
* })
*
* @typeParam T - The type of the value contained in the document
* @param doc - The document to clone
* @param _opts - Either an actor ID to use for the new doc or an {@link InitOptions}
*/
export declare function clone<T>(doc: Doc<T>, _opts?: ActorId | InitOptions<T>): Doc<T>;
/** Explicity free the memory backing a document. Note that this is note
* necessary in environments which support
* [`FinalizationRegistry`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry)
*/
export declare function free<T>(doc: Doc<T>): void;
/**
* Create an automerge document from a POJO
*
* @param initialState - The initial state which will be copied into the document
* @typeParam T - The type of the value passed to `from` _and_ the type the resulting document will contain
* @typeParam actor - The actor ID of the resulting document, if this is null a random actor ID will be used
*
* @example
* ```
* const doc = automerge.from({
* tasks: [
* {description: "feed dogs", done: false}
* ]
* let doc2 = automerge.init<DocType>()
* doc2 = automerge.merge(doc2, automerge.clone(doc1))
* doc2 = automerge.change<DocType>(doc2, d => {
* d.ideas.push(new automerge.Text("which records it's history"))
* })
* ```
*/
export declare function from<T extends Record<string, unknown>>(initialState: T | Doc<T>, actor?: ActorId): Doc<T>;
/**
* Update the contents of an automerge document
* @typeParam T - The type of the value contained in the document
* @param doc - The document to update
* @param options - Either a message, an {@link ChangeOptions}, or a {@link ChangeFn}
* @param callback - A `ChangeFn` to be used if `options` was a `string`
*
* Note that if the second argument is a function it will be used as the `ChangeFn` regardless of what the third argument is.
*
* @example A simple change
* ```
* let doc1 = automerge.init()
* // Note the `automerge.clone` call, see the "cloning" section of this readme for
* // more detail
* doc1 = automerge.merge(doc1, automerge.clone(doc2))
* doc1 = automerge.change(doc1, d => {
* d.key = "value"
* d.ideas[0].deleteAt(13, 8)
* d.ideas[0].insertAt(13, "object")
* })
* assert.equal(doc1.key, "value")
*
* let doc3 = automerge.merge(doc1, doc2)
* // doc3 is now {ideas: ["an immutable object", "which records it's history"]}
* ```
*
* @example A change with a message
* ## Applying changes from another document
*
* ```
* doc1 = automerge.change(doc1, "add another value", d => {
* d.key2 = "value2"
* })
* ```
* You can get a representation of the result of the last {@link change} you made
* to a document with {@link getLastLocalChange} and you can apply that change to
* another document using {@link applyChanges}.
*
* @example A change with a message and a timestamp
* If you need to get just the changes which are in one document but not in another
* you can use {@link getHeads} to get the heads of the document without the
* changes and then {@link getMissingDeps}, passing the result of {@link getHeads}
* on the document with the changes.
*
* ```
* doc1 = automerge.change(doc1, {message: "add another value", timestamp: 1640995200}, d => {
* d.key2 = "value2"
* })
* ```
* ## Saving and loading documents
*
* @example responding to a patch callback
* ```
* let patchedPath
* let patchCallback = patch => {
* patchedPath = patch.path
* }
* doc1 = automerge.change(doc1, {message, "add another value", timestamp: 1640995200, patchCallback}, d => {
* d.key2 = "value2"
* You can {@link save} a document to generate a compresed binary representation of
* the document which can be loaded with {@link load}. If you have a document which
* you have recently made changes to you can generate recent changes with {@link
* saveIncremental}, this will generate all the changes since you last called
* `saveIncremental`, the changes generated can be applied to another document with
* {@link loadIncremental}.
*
* ## Viewing different versions of a document
*
* Occasionally you may wish to explicitly step to a different point in a document
* history. One common reason to do this is if you need to obtain a set of changes
* which take the document from one state to another in order to send those changes
* to another peer (or to save them somewhere). You can use {@link view} to do this.
*
* ```ts
* import * as automerge from "@automerge/automerge"
* import * as assert from "assert"
*
* let doc = automerge.from({
* key1: "value1",
* })
* assert.equal(patchedPath, ["key2"])
* ```
*/
export declare function change<T>(doc: Doc<T>, options: string | ChangeOptions<T> | ChangeFn<T>, callback?: ChangeFn<T>): Doc<T>;
/**
* Make a change to a document which does not modify the document
*
* @param doc - The doc to add the empty change to
* @param options - Either a message or a {@link ChangeOptions} for the new change
* // Make a clone of the document at this point, maybe this is actually on another
* // peer.
* let doc2 = automerge.clone < any > doc
*
* Why would you want to do this? One reason might be that you have merged
* changes from some other peers and you want to generate a change which
* depends on those merged changes so that you can sign the new change with all
* of the merged changes as part of the new change.
*/
export declare function emptyChange<T>(doc: Doc<T>, options: string | ChangeOptions<T> | void): Doc<T>;
/**
* Load an automerge document from a compressed document produce by {@link save}
* let heads = automerge.getHeads(doc)
*
* @typeParam T - The type of the value which is contained in the document.
* Note that no validation is done to make sure this type is in
* fact the type of the contained value so be a bit careful
* @param data - The compressed document
* @param _opts - Either an actor ID or some {@link InitOptions}, if the actor
* ID is null a random actor ID will be created
* doc =
* automerge.change <
* any >
* (doc,
* d => {
* d.key2 = "value2"
* })
*
* Note that `load` will throw an error if passed incomplete content (for
* example if you are receiving content over the network and don't know if you
* have the complete document yet). If you need to handle incomplete content use
* {@link init} followed by {@link loadIncremental}.
*/
export declare function load<T>(data: Uint8Array, _opts?: ActorId | InitOptions<T>): Doc<T>;
/**
* Load changes produced by {@link saveIncremental}, or partial changes
* doc =
* automerge.change <
* any >
* (doc,
* d => {
* d.key3 = "value3"
* })
*
* @typeParam T - The type of the value which is contained in the document.
* Note that no validation is done to make sure this type is in
* fact the type of the contained value so be a bit careful
* @param data - The compressedchanges
* @param opts - an {@link ApplyOptions}
* // At this point we've generated two separate changes, now we want to send
* // just those changes to someone else
*
* This function is useful when staying up to date with a connected peer.
* Perhaps the other end sent you a full compresed document which you loaded
* with {@link load} and they're sending you the result of
* {@link getLastLocalChange} every time they make a change.
* // view is a cheap reference based copy of a document at a given set of heads
* let before = automerge.view(doc, heads)
*
* Note that this function will succesfully load the results of {@link save} as
* well as {@link getLastLocalChange} or any other incremental change.
*/
export declare function loadIncremental<T>(doc: Doc<T>, data: Uint8Array, opts?: ApplyOptions<T>): Doc<T>;
/**
* Export the contents of a document to a compressed format
* // This view doesn't show the last two changes in the document state
* assert.deepEqual(before, {
* key1: "value1",
* })
*
* @param doc - The doc to save
* // Get the changes to send to doc2
* let changes = automerge.getChanges(before, doc)
*
* The returned bytes can be passed to {@link load} or {@link loadIncremental}
*/
export declare function save<T>(doc: Doc<T>): Uint8Array;
/**
* Merge `local` into `remote`
* @typeParam T - The type of values contained in each document
* @param local - The document to merge changes into
* @param remote - The document to merge changes from
* // Apply the changes at doc2
* doc2 = automerge.applyChanges < any > (doc2, changes)[0]
* assert.deepEqual(doc2, {
* key1: "value1",
* key2: "value2",
* key3: "value3",
* })
* ```
*
* @returns - The merged document
* If you have a {@link view} of a document which you want to make changes to you
* can {@link clone} the viewed document.
*
* Often when you are merging documents you will also need to clone them. Both
* arguments to `merge` are frozen after the call so you can no longer call
* mutating methods (such as {@link change}) on them. The symtom of this will be
* an error which says "Attempting to change an out of date document". To
* overcome this call {@link clone} on the argument before passing it to {@link
* merge}.
*/
export declare function merge<T>(local: Doc<T>, remote: Doc<T>): Doc<T>;
/**
* Get the actor ID associated with the document
*/
export declare function getActorId<T>(doc: Doc<T>): ActorId;
/**
* The type of conflicts for particular key or index
* ## Syncing
*
* Maps and sequences in automerge can contain conflicting values for a
* particular key or index. In this case {@link getConflicts} can be used to
* obtain a `Conflicts` representing the multiple values present for the property
* The sync protocol is stateful. This means that we start by creating a {@link
* SyncState} for each peer we are communicating with using {@link initSyncState}.
* Then we generate a message to send to the peer by calling {@link
* generateSyncMessage}. When we receive a message from the peer we call {@link
* receiveSyncMessage}. Here's a simple example of a loop which just keeps two
* peers in sync.
*
* A `Conflicts` is a map from a unique (per property or index) key to one of
* the possible conflicting values for the given property.
*/
type Conflicts = {
[key: string]: AutomergeValue;
};
/**
* Get the conflicts associated with a property
* ```ts
* let sync1 = automerge.initSyncState()
* let msg: Uint8Array | null
* ;[sync1, msg] = automerge.generateSyncMessage(doc1, sync1)
*
* The values of properties in a map in automerge can be conflicted if there
* are concurrent "put" operations to the same key. Automerge chooses one value
* arbitrarily (but deterministically, any two nodes who have the same set of
* changes will choose the same value) from the set of conflicting values to
* present as the value of the key.
* while (true) {
* if (msg != null) {
* network.send(msg)
* }
* let resp: Uint8Array =
* (network.receive()[(doc1, sync1, _ignore)] =
* automerge.receiveSyncMessage(doc1, sync1, resp)[(sync1, msg)] =
* automerge.generateSyncMessage(doc1, sync1))
* }
* ```
*
* Sometimes you may want to examine these conflicts, in this case you can use
* {@link getConflicts} to get the conflicts for the key.
* ## Conflicts
*
* @example
* The only time conflicts occur in automerge documents is in concurrent
* assignments to the same key in an object. In this case automerge
* deterministically chooses an arbitrary value to present to the application but
* you can examine the conflicts using {@link getConflicts}.
*
* ```

@@ -312,122 +191,53 @@ * import * as automerge from "@automerge/automerge"

* ```
*/
export declare function getConflicts<T>(doc: Doc<T>, prop: Prop): Conflicts | undefined;
/**
* Get the binary representation of the last change which was made to this doc
*
* This is most useful when staying in sync with other peers, every time you
* make a change locally via {@link change} you immediately call {@link
* getLastLocalChange} and send the result over the network to other peers.
*/
export declare function getLastLocalChange<T>(doc: Doc<T>): Change | undefined;
/**
* Return the object ID of an arbitrary javascript value
* ## Actor IDs
*
* This is useful to determine if something is actually an automerge document,
* if `doc` is not an automerge document this will return null.
*/
export declare function getObjectId(doc: any): ObjID | null;
/**
* Get the changes which are in `newState` but not in `oldState`. The returned
* changes can be loaded in `oldState` via {@link applyChanges}.
* By default automerge will generate a random actor ID for you, but most methods
* for creating a document allow you to set the actor ID. You can get the actor ID
* associated with the document by calling {@link getActorId}. Actor IDs must not
* be used in concurrent threads of executiong - all changes by a given actor ID
* are expected to be sequential.
*
* Note that this will crash if there are changes in `oldState` which are not in `newState`.
*/
export declare function getChanges<T>(oldState: Doc<T>, newState: Doc<T>): Change[];
/**
* Get all the changes in a document
* ## Listening to patches
*
* This is different to {@link save} because the output is an array of changes
* which can be individually applied via {@link applyChanges}`
* Sometimes you want to respond to changes made to an automerge document. In this
* case you can use the {@link PatchCallback} type to receive notifications when
* changes have been made.
*
*/
export declare function getAllChanges<T>(doc: Doc<T>): Change[];
/**
* Apply changes received from another document
* ## Cloning
*
* `doc` will be updated to reflect the `changes`. If there are changes which
* we do not have dependencies for yet those will be stored in the document and
* applied when the depended on changes arrive.
* Currently you cannot make mutating changes (i.e. call {@link change}) to a
* document which you have two pointers to. For example, in this code:
*
* You can use the {@link ApplyOptions} to pass a patchcallback which will be
* informed of any changes which occur as a result of applying the changes
* ```javascript
* let doc1 = automerge.init()
* let doc2 = automerge.change(doc1, d => (d.key = "value"))
* ```
*
*/
export declare function applyChanges<T>(doc: Doc<T>, changes: Change[], opts?: ApplyOptions<T>): [Doc<T>];
/** @hidden */
export declare function getHistory<T>(doc: Doc<T>): State<T>[];
/** @hidden */
export declare function equals(val1: unknown, val2: unknown): boolean;
/**
* encode a {@link SyncState} into binary to send over the network
* `doc1` and `doc2` are both pointers to the same state. Any attempt to call
* mutating methods on `doc1` will now result in an error like
*
* @group sync
* */
export declare function encodeSyncState(state: SyncState): Uint8Array;
/**
* Decode some binary data into a {@link SyncState}
* Attempting to change an out of date document
*
* @group sync
*/
export declare function decodeSyncState(state: Uint8Array): SyncState;
/**
* Generate a sync message to send to the peer represented by `inState`
* @param doc - The doc to generate messages about
* @param inState - The {@link SyncState} representing the peer we are talking to
* If you encounter this you need to clone the original document, the above sample
* would work as:
*
* @group sync
* ```javascript
* let doc1 = automerge.init()
* let doc2 = automerge.change(automerge.clone(doc1), d => (d.key = "value"))
* ```
* @packageDocumentation
*
* @returns An array of `[newSyncState, syncMessage | null]` where
* `newSyncState` should replace `inState` and `syncMessage` should be sent to
* the peer if it is not null. If `syncMessage` is null then we are up to date.
*/
export declare function generateSyncMessage<T>(doc: Doc<T>, inState: SyncState): [SyncState, SyncMessage | null];
/**
* Update a document and our sync state on receiving a sync message
* ## The {@link unstable} module
*
* @group sync
*
* @param doc - The doc the sync message is about
* @param inState - The {@link SyncState} for the peer we are communicating with
* @param message - The message which was received
* @param opts - Any {@link ApplyOption}s, used for passing a
* {@link PatchCallback} which will be informed of any changes
* in `doc` which occur because of the received sync message.
*
* @returns An array of `[newDoc, newSyncState, syncMessage | null]` where
* `newDoc` is the updated state of `doc`, `newSyncState` should replace
* `inState` and `syncMessage` should be sent to the peer if it is not null. If
* `syncMessage` is null then we are up to date.
* We are working on some changes to automerge which are not yet complete and
* will result in backwards incompatible API changes. Once these changes are
* ready for production use we will release a new major version of automerge.
* However, until that point you can use the {@link unstable} module to try out
* the new features, documents from the {@link unstable} module are
* interoperable with documents from the main module. Please see the docs for
* the {@link unstable} module for more details.
*/
export declare function receiveSyncMessage<T>(doc: Doc<T>, inState: SyncState, message: SyncMessage, opts?: ApplyOptions<T>): [Doc<T>, SyncState, null];
/**
* Create a new, blank {@link SyncState}
*
* When communicating with a peer for the first time use this to generate a new
* {@link SyncState} for them
*
* @group sync
*/
export declare function initSyncState(): SyncState;
/** @hidden */
export declare function encodeChange(change: DecodedChange): Change;
/** @hidden */
export declare function decodeChange(data: Change): DecodedChange;
/** @hidden */
export declare function encodeSyncMessage(message: DecodedSyncMessage): SyncMessage;
/** @hidden */
export declare function decodeSyncMessage(message: SyncMessage): DecodedSyncMessage;
/**
* Get any changes in `doc` which are not dependencies of `heads`
*/
export declare function getMissingDeps<T>(doc: Doc<T>, heads: Heads): Heads;
/**
* Get the hashes of the heads of this document
*/
export declare function getHeads<T>(doc: Doc<T>): Heads;
/** @hidden */
export declare function dump<T>(doc: Doc<T>): void;
/** @hidden */
export declare function toJS<T>(doc: Doc<T>): T;
export declare function isAutomerge(doc: unknown): boolean;
export type { API, SyncState, ActorId, Conflicts, Prop, Change, ObjID, DecodedChange, DecodedSyncMessage, Heads, MaterializeValue };
export * from "./stable";
import * as unstable from "./unstable";
export { unstable };

@@ -0,3 +1,4 @@

export { ChangeToEncode } from "@automerge/automerge-wasm";
import { API } from "@automerge/automerge-wasm";
export declare function UseApi(api: API): void;
export declare const ApiHandler: API;
// Properties of the document root object
//const OPTIONS = Symbol('_options') // object containing options passed to init()
//const CACHE = Symbol('_cache') // map from objectId to immutable object
//export const STATE = Symbol.for('_am_state') // object containing metadata about current state (e.g. sequence numbers)
export const STATE = Symbol.for('_am_meta'); // object containing metadata about current state (e.g. sequence numbers)
export const HEADS = Symbol.for('_am_heads'); // object containing metadata about current state (e.g. sequence numbers)
export const TRACE = Symbol.for('_am_trace'); // object containing metadata about current state (e.g. sequence numbers)
export const OBJECT_ID = Symbol.for('_am_objectId'); // object containing metadata about current state (e.g. sequence numbers)
export const READ_ONLY = Symbol.for('_am_readOnly'); // object containing metadata about current state (e.g. sequence numbers)
export const FROZEN = Symbol.for('_am_frozen'); // object containing metadata about current state (e.g. sequence numbers)
export const UINT = Symbol.for('_am_uint');
export const INT = Symbol.for('_am_int');
export const F64 = Symbol.for('_am_f64');
export const COUNTER = Symbol.for('_am_counter');
export const TEXT = Symbol.for('_am_text');
// Properties of all Automerge objects
//const OBJECT_ID = Symbol('_objectId') // the object ID of the current object (string)
//const CONFLICTS = Symbol('_conflicts') // map or list (depending on object type) of conflicts
//const CHANGE = Symbol('_change') // the context object on proxy objects used in change callback
//const ELEM_IDS = Symbol('_elemIds') // list containing the element ID of each list element
export const STATE = Symbol.for("_am_meta"); // symbol used to hide application metadata on automerge objects
export const TRACE = Symbol.for("_am_trace"); // used for debugging
export const OBJECT_ID = Symbol.for("_am_objectId"); // synbol used to hide the object id on automerge objects
export const IS_PROXY = Symbol.for("_am_isProxy"); // symbol used to test if the document is a proxy object
export const UINT = Symbol.for("_am_uint");
export const INT = Symbol.for("_am_int");
export const F64 = Symbol.for("_am_f64");
export const COUNTER = Symbol.for("_am_counter");
export const TEXT = Symbol.for("_am_text");

@@ -56,3 +56,3 @@ import { COUNTER } from "./constants";

increment(delta) {
delta = typeof delta === 'number' ? delta : 1;
delta = typeof delta === "number" ? delta : 1;
this.context.increment(this.objectId, this.key, delta);

@@ -67,3 +67,3 @@ this.value += delta;

decrement(delta) {
return this.increment(typeof delta === 'number' ? -delta : -1);
return this.increment(typeof delta === "number" ? -delta : -1);
}

@@ -77,3 +77,3 @@ }

* located.
*/
*/
export function getWriteableCounter(value, context, path, objectId, key) {

@@ -80,0 +80,0 @@ return new WriteableCounter(value, context, path, objectId, key);

@@ -1,450 +0,161 @@

var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
/** @hidden **/
export { /** @hidden */ uuid } from './uuid';
import { rootProxy, listProxy, textProxy, mapProxy } from "./proxies";
import { STATE, HEADS, TRACE, OBJECT_ID, READ_ONLY, FROZEN } from "./constants";
import { Text, Counter } from "./types";
export { Text, Counter, Int, Uint, Float64 } from "./types";
import { ApiHandler, UseApi } from "./low_level";
/** @hidden **/
export function use(api) {
UseApi(api);
}
import * as wasm from "@automerge/automerge-wasm";
use(wasm);
/** @hidden */
export function getBackend(doc) {
return _state(doc).handle;
}
function _state(doc, checkroot = true) {
if (typeof doc !== 'object') {
throw new RangeError("must be the document root");
}
const state = Reflect.get(doc, STATE);
if (state === undefined || state == null || (checkroot && _obj(doc) !== "_root")) {
throw new RangeError("must be the document root");
}
return state;
}
function _frozen(doc) {
return Reflect.get(doc, FROZEN) === true;
}
function _trace(doc) {
return Reflect.get(doc, TRACE);
}
function _set_heads(doc, heads) {
_state(doc).heads = heads;
}
function _clear_heads(doc) {
Reflect.set(doc, HEADS, undefined);
Reflect.set(doc, TRACE, undefined);
}
function _obj(doc) {
if (!(typeof doc === 'object') || doc === null) {
return null;
}
return Reflect.get(doc, OBJECT_ID);
}
function _readonly(doc) {
return Reflect.get(doc, READ_ONLY) !== false;
}
function importOpts(_actor) {
if (typeof _actor === 'object') {
return _actor;
}
else {
return { actor: _actor };
}
}
/**
* Create a new automerge document
* # Automerge
*
* @typeParam T - The type of value contained in the document. This will be the
* type that is passed to the change closure in {@link change}
* @param _opts - Either an actorId or an {@link InitOptions} (which may
* contain an actorId). If this is null the document will be initialised with a
* random actor ID
*/
export function init(_opts) {
let opts = importOpts(_opts);
let freeze = !!opts.freeze;
let patchCallback = opts.patchCallback;
const handle = ApiHandler.create(opts.actor);
handle.enablePatches(true);
handle.enableFreeze(!!opts.freeze);
handle.registerDatatype("counter", (n) => new Counter(n));
handle.registerDatatype("text", (n) => new Text(n));
const doc = handle.materialize("/", undefined, { handle, heads: undefined, freeze, patchCallback });
return doc;
}
/**
* Make an immutable view of an automerge document as at `heads`
* This library provides the core automerge data structure and sync algorithms.
* Other libraries can be built on top of this one which provide IO and
* persistence.
*
* @remarks
* The document returned from this function cannot be passed to {@link change}.
* This is because it shares the same underlying memory as `doc`, but it is
* consequently a very cheap copy.
* An automerge document can be though of an immutable POJO (plain old javascript
* object) which `automerge` tracks the history of, allowing it to be merged with
* any other automerge document.
*
* Note that this function will throw an error if any of the hashes in `heads`
* are not in the document.
* ## Creating and modifying a document
*
* @typeParam T - The type of the value contained in the document
* @param doc - The document to create a view of
* @param heads - The hashes of the heads to create a view at
*/
export function view(doc, heads) {
const state = _state(doc);
const handle = state.handle;
return state.handle.materialize("/", heads, Object.assign(Object.assign({}, state), { handle, heads }));
}
/**
* Make a full writable copy of an automerge document
* You can create a document with {@link init} or {@link from} and then make
* changes to it with {@link change}, you can merge two documents with {@link
* merge}.
*
* @remarks
* Unlike {@link view} this function makes a full copy of the memory backing
* the document and can thus be passed to {@link change}. It also generates a
* new actor ID so that changes made in the new document do not create duplicate
* sequence numbers with respect to the old document. If you need control over
* the actor ID which is generated you can pass the actor ID as the second
* argument
* ```ts
* import * as automerge from "@automerge/automerge"
*
* @typeParam T - The type of the value contained in the document
* @param doc - The document to clone
* @param _opts - Either an actor ID to use for the new doc or an {@link InitOptions}
*/
export function clone(doc, _opts) {
const state = _state(doc);
const heads = state.heads;
const opts = importOpts(_opts);
const handle = state.handle.fork(opts.actor, heads);
// `change` uses the presence of state.heads to determine if we are in a view
// set it to undefined to indicate that this is a full fat document
const { heads: oldHeads } = state, stateSansHeads = __rest(state, ["heads"]);
return handle.applyPatches(doc, Object.assign(Object.assign({}, stateSansHeads), { handle }));
}
/** Explicity free the memory backing a document. Note that this is note
* necessary in environments which support
* [`FinalizationRegistry`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry)
*/
export function free(doc) {
return _state(doc).handle.free();
}
/**
* Create an automerge document from a POJO
* type DocType = {ideas: Array<automerge.Text>}
*
* @param initialState - The initial state which will be copied into the document
* @typeParam T - The type of the value passed to `from` _and_ the type the resulting document will contain
* @typeParam actor - The actor ID of the resulting document, if this is null a random actor ID will be used
* let doc1 = automerge.init<DocType>()
* doc1 = automerge.change(doc1, d => {
* d.ideas = [new automerge.Text("an immutable document")]
* })
*
* @example
* ```
* const doc = automerge.from({
* tasks: [
* {description: "feed dogs", done: false}
* ]
* let doc2 = automerge.init<DocType>()
* doc2 = automerge.merge(doc2, automerge.clone(doc1))
* doc2 = automerge.change<DocType>(doc2, d => {
* d.ideas.push(new automerge.Text("which records it's history"))
* })
* ```
*/
export function from(initialState, actor) {
return change(init(actor), (d) => Object.assign(d, initialState));
}
/**
* Update the contents of an automerge document
* @typeParam T - The type of the value contained in the document
* @param doc - The document to update
* @param options - Either a message, an {@link ChangeOptions}, or a {@link ChangeFn}
* @param callback - A `ChangeFn` to be used if `options` was a `string`
*
* Note that if the second argument is a function it will be used as the `ChangeFn` regardless of what the third argument is.
*
* @example A simple change
* ```
* let doc1 = automerge.init()
* // Note the `automerge.clone` call, see the "cloning" section of this readme for
* // more detail
* doc1 = automerge.merge(doc1, automerge.clone(doc2))
* doc1 = automerge.change(doc1, d => {
* d.key = "value"
* d.ideas[0].deleteAt(13, 8)
* d.ideas[0].insertAt(13, "object")
* })
* assert.equal(doc1.key, "value")
*
* let doc3 = automerge.merge(doc1, doc2)
* // doc3 is now {ideas: ["an immutable object", "which records it's history"]}
* ```
*
* @example A change with a message
* ## Applying changes from another document
*
* ```
* doc1 = automerge.change(doc1, "add another value", d => {
* d.key2 = "value2"
* })
* ```
* You can get a representation of the result of the last {@link change} you made
* to a document with {@link getLastLocalChange} and you can apply that change to
* another document using {@link applyChanges}.
*
* @example A change with a message and a timestamp
* If you need to get just the changes which are in one document but not in another
* you can use {@link getHeads} to get the heads of the document without the
* changes and then {@link getMissingDeps}, passing the result of {@link getHeads}
* on the document with the changes.
*
* ```
* doc1 = automerge.change(doc1, {message: "add another value", timestamp: 1640995200}, d => {
* d.key2 = "value2"
* ## Saving and loading documents
*
* You can {@link save} a document to generate a compresed binary representation of
* the document which can be loaded with {@link load}. If you have a document which
* you have recently made changes to you can generate recent changes with {@link
* saveIncremental}, this will generate all the changes since you last called
* `saveIncremental`, the changes generated can be applied to another document with
* {@link loadIncremental}.
*
* ## Viewing different versions of a document
*
* Occasionally you may wish to explicitly step to a different point in a document
* history. One common reason to do this is if you need to obtain a set of changes
* which take the document from one state to another in order to send those changes
* to another peer (or to save them somewhere). You can use {@link view} to do this.
*
* ```ts
* import * as automerge from "@automerge/automerge"
* import * as assert from "assert"
*
* let doc = automerge.from({
* key1: "value1",
* })
* ```
*
* @example responding to a patch callback
* ```
* let patchedPath
* let patchCallback = patch => {
* patchedPath = patch.path
* }
* doc1 = automerge.change(doc1, {message, "add another value", timestamp: 1640995200, patchCallback}, d => {
* // Make a clone of the document at this point, maybe this is actually on another
* // peer.
* let doc2 = automerge.clone < any > doc
*
* let heads = automerge.getHeads(doc)
*
* doc =
* automerge.change <
* any >
* (doc,
* d => {
* d.key2 = "value2"
* })
* assert.equal(patchedPath, ["key2"])
* ```
*/
export function change(doc, options, callback) {
if (typeof options === 'function') {
return _change(doc, {}, options);
}
else if (typeof callback === 'function') {
if (typeof options === "string") {
options = { message: options };
}
return _change(doc, options, callback);
}
else {
throw RangeError("Invalid args for change");
}
}
function progressDocument(doc, heads, callback) {
if (heads == null) {
return doc;
}
let state = _state(doc);
let nextState = Object.assign(Object.assign({}, state), { heads: undefined });
let nextDoc = state.handle.applyPatches(doc, nextState, callback);
state.heads = heads;
return nextDoc;
}
function _change(doc, options, callback) {
if (typeof callback !== "function") {
throw new RangeError("invalid change function");
}
const state = _state(doc);
if (doc === undefined || state === undefined) {
throw new RangeError("must be the document root");
}
if (state.heads) {
throw new RangeError("Attempting to change an outdated document. Use Automerge.clone() if you wish to make a writable copy.");
}
if (_readonly(doc) === false) {
throw new RangeError("Calls to Automerge.change cannot be nested");
}
const heads = state.handle.getHeads();
try {
state.heads = heads;
const root = rootProxy(state.handle);
callback(root);
if (state.handle.pendingOps() === 0) {
state.heads = undefined;
return doc;
}
else {
state.handle.commit(options.message, options.time);
return progressDocument(doc, heads, options.patchCallback || state.patchCallback);
}
}
catch (e) {
//console.log("ERROR: ",e)
state.heads = undefined;
state.handle.rollback();
throw e;
}
}
/**
* Make a change to a document which does not modify the document
* })
*
* @param doc - The doc to add the empty change to
* @param options - Either a message or a {@link ChangeOptions} for the new change
* doc =
* automerge.change <
* any >
* (doc,
* d => {
* d.key3 = "value3"
* })
*
* Why would you want to do this? One reason might be that you have merged
* changes from some other peers and you want to generate a change which
* depends on those merged changes so that you can sign the new change with all
* of the merged changes as part of the new change.
*/
export function emptyChange(doc, options) {
if (options === undefined) {
options = {};
}
if (typeof options === "string") {
options = { message: options };
}
const state = _state(doc);
if (state.heads) {
throw new RangeError("Attempting to change an outdated document. Use Automerge.clone() if you wish to make a writable copy.");
}
if (_readonly(doc) === false) {
throw new RangeError("Calls to Automerge.change cannot be nested");
}
const heads = state.handle.getHeads();
state.handle.emptyChange(options.message, options.time);
return progressDocument(doc, heads);
}
/**
* Load an automerge document from a compressed document produce by {@link save}
* // At this point we've generated two separate changes, now we want to send
* // just those changes to someone else
*
* @typeParam T - The type of the value which is contained in the document.
* Note that no validation is done to make sure this type is in
* fact the type of the contained value so be a bit careful
* @param data - The compressed document
* @param _opts - Either an actor ID or some {@link InitOptions}, if the actor
* ID is null a random actor ID will be created
* // view is a cheap reference based copy of a document at a given set of heads
* let before = automerge.view(doc, heads)
*
* Note that `load` will throw an error if passed incomplete content (for
* example if you are receiving content over the network and don't know if you
* have the complete document yet). If you need to handle incomplete content use
* {@link init} followed by {@link loadIncremental}.
*/
export function load(data, _opts) {
const opts = importOpts(_opts);
const actor = opts.actor;
const patchCallback = opts.patchCallback;
const handle = ApiHandler.load(data, actor);
handle.enablePatches(true);
handle.enableFreeze(!!opts.freeze);
handle.registerDatatype("counter", (n) => new Counter(n));
handle.registerDatatype("text", (n) => new Text(n));
const doc = handle.materialize("/", undefined, { handle, heads: undefined, patchCallback });
return doc;
}
/**
* Load changes produced by {@link saveIncremental}, or partial changes
* // This view doesn't show the last two changes in the document state
* assert.deepEqual(before, {
* key1: "value1",
* })
*
* @typeParam T - The type of the value which is contained in the document.
* Note that no validation is done to make sure this type is in
* fact the type of the contained value so be a bit careful
* @param data - The compressedchanges
* @param opts - an {@link ApplyOptions}
* // Get the changes to send to doc2
* let changes = automerge.getChanges(before, doc)
*
* This function is useful when staying up to date with a connected peer.
* Perhaps the other end sent you a full compresed document which you loaded
* with {@link load} and they're sending you the result of
* {@link getLastLocalChange} every time they make a change.
* // Apply the changes at doc2
* doc2 = automerge.applyChanges < any > (doc2, changes)[0]
* assert.deepEqual(doc2, {
* key1: "value1",
* key2: "value2",
* key3: "value3",
* })
* ```
*
* Note that this function will succesfully load the results of {@link save} as
* well as {@link getLastLocalChange} or any other incremental change.
*/
export function loadIncremental(doc, data, opts) {
if (!opts) {
opts = {};
}
const state = _state(doc);
if (state.heads) {
throw new RangeError("Attempting to change an out of date document - set at: " + _trace(doc));
}
if (_readonly(doc) === false) {
throw new RangeError("Calls to Automerge.change cannot be nested");
}
const heads = state.handle.getHeads();
state.handle.loadIncremental(data);
return progressDocument(doc, heads, opts.patchCallback || state.patchCallback);
}
/**
* Export the contents of a document to a compressed format
* If you have a {@link view} of a document which you want to make changes to you
* can {@link clone} the viewed document.
*
* @param doc - The doc to save
* ## Syncing
*
* The returned bytes can be passed to {@link load} or {@link loadIncremental}
*/
export function save(doc) {
return _state(doc).handle.save();
}
/**
* Merge `local` into `remote`
* @typeParam T - The type of values contained in each document
* @param local - The document to merge changes into
* @param remote - The document to merge changes from
* The sync protocol is stateful. This means that we start by creating a {@link
* SyncState} for each peer we are communicating with using {@link initSyncState}.
* Then we generate a message to send to the peer by calling {@link
* generateSyncMessage}. When we receive a message from the peer we call {@link
* receiveSyncMessage}. Here's a simple example of a loop which just keeps two
* peers in sync.
*
* @returns - The merged document
* ```ts
* let sync1 = automerge.initSyncState()
* let msg: Uint8Array | null
* ;[sync1, msg] = automerge.generateSyncMessage(doc1, sync1)
*
* Often when you are merging documents you will also need to clone them. Both
* arguments to `merge` are frozen after the call so you can no longer call
* mutating methods (such as {@link change}) on them. The symtom of this will be
* an error which says "Attempting to change an out of date document". To
* overcome this call {@link clone} on the argument before passing it to {@link
* merge}.
*/
export function merge(local, remote) {
const localState = _state(local);
if (localState.heads) {
throw new RangeError("Attempting to change an out of date document - set at: " + _trace(local));
}
const heads = localState.handle.getHeads();
const remoteState = _state(remote);
const changes = localState.handle.getChangesAdded(remoteState.handle);
localState.handle.applyChanges(changes);
return progressDocument(local, heads, localState.patchCallback);
}
/**
* Get the actor ID associated with the document
*/
export function getActorId(doc) {
const state = _state(doc);
return state.handle.getActorId();
}
function conflictAt(context, objectId, prop) {
const values = context.getAll(objectId, prop);
if (values.length <= 1) {
return;
}
const result = {};
for (const fullVal of values) {
switch (fullVal[0]) {
case "map":
result[fullVal[1]] = mapProxy(context, fullVal[1], [prop], true);
break;
case "list":
result[fullVal[1]] = listProxy(context, fullVal[1], [prop], true);
break;
case "text":
result[fullVal[1]] = textProxy(context, fullVal[1], [prop], true);
break;
//case "table":
//case "cursor":
case "str":
case "uint":
case "int":
case "f64":
case "boolean":
case "bytes":
case "null":
result[fullVal[2]] = fullVal[1];
break;
case "counter":
result[fullVal[2]] = new Counter(fullVal[1]);
break;
case "timestamp":
result[fullVal[2]] = new Date(fullVal[1]);
break;
default:
throw RangeError(`datatype ${fullVal[0]} unimplemented`);
}
}
return result;
}
/**
* Get the conflicts associated with a property
* while (true) {
* if (msg != null) {
* network.send(msg)
* }
* let resp: Uint8Array =
* (network.receive()[(doc1, sync1, _ignore)] =
* automerge.receiveSyncMessage(doc1, sync1, resp)[(sync1, msg)] =
* automerge.generateSyncMessage(doc1, sync1))
* }
* ```
*
* The values of properties in a map in automerge can be conflicted if there
* are concurrent "put" operations to the same key. Automerge chooses one value
* arbitrarily (but deterministically, any two nodes who have the same set of
* changes will choose the same value) from the set of conflicting values to
* present as the value of the key.
* ## Conflicts
*
* Sometimes you may want to examine these conflicts, in this case you can use
* {@link getConflicts} to get the conflicts for the key.
* The only time conflicts occur in automerge documents is in concurrent
* assignments to the same key in an object. In this case automerge
* deterministically chooses an arbitrary value to present to the application but
* you can examine the conflicts using {@link getConflicts}.
*
* @example
* ```

@@ -480,245 +191,53 @@ * import * as automerge from "@automerge/automerge"

* ```
*/
export function getConflicts(doc, prop) {
const state = _state(doc, false);
const objectId = _obj(doc);
if (objectId != null) {
return conflictAt(state.handle, objectId, prop);
}
else {
return undefined;
}
}
/**
* Get the binary representation of the last change which was made to this doc
*
* This is most useful when staying in sync with other peers, every time you
* make a change locally via {@link change} you immediately call {@link
* getLastLocalChange} and send the result over the network to other peers.
*/
export function getLastLocalChange(doc) {
const state = _state(doc);
return state.handle.getLastLocalChange() || undefined;
}
/**
* Return the object ID of an arbitrary javascript value
* ## Actor IDs
*
* This is useful to determine if something is actually an automerge document,
* if `doc` is not an automerge document this will return null.
*/
export function getObjectId(doc) {
return _obj(doc);
}
/**
* Get the changes which are in `newState` but not in `oldState`. The returned
* changes can be loaded in `oldState` via {@link applyChanges}.
* By default automerge will generate a random actor ID for you, but most methods
* for creating a document allow you to set the actor ID. You can get the actor ID
* associated with the document by calling {@link getActorId}. Actor IDs must not
* be used in concurrent threads of executiong - all changes by a given actor ID
* are expected to be sequential.
*
* Note that this will crash if there are changes in `oldState` which are not in `newState`.
*/
export function getChanges(oldState, newState) {
const o = _state(oldState);
const n = _state(newState);
return n.handle.getChanges(getHeads(oldState));
}
/**
* Get all the changes in a document
* ## Listening to patches
*
* This is different to {@link save} because the output is an array of changes
* which can be individually applied via {@link applyChanges}`
* Sometimes you want to respond to changes made to an automerge document. In this
* case you can use the {@link PatchCallback} type to receive notifications when
* changes have been made.
*
*/
export function getAllChanges(doc) {
const state = _state(doc);
return state.handle.getChanges([]);
}
/**
* Apply changes received from another document
* ## Cloning
*
* `doc` will be updated to reflect the `changes`. If there are changes which
* we do not have dependencies for yet those will be stored in the document and
* applied when the depended on changes arrive.
* Currently you cannot make mutating changes (i.e. call {@link change}) to a
* document which you have two pointers to. For example, in this code:
*
* You can use the {@link ApplyOptions} to pass a patchcallback which will be
* informed of any changes which occur as a result of applying the changes
* ```javascript
* let doc1 = automerge.init()
* let doc2 = automerge.change(doc1, d => (d.key = "value"))
* ```
*
*/
export function applyChanges(doc, changes, opts) {
const state = _state(doc);
if (!opts) {
opts = {};
}
if (state.heads) {
throw new RangeError("Attempting to change an outdated document. Use Automerge.clone() if you wish to make a writable copy.");
}
if (_readonly(doc) === false) {
throw new RangeError("Calls to Automerge.change cannot be nested");
}
const heads = state.handle.getHeads();
state.handle.applyChanges(changes);
state.heads = heads;
return [progressDocument(doc, heads, opts.patchCallback || state.patchCallback)];
}
/** @hidden */
export function getHistory(doc) {
const history = getAllChanges(doc);
return history.map((change, index) => ({
get change() {
return decodeChange(change);
},
get snapshot() {
const [state] = applyChanges(init(), history.slice(0, index + 1));
return state;
}
}));
}
/** @hidden */
// FIXME : no tests
// FIXME can we just use deep equals now?
export function equals(val1, val2) {
if (!isObject(val1) || !isObject(val2))
return val1 === val2;
const keys1 = Object.keys(val1).sort(), keys2 = Object.keys(val2).sort();
if (keys1.length !== keys2.length)
return false;
for (let i = 0; i < keys1.length; i++) {
if (keys1[i] !== keys2[i])
return false;
if (!equals(val1[keys1[i]], val2[keys2[i]]))
return false;
}
return true;
}
/**
* encode a {@link SyncState} into binary to send over the network
* `doc1` and `doc2` are both pointers to the same state. Any attempt to call
* mutating methods on `doc1` will now result in an error like
*
* @group sync
* */
export function encodeSyncState(state) {
const sync = ApiHandler.importSyncState(state);
const result = ApiHandler.encodeSyncState(sync);
sync.free();
return result;
}
/**
* Decode some binary data into a {@link SyncState}
* Attempting to change an out of date document
*
* @group sync
*/
export function decodeSyncState(state) {
let sync = ApiHandler.decodeSyncState(state);
let result = ApiHandler.exportSyncState(sync);
sync.free();
return result;
}
/**
* Generate a sync message to send to the peer represented by `inState`
* @param doc - The doc to generate messages about
* @param inState - The {@link SyncState} representing the peer we are talking to
* If you encounter this you need to clone the original document, the above sample
* would work as:
*
* @group sync
* ```javascript
* let doc1 = automerge.init()
* let doc2 = automerge.change(automerge.clone(doc1), d => (d.key = "value"))
* ```
* @packageDocumentation
*
* @returns An array of `[newSyncState, syncMessage | null]` where
* `newSyncState` should replace `inState` and `syncMessage` should be sent to
* the peer if it is not null. If `syncMessage` is null then we are up to date.
*/
export function generateSyncMessage(doc, inState) {
const state = _state(doc);
const syncState = ApiHandler.importSyncState(inState);
const message = state.handle.generateSyncMessage(syncState);
const outState = ApiHandler.exportSyncState(syncState);
return [outState, message];
}
/**
* Update a document and our sync state on receiving a sync message
* ## The {@link unstable} module
*
* @group sync
*
* @param doc - The doc the sync message is about
* @param inState - The {@link SyncState} for the peer we are communicating with
* @param message - The message which was received
* @param opts - Any {@link ApplyOption}s, used for passing a
* {@link PatchCallback} which will be informed of any changes
* in `doc` which occur because of the received sync message.
*
* @returns An array of `[newDoc, newSyncState, syncMessage | null]` where
* `newDoc` is the updated state of `doc`, `newSyncState` should replace
* `inState` and `syncMessage` should be sent to the peer if it is not null. If
* `syncMessage` is null then we are up to date.
* We are working on some changes to automerge which are not yet complete and
* will result in backwards incompatible API changes. Once these changes are
* ready for production use we will release a new major version of automerge.
* However, until that point you can use the {@link unstable} module to try out
* the new features, documents from the {@link unstable} module are
* interoperable with documents from the main module. Please see the docs for
* the {@link unstable} module for more details.
*/
export function receiveSyncMessage(doc, inState, message, opts) {
const syncState = ApiHandler.importSyncState(inState);
if (!opts) {
opts = {};
}
const state = _state(doc);
if (state.heads) {
throw new RangeError("Attempting to change an outdated document. Use Automerge.clone() if you wish to make a writable copy.");
}
if (_readonly(doc) === false) {
throw new RangeError("Calls to Automerge.change cannot be nested");
}
const heads = state.handle.getHeads();
state.handle.receiveSyncMessage(syncState, message);
const outSyncState = ApiHandler.exportSyncState(syncState);
return [progressDocument(doc, heads, opts.patchCallback || state.patchCallback), outSyncState, null];
}
/**
* Create a new, blank {@link SyncState}
*
* When communicating with a peer for the first time use this to generate a new
* {@link SyncState} for them
*
* @group sync
*/
export function initSyncState() {
return ApiHandler.exportSyncState(ApiHandler.initSyncState());
}
/** @hidden */
export function encodeChange(change) {
return ApiHandler.encodeChange(change);
}
/** @hidden */
export function decodeChange(data) {
return ApiHandler.decodeChange(data);
}
/** @hidden */
export function encodeSyncMessage(message) {
return ApiHandler.encodeSyncMessage(message);
}
/** @hidden */
export function decodeSyncMessage(message) {
return ApiHandler.decodeSyncMessage(message);
}
/**
* Get any changes in `doc` which are not dependencies of `heads`
*/
export function getMissingDeps(doc, heads) {
const state = _state(doc);
return state.handle.getMissingDeps(heads);
}
/**
* Get the hashes of the heads of this document
*/
export function getHeads(doc) {
const state = _state(doc);
return state.heads || state.handle.getHeads();
}
/** @hidden */
export function dump(doc) {
const state = _state(doc);
state.handle.dump();
}
/** @hidden */
export function toJS(doc) {
const state = _state(doc);
const enabled = state.handle.enableFreeze(false);
const result = state.handle.materialize();
state.handle.enableFreeze(enabled);
return result;
}
export function isAutomerge(doc) {
return getObjectId(doc) === "_root" && !!Reflect.get(doc, STATE);
}
function isObject(obj) {
return typeof obj === 'object' && obj !== null;
}
export * from "./stable";
import * as unstable from "./unstable";
export { unstable };

@@ -8,14 +8,36 @@ export function UseApi(api) {

export const ApiHandler = {
create(actor) { throw new RangeError("Automerge.use() not called"); },
load(data, actor) { throw new RangeError("Automerge.use() not called (load)"); },
encodeChange(change) { throw new RangeError("Automerge.use() not called (encodeChange)"); },
decodeChange(change) { throw new RangeError("Automerge.use() not called (decodeChange)"); },
initSyncState() { throw new RangeError("Automerge.use() not called (initSyncState)"); },
encodeSyncMessage(message) { throw new RangeError("Automerge.use() not called (encodeSyncMessage)"); },
decodeSyncMessage(msg) { throw new RangeError("Automerge.use() not called (decodeSyncMessage)"); },
encodeSyncState(state) { throw new RangeError("Automerge.use() not called (encodeSyncState)"); },
decodeSyncState(data) { throw new RangeError("Automerge.use() not called (decodeSyncState)"); },
exportSyncState(state) { throw new RangeError("Automerge.use() not called (exportSyncState)"); },
importSyncState(state) { throw new RangeError("Automerge.use() not called (importSyncState)"); },
create(textV2, actor) {
throw new RangeError("Automerge.use() not called");
},
load(data, textV2, actor) {
throw new RangeError("Automerge.use() not called (load)");
},
encodeChange(change) {
throw new RangeError("Automerge.use() not called (encodeChange)");
},
decodeChange(change) {
throw new RangeError("Automerge.use() not called (decodeChange)");
},
initSyncState() {
throw new RangeError("Automerge.use() not called (initSyncState)");
},
encodeSyncMessage(message) {
throw new RangeError("Automerge.use() not called (encodeSyncMessage)");
},
decodeSyncMessage(msg) {
throw new RangeError("Automerge.use() not called (decodeSyncMessage)");
},
encodeSyncState(state) {
throw new RangeError("Automerge.use() not called (encodeSyncState)");
},
decodeSyncState(data) {
throw new RangeError("Automerge.use() not called (decodeSyncState)");
},
exportSyncState(state) {
throw new RangeError("Automerge.use() not called (exportSyncState)");
},
importSyncState(state) {
throw new RangeError("Automerge.use() not called (importSyncState)");
},
};
/* eslint-enable */

@@ -5,3 +5,5 @@ // Convience classes to allow users to stricly specify the number type they want

constructor(value) {
if (!(Number.isInteger(value) && value <= Number.MAX_SAFE_INTEGER && value >= Number.MIN_SAFE_INTEGER)) {
if (!(Number.isInteger(value) &&
value <= Number.MAX_SAFE_INTEGER &&
value >= Number.MIN_SAFE_INTEGER)) {
throw new RangeError(`Value ${value} cannot be a uint`);

@@ -16,3 +18,5 @@ }

constructor(value) {
if (!(Number.isInteger(value) && value <= Number.MAX_SAFE_INTEGER && value >= 0)) {
if (!(Number.isInteger(value) &&
value <= Number.MAX_SAFE_INTEGER &&
value >= 0)) {
throw new RangeError(`Value ${value} cannot be a uint`);

@@ -27,3 +31,3 @@ }

constructor(value) {
if (typeof value !== 'number') {
if (typeof value !== "number") {
throw new RangeError(`Value ${value} cannot be a float64`);

@@ -30,0 +34,0 @@ }

@@ -0,13 +1,13 @@

import { Text } from "./text";
import { Counter, getWriteableCounter } from "./counter";
import { Text } from "./text";
import { STATE, HEADS, TRACE, FROZEN, OBJECT_ID, READ_ONLY, COUNTER, INT, UINT, F64, TEXT } from "./constants";
import { STATE, TRACE, IS_PROXY, OBJECT_ID, COUNTER, INT, UINT, F64, } from "./constants";
import { RawString } from "./raw_string";
function parseListIndex(key) {
if (typeof key === 'string' && /^[0-9]+$/.test(key))
if (typeof key === "string" && /^[0-9]+$/.test(key))
key = parseInt(key, 10);
if (typeof key !== 'number') {
// throw new TypeError('A list index must be a number, but you passed ' + JSON.stringify(key))
if (typeof key !== "number") {
return key;
}
if (key < 0 || isNaN(key) || key === Infinity || key === -Infinity) {
throw new RangeError('A list index must be positive, but you passed ' + key);
throw new RangeError("A list index must be positive, but you passed " + key);
}

@@ -17,3 +17,3 @@ return key;

function valueAt(target, prop) {
const { context, objectId, path, readonly, heads } = target;
const { context, objectId, path, readonly, heads, textV2 } = target;
const value = context.getWithType(objectId, prop, heads);

@@ -26,16 +26,31 @@ if (value === null) {

switch (datatype) {
case undefined: return;
case "map": return mapProxy(context, val, [...path, prop], readonly, heads);
case "list": return listProxy(context, val, [...path, prop], readonly, heads);
case "text": return textProxy(context, val, [...path, prop], readonly, heads);
//case "table":
//case "cursor":
case "str": return val;
case "uint": return val;
case "int": return val;
case "f64": return val;
case "boolean": return val;
case "null": return null;
case "bytes": return val;
case "timestamp": return val;
case undefined:
return;
case "map":
return mapProxy(context, val, textV2, [...path, prop], readonly, heads);
case "list":
return listProxy(context, val, textV2, [...path, prop], readonly, heads);
case "text":
if (textV2) {
return context.text(val, heads);
}
else {
return textProxy(context, val, [...path, prop], readonly, heads);
}
case "str":
return val;
case "uint":
return val;
case "int":
return val;
case "f64":
return val;
case "boolean":
return val;
case "null":
return null;
case "bytes":
return val;
case "timestamp":
return val;
case "counter": {

@@ -53,5 +68,5 @@ if (readonly) {

}
function import_value(value) {
function import_value(value, textV2) {
switch (typeof value) {
case 'object':
case "object":
if (value == null) {

@@ -72,8 +87,11 @@ return [null, "null"];

}
else if (value[TEXT]) {
return [value, "text"];
}
else if (value instanceof Date) {
return [value.getTime(), "timestamp"];
}
else if (value instanceof RawString) {
return [value.val, "str"];
}
else if (value instanceof Text) {
return [value, "text"];
}
else if (value instanceof Uint8Array) {

@@ -89,3 +107,3 @@ return [value, "bytes"];

else if (value[OBJECT_ID]) {
throw new RangeError('Cannot create a reference to an existing document object');
throw new RangeError("Cannot create a reference to an existing document object");
}

@@ -95,6 +113,5 @@ else {

}
break;
case 'boolean':
case "boolean":
return [value, "boolean"];
case 'number':
case "number":
if (Number.isInteger(value)) {

@@ -106,6 +123,9 @@ return [value, "int"];

}
break;
case 'string':
return [value];
break;
case "string":
if (textV2) {
return [value, "text"];
}
else {
return [value, "str"];
}
default:

@@ -117,3 +137,3 @@ throw new RangeError(`Unsupported type of value: ${typeof value}`);

get(target, key) {
const { context, objectId, readonly, frozen, heads, cache } = target;
const { context, objectId, cache } = target;
if (key === Symbol.toStringTag) {

@@ -124,12 +144,8 @@ return target[Symbol.toStringTag];

return objectId;
if (key === READ_ONLY)
return readonly;
if (key === FROZEN)
return frozen;
if (key === HEADS)
return heads;
if (key === IS_PROXY)
return true;
if (key === TRACE)
return target.trace;
if (key === STATE)
return context;
return { handle: context };
if (!cache[key]) {

@@ -141,15 +157,7 @@ cache[key] = valueAt(target, key);

set(target, key, val) {
const { context, objectId, path, readonly, frozen } = target;
const { context, objectId, path, readonly, frozen, textV2 } = target;
target.cache = {}; // reset cache on set
if (val && val[OBJECT_ID]) {
throw new RangeError('Cannot create a reference to an existing document object');
throw new RangeError("Cannot create a reference to an existing document object");
}
if (key === FROZEN) {
target.frozen = val;
return true;
}
if (key === HEADS) {
target.heads = val;
return true;
}
if (key === TRACE) {

@@ -159,3 +167,3 @@ target.trace = val;

}
const [value, datatype] = import_value(val);
const [value, datatype] = import_value(val, textV2);
if (frozen) {

@@ -170,3 +178,3 @@ throw new RangeError("Attempting to use an outdated Automerge document");

const list = context.putObject(objectId, key, []);
const proxyList = listProxy(context, list, [...path, key], readonly);
const proxyList = listProxy(context, list, textV2, [...path, key], readonly);
for (let i = 0; i < value.length; i++) {

@@ -178,7 +186,12 @@ proxyList[i] = value[i];

case "text": {
const text = context.putObject(objectId, key, "", "text");
const proxyText = textProxy(context, text, [...path, key], readonly);
for (let i = 0; i < value.length; i++) {
proxyText[i] = value.get(i);
if (textV2) {
context.putObject(objectId, key, value);
}
else {
const text = context.putObject(objectId, key, "");
const proxyText = textProxy(context, text, [...path, key], readonly);
for (let i = 0; i < value.length; i++) {
proxyText[i] = value.get(i);
}
}
break;

@@ -188,3 +201,3 @@ }

const map = context.putObject(objectId, key, {});
const proxyMap = mapProxy(context, map, [...path, key], readonly);
const proxyMap = mapProxy(context, map, textV2, [...path, key], readonly);
for (const key in value) {

@@ -216,5 +229,7 @@ proxyMap[key] = value[key];

const value = this.get(target, key);
if (typeof value !== 'undefined') {
if (typeof value !== "undefined") {
return {
configurable: true, enumerable: true, value
configurable: true,
enumerable: true,
value,
};

@@ -232,6 +247,8 @@ }

get(target, index) {
const { context, objectId, readonly, frozen, heads } = target;
const { context, objectId, heads } = target;
index = parseListIndex(index);
if (index === Symbol.hasInstance) {
return (instance) => { return Array.isArray(instance); };
return instance => {
return Array.isArray(instance);
};
}

@@ -243,15 +260,11 @@ if (index === Symbol.toStringTag) {

return objectId;
if (index === READ_ONLY)
return readonly;
if (index === FROZEN)
return frozen;
if (index === HEADS)
return heads;
if (index === IS_PROXY)
return true;
if (index === TRACE)
return target.trace;
if (index === STATE)
return context;
if (index === 'length')
return { handle: context };
if (index === "length")
return context.length(objectId, heads);
if (typeof index === 'number') {
if (typeof index === "number") {
return valueAt(target, index);

@@ -264,15 +277,7 @@ }

set(target, index, val) {
const { context, objectId, path, readonly, frozen } = target;
const { context, objectId, path, readonly, frozen, textV2 } = target;
index = parseListIndex(index);
if (val && val[OBJECT_ID]) {
throw new RangeError('Cannot create a reference to an existing document object');
throw new RangeError("Cannot create a reference to an existing document object");
}
if (index === FROZEN) {
target.frozen = val;
return true;
}
if (index === HEADS) {
target.heads = val;
return true;
}
if (index === TRACE) {

@@ -283,5 +288,5 @@ target.trace = val;

if (typeof index == "string") {
throw new RangeError('list index must be a number');
throw new RangeError("list index must be a number");
}
const [value, datatype] = import_value(val);
const [value, datatype] = import_value(val, textV2);
if (frozen) {

@@ -302,3 +307,3 @@ throw new RangeError("Attempting to use an outdated Automerge document");

}
const proxyList = listProxy(context, list, [...path, index], readonly);
const proxyList = listProxy(context, list, textV2, [...path, index], readonly);
proxyList.splice(0, 0, ...value);

@@ -308,11 +313,21 @@ break;

case "text": {
let text;
if (index >= context.length(objectId)) {
text = context.insertObject(objectId, index, "", "text");
if (textV2) {
if (index >= context.length(objectId)) {
context.insertObject(objectId, index, value);
}
else {
context.putObject(objectId, index, value);
}
}
else {
text = context.putObject(objectId, index, "", "text");
let text;
if (index >= context.length(objectId)) {
text = context.insertObject(objectId, index, "");
}
else {
text = context.putObject(objectId, index, "");
}
const proxyText = textProxy(context, text, [...path, index], readonly);
proxyText.splice(0, 0, ...value);
}
const proxyText = textProxy(context, text, [...path, index], readonly);
proxyText.splice(0, 0, ...value);
break;

@@ -328,3 +343,3 @@ }

}
const proxyMap = mapProxy(context, map, [...path, index], readonly);
const proxyMap = mapProxy(context, map, textV2, [...path, index], readonly);
for (const key in value) {

@@ -348,4 +363,5 @@ proxyMap[key] = value[key];

index = parseListIndex(index);
if (context.get(objectId, index)[0] == "counter") {
throw new TypeError('Unsupported operation: deleting a counter from a list');
const elem = context.get(objectId, index);
if (elem != null && elem[0] == "counter") {
throw new TypeError("Unsupported operation: deleting a counter from a list");
}

@@ -358,10 +374,10 @@ context.delete(objectId, index);

index = parseListIndex(index);
if (typeof index === 'number') {
if (typeof index === "number") {
return index < context.length(objectId, heads);
}
return index === 'length';
return index === "length";
},
getOwnPropertyDescriptor(target, index) {
const { context, objectId, heads } = target;
if (index === 'length')
if (index === "length")
return { writable: true, value: context.length(objectId, heads) };

@@ -374,3 +390,5 @@ if (index === OBJECT_ID)

},
getPrototypeOf(target) { return Object.getPrototypeOf(target); },
getPrototypeOf(target) {
return Object.getPrototypeOf(target);
},
ownKeys( /*target*/) {

@@ -384,30 +402,27 @@ const keys = [];

return keys;
}
},
};
const TextHandler = Object.assign({}, ListHandler, {
get(target, index) {
// FIXME this is a one line change from ListHandler.get()
const { context, objectId, readonly, frozen, heads } = target;
const { context, objectId, heads } = target;
index = parseListIndex(index);
if (index === Symbol.hasInstance) {
return (instance) => {
return Array.isArray(instance);
};
}
if (index === Symbol.toStringTag) {
return target[Symbol.toStringTag];
}
if (index === Symbol.hasInstance) {
return (instance) => { return Array.isArray(instance); };
}
if (index === OBJECT_ID)
return objectId;
if (index === READ_ONLY)
return readonly;
if (index === FROZEN)
return frozen;
if (index === HEADS)
return heads;
if (index === IS_PROXY)
return true;
if (index === TRACE)
return target.trace;
if (index === STATE)
return context;
if (index === 'length')
return { handle: context };
if (index === "length")
return context.length(objectId, heads);
if (typeof index === 'number') {
if (typeof index === "number") {
return valueAt(target, index);

@@ -423,24 +438,57 @@ }

});
export function mapProxy(context, objectId, path, readonly, heads) {
return new Proxy({ context, objectId, path, readonly: !!readonly, frozen: false, heads, cache: {} }, MapHandler);
export function mapProxy(context, objectId, textV2, path, readonly, heads) {
const target = {
context,
objectId,
path: path || [],
readonly: !!readonly,
frozen: false,
heads,
cache: {},
textV2,
};
const proxied = {};
Object.assign(proxied, target);
let result = new Proxy(proxied, MapHandler);
// conversion through unknown is necessary because the types are so different
return result;
}
export function listProxy(context, objectId, path, readonly, heads) {
const target = [];
Object.assign(target, { context, objectId, path, readonly: !!readonly, frozen: false, heads, cache: {} });
return new Proxy(target, ListHandler);
export function listProxy(context, objectId, textV2, path, readonly, heads) {
const target = {
context,
objectId,
path: path || [],
readonly: !!readonly,
frozen: false,
heads,
cache: {},
textV2,
};
const proxied = [];
Object.assign(proxied, target);
// @ts-ignore
return new Proxy(proxied, ListHandler);
}
export function textProxy(context, objectId, path, readonly, heads) {
const target = [];
Object.assign(target, { context, objectId, path, readonly: !!readonly, frozen: false, heads, cache: {} });
const target = {
context,
objectId,
path: path || [],
readonly: !!readonly,
frozen: false,
heads,
cache: {},
textV2: false,
};
return new Proxy(target, TextHandler);
}
export function rootProxy(context, readonly) {
export function rootProxy(context, textV2, readonly) {
/* eslint-disable-next-line */
return mapProxy(context, "_root", [], !!readonly);
return mapProxy(context, "_root", textV2, [], !!readonly);
}
function listMethods(target) {
const { context, objectId, path, readonly, frozen, heads } = target;
const { context, objectId, path, readonly, frozen, heads, textV2 } = target;
const methods = {
deleteAt(index, numDelete) {
if (typeof numDelete === 'number') {
if (typeof numDelete === "number") {
context.splice(objectId, index, numDelete);

@@ -454,3 +502,3 @@ }

fill(val, start, end) {
const [value, datatype] = import_value(val);
const [value, datatype] = import_value(val, textV2);
const length = context.length(objectId);

@@ -460,3 +508,8 @@ start = parseListIndex(start || 0);

for (let i = start; i < Math.min(end, length); i++) {
context.put(objectId, i, value, datatype);
if (datatype === "text" || datatype === "list" || datatype === "map") {
context.putObject(objectId, i, value);
}
else {
context.put(objectId, i, value, datatype);
}
}

@@ -469,3 +522,3 @@ return this;

const value = context.getWithType(objectId, i, heads);
if (value && value[1] === o[OBJECT_ID] || value[1] === o) {
if (value && (value[1] === o[OBJECT_ID] || value[1] === o)) {
return i;

@@ -506,3 +559,3 @@ }

if (val && val[OBJECT_ID]) {
throw new RangeError('Cannot create a reference to an existing document object');
throw new RangeError("Cannot create a reference to an existing document object");
}

@@ -524,3 +577,3 @@ }

}
const values = vals.map((val) => import_value(val));
const values = vals.map(val => import_value(val, textV2));
for (const [value, datatype] of values) {

@@ -530,3 +583,3 @@ switch (datatype) {

const list = context.insertObject(objectId, index, []);
const proxyList = listProxy(context, list, [...path, index], readonly);
const proxyList = listProxy(context, list, textV2, [...path, index], readonly);
proxyList.splice(0, 0, ...value);

@@ -536,5 +589,10 @@ break;

case "text": {
const text = context.insertObject(objectId, index, "", "text");
const proxyText = textProxy(context, text, [...path, index], readonly);
proxyText.splice(0, 0, ...value);
if (textV2) {
context.insertObject(objectId, index, value);
}
else {
const text = context.insertObject(objectId, index, "");
const proxyText = textProxy(context, text, [...path, index], readonly);
proxyText.splice(0, 0, ...value);
}
break;

@@ -544,3 +602,3 @@ }

const map = context.insertObject(objectId, index, {});
const proxyMap = mapProxy(context, map, [...path, index], readonly);
const proxyMap = mapProxy(context, map, textV2, [...path, index], readonly);
for (const key in value) {

@@ -573,3 +631,3 @@ proxyMap[key] = value[key];

}
}
},
};

@@ -589,3 +647,3 @@ return iterator;

return { value, done: true };
}
},
};

@@ -605,3 +663,3 @@ return iterator;

}
}
},
};

@@ -645,3 +703,3 @@ return iterator;

let index = 0;
for (let v of this) {
for (const v of this) {
if (f(v, index)) {

@@ -655,3 +713,3 @@ return v;

let index = 0;
for (let v of this) {
for (const v of this) {
if (f(v, index)) {

@@ -665,3 +723,3 @@ return index;

includes(elem) {
return this.find((e) => e === elem) !== undefined;
return this.find(e => e === elem) !== undefined;
},

@@ -688,3 +746,3 @@ join(sep) {

let index = 0;
for (let v of this) {
for (const v of this) {
if (f(v, index)) {

@@ -705,3 +763,3 @@ return true;

}
}
},
};

@@ -714,3 +772,3 @@ return methods;

set(index, value) {
return this[index] = value;
return (this[index] = value);
},

@@ -721,11 +779,11 @@ get(index) {

toString() {
return context.text(objectId, heads).replace(//g, '');
return context.text(objectId, heads).replace(//g, "");
},
toSpans() {
const spans = [];
let chars = '';
let chars = "";
const length = context.length(objectId);
for (let i = 0; i < length; i++) {
const value = this[i];
if (typeof value === 'string') {
if (typeof value === "string") {
chars += value;

@@ -736,3 +794,3 @@ }

spans.push(chars);
chars = '';
chars = "";
}

@@ -753,5 +811,5 @@ spans.push(value);

return text.indexOf(o, start);
}
},
};
return methods;
}
import { TEXT, STATE } from "./constants";
export class Text {
constructor(text) {
if (typeof text === 'string') {
if (typeof text === "string") {
this.elems = [...text];

@@ -40,3 +40,3 @@ }

}
}
},
};

@@ -53,8 +53,8 @@ }

// https://jsperf.com/join-vs-loop-w-type-test
this.str = '';
this.str = "";
for (const elem of this.elems) {
if (typeof elem === 'string')
if (typeof elem === "string")
this.str += elem;
else
this.str += '\uFFFC';
this.str += "\uFFFC";
}

@@ -68,4 +68,4 @@ }

*
* For example, the value ['a', 'b', {x: 3}, 'c', 'd'] has spans:
* => ['ab', {x: 3}, 'cd']
* For example, the value `['a', 'b', {x: 3}, 'c', 'd']` has spans:
* `=> ['ab', {x: 3}, 'cd']`
*/

@@ -75,5 +75,5 @@ toSpans() {

this.spans = [];
let chars = '';
let chars = "";
for (const elem of this.elems) {
if (typeof elem === 'string') {
if (typeof elem === "string") {
chars += elem;

@@ -84,3 +84,3 @@ }

this.spans.push(chars);
chars = '';
chars = "";
}

@@ -87,0 +87,0 @@ this.spans.push(elem);

@@ -1,4 +0,4 @@

import { v4 } from 'uuid';
import { v4 } from "uuid";
function defaultFactory() {
return v4().replace(/-/g, '');
return v4().replace(/-/g, "");
}

@@ -9,3 +9,7 @@ let factory = defaultFactory;

};
uuid.setFactory = newFactory => { factory = newFactory; };
uuid.reset = () => { factory = defaultFactory; };
uuid.setFactory = newFactory => {
factory = newFactory;
};
uuid.reset = () => {
factory = defaultFactory;
};
import { Automerge, Heads, ObjID } from "@automerge/automerge-wasm";
import { Prop } from "@automerge/automerge-wasm";
import { MapValue, ListValue, TextValue } from "./types";
export declare function mapProxy(context: Automerge, objectId: ObjID, path?: Prop[], readonly?: boolean, heads?: Heads): MapValue;
export declare function listProxy(context: Automerge, objectId: ObjID, path?: Prop[], readonly?: boolean, heads?: Heads): ListValue;
export declare function mapProxy(context: Automerge, objectId: ObjID, textV2: boolean, path?: Prop[], readonly?: boolean, heads?: Heads): MapValue;
export declare function listProxy(context: Automerge, objectId: ObjID, textV2: boolean, path?: Prop[], readonly?: boolean, heads?: Heads): ListValue;
export declare function textProxy(context: Automerge, objectId: ObjID, path?: Prop[], readonly?: boolean, heads?: Heads): TextValue;
export declare function rootProxy<T>(context: Automerge, readonly?: boolean): T;
export declare function rootProxy<T>(context: Automerge, textV2: boolean, readonly?: boolean): T;
import { Value } from "@automerge/automerge-wasm";
export declare class Text {
elems: Value[];
elems: Array<any>;
str: string | undefined;
spans: Value[] | undefined;
spans: Array<any> | undefined;
constructor(text?: string | string[] | Value[]);
get length(): number;
get(index: number): Value | undefined;
get(index: number): any;
/**

@@ -16,3 +16,3 @@ * Iterates over the text elements character by character, including any

done: boolean;
value: Value;
value: any;
} | {

@@ -32,6 +32,6 @@ done: boolean;

*
* For example, the value ['a', 'b', {x: 3}, 'c', 'd'] has spans:
* => ['ab', {x: 3}, 'cd']
* For example, the value `['a', 'b', {x: 3}, 'c', 'd']` has spans:
* `=> ['ab', {x: 3}, 'cd']`
*/
toSpans(): Value[];
toSpans(): Array<Value | Object>;
/**

@@ -49,3 +49,3 @@ * Returns the content of the Text object as a simple string, so that the

*/
insertAt(index: number, ...values: Value[]): void;
insertAt(index: number, ...values: Array<Value | Object>): void;
/**

@@ -56,10 +56,10 @@ * Deletes `numDelete` list items starting at position `index`.

deleteAt(index: number, numDelete?: number): void;
map<T>(callback: (e: Value) => T): void;
map<T>(callback: (e: Value | Object) => T): void;
lastIndexOf(searchElement: Value, fromIndex?: number): void;
concat(other: Text): Text;
every(test: (Value: any) => boolean): boolean;
filter(test: (Value: any) => boolean): Text;
find(test: (Value: any) => boolean): Value | undefined;
findIndex(test: (Value: any) => boolean): number | undefined;
forEach(f: (Value: any) => undefined): void;
every(test: (v: Value) => boolean): boolean;
filter(test: (v: Value) => boolean): Text;
find(test: (v: Value) => boolean): Value | undefined;
findIndex(test: (v: Value) => boolean): number | undefined;
forEach(f: (v: Value) => undefined): void;
includes(elem: Value): boolean;

@@ -66,0 +66,0 @@ indexOf(elem: Value): number;

@@ -1,2 +0,1 @@

import { Text } from "./text";
export { Text } from "./text";

@@ -6,5 +5,7 @@ export { Counter } from "./counter";

import { Counter } from "./counter";
import type { Patch } from "@automerge/automerge-wasm";
export type { Patch } from "@automerge/automerge-wasm";
export type AutomergeValue = ScalarValue | {
[key: string]: AutomergeValue;
} | Array<AutomergeValue> | Text;
} | Array<AutomergeValue>;
export type MapValue = {

@@ -16,1 +17,19 @@ [key: string]: AutomergeValue;

export type ScalarValue = string | number | null | boolean | Date | Counter | Uint8Array;
/**
* An automerge document.
* @typeParam T - The type of the value contained in this document
*
* Note that this provides read only access to the fields of the value. To
* modify the value use {@link change}
*/
export type Doc<T> = {
readonly [P in keyof T]: T[P];
};
/**
* Callback which is called by various methods in this library to notify the
* user of what changes have been made.
* @param patch - A description of the changes made
* @param before - The document before the change was made
* @param after - The document after the change was made
*/
export type PatchCallback<T> = (patches: Array<Patch>, before: Doc<T>, after: Doc<T>) => void;

@@ -7,3 +7,3 @@ {

],
"version": "2.0.1-alpha.2",
"version": "2.0.1-alpha.3",
"description": "Javascript implementation of automerge, backed by @automerge/automerge-wasm",

@@ -16,22 +16,6 @@ "homepage": "https://github.com/automerge/automerge-rs/tree/main/wrappers/javascript",

"package.json",
"index.d.ts",
"dist/*.d.ts",
"dist/cjs/constants.js",
"dist/cjs/types.js",
"dist/cjs/numbers.js",
"dist/cjs/index.js",
"dist/cjs/uuid.js",
"dist/cjs/counter.js",
"dist/cjs/low_level.js",
"dist/cjs/text.js",
"dist/cjs/proxies.js",
"dist/mjs/constants.js",
"dist/mjs/types.js",
"dist/mjs/numbers.js",
"dist/mjs/index.js",
"dist/mjs/uuid.js",
"dist/mjs/counter.js",
"dist/mjs/low_level.js",
"dist/mjs/text.js",
"dist/mjs/proxies.js"
"dist/index.d.ts",
"dist/cjs/**/*.js",
"dist/mjs/**/*.js",
"dist/*.d.ts"
],

@@ -44,25 +28,26 @@ "types": "./dist/index.d.ts",

"lint": "eslint src",
"build": "tsc -p config/mjs.json && tsc -p config/cjs.json && tsc --emitDeclarationOnly",
"build": "tsc -p config/mjs.json && tsc -p config/cjs.json && tsc -p config/declonly.json --emitDeclarationOnly",
"test": "ts-mocha test/*.ts",
"watch-docs": "typedoc src/index.ts --watch --readme typedoc-readme.md"
"watch-docs": "typedoc src/index.ts --watch --readme none"
},
"devDependencies": {
"@types/expect": "^24.3.0",
"@types/mocha": "^9.1.1",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^5.25.0",
"@typescript-eslint/parser": "^5.25.0",
"eslint": "^8.15.0",
"@types/mocha": "^10.0.1",
"@types/uuid": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^5.46.0",
"@typescript-eslint/parser": "^5.46.0",
"eslint": "^8.29.0",
"fast-sha256": "^1.3.0",
"mocha": "^10.0.0",
"pako": "^2.0.4",
"mocha": "^10.2.0",
"pako": "^2.1.0",
"prettier": "^2.8.1",
"ts-mocha": "^10.0.0",
"ts-node": "^10.9.1",
"typedoc": "^0.23.16",
"typescript": "^4.6.4"
"typedoc": "^0.23.22",
"typescript": "^4.9.4"
},
"dependencies": {
"@automerge/automerge-wasm": "0.1.19",
"uuid": "^8.3"
"@automerge/automerge-wasm": "0.1.21",
"uuid": "^9.0.0"
}
}

@@ -22,3 +22,2 @@ ## Automerge

`@automerge/automerge` is a wrapper around a core library which is written in

@@ -58,9 +57,9 @@ rust, compiled to WebAssembly and distributed as a separate package called

let doc1 = automerge.from({
tasks: [
{description: "feed fish", done: false},
{description: "water plants", done: false},
]
tasks: [
{ description: "feed fish", done: false },
{ description: "water plants", done: false },
],
})
// Create a new thread of execution
// Create a new thread of execution
let doc2 = automerge.clone(doc1)

@@ -72,3 +71,3 @@

doc2 = automerge.change(doc2, d => {
d.tasks[0].done = true
d.tasks[0].done = true
})

@@ -78,6 +77,6 @@

doc1 = automerge.change(doc1, d => {
d.tasks.push({
description: "water fish",
done: false
})
d.tasks.push({
description: "water fish",
done: false,
})
})

@@ -91,15 +90,15 @@

assert.deepEqual(doc1, {
tasks: [
{description: "feed fish", done: true},
{description: "water plants", done: false},
{description: "water fish", done: false},
]
tasks: [
{ description: "feed fish", done: true },
{ description: "water plants", done: false },
{ description: "water fish", done: false },
],
})
assert.deepEqual(doc2, {
tasks: [
{description: "feed fish", done: true},
{description: "water plants", done: false},
{description: "water fish", done: false},
]
tasks: [
{ description: "feed fish", done: true },
{ description: "water plants", done: false },
{ description: "water fish", done: false },
],
})

@@ -106,0 +105,0 @@ ```

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc