@njdamstra/appwrite-utils-cli 1.8.9 → 1.10.1

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 (285) 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 +85 -35
  25. package/dist/collections/indexes.js +2 -4
  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 +90 -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 +64 -10
  44. package/dist/main.js +130 -177
  45. package/dist/migrations/afterImportActions.js +2 -3
  46. package/dist/migrations/appwriteToX.d.ts +97 -1
  47. package/dist/migrations/appwriteToX.js +9 -7
  48. package/dist/migrations/comprehensiveTransfer.js +3 -5
  49. package/dist/migrations/dataLoader.d.ts +194 -2
  50. package/dist/migrations/dataLoader.js +2 -5
  51. package/dist/migrations/importController.js +3 -4
  52. package/dist/migrations/importDataActions.js +3 -3
  53. package/dist/migrations/relationships.js +1 -2
  54. package/dist/migrations/services/DataTransformationService.js +2 -2
  55. package/dist/migrations/services/FileHandlerService.js +1 -1
  56. package/dist/migrations/services/ImportOrchestrator.js +4 -4
  57. package/dist/migrations/services/RateLimitManager.js +1 -1
  58. package/dist/migrations/services/RelationshipResolver.js +1 -1
  59. package/dist/migrations/services/UserMappingService.js +1 -1
  60. package/dist/migrations/services/ValidationService.js +1 -1
  61. package/dist/migrations/transfer.d.ts +8 -4
  62. package/dist/migrations/transfer.js +106 -55
  63. package/dist/migrations/yaml/YamlImportConfigLoader.js +1 -1
  64. package/dist/migrations/yaml/YamlImportIntegration.js +2 -2
  65. package/dist/migrations/yaml/generateImportSchemas.js +1 -1
  66. package/dist/setupCommands.d.ts +1 -1
  67. package/dist/setupCommands.js +5 -6
  68. package/dist/setupController.js +1 -1
  69. package/dist/shared/backupTracking.d.ts +1 -1
  70. package/dist/shared/backupTracking.js +2 -2
  71. package/dist/shared/confirmationDialogs.js +1 -1
  72. package/dist/shared/migrationHelpers.d.ts +1 -1
  73. package/dist/shared/migrationHelpers.js +3 -3
  74. package/dist/shared/operationQueue.d.ts +1 -1
  75. package/dist/shared/operationQueue.js +2 -3
  76. package/dist/shared/operationsTable.d.ts +1 -1
  77. package/dist/shared/operationsTable.js +2 -2
  78. package/dist/shared/progressManager.js +1 -1
  79. package/dist/shared/selectionDialogs.js +9 -8
  80. package/dist/storage/methods.js +4 -4
  81. package/dist/storage/schemas.d.ts +386 -2
  82. package/dist/tables/indexManager.d.ts +65 -0
  83. package/dist/tables/indexManager.js +294 -0
  84. package/dist/types.d.ts +2 -2
  85. package/dist/types.js +1 -1
  86. package/dist/users/methods.js +2 -3
  87. package/dist/utils/configMigration.js +1 -1
  88. package/dist/utils/index.d.ts +1 -1
  89. package/dist/utils/index.js +1 -1
  90. package/dist/utils/loadConfigs.d.ts +2 -2
  91. package/dist/utils/loadConfigs.js +6 -7
  92. package/dist/utils/setupFiles.js +5 -7
  93. package/dist/utilsController.d.ts +15 -8
  94. package/dist/utilsController.js +57 -28
  95. package/package.json +8 -4
  96. package/src/adapters/index.ts +8 -34
  97. package/src/backups/operations/bucketBackup.ts +2 -2
  98. package/src/backups/operations/collectionBackup.ts +4 -4
  99. package/src/backups/operations/comprehensiveBackup.ts +3 -3
  100. package/src/backups/tracking/centralizedTracking.ts +3 -3
  101. package/src/cli/commands/configCommands.ts +72 -8
  102. package/src/cli/commands/databaseCommands.ts +161 -9
  103. package/src/cli/commands/functionCommands.ts +4 -3
  104. package/src/cli/commands/importFileCommands.ts +815 -0
  105. package/src/cli/commands/schemaCommands.ts +3 -3
  106. package/src/cli/commands/storageCommands.ts +2 -3
  107. package/src/cli/commands/transferCommands.ts +3 -6
  108. package/src/collections/attributes.ts +155 -39
  109. package/src/collections/indexes.ts +5 -7
  110. package/src/collections/methods.ts +115 -150
  111. package/src/collections/tableOperations.ts +92 -21
  112. package/src/collections/transferOperations.ts +4 -5
  113. package/src/collections/wipeOperations.ts +154 -51
  114. package/src/databases/methods.ts +2 -2
  115. package/src/databases/setup.ts +2 -2
  116. package/src/examples/yamlTerminologyExample.ts +2 -2
  117. package/src/functions/deployments.ts +6 -5
  118. package/src/functions/fnConfigDiscovery.ts +2 -2
  119. package/src/functions/methods.ts +19 -6
  120. package/src/init.ts +1 -1
  121. package/src/interactiveCLI.ts +78 -13
  122. package/src/main.ts +143 -287
  123. package/src/migrations/afterImportActions.ts +2 -3
  124. package/src/migrations/appwriteToX.ts +12 -8
  125. package/src/migrations/comprehensiveTransfer.ts +6 -6
  126. package/src/migrations/dataLoader.ts +2 -5
  127. package/src/migrations/importController.ts +3 -4
  128. package/src/migrations/importDataActions.ts +3 -3
  129. package/src/migrations/relationships.ts +1 -2
  130. package/src/migrations/services/DataTransformationService.ts +2 -2
  131. package/src/migrations/services/FileHandlerService.ts +1 -1
  132. package/src/migrations/services/ImportOrchestrator.ts +4 -4
  133. package/src/migrations/services/RateLimitManager.ts +1 -1
  134. package/src/migrations/services/RelationshipResolver.ts +1 -1
  135. package/src/migrations/services/UserMappingService.ts +1 -1
  136. package/src/migrations/services/ValidationService.ts +1 -1
  137. package/src/migrations/transfer.ts +126 -83
  138. package/src/migrations/yaml/YamlImportConfigLoader.ts +1 -1
  139. package/src/migrations/yaml/YamlImportIntegration.ts +2 -2
  140. package/src/migrations/yaml/generateImportSchemas.ts +1 -1
  141. package/src/setupCommands.ts +5 -6
  142. package/src/setupController.ts +1 -1
  143. package/src/shared/backupTracking.ts +3 -3
  144. package/src/shared/confirmationDialogs.ts +1 -1
  145. package/src/shared/migrationHelpers.ts +4 -4
  146. package/src/shared/operationQueue.ts +3 -4
  147. package/src/shared/operationsTable.ts +3 -3
  148. package/src/shared/progressManager.ts +1 -1
  149. package/src/shared/selectionDialogs.ts +9 -8
  150. package/src/storage/methods.ts +4 -4
  151. package/src/tables/indexManager.ts +409 -0
  152. package/src/types.ts +2 -2
  153. package/src/users/methods.ts +2 -3
  154. package/src/utils/configMigration.ts +1 -1
  155. package/src/utils/index.ts +1 -1
  156. package/src/utils/loadConfigs.ts +15 -7
  157. package/src/utils/setupFiles.ts +5 -7
  158. package/src/utilsController.ts +86 -32
  159. package/dist/adapters/AdapterFactory.d.ts +0 -94
  160. package/dist/adapters/AdapterFactory.js +0 -405
  161. package/dist/adapters/DatabaseAdapter.d.ts +0 -233
  162. package/dist/adapters/DatabaseAdapter.js +0 -50
  163. package/dist/adapters/LegacyAdapter.d.ts +0 -50
  164. package/dist/adapters/LegacyAdapter.js +0 -612
  165. package/dist/adapters/TablesDBAdapter.d.ts +0 -45
  166. package/dist/adapters/TablesDBAdapter.js +0 -571
  167. package/dist/config/ConfigManager.d.ts +0 -445
  168. package/dist/config/ConfigManager.js +0 -625
  169. package/dist/config/configMigration.d.ts +0 -87
  170. package/dist/config/configMigration.js +0 -390
  171. package/dist/config/configValidation.d.ts +0 -66
  172. package/dist/config/configValidation.js +0 -358
  173. package/dist/config/index.d.ts +0 -8
  174. package/dist/config/index.js +0 -7
  175. package/dist/config/services/ConfigDiscoveryService.d.ts +0 -126
  176. package/dist/config/services/ConfigDiscoveryService.js +0 -374
  177. package/dist/config/services/ConfigLoaderService.d.ts +0 -129
  178. package/dist/config/services/ConfigLoaderService.js +0 -540
  179. package/dist/config/services/ConfigMergeService.d.ts +0 -208
  180. package/dist/config/services/ConfigMergeService.js +0 -308
  181. package/dist/config/services/ConfigValidationService.d.ts +0 -214
  182. package/dist/config/services/ConfigValidationService.js +0 -310
  183. package/dist/config/services/SessionAuthService.d.ts +0 -225
  184. package/dist/config/services/SessionAuthService.js +0 -456
  185. package/dist/config/services/__tests__/ConfigMergeService.test.d.ts +0 -1
  186. package/dist/config/services/__tests__/ConfigMergeService.test.js +0 -271
  187. package/dist/config/services/index.d.ts +0 -13
  188. package/dist/config/services/index.js +0 -10
  189. package/dist/config/yamlConfig.d.ts +0 -722
  190. package/dist/config/yamlConfig.js +0 -702
  191. package/dist/functions/pathResolution.d.ts +0 -37
  192. package/dist/functions/pathResolution.js +0 -185
  193. package/dist/shared/attributeMapper.d.ts +0 -20
  194. package/dist/shared/attributeMapper.js +0 -203
  195. package/dist/shared/errorUtils.d.ts +0 -54
  196. package/dist/shared/errorUtils.js +0 -95
  197. package/dist/shared/functionManager.d.ts +0 -48
  198. package/dist/shared/functionManager.js +0 -336
  199. package/dist/shared/indexManager.d.ts +0 -24
  200. package/dist/shared/indexManager.js +0 -151
  201. package/dist/shared/jsonSchemaGenerator.d.ts +0 -50
  202. package/dist/shared/jsonSchemaGenerator.js +0 -290
  203. package/dist/shared/logging.d.ts +0 -61
  204. package/dist/shared/logging.js +0 -116
  205. package/dist/shared/messageFormatter.d.ts +0 -39
  206. package/dist/shared/messageFormatter.js +0 -162
  207. package/dist/shared/pydanticModelGenerator.d.ts +0 -17
  208. package/dist/shared/pydanticModelGenerator.js +0 -615
  209. package/dist/shared/schemaGenerator.d.ts +0 -40
  210. package/dist/shared/schemaGenerator.js +0 -556
  211. package/dist/utils/ClientFactory.d.ts +0 -87
  212. package/dist/utils/ClientFactory.js +0 -212
  213. package/dist/utils/configDiscovery.d.ts +0 -78
  214. package/dist/utils/configDiscovery.js +0 -472
  215. package/dist/utils/constantsGenerator.d.ts +0 -31
  216. package/dist/utils/constantsGenerator.js +0 -321
  217. package/dist/utils/dataConverters.d.ts +0 -46
  218. package/dist/utils/dataConverters.js +0 -139
  219. package/dist/utils/directoryUtils.d.ts +0 -22
  220. package/dist/utils/directoryUtils.js +0 -59
  221. package/dist/utils/getClientFromConfig.d.ts +0 -39
  222. package/dist/utils/getClientFromConfig.js +0 -199
  223. package/dist/utils/helperFunctions.d.ts +0 -63
  224. package/dist/utils/helperFunctions.js +0 -156
  225. package/dist/utils/pathResolvers.d.ts +0 -53
  226. package/dist/utils/pathResolvers.js +0 -72
  227. package/dist/utils/projectConfig.d.ts +0 -119
  228. package/dist/utils/projectConfig.js +0 -171
  229. package/dist/utils/retryFailedPromises.d.ts +0 -2
  230. package/dist/utils/retryFailedPromises.js +0 -23
  231. package/dist/utils/sessionAuth.d.ts +0 -48
  232. package/dist/utils/sessionAuth.js +0 -164
  233. package/dist/utils/typeGuards.d.ts +0 -35
  234. package/dist/utils/typeGuards.js +0 -57
  235. package/dist/utils/validationRules.d.ts +0 -43
  236. package/dist/utils/validationRules.js +0 -42
  237. package/dist/utils/versionDetection.d.ts +0 -58
  238. package/dist/utils/versionDetection.js +0 -251
  239. package/dist/utils/yamlConverter.d.ts +0 -100
  240. package/dist/utils/yamlConverter.js +0 -428
  241. package/dist/utils/yamlLoader.d.ts +0 -70
  242. package/dist/utils/yamlLoader.js +0 -267
  243. package/src/adapters/AdapterFactory.ts +0 -510
  244. package/src/adapters/DatabaseAdapter.ts +0 -306
  245. package/src/adapters/LegacyAdapter.ts +0 -841
  246. package/src/adapters/TablesDBAdapter.ts +0 -773
  247. package/src/config/ConfigManager.ts +0 -808
  248. package/src/config/README.md +0 -274
  249. package/src/config/configMigration.ts +0 -575
  250. package/src/config/configValidation.ts +0 -445
  251. package/src/config/index.ts +0 -10
  252. package/src/config/services/ConfigDiscoveryService.ts +0 -463
  253. package/src/config/services/ConfigLoaderService.ts +0 -740
  254. package/src/config/services/ConfigMergeService.ts +0 -388
  255. package/src/config/services/ConfigValidationService.ts +0 -394
  256. package/src/config/services/SessionAuthService.ts +0 -565
  257. package/src/config/services/__tests__/ConfigMergeService.test.ts +0 -351
  258. package/src/config/services/index.ts +0 -29
  259. package/src/config/yamlConfig.ts +0 -761
  260. package/src/functions/pathResolution.ts +0 -227
  261. package/src/shared/attributeMapper.ts +0 -229
  262. package/src/shared/errorUtils.ts +0 -110
  263. package/src/shared/functionManager.ts +0 -525
  264. package/src/shared/indexManager.ts +0 -254
  265. package/src/shared/jsonSchemaGenerator.ts +0 -383
  266. package/src/shared/logging.ts +0 -149
  267. package/src/shared/messageFormatter.ts +0 -208
  268. package/src/shared/pydanticModelGenerator.ts +0 -618
  269. package/src/shared/schemaGenerator.ts +0 -644
  270. package/src/utils/ClientFactory.ts +0 -240
  271. package/src/utils/configDiscovery.ts +0 -557
  272. package/src/utils/constantsGenerator.ts +0 -369
  273. package/src/utils/dataConverters.ts +0 -159
  274. package/src/utils/directoryUtils.ts +0 -61
  275. package/src/utils/getClientFromConfig.ts +0 -257
  276. package/src/utils/helperFunctions.ts +0 -228
  277. package/src/utils/pathResolvers.ts +0 -81
  278. package/src/utils/projectConfig.ts +0 -299
  279. package/src/utils/retryFailedPromises.ts +0 -29
  280. package/src/utils/sessionAuth.ts +0 -230
  281. package/src/utils/typeGuards.ts +0 -65
  282. package/src/utils/validationRules.ts +0 -88
  283. package/src/utils/versionDetection.ts +0 -292
  284. package/src/utils/yamlConverter.ts +0 -542
  285. package/src/utils/yamlLoader.ts +0 -371
