Skip to content

Commit 6685c33

Browse files
committed
Cleanup in preparation for stable 1.0 release
1 parent 0fb7915 commit 6685c33

11 files changed

Lines changed: 326 additions & 113 deletions

File tree

.changeset/kind-flowers-mate.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@plotday/twister": minor
3+
---
4+
5+
Changed: BREAKING: Minor type and signature changes in preparation for the stable 1.0 interface

.changeset/salty-pigs-bathe.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@plotday/twister": patch
3+
---
4+
5+
Added: archived field on Priority, Activity, and Note

twister/src/common/calendar.ts

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { ActivityLink, NewActivityWithNotes, SyncUpdate } from "../index";
2+
import type { NoFunctions } from "../utils/types";
23

34
/**
45
* Represents successful calendar authorization.
@@ -86,9 +87,12 @@ export interface SyncOptions {
8687
* const primaryCalendar = calendars.find(c => c.primary);
8788
* if (primaryCalendar) {
8889
* await this.googleCalendar.startSync(
89-
* auth.authToken,
90-
* primaryCalendar.id,
91-
* "onCalendarEvent"
90+
* {
91+
* authToken: auth.authToken,
92+
* calendarId: primaryCalendar.id
93+
* },
94+
* this.onCalendarEvent,
95+
* { initialSync: true }
9296
* );
9397
* }
9498
* }
@@ -151,27 +155,24 @@ export interface CalendarTool {
151155
* - Use Uuid.Generate() and store ID mappings when creating multiple activities per event
152156
* - See SYNC_STRATEGIES.md for when this is appropriate
153157
*
154-
* @param authToken - Authorization token for calendar access
155-
* @param calendarId - ID of the calendar to sync
158+
* @param options - Sync configuration options
159+
* @param options.authToken - Authorization token for calendar access
160+
* @param options.calendarId - ID of the calendar to sync
161+
* @param options.timeMin - Earliest date to sync events from (inclusive)
162+
* @param options.timeMax - Latest date to sync events to (exclusive)
156163
* @param callback - Function receiving (syncUpdate, ...extraArgs) for each synced event
157-
* @param extraArgs - Additional arguments to pass to the callback (type-checked)
164+
* @param extraArgs - Additional arguments to pass to the callback (type-checked, no functions allowed)
158165
* @returns Promise that resolves when sync setup is complete
159166
* @throws When auth token is invalid or calendar doesn't exist
160167
*/
161-
startSync<
162-
TCallback extends (
163-
syncUpdate: SyncUpdate,
164-
...args: any[]
165-
) => any
166-
>(
167-
authToken: string,
168-
calendarId: string,
168+
startSync<TCallback extends (syncUpdate: SyncUpdate, ...args: any[]) => any>(
169+
options: {
170+
authToken: string;
171+
calendarId: string;
172+
} & SyncOptions,
169173
callback: TCallback,
170-
...extraArgs: TCallback extends (
171-
syncUpdate: any,
172-
...rest: infer R
173-
) => any
174-
? R
174+
...extraArgs: TCallback extends (syncUpdate: any, ...rest: infer R) => any
175+
? NoFunctions<R>
175176
: []
176177
): Promise<void>;
177178

twister/src/common/messaging.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { ActivityLink, NewActivityWithNotes, SyncUpdate } from "../index";
2+
import type { NoFunctions } from "../utils/types";
23

34
/**
45
* Represents a successful messaging service authorization.
@@ -91,22 +92,22 @@ export interface MessagingTool {
9192
* - Use Uuid.Generate() and store ID mappings when creating multiple activities per thread
9293
* - See SYNC_STRATEGIES.md for when this is appropriate
9394
*
94-
* @param authToken - Authorization token for access
95-
* @param channelId - ID of the channel (e.g., channel, inbox) to sync
95+
* @param options - Sync configuration options
96+
* @param options.authToken - Authorization token for access
97+
* @param options.channelId - ID of the channel (e.g., channel, inbox) to sync
98+
* @param options.timeMin - Earliest date to sync events from (inclusive)
9699
* @param callback - Function receiving (syncUpdate, ...extraArgs) for each synced conversation
97-
* @param options - Optional configuration for limiting the sync scope (e.g., time range)
98-
* @param extraArgs - Additional arguments to pass to the callback (type-checked)
100+
* @param extraArgs - Additional arguments to pass to the callback (type-checked, no functions allowed)
99101
* @returns Promise that resolves when sync setup is complete
100102
*/
101-
startSync<
102-
TCallback extends (syncUpdate: SyncUpdate, ...args: any[]) => any
103-
>(
104-
authToken: string,
105-
channelId: string,
103+
startSync<TCallback extends (syncUpdate: SyncUpdate, ...args: any[]) => any>(
104+
options: {
105+
authToken: string;
106+
channelId: string;
107+
} & MessageSyncOptions,
106108
callback: TCallback,
107-
options?: MessageSyncOptions,
108109
...extraArgs: TCallback extends (syncUpdate: any, ...rest: infer R) => any
109-
? R
110+
? NoFunctions<R>
110111
: []
111112
): Promise<void>;
112113

twister/src/common/projects.ts

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
NewActivityWithNotes,
55
SyncUpdate,
66
} from "../index";
7+
import type { NoFunctions } from "../utils/types";
78

89
/**
910
* Represents a successful project management service authorization.
@@ -98,28 +99,22 @@ export interface ProjectTool {
9899
* - Use Uuid.Generate() and store ID mappings when creating multiple activities per issue
99100
* - See SYNC_STRATEGIES.md for when this is appropriate
100101
*
101-
* @param authToken - Authorization token for access
102-
* @param projectId - ID of the project to sync
102+
* @param options - Sync configuration options
103+
* @param options.authToken - Authorization token for access
104+
* @param options.projectId - ID of the project to sync
105+
* @param options.timeMin - Earliest date to sync issues from (inclusive)
103106
* @param callback - Function receiving (syncUpdate, ...extraArgs) for each synced issue
104-
* @param options - Optional configuration for limiting the sync scope (e.g., time range)
105-
* @param extraArgs - Additional arguments to pass to the callback (type-checked)
107+
* @param extraArgs - Additional arguments to pass to the callback (type-checked, no functions allowed)
106108
* @returns Promise that resolves when sync setup is complete
107109
*/
108-
startSync<
109-
TCallback extends (
110-
syncUpdate: SyncUpdate,
111-
...args: any[]
112-
) => any
113-
>(
114-
authToken: string,
115-
projectId: string,
110+
startSync<TCallback extends (syncUpdate: SyncUpdate, ...args: any[]) => any>(
111+
options: {
112+
authToken: string;
113+
projectId: string;
114+
} & ProjectSyncOptions,
116115
callback: TCallback,
117-
options?: ProjectSyncOptions,
118-
...extraArgs: TCallback extends (
119-
syncUpdate: any,
120-
...rest: infer R
121-
) => any
122-
? R
116+
...extraArgs: TCallback extends (syncUpdate: any, ...rest: infer R) => any
117+
? NoFunctions<R>
123118
: []
124119
): Promise<void>;
125120

twister/src/plot.ts

Lines changed: 126 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,71 @@
11
import { type Tag } from "./tag";
22
import { type Callback } from "./tools/callbacks";
3+
import { type JSONValue } from "./utils/types";
34
import { Uuid } from "./utils/uuid";
45

56
export { Tag } from "./tag";
67
export { Uuid } from "./utils/uuid";
8+
export { type JSONValue } from "./utils/types";
9+
10+
/**
11+
* @fileoverview
12+
* Core Plot entity types for working with activities, notes, priorities, and contacts.
13+
*
14+
* ## Type Pattern: Null vs Undefined Semantics
15+
*
16+
* Plot entity types use a consistent pattern to distinguish between missing, unset, and explicitly cleared values:
17+
*
18+
* ### Entity Types (Activity, Priority, Note, Actor)
19+
* - **Required fields**: No `?`, cannot be `undefined`
20+
* - Example: `id: Uuid`, `type: ActivityType`
21+
* - **Nullable fields**: Use `| null` to allow explicit clearing
22+
* - Example: `assignee: ActorId | null`, `done: Date | null`
23+
* - `null` = field is explicitly unset/cleared
24+
* - Non-null value = field has a value
25+
* - **Optional nullable fields**: Use `?` with `| null` for permission-based access
26+
* - Example: `email?: string | null`, `name?: string | null`
27+
* - `undefined` = field not included (e.g., no permission to access)
28+
* - `null` = field included but not set
29+
* - Value = field has a value
30+
*
31+
* ### New* Types (NewActivity, NewNote, NewPriority)
32+
* Used for creating or updating entities. Support partial updates by distinguishing omitted vs cleared fields:
33+
* - **Required fields**: Must be provided (no `?`)
34+
* - Example: `type: ActivityType` in NewActivity
35+
* - **Optional fields**: Use `?` to make them optional
36+
* - Example: `title?: string`, `author?: NewActor`
37+
* - `undefined` (omitted) = don't set/update this field
38+
* - Provided value = set/update this field
39+
* - **Optional nullable fields**: Use `?` with `| null` to support clearing
40+
* - Example: `assignee?: NewActor | null`
41+
* - `undefined` (omitted) = don't change assignee
42+
* - `null` = clear the assignee
43+
* - NewActor = set/update the assignee
44+
*
45+
* This pattern allows API consumers to:
46+
* 1. Omit fields they don't want to change (undefined)
47+
* 2. Explicitly clear fields by setting to null
48+
* 3. Set or update fields by providing values
49+
*
50+
* @example
51+
* ```typescript
52+
* // Creating a new activity
53+
* const newActivity: NewActivity = {
54+
* type: ActivityType.Action, // Required
55+
* title: "Review PR", // Optional, provided
56+
* assignee: null, // Optional nullable, explicitly clearing
57+
* // priority is omitted (undefined), will auto-select or use default
58+
* };
59+
*
60+
* // Updating an activity - only change what's specified
61+
* const update: ActivityUpdate = {
62+
* id: activityId,
63+
* done: new Date(), // Mark as done
64+
* assignee: null, // Clear assignee
65+
* // title is omitted, won't be changed
66+
* };
67+
* ```
68+
*/
769

870
/**
971
* Represents a unique user, contact, or twist in Plot.
@@ -24,21 +86,55 @@ export type ActorId = string & { readonly __brand: "ActorId" };
2486
*/
2587
export type Priority = {
2688
/** Unique identifier for the priority */
27-
id: string;
89+
id: Uuid;
2890
/** Human-readable title for the priority */
2991
title: string;
92+
/** Whether this priority has been archived */
93+
archived: boolean;
3094
};
3195

3296
/**
3397
* Type for creating new priorities.
3498
*
35-
* Excludes the auto-generated ID field and adds an optional parentId
36-
* for creating hierarchical priority structures.
99+
* Supports multiple creation patterns:
100+
* - Provide a specific UUID for the priority
101+
* - Provide a key for upsert within the user's priorities
102+
* - Omit both to auto-generate a new UUID
103+
*
104+
* Optionally specify a parent priority by ID or key for hierarchical structures.
37105
*/
38-
export type NewPriority = Omit<Priority, "id"> & {
39-
/** Optional ID of the parent priority for creating hierarchies */
40-
parentId?: string;
41-
};
106+
export type NewPriority = Pick<Priority, "title"> &
107+
Partial<Omit<Priority, "id" | "title">> &
108+
(
109+
| {
110+
/**
111+
* Unique identifier for the priority, generated by Uuid.Generate().
112+
* Specifying an ID allows tools to track and upsert priorities.
113+
*/
114+
id: Uuid;
115+
}
116+
| {
117+
/**
118+
* Unique key for the priority within the user's priorities.
119+
* Can be used to upsert without knowing the UUID.
120+
* For example, "@plot" identifies the Plot priority.
121+
*/
122+
key: string;
123+
}
124+
| {
125+
/* Neither id nor key is required. An id will be generated and returned. */
126+
}
127+
) & {
128+
/** Add the new priority as the child of another priority */
129+
parent?: { id: Uuid } | { key: string };
130+
};
131+
132+
/**
133+
* Type for updating existing priorities.
134+
* Must provide either id or key to identify the priority to update.
135+
*/
136+
export type PriorityUpdate = ({ id: Uuid } | { key: string }) &
137+
Partial<Pick<Priority, "title" | "archived">>;
42138

43139
/**
44140
* Enumeration of supported activity types in Plot.
@@ -178,6 +274,9 @@ export type ActivityLink =
178274
* which is useful for synchronization, linking back to external systems,
179275
* and storing tool-specific data.
180276
*
277+
* Must be valid JSON data (strings, numbers, booleans, null, objects, arrays).
278+
* Functions and other non-JSON values are not supported.
279+
*
181280
* @example
182281
* ```typescript
183282
* // Calendar event metadata
@@ -206,7 +305,7 @@ export type ActivityLink =
206305
*/
207306
export type ActivityMeta = {
208307
/** Source-specific properties and metadata */
209-
[key: string]: any;
308+
[key: string]: JSONValue;
210309
};
211310

212311
/**
@@ -414,8 +513,8 @@ export type NewActivityWithNotes = NewActivity & {
414513
* // Score based on content (max 100 points) and require exact type match
415514
* pickPriority: { content: 100, type: true }
416515
*
417-
* // Match on meta source and score content
418-
* pickPriority: { "meta.source": true, content: 50 }
516+
* // Match on meta and score content
517+
* pickPriority: { "meta.projectId": true, content: 50 }
419518
* ```
420519
*/
421520
export type PickPriorityConfig = {
@@ -590,6 +689,7 @@ export type ActivityUpdate = (
590689
| "assignee"
591690
| "draft"
592691
| "private"
692+
| "archived"
593693
| "meta"
594694
| "recurrenceRule"
595695
| "recurrenceDates"
@@ -732,7 +832,9 @@ export type NewNote = Partial<
732832
* Must provide either id or key to identify the note to update.
733833
*/
734834
export type NoteUpdate = ({ id: Uuid } | { key: string }) &
735-
Partial<Pick<Note, "draft" | "private" | "content" | "links">> & {
835+
Partial<
836+
Pick<Note, "draft" | "private" | "archived" | "content" | "links">
837+
> & {
736838
/**
737839
* Format of the note content. Determines how the note is processed:
738840
* - 'text': Plain text that will be converted to markdown (auto-links URLs, preserves line breaks)
@@ -781,9 +883,19 @@ export type Actor = {
781883
id: ActorId;
782884
/** Type of actor (User, Contact, or Twist) */
783885
type: ActorType;
784-
/** Email address (only included with ContactAccess.Read permission) */
785-
email?: string;
786-
/** Display name (undefined if not included due to permissions, null if not set) */
886+
/**
887+
* Email address (only included with ContactAccess.Read permission).
888+
* - `undefined`: No permission to read email
889+
* - `null`: Permission granted but email not set
890+
* - `string`: Email address
891+
*/
892+
email?: string | null;
893+
/**
894+
* Display name.
895+
* - `undefined`: Not included due to permissions
896+
* - `null`: Not set
897+
* - `string`: Display name
898+
*/
787899
name?: string | null;
788900
};
789901

0 commit comments

Comments
 (0)