@njdamstra/appwrite-utils-cli 1.8.9

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 (392) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +1133 -0
  3. package/dist/adapters/AdapterFactory.d.ts +94 -0
  4. package/dist/adapters/AdapterFactory.js +405 -0
  5. package/dist/adapters/DatabaseAdapter.d.ts +233 -0
  6. package/dist/adapters/DatabaseAdapter.js +50 -0
  7. package/dist/adapters/LegacyAdapter.d.ts +50 -0
  8. package/dist/adapters/LegacyAdapter.js +612 -0
  9. package/dist/adapters/TablesDBAdapter.d.ts +45 -0
  10. package/dist/adapters/TablesDBAdapter.js +571 -0
  11. package/dist/adapters/index.d.ts +11 -0
  12. package/dist/adapters/index.js +12 -0
  13. package/dist/backups/operations/bucketBackup.d.ts +19 -0
  14. package/dist/backups/operations/bucketBackup.js +197 -0
  15. package/dist/backups/operations/collectionBackup.d.ts +30 -0
  16. package/dist/backups/operations/collectionBackup.js +201 -0
  17. package/dist/backups/operations/comprehensiveBackup.d.ts +25 -0
  18. package/dist/backups/operations/comprehensiveBackup.js +238 -0
  19. package/dist/backups/schemas/bucketManifest.d.ts +93 -0
  20. package/dist/backups/schemas/bucketManifest.js +33 -0
  21. package/dist/backups/schemas/comprehensiveManifest.d.ts +108 -0
  22. package/dist/backups/schemas/comprehensiveManifest.js +32 -0
  23. package/dist/backups/tracking/centralizedTracking.d.ts +34 -0
  24. package/dist/backups/tracking/centralizedTracking.js +274 -0
  25. package/dist/cli/commands/configCommands.d.ts +8 -0
  26. package/dist/cli/commands/configCommands.js +166 -0
  27. package/dist/cli/commands/databaseCommands.d.ts +13 -0
  28. package/dist/cli/commands/databaseCommands.js +554 -0
  29. package/dist/cli/commands/functionCommands.d.ts +7 -0
  30. package/dist/cli/commands/functionCommands.js +330 -0
  31. package/dist/cli/commands/schemaCommands.d.ts +7 -0
  32. package/dist/cli/commands/schemaCommands.js +169 -0
  33. package/dist/cli/commands/storageCommands.d.ts +5 -0
  34. package/dist/cli/commands/storageCommands.js +143 -0
  35. package/dist/cli/commands/transferCommands.d.ts +5 -0
  36. package/dist/cli/commands/transferCommands.js +384 -0
  37. package/dist/collections/attributes.d.ts +13 -0
  38. package/dist/collections/attributes.js +1364 -0
  39. package/dist/collections/indexes.d.ts +12 -0
  40. package/dist/collections/indexes.js +217 -0
  41. package/dist/collections/methods.d.ts +19 -0
  42. package/dist/collections/methods.js +682 -0
  43. package/dist/collections/tableOperations.d.ts +86 -0
  44. package/dist/collections/tableOperations.js +434 -0
  45. package/dist/collections/transferOperations.d.ts +8 -0
  46. package/dist/collections/transferOperations.js +412 -0
  47. package/dist/collections/wipeOperations.d.ts +16 -0
  48. package/dist/collections/wipeOperations.js +233 -0
  49. package/dist/config/ConfigManager.d.ts +445 -0
  50. package/dist/config/ConfigManager.js +625 -0
  51. package/dist/config/configMigration.d.ts +87 -0
  52. package/dist/config/configMigration.js +390 -0
  53. package/dist/config/configValidation.d.ts +66 -0
  54. package/dist/config/configValidation.js +358 -0
  55. package/dist/config/index.d.ts +8 -0
  56. package/dist/config/index.js +7 -0
  57. package/dist/config/services/ConfigDiscoveryService.d.ts +126 -0
  58. package/dist/config/services/ConfigDiscoveryService.js +374 -0
  59. package/dist/config/services/ConfigLoaderService.d.ts +129 -0
  60. package/dist/config/services/ConfigLoaderService.js +540 -0
  61. package/dist/config/services/ConfigMergeService.d.ts +208 -0
  62. package/dist/config/services/ConfigMergeService.js +308 -0
  63. package/dist/config/services/ConfigValidationService.d.ts +214 -0
  64. package/dist/config/services/ConfigValidationService.js +310 -0
  65. package/dist/config/services/SessionAuthService.d.ts +225 -0
  66. package/dist/config/services/SessionAuthService.js +456 -0
  67. package/dist/config/services/__tests__/ConfigMergeService.test.d.ts +1 -0
  68. package/dist/config/services/__tests__/ConfigMergeService.test.js +271 -0
  69. package/dist/config/services/index.d.ts +13 -0
  70. package/dist/config/services/index.js +10 -0
  71. package/dist/config/yamlConfig.d.ts +722 -0
  72. package/dist/config/yamlConfig.js +702 -0
  73. package/dist/databases/methods.d.ts +6 -0
  74. package/dist/databases/methods.js +35 -0
  75. package/dist/databases/setup.d.ts +5 -0
  76. package/dist/databases/setup.js +45 -0
  77. package/dist/examples/yamlTerminologyExample.d.ts +42 -0
  78. package/dist/examples/yamlTerminologyExample.js +272 -0
  79. package/dist/functions/deployments.d.ts +4 -0
  80. package/dist/functions/deployments.js +146 -0
  81. package/dist/functions/fnConfigDiscovery.d.ts +3 -0
  82. package/dist/functions/fnConfigDiscovery.js +108 -0
  83. package/dist/functions/methods.d.ts +16 -0
  84. package/dist/functions/methods.js +162 -0
  85. package/dist/functions/pathResolution.d.ts +37 -0
  86. package/dist/functions/pathResolution.js +185 -0
  87. package/dist/functions/templates/count-docs-in-collection/README.md +54 -0
  88. package/dist/functions/templates/count-docs-in-collection/src/main.ts +159 -0
  89. package/dist/functions/templates/count-docs-in-collection/src/request.ts +9 -0
  90. package/dist/functions/templates/hono-typescript/README.md +286 -0
  91. package/dist/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
  92. package/dist/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
  93. package/dist/functions/templates/hono-typescript/src/app.ts +180 -0
  94. package/dist/functions/templates/hono-typescript/src/context.ts +103 -0
  95. package/dist/functions/templates/hono-typescript/src/index.ts +54 -0
  96. package/dist/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
  97. package/dist/functions/templates/typescript-node/README.md +32 -0
  98. package/dist/functions/templates/typescript-node/src/context.ts +103 -0
  99. package/dist/functions/templates/typescript-node/src/index.ts +29 -0
  100. package/dist/functions/templates/uv/README.md +31 -0
  101. package/dist/functions/templates/uv/pyproject.toml +30 -0
  102. package/dist/functions/templates/uv/src/__init__.py +0 -0
  103. package/dist/functions/templates/uv/src/context.py +125 -0
  104. package/dist/functions/templates/uv/src/index.py +46 -0
  105. package/dist/init.d.ts +2 -0
  106. package/dist/init.js +57 -0
  107. package/dist/interactiveCLI.d.ts +31 -0
  108. package/dist/interactiveCLI.js +898 -0
  109. package/dist/main.d.ts +2 -0
  110. package/dist/main.js +1172 -0
  111. package/dist/migrations/afterImportActions.d.ts +17 -0
  112. package/dist/migrations/afterImportActions.js +306 -0
  113. package/dist/migrations/appwriteToX.d.ts +211 -0
  114. package/dist/migrations/appwriteToX.js +491 -0
  115. package/dist/migrations/comprehensiveTransfer.d.ts +147 -0
  116. package/dist/migrations/comprehensiveTransfer.js +1317 -0
  117. package/dist/migrations/dataLoader.d.ts +753 -0
  118. package/dist/migrations/dataLoader.js +1250 -0
  119. package/dist/migrations/importController.d.ts +23 -0
  120. package/dist/migrations/importController.js +268 -0
  121. package/dist/migrations/importDataActions.d.ts +50 -0
  122. package/dist/migrations/importDataActions.js +230 -0
  123. package/dist/migrations/relationships.d.ts +29 -0
  124. package/dist/migrations/relationships.js +204 -0
  125. package/dist/migrations/services/DataTransformationService.d.ts +55 -0
  126. package/dist/migrations/services/DataTransformationService.js +158 -0
  127. package/dist/migrations/services/FileHandlerService.d.ts +75 -0
  128. package/dist/migrations/services/FileHandlerService.js +236 -0
  129. package/dist/migrations/services/ImportOrchestrator.d.ts +97 -0
  130. package/dist/migrations/services/ImportOrchestrator.js +485 -0
  131. package/dist/migrations/services/RateLimitManager.d.ts +138 -0
  132. package/dist/migrations/services/RateLimitManager.js +279 -0
  133. package/dist/migrations/services/RelationshipResolver.d.ts +120 -0
  134. package/dist/migrations/services/RelationshipResolver.js +332 -0
  135. package/dist/migrations/services/UserMappingService.d.ts +109 -0
  136. package/dist/migrations/services/UserMappingService.js +277 -0
  137. package/dist/migrations/services/ValidationService.d.ts +74 -0
  138. package/dist/migrations/services/ValidationService.js +260 -0
  139. package/dist/migrations/transfer.d.ts +26 -0
  140. package/dist/migrations/transfer.js +608 -0
  141. package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +131 -0
  142. package/dist/migrations/yaml/YamlImportConfigLoader.js +383 -0
  143. package/dist/migrations/yaml/YamlImportIntegration.d.ts +93 -0
  144. package/dist/migrations/yaml/YamlImportIntegration.js +341 -0
  145. package/dist/migrations/yaml/generateImportSchemas.d.ts +30 -0
  146. package/dist/migrations/yaml/generateImportSchemas.js +1327 -0
  147. package/dist/schemas/authUser.d.ts +24 -0
  148. package/dist/schemas/authUser.js +17 -0
  149. package/dist/setup.d.ts +2 -0
  150. package/dist/setup.js +5 -0
  151. package/dist/setupCommands.d.ts +58 -0
  152. package/dist/setupCommands.js +490 -0
  153. package/dist/setupController.d.ts +9 -0
  154. package/dist/setupController.js +34 -0
  155. package/dist/shared/attributeMapper.d.ts +20 -0
  156. package/dist/shared/attributeMapper.js +203 -0
  157. package/dist/shared/backupMetadataSchema.d.ts +94 -0
  158. package/dist/shared/backupMetadataSchema.js +38 -0
  159. package/dist/shared/backupTracking.d.ts +18 -0
  160. package/dist/shared/backupTracking.js +176 -0
  161. package/dist/shared/confirmationDialogs.d.ts +75 -0
  162. package/dist/shared/confirmationDialogs.js +236 -0
  163. package/dist/shared/errorUtils.d.ts +54 -0
  164. package/dist/shared/errorUtils.js +95 -0
  165. package/dist/shared/functionManager.d.ts +48 -0
  166. package/dist/shared/functionManager.js +336 -0
  167. package/dist/shared/indexManager.d.ts +24 -0
  168. package/dist/shared/indexManager.js +151 -0
  169. package/dist/shared/jsonSchemaGenerator.d.ts +50 -0
  170. package/dist/shared/jsonSchemaGenerator.js +290 -0
  171. package/dist/shared/logging.d.ts +61 -0
  172. package/dist/shared/logging.js +116 -0
  173. package/dist/shared/messageFormatter.d.ts +39 -0
  174. package/dist/shared/messageFormatter.js +162 -0
  175. package/dist/shared/migrationHelpers.d.ts +61 -0
  176. package/dist/shared/migrationHelpers.js +145 -0
  177. package/dist/shared/operationLogger.d.ts +10 -0
  178. package/dist/shared/operationLogger.js +12 -0
  179. package/dist/shared/operationQueue.d.ts +40 -0
  180. package/dist/shared/operationQueue.js +311 -0
  181. package/dist/shared/operationsTable.d.ts +26 -0
  182. package/dist/shared/operationsTable.js +286 -0
  183. package/dist/shared/operationsTableSchema.d.ts +48 -0
  184. package/dist/shared/operationsTableSchema.js +35 -0
  185. package/dist/shared/progressManager.d.ts +62 -0
  186. package/dist/shared/progressManager.js +215 -0
  187. package/dist/shared/pydanticModelGenerator.d.ts +17 -0
  188. package/dist/shared/pydanticModelGenerator.js +615 -0
  189. package/dist/shared/relationshipExtractor.d.ts +56 -0
  190. package/dist/shared/relationshipExtractor.js +138 -0
  191. package/dist/shared/schemaGenerator.d.ts +40 -0
  192. package/dist/shared/schemaGenerator.js +556 -0
  193. package/dist/shared/selectionDialogs.d.ts +214 -0
  194. package/dist/shared/selectionDialogs.js +544 -0
  195. package/dist/storage/backupCompression.d.ts +20 -0
  196. package/dist/storage/backupCompression.js +67 -0
  197. package/dist/storage/methods.d.ts +32 -0
  198. package/dist/storage/methods.js +472 -0
  199. package/dist/storage/schemas.d.ts +842 -0
  200. package/dist/storage/schemas.js +175 -0
  201. package/dist/types.d.ts +4 -0
  202. package/dist/types.js +3 -0
  203. package/dist/users/methods.d.ts +16 -0
  204. package/dist/users/methods.js +277 -0
  205. package/dist/utils/ClientFactory.d.ts +87 -0
  206. package/dist/utils/ClientFactory.js +212 -0
  207. package/dist/utils/configDiscovery.d.ts +78 -0
  208. package/dist/utils/configDiscovery.js +472 -0
  209. package/dist/utils/configMigration.d.ts +1 -0
  210. package/dist/utils/configMigration.js +261 -0
  211. package/dist/utils/constantsGenerator.d.ts +31 -0
  212. package/dist/utils/constantsGenerator.js +321 -0
  213. package/dist/utils/dataConverters.d.ts +46 -0
  214. package/dist/utils/dataConverters.js +139 -0
  215. package/dist/utils/directoryUtils.d.ts +22 -0
  216. package/dist/utils/directoryUtils.js +59 -0
  217. package/dist/utils/getClientFromConfig.d.ts +39 -0
  218. package/dist/utils/getClientFromConfig.js +199 -0
  219. package/dist/utils/helperFunctions.d.ts +63 -0
  220. package/dist/utils/helperFunctions.js +156 -0
  221. package/dist/utils/index.d.ts +2 -0
  222. package/dist/utils/index.js +2 -0
  223. package/dist/utils/loadConfigs.d.ts +50 -0
  224. package/dist/utils/loadConfigs.js +358 -0
  225. package/dist/utils/pathResolvers.d.ts +53 -0
  226. package/dist/utils/pathResolvers.js +72 -0
  227. package/dist/utils/projectConfig.d.ts +119 -0
  228. package/dist/utils/projectConfig.js +171 -0
  229. package/dist/utils/retryFailedPromises.d.ts +2 -0
  230. package/dist/utils/retryFailedPromises.js +23 -0
  231. package/dist/utils/sessionAuth.d.ts +48 -0
  232. package/dist/utils/sessionAuth.js +164 -0
  233. package/dist/utils/setupFiles.d.ts +4 -0
  234. package/dist/utils/setupFiles.js +1192 -0
  235. package/dist/utils/typeGuards.d.ts +35 -0
  236. package/dist/utils/typeGuards.js +57 -0
  237. package/dist/utils/validationRules.d.ts +43 -0
  238. package/dist/utils/validationRules.js +42 -0
  239. package/dist/utils/versionDetection.d.ts +58 -0
  240. package/dist/utils/versionDetection.js +251 -0
  241. package/dist/utils/yamlConverter.d.ts +100 -0
  242. package/dist/utils/yamlConverter.js +428 -0
  243. package/dist/utils/yamlLoader.d.ts +70 -0
  244. package/dist/utils/yamlLoader.js +267 -0
  245. package/dist/utilsController.d.ts +106 -0
  246. package/dist/utilsController.js +863 -0
  247. package/package.json +75 -0
  248. package/scripts/copy-templates.ts +23 -0
  249. package/src/adapters/AdapterFactory.ts +510 -0
  250. package/src/adapters/DatabaseAdapter.ts +306 -0
  251. package/src/adapters/LegacyAdapter.ts +841 -0
  252. package/src/adapters/TablesDBAdapter.ts +773 -0
  253. package/src/adapters/index.ts +37 -0
  254. package/src/backups/operations/bucketBackup.ts +277 -0
  255. package/src/backups/operations/collectionBackup.ts +310 -0
  256. package/src/backups/operations/comprehensiveBackup.ts +342 -0
  257. package/src/backups/schemas/bucketManifest.ts +78 -0
  258. package/src/backups/schemas/comprehensiveManifest.ts +76 -0
  259. package/src/backups/tracking/centralizedTracking.ts +352 -0
  260. package/src/cli/commands/configCommands.ts +201 -0
  261. package/src/cli/commands/databaseCommands.ts +749 -0
  262. package/src/cli/commands/functionCommands.ts +418 -0
  263. package/src/cli/commands/schemaCommands.ts +200 -0
  264. package/src/cli/commands/storageCommands.ts +152 -0
  265. package/src/cli/commands/transferCommands.ts +457 -0
  266. package/src/collections/attributes.ts +2054 -0
  267. package/src/collections/attributes.ts.backup +1555 -0
  268. package/src/collections/indexes.ts +352 -0
  269. package/src/collections/methods.ts +745 -0
  270. package/src/collections/tableOperations.ts +506 -0
  271. package/src/collections/transferOperations.ts +590 -0
  272. package/src/collections/wipeOperations.ts +346 -0
  273. package/src/config/ConfigManager.ts +808 -0
  274. package/src/config/README.md +274 -0
  275. package/src/config/configMigration.ts +575 -0
  276. package/src/config/configValidation.ts +445 -0
  277. package/src/config/index.ts +10 -0
  278. package/src/config/services/ConfigDiscoveryService.ts +463 -0
  279. package/src/config/services/ConfigLoaderService.ts +740 -0
  280. package/src/config/services/ConfigMergeService.ts +388 -0
  281. package/src/config/services/ConfigValidationService.ts +394 -0
  282. package/src/config/services/SessionAuthService.ts +565 -0
  283. package/src/config/services/__tests__/ConfigMergeService.test.ts +351 -0
  284. package/src/config/services/index.ts +29 -0
  285. package/src/config/yamlConfig.ts +761 -0
  286. package/src/databases/methods.ts +49 -0
  287. package/src/databases/setup.ts +77 -0
  288. package/src/examples/yamlTerminologyExample.ts +346 -0
  289. package/src/functions/deployments.ts +220 -0
  290. package/src/functions/fnConfigDiscovery.ts +103 -0
  291. package/src/functions/methods.ts +271 -0
  292. package/src/functions/pathResolution.ts +227 -0
  293. package/src/functions/templates/count-docs-in-collection/README.md +54 -0
  294. package/src/functions/templates/count-docs-in-collection/src/main.ts +159 -0
  295. package/src/functions/templates/count-docs-in-collection/src/request.ts +9 -0
  296. package/src/functions/templates/hono-typescript/README.md +286 -0
  297. package/src/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
  298. package/src/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
  299. package/src/functions/templates/hono-typescript/src/app.ts +180 -0
  300. package/src/functions/templates/hono-typescript/src/context.ts +103 -0
  301. package/src/functions/templates/hono-typescript/src/index.ts +54 -0
  302. package/src/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
  303. package/src/functions/templates/typescript-node/README.md +32 -0
  304. package/src/functions/templates/typescript-node/src/context.ts +103 -0
  305. package/src/functions/templates/typescript-node/src/index.ts +29 -0
  306. package/src/functions/templates/uv/README.md +31 -0
  307. package/src/functions/templates/uv/pyproject.toml +30 -0
  308. package/src/functions/templates/uv/src/__init__.py +0 -0
  309. package/src/functions/templates/uv/src/context.py +125 -0
  310. package/src/functions/templates/uv/src/index.py +46 -0
  311. package/src/init.ts +62 -0
  312. package/src/interactiveCLI.ts +1136 -0
  313. package/src/main.ts +1661 -0
  314. package/src/migrations/afterImportActions.ts +580 -0
  315. package/src/migrations/appwriteToX.ts +664 -0
  316. package/src/migrations/comprehensiveTransfer.ts +2285 -0
  317. package/src/migrations/dataLoader.ts +1702 -0
  318. package/src/migrations/importController.ts +428 -0
  319. package/src/migrations/importDataActions.ts +315 -0
  320. package/src/migrations/relationships.ts +334 -0
  321. package/src/migrations/services/DataTransformationService.ts +196 -0
  322. package/src/migrations/services/FileHandlerService.ts +311 -0
  323. package/src/migrations/services/ImportOrchestrator.ts +666 -0
  324. package/src/migrations/services/RateLimitManager.ts +363 -0
  325. package/src/migrations/services/RelationshipResolver.ts +461 -0
  326. package/src/migrations/services/UserMappingService.ts +345 -0
  327. package/src/migrations/services/ValidationService.ts +349 -0
  328. package/src/migrations/transfer.ts +1068 -0
  329. package/src/migrations/yaml/YamlImportConfigLoader.ts +439 -0
  330. package/src/migrations/yaml/YamlImportIntegration.ts +446 -0
  331. package/src/migrations/yaml/generateImportSchemas.ts +1354 -0
  332. package/src/schemas/authUser.ts +23 -0
  333. package/src/setup.ts +8 -0
  334. package/src/setupCommands.ts +603 -0
  335. package/src/setupController.ts +43 -0
  336. package/src/shared/attributeMapper.ts +229 -0
  337. package/src/shared/backupMetadataSchema.ts +93 -0
  338. package/src/shared/backupTracking.ts +211 -0
  339. package/src/shared/confirmationDialogs.ts +327 -0
  340. package/src/shared/errorUtils.ts +110 -0
  341. package/src/shared/functionManager.ts +525 -0
  342. package/src/shared/indexManager.ts +254 -0
  343. package/src/shared/jsonSchemaGenerator.ts +383 -0
  344. package/src/shared/logging.ts +149 -0
  345. package/src/shared/messageFormatter.ts +208 -0
  346. package/src/shared/migrationHelpers.ts +232 -0
  347. package/src/shared/operationLogger.ts +20 -0
  348. package/src/shared/operationQueue.ts +377 -0
  349. package/src/shared/operationsTable.ts +338 -0
  350. package/src/shared/operationsTableSchema.ts +60 -0
  351. package/src/shared/progressManager.ts +278 -0
  352. package/src/shared/pydanticModelGenerator.ts +618 -0
  353. package/src/shared/relationshipExtractor.ts +214 -0
  354. package/src/shared/schemaGenerator.ts +644 -0
  355. package/src/shared/selectionDialogs.ts +749 -0
  356. package/src/storage/backupCompression.ts +88 -0
  357. package/src/storage/methods.ts +698 -0
  358. package/src/storage/schemas.ts +205 -0
  359. package/src/types/node-appwrite-tablesdb.d.ts +44 -0
  360. package/src/types.ts +9 -0
  361. package/src/users/methods.ts +359 -0
  362. package/src/utils/ClientFactory.ts +240 -0
  363. package/src/utils/configDiscovery.ts +557 -0
  364. package/src/utils/configMigration.ts +348 -0
  365. package/src/utils/constantsGenerator.ts +369 -0
  366. package/src/utils/dataConverters.ts +159 -0
  367. package/src/utils/directoryUtils.ts +61 -0
  368. package/src/utils/getClientFromConfig.ts +257 -0
  369. package/src/utils/helperFunctions.ts +228 -0
  370. package/src/utils/index.ts +2 -0
  371. package/src/utils/loadConfigs.ts +449 -0
  372. package/src/utils/pathResolvers.ts +81 -0
  373. package/src/utils/projectConfig.ts +299 -0
  374. package/src/utils/retryFailedPromises.ts +29 -0
  375. package/src/utils/sessionAuth.ts +230 -0
  376. package/src/utils/setupFiles.ts +1238 -0
  377. package/src/utils/typeGuards.ts +65 -0
  378. package/src/utils/validationRules.ts +88 -0
  379. package/src/utils/versionDetection.ts +292 -0
  380. package/src/utils/yamlConverter.ts +542 -0
  381. package/src/utils/yamlLoader.ts +371 -0
  382. package/src/utilsController.ts +1203 -0
  383. package/tests/README.md +497 -0
  384. package/tests/adapters/AdapterFactory.test.ts +277 -0
  385. package/tests/integration/syncOperations.test.ts +463 -0
  386. package/tests/jest.config.js +25 -0
  387. package/tests/migration/configMigration.test.ts +546 -0
  388. package/tests/setup.ts +62 -0
  389. package/tests/testUtils.ts +340 -0
  390. package/tests/utils/loadConfigs.test.ts +350 -0
  391. package/tests/validation/configValidation.test.ts +412 -0
  392. package/tsconfig.json +44 -0
