appwrite-utils-cli 0.9.77 → 0.9.78

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/dist/main.js CHANGED
@@ -6,6 +6,9 @@ import { InteractiveCLI } from "./interactiveCLI.js";
6
6
  import { UtilsController } from "./utilsController.js";
7
7
  import { Databases, Storage } from "node-appwrite";
8
8
  import { getClient } from "./utils/getClientFromConfig.js";
9
+ import { fetchAllDatabases } from "./migrations/databases.js";
10
+ import { setupDirsFiles } from "./utils/setupFiles.js";
11
+ import { fetchAllCollections } from "./collections/methods.js";
9
12
  const argv = yargs(hideBin(process.argv))
10
13
  .option("it", {
11
14
  alias: ["interactive", "i"],
@@ -28,6 +31,10 @@ const argv = yargs(hideBin(process.argv))
28
31
  .option("wipe", {
29
32
  choices: ["all", "docs", "users"],
30
33
  description: "Wipe data (all: everything, docs: only documents, users: only user data)",
34
+ })
35
+ .option("wipeCollections", {
36
+ type: "boolean",
37
+ description: "Wipe collections, uses collectionIds option to get the collections to wipe",
31
38
  })
32
39
  .option("generate", {
33
40
  type: "boolean",
@@ -90,78 +97,79 @@ const argv = yargs(hideBin(process.argv))
90
97
  description: "Set the destination collection ID for transfer",
91
98
  })
92
99
  .option("fromBucketId", {
93
- alias: ["fromBucket"],
94
100
  type: "string",
95
101
  description: "Set the source bucket ID for transfer",
96
102
  })
97
103
  .option("toBucketId", {
98
- alias: ["toBucket"],
99
104
  type: "string",
100
105
  description: "Set the destination bucket ID for transfer",
101
106
  })
102
107
  .option("remoteEndpoint", {
103
108
  type: "string",
104
- description: "Set the remote Appwrite endpoint for transfers",
109
+ description: "Set the remote Appwrite endpoint for transfer",
105
110
  })
106
111
  .option("remoteProjectId", {
107
112
  type: "string",
108
- description: "Set the remote Appwrite project ID for transfers",
113
+ description: "Set the remote Appwrite project ID for transfer",
109
114
  })
110
115
  .option("remoteApiKey", {
111
116
  type: "string",
112
- description: "Set the remote Appwrite API key for transfers",
117
+ description: "Set the remote Appwrite API key for transfer",
113
118
  })
114
119
  .option("setup", {
115
120
  type: "boolean",
116
- description: "Setup the project with example data",
121
+ description: "Setup directories and files",
117
122
  })
118
- .help()
119
123
  .parse();
120
124
  async function main() {
121
- const parsedArgv = (await argv);
122
- let controller;
123
- if (parsedArgv.it) {
124
- try {
125
- controller = new UtilsController(process.cwd());
126
- await controller.init();
127
- }
128
- catch (error) {
129
- // If it fails, that means there's no config, more than likely
130
- console.log("No config found, you probably need to create the setup files");
131
- }
125
+ const controller = new UtilsController(process.cwd());
126
+ if (argv.it) {
132
127
  const cli = new InteractiveCLI(process.cwd());
133
128
  await cli.run();
134
129
  }
135
130
  else {
136
- if (!controller) {
137
- controller = new UtilsController(process.cwd());
138
- await controller.init();
131
+ await controller.init();
132
+ if (argv.setup) {
133
+ await setupDirsFiles(false, process.cwd());
134
+ return;
139
135
  }
140
- // Handle non-interactive mode with the new options
136
+ const parsedArgv = argv;
141
137
  const options = {
142
138
  databases: parsedArgv.dbIds
143
- ? await controller.getDatabasesByIds(parsedArgv.dbIds.replace(" ", "").split(","))
144
- : undefined,
145
- collections: parsedArgv.collectionIds
146
- ? parsedArgv.collectionIds.replace(" ", "").split(",")
139
+ ? await controller.getDatabasesByIds(parsedArgv.dbIds.split(","))
147
140
  : undefined,
141
+ collections: parsedArgv.collectionIds?.split(","),
148
142
  doBackup: parsedArgv.backup,
149
143
  wipeDatabase: parsedArgv.wipe === "all" || parsedArgv.wipe === "docs",
150
144
  wipeDocumentStorage: parsedArgv.wipe === "all",
151
145
  wipeUsers: parsedArgv.wipe === "all" || parsedArgv.wipe === "users",
152
146
  generateSchemas: parsedArgv.generate,
153
147
  importData: parsedArgv.import,
154
- checkDuplicates: false,
155
148
  shouldWriteFile: parsedArgv.writeData,
149
+ wipeCollections: parsedArgv.wipeCollections,
156
150
  };
157
- if (parsedArgv.push) {
158
- await controller.syncDb();
151
+ if (parsedArgv.push || parsedArgv.sync) {
152
+ const databases = options.databases || await fetchAllDatabases(controller.database);
153
+ let collections = [];
154
+ if (options.collections) {
155
+ for (const db of databases) {
156
+ const dbCollections = await fetchAllCollections(db.$id, controller.database);
157
+ collections = collections.concat(dbCollections.filter(c => options.collections.includes(c.$id)));
158
+ }
159
+ }
160
+ if (parsedArgv.push) {
161
+ await controller.syncDb(databases, collections);
162
+ }
163
+ else if (parsedArgv.sync) {
164
+ await controller.synchronizeConfigurations(databases);
165
+ }
159
166
  }
160
167
  if (options.wipeDatabase ||
161
168
  options.wipeDocumentStorage ||
162
- options.wipeUsers) {
163
- if (options.wipeDatabase) {
164
- for (const db of options.databases || []) {
169
+ options.wipeUsers ||
170
+ options.wipeCollections) {
171
+ if (options.wipeDatabase && options.databases) {
172
+ for (const db of options.databases) {
165
173
  await controller.wipeDatabase(db);
166
174
  }
167
175
  }
@@ -173,9 +181,18 @@ async function main() {
173
181
  if (options.wipeUsers) {
174
182
  await controller.wipeUsers();
175
183
  }
184
+ if (options.wipeCollections && options.databases) {
185
+ for (const db of options.databases) {
186
+ const dbCollections = await fetchAllCollections(db.$id, controller.database);
187
+ const collectionsToWipe = dbCollections.filter(c => options.collections.includes(c.$id));
188
+ for (const collection of collectionsToWipe) {
189
+ await controller.wipeCollection(db, collection);
190
+ }
191
+ }
192
+ }
176
193
  }
177
- if (options.doBackup) {
178
- for (const db of options.databases || []) {
194
+ if (options.doBackup && options.databases) {
195
+ for (const db of options.databases) {
179
196
  await controller.backupDatabase(db);
180
197
  }
181
198
  }
@@ -200,8 +217,8 @@ async function main() {
200
217
  const remoteClient = getClient(parsedArgv.remoteEndpoint, parsedArgv.remoteProjectId, parsedArgv.remoteApiKey);
201
218
  targetDatabases = new Databases(remoteClient);
202
219
  targetStorage = new Storage(remoteClient);
203
- const remoteDbs = await targetDatabases.list();
204
- toDb = remoteDbs.databases.find((db) => db.$id === parsedArgv.toDbId);
220
+ const remoteDbs = await fetchAllDatabases(targetDatabases);
221
+ toDb = remoteDbs.find((db) => db.$id === parsedArgv.toDbId);
205
222
  }
206
223
  else {
207
224
  toDb = (await controller.getDatabasesByIds([parsedArgv.toDbId]))[0];
@@ -233,9 +250,6 @@ async function main() {
233
250
  };
234
251
  await controller.transferData(transferOptions);
235
252
  }
236
- if (parsedArgv.sync) {
237
- await controller.synchronizeConfigurations(options.databases);
238
- }
239
253
  }
240
254
  }
241
255
  main().catch((error) => {
@@ -16,8 +16,8 @@ export declare class ImportController {
16
16
  private postImportActionsQueue;
17
17
  private databasesToRun;
18
18
  constructor(config: AppwriteConfig, database: Databases, storage: Storage, appwriteFolderPath: string, importDataActions: ImportDataActions, setupOptions: SetupOptions, databasesToRun?: Models.Database[]);
19
- run(): Promise<void>;
19
+ run(specificCollections?: string[]): Promise<void>;
20
20
  updateOthersToFinalData(updatedDb: Models.Database, targetDb: Models.Database): Promise<void>;
21
- importCollections(db: ConfigDatabase, dataLoader: DataLoader): Promise<void>;
22
- executePostImportActions(dbId: string, dataLoader: DataLoader): Promise<void>;
21
+ importCollections(db: ConfigDatabase, dataLoader: DataLoader, specificCollections?: string[]): Promise<void>;
22
+ executePostImportActions(dbId: string, dataLoader: DataLoader, specificCollections?: string[]): Promise<void>;
23
23
  }
@@ -30,7 +30,7 @@ export class ImportController {
30
30
  this.documentCache = new Map();
31
31
  this.databasesToRun = databasesToRun || [];
32
32
  }
33
- async run() {
33
+ async run(specificCollections) {
34
34
  let databasesToProcess;
35
35
  if (this.databasesToRun.length > 0) {
36
36
  // Use the provided databases
@@ -54,9 +54,9 @@ export class ImportController {
54
54
  databaseRan = db;
55
55
  dataLoader = new DataLoader(this.appwriteFolderPath, this.importDataActions, this.database, this.config, this.setupOptions.shouldWriteFile);
56
56
  await dataLoader.start(db.$id);
57
- await this.importCollections(db, dataLoader);
57
+ await this.importCollections(db, dataLoader, specificCollections);
58
58
  await resolveAndUpdateRelationships(db.$id, this.database, this.config);
59
- await this.executePostImportActions(db.$id, dataLoader);
59
+ await this.executePostImportActions(db.$id, dataLoader, specificCollections);
60
60
  }
61
61
  else if (databaseRan.$id !== db.$id) {
62
62
  await this.updateOthersToFinalData(databaseRan, db);
@@ -76,170 +76,147 @@ export class ImportController {
76
76
  await transferStorageLocalToLocal(this.storage, updatedDbConfig.bucket.$id, targetDbConfig.bucket.$id);
77
77
  }
78
78
  }
79
- async importCollections(db, dataLoader) {
80
- if (!this.config.collections) {
81
- return;
82
- }
83
- for (const collection of this.config.collections) {
84
- let isUsersCollection = dataLoader.getCollectionKey(this.config.usersCollectionName) ===
85
- dataLoader.getCollectionKey(collection.name);
86
- const importOperationId = dataLoader.collectionImportOperations.get(dataLoader.getCollectionKey(collection.name));
87
- const createBatches = (finalData) => {
88
- let maxBatchLength = 100;
89
- const finalBatches = [];
90
- for (let i = 0; i < finalData.length; i++) {
91
- if (i % maxBatchLength === 0) {
92
- finalBatches.push([]);
79
+ async importCollections(db, dataLoader, specificCollections) {
80
+ const collectionsToImport = specificCollections || (this.config.collections ? this.config.collections.map(c => c.name) : []);
81
+ for (const collection of this.config.collections || []) {
82
+ if (collectionsToImport.includes(collection.name)) {
83
+ let isUsersCollection = dataLoader.getCollectionKey(this.config.usersCollectionName) ===
84
+ dataLoader.getCollectionKey(collection.name);
85
+ const importOperationId = dataLoader.collectionImportOperations.get(dataLoader.getCollectionKey(collection.name));
86
+ const createBatches = (finalData) => {
87
+ let maxBatchLength = 100;
88
+ const finalBatches = [];
89
+ for (let i = 0; i < finalData.length; i++) {
90
+ if (i % maxBatchLength === 0) {
91
+ finalBatches.push([]);
92
+ }
93
+ finalBatches[finalBatches.length - 1].push(finalData[i]);
93
94
  }
94
- finalBatches[finalBatches.length - 1].push(finalData[i]);
95
- }
96
- return finalBatches;
97
- };
98
- if (isUsersCollection && !this.hasImportedUsers) {
99
- const usersDataMap = dataLoader.importMap.get(dataLoader.getCollectionKey("users"));
100
- const usersData = usersDataMap?.data;
101
- const usersController = new UsersController(this.config, this.database);
102
- if (usersData) {
103
- console.log("Found users data", usersData.length);
104
- const userDataBatches = createBatches(usersData);
105
- for (const batch of userDataBatches) {
106
- console.log("Importing users batch", batch.length);
107
- const userBatchPromises = batch
108
- .filter((item) => {
109
- let itemId;
110
- if (item.finalData.userId) {
111
- itemId = item.finalData.userId;
112
- }
113
- else if (item.finalData.docId) {
114
- itemId = item.finalData.docId;
115
- }
116
- if (!itemId) {
117
- return false;
118
- }
119
- return (item &&
120
- item.finalData &&
121
- !dataLoader.userExistsMap.has(itemId));
122
- })
123
- .map((item) => {
124
- dataLoader.userExistsMap.set(item.finalData.userId ||
125
- item.finalData.docId ||
126
- item.context.userId ||
127
- item.context.docId, true);
128
- return usersController.createUserAndReturn(item.finalData);
129
- });
130
- const promiseResults = await Promise.allSettled(userBatchPromises);
131
- for (const item of batch) {
132
- if (item && item.finalData) {
95
+ return finalBatches;
96
+ };
97
+ if (isUsersCollection && !this.hasImportedUsers) {
98
+ const usersDataMap = dataLoader.importMap.get(dataLoader.getCollectionKey("users"));
99
+ const usersData = usersDataMap?.data;
100
+ const usersController = new UsersController(this.config, this.database);
101
+ if (usersData) {
102
+ console.log("Found users data", usersData.length);
103
+ const userDataBatches = createBatches(usersData);
104
+ for (const batch of userDataBatches) {
105
+ console.log("Importing users batch", batch.length);
106
+ const userBatchPromises = batch
107
+ .filter((item) => {
108
+ let itemId;
109
+ if (item.finalData.userId) {
110
+ itemId = item.finalData.userId;
111
+ }
112
+ else if (item.finalData.docId) {
113
+ itemId = item.finalData.docId;
114
+ }
115
+ if (!itemId) {
116
+ return false;
117
+ }
118
+ return (item &&
119
+ item.finalData &&
120
+ !dataLoader.userExistsMap.has(itemId));
121
+ })
122
+ .map((item) => {
133
123
  dataLoader.userExistsMap.set(item.finalData.userId ||
134
124
  item.finalData.docId ||
135
125
  item.context.userId ||
136
126
  item.context.docId, true);
127
+ return usersController.createUserAndReturn(item.finalData);
128
+ });
129
+ const promiseResults = await Promise.allSettled(userBatchPromises);
130
+ for (const item of batch) {
131
+ if (item && item.finalData) {
132
+ dataLoader.userExistsMap.set(item.finalData.userId ||
133
+ item.finalData.docId ||
134
+ item.context.userId ||
135
+ item.context.docId, true);
136
+ }
137
137
  }
138
+ console.log("Finished importing users batch");
138
139
  }
139
- console.log("Finished importing users batch");
140
+ this.hasImportedUsers = true;
141
+ console.log("Finished importing users");
140
142
  }
141
- this.hasImportedUsers = true;
142
- console.log("Finished importing users");
143
143
  }
144
- }
145
- if (!importOperationId) {
146
- // Skip further processing if no import operation is found
147
- continue;
148
- }
149
- const importOperation = await this.database.getDocument("migrations", "currentOperations", importOperationId);
150
- await updateOperation(this.database, importOperation.$id, {
151
- status: "in_progress",
152
- });
153
- const collectionData = dataLoader.importMap.get(dataLoader.getCollectionKey(collection.name));
154
- console.log(`Processing collection: ${collection.name}...`);
155
- if (!collectionData) {
156
- console.log("No collection data for ", collection.name);
157
- continue;
158
- }
159
- const dataSplit = createBatches(collectionData.data);
160
- let processedItems = 0;
161
- for (let i = 0; i < dataSplit.length; i++) {
162
- const batches = dataSplit[i];
163
- console.log(`Processing batch ${i + 1} of ${dataSplit.length}`);
164
- // const documentExistsPromises = batches.map(async (item) => {
165
- // try {
166
- // const id =
167
- // item.finalData.docId ||
168
- // item.finalData.userId ||
169
- // item.context.docId ||
170
- // item.context.userId;
171
- // if (!item.finalData) {
172
- // return Promise.resolve(null);
173
- // }
174
- // return tryAwaitWithRetry(
175
- // async () =>
176
- // await documentExists(
177
- // this.database,
178
- // db.$id,
179
- // collection.$id,
180
- // item.finalData
181
- // )
182
- // );
183
- // } catch (error) {
184
- // console.error(error);
185
- // return Promise.resolve(null);
186
- // }
187
- // });
188
- // const documentExistsResults = await Promise.all(documentExistsPromises);
189
- const batchPromises = batches.map((item, index) => {
190
- try {
191
- const id = item.finalData.docId ||
192
- item.finalData.userId ||
193
- item.context.docId ||
194
- item.context.userId;
195
- if (item.finalData.hasOwnProperty("userId")) {
196
- delete item.finalData.userId;
197
- }
198
- if (item.finalData.hasOwnProperty("docId")) {
199
- delete item.finalData.docId;
144
+ if (!importOperationId) {
145
+ // Skip further processing if no import operation is found
146
+ continue;
147
+ }
148
+ const importOperation = await this.database.getDocument("migrations", "currentOperations", importOperationId);
149
+ await updateOperation(this.database, importOperation.$id, {
150
+ status: "in_progress",
151
+ });
152
+ const collectionData = dataLoader.importMap.get(dataLoader.getCollectionKey(collection.name));
153
+ console.log(`Processing collection: ${collection.name}...`);
154
+ if (!collectionData) {
155
+ console.log("No collection data for ", collection.name);
156
+ continue;
157
+ }
158
+ const dataSplit = createBatches(collectionData.data);
159
+ let processedItems = 0;
160
+ for (let i = 0; i < dataSplit.length; i++) {
161
+ const batches = dataSplit[i];
162
+ console.log(`Processing batch ${i + 1} of ${dataSplit.length}`);
163
+ const batchPromises = batches.map((item, index) => {
164
+ try {
165
+ const id = item.finalData.docId ||
166
+ item.finalData.userId ||
167
+ item.context.docId ||
168
+ item.context.userId;
169
+ if (item.finalData.hasOwnProperty("userId")) {
170
+ delete item.finalData.userId;
171
+ }
172
+ if (item.finalData.hasOwnProperty("docId")) {
173
+ delete item.finalData.docId;
174
+ }
175
+ if (!item.finalData) {
176
+ return Promise.resolve();
177
+ }
178
+ return tryAwaitWithRetry(async () => await this.database.createDocument(db.$id, collection.$id, id, item.finalData));
200
179
  }
201
- if (!item.finalData) {
180
+ catch (error) {
181
+ console.error(error);
202
182
  return Promise.resolve();
203
183
  }
204
- return tryAwaitWithRetry(async () => await this.database.createDocument(db.$id, collection.$id, id, item.finalData));
205
- }
206
- catch (error) {
207
- console.error(error);
208
- return Promise.resolve();
209
- }
210
- });
211
- // Wait for all promises in the current batch to resolve
212
- await Promise.all(batchPromises);
213
- console.log(`Completed batch ${i + 1} of ${dataSplit.length}`);
184
+ });
185
+ // Wait for all promises in the current batch to resolve
186
+ await Promise.all(batchPromises);
187
+ console.log(`Completed batch ${i + 1} of ${dataSplit.length}`);
188
+ await updateOperation(this.database, importOperation.$id, {
189
+ progress: processedItems,
190
+ });
191
+ }
192
+ // After all batches are processed, update the operation status to completed
214
193
  await updateOperation(this.database, importOperation.$id, {
215
- progress: processedItems,
194
+ status: "completed",
216
195
  });
217
196
  }
218
- // After all batches are processed, update the operation status to completed
219
- await updateOperation(this.database, importOperation.$id, {
220
- status: "completed",
221
- });
222
197
  }
223
198
  }
224
- async executePostImportActions(dbId, dataLoader) {
199
+ async executePostImportActions(dbId, dataLoader, specificCollections) {
200
+ const collectionsToProcess = specificCollections || Array.from(dataLoader.importMap.keys());
225
201
  // Iterate over each collection in the importMap
226
- for (const [collectionKey, collectionData,] of dataLoader.importMap.entries()) {
227
- console.log(`Processing post-import actions for collection: ${collectionKey}`);
228
- // Iterate over each item in the collectionData.data
229
- for (const item of collectionData.data) {
230
- // Assuming each item has attributeMappings that contain actions to be executed
231
- if (item.importDef && item.importDef.attributeMappings) {
232
- // Use item.context as the context for action execution
233
- const context = item.context; // Directly use item.context as the context for action execution
234
- // Iterate through attributeMappings to execute actions
235
- try {
236
- // Execute post-import actions for the current attributeMapping
237
- // Pass item.finalData as the data to be processed along with the context
238
- await this.importDataActions.executeAfterImportActions(item.finalData, item.importDef.attributeMappings, context);
239
- }
240
- catch (error) {
241
- console.error(`Failed to execute post-import actions for item in collection ${collectionKey}:`, error);
242
- // Handle error (e.g., log, retry, continue with next action)
202
+ for (const [collectionKey, collectionData] of dataLoader.importMap.entries()) {
203
+ if (collectionsToProcess.includes(collectionKey)) {
204
+ console.log(`Processing post-import actions for collection: ${collectionKey}`);
205
+ // Iterate over each item in the collectionData.data
206
+ for (const item of collectionData.data) {
207
+ // Assuming each item has attributeMappings that contain actions to be executed
208
+ if (item.importDef && item.importDef.attributeMappings) {
209
+ // Use item.context as the context for action execution
210
+ const context = item.context; // Directly use item.context as the context for action execution
211
+ // Iterate through attributeMappings to execute actions
212
+ try {
213
+ // Execute post-import actions for the current attributeMapping
214
+ // Pass item.finalData as the data to be processed along with the context
215
+ await this.importDataActions.executeAfterImportActions(item.finalData, item.importDef.attributeMappings, context);
216
+ }
217
+ catch (error) {
218
+ console.error(`Failed to execute post-import actions for item in collection ${collectionKey}:`, error);
219
+ }
243
220
  }
244
221
  }
245
222
  }
@@ -1,5 +1,6 @@
1
1
  import { Databases, type Models } from "node-appwrite";
2
2
  import { type AppwriteConfig } from "appwrite-utils";
3
3
  export declare const setupMigrationDatabase: (config: AppwriteConfig) => Promise<void>;
4
- export declare const ensureDatabasesExist: (config: AppwriteConfig) => Promise<void>;
4
+ export declare const ensureDatabasesExist: (config: AppwriteConfig, databasesToEnsure?: Models.Database[]) => Promise<void>;
5
5
  export declare const wipeOtherDatabases: (database: Databases, databasesToKeep: Models.Database[]) => Promise<void>;
6
+ export declare const ensureCollectionsExist: (config: AppwriteConfig, database: Models.Database, collectionsToEnsure?: Models.Collection[]) => Promise<void>;
@@ -9,6 +9,9 @@ export const setupMigrationDatabase = async (config) => {
9
9
  console.log("Starting Migrations Setup");
10
10
  console.log("---------------------------------");
11
11
  const database = new Databases(config.appwriteClient);
12
+ if (!config.appwriteClient) {
13
+ throw new Error("Appwrite client is not initialized in the config");
14
+ }
12
15
  let db;
13
16
  const migrationCollectionsSetup = getMigrationCollectionSchemas();
14
17
  try {
@@ -60,16 +63,23 @@ export const setupMigrationDatabase = async (config) => {
60
63
  console.log("Migrations Setup Complete");
61
64
  console.log("---------------------------------");
62
65
  };
63
- export const ensureDatabasesExist = async (config) => {
66
+ export const ensureDatabasesExist = async (config, databasesToEnsure) => {
67
+ if (!config.appwriteClient) {
68
+ throw new Error("Appwrite client is not initialized in the config");
69
+ }
64
70
  const database = new Databases(config.appwriteClient);
65
- const databasesToEnsure = config.databases;
71
+ const databasesToCreate = databasesToEnsure || config.databases || [];
72
+ if (!databasesToCreate.length) {
73
+ console.log("No databases to create");
74
+ return;
75
+ }
66
76
  const existingDatabases = await tryAwaitWithRetry(async () => await database.list([Query.limit(500)]));
67
77
  const migrationsDatabase = existingDatabases.databases.find((d) => d.name.toLowerCase().trim().replace(" ", "") === "migrations");
68
78
  if (existingDatabases.databases.length !== 0 && migrationsDatabase) {
69
79
  console.log("Creating all databases except migrations");
70
- databasesToEnsure.push(migrationsDatabase);
80
+ databasesToCreate.push(migrationsDatabase);
71
81
  }
72
- for (const db of databasesToEnsure) {
82
+ for (const db of databasesToCreate) {
73
83
  if (!existingDatabases.databases.some((d) => d.name === db.name)) {
74
84
  await tryAwaitWithRetry(async () => await database.create(db.$id || ulid(), db.name, true));
75
85
  console.log(`${db.name} database created`);
@@ -77,7 +87,7 @@ export const ensureDatabasesExist = async (config) => {
77
87
  }
78
88
  };
79
89
  export const wipeOtherDatabases = async (database, databasesToKeep) => {
80
- console.log(`Databases to keep: ${databasesToKeep.join(", ")}`);
90
+ console.log(`Databases to keep: ${databasesToKeep.map(db => db.name).join(", ")}`);
81
91
  const allDatabases = await tryAwaitWithRetry(async () => await database.list([Query.limit(500)]));
82
92
  const migrationsDatabase = allDatabases.databases.find((d) => d.name.toLowerCase().trim().replace(" ", "") === "migrations");
83
93
  if (allDatabases.databases.length !== 0 && migrationsDatabase) {
@@ -91,3 +101,15 @@ export const wipeOtherDatabases = async (database, databasesToKeep) => {
91
101
  }
92
102
  }
93
103
  };
104
+ export const ensureCollectionsExist = async (config, database, collectionsToEnsure) => {
105
+ const databaseClient = new Databases(config.appwriteClient);
106
+ const collectionsToCreate = collectionsToEnsure ||
107
+ (config.collections ? config.collections : []);
108
+ const existingCollections = await tryAwaitWithRetry(async () => await databaseClient.listCollections(database.$id, [Query.limit(500)]));
109
+ for (const collection of collectionsToCreate) {
110
+ if (!existingCollections.collections.some((c) => c.name === collection.name)) {
111
+ await tryAwaitWithRetry(async () => await databaseClient.createCollection(database.$id, ulid(), collection.name, undefined, true, true));
112
+ console.log(`${collection.name} collection created in ${database.name}`);
113
+ }
114
+ }
115
+ };
@@ -74,7 +74,10 @@ export const ensureDatabaseConfigBucketsExist = async (storage, config, database
74
74
  console.log(`Bucket ${database.bucket.$id} created successfully.`);
75
75
  }
76
76
  catch (createError) {
77
- console.error(`Failed to create bucket ${database.bucket.$id}:`, createError);
77
+ // console.error(
78
+ // `Failed to create bucket ${database.bucket.$id}:`,
79
+ // createError
80
+ // );
78
81
  }
79
82
  }
80
83
  }
@@ -71,10 +71,13 @@ const baseConfig = {
71
71
  const collectionsConfig = [
72
72
  {
73
73
  name: "ExampleCollection",
74
- content: `import { CollectionCreate } from "appwrite-utils";
74
+ content: `import type { CollectionCreate } from "appwrite-utils";
75
75
 
76
76
  const ExampleCollection: Partial<CollectionCreate> = {
77
77
  name: 'ExampleCollection',
78
+ $id: '${ulid()}',
79
+ documentSecurity: false,
80
+ enabled: true,
78
81
  $permissions: [
79
82
  { permission: 'read', target: 'any' },
80
83
  { permission: 'create', target: 'users' },