@doxsync/core 0.1.0-alpha.2 → 0.1.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,207 +1,96 @@
1
1
  # @doxsync/core
2
2
 
3
- ESM bindings for the doxsync Rust synchronization engine, for modern browsers
4
- and Node.js 22+. Rust owns documents, diffing, message encoding, decoding, and
5
- state. JavaScript provides loading and a small synchronous API.
3
+ Synchronize documents with compact updates in modern browsers and Node.js 22+.
4
+ ESM and TypeScript are supported; your application supplies the transport.
6
5
 
7
- ## Build from this repository
8
-
9
- Install Rust, Node.js 22.12+ (or 24+), and `wasm-pack`, then run:
10
-
11
- ```sh
12
- rustup target add wasm32-unknown-unknown
13
- cd doxsync-js
14
- npm ci
15
- npm run build
16
- node examples/node.mjs
17
- ```
18
-
19
- `npm run build` runs `wasm-pack` in release mode with the locked Cargo
20
- dependencies and the `wasm` feature, then builds the TypeScript wrapper with Vite in library mode.
21
- `vite-plugin-dts` generates type declarations in `dist/`. There are no npm runtime dependencies. To create a local npm
22
- tarball, run `npm pack`; it builds first and includes the WASM artifact.
23
- Installing that tarball does not require Rust or wasm-pack.
24
-
25
- The Rust crate continues to support native use without JS dependencies:
6
+ ## Install
26
7
 
27
8
  ```sh
28
- cargo build --manifest-path ../doxsync-rs/Cargo.toml
29
- cargo build --manifest-path ../doxsync-rs/Cargo.toml \
30
- --target wasm32-unknown-unknown --features wasm --release
9
+ npm install @doxsync/core
31
10
  ```
32
11
 
33
- The second command produces the Rust WASM artifact. Use `npm run build` to
34
- also generate the JS glue required to load it.
35
-
36
- ## Synchronize a document
37
-
38
- After installing the package from the local tarball:
12
+ ## Quick start
39
13
 
40
14
  ```js
41
15
  import { init, Producer, Consumer, supportedProtocols } from "@doxsync/core";
42
16
 
43
17
  await init();
44
- const producer = new Producer({ count: 1n, ratio: 0.5 }, supportedProtocols());
18
+ const producer = new Producer({ count: 1n }, supportedProtocols());
45
19
  const consumer = new Consumer();
46
20
 
47
21
  try {
48
- consumer.consumeDiff(producer.produceDiff()); // First message is a snapshot.
49
- producer.replace({ count: 2n, ratio: 0.75 });
22
+ consumer.consumeDiff(producer.produceDiff());
23
+ producer.replace({ count: 2n });
50
24
 
51
25
  const message = producer.produceDiff();
52
26
  if (message !== undefined) consumer.consumeDiff(message);
53
27
 
54
- console.log(consumer.document()); // { count: 2n, ratio: 0.75 }
55
- console.log(producer.produceDiff()); // undefined: no changes
28
+ console.log(consumer.document()); // { count: 2n }
56
29
  } finally {
57
30
  producer.free();
58
31
  consumer.free();
59
32
  }
60
33
  ```
61
34
 
