@zinn-dev/core 0.0.4 → 0.0.5

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 CHANGED
@@ -1,31 +1,70 @@
1
+ import type { Task } from "./src/types";
2
+
3
+ import { randomUUIDv7 } from "bun";
4
+ import { generateKeyBetween } from "fractional-indexing";
5
+
1
6
  // TODO: do not initialize before the first "valid" command
2
7
  import "./src/config";
3
- import { db, addProject, getProjectByKey, deleteProjectByKey } from "./src/db";
8
+ import * as dbProject from "./src/db/project";
9
+ import * as dbTask from "./src/db/task";
10
+ import { standardizeProjectKey } from "./src/lib";
11
+
12
+ export const project = {
13
+ getByKey: dbProject.getByKey,
14
+ create: ({ key, name }: { key: string; name: string }) => {
15
+ const standardizedKey = standardizeProjectKey(key);
16
+ const project = dbProject.getByKey(standardizedKey);
17
+
18
+ if (project != null) {
19
+ throw new Error(`Project with key "${standardizedKey}" already exists!`);
20
+ }
21
+
22
+ dbProject.create(standardizedKey, name);
23
+ },
24
+ delete: (key: string) => {
25
+ const standardizedKey = standardizeProjectKey(key);
26
+ const project = dbProject.getByKey(standardizedKey);
27
+
28
+ if (project == null) {
29
+ throw new Error(`Project with key "${standardizedKey}" does not exist!`);
30
+ }
4
31
 
5
- function standardizeKey(key: string) {
6
- // TODO: maybe force latin characters only to prevent unexpected stuff from charaters like Ğ, İ, etc.
7
- return key.toUpperCase();
8
- }
32
+ dbProject.deleteByKey(standardizedKey);
33
+ },
34
+ standardizeKey: standardizeProjectKey,
35
+ };
9
36
 
10
- // TODO: switch to object param
11
- export function createProject(key: string, name: string) {
12
- const standardizedKey = standardizeKey(key);
13
- const project = getProjectByKey(standardizedKey);
37
+ export const task = {
38
+ create: (task: Pick<Task, "project_id" | "name" | "description">) => {
39
+ // TODO: if anything after this fails, especially the task creation, the counter is still incremented but not assigned to any task
40
+ const project = dbProject.incrementTaskCounterById(task.project_id);
14
41
 
15
- if (project != null) {
16
- throw new Error(`Project with key "${standardizedKey}" already exists!`);
17
- }
42
+ if (project == null) {
43
+ throw new Error(`Project's next task number could not be retrieved (ID:${task.project_id})`);
44
+ }
18
45
 
19
- addProject(standardizedKey, name);
20
- }
46
+ // TODO: do proper refined query
47
+ // FIXME: returns all tasks from *all* projects
48
+ const allTasks = dbTask.getAll();
21
49
 
22
- export function deleteProject(key: string) {
23
- const standardizedKey = standardizeKey(key);
24
- const project = getProjectByKey(standardizedKey);
50
+ let previousTaskOrder = null;
51
+ if (allTasks.length > 0) {
52
+ const lastTask = allTasks[allTasks.length - 1]!;
53
+ previousTaskOrder = lastTask.task_order;
54
+ }
25
55
 
26
- if (project == null) {
27
- throw new Error(`Project with key "${standardizedKey}" does not exist!`);
28
- }
56
+ const order = generateKeyBetween(previousTaskOrder, null);
29
57
 
30
- deleteProjectByKey(standardizedKey);
31
- }
58
+ const now = Date.now();
59
+ return dbTask.create({
60
+ id: randomUUIDv7(),
61
+ project_id: task.project_id,
62
+ number: project.task_count,
63
+ name: task.name,
64
+ description: task.description,
65
+ task_order: order,
66
+ created_at: now,
67
+ updated_at: now,
68
+ });
69
+ },
70
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zinn-dev/core",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Shared logic for zinn, a kanban workflow in the terminal.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,5 +22,8 @@
22
22
  ],
23
23
  "publishConfig": {
24
24
  "access": "public"
25
+ },
26
+ "dependencies": {
27
+ "fractional-indexing": "4.0.0"
25
28
  }
26
29
  }
