@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
@@ -1,4 +1,4 @@
1
- import { mapToCreateAttributeParams, mapToUpdateAttributeParams } from "../shared/attributeMapper.js";
1
+ import { mapToCreateAttributeParams, mapToUpdateAttributeParams } from "@njdamstra/appwrite-utils-helpers";
2
2
  import { Decimal } from "decimal.js";
3
3
  const EXTREME_BOUND = new Decimal('1e12');
4
4
  // Property configuration for different column types
@@ -90,11 +90,13 @@ export function normalizeAttributeToComparable(attr) {
90
90
  return base;
91
91
  }
92
92
  export function normalizeColumnToComparable(col) {
93
- // Detect enum surfaced as string+elements from server and normalize to enum for comparison
93
+ // Detect enum surfaced as string+elements or string+format:enum from server and normalize to enum for comparison
94
94
  let t = String((col?.type ?? col?.columnType ?? '')).toLowerCase();
95
95
  const hasElements = Array.isArray(col?.elements) && col.elements.length > 0;
96
- if (t === 'string' && hasElements)
96
+ const hasEnumFormat = (col?.format === 'enum');
97
+ if (t === 'string' && (hasElements || hasEnumFormat)) {
97
98
  t = 'enum';
99
+ }
98
100
  const base = {
99
101
  key: col?.key,
100
102
  type: t,
@@ -185,18 +187,27 @@ export function isIndexEqualToIndex(a, b) {
185
187
  if (String(a.type).toLowerCase() !== String(b.type).toLowerCase())
186
188
  return false;
187
189
  // Compare attributes as sets (order-insensitive)
188
- const attrsA = Array.isArray(a.attributes) ? [...a.attributes].sort() : [];
189
- const attrsB = Array.isArray(b.attributes) ? [...b.attributes].sort() : [];
190
+ // Support TablesDB which returns 'columns' instead of 'attributes'
191
+ const attrsAraw = Array.isArray(a.attributes)
192
+ ? a.attributes
193
+ : (Array.isArray(a.columns) ? a.columns : []);
194
+ const attrsA = [...attrsAraw].sort();
195
+ const attrsB = Array.isArray(b.attributes)
196
+ ? [...b.attributes].sort()
197
+ : (Array.isArray(b.columns) ? [...b.columns].sort() : []);
190
198
  if (attrsA.length !== attrsB.length)
191
199
  return false;
192
200
  for (let i = 0; i < attrsA.length; i++)
193
201
  if (attrsA[i] !== attrsB[i])
194
202
  return false;
195
- // Orders are only considered if BOTH have orders defined
196
- const hasOrdersA = Array.isArray(a.orders) && a.orders.length > 0;
197
- const hasOrdersB = Array.isArray(b.orders) && b.orders.length > 0;
198
- if (hasOrdersA && hasOrdersB) {
199
- const ordersA = [...a.orders].sort();
203
+ // Orders are only considered if CONFIG (b) has orders defined
204
+ // This prevents false positives when Appwrite returns orders but user didn't specify them
205
+ const hasConfigOrders = Array.isArray(b.orders) && b.orders.length > 0;
206
+ if (hasConfigOrders) {
207
+ // Some APIs may expose 'directions' instead of 'orders'
208
+ const ordersA = Array.isArray(a.orders)
209
+ ? [...a.orders].sort()
210
+ : (Array.isArray(a.directions) ? [...a.directions].sort() : []);
200
211
  const ordersB = [...b.orders].sort();
201
212
  if (ordersA.length !== ordersB.length)
202
213
  return false;
@@ -204,7 +215,6 @@ export function isIndexEqualToIndex(a, b) {
204
215
  if (ordersA[i] !== ordersB[i])
205
216
  return false;
206
217
  }
207
- // If only one side has orders, treat as equal (orders unspecified by user)
208
218
  return true;
209
219
  }
210
220
  /**
@@ -213,6 +223,7 @@ export function isIndexEqualToIndex(a, b) {
213
223
  function compareColumnProperties(oldColumn, newAttribute, columnType) {
214
224
  const changes = [];
215
225
  const t = String(columnType || newAttribute.type || '').toLowerCase();
226
+ const key = newAttribute?.key || 'unknown';
216
227
  const mutableProps = MUTABLE_PROPERTIES[t] || [];
217
228
  const immutableProps = IMMUTABLE_PROPERTIES[t] || [];
218
229
  const getNewVal = (prop) => {
@@ -233,8 +244,9 @@ function compareColumnProperties(oldColumn, newAttribute, columnType) {
233
244
  let newValue = getNewVal(prop);
234
245
  // Special-case: enum elements empty/missing should not trigger updates
235
246
  if (t === 'enum' && prop === 'elements') {
236
- if (!Array.isArray(newValue) || newValue.length === 0)
247
+ if (!Array.isArray(newValue) || newValue.length === 0) {
237
248
  newValue = oldValue;
249
+ }
238
250
  }
239
251
  if (Array.isArray(oldValue) && Array.isArray(newValue)) {
240
252
  if (oldValue.length !== newValue.length || oldValue.some((v, i) => v !== newValue[i])) {
@@ -260,7 +272,8 @@ function compareColumnProperties(oldColumn, newAttribute, columnType) {
260
272
  // Type change requires recreate (normalize string+elements to enum on old side)
261
273
  const oldTypeRaw = String(oldColumn?.type || oldColumn?.columnType || '').toLowerCase();
262
274
  const oldHasElements = Array.isArray(oldColumn?.elements) && oldColumn.elements.length > 0;
263
- const oldType = oldTypeRaw === 'string' && oldHasElements ? 'enum' : oldTypeRaw;
275
+ const oldHasEnumFormat = (oldColumn?.format === 'enum');
276
+ const oldType = oldTypeRaw === 'string' && (oldHasElements || oldHasEnumFormat) ? 'enum' : oldTypeRaw;
264
277
  if (oldType && t && oldType !== t && TYPE_CHANGE_REQUIRES_RECREATE.includes(oldType)) {
265
278
  changes.push({ property: 'type', oldValue: oldType, newValue: t, requiresRecreate: true });
266
279
  }
@@ -299,29 +312,48 @@ function analyzeColumnChanges(oldColumn, newAttribute) {
299
312
  /**
300
313
  * Enhanced version of columns diff with detailed change analysis
301
314
  * Order: desired first, then existing (matches internal usage here)
315
+ * Handles case-insensitive key matches as renames (recreates)
302
316
  */
303
317
  export function diffColumnsDetailed(desiredAttributes, existingColumns) {
318
+ // Exact key lookup (case-sensitive)
304
319
  const byKey = new Map((existingColumns || []).map((col) => [col?.key, col]));
320
+ // Case-insensitive key lookup for detecting renames
321
+ const byKeyLower = new Map((existingColumns || []).map((col) => [col?.key?.toLowerCase(), col]));
305
322
  const toCreate = [];
306
323
  const toUpdate = [];
307
324
  const toRecreate = [];
308
325
  const unchanged = [];
326
+ const handledExistingKeys = new Set(); // Track which existing columns we've handled
309
327
  for (const attr of desiredAttributes || []) {
310
328
  const key = attr?.key;
311
- const existing = key ? byKey.get(key) : undefined;
312
- if (!existing) {
313
- toCreate.push(attr);
329
+ if (!key)
330
+ continue;
331
+ // First try exact match
332
+ const exactMatch = byKey.get(key);
333
+ if (exactMatch) {
334
+ handledExistingKeys.add(key);
335
+ const analysis = analyzeColumnChanges(exactMatch, attr);
336
+ if (!analysis.hasChanges)
337
+ unchanged.push(analysis.columnKey);
338
+ else if (analysis.requiresRecreate)
339
+ toRecreate.push({ oldAttribute: exactMatch, newAttribute: attr });
340
+ else
341
+ toUpdate.push({ attribute: attr, changes: analysis.changes });
314
342
  continue;
315
343
  }
316
- const analysis = analyzeColumnChanges(existing, attr);
317
- if (!analysis.hasChanges)
318
- unchanged.push(analysis.columnKey);
319
- else if (analysis.requiresRecreate)
320
- toRecreate.push({ oldAttribute: existing, newAttribute: attr });
321
- else
322
- toUpdate.push({ attribute: attr, changes: analysis.changes });
344
+ // Check for case-insensitive match (rename scenario like oAuthAccounts -> oauthAccounts)
345
+ const caseInsensitiveMatch = byKeyLower.get(key.toLowerCase());
346
+ if (caseInsensitiveMatch && caseInsensitiveMatch.key !== key) {
347
+ // This is a rename - treat as recreate (delete old, create new)
348
+ handledExistingKeys.add(caseInsensitiveMatch.key);
349
+ toRecreate.push({ oldAttribute: caseInsensitiveMatch, newAttribute: attr });
350
+ continue;
351
+ }
352
+ // No match - it's a new attribute
353
+ toCreate.push(attr);
323
354
  }
324
355
  // Note: we keep toDelete empty for now (conservative behavior)
356
+ // Deletions are handled separately in methods.ts
325
357
  return { toCreate, toUpdate, toRecreate, toDelete: [], unchanged };
326
358
  }
327
359
  /**
@@ -1,5 +1,5 @@
1
1
  import { Databases } from "node-appwrite";
2
- import type { DatabaseAdapter } from "../adapters/DatabaseAdapter.js";
2
+ import type { DatabaseAdapter } from "@njdamstra/appwrite-utils-helpers";
3
3
  /**
4
4
  * Transfers all documents from one collection to another in a different database
5
5
  * within the same Appwrite Project
@@ -1,9 +1,8 @@
1
1
  import { Client, Databases, ID, Query, } from "node-appwrite";
2
- import { tryAwaitWithRetry, delay, calculateExponentialBackoff } from "../utils/helperFunctions.js";
3
- import { MessageFormatter } from "../shared/messageFormatter.js";
2
+ import { tryAwaitWithRetry, delay, calculateExponentialBackoff, MessageFormatter } from "@njdamstra/appwrite-utils-helpers";
4
3
  import { chunk } from "es-toolkit";
5
- import { isLegacyDatabases } from "../utils/typeGuards.js";
6
- import { getAdapter } from "../utils/getClientFromConfig.js";
4
+ import { isLegacyDatabases } from "@njdamstra/appwrite-utils-helpers";
5
+ import { getAdapter } from "@njdamstra/appwrite-utils-helpers";
7
6
  /**
8
7
  * Transfers all documents from one collection to another in a different database
9
8
  * within the same Appwrite Project
@@ -1,5 +1,5 @@
1
1
  import { Databases } from "node-appwrite";
2
- import type { DatabaseAdapter } from "../adapters/DatabaseAdapter.js";
2
+ import type { DatabaseAdapter } from "@njdamstra/appwrite-utils-helpers";
3
3
  export declare const wipeDatabase: (database: Databases, databaseId: string) => Promise<{
4
4
  collectionId: string;
5
5
  collectionName: string;
@@ -10,7 +10,8 @@ export declare const wipeAllTables: (adapter: DatabaseAdapter, databaseId: strin
10
10
  tableName: string;
11
11
  }[]>;
12
12
  /**
13
- * Optimized deletion of all rows from a table using direct bulk deletion
14
- * Uses Query.limit() to delete rows without fetching IDs first
13
+ * Optimized deletion of all rows from a table.
14
+ * Uses bulk deletion when possible, but falls back to individual row deletion
15
+ * for tables with relationship columns (bulk delete not supported for those).
15
16
  */
16
17
  export declare const wipeTableRows: (adapter: DatabaseAdapter, databaseId: string, tableId: string) => Promise<void>;
@@ -1,9 +1,8 @@
1
1
  import { Databases, Query, } from "node-appwrite";
2
- import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
3
- import { MessageFormatter } from "../shared/messageFormatter.js";
2
+ import { tryAwaitWithRetry } from "@njdamstra/appwrite-utils-helpers";
3
+ import { MessageFormatter, isRetryableError, isCriticalError } from "@njdamstra/appwrite-utils-helpers";
4
4
  import { ProgressManager } from "../shared/progressManager.js";
5
- import { isRetryableError, isCriticalError } from "../shared/errorUtils.js";
6
- import { delay } from "../utils/helperFunctions.js";
5
+ import { delay } from "@njdamstra/appwrite-utils-helpers";
7
6
  import { chunk } from "es-toolkit";
8
7
  import pLimit from "p-limit";
9
8
  import { fetchAllCollections } from "./methods.js";
@@ -167,52 +166,126 @@ export const wipeAllTables = async (adapter, databaseId) => {
167
166
  return deleted;
168
167
  };
169
168
  /**
170
- * Optimized deletion of all rows from a table using direct bulk deletion
171
- * Uses Query.limit() to delete rows without fetching IDs first
169
+ * Optimized deletion of all rows from a table.
170
+ * Uses bulk deletion when possible, but falls back to individual row deletion
171
+ * for tables with relationship columns (bulk delete not supported for those).
172
172
  */
173
173
  export const wipeTableRows = async (adapter, databaseId, tableId) => {
174
174
  try {
175
- // Check if bulk deletion is available
176
- if (!adapter.bulkDeleteRows) {
177
- MessageFormatter.error("Bulk deletion not available for this adapter - wipe operation not supported", new Error("bulkDeleteRows not available"), { prefix: "Wipe" });
178
- throw new Error("Bulk deletion required for wipe operations");
179
- }
180
- const DELETE_BATCH_SIZE = 250; // How many rows to delete per batch
175
+ // Check if the table has relationship columns — bulk delete is not supported for those
176
+ const tableInfo = await adapter.getTable({ databaseId, tableId });
177
+ const columns = tableInfo.data?.columns || [];
178
+ const hasRelationships = columns.some((col) => col.type === "relationship");
179
+ const DELETE_BATCH_SIZE = 250;
181
180
  let totalDeleted = 0;
182
181
  let hasMoreRows = true;
183
- MessageFormatter.info("Starting optimized table row deletion...", { prefix: "Wipe" });
184
- const progress = ProgressManager.create(`delete-${tableId}`, 1, // Start with 1, will update as we discover more
185
- { title: "Deleting table rows" });
186
- while (hasMoreRows) {
187
- try {
188
- // Delete next batch using Query.limit() - no fetching needed!
189
- const result = await tryAwaitWithRetry(async () => adapter.bulkDeleteRows({
190
- databaseId,
191
- tableId,
192
- rowIds: [], // Empty array signals we want to use Query.limit instead
193
- batchSize: DELETE_BATCH_SIZE
194
- }));
195
- const deletedCount = result.total || 0;
196
- if (deletedCount === 0) {
182
+ const progress = ProgressManager.create(`delete-${tableId}`, 1, { title: "Deleting table rows" });
183
+ if (hasRelationships) {
184
+ // ── Relationship table: fetch rows then delete individually ──
185
+ MessageFormatter.info("Table has relationship columns — using individual row deletion (bulk delete not supported)", { prefix: "Wipe" });
186
+ const FETCH_BATCH_SIZE = 1000;
187
+ const MAX_CONCURRENT_DELETES = 25;
188
+ const limit = pLimit(MAX_CONCURRENT_DELETES);
189
+ // Pipeline: prefetch the first batch, then overlap fetch+delete
190
+ let pendingRows = [];
191
+ let totalDiscovered = 0;
192
+ // Fetch helper — always fetches from the top since we're deleting everything
193
+ const fetchBatch = async () => {
194
+ const queries = [Query.limit(FETCH_BATCH_SIZE)];
195
+ const response = await tryAwaitWithRetry(async () => adapter.listRows({ databaseId, tableId, queries }));
196
+ return response.rows || response.data || [];
197
+ };
198
+ // Kick off the first fetch
199
+ let nextFetchPromise = fetchBatch();
200
+ while (hasMoreRows) {
201
+ // Await the prefetched batch
202
+ const rows = nextFetchPromise ? await nextFetchPromise : [];
203
+ nextFetchPromise = null;
204
+ if (rows.length === 0) {
197
205
  hasMoreRows = false;
198
206
  break;
199
207
  }
200
- totalDeleted += deletedCount;
201
- progress.setTotal(totalDeleted + 100); // Estimate more rows exist
202
- progress.update(totalDeleted);
203
- MessageFormatter.progress(`Deleted ${deletedCount} rows (${totalDeleted} total so far)`, { prefix: "Wipe" });
204
- // Small delay between batches to be respectful to the API
208
+ totalDiscovered += rows.length;
209
+ const isLastBatch = rows.length < FETCH_BATCH_SIZE;
210
+ if (!isLastBatch) {
211
+ progress.setTotal(totalDiscovered + 1000);
212
+ }
213
+ else {
214
+ progress.setTotal(totalDiscovered);
215
+ }
216
+ MessageFormatter.progress(`Fetched ${rows.length} rows (${totalDiscovered} discovered, ${totalDeleted} deleted so far)`, { prefix: "Wipe" });
217
+ // Start deleting this batch — and prefetch the next one concurrently
218
+ // (only prefetch if we expect more rows)
219
+ if (!isLastBatch) {
220
+ // Wait a moment before prefetching so the first few deletes free up API capacity
221
+ nextFetchPromise = delay(200).then(() => fetchBatch());
222
+ }
223
+ // Delete each row with concurrency limit
224
+ const deletePromises = rows.map((row) => limit(async () => {
225
+ try {
226
+ await tryAwaitWithRetry(async () => adapter.deleteRow({ databaseId, tableId, id: row.$id }));
227
+ totalDeleted++;
228
+ progress.update(totalDeleted);
229
+ }
230
+ catch (error) {
231
+ const errorMessage = error.message || String(error);
232
+ if (errorMessage.includes("could not be found")) {
233
+ totalDeleted++;
234
+ progress.update(totalDeleted);
235
+ }
236
+ else if (isCriticalError(errorMessage)) {
237
+ MessageFormatter.error(`Critical error deleting row ${row.$id}: ${errorMessage}`, error, { prefix: "Wipe" });
238
+ throw error;
239
+ }
240
+ else {
241
+ MessageFormatter.error(`Failed to delete row ${row.$id}: ${errorMessage}`, error, { prefix: "Wipe" });
242
+ totalDeleted++;
243
+ progress.update(totalDeleted);
244
+ }
245
+ }
246
+ }));
247
+ await Promise.all(deletePromises);
248
+ if (isLastBatch) {
249
+ hasMoreRows = false;
250
+ }
205
251
  await delay(10);
206
252
  }
207
- catch (error) {
208
- const errorMessage = error.message || String(error);
209
- if (isCriticalError(errorMessage)) {
210
- MessageFormatter.error(`Critical error during bulk deletion: ${errorMessage}`, error, { prefix: "Wipe" });
211
- throw error;
253
+ }
254
+ else {
255
+ // ── No relationships: use fast bulk deletion ──
256
+ if (!adapter.bulkDeleteRows) {
257
+ MessageFormatter.error("Bulk deletion not available for this adapter - wipe operation not supported", new Error("bulkDeleteRows not available"), { prefix: "Wipe" });
258
+ throw new Error("Bulk deletion required for wipe operations");
259
+ }
260
+ MessageFormatter.info("Starting optimized table row deletion...", { prefix: "Wipe" });
261
+ while (hasMoreRows) {
262
+ try {
263
+ const result = await tryAwaitWithRetry(async () => adapter.bulkDeleteRows({
264
+ databaseId,
265
+ tableId,
266
+ rowIds: [],
267
+ batchSize: DELETE_BATCH_SIZE
268
+ }));
269
+ const deletedCount = result.total || 0;
270
+ if (deletedCount === 0) {
271
+ hasMoreRows = false;
272
+ break;
273
+ }
274
+ totalDeleted += deletedCount;
275
+ progress.setTotal(totalDeleted + 100);
276
+ progress.update(totalDeleted);
277
+ MessageFormatter.progress(`Deleted ${deletedCount} rows (${totalDeleted} total so far)`, { prefix: "Wipe" });
278
+ await delay(10);
212
279
  }
213
- else {
214
- MessageFormatter.error(`Error during deletion batch: ${errorMessage}`, error, { prefix: "Wipe" });
215
- // Continue trying with next batch
280
+ catch (error) {
281
+ const errorMessage = error.message || String(error);
282
+ if (isCriticalError(errorMessage)) {
283
+ MessageFormatter.error(`Critical error during bulk deletion: ${errorMessage}`, error, { prefix: "Wipe" });
284
+ throw error;
285
+ }
286
+ else {
287
+ MessageFormatter.error(`Error during deletion batch: ${errorMessage}`, error, { prefix: "Wipe" });
288
+ }
216
289
  }
217
290
  }
218
291
  }
@@ -1,7 +1,7 @@
1
1
  import { Databases, Query } 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
  export const fetchAllDatabases = async (database) => {
6
6
  const databases = await tryAwaitWithRetry(async () => await database.list([Query.limit(25)]));
7
7
  const allDatabases = databases.databases;
@@ -1,8 +1,8 @@
1
1
  import { Databases, Query } from "node-appwrite";
2
- import { tryAwaitWithRetry } from "../utils/index.js";
2
+ import { tryAwaitWithRetry } from "@njdamstra/appwrite-utils-helpers";
3
3
  import {} 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
  export const ensureDatabasesExist = async (config, databasesToEnsure) => {
7
7
  if (!config.appwriteClient) {
8
8
  throw new Error("Appwrite client is not initialized in the config");
@@ -7,8 +7,8 @@
7
7
  * 3. Validate terminology consistency
8
8
  * 4. Migrate between formats
9
9
  */
10
- import { collectionToYaml, generateYamlTemplate, generateExampleYamls, convertTerminology, normalizeYamlData, usesTableTerminology } from "../utils/yamlConverter.js";
11
- import { createYamlLoader } from "../utils/yamlLoader.js";
10
+ import { collectionToYaml, generateYamlTemplate, generateExampleYamls, convertTerminology, normalizeYamlData, usesTableTerminology } from "@njdamstra/appwrite-utils-helpers";
11
+ import { createYamlLoader } from "@njdamstra/appwrite-utils-helpers";
12
12
  import { YamlImportIntegration } from "../migrations/yaml/YamlImportIntegration.js";
13
13
  import { createImportSchemas } from "../migrations/yaml/generateImportSchemas.js";
14
14
  import { CollectionCreateSchema } from "@njdamstra/appwrite-utils";
@@ -1,4 +1,4 @@
1
1
  import { Client, type Models } from "node-appwrite";
2
2
  import { type AppwriteFunction } from "@njdamstra/appwrite-utils";
3
3
  export declare const deployFunction: (client: Client, functionId: string, codePath: string, activate?: boolean, entrypoint?: string, commands?: string, ignored?: string[]) => Promise<Models.Deployment>;
4
- export declare const deployLocalFunction: (client: Client, functionName: string, functionConfig: AppwriteFunction, functionPath?: string) => Promise<Models.Deployment>;
4
+ export declare const deployLocalFunction: (client: Client, functionName: string, functionConfig: AppwriteFunction, functionPath?: string, configDirPath?: string) => Promise<Models.Deployment>;
@@ -10,8 +10,8 @@ import cliProgress from "cli-progress";
10
10
  import { execSync } from "child_process";
11
11
  import { createFunction, getFunction, updateFunction, updateFunctionSpecifications, } from "./methods.js";
12
12
  import ignore from "ignore";
13
- import { MessageFormatter } from "../shared/messageFormatter.js";
14
- import { resolveFunctionDirectory, validateFunctionDirectory } from './pathResolution.js';
13
+ import { MessageFormatter } from "@njdamstra/appwrite-utils-helpers";
14
+ import { resolveFunctionDirectory, validateFunctionDirectory } from '@njdamstra/appwrite-utils-helpers';
15
15
  export const deployFunction = async (client, functionId, codePath, activate = true, entrypoint = "index.js", commands = "npm install", ignored = [
16
16
  "node_modules",
17
17
  ".git",
@@ -98,7 +98,7 @@ export const deployFunction = async (client, functionId, codePath, activate = tr
98
98
  throw error;
99
99
  }
100
100
  };
101
- export const deployLocalFunction = async (client, functionName, functionConfig, functionPath) => {
101
+ export const deployLocalFunction = async (client, functionName, functionConfig, functionPath, configDirPath) => {
102
102
  let functionExists = true;
103
103
  let functionThatExists;
104
104
  try {
@@ -107,8 +107,8 @@ export const deployLocalFunction = async (client, functionName, functionConfig,
107
107
  catch (error) {
108
108
  functionExists = false;
109
109
  }
110
- const configDirPath = process.cwd(); // TODO: This should be passed from caller
111
- const resolvedPath = resolveFunctionDirectory(functionName, configDirPath, functionConfig.dirPath, functionPath);
110
+ const resolvedConfigDir = configDirPath ?? process.cwd();
111
+ const resolvedPath = resolveFunctionDirectory(functionName, resolvedConfigDir, functionConfig.dirPath, functionPath);
112
112
  if (!validateFunctionDirectory(resolvedPath)) {
113
113
  throw new Error(`Function directory is invalid or missing required files: ${resolvedPath}`);
114
114
  }
@@ -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 } 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
  function findGitRoot(startDir) {
9
9
  let dir = path.resolve(startDir);
10
10
  while (dir !== path.parse(dir).root) {
@@ -2,14 +2,15 @@ import { AppwriteException, Client, Functions, Query, Runtime, } from "node-appw
2
2
  import { join, dirname } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import fs from "node:fs";
5
- import {} from "@njdamstra/appwrite-utils";
5
+ import { EventTypeSchema, } from "@njdamstra/appwrite-utils";
6
6
  import chalk from "chalk";
7
7
  import { extract as extractTar } from "tar";
8
- import { MessageFormatter } from "../shared/messageFormatter.js";
9
- import { expandTildePath, normalizeFunctionName } from "./pathResolution.js";
8
+ import { MessageFormatter } from "@njdamstra/appwrite-utils-helpers";
9
+ import { expandTildePath, normalizeFunctionName } from '@njdamstra/appwrite-utils-helpers';
10
10
  /**
11
11
  * Validates and filters events array for Appwrite functions
12
12
  * - Filters out empty/invalid strings
13
+ * - Validates against EventTypeSchema
13
14
  * - Limits to 100 items maximum (Appwrite limit)
14
15
  * - Returns empty array if input is invalid
15
16
  */
@@ -17,7 +18,18 @@ const validateEvents = (events) => {
17
18
  if (!events || !Array.isArray(events))
18
19
  return [];
19
20
  return events
20
- .filter(event => event && typeof event === 'string' && event.trim().length > 0)
21
+ .filter(event => {
22
+ if (!event || typeof event !== 'string' || event.trim().length === 0) {
23
+ return false;
24
+ }
25
+ // Validate against EventTypeSchema
26
+ const result = EventTypeSchema.safeParse(event);
27
+ if (!result.success) {
28
+ MessageFormatter.warning(`Invalid event type "${event}" will be filtered out`, { prefix: "Functions" });
29
+ return false;
30
+ }
31
+ return true;
32
+ })
21
33
  .slice(0, 100);
22
34
  };
23
35
  export const listFunctions = async (client, queries, search) => {
package/dist/init.js 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
  MessageFormatter.banner("Appwrite Utils CLI Tool", "For more information, visit https://github.com/njdamstra/AppwriteUtils");
6
6
  async function main() {
7
7
  const answers = await inquirer.prompt([
@@ -1,9 +1,14 @@
1
+ export interface InteractiveCLIOptions {
2
+ useSession?: boolean;
3
+ sessionCookie?: string;
4
+ }
1
5
  export declare class InteractiveCLI {
2
6
  private currentDir;
3
7
  private controller;
4
8
  private isUsingTypeScriptConfig;
5
9
  private lastSelectedCollectionIds;
6
- constructor(currentDir: string);
10
+ private options;
11
+ constructor(currentDir: string, options?: InteractiveCLIOptions);
7
12
  run(): Promise<void>;
8
13
  private initControllerIfNeeded;
9
14
  private manageBuckets;