62
- Each `consumeDiff` call takes one complete `Uint8Array` message. Transport is
63
- up to the application. Messages must be delivered reliably, exactly once, and
64
- in order to the corresponding consumer: the producer advances its baseline
65
- when it returns a message. Retain those bytes until delivery; calling
66
- `produceDiff` again does not retransmit them. For a new connection without
67
- shared state, create a new producer and consumer and start with a snapshot.
68
-
69
- `replace` copies and validates the input before changing the producer. It
70
- does not emit a message. `document()` returns `undefined` before a snapshot,
71
- and an independent JS value afterwards (including `null` for a null document).
72
- Mutating an input, returned document, or returned message does not change the
73
- engine's stored document. Invalid messages leave consumer state unchanged.
74
-
75
- Call `free()` when finished to release WASM resources promptly. It is
76
- idempotent; business methods throw after release. The generated bindings also
77
- provide garbage-collection cleanup, but its timing is not deterministic.
78
-
79
- ## WASM initialization
80
-
81
- Call and await `init()` before creating either class or calling
82
- `supportedProtocols()`. Initialization is the
83
- only asynchronous operation. Concurrent calls share the first call's promise
84
- and source; successful initialization is reused, and failed initialization
85
- can be retried.
86
-
87
- - Browsers fetch the WASM next to the generated JS. Serve `dist/` over HTTP;
88
- retain the `dist/wasm/` directory and serve `.wasm` as `application/wasm`.
89
- - Node.js uses the package's conditional export to read the same packaged
90
- WASM from disk. Browser entry points contain no Node.js imports.
91
- - `init(source)` also accepts a URL string, `URL`, `ArrayBuffer`, `Uint8Array`,
92
- or compiled `WebAssembly.Module`. Strings are URLs, not filesystem paths.
93
- Node.js additionally reads `file:` URLs from disk.
94
- - Bundlers must deploy the WASM asset. If their asset handling changes its
95
- location, pass the deployed URL or bytes explicitly; the package exports
96
- the asset as `@doxsync/core/doxsync.wasm` for resolution by build tools.
97
-
98
- For a browser example, serve this directory and open `/examples/browser.html`:
35
+ ## API and delivery
99
36
 
100
- ```sh
101
- python3 -m http.server 8000
102
- ```
37
+ - `await init(source?)`: initialize once before using the API. Other calls are synchronous.
38
+ - `supportedProtocols()`: get supported versions. For remote peers, pass the
39
+ **consumer's** advertised list to `new Producer(value, protocols)`.
40
+ - `producer.replace(value)`: validate and copy a new document without sending it.
41
+ - `producer.produceDiff()`: return a `Uint8Array`, or `undefined` if unchanged.
42
+ The first message contains the complete document.
43
+ - `consumer.consumeDiff(message)`: apply one complete message. Invalid messages
44
+ throw and leave the consumer unchanged.
45
+ - `consumer.document()`: return an independent copy, or `undefined` before the first message.
46
+ - `free()`: release each instance when finished.
47
+
48
+ Deliver messages exactly once and in order to the matching consumer. Keep each
49
+ message until delivered: calling `produceDiff()` again does not retransmit it.
50
+ If either side loses its session state, create a new producer and consumer.
51
+
52
+ ## Supported values
103
53
 
104
- The browser demo changes the producer randomly every 1.5 seconds and delivers
105
- messages after a simulated 1-second delay. It shows both documents, highlights
106
- updated lines, and lists the actual message bytes and delivery status. Adjust
107
- the delay or update interval, pause automatic changes, step manually, or reset
108
- the session. Pausing lets queued messages finish; changing latency never
109
- reorders messages. Some iterations deliberately leave the document unchanged
110
- to demonstrate that no message is produced.
111
-
112
- Browsers must support WebAssembly, BigInt, ESM, and private class fields. No
113
- CommonJS entry point or polyfills are supplied.
114
-
115
- ## Values and errors
116
-
117
- | JavaScript | doxsync |
118
- | --- | --- |
119
- | `null`, `boolean`, `string` | Null, Bool, UTF-8 text |
120
- | `number` | Float, including `-0`, NaN, and infinities |
121
- | `bigint` | Int in `[-2^64, 2^64 - 1]` |
122
- | `Uint8Array` (including Node Buffer) | Binary string, copied |
123
- | Dense arrays | Arrays, copied recursively |
124
- | Plain or null-prototype objects | Maps with string keys |
125
-
126
- `42` and `42n` are different document values. Integers from Rust always read
127
- back as `bigint`, including small integers. Floating-point NaN values remain
128
- NaN, but JS does not promise to preserve NaN payload bits.
129
-
130
- Maps use own enumerable string properties, not inherited or non-enumerable
131
- properties. Output maps are ordinary objects; keys such as `__proto__` are
132
- defined as safe own data properties. Arrays serialize their indexed elements;
133
- extra named properties are not part of the document. Object identity and
134
- insertion order are not preserved. Shared references are copied by value.
135
-
136
- Unsupported values are rejected: `undefined`, functions, symbols (including
137
- enumerable symbol map keys), sparse arrays, cyclic references, accessor
138
- properties used as document values, out-of-range integers, unpaired UTF-16
139
- surrogates in strings or keys, and objects such as Date, Map, Set, or class
140
- instances. Use values from the same JS realm as the library; custom prototypes
141
- and proxies are not supported as a document model.
142
-
143
- Rust and conversion errors are JS `Error` instances with `kind` equal to
144
- `Internal`, `InvalidData`, or `UnexpectedType`. Initialization, lifecycle,
145
- and host JavaScript exceptions may be ordinary errors without `kind`.
146
- TypeScript declarations include `SyncValue`, `InitSource`, and `DoxsyncError`.
54
+ Use `null`, booleans, strings, numbers, `bigint`, `Uint8Array`, dense arrays,
55
+ and plain objects with string keys. Nested values are copied by value.
56
+ `number` and `bigint` are distinct; integers must be in `[-2^64, 2^64 - 1]`.
57
+ `undefined`, cycles, sparse arrays, getters, functions, and objects such as
58
+ `Date`, `Map`, or custom class instances are not supported.
59
+
60
+ For exact decimals, import `Decimal` from this package and use strings:
147
61
 