package/src/constant.ts CHANGED
@@ -12,6 +12,7 @@ export const DATA_DIR = join(MAIN_DIR, "data");
12
12
  export const DB_PATH = join(DATA_DIR, DB_FILE_NAME);
13
13
  export const DB_TABLE = {
14
14
  project: "project",
15
+ task: "task",
15
16
  };
16
17
 
17
18
  // TODO: implement zod schema for setting.json
@@ -0,0 +1,46 @@
1
+ import type { Bind, Project } from "../types";
2
+
3
+ import { randomUUIDv7 } from "bun";
4
+
5
+ import { DB_TABLE } from "../constant";
6
+ import { getDb } from "../db";
7
+ import { standardizeProjectKey } from "../lib";
8
+
9
+ export function getByKey(key: string) {
10
+ const db = getDb();
11
+ const standardizedKey = standardizeProjectKey(key);
12
+
13
+ return db
14
+ .query<Project, Bind<{ key: string }>>(`SELECT * FROM ${DB_TABLE.project} WHERE key = $key`)
15
+ .get({ $key: standardizedKey });
16
+ }
17
+
18
+ // TODO: switch to object param
19
+ export function create(key: string, name: string) {
20
+ const db = getDb();
21
+
22
+ const query = db.query(`INSERT INTO
23
+ ${DB_TABLE.project} (id, key, name, created_at, updated_at)
24
+ VALUES ($id, $key, $name, $created, $updated);`);
25
+ const time = Date.now();
26
+
27
+ return query.run({ $id: randomUUIDv7(), $key: key, $name: name, $created: time, $updated: time });
28
+ }
29
+
30
+ export function incrementTaskCounterById(projectId: string) {
31
+ const db = getDb();
32
+
33
+ const nextTaskNumber = db
34
+ .query<Pick<Project, "task_count">, Bind<Pick<Project, "id">>>(`UPDATE ${DB_TABLE.project}
35
+ SET task_count = task_count + 1
36
+ WHERE id = $id RETURNING task_count`)
37
+ .get({ $id: projectId });
38
+ return nextTaskNumber;
39
+ }
40
+
41
+ // TODO: maybe allow both by `key` and `id`, and use whichever is provided
42
+ export function deleteByKey(key: string) {
43
+ const db = getDb();
44
+
45
+ return db.query(`DELETE FROM ${DB_TABLE.project} WHERE key = $key;`).run({ $key: key });
46
+ }
package/src/db/task.ts ADDED
@@ -0,0 +1,29 @@
1
+ import type { Bind, Task } from "../types";
2
+
3
+ import { getDb } from "../db";
4
+ import { DB_TABLE } from "../constant";
5
+
6
+ // TODO: refined parameters, preferably of type SQLBindings
7
+ export function getAll() {
8
+ const db = getDb();
9
+ return db.query<Task, any>(`SELECT * FROM ${DB_TABLE.task}`).all();
10
+ }
11
+
12
+ export function create(task: Omit<Task, "archived_at">) {
13
+ const db = getDb();
14
+
15
+ const q = db.query<Task, Bind<Omit<Task, "archived_at">>>(`INSERT INTO
16
+ ${DB_TABLE.task} (id, project_id, number, name, description, task_order, created_at, updated_at)
17
+ VALUES ($id, $project_id, $number, $name, $description, $task_order, $created_at, $updated_at);`);
18
+
19
+ return q.run({
20
+ $id: task.id,
21
+ $project_id: task.project_id,
22
+ $number: task.number,
23
+ $name: task.name,
24
+ $description: task.description,
25
+ $task_order: task.task_order,
26
+ $created_at: task.created_at,
27
+ $updated_at: task.updated_at,
28
+ });
29
+ }
package/src/db.ts CHANGED
@@ -1,34 +1,48 @@
1
1
  import { Database } from "bun:sqlite";
2
2
  import { DB_PATH, DB_TABLE } from "./constant";
3
- import { randomUUIDv7 } from "bun";
4
3
 
5
- export const db = new Database(DB_PATH, { create: true });
6
- db.run("PRAGMA journal_mode = WAL;");
7
- // TODO: https://bun.com/docs/runtime/sqlite#wal-sidecar-file-cleanup macOS does not auto cleanup
4
+ let db: Database | null = null;
8
5
 
