@plandesk/api 0.7.0 → 0.8.0

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.
@@ -0,0 +1,107 @@
1
+ import { createFolder as dbCreateFolder, deleteFolder as dbDeleteFolder, getFolder as dbGetFolder, getProject, listFolders as dbListFolders, moveDocumentsToFolder, reparentChildFolders, updateFolder as dbUpdateFolder, } from '@plandesk/db';
2
+ import { serializeFolder } from '../serialize.js';
3
+ export class InvalidFolderError extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = 'InvalidFolderError';
7
+ }
8
+ }
9
+ function assertNonEmptyName(name) {
10
+ if (name.trim() === '') {
11
+ throw new InvalidFolderError('Folder name must not be empty');
12
+ }
13
+ }
14
+ function assertParentInProject(db, projectId, parentFolderId) {
15
+ const parent = dbGetFolder(db, parentFolderId);
16
+ if (!parent || parent.projectId !== projectId) {
17
+ throw new InvalidFolderError('Parent folder does not belong to project');
18
+ }
19
+ }
20
+ function assertNoCycle(db, folderId, newParentFolderId) {
21
+ const visited = new Set();
22
+ let current = newParentFolderId;
23
+ while (current !== null) {
24
+ if (current === folderId) {
25
+ throw new InvalidFolderError('Re-parenting would create a folder cycle');
26
+ }
27
+ if (visited.has(current)) {
28
+ return;
29
+ }
30
+ visited.add(current);
31
+ current = dbGetFolder(db, current)?.parentFolderId ?? null;
32
+ }
33
+ }
34
+ export function createFolderService(deps) {
35
+ const { db, eventBus } = deps;
36
+ return {
37
+ list(projectId) {
38
+ const project = getProject(db, projectId);
39
+ if (!project) {
40
+ return undefined;
41
+ }
42
+ return dbListFolders(db, projectId).map(serializeFolder);
43
+ },
44
+ create(projectId, input) {
45
+ const project = getProject(db, projectId);
46
+ if (!project) {
47
+ return undefined;
48
+ }
49
+ assertNonEmptyName(input.name);
50
+ if (input.parentFolderId !== undefined && input.parentFolderId !== null) {
51
+ assertParentInProject(db, projectId, input.parentFolderId);
52
+ }
53
+ const folder = dbCreateFolder(db, {
54
+ projectId,
55
+ name: input.name,
56
+ parentFolderId: input.parentFolderId,
57
+ });
58
+ eventBus.emit({ type: 'folder_created', folderId: folder.id, projectId });
59
+ return serializeFolder(folder);
60
+ },
61
+ get(id) {
62
+ const folder = dbGetFolder(db, id);
63
+ if (!folder) {
64
+ return undefined;
65
+ }
66
+ return serializeFolder(folder);
67
+ },
68
+ update(id, input) {
69
+ const existing = dbGetFolder(db, id);
70
+ if (!existing) {
71
+ return undefined;
72
+ }
73
+ if (input.name !== undefined) {
74
+ assertNonEmptyName(input.name);
75
+ }
76
+ if (input.parentFolderId !== undefined && input.parentFolderId !== null) {
77
+ if (input.parentFolderId === id) {
78
+ throw new InvalidFolderError('Folder cannot be its own parent');
79
+ }
80
+ assertParentInProject(db, existing.projectId, input.parentFolderId);
81
+ assertNoCycle(db, id, input.parentFolderId);
82
+ }
83
+ const folder = dbUpdateFolder(db, id, input);
84
+ if (!folder) {
85
+ return undefined;
86
+ }
87
+ eventBus.emit({ type: 'folder_updated', folderId: folder.id, projectId: folder.projectId });
88
+ return serializeFolder(folder);
89
+ },
90
+ delete(id) {
91
+ const existing = dbGetFolder(db, id);
92
+ if (!existing) {
93
+ return false;
94
+ }
95
+ // Never orphan: children folders and contained documents move to the
96
+ // deleted folder's parent (or root when the folder was at root).
97
+ db.transaction((tx) => {
98
+ reparentChildFolders(tx, id, existing.parentFolderId);
99
+ moveDocumentsToFolder(tx, id, existing.parentFolderId);
100
+ dbDeleteFolder(tx, id);
101
+ });
102
+ eventBus.emit({ type: 'folder_updated', folderId: id, projectId: existing.projectId });
103
+ return true;
104
+ },
105
+ };
106
+ }
107
+ //# sourceMappingURL=folders.js.map
@@ -3,9 +3,11 @@ import { type EventBus } from '../events.js';
3
3
  import { type CanvasService } from './canvas.js';
