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

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
@@ -18,7 +18,8 @@ node examples/node.mjs
18
18
 
19
19
  `npm run build` runs `wasm-pack` in release mode with the locked Cargo
20
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
21
+ `vite-plugin-dts` generates type declarations in `dist/`. The runtime dependency
22
+ `decimal.js` supplies exact JS decimal values. To create a local npm
22
23
  tarball, run `npm pack`; it builds first and includes the WASM artifact.
23
24
  Installing that tarball does not require Rust or wasm-pack.
24
25
 
@@ -86,6 +87,8 @@ can be retried.
86
87
 
87
88
  - Browsers fetch the WASM next to the generated JS. Serve `dist/` over HTTP;
88
89
  retain the `dist/wasm/` directory and serve `.wasm` as `application/wasm`.
90
+ When loading without a bundler, provide an import map for `decimal.js`;
91
+ `examples/browser.html` maps it to the local npm installation.
89
92
  - Node.js uses the package's conditional export to read the same packaged
90
93
  WASM from disk. Browser entry points contain no Node.js imports.
91
94
  - `init(source)` also accepts a URL string, `URL`, `ArrayBuffer`, `Uint8Array`,
@@ -119,6 +122,7 @@ CommonJS entry point or polyfills are supplied.
119
122
  | `null`, `boolean`, `string` | Null, Bool, UTF-8 text |
120
123
  | `number` | Float, including `-0`, NaN, and infinities |
121
124
  | `bigint` | Int in `[-2^64, 2^64 - 1]` |
125
+ | `Decimal` from `decimal.js` | Exact Decimal with a 96-bit coefficient and scale 0–28 |
122
126
  | `Uint8Array` (including Node Buffer) | Binary string, copied |
123
127
  | Dense arrays | Arrays, copied recursively |
124
128
  | Plain or null-prototype objects | Maps with string keys |
@@ -137,8 +141,8 @@ Unsupported values are rejected: `undefined`, functions, symbols (including
137
141
  enumerable symbol map keys), sparse arrays, cyclic references, accessor
138
142
  properties used as document values, out-of-range integers, unpaired UTF-16
139
143
  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.
144
+ instances other than Decimal. Use values from the same JS realm as the library;
145
+ custom prototypes and proxies are not supported as a document model.
142
146
 
143
147
  Rust and conversion errors are JS `Error` instances with `kind` equal to
144
148
  `Internal`, `InvalidData`, or `UnexpectedType`. Initialization, lifecycle,
@@ -153,15 +157,58 @@ try {
153
157
  }
154
158
  ```
155
159
 
156
- The JS package exposes `init`, `supportedProtocols`, `Producer`, and `Consumer`
160
+ The JS package exposes `init`, `supportedProtocols`, `Producer`, `Consumer`, and `Decimal`
157
161
  at runtime.
158
162
  Rust's lower-level Document, Value, and Message APIs remain internal to this
159
163
  binding, and the wire protocol is shared with native Rust.
160
164
 
165
+ ### Decimal values
166
+
167
+ ```js
168
+ import { init, Producer, Consumer, Decimal, supportedProtocols } from "@doxsync/core";
169
+
170
+ await init();
171
+ const producer = new Producer({ price: new Decimal("12.34") }, supportedProtocols());
172
+ const consumer = new Consumer();
173
+ try {
174
+ consumer.consumeDiff(producer.produceDiff());
175
+ console.log(consumer.document().price.toFixed(2)); // "12.34"
176
+ } finally {
177
+ producer.free();
178
+ consumer.free();
179
+ }
180
+ ```
181
+
182
+ Pass decimal strings to the Decimal constructor to avoid introducing binary
183
+ floating-point rounding before synchronization. Instances imported directly from
184
+ `decimal.js` or created by `Decimal.clone()` are also accepted. Values must be
185
+ finite and exactly representable using a sign and a 96-bit unsigned coefficient
186
+ divided by `10^scale`, with scale from 0 through 28. Invalid values throw `InvalidData`;
187
+ conversion never silently rounds. Input `number` values remain Float values.
188
+
189
+ Protocol 2 returns decimal.js instances, constructed independently of the user's
190
+ global decimal.js configuration with 29 significant digits of arithmetic
191
+ precision and an exponent range of -100 through 100. Their initial values are
192
+ exact. Arithmetic follows decimal.js precision rules; synchronization does not
193
+ implement arithmetic or recover precision already lost by the caller.
194
+
195
+ Rust preserves scale: `Decimal::from_str_exact("1.20")` remains distinct from
196
+ `Decimal::from_str_exact("1.2")` when wrapped with `Value::decimal`. It also
197
+ provides `as_decimal`, `as_decimal_and_modify`, and `value!` conversions for owned
198
+ or borrowed Decimal values. Negative zero is normalized without changing scale.
199
+ JS decimal.js instances do not retain trailing zeros: a Rust value of `1.20`
200
+ read through `document()` and sent back through JS may become `1.2`.
201
+
202
+ With a protocol 1 producer (`new Producer(value, [1])`), decimals arrive as
203
+ ordinary strings. Rust preserves the source scale in those strings; JS can only
204
+ send the scale still present in its decimal.js value. Strings are never
205
+ automatically parsed as decimals. Switching between a Decimal and its identical
206
+ fallback string produces no change under protocol 1.
207
+
161
208
  ### Protocol negotiation
162
209
 
163
210
  Call `supportedProtocols()` after `await init()` to get all protocol versions
164
- supported by the local build, currently `[1]`. Each call returns an independent
211
+ supported by the local build, currently `[1, 2]`. Each call returns an independent
165
212
  ordinary `number[]`; modifying it does not affect subsequent calls or the engine.
166
213
  No producer or consumer instance is needed. Rust callers can use
167
214
  `doxsync::supported_protocols() -> &'static [u32]` without initialization.
