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
@@ -64,7 +64,14 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
64
64
|
|
65
65
|
async createRow(params: CreateRowParams): Promise<ApiResponse> {
|
66
66
|
try {
|
67
|
-
|
67
|
+
// Remap 'id' to 'rowId' for TablesDB SDK compatibility
|
68
|
+
const result = await this.tablesDB.createRow({
|
69
|
+
databaseId: params.databaseId,
|
70
|
+
tableId: params.tableId,
|
71
|
+
rowId: params.id,
|
72
|
+
data: params.data,
|
73
|
+
permissions: params.permissions
|
74
|
+
});
|
68
75
|
return {
|
69
76
|
data: result,
|
70
77
|
rows: [result]
|
@@ -80,7 +87,13 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
80
87
|
|
81
88
|
async updateRow(params: UpdateRowParams): Promise<ApiResponse> {
|
82
89
|
try {
|
83
|
-
const result = await this.tablesDB.updateRow(
|
90
|
+
const result = await this.tablesDB.updateRow(
|
91
|
+
params.databaseId,
|
92
|
+
params.tableId,
|
93
|
+
params.id,
|
94
|
+
params.data,
|
95
|
+
params.permissions || []
|
96
|
+
);
|
84
97
|
return {
|
85
98
|
data: result,
|
86
99
|
rows: [result]
|
@@ -93,10 +106,14 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
93
106
|
);
|
94
107
|
}
|
95
108
|
}
|
96
|
-
|
109
|
+
|
97
110
|
async deleteRow(params: DeleteRowParams): Promise<ApiResponse> {
|
98
111
|
try {
|
99
|
-
const result = await this.tablesDB.deleteRow(
|
112
|
+
const result = await this.tablesDB.deleteRow(
|
113
|
+
params.databaseId,
|
114
|
+
params.tableId,
|
115
|
+
params.id
|
116
|
+
);
|
100
117
|
return { data: result };
|
101
118
|
} catch (error) {
|
102
119
|
throw new AdapterError(
|
@@ -106,10 +123,14 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
106
123
|
);
|
107
124
|
}
|
108
125
|
}
|
109
|
-
|
126
|
+
|
110
127
|
async getRow(params: { databaseId: string; tableId: string; id: string }): Promise<ApiResponse> {
|
111
128
|
try {
|
112
|
-
const result = await this.tablesDB.getRow(
|
129
|
+
const result = await this.tablesDB.getRow(
|
130
|
+
params.databaseId,
|
131
|
+
params.tableId,
|
132
|
+
params.id
|
133
|
+
);
|
113
134
|
return {
|
114
135
|
data: result,
|
115
136
|
rows: [result]
|
@@ -143,7 +164,14 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
143
164
|
|
144
165
|
async createTable(params: CreateTableParams): Promise<ApiResponse> {
|
145
166
|
try {
|
146
|
-
const result = await this.tablesDB.createTable(
|
167
|
+
const result = await this.tablesDB.createTable(
|
168
|
+
params.databaseId,
|
169
|
+
params.id, // tableId
|
170
|
+
params.name,
|
171
|
+
params.permissions || [],
|
172
|
+
params.documentSecurity ?? false,
|
173
|
+
params.enabled ?? true
|
174
|
+
);
|
147
175
|
return {
|
148
176
|
data: result,
|
149
177
|
tables: [result]
|
@@ -159,7 +187,14 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
159
187
|
|
160
188
|
async updateTable(params: UpdateTableParams): Promise<ApiResponse> {
|
161
189
|
try {
|
162
|
-
const result = await this.tablesDB.updateTable(
|
190
|
+
const result = await this.tablesDB.updateTable(
|
191
|
+
params.databaseId,
|
192
|
+
params.id, // tableId
|
193
|
+
params.name,
|
194
|
+
params.permissions,
|
195
|
+
params.documentSecurity,
|
196
|
+
params.enabled
|
197
|
+
);
|
163
198
|
return {
|
164
199
|
data: result,
|
165
200
|
tables: [result]
|
@@ -172,10 +207,13 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
172
207
|
);
|
173
208
|
}
|
174
209
|
}
|
175
|
-
|
210
|
+
|
176
211
|
async deleteTable(params: DeleteTableParams): Promise<ApiResponse> {
|
177
212
|
try {
|
178
|
-
const result = await this.tablesDB.deleteTable(
|
213
|
+
const result = await this.tablesDB.deleteTable(
|
214
|
+
params.databaseId,
|
215
|
+
params.tableId
|
216
|
+
);
|
179
217
|
return { data: result };
|
180
218
|
} catch (error) {
|
181
219
|
throw new AdapterError(
|
@@ -185,10 +223,13 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
185
223
|
);
|
186
224
|
}
|
187
225
|
}
|
188
|
-
|
226
|
+
|
189
227
|
async getTable(params: GetTableParams): Promise<ApiResponse> {
|
190
228
|
try {
|
191
|
-
const result = await this.tablesDB.getTable(
|
229
|
+
const result = await this.tablesDB.getTable(
|
230
|
+
params.databaseId,
|
231
|
+
params.tableId
|
232
|
+
);
|
192
233
|
return {
|
193
234
|
data: result,
|
194
235
|
tables: [result]
|
@@ -221,7 +262,14 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
221
262
|
|
222
263
|
async createIndex(params: CreateIndexParams): Promise<ApiResponse> {
|
223
264
|
try {
|
224
|
-
const result = await this.tablesDB.createIndex(
|
265
|
+
const result = await this.tablesDB.createIndex(
|
266
|
+
params.databaseId,
|
267
|
+
params.tableId,
|
268
|
+
params.key,
|
269
|
+
params.type,
|
270
|
+
params.attributes,
|
271
|
+
params.orders || []
|
272
|
+
);
|
225
273
|
return { data: result };
|
226
274
|
} catch (error) {
|
227
275
|
throw new AdapterError(
|
@@ -231,10 +279,14 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
231
279
|
);
|
232
280
|
}
|
233
281
|
}
|
234
|
-
|
282
|
+
|
235
283
|
async deleteIndex(params: DeleteIndexParams): Promise<ApiResponse> {
|
236
284
|
try {
|
237
|
-
const result = await this.tablesDB.deleteIndex(
|
285
|
+
const result = await this.tablesDB.deleteIndex(
|
286
|
+
params.databaseId,
|
287
|
+
params.tableId,
|
288
|
+
params.key
|
289
|
+
);
|
238
290
|
return { data: result };
|
239
291
|
} catch (error) {
|
240
292
|
throw new AdapterError(
|
@@ -246,13 +298,140 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
246
298
|
}
|
247
299
|
|
248
300
|
// Attribute Operations
|
249
|
-
async createAttribute(params: CreateAttributeParams): Promise<ApiResponse> {
|
250
|
-
try {
|
251
|
-
//
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
301
|
+
async createAttribute(params: CreateAttributeParams): Promise<ApiResponse> {
|
302
|
+
try {
|
303
|
+
// TablesDB uses type-specific attribute methods like the legacy SDK
|
304
|
+
let result;
|
305
|
+
|
306
|
+
switch (params.type.toLowerCase()) {
|
307
|
+
case 'string':
|
308
|
+
result = await this.tablesDB.createStringAttribute(
|
309
|
+
params.databaseId,
|
310
|
+
params.tableId,
|
311
|
+
params.key,
|
312
|
+
params.size || 255,
|
313
|
+
params.required ?? false,
|
314
|
+
params.default,
|
315
|
+
params.array ?? false,
|
316
|
+
params.encrypt ?? false
|
317
|
+
);
|
318
|
+
break;
|
319
|
+
|
320
|
+
case 'integer':
|
321
|
+
result = await this.tablesDB.createIntegerAttribute(
|
322
|
+
params.databaseId,
|
323
|
+
params.tableId,
|
324
|
+
params.key,
|
325
|
+
params.required ?? false,
|
326
|
+
params.min,
|
327
|
+
params.max,
|
328
|
+
params.default,
|
329
|
+
params.array ?? false
|
330
|
+
);
|
331
|
+
break;
|
332
|
+
|
333
|
+
case 'float':
|
334
|
+
case 'double':
|
335
|
+
result = await this.tablesDB.createFloatAttribute(
|
336
|
+
params.databaseId,
|
337
|
+
params.tableId,
|
338
|
+
params.key,
|
339
|
+
params.required ?? false,
|
340
|
+
params.min,
|
341
|
+
params.max,
|
342
|
+
params.default,
|
343
|
+
params.array ?? false
|
344
|
+
);
|
345
|
+
break;
|
346
|
+
|
347
|
+
case 'boolean':
|
348
|
+
result = await this.tablesDB.createBooleanAttribute(
|
349
|
+
params.databaseId,
|
350
|
+
params.tableId,
|
351
|
+
params.key,
|
352
|
+
params.required ?? false,
|
353
|
+
params.default,
|
354
|
+
params.array ?? false
|
355
|
+
);
|
356
|
+
break;
|
357
|
+
|
358
|
+
case 'datetime':
|
359
|
+
result = await this.tablesDB.createDatetimeAttribute(
|
360
|
+
params.databaseId,
|
361
|
+
params.tableId,
|
362
|
+
params.key,
|
363
|
+
params.required ?? false,
|
364
|
+
params.default,
|
365
|
+
params.array ?? false
|
366
|
+
);
|
367
|
+
break;
|
368
|
+
|
369
|
+
case 'email':
|
370
|
+
result = await this.tablesDB.createEmailAttribute(
|
371
|
+
params.databaseId,
|
372
|
+
params.tableId,
|
373
|
+
params.key,
|
374
|
+
params.required ?? false,
|
375
|
+
params.default,
|
376
|
+
params.array ?? false
|
377
|
+
);
|
378
|
+
break;
|
379
|
+
|
380
|
+
case 'enum':
|
381
|
+
result = await this.tablesDB.createEnumAttribute(
|
382
|
+
params.databaseId,
|
383
|
+
params.tableId,
|
384
|
+
params.key,
|
385
|
+
params.elements || [],
|
386
|
+
params.required ?? false,
|
387
|
+
params.default,
|
388
|
+
params.array ?? false
|
389
|
+
);
|
390
|
+
break;
|
391
|
+
|
392
|
+
case 'ip':
|
393
|
+
result = await this.tablesDB.createIpAttribute(
|
394
|
+
params.databaseId,
|
395
|
+
params.tableId,
|
396
|
+
params.key,
|
397
|
+
params.required ?? false,
|
398
|
+
params.default,
|
399
|
+
params.array ?? false
|
400
|
+
);
|
401
|
+
break;
|
402
|
+
|
403
|
+
case 'url':
|
404
|
+
result = await this.tablesDB.createUrlAttribute(
|
405
|
+
params.databaseId,
|
406
|
+
params.tableId,
|
407
|
+
params.key,
|
408
|
+
params.required ?? false,
|
409
|
+
params.default,
|
410
|
+
params.array ?? false
|
411
|
+
);
|
412
|
+
break;
|
413
|
+
|
414
|
+
case 'relationship':
|
415
|
+
result = await this.tablesDB.createRelationshipAttribute(
|
416
|
+
params.databaseId,
|
417
|
+
params.tableId,
|
418
|
+
params.key,
|
419
|
+
params.relatedCollection || '',
|
420
|
+
params.type || 'oneToOne',
|
421
|
+
params.twoWay ?? false,
|
422
|
+
params.onDelete || 'restrict'
|
423
|
+
);
|
424
|
+
break;
|
425
|
+
|
426
|
+
default:
|
427
|
+
throw new AdapterError(
|
428
|
+
`Unsupported attribute type: ${params.type}`,
|
429
|
+
'UNSUPPORTED_ATTRIBUTE_TYPE'
|
430
|
+
);
|
431
|
+
}
|
432
|
+
|
433
|
+
return { data: result };
|
434
|
+
} catch (error) {
|
256
435
|
throw new AdapterError(
|
257
436
|
`Failed to create attribute: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
258
437
|
'CREATE_ATTRIBUTE_FAILED',
|
@@ -261,12 +440,19 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
261
440
|
}
|
262
441
|
}
|
263
442
|
|
264
|
-
async updateAttribute(params: UpdateAttributeParams): Promise<ApiResponse> {
|
265
|
-
try {
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
443
|
+
async updateAttribute(params: UpdateAttributeParams): Promise<ApiResponse> {
|
444
|
+
try {
|
445
|
+
// TablesDB uses type-specific update methods or generic updateAttribute with positional params
|
446
|
+
// Try type-specific first, fallback to generic
|
447
|
+
const result = await this.tablesDB.updateStringAttribute(
|
448
|
+
params.databaseId,
|
449
|
+
params.tableId,
|
450
|
+
params.key,
|
451
|
+
params.required ?? false,
|
452
|
+
params.default
|
453
|
+
);
|
454
|
+
return { data: result };
|
455
|
+
} catch (error) {
|
270
456
|
throw new AdapterError(
|
271
457
|
`Failed to update attribute: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
272
458
|
'UPDATE_ATTRIBUTE_FAILED',
|
@@ -274,13 +460,16 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
274
460
|
);
|
275
461
|
}
|
276
462
|
}
|
277
|
-
|
278
|
-
async deleteAttribute(params: DeleteAttributeParams): Promise<ApiResponse> {
|
279
|
-
try {
|
280
|
-
const
|
281
|
-
|
282
|
-
|
283
|
-
|
463
|
+
|
464
|
+
async deleteAttribute(params: DeleteAttributeParams): Promise<ApiResponse> {
|
465
|
+
try {
|
466
|
+
const result = await this.tablesDB.deleteAttribute(
|
467
|
+
params.databaseId,
|
468
|
+
params.tableId,
|
469
|
+
params.key
|
470
|
+
);
|
471
|
+
return { data: result };
|
472
|
+
} catch (error) {
|
284
473
|
throw new AdapterError(
|
285
474
|
`Failed to delete attribute: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
286
475
|
'DELETE_ATTRIBUTE_FAILED',
|
@@ -429,4 +618,4 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
429
618
|
);
|
430
619
|
}
|
431
620
|
}
|
432
|
-
}
|
621
|
+
}
|
@@ -0,0 +1,277 @@
|
|
1
|
+
import type { Storage, Models } from "node-appwrite";
|
2
|
+
import JSZip from "jszip";
|
3
|
+
import { ID, Query } from "node-appwrite";
|
4
|
+
import { InputFile } from "node-appwrite/file";
|
5
|
+
import pLimit from "p-limit";
|
6
|
+
import { MessageFormatter } from "../../shared/messageFormatter.js";
|
7
|
+
import { logger } from "../../shared/logging.js";
|
8
|
+
import type { BucketManifest, BucketFileMetadata } from "../schemas/bucketManifest.js";
|
9
|
+
import { ulid } from "ulidx";
|
10
|
+
|
11
|
+
export interface BucketBackupOptions {
|
12
|
+
compressionLevel?: number; // 0-9, default 6
|
13
|
+
parallelDownloads?: number; // Number of concurrent downloads, default 10
|
14
|
+
onProgress?: (current: number, total: number, fileName: string) => void;
|
15
|
+
}
|
16
|
+
|
17
|
+
export interface BucketBackupResult {
|
18
|
+
backupFileId: string;
|
19
|
+
manifestFileId: string;
|
20
|
+
fileCount: number;
|
21
|
+
totalSizeBytes: number;
|
22
|
+
zipSizeBytes: number;
|
23
|
+
status: 'completed' | 'partial' | 'failed';
|
24
|
+
errors?: string[];
|
25
|
+
}
|
26
|
+
|
27
|
+
/**
|
28
|
+
* Downloads all files from a bucket in parallel and creates a ZIP backup
|
29
|
+
*/
|
30
|
+
export async function backupBucket(
|
31
|
+
storage: Storage,
|
32
|
+
bucketId: string,
|
33
|
+
backupBucketId: string,
|
34
|
+
options: BucketBackupOptions = {}
|
35
|
+
): Promise<BucketBackupResult> {
|
36
|
+
const {
|
37
|
+
compressionLevel = 6,
|
38
|
+
parallelDownloads = 10,
|
39
|
+
onProgress
|
40
|
+
} = options;
|
41
|
+
|
42
|
+
const errors: string[] = [];
|
43
|
+
let totalSizeBytes = 0;
|
44
|
+
|
45
|
+
try {
|
46
|
+
// Step 1: Get bucket metadata
|
47
|
+
MessageFormatter.info(`Fetching bucket metadata for ${bucketId}`, { prefix: "Backup" });
|
48
|
+
const bucket = await storage.getBucket(bucketId);
|
49
|
+
|
50
|
+
// Step 2: List all files in bucket with pagination
|
51
|
+
MessageFormatter.info(`Listing all files in bucket ${bucketId}`, { prefix: "Backup" });
|
52
|
+
const allFiles: Models.File[] = [];
|
53
|
+
let lastFileId: string | undefined;
|
54
|
+
|
55
|
+
while (true) {
|
56
|
+
const queries = [Query.limit(100)];
|
57
|
+
if (lastFileId) {
|
58
|
+
queries.push(Query.cursorAfter(lastFileId));
|
59
|
+
}
|
60
|
+
|
61
|
+
const filesPage = await storage.listFiles(bucketId, queries);
|
62
|
+
allFiles.push(...filesPage.files);
|
63
|
+
|
64
|
+
if (filesPage.files.length < 100) break;
|
65
|
+
lastFileId = filesPage.files[filesPage.files.length - 1].$id;
|
66
|
+
}
|
67
|
+
|
68
|
+
MessageFormatter.info(`Found ${allFiles.length} files to backup`, { prefix: "Backup" });
|
69
|
+
|
70
|
+
if (allFiles.length === 0) {
|
71
|
+
// Empty bucket - create minimal backup
|
72
|
+
const manifest: BucketManifest = {
|
73
|
+
version: "1.0",
|
74
|
+
bucketId: bucket.$id,
|
75
|
+
bucketName: bucket.name,
|
76
|
+
createdAt: new Date().toISOString(),
|
77
|
+
fileCount: 0,
|
78
|
+
totalSizeBytes: 0,
|
79
|
+
compression: bucket.compression === 'gzip' ? 'gzip' : 'none',
|
80
|
+
files: [],
|
81
|
+
bucketConfiguration: {
|
82
|
+
$permissions: bucket.$permissions,
|
83
|
+
fileSecurity: bucket.fileSecurity,
|
84
|
+
enabled: bucket.enabled,
|
85
|
+
maximumFileSize: bucket.maximumFileSize,
|
86
|
+
allowedFileExtensions: bucket.allowedFileExtensions,
|
87
|
+
compression: bucket.compression,
|
88
|
+
encryption: bucket.encryption,
|
89
|
+
antivirus: bucket.antivirus
|
90
|
+
}
|
91
|
+
};
|
92
|
+
|
93
|
+
const manifestFileId = await uploadManifest(storage, backupBucketId, bucketId, manifest);
|
94
|
+
|
95
|
+
return {
|
96
|
+
backupFileId: '',
|
97
|
+
manifestFileId,
|
98
|
+
fileCount: 0,
|
99
|
+
totalSizeBytes: 0,
|
100
|
+
zipSizeBytes: 0,
|
101
|
+
status: 'completed'
|
102
|
+
};
|
103
|
+
}
|
104
|
+
|
105
|
+
// Step 3: Download all files in parallel with concurrency limit
|
106
|
+
MessageFormatter.info(`Downloading ${allFiles.length} files in parallel (max ${parallelDownloads} concurrent)`, { prefix: "Backup" });
|
107
|
+
|
108
|
+
const limit = pLimit(parallelDownloads);
|
109
|
+
const downloadedFiles: Map<string, { buffer: Buffer; file: Models.File }> = new Map();
|
110
|
+
let successCount = 0;
|
111
|
+
let errorCount = 0;
|
112
|
+
|
113
|
+
const downloadTasks = allFiles.map((file, index) =>
|
114
|
+
limit(async () => {
|
115
|
+
try {
|
116
|
+
const fileBuffer = await storage.getFileDownload(bucketId, file.$id);
|
117
|
+
const buffer = Buffer.from(fileBuffer as ArrayBuffer);
|
118
|
+
|
119
|
+
downloadedFiles.set(file.$id, { buffer, file });
|
120
|
+
successCount++;
|
121
|
+
totalSizeBytes += file.sizeOriginal || buffer.length;
|
122
|
+
|
123
|
+
if (onProgress) {
|
124
|
+
onProgress(successCount + errorCount, allFiles.length, file.name);
|
125
|
+
}
|
126
|
+
|
127
|
+
logger.debug(`Downloaded file ${file.name}`, {
|
128
|
+
fileId: file.$id,
|
129
|
+
size: buffer.length
|
130
|
+
});
|
131
|
+
} catch (error) {
|
132
|
+
errorCount++;
|
133
|
+
const errorMsg = `Failed to download file ${file.name} (${file.$id}): ${error instanceof Error ? error.message : String(error)}`;
|
134
|
+
errors.push(errorMsg);
|
135
|
+
logger.error(errorMsg);
|
136
|
+
|
137
|
+
if (onProgress) {
|
138
|
+
onProgress(successCount + errorCount, allFiles.length, file.name);
|
139
|
+
}
|
140
|
+
}
|
141
|
+
})
|
142
|
+
);
|
143
|
+
|
144
|
+
await Promise.all(downloadTasks);
|
145
|
+
|
146
|
+
if (successCount === 0) {
|
147
|
+
throw new Error(`Failed to download any files from bucket ${bucketId}`);
|
148
|
+
}
|
149
|
+
|
150
|
+
MessageFormatter.info(`Successfully downloaded ${successCount}/${allFiles.length} files`, { prefix: "Backup" });
|
151
|
+
|
152
|
+
// Step 4: Create ZIP archive with all files
|
153
|
+
MessageFormatter.info(`Creating ZIP archive for bucket ${bucketId}`, { prefix: "Backup" });
|
154
|
+
const zip = new JSZip();
|
155
|
+
|
156
|
+
for (const [fileId, { buffer, file }] of downloadedFiles.entries()) {
|
157
|
+
// Preserve file structure by using file name
|
158
|
+
const fileName = file.name || `file_${fileId}`;
|
159
|
+
zip.file(fileName, new Uint8Array(buffer));
|
160
|
+
}
|
161
|
+
|
162
|
+
// Generate ZIP buffer
|
163
|
+
const zipBuffer = await zip.generateAsync({
|
164
|
+
type: "nodebuffer",
|
165
|
+
compression: "DEFLATE",
|
166
|
+
compressionOptions: { level: compressionLevel }
|
167
|
+
});
|
168
|
+
|
169
|
+
const zipSizeBytes = zipBuffer.length;
|
170
|
+
MessageFormatter.info(`ZIP archive created: ${MessageFormatter.formatBytes(zipSizeBytes)}`, { prefix: "Backup" });
|
171
|
+
|
172
|
+
// Step 5: Create manifest
|
173
|
+
const fileMetadata: BucketFileMetadata[] = Array.from(downloadedFiles.values()).map(({ file }) => ({
|
174
|
+
$id: file.$id,
|
175
|
+
name: file.name,
|
176
|
+
size: file.sizeOriginal,
|
177
|
+
mimeType: file.mimeType,
|
178
|
+
$permissions: file.$permissions,
|
179
|
+
chunksCount: file.chunksTotal,
|
180
|
+
signature: file.signature,
|
181
|
+
$createdAt: file.$createdAt,
|
182
|
+
$updatedAt: file.$updatedAt
|
183
|
+
}));
|
184
|
+
|
185
|
+
const manifest: BucketManifest = {
|
186
|
+
version: "1.0",
|
187
|
+
bucketId: bucket.$id,
|
188
|
+
bucketName: bucket.name,
|
189
|
+
createdAt: new Date().toISOString(),
|
190
|
+
fileCount: successCount,
|
191
|
+
totalSizeBytes,
|
192
|
+
compression: bucket.compression === 'gzip' ? 'gzip' : 'none',
|
193
|
+
files: fileMetadata,
|
194
|
+
bucketConfiguration: {
|
195
|
+
$permissions: bucket.$permissions,
|
196
|
+
fileSecurity: bucket.fileSecurity,
|
197
|
+
enabled: bucket.enabled,
|
198
|
+
maximumFileSize: bucket.maximumFileSize,
|
199
|
+
allowedFileExtensions: bucket.allowedFileExtensions,
|
200
|
+
compression: bucket.compression,
|
201
|
+
encryption: bucket.encryption,
|
202
|
+
antivirus: bucket.antivirus
|
203
|
+
}
|
204
|
+
};
|
205
|
+
|
206
|
+
// Step 6: Upload backup ZIP to backup bucket
|
207
|
+
MessageFormatter.info(`Uploading backup ZIP to bucket ${backupBucketId}`, { prefix: "Backup" });
|
208
|
+
const backupFileName = `${bucketId}.zip`;
|
209
|
+
const backupFile = await storage.createFile(
|
210
|
+
backupBucketId,
|
211
|
+
ID.unique(),
|
212
|
+
InputFile.fromBuffer(new Uint8Array(zipBuffer), backupFileName)
|
213
|
+
);
|
214
|
+
|
215
|
+
// Step 7: Upload manifest JSON
|
216
|
+
const manifestFileId = await uploadManifest(storage, backupBucketId, bucketId, manifest);
|
217
|
+
|
218
|
+
const status: 'completed' | 'partial' | 'failed' =
|
219
|
+
errorCount === 0 ? 'completed' :
|
220
|
+
successCount > 0 ? 'partial' :
|
221
|
+
'failed';
|
222
|
+
|
223
|
+
MessageFormatter.success(
|
224
|
+
`Bucket backup ${status}: ${successCount}/${allFiles.length} files backed up`,
|
225
|
+
{ prefix: "Backup" }
|
226
|
+
);
|
227
|
+
|
228
|
+
return {
|
229
|
+
backupFileId: backupFile.$id,
|
230
|
+
manifestFileId,
|
231
|
+
fileCount: successCount,
|
232
|
+
totalSizeBytes,
|
233
|
+
zipSizeBytes,
|
234
|
+
status,
|
235
|
+
errors: errors.length > 0 ? errors : undefined
|
236
|
+
};
|
237
|
+
} catch (error) {
|
238
|
+
const errorMsg = `Bucket backup failed: ${error instanceof Error ? error.message : String(error)}`;
|
239
|
+
MessageFormatter.error(errorMsg, error instanceof Error ? error : new Error(errorMsg), { prefix: "Backup" });
|
240
|
+
|
241
|
+
return {
|
242
|
+
backupFileId: '',
|
243
|
+
manifestFileId: '',
|
244
|
+
fileCount: 0,
|
245
|
+
totalSizeBytes: 0,
|
246
|
+
zipSizeBytes: 0,
|
247
|
+
status: 'failed',
|
248
|
+
errors: [errorMsg, ...errors]
|
249
|
+
};
|
250
|
+
}
|
251
|
+
}
|
252
|
+
|
253
|
+
/**
|
254
|
+
* Uploads bucket manifest JSON to backup bucket
|
255
|
+
*/
|
256
|
+
async function uploadManifest(
|
257
|
+
storage: Storage,
|
258
|
+
backupBucketId: string,
|
259
|
+
bucketId: string,
|
260
|
+
manifest: BucketManifest
|
261
|
+
): Promise<string> {
|
262
|
+
const manifestFileName = `${bucketId}.json`;
|
263
|
+
const manifestBuffer = Buffer.from(JSON.stringify(manifest, null, 2), 'utf-8');
|
264
|
+
|
265
|
+
const manifestFile = await storage.createFile(
|
266
|
+
backupBucketId,
|
267
|
+
ID.unique(),
|
268
|
+
InputFile.fromBuffer(new Uint8Array(manifestBuffer), manifestFileName)
|
269
|
+
);
|
270
|
+
|
271
|
+
logger.info("Uploaded bucket manifest", {
|
272
|
+
manifestFileId: manifestFile.$id,
|
273
|
+
bucketId
|
274
|
+
});
|
275
|
+
|
276
|
+
return manifestFile.$id;
|
277
|
+
}
|