@openparachute/vault 0.4.0 → 0.4.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 +133 -0
- package/core/src/core.test.ts +1171 -518
- package/core/src/mcp.ts +37 -426
- package/core/src/notes.ts +405 -32
- package/core/src/schema-defaults.ts +214 -170
- package/core/src/schema.ts +104 -32
- package/core/src/store.ts +90 -78
- package/core/src/tag-hierarchy.ts +36 -2
- package/core/src/types.ts +37 -42
- package/core/src/vault-projection.ts +309 -0
- package/package.json +2 -2
- package/src/auth-hub-jwt.test.ts +142 -13
- package/src/auth.ts +29 -0
- package/src/hub-jwt.test.ts +16 -5
- package/src/hub-jwt.ts +9 -0
- package/src/mcp-http.ts +4 -2
- package/src/mcp-tools.ts +101 -90
- package/src/routes.ts +313 -206
- package/src/routing.test.ts +12 -12
- package/src/routing.ts +0 -2
- package/src/tokens-routes.test.ts +11 -4
- package/src/vault.test.ts +875 -297
- package/core/src/note-schemas.ts +0 -232
package/README.md
CHANGED
|
@@ -432,6 +432,139 @@ GET /vaults/list public: vault
|
|
|
432
432
|
GET /health health check
|
|
433
433
|
```
|
|
434
434
|
|
|
435
|
+
## Common queries / cookbook
|
|
436
|
+
|
|
437
|
+
Patterns that fall out of vault's read and write surfaces. All examples assume your vault is reachable at `http://localhost:1940` and your token is in `$VAULT_TOKEN`. MCP-side examples are how the same operation looks via the tool layer (Claude Code, Claude Desktop, Daily). Each recipe answers a question consumers have asked while building against vault — see [vault#285](https://github.com/ParachuteComputer/parachute-vault/issues/285) for the field-input thread these distilled from.
|
|
438
|
+
|
|
439
|
+
### Fetch every note under a path subtree
|
|
440
|
+
|
|
441
|
+
For an SSG or wiki rendering a section of the vault. `path_prefix` matches against the normalized path string — no `.md`, no trailing slash.
|
|
442
|
+
|
|
443
|
+
```bash
|
|
444
|
+
curl -H "Authorization: Bearer $VAULT_TOKEN" \
|
|
445
|
+
"http://localhost:1940/vault/default/api/notes?path_prefix=Boulder%20Civics/City%20Council/&include_content=true"
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
```jsonc
|
|
449
|
+
// MCP
|
|
450
|
+
{ "name": "query-notes", "arguments": { "path_prefix": "Boulder Civics/City Council/", "include_content": true } }
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### Sort by a metadata field
|
|
454
|
+
|
|
455
|
+
Sort by something other than `created_at`. The field must be declared `indexed: true` in some tag schema first — that's what materializes the backing generated column + B-tree index. The "tag authorizes the index" pattern lives in [`core/src/indexed-fields.ts`](./core/src/indexed-fields.ts).
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
curl -H "Authorization: Bearer $VAULT_TOKEN" \
|
|
459
|
+
"http://localhost:1940/vault/default/api/notes?order_by=meeting_date&sort=desc&include_content=true"
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
Declare the index via `update-tag`:
|
|
463
|
+
|
|
464
|
+
```jsonc
|
|
465
|
+
{ "name": "update-tag", "arguments": {
|
|
466
|
+
"tag": "meeting",
|
|
467
|
+
"fields": { "meeting_date": { "type": "string", "indexed": true } }
|
|
468
|
+
} }
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### Return previews instead of full bodies
|
|
472
|
+
|
|
473
|
+
Listing pages don't need the whole note. By default `GET /notes` returns the lean shape — 120-char whitespace-collapsed `preview`, `byteSize`, and the usual metadata — with no `content`. Single-note `GET /notes/:id` still returns full content by default; pass `include_content=false` to drop it.
|
|
474
|
+
|
|
475
|
+
```bash
|
|
476
|
+
curl -H "Authorization: Bearer $VAULT_TOKEN" \
|
|
477
|
+
"http://localhost:1940/vault/default/api/notes?path_prefix=Posts/"
|
|
478
|
+
# → [{ id, path, byteSize, preview, tags, metadata, ... }, ...]
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
Caller-tunable preview length is a future enhancement — file an issue if 120 chars isn't enough.
|
|
482
|
+
|
|
483
|
+
### Incremental rebuilds: "what changed since X"
|
|
484
|
+
|
|
485
|
+
The SSG / sync pattern. Two equivalent forms — bracket-style is canonical going forward; the flat form is the same shape that ships through the REST/MCP date filter today.
|
|
486
|
+
|
|
487
|
+
```bash
|
|
488
|
+
# Bracket-style (canonical)
|
|
489
|
+
curl -H "Authorization: Bearer $VAULT_TOKEN" \
|
|
490
|
+
"http://localhost:1940/vault/default/api/notes?meta[updated_at][gte]=2026-04-01T00:00:00Z"
|
|
491
|
+
|
|
492
|
+
# Flat form (DEPRECATED in 0.4.3; planned removal 0.6.0 per vault#288)
|
|
493
|
+
curl -H "Authorization: Bearer $VAULT_TOKEN" \
|
|
494
|
+
"http://localhost:1940/vault/default/api/notes?date_field=updated_at&date_from=2026-04-01T00:00:00Z"
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
```jsonc
|
|
498
|
+
// MCP — `date_filter` accepts created_at, updated_at, or any indexed metadata field.
|
|
499
|
+
{ "name": "query-notes", "arguments": { "date_filter": { "field": "updated_at", "from": "2026-04-01T00:00:00Z" } } }
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Only `gte` and `lt` are accepted on `created_at` / `updated_at` (other operators reject with `INVALID_QUERY` — these bracket forms route to the real-column `dateFilter`, not the full metadata operator set, so the half-open `[from, to)` shape is the only expressible range). The full operator set in the next recipe applies to *metadata* fields only.
|
|
503
|
+
|
|
504
|
+
### Filter by metadata values (bracket style)
|
|
505
|
+
|
|
506
|
+
Equality on any metadata field works with no setup:
|
|
507
|
+
|
|
508
|
+
```bash
|
|
509
|
+
curl -H "Authorization: Bearer $VAULT_TOKEN" \
|
|
510
|
+
"http://localhost:1940/vault/default/api/notes?meta[meeting-type]=study-session&include_content=true"
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
Range, `in`, `not_in`, and `exists` operators require the field to be declared `indexed: true` (same gate as `order_by`):
|
|
514
|
+
|
|
515
|
+
```bash
|
|
516
|
+
# Range — both bounds AND together on one field
|
|
517
|
+
"…/notes?meta[date][gte]=2026-01-01&meta[date][lt]=2026-04-01"
|
|
518
|
+
|
|
519
|
+
# Membership — `[]` array form OR comma-separated; same result.
|
|
520
|
+
"…/notes?meta[status][in][]=active&meta[status][in][]=exploring"
|
|
521
|
+
"…/notes?meta[status][in]=active,exploring"
|
|
522
|
+
|
|
523
|
+
# Presence check
|
|
524
|
+
"…/notes?meta[deadline][exists]=true"
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
Supported operators: `eq` (default when no operator given), `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `not_in`, `exists`. Multiple `meta[…]` filters AND together. Bracket-style and shorthand on the same field in one request reject loudly — pick one form.
|
|
528
|
+
|
|
529
|
+
### Edit one line in a large note (without re-sending the whole body)
|
|
530
|
+
|
|
531
|
+
`content_edit` does surgical find-and-replace. Payload is the changed text, not the whole note. `old_text` must occur exactly once; multiple matches or zero matches reject with a "re-read and retry" error.
|
|
532
|
+
|
|
533
|
+
```jsonc
|
|
534
|
+
// MCP
|
|
535
|
+
{ "name": "update-note", "arguments": {
|
|
536
|
+
"id": "notes/decisions",
|
|
537
|
+
"content_edit": {
|
|
538
|
+
"old_text": "Status: pending",
|
|
539
|
+
"new_text": "Status: shipped (2026-05-10)"
|
|
540
|
+
},
|
|
541
|
+
"if_updated_at": "2026-05-10T12:34:56.789Z"
|
|
542
|
+
} }
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
```bash
|
|
546
|
+
# REST equivalent
|
|
547
|
+
curl -X PATCH -H "Authorization: Bearer $VAULT_TOKEN" -H "Content-Type: application/json" \
|
|
548
|
+
-d '{"content_edit":{"old_text":"Status: pending","new_text":"Status: shipped (2026-05-10)"},"if_updated_at":"…"}' \
|
|
549
|
+
"http://localhost:1940/vault/default/api/notes/notes/decisions"
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
Set `include_content: false` on the request to cut the response cost too — you get back the lean `NoteIndex` shape instead of the full updated note.
|
|
553
|
+
|
|
554
|
+
### Append to a note (no concurrency ceremony)
|
|
555
|
+
|
|
556
|
+
Atomic at the SQL layer: two concurrent appends both land in some order, never clobber. Append-only and prepend-only updates are exempt from the `if_updated_at` precondition that other mutations require — the SQL-atomic concatenation can't lose data on a stale read, so the precondition would be ceremony for no benefit. Underlying mechanic in [`core/src/notes.ts:295-322`](./core/src/notes.ts).
|
|
557
|
+
|
|
558
|
+
```jsonc
|
|
559
|
+
{ "name": "update-note", "arguments": { "id": "notes/journal/2026-05-10", "append": "\n\nEvening note: …" } }
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
`prepend` is frontmatter-aware: if the note opens with YAML frontmatter, the prepended text is automatically injected *after* the closing `---` fence so parsers that expect frontmatter at byte 0 still find it. Detection is done in the same SQL UPDATE expression, so atomicity is preserved.
|
|
563
|
+
|
|
564
|
+
### CI / public access via Tailscale Funnel
|
|
565
|
+
|
|
566
|
+
The shortest path to a public HTTPS URL for a vault you control — useful for SSG rebuilds running on GitHub Actions, Vercel, or any runner that isn't on your tailnet. See [Remote access via Tailscale Funnel](#remote-access-via-tailscale-funnel) below for the full setup.
|
|
567
|
+
|
|
435
568
|
## Data model
|
|
436
569
|
|
|
437
570
|
```
|