148
62
  ```js
149
- try {
150
- consumer.consumeDiff(receivedBytes);
151
- } catch (error) {
152
- console.error(error.kind, error.message);
153
- }
63
+ import { Decimal } from "@doxsync/core";
64
+ const price = new Decimal("12.34");
65
+ ```
66
+
67
+ Decimals must be finite, fit a 96-bit coefficient, and have scale 0–28;
68
+ unsupported values are rejected without rounding. Trailing zeros are not preserved.
69
+ Protocol 1 delivers decimals as strings; protocols 2 and 3 preserve Decimal values.
70
+ The producer automatically selects the highest version supported by both peers.
71
+
72
+ Errors may expose `kind`: `InvalidData`, `UnexpectedType`, or `Internal`.
73
+ Initialization and lifecycle errors may be ordinary JavaScript errors.
74
+
75
+ ## Browser setup
76
+
77
+ Deploy the packaged WASM asset alongside the JavaScript. If your bundler relocates
78
+ it, pass its deployed URL or bytes to `init(source)`. The asset is exported as
79
+ `@doxsync/core/doxsync.wasm`; serve `.wasm` files as `application/wasm`.
80
+ Node.js loads the packaged asset automatically. Without a bundler, also provide
81
+ an import map for `decimal.js`; see the [browser example](examples/browser.html).
82
+
83
+ ## Build from source
84
+
85
+ Requires Rust, wasm-pack, Node.js 22.12+ (or 24+), and pnpm 9.15.0.
86
+
87
+ ```sh
88
+ rustup target add wasm32-unknown-unknown
89
+ cd doxsync-js
90
+ pnpm install --frozen-lockfile
91
+ pnpm build
92
+ pnpm example
154
93
  ```
155
94
 
