@dinoxx/dinox-cli 1.0.21 → 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.
Files changed (147) hide show
  1. package/README.md +29 -9
  2. package/dist/auth/userInfo.d.ts +2 -0
  3. package/dist/auth/userInfo.js +55 -14
  4. package/dist/cli.js +37 -2
  5. package/dist/cliTypes.d.ts +1 -0
  6. package/dist/commands/auth/index.d.ts +35 -0
  7. package/dist/commands/auth/index.js +382 -83
  8. package/dist/commands/boxes/index.js +4 -8
  9. package/dist/commands/boxes/model.d.ts +3 -0
  10. package/dist/commands/boxes/model.js +16 -1
  11. package/dist/commands/boxes/operations.js +18 -8
  12. package/dist/commands/boxes/repo.js +13 -11
  13. package/dist/commands/boxes/store.js +1 -1
  14. package/dist/commands/config/index.js +7 -5
  15. package/dist/commands/descriptor/register.js +9 -0
  16. package/dist/commands/doctor.d.ts +123 -0
  17. package/dist/commands/doctor.js +539 -0
  18. package/dist/commands/graph/index.d.ts +1 -1
  19. package/dist/commands/graph/index.js +39 -18
  20. package/dist/commands/graph/repo.js +92 -17
  21. package/dist/commands/meta/index.js +4 -4
  22. package/dist/commands/meta/repo.js +4 -3
  23. package/dist/commands/notes/actions.d.ts +1 -1
  24. package/dist/commands/notes/actions.js +121 -78
  25. package/dist/commands/notes/bulk.d.ts +1 -0
  26. package/dist/commands/notes/bulk.js +23 -17
  27. package/dist/commands/notes/contentEngine.js +69 -11
  28. package/dist/commands/notes/contentPatch.d.ts +4 -1
  29. package/dist/commands/notes/contentPatch.js +105 -41
  30. package/dist/commands/notes/contentPatchCommand.js +28 -11
  31. package/dist/commands/notes/descriptors.js +52 -22
  32. package/dist/commands/notes/dryRun.d.ts +1 -0
  33. package/dist/commands/notes/dryRun.js +4 -7
  34. package/dist/commands/notes/export.d.ts +6 -0
  35. package/dist/commands/notes/export.js +26 -6
  36. package/dist/commands/notes/metadataMutation.d.ts +1 -0
  37. package/dist/commands/notes/metadataMutation.js +14 -3
  38. package/dist/commands/notes/repo.d.ts +46 -4
  39. package/dist/commands/notes/repo.js +113 -28
  40. package/dist/commands/notes/runtime.d.ts +2 -1
  41. package/dist/commands/notes/runtime.js +17 -1
  42. package/dist/commands/notes/searchSql.js +13 -6
  43. package/dist/commands/notes/writeReceipt.d.ts +20 -0
  44. package/dist/commands/notes/writeReceipt.js +82 -0
  45. package/dist/commands/prompt/index.js +5 -5
  46. package/dist/commands/prompt/repo.d.ts +4 -1
  47. package/dist/commands/prompt/repo.js +17 -7
  48. package/dist/commands/runtime.d.ts +22 -2
  49. package/dist/commands/runtime.js +109 -16
  50. package/dist/commands/schema/globalOptions.d.ts +1 -0
  51. package/dist/commands/schema/globalOptions.js +1 -0
  52. package/dist/commands/schema/renderSkills.js +6 -2
  53. package/dist/commands/schema/spec.js +4 -0
  54. package/dist/commands/storage/index.js +10 -4
  55. package/dist/commands/storage/repo.d.ts +4 -0
  56. package/dist/commands/storage/repo.js +356 -92
  57. package/dist/commands/sync.js +33 -14
  58. package/dist/commands/tags/content.js +9 -0
  59. package/dist/commands/tags/index.js +4 -8
  60. package/dist/commands/tags/operations.js +15 -10
  61. package/dist/commands/tags/repo.d.ts +2 -0
  62. package/dist/commands/tags/repo.js +18 -5
  63. package/dist/commands/tags/store.js +1 -1
  64. package/dist/commands/todo/contentModel.d.ts +4 -3
  65. package/dist/commands/todo/contentModel.js +78 -28
  66. package/dist/commands/todo/dryRun.d.ts +1 -0
  67. package/dist/commands/todo/dryRun.js +17 -13
  68. package/dist/commands/todo/index.d.ts +6 -0
  69. package/dist/commands/todo/index.js +91 -125
  70. package/dist/commands/todo/repo.d.ts +36 -1
  71. package/dist/commands/todo/repo.js +146 -37
  72. package/dist/commands/todo/taskIdentity.d.ts +2 -0
  73. package/dist/commands/todo/taskIdentity.js +64 -0
  74. package/dist/commands/todo/taskModel.js +27 -17
  75. package/dist/commands/update.d.ts +13 -1
  76. package/dist/commands/update.js +135 -23
  77. package/dist/commands/writeDryRun.d.ts +3 -2
  78. package/dist/commands/writeDryRun.js +4 -3
  79. package/dist/commands/writeDurability.d.ts +5 -0
  80. package/dist/commands/writeDurability.js +22 -0
  81. package/dist/config/paths.js +6 -1
  82. package/dist/config/resolve.d.ts +2 -0
  83. package/dist/config/resolve.js +23 -9
  84. package/dist/config/store.d.ts +3 -0
  85. package/dist/config/store.js +61 -14
  86. package/dist/config/types.d.ts +4 -0
  87. package/dist/daemon/client.d.ts +26 -1
  88. package/dist/daemon/client.js +236 -12
  89. package/dist/daemon/ensure.js +74 -12
  90. package/dist/daemon/index.d.ts +42 -0
  91. package/dist/daemon/index.js +202 -52
  92. package/dist/daemon/lifecycle.d.ts +42 -0
  93. package/dist/daemon/lifecycle.js +115 -0
  94. package/dist/daemon/pid.d.ts +19 -6
  95. package/dist/daemon/pid.js +147 -13
  96. package/dist/daemon/route.d.ts +14 -0
  97. package/dist/daemon/route.js +40 -0
  98. package/dist/daemon/server.d.ts +11 -0
  99. package/dist/daemon/server.js +201 -50
  100. package/dist/daemon/types.d.ts +29 -1
  101. package/dist/daemon/types.js +1 -0
  102. package/dist/dinox.js +25 -1
  103. package/dist/powersync/connector.js +31 -16
  104. package/dist/powersync/exclusiveRuntime.d.ts +19 -0
  105. package/dist/powersync/exclusiveRuntime.js +42 -0
  106. package/dist/powersync/runtime.d.ts +24 -4
  107. package/dist/powersync/runtime.js +236 -32
  108. package/dist/powersync/syncGate.d.ts +26 -0
  109. package/dist/powersync/syncGate.js +42 -0
  110. package/dist/powersync/tokenIndex.d.ts +11 -0
  111. package/dist/powersync/tokenIndex.js +159 -22
  112. package/dist/powersync/uploader.js +107 -52
  113. package/dist/utils/abort.js +0 -4
  114. package/dist/utils/asyncRelease.d.ts +2 -0
  115. package/dist/utils/asyncRelease.js +25 -0
  116. package/dist/utils/bidlinkCanonicalization.js +3 -0
  117. package/dist/utils/dryRun.d.ts +1 -1
  118. package/dist/utils/errors.d.ts +15 -1
  119. package/dist/utils/errors.js +223 -5
  120. package/dist/utils/output.d.ts +1 -0
  121. package/dist/utils/output.js +51 -15
  122. package/dist/utils/redact.js +1 -1
  123. package/dist/utils/sqliteLease.d.ts +8 -0
  124. package/dist/utils/sqliteLease.js +59 -0
  125. package/dist/utils/syncTimeout.d.ts +7 -1
  126. package/dist/utils/syncTimeout.js +8 -7
  127. package/dist/utils/terminalSanitization.js +6 -2
  128. package/dist/utils/tiptapMarkdown.js +49 -3
  129. package/dist/utils/tiptapMarkdownSyntax.d.ts +9 -0
  130. package/dist/utils/tiptapMarkdownSyntax.js +249 -0
  131. package/dist/utils/tokenize.js +24 -2
  132. package/dist/utils/updateNotice.js +60 -12
  133. package/package.json +12 -10
  134. package/dist/commands/mcp/auth.d.ts +0 -4
  135. package/dist/commands/mcp/auth.js +0 -75
  136. package/dist/commands/mcp/index.d.ts +0 -2
  137. package/dist/commands/mcp/index.js +0 -86
  138. package/dist/commands/mcp/noteContentTools.d.ts +0 -222
  139. package/dist/commands/mcp/noteContentTools.js +0 -390
  140. package/dist/commands/mcp/server.d.ts +0 -34
  141. package/dist/commands/mcp/server.js +0 -424
  142. package/dist/commands/mcp/tools.d.ts +0 -2
  143. package/dist/commands/mcp/tools.js +0 -32
  144. package/dist/commands/mcp/types.d.ts +0 -39
  145. package/dist/commands/mcp/types.js +0 -1
  146. package/dist/utils/noteContentEngine.d.ts +0 -333
  147. package/dist/utils/noteContentEngine.js +0 -740
@@ -1,10 +1,14 @@
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 { withExclusivePowerSync } from '../powersync/exclusiveRuntime.js';
5
+ import { getStatusSnapshot, waitForIdleDataFlow, waitForLastSyncedAfter, waitForUploadQueueEmpty, } from '../powersync/runtime.js';
6
+ import { createSyncRequiredError, evaluateSyncGate } from '../powersync/syncGate.js';
5
7
  import { syncNoteTokenIndex } from '../powersync/tokenIndex.js';
6
- import { printHumanReadableLine } from '../utils/output.js';
8
+ import { DinoxError } from '../utils/errors.js';
9
+ import { printHumanReadableWarning } from '../utils/output.js';
7
10
  import { parseSyncTimeoutMs } from '../utils/syncTimeout.js';
11
+ import { parseWriteDurability } from './writeDurability.js';
8
12
  const PRE_SYNC_TIMEOUT_MS = 10_000;
9
13
  const POST_WRITE_IDLE_TIMEOUT_MS = 5_000;
10
14
  function isCommandExecutionContext(source) {
@@ -25,17 +29,34 @@ function resolveGlobals(source) {
25
29
  export function commandStale(runtime) {
26
30
  return runtime.offline ? true : runtime.stale;
27
31
  }
32
+ export function resolveWriteDurabilityForRuntime(runtime, value) {
33
+ const durability = parseWriteDurability(value);
34
+ if (runtime.offline && durability === 'uploaded') {
35
+ throw new DinoxError('--durability uploaded cannot be used with --offline', {
36
+ code: 'USAGE_ERROR',
37
+ suggestion: 'Remove --offline or use --durability local for local-only writes.',
38
+ recoverable: true,
39
+ });
40
+ }
41
+ return durability;
42
+ }
28
43
  export async function withCommandRuntime(source, run) {
44
+ const startedAtIso = new Date().toISOString();
29
45
  const globals = resolveGlobals(source);
30
46
  const rawConfig = await loadConfig();
31
47
  const baseConfig = resolveConfig(rawConfig);
32
48
  const timeoutMs = parseSyncTimeoutMs(globals.syncTimeout) ?? baseConfig.sync.timeoutMs;
33
49
  const offline = Boolean(globals.offline);
50
+ const requireSync = Boolean(globals.requireSync);
34
51
  const { config } = offline
35
52
  ? { config: baseConfig }
36
53
  : await hydrateResolvedAuthIdentity(baseConfig, { timeoutMs });
37
- const { db, stale } = await connectPowerSync({ config, offline, timeoutMs });
38
- try {
54
+ return withExclusivePowerSync({
55
+ config,
56
+ offline,
57
+ timeoutMs,
58
+ expectedTokenFingerprint: config.auth.tokenFingerprint ?? null,
59
+ }, async ({ db, stale }) => {
39
60
  return await run({
40
61
  db,
41
62
  config,
@@ -43,34 +64,106 @@ export async function withCommandRuntime(source, run) {
43
64
  offline,
44
65
  timeoutMs,
45
66
  stale,
67
+ requireSync,
68
+ sync: {
69
+ firstSyncStale: stale,
70
+ preSyncIdle: null,
71
+ recentSync: null,
72
+ gate: null,
73
+ startedAtIso,
74
+ },
46
75
  });
47
- }
48
- finally {
49
- await db.close().catch(() => undefined);
50
- }
76
+ });
51
77
  }
52
78
  export async function syncBeforeCommand(runtime, options = {}) {
53
79
  if (runtime.offline) {
80
+ if (runtime.requireSync) {
81
+ const gate = evaluateSyncGate({
82
+ offline: true,
83
+ firstSyncStale: runtime.sync.firstSyncStale,
84
+ idle: false,
85
+ recentSync: false,
86
+ status: {
87
+ connected: false,
88
+ hasSynced: false,
89
+ dataFlowStatus: {},
90
+ },
91
+ });
92
+ runtime.sync.gate = gate;
93
+ throw createSyncRequiredError(gate);
94
+ }
54
95
  return;
55
96
  }
56
- await waitForIdleDataFlow(runtime.db, Math.min(runtime.timeoutMs, options.timeoutMs ?? PRE_SYNC_TIMEOUT_MS));
97
+ const idle = await waitForIdleDataFlow(runtime.db, Math.min(runtime.timeoutMs, options.timeoutMs ?? PRE_SYNC_TIMEOUT_MS));
98
+ runtime.sync.preSyncIdle = idle;
99
+ const recentSync = runtime.requireSync
100
+ ? await waitForLastSyncedAfter(runtime.db, runtime.sync.startedAtIso, Math.min(runtime.timeoutMs, options.timeoutMs ?? PRE_SYNC_TIMEOUT_MS))
101
+ : true;
102
+ runtime.sync.recentSync = recentSync;
57
103
  if (options.syncTokenIndex) {
58
104
  await syncNoteTokenIndex(runtime.db);
59
105
  }
106
+ const status = getStatusSnapshot(runtime.db);
107
+ const gate = evaluateSyncGate({
108
+ offline: runtime.offline,
109
+ firstSyncStale: runtime.sync.firstSyncStale,
110
+ idle,
111
+ recentSync,
112
+ status,
113
+ });
114
+ runtime.sync.gate = gate;
115
+ runtime.stale = !gate.ok;
116
+ if (runtime.requireSync && !gate.ok) {
117
+ throw createSyncRequiredError(gate, status);
118
+ }
60
119
  }
61
120
  export async function waitForMutationUpload(runtime, options = {}) {
121
+ const requestedDurability = resolveWriteDurabilityForRuntime(runtime, options.durability);
62
122
  if (runtime.offline) {
63
- return;
123
+ return { durability: 'local', upload_queue_remaining: null };
64
124
  }
65
- await waitForIdleDataFlow(runtime.db, Math.min(runtime.timeoutMs, options.timeoutMs ?? POST_WRITE_IDLE_TIMEOUT_MS));
66
- }
67
- export function printUploadDisabledWarning(runtime, message = 'warning: upload disabled (powersync.uploadBaseUrl is unset); changes are local-only for now') {
68
- if (!runtime.offline && !runtime.config.powersync.uploadBaseUrl) {
69
- printHumanReadableLine(message);
125
+ const result = await waitForUploadQueueEmpty(runtime.db, Math.min(runtime.timeoutMs, options.timeoutMs
126
+ ?? (runtime.requireSync || requestedDurability === 'uploaded'
127
+ ? runtime.timeoutMs
128
+ : POST_WRITE_IDLE_TIMEOUT_MS)));
129
+ if (result.ok) {
130
+ return { durability: 'uploaded', upload_queue_remaining: 0 };
131
+ }
132
+ const uploadError = result.uploadError
133
+ ? { name: result.uploadError.name, message: result.uploadError.message }
134
+ : undefined;
135
+ const details = {
136
+ pendingCount: result.pendingCount,
137
+ timedOut: result.timedOut,
138
+ ...(uploadError ? { uploadError } : {}),
139
+ ...(result.queueError ? { queueError: result.queueError } : {}),
140
+ };
141
+ const message = result.queueError
142
+ ? `Changes were saved locally, but the upload queue could not be verified: ${result.queueError.message}`
143
+ : result.uploadError
144
+ ? `Changes were saved locally, but upload failed: ${result.uploadError.message}`
145
+ : `Changes were saved locally, but ${result.pendingCount ?? 'an unknown number of'} pending upload(s) did not finish before timeout.`;
146
+ if (runtime.requireSync || requestedDurability === 'uploaded') {
147
+ throw new DinoxError(message, {
148
+ code: 'UPLOAD_PENDING',
149
+ details,
150
+ suggestion: 'Check network connectivity and retry with a larger --sync-timeout.',
151
+ suggestedAction: {
152
+ command: 'dino doctor --fix --format json',
153
+ reason: 'Inspect sync health and wait for pending local uploads before retrying the write.',
154
+ },
155
+ recoverable: true,
156
+ });
70
157
  }
158
+ printHumanReadableWarning(`warning: ${message}`);
159
+ return {
160
+ durability: 'local',
161
+ upload_queue_remaining: result.pendingCount,
162
+ ...(uploadError ? { upload_error: uploadError } : {}),
163
+ };
71
164
  }
72
165
  export function printStaleCacheWarning(runtime, message = 'warning: sync timed out; local cache may be stale') {
73
166
  if (runtime.stale && !runtime.offline) {
74
- printHumanReadableLine(message);
167
+ printHumanReadableWarning(message);
75
168
  }
76
169
  }
@@ -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>'];
@@ -4,6 +4,7 @@ import { listCommandSchemas } from './spec.js';
4
4
  const SECTION_ORDER = [
5
5
  { title: 'Auth', paths: ['auth.login', 'auth.logout', 'auth.status'] },
6
6
  { title: 'Daemon', paths: ['daemon.start', 'daemon.status', 'daemon.restart', 'daemon.stop'] },
7
+ { title: 'Doctor', paths: ['doctor'] },
7
8
  { title: 'Sync', paths: ['sync'] },
8
9
  { title: 'Schema', paths: ['schema'] },
9
10
  { title: 'Update CLI', paths: ['update'] },
@@ -15,12 +16,14 @@ const SECTION_ORDER = [
15
16
  { title: 'Storage', paths: ['storage.list', 'storage.test', 'storage.upload', 'storage.stats'] },
16
17
  { title: 'Config', paths: ['config.get', 'config.set'] },
17
18
  { title: 'Info', paths: ['info'] },
19
+ { title: 'Metadata', paths: ['meta.stats', 'meta.schema'] },
18
20
  { title: 'Graph', paths: ['graph.backlinks', 'graph.outlinks', 'graph.related', 'graph.stats'] },
19
21
  ];
20
22
  const GLOBAL_OPTIONS = [
21
23
  ['`--format <yaml|json>`', 'Structured output format. Prefer `json` for agent and script integrations.'],
22
24
  ['`--json`', 'Legacy alias for machine-readable YAML output. Keep only for backward compatibility.'],
23
25
  ['`--offline`', 'Skip sync, use local cache only'],
26
+ ['`--require-sync`', 'Fail if the local PowerSync cache cannot be confirmed fresh before reading'],
24
27
  ['`--sync-timeout <ms>`', 'Override default 300000 ms (5 min) sync/connect timeout'],
25
28
  ['`--verbose`', 'Enable verbose logging'],
26
29
  ];
@@ -89,7 +92,7 @@ const GENERATED_COMMAND_SECTIONS = {
89
92
  'Use `--boxes` for public box filters.',
90
93
  'Prefer `--fields id,title,summary,tags,created_at,boxes,is_starred` when the user only needs search metadata.',
91
94
  'Use `note export` for backup or migration; Markdown exports include frontmatter, JSON exports preserve note metadata and content JSON.',
92
- 'Use `note content-read` immediately before `note patch`; the returned readToken, content hash, block index, outline, and resolved hashtag paths are the required edit context.',
95
+ 'Use `note content-read` immediately before `note patch`; the returned single-use readToken, content hash, block index, outline, and resolved hashtag paths are the required edit context. Run `content-read` again after every patch attempt.',
93
96
  '`--sql` remains storage-oriented and still uses the field name `zettel_boxes`.',
94
97
  ],
95
98
  },
@@ -112,7 +115,7 @@ const GENERATED_COMMAND_SECTIONS = {
112
115
  '`note update` is for explicit note ids and full metadata replacement. It is not an append command.',
113
116
  '`note tag` and `note move` are explicit-id incremental organizers: add/remove preserves existing values, replace overwrites the whole list.',
114
117
  '`note move` changes zettel box membership only; it does not move files or note content.',
115
- '`note patch` is only for structured content edits. Run `note content-read` immediately before it, pass `--read-token` for real writes, and use `--allow-protected-replace` only after reviewing protected blocks.',
118
+ '`note patch` is only for structured content edits. Run `note content-read` immediately before it and pass `--read-token` for real writes. The token is single-use and is consumed before any real patch attempt, so run `content-read` again after every failed attempt. Use `--allow-protected-replace` only after reviewing protected blocks.',
116
119
  '`note bulk` is for filter-based batch metadata organization; it never accepts `--sql`, and real writes require `--confirm --expected-count <n>`.',
117
120
  'Prefer `note star` / `note unstar` for pure starring changes, and `note update` when multiple fields change together.',
118
121
  'Run the same command with `--dry-run --format json` first.',
@@ -133,6 +136,7 @@ const GENERATED_COMMAND_SECTIONS = {
133
136
  notes: [
134
137
  'Run the same command with `--dry-run` first.',
135
138
  'For append, prefer explicit `--note-id` unless the user confirms the default target behavior.',
139
+ 'For update, pass the `note_id` returned by todo search as `--note-id` when available; broad task lookup is bounded and refuses truncated results. Legacy ids bind to the searched task snapshot, so search again after the note changes.',
136
140
  ],
137
141
  },
138
142
  };
@@ -4,8 +4,10 @@ import { commandSchemasFromGroupDescriptor } from '../descriptor/register.js';
4
4
  import { standaloneCommandSchemaFromDescriptor } from '../descriptor/register.js';
5
5
  import { configCommandGroup } from '../config/index.js';
6
6
  import { daemonCommandGroup } from '../../daemon/index.js';
7
+ import { doctorCommandDescriptor } from '../doctor.js';
7
8
  import { graphCommandGroup } from '../graph/index.js';
8
9
  import { infoCommandDescriptor } from '../info/index.js';
10
+ import { metaCommandGroup } from '../meta/index.js';
9
11
  import { noteCommandGroup } from '../notes/index.js';
10
12
  import { promptCommandGroup } from '../prompt/index.js';
11
13
  import { schemaCommandMetadata } from './commandMetadata.js';
@@ -18,10 +20,12 @@ export const COMMAND_SCHEMAS = [
18
20
  ...commandSchemasFromGroupDescriptor(authCommandGroup),
19
21
  ...commandSchemasFromGroupDescriptor(configCommandGroup),
20
22
  ...commandSchemasFromGroupDescriptor(daemonCommandGroup),
23
+ standaloneCommandSchemaFromDescriptor(doctorCommandDescriptor),
21
24
  standaloneCommandSchemaFromDescriptor(syncCommandDescriptor),
22
25
  standaloneCommandSchemaFromDescriptor({ ...schemaCommandMetadata, action: async () => undefined }),
23
26
  standaloneCommandSchemaFromDescriptor(infoCommandDescriptor),
24
27
  standaloneCommandSchemaFromDescriptor(updateCommandDescriptor),
28
+ ...commandSchemasFromGroupDescriptor(metaCommandGroup),
25
29
  ...commandSchemasFromGroupDescriptor(tagCommandGroup),
26
30
  ...commandSchemasFromGroupDescriptor(boxCommandGroup),
27
31
  ...commandSchemasFromGroupDescriptor(promptCommandGroup),
@@ -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
- import { commandStale, printStaleCacheWarning, printUploadDisabledWarning, syncBeforeCommand, waitForMutationUpload, withCommandRuntime, } from '../runtime.js';
8
+ import { commandStale, printStaleCacheWarning, 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);
@@ -24,6 +24,9 @@ async function runStorageUpload(context) {
24
24
  throw new DinoxError('File path is required. Use `dino storage upload <file>`.');
25
25
  }
26
26
  const options = context.options;
27
+ if (options.overwrite && !options.key?.trim()) {
28
+ throw new DinoxError('--overwrite requires an explicit --key.');
29
+ }
27
30
  const category = parseStorageUploadCategory(options.category ?? 'files');
28
31
  const structuredOutput = getRequestedStructuredOutputFormat() !== null;
29
32
  await withCommandRuntime(context, async (runtime) => {
@@ -49,6 +52,7 @@ async function runStorageUpload(context) {
49
52
  storageKey: plan.storageKey,
50
53
  storageUrl: plan.storageUrl,
51
54
  selectionSource: plan.selectionSource,
55
+ overwrite: options.overwrite === true,
52
56
  }));
53
57
  return;
54
58
  }
@@ -58,8 +62,8 @@ async function runStorageUpload(context) {
58
62
  category,
59
63
  storageId: options.storageId,
60
64
  key: options.key,
65
+ overwrite: options.overwrite,
61
66
  });
62
- printUploadDisabledWarning(runtime, 'warning: upload disabled (powersync.uploadBaseUrl is unset); resource metadata is local-only for now');
63
67
  await waitForMutationUpload(runtime);
64
68
  const payload = {
65
69
  ok: true,
@@ -219,6 +223,7 @@ export const storageCommandGroup = {
219
223
  { flags: '--storage-id <id>', description: 'Explicit storage config id (otherwise use active custom config)', type: 'string' },
220
224
  { flags: '--category <kind>', description: 'Upload category: images|audios|files|videos', type: 'string', defaultValue: 'files', enum: ['images', 'audios', 'files', 'videos'] },
221
225
  { flags: '--key <string>', description: 'Explicit object key override', type: 'string' },
226
+ { flags: '--overwrite', description: 'Replace existing objects addressed by an explicit --key', type: 'boolean' },
222
227
  { flags: '--dry-run', description: 'Preview the upload target and resource record without uploading', type: 'boolean' },
223
228
  ],
224
229
  schema: {
@@ -234,6 +239,7 @@ export const storageCommandGroup = {
234
239
  examples: [
235
240
  'dino storage upload ./report.pdf --format json --dry-run',
236
241
  'dino storage upload ./photo.jpg --category images --storage-id <id> --format json',
242
+ 'dino storage upload ./report.pdf --key imports/report.pdf --overwrite --format json',
237
243
  ],
238
244
  },
239
245
  action: runStorageUpload,
@@ -113,6 +113,7 @@ type UploadObjectInput = {
113
113
  key: string;
114
114
  contentType: string;
115
115
  contentLength: number;
116
+ overwrite: boolean;
116
117
  storage: StoredCustomStorageConfig;
117
118
  };
118
119
  type UploadObjectResult = {
@@ -124,6 +125,7 @@ type UploadDeps = {
124
125
  deleteObject?: (input: {
125
126
  bucket: string;
126
127
  key: string;
128
+ etag?: string | null;
127
129
  storage: StoredCustomStorageConfig;
128
130
  }) => Promise<void>;
129
131
  uploadBytes?: (input: {
@@ -131,6 +133,7 @@ type UploadDeps = {
131
133
  bucket: string;
132
134
  key: string;
133
135
  contentType: string;
136
+ overwrite?: boolean;
134
137
  storage: StoredCustomStorageConfig;
135
138
  }) => Promise<UploadObjectResult>;
136
139
  };
@@ -158,5 +161,6 @@ export declare function uploadFileToStorage(db: AbstractPowerSyncDatabase, input
158
161
  category: StorageUploadCategory;
159
162
  storageId?: string;
160
163
  key?: string;
164
+ overwrite?: boolean;
161
165
  }, deps?: UploadDeps): Promise<StorageUploadResult>;
162
166
  export {};