Compare commits

...

10 commits

Author SHA1 Message Date
alexjg
cc3757f9fd Revert "Remove async automerge-wasm wrapper"
This reverts commit a9e23308ce.
2022-10-04 17:25:10 +01:00
alexjg
824fa0a99a Revert "Rename automerge-js to automerge"
This reverts commit 8557ce0b69.
2022-10-04 17:25:10 +01:00
alexjg
7cb2ec6e0e Revert "Add examples of using automerge with bundlers"
This reverts commit 7825da3ab9.
2022-10-04 17:25:10 +01:00
alexjg
648a62b71c Revert "Add an e2e testing tool for the JS packaging"
This reverts commit 4f03cd2a37.
2022-10-04 17:25:10 +01:00
alexjg
a360cf44cc Revert "Set optimization levels to 'Z' for release profile"
This reverts commit 20dc0fb54e.
2022-10-04 17:25:10 +01:00
alexjg
b2888f2871 Revert "update wasm-bindgen"
This reverts commit 577bda3e7f.
2022-10-04 17:25:10 +01:00
alexjg
55e4a53f20 Revert "build both nodejs and bundler packages in yarn build"
This reverts commit da51492327.
2022-10-04 17:25:10 +01:00
alexjg
de25e671aa Revert "Generate index.d.ts from source"
This reverts commit 16f2272b5b.
2022-10-04 17:25:10 +01:00
alexjg
e4a3b26eb8 Revert "Fix a few small typescript complaints"
This reverts commit b6c375efb9.
2022-10-04 17:25:10 +01:00
alexjg
cfd2c95519 Revert "Update JS README"
This reverts commit d6a8d41e0a.
2022-10-04 17:25:10 +01:00
71 changed files with 530 additions and 12712 deletions

View file

@ -11,11 +11,7 @@ resolver = "2"
[profile.release]
debug = true
lto = true
opt-level = 'z'
opt-level = 3
[profile.bench]
debug = true
[profile.release.package.automerge-wasm]
debug = false
opt-level = 'z'

View file

