appwrite-utils-cli 1.7.9 → 1.8.2

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 (70) hide show
  1. package/CHANGELOG.md +14 -199
  2. package/README.md +87 -30
  3. package/dist/adapters/AdapterFactory.js +5 -25
  4. package/dist/adapters/DatabaseAdapter.d.ts +17 -2
  5. package/dist/adapters/LegacyAdapter.d.ts +2 -1
  6. package/dist/adapters/LegacyAdapter.js +212 -16
  7. package/dist/adapters/TablesDBAdapter.d.ts +2 -12
  8. package/dist/adapters/TablesDBAdapter.js +261 -57
  9. package/dist/cli/commands/databaseCommands.js +4 -3
  10. package/dist/cli/commands/functionCommands.js +17 -8
  11. package/dist/collections/attributes.js +447 -125
  12. package/dist/collections/methods.js +197 -186
  13. package/dist/collections/tableOperations.d.ts +86 -0
  14. package/dist/collections/tableOperations.js +434 -0
  15. package/dist/collections/transferOperations.d.ts +3 -2
  16. package/dist/collections/transferOperations.js +93 -12
  17. package/dist/config/yamlConfig.d.ts +221 -88
  18. package/dist/examples/yamlTerminologyExample.d.ts +1 -1
  19. package/dist/examples/yamlTerminologyExample.js +6 -3
  20. package/dist/functions/fnConfigDiscovery.d.ts +3 -0
  21. package/dist/functions/fnConfigDiscovery.js +108 -0
  22. package/dist/interactiveCLI.js +18 -15
  23. package/dist/main.js +211 -73
  24. package/dist/migrations/appwriteToX.d.ts +88 -23
  25. package/dist/migrations/comprehensiveTransfer.d.ts +2 -0
  26. package/dist/migrations/comprehensiveTransfer.js +83 -6
  27. package/dist/migrations/dataLoader.d.ts +227 -69
  28. package/dist/migrations/dataLoader.js +3 -3
  29. package/dist/migrations/importController.js +3 -3
  30. package/dist/migrations/relationships.d.ts +8 -2
  31. package/dist/migrations/services/ImportOrchestrator.js +3 -3
  32. package/dist/migrations/transfer.js +159 -37
  33. package/dist/shared/attributeMapper.d.ts +20 -0
  34. package/dist/shared/attributeMapper.js +203 -0
  35. package/dist/shared/selectionDialogs.js +8 -4
  36. package/dist/storage/schemas.d.ts +354 -92
  37. package/dist/utils/configDiscovery.js +4 -3
  38. package/dist/utils/versionDetection.d.ts +0 -4
  39. package/dist/utils/versionDetection.js +41 -173
  40. package/dist/utils/yamlConverter.js +89 -16
  41. package/dist/utils/yamlLoader.d.ts +1 -1
  42. package/dist/utils/yamlLoader.js +6 -2
  43. package/dist/utilsController.js +56 -19
  44. package/package.json +4 -4
  45. package/src/adapters/AdapterFactory.ts +119 -143
  46. package/src/adapters/DatabaseAdapter.ts +18 -3
  47. package/src/adapters/LegacyAdapter.ts +236 -105
  48. package/src/adapters/TablesDBAdapter.ts +773 -643
  49. package/src/cli/commands/databaseCommands.ts +13 -12
  50. package/src/cli/commands/functionCommands.ts +23 -14
  51. package/src/collections/attributes.ts +2054 -1611
  52. package/src/collections/methods.ts +208 -293
  53. package/src/collections/tableOperations.ts +506 -0
  54. package/src/collections/transferOperations.ts +218 -144
  55. package/src/examples/yamlTerminologyExample.ts +10 -5
  56. package/src/functions/fnConfigDiscovery.ts +103 -0
  57. package/src/interactiveCLI.ts +25 -20
  58. package/src/main.ts +549 -194
  59. package/src/migrations/comprehensiveTransfer.ts +126 -50
  60. package/src/migrations/dataLoader.ts +3 -3
  61. package/src/migrations/importController.ts +3 -3
  62. package/src/migrations/services/ImportOrchestrator.ts +3 -3
  63. package/src/migrations/transfer.ts +148 -131
  64. package/src/shared/attributeMapper.ts +229 -0
  65. package/src/shared/selectionDialogs.ts +29 -25
  66. package/src/utils/configDiscovery.ts +9 -3
  67. package/src/utils/versionDetection.ts +74 -228
  68. package/src/utils/yamlConverter.ts +94 -17
  69. package/src/utils/yamlLoader.ts +11 -4
  70. package/src/utilsController.ts +80 -30
@@ -1,33 +1,34 @@
1
1
  import { Query } from "node-appwrite";
2
2
  import { attributeSchema, parseAttribute, } from "appwrite-utils";
3
- import { nameToIdMapping, enqueueOperation, markAttributeProcessed, isAttributeProcessed } from "../shared/operationQueue.js";
4
- import { delay, tryAwaitWithRetry, calculateExponentialBackoff } from "../utils/helperFunctions.js";
3
+ import { nameToIdMapping, enqueueOperation, markAttributeProcessed, isAttributeProcessed, } from "../shared/operationQueue.js";
4
+ import { delay, tryAwaitWithRetry, calculateExponentialBackoff, } from "../utils/helperFunctions.js";
5
5
  import chalk from "chalk";
6
+ import { Decimal } from "decimal.js";
6
7
  import { logger } from "../shared/logging.js";
7
8
  import { MessageFormatter } from "../shared/messageFormatter.js";
8
9
  import { isDatabaseAdapter } from "../utils/typeGuards.js";
9
- // Threshold for treating min/max values as undefined (1 trillion)
10
- const MIN_MAX_THRESHOLD = 1_000_000_000_000;
11
10
  // Extreme values that Appwrite may return, which should be treated as undefined
12
11
  const EXTREME_MIN_INTEGER = -9223372036854776000;
13
12
  const EXTREME_MAX_INTEGER = 9223372036854776000;
14
- const EXTREME_MIN_FLOAT = -1.7976931348623157e+308;
15
- const EXTREME_MAX_FLOAT = 1.7976931348623157e+308;
13
+ const EXTREME_MIN_FLOAT = -1.7976931348623157e308;
14
+ const EXTREME_MAX_FLOAT = 1.7976931348623157e308;
16
15
  /**
17
16
  * Type guard to check if an attribute has min/max properties
18
17
  */
19
18
  const hasMinMaxProperties = (attribute) => {
20
- return attribute.type === 'integer' || attribute.type === 'double' || attribute.type === 'float';
19
+ return (attribute.type === "integer" ||
20
+ attribute.type === "double" ||
21
+ attribute.type === "float");
21
22
  };
22
23
  /**
23
- * Normalizes min/max values for integer and float attributes
24
- * Sets values to undefined if they exceed the threshold or are extreme values from database
24
+ * Normalizes min/max values for integer and float attributes using Decimal.js for precision
25
+ * Validates that min < max and handles extreme database values
25
26
  */
26
27
  const normalizeMinMaxValues = (attribute) => {
27
28
  if (!hasMinMaxProperties(attribute)) {
28
29
  logger.debug(`Attribute '${attribute.key}' does not have min/max properties`, {
29
30
  type: attribute.type,
30
- operation: 'normalizeMinMaxValues'
31
+ operation: "normalizeMinMaxValues",
31
32
  });
32
33
  return {};
33
34
  }
@@ -38,21 +39,20 @@ const normalizeMinMaxValues = (attribute) => {
38
39
  type,
39
40
  originalMin: min,
40
41
  originalMax: max,
41
- operation: 'normalizeMinMaxValues'
42
+ operation: "normalizeMinMaxValues",
42
43
  });
