@noteplanco/noteplan-mcp 1.1.21 → 1.1.24

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.
Files changed (177) hide show
  1. package/README.md +7 -0
  2. package/dist/index.js +6 -0
  3. package/dist/index.js.map +1 -1
  4. package/dist/noteplan/attachments-paths.d.ts +13 -0
  5. package/dist/noteplan/attachments-paths.d.ts.map +1 -0
  6. package/dist/noteplan/attachments-paths.js +27 -0
  7. package/dist/noteplan/attachments-paths.js.map +1 -0
  8. package/dist/noteplan/embeddings.js +1 -1
  9. package/dist/noteplan/embeddings.js.map +1 -1
  10. package/dist/noteplan/file-reader.d.ts +37 -46
  11. package/dist/noteplan/file-reader.d.ts.map +1 -1
  12. package/dist/noteplan/file-reader.js +200 -202
  13. package/dist/noteplan/file-reader.js.map +1 -1
  14. package/dist/noteplan/file-reader.test.d.ts +2 -0
  15. package/dist/noteplan/file-reader.test.d.ts.map +1 -0
  16. package/dist/noteplan/file-reader.test.js +67 -0
  17. package/dist/noteplan/file-reader.test.js.map +1 -0
  18. package/dist/noteplan/file-writer.d.ts +35 -31
  19. package/dist/noteplan/file-writer.d.ts.map +1 -1
  20. package/dist/noteplan/file-writer.js +280 -164
  21. package/dist/noteplan/file-writer.js.map +1 -1
  22. package/dist/noteplan/file-writer.test.js +704 -191
  23. package/dist/noteplan/file-writer.test.js.map +1 -1
  24. package/dist/noteplan/filter-store.d.ts +5 -5
  25. package/dist/noteplan/filter-store.d.ts.map +1 -1
  26. package/dist/noteplan/filter-store.js +94 -79
  27. package/dist/noteplan/filter-store.js.map +1 -1
  28. package/dist/noteplan/frontmatter-parser.d.ts.map +1 -1
  29. package/dist/noteplan/frontmatter-parser.js +5 -4
  30. package/dist/noteplan/frontmatter-parser.js.map +1 -1
  31. package/dist/noteplan/frontmatter-parser.test.js +44 -0
  32. package/dist/noteplan/frontmatter-parser.test.js.map +1 -1
  33. package/dist/noteplan/markdown-parser.js +1 -1
  34. package/dist/noteplan/markdown-parser.js.map +1 -1
  35. package/dist/noteplan/markdown-parser.test.js +194 -0
  36. package/dist/noteplan/markdown-parser.test.js.map +1 -1
  37. package/dist/noteplan/preferences.d.ts +1 -0
  38. package/dist/noteplan/preferences.d.ts.map +1 -1
  39. package/dist/noteplan/preferences.js +1 -0
  40. package/dist/noteplan/preferences.js.map +1 -1
  41. package/dist/noteplan/ripgrep-search.d.ts +25 -2
  42. package/dist/noteplan/ripgrep-search.d.ts.map +1 -1
  43. package/dist/noteplan/ripgrep-search.js +75 -2
  44. package/dist/noteplan/ripgrep-search.js.map +1 -1
  45. package/dist/noteplan/space-row-utils.d.ts +20 -0
  46. package/dist/noteplan/space-row-utils.d.ts.map +1 -0
  47. package/dist/noteplan/space-row-utils.js +78 -0
  48. package/dist/noteplan/space-row-utils.js.map +1 -0
  49. package/dist/noteplan/space-row-utils.test.d.ts +2 -0
  50. package/dist/noteplan/space-row-utils.test.d.ts.map +1 -0
  51. package/dist/noteplan/space-row-utils.test.js +123 -0
  52. package/dist/noteplan/space-row-utils.test.js.map +1 -0
  53. package/dist/noteplan/sqlite-reader.d.ts +12 -27
  54. package/dist/noteplan/sqlite-reader.d.ts.map +1 -1
  55. package/dist/noteplan/sqlite-reader.js +325 -223
  56. package/dist/noteplan/sqlite-reader.js.map +1 -1
  57. package/dist/noteplan/sqlite-writer.d.ts +1 -1
  58. package/dist/noteplan/sqlite-writer.d.ts.map +1 -1
  59. package/dist/noteplan/sqlite-writer.js +2 -2
  60. package/dist/noteplan/sqlite-writer.js.map +1 -1
  61. package/dist/noteplan/unified-store.d.ts +41 -30
  62. package/dist/noteplan/unified-store.d.ts.map +1 -1
  63. package/dist/noteplan/unified-store.js +307 -161
  64. package/dist/noteplan/unified-store.js.map +1 -1
  65. package/dist/server.d.ts.map +1 -1
  66. package/dist/server.js +143 -62
  67. package/dist/server.js.map +1 -1
  68. package/dist/tools/attachments.d.ts +9 -9
  69. package/dist/tools/attachments.d.ts.map +1 -1
  70. package/dist/tools/attachments.js +74 -83
  71. package/dist/tools/attachments.js.map +1 -1
  72. package/dist/tools/attachments.test.js +170 -129
  73. package/dist/tools/attachments.test.js.map +1 -1
  74. package/dist/tools/calendar.d.ts +36 -13
  75. package/dist/tools/calendar.d.ts.map +1 -1
  76. package/dist/tools/calendar.js +44 -17
  77. package/dist/tools/calendar.js.map +1 -1
  78. package/dist/tools/embeddings.d.ts +6 -6
  79. package/dist/tools/embeddings.d.ts.map +1 -1
  80. package/dist/tools/embeddings.js +6 -6
  81. package/dist/tools/embeddings.js.map +1 -1
  82. package/dist/tools/events.d.ts +7 -3
  83. package/dist/tools/events.d.ts.map +1 -1
  84. package/dist/tools/events.js +51 -16
  85. package/dist/tools/events.js.map +1 -1
  86. package/dist/tools/filters.d.ts +28 -33
  87. package/dist/tools/filters.d.ts.map +1 -1
  88. package/dist/tools/filters.js +42 -105
  89. package/dist/tools/filters.js.map +1 -1
  90. package/dist/tools/notes.d.ts +80 -218
  91. package/dist/tools/notes.d.ts.map +1 -1
  92. package/dist/tools/notes.js +194 -180
  93. package/dist/tools/notes.js.map +1 -1
  94. package/dist/tools/notes.test.js +501 -21
  95. package/dist/tools/notes.test.js.map +1 -1
  96. package/dist/tools/search.d.ts +4 -3
  97. package/dist/tools/search.d.ts.map +1 -1
  98. package/dist/tools/search.js +9 -5
  99. package/dist/tools/search.js.map +1 -1
  100. package/dist/tools/search.test.d.ts +2 -0
  101. package/dist/tools/search.test.d.ts.map +1 -0
  102. package/dist/tools/search.test.js +37 -0
  103. package/dist/tools/search.test.js.map +1 -0
  104. package/dist/tools/spaces.d.ts +20 -20
  105. package/dist/tools/spaces.d.ts.map +1 -1
  106. package/dist/tools/spaces.js +28 -28
  107. package/dist/tools/spaces.js.map +1 -1
  108. package/dist/tools/tasks.d.ts +22 -22
  109. package/dist/tools/tasks.d.ts.map +1 -1
  110. package/dist/tools/tasks.js +22 -22
  111. package/dist/tools/tasks.js.map +1 -1
  112. package/dist/tools/templates.d.ts +7 -7
  113. package/dist/tools/templates.d.ts.map +1 -1
  114. package/dist/tools/templates.js +4 -4
  115. package/dist/tools/templates.js.map +1 -1
  116. package/dist/tools/themes.js +1 -1
  117. package/dist/tools/themes.js.map +1 -1
  118. package/dist/transport/bridge-availability.d.ts +5 -0
  119. package/dist/transport/bridge-availability.d.ts.map +1 -0
  120. package/dist/transport/bridge-availability.js +92 -0
  121. package/dist/transport/bridge-availability.js.map +1 -0
  122. package/dist/transport/bridge-cascade.d.ts +18 -0
  123. package/dist/transport/bridge-cascade.d.ts.map +1 -0
  124. package/dist/transport/bridge-cascade.js +78 -0
  125. package/dist/transport/bridge-cascade.js.map +1 -0
  126. package/dist/transport/bridge-cascade.test.d.ts +2 -0
  127. package/dist/transport/bridge-cascade.test.d.ts.map +1 -0
  128. package/dist/transport/bridge-cascade.test.js +160 -0
  129. package/dist/transport/bridge-cascade.test.js.map +1 -0
  130. package/dist/transport/bridge-client.d.ts +197 -0
  131. package/dist/transport/bridge-client.d.ts.map +1 -0
  132. package/dist/transport/bridge-client.js +288 -0
  133. package/dist/transport/bridge-client.js.map +1 -0
  134. package/dist/transport/bridge-client.test.d.ts +2 -0
  135. package/dist/transport/bridge-client.test.d.ts.map +1 -0
  136. package/dist/transport/bridge-client.test.js +384 -0
  137. package/dist/transport/bridge-client.test.js.map +1 -0
  138. package/dist/transport/bridge-context.d.ts +10 -0
  139. package/dist/transport/bridge-context.d.ts.map +1 -0
  140. package/dist/transport/bridge-context.js +18 -0
  141. package/dist/transport/bridge-context.js.map +1 -0
  142. package/dist/transport/bridge-fs.d.ts +25 -0
  143. package/dist/transport/bridge-fs.d.ts.map +1 -0
  144. package/dist/transport/bridge-fs.js +129 -0
  145. package/dist/transport/bridge-fs.js.map +1 -0
  146. package/dist/utils/date-utils.d.ts +24 -0
  147. package/dist/utils/date-utils.d.ts.map +1 -1
  148. package/dist/utils/date-utils.js +55 -0
  149. package/dist/utils/date-utils.js.map +1 -1
  150. package/dist/utils/date-utils.test.d.ts +2 -0
  151. package/dist/utils/date-utils.test.d.ts.map +1 -0
  152. package/dist/utils/date-utils.test.js +109 -0
  153. package/dist/utils/date-utils.test.js.map +1 -0
  154. package/dist/utils/folder-access.d.ts +23 -0
  155. package/dist/utils/folder-access.d.ts.map +1 -0
  156. package/dist/utils/folder-access.js +131 -0
  157. package/dist/utils/folder-access.js.map +1 -0
  158. package/dist/utils/folder-access.test.d.ts +2 -0
  159. package/dist/utils/folder-access.test.d.ts.map +1 -0
  160. package/dist/utils/folder-access.test.js +182 -0
  161. package/dist/utils/folder-access.test.js.map +1 -0
  162. package/dist/utils/folder-matcher.d.ts.map +1 -1
  163. package/dist/utils/folder-matcher.js +16 -0
  164. package/dist/utils/folder-matcher.js.map +1 -1
  165. package/dist/utils/folder-matcher.test.js +42 -0
  166. package/dist/utils/folder-matcher.test.js.map +1 -1
  167. package/dist/utils/server-config.d.ts +10 -2
  168. package/dist/utils/server-config.d.ts.map +1 -1
  169. package/dist/utils/server-config.js +16 -2
  170. package/dist/utils/server-config.js.map +1 -1
  171. package/dist/utils/version.d.ts +2 -0
  172. package/dist/utils/version.d.ts.map +1 -1
  173. package/dist/utils/version.js +5 -1
  174. package/dist/utils/version.js.map +1 -1
  175. package/package.json +4 -3
  176. package/scripts/calendar-helper +0 -0
  177. package/scripts/reminders-helper +0 -0