156
- The JS package exposes `init`, `supportedProtocols`, `Producer`, and `Consumer`
157
- at runtime.
158
- Rust's lower-level Document, Value, and Message APIs remain internal to this
159
- binding, and the wire protocol is shared with native Rust.
160
-
161
- ### Protocol negotiation
162
-
163
- Call `supportedProtocols()` after `await init()` to get all protocol versions
164
- supported by the local build, currently `[1]`. Each call returns an independent
165
- ordinary `number[]`; modifying it does not affect subsequent calls or the engine.
166
- No producer or consumer instance is needed. Rust callers can use
167
- `doxsync::supported_protocols() -> &'static [u32]` without initialization.
168
-
169
- For peers running in separate processes, have the consumer advertise its list
170
- and pass that received list to the producer. The examples above use the local
171
- list because both peers share the same build. This query reports supported
172
- versions, not the version negotiated for an individual stream.
173
-
174
- The second `Producer` constructor argument is required: pass an array of protocol
175
- versions supported by the consumer, such as `[1]`. Versions must be integers in
176
- `[0, 2^32 - 1]`. The producer selects the highest version supported by both peers;
177
- order and duplicates do not matter. Currently only version `1` is implemented.
178
- Empty or incompatible lists, omitted arguments, and invalid values throw an error
179
- with `kind: "InvalidData"`.
180
-
181
- The first packed message starts its metadata with protocol instruction `2` followed
182
- by the selected version, currently `1`; both are unsigned variable-length integers.
183
- Later messages omit this declaration and retain the same protocol for the stream.
184
- The consumer rejects first messages without a leading protocol declaration,
185
- unsupported versions, and repeated declarations. Legacy messages without version
186
- metadata are no longer accepted. Both peers must be upgraded together.
187
-
188
- Failed encoding or consumption preserves protocol initialization state as well as
189
- pool state, allowing retries. Keep and deliver packed messages in order as usual.
190
-
191
- ### Path key encoding
192
-
193
- Protocol v1 path definitions use the low two bits of each segment's unsigned
194
- variable-length integer as a tag: `0b00` carries a UTF-8 byte length followed by
195
- the key bytes, `0b01` carries an array index, and `0b10` carries a string pool
196
- index. The payload is shifted left by two bits before adding the tag; `0b11`
197
- is reserved and rejected.
198
-
199
- A cached key uses `0b10` only when its reference is strictly shorter than its
200
- literal encoding. Uncached keys remain inline without adding string pool entries.
201
- The initial snapshot populates the string pool, so subsequent path definitions
202
- can reuse those keys. Once a whole path is cached, actions reuse its path pool ID.
203
- String pool updates precede path definitions; referenced strings stay resident
204
- through message encoding and are resolved when the consumer reads the definition.
205
-
206
- This extends v1 directly. Both peers must be upgraded together: older decoders
207
- reject the new `0b10` path segment tag.
95
+ To run the browser example, serve `doxsync-js/` with `python3 -m http.server 8000`
96
+ and open `/packages/core/examples/browser.html`.
@@ -0,0 +1,3 @@
1
+ import { Decimal } from 'decimal.js';
2
+ export declare function decimalToString(value: unknown): string | undefined;
3
+ export declare function decimalFromString(value: string): Decimal;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { InitSource } from './types.js';
2
2
  export type { SyncValue, InitSource, DoxsyncError } from './types.js';
3
+ export { Decimal } from 'decimal.js';
3
4
  export { Producer, Consumer, supportedProtocols } from './runtime.js';
4
5
  /**
5
6
  * Initialize once before constructing Producer or Consumer or calling supportedProtocols.
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
- import { i as e, n as t, r as n, t as r } from "./runtime-CJqt5vND.js";
1
+ import { i as e, n as t, r as n, t as r } from "./runtime-BePF61JJ.js";
2
+ import { Decimal as i } from "decimal.js";
2
3
  //#region src/index.ts
3
- function i(e) {
4
+ function a(e) {
4
5
  return n(() => e === void 0 ? new URL(
5
6
  /* @vite-ignore */
6
7
  "./wasm/doxsync_bg.wasm",
@@ -8,4 +9,4 @@ function i(e) {
8
9
  ) : e);
9
10
  }
10
11
  //#endregion
11
- export { r as Consumer, t as Producer, i as init, e as supportedProtocols };
12
+ export { r as Consumer, i as Decimal, t as Producer, a as init, e as supportedProtocols };
package/dist/node.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { InitSource } from './types.js';
2
2
  export type { SyncValue, InitSource, DoxsyncError } from './types.js';
3
+ export { Decimal } from 'decimal.js';
3
4
  export { Producer, Consumer, supportedProtocols } from './runtime.js';
4
5
  /**
5
6
  * Initialize once before constructing Producer or Consumer or calling supportedProtocols.
package/dist/node.js CHANGED
@@ -1,12 +1,13 @@
1
- import { i as e, n as t, r as n, t as r } from "./runtime-CJqt5vND.js";
2
- import { readFile as i } from "node:fs/promises";
1
+ import { i as e, n as t, r as n, t as r } from "./runtime-BePF61JJ.js";
2
+ import { Decimal as i } from "decimal.js";
3
+ import { readFile as a } from "node:fs/promises";
3
4
  //#region src/node.ts
4
- function a(e) {
5
- return n(() => e === void 0 ? i(new URL(
5
+ function o(e) {
6
+ return n(() => e === void 0 ? a(new URL(
6
7
  /* @vite-ignore */
7
8
  "./wasm/doxsync_bg.wasm",
8
9
  import.meta.url