43
- // Handle min value
44
+ // Handle min value - only filter out extreme database values
44
45
  if (normalizedMin !== undefined && normalizedMin !== null) {
45
46
  const minValue = Number(normalizedMin);
46
47
  const originalMin = normalizedMin;
47
- // Check if it exceeds threshold or is an extreme database value
48
+ // Check if it's an extreme database value (but don't filter out large numbers)
48
49
  if (type === 'integer') {
49
- if (Math.abs(minValue) >= MIN_MAX_THRESHOLD || minValue === EXTREME_MIN_INTEGER) {
50
+ if (minValue === EXTREME_MIN_INTEGER) {
50
51
  logger.debug(`Min value normalized to undefined for attribute '${attribute.key}'`, {
51
52
  type,
52
53
  originalValue: originalMin,
53
54
  numericValue: minValue,
54
- reason: Math.abs(minValue) >= MIN_MAX_THRESHOLD ? 'exceeds_threshold' : 'extreme_database_value',
55
- threshold: MIN_MAX_THRESHOLD,
55
+ reason: 'extreme_database_value',
56
56
  extremeValue: EXTREME_MIN_INTEGER,
57
57
  operation: 'normalizeMinMaxValues'
58
58
  });
@@ -60,13 +60,12 @@ const normalizeMinMaxValues = (attribute) => {
60
60
  }
61
61
  }
62
62
  else { // float/double
63
- if (Math.abs(minValue) >= MIN_MAX_THRESHOLD || minValue === EXTREME_MIN_FLOAT) {
63
+ if (minValue === EXTREME_MIN_FLOAT) {
64
64
  logger.debug(`Min value normalized to undefined for attribute '${attribute.key}'`, {
65
65
  type,
66
66
  originalValue: originalMin,
67
67
  numericValue: minValue,
68
- reason: Math.abs(minValue) >= MIN_MAX_THRESHOLD ? 'exceeds_threshold' : 'extreme_database_value',
69
- threshold: MIN_MAX_THRESHOLD,
68
+ reason: 'extreme_database_value',
70
69
  extremeValue: EXTREME_MIN_FLOAT,
71
70
  operation: 'normalizeMinMaxValues'
72
71
  });
@@ -74,19 +73,18 @@ const normalizeMinMaxValues = (attribute) => {
74
73
  }
75
74
  }
76
75
  }
77
- // Handle max value
76
+ // Handle max value - only filter out extreme database values
78
77
  if (normalizedMax !== undefined && normalizedMax !== null) {
79
78
  const maxValue = Number(normalizedMax);
80
79
  const originalMax = normalizedMax;
81
- // Check if it exceeds threshold or is an extreme database value
80
+ // Check if it's an extreme database value (but don't filter out large numbers)
82
81
  if (type === 'integer') {
83
- if (Math.abs(maxValue) >= MIN_MAX_THRESHOLD || maxValue === EXTREME_MAX_INTEGER) {
82
+ if (maxValue === EXTREME_MAX_INTEGER) {
84
83
  logger.debug(`Max value normalized to undefined for attribute '${attribute.key}'`, {
85
84
  type,
86
85
  originalValue: originalMax,
87
86
  numericValue: maxValue,
88
- reason: Math.abs(maxValue) >= MIN_MAX_THRESHOLD ? 'exceeds_threshold' : 'extreme_database_value',
89
- threshold: MIN_MAX_THRESHOLD,
87
+ reason: 'extreme_database_value',
90
88
  extremeValue: EXTREME_MAX_INTEGER,
91
89
  operation: 'normalizeMinMaxValues'
92
90
  });
@@ -94,13 +92,12 @@ const normalizeMinMaxValues = (attribute) => {
94
92
  }
95
93
  }
96
94
  else { // float/double
97
- if (Math.abs(maxValue) >= MIN_MAX_THRESHOLD || maxValue === EXTREME_MAX_FLOAT) {
95
+ if (maxValue === EXTREME_MAX_FLOAT) {
98
96
  logger.debug(`Max value normalized to undefined for attribute '${attribute.key}'`, {
99
97
  type,
100
98
  originalValue: originalMax,
101
99
  numericValue: maxValue,
102
- reason: Math.abs(maxValue) >= MIN_MAX_THRESHOLD ? 'exceeds_threshold' : 'extreme_database_value',
103
- threshold: MIN_MAX_THRESHOLD,
100
+ reason: 'extreme_database_value',
104
101
  extremeValue: EXTREME_MAX_FLOAT,
105
102
  operation: 'normalizeMinMaxValues'
106
103
  });
@@ -108,11 +105,115 @@ const normalizeMinMaxValues = (attribute) => {
108
105
  }
109
106
  }
110
107
  }
108
+ // Validate that min < max using multiple comparison methods for reliability
109
+ if (normalizedMin !== undefined && normalizedMax !== undefined &&
110
+ normalizedMin !== null && normalizedMax !== null) {
111
+ logger.debug(`Validating min/max values for attribute '${attribute.key}'`, {
112
+ type,
113
+ normalizedMin,
114
+ normalizedMax,
115
+ normalizedMinType: typeof normalizedMin,
116
+ normalizedMaxType: typeof normalizedMax,
117
+ operation: 'normalizeMinMaxValues'
118
+ });
119
+ // Use multiple validation approaches to ensure reliability
120
+ let needsSwap = false;
121
+ let comparisonMethod = '';
122
+ try {
123
+ // Method 1: Direct number comparison (most reliable for normal numbers)
124
+ const minNum = Number(normalizedMin);
125
+ const maxNum = Number(normalizedMax);
126
+ if (!isNaN(minNum) && !isNaN(maxNum)) {
127
+ needsSwap = minNum >= maxNum;
128
+ comparisonMethod = 'direct_number_comparison';
129
+ logger.debug(`Direct number comparison: ${minNum} >= ${maxNum} = ${needsSwap}`, {
130
+ operation: 'normalizeMinMaxValues'
131
+ });
132
+ }
133
+ // Method 2: Fallback to string comparison for very large numbers
134
+ if (!needsSwap && (isNaN(minNum) || isNaN(maxNum) || Math.abs(minNum) > Number.MAX_SAFE_INTEGER || Math.abs(maxNum) > Number.MAX_SAFE_INTEGER)) {
135
+ const minStr = normalizedMin.toString();
136
+ const maxStr = normalizedMax.toString();
137
+ // Simple string length and lexicographical comparison for very large numbers
138
+ if (minStr.length !== maxStr.length) {
139
+ needsSwap = minStr.length > maxStr.length;
140
+ }
141
+ else {
142
+ needsSwap = minStr >= maxStr;
143
+ }
144
+ comparisonMethod = 'string_comparison_fallback';
145
+ logger.debug(`String comparison fallback: '${minStr}' >= '${maxStr}' = ${needsSwap}`, {
146
+ operation: 'normalizeMinMaxValues'
147
+ });
148
+ }
149
+ // Method 3: Final validation using Decimal.js as last resort
150
+ if (!needsSwap && (typeof normalizedMin === 'string' || typeof normalizedMax === 'string')) {
151
+ try {
152
+ const minDecimal = new Decimal(normalizedMin.toString());
153
+ const maxDecimal = new Decimal(normalizedMax.toString());
154
+ needsSwap = minDecimal.greaterThanOrEqualTo(maxDecimal);
155
+ comparisonMethod = 'decimal_js_fallback';
156
+ logger.debug(`Decimal.js fallback: ${normalizedMin} >= ${normalizedMax} = ${needsSwap}`, {
157
+ operation: 'normalizeMinMaxValues'
158
+ });
159
+ }
160
+ catch (decimalError) {
161
+ logger.warn(`Decimal.js comparison failed for attribute '${attribute.key}': ${decimalError instanceof Error ? decimalError.message : String(decimalError)}`, {
162
+ operation: 'normalizeMinMaxValues'
163
+ });
164
+ }
165
+ }
166
+ // Log final validation result
167
+ if (needsSwap) {
168
+ logger.error(`Invalid min/max values detected for attribute '${attribute.key}': min (${normalizedMin}) must be less than max (${normalizedMax})`, {
169
+ type,
170
+ min: normalizedMin,
171
+ max: normalizedMax,
172
+ comparisonMethod,
173
+ operation: 'normalizeMinMaxValues'
174
+ });
175
+ // Swap values to ensure min < max (graceful handling)
176
+ logger.warn(`Swapping min/max values for attribute '${attribute.key}' to fix validation`, {
177
+ type,
178
+ originalMin: normalizedMin,
179
+ originalMax: normalizedMax,
180
+ newMin: normalizedMax,
181
+ newMax: normalizedMin,
182
+ comparisonMethod,
183
+ operation: 'normalizeMinMaxValues'
184
+ });
185
+ const temp = normalizedMin;
186
+ normalizedMin = normalizedMax;
187
+ normalizedMax = temp;
188
+ }
189
+ else {
190
+ logger.debug(`Min/max validation passed for attribute '${attribute.key}'`, {
191
+ type,
192
+ min: normalizedMin,
193
+ max: normalizedMax,
194
+ comparisonMethod,
195
+ operation: 'normalizeMinMaxValues'
196
+ });
197
+ }
198
+ }
199
+ catch (error) {
200
+ logger.error(`Critical error during min/max validation for attribute '${attribute.key}'`, {
201
+ type,
202
+ min: normalizedMin,
203
+ max: normalizedMax,
204
+ error: error instanceof Error ? error.message : String(error),
205
+ operation: 'normalizeMinMaxValues'
206
+ });
207
+ // If all comparison methods fail, set both to undefined to avoid API errors
208
+ normalizedMin = undefined;
209
+ normalizedMax = undefined;
210
+ }
211
+ }
111
212
  const result = { min: normalizedMin, max: normalizedMax };
112
213
  logger.debug(`Min/max normalization complete for attribute '${attribute.key}'`, {
113
214
  type,
114
215
  result,
115
- operation: 'normalizeMinMaxValues'
216
+ operation: "normalizeMinMaxValues",
116
217
  });
117
218
  return result;
118
219
  };
@@ -122,6 +223,10 @@ const normalizeMinMaxValues = (attribute) => {
122
223
  */
123
224
  const normalizeAttributeForComparison = (attribute) => {
124
225
  const normalized = { ...attribute };
226
+ // Ignore defaults on required attributes to prevent false positives
227
+ if (normalized.required === true && "xdefault" in normalized) {
228
+ delete normalized.xdefault;
229
+ }
125
230
  // Normalize min/max for numeric types
126
231
  if (hasMinMaxProperties(attribute)) {
127
232
  const { min, max } = normalizeMinMaxValues(attribute);
@@ -130,7 +235,8 @@ const normalizeAttributeForComparison = (attribute) => {
130
235
  }
131
236
  // Remove xdefault if null/undefined to ensure consistent comparison
132
237
  // Appwrite sets xdefault: null for required attributes, but config files omit it
133
- if ('xdefault' in normalized && (normalized.xdefault === null || normalized.xdefault === undefined)) {
238
+ if ("xdefault" in normalized &&
239
+ (normalized.xdefault === null || normalized.xdefault === undefined)) {
134
240
  delete normalized.xdefault;
135
241
  }
136
242
  return normalized;
@@ -140,13 +246,13 @@ const normalizeAttributeForComparison = (attribute) => {
140
246
  */
141
247
  const createAttributeViaAdapter = async (db, dbId, collectionId, attribute) => {
142
248
  const startTime = Date.now();
143
- const adapterType = isDatabaseAdapter(db) ? 'adapter' : 'legacy';
249
+ const adapterType = isDatabaseAdapter(db) ? "adapter" : "legacy";
144
250
  logger.info(`Creating attribute '${attribute.key}' via ${adapterType}`, {
145
251
  type: attribute.type,
146
252
  dbId,
147
253
  collectionId,
148
254
  adapterType,
149
- operation: 'createAttributeViaAdapter'
255
+ operation: "createAttributeViaAdapter",
150
256
  });
151
257
  if (isDatabaseAdapter(db)) {
152
258
  // Use the adapter's unified createAttribute method
@@ -158,38 +264,57 @@ const createAttributeViaAdapter = async (db, dbId, collectionId, attribute) => {
158
264
  required: attribute.required || false,
159
265
  array: attribute.array || false,
160
266
  ...(attribute.size && { size: attribute.size }),
161
- ...(attribute.xdefault !== undefined && !attribute.required && { default: attribute.xdefault }),
162
- ...(attribute.encrypted && { encrypt: attribute.encrypted }),
163
- ...(attribute.min !== undefined && { min: attribute.min }),
164
- ...(attribute.max !== undefined && { max: attribute.max }),
165
- ...(attribute.elements && { elements: attribute.elements }),
166
- ...(attribute.relatedCollection && { relatedCollection: attribute.relatedCollection }),
167
- ...(attribute.relationType && { relationType: attribute.relationType }),
168
- ...(attribute.twoWay !== undefined && { twoWay: attribute.twoWay }),
169
- ...(attribute.onDelete && { onDelete: attribute.onDelete }),
170
- ...(attribute.twoWayKey && { twoWayKey: attribute.twoWayKey })
267
+ ...(attribute.xdefault !== undefined &&
268
+ !attribute.required && { default: attribute.xdefault }),
269
+ ...(attribute.encrypted && {
270
+ encrypt: attribute.encrypted,
271
+ }),
272
+ ...(attribute.min !== undefined && {
273
+ min: attribute.min,
274
+ }),
275
+ ...(attribute.max !== undefined && {
276
+ max: attribute.max,
277
+ }),
278
+ ...(attribute.elements && {
279
+ elements: attribute.elements,
280
+ }),
281
+ ...(attribute.relatedCollection && {
282
+ relatedCollection: attribute.relatedCollection,
283
+ }),
284
+ ...(attribute.relationType && {
285
+ relationType: attribute.relationType,
286
+ }),
287
+ ...(attribute.twoWay !== undefined && {
288
+ twoWay: attribute.twoWay,
289
+ }),
290
+ ...(attribute.onDelete && {
291
+ onDelete: attribute.onDelete,
292
+ }),
293
+ ...(attribute.twoWayKey && {
294
+ twoWayKey: attribute.twoWayKey,
295
+ }),
171
296
  };
172
297
  logger.debug(`Adapter create parameters for '${attribute.key}'`, {
173
298
  params,
174
- operation: 'createAttributeViaAdapter'
299
+ operation: "createAttributeViaAdapter",
175
300
  });
176
301
  await db.createAttribute(params);
177
302
  const duration = Date.now() - startTime;
178
303
  logger.info(`Successfully created attribute '${attribute.key}' via adapter`, {
179
304
  duration,
180
- operation: 'createAttributeViaAdapter'
305
+ operation: "createAttributeViaAdapter",
181
306
  });
182
307
  }
183
308
  else {
184
309
  // Use legacy type-specific methods
185
310
  logger.debug(`Using legacy creation for attribute '${attribute.key}'`, {
186
- operation: 'createAttributeViaAdapter'
311
+ operation: "createAttributeViaAdapter",
187
312
  });
188
313
  await createLegacyAttribute(db, dbId, collectionId, attribute);
189
314
  const duration = Date.now() - startTime;
190
315
  logger.info(`Successfully created attribute '${attribute.key}' via legacy`, {
191
316
  duration,
192
- operation: 'createAttributeViaAdapter'
317
+ operation: "createAttributeViaAdapter",
193
318
  });
194
319
  }
195
320
  };
@@ -203,9 +328,23 @@ const updateAttributeViaAdapter = async (db, dbId, collectionId, attribute) => {
203
328
  databaseId: dbId,
204
329
  tableId: collectionId,
205
330
  key: attribute.key,
331
+ type: attribute.type,
206
332
  required: attribute.required || false,
207
- ...(attribute.xdefault !== undefined && !attribute.required && { default: attribute.xdefault })
333
+ array: attribute.array || false,
334
+ size: attribute.size,
335
+ min: attribute.min,
336
+ max: attribute.max,
337
+ encrypt: attribute.encrypted ?? attribute.encrypt,
338
+ elements: attribute.elements,
339
+ relatedCollection: attribute.relatedCollection,
340
+ relationType: attribute.relationType,
341
+ twoWay: attribute.twoWay,
342
+ twoWayKey: attribute.twoWayKey,
343
+ onDelete: attribute.onDelete
208
344
  };
345
+ if (!attribute.required && attribute.xdefault !== undefined) {
346
+ params.default = attribute.xdefault;
347
+ }
209
348
  await db.updateAttribute(params);
210
349
  }
211
350
  else {
@@ -225,58 +364,80 @@ const createLegacyAttribute = async (db, dbId, collectionId, attribute) => {
225
364
  collectionId,
226
365
  normalizedMin,
227
366
  normalizedMax,
228
- operation: 'createLegacyAttribute'
367
+ operation: "createLegacyAttribute",
229
368
  });
230
369
  switch (attribute.type) {
231
370
  case "string":
232
371
  const stringParams = {
233
372
  size: attribute.size || 255,
234
373
  required: attribute.required || false,
235
- defaultValue: attribute.xdefault !== undefined && !attribute.required ? attribute.xdefault : undefined,
374
+ defaultValue: attribute.xdefault !== undefined && !attribute.required
375
+ ? attribute.xdefault
376
+ : undefined,
236
377
  array: attribute.array || false,
237
- encrypted: attribute.encrypted
378
+ encrypted: attribute.encrypted,
238
379
  };
239
380
  logger.debug(`Creating string attribute '${attribute.key}'`, {
240
381
  ...stringParams,
241
- operation: 'createLegacyAttribute'
382
+ operation: "createLegacyAttribute",
242
383
  });
243
384
  await db.createStringAttribute(dbId, collectionId, attribute.key, stringParams.size, stringParams.required, stringParams.defaultValue, stringParams.array, stringParams.encrypted);
244
385
  break;
245
386
  case "integer":
246
387
  const integerParams = {
247
388
  required: attribute.required || false,
248
- min: normalizedMin !== undefined ? parseInt(String(normalizedMin)) : undefined,
249
- max: normalizedMax !== undefined ? parseInt(String(normalizedMax)) : undefined,
250
- defaultValue: attribute.xdefault !== undefined && !attribute.required ? attribute.xdefault : undefined,
251
- array: attribute.array || false
389
+ min: normalizedMin !== undefined
390
+ ? parseInt(String(normalizedMin))
391
+ : undefined,
392
+ max: normalizedMax !== undefined
393
+ ? parseInt(String(normalizedMax))
394
+ : undefined,
395
+ defaultValue: attribute.xdefault !== undefined && !attribute.required
396
+ ? attribute.xdefault
397
+ : undefined,
398
+ array: attribute.array || false,
252
399
  };
253
400
  logger.debug(`Creating integer attribute '${attribute.key}'`, {
254
401
  ...integerParams,
255
- operation: 'createLegacyAttribute'
402
+ operation: "createLegacyAttribute",
256
403
  });
257
404
  await db.createIntegerAttribute(dbId, collectionId, attribute.key, integerParams.required, integerParams.min, integerParams.max, integerParams.defaultValue, integerParams.array);
258
405
  break;
259
406
  case "double":
260
407
  case "float":
261
- await db.createFloatAttribute(dbId, collectionId, attribute.key, attribute.required || false, normalizedMin !== undefined ? Number(normalizedMin) : undefined, normalizedMax !== undefined ? Number(normalizedMax) : undefined, attribute.xdefault !== undefined && !attribute.required ? attribute.xdefault : undefined, attribute.array || false);
408
+ await db.createFloatAttribute(dbId, collectionId, attribute.key, attribute.required || false, normalizedMin !== undefined ? Number(normalizedMin) : undefined, normalizedMax !== undefined ? Number(normalizedMax) : undefined, attribute.xdefault !== undefined && !attribute.required
409
+ ? attribute.xdefault
410
+ : undefined, attribute.array || false);
262
411
  break;
263
412
  case "boolean":
264
- await db.createBooleanAttribute(dbId, collectionId, attribute.key, attribute.required || false, attribute.xdefault !== undefined && !attribute.required ? attribute.xdefault : undefined, attribute.array || false);
413
+ await db.createBooleanAttribute(dbId, collectionId, attribute.key, attribute.required || false, attribute.xdefault !== undefined && !attribute.required
414
+ ? attribute.xdefault
415
+ : undefined, attribute.array || false);
265
416
  break;
266
417
  case "datetime":
267
- await db.createDatetimeAttribute(dbId, collectionId, attribute.key, attribute.required || false, attribute.xdefault !== undefined && !attribute.required ? attribute.xdefault : undefined, attribute.array || false);
418
+ await db.createDatetimeAttribute(dbId, collectionId, attribute.key, attribute.required || false, attribute.xdefault !== undefined && !attribute.required
419
+ ? attribute.xdefault
420
+ : undefined, attribute.array || false);
268
421
  break;
269
422
  case "email":
270
- await db.createEmailAttribute(dbId, collectionId, attribute.key, attribute.required || false, attribute.xdefault !== undefined && !attribute.required ? attribute.xdefault : undefined, attribute.array || false);
423
+ await db.createEmailAttribute(dbId, collectionId, attribute.key, attribute.required || false, attribute.xdefault !== undefined && !attribute.required
424
+ ? attribute.xdefault
425
+ : undefined, attribute.array || false);
271
426
  break;
272
427
  case "ip":
273
- await db.createIpAttribute(dbId, collectionId, attribute.key, attribute.required || false, attribute.xdefault !== undefined && !attribute.required ? attribute.xdefault : undefined, attribute.array || false);
428
+ await db.createIpAttribute(dbId, collectionId, attribute.key, attribute.required || false, attribute.xdefault !== undefined && !attribute.required
429
+ ? attribute.xdefault
430
+ : undefined, attribute.array || false);
274
431
  break;
275
432
  case "url":
276
- await db.createUrlAttribute(dbId, collectionId, attribute.key, attribute.required || false, attribute.xdefault !== undefined && !attribute.required ? attribute.xdefault : undefined, attribute.array || false);
433
+ await db.createUrlAttribute(dbId, collectionId, attribute.key, attribute.required || false, attribute.xdefault !== undefined && !attribute.required
434
+ ? attribute.xdefault
435
+ : undefined, attribute.array || false);
277
436
  break;
278
437
  case "enum":
279
- await db.createEnumAttribute(dbId, collectionId, attribute.key, attribute.elements || [], attribute.required || false, attribute.xdefault !== undefined && !attribute.required ? attribute.xdefault : undefined, attribute.array || false);
438
+ await db.createEnumAttribute(dbId, collectionId, attribute.key, attribute.elements || [], attribute.required || false, attribute.xdefault !== undefined && !attribute.required
439
+ ? attribute.xdefault
440
+ : undefined, attribute.array || false);
280
441
  break;
281
442
  case "relationship":
282
443
  await db.createRelationshipAttribute(dbId, collectionId, attribute.relatedCollection, attribute.relationType, attribute.twoWay, attribute.key, attribute.twoWayKey, attribute.onDelete);
@@ -285,8 +446,20 @@ const createLegacyAttribute = async (db, dbId, collectionId, attribute) => {
285
446
  const error = new Error(`Unsupported attribute type: ${attribute.type}`);
286
447
  logger.error(`Unsupported attribute type for '${attribute.key}'`, {
287
448
  type: attribute.type,
288
- supportedTypes: ['string', 'integer', 'double', 'float', 'boolean', 'datetime', 'email', 'ip', 'url', 'enum', 'relationship'],
289
- operation: 'createLegacyAttribute'
449
+ supportedTypes: [
450
+ "string",
451
+ "integer",
452
+ "double",
453
+ "float",
454
+ "boolean",
455
+ "datetime",
456
+ "email",
457
+ "ip",
458
+ "url",
459
+ "enum",
460
+ "relationship",
461
+ ],
462
+ operation: "createLegacyAttribute",
290
463
  });
291
464
  throw error;
292
465
  }
@@ -294,42 +467,72 @@ const createLegacyAttribute = async (db, dbId, collectionId, attribute) => {
294
467
  logger.info(`Successfully created legacy attribute '${attribute.key}'`, {
295
468
  type: attribute.type,
296
469
  duration,
297
- operation: 'createLegacyAttribute'
470
+ operation: "createLegacyAttribute",
298
471
  });
299
472
  };
300
473
  /**
301
474
  * Legacy attribute update using type-specific methods
302
475
  */
303
476
  const updateLegacyAttribute = async (db, dbId, collectionId, attribute) => {
477
+ console.log(`DEBUG updateLegacyAttribute before normalizeMinMaxValues:`, {
478
+ key: attribute.key,
479
+ type: attribute.type,
480
+ min: attribute.min,
481
+ max: attribute.max
482
+ });
304
483
  const { min: normalizedMin, max: normalizedMax } = normalizeMinMaxValues(attribute);
305
484
  switch (attribute.type) {
306
485
  case "string":
307
- await db.updateStringAttribute(dbId, collectionId, attribute.key, attribute.required || false, !attribute.required && attribute.xdefault !== undefined ? attribute.xdefault : null, attribute.size);
486
+ await db.updateStringAttribute(dbId, collectionId, attribute.key, attribute.required || false, !attribute.required && attribute.xdefault !== undefined
487
+ ? attribute.xdefault
488
+ : null, attribute.size);
308
489
  break;
309
490
  case "integer":
310
- await db.updateIntegerAttribute(dbId, collectionId, attribute.key, attribute.required || false, !attribute.required && attribute.xdefault !== undefined ? attribute.xdefault : null, normalizedMin !== undefined ? parseInt(String(normalizedMin)) : undefined, normalizedMax !== undefined ? parseInt(String(normalizedMax)) : undefined);
491
+ await db.updateIntegerAttribute(dbId, collectionId, attribute.key, attribute.required || false, !attribute.required && attribute.xdefault !== undefined
492
+ ? attribute.xdefault
493
+ : null, normalizedMin !== undefined
494
+ ? parseInt(String(normalizedMin))
495
+ : undefined, normalizedMax !== undefined
496
+ ? parseInt(String(normalizedMax))
497
+ : undefined);
311
498
  break;
312
499
  case "double":
313
500
  case "float":
314
- await db.updateFloatAttribute(dbId, collectionId, attribute.key, attribute.required || false, normalizedMin !== undefined ? Number(normalizedMin) : undefined, normalizedMax !== undefined ? Number(normalizedMax) : undefined, !attribute.required && attribute.xdefault !== undefined ? attribute.xdefault : null);
501
+ const minParam = normalizedMin !== undefined ? Number(normalizedMin) : undefined;
502
+ const maxParam = normalizedMax !== undefined ? Number(normalizedMax) : undefined;
503
+ await db.updateFloatAttribute(dbId, collectionId, attribute.key, attribute.required || false, minParam, maxParam, !attribute.required && attribute.xdefault !== undefined
504
+ ? attribute.xdefault
505
+ : null);
315
506
  break;
316
507
  case "boolean":
317
- await db.updateBooleanAttribute(dbId, collectionId, attribute.key, attribute.required || false, !attribute.required && attribute.xdefault !== undefined ? attribute.xdefault : null);
508
+ await db.updateBooleanAttribute(dbId, collectionId, attribute.key, attribute.required || false, !attribute.required && attribute.xdefault !== undefined
509
+ ? attribute.xdefault
510
+ : null);
318
511
  break;
319
512
  case "datetime":
320
- await db.updateDatetimeAttribute(dbId, collectionId, attribute.key, attribute.required || false, !attribute.required && attribute.xdefault !== undefined ? attribute.xdefault : null);
513
+ await db.updateDatetimeAttribute(dbId, collectionId, attribute.key, attribute.required || false, !attribute.required && attribute.xdefault !== undefined
514
+ ? attribute.xdefault
515
+ : null);
321
516
  break;
322
517
  case "email":
323
- await db.updateEmailAttribute(dbId, collectionId, attribute.key, attribute.required || false, !attribute.required && attribute.xdefault !== undefined ? attribute.xdefault : null);
518
+ await db.updateEmailAttribute(dbId, collectionId, attribute.key, attribute.required || false, !attribute.required && attribute.xdefault !== undefined
519
+ ? attribute.xdefault
520
+ : null);
324
521
  break;
325
522
  case "ip":
326
- await db.updateIpAttribute(dbId, collectionId, attribute.key, attribute.required || false, !attribute.required && attribute.xdefault !== undefined ? attribute.xdefault : null);
523
+ await db.updateIpAttribute(dbId, collectionId, attribute.key, attribute.required || false, !attribute.required && attribute.xdefault !== undefined
524
+ ? attribute.xdefault
525
+ : null);
327
526
  break;
328
527
  case "url":
329
- await db.updateUrlAttribute(dbId, collectionId, attribute.key, attribute.required || false, !attribute.required && attribute.xdefault !== undefined ? attribute.xdefault : null);
528
+ await db.updateUrlAttribute(dbId, collectionId, attribute.key, attribute.required || false, !attribute.required && attribute.xdefault !== undefined
529
+ ? attribute.xdefault
530
+ : null);
330
531
  break;
331
532
  case "enum":
332
- await db.updateEnumAttribute(dbId, collectionId, attribute.key, attribute.elements || [], attribute.required || false, !attribute.required && attribute.xdefault !== undefined ? attribute.xdefault : null);
533
+ await db.updateEnumAttribute(dbId, collectionId, attribute.key, attribute.elements || [], attribute.required || false, !attribute.required && attribute.xdefault !== undefined
534
+ ? attribute.xdefault
535
+ : null);
333
536
  break;
334
537
  case "relationship":
335
538
  await db.updateRelationshipAttribute(dbId, collectionId, attribute.key, attribute.onDelete);
@@ -351,7 +554,7 @@ retryCount = 0, maxRetries = 5) => {
351
554
  maxWaitTime,
352
555
  retryCount,
353
556
  maxRetries,
354
- operation: 'waitForAttributeAvailable'
557
+ operation: "waitForAttributeAvailable",
355
558
  });
356
559
  // Calculate exponential backoff: 2s, 4s, 8s, 16s, 30s (capped at 30s)
357
560
  if (retryCount > 0) {
@@ -375,7 +578,7 @@ retryCount = 0, maxRetries = 5) => {
375
578
  dbId,
376
579
  collectionId,
377
580
  waitTime: Date.now() - startTime,
378
- operation: 'waitForAttributeAvailable'
581
+ operation: "waitForAttributeAvailable",
379
582
  };
380
583
  switch (attribute.status) {
381
584
  case "available":
@@ -405,13 +608,13 @@ retryCount = 0, maxRetries = 5) => {
405
608
  catch (error) {
406
609
  const errorMessage = error instanceof Error ? error.message : String(error);
407
610
  MessageFormatter.error(`Error checking attribute status: ${errorMessage}`);
408
- logger.error('Error checking attribute status', {
611
+ logger.error("Error checking attribute status", {
409
612
  attributeKey,
410
613
  dbId,
411
614
  collectionId,
412
615
  error: errorMessage,
413
616
  waitTime: Date.now() - startTime,
414
- operation: 'waitForAttributeAvailable'
617
+ operation: "waitForAttributeAvailable",
415
618
  });
416
619
  return false;
417
620
  }
@@ -464,7 +667,7 @@ const deleteAndRecreateCollection = async (db, dbId, collection, retryCount) =>
464
667
  name: collection.name,
465
668
  permissions: collection.$permissions,
466
669
  documentSecurity: collection.documentSecurity,
467
- enabled: collection.enabled
670
+ enabled: collection.enabled,
468
671
  })).data
469
672
  : await db.createCollection(dbId, collection.$id, collection.name, collection.$permissions, collection.documentSecurity, collection.enabled);
470
673
  MessageFormatter.success(`✅ Recreated collection '${collection.name}'`);
@@ -491,7 +694,14 @@ const getComparableFields = (type) => {
491
694
  case "enum":
492
695
  return [...baseFields, "elements"];
493
696
  case "relationship":
494
- return [...baseFields, "relationType", "twoWay", "twoWayKey", "onDelete", "relatedCollection"];
697
+ return [
698
+ ...baseFields,
699
+ "relationType",
700
+ "twoWay",
701
+ "twoWayKey",
702
+ "onDelete",
703
+ "relatedCollection",
704
+ ];
495
705
  case "boolean":
496
706
  case "datetime":
497
707
  case "email":
@@ -501,9 +711,21 @@ const getComparableFields = (type) => {
501
711
  default:
502
712
  // Fallback to all fields for unknown types
503
713
  return [
504
- "key", "type", "array", "encrypted", "required", "size",
505
- "min", "max", "xdefault", "elements", "relationType",
506
- "twoWay", "twoWayKey", "onDelete", "relatedCollection"
714
+ "key",
715
+ "type",
716
+ "array",
717
+ "encrypted",
718
+ "required",
719
+ "size",
720
+ "min",
721
+ "max",
722
+ "xdefault",
723
+ "elements",
724
+ "relationType",
725
+ "twoWay",
726
+ "twoWayKey",
727
+ "onDelete",
728
+ "relatedCollection",
507
729
  ];
508
730
  }
509
731
  };
@@ -513,8 +735,16 @@ const attributesSame = (databaseAttribute, configAttribute) => {
513
735
  const normalizedConfigAttr = normalizeAttributeForComparison(configAttribute);
514
736
  // Use type-specific field list to avoid false positives from irrelevant fields
515
737
  const attributesToCheck = getComparableFields(normalizedConfigAttr.type);
738
+ const fieldsToCheck = attributesToCheck.filter((attr) => {
739
+ if (attr !== "xdefault") {
740
+ return true;
741
+ }
742
+ const dbRequired = Boolean(normalizedDbAttr.required);
743
+ const configRequired = Boolean(normalizedConfigAttr.required);
744
+ return !(dbRequired || configRequired);
745
+ });
516
746
  const differences = [];
517
- const result = attributesToCheck.every((attr) => {
747
+ const result = fieldsToCheck.every((attr) => {
518
748
  // Check if both objects have the attribute
519
749
  const dbHasAttr = attr in normalizedDbAttr;
520
750
  const configHasAttr = attr in normalizedConfigAttr;
@@ -536,8 +766,14 @@ const attributesSame = (databaseAttribute, configAttribute) => {
536
766
  return boolMatch;
537
767
  }
538
768
  // For numeric comparisons, compare numbers if both are numeric-like
539
- if ((typeof dbValue === "number" || (typeof dbValue === "string" && dbValue !== "" && !isNaN(Number(dbValue)))) &&
540
- (typeof configValue === "number" || (typeof configValue === "string" && configValue !== "" && !isNaN(Number(configValue))))) {
769
+ if ((typeof dbValue === "number" ||
770
+ (typeof dbValue === "string" &&
771
+ dbValue !== "" &&
772
+ !isNaN(Number(dbValue)))) &&
773
+ (typeof configValue === "number" ||
774
+ (typeof configValue === "string" &&
775
+ configValue !== "" &&
776
+ !isNaN(Number(configValue))))) {
541
777
  const numMatch = Number(dbValue) === Number(configValue);
542
778
  if (!numMatch) {
543
779
  differences.push(`${attr}: db=${dbValue} config=${configValue}`);
@@ -600,18 +836,12 @@ const attributesSame = (databaseAttribute, configAttribute) => {
600
836
  differences.push(`${attr}: unexpected comparison state`);
601
837
  return false;
602
838
  });
603
- // Log differences if any were found
604
- if (differences.length > 0) {
605
- logger.debug(`Attribute '${normalizedDbAttr.key}' comparison found differences:`, {
606
- differences,
607
- operation: 'attributesSame'
608
- });
609
- }
610
- // Log differences if comparison failed (for debugging)
611
839
  if (!result && differences.length > 0) {
612
- MessageFormatter.debug(`Attribute '${configAttribute.key}' differences detected:`, { prefix: "Attributes" });
613
- differences.forEach(diff => {
614
- MessageFormatter.debug(` ${diff}`, { prefix: "Attributes" });
840
+ logger.debug(`Attribute mismatch detected for '${normalizedConfigAttr.key}'`, {
841
+ differences,
842
+ dbAttribute: normalizedDbAttr,
843
+ configAttribute: normalizedConfigAttr,
844
+ operation: "attributesSame",
615
845
  });
616
846
  }
617
847
  return result;
@@ -646,7 +876,11 @@ export const createOrUpdateAttributeWithStatusCheck = async (db, dbId, collectio
646
876
  // Try to delete the specific stuck attribute instead of the entire collection
647
877
  try {
648
878
  if (isDatabaseAdapter(db)) {
649
- await db.deleteAttribute({ databaseId: dbId, tableId: collection.$id, key: attribute.key });
879
+ await db.deleteAttribute({
880
+ databaseId: dbId,
881
+ tableId: collection.$id,
882
+ key: attribute.key,
883
+ });
650
884
  }
651
885
  else {
652
886
  await db.deleteAttribute(dbId, collection.$id, attribute.key);
@@ -656,7 +890,8 @@ export const createOrUpdateAttributeWithStatusCheck = async (db, dbId, collectio
656
890
  await delay(3000);
657
891
  // Get fresh collection data
658
892
  const freshCollection = isDatabaseAdapter(db)
659
- ? (await db.getTable({ databaseId: dbId, tableId: collection.$id })).data
893
+ ? (await db.getTable({ databaseId: dbId, tableId: collection.$id }))
894
+ .data
660
895
  : await db.getCollection(dbId, collection.$id);
661
896
  // Retry with the same collection (attribute should be gone now)
662
897
  return await createOrUpdateAttributeWithStatusCheck(db, dbId, freshCollection, attribute, retryCount + 1, maxRetries);
@@ -668,7 +903,8 @@ export const createOrUpdateAttributeWithStatusCheck = async (db, dbId, collectio
668
903
  MessageFormatter.info(chalk.yellow(`Last resort: Recreating collection for attribute '${attribute.key}'`));
669
904
  // Get fresh collection data
670
905
  const freshCollection = isDatabaseAdapter(db)
671
- ? (await db.getTable({ databaseId: dbId, tableId: collection.$id })).data
906
+ ? (await db.getTable({ databaseId: dbId, tableId: collection.$id }))
907
+ .data
672
908
  : await db.getCollection(dbId, collection.$id);
673
909
  // Delete and recreate collection
674
910
  const newCollection = await deleteAndRecreateCollection(db, dbId, freshCollection, retryCount + 1);
@@ -709,6 +945,30 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
709
945
  catch (error) {
710
946
  foundAttribute = undefined;
711
947
  }
948
+ // If attribute exists but type changed, delete it so we can recreate with new type
949
+ if (foundAttribute &&
950
+ foundAttribute.type !== attribute.type) {
951
+ MessageFormatter.info(chalk.yellow(`Attribute '${attribute.key}' type changed from '${foundAttribute.type}' to '${attribute.type}'. Recreating attribute.`));
952
+ try {
953
+ if (isDatabaseAdapter(db)) {
954
+ await db.deleteAttribute({
955
+ databaseId: dbId,
956
+ tableId: collection.$id,
957
+ key: attribute.key
958
+ });
959
+ }
960
+ else {
961
+ await db.deleteAttribute(dbId, collection.$id, attribute.key);
962
+ }
963
+ // Remove from local collection metadata so downstream logic treats it as new
964
+ collection.attributes = collection.attributes.filter((attr) => attr.key !== attribute.key);
965
+ foundAttribute = undefined;
966
+ }
967
+ catch (deleteError) {
968
+ MessageFormatter.error(`Failed to delete attribute '${attribute.key}' before recreation: ${deleteError}`);
969
+ return "error";
970
+ }
971
+ }
712
972
  if (foundAttribute &&
713
973
  attributesSame(foundAttribute, attribute) &&
714
974
  updateEnabled) {
@@ -721,17 +981,46 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
721
981
  // MessageFormatter.info(
722
982
  // `Updating attribute with same key ${attribute.key} but different values`
723
983
  // );
984
+ // DEBUG: Log before object merge to detect corruption
985
+ if ((attribute.key === 'conversationType' || attribute.key === 'messageStreakCount')) {
986
+ console.log(`[DEBUG] MERGE - key="${attribute.key}"`, {
987
+ found: {
988
+ elements: foundAttribute?.elements,
989
+ min: foundAttribute?.min,
990
+ max: foundAttribute?.max
991
+ },
992
+ desired: {
993
+ elements: attribute?.elements,
994
+ min: attribute?.min,
995
+ max: attribute?.max
996
+ }
997
+ });
998
+ }
724
999
  finalAttribute = {
725
1000
  ...foundAttribute,
726
1001
  ...attribute,
727
1002
  };
1003
+ // DEBUG: Log after object merge to detect corruption
1004
+ if ((finalAttribute.key === 'conversationType' || finalAttribute.key === 'messageStreakCount')) {
1005
+ console.log(`[DEBUG] AFTER_MERGE - key="${finalAttribute.key}"`, {
1006
+ merged: {
1007
+ elements: finalAttribute?.elements,
1008
+ min: finalAttribute?.min,
1009
+ max: finalAttribute?.max
1010
+ }
1011
+ });
1012
+ }
728
1013
  action = "update";
729
1014
  }
730
1015
  else if (!updateEnabled &&
731
1016
  foundAttribute &&
732
1017
  !attributesSame(foundAttribute, attribute)) {
733
1018
  if (isDatabaseAdapter(db)) {
734
- await db.deleteAttribute({ databaseId: dbId, tableId: collection.$id, key: attribute.key });
1019
+ await db.deleteAttribute({
1020
+ databaseId: dbId,
1021
+ tableId: collection.$id,
1022
+ key: attribute.key,
1023
+ });
735
1024
  }
736
1025
  else {
737
1026
  await db.deleteAttribute(dbId, collection.$id, attribute.key);
@@ -747,7 +1036,10 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
747
1036
  // First try treating relatedCollection as an ID directly
748
1037
  try {
749
1038
  const byIdCollection = isDatabaseAdapter(db)
750
- ? (await db.getTable({ databaseId: dbId, tableId: finalAttribute.relatedCollection })).data
1039
+ ? (await db.getTable({
1040
+ databaseId: dbId,
1041
+ tableId: finalAttribute.relatedCollection,
1042
+ })).data
751
1043
  : await db.getCollection(dbId, finalAttribute.relatedCollection);
752
1044
  collectionFoundViaRelatedCollection = byIdCollection;
753
1045
  relatedCollectionId = byIdCollection.$id;
@@ -757,11 +1049,15 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
757
1049
  catch (_) {
758
1050
  // Not an ID or not found — fall back to name-based resolution below
759
1051
  }
760
- if (!collectionFoundViaRelatedCollection && nameToIdMapping.has(finalAttribute.relatedCollection)) {
1052
+ if (!collectionFoundViaRelatedCollection &&
1053
+ nameToIdMapping.has(finalAttribute.relatedCollection)) {
761
1054
  relatedCollectionId = nameToIdMapping.get(finalAttribute.relatedCollection);
762
1055
  try {
763
1056
  collectionFoundViaRelatedCollection = isDatabaseAdapter(db)
764
- ? (await db.getTable({ databaseId: dbId, tableId: relatedCollectionId })).data
1057
+ ? (await db.getTable({
1058
+ databaseId: dbId,
1059
+ tableId: relatedCollectionId,
1060
+ })).data
765
1061
  : await db.getCollection(dbId, relatedCollectionId);
766
1062
  }
767
1063
  catch (e) {
@@ -773,8 +1069,13 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
773
1069
  }
774
1070
  else if (!collectionFoundViaRelatedCollection) {
775
1071
  const collectionsPulled = isDatabaseAdapter(db)
776
- ? await db.listTables({ databaseId: dbId, queries: [Query.equal("name", finalAttribute.relatedCollection)] })
777
- : await db.listCollections(dbId, [Query.equal("name", finalAttribute.relatedCollection)]);
1072
+ ? await db.listTables({
1073
+ databaseId: dbId,
1074
+ queries: [Query.equal("name", finalAttribute.relatedCollection)],
1075
+ })
1076
+ : await db.listCollections(dbId, [
1077
+ Query.equal("name", finalAttribute.relatedCollection),
1078
+ ]);
778
1079
  if (collectionsPulled.total && collectionsPulled.total > 0) {
779
1080
  collectionFoundViaRelatedCollection = isDatabaseAdapter(db)
780
1081
  ? collectionsPulled.tables?.[0]
@@ -808,8 +1109,9 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
808
1109
  catch (error) {
809
1110
  // Collection doesn't exist - create it
810
1111
  if (error.code === 404 ||
811
- (error instanceof Error && (error.message.includes('collection_not_found') ||
812
- error.message.includes('Collection with the requested ID could not be found')))) {
1112
+ (error instanceof Error &&
1113
+ (error.message.includes("collection_not_found") ||
1114
+ error.message.includes("Collection with the requested ID could not be found")))) {
813
1115
  MessageFormatter.info(`Collection '${collection.name}' doesn't exist, creating it first...`);
814
1116
  try {
815
1117
  if (isDatabaseAdapter(db)) {
@@ -819,7 +1121,7 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
819
1121
  name: collection.name,
820
1122
  permissions: collection.$permissions || [],
821
1123
  documentSecurity: collection.documentSecurity ?? false,
822
- enabled: collection.enabled ?? true
1124
+ enabled: collection.enabled ?? true,
823
1125
  });
824
1126
  }
825
1127
  else {
@@ -829,7 +1131,9 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
829
1131
  await delay(500); // Wait for collection to be ready
830
1132
  }
831
1133
  catch (createError) {
832
- MessageFormatter.error(`Failed to create collection '${collection.name}'`, createError instanceof Error ? createError : new Error(String(createError)));
1134
+ MessageFormatter.error(`Failed to create collection '${collection.name}'`, createError instanceof Error
1135
+ ? createError
1136
+ : new Error(String(createError)));
833
1137
  return "error";
834
1138
  }
835
1139
  }
@@ -843,6 +1147,10 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
843
1147
  await tryAwaitWithRetry(async () => await createAttributeViaAdapter(db, dbId, collection.$id, finalAttribute));
844
1148
  }
845
1149
  else {
1150
+ console.log(`Updating attribute '${finalAttribute.key}'...`);
1151
+ if (finalAttribute.type === "double" || finalAttribute.type === "integer") {
1152
+ console.log("finalAttribute:", finalAttribute);
1153
+ }
846
1154
  await tryAwaitWithRetry(async () => await updateAttributeViaAdapter(db, dbId, collection.$id, finalAttribute));
847
1155
  }
848
1156
  return "processed";
@@ -851,9 +1159,7 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
851
1159
  * Enhanced collection attribute creation with proper status monitoring
852
1160
  */
853
1161
  export const createUpdateCollectionAttributesWithStatusCheck = async (db, dbId, collection, attributes) => {
854
- const existingAttributes =
855
- // @ts-expect-error
856
- collection.attributes.map((attr) => parseAttribute(attr)) || [];
1162
+ const existingAttributes = collection.attributes.map((attr) => parseAttribute(attr)) || [];
857
1163
  const attributesToRemove = existingAttributes.filter((attr) => !attributes.some((a) => a.key === attr.key));
858
1164
  const indexesToRemove = collection.indexes.filter((index) => attributesToRemove.some((attr) => index.attributes.includes(attr.key)));
859
1165
  // Handle attribute removal first
@@ -865,7 +1171,11 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (db, dbId,
865
1171
  for (const index of indexesToRemove) {
866
1172
  await tryAwaitWithRetry(async () => {
867
1173
  if (isDatabaseAdapter(db)) {
868
- await db.deleteIndex({ databaseId: dbId, tableId: collection.$id, key: index.key });
1174
+ await db.deleteIndex({
1175
+ databaseId: dbId,
1176
+ tableId: collection.$id,
1177
+ key: index.key,
1178
+ });
869
1179
  }
870
1180
  else {
871
1181
  await db.deleteIndex(dbId, collection.$id, index.key);
@@ -878,7 +1188,11 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (db, dbId,
878
1188
  MessageFormatter.info(chalk.red(`Removing attribute: ${attr.key} as it is no longer in the collection`));
879
1189
  await tryAwaitWithRetry(async () => {
880
1190
  if (isDatabaseAdapter(db)) {
881
- await db.deleteAttribute({ databaseId: dbId, tableId: collection.$id, key: attr.key });
1191
+ await db.deleteAttribute({
1192
+ databaseId: dbId,
1193
+ tableId: collection.$id,
1194
+ key: attr.key,
1195
+ });
882
1196
  }
883
1197
  else {
884
1198
  await db.deleteAttribute(dbId, collection.$id, attr.key);
@@ -899,9 +1213,7 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (db, dbId,
899
1213
  }
900
1214
  const existingAttributesMap = new Map();
901
1215
  try {
902
- const parsedAttributes = currentCollection.attributes.map((attr) =>
903
- // @ts-expect-error
904
- parseAttribute(attr));
1216
+ const parsedAttributes = currentCollection.attributes.map((attr) => parseAttribute(attr));
905
1217
  parsedAttributes.forEach((attr) => existingAttributesMap.set(attr.key, attr));
906
1218
  }
907
1219
  catch (error) {
@@ -946,7 +1258,10 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (db, dbId,
946
1258
  try {
947
1259
  currentCollection = isDatabaseAdapter(db)
948
1260
  ? (await db.getTable({ databaseId: dbId, tableId: collection.$id })).data
949
- : await db.getCollection(dbId, collection.$id);
1261
+ : await db.getCollection({
1262
+ databaseId: dbId,
1263
+ collectionId: collection.$id,
1264
+ });
950
1265
  }
951
1266
  catch (error) {
952
1267
  MessageFormatter.info(chalk.yellow(`Warning: Could not refresh collection data: ${error}`));
@@ -969,7 +1284,8 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (db, dbId,
969
1284
  // Refresh collection data before retry
970
1285
  try {
971
1286
  currentCollection = isDatabaseAdapter(db)
972
- ? (await db.getTable({ databaseId: dbId, tableId: collection.$id })).data
1287
+ ? (await db.getTable({ databaseId: dbId, tableId: collection.$id }))
1288
+ .data
973
1289
  : await db.getCollection(dbId, collection.$id);
974
1290
  }
975
1291
  catch (error) {
@@ -990,9 +1306,7 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (db, dbId,
990
1306
  };
991
1307
  export const createUpdateCollectionAttributes = async (db, dbId, collection, attributes) => {
992
1308
  MessageFormatter.info(chalk.green(`Creating/Updating attributes for collection: ${collection.name}`));
993
- const existingAttributes =
994
- // @ts-expect-error
995
- collection.attributes.map((attr) => parseAttribute(attr)) || [];
1309
+ const existingAttributes = collection.attributes.map((attr) => parseAttribute(attr)) || [];
996
1310
  const attributesToRemove = existingAttributes.filter((attr) => !attributes.some((a) => a.key === attr.key));
997
1311
  const indexesToRemove = collection.indexes.filter((index) => attributesToRemove.some((attr) => index.attributes.includes(attr.key)));
998
1312
  if (attributesToRemove.length > 0) {
@@ -1003,7 +1317,11 @@ export const createUpdateCollectionAttributes = async (db, dbId, collection, att
1003
1317
  for (const index of indexesToRemove) {
1004
1318
  await tryAwaitWithRetry(async () => {
1005
1319
  if (isDatabaseAdapter(db)) {
1006
- await db.deleteIndex({ databaseId: dbId, tableId: collection.$id, key: index.key });
1320
+ await db.deleteIndex({
1321
+ databaseId: dbId,
1322
+ tableId: collection.$id,
1323
+ key: index.key,
1324
+ });
1007
1325
  }
1008
1326
  else {
1009
1327
  await db.deleteIndex(dbId, collection.$id, index.key);
@@ -1016,7 +1334,11 @@ export const createUpdateCollectionAttributes = async (db, dbId, collection, att
1016
1334
  MessageFormatter.info(chalk.red(`Removing attribute: ${attr.key} as it is no longer in the collection`));
1017
1335
  await tryAwaitWithRetry(async () => {
1018
1336
  if (isDatabaseAdapter(db)) {
1019
- await db.deleteAttribute({ databaseId: dbId, tableId: collection.$id, key: attr.key });
1337
+ await db.deleteAttribute({
1338
+ databaseId: dbId,
1339
+ tableId: collection.$id,
1340
+ key: attr.key,
1341
+ });
1020
1342
  }
1021
1343
  else {
1022
1344
  await db.deleteAttribute(dbId, collection.$id, attr.key);