appwrite-utils-cli 1.11.0 → 1.12.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 (250) hide show
  1. package/{src/adapters/index.ts → dist/adapters/index.d.ts} +0 -1
  2. package/dist/adapters/index.js +10 -0
  3. package/dist/backups/operations/bucketBackup.d.ts +19 -0
  4. package/dist/backups/operations/bucketBackup.js +197 -0
  5. package/dist/backups/operations/collectionBackup.d.ts +30 -0
  6. package/dist/backups/operations/collectionBackup.js +201 -0
  7. package/dist/backups/operations/comprehensiveBackup.d.ts +25 -0
  8. package/dist/backups/operations/comprehensiveBackup.js +238 -0
  9. package/dist/backups/schemas/bucketManifest.d.ts +93 -0
  10. package/dist/backups/schemas/bucketManifest.js +33 -0
  11. package/dist/backups/schemas/comprehensiveManifest.d.ts +108 -0
  12. package/dist/backups/schemas/comprehensiveManifest.js +32 -0
  13. package/dist/backups/tracking/centralizedTracking.d.ts +34 -0
  14. package/dist/backups/tracking/centralizedTracking.js +274 -0
  15. package/dist/cli/commands/configCommands.d.ts +8 -0
  16. package/dist/cli/commands/configCommands.js +210 -0
  17. package/dist/cli/commands/databaseCommands.d.ts +14 -0
  18. package/dist/cli/commands/databaseCommands.js +696 -0
  19. package/dist/cli/commands/functionCommands.d.ts +7 -0
  20. package/dist/cli/commands/functionCommands.js +330 -0
  21. package/dist/cli/commands/importFileCommands.d.ts +7 -0
  22. package/dist/cli/commands/importFileCommands.js +674 -0
  23. package/dist/cli/commands/schemaCommands.d.ts +7 -0
  24. package/dist/cli/commands/schemaCommands.js +169 -0
  25. package/dist/cli/commands/storageCommands.d.ts +5 -0
  26. package/dist/cli/commands/storageCommands.js +142 -0
  27. package/dist/cli/commands/transferCommands.d.ts +5 -0
  28. package/dist/cli/commands/transferCommands.js +382 -0
  29. package/dist/collections/columns.d.ts +13 -0
  30. package/dist/collections/columns.js +1339 -0
  31. package/dist/collections/indexes.d.ts +12 -0
  32. package/dist/collections/indexes.js +215 -0
  33. package/dist/collections/methods.d.ts +19 -0
  34. package/dist/collections/methods.js +605 -0
  35. package/dist/collections/tableOperations.d.ts +87 -0
  36. package/dist/collections/tableOperations.js +466 -0
  37. package/dist/collections/transferOperations.d.ts +8 -0
  38. package/dist/collections/transferOperations.js +411 -0
  39. package/dist/collections/wipeOperations.d.ts +17 -0
  40. package/dist/collections/wipeOperations.js +306 -0
  41. package/dist/databases/methods.d.ts +6 -0
  42. package/dist/databases/methods.js +35 -0
  43. package/dist/databases/setup.d.ts +5 -0
  44. package/dist/databases/setup.js +45 -0
  45. package/dist/examples/yamlTerminologyExample.d.ts +42 -0
  46. package/dist/examples/yamlTerminologyExample.js +272 -0
  47. package/dist/functions/deployments.d.ts +4 -0
  48. package/dist/functions/deployments.js +146 -0
  49. package/dist/functions/fnConfigDiscovery.d.ts +3 -0
  50. package/dist/functions/fnConfigDiscovery.js +108 -0
  51. package/dist/functions/methods.d.ts +16 -0
  52. package/dist/functions/methods.js +174 -0
  53. package/dist/init.d.ts +2 -0
  54. package/dist/init.js +57 -0
  55. package/dist/interactiveCLI.d.ts +36 -0
  56. package/dist/interactiveCLI.js +952 -0
  57. package/dist/main.d.ts +2 -0
  58. package/dist/main.js +1125 -0
  59. package/dist/migrations/afterImportActions.d.ts +17 -0
  60. package/dist/migrations/afterImportActions.js +305 -0
  61. package/dist/migrations/appwriteToX.d.ts +211 -0
  62. package/dist/migrations/appwriteToX.js +493 -0
  63. package/dist/migrations/comprehensiveTransfer.d.ts +147 -0
  64. package/dist/migrations/comprehensiveTransfer.js +1315 -0
  65. package/dist/migrations/dataLoader.d.ts +755 -0
  66. package/dist/migrations/dataLoader.js +1272 -0
  67. package/dist/migrations/importController.d.ts +25 -0
  68. package/dist/migrations/importController.js +283 -0
  69. package/dist/migrations/importDataActions.d.ts +50 -0
  70. package/dist/migrations/importDataActions.js +230 -0
  71. package/dist/migrations/relationships.d.ts +29 -0
  72. package/dist/migrations/relationships.js +203 -0
  73. package/dist/migrations/services/DataTransformationService.d.ts +55 -0
  74. package/dist/migrations/services/DataTransformationService.js +158 -0
  75. package/dist/migrations/services/FileHandlerService.d.ts +75 -0
  76. package/dist/migrations/services/FileHandlerService.js +236 -0
  77. package/dist/migrations/services/ImportOrchestrator.d.ts +99 -0
  78. package/dist/migrations/services/ImportOrchestrator.js +493 -0
  79. package/dist/migrations/services/RateLimitManager.d.ts +138 -0
  80. package/dist/migrations/services/RateLimitManager.js +279 -0
  81. package/dist/migrations/services/RelationshipResolver.d.ts +120 -0
  82. package/dist/migrations/services/RelationshipResolver.js +332 -0
  83. package/dist/migrations/services/UserMappingService.d.ts +109 -0
  84. package/dist/migrations/services/UserMappingService.js +277 -0
  85. package/dist/migrations/services/ValidationService.d.ts +74 -0
  86. package/dist/migrations/services/ValidationService.js +260 -0
  87. package/dist/migrations/transfer.d.ts +30 -0
  88. package/dist/migrations/transfer.js +661 -0
  89. package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +131 -0
  90. package/dist/migrations/yaml/YamlImportConfigLoader.js +383 -0
  91. package/dist/migrations/yaml/YamlImportIntegration.d.ts +93 -0
  92. package/dist/migrations/yaml/YamlImportIntegration.js +341 -0
  93. package/dist/migrations/yaml/generateImportSchemas.d.ts +30 -0
  94. package/dist/migrations/yaml/generateImportSchemas.js +1327 -0
  95. package/dist/schemas/authUser.d.ts +24 -0
  96. package/dist/schemas/authUser.js +17 -0
  97. package/dist/setup.d.ts +2 -0
  98. package/{src/setup.ts → dist/setup.js} +0 -3
  99. package/dist/setupCommands.d.ts +58 -0
  100. package/dist/setupCommands.js +489 -0
  101. package/dist/setupController.d.ts +9 -0
  102. package/dist/setupController.js +34 -0
  103. package/dist/shared/backupMetadataSchema.d.ts +94 -0
  104. package/dist/shared/backupMetadataSchema.js +38 -0
  105. package/dist/shared/backupTracking.d.ts +18 -0
  106. package/dist/shared/backupTracking.js +176 -0
  107. package/dist/shared/confirmationDialogs.d.ts +75 -0
  108. package/dist/shared/confirmationDialogs.js +236 -0
  109. package/dist/shared/migrationHelpers.d.ts +61 -0
  110. package/dist/shared/migrationHelpers.js +145 -0
  111. package/{src/shared/operationLogger.ts → dist/shared/operationLogger.d.ts} +1 -11
  112. package/dist/shared/operationLogger.js +12 -0
  113. package/dist/shared/operationQueue.d.ts +40 -0
  114. package/dist/shared/operationQueue.js +310 -0
  115. package/dist/shared/operationsTable.d.ts +26 -0
  116. package/dist/shared/operationsTable.js +287 -0
  117. package/dist/shared/operationsTableSchema.d.ts +48 -0
  118. package/dist/shared/operationsTableSchema.js +35 -0
  119. package/dist/shared/progressManager.d.ts +62 -0
  120. package/dist/shared/progressManager.js +215 -0
  121. package/dist/shared/relationshipExtractor.d.ts +56 -0
  122. package/dist/shared/relationshipExtractor.js +138 -0
  123. package/dist/shared/selectionDialogs.d.ts +220 -0
  124. package/dist/shared/selectionDialogs.js +588 -0
  125. package/dist/storage/backupCompression.d.ts +20 -0
  126. package/dist/storage/backupCompression.js +67 -0
  127. package/dist/storage/methods.d.ts +44 -0
  128. package/dist/storage/methods.js +475 -0
  129. package/dist/storage/schemas.d.ts +842 -0
  130. package/dist/storage/schemas.js +175 -0
  131. package/dist/tables/indexManager.d.ts +65 -0
  132. package/dist/tables/indexManager.js +294 -0
  133. package/{src/types.ts → dist/types.d.ts} +1 -6
  134. package/dist/types.js +3 -0
  135. package/dist/users/methods.d.ts +16 -0
  136. package/dist/users/methods.js +276 -0
  137. package/dist/utils/configMigration.d.ts +1 -0
  138. package/dist/utils/configMigration.js +261 -0
  139. package/dist/utils/index.js +2 -0
  140. package/dist/utils/loadConfigs.d.ts +50 -0
  141. package/dist/utils/loadConfigs.js +357 -0
  142. package/dist/utils/setupFiles.d.ts +4 -0
  143. package/dist/utils/setupFiles.js +1190 -0
  144. package/dist/utilsController.d.ts +114 -0
  145. package/dist/utilsController.js +898 -0
  146. package/package.json +6 -3
  147. package/CHANGELOG.md +0 -35
  148. package/CONFIG_TODO.md +0 -1189
  149. package/SELECTION_DIALOGS.md +0 -146
  150. package/SERVICE_IMPLEMENTATION_REPORT.md +0 -462
  151. package/scripts/copy-templates.ts +0 -23
  152. package/src/backups/operations/bucketBackup.ts +0 -277
  153. package/src/backups/operations/collectionBackup.ts +0 -310
  154. package/src/backups/operations/comprehensiveBackup.ts +0 -342
  155. package/src/backups/schemas/bucketManifest.ts +0 -78
  156. package/src/backups/schemas/comprehensiveManifest.ts +0 -76
  157. package/src/backups/tracking/centralizedTracking.ts +0 -352
  158. package/src/cli/commands/configCommands.ts +0 -265
  159. package/src/cli/commands/databaseCommands.ts +0 -931
  160. package/src/cli/commands/functionCommands.ts +0 -419
  161. package/src/cli/commands/importFileCommands.ts +0 -815
  162. package/src/cli/commands/schemaCommands.ts +0 -200
  163. package/src/cli/commands/storageCommands.ts +0 -151
  164. package/src/cli/commands/transferCommands.ts +0 -454
  165. package/src/collections/attributes.ts.backup +0 -1555
  166. package/src/collections/columns.ts +0 -2025
  167. package/src/collections/indexes.ts +0 -350
  168. package/src/collections/methods.ts +0 -714
  169. package/src/collections/tableOperations.ts +0 -542
  170. package/src/collections/transferOperations.ts +0 -589
  171. package/src/collections/wipeOperations.ts +0 -449
  172. package/src/databases/methods.ts +0 -49
  173. package/src/databases/setup.ts +0 -77
  174. package/src/examples/yamlTerminologyExample.ts +0 -346
  175. package/src/functions/deployments.ts +0 -221
  176. package/src/functions/fnConfigDiscovery.ts +0 -103
  177. package/src/functions/methods.ts +0 -284
  178. package/src/init.ts +0 -62
  179. package/src/interactiveCLI.ts +0 -1201
  180. package/src/main.ts +0 -1517
  181. package/src/migrations/afterImportActions.ts +0 -579
  182. package/src/migrations/appwriteToX.ts +0 -668
  183. package/src/migrations/comprehensiveTransfer.ts +0 -2285
  184. package/src/migrations/dataLoader.ts +0 -1729
  185. package/src/migrations/importController.ts +0 -440
  186. package/src/migrations/importDataActions.ts +0 -315
  187. package/src/migrations/relationships.ts +0 -333
  188. package/src/migrations/services/DataTransformationService.ts +0 -196
  189. package/src/migrations/services/FileHandlerService.ts +0 -311
  190. package/src/migrations/services/ImportOrchestrator.ts +0 -675
  191. package/src/migrations/services/RateLimitManager.ts +0 -363
  192. package/src/migrations/services/RelationshipResolver.ts +0 -461
  193. package/src/migrations/services/UserMappingService.ts +0 -345
  194. package/src/migrations/services/ValidationService.ts +0 -349
  195. package/src/migrations/transfer.ts +0 -1113
  196. package/src/migrations/yaml/YamlImportConfigLoader.ts +0 -439
  197. package/src/migrations/yaml/YamlImportIntegration.ts +0 -446
  198. package/src/migrations/yaml/generateImportSchemas.ts +0 -1354
  199. package/src/schemas/authUser.ts +0 -23
  200. package/src/setupCommands.ts +0 -602
  201. package/src/setupController.ts +0 -43
  202. package/src/shared/backupMetadataSchema.ts +0 -93
  203. package/src/shared/backupTracking.ts +0 -211
  204. package/src/shared/confirmationDialogs.ts +0 -327
  205. package/src/shared/migrationHelpers.ts +0 -232
  206. package/src/shared/operationQueue.ts +0 -376
  207. package/src/shared/operationsTable.ts +0 -338
  208. package/src/shared/operationsTableSchema.ts +0 -60
  209. package/src/shared/progressManager.ts +0 -278
  210. package/src/shared/relationshipExtractor.ts +0 -214
  211. package/src/shared/selectionDialogs.ts +0 -802
  212. package/src/storage/backupCompression.ts +0 -88
  213. package/src/storage/methods.ts +0 -711
  214. package/src/storage/schemas.ts +0 -205
  215. package/src/tables/indexManager.ts +0 -409
  216. package/src/types/node-appwrite-tablesdb.d.ts +0 -44
  217. package/src/users/methods.ts +0 -358
  218. package/src/utils/configMigration.ts +0 -348
  219. package/src/utils/loadConfigs.ts +0 -457
  220. package/src/utils/setupFiles.ts +0 -1236
  221. package/src/utilsController.ts +0 -1263
  222. package/tests/README.md +0 -497
  223. package/tests/adapters/AdapterFactory.test.ts +0 -277
  224. package/tests/integration/syncOperations.test.ts +0 -463
  225. package/tests/jest.config.js +0 -25
  226. package/tests/migration/configMigration.test.ts +0 -546
  227. package/tests/setup.ts +0 -62
  228. package/tests/testUtils.ts +0 -340
  229. package/tests/utils/loadConfigs.test.ts +0 -350
  230. package/tests/validation/configValidation.test.ts +0 -412
  231. package/tsconfig.json +0 -44
  232. /package/{src → dist}/functions/templates/count-docs-in-collection/README.md +0 -0
  233. /package/{src → dist}/functions/templates/count-docs-in-collection/src/main.ts +0 -0
  234. /package/{src → dist}/functions/templates/count-docs-in-collection/src/request.ts +0 -0
  235. /package/{src → dist}/functions/templates/hono-typescript/README.md +0 -0
  236. /package/{src → dist}/functions/templates/hono-typescript/src/adapters/request.ts +0 -0
  237. /package/{src → dist}/functions/templates/hono-typescript/src/adapters/response.ts +0 -0
  238. /package/{src → dist}/functions/templates/hono-typescript/src/app.ts +0 -0
  239. /package/{src → dist}/functions/templates/hono-typescript/src/context.ts +0 -0
  240. /package/{src → dist}/functions/templates/hono-typescript/src/main.ts +0 -0
  241. /package/{src → dist}/functions/templates/hono-typescript/src/middleware/appwrite.ts +0 -0
  242. /package/{src → dist}/functions/templates/typescript-node/README.md +0 -0
  243. /package/{src → dist}/functions/templates/typescript-node/src/context.ts +0 -0
  244. /package/{src → dist}/functions/templates/typescript-node/src/main.ts +0 -0
  245. /package/{src → dist}/functions/templates/uv/README.md +0 -0
  246. /package/{src → dist}/functions/templates/uv/pyproject.toml +0 -0
  247. /package/{src → dist}/functions/templates/uv/src/__init__.py +0 -0
  248. /package/{src → dist}/functions/templates/uv/src/context.py +0 -0
  249. /package/{src → dist}/functions/templates/uv/src/main.py +0 -0
  250. /package/{src/utils/index.ts → dist/utils/index.d.ts} +0 -0
@@ -0,0 +1,661 @@
1
+ import { converterFunctions, tryAwaitWithRetry } from "appwrite-utils";
2
+ import { Client, Databases, IndexType, Query, Storage, Users, } from "node-appwrite";
3
+ import { InputFile } from "node-appwrite/file";
4
+ import { getAppwriteClient } from "appwrite-utils-helpers";
5
+ // Legacy attribute helpers retained only for local-to-local flows if needed
6
+ import { parseAttribute } from "appwrite-utils";
7
+ import chalk from "chalk";
8
+ import { fetchAllCollections } from "../collections/methods.js";
9
+ import { MessageFormatter, mapToCreateAttributeParams } from "appwrite-utils-helpers";
10
+ import { ProgressManager } from "../shared/progressManager.js";
11
+ import { getClient, getAdapter } from "appwrite-utils-helpers";
12
+ import { diffTableColumns } from "../collections/tableOperations.js";
13
+ import {} from "appwrite-utils-helpers";
14
+ export const transferStorageLocalToLocal = async (storage, fromBucketId, toBucketId) => {
15
+ MessageFormatter.info(`Transferring files from ${fromBucketId} to ${toBucketId}`, { prefix: "Transfer" });
16
+ let lastFileId;
17
+ let fromFiles = await tryAwaitWithRetry(async () => await storage.listFiles(fromBucketId, [Query.limit(100)]));
18
+ const allFromFiles = fromFiles.files;
19
+ let numberOfFiles = 0;
20
+ const downloadFileWithRetry = async (bucketId, fileId) => {
21
+ let attempts = 3;
22
+ while (attempts > 0) {
23
+ try {
24
+ return await storage.getFileDownload(bucketId, fileId);
25
+ }
26
+ catch (error) {
27
+ MessageFormatter.error(`Error downloading file ${fileId}`, error instanceof Error ? error : new Error(String(error)), { prefix: "Transfer" });
28
+ attempts--;
29
+ if (attempts === 0)
30
+ throw error;
31
+ }
32
+ }
33
+ };
34
+ if (fromFiles.files.length < 100) {
35
+ for (const file of allFromFiles) {
36
+ const fileData = await tryAwaitWithRetry(async () => await downloadFileWithRetry(file.bucketId, file.$id));
37
+ if (!fileData) {
38
+ MessageFormatter.error(`Error downloading file ${file.$id}`, undefined, { prefix: "Transfer" });
39
+ continue;
40
+ }
41
+ const fileToCreate = InputFile.fromBuffer(new Uint8Array(fileData), file.name);
42
+ MessageFormatter.progress(`Creating file: ${file.name}`, {
43
+ prefix: "Transfer",
44
+ });
45
+ try {
46
+ await tryAwaitWithRetry(async () => await storage.createFile(toBucketId, file.$id, fileToCreate, file.$permissions));
47
+ }
48
+ catch (error) {
49
+ // File already exists, so we can skip it
50
+ continue;
51
+ }
52
+ numberOfFiles++;
53
+ }
54
+ }
55
+ else {
56
+ lastFileId = fromFiles.files[fromFiles.files.length - 1].$id;
57
+ while (lastFileId) {
58
+ const files = await tryAwaitWithRetry(async () => await storage.listFiles(fromBucketId, [
59
+ Query.limit(100),
60
+ Query.cursorAfter(lastFileId),
61
+ ]));
62
+ allFromFiles.push(...files.files);
63
+ if (files.files.length < 100) {
64
+ lastFileId = undefined;
65
+ }
66
+ else {
67
+ lastFileId = files.files[files.files.length - 1].$id;
68
+ }
69
+ }
70
+ for (const file of allFromFiles) {
71
+ const fileData = await tryAwaitWithRetry(async () => await downloadFileWithRetry(file.bucketId, file.$id));
72
+ if (!fileData) {
73
+ MessageFormatter.error(`Error downloading file ${file.$id}`, undefined, { prefix: "Transfer" });
74
+ continue;
75
+ }
76
+ const fileToCreate = InputFile.fromBuffer(new Uint8Array(fileData), file.name);
77
+ try {
78
+ await tryAwaitWithRetry(async () => await storage.createFile(toBucketId, file.$id, fileToCreate, file.$permissions));
79
+ }
80
+ catch (error) {
81
+ // File already exists, so we can skip it
82
+ MessageFormatter.warning(`File ${file.$id} already exists, skipping...`, { prefix: "Transfer" });
83
+ continue;
84
+ }
85
+ numberOfFiles++;
86
+ }
87
+ }
88
+ MessageFormatter.success(`Transferred ${numberOfFiles} files from ${fromBucketId} to ${toBucketId}`, { prefix: "Transfer" });
89
+ };
90
+ export const transferStorageLocalToRemote = async (localStorage, endpoint, projectId, apiKey, fromBucketId, toBucketId) => {
91
+ MessageFormatter.info(`Transferring files from current storage ${fromBucketId} to ${endpoint} bucket ${toBucketId}`, { prefix: "Transfer" });
92
+ const client = getAppwriteClient(endpoint, projectId, apiKey);
93
+ const remoteStorage = new Storage(client);
94
+ let numberOfFiles = 0;
95
+ let lastFileId;
96
+ let fromFiles = await tryAwaitWithRetry(async () => await localStorage.listFiles(fromBucketId, [Query.limit(100)]));
97
+ const allFromFiles = fromFiles.files;
98
+ if (fromFiles.files.length === 100) {
99
+ lastFileId = fromFiles.files[fromFiles.files.length - 1].$id;
100
+ while (lastFileId) {
101
+ const files = await tryAwaitWithRetry(async () => await localStorage.listFiles(fromBucketId, [
102
+ Query.limit(100),
103
+ Query.cursorAfter(lastFileId),
104
+ ]));
105
+ allFromFiles.push(...files.files);
106
+ if (files.files.length < 100) {
107
+ break;
108
+ }
109
+ lastFileId = files.files[files.files.length - 1].$id;
110
+ }
111
+ }
112
+ for (const file of allFromFiles) {
113
+ const fileData = await tryAwaitWithRetry(async () => await localStorage.getFileDownload(file.bucketId, file.$id));
114
+ const fileToCreate = InputFile.fromBuffer(new Uint8Array(fileData), file.name);
115
+ try {
116
+ await tryAwaitWithRetry(async () => await remoteStorage.createFile(toBucketId, file.$id, fileToCreate, file.$permissions));
117
+ }
118
+ catch (error) {
119
+ // File already exists, so we can skip it
120
+ MessageFormatter.warning(`File ${file.$id} already exists, skipping...`, {
121
+ prefix: "Transfer",
122
+ });
123
+ continue;
124
+ }
125
+ numberOfFiles++;
126
+ }
127
+ MessageFormatter.success(`Transferred ${numberOfFiles} files from ${fromBucketId} to ${toBucketId}`, { prefix: "Transfer" });
128
+ };
129
+ // Document transfer functions moved to collections/methods.ts with enhanced UX
130
+ // Remote document transfer functions moved to collections/methods.ts with enhanced UX
131
+ /**
132
+ * Transfers all tables/collections and documents from one local database to another local database.
133
+ * Uses the DatabaseAdapter for unified TablesDB / legacy support.
134
+ *
135
+ * @param {Databases} localDb - The local database instance (kept for signature compat).
136
+ * @param {string} fromDbId - The ID of the source database.
137
+ * @param {string} targetDbId - The ID of the target database.
138
+ * @param {string[]} collectionIds - Optional filter: specific table/collection IDs to transfer. undefined = all, empty array = none.
139
+ * @param {DatabaseAdapter} adapter - The database adapter (TablesDB or Legacy).
140
+ * @return {Promise<void>} A promise that resolves when the transfer is complete.
141
+ */
142
+ export const transferDatabaseLocalToLocal = async (localDb, fromDbId, targetDbId, collectionIds, adapter) => {
143
+ if (!adapter) {
144
+ throw new Error("DatabaseAdapter is required for transferDatabaseLocalToLocal");
145
+ }
146
+ const dbAdapter = adapter;
147
+ MessageFormatter.info(`Starting database transfer from ${fromDbId} to ${targetDbId} (mode: ${dbAdapter.getApiMode()})`, { prefix: "Transfer" });
148
+ // Get all tables/collections from source database via adapter
149
+ const sourceListRes = await dbAdapter.listTables({ databaseId: fromDbId, queries: [Query.limit(500)] });
150
+ let sourceTables = sourceListRes.tables || sourceListRes.collections || sourceListRes.data || [];
151
+ // Filter by collectionIds if provided (match by $id or by name)
152
+ if (collectionIds !== undefined) {
153
+ if (collectionIds.length === 0) {
154
+ MessageFormatter.info("No tables/collections selected for transfer, skipping.", { prefix: "Transfer" });
155
+ return;
156
+ }
157
+ const idSet = new Set(collectionIds);
158
+ sourceTables = sourceTables.filter((c) => idSet.has(c.$id) || idSet.has(c.name));
159
+ }
160
+ MessageFormatter.info(`Found ${sourceTables.length} tables/collections in source database`, { prefix: "Transfer" });
161
+ // Process each table/collection
162
+ for (const table of sourceTables) {
163
+ MessageFormatter.processing(`Processing table: ${table.name} (${table.$id})`, { prefix: "Transfer" });
164
+ try {
165
+ // Check if table exists in target via adapter
166
+ let targetTableId = table.$id;
167
+ let targetTableData;
168
+ try {
169
+ const existingRes = await dbAdapter.getTable({ databaseId: targetDbId, tableId: table.$id });
170
+ targetTableData = existingRes.data || (existingRes.tables && existingRes.tables[0]);
171
+ if (targetTableData) {
172
+ MessageFormatter.info(`Table ${table.name} exists in target database`, { prefix: "Transfer" });
173
+ // Update table if needed
174
+ const securityField = table.rowSecurity ?? table.documentSecurity ?? false;
175
+ const targetSecurity = targetTableData.rowSecurity ?? targetTableData.documentSecurity ?? false;
176
+ if (targetTableData.name !== table.name ||
177
+ JSON.stringify(targetTableData.$permissions) !== JSON.stringify(table.$permissions) ||
178
+ targetSecurity !== securityField ||
179
+ targetTableData.enabled !== table.enabled) {
180
+ await tryAwaitWithRetry(async () => dbAdapter.updateTable({
181
+ databaseId: targetDbId,
182
+ id: table.$id,
183
+ name: table.name,
184
+ permissions: table.$permissions,
185
+ documentSecurity: table.documentSecurity,
186
+ rowSecurity: table.rowSecurity,
187
+ enabled: table.enabled,
188
+ }));
189
+ MessageFormatter.success(`Table ${table.name} updated`, { prefix: "Transfer" });
190
+ }
191
+ }
192
+ }
193
+ catch {
194
+ // Table does not exist in target, create it
195
+ targetTableData = null;
196
+ }
197
+ if (!targetTableData) {
198
+ MessageFormatter.progress(`Creating table ${table.name} in target database...`, { prefix: "Transfer" });
199
+ const createRes = await tryAwaitWithRetry(async () => dbAdapter.createTable({
200
+ databaseId: targetDbId,
201
+ id: table.$id,
202
+ name: table.name,
203
+ permissions: table.$permissions,
204
+ documentSecurity: table.documentSecurity,
205
+ rowSecurity: table.rowSecurity,
206
+ enabled: table.enabled,
207
+ }));
208
+ targetTableData = createRes.data || (createRes.tables && createRes.tables[0]);
209
+ }
210
+ // Create attributes via adapter
211
+ MessageFormatter.info(`Creating attributes for ${table.name} via adapter...`, { prefix: 'Transfer' });
212
+ // Fetch existing attributes in target to skip already-created ones
213
+ const targetTableRes = await dbAdapter.getTable({ databaseId: targetDbId, tableId: targetTableId });
214
+ const targetInfo = targetTableRes.data || (targetTableRes.tables && targetTableRes.tables[0]);
215
+ const existingAttrs = targetInfo?.attributes || targetInfo?.columns || [];
216
+ const existingKeys = new Set(existingAttrs.map((a) => a.key || a.$id));
217
+ const uniformAttrs = (table.attributes || []).map((attr) => parseAttribute(attr));
218
+ const nonRel = uniformAttrs.filter((a) => a.type !== 'relationship');
219
+ for (const attr of nonRel) {
220
+ if (existingKeys.has(attr.key))
221
+ continue;
222
+ const params = mapToCreateAttributeParams(attr, { databaseId: targetDbId, tableId: targetTableId });
223
+ await dbAdapter.createAttribute(params);
224
+ await new Promise((r) => setTimeout(r, 150));
225
+ }
226
+ // Wait for attributes to become available
227
+ for (const attr of nonRel) {
228
+ const maxWait = 60000;
229
+ const start = Date.now();
230
+ let lastStatus = '';
231
+ while (Date.now() - start < maxWait) {
232
+ try {
233
+ const tableRes = await dbAdapter.getTable({ databaseId: targetDbId, tableId: targetTableId });
234
+ const tInfo = tableRes.data || (tableRes.tables && tableRes.tables[0]);
235
+ const attrs = tInfo?.attributes || tInfo?.columns || [];
236
+ const found = attrs.find((a) => a.key === attr.key);
237
+ if (found) {
238
+ if (found.status === 'available')
239
+ break;
240
+ if (found.status === 'failed' || found.status === 'stuck') {
241
+ throw new Error(found.error || `Attribute ${attr.key} failed`);
242
+ }
243
+ lastStatus = found.status;
244
+ }
245
+ await new Promise((r) => setTimeout(r, 2000));
246
+ }
247
+ catch {
248
+ await new Promise((r) => setTimeout(r, 2000));
249
+ }
250
+ }
251
+ if (Date.now() - start >= maxWait) {
252
+ MessageFormatter.warning(`Attribute ${attr.key} did not become available within 60s (last: ${lastStatus})`, { prefix: 'Transfer' });
253
+ }
254
+ }
255
+ // Relationship attributes
256
+ const rels = uniformAttrs.filter((a) => a.type === 'relationship');
257
+ for (const attr of rels) {
258
+ if (existingKeys.has(attr.key))
259
+ continue;
260
+ const params = mapToCreateAttributeParams(attr, { databaseId: targetDbId, tableId: targetTableId });
261
+ await dbAdapter.createAttribute(params);
262
+ await new Promise((r) => setTimeout(r, 150));
263
+ }
264
+ // Handle indexes via adapter (create or update)
265
+ for (const idx of (table.indexes || [])) {
266
+ try {
267
+ await dbAdapter.createIndex({
268
+ databaseId: targetDbId,
269
+ tableId: targetTableId,
270
+ key: idx.key,
271
+ type: idx.type,
272
+ attributes: idx.attributes,
273
+ orders: idx.orders || []
274
+ });
275
+ await new Promise((r) => setTimeout(r, 150));
276
+ MessageFormatter.success(`Index ${idx.key} created`, { prefix: 'Transfer' });
277
+ }
278
+ catch (e) {
279
+ // Try update path by deleting and recreating if necessary
280
+ try {
281
+ await dbAdapter.deleteIndex({ databaseId: targetDbId, tableId: targetTableId, key: idx.key });
282
+ await dbAdapter.createIndex({
283
+ databaseId: targetDbId,
284
+ tableId: targetTableId,
285
+ key: idx.key,
286
+ type: idx.type,
287
+ attributes: idx.attributes,
288
+ orders: idx.orders || []
289
+ });
290
+ await new Promise((r) => setTimeout(r, 150));
291
+ MessageFormatter.info(`Index ${idx.key} recreated`, { prefix: 'Transfer' });
292
+ }
293
+ catch (e2) {
294
+ MessageFormatter.error(`Failed to ensure index ${idx.key}`, e2 instanceof Error ? e2 : new Error(String(e2)), { prefix: 'Transfer' });
295
+ }
296
+ }
297
+ }
298
+ // Transfer documents/rows via adapter
299
+ const { transferDocumentsBetweenDbsLocalToLocal } = await import("../collections/transferOperations.js");
300
+ await transferDocumentsBetweenDbsLocalToLocal(dbAdapter, fromDbId, targetDbId, table.$id, targetTableId);
301
+ }
302
+ catch (error) {
303
+ MessageFormatter.error(`Error processing table ${table.name}`, error instanceof Error ? error : new Error(String(error)), { prefix: "Transfer" });
304
+ }
305
+ }
306
+ };
307
+ export const transferDatabaseLocalToRemote = async (localDb, endpoint, projectId, apiKey, fromDbId, toDbId, collectionIds) => {
308
+ const client = getAppwriteClient(endpoint, projectId, apiKey);
309
+ const remoteDb = new Databases(client);
310
+ // Get all collections from source database
311
+ let sourceCollections = await fetchAllCollections(fromDbId, localDb);
312
+ if (collectionIds && collectionIds.length > 0) {
313
+ const idSet = new Set(collectionIds);
314
+ sourceCollections = sourceCollections.filter((c) => idSet.has(c.$id));
315
+ }
316
+ MessageFormatter.info(`Found ${sourceCollections.length} collections in source database`, { prefix: "Transfer" });
317
+ // Process each collection
318
+ for (const collection of sourceCollections) {
319
+ MessageFormatter.processing(`Processing collection: ${collection.name} (${collection.$id})`, { prefix: "Transfer" });
320
+ try {
321
+ // Create or update collection in target
322
+ let targetCollection;
323
+ const existingCollection = await tryAwaitWithRetry(async () => remoteDb.listCollections(toDbId, [Query.equal("$id", collection.$id)]));
324
+ if (existingCollection.collections.length > 0) {
325
+ targetCollection = existingCollection.collections[0];
326
+ MessageFormatter.info(`Collection ${collection.name} exists in remote database`, { prefix: "Transfer" });
327
+ // Update collection if needed
328
+ if (targetCollection.name !== collection.name ||
329
+ targetCollection.$permissions !== collection.$permissions ||
330
+ targetCollection.documentSecurity !== collection.documentSecurity ||
331
+ targetCollection.enabled !== collection.enabled) {
332
+ targetCollection = await tryAwaitWithRetry(async () => remoteDb.updateCollection(toDbId, collection.$id, collection.name, collection.$permissions, collection.documentSecurity, collection.enabled));
333
+ MessageFormatter.success(`Collection ${collection.name} updated`, { prefix: "Transfer" });
334
+ }
335
+ }
336
+ else {
337
+ MessageFormatter.progress(`Creating collection ${collection.name} in remote database...`, { prefix: "Transfer" });
338
+ targetCollection = await tryAwaitWithRetry(async () => remoteDb.createCollection(toDbId, collection.$id, collection.name, collection.$permissions, collection.documentSecurity, collection.enabled));
339
+ }
340
+ // Create/Update attributes via adapter (prefer adapter for remote)
341
+ const { adapter: remoteAdapter } = await getAdapter(endpoint, projectId, apiKey, 'auto');
342
+ MessageFormatter.info(`Creating attributes for ${collection.name} via adapter...`, { prefix: 'Transfer' });
343
+ const uniformAttrs = collection.attributes.map((attr) => parseAttribute(attr));
344
+ const nonRel = uniformAttrs.filter((a) => a.type !== 'relationship');
345
+ if (nonRel.length > 0) {
346
+ const tableInfo = await remoteAdapter.getTable({ databaseId: toDbId, tableId: collection.$id });
347
+ const existingCols = tableInfo.columns || tableInfo.attributes || [];
348
+ const { toCreate, toUpdate } = diffTableColumns(existingCols, nonRel);
349
+ for (const a of toUpdate) {
350
+ const p = mapToCreateAttributeParams(a, { databaseId: toDbId, tableId: collection.$id });
351
+ await remoteAdapter.updateAttribute(p);
352
+ await new Promise((r) => setTimeout(r, 150));
353
+ }
354
+ for (const a of toCreate) {
355
+ const p = mapToCreateAttributeParams(a, { databaseId: toDbId, tableId: collection.$id });
356
+ await remoteAdapter.createAttribute(p);
357
+ await new Promise((r) => setTimeout(r, 150));
358
+ }
359
+ }
360
+ // Wait for non-relationship attributes to become available
361
+ for (const attr of nonRel) {
362
+ const maxWait = 60000;
363
+ const start = Date.now();
364
+ let lastStatus = '';
365
+ while (Date.now() - start < maxWait) {
366
+ try {
367
+ const tableRes = await remoteAdapter.getTable({ databaseId: toDbId, tableId: collection.$id });
368
+ const attrs = tableRes.attributes || tableRes.columns || [];
369
+ const found = attrs.find((a) => a.key === attr.key);
370
+ if (found) {
371
+ if (found.status === 'available')
372
+ break;
373
+ if (found.status === 'failed' || found.status === 'stuck') {
374
+ throw new Error(found.error || `Attribute ${attr.key} failed`);
375
+ }
376
+ lastStatus = found.status;
377
+ }
378
+ await new Promise((r) => setTimeout(r, 2000));
379
+ }
380
+ catch {
381
+ await new Promise((r) => setTimeout(r, 2000));
382
+ }
383
+ }
384
+ if (Date.now() - start >= maxWait) {
385
+ MessageFormatter.warning(`Attribute ${attr.key} did not become available within 60s (last: ${lastStatus})`, { prefix: 'Transfer' });
386
+ }
387
+ }
388
+ // Relationship attributes
389
+ const rels = uniformAttrs.filter((a) => a.type === 'relationship');
390
+ if (rels.length > 0) {
391
+ const tableInfo2 = await remoteAdapter.getTable({ databaseId: toDbId, tableId: collection.$id });
392
+ const existingCols2 = tableInfo2.columns || tableInfo2.attributes || [];
393
+ const { toCreate: rCreate, toUpdate: rUpdate } = diffTableColumns(existingCols2, rels);
394
+ for (const a of rUpdate) {
395
+ const p = mapToCreateAttributeParams(a, { databaseId: toDbId, tableId: collection.$id });
396
+ await remoteAdapter.updateAttribute(p);
397
+ await new Promise((r) => setTimeout(r, 150));
398
+ }
399
+ for (const a of rCreate) {
400
+ const p = mapToCreateAttributeParams(a, { databaseId: toDbId, tableId: collection.$id });
401
+ await remoteAdapter.createAttribute(p);
402
+ await new Promise((r) => setTimeout(r, 150));
403
+ }
404
+ }
405
+ // Handle indexes with enhanced status checking
406
+ MessageFormatter.info(`Creating indexes for collection ${collection.name} with enhanced monitoring...`, { prefix: "Transfer" });
407
+ // Create indexes via adapter
408
+ for (const idx of collection.indexes || []) {
409
+ try {
410
+ await remoteAdapter.createIndex({
411
+ databaseId: toDbId,
412
+ tableId: collection.$id,
413
+ key: idx.key,
414
+ type: idx.type,
415
+ attributes: idx.attributes,
416
+ orders: idx.orders || []
417
+ });
418
+ await new Promise((r) => setTimeout(r, 150));
419
+ }
420
+ catch (e) {
421
+ MessageFormatter.error(`Failed to create index ${idx.key}`, e instanceof Error ? e : new Error(String(e)), { prefix: 'Transfer' });
422
+ }
423
+ }
424
+ // Transfer documents
425
+ const { transferDocumentsBetweenDbsLocalToRemote } = await import("../collections/methods.js");
426
+ await transferDocumentsBetweenDbsLocalToRemote(localDb, endpoint, projectId, apiKey, fromDbId, toDbId, collection.$id, targetCollection.$id);
427
+ }
428
+ catch (error) {
429
+ MessageFormatter.error(`Error processing collection ${collection.name}`, error instanceof Error ? error : new Error(String(error)), { prefix: "Transfer" });
430
+ }
431
+ }
432
+ };
433
+ export const transferUsersLocalToRemote = async (localUsers, endpoint, projectId, apiKey) => {
434
+ MessageFormatter.info("Starting user transfer to remote instance...", { prefix: "Transfer" });
435
+ const client = getClient(endpoint, projectId, apiKey);
436
+ const remoteUsers = new Users(client);
437
+ let totalTransferred = 0;
438
+ let lastId;
439
+ while (true) {
440
+ const queries = [Query.limit(100)];
441
+ if (lastId) {
442
+ queries.push(Query.cursorAfter(lastId));
443
+ }
444
+ const usersList = await tryAwaitWithRetry(async () => localUsers.list(queries));
445
+ if (usersList.users.length === 0) {
446
+ break;
447
+ }
448
+ for (const user of usersList.users) {
449
+ try {
450
+ // Check if user already exists in remote
451
+ let remoteUser;
452
+ try {
453
+ remoteUser = await tryAwaitWithRetry(async () => remoteUsers.get(user.$id));
454
+ // If user exists, update only the differences
455
+ if (remoteUser) {
456
+ MessageFormatter.info(`User ${user.$id} exists, checking for updates...`, { prefix: "Transfer" });
457
+ let hasUpdates = false;
458
+ // Update name if different
459
+ if (remoteUser.name !== user.name) {
460
+ await tryAwaitWithRetry(async () => remoteUsers.updateName(user.$id, user.name));
461
+ MessageFormatter.success(`Updated name for user ${user.$id}`, { prefix: "Transfer" });
462
+ hasUpdates = true;
463
+ }
464
+ // Update email if different
465
+ if (remoteUser.email !== user.email) {
466
+ await tryAwaitWithRetry(async () => remoteUsers.updateEmail(user.$id, user.email));
467
+ MessageFormatter.success(`Updated email for user ${user.$id}`, { prefix: "Transfer" });
468
+ hasUpdates = true;
469
+ }
470
+ // Update phone if different
471
+ const normalizedLocalPhone = user.phone
472
+ ? converterFunctions.convertPhoneStringToUSInternational(user.phone)
473
+ : undefined;
474
+ if (remoteUser.phone !== normalizedLocalPhone) {
475
+ if (normalizedLocalPhone) {
476
+ await tryAwaitWithRetry(async () => remoteUsers.updatePhone(user.$id, normalizedLocalPhone));
477
+ }
478
+ MessageFormatter.success(`Updated phone for user ${user.$id}`, { prefix: "Transfer" });
479
+ hasUpdates = true;
480
+ }
481
+ // Update preferences if different
482
+ if (JSON.stringify(remoteUser.prefs) !== JSON.stringify(user.prefs)) {
483
+ await tryAwaitWithRetry(async () => remoteUsers.updatePrefs(user.$id, user.prefs));
484
+ MessageFormatter.success(`Updated preferences for user ${user.$id}`, { prefix: "Transfer" });
485
+ hasUpdates = true;
486
+ }
487
+ // Update labels if different
488
+ if (JSON.stringify(remoteUser.labels) !== JSON.stringify(user.labels)) {
489
+ await tryAwaitWithRetry(async () => remoteUsers.updateLabels(user.$id, user.labels));
490
+ MessageFormatter.success(`Updated labels for user ${user.$id}`, { prefix: "Transfer" });
491
+ hasUpdates = true;
492
+ }
493
+ // Update email verification if different
494
+ if (remoteUser.emailVerification !== user.emailVerification) {
495
+ await tryAwaitWithRetry(async () => remoteUsers.updateEmailVerification(user.$id, user.emailVerification));
496
+ MessageFormatter.success(`Updated email verification for user ${user.$id}`, { prefix: "Transfer" });
497
+ hasUpdates = true;
498
+ }
499
+ // Update phone verification if different
500
+ if (remoteUser.phoneVerification !== user.phoneVerification) {
501
+ await tryAwaitWithRetry(async () => remoteUsers.updatePhoneVerification(user.$id, user.phoneVerification));
502
+ MessageFormatter.success(`Updated phone verification for user ${user.$id}`, { prefix: "Transfer" });
503
+ hasUpdates = true;
504
+ }
505
+ // Update status if different
506
+ if (remoteUser.status !== user.status) {
507
+ await tryAwaitWithRetry(async () => remoteUsers.updateStatus(user.$id, user.status));
508
+ MessageFormatter.success(`Updated status for user ${user.$id}`, { prefix: "Transfer" });
509
+ hasUpdates = true;
510
+ }
511
+ if (!hasUpdates) {
512
+ MessageFormatter.info(`User ${user.$id} is already up to date, skipping...`, { prefix: "Transfer" });
513
+ }
514
+ else {
515
+ totalTransferred++;
516
+ MessageFormatter.success(`Updated user ${user.$id}`, { prefix: "Transfer" });
517
+ }
518
+ continue;
519
+ }
520
+ }
521
+ catch (error) {
522
+ // User doesn't exist, proceed with creation
523
+ }
524
+ const phone = user.phone
525
+ ? converterFunctions.convertPhoneStringToUSInternational(user.phone)
526
+ : undefined;
527
+ // Handle user creation based on hash type
528
+ if (user.hash && user.password) {
529
+ // User has a hashed password - recreate with proper hash method
530
+ const hashType = user.hash.toLowerCase();
531
+ const hashedPassword = user.password; // This is already hashed
532
+ const hashOptions = user.hashOptions || {};
533
+ try {
534
+ switch (hashType) {
535
+ case "argon2":
536
+ await tryAwaitWithRetry(async () => remoteUsers.createArgon2User(user.$id, user.email, hashedPassword, user.name));
537
+ break;
538
+ case "bcrypt":
539
+ await tryAwaitWithRetry(async () => remoteUsers.createBcryptUser(user.$id, user.email, hashedPassword, user.name));
540
+ break;
541
+ case "scrypt":
542
+ // Scrypt requires additional parameters from hashOptions
543
+ const salt = typeof hashOptions.salt === "string" ? hashOptions.salt : "";
544
+ const costCpu = typeof hashOptions.costCpu === "number"
545
+ ? hashOptions.costCpu
546
+ : 32768;
547
+ const costMemory = typeof hashOptions.costMemory === "number"
548
+ ? hashOptions.costMemory
549
+ : 14;
550
+ const costParallel = typeof hashOptions.costParallel === "number"
551
+ ? hashOptions.costParallel
552
+ : 1;
553
+ const length = typeof hashOptions.length === "number"
554
+ ? hashOptions.length
555
+ : 64;
556
+ // Warn if using default values due to missing hash options
557
+ if (!hashOptions.salt ||
558
+ typeof hashOptions.costCpu !== "number") {
559
+ MessageFormatter.warning(`User ${user.$id}: Using default Scrypt parameters due to missing hashOptions`, { prefix: "Transfer" });
560
+ }
561
+ await tryAwaitWithRetry(async () => remoteUsers.createScryptUser(user.$id, user.email, hashedPassword, salt, costCpu, costMemory, costParallel, length, user.name));
562
+ break;
563
+ case "scryptmodified":
564
+ // Scrypt Modified (Firebase) requires salt, separator, and signer key
565
+ const modSalt = typeof hashOptions.salt === "string" ? hashOptions.salt : "";
566
+ const saltSeparator = typeof hashOptions.saltSeparator === "string"
567
+ ? hashOptions.saltSeparator
568
+ : "";
569
+ const signerKey = typeof hashOptions.signerKey === "string"
570
+ ? hashOptions.signerKey
571
+ : "";
572
+ // Warn if critical parameters are missing
573
+ if (!hashOptions.salt ||
574
+ !hashOptions.saltSeparator ||
575
+ !hashOptions.signerKey) {
576
+ MessageFormatter.warning(`User ${user.$id}: Missing critical Scrypt Modified parameters in hashOptions`, { prefix: "Transfer" });
577
+ }
578
+ await tryAwaitWithRetry(async () => remoteUsers.createScryptModifiedUser(user.$id, user.email, hashedPassword, modSalt, saltSeparator, signerKey, user.name));
579
+ break;
580
+ case "md5":
581
+ await tryAwaitWithRetry(async () => remoteUsers.createMD5User(user.$id, user.email, hashedPassword, user.name));
582
+ break;
583
+ case "sha":
584
+ case "sha1":
585
+ case "sha256":
586
+ case "sha512":
587
+ // SHA variants - determine version from hash type
588
+ const getPasswordHashVersion = (hash) => {
589
+ switch (hash.toLowerCase()) {
590
+ case "sha1":
591
+ return "sha1";
592
+ case "sha256":
593
+ return "sha256";
594
+ case "sha512":
595
+ return "sha512";
596
+ default:
597
+ return "sha256"; // Default to SHA256
598
+ }
599
+ };
600
+ await tryAwaitWithRetry(async () => remoteUsers.createSHAUser(user.$id, user.email, hashedPassword, getPasswordHashVersion(hashType), user.name));
601
+ break;
602
+ case "phpass":
603
+ await tryAwaitWithRetry(async () => remoteUsers.createPHPassUser(user.$id, user.email, hashedPassword, user.name));
604
+ break;
605
+ default:
606
+ MessageFormatter.warning(`Unknown hash type '${hashType}' for user ${user.$id}, falling back to Argon2`, { prefix: "Transfer" });
607
+ await tryAwaitWithRetry(async () => remoteUsers.createArgon2User(user.$id, user.email, hashedPassword, user.name));
608
+ break;
609
+ }
610
+ MessageFormatter.success(`User ${user.$id} created with preserved ${hashType} password`, { prefix: "Transfer" });
611
+ }
612
+ catch (error) {
613
+ MessageFormatter.warning(`Failed to create user ${user.$id} with ${hashType} hash, trying with temporary password`, { prefix: "Transfer" });
614
+ // Fallback to creating user with temporary password
615
+ await tryAwaitWithRetry(async () => remoteUsers.create(user.$id, user.email, phone, `changeMe${user.email}`, user.name));
616
+ MessageFormatter.warning(`User ${user.$id} created with temporary password - password reset required`, { prefix: "Transfer" });
617
+ }
618
+ }
619
+ else {
620
+ // No hash or password - create with temporary password
621
+ const tempPassword = user.password || `changeMe${user.email}`;
622
+ await tryAwaitWithRetry(async () => remoteUsers.create(user.$id, user.email, phone, tempPassword, user.name));
623
+ if (!user.password) {
624
+ MessageFormatter.warning(`User ${user.$id} created with temporary password - password reset required`, { prefix: "Transfer" });
625
+ }
626
+ }
627
+ // Update phone, labels, and other attributes for newly created users
628
+ if (phone) {
629
+ await tryAwaitWithRetry(async () => remoteUsers.updatePhone(user.$id, phone));
630
+ }
631
+ if (user.labels && user.labels.length > 0) {
632
+ await tryAwaitWithRetry(async () => remoteUsers.updateLabels(user.$id, user.labels));
633
+ }
634
+ // Update user preferences and status for newly created users
635
+ await tryAwaitWithRetry(async () => remoteUsers.updatePrefs(user.$id, user.prefs));
636
+ if (user.emailVerification) {
637
+ await tryAwaitWithRetry(async () => remoteUsers.updateEmailVerification(user.$id, true));
638
+ }
639
+ else {
640
+ await tryAwaitWithRetry(async () => remoteUsers.updateEmailVerification(user.$id, false));
641
+ }
642
+ if (user.phoneVerification) {
643
+ await tryAwaitWithRetry(async () => remoteUsers.updatePhoneVerification(user.$id, true));
644
+ }
645
+ if (user.status === false) {
646
+ await tryAwaitWithRetry(async () => remoteUsers.updateStatus(user.$id, false));
647
+ }
648
+ totalTransferred++;
649
+ MessageFormatter.success(`Transferred user ${user.$id}`, { prefix: "Transfer" });
650
+ }
651
+ catch (error) {
652
+ MessageFormatter.error(`Failed to transfer user ${user.$id}`, error instanceof Error ? error : new Error(String(error)), { prefix: "Transfer" });
653
+ }
654
+ }
655
+ if (usersList.users.length < 100) {
656
+ break;
657
+ }
658
+ lastId = usersList.users[usersList.users.length - 1].$id;
659
+ }
660
+ MessageFormatter.success(`Successfully transferred ${totalTransferred} users`, { prefix: "Transfer" });
661
+ };