@@ -174,20 +221,47 @@ versions, not the version negotiated for an individual stream.
174
221
  The second `Producer` constructor argument is required: pass an array of protocol
175
222
  versions supported by the consumer, such as `[1]`. Versions must be integers in
176
223
  `[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.
224
+ order and duplicates do not matter. Versions `1` and `2` are implemented.
178
225
  Empty or incompatible lists, omitted arguments, and invalid values throw an error
179
226
  with `kind: "InvalidData"`.
180
227
 
181
228
  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.
229
+ by the selected version; both are unsigned variable-length integers.
183
230
  Later messages omit this declaration and retain the same protocol for the stream.
184
231
  The consumer rejects first messages without a leading protocol declaration,
185
232
  unsupported versions, and repeated declarations. Legacy messages without version
186
- metadata are no longer accepted. Both peers must be upgraded together.
233
+ metadata are not accepted. Existing protocol 1 peers can continue to receive
234
+ updates, with decimals represented as strings.
187
235
 
188
236
  Failed encoding or consumption preserves protocol initialization state as well as
189
237
  pool state, allowing retries. Keep and deliver packed messages in order as usual.
190
238
 
239
+ ### Decimal wire encoding
240
+
241
+ Protocol 2 represents `sign × coefficient × 10^-scale`. The first byte's high
242
+ nibble is `0x9` for nonnegative decimals or `0xA` for negative decimals; `0x6`
243
+ remains reserved. Its low nibble directly contains scales 0–14. A low nibble of
244
+ `0xF` is followed by one byte containing the complete scale, 15–28.
245
+
246
+ The absolute coefficient follows as minimal unsigned LEB128: seven data bits
247
+ per byte, least significant group first, with the high bit indicating continuation.
248
+ Zero uses one `00` byte. There is no ZigZag transform or negative offset.
249
+ Coefficients occupy 1–14 bytes and must fit in 96 bits. The fourteenth byte
250
+ must have its continuation bit clear and contain at most `0x1F`.
251
+
252
+ | Value | Hex bytes (excluding the message header) |
253
+ | --- | --- |
254
+ | `0.01` | `92 01` |
255
+ | `1.20` | `92 78` |
256
+ | `12.34` | `92 D2 09` |
257
+ | `-12.34` | `A2 D2 09` |
258
+ | `0.00` | `92 00` |
259
+ | `0.000000000000001` | `9F 0F 01` |
260
+
261
+ The maximum value encoding length is 16 bytes. Decoders reject negative zero,
262
+ non-minimal coefficients, invalid scales, overflow, truncation, and Decimal
263
+ tags in protocol 1. Existing Int encoding is unchanged.
264
+
191
265
  ### Path key encoding
192
266
 
193
267
  Protocol v1 path definitions use the low two bits of each segment's unsigned
@@ -205,3 +279,31 @@ through message encoding and are resolved when the consumer reads the definition
205
279
 
206
280
  This extends v1 directly. Both peers must be upgraded together: older decoders
207
281
  reject the new `0b10` path segment tag.
282
+
283
+ ### Compact floating-point values
284
+
285
+ Protocol 2 encodes numbers as binary16, binary32, or
286
+ binary64, choosing the shortest representation that preserves the original
287
+ binary64 bits. Decoded values remain JavaScript numbers. Signed zero is
288
+ preserved; NaNs retain binary64 encoding. Protocol 1 continues to use
289
+ binary64 for every number.
290
+
291
+ ### Binary value reuse
292
+
293
+ Protocol 2 pools binary values by content, so repeated `Uint8Array` values can
294
+ share a compact reference within a message and across subsequent messages.
295
+ Producers admit payloads from 16 bytes through 64 KiB, using up to 256 slots
296
+ and at most 1 MiB of definitions per message. Values that cannot be admitted
297
+ remain inline. The first definition has an overhead, so pooling does not make
298
+ every message smaller.
299
+
300
+ The Rust pool shares immutable payload storage. Each `document()` conversion
301
+ still returns independent `Uint8Array` copies: modifying one field cannot change
302
+ another field, the consumer's document, or a later message.
303
+
304
+ This extends unreleased protocol 2 without adding a version. New consumers accept
305
+ earlier v2 messages, but earlier v2 consumers cannot read binary pool definitions
306
+ or references. Upgrade both v2 peers together; explicitly select `[1]` when
307
+ communicating with an implementation that lacks this extension. Version
308
+ negotiation alone cannot distinguish these v2 builds. Protocol 1 continues to
309
+ encode binary values inline.
@@ -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].
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 and 2 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; protocol 2 preserves the type.
17
+ * Protocol 2 also pools 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 or 2.
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 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.3",
4
4
  "description": "JavaScript bindings for the doxsync Rust synchronization engine",
5
5
  "type": "module",
6
6
  "engines": {
@@ -37,5 +37,8 @@
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 };