@njdamstra/appwrite-utils-cli 1.11.4 → 1.11.5
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.
|
@@ -320,7 +320,7 @@ async function migrateOneAttribute(adapter, entry, cpEntry, checkpoint, checkpoi
|
|
|
320
320
|
MessageFormatter.info(` Creating backup attribute ${backupKey}...`, {
|
|
321
321
|
prefix: "Migrate",
|
|
322
322
|
});
|
|
323
|
-
await
|
|
323
|
+
await createAttributeIfNotExists(adapter, {
|
|
324
324
|
databaseId,
|
|
325
325
|
tableId: collectionId,
|
|
326
326
|
key: backupKey,
|
|
@@ -328,7 +328,7 @@ async function migrateOneAttribute(adapter, entry, cpEntry, checkpoint, checkpoi
|
|
|
328
328
|
size: entry.currentSize,
|
|
329
329
|
required: false, // always optional for backup
|
|
330
330
|
array: entry.isArray,
|
|
331
|
-
})
|
|
331
|
+
});
|
|
332
332
|
const available = await waitForAttribute(adapter, databaseId, collectionId, backupKey);
|
|
333
333
|
if (!available)
|
|
334
334
|
throw new Error(`Backup attribute ${backupKey} stuck`);
|
|
@@ -385,7 +385,7 @@ async function migrateOneAttribute(adapter, entry, cpEntry, checkpoint, checkpoi
|
|
|
385
385
|
if (entry.hasDefault && entry.defaultValue !== undefined) {
|
|
386
386
|
createParams.default = entry.defaultValue;
|
|
387
387
|
}
|
|
388
|
-
await
|
|
388
|
+
await createAttributeIfNotExists(adapter, createParams);
|
|
389
389
|
const available = await waitForAttribute(adapter, databaseId, collectionId, attributeKey);
|
|
390
390
|
if (!available)
|
|
391
391
|
throw new Error(`New attribute ${attributeKey} stuck after creation`);
|
|
@@ -507,7 +507,7 @@ async function verifyDataCopy(adapter, databaseId, collectionId, sourceKey, targ
|
|
|
507
507
|
for (const doc of docs) {
|
|
508
508
|
if (!(sourceKey in doc))
|
|
509
509
|
continue;
|
|
510
|
-
if (doc[sourceKey] !== doc[targetKey]) {
|
|
510
|
+
if (JSON.stringify(doc[sourceKey]) !== JSON.stringify(doc[targetKey])) {
|
|
511
511
|
throw new Error(`Verification failed: doc ${doc.$id} has ${sourceKey}=${JSON.stringify(doc[sourceKey])} but ${targetKey}=${JSON.stringify(doc[targetKey])}`);
|
|
512
512
|
}
|
|
513
513
|
}
|
|
@@ -515,7 +515,7 @@ async function verifyDataCopy(adapter, databaseId, collectionId, sourceKey, targ
|
|
|
515
515
|
// ────────────────────────────────────────────────────────
|
|
516
516
|
// Helper: wait for attribute to become available
|
|
517
517
|
// ────────────────────────────────────────────────────────
|
|
518
|
-
async function waitForAttribute(adapter, databaseId, collectionId, key, maxWaitMs =
|
|
518
|
+
async function waitForAttribute(adapter, databaseId, collectionId, key, maxWaitMs = 120_000) {
|
|
519
519
|
const start = Date.now();
|
|
520
520
|
const checkInterval = 2000;
|
|
521
521
|
while (Date.now() - start < maxWaitMs) {
|
|
@@ -726,6 +726,22 @@ function printDryRunSummary(plan) {
|
|
|
726
726
|
// ────────────────────────────────────────────────────────
|
|
727
727
|
// Utility
|
|
728
728
|
// ────────────────────────────────────────────────────────
|
|
729
|
+
async function createAttributeIfNotExists(adapter, params) {
|
|
730
|
+
try {
|
|
731
|
+
await tryAwaitWithRetry(() => adapter.createAttribute(params), 0, true);
|
|
732
|
+
}
|
|
733
|
+
catch (err) {
|
|
734
|
+
const code = err?.code || err?.originalError?.code;
|
|
735
|
+
const type = err?.originalError?.type || "";
|
|
736
|
+
if (code === 409 || type === "column_already_exists") {
|
|
737
|
+
MessageFormatter.info(` (backup attribute already exists, reusing)`, {
|
|
738
|
+
prefix: "Migrate",
|
|
739
|
+
});
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
throw err;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
729
745
|
function delay(ms) {
|
|
730
746
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
731
747
|
}
|
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.
|
|
4
|
+
"version": "1.11.5",
|
|
5
5
|
"main": "dist/main.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
@@ -458,17 +458,15 @@ async function migrateOneAttribute(
|
|
|
458
458
|
MessageFormatter.info(` Creating backup attribute ${backupKey}...`, {
|
|
459
459
|
prefix: "Migrate",
|
|
460
460
|
});
|
|
461
|
-
await
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
})
|
|
471
|
-
);
|
|
461
|
+
await createAttributeIfNotExists(adapter, {
|
|
462
|
+
databaseId,
|
|
463
|
+
tableId: collectionId,
|
|
464
|
+
key: backupKey,
|
|
465
|
+
type: "string", // backup keeps original type
|
|
466
|
+
size: entry.currentSize,
|
|
467
|
+
required: false, // always optional for backup
|
|
468
|
+
array: entry.isArray,
|
|
469
|
+
});
|
|
472
470
|
const available = await waitForAttribute(
|
|
473
471
|
adapter,
|
|
474
472
|
databaseId,
|
|
@@ -560,7 +558,7 @@ async function migrateOneAttribute(
|
|
|
560
558
|
createParams.default = entry.defaultValue;
|
|
561
559
|
}
|
|
562
560
|
|
|
563
|
-
await
|
|
561
|
+
await createAttributeIfNotExists(adapter, createParams as any);
|
|
564
562
|
const available = await waitForAttribute(
|
|
565
563
|
adapter,
|
|
566
564
|
databaseId,
|
|
@@ -756,7 +754,7 @@ async function verifyDataCopy(
|
|
|
756
754
|
const docs = res?.documents || res?.rows || [];
|
|
757
755
|
for (const doc of docs) {
|
|
758
756
|
if (!(sourceKey in doc)) continue;
|
|
759
|
-
if (doc[sourceKey] !== doc[targetKey]) {
|
|
757
|
+
if (JSON.stringify(doc[sourceKey]) !== JSON.stringify(doc[targetKey])) {
|
|
760
758
|
throw new Error(
|
|
761
759
|
`Verification failed: doc ${doc.$id} has ${sourceKey}=${JSON.stringify(doc[sourceKey])} but ${targetKey}=${JSON.stringify(doc[targetKey])}`
|
|
762
760
|
);
|
|
@@ -773,7 +771,7 @@ async function waitForAttribute(
|
|
|
773
771
|
databaseId: string,
|
|
774
772
|
collectionId: string,
|
|
775
773
|
key: string,
|
|
776
|
-
maxWaitMs: number =
|
|
774
|
+
maxWaitMs: number = 120_000
|
|
777
775
|
): Promise<boolean> {
|
|
778
776
|
const start = Date.now();
|
|
779
777
|
const checkInterval = 2000;
|
|
@@ -1081,6 +1079,25 @@ function printDryRunSummary(plan: MigrationPlan): void {
|
|
|
1081
1079
|
// Utility
|
|
1082
1080
|
// ────────────────────────────────────────────────────────
|
|
1083
1081
|
|
|
1082
|
+
async function createAttributeIfNotExists(
|
|
1083
|
+
adapter: DatabaseAdapter,
|
|
1084
|
+
params: Parameters<DatabaseAdapter["createAttribute"]>[0]
|
|
1085
|
+
): Promise<void> {
|
|
1086
|
+
try {
|
|
1087
|
+
await tryAwaitWithRetry(() => adapter.createAttribute(params), 0, true);
|
|
1088
|
+
} catch (err: any) {
|
|
1089
|
+
const code = err?.code || err?.originalError?.code;
|
|
1090
|
+
const type = err?.originalError?.type || "";
|
|
1091
|
+
if (code === 409 || type === "column_already_exists") {
|
|
1092
|
+
MessageFormatter.info(` (backup attribute already exists, reusing)`, {
|
|
1093
|
+
prefix: "Migrate",
|
|
1094
|
+
});
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
throw err;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1084
1101
|
function delay(ms: number): Promise<void> {
|
|
1085
1102
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1086
1103
|
}
|