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
@@ -1,1113 +0,0 @@
1
- import { converterFunctions, tryAwaitWithRetry } from "appwrite-utils";
2
- import {
3
- Client,
4
- Databases,
5
- IndexType,
6
- Query,
7
- Storage,
8
- Users,
9
- type Models,
10
- } from "node-appwrite";
11
- import { InputFile } from "node-appwrite/file";
12
- import { getAppwriteClient } from "appwrite-utils-helpers";
13
- // Legacy attribute helpers retained only for local-to-local flows if needed
14
- import { parseAttribute } from "appwrite-utils";
15
- import chalk from "chalk";
16
- import { fetchAllCollections } from "../collections/methods.js";
17
- import { MessageFormatter, mapToCreateAttributeParams } from "appwrite-utils-helpers";
18
- import { ProgressManager } from "../shared/progressManager.js";
19
- import { getClient, getAdapter } from "appwrite-utils-helpers";
20
- import { diffTableColumns } from "../collections/tableOperations.js";
21
- import { type DatabaseAdapter } from "appwrite-utils-helpers";
22
-
23
- export interface TransferOptions {
24
- fromDb: Models.Database | undefined;
25
- targetDb: Models.Database | undefined;
26
- isRemote: boolean;
27
- collections?: string[];
28
- transferEndpoint?: string;
29
- transferProject?: string;
30
- transferKey?: string;
31
- sourceBucket?: Models.Bucket;
32
- targetBucket?: Models.Bucket;
33
- transferUsers?: boolean;
34
- }
35
-
36
- export const transferStorageLocalToLocal = async (
37
- storage: Storage,
38
- fromBucketId: string,
39
- toBucketId: string
40
- ) => {
41
- MessageFormatter.info(
42
- `Transferring files from ${fromBucketId} to ${toBucketId}`,
43
- { prefix: "Transfer" }
44
- );
45
- let lastFileId: string | undefined;
46
- let fromFiles = await tryAwaitWithRetry(
47
- async () => await storage.listFiles(fromBucketId, [Query.limit(100)])
48
- );
49
- const allFromFiles = fromFiles.files;
50
- let numberOfFiles = 0;
51
-
52
- const downloadFileWithRetry = async (bucketId: string, fileId: string) => {
53
- let attempts = 3;
54
- while (attempts > 0) {
55
- try {
56
- return await storage.getFileDownload(bucketId, fileId);
57
- } catch (error) {
58
- MessageFormatter.error(
59
- `Error downloading file ${fileId}`,
60
- error instanceof Error ? error : new Error(String(error)),
61
- { prefix: "Transfer" }
62
- );
63
- attempts--;
64
- if (attempts === 0) throw error;
65
- }
66
- }
67
- };
68
-
69
- if (fromFiles.files.length < 100) {
70
- for (const file of allFromFiles) {
71
- const fileData = await tryAwaitWithRetry(
72
- async () => await downloadFileWithRetry(file.bucketId, file.$id)
73
- );
74
- if (!fileData) {
75
- MessageFormatter.error(
76
- `Error downloading file ${file.$id}`,
77
- undefined,
78
- { prefix: "Transfer" }
79
- );
80
- continue;
81
- }
82
- const fileToCreate = InputFile.fromBuffer(
83
- new Uint8Array(fileData),
84
- file.name
85
- );
86
- MessageFormatter.progress(`Creating file: ${file.name}`, {
87
- prefix: "Transfer",
88
- });
89
- try {
90
- await tryAwaitWithRetry(
91
- async () =>
92
- await storage.createFile(
93
- toBucketId,
94
- file.$id,
95
- fileToCreate,
96
- file.$permissions
97
- )
98
- );
99
- } catch (error: any) {
100
- // File already exists, so we can skip it
101
- continue;
102
- }
103
- numberOfFiles++;
104
- }
105
- } else {
106
- lastFileId = fromFiles.files[fromFiles.files.length - 1].$id;
107
- while (lastFileId) {
108
- const files = await tryAwaitWithRetry(
109
- async () =>
110
- await storage.listFiles(fromBucketId, [
111
- Query.limit(100),
112
- Query.cursorAfter(lastFileId!),
113
- ])
114
- );
115
- allFromFiles.push(...files.files);
116
- if (files.files.length < 100) {
117
- lastFileId = undefined;
118
- } else {
119
- lastFileId = files.files[files.files.length - 1].$id;
120
- }
121
- }
122
- for (const file of allFromFiles) {
123
- const fileData = await tryAwaitWithRetry(
124
- async () => await downloadFileWithRetry(file.bucketId, file.$id)
125
- );
126
- if (!fileData) {
127
- MessageFormatter.error(
128
- `Error downloading file ${file.$id}`,
129
- undefined,
130
- { prefix: "Transfer" }
131
- );
132
- continue;
133
- }
134
- const fileToCreate = InputFile.fromBuffer(
135
- new Uint8Array(fileData),
136
- file.name
137
- );
138
- try {
139
- await tryAwaitWithRetry(
140
- async () =>
141
- await storage.createFile(
142
- toBucketId,
143
- file.$id,
144
- fileToCreate,
145
- file.$permissions
146
- )
147
- );
148
- } catch (error: any) {
149
- // File already exists, so we can skip it
150
- MessageFormatter.warning(
151
- `File ${file.$id} already exists, skipping...`,
152
- { prefix: "Transfer" }
153
- );
154
- continue;
155
- }
156
- numberOfFiles++;
157
- }
158
- }
159
-
160
- MessageFormatter.success(
161
- `Transferred ${numberOfFiles} files from ${fromBucketId} to ${toBucketId}`,
162
- { prefix: "Transfer" }
163
- );
164
- };
165
-
166
- export const transferStorageLocalToRemote = async (
167
- localStorage: Storage,
168
- endpoint: string,
169
- projectId: string,
170
- apiKey: string,
171
- fromBucketId: string,
172
- toBucketId: string
173
- ) => {
174
- MessageFormatter.info(
175
- `Transferring files from current storage ${fromBucketId} to ${endpoint} bucket ${toBucketId}`,
176
- { prefix: "Transfer" }
177
- );
178
- const client = getAppwriteClient(endpoint, projectId, apiKey);
179
- const remoteStorage = new Storage(client);
180
- let numberOfFiles = 0;
181
- let lastFileId: string | undefined;
182
- let fromFiles = await tryAwaitWithRetry(
183
- async () => await localStorage.listFiles(fromBucketId, [Query.limit(100)])
184
- );
185
- const allFromFiles = fromFiles.files;
186
- if (fromFiles.files.length === 100) {
187
- lastFileId = fromFiles.files[fromFiles.files.length - 1].$id;
188
- while (lastFileId) {
189
- const files = await tryAwaitWithRetry(
190
- async () =>
191
- await localStorage.listFiles(fromBucketId, [
192
- Query.limit(100),
193
- Query.cursorAfter(lastFileId!),
194
- ])
195
- );
196
- allFromFiles.push(...files.files);
197
- if (files.files.length < 100) {
198
- break;
199
- }
200
- lastFileId = files.files[files.files.length - 1].$id;
201
- }
202
- }
203
-
204
- for (const file of allFromFiles) {
205
- const fileData = await tryAwaitWithRetry(
206
- async () => await localStorage.getFileDownload(file.bucketId, file.$id)
207
- );
208
- const fileToCreate = InputFile.fromBuffer(
209
- new Uint8Array(fileData),
210
- file.name
211
- );
212
- try {
213
- await tryAwaitWithRetry(
214
- async () =>
215
- await remoteStorage.createFile(
216
- toBucketId,
217
- file.$id,
218
- fileToCreate,
219
- file.$permissions
220
- )
221
- );
222
- } catch (error: any) {
223
- // File already exists, so we can skip it
224
- MessageFormatter.warning(`File ${file.$id} already exists, skipping...`, {
225
- prefix: "Transfer",
226
- });
227
- continue;
228
- }
229
- numberOfFiles++;
230
- }
231
- MessageFormatter.success(
232
- `Transferred ${numberOfFiles} files from ${fromBucketId} to ${toBucketId}`,
233
- { prefix: "Transfer" }
234
- );
235
- };
236
-
237
- // Document transfer functions moved to collections/methods.ts with enhanced UX
238
-
239
- // Remote document transfer functions moved to collections/methods.ts with enhanced UX
240
-
241
- /**
242
- * Transfers all tables/collections and documents from one local database to another local database.
243
- * Uses the DatabaseAdapter for unified TablesDB / legacy support.
244
- *
245
- * @param {Databases} localDb - The local database instance (kept for signature compat).
246
- * @param {string} fromDbId - The ID of the source database.
247
- * @param {string} targetDbId - The ID of the target database.
248
- * @param {string[]} collectionIds - Optional filter: specific table/collection IDs to transfer. undefined = all, empty array = none.
249
- * @param {DatabaseAdapter} adapter - The database adapter (TablesDB or Legacy).
250
- * @return {Promise<void>} A promise that resolves when the transfer is complete.
251
- */
252
- export const transferDatabaseLocalToLocal = async (
253
- localDb: Databases,
254
- fromDbId: string,
255
- targetDbId: string,
256
- collectionIds?: string[],
257
- adapter?: DatabaseAdapter
258
- ) => {
259
- if (!adapter) {
260
- throw new Error("DatabaseAdapter is required for transferDatabaseLocalToLocal");
261
- }
262
- const dbAdapter: DatabaseAdapter = adapter;
263
-
264
- MessageFormatter.info(
265
- `Starting database transfer from ${fromDbId} to ${targetDbId} (mode: ${dbAdapter.getApiMode()})`,
266
- { prefix: "Transfer" }
267
- );
268
-
269
- // Get all tables/collections from source database via adapter
270
- const sourceListRes = await dbAdapter.listTables({ databaseId: fromDbId, queries: [Query.limit(500)] });
271
- let sourceTables: any[] = sourceListRes.tables || sourceListRes.collections || sourceListRes.data || [];
272
-
273
- // Filter by collectionIds if provided (match by $id or by name)
274
- if (collectionIds !== undefined) {
275
- if (collectionIds.length === 0) {
276
- MessageFormatter.info("No tables/collections selected for transfer, skipping.", { prefix: "Transfer" });
277
- return;
278
- }
279
- const idSet = new Set(collectionIds);
280
- sourceTables = sourceTables.filter((c: any) => idSet.has(c.$id) || idSet.has(c.name));
281
- }
282
-
283
- MessageFormatter.info(
284
- `Found ${sourceTables.length} tables/collections in source database`,
285
- { prefix: "Transfer" }
286
- );
287
-
288
- // Process each table/collection
289
- for (const table of sourceTables) {
290
- MessageFormatter.processing(
291
- `Processing table: ${table.name} (${table.$id})`,
292
- { prefix: "Transfer" }
293
- );
294
-
295
- try {
296
- // Check if table exists in target via adapter
297
- let targetTableId = table.$id;
298
- let targetTableData: any;
299
-
300
- try {
301
- const existingRes = await dbAdapter.getTable({ databaseId: targetDbId, tableId: table.$id });
302
- targetTableData = existingRes.data || (existingRes.tables && existingRes.tables[0]);
303
-
304
- if (targetTableData) {
305
- MessageFormatter.info(
306
- `Table ${table.name} exists in target database`,
307
- { prefix: "Transfer" }
308
- );
309
-
310
- // Update table if needed
311
- const securityField = table.rowSecurity ?? table.documentSecurity ?? false;
312
- const targetSecurity = targetTableData.rowSecurity ?? targetTableData.documentSecurity ?? false;
313
- if (
314
- targetTableData.name !== table.name ||
315
- JSON.stringify(targetTableData.$permissions) !== JSON.stringify(table.$permissions) ||
316
- targetSecurity !== securityField ||
317
- targetTableData.enabled !== table.enabled
318
- ) {
319
- await tryAwaitWithRetry(async () =>
320
- dbAdapter.updateTable({
321
- databaseId: targetDbId,
322
- id: table.$id,
323
- name: table.name,
324
- permissions: table.$permissions,
325
- documentSecurity: table.documentSecurity,
326
- rowSecurity: table.rowSecurity,
327
- enabled: table.enabled,
328
- })
329
- );
330
- MessageFormatter.success(
331
- `Table ${table.name} updated`,
332
- { prefix: "Transfer" }
333
- );
334
- }
335
- }
336
- } catch {
337
- // Table does not exist in target, create it
338
- targetTableData = null;
339
- }
340
-
341
- if (!targetTableData) {
342
- MessageFormatter.progress(
343
- `Creating table ${table.name} in target database...`,
344
- { prefix: "Transfer" }
345
- );
346
- const createRes = await tryAwaitWithRetry(async () =>
347
- dbAdapter.createTable({
348
- databaseId: targetDbId,
349
- id: table.$id,
350
- name: table.name,
351
- permissions: table.$permissions,
352
- documentSecurity: table.documentSecurity,
353
- rowSecurity: table.rowSecurity,
354
- enabled: table.enabled,
355
- })
356
- );
357
- targetTableData = createRes.data || (createRes.tables && createRes.tables[0]);
358
- }
359
-
360
- // Create attributes via adapter
361
- MessageFormatter.info(`Creating attributes for ${table.name} via adapter...`, { prefix: 'Transfer' });
362
-
363
- // Fetch existing attributes in target to skip already-created ones
364
- const targetTableRes = await dbAdapter.getTable({ databaseId: targetDbId, tableId: targetTableId });
365
- const targetInfo = targetTableRes.data || (targetTableRes.tables && targetTableRes.tables[0]);
366
- const existingAttrs = targetInfo?.attributes || targetInfo?.columns || [];
367
- const existingKeys = new Set(existingAttrs.map((a: any) => a.key || a.$id));
368
-
369
- const uniformAttrs = (table.attributes || []).map((attr: any) => parseAttribute(attr as any));
370
- const nonRel = uniformAttrs.filter((a: any) => a.type !== 'relationship');
371
- for (const attr of nonRel) {
372
- if (existingKeys.has(attr.key)) continue;
373
- const params = mapToCreateAttributeParams(attr as any, { databaseId: targetDbId, tableId: targetTableId });
374
- await dbAdapter.createAttribute(params);
375
- await new Promise((r) => setTimeout(r, 150));
376
- }
377
-
378
- // Wait for attributes to become available
379
- for (const attr of nonRel) {
380
- const maxWait = 60000; const start = Date.now();
381
- let lastStatus = '';
382
- while (Date.now() - start < maxWait) {
383
- try {
384
- const tableRes = await dbAdapter.getTable({ databaseId: targetDbId, tableId: targetTableId });
385
- const tInfo = tableRes.data || (tableRes.tables && tableRes.tables[0]);
386
- const attrs = tInfo?.attributes || tInfo?.columns || [];
387
- const found = attrs.find((a: any) => a.key === attr.key);
388
- if (found) {
389
- if (found.status === 'available') break;
390
- if (found.status === 'failed' || found.status === 'stuck') {
391
- throw new Error(found.error || `Attribute ${attr.key} failed`);
392
- }
393
- lastStatus = found.status;
394
- }
395
- await new Promise((r) => setTimeout(r, 2000));
396
- } catch {
397
- await new Promise((r) => setTimeout(r, 2000));
398
- }
399
- }
400
- if (Date.now() - start >= maxWait) {
401
- MessageFormatter.warning(`Attribute ${attr.key} did not become available within 60s (last: ${lastStatus})`, { prefix: 'Transfer' });
402
- }
403
- }
404
-
405
- // Relationship attributes
406
- const rels = uniformAttrs.filter((a: any) => a.type === 'relationship');
407
- for (const attr of rels) {
408
- if (existingKeys.has(attr.key)) continue;
409
- const params = mapToCreateAttributeParams(attr as any, { databaseId: targetDbId, tableId: targetTableId });
410
- await dbAdapter.createAttribute(params);
411
- await new Promise((r) => setTimeout(r, 150));
412
- }
413
-
414
- // Handle indexes via adapter (create or update)
415
- for (const idx of (table.indexes || [])) {
416
- try {
417
- await dbAdapter.createIndex({
418
- databaseId: targetDbId,
419
- tableId: targetTableId,
420
- key: (idx as any).key,
421
- type: (idx as any).type,
422
- attributes: (idx as any).attributes,
423
- orders: (idx as any).orders || []
424
- });
425
- await new Promise((r) => setTimeout(r, 150));
426
- MessageFormatter.success(`Index ${(idx as any).key} created`, { prefix: 'Transfer' });
427
- } catch (e) {
428
- // Try update path by deleting and recreating if necessary
429
- try {
430
- await dbAdapter.deleteIndex({ databaseId: targetDbId, tableId: targetTableId, key: (idx as any).key });
431
- await dbAdapter.createIndex({
432
- databaseId: targetDbId,
433
- tableId: targetTableId,
434
- key: (idx as any).key,
435
- type: (idx as any).type,
436
- attributes: (idx as any).attributes,
437
- orders: (idx as any).orders || []
438
- });
439
- await new Promise((r) => setTimeout(r, 150));
440
- MessageFormatter.info(`Index ${(idx as any).key} recreated`, { prefix: 'Transfer' });
441
- } catch (e2) {
442
- MessageFormatter.error(`Failed to ensure index ${(idx as any).key}`, e2 instanceof Error ? e2 : new Error(String(e2)), { prefix: 'Transfer' });
443
- }
444
- }
445
- }
446
-
447
- // Transfer documents/rows via adapter
448
- const { transferDocumentsBetweenDbsLocalToLocal } = await import(
449
- "../collections/transferOperations.js"
450
- );
451
- await transferDocumentsBetweenDbsLocalToLocal(
452
- dbAdapter,
453
- fromDbId,
454
- targetDbId,
455
- table.$id,
456
- targetTableId
457
- );
458
- } catch (error) {
459
- MessageFormatter.error(
460
- `Error processing table ${table.name}`,
461
- error instanceof Error ? error : new Error(String(error)),
462
- { prefix: "Transfer" }
463
- );
464
- }
465
- }
466
- };
467
-
468
- export const transferDatabaseLocalToRemote = async (
469
- localDb: Databases,
470
- endpoint: string,
471
- projectId: string,
472
- apiKey: string,
473
- fromDbId: string,
474
- toDbId: string,
475
- collectionIds?: string[]
476
- ) => {
477
- const client = getAppwriteClient(endpoint, projectId, apiKey);
478
- const remoteDb = new Databases(client);
479
-
480
- // Get all collections from source database
481
- let sourceCollections = await fetchAllCollections(fromDbId, localDb);
482
- if (collectionIds && collectionIds.length > 0) {
483
- const idSet = new Set(collectionIds);
484
- sourceCollections = sourceCollections.filter((c) => idSet.has(c.$id));
485
- }
486
- MessageFormatter.info(
487
- `Found ${sourceCollections.length} collections in source database`,
488
- { prefix: "Transfer" }
489
- );
490
-
491
- // Process each collection
492
- for (const collection of sourceCollections) {
493
- MessageFormatter.processing(
494
- `Processing collection: ${collection.name} (${collection.$id})`,
495
- { prefix: "Transfer" }
496
- );
497
-
498
- try {
499
- // Create or update collection in target
500
- let targetCollection: Models.Collection;
501
- const existingCollection = await tryAwaitWithRetry(async () =>
502
- remoteDb.listCollections(toDbId, [Query.equal("$id", collection.$id)])
503
- );
504
-
505
- if (existingCollection.collections.length > 0) {
506
- targetCollection = existingCollection.collections[0];
507
- MessageFormatter.info(
508
- `Collection ${collection.name} exists in remote database`,
509
- { prefix: "Transfer" }
510
- );
511
-
512
- // Update collection if needed
513
- if (
514
- targetCollection.name !== collection.name ||
515
- targetCollection.$permissions !== collection.$permissions ||
516
- targetCollection.documentSecurity !== collection.documentSecurity ||
517
- targetCollection.enabled !== collection.enabled
518
- ) {
519
- targetCollection = await tryAwaitWithRetry(async () =>
520
- remoteDb.updateCollection(
521
- toDbId,
522
- collection.$id,
523
- collection.name,
524
- collection.$permissions,
525
- collection.documentSecurity,
526
- collection.enabled
527
- )
528
- );
529
- MessageFormatter.success(
530
- `Collection ${collection.name} updated`,
531
- { prefix: "Transfer" }
532
- );
533
- }
534
- } else {
535
- MessageFormatter.progress(
536
- `Creating collection ${collection.name} in remote database...`,
537
- { prefix: "Transfer" }
538
- );
539
- targetCollection = await tryAwaitWithRetry(async () =>
540
- remoteDb.createCollection(
541
- toDbId,
542
- collection.$id,
543
- collection.name,
544
- collection.$permissions,
545
- collection.documentSecurity,
546
- collection.enabled
547
- )
548
- );
549
- }
550
-
551
- // Create/Update attributes via adapter (prefer adapter for remote)
552
- const { adapter: remoteAdapter } = await getAdapter(endpoint, projectId, apiKey, 'auto');
553
- MessageFormatter.info(`Creating attributes for ${collection.name} via adapter...`, { prefix: 'Transfer' });
554
- const uniformAttrs = collection.attributes.map((attr) => parseAttribute(attr as any));
555
- const nonRel = uniformAttrs.filter((a: any) => a.type !== 'relationship');
556
- if (nonRel.length > 0) {
557
- const tableInfo = await (remoteAdapter as DatabaseAdapter).getTable({ databaseId: toDbId, tableId: collection.$id });
558
- const existingCols: any[] = (tableInfo as any).columns || (tableInfo as any).attributes || [];
559
- const { toCreate, toUpdate } = diffTableColumns(existingCols, nonRel as any);
560
- for (const a of toUpdate) { const p = mapToCreateAttributeParams(a as any, { databaseId: toDbId, tableId: collection.$id }); await (remoteAdapter as DatabaseAdapter).updateAttribute(p as any); await new Promise((r)=>setTimeout(r,150)); }
561
- for (const a of toCreate) { const p = mapToCreateAttributeParams(a as any, { databaseId: toDbId, tableId: collection.$id }); await (remoteAdapter as DatabaseAdapter).createAttribute(p); await new Promise((r)=>setTimeout(r,150)); }
562
- }
563
-
564
- // Wait for non-relationship attributes to become available
565
- for (const attr of nonRel) {
566
- const maxWait = 60000; const start = Date.now();
567
- let lastStatus = '';
568
- while (Date.now() - start < maxWait) {
569
- try {
570
- const tableRes = await (remoteAdapter as DatabaseAdapter).getTable({ databaseId: toDbId, tableId: collection.$id });
571
- const attrs = (tableRes as any).attributes || (tableRes as any).columns || [];
572
- const found = attrs.find((a: any) => a.key === attr.key);
573
- if (found) {
574
- if (found.status === 'available') break;
575
- if (found.status === 'failed' || found.status === 'stuck') {
576
- throw new Error(found.error || `Attribute ${attr.key} failed`);
577
- }
578
- lastStatus = found.status;
579
- }
580
- await new Promise((r) => setTimeout(r, 2000));
581
- } catch {
582
- await new Promise((r) => setTimeout(r, 2000));
583
- }
584
- }
585
- if (Date.now() - start >= maxWait) {
586
- MessageFormatter.warning(`Attribute ${attr.key} did not become available within 60s (last: ${lastStatus})`, { prefix: 'Transfer' });
587
- }
588
- }
589
-
590
- // Relationship attributes
591
- const rels = uniformAttrs.filter((a: any) => a.type === 'relationship');
592
- if (rels.length > 0) {
593
- const tableInfo2 = await (remoteAdapter as DatabaseAdapter).getTable({ databaseId: toDbId, tableId: collection.$id });
594
- const existingCols2: any[] = (tableInfo2 as any).columns || (tableInfo2 as any).attributes || [];
595
- const { toCreate: rCreate, toUpdate: rUpdate } = diffTableColumns(existingCols2, rels as any);
596
- for (const a of rUpdate) { const p = mapToCreateAttributeParams(a as any, { databaseId: toDbId, tableId: collection.$id }); await (remoteAdapter as DatabaseAdapter).updateAttribute(p as any); await new Promise((r)=>setTimeout(r,150)); }
597
- for (const a of rCreate) { const p = mapToCreateAttributeParams(a as any, { databaseId: toDbId, tableId: collection.$id }); await (remoteAdapter as DatabaseAdapter).createAttribute(p); await new Promise((r)=>setTimeout(r,150)); }
598
- }
599
-
600
- // Handle indexes with enhanced status checking
601
- MessageFormatter.info(
602
- `Creating indexes for collection ${collection.name} with enhanced monitoring...`,
603
- { prefix: "Transfer" }
604
- );
605
-
606
- // Create indexes via adapter
607
- for (const idx of (collection.indexes as any[]) || []) {
608
- try {
609
- await (remoteAdapter as DatabaseAdapter).createIndex({
610
- databaseId: toDbId,
611
- tableId: collection.$id,
612
- key: idx.key,
613
- type: idx.type,
614
- attributes: idx.attributes,
615
- orders: idx.orders || []
616
- });
617
- await new Promise((r) => setTimeout(r, 150));
618
- } catch (e) {
619
- MessageFormatter.error(`Failed to create index ${idx.key}`, e instanceof Error ? e : new Error(String(e)), { prefix: 'Transfer' });
620
- }
621
- }
622
-
623
- // Transfer documents
624
- const { transferDocumentsBetweenDbsLocalToRemote } = await import(
625
- "../collections/methods.js"
626
- );
627
- await transferDocumentsBetweenDbsLocalToRemote(
628
- localDb,
629
- endpoint,
630
- projectId,
631
- apiKey,
632
- fromDbId,
633
- toDbId,
634
- collection.$id,
635
- targetCollection.$id
636
- );
637
- } catch (error) {
638
- MessageFormatter.error(
639
- `Error processing collection ${collection.name}`,
640
- error instanceof Error ? error : new Error(String(error)),
641
- { prefix: "Transfer" }
642
- );
643
- }
644
- }
645
- };
646
-
647
- export const transferUsersLocalToRemote = async (
648
- localUsers: Users,
649
- endpoint: string,
650
- projectId: string,
651
- apiKey: string
652
- ) => {
653
- MessageFormatter.info(
654
- "Starting user transfer to remote instance...",
655
- { prefix: "Transfer" }
656
- );
657
-
658
- const client = getClient(endpoint, projectId, apiKey);
659
- const remoteUsers = new Users(client);
660
-
661
- let totalTransferred = 0;
662
- let lastId: string | undefined;
663
-
664
- while (true) {
665
- const queries = [Query.limit(100)];
666
- if (lastId) {
667
- queries.push(Query.cursorAfter(lastId));
668
- }
669
-
670
- const usersList = await tryAwaitWithRetry(async () =>
671
- localUsers.list(queries)
672
- );
673
-
674
- if (usersList.users.length === 0) {
675
- break;
676
- }
677
-
678
- for (const user of usersList.users) {
679
- try {
680
- // Check if user already exists in remote
681
- let remoteUser: Models.User<Models.Preferences> | undefined;
682
- try {
683
- remoteUser = await tryAwaitWithRetry(async () =>
684
- remoteUsers.get(user.$id)
685
- );
686
-
687
- // If user exists, update only the differences
688
- if (remoteUser) {
689
- MessageFormatter.info(
690
- `User ${user.$id} exists, checking for updates...`,
691
- { prefix: "Transfer" }
692
- );
693
- let hasUpdates = false;
694
-
695
- // Update name if different
696
- if (remoteUser.name !== user.name) {
697
- await tryAwaitWithRetry(async () =>
698
- remoteUsers.updateName(user.$id, user.name)
699
- );
700
- MessageFormatter.success(
701
- `Updated name for user ${user.$id}`,
702
- { prefix: "Transfer" }
703
- );
704
- hasUpdates = true;
705
- }
706
-
707
- // Update email if different
708
- if (remoteUser.email !== user.email) {
709
- await tryAwaitWithRetry(async () =>
710
- remoteUsers.updateEmail(user.$id, user.email)
711
- );
712
- MessageFormatter.success(
713
- `Updated email for user ${user.$id}`,
714
- { prefix: "Transfer" }
715
- );
716
- hasUpdates = true;
717
- }
718
-
719
- // Update phone if different
720
- const normalizedLocalPhone = user.phone
721
- ? converterFunctions.convertPhoneStringToUSInternational(user.phone)
722
- : undefined;
723
- if (remoteUser.phone !== normalizedLocalPhone) {
724
- if (normalizedLocalPhone) {
725
- await tryAwaitWithRetry(async () =>
726
- remoteUsers.updatePhone(user.$id, normalizedLocalPhone)
727
- );
728
- }
729
- MessageFormatter.success(
730
- `Updated phone for user ${user.$id}`,
731
- { prefix: "Transfer" }
732
- );
733
- hasUpdates = true;
734
- }
735
-
736
- // Update preferences if different
737
- if (JSON.stringify(remoteUser.prefs) !== JSON.stringify(user.prefs)) {
738
- await tryAwaitWithRetry(async () =>
739
- remoteUsers.updatePrefs(user.$id, user.prefs)
740
- );
741
- MessageFormatter.success(
742
- `Updated preferences for user ${user.$id}`,
743
- { prefix: "Transfer" }
744
- );
745
- hasUpdates = true;
746
- }
747
-
748
- // Update labels if different
749
- if (JSON.stringify(remoteUser.labels) !== JSON.stringify(user.labels)) {
750
- await tryAwaitWithRetry(async () =>
751
- remoteUsers.updateLabels(user.$id, user.labels)
752
- );
753
- MessageFormatter.success(
754
- `Updated labels for user ${user.$id}`,
755
- { prefix: "Transfer" }
756
- );
757
- hasUpdates = true;
758
- }
759
-
760
- // Update email verification if different
761
- if (remoteUser.emailVerification !== user.emailVerification) {
762
- await tryAwaitWithRetry(async () =>
763
- remoteUsers.updateEmailVerification(user.$id, user.emailVerification)
764
- );
765
- MessageFormatter.success(
766
- `Updated email verification for user ${user.$id}`,
767
- { prefix: "Transfer" }
768
- );
769
- hasUpdates = true;
770
- }
771
-
772
- // Update phone verification if different
773
- if (remoteUser.phoneVerification !== user.phoneVerification) {
774
- await tryAwaitWithRetry(async () =>
775
- remoteUsers.updatePhoneVerification(user.$id, user.phoneVerification)
776
- );
777
- MessageFormatter.success(
778
- `Updated phone verification for user ${user.$id}`,
779
- { prefix: "Transfer" }
780
- );
781
- hasUpdates = true;
782
- }
783
-
784
- // Update status if different
785
- if (remoteUser.status !== user.status) {
786
- await tryAwaitWithRetry(async () =>
787
- remoteUsers.updateStatus(user.$id, user.status)
788
- );
789
- MessageFormatter.success(
790
- `Updated status for user ${user.$id}`,
791
- { prefix: "Transfer" }
792
- );
793
- hasUpdates = true;
794
- }
795
-
796
- if (!hasUpdates) {
797
- MessageFormatter.info(
798
- `User ${user.$id} is already up to date, skipping...`,
799
- { prefix: "Transfer" }
800
- );
801
- } else {
802
- totalTransferred++;
803
- MessageFormatter.success(
804
- `Updated user ${user.$id}`,
805
- { prefix: "Transfer" }
806
- );
807
- }
808
- continue;
809
- }
810
- } catch (error: any) {
811
- // User doesn't exist, proceed with creation
812
- }
813
-
814
- const phone = user.phone
815
- ? converterFunctions.convertPhoneStringToUSInternational(user.phone)
816
- : undefined;
817
-
818
- // Handle user creation based on hash type
819
- if (user.hash && user.password) {
820
- // User has a hashed password - recreate with proper hash method
821
- const hashType = user.hash.toLowerCase();
822
- const hashedPassword = user.password; // This is already hashed
823
- const hashOptions = (user.hashOptions as Record<string, any>) || {};
824
-
825
- try {
826
- switch (hashType) {
827
- case "argon2":
828
- await tryAwaitWithRetry(async () =>
829
- remoteUsers.createArgon2User(
830
- user.$id,
831
- user.email,
832
- hashedPassword,
833
- user.name
834
- )
835
- );
836
- break;
837
-
838
- case "bcrypt":
839
- await tryAwaitWithRetry(async () =>
840
- remoteUsers.createBcryptUser(
841
- user.$id,
842
- user.email,
843
- hashedPassword,
844
- user.name
845
- )
846
- );
847
- break;
848
-
849
- case "scrypt":
850
- // Scrypt requires additional parameters from hashOptions
851
- const salt =
852
- typeof hashOptions.salt === "string" ? hashOptions.salt : "";
853
- const costCpu =
854
- typeof hashOptions.costCpu === "number"
855
- ? hashOptions.costCpu
856
- : 32768;
857
- const costMemory =
858
- typeof hashOptions.costMemory === "number"
859
- ? hashOptions.costMemory
860
- : 14;
861
- const costParallel =
862
- typeof hashOptions.costParallel === "number"
863
- ? hashOptions.costParallel
864
- : 1;
865
- const length =
866
- typeof hashOptions.length === "number"
867
- ? hashOptions.length
868
- : 64;
869
-
870
- // Warn if using default values due to missing hash options
871
- if (
872
- !hashOptions.salt ||
873
- typeof hashOptions.costCpu !== "number"
874
- ) {
875
- MessageFormatter.warning(
876
- `User ${user.$id}: Using default Scrypt parameters due to missing hashOptions`,
877
- { prefix: "Transfer" }
878
- );
879
- }
880
-
881
- await tryAwaitWithRetry(async () =>
882
- remoteUsers.createScryptUser(
883
- user.$id,
884
- user.email,
885
- hashedPassword,
886
- salt,
887
- costCpu,
888
- costMemory,
889
- costParallel,
890
- length,
891
- user.name
892
- )
893
- );
894
- break;
895
-
896
- case "scryptmodified":
897
- // Scrypt Modified (Firebase) requires salt, separator, and signer key
898
- const modSalt =
899
- typeof hashOptions.salt === "string" ? hashOptions.salt : "";
900
- const saltSeparator =
901
- typeof hashOptions.saltSeparator === "string"
902
- ? hashOptions.saltSeparator
903
- : "";
904
- const signerKey =
905
- typeof hashOptions.signerKey === "string"
906
- ? hashOptions.signerKey
907
- : "";
908
-
909
- // Warn if critical parameters are missing
910
- if (
911
- !hashOptions.salt ||
912
- !hashOptions.saltSeparator ||
913
- !hashOptions.signerKey
914
- ) {
915
- MessageFormatter.warning(
916
- `User ${user.$id}: Missing critical Scrypt Modified parameters in hashOptions`,
917
- { prefix: "Transfer" }
918
- );
919
- }
920
-
921
- await tryAwaitWithRetry(async () =>
922
- remoteUsers.createScryptModifiedUser(
923
- user.$id,
924
- user.email,
925
- hashedPassword,
926
- modSalt,
927
- saltSeparator,
928
- signerKey,
929
- user.name
930
- )
931
- );
932
- break;
933
-
934
- case "md5":
935
- await tryAwaitWithRetry(async () =>
936
- remoteUsers.createMD5User(
937
- user.$id,
938
- user.email,
939
- hashedPassword,
940
- user.name
941
- )
942
- );
943
- break;
944
-
945
- case "sha":
946
- case "sha1":
947
- case "sha256":
948
- case "sha512":
949
- // SHA variants - determine version from hash type
950
- const getPasswordHashVersion = (hash: string) => {
951
- switch (hash.toLowerCase()) {
952
- case "sha1":
953
- return "sha1" as any;
954
- case "sha256":
955
- return "sha256" as any;
956
- case "sha512":
957
- return "sha512" as any;
958
- default:
959
- return "sha256" as any; // Default to SHA256
960
- }
961
- };
962
-
963
- await tryAwaitWithRetry(async () =>
964
- remoteUsers.createSHAUser(
965
- user.$id,
966
- user.email,
967
- hashedPassword,
968
- getPasswordHashVersion(hashType),
969
- user.name
970
- )
971
- );
972
- break;
973
-
974
- case "phpass":
975
- await tryAwaitWithRetry(async () =>
976
- remoteUsers.createPHPassUser(
977
- user.$id,
978
- user.email,
979
- hashedPassword,
980
- user.name
981
- )
982
- );
983
- break;
984
-
985
- default:
986
- MessageFormatter.warning(
987
- `Unknown hash type '${hashType}' for user ${user.$id}, falling back to Argon2`,
988
- { prefix: "Transfer" }
989
- );
990
- await tryAwaitWithRetry(async () =>
991
- remoteUsers.createArgon2User(
992
- user.$id,
993
- user.email,
994
- hashedPassword,
995
- user.name
996
- )
997
- );
998
- break;
999
- }
1000
-
1001
- MessageFormatter.success(
1002
- `User ${user.$id} created with preserved ${hashType} password`,
1003
- { prefix: "Transfer" }
1004
- );
1005
- } catch (error) {
1006
- MessageFormatter.warning(
1007
- `Failed to create user ${user.$id} with ${hashType} hash, trying with temporary password`,
1008
- { prefix: "Transfer" }
1009
- );
1010
-
1011
- // Fallback to creating user with temporary password
1012
- await tryAwaitWithRetry(async () =>
1013
- remoteUsers.create(
1014
- user.$id,
1015
- user.email,
1016
- phone,
1017
- `changeMe${user.email}`,
1018
- user.name
1019
- )
1020
- );
1021
-
1022
- MessageFormatter.warning(
1023
- `User ${user.$id} created with temporary password - password reset required`,
1024
- { prefix: "Transfer" }
1025
- );
1026
- }
1027
- } else {
1028
- // No hash or password - create with temporary password
1029
- const tempPassword = user.password || `changeMe${user.email}`;
1030
-
1031
- await tryAwaitWithRetry(async () =>
1032
- remoteUsers.create(
1033
- user.$id,
1034
- user.email,
1035
- phone,
1036
- tempPassword,
1037
- user.name
1038
- )
1039
- );
1040
-
1041
- if (!user.password) {
1042
- MessageFormatter.warning(
1043
- `User ${user.$id} created with temporary password - password reset required`,
1044
- { prefix: "Transfer" }
1045
- );
1046
- }
1047
- }
1048
-
1049
- // Update phone, labels, and other attributes for newly created users
1050
- if (phone) {
1051
- await tryAwaitWithRetry(async () =>
1052
- remoteUsers.updatePhone(user.$id, phone)
1053
- );
1054
- }
1055
-
1056
- if (user.labels && user.labels.length > 0) {
1057
- await tryAwaitWithRetry(async () =>
1058
- remoteUsers.updateLabels(user.$id, user.labels)
1059
- );
1060
- }
1061
-
1062
- // Update user preferences and status for newly created users
1063
- await tryAwaitWithRetry(async () =>
1064
- remoteUsers.updatePrefs(user.$id, user.prefs)
1065
- );
1066
-
1067
- if (user.emailVerification) {
1068
- await tryAwaitWithRetry(async () =>
1069
- remoteUsers.updateEmailVerification(user.$id, true)
1070
- );
1071
- } else {
1072
- await tryAwaitWithRetry(async () =>
1073
- remoteUsers.updateEmailVerification(user.$id, false)
1074
- );
1075
- }
1076
-
1077
- if (user.phoneVerification) {
1078
- await tryAwaitWithRetry(async () =>
1079
- remoteUsers.updatePhoneVerification(user.$id, true)
1080
- );
1081
- }
1082
-
1083
- if (user.status === false) {
1084
- await tryAwaitWithRetry(async () =>
1085
- remoteUsers.updateStatus(user.$id, false)
1086
- );
1087
- }
1088
-
1089
- totalTransferred++;
1090
- MessageFormatter.success(
1091
- `Transferred user ${user.$id}`,
1092
- { prefix: "Transfer" }
1093
- );
1094
- } catch (error) {
1095
- MessageFormatter.error(
1096
- `Failed to transfer user ${user.$id}`,
1097
- error instanceof Error ? error : new Error(String(error)),
1098
- { prefix: "Transfer" }
1099
- );
1100
- }
1101
- }
1102
-
1103
- if (usersList.users.length < 100) {
1104
- break;
1105
- }
1106
- lastId = usersList.users[usersList.users.length - 1].$id;
1107
- }
1108
-
1109
- MessageFormatter.success(
1110
- `Successfully transferred ${totalTransferred} users`,
1111
- { prefix: "Transfer" }
1112
- );
1113
- };