@dinoxx/dinox-cli 1.0.19 → 1.0.20

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.
@@ -5,6 +5,8 @@ export type AuthIdentity = {
5
5
  email: string | null;
6
6
  syncedAt: string;
7
7
  };
8
+ export declare function buildPendingAuthConfig(rawConfig: DinoxConfig, authorization: string): DinoxConfig;
9
+ export declare function mergeAuthIdentityIntoConfig(rawConfig: DinoxConfig, authorization: string, identity: AuthIdentity): DinoxConfig;
8
10
  export declare function fetchUserInfoWithAuthorization(input: {
9
11
  authorization: string;
10
12
  config: ResolvedDinoxConfig;
@@ -3,6 +3,33 @@ import { DinoxError } from '../utils/errors.js';
3
3
  import { createTimeoutSignal, mergeAbortSignals } from '../utils/abort.js';
4
4
  import { toAuthorizationHeaderValue } from '../utils/authToken.js';
5
5
  const AUTH_CODE_OK = '000000';
6
+ export function buildPendingAuthConfig(rawConfig, authorization) {
7
+ return {
8
+ ...rawConfig,
9
+ auth: {
10
+ ...(rawConfig.auth ?? {}),
11
+ authorization,
12
+ userId: undefined,
13
+ username: undefined,
14
+ email: undefined,
15
+ userInfoSyncedAt: undefined,
16
+ },
17
+ };
18
+ }
19
+ export function mergeAuthIdentityIntoConfig(rawConfig, authorization, identity) {
20
+ const pendingConfig = buildPendingAuthConfig(rawConfig, authorization);
21
+ return {
22
+ ...pendingConfig,
23
+ auth: {
24
+ ...(pendingConfig.auth ?? {}),
25
+ authorization,
26
+ userId: identity.userId,
27
+ username: identity.username,
28
+ email: identity.email,
29
+ userInfoSyncedAt: identity.syncedAt,
30
+ },
31
+ };
32
+ }
6
33
  function normalizeOptionalString(value) {
7
34
  if (typeof value !== 'string') {
8
35
  return undefined;
@@ -105,17 +132,7 @@ export async function refreshAndPersistAuthIdentity(rawConfig, resolvedConfig) {
105
132
  authorization,
106
133
  config: resolvedConfig,
107
134
  });
108
- const nextConfig = {
109
- ...rawConfig,
110
- auth: {
111
- ...(rawConfig.auth ?? {}),
112
- authorization: rawConfig.auth?.authorization ?? authorization,
113
- userId: identity.userId,
114
- username: identity.username,
115
- email: identity.email,
116
- userInfoSyncedAt: identity.syncedAt,
117
- },
118
- };
135
+ const nextConfig = mergeAuthIdentityIntoConfig(rawConfig, authorization, identity);
119
136
  await saveConfig(nextConfig);
120
137
  return identity;
121
138
  }
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs/promises';
2
- import { refreshAndPersistAuthIdentity } from '../../auth/userInfo.js';
3
- import { getPowerSyncDbPath } from '../../config/paths.js';
2
+ import { buildPendingAuthConfig, fetchUserInfoWithAuthorization, mergeAuthIdentityIntoConfig, } from '../../auth/userInfo.js';
3
+ import { getLegacyPowerSyncDbPath, getPowerSyncDbPath } from '../../config/paths.js';
4
4
  import { loadConfig, saveConfig } from '../../config/store.js';
5
5
  import { resolveConfig } from '../../config/resolve.js';
6
6
  import { fetchCredentialsWithAuthorization } from '../../powersync/connector.js';
@@ -26,10 +26,9 @@ async function runAuthLogin(context) {
26
26
  if (!rawToken) {
27
27
  throw new DinoxError('Token is required');
28
28
  }
29
- const config = await loadConfig();
30
- config.auth = { ...(config.auth ?? {}), authorization: rawToken };
31
- await saveConfig(config);
32
- const resolved = resolveConfig(config);
29
+ const previousConfig = await loadConfig();
30
+ const pendingConfig = buildPendingAuthConfig(previousConfig, rawToken);
31
+ const resolved = resolveConfig(pendingConfig);
33
32
  const timeoutMs = parseSyncTimeoutMs(context.globals.syncTimeout) ?? resolved.sync.timeoutMs;
34
33
  const structuredOutput = getRequestedStructuredOutputFormat() !== null;
35
34
  await fetchCredentialsWithAuthorization({
@@ -38,19 +37,26 @@ async function runAuthLogin(context) {
38
37
  credentialsUrl: resolved.powersync.tokenEndpoint,
39
38
  timeoutMs,
40
39
  });
41
- const identity = await refreshAndPersistAuthIdentity(config, resolved);
42
- const { db, dbPath, stale } = await connectPowerSync({
40
+ const identity = await fetchUserInfoWithAuthorization({
41
+ authorization: rawToken,
43
42
  config: resolved,
43
+ timeoutMs,
44
+ });
45
+ const verifiedConfig = mergeAuthIdentityIntoConfig(previousConfig, rawToken, identity);
46
+ const verifiedResolved = resolveConfig(verifiedConfig);
47
+ const { db, dbPath, stale } = await connectPowerSync({
48
+ config: verifiedResolved,
44
49
  offline: false,
45
50
  timeoutMs,
46
51
  });
47
52
  try {
48
53
  await runSingleSyncPass(db, timeoutMs);
54
+ await saveConfig(verifiedConfig);
49
55
  const status = getStatusSnapshot(db);
50
56
  const result = {
51
57
  ok: true,
52
58
  verified: true,
53
- endpoint: resolved.powersync.endpoint,
59
+ endpoint: verifiedResolved.powersync.endpoint,
54
60
  dbPath,
55
61
  stale,
56
62
  lastSyncedAt: status.lastSyncedAt,
@@ -78,6 +84,7 @@ async function runAuthLogin(context) {
78
84
  async function runAuthLogout(context) {
79
85
  const options = context.options;
80
86
  const config = await loadConfig();
87
+ const currentUserId = config.auth?.userId;
81
88
  if (config.auth) {
82
89
  config.auth.authorization = undefined;
83
90
  config.auth.userId = undefined;
@@ -87,7 +94,8 @@ async function runAuthLogout(context) {
87
94
  }
88
95
  await saveConfig(config);
89
96
  if (options.clearLocalDb) {
90
- await removeSqliteArtifacts(getPowerSyncDbPath());
97
+ await removeSqliteArtifacts(getPowerSyncDbPath(currentUserId));
98
+ await removeSqliteArtifacts(getLegacyPowerSyncDbPath());
91
99
  }
92
100
  if (getRequestedStructuredOutputFormat()) {
93
101
  printSuccess({
@@ -103,7 +111,7 @@ async function runAuthStatus(context) {
103
111
  const offline = Boolean(context.globals.offline);
104
112
  const structuredOutput = getRequestedStructuredOutputFormat() !== null;
105
113
  const timeoutMs = parseSyncTimeoutMs(context.globals.syncTimeout) ?? resolved.sync.timeoutMs;
106
- const dbPath = getPowerSyncDbPath();
114
+ const dbPath = getPowerSyncDbPath(resolved.auth.userId);
107
115
  const base = {
108
116
  loggedIn: Boolean(resolved.auth.authorization),
109
117
  authorization: redactAuthorization(resolved.auth.authorization),
@@ -216,7 +216,8 @@ export class MCPServer {
216
216
  case 'dinox_search_notes': {
217
217
  const keyword = typeof args.query === 'string' ? args.query : '';
218
218
  const tagsExpression = typeof args.tags === 'string' ? args.tags.trim() : '';
219
- const { names: boxNames, matchEmpty: matchEmptyBoxes } = parseBoxFilter(args.box);
219
+ const rawBoxes = args.boxes ?? args.box;
220
+ const { names: boxNames, matchEmpty: matchEmptyBoxes } = parseBoxFilter(rawBoxes);
220
221
  const zettelBoxIds = await resolveZettelBoxIdsForSearch(this.db, boxNames);
221
222
  const daysInput = typeof args.days === 'number' ? String(args.days) : args.days;
222
223
  const { createdAtFrom, createdAtTo } = parseCreatedAtRange({
@@ -151,7 +151,7 @@ export declare function inspectStorageTest(db: AbstractPowerSyncDatabase, input:
151
151
  export declare function testStorageUpload(db: AbstractPowerSyncDatabase, input: {
152
152
  userId: string;
153
153
  storageId?: string;
154
- }, deps?: Pick<UploadDeps, 'uploadBytes'>): Promise<StorageTestResult>;
154
+ }, deps?: Pick<UploadDeps, 'deleteObject' | 'uploadBytes'>): Promise<StorageTestResult>;
155
155
  export declare function uploadFileToStorage(db: AbstractPowerSyncDatabase, input: {
156
156
  userId: string;
157
157
  filePath: string;
@@ -515,12 +515,13 @@ export async function getStorageStats(db, userId) {
515
515
  COUNT(1) AS file_count
516
516
  FROM c_resource
517
517
  WHERE (is_del IS NULL OR is_del = 0)
518
+ AND TRIM(COALESCE(user_id, '')) = ?
518
519
  AND TRIM(COALESCE(storage_provider, '')) <> ''
519
520
  AND TRIM(COALESCE(storage_provider, '')) <> 'local'
520
521
  AND TRIM(COALESCE(status, '')) = 'uploaded'
521
522
  GROUP BY storage_provider, bucket
522
523
  ORDER BY total_bytes DESC, provider ASC, bucket ASC
523
- `, []);
524
+ `, [userId]);
524
525
  const configs = await listStoredCustomStorageConfigs(db, userId);
525
526
  const configsByKey = new Map();
526
527
  const configsByProvider = new Map();
@@ -639,6 +640,7 @@ export async function testStorageUpload(db, input, deps) {
639
640
  throw new DinoxError(`Storage config not found: ${plan.storage.id}`);
640
641
  }
641
642
  const uploadBytes = deps?.uploadBytes ?? defaultUploadBytes;
643
+ const deleteObject = deps?.deleteObject ?? defaultDeleteObject;
642
644
  const payload = Buffer.from(`dinox-cli storage test\nprovider=${storage.provider}\nuser=${input.userId}\ncreated_at=${new Date().toISOString()}\n`, 'utf8');
643
645
  await uploadBytes({
644
646
  body: payload,
@@ -647,6 +649,23 @@ export async function testStorageUpload(db, input, deps) {
647
649
  contentType: plan.contentType,
648
650
  storage,
649
651
  });
652
+ try {
653
+ await deleteObject({
654
+ bucket: storage.bucket,
655
+ key: plan.storageKey,
656
+ storage,
657
+ });
658
+ }
659
+ catch (error) {
660
+ throw new DinoxError('Storage test uploaded the temporary object but failed to clean it up.', {
661
+ cause: error,
662
+ code: 'STORAGE_TEST_CLEANUP_FAILED',
663
+ details: {
664
+ storageKey: plan.storageKey,
665
+ bucket: storage.bucket,
666
+ },
667
+ });
668
+ }
650
669
  return {
651
670
  storageConfigId: storage.id,
652
671
  aliasName: storage.aliasName,
@@ -1,4 +1,5 @@
1
1
  export declare function getConfigDir(): string;
2
2
  export declare function getConfigFilePath(): string;
3
3
  export declare function getDataDir(): string;
4
- export declare function getPowerSyncDbPath(): string;
4
+ export declare function getLegacyPowerSyncDbPath(): string;
5
+ export declare function getPowerSyncDbPath(userId?: string | null): string;
@@ -1,6 +1,9 @@
1
1
  import os from 'node:os';
2
2
  import path from 'node:path';
3
3
  const POWERSYNC_DB_FILENAME = 'dinox-cli.sqlite';
4
+ function normalizeUserPathSegment(userId) {
5
+ return encodeURIComponent(userId.trim());
6
+ }
4
7
  function resolveConfigHome() {
5
8
  if (process.env.DINOX_CONFIG_DIR) {
6
9
  return process.env.DINOX_CONFIG_DIR;
@@ -35,6 +38,13 @@ export function getConfigFilePath() {
35
38
  export function getDataDir() {
36
39
  return path.join(resolveDataHome(), 'dinox');
37
40
  }
38
- export function getPowerSyncDbPath() {
41
+ export function getLegacyPowerSyncDbPath() {
39
42
  return path.join(getDataDir(), POWERSYNC_DB_FILENAME);
40
43
  }
44
+ export function getPowerSyncDbPath(userId) {
45
+ const normalizedUserId = typeof userId === 'string' ? userId.trim() : '';
46
+ if (!normalizedUserId) {
47
+ return getLegacyPowerSyncDbPath();
48
+ }
49
+ return path.join(getDataDir(), 'users', normalizeUserPathSegment(normalizedUserId), POWERSYNC_DB_FILENAME);
50
+ }
@@ -35,13 +35,15 @@ export function resolveConfig(raw) {
35
35
  const envUploadV4Path = nonEmptyString(process.env.DINOX_POWERSYNC_UPLOAD_V4_PATH);
36
36
  const envUploadV2Path = nonEmptyString(process.env.DINOX_POWERSYNC_UPLOAD_V2_PATH);
37
37
  const envTimeoutMs = parsePositiveInt(process.env.DINOX_SYNC_TIMEOUT_MS);
38
+ const effectiveAuth = envAuth ?? fileAuth;
39
+ const canReusePersistedIdentity = !envAuth || envAuth === fileAuth;
38
40
  return {
39
41
  auth: {
40
- authorization: envAuth ?? fileAuth,
41
- userId: fileUserId,
42
- username: fileUsername,
43
- email: fileEmail,
44
- userInfoSyncedAt: fileUserInfoSyncedAt,
42
+ authorization: effectiveAuth,
43
+ userId: canReusePersistedIdentity ? fileUserId : undefined,
44
+ username: canReusePersistedIdentity ? fileUsername : undefined,
45
+ email: canReusePersistedIdentity ? fileEmail : undefined,
46
+ userInfoSyncedAt: canReusePersistedIdentity ? fileUserInfoSyncedAt : undefined,
45
47
  },
46
48
  powersync: {
47
49
  endpoint: POWERSYNC_DEFAULT_ENDPOINT,
@@ -25,7 +25,7 @@ export declare function getStatusSnapshot(db: AbstractPowerSyncDatabase): {
25
25
  };
26
26
  raw: SyncStatusOptions;
27
27
  };
28
- export declare function openPowerSyncDatabase(): Promise<{
28
+ export declare function openPowerSyncDatabase(userId?: string | null): Promise<{
29
29
  db: PowerSyncDatabase;
30
30
  dbPath: string;
31
31
  }>;
@@ -1,4 +1,5 @@
1
1
  import fs from 'node:fs/promises';
2
+ import path from 'node:path';
2
3
  import { PowerSyncDatabase } from '@powersync/node';
3
4
  import { getDataDir, getPowerSyncDbPath } from '../config/paths.js';
4
5
  import { DinoxError } from '../utils/errors.js';
@@ -33,9 +34,10 @@ export function getStatusSnapshot(db) {
33
34
  raw,
34
35
  };
35
36
  }
36
- export async function openPowerSyncDatabase() {
37
+ export async function openPowerSyncDatabase(userId) {
37
38
  await ensureDataDir();
38
- const dbPath = getPowerSyncDbPath();
39
+ const dbPath = getPowerSyncDbPath(userId);
40
+ await fs.mkdir(path.dirname(dbPath), { recursive: true, mode: 0o700 });
39
41
  const db = new PowerSyncDatabase({
40
42
  schema: AppSchema,
41
43
  database: {
@@ -52,7 +54,7 @@ function assertConfigured(config) {
52
54
  }
53
55
  }
54
56
  export async function connectPowerSync(args) {
55
- const { db, dbPath } = await openPowerSyncDatabase();
57
+ const { db, dbPath } = await openPowerSyncDatabase(args.config.auth.userId);
56
58
  if (args.offline) {
57
59
  return { db, dbPath, stale: true };
58
60
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dinoxx/dinox-cli",
3
- "version": "1.0.19",
3
+ "version": "1.0.20",
4
4
  "description": "Dinox CLI",
5
5
  "main": "dist/dinox.js",
6
6
  "scripts": {