@@ -0,0 +1,863 @@
1
+ import { Client, Databases, Query, Storage, Users, } from "node-appwrite";
2
+ import {} from "@njdamstra/appwrite-utils";
3
+ import { findAppwriteConfig, findFunctionsDir, } from "./utils/loadConfigs.js";
4
+ import { normalizeFunctionName, validateFunctionDirectory } from './functions/pathResolution.js';
5
+ import { UsersController } from "./users/methods.js";
6
+ import { AppwriteToX } from "./migrations/appwriteToX.js";
7
+ import { ImportController } from "./migrations/importController.js";
8
+ import { ImportDataActions } from "./migrations/importDataActions.js";
9
+ import { ensureDatabasesExist, wipeOtherDatabases, ensureCollectionsExist, } from "./databases/setup.js";
10
+ import { createOrUpdateCollections, createOrUpdateCollectionsViaAdapter, wipeDatabase, generateSchemas, fetchAllCollections, wipeCollection, } from "./collections/methods.js";
11
+ import { wipeAllTables, wipeTableRows } from "./collections/methods.js";
12
+ import { backupDatabase, ensureDatabaseConfigBucketsExist, wipeDocumentStorage, } from "./storage/methods.js";
13
+ import path from "path";
14
+ import { converterFunctions, validationRules, } from "@njdamstra/appwrite-utils";
15
+ import { afterImportActions } from "./migrations/afterImportActions.js";
16
+ import { transferDatabaseLocalToLocal, transferDatabaseLocalToRemote, transferStorageLocalToLocal, transferStorageLocalToRemote, transferUsersLocalToRemote, } from "./migrations/transfer.js";
17
+ import { getClient, getClientWithAuth } from "./utils/getClientFromConfig.js";
18
+ import { getAdapterFromConfig } from "./utils/getClientFromConfig.js";
19
+ import { hasSessionAuth, findSessionByEndpointAndProject, isValidSessionCookie } from "./utils/sessionAuth.js";
20
+ import { fetchAllDatabases } from "./databases/methods.js";
21
+ import { listFunctions, updateFunctionSpecifications, } from "./functions/methods.js";
22
+ import chalk from "chalk";
23
+ import { deployLocalFunction } from "./functions/deployments.js";
24
+ import fs from "node:fs";
25
+ import { configureLogging, updateLogger, logger } from "./shared/logging.js";
26
+ import { MessageFormatter, Messages } from "./shared/messageFormatter.js";
27
+ import { SchemaGenerator } from "./shared/schemaGenerator.js";
28
+ import { findYamlConfig } from "./config/yamlConfig.js";
29
+ import { createImportSchemas } from "./migrations/yaml/generateImportSchemas.js";
30
+ import { validateCollectionsTablesConfig, reportValidationResults, validateWithStrictMode } from "./config/configValidation.js";
31
+ import { ConfigManager } from "./config/ConfigManager.js";
32
+ import { ClientFactory } from "./utils/ClientFactory.js";
33
+ export class UtilsController {
34
+ // ──────────────────────────────────────────────────
35
+ // SINGLETON PATTERN
36
+ // ──────────────────────────────────────────────────
37
+ static instance = null;
38
+ isInitialized = false;
39
+ /**
40
+ * Get the UtilsController singleton instance
41
+ */
42
+ static getInstance(currentUserDir, directConfig) {
43
+ // Clear instance if currentUserDir has changed
44
+ if (UtilsController.instance &&
45
+ UtilsController.instance.currentUserDir !== currentUserDir) {
46
+ logger.debug(`Clearing singleton: currentUserDir changed from ${UtilsController.instance.currentUserDir} to ${currentUserDir}`, { prefix: "UtilsController" });
47
+ UtilsController.clearInstance();
48
+ }
49
+ // Clear instance if directConfig endpoint or project has changed
50
+ if (UtilsController.instance && directConfig) {
51
+ const existingConfig = UtilsController.instance.config;
52
+ if (existingConfig) {
53
+ const endpointChanged = directConfig.appwriteEndpoint &&
54
+ existingConfig.appwriteEndpoint !== directConfig.appwriteEndpoint;
55
+ const projectChanged = directConfig.appwriteProject &&
56
+ existingConfig.appwriteProject !== directConfig.appwriteProject;
57
+ if (endpointChanged || projectChanged) {
58
+ logger.debug("Clearing singleton: endpoint or project changed", { prefix: "UtilsController" });
59
+ UtilsController.clearInstance();
60
+ }
61
+ }
62
+ }
63
+ if (!UtilsController.instance) {
64
+ UtilsController.instance = new UtilsController(currentUserDir, directConfig);
65
+ }
66
+ return UtilsController.instance;
67
+ }
68
+ /**
69
+ * Clear the singleton instance (useful for testing)
70
+ */
71
+ static clearInstance() {
72
+ UtilsController.instance = null;
73
+ }
74
+ // ──────────────────────────────────────────────────
75
+ // INSTANCE FIELDS
76
+ // ──────────────────────────────────────────────────
77
+ appwriteFolderPath;
78
+ appwriteConfigPath;
79
+ currentUserDir;
80
+ config;
81
+ appwriteServer;
82
+ database;
83
+ storage;
84
+ adapter;
85
+ converterDefinitions = converterFunctions;
86
+ validityRuleDefinitions = validationRules;
87
+ afterImportActionsDefinitions = afterImportActions;
88
+ constructor(currentUserDir, directConfig) {
89
+ this.currentUserDir = currentUserDir;
90
+ const basePath = currentUserDir;
91
+ if (directConfig) {
92
+ let hasErrors = false;
93
+ if (!directConfig.appwriteEndpoint) {
94
+ MessageFormatter.error("Appwrite endpoint is required", undefined, { prefix: "Config" });
95
+ hasErrors = true;
96
+ }
97
+ if (!directConfig.appwriteProject) {
98
+ MessageFormatter.error("Appwrite project is required", undefined, { prefix: "Config" });
99
+ hasErrors = true;
100
+ }
101
+ // Check authentication: either API key or session auth is required
102
+ const hasValidSession = directConfig.appwriteEndpoint && directConfig.appwriteProject &&
103
+ hasSessionAuth(directConfig.appwriteEndpoint, directConfig.appwriteProject);
104
+ if (!directConfig.appwriteKey && !hasValidSession) {
105
+ MessageFormatter.error("Authentication required: provide an API key or login with 'appwrite login'", undefined, { prefix: "Config" });
106
+ hasErrors = true;
107
+ }
108
+ else if (!directConfig.appwriteKey && hasValidSession) {
109
+ MessageFormatter.info("Using session authentication (no API key required)", { prefix: "Auth" });
110
+ }
111
+ else if (directConfig.appwriteKey && hasValidSession) {
112
+ MessageFormatter.info("API key provided, session authentication also available", { prefix: "Auth" });
113
+ }
114
+ if (!hasErrors) {
115
+ // Only set config if we have all required fields
116
+ this.appwriteFolderPath = basePath;
117
+ this.config = {
118
+ appwriteEndpoint: directConfig.appwriteEndpoint,
119
+ appwriteProject: directConfig.appwriteProject,
120
+ appwriteKey: directConfig.appwriteKey || "",
121
+ appwriteClient: null,
122
+ apiMode: "auto", // Default to auto-detect for dual API support
123
+ authMethod: "auto", // Default to auto-detect authentication method
124
+ enableBackups: false,
125
+ backupInterval: 0,
126
+ backupRetention: 0,
127
+ enableBackupCleanup: false,
128
+ enableMockData: false,
129
+ documentBucketId: "",
130
+ usersCollectionName: "",
131
+ databases: [],
132
+ buckets: [],
133
+ functions: [],
134
+ logging: {
135
+ enabled: false,
136
+ level: "info",
137
+ console: false,
138
+ },
139
+ };
140
+ }
141
+ }
142
+ else {
143
+ // Try to find config file
144
+ const appwriteConfigFound = findAppwriteConfig(basePath);
145
+ if (!appwriteConfigFound) {
146
+ MessageFormatter.warning("No appwriteConfig.ts found and no direct configuration provided", { prefix: "Config" });
147
+ return;
148
+ }
149
+ this.appwriteConfigPath = appwriteConfigFound;
150
+ this.appwriteFolderPath = appwriteConfigFound; // For YAML configs, findAppwriteConfig already returns the correct directory
151
+ }
152
+ }
153
+ async init(options = {}) {
154
+ const { validate = false, strictMode = false } = options;
155
+ const configManager = ConfigManager.getInstance();
156
+ // Load config if not already loaded
157
+ if (!configManager.hasConfig()) {
158
+ await configManager.loadConfig({
159
+ configDir: this.currentUserDir,
160
+ validate,
161
+ strictMode,
162
+ });
163
+ }
164
+ const config = configManager.getConfig();
165
+ // Configure logging based on config
166
+ if (config.logging) {
167
+ configureLogging(config.logging);
168
+ updateLogger();
169
+ }
170
+ // Create client and adapter (session already in config from ConfigManager)
171
+ const { client, adapter } = await ClientFactory.createFromConfig(config);
172
+ this.appwriteServer = client;
173
+ this.adapter = adapter;
174
+ this.config = config;
175
+ // Update config.apiMode from adapter if it's auto or not set
176
+ if (adapter && (!config.apiMode || config.apiMode === 'auto')) {
177
+ this.config.apiMode = adapter.getApiMode();
178
+ logger.debug(`Updated config.apiMode from adapter during init: ${this.config.apiMode}`, { prefix: "UtilsController" });
179
+ }
180
+ this.database = new Databases(this.appwriteServer);
181
+ this.storage = new Storage(this.appwriteServer);
182
+ this.config.appwriteClient = this.appwriteServer;
183
+ // Log only on FIRST initialization to avoid spam
184
+ if (!this.isInitialized) {
185
+ const apiMode = adapter.getApiMode();
186
+ const configApiMode = this.config.apiMode;
187
+ MessageFormatter.info(`Database adapter initialized (apiMode: ${apiMode}, config.apiMode: ${configApiMode})`, { prefix: "Adapter" });
188
+ this.isInitialized = true;
189
+ }
190
+ else {
191
+ logger.debug("Adapter reused from cache", { prefix: "UtilsController" });
192
+ }
193
+ }
194
+ async reloadConfig() {
195
+ const configManager = ConfigManager.getInstance();
196
+ // Session preservation is automatic in ConfigManager
197
+ const config = await configManager.reloadConfig();
198
+ // Configure logging based on updated config
199
+ if (config.logging) {
200
+ configureLogging(config.logging);
201
+ updateLogger();
202
+ }
203
+ // Recreate client and adapter
204
+ const { client, adapter } = await ClientFactory.createFromConfig(config);
205
+ this.appwriteServer = client;
206
+ this.adapter = adapter;
207
+ this.config = config;
208
+ this.database = new Databases(this.appwriteServer);
209
+ this.storage = new Storage(this.appwriteServer);
210
+ this.config.appwriteClient = this.appwriteServer;
211
+ logger.debug("Config reloaded, adapter refreshed", { prefix: "UtilsController" });
212
+ }
213
+ async ensureDatabaseConfigBucketsExist(databases = []) {
214
+ await this.init();
215
+ if (!this.storage) {
216
+ MessageFormatter.error("Storage not initialized", undefined, { prefix: "Controller" });
217
+ return;
218
+ }
219
+ if (!this.config) {
220
+ MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
221
+ return;
222
+ }
223
+ await ensureDatabaseConfigBucketsExist(this.storage, this.config, databases);
224
+ }
225
+ async ensureDatabasesExist(databases) {
226
+ await this.init();
227
+ if (!this.config) {
228
+ MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
229
+ return;
230
+ }
231
+ await this.ensureDatabaseConfigBucketsExist(databases);
232
+ await ensureDatabasesExist(this.config, databases);
233
+ }
234
+ async ensureCollectionsExist(database, collections) {
235
+ await this.init();
236
+ if (!this.config) {
237
+ MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
238
+ return;
239
+ }
240
+ await ensureCollectionsExist(this.config, database, collections);
241
+ }
242
+ async getDatabasesByIds(ids) {
243
+ await this.init();
244
+ if (!this.database) {
245
+ MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
246
+ return;
247
+ }
248
+ if (ids.length === 0)
249
+ return [];
250
+ const dbs = await this.database.list([
251
+ Query.limit(500),
252
+ Query.equal("$id", ids),
253
+ ]);
254
+ return dbs.databases;
255
+ }
256
+ async fetchAllBuckets() {
257
+ await this.init();
258
+ if (!this.storage) {
259
+ MessageFormatter.warning("Storage not initialized - buckets will be empty", { prefix: "Controller" });
260
+ return { buckets: [] };
261
+ }
262
+ try {
263
+ const result = await this.storage.listBuckets([
264
+ Query.limit(1000) // Increase limit to get all buckets
265
+ ]);
266
+ MessageFormatter.success(`Found ${result.buckets.length} buckets`, { prefix: "Controller" });
267
+ return result;
268
+ }
269
+ catch (error) {
270
+ MessageFormatter.error(`Failed to fetch buckets: ${error.message || error}`, error instanceof Error ? error : undefined, { prefix: "Controller" });
271
+ return { buckets: [] };
272
+ }
273
+ }
274
+ async wipeOtherDatabases(databasesToKeep) {
275
+ await this.init();
276
+ if (!this.database) {
277
+ MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
278
+ return;
279
+ }
280
+ await wipeOtherDatabases(this.database, databasesToKeep);
281
+ }
282
+ async wipeUsers() {
283
+ await this.init();
284
+ if (!this.config || !this.database) {
285
+ MessageFormatter.error("Config or database not initialized", undefined, { prefix: "Controller" });
286
+ return;
287
+ }
288
+ const usersController = new UsersController(this.config, this.database);
289
+ await usersController.wipeUsers();
290
+ }
291
+ async backupDatabase(database, format = 'json') {
292
+ await this.init();
293
+ if (!this.database || !this.storage || !this.config) {
294
+ MessageFormatter.error("Database, storage, or config not initialized", undefined, { prefix: "Controller" });
295
+ return;
296
+ }
297
+ await backupDatabase(this.config, this.database, database.$id, this.storage, format);
298
+ }
299
+ async listAllFunctions() {
300
+ await this.init();
301
+ if (!this.appwriteServer) {
302
+ MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
303
+ return [];
304
+ }
305
+ const { functions } = await listFunctions(this.appwriteServer, [
306
+ Query.limit(1000),
307
+ ]);
308
+ return functions;
309
+ }
310
+ async findFunctionDirectories() {
311
+ if (!this.appwriteFolderPath) {
312
+ MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
313
+ return new Map();
314
+ }
315
+ const functionsDir = findFunctionsDir(this.appwriteFolderPath);
316
+ if (!functionsDir) {
317
+ MessageFormatter.error("Failed to find functions directory", undefined, { prefix: "Controller" });
318
+ return new Map();
319
+ }
320
+ const functionDirMap = new Map();
321
+ const entries = fs.readdirSync(functionsDir, { withFileTypes: true });
322
+ for (const entry of entries) {
323
+ if (entry.isDirectory()) {
324
+ const functionPath = path.join(functionsDir, entry.name);
325
+ // Validate it's a function directory
326
+ if (!validateFunctionDirectory(functionPath)) {
327
+ continue; // Skip invalid directories
328
+ }
329
+ // Match with config functions using normalized names
330
+ if (this.config?.functions) {
331
+ const normalizedEntryName = normalizeFunctionName(entry.name);
332
+ const matchingFunc = this.config.functions.find((f) => normalizeFunctionName(f.name) === normalizedEntryName);
333
+ if (matchingFunc) {
334
+ functionDirMap.set(matchingFunc.name, functionPath);
335
+ }
336
+ }
337
+ }
338
+ }
339
+ return functionDirMap;
340
+ }
341
+ async deployFunction(functionName, functionPath, functionConfig) {
342
+ await this.init();
343
+ if (!this.appwriteServer) {
344
+ MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
345
+ return;
346
+ }
347
+ if (!functionConfig) {
348
+ functionConfig = this.config?.functions?.find((f) => f.name === functionName);
349
+ }
350
+ if (!functionConfig) {
351
+ MessageFormatter.error(`Function ${functionName} not found in config`, undefined, { prefix: "Controller" });
352
+ return;
353
+ }
354
+ await deployLocalFunction(this.appwriteServer, functionName, functionConfig, functionPath);
355
+ }
356
+ async syncFunctions() {
357
+ await this.init();
358
+ if (!this.appwriteServer) {
359
+ MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
360
+ return;
361
+ }
362
+ const localFunctions = this.config?.functions || [];
363
+ const remoteFunctions = await listFunctions(this.appwriteServer, [
364
+ Query.limit(1000),
365
+ ]);
366
+ for (const localFunction of localFunctions) {
367
+ MessageFormatter.progress(`Syncing function ${localFunction.name}...`, { prefix: "Functions" });
368
+ await this.deployFunction(localFunction.name);
369
+ }
370
+ MessageFormatter.success("All functions synchronized successfully!", { prefix: "Functions" });
371
+ }
372
+ async wipeDatabase(database, wipeBucket = false) {
373
+ await this.init();
374
+ if (!this.database || !this.config)
375
+ throw new Error("Database not initialized");
376
+ try {
377
+ // Session is already in config from ConfigManager
378
+ const { adapter, apiMode } = await getAdapterFromConfig(this.config, false);
379
+ if (apiMode === 'tablesdb') {
380
+ await wipeAllTables(adapter, database.$id);
381
+ }
382
+ else {
383
+ await wipeDatabase(this.database, database.$id);
384
+ }
385
+ }
386
+ catch {
387
+ await wipeDatabase(this.database, database.$id);
388
+ }
389
+ if (wipeBucket) {
390
+ await this.wipeBucketFromDatabase(database);
391
+ }
392
+ }
393
+ async wipeBucketFromDatabase(database) {
394
+ // Check configured bucket in database config
395
+ const configuredBucket = this.config?.databases?.find((db) => db.$id === database.$id)?.bucket;
396
+ if (configuredBucket?.$id) {
397
+ await this.wipeDocumentStorage(configuredBucket.$id);
398
+ }
399
+ // Also check for document bucket ID pattern
400
+ if (this.config?.documentBucketId) {
401
+ const documentBucketId = `${this.config.documentBucketId}_${database.$id
402
+ .toLowerCase()
403
+ .trim()
404
+ .replace(/\s+/g, "")}`;
405
+ try {
406
+ await this.wipeDocumentStorage(documentBucketId);
407
+ }
408
+ catch (error) {
409
+ // Ignore if bucket doesn't exist
410
+ if (error?.type !== "storage_bucket_not_found") {
411
+ throw error;
412
+ }
413
+ }
414
+ }
415
+ }
416
+ async wipeCollection(database, collection) {
417
+ await this.init();
418
+ if (!this.database || !this.config)
419
+ throw new Error("Database not initialized");
420
+ try {
421
+ // Session is already in config from ConfigManager
422
+ const { adapter, apiMode } = await getAdapterFromConfig(this.config, false);
423
+ if (apiMode === 'tablesdb') {
424
+ await wipeTableRows(adapter, database.$id, collection.$id);
425
+ }
426
+ else {
427
+ await wipeCollection(this.database, database.$id, collection.$id);
428
+ }
429
+ }
430
+ catch {
431
+ await wipeCollection(this.database, database.$id, collection.$id);
432
+ }
433
+ }
434
+ async wipeDocumentStorage(bucketId) {
435
+ await this.init();
436
+ if (!this.storage)
437
+ throw new Error("Storage not initialized");
438
+ await wipeDocumentStorage(this.storage, bucketId);
439
+ }
440
+ async createOrUpdateCollectionsForDatabases(databases, collections = []) {
441
+ await this.init();
442
+ if (!this.database || !this.config)
443
+ throw new Error("Database or config not initialized");
444
+ for (const database of databases) {
445
+ await this.createOrUpdateCollections(database, undefined, collections);
446
+ }
447
+ }
448
+ async createOrUpdateCollections(database, deletedCollections, collections = []) {
449
+ await this.init();
450
+ if (!this.database || !this.config)
451
+ throw new Error("Database or config not initialized");
452
+ // Ensure apiMode is properly set from adapter
453
+ if (this.adapter && (!this.config.apiMode || this.config.apiMode === 'auto')) {
454
+ this.config.apiMode = this.adapter.getApiMode();
455
+ logger.debug(`Updated config.apiMode from adapter: ${this.config.apiMode}`, { prefix: "UtilsController" });
456
+ }
457
+ // Ensure we don't carry state between databases in a multi-db push
458
+ // This resets processed sets and name->id mapping per database
459
+ try {
460
+ const { clearProcessingState } = await import('./shared/operationQueue.js');
461
+ clearProcessingState();
462
+ }
463
+ catch { }
464
+ // Always prefer adapter path for unified behavior. LegacyAdapter internally translates when needed.
465
+ if (this.adapter) {
466
+ logger.debug("Using adapter for createOrUpdateCollections (unified path)", {
467
+ prefix: "UtilsController",
468
+ apiMode: this.adapter.getApiMode()
469
+ });
470
+ await createOrUpdateCollectionsViaAdapter(this.adapter, database.$id, this.config, deletedCollections, collections);
471
+ }
472
+ else {
473
+ // Fallback if adapter is unavailable for some reason
474
+ logger.debug("Adapter unavailable, falling back to legacy Databases path", { prefix: "UtilsController" });
475
+ await createOrUpdateCollections(this.database, database.$id, this.config, deletedCollections, collections);
476
+ }
477
+ }
478
+ async generateSchemas() {
479
+ // Schema generation doesn't need Appwrite connection, just config
480
+ if (!this.config) {
481
+ MessageFormatter.progress("Loading config from ConfigManager...", { prefix: "Config" });
482
+ try {
483
+ const configManager = ConfigManager.getInstance();
484
+ // Load config if not already loaded
485
+ if (!configManager.hasConfig()) {
486
+ await configManager.loadConfig({
487
+ configDir: this.currentUserDir,
488
+ validate: false,
489
+ strictMode: false,
490
+ });
491
+ }
492
+ this.config = configManager.getConfig();
493
+ MessageFormatter.info("Config loaded successfully from ConfigManager", { prefix: "Config" });
494
+ }
495
+ catch (error) {
496
+ MessageFormatter.error("Failed to load config", error instanceof Error ? error : undefined, { prefix: "Config" });
497
+ return;
498
+ }
499
+ }
500
+ if (!this.appwriteFolderPath) {
501
+ MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
502
+ return;
503
+ }
504
+ await generateSchemas(this.config, this.appwriteFolderPath);
505
+ }
506
+ async importData(options = {}) {
507
+ await this.init();
508
+ if (!this.database) {
509
+ MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
510
+ return;
511
+ }
512
+ if (!this.storage) {
513
+ MessageFormatter.error("Storage not initialized", undefined, { prefix: "Controller" });
514
+ return;
515
+ }
516
+ if (!this.config) {
517
+ MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
518
+ return;
519
+ }
520
+ if (!this.appwriteFolderPath) {
521
+ MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
522
+ return;
523
+ }
524
+ const importDataActions = new ImportDataActions(this.database, this.storage, this.config, this.converterDefinitions, this.validityRuleDefinitions, this.afterImportActionsDefinitions);
525
+ const importController = new ImportController(this.config, this.database, this.storage, this.appwriteFolderPath, importDataActions, options, options.databases);
526
+ await importController.run(options.collections);
527
+ }
528
+ async synchronizeConfigurations(databases, config, databaseSelections, bucketSelections) {
529
+ await this.init();
530
+ if (!this.storage) {
531
+ MessageFormatter.error("Storage not initialized", undefined, { prefix: "Controller" });
532
+ return;
533
+ }
534
+ const configToUse = config || this.config;
535
+ if (!configToUse) {
536
+ MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
537
+ return;
538
+ }
539
+ if (!this.appwriteFolderPath) {
540
+ MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
541
+ return;
542
+ }
543
+ // If selections are provided, filter the databases accordingly
544
+ let filteredDatabases = databases;
545
+ if (databaseSelections && databaseSelections.length > 0) {
546
+ // Convert selections to Models.Database format
547
+ filteredDatabases = [];
548
+ const allDatabases = databases ? databases : await fetchAllDatabases(this.database);
549
+ for (const selection of databaseSelections) {
550
+ const database = allDatabases.find(db => db.$id === selection.databaseId);
551
+ if (database) {
552
+ filteredDatabases.push(database);
553
+ }
554
+ else {
555
+ MessageFormatter.warning(`Database with ID ${selection.databaseId} not found`, { prefix: "Controller" });
556
+ }
557
+ }
558
+ MessageFormatter.info(`Syncing ${filteredDatabases.length} selected databases out of ${allDatabases.length} available`, { prefix: "Controller" });
559
+ }
560
+ const appwriteToX = new AppwriteToX(configToUse, this.appwriteFolderPath, this.storage);
561
+ await appwriteToX.toSchemas(filteredDatabases);
562
+ // Update the controller's config with the synchronized collections
563
+ this.config = appwriteToX.updatedConfig;
564
+ // Write the updated config back to disk
565
+ const generator = new SchemaGenerator(this.config, this.appwriteFolderPath);
566
+ const yamlConfigPath = findYamlConfig(this.appwriteFolderPath);
567
+ const isYamlProject = !!yamlConfigPath;
568
+ await generator.updateConfig(this.config, isYamlProject);
569
+ // Regenerate JSON schemas to reflect any table terminology fixes
570
+ try {
571
+ MessageFormatter.progress("Regenerating JSON schemas...", { prefix: "Sync" });
572
+ await createImportSchemas(this.appwriteFolderPath);
573
+ MessageFormatter.success("JSON schemas regenerated successfully", { prefix: "Sync" });
574
+ }
575
+ catch (error) {
576
+ // Log error but don't fail the sync process
577
+ const errorMessage = error instanceof Error ? error.message : String(error);
578
+ MessageFormatter.warning(`Failed to regenerate JSON schemas, but sync completed: ${errorMessage}`, { prefix: "Sync" });
579
+ logger.warn("Schema regeneration failed during sync:", error);
580
+ }
581
+ }
582
+ async selectivePull(databaseSelections, bucketSelections) {
583
+ await this.init();
584
+ if (!this.database) {
585
+ MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
586
+ return;
587
+ }
588
+ MessageFormatter.progress("Starting selective pull (Appwrite → local config)...", { prefix: "Controller" });
589
+ // Convert database selections to Models.Database format
590
+ const selectedDatabases = [];
591
+ for (const dbSelection of databaseSelections) {
592
+ // Get the full database object from the controller
593
+ const databases = await fetchAllDatabases(this.database);
594
+ const database = databases.find(db => db.$id === dbSelection.databaseId);
595
+ if (database) {
596
+ selectedDatabases.push(database);
597
+ MessageFormatter.info(`Selected database: ${database.name} (${database.$id})`, { prefix: "Controller" });
598
+ // Log selected tables for this database
599
+ if (dbSelection.tableIds && dbSelection.tableIds.length > 0) {
600
+ MessageFormatter.info(` Tables: ${dbSelection.tableIds.join(', ')}`, { prefix: "Controller" });
601
+ }
602
+ }
603
+ else {
604
+ MessageFormatter.warning(`Database with ID ${dbSelection.databaseId} not found`, { prefix: "Controller" });
605
+ }
606
+ }
607
+ if (selectedDatabases.length === 0) {
608
+ MessageFormatter.warning("No valid databases selected for pull", { prefix: "Controller" });
609
+ return;
610
+ }
611
+ // Log bucket selections if provided
612
+ if (bucketSelections && bucketSelections.length > 0) {
613
+ MessageFormatter.info(`Selected ${bucketSelections.length} buckets:`, { prefix: "Controller" });
614
+ for (const bucketSelection of bucketSelections) {
615
+ const dbInfo = bucketSelection.databaseId ? ` (DB: ${bucketSelection.databaseId})` : '';
616
+ MessageFormatter.info(` - ${bucketSelection.bucketName} (${bucketSelection.bucketId})${dbInfo}`, { prefix: "Controller" });
617
+ }
618
+ }
619
+ // Perform selective sync using the enhanced synchronizeConfigurations method
620
+ await this.synchronizeConfigurations(selectedDatabases, this.config, databaseSelections, bucketSelections);
621
+ MessageFormatter.success("Selective pull completed successfully! Remote config pulled to local.", { prefix: "Controller" });
622
+ }
623
+ async selectivePush(databaseSelections, bucketSelections) {
624
+ await this.init();
625
+ if (!this.database) {
626
+ MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
627
+ return;
628
+ }
629
+ // Always reload config from disk so pushes use current local YAML/Ts definitions
630
+ try {
631
+ await this.reloadConfig();
632
+ MessageFormatter.info("Reloaded config from disk for push", { prefix: "Controller" });
633
+ }
634
+ catch (e) {
635
+ // Non-fatal; continue with existing config
636
+ MessageFormatter.warning("Could not reload config; continuing with current in-memory config", { prefix: "Controller" });
637
+ }
638
+ MessageFormatter.progress("Starting selective push (local config → Appwrite)...", { prefix: "Controller" });
639
+ // Convert database selections to Models.Database format
640
+ const selectedDatabases = [];
641
+ for (const dbSelection of databaseSelections) {
642
+ // Get the full database object from the controller
643
+ const databases = await fetchAllDatabases(this.database);
644
+ const database = databases.find(db => db.$id === dbSelection.databaseId);
645
+ if (database) {
646
+ selectedDatabases.push(database);
647
+ MessageFormatter.info(`Selected database: ${database.name} (${database.$id})`, { prefix: "Controller" });
648
+ // Log selected tables for this database
649
+ if (dbSelection.tableIds && dbSelection.tableIds.length > 0) {
650
+ MessageFormatter.info(` Tables: ${dbSelection.tableIds.join(', ')}`, { prefix: "Controller" });
651
+ }
652
+ }
653
+ else {
654
+ MessageFormatter.warning(`Database with ID ${dbSelection.databaseId} not found`, { prefix: "Controller" });
655
+ }
656
+ }
657
+ if (selectedDatabases.length === 0) {
658
+ MessageFormatter.warning("No valid databases selected for push", { prefix: "Controller" });
659
+ return;
660
+ }
661
+ // Log bucket selections if provided
662
+ if (bucketSelections && bucketSelections.length > 0) {
663
+ MessageFormatter.info(`Selected ${bucketSelections.length} buckets:`, { prefix: "Controller" });
664
+ for (const bucketSelection of bucketSelections) {
665
+ const dbInfo = bucketSelection.databaseId ? ` (DB: ${bucketSelection.databaseId})` : '';
666
+ MessageFormatter.info(` - ${bucketSelection.bucketName} (${bucketSelection.bucketId})${dbInfo}`, { prefix: "Controller" });
667
+ }
668
+ }
669
+ // PUSH OPERATION: Push local configuration to Appwrite
670
+ // Build database-specific collection mappings from databaseSelections
671
+ const databaseCollectionsMap = new Map();
672
+ // Get all collections/tables from config (they're at the root level, not nested in databases)
673
+ const allCollections = this.config?.collections || this.config?.tables || [];
674
+ // Create database-specific collection mapping to preserve relationships
675
+ for (const dbSelection of databaseSelections) {
676
+ const collectionsForDatabase = [];
677
+ MessageFormatter.info(`Processing collections for database: ${dbSelection.databaseId}`, { prefix: "Controller" });
678
+ // Filter collections that were selected for THIS specific database
679
+ for (const collection of allCollections) {
680
+ const collectionId = collection.$id || collection.id;
681
+ // Check if this collection was selected for THIS database
682
+ if (dbSelection.tableIds.includes(collectionId)) {
683
+ collectionsForDatabase.push(collection);
684
+ const source = collection._isFromTablesDir ? 'tables/' : 'collections/';
685
+ MessageFormatter.info(` - Selected collection: ${collection.name || collectionId} for database ${dbSelection.databaseId} [source: ${source}]`, { prefix: "Controller" });
686
+ }
687
+ }
688
+ databaseCollectionsMap.set(dbSelection.databaseId, collectionsForDatabase);
689
+ MessageFormatter.info(`Database ${dbSelection.databaseId}: ${collectionsForDatabase.length} collections selected`, { prefix: "Controller" });
690
+ }
691
+ // Calculate total collections for logging
692
+ const totalSelectedCollections = Array.from(databaseCollectionsMap.values())
693
+ .reduce((total, collections) => total + collections.length, 0);
694
+ MessageFormatter.info(`Pushing ${totalSelectedCollections} selected tables/collections to ${databaseCollectionsMap.size} databases`, { prefix: "Controller" });
695
+ // Ensure databases exist
696
+ await this.ensureDatabasesExist(selectedDatabases);
697
+ await this.ensureDatabaseConfigBucketsExist(selectedDatabases);
698
+ // Create/update collections with database-specific context
699
+ for (const database of selectedDatabases) {
700
+ const collectionsForThisDatabase = databaseCollectionsMap.get(database.$id) || [];
701
+ if (collectionsForThisDatabase.length > 0) {
702
+ MessageFormatter.info(`Pushing ${collectionsForThisDatabase.length} collections to database ${database.$id} (${database.name})`, { prefix: "Controller" });
703
+ await this.createOrUpdateCollections(database, undefined, collectionsForThisDatabase);
704
+ }
705
+ else {
706
+ MessageFormatter.info(`No collections selected for database ${database.$id} (${database.name})`, { prefix: "Controller" });
707
+ }
708
+ }
709
+ MessageFormatter.success("Selective push completed successfully! Local config pushed to Appwrite.", { prefix: "Controller" });
710
+ }
711
+ async syncDb(databases = [], collections = []) {
712
+ await this.init();
713
+ if (!this.database) {
714
+ MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
715
+ return;
716
+ }
717
+ if (databases.length === 0) {
718
+ const allDatabases = await fetchAllDatabases(this.database);
719
+ databases = allDatabases;
720
+ }
721
+ // Ensure DBs exist
722
+ await this.ensureDatabasesExist(databases);
723
+ await this.ensureDatabaseConfigBucketsExist(databases);
724
+ await this.createOrUpdateCollectionsForDatabases(databases, collections);
725
+ }
726
+ getAppwriteFolderPath() {
727
+ return this.appwriteFolderPath;
728
+ }
729
+ async transferData(options) {
730
+ let sourceClient = this.database;
731
+ let targetClient;
732
+ let sourceDatabases = [];
733
+ let targetDatabases = [];
734
+ if (!sourceClient) {
735
+ MessageFormatter.error("Source database not initialized", undefined, { prefix: "Controller" });
736
+ return;
737
+ }
738
+ if (options.isRemote) {
739
+ if (!options.transferEndpoint ||
740
+ !options.transferProject ||
741
+ !options.transferKey) {
742
+ MessageFormatter.error("Remote transfer options are missing", undefined, { prefix: "Controller" });
743
+ return;
744
+ }
745
+ const remoteClient = getClient(options.transferEndpoint, options.transferProject, options.transferKey);
746
+ targetClient = new Databases(remoteClient);
747
+ sourceDatabases = await fetchAllDatabases(sourceClient);
748
+ targetDatabases = await fetchAllDatabases(targetClient);
749
+ }
750
+ else {
751
+ targetClient = sourceClient;
752
+ sourceDatabases = targetDatabases = await fetchAllDatabases(sourceClient);
753
+ }
754
+ // Always perform database transfer if databases are specified
755
+ if (options.fromDb && options.targetDb) {
756
+ const fromDb = sourceDatabases.find((db) => db.$id === options.fromDb.$id);
757
+ const targetDb = targetDatabases.find((db) => db.$id === options.targetDb.$id);
758
+ if (!fromDb || !targetDb) {
759
+ MessageFormatter.error("Source or target database not found", undefined, { prefix: "Controller" });
760
+ return;
761
+ }
762
+ if (options.isRemote && targetClient) {
763
+ await transferDatabaseLocalToRemote(sourceClient, options.transferEndpoint, options.transferProject, options.transferKey, fromDb.$id, targetDb.$id);
764
+ }
765
+ else {
766
+ await transferDatabaseLocalToLocal(sourceClient, fromDb.$id, targetDb.$id);
767
+ }
768
+ }
769
+ if (options.transferUsers) {
770
+ if (!options.isRemote) {
771
+ MessageFormatter.warning("User transfer is only supported for remote transfers. Skipping...", { prefix: "Controller" });
772
+ }
773
+ else if (!this.appwriteServer) {
774
+ MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
775
+ return;
776
+ }
777
+ else {
778
+ MessageFormatter.progress("Starting user transfer...", { prefix: "Transfer" });
779
+ const localUsers = new Users(this.appwriteServer);
780
+ await transferUsersLocalToRemote(localUsers, options.transferEndpoint, options.transferProject, options.transferKey);
781
+ MessageFormatter.success("User transfer completed", { prefix: "Transfer" });
782
+ }
783
+ }
784
+ // Handle storage transfer
785
+ if (this.storage && (options.sourceBucket || options.fromDb)) {
786
+ const sourceBucketId = options.sourceBucket?.$id ||
787
+ (options.fromDb &&
788
+ this.config?.documentBucketId &&
789
+ `${this.config.documentBucketId}_${options.fromDb.$id
790
+ .toLowerCase()
791
+ .trim()
792
+ .replace(/\s+/g, "")}`);
793
+ const targetBucketId = options.targetBucket?.$id ||
794
+ (options.targetDb &&
795
+ this.config?.documentBucketId &&
796
+ `${this.config.documentBucketId}_${options.targetDb.$id
797
+ .toLowerCase()
798
+ .trim()
799
+ .replace(/\s+/g, "")}`);
800
+ if (sourceBucketId && targetBucketId) {
801
+ MessageFormatter.progress(`Starting storage transfer from ${sourceBucketId} to ${targetBucketId}`, { prefix: "Transfer" });
802
+ if (options.isRemote) {
803
+ await transferStorageLocalToRemote(this.storage, options.transferEndpoint, options.transferProject, options.transferKey, sourceBucketId, targetBucketId);
804
+ }
805
+ else {
806
+ await transferStorageLocalToLocal(this.storage, sourceBucketId, targetBucketId);
807
+ }
808
+ }
809
+ }
810
+ MessageFormatter.success("Transfer completed", { prefix: "Transfer" });
811
+ }
812
+ async updateFunctionSpecifications(functionId, specification) {
813
+ await this.init();
814
+ if (!this.appwriteServer)
815
+ throw new Error("Appwrite server not initialized");
816
+ MessageFormatter.progress(`Updating function specifications for ${functionId} to ${specification}`, { prefix: "Functions" });
817
+ await updateFunctionSpecifications(this.appwriteServer, functionId, specification);
818
+ MessageFormatter.success(`Successfully updated function specifications for ${functionId} to ${specification}`, { prefix: "Functions" });
819
+ }
820
+ /**
821
+ * Validates the current configuration for collections/tables conflicts
822
+ */
823
+ async validateConfiguration(strictMode = false) {
824
+ await this.init();
825
+ if (!this.config) {
826
+ throw new Error("Configuration not loaded");
827
+ }
828
+ MessageFormatter.progress("Validating configuration...", { prefix: "Validation" });
829
+ const validation = strictMode
830
+ ? validateWithStrictMode(this.config, strictMode)
831
+ : validateCollectionsTablesConfig(this.config);
832
+ reportValidationResults(validation, { verbose: true });
833
+ if (validation.isValid) {
834
+ MessageFormatter.success("Configuration validation passed", { prefix: "Validation" });
835
+ }
836
+ else {
837
+ MessageFormatter.error(`Configuration validation failed with ${validation.errors.length} errors`, undefined, { prefix: "Validation" });
838
+ }
839
+ return validation;
840
+ }
841
+ /**
842
+ * Get current session information for debugging/logging purposes
843
+ * Delegates to ConfigManager for session info
844
+ */
845
+ async getSessionInfo() {
846
+ const configManager = ConfigManager.getInstance();
847
+ try {
848
+ const authStatus = await configManager.getAuthStatus();
849
+ return {
850
+ hasSession: authStatus.hasValidSession,
851
+ authMethod: authStatus.authMethod,
852
+ email: authStatus.sessionInfo?.email,
853
+ expiresAt: authStatus.sessionInfo?.expiresAt
854
+ };
855
+ }
856
+ catch (error) {
857
+ // If config not loaded, return empty status
858
+ return {
859
+ hasSession: false
860
+ };
861
+ }
862
+ }
863
+ }