@ -1,18 +1,25 @@
## Automerge
## Automerge JS
Automerge is a library of data structures for building collaborative
applications, this package is the javascript implementation.
This is a reimplementation of Automerge as a JavaScript wrapper around the "automerge-wasm".
Please see [automerge.org](http://automerge.org/) for documentation.
This package is in alpha and feedback in welcome.
## Setup
The primary differences between using this package and "automerge" are as follows:
This package is a wrapper around a core library which is written in rust and
compiled to WASM. In `node` this should be transparent to you, but in the
browser you will need a bundler to include the WASM blob as part of your module
hierarchy. There are examples of doing this with common bundlers in `./examples`.
1. The low level api needs to plugged in via the use function. The only current implementation of "automerge-wasm" but another could used in theory.
## Meta
```javascript
import * as Automerge from "automerge-js";
import * as wasm_api from "automerge-wasm";
Copyright 20172021, the Automerge contributors. Released under the terms of the
MIT license (see `LICENSE`).
// browsers require an async wasm load - see automerge-wasm docs
Automerge.use(wasm_api);
```
2. There is no front-end back-end split, and no patch format or patch observer. These concepts don't make sense with the wasm implementation.
3. The basic `Doc<T>` object is now a Proxy object and will behave differently in a repl environment.
4. The 'Text' class is currently very slow and needs to be re-worked.
Beyond this please refer to the Automerge [README](http://github.com/automerge/automerge/) for further information.

View file

@ -1,3 +0,0 @@
node_modules/
verdacciodb/
htpasswd

View file

@ -1,71 +0,0 @@
#End to end testing for javascript packaging
The network of packages and bundlers we rely on to get the `automerge` package
working is a little complex. We have the `automerge-wasm` package, which the
`automerge` package depends upon, which means that anyone who depends on
`automerge` needs to either a) be using node or b) use a bundler in order to
load the underlying WASM module which is packaged in `automerge-wasm`.
The various bundlers involved are complicated and capricious and so we need an
easy way of testing that everything is in fact working as expected. To do this
we run a custom NPM registry (namely [Verdaccio](https://verdaccio.org/)) and
build the `automerge-wasm` and `automerge` packages and publish them to this
registry. Once we have this registry running we are able to build the example
projects which depend on these packages and check that everything works as
expected.
## Usage
First, install everything:
```
yarn install
```
### Build `automerge-js`
This builds the `automerge-wasm` package and then runs `yarn build` in the
`automerge-js` project with the `--registry` set to the verdaccio registry. The
end result is that you can run `yarn test` in the resulting `automerge-js`
directory in order to run tests against the current `automerge-wasm`.
```
yarn e2e buildjs
```
### Build examples
This either builds or the examples in `automerge-js/examples` or just a subset
of them. Once this is complete you can run the relevant scripts (e.g. `vite dev`
for the Vite example) to check everything works.
```
yarn e2e buildexamples
```
Or, to just build the webpack example
```
yarn e2e buildexamples -e webpack
```
### Run Registry
If you're experimenting with a project which is not in the `examples` folder
you'll need a running registry. `run-registry` builds and publishes
`automerge-js` and `automerge-wasm` and then runs the registry at
`localhost:4873`.
```
yarn e2e run-registry
```
You can now run `yarn install --registry http://localhost:4873` to experiment
with the built packages.
## Using the `dev` build of `automerge-wasm`
All the commands above take a `-p` flag which can be either `release` or
`debug`. The `debug` builds with additional debug symbols which makes errors
less cryptic.

View file

@ -1,438 +0,0 @@
import {once} from "events"
import {setTimeout} from "timers/promises"
import {spawn, ChildProcess} from "child_process"
import * as child_process from "child_process"
import {command, subcommands, run, array, multioption, option, Type} from "cmd-ts"
import * as path from "path"
import * as fsPromises from "fs/promises"
import fetch from "node-fetch"
const VERDACCIO_DB_PATH = path.normalize(`${__dirname}/verdacciodb`)
const VERDACCIO_CONFIG_PATH = path.normalize(`${__dirname}/verdaccio.yaml`)
const AUTOMERGE_WASM_PATH = path.normalize(`${__dirname}/../../automerge-wasm`)
const AUTOMERGE_JS_PATH = path.normalize(`${__dirname}/..`)
const EXAMPLES_DIR = path.normalize(path.join(__dirname, "../", "examples"))
// The different example projects in "../examples"
type Example = "webpack" | "vite" | "create-react-app"
// Type to parse strings to `Example` so the types line up for the `buildExamples` commmand
const ReadExample: Type<string, Example> = {
async from(str) {
if (str === "webpack") {
return "webpack"
} else if (str === "vite") {
return "vite"
} else if (str === "create-react-app") {
return "create-react-app"
} else {
throw new Error(`Unknown example type ${str}`)
}
}
}
type Profile = "dev" | "release"
const ReadProfile: Type<string, Profile> = {
async from(str) {
if (str === "dev") {
return "dev"
} else if (str === "release") {
return "release"
} else {
throw new Error(`Unknown profile ${str}`)
}
}
}
const buildjs = command({
name: "buildjs",
args: {
profile: option({
type: ReadProfile,
long: "profile",
short: "p",
defaultValue: () => "dev" as Profile
})
},
handler: ({profile}) => {
console.log("building js")
withPublishedWasm(profile, async (registryUrl: string) => {
await buildAndPublishAutomergeJs(registryUrl)
})
}
})
const buildWasm = command({
name: "buildwasm",
args: {
profile: option({
type: ReadProfile,
long: "profile",
short: "p",
defaultValue: () => "dev" as Profile
})
},
handler: ({profile}) => {
console.log("building automerge-wasm")
withRegistry(
buildAutomergeWasm(profile),
)
}
})
const buildexamples = command({
name: "buildexamples",
args: {
examples: multioption({
long: "example",
short: "e",
type: array(ReadExample),
}),
profile: option({
type: ReadProfile,
long: "profile",
short: "p",
defaultValue: () => "dev" as Profile
})
},
handler: ({examples, profile}) => {
if (examples.length === 0) {
examples = ["webpack", "vite", "create-react-app"]
}
buildExamples(examples, profile)
}
})
const runRegistry = command({
name: "run-registry",
args: {
profile: option({
type: ReadProfile,
long: "profile",
short: "p",
defaultValue: () => "dev" as Profile
})
},
handler: ({profile}) => {
withPublishedWasm(profile, async (registryUrl: string) => {
await buildAndPublishAutomergeJs(registryUrl)
console.log("\n************************")
console.log(` Verdaccio NPM registry is running at ${registryUrl}`)
console.log(" press CTRL-C to exit ")
console.log("************************")
await once(process, "SIGINT")
}).catch(e => {
console.error(`Failed: ${e}`)
})
}
})
const app = subcommands({
name: "e2e",
cmds: {buildjs, buildexamples, buildwasm: buildWasm, "run-registry": runRegistry}
})
run(app, process.argv.slice(2))
async function buildExamples(examples: Array<Example>, profile: Profile) {
await withPublishedWasm(profile, async (registryUrl) => {
printHeader("building and publishing automerge")
await buildAndPublishAutomergeJs(registryUrl)
for (const example of examples) {
printHeader(`building ${example} example`)
if (example === "webpack") {
const projectPath = path.join(EXAMPLES_DIR, example)
await removeExistingAutomerge(projectPath)
await fsPromises.rm(path.join(projectPath, "yarn.lock"), {force: true})
await spawnAndWait("yarn", ["--cwd", projectPath, "install", "--registry", registryUrl, "--check-files"], {stdio: "inherit"})
await spawnAndWait("yarn", ["--cwd", projectPath, "build"], {stdio: "inherit"})
} else if (example === "vite") {
const projectPath = path.join(EXAMPLES_DIR, example)
await removeExistingAutomerge(projectPath)
await fsPromises.rm(path.join(projectPath, "yarn.lock"), {force: true})
await spawnAndWait("yarn", ["--cwd", projectPath, "install", "--registry", registryUrl, "--check-files"], {stdio: "inherit"})
await spawnAndWait("yarn", ["--cwd", projectPath, "build"], {stdio: "inherit"})
} else if (example === "create-react-app") {
const projectPath = path.join(EXAMPLES_DIR, example)
await removeExistingAutomerge(projectPath)
await fsPromises.rm(path.join(projectPath, "yarn.lock"), {force: true})
await spawnAndWait("yarn", ["--cwd", projectPath, "install", "--registry", registryUrl, "--check-files"], {stdio: "inherit"})
await spawnAndWait("yarn", ["--cwd", projectPath, "build"], {stdio: "inherit"})
}
}
})
}
type WithRegistryAction = (registryUrl: string) => Promise<void>
async function withRegistry(action: WithRegistryAction, ...actions: Array<WithRegistryAction>) {
// First, start verdaccio
printHeader("Starting verdaccio NPM server")
const verd = await VerdaccioProcess.start()
actions.unshift(action)
for (const action of actions) {
try {
type Step = "verd-died" | "action-completed"
const verdDied: () => Promise<Step> = async () => {
await verd.died()
return "verd-died"
}
const actionComplete: () => Promise<Step> = async () => {
await action("http://localhost:4873")
return "action-completed"
}
const result = await Promise.race([verdDied(), actionComplete()])
if (result === "verd-died") {
throw new Error("verdaccio unexpectedly exited")
}
} catch(e) {
await verd.kill()
throw e
}
}
await verd.kill()
}
async function withPublishedWasm(profile: Profile, action: WithRegistryAction) {
await withRegistry(
buildAutomergeWasm(profile),
publishAutomergeWasm,
action
)
}
function buildAutomergeWasm(profile: Profile): WithRegistryAction {
return async (registryUrl: string) => {
printHeader("building automerge-wasm")
await spawnAndWait("yarn", ["--cwd", AUTOMERGE_WASM_PATH, "--registry", registryUrl, "install"], {stdio: "inherit"})
const cmd = profile === "release" ? "release" : "debug"
await spawnAndWait("yarn", ["--cwd", AUTOMERGE_WASM_PATH, cmd], {stdio: "inherit"})
}
}
async function publishAutomergeWasm(registryUrl: string) {
printHeader("Publishing automerge-wasm to verdaccio")
await fsPromises.rm(path.join(VERDACCIO_DB_PATH, "automerge-wasm"), { recursive: true, force: true} )
await yarnPublish(registryUrl, AUTOMERGE_WASM_PATH)
}
async function buildAndPublishAutomergeJs(registryUrl: string) {
// Build the js package
printHeader("Building automerge")
await removeExistingAutomerge(AUTOMERGE_JS_PATH)
await removeFromVerdaccio("automerge")
await fsPromises.rm(path.join(AUTOMERGE_JS_PATH, "yarn.lock"), {force: true})
await spawnAndWait("yarn", ["--cwd", AUTOMERGE_JS_PATH, "install", "--registry", registryUrl, "--check-files"], {stdio: "inherit"})
await spawnAndWait("yarn", ["--cwd", AUTOMERGE_JS_PATH, "build"], {stdio: "inherit"})
await yarnPublish(registryUrl, AUTOMERGE_JS_PATH)
}
/**
* A running verdaccio process
*
*/
class VerdaccioProcess {
child: ChildProcess
stdout: Array<Buffer>
stderr: Array<Buffer>
constructor(child: ChildProcess) {
this.child = child
// Collect stdout/stderr otherwise the subprocess gets blocked writing
this.stdout = []
this.stderr = []
this.child.stdout && this.child.stdout.on("data", (data) => this.stdout.push(data))
this.child.stderr && this.child.stderr.on("data", (data) => this.stderr.push(data))
const errCallback = (e: any) => {
console.error("!!!!!!!!!ERROR IN VERDACCIO PROCESS!!!!!!!!!")
console.error(" ", e)
if (this.stdout.length > 0) {
console.log("\n**Verdaccio stdout**")
const stdout = Buffer.concat(this.stdout)
process.stdout.write(stdout)
}
if (this.stderr.length > 0) {
console.log("\n**Verdaccio stderr**")
const stdout = Buffer.concat(this.stderr)
process.stdout.write(stdout)
}
process.exit(-1)
}
this.child.on("error", errCallback)
}
/**
* Spawn a verdaccio process and wait for it to respond succesfully to http requests
*
* The returned `VerdaccioProcess` can be used to control the subprocess
*/
static async start() {
const child = spawn("yarn", ["verdaccio", "--config", VERDACCIO_CONFIG_PATH], {env: { ...process.env, FORCE_COLOR: "true"}})
// Forward stdout and stderr whilst waiting for startup to complete
const stdoutCallback = (data: Buffer) => process.stdout.write(data)
const stderrCallback = (data: Buffer) => process.stderr.write(data)
child.stdout && child.stdout.on("data", stdoutCallback)
child.stderr && child.stderr.on("data", stderrCallback)
const healthCheck = async () => {
while (true) {
try {
const resp = await fetch("http://localhost:4873")
if (resp.status === 200) {
return
} else {
console.log(`Healthcheck failed: bad status ${resp.status}`)
}
} catch (e) {
console.error(`Healthcheck failed: ${e}`)
}
await setTimeout(500)
}
}
await withTimeout(healthCheck(), 10000)
// Stop forwarding stdout/stderr
child.stdout && child.stdout.off("data", stdoutCallback)
child.stderr && child.stderr.off("data", stderrCallback)
return new VerdaccioProcess(child)
}
/**
* Send a SIGKILL to the process and wait for it to stop
*/
async kill() {
this.child.stdout && this.child.stdout.destroy()
this.child.stderr && this.child.stderr.destroy()
this.child.kill();
try {
await withTimeout(once(this.child, "close"), 500)
} catch (e) {
console.error("unable to kill verdaccio subprocess, trying -9")
this.child.kill(9)
await withTimeout(once(this.child, "close"), 500)
}
}
/**
* A promise which resolves if the subprocess exits for some reason
*/
async died(): Promise<number | null> {
const [exit, _signal] = await once(this.child, "exit")
return exit
}
}
function printHeader(header: string) {
console.log("\n===============================")
console.log(` ${header}`)
console.log("===============================")
}
/**
* Removes the automerge, automerge-wasm, and automerge-js packages from
* `$packageDir/node_modules`
*
* This is useful to force refreshing a package by use in combination with
* `yarn install --check-files`, which checks if a package is present in
* `node_modules` and if it is not forces a reinstall.
*
* @param packageDir - The directory containing the package.json of the target project
*/
async function removeExistingAutomerge(packageDir: string) {
await fsPromises.rm(path.join(packageDir, "node_modules", "automerge-wasm"), {recursive: true, force: true})
await fsPromises.rm(path.join(packageDir, "node_modules", "automerge"), {recursive: true, force: true})
}
type SpawnResult = {
stdout?: Buffer,
stderr?: Buffer,
}
async function spawnAndWait(cmd: string, args: Array<string>, options: child_process.SpawnOptions): Promise<SpawnResult> {
const child = spawn(cmd, args, options)
let stdout = null
let stderr = null
if (child.stdout) {
stdout = []
child.stdout.on("data", data => stdout.push(data))
}
if (child.stderr) {
stderr = []
child.stderr.on("data", data => stderr.push(data))
}
const [exit, _signal] = await once(child, "exit")
if (exit && exit !== 0) {
throw new Error("nonzero exit code")
}
return {
stderr: stderr? Buffer.concat(stderr) : null,
stdout: stdout ? Buffer.concat(stdout) : null
}
}
/**
* Remove a package from the verdaccio registry. This is necessary because we
* often want to _replace_ a version rather than update the version number.
* Obviously this is very bad and verboten in normal circumastances, but the
* whole point here is to be able to test the entire packaging story so it's
* okay I Promise.
*/
async function removeFromVerdaccio(packageName: string) {
await fsPromises.rm(path.join(VERDACCIO_DB_PATH, packageName), {force: true, recursive: true})
}
async function yarnPublish(registryUrl: string, cwd: string) {
await spawnAndWait(
"yarn",
[
"--registry",
registryUrl,
"--cwd",
cwd,
"publish",
"--non-interactive",
],
{
stdio: "inherit",
env: {
...process.env,
FORCE_COLOR: "true",
// This is a fake token, it just has to be the right format
npm_config__auth: "//localhost:4873/:_authToken=Gp2Mgxm4faa/7wp0dMSuRA=="
}
})
}
/**
* Wait for a given delay to resolve a promise, throwing an error if the
* promise doesn't resolve with the timeout
*
* @param promise - the promise to wait for @param timeout - the delay in
* milliseconds to wait before throwing
*/
async function withTimeout<T>(promise: Promise<T>, timeout: number): Promise<T> {
type Step = "timed-out" | {result: T}
const timedOut: () => Promise<Step> = async () => {
await setTimeout(timeout)
return "timed-out"
}
const succeeded: () => Promise<Step> = async () => {
const result = await promise
return {result}
}
const result = await Promise.race([timedOut(), succeeded()])
if (result === "timed-out") {
throw new Error("timed out")
} else {
return result.result
}
}

View file

@ -1,23 +0,0 @@
{
"name": "e2e",
"version": "0.0.1",
"description": "",
"main": "index.js",
"scripts": {
"e2e": "ts-node index.ts"
},
"author": "",
"license": "ISC",
"dependencies": {
"@types/node": "^18.7.18",
"cmd-ts": "^0.11.0",
"node-fetch": "^2",
"ts-node": "^10.9.1",
"typed-emitter": "^2.1.0",
"typescript": "^4.8.3",
"verdaccio": "5"
},
"devDependencies": {
"@types/node-fetch": "2.x"
}
}

View file

@ -1,6 +0,0 @@
{
"compilerOptions": {
"types": ["node"]
},
"module": "nodenext"
}

View file

@ -1,25 +0,0 @@
storage: "./verdacciodb"
auth:
htpasswd:
file: ./htpasswd
publish:
allow_offline: true
logs: {type: stdout, format: pretty, level: info}
packages:
"automerge-wasm":
access: "$all"
publish: "$all"
"automerge-js":
access: "$all"
publish: "$all"
"*":
access: "$all"
publish: "$all"
proxy: npmjs
"@*/*":
access: "$all"
publish: "$all"
proxy: npmjs
uplinks:
npmjs:
url: https://registry.npmjs.org/

File diff suppressed because it is too large Load diff

View file

@ -1 +0,0 @@
node_modules/

View file

@ -1,59 +0,0 @@
# Automerge + `create-react-app`
This is a little fiddly to get working. The problem is that `create-react-app`
hard codes a webpack configuration which does not support WASM modules, which we
require in order to bundle the WASM implementation of automerge. To get around
this we use [`craco`](https://github.com/dilanx/craco) which does some monkey
patching to allow us to modify the webpack config that `create-react-app`
bundles. Then we use a craco plugin called
[`craco-wasm`](https://www.npmjs.com/package/craco-wasm) to perform the
necessary modifications to the webpack config. It should be noted that this is
all quite fragile and ideally you probably don't want to use `create-react-app`
to do this in production.
## Setup
Assuming you have already run `create-react-app` and your working directory is
the project.
### Install craco and craco-wasm
```bash
yarn add craco craco-wasm
```
### Modify `package.json` to use `craco` for scripts
In `package.json` the `scripts` section will look like this:
```json
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "craco eject"
},
```
Replace that section with:
```json
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "craco eject"
},
```
### Create `craco.config.js`
In the root of the project add the following contents to `craco.config.js`
```javascript
const cracoWasm = require("craco-wasm")
module.exports = {
plugins: [cracoWasm()]
}
```

View file

@ -1,5 +0,0 @@
const cracoWasm = require("craco-wasm")
module.exports = {
plugins: [cracoWasm()]
}

View file

@ -1,41 +0,0 @@
{
"name": "automerge-create-react-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"@craco/craco": "^7.0.0-alpha.8",
"craco-wasm": "0.0.1",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"automerge": "2.0.0-alpha.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "craco eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -1,43 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View file

@ -1,25 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View file

@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View file

@ -1,38 +0,0 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View file

@ -1,21 +0,0 @@
import * as Automerge from "automerge"
import logo from './logo.svg';
import './App.css';
let doc = Automerge.init()
doc = Automerge.change(doc, (d) => d.hello = "from automerge-js")
const result = JSON.stringify(doc)
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>{result}</p>
</header>
</div>
);
}
export default App;

View file

@ -1,8 +0,0 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View file

@ -1,13 +0,0 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View file

@ -1,17 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -1,13 +0,0 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

View file

@ -1,5 +0,0 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

File diff suppressed because it is too large Load diff

View file

@ -1,2 +0,0 @@
node_modules/
yarn.lock

View file

@ -1,47 +0,0 @@
# Vite + Automerge
There are three things you need to do to get WASM packaging working with vite:
1. Install the top level await plugin
2. Install the `vite-plugin-wasm` plugin
3. Exclude `automerge-wasm` from the optimizer
First, install the packages we need:
```bash
yarn add vite-plugin-top-level-await
yarn add vite-plugin-wasm
```
In `vite.config.js`
```javascript
import { defineConfig } from "vite"
import wasm from "vite-plugin-wasm"
import topLevelAwait from "vite-plugin-top-level-await"
export default defineConfig({
plugins: [topLevelAwait(), wasm()],
optimizeDeps: {
// This is necessary because otherwise `vite dev` includes two separate
// versions of the JS wrapper. This causes problems because the JS
// wrapper has a module level variable to track JS side heap
// allocations, initializing this twice causes horrible breakage
exclude: ["automerge-wasm"]
}
})
```
Now start the dev server:
```bash
yarn vite
```
## Running the example
```bash
yarn install
yarn dev
```

View file

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View file

@ -1,15 +0,0 @@
import * as Automerge from "/node_modules/.vite/deps/automerge-js.js?v=6e973f28";
console.log(Automerge);
let doc = Automerge.init();
doc = Automerge.change(doc, (d) => d.hello = "from automerge-js");
console.log(doc);
const result = JSON.stringify(doc);
if (typeof document !== "undefined") {
const element = document.createElement("div");
element.innerHTML = JSON.stringify(result);
document.body.appendChild(element);
} else {
console.log("node:", result);
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9ob21lL2FsZXgvUHJvamVjdHMvYXV0b21lcmdlL2F1dG9tZXJnZS1ycy9hdXRvbWVyZ2UtanMvZXhhbXBsZXMvdml0ZS9zcmMvbWFpbi50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBBdXRvbWVyZ2UgZnJvbSBcImF1dG9tZXJnZS1qc1wiXG5cbi8vIGhlbGxvIHdvcmxkIGNvZGUgdGhhdCB3aWxsIHJ1biBjb3JyZWN0bHkgb24gd2ViIG9yIG5vZGVcblxuY29uc29sZS5sb2coQXV0b21lcmdlKVxubGV0IGRvYyA9IEF1dG9tZXJnZS5pbml0KClcbmRvYyA9IEF1dG9tZXJnZS5jaGFuZ2UoZG9jLCAoZDogYW55KSA9PiBkLmhlbGxvID0gXCJmcm9tIGF1dG9tZXJnZS1qc1wiKVxuY29uc29sZS5sb2coZG9jKVxuY29uc3QgcmVzdWx0ID0gSlNPTi5zdHJpbmdpZnkoZG9jKVxuXG5pZiAodHlwZW9mIGRvY3VtZW50ICE9PSAndW5kZWZpbmVkJykge1xuICAgIC8vIGJyb3dzZXJcbiAgICBjb25zdCBlbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnZGl2Jyk7XG4gICAgZWxlbWVudC5pbm5lckhUTUwgPSBKU09OLnN0cmluZ2lmeShyZXN1bHQpXG4gICAgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChlbGVtZW50KTtcbn0gZWxzZSB7XG4gICAgLy8gc2VydmVyXG4gICAgY29uc29sZS5sb2coXCJub2RlOlwiLCByZXN1bHQpXG59XG5cbiJdLCJtYXBwaW5ncyI6IkFBQUEsWUFBWSxlQUFlO0FBSTNCLFFBQVEsSUFBSSxTQUFTO0FBQ3JCLElBQUksTUFBTSxVQUFVLEtBQUs7QUFDekIsTUFBTSxVQUFVLE9BQU8sS0FBSyxDQUFDLE1BQVcsRUFBRSxRQUFRLG1CQUFtQjtBQUNyRSxRQUFRLElBQUksR0FBRztBQUNmLE1BQU0sU0FBUyxLQUFLLFVBQVUsR0FBRztBQUVqQyxJQUFJLE9BQU8sYUFBYSxhQUFhO0FBRWpDLFFBQU0sVUFBVSxTQUFTLGNBQWMsS0FBSztBQUM1QyxVQUFRLFlBQVksS0FBSyxVQUFVLE1BQU07QUFDekMsV0FBUyxLQUFLLFlBQVksT0FBTztBQUNyQyxPQUFPO0FBRUgsVUFBUSxJQUFJLFNBQVMsTUFBTTtBQUMvQjsiLCJuYW1lcyI6W119

View file

@ -1,20 +0,0 @@
{
"name": "autovite",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"automerge": "2.0.0-alpha.1"
},
"devDependencies": {
"typescript": "^4.6.4",
"vite": "^3.1.0",
"vite-plugin-top-level-await": "^1.1.1",
"vite-plugin-wasm": "^2.1.0"
}
}

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1,9 +0,0 @@
export function setupCounter(element: HTMLButtonElement) {
let counter = 0
const setCounter = (count: number) => {
counter = count
element.innerHTML = `count is ${counter}`
}
element.addEventListener('click', () => setCounter(++counter))
setCounter(0)
}

View file

@ -1,18 +0,0 @@
import * as Automerge from "automerge"
// hello world code that will run correctly on web or node
let doc = Automerge.init()
doc = Automerge.change(doc, (d: any) => d.hello = "from automerge-js")
const result = JSON.stringify(doc)
if (typeof document !== 'undefined') {
// browser
const element = document.createElement('div');
element.innerHTML = JSON.stringify(result)
document.body.appendChild(element);
} else {
// server
console.log("node:", result)
}

View file

@ -1,97 +0,0 @@
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vanilla:hover {
filter: drop-shadow(0 0 2em #3178c6aa);
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="32" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path fill="#007ACC" d="M0 128v128h256V0H0z"></path><path fill="#FFF" d="m56.612 128.85l-.081 10.483h33.32v94.68h23.568v-94.68h33.321v-10.28c0-5.69-.122-10.444-.284-10.566c-.122-.162-20.4-.244-44.983-.203l-44.74.122l-.121 10.443Zm149.955-10.742c6.501 1.625 11.459 4.51 16.01 9.224c2.357 2.52 5.851 7.111 6.136 8.208c.08.325-11.053 7.802-17.798 11.988c-.244.162-1.22-.894-2.317-2.52c-3.291-4.795-6.745-6.867-12.028-7.233c-7.76-.528-12.759 3.535-12.718 10.321c0 1.992.284 3.17 1.097 4.795c1.707 3.536 4.876 5.649 14.832 9.956c18.326 7.883 26.168 13.084 31.045 20.48c5.445 8.249 6.664 21.415 2.966 31.208c-4.063 10.646-14.14 17.879-28.323 20.276c-4.388.772-14.79.65-19.504-.203c-10.28-1.828-20.033-6.908-26.047-13.572c-2.357-2.6-6.949-9.387-6.664-9.874c.122-.163 1.178-.813 2.356-1.504c1.138-.65 5.446-3.129 9.509-5.485l7.355-4.267l1.544 2.276c2.154 3.29 6.867 7.801 9.712 9.305c8.167 4.307 19.383 3.698 24.909-1.26c2.357-2.153 3.332-4.388 3.332-7.68c0-2.966-.366-4.266-1.91-6.501c-1.99-2.845-6.054-5.242-17.595-10.24c-13.206-5.69-18.895-9.224-24.096-14.832c-3.007-3.25-5.852-8.452-7.03-12.8c-.975-3.617-1.22-12.678-.447-16.335c2.723-12.76 12.353-21.659 26.25-24.3c4.51-.853 14.994-.528 19.424.569Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1 +0,0 @@
/// <reference types="vite/client" />

View file

@ -1,20 +0,0 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ESNext", "DOM"],
"moduleResolution": "Node",
"strict": true,
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true
},
"include": ["src"]
}

View file

@ -1,15 +0,0 @@
import { defineConfig } from "vite"
import wasm from "vite-plugin-wasm"
import topLevelAwait from "vite-plugin-top-level-await"
export default defineConfig({
plugins: [topLevelAwait(), wasm()],
optimizeDeps: {
// This is necessary because otherwise `vite dev` includes two separate
// versions of the JS wrapper. This causes problems because the JS
// wrapper has a module level variable to track JS side heap
// allocations, initializing this twice causes horrible breakage
exclude: ["automerge-wasm"]
}
})

View file

@ -1,37 +0,0 @@
# Webpack + Automerge
Getting WASM working in webpack 5 is very easy. You just need to enable the
`asyncWebAssembly`
[experiment](https://webpack.js.org/configuration/experiments/). For example:
```javascript
const path = require('path');
const clientConfig = {
experiments: { asyncWebAssembly: true },
target: 'web',
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'public'),
},
mode: "development", // or production
performance: { // we dont want the wasm blob to generate warnings
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000
}
};
module.exports = clientConfig
```
## Running the example
```bash
yarn install
yarn start
```

View file

@ -10,13 +10,13 @@
},
"author": "",
"dependencies": {
"automerge": "2.0.0-alpha.1"
"automerge-js": "file:automerge-js-0.1.0.tgz",
"automerge-wasm": "file:automerge-wasm-0.1.3.tgz"
},
"devDependencies": {
"serve": "^13.0.2",
"webpack": "^5.72.1",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.11.1",
"webpack-node-externals": "^3.0.0"
}
}

View file

@ -1,7 +1,10 @@
import * as Automerge from "automerge"
import * as Automerge from "automerge-js"
import init from "automerge-wasm"
// hello world code that will run correctly on web or node
init().then((api) => {
Automerge.use(api)
let doc = Automerge.init()
doc = Automerge.change(doc, (d) => d.hello = "from automerge-js")
const result = JSON.stringify(doc)
@ -15,4 +18,5 @@ if (typeof document !== 'undefined') {
// server
console.log("node:", result)
}
})

View file

@ -18,7 +18,6 @@ const serverConfig = {
};
const clientConfig = {
experiments: { asyncWebAssembly: true },
target: 'web',
entry: './src/index.js',
output: {

113
automerge-js/index.d.ts vendored Normal file
View file

@ -0,0 +1,113 @@
import { API as LowLevelApi } from "automerge-types";
import { Actor as ActorId, Prop, ObjID, Change, DecodedChange, Heads, MaterializeValue } from "automerge-types";
import { JsSyncState as SyncState, SyncMessage, DecodedSyncMessage } from "automerge-types";
export { API as LowLevelApi } from "automerge-types";
export { Actor as ActorId, Prop, ObjID, Change, DecodedChange, Heads, Automerge, MaterializeValue } from "automerge-types";
export { JsSyncState as SyncState, SyncMessage, DecodedSyncMessage } from "automerge-types";
export type ChangeOptions = {
message?: string;
time?: number;
};
export class Int {
value: number;
constructor(value: number);
}
export class Uint {
value: number;
constructor(value: number);
}
export class Float64 {
value: number;
constructor(value: number);
}
export class Counter {
value: number;
constructor(value?: number);
valueOf(): number;
toString(): string;
toJSON(): number;
}
export class Text {
elems: AutomergeValue[];
constructor(text?: string | string[]);
get length(): number;
get(index: number): AutomergeValue | undefined;
[index: number]: AutomergeValue | undefined;
[Symbol.iterator](): {
next(): {
done: boolean;
value: AutomergeValue;
} | {
done: boolean;
value?: undefined;
};
};
toString(): string;
toSpans(): AutomergeValue[];
toJSON(): string;
set(index: number, value: AutomergeValue): void;
insertAt(index: number, ...values: AutomergeValue[]): void;
deleteAt(index: number, numDelete?: number): void;
map<T>(callback: (e: AutomergeValue) => T): void;
}
export type Doc<T> = {
readonly [P in keyof T]: T[P];
};
export type ChangeFn<T> = (doc: T) => void;
export interface State<T> {
change: DecodedChange;
snapshot: T;
}
export type ScalarValue = string | number | null | boolean | Date | Counter | Uint8Array;
export type AutomergeValue = ScalarValue | {[key: string]: AutomergeValue;} | Array<AutomergeValue>;
type Conflicts = {
[key: string]: AutomergeValue;
};
export function use(api: LowLevelApi): void;
export function getBackend<T>(doc: Doc<T>) : Automerge;
export function init<T>(actor?: ActorId): Doc<T>;
export function clone<T>(doc: Doc<T>): Doc<T>;
export function free<T>(doc: Doc<T>): void;
export function from<T>(initialState: T | Doc<T>, actor?: ActorId): Doc<T>;
export function change<T>(doc: Doc<T>, options: string | ChangeOptions | ChangeFn<T>, callback?: ChangeFn<T>): Doc<T>;
export function emptyChange<T>(doc: Doc<T>, options: ChangeOptions): unknown;
export function load<T>(data: Uint8Array, actor?: ActorId): Doc<T>;
export function save<T>(doc: Doc<T>): Uint8Array;
export function merge<T>(local: Doc<T>, remote: Doc<T>): Doc<T>;
export function getActorId<T>(doc: Doc<T>): ActorId;
export function getConflicts<T>(doc: Doc<T>, prop: Prop): Conflicts | undefined;
export function getLastLocalChange<T>(doc: Doc<T>): Change | undefined;
export function getObjectId<T>(doc: Doc<T>): ObjID;
export function getChanges<T>(oldState: Doc<T>, newState: Doc<T>): Change[];
export function getAllChanges<T>(doc: Doc<T>): Change[];
export function applyChanges<T>(doc: Doc<T>, changes: Change[]): [Doc<T>];
export function getHistory<T>(doc: Doc<T>): State<T>[];
export function equals<T>(val1: Doc<T>, val2: Doc<T>): boolean;
export function encodeSyncState(state: SyncState): Uint8Array;
export function decodeSyncState(state: Uint8Array): SyncState;
export function generateSyncMessage<T>(doc: Doc<T>, inState: SyncState): [SyncState, SyncMessage | null];
export function receiveSyncMessage<T>(doc: Doc<T>, inState: SyncState, message: SyncMessage): [Doc<T>, SyncState, null];
export function initSyncState(): SyncState;
export function encodeChange(change: DecodedChange): Change;
export function decodeChange(data: Change): DecodedChange;
export function encodeSyncMessage(message: DecodedSyncMessage): SyncMessage;
export function decodeSyncMessage(message: SyncMessage): DecodedSyncMessage;
export function getMissingDeps<T>(doc: Doc<T>, heads: Heads): Heads;
export function getHeads<T>(doc: Doc<T>): Heads;
export function dump<T>(doc: Doc<T>): void;
export function toJS<T>(doc: Doc<T>): MaterializeValue;
export function uuid(): string;

View file

@ -1,10 +1,10 @@
{
"name": "automerge",
"name": "automerge-js",
"collaborators": [
"Orion Henry <orion@inkandswitch.com>",
"Martin Kleppmann"
],
"version": "2.0.0-alpha.1",
"version": "0.1.12",
"description": "Reimplementation of `automerge` on top of the automerge-wasm backend",
"homepage": "https://github.com/automerge/automerge-rs/tree/main/automerge-js",
"repository": "github:automerge/automerge-rs",
@ -13,7 +13,6 @@
"LICENSE",
"package.json",
"index.d.ts",
"dist/cjs/*.d.ts",
"dist/cjs/constants.js",
"dist/cjs/types.js",
"dist/cjs/numbers.js",
@ -23,7 +22,6 @@
"dist/cjs/low_level.js",
"dist/cjs/text.js",
"dist/cjs/proxies.js",
"dist/mjs/*.d.ts",
"dist/mjs/constants.js",
"dist/mjs/types.js",
"dist/mjs/numbers.js",
@ -49,6 +47,7 @@
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^5.25.0",
"@typescript-eslint/parser": "^5.25.0",
"automerge-wasm": "^0.1.6",
"eslint": "^8.15.0",
"fast-sha256": "^1.3.0",
"mocha": "^10.0.0",
@ -57,7 +56,7 @@
"typescript": "^4.6.4"
},
"dependencies": {
"automerge-wasm": "0.1.7",
"automerge-types": "0.1.5",
"uuid": "^8.3"
}
}

View file

@ -1,4 +1,4 @@
import { Automerge, ObjID, Prop } from "automerge-wasm"
import { Automerge, ObjID, Prop } from "automerge-types"
import { COUNTER } from "./constants"
/**
* The most basic CRDT: an integer value that can be changed only by

View file

@ -7,11 +7,11 @@ import { STATE, HEADS, TRACE, OBJECT_ID, READ_ONLY, FROZEN } from "./constants"
import { AutomergeValue, Counter } from "./types"
export { AutomergeValue, Text, Counter, Int, Uint, Float64 } from "./types"
import { API } from "automerge-wasm";
import { API } from "automerge-types";
import { ApiHandler, UseApi } from "./low_level"
import { Actor as ActorId, Prop, ObjID, Change, DecodedChange, Heads, Automerge, MaterializeValue } from "automerge-wasm"
import { JsSyncState as SyncState, SyncMessage, DecodedSyncMessage } from "automerge-wasm"
import { Actor as ActorId, Prop, ObjID, Change, DecodedChange, Heads, Automerge, MaterializeValue } from "automerge-types"
import { JsSyncState as SyncState, SyncMessage, DecodedSyncMessage } from "automerge-types"
export type ChangeOptions = { message?: string, time?: number }
@ -24,14 +24,10 @@ export interface State<T> {
snapshot: T
}
export function use(api: API) {
UseApi(api)
}
import * as wasm from "automerge-wasm"
use(wasm)
export function getBackend<T>(doc: Doc<T>) : Automerge {
return _state(doc)
}
@ -91,7 +87,7 @@ export function free<T>(doc: Doc<T>) {
return _state(doc).free()
}
export function from<T extends Record<string, unknown>>(initialState: T | Doc<T>, actor?: ActorId): Doc<T> {
export function from<T>(initialState: T | Doc<T>, actor?: ActorId): Doc<T> {
return change(init(actor), (d) => Object.assign(d, initialState))
}

View file

@ -1,6 +1,6 @@
import { Automerge, Change, DecodedChange, Actor, SyncState, SyncMessage, JsSyncState, DecodedSyncMessage } from "automerge-wasm"
import { API } from "automerge-wasm"
import { Automerge, Change, DecodedChange, Actor, SyncState, SyncMessage, JsSyncState, DecodedSyncMessage } from "automerge-types"
import { API } from "automerge-types"
export function UseApi(api: API) {
for (const k in api) {
@ -11,15 +11,15 @@ export function UseApi(api: API) {
/* eslint-disable */
export const ApiHandler : API = {
create(actor?: Actor): Automerge { throw new RangeError("Automerge.use() not called") },
load(data: Uint8Array, actor?: Actor): Automerge { throw new RangeError("Automerge.use() not called (load)") },
encodeChange(change: DecodedChange): Change { throw new RangeError("Automerge.use() not called (encodeChange)") },
decodeChange(change: Change): DecodedChange { throw new RangeError("Automerge.use() not called (decodeChange)") },
initSyncState(): SyncState { throw new RangeError("Automerge.use() not called (initSyncState)") },
encodeSyncMessage(message: DecodedSyncMessage): SyncMessage { throw new RangeError("Automerge.use() not called (encodeSyncMessage)") },
decodeSyncMessage(msg: SyncMessage): DecodedSyncMessage { throw new RangeError("Automerge.use() not called (decodeSyncMessage)") },
encodeSyncState(state: SyncState): Uint8Array { throw new RangeError("Automerge.use() not called (encodeSyncState)") },
decodeSyncState(data: Uint8Array): SyncState { throw new RangeError("Automerge.use() not called (decodeSyncState)") },
exportSyncState(state: SyncState): JsSyncState { throw new RangeError("Automerge.use() not called (exportSyncState)") },
importSyncState(state: JsSyncState): SyncState { throw new RangeError("Automerge.use() not called (importSyncState)") },
load(data: Uint8Array, actor?: Actor): Automerge { throw new RangeError("Automerge.use() not called") },
encodeChange(change: DecodedChange): Change { throw new RangeError("Automerge.use() not called") },
decodeChange(change: Change): DecodedChange { throw new RangeError("Automerge.use() not called") },
initSyncState(): SyncState { throw new RangeError("Automerge.use() not called") },
encodeSyncMessage(message: DecodedSyncMessage): SyncMessage { throw new RangeError("Automerge.use() not called") },
decodeSyncMessage(msg: SyncMessage): DecodedSyncMessage { throw new RangeError("Automerge.use() not called") },
encodeSyncState(state: SyncState): Uint8Array { throw new RangeError("Automerge.use() not called") },
decodeSyncState(data: Uint8Array): SyncState { throw new RangeError("Automerge.use() not called") },
exportSyncState(state: SyncState): JsSyncState { throw new RangeError("Automerge.use() not called") },
importSyncState(state: JsSyncState): SyncState { throw new RangeError("Automerge.use() not called") },
}
/* eslint-enable */

View file

@ -1,7 +1,8 @@
import { Automerge, Heads, ObjID } from "automerge-wasm"
import { Prop } from "automerge-wasm"
import { Automerge, Heads, ObjID } from "automerge-types"
import { Prop } from "automerge-types"
import { AutomergeValue, ScalarValue, MapValue, ListValue, TextValue } from "./types"
import { Int, Uint, Float64 } from "./numbers"
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"
@ -199,7 +200,7 @@ const MapHandler = {
ownKeys (target) {
const { context, objectId, heads} = target
// FIXME - this is a tmp workaround until fix the dupe key bug in keys()
const keys = context.keys(objectId, heads)
let keys = context.keys(objectId, heads)
return [...new Set<string>(keys)]
},
}

View file

@ -1,4 +1,4 @@
import { Value } from "automerge-wasm"
import { Value } from "automerge-types"
import { TEXT } from "./constants"
export class Text {

View file

@ -1,5 +1,10 @@
import * as tt from "automerge-types"
import * as assert from 'assert'
import * as util from 'util'
import * as Automerge from '../src'
import * as AutomergeWASM from "automerge-wasm"
Automerge.use(AutomergeWASM)
describe('Automerge', () => {
describe('basics', () => {
@ -170,15 +175,4 @@ describe('Automerge', () => {
console.log(doc.text.indexOf("world"))
})
})
it('should obtain the same conflicts, regardless of merge order', () => {
let s1 = Automerge.init()
let s2 = Automerge.init()
s1 = Automerge.change(s1, doc => { doc.x = 1; doc.y = 2 })
s2 = Automerge.change(s2, doc => { doc.x = 3; doc.y = 4 })
const m1 = Automerge.merge(Automerge.clone(s1), Automerge.clone(s2))
const m2 = Automerge.merge(Automerge.clone(s2), Automerge.clone(s1))
assert.deepStrictEqual(Automerge.getConflicts(m1, 'x'), Automerge.getConflicts(m2, 'x'))
})
})

View file

@ -2,6 +2,9 @@ import * as assert from 'assert'
import { checkEncoded } from './helpers'
import * as Automerge from '../src'
import { encodeChange, decodeChange } from '../src'
import * as AutomergeWASM from "automerge-wasm"
Automerge.use(AutomergeWASM)
describe('change encoding', () => {
it('should encode text edits', () => {

View file

@ -2,6 +2,9 @@ import * as assert from 'assert'
import * as Automerge from '../src'
import { assertEqualsOneOf } from './helpers'
import { decodeChange } from './legacy/columnar'
import * as AutomergeWASM from "automerge-wasm"
Automerge.use(AutomergeWASM)
const UUID_PATTERN = /^[0-9a-f]{32}$/
const OPID_PATTERN = /^[0-9]+@[0-9a-f]{32}$/

View file

@ -3,6 +3,9 @@ import * as Automerge from '../src'
import { BloomFilter } from './legacy/sync'
import { decodeChangeMeta } from './legacy/columnar'
import { decodeSyncMessage, encodeSyncMessage, decodeSyncState, encodeSyncState, initSyncState } from "../src"
import * as AutomergeWASM from "automerge-wasm"
Automerge.use(AutomergeWASM)
function inspect(a) {
const util = require("util");

View file

@ -1,6 +1,9 @@
import * as assert from 'assert'
import * as Automerge from '../src'
import { assertEqualsOneOf } from './helpers'
import * as AutomergeWASM from "automerge-wasm"
Automerge.use(AutomergeWASM)
function attributeStateToAttributes(accumulatedAttributes) {
const attributes = {}

View file

@ -1,5 +1,8 @@
import * as assert from 'assert'
import * as Automerge from '../src'
import * as AutomergeWASM from "automerge-wasm"
Automerge.use(AutomergeWASM)
const uuid = Automerge.uuid

View file

@ -2,7 +2,7 @@
"compilerOptions": {
"target": "es2016",
"sourceMap": false,
"declaration": true,
"declaration": false,
"resolveJsonModule": true,
"module": "commonjs",
"moduleResolution": "node",

View file

@ -35,7 +35,7 @@ hex = "^0.4.3"
regex = "^1.5"
[dependencies.wasm-bindgen]
version = "^0.2.83"
version = "^0.2"
#features = ["std"]
features = ["serde-serialize", "std"]

View file

@ -1,205 +1,2 @@
export type Actor = string;
export type ObjID = string;
export type Change = Uint8Array;
export type SyncMessage = Uint8Array;
export type Prop = string | number;
export type Hash = string;
export type Heads = Hash[];
export type Value = string | number | boolean | null | Date | Uint8Array
export type MaterializeValue = { [key:string]: MaterializeValue } | Array<MaterializeValue> | Value
export type ObjType = string | Array<ObjType | Value> | { [key: string]: ObjType | Value }
export type FullValue =
["str", string] |
["int", number] |
["uint", number] |
["f64", number] |
["boolean", boolean] |
["timestamp", Date] |
["counter", number] |
["bytes", Uint8Array] |
["null", null] |
["map", ObjID] |
["list", ObjID] |
["text", ObjID] |
["table", ObjID]
export type FullValueWithId =
["str", string, ObjID ] |
["int", number, ObjID ] |
["uint", number, ObjID ] |
["f64", number, ObjID ] |
["boolean", boolean, ObjID ] |
["timestamp", Date, ObjID ] |
["counter", number, ObjID ] |
["bytes", Uint8Array, ObjID ] |
["null", null, ObjID ] |
["map", ObjID ] |
["list", ObjID] |
["text", ObjID] |
["table", ObjID]
export enum ObjTypeName {
list = "list",
map = "map",
table = "table",
text = "text",
}
export type Datatype =
"boolean" |
"str" |
"int" |
"uint" |
"f64" |
"null" |
"timestamp" |
"counter" |
"bytes" |
"map" |
"text" |
"list";
export type SyncHave = {
lastSync: Heads,
bloom: Uint8Array,
}
export type DecodedSyncMessage = {
heads: Heads,
need: Heads,
have: SyncHave[]
changes: Change[]
}
export type DecodedChange = {
actor: Actor,
seq: number
startOp: number,
time: number,
message: string | null,
deps: Heads,
hash: Hash,
ops: Op[]
}
export type Op = {
action: string,
obj: ObjID,
key: string,
value?: string | number | boolean,
datatype?: string,
pred: string[],
}
export type Patch = {
obj: ObjID
action: 'assign' | 'insert' | 'delete'
key: Prop
value: Value
datatype: Datatype
conflict: boolean
}
export function create(actor?: Actor): Automerge;
export function load(data: Uint8Array, actor?: Actor): Automerge;
export function encodeChange(change: DecodedChange): Change;
export function decodeChange(change: Change): DecodedChange;
export function initSyncState(): SyncState;
export function encodeSyncMessage(message: DecodedSyncMessage): SyncMessage;
export function decodeSyncMessage(msg: SyncMessage): DecodedSyncMessage;
export function encodeSyncState(state: SyncState): Uint8Array;
export function decodeSyncState(data: Uint8Array): SyncState;
export function exportSyncState(state: SyncState): JsSyncState;
export function importSyncState(state: JsSyncState): SyncState;
export class API {
create(actor?: Actor): Automerge;
load(data: Uint8Array, actor?: Actor): Automerge;
encodeChange(change: DecodedChange): Change;
decodeChange(change: Change): DecodedChange;
initSyncState(): SyncState;
encodeSyncMessage(message: DecodedSyncMessage): SyncMessage;
decodeSyncMessage(msg: SyncMessage): DecodedSyncMessage;
encodeSyncState(state: SyncState): Uint8Array;
decodeSyncState(data: Uint8Array): SyncState;
exportSyncState(state: SyncState): JsSyncState;
importSyncState(state: JsSyncState): SyncState;
}
export class Automerge {
// change state
put(obj: ObjID, prop: Prop, value: Value, datatype?: Datatype): void;
putObject(obj: ObjID, prop: Prop, value: ObjType): ObjID;
insert(obj: ObjID, index: number, value: Value, datatype?: Datatype): void;
insertObject(obj: ObjID, index: number, value: ObjType): ObjID;
push(obj: ObjID, value: Value, datatype?: Datatype): void;
pushObject(obj: ObjID, value: ObjType): ObjID;
splice(obj: ObjID, start: number, delete_count: number, text?: string | Array<Value>): ObjID[] | undefined;
increment(obj: ObjID, prop: Prop, value: number): void;
delete(obj: ObjID, prop: Prop): void;
// returns a single value - if there is a conflict return the winner
get(obj: ObjID, prop: Prop, heads?: Heads): Value | undefined;
getWithType(obj: ObjID, prop: Prop, heads?: Heads): FullValue | null;
// return all values in case of a conflict
getAll(obj: ObjID, arg: Prop, heads?: Heads): FullValueWithId[];
keys(obj: ObjID, heads?: Heads): string[];
text(obj: ObjID, heads?: Heads): string;
length(obj: ObjID, heads?: Heads): number;
materialize(obj?: ObjID, heads?: Heads): MaterializeValue;
// transactions
commit(message?: string, time?: number): Hash;
merge(other: Automerge): Heads;
getActorId(): Actor;
pendingOps(): number;
rollback(): number;
// patches
enablePatches(enable: boolean): void;
popPatches(): Patch[];
// save and load to local store
save(): Uint8Array;
saveIncremental(): Uint8Array;
loadIncremental(data: Uint8Array): number;
// sync over network
receiveSyncMessage(state: SyncState, message: SyncMessage): void;
generateSyncMessage(state: SyncState): SyncMessage | null;
// low level change functions
applyChanges(changes: Change[]): void;
getChanges(have_deps: Heads): Change[];
getChangeByHash(hash: Hash): Change | null;
getChangesAdded(other: Automerge): Change[];
getHeads(): Heads;
getLastLocalChange(): Change | null;
getMissingDeps(heads?: Heads): Heads;
// memory management
free(): void;
clone(actor?: string): Automerge;
fork(actor?: string): Automerge;
forkAt(heads: Heads, actor?: string): Automerge;
// dump internal state to console.log
dump(): void;
}
export class JsSyncState {
sharedHeads: Heads;
lastSentHeads: Heads;
theirHeads: Heads | undefined;
theirHeed: Heads | undefined;
theirHave: SyncHave[] | undefined;
sentHashes: Heads;
}
export class SyncState {
free(): void;
clone(): SyncState;
lastSentHeads: Heads;
sentHashes: Heads;
readonly sharedHeads: Heads;
}
export * from "automerge-types"
export { default } from "automerge-types"

View file

@ -0,0 +1,5 @@
let wasm = require("./bindgen")
module.exports = wasm
module.exports.load = module.exports.loadDoc
delete module.exports.loadDoc
module.exports.init = () => (new Promise((resolve,reject) => { resolve(module.exports) }))

View file

@ -8,29 +8,29 @@
"description": "wasm-bindgen bindings to the automerge rust implementation",
"homepage": "https://github.com/automerge/automerge-rs/tree/main/automerge-wasm",
"repository": "github:automerge/automerge-rs",
"version": "0.1.7",
"version": "0.1.6",
"license": "MIT",
"files": [
"README.md",
"LICENSE",
"package.json",
"index.d.ts",
"nodejs/index.js",
"nodejs/bindgen.js",
"nodejs/bindgen_bg.wasm",
"bundler/bindgen.js",
"bundler/bindgen_bg.js",
"bundler/bindgen_bg.wasm"
"web/index.js",
"web/bindgen.js",
"web/bindgen_bg.wasm"
],
"types": "index.d.ts",
"module": "./bundler/bindgen.js",
"main": "./nodejs/bindgen.js",
"module": "./web/index.js",
"main": "./nodejs/index.js",
"scripts": {
"lint": "eslint test/*.ts",
"debug": "cross-env PROFILE=dev yarn buildall",
"build": "cross-env PROFILE=dev FEATURES='' yarn buildall",
"build": "cross-env PROFILE=dev TARGET=nodejs FEATURES='' yarn target",
"release": "cross-env PROFILE=release yarn buildall",
"buildall": "cross-env TARGET=nodejs yarn target && cross-env TARGET=bundler yarn target",
"target": "rimraf ./$TARGET && wasm-pack build --target $TARGET --$PROFILE --out-name bindgen -d $TARGET -- $FEATURES",
"buildall": "cross-env TARGET=nodejs yarn target && cross-env TARGET=web yarn target",
"target": "rimraf ./$TARGET && wasm-pack build --target $TARGET --$PROFILE --out-name bindgen -d $TARGET -- $FEATURES && cp $TARGET-index.js $TARGET/index.js",
"test": "ts-mocha -p tsconfig.json --type-check --bail --full-trace test/*.ts"
},
"devDependencies": {
@ -50,8 +50,7 @@
"ts-mocha": "^9.0.2",
"typescript": "^4.6.4"
},
"exports": {
"browser": "./bundler/bindgen.js",
"require": "./nodejs/bindgen.js"
"dependencies": {
"automerge-types": "0.1.5"
}
}

View file

@ -853,7 +853,7 @@ pub fn init(actor: Option<String>) -> Result<Automerge, JsValue> {
Automerge::new(actor)
}
#[wasm_bindgen(js_name = load)]
#[wasm_bindgen(js_name = loadDoc)]
pub fn load(data: Uint8Array, actor: Option<String>) -> Result<Automerge, JsValue> {
let data = data.to_vec();
let observer = None;

View file

@ -1,7 +1,7 @@
import { describe, it } from 'mocha';
import * as assert from 'assert'
//@ts-ignore
import { create, load } from '..'
import { init, create, load } from '..'
describe('Automerge', () => {
describe('Readme Examples', () => {
@ -10,10 +10,12 @@ describe('Automerge', () => {
doc.free()
})
it('Using the Library and Creating a Document (2)', (done) => {
init().then((_:any) => {
const doc = create()
doc.free()
done()
})
})
it('Automerge Scalar Types (1)', () => {
const doc = create()
doc.put("/", "prop1", 100) // int

View file

@ -3,7 +3,7 @@ import { describe, it } from 'mocha';
import assert from 'assert'
//@ts-ignore
import { BloomFilter } from './helpers/sync'
import { create, load, SyncState, Automerge, encodeChange, decodeChange, initSyncState, decodeSyncMessage, decodeSyncState, encodeSyncState, encodeSyncMessage } from '..'
import { init, create, load, SyncState, Automerge, encodeChange, decodeChange, initSyncState, decodeSyncMessage, decodeSyncState, encodeSyncState, encodeSyncMessage } from '..'
import { DecodedSyncMessage, Hash } from '..';
function sync(a: Automerge, b: Automerge, aSyncState = initSyncState(), bSyncState = initSyncState()) {
@ -28,6 +28,9 @@ function sync(a: Automerge, b: Automerge, aSyncState = initSyncState(), bSyncSta
describe('Automerge', () => {
describe('basics', () => {
it('default import init() should return a promise', () => {
assert(init() instanceof Promise)
})
it('should create, clone and free', () => {
const doc1 = create()

View file

@ -0,0 +1,10 @@
MIT License
Copyright 2022, Ink & Switch LLC
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

209
automerge-wasm/types/index.d.ts vendored Normal file
View file

@ -0,0 +1,209 @@
export type Actor = string;
export type ObjID = string;
export type Change = Uint8Array;
export type SyncMessage = Uint8Array;
export type Prop = string | number;
export type Hash = string;
export type Heads = Hash[];
export type Value = string | number | boolean | null | Date | Uint8Array
export type MaterializeValue = { [key:string]: MaterializeValue } | Array<MaterializeValue> | Value
export type ObjType = string | Array<ObjType | Value> | { [key: string]: ObjType | Value }
export type FullValue =
["str", string] |
["int", number] |
["uint", number] |
["f64", number] |
["boolean", boolean] |
["timestamp", Date] |
["counter", number] |
["bytes", Uint8Array] |
["null", null] |
["map", ObjID] |
["list", ObjID] |
["text", ObjID] |
["table", ObjID]
export type FullValueWithId =
["str", string, ObjID ] |
["int", number, ObjID ] |
["uint", number, ObjID ] |
["f64", number, ObjID ] |
["boolean", boolean, ObjID ] |
["timestamp", Date, ObjID ] |
["counter", number, ObjID ] |
["bytes", Uint8Array, ObjID ] |
["null", null, ObjID ] |
["map", ObjID ] |
["list", ObjID] |
["text", ObjID] |
["table", ObjID]
export enum ObjTypeName {
list = "list",
map = "map",
table = "table",
text = "text",
}
export type Datatype =
"boolean" |
"str" |
"int" |
"uint" |
"f64" |
"null" |
"timestamp" |
"counter" |
"bytes" |
"map" |
"text" |
"list";
export type SyncHave = {
lastSync: Heads,
bloom: Uint8Array,
}
export type DecodedSyncMessage = {
heads: Heads,
need: Heads,
have: SyncHave[]
changes: Change[]
}
export type DecodedChange = {
actor: Actor,
seq: number
startOp: number,
time: number,
message: string | null,
deps: Heads,
hash: Hash,
ops: Op[]
}
export type Op = {
action: string,
obj: ObjID,
key: string,
value?: string | number | boolean,
datatype?: string,
pred: string[],
}
export type Patch = {
obj: ObjID
action: 'assign' | 'insert' | 'delete'
key: Prop
value: Value
datatype: Datatype
conflict: boolean
}
export function create(actor?: Actor): Automerge;
export function load(data: Uint8Array, actor?: Actor): Automerge;
export function encodeChange(change: DecodedChange): Change;
export function decodeChange(change: Change): DecodedChange;
export function initSyncState(): SyncState;
export function encodeSyncMessage(message: DecodedSyncMessage): SyncMessage;
export function decodeSyncMessage(msg: SyncMessage): DecodedSyncMessage;
export function encodeSyncState(state: SyncState): Uint8Array;
export function decodeSyncState(data: Uint8Array): SyncState;
export function exportSyncState(state: SyncState): JsSyncState;
export function importSyncState(state: JsSyncState): SyncState;
export class API {
create(actor?: Actor): Automerge;
load(data: Uint8Array, actor?: Actor): Automerge;
encodeChange(change: DecodedChange): Change;
decodeChange(change: Change): DecodedChange;
initSyncState(): SyncState;
encodeSyncMessage(message: DecodedSyncMessage): SyncMessage;
decodeSyncMessage(msg: SyncMessage): DecodedSyncMessage;
encodeSyncState(state: SyncState): Uint8Array;
decodeSyncState(data: Uint8Array): SyncState;
exportSyncState(state: SyncState): JsSyncState;
importSyncState(state: JsSyncState): SyncState;
}
export class Automerge {
// change state
put(obj: ObjID, prop: Prop, value: Value, datatype?: Datatype): void;
putObject(obj: ObjID, prop: Prop, value: ObjType): ObjID;
insert(obj: ObjID, index: number, value: Value, datatype?: Datatype): void;
insertObject(obj: ObjID, index: number, value: ObjType): ObjID;
push(obj: ObjID, value: Value, datatype?: Datatype): void;
pushObject(obj: ObjID, value: ObjType): ObjID;
splice(obj: ObjID, start: number, delete_count: number, text?: string | Array<Value>): ObjID[] | undefined;
increment(obj: ObjID, prop: Prop, value: number): void;
delete(obj: ObjID, prop: Prop): void;
// returns a single value - if there is a conflict return the winner
get(obj: ObjID, prop: Prop, heads?: Heads): Value | undefined;
getWithType(obj: ObjID, prop: Prop, heads?: Heads): FullValue | null;
// return all values in case of a conflict
getAll(obj: ObjID, arg: Prop, heads?: Heads): FullValueWithId[];
keys(obj: ObjID, heads?: Heads): string[];
text(obj: ObjID, heads?: Heads): string;
length(obj: ObjID, heads?: Heads): number;
materialize(obj?: ObjID, heads?: Heads): MaterializeValue;
// transactions
commit(message?: string, time?: number): Hash;
merge(other: Automerge): Heads;
getActorId(): Actor;
pendingOps(): number;
rollback(): number;
// patches
enablePatches(enable: boolean): void;
popPatches(): Patch[];
// save and load to local store
save(): Uint8Array;
saveIncremental(): Uint8Array;
loadIncremental(data: Uint8Array): number;
// sync over network
receiveSyncMessage(state: SyncState, message: SyncMessage): void;
generateSyncMessage(state: SyncState): SyncMessage | null;
// low level change functions
applyChanges(changes: Change[]): void;
getChanges(have_deps: Heads): Change[];
getChangeByHash(hash: Hash): Change | null;
getChangesAdded(other: Automerge): Change[];
getHeads(): Heads;
getLastLocalChange(): Change | null;
getMissingDeps(heads?: Heads): Heads;
// memory management
free(): void;
clone(actor?: string): Automerge;
fork(actor?: string): Automerge;
forkAt(heads: Heads, actor?: string): Automerge;
// dump internal state to console.log
dump(): void;
}
export class JsSyncState {
sharedHeads: Heads;
lastSentHeads: Heads;
theirHeads: Heads | undefined;
theirHeed: Heads | undefined;
theirHave: SyncHave[] | undefined;
sentHashes: Heads;
}
export class SyncState {
free(): void;
clone(): SyncState;
lastSentHeads: Heads;
sentHashes: Heads;
readonly sharedHeads: Heads;
}
export function init (): Promise<API>;

View file

@ -0,0 +1,18 @@
{
"collaborators": [
"Orion Henry <orion@inkandswitch.com>"
],
"name": "automerge-types",
"description": "typescript types for low level automerge api",
"homepage": "https://github.com/automerge/automerge-rs/tree/main/automerge-wasm",
"repository": "github:automerge/automerge-rs",
"version": "0.1.5",
"license": "MIT",
"files": [
"LICENSE",
"package.json",
"index.d.ts"
],
"types": "index.d.ts",
"main": ""
}

View file

@ -0,0 +1,49 @@
export {
loadDoc as load,
create,
encodeChange,
decodeChange,
initSyncState,
encodeSyncMessage,
decodeSyncMessage,
encodeSyncState,
decodeSyncState,
exportSyncState,
importSyncState,
} from "./bindgen.js"
import {
loadDoc as load,
create,
encodeChange,
decodeChange,
initSyncState,
encodeSyncMessage,
decodeSyncMessage,
encodeSyncState,
decodeSyncState,
exportSyncState,
importSyncState,
} from "./bindgen.js"
let api = {
load,
create,
encodeChange,
decodeChange,
initSyncState,
encodeSyncMessage,
decodeSyncMessage,
encodeSyncState,
decodeSyncState,
exportSyncState,
importSyncState
}
import wasm_init from "./bindgen.js"
export function init() {
return new Promise((resolve,reject) => wasm_init().then(() => {
resolve({ ... api, load, create })
}))
}

View file

@ -3,11 +3,18 @@ set -e
THIS_SCRIPT=$(dirname "$0");
WASM_PROJECT=$THIS_SCRIPT/../../automerge-wasm;
JS_PROJECT=$THIS_SCRIPT/../../automerge-js;
E2E_PROJECT=$THIS_SCRIPT/../../automerge-js/e2e;
yarn --cwd $E2E_PROJECT install;
# This will build the automerge-wasm project, publish it to a local NPM
# repository, then run `yarn build` in the `automerge-js` directory with
# the local registry
yarn --cwd $E2E_PROJECT e2e buildjs;
yarn --cwd $JS_PROJECT test
yarn --cwd $WASM_PROJECT install;
# This will take care of running wasm-pack
yarn --cwd $WASM_PROJECT build;
# If the dependencies are already installed we delete automerge-wasm. This makes
# this script usable for iterative development.
if [ -d $JS_PROJECT/node_modules/automerge-wasm ]; then
rm -rf $JS_PROJECT/node_modules/automerge-wasm
fi
# --check-files forces yarn to check if the local dep has changed
yarn --cwd $JS_PROJECT install --check-files;
yarn --cwd $JS_PROJECT test;