@dinoxx/dinox-cli 1.0.13 → 1.0.15
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/dist/auth/userInfo.d.ts +1 -0
- package/dist/auth/userInfo.js +29 -10
- package/dist/commands/auth/index.js +16 -19
- package/dist/commands/boxes/index.js +1 -10
- package/dist/commands/info/index.js +3 -0
- package/dist/commands/notes/index.d.ts +39 -0
- package/dist/commands/notes/index.js +61 -12
- package/dist/commands/prompt/index.js +1 -10
- package/dist/commands/sync.js +1 -11
- package/dist/commands/tags/index.js +1 -10
- package/dist/commands/todo/index.js +1 -10
- package/dist/commands/update.d.ts +10 -1
- package/dist/commands/update.js +22 -6
- package/dist/config/resolve.js +3 -2
- package/dist/config/store.js +17 -2
- package/dist/config/types.d.ts +1 -2
- package/dist/constants/links.d.ts +1 -0
- package/dist/constants/links.js +1 -0
- package/dist/powersync/connector.d.ts +2 -1
- package/dist/powersync/connector.js +27 -9
- package/dist/powersync/runtime.js +4 -9
- package/dist/powersync/uploader.d.ts +1 -0
- package/dist/powersync/uploader.js +36 -13
- package/dist/utils/abort.d.ts +6 -0
- package/dist/utils/abort.js +23 -0
- package/dist/utils/authToken.d.ts +2 -0
- package/dist/utils/authToken.js +23 -0
- package/dist/utils/syncTimeout.d.ts +1 -0
- package/dist/utils/syncTimeout.js +19 -0
- package/package.json +1 -1
package/dist/auth/userInfo.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export type AuthIdentity = {
|
|
|
8
8
|
export declare function fetchUserInfoWithAuthorization(input: {
|
|
9
9
|
authorization: string;
|
|
10
10
|
config: ResolvedDinoxConfig;
|
|
11
|
+
timeoutMs?: number;
|
|
11
12
|
signal?: AbortSignal;
|
|
12
13
|
}): Promise<AuthIdentity>;
|
|
13
14
|
export declare function refreshAndPersistAuthIdentity(rawConfig: DinoxConfig, resolvedConfig: ResolvedDinoxConfig): Promise<AuthIdentity>;
|
package/dist/auth/userInfo.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { saveConfig } from '../config/store.js';
|
|
2
2
|
import { DinoxError } from '../utils/errors.js';
|
|
3
|
+
import { createTimeoutSignal, mergeAbortSignals } from '../utils/abort.js';
|
|
4
|
+
import { toAuthorizationHeaderValue } from '../utils/authToken.js';
|
|
3
5
|
const AUTH_CODE_OK = '000000';
|
|
4
6
|
function normalizeOptionalString(value) {
|
|
5
7
|
if (typeof value !== 'string') {
|
|
@@ -59,17 +61,34 @@ function resolveUserInfoEndpoint(config) {
|
|
|
59
61
|
export async function fetchUserInfoWithAuthorization(input) {
|
|
60
62
|
const authorization = normalizeOptionalString(input.authorization);
|
|
61
63
|
if (!authorization) {
|
|
62
|
-
throw new DinoxError('
|
|
64
|
+
throw new DinoxError('Token is required for user info');
|
|
63
65
|
}
|
|
64
66
|
const endpoint = resolveUserInfoEndpoint(input.config);
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
const effectiveTimeoutMs = input.timeoutMs ?? input.config.sync.timeoutMs;
|
|
68
|
+
const timeout = typeof effectiveTimeoutMs === 'number' && Number.isFinite(effectiveTimeoutMs) && effectiveTimeoutMs > 0
|
|
69
|
+
? createTimeoutSignal(effectiveTimeoutMs)
|
|
70
|
+
: null;
|
|
71
|
+
const requestSignal = mergeAbortSignals([input.signal, timeout?.signal]);
|
|
72
|
+
let response;
|
|
73
|
+
try {
|
|
74
|
+
response = await fetch(endpoint, {
|
|
75
|
+
method: 'GET',
|
|
76
|
+
headers: {
|
|
77
|
+
Authorization: toAuthorizationHeaderValue(authorization),
|
|
78
|
+
'Content-Type': 'application/json',
|
|
79
|
+
},
|
|
80
|
+
signal: requestSignal,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (timeout?.signal.aborted && !input.signal?.aborted) {
|
|
85
|
+
throw new DinoxError(`User info request timed out after ${effectiveTimeoutMs}ms`);
|
|
86
|
+
}
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
timeout?.cancel();
|
|
91
|
+
}
|
|
73
92
|
if (!response.ok) {
|
|
74
93
|
const body = await response.text().catch(() => '');
|
|
75
94
|
throw new DinoxError(`User info request failed (${response.status}): ${body || endpoint}`);
|
|
@@ -80,7 +99,7 @@ export async function fetchUserInfoWithAuthorization(input) {
|
|
|
80
99
|
export async function refreshAndPersistAuthIdentity(rawConfig, resolvedConfig) {
|
|
81
100
|
const authorization = normalizeOptionalString(resolvedConfig.auth.authorization);
|
|
82
101
|
if (!authorization) {
|
|
83
|
-
throw new DinoxError('
|
|
102
|
+
throw new DinoxError('Token is required to fetch user info');
|
|
84
103
|
}
|
|
85
104
|
const identity = await fetchUserInfoWithAuthorization({
|
|
86
105
|
authorization,
|
|
@@ -5,20 +5,12 @@ import { getPowerSyncDbPath } from '../../config/paths.js';
|
|
|
5
5
|
import { redactAuthorization } from '../../utils/redact.js';
|
|
6
6
|
import { DinoxError } from '../../utils/errors.js';
|
|
7
7
|
import { printYaml } from '../../utils/output.js';
|
|
8
|
+
import { parseSyncTimeoutMs } from '../../utils/syncTimeout.js';
|
|
9
|
+
import { normalizeAuthToken } from '../../utils/authToken.js';
|
|
8
10
|
import { refreshAndPersistAuthIdentity } from '../../auth/userInfo.js';
|
|
9
11
|
import { connectPowerSync, getStatusSnapshot } from '../../powersync/runtime.js';
|
|
10
12
|
import { fetchCredentialsWithAuthorization } from '../../powersync/connector.js';
|
|
11
13
|
import { runSingleSyncPass } from '../../powersync/syncPass.js';
|
|
12
|
-
function parseSyncTimeoutMs(value) {
|
|
13
|
-
if (typeof value !== 'string') {
|
|
14
|
-
return undefined;
|
|
15
|
-
}
|
|
16
|
-
const parsed = Number(value);
|
|
17
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
18
|
-
throw new DinoxError('--sync-timeout must be a positive integer');
|
|
19
|
-
}
|
|
20
|
-
return Math.trunc(parsed);
|
|
21
|
-
}
|
|
22
14
|
async function removeSqliteArtifacts(dbPath) {
|
|
23
15
|
const artifacts = ['', '-wal', '-shm'];
|
|
24
16
|
for (const suffix of artifacts) {
|
|
@@ -31,15 +23,15 @@ export function registerAuthCommands(program) {
|
|
|
31
23
|
.description('Authentication and PowerSync connectivity commands');
|
|
32
24
|
auth
|
|
33
25
|
.command('login')
|
|
34
|
-
.description('Save
|
|
35
|
-
.argument('<
|
|
36
|
-
.action(async (
|
|
37
|
-
const
|
|
38
|
-
if (!
|
|
39
|
-
throw new DinoxError('
|
|
26
|
+
.description('Save login token and verify PowerSync connectivity')
|
|
27
|
+
.argument('<token>', 'login token (pass the raw token string, without Bearer prefix)')
|
|
28
|
+
.action(async (token, _options, command) => {
|
|
29
|
+
const rawToken = normalizeAuthToken(token);
|
|
30
|
+
if (!rawToken) {
|
|
31
|
+
throw new DinoxError('Token is required');
|
|
40
32
|
}
|
|
41
33
|
const config = await loadConfig();
|
|
42
|
-
config.auth = { ...(config.auth ?? {}), authorization:
|
|
34
|
+
config.auth = { ...(config.auth ?? {}), authorization: rawToken };
|
|
43
35
|
await saveConfig(config);
|
|
44
36
|
const resolved = resolveConfig(config);
|
|
45
37
|
const globals = command.optsWithGlobals?.() ?? {};
|
|
@@ -47,9 +39,10 @@ export function registerAuthCommands(program) {
|
|
|
47
39
|
const jsonOutput = Boolean(globals.json);
|
|
48
40
|
// 1) Token exchange sanity check.
|
|
49
41
|
await fetchCredentialsWithAuthorization({
|
|
50
|
-
authorization:
|
|
42
|
+
authorization: rawToken,
|
|
51
43
|
endpoint: resolved.powersync.endpoint,
|
|
52
44
|
credentialsUrl: resolved.powersync.tokenEndpoint,
|
|
45
|
+
timeoutMs,
|
|
53
46
|
});
|
|
54
47
|
// 1.5) Fetch and persist user identity.
|
|
55
48
|
const identity = await refreshAndPersistAuthIdentity(config, resolved);
|
|
@@ -93,12 +86,16 @@ export function registerAuthCommands(program) {
|
|
|
93
86
|
});
|
|
94
87
|
auth
|
|
95
88
|
.command('logout')
|
|
96
|
-
.description('Clear saved
|
|
89
|
+
.description('Clear saved login token')
|
|
97
90
|
.option('--clear-local-db', 'delete the local PowerSync SQLite database')
|
|
98
91
|
.action(async (options) => {
|
|
99
92
|
const config = await loadConfig();
|
|
100
93
|
if (config.auth) {
|
|
101
94
|
config.auth.authorization = undefined;
|
|
95
|
+
config.auth.userId = undefined;
|
|
96
|
+
config.auth.username = undefined;
|
|
97
|
+
config.auth.email = undefined;
|
|
98
|
+
config.auth.userInfoSyncedAt = undefined;
|
|
102
99
|
}
|
|
103
100
|
await saveConfig(config);
|
|
104
101
|
if (options.clearLocalDb) {
|
|
@@ -2,20 +2,11 @@ import { loadConfig } from '../../config/store.js';
|
|
|
2
2
|
import { resolveConfig } from '../../config/resolve.js';
|
|
3
3
|
import { DinoxError } from '../../utils/errors.js';
|
|
4
4
|
import { printYaml } from '../../utils/output.js';
|
|
5
|
+
import { parseSyncTimeoutMs } from '../../utils/syncTimeout.js';
|
|
5
6
|
import { connectPowerSync, waitForIdleDataFlow } from '../../powersync/runtime.js';
|
|
6
7
|
import { syncNoteTokenIndex } from '../../powersync/tokenIndex.js';
|
|
7
8
|
import { resolveAuthIdentity } from '../../auth/userInfo.js';
|
|
8
9
|
import { addBox, listBoxes } from './repo.js';
|
|
9
|
-
function parseSyncTimeoutMs(value) {
|
|
10
|
-
if (typeof value !== 'string') {
|
|
11
|
-
return undefined;
|
|
12
|
-
}
|
|
13
|
-
const parsed = Number(value);
|
|
14
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
15
|
-
throw new DinoxError('--sync-timeout must be a positive integer');
|
|
16
|
-
}
|
|
17
|
-
return Math.trunc(parsed);
|
|
18
|
-
}
|
|
19
10
|
async function syncBeforeBoxCommand(db, offline, timeoutMs) {
|
|
20
11
|
if (offline) {
|
|
21
12
|
return;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SKILLS_REPO_URL } from '../../constants/links.js';
|
|
1
2
|
import { printYaml } from '../../utils/output.js';
|
|
2
3
|
import { getPackageVersion } from '../../utils/version.js';
|
|
3
4
|
export function registerInfoCommand(program) {
|
|
@@ -10,11 +11,13 @@ export function registerInfoCommand(program) {
|
|
|
10
11
|
const payload = {
|
|
11
12
|
name: 'dino',
|
|
12
13
|
version: getPackageVersion(),
|
|
14
|
+
skills_repo: SKILLS_REPO_URL,
|
|
13
15
|
};
|
|
14
16
|
if (jsonOutput) {
|
|
15
17
|
printYaml(payload);
|
|
16
18
|
return;
|
|
17
19
|
}
|
|
18
20
|
console.log(payload.version);
|
|
21
|
+
console.log(`skills repo: ${payload.skills_repo}`);
|
|
19
22
|
});
|
|
20
23
|
}
|
|
@@ -1,2 +1,41 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import { type SearchedNote } from './repo.js';
|
|
3
|
+
type NoteSearchResponse = {
|
|
4
|
+
meta: {
|
|
5
|
+
query: string;
|
|
6
|
+
returned_count: number;
|
|
7
|
+
total_count: number;
|
|
8
|
+
stale: boolean;
|
|
9
|
+
applied_filters: {
|
|
10
|
+
include_deleted: boolean;
|
|
11
|
+
tags_expression: string | null;
|
|
12
|
+
created_at_range: {
|
|
13
|
+
from: string | null;
|
|
14
|
+
to: string | null;
|
|
15
|
+
} | null;
|
|
16
|
+
days: number | null;
|
|
17
|
+
box_names: string[];
|
|
18
|
+
box_ids: string[];
|
|
19
|
+
match_empty_boxes: boolean;
|
|
20
|
+
sql_expression: string | null;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
notes: SearchedNote[];
|
|
24
|
+
};
|
|
25
|
+
type BuildNoteSearchResponseInput = {
|
|
26
|
+
query: string;
|
|
27
|
+
notes: SearchedNote[];
|
|
28
|
+
stale: boolean;
|
|
29
|
+
includeDeleted: boolean;
|
|
30
|
+
tagsExpression?: string;
|
|
31
|
+
createdAtFrom?: string;
|
|
32
|
+
createdAtTo?: string;
|
|
33
|
+
days?: number;
|
|
34
|
+
boxNames: string[];
|
|
35
|
+
boxIds: string[];
|
|
36
|
+
matchEmptyBoxes: boolean;
|
|
37
|
+
sqlExpression?: string;
|
|
38
|
+
};
|
|
39
|
+
export declare function buildNoteSearchResponse(input: BuildNoteSearchResponseInput): NoteSearchResponse;
|
|
2
40
|
export declare function registerNoteCommands(program: Command): void;
|
|
41
|
+
export {};
|
|
@@ -5,21 +5,12 @@ import { readStringOrFile } from '../../utils/argValue.js';
|
|
|
5
5
|
import { generateId } from '../../utils/id.js';
|
|
6
6
|
import { convertMarkdownToNoteContent } from '../../utils/tiptapMarkdown.js';
|
|
7
7
|
import { printYaml } from '../../utils/output.js';
|
|
8
|
+
import { parseSyncTimeoutMs } from '../../utils/syncTimeout.js';
|
|
8
9
|
import { resolveAuthIdentity } from '../../auth/userInfo.js';
|
|
9
10
|
import { connectPowerSync, waitForIdleDataFlow } from '../../powersync/runtime.js';
|
|
10
11
|
import { syncNoteTokenIndex } from '../../powersync/tokenIndex.js';
|
|
11
12
|
import { createNote, getNote, getNoteDetails, assertNotesUpdatable, resolveZettelBoxIdsForCreate, resolveZettelBoxIdsForSearch, resolveZettelBoxIdsForUpdate, searchNotes, softDeleteNote, updateNote, validateTagsForCreate, validateTagsForUpdate, } from './repo.js';
|
|
12
13
|
import { parseCreatedAtRange } from './searchTime.js';
|
|
13
|
-
function parseSyncTimeoutMs(value) {
|
|
14
|
-
if (typeof value !== 'string') {
|
|
15
|
-
return undefined;
|
|
16
|
-
}
|
|
17
|
-
const parsed = Number(value);
|
|
18
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
19
|
-
throw new DinoxError('--sync-timeout must be a positive integer');
|
|
20
|
-
}
|
|
21
|
-
return Math.trunc(parsed);
|
|
22
|
-
}
|
|
23
14
|
function parseNoteType(value) {
|
|
24
15
|
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
25
16
|
if (normalized === 'note' || normalized === 'crawl') {
|
|
@@ -105,6 +96,50 @@ async function syncBeforeNoteCommand(db, offline, timeoutMs) {
|
|
|
105
96
|
await waitForIdleDataFlow(db, Math.min(timeoutMs, 10_000));
|
|
106
97
|
await syncNoteTokenIndex(db);
|
|
107
98
|
}
|
|
99
|
+
function normalizeDaysForMeta(value) {
|
|
100
|
+
if (typeof value !== 'string') {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
const trimmed = value.trim();
|
|
104
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
const parsed = Number(trimmed);
|
|
108
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
return Math.trunc(parsed);
|
|
112
|
+
}
|
|
113
|
+
export function buildNoteSearchResponse(input) {
|
|
114
|
+
const query = input.query.trim();
|
|
115
|
+
const tagsExpression = input.tagsExpression?.trim() ?? '';
|
|
116
|
+
const sqlExpression = input.sqlExpression?.trim() ?? '';
|
|
117
|
+
const hasCreatedAtRange = Boolean(input.createdAtFrom || input.createdAtTo);
|
|
118
|
+
return {
|
|
119
|
+
meta: {
|
|
120
|
+
query,
|
|
121
|
+
returned_count: input.notes.length,
|
|
122
|
+
total_count: input.notes.length,
|
|
123
|
+
stale: input.stale,
|
|
124
|
+
applied_filters: {
|
|
125
|
+
include_deleted: input.includeDeleted,
|
|
126
|
+
tags_expression: tagsExpression || null,
|
|
127
|
+
created_at_range: hasCreatedAtRange
|
|
128
|
+
? {
|
|
129
|
+
from: input.createdAtFrom ?? null,
|
|
130
|
+
to: input.createdAtTo ?? null,
|
|
131
|
+
}
|
|
132
|
+
: null,
|
|
133
|
+
days: input.days ?? null,
|
|
134
|
+
box_names: [...input.boxNames],
|
|
135
|
+
box_ids: [...input.boxIds],
|
|
136
|
+
match_empty_boxes: input.matchEmptyBoxes,
|
|
137
|
+
sql_expression: sqlExpression || null,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
notes: input.notes,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
108
143
|
export function registerNoteCommands(program) {
|
|
109
144
|
const note = program.command('note').description('Manage notes');
|
|
110
145
|
note
|
|
@@ -123,7 +158,7 @@ export function registerNoteCommands(program) {
|
|
|
123
158
|
const offline = Boolean(globals.offline);
|
|
124
159
|
const config = resolveConfig(await loadConfig());
|
|
125
160
|
const timeoutMs = parseSyncTimeoutMs(globals.syncTimeout) ?? config.sync.timeoutMs;
|
|
126
|
-
const { db } = await connectPowerSync({ config, offline, timeoutMs });
|
|
161
|
+
const { db, stale } = await connectPowerSync({ config, offline, timeoutMs });
|
|
127
162
|
try {
|
|
128
163
|
await syncBeforeNoteCommand(db, offline, timeoutMs);
|
|
129
164
|
const keyword = query?.trim() ?? '';
|
|
@@ -148,7 +183,21 @@ export function registerNoteCommands(program) {
|
|
|
148
183
|
matchEmptyZettelBoxes: parsedBoxInput.isExplicitEmptyArray,
|
|
149
184
|
sqlExpression: sqlExpression || undefined,
|
|
150
185
|
});
|
|
151
|
-
|
|
186
|
+
const response = buildNoteSearchResponse({
|
|
187
|
+
query: keyword,
|
|
188
|
+
notes: rows,
|
|
189
|
+
stale: offline ? true : stale,
|
|
190
|
+
includeDeleted: Boolean(options.includeDeleted),
|
|
191
|
+
tagsExpression: tagsExpression || undefined,
|
|
192
|
+
createdAtFrom,
|
|
193
|
+
createdAtTo,
|
|
194
|
+
days: normalizeDaysForMeta(options.days),
|
|
195
|
+
boxNames: parsedBoxInput.values,
|
|
196
|
+
boxIds: zettelBoxIds,
|
|
197
|
+
matchEmptyBoxes: parsedBoxInput.isExplicitEmptyArray,
|
|
198
|
+
sqlExpression: sqlExpression || undefined,
|
|
199
|
+
});
|
|
200
|
+
printYaml(response);
|
|
152
201
|
}
|
|
153
202
|
finally {
|
|
154
203
|
await db.close().catch(() => undefined);
|
|
@@ -2,20 +2,11 @@ import { loadConfig } from '../../config/store.js';
|
|
|
2
2
|
import { resolveConfig } from '../../config/resolve.js';
|
|
3
3
|
import { DinoxError } from '../../utils/errors.js';
|
|
4
4
|
import { printYaml } from '../../utils/output.js';
|
|
5
|
+
import { parseSyncTimeoutMs } from '../../utils/syncTimeout.js';
|
|
5
6
|
import { connectPowerSync, waitForIdleDataFlow } from '../../powersync/runtime.js';
|
|
6
7
|
import { syncNoteTokenIndex } from '../../powersync/tokenIndex.js';
|
|
7
8
|
import { resolveAuthIdentity } from '../../auth/userInfo.js';
|
|
8
9
|
import { addPrompt, listPrompts } from './repo.js';
|
|
9
|
-
function parseSyncTimeoutMs(value) {
|
|
10
|
-
if (typeof value !== 'string') {
|
|
11
|
-
return undefined;
|
|
12
|
-
}
|
|
13
|
-
const parsed = Number(value);
|
|
14
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
15
|
-
throw new DinoxError('--sync-timeout must be a positive integer');
|
|
16
|
-
}
|
|
17
|
-
return Math.trunc(parsed);
|
|
18
|
-
}
|
|
19
10
|
async function syncBeforePromptCommand(db, offline, timeoutMs) {
|
|
20
11
|
if (offline) {
|
|
21
12
|
return;
|
package/dist/commands/sync.js
CHANGED
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
import { loadConfig } from '../config/store.js';
|
|
2
2
|
import { resolveConfig } from '../config/resolve.js';
|
|
3
|
-
import { DinoxError } from '../utils/errors.js';
|
|
4
3
|
import { printYaml } from '../utils/output.js';
|
|
4
|
+
import { parseSyncTimeoutMs } from '../utils/syncTimeout.js';
|
|
5
5
|
import { connectPowerSync, getStatusSnapshot } from '../powersync/runtime.js';
|
|
6
6
|
import { runSingleSyncPass } from '../powersync/syncPass.js';
|
|
7
|
-
function parseSyncTimeoutMs(value) {
|
|
8
|
-
if (typeof value !== 'string') {
|
|
9
|
-
return undefined;
|
|
10
|
-
}
|
|
11
|
-
const parsed = Number(value);
|
|
12
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
13
|
-
throw new DinoxError('--sync-timeout must be a positive integer');
|
|
14
|
-
}
|
|
15
|
-
return Math.trunc(parsed);
|
|
16
|
-
}
|
|
17
7
|
export function registerSyncCommand(program) {
|
|
18
8
|
program
|
|
19
9
|
.command('sync')
|
|
@@ -2,20 +2,11 @@ import { loadConfig } from '../../config/store.js';
|
|
|
2
2
|
import { resolveConfig } from '../../config/resolve.js';
|
|
3
3
|
import { DinoxError } from '../../utils/errors.js';
|
|
4
4
|
import { printYaml } from '../../utils/output.js';
|
|
5
|
+
import { parseSyncTimeoutMs } from '../../utils/syncTimeout.js';
|
|
5
6
|
import { connectPowerSync, waitForIdleDataFlow } from '../../powersync/runtime.js';
|
|
6
7
|
import { syncNoteTokenIndex } from '../../powersync/tokenIndex.js';
|
|
7
8
|
import { resolveAuthIdentity } from '../../auth/userInfo.js';
|
|
8
9
|
import { addTag, listTags } from './repo.js';
|
|
9
|
-
function parseSyncTimeoutMs(value) {
|
|
10
|
-
if (typeof value !== 'string') {
|
|
11
|
-
return undefined;
|
|
12
|
-
}
|
|
13
|
-
const parsed = Number(value);
|
|
14
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
15
|
-
throw new DinoxError('--sync-timeout must be a positive integer');
|
|
16
|
-
}
|
|
17
|
-
return Math.trunc(parsed);
|
|
18
|
-
}
|
|
19
10
|
async function syncBeforeTagCommand(db, offline, timeoutMs) {
|
|
20
11
|
if (offline) {
|
|
21
12
|
return;
|
|
@@ -3,6 +3,7 @@ import { resolveConfig } from '../../config/resolve.js';
|
|
|
3
3
|
import { DinoxError } from '../../utils/errors.js';
|
|
4
4
|
import { readStringOrFile } from '../../utils/argValue.js';
|
|
5
5
|
import { printYaml } from '../../utils/output.js';
|
|
6
|
+
import { parseSyncTimeoutMs } from '../../utils/syncTimeout.js';
|
|
6
7
|
import { connectPowerSync, waitForIdleDataFlow } from '../../powersync/runtime.js';
|
|
7
8
|
import { resolveAuthIdentity } from '../../auth/userInfo.js';
|
|
8
9
|
import { appendTodoTasks, createTodoNote, normalizeTaskTexts, searchTodoTasks, updateTodoTaskStatus, } from './repo.js';
|
|
@@ -12,16 +13,6 @@ const DEFAULT_TASK_LIMIT = 50;
|
|
|
12
13
|
const DEFAULT_NOTE_SCAN_LIMIT = 200;
|
|
13
14
|
const MAX_TASK_LIMIT = 500;
|
|
14
15
|
const MAX_NOTE_SCAN_LIMIT = 5000;
|
|
15
|
-
function parseSyncTimeoutMs(value) {
|
|
16
|
-
if (typeof value !== 'string') {
|
|
17
|
-
return undefined;
|
|
18
|
-
}
|
|
19
|
-
const parsed = Number(value);
|
|
20
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
21
|
-
throw new DinoxError('--sync-timeout must be a positive integer');
|
|
22
|
-
}
|
|
23
|
-
return Math.trunc(parsed);
|
|
24
|
-
}
|
|
25
16
|
function parseTaskStatus(value) {
|
|
26
17
|
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
27
18
|
if (normalized === 'all' || normalized === 'completed' || normalized === 'uncompleted') {
|
|
@@ -1,2 +1,11 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
|
|
2
|
+
type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun';
|
|
3
|
+
type LogFn = (...parts: unknown[]) => void;
|
|
4
|
+
type UpdateCommandDeps = {
|
|
5
|
+
detectPackageManager: () => PackageManager;
|
|
6
|
+
runUpdate: (pm: PackageManager) => Promise<void>;
|
|
7
|
+
getVersion: () => string;
|
|
8
|
+
log: LogFn;
|
|
9
|
+
};
|
|
10
|
+
export declare function registerUpdateCommand(program: Command, overrides?: Partial<UpdateCommandDeps>): void;
|
|
11
|
+
export {};
|
package/dist/commands/update.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { SKILLS_REPO_URL } from '../constants/links.js';
|
|
2
3
|
import { getPackageVersion } from '../utils/version.js';
|
|
3
4
|
const PACKAGE_NAME = '@dinoxx/dinox-cli';
|
|
4
5
|
function detectPackageManager() {
|
|
@@ -32,15 +33,30 @@ function runUpdate(pm) {
|
|
|
32
33
|
child.on('error', reject);
|
|
33
34
|
});
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
+
function resolveDeps(overrides) {
|
|
37
|
+
return {
|
|
38
|
+
detectPackageManager,
|
|
39
|
+
runUpdate,
|
|
40
|
+
getVersion: getPackageVersion,
|
|
41
|
+
log: console.log,
|
|
42
|
+
...overrides,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function printSkillsReminder(log) {
|
|
46
|
+
log(`skills repo: ${SKILLS_REPO_URL}`);
|
|
47
|
+
log('ai reminder: review and update local Dinox skills after CLI updates (especially dino-dinox and dino-update-cli).');
|
|
48
|
+
}
|
|
49
|
+
export function registerUpdateCommand(program, overrides = {}) {
|
|
50
|
+
const deps = resolveDeps(overrides);
|
|
36
51
|
program
|
|
37
52
|
.command('update')
|
|
38
53
|
.description(`Update ${PACKAGE_NAME} to the latest version`)
|
|
39
54
|
.action(async () => {
|
|
40
|
-
const pm = detectPackageManager();
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
await runUpdate(pm);
|
|
44
|
-
|
|
55
|
+
const pm = deps.detectPackageManager();
|
|
56
|
+
deps.log(`current version: ${deps.getVersion()}`);
|
|
57
|
+
deps.log(`updating ${PACKAGE_NAME} via ${pm}...`);
|
|
58
|
+
await deps.runUpdate(pm);
|
|
59
|
+
deps.log('done');
|
|
60
|
+
printSkillsReminder(deps.log);
|
|
45
61
|
});
|
|
46
62
|
}
|
package/dist/config/resolve.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { POWERSYNC_DEFAULT_ENDPOINT, POWERSYNC_DEFAULT_TOKEN_ENDPOINT, POWERSYNC_DEFAULT_UPLOAD_BASE_URL, } from './serviceEndpoints.js';
|
|
2
|
+
import { normalizeAuthToken } from '../utils/authToken.js';
|
|
2
3
|
const DEFAULT_UPLOAD_V4_PATH = '/powersync/v4/uploadData';
|
|
3
4
|
const DEFAULT_UPLOAD_V2_PATH = '/powersync/v2/uploadData';
|
|
4
5
|
const DEFAULT_SYNC_TIMEOUT_MS = 300_000;
|
|
@@ -20,7 +21,7 @@ function parsePositiveInt(value) {
|
|
|
20
21
|
return Math.trunc(parsed);
|
|
21
22
|
}
|
|
22
23
|
export function resolveConfig(raw) {
|
|
23
|
-
const fileAuth =
|
|
24
|
+
const fileAuth = normalizeAuthToken(raw.auth?.authorization);
|
|
24
25
|
const fileUserId = nonEmptyString(raw.auth?.userId);
|
|
25
26
|
const fileUsername = nonEmptyString(raw.auth?.username ?? undefined) ?? raw.auth?.username ?? undefined;
|
|
26
27
|
const fileEmail = nonEmptyString(raw.auth?.email ?? undefined) ?? raw.auth?.email ?? undefined;
|
|
@@ -29,7 +30,7 @@ export function resolveConfig(raw) {
|
|
|
29
30
|
const fileUploadV4Path = nonEmptyString(raw.powersync?.uploadV4Path);
|
|
30
31
|
const fileUploadV2Path = nonEmptyString(raw.powersync?.uploadV2Path);
|
|
31
32
|
const fileTimeoutMs = parsePositiveInt(raw.sync?.timeoutMs);
|
|
32
|
-
const envAuth =
|
|
33
|
+
const envAuth = normalizeAuthToken(process.env.DINOX_AUTHORIZATION);
|
|
33
34
|
const envTokenEndpoint = nonEmptyString(process.env.DINOX_POWERSYNC_TOKEN_ENDPOINT) ?? nonEmptyString(process.env.POWERSYNC_TOKEN_ENDPOINT);
|
|
34
35
|
const envUploadV4Path = nonEmptyString(process.env.DINOX_POWERSYNC_UPLOAD_V4_PATH);
|
|
35
36
|
const envUploadV2Path = nonEmptyString(process.env.DINOX_POWERSYNC_UPLOAD_V2_PATH);
|
package/dist/config/store.js
CHANGED
|
@@ -2,9 +2,24 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { getConfigDir, getConfigFilePath } from './paths.js';
|
|
4
4
|
import { generateId } from '../utils/id.js';
|
|
5
|
+
import { DinoxError } from '../utils/errors.js';
|
|
5
6
|
function isPlainObject(value) {
|
|
6
7
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
7
8
|
}
|
|
9
|
+
const FORBIDDEN_KEY_PATH_SEGMENTS = new Set(['__proto__', 'prototype', 'constructor']);
|
|
10
|
+
function splitKeyPath(keyPath) {
|
|
11
|
+
const segments = keyPath
|
|
12
|
+
.split('.')
|
|
13
|
+
.map(segment => segment.trim())
|
|
14
|
+
.filter(Boolean);
|
|
15
|
+
for (const segment of segments) {
|
|
16
|
+
const normalized = segment.toLowerCase();
|
|
17
|
+
if (FORBIDDEN_KEY_PATH_SEGMENTS.has(normalized)) {
|
|
18
|
+
throw new DinoxError(`Invalid config key path: ${keyPath}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return segments;
|
|
22
|
+
}
|
|
8
23
|
async function ensureConfigDir() {
|
|
9
24
|
const dir = getConfigDir();
|
|
10
25
|
await fs.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
@@ -47,7 +62,7 @@ export async function saveConfig(config) {
|
|
|
47
62
|
await fs.rename(tmpPath, filePath);
|
|
48
63
|
}
|
|
49
64
|
function setDeepValue(target, keyPath, value) {
|
|
50
|
-
const segments = keyPath
|
|
65
|
+
const segments = splitKeyPath(keyPath);
|
|
51
66
|
if (segments.length === 0) {
|
|
52
67
|
return;
|
|
53
68
|
}
|
|
@@ -63,7 +78,7 @@ function setDeepValue(target, keyPath, value) {
|
|
|
63
78
|
cursor[segments[segments.length - 1]] = value;
|
|
64
79
|
}
|
|
65
80
|
function getDeepValue(target, keyPath) {
|
|
66
|
-
const segments = keyPath
|
|
81
|
+
const segments = splitKeyPath(keyPath);
|
|
67
82
|
let cursor = target;
|
|
68
83
|
for (const segment of segments) {
|
|
69
84
|
if (!isPlainObject(cursor)) {
|
package/dist/config/types.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SKILLS_REPO_URL = "https://github.com/ryzencool/dinox-cli-skills";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const SKILLS_REPO_URL = 'https://github.com/ryzencool/dinox-cli-skills';
|
|
@@ -13,9 +13,10 @@ export declare class PowerSyncConnector implements PowerSyncBackendConnector {
|
|
|
13
13
|
fetchCredentials(): Promise<PowerSyncCredentials>;
|
|
14
14
|
uploadData(database: AbstractPowerSyncDatabase): Promise<void>;
|
|
15
15
|
}
|
|
16
|
-
export declare function fetchCredentialsWithAuthorization({ authorization, endpoint, credentialsUrl, signal, }: {
|
|
16
|
+
export declare function fetchCredentialsWithAuthorization({ authorization, endpoint, credentialsUrl, timeoutMs, signal, }: {
|
|
17
17
|
authorization: string;
|
|
18
18
|
endpoint: string;
|
|
19
19
|
credentialsUrl: string;
|
|
20
|
+
timeoutMs?: number;
|
|
20
21
|
signal?: AbortSignal;
|
|
21
22
|
}): Promise<PowerSyncCredentials>;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createTimeoutSignal, mergeAbortSignals } from '../utils/abort.js';
|
|
2
|
+
import { toAuthorizationHeaderValue } from '../utils/authToken.js';
|
|
1
3
|
/**
|
|
2
4
|
* Connector that fetches PowerSync credentials from your backend.
|
|
3
5
|
* This is injectable for testability.
|
|
@@ -23,7 +25,7 @@ export class PowerSyncConnector {
|
|
|
23
25
|
await this.uploader(database);
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
|
-
export async function fetchCredentialsWithAuthorization({ authorization, endpoint, credentialsUrl, signal, }) {
|
|
28
|
+
export async function fetchCredentialsWithAuthorization({ authorization, endpoint, credentialsUrl, timeoutMs, signal, }) {
|
|
27
29
|
if (!authorization) {
|
|
28
30
|
throw new Error('PowerSync credential exchange requires an authorization token');
|
|
29
31
|
}
|
|
@@ -33,14 +35,30 @@ export async function fetchCredentialsWithAuthorization({ authorization, endpoin
|
|
|
33
35
|
if (!credentialsUrl) {
|
|
34
36
|
throw new Error('PowerSync credential exchange requires a token endpoint');
|
|
35
37
|
}
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
const timeout = typeof timeoutMs === 'number' && Number.isFinite(timeoutMs) && timeoutMs > 0
|
|
39
|
+
? createTimeoutSignal(timeoutMs)
|
|
40
|
+
: null;
|
|
41
|
+
const requestSignal = mergeAbortSignals([signal, timeout?.signal]);
|
|
42
|
+
let response;
|
|
43
|
+
try {
|
|
44
|
+
response = await fetch(credentialsUrl, {
|
|
45
|
+
method: 'GET',
|
|
46
|
+
headers: {
|
|
47
|
+
Authorization: toAuthorizationHeaderValue(authorization),
|
|
48
|
+
'Content-Type': 'application/json',
|
|
49
|
+
},
|
|
50
|
+
signal: requestSignal,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (timeout?.signal.aborted && !signal?.aborted) {
|
|
55
|
+
throw new Error(`PowerSync credential exchange timed out after ${timeoutMs}ms`);
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
timeout?.cancel();
|
|
61
|
+
}
|
|
44
62
|
if (!response.ok) {
|
|
45
63
|
const errorText = await response.text().catch(() => '');
|
|
46
64
|
throw new Error(`Received ${response.status} from ${credentialsUrl}: ${errorText}`);
|
|
@@ -2,6 +2,7 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import { PowerSyncDatabase } from '@powersync/node';
|
|
3
3
|
import { getDataDir, getPowerSyncDbPath } from '../config/paths.js';
|
|
4
4
|
import { DinoxError } from '../utils/errors.js';
|
|
5
|
+
import { createTimeoutSignal } from '../utils/abort.js';
|
|
5
6
|
import { AppSchema } from './schema/index.js';
|
|
6
7
|
import { PowerSyncConnector, fetchCredentialsWithAuthorization } from './connector.js';
|
|
7
8
|
import { createPowerSyncUploadHandler } from './uploader.js';
|
|
@@ -10,14 +11,6 @@ async function ensureDataDir() {
|
|
|
10
11
|
await fs.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
11
12
|
return dir;
|
|
12
13
|
}
|
|
13
|
-
function createTimeoutSignal(timeoutMs) {
|
|
14
|
-
const controller = new AbortController();
|
|
15
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
16
|
-
return {
|
|
17
|
-
signal: controller.signal,
|
|
18
|
-
cancel: () => clearTimeout(timer),
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
14
|
export function getStatusSnapshot(db) {
|
|
22
15
|
const raw = db.currentStatus?.toJSON?.() ?? {};
|
|
23
16
|
const lastSyncedAt = raw.lastSyncedAt instanceof Date ? raw.lastSyncedAt.toISOString() : raw.lastSyncedAt ?? null;
|
|
@@ -55,7 +48,7 @@ export async function openPowerSyncDatabase() {
|
|
|
55
48
|
}
|
|
56
49
|
function assertConfigured(config) {
|
|
57
50
|
if (!config.auth.authorization) {
|
|
58
|
-
throw new DinoxError('Not logged in. Run: dino auth login <
|
|
51
|
+
throw new DinoxError('Not logged in. Run: dino auth login <token>');
|
|
59
52
|
}
|
|
60
53
|
}
|
|
61
54
|
export async function connectPowerSync(args) {
|
|
@@ -70,6 +63,7 @@ export async function connectPowerSync(args) {
|
|
|
70
63
|
uploadBaseUrl: args.config.powersync.uploadBaseUrl,
|
|
71
64
|
uploadV4Path: args.config.powersync.uploadV4Path,
|
|
72
65
|
uploadV2Path: args.config.powersync.uploadV2Path,
|
|
66
|
+
timeoutMs: args.timeoutMs,
|
|
73
67
|
})
|
|
74
68
|
: undefined;
|
|
75
69
|
const connector = new PowerSyncConnector(async () => {
|
|
@@ -77,6 +71,7 @@ export async function connectPowerSync(args) {
|
|
|
77
71
|
authorization: args.config.auth.authorization,
|
|
78
72
|
endpoint: args.config.powersync.endpoint,
|
|
79
73
|
credentialsUrl: args.config.powersync.tokenEndpoint,
|
|
74
|
+
timeoutMs: args.timeoutMs,
|
|
80
75
|
});
|
|
81
76
|
}, uploader);
|
|
82
77
|
await db.connect(connector);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { UpdateType } from '@powersync/common';
|
|
2
|
+
import { createTimeoutSignal, mergeAbortSignals } from '../utils/abort.js';
|
|
3
|
+
import { toAuthorizationHeaderValue } from '../utils/authToken.js';
|
|
2
4
|
const FATAL_RESPONSE_CODES = [/^22...$/, /^23...$/, /^42501$/];
|
|
3
5
|
function shouldFallbackToLegacy(status) {
|
|
4
6
|
return status === 404 || status === 405;
|
|
@@ -45,14 +47,34 @@ function isFatalUploadError(error) {
|
|
|
45
47
|
return typeof code === 'string' && FATAL_RESPONSE_CODES.some(regex => regex.test(code));
|
|
46
48
|
}
|
|
47
49
|
async function dispatchMutation(options) {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
const timeout = typeof options.timeoutMs === 'number' && Number.isFinite(options.timeoutMs) && options.timeoutMs > 0
|
|
51
|
+
? createTimeoutSignal(options.timeoutMs)
|
|
52
|
+
: null;
|
|
53
|
+
const requestSignal = mergeAbortSignals([options.signal, timeout?.signal]);
|
|
54
|
+
let response;
|
|
55
|
+
try {
|
|
56
|
+
response = await fetch(options.url, {
|
|
57
|
+
method: options.method,
|
|
58
|
+
headers: {
|
|
59
|
+
Authorization: toAuthorizationHeaderValue(options.authorization),
|
|
60
|
+
'Content-Type': 'application/json',
|
|
61
|
+
},
|
|
62
|
+
body: JSON.stringify(options.payload),
|
|
63
|
+
signal: requestSignal,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (timeout?.signal.aborted && !options.signal?.aborted) {
|
|
68
|
+
const err = new Error(`PowerSync upload timed out (${options.method} ${options.url}) after ${options.timeoutMs}ms`);
|
|
69
|
+
err.name = 'PowerSyncUploadError';
|
|
70
|
+
err.status = 408;
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
timeout?.cancel();
|
|
77
|
+
}
|
|
56
78
|
if (response.ok) {
|
|
57
79
|
return;
|
|
58
80
|
}
|
|
@@ -75,11 +97,11 @@ async function uploadTransaction(args) {
|
|
|
75
97
|
switch (op.op) {
|
|
76
98
|
case UpdateType.PUT:
|
|
77
99
|
try {
|
|
78
|
-
await dispatchMutation({ authorization: args.authorization, url: primaryUrl, method: 'PUT', payload: record });
|
|
100
|
+
await dispatchMutation({ authorization: args.authorization, url: primaryUrl, method: 'PUT', payload: record, timeoutMs: args.timeoutMs });
|
|
79
101
|
}
|
|
80
102
|
catch (error) {
|
|
81
103
|
if (shouldFallbackToLegacy(error?.status)) {
|
|
82
|
-
await dispatchMutation({ authorization: args.authorization, url: fallbackUrl, method: 'PUT', payload: record });
|
|
104
|
+
await dispatchMutation({ authorization: args.authorization, url: fallbackUrl, method: 'PUT', payload: record, timeoutMs: args.timeoutMs });
|
|
83
105
|
}
|
|
84
106
|
else {
|
|
85
107
|
throw error;
|
|
@@ -88,11 +110,11 @@ async function uploadTransaction(args) {
|
|
|
88
110
|
break;
|
|
89
111
|
case UpdateType.PATCH:
|
|
90
112
|
try {
|
|
91
|
-
await dispatchMutation({ authorization: args.authorization, url: primaryUrl, method: 'PATCH', payload: record });
|
|
113
|
+
await dispatchMutation({ authorization: args.authorization, url: primaryUrl, method: 'PATCH', payload: record, timeoutMs: args.timeoutMs });
|
|
92
114
|
}
|
|
93
115
|
catch (error) {
|
|
94
116
|
if (shouldFallbackToLegacy(error?.status)) {
|
|
95
|
-
await dispatchMutation({ authorization: args.authorization, url: fallbackUrl, method: 'PATCH', payload: record });
|
|
117
|
+
await dispatchMutation({ authorization: args.authorization, url: fallbackUrl, method: 'PATCH', payload: record, timeoutMs: args.timeoutMs });
|
|
96
118
|
}
|
|
97
119
|
else {
|
|
98
120
|
throw error;
|
|
@@ -100,7 +122,7 @@ async function uploadTransaction(args) {
|
|
|
100
122
|
}
|
|
101
123
|
break;
|
|
102
124
|
case UpdateType.DELETE:
|
|
103
|
-
await dispatchMutation({ authorization: args.authorization, url: fallbackUrl, method: 'DELETE', payload: record });
|
|
125
|
+
await dispatchMutation({ authorization: args.authorization, url: fallbackUrl, method: 'DELETE', payload: record, timeoutMs: args.timeoutMs });
|
|
104
126
|
break;
|
|
105
127
|
default:
|
|
106
128
|
break;
|
|
@@ -120,6 +142,7 @@ export function createPowerSyncUploadHandler(options) {
|
|
|
120
142
|
uploadBaseUrl: options.uploadBaseUrl,
|
|
121
143
|
uploadV4Path: options.uploadV4Path,
|
|
122
144
|
uploadV2Path: options.uploadV2Path,
|
|
145
|
+
timeoutMs: options.timeoutMs,
|
|
123
146
|
});
|
|
124
147
|
await transaction.complete();
|
|
125
148
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type TimeoutSignal = {
|
|
2
|
+
signal: AbortSignal;
|
|
3
|
+
cancel: () => void;
|
|
4
|
+
};
|
|
5
|
+
export declare function createTimeoutSignal(timeoutMs: number): TimeoutSignal;
|
|
6
|
+
export declare function mergeAbortSignals(signals: Array<AbortSignal | undefined>): AbortSignal | undefined;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function createTimeoutSignal(timeoutMs) {
|
|
2
|
+
const controller = new AbortController();
|
|
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
|
+
return {
|
|
9
|
+
signal: controller.signal,
|
|
10
|
+
cancel: () => clearTimeout(timer),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function mergeAbortSignals(signals) {
|
|
14
|
+
const filtered = signals.filter(Boolean);
|
|
15
|
+
if (filtered.length === 0) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
if (filtered.length === 1) {
|
|
19
|
+
return filtered[0];
|
|
20
|
+
}
|
|
21
|
+
// Node.js 20+ supports AbortSignal.any().
|
|
22
|
+
return AbortSignal.any(filtered);
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function normalizeAuthToken(value) {
|
|
2
|
+
if (typeof value !== 'string') {
|
|
3
|
+
return undefined;
|
|
4
|
+
}
|
|
5
|
+
const trimmed = value.trim();
|
|
6
|
+
if (!trimmed) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
if (trimmed.toLowerCase().startsWith('bearer ')) {
|
|
10
|
+
const withoutPrefix = trimmed.slice('bearer '.length).trim();
|
|
11
|
+
return withoutPrefix || undefined;
|
|
12
|
+
}
|
|
13
|
+
return trimmed;
|
|
14
|
+
}
|
|
15
|
+
export function toAuthorizationHeaderValue(token) {
|
|
16
|
+
// Backend expects a standard Authorization header. Be defensive: callers may pass
|
|
17
|
+
// a full "Bearer ..." header value or a raw token.
|
|
18
|
+
const normalized = normalizeAuthToken(token);
|
|
19
|
+
if (!normalized) {
|
|
20
|
+
throw new Error('Missing auth token');
|
|
21
|
+
}
|
|
22
|
+
return `Bearer ${normalized}`;
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function parseSyncTimeoutMs(value: unknown): number | undefined;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { DinoxError } from './errors.js';
|
|
2
|
+
export function parseSyncTimeoutMs(value) {
|
|
3
|
+
if (value === undefined || value === null) {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
const text = typeof value === 'string' ? value.trim() : typeof value === 'number' ? String(value) : '';
|
|
7
|
+
if (!text) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
// Commander options are typically strings; be strict and fail fast for non-integers.
|
|
11
|
+
if (!/^\d+$/.test(text)) {
|
|
12
|
+
throw new DinoxError('--sync-timeout must be a positive integer');
|
|
13
|
+
}
|
|
14
|
+
const parsed = Number(text);
|
|
15
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
16
|
+
throw new DinoxError('--sync-timeout must be a positive integer');
|
|
17
|
+
}
|
|
18
|
+
return parsed;
|
|
19
|
+
}
|