@dinoxx/dinox-cli 1.0.22 → 1.1.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/README.md +29 -9
- package/dist/auth/userInfo.d.ts +2 -0
- package/dist/auth/userInfo.js +55 -14
- package/dist/cli.js +36 -2
- package/dist/commands/auth/index.d.ts +35 -0
- package/dist/commands/auth/index.js +382 -83
- package/dist/commands/boxes/index.js +2 -6
- package/dist/commands/boxes/model.d.ts +3 -0
- package/dist/commands/boxes/model.js +16 -1
- package/dist/commands/boxes/operations.js +18 -8
- package/dist/commands/boxes/repo.js +13 -11
- package/dist/commands/boxes/store.js +1 -1
- package/dist/commands/config/index.js +7 -5
- package/dist/commands/descriptor/register.js +9 -0
- package/dist/commands/doctor.d.ts +123 -0
- package/dist/commands/doctor.js +539 -0
- package/dist/commands/graph/index.d.ts +1 -1
- package/dist/commands/graph/index.js +35 -21
- package/dist/commands/graph/repo.js +92 -17
- package/dist/commands/meta/index.js +4 -4
- package/dist/commands/meta/repo.js +4 -3
- package/dist/commands/notes/actions.d.ts +1 -1
- package/dist/commands/notes/actions.js +121 -78
- package/dist/commands/notes/bulk.d.ts +1 -0
- package/dist/commands/notes/bulk.js +23 -17
- package/dist/commands/notes/contentEngine.js +69 -11
- package/dist/commands/notes/contentPatch.d.ts +4 -1
- package/dist/commands/notes/contentPatch.js +105 -41
- package/dist/commands/notes/contentPatchCommand.js +28 -11
- package/dist/commands/notes/descriptors.js +50 -20
- package/dist/commands/notes/dryRun.d.ts +1 -0
- package/dist/commands/notes/dryRun.js +4 -7
- package/dist/commands/notes/export.d.ts +6 -0
- package/dist/commands/notes/export.js +26 -6
- package/dist/commands/notes/metadataMutation.d.ts +1 -0
- package/dist/commands/notes/metadataMutation.js +14 -3
- package/dist/commands/notes/repo.d.ts +46 -4
- package/dist/commands/notes/repo.js +113 -28
- package/dist/commands/notes/runtime.d.ts +2 -1
- package/dist/commands/notes/runtime.js +17 -1
- package/dist/commands/notes/searchSql.js +13 -6
- package/dist/commands/notes/writeReceipt.d.ts +20 -0
- package/dist/commands/notes/writeReceipt.js +82 -0
- package/dist/commands/prompt/index.js +3 -3
- package/dist/commands/prompt/repo.d.ts +4 -1
- package/dist/commands/prompt/repo.js +17 -7
- package/dist/commands/runtime.d.ts +14 -2
- package/dist/commands/runtime.js +74 -15
- package/dist/commands/schema/renderSkills.js +5 -2
- package/dist/commands/schema/spec.js +4 -0
- package/dist/commands/storage/index.js +8 -2
- package/dist/commands/storage/repo.d.ts +4 -0
- package/dist/commands/storage/repo.js +356 -92
- package/dist/commands/sync.js +16 -12
- package/dist/commands/tags/content.js +9 -0
- package/dist/commands/tags/index.js +2 -6
- package/dist/commands/tags/operations.js +15 -10
- package/dist/commands/tags/repo.d.ts +2 -0
- package/dist/commands/tags/repo.js +18 -5
- package/dist/commands/tags/store.js +1 -1
- package/dist/commands/todo/contentModel.d.ts +4 -3
- package/dist/commands/todo/contentModel.js +78 -28
- package/dist/commands/todo/dryRun.d.ts +1 -0
- package/dist/commands/todo/dryRun.js +17 -13
- package/dist/commands/todo/index.d.ts +6 -0
- package/dist/commands/todo/index.js +89 -123
- package/dist/commands/todo/repo.d.ts +36 -1
- package/dist/commands/todo/repo.js +146 -37
- package/dist/commands/todo/taskIdentity.d.ts +2 -0
- package/dist/commands/todo/taskIdentity.js +64 -0
- package/dist/commands/todo/taskModel.js +27 -17
- package/dist/commands/update.d.ts +13 -1
- package/dist/commands/update.js +135 -23
- package/dist/commands/writeDryRun.d.ts +3 -2
- package/dist/commands/writeDryRun.js +4 -3
- package/dist/commands/writeDurability.d.ts +5 -0
- package/dist/commands/writeDurability.js +22 -0
- package/dist/config/paths.js +6 -1
- package/dist/config/resolve.d.ts +2 -0
- package/dist/config/resolve.js +23 -9
- package/dist/config/store.d.ts +3 -0
- package/dist/config/store.js +61 -14
- package/dist/config/types.d.ts +4 -0
- package/dist/daemon/client.d.ts +26 -1
- package/dist/daemon/client.js +236 -12
- package/dist/daemon/ensure.js +74 -12
- package/dist/daemon/index.d.ts +42 -0
- package/dist/daemon/index.js +202 -52
- package/dist/daemon/lifecycle.d.ts +42 -0
- package/dist/daemon/lifecycle.js +115 -0
- package/dist/daemon/pid.d.ts +19 -6
- package/dist/daemon/pid.js +147 -13
- package/dist/daemon/route.d.ts +14 -0
- package/dist/daemon/route.js +40 -0
- package/dist/daemon/server.d.ts +11 -0
- package/dist/daemon/server.js +201 -50
- package/dist/daemon/types.d.ts +29 -1
- package/dist/daemon/types.js +1 -0
- package/dist/dinox.js +25 -1
- package/dist/powersync/connector.js +31 -16
- package/dist/powersync/exclusiveRuntime.d.ts +19 -0
- package/dist/powersync/exclusiveRuntime.js +42 -0
- package/dist/powersync/runtime.d.ts +24 -4
- package/dist/powersync/runtime.js +234 -36
- package/dist/powersync/syncGate.d.ts +3 -1
- package/dist/powersync/syncGate.js +7 -1
- package/dist/powersync/tokenIndex.d.ts +11 -0
- package/dist/powersync/tokenIndex.js +159 -22
- package/dist/powersync/uploader.js +107 -52
- package/dist/utils/abort.js +0 -4
- package/dist/utils/asyncRelease.d.ts +2 -0
- package/dist/utils/asyncRelease.js +25 -0
- package/dist/utils/bidlinkCanonicalization.js +3 -0
- package/dist/utils/dryRun.d.ts +1 -1
- package/dist/utils/errors.d.ts +15 -1
- package/dist/utils/errors.js +223 -5
- package/dist/utils/output.d.ts +1 -0
- package/dist/utils/output.js +51 -15
- package/dist/utils/redact.js +1 -1
- package/dist/utils/sqliteLease.d.ts +8 -0
- package/dist/utils/sqliteLease.js +59 -0
- package/dist/utils/syncTimeout.d.ts +7 -1
- package/dist/utils/syncTimeout.js +8 -7
- package/dist/utils/terminalSanitization.js +6 -2
- package/dist/utils/tiptapMarkdown.js +49 -3
- package/dist/utils/tiptapMarkdownSyntax.d.ts +9 -0
- package/dist/utils/tiptapMarkdownSyntax.js +249 -0
- package/dist/utils/tokenize.js +24 -2
- package/dist/utils/updateNotice.js +60 -12
- package/package.json +12 -10
- package/dist/commands/mcp/auth.d.ts +0 -4
- package/dist/commands/mcp/auth.js +0 -75
- package/dist/commands/mcp/index.d.ts +0 -2
- package/dist/commands/mcp/index.js +0 -86
- package/dist/commands/mcp/noteContentTools.d.ts +0 -222
- package/dist/commands/mcp/noteContentTools.js +0 -390
- package/dist/commands/mcp/server.d.ts +0 -34
- package/dist/commands/mcp/server.js +0 -424
- package/dist/commands/mcp/tools.d.ts +0 -2
- package/dist/commands/mcp/tools.js +0 -32
- package/dist/commands/mcp/types.d.ts +0 -39
- package/dist/commands/mcp/types.js +0 -1
- package/dist/utils/noteContentEngine.d.ts +0 -333
- package/dist/utils/noteContentEngine.js +0 -740
|
@@ -8,7 +8,7 @@ export async function getBacklinks(db, noteId) {
|
|
|
8
8
|
n.title
|
|
9
9
|
FROM c_note_link l
|
|
10
10
|
LEFT JOIN c_note n ON l.src_note_id = n.id
|
|
11
|
-
WHERE l.dst_note_id = ? AND l.is_del = 0 AND (n.is_del = 0 OR n.is_del IS NULL)
|
|
11
|
+
WHERE l.dst_note_id = ? AND (l.is_del IS NULL OR l.is_del = 0) AND (n.is_del = 0 OR n.is_del IS NULL)
|
|
12
12
|
ORDER BY l.created_at DESC`, [noteId]);
|
|
13
13
|
const backlinks = rows.map((row) => ({
|
|
14
14
|
note_id: row.src_note_id,
|
|
@@ -32,7 +32,7 @@ export async function getOutlinks(db, noteId) {
|
|
|
32
32
|
n.title
|
|
33
33
|
FROM c_note_link l
|
|
34
34
|
LEFT JOIN c_note n ON l.dst_note_id = n.id
|
|
35
|
-
WHERE l.src_note_id = ? AND l.is_del = 0 AND (n.is_del = 0 OR n.is_del IS NULL)
|
|
35
|
+
WHERE l.src_note_id = ? AND (l.is_del IS NULL OR l.is_del = 0) AND (n.is_del = 0 OR n.is_del IS NULL)
|
|
36
36
|
ORDER BY l.created_at DESC`, [noteId]);
|
|
37
37
|
const outlinks = rows.map((row) => ({
|
|
38
38
|
note_id: row.dst_note_id,
|
|
@@ -46,27 +46,102 @@ export async function getOutlinks(db, noteId) {
|
|
|
46
46
|
count: outlinks.length,
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
+
// Keep IN-list sizes under SQLite's bound-parameter limit when expanding a frontier.
|
|
50
|
+
const NOTE_LINK_QUERY_CHUNK_SIZE = 500;
|
|
51
|
+
function chunkIds(ids, size) {
|
|
52
|
+
const chunks = [];
|
|
53
|
+
for (let i = 0; i < ids.length; i += size) {
|
|
54
|
+
chunks.push(ids.slice(i, i + size));
|
|
55
|
+
}
|
|
56
|
+
return chunks;
|
|
57
|
+
}
|
|
58
|
+
/** Backlinks for many notes at once, grouped by the target (dst) note id. */
|
|
59
|
+
async function getBacklinksForNodes(db, ids) {
|
|
60
|
+
const grouped = new Map();
|
|
61
|
+
for (const group of chunkIds(ids, NOTE_LINK_QUERY_CHUNK_SIZE)) {
|
|
62
|
+
const placeholders = group.map(() => '?').join(', ');
|
|
63
|
+
const rows = await db.getAll(`SELECT
|
|
64
|
+
l.id,
|
|
65
|
+
l.src_note_id,
|
|
66
|
+
l.dst_note_id,
|
|
67
|
+
l.relation,
|
|
68
|
+
l.created_at,
|
|
69
|
+
n.title
|
|
70
|
+
FROM c_note_link l
|
|
71
|
+
LEFT JOIN c_note n ON l.src_note_id = n.id
|
|
72
|
+
WHERE l.dst_note_id IN (${placeholders}) AND (l.is_del IS NULL OR l.is_del = 0) AND (n.is_del = 0 OR n.is_del IS NULL)
|
|
73
|
+
ORDER BY l.created_at DESC`, group);
|
|
74
|
+
for (const row of rows) {
|
|
75
|
+
const list = grouped.get(row.dst_note_id) ?? [];
|
|
76
|
+
list.push({
|
|
77
|
+
note_id: row.src_note_id,
|
|
78
|
+
title: row.title || '',
|
|
79
|
+
relation: row.relation,
|
|
80
|
+
link_created_at: row.created_at,
|
|
81
|
+
});
|
|
82
|
+
grouped.set(row.dst_note_id, list);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return grouped;
|
|
86
|
+
}
|
|
87
|
+
/** Outlinks for many notes at once, grouped by the source (src) note id. */
|
|
88
|
+
async function getOutlinksForNodes(db, ids) {
|
|
89
|
+
const grouped = new Map();
|
|
90
|
+
for (const group of chunkIds(ids, NOTE_LINK_QUERY_CHUNK_SIZE)) {
|
|
91
|
+
const placeholders = group.map(() => '?').join(', ');
|
|
92
|
+
const rows = await db.getAll(`SELECT
|
|
93
|
+
l.id,
|
|
94
|
+
l.src_note_id,
|
|
95
|
+
l.dst_note_id,
|
|
96
|
+
l.relation,
|
|
97
|
+
l.created_at,
|
|
98
|
+
n.title
|
|
99
|
+
FROM c_note_link l
|
|
100
|
+
LEFT JOIN c_note n ON l.dst_note_id = n.id
|
|
101
|
+
WHERE l.src_note_id IN (${placeholders}) AND (l.is_del IS NULL OR l.is_del = 0) AND (n.is_del = 0 OR n.is_del IS NULL)
|
|
102
|
+
ORDER BY l.created_at DESC`, group);
|
|
103
|
+
for (const row of rows) {
|
|
104
|
+
const list = grouped.get(row.src_note_id) ?? [];
|
|
105
|
+
list.push({
|
|
106
|
+
note_id: row.dst_note_id,
|
|
107
|
+
title: row.title || '',
|
|
108
|
+
relation: row.relation,
|
|
109
|
+
link_created_at: row.created_at,
|
|
110
|
+
});
|
|
111
|
+
grouped.set(row.src_note_id, list);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return grouped;
|
|
115
|
+
}
|
|
49
116
|
export async function getRelatedNotes(db, noteId, depth = 2) {
|
|
50
117
|
const visited = new Set([noteId]);
|
|
51
118
|
const related = new Map();
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
119
|
+
// Breadth-first by degree of separation: each level expands the whole frontier with
|
|
120
|
+
// two batched queries instead of two per node, so cost is O(depth) round-trips rather
|
|
121
|
+
// than O(visited nodes). BFS also attaches each note to the link on its shortest path.
|
|
122
|
+
let frontier = [noteId];
|
|
123
|
+
for (let level = 1; level <= depth && frontier.length > 0; level += 1) {
|
|
124
|
+
const [backlinksByDst, outlinksBySrc] = await Promise.all([
|
|
125
|
+
getBacklinksForNodes(db, frontier),
|
|
126
|
+
getOutlinksForNodes(db, frontier),
|
|
58
127
|
]);
|
|
59
|
-
|
|
60
|
-
|
|
128
|
+
const nextFrontier = [];
|
|
129
|
+
for (const nodeId of frontier) {
|
|
130
|
+
const neighbors = [
|
|
131
|
+
...(backlinksByDst.get(nodeId) ?? []),
|
|
132
|
+
...(outlinksBySrc.get(nodeId) ?? []),
|
|
133
|
+
];
|
|
134
|
+
for (const link of neighbors) {
|
|
135
|
+
if (visited.has(link.note_id)) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
61
138
|
visited.add(link.note_id);
|
|
62
139
|
related.set(link.note_id, link);
|
|
63
|
-
|
|
64
|
-
await traverse(link.note_id, currentDepth + 1);
|
|
65
|
-
}
|
|
140
|
+
nextFrontier.push(link.note_id);
|
|
66
141
|
}
|
|
67
142
|
}
|
|
143
|
+
frontier = nextFrontier;
|
|
68
144
|
}
|
|
69
|
-
await traverse(noteId, 1);
|
|
70
145
|
return {
|
|
71
146
|
note_id: noteId,
|
|
72
147
|
related: Array.from(related.values()),
|
|
@@ -86,7 +161,7 @@ export async function getGraphStats(db) {
|
|
|
86
161
|
FROM c_note_link l
|
|
87
162
|
JOIN c_note src ON src.id = l.src_note_id
|
|
88
163
|
JOIN c_note dst ON dst.id = l.dst_note_id
|
|
89
|
-
WHERE l.is_del = 0
|
|
164
|
+
WHERE (l.is_del IS NULL OR l.is_del = 0)
|
|
90
165
|
AND (src.is_del IS NULL OR src.is_del = 0)
|
|
91
166
|
AND (dst.is_del IS NULL OR dst.is_del = 0)
|
|
92
167
|
`, []),
|
|
@@ -99,7 +174,7 @@ export async function getGraphStats(db) {
|
|
|
99
174
|
FROM c_note_link l
|
|
100
175
|
JOIN c_note src ON src.id = l.src_note_id
|
|
101
176
|
JOIN c_note dst ON dst.id = l.dst_note_id
|
|
102
|
-
WHERE l.is_del = 0
|
|
177
|
+
WHERE (l.is_del IS NULL OR l.is_del = 0)
|
|
103
178
|
AND (src.is_del IS NULL OR src.is_del = 0)
|
|
104
179
|
AND (dst.is_del IS NULL OR dst.is_del = 0)
|
|
105
180
|
UNION
|
|
@@ -107,7 +182,7 @@ export async function getGraphStats(db) {
|
|
|
107
182
|
FROM c_note_link l
|
|
108
183
|
JOIN c_note src ON src.id = l.src_note_id
|
|
109
184
|
JOIN c_note dst ON dst.id = l.dst_note_id
|
|
110
|
-
WHERE l.is_del = 0
|
|
185
|
+
WHERE (l.is_del IS NULL OR l.is_del = 0)
|
|
111
186
|
AND (src.is_del IS NULL OR src.is_del = 0)
|
|
112
187
|
AND (dst.is_del IS NULL OR dst.is_del = 0)
|
|
113
188
|
) all_linked
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { registerCommandGroup } from '../descriptor/register.js';
|
|
2
|
-
import { OFFLINE_OPTIONS, REQUIRE_SYNC_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
|
|
2
|
+
import { FORMAT_OPTIONS, OFFLINE_OPTIONS, REQUIRE_SYNC_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
|
|
3
3
|
import { printSuccess } from '../../utils/output.js';
|
|
4
4
|
import { commandStale, syncBeforeCommand, withCommandRuntime } from '../runtime.js';
|
|
5
5
|
import { getMetaSchema, getMetaStats } from './repo.js';
|
|
@@ -26,7 +26,7 @@ export const metaCommandGroup = {
|
|
|
26
26
|
path: 'meta.stats',
|
|
27
27
|
risk: 'read',
|
|
28
28
|
auth: 'required',
|
|
29
|
-
globalOptions: [...OFFLINE_OPTIONS, ...REQUIRE_SYNC_OPTIONS, ...SYNC_TIMEOUT_OPTIONS],
|
|
29
|
+
globalOptions: [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...REQUIRE_SYNC_OPTIONS, ...SYNC_TIMEOUT_OPTIONS],
|
|
30
30
|
output: {
|
|
31
31
|
type: 'structured',
|
|
32
32
|
summary: 'Metadata stats payload',
|
|
@@ -43,11 +43,11 @@ export const metaCommandGroup = {
|
|
|
43
43
|
path: 'meta.schema',
|
|
44
44
|
risk: 'read',
|
|
45
45
|
auth: 'none',
|
|
46
|
-
globalOptions:
|
|
46
|
+
globalOptions: FORMAT_OPTIONS,
|
|
47
47
|
output: {
|
|
48
48
|
type: 'structured',
|
|
49
49
|
summary: 'Structured Dinox data schema payload',
|
|
50
|
-
fields: ['
|
|
50
|
+
fields: ['version', 'entities'],
|
|
51
51
|
},
|
|
52
52
|
examples: ['dino meta schema --format json'],
|
|
53
53
|
},
|
|
@@ -33,7 +33,7 @@ export function getMetaSchema() {
|
|
|
33
33
|
version: 1,
|
|
34
34
|
entities: {
|
|
35
35
|
note_search_result: {
|
|
36
|
-
fields: ['id', 'title', 'summary', 'tags', 'created_at', '
|
|
36
|
+
fields: ['id', 'title', 'summary', 'tags', 'created_at', 'boxes', 'is_starred'],
|
|
37
37
|
meta_fields: [
|
|
38
38
|
'query',
|
|
39
39
|
'returned_count',
|
|
@@ -55,13 +55,14 @@ export function getMetaSchema() {
|
|
|
55
55
|
'created_at',
|
|
56
56
|
'updated_at',
|
|
57
57
|
'is_del',
|
|
58
|
-
'
|
|
58
|
+
'is_starred',
|
|
59
|
+
'boxes',
|
|
59
60
|
'links.backlinks',
|
|
60
61
|
'links.outlinks',
|
|
61
62
|
],
|
|
62
63
|
},
|
|
63
64
|
graph_stats: {
|
|
64
|
-
fields: ['notes_count', 'links_count', 'connected_notes_count', 'isolated_notes_count'],
|
|
65
|
+
fields: ['notes_count', 'links_count', 'connected_notes_count', 'isolated_notes_count', 'stale'],
|
|
65
66
|
},
|
|
66
67
|
meta_stats: {
|
|
67
68
|
fields: ['notes_count', 'tags_count', 'boxes_count', 'latest_note_updated_at'],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CommandExecutionContext } from '../descriptor/types.js';
|
|
2
|
-
export declare function runNoteSearch({ command, options, args }: CommandExecutionContext): Promise<void>;
|
|
2
|
+
export declare function runNoteSearch({ command, options, args, globals }: CommandExecutionContext): Promise<void>;
|
|
3
3
|
export declare function runNoteGet({ command, options, args }: CommandExecutionContext): Promise<void>;
|
|
4
4
|
export declare function runNotePreview({ command, options, args }: CommandExecutionContext): Promise<void>;
|
|
5
5
|
export declare function runNoteDetail({ command, options, args }: CommandExecutionContext): Promise<void>;
|
|
@@ -5,14 +5,16 @@ import { generateId } from '../../utils/id.js';
|
|
|
5
5
|
import { convertMarkdownToNoteContent } from '../../utils/tiptapMarkdown.js';
|
|
6
6
|
import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
|
|
7
7
|
import { DinoxError } from '../../utils/errors.js';
|
|
8
|
-
import { commandStale, printStaleCacheWarning,
|
|
9
|
-
import { createNote, getNote, getNoteContext, getNoteDetails, getNotePreview, assertNotesUpdatable, resolveZettelBoxIdsForCreate,
|
|
8
|
+
import { commandStale, printStaleCacheWarning, resolveWriteDurabilityForRuntime, waitForMutationUpload, } from '../runtime.js';
|
|
9
|
+
import { createNote, executeNoteSearch, getNote, getNoteContext, getNoteDetails, getNotePreview, assertNotesUpdatable, resolveZettelBoxIdsForCreate, resolveZettelBoxIdsForUpdate, softDeleteNote, updateNote, validateTagsForCreate, validateTagsForUpdate, } from './repo.js';
|
|
10
|
+
import { routeRead } from '../../daemon/route.js';
|
|
10
11
|
import { buildNoteCreateDryRunPreview, buildNoteDeleteDryRunPreview, buildNoteUpdateDryRunPreview, } from './dryRun.js';
|
|
11
12
|
import { applyNoteMetadataMutation, planNoteMetadataMutation, } from './metadataMutation.js';
|
|
13
|
+
import { buildBatchWriteReceiptFields, readNoteRevisionReceipts, readNoteWriteReceipt, } from './writeReceipt.js';
|
|
12
14
|
import { parseNoteSearchFields, projectNoteSearchFields } from './searchOutput.js';
|
|
13
15
|
import { parseCreatedAtRange } from './searchTime.js';
|
|
14
16
|
import { buildNoteSearchResponse, normalizeDaysForMeta } from './searchResponse.js';
|
|
15
|
-
import { parseNoteType, parsePreviewLineCount, parseSearchLimit, parseSearchListInput, parseSearchOffset, parseStarredOption, parseStringInput, parseStringListInput, parseTargetNoteIds, syncBeforeNoteCommand, withNoteDatabase, } from './runtime.js';
|
|
17
|
+
import { parseMutationListInput, parseNoteType, parsePreviewLineCount, parseSearchLimit, parseSearchListInput, parseSearchOffset, parseStarredOption, parseStringInput, parseStringListInput, parseTargetNoteIds, syncBeforeNoteCommand, withNoteDatabase, } from './runtime.js';
|
|
16
18
|
function toPublicNoteContext(note) {
|
|
17
19
|
if (!note) {
|
|
18
20
|
return null;
|
|
@@ -45,6 +47,7 @@ async function executeNoteUpdateOperation(args) {
|
|
|
45
47
|
await withNoteDatabase(args.command, async (runtime) => {
|
|
46
48
|
const { db, config, offline } = runtime;
|
|
47
49
|
await syncBeforeNoteCommand(runtime);
|
|
50
|
+
const durability = resolveWriteDurabilityForRuntime(runtime, args.durability);
|
|
48
51
|
const ids = await parseTargetNoteIds(args.id, args.idsOption, 'update');
|
|
49
52
|
const hasTags = typeof args.tagsOption === 'string';
|
|
50
53
|
const hasBoxes = typeof args.boxOption === 'string';
|
|
@@ -52,11 +55,20 @@ async function executeNoteUpdateOperation(args) {
|
|
|
52
55
|
if (!hasTags && !hasBoxes && !hasStarred) {
|
|
53
56
|
throw new DinoxError('At least one option is required: --tags, --boxes, or --starred');
|
|
54
57
|
}
|
|
55
|
-
const
|
|
56
|
-
? await
|
|
58
|
+
const requestedTags = hasTags
|
|
59
|
+
? await parseMutationListInput(args.tagsOption, '--tags')
|
|
57
60
|
: undefined;
|
|
58
|
-
const
|
|
59
|
-
? await
|
|
61
|
+
const requestedBoxNames = hasBoxes
|
|
62
|
+
? await parseMutationListInput(args.boxOption, '--boxes')
|
|
63
|
+
: undefined;
|
|
64
|
+
const tagUserId = requestedTags
|
|
65
|
+
? (await resolveAuthIdentity(config)).userId
|
|
66
|
+
: undefined;
|
|
67
|
+
const tags = requestedTags
|
|
68
|
+
? await validateTagsForUpdate(db, requestedTags, tagUserId)
|
|
69
|
+
: undefined;
|
|
70
|
+
const zettelBoxIds = requestedBoxNames
|
|
71
|
+
? await resolveZettelBoxIdsForUpdate(db, requestedBoxNames)
|
|
60
72
|
: undefined;
|
|
61
73
|
await assertNotesUpdatable(db, ids);
|
|
62
74
|
if (args.dryRun) {
|
|
@@ -64,9 +76,10 @@ async function executeNoteUpdateOperation(args) {
|
|
|
64
76
|
db,
|
|
65
77
|
config,
|
|
66
78
|
offline,
|
|
79
|
+
userId: tagUserId,
|
|
67
80
|
ids,
|
|
68
81
|
tagsInput: tags,
|
|
69
|
-
zettelBoxNames:
|
|
82
|
+
zettelBoxNames: requestedBoxNames,
|
|
70
83
|
starred: hasStarred ? args.starred : undefined,
|
|
71
84
|
commandName: args.commandName ?? 'dino note update',
|
|
72
85
|
action: args.actionName ?? 'update_note',
|
|
@@ -84,20 +97,22 @@ async function executeNoteUpdateOperation(args) {
|
|
|
84
97
|
}));
|
|
85
98
|
}
|
|
86
99
|
const updatedCount = results.filter((item) => item.updated).length;
|
|
87
|
-
|
|
88
|
-
|
|
100
|
+
const upload = await waitForMutationUpload(runtime, { durability });
|
|
101
|
+
const stale = commandStale(runtime);
|
|
102
|
+
const revisions = await readNoteRevisionReceipts(db, results.map((item) => item.id), new Map(results.map((item) => [item.id, item.changed])));
|
|
89
103
|
const payload = {
|
|
90
104
|
ok: true,
|
|
91
105
|
total: results.length,
|
|
92
106
|
updatedCount,
|
|
107
|
+
...buildBatchWriteReceiptFields(upload, stale),
|
|
93
108
|
results: results.map((item) => ({
|
|
94
109
|
id: item.id,
|
|
95
110
|
updated: item.updated,
|
|
96
111
|
tags: item.tags,
|
|
97
112
|
boxes: item.zettelBoxIds,
|
|
98
113
|
is_starred: item.is_starred,
|
|
114
|
+
...revisions.get(item.id),
|
|
99
115
|
})),
|
|
100
|
-
stale: commandStale(runtime),
|
|
101
116
|
};
|
|
102
117
|
if (structuredOutput) {
|
|
103
118
|
printSuccess(payload);
|
|
@@ -144,11 +159,15 @@ async function executeNoteMetadataListOperation(args) {
|
|
|
144
159
|
await withNoteDatabase(args.command, async (runtime) => {
|
|
145
160
|
const { db, config, offline } = runtime;
|
|
146
161
|
await syncBeforeNoteCommand(runtime);
|
|
162
|
+
const durability = resolveWriteDurabilityForRuntime(runtime, args.options.durability);
|
|
147
163
|
const ids = await parseTargetNoteIds(args.id, args.idsOption, 'update');
|
|
148
164
|
const selected = selectMetadataOperation(args.options);
|
|
149
|
-
const requestedValues = await
|
|
165
|
+
const requestedValues = await parseMutationListInput(selected.rawValues, selected.optionName);
|
|
166
|
+
const tagUserId = args.field === 'tags'
|
|
167
|
+
? (await resolveAuthIdentity(config)).userId
|
|
168
|
+
: undefined;
|
|
150
169
|
const values = args.field === 'tags'
|
|
151
|
-
? await validateTagsForUpdate(db, requestedValues)
|
|
170
|
+
? await validateTagsForUpdate(db, requestedValues, tagUserId)
|
|
152
171
|
: await resolveZettelBoxIdsForUpdate(db, requestedValues);
|
|
153
172
|
const plan = await planNoteMetadataMutation(db, {
|
|
154
173
|
ids,
|
|
@@ -166,9 +185,6 @@ async function executeNoteMetadataListOperation(args) {
|
|
|
166
185
|
summary: `Preview ${args.field} ${selected.operation} for ${plan.total} note(s); ${plan.changedCount} note(s) would change.`,
|
|
167
186
|
warnings: [
|
|
168
187
|
...(offline ? ['Execution would run in offline mode and only use the local cache.'] : []),
|
|
169
|
-
...(!offline && !config.powersync.uploadBaseUrl
|
|
170
|
-
? ['Execution would be local-only because powersync.uploadBaseUrl is unset.']
|
|
171
|
-
: []),
|
|
172
188
|
],
|
|
173
189
|
inputs: {
|
|
174
190
|
ids,
|
|
@@ -187,20 +203,22 @@ async function executeNoteMetadataListOperation(args) {
|
|
|
187
203
|
return;
|
|
188
204
|
}
|
|
189
205
|
await applyNoteMetadataMutation(db, plan);
|
|
190
|
-
|
|
191
|
-
|
|
206
|
+
const upload = await waitForMutationUpload(runtime, { durability });
|
|
207
|
+
const stale = commandStale(runtime);
|
|
208
|
+
const revisions = await readNoteRevisionReceipts(db, plan.results.map((item) => item.id), new Map(plan.results.map((item) => [item.id, item.changed ? [args.field] : []])));
|
|
192
209
|
const payload = {
|
|
193
210
|
ok: true,
|
|
194
211
|
field: args.field,
|
|
195
212
|
operation: selected.operation,
|
|
196
213
|
total: plan.total,
|
|
197
214
|
updatedCount: plan.changedCount,
|
|
215
|
+
...buildBatchWriteReceiptFields(upload, stale),
|
|
198
216
|
results: plan.results.map((item) => ({
|
|
199
217
|
id: item.id,
|
|
200
218
|
updated: item.changed,
|
|
201
219
|
[args.field]: item.after,
|
|
220
|
+
...revisions.get(item.id),
|
|
202
221
|
})),
|
|
203
|
-
stale: commandStale(runtime),
|
|
204
222
|
};
|
|
205
223
|
if (structuredOutput) {
|
|
206
224
|
printSuccess(payload);
|
|
@@ -210,60 +228,66 @@ async function executeNoteMetadataListOperation(args) {
|
|
|
210
228
|
printStaleCacheWarning(runtime);
|
|
211
229
|
});
|
|
212
230
|
}
|
|
213
|
-
export async function runNoteSearch({ command, options, args }) {
|
|
231
|
+
export async function runNoteSearch({ command, options, args, globals }) {
|
|
214
232
|
const searchOptions = options;
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
days: searchOptions.days,
|
|
224
|
-
});
|
|
225
|
-
const starred = typeof searchOptions.starred === 'string'
|
|
226
|
-
? parseStarredOption(searchOptions.starred, '--starred')
|
|
227
|
-
: undefined;
|
|
228
|
-
const boxInput = typeof searchOptions.boxes === 'string' ? searchOptions.boxes : '';
|
|
229
|
-
const parsedBoxInput = boxInput
|
|
230
|
-
? await parseSearchListInput(boxInput, '--boxes')
|
|
231
|
-
: { values: [], isExplicitEmptyArray: false };
|
|
232
|
-
const zettelBoxIds = await resolveZettelBoxIdsForSearch(db, parsedBoxInput.values);
|
|
233
|
-
const sqlExpression = typeof searchOptions.sql === 'string' ? searchOptions.sql.trim() : '';
|
|
234
|
-
const limit = parseSearchLimit(searchOptions.limit);
|
|
235
|
-
const offset = parseSearchOffset(searchOptions.offset);
|
|
236
|
-
const fields = parseNoteSearchFields(searchOptions.fields, '--fields');
|
|
237
|
-
const rows = await searchNotes(db, keyword, {
|
|
238
|
-
includeDeleted: Boolean(searchOptions.includeDeleted),
|
|
239
|
-
tagsExpression: tagsExpression || undefined,
|
|
240
|
-
createdAtFrom,
|
|
241
|
-
createdAtTo,
|
|
242
|
-
starred,
|
|
243
|
-
zettelBoxIds,
|
|
244
|
-
matchEmptyZettelBoxes: parsedBoxInput.isExplicitEmptyArray,
|
|
245
|
-
sqlExpression: sqlExpression || undefined,
|
|
246
|
-
});
|
|
247
|
-
printSuccess(buildNoteSearchResponse({
|
|
248
|
-
query: keyword,
|
|
249
|
-
notes: projectNoteSearchFields(rows.slice(offset, offset + limit), fields),
|
|
250
|
-
totalCount: rows.length,
|
|
251
|
-
stale: commandStale(runtime),
|
|
252
|
-
includeDeleted: Boolean(searchOptions.includeDeleted),
|
|
253
|
-
limit,
|
|
254
|
-
offset,
|
|
255
|
-
fields,
|
|
256
|
-
tagsExpression: tagsExpression || undefined,
|
|
257
|
-
createdAtFrom,
|
|
258
|
-
createdAtTo,
|
|
259
|
-
days: normalizeDaysForMeta(searchOptions.days),
|
|
260
|
-
starred,
|
|
261
|
-
boxNames: parsedBoxInput.values,
|
|
262
|
-
boxIds: zettelBoxIds,
|
|
263
|
-
matchEmptyBoxes: parsedBoxInput.isExplicitEmptyArray,
|
|
264
|
-
sqlExpression: sqlExpression || undefined,
|
|
265
|
-
}));
|
|
233
|
+
// Option parsing and output formatting stay here, in one place; only the db-touching
|
|
234
|
+
// step (box resolution + search) runs through executeNoteSearch — locally or on the daemon.
|
|
235
|
+
const keyword = args.query?.trim() ?? '';
|
|
236
|
+
const tagsExpression = typeof searchOptions.tags === 'string' ? searchOptions.tags.trim() : '';
|
|
237
|
+
const { createdAtFrom, createdAtTo } = parseCreatedAtRange({
|
|
238
|
+
from: searchOptions.from,
|
|
239
|
+
to: searchOptions.to,
|
|
240
|
+
days: searchOptions.days,
|
|
266
241
|
});
|
|
242
|
+
const starred = typeof searchOptions.starred === 'string'
|
|
243
|
+
? parseStarredOption(searchOptions.starred, '--starred')
|
|
244
|
+
: undefined;
|
|
245
|
+
const boxInput = typeof searchOptions.boxes === 'string' ? searchOptions.boxes : '';
|
|
246
|
+
const parsedBoxInput = boxInput
|
|
247
|
+
? await parseSearchListInput(boxInput, '--boxes')
|
|
248
|
+
: { values: [], isExplicitEmptyArray: false };
|
|
249
|
+
const sqlExpression = typeof searchOptions.sql === 'string' ? searchOptions.sql.trim() : '';
|
|
250
|
+
const limit = parseSearchLimit(searchOptions.limit);
|
|
251
|
+
const offset = parseSearchOffset(searchOptions.offset);
|
|
252
|
+
const fields = parseNoteSearchFields(searchOptions.fields, '--fields');
|
|
253
|
+
const execParams = {
|
|
254
|
+
keyword,
|
|
255
|
+
includeDeleted: Boolean(searchOptions.includeDeleted),
|
|
256
|
+
tagsExpression: tagsExpression || undefined,
|
|
257
|
+
createdAtFrom,
|
|
258
|
+
createdAtTo,
|
|
259
|
+
starred,
|
|
260
|
+
boxValues: parsedBoxInput.values,
|
|
261
|
+
matchEmptyZettelBoxes: parsedBoxInput.isExplicitEmptyArray,
|
|
262
|
+
sqlExpression: sqlExpression || undefined,
|
|
263
|
+
limit,
|
|
264
|
+
offset,
|
|
265
|
+
};
|
|
266
|
+
const result = await routeRead(globals, 'note.search', execParams, () => withNoteDatabase(command, async (runtime) => {
|
|
267
|
+
await syncBeforeNoteCommand(runtime);
|
|
268
|
+
const local = await executeNoteSearch(runtime.db, execParams);
|
|
269
|
+
return { ...local, stale: commandStale(runtime) };
|
|
270
|
+
}));
|
|
271
|
+
const stale = result.stale ?? false;
|
|
272
|
+
printSuccess(buildNoteSearchResponse({
|
|
273
|
+
query: keyword,
|
|
274
|
+
notes: projectNoteSearchFields(result.rows, fields),
|
|
275
|
+
totalCount: result.totalCount,
|
|
276
|
+
stale,
|
|
277
|
+
includeDeleted: Boolean(searchOptions.includeDeleted),
|
|
278
|
+
limit,
|
|
279
|
+
offset,
|
|
280
|
+
fields,
|
|
281
|
+
tagsExpression: tagsExpression || undefined,
|
|
282
|
+
createdAtFrom,
|
|
283
|
+
createdAtTo,
|
|
284
|
+
days: normalizeDaysForMeta(searchOptions.days),
|
|
285
|
+
starred,
|
|
286
|
+
boxNames: parsedBoxInput.values,
|
|
287
|
+
boxIds: result.zettelBoxIds,
|
|
288
|
+
matchEmptyBoxes: parsedBoxInput.isExplicitEmptyArray,
|
|
289
|
+
sqlExpression: sqlExpression || undefined,
|
|
290
|
+
}));
|
|
267
291
|
}
|
|
268
292
|
export async function runNoteGet({ command, options, args }) {
|
|
269
293
|
const getOptions = options;
|
|
@@ -355,6 +379,7 @@ export async function runNoteCreate({ command, options }) {
|
|
|
355
379
|
await withNoteDatabase(command, async (runtime) => {
|
|
356
380
|
const { db, config, offline } = runtime;
|
|
357
381
|
await syncBeforeNoteCommand(runtime);
|
|
382
|
+
const durability = resolveWriteDurabilityForRuntime(runtime, createOptions.durability);
|
|
358
383
|
const identity = await resolveAuthIdentity(config);
|
|
359
384
|
const contentMd = await parseStringInput(String(createOptions.content));
|
|
360
385
|
const canonicalContentMd = await canonicalizeBidlinksInMarkdown(db, contentMd, identity.userId);
|
|
@@ -388,13 +413,20 @@ export async function runNoteCreate({ command, options }) {
|
|
|
388
413
|
contentMd: canonicalContentMd,
|
|
389
414
|
contentJson,
|
|
390
415
|
contentText,
|
|
391
|
-
tags: await validateTagsForCreate(db, tagsInput),
|
|
416
|
+
tags: await validateTagsForCreate(db, tagsInput, identity.userId),
|
|
392
417
|
zettelBoxIds: await resolveZettelBoxIdsForCreate(db, boxNames),
|
|
393
418
|
userId: identity.userId,
|
|
394
419
|
});
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
420
|
+
const upload = await waitForMutationUpload(runtime, { durability });
|
|
421
|
+
const payload = {
|
|
422
|
+
ok: true,
|
|
423
|
+
id,
|
|
424
|
+
...(await readNoteWriteReceipt(db, id, {
|
|
425
|
+
changed: ['title', 'type', 'content_md', 'content_json', 'content_text', 'tags', 'boxes'],
|
|
426
|
+
upload,
|
|
427
|
+
stale: commandStale(runtime),
|
|
428
|
+
})),
|
|
429
|
+
};
|
|
398
430
|
if (structuredOutput) {
|
|
399
431
|
printSuccess(payload);
|
|
400
432
|
return;
|
|
@@ -413,14 +445,22 @@ export async function runNoteDelete({ command, options, args }) {
|
|
|
413
445
|
await withNoteDatabase(command, async (runtime) => {
|
|
414
446
|
const { db, config, offline } = runtime;
|
|
415
447
|
await syncBeforeNoteCommand(runtime);
|
|
448
|
+
const durability = resolveWriteDurabilityForRuntime(runtime, deleteOptions.durability);
|
|
416
449
|
if (Boolean(deleteOptions.dryRun)) {
|
|
417
450
|
printDryRunPreview(await buildNoteDeleteDryRunPreview({ db, config, offline, id }));
|
|
418
451
|
return;
|
|
419
452
|
}
|
|
420
453
|
await softDeleteNote(db, id);
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
454
|
+
const upload = await waitForMutationUpload(runtime, { durability });
|
|
455
|
+
const payload = {
|
|
456
|
+
ok: true,
|
|
457
|
+
id,
|
|
458
|
+
...(await readNoteWriteReceipt(db, id, {
|
|
459
|
+
changed: ['is_del'],
|
|
460
|
+
upload,
|
|
461
|
+
stale: commandStale(runtime),
|
|
462
|
+
})),
|
|
463
|
+
};
|
|
424
464
|
if (structuredOutput) {
|
|
425
465
|
printSuccess(payload);
|
|
426
466
|
return;
|
|
@@ -443,6 +483,7 @@ export async function runNoteUpdate({ command, options, args }) {
|
|
|
443
483
|
tagsOption: updateOptions.tags,
|
|
444
484
|
commandName: 'dino note update',
|
|
445
485
|
actionName: 'update_note',
|
|
486
|
+
durability: updateOptions.durability,
|
|
446
487
|
});
|
|
447
488
|
}
|
|
448
489
|
export async function runNoteTag({ command, options, args }) {
|
|
@@ -479,6 +520,7 @@ export async function runNoteStar({ command, options, args }) {
|
|
|
479
520
|
staleVerb: 'starred',
|
|
480
521
|
commandName: 'dino note star',
|
|
481
522
|
actionName: 'star_note',
|
|
523
|
+
durability: starOptions.durability,
|
|
482
524
|
});
|
|
483
525
|
}
|
|
484
526
|
export async function runNoteUnstar({ command, options, args }) {
|
|
@@ -493,5 +535,6 @@ export async function runNoteUnstar({ command, options, args }) {
|
|
|
493
535
|
staleVerb: 'unstarred',
|
|
494
536
|
commandName: 'dino note unstar',
|
|
495
537
|
actionName: 'unstar_note',
|
|
538
|
+
durability: unstarOptions.durability,
|
|
496
539
|
});
|
|
497
540
|
}
|