@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/core/src/store.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
loadTagHierarchy,
|
|
12
12
|
getTagDescendants,
|
|
13
13
|
TAG_CONFIG_PREFIX,
|
|
14
|
+
DEFAULT_TAG_NAME,
|
|
14
15
|
type TagHierarchy,
|
|
15
16
|
} from "./tag-hierarchy.js";
|
|
16
17
|
import {
|
|
@@ -19,7 +20,6 @@ import {
|
|
|
19
20
|
type ResolvedSchemas,
|
|
20
21
|
type ValidationStatus,
|
|
21
22
|
} from "./schema-defaults.js";
|
|
22
|
-
import * as noteSchemaOps from "./note-schemas.js";
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* bun:sqlite-backed Store implementation. Internally everything is
|
|
@@ -29,12 +29,11 @@ import * as noteSchemaOps from "./note-schemas.js";
|
|
|
29
29
|
export class BunSqliteStore implements Store {
|
|
30
30
|
public readonly hooks: HookRegistry;
|
|
31
31
|
|
|
32
|
-
// Lazy-built caches over the post-v14 `tags` table
|
|
33
|
-
// parent_names
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
// post-write state.
|
|
32
|
+
// Lazy-built caches over the post-v14 `tags` table — hierarchy via
|
|
33
|
+
// parent_names, schema validation via `fields`. Null means "not yet
|
|
34
|
+
// loaded or invalidated"; the next read rebuilds. We invalidate
|
|
35
|
+
// synchronously inside the writers that mutate the source rows so reads
|
|
36
|
+
// after writes always see the post-write state.
|
|
38
37
|
private _tagHierarchy: TagHierarchy | null = null;
|
|
39
38
|
private _schemaConfig: ResolvedSchemas | null = null;
|
|
40
39
|
|
|
@@ -55,8 +54,8 @@ export class BunSqliteStore implements Store {
|
|
|
55
54
|
}
|
|
56
55
|
|
|
57
56
|
/**
|
|
58
|
-
* Lazy accessor for the
|
|
59
|
-
*
|
|
57
|
+
* Lazy accessor for the per-tag `fields` resolution. Same lifecycle as
|
|
58
|
+
* the tag hierarchy cache.
|
|
60
59
|
*/
|
|
61
60
|
private getSchemaConfig(): ResolvedSchemas {
|
|
62
61
|
if (!this._schemaConfig) this._schemaConfig = loadSchemaConfig(this.db);
|
|
@@ -79,9 +78,9 @@ export class BunSqliteStore implements Store {
|
|
|
79
78
|
* alongside new for rename cases (a note moved out of `_tags/` should
|
|
80
79
|
* still invalidate).
|
|
81
80
|
*
|
|
82
|
-
* Post-
|
|
83
|
-
* invalidation hook is on `
|
|
84
|
-
* `
|
|
81
|
+
* Post-v17 the schema-config cache is purely tag-driven — its
|
|
82
|
+
* invalidation hook is on `upsertTagSchema` / `upsertTagRecord` /
|
|
83
|
+
* `deleteTagSchema` / `deleteTag` (mutations of `tags.fields`).
|
|
85
84
|
*/
|
|
86
85
|
private invalidateConfigCachesForPath(path: string | null | undefined, oldPath?: string | null): void {
|
|
87
86
|
const isTagConfig = (p: string | null | undefined): boolean =>
|
|
@@ -219,16 +218,45 @@ export class BunSqliteStore implements Store {
|
|
|
219
218
|
* each input tag is replaced with `{tag} ∪ descendants(tag)`. The SQL
|
|
220
219
|
* builder uses this to widen the tag join from `name = ?` to
|
|
221
220
|
* `name IN (...)`, so a query for `#manual` matches notes tagged with
|
|
222
|
-
* any descendant declared via `
|
|
221
|
+
* any descendant declared via `tags.parent_names`.
|
|
223
222
|
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
223
|
+
* `_default` magic (vault#270): when a `_default` tag row exists in the
|
|
224
|
+
* vault, it's the implicit parent of every note (tagged or not). A query
|
|
225
|
+
* filter that names `_default` is therefore equivalent to "no tag filter
|
|
226
|
+
* at all" — but the precise treatment depends on `tagMatch`:
|
|
227
|
+
*
|
|
228
|
+
* - `all` (default, AND-semantics): `_default` is universally satisfied,
|
|
229
|
+
* so it can be dropped from the tag list. Other tags' AND-semantics
|
|
230
|
+
* still apply. If `_default` was the only entry, drop the filter
|
|
231
|
+
* entirely so untagged notes match.
|
|
232
|
+
* - `any` (OR-semantics): `_default` matches every note, so the disjunction
|
|
233
|
+
* collapses to "every note." Drop the filter entirely regardless of
|
|
234
|
+
* what else was in the list (otherwise we'd narrow to the union of
|
|
235
|
+
* the other tags' notes — wrong).
|
|
236
|
+
*
|
|
237
|
+
* Other filters (path, metadata, dates) still apply in both cases.
|
|
226
238
|
*/
|
|
227
239
|
private expandQueryTags(opts: QueryOpts): QueryOpts {
|
|
228
240
|
if (!opts.tags || opts.tags.length === 0) return opts;
|
|
229
241
|
const hierarchy = this.getTagHierarchy();
|
|
242
|
+
|
|
243
|
+
let tags = opts.tags;
|
|
244
|
+
if (hierarchy.allTags.has(DEFAULT_TAG_NAME) && tags.includes(DEFAULT_TAG_NAME)) {
|
|
245
|
+
const match = opts.tagMatch ?? "all";
|
|
246
|
+
if (match === "any") {
|
|
247
|
+
const { tags: _drop, ..._rest } = opts;
|
|
248
|
+
return _rest as QueryOpts;
|
|
249
|
+
}
|
|
250
|
+
tags = tags.filter((t) => t !== DEFAULT_TAG_NAME);
|
|
251
|
+
if (tags.length === 0) {
|
|
252
|
+
const { tags: _drop, ..._rest } = opts;
|
|
253
|
+
return _rest as QueryOpts;
|
|
254
|
+
}
|
|
255
|
+
opts = { ...opts, tags };
|
|
256
|
+
}
|
|
257
|
+
|
|
230
258
|
if (hierarchy.childrenOf.size === 0) return opts;
|
|
231
|
-
const expanded =
|
|
259
|
+
const expanded = tags.map((t) => Array.from(getTagDescendants(hierarchy, t)));
|
|
232
260
|
return { ...opts, _tagsExpanded: expanded } as QueryOpts;
|
|
233
261
|
}
|
|
234
262
|
|
|
@@ -237,9 +265,16 @@ export class BunSqliteStore implements Store {
|
|
|
237
265
|
// should match notes tagged with any descendant tag. The underlying
|
|
238
266
|
// FTS path already uses `IN (...)` for tags, so we flatten the
|
|
239
267
|
// per-input expansions into a single union (search semantics are
|
|
240
|
-
// "any tag matches").
|
|
268
|
+
// "any tag matches"). When `_default` is among the requested tags
|
|
269
|
+
// (and a `_default` row exists), the OR collapses to "every note" —
|
|
270
|
+
// drop the tag filter entirely so the search hits the full corpus
|
|
271
|
+
// and untagged notes are reachable.
|
|
241
272
|
if (opts?.tags && opts.tags.length > 0) {
|
|
242
273
|
const hierarchy = this.getTagHierarchy();
|
|
274
|
+
if (hierarchy.allTags.has(DEFAULT_TAG_NAME) && opts.tags.includes(DEFAULT_TAG_NAME)) {
|
|
275
|
+
const { tags: _drop, ..._rest } = opts;
|
|
276
|
+
return noteOps.searchNotes(this.db, query, _rest);
|
|
277
|
+
}
|
|
243
278
|
if (hierarchy.childrenOf.size > 0) {
|
|
244
279
|
const expanded = new Set<string>();
|
|
245
280
|
for (const t of opts.tags) {
|
|
@@ -277,17 +312,22 @@ export class BunSqliteStore implements Store {
|
|
|
277
312
|
|
|
278
313
|
async deleteTag(name: string): Promise<{ deleted: boolean; notes_untagged: number }> {
|
|
279
314
|
const result = noteOps.deleteTag(this.db, name);
|
|
280
|
-
// The deleted tag may have been a parent or child in the hierarchy
|
|
315
|
+
// The deleted tag may have been a parent or child in the hierarchy
|
|
316
|
+
// and may have declared `fields` powering schema validation.
|
|
281
317
|
this._tagHierarchy = null;
|
|
318
|
+
this._schemaConfig = null;
|
|
282
319
|
return result;
|
|
283
320
|
}
|
|
284
321
|
|
|
285
322
|
async renameTag(oldName: string, newName: string): Promise<noteOps.RenameTagResult> {
|
|
286
323
|
const result = noteOps.renameTag(this.db, oldName, newName);
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
//
|
|
324
|
+
// Vault#240: the cascade rewrites parent_names in OTHER tag rows as
|
|
325
|
+
// part of the same transaction, plus tokens.scoped_tags and
|
|
326
|
+
// indexed_fields.declarer_tags. Both caches are tag-keyed, so they
|
|
327
|
+
// must be rebuilt regardless — the hierarchy by tag-set identity,
|
|
328
|
+
// the schema-config by parent_names + fields content.
|
|
290
329
|
this._tagHierarchy = null;
|
|
330
|
+
this._schemaConfig = null;
|
|
291
331
|
return result;
|
|
292
332
|
}
|
|
293
333
|
|
|
@@ -297,8 +337,10 @@ export class BunSqliteStore implements Store {
|
|
|
297
337
|
): Promise<{ merged: Record<string, number>; target: string }> {
|
|
298
338
|
const result = noteOps.mergeTags(this.db, sources, target);
|
|
299
339
|
// Source tags drop out of the hierarchy; downstream callers asking
|
|
300
|
-
// for descendants of target should pick up any merged children.
|
|
340
|
+
// for descendants of target should pick up any merged children. Also
|
|
341
|
+
// bust the schema cache — `fields` declarations follow tag identity.
|
|
301
342
|
this._tagHierarchy = null;
|
|
343
|
+
this._schemaConfig = null;
|
|
302
344
|
return result;
|
|
303
345
|
}
|
|
304
346
|
|
|
@@ -332,9 +374,9 @@ export class BunSqliteStore implements Store {
|
|
|
332
374
|
const notes = noteOps.createNotes(this.db, inputs);
|
|
333
375
|
for (const note of notes) {
|
|
334
376
|
// Bulk path needs the same config-cache invalidation as singleton
|
|
335
|
-
// createNote — without it, a batch that includes `_tags/*`
|
|
336
|
-
//
|
|
337
|
-
//
|
|
377
|
+
// createNote — without it, a batch that includes `_tags/*` notes
|
|
378
|
+
// would leave the hierarchy cache stale until the next singleton
|
|
379
|
+
// write happened to bust it.
|
|
338
380
|
this.invalidateConfigCachesForPath(note.path);
|
|
339
381
|
this.hooks.dispatch("created", note, this);
|
|
340
382
|
}
|
|
@@ -370,11 +412,17 @@ export class BunSqliteStore implements Store {
|
|
|
370
412
|
}
|
|
371
413
|
|
|
372
414
|
async upsertTagSchema(tag: string, schema: { description?: string; fields?: Record<string, tagSchemaOps.TagFieldSchema> }) {
|
|
373
|
-
|
|
415
|
+
const result = tagSchemaOps.upsertTagSchema(this.db, tag, schema);
|
|
416
|
+
// `fields` drives validation — bust the schema cache so the next
|
|
417
|
+
// create/update sees the new declarations.
|
|
418
|
+
this._schemaConfig = null;
|
|
419
|
+
return result;
|
|
374
420
|
}
|
|
375
421
|
|
|
376
422
|
async deleteTagSchema(tag: string) {
|
|
377
|
-
|
|
423
|
+
const result = tagSchemaOps.deleteTagSchema(this.db, tag);
|
|
424
|
+
if (result) this._schemaConfig = null;
|
|
425
|
+
return result;
|
|
378
426
|
}
|
|
379
427
|
|
|
380
428
|
async getTagSchemaMap() {
|
|
@@ -407,7 +455,20 @@ export class BunSqliteStore implements Store {
|
|
|
407
455
|
) {
|
|
408
456
|
const result = tagSchemaOps.upsertTagRecord(this.db, tag, patch);
|
|
409
457
|
if (patch.parent_names !== undefined) {
|
|
458
|
+
// parent_names drives both query expansion (tag hierarchy) AND, post
|
|
459
|
+
// vault#270, schema inheritance — bust both caches.
|
|
460
|
+
this._tagHierarchy = null;
|
|
461
|
+
this._schemaConfig = null;
|
|
462
|
+
}
|
|
463
|
+
if (patch.fields !== undefined) {
|
|
464
|
+
this._schemaConfig = null;
|
|
465
|
+
}
|
|
466
|
+
// First-time creation of a tag row (e.g. an empty `_default` placeholder)
|
|
467
|
+
// changes the `_default` universal-parent gate even when no fields or
|
|
468
|
+
// parent_names are touched. Cheap to bust: caches rebuild on next read.
|
|
469
|
+
if (tag === "_default") {
|
|
410
470
|
this._tagHierarchy = null;
|
|
471
|
+
this._schemaConfig = null;
|
|
411
472
|
}
|
|
412
473
|
return result;
|
|
413
474
|
}
|
|
@@ -431,63 +492,14 @@ export class BunSqliteStore implements Store {
|
|
|
431
492
|
/**
|
|
432
493
|
* Drop the config caches unconditionally. Used by bulk-import paths that
|
|
433
494
|
* skip per-note invalidation for throughput, and by importers that
|
|
434
|
-
* directly
|
|
495
|
+
* directly mutate `tags` / `tags.fields` outside the singleton write
|
|
496
|
+
* methods.
|
|
435
497
|
*/
|
|
436
498
|
rebuildConfigCaches(): void {
|
|
437
499
|
this._tagHierarchy = null;
|
|
438
500
|
this._schemaConfig = null;
|
|
439
501
|
}
|
|
440
502
|
|
|
441
|
-
// ---- Note schemas (post-v15: validation by path-prefix or tag) ----
|
|
442
|
-
|
|
443
|
-
async listNoteSchemas() {
|
|
444
|
-
return noteSchemaOps.listNoteSchemas(this.db);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
async getNoteSchema(name: string) {
|
|
448
|
-
return noteSchemaOps.getNoteSchema(this.db, name);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* Partial-upsert a note schema. Auto-creates the row if missing. Any
|
|
453
|
-
* patch field left undefined is preserved; pass null to clear. Empty
|
|
454
|
-
* `required: []` collapses to null. Invalidates the schema-config cache.
|
|
455
|
-
*/
|
|
456
|
-
async upsertNoteSchema(name: string, patch: noteSchemaOps.NoteSchemaPatch) {
|
|
457
|
-
const result = noteSchemaOps.upsertNoteSchema(this.db, name, patch);
|
|
458
|
-
this._schemaConfig = null;
|
|
459
|
-
return result;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
async deleteNoteSchema(name: string) {
|
|
463
|
-
const removed = noteSchemaOps.deleteNoteSchema(this.db, name);
|
|
464
|
-
if (removed) this._schemaConfig = null;
|
|
465
|
-
return removed;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
async listSchemaMappings(opts?: noteSchemaOps.ListMappingsOpts) {
|
|
469
|
-
return noteSchemaOps.listSchemaMappings(this.db, opts ?? {});
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
async setSchemaMapping(
|
|
473
|
-
schema_name: string,
|
|
474
|
-
match_kind: noteSchemaOps.SchemaMappingKind,
|
|
475
|
-
match_value: string,
|
|
476
|
-
) {
|
|
477
|
-
noteSchemaOps.setSchemaMapping(this.db, schema_name, match_kind, match_value);
|
|
478
|
-
this._schemaConfig = null;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
async deleteSchemaMapping(
|
|
482
|
-
schema_name: string,
|
|
483
|
-
match_kind: noteSchemaOps.SchemaMappingKind,
|
|
484
|
-
match_value: string,
|
|
485
|
-
) {
|
|
486
|
-
const removed = noteSchemaOps.deleteSchemaMapping(this.db, schema_name, match_kind, match_value);
|
|
487
|
-
if (removed) this._schemaConfig = null;
|
|
488
|
-
return removed;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
503
|
/**
|
|
492
504
|
* Sync wikilinks for all notes in the vault.
|
|
493
505
|
* Efficient for bulk imports — call once after importing all notes.
|
|
@@ -32,8 +32,23 @@ export interface TagHierarchy {
|
|
|
32
32
|
childrenOf: Map<string, Set<string>>;
|
|
33
33
|
/** Memoization cache: tag → set including the tag itself plus all transitive descendants. */
|
|
34
34
|
descendantsCache: Map<string, Set<string>>;
|
|
35
|
+
/**
|
|
36
|
+
* Every known tag name in the vault. Loaded once at hierarchy build so the
|
|
37
|
+
* `_default` universal-parent magic in `getTagDescendants` can answer
|
|
38
|
+
* "expand `_default` → every tag" without a second DB scan.
|
|
39
|
+
*/
|
|
40
|
+
allTags: Set<string>;
|
|
35
41
|
}
|
|
36
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Reserved tag name treated as the implicit universal parent of every other
|
|
45
|
+
* tag (vault#270). When a row named `_default` exists in the `tags` table,
|
|
46
|
+
* its schema applies to all notes — tagged or not — and tag-hierarchy queries
|
|
47
|
+
* for `_default` expand to the full tag list. The `tags.parent_names` column
|
|
48
|
+
* is never auto-mutated; the universal-parent property lives at resolve time.
|
|
49
|
+
*/
|
|
50
|
+
export const DEFAULT_TAG_NAME = "_default";
|
|
51
|
+
|
|
37
52
|
/**
|
|
38
53
|
* Pre-v14 path prefix that marked a note as a tag-hierarchy declaration.
|
|
39
54
|
* Retained as an exported constant so call-sites that still need to know
|
|
@@ -59,16 +74,21 @@ function readParentNames(raw: string | null): string[] {
|
|
|
59
74
|
/**
|
|
60
75
|
* Scan the `tags` table and build the parent→children adjacency map.
|
|
61
76
|
* Each row's `parent_names` JSON array contributes one edge per parent.
|
|
77
|
+
* Also collects every known tag name in `allTags` so the `_default`
|
|
78
|
+
* universal-parent leg in `getTagDescendants` can return the full tag list
|
|
79
|
+
* without a second scan.
|
|
62
80
|
*/
|
|
63
81
|
export function loadTagHierarchy(db: Database): TagHierarchy {
|
|
64
82
|
const rows = db.prepare(
|
|
65
|
-
`SELECT name, parent_names FROM tags
|
|
83
|
+
`SELECT name, parent_names FROM tags`,
|
|
66
84
|
).all() as { name: string; parent_names: string | null }[];
|
|
67
85
|
|
|
68
86
|
const childrenOf = new Map<string, Set<string>>();
|
|
87
|
+
const allTags = new Set<string>();
|
|
69
88
|
|
|
70
89
|
for (const row of rows) {
|
|
71
90
|
if (!row.name) continue;
|
|
91
|
+
allTags.add(row.name);
|
|
72
92
|
const parents = readParentNames(row.parent_names);
|
|
73
93
|
for (const parent of parents) {
|
|
74
94
|
let children = childrenOf.get(parent);
|
|
@@ -80,18 +100,32 @@ export function loadTagHierarchy(db: Database): TagHierarchy {
|
|
|
80
100
|
}
|
|
81
101
|
}
|
|
82
102
|
|
|
83
|
-
return { childrenOf, descendantsCache: new Map() };
|
|
103
|
+
return { childrenOf, descendantsCache: new Map(), allTags };
|
|
84
104
|
}
|
|
85
105
|
|
|
86
106
|
/**
|
|
87
107
|
* Return the tag plus all transitive descendants. Always includes the tag
|
|
88
108
|
* itself, so callers can use the result as a drop-in replacement for the
|
|
89
109
|
* input tag when expanding queries.
|
|
110
|
+
*
|
|
111
|
+
* Special case: `_default` is treated as the implicit parent of every tag
|
|
112
|
+
* (vault#270). When a row named `_default` exists in the `tags` table, this
|
|
113
|
+
* function returns the full set of known tag names — symmetric with the
|
|
114
|
+
* schema-inheritance model where `_default`'s fields apply to every note.
|
|
115
|
+
* When `_default` is *not* declared as a tag row, the call falls through to
|
|
116
|
+
* normal descendant traversal (which yields just `{_default}` since nothing
|
|
117
|
+
* lists it as a parent).
|
|
90
118
|
*/
|
|
91
119
|
export function getTagDescendants(h: TagHierarchy, tag: string): Set<string> {
|
|
92
120
|
const cached = h.descendantsCache.get(tag);
|
|
93
121
|
if (cached) return cached;
|
|
94
122
|
|
|
123
|
+
if (tag === DEFAULT_TAG_NAME && h.allTags.has(DEFAULT_TAG_NAME)) {
|
|
124
|
+
const universal = new Set<string>(h.allTags);
|
|
125
|
+
h.descendantsCache.set(tag, universal);
|
|
126
|
+
return universal;
|
|
127
|
+
}
|
|
128
|
+
|
|
95
129
|
const result = new Set<string>([tag]);
|
|
96
130
|
const stack = [tag];
|
|
97
131
|
while (stack.length > 0) {
|
package/core/src/types.ts
CHANGED
|
@@ -1,23 +1,8 @@
|
|
|
1
1
|
import type { TagFieldSchema, TagRelationship, TagRecord } from "./tag-schemas.js";
|
|
2
|
-
import type {
|
|
3
|
-
NoteSchemaField,
|
|
4
|
-
NoteSchemaRecord,
|
|
5
|
-
NoteSchemaPatch,
|
|
6
|
-
SchemaMappingKind,
|
|
7
|
-
SchemaMappingRecord,
|
|
8
|
-
ListMappingsOpts,
|
|
9
|
-
} from "./note-schemas.js";
|
|
10
2
|
|
|
11
3
|
// ---- Re-exports ----
|
|
12
4
|
|
|
13
5
|
export type { TagFieldSchema, TagRelationship, TagRecord } from "./tag-schemas.js";
|
|
14
|
-
export type {
|
|
15
|
-
NoteSchemaField,
|
|
16
|
-
NoteSchemaRecord,
|
|
17
|
-
NoteSchemaPatch,
|
|
18
|
-
SchemaMappingKind,
|
|
19
|
-
SchemaMappingRecord,
|
|
20
|
-
} from "./note-schemas.js";
|
|
21
6
|
|
|
22
7
|
// ---- Note ----
|
|
23
8
|
|
|
@@ -95,12 +80,14 @@ export interface QueryOpts {
|
|
|
95
80
|
// as the common path; specifying both this and `dateFilter` rejects.
|
|
96
81
|
dateFrom?: string; // ISO date
|
|
97
82
|
dateTo?: string; // ISO date
|
|
98
|
-
// Generalized date range. `field` defaults to `created_at`;
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
83
|
+
// Generalized date range. `field` defaults to `created_at`; `updated_at`
|
|
84
|
+
// is also a recognized real column (the incremental-rebuild path —
|
|
85
|
+
// vault#285 1.5). Any other field must be declared `indexed: true` in a
|
|
86
|
+
// tag schema (so the SQL hits a real B-tree index, same contract as
|
|
87
|
+
// `metadata` operator queries and `orderBy`). Use this to filter on a
|
|
88
|
+
// *content* date — an email's received date, a meeting's scheduled
|
|
89
|
+
// date — rather than the ingestion timestamp, or on `updated_at` to ask
|
|
90
|
+
// "what changed since X."
|
|
104
91
|
dateFilter?: {
|
|
105
92
|
field?: string;
|
|
106
93
|
from?: string;
|
|
@@ -175,7 +162,19 @@ export interface Store {
|
|
|
175
162
|
renameTag(
|
|
176
163
|
oldName: string,
|
|
177
164
|
newName: string,
|
|
178
|
-
): Promise<
|
|
165
|
+
): Promise<
|
|
166
|
+
| {
|
|
167
|
+
renamed: number;
|
|
168
|
+
sub_tags_renamed: number;
|
|
169
|
+
parent_refs_updated: number;
|
|
170
|
+
tokens_updated: number;
|
|
171
|
+
indexed_field_declarers_updated: number;
|
|
172
|
+
notes_rewritten: number;
|
|
173
|
+
paths_renamed: number;
|
|
174
|
+
}
|
|
175
|
+
| { error: "not_found" }
|
|
176
|
+
| { error: "target_exists"; conflicting: string[] }
|
|
177
|
+
>;
|
|
179
178
|
mergeTags(
|
|
180
179
|
sources: string[],
|
|
181
180
|
target: string,
|
|
@@ -228,30 +227,26 @@ export interface Store {
|
|
|
228
227
|
},
|
|
229
228
|
): Promise<TagRecord>;
|
|
230
229
|
|
|
231
|
-
// Schema validation (post-
|
|
232
|
-
//
|
|
233
|
-
//
|
|
230
|
+
// Schema validation (post-v17: backed by `tags.fields` only — the
|
|
231
|
+
// standalone note_schemas + schema_mappings subsystem retired in v17, see
|
|
232
|
+
// vault#267). Post vault#270 the resolver walks `parent_names` so a note's
|
|
233
|
+
// effective fields include all ancestors' declarations (first-in-walk wins
|
|
234
|
+
// on conflict, surfaced as `schema_conflict` warnings); a tag named
|
|
235
|
+
// `_default` is the implicit universal parent. Returns null when no
|
|
236
|
+
// ancestor declares any fields. The underlying resolver is in-memory after
|
|
237
|
+
// the first lazy load.
|
|
234
238
|
validateNoteAgainstSchemas(note: { path?: string | null; tags?: string[]; metadata?: Record<string, unknown> }): {
|
|
235
239
|
schemas: string[];
|
|
236
|
-
warnings: {
|
|
240
|
+
warnings: {
|
|
241
|
+
field: string;
|
|
242
|
+
schema: string;
|
|
243
|
+
reason: "type_mismatch" | "enum_mismatch" | "schema_conflict";
|
|
244
|
+
message: string;
|
|
245
|
+
/** Set only on `schema_conflict` — the tag whose declaration was overridden. */
|
|
246
|
+
loser_schema?: string;
|
|
247
|
+
}[];
|
|
237
248
|
} | null;
|
|
238
249
|
|
|
239
|
-
// Note schemas (post-v15 — the writable surface that drives validation).
|
|
240
|
-
listNoteSchemas(): Promise<NoteSchemaRecord[]>;
|
|
241
|
-
getNoteSchema(name: string): Promise<NoteSchemaRecord | null>;
|
|
242
|
-
/**
|
|
243
|
-
* Partial-upsert. Auto-creates the row if missing. Any patch field left
|
|
244
|
-
* undefined is preserved; pass null to clear. Empty `required: []`
|
|
245
|
-
* collapses to null.
|
|
246
|
-
*/
|
|
247
|
-
upsertNoteSchema(name: string, patch: NoteSchemaPatch): Promise<NoteSchemaRecord>;
|
|
248
|
-
deleteNoteSchema(name: string): Promise<boolean>;
|
|
249
|
-
|
|
250
|
-
// Schema mappings (post-v15 — replaces the singleton `_schema_defaults`).
|
|
251
|
-
listSchemaMappings(opts?: ListMappingsOpts): Promise<SchemaMappingRecord[]>;
|
|
252
|
-
setSchemaMapping(schema_name: string, match_kind: SchemaMappingKind, match_value: string): Promise<void>;
|
|
253
|
-
deleteSchemaMapping(schema_name: string, match_kind: SchemaMappingKind, match_value: string): Promise<boolean>;
|
|
254
|
-
|
|
255
250
|
// Attachments
|
|
256
251
|
addAttachment(noteId: string, path: string, mimeType: string, metadata?: Record<string, unknown>): Promise<Attachment>;
|
|
257
252
|
getAttachments(noteId: string): Promise<Attachment[]>;
|