feat: add actions to FeedSource interface

Add listActions() and executeAction() to FeedSource for write
operations back to external services. Actions use arktype schemas
for input validation via StandardSchemaV1.

- ActionDefinition type with optional input schema
- FeedEngine routes actions with existence and ID validation
- Source IDs use reverse-domain format (aris.location, aris.tfl)
- LocationSource: update-location action with schema validation
- TflSource: set-lines-of-interest action with lineId validation
- No-op implementations for sources without actions

Co-authored-by: Ona <no-reply@ona.com>
This commit is contained in:
2026-02-15 12:26:23 +00:00
parent 4d6cac7ec8
commit 699155e0d8
29 changed files with 1169 additions and 116 deletions

View File

@@ -78,7 +78,7 @@ class MockDAVClient implements CalendarDAVClient {
describe("CalendarSource", () => {
test("has correct id", () => {
const source = new CalendarSource(new NullCredentialProvider(), "user-1")
expect(source.id).toBe("apple-calendar")
expect(source.id).toBe("aris.apple-calendar")
})
test("returns empty array when credentials are null", async () => {
@@ -121,7 +121,10 @@ describe("CalendarSource", () => {
const objects: Record<string, CalendarDAVObject[]> = {
"/cal/work": [{ url: "/cal/work/event1.ics", data: loadFixture("single-event.ics") }],
"/cal/personal": [
{ url: "/cal/personal/event2.ics", data: loadFixture("all-day-event.ics") },
{
url: "/cal/personal/event2.ics",
data: loadFixture("all-day-event.ics"),
},
],
}
const client = new MockDAVClient(
@@ -206,7 +209,12 @@ describe("CalendarSource", () => {
test("handles calendar with non-string displayName", async () => {
const objects: Record<string, CalendarDAVObject[]> = {
"/cal/weird": [{ url: "/cal/weird/event1.ics", data: loadFixture("minimal-event.ics") }],
"/cal/weird": [
{
url: "/cal/weird/event1.ics",
data: loadFixture("minimal-event.ics"),
},
],
}
const client = new MockDAVClient(
[{ url: "/cal/weird", displayName: { _cdata: "Weird Calendar" } }],
@@ -222,7 +230,12 @@ describe("CalendarSource", () => {
test("handles recurring events with exceptions", async () => {
const objects: Record<string, CalendarDAVObject[]> = {
"/cal/work": [{ url: "/cal/work/recurring.ics", data: loadFixture("recurring-event.ics") }],
"/cal/work": [
{
url: "/cal/work/recurring.ics",
data: loadFixture("recurring-event.ics"),
},
],
}
const client = new MockDAVClient([{ url: "/cal/work", displayName: "Work" }], objects)
const source = new CalendarSource(new MockCredentialProvider(), "user-1", {