@njdamstra/appwrite-utils-cli 1.11.3 → 1.11.4

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.
@@ -4,6 +4,7 @@ import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import inquirer from "inquirer";
6
6
  import chalk from "chalk";
7
+ import pLimit from "p-limit";
7
8
  import { MessageFormatter, tryAwaitWithRetry, } from "@njdamstra/appwrite-utils-helpers";
8
9
  import { ProgressManager } from "../shared/progressManager.js";
9
10
  import { MigrationPlanSchema, MigrationCheckpointSchema, suggestTargetType, generateBackupKey, } from "./migrateStringsTypes.js";
@@ -461,6 +462,7 @@ async function copyAttributeData(adapter, databaseId, collectionId, sourceKey, t
461
462
  title: ` Copy ${sourceKey} → ${targetKey}`,
462
463
  })
463
464
  : undefined;
465
+ const limit = pLimit(5);
464
466
  while (true) {
465
467
  const queries = [Query.limit(batchSize)];
466
468
  if (lastId)
@@ -469,34 +471,18 @@ async function copyAttributeData(adapter, databaseId, collectionId, sourceKey, t
469
471
  const docs = res?.documents || res?.rows || [];
470
472
  if (docs.length === 0)
471
473
  break;
472
- // Batch update: copy sourceKey → targetKey
473
- if (adapter.supportsBulkOperations() && adapter.bulkUpsertRows) {
474
- const rows = docs
475
- .filter((d) => d[sourceKey] !== undefined)
476
- .map((d) => ({
477
- id: d.$id,
478
- data: { [targetKey]: d[sourceKey] },
479
- }));
480
- if (rows.length > 0) {
481
- await tryAwaitWithRetry(() => adapter.bulkUpsertRows({
482
- databaseId,
483
- tableId: collectionId,
484
- rows,
485
- }));
486
- }
487
- }
488
- else {
489
- for (const doc of docs) {
490
- if (doc[sourceKey] === undefined)
491
- continue;
492
- await tryAwaitWithRetry(() => adapter.updateRow({
493
- databaseId,
494
- tableId: collectionId,
495
- id: doc.$id,
496
- data: { [targetKey]: doc[sourceKey] },
497
- }));
498
- }
499
- }
474
+ // Partial update: copy sourceKey → targetKey using updateRow (not bulkUpsert
475
+ // which requires complete document structure and fails on partial payloads)
476
+ const updatePromises = docs
477
+ .filter((d) => d[sourceKey] !== undefined)
478
+ .map((d) => limit(() => tryAwaitWithRetry(() => adapter.updateRow({
479
+ databaseId,
480
+ tableId: collectionId,
481
+ id: d.$id,
482
+ data: { [targetKey]: d[sourceKey] },
483
+ }), 0, true // throwError — surface 400s immediately
484
+ )));
485
+ await Promise.all(updatePromises);
500
486
  totalCopied += docs.length;
501
487
  lastId = docs[docs.length - 1].$id;
502
488
  progress?.update(totalCopied);
@@ -519,7 +505,7 @@ async function verifyDataCopy(adapter, databaseId, collectionId, sourceKey, targ
519
505
  }));
520
506
  const docs = res?.documents || res?.rows || [];
521
507
  for (const doc of docs) {
522
- if (doc[sourceKey] === undefined)
508
+ if (!(sourceKey in doc))
523
509
  continue;
524
510
  if (doc[sourceKey] !== doc[targetKey]) {
525
511
  throw new Error(`Verification failed: doc ${doc.$id} has ${sourceKey}=${JSON.stringify(doc[sourceKey])} but ${targetKey}=${JSON.stringify(doc[targetKey])}`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@njdamstra/appwrite-utils-cli",
3
3
  "description": "Appwrite Utility Functions to help with database management, data conversion, data import, migrations, and much more. Meant to be used as a CLI tool, I do not recommend installing this in frontend environments.",
4
- "version": "1.11.3",
4
+ "version": "1.11.4",
5
5
  "main": "dist/main.js",
6
6
  "type": "module",
7
7
  "repository": {
@@ -4,6 +4,7 @@ import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import inquirer from "inquirer";
6
6
  import chalk from "chalk";
7
+ import pLimit from "p-limit";
7
8
  import {
8
9
  type DatabaseAdapter,
9
10
  MessageFormatter,
@@ -688,6 +689,8 @@ async function copyAttributeData(
688
689
  })
689
690
  : undefined;
690
691
 
692
+ const limit = pLimit(5);
693
+
691
694
  while (true) {
692
695
  const queries: string[] = [Query.limit(batchSize)];
693
696
  if (lastId) queries.push(Query.cursorAfter(lastId));
@@ -699,37 +702,26 @@ async function copyAttributeData(
699
702
  const docs = res?.documents || res?.rows || [];
700
703
  if (docs.length === 0) break;
701
704
 
702
- // Batch update: copy sourceKey → targetKey
703
- if (adapter.supportsBulkOperations() && adapter.bulkUpsertRows) {
704
- const rows = docs
705
- .filter((d: any) => d[sourceKey] !== undefined)
706
- .map((d: any) => ({
707
- id: d.$id,
708
- data: { [targetKey]: d[sourceKey] },
709
- }));
710
-
711
- if (rows.length > 0) {
712
- await tryAwaitWithRetry(() =>
713
- adapter.bulkUpsertRows!({
714
- databaseId,
715
- tableId: collectionId,
716
- rows,
717
- })
718
- );
719
- }
720
- } else {
721
- for (const doc of docs) {
722
- if (doc[sourceKey] === undefined) continue;
723
- await tryAwaitWithRetry(() =>
724
- adapter.updateRow({
725
- databaseId,
726
- tableId: collectionId,
727
- id: doc.$id,
728
- data: { [targetKey]: doc[sourceKey] },
729
- })
730
- );
731
- }
732
- }
705
+ // Partial update: copy sourceKey → targetKey using updateRow (not bulkUpsert
706
+ // which requires complete document structure and fails on partial payloads)
707
+ const updatePromises = docs
708
+ .filter((d: any) => d[sourceKey] !== undefined)
709
+ .map((d: any) =>
710
+ limit(() =>
711
+ tryAwaitWithRetry(
712
+ () =>
713
+ adapter.updateRow({
714
+ databaseId,
715
+ tableId: collectionId,
716
+ id: d.$id,
717
+ data: { [targetKey]: d[sourceKey] },
718
+ }),
719
+ 0,
720
+ true // throwError — surface 400s immediately
721
+ )
722
+ )
723
+ );
724
+ await Promise.all(updatePromises);
733
725
 
734
726
  totalCopied += docs.length;
735
727
  lastId = docs[docs.length - 1].$id;
@@ -763,7 +755,7 @@ async function verifyDataCopy(
763
755
  );
764
756
  const docs = res?.documents || res?.rows || [];
765
757
  for (const doc of docs) {
766
- if (doc[sourceKey] === undefined) continue;
758
+ if (!(sourceKey in doc)) continue;
767
759
  if (doc[sourceKey] !== doc[targetKey]) {
768
760
  throw new Error(
769
761
  `Verification failed: doc ${doc.$id} has ${sourceKey}=${JSON.stringify(doc[sourceKey])} but ${targetKey}=${JSON.stringify(doc[targetKey])}`