diff --git a/internal/ipc/api.go b/internal/ipc/api.go index 8128700..113c825 100644 --- a/internal/ipc/api.go +++ b/internal/ipc/api.go @@ -594,7 +594,7 @@ type setTaskFieldsReq struct { BlockedOn string `json:"blocked_on,omitempty"` } -// idReq — methods keyed by a single id. +// idReq — methods keyed by a single id, which is every routine transition. type idReq struct { ID int64 `json:"id"` } @@ -652,6 +652,11 @@ type calendarEventsReq struct { type revertReq struct { Key string `json:"key"` } + +// revertResp — the id of the voiding fact the revert wrote. +type revertResp struct { + NewID int64 `json:"new_id"` +} type writeNoteReq struct { Ts time.Time `json:"ts"` Text string `json:"text"` @@ -780,14 +785,6 @@ type listProposedRoutinesResp struct { Routines []ProposedRoutine `json:"routines"` } -type dismissProposedRoutineReq struct { - ID int64 `json:"id"` -} - -type acceptProposedRoutineReq struct { - ID int64 `json:"id"` -} - // IntakeEvent — one entry of the unified intake journal on the wire. Mirrors // event.Event field for field; the ipc package does not import internal/event // so the wire shape stays independent of the in-process type. diff --git a/internal/ipc/client.go b/internal/ipc/client.go index ccba692..946ed37 100644 --- a/internal/ipc/client.go +++ b/internal/ipc/client.go @@ -649,11 +649,11 @@ func (c *Client) ModelStatus(ctx context.Context) (ModelStatusResp, error) { } func (c *Client) DismissProposedRoutine(ctx context.Context, id int64) error { - return c.call(ctx, MethodDismissProposedRoutine, dismissProposedRoutineReq{ID: id}, nil) + return c.call(ctx, MethodDismissProposedRoutine, idReq{ID: id}, nil) } func (c *Client) AcceptProposedRoutine(ctx context.Context, id int64) error { - return c.call(ctx, MethodAcceptProposedRoutine, acceptProposedRoutineReq{ID: id}, nil) + return c.call(ctx, MethodAcceptProposedRoutine, idReq{ID: id}, nil) } func (c *Client) Chat(ctx context.Context, conversation, text string) (ChatReply, error) { @@ -713,13 +713,11 @@ func (c *Client) DayPlan(ctx context.Context) (DayPlan, error) { } func (c *Client) RevertFact(ctx context.Context, key string) (int64, error) { - var result struct { - NewID int64 `json:"new_id"` - } - if err := c.call(ctx, MethodRevertFact, map[string]string{"key": key}, &result); err != nil { + var r revertResp + if err := c.call(ctx, MethodRevertFact, revertReq{Key: key}, &r); err != nil { return 0, err } - return result.NewID, nil + return r.NewID, nil } // Ping asks whether the daemon is there, and whether it is locked. It is not a diff --git a/internal/ipc/server.go b/internal/ipc/server.go index b421aed..4d035a3 100644 --- a/internal/ipc/server.go +++ b/internal/ipc/server.go @@ -335,6 +335,22 @@ func withoutParams[R any](fn func(ctx context.Context, api CoreAPI) (R, error)) } } +// withParamsSlice is withParams for a list read. It replaces a nil slice with +// an empty one so the wire carries [] rather than null, which every reader of +// these methods relies on. +func withParamsSlice[P any, E any](fn func(ctx context.Context, api CoreAPI, p P) ([]E, error)) handlerFunc { + return withParams(func(ctx context.Context, api CoreAPI, p P) ([]E, error) { + out, err := fn(ctx, api, p) + if err != nil { + return nil, err + } + if out == nil { + out = []E{} + } + return out, nil + }) +} + // methodTable — one entry per CoreAPI-backed method. Built once at package // init, not per-Server and not per-dispatch: entries close over nothing but // the CoreAPI method being called, and dispatch passes in the *current* @@ -373,15 +389,8 @@ var methodTable = map[Method]handlerFunc{ MethodMarkReminder: withParamsVoid(func(ctx context.Context, api CoreAPI, p markReminderReq) error { return api.MarkReminder(ctx, p.ID, p.Status) }), - MethodListReminders: withParams(func(ctx context.Context, api CoreAPI, p nReq) ([]Reminder, error) { - out, err := api.ListReminders(ctx, p.N) - if err != nil { - return nil, err - } - if out == nil { - out = []Reminder{} - } - return out, nil + MethodListReminders: withParamsSlice(func(ctx context.Context, api CoreAPI, p nReq) ([]Reminder, error) { + return api.ListReminders(ctx, p.N) }), MethodRecordNudge: withParams(func(ctx context.Context, api CoreAPI, p recordNudgeReq) (idResp, error) { id, err := api.RecordNudge(ctx, p.Rule, p.Channel, p.Message, p.Ts) @@ -390,109 +399,39 @@ var methodTable = map[Method]handlerFunc{ MethodResolveNudge: withParamsVoid(func(ctx context.Context, api CoreAPI, p resolveNudgeReq) error { return api.ResolveNudge(ctx, p.ID, p.Outcome, p.Ts) }), - MethodRecentOutcomes: withParams(func(ctx context.Context, api CoreAPI, p outcomesReq) ([]string, error) { - out, err := api.RecentOutcomes(ctx, p.Rule, p.N) - if err != nil { - return nil, err - } - if out == nil { - out = []string{} // stable non-null on the wire - } - return out, nil + MethodRecentOutcomes: withParamsSlice(func(ctx context.Context, api CoreAPI, p outcomesReq) ([]string, error) { + return api.RecentOutcomes(ctx, p.Rule, p.N) }), - MethodRecentFacts: withParams(func(ctx context.Context, api CoreAPI, p nReq) ([]Fact, error) { - out, err := api.RecentFacts(ctx, p.N) - if err != nil { - return nil, err - } - if out == nil { - out = []Fact{} - } - return out, nil + MethodRecentFacts: withParamsSlice(func(ctx context.Context, api CoreAPI, p nReq) ([]Fact, error) { + return api.RecentFacts(ctx, p.N) }), - MethodRecentActiveFacts: withParams(func(ctx context.Context, api CoreAPI, p kindNReq) ([]Fact, error) { - out, err := api.RecentActiveFactsByKind(ctx, p.Kind, p.N) - if err != nil { - return nil, err - } - if out == nil { - out = []Fact{} - } - return out, nil + MethodRecentActiveFacts: withParamsSlice(func(ctx context.Context, api CoreAPI, p kindNReq) ([]Fact, error) { + return api.RecentActiveFactsByKind(ctx, p.Kind, p.N) }), - MethodCalendarEvents: withParams(func(ctx context.Context, api CoreAPI, p calendarEventsReq) ([]Fact, error) { - out, err := api.CalendarEvents(ctx, p.From, p.To) - if err != nil { - return nil, err - } - if out == nil { - out = []Fact{} - } - return out, nil + MethodCalendarEvents: withParamsSlice(func(ctx context.Context, api CoreAPI, p calendarEventsReq) ([]Fact, error) { + return api.CalendarEvents(ctx, p.From, p.To) }), - MethodRecentEcoTraces: withParams(func(ctx context.Context, api CoreAPI, p nReq) ([]EcosystemTrace, error) { - out, err := api.RecentEcosystemTraces(ctx, p.N) - if err != nil { - return nil, err - } - if out == nil { - out = []EcosystemTrace{} - } - return out, nil + MethodRecentEcoTraces: withParamsSlice(func(ctx context.Context, api CoreAPI, p nReq) ([]EcosystemTrace, error) { + return api.RecentEcosystemTraces(ctx, p.N) }), - MethodDeliveryAttempts: withParams(func(ctx context.Context, api CoreAPI, p deliveryAttemptsReq) ([]DeliveryAttempt, error) { - out, err := api.DeliveryAttempts(ctx, p.Status, p.N) - if err != nil { - return nil, err - } - if out == nil { - out = []DeliveryAttempt{} - } - return out, nil + MethodDeliveryAttempts: withParamsSlice(func(ctx context.Context, api CoreAPI, p deliveryAttemptsReq) ([]DeliveryAttempt, error) { + return api.DeliveryAttempts(ctx, p.Status, p.N) }), - MethodRecentNudges: withParams(func(ctx context.Context, api CoreAPI, p nReq) ([]Nudge, error) { - out, err := api.RecentNudges(ctx, p.N) - if err != nil { - return nil, err - } - if out == nil { - out = []Nudge{} - } - return out, nil + MethodRecentNudges: withParamsSlice(func(ctx context.Context, api CoreAPI, p nReq) ([]Nudge, error) { + return api.RecentNudges(ctx, p.N) }), MethodWriteNote: withParams(func(ctx context.Context, api CoreAPI, p writeNoteReq) (idResp, error) { id, err := api.WriteNote(ctx, p.Ts, p.Text, p.Embedding, p.Source) return idResp{ID: id}, err }), - MethodQueryNotes: withParams(func(ctx context.Context, api CoreAPI, p queryNotesReq) ([]Note, error) { - out, err := api.QueryNotes(ctx, p.Embedding, p.K) - if err != nil { - return nil, err - } - if out == nil { - out = []Note{} - } - return out, nil + MethodQueryNotes: withParamsSlice(func(ctx context.Context, api CoreAPI, p queryNotesReq) ([]Note, error) { + return api.QueryNotes(ctx, p.Embedding, p.K) }), - MethodRecentNotesFromSource: withParams(func(ctx context.Context, api CoreAPI, p sourceNReq) ([]Note, error) { - out, err := api.RecentNotesFromSource(ctx, p.Prefix, p.N) - if err != nil { - return nil, err - } - if out == nil { - out = []Note{} - } - return out, nil + MethodRecentNotesFromSource: withParamsSlice(func(ctx context.Context, api CoreAPI, p sourceNReq) ([]Note, error) { + return api.RecentNotesFromSource(ctx, p.Prefix, p.N) }), - MethodRecentNotes: withParams(func(ctx context.Context, api CoreAPI, p nReq) ([]Note, error) { - out, err := api.RecentNotes(ctx, p.N) - if err != nil { - return nil, err - } - if out == nil { - out = []Note{} - } - return out, nil + MethodRecentNotes: withParamsSlice(func(ctx context.Context, api CoreAPI, p nReq) ([]Note, error) { + return api.RecentNotes(ctx, p.N) }), MethodProposeTool: withParams(func(ctx context.Context, api CoreAPI, p proposeToolReq) (proposeToolResp, error) { ok, err := api.ProposeTool(ctx, p.Name, p.Utterance, p.Scope, p.Ts) @@ -563,18 +502,15 @@ var methodTable = map[Method]handlerFunc{ } return listProposedRoutinesResp{Routines: out}, nil }), - MethodDismissProposedRoutine: withParamsVoid(func(ctx context.Context, api CoreAPI, p dismissProposedRoutineReq) error { + MethodDismissProposedRoutine: withParamsVoid(func(ctx context.Context, api CoreAPI, p idReq) error { return api.DismissProposedRoutine(ctx, p.ID) }), - MethodAcceptProposedRoutine: withParamsVoid(func(ctx context.Context, api CoreAPI, p acceptProposedRoutineReq) error { + MethodAcceptProposedRoutine: withParamsVoid(func(ctx context.Context, api CoreAPI, p idReq) error { return api.AcceptProposedRoutine(ctx, p.ID) }), - MethodRevertFact: withParams(func(ctx context.Context, api CoreAPI, p revertReq) (map[string]int64, error) { + MethodRevertFact: withParams(func(ctx context.Context, api CoreAPI, p revertReq) (revertResp, error) { newID, err := api.RevertFact(ctx, p.Key) - if err != nil { - return nil, err - } - return map[string]int64{"new_id": newID}, nil + return revertResp{NewID: newID}, err }), MethodChat: withParams(func(ctx context.Context, api CoreAPI, p chatReq) (chatResp, error) { reply, err := api.Chat(ctx, p.Conversation, p.Text) @@ -599,15 +535,8 @@ var methodTable = map[Method]handlerFunc{ MethodMorningStatus: withoutParams(func(ctx context.Context, api CoreAPI) ([]MorningRoutineStatus, error) { return api.MorningStatus(ctx) }), - MethodRecentEvents: withParams(func(ctx context.Context, api CoreAPI, p nReq) ([]IntakeEvent, error) { - out, err := api.RecentEvents(ctx, p.N) - if err != nil { - return nil, err - } - if out == nil { - out = []IntakeEvent{} - } - return out, nil + MethodRecentEvents: withParamsSlice(func(ctx context.Context, api CoreAPI, p nReq) ([]IntakeEvent, error) { + return api.RecentEvents(ctx, p.N) }), MethodMCPServers: withoutParams(func(ctx context.Context, api CoreAPI) ([]MCPServerStatus, error) { out, err := api.MCPServers(ctx) @@ -654,180 +583,113 @@ func (s *Server) dispatch(ctx context.Context, req Request) (json.RawMessage, er return marshalResult(PingResp{Alive: true, Locked: locked}), nil case MethodAssertStepUp: - if s.StepUp != nil { - return marshalResult(nil), s.StepUp(ctx) + if s.StepUp == nil { + return nil, unknownMethod(req.Method) } - return nil, fmt.Errorf("%w: %s", ErrUnknownMethod, req.Method) + return marshalResult(nil), s.StepUp(ctx) case MethodStoreEncryptionKey: - if s.WrapKeyFn != nil { - var p storeEncryptionKeyReq - if err := unmarshalParams(req.Params, &p); err != nil { - return nil, err - } - return marshalResult(nil), s.WrapKeyFn(ctx, p.Secret, p.Explicit) + if s.WrapKeyFn == nil { + return nil, unknownMethod(req.Method) } - return nil, fmt.Errorf("%w: %s", ErrUnknownMethod, req.Method) + var p storeEncryptionKeyReq + if err := unmarshalParams(req.Params, &p); err != nil { + return nil, err + } + return marshalResult(nil), s.WrapKeyFn(ctx, p.Secret, p.Explicit) case MethodUnlock: - if s.UnlockFn != nil { - var p unlockReq - if err := unmarshalParams(req.Params, &p); err != nil { - return nil, err - } - return marshalResult(nil), s.UnlockFn(ctx, p.Secret) + if s.UnlockFn == nil { + return nil, unknownMethod(req.Method) } - return nil, fmt.Errorf("%w: %s", ErrUnknownMethod, req.Method) + var p unlockReq + if err := unmarshalParams(req.Params, &p); err != nil { + return nil, err + } + return marshalResult(nil), s.UnlockFn(ctx, p.Secret) case MethodIngestMail: - if s.IngestMailFn != nil { - var p IngestMailReq - if err := unmarshalParams(req.Params, &p); err != nil { - return nil, err - } - resp, err := s.IngestMailFn(ctx, p) - if err != nil { - return nil, err - } - return marshalResult(resp), nil - } - return nil, fmt.Errorf("%w: %s", ErrUnknownMethod, req.Method) - + return callDirect(ctx, req, s.IngestMailFn) case MethodSwapModel: - if s.SwapModelFn != nil { - var p SwapModelReq - if err := unmarshalParams(req.Params, &p); err != nil { - return nil, err - } - resp, err := s.SwapModelFn(ctx, p) - if err != nil { - return nil, err - } - return marshalResult(resp), nil - } - return nil, fmt.Errorf("%w: %s", ErrUnknownMethod, req.Method) - + return callDirect(ctx, req, s.SwapModelFn) case MethodDescribeImage: - if s.DescribeImageFn != nil { - var p DescribeImageReq - if err := unmarshalParams(req.Params, &p); err != nil { - return nil, err - } - resp, err := s.DescribeImageFn(ctx, p) - if err != nil { - return nil, err - } - return marshalResult(resp), nil - } - return nil, fmt.Errorf("%w: %s", ErrUnknownMethod, req.Method) - + return callDirect(ctx, req, s.DescribeImageFn) case MethodCaptureStart: - if s.CaptureStartFn != nil { - var p CaptureStartReq - if err := unmarshalParams(req.Params, &p); err != nil { - return nil, err - } - resp, err := s.CaptureStartFn(ctx, p) - if err != nil { - return nil, err - } - return marshalResult(resp), nil - } - return nil, fmt.Errorf("%w: %s", ErrUnknownMethod, req.Method) - + return callDirect(ctx, req, s.CaptureStartFn) case MethodCaptureAppend: - if s.CaptureAppendFn != nil { - var p CaptureAppendReq - if err := unmarshalParams(req.Params, &p); err != nil { - return nil, err - } - resp, err := s.CaptureAppendFn(ctx, p) - if err != nil { - return nil, err - } - return marshalResult(resp), nil - } - return nil, fmt.Errorf("%w: %s", ErrUnknownMethod, req.Method) - + return callDirect(ctx, req, s.CaptureAppendFn) case MethodCaptureStop: - if s.CaptureStopFn != nil { - var p CaptureStopReq - if err := unmarshalParams(req.Params, &p); err != nil { - return nil, err - } - resp, err := s.CaptureStopFn(ctx, p) - if err != nil { - return nil, err - } - return marshalResult(resp), nil - } - return nil, fmt.Errorf("%w: %s", ErrUnknownMethod, req.Method) - - case MethodCaptureStatus: - if s.CaptureStatusFn != nil { - resp, err := s.CaptureStatusFn(ctx) - if err != nil { - return nil, err - } - return marshalResult(resp), nil - } - return nil, fmt.Errorf("%w: %s", ErrUnknownMethod, req.Method) - + return callDirect(ctx, req, s.CaptureStopFn) case MethodEnrollSpeaker: - if s.EnrollSpeakerFn != nil { - var p EnrollSpeakerReq - if err := unmarshalParams(req.Params, &p); err != nil { - return nil, err - } - resp, err := s.EnrollSpeakerFn(ctx, p) - if err != nil { - return nil, err - } - return marshalResult(resp), nil - } - return nil, fmt.Errorf("%w: %s", ErrUnknownMethod, req.Method) - + return callDirect(ctx, req, s.EnrollSpeakerFn) + case MethodCaptureStatus: + return callDirectNoParams(ctx, req, s.CaptureStatusFn) case MethodListSpeakers: - if s.ListSpeakersFn != nil { - resp, err := s.ListSpeakersFn(ctx) - if err != nil { - return nil, err - } - return marshalResult(resp), nil - } - return nil, fmt.Errorf("%w: %s", ErrUnknownMethod, req.Method) - - case MethodForgetSpeaker: - if s.ForgetSpeakerFn != nil { - var p ForgetSpeakerReq - if err := unmarshalParams(req.Params, &p); err != nil { - return nil, err - } - if err := s.ForgetSpeakerFn(ctx, p); err != nil { - return nil, err - } - return marshalResult(nil), nil - } - return nil, fmt.Errorf("%w: %s", ErrUnknownMethod, req.Method) - + return callDirectNoParams(ctx, req, s.ListSpeakersFn) case MethodModelStatus: - if s.ModelStatusFn != nil { - resp, err := s.ModelStatusFn(ctx) - if err != nil { - return nil, err - } - return marshalResult(resp), nil - } - return nil, fmt.Errorf("%w: %s", ErrUnknownMethod, req.Method) + return callDirectNoParams(ctx, req, s.ModelStatusFn) + case MethodForgetSpeaker: + return callDirectVoid(ctx, req, s.ForgetSpeakerFn) } h, ok := methodTable[req.Method] if !ok { - return nil, fmt.Errorf("%w: %s", ErrUnknownMethod, req.Method) + return nil, unknownMethod(req.Method) } return h(ctx, api, req.Params) } +// callDirect runs a daemon-supplied handler that bypasses CoreAPI: unmarshal +// the params, call it, marshal the reply. A nil handler is the capability being +// unconfigured on this box, and the wire says so as an unknown method. +func callDirect[P any, R any](ctx context.Context, req Request, fn func(context.Context, P) (R, error)) (json.RawMessage, error) { + if fn == nil { + return nil, unknownMethod(req.Method) + } + var p P + if err := unmarshalParams(req.Params, &p); err != nil { + return nil, err + } + r, err := fn(ctx, p) + if err != nil { + return nil, err + } + return marshalResult(r), nil +} + +// callDirectNoParams is callDirect for a handler that reads no params. Like +// withoutParams it never touches req.Params. +func callDirectNoParams[R any](ctx context.Context, req Request, fn func(context.Context) (R, error)) (json.RawMessage, error) { + if fn == nil { + return nil, unknownMethod(req.Method) + } + r, err := fn(ctx) + if err != nil { + return nil, err + } + return marshalResult(r), nil +} + +// callDirectVoid is callDirect for a handler with nothing to report back. The +// wire reply is always null. +func callDirectVoid[P any](ctx context.Context, req Request, fn func(context.Context, P) error) (json.RawMessage, error) { + if fn == nil { + return nil, unknownMethod(req.Method) + } + var p P + if err := unmarshalParams(req.Params, &p); err != nil { + return nil, err + } + if err := fn(ctx, p); err != nil { + return nil, err + } + return marshalResult(nil), nil +} + +func unknownMethod(m Method) error { + return fmt.Errorf("%w: %s", ErrUnknownMethod, m) +} + func unmarshalParams(raw json.RawMessage, v any) error { if len(raw) == 0 { raw = []byte("null") diff --git a/internal/ipc/storeapi.go b/internal/ipc/storeapi.go index 6158d36..21fd7da 100644 --- a/internal/ipc/storeapi.go +++ b/internal/ipc/storeapi.go @@ -77,18 +77,24 @@ func (a *storeAPI) MarkReminder(ctx context.Context, id int64, status string) er return mapErr(a.s.MarkReminder(ctx, id, status)) } -func (a *storeAPI) ListReminders(ctx context.Context, n int) ([]Reminder, error) { - rs, err := a.s.ListReminders(ctx, n) +// mapRows carries a store read's error through mapErr and converts the rows to +// their wire shape. Every list method here is that one shape. +func mapRows[S any, W any](rows []S, err error, conv func(S) W) ([]W, error) { if err != nil { return nil, mapErr(err) } - out := make([]Reminder, len(rs)) - for i, r := range rs { - out[i] = toReminder(r) + out := make([]W, len(rows)) + for i, r := range rows { + out[i] = conv(r) } return out, nil } +func (a *storeAPI) ListReminders(ctx context.Context, n int) ([]Reminder, error) { + rs, err := a.s.ListReminders(ctx, n) + return mapRows(rs, err, toReminder) +} + func (a *storeAPI) RescheduleReminder(ctx context.Context, id int64, now time.Time) error { return mapErr(a.s.RescheduleReminder(ctx, id, now)) } @@ -109,85 +115,48 @@ func (a *storeAPI) RecentOutcomes(ctx context.Context, rule string, n int) ([]st func (a *storeAPI) RecentFacts(ctx context.Context, n int) ([]Fact, error) { fs, err := a.s.RecentFacts(ctx, n) - if err != nil { - return nil, mapErr(err) - } - out := make([]Fact, len(fs)) - for i, f := range fs { - out[i] = toFact(f) - } - return out, nil + return mapRows(fs, err, toFact) } func (a *storeAPI) RecentActiveFactsByKind(ctx context.Context, kind string, n int) ([]Fact, error) { fs, err := a.s.RecentActiveFactsByKind(ctx, store.FactKind(kind), n) - if err != nil { - return nil, mapErr(err) - } - out := make([]Fact, len(fs)) - for i, f := range fs { - out[i] = toFact(f) - } - return out, nil + return mapRows(fs, err, toFact) } func (a *storeAPI) CalendarEvents(ctx context.Context, from, to time.Time) ([]Fact, error) { fs, err := a.s.CalendarEvents(ctx, from, to) - if err != nil { - return nil, mapErr(err) - } - out := make([]Fact, len(fs)) - for i, f := range fs { - out[i] = toFact(f) - } - return out, nil + return mapRows(fs, err, toFact) } func (a *storeAPI) RecentEcosystemTraces(ctx context.Context, n int) ([]EcosystemTrace, error) { trs, err := a.s.RecentEcosystemTraces(ctx, n) - if err != nil { - return nil, mapErr(err) - } - out := make([]EcosystemTrace, len(trs)) - for i, tr := range trs { - out[i] = EcosystemTrace{ + return mapRows(trs, err, func(tr store.EcosystemTrace) EcosystemTrace { + return EcosystemTrace{ ID: tr.ID, Ts: tr.Ts, Service: tr.Service, Operation: tr.Operation, Status: tr.Status, DurationMs: tr.DurationMs, CorrelationID: tr.CorrelationID, CausationID: tr.CausationID, HTTPStatus: tr.HTTPStatus, Fields: tr.Fields, } - } - return out, nil + }) } func (a *storeAPI) RecentNudges(ctx context.Context, n int) ([]Nudge, error) { ns, err := a.s.RecentNudges(ctx, n) - if err != nil { - return nil, mapErr(err) - } - out := make([]Nudge, len(ns)) - for i, ng := range ns { - out[i] = toNudge(ng) - } - return out, nil + return mapRows(ns, err, toNudge) } func (a *storeAPI) DeliveryAttempts(ctx context.Context, status string, n int) ([]DeliveryAttempt, error) { as, err := a.s.ListDeliveryAttempts(ctx, status, n) - if err != nil { - return nil, mapErr(err) - } - out := make([]DeliveryAttempt, len(as)) - for i, at := range as { - out[i] = DeliveryAttempt{ + return mapRows(as, err, func(at store.DeliveryAttempt) DeliveryAttempt { + out := DeliveryAttempt{ ID: at.ID, Kind: at.Kind, Rule: at.Rule, ReminderID: at.ReminderID, Channel: at.Channel, Status: at.Status, Created: at.Created, } if at.HasComplete { t := at.Completed - out[i].Completed = &t + out.Completed = &t } - } - return out, nil + return out + }) } func (a *storeAPI) WriteNote(ctx context.Context, ts time.Time, text string, embedding []float32, source string) (int64, error) { @@ -197,38 +166,17 @@ func (a *storeAPI) WriteNote(ctx context.Context, ts time.Time, text string, emb func (a *storeAPI) QueryNotes(ctx context.Context, embedding []float32, k int) ([]Note, error) { ns, err := a.s.QueryNotes(ctx, embedding, k) - if err != nil { - return nil, mapErr(err) - } - out := make([]Note, len(ns)) - for i, n := range ns { - out[i] = toNote(n) - } - return out, nil + return mapRows(ns, err, toNote) } func (a *storeAPI) RecentNotesFromSource(ctx context.Context, prefix string, n int) ([]Note, error) { ns, err := a.s.RecentNotesFromSource(ctx, prefix, n) - if err != nil { - return nil, mapErr(err) - } - out := make([]Note, len(ns)) - for i, note := range ns { - out[i] = toNote(note) - } - return out, nil + return mapRows(ns, err, toNote) } func (a *storeAPI) RecentNotes(ctx context.Context, n int) ([]Note, error) { ns, err := a.s.RecentNotes(ctx, n) - if err != nil { - return nil, mapErr(err) - } - out := make([]Note, len(ns)) - for i, note := range ns { - out[i] = toNote(note) - } - return out, nil + return mapRows(ns, err, toNote) } func (a *storeAPI) ProposeTool(ctx context.Context, name, utterance, scope string, ts time.Time) (bool, error) { @@ -299,14 +247,7 @@ func (a *storeAPI) DayPlan(ctx context.Context) (DayPlan, error) { func (a *storeAPI) ListTools(ctx context.Context, status string) ([]Tool, error) { ts, err := a.s.ListTools(ctx, status) - if err != nil { - return nil, mapErr(err) - } - out := make([]Tool, len(ts)) - for i, t := range ts { - out[i] = toTool(t) - } - return out, nil + return mapRows(ts, err, toTool) } func (a *storeAPI) DeleteTool(ctx context.Context, name string) error { @@ -334,12 +275,8 @@ func (a *storeAPI) CaptureTask(ctx context.Context, req CaptureTaskReq) (Capture func (a *storeAPI) ListTasks(ctx context.Context, status string) ([]Task, error) { ts, err := a.s.ListTasks(ctx, status) - if err != nil { - return nil, mapErr(err) - } - out := make([]Task, len(ts)) - for i, t := range ts { - out[i] = Task{ + return mapRows(ts, err, func(t store.Task) Task { + return Task{ ID: t.ID, CreatedTs: t.CreatedTs, Text: t.Text, @@ -354,8 +291,7 @@ func (a *storeAPI) ListTasks(ctx context.Context, status string) ([]Task, error) DoneWhen: t.DoneWhen, BlockedOn: t.BlockedOn, } - } - return out, nil + }) } func (a *storeAPI) SetTaskStatus(ctx context.Context, id int64, status string, ts time.Time, by string) error { @@ -379,24 +315,17 @@ func (a *storeAPI) SetTaskFields(ctx context.Context, id int64, doneWhen, blocke func (a *storeAPI) ListProposedRoutines(ctx context.Context) ([]ProposedRoutine, error) { rs, err := a.s.ListProposedRoutines(ctx) - if err != nil { - return nil, mapErr(err) - } - out := make([]ProposedRoutine, len(rs)) - for i, r := range rs { - out[i] = ProposedRoutine{ + return mapRows(rs, err, func(r store.ProposedRoutine) ProposedRoutine { + return ProposedRoutine{ ID: r.ID, Action: r.Action, Object: r.Object, IntervalDays: r.IntervalDays, Status: string(r.Status), CreatedTs: r.CreatedTs.UnixMilli(), + ReminderID: r.ReminderID, } - if r.ReminderID != nil { - out[i].ReminderID = r.ReminderID - } - } - return out, nil + }) } func (a *storeAPI) DismissProposedRoutine(ctx context.Context, id int64) error {