9
- const projectTableSetup = db.query(`CREATE TABLE IF NOT EXISTS ${DB_TABLE.project} (
6
+ function initDb() {
7
+ // TODO: turn on strict mode, and refactor `$` prefixes: https://bun.com/docs/runtime/sqlite#strict-true-lets-you-bind-values-without-prefixes
8
+ const db = new Database(DB_PATH, { create: true });
9
+
10
+ db.run("PRAGMA journal_mode = WAL;");
11
+ db.run("PRAGMA foreign_keys = true;");
12
+ // TODO: https://bun.com/docs/runtime/sqlite#wal-sidecar-file-cleanup macOS does not auto cleanup
13
+
14
+ // --- PROJECT TABLE
15
+ db.query(`CREATE TABLE IF NOT EXISTS ${DB_TABLE.project} (
10
16
  id TEXT PRIMARY KEY,
11
17
  key TEXT NOT NULL UNIQUE,
12
18
  name TEXT NOT NULL,
19
+ task_count INTEGER NOT NULL DEFAULT 0,
20
+ created_at INTEGER NOT NULL,
21
+ updated_at INTEGER NOT NULL,
22
+ archived_at INTEGER);`).run();
23
+
24
+ // --- TASK TABLE
25
+ db.query(`CREATE TABLE IF NOT EXISTS ${DB_TABLE.task} (
26
+ id TEXT PRIMARY KEY,
27
+ project_id TEXT NOT NULL REFERENCES project(id) ON DELETE CASCADE,
28
+ number INTEGER NOT NULL,
29
+ name TEXT NOT NULL,
30
+ description TEXT,
31
+ task_order TEXT,
13
32
  created_at INTEGER NOT NULL,
14
33
  updated_at INTEGER NOT NULL,
15
- archived_at INTEGER);`);
16
- projectTableSetup.run();
34
+ archived_at INTEGER);`).run();
17
35
 
18
- export function getProjectByKey(key: string) {
19
- return db.query(`SELECT * FROM ${DB_TABLE.project} WHERE key = $key`).get({ $key: key });
36
+ return db;
20
37
  }
21
38
 
22
- // TODO: switch to object param
23
- export function addProject(key: string, name: string) {
24
- const query = db.query(`INSERT INTO
25
- ${DB_TABLE.project} (id, key, name, created_at, updated_at)
26
- VALUES ($id, $key, $name, $created, $updated);`);
27
- const time = Date.now();
39
+ db = initDb();
28
40
 
29
- return query.run({ $id: randomUUIDv7(), $key: key, $name: name, $created: time, $updated: time });
30
- }
41
+ export function getDb() {
42
+ if (db == null) {
43
+ console.error("There was a problem initializing the database");
44
+ process.exit(1);
45
+ }
31
46
 
32
- export function deleteProjectByKey(key: string) {
33
- return db.query(`DELETE FROM ${DB_TABLE.project} WHERE key = $key;`).run({ $key: key });
47
+ return db;
34
48
  }
package/src/lib.ts ADDED
@@ -0,0 +1,4 @@
1
+ export function standardizeProjectKey(key: string) {
2
+ // TODO: maybe force latin characters only to prevent unexpected stuff from charaters like Ğ, İ, etc.
3
+ return key.toUpperCase();
4
+ }
package/src/types.ts ADDED
@@ -0,0 +1,28 @@
1
+ // https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html
2
+ type BoundProperty<P extends string> = `$${P}`;
3
+ // https://www.typescriptlang.org/docs/handbook/2/mapped-types.html
4
+ export type Bind<T, K extends string & keyof T = string & keyof T> = {
5
+ [Property in BoundProperty<K>]: T[keyof T];
6
+ };
7
+
8
+ export type Project = {
9
+ id: string;
10
+ key: string;
11
+ name: string;
12
+ task_count: number;
13
+ created_at: number;
14
+ updated_at: number;
15
+ archived_at: number | null;
16
+ };
17
+
18
+ export type Task = {
19
+ id: string;
20
+ project_id: string;
21
+ number: number;
22
+ name: string;
23
+ description: string | null;
24
+ task_order: string;
25
+ created_at: number;
26
+ updated_at: number;
27
+ archived_at: number | null;
28
+ };