Always init

This commit is contained in:
Andras Schmelczer 2025-07-07 22:30:33 +01:00
parent c38b7c9972
commit 70ef5cc0de
No known key found for this signature in database
GPG key ID: FC8F2C3D3D1A718C
4 changed files with 20 additions and 59 deletions

View file

@ -59,9 +59,6 @@ Then use in your application:
```javascript ```javascript
import { init, reconcile } from 'reconcile'; import { init, reconcile } from 'reconcile';
// One-time setup: initialise the WASM module
await init();
// Same example as above // Same example as above
const parent = 'Hello world'; const parent = 'Hello world';
const left = 'Hello beautiful world'; const left = 'Hello beautiful world';

View file

@ -1,4 +1,4 @@
import { init, reconcileWithHistory } from 'reconcile'; import { reconcileWithHistory } from 'reconcile';
import type { Tokenizer } from 'reconcile'; import type { Tokenizer } from 'reconcile';
import './style.scss'; import './style.scss';
@ -13,8 +13,6 @@ const tokenizerRadios = document.querySelectorAll(
const sampleText = `The \`reconcile\` Rust library is embedded on this page as a WASM module and powers these text boxes. Experiment with changing the "Original", "First concurrent edit", and "Second concurrent edit" text boxes to see competing changes get merged in real-time within the "Deconflicted result" box. Here, you will see color-coded tokens marking the origin of each token, including ones that got deleted. The result highly depends on the tokenization strategy, for example, deciding how casing or whitespace is taken into account.`; const sampleText = `The \`reconcile\` Rust library is embedded on this page as a WASM module and powers these text boxes. Experiment with changing the "Original", "First concurrent edit", and "Second concurrent edit" text boxes to see competing changes get merged in real-time within the "Deconflicted result" box. Here, you will see color-coded tokens marking the origin of each token, including ones that got deleted. The result highly depends on the tokenization strategy, for example, deciding how casing or whitespace is taken into account.`;
async function main(): Promise<void> { async function main(): Promise<void> {
await init();
originalTextArea.addEventListener('input', updateMergedText); originalTextArea.addEventListener('input', updateMergedText);
leftTextArea.addEventListener('input', updateMergedText); leftTextArea.addEventListener('input', updateMergedText);
rightTextArea.addEventListener('input', updateMergedText); rightTextArea.addEventListener('input', updateMergedText);

View file

@ -1,24 +1,11 @@
import { init, reconcile, reconcileWithHistory } from './index'; import { reconcile, reconcileWithHistory } from './index';
import * as fs from 'fs';
describe('reconcile', () => { describe('reconcile', () => {
it('tries calling functions without init', () => { it('call reconcile without cursors', () => {
expect(() => reconcile('Hello', 'Hello world', 'Hi world')).toThrow(/call init()/);
expect(() => reconcileWithHistory('Hello', 'Hello world', 'Hi world')).toThrow(
/call init()/
);
});
it('call reconcile without cursors', async () => {
await initWasm();
expect(reconcile('Hello', 'Hello world', 'Hi world').text).toEqual('Hi world'); expect(reconcile('Hello', 'Hello world', 'Hi world').text).toEqual('Hi world');
}); });
it('call reconcile with cursors', async () => { it('call reconcile with cursors', () => {
await initWasm();
const result = reconcile( const result = reconcile(
'Hello', 'Hello',
{ {
@ -50,17 +37,10 @@ describe('reconcile', () => {
]); ]);
}); });
it('call reconcileWithHistory', async () => { it('call reconcileWithHistory', () => {
await initWasm();
const result = reconcileWithHistory('Hello', 'Hello world', 'Hi world'); const result = reconcileWithHistory('Hello', 'Hello world', 'Hi world');
expect(result.text).toEqual('Hi world'); expect(result.text).toEqual('Hi world');
expect(result.history.length).toBeGreaterThan(0); expect(result.history.length).toBeGreaterThan(0);
}); });
}); });
async function initWasm() {
const wasmBin = fs.readFileSync('../pkg/reconcile_bg.wasm');
await init({ module_or_path: wasmBin });
}

View file

@ -6,9 +6,11 @@ import wasmInit, {
BuiltinTokenizer, BuiltinTokenizer,
reconcileWithHistory as wasmReconcileWithHistory, reconcileWithHistory as wasmReconcileWithHistory,
History, History,
InitInput, initSync,
} from 'reconcile'; } from 'reconcile';
import wasm from 'reconcile/reconcile_bg.wasm';
export interface TextWithCursors { export interface TextWithCursors {
/** The document's entire content */ /** The document's entire content */
text: string; text: string;
@ -44,32 +46,10 @@ const TOKENIZERS = ['Line', 'Word', 'Character'];
let isInitialised = false; let isInitialised = false;
const UNINITIALISED_MODULE_ERROR =
'Reconcile module has not been initialized. Please call init() before using any other functions.';
const UNSUPPORTED_TOKENIZER_ERROR = `Unsupported tokenizer. Only ${TOKENIZERS.join( const UNSUPPORTED_TOKENIZER_ERROR = `Unsupported tokenizer. Only ${TOKENIZERS.join(
', ' ', '
)} are supported.`; )} are supported.`;
/**
* Initializes the WASM module for text reconciliation.
* Must be called before using any other functions.
*
* The function is idempotent.
*
* @param content - Optional initialization input for the WASM module during testing.
* @returns Promise that resolves when initialization is complete
*/
export async function init(content?: InitInput) {
if (isInitialised) {
return;
}
await wasmInit(content);
isInitialised = true;
}
/** /**
* Merges three versions of text (original, left, right) and cursor positions. * Merges three versions of text (original, left, right) and cursor positions.
* *
@ -85,9 +65,7 @@ export function reconcile(
right: string | TextWithCursors, right: string | TextWithCursors,
tokenizer: BuiltinTokenizer = 'Word' tokenizer: BuiltinTokenizer = 'Word'
): TextWithCursors { ): TextWithCursors {
if (!isInitialised) { init();
throw new Error(UNINITIALISED_MODULE_ERROR);
}
if (!TOKENIZERS.includes(tokenizer)) { if (!TOKENIZERS.includes(tokenizer)) {
throw new Error(UNSUPPORTED_TOKENIZER_ERROR); throw new Error(UNSUPPORTED_TOKENIZER_ERROR);
@ -124,9 +102,7 @@ export function reconcileWithHistory(
right: string | TextWithCursors, right: string | TextWithCursors,
tokenizer: BuiltinTokenizer = 'Word' tokenizer: BuiltinTokenizer = 'Word'
): TextWithCursorsAndHistory { ): TextWithCursorsAndHistory {
if (!isInitialised) { init();
throw new Error(UNINITIALISED_MODULE_ERROR);
}
if (!TOKENIZERS.includes(tokenizer)) { if (!TOKENIZERS.includes(tokenizer)) {
throw new Error(UNSUPPORTED_TOKENIZER_ERROR); throw new Error(UNSUPPORTED_TOKENIZER_ERROR);
@ -150,6 +126,16 @@ export function reconcileWithHistory(
}; };
} }
function init() {
if (isInitialised) {
return;
}
initSync({ module: (wasm as any).default });
isInitialised = true;
}
function toWasmTextWithCursors(text: string | TextWithCursors): wasmTextWithCursors { function toWasmTextWithCursors(text: string | TextWithCursors): wasmTextWithCursors {
const isInputString = typeof text == 'string'; const isInputString = typeof text == 'string';
const leftText = isInputString ? text : text.text; const leftText = isInputString ? text : text.text;