@zinn-dev/core 0.6.0 → 0.7.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.
- package/index.ts +37 -9
- package/package.json +1 -1
- package/src/db/relation.test.ts +26 -0
- package/src/db/task.ts +4 -0
- package/src/schemas/relation.ts +1 -0
package/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Column } from "./src/types";
|
|
1
|
+
import type { Column, Task, TaskRelation } from "./src/types";
|
|
2
2
|
import type { ProjectCreateInput, ProjectEditInput } from "./src/schemas/project";
|
|
3
3
|
import type { TaskCreateInput, TaskEditInput } from "./src/schemas/task";
|
|
4
4
|
import type { RelationCreateInput } from "./src/schemas/relation";
|
|
@@ -382,6 +382,41 @@ export const task = {
|
|
|
382
382
|
},
|
|
383
383
|
};
|
|
384
384
|
|
|
385
|
+
type TaskWithKey = Task & { key: string };
|
|
386
|
+
|
|
387
|
+
// TODO: would be better to reuse where task keys attached manually
|
|
388
|
+
function attachTaskKey(task: Task): TaskWithKey {
|
|
389
|
+
const project = dbProject.getById(task.project_id)!;
|
|
390
|
+
return { ...task, key: `${project.key}-${task.number}` };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
type RelationResult = TaskRelation & {
|
|
394
|
+
source?: TaskWithKey;
|
|
395
|
+
target?: TaskWithKey;
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
type RelationGetOptions = {
|
|
399
|
+
include?: { tasks?: boolean };
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
function getRelations(taskKey: string, options?: RelationGetOptions): Array<RelationResult> {
|
|
403
|
+
if (taskKey == null) {
|
|
404
|
+
throw new Error("A task key is required to list relations");
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const taskMatch = getTaskByKey(taskKey);
|
|
408
|
+
const relations = dbRelation.getAllByTaskId(taskMatch.id);
|
|
409
|
+
if (!options?.include?.tasks) {
|
|
410
|
+
return relations;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return relations.map((entry) => ({
|
|
414
|
+
...entry,
|
|
415
|
+
source: attachTaskKey(dbTask.getById(entry.source_task_id)!),
|
|
416
|
+
target: attachTaskKey(dbTask.getById(entry.target_task_id)!),
|
|
417
|
+
}));
|
|
418
|
+
}
|
|
419
|
+
|
|
385
420
|
export const relation = {
|
|
386
421
|
create: (input: RelationCreateInput) => {
|
|
387
422
|
const props = validateTaskInput(relationCreateSchema, input);
|
|
@@ -423,12 +458,5 @@ export const relation = {
|
|
|
423
458
|
relation_type: props.relation_type,
|
|
424
459
|
});
|
|
425
460
|
},
|
|
426
|
-
getAll:
|
|
427
|
-
if (taskKey == null) {
|
|
428
|
-
throw new Error("Provide a task key to list relations");
|
|
429
|
-
}
|
|
430
|
-
const taskMatch = getTaskByKey(taskKey);
|
|
431
|
-
|
|
432
|
-
return dbRelation.getAllByTaskId(taskMatch.id);
|
|
433
|
-
},
|
|
461
|
+
getAll: getRelations,
|
|
434
462
|
};
|
package/package.json
CHANGED
package/src/db/relation.test.ts
CHANGED
|
@@ -101,3 +101,29 @@ test("relations clean up without deleting connected tasks", () => {
|
|
|
101
101
|
assert.deepEqual(getDb().query("PRAGMA foreign_key_check").all(), []);
|
|
102
102
|
`);
|
|
103
103
|
});
|
|
104
|
+
|
|
105
|
+
test("duplicate direction and optional task enrichment preserve raw lookup", () => {
|
|
106
|
+
runRelationCheck(`
|
|
107
|
+
const app = project.create({ key: "APP" });
|
|
108
|
+
const other = project.create({ key: "OTHER" });
|
|
109
|
+
task.create({ project_id: app.id, title: "Copy", description: null });
|
|
110
|
+
task.create({ project_id: other.id, title: "Original", description: null });
|
|
111
|
+
const before = task.getByKey("OTHER-1");
|
|
112
|
+
const created = relation.create({ sourceTaskKey: "APP-1", targetTaskKey: "OTHER-1", relation_type: "duplicate" });
|
|
113
|
+
assert.equal(created.source_task_id, task.getByKey("APP-1").id);
|
|
114
|
+
assert.equal(created.target_task_id, before.id);
|
|
115
|
+
assert.deepEqual(task.getByKey("OTHER-1"), before);
|
|
116
|
+
assert.throws(() => relation.create({ sourceTaskKey: "APP-1", targetTaskKey: "OTHER-1", relation_type: "duplicate" }), /already exists/);
|
|
117
|
+
const raw = relation.getAll("APP-1");
|
|
118
|
+
assert.equal("source" in raw[0], false);
|
|
119
|
+
assert.deepEqual(relation.getAll("APP-1", { include: { tasks: false } }), raw);
|
|
120
|
+
task.archive("OTHER-1");
|
|
121
|
+
const [expanded] = relation.getAll("APP-1", { include: { tasks: true } });
|
|
122
|
+
assert.equal(expanded.source.key, "APP-1");
|
|
123
|
+
assert.equal(expanded.target.key, "OTHER-1");
|
|
124
|
+
assert.equal(expanded.target.title, "Original");
|
|
125
|
+
assert.ok(expanded.target.archived_at);
|
|
126
|
+
relation.create({ sourceTaskKey: "OTHER-1", targetTaskKey: "APP-1", relation_type: "duplicate" });
|
|
127
|
+
assert.equal(relation.getAll("APP-1").length, 2);
|
|
128
|
+
`);
|
|
129
|
+
});
|
package/src/db/task.ts
CHANGED
|
@@ -16,6 +16,10 @@ function getArchiveCondition(filter: TaskArchiveFilter) {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
export function getById(taskId: string) {
|
|
20
|
+
return getDb().query<Task, [string]>(`SELECT * FROM ${DB_TABLE.task} WHERE id = ?`).get(taskId);
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
// TODO: refined parameters, preferably of type SQLBindings
|
|
20
24
|
export function getAll(archive: TaskArchiveFilter = "active") {
|
|
21
25
|
const db = getDb();
|
package/src/schemas/relation.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
|
|
3
3
|
export const relationCreateSchema = z.strictObject({
|
|
4
4
|
// For dependency, the source task depends on the target task.
|
|
5
|
+
// For duplicate, the source task is a duplicate of the target task.
|
|
5
6
|
sourceTaskKey: z.string(),
|
|
6
7
|
targetTaskKey: z.string(),
|
|
7
8
|
relation_type: z.enum(["related", "dependency", "duplicate"]),
|