@openparachute/vault 0.6.4-rc.2 → 0.6.4-rc.4
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 +38 -1
- package/core/src/enforced-writes.test.ts +533 -0
- package/core/src/mcp.ts +183 -4
- package/core/src/migrate-tag-field.test.ts +471 -0
- package/core/src/migrate-tag-field.ts +638 -0
- package/core/src/notes.ts +128 -4
- package/core/src/query-operators.ts +117 -0
- package/core/src/schema-defaults.ts +162 -9
- package/core/src/tag-schemas.ts +12 -0
- package/core/src/triggers-store.ts +6 -0
- package/core/src/types.ts +2 -11
- package/core/src/vault-projection.ts +19 -0
- package/package.json +1 -1
- package/src/cli.ts +164 -3
- package/src/config.ts +11 -0
- package/src/mcp-http.ts +27 -0
- package/src/mcp-tools.ts +15 -3
- package/src/routes.ts +133 -3
- package/src/routing.ts +10 -2
- package/src/scopes.test.ts +24 -0
- package/src/scopes.ts +63 -0
- package/src/triggers-api.ts +24 -0
- package/src/triggers.test.ts +33 -0
- package/src/triggers.ts +17 -0
package/src/triggers.test.ts
CHANGED
|
@@ -95,6 +95,39 @@ describe("buildPredicate", () => {
|
|
|
95
95
|
expect(pred(makeNote({ tags: ["reader"] }))).toBe(false);
|
|
96
96
|
expect(pred(makeNote({ tags: ["reader", "important"] }))).toBe(true);
|
|
97
97
|
});
|
|
98
|
+
|
|
99
|
+
// Value-matched metadata grammar (vault#299 Part B).
|
|
100
|
+
it("metadata eq: fires only when the field equals the value", () => {
|
|
101
|
+
const pred = buildPredicate({ metadata: { state: { eq: "published" } } }, "publish_hook");
|
|
102
|
+
expect(pred(makeNote({ metadata: { state: "published" } }))).toBe(true);
|
|
103
|
+
expect(pred(makeNote({ metadata: { state: "drafted" } }))).toBe(false);
|
|
104
|
+
expect(pred(makeNote({ metadata: {} }))).toBe(false); // absent → no fire
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("metadata in: fires on membership", () => {
|
|
108
|
+
const pred = buildPredicate({ metadata: { state: { in: ["produced", "published"] } } }, "h");
|
|
109
|
+
expect(pred(makeNote({ metadata: { state: "produced" } }))).toBe(true);
|
|
110
|
+
expect(pred(makeNote({ metadata: { state: "idea" } }))).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("metadata combines with tag + presence filters (all must hold)", () => {
|
|
114
|
+
const pred = buildPredicate(
|
|
115
|
+
{ tags: ["piece"], metadata: { state: { eq: "published" } }, missing_metadata: ["notified_at"] },
|
|
116
|
+
"h",
|
|
117
|
+
);
|
|
118
|
+
expect(pred(makeNote({ tags: ["piece"], metadata: { state: "published" } }))).toBe(true);
|
|
119
|
+
// right state, wrong tag.
|
|
120
|
+
expect(pred(makeNote({ tags: ["other"], metadata: { state: "published" } }))).toBe(false);
|
|
121
|
+
// right tag + state but already notified.
|
|
122
|
+
expect(
|
|
123
|
+
pred(makeNote({ tags: ["piece"], metadata: { state: "published", notified_at: "x" } })),
|
|
124
|
+
).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("a malformed operator never throws from the predicate (treated as no-match)", () => {
|
|
128
|
+
const pred = buildPredicate({ metadata: { state: { bogus: "x" } as any } }, "h");
|
|
129
|
+
expect(pred(makeNote({ metadata: { state: "published" } }))).toBe(false);
|
|
130
|
+
});
|
|
98
131
|
});
|
|
99
132
|
|
|
100
133
|
describe("registerTriggers — dispatch modes", async () => {
|
package/src/triggers.ts
CHANGED
|
@@ -30,6 +30,7 @@ import { mkdirSync, readFileSync, writeFileSync, existsSync } from "fs";
|
|
|
30
30
|
import crypto from "node:crypto";
|
|
31
31
|
import type { Note, Store, Attachment } from "../core/src/types.ts";
|
|
32
32
|
import type { HookRegistry, HookEvent, NoteHookPayload } from "../core/src/hooks.ts";
|
|
33
|
+
import { matchesOperator } from "../core/src/query-operators.ts";
|
|
33
34
|
import type { TriggerConfig, TriggerWhen } from "./config.ts";
|
|
34
35
|
import type { StoredTrigger } from "../core/src/triggers-store.ts";
|
|
35
36
|
import { getVaultNameForStore } from "./vault-store.ts";
|
|
@@ -106,6 +107,22 @@ export function buildPredicate(when: TriggerWhen, triggerName: string): (note: N
|
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
109
|
|
|
110
|
+
// Value-matched metadata filter (vault#299 Part B). Each field's
|
|
111
|
+
// operator-object is evaluated against the note's live metadata with the
|
|
112
|
+
// SAME engine query-notes uses. ALL fields must match (AND). A malformed
|
|
113
|
+
// operator throws at registration-time validation, not here — by the time
|
|
114
|
+
// the predicate runs the shape is known-good, but we guard defensively so
|
|
115
|
+
// a bad config can never crash the hook dispatch loop (treat as no-match).
|
|
116
|
+
if (when.metadata) {
|
|
117
|
+
for (const [field, opObj] of Object.entries(when.metadata)) {
|
|
118
|
+
try {
|
|
119
|
+
if (!matchesOperator(field, meta?.[field], opObj)) return false;
|
|
120
|
+
} catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
109
126
|
return true;
|
|
110
127
|
};
|
|
111
128
|
}
|