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 +343 -0
- package/diaryx_wasm.d.ts +891 -0
- package/diaryx_wasm.js +2255 -0
- package/diaryx_wasm_bg.wasm +0 -0
- package/package.json +21 -0
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.
|