4
4
  import { type CommentService } from './comments.js';
5
5
  import { type DocumentService } from './documents.js';
6
+ import { type FolderService } from './folders.js';
6
7
  import { type NoteService } from './notes.js';
7
8
  import { type ProjectService } from './projects.js';
8
9
  import { type AgentRunService } from './agent-runs.js';
10
+ import { type TagService } from './tags.js';
9
11
  import { type TaskService } from './tasks.js';
10
12
  import { type TokenService } from './tokens.js';
11
13
  import { type ShareService } from './share.js';
@@ -18,8 +20,10 @@ export type Services = {
18
20
  eventBus: EventBus;
19
21
  projectService: ProjectService;
20
22
  taskService: TaskService;
23
+ tagService: TagService;
21
24
  canvasService: CanvasService;
22
25
  documentService: DocumentService;
26
+ folderService: FolderService;
23
27
  noteService: NoteService;
24
28
  commentService: CommentService;
25
29
  agentRunService: AgentRunService;
@@ -2,9 +2,11 @@ import { createEventBus } from '../events.js';
2
2
  import { createCanvasService } from './canvas.js';
3
3
  import { createCommentService } from './comments.js';
4
4
  import { createDocumentService } from './documents.js';
5
+ import { createFolderService } from './folders.js';
5
6
  import { createNoteService } from './notes.js';
6
7
  import { createProjectService } from './projects.js';
7
8
  import { createAgentRunService } from './agent-runs.js';
9
+ import { createTagService } from './tags.js';
8
10
  import { createTaskService } from './tasks.js';
9
11
  import { createTokenService } from './tokens.js';
10
12
  import { createShareService } from './share.js';
@@ -13,8 +15,10 @@ export function createServices(deps) {
13
15
  const eventBus = deps.eventBus ?? createEventBus();
14
16
  const projectService = createProjectService({ db: deps.db, eventBus });
15
17
  const taskService = createTaskService({ db: deps.db, eventBus });
18
+ const tagService = createTagService({ db: deps.db, eventBus });
16
19
  const canvasService = createCanvasService({ db: deps.db, eventBus });
17
20
  const documentService = createDocumentService({ db: deps.db, eventBus });
21
+ const folderService = createFolderService({ db: deps.db, eventBus });
18
22
  const noteService = createNoteService({ db: deps.db, eventBus });
19
23
  const commentService = createCommentService({ db: deps.db, eventBus });
20
24
  const agentRunService = createAgentRunService({ db: deps.db, eventBus });
@@ -25,8 +29,10 @@ export function createServices(deps) {
25
29
  eventBus,
26
30
  projectService,
27
31
  taskService,
32
+ tagService,
28
33
  canvasService,
29
34
  documentService,
35
+ folderService,
30
36
  noteService,
31
37
  commentService,
32
38
  agentRunService,
@@ -1,4 +1,4 @@
1
- import { clearDocumentParentRefsByProject, createDocument, createEdge, createProject as dbCreateProject, createTask, deleteAgentRun, deleteAgentRunEventsByRunId, deleteCommentsByProjectId, deleteDocumentsByProjectId, deleteNotesByProjectId, deleteEdgesByProjectId, deleteShareSubmissionsByProjectId, deleteSharesByProjectId, deleteSyncRemoteByProjectId, deleteSyncStateByProjectId, deleteProject as dbDeleteProject, deleteTasksByProjectId, getProject as dbGetProject, InvalidTaskStatusError, isTaskStatus, listAgentRuns, listProjects as dbListProjects, listTasks, updateProject as dbUpdateProject, } from '@plandesk/db';
1
+ import { clearDocumentParentRefsByProject, clearFolderParentRefsByProject, createDocument, createEdge, createProject as dbCreateProject, createTask, deleteAgentRun, deleteAgentRunEventsByRunId, deleteCommentsByProjectId, deleteDocumentsByProjectId, deleteFoldersByProjectId, deleteNotesByProjectId, deleteEdgesByProjectId, deleteShareSubmissionsByProjectId, deleteSharesByProjectId, deleteTagsByProjectId, deleteSyncRemoteByProjectId, deleteSyncStateByProjectId, deleteProject as dbDeleteProject, deleteTasksByProjectId, getProject as dbGetProject, InvalidTaskStatusError, isTaskStatus, listAgentRuns, listProjects as dbListProjects, listTasks, updateProject as dbUpdateProject, } from '@plandesk/db';
2
2
  import { emptyTaskStatusSummary, serializeDocument, serializeEdge, serializeProject, serializeProjectDetail, serializeTask, } from '../serialize.js';
3
3
  export class InvalidScaffoldError extends Error {
4
4
  constructor(message) {
@@ -89,7 +89,10 @@ export function createProjectService(deps) {
89
89
  clearDocumentParentRefsByProject(tx, id);
90
90
  deleteCommentsByProjectId(tx, id);
91
91
  deleteDocumentsByProjectId(tx, id);
92
+ clearFolderParentRefsByProject(tx, id);
93
+ deleteFoldersByProjectId(tx, id);
92
94
  deleteNotesByProjectId(tx, id);
95
+ deleteTagsByProjectId(tx, id);
93
96
  deleteTasksByProjectId(tx, id);
94
97
  deleteShareSubmissionsByProjectId(tx, id);
95
98
  deleteSyncStateByProjectId(tx, id);
@@ -164,7 +167,7 @@ export function createProjectService(deps) {
164
167
  }
165
168
  return {
166
169
  project: serializeProject(project),
167
- tasks: taskRows.map(serializeTask),
170
+ tasks: taskRows.map((task) => serializeTask(task)),
168
171
  edges: edgeRows.map(serializeEdge),
169
172
  documents: documentRows.map(serializeDocument),
170
173
  key_to_id: Object.fromEntries(keyToId),
@@ -0,0 +1,27 @@
1
+ import { type Db } from '@plandesk/db';
2
+ import type { EventBus } from '../events.js';
3
+ import { type SerializedTag } from '../serialize.js';
4
+ export type TagServiceDeps = {
5
+ db: Db;
6
+ eventBus: EventBus;
7
+ };
8
+ export type CreateTagInput = {
9
+ name: string;
10
+ color?: string | null;
11
+ };
12
+ export type UpdateTagInput = {
13
+ name?: string;
14
+ color?: string | null;
15
+ };
16
+ export declare class InvalidTagError extends Error {
17
+ constructor(message: string);
18
+ }
19
+ export declare function normalizeTagName(name: string): string;
20
+ export declare function createTagService(deps: TagServiceDeps): {
21
+ list(projectId: string): SerializedTag[] | undefined;
22
+ create(projectId: string, input: CreateTagInput): SerializedTag | undefined;
23
+ update(id: string, input: UpdateTagInput): SerializedTag | undefined;
24
+ delete(id: string): boolean;
25
+ };
26
+ export type TagService = ReturnType<typeof createTagService>;
27
+ //# sourceMappingURL=tags.d.ts.map
@@ -0,0 +1,79 @@
1
+ import { createTag as dbCreateTag, deleteTag as dbDeleteTag, getProject, getTag as dbGetTag, getTagByName, listTags as dbListTags, updateTag as dbUpdateTag, } from '@plandesk/db';
2
+ import { serializeTag } from '../serialize.js';
3
+ export class InvalidTagError extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = 'InvalidTagError';
7
+ }
8
+ }
9
+ export function normalizeTagName(name) {
10
+ const trimmed = name.trim();
11
+ if (trimmed === '') {
12
+ throw new InvalidTagError('Tag name must not be empty');
13
+ }
14
+ return trimmed;
15
+ }
16
+ export function createTagService(deps) {
17
+ const { db, eventBus } = deps;
18
+ return {
19
+ list(projectId) {
20
+ const project = getProject(db, projectId);
21
+ if (!project) {
22
+ return undefined;
23
+ }
24
+ return dbListTags(db, projectId).map(serializeTag);
25
+ },
26
+ create(projectId, input) {
27
+ const project = getProject(db, projectId);
28
+ if (!project) {
29
+ return undefined;
30
+ }
31
+ const name = normalizeTagName(input.name);
32
+ if (getTagByName(db, projectId, name)) {
33
+ throw new InvalidTagError(`Tag already exists: ${name}`);
34
+ }
35
+ const tag = dbCreateTag(db, { projectId, name, color: input.color });
36
+ eventBus.emit({ type: 'tag_updated', projectId });
37
+ return serializeTag(tag);
38
+ },
39
+ // Renaming propagates everywhere automatically: tasks reference the single
40
+ // tag row through the join table.
41
+ update(id, input) {
42
+ const existing = dbGetTag(db, id);
43
+ if (!existing) {
44
+ return undefined;
45
+ }
46
+ let name;
47
+ if (input.name !== undefined) {
48
+ name = normalizeTagName(input.name);
49
+ const conflict = getTagByName(db, existing.projectId, name);
50
+ if (conflict && conflict.id !== id) {
51
+ throw new InvalidTagError(`Tag already exists: ${name}`);
52
+ }
53
+ }
54
+ const tag = dbUpdateTag(db, id, {
55
+ ...(name !== undefined ? { name } : {}),
56
+ ...(input.color !== undefined ? { color: input.color } : {}),
57
+ });
58
+ if (!tag) {
59
+ return undefined;
60
+ }
61
+ eventBus.emit({ type: 'tag_updated', projectId: tag.projectId });
62
+ return serializeTag(tag);
63
+ },
64
+ // Deleting a tag removes it from all its tasks (cascade on the join table).
65
+ delete(id) {
66
+ const existing = dbGetTag(db, id);
67
+ if (!existing) {
68
+ return false;
69
+ }
70
+ const deleted = dbDeleteTag(db, id);
71
+ if (!deleted) {
72
+ return false;
73
+ }
74
+ eventBus.emit({ type: 'tag_updated', projectId: existing.projectId });
75
+ return true;
76
+ },
77
+ };
78
+ }
79
+ //# sourceMappingURL=tags.js.map
@@ -23,6 +23,7 @@ export type CreateTaskInput = {
23
23
  y?: number;
24
24
  assignee?: string | null;
25
25
  dueDate?: Date | null;
26
+ tags?: string[];
26
27
  };
27
28
  export type UpdateTaskInput = {
28
29
  label?: string;
@@ -30,12 +31,15 @@ export type UpdateTaskInput = {
30
31
  description?: string | null;
31
32
  x?: number;
32
33
  y?: number;
34
+ tags?: string[];
33
35
  };
34
36
  export type ListTasksFilter = {
35
37
  status?: string;
38
+ tags?: string[];
36
39
  };
37
40
  export declare function createTaskService(deps: TaskServiceDeps): {
38
41
  get(id: string): {
42
+ tags?: import("../serialize.js").SerializedTag[] | undefined;
39
43
  id: string;
40
44
  project_id: string;
41
45
  label: string;
@@ -49,6 +53,7 @@ export declare function createTaskService(deps: TaskServiceDeps): {
49
53
  updated_at: string;
50
54
  } | undefined;
51
55
  listByProject(projectId: string, filter?: ListTasksFilter, pagination?: PaginationParams): {
56
+ tags?: import("../serialize.js").SerializedTag[] | undefined;
52
57
  id: string;
53
58
  project_id: string;
54
59
  label: string;
@@ -62,6 +67,7 @@ export declare function createTaskService(deps: TaskServiceDeps): {
62
67
  updated_at: string;
63
68
  }[] | undefined;
64
69
  create(projectId: string, input: CreateTaskInput): {
70
+ tags?: import("../serialize.js").SerializedTag[] | undefined;
65
71
  id: string;
66
72
  project_id: string;
67
73
  label: string;
@@ -75,6 +81,7 @@ export declare function createTaskService(deps: TaskServiceDeps): {
75
81
  updated_at: string;
76
82
  } | undefined;
77
83
  update(id: string, input: UpdateTaskInput): {
84
+ tags?: import("../serialize.js").SerializedTag[] | undefined;
78
85
  id: string;
79
86
  project_id: string;
80
87
  label: string;
@@ -88,7 +95,9 @@ export declare function createTaskService(deps: TaskServiceDeps): {
88
95
  updated_at: string;
89
96
  } | undefined;
90
97
  delete(id: string): boolean;
91
- nextActionable(projectId: string): NextActionableResult | undefined;
98
+ nextActionable(projectId: string, filter?: {
99
+ tags?: string[];
100
+ }): NextActionableResult | undefined;
92
101
  };
93
102
  export type TaskService = ReturnType<typeof createTaskService>;
94
103
  export {};
@@ -1,5 +1,6 @@
1
- import { createTask, deleteEdgesByTaskId, deleteTask as dbDeleteTask, getProject, getTask, InvalidTaskStatusError, isTaskStatus, listEdges, listTasks, nullDocumentsLinkedTask, updateTask, } from '@plandesk/db';
1
+ import { createTag, createTask, deleteEdgesByTaskId, deleteTask as dbDeleteTask, deleteTaskTagsByTaskId, getProject, getTagByName, getTask, InvalidTaskStatusError, isTaskStatus, listEdges, listTagsByTaskForProject, listTagsForTask, listTasks, nullDocumentsLinkedTask, setTaskTags, taskIdsWithAnyTagName, updateTask, } from '@plandesk/db';
2
2
  import { serializeTask } from '../serialize.js';
3
+ import { normalizeTagName } from './tags.js';
3
4
  // depends_on: prerequisite = to, dependent = from. All other labels: prerequisite = from, dependent = to.
4
5
  function prerequisiteAndDependent(edge) {
5
6
  if (edge.fromTaskId === edge.toTaskId) {
@@ -10,6 +11,21 @@ function prerequisiteAndDependent(edge) {
10
11
  }
11
12
  return { prerequisite: edge.fromTaskId, dependent: edge.toTaskId };
12
13
  }
14
+ // Resolves tag names to ids, auto-creating tags that do not exist yet.
15
+ function resolveTagIdsByName(db, projectId, names) {
16
+ const seen = new Set();
17
+ const ids = [];
18
+ for (const raw of names) {
19
+ const name = normalizeTagName(raw);
20
+ if (seen.has(name)) {
21
+ continue;
22
+ }
23
+ seen.add(name);
24
+ const tag = getTagByName(db, projectId, name) ?? createTag(db, { projectId, name });
25
+ ids.push(tag.id);
26
+ }
27
+ return ids;
28
+ }
13
29
  export function createTaskService(deps) {
14
30
  const { db, eventBus } = deps;
15
31
  return {
@@ -18,7 +34,7 @@ export function createTaskService(deps) {
18
34
  if (!task) {
19
35
  return undefined;
20
36
  }
21
- return serializeTask(task);
37
+ return serializeTask(task, listTagsForTask(db, id));
22
38
  },
23
39
  listByProject(projectId, filter = {}, pagination = {}) {
24
40
  if (filter.status !== undefined && !isTaskStatus(filter.status)) {
@@ -29,10 +45,13 @@ export function createTaskService(deps) {
29
45
  return undefined;
30
46
  }
31
47
  const statusFilter = filter.status;
32
- const tasks = statusFilter !== undefined
33
- ? listTasks(db, projectId, { status: statusFilter, ...pagination })
34
- : listTasks(db, projectId, pagination);
35
- return tasks.map(serializeTask);
48
+ const tasks = listTasks(db, projectId, {
49
+ ...(statusFilter !== undefined ? { status: statusFilter } : {}),
50
+ ...(filter.tags !== undefined ? { tagNames: filter.tags.map(normalizeTagName) } : {}),
51
+ ...pagination,
52
+ });
53
+ const tagsByTask = listTagsByTaskForProject(db, projectId);
54
+ return tasks.map((task) => serializeTask(task, tagsByTask.get(task.id) ?? []));
36
55
  },
37
56
  create(projectId, input) {
38
57
  if (input.status !== undefined && !isTaskStatus(input.status)) {
@@ -42,22 +61,28 @@ export function createTaskService(deps) {
42
61
  if (!project) {
43
62
  return undefined;
44
63
  }
45
- const task = createTask(db, {
46
- projectId,
47
- label: input.label,
48
- status: input.status,
49
- description: input.description,
50
- x: input.x,
51
- y: input.y,
52
- assignee: input.assignee,
53
- dueDate: input.dueDate,
64
+ const { task, tags } = db.transaction((tx) => {
65
+ const row = createTask(tx, {
66
+ projectId,
67
+ label: input.label,
68
+ status: input.status,
69
+ description: input.description,
70
+ x: input.x,
71
+ y: input.y,
72
+ assignee: input.assignee,
73
+ dueDate: input.dueDate,
74
+ });
75
+ if (input.tags !== undefined) {
76
+ setTaskTags(tx, row.id, resolveTagIdsByName(tx, projectId, input.tags));
77
+ }
78
+ return { task: row, tags: listTagsForTask(tx, row.id) };
54
79
  });
55
80
  eventBus.emit({
56
81
  type: 'task_updated',
57
82
  taskId: task.id,
58
83
  projectId,
59
84
  });
60
- return serializeTask(task);
85
+ return serializeTask(task, tags);
61
86
  },
62
87
  update(id, input) {
63
88
  if (input.status !== undefined && !isTaskStatus(input.status)) {
@@ -67,16 +92,26 @@ export function createTaskService(deps) {
67
92
  if (!existing) {
68
93
  return undefined;
69
94
  }
70
- const task = updateTask(db, id, input);
71
- if (!task) {
95
+ const { tags: tagNames, ...columns } = input;
96
+ const result = db.transaction((tx) => {
97
+ const row = updateTask(tx, id, columns);
98
+ if (!row) {
99
+ return undefined;
100
+ }
101
+ if (tagNames !== undefined) {
102
+ setTaskTags(tx, id, resolveTagIdsByName(tx, existing.projectId, tagNames));
103
+ }
104
+ return { task: row, tags: listTagsForTask(tx, id) };
105
+ });
106
+ if (!result) {
72
107
  return undefined;
73
108
  }
74
109
  eventBus.emit({
75
110
  type: 'task_updated',
76
- taskId: task.id,
77
- projectId: task.projectId,
111
+ taskId: result.task.id,
112
+ projectId: result.task.projectId,
78
113
  });
79
- return serializeTask(task);
114
+ return serializeTask(result.task, result.tags);
80
115
  },
81
116
  delete(id) {
82
117
  const task = getTask(db, id);
@@ -87,12 +122,16 @@ export function createTaskService(deps) {
87
122
  db.transaction((tx) => {
88
123
  deleteEdgesByTaskId(tx, id);
89
124
  nullDocumentsLinkedTask(tx, id);
125
+ deleteTaskTagsByTaskId(tx, id);
90
126
  dbDeleteTask(tx, id);
91
127
  });
92
128
  eventBus.emit({ type: 'canvas_updated', projectId });
93
129
  return true;
94
130
  },
95
- nextActionable(projectId) {
131
+ // filter.tags (OR semantics): only tasks carrying ANY of the given tag names
132
+ // are candidates for next_task / blocked; prerequisite completion is still
133
+ // evaluated against all tasks in the project.
134
+ nextActionable(projectId, filter = {}) {
96
135
  const project = getProject(db, projectId);
97
136
  if (!project) {
98
137
  return undefined;
@@ -100,6 +139,11 @@ export function createTaskService(deps) {
100
139
  const tasks = listTasks(db, projectId).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
101
140
  const edges = listEdges(db, projectId);
102
141
  const taskById = new Map(tasks.map((task) => [task.id, task]));
142
+ const tagsByTask = listTagsByTaskForProject(db, projectId);
143
+ const tagMatches = filter.tags !== undefined && filter.tags.length > 0
144
+ ? taskIdsWithAnyTagName(db, projectId, filter.tags.map(normalizeTagName))
145
+ : undefined;
146
+ const serialize = (task) => serializeTask(task, tagsByTask.get(task.id) ?? []);
103
147
  const prerequisites = new Map();
104
148
  for (const edge of edges) {
105
149
  const pair = prerequisiteAndDependent(edge);
@@ -116,7 +160,7 @@ export function createTaskService(deps) {
116
160
  if (tasks.length === 0) {
117
161
  return { next_task: null, reason: 'no_tasks', blocked: [] };
118
162
  }
119
- const todoTasks = tasks.filter((task) => task.status === 'todo');
163
+ const todoTasks = tasks.filter((task) => task.status === 'todo' && (tagMatches === undefined || tagMatches.has(task.id)));
120
164
  if (todoTasks.length === 0) {
121
165
  return { next_task: null, reason: 'no_todo_tasks', blocked: [] };
122
166
  }
@@ -141,19 +185,19 @@ export function createTaskService(deps) {
141
185
  return [...prereqs]
142
186
  .map((id) => taskById.get(id))
143
187
  .filter((task) => task !== undefined && task.status !== 'done')
144
- .map(serializeTask);
188
+ .map(serialize);
145
189
  };
146
190
  const blocked = [];
147
191
  let nextTask = null;
148
192
  for (const task of todoTasks) {
149
193
  if (isActionable(task.id)) {
150
194
  if (nextTask === null) {
151
- nextTask = serializeTask(task);
195
+ nextTask = serialize(task);
152
196
  }
153
197
  }
154
198
  else {
155
199
  blocked.push({
156
- task: serializeTask(task),
200
+ task: serialize(task),
157
201
  waiting_on: unfinishedPrereqs(task.id),
158
202
  });
159
203
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plandesk/api",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -18,8 +18,8 @@
18
18
  "devDependencies": {
19
19
  "typescript": "^6.0.3",
20
20
  "vitest": "^3.2.6",
21
- "@plandesk/db": "0.5.0",
22
- "@plandesk/sync-server": "0.4.2"
21
+ "@plandesk/db": "0.6.0",
22
+ "@plandesk/sync-server": "0.5.0"
23
23
  },
24
24
  "dependencies": {
25
25
  "@hono/node-server": "^2.0.4",