vaultmd 0.1.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/LICENSE +21 -0
- package/README.md +254 -0
- package/dist/index.d.ts +253 -0
- package/dist/index.js +1787 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ivan Kalinichenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# vaultmd
|
|
2
|
+
|
|
3
|
+
> A headless markdown-vault data layer for [Bun](https://bun.sh) — CRUD over `.md` notes plus a derived SQLite index for collection queries, backlinks, and full-text search. No Obsidian, no Electron, no plugin.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/vaultmd)
|
|
6
|
+
[](https://bun.sh)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
[](#status)
|
|
9
|
+
|
|
10
|
+
`vaultmd` is an npm package that gives your Bun app a programmatic data layer
|
|
11
|
+
over a folder of markdown notes. Your `.md` files on disk stay the **single
|
|
12
|
+
source of truth**; vaultmd maintains a rebuildable `bun:sqlite` index alongside
|
|
13
|
+
them so you can query notes by tag or frontmatter, walk backlinks, and run
|
|
14
|
+
keyword search — all without an editor, sync engine, or background daemon.
|
|
15
|
+
|
|
16
|
+
It's the engine, not the app: generic vault mechanics only. Personas, domain
|
|
17
|
+
schemas, and sync logic live in whatever you build on top.
|
|
18
|
+
|
|
19
|
+
## Status
|
|
20
|
+
|
|
21
|
+
Released (`0.1.0`) — the first published version is live on npm. The public API
|
|
22
|
+
is frozen and tested, and the package ships as a bundled `dist/` (ESM + types).
|
|
23
|
+
Being `0.x`, the surface may still evolve before `1.0`; see
|
|
24
|
+
[CHANGELOG.md](./CHANGELOG.md) for what changed.
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- **CRUD over markdown** — create, read, update, delete `.md` notes with flat
|
|
29
|
+
YAML frontmatter.
|
|
30
|
+
- **Derived SQLite index** — a rebuildable cache, never the source of truth.
|
|
31
|
+
Delete it and it rebuilds from disk.
|
|
32
|
+
- **Collection queries** — filter notes by tag, frontmatter field, or folder;
|
|
33
|
+
order and paginate.
|
|
34
|
+
- **Backlinks & outbound links** — `[[wikilink]]` or relative-link resolution.
|
|
35
|
+
- **Full-text search** — keyword search over note bodies (SQLite FTS5) with
|
|
36
|
+
highlighted snippets.
|
|
37
|
+
- **Write-through indexing** — every mutation updates the index inside the same
|
|
38
|
+
per-file lock as the file write; the two never drift.
|
|
39
|
+
- **Concurrency-safe** — in-process mutex plus optional cross-process lockfiles
|
|
40
|
+
guard concurrent writers.
|
|
41
|
+
- **Scoped access** — per-instance read/write path allowlists make it safe to
|
|
42
|
+
hand different parts of the vault to different consumers.
|
|
43
|
+
- **TypeScript-first** — full types, a small frozen public surface, and lower
|
|
44
|
+
level primitives exported for advanced use.
|
|
45
|
+
|
|
46
|
+
## Requirements
|
|
47
|
+
|
|
48
|
+
- **[Bun](https://bun.sh) ≥ 1.1.0.** vaultmd uses `bun:sqlite`, `Bun.file`, and
|
|
49
|
+
other Bun built-ins — it does **not** run under Node.
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
bun add vaultmd
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Quick start
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { createVault } from 'vaultmd';
|
|
61
|
+
|
|
62
|
+
const vault = await createVault({
|
|
63
|
+
root: '/path/to/vault',
|
|
64
|
+
// Read everything, but only write under Notes/.
|
|
65
|
+
prefixes: { read: [''], write: ['Notes/'] },
|
|
66
|
+
// The index db lives in a DATA dir, NOT inside the synced vault.
|
|
67
|
+
indexPath: './data/vault-index.db',
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Create a note with frontmatter + body.
|
|
71
|
+
await vault.notes.createNote('Notes/today.md', {
|
|
72
|
+
frontmatter: { tags: ['project', 'daily'], status: 'open' },
|
|
73
|
+
body: '# Today\n\nSee [[roadmap]] for context.\n',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Query the collection.
|
|
77
|
+
const open = vault.query.queryNotes({
|
|
78
|
+
tag: 'project',
|
|
79
|
+
where: { status: 'open' },
|
|
80
|
+
orderBy: { field: 'mtime_ms', dir: 'desc' },
|
|
81
|
+
limit: 20,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Walk the link graph.
|
|
85
|
+
const incoming = vault.query.backlinks('Notes/roadmap.md');
|
|
86
|
+
|
|
87
|
+
// Full-text search.
|
|
88
|
+
const hits = vault.query.searchText('context');
|
|
89
|
+
|
|
90
|
+
// Append to a note (atomic, write-through indexed).
|
|
91
|
+
await vault.notes.updateNote('Notes/today.md', { append: '\n- shipped readme' });
|
|
92
|
+
|
|
93
|
+
vault.close();
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Concepts
|
|
97
|
+
|
|
98
|
+
**Files are the source of truth; the index is a cache.** Every note is a plain
|
|
99
|
+
`.md` file you can edit by hand, sync with git or Dropbox, or open in any editor.
|
|
100
|
+
The SQLite index is derived from those files and can be rebuilt at any time
|
|
101
|
+
(`vault.rebuild()`), so it never has to be backed up or trusted over disk.
|
|
102
|
+
|
|
103
|
+
**Read/write scopes.** `prefixes.read` and `prefixes.write` are path-prefix
|
|
104
|
+
allowlists. An empty string (`''`) means "the whole vault". Queries only ever
|
|
105
|
+
return notes the instance is allowed to read; writes are rejected outside the
|
|
106
|
+
write scope. This is the security chokepoint — all path canonicalization and
|
|
107
|
+
containment checks live behind it.
|
|
108
|
+
|
|
109
|
+
**Write-through indexing.** `createNote`, `updateNote`, `editFrontmatter`, and
|
|
110
|
+
`deleteNote` update the index *inside the same per-file lock* as the file write.
|
|
111
|
+
The file and its index row are never updated in separate transactions, so a
|
|
112
|
+
crash can't leave them disagreeing.
|
|
113
|
+
|
|
114
|
+
**Lazy reconcile.** Reads stay synchronous. The first read (and the first after
|
|
115
|
+
each TTL window) fires a single background sweep that picks up any out-of-band
|
|
116
|
+
edits — files you changed in your editor while the process was running. The
|
|
117
|
+
result is visible to the *next* read; a failed sweep never breaks a read.
|
|
118
|
+
|
|
119
|
+
**Index location.** The `.db` file and its `-wal` / `-shm` sidecars must live in
|
|
120
|
+
a data directory **outside** the synced vault, and stay gitignored (`*.db*`).
|
|
121
|
+
Never let the cache get synced as if it were content.
|
|
122
|
+
|
|
123
|
+
## API
|
|
124
|
+
|
|
125
|
+
The only public entry point is `createVault`. Everything below hangs off the
|
|
126
|
+
`Vault` it returns.
|
|
127
|
+
|
|
128
|
+
### `createVault(config): Promise<Vault>`
|
|
129
|
+
|
|
130
|
+
| Option | Type | Default | Description |
|
|
131
|
+
| ------------------------ | -------------------------------------- | ------------ | --------------------------------------------------------------------------- |
|
|
132
|
+
| `root` | `string` | *(required)* | Absolute path to the vault directory. |
|
|
133
|
+
| `prefixes` | `{ read: string[]; write: string[] }` | *(required)* | Read/write path-prefix allowlists (`''` = whole vault). |
|
|
134
|
+
| `indexPath` | `string` | *(required)* | Where the SQLite index lives. Keep it **out** of the vault. |
|
|
135
|
+
| `caseSensitive` | `boolean` | *(auto)* | Override filesystem case sensitivity detection. |
|
|
136
|
+
| `ignore` | `string[]` | `[]` | Glob patterns to exclude from indexing. |
|
|
137
|
+
| `linkResolution` | `'wikilink' \| 'relative'` | `'wikilink'` | How links are extracted and resolved. |
|
|
138
|
+
| `lazyReconcile` | `boolean` | `true` | Fire background reconcile sweeps on read. |
|
|
139
|
+
| `reconcileTtlMs` | `number` | `2000` | Minimum gap between lazy sweeps. |
|
|
140
|
+
| `sqliteBusyTimeoutMs` | `number` | `5000` | SQLite busy timeout / cross-process lock wait. |
|
|
141
|
+
| `crossProcessWriterLock` | `boolean` | `true` | Guard writes with cross-process lockfiles. |
|
|
142
|
+
| `onCommit` | `(e: CommitEvent) => void \| Promise` | — | Hook fired after each committed mutation (e.g. to mirror changes upstream). |
|
|
143
|
+
|
|
144
|
+
### `vault.notes`
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
// Read a note; pass { withLinks: true } to include outbound + backlinks.
|
|
148
|
+
readNote(path, opts?: { withLinks?: boolean }): Promise<ReadNoteResult>
|
|
149
|
+
|
|
150
|
+
// Create a note. Throws ALREADY_EXISTS rather than clobbering.
|
|
151
|
+
createNote(path, input: { frontmatter?: Record<string, unknown>; body: string }): Promise<void>
|
|
152
|
+
|
|
153
|
+
// Mutate body: append text, or replace an exact, unambiguous match.
|
|
154
|
+
updateNote(path, op: { append: string } | { editByMatch: { old: string; new: string } }): Promise<void>
|
|
155
|
+
|
|
156
|
+
// Edit flat frontmatter via a mutator callback. Returns 'edited' | 'unchanged' | 'unverifiable'.
|
|
157
|
+
editFrontmatter(path, mutate: (fm: Record<string, unknown>) => void): Promise<EditOutcome>
|
|
158
|
+
|
|
159
|
+
// Delete a note. Returns whether a file was actually removed.
|
|
160
|
+
deleteNote(path): Promise<boolean>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`ReadNoteResult` is `{ frontmatter, tags, body, valid, outbound?, backlinks? }`,
|
|
164
|
+
where `valid` is `'flat' | 'present-but-invalid' | 'none'`.
|
|
165
|
+
|
|
166
|
+
### `vault.query`
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
// Filter the collection. Returns NoteHit[] = { path, title, frontmatter, tags }[].
|
|
170
|
+
queryNotes(opts?: {
|
|
171
|
+
tag?: string;
|
|
172
|
+
where?: Record<string, string | number | boolean>; // frontmatter equality
|
|
173
|
+
folder?: string;
|
|
174
|
+
orderBy?: { field: 'mtime_ms' | 'path' | 'title'; dir: 'asc' | 'desc' };
|
|
175
|
+
limit?: number;
|
|
176
|
+
offset?: number;
|
|
177
|
+
}): NoteHit[]
|
|
178
|
+
|
|
179
|
+
// Notes linking TO this path. Returns { from: string }[].
|
|
180
|
+
backlinks(path, opts?: { limit?: number; offset?: number }): Backlink[]
|
|
181
|
+
|
|
182
|
+
// Links FROM this path. Returns { target, resolved }[] (resolved is null if dangling).
|
|
183
|
+
outboundLinks(path, opts?: { limit?: number; offset?: number }): OutboundLink[]
|
|
184
|
+
|
|
185
|
+
// Full-text keyword search over bodies. Returns { path, title, snippet? }[].
|
|
186
|
+
searchText(q, opts?: { tag?: string; folder?: string; limit?: number; offset?: number }): SearchHit[]
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Lifecycle
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
vault.reconcile(): Promise<void> // full sweep now (vs. lazy)
|
|
193
|
+
vault.reconcilePaths(rels: string[]): Promise<void> // reconcile specific paths
|
|
194
|
+
vault.rebuild(): Promise<void> // drop & rebuild the index from disk
|
|
195
|
+
vault.close(): void // close the db handle
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Lower-level primitives
|
|
199
|
+
|
|
200
|
+
For advanced use, the package also exports the building blocks `createVault`
|
|
201
|
+
assembles — the IO chokepoint, atomic locked-file transforms, and the pure
|
|
202
|
+
frontmatter / link parsers:
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
import {
|
|
206
|
+
createVaultIo, // path → safe-IO security layer
|
|
207
|
+
withFileTransform, // atomic compare-and-swap file edit
|
|
208
|
+
withFileDelete, // atomic delete with commit hook
|
|
209
|
+
parseFrontmatter, // pure flat-YAML frontmatter parser
|
|
210
|
+
editFrontmatter, // pure frontmatter editor
|
|
211
|
+
isFlatFrontmatter,
|
|
212
|
+
deriveTags,
|
|
213
|
+
extractLinks, // pull wikilinks / relative links from text
|
|
214
|
+
storedLinksFor,
|
|
215
|
+
} from 'vaultmd';
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Error handling
|
|
219
|
+
|
|
220
|
+
Every failure throws an `MdVaultError` carrying a stable `code`. Catch and
|
|
221
|
+
switch on `err.code`, never on the message:
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
import { MdVaultError } from 'vaultmd';
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
await vault.notes.createNote('Notes/today.md', { body: '...' });
|
|
228
|
+
} catch (err) {
|
|
229
|
+
if (err instanceof MdVaultError && err.code === 'ALREADY_EXISTS') {
|
|
230
|
+
// handle the clash
|
|
231
|
+
} else {
|
|
232
|
+
throw err;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Codes: `ALLOWLIST_VIOLATION`, `NOT_MARKDOWN`, `NOT_FOUND`, `ALREADY_EXISTS`,
|
|
238
|
+
`NO_MATCH`, `AMBIGUOUS_MATCH`, `MTIME_CONFLICT`, `REFUSE_CREATE`,
|
|
239
|
+
`FRONTMATTER_INVALID`, `VALIDATION_ERROR`, `COMMIT_FAILED`, `INDEX_UNAVAILABLE`.
|
|
240
|
+
|
|
241
|
+
## Development
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
bun install
|
|
245
|
+
bun test # full suite
|
|
246
|
+
bun run check # biome check . && tsc --noEmit — the authoritative gate
|
|
247
|
+
bun run format # biome format --write .
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Run `bun run check` before sending a change; it's green/red, not advisory.
|
|
251
|
+
|
|
252
|
+
## License
|
|
253
|
+
|
|
254
|
+
[MIT](./LICENSE) © Ivan Kalinichenko
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
type MdVaultCode = 'ALLOWLIST_VIOLATION' | 'NOT_MARKDOWN' | 'NOT_FOUND' | 'ALREADY_EXISTS' | 'NO_MATCH' | 'AMBIGUOUS_MATCH' | 'MTIME_CONFLICT' | 'REFUSE_CREATE' | 'FRONTMATTER_INVALID' | 'VALIDATION_ERROR' | 'COMMIT_FAILED' | 'INDEX_UNAVAILABLE';
|
|
4
|
+
declare class MdVaultError extends Error {
|
|
5
|
+
readonly code: MdVaultCode;
|
|
6
|
+
constructor(code: MdVaultCode, message: string, options?: {
|
|
7
|
+
cause?: unknown;
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type EditOutcome = 'edited' | 'unchanged' | 'unverifiable';
|
|
12
|
+
|
|
13
|
+
declare function editFrontmatter(content: string, mutate: (fm: Record<string, unknown>) => void): {
|
|
14
|
+
content: string;
|
|
15
|
+
outcome: EditOutcome;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type FrontmatterValidity = 'flat' | 'present-but-invalid' | 'none';
|
|
19
|
+
|
|
20
|
+
type ParsedFrontmatter = {
|
|
21
|
+
frontmatter: Record<string, unknown>;
|
|
22
|
+
tags: string[];
|
|
23
|
+
body: string;
|
|
24
|
+
valid: FrontmatterValidity;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
declare function parseFrontmatter(content: string): ParsedFrontmatter;
|
|
28
|
+
|
|
29
|
+
declare function deriveTags(frontmatter: Record<string, unknown>): string[];
|
|
30
|
+
|
|
31
|
+
declare function isFlatFrontmatter(fm: Record<string, unknown>): boolean;
|
|
32
|
+
|
|
33
|
+
type Sig = {
|
|
34
|
+
mtimeMs: number;
|
|
35
|
+
size: number;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
type ExtractedLinks = {
|
|
39
|
+
wikilinks: string[];
|
|
40
|
+
embeds: string[];
|
|
41
|
+
mdLinks: string[];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
declare function extractLinks(content: string): ExtractedLinks;
|
|
45
|
+
|
|
46
|
+
type LinkResolution = 'wikilink' | 'relative';
|
|
47
|
+
|
|
48
|
+
type StoredLink = {
|
|
49
|
+
target: string;
|
|
50
|
+
base: string | null;
|
|
51
|
+
kind: 'wikilink' | 'embed' | 'mdlink';
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
declare function storedLinksFor(content: string, srcRel: string, mode: LinkResolution): StoredLink[];
|
|
55
|
+
|
|
56
|
+
type CommitEvent = {
|
|
57
|
+
op: 'create' | 'update';
|
|
58
|
+
path: string;
|
|
59
|
+
content: string;
|
|
60
|
+
} | {
|
|
61
|
+
op: 'delete';
|
|
62
|
+
path: string;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
type CrossLock = {
|
|
66
|
+
lockDir: string;
|
|
67
|
+
busyTimeoutMs: number;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param lockKey Canonical/case-folded serialization key — pass `VaultIo.toKey(rel)`.
|
|
72
|
+
* @param relForCommit Display path written to `CommitEvent.path` — pass `VaultIo.toVaultRelative(rel)`.
|
|
73
|
+
*/
|
|
74
|
+
declare function withFileDelete(fullPath: string, lockKey: string, relForCommit: string, opts?: {
|
|
75
|
+
onCommit?: (e: CommitEvent) => void | Promise<void>;
|
|
76
|
+
cross?: CrossLock | false;
|
|
77
|
+
}): Promise<{
|
|
78
|
+
deleted: boolean;
|
|
79
|
+
}>;
|
|
80
|
+
|
|
81
|
+
type TransformOpts = {
|
|
82
|
+
allowCreate?: boolean;
|
|
83
|
+
onCommit?: (e: CommitEvent) => void | Promise<void>;
|
|
84
|
+
maxRetries?: number;
|
|
85
|
+
cross?: CrossLock | false;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
type TransformResult = {
|
|
89
|
+
content: string | null;
|
|
90
|
+
outcome: 'created' | 'updated' | 'unchanged';
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param lockKey Canonical/case-folded serialization key — pass `VaultIo.toKey(rel)`.
|
|
95
|
+
* @param relForCommit Display path written to `CommitEvent.path` — pass `VaultIo.toVaultRelative(rel)`.
|
|
96
|
+
*/
|
|
97
|
+
declare function withFileTransform(fullPath: string, lockKey: string, relForCommit: string, transform: (current: string | null) => string | null, opts?: TransformOpts): Promise<TransformResult>;
|
|
98
|
+
|
|
99
|
+
type Backlink = {
|
|
100
|
+
from: string;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
type NoteHit = {
|
|
104
|
+
path: string;
|
|
105
|
+
title: string;
|
|
106
|
+
frontmatter: Record<string, unknown>;
|
|
107
|
+
tags: string[];
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
type OrderField = 'mtime_ms' | 'path' | 'title';
|
|
111
|
+
type QueryOrder = {
|
|
112
|
+
field: OrderField;
|
|
113
|
+
dir: 'asc' | 'desc';
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
type OutboundLink = {
|
|
117
|
+
target: string;
|
|
118
|
+
resolved: string | null;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
type SearchHit = {
|
|
122
|
+
path: string;
|
|
123
|
+
title: string;
|
|
124
|
+
snippet?: string;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
type WhereMap = Record<string, string | number | boolean>;
|
|
128
|
+
|
|
129
|
+
type Access = 'read' | 'write';
|
|
130
|
+
|
|
131
|
+
type VaultIo = {
|
|
132
|
+
toVaultRelative(rel: string): string;
|
|
133
|
+
toKey(rel: string): string;
|
|
134
|
+
can(rel: string, access: Access): boolean;
|
|
135
|
+
resolveVaultPath(rel: string, access?: Access): string;
|
|
136
|
+
readVaultFile(rel: string): Promise<{
|
|
137
|
+
content: string;
|
|
138
|
+
sig: Sig;
|
|
139
|
+
} | null>;
|
|
140
|
+
writeVaultFile(rel: string, content: string): Promise<Sig>;
|
|
141
|
+
rewriteIfUnchanged(rel: string, content: string, expected: Sig): Promise<Sig>;
|
|
142
|
+
unlinkIfUnchanged(rel: string, expected: Sig): Promise<boolean>;
|
|
143
|
+
stat(rel: string): Promise<Sig | null>;
|
|
144
|
+
listMarkdown(dir?: string): Promise<string[]>;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
type VaultPrefixes = {
|
|
148
|
+
read: string[];
|
|
149
|
+
write: string[];
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
type VaultIoConfig = {
|
|
153
|
+
root: string;
|
|
154
|
+
prefixes: VaultPrefixes;
|
|
155
|
+
caseSensitive?: boolean;
|
|
156
|
+
ignore?: string[];
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
declare function createVaultIo(config: VaultIoConfig): VaultIo;
|
|
160
|
+
|
|
161
|
+
type IndexConfig = {
|
|
162
|
+
linkResolution: LinkResolution;
|
|
163
|
+
caseSensitive: boolean;
|
|
164
|
+
ignore: string[];
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
declare function createQuery(db: Database, vaultIo: VaultIo, cfg: IndexConfig): {
|
|
168
|
+
queryNotes: (opts?: {
|
|
169
|
+
tag?: string;
|
|
170
|
+
where?: WhereMap;
|
|
171
|
+
folder?: string;
|
|
172
|
+
orderBy?: QueryOrder;
|
|
173
|
+
limit?: number;
|
|
174
|
+
offset?: number;
|
|
175
|
+
}) => NoteHit[];
|
|
176
|
+
backlinks: (path: string, opts?: {
|
|
177
|
+
limit?: number;
|
|
178
|
+
offset?: number;
|
|
179
|
+
}) => Backlink[];
|
|
180
|
+
outboundLinks: (path: string, opts?: {
|
|
181
|
+
limit?: number;
|
|
182
|
+
offset?: number;
|
|
183
|
+
}) => OutboundLink[];
|
|
184
|
+
searchText: (q: string, opts?: {
|
|
185
|
+
tag?: string;
|
|
186
|
+
folder?: string;
|
|
187
|
+
limit?: number;
|
|
188
|
+
offset?: number;
|
|
189
|
+
}) => SearchHit[];
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
type ReadNoteResult = {
|
|
193
|
+
frontmatter: Record<string, unknown>;
|
|
194
|
+
tags: string[];
|
|
195
|
+
body: string;
|
|
196
|
+
valid: FrontmatterValidity;
|
|
197
|
+
outbound?: OutboundLink[];
|
|
198
|
+
backlinks?: Backlink[];
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
type UpdateOp = {
|
|
202
|
+
editByMatch: {
|
|
203
|
+
old: string;
|
|
204
|
+
new: string;
|
|
205
|
+
};
|
|
206
|
+
} | {
|
|
207
|
+
append: string;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
type NotesDeps = {
|
|
211
|
+
db: Database;
|
|
212
|
+
vaultIo: VaultIo;
|
|
213
|
+
cfg: IndexConfig;
|
|
214
|
+
query: ReturnType<typeof createQuery>;
|
|
215
|
+
onCommit?: (e: CommitEvent) => void | Promise<void>;
|
|
216
|
+
cross?: CrossLock | false;
|
|
217
|
+
};
|
|
218
|
+
declare function createNotes(deps: NotesDeps): {
|
|
219
|
+
readNote: (path: string, opts?: {
|
|
220
|
+
withLinks?: boolean;
|
|
221
|
+
}) => Promise<ReadNoteResult>;
|
|
222
|
+
createNote: (path: string, input: {
|
|
223
|
+
frontmatter?: Record<string, unknown>;
|
|
224
|
+
body: string;
|
|
225
|
+
}) => Promise<void>;
|
|
226
|
+
updateNote: (path: string, op: UpdateOp) => Promise<void>;
|
|
227
|
+
editFrontmatter: (path: string, mutate: (fm: Record<string, unknown>) => void) => Promise<EditOutcome>;
|
|
228
|
+
deleteNote: (path: string) => Promise<boolean>;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
type CreateVaultConfig = VaultIoConfig & {
|
|
232
|
+
indexPath: string;
|
|
233
|
+
linkResolution?: LinkResolution;
|
|
234
|
+
lazyReconcile?: boolean;
|
|
235
|
+
reconcileTtlMs?: number;
|
|
236
|
+
sqliteBusyTimeoutMs?: number;
|
|
237
|
+
crossProcessWriterLock?: boolean;
|
|
238
|
+
onCommit?: (e: CommitEvent) => void | Promise<void>;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
type Vault = {
|
|
242
|
+
io: VaultIo;
|
|
243
|
+
notes: ReturnType<typeof createNotes>;
|
|
244
|
+
query: ReturnType<typeof createQuery>;
|
|
245
|
+
reconcile(): Promise<void>;
|
|
246
|
+
reconcilePaths(rels: string[]): Promise<void>;
|
|
247
|
+
rebuild(): Promise<void>;
|
|
248
|
+
close(): void;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
declare function createVault(config: CreateVaultConfig): Promise<Vault>;
|
|
252
|
+
|
|
253
|
+
export { type Access, type CommitEvent, type CreateVaultConfig, type CrossLock, type EditOutcome, type ExtractedLinks, type FrontmatterValidity, type LinkResolution, type MdVaultCode, MdVaultError, type NoteHit, type OrderField, type ParsedFrontmatter, type QueryOrder, type ReadNoteResult, type SearchHit, type Sig, type StoredLink, type TransformOpts, type TransformResult, type UpdateOp, type Vault, type VaultIo, type VaultIoConfig, type VaultPrefixes, type WhereMap, createVault, createVaultIo, deriveTags, editFrontmatter, extractLinks, isFlatFrontmatter, parseFrontmatter, storedLinksFor, withFileDelete, withFileTransform };
|