@njdamstra/appwrite-utils-cli 1.8.9 → 1.10.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 (284) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/CONFIG_TODO.md +1189 -0
  3. package/SELECTION_DIALOGS.md +146 -0
  4. package/SERVICE_IMPLEMENTATION_REPORT.md +462 -0
  5. package/dist/adapters/index.d.ts +7 -8
  6. package/dist/adapters/index.js +7 -9
  7. package/dist/backups/operations/bucketBackup.js +2 -2
  8. package/dist/backups/operations/collectionBackup.d.ts +1 -1
  9. package/dist/backups/operations/collectionBackup.js +3 -3
  10. package/dist/backups/operations/comprehensiveBackup.d.ts +1 -1
  11. package/dist/backups/operations/comprehensiveBackup.js +2 -2
  12. package/dist/backups/tracking/centralizedTracking.d.ts +1 -1
  13. package/dist/backups/tracking/centralizedTracking.js +2 -2
  14. package/dist/cli/commands/configCommands.js +51 -7
  15. package/dist/cli/commands/databaseCommands.d.ts +1 -0
  16. package/dist/cli/commands/databaseCommands.js +119 -9
  17. package/dist/cli/commands/functionCommands.js +3 -3
  18. package/dist/cli/commands/importFileCommands.d.ts +7 -0
  19. package/dist/cli/commands/importFileCommands.js +674 -0
  20. package/dist/cli/commands/schemaCommands.js +3 -3
  21. package/dist/cli/commands/storageCommands.js +2 -3
  22. package/dist/cli/commands/transferCommands.js +3 -5
  23. package/dist/collections/attributes.d.ts +1 -1
  24. package/dist/collections/attributes.js +2 -35
  25. package/dist/collections/indexes.js +1 -3
  26. package/dist/collections/methods.d.ts +1 -1
  27. package/dist/collections/methods.js +111 -192
  28. package/dist/collections/tableOperations.d.ts +1 -0
  29. package/dist/collections/tableOperations.js +55 -23
  30. package/dist/collections/transferOperations.d.ts +1 -1
  31. package/dist/collections/transferOperations.js +3 -4
  32. package/dist/collections/wipeOperations.d.ts +4 -3
  33. package/dist/collections/wipeOperations.js +112 -39
  34. package/dist/databases/methods.js +2 -2
  35. package/dist/databases/setup.js +2 -2
  36. package/dist/examples/yamlTerminologyExample.js +2 -2
  37. package/dist/functions/deployments.d.ts +1 -1
  38. package/dist/functions/deployments.js +5 -5
  39. package/dist/functions/fnConfigDiscovery.js +2 -2
  40. package/dist/functions/methods.js +16 -4
  41. package/dist/init.js +1 -1
  42. package/dist/interactiveCLI.d.ts +6 -1
  43. package/dist/interactiveCLI.js +63 -9
  44. package/dist/main.js +130 -177
  45. package/dist/migrations/afterImportActions.js +2 -3
  46. package/dist/migrations/appwriteToX.d.ts +1 -1
  47. package/dist/migrations/appwriteToX.js +9 -7
  48. package/dist/migrations/comprehensiveTransfer.js +3 -5
  49. package/dist/migrations/dataLoader.js +2 -5
  50. package/dist/migrations/importController.js +3 -4
  51. package/dist/migrations/importDataActions.js +3 -3
  52. package/dist/migrations/relationships.js +1 -2
  53. package/dist/migrations/services/DataTransformationService.js +2 -2
  54. package/dist/migrations/services/FileHandlerService.js +1 -1
  55. package/dist/migrations/services/ImportOrchestrator.js +4 -4
  56. package/dist/migrations/services/RateLimitManager.js +1 -1
  57. package/dist/migrations/services/RelationshipResolver.js +1 -1
  58. package/dist/migrations/services/UserMappingService.js +1 -1
  59. package/dist/migrations/services/ValidationService.js +1 -1
  60. package/dist/migrations/transfer.d.ts +8 -4
  61. package/dist/migrations/transfer.js +106 -55
  62. package/dist/migrations/yaml/YamlImportConfigLoader.js +1 -1
  63. package/dist/migrations/yaml/YamlImportIntegration.js +2 -2
  64. package/dist/migrations/yaml/generateImportSchemas.js +1 -1
  65. package/dist/setupCommands.d.ts +1 -1
  66. package/dist/setupCommands.js +5 -6
  67. package/dist/setupController.js +1 -1
  68. package/dist/shared/backupTracking.d.ts +1 -1
  69. package/dist/shared/backupTracking.js +2 -2
  70. package/dist/shared/confirmationDialogs.js +1 -1
  71. package/dist/shared/migrationHelpers.d.ts +1 -1
  72. package/dist/shared/migrationHelpers.js +3 -3
  73. package/dist/shared/operationQueue.d.ts +1 -1
  74. package/dist/shared/operationQueue.js +2 -3
  75. package/dist/shared/operationsTable.d.ts +1 -1
  76. package/dist/shared/operationsTable.js +2 -2
  77. package/dist/shared/progressManager.js +1 -1
  78. package/dist/shared/selectionDialogs.js +9 -8
  79. package/dist/storage/methods.js +4 -4
  80. package/dist/storage/schemas.d.ts +2 -2
  81. package/dist/tables/indexManager.d.ts +65 -0
  82. package/dist/tables/indexManager.js +294 -0
  83. package/dist/types.d.ts +2 -2
  84. package/dist/types.js +1 -1
  85. package/dist/users/methods.js +2 -3
  86. package/dist/utils/configMigration.js +1 -1
  87. package/dist/utils/index.d.ts +1 -1
  88. package/dist/utils/index.js +1 -1
  89. package/dist/utils/loadConfigs.d.ts +2 -2
  90. package/dist/utils/loadConfigs.js +6 -7
  91. package/dist/utils/setupFiles.js +5 -7
  92. package/dist/utilsController.d.ts +15 -8
  93. package/dist/utilsController.js +57 -28
  94. package/package.json +7 -3
  95. package/src/adapters/index.ts +8 -34
  96. package/src/backups/operations/bucketBackup.ts +2 -2
  97. package/src/backups/operations/collectionBackup.ts +4 -4
  98. package/src/backups/operations/comprehensiveBackup.ts +3 -3
  99. package/src/backups/tracking/centralizedTracking.ts +3 -3
  100. package/src/cli/commands/configCommands.ts +72 -8
  101. package/src/cli/commands/databaseCommands.ts +161 -9
  102. package/src/cli/commands/functionCommands.ts +4 -3
  103. package/src/cli/commands/importFileCommands.ts +815 -0
  104. package/src/cli/commands/schemaCommands.ts +3 -3
  105. package/src/cli/commands/storageCommands.ts +2 -3
  106. package/src/cli/commands/transferCommands.ts +3 -6
  107. package/src/collections/attributes.ts +3 -39
  108. package/src/collections/indexes.ts +2 -4
  109. package/src/collections/methods.ts +115 -150
  110. package/src/collections/tableOperations.ts +57 -21
  111. package/src/collections/transferOperations.ts +4 -5
  112. package/src/collections/wipeOperations.ts +154 -51
  113. package/src/databases/methods.ts +2 -2
  114. package/src/databases/setup.ts +2 -2
  115. package/src/examples/yamlTerminologyExample.ts +2 -2
  116. package/src/functions/deployments.ts +6 -5
  117. package/src/functions/fnConfigDiscovery.ts +2 -2
  118. package/src/functions/methods.ts +17 -4
  119. package/src/init.ts +1 -1
  120. package/src/interactiveCLI.ts +75 -10
  121. package/src/main.ts +143 -287
  122. package/src/migrations/afterImportActions.ts +2 -3
  123. package/src/migrations/appwriteToX.ts +12 -8
  124. package/src/migrations/comprehensiveTransfer.ts +6 -6
  125. package/src/migrations/dataLoader.ts +2 -5
  126. package/src/migrations/importController.ts +3 -4
  127. package/src/migrations/importDataActions.ts +3 -3
  128. package/src/migrations/relationships.ts +1 -2
  129. package/src/migrations/services/DataTransformationService.ts +2 -2
  130. package/src/migrations/services/FileHandlerService.ts +1 -1
  131. package/src/migrations/services/ImportOrchestrator.ts +4 -4
  132. package/src/migrations/services/RateLimitManager.ts +1 -1
  133. package/src/migrations/services/RelationshipResolver.ts +1 -1
  134. package/src/migrations/services/UserMappingService.ts +1 -1
  135. package/src/migrations/services/ValidationService.ts +1 -1
  136. package/src/migrations/transfer.ts +126 -83
  137. package/src/migrations/yaml/YamlImportConfigLoader.ts +1 -1
  138. package/src/migrations/yaml/YamlImportIntegration.ts +2 -2
  139. package/src/migrations/yaml/generateImportSchemas.ts +1 -1
  140. package/src/setupCommands.ts +5 -6
  141. package/src/setupController.ts +1 -1
  142. package/src/shared/backupTracking.ts +3 -3
  143. package/src/shared/confirmationDialogs.ts +1 -1
  144. package/src/shared/migrationHelpers.ts +4 -4
  145. package/src/shared/operationQueue.ts +3 -4
  146. package/src/shared/operationsTable.ts +3 -3
  147. package/src/shared/progressManager.ts +1 -1
  148. package/src/shared/selectionDialogs.ts +9 -8
  149. package/src/storage/methods.ts +4 -4
  150. package/src/tables/indexManager.ts +409 -0
  151. package/src/types.ts +2 -2
  152. package/src/users/methods.ts +2 -3
  153. package/src/utils/configMigration.ts +1 -1
  154. package/src/utils/index.ts +1 -1
  155. package/src/utils/loadConfigs.ts +15 -7
  156. package/src/utils/setupFiles.ts +5 -7
  157. package/src/utilsController.ts +86 -32
  158. package/dist/adapters/AdapterFactory.d.ts +0 -94
  159. package/dist/adapters/AdapterFactory.js +0 -405
  160. package/dist/adapters/DatabaseAdapter.d.ts +0 -233
  161. package/dist/adapters/DatabaseAdapter.js +0 -50
  162. package/dist/adapters/LegacyAdapter.d.ts +0 -50
  163. package/dist/adapters/LegacyAdapter.js +0 -612
  164. package/dist/adapters/TablesDBAdapter.d.ts +0 -45
  165. package/dist/adapters/TablesDBAdapter.js +0 -571
  166. package/dist/config/ConfigManager.d.ts +0 -445
  167. package/dist/config/ConfigManager.js +0 -625
  168. package/dist/config/configMigration.d.ts +0 -87
  169. package/dist/config/configMigration.js +0 -390
  170. package/dist/config/configValidation.d.ts +0 -66
  171. package/dist/config/configValidation.js +0 -358
  172. package/dist/config/index.d.ts +0 -8
  173. package/dist/config/index.js +0 -7
  174. package/dist/config/services/ConfigDiscoveryService.d.ts +0 -126
  175. package/dist/config/services/ConfigDiscoveryService.js +0 -374
  176. package/dist/config/services/ConfigLoaderService.d.ts +0 -129
  177. package/dist/config/services/ConfigLoaderService.js +0 -540
  178. package/dist/config/services/ConfigMergeService.d.ts +0 -208
  179. package/dist/config/services/ConfigMergeService.js +0 -308
  180. package/dist/config/services/ConfigValidationService.d.ts +0 -214
  181. package/dist/config/services/ConfigValidationService.js +0 -310
  182. package/dist/config/services/SessionAuthService.d.ts +0 -225
  183. package/dist/config/services/SessionAuthService.js +0 -456
  184. package/dist/config/services/__tests__/ConfigMergeService.test.d.ts +0 -1
  185. package/dist/config/services/__tests__/ConfigMergeService.test.js +0 -271
  186. package/dist/config/services/index.d.ts +0 -13
  187. package/dist/config/services/index.js +0 -10
  188. package/dist/config/yamlConfig.d.ts +0 -722
  189. package/dist/config/yamlConfig.js +0 -702
  190. package/dist/functions/pathResolution.d.ts +0 -37
  191. package/dist/functions/pathResolution.js +0 -185
  192. package/dist/shared/attributeMapper.d.ts +0 -20
  193. package/dist/shared/attributeMapper.js +0 -203
  194. package/dist/shared/errorUtils.d.ts +0 -54
  195. package/dist/shared/errorUtils.js +0 -95
  196. package/dist/shared/functionManager.d.ts +0 -48
  197. package/dist/shared/functionManager.js +0 -336
  198. package/dist/shared/indexManager.d.ts +0 -24
  199. package/dist/shared/indexManager.js +0 -151
  200. package/dist/shared/jsonSchemaGenerator.d.ts +0 -50
  201. package/dist/shared/jsonSchemaGenerator.js +0 -290
  202. package/dist/shared/logging.d.ts +0 -61
  203. package/dist/shared/logging.js +0 -116
  204. package/dist/shared/messageFormatter.d.ts +0 -39
  205. package/dist/shared/messageFormatter.js +0 -162
  206. package/dist/shared/pydanticModelGenerator.d.ts +0 -17
  207. package/dist/shared/pydanticModelGenerator.js +0 -615
  208. package/dist/shared/schemaGenerator.d.ts +0 -40
  209. package/dist/shared/schemaGenerator.js +0 -556
  210. package/dist/utils/ClientFactory.d.ts +0 -87
  211. package/dist/utils/ClientFactory.js +0 -212
  212. package/dist/utils/configDiscovery.d.ts +0 -78
  213. package/dist/utils/configDiscovery.js +0 -472
  214. package/dist/utils/constantsGenerator.d.ts +0 -31
  215. package/dist/utils/constantsGenerator.js +0 -321
  216. package/dist/utils/dataConverters.d.ts +0 -46
  217. package/dist/utils/dataConverters.js +0 -139
  218. package/dist/utils/directoryUtils.d.ts +0 -22
  219. package/dist/utils/directoryUtils.js +0 -59
  220. package/dist/utils/getClientFromConfig.d.ts +0 -39
  221. package/dist/utils/getClientFromConfig.js +0 -199
  222. package/dist/utils/helperFunctions.d.ts +0 -63
  223. package/dist/utils/helperFunctions.js +0 -156
  224. package/dist/utils/pathResolvers.d.ts +0 -53
  225. package/dist/utils/pathResolvers.js +0 -72
  226. package/dist/utils/projectConfig.d.ts +0 -119
  227. package/dist/utils/projectConfig.js +0 -171
  228. package/dist/utils/retryFailedPromises.d.ts +0 -2
  229. package/dist/utils/retryFailedPromises.js +0 -23
  230. package/dist/utils/sessionAuth.d.ts +0 -48
  231. package/dist/utils/sessionAuth.js +0 -164
  232. package/dist/utils/typeGuards.d.ts +0 -35
  233. package/dist/utils/typeGuards.js +0 -57
  234. package/dist/utils/validationRules.d.ts +0 -43
  235. package/dist/utils/validationRules.js +0 -42
  236. package/dist/utils/versionDetection.d.ts +0 -58
  237. package/dist/utils/versionDetection.js +0 -251
  238. package/dist/utils/yamlConverter.d.ts +0 -100
  239. package/dist/utils/yamlConverter.js +0 -428
  240. package/dist/utils/yamlLoader.d.ts +0 -70
  241. package/dist/utils/yamlLoader.js +0 -267
  242. package/src/adapters/AdapterFactory.ts +0 -510
  243. package/src/adapters/DatabaseAdapter.ts +0 -306
  244. package/src/adapters/LegacyAdapter.ts +0 -841
  245. package/src/adapters/TablesDBAdapter.ts +0 -773
  246. package/src/config/ConfigManager.ts +0 -808
  247. package/src/config/README.md +0 -274
  248. package/src/config/configMigration.ts +0 -575
  249. package/src/config/configValidation.ts +0 -445
  250. package/src/config/index.ts +0 -10
  251. package/src/config/services/ConfigDiscoveryService.ts +0 -463
  252. package/src/config/services/ConfigLoaderService.ts +0 -740
  253. package/src/config/services/ConfigMergeService.ts +0 -388
  254. package/src/config/services/ConfigValidationService.ts +0 -394
  255. package/src/config/services/SessionAuthService.ts +0 -565
  256. package/src/config/services/__tests__/ConfigMergeService.test.ts +0 -351
  257. package/src/config/services/index.ts +0 -29
  258. package/src/config/yamlConfig.ts +0 -761
  259. package/src/functions/pathResolution.ts +0 -227
  260. package/src/shared/attributeMapper.ts +0 -229
  261. package/src/shared/errorUtils.ts +0 -110
  262. package/src/shared/functionManager.ts +0 -525
  263. package/src/shared/indexManager.ts +0 -254
  264. package/src/shared/jsonSchemaGenerator.ts +0 -383
  265. package/src/shared/logging.ts +0 -149
  266. package/src/shared/messageFormatter.ts +0 -208
  267. package/src/shared/pydanticModelGenerator.ts +0 -618
  268. package/src/shared/schemaGenerator.ts +0 -644
  269. package/src/utils/ClientFactory.ts +0 -240
  270. package/src/utils/configDiscovery.ts +0 -557
  271. package/src/utils/constantsGenerator.ts +0 -369
  272. package/src/utils/dataConverters.ts +0 -159
  273. package/src/utils/directoryUtils.ts +0 -61
  274. package/src/utils/getClientFromConfig.ts +0 -257
  275. package/src/utils/helperFunctions.ts +0 -228
  276. package/src/utils/pathResolvers.ts +0 -81
  277. package/src/utils/projectConfig.ts +0 -299
  278. package/src/utils/retryFailedPromises.ts +0 -29
  279. package/src/utils/sessionAuth.ts +0 -230
  280. package/src/utils/typeGuards.ts +0 -65
  281. package/src/utils/validationRules.ts +0 -88
  282. package/src/utils/versionDetection.ts +0 -292
  283. package/src/utils/yamlConverter.ts +0 -542
  284. package/src/utils/yamlLoader.ts +0 -371
