appwrite-utils-cli 0.9.77 → 0.9.79

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.
@@ -67,7 +67,7 @@ export class ImportController {
67
67
  this.databasesToRun = databasesToRun || [];
68
68
  }
69
69
 
70
- async run() {
70
+ async run(specificCollections?: string[]) {
71
71
  let databasesToProcess: Models.Database[];
72
72
 
73
73
  if (this.databasesToRun.length > 0) {
@@ -101,9 +101,9 @@ export class ImportController {
101
101
  this.setupOptions.shouldWriteFile
102
102
  );
103
103
  await dataLoader.start(db.$id);
104
- await this.importCollections(db, dataLoader);
104
+ await this.importCollections(db, dataLoader, specificCollections);
105
105
  await resolveAndUpdateRelationships(db.$id, this.database, this.config);
106
- await this.executePostImportActions(db.$id, dataLoader);
106
+ await this.executePostImportActions(db.$id, dataLoader, specificCollections);
107
107
  } else if (databaseRan.$id !== db.$id) {
108
108
  await this.updateOthersToFinalData(databaseRan, db);
109
109
  }
@@ -142,307 +142,195 @@ export class ImportController {
142
142
  }
143
143
  }
144
144
 
145
- async importCollections(db: ConfigDatabase, dataLoader: DataLoader) {
146
- if (!this.config.collections) {
147
- return;
148
- }
149
- for (const collection of this.config.collections) {
150
- let isUsersCollection =
151
- dataLoader.getCollectionKey(this.config.usersCollectionName) ===
152
- dataLoader.getCollectionKey(collection.name);
153
- const importOperationId = dataLoader.collectionImportOperations.get(
154
- dataLoader.getCollectionKey(collection.name)
155
- );
156
- const createBatches = (finalData: CollectionImportData["data"]) => {
157
- let maxBatchLength = 100;
158
- const finalBatches: CollectionImportData["data"][] = [];
159
- for (let i = 0; i < finalData.length; i++) {
160
- if (i % maxBatchLength === 0) {
161
- finalBatches.push([]);
162
- }
163
- finalBatches[finalBatches.length - 1].push(finalData[i]);
164
- }
165
- return finalBatches;
166
- };
145
+ async importCollections(db: ConfigDatabase, dataLoader: DataLoader, specificCollections?: string[]) {
146
+ const collectionsToImport = specificCollections || (this.config.collections ? this.config.collections.map(c => c.name) : []);
167
147
 
168
- if (isUsersCollection && !this.hasImportedUsers) {
169
- const usersDataMap = dataLoader.importMap.get(
170
- dataLoader.getCollectionKey("users")
148
+ for (const collection of this.config.collections || []) {
149
+ if (collectionsToImport.includes(collection.name)) {
150
+ let isUsersCollection =
151
+ dataLoader.getCollectionKey(this.config.usersCollectionName) ===
152
+ dataLoader.getCollectionKey(collection.name);
153
+ const importOperationId = dataLoader.collectionImportOperations.get(
154
+ dataLoader.getCollectionKey(collection.name)
171
155
  );
172
- const usersData = usersDataMap?.data;
173
- const usersController = new UsersController(this.config, this.database);
174
- if (usersData) {
175
- console.log("Found users data", usersData.length);
176
- const userDataBatches = createBatches(usersData);
177
- for (const batch of userDataBatches) {
178
- console.log("Importing users batch", batch.length);
179
- const userBatchPromises = batch
180
- .filter((item) => {
181
- let itemId: string | undefined;
182
- if (item.finalData.userId) {
183
- itemId = item.finalData.userId;
184
- } else if (item.finalData.docId) {
185
- itemId = item.finalData.docId;
186
- }
187
- if (!itemId) {
188
- return false;
156
+ const createBatches = (finalData: CollectionImportData["data"]) => {
157
+ let maxBatchLength = 100;
158
+ const finalBatches: CollectionImportData["data"][] = [];
159
+ for (let i = 0; i < finalData.length; i++) {
160
+ if (i % maxBatchLength === 0) {
161
+ finalBatches.push([]);
162
+ }
163
+ finalBatches[finalBatches.length - 1].push(finalData[i]);
164
+ }
165
+ return finalBatches;
166
+ };
167
+
168
+ if (isUsersCollection && !this.hasImportedUsers) {
169
+ const usersDataMap = dataLoader.importMap.get(
170
+ dataLoader.getCollectionKey("users")
171
+ );
172
+ const usersData = usersDataMap?.data;
173
+ const usersController = new UsersController(this.config, this.database);
174
+ if (usersData) {
175
+ console.log("Found users data", usersData.length);
176
+ const userDataBatches = createBatches(usersData);
177
+ for (const batch of userDataBatches) {
178
+ console.log("Importing users batch", batch.length);
179
+ const userBatchPromises = batch
180
+ .filter((item) => {
181
+ let itemId: string | undefined;
182
+ if (item.finalData.userId) {
183
+ itemId = item.finalData.userId;
184
+ } else if (item.finalData.docId) {
185
+ itemId = item.finalData.docId;
186
+ }
187
+ if (!itemId) {
188
+ return false;
189
+ }
190
+ return (
191
+ item &&
192
+ item.finalData &&
193
+ !dataLoader.userExistsMap.has(itemId)
194
+ );
195
+ })
196
+ .map((item) => {
197
+ dataLoader.userExistsMap.set(
198
+ item.finalData.userId ||
199
+ item.finalData.docId ||
200
+ item.context.userId ||
201
+ item.context.docId,
202
+ true
203
+ );
204
+ return usersController.createUserAndReturn(item.finalData);
205
+ });
206
+ const promiseResults = await Promise.allSettled(userBatchPromises);
207
+ for (const item of batch) {
208
+ if (item && item.finalData) {
209
+ dataLoader.userExistsMap.set(
210
+ item.finalData.userId ||
211
+ item.finalData.docId ||
212
+ item.context.userId ||
213
+ item.context.docId,
214
+ true
215
+ );
189
216
  }
190
- return (
191
- item &&
192
- item.finalData &&
193
- !dataLoader.userExistsMap.has(itemId)
194
- );
195
- })
196
- .map((item) => {
197
- dataLoader.userExistsMap.set(
198
- item.finalData.userId ||
199
- item.finalData.docId ||
200
- item.context.userId ||
201
- item.context.docId,
202
- true
203
- );
204
- return usersController.createUserAndReturn(item.finalData);
205
- });
206
- const promiseResults = await Promise.allSettled(userBatchPromises);
207
- for (const item of batch) {
208
- if (item && item.finalData) {
209
- dataLoader.userExistsMap.set(
210
- item.finalData.userId ||
211
- item.finalData.docId ||
212
- item.context.userId ||
213
- item.context.docId,
214
- true
215
- );
216
217
  }
218
+ console.log("Finished importing users batch");
217
219
  }
218
- console.log("Finished importing users batch");
220
+ this.hasImportedUsers = true;
221
+ console.log("Finished importing users");
219
222
  }
220
- this.hasImportedUsers = true;
221
- console.log("Finished importing users");
222
223
  }
223
- }
224
-
225
- if (!importOperationId) {
226
- // Skip further processing if no import operation is found
227
- continue;
228
- }
229
-
230
- const importOperation = await this.database.getDocument(
231
- "migrations",
232
- "currentOperations",
233
- importOperationId
234
- );
235
- await updateOperation(this.database, importOperation.$id, {
236
- status: "in_progress",
237
- });
238
- const collectionData = dataLoader.importMap.get(
239
- dataLoader.getCollectionKey(collection.name)
240
- );
241
- console.log(`Processing collection: ${collection.name}...`);
242
- if (!collectionData) {
243
- console.log("No collection data for ", collection.name);
244
- continue;
245
- }
246
-
247
- const dataSplit = createBatches(collectionData.data);
248
- let processedItems = 0;
249
- for (let i = 0; i < dataSplit.length; i++) {
250
- const batches = dataSplit[i];
251
- console.log(`Processing batch ${i + 1} of ${dataSplit.length}`);
252
224
 
253
- // const documentExistsPromises = batches.map(async (item) => {
254
- // try {
255
- // const id =
256
- // item.finalData.docId ||
257
- // item.finalData.userId ||
258
- // item.context.docId ||
259
- // item.context.userId;
225
+ if (!importOperationId) {
226
+ // Skip further processing if no import operation is found
227
+ continue;
228
+ }
260
229
 
261
- // if (!item.finalData) {
262
- // return Promise.resolve(null);
263
- // }
264
- // return tryAwaitWithRetry(
265
- // async () =>
266
- // await documentExists(
267
- // this.database,
268
- // db.$id,
269
- // collection.$id,
270
- // item.finalData
271
- // )
272
- // );
273
- // } catch (error) {
274
- // console.error(error);
275
- // return Promise.resolve(null);
276
- // }
277
- // });
230
+ const importOperation = await this.database.getDocument(
231
+ "migrations",
232
+ "currentOperations",
233
+ importOperationId
234
+ );
235
+ await updateOperation(this.database, importOperation.$id, {
236
+ status: "in_progress",
237
+ });
238
+ const collectionData = dataLoader.importMap.get(
239
+ dataLoader.getCollectionKey(collection.name)
240
+ );
241
+ console.log(`Processing collection: ${collection.name}...`);
242
+ if (!collectionData) {
243
+ console.log("No collection data for ", collection.name);
244
+ continue;
245
+ }
278
246
 
279
- // const documentExistsResults = await Promise.all(documentExistsPromises);
247
+ const dataSplit = createBatches(collectionData.data);
248
+ let processedItems = 0;
249
+ for (let i = 0; i < dataSplit.length; i++) {
250
+ const batches = dataSplit[i];
251
+ console.log(`Processing batch ${i + 1} of ${dataSplit.length}`);
280
252
 
281
- const batchPromises = batches.map((item, index) => {
282
- try {
283
- const id =
284
- item.finalData.docId ||
285
- item.finalData.userId ||
286
- item.context.docId ||
287
- item.context.userId;
253
+ const batchPromises = batches.map((item, index) => {
254
+ try {
255
+ const id =
256
+ item.finalData.docId ||
257
+ item.finalData.userId ||
258
+ item.context.docId ||
259
+ item.context.userId;
288
260
 
289
- if (item.finalData.hasOwnProperty("userId")) {
290
- delete item.finalData.userId;
291
- }
292
- if (item.finalData.hasOwnProperty("docId")) {
293
- delete item.finalData.docId;
294
- }
295
- if (!item.finalData) {
261
+ if (item.finalData.hasOwnProperty("userId")) {
262
+ delete item.finalData.userId;
263
+ }
264
+ if (item.finalData.hasOwnProperty("docId")) {
265
+ delete item.finalData.docId;
266
+ }
267
+ if (!item.finalData) {
268
+ return Promise.resolve();
269
+ }
270
+ return tryAwaitWithRetry(
271
+ async () =>
272
+ await this.database.createDocument(
273
+ db.$id,
274
+ collection.$id,
275
+ id,
276
+ item.finalData
277
+ )
278
+ );
279
+ } catch (error) {
280
+ console.error(error);
296
281
  return Promise.resolve();
297
282
  }
298
- return tryAwaitWithRetry(
299
- async () =>
300
- await this.database.createDocument(
301
- db.$id,
302
- collection.$id,
303
- id,
304
- item.finalData
305
- )
306
- );
307
- } catch (error) {
308
- console.error(error);
309
- return Promise.resolve();
310
- }
311
- });
283
+ });
312
284
 
313
- // Wait for all promises in the current batch to resolve
314
- await Promise.all(batchPromises);
315
- console.log(`Completed batch ${i + 1} of ${dataSplit.length}`);
285
+ // Wait for all promises in the current batch to resolve
286
+ await Promise.all(batchPromises);
287
+ console.log(`Completed batch ${i + 1} of ${dataSplit.length}`);
288
+ await updateOperation(this.database, importOperation.$id, {
289
+ progress: processedItems,
290
+ });
291
+ }
292
+ // After all batches are processed, update the operation status to completed
316
293
  await updateOperation(this.database, importOperation.$id, {
317
- progress: processedItems,
294
+ status: "completed",
318
295
  });
319
296
  }
320
- // After all batches are processed, update the operation status to completed
321
- await updateOperation(this.database, importOperation.$id, {
322
- status: "completed",
323
- });
324
297
  }
325
298
  }
326
299
 
327
- async executePostImportActions(dbId: string, dataLoader: DataLoader) {
300
+ async executePostImportActions(dbId: string, dataLoader: DataLoader, specificCollections?: string[]) {
301
+ const collectionsToProcess = specificCollections || Array.from(dataLoader.importMap.keys());
302
+
328
303
  // Iterate over each collection in the importMap
329
- for (const [
330
- collectionKey,
331
- collectionData,
332
- ] of dataLoader.importMap.entries()) {
333
- console.log(
334
- `Processing post-import actions for collection: ${collectionKey}`
335
- );
304
+ for (const [collectionKey, collectionData] of dataLoader.importMap.entries()) {
305
+ if (collectionsToProcess.includes(collectionKey)) {
306
+ console.log(
307
+ `Processing post-import actions for collection: ${collectionKey}`
308
+ );
336
309
 
337
- // Iterate over each item in the collectionData.data
338
- for (const item of collectionData.data) {
339
- // Assuming each item has attributeMappings that contain actions to be executed
340
- if (item.importDef && item.importDef.attributeMappings) {
341
- // Use item.context as the context for action execution
342
- const context = item.context; // Directly use item.context as the context for action execution
343
- // Iterate through attributeMappings to execute actions
344
- try {
345
- // Execute post-import actions for the current attributeMapping
346
- // Pass item.finalData as the data to be processed along with the context
347
- await this.importDataActions.executeAfterImportActions(
348
- item.finalData,
349
- item.importDef.attributeMappings,
350
- context
351
- );
352
- } catch (error) {
353
- console.error(
354
- `Failed to execute post-import actions for item in collection ${collectionKey}:`,
355
- error
356
- );
357
- // Handle error (e.g., log, retry, continue with next action)
310
+ // Iterate over each item in the collectionData.data
311
+ for (const item of collectionData.data) {
312
+ // Assuming each item has attributeMappings that contain actions to be executed
313
+ if (item.importDef && item.importDef.attributeMappings) {
314
+ // Use item.context as the context for action execution
315
+ const context = item.context; // Directly use item.context as the context for action execution
316
+ // Iterate through attributeMappings to execute actions
317
+ try {
318
+ // Execute post-import actions for the current attributeMapping
319
+ // Pass item.finalData as the data to be processed along with the context
320
+ await this.importDataActions.executeAfterImportActions(
321
+ item.finalData,
322
+ item.importDef.attributeMappings,
323
+ context
324
+ );
325
+ } catch (error) {
326
+ console.error(
327
+ `Failed to execute post-import actions for item in collection ${collectionKey}:`,
328
+ error
329
+ );
330
+ }
358
331
  }
359
332
  }
360
333
  }
361
334
  }
362
335
  }
363
-
364
- // async executeActionsInParallel(dbId: string, collection: ConfigCollection) {
365
- // const collectionExists = await checkForCollection(
366
- // this.database,
367
- // dbId,
368
- // collection
369
- // );
370
- // if (!collectionExists) {
371
- // logger.error(`No collection found for ${collection.name}`);
372
- // return; // Skip this iteration
373
- // }
374
- // const operations = await getAfterImportOperations(
375
- // this.database,
376
- // collectionExists.$id
377
- // );
378
-
379
- // for (const operation of operations) {
380
- // if (!operation.batches) {
381
- // continue;
382
- // }
383
- // const batches = operation.batches;
384
- // const promises = [];
385
- // for (const batch of batches) {
386
- // const batchId = batch;
387
- // promises.push(
388
- // this.database.getDocument("migrations", "batches", batchId)
389
- // );
390
- // }
391
- // const results = await Promise.allSettled(promises);
392
- // results.forEach((result) => {
393
- // if (result.status === "rejected") {
394
- // logger.error("A process batch promise was rejected:", result.reason);
395
- // }
396
- // });
397
- // const resultsData = results
398
- // .map((result) => (result.status === "fulfilled" ? result.value : null))
399
- // .filter((result: any) => result !== null && !result.processed)
400
- // .map((result) => BatchSchema.parse(result));
401
- // for (const batch of resultsData) {
402
- // const actionOperation = ContextObject.parse(JSON.parse(batch.data));
403
- // const { context, finalItem, attributeMappings } = actionOperation;
404
- // if (finalItem.$id && !context.docId) {
405
- // context.docId =
406
- // finalItem.$id || context.createdDoc.$id || context.$id || undefined;
407
- // logger.info(
408
- // `Setting docId to ${
409
- // finalItem.$id
410
- // } because docId not found in context, batch ${
411
- // batch.$id
412
- // }, context is ${JSON.stringify(context)}`
413
- // );
414
- // }
415
- // try {
416
- // await this.importDataActions.executeAfterImportActions(
417
- // finalItem,
418
- // attributeMappings,
419
- // context
420
- // );
421
- // // Mark batch as processed
422
- // await this.database.deleteDocument(
423
- // "migrations",
424
- // "batches",
425
- // batch.$id
426
- // );
427
- // } catch (error) {
428
- // logger.error(
429
- // `Failed to execute batch ${batch.$id}:`,
430
- // error,
431
- // "Context is :",
432
- // context
433
- // );
434
- // await this.database.deleteDocument(
435
- // "migrations",
436
- // "batches",
437
- // batch.$id
438
- // );
439
- // }
440
- // }
441
-
442
- // // After processing all batches, update the operation status
443
- // await updateOperation(this.database, operation.$id, {
444
- // status: "completed", // Or determine based on batch success/failure
445
- // });
446
- // }
447
- // }
448
- }
336
+ }
@@ -14,7 +14,10 @@ export const setupMigrationDatabase = async (config: AppwriteConfig) => {
14
14
  console.log("---------------------------------");
15
15
  console.log("Starting Migrations Setup");
16
16
  console.log("---------------------------------");
17
- const database = new Databases(config.appwriteClient!);
17
+ const database = new Databases(config.appwriteClient);
18
+ if (!config.appwriteClient) {
19
+ throw new Error("Appwrite client is not initialized in the config");
20
+ }
18
21
  let db: Models.Database | undefined;
19
22
  const migrationCollectionsSetup = getMigrationCollectionSchemas();
20
23
 
@@ -103,9 +106,17 @@ export const setupMigrationDatabase = async (config: AppwriteConfig) => {
103
106
  console.log("---------------------------------");
104
107
  };
105
108
 
106
- export const ensureDatabasesExist = async (config: AppwriteConfig) => {
107
- const database = new Databases(config.appwriteClient!);
108
- const databasesToEnsure = config.databases;
109
+ export const ensureDatabasesExist = async (config: AppwriteConfig, databasesToEnsure?: Models.Database[]) => {
110
+ if (!config.appwriteClient) {
111
+ throw new Error("Appwrite client is not initialized in the config");
112
+ }
113
+ const database = new Databases(config.appwriteClient);
114
+ const databasesToCreate = databasesToEnsure || config.databases || [];
115
+
116
+ if (!databasesToCreate.length) {
117
+ console.log("No databases to create");
118
+ return;
119
+ }
109
120
 
110
121
  const existingDatabases = await tryAwaitWithRetry(
111
122
  async () => await database.list([Query.limit(500)])
@@ -116,10 +127,10 @@ export const ensureDatabasesExist = async (config: AppwriteConfig) => {
116
127
  );
117
128
  if (existingDatabases.databases.length !== 0 && migrationsDatabase) {
118
129
  console.log("Creating all databases except migrations");
119
- databasesToEnsure.push(migrationsDatabase);
130
+ databasesToCreate.push(migrationsDatabase);
120
131
  }
121
132
 
122
- for (const db of databasesToEnsure) {
133
+ for (const db of databasesToCreate) {
123
134
  if (!existingDatabases.databases.some((d) => d.name === db.name)) {
124
135
  await tryAwaitWithRetry(
125
136
  async () => await database.create(db.$id || ulid(), db.name, true)
@@ -133,7 +144,7 @@ export const wipeOtherDatabases = async (
133
144
  database: Databases,
134
145
  databasesToKeep: Models.Database[]
135
146
  ) => {
136
- console.log(`Databases to keep: ${databasesToKeep.join(", ")}`);
147
+ console.log(`Databases to keep: ${databasesToKeep.map(db => db.name).join(", ")}`);
137
148
  const allDatabases = await tryAwaitWithRetry(
138
149
  async () => await database.list([Query.limit(500)])
139
150
  );
@@ -151,3 +162,33 @@ export const wipeOtherDatabases = async (
151
162
  }
152
163
  }
153
164
  };
165
+
166
+ export const ensureCollectionsExist = async (
167
+ config: AppwriteConfig,
168
+ database: Models.Database,
169
+ collectionsToEnsure?: Models.Collection[]
170
+ ) => {
171
+ const databaseClient = new Databases(config.appwriteClient!);
172
+ const collectionsToCreate = collectionsToEnsure ||
173
+ (config.collections ? config.collections : []);
174
+
175
+ const existingCollections = await tryAwaitWithRetry(
176
+ async () => await databaseClient.listCollections(database.$id, [Query.limit(500)])
177
+ );
178
+
179
+ for (const collection of collectionsToCreate) {
180
+ if (!existingCollections.collections.some((c) => c.name === collection.name)) {
181
+ await tryAwaitWithRetry(
182
+ async () => await databaseClient.createCollection(
183
+ database.$id,
184
+ ulid(),
185
+ collection.name,
186
+ undefined,
187
+ true,
188
+ true
189
+ )
190
+ );
191
+ console.log(`${collection.name} collection created in ${database.name}`);
192
+ }
193
+ }
194
+ };
@@ -155,10 +155,10 @@ export const ensureDatabaseConfigBucketsExist = async (
155
155
  );
156
156
  console.log(`Bucket ${database.bucket.$id} created successfully.`);
157
157
  } catch (createError) {
158
- console.error(
159
- `Failed to create bucket ${database.bucket.$id}:`,
160
- createError
161
- );
158
+ // console.error(
159
+ // `Failed to create bucket ${database.bucket.$id}:`,
160
+ // createError
161
+ // );
162
162
  }
163
163
  }
164
164
  }
@@ -74,10 +74,13 @@ const baseConfig: AppwriteConfig = {
74
74
  const collectionsConfig: { name: string; content: string }[] = [
75
75
  {
76
76
  name: "ExampleCollection",
77
- content: `import { CollectionCreate } from "appwrite-utils";
77
+ content: `import type { CollectionCreate } from "appwrite-utils";
78
78
 
79
79
  const ExampleCollection: Partial<CollectionCreate> = {
80
80
  name: 'ExampleCollection',
81
+ $id: '${ulid()}',
82
+ documentSecurity: false,
83
+ enabled: true,
81
84
  $permissions: [
82
85
  { permission: 'read', target: 'any' },
83
86
  { permission: 'create', target: 'users' },