diaryx_wasm 0.11.0

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 ADDED
@@ -0,0 +1,343 @@
1
+ ---
2
+ title: diaryx_wasm
3
+ description: WASM bindings for diaryx_core
4
+ part_of: '[README](/crates/README.md)'
5
+ audience:
6
+ - developers
7
+ contents:
8
+ - '[README](/crates/diaryx_wasm/src/README.md)'
9
+ attachments:
10
+ - '[Cargo.toml](/crates/diaryx_wasm/Cargo.toml)'
11
+ - '[build.rs](/crates/diaryx_wasm/build.rs)'
12
+ exclude:
13
+ - '*.lock'
14
+ - '*.db'
15
+ - 'pkg/**'
16
+ ---
17
+
18
+ # diaryx_wasm
19
+
20
+ WebAssembly bindings for `diaryx_core`, used by the web frontend in `apps/web`.
21
+
22
+ ## Building
23
+
24
+ To build the WebAssembly module:
25
+
26
+ ```bash
27
+ wasm-pack build --target web --out-dir ../../apps/web/src/lib/wasm
28
+ ```
29
+
30
+ ## Architecture
31
+
32
+ The crate provides typed class-based APIs that wrap `diaryx_core` functionality:
33
+
34
+ | Class | Purpose |
35
+ | ------------------------ | ----------------------------------------- |
36
+ | `DiaryxWorkspace` | Workspace tree operations |
37
+ | `DiaryxEntry` | Entry CRUD operations |
38
+ | `DiaryxFrontmatter` | Frontmatter manipulation |
39
+ | `DiaryxSearch` | Workspace search |
40
+ | `DiaryxTemplate` | Template management |
41
+ | `DiaryxValidation` | Link integrity validation and fixing |
42
+ | `DiaryxExport` | Export with audience filtering |
43
+ | `DiaryxAttachment` | Attachment upload/download |
44
+ | `DiaryxFilesystem` | Low-level filesystem operations (sync) |
45
+ | `DiaryxAsyncFilesystem` | Async filesystem operations with Promises |
46
+ | `Diaryx` | Unified command API (includes CRDT ops) |
47
+
48
+ ### In-Memory Filesystem
49
+
50
+ Unlike the CLI and Tauri backends which use `RealFileSystem` (native filesystem), the WASM backend uses `InMemoryFileSystem`. This allows the web app to:
51
+
52
+ 1. Load files from IndexedDB on startup
53
+ 2. Operate entirely in memory during use
54
+ 3. Persist changes back to IndexedDB
55
+
56
+ ## API Reference
57
+
58
+ ### DiaryxValidation
59
+
60
+ Validates workspace link integrity and fixes issues.
61
+
62
+ ```javascript
63
+ import init, { DiaryxValidation } from "./wasm/diaryx_wasm.js";
64
+
65
+ await init();
66
+ const validation = new DiaryxValidation();
67
+
68
+ // Validate entire workspace
69
+ const result = validation.validate("workspace");
70
+ console.log(`Checked ${result.files_checked} files`);
71
+ console.log(`Errors: ${result.errors.length}`);
72
+ console.log(`Warnings: ${result.warnings.length}`);
73
+
74
+ // Validate single file
75
+ const fileResult = validation.validate_file("workspace/notes/my-note.md");
76
+
77
+ // Fix all issues
78
+ const fixSummary = validation.fix_all(result);
79
+ console.log(
80
+ `Fixed: ${fixSummary.total_fixed}, Failed: ${fixSummary.total_failed}`,
81
+ );
82
+
83
+ // Fix individual issues
84
+ validation.fix_broken_part_of("workspace/broken.md");
85
+ validation.fix_broken_contents_ref("workspace/index.md", "missing.md");
86
+ validation.fix_unlisted_file("workspace/index.md", "workspace/unlisted.md");
87
+ validation.fix_missing_part_of("workspace/orphan.md", "workspace/index.md");
88
+ ```
89
+
90
+ #### Validation Errors
91
+
92
+ - `BrokenPartOf` - `part_of` points to non-existent file
93
+ - `BrokenContentsRef` - `contents` references non-existent file
94
+ - `BrokenAttachment` - `attachments` references non-existent file
95
+
96
+ #### Validation Warnings
97
+
98
+ - `OrphanFile` - Markdown file not in any index's `contents`
99
+ - `UnlinkedEntry` - File/directory not in contents hierarchy
100
+ - `CircularReference` - Circular reference in hierarchy
101
+ - `NonPortablePath` - Path contains absolute or `.`/`..` components
102
+ - `MultipleIndexes` - Multiple index files in same directory
103
+ - `OrphanBinaryFile` - Binary file not in any index's `attachments`
104
+ - `MissingPartOf` - Non-index file has no `part_of`
105
+
106
+ #### Exclude Patterns
107
+
108
+ Use `exclude` in frontmatter to suppress warnings. Patterns inherit up the `part_of` chain:
109
+
110
+ ```yaml
111
+ exclude:
112
+ - "LICENSE.md"
113
+ - "*.lock"
114
+ ```
115
+
116
+ ### Legacy API
117
+
118
+ For backwards compatibility, standalone functions are also exported:
119
+
120
+ ```javascript
121
+ import {
122
+ validate_workspace,
123
+ validate_file,
124
+ fix_all_validation_issues,
125
+ } from "./wasm/diaryx_wasm.js";
126
+
127
+ const result = validate_workspace("workspace");
128
+ const fixSummary = fix_all_validation_issues(result);
129
+ ```
130
+
131
+ ### DiaryxAsyncFilesystem
132
+
133
+ Async filesystem operations that return JavaScript Promises. This is useful for consistent async/await patterns in JavaScript and future integration with truly async storage (e.g., IndexedDB).
134
+
135
+ ```javascript
136
+ import init, { DiaryxAsyncFilesystem } from "./wasm/diaryx_wasm.js";
137
+
138
+ await init();
139
+ const asyncFs = new DiaryxAsyncFilesystem();
140
+
141
+ // All methods return Promises
142
+ const content = await asyncFs.read_file("workspace/README.md");
143
+ await asyncFs.write_file("workspace/new.md", "# New File");
144
+ const exists = await asyncFs.file_exists("workspace/new.md");
145
+
146
+ // Directory operations
147
+ await asyncFs.create_dir_all("workspace/notes/2024");
148
+ const isDir = await asyncFs.is_dir("workspace/notes");
149
+
150
+ // List files
151
+ const mdFiles = await asyncFs.list_md_files("workspace");
152
+ console.log(`Found ${mdFiles.count} markdown files:`, mdFiles.files);
153
+
154
+ // Recursive listing
155
+ const allMd = await asyncFs.list_md_files_recursive("workspace");
156
+ const allFiles = await asyncFs.list_all_files_recursive("workspace");
157
+
158
+ // Binary file operations
159
+ const data = await asyncFs.read_binary("workspace/image.png");
160
+ await asyncFs.write_binary("workspace/copy.png", data);
161
+
162
+ // Bulk operations for IndexedDB sync
163
+ const backupData = await asyncFs.get_backup_data();
164
+ // ... persist to IndexedDB ...
165
+ await asyncFs.restore_from_backup(backupData);
166
+
167
+ // Load/export files
168
+ await asyncFs.load_files([
169
+ ["workspace/README.md", "# Hello"],
170
+ ["workspace/notes.md", "# Notes"],
171
+ ]);
172
+ const entries = await asyncFs.export_files();
173
+
174
+ // Clear filesystem
175
+ await asyncFs.clear();
176
+ ```
177
+
178
+ #### Async vs Sync Filesystem
179
+
180
+ - `DiaryxFilesystem` - Synchronous methods, returns values directly
181
+ - `DiaryxAsyncFilesystem` - All methods return Promises
182
+
183
+ While the underlying `InMemoryFileSystem` is synchronous, `DiaryxAsyncFilesystem` provides a Promise-based API that:
184
+
185
+ 1. Enables consistent async/await patterns in JavaScript
186
+ 2. Allows for future integration with truly async operations
187
+ 3. Works well with JavaScript's event loop
188
+
189
+ ### Diaryx (Command API with CRDT)
190
+
191
+ The `Diaryx` class provides a unified command API that includes CRDT operations for real-time collaboration. Commands are executed via `execute()` (returns typed result) or `executeJs()` (returns JS-friendly object).
192
+
193
+ ```javascript
194
+ import init, { Diaryx } from "./wasm/diaryx_wasm.js";
195
+
196
+ await init();
197
+ const diaryx = new Diaryx();
198
+
199
+ // All CRDT commands use the execute/executeJs pattern
200
+ ```
201
+
202
+ #### CRDT Workspace Operations
203
+
204
+ ```javascript
205
+ // Set file metadata in the CRDT workspace
206
+ await diaryx.executeJs({
207
+ type: "SetFileMetadata",
208
+ path: "notes/my-note.md",
209
+ metadata: {
210
+ title: "My Note",
211
+ audience: ["public"],
212
+ part_of: "README.md",
213
+ },
214
+ });
215
+
216
+ // Get file metadata
217
+ const result = await diaryx.executeJs({
218
+ type: "GetFileMetadata",
219
+ path: "notes/my-note.md",
220
+ });
221
+ console.log(result.metadata); // { title: "My Note", ... }
222
+
223
+ // List all files in workspace
224
+ const files = await diaryx.executeJs({ type: "ListFiles" });
225
+ console.log(files.files); // ["notes/my-note.md", ...]
226
+
227
+ // Remove a file from workspace
228
+ await diaryx.executeJs({
229
+ type: "RemoveFile",
230
+ path: "notes/my-note.md",
231
+ });
232
+ ```
233
+
234
+ #### CRDT Body Document Operations
235
+
236
+ ```javascript
237
+ // Set document body content
238
+ await diaryx.executeJs({
239
+ type: "SetBody",
240
+ path: "notes/my-note.md",
241
+ content: "# Hello World\n\nThis is my note.",
242
+ });
243
+
244
+ // Get document body
245
+ const body = await diaryx.executeJs({
246
+ type: "GetBody",
247
+ path: "notes/my-note.md",
248
+ });
249
+ console.log(body.content);
250
+
251
+ // Insert text at position (collaborative editing)
252
+ await diaryx.executeJs({
253
+ type: "InsertAt",
254
+ path: "notes/my-note.md",
255
+ position: 0,
256
+ text: "Prefix: ",
257
+ });
258
+
259
+ // Delete text range
260
+ await diaryx.executeJs({
261
+ type: "DeleteRange",
262
+ path: "notes/my-note.md",
263
+ start: 0,
264
+ end: 8,
265
+ });
266
+ ```
267
+
268
+ #### CRDT Sync Operations
269
+
270
+ For synchronizing with Hocuspocus or other Y.js-compatible servers:
271
+
272
+ ```javascript
273
+ // Get sync state (state vector) for initial handshake
274
+ const syncState = await diaryx.executeJs({
275
+ type: "GetSyncState",
276
+ doc_type: "workspace", // or "body"
277
+ doc_name: null, // required for "body" type
278
+ });
279
+ const stateVector = syncState.state_vector; // Uint8Array
280
+
281
+ // Apply remote update from server
282
+ await diaryx.executeJs({
283
+ type: "ApplyRemoteUpdate",
284
+ doc_type: "workspace",
285
+ doc_name: null,
286
+ update: remoteUpdateBytes, // Uint8Array from WebSocket
287
+ });
288
+
289
+ // Encode full state to send to server
290
+ const state = await diaryx.executeJs({
291
+ type: "EncodeState",
292
+ doc_type: "workspace",
293
+ doc_name: null,
294
+ });
295
+ sendToServer(state.state); // Uint8Array
296
+
297
+ // Encode incremental update since a state vector
298
+ const diff = await diaryx.executeJs({
299
+ type: "EncodeStateAsUpdate",
300
+ doc_type: "workspace",
301
+ doc_name: null,
302
+ state_vector: remoteStateVector, // Uint8Array
303
+ });
304
+ sendToServer(diff.update); // Uint8Array
305
+ ```
306
+
307
+ #### Version History
308
+
309
+ ```javascript
310
+ // Get version history for a document
311
+ const history = await diaryx.executeJs({
312
+ type: "GetHistory",
313
+ doc_type: "workspace", // or "body"
314
+ doc_name: null, // required for "body" type
315
+ });
316
+
317
+ for (const entry of history.entries) {
318
+ console.log(`Version ${entry.version} at ${entry.timestamp}`);
319
+ console.log(` Origin: ${entry.origin}`); // "local" or "remote"
320
+ console.log(` Size: ${entry.update.length} bytes`);
321
+ }
322
+
323
+ // Restore to a specific version (time travel)
324
+ await diaryx.executeJs({
325
+ type: "RestoreToVersion",
326
+ doc_type: "workspace",
327
+ doc_name: null,
328
+ version: 5,
329
+ });
330
+ ```
331
+
332
+ #### Document Types
333
+
334
+ CRDT operations use `doc_type` to specify which document to operate on:
335
+
336
+ | doc_type | doc_name | Description |
337
+ | ----------- | --------- | ---------------------------------------- |
338
+ | `workspace` | `null` | The workspace file hierarchy metadata |
339
+ | `body` | file path | Per-file body content (e.g., `notes/a.md`) |
340
+
341
+ ## Error Handling
342
+
343
+ All methods return `Result<T, JsValue>` for JavaScript interop. Errors are converted to JavaScript exceptions with descriptive messages.