9
- )) : e instanceof URL && e.protocol === "file:" ? i(e) : typeof e == "string" && e.startsWith("file:") ? i(new URL(e)) : e);
10
+ )) : e instanceof URL && e.protocol === "file:" ? a(e) : typeof e == "string" && e.startsWith("file:") ? a(new URL(e)) : e);
10
11
  }
11
12
  //#endregion
12
- export { r as Consumer, t as Producer, a as init, e as supportedProtocols };
13
+ export { r as Consumer, i as Decimal, t as Producer, o as init, e as supportedProtocols };
@@ -0,0 +1,72 @@
1
+ import { Decimal as e } from "decimal.js";
2
+ import t, { Consumer as n, Producer as r, supportedProtocols as i } from "./wasm/doxsync.js";
3
+ //#region src/decimal.ts
4
+ var a = e.clone({
5
+ precision: 29,
6
+ minE: -100,
7
+ maxE: 100
8
+ });
9
+ function o(t) {
10
+ if (e.isDecimal(t)) {
11
+ if (!t.isFinite() || t.decimalPlaces() > 28 || t.e > 28) throw Object.assign(/* @__PURE__ */ Error("Decimal outside the supported coefficient or scale range"), { kind: "InvalidData" });
12
+ return t.toFixed();
13
+ }
14
+ }
15
+ function s(e) {
16
+ return new a(e);
17
+ }
18
+ //#endregion
19
+ //#region src/runtime.ts
20
+ var c, l = !1;
21
+ function u(e) {
22
+ return c ||= Promise.resolve().then(e).then((e) => t({ module_or_path: e })).then(() => {
23
+ l = !0;
24
+ }).catch((e) => {
25
+ throw c = void 0, e;
26
+ }), c;
27
+ }
28
+ function d() {
29
+ if (!l) throw Error("Call and await init() before using the doxsync API");
30
+ }
31
+ function f() {
32
+ return d(), i();
33
+ }
34
+ var p = class {
35
+ #e;
36
+ constructor(e, t) {
37
+ d(), this.#e = new r(e, t, o);
38
+ }
39
+ #t() {
40
+ if (!this.#e) throw Error("Producer has been freed");
41
+ return this.#e;
42
+ }
43
+ replace(e) {
44
+ this.#t().replace(e);
45
+ }
46
+ produceDiff() {
47
+ return this.#t().produceDiff();
48
+ }
49
+ free() {
50
+ this.#e &&= (this.#e.free(), void 0);
51
+ }
52
+ }, m = class {
53
+ #e;
54
+ constructor() {
55
+ d(), this.#e = new n();
56
+ }
57
+ #t() {
58
+ if (!this.#e) throw Error("Consumer has been freed");
59
+ return this.#e;
60
+ }
61
+ consumeDiff(e) {
62
+ this.#t().consumeDiff(e);
63
+ }
64
+ document() {
65
+ return this.#t().document(s);
66
+ }
67
+ free() {
68
+ this.#e &&= (this.#e.free(), void 0);
69
+ }
70
+ };
71
+ //#endregion
72
+ export { f as i, p as n, u as r, m as t };
package/dist/runtime.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { InitSource, SyncValue } from './types.js';
2
2
  export declare function initialize(loadSource: () => InitSource | Promise<InitSource>): Promise<void>;
3
3
  /**
4
- * Returns all protocol versions supported by this build, currently [1].
4
+ * Returns all protocol versions supported by this build, currently [1, 2, 3].
5
5
  * Requires await init(). Each call returns an independent ordinary array.
6
6
  * Consumers can advertise this list to producers for protocol negotiation.
7
7
  */