@@ -4,12 +4,11 @@ import {
4
4
  ID,
5
5
  Query,
6
6
  } from "node-appwrite";
7
- import { tryAwaitWithRetry, delay, calculateExponentialBackoff } from "../utils/helperFunctions.js";
8
- import { MessageFormatter } from "../shared/messageFormatter.js";
7
+ import { tryAwaitWithRetry, delay, calculateExponentialBackoff, MessageFormatter } from "@njdamstra/appwrite-utils-helpers";
9
8
  import { chunk } from "es-toolkit";
10
- import type { DatabaseAdapter } from "../adapters/DatabaseAdapter.js";
11
- import { isLegacyDatabases } from "../utils/typeGuards.js";
12
- import { getAdapter } from "../utils/getClientFromConfig.js";
9
+ import type { DatabaseAdapter } from "@njdamstra/appwrite-utils-helpers";
10
+ import { isLegacyDatabases } from "@njdamstra/appwrite-utils-helpers";
11
+ import { getAdapter } from "@njdamstra/appwrite-utils-helpers";
13
12
 
14
13
  /**
15
14
  * Transfers all documents from one collection to another in a different database
@@ -3,12 +3,11 @@ import {
3
3
  Query,
4
4
  type Models,
5
5
  } from "node-appwrite";
6
- import type { DatabaseAdapter } from "../adapters/DatabaseAdapter.js";
7
- import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
8
- import { MessageFormatter } from "../shared/messageFormatter.js";
6
+ import type { DatabaseAdapter } from "@njdamstra/appwrite-utils-helpers";
7
+ import { tryAwaitWithRetry } from "@njdamstra/appwrite-utils-helpers";
8
+ import { MessageFormatter, isRetryableError, isCriticalError } from "@njdamstra/appwrite-utils-helpers";
9
9
  import { ProgressManager } from "../shared/progressManager.js";
10
- import { isRetryableError, isCriticalError } from "../shared/errorUtils.js";
11
- import { delay } from "../utils/helperFunctions.js";
10
+ import { delay } from "@njdamstra/appwrite-utils-helpers";
12
11
  import { chunk } from "es-toolkit";
13
12
  import pLimit from "p-limit";
14
13
  import { fetchAllCollections } from "./methods.js";
@@ -239,8 +238,9 @@ export const wipeAllTables = async (
239
238
  };
240
239
 
241
240
  /**
242
- * Optimized deletion of all rows from a table using direct bulk deletion
243
- * Uses Query.limit() to delete rows without fetching IDs first
241
+ * Optimized deletion of all rows from a table.
242
+ * Uses bulk deletion when possible, but falls back to individual row deletion
243
+ * for tables with relationship columns (bulk delete not supported for those).
244
244
  */