@@ -5,6 +5,8 @@ import * as fs from 'fs';
5
5
  import { SQLITE_NOTE_TYPES } from './types.js';
6
6
  import { extractTitle, extractTagsFromContent } from './markdown-parser.js';
7
7
  import { isSqliteAvailable, SqliteDatabase } from './sqlite-loader.js';
8
+ import { bridgeOrFallback } from '../transport/bridge-cascade.js';
9
+ import { bridgeRowToNote, filterBridgeRowsByTrash, findRootSpaceIdFromRows, isTrashFolderRow, } from './space-row-utils.js';
8
10
  // Possible NotePlan storage paths (same as file-reader.ts)
9
11
  const POSSIBLE_PATHS = [
10
12
  // Direct local paths (AppStore version) - preferred for local dev
@@ -90,36 +92,51 @@ export function closeDatabase() {
90
92
  db = null;
91
93
  }
92
94
  }
93
- /**
94
- * List all spaces
95
- */
96
- export function listSpaces() {
97
- const database = getDatabase();
98
- if (!database)
99
- return [];
100
- try {
101
- // Spaces are stored as note_type = 10 with is_dir = 1
102
- const rows = database
103
- .prepare(`
104
- SELECT
105
- id,
106
- title,
107
- (SELECT COUNT(*) FROM notes n2 WHERE n2.parent = notes.id AND n2.note_type IN (?, ?)) as note_count
108
- FROM notes
109
- WHERE note_type = ?
110
- AND is_dir = 1
111
- `)
112
- .all(SQLITE_NOTE_TYPES.TEAMSPACE_NOTE, SQLITE_NOTE_TYPES.TEAMSPACE_CALENDAR, SQLITE_NOTE_TYPES.TEAMSPACE);
113
- return rows.map((row) => ({
95
+ export async function listSpaces() {
96
+ return bridgeOrFallback(async (bridge) => {
97
+ // Independent fetches — run in parallel.
98
+ const [teamspaceRows, allRows] = await Promise.all([
99
+ bridge.listTeamspaces(),
100
+ bridge.listSpaceNoteRows(),
101
+ ]);
102
+ const noteCountByParent = new Map();
103
+ for (const row of allRows) {
104
+ if (row.is_dir === 0 && row.parent) {
105
+ noteCountByParent.set(row.parent, (noteCountByParent.get(row.parent) ?? 0) + 1);
106
+ }
107
+ }
108
+ return teamspaceRows.map((row) => ({
114
109
  id: row.id,
115
110
  name: row.title || row.id,
116
- noteCount: row.note_count,
111
+ noteCount: noteCountByParent.get(row.id) ?? 0,
117
112
  }));
118
- }
119
- catch (error) {
120
- console.error('Error listing spaces:', error);
121
- return [];
122
- }
113
+ }, () => {
114
+ const database = getDatabase();
115
+ if (!database)
116
+ return [];
117
+ try {
118
+ const rows = database
119
+ .prepare(`
120
+ SELECT
121
+ id,
122
+ title,
123
+ (SELECT COUNT(*) FROM notes n2 WHERE n2.parent = notes.id AND n2.note_type IN (?, ?)) as note_count
124
+ FROM notes
125
+ WHERE note_type = ?
126
+ AND is_dir = 1
127
+ `)
128
+ .all(SQLITE_NOTE_TYPES.TEAMSPACE_NOTE, SQLITE_NOTE_TYPES.TEAMSPACE_CALENDAR, SQLITE_NOTE_TYPES.TEAMSPACE);
129
+ return rows.map((row) => ({
130
+ id: row.id,
131
+ name: row.title || row.id,
132
+ noteCount: row.note_count,
133
+ }));
134
+ }
135
+ catch (error) {
136
+ console.error('Error listing spaces:', error);
137
+ return [];
138
+ }
139
+ });
123
140
  }
124
141
  /**
125
142
  * Get all descendant IDs of a space (folders and notes)
@@ -145,10 +162,41 @@ function getSpaceDescendantIds(database, spaceId) {
145
162
  return [];
146
163
  }
147
164
  }
148
- /**
149
- * Count notes and subfolders under a space folder (recursive)
150
- */
151
- export function countSpaceFolderContents(folderId) {
165
+ export async function countSpaceFolderContents(folderId) {
166
+ return bridgeOrFallback(async (bridge) => {
167
+ const allRows = await bridge.listSpaceNoteRows();
168
+ const childrenByParent = new Map();
169
+ for (const r of allRows) {
170
+ if (!r.parent)
171
+ continue;
172
+ const arr = childrenByParent.get(r.parent) ?? [];
173
+ arr.push(r);
174
+ childrenByParent.set(r.parent, arr);
175
+ }
176
+ let noteCount = 0;
177
+ let folderCount = 0;
178
+ const queue = [folderId];
179
+ const seen = new Set();
180
+ while (queue.length > 0) {
181
+ const id = queue.shift();
182
+ const children = childrenByParent.get(id) ?? [];
183
+ for (const child of children) {
184
+ if (seen.has(child.id))
185
+ continue;
186
+ seen.add(child.id);
187
+ if (child.is_dir === 1) {
188
+ folderCount += 1;
189
+ queue.push(child.id);
190
+ }
191
+ else {
192
+ noteCount += 1;
193
+ }
194
+ }
195
+ }
196
+ return { noteCount, folderCount };
197
+ }, () => sqlCountSpaceFolderContents(folderId));
198
+ }
199
+ function sqlCountSpaceFolderContents(folderId) {
152
200
  const database = getDatabase();
153
201
  if (!database)
154
202
  return { noteCount: 0, folderCount: 0 };
@@ -263,7 +311,7 @@ function rowToNote(row, database) {
263
311
  const isCalendar = row.note_type === SQLITE_NOTE_TYPES.TEAMSPACE_CALENDAR;
264
312
  return {
265
313
  id: row.id,
266
- title: row.title || extractTitle(row.content || ''),
314
+ title: row.content ? extractTitle(row.content) : row.title || 'Untitled',
267
315
  filename: row.filename,
268
316
  content: row.content || '',
269
317
  type: isCalendar ? 'calendar' : 'note',
@@ -274,73 +322,84 @@ function rowToNote(row, database) {
274
322
  createdAt: row.created_at ? new Date(row.created_at) : undefined,
275
323
  };
276
324
  }
277
- /**
278
- * List notes in a teamspace
279
- */
280
- export function listSpaceNotes(spaceIdOrOptions) {
281
- const database = getDatabase();
282
- if (!database)
283
- return [];
325
+ export async function listSpaceNotes(spaceIdOrOptions) {
284
326
  const { spaceId, includeTrash } = normalizeListSpaceOptions(spaceIdOrOptions);
285
- try {
286
- let query = `
287
- SELECT id, content, note_type, title, filename, parent, is_dir, created_at, modified_at
288
- FROM notes
289
- WHERE note_type IN (?, ?)
290
- AND is_dir = 0
291
- `;
292
- const params = [
293
- SQLITE_NOTE_TYPES.TEAMSPACE_NOTE,
294
- SQLITE_NOTE_TYPES.TEAMSPACE_CALENDAR,
295
- ];
296
- if (spaceId) {
297
- const descendantIds = getSpaceDescendantIds(database, spaceId);
298
- if (descendantIds.length === 0)
299
- return [];
300
- const placeholders = descendantIds.map(() => '?').join(',');
301
- query += ` AND id IN (${placeholders})`;
302
- params.push(...descendantIds);
327
+ return bridgeOrFallback(async (bridge) => {
328
+ const allRows = await bridge.listSpaceNoteRows({ spaceId });
329
+ const visible = filterBridgeRowsByTrash(allRows, includeTrash);
330
+ const notesOnly = visible.filter((r) => r.is_dir === 0);
331
+ return notesOnly.map((row) => bridgeRowToNote(row, allRows));
332
+ }, () => {
333
+ const database = getDatabase();
334
+ if (!database)
335
+ return [];
336
+ try {
337
+ let query = `
338
+ SELECT id, content, note_type, title, filename, parent, is_dir, created_at, modified_at
339
+ FROM notes
340
+ WHERE note_type IN (?, ?)
341
+ AND is_dir = 0
342
+ `;
343
+ const params = [
344
+ SQLITE_NOTE_TYPES.TEAMSPACE_NOTE,
345
+ SQLITE_NOTE_TYPES.TEAMSPACE_CALENDAR,
346
+ ];
347
+ if (spaceId) {
348
+ const descendantIds = getSpaceDescendantIds(database, spaceId);
349
+ if (descendantIds.length === 0)
350
+ return [];
351
+ const placeholders = descendantIds.map(() => '?').join(',');
352
+ query += ` AND id IN (${placeholders})`;
353
+ params.push(...descendantIds);
354
+ }
355
+ const rows = database.prepare(query).all(...params);
356
+ const filteredRows = filterRowsByTrash(database, rows, spaceId, includeTrash);
357
+ return filteredRows.map(row => rowToNote(row, database));
303
358
  }
304
- const rows = database.prepare(query).all(...params);
305
- const filteredRows = filterRowsByTrash(database, rows, spaceId, includeTrash);
306
- return filteredRows.map(row => rowToNote(row, database));
307
- }
308
- catch (error) {
309
- console.error('Error listing teamspace notes:', error);
310
- return [];
311
- }
359
+ catch (error) {
360
+ console.error('Error listing teamspace notes:', error);
361
+ return [];
362
+ }
363
+ });
312
364
  }
313
- /**
314
- * Get a specific teamspace note by ID or filename
315
- */
316
- export function getSpaceNote(identifier) {
317
- const database = getDatabase();
318
- if (!database)
319
- return null;
320
- try {
321
- const row = database
322
- .prepare(`
323
- SELECT id, content, note_type, title, filename, parent, is_dir, created_at, modified_at
324
- FROM notes
325
- WHERE (id = ? OR filename = ?)
326
- AND is_dir = 0
327
- `)
328
- .get(identifier, identifier);
329
- return row ? rowToNote(row, database) : null;
330
- }
331
- catch (error) {
332
- console.error('Error getting teamspace note:', error);
333
- return null;
334
- }
365
+ export async function getSpaceNote(identifier) {
366
+ return bridgeOrFallback(async (bridge) => {
367
+ const row = (await bridge.getSpaceNoteRow({ id: identifier })) ??
368
+ (await bridge.getSpaceNoteRow({ filename: identifier }));
369
+ if (!row)
370
+ return null;
371
+ const parents = await bridge.getSpaceParentRows(row.id);
372
+ return bridgeRowToNote(row, [row, ...parents]);
373
+ }, () => {
374
+ const database = getDatabase();
375
+ if (!database)
376
+ return null;
377
+ try {
378
+ const row = database
379
+ .prepare(`
380
+ SELECT id, content, note_type, title, filename, parent, is_dir, created_at, modified_at
381
+ FROM notes
382
+ WHERE (id = ? OR filename = ?)
383
+ AND is_dir = 0
384
+ `)
385
+ .get(identifier, identifier);
386
+ return row ? rowToNote(row, database) : null;
387
+ }
388
+ catch (error) {
389
+ console.error('Error getting teamspace note:', error);
390
+ return null;
391
+ }
392
+ });
335
393
  }
336
394
  /**
337
395
  * Get a teamspace note by title
338
396
  */
339
- export function getSpaceNoteByTitle(title, spaceId, includeTrash = false) {
397
+ export async function getSpaceNoteByTitle(title, spaceId, includeTrash = false) {
340
398
  const database = getDatabase();
341
399
  if (!database)
342
400
  return null;
343
401
  try {
402
+ // First try matching the SQLite title column directly
344
403
  let query = `
345
404
  SELECT id, content, note_type, title, filename, parent, is_dir, created_at, modified_at
346
405
  FROM notes
@@ -363,8 +422,15 @@ export function getSpaceNoteByTitle(title, spaceId, includeTrash = false) {
363
422
  }
364
423
  const rows = database.prepare(query).all(...params);
365
424
  const filteredRows = filterRowsByTrash(database, rows, spaceId, includeTrash);
366
- const row = filteredRows[0];
367
- return row ? rowToNote(row, database) : null;
425
+ if (filteredRows.length > 0) {
426
+ return rowToNote(filteredRows[0], database);
427
+ }
428
+ // Fallback: the SQLite title column may not reflect the frontmatter title.
429
+ // Search notes whose content contains the title in frontmatter (title: or name: keys)
430
+ // and resolve via extractTitle to confirm.
431
+ const lowerTitle = title.toLowerCase();
432
+ const candidates = await listSpaceNotes({ spaceId, includeTrash });
433
+ return candidates.find((note) => note.title.toLowerCase() === lowerTitle) || null;
368
434
  }
369
435
  catch (error) {
370
436
  console.error('Error getting teamspace note by title:', error);
@@ -425,100 +491,123 @@ function parseSearchPatterns(input) {
425
491
  * Search space notes using LIKE with OR pattern support
426
492
  * This is the primary search method - we don't modify NotePlan's database
427
493
  */
428
- export function searchSpaceNotesFTS(query, options = {}) {
429
- const database = getDatabase();
430
- if (!database)
431
- return [];
494
+ export async function searchSpaceNotesFTS(query, options = {}) {
432
495
  const { spaceId, limit = 50, includeTrash = false } = options;
433
- // Parse OR patterns
434
496
  const patterns = parseSearchPatterns(query);
435
497
  if (patterns.length === 0)
436
498
  return [];
437
- try {
438
- // Build OR conditions for multiple patterns
439
- const orConditions = patterns
440
- .map(() => '(content LIKE ? OR title LIKE ?)')
441
- .join(' OR ');
442
- let sql = `
443
- SELECT id, content, note_type, title, filename, parent, is_dir, created_at, modified_at
444
- FROM notes
445
- WHERE (${orConditions})
446
- AND note_type IN (?, ?)
447
- AND is_dir = 0
448
- `;
449
- // Build params: each pattern needs two placeholders (content, title)
450
- const params = [];
451
- for (const pattern of patterns) {
452
- const searchPattern = `%${pattern}%`;
453
- params.push(searchPattern, searchPattern);
499
+ return bridgeOrFallback(async (bridge) => {
500
+ // Two independent fetches: regex matches + parent context for trash
501
+ // filtering and root-space resolution. Parallel cuts wall-clock by ~half.
502
+ const [matchRows, contextRows] = await Promise.all([
503
+ bridge.searchSpaceNoteRows(patterns.join('|'), { spaceId, limit: limit * 2 }),
504
+ bridge.listSpaceNoteRows({ spaceId }),
505
+ ]);
506
+ const trashFreeIds = new Set(filterBridgeRowsByTrash(contextRows, includeTrash).map((r) => r.id));
507
+ return matchRows
508
+ .filter((r) => trashFreeIds.has(r.id))
509
+ .slice(0, limit)
510
+ .map((row) => bridgeRowToNote(row, contextRows));
511
+ }, () => {
512
+ const database = getDatabase();
513
+ if (!database)
514
+ return [];
515
+ try {
516
+ const orConditions = patterns
517
+ .map(() => '(content LIKE ? OR title LIKE ?)')
518
+ .join(' OR ');
519
+ let sql = `
520
+ SELECT id, content, note_type, title, filename, parent, is_dir, created_at, modified_at
521
+ FROM notes
522
+ WHERE (${orConditions})
523
+ AND note_type IN (?, ?)
524
+ AND is_dir = 0
525
+ `;
526
+ const params = [];
527
+ for (const pattern of patterns) {
528
+ const searchPattern = `%${pattern}%`;
529
+ params.push(searchPattern, searchPattern);
530
+ }
531
+ params.push(SQLITE_NOTE_TYPES.TEAMSPACE_NOTE, SQLITE_NOTE_TYPES.TEAMSPACE_CALENDAR);
532
+ if (spaceId) {
533
+ const descendantIds = getSpaceDescendantIds(database, spaceId);
534
+ if (descendantIds.length === 0)
535
+ return [];
536
+ const placeholders = descendantIds.map(() => '?').join(',');
537
+ sql += ` AND id IN (${placeholders})`;
538
+ params.push(...descendantIds);
539
+ }
540
+ sql += ` ORDER BY modified_at DESC LIMIT ?`;
541
+ params.push(limit);
542
+ const rows = database.prepare(sql).all(...params);
543
+ const filteredRows = filterRowsByTrash(database, rows, spaceId, includeTrash);
544
+ return filteredRows.map((row) => rowToNote(row, database));
454
545
  }
455
- params.push(SQLITE_NOTE_TYPES.TEAMSPACE_NOTE, SQLITE_NOTE_TYPES.TEAMSPACE_CALENDAR);
456
- if (spaceId) {
457
- const descendantIds = getSpaceDescendantIds(database, spaceId);
458
- if (descendantIds.length === 0)
459
- return [];
460
- const placeholders = descendantIds.map(() => '?').join(',');
461
- sql += ` AND id IN (${placeholders})`;
462
- params.push(...descendantIds);
546
+ catch (error) {
547
+ console.error('Error searching space notes:', error);
548
+ return [];
463
549
  }
464
- // Order by modified_at (most recent first) for relevance
465
- sql += ` ORDER BY modified_at DESC LIMIT ?`;
466
- params.push(limit);
467
- const rows = database.prepare(sql).all(...params);
468
- const filteredRows = filterRowsByTrash(database, rows, spaceId, includeTrash);
469
- return filteredRows.map((row) => rowToNote(row, database));
470
- }
471
- catch (error) {
472
- console.error('Error searching space notes:', error);
473
- return [];
474
- }
550
+ });
475
551
  }
476
552
  /**
477
553
  * List folders in teamspace
478
554
  */
479
- export function listSpaceFolders(spaceIdOrOptions) {
480
- const database = getDatabase();
481
- if (!database)
482
- return [];
555
+ export async function listSpaceFolders(spaceIdOrOptions) {
483
556
  const { spaceId, includeTrash } = normalizeListSpaceOptions(spaceIdOrOptions);
484
- try {
485
- let query = `
486
- SELECT id, title, filename, parent
487
- FROM notes
488
- WHERE is_dir = 1
489
- `;
490
- const params = [];
491
- if (spaceId) {
492
- const descendantIds = getSpaceDescendantIds(database, spaceId);
493
- if (descendantIds.length === 0)
494
- return [];
495
- const placeholders = descendantIds.map(() => '?').join(',');
496
- query += ` AND id IN (${placeholders})`;
497
- params.push(...descendantIds);
498
- }
499
- let rows = database.prepare(query).all(...params);
500
- if (!includeTrash) {
501
- rows = rows.filter((row) => row.title?.toLowerCase() !== SPACE_TRASH_FOLDER_TITLE.toLowerCase());
502
- }
503
- return rows.map((row) => ({
557
+ return bridgeOrFallback(async (bridge) => {
558
+ const allRows = await bridge.listSpaceNoteRows({ spaceId });
559
+ const visible = filterBridgeRowsByTrash(allRows, includeTrash);
560
+ const folderRows = visible.filter((r) => r.is_dir === 1 && (includeTrash || !isTrashFolderRow(r)));
561
+ return folderRows.map((row) => ({
504
562
  id: row.id,
505
563
  path: row.filename,
506
564
  name: row.title || row.id,
507
565
  source: 'space',
508
- spaceId: findRootSpaceId(database, row.id),
566
+ spaceId: findRootSpaceIdFromRows(row.id, allRows),
509
567
  }));
510
- }
511
- catch (error) {
512
- console.error('Error listing teamspace folders:', error);
513
- return [];
514
- }
568
+ }, () => {
569
+ const database = getDatabase();
570
+ if (!database)
571
+ return [];
572
+ try {
573
+ let query = `
574
+ SELECT id, title, filename, parent
575
+ FROM notes
576
+ WHERE is_dir = 1
577
+ `;
578
+ const params = [];
579
+ if (spaceId) {
580
+ const descendantIds = getSpaceDescendantIds(database, spaceId);
581
+ if (descendantIds.length === 0)
582
+ return [];
583
+ const placeholders = descendantIds.map(() => '?').join(',');
584
+ query += ` AND id IN (${placeholders})`;
585
+ params.push(...descendantIds);
586
+ }
587
+ let rows = database.prepare(query).all(...params);
588
+ if (!includeTrash) {
589
+ rows = rows.filter((row) => !isTrashFolderRow({ ...row, is_dir: 1 }));
590
+ }
591
+ return rows.map((row) => ({
592
+ id: row.id,
593
+ path: row.filename,
594
+ name: row.title || row.id,
595
+ source: 'space',
596
+ spaceId: findRootSpaceId(database, row.id),
597
+ }));
598
+ }
599
+ catch (error) {
600
+ console.error('Error listing teamspace folders:', error);
601
+ return [];
602
+ }
603
+ });
515
604
  }
516
- export function resolveSpaceFolder(spaceId, identifier, options = {}) {
605
+ export async function resolveSpaceFolder(spaceId, identifier, options = {}) {
517
606
  const query = identifier.trim();
518
607
  if (!query)
519
608
  return null;
520
609
  const lowerQuery = query.toLowerCase();
521
- const folders = listSpaceFolders({ spaceId, includeTrash: options.includeTrash === true });
610
+ const folders = await listSpaceFolders({ spaceId, includeTrash: options.includeTrash === true });
522
611
  const exactById = folders.find((folder) => folder.id === query);
523
612
  if (exactById)
524
613
  return exactById;
@@ -531,42 +620,51 @@ export function resolveSpaceFolder(spaceId, identifier, options = {}) {
531
620
  }
532
621
  return null;
533
622
  }
534
- export function isSpaceNoteInTrash(identifier) {
535
- const database = getDatabase();
536
- if (!database)
537
- return false;
538
- try {
539
- const row = database
540
- .prepare(`
541
- WITH RECURSIVE parent_chain AS (
542
- SELECT id, parent, is_dir, title
543
- FROM notes
544
- WHERE (id = ? OR filename = ?)
545
- AND is_dir = 0
546
- UNION ALL
547
- SELECT n.id, n.parent, n.is_dir, n.title
548
- FROM notes n
549
- INNER JOIN parent_chain pc ON n.id = pc.parent
550
- )
551
- SELECT id
552
- FROM parent_chain
553
- WHERE is_dir = 1
554
- AND lower(title) = lower(?)
555
- LIMIT 1
556
- `)
557
- .get(identifier, identifier, SPACE_TRASH_FOLDER_TITLE);
558
- return Boolean(row?.id);
559
- }
560
- catch (error) {
561
- console.error('Error checking TeamSpace trash status:', error);
562
- return false;
563
- }
623
+ export async function isSpaceNoteInTrash(identifier) {
624
+ return bridgeOrFallback(async (bridge) => {
625
+ const target = (await bridge.getSpaceNoteRow({ id: identifier })) ??
626
+ (await bridge.getSpaceNoteRow({ filename: identifier }));
627
+ if (!target)
628
+ return false;
629
+ const parents = await bridge.getSpaceParentRows(target.id);
630
+ return parents.some(isTrashFolderRow);
631
+ }, () => {
632
+ const database = getDatabase();
633
+ if (!database)
634
+ return false;
635
+ try {
636
+ const row = database
637
+ .prepare(`
638
+ WITH RECURSIVE parent_chain AS (
639
+ SELECT id, parent, is_dir, title
640
+ FROM notes
641
+ WHERE (id = ? OR filename = ?)
642
+ AND is_dir = 0
643
+ UNION ALL
644
+ SELECT n.id, n.parent, n.is_dir, n.title
645
+ FROM notes n
646
+ INNER JOIN parent_chain pc ON n.id = pc.parent
647
+ )
648
+ SELECT id
649
+ FROM parent_chain
650
+ WHERE is_dir = 1
651
+ AND lower(title) = lower(?)
652
+ LIMIT 1
653
+ `)
654
+ .get(identifier, identifier, SPACE_TRASH_FOLDER_TITLE);
655
+ return Boolean(row?.id);
656
+ }
657
+ catch (error) {
658
+ console.error('Error checking TeamSpace trash status:', error);
659
+ return false;
660
+ }
661
+ });
564
662
  }
565
663
  /**
566
664
  * Extract all unique tags from teamspace notes
567
665
  */
568
- export function extractSpaceTags(spaceId) {
569
- const notes = listSpaceNotes(spaceId);
666
+ export async function extractSpaceTags(spaceId) {
667
+ const notes = await listSpaceNotes(spaceId);
570
668
  const tags = new Set();
571
669
  for (const note of notes) {
572
670
  for (const tag of extractTagsFromContent(note.content)) {
@@ -575,34 +673,38 @@ export function extractSpaceTags(spaceId) {
575
673
  }
576
674
  return Array.from(tags).sort();
577
675
  }
578
- /**
579
- * Get calendar note from teamspace by date
580
- */
581
- export function getSpaceCalendarNote(dateStr, spaceId) {
582
- const database = getDatabase();
583
- if (!database)
584
- return null;
585
- try {
586
- // Get descendants first
587
- const descendantIds = getSpaceDescendantIds(database, spaceId);
588
- if (descendantIds.length === 0)
676
+ export async function getSpaceCalendarNote(dateStr, spaceId) {
677
+ return bridgeOrFallback(async (bridge) => {
678
+ const allRows = await bridge.listSpaceNoteRows({ spaceId });
679
+ const match = allRows.find((r) => r.note_type === SQLITE_NOTE_TYPES.TEAMSPACE_CALENDAR &&
680
+ r.is_dir === 0 &&
681
+ r.filename.includes(dateStr));
682
+ return match ? bridgeRowToNote(match, allRows) : null;
683
+ }, () => {
684
+ const database = getDatabase();
685
+ if (!database)
589
686
  return null;
590
- const placeholders = descendantIds.map(() => '?').join(',');
591
- const row = database
592
- .prepare(`
593
- SELECT id, content, note_type, title, filename, parent, is_dir, created_at, modified_at
594
- FROM notes
595
- WHERE note_type = ?
596
- AND id IN (${placeholders})
597
- AND filename LIKE ?
598
- AND is_dir = 0
599
- `)
600
- .get(SQLITE_NOTE_TYPES.TEAMSPACE_CALENDAR, ...descendantIds, `%${dateStr}%`);
601
- return row ? rowToNote(row, database) : null;
602
- }
603
- catch (error) {
604
- console.error('Error getting teamspace calendar note:', error);
605
- return null;
606
- }
687
+ try {
688
+ const descendantIds = getSpaceDescendantIds(database, spaceId);
689
+ if (descendantIds.length === 0)
690
+ return null;
691
+ const placeholders = descendantIds.map(() => '?').join(',');
692
+ const row = database
693
+ .prepare(`
694
+ SELECT id, content, note_type, title, filename, parent, is_dir, created_at, modified_at
695
+ FROM notes
696
+ WHERE note_type = ?
697
+ AND id IN (${placeholders})
698
+ AND filename LIKE ?
699
+ AND is_dir = 0
700
+ `)
701
+ .get(SQLITE_NOTE_TYPES.TEAMSPACE_CALENDAR, ...descendantIds, `%${dateStr}%`);
702
+ return row ? rowToNote(row, database) : null;
703
+ }
704
+ catch (error) {
705
+ console.error('Error getting teamspace calendar note:', error);
706
+ return null;
707
+ }
708
+ });
607
709
  }
608
710
  //# sourceMappingURL=sqlite-reader.js.map