@@ -11,8 +11,10 @@ export declare class Producer {
11
11
  /**
12
12
  * Copies the value into WASM and selects the highest common protocol version.
13
13
  * protocols lists the consumer's supported versions as u32 integers.
14
- * Currently only 1 is supported. Missing, invalid, empty, or incompatible
14
+ * Versions 1, 2, and 3 are supported. Missing, invalid, empty, or incompatible
15
15
  * lists throw InvalidData errors. Order and duplicates do not matter.
16
+ * Protocol 1 transmits Decimal values as strings; protocols 2 and 3 preserve the type.
17
+ * Protocols 2 and 3 also pool binary values; both peers must support that extension.
16
18
  */
17
19
  constructor(value: SyncValue, protocols: readonly number[]);
18
20
  /** Replaces the document after validating and copying the entire value. */
@@ -29,11 +31,13 @@ export declare class Consumer {
29
31
  #private;
30
32
  constructor();
31
33
  /**
32
- * Applies one complete message. The first message must declare protocol 1.
34
+ * Applies one complete message. The first message must declare protocol 1, 2, or 3.
33
35
  * Failures leave the document, pools, and protocol state intact.
34
36
  */
35
37
  consumeDiff(bytes: Uint8Array): void;
36
- /** Returns an independent copy, or undefined before the first snapshot. */
38
+ /** Returns an independent copy, or undefined before the first snapshot.
39
+ * Protocol 2 and 3 decimals become decimal.js instances; their original scale is lost.
40
+ */
37
41
  document(): SyncValue | undefined;
38
42
  /** Release WASM resources. Safe to call repeatedly. */
39
43
  free(): void;
package/dist/types.d.ts CHANGED
@@ -1,5 +1,8 @@
1
- /** Numbers are floats; bigint values are integers in [-2^64, 2^64 - 1]. */
2
- export type SyncValue = null | boolean | string | number | bigint | Uint8Array | SyncValue[] | {
1
+ import { Decimal } from 'decimal.js';
2
+ /** Numbers are floats; bigint values are integers in [-2^64, 2^64 - 1].
3
+ * Decimal instances preserve numeric value, but not trailing zeros.
4
+ */
5
+ export type SyncValue = null | boolean | string | number | bigint | Decimal | Uint8Array | SyncValue[] | {
3
6
  [key: string]: SyncValue;
4
7
  };
5
8
  export type InitSource = string | URL | ArrayBuffer | Uint8Array | WebAssembly.Module;
@@ -5,14 +5,14 @@ export class Consumer {
5
5
  free(): void;
6
6
  [Symbol.dispose](): void;
7
7
  consumeDiff(bytes: any): void;
8
- document(): any;
8
+ document(decimal_from_string: Function): any;
9
9
  constructor();
10
10
  }
11
11
 
12
12
  export class Producer {
13
13
  free(): void;
14
14
  [Symbol.dispose](): void;
15
- constructor(value: any, protocols: any);
15
+ constructor(value: any, protocols: any, decimal_to_string: Function);
16
16
  produceDiff(): any;
17
17
  replace(value: any): void;
18
18
  }
@@ -26,9 +26,9 @@ export interface InitOutput {
26
26
  readonly __wbg_consumer_free: (a: number, b: number) => void;
27
27
  readonly __wbg_producer_free: (a: number, b: number) => void;
28
28
  readonly consumer_consumeDiff: (a: number, b: any) => [number, number];
29
- readonly consumer_document: (a: number) => [number, number, number];
29
+ readonly consumer_document: (a: number, b: any) => [number, number, number];
30
30
  readonly consumer_new: () => number;
31
- readonly producer_new: (a: any, b: any) => [number, number, number];
31
+ readonly producer_new: (a: any, b: any, c: any) => [number, number, number];
32
32
  readonly producer_produceDiff: (a: number) => [number, number, number];
33
33
  readonly producer_replace: (a: number, b: any) => [number, number];
34
34
  readonly supportedProtocols: () => any;
@@ -21,10 +21,11 @@ export class Consumer {
21
21
  }
22
22
  }
23
23
  /**
24
+ * @param {Function} decimal_from_string
24
25
  * @returns {any}
25
26
  */
26
- document() {
27
- const ret = wasm.consumer_document(this.__wbg_ptr);
27
+ document(decimal_from_string) {
28
+ const ret = wasm.consumer_document(this.__wbg_ptr, decimal_from_string);
28
29
  if (ret[2]) {
29
30
  throw takeFromExternrefTable0(ret[1]);
30
31
  }
@@ -53,9 +54,10 @@ export class Producer {
53
54
  /**
54
55
  * @param {any} value
55
56
  * @param {any} protocols
57
+ * @param {Function} decimal_to_string
56
58
  */
57
- constructor(value, protocols) {
58
- const ret = wasm.producer_new(value, protocols);
59
+ constructor(value, protocols, decimal_to_string) {
60
+ const ret = wasm.producer_new(value, protocols, decimal_to_string);
59
61
  if (ret[2]) {
60
62
  throw takeFromExternrefTable0(ret[1]);
61
63
  }
@@ -156,6 +158,10 @@ function __wbg_get_imports() {
156
158
  const ret = arg0.add(arg1);
157
159
  return ret;
158
160
  },
161
+ __wbg_call_6bcf8d3e20937e46: function() { return handleError(function (arg0, arg1, arg2) {
162
+ const ret = arg0.call(arg1, arg2);
163
+ return ret;
164
+ }, arguments); },
159
165
  __wbg_charCodeAt_90635349fbda9f5f: function(arg0, arg1) {
160
166
  const ret = arg0.charCodeAt(arg1 >>> 0);
161
167
  return ret;
Binary file
@@ -4,9 +4,9 @@ export const memory: WebAssembly.Memory;
4
4
  export const __wbg_consumer_free: (a: number, b: number) => void;
5
5
  export const __wbg_producer_free: (a: number, b: number) => void;
6
6
  export const consumer_consumeDiff: (a: number, b: any) => [number, number];
7
- export const consumer_document: (a: number) => [number, number, number];
7
+ export const consumer_document: (a: number, b: any) => [number, number, number];
8
8
  export const consumer_new: () => number;
9
- export const producer_new: (a: any, b: any) => [number, number, number];
9
+ export const producer_new: (a: any, b: any, c: any) => [number, number, number];
10
10
  export const producer_produceDiff: (a: number) => [number, number, number];
11
11
  export const producer_replace: (a: number, b: any) => [number, number];
12
12
  export const supportedProtocols: () => any;
@@ -5,6 +5,9 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>doxsync · Sync playground</title>
7
7
  <link rel="stylesheet" href="./browser.css">
8
+ <script type="importmap">
9
+ { "imports": { "decimal.js": "../node_modules/decimal.js/decimal.mjs" } }
10
+ </script>
8
11
  <script type="module" src="./browser.js"></script>
9
12
  </head>
10
13
  <body>
@@ -11,6 +11,7 @@ let loading = false;
11
11
  const initialDocument = () => ({
12
12
  title: "doxsync playground",
13
13
  visitors: 12n,
14
+ price: new bindings.Decimal("12.34"),
14
15
  online: true,
15
16
  settings: { theme: "light", volume: 60 },
16
17
  tags: ["rust", "wasm"],
@@ -28,6 +29,7 @@ const delayValue = () => {
28
29
  // suffix preserves bigint's type instead of presenting it as a JSON string.
29
30
  function format(value, depth = 0) {
30
31
  if (typeof value === "bigint") return `${value}n`;
32
+ if (bindings.Decimal.isDecimal(value)) return `Decimal("${value.toFixed()}")`;
31
33
  if (value === null || typeof value !== "object") return JSON.stringify(value);
32
34
  const array = Array.isArray(value);
33
35
  const entries = array ? value : Object.keys(value).sort();
@@ -219,6 +221,7 @@ function send(summary) {
219
221
  }
220
222
 
221
223
  const mutations = [
224
+ (doc) => { doc.price = doc.price.plus("0.01"); return `price → ${doc.price.toFixed(2)}`; },
222
225
  (doc) => { const delta = BigInt(number(1, 5)); doc.visitors += delta; return `visitors increased by ${delta}`; },
223
226
  (doc) => { doc.online = !doc.online; return `online → ${doc.online}`; },
224
227
  (doc) => { doc.settings.theme = pick(["light", "dark", "system"].filter((value) => value !== doc.settings.theme)); return `settings.theme → ${doc.settings.theme}`; },
@@ -246,7 +249,13 @@ function mutate() {
246
249
  try {
247
250
  // Producer owns a copy. Keep this display model in step with successfully
248
251
  // accepted replacements; no JS implementation of the diff algorithm.
249
- const next = structuredClone(state.document);
252
+ // Copy this demo's containers explicitly: structuredClone cannot copy Decimal.
253
+ const next = {
254
+ ...state.document,
255
+ settings: { ...state.document.settings },
256
+ tags: [...state.document.tags],
257
+ price: new bindings.Decimal(state.document.price),
258
+ };
250
259
  const summary = pick(mutations)(next);
251
260
  state.producer.replace(next);
252
261
  state.document = next;
package/examples/node.mjs CHANGED
@@ -1,17 +1,18 @@
1
- import { init, Producer, Consumer, supportedProtocols } from "@doxsync/core";
1
+ import { init, Producer, Consumer, Decimal, supportedProtocols } from "@doxsync/core";
2
2
 
3
3
  await init();
4
- const producer = new Producer({ count: 1n, data: new Uint8Array([1, 2, 3]) }, supportedProtocols());
4
+ const producer = new Producer({ count: 1n, price: new Decimal("12.34"), data: new Uint8Array([1, 2, 3]) }, supportedProtocols());
5
5
  const consumer = new Consumer();
6
6
 
7
7
  try {
8
8
  consumer.consumeDiff(producer.produceDiff());
9
9
  console.log("Snapshot:", consumer.document());
10
10
 
11
- producer.replace({ count: 2n, data: new Uint8Array([1, 2, 3]) });
11
+ producer.replace({ count: 2n, price: new Decimal("12.35"), data: new Uint8Array([1, 2, 3]) });
12
12
  const message = producer.produceDiff();
13
13
  if (message !== undefined) consumer.consumeDiff(message);
14
14
  console.log("Updated:", consumer.document());
15
+ console.log("Exact price:", consumer.document().price.toFixed(2));
15
16
  console.log("Unchanged:", producer.produceDiff());
16
17
  } finally {
17
18
  producer.free();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doxsync/core",
3
- "version": "0.1.0-alpha.2",
3
+ "version": "0.1.0-alpha.4",
4
4
  "description": "JavaScript bindings for the doxsync Rust synchronization engine",
5
5
  "type": "module",
6
6
  "engines": {
@@ -24,18 +24,21 @@
24
24
  ],
25
25
  "scripts": {
26
26
  "build": "node scripts/build-wasm.mjs && vite build",
27
- "prepack": "npm run build"
27
+ "prepack": "pnpm run build"
28
28
  },
29
29
  "license": "MIT OR Apache-2.0",
30
30
  "repository": {
31
31
  "type": "git",
32
32
  "url": "git+https://github.com/PeterlitsZo/doxsync.git",
33
- "directory": "doxsync-js"
33
+ "directory": "doxsync-js/packages/core"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^22.20.4",
37
37
  "typescript": "^6.0.3",
38
38
  "vite": "^8.3.0",
39
39
  "vite-plugin-dts": "^5.1.1"
40
+ },
41
+ "dependencies": {
42
+ "decimal.js": "^10.6.0"
40
43
  }
41
44
  }
@@ -1,55 +0,0 @@
1
- import e, { Consumer as t, Producer as n, supportedProtocols as r } from "./wasm/doxsync.js";
2
- //#region src/runtime.ts
3
- var i, a = !1;
4
- function o(t) {
5
- return i ||= Promise.resolve().then(t).then((t) => e({ module_or_path: t })).then(() => {
6
- a = !0;
7
- }).catch((e) => {
8
- throw i = void 0, e;
9
- }), i;
10
- }
11
- function s() {
12
- if (!a) throw Error("Call and await init() before using the doxsync API");
13
- }
14
- function c() {
15
- return s(), r();
16
- }
17
- var l = class {
18
- #e;
19
- constructor(e, t) {
20
- s(), this.#e = new n(e, t);
21
- }
22
- #t() {
23
- if (!this.#e) throw Error("Producer has been freed");
24
- return this.#e;
25
- }
26
- replace(e) {
27
- this.#t().replace(e);
28
- }
29
- produceDiff() {
30
- return this.#t().produceDiff();
31
- }
32
- free() {
33
- this.#e &&= (this.#e.free(), void 0);
34
- }
35
- }, u = class {
36
- #e;
37
- constructor() {
38
- s(), this.#e = new t();
39
- }
40
- #t() {
41
- if (!this.#e) throw Error("Consumer has been freed");
42
- return this.#e;
43
- }
44
- consumeDiff(e) {
45
- this.#t().consumeDiff(e);
46
- }
47
- document() {
48
- return this.#t().document();
49
- }
50
- free() {
51
- this.#e &&= (this.#e.free(), void 0);
52
- }
53
- };
54
- //#endregion
55
- export { c as i, l as n, o as r, u as t };