Detailed changes
@@ -24,7 +24,7 @@ task # Include vulnerability and copyright checks
Create methods return `(nil, nil)` when a matching entity already exists (HTTP 204). This is intentional API behavior, not an error:
```go
-task, err := lunatask.NewTask("Review PR").Create(ctx, client)
+task, err := client.NewTask("Review PR").Create(ctx)
if err != nil {
return err // actual error
}
@@ -53,7 +53,7 @@ func main() {
}
// Create a task
- task, err := lunatask.NewTask("Review pull requests").Create(context.Background(), client)
+ task, err := client.NewTask("Review pull requests").Create(context.Background())
if err != nil {
log.Fatal(err)
}
@@ -70,7 +70,7 @@ exists. This is intentional API behavior on Lunatask's part because of
its end-to-end encryption.
```go
-task, err := lunatask.NewTask("Review PR").Create(ctx, client)
+task, err := client.NewTask("Review PR").Create(ctx)
if err != nil {
return err // actual error
}
@@ -39,16 +39,17 @@ type journalEntryResponse struct {
// Journal content is encrypted client-side; the API accepts it on create but
// returns null on read.
//
-// entry, err := lunatask.NewJournalEntry(lunatask.Today()).
+// entry, err := client.NewJournalEntry(lunatask.Today()).
// WithContent("Shipped the new feature!").
-// Create(ctx, client)
+// Create(ctx)
type JournalEntryBuilder struct {
- req createJournalEntryRequest
+ client *Client
+ req createJournalEntryRequest
}
// NewJournalEntry starts building a journal entry for the given date.
-func NewJournalEntry(date Date) *JournalEntryBuilder {
- return &JournalEntryBuilder{req: createJournalEntryRequest{DateOn: date}} //nolint:exhaustruct
+func (c *Client) NewJournalEntry(date Date) *JournalEntryBuilder {
+ return &JournalEntryBuilder{client: c, req: createJournalEntryRequest{DateOn: date}}
}
// WithName sets the entry's title. Defaults to the weekday name if omitted.
@@ -67,12 +68,12 @@ func (b *JournalEntryBuilder) WithContent(content string) *JournalEntryBuilder {
// Create sends the journal entry to Lunatask. Returns the created entry's metadata;
// Name and Content won't round-trip due to E2EE.
-func (b *JournalEntryBuilder) Create(ctx context.Context, c *Client) (*JournalEntry, error) {
+func (b *JournalEntryBuilder) Create(ctx context.Context) (*JournalEntry, error) {
if b.req.DateOn.IsZero() {
return nil, fmt.Errorf("%w: date_on is required", ErrBadRequest)
}
- resp, _, err := doJSON[journalEntryResponse](ctx, c, http.MethodPost, "/journal_entries", b.req)
+ resp, _, err := doJSON[journalEntryResponse](ctx, b.client, http.MethodPost, "/journal_entries", b.req)
if err != nil {
return nil, err
}
@@ -23,9 +23,9 @@ func TestCreateJournalEntry_Success(t *testing.T) {
entryDate := lunatask.NewDate(time.Date(2021, 1, 10, 0, 0, 0, 0, time.UTC))
- entry, err := lunatask.NewJournalEntry(entryDate).
+ entry, err := client.NewJournalEntry(entryDate).
WithContent("Today was a tough day, but on the other side...").
- Create(ctx(), client)
+ Create(ctx())
if err != nil {
t.Fatalf("error = %v", err)
}
@@ -52,10 +52,10 @@ func TestCreateJournalEntry_WithName(t *testing.T) {
entryDate := lunatask.NewDate(time.Date(2021, 1, 10, 0, 0, 0, 0, time.UTC))
- _, err := lunatask.NewJournalEntry(entryDate).
+ _, err := client.NewJournalEntry(entryDate).
WithName("Custom Title").
WithContent("Some content").
- Create(ctx(), client)
+ Create(ctx())
if err != nil {
t.Fatalf("error = %v", err)
}
@@ -70,7 +70,7 @@ func TestCreateJournalEntry_Errors(t *testing.T) {
testErrorCases(t, func(c *lunatask.Client) error {
entryDate := lunatask.NewDate(time.Date(2021, 1, 10, 0, 0, 0, 0, time.UTC))
- _, err := lunatask.NewJournalEntry(entryDate).Create(ctx(), c)
+ _, err := c.NewJournalEntry(entryDate).Create(ctx())
return err //nolint:wrapcheck // test helper
})
@@ -84,18 +84,19 @@ func (c *Client) DeleteNote(ctx context.Context, noteID string) (*Note, error) {
// Note fields are encrypted client-side by Lunatask; the API accepts them
// on create but returns null on read.
//
-// note, err := lunatask.NewNote().
+// note, err := client.NewNote().
// WithName("Meeting notes").
// WithContent("# Summary\n\n...").
// InNotebook(notebookID).
-// Create(ctx, client)
+// Create(ctx)
type NoteBuilder struct {
- req createNoteRequest
+ client *Client
+ req createNoteRequest
}
// NewNote starts building a note.
-func NewNote() *NoteBuilder {
- return &NoteBuilder{} //nolint:exhaustruct
+func (c *Client) NewNote() *NoteBuilder {
+ return &NoteBuilder{client: c}
}
// WithName sets the note's title.
@@ -130,24 +131,25 @@ func (b *NoteBuilder) FromSource(source, sourceID string) *NoteBuilder {
// Create sends the note to Lunatask. Returns (nil, nil) if a duplicate exists
// in the same notebook with matching source/source_id.
-func (b *NoteBuilder) Create(ctx context.Context, c *Client) (*Note, error) {
- return create(ctx, c, "/notes", b.req, func(r noteResponse) Note { return r.Note })
+func (b *NoteBuilder) Create(ctx context.Context) (*Note, error) {
+ return create(ctx, b.client, "/notes", b.req, func(r noteResponse) Note { return r.Note })
}
// NoteUpdateBuilder constructs and updates a note via method chaining.
// Only fields you set will be modified; others remain unchanged.
//
-// note, err := lunatask.NewNoteUpdate(noteID).
+// note, err := client.NewNoteUpdate(noteID).
// WithContent("# Updated content").
-// Update(ctx, client)
+// Update(ctx)
type NoteUpdateBuilder struct {
+ client *Client
noteID string
req updateNoteRequest
}
// NewNoteUpdate starts building a note update for the given note ID.
-func NewNoteUpdate(noteID string) *NoteUpdateBuilder {
- return &NoteUpdateBuilder{noteID: noteID} //nolint:exhaustruct
+func (c *Client) NewNoteUpdate(noteID string) *NoteUpdateBuilder {
+ return &NoteUpdateBuilder{client: c, noteID: noteID}
}
// WithName sets the note's title.
@@ -179,6 +181,6 @@ func (b *NoteUpdateBuilder) OnDate(date Date) *NoteUpdateBuilder {
}
// Update sends the changes to Lunatask.
-func (b *NoteUpdateBuilder) Update(ctx context.Context, c *Client) (*Note, error) {
- return update(ctx, c, "/notes", b.noteID, "note", b.req, func(r noteResponse) Note { return r.Note })
+func (b *NoteUpdateBuilder) Update(ctx context.Context) (*Note, error) {
+ return update(ctx, b.client, "/notes", b.noteID, "note", b.req, func(r noteResponse) Note { return r.Note })
}
@@ -133,12 +133,12 @@ func TestCreateNote_Success(t *testing.T) {
client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
- note, err := lunatask.NewNote().
+ note, err := client.NewNote().
WithName("My note").
WithContent("Note content").
InNotebook(notebookID).
FromSource("evernote", "ext-123").
- Create(ctx(), client)
+ Create(ctx())
if err != nil {
t.Fatalf("error = %v", err)
}
@@ -168,11 +168,11 @@ func TestCreateNote_Duplicate(t *testing.T) {
client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
- note, err := lunatask.NewNote().
+ note, err := client.NewNote().
WithName("Duplicate").
InNotebook(notebookID).
FromSource("evernote", "dup-123").
- Create(ctx(), client)
+ Create(ctx())
if err != nil {
t.Fatalf("error = %v, want nil", err)
}
@@ -186,7 +186,7 @@ func TestCreateNote_Errors(t *testing.T) {
t.Parallel()
testErrorCases(t, func(c *lunatask.Client) error {
- _, err := lunatask.NewNote().WithName("x").Create(ctx(), c)
+ _, err := c.NewNote().WithName("x").Create(ctx())
return err //nolint:wrapcheck // test helper
})
@@ -202,10 +202,10 @@ func TestUpdateNote_Success(t *testing.T) {
client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
- note, err := lunatask.NewNoteUpdate(noteID).
+ note, err := client.NewNoteUpdate(noteID).
WithName("Updated name").
WithContent("New content").
- Update(ctx(), client)
+ Update(ctx())
if err != nil {
t.Fatalf("error = %v", err)
}
@@ -227,12 +227,12 @@ func TestUpdateNote_AllFields(t *testing.T) {
client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
dateOn := lunatask.NewDate(time.Date(2024, 5, 10, 0, 0, 0, 0, time.UTC))
- _, err := lunatask.NewNoteUpdate(noteID).
+ _, err := client.NewNoteUpdate(noteID).
WithName("Full update").
WithContent("Full content").
InNotebook("new-notebook-id").
OnDate(dateOn).
- Update(ctx(), client)
+ Update(ctx())
if err != nil {
t.Fatalf("error = %v", err)
}
@@ -247,7 +247,7 @@ func TestUpdateNote_Errors(t *testing.T) {
t.Parallel()
testErrorCases(t, func(c *lunatask.Client) error {
- _, err := lunatask.NewNoteUpdate(noteID).WithName("x").Update(ctx(), c)
+ _, err := c.NewNoteUpdate(noteID).WithName("x").Update(ctx())
return err //nolint:wrapcheck // test helper
})
@@ -113,16 +113,20 @@ func (c *Client) DeletePerson(ctx context.Context, personID string) (*Person, er
// Name fields are encrypted client-side; the API accepts them on create but
// returns null on read.
//
-// person, err := lunatask.NewPerson("Ada", "Lovelace").
+// person, err := client.NewPerson("Ada", "Lovelace").
// WithRelationshipStrength(lunatask.RelationshipCloseFriend).
-// Create(ctx, client)
+// Create(ctx)
type PersonBuilder struct {
- req createPersonRequest
+ client *Client
+ req createPersonRequest
}
// NewPerson starts building a person entry with the given name.
-func NewPerson(firstName, lastName string) *PersonBuilder {
- return &PersonBuilder{req: createPersonRequest{FirstName: &firstName, LastName: &lastName}} //nolint:exhaustruct
+func (c *Client) NewPerson(firstName, lastName string) *PersonBuilder {
+ return &PersonBuilder{
+ client: c,
+ req: createPersonRequest{FirstName: &firstName, LastName: &lastName},
+ }
}
// WithRelationshipStrength categorizes the closeness of the relationship.
@@ -157,6 +161,6 @@ func (b *PersonBuilder) WithCustomField(key string, value any) *PersonBuilder {
// Create sends the person to Lunatask.
// Returns (nil, nil) if a duplicate exists with matching source/source_id.
-func (b *PersonBuilder) Create(ctx context.Context, c *Client) (*Person, error) {
- return create(ctx, c, "/people", b.req, func(r personResponse) Person { return r.Person })
+func (b *PersonBuilder) Create(ctx context.Context) (*Person, error) {
+ return create(ctx, b.client, "/people", b.req, func(r personResponse) Person { return r.Person })
}
@@ -134,10 +134,10 @@ func TestCreatePerson_Success(t *testing.T) {
client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
- person, err := lunatask.NewPerson("John", "Doe").
+ person, err := client.NewPerson("John", "Doe").
WithRelationshipStrength(lunatask.RelationshipBusiness).
FromSource("salesforce", "sf-123").
- Create(ctx(), client)
+ Create(ctx())
if err != nil {
t.Fatalf("error = %v", err)
}
@@ -165,11 +165,11 @@ func TestCreatePerson_WithCustomFields(t *testing.T) {
client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
- _, err := lunatask.NewPerson("Ada", "Lovelace").
+ _, err := client.NewPerson("Ada", "Lovelace").
WithRelationshipStrength(lunatask.RelationshipCloseFriend).
WithCustomField("email", "ada@example.com").
WithCustomField("phone", "+1234567890").
- Create(ctx(), client)
+ Create(ctx())
if err != nil {
t.Fatalf("error = %v", err)
}
@@ -191,9 +191,9 @@ func TestCreatePerson_Duplicate(t *testing.T) {
client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
- person, err := lunatask.NewPerson("Dup", "Person").
+ person, err := client.NewPerson("Dup", "Person").
FromSource("salesforce", "dup-123").
- Create(ctx(), client)
+ Create(ctx())
if err != nil {
t.Fatalf("error = %v, want nil", err)
}
@@ -207,7 +207,7 @@ func TestCreatePerson_Errors(t *testing.T) {
t.Parallel()
testErrorCases(t, func(c *lunatask.Client) error {
- _, err := lunatask.NewPerson("Test", "User").Create(ctx(), c)
+ _, err := c.NewPerson("Test", "User").Create(ctx())
return err //nolint:wrapcheck // test helper
})
@@ -107,18 +107,19 @@ func (c *Client) DeleteTask(ctx context.Context, taskID string) (*Task, error) {
// TaskBuilder constructs and creates a task via method chaining.
//
-// task, err := lunatask.NewTask("Review PR").
+// task, err := client.NewTask("Review PR").
// InArea(areaID).
// WithStatus(lunatask.StatusNext).
// WithEstimate(30).
-// Create(ctx, client)
+// Create(ctx)
type TaskBuilder struct {
- req createTaskRequest
+ client *Client
+ req createTaskRequest
}
// NewTask starts building a task with the given name.
-func NewTask(name string) *TaskBuilder {
- return &TaskBuilder{req: createTaskRequest{Name: name}} //nolint:exhaustruct
+func (c *Client) NewTask(name string) *TaskBuilder {
+ return &TaskBuilder{client: c, req: createTaskRequest{Name: name}}
}
// InArea assigns the task to an area. IDs are in the area's settings in the app.
@@ -205,25 +206,26 @@ func (b *TaskBuilder) FromSource(source, sourceID string) *TaskBuilder {
// Create sends the task to Lunatask. Returns (nil, nil) if a not-completed
// task already exists in the same area with matching source/source_id.
-func (b *TaskBuilder) Create(ctx context.Context, c *Client) (*Task, error) {
- return create(ctx, c, "/tasks", b.req, func(r taskResponse) Task { return r.Task })
+func (b *TaskBuilder) Create(ctx context.Context) (*Task, error) {
+ return create(ctx, b.client, "/tasks", b.req, func(r taskResponse) Task { return r.Task })
}
// TaskUpdateBuilder constructs and updates a task via method chaining.
// Only fields you set will be modified; others remain unchanged.
//
-// task, err := lunatask.NewTaskUpdate(taskID).
+// task, err := client.NewTaskUpdate(taskID).
// WithStatus(lunatask.StatusCompleted).
// CompletedAt(time.Now()).
-// Update(ctx, client)
+// Update(ctx)
type TaskUpdateBuilder struct {
+ client *Client
taskID string
req updateTaskRequest
}
// NewTaskUpdate starts building a task update for the given task ID.
-func NewTaskUpdate(taskID string) *TaskUpdateBuilder {
- return &TaskUpdateBuilder{taskID: taskID} //nolint:exhaustruct
+func (c *Client) NewTaskUpdate(taskID string) *TaskUpdateBuilder {
+ return &TaskUpdateBuilder{client: c, taskID: taskID}
}
// Name changes the task's name.
@@ -307,6 +309,6 @@ func (b *TaskUpdateBuilder) CompletedAt(t time.Time) *TaskUpdateBuilder {
}
// Update sends the changes to Lunatask.
-func (b *TaskUpdateBuilder) Update(ctx context.Context, c *Client) (*Task, error) {
- return update(ctx, c, "/tasks", b.taskID, "task", b.req, func(r taskResponse) Task { return r.Task })
+func (b *TaskUpdateBuilder) Update(ctx context.Context) (*Task, error) {
+ return update(ctx, b.client, "/tasks", b.taskID, "task", b.req, func(r taskResponse) Task { return r.Task })
}
@@ -165,10 +165,10 @@ func TestCreateTask_Success(t *testing.T) {
client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
- task, err := lunatask.NewTask("My task").
+ task, err := client.NewTask("My task").
InArea(areaID).
FromSource(sourceGitHub, sourceID123).
- Create(ctx(), client)
+ Create(ctx())
if err != nil {
t.Fatalf("error = %v", err)
}
@@ -198,7 +198,7 @@ func TestCreateTask_AllBuilderFields(t *testing.T) {
scheduledDate := lunatask.NewDate(time.Date(2024, 3, 15, 0, 0, 0, 0, time.UTC))
completedTime := time.Date(2024, 3, 15, 14, 30, 0, 0, time.UTC)
- _, err := lunatask.NewTask("Full task").
+ _, err := client.NewTask("Full task").
InArea(areaID).
InGoal(goalID).
WithNote("Some markdown note").
@@ -210,7 +210,7 @@ func TestCreateTask_AllBuilderFields(t *testing.T) {
ScheduledOn(scheduledDate).
CompletedAt(completedTime).
FromSource(sourceGitHub, sourceID123).
- Create(ctx(), client)
+ Create(ctx())
if err != nil {
t.Fatalf("error = %v", err)
}
@@ -238,10 +238,10 @@ func TestCreateTask_Duplicate(t *testing.T) {
client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
- task, err := lunatask.NewTask("Duplicate task").
+ task, err := client.NewTask("Duplicate task").
InArea(areaID).
FromSource(sourceGitHub, sourceID123).
- Create(ctx(), client)
+ Create(ctx())
// Per AGENTS.md: Create methods return (nil, nil) for duplicates
if err != nil {
t.Fatalf("error = %v, want nil", err)
@@ -256,7 +256,7 @@ func TestCreateTask_Errors(t *testing.T) {
t.Parallel()
testErrorCases(t, func(c *lunatask.Client) error {
- _, err := lunatask.NewTask("Test").InArea(areaID).Create(ctx(), c)
+ _, err := c.NewTask("Test").InArea(areaID).Create(ctx())
return err //nolint:wrapcheck // test helper
})
@@ -272,11 +272,11 @@ func TestUpdateTask_Success(t *testing.T) {
client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
- task, err := lunatask.NewTaskUpdate(taskID).
+ task, err := client.NewTaskUpdate(taskID).
Name("Updated name").
WithStatus(lunatask.StatusCompleted).
WithMotivation(lunatask.MotivationMust).
- Update(ctx(), client)
+ Update(ctx())
if err != nil {
t.Fatalf("error = %v", err)
}
@@ -304,7 +304,7 @@ func TestUpdateTask_AllBuilderFields(t *testing.T) {
scheduledDate := lunatask.NewDate(time.Date(2024, 6, 20, 0, 0, 0, 0, time.UTC))
completedTime := time.Date(2024, 6, 20, 16, 45, 0, 0, time.UTC)
- _, err := lunatask.NewTaskUpdate(taskID).
+ _, err := client.NewTaskUpdate(taskID).
Name("Full update").
InArea(areaID).
InGoal("goal-id").
@@ -316,7 +316,7 @@ func TestUpdateTask_AllBuilderFields(t *testing.T) {
WithPriority(-1).
ScheduledOn(scheduledDate).
CompletedAt(completedTime).
- Update(ctx(), client)
+ Update(ctx())
if err != nil {
t.Fatalf("error = %v", err)
}
@@ -338,7 +338,7 @@ func TestUpdateTask_Errors(t *testing.T) {
t.Parallel()
testErrorCases(t, func(c *lunatask.Client) error {
- _, err := lunatask.NewTaskUpdate(taskID).Name("x").Update(ctx(), c)
+ _, err := c.NewTaskUpdate(taskID).Name("x").Update(ctx())
return err //nolint:wrapcheck // test helper
})
@@ -38,18 +38,19 @@ type personTimelineNoteResponse struct {
// TimelineNoteBuilder constructs and creates a timeline note via method chaining.
// Content is encrypted client-side; the API accepts it on create but returns null on read.
//
-// note, err := lunatask.NewTimelineNote(personID).
+// note, err := client.NewTimelineNote(personID).
// OnDate(lunatask.Today()).
// WithContent("Had coffee, discussed the project.").
-// Create(ctx, client)
+// Create(ctx)
type TimelineNoteBuilder struct {
- req createPersonTimelineNoteRequest
+ client *Client
+ req createPersonTimelineNoteRequest
}
// NewTimelineNote starts building a timeline note for the given person.
// Get person IDs from [Client.ListPeople].
-func NewTimelineNote(personID string) *TimelineNoteBuilder {
- return &TimelineNoteBuilder{req: createPersonTimelineNoteRequest{PersonID: personID}} //nolint:exhaustruct
+func (c *Client) NewTimelineNote(personID string) *TimelineNoteBuilder {
+ return &TimelineNoteBuilder{client: c, req: createPersonTimelineNoteRequest{PersonID: personID}}
}
// OnDate sets when this interaction occurred.
@@ -68,12 +69,12 @@ func (b *TimelineNoteBuilder) WithContent(content string) *TimelineNoteBuilder {
// Create sends the timeline note to Lunatask.
// Get person IDs from [Client.ListPeople].
-func (b *TimelineNoteBuilder) Create(ctx context.Context, c *Client) (*PersonTimelineNote, error) {
+func (b *TimelineNoteBuilder) Create(ctx context.Context) (*PersonTimelineNote, error) {
if b.req.PersonID == "" {
return nil, fmt.Errorf("%w: person_id is required", ErrBadRequest)
}
- resp, _, err := doJSON[personTimelineNoteResponse](ctx, c, http.MethodPost, "/person_timeline_notes", b.req)
+ resp, _, err := doJSON[personTimelineNoteResponse](ctx, b.client, http.MethodPost, "/person_timeline_notes", b.req)
if err != nil {
return nil, err
}
@@ -23,10 +23,10 @@ func TestCreateTimelineNote_Success(t *testing.T) {
noteDate := lunatask.NewDate(time.Date(2021, 1, 10, 0, 0, 0, 0, time.UTC))
- note, err := lunatask.NewTimelineNote(personID).
+ note, err := client.NewTimelineNote(personID).
OnDate(noteDate).
WithContent("Today we talked about ...").
- Create(ctx(), client)
+ Create(ctx())
if err != nil {
t.Fatalf("error = %v", err)
}
@@ -53,8 +53,8 @@ func TestCreateTimelineNote_MinimalFields(t *testing.T) {
client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
// Per docs: date_on is optional (defaults to today), content is optional
- _, err := lunatask.NewTimelineNote(personID).
- Create(ctx(), client)
+ _, err := client.NewTimelineNote(personID).
+ Create(ctx())
if err != nil {
t.Fatalf("error = %v", err)
}
@@ -66,7 +66,7 @@ func TestCreateTimelineNote_Errors(t *testing.T) {
t.Parallel()
testErrorCases(t, func(c *lunatask.Client) error {
- _, err := lunatask.NewTimelineNote(personID).Create(ctx(), c)
+ _, err := c.NewTimelineNote(personID).Create(ctx())
return err //nolint:wrapcheck // test helper
})