trekoon 0.1.5 → 0.1.6

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.
@@ -1,7 +1,8 @@
1
1
  import { hasFlag, parseArgs, parseStrictPositiveInt, readEnumOption, readMissingOptionValue, readOption } from "./arg-parser";
2
2
 
3
- import { DomainError, type TaskRecord } from "../domain/types";
3
+ import { MutationService } from "../domain/mutation-service";
4
4
  import { TrackerDomain } from "../domain/tracker-domain";
5
+ import { DomainError, type TaskRecord } from "../domain/types";
5
6
  import { formatHumanTable } from "../io/human-table";
6
7
  import { failResult, okResult } from "../io/output";
7
8
  import { type CliContext, type CliResult } from "../runtime/command-types";
@@ -192,6 +193,7 @@ export async function runTask(context: CliContext): Promise<CliResult> {
192
193
  const parsed = parseArgs(context.args);
193
194
  const subcommand: string | undefined = parsed.positional[0];
194
195
  const domain = new TrackerDomain(database.db);
196
+ const mutations = new MutationService(database.db, context.cwd);
195
197
 
196
198
  switch (subcommand) {
197
199
  case "create": {
@@ -207,7 +209,7 @@ export async function runTask(context: CliContext): Promise<CliResult> {
207
209
  const title: string | undefined = readOption(parsed.options, "title", "t");
208
210
  const description: string | undefined = readOption(parsed.options, "description", "d");
209
211
  const status: string | undefined = readOption(parsed.options, "status", "s");
210
- const task = domain.createTask({
212
+ const task = mutations.createTask({
211
213
  epicId: epicId ?? "",
212
214
  title: title ?? "",
213
215
  description: description ?? "",
@@ -482,7 +484,7 @@ export async function runTask(context: CliContext): Promise<CliResult> {
482
484
 
483
485
  const targets = updateAll ? [...domain.listTasks()] : ids.map((id) => domain.getTaskOrThrow(id));
484
486
  const tasks = targets.map((target) =>
485
- domain.updateTask(target.id, {
487
+ mutations.updateTask(target.id, {
486
488
  status,
487
489
  description: append === undefined ? undefined : appendLine(target.description, append),
488
490
  }),
@@ -515,7 +517,7 @@ export async function runTask(context: CliContext): Promise<CliResult> {
515
517
  append === undefined
516
518
  ? description
517
519
  : appendLine(domain.getTaskOrThrow(taskId).description, append);
518
- const task = domain.updateTask(taskId, { title, description: nextDescription, status });
520
+ const task = mutations.updateTask(taskId, { title, description: nextDescription, status });
519
521
 
520
522
  return okResult({
521
523
  command: "task.update",
@@ -525,7 +527,7 @@ export async function runTask(context: CliContext): Promise<CliResult> {
525
527
  }
526
528
  case "delete": {
527
529
  const taskId: string = parsed.positional[1] ?? "";
528
- domain.deleteTask(taskId);
530
+ mutations.deleteTask(taskId);
529
531
 
530
532
  return okResult({
531
533
  command: "task.delete",
@@ -0,0 +1,27 @@
1
+ export const ENTITY_OPERATIONS = {
2
+ epic: {
3
+ created: "epic.created",
4
+ updated: "epic.updated",
5
+ deleted: "epic.deleted",
6
+ },
7
+ task: {
8
+ created: "task.created",
9
+ updated: "task.updated",
10
+ deleted: "task.deleted",
11
+ },
12
+ subtask: {
13
+ created: "subtask.created",
14
+ updated: "subtask.updated",
15
+ deleted: "subtask.deleted",
16
+ },
17
+ dependency: {
18
+ added: "dependency.added",
19
+ removed: "dependency.removed",
20
+ },
21
+ } as const;
22
+
23
+ export type MutationOperation =
24
+ | (typeof ENTITY_OPERATIONS)["epic"][keyof (typeof ENTITY_OPERATIONS)["epic"]]
25
+ | (typeof ENTITY_OPERATIONS)["task"][keyof (typeof ENTITY_OPERATIONS)["task"]]
26
+ | (typeof ENTITY_OPERATIONS)["subtask"][keyof (typeof ENTITY_OPERATIONS)["subtask"]]
27
+ | (typeof ENTITY_OPERATIONS)["dependency"][keyof (typeof ENTITY_OPERATIONS)["dependency"]];
@@ -0,0 +1,169 @@
1
+ import { type Database } from "bun:sqlite";
2
+
3
+ import { appendEventWithGitContext } from "../sync/event-writes";
4
+ import { ENTITY_OPERATIONS } from "./mutation-operations";
5
+ import { TrackerDomain } from "./tracker-domain";
6
+ import { type DependencyRecord, type EpicRecord, type SubtaskRecord, type TaskRecord } from "./types";
7
+
8
+ export class MutationService {
9
+ readonly #db: Database;
10
+ readonly #cwd: string;
11
+ readonly #domain: TrackerDomain;
12
+
13
+ constructor(db: Database, cwd: string) {
14
+ this.#db = db;
15
+ this.#cwd = cwd;
16
+ this.#domain = new TrackerDomain(db);
17
+ }
18
+
19
+ createEpic(input: { title: string; description: string; status?: string | undefined }): EpicRecord {
20
+ return this.#db.transaction((): EpicRecord => {
21
+ const epic = this.#domain.createEpic(input);
22
+ this.#appendEntityEvent("epic", epic.id, ENTITY_OPERATIONS.epic.created, {
23
+ title: epic.title,
24
+ description: epic.description,
25
+ status: epic.status,
26
+ });
27
+ return epic;
28
+ })();
29
+ }
30
+
31
+ updateEpic(
32
+ id: string,
33
+ input: { title?: string | undefined; description?: string | undefined; status?: string | undefined },
34
+ ): EpicRecord {
35
+ return this.#db.transaction((): EpicRecord => {
36
+ const epic = this.#domain.updateEpic(id, input);
37
+ this.#appendEntityEvent("epic", epic.id, ENTITY_OPERATIONS.epic.updated, {
38
+ title: epic.title,
39
+ description: epic.description,
40
+ status: epic.status,
41
+ });
42
+ return epic;
43
+ })();
44
+ }
45
+
46
+ deleteEpic(id: string): void {
47
+ this.#db.transaction((): void => {
48
+ this.#domain.deleteEpic(id);
49
+ this.#appendEntityEvent("epic", id, ENTITY_OPERATIONS.epic.deleted, {});
50
+ })();
51
+ }
52
+
53
+ createTask(input: { epicId: string; title: string; description: string; status?: string | undefined }): TaskRecord {
54
+ return this.#db.transaction((): TaskRecord => {
55
+ const task = this.#domain.createTask(input);
56
+ this.#appendEntityEvent("task", task.id, ENTITY_OPERATIONS.task.created, {
57
+ epic_id: task.epicId,
58
+ title: task.title,
59
+ description: task.description,
60
+ status: task.status,
61
+ });
62
+ return task;
63
+ })();
64
+ }
65
+
66
+ updateTask(
67
+ id: string,
68
+ input: { title?: string | undefined; description?: string | undefined; status?: string | undefined },
69
+ ): TaskRecord {
70
+ return this.#db.transaction((): TaskRecord => {
71
+ const task = this.#domain.updateTask(id, input);
72
+ this.#appendEntityEvent("task", task.id, ENTITY_OPERATIONS.task.updated, {
73
+ epic_id: task.epicId,
74
+ title: task.title,
75
+ description: task.description,
76
+ status: task.status,
77
+ });
78
+ return task;
79
+ })();
80
+ }
81
+
82
+ deleteTask(id: string): void {
83
+ this.#db.transaction((): void => {
84
+ this.#domain.deleteTask(id);
85
+ this.#appendEntityEvent("task", id, ENTITY_OPERATIONS.task.deleted, {});
86
+ })();
87
+ }
88
+
89
+ createSubtask(input: {
90
+ taskId: string;
91
+ title: string;
92
+ description?: string | undefined;
93
+ status?: string | undefined;
94
+ }): SubtaskRecord {
95
+ return this.#db.transaction((): SubtaskRecord => {
96
+ const subtask = this.#domain.createSubtask(input);
97
+ this.#appendEntityEvent("subtask", subtask.id, ENTITY_OPERATIONS.subtask.created, {
98
+ task_id: subtask.taskId,
99
+ title: subtask.title,
100
+ description: subtask.description,
101
+ status: subtask.status,
102
+ });
103
+ return subtask;
104
+ })();
105
+ }
106
+
107
+ updateSubtask(
108
+ id: string,
109
+ input: { title?: string | undefined; description?: string | undefined; status?: string | undefined },
110
+ ): SubtaskRecord {
111
+ return this.#db.transaction((): SubtaskRecord => {
112
+ const subtask = this.#domain.updateSubtask(id, input);
113
+ this.#appendEntityEvent("subtask", subtask.id, ENTITY_OPERATIONS.subtask.updated, {
114
+ task_id: subtask.taskId,
115
+ title: subtask.title,
116
+ description: subtask.description,
117
+ status: subtask.status,
118
+ });
119
+ return subtask;
120
+ })();
121
+ }
122
+
123
+ deleteSubtask(id: string): void {
124
+ this.#db.transaction((): void => {
125
+ this.#domain.deleteSubtask(id);
126
+ this.#appendEntityEvent("subtask", id, ENTITY_OPERATIONS.subtask.deleted, {});
127
+ })();
128
+ }
129
+
130
+ addDependency(sourceId: string, dependsOnId: string): DependencyRecord {
131
+ return this.#db.transaction((): DependencyRecord => {
132
+ const dependency = this.#domain.addDependency(sourceId, dependsOnId);
133
+ this.#appendEntityEvent("dependency", dependency.id, ENTITY_OPERATIONS.dependency.added, {
134
+ source_id: dependency.sourceId,
135
+ source_kind: dependency.sourceKind,
136
+ depends_on_id: dependency.dependsOnId,
137
+ depends_on_kind: dependency.dependsOnKind,
138
+ });
139
+ return dependency;
140
+ })();
141
+ }
142
+
143
+ removeDependency(sourceId: string, dependsOnId: string): number {
144
+ return this.#db.transaction((): number => {
145
+ const removed = this.#domain.removeDependency(sourceId, dependsOnId);
146
+ if (removed > 0) {
147
+ this.#appendEntityEvent("dependency", `${sourceId}->${dependsOnId}`, ENTITY_OPERATIONS.dependency.removed, {
148
+ source_id: sourceId,
149
+ depends_on_id: dependsOnId,
150
+ });
151
+ }
152
+ return removed;
153
+ })();
154
+ }
155
+
156
+ #appendEntityEvent(
157
+ entityKind: "epic" | "task" | "subtask" | "dependency",
158
+ entityId: string,
159
+ operation: string,
160
+ fields: Record<string, unknown>,
161
+ ): void {
162
+ appendEventWithGitContext(this.#db, this.#cwd, {
163
+ entityKind,
164
+ entityId,
165
+ operation,
166
+ fields,
167
+ });
168
+ }
169
+ }