@thingd/cli 0.33.2 → 0.35.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.
@@ -2,4 +2,5 @@ import { type CliContext } from "./index.js";
2
2
  export declare function runExport(context: CliContext): Promise<void>;
3
3
  export declare function runImport(context: CliContext): Promise<void>;
4
4
  export declare function runSnapshot(context: CliContext): Promise<void>;
5
+ export declare function runBackup(context: CliContext): Promise<void>;
5
6
  //# sourceMappingURL=data-movement.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"data-movement.d.ts","sourceRoot":"","sources":["../src/data-movement.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,KAAK,UAAU,EAAwD,MAAM,YAAY,CAAC;AA+DnG,wBAAsB,SAAS,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAyClE;AAED,wBAAsB,SAAS,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAuElE;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAoGpE"}
1
+ {"version":3,"file":"data-movement.d.ts","sourceRoot":"","sources":["../src/data-movement.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,KAAK,UAAU,EAAwD,MAAM,YAAY,CAAC;AA+DnG,wBAAsB,SAAS,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAyClE;AAED,wBAAsB,SAAS,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAuElE;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAiHpE;AAED,wBAAsB,SAAS,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBlE"}
@@ -201,57 +201,81 @@ export async function runSnapshot(context) {
201
201
  if (snapshot.version !== "1.0.0") {
202
202
  throw new Error(`Unsupported snapshot version: ${snapshot.version}`);
203
203
  }
204
+ console.error("Restoring snapshot... (consider 'thingd backup --out pre-restore.db' first)");
204
205
  await withDb(context, async (db) => {
205
- // 1. Restore Collections (clear existing first for true restore)
206
- if (snapshot.collections) {
207
- for (const [colName, objects] of Object.entries(snapshot.collections)) {
208
- const currentObjs = await db.listObjects(colName);
209
- for (const obj of currentObjs) {
210
- await db.delete(colName, obj.id);
206
+ try {
207
+ // 1. Restore Collections (clear existing first for true restore)
208
+ if (snapshot.collections) {
209
+ for (const [colName, objects] of Object.entries(snapshot.collections)) {
210
+ const currentObjs = await db.listObjects(colName);
211
+ for (const obj of currentObjs) {
212
+ await db.delete(colName, obj.id);
213
+ }
214
+ for (const obj of objects) {
215
+ const cleanObj = { ...obj };
216
+ delete cleanObj.collection;
217
+ delete cleanObj.createdAt;
218
+ delete cleanObj.updatedAt;
219
+ delete cleanObj.version;
220
+ await db.put(colName, cleanObj);
221
+ }
211
222
  }
212
- for (const obj of objects) {
213
- const cleanObj = { ...obj };
214
- delete cleanObj.collection;
215
- delete cleanObj.createdAt;
216
- delete cleanObj.updatedAt;
217
- delete cleanObj.version;
218
- await db.put(colName, cleanObj);
219
- }
220
- }
221
- }
222
- // 2. Restore Events
223
- if (snapshot.events) {
224
- for (const ev of snapshot.events) {
225
- const cleanEv = { ...ev };
226
- delete cleanEv.id;
227
- delete cleanEv.createdAt;
228
- delete cleanEv.stream;
229
- await db.events.append(ev.stream, cleanEv);
230
223
  }
231
- }
232
- // 3. Restore Queues
233
- if (snapshot.queues) {
234
- for (const [qName, jobsData] of Object.entries(snapshot.queues)) {
235
- const queue = db.queue(qName);
236
- const { active, dead } = jobsData;
237
- for (const job of active) {
238
- await queue.push(job.payload, {
239
- idempotencyKey: job.id,
240
- maxAttempts: job.maxAttempts,
241
- });
224
+ // 2. Restore Events
225
+ if (snapshot.events) {
226
+ for (const ev of snapshot.events) {
227
+ const cleanEv = { ...ev };
228
+ delete cleanEv.id;
229
+ delete cleanEv.createdAt;
230
+ delete cleanEv.stream;
231
+ await db.events.append(ev.stream, cleanEv);
242
232
  }
243
- for (const job of dead) {
244
- await queue.push(job.payload, {
245
- idempotencyKey: job.id,
246
- maxAttempts: job.maxAttempts,
247
- });
233
+ }
234
+ // 3. Restore Queues
235
+ if (snapshot.queues) {
236
+ for (const [qName, jobsData] of Object.entries(snapshot.queues)) {
237
+ const queue = db.queue(qName);
238
+ const { active, dead } = jobsData;
239
+ for (const job of active) {
240
+ await queue.push(job.payload, {
241
+ idempotencyKey: job.id,
242
+ maxAttempts: job.maxAttempts,
243
+ });
244
+ }
245
+ for (const job of dead) {
246
+ await queue.push(job.payload, {
247
+ idempotencyKey: job.id,
248
+ maxAttempts: job.maxAttempts,
249
+ });
250
+ }
248
251
  }
249
252
  }
253
+ writeJson(context.stdout, { success: true, in: inPath }, context.pretty);
254
+ }
255
+ catch (err) {
256
+ const message = err instanceof Error ? err.message : String(err);
257
+ // Restore failed — recommend using backup for atomic recovery
258
+ console.error(`Restore failed: ${message}. Data may be partially restored.\n` +
259
+ `For atomic restore, use 'thingd backup --out backup.db' before making changes\n` +
260
+ `and copy the backup file to restore.`);
261
+ throw err;
250
262
  }
251
- writeJson(context.stdout, { success: true, in: inPath }, context.pretty);
252
263
  });
253
264
  }
254
265
  else {
255
266
  throw new Error(`Unknown snapshot command: ${subCommand}. Expected 'create' or 'restore'.`);
256
267
  }
257
268
  }
269
+ export async function runBackup(context) {
270
+ const outPath = requiredFlag(context.parsed, "out");
271
+ await withDb(context, async (db) => {
272
+ if (db.path === ":memory:") {
273
+ throw new Error("Cannot backup an in-memory database. Use a file-based SQLite database.");
274
+ }
275
+ db.backupTo(outPath);
276
+ const { statSync } = await import("node:fs");
277
+ const stats = statSync(outPath);
278
+ const sizeMb = (stats.size / (1024 * 1024)).toFixed(2);
279
+ console.log(`Backup created: ${outPath} (${sizeMb} MB)`);
280
+ });
281
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAQA,OAAO,EASL,MAAM,EACN,KAAK,YAAY,EAClB,MAAM,aAAa,CAAC;AAOrB,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAEjD,KAAK,YAAY,GAAG;IAClB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7B,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAsCF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAmEF,wBAAsB,MAAM,CAC1B,IAAI,WAAwB,EAC5B,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CA6EjB;AAkxBD,wBAAsB,MAAM,CAC1B,OAAO,EAAE,UAAU,EACnB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GACtC,OAAO,CAAC,IAAI,CAAC,CAcf;AA4BD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,UAAU,GAAG,iBAAiB,CA2BxE;AAiDD,wBAAgB,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjE;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE/E;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAGlF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMrE;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEnF;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMrF;AAyFD,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAEpF;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAElE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAQA,OAAO,EASL,MAAM,EACN,KAAK,YAAY,EAClB,MAAM,aAAa,CAAC;AAOrB,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAEjD,KAAK,YAAY,GAAG;IAClB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7B,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAsCF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAoEF,wBAAsB,MAAM,CAC1B,IAAI,WAAwB,EAC5B,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CA6EjB;AAwxBD,wBAAsB,MAAM,CAC1B,OAAO,EAAE,UAAU,EACnB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GACtC,OAAO,CAAC,IAAI,CAAC,CAcf;AA4BD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,UAAU,GAAG,iBAAiB,CA2BxE;AAiDD,wBAAgB,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjE;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE/E;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAGlF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMrE;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEnF;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMrF;AAyFD,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAEpF;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAElE"}
package/dist/index.js CHANGED
@@ -75,6 +75,7 @@ Usage:
75
75
  thingd import --collection <name> --in <path>
76
76
  thingd snapshot create --out <path>
77
77
  thingd snapshot restore --in <path>
78
+ thingd backup --out <path>
78
79
 
79
80
  Options:
80
81
  --url <url> remote thingd URL. Defaults to THINGD_URL
@@ -240,6 +241,11 @@ async function runCommand(context) {
240
241
  await runSnapshot(context);
241
242
  return;
242
243
  }
244
+ if (command === "backup") {
245
+ const { runBackup } = await import("./data-movement.js");
246
+ await runBackup(context);
247
+ return;
248
+ }
243
249
  throw new Error(`Unknown command: ${command}`);
244
250
  }
245
251
  async function runBench(context) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thingd/cli",
3
- "version": "0.33.2",
3
+ "version": "0.35.0",
4
4
  "description": "CLI, Interactive TUI Dashboard, and MCP server for thingd — a fast object-first data engine for applications and AI agents.",
5
5
  "type": "module",
6
6
  "author": "Sayan Mohsin",
@@ -44,7 +44,7 @@
44
44
  "cli-table3": "^0.6.5",
45
45
  "picocolors": "^1.1.1",
46
46
  "zod": "^4.4.3",
47
- "@thingd/sdk": "0.33.2"
47
+ "@thingd/sdk": "0.35.0"
48
48
  },
49
49
  "engines": {
50
50
  "node": ">=24.0.0"