@plandesk/api 0.7.0 → 0.9.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.
Files changed (44) hide show
  1. package/dist/events.d.ts +20 -1
  2. package/dist/index.d.ts +8 -1
  3. package/dist/index.js +5 -1
  4. package/dist/routes/agent-runs.d.ts +1 -1
  5. package/dist/routes/agent-runs.js +20 -0
  6. package/dist/routes/documents.js +2 -0
  7. package/dist/routes/folders.d.ts +4 -0
  8. package/dist/routes/folders.js +69 -0
  9. package/dist/routes/goals.d.ts +4 -0
  10. package/dist/routes/goals.js +138 -0
  11. package/dist/routes/projects.d.ts +1 -1
  12. package/dist/routes/projects.js +23 -3
  13. package/dist/routes/submissions.d.ts +5 -0
  14. package/dist/routes/submissions.js +58 -0
  15. package/dist/routes/tags.d.ts +4 -0
  16. package/dist/routes/tags.js +62 -0
  17. package/dist/routes/tasks.d.ts +1 -0
  18. package/dist/routes/tasks.js +9 -1
  19. package/dist/serialize.d.ts +51 -2
  20. package/dist/serialize.js +101 -1
  21. package/dist/server.js +9 -1
  22. package/dist/services/canvas.d.ts +4 -0
  23. package/dist/services/canvas.js +3 -2
  24. package/dist/services/documents.d.ts +5 -1
  25. package/dist/services/documents.js +31 -2
  26. package/dist/services/folders.d.ts +27 -0
  27. package/dist/services/folders.js +107 -0
  28. package/dist/services/goals.d.ts +239 -0
  29. package/dist/services/goals.js +323 -0
  30. package/dist/services/index.d.ts +6 -0
  31. package/dist/services/index.js +9 -0
  32. package/dist/services/projects.js +8 -2
  33. package/dist/services/sync.d.ts +5 -3
  34. package/dist/services/sync.js +45 -2
  35. package/dist/services/tags.d.ts +27 -0
  36. package/dist/services/tags.js +79 -0
  37. package/dist/services/tasks.d.ts +20 -2
  38. package/dist/services/tasks.js +98 -26
  39. package/package.json +3 -3
  40. package/web/assets/index-DcD7-VGi.css +1 -0
  41. package/web/assets/index-Dcaq3wR-.js +318 -0
  42. package/web/index.html +2 -2
  43. package/web/assets/index-CUxSE_WZ.css +0 -1
  44. package/web/assets/index-cyyf9-Uw.js +0 -234
