things-api 0.5.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/LICENSE +21 -0
- package/README.md +77 -0
- package/bin/things.js +9 -0
- package/dist/audit/log.d.ts +10 -0
- package/dist/audit/log.js +31 -0
- package/dist/audit/log.js.map +1 -0
- package/dist/audit/schema.d.ts +37 -0
- package/dist/audit/schema.js +9 -0
- package/dist/audit/schema.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +9 -0
- package/dist/cli/commands/doctor.js +60 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +7 -0
- package/dist/cli/commands/mcp.js +26 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/project.d.ts +5 -0
- package/dist/cli/commands/project.js +50 -0
- package/dist/cli/commands/project.js.map +1 -0
- package/dist/cli/commands/reads.d.ts +21 -0
- package/dist/cli/commands/reads.js +295 -0
- package/dist/cli/commands/reads.js.map +1 -0
- package/dist/cli/commands/snapshot.d.ts +7 -0
- package/dist/cli/commands/snapshot.js +22 -0
- package/dist/cli/commands/snapshot.js.map +1 -0
- package/dist/cli/commands/todo.d.ts +6 -0
- package/dist/cli/commands/todo.js +43 -0
- package/dist/cli/commands/todo.js.map +1 -0
- package/dist/cli/commands/writes.d.ts +9 -0
- package/dist/cli/commands/writes.js +994 -0
- package/dist/cli/commands/writes.js.map +1 -0
- package/dist/cli/exit-codes.d.ts +26 -0
- package/dist/cli/exit-codes.js +26 -0
- package/dist/cli/exit-codes.js.map +1 -0
- package/dist/cli/main.d.ts +4 -0
- package/dist/cli/main.js +73 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/output.d.ts +42 -0
- package/dist/cli/output.js +16 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/style.d.ts +6 -0
- package/dist/cli/style.js +25 -0
- package/dist/cli/style.js.map +1 -0
- package/dist/client.d.ts +184 -0
- package/dist/client.js +187 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.js +76 -0
- package/dist/config.js.map +1 -0
- package/dist/contracts.d.ts +79 -0
- package/dist/contracts.js +46 -0
- package/dist/contracts.js.map +1 -0
- package/dist/db/baselines/db-v26.d.ts +8 -0
- package/dist/db/baselines/db-v26.js +16 -0
- package/dist/db/baselines/db-v26.js.map +1 -0
- package/dist/db/baselines/index.d.ts +3 -0
- package/dist/db/baselines/index.js +4 -0
- package/dist/db/baselines/index.js.map +1 -0
- package/dist/db/connection.d.ts +22 -0
- package/dist/db/connection.js +40 -0
- package/dist/db/connection.js.map +1 -0
- package/dist/db/fingerprint.d.ts +42 -0
- package/dist/db/fingerprint.js +82 -0
- package/dist/db/fingerprint.js.map +1 -0
- package/dist/db/locate.d.ts +13 -0
- package/dist/db/locate.js +44 -0
- package/dist/db/locate.js.map +1 -0
- package/dist/db/schema.d.ts +37 -0
- package/dist/db/schema.js +81 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/diagnose.d.ts +67 -0
- package/dist/diagnose.js +143 -0
- package/dist/diagnose.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/server.d.ts +8 -0
- package/dist/mcp/server.js +1002 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/model/dates.d.ts +38 -0
- package/dist/model/dates.js +104 -0
- package/dist/model/dates.js.map +1 -0
- package/dist/model/entities.d.ts +117 -0
- package/dist/model/entities.js +21 -0
- package/dist/model/entities.js.map +1 -0
- package/dist/model/mappers.d.ts +55 -0
- package/dist/model/mappers.js +105 -0
- package/dist/model/mappers.js.map +1 -0
- package/dist/model/occurrences.d.ts +46 -0
- package/dist/model/occurrences.js +164 -0
- package/dist/model/occurrences.js.map +1 -0
- package/dist/model/recurrence.d.ts +52 -0
- package/dist/model/recurrence.js +164 -0
- package/dist/model/recurrence.js.map +1 -0
- package/dist/paths.d.ts +6 -0
- package/dist/paths.js +30 -0
- package/dist/paths.js.map +1 -0
- package/dist/read/detail.d.ts +8 -0
- package/dist/read/detail.js +34 -0
- package/dist/read/detail.js.map +1 -0
- package/dist/read/project-view.d.ts +34 -0
- package/dist/read/project-view.js +87 -0
- package/dist/read/project-view.js.map +1 -0
- package/dist/read/queries.d.ts +39 -0
- package/dist/read/queries.js +134 -0
- package/dist/read/queries.js.map +1 -0
- package/dist/read/snapshot.d.ts +25 -0
- package/dist/read/snapshot.js +39 -0
- package/dist/read/snapshot.js.map +1 -0
- package/dist/read/tags.d.ts +17 -0
- package/dist/read/tags.js +64 -0
- package/dist/read/tags.js.map +1 -0
- package/dist/read/views.d.ts +152 -0
- package/dist/read/views.js +322 -0
- package/dist/read/views.js.map +1 -0
- package/dist/surface-copy.d.ts +15 -0
- package/dist/surface-copy.js +16 -0
- package/dist/surface-copy.js.map +1 -0
- package/dist/write/automation-probe.d.ts +12 -0
- package/dist/write/automation-probe.js +59 -0
- package/dist/write/automation-probe.js.map +1 -0
- package/dist/write/batch.d.ts +51 -0
- package/dist/write/batch.js +97 -0
- package/dist/write/batch.js.map +1 -0
- package/dist/write/capabilities.d.ts +16 -0
- package/dist/write/capabilities.js +16 -0
- package/dist/write/capabilities.js.map +1 -0
- package/dist/write/commands.d.ts +32 -0
- package/dist/write/commands.js +1274 -0
- package/dist/write/commands.js.map +1 -0
- package/dist/write/environment.d.ts +29 -0
- package/dist/write/environment.js +105 -0
- package/dist/write/environment.js.map +1 -0
- package/dist/write/experimental.d.ts +7 -0
- package/dist/write/experimental.js +39 -0
- package/dist/write/experimental.js.map +1 -0
- package/dist/write/failure-hints.d.ts +32 -0
- package/dist/write/failure-hints.js +71 -0
- package/dist/write/failure-hints.js.map +1 -0
- package/dist/write/guards.d.ts +21 -0
- package/dist/write/guards.js +312 -0
- package/dist/write/guards.js.map +1 -0
- package/dist/write/lock.d.ts +10 -0
- package/dist/write/lock.js +70 -0
- package/dist/write/lock.js.map +1 -0
- package/dist/write/operations.d.ts +262 -0
- package/dist/write/operations.js +34 -0
- package/dist/write/operations.js.map +1 -0
- package/dist/write/pipeline.d.ts +90 -0
- package/dist/write/pipeline.js +375 -0
- package/dist/write/pipeline.js.map +1 -0
- package/dist/write/planner.d.ts +40 -0
- package/dist/write/planner.js +57 -0
- package/dist/write/planner.js.map +1 -0
- package/dist/write/pre-state.d.ts +125 -0
- package/dist/write/pre-state.js +299 -0
- package/dist/write/pre-state.js.map +1 -0
- package/dist/write/reopen.d.ts +29 -0
- package/dist/write/reopen.js +63 -0
- package/dist/write/reopen.js.map +1 -0
- package/dist/write/reorder.d.ts +19 -0
- package/dist/write/reorder.js +285 -0
- package/dist/write/reorder.js.map +1 -0
- package/dist/write/undo.d.ts +53 -0
- package/dist/write/undo.js +703 -0
- package/dist/write/undo.js.map +1 -0
- package/dist/write/vectors/applescript.d.ts +5 -0
- package/dist/write/vectors/applescript.js +216 -0
- package/dist/write/vectors/applescript.js.map +1 -0
- package/dist/write/vectors/registry.d.ts +2 -0
- package/dist/write/vectors/registry.js +11 -0
- package/dist/write/vectors/registry.js.map +1 -0
- package/dist/write/vectors/types.d.ts +48 -0
- package/dist/write/vectors/types.js +2 -0
- package/dist/write/vectors/types.js.map +1 -0
- package/dist/write/vectors/url-scheme.d.ts +3 -0
- package/dist/write/vectors/url-scheme.js +176 -0
- package/dist/write/vectors/url-scheme.js.map +1 -0
- package/dist/write/verify/delta.d.ts +126 -0
- package/dist/write/verify/delta.js +281 -0
- package/dist/write/verify/delta.js.map +1 -0
- package/dist/write/verify/poller.d.ts +24 -0
- package/dist/write/verify/poller.js +28 -0
- package/dist/write/verify/poller.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Low-level row fetchers. Every SELECT names columns exclusively from the
|
|
3
|
+
* schema manifest so removed columns fail loudly (drift), never silently.
|
|
4
|
+
*/
|
|
5
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
6
|
+
import type { Ref } from "../model/entities.ts";
|
|
7
|
+
import type { ChecklistRow, TaskRow } from "../model/mappers.ts";
|
|
8
|
+
/** Rows that repeat via a template are normal; template rows are invisible in list views. */
|
|
9
|
+
export declare const NOT_TEMPLATE = "(t.rt1_recurrenceRule IS NULL AND t.repeater IS NULL)";
|
|
10
|
+
/**
|
|
11
|
+
* UI-faithful tag membership for list filtering: direct tag, or inherited
|
|
12
|
+
* through the ancestor chain heading → project → area (T18/U18/A13 — the
|
|
13
|
+
* same chain inheritedTagsFor() walks). Takes a SET of tag uuids (the target
|
|
14
|
+
* plus its hierarchy descendants) — each of the six clauses gets the full
|
|
15
|
+
* set, so callers bind `uuids.length * 6` values via tagScopeBinds().
|
|
16
|
+
*/
|
|
17
|
+
export declare function tagScopeSql(uuidCount: number): string;
|
|
18
|
+
export declare function tagScopeBinds(uuids: string[]): string[];
|
|
19
|
+
/**
|
|
20
|
+
* A tag plus every hierarchy descendant. Filtering by a parent tag matches
|
|
21
|
+
* child-tagged items — DOCUMENTED app behavior (the UI's tag filter works
|
|
22
|
+
* this way), not lab-oracled: the UI's filter clicks aren't automatable.
|
|
23
|
+
* UNION (not UNION ALL): dedupes, so a parent cycle in TMTag data can't
|
|
24
|
+
* recurse forever.
|
|
25
|
+
*/
|
|
26
|
+
export declare function tagWithDescendants(db: DatabaseSync, uuid: string): string[];
|
|
27
|
+
/** Resolve a tag reference (uuid or unique case-insensitive title) — loud on miss. */
|
|
28
|
+
export declare function resolveTagUuid(db: DatabaseSync, ref: string): string;
|
|
29
|
+
/** Resolve a project reference (uuid or unique case-insensitive title) — loud on miss. */
|
|
30
|
+
export declare function resolveProjectUuid(db: DatabaseSync, ref: string): string;
|
|
31
|
+
/** Resolve an area reference (uuid or unique case-insensitive title) — loud on miss. */
|
|
32
|
+
export declare function resolveAreaUuid(db: DatabaseSync, ref: string): string;
|
|
33
|
+
export declare function fetchTaskRows(db: DatabaseSync, where: string, params?: unknown[]): TaskRow[];
|
|
34
|
+
export declare function fetchTaskByUuid(db: DatabaseSync, uuid: string): TaskRow | null;
|
|
35
|
+
export declare function fetchChecklistRows(db: DatabaseSync, taskUuid: string): ChecklistRow[];
|
|
36
|
+
/** Direct tags for a set of tasks, in one query. Returns uuid -> Ref[] (sorted by tag title). */
|
|
37
|
+
export declare function fetchTagsForTasks(db: DatabaseSync, taskUuids: string[]): Map<string, Ref[]>;
|
|
38
|
+
/** Lazy uuid -> Ref resolver over TMTask + TMArea titles, cached per instance. */
|
|
39
|
+
export declare function makeRefResolver(db: DatabaseSync): (uuid: string | null) => Ref | null;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { q, selectList } from "../db/schema.js";
|
|
2
|
+
/** Rows that repeat via a template are normal; template rows are invisible in list views. */
|
|
3
|
+
export const NOT_TEMPLATE = "(t.rt1_recurrenceRule IS NULL AND t.repeater IS NULL)";
|
|
4
|
+
/**
|
|
5
|
+
* UI-faithful tag membership for list filtering: direct tag, or inherited
|
|
6
|
+
* through the ancestor chain heading → project → area (T18/U18/A13 — the
|
|
7
|
+
* same chain inheritedTagsFor() walks). Takes a SET of tag uuids (the target
|
|
8
|
+
* plus its hierarchy descendants) — each of the six clauses gets the full
|
|
9
|
+
* set, so callers bind `uuids.length * 6` values via tagScopeBinds().
|
|
10
|
+
*/
|
|
11
|
+
export function tagScopeSql(uuidCount) {
|
|
12
|
+
const set = `(${Array.from({ length: uuidCount }, () => "?").join(", ")})`;
|
|
13
|
+
return `(
|
|
14
|
+
EXISTS (SELECT 1 FROM TMTaskTag tt WHERE tt.tasks = t.uuid AND tt.tags IN ${set})
|
|
15
|
+
OR EXISTS (SELECT 1 FROM TMTaskTag tt WHERE tt.tasks = t.project AND tt.tags IN ${set})
|
|
16
|
+
OR EXISTS (SELECT 1 FROM TMAreaTag at WHERE at.areas = t.area AND at.tags IN ${set})
|
|
17
|
+
OR EXISTS (SELECT 1 FROM TMTask p JOIN TMAreaTag at ON at.areas = p.area
|
|
18
|
+
WHERE p.uuid = t.project AND at.tags IN ${set})
|
|
19
|
+
OR EXISTS (SELECT 1 FROM TMTask h JOIN TMTaskTag tt ON tt.tasks = h.project
|
|
20
|
+
WHERE h.uuid = t.heading AND tt.tags IN ${set})
|
|
21
|
+
OR EXISTS (SELECT 1 FROM TMTask h JOIN TMTask p ON p.uuid = h.project
|
|
22
|
+
JOIN TMAreaTag at ON at.areas = p.area WHERE h.uuid = t.heading AND at.tags IN ${set})
|
|
23
|
+
)`;
|
|
24
|
+
}
|
|
25
|
+
export function tagScopeBinds(uuids) {
|
|
26
|
+
return Array.from({ length: 6 }, () => uuids).flat();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A tag plus every hierarchy descendant. Filtering by a parent tag matches
|
|
30
|
+
* child-tagged items — DOCUMENTED app behavior (the UI's tag filter works
|
|
31
|
+
* this way), not lab-oracled: the UI's filter clicks aren't automatable.
|
|
32
|
+
* UNION (not UNION ALL): dedupes, so a parent cycle in TMTag data can't
|
|
33
|
+
* recurse forever.
|
|
34
|
+
*/
|
|
35
|
+
export function tagWithDescendants(db, uuid) {
|
|
36
|
+
const rows = db
|
|
37
|
+
.prepare(`WITH RECURSIVE d(uuid) AS (
|
|
38
|
+
SELECT ? UNION
|
|
39
|
+
SELECT tg.uuid FROM TMTag tg JOIN d ON tg.parent = d.uuid
|
|
40
|
+
) SELECT uuid FROM d`)
|
|
41
|
+
.all(uuid);
|
|
42
|
+
return rows.map((r) => r.uuid);
|
|
43
|
+
}
|
|
44
|
+
/** Resolve a tag reference (uuid or unique case-insensitive title) — loud on miss. */
|
|
45
|
+
export function resolveTagUuid(db, ref) {
|
|
46
|
+
const byId = db.prepare("SELECT uuid FROM TMTag WHERE uuid = ?").get(ref);
|
|
47
|
+
if (byId !== undefined)
|
|
48
|
+
return byId.uuid;
|
|
49
|
+
const rows = db.prepare("SELECT uuid FROM TMTag WHERE title = ? COLLATE NOCASE").all(ref);
|
|
50
|
+
if (rows.length === 1 && rows[0] !== undefined)
|
|
51
|
+
return rows[0].uuid;
|
|
52
|
+
throw new RangeError(rows.length === 0
|
|
53
|
+
? `tag not found: ${ref} (list tags with \`things tags\`)`
|
|
54
|
+
: `tag reference is ambiguous: ${ref} (${rows.length} matches — use the uuid)`);
|
|
55
|
+
}
|
|
56
|
+
/** Resolve a project reference (uuid or unique case-insensitive title) — loud on miss. */
|
|
57
|
+
export function resolveProjectUuid(db, ref) {
|
|
58
|
+
const byId = db
|
|
59
|
+
.prepare("SELECT uuid FROM TMTask WHERE uuid = ? AND type = 1 AND trashed = 0")
|
|
60
|
+
.get(ref);
|
|
61
|
+
if (byId !== undefined)
|
|
62
|
+
return byId.uuid;
|
|
63
|
+
const rows = db
|
|
64
|
+
.prepare("SELECT uuid FROM TMTask WHERE title = ? COLLATE NOCASE AND type = 1 AND trashed = 0")
|
|
65
|
+
.all(ref);
|
|
66
|
+
if (rows.length === 1 && rows[0] !== undefined)
|
|
67
|
+
return rows[0].uuid;
|
|
68
|
+
throw new RangeError(rows.length === 0
|
|
69
|
+
? `project not found: ${ref} (list projects with \`things projects\`)`
|
|
70
|
+
: `project reference is ambiguous: ${ref} (${rows.length} matches — use the uuid)`);
|
|
71
|
+
}
|
|
72
|
+
/** Resolve an area reference (uuid or unique case-insensitive title) — loud on miss. */
|
|
73
|
+
export function resolveAreaUuid(db, ref) {
|
|
74
|
+
const byId = db.prepare("SELECT uuid FROM TMArea WHERE uuid = ?").get(ref);
|
|
75
|
+
if (byId !== undefined)
|
|
76
|
+
return byId.uuid;
|
|
77
|
+
const rows = db.prepare("SELECT uuid FROM TMArea WHERE title = ? COLLATE NOCASE").all(ref);
|
|
78
|
+
if (rows.length === 1 && rows[0] !== undefined)
|
|
79
|
+
return rows[0].uuid;
|
|
80
|
+
throw new RangeError(rows.length === 0
|
|
81
|
+
? `area not found: ${ref} (list areas with \`things areas\`)`
|
|
82
|
+
: `area reference is ambiguous: ${ref} (${rows.length} matches — use the uuid)`);
|
|
83
|
+
}
|
|
84
|
+
export function fetchTaskRows(db, where, params = []) {
|
|
85
|
+
const sql = `SELECT ${selectList("TMTask")
|
|
86
|
+
.split(", ")
|
|
87
|
+
.map((c) => `t.${c}`)
|
|
88
|
+
.join(", ")} FROM TMTask t WHERE ${where}`;
|
|
89
|
+
return db.prepare(sql).all(...params);
|
|
90
|
+
}
|
|
91
|
+
export function fetchTaskByUuid(db, uuid) {
|
|
92
|
+
const rows = fetchTaskRows(db, "t.uuid = ?", [uuid]);
|
|
93
|
+
return rows[0] ?? null;
|
|
94
|
+
}
|
|
95
|
+
export function fetchChecklistRows(db, taskUuid) {
|
|
96
|
+
const sql = `SELECT ${selectList("TMChecklistItem")} FROM TMChecklistItem WHERE task = ? ORDER BY ${q("index")} ASC`;
|
|
97
|
+
return db.prepare(sql).all(taskUuid);
|
|
98
|
+
}
|
|
99
|
+
/** Direct tags for a set of tasks, in one query. Returns uuid -> Ref[] (sorted by tag title). */
|
|
100
|
+
export function fetchTagsForTasks(db, taskUuids) {
|
|
101
|
+
const map = new Map();
|
|
102
|
+
if (taskUuids.length === 0)
|
|
103
|
+
return map;
|
|
104
|
+
const placeholders = taskUuids.map(() => "?").join(",");
|
|
105
|
+
const sql = `SELECT tt.tasks AS task, tg.uuid AS uuid, tg.title AS title
|
|
106
|
+
FROM TMTaskTag tt JOIN TMTag tg ON tg.uuid = tt.tags
|
|
107
|
+
WHERE tt.tasks IN (${placeholders})
|
|
108
|
+
ORDER BY tg.title`;
|
|
109
|
+
const rows = db.prepare(sql).all(...taskUuids);
|
|
110
|
+
for (const row of rows) {
|
|
111
|
+
const list = map.get(row.task) ?? [];
|
|
112
|
+
list.push({ uuid: row.uuid, title: row.title });
|
|
113
|
+
map.set(row.task, list);
|
|
114
|
+
}
|
|
115
|
+
return map;
|
|
116
|
+
}
|
|
117
|
+
/** Lazy uuid -> Ref resolver over TMTask + TMArea titles, cached per instance. */
|
|
118
|
+
export function makeRefResolver(db) {
|
|
119
|
+
const cache = new Map();
|
|
120
|
+
const taskStmt = db.prepare("SELECT uuid, title FROM TMTask WHERE uuid = ?");
|
|
121
|
+
const areaStmt = db.prepare("SELECT uuid, title FROM TMArea WHERE uuid = ?");
|
|
122
|
+
return (uuid) => {
|
|
123
|
+
if (uuid === null)
|
|
124
|
+
return null;
|
|
125
|
+
const cached = cache.get(uuid);
|
|
126
|
+
if (cached !== undefined)
|
|
127
|
+
return cached;
|
|
128
|
+
const hit = (taskStmt.get(uuid) ?? areaStmt.get(uuid));
|
|
129
|
+
const ref = hit ? { uuid: hit.uuid, title: hit.title ?? "" } : null;
|
|
130
|
+
cache.set(uuid, ref);
|
|
131
|
+
return ref;
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=queries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/read/queries.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full normalized dump of the library — every TMTask row (all types and
|
|
3
|
+
* states, including repeating templates and trashed rows), all areas, tags,
|
|
4
|
+
* and checklist items. UUID-ordered for stable diffing; the lab harness and
|
|
5
|
+
* pre/post-mutation forensics both consume this.
|
|
6
|
+
*/
|
|
7
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
8
|
+
import type { AnyTask, Area, ChecklistItem, Tag } from "../model/entities.ts";
|
|
9
|
+
export interface Snapshot {
|
|
10
|
+
areas: Area[];
|
|
11
|
+
tags: Tag[];
|
|
12
|
+
tasks: AnyTask[];
|
|
13
|
+
checklistItems: ChecklistItem[];
|
|
14
|
+
counts: {
|
|
15
|
+
areas: number;
|
|
16
|
+
tags: number;
|
|
17
|
+
todos: number;
|
|
18
|
+
projects: number;
|
|
19
|
+
headings: number;
|
|
20
|
+
checklistItems: number;
|
|
21
|
+
trashed: number;
|
|
22
|
+
repeatingTemplates: number;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export declare function snapshotView(db: DatabaseSync): Snapshot;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { q, selectList } from "../db/schema.js";
|
|
2
|
+
import { mapChecklistItem, mapHeading, mapProject, mapTodo, } from "../model/mappers.js";
|
|
3
|
+
import { fetchTagsForTasks, fetchTaskRows, makeRefResolver } from "./queries.js";
|
|
4
|
+
import { areasView, tagsView } from "./tags.js";
|
|
5
|
+
export function snapshotView(db) {
|
|
6
|
+
const areas = areasView(db);
|
|
7
|
+
const tags = tagsView(db);
|
|
8
|
+
const rows = fetchTaskRows(db, "1=1 ORDER BY t.uuid ASC");
|
|
9
|
+
const refs = makeRefResolver(db);
|
|
10
|
+
const tagMap = fetchTagsForTasks(db, rows.map((r) => r.uuid));
|
|
11
|
+
const tasks = rows.map((row) => {
|
|
12
|
+
if (row.type === 2)
|
|
13
|
+
return mapHeading(row, refs);
|
|
14
|
+
const rowTags = tagMap.get(row.uuid) ?? [];
|
|
15
|
+
return row.type === 1 ? mapProject(row, refs, rowTags) : mapTodo(row, refs, rowTags);
|
|
16
|
+
});
|
|
17
|
+
const checklistRows = db
|
|
18
|
+
.prepare(`SELECT ${selectList("TMChecklistItem")} FROM TMChecklistItem ORDER BY ${q("uuid")} ASC`)
|
|
19
|
+
.all();
|
|
20
|
+
const checklistItems = checklistRows.map(mapChecklistItem);
|
|
21
|
+
return {
|
|
22
|
+
areas,
|
|
23
|
+
tags,
|
|
24
|
+
tasks,
|
|
25
|
+
checklistItems,
|
|
26
|
+
counts: {
|
|
27
|
+
areas: areas.length,
|
|
28
|
+
tags: tags.length,
|
|
29
|
+
todos: rows.filter((r) => r.type === 0).length,
|
|
30
|
+
projects: rows.filter((r) => r.type === 1).length,
|
|
31
|
+
headings: rows.filter((r) => r.type === 2).length,
|
|
32
|
+
checklistItems: checklistItems.length,
|
|
33
|
+
trashed: rows.filter((r) => r.trashed === 1).length,
|
|
34
|
+
repeatingTemplates: rows.filter((r) => r.rt1_recurrenceRule !== null || r.repeater !== null)
|
|
35
|
+
.length,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=snapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/read/snapshot.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,EACL,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,OAAO,GAER,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAmBhD,MAAM,UAAU,YAAY,CAAC,EAAgB;IAC3C,MAAM,KAAK,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC1B,MAAM,IAAI,GAAG,aAAa,CAAC,EAAE,EAAE,yBAAyB,CAAC,CAAC;IAC1D,MAAM,IAAI,GAAG,eAAe,CAAC,EAAE,CAAC,CAAC;IACjC,MAAM,MAAM,GAAG,iBAAiB,CAC9B,EAAE,EACF,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CACxB,CAAC;IACF,MAAM,KAAK,GAAc,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACxC,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACjD,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAC3C,OAAO,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACvF,CAAC,CAAC,CAAC;IACH,MAAM,aAAa,GAAG,EAAE;SACrB,OAAO,CACN,UAAU,UAAU,CAAC,iBAAiB,CAAC,kCAAkC,CAAC,CAAC,MAAM,CAAC,MAAM,CACzF;SACA,GAAG,EAA+B,CAAC;IACtC,MAAM,cAAc,GAAG,aAAa,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAE3D,OAAO;QACL,KAAK;QACL,IAAI;QACJ,KAAK;QACL,cAAc;QACd,MAAM,EAAE;YACN,KAAK,EAAE,KAAK,CAAC,MAAM;YACnB,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,MAAM;YAC9C,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,MAAM;YACjD,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,MAAM;YACjD,cAAc,EAAE,cAAc,CAAC,MAAM;YACrC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM;YACnD,kBAAkB,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,kBAAkB,KAAK,IAAI,IAAI,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC;iBACzF,MAAM;SACV;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tag reads: taxonomy, and inherited-tag resolution.
|
|
3
|
+
*
|
|
4
|
+
* The DB stores DIRECT tags only (TMTaskTag/TMAreaTag). The native UI's tag
|
|
5
|
+
* filtering also honors tags inherited from ancestor area/project (validated
|
|
6
|
+
* T18: things.py misses this). inheritedTagsFor() reconstructs that chain:
|
|
7
|
+
* task -> heading's project -> project -> area, collecting each ancestor's
|
|
8
|
+
* direct tags.
|
|
9
|
+
*/
|
|
10
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
11
|
+
import type { Area, Ref, Tag } from "../model/entities.ts";
|
|
12
|
+
import type { TaskRow } from "../model/mappers.ts";
|
|
13
|
+
export declare function tagsView(db: DatabaseSync): Tag[];
|
|
14
|
+
export declare function areasView(db: DatabaseSync): Area[];
|
|
15
|
+
export declare function areaTags(db: DatabaseSync, areaUuid: string): Ref[];
|
|
16
|
+
/** Tags inherited from ancestors (heading's project, project, area) — direct tags excluded. */
|
|
17
|
+
export declare function inheritedTagsFor(db: DatabaseSync, row: TaskRow): Ref[];
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { q, selectList } from "../db/schema.js";
|
|
2
|
+
import { fetchTagsForTasks, fetchTaskByUuid } from "./queries.js";
|
|
3
|
+
export function tagsView(db) {
|
|
4
|
+
const rows = db
|
|
5
|
+
.prepare(`SELECT ${selectList("TMTag")} FROM TMTag ORDER BY ${q("index")} ASC`)
|
|
6
|
+
.all();
|
|
7
|
+
const byUuid = new Map(rows.map((r) => [r.uuid, r]));
|
|
8
|
+
return rows.map((row) => {
|
|
9
|
+
const parentRow = row.parent ? byUuid.get(row.parent) : undefined;
|
|
10
|
+
return {
|
|
11
|
+
uuid: row.uuid,
|
|
12
|
+
title: row.title ?? "",
|
|
13
|
+
shortcut: row.shortcut,
|
|
14
|
+
parent: parentRow ? { uuid: parentRow.uuid, title: parentRow.title ?? "" } : null,
|
|
15
|
+
index: row.index ?? 0,
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export function areasView(db) {
|
|
20
|
+
const rows = db
|
|
21
|
+
.prepare(`SELECT ${selectList("TMArea")} FROM TMArea ORDER BY ${q("index")} ASC`)
|
|
22
|
+
.all();
|
|
23
|
+
return rows.map((row) => ({
|
|
24
|
+
uuid: row.uuid,
|
|
25
|
+
title: row.title ?? "",
|
|
26
|
+
visible: row.visible !== 0,
|
|
27
|
+
index: row.index ?? 0,
|
|
28
|
+
tags: areaTags(db, row.uuid),
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
export function areaTags(db, areaUuid) {
|
|
32
|
+
const rows = db
|
|
33
|
+
.prepare(`SELECT tg.uuid AS uuid, tg.title AS title
|
|
34
|
+
FROM TMAreaTag at JOIN TMTag tg ON tg.uuid = at.tags
|
|
35
|
+
WHERE at.areas = ? ORDER BY tg.title`)
|
|
36
|
+
.all(areaUuid);
|
|
37
|
+
return rows.map((r) => ({ uuid: r.uuid, title: r.title ?? "" }));
|
|
38
|
+
}
|
|
39
|
+
/** Tags inherited from ancestors (heading's project, project, area) — direct tags excluded. */
|
|
40
|
+
export function inheritedTagsFor(db, row) {
|
|
41
|
+
const collected = new Map();
|
|
42
|
+
const addAll = (refs) => {
|
|
43
|
+
for (const ref of refs)
|
|
44
|
+
collected.set(ref.uuid, ref);
|
|
45
|
+
};
|
|
46
|
+
let projectUuid = row.project;
|
|
47
|
+
if (!projectUuid && row.heading) {
|
|
48
|
+
const heading = fetchTaskByUuid(db, row.heading);
|
|
49
|
+
projectUuid = heading?.project ?? null;
|
|
50
|
+
}
|
|
51
|
+
let areaUuid = row.area;
|
|
52
|
+
if (projectUuid) {
|
|
53
|
+
const project = fetchTaskByUuid(db, projectUuid);
|
|
54
|
+
if (project) {
|
|
55
|
+
addAll(fetchTagsForTasks(db, [project.uuid]).get(project.uuid) ?? []);
|
|
56
|
+
areaUuid = areaUuid ?? project.area;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (areaUuid)
|
|
60
|
+
addAll(areaTags(db, areaUuid));
|
|
61
|
+
const direct = new Set((fetchTagsForTasks(db, [row.uuid]).get(row.uuid) ?? []).map((t) => t.uuid));
|
|
62
|
+
return [...collected.values()].filter((t) => !direct.has(t.uuid));
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=tags.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tags.js","sourceRoot":"","sources":["../../src/read/tags.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List views mirroring the Things sidebar. Predicates derived from the
|
|
3
|
+
* validated research plus live probes (docs/atlas/schema-v26.md):
|
|
4
|
+
*
|
|
5
|
+
* - inbox: start=0
|
|
6
|
+
* - today: (startDate <= today AND start IN (1, 2)) OR a DUE DEADLINE
|
|
7
|
+
* with no startDate — a due/overdue deadline pulls an item into
|
|
8
|
+
* Today even from the Inbox; a FUTURE startDate suppresses it
|
|
9
|
+
* (UI-oracle research, lab 2026-07-04, two reproducing runs —
|
|
10
|
+
* docs/lab/today-order-research.md; this CORRECTS the earlier
|
|
11
|
+
* "deadline-only items never enter Today" badge inference, which
|
|
12
|
+
* held only while no deadline-only item was actually due).
|
|
13
|
+
* ORDER: startBucket, then todayIndexReferenceDate DESC (the
|
|
14
|
+
* date the item ENTERED Today — newest cohorts on top), then
|
|
15
|
+
* todayIndex ASC, then uuid (observed stable tiebreak). start=2
|
|
16
|
+
* rows with a past date are "pending promotion" (Things
|
|
17
|
+
* recomputes on launch; the UI shows them in Today either way).
|
|
18
|
+
* THIS EVENING expires daily: an item renders in the Evening
|
|
19
|
+
* section only when startBucket=1 AND startDate == today exactly;
|
|
20
|
+
* overdue bucket=1 items roll back into Today proper (live-
|
|
21
|
+
* verified against the UI, 2026-07-02).
|
|
22
|
+
* Sidebar badge split: red = items with deadline <= today,
|
|
23
|
+
* gray = the rest (exact 270/122 reconciliation on live data).
|
|
24
|
+
* - anytime: ALL active items — unscheduled PLUS Today members (the UI
|
|
25
|
+
* renders Today members with a star; live-verified via screenshot
|
|
26
|
+
* 2026-07-02: starred = in Today, unstarred = unscheduled).
|
|
27
|
+
* Star equivalence: startDate != NULL && <= today.
|
|
28
|
+
* CONTAINER CASCADE (live-verified 2026-07-09): children of a
|
|
29
|
+
* project that is not itself anytime-visible (someday/future/
|
|
30
|
+
* logged/trashed) are excluded — the project row represents
|
|
31
|
+
* them. Result is grouped in sidebar order (SidebarSection[]).
|
|
32
|
+
* - upcoming: start=2 AND startDate > today, PLUS each fixed repeating
|
|
33
|
+
* template's next occurrence synthesized from
|
|
34
|
+
* rt1_nextInstanceStartDate (UI parity; opt out via
|
|
35
|
+
* repeats:false). Occurrence deadline = start − rule.ts
|
|
36
|
+
* (instance-validated 2026-07-04).
|
|
37
|
+
* - someday: start=2 AND startDate IS NULL, container-less members only
|
|
38
|
+
* (project children appear ONLY via the activeProjectItems
|
|
39
|
+
* toggle, and only for anytime-active projects — mirrors the
|
|
40
|
+
* UI's "Show items from active projects"). Grouped in sidebar
|
|
41
|
+
* order like anytime.
|
|
42
|
+
* - logbook: status IN (2,3), by stopDate DESC
|
|
43
|
+
* - trash: trashed=1
|
|
44
|
+
*
|
|
45
|
+
* All views: to-dos + projects (type IN (0,1)), open (unless noted),
|
|
46
|
+
* untrashed (unless trash), repeating TEMPLATE rows excluded.
|
|
47
|
+
*/
|
|
48
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
49
|
+
import type { Project, Ref, Todo } from "../model/entities.ts";
|
|
50
|
+
export type ListItem = Todo | Project;
|
|
51
|
+
/** Optional list-view filters. */
|
|
52
|
+
export interface ViewFilter {
|
|
53
|
+
/**
|
|
54
|
+
* Tag (uuid or unique title): direct OR inherited membership (UI
|
|
55
|
+
* semantics), INCLUDING items tagged with a hierarchy descendant of the
|
|
56
|
+
* given tag (documented app behavior — the UI's tag filter matches
|
|
57
|
+
* child-tagged items; not lab-oracled).
|
|
58
|
+
*/
|
|
59
|
+
tag?: string;
|
|
60
|
+
/**
|
|
61
|
+
* Match the given tag ONLY — no hierarchy descendants. Useful when a
|
|
62
|
+
* parent tag has its own direct assignments distinct from its children's.
|
|
63
|
+
*/
|
|
64
|
+
exactTag?: boolean;
|
|
65
|
+
}
|
|
66
|
+
export interface TodayView {
|
|
67
|
+
today: ListItem[];
|
|
68
|
+
evening: ListItem[];
|
|
69
|
+
/** Mirrors the sidebar badge: red = deadline due/overdue, gray = the rest. */
|
|
70
|
+
badge: {
|
|
71
|
+
dueOrOverdue: number;
|
|
72
|
+
other: number;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export declare function todayView(db: DatabaseSync, now?: Date, filter?: ViewFilter): TodayView;
|
|
76
|
+
export declare function inboxView(db: DatabaseSync, filter?: ViewFilter): ListItem[];
|
|
77
|
+
/**
|
|
78
|
+
* One sidebar-ordered block of a grouped view (anytime/someday): the area
|
|
79
|
+
* (null = the top-level, area-less block, which the UI renders first) and its
|
|
80
|
+
* members in UI order — direct to-dos first, then each project block (the
|
|
81
|
+
* project row immediately followed by its member to-dos).
|
|
82
|
+
*/
|
|
83
|
+
export interface SidebarSection {
|
|
84
|
+
area: Ref | null;
|
|
85
|
+
items: ListItem[];
|
|
86
|
+
}
|
|
87
|
+
export declare function anytimeView(db: DatabaseSync, now?: Date, filter?: ViewFilter): SidebarSection[];
|
|
88
|
+
/** The UI's star marker in Anytime: the item is also a Today member. */
|
|
89
|
+
export declare function isTodayMember(item: ListItem, now?: Date): boolean;
|
|
90
|
+
export interface UpcomingFilter extends ViewFilter {
|
|
91
|
+
/** Include repeating templates' next occurrences (UI parity; default true). */
|
|
92
|
+
repeats?: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Occurrences to surface per repeating template (default 1 = only the
|
|
95
|
+
* app-materialized next instance, exactly the UI). Higher values PROJECT
|
|
96
|
+
* later occurrences from the decoded rule (fixed rules only; capped at
|
|
97
|
+
* 10) — projections are host math the app has not materialized yet.
|
|
98
|
+
*/
|
|
99
|
+
horizon?: number;
|
|
100
|
+
}
|
|
101
|
+
export declare function upcomingView(db: DatabaseSync, now?: Date, filter?: UpcomingFilter): ListItem[];
|
|
102
|
+
export interface SomedayFilter extends ViewFilter {
|
|
103
|
+
/**
|
|
104
|
+
* Also list someday to-dos that live inside ACTIVE projects — the UI's
|
|
105
|
+
* "Show items from active projects" toggle (default false, the UI's
|
|
106
|
+
* default). Children of someday projects are never listed either way: the
|
|
107
|
+
* project row stands for them.
|
|
108
|
+
*/
|
|
109
|
+
activeProjectItems?: boolean;
|
|
110
|
+
}
|
|
111
|
+
export declare function somedayView(db: DatabaseSync, now?: Date, filter?: SomedayFilter): SidebarSection[];
|
|
112
|
+
export declare function logbookView(db: DatabaseSync, options?: {
|
|
113
|
+
limit?: number;
|
|
114
|
+
tag?: string;
|
|
115
|
+
}): ListItem[];
|
|
116
|
+
export declare function trashView(db: DatabaseSync, options?: {
|
|
117
|
+
limit?: number;
|
|
118
|
+
}): ListItem[];
|
|
119
|
+
export declare function projectsView(db: DatabaseSync, options?: {
|
|
120
|
+
areaUuid?: string;
|
|
121
|
+
}): Project[];
|
|
122
|
+
export interface SearchOptions extends ViewFilter {
|
|
123
|
+
limit?: number;
|
|
124
|
+
/** Restrict to one project's children (headed children included). */
|
|
125
|
+
project?: string;
|
|
126
|
+
/** Restrict to one area's direct members. */
|
|
127
|
+
area?: string;
|
|
128
|
+
type?: "to-do" | "project";
|
|
129
|
+
/** Include completed/canceled items (default: open only). */
|
|
130
|
+
logged?: boolean;
|
|
131
|
+
/** Include trashed items (default: untrashed only). */
|
|
132
|
+
trashed?: boolean;
|
|
133
|
+
/** Everything — the legacy behavior (open + logged + trashed). */
|
|
134
|
+
all?: boolean;
|
|
135
|
+
}
|
|
136
|
+
export type ChangeKind = "created" | "modified";
|
|
137
|
+
export type ChangedItem = ListItem & {
|
|
138
|
+
changeKind: ChangeKind;
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Everything that changed since a moment — created or modified TMTask rows,
|
|
142
|
+
* INCLUDING trashed, logged, and repeating-template rows (an agent syncing
|
|
143
|
+
* state needs to see deletions and template edits too; check `trashed`,
|
|
144
|
+
* `status`, and `repeating.isTemplate` on each item). Caveats: TMArea/TMTag
|
|
145
|
+
* carry no modification dates, and checklist-item edits do not bump the
|
|
146
|
+
* parent task — those changes are invisible here.
|
|
147
|
+
*/
|
|
148
|
+
export declare function changesView(db: DatabaseSync, options: {
|
|
149
|
+
since: Date;
|
|
150
|
+
limit?: number;
|
|
151
|
+
}): ChangedItem[];
|
|
152
|
+
export declare function searchView(db: DatabaseSync, query: string, options?: SearchOptions): ListItem[];
|