@@ -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
 
@@ -124,7 +137,7 @@ export const createFunction = async (
124
137
  functionConfig.logging,
125
138
  functionConfig.entrypoint,
126
139
  functionConfig.commands,
127
- functionConfig.scopes,
140
+ functionConfig.scopes as any[],
128
141
  functionConfig.installationId,
129
142
  functionConfig.providerRepositoryId,
130
143
  functionConfig.providerBranch,
@@ -204,7 +217,7 @@ export const updateFunction = async (
204
217
  functionConfig.logging,
205
218
  functionConfig.entrypoint,
206
219
  functionConfig.commands,
207
- functionConfig.scopes,
220
+ functionConfig.scopes as any[],
208
221
  functionConfig.installationId,
209
222
  functionConfig.providerRepositoryId,
210
223
  functionConfig.providerBranch,
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,26 +67,35 @@ 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(
85
94
  "Appwrite Utils CLI",
86
- "Welcome to Appwrite Utils CLI Tool by Zach Handley"
95
+ "Welcome to Appwrite Utils CLI Tool"
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
 
@@ -1007,7 +1069,7 @@ export class InteractiveCLI {
1007
1069
  compression: bucketCompressionType as Compression,
1008
1070
  encryption: bucketEncryption,
1009
1071
  antivirus: bucketAntivirus,
1010
- },
1072
+ } as any,
1011
1073
  bucketId.length > 0 ? bucketId : ulid()
1012
1074
  );
1013
1075
  }
@@ -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(),
@@ -1046,7 +1111,7 @@ export class InteractiveCLI {
1046
1111
  name: db.name,
1047
1112
  enabled: true,
1048
1113
  type: "tablesdb" as DatabaseType,
1049
- }));
1114
+ } as Models.Database));
1050
1115
  }
1051
1116
 
1052
1117