@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
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
import { registerCommandGroup } from '../descriptor/register.js';
|
|
2
2
|
import { FORMAT_OPTIONS, OFFLINE_OPTIONS, REQUIRE_SYNC_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
|
|
3
|
+
import { WRITE_DURABILITY_OPTION } from '../writeDurability.js';
|
|
3
4
|
import { runNoteCreate, runNoteDelete, runNoteDetail, runNoteGet, runNoteMove, runNotePreview, runNoteSearch, runNoteStar, runNoteTag, runNoteUnstar, runNoteUpdate, } from './actions.js';
|
|
4
5
|
import { runNoteBulk as runNoteBulkAction } from './bulk.js';
|
|
5
6
|
import { runNoteContentRead, runNotePatch } from './contentPatchCommand.js';
|
|
6
7
|
import { runNoteExport } from './export.js';
|
|
7
8
|
const NOTE_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...REQUIRE_SYNC_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
|
|
9
|
+
const NOTE_WRITE_RECEIPT_FIELDS = [
|
|
10
|
+
'durability',
|
|
11
|
+
'upload_queue_remaining',
|
|
12
|
+
'version',
|
|
13
|
+
'updated_at',
|
|
14
|
+
'content_hash',
|
|
15
|
+
'changed',
|
|
16
|
+
'stale',
|
|
17
|
+
];
|
|
18
|
+
const NOTE_BATCH_WRITE_RECEIPT_FIELDS = [
|
|
19
|
+
'durability',
|
|
20
|
+
'upload_queue_remaining',
|
|
21
|
+
'results.version',
|
|
22
|
+
'results.updated_at',
|
|
23
|
+
'results.content_hash',
|
|
24
|
+
'results.changed',
|
|
25
|
+
'stale',
|
|
26
|
+
];
|
|
8
27
|
export const noteCommandGroup = {
|
|
9
28
|
name: 'note',
|
|
10
29
|
description: 'Manage notes',
|
|
@@ -161,6 +180,7 @@ export const noteCommandGroup = {
|
|
|
161
180
|
{ flags: '--type <note|crawl>', description: 'Note type: note or crawl', type: 'string', defaultValue: 'crawl', enum: ['note', 'crawl'] },
|
|
162
181
|
{ flags: '--tags <string|@file>', description: 'Tag list (JSON array or comma/newline-separated)', type: 'string' },
|
|
163
182
|
{ flags: '--boxes <string|@file>', description: 'Box paths or unique names (JSON array or comma/newline-separated)', type: 'string' },
|
|
183
|
+
WRITE_DURABILITY_OPTION,
|
|
164
184
|
{ flags: '--dry-run', description: 'Preview the write without executing it', type: 'boolean' },
|
|
165
185
|
],
|
|
166
186
|
schema: {
|
|
@@ -170,10 +190,10 @@ export const noteCommandGroup = {
|
|
|
170
190
|
globalOptions: NOTE_GLOBAL_OPTIONS,
|
|
171
191
|
output: {
|
|
172
192
|
type: 'structured',
|
|
173
|
-
summary: 'Creation acknowledgement with
|
|
174
|
-
fields: ['id',
|
|
193
|
+
summary: 'Creation acknowledgement with write durability, queue state, and note revision receipt',
|
|
194
|
+
fields: ['id', ...NOTE_WRITE_RECEIPT_FIELDS],
|
|
175
195
|
},
|
|
176
|
-
examples: ['dino note create --title "Weekly note" --content @/tmp/note.md --type note --format json'],
|
|
196
|
+
examples: ['dino note create --title "Weekly note" --content @/tmp/note.md --type note --durability uploaded --format json'],
|
|
177
197
|
},
|
|
178
198
|
action: runNoteCreate,
|
|
179
199
|
},
|
|
@@ -186,6 +206,7 @@ export const noteCommandGroup = {
|
|
|
186
206
|
{ flags: '--tags <string|@file>', description: 'Replace the entire tag list; use [] to clear all tags', type: 'string' },
|
|
187
207
|
{ flags: '--boxes <string|@file>', description: 'Replace the entire box list; use [] to clear all boxes', type: 'string' },
|
|
188
208
|
{ flags: '--starred <true|false>', description: 'Replace the starred state', type: 'string', enum: ['true', 'false'] },
|
|
209
|
+
WRITE_DURABILITY_OPTION,
|
|
189
210
|
{ flags: '--dry-run', description: 'Preview the write without executing it', type: 'boolean' },
|
|
190
211
|
],
|
|
191
212
|
schema: {
|
|
@@ -195,8 +216,8 @@ export const noteCommandGroup = {
|
|
|
195
216
|
globalOptions: NOTE_GLOBAL_OPTIONS,
|
|
196
217
|
output: {
|
|
197
218
|
type: 'structured',
|
|
198
|
-
summary: 'Explicit-id full metadata replacement result with per-note
|
|
199
|
-
fields: ['total', 'updatedCount', 'results',
|
|
219
|
+
summary: 'Explicit-id full metadata replacement result with per-note write receipts',
|
|
220
|
+
fields: ['total', 'updatedCount', 'results', ...NOTE_BATCH_WRITE_RECEIPT_FIELDS],
|
|
200
221
|
},
|
|
201
222
|
examples: [
|
|
202
223
|
'dino note update <id> --tags "work,ai" --format json --dry-run',
|
|
@@ -214,6 +235,7 @@ export const noteCommandGroup = {
|
|
|
214
235
|
{ flags: '--add <string|@file>', description: 'Tags to add without removing existing tags', type: 'string' },
|
|
215
236
|
{ flags: '--remove <string|@file>', description: 'Tags to remove while preserving the rest', type: 'string' },
|
|
216
237
|
{ flags: '--replace <string|@file>', description: 'Replace the entire tag list; use [] to clear all tags', type: 'string' },
|
|
238
|
+
WRITE_DURABILITY_OPTION,
|
|
217
239
|
{ flags: '--dry-run', description: 'Preview the write without executing it', type: 'boolean' },
|
|
218
240
|
],
|
|
219
241
|
schema: {
|
|
@@ -223,8 +245,8 @@ export const noteCommandGroup = {
|
|
|
223
245
|
globalOptions: NOTE_GLOBAL_OPTIONS,
|
|
224
246
|
output: {
|
|
225
247
|
type: 'structured',
|
|
226
|
-
summary: 'Explicit-id incremental tag organization result with per-note
|
|
227
|
-
fields: ['field', 'operation', 'total', 'updatedCount', 'results',
|
|
248
|
+
summary: 'Explicit-id incremental tag organization result with per-note write receipts',
|
|
249
|
+
fields: ['field', 'operation', 'total', 'updatedCount', 'results', ...NOTE_BATCH_WRITE_RECEIPT_FIELDS],
|
|
228
250
|
},
|
|
229
251
|
examples: [
|
|
230
252
|
'dino note tag <id> --add "AI,work" --format json --dry-run',
|
|
@@ -242,6 +264,7 @@ export const noteCommandGroup = {
|
|
|
242
264
|
{ flags: '--add <string|@file>', description: 'Box paths or unique names to add without removing existing boxes', type: 'string' },
|
|
243
265
|
{ flags: '--remove <string|@file>', description: 'Box paths or unique names to remove while preserving the rest', type: 'string' },
|
|
244
266
|
{ flags: '--replace <string|@file>', description: 'Replace the entire box list; use [] to clear all boxes', type: 'string' },
|
|
267
|
+
WRITE_DURABILITY_OPTION,
|
|
245
268
|
{ flags: '--dry-run', description: 'Preview the write without executing it', type: 'boolean' },
|
|
246
269
|
],
|
|
247
270
|
schema: {
|
|
@@ -251,8 +274,8 @@ export const noteCommandGroup = {
|
|
|
251
274
|
globalOptions: NOTE_GLOBAL_OPTIONS,
|
|
252
275
|
output: {
|
|
253
276
|
type: 'structured',
|
|
254
|
-
summary: 'Explicit-id incremental box organization result with per-note
|
|
255
|
-
fields: ['field', 'operation', 'total', 'updatedCount', 'results',
|
|
277
|
+
summary: 'Explicit-id incremental box organization result with per-note write receipts',
|
|
278
|
+
fields: ['field', 'operation', 'total', 'updatedCount', 'results', ...NOTE_BATCH_WRITE_RECEIPT_FIELDS],
|
|
256
279
|
},
|
|
257
280
|
examples: [
|
|
258
281
|
'dino note move <id> --add "Work/Inbox" --format json --dry-run',
|
|
@@ -274,6 +297,7 @@ export const noteCommandGroup = {
|
|
|
274
297
|
{ flags: '--content <string|@file>', description: 'Markdown content to insert', type: 'string' },
|
|
275
298
|
{ flags: '--read-token <token>', description: 'Required for real writes; returned by note content-read', type: 'string' },
|
|
276
299
|
{ flags: '--allow-protected-replace', description: 'Allow replace operations to affect media, table, container, or unknown blocks after reviewing content-read output', type: 'boolean' },
|
|
300
|
+
WRITE_DURABILITY_OPTION,
|
|
277
301
|
{ flags: '--dry-run', description: 'Preview the patch without writing', type: 'boolean' },
|
|
278
302
|
],
|
|
279
303
|
schema: {
|
|
@@ -283,8 +307,8 @@ export const noteCommandGroup = {
|
|
|
283
307
|
globalOptions: NOTE_GLOBAL_OPTIONS,
|
|
284
308
|
output: {
|
|
285
309
|
type: 'structured',
|
|
286
|
-
summary: 'Structured content edit result with content hashes, block context,
|
|
287
|
-
fields: ['noteId', 'title', 'operation', '
|
|
310
|
+
summary: 'Structured content edit result with content hashes, block context, synchronized derived metadata, and write receipt',
|
|
311
|
+
fields: ['noteId', 'title', 'operation', 'content_changed', 'saved', 'beforeHash', 'afterHash', 'target', 'derived', ...NOTE_WRITE_RECEIPT_FIELDS],
|
|
288
312
|
},
|
|
289
313
|
examples: [
|
|
290
314
|
'dino note content-read <id> --format json',
|
|
@@ -313,6 +337,7 @@ export const noteCommandGroup = {
|
|
|
313
337
|
{ flags: '--box-replace <string|@file>', description: 'Replace the entire box list on every matched note; use [] to clear all boxes', type: 'string' },
|
|
314
338
|
{ flags: '--expected-count <n>', description: 'Required for real writes; must equal the current matched note count', type: 'number' },
|
|
315
339
|
{ flags: '--confirm', description: 'Required for real writes after reviewing a dry run', type: 'boolean' },
|
|
340
|
+
WRITE_DURABILITY_OPTION,
|
|
316
341
|
{ flags: '--dry-run', description: 'Preview matched targets and changes without executing', type: 'boolean' },
|
|
317
342
|
],
|
|
318
343
|
schema: {
|
|
@@ -322,8 +347,8 @@ export const noteCommandGroup = {
|
|
|
322
347
|
globalOptions: NOTE_GLOBAL_OPTIONS,
|
|
323
348
|
output: {
|
|
324
349
|
type: 'structured',
|
|
325
|
-
summary: 'Filter-based bulk metadata organization result with applied filters and per-note
|
|
326
|
-
fields: ['field', 'operation', 'total', 'updatedCount', 'filters', 'results',
|
|
350
|
+
summary: 'Filter-based bulk metadata organization result with applied filters and per-note write receipts',
|
|
351
|
+
fields: ['field', 'operation', 'total', 'updatedCount', 'filters', 'results', ...NOTE_BATCH_WRITE_RECEIPT_FIELDS],
|
|
327
352
|
},
|
|
328
353
|
examples: [
|
|
329
354
|
'dino note bulk --tags "Inbox" --tag-add "AI" --format json --dry-run',
|
|
@@ -336,7 +361,10 @@ export const noteCommandGroup = {
|
|
|
336
361
|
name: 'delete',
|
|
337
362
|
description: 'Soft-delete a note by setting is_del=1',
|
|
338
363
|
arguments: [{ syntax: '<id>', description: 'Note id' }],
|
|
339
|
-
options: [
|
|
364
|
+
options: [
|
|
365
|
+
WRITE_DURABILITY_OPTION,
|
|
366
|
+
{ flags: '--dry-run', description: 'Preview the write without executing it', type: 'boolean' },
|
|
367
|
+
],
|
|
340
368
|
schema: {
|
|
341
369
|
path: 'note.delete',
|
|
342
370
|
risk: 'high-risk-write',
|
|
@@ -344,8 +372,8 @@ export const noteCommandGroup = {
|
|
|
344
372
|
globalOptions: NOTE_GLOBAL_OPTIONS,
|
|
345
373
|
output: {
|
|
346
374
|
type: 'structured',
|
|
347
|
-
summary: 'Deletion acknowledgement',
|
|
348
|
-
fields: ['id',
|
|
375
|
+
summary: 'Deletion acknowledgement with write durability and note revision receipt',
|
|
376
|
+
fields: ['id', ...NOTE_WRITE_RECEIPT_FIELDS],
|
|
349
377
|
},
|
|
350
378
|
examples: ['dino note delete <id> --format json'],
|
|
351
379
|
},
|
|
@@ -357,6 +385,7 @@ export const noteCommandGroup = {
|
|
|
357
385
|
arguments: [{ syntax: '[id]', description: 'Optional note id for single-note update' }],
|
|
358
386
|
options: [
|
|
359
387
|
{ flags: '--ids <string|@file>', description: 'Batch note ids (JSON array or comma/newline-separated)', type: 'string' },
|
|
388
|
+
WRITE_DURABILITY_OPTION,
|
|
360
389
|
{ flags: '--dry-run', description: 'Preview the write without executing it', type: 'boolean' },
|
|
361
390
|
],
|
|
362
391
|
schema: {
|
|
@@ -366,8 +395,8 @@ export const noteCommandGroup = {
|
|
|
366
395
|
globalOptions: NOTE_GLOBAL_OPTIONS,
|
|
367
396
|
output: {
|
|
368
397
|
type: 'structured',
|
|
369
|
-
summary: 'Mutation result with per-note starred status',
|
|
370
|
-
fields: ['total', 'updatedCount', 'results',
|
|
398
|
+
summary: 'Mutation result with per-note starred status and write receipts',
|
|
399
|
+
fields: ['total', 'updatedCount', 'results', ...NOTE_BATCH_WRITE_RECEIPT_FIELDS],
|
|
371
400
|
},
|
|
372
401
|
examples: ['dino note star <id> --format json --dry-run'],
|
|
373
402
|
},
|
|
@@ -379,6 +408,7 @@ export const noteCommandGroup = {
|
|
|
379
408
|
arguments: [{ syntax: '[id]', description: 'Optional note id for single-note update' }],
|
|
380
409
|
options: [
|
|
381
410
|
{ flags: '--ids <string|@file>', description: 'Batch note ids (JSON array or comma/newline-separated)', type: 'string' },
|
|
411
|
+
WRITE_DURABILITY_OPTION,
|
|
382
412
|
{ flags: '--dry-run', description: 'Preview the write without executing it', type: 'boolean' },
|
|
383
413
|
],
|
|
384
414
|
schema: {
|
|
@@ -388,8 +418,8 @@ export const noteCommandGroup = {
|
|
|
388
418
|
globalOptions: NOTE_GLOBAL_OPTIONS,
|
|
389
419
|
output: {
|
|
390
420
|
type: 'structured',
|
|
391
|
-
summary: 'Mutation result with per-note starred status',
|
|
392
|
-
fields: ['total', 'updatedCount', 'results',
|
|
421
|
+
summary: 'Mutation result with per-note starred status and write receipts',
|
|
422
|
+
fields: ['total', 'updatedCount', 'results', ...NOTE_BATCH_WRITE_RECEIPT_FIELDS],
|
|
393
423
|
},
|
|
394
424
|
examples: ['dino note unstar <id> --format json --dry-run'],
|
|
395
425
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { convertMarkdownToNoteContent } from '../../utils/tiptapMarkdown.js';
|
|
2
2
|
import { canonicalizeBidlinksInMarkdown } from '../../utils/bidlinkCanonicalization.js';
|
|
3
3
|
import { DinoxError } from '../../utils/errors.js';
|
|
4
|
-
import { assertNotesUpdatable, getNoteDetails,
|
|
4
|
+
import { assertNotesUpdatable, getNoteDetails, resolveZettelBoxIdsForCreate, resolveZettelBoxIdsForUpdate, validateTagsForCreate, validateTagsForUpdate, } from './repo.js';
|
|
5
5
|
function normalizeDistinctStrings(values) {
|
|
6
6
|
return Array.from(new Set(values.map((value) => value.trim()).filter(Boolean)));
|
|
7
7
|
}
|
|
@@ -21,15 +21,12 @@ function buildExecutionWarnings(config, offline) {
|
|
|
21
21
|
if (offline) {
|
|
22
22
|
warnings.push('Execution would run in offline mode and only use the local cache.');
|
|
23
23
|
}
|
|
24
|
-
if (!offline && !config.powersync.uploadBaseUrl) {
|
|
25
|
-
warnings.push('Execution would be local-only because powersync.uploadBaseUrl is unset.');
|
|
26
|
-
}
|
|
27
24
|
return warnings;
|
|
28
25
|
}
|
|
29
26
|
export async function buildNoteCreateDryRunPreview(args) {
|
|
30
27
|
const canonicalContentMd = await canonicalizeBidlinksInMarkdown(args.db, args.contentMd, args.userId);
|
|
31
28
|
const { contentText } = convertMarkdownToNoteContent(canonicalContentMd);
|
|
32
|
-
const tags = await validateTagsForCreate(args.db, args.tagsInput);
|
|
29
|
+
const tags = await validateTagsForCreate(args.db, args.tagsInput, args.userId);
|
|
33
30
|
await resolveZettelBoxIdsForCreate(args.db, args.zettelBoxNames);
|
|
34
31
|
return {
|
|
35
32
|
dry_run: true,
|
|
@@ -59,14 +56,14 @@ export async function buildNoteCreateDryRunPreview(args) {
|
|
|
59
56
|
export async function buildNoteUpdateDryRunPreview(args) {
|
|
60
57
|
await assertNotesUpdatable(args.db, args.ids);
|
|
61
58
|
const nextTags = Array.isArray(args.tagsInput)
|
|
62
|
-
? await validateTagsForUpdate(args.db, args.tagsInput)
|
|
59
|
+
? await validateTagsForUpdate(args.db, args.tagsInput, args.userId ?? '')
|
|
63
60
|
: undefined;
|
|
64
61
|
if (Array.isArray(args.zettelBoxNames)) {
|
|
65
62
|
await resolveZettelBoxIdsForUpdate(args.db, args.zettelBoxNames);
|
|
66
63
|
}
|
|
67
64
|
const details = await getNoteDetails(args.db, args.ids);
|
|
68
65
|
const changes = details.map((note) => {
|
|
69
|
-
const currentTags =
|
|
66
|
+
const currentTags = note.tags;
|
|
70
67
|
const nextBoxes = Array.isArray(args.zettelBoxNames) ? normalizeDistinctStrings(args.zettelBoxNames) : note.zettel_boxes;
|
|
71
68
|
const tagsTo = nextTags ?? currentTags;
|
|
72
69
|
const starredTo = typeof args.starred === 'boolean' ? args.starred : note.is_starred;
|
|
@@ -14,6 +14,12 @@ export type NoteExportResult = {
|
|
|
14
14
|
};
|
|
15
15
|
export declare function parseExportFormat(value: unknown): NoteExportFormat;
|
|
16
16
|
export declare function buildExportFileName(note: NoteExportItem, format: NoteExportFormat): string;
|
|
17
|
+
export declare function writeExportFiles(args: {
|
|
18
|
+
notes: NoteExportItem[];
|
|
19
|
+
format: NoteExportFormat;
|
|
20
|
+
outputPath: string;
|
|
21
|
+
overwrite: boolean;
|
|
22
|
+
}): Promise<NoteExportFile[]>;
|
|
17
23
|
export declare function buildNoteExportStdout(args: {
|
|
18
24
|
notes: NoteExportItem[];
|
|
19
25
|
format: NoteExportFormat;
|
|
@@ -22,13 +22,25 @@ export function parseExportFormat(value) {
|
|
|
22
22
|
}
|
|
23
23
|
throw new DinoxError('--type must be one of: markdown, json');
|
|
24
24
|
}
|
|
25
|
+
function truncateUtf8(value, maxBytes) {
|
|
26
|
+
let result = '';
|
|
27
|
+
let bytes = 0;
|
|
28
|
+
for (const char of value) {
|
|
29
|
+
const charBytes = Buffer.byteLength(char, 'utf8');
|
|
30
|
+
if (bytes + charBytes > maxBytes) {
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
result += char;
|
|
34
|
+
bytes += charBytes;
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
25
38
|
function normalizeFileNamePart(value) {
|
|
26
39
|
const normalized = value
|
|
27
40
|
.normalize('NFKD')
|
|
28
41
|
.replace(/[^\w\u4e00-\u9fff.-]+/g, '-')
|
|
29
|
-
.replace(/^-+|-+$/g, '')
|
|
30
|
-
|
|
31
|
-
return normalized || 'untitled';
|
|
42
|
+
.replace(/^-+|-+$/g, '');
|
|
43
|
+
return truncateUtf8(normalized, 80).replace(/-+$/g, '') || 'untitled';
|
|
32
44
|
}
|
|
33
45
|
export function buildExportFileName(note, format) {
|
|
34
46
|
const title = normalizeFileNamePart(note.title?.trim() ?? '');
|
|
@@ -91,9 +103,10 @@ async function pathExists(filePath) {
|
|
|
91
103
|
}
|
|
92
104
|
}
|
|
93
105
|
async function resolveOutputTargets(args) {
|
|
106
|
+
const outputLooksLikeDirectory = /[\\/]$/.test(args.outputPath);
|
|
94
107
|
const outputPath = path.resolve(args.outputPath);
|
|
95
108
|
if (args.notes.length === 1) {
|
|
96
|
-
if (
|
|
109
|
+
if (outputLooksLikeDirectory) {
|
|
97
110
|
return [{
|
|
98
111
|
id: args.notes[0].id,
|
|
99
112
|
path: path.join(outputPath, buildExportFileName(args.notes[0], args.format)),
|
|
@@ -130,7 +143,7 @@ async function resolveOutputTargets(args) {
|
|
|
130
143
|
format: args.format,
|
|
131
144
|
}));
|
|
132
145
|
}
|
|
133
|
-
async function writeExportFiles(args) {
|
|
146
|
+
export async function writeExportFiles(args) {
|
|
134
147
|
const targets = await resolveOutputTargets({
|
|
135
148
|
outputPath: args.outputPath,
|
|
136
149
|
notes: args.notes,
|
|
@@ -158,7 +171,14 @@ async function writeExportFiles(args) {
|
|
|
158
171
|
const content = args.format === 'markdown'
|
|
159
172
|
? serializeMarkdownExport(note)
|
|
160
173
|
: toJson(note);
|
|
161
|
-
await fs.writeFile(target.path, content, {
|
|
174
|
+
await fs.writeFile(target.path, content, {
|
|
175
|
+
encoding: 'utf8',
|
|
176
|
+
flag: args.overwrite ? 'w' : 'wx',
|
|
177
|
+
mode: 0o600,
|
|
178
|
+
});
|
|
179
|
+
if (process.platform !== 'win32') {
|
|
180
|
+
await fs.chmod(target.path, 0o600);
|
|
181
|
+
}
|
|
162
182
|
}
|
|
163
183
|
return targets;
|
|
164
184
|
}
|
|
@@ -25,6 +25,10 @@ function sameOrdered(left, right) {
|
|
|
25
25
|
function keySet(values) {
|
|
26
26
|
return new Set(values.map((value) => value.trim().toLowerCase()).filter(Boolean));
|
|
27
27
|
}
|
|
28
|
+
function normalizeVersion(value) {
|
|
29
|
+
const parsed = Number(value ?? 0);
|
|
30
|
+
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : 0;
|
|
31
|
+
}
|
|
28
32
|
function applyListOperation(current, values, operation) {
|
|
29
33
|
const normalizedValues = normalizeDistinct(values);
|
|
30
34
|
if (operation === 'replace') {
|
|
@@ -59,7 +63,7 @@ async function loadNoteMetadataState(db, ids) {
|
|
|
59
63
|
const batch = ids.slice(index, index + chunkSize);
|
|
60
64
|
const placeholders = batch.map(() => '?').join(', ');
|
|
61
65
|
const rows = await db.getAll(`
|
|
62
|
-
SELECT id, title, tags, zettel_boxes, is_del
|
|
66
|
+
SELECT id, title, tags, zettel_boxes, is_del, version
|
|
63
67
|
FROM c_note
|
|
64
68
|
WHERE id IN (${placeholders})
|
|
65
69
|
`, batch);
|
|
@@ -95,6 +99,7 @@ export async function planNoteMetadataMutation(db, input) {
|
|
|
95
99
|
before,
|
|
96
100
|
after,
|
|
97
101
|
changed: !sameOrdered(before, after),
|
|
102
|
+
expectedVersion: normalizeVersion(note.version),
|
|
98
103
|
};
|
|
99
104
|
});
|
|
100
105
|
return {
|
|
@@ -116,11 +121,17 @@ export async function applyNoteMetadataMutation(db, plan) {
|
|
|
116
121
|
const timestamp = nowIsoUtc();
|
|
117
122
|
for (const item of changed) {
|
|
118
123
|
const column = plan.field === 'tags' ? 'tags' : 'zettel_boxes';
|
|
119
|
-
await tx.execute(`
|
|
124
|
+
const result = await tx.execute(`
|
|
120
125
|
UPDATE c_note
|
|
121
126
|
SET ${column} = ?, updated_at = ?, version = COALESCE(version, 0) + 1
|
|
122
127
|
WHERE id = ?
|
|
123
|
-
|
|
128
|
+
AND COALESCE(version, 0) = ?
|
|
129
|
+
AND (is_del IS NULL OR is_del = 0)
|
|
130
|
+
RETURNING id
|
|
131
|
+
`, [JSON.stringify(item.after), timestamp, item.id, item.expectedVersion]);
|
|
132
|
+
if (result.rows?.length !== 1) {
|
|
133
|
+
throw new DinoxError(`Note changed concurrently: ${item.id}. Re-run the command with fresh data.`);
|
|
134
|
+
}
|
|
124
135
|
}
|
|
125
136
|
});
|
|
126
137
|
return plan;
|
|
@@ -36,13 +36,16 @@ export type SearchedNote = {
|
|
|
36
36
|
export type NoteDetail = {
|
|
37
37
|
id: string;
|
|
38
38
|
created_at: string | null;
|
|
39
|
+
updated_at: string | null;
|
|
39
40
|
title: string | null;
|
|
40
41
|
type: string | null;
|
|
41
42
|
source: string | null;
|
|
42
|
-
tags: string
|
|
43
|
+
tags: string[];
|
|
43
44
|
is_del: number | null;
|
|
44
45
|
is_starred: boolean;
|
|
45
46
|
content_md: string | null;
|
|
47
|
+
content_text: string | null;
|
|
48
|
+
content_json: unknown;
|
|
46
49
|
zettel_boxes: string[];
|
|
47
50
|
};
|
|
48
51
|
export type NoteExportItem = {
|
|
@@ -90,11 +93,50 @@ export type NotePreview = {
|
|
|
90
93
|
export type UpdateNoteResult = {
|
|
91
94
|
id: string;
|
|
92
95
|
updated: boolean;
|
|
96
|
+
changed: string[];
|
|
93
97
|
tags: string[];
|
|
94
98
|
zettelBoxIds: string[];
|
|
95
99
|
is_starred: boolean;
|
|
96
100
|
};
|
|
101
|
+
/**
|
|
102
|
+
* JSON-safe note-search request. Box *names/paths* are carried (not resolved ids) so the
|
|
103
|
+
* same payload can be executed either in-process or sent to the daemon over the wire;
|
|
104
|
+
* id resolution happens inside {@link executeNoteSearch} against whichever db runs it.
|
|
105
|
+
*/
|
|
106
|
+
export type NoteSearchExecuteParams = {
|
|
107
|
+
keyword: string;
|
|
108
|
+
includeDeleted: boolean;
|
|
109
|
+
tagsExpression?: string;
|
|
110
|
+
createdAtFrom?: string;
|
|
111
|
+
createdAtTo?: string;
|
|
112
|
+
starred?: boolean;
|
|
113
|
+
boxValues: string[];
|
|
114
|
+
matchEmptyZettelBoxes: boolean;
|
|
115
|
+
sqlExpression?: string;
|
|
116
|
+
limit?: number;
|
|
117
|
+
offset?: number;
|
|
118
|
+
};
|
|
119
|
+
export type NoteSearchExecuteResult = {
|
|
120
|
+
rows: SearchedNote[];
|
|
121
|
+
totalCount: number;
|
|
122
|
+
/** Resolved zettel-box ids, surfaced so callers can echo them in response metadata. */
|
|
123
|
+
zettelBoxIds: string[];
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Resolve box names to ids and run the note search. Shared by the local command path and
|
|
127
|
+
* the daemon RPC handler so both produce identical results from one implementation.
|
|
128
|
+
*/
|
|
129
|
+
export declare function executeNoteSearch(db: AbstractPowerSyncDatabase, params: NoteSearchExecuteParams): Promise<NoteSearchExecuteResult>;
|
|
97
130
|
export declare function searchNotes(db: AbstractPowerSyncDatabase, query: string, options: SearchNotesOptions): Promise<SearchedNote[]>;
|
|
131
|
+
type SearchNotesPageOptions = SearchNotesOptions & {
|
|
132
|
+
limit?: number;
|
|
133
|
+
offset?: number;
|
|
134
|
+
};
|
|
135
|
+
type SearchNotesPageResult = {
|
|
136
|
+
rows: SearchedNote[];
|
|
137
|
+
totalCount: number;
|
|
138
|
+
};
|
|
139
|
+
export declare function searchNotesPage(db: AbstractPowerSyncDatabase, query: string, options: SearchNotesPageOptions): Promise<SearchNotesPageResult>;
|
|
98
140
|
export declare function parseZettelBoxIds(raw: string | null): string[];
|
|
99
141
|
export declare function getNote(db: AbstractPowerSyncDatabase, id: string): Promise<NoteRow | null>;
|
|
100
142
|
export declare function getNoteContext(db: AbstractPowerSyncDatabase, id: string): Promise<NoteContext | null>;
|
|
@@ -116,9 +158,9 @@ export declare function createNote(db: AbstractPowerSyncDatabase, input: {
|
|
|
116
158
|
}): Promise<{
|
|
117
159
|
id: string;
|
|
118
160
|
}>;
|
|
119
|
-
export declare function validateTagsForCreate(db: AbstractPowerSyncDatabase, requestedTags: string[]): Promise<string[]>;
|
|
120
|
-
export declare function validateTagsForUpdate(db: AbstractPowerSyncDatabase, requestedTags: string[]): Promise<string[]>;
|
|
121
|
-
export declare function validateTagsForBulk(db: AbstractPowerSyncDatabase, requestedTags: string[]): Promise<string[]>;
|
|
161
|
+
export declare function validateTagsForCreate(db: AbstractPowerSyncDatabase, requestedTags: string[], userId: string): Promise<string[]>;
|
|
162
|
+
export declare function validateTagsForUpdate(db: AbstractPowerSyncDatabase, requestedTags: string[], userId: string): Promise<string[]>;
|
|
163
|
+
export declare function validateTagsForBulk(db: AbstractPowerSyncDatabase, requestedTags: string[], userId: string): Promise<string[]>;
|
|
122
164
|
export declare function parseStoredTags(raw: string | null): string[];
|
|
123
165
|
export declare function updateNote(db: AbstractPowerSyncDatabase, input: {
|
|
124
166
|
id: string;
|