@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
|
@@ -50,28 +50,7 @@ export async function syncNoteTokenIndex(db, options) {
|
|
|
50
50
|
break;
|
|
51
51
|
}
|
|
52
52
|
for (const row of rows) {
|
|
53
|
-
result
|
|
54
|
-
const sortKey = noteSortKey(row);
|
|
55
|
-
const bodySource = selectIndexBodyText(row);
|
|
56
|
-
const sourceHash = createSourceHash(row, bodySource);
|
|
57
|
-
const meta = await db.getOptional(`
|
|
58
|
-
SELECT source_hash
|
|
59
|
-
FROM note_local_index_meta
|
|
60
|
-
WHERE id = ?
|
|
61
|
-
`, [row.id]);
|
|
62
|
-
if (meta?.source_hash === sourceHash) {
|
|
63
|
-
result.skipped += 1;
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
const changed = await reindexOneNoteFts(db, row, sourceHash, bodySource, nowIso, tokenize);
|
|
67
|
-
if (changed.upserted) {
|
|
68
|
-
result.reindexed += 1;
|
|
69
|
-
result.ftsRowsUpserted += 1;
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
result.removed += 1;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
53
|
+
const sortKey = await indexNoteRow(db, row, result, nowIso, tokenize);
|
|
75
54
|
cursorUpdatedAt = sortKey;
|
|
76
55
|
cursorId = row.id;
|
|
77
56
|
}
|
|
@@ -81,12 +60,147 @@ export async function syncNoteTokenIndex(db, options) {
|
|
|
81
60
|
WHERE id = ?
|
|
82
61
|
`, [cursorUpdatedAt, cursorId, nowIso(), INDEX_STATE_ID]);
|
|
83
62
|
}
|
|
63
|
+
await reconcileSkippedRows(db, result, batchSize, nowIso, tokenize);
|
|
84
64
|
result.cursor = {
|
|
85
65
|
updatedAt: cursorUpdatedAt,
|
|
86
66
|
id: cursorId,
|
|
87
67
|
};
|
|
88
68
|
return result;
|
|
89
69
|
}
|
|
70
|
+
export async function inspectNoteTokenIndex(db) {
|
|
71
|
+
const notes = await countRows(db, `
|
|
72
|
+
SELECT COUNT(*) AS count
|
|
73
|
+
FROM c_note
|
|
74
|
+
WHERE is_del IS NULL OR is_del = 0
|
|
75
|
+
`);
|
|
76
|
+
const metaRows = await countRowsIfAvailable(db, `
|
|
77
|
+
SELECT COUNT(*) AS count
|
|
78
|
+
FROM note_local_index_meta
|
|
79
|
+
`);
|
|
80
|
+
const ftsRows = await countRowsIfAvailable(db, `
|
|
81
|
+
SELECT COUNT(DISTINCT note_id) AS count
|
|
82
|
+
FROM ${NOTE_LOCAL_FTS_TABLE}
|
|
83
|
+
`);
|
|
84
|
+
if (ftsRows === null) {
|
|
85
|
+
return {
|
|
86
|
+
notes,
|
|
87
|
+
ftsRows: 0,
|
|
88
|
+
metaRows: metaRows ?? 0,
|
|
89
|
+
missingRows: notes,
|
|
90
|
+
extraRows: 0,
|
|
91
|
+
drift: notes,
|
|
92
|
+
ftsAvailable: false,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const missingRows = await countRows(db, `
|
|
96
|
+
SELECT COUNT(*) AS count
|
|
97
|
+
FROM c_note n
|
|
98
|
+
LEFT JOIN ${NOTE_LOCAL_FTS_TABLE} f ON f.note_id = n.id
|
|
99
|
+
WHERE (n.is_del IS NULL OR n.is_del = 0)
|
|
100
|
+
AND f.note_id IS NULL
|
|
101
|
+
`);
|
|
102
|
+
const extraRows = await countRows(db, `
|
|
103
|
+
SELECT COUNT(DISTINCT f.note_id) AS count
|
|
104
|
+
FROM ${NOTE_LOCAL_FTS_TABLE} f
|
|
105
|
+
LEFT JOIN c_note n
|
|
106
|
+
ON n.id = f.note_id
|
|
107
|
+
AND (n.is_del IS NULL OR n.is_del = 0)
|
|
108
|
+
WHERE n.id IS NULL
|
|
109
|
+
`);
|
|
110
|
+
return {
|
|
111
|
+
notes,
|
|
112
|
+
ftsRows,
|
|
113
|
+
metaRows: metaRows ?? 0,
|
|
114
|
+
missingRows,
|
|
115
|
+
extraRows,
|
|
116
|
+
drift: missingRows + extraRows,
|
|
117
|
+
ftsAvailable: true,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
export async function rebuildNoteTokenIndex(db, options) {
|
|
121
|
+
await ensureFtsTable(db);
|
|
122
|
+
await db.execute(`DELETE FROM ${NOTE_LOCAL_FTS_TABLE}`, []);
|
|
123
|
+
await db.execute('DELETE FROM note_local_index_meta', []);
|
|
124
|
+
await db.execute('DELETE FROM note_local_index_state WHERE id = ?', [INDEX_STATE_ID]);
|
|
125
|
+
return syncNoteTokenIndex(db, options);
|
|
126
|
+
}
|
|
127
|
+
async function indexNoteRow(db, row, result, nowIso, tokenize) {
|
|
128
|
+
result.scanned += 1;
|
|
129
|
+
const sortKey = noteSortKey(row);
|
|
130
|
+
const bodySource = selectIndexBodyText(row);
|
|
131
|
+
const sourceHash = createSourceHash(row, bodySource);
|
|
132
|
+
const deleted = isDeleted(row.is_del);
|
|
133
|
+
const meta = await db.getOptional(`
|
|
134
|
+
SELECT source_hash
|
|
135
|
+
FROM note_local_index_meta
|
|
136
|
+
WHERE id = ?
|
|
137
|
+
`, [row.id]);
|
|
138
|
+
if (meta?.source_hash === sourceHash) {
|
|
139
|
+
await db.execute(`
|
|
140
|
+
UPDATE note_local_index_meta
|
|
141
|
+
SET note_updated_at = ?, is_del = ?, indexed_at = ?
|
|
142
|
+
WHERE id = ?
|
|
143
|
+
`, [sortKey, deleted ? 1 : 0, nowIso(), row.id]);
|
|
144
|
+
result.skipped += 1;
|
|
145
|
+
return sortKey;
|
|
146
|
+
}
|
|
147
|
+
const changed = await reindexOneNoteFts(db, row, sourceHash, bodySource, nowIso, tokenize);
|
|
148
|
+
if (changed.upserted) {
|
|
149
|
+
result.reindexed += 1;
|
|
150
|
+
result.ftsRowsUpserted += 1;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
result.removed += 1;
|
|
154
|
+
}
|
|
155
|
+
return sortKey;
|
|
156
|
+
}
|
|
157
|
+
async function reconcileSkippedRows(db, result, batchSize, nowIso, tokenize) {
|
|
158
|
+
while (true) {
|
|
159
|
+
const rows = await db.getAll(`
|
|
160
|
+
SELECT
|
|
161
|
+
n.id,
|
|
162
|
+
n.title,
|
|
163
|
+
n.content_md,
|
|
164
|
+
n.content_text,
|
|
165
|
+
n.tags,
|
|
166
|
+
n.created_at,
|
|
167
|
+
n.updated_at,
|
|
168
|
+
n.is_del,
|
|
169
|
+
n.version
|
|
170
|
+
FROM c_note n
|
|
171
|
+
LEFT JOIN note_local_index_meta m ON m.id = n.id
|
|
172
|
+
WHERE
|
|
173
|
+
m.id IS NULL
|
|
174
|
+
OR COALESCE(m.note_updated_at, '') <> COALESCE(n.updated_at, n.created_at, '')
|
|
175
|
+
OR COALESCE(m.is_del, 0) <> CASE WHEN n.is_del IS NOT NULL AND n.is_del <> 0 THEN 1 ELSE 0 END
|
|
176
|
+
ORDER BY COALESCE(n.updated_at, n.created_at, '') ASC, n.id ASC
|
|
177
|
+
LIMIT ?
|
|
178
|
+
`, [batchSize]);
|
|
179
|
+
if (rows.length === 0) {
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
for (const row of rows) {
|
|
183
|
+
await indexNoteRow(db, row, result, nowIso, tokenize);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
while (true) {
|
|
187
|
+
const rows = await db.getAll(`
|
|
188
|
+
SELECT m.id
|
|
189
|
+
FROM note_local_index_meta m
|
|
190
|
+
LEFT JOIN c_note n ON n.id = m.id
|
|
191
|
+
WHERE n.id IS NULL
|
|
192
|
+
LIMIT ?
|
|
193
|
+
`, [batchSize]);
|
|
194
|
+
if (rows.length === 0) {
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
for (const row of rows) {
|
|
198
|
+
await db.execute(`DELETE FROM ${NOTE_LOCAL_FTS_TABLE} WHERE note_id = ?`, [row.id]);
|
|
199
|
+
await db.execute('DELETE FROM note_local_index_meta WHERE id = ?', [row.id]);
|
|
200
|
+
result.removed += 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
90
204
|
async function ensureFtsTable(db) {
|
|
91
205
|
try {
|
|
92
206
|
await db.execute(`
|
|
@@ -105,6 +219,29 @@ async function ensureFtsTable(db) {
|
|
|
105
219
|
throw new Error(`Failed to initialize FTS5 table (${NOTE_LOCAL_FTS_TABLE}): ${message}`);
|
|
106
220
|
}
|
|
107
221
|
}
|
|
222
|
+
function isMissingLocalIndexTableError(error) {
|
|
223
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
224
|
+
return message.toLowerCase().includes('no such table');
|
|
225
|
+
}
|
|
226
|
+
function normalizeCount(value) {
|
|
227
|
+
const parsed = Number(value ?? 0);
|
|
228
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.trunc(parsed) : 0;
|
|
229
|
+
}
|
|
230
|
+
async function countRows(db, sql, params = []) {
|
|
231
|
+
const row = await db.getOptional(sql, params);
|
|
232
|
+
return normalizeCount(row?.count);
|
|
233
|
+
}
|
|
234
|
+
async function countRowsIfAvailable(db, sql, params = []) {
|
|
235
|
+
try {
|
|
236
|
+
return await countRows(db, sql, params);
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
if (isMissingLocalIndexTableError(error)) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
108
245
|
async function ensureIndexState(db, nowIso) {
|
|
109
246
|
const state = await db.getOptional(`
|
|
110
247
|
SELECT cursor_updated_at, cursor_id
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { UpdateType } from '@powersync/common';
|
|
2
2
|
import { createTimeoutSignal, mergeAbortSignals } from '../utils/abort.js';
|
|
3
3
|
import { toAuthorizationHeaderValue } from '../utils/authToken.js';
|
|
4
|
+
import { sanitizeForTerminal } from '../utils/terminalSanitization.js';
|
|
4
5
|
const FATAL_RESPONSE_CODES = [/^22...$/, /^23...$/, /^42501$/];
|
|
6
|
+
const legacyUploadTargets = new Set();
|
|
5
7
|
function shouldFallbackToLegacy(status) {
|
|
6
8
|
return status === 404 || status === 405;
|
|
7
9
|
}
|
|
8
10
|
function normalizeBaseUrl(baseUrl) {
|
|
9
11
|
return baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
10
12
|
}
|
|
13
|
+
function legacyUploadTargetKey(baseUrl, uploadV4Path) {
|
|
14
|
+
return `${normalizeBaseUrl(baseUrl)}${uploadV4Path}`;
|
|
15
|
+
}
|
|
11
16
|
async function readErrorBody(response) {
|
|
12
17
|
const contentType = response.headers.get('content-type') ?? '';
|
|
13
|
-
const text = await response.text()
|
|
18
|
+
const text = await response.text();
|
|
14
19
|
if (contentType.includes('application/json')) {
|
|
15
20
|
try {
|
|
16
21
|
const json = JSON.parse(text);
|
|
@@ -43,17 +48,60 @@ function isFatalUploadError(error) {
|
|
|
43
48
|
if (!error || typeof error !== 'object') {
|
|
44
49
|
return false;
|
|
45
50
|
}
|
|
46
|
-
const
|
|
47
|
-
|
|
51
|
+
const uploadError = error;
|
|
52
|
+
if (uploadError.fatal === true) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return typeof uploadError.code === 'string'
|
|
56
|
+
&& FATAL_RESPONSE_CODES.some(regex => regex.test(uploadError.code));
|
|
57
|
+
}
|
|
58
|
+
function createUnsupportedCrudOperationError(op) {
|
|
59
|
+
const error = new Error(`Unsupported PowerSync CRUD operation: ${String(op.op)} (${String(op.table)}:${String(op.id ?? '')})`);
|
|
60
|
+
error.name = 'PowerSyncUploadError';
|
|
61
|
+
error.code = 'UNSUPPORTED_CRUD_OPERATION';
|
|
62
|
+
error.fatal = true;
|
|
63
|
+
return error;
|
|
64
|
+
}
|
|
65
|
+
function summarizeCrudOperation(op) {
|
|
66
|
+
const opName = UpdateType[op.op] ?? String(op.op);
|
|
67
|
+
return {
|
|
68
|
+
id: String(op.id ?? ''),
|
|
69
|
+
op: opName,
|
|
70
|
+
table: String(op.table ?? ''),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function attachFatalContext(error, transaction, failedIndex) {
|
|
74
|
+
if (!isFatalUploadError(error)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
error.fatalContext = {
|
|
78
|
+
failedIndex,
|
|
79
|
+
operations: transaction.crud.map((op) => summarizeCrudOperation(op)),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function logFatalUploadFailure(error) {
|
|
83
|
+
const context = error.fatalContext;
|
|
84
|
+
if (!context) {
|
|
85
|
+
process.stderr.write(`${sanitizeForTerminal(`warning: fatal PowerSync upload error (${error.code ?? error.status ?? 'unknown'}); queued operations were retained.`)}\n`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const failedOperation = context.operations[context.failedIndex];
|
|
89
|
+
const failedOperationText = failedOperation
|
|
90
|
+
? `${failedOperation.op}:${failedOperation.table}:${failedOperation.id || '(no-id)'}`
|
|
91
|
+
: `index ${context.failedIndex}`;
|
|
92
|
+
const succeededBeforeFailure = Math.min(context.failedIndex, context.operations.length);
|
|
93
|
+
const replayWarning = succeededBeforeFailure > 0
|
|
94
|
+
? ` ${succeededBeforeFailure} earlier operation(s) already returned success and may be sent again on retry; the legacy upload endpoints do not provide a transaction-level acknowledgement.`
|
|
95
|
+
: '';
|
|
96
|
+
process.stderr.write(`${sanitizeForTerminal(`warning: fatal PowerSync upload error (${error.code ?? error.status ?? 'unknown'}) at ${failedOperationText}; PowerSync retained the entire ${context.operations.length}-operation transaction.${replayWarning}`)}\n`);
|
|
48
97
|
}
|
|
49
98
|
async function dispatchMutation(options) {
|
|
50
99
|
const timeout = typeof options.timeoutMs === 'number' && Number.isFinite(options.timeoutMs) && options.timeoutMs > 0
|
|
51
100
|
? createTimeoutSignal(options.timeoutMs)
|
|
52
101
|
: null;
|
|
53
102
|
const requestSignal = mergeAbortSignals([options.signal, timeout?.signal]);
|
|
54
|
-
let response;
|
|
55
103
|
try {
|
|
56
|
-
response = await fetch(options.url, {
|
|
104
|
+
const response = await fetch(options.url, {
|
|
57
105
|
method: options.method,
|
|
58
106
|
headers: {
|
|
59
107
|
Authorization: toAuthorizationHeaderValue(options.authorization),
|
|
@@ -62,6 +110,19 @@ async function dispatchMutation(options) {
|
|
|
62
110
|
body: JSON.stringify(options.payload),
|
|
63
111
|
signal: requestSignal,
|
|
64
112
|
});
|
|
113
|
+
if (response.ok) {
|
|
114
|
+
await response.arrayBuffer();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const body = await readErrorBody(response);
|
|
118
|
+
throw createUploadError({
|
|
119
|
+
status: response.status,
|
|
120
|
+
method: options.method,
|
|
121
|
+
url: options.url,
|
|
122
|
+
code: body.code,
|
|
123
|
+
message: body.message,
|
|
124
|
+
text: body.text,
|
|
125
|
+
});
|
|
65
126
|
}
|
|
66
127
|
catch (error) {
|
|
67
128
|
if (timeout?.signal.aborted && !options.signal?.aborted) {
|
|
@@ -75,57 +136,50 @@ async function dispatchMutation(options) {
|
|
|
75
136
|
finally {
|
|
76
137
|
timeout?.cancel();
|
|
77
138
|
}
|
|
78
|
-
if (response.ok) {
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
const body = await readErrorBody(response);
|
|
82
|
-
throw createUploadError({
|
|
83
|
-
status: response.status,
|
|
84
|
-
method: options.method,
|
|
85
|
-
url: options.url,
|
|
86
|
-
code: body.code,
|
|
87
|
-
message: body.message,
|
|
88
|
-
text: body.text,
|
|
89
|
-
});
|
|
90
139
|
}
|
|
91
140
|
async function uploadTransaction(args) {
|
|
92
141
|
const baseUrl = normalizeBaseUrl(args.uploadBaseUrl);
|
|
93
142
|
const primaryUrl = `${baseUrl}${args.uploadV4Path}`;
|
|
94
143
|
const fallbackUrl = `${baseUrl}${args.uploadV2Path}`;
|
|
95
|
-
|
|
144
|
+
const primaryKey = legacyUploadTargetKey(baseUrl, args.uploadV4Path);
|
|
145
|
+
const dispatchPutOrPatch = async (method, payload) => {
|
|
146
|
+
if (legacyUploadTargets.has(primaryKey)) {
|
|
147
|
+
await dispatchMutation({ authorization: args.authorization, url: fallbackUrl, method, payload, timeoutMs: args.timeoutMs });
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
await dispatchMutation({ authorization: args.authorization, url: primaryUrl, method, payload, timeoutMs: args.timeoutMs });
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (shouldFallbackToLegacy(error?.status)) {
|
|
155
|
+
legacyUploadTargets.add(primaryKey);
|
|
156
|
+
await dispatchMutation({ authorization: args.authorization, url: fallbackUrl, method, payload, timeoutMs: args.timeoutMs });
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
for (let index = 0; index < args.transaction.crud.length; index += 1) {
|
|
163
|
+
const op = args.transaction.crud[index];
|
|
96
164
|
const record = { table: op.table, data: { ...(op.opData ?? {}), id: op.id } };
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
await
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
catch (error) {
|
|
116
|
-
if (shouldFallbackToLegacy(error?.status)) {
|
|
117
|
-
await dispatchMutation({ authorization: args.authorization, url: fallbackUrl, method: 'PATCH', payload: record, timeoutMs: args.timeoutMs });
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
throw error;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
break;
|
|
124
|
-
case UpdateType.DELETE:
|
|
125
|
-
await dispatchMutation({ authorization: args.authorization, url: fallbackUrl, method: 'DELETE', payload: record, timeoutMs: args.timeoutMs });
|
|
126
|
-
break;
|
|
127
|
-
default:
|
|
128
|
-
break;
|
|
165
|
+
try {
|
|
166
|
+
switch (op.op) {
|
|
167
|
+
case UpdateType.PUT:
|
|
168
|
+
await dispatchPutOrPatch('PUT', record);
|
|
169
|
+
break;
|
|
170
|
+
case UpdateType.PATCH:
|
|
171
|
+
await dispatchPutOrPatch('PATCH', record);
|
|
172
|
+
break;
|
|
173
|
+
case UpdateType.DELETE:
|
|
174
|
+
await dispatchMutation({ authorization: args.authorization, url: fallbackUrl, method: 'DELETE', payload: record, timeoutMs: args.timeoutMs });
|
|
175
|
+
break;
|
|
176
|
+
default:
|
|
177
|
+
throw createUnsupportedCrudOperationError(op);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
attachFatalContext(error, args.transaction, index);
|
|
182
|
+
throw error;
|
|
129
183
|
}
|
|
130
184
|
}
|
|
131
185
|
}
|
|
@@ -148,8 +202,9 @@ export function createPowerSyncUploadHandler(options) {
|
|
|
148
202
|
}
|
|
149
203
|
catch (error) {
|
|
150
204
|
if (isFatalUploadError(error)) {
|
|
151
|
-
|
|
152
|
-
|
|
205
|
+
// v4/v2 acknowledge individual operations, so completing here would discard
|
|
206
|
+
// the failed suffix; retaining the transaction can replay an accepted prefix.
|
|
207
|
+
logFatalUploadFailure(error);
|
|
153
208
|
}
|
|
154
209
|
throw error;
|
|
155
210
|
}
|
package/dist/utils/abort.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
export function createTimeoutSignal(timeoutMs) {
|
|
2
2
|
const controller = new AbortController();
|
|
3
3
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
4
|
-
// Best-effort: don't keep the process alive solely for the timeout.
|
|
5
|
-
if (typeof timer.unref === 'function') {
|
|
6
|
-
timer.unref();
|
|
7
|
-
}
|
|
8
4
|
return {
|
|
9
5
|
signal: controller.signal,
|
|
10
6
|
cancel: () => clearTimeout(timer),
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export async function withAsyncRelease(acquire, run) {
|
|
2
|
+
const release = await acquire();
|
|
3
|
+
let operationFailed = false;
|
|
4
|
+
let operationError;
|
|
5
|
+
let value;
|
|
6
|
+
try {
|
|
7
|
+
value = await run();
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
operationFailed = true;
|
|
11
|
+
operationError = error;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
await release();
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (!operationFailed) {
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (operationFailed) {
|
|
22
|
+
throw operationError;
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
@@ -151,6 +151,9 @@ export async function canonicalizeBidlinksInMarkdown(db, markdown, userId) {
|
|
|
151
151
|
if (!resolvedId) {
|
|
152
152
|
continue;
|
|
153
153
|
}
|
|
154
|
+
if (nextMarkdown.slice(occurrence.startOffset, occurrence.endOffset) !== occurrence.raw) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
154
157
|
const replacement = `[[${escapeBidlinkValue(occurrence.label)}|${escapeBidlinkValue(resolvedId)}]]`;
|
|
155
158
|
nextMarkdown =
|
|
156
159
|
nextMarkdown.slice(0, occurrence.startOffset) +
|
package/dist/utils/dryRun.d.ts
CHANGED
package/dist/utils/errors.d.ts
CHANGED
|
@@ -4,24 +4,38 @@ export type DinoxErrorOptions = {
|
|
|
4
4
|
details?: unknown;
|
|
5
5
|
code?: string;
|
|
6
6
|
suggestion?: string;
|
|
7
|
+
suggestedAction?: SuggestedAction;
|
|
7
8
|
recoverable?: boolean;
|
|
8
9
|
};
|
|
10
|
+
export type SuggestedAction = {
|
|
11
|
+
command: string;
|
|
12
|
+
reason: string;
|
|
13
|
+
};
|
|
9
14
|
export type StructuredErrorPayload = {
|
|
10
15
|
name: string;
|
|
11
16
|
code: string;
|
|
12
17
|
message: string;
|
|
13
18
|
recoverable: boolean;
|
|
19
|
+
exit_code: number;
|
|
20
|
+
cause?: {
|
|
21
|
+
name: string;
|
|
22
|
+
message: string;
|
|
23
|
+
stack?: string;
|
|
24
|
+
};
|
|
14
25
|
suggestion?: string;
|
|
26
|
+
suggested_action?: SuggestedAction;
|
|
15
27
|
details?: unknown;
|
|
16
28
|
stack?: string;
|
|
17
29
|
};
|
|
18
30
|
export declare class DinoxError extends Error {
|
|
19
31
|
readonly exitCode: number;
|
|
20
32
|
readonly details?: unknown;
|
|
21
|
-
readonly code
|
|
33
|
+
readonly code: string;
|
|
22
34
|
readonly suggestion?: string;
|
|
35
|
+
readonly suggestedAction?: SuggestedAction;
|
|
23
36
|
readonly recoverable: boolean;
|
|
24
37
|
constructor(message: string, options?: DinoxErrorOptions);
|
|
25
38
|
}
|
|
26
39
|
export declare function isDinoxError(error: unknown): error is DinoxError;
|
|
27
40
|
export declare function buildStructuredErrorPayload(error: unknown, verbose: boolean): StructuredErrorPayload;
|
|
41
|
+
export declare function exitCodeForErrorCode(code: string): number;
|