appwrite-utils-cli 1.5.2 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +199 -0
- package/README.md +251 -29
- package/dist/adapters/AdapterFactory.d.ts +10 -3
- package/dist/adapters/AdapterFactory.js +213 -17
- package/dist/adapters/TablesDBAdapter.js +60 -17
- package/dist/backups/operations/bucketBackup.d.ts +19 -0
- package/dist/backups/operations/bucketBackup.js +197 -0
- package/dist/backups/operations/collectionBackup.d.ts +30 -0
- package/dist/backups/operations/collectionBackup.js +201 -0
- package/dist/backups/operations/comprehensiveBackup.d.ts +25 -0
- package/dist/backups/operations/comprehensiveBackup.js +238 -0
- package/dist/backups/schemas/bucketManifest.d.ts +93 -0
- package/dist/backups/schemas/bucketManifest.js +33 -0
- package/dist/backups/schemas/comprehensiveManifest.d.ts +108 -0
- package/dist/backups/schemas/comprehensiveManifest.js +32 -0
- package/dist/backups/tracking/centralizedTracking.d.ts +34 -0
- package/dist/backups/tracking/centralizedTracking.js +274 -0
- package/dist/cli/commands/configCommands.d.ts +8 -0
- package/dist/cli/commands/configCommands.js +160 -0
- package/dist/cli/commands/databaseCommands.d.ts +13 -0
- package/dist/cli/commands/databaseCommands.js +479 -0
- package/dist/cli/commands/functionCommands.d.ts +7 -0
- package/dist/cli/commands/functionCommands.js +289 -0
- package/dist/cli/commands/schemaCommands.d.ts +7 -0
- package/dist/cli/commands/schemaCommands.js +134 -0
- package/dist/cli/commands/transferCommands.d.ts +5 -0
- package/dist/cli/commands/transferCommands.js +384 -0
- package/dist/collections/attributes.d.ts +5 -4
- package/dist/collections/attributes.js +539 -246
- package/dist/collections/indexes.js +39 -37
- package/dist/collections/methods.d.ts +2 -16
- package/dist/collections/methods.js +90 -538
- package/dist/collections/transferOperations.d.ts +7 -0
- package/dist/collections/transferOperations.js +331 -0
- package/dist/collections/wipeOperations.d.ts +16 -0
- package/dist/collections/wipeOperations.js +328 -0
- package/dist/config/configMigration.d.ts +87 -0
- package/dist/config/configMigration.js +390 -0
- package/dist/config/configValidation.d.ts +66 -0
- package/dist/config/configValidation.js +358 -0
- package/dist/config/yamlConfig.d.ts +455 -1
- package/dist/config/yamlConfig.js +145 -52
- package/dist/databases/methods.js +3 -2
- package/dist/databases/setup.d.ts +1 -2
- package/dist/databases/setup.js +9 -87
- package/dist/examples/yamlTerminologyExample.d.ts +42 -0
- package/dist/examples/yamlTerminologyExample.js +269 -0
- package/dist/functions/deployments.js +11 -10
- package/dist/functions/methods.d.ts +1 -1
- package/dist/functions/methods.js +5 -4
- package/dist/init.js +9 -9
- package/dist/interactiveCLI.d.ts +8 -17
- package/dist/interactiveCLI.js +209 -1172
- package/dist/main.js +364 -21
- package/dist/migrations/afterImportActions.js +22 -30
- package/dist/migrations/appwriteToX.js +71 -25
- package/dist/migrations/dataLoader.js +35 -26
- package/dist/migrations/importController.js +29 -30
- package/dist/migrations/relationships.js +13 -12
- package/dist/migrations/services/ImportOrchestrator.js +16 -19
- package/dist/migrations/transfer.js +46 -46
- package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +3 -1
- package/dist/migrations/yaml/YamlImportConfigLoader.js +6 -3
- package/dist/migrations/yaml/YamlImportIntegration.d.ts +9 -3
- package/dist/migrations/yaml/YamlImportIntegration.js +22 -11
- package/dist/migrations/yaml/generateImportSchemas.d.ts +14 -1
- package/dist/migrations/yaml/generateImportSchemas.js +736 -7
- package/dist/schemas/authUser.d.ts +1 -1
- package/dist/setupController.js +3 -2
- package/dist/shared/backupMetadataSchema.d.ts +94 -0
- package/dist/shared/backupMetadataSchema.js +38 -0
- package/dist/shared/backupTracking.d.ts +18 -0
- package/dist/shared/backupTracking.js +176 -0
- package/dist/shared/confirmationDialogs.js +15 -15
- package/dist/shared/errorUtils.d.ts +54 -0
- package/dist/shared/errorUtils.js +95 -0
- package/dist/shared/functionManager.js +20 -19
- package/dist/shared/indexManager.js +12 -11
- package/dist/shared/jsonSchemaGenerator.js +10 -26
- package/dist/shared/logging.d.ts +51 -0
- package/dist/shared/logging.js +70 -0
- package/dist/shared/messageFormatter.d.ts +2 -0
- package/dist/shared/messageFormatter.js +10 -0
- package/dist/shared/migrationHelpers.d.ts +6 -16
- package/dist/shared/migrationHelpers.js +24 -21
- package/dist/shared/operationLogger.d.ts +8 -1
- package/dist/shared/operationLogger.js +11 -24
- package/dist/shared/operationQueue.d.ts +28 -1
- package/dist/shared/operationQueue.js +268 -66
- package/dist/shared/operationsTable.d.ts +26 -0
- package/dist/shared/operationsTable.js +286 -0
- package/dist/shared/operationsTableSchema.d.ts +48 -0
- package/dist/shared/operationsTableSchema.js +35 -0
- package/dist/shared/relationshipExtractor.d.ts +56 -0
- package/dist/shared/relationshipExtractor.js +138 -0
- package/dist/shared/schemaGenerator.d.ts +19 -1
- package/dist/shared/schemaGenerator.js +56 -75
- package/dist/storage/backupCompression.d.ts +20 -0
- package/dist/storage/backupCompression.js +67 -0
- package/dist/storage/methods.d.ts +16 -2
- package/dist/storage/methods.js +98 -14
- package/dist/users/methods.js +9 -8
- package/dist/utils/configDiscovery.d.ts +78 -0
- package/dist/utils/configDiscovery.js +430 -0
- package/dist/utils/directoryUtils.d.ts +22 -0
- package/dist/utils/directoryUtils.js +59 -0
- package/dist/utils/getClientFromConfig.d.ts +17 -8
- package/dist/utils/getClientFromConfig.js +162 -17
- package/dist/utils/helperFunctions.d.ts +16 -2
- package/dist/utils/helperFunctions.js +19 -5
- package/dist/utils/loadConfigs.d.ts +34 -9
- package/dist/utils/loadConfigs.js +236 -316
- package/dist/utils/pathResolvers.d.ts +53 -0
- package/dist/utils/pathResolvers.js +72 -0
- package/dist/utils/projectConfig.d.ts +119 -0
- package/dist/utils/projectConfig.js +171 -0
- package/dist/utils/retryFailedPromises.js +4 -2
- package/dist/utils/sessionAuth.d.ts +48 -0
- package/dist/utils/sessionAuth.js +164 -0
- package/dist/utils/sessionPreservationExample.d.ts +1666 -0
- package/dist/utils/sessionPreservationExample.js +101 -0
- package/dist/utils/setupFiles.js +301 -41
- package/dist/utils/typeGuards.d.ts +35 -0
- package/dist/utils/typeGuards.js +57 -0
- package/dist/utils/versionDetection.js +145 -9
- package/dist/utils/yamlConverter.d.ts +53 -3
- package/dist/utils/yamlConverter.js +232 -13
- package/dist/utils/yamlLoader.d.ts +70 -0
- package/dist/utils/yamlLoader.js +263 -0
- package/dist/utilsController.d.ts +36 -3
- package/dist/utilsController.js +186 -56
- package/package.json +12 -2
- package/src/adapters/AdapterFactory.ts +263 -35
- package/src/adapters/TablesDBAdapter.ts +225 -36
- package/src/backups/operations/bucketBackup.ts +277 -0
- package/src/backups/operations/collectionBackup.ts +310 -0
- package/src/backups/operations/comprehensiveBackup.ts +342 -0
- package/src/backups/schemas/bucketManifest.ts +78 -0
- package/src/backups/schemas/comprehensiveManifest.ts +76 -0
- package/src/backups/tracking/centralizedTracking.ts +352 -0
- package/src/cli/commands/configCommands.ts +194 -0
- package/src/cli/commands/databaseCommands.ts +635 -0
- package/src/cli/commands/functionCommands.ts +379 -0
- package/src/cli/commands/schemaCommands.ts +163 -0
- package/src/cli/commands/transferCommands.ts +457 -0
- package/src/collections/attributes.ts +900 -621
- package/src/collections/attributes.ts.backup +1555 -0
- package/src/collections/indexes.ts +116 -114
- package/src/collections/methods.ts +295 -968
- package/src/collections/transferOperations.ts +516 -0
- package/src/collections/wipeOperations.ts +501 -0
- package/src/config/README.md +274 -0
- package/src/config/configMigration.ts +575 -0
- package/src/config/configValidation.ts +445 -0
- package/src/config/yamlConfig.ts +168 -55
- package/src/databases/methods.ts +3 -2
- package/src/databases/setup.ts +11 -138
- package/src/examples/yamlTerminologyExample.ts +341 -0
- package/src/functions/deployments.ts +14 -12
- package/src/functions/methods.ts +11 -11
- package/src/functions/templates/hono-typescript/README.md +286 -0
- package/src/functions/templates/hono-typescript/package.json +26 -0
- package/src/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
- package/src/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
- package/src/functions/templates/hono-typescript/src/app.ts +180 -0
- package/src/functions/templates/hono-typescript/src/context.ts +103 -0
- package/src/functions/templates/hono-typescript/src/index.ts +54 -0
- package/src/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
- package/src/functions/templates/hono-typescript/tsconfig.json +20 -0
- package/src/functions/templates/typescript-node/package.json +2 -1
- package/src/functions/templates/typescript-node/src/context.ts +103 -0
- package/src/functions/templates/typescript-node/src/index.ts +18 -12
- package/src/functions/templates/uv/pyproject.toml +1 -0
- package/src/functions/templates/uv/src/context.py +125 -0
- package/src/functions/templates/uv/src/index.py +35 -5
- package/src/init.ts +9 -11
- package/src/interactiveCLI.ts +274 -1563
- package/src/main.ts +418 -24
- package/src/migrations/afterImportActions.ts +71 -44
- package/src/migrations/appwriteToX.ts +100 -34
- package/src/migrations/dataLoader.ts +48 -34
- package/src/migrations/importController.ts +44 -39
- package/src/migrations/relationships.ts +28 -18
- package/src/migrations/services/ImportOrchestrator.ts +24 -27
- package/src/migrations/transfer.ts +159 -121
- package/src/migrations/yaml/YamlImportConfigLoader.ts +11 -4
- package/src/migrations/yaml/YamlImportIntegration.ts +47 -20
- package/src/migrations/yaml/generateImportSchemas.ts +751 -12
- package/src/setupController.ts +3 -2
- package/src/shared/backupMetadataSchema.ts +93 -0
- package/src/shared/backupTracking.ts +211 -0
- package/src/shared/confirmationDialogs.ts +19 -19
- package/src/shared/errorUtils.ts +110 -0
- package/src/shared/functionManager.ts +21 -20
- package/src/shared/indexManager.ts +12 -11
- package/src/shared/jsonSchemaGenerator.ts +38 -52
- package/src/shared/logging.ts +75 -0
- package/src/shared/messageFormatter.ts +14 -1
- package/src/shared/migrationHelpers.ts +45 -38
- package/src/shared/operationLogger.ts +11 -36
- package/src/shared/operationQueue.ts +322 -93
- package/src/shared/operationsTable.ts +338 -0
- package/src/shared/operationsTableSchema.ts +60 -0
- package/src/shared/relationshipExtractor.ts +214 -0
- package/src/shared/schemaGenerator.ts +179 -219
- package/src/storage/backupCompression.ts +88 -0
- package/src/storage/methods.ts +131 -34
- package/src/users/methods.ts +11 -9
- package/src/utils/configDiscovery.ts +502 -0
- package/src/utils/directoryUtils.ts +61 -0
- package/src/utils/getClientFromConfig.ts +205 -22
- package/src/utils/helperFunctions.ts +23 -5
- package/src/utils/loadConfigs.ts +313 -345
- package/src/utils/pathResolvers.ts +81 -0
- package/src/utils/projectConfig.ts +299 -0
- package/src/utils/retryFailedPromises.ts +4 -2
- package/src/utils/sessionAuth.ts +230 -0
- package/src/utils/setupFiles.ts +322 -54
- package/src/utils/typeGuards.ts +65 -0
- package/src/utils/versionDetection.ts +218 -64
- package/src/utils/yamlConverter.ts +296 -13
- package/src/utils/yamlLoader.ts +364 -0
- package/src/utilsController.ts +314 -110
- package/tests/README.md +497 -0
- package/tests/adapters/AdapterFactory.test.ts +277 -0
- package/tests/integration/syncOperations.test.ts +463 -0
- package/tests/jest.config.js +25 -0
- package/tests/migration/configMigration.test.ts +546 -0
- package/tests/setup.ts +62 -0
- package/tests/testUtils.ts +340 -0
- package/tests/utils/loadConfigs.test.ts +350 -0
- package/tests/validation/configValidation.test.ts +412 -0
- package/src/utils/schemaStrings.ts +0 -517
@@ -4,9 +4,530 @@ import {
|
|
4
4
|
parseAttribute,
|
5
5
|
type Attribute,
|
6
6
|
} from "appwrite-utils";
|
7
|
-
import {
|
8
|
-
|
7
|
+
import {
|
8
|
+
nameToIdMapping,
|
9
|
+
enqueueOperation,
|
10
|
+
markAttributeProcessed,
|
11
|
+
isAttributeProcessed
|
12
|
+
} from "../shared/operationQueue.js";
|
13
|
+
import { delay, tryAwaitWithRetry, calculateExponentialBackoff } from "../utils/helperFunctions.js";
|
9
14
|
import chalk from "chalk";
|
15
|
+
import type { DatabaseAdapter, CreateAttributeParams, UpdateAttributeParams, DeleteAttributeParams } from "../adapters/DatabaseAdapter.js";
|
16
|
+
import { logger } from "../shared/logging.js";
|
17
|
+
import { MessageFormatter } from "../shared/messageFormatter.js";
|
18
|
+
import { isDatabaseAdapter } from "../utils/typeGuards.js";
|
19
|
+
|
20
|
+
// Threshold for treating min/max values as undefined (10 billion)
|
21
|
+
const MIN_MAX_THRESHOLD = 10_000_000_000;
|
22
|
+
|
23
|
+
// Extreme values that Appwrite may return, which should be treated as undefined
|
24
|
+
const EXTREME_MIN_INTEGER = -9223372036854776000;
|
25
|
+
const EXTREME_MAX_INTEGER = 9223372036854776000;
|
26
|
+
const EXTREME_MIN_FLOAT = -1.7976931348623157e+308;
|
27
|
+
const EXTREME_MAX_FLOAT = 1.7976931348623157e+308;
|
28
|
+
|
29
|
+
/**
|
30
|
+
* Type guard to check if an attribute has min/max properties
|
31
|
+
*/
|
32
|
+
const hasMinMaxProperties = (attribute: Attribute): attribute is Attribute & { min?: number; max?: number } => {
|
33
|
+
return attribute.type === 'integer' || attribute.type === 'double' || attribute.type === 'float';
|
34
|
+
};
|
35
|
+
|
36
|
+
/**
|
37
|
+
* Normalizes min/max values for integer and float attributes
|
38
|
+
* Sets values to undefined if they exceed the threshold or are extreme values from database
|
39
|
+
*/
|
40
|
+
const normalizeMinMaxValues = (attribute: Attribute): { min?: number; max?: number } => {
|
41
|
+
if (!hasMinMaxProperties(attribute)) {
|
42
|
+
logger.debug(`Attribute '${attribute.key}' does not have min/max properties`, {
|
43
|
+
type: attribute.type,
|
44
|
+
operation: 'normalizeMinMaxValues'
|
45
|
+
});
|
46
|
+
return {};
|
47
|
+
}
|
48
|
+
|
49
|
+
const { type, min, max } = attribute;
|
50
|
+
let normalizedMin = min;
|
51
|
+
let normalizedMax = max;
|
52
|
+
|
53
|
+
logger.debug(`Normalizing min/max values for attribute '${attribute.key}'`, {
|
54
|
+
type,
|
55
|
+
originalMin: min,
|
56
|
+
originalMax: max,
|
57
|
+
operation: 'normalizeMinMaxValues'
|
58
|
+
});
|
59
|
+
|
60
|
+
// Handle min value
|
61
|
+
if (normalizedMin !== undefined && normalizedMin !== null) {
|
62
|
+
const minValue = Number(normalizedMin);
|
63
|
+
const originalMin = normalizedMin;
|
64
|
+
|
65
|
+
// Check if it exceeds threshold or is an extreme database value
|
66
|
+
if (type === 'integer') {
|
67
|
+
if (Math.abs(minValue) >= MIN_MAX_THRESHOLD || minValue === EXTREME_MIN_INTEGER) {
|
68
|
+
logger.debug(`Min value normalized to undefined for attribute '${attribute.key}'`, {
|
69
|
+
type,
|
70
|
+
originalValue: originalMin,
|
71
|
+
numericValue: minValue,
|
72
|
+
reason: Math.abs(minValue) >= MIN_MAX_THRESHOLD ? 'exceeds_threshold' : 'extreme_database_value',
|
73
|
+
threshold: MIN_MAX_THRESHOLD,
|
74
|
+
extremeValue: EXTREME_MIN_INTEGER,
|
75
|
+
operation: 'normalizeMinMaxValues'
|
76
|
+
});
|
77
|
+
normalizedMin = undefined;
|
78
|
+
}
|
79
|
+
} else { // float/double
|
80
|
+
if (Math.abs(minValue) >= MIN_MAX_THRESHOLD || minValue === EXTREME_MIN_FLOAT) {
|
81
|
+
logger.debug(`Min value normalized to undefined for attribute '${attribute.key}'`, {
|
82
|
+
type,
|
83
|
+
originalValue: originalMin,
|
84
|
+
numericValue: minValue,
|
85
|
+
reason: Math.abs(minValue) >= MIN_MAX_THRESHOLD ? 'exceeds_threshold' : 'extreme_database_value',
|
86
|
+
threshold: MIN_MAX_THRESHOLD,
|
87
|
+
extremeValue: EXTREME_MIN_FLOAT,
|
88
|
+
operation: 'normalizeMinMaxValues'
|
89
|
+
});
|
90
|
+
normalizedMin = undefined;
|
91
|
+
}
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
// Handle max value
|
96
|
+
if (normalizedMax !== undefined && normalizedMax !== null) {
|
97
|
+
const maxValue = Number(normalizedMax);
|
98
|
+
const originalMax = normalizedMax;
|
99
|
+
|
100
|
+
// Check if it exceeds threshold or is an extreme database value
|
101
|
+
if (type === 'integer') {
|
102
|
+
if (Math.abs(maxValue) >= MIN_MAX_THRESHOLD || maxValue === EXTREME_MAX_INTEGER) {
|
103
|
+
logger.debug(`Max value normalized to undefined for attribute '${attribute.key}'`, {
|
104
|
+
type,
|
105
|
+
originalValue: originalMax,
|
106
|
+
numericValue: maxValue,
|
107
|
+
reason: Math.abs(maxValue) >= MIN_MAX_THRESHOLD ? 'exceeds_threshold' : 'extreme_database_value',
|
108
|
+
threshold: MIN_MAX_THRESHOLD,
|
109
|
+
extremeValue: EXTREME_MAX_INTEGER,
|
110
|
+
operation: 'normalizeMinMaxValues'
|
111
|
+
});
|
112
|
+
normalizedMax = undefined;
|
113
|
+
}
|
114
|
+
} else { // float/double
|
115
|
+
if (Math.abs(maxValue) >= MIN_MAX_THRESHOLD || maxValue === EXTREME_MAX_FLOAT) {
|
116
|
+
logger.debug(`Max value normalized to undefined for attribute '${attribute.key}'`, {
|
117
|
+
type,
|
118
|
+
originalValue: originalMax,
|
119
|
+
numericValue: maxValue,
|
120
|
+
reason: Math.abs(maxValue) >= MIN_MAX_THRESHOLD ? 'exceeds_threshold' : 'extreme_database_value',
|
121
|
+
threshold: MIN_MAX_THRESHOLD,
|
122
|
+
extremeValue: EXTREME_MAX_FLOAT,
|
123
|
+
operation: 'normalizeMinMaxValues'
|
124
|
+
});
|
125
|
+
normalizedMax = undefined;
|
126
|
+
}
|
127
|
+
}
|
128
|
+
}
|
129
|
+
|
130
|
+
const result = { min: normalizedMin, max: normalizedMax };
|
131
|
+
logger.debug(`Min/max normalization complete for attribute '${attribute.key}'`, {
|
132
|
+
type,
|
133
|
+
result,
|
134
|
+
operation: 'normalizeMinMaxValues'
|
135
|
+
});
|
136
|
+
|
137
|
+
return result;
|
138
|
+
};
|
139
|
+
|
140
|
+
/**
|
141
|
+
* Normalizes an attribute for comparison by handling extreme database values
|
142
|
+
* This is used when comparing database attributes with config attributes
|
143
|
+
*/
|
144
|
+
const normalizeAttributeForComparison = (attribute: Attribute): Attribute => {
|
145
|
+
if (!hasMinMaxProperties(attribute)) {
|
146
|
+
return attribute;
|
147
|
+
}
|
148
|
+
|
149
|
+
const { min, max } = normalizeMinMaxValues(attribute);
|
150
|
+
return { ...(attribute as any), min, max };
|
151
|
+
};
|
152
|
+
|
153
|
+
/**
|
154
|
+
* Helper function to create an attribute using either the adapter or legacy API
|
155
|
+
*/
|
156
|
+
const createAttributeViaAdapter = async (
|
157
|
+
db: Databases | DatabaseAdapter,
|
158
|
+
dbId: string,
|
159
|
+
collectionId: string,
|
160
|
+
attribute: Attribute
|
161
|
+
): Promise<void> => {
|
162
|
+
const startTime = Date.now();
|
163
|
+
const adapterType = isDatabaseAdapter(db) ? 'adapter' : 'legacy';
|
164
|
+
|
165
|
+
logger.info(`Creating attribute '${attribute.key}' via ${adapterType}`, {
|
166
|
+
type: attribute.type,
|
167
|
+
dbId,
|
168
|
+
collectionId,
|
169
|
+
adapterType,
|
170
|
+
operation: 'createAttributeViaAdapter'
|
171
|
+
});
|
172
|
+
|
173
|
+
if (isDatabaseAdapter(db)) {
|
174
|
+
// Use the adapter's unified createAttribute method
|
175
|
+
const params: CreateAttributeParams = {
|
176
|
+
databaseId: dbId,
|
177
|
+
tableId: collectionId,
|
178
|
+
key: attribute.key,
|
179
|
+
type: attribute.type,
|
180
|
+
required: attribute.required || false,
|
181
|
+
array: attribute.array || false,
|
182
|
+
...((attribute as any).size && { size: (attribute as any).size }),
|
183
|
+
...((attribute as any).xdefault !== undefined && !attribute.required && { default: (attribute as any).xdefault }),
|
184
|
+
...((attribute as any).encrypted && { encrypt: (attribute as any).encrypted }),
|
185
|
+
...((attribute as any).min !== undefined && { min: (attribute as any).min }),
|
186
|
+
...((attribute as any).max !== undefined && { max: (attribute as any).max }),
|
187
|
+
...((attribute as any).elements && { elements: (attribute as any).elements }),
|
188
|
+
...((attribute as any).relatedCollection && { relatedCollection: (attribute as any).relatedCollection }),
|
189
|
+
...((attribute as any).relationType && { relationType: (attribute as any).relationType }),
|
190
|
+
...((attribute as any).twoWay !== undefined && { twoWay: (attribute as any).twoWay }),
|
191
|
+
...((attribute as any).onDelete && { onDelete: (attribute as any).onDelete }),
|
192
|
+
...((attribute as any).twoWayKey && { twoWayKey: (attribute as any).twoWayKey })
|
193
|
+
};
|
194
|
+
|
195
|
+
logger.debug(`Adapter create parameters for '${attribute.key}'`, {
|
196
|
+
params,
|
197
|
+
operation: 'createAttributeViaAdapter'
|
198
|
+
});
|
199
|
+
|
200
|
+
await db.createAttribute(params);
|
201
|
+
|
202
|
+
const duration = Date.now() - startTime;
|
203
|
+
logger.info(`Successfully created attribute '${attribute.key}' via adapter`, {
|
204
|
+
duration,
|
205
|
+
operation: 'createAttributeViaAdapter'
|
206
|
+
});
|
207
|
+
} else {
|
208
|
+
// Use legacy type-specific methods
|
209
|
+
logger.debug(`Using legacy creation for attribute '${attribute.key}'`, {
|
210
|
+
operation: 'createAttributeViaAdapter'
|
211
|
+
});
|
212
|
+
await createLegacyAttribute(db, dbId, collectionId, attribute);
|
213
|
+
|
214
|
+
const duration = Date.now() - startTime;
|
215
|
+
logger.info(`Successfully created attribute '${attribute.key}' via legacy`, {
|
216
|
+
duration,
|
217
|
+
operation: 'createAttributeViaAdapter'
|
218
|
+
});
|
219
|
+
}
|
220
|
+
};
|
221
|
+
|
222
|
+
/**
|
223
|
+
* Helper function to update an attribute using either the adapter or legacy API
|
224
|
+
*/
|
225
|
+
const updateAttributeViaAdapter = async (
|
226
|
+
db: Databases | DatabaseAdapter,
|
227
|
+
dbId: string,
|
228
|
+
collectionId: string,
|
229
|
+
attribute: Attribute
|
230
|
+
): Promise<void> => {
|
231
|
+
if (isDatabaseAdapter(db)) {
|
232
|
+
// Use the adapter's unified updateAttribute method
|
233
|
+
const params: UpdateAttributeParams = {
|
234
|
+
databaseId: dbId,
|
235
|
+
tableId: collectionId,
|
236
|
+
key: attribute.key,
|
237
|
+
required: attribute.required || false,
|
238
|
+
...((attribute as any).xdefault !== undefined && !attribute.required && { default: (attribute as any).xdefault })
|
239
|
+
};
|
240
|
+
await db.updateAttribute(params);
|
241
|
+
} else {
|
242
|
+
// Use legacy type-specific methods
|
243
|
+
await updateLegacyAttribute(db, dbId, collectionId, attribute);
|
244
|
+
}
|
245
|
+
};
|
246
|
+
|
247
|
+
/**
|
248
|
+
* Legacy attribute creation using type-specific methods
|
249
|
+
*/
|
250
|
+
const createLegacyAttribute = async (
|
251
|
+
db: Databases,
|
252
|
+
dbId: string,
|
253
|
+
collectionId: string,
|
254
|
+
attribute: Attribute
|
255
|
+
): Promise<void> => {
|
256
|
+
const startTime = Date.now();
|
257
|
+
const { min: normalizedMin, max: normalizedMax } = normalizeMinMaxValues(attribute);
|
258
|
+
|
259
|
+
logger.info(`Creating legacy attribute '${attribute.key}'`, {
|
260
|
+
type: attribute.type,
|
261
|
+
dbId,
|
262
|
+
collectionId,
|
263
|
+
normalizedMin,
|
264
|
+
normalizedMax,
|
265
|
+
operation: 'createLegacyAttribute'
|
266
|
+
});
|
267
|
+
|
268
|
+
switch (attribute.type) {
|
269
|
+
case "string":
|
270
|
+
const stringParams = {
|
271
|
+
size: (attribute as any).size || 255,
|
272
|
+
required: attribute.required || false,
|
273
|
+
defaultValue: (attribute as any).xdefault !== undefined && !attribute.required ? (attribute as any).xdefault : undefined,
|
274
|
+
array: attribute.array || false,
|
275
|
+
encrypted: (attribute as any).encrypted
|
276
|
+
};
|
277
|
+
logger.debug(`Creating string attribute '${attribute.key}'`, {
|
278
|
+
...stringParams,
|
279
|
+
operation: 'createLegacyAttribute'
|
280
|
+
});
|
281
|
+
await db.createStringAttribute(
|
282
|
+
dbId,
|
283
|
+
collectionId,
|
284
|
+
attribute.key,
|
285
|
+
stringParams.size,
|
286
|
+
stringParams.required,
|
287
|
+
stringParams.defaultValue,
|
288
|
+
stringParams.array,
|
289
|
+
stringParams.encrypted
|
290
|
+
);
|
291
|
+
break;
|
292
|
+
case "integer":
|
293
|
+
const integerParams = {
|
294
|
+
required: attribute.required || false,
|
295
|
+
min: normalizedMin !== undefined ? parseInt(String(normalizedMin)) : undefined,
|
296
|
+
max: normalizedMax !== undefined ? parseInt(String(normalizedMax)) : undefined,
|
297
|
+
defaultValue: (attribute as any).xdefault !== undefined && !attribute.required ? (attribute as any).xdefault : undefined,
|
298
|
+
array: attribute.array || false
|
299
|
+
};
|
300
|
+
logger.debug(`Creating integer attribute '${attribute.key}'`, {
|
301
|
+
...integerParams,
|
302
|
+
operation: 'createLegacyAttribute'
|
303
|
+
});
|
304
|
+
await db.createIntegerAttribute(
|
305
|
+
dbId,
|
306
|
+
collectionId,
|
307
|
+
attribute.key,
|
308
|
+
integerParams.required,
|
309
|
+
integerParams.min,
|
310
|
+
integerParams.max,
|
311
|
+
integerParams.defaultValue,
|
312
|
+
integerParams.array
|
313
|
+
);
|
314
|
+
break;
|
315
|
+
case "double":
|
316
|
+
case "float":
|
317
|
+
await db.createFloatAttribute(
|
318
|
+
dbId,
|
319
|
+
collectionId,
|
320
|
+
attribute.key,
|
321
|
+
attribute.required || false,
|
322
|
+
normalizedMin !== undefined ? Number(normalizedMin) : undefined,
|
323
|
+
normalizedMax !== undefined ? Number(normalizedMax) : undefined,
|
324
|
+
(attribute as any).xdefault !== undefined && !attribute.required ? (attribute as any).xdefault : undefined,
|
325
|
+
attribute.array || false
|
326
|
+
);
|
327
|
+
break;
|
328
|
+
case "boolean":
|
329
|
+
await db.createBooleanAttribute(
|
330
|
+
dbId,
|
331
|
+
collectionId,
|
332
|
+
attribute.key,
|
333
|
+
attribute.required || false,
|
334
|
+
(attribute as any).xdefault !== undefined && !attribute.required ? (attribute as any).xdefault : undefined,
|
335
|
+
attribute.array || false
|
336
|
+
);
|
337
|
+
break;
|
338
|
+
case "datetime":
|
339
|
+
await db.createDatetimeAttribute(
|
340
|
+
dbId,
|
341
|
+
collectionId,
|
342
|
+
attribute.key,
|
343
|
+
attribute.required || false,
|
344
|
+
(attribute as any).xdefault !== undefined && !attribute.required ? (attribute as any).xdefault : undefined,
|
345
|
+
attribute.array || false
|
346
|
+
);
|
347
|
+
break;
|
348
|
+
case "email":
|
349
|
+
await db.createEmailAttribute(
|
350
|
+
dbId,
|
351
|
+
collectionId,
|
352
|
+
attribute.key,
|
353
|
+
attribute.required || false,
|
354
|
+
(attribute as any).xdefault !== undefined && !attribute.required ? (attribute as any).xdefault : undefined,
|
355
|
+
attribute.array || false
|
356
|
+
);
|
357
|
+
break;
|
358
|
+
case "ip":
|
359
|
+
await db.createIpAttribute(
|
360
|
+
dbId,
|
361
|
+
collectionId,
|
362
|
+
attribute.key,
|
363
|
+
attribute.required || false,
|
364
|
+
(attribute as any).xdefault !== undefined && !attribute.required ? (attribute as any).xdefault : undefined,
|
365
|
+
attribute.array || false
|
366
|
+
);
|
367
|
+
break;
|
368
|
+
case "url":
|
369
|
+
await db.createUrlAttribute(
|
370
|
+
dbId,
|
371
|
+
collectionId,
|
372
|
+
attribute.key,
|
373
|
+
attribute.required || false,
|
374
|
+
(attribute as any).xdefault !== undefined && !attribute.required ? (attribute as any).xdefault : undefined,
|
375
|
+
attribute.array || false
|
376
|
+
);
|
377
|
+
break;
|
378
|
+
case "enum":
|
379
|
+
await db.createEnumAttribute(
|
380
|
+
dbId,
|
381
|
+
collectionId,
|
382
|
+
attribute.key,
|
383
|
+
(attribute as any).elements || [],
|
384
|
+
attribute.required || false,
|
385
|
+
(attribute as any).xdefault !== undefined && !attribute.required ? (attribute as any).xdefault : undefined,
|
386
|
+
attribute.array || false
|
387
|
+
);
|
388
|
+
break;
|
389
|
+
case "relationship":
|
390
|
+
await db.createRelationshipAttribute(
|
391
|
+
dbId,
|
392
|
+
collectionId,
|
393
|
+
(attribute as any).relatedCollection!,
|
394
|
+
(attribute as any).relationType!,
|
395
|
+
(attribute as any).twoWay,
|
396
|
+
attribute.key,
|
397
|
+
(attribute as any).twoWayKey,
|
398
|
+
(attribute as any).onDelete
|
399
|
+
);
|
400
|
+
break;
|
401
|
+
default:
|
402
|
+
const error = new Error(`Unsupported attribute type: ${(attribute as any).type}`);
|
403
|
+
logger.error(`Unsupported attribute type for '${(attribute as any).key}'`, {
|
404
|
+
type: (attribute as any).type,
|
405
|
+
supportedTypes: ['string', 'integer', 'double', 'float', 'boolean', 'datetime', 'email', 'ip', 'url', 'enum', 'relationship'],
|
406
|
+
operation: 'createLegacyAttribute'
|
407
|
+
});
|
408
|
+
throw error;
|
409
|
+
}
|
410
|
+
|
411
|
+
const duration = Date.now() - startTime;
|
412
|
+
logger.info(`Successfully created legacy attribute '${attribute.key}'`, {
|
413
|
+
type: attribute.type,
|
414
|
+
duration,
|
415
|
+
operation: 'createLegacyAttribute'
|
416
|
+
});
|
417
|
+
};
|
418
|
+
|
419
|
+
/**
|
420
|
+
* Legacy attribute update using type-specific methods
|
421
|
+
*/
|
422
|
+
const updateLegacyAttribute = async (
|
423
|
+
db: Databases,
|
424
|
+
dbId: string,
|
425
|
+
collectionId: string,
|
426
|
+
attribute: Attribute
|
427
|
+
): Promise<void> => {
|
428
|
+
const { min: normalizedMin, max: normalizedMax } = normalizeMinMaxValues(attribute);
|
429
|
+
|
430
|
+
switch (attribute.type) {
|
431
|
+
case "string":
|
432
|
+
await db.updateStringAttribute(
|
433
|
+
dbId,
|
434
|
+
collectionId,
|
435
|
+
attribute.key,
|
436
|
+
attribute.required || false,
|
437
|
+
(attribute as any).xdefault !== undefined && !attribute.required ? (attribute as any).xdefault : undefined,
|
438
|
+
attribute.size
|
439
|
+
);
|
440
|
+
break;
|
441
|
+
case "integer":
|
442
|
+
await db.updateIntegerAttribute(
|
443
|
+
dbId,
|
444
|
+
collectionId,
|
445
|
+
attribute.key,
|
446
|
+
attribute.required || false,
|
447
|
+
(attribute as any).xdefault !== undefined && !attribute.required ? (attribute as any).xdefault : undefined,
|
448
|
+
normalizedMin !== undefined ? parseInt(String(normalizedMin)) : undefined,
|
449
|
+
normalizedMax !== undefined ? parseInt(String(normalizedMax)) : undefined
|
450
|
+
);
|
451
|
+
break;
|
452
|
+
case "double":
|
453
|
+
case "float":
|
454
|
+
await db.updateFloatAttribute(
|
455
|
+
dbId,
|
456
|
+
collectionId,
|
457
|
+
attribute.key,
|
458
|
+
attribute.required || false,
|
459
|
+
normalizedMin !== undefined ? Number(normalizedMin) : undefined,
|
460
|
+
normalizedMax !== undefined ? Number(normalizedMax) : undefined,
|
461
|
+
attribute.xdefault !== undefined && attribute.xdefault !== null && !attribute.required ? attribute.xdefault : undefined
|
462
|
+
);
|
463
|
+
break;
|
464
|
+
case "boolean":
|
465
|
+
await db.updateBooleanAttribute(
|
466
|
+
dbId,
|
467
|
+
collectionId,
|
468
|
+
attribute.key,
|
469
|
+
attribute.required || false,
|
470
|
+
attribute.xdefault !== undefined && attribute.xdefault !== null && !attribute.required ? attribute.xdefault : undefined
|
471
|
+
);
|
472
|
+
break;
|
473
|
+
case "datetime":
|
474
|
+
await db.updateDatetimeAttribute(
|
475
|
+
dbId,
|
476
|
+
collectionId,
|
477
|
+
attribute.key,
|
478
|
+
attribute.required || false,
|
479
|
+
attribute.xdefault !== undefined && attribute.xdefault !== null && !attribute.required ? attribute.xdefault : undefined
|
480
|
+
);
|
481
|
+
break;
|
482
|
+
case "email":
|
483
|
+
await db.updateEmailAttribute(
|
484
|
+
dbId,
|
485
|
+
collectionId,
|
486
|
+
attribute.key,
|
487
|
+
attribute.required || false,
|
488
|
+
attribute.xdefault !== undefined && attribute.xdefault !== null && !attribute.required ? attribute.xdefault : undefined
|
489
|
+
);
|
490
|
+
break;
|
491
|
+
case "ip":
|
492
|
+
await db.updateIpAttribute(
|
493
|
+
dbId,
|
494
|
+
collectionId,
|
495
|
+
attribute.key,
|
496
|
+
attribute.required || false,
|
497
|
+
attribute.xdefault !== undefined && attribute.xdefault !== null && !attribute.required ? attribute.xdefault : undefined
|
498
|
+
);
|
499
|
+
break;
|
500
|
+
case "url":
|
501
|
+
await db.updateUrlAttribute(
|
502
|
+
dbId,
|
503
|
+
collectionId,
|
504
|
+
attribute.key,
|
505
|
+
attribute.required || false,
|
506
|
+
attribute.xdefault !== undefined && attribute.xdefault !== null && !attribute.required ? attribute.xdefault : undefined
|
507
|
+
);
|
508
|
+
break;
|
509
|
+
case "enum":
|
510
|
+
await db.updateEnumAttribute(
|
511
|
+
dbId,
|
512
|
+
collectionId,
|
513
|
+
attribute.key,
|
514
|
+
(attribute as any).elements || [],
|
515
|
+
attribute.required || false,
|
516
|
+
attribute.xdefault !== undefined && attribute.xdefault !== null && !attribute.required ? attribute.xdefault : undefined
|
517
|
+
);
|
518
|
+
break;
|
519
|
+
case "relationship":
|
520
|
+
await db.updateRelationshipAttribute(
|
521
|
+
dbId,
|
522
|
+
collectionId,
|
523
|
+
attribute.key,
|
524
|
+
(attribute as any).onDelete
|
525
|
+
);
|
526
|
+
break;
|
527
|
+
default:
|
528
|
+
throw new Error(`Unsupported attribute type for update: ${(attribute as any).type}`);
|
529
|
+
}
|
530
|
+
};
|
10
531
|
|
11
532
|
// Interface for attribute with status (fixing the type issue)
|
12
533
|
interface AttributeWithStatus {
|
@@ -25,7 +546,7 @@ interface AttributeWithStatus {
|
|
25
546
|
* Wait for attribute to become available, with retry logic for stuck attributes and exponential backoff
|
26
547
|
*/
|
27
548
|
const waitForAttributeAvailable = async (
|
28
|
-
db: Databases,
|
549
|
+
db: Databases | DatabaseAdapter,
|
29
550
|
dbId: string,
|
30
551
|
collectionId: string,
|
31
552
|
attributeKey: string,
|
@@ -36,17 +557,26 @@ const waitForAttributeAvailable = async (
|
|
36
557
|
const startTime = Date.now();
|
37
558
|
let checkInterval = 2000; // Start with 2 seconds
|
38
559
|
|
560
|
+
logger.info(`Waiting for attribute '${attributeKey}' to become available`, {
|
561
|
+
dbId,
|
562
|
+
collectionId,
|
563
|
+
maxWaitTime,
|
564
|
+
retryCount,
|
565
|
+
maxRetries,
|
566
|
+
operation: 'waitForAttributeAvailable'
|
567
|
+
});
|
568
|
+
|
39
569
|
// Calculate exponential backoff: 2s, 4s, 8s, 16s, 30s (capped at 30s)
|
40
570
|
if (retryCount > 0) {
|
41
|
-
const exponentialDelay =
|
42
|
-
|
571
|
+
const exponentialDelay = calculateExponentialBackoff(retryCount);
|
572
|
+
MessageFormatter.info(
|
43
573
|
chalk.blue(
|
44
574
|
`Waiting for attribute '${attributeKey}' to become available (retry ${retryCount}, backoff: ${exponentialDelay}ms)...`
|
45
575
|
)
|
46
576
|
);
|
47
577
|
await delay(exponentialDelay);
|
48
578
|
} else {
|
49
|
-
|
579
|
+
MessageFormatter.info(
|
50
580
|
chalk.blue(
|
51
581
|
`Waiting for attribute '${attributeKey}' to become available...`
|
52
582
|
)
|
@@ -55,71 +585,100 @@ const waitForAttributeAvailable = async (
|
|
55
585
|
|
56
586
|
while (Date.now() - startTime < maxWaitTime) {
|
57
587
|
try {
|
58
|
-
const collection =
|
588
|
+
const collection = isDatabaseAdapter(db)
|
589
|
+
? (await db.getTable({ databaseId: dbId, tableId: collectionId })).data
|
590
|
+
: await db.getCollection(dbId, collectionId);
|
59
591
|
const attribute = (collection.attributes as any[]).find(
|
60
592
|
(attr: AttributeWithStatus) => attr.key === attributeKey
|
61
593
|
) as AttributeWithStatus | undefined;
|
62
594
|
|
63
595
|
if (!attribute) {
|
64
|
-
|
596
|
+
MessageFormatter.error(`Attribute '${attributeKey}' not found`);
|
65
597
|
return false;
|
66
598
|
}
|
67
599
|
|
68
|
-
|
600
|
+
MessageFormatter.info(
|
69
601
|
chalk.gray(`Attribute '${attributeKey}' status: ${attribute.status}`)
|
70
602
|
);
|
71
603
|
|
604
|
+
const statusInfo = {
|
605
|
+
attributeKey,
|
606
|
+
status: attribute.status,
|
607
|
+
error: attribute.error,
|
608
|
+
dbId,
|
609
|
+
collectionId,
|
610
|
+
waitTime: Date.now() - startTime,
|
611
|
+
operation: 'waitForAttributeAvailable'
|
612
|
+
};
|
613
|
+
|
72
614
|
switch (attribute.status) {
|
73
615
|
case "available":
|
74
|
-
|
616
|
+
MessageFormatter.info(
|
75
617
|
chalk.green(`✅ Attribute '${attributeKey}' is now available`)
|
76
618
|
);
|
619
|
+
logger.info(`Attribute '${attributeKey}' became available`, statusInfo);
|
77
620
|
return true;
|
78
621
|
|
79
622
|
case "failed":
|
80
|
-
|
623
|
+
MessageFormatter.info(
|
81
624
|
chalk.red(
|
82
625
|
`❌ Attribute '${attributeKey}' failed: ${attribute.error}`
|
83
626
|
)
|
84
627
|
);
|
628
|
+
logger.error(`Attribute '${attributeKey}' failed`, statusInfo);
|
85
629
|
return false;
|
86
630
|
|
87
631
|
case "stuck":
|
88
|
-
|
632
|
+
MessageFormatter.info(
|
89
633
|
chalk.yellow(
|
90
634
|
`⚠️ Attribute '${attributeKey}' is stuck, will retry...`
|
91
635
|
)
|
92
636
|
);
|
637
|
+
logger.warn(`Attribute '${attributeKey}' is stuck`, statusInfo);
|
93
638
|
return false;
|
94
639
|
|
95
640
|
case "processing":
|
96
641
|
// Continue waiting
|
642
|
+
logger.debug(`Attribute '${attributeKey}' still processing`, statusInfo);
|
97
643
|
break;
|
98
644
|
|
99
645
|
case "deleting":
|
100
|
-
|
646
|
+
MessageFormatter.info(
|
101
647
|
chalk.yellow(`Attribute '${attributeKey}' is being deleted`)
|
102
648
|
);
|
649
|
+
logger.warn(`Attribute '${attributeKey}' is being deleted`, statusInfo);
|
103
650
|
break;
|
104
651
|
|
105
652
|
default:
|
106
|
-
|
653
|
+
MessageFormatter.info(
|
107
654
|
chalk.yellow(
|
108
655
|
`Unknown status '${attribute.status}' for attribute '${attributeKey}'`
|
109
656
|
)
|
110
657
|
);
|
658
|
+
logger.warn(`Unknown status for attribute '${attributeKey}'`, statusInfo);
|
111
659
|
break;
|
112
660
|
}
|
113
661
|
|
114
662
|
await delay(checkInterval);
|
115
663
|
} catch (error) {
|
116
|
-
|
664
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
665
|
+
MessageFormatter.error(`Error checking attribute status: ${errorMessage}`);
|
666
|
+
|
667
|
+
logger.error('Error checking attribute status', {
|
668
|
+
attributeKey,
|
669
|
+
dbId,
|
670
|
+
collectionId,
|
671
|
+
error: errorMessage,
|
672
|
+
waitTime: Date.now() - startTime,
|
673
|
+
operation: 'waitForAttributeAvailable'
|
674
|
+
});
|
675
|
+
|
117
676
|
return false;
|
118
677
|
}
|
119
678
|
}
|
120
679
|
|
121
680
|
// Timeout reached
|
122
|
-
|
681
|
+
MessageFormatter.info(
|
123
682
|
chalk.yellow(
|
124
683
|
`⏰ Timeout waiting for attribute '${attributeKey}' (${maxWaitTime}ms)`
|
125
684
|
)
|
@@ -127,7 +686,7 @@ const waitForAttributeAvailable = async (
|
|
127
686
|
|
128
687
|
// If we have retries left and this isn't the last retry, try recreating
|
129
688
|
if (retryCount < maxRetries) {
|
130
|
-
|
689
|
+
MessageFormatter.info(
|
131
690
|
chalk.yellow(
|
132
691
|
`🔄 Retrying attribute creation (attempt ${
|
133
692
|
retryCount + 1
|
@@ -144,13 +703,13 @@ const waitForAttributeAvailable = async (
|
|
144
703
|
* Wait for all attributes in a collection to become available
|
145
704
|
*/
|
146
705
|
const waitForAllAttributesAvailable = async (
|
147
|
-
db: Databases,
|
706
|
+
db: Databases | DatabaseAdapter,
|
148
707
|
dbId: string,
|
149
708
|
collectionId: string,
|
150
709
|
attributeKeys: string[],
|
151
710
|
maxWaitTime: number = 60000
|
152
711
|
): Promise<string[]> => {
|
153
|
-
|
712
|
+
MessageFormatter.info(
|
154
713
|
chalk.blue(
|
155
714
|
`Waiting for ${attributeKeys.length} attributes to become available...`
|
156
715
|
)
|
@@ -178,40 +737,53 @@ const waitForAllAttributesAvailable = async (
|
|
178
737
|
* Delete collection and recreate with retry logic
|
179
738
|
*/
|
180
739
|
const deleteAndRecreateCollection = async (
|
181
|
-
db: Databases,
|
740
|
+
db: Databases | DatabaseAdapter,
|
182
741
|
dbId: string,
|
183
742
|
collection: Models.Collection,
|
184
743
|
retryCount: number
|
185
744
|
): Promise<Models.Collection | null> => {
|
186
745
|
try {
|
187
|
-
|
746
|
+
MessageFormatter.info(
|
188
747
|
chalk.yellow(
|
189
748
|
`🗑️ Deleting collection '${collection.name}' for retry ${retryCount}`
|
190
749
|
)
|
191
750
|
);
|
192
751
|
|
193
752
|
// Delete the collection
|
194
|
-
|
195
|
-
|
753
|
+
if (isDatabaseAdapter(db)) {
|
754
|
+
await db.deleteTable({ databaseId: dbId, tableId: collection.$id });
|
755
|
+
} else {
|
756
|
+
await db.deleteCollection(dbId, collection.$id);
|
757
|
+
}
|
758
|
+
MessageFormatter.warning(`Deleted collection '${collection.name}'`);
|
196
759
|
|
197
760
|
// Wait a bit before recreating
|
198
761
|
await delay(2000);
|
199
762
|
|
200
763
|
// Recreate the collection
|
201
|
-
|
202
|
-
const newCollection =
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
764
|
+
MessageFormatter.info(`🔄 Recreating collection '${collection.name}'`);
|
765
|
+
const newCollection = isDatabaseAdapter(db)
|
766
|
+
? (await db.createTable({
|
767
|
+
databaseId: dbId,
|
768
|
+
id: collection.$id,
|
769
|
+
name: collection.name,
|
770
|
+
permissions: collection.$permissions,
|
771
|
+
documentSecurity: collection.documentSecurity,
|
772
|
+
enabled: collection.enabled
|
773
|
+
})).data
|
774
|
+
: await db.createCollection(
|
775
|
+
dbId,
|
776
|
+
collection.$id,
|
777
|
+
collection.name,
|
778
|
+
collection.$permissions,
|
779
|
+
collection.documentSecurity,
|
780
|
+
collection.enabled
|
781
|
+
);
|
210
782
|
|
211
|
-
|
783
|
+
MessageFormatter.success(`✅ Recreated collection '${collection.name}'`);
|
212
784
|
return newCollection;
|
213
785
|
} catch (error) {
|
214
|
-
|
786
|
+
MessageFormatter.info(
|
215
787
|
chalk.red(
|
216
788
|
`Failed to delete/recreate collection '${collection.name}': ${error}`
|
217
789
|
)
|
@@ -224,33 +796,37 @@ const attributesSame = (
|
|
224
796
|
databaseAttribute: Attribute,
|
225
797
|
configAttribute: Attribute
|
226
798
|
): boolean => {
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
"
|
233
|
-
"
|
234
|
-
"
|
235
|
-
"
|
236
|
-
"
|
237
|
-
"
|
238
|
-
"
|
239
|
-
"
|
240
|
-
"
|
241
|
-
"
|
242
|
-
"
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
799
|
+
// Normalize both attributes for comparison (handle extreme database values)
|
800
|
+
const normalizedDbAttr = normalizeAttributeForComparison(databaseAttribute);
|
801
|
+
const normalizedConfigAttr = normalizeAttributeForComparison(configAttribute);
|
802
|
+
|
803
|
+
const attributesToCheck = [
|
804
|
+
"key",
|
805
|
+
"type",
|
806
|
+
"array",
|
807
|
+
"encrypted",
|
808
|
+
"required",
|
809
|
+
"size",
|
810
|
+
"min",
|
811
|
+
"max",
|
812
|
+
"xdefault",
|
813
|
+
"elements",
|
814
|
+
"relationType",
|
815
|
+
"twoWay",
|
816
|
+
"twoWayKey",
|
817
|
+
"onDelete",
|
818
|
+
"relatedCollection",
|
819
|
+
];
|
820
|
+
|
821
|
+
return attributesToCheck.every((attr) => {
|
822
|
+
// Check if both objects have the attribute
|
823
|
+
const dbHasAttr = attr in normalizedDbAttr;
|
824
|
+
const configHasAttr = attr in normalizedConfigAttr;
|
249
825
|
|
250
826
|
// If both have the attribute, compare values
|
251
|
-
if (dbHasAttr && configHasAttr) {
|
252
|
-
const dbValue =
|
253
|
-
const configValue =
|
827
|
+
if (dbHasAttr && configHasAttr) {
|
828
|
+
const dbValue = normalizedDbAttr[attr as keyof typeof normalizedDbAttr];
|
829
|
+
const configValue = normalizedConfigAttr[attr as keyof typeof normalizedConfigAttr];
|
254
830
|
|
255
831
|
// Consider undefined and null as equivalent
|
256
832
|
if (
|
@@ -260,19 +836,19 @@ const attributesSame = (
|
|
260
836
|
return true;
|
261
837
|
}
|
262
838
|
|
263
|
-
// Normalize booleans: treat undefined and false as equivalent
|
264
|
-
if (typeof dbValue === "boolean" || typeof configValue === "boolean") {
|
265
|
-
return Boolean(dbValue) === Boolean(configValue);
|
266
|
-
}
|
267
|
-
// For numeric comparisons, compare numbers if both are numeric-like
|
268
|
-
if (
|
269
|
-
(typeof dbValue === "number" || (typeof dbValue === "string" && dbValue !== "" && !isNaN(Number(dbValue)))) &&
|
270
|
-
(typeof configValue === "number" || (typeof configValue === "string" && configValue !== "" && !isNaN(Number(configValue))))
|
271
|
-
) {
|
272
|
-
return Number(dbValue) === Number(configValue);
|
273
|
-
}
|
274
|
-
return dbValue === configValue;
|
275
|
-
}
|
839
|
+
// Normalize booleans: treat undefined and false as equivalent
|
840
|
+
if (typeof dbValue === "boolean" || typeof configValue === "boolean") {
|
841
|
+
return Boolean(dbValue) === Boolean(configValue);
|
842
|
+
}
|
843
|
+
// For numeric comparisons, compare numbers if both are numeric-like
|
844
|
+
if (
|
845
|
+
(typeof dbValue === "number" || (typeof dbValue === "string" && dbValue !== "" && !isNaN(Number(dbValue)))) &&
|
846
|
+
(typeof configValue === "number" || (typeof configValue === "string" && configValue !== "" && !isNaN(Number(configValue))))
|
847
|
+
) {
|
848
|
+
return Number(dbValue) === Number(configValue);
|
849
|
+
}
|
850
|
+
return dbValue === configValue;
|
851
|
+
}
|
276
852
|
|
277
853
|
// If neither has the attribute, consider it the same
|
278
854
|
if (!dbHasAttr && !configHasAttr) {
|
@@ -280,23 +856,23 @@ const attributesSame = (
|
|
280
856
|
}
|
281
857
|
|
282
858
|
// If one has the attribute and the other doesn't, check if it's undefined or null
|
283
|
-
if (dbHasAttr && !configHasAttr) {
|
284
|
-
const dbValue =
|
285
|
-
// Consider default-false booleans as equal to missing in config
|
286
|
-
if (typeof dbValue === "boolean") {
|
287
|
-
return dbValue === false; // missing in config equals false in db
|
288
|
-
}
|
289
|
-
return dbValue === undefined || dbValue === null;
|
290
|
-
}
|
291
|
-
|
292
|
-
if (!dbHasAttr && configHasAttr) {
|
293
|
-
const configValue =
|
294
|
-
// Consider default-false booleans as equal to missing in db
|
295
|
-
if (typeof configValue === "boolean") {
|
296
|
-
return configValue === false; // missing in db equals false in config
|
297
|
-
}
|
298
|
-
return configValue === undefined || configValue === null;
|
299
|
-
}
|
859
|
+
if (dbHasAttr && !configHasAttr) {
|
860
|
+
const dbValue = normalizedDbAttr[attr as keyof typeof normalizedDbAttr];
|
861
|
+
// Consider default-false booleans as equal to missing in config
|
862
|
+
if (typeof dbValue === "boolean") {
|
863
|
+
return dbValue === false; // missing in config equals false in db
|
864
|
+
}
|
865
|
+
return dbValue === undefined || dbValue === null;
|
866
|
+
}
|
867
|
+
|
868
|
+
if (!dbHasAttr && configHasAttr) {
|
869
|
+
const configValue = normalizedConfigAttr[attr as keyof typeof normalizedConfigAttr];
|
870
|
+
// Consider default-false booleans as equal to missing in db
|
871
|
+
if (typeof configValue === "boolean") {
|
872
|
+
return configValue === false; // missing in db equals false in config
|
873
|
+
}
|
874
|
+
return configValue === undefined || configValue === null;
|
875
|
+
}
|
300
876
|
|
301
877
|
// If we reach here, the attributes are different
|
302
878
|
return false;
|
@@ -306,15 +882,15 @@ const attributesSame = (
|
|
306
882
|
/**
|
307
883
|
* Enhanced attribute creation with proper status monitoring and retry logic
|
308
884
|
*/
|
309
|
-
export const createOrUpdateAttributeWithStatusCheck = async (
|
310
|
-
db: Databases,
|
311
|
-
dbId: string,
|
312
|
-
collection: Models.Collection,
|
313
|
-
attribute: Attribute,
|
314
|
-
retryCount: number = 0,
|
315
|
-
maxRetries: number = 5
|
316
|
-
): Promise<boolean> => {
|
317
|
-
|
885
|
+
export const createOrUpdateAttributeWithStatusCheck = async (
|
886
|
+
db: Databases | DatabaseAdapter,
|
887
|
+
dbId: string,
|
888
|
+
collection: Models.Collection,
|
889
|
+
attribute: Attribute,
|
890
|
+
retryCount: number = 0,
|
891
|
+
maxRetries: number = 5
|
892
|
+
): Promise<boolean> => {
|
893
|
+
MessageFormatter.info(
|
318
894
|
chalk.blue(
|
319
895
|
`Creating/updating attribute '${attribute.key}' (attempt ${
|
320
896
|
retryCount + 1
|
@@ -322,31 +898,31 @@ export const createOrUpdateAttributeWithStatusCheck = async (
|
|
322
898
|
)
|
323
899
|
);
|
324
900
|
|
325
|
-
try {
|
326
|
-
// First, try to create/update the attribute using existing logic
|
327
|
-
const result = await createOrUpdateAttribute(db, dbId, collection, attribute);
|
328
|
-
|
329
|
-
// If the attribute was queued (relationship dependency unresolved),
|
330
|
-
// skip status polling and retry logic — the queue will handle it later.
|
331
|
-
if (result === "queued") {
|
332
|
-
|
333
|
-
chalk.yellow(
|
334
|
-
`⏭️ Deferred relationship attribute '${attribute.key}' — queued for later once dependencies are available`
|
335
|
-
)
|
336
|
-
);
|
337
|
-
return true;
|
338
|
-
}
|
339
|
-
|
340
|
-
// Now wait for the attribute to become available
|
341
|
-
const success = await waitForAttributeAvailable(
|
342
|
-
db,
|
343
|
-
dbId,
|
344
|
-
collection.$id,
|
345
|
-
attribute.key,
|
346
|
-
60000, // 1 minute timeout
|
347
|
-
retryCount,
|
348
|
-
maxRetries
|
349
|
-
);
|
901
|
+
try {
|
902
|
+
// First, try to create/update the attribute using existing logic
|
903
|
+
const result = await createOrUpdateAttribute(db, dbId, collection, attribute);
|
904
|
+
|
905
|
+
// If the attribute was queued (relationship dependency unresolved),
|
906
|
+
// skip status polling and retry logic — the queue will handle it later.
|
907
|
+
if (result === "queued") {
|
908
|
+
MessageFormatter.info(
|
909
|
+
chalk.yellow(
|
910
|
+
`⏭️ Deferred relationship attribute '${attribute.key}' — queued for later once dependencies are available`
|
911
|
+
)
|
912
|
+
);
|
913
|
+
return true;
|
914
|
+
}
|
915
|
+
|
916
|
+
// Now wait for the attribute to become available
|
917
|
+
const success = await waitForAttributeAvailable(
|
918
|
+
db,
|
919
|
+
dbId,
|
920
|
+
collection.$id,
|
921
|
+
attribute.key,
|
922
|
+
60000, // 1 minute timeout
|
923
|
+
retryCount,
|
924
|
+
maxRetries
|
925
|
+
);
|
350
926
|
|
351
927
|
if (success) {
|
352
928
|
return true;
|
@@ -354,7 +930,7 @@ export const createOrUpdateAttributeWithStatusCheck = async (
|
|
354
930
|
|
355
931
|
// If not successful and we have retries left, delete specific attribute and try again
|
356
932
|
if (retryCount < maxRetries) {
|
357
|
-
|
933
|
+
MessageFormatter.info(
|
358
934
|
chalk.yellow(
|
359
935
|
`Attribute '${attribute.key}' failed/stuck, deleting and retrying...`
|
360
936
|
)
|
@@ -362,8 +938,12 @@ export const createOrUpdateAttributeWithStatusCheck = async (
|
|
362
938
|
|
363
939
|
// Try to delete the specific stuck attribute instead of the entire collection
|
364
940
|
try {
|
365
|
-
|
366
|
-
|
941
|
+
if (isDatabaseAdapter(db)) {
|
942
|
+
await db.deleteAttribute({ databaseId: dbId, tableId: collection.$id, key: attribute.key });
|
943
|
+
} else {
|
944
|
+
await db.deleteAttribute(dbId, collection.$id, attribute.key);
|
945
|
+
}
|
946
|
+
MessageFormatter.info(
|
367
947
|
chalk.yellow(
|
368
948
|
`Deleted stuck attribute '${attribute.key}', will retry creation`
|
369
949
|
)
|
@@ -373,7 +953,9 @@ export const createOrUpdateAttributeWithStatusCheck = async (
|
|
373
953
|
await delay(3000);
|
374
954
|
|
375
955
|
// Get fresh collection data
|
376
|
-
const freshCollection =
|
956
|
+
const freshCollection = isDatabaseAdapter(db)
|
957
|
+
? (await db.getTable({ databaseId: dbId, tableId: collection.$id })).data
|
958
|
+
: await db.getCollection(dbId, collection.$id);
|
377
959
|
|
378
960
|
// Retry with the same collection (attribute should be gone now)
|
379
961
|
return await createOrUpdateAttributeWithStatusCheck(
|
@@ -385,7 +967,7 @@ export const createOrUpdateAttributeWithStatusCheck = async (
|
|
385
967
|
maxRetries
|
386
968
|
);
|
387
969
|
} catch (deleteError) {
|
388
|
-
|
970
|
+
MessageFormatter.info(
|
389
971
|
chalk.red(
|
390
972
|
`Failed to delete stuck attribute '${attribute.key}': ${deleteError}`
|
391
973
|
)
|
@@ -393,14 +975,16 @@ export const createOrUpdateAttributeWithStatusCheck = async (
|
|
393
975
|
|
394
976
|
// If attribute deletion fails, only then try collection recreation as last resort
|
395
977
|
if (retryCount >= maxRetries - 1) {
|
396
|
-
|
978
|
+
MessageFormatter.info(
|
397
979
|
chalk.yellow(
|
398
980
|
`Last resort: Recreating collection for attribute '${attribute.key}'`
|
399
981
|
)
|
400
982
|
);
|
401
983
|
|
402
984
|
// Get fresh collection data
|
403
|
-
const freshCollection =
|
985
|
+
const freshCollection = isDatabaseAdapter(db)
|
986
|
+
? (await db.getTable({ databaseId: dbId, tableId: collection.$id })).data
|
987
|
+
: await db.getCollection(dbId, collection.$id);
|
404
988
|
|
405
989
|
// Delete and recreate collection
|
406
990
|
const newCollection = await deleteAndRecreateCollection(
|
@@ -435,7 +1019,7 @@ export const createOrUpdateAttributeWithStatusCheck = async (
|
|
435
1019
|
}
|
436
1020
|
}
|
437
1021
|
|
438
|
-
|
1022
|
+
MessageFormatter.info(
|
439
1023
|
chalk.red(
|
440
1024
|
`❌ Failed to create attribute '${attribute.key}' after ${
|
441
1025
|
maxRetries + 1
|
@@ -444,12 +1028,12 @@ export const createOrUpdateAttributeWithStatusCheck = async (
|
|
444
1028
|
);
|
445
1029
|
return false;
|
446
1030
|
} catch (error) {
|
447
|
-
|
1031
|
+
MessageFormatter.info(
|
448
1032
|
chalk.red(`Error creating attribute '${attribute.key}': ${error}`)
|
449
1033
|
);
|
450
1034
|
|
451
1035
|
if (retryCount < maxRetries) {
|
452
|
-
|
1036
|
+
MessageFormatter.info(
|
453
1037
|
chalk.yellow(`Retrying attribute '${attribute.key}' due to error...`)
|
454
1038
|
);
|
455
1039
|
|
@@ -470,12 +1054,12 @@ export const createOrUpdateAttributeWithStatusCheck = async (
|
|
470
1054
|
}
|
471
1055
|
};
|
472
1056
|
|
473
|
-
export const createOrUpdateAttribute = async (
|
474
|
-
db: Databases,
|
475
|
-
dbId: string,
|
476
|
-
collection: Models.Collection,
|
477
|
-
attribute: Attribute
|
478
|
-
): Promise<"queued" | "processed"> => {
|
1057
|
+
export const createOrUpdateAttribute = async (
|
1058
|
+
db: Databases | DatabaseAdapter,
|
1059
|
+
dbId: string,
|
1060
|
+
collection: Models.Collection,
|
1061
|
+
attribute: Attribute
|
1062
|
+
): Promise<"queued" | "processed"> => {
|
479
1063
|
let action = "create";
|
480
1064
|
let foundAttribute: Attribute | undefined;
|
481
1065
|
const updateEnabled = true;
|
@@ -485,24 +1069,24 @@ export const createOrUpdateAttribute = async (
|
|
485
1069
|
(attr: any) => attr.key === attribute.key
|
486
1070
|
) as unknown as any;
|
487
1071
|
foundAttribute = parseAttribute(collectionAttr);
|
488
|
-
|
1072
|
+
|
489
1073
|
} catch (error) {
|
490
1074
|
foundAttribute = undefined;
|
491
1075
|
}
|
492
1076
|
|
493
|
-
if (
|
494
|
-
foundAttribute &&
|
495
|
-
attributesSame(foundAttribute, attribute) &&
|
496
|
-
updateEnabled
|
497
|
-
) {
|
498
|
-
// No need to do anything, they are the same
|
499
|
-
return "processed";
|
1077
|
+
if (
|
1078
|
+
foundAttribute &&
|
1079
|
+
attributesSame(foundAttribute, attribute) &&
|
1080
|
+
updateEnabled
|
1081
|
+
) {
|
1082
|
+
// No need to do anything, they are the same
|
1083
|
+
return "processed";
|
500
1084
|
} else if (
|
501
1085
|
foundAttribute &&
|
502
1086
|
!attributesSame(foundAttribute, attribute) &&
|
503
1087
|
updateEnabled
|
504
1088
|
) {
|
505
|
-
//
|
1089
|
+
// MessageFormatter.info(
|
506
1090
|
// `Updating attribute with same key ${attribute.key} but different values`
|
507
1091
|
// );
|
508
1092
|
finalAttribute = {
|
@@ -514,460 +1098,114 @@ export const createOrUpdateAttribute = async (
|
|
514
1098
|
!updateEnabled &&
|
515
1099
|
foundAttribute &&
|
516
1100
|
!attributesSame(foundAttribute, attribute)
|
517
|
-
) {
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
1101
|
+
) {
|
1102
|
+
if (isDatabaseAdapter(db)) {
|
1103
|
+
await db.deleteAttribute({ databaseId: dbId, tableId: collection.$id, key: attribute.key });
|
1104
|
+
} else {
|
1105
|
+
await db.deleteAttribute(dbId, collection.$id, attribute.key);
|
1106
|
+
}
|
1107
|
+
MessageFormatter.info(
|
1108
|
+
`Deleted attribute: ${attribute.key} to recreate it because they diff (update disabled temporarily)`
|
1109
|
+
);
|
1110
|
+
return "processed";
|
1111
|
+
}
|
524
1112
|
|
525
|
-
|
1113
|
+
|
526
1114
|
|
527
1115
|
// Relationship attribute logic with adjustments
|
528
1116
|
let collectionFoundViaRelatedCollection: Models.Collection | undefined;
|
529
1117
|
let relatedCollectionId: string | undefined;
|
530
|
-
if (
|
531
|
-
finalAttribute.type === "relationship" &&
|
532
|
-
finalAttribute.relatedCollection
|
533
|
-
) {
|
534
|
-
// First try treating relatedCollection as an ID directly
|
535
|
-
try {
|
536
|
-
const byIdCollection =
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
}
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
//
|
557
|
-
//
|
558
|
-
|
559
|
-
|
560
|
-
} else if (!collectionFoundViaRelatedCollection) {
|
561
|
-
const collectionsPulled = await db.listCollections(dbId, [
|
562
|
-
Query.equal("name", finalAttribute.relatedCollection),
|
563
|
-
]);
|
564
|
-
if (collectionsPulled.total > 0) {
|
565
|
-
collectionFoundViaRelatedCollection = collectionsPulled.collections[0];
|
566
|
-
relatedCollectionId = collectionFoundViaRelatedCollection.$id;
|
567
|
-
nameToIdMapping.set(
|
568
|
-
finalAttribute.relatedCollection,
|
569
|
-
relatedCollectionId
|
570
|
-
);
|
571
|
-
}
|
572
|
-
}
|
573
|
-
// ONLY queue relationship attributes that have actual unresolved dependencies
|
574
|
-
if (!(relatedCollectionId && collectionFoundViaRelatedCollection)) {
|
575
|
-
console.log(
|
576
|
-
chalk.yellow(
|
577
|
-
`⏳ Queueing relationship attribute '${finalAttribute.key}' - related collection '${finalAttribute.relatedCollection}' not found yet`
|
578
|
-
)
|
579
|
-
);
|
580
|
-
enqueueOperation({
|
581
|
-
type: "attribute",
|
582
|
-
collectionId: collection.$id,
|
583
|
-
collection: collection,
|
584
|
-
attribute,
|
585
|
-
dependencies: [finalAttribute.relatedCollection],
|
586
|
-
});
|
587
|
-
return "queued";
|
588
|
-
}
|
589
|
-
}
|
590
|
-
finalAttribute = parseAttribute(finalAttribute);
|
591
|
-
// console.log(`Final Attribute: ${JSON.stringify(finalAttribute)}`);
|
592
|
-
switch (finalAttribute.type) {
|
593
|
-
case "string":
|
594
|
-
if (action === "create") {
|
595
|
-
await tryAwaitWithRetry(
|
596
|
-
async () =>
|
597
|
-
await db.createStringAttribute(
|
598
|
-
dbId,
|
599
|
-
collection.$id,
|
600
|
-
finalAttribute.key,
|
601
|
-
finalAttribute.size,
|
602
|
-
finalAttribute.required || false,
|
603
|
-
finalAttribute.xdefault !== undefined && !finalAttribute.required
|
604
|
-
? finalAttribute.xdefault
|
605
|
-
: null,
|
606
|
-
finalAttribute.array || false,
|
607
|
-
finalAttribute.encrypted
|
608
|
-
)
|
609
|
-
);
|
610
|
-
} else {
|
611
|
-
await tryAwaitWithRetry(
|
612
|
-
async () =>
|
613
|
-
await db.updateStringAttribute(
|
614
|
-
dbId,
|
615
|
-
collection.$id,
|
616
|
-
finalAttribute.key,
|
617
|
-
finalAttribute.required || false,
|
618
|
-
finalAttribute.xdefault !== undefined && !finalAttribute.required
|
619
|
-
? finalAttribute.xdefault
|
620
|
-
: null,
|
621
|
-
finalAttribute.size
|
622
|
-
)
|
623
|
-
);
|
1118
|
+
if (
|
1119
|
+
finalAttribute.type === "relationship" &&
|
1120
|
+
finalAttribute.relatedCollection
|
1121
|
+
) {
|
1122
|
+
// First try treating relatedCollection as an ID directly
|
1123
|
+
try {
|
1124
|
+
const byIdCollection = isDatabaseAdapter(db)
|
1125
|
+
? (await db.getTable({ databaseId: dbId, tableId: finalAttribute.relatedCollection })).data
|
1126
|
+
: await db.getCollection(dbId, finalAttribute.relatedCollection);
|
1127
|
+
collectionFoundViaRelatedCollection = byIdCollection;
|
1128
|
+
relatedCollectionId = byIdCollection.$id;
|
1129
|
+
// Cache by name for subsequent lookups
|
1130
|
+
nameToIdMapping.set(byIdCollection.name, byIdCollection.$id);
|
1131
|
+
} catch (_) {
|
1132
|
+
// Not an ID or not found — fall back to name-based resolution below
|
1133
|
+
}
|
1134
|
+
|
1135
|
+
if (!collectionFoundViaRelatedCollection && nameToIdMapping.has(finalAttribute.relatedCollection)) {
|
1136
|
+
relatedCollectionId = nameToIdMapping.get(
|
1137
|
+
finalAttribute.relatedCollection
|
1138
|
+
);
|
1139
|
+
try {
|
1140
|
+
collectionFoundViaRelatedCollection = isDatabaseAdapter(db)
|
1141
|
+
? (await db.getTable({ databaseId: dbId, tableId: relatedCollectionId! })).data
|
1142
|
+
: await db.getCollection(dbId, relatedCollectionId!);
|
1143
|
+
} catch (e) {
|
1144
|
+
// MessageFormatter.info(
|
1145
|
+
// `Collection not found: ${finalAttribute.relatedCollection} when nameToIdMapping was set`
|
1146
|
+
// );
|
1147
|
+
collectionFoundViaRelatedCollection = undefined;
|
624
1148
|
}
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
if (
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
1149
|
+
} else if (!collectionFoundViaRelatedCollection) {
|
1150
|
+
const collectionsPulled = isDatabaseAdapter(db)
|
1151
|
+
? await db.listTables({ databaseId: dbId, queries: [Query.equal("name", finalAttribute.relatedCollection)] })
|
1152
|
+
: await db.listCollections(dbId, [Query.equal("name", finalAttribute.relatedCollection)]);
|
1153
|
+
if (collectionsPulled.total && collectionsPulled.total > 0) {
|
1154
|
+
collectionFoundViaRelatedCollection = isDatabaseAdapter(db)
|
1155
|
+
? (collectionsPulled as any).tables?.[0]
|
1156
|
+
: (collectionsPulled as any).collections?.[0];
|
1157
|
+
relatedCollectionId = collectionFoundViaRelatedCollection?.$id;
|
1158
|
+
if (relatedCollectionId) {
|
1159
|
+
nameToIdMapping.set(
|
1160
|
+
finalAttribute.relatedCollection,
|
1161
|
+
relatedCollectionId
|
1162
|
+
);
|
639
1163
|
}
|
640
|
-
const minValue =
|
641
|
-
finalAttribute.min !== undefined && finalAttribute.min !== null
|
642
|
-
? parseInt(finalAttribute.min)
|
643
|
-
: -9007199254740991;
|
644
|
-
const maxValue =
|
645
|
-
finalAttribute.max !== undefined && finalAttribute.max !== null
|
646
|
-
? parseInt(finalAttribute.max)
|
647
|
-
: 9007199254740991;
|
648
|
-
console.log(
|
649
|
-
`DEBUG: Creating integer attribute '${
|
650
|
-
finalAttribute.key
|
651
|
-
}' with min=${minValue}, max=${maxValue}, minType=${typeof minValue}, maxType=${typeof maxValue}`
|
652
|
-
);
|
653
|
-
await tryAwaitWithRetry(
|
654
|
-
async () =>
|
655
|
-
await db.createIntegerAttribute(
|
656
|
-
dbId,
|
657
|
-
collection.$id,
|
658
|
-
finalAttribute.key,
|
659
|
-
finalAttribute.required || false,
|
660
|
-
minValue,
|
661
|
-
maxValue,
|
662
|
-
finalAttribute.xdefault !== undefined && !finalAttribute.required
|
663
|
-
? finalAttribute.xdefault
|
664
|
-
: null,
|
665
|
-
finalAttribute.array || false
|
666
|
-
)
|
667
|
-
);
|
668
|
-
} else {
|
669
|
-
if (
|
670
|
-
finalAttribute.min &&
|
671
|
-
BigInt(finalAttribute.min) === BigInt(-9223372036854776000)
|
672
|
-
) {
|
673
|
-
finalAttribute.min = undefined;
|
674
|
-
}
|
675
|
-
if (
|
676
|
-
finalAttribute.max &&
|
677
|
-
BigInt(finalAttribute.max) === BigInt(9223372036854776000)
|
678
|
-
) {
|
679
|
-
finalAttribute.max = undefined;
|
680
|
-
}
|
681
|
-
const minValue =
|
682
|
-
finalAttribute.min !== undefined && finalAttribute.min !== null
|
683
|
-
? parseInt(finalAttribute.min)
|
684
|
-
: -9007199254740991;
|
685
|
-
const maxValue =
|
686
|
-
finalAttribute.max !== undefined && finalAttribute.max !== null
|
687
|
-
? parseInt(finalAttribute.max)
|
688
|
-
: 9007199254740991;
|
689
|
-
console.log(
|
690
|
-
`DEBUG: Updating integer attribute '${
|
691
|
-
finalAttribute.key
|
692
|
-
}' with min=${minValue}, max=${maxValue}, minType=${typeof minValue}, maxType=${typeof maxValue}`
|
693
|
-
);
|
694
|
-
await tryAwaitWithRetry(
|
695
|
-
async () =>
|
696
|
-
await db.updateIntegerAttribute(
|
697
|
-
dbId,
|
698
|
-
collection.$id,
|
699
|
-
finalAttribute.key,
|
700
|
-
finalAttribute.required || false,
|
701
|
-
finalAttribute.xdefault !== undefined && !finalAttribute.required
|
702
|
-
? finalAttribute.xdefault
|
703
|
-
: null,
|
704
|
-
minValue,
|
705
|
-
maxValue
|
706
|
-
)
|
707
|
-
);
|
708
|
-
}
|
709
|
-
break;
|
710
|
-
case "double":
|
711
|
-
case "float": // Backward compatibility
|
712
|
-
if (action === "create") {
|
713
|
-
await tryAwaitWithRetry(
|
714
|
-
async () =>
|
715
|
-
await db.createFloatAttribute(
|
716
|
-
dbId,
|
717
|
-
collection.$id,
|
718
|
-
finalAttribute.key,
|
719
|
-
finalAttribute.required || false,
|
720
|
-
finalAttribute.min,
|
721
|
-
finalAttribute.max,
|
722
|
-
finalAttribute.xdefault !== undefined && !finalAttribute.required
|
723
|
-
? finalAttribute.xdefault
|
724
|
-
: null,
|
725
|
-
finalAttribute.array || false
|
726
|
-
)
|
727
|
-
);
|
728
|
-
} else {
|
729
|
-
await tryAwaitWithRetry(
|
730
|
-
async () =>
|
731
|
-
await db.updateFloatAttribute(
|
732
|
-
dbId,
|
733
|
-
collection.$id,
|
734
|
-
finalAttribute.key,
|
735
|
-
finalAttribute.required || false,
|
736
|
-
finalAttribute.min,
|
737
|
-
finalAttribute.max,
|
738
|
-
finalAttribute.xdefault !== undefined && !finalAttribute.required
|
739
|
-
? finalAttribute.xdefault
|
740
|
-
: null
|
741
|
-
)
|
742
|
-
);
|
743
|
-
}
|
744
|
-
break;
|
745
|
-
case "boolean":
|
746
|
-
if (action === "create") {
|
747
|
-
await tryAwaitWithRetry(
|
748
|
-
async () =>
|
749
|
-
await db.createBooleanAttribute(
|
750
|
-
dbId,
|
751
|
-
collection.$id,
|
752
|
-
finalAttribute.key,
|
753
|
-
finalAttribute.required || false,
|
754
|
-
finalAttribute.xdefault !== undefined && !finalAttribute.required
|
755
|
-
? finalAttribute.xdefault
|
756
|
-
: null,
|
757
|
-
finalAttribute.array || false
|
758
|
-
)
|
759
|
-
);
|
760
|
-
} else {
|
761
|
-
await tryAwaitWithRetry(
|
762
|
-
async () =>
|
763
|
-
await db.updateBooleanAttribute(
|
764
|
-
dbId,
|
765
|
-
collection.$id,
|
766
|
-
finalAttribute.key,
|
767
|
-
finalAttribute.required || false,
|
768
|
-
finalAttribute.xdefault !== undefined && !finalAttribute.required
|
769
|
-
? finalAttribute.xdefault
|
770
|
-
: null
|
771
|
-
)
|
772
|
-
);
|
773
|
-
}
|
774
|
-
break;
|
775
|
-
case "datetime":
|
776
|
-
if (action === "create") {
|
777
|
-
await tryAwaitWithRetry(
|
778
|
-
async () =>
|
779
|
-
await db.createDatetimeAttribute(
|
780
|
-
dbId,
|
781
|
-
collection.$id,
|
782
|
-
finalAttribute.key,
|
783
|
-
finalAttribute.required || false,
|
784
|
-
finalAttribute.xdefault !== undefined && !finalAttribute.required
|
785
|
-
? finalAttribute.xdefault
|
786
|
-
: null,
|
787
|
-
finalAttribute.array || false
|
788
|
-
)
|
789
|
-
);
|
790
|
-
} else {
|
791
|
-
await tryAwaitWithRetry(
|
792
|
-
async () =>
|
793
|
-
await db.updateDatetimeAttribute(
|
794
|
-
dbId,
|
795
|
-
collection.$id,
|
796
|
-
finalAttribute.key,
|
797
|
-
finalAttribute.required || false,
|
798
|
-
finalAttribute.xdefault !== undefined && !finalAttribute.required
|
799
|
-
? finalAttribute.xdefault
|
800
|
-
: null
|
801
|
-
)
|
802
|
-
);
|
803
|
-
}
|
804
|
-
break;
|
805
|
-
case "email":
|
806
|
-
if (action === "create") {
|
807
|
-
await tryAwaitWithRetry(
|
808
|
-
async () =>
|
809
|
-
await db.createEmailAttribute(
|
810
|
-
dbId,
|
811
|
-
collection.$id,
|
812
|
-
finalAttribute.key,
|
813
|
-
finalAttribute.required || false,
|
814
|
-
finalAttribute.xdefault !== undefined && !finalAttribute.required
|
815
|
-
? finalAttribute.xdefault
|
816
|
-
: null,
|
817
|
-
finalAttribute.array || false
|
818
|
-
)
|
819
|
-
);
|
820
|
-
} else {
|
821
|
-
await tryAwaitWithRetry(
|
822
|
-
async () =>
|
823
|
-
await db.updateEmailAttribute(
|
824
|
-
dbId,
|
825
|
-
collection.$id,
|
826
|
-
finalAttribute.key,
|
827
|
-
finalAttribute.required || false,
|
828
|
-
finalAttribute.xdefault !== undefined && !finalAttribute.required
|
829
|
-
? finalAttribute.xdefault
|
830
|
-
: null
|
831
|
-
)
|
832
|
-
);
|
833
|
-
}
|
834
|
-
break;
|
835
|
-
case "ip":
|
836
|
-
if (action === "create") {
|
837
|
-
await tryAwaitWithRetry(
|
838
|
-
async () =>
|
839
|
-
await db.createIpAttribute(
|
840
|
-
dbId,
|
841
|
-
collection.$id,
|
842
|
-
finalAttribute.key,
|
843
|
-
finalAttribute.required || false,
|
844
|
-
finalAttribute.xdefault !== undefined && !finalAttribute.required
|
845
|
-
? finalAttribute.xdefault
|
846
|
-
: null,
|
847
|
-
finalAttribute.array || false
|
848
|
-
)
|
849
|
-
);
|
850
|
-
} else {
|
851
|
-
await tryAwaitWithRetry(
|
852
|
-
async () =>
|
853
|
-
await db.updateIpAttribute(
|
854
|
-
dbId,
|
855
|
-
collection.$id,
|
856
|
-
finalAttribute.key,
|
857
|
-
finalAttribute.required || false,
|
858
|
-
finalAttribute.xdefault !== undefined && !finalAttribute.required
|
859
|
-
? finalAttribute.xdefault
|
860
|
-
: null
|
861
|
-
)
|
862
|
-
);
|
863
|
-
}
|
864
|
-
break;
|
865
|
-
case "url":
|
866
|
-
if (action === "create") {
|
867
|
-
await tryAwaitWithRetry(
|
868
|
-
async () =>
|
869
|
-
await db.createUrlAttribute(
|
870
|
-
dbId,
|
871
|
-
collection.$id,
|
872
|
-
finalAttribute.key,
|
873
|
-
finalAttribute.required || false,
|
874
|
-
finalAttribute.xdefault !== undefined && !finalAttribute.required
|
875
|
-
? finalAttribute.xdefault
|
876
|
-
: null,
|
877
|
-
finalAttribute.array || false
|
878
|
-
)
|
879
|
-
);
|
880
|
-
} else {
|
881
|
-
await tryAwaitWithRetry(
|
882
|
-
async () =>
|
883
|
-
await db.updateUrlAttribute(
|
884
|
-
dbId,
|
885
|
-
collection.$id,
|
886
|
-
finalAttribute.key,
|
887
|
-
finalAttribute.required || false,
|
888
|
-
finalAttribute.xdefault !== undefined && !finalAttribute.required
|
889
|
-
? finalAttribute.xdefault
|
890
|
-
: null
|
891
|
-
)
|
892
|
-
);
|
893
|
-
}
|
894
|
-
break;
|
895
|
-
case "enum":
|
896
|
-
if (action === "create") {
|
897
|
-
await tryAwaitWithRetry(
|
898
|
-
async () =>
|
899
|
-
await db.createEnumAttribute(
|
900
|
-
dbId,
|
901
|
-
collection.$id,
|
902
|
-
finalAttribute.key,
|
903
|
-
finalAttribute.elements,
|
904
|
-
finalAttribute.required || false,
|
905
|
-
finalAttribute.xdefault !== undefined && !finalAttribute.required
|
906
|
-
? finalAttribute.xdefault
|
907
|
-
: null,
|
908
|
-
finalAttribute.array || false
|
909
|
-
)
|
910
|
-
);
|
911
|
-
} else {
|
912
|
-
await tryAwaitWithRetry(
|
913
|
-
async () =>
|
914
|
-
await db.updateEnumAttribute(
|
915
|
-
dbId,
|
916
|
-
collection.$id,
|
917
|
-
finalAttribute.key,
|
918
|
-
finalAttribute.elements,
|
919
|
-
finalAttribute.required || false,
|
920
|
-
finalAttribute.xdefault !== undefined && !finalAttribute.required
|
921
|
-
? finalAttribute.xdefault
|
922
|
-
: null
|
923
|
-
)
|
924
|
-
);
|
925
|
-
}
|
926
|
-
break;
|
927
|
-
case "relationship":
|
928
|
-
if (action === "create") {
|
929
|
-
await tryAwaitWithRetry(
|
930
|
-
async () =>
|
931
|
-
await db.createRelationshipAttribute(
|
932
|
-
dbId,
|
933
|
-
collection.$id,
|
934
|
-
relatedCollectionId!,
|
935
|
-
finalAttribute.relationType,
|
936
|
-
finalAttribute.twoWay,
|
937
|
-
finalAttribute.key,
|
938
|
-
finalAttribute.twoWayKey,
|
939
|
-
finalAttribute.onDelete
|
940
|
-
)
|
941
|
-
);
|
942
|
-
} else {
|
943
|
-
await tryAwaitWithRetry(
|
944
|
-
async () =>
|
945
|
-
await db.updateRelationshipAttribute(
|
946
|
-
dbId,
|
947
|
-
collection.$id,
|
948
|
-
finalAttribute.key,
|
949
|
-
finalAttribute.onDelete
|
950
|
-
)
|
951
|
-
);
|
952
1164
|
}
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
1165
|
+
}
|
1166
|
+
// ONLY queue relationship attributes that have actual unresolved dependencies
|
1167
|
+
if (!(relatedCollectionId && collectionFoundViaRelatedCollection)) {
|
1168
|
+
MessageFormatter.info(
|
1169
|
+
chalk.yellow(
|
1170
|
+
`⏳ Queueing relationship attribute '${finalAttribute.key}' - related collection '${finalAttribute.relatedCollection}' not found yet`
|
1171
|
+
)
|
1172
|
+
);
|
1173
|
+
enqueueOperation({
|
1174
|
+
type: "attribute",
|
1175
|
+
collectionId: collection.$id,
|
1176
|
+
collection: collection,
|
1177
|
+
attribute,
|
1178
|
+
dependencies: [finalAttribute.relatedCollection],
|
1179
|
+
});
|
1180
|
+
return "queued";
|
1181
|
+
}
|
1182
|
+
}
|
1183
|
+
finalAttribute = parseAttribute(finalAttribute);
|
1184
|
+
|
1185
|
+
|
1186
|
+
// Use adapter-based attribute creation/update
|
1187
|
+
if (action === "create") {
|
1188
|
+
await tryAwaitWithRetry(
|
1189
|
+
async () => await createAttributeViaAdapter(db, dbId, collection.$id, finalAttribute)
|
1190
|
+
);
|
1191
|
+
} else {
|
1192
|
+
await tryAwaitWithRetry(
|
1193
|
+
async () => await updateAttributeViaAdapter(db, dbId, collection.$id, finalAttribute)
|
1194
|
+
);
|
1195
|
+
}
|
1196
|
+
return "processed";
|
1197
|
+
};
|
960
1198
|
|
961
1199
|
/**
|
962
1200
|
* Enhanced collection attribute creation with proper status monitoring
|
963
1201
|
*/
|
964
1202
|
export const createUpdateCollectionAttributesWithStatusCheck = async (
|
965
|
-
db: Databases,
|
1203
|
+
db: Databases | DatabaseAdapter,
|
966
1204
|
dbId: string,
|
967
1205
|
collection: Models.Collection,
|
968
1206
|
attributes: Attribute[]
|
969
1207
|
): Promise<boolean> => {
|
970
|
-
|
1208
|
+
MessageFormatter.info(
|
971
1209
|
chalk.green(
|
972
1210
|
`Creating/Updating attributes for collection: ${collection.name} with status monitoring`
|
973
1211
|
)
|
@@ -987,7 +1225,7 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
|
|
987
1225
|
// Handle attribute removal first
|
988
1226
|
if (attributesToRemove.length > 0) {
|
989
1227
|
if (indexesToRemove.length > 0) {
|
990
|
-
|
1228
|
+
MessageFormatter.info(
|
991
1229
|
chalk.red(
|
992
1230
|
`Removing indexes as they rely on an attribute that is being removed: ${indexesToRemove
|
993
1231
|
.map((index) => index.key)
|
@@ -996,26 +1234,38 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
|
|
996
1234
|
);
|
997
1235
|
for (const index of indexesToRemove) {
|
998
1236
|
await tryAwaitWithRetry(
|
999
|
-
async () =>
|
1237
|
+
async () => {
|
1238
|
+
if (isDatabaseAdapter(db)) {
|
1239
|
+
await db.deleteIndex({ databaseId: dbId, tableId: collection.$id, key: index.key });
|
1240
|
+
} else {
|
1241
|
+
await db.deleteIndex(dbId, collection.$id, index.key);
|
1242
|
+
}
|
1243
|
+
}
|
1000
1244
|
);
|
1001
1245
|
await delay(500); // Longer delay for deletions
|
1002
1246
|
}
|
1003
1247
|
}
|
1004
1248
|
for (const attr of attributesToRemove) {
|
1005
|
-
|
1249
|
+
MessageFormatter.info(
|
1006
1250
|
chalk.red(
|
1007
1251
|
`Removing attribute: ${attr.key} as it is no longer in the collection`
|
1008
1252
|
)
|
1009
1253
|
);
|
1010
1254
|
await tryAwaitWithRetry(
|
1011
|
-
async () =>
|
1255
|
+
async () => {
|
1256
|
+
if (isDatabaseAdapter(db)) {
|
1257
|
+
await db.deleteAttribute({ databaseId: dbId, tableId: collection.$id, key: attr.key });
|
1258
|
+
} else {
|
1259
|
+
await db.deleteAttribute(dbId, collection.$id, attr.key);
|
1260
|
+
}
|
1261
|
+
}
|
1012
1262
|
);
|
1013
1263
|
await delay(500); // Longer delay for deletions
|
1014
1264
|
}
|
1015
1265
|
}
|
1016
1266
|
|
1017
1267
|
// First, get fresh collection data and determine which attributes actually need processing
|
1018
|
-
|
1268
|
+
MessageFormatter.info(
|
1019
1269
|
chalk.blue(
|
1020
1270
|
`Analyzing ${attributes.length} attributes to determine which need processing...`
|
1021
1271
|
)
|
@@ -1023,9 +1273,11 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
|
|
1023
1273
|
|
1024
1274
|
let currentCollection = collection;
|
1025
1275
|
try {
|
1026
|
-
currentCollection =
|
1276
|
+
currentCollection = isDatabaseAdapter(db)
|
1277
|
+
? (await db.getTable({ databaseId: dbId, tableId: collection.$id })).data
|
1278
|
+
: await db.getCollection(dbId, collection.$id);
|
1027
1279
|
} catch (error) {
|
1028
|
-
|
1280
|
+
MessageFormatter.info(
|
1029
1281
|
chalk.yellow(`Warning: Could not refresh collection data: ${error}`)
|
1030
1282
|
);
|
1031
1283
|
}
|
@@ -1040,24 +1292,32 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
|
|
1040
1292
|
existingAttributesMap.set(attr.key, attr)
|
1041
1293
|
);
|
1042
1294
|
} catch (error) {
|
1043
|
-
|
1295
|
+
MessageFormatter.info(
|
1044
1296
|
chalk.yellow(`Warning: Could not parse existing attributes: ${error}`)
|
1045
1297
|
);
|
1046
1298
|
}
|
1047
1299
|
|
1048
|
-
// Filter to only attributes that need processing (new or
|
1300
|
+
// Filter to only attributes that need processing (new, changed, or not yet processed)
|
1049
1301
|
const attributesToProcess = attributes.filter((attribute) => {
|
1302
|
+
// Skip if already processed in this session
|
1303
|
+
if (isAttributeProcessed(currentCollection.$id, attribute.key)) {
|
1304
|
+
MessageFormatter.info(
|
1305
|
+
chalk.gray(`⏭️ Attribute '${attribute.key}' already processed in this session (skipping)`)
|
1306
|
+
);
|
1307
|
+
return false;
|
1308
|
+
}
|
1309
|
+
|
1050
1310
|
const existing = existingAttributesMap.get(attribute.key);
|
1051
1311
|
if (!existing) {
|
1052
|
-
|
1312
|
+
MessageFormatter.info(`➕ New attribute: ${attribute.key}`);
|
1053
1313
|
return true;
|
1054
1314
|
}
|
1055
1315
|
|
1056
1316
|
const needsUpdate = !attributesSame(existing, attribute);
|
1057
1317
|
if (needsUpdate) {
|
1058
|
-
|
1318
|
+
MessageFormatter.info(`🔄 Changed attribute: ${attribute.key}`);
|
1059
1319
|
} else {
|
1060
|
-
|
1320
|
+
MessageFormatter.info(
|
1061
1321
|
chalk.gray(`✅ Unchanged attribute: ${attribute.key} (skipping)`)
|
1062
1322
|
);
|
1063
1323
|
}
|
@@ -1065,7 +1325,7 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
|
|
1065
1325
|
});
|
1066
1326
|
|
1067
1327
|
if (attributesToProcess.length === 0) {
|
1068
|
-
|
1328
|
+
MessageFormatter.info(
|
1069
1329
|
chalk.green(
|
1070
1330
|
`✅ All ${attributes.length} attributes are already up to date for collection: ${collection.name}`
|
1071
1331
|
)
|
@@ -1073,7 +1333,7 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
|
|
1073
1333
|
return true;
|
1074
1334
|
}
|
1075
1335
|
|
1076
|
-
|
1336
|
+
MessageFormatter.info(
|
1077
1337
|
chalk.blue(
|
1078
1338
|
`Creating ${attributesToProcess.length} attributes sequentially with status monitoring...`
|
1079
1339
|
)
|
@@ -1090,7 +1350,7 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
|
|
1090
1350
|
const attributesToProcessThisRound = [...remainingAttributes];
|
1091
1351
|
remainingAttributes = []; // Reset for next iteration
|
1092
1352
|
|
1093
|
-
|
1353
|
+
MessageFormatter.info(
|
1094
1354
|
chalk.blue(
|
1095
1355
|
`\n=== Attempt ${
|
1096
1356
|
overallRetryCount + 1
|
@@ -1101,7 +1361,7 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
|
|
1101
1361
|
);
|
1102
1362
|
|
1103
1363
|
for (const attribute of attributesToProcessThisRound) {
|
1104
|
-
|
1364
|
+
MessageFormatter.info(
|
1105
1365
|
chalk.blue(`\n--- Processing attribute: ${attribute.key} ---`)
|
1106
1366
|
);
|
1107
1367
|
|
@@ -1113,15 +1373,20 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
|
|
1113
1373
|
);
|
1114
1374
|
|
1115
1375
|
if (success) {
|
1116
|
-
|
1376
|
+
MessageFormatter.info(
|
1117
1377
|
chalk.green(`✅ Successfully created attribute: ${attribute.key}`)
|
1118
1378
|
);
|
1119
1379
|
|
1380
|
+
// Mark this specific attribute as processed
|
1381
|
+
markAttributeProcessed(currentCollection.$id, attribute.key);
|
1382
|
+
|
1120
1383
|
// Get updated collection data for next iteration
|
1121
1384
|
try {
|
1122
|
-
currentCollection =
|
1385
|
+
currentCollection = isDatabaseAdapter(db)
|
1386
|
+
? (await db.getTable({ databaseId: dbId, tableId: collection.$id })).data as Models.Collection
|
1387
|
+
: await db.getCollection(dbId, collection.$id);
|
1123
1388
|
} catch (error) {
|
1124
|
-
|
1389
|
+
MessageFormatter.info(
|
1125
1390
|
chalk.yellow(`Warning: Could not refresh collection data: ${error}`)
|
1126
1391
|
);
|
1127
1392
|
}
|
@@ -1129,7 +1394,7 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
|
|
1129
1394
|
// Add delay between successful attributes
|
1130
1395
|
await delay(1000);
|
1131
1396
|
} else {
|
1132
|
-
|
1397
|
+
MessageFormatter.info(
|
1133
1398
|
chalk.red(
|
1134
1399
|
`❌ Failed to create attribute: ${attribute.key}, will retry in next round`
|
1135
1400
|
)
|
@@ -1139,7 +1404,7 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
|
|
1139
1404
|
}
|
1140
1405
|
|
1141
1406
|
if (remainingAttributes.length === 0) {
|
1142
|
-
|
1407
|
+
MessageFormatter.info(
|
1143
1408
|
chalk.green(
|
1144
1409
|
`\n✅ Successfully created all ${attributesToProcess.length} attributes for collection: ${collection.name}`
|
1145
1410
|
)
|
@@ -1150,7 +1415,7 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
|
|
1150
1415
|
overallRetryCount++;
|
1151
1416
|
|
1152
1417
|
if (overallRetryCount < maxOverallRetries) {
|
1153
|
-
|
1418
|
+
MessageFormatter.info(
|
1154
1419
|
chalk.yellow(
|
1155
1420
|
`\n⏳ Waiting 5 seconds before retrying ${attributesToProcess.length} failed attributes...`
|
1156
1421
|
)
|
@@ -1159,10 +1424,12 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
|
|
1159
1424
|
|
1160
1425
|
// Refresh collection data before retry
|
1161
1426
|
try {
|
1162
|
-
currentCollection =
|
1163
|
-
|
1427
|
+
currentCollection = isDatabaseAdapter(db)
|
1428
|
+
? (await db.getTable({ databaseId: dbId, tableId: collection.$id })).data as Models.Collection
|
1429
|
+
: await db.getCollection(dbId, collection.$id);
|
1430
|
+
MessageFormatter.info(`Refreshed collection data for retry`);
|
1164
1431
|
} catch (error) {
|
1165
|
-
|
1432
|
+
MessageFormatter.info(
|
1166
1433
|
chalk.yellow(
|
1167
1434
|
`Warning: Could not refresh collection data for retry: ${error}`
|
1168
1435
|
)
|
@@ -1173,7 +1440,7 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
|
|
1173
1440
|
|
1174
1441
|
// If we get here, some attributes still failed after all retries
|
1175
1442
|
if (attributesToProcess.length > 0) {
|
1176
|
-
|
1443
|
+
MessageFormatter.info(
|
1177
1444
|
chalk.red(
|
1178
1445
|
`\n❌ Failed to create ${
|
1179
1446
|
attributesToProcess.length
|
@@ -1182,7 +1449,7 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
|
|
1182
1449
|
.join(", ")}`
|
1183
1450
|
)
|
1184
1451
|
);
|
1185
|
-
|
1452
|
+
MessageFormatter.info(
|
1186
1453
|
chalk.red(
|
1187
1454
|
`This may indicate a fundamental issue with the attribute definitions or Appwrite instance`
|
1188
1455
|
)
|
@@ -1190,7 +1457,7 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
|
|
1190
1457
|
return false;
|
1191
1458
|
}
|
1192
1459
|
|
1193
|
-
|
1460
|
+
MessageFormatter.info(
|
1194
1461
|
chalk.green(
|
1195
1462
|
`\n✅ Successfully created all ${attributes.length} attributes for collection: ${collection.name}`
|
1196
1463
|
)
|
@@ -1199,12 +1466,12 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
|
|
1199
1466
|
};
|
1200
1467
|
|
1201
1468
|
export const createUpdateCollectionAttributes = async (
|
1202
|
-
db: Databases,
|
1469
|
+
db: Databases | DatabaseAdapter,
|
1203
1470
|
dbId: string,
|
1204
1471
|
collection: Models.Collection,
|
1205
1472
|
attributes: Attribute[]
|
1206
1473
|
): Promise<void> => {
|
1207
|
-
|
1474
|
+
MessageFormatter.info(
|
1208
1475
|
chalk.green(
|
1209
1476
|
`Creating/Updating attributes for collection: ${collection.name}`
|
1210
1477
|
)
|
@@ -1223,7 +1490,7 @@ export const createUpdateCollectionAttributes = async (
|
|
1223
1490
|
|
1224
1491
|
if (attributesToRemove.length > 0) {
|
1225
1492
|
if (indexesToRemove.length > 0) {
|
1226
|
-
|
1493
|
+
MessageFormatter.info(
|
1227
1494
|
chalk.red(
|
1228
1495
|
`Removing indexes as they rely on an attribute that is being removed: ${indexesToRemove
|
1229
1496
|
.map((index) => index.key)
|
@@ -1232,19 +1499,31 @@ export const createUpdateCollectionAttributes = async (
|
|
1232
1499
|
);
|
1233
1500
|
for (const index of indexesToRemove) {
|
1234
1501
|
await tryAwaitWithRetry(
|
1235
|
-
async () =>
|
1502
|
+
async () => {
|
1503
|
+
if (isDatabaseAdapter(db)) {
|
1504
|
+
await db.deleteIndex({ databaseId: dbId, tableId: collection.$id, key: index.key });
|
1505
|
+
} else {
|
1506
|
+
await db.deleteIndex(dbId, collection.$id, index.key);
|
1507
|
+
}
|
1508
|
+
}
|
1236
1509
|
);
|
1237
1510
|
await delay(100);
|
1238
1511
|
}
|
1239
1512
|
}
|
1240
1513
|
for (const attr of attributesToRemove) {
|
1241
|
-
|
1514
|
+
MessageFormatter.info(
|
1242
1515
|
chalk.red(
|
1243
1516
|
`Removing attribute: ${attr.key} as it is no longer in the collection`
|
1244
1517
|
)
|
1245
1518
|
);
|
1246
1519
|
await tryAwaitWithRetry(
|
1247
|
-
async () =>
|
1520
|
+
async () => {
|
1521
|
+
if (isDatabaseAdapter(db)) {
|
1522
|
+
await db.deleteAttribute({ databaseId: dbId, tableId: collection.$id, key: attr.key });
|
1523
|
+
} else {
|
1524
|
+
await db.deleteAttribute(dbId, collection.$id, attr.key);
|
1525
|
+
}
|
1526
|
+
}
|
1248
1527
|
);
|
1249
1528
|
await delay(50);
|
1250
1529
|
}
|
@@ -1263,14 +1542,14 @@ export const createUpdateCollectionAttributes = async (
|
|
1263
1542
|
const results = await Promise.allSettled(attributePromises);
|
1264
1543
|
results.forEach((result) => {
|
1265
1544
|
if (result.status === "rejected") {
|
1266
|
-
|
1545
|
+
MessageFormatter.error("An attribute promise was rejected:", result.reason);
|
1267
1546
|
}
|
1268
1547
|
});
|
1269
1548
|
|
1270
1549
|
// Add delay after each batch
|
1271
1550
|
await delay(200);
|
1272
1551
|
}
|
1273
|
-
|
1552
|
+
MessageFormatter.info(
|
1274
1553
|
`Finished creating/updating attributes for collection: ${collection.name}`
|
1275
1554
|
);
|
1276
1555
|
};
|