@anchrd/intel-api 0.27.0 → 0.28.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,116 @@
1
+ -- Two more node kinds, `board` and `task`, and the two side tables that carry what a node row must
2
+ -- not (D66, anchrd/intel#376).
3
+ --
4
+ -- ⚠️ BOTH words in ONE rebuild. Adding them in two migrations would be the same risky operation
5
+ -- twice, and this one has gone wrong before: `0005` committed with every row of `node_links` gone.
6
+ --
7
+ -- The split this migration encodes: IDENTITY through `kind`, FIELDS through a side table.
8
+ -- * `kind` because the tree has to hide `task` on EVERY level query. As a kind that is a condition
9
+ -- on a column the query already carries; as a `document` plus a side-table row it would be a
10
+ -- join on the hottest path in the interface.
11
+ -- * the side table because `status`, `assignee` and the dates are FILTER columns. In the `nodes`
12
+ -- row they would stand empty for every other kind.
13
+ --
14
+ -- The recipe below is the one in `packages/api/CLAUDE.md`, *Rebuilding `nodes` is a recipe*. All
15
+ -- four points, and each is one a previous migration got wrong first:
16
+ -- 1. the new table is created under its FINAL name — never `ALTER TABLE … RENAME`, which rewrites
17
+ -- every `REFERENCES` clause and drags the children onto the table about to be dropped;
18
+ -- 2. `node_links` is carried out and back with `INSERT OR IGNORE`, because it is the one child
19
+ -- declared `ON DELETE CASCADE` and `DROP TABLE nodes` fires an implicit `DELETE FROM`;
20
+ -- 3. `PRAGMA defer_foreign_keys`, never `foreign_keys = OFF` — D1 ignores the latter over its HTTP
21
+ -- API while miniflare honours it, so a green test here would prove nothing about production;
22
+ -- 4. the columns are named on BOTH sides of `INSERT … SELECT`, so a future reordering cannot turn
23
+ -- every title into an owner id and still commit.
24
+ PRAGMA defer_foreign_keys = TRUE;
25
+
26
+ CREATE TABLE nodes_carry AS SELECT * FROM nodes;
27
+ CREATE TABLE node_links_carry AS SELECT * FROM node_links;
28
+
29
+ DROP TABLE nodes;
30
+
31
+ CREATE TABLE nodes (
32
+ id TEXT PRIMARY KEY NOT NULL,
33
+ parent_id TEXT REFERENCES nodes(id),
34
+ kind TEXT NOT NULL CHECK (kind IN ('folder', 'document', 'attachment', 'table', 'board', 'task')),
35
+ title TEXT NOT NULL CHECK (length(title) BETWEEN 1 AND 240),
36
+ description TEXT CHECK (description IS NULL OR length(description) <= 2000),
37
+ owner_id TEXT NOT NULL,
38
+ current_version_id TEXT,
39
+ created_at TEXT NOT NULL,
40
+ updated_at TEXT NOT NULL,
41
+ archived_at TEXT
42
+ );
43
+
44
+ INSERT INTO nodes (
45
+ id, parent_id, kind, title, description, owner_id,
46
+ current_version_id, created_at, updated_at, archived_at
47
+ )
48
+ SELECT
49
+ id, parent_id, kind, title, description, owner_id,
50
+ current_version_id, created_at, updated_at, archived_at
51
+ FROM nodes_carry;
52
+
53
+ INSERT OR IGNORE INTO node_links SELECT * FROM node_links_carry;
54
+
55
+ DROP TABLE nodes_carry;
56
+ DROP TABLE node_links_carry;
57
+
58
+ CREATE INDEX nodes_parent_idx ON nodes(parent_id, archived_at, title);
59
+ CREATE INDEX nodes_owner_idx ON nodes(owner_id, archived_at);
60
+
61
+ -- The board's own configuration: which columns it has and in what order. It is one row per board
62
+ -- node, and it exists because a list of statuses is not a node property — every other kind would
63
+ -- carry it empty.
64
+ --
65
+ -- ⚠️ `ON DELETE CASCADE` here and on `board_tasks` below, and that is deliberate in a repository
66
+ -- whose rule is *refuse rather than cascade*. The rule protects a CONTAINER from taking its contents
67
+ -- down with it; these two rows are not contents but the node's own fields, split off only because
68
+ -- SQLite has no per-kind columns. A board whose row here outlived it would be a configuration for a
69
+ -- board that does not exist.
70
+ CREATE TABLE boards (
71
+ node_id TEXT PRIMARY KEY NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
72
+ statuses_json TEXT NOT NULL DEFAULT '[]',
73
+ created_at TEXT NOT NULL,
74
+ updated_at TEXT NOT NULL
75
+ );
76
+
77
+ -- What a task carries beyond a node. Every column here is one somebody filters or sorts by — that
78
+ -- is the whole reason they are not in the body.
79
+ --
80
+ -- ⚠️ `position` is REAL, not INTEGER. Dragging a card between two neighbours then needs no rewrite
81
+ -- of the whole column: the new value is the midpoint of the two. With integers every drop would
82
+ -- renumber the rows below it, and two people dropping at once would fight over rows neither touched.
83
+ CREATE TABLE board_tasks (
84
+ node_id TEXT PRIMARY KEY NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
85
+ board_id TEXT NOT NULL REFERENCES nodes(id),
86
+ status TEXT NOT NULL,
87
+ assignee_id TEXT,
88
+ labels_json TEXT NOT NULL DEFAULT '[]',
89
+ start_date TEXT,
90
+ due_date TEXT,
91
+ -- "waits for", as a COLUMN and not as a sentence in the body. The DoD of #376 says it in one
92
+ -- line: relations are columns the text points at, never the other way round. A dependency living
93
+ -- in prose cannot be asked "what is blocked right now", and `node_links` cannot answer it either
94
+ -- — that table says two nodes are related, not that one waits for the other.
95
+ --
96
+ -- ⚠️ It points at `nodes`, not at `board_tasks`. A task may wait for something that is not a
97
+ -- task: a document that has to be approved first, a board that has to be finished. Narrowing it
98
+ -- to the side table would decide today that only tasks can block, and nothing asked for that.
99
+ depends_on TEXT REFERENCES nodes(id),
100
+ position REAL NOT NULL DEFAULT 0,
101
+ created_at TEXT NOT NULL,
102
+ updated_at TEXT NOT NULL
103
+ );
104
+
105
+ -- The board view reads one column at a time, ordered. Both columns of the sort are in the index, so
106
+ -- the read needs no sorting pass — the same reason `audit_events_feed_idx` carries its tie-break
107
+ -- (anchrd/intel#620).
108
+ CREATE INDEX board_tasks_column_idx ON board_tasks(board_id, status, position);
109
+
110
+ -- "What is assigned to me, soonest first" — across every board, which is why `assignee_id` leads
111
+ -- and `board_id` does not appear.
112
+ CREATE INDEX board_tasks_assignee_idx ON board_tasks(assignee_id, due_date);
113
+
114
+ -- "What is blocked by this node" — the direction the interface asks in. Without it the question is
115
+ -- a scan of every task on every board.
116
+ CREATE INDEX board_tasks_depends_idx ON board_tasks(depends_on) WHERE depends_on IS NOT NULL;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anchrd/intel-api",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -43,7 +43,7 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "@anchrd/gate-sdk": "^0.21.0",
46
- "@anchrd/intel-contract": "^0.22.0",
46
+ "@anchrd/intel-contract": "^0.23.0",
47
47
  "@cfworker/json-schema": "^4.1.1",
48
48
  "@modelcontextprotocol/sdk": "^1.30.0",
49
49
  "fflate": "^0.8.3",