245
245
  export const wipeTableRows = async (
246
246
  adapter: DatabaseAdapter,
@@ -248,76 +248,179 @@ export const wipeTableRows = async (
248
248
  tableId: string
249
249
  ): Promise<void> => {
250
250
  try {
251
- // Check if bulk deletion is available
252
- if (!adapter.bulkDeleteRows) {
253
- MessageFormatter.error(
254
- "Bulk deletion not available for this adapter - wipe operation not supported",
255
- new Error("bulkDeleteRows not available"),
256
- { prefix: "Wipe" }
257
- );
258
- throw new Error("Bulk deletion required for wipe operations");
259
- }
251
+ // Check if the table has relationship columns — bulk delete is not supported for those
252
+ const tableInfo = await adapter.getTable({ databaseId, tableId });
253
+ const columns: any[] = (tableInfo.data as any)?.columns || [];
254
+ const hasRelationships = columns.some((col: any) => col.type === "relationship");
260
255
 
261
- const DELETE_BATCH_SIZE = 250; // How many rows to delete per batch
256
+ const DELETE_BATCH_SIZE = 250;
262
257
  let totalDeleted = 0;
263
258
  let hasMoreRows = true;
264
259
 
265
- MessageFormatter.info("Starting optimized table row deletion...", { prefix: "Wipe" });
266
-
267
260
  const progress = ProgressManager.create(
268
261
  `delete-${tableId}`,
269
- 1, // Start with 1, will update as we discover more
262
+ 1,
270
263
  { title: "Deleting table rows" }
271
264
  );
272
265
 
273
- while (hasMoreRows) {
274
- try {
275
- // Delete next batch using Query.limit() - no fetching needed!
276
- const result = await tryAwaitWithRetry(async () =>
277
- adapter.bulkDeleteRows!({
278
- databaseId,
279
- tableId,
280
- rowIds: [], // Empty array signals we want to use Query.limit instead
281
- batchSize: DELETE_BATCH_SIZE
282
- })
266
+ if (hasRelationships) {
267
+ // ── Relationship table: fetch rows then delete individually ──
268
+ MessageFormatter.info(
269
+ "Table has relationship columns — using individual row deletion (bulk delete not supported)",
270
+ { prefix: "Wipe" }
271
+ );
272
+
273
+ const FETCH_BATCH_SIZE = 1000;
274
+ const MAX_CONCURRENT_DELETES = 25;
275
+ const limit = pLimit(MAX_CONCURRENT_DELETES);
276
+
277
+ // Pipeline: prefetch the first batch, then overlap fetch+delete
278
+ let pendingRows: any[] = [];
279
+ let totalDiscovered = 0;
280
+
281
+ // Fetch helper — always fetches from the top since we're deleting everything
282
+ const fetchBatch = async (): Promise<any[]> => {
283
+ const queries: any[] = [Query.limit(FETCH_BATCH_SIZE)];
284
+ const response = await tryAwaitWithRetry(async () =>
285
+ adapter.listRows({ databaseId, tableId, queries })
283
286
  );
287
+ return (response as any).rows || (response as any).data || [];
288
+ };
284
289
 
285
- const deletedCount = (result as any).total || 0;
290
+ // Kick off the first fetch
291
+ let nextFetchPromise: Promise<any[]> | null = fetchBatch();
286
292
 
287
- if (deletedCount === 0) {
293
+ while (hasMoreRows) {
294
+ // Await the prefetched batch
295
+ const rows = nextFetchPromise ? await nextFetchPromise : [];
296
+ nextFetchPromise = null;
297
+
298
+ if (rows.length === 0) {
288
299
  hasMoreRows = false;
289
300
  break;
290
301
  }
291
302
 
292
- totalDeleted += deletedCount;
293
- progress.setTotal(totalDeleted + 100); // Estimate more rows exist
294
- progress.update(totalDeleted);
303
+ totalDiscovered += rows.length;
304
+ const isLastBatch = rows.length < FETCH_BATCH_SIZE;
305
+
306
+ if (!isLastBatch) {
307
+ progress.setTotal(totalDiscovered + 1000);
308
+ } else {
309
+ progress.setTotal(totalDiscovered);
310
+ }
295
311
 
296
312
  MessageFormatter.progress(
297
- `Deleted ${deletedCount} rows (${totalDeleted} total so far)`,
313
+ `Fetched ${rows.length} rows (${totalDiscovered} discovered, ${totalDeleted} deleted so far)`,
298
314
  { prefix: "Wipe" }
299
315
  );
300
316
 
301
- // Small delay between batches to be respectful to the API
302
- await delay(10);
317
+ // Start deleting this batch and prefetch the next one concurrently
318
+ // (only prefetch if we expect more rows)
319
+ if (!isLastBatch) {
320
+ // Wait a moment before prefetching so the first few deletes free up API capacity
321
+ nextFetchPromise = delay(200).then(() => fetchBatch());
322
+ }
323
+
324
+ // Delete each row with concurrency limit
325
+ const deletePromises = rows.map((row: any) =>
326
+ limit(async () => {
327
+ try {
328
+ await tryAwaitWithRetry(async () =>
329
+ adapter.deleteRow({ databaseId, tableId, id: row.$id })
330
+ );
331
+ totalDeleted++;
332
+ progress.update(totalDeleted);
333
+ } catch (error: any) {
334
+ const errorMessage = error.message || String(error);
335
+ if (errorMessage.includes("could not be found")) {
336
+ totalDeleted++;
337
+ progress.update(totalDeleted);
338
+ } else if (isCriticalError(errorMessage)) {
339
+ MessageFormatter.error(
340
+ `Critical error deleting row ${row.$id}: ${errorMessage}`,
341
+ error,
342
+ { prefix: "Wipe" }
343
+ );
344
+ throw error;
345
+ } else {
346
+ MessageFormatter.error(
347
+ `Failed to delete row ${row.$id}: ${errorMessage}`,
348
+ error,
349
+ { prefix: "Wipe" }
350
+ );
351
+ totalDeleted++;
352
+ progress.update(totalDeleted);
353
+ }
354
+ }
355
+ })
356
+ );
303
357
 
304
- } catch (error: any) {
305
- const errorMessage = error.message || String(error);
358
+ await Promise.all(deletePromises);
306
359
 
307
- if (isCriticalError(errorMessage)) {
308
- MessageFormatter.error(
309
- `Critical error during bulk deletion: ${errorMessage}`,
310
- error,
311
- { prefix: "Wipe" }
360
+ if (isLastBatch) {
361
+ hasMoreRows = false;
362
+ }
363
+
364
+ await delay(10);
365
+ }
366
+ } else {
367
+ // ── No relationships: use fast bulk deletion ──
368
+ if (!adapter.bulkDeleteRows) {
369
+ MessageFormatter.error(
370
+ "Bulk deletion not available for this adapter - wipe operation not supported",
371
+ new Error("bulkDeleteRows not available"),
372
+ { prefix: "Wipe" }
373
+ );
374
+ throw new Error("Bulk deletion required for wipe operations");
375
+ }
376
+
377
+ MessageFormatter.info("Starting optimized table row deletion...", { prefix: "Wipe" });
378
+
379
+ while (hasMoreRows) {
380
+ try {
381
+ const result = await tryAwaitWithRetry(async () =>
382
+ adapter.bulkDeleteRows!({
383
+ databaseId,
384
+ tableId,
385
+ rowIds: [],
386
+ batchSize: DELETE_BATCH_SIZE
387
+ })
312
388
  );
313
- throw error;
314
- } else {
315
- MessageFormatter.error(
316
- `Error during deletion batch: ${errorMessage}`,
317
- error,
389
+
390
+ const deletedCount = (result as any).total || 0;
391
+
392
+ if (deletedCount === 0) {
393
+ hasMoreRows = false;
394
+ break;
395
+ }
396
+
397
+ totalDeleted += deletedCount;
398
+ progress.setTotal(totalDeleted + 100);
399
+ progress.update(totalDeleted);
400
+
401
+ MessageFormatter.progress(
402
+ `Deleted ${deletedCount} rows (${totalDeleted} total so far)`,
318
403
  { prefix: "Wipe" }
319
404
  );
320
- // Continue trying with next batch
405
+
406
+ await delay(10);
407
+ } catch (error: any) {
408
+ const errorMessage = error.message || String(error);
409
+
410
+ if (isCriticalError(errorMessage)) {
411
+ MessageFormatter.error(
412
+ `Critical error during bulk deletion: ${errorMessage}`,
413
+ error,
414
+ { prefix: "Wipe" }
415
+ );
416
+ throw error;
417
+ } else {
418
+ MessageFormatter.error(
419
+ `Error during deletion batch: ${errorMessage}`,
420
+ error,
421
+ { prefix: "Wipe" }
422
+ );
423
+ }
321
424
  }
322
425
  }
323
426
  }
@@ -1,7 +1,7 @@
1
1
  import { Databases, Query, type Models } from "node-appwrite";
2
- import { delay, tryAwaitWithRetry } from "../utils/helperFunctions.js";
2
+ import { delay, tryAwaitWithRetry } from "@njdamstra/appwrite-utils-helpers";
3
3
  import { fetchAllCollections } from "../collections/methods.js";
4
- import { MessageFormatter } from "../shared/messageFormatter.js";
4
+ import { MessageFormatter } from "@njdamstra/appwrite-utils-helpers";
5
5
 
6
6
  export const fetchAllDatabases = async (
7
7
  database: Databases
@@ -1,8 +1,8 @@
1
1
  import { Databases, Query, type Models } from "node-appwrite";
2
- import { tryAwaitWithRetry } from "../utils/index.js";
2
+ import { tryAwaitWithRetry } from "@njdamstra/appwrite-utils-helpers";
3
3
  import { type AppwriteConfig } from "@njdamstra/appwrite-utils";
4
4
  import { ulid } from "ulidx";
5
- import { MessageFormatter } from "../shared/messageFormatter.js";
5
+ import { MessageFormatter } from "@njdamstra/appwrite-utils-helpers";
6
6
 
7
7
  export const ensureDatabasesExist = async (config: AppwriteConfig, databasesToEnsure?: Models.Database[]) => {
8
8
  if (!config.appwriteClient) {
@@ -17,8 +17,8 @@ import {
17
17
  usesTableTerminology,
18
18
  type YamlTerminologyConfig,
19
19
  type YamlCollectionData
20
- } from "../utils/yamlConverter.js";
21
- import { createYamlLoader } from "../utils/yamlLoader.js";
20
+ } from "@njdamstra/appwrite-utils-helpers";
21
+ import { createYamlLoader } from "@njdamstra/appwrite-utils-helpers";
22
22
  import { YamlImportIntegration } from "../migrations/yaml/YamlImportIntegration.js";
23
23
  import { createImportSchemas } from "../migrations/yaml/generateImportSchemas.js";
24
24
  import {
@@ -15,8 +15,8 @@ import {
15
15
  updateFunctionSpecifications,
16
16
  } from "./methods.js";
17
17
  import ignore from "ignore";
18
- import { MessageFormatter } from "../shared/messageFormatter.js";
19
- import { resolveFunctionDirectory, validateFunctionDirectory } from './pathResolution.js';
18
+ import { MessageFormatter } from "@njdamstra/appwrite-utils-helpers";
19
+ import { resolveFunctionDirectory, validateFunctionDirectory } from '@njdamstra/appwrite-utils-helpers';
20
20
 
21
21
  export const deployFunction = async (
22
22
  client: Client,
@@ -150,7 +150,8 @@ export const deployLocalFunction = async (
150
150
  client: Client,
151
151
  functionName: string,
152
152
  functionConfig: AppwriteFunction,
153
- functionPath?: string
153
+ functionPath?: string,
154
+ configDirPath?: string
154
155
  ) => {
155
156
  let functionExists = true;
156
157
  let functionThatExists: Models.Function;
@@ -160,10 +161,10 @@ export const deployLocalFunction = async (
160
161
  functionExists = false;
161
162
  }
162
163
 
163
- const configDirPath = process.cwd(); // TODO: This should be passed from caller
164
+ const resolvedConfigDir = configDirPath ?? process.cwd();
164
165
  const resolvedPath = resolveFunctionDirectory(
165
166
  functionName,
166
- configDirPath,
167
+ resolvedConfigDir,
167
168
  functionConfig.dirPath,
168
169
  functionPath
169
170
  );
@@ -3,8 +3,8 @@ import path from 'node:path';
3
3
  import yaml from 'js-yaml';
4
4
  import { homedir } from 'node:os';
5
5
  import { AppwriteFunctionSchema, type AppwriteFunction } from '@njdamstra/appwrite-utils';
6
- import { shouldIgnoreDirectory } from '../utils/directoryUtils.js';
7
- import { MessageFormatter } from '../shared/messageFormatter.js';
6
+ import { shouldIgnoreDirectory } from '@njdamstra/appwrite-utils-helpers';
7
+ import { MessageFormatter } from '@njdamstra/appwrite-utils-helpers';
8
8
 
9
9
  function findGitRoot(startDir: string): string {
10
10
  let dir = path.resolve(startDir);
@@ -13,23 +13,36 @@ import {
13
13
  type FunctionScope,
14
14
  type Specification,
15
15
  type Runtime as AppwriteUtilsRuntime,
16
+ EventTypeSchema,
16
17
  } from "@njdamstra/appwrite-utils";
17
18
  import chalk from "chalk";
18
19
  import { extract as extractTar } from "tar";
19
- import { MessageFormatter } from "../shared/messageFormatter.js";
20
- import { expandTildePath, normalizeFunctionName } from "./pathResolution.js";
20
+ import { MessageFormatter } from "@njdamstra/appwrite-utils-helpers";
21
+ import { expandTildePath, normalizeFunctionName } from '@njdamstra/appwrite-utils-helpers';
21
22
 
22
23
  /**
23
24
  * Validates and filters events array for Appwrite functions
24
25
  * - Filters out empty/invalid strings
26
+ * - Validates against EventTypeSchema
25
27
  * - Limits to 100 items maximum (Appwrite limit)
26
28
  * - Returns empty array if input is invalid
27
29
  */
28
30
  const validateEvents = (events?: string[]): string[] => {
29
31
  if (!events || !Array.isArray(events)) return [];
30
-
32
+
31
33
  return events
32
- .filter(event => event && typeof event === 'string' && event.trim().length > 0)
34
+ .filter(event => {
35
+ if (!event || typeof event !== 'string' || event.trim().length === 0) {
36
+ return false;
37
+ }
38
+ // Validate against EventTypeSchema
39
+ const result = EventTypeSchema.safeParse(event);
40
+ if (!result.success) {
41
+ MessageFormatter.warning(`Invalid event type "${event}" will be filtered out`, { prefix: "Functions" });
42
+ return false;
43
+ }
44
+ return true;
45
+ })
33
46
  .slice(0, 100);
34
47
  };
35
48
 
package/src/init.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import inquirer from "inquirer";
3
3
  import { createEmptyCollection, setupDirsFiles } from "./utils/setupFiles.js";
4
- import { MessageFormatter } from "./shared/messageFormatter.js";
4
+ import { MessageFormatter } from "@njdamstra/appwrite-utils-helpers";
5
5
 
6
6
  MessageFormatter.banner("Appwrite Utils CLI Tool", "For more information, visit https://github.com/njdamstra/AppwriteUtils");
7
7
 
@@ -35,9 +35,8 @@ import { join } from "node:path";
35
35
  import path from "path";
36
36
  import fs from "node:fs";
37
37
  import os from "node:os";
38
- import { MessageFormatter } from "./shared/messageFormatter.js";
38
+ import { MessageFormatter, findYamlConfig } from "@njdamstra/appwrite-utils-helpers";
39
39
  import { findAppwriteConfig } from "./utils/loadConfigs.js";
40
- import { findYamlConfig } from "./config/yamlConfig.js";
41
40
 
42
41
  // Import command modules
43
42
  import { configCommands } from "./cli/commands/configCommands.js";
@@ -46,6 +45,7 @@ import { functionCommands } from "./cli/commands/functionCommands.js";
46
45
  import { storageCommands } from "./cli/commands/storageCommands.js";
47
46
  import { transferCommands } from "./cli/commands/transferCommands.js";
48
47
  import { schemaCommands } from "./cli/commands/schemaCommands.js";
48
+ import { importFileCommands } from "./cli/commands/importFileCommands.js";
49
49
 
50
50
  enum CHOICES {
51
51
  MIGRATE_CONFIG = "🔄 Migrate TypeScript config to YAML (.appwrite structure)",
@@ -67,18 +67,27 @@ enum CHOICES {
67
67
  GENERATE_SCHEMAS = "🏗️ Generate schemas",
68
68
  GENERATE_CONSTANTS = "📋 Generate cross-language constants (TypeScript, Python, PHP, Dart, etc.)",
69
69
  IMPORT_DATA = "📥 Import data",
70
+ IMPORT_FILE = "📄 Import file (CSV/JSON) directly into a table",
70
71
  RELOAD_CONFIG = "🔄 Reload configuration files",
71
72
  UPDATE_FUNCTION_SPEC = "⚙️ Update function specifications",
72
73
  MANAGE_BUCKETS = "🪣 Manage storage buckets",
73
74
  EXIT = "👋 Exit",
74
75
  }
75
76
 
77
+ export interface InteractiveCLIOptions {
78
+ useSession?: boolean;
79
+ sessionCookie?: string;
80
+ }
81
+
76
82
  export class InteractiveCLI {
77
83
  private controller: UtilsController | undefined;
78
84
  private isUsingTypeScriptConfig: boolean = false;
79
85
  private lastSelectedCollectionIds: string[] = [];
86
+ private options: InteractiveCLIOptions;
80
87
 
81
- constructor(private currentDir: string) {}
88
+ constructor(private currentDir: string, options: InteractiveCLIOptions = {}) {
89
+ this.options = options;
90
+ }
82
91
 
83
92
  async run(): Promise<void> {
84
93
  MessageFormatter.banner(
@@ -86,7 +95,7 @@ export class InteractiveCLI {
86
95
  "Welcome to Appwrite Utils CLI Tool by Zach Handley"
87
96
  );
88
97
  MessageFormatter.info(
89
- "For more information, visit https://github.com/zachhandley/AppwriteUtils"
98
+ "For more information, visit https://github.com/njdamstra/AppwriteUtils"
90
99
  );
91
100
 
92
101
  // Detect configuration type
@@ -180,6 +189,10 @@ export class InteractiveCLI {
180
189
  await this.initControllerIfNeeded();
181
190
  await schemaCommands.importData(this);
182
191
  break;
192
+ case CHOICES.IMPORT_FILE:
193
+ await this.initControllerIfNeeded();
194
+ await importFileCommands.importFile(this);
195
+ break;
183
196
  case CHOICES.RELOAD_CONFIG:
184
197
  await configCommands.reloadConfigWithSessionPreservation(this);
185
198
  break;
@@ -204,7 +217,10 @@ export class InteractiveCLI {
204
217
  }): Promise<void> {
205
218
  if (!this.controller) {
206
219
  this.controller = UtilsController.getInstance(this.currentDir, directConfig);
207
- await this.controller.init();
220
+ await this.controller.init({
221
+ useSession: this.options.useSession,
222
+ sessionCookie: this.options.sessionCookie
223
+ });
208
224
  } else {
209
225
  // Extract session info from existing controller before reinitializing
210
226
  const sessionInfo = await this.controller.getSessionInfo();
@@ -219,12 +235,18 @@ export class InteractiveCLI {
219
235
  // Reinitialize with session preservation
220
236
  UtilsController.clearInstance();
221
237
  this.controller = UtilsController.getInstance(this.currentDir, enhancedDirectConfig);
222
- await this.controller.init();
238
+ await this.controller.init({
239
+ useSession: this.options.useSession,
240
+ sessionCookie: this.options.sessionCookie
241
+ });
223
242
  } else if (directConfig) {
224
243
  // Standard reinitialize without session
225
244
  UtilsController.clearInstance();
226
245
  this.controller = UtilsController.getInstance(this.currentDir, directConfig);
227
- await this.controller.init();
246
+ await this.controller.init({
247
+ useSession: this.options.useSession,
248
+ sessionCookie: this.options.sessionCookie
249
+ });
228
250
  }
229
251
  // If no directConfig provided, keep existing controller
230
252
  }
@@ -308,7 +330,8 @@ export class InteractiveCLI {
308
330
  },
309
331
  ]);
310
332
 
311
- return selectedDatabases;
333
+ // "list" type returns a single value, "checkbox" returns an array — normalize to array
334
+ return Array.isArray(selectedDatabases) ? selectedDatabases : [selectedDatabases];
312
335
  }
313
336
 
314
337
  private async selectCollections(
@@ -383,8 +406,14 @@ export class InteractiveCLI {
383
406
  (coll) => !configCollections.some((c) => c.name === coll.name || c.$id === coll.$id)
384
407
  );
385
408
 
409
+ const getCollectionId = (collection: Models.Collection) => collection.$id || collection.name;
410
+ const localCollectionIds = new Set(configCollections.map((c) => c.$id || c.name));
411
+ const localCollections = allCollections.filter((collection) =>
412
+ localCollectionIds.has(getCollectionId(collection))
413
+ );
414
+
386
415
  // Enhanced choice display with type indicators
387
- const choices = allCollections
416
+ const choices: { name: string; value: Models.Collection | string }[] = allCollections
388
417
  .sort((a, b) => {
389
418
  // Sort by type first (collections before tables), then by name
390
419
  const aIsTable = (a as any)._isFromTablesDir || false;
@@ -438,6 +467,20 @@ export class InteractiveCLI {
438
467
  };
439
468
  });
440
469
 
470
+ if (multiSelect && localCollections.length > 1) {
471
+ choices.unshift({
472
+ name: chalk.green.bold(`📋 Select All Local Items (${localCollections.length})`),
473
+ value: "__SELECT_ALL_LOCAL__"
474
+ });
475
+ }
476
+
477
+ if (multiSelect && allCollections.length > 1) {
478
+ choices.unshift({
479
+ name: chalk.green.bold(`📋 Select All Shown (${allCollections.length})`),
480
+ value: "__SELECT_ALL__"
481
+ });
482
+ }
483
+
441
484
  const { selectedCollections } = await inquirer.prompt([
442
485
  {
443
486
  type: multiSelect ? "checkbox" : "list",
@@ -446,9 +489,28 @@ export class InteractiveCLI {
446
489
  choices,
447
490
  loop: true,
448
491
  pageSize: 15, // Increased page size to accommodate additional info
492
+ validate: (input: any[]) => {
493
+ if (!multiSelect) return true;
494
+ if (input.includes("__SELECT_ALL__") && input.length > 1) {
495
+ return "Cannot select 'Select All' with individual items.";
496
+ }
497
+ if (input.includes("__SELECT_ALL_LOCAL__") && input.length > 1) {
498
+ return "Cannot select 'Select All Local' with individual items.";
499
+ }
500
+ return true;
501
+ }
449
502
  },
450
503
  ]);
451
504
 
505
+ if (multiSelect && Array.isArray(selectedCollections)) {
506
+ if (selectedCollections.includes("__SELECT_ALL__")) {
507
+ return allCollections;
508
+ }
509
+ if (selectedCollections.includes("__SELECT_ALL_LOCAL__")) {
510
+ return localCollections;
511
+ }
512
+ }
513
+
452
514
  return selectedCollections;
453
515
  }
454
516
 
@@ -1019,7 +1081,10 @@ export class InteractiveCLI {
1019
1081
  _sourceFolder?: string;
1020
1082
  databaseId?: string;
1021
1083
  })[] {
1022
- const configCollections = this.controller!.config?.collections || [];
1084
+ const configCollections = [
1085
+ ...(this.controller!.config?.collections || []),
1086
+ ...(this.controller!.config?.tables || [])
1087
+ ];
1023
1088
  // @ts-expect-error - appwrite invalid types
1024
1089
  return configCollections.map((c) => ({
1025
1090
  $id: c.$id || ulid(),