@@ -0,0 +1,239 @@
1
+ import { type Db, type GoalStatus } from '@plandesk/db';
2
+ import type { EventBus } from '../events.js';
3
+ export type GoalServiceDeps = {
4
+ db: Db;
5
+ eventBus: EventBus;
6
+ };
7
+ export declare class InvalidGoalTransitionError extends Error {
8
+ constructor(message: string);
9
+ }
10
+ export declare class GoalCompletionBlockedError extends Error {
11
+ incompleteTaskIds: string[];
12
+ constructor(incompleteTaskIds: string[]);
13
+ }
14
+ export declare class InvalidVerificationSurfaceError extends Error {
15
+ constructor(message: string);
16
+ }
17
+ export declare class GoalVerificationRequiredError extends Error {
18
+ requiredKind: VerificationSurfaceKind;
19
+ constructor(requiredKind: VerificationSurfaceKind);
20
+ }
21
+ export type VerificationSurfaceKind = 'gate_command' | 'acceptance_checklist' | 'human_sign_off';
22
+ export type GateCommandSurface = {
23
+ kind: 'gate_command';
24
+ command: string;
25
+ };
26
+ export type AcceptanceChecklistSurface = {
27
+ kind: 'acceptance_checklist';
28
+ items: Array<{
29
+ criterion: string;
30
+ }>;
31
+ };
32
+ export type HumanSignOffSurface = {
33
+ kind: 'human_sign_off';
34
+ };
35
+ export type VerificationSurface = GateCommandSurface | AcceptanceChecklistSurface | HumanSignOffSurface;
36
+ export type GateCommandEvidence = {
37
+ kind: 'gate_command';
38
+ exit_code: number;
39
+ command?: string;
40
+ detail?: string;
41
+ };
42
+ export type AcceptanceChecklistEvidence = {
43
+ kind: 'acceptance_checklist';
44
+ checked: string[];
45
+ };
46
+ export type HumanSignOffEvidence = {
47
+ kind: 'human_sign_off';
48
+ approved_by: string;
49
+ };
50
+ export type VerificationEvidence = GateCommandEvidence | AcceptanceChecklistEvidence | HumanSignOffEvidence;
51
+ export type LastVerificationRecord = {
52
+ at: string;
53
+ green: boolean;
54
+ kind: VerificationSurfaceKind | null;
55
+ detail?: string;
56
+ };
57
+ export type CreateGoalInput = {
58
+ objective: string;
59
+ verificationSurface?: string | null;
60
+ constraints?: string | null;
61
+ boundaries?: string | null;
62
+ iterationPolicy?: string | null;
63
+ stopCondition?: string | null;
64
+ budget?: string | null;
65
+ status?: GoalStatus;
66
+ };
67
+ export type UpdateGoalInput = {
68
+ objective?: string;
69
+ verificationSurface?: string | null;
70
+ constraints?: string | null;
71
+ boundaries?: string | null;
72
+ iterationPolicy?: string | null;
73
+ stopCondition?: string | null;
74
+ budget?: string | null;
75
+ };
76
+ export declare function parseVerificationSurface(raw: string | null): VerificationSurface | null;
77
+ export declare function evaluateEvidence(surface: VerificationSurface, evidence: VerificationEvidence): {
78
+ green: boolean;
79
+ detail?: string;
80
+ };
81
+ export declare function createGoalService(deps: GoalServiceDeps): {
82
+ create(projectId: string, input: CreateGoalInput): {
83
+ id: string;
84
+ project_id: string;
85
+ objective: string;
86
+ status: "active" | "paused" | "complete" | "blocked";
87
+ verification_surface: string | null;
88
+ constraints: string | null;
89
+ boundaries: string | null;
90
+ iteration_policy: string | null;
91
+ stop_condition: string | null;
92
+ budget: string | null;
93
+ last_verification: {
94
+ at: string;
95
+ green: boolean;
96
+ kind: string | null;
97
+ detail?: string;
98
+ } | null;
99
+ created_at: string;
100
+ updated_at: string;
101
+ } | undefined;
102
+ get(goalId: string): {
103
+ cycle_tasks: {
104
+ tags?: import("../serialize.js").SerializedTag[] | undefined;
105
+ id: string;
106
+ project_id: string;
107
+ goal_id: string;
108
+ label: string;
109
+ status: "scope" | "todo" | "in_progress" | "done" | "backlog";
110
+ description: string | null;
111
+ x: number;
112
+ y: number;
113
+ assignee: string | null;
114
+ due_date: string | null;
115
+ created_at: string;
116
+ updated_at: string;
117
+ }[];
118
+ id: string;
119
+ project_id: string;
120
+ objective: string;
121
+ status: "active" | "paused" | "complete" | "blocked";
122
+ verification_surface: string | null;
123
+ constraints: string | null;
124
+ boundaries: string | null;
125
+ iteration_policy: string | null;
126
+ stop_condition: string | null;
127
+ budget: string | null;
128
+ last_verification: {
129
+ at: string;
130
+ green: boolean;
131
+ kind: string | null;
132
+ detail?: string;
133
+ } | null;
134
+ created_at: string;
135
+ updated_at: string;
136
+ } | undefined;
137
+ listByProject(projectId: string): {
138
+ id: string;
139
+ project_id: string;
140
+ objective: string;
141
+ status: "active" | "paused" | "complete" | "blocked";
142
+ verification_surface: string | null;
143
+ constraints: string | null;
144
+ boundaries: string | null;
145
+ iteration_policy: string | null;
146
+ stop_condition: string | null;
147
+ budget: string | null;
148
+ last_verification: {
149
+ at: string;
150
+ green: boolean;
151
+ kind: string | null;
152
+ detail?: string;
153
+ } | null;
154
+ created_at: string;
155
+ updated_at: string;
156
+ }[] | undefined;
157
+ update(goalId: string, input: UpdateGoalInput): {
158
+ id: string;
159
+ project_id: string;
160
+ objective: string;
161
+ status: "active" | "paused" | "complete" | "blocked";
162
+ verification_surface: string | null;
163
+ constraints: string | null;
164
+ boundaries: string | null;
165
+ iteration_policy: string | null;
166
+ stop_condition: string | null;
167
+ budget: string | null;
168
+ last_verification: {
169
+ at: string;
170
+ green: boolean;
171
+ kind: string | null;
172
+ detail?: string;
173
+ } | null;
174
+ created_at: string;
175
+ updated_at: string;
176
+ } | undefined;
177
+ pause(goalId: string): {
178
+ id: string;
179
+ project_id: string;
180
+ objective: string;
181
+ status: "active" | "paused" | "complete" | "blocked";
182
+ verification_surface: string | null;
183
+ constraints: string | null;
184
+ boundaries: string | null;
185
+ iteration_policy: string | null;
186
+ stop_condition: string | null;
187
+ budget: string | null;
188
+ last_verification: {
189
+ at: string;
190
+ green: boolean;
191
+ kind: string | null;
192
+ detail?: string;
193
+ } | null;
194
+ created_at: string;
195
+ updated_at: string;
196
+ } | undefined;
197
+ resume(goalId: string): {
198
+ id: string;
199
+ project_id: string;
200
+ objective: string;
201
+ status: "active" | "paused" | "complete" | "blocked";
202
+ verification_surface: string | null;
203
+ constraints: string | null;
204
+ boundaries: string | null;
205
+ iteration_policy: string | null;
206
+ stop_condition: string | null;
207
+ budget: string | null;
208
+ last_verification: {
209
+ at: string;
210
+ green: boolean;
211
+ kind: string | null;
212
+ detail?: string;
213
+ } | null;
214
+ created_at: string;
215
+ updated_at: string;
216
+ } | undefined;
217
+ complete(goalId: string, evidence?: VerificationEvidence): {
218
+ id: string;
219
+ project_id: string;
220
+ objective: string;
221
+ status: "active" | "paused" | "complete" | "blocked";
222
+ verification_surface: string | null;
223
+ constraints: string | null;
224
+ boundaries: string | null;
225
+ iteration_policy: string | null;
226
+ stop_condition: string | null;
227
+ budget: string | null;
228
+ last_verification: {
229
+ at: string;
230
+ green: boolean;
231
+ kind: string | null;
232
+ detail?: string;
233
+ } | null;
234
+ created_at: string;
235
+ updated_at: string;
236
+ } | undefined;
237
+ };
238
+ export type GoalService = ReturnType<typeof createGoalService>;
239
+ //# sourceMappingURL=goals.d.ts.map
@@ -0,0 +1,323 @@
1
+ import { createGoal, createTask, getGoal, getProject, InvalidGoalStatusError, isGoalStatus, listGoals, listTagsByTaskForProject, listTasks, updateGoal, updateGoalStatus, updateTask, } from '@plandesk/db';
2
+ import { serializeGoal, serializeTask } from '../serialize.js';
3
+ export class InvalidGoalTransitionError extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = 'InvalidGoalTransitionError';
7
+ }
8
+ }
9
+ export class GoalCompletionBlockedError extends Error {
10
+ incompleteTaskIds;
11
+ constructor(incompleteTaskIds) {
12
+ super('Goal cannot be completed until all cycle-tasks are done');
13
+ this.name = 'GoalCompletionBlockedError';
14
+ this.incompleteTaskIds = incompleteTaskIds;
15
+ }
16
+ }
17
+ export class InvalidVerificationSurfaceError extends Error {
18
+ constructor(message) {
19
+ super(message);
20
+ this.name = 'InvalidVerificationSurfaceError';
21
+ }
22
+ }
23
+ export class GoalVerificationRequiredError extends Error {
24
+ requiredKind;
25
+ constructor(requiredKind) {
26
+ super(`Verification evidence required for surface kind: ${requiredKind}`);
27
+ this.name = 'GoalVerificationRequiredError';
28
+ this.requiredKind = requiredKind;
29
+ }
30
+ }
31
+ function isRecord(value) {
32
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
33
+ }
34
+ function isNonEmptyString(value) {
35
+ return typeof value === 'string' && value.trim().length > 0;
36
+ }
37
+ export function parseVerificationSurface(raw) {
38
+ if (raw === null) {
39
+ return null;
40
+ }
41
+ let parsed;
42
+ try {
43
+ parsed = JSON.parse(raw);
44
+ }
45
+ catch {
46
+ throw new InvalidVerificationSurfaceError('verification_surface must be valid JSON');
47
+ }
48
+ if (!isRecord(parsed) || typeof parsed.kind !== 'string') {
49
+ throw new InvalidVerificationSurfaceError('verification_surface must include a kind');
50
+ }
51
+ switch (parsed.kind) {
52
+ case 'gate_command': {
53
+ if (!isNonEmptyString(parsed.command)) {
54
+ throw new InvalidVerificationSurfaceError('gate_command requires a command');
55
+ }
56
+ return { kind: 'gate_command', command: parsed.command };
57
+ }
58
+ case 'acceptance_checklist': {
59
+ if (!Array.isArray(parsed.items) || parsed.items.length === 0) {
60
+ throw new InvalidVerificationSurfaceError('acceptance_checklist requires non-empty items');
61
+ }
62
+ const items = [];
63
+ for (const item of parsed.items) {
64
+ if (!isRecord(item) || !isNonEmptyString(item.criterion)) {
65
+ throw new InvalidVerificationSurfaceError('acceptance_checklist items require a criterion');
66
+ }
67
+ items.push({ criterion: item.criterion });
68
+ }
69
+ return { kind: 'acceptance_checklist', items };
70
+ }
71
+ case 'human_sign_off':
72
+ return { kind: 'human_sign_off' };
73
+ default:
74
+ throw new InvalidVerificationSurfaceError(`Unknown verification_surface kind: ${parsed.kind}`);
75
+ }
76
+ }
77
+ export function evaluateEvidence(surface, evidence) {
78
+ if (surface.kind !== evidence.kind) {
79
+ return { green: false, detail: `Expected evidence kind ${surface.kind}` };
80
+ }
81
+ if (surface.kind === 'gate_command' && evidence.kind === 'gate_command') {
82
+ if (evidence.exit_code === 0) {
83
+ return { green: true };
84
+ }
85
+ const detail = evidence.detail ??
86
+ `exit_code ${String(evidence.exit_code)}${evidence.command ? ` for \`${evidence.command}\`` : ''}`;
87
+ return { green: false, detail };
88
+ }
89
+ if (surface.kind === 'acceptance_checklist' && evidence.kind === 'acceptance_checklist') {
90
+ const missing = surface.items
91
+ .map((item) => item.criterion)
92
+ .filter((criterion) => !evidence.checked.includes(criterion));
93
+ if (missing.length === 0) {
94
+ return { green: true };
95
+ }
96
+ return { green: false, detail: `Missing criteria: ${missing.join(', ')}` };
97
+ }
98
+ if (surface.kind === 'human_sign_off' && evidence.kind === 'human_sign_off') {
99
+ if (isNonEmptyString(evidence.approved_by)) {
100
+ return { green: true };
101
+ }
102
+ return { green: false, detail: 'approved_by is required' };
103
+ }
104
+ return { green: false, detail: `Expected evidence kind ${surface.kind}` };
105
+ }
106
+ function validateVerificationSurfaceInput(raw) {
107
+ if (raw === undefined) {
108
+ return;
109
+ }
110
+ parseVerificationSurface(raw);
111
+ }
112
+ function acceptanceBlockMarker(goalId) {
113
+ return `Acceptance-block-for: ${goalId}`;
114
+ }
115
+ function hasUnresolvedBlockingTask(db, projectId, goalId) {
116
+ const marker = acceptanceBlockMarker(goalId);
117
+ return listTasks(db, projectId).some((task) => task.goalId === goalId && task.status === 'scope' && task.description?.includes(marker));
118
+ }
119
+ // When acceptance finally passes, the remediation task filed on the earlier red
120
+ // result is obsolete — resolve it so a completed goal never shows open work.
121
+ function resolveBlockingTasks(db, projectId, goalId) {
122
+ const marker = acceptanceBlockMarker(goalId);
123
+ for (const task of listTasks(db, projectId)) {
124
+ if (task.goalId === goalId && task.status !== 'done' && task.description?.includes(marker)) {
125
+ updateTask(db, task.id, { status: 'done' });
126
+ }
127
+ }
128
+ }
129
+ function truncateObjective(objective, maxLength = 80) {
130
+ if (objective.length <= maxLength) {
131
+ return objective;
132
+ }
133
+ return `${objective.slice(0, maxLength - 1)}…`;
134
+ }
135
+ function buildBlockingTaskDescription(goalId, surfaceKind, detail) {
136
+ const lines = [acceptanceBlockMarker(goalId), '', `Surface kind: ${surfaceKind}`];
137
+ if (detail) {
138
+ lines.push(`Detail: ${detail}`);
139
+ }
140
+ return lines.join('\n');
141
+ }
142
+ function cycleTasksForGoal(db, projectId, goalId) {
143
+ const tagsByTask = listTagsByTaskForProject(db, projectId);
144
+ return listTasks(db, projectId)
145
+ .filter((task) => task.goalId === goalId)
146
+ .map((task) => serializeTask(task, tagsByTask.get(task.id) ?? []));
147
+ }
148
+ function emitGoalUpdated(eventBus, goalId, projectId) {
149
+ eventBus.emit({ type: 'goal_updated', goalId, projectId });
150
+ }
151
+ function recordLastVerification(at, green, kind, detail) {
152
+ const record = { at, green, kind };
153
+ if (detail) {
154
+ record.detail = detail;
155
+ }
156
+ return JSON.stringify(record);
157
+ }
158
+ export function createGoalService(deps) {
159
+ const { db, eventBus } = deps;
160
+ return {
161
+ create(projectId, input) {
162
+ if (input.status !== undefined && !isGoalStatus(input.status)) {
163
+ throw new InvalidGoalStatusError(input.status);
164
+ }
165
+ validateVerificationSurfaceInput(input.verificationSurface);
166
+ const project = getProject(db, projectId);
167
+ if (!project) {
168
+ return undefined;
169
+ }
170
+ const goal = db.transaction((tx) => createGoal(tx, {
171
+ projectId,
172
+ objective: input.objective,
173
+ status: input.status,
174
+ verificationSurface: input.verificationSurface,
175
+ constraints: input.constraints,
176
+ boundaries: input.boundaries,
177
+ iterationPolicy: input.iterationPolicy,
178
+ stopCondition: input.stopCondition,
179
+ budget: input.budget,
180
+ }));
181
+ emitGoalUpdated(eventBus, goal.id, projectId);
182
+ return serializeGoal(goal);
183
+ },
184
+ get(goalId) {
185
+ const goal = getGoal(db, goalId);
186
+ if (!goal) {
187
+ return undefined;
188
+ }
189
+ return {
190
+ ...serializeGoal(goal),
191
+ cycle_tasks: cycleTasksForGoal(db, goal.projectId, goalId),
192
+ };
193
+ },
194
+ listByProject(projectId) {
195
+ const project = getProject(db, projectId);
196
+ if (!project) {
197
+ return undefined;
198
+ }
199
+ return listGoals(db, projectId).map(serializeGoal);
200
+ },
201
+ update(goalId, input) {
202
+ const existing = getGoal(db, goalId);
203
+ if (!existing) {
204
+ return undefined;
205
+ }
206
+ validateVerificationSurfaceInput(input.verificationSurface);
207
+ const goal = db.transaction((tx) => updateGoal(tx, goalId, input));
208
+ if (!goal) {
209
+ return undefined;
210
+ }
211
+ emitGoalUpdated(eventBus, goalId, existing.projectId);
212
+ return serializeGoal(goal);
213
+ },
214
+ pause(goalId) {
215
+ const existing = getGoal(db, goalId);
216
+ if (!existing) {
217
+ return undefined;
218
+ }
219
+ if (existing.status !== 'active') {
220
+ throw new InvalidGoalTransitionError('Goal can only be paused from active status');
221
+ }
222
+ const goal = db.transaction((tx) => updateGoalStatus(tx, goalId, 'paused'));
223
+ if (!goal) {
224
+ return undefined;
225
+ }
226
+ emitGoalUpdated(eventBus, goalId, existing.projectId);
227
+ return serializeGoal(goal);
228
+ },
229
+ resume(goalId) {
230
+ const existing = getGoal(db, goalId);
231
+ if (!existing) {
232
+ return undefined;
233
+ }
234
+ if (existing.status !== 'paused') {
235
+ throw new InvalidGoalTransitionError('Goal can only be resumed from paused status');
236
+ }
237
+ const goal = db.transaction((tx) => updateGoalStatus(tx, goalId, 'active'));
238
+ if (!goal) {
239
+ return undefined;
240
+ }
241
+ emitGoalUpdated(eventBus, goalId, existing.projectId);
242
+ return serializeGoal(goal);
243
+ },
244
+ complete(goalId, evidence) {
245
+ const existing = getGoal(db, goalId);
246
+ if (!existing) {
247
+ return undefined;
248
+ }
249
+ if (existing.status === 'complete') {
250
+ throw new InvalidGoalTransitionError('Goal is already complete');
251
+ }
252
+ if (existing.status !== 'blocked') {
253
+ const incomplete = listTasks(db, existing.projectId)
254
+ .filter((task) => task.goalId === goalId && task.status !== 'done')
255
+ .map((task) => task.id);
256
+ if (incomplete.length > 0) {
257
+ throw new GoalCompletionBlockedError(incomplete);
258
+ }
259
+ }
260
+ const surface = parseVerificationSurface(existing.verificationSurface);
261
+ const at = new Date().toISOString();
262
+ if (!surface) {
263
+ const goal = db.transaction((tx) => updateGoal(tx, goalId, {
264
+ status: 'complete',
265
+ lastVerification: recordLastVerification(at, true, null),
266
+ }));
267
+ if (!goal) {
268
+ return undefined;
269
+ }
270
+ emitGoalUpdated(eventBus, goalId, existing.projectId);
271
+ return serializeGoal(goal);
272
+ }
273
+ if (!evidence || evidence.kind !== surface.kind) {
274
+ throw new GoalVerificationRequiredError(surface.kind);
275
+ }
276
+ const evaluation = evaluateEvidence(surface, evidence);
277
+ const lastVerification = recordLastVerification(at, evaluation.green, surface.kind, evaluation.detail);
278
+ if (evaluation.green) {
279
+ const goal = db.transaction((tx) => {
280
+ const updated = updateGoal(tx, goalId, {
281
+ status: 'complete',
282
+ lastVerification,
283
+ });
284
+ if (!updated) {
285
+ return undefined;
286
+ }
287
+ resolveBlockingTasks(tx, existing.projectId, goalId);
288
+ return updated;
289
+ });
290
+ if (!goal) {
291
+ return undefined;
292
+ }
293
+ emitGoalUpdated(eventBus, goalId, existing.projectId);
294
+ return serializeGoal(goal);
295
+ }
296
+ const goal = db.transaction((tx) => {
297
+ const updated = updateGoal(tx, goalId, {
298
+ status: 'blocked',
299
+ lastVerification,
300
+ });
301
+ if (!updated) {
302
+ return undefined;
303
+ }
304
+ if (!hasUnresolvedBlockingTask(tx, existing.projectId, goalId)) {
305
+ createTask(tx, {
306
+ projectId: existing.projectId,
307
+ goalId,
308
+ label: `Fix acceptance failure: ${truncateObjective(existing.objective)}`,
309
+ status: 'scope',
310
+ description: buildBlockingTaskDescription(goalId, surface.kind, evaluation.detail),
311
+ });
312
+ }
313
+ return updated;
314
+ });
315
+ if (!goal) {
316
+ return undefined;
317
+ }
318
+ emitGoalUpdated(eventBus, goalId, existing.projectId);
319
+ return serializeGoal(goal);
320
+ },
321
+ };
322
+ }
323
+ //# sourceMappingURL=goals.js.map
@@ -3,9 +3,12 @@ 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';
11
+ import { type GoalService } from './goals.js';
9
12
  import { type TaskService } from './tasks.js';
10
13
  import { type TokenService } from './tokens.js';
11
14
  import { type ShareService } from './share.js';
@@ -17,9 +20,12 @@ export type ServicesDeps = {
17
20
  export type Services = {
18
21
  eventBus: EventBus;
19
22
  projectService: ProjectService;
23
+ goalService: GoalService;
20
24
  taskService: TaskService;
25
+ tagService: TagService;
21
26
  canvasService: CanvasService;
22
27
  documentService: DocumentService;
28
+ folderService: FolderService;
23
29
  noteService: NoteService;
24
30
  commentService: CommentService;
25
31
  agentRunService: AgentRunService;
@@ -2,9 +2,12 @@ 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';
10
+ import { createGoalService } from './goals.js';
8
11
  import { createTaskService } from './tasks.js';
9
12
  import { createTokenService } from './tokens.js';
10
13
  import { createShareService } from './share.js';
@@ -12,9 +15,12 @@ import { createSyncService } from './sync.js';
12
15
  export function createServices(deps) {
13
16
  const eventBus = deps.eventBus ?? createEventBus();
14
17
  const projectService = createProjectService({ db: deps.db, eventBus });
18
+ const goalService = createGoalService({ db: deps.db, eventBus });
15
19
  const taskService = createTaskService({ db: deps.db, eventBus });
20
+ const tagService = createTagService({ db: deps.db, eventBus });
16
21
  const canvasService = createCanvasService({ db: deps.db, eventBus });
17
22
  const documentService = createDocumentService({ db: deps.db, eventBus });
23
+ const folderService = createFolderService({ db: deps.db, eventBus });
18
24
  const noteService = createNoteService({ db: deps.db, eventBus });
19
25
  const commentService = createCommentService({ db: deps.db, eventBus });
20
26
  const agentRunService = createAgentRunService({ db: deps.db, eventBus });
@@ -24,9 +30,12 @@ export function createServices(deps) {
24
30
  return {
25
31
  eventBus,
26
32
  projectService,
33
+ goalService,
27
34
  taskService,
35
+ tagService,
28
36
  canvasService,
29
37
  documentService,
38
+ folderService,
30
39
  noteService,
31
40
  commentService,
32
41
  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, deleteGoalsByProjectId, deleteShareSubmissionsByProjectId, deleteSharesByProjectId, deleteTagsByProjectId, deleteSyncRemoteByProjectId, deleteSyncStateByProjectId, deleteProject as dbDeleteProject, deleteTasksByProjectId, getOrCreateDefaultGoal, 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,8 +89,12 @@ 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);
97
+ deleteGoalsByProjectId(tx, id);
94
98
  deleteShareSubmissionsByProjectId(tx, id);
95
99
  deleteSyncStateByProjectId(tx, id);
96
100
  deleteSyncRemoteByProjectId(tx, id);
@@ -113,11 +117,13 @@ export function createProjectService(deps) {
113
117
  description: input.description,
114
118
  });
115
119
  projectId = project.id;
120
+ const defaultGoal = getOrCreateDefaultGoal(tx, project.id);
116
121
  input.tasks.forEach((taskInput, i) => {
117
122
  const x = taskInput.x ?? (i % 4) * 240;
118
123
  const y = taskInput.y ?? Math.floor(i / 4) * 160;
119
124
  const task = createTask(tx, {
120
125
  projectId: project.id,
126
+ goalId: defaultGoal.id,
121
127
  label: taskInput.label,
122
128
  status: taskInput.status,
123
129
  description: taskInput.description,
@@ -164,7 +170,7 @@ export function createProjectService(deps) {
164
170
  }
165
171
  return {
166
172
  project: serializeProject(project),
167
- tasks: taskRows.map(serializeTask),
173
+ tasks: taskRows.map((task) => serializeTask(task)),
168
174
  edges: edgeRows.map(serializeEdge),
169
175
  documents: documentRows.map(serializeDocument),
170
176
  key_to_id: Object.fromEntries(keyToId),