@dinoxx/dinox-cli 1.0.22 → 1.1.1

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 +33 -10
  2. package/dist/auth/userInfo.d.ts +2 -0
  3. package/dist/auth/userInfo.js +55 -14
  4. package/dist/cli.js +36 -2
  5. package/dist/commands/auth/index.d.ts +35 -0
  6. package/dist/commands/auth/index.js +382 -83
  7. package/dist/commands/boxes/index.js +2 -6
  8. package/dist/commands/boxes/model.d.ts +3 -0
  9. package/dist/commands/boxes/model.js +16 -1
  10. package/dist/commands/boxes/operations.js +18 -8
  11. package/dist/commands/boxes/repo.js +13 -11
  12. package/dist/commands/boxes/store.js +1 -1
  13. package/dist/commands/config/index.js +7 -5
  14. package/dist/commands/descriptor/register.js +9 -0
  15. package/dist/commands/doctor.d.ts +123 -0
  16. package/dist/commands/doctor.js +539 -0
  17. package/dist/commands/graph/index.d.ts +1 -1
  18. package/dist/commands/graph/index.js +35 -21
  19. package/dist/commands/graph/repo.js +92 -17
  20. package/dist/commands/meta/index.js +4 -4
  21. package/dist/commands/meta/repo.js +4 -3
  22. package/dist/commands/notes/actions.d.ts +1 -1
  23. package/dist/commands/notes/actions.js +121 -78
  24. package/dist/commands/notes/bulk.d.ts +1 -0
  25. package/dist/commands/notes/bulk.js +23 -17
  26. package/dist/commands/notes/contentEngine.js +69 -11
  27. package/dist/commands/notes/contentPatch.d.ts +4 -1
  28. package/dist/commands/notes/contentPatch.js +105 -41
  29. package/dist/commands/notes/contentPatchCommand.js +28 -11
  30. package/dist/commands/notes/descriptors.js +50 -20
  31. package/dist/commands/notes/dryRun.d.ts +1 -0
  32. package/dist/commands/notes/dryRun.js +4 -7
  33. package/dist/commands/notes/export.d.ts +6 -0
  34. package/dist/commands/notes/export.js +26 -6
  35. package/dist/commands/notes/metadataMutation.d.ts +1 -0
  36. package/dist/commands/notes/metadataMutation.js +14 -3
  37. package/dist/commands/notes/repo.d.ts +46 -4
  38. package/dist/commands/notes/repo.js +113 -28
  39. package/dist/commands/notes/runtime.d.ts +2 -1
  40. package/dist/commands/notes/runtime.js +17 -1
  41. package/dist/commands/notes/searchSql.js +13 -6
  42. package/dist/commands/notes/writeReceipt.d.ts +20 -0
  43. package/dist/commands/notes/writeReceipt.js +82 -0
  44. package/dist/commands/prompt/index.js +3 -3
  45. package/dist/commands/prompt/repo.d.ts +4 -1
  46. package/dist/commands/prompt/repo.js +17 -7
  47. package/dist/commands/runtime.d.ts +14 -2
  48. package/dist/commands/runtime.js +89 -19
  49. package/dist/commands/schema/renderSkills.js +5 -2
  50. package/dist/commands/schema/spec.js +4 -0
  51. package/dist/commands/storage/index.js +8 -2
  52. package/dist/commands/storage/repo.d.ts +4 -0
  53. package/dist/commands/storage/repo.js +356 -92
  54. package/dist/commands/sync.d.ts +23 -1
  55. package/dist/commands/sync.js +105 -60
  56. package/dist/commands/tags/content.js +9 -0
  57. package/dist/commands/tags/index.js +2 -6
  58. package/dist/commands/tags/operations.js +15 -10
  59. package/dist/commands/tags/repo.d.ts +2 -0
  60. package/dist/commands/tags/repo.js +18 -5
  61. package/dist/commands/tags/store.js +1 -1
  62. package/dist/commands/todo/contentModel.d.ts +4 -3
  63. package/dist/commands/todo/contentModel.js +78 -28
  64. package/dist/commands/todo/dryRun.d.ts +1 -0
  65. package/dist/commands/todo/dryRun.js +17 -13
  66. package/dist/commands/todo/index.d.ts +6 -0
  67. package/dist/commands/todo/index.js +89 -123
  68. package/dist/commands/todo/repo.d.ts +36 -1
  69. package/dist/commands/todo/repo.js +146 -37
  70. package/dist/commands/todo/taskIdentity.d.ts +2 -0
  71. package/dist/commands/todo/taskIdentity.js +64 -0
  72. package/dist/commands/todo/taskModel.js +27 -17
  73. package/dist/commands/update.d.ts +13 -1
  74. package/dist/commands/update.js +135 -23
  75. package/dist/commands/writeDryRun.d.ts +3 -2
  76. package/dist/commands/writeDryRun.js +4 -3
  77. package/dist/commands/writeDurability.d.ts +5 -0
  78. package/dist/commands/writeDurability.js +22 -0
  79. package/dist/config/paths.js +6 -1
  80. package/dist/config/resolve.d.ts +2 -0
  81. package/dist/config/resolve.js +23 -9
  82. package/dist/config/store.d.ts +3 -0
  83. package/dist/config/store.js +61 -14
  84. package/dist/config/types.d.ts +4 -0
  85. package/dist/daemon/client.d.ts +26 -1
  86. package/dist/daemon/client.js +236 -12
  87. package/dist/daemon/ensure.js +74 -12
  88. package/dist/daemon/index.d.ts +42 -0
  89. package/dist/daemon/index.js +202 -52
  90. package/dist/daemon/lifecycle.d.ts +44 -0
  91. package/dist/daemon/lifecycle.js +118 -0
  92. package/dist/daemon/pid.d.ts +19 -6
  93. package/dist/daemon/pid.js +147 -13
  94. package/dist/daemon/route.d.ts +14 -0
  95. package/dist/daemon/route.js +40 -0
  96. package/dist/daemon/server.d.ts +11 -0
  97. package/dist/daemon/server.js +201 -50
  98. package/dist/daemon/types.d.ts +29 -1
  99. package/dist/daemon/types.js +1 -0
  100. package/dist/dinox.js +25 -1
  101. package/dist/powersync/connector.js +31 -16
  102. package/dist/powersync/exclusiveRuntime.d.ts +20 -0
  103. package/dist/powersync/exclusiveRuntime.js +48 -0
  104. package/dist/powersync/runtime.d.ts +25 -4
  105. package/dist/powersync/runtime.js +255 -35
  106. package/dist/powersync/syncGate.d.ts +4 -1
  107. package/dist/powersync/syncGate.js +11 -2
  108. package/dist/powersync/syncPass.d.ts +2 -0
  109. package/dist/powersync/syncPass.js +15 -4
  110. package/dist/powersync/tokenIndex.d.ts +13 -0
  111. package/dist/powersync/tokenIndex.js +216 -37
  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 +14 -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,11 +1,15 @@
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, getStatusSnapshot, waitForIdleDataFlow } from '../powersync/runtime.js';
4
+ import { withExclusivePowerSync } from '../powersync/exclusiveRuntime.js';
5
+ import { getStatusSnapshot, waitForDownloadIdle, waitForLastSyncedAfter, waitForUploadQueueEmpty, } from '../powersync/runtime.js';
5
6
  import { createSyncRequiredError, evaluateSyncGate } from '../powersync/syncGate.js';
6
7
  import { syncNoteTokenIndex } from '../powersync/tokenIndex.js';
7
- import { printHumanReadableLine } from '../utils/output.js';
8
+ import { createTimeoutSignal } from '../utils/abort.js';
9
+ import { DinoxError } from '../utils/errors.js';
10
+ import { printHumanReadableWarning } from '../utils/output.js';
8
11
  import { parseSyncTimeoutMs } from '../utils/syncTimeout.js';
12
+ import { parseWriteDurability } from './writeDurability.js';
9
13
  const PRE_SYNC_TIMEOUT_MS = 10_000;
10
14
  const POST_WRITE_IDLE_TIMEOUT_MS = 5_000;
11
15
  function isCommandExecutionContext(source) {
@@ -26,7 +30,19 @@ function resolveGlobals(source) {
26
30
  export function commandStale(runtime) {
27
31
  return runtime.offline ? true : runtime.stale;
28
32
  }
33
+ export function resolveWriteDurabilityForRuntime(runtime, value) {
34
+ const durability = parseWriteDurability(value);
35
+ if (runtime.offline && durability === 'uploaded') {
36
+ throw new DinoxError('--durability uploaded cannot be used with --offline', {
37
+ code: 'USAGE_ERROR',
38
+ suggestion: 'Remove --offline or use --durability local for local-only writes.',
39
+ recoverable: true,
40
+ });
41
+ }
42
+ return durability;
43
+ }
29
44
  export async function withCommandRuntime(source, run) {
45
+ const startedAtIso = new Date().toISOString();
30
46
  const globals = resolveGlobals(source);
31
47
  const rawConfig = await loadConfig();
32
48
  const baseConfig = resolveConfig(rawConfig);
@@ -36,8 +52,12 @@ export async function withCommandRuntime(source, run) {
36
52
  const { config } = offline
37
53
  ? { config: baseConfig }
38
54
  : await hydrateResolvedAuthIdentity(baseConfig, { timeoutMs });
39
- const { db, stale } = await connectPowerSync({ config, offline, timeoutMs });
40
- try {
55
+ return withExclusivePowerSync({
56
+ config,
57
+ offline,
58
+ timeoutMs,
59
+ expectedTokenFingerprint: config.auth.tokenFingerprint ?? null,
60
+ }, async ({ db, stale }) => {
41
61
  return await run({
42
62
  db,
43
63
  config,
@@ -49,13 +69,12 @@ export async function withCommandRuntime(source, run) {
49
69
  sync: {
50
70
  firstSyncStale: stale,
51
71
  preSyncIdle: null,
72
+ recentSync: null,
52
73
  gate: null,
74
+ startedAtIso,
53
75
  },
54
76
  });
55
- }
56
- finally {
57
- await db.close().catch(() => undefined);
58
- }
77
+ });
59
78
  }
60
79
  export async function syncBeforeCommand(runtime, options = {}) {
61
80
  if (runtime.offline) {
@@ -64,7 +83,9 @@ export async function syncBeforeCommand(runtime, options = {}) {
64
83
  offline: true,
65
84
  firstSyncStale: runtime.sync.firstSyncStale,
66
85
  idle: false,
86
+ recentSync: false,
67
87
  status: {
88
+ connected: false,
68
89
  hasSynced: false,
69
90
  dataFlowStatus: {},
70
91
  },
@@ -74,16 +95,31 @@ export async function syncBeforeCommand(runtime, options = {}) {
74
95
  }
75
96
  return;
76
97
  }
77
- const idle = await waitForIdleDataFlow(runtime.db, Math.min(runtime.timeoutMs, options.timeoutMs ?? PRE_SYNC_TIMEOUT_MS));
78
- runtime.sync.preSyncIdle = idle;
98
+ const timeoutMs = Math.min(runtime.timeoutMs, options.timeoutMs ?? PRE_SYNC_TIMEOUT_MS);
99
+ const deadlineAtMs = Date.now() + timeoutMs;
100
+ const remainingTimeoutMs = () => Math.max(0, deadlineAtMs - Date.now());
101
+ const recentSync = runtime.requireSync
102
+ ? await waitForLastSyncedAfter(runtime.db, runtime.sync.startedAtIso, Math.max(1, remainingTimeoutMs()))
103
+ : true;
104
+ runtime.sync.recentSync = recentSync;
105
+ const downloadIdle = await waitForDownloadIdle(runtime.db, Math.max(1, remainingTimeoutMs()));
106
+ runtime.sync.preSyncIdle = downloadIdle;
107
+ let indexComplete;
79
108
  if (options.syncTokenIndex) {
80
- await syncNoteTokenIndex(runtime.db);
109
+ const indexTimeoutMs = remainingTimeoutMs();
110
+ const timeout = indexTimeoutMs > 0 ? createTimeoutSignal(indexTimeoutMs) : null;
111
+ const tokenIndex = await syncNoteTokenIndex(runtime.db, {
112
+ signal: timeout?.signal ?? AbortSignal.abort(),
113
+ }).finally(() => timeout?.cancel());
114
+ indexComplete = tokenIndex.complete;
81
115
  }
82
116
  const status = getStatusSnapshot(runtime.db);
83
117
  const gate = evaluateSyncGate({
84
118
  offline: runtime.offline,
85
119
  firstSyncStale: runtime.sync.firstSyncStale,
86
- idle,
120
+ idle: downloadIdle,
121
+ indexComplete,
122
+ recentSync,
87
123
  status,
88
124
  });
89
125
  runtime.sync.gate = gate;
@@ -93,18 +129,52 @@ export async function syncBeforeCommand(runtime, options = {}) {
93
129
  }
94
130
  }
95
131
  export async function waitForMutationUpload(runtime, options = {}) {
132
+ const requestedDurability = resolveWriteDurabilityForRuntime(runtime, options.durability);
96
133
  if (runtime.offline) {
97
- return;
134
+ return { durability: 'local', upload_queue_remaining: null };
98
135
  }
99
- await waitForIdleDataFlow(runtime.db, Math.min(runtime.timeoutMs, options.timeoutMs ?? POST_WRITE_IDLE_TIMEOUT_MS));
100
- }
101
- export function printUploadDisabledWarning(runtime, message = 'warning: upload disabled (powersync.uploadBaseUrl is unset); changes are local-only for now') {
102
- if (!runtime.offline && !runtime.config.powersync.uploadBaseUrl) {
103
- printHumanReadableLine(message);
136
+ const result = await waitForUploadQueueEmpty(runtime.db, Math.min(runtime.timeoutMs, options.timeoutMs
137
+ ?? (runtime.requireSync || requestedDurability === 'uploaded'
138
+ ? runtime.timeoutMs
139
+ : POST_WRITE_IDLE_TIMEOUT_MS)));
140
+ if (result.ok) {
141
+ return { durability: 'uploaded', upload_queue_remaining: 0 };
142
+ }
143
+ const uploadError = result.uploadError
144
+ ? { name: result.uploadError.name, message: result.uploadError.message }
145
+ : undefined;
146
+ const details = {
147
+ pendingCount: result.pendingCount,
148
+ timedOut: result.timedOut,
149
+ ...(uploadError ? { uploadError } : {}),
150
+ ...(result.queueError ? { queueError: result.queueError } : {}),
151
+ };
152
+ const message = result.queueError
153
+ ? `Changes were saved locally, but the upload queue could not be verified: ${result.queueError.message}`
154
+ : result.uploadError
155
+ ? `Changes were saved locally, but upload failed: ${result.uploadError.message}`
156
+ : `Changes were saved locally, but ${result.pendingCount ?? 'an unknown number of'} pending upload(s) did not finish before timeout.`;
157
+ if (runtime.requireSync || requestedDurability === 'uploaded') {
158
+ throw new DinoxError(message, {
159
+ code: 'UPLOAD_PENDING',
160
+ details,
161
+ suggestion: 'Check network connectivity and retry with a larger --sync-timeout.',
162
+ suggestedAction: {
163
+ command: 'dino doctor --fix --format json',
164
+ reason: 'Inspect sync health and wait for pending local uploads before retrying the write.',
165
+ },
166
+ recoverable: true,
167
+ });
104
168
  }
169
+ printHumanReadableWarning(`warning: ${message}`);
170
+ return {
171
+ durability: 'local',
172
+ upload_queue_remaining: result.pendingCount,
173
+ ...(uploadError ? { upload_error: uploadError } : {}),
174
+ };
105
175
  }
106
176
  export function printStaleCacheWarning(runtime, message = 'warning: sync timed out; local cache may be stale') {
107
177
  if (runtime.stale && !runtime.offline) {
108
- printHumanReadableLine(message);
178
+ printHumanReadableWarning(message);
109
179
  }
110
180
  }
@@ -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,6 +16,7 @@ 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 = [
@@ -90,7 +92,7 @@ const GENERATED_COMMAND_SECTIONS = {
90
92
  'Use `--boxes` for public box filters.',
91
93
  'Prefer `--fields id,title,summary,tags,created_at,boxes,is_starred` when the user only needs search metadata.',
92
94
  'Use `note export` for backup or migration; Markdown exports include frontmatter, JSON exports preserve note metadata and content JSON.',
93
- '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.',
94
96
  '`--sql` remains storage-oriented and still uses the field name `zettel_boxes`.',
95
97
  ],
96
98
  },
@@ -113,7 +115,7 @@ const GENERATED_COMMAND_SECTIONS = {
113
115
  '`note update` is for explicit note ids and full metadata replacement. It is not an append command.',
114
116
  '`note tag` and `note move` are explicit-id incremental organizers: add/remove preserves existing values, replace overwrites the whole list.',
115
117
  '`note move` changes zettel box membership only; it does not move files or note content.',
116
- '`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.',
117
119
  '`note bulk` is for filter-based batch metadata organization; it never accepts `--sql`, and real writes require `--confirm --expected-count <n>`.',
118
120
  'Prefer `note star` / `note unstar` for pure starring changes, and `note update` when multiple fields change together.',
119
121
  'Run the same command with `--dry-run --format json` first.',
@@ -134,6 +136,7 @@ const GENERATED_COMMAND_SECTIONS = {
134
136
  notes: [
135
137
  'Run the same command with `--dry-run` first.',
136
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.',
137
140
  ],
138
141
  },
139
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),
@@ -5,7 +5,7 @@ import { buildStorageTestDryRunPreview, buildStorageUploadDryRunPreview } from '
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
10
  const STORAGE_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...REQUIRE_SYNC_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
11
11
  async function runStorageList(context) {
@@ -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 {};