@dinoxx/dinox-cli 1.0.21 → 1.0.22

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/cli.js CHANGED
@@ -28,6 +28,7 @@ export function createCli() {
28
28
  .option('--json', 'legacy machine-readable YAML output (equivalent to --format yaml)')
29
29
  .option('--format <format>', 'structured output format: yaml | json')
30
30
  .option('--offline', 'skip connect/sync and only use local cache')
31
+ .option('--require-sync', 'fail if the local PowerSync cache cannot be confirmed fresh before reading')
31
32
  .option('--sync-timeout <ms>', 'override sync/connect timeout (milliseconds)')
32
33
  .option('--verbose', 'enable verbose logging');
33
34
  program.hook('preAction', (_thisCommand, actionCommand) => {
@@ -1,6 +1,7 @@
1
1
  export type GlobalOptions = {
2
2
  json?: boolean;
3
3
  offline?: boolean;
4
+ requireSync?: boolean;
4
5
  syncTimeout?: string;
5
6
  verbose?: boolean;
6
7
  };
@@ -1,6 +1,6 @@
1
1
  import { resolveAuthIdentity } from '../../auth/userInfo.js';
2
2
  import { registerCommandGroup } from '../descriptor/register.js';
3
- import { FORMAT_OPTIONS, OFFLINE_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
3
+ import { FORMAT_OPTIONS, OFFLINE_OPTIONS, REQUIRE_SYNC_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
4
4
  import { buildBoxAddDryRunPreview, buildBoxMutationDryRunPreview } from '../writeDryRun.js';
5
5
  import { printDryRunPreview } from '../../utils/dryRun.js';
6
6
  import { DinoxError } from '../../utils/errors.js';
@@ -8,7 +8,7 @@ import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSucces
8
8
  import { commandStale, printStaleCacheWarning, printUploadDisabledWarning, syncBeforeCommand, waitForMutationUpload, withCommandRuntime, } from '../runtime.js';
9
9
  import { addBox, inspectAddBox, listBoxes } from './repo.js';
10
10
  import { getBoxStats, getBoxTree, inspectBoxCleanup, mergeBox, moveBox, renameBox, } from './operations.js';
11
- const BOX_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
11
+ const BOX_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...REQUIRE_SYNC_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
12
12
  async function runBoxList(context) {
13
13
  await withCommandRuntime(context, async (runtime) => {
14
14
  const identity = await resolveAuthIdentity(runtime.config);
@@ -1,12 +1,12 @@
1
1
  import { sendDaemonRequest } from '../../daemon/client.js';
2
2
  import { ensureDaemonRunning } from '../../daemon/ensure.js';
3
3
  import { registerCommandGroup } from '../descriptor/register.js';
4
- import { FORMAT_OPTIONS, OFFLINE_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
4
+ import { FORMAT_OPTIONS, OFFLINE_OPTIONS, REQUIRE_SYNC_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
5
5
  import { DinoxError } from '../../utils/errors.js';
6
6
  import { printSuccess } from '../../utils/output.js';
7
- import { withCommandRuntime } from '../runtime.js';
7
+ import { syncBeforeCommand, withCommandRuntime } from '../runtime.js';
8
8
  import { getBacklinks, getGraphStats, getOutlinks, getRelatedNotes } from './repo.js';
9
- const GRAPH_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
9
+ const GRAPH_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...REQUIRE_SYNC_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
10
10
  function parseDepth(value) {
11
11
  if (value === undefined || value === null || value === '') {
12
12
  return 2;
@@ -37,6 +37,13 @@ async function runGraphCommand(context, onlineAction, offlineAction) {
37
37
  printSuccess(await withOfflineDb(context.command, offlineAction));
38
38
  return;
39
39
  }
40
+ if (Boolean(globals.requireSync)) {
41
+ await withCommandRuntime(context, async (runtime) => {
42
+ await syncBeforeCommand(runtime, { syncTokenIndex: true });
43
+ printSuccess(await offlineAction(runtime.db));
44
+ });
45
+ return;
46
+ }
40
47
  await ensureDaemonRunning();
41
48
  printSuccess(await onlineAction());
42
49
  }
@@ -1,5 +1,5 @@
1
1
  import { registerCommandGroup } from '../descriptor/register.js';
2
- import { OFFLINE_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
2
+ import { OFFLINE_OPTIONS, REQUIRE_SYNC_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
3
3
  import { printSuccess } from '../../utils/output.js';
4
4
  import { commandStale, syncBeforeCommand, withCommandRuntime } from '../runtime.js';
5
5
  import { getMetaSchema, getMetaStats } from './repo.js';
@@ -26,7 +26,7 @@ export const metaCommandGroup = {
26
26
  path: 'meta.stats',
27
27
  risk: 'read',
28
28
  auth: 'required',
29
- globalOptions: [...OFFLINE_OPTIONS, ...SYNC_TIMEOUT_OPTIONS],
29
+ globalOptions: [...OFFLINE_OPTIONS, ...REQUIRE_SYNC_OPTIONS, ...SYNC_TIMEOUT_OPTIONS],
30
30
  output: {
31
31
  type: 'structured',
32
32
  summary: 'Metadata stats payload',
@@ -1,10 +1,10 @@
1
1
  import { registerCommandGroup } from '../descriptor/register.js';
2
- import { FORMAT_OPTIONS, OFFLINE_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
2
+ import { FORMAT_OPTIONS, OFFLINE_OPTIONS, REQUIRE_SYNC_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
3
3
  import { runNoteCreate, runNoteDelete, runNoteDetail, runNoteGet, runNoteMove, runNotePreview, runNoteSearch, runNoteStar, runNoteTag, runNoteUnstar, runNoteUpdate, } from './actions.js';
4
4
  import { runNoteBulk as runNoteBulkAction } from './bulk.js';
5
5
  import { runNoteContentRead, runNotePatch } from './contentPatchCommand.js';
6
6
  import { runNoteExport } from './export.js';
7
- const NOTE_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
7
+ const NOTE_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...REQUIRE_SYNC_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
8
8
  export const noteCommandGroup = {
9
9
  name: 'note',
10
10
  description: 'Manage notes',
@@ -1,13 +1,13 @@
1
1
  import { resolveAuthIdentity } from '../../auth/userInfo.js';
2
2
  import { registerCommandGroup } from '../descriptor/register.js';
3
- import { FORMAT_OPTIONS, OFFLINE_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
3
+ import { FORMAT_OPTIONS, OFFLINE_OPTIONS, REQUIRE_SYNC_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
4
4
  import { buildPromptAddDryRunPreview } from '../writeDryRun.js';
5
5
  import { printDryRunPreview } from '../../utils/dryRun.js';
6
6
  import { DinoxError } from '../../utils/errors.js';
7
7
  import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
8
8
  import { commandStale, printStaleCacheWarning, printUploadDisabledWarning, syncBeforeCommand, waitForMutationUpload, withCommandRuntime, } from '../runtime.js';
9
9
  import { addPrompt, inspectAddPrompt, listPrompts } from './repo.js';
10
- const PROMPT_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
10
+ const PROMPT_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...REQUIRE_SYNC_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
11
11
  async function runPromptList(context) {
12
12
  await withCommandRuntime(context, async (runtime) => {
13
13
  await syncBeforeCommand(runtime, { syncTokenIndex: true });
@@ -2,7 +2,13 @@ import type { AbstractPowerSyncDatabase } from '@powersync/common';
2
2
  import type { Command } from 'commander';
3
3
  import { loadConfig } from '../config/store.js';
4
4
  import type { ResolvedDinoxConfig } from '../config/types.js';
5
+ import { type SyncGateResult } from '../powersync/syncGate.js';
5
6
  import type { CommandExecutionContext } from './descriptor/types.js';
7
+ export type CommandSyncState = {
8
+ firstSyncStale: boolean;
9
+ preSyncIdle: boolean | null;
10
+ gate: SyncGateResult | null;
11
+ };
6
12
  export type CommandRuntime = {
7
13
  db: AbstractPowerSyncDatabase;
8
14
  config: ResolvedDinoxConfig;
@@ -10,6 +16,8 @@ export type CommandRuntime = {
10
16
  offline: boolean;
11
17
  timeoutMs: number;
12
18
  stale: boolean;
19
+ requireSync: boolean;
20
+ sync: CommandSyncState;
13
21
  };
14
22
  type RuntimeSource = CommandExecutionContext | Command | Record<string, unknown>;
15
23
  export declare function commandStale(runtime: Pick<CommandRuntime, 'offline' | 'stale'>): boolean;
@@ -1,7 +1,8 @@
1
1
  import { hydrateResolvedAuthIdentity } from '../auth/userInfo.js';
2
2
  import { loadConfig } from '../config/store.js';
3
3
  import { resolveConfig } from '../config/resolve.js';
4
- import { connectPowerSync, waitForIdleDataFlow } from '../powersync/runtime.js';
4
+ import { connectPowerSync, getStatusSnapshot, waitForIdleDataFlow } from '../powersync/runtime.js';
5
+ import { createSyncRequiredError, evaluateSyncGate } from '../powersync/syncGate.js';
5
6
  import { syncNoteTokenIndex } from '../powersync/tokenIndex.js';
6
7
  import { printHumanReadableLine } from '../utils/output.js';
7
8
  import { parseSyncTimeoutMs } from '../utils/syncTimeout.js';
@@ -31,6 +32,7 @@ export async function withCommandRuntime(source, run) {
31
32
  const baseConfig = resolveConfig(rawConfig);
32
33
  const timeoutMs = parseSyncTimeoutMs(globals.syncTimeout) ?? baseConfig.sync.timeoutMs;
33
34
  const offline = Boolean(globals.offline);
35
+ const requireSync = Boolean(globals.requireSync);
34
36
  const { config } = offline
35
37
  ? { config: baseConfig }
36
38
  : await hydrateResolvedAuthIdentity(baseConfig, { timeoutMs });
@@ -43,6 +45,12 @@ export async function withCommandRuntime(source, run) {
43
45
  offline,
44
46
  timeoutMs,
45
47
  stale,
48
+ requireSync,
49
+ sync: {
50
+ firstSyncStale: stale,
51
+ preSyncIdle: null,
52
+ gate: null,
53
+ },
46
54
  });
47
55
  }
48
56
  finally {
@@ -51,12 +59,38 @@ export async function withCommandRuntime(source, run) {
51
59
  }
52
60
  export async function syncBeforeCommand(runtime, options = {}) {
53
61
  if (runtime.offline) {
62
+ if (runtime.requireSync) {
63
+ const gate = evaluateSyncGate({
64
+ offline: true,
65
+ firstSyncStale: runtime.sync.firstSyncStale,
66
+ idle: false,
67
+ status: {
68
+ hasSynced: false,
69
+ dataFlowStatus: {},
70
+ },
71
+ });
72
+ runtime.sync.gate = gate;
73
+ throw createSyncRequiredError(gate);
74
+ }
54
75
  return;
55
76
  }
56
- await waitForIdleDataFlow(runtime.db, Math.min(runtime.timeoutMs, options.timeoutMs ?? PRE_SYNC_TIMEOUT_MS));
77
+ const idle = await waitForIdleDataFlow(runtime.db, Math.min(runtime.timeoutMs, options.timeoutMs ?? PRE_SYNC_TIMEOUT_MS));
78
+ runtime.sync.preSyncIdle = idle;
57
79
  if (options.syncTokenIndex) {
58
80
  await syncNoteTokenIndex(runtime.db);
59
81
  }
82
+ const status = getStatusSnapshot(runtime.db);
83
+ const gate = evaluateSyncGate({
84
+ offline: runtime.offline,
85
+ firstSyncStale: runtime.sync.firstSyncStale,
86
+ idle,
87
+ status,
88
+ });
89
+ runtime.sync.gate = gate;
90
+ runtime.stale = !gate.ok;
91
+ if (runtime.requireSync && !gate.ok) {
92
+ throw createSyncRequiredError(gate, status);
93
+ }
60
94
  }
61
95
  export async function waitForMutationUpload(runtime, options = {}) {
62
96
  if (runtime.offline) {
@@ -1,3 +1,4 @@
1
1
  export declare const FORMAT_OPTIONS: string[];
2
2
  export declare const OFFLINE_OPTIONS: string[];
3
+ export declare const REQUIRE_SYNC_OPTIONS: string[];
3
4
  export declare const SYNC_TIMEOUT_OPTIONS: string[];
@@ -1,3 +1,4 @@
1
1
  export const FORMAT_OPTIONS = ['--format <yaml|json>', '--json'];
2
2
  export const OFFLINE_OPTIONS = ['--offline'];
3
+ export const REQUIRE_SYNC_OPTIONS = ['--require-sync'];
3
4
  export const SYNC_TIMEOUT_OPTIONS = ['--sync-timeout <ms>'];
@@ -21,6 +21,7 @@ const GLOBAL_OPTIONS = [
21
21
  ['`--format <yaml|json>`', 'Structured output format. Prefer `json` for agent and script integrations.'],
22
22
  ['`--json`', 'Legacy alias for machine-readable YAML output. Keep only for backward compatibility.'],
23
23
  ['`--offline`', 'Skip sync, use local cache only'],
24
+ ['`--require-sync`', 'Fail if the local PowerSync cache cannot be confirmed fresh before reading'],
24
25
  ['`--sync-timeout <ms>`', 'Override default 300000 ms (5 min) sync/connect timeout'],
25
26
  ['`--verbose`', 'Enable verbose logging'],
26
27
  ];
@@ -1,13 +1,13 @@
1
1
  import { resolveAuthIdentity } from '../../auth/userInfo.js';
2
2
  import { registerCommandGroup } from '../descriptor/register.js';
3
- import { FORMAT_OPTIONS, OFFLINE_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
3
+ import { FORMAT_OPTIONS, OFFLINE_OPTIONS, REQUIRE_SYNC_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
4
4
  import { buildStorageTestDryRunPreview, buildStorageUploadDryRunPreview } from '../writeDryRun.js';
5
5
  import { printDryRunPreview } from '../../utils/dryRun.js';
6
6
  import { DinoxError } from '../../utils/errors.js';
7
7
  import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
8
8
  import { commandStale, printStaleCacheWarning, printUploadDisabledWarning, syncBeforeCommand, waitForMutationUpload, withCommandRuntime, } from '../runtime.js';
9
9
  import { getStorageStats, inspectStorageTest, inspectStorageUpload, listStorageConfigs, parseStorageUploadCategory, testStorageUpload, uploadFileToStorage, } from './repo.js';
10
- const STORAGE_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
10
+ const STORAGE_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...REQUIRE_SYNC_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
11
11
  async function runStorageList(context) {
12
12
  await withCommandRuntime(context, async (runtime) => {
13
13
  const identity = await resolveAuthIdentity(runtime.config);
@@ -7,9 +7,13 @@ import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSucces
7
7
  import { parseSyncTimeoutMs } from '../utils/syncTimeout.js';
8
8
  import { connectPowerSync, getStatusSnapshot } from '../powersync/runtime.js';
9
9
  import { runSingleSyncPass } from '../powersync/syncPass.js';
10
+ import { createSyncRequiredError, evaluateSyncGate } from '../powersync/syncGate.js';
10
11
  export const syncCommandDescriptor = {
11
12
  name: 'sync',
12
13
  description: 'Connect and synchronize the local PowerSync database',
14
+ options: [
15
+ { flags: '--strict', description: 'Fail unless first sync completed, data flow is idle, and no download error exists', type: 'boolean' },
16
+ ],
13
17
  schema: {
14
18
  path: 'sync',
15
19
  risk: 'write',
@@ -18,9 +22,9 @@ export const syncCommandDescriptor = {
18
22
  output: {
19
23
  type: 'structured',
20
24
  summary: 'Sync result with connection status and token index counters',
21
- fields: ['dbPath', 'stale', 'idle', 'uploadEnabled', 'tokenIndex', 'status'],
25
+ fields: ['dbPath', 'stale', 'idle', 'strict', 'gate', 'uploadEnabled', 'tokenIndex', 'status'],
22
26
  },
23
- examples: ['dino sync --format json', 'dino sync --sync-timeout 600000 --format json'],
27
+ examples: ['dino sync --format json', 'dino sync --strict --sync-timeout 600000 --format json'],
24
28
  },
25
29
  action: async (context) => {
26
30
  const baseConfig = resolveConfig(await loadConfig());
@@ -35,15 +39,26 @@ export const syncCommandDescriptor = {
35
39
  try {
36
40
  const syncPass = await runSingleSyncPass(db, timeoutMs);
37
41
  const status = getStatusSnapshot(db);
42
+ const gate = evaluateSyncGate({
43
+ offline: false,
44
+ firstSyncStale: stale,
45
+ idle: syncPass.idle,
46
+ status,
47
+ });
38
48
  const payload = {
39
49
  ok: true,
40
50
  dbPath,
41
- stale,
51
+ stale: !gate.ok,
42
52
  idle: syncPass.idle,
53
+ strict: Boolean(context.options.strict),
54
+ gate,
43
55
  uploadEnabled: Boolean(config.powersync.uploadBaseUrl),
44
56
  tokenIndex: syncPass.tokenIndex,
45
57
  status,
46
58
  };
59
+ if (context.options.strict && !gate.ok) {
60
+ throw createSyncRequiredError(gate, status);
61
+ }
47
62
  if (structuredOutput) {
48
63
  printSuccess(payload);
49
64
  return;
@@ -1,6 +1,6 @@
1
1
  import { resolveAuthIdentity } from '../../auth/userInfo.js';
2
2
  import { registerCommandGroup } from '../descriptor/register.js';
3
- import { FORMAT_OPTIONS, OFFLINE_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
3
+ import { FORMAT_OPTIONS, OFFLINE_OPTIONS, REQUIRE_SYNC_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
4
4
  import { buildTagAddDryRunPreview, buildTagMutationDryRunPreview } from '../writeDryRun.js';
5
5
  import { printDryRunPreview } from '../../utils/dryRun.js';
6
6
  import { DinoxError } from '../../utils/errors.js';
@@ -8,7 +8,7 @@ import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSucces
8
8
  import { commandStale, printStaleCacheWarning, printUploadDisabledWarning, syncBeforeCommand, waitForMutationUpload, withCommandRuntime, } from '../runtime.js';
9
9
  import { addTag, inspectAddTag, listTags } from './repo.js';
10
10
  import { getTagStats, getTagTree, inspectTagCleanup, mergeTag, moveTag, renameTag, suggestTagMerges, } from './operations.js';
11
- const TAG_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
11
+ const TAG_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...REQUIRE_SYNC_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
12
12
  async function runTagList(context) {
13
13
  const structuredOutput = getRequestedStructuredOutputFormat() !== null;
14
14
  await withCommandRuntime(context, async (runtime) => {
@@ -1,6 +1,6 @@
1
1
  import { resolveAuthIdentity } from '../../auth/userInfo.js';
2
2
  import { registerCommandGroup } from '../descriptor/register.js';
3
- import { FORMAT_OPTIONS, OFFLINE_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
3
+ import { FORMAT_OPTIONS, OFFLINE_OPTIONS, REQUIRE_SYNC_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
4
4
  import { readStringOrFile } from '../../utils/argValue.js';
5
5
  import { printDryRunPreview } from '../../utils/dryRun.js';
6
6
  import { DinoxError } from '../../utils/errors.js';
@@ -14,7 +14,7 @@ const DEFAULT_TASK_LIMIT = 50;
14
14
  const DEFAULT_NOTE_SCAN_LIMIT = 200;
15
15
  const MAX_TASK_LIMIT = 500;
16
16
  const MAX_NOTE_SCAN_LIMIT = 5000;
17
- const TODO_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
17
+ const TODO_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...REQUIRE_SYNC_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
18
18
  function parseTaskStatus(value) {
19
19
  const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
20
20
  if (normalized === 'all' || normalized === 'completed' || normalized === 'uncompleted') {
@@ -82,6 +82,12 @@ export async function connectPowerSync(args) {
82
82
  await db.waitForFirstSync(signal);
83
83
  return { db, dbPath, stale: signal.aborted };
84
84
  }
85
+ catch (error) {
86
+ if (signal.aborted) {
87
+ return { db, dbPath, stale: true };
88
+ }
89
+ throw error;
90
+ }
85
91
  finally {
86
92
  cancel();
87
93
  }
@@ -0,0 +1,24 @@
1
+ import type { SyncStatusOptions } from '@powersync/common';
2
+ import { DinoxError } from '../utils/errors.js';
3
+ export type SyncGateFailureReason = 'offline' | 'first-sync-timeout' | 'data-flow-timeout' | 'not-synced' | 'download-error';
4
+ export type SyncGateResult = {
5
+ ok: boolean;
6
+ reasons: SyncGateFailureReason[];
7
+ };
8
+ export type SyncGateInput = {
9
+ offline: boolean;
10
+ firstSyncStale: boolean;
11
+ idle: boolean;
12
+ status: {
13
+ hasSynced: boolean;
14
+ dataFlowStatus: {
15
+ downloadError?: {
16
+ name: string;
17
+ message: string;
18
+ };
19
+ };
20
+ raw?: SyncStatusOptions;
21
+ };
22
+ };
23
+ export declare function evaluateSyncGate(input: SyncGateInput): SyncGateResult;
24
+ export declare function createSyncRequiredError(gate: SyncGateResult, details?: unknown): DinoxError;
@@ -0,0 +1,36 @@
1
+ import { DinoxError } from '../utils/errors.js';
2
+ export function evaluateSyncGate(input) {
3
+ const reasons = [];
4
+ if (input.offline) {
5
+ return {
6
+ ok: false,
7
+ reasons: ['offline'],
8
+ };
9
+ }
10
+ if (input.firstSyncStale) {
11
+ reasons.push('first-sync-timeout');
12
+ }
13
+ if (!input.idle) {
14
+ reasons.push('data-flow-timeout');
15
+ }
16
+ if (!input.status.hasSynced) {
17
+ reasons.push('not-synced');
18
+ }
19
+ if (input.status.dataFlowStatus.downloadError) {
20
+ reasons.push('download-error');
21
+ }
22
+ return {
23
+ ok: reasons.length === 0,
24
+ reasons,
25
+ };
26
+ }
27
+ export function createSyncRequiredError(gate, details) {
28
+ return new DinoxError('Local PowerSync cache is not fully synchronized; refusing to use stale data.', {
29
+ code: 'SYNC_REQUIRED',
30
+ details: {
31
+ reasons: gate.reasons,
32
+ ...(details !== undefined ? { status: details } : {}),
33
+ },
34
+ suggestion: 'Run `dino sync --strict --sync-timeout 600000 --format json`, then retry the command.',
35
+ });
36
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dinoxx/dinox-cli",
3
- "version": "1.0.21",
3
+ "version": "1.0.22",
4
4
  "description": "Dinox CLI",
5
5
  "main": "dist/dinox.js",
6
6
  "scripts": {