appwrite-utils-cli 0.10.1 → 0.10.3
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/README.md +2 -0
- package/dist/collections/methods.js +32 -11
- package/dist/interactiveCLI.js +30 -12
- package/package.json +1 -1
- package/src/collections/methods.ts +57 -20
- package/src/interactiveCLI.ts +48 -19
- package/dist/functions/templates/count-docs-in-collection/src/main.d.ts +0 -21
- package/dist/functions/templates/count-docs-in-collection/src/main.js +0 -114
- package/dist/functions/templates/count-docs-in-collection/src/request.d.ts +0 -15
- package/dist/functions/templates/count-docs-in-collection/src/request.js +0 -6
- package/dist/functions/templates/typescript-node/src/index.d.ts +0 -11
- package/dist/functions/templates/typescript-node/src/index.js +0 -11
package/README.md
CHANGED
@@ -147,6 +147,8 @@ This updated CLI ensures that developers have robust tools at their fingertips t
|
|
147
147
|
|
148
148
|
## Changelog
|
149
149
|
|
150
|
+
- 0.10.03: Fixed `syncDb` to push the configurations properly, accidentally hurt it during `synchronizeConfigurations`
|
151
|
+
- 0.10.02: Updated `wipeCollection` to handle errors gracefully
|
150
152
|
- 0.10.01: Fixed `predeployCommands` to work
|
151
153
|
- 0.10.001: Updated `deployFunction` to not updateConfig if it's already present
|
152
154
|
- 0.10.0: Fixed `synchronize configurations` for functions, now you do not need to deploy the function first
|
@@ -87,19 +87,40 @@ export const fetchAndCacheCollectionByName = async (db, dbId, collectionName) =>
|
|
87
87
|
}
|
88
88
|
};
|
89
89
|
async function wipeDocumentsFromCollection(database, databaseId, collectionId) {
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
documents
|
90
|
+
try {
|
91
|
+
const initialDocuments = await database.listDocuments(databaseId, collectionId, [Query.limit(1000)]);
|
92
|
+
let documents = initialDocuments.documents;
|
93
|
+
let totalDocuments = documents.length;
|
94
|
+
while (documents.length === 1000) {
|
95
|
+
const docsResponse = await database.listDocuments(databaseId, collectionId, [Query.limit(1000)]);
|
96
|
+
documents = documents.concat(docsResponse.documents);
|
97
|
+
totalDocuments = documents.length;
|
98
|
+
}
|
99
|
+
console.log(`Found ${totalDocuments} documents to delete`);
|
100
|
+
const maxStackSize = 25; // Reduced batch size
|
101
|
+
for (let i = 0; i < documents.length; i += maxStackSize) {
|
102
|
+
const batch = documents.slice(i, i + maxStackSize);
|
103
|
+
const deletePromises = batch.map(async (doc) => {
|
104
|
+
try {
|
105
|
+
await database.deleteDocument(databaseId, collectionId, doc.$id);
|
106
|
+
}
|
107
|
+
catch (error) {
|
108
|
+
// Skip if document doesn't exist or other non-critical errors
|
109
|
+
if (!error.message?.includes("Document with the requested ID could not be found")) {
|
110
|
+
console.error(`Failed to delete document ${doc.$id}:`, error.message);
|
111
|
+
}
|
112
|
+
}
|
113
|
+
});
|
114
|
+
await Promise.all(deletePromises);
|
115
|
+
await delay(250); // Increased delay between batches
|
116
|
+
console.log(`Deleted batch of ${batch.length} documents (${i + batch.length}/${totalDocuments})`);
|
117
|
+
}
|
118
|
+
console.log(`Completed deletion of ${totalDocuments} documents from collection ${collectionId}`);
|
95
119
|
}
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
await Promise.all(batchDeletePromises.slice(i, i + maxStackSize));
|
100
|
-
await delay(100);
|
120
|
+
catch (error) {
|
121
|
+
console.error(`Error wiping documents from collection ${collectionId}:`, error);
|
122
|
+
throw error;
|
101
123
|
}
|
102
|
-
console.log(`Deleted ${documents.length} documents from collection ${collectionId}`);
|
103
124
|
}
|
104
125
|
export const wipeDatabase = async (database, databaseId) => {
|
105
126
|
console.log(`Wiping database: ${databaseId}`);
|
package/dist/interactiveCLI.js
CHANGED
@@ -718,28 +718,46 @@ export class InteractiveCLI {
|
|
718
718
|
}, bucketId.length > 0 ? bucketId : ulid());
|
719
719
|
}
|
720
720
|
async syncDb() {
|
721
|
-
console.log(chalk.
|
722
|
-
const
|
723
|
-
|
724
|
-
|
721
|
+
console.log(chalk.blue("Pushing local configuration to Appwrite..."));
|
722
|
+
const databases = await this.selectDatabases(this.getLocalDatabases(), chalk.blue("Select local databases to push:"), true);
|
723
|
+
if (!databases.length) {
|
724
|
+
console.log(chalk.yellow("No databases selected. Skipping database sync."));
|
725
|
+
return;
|
726
|
+
}
|
727
|
+
const collections = await this.selectCollections(databases[0], this.controller.database, chalk.blue("Select local collections to push:"), true, true // prefer local
|
725
728
|
);
|
726
|
-
const
|
729
|
+
const { syncFunctions } = await inquirer.prompt([
|
727
730
|
{
|
728
731
|
type: "confirm",
|
729
732
|
name: "syncFunctions",
|
730
|
-
message: "Do you want to
|
733
|
+
message: "Do you want to push local functions to remote?",
|
731
734
|
default: false,
|
732
735
|
},
|
733
736
|
]);
|
734
|
-
|
735
|
-
|
736
|
-
);
|
737
|
+
try {
|
738
|
+
// First sync databases and collections
|
737
739
|
await this.controller.syncDb(databases, collections);
|
738
|
-
|
739
|
-
|
740
|
+
console.log(chalk.green("Database and collections pushed successfully"));
|
741
|
+
// Then handle functions if requested
|
742
|
+
if (syncFunctions && this.controller.config?.functions?.length) {
|
743
|
+
const functions = await this.selectFunctions(chalk.blue("Select local functions to push:"), true, true // prefer local
|
744
|
+
);
|
745
|
+
for (const func of functions) {
|
746
|
+
try {
|
747
|
+
await this.controller.deployFunction(func.name);
|
748
|
+
console.log(chalk.green(`Function ${func.name} deployed successfully`));
|
749
|
+
}
|
750
|
+
catch (error) {
|
751
|
+
console.error(chalk.red(`Failed to deploy function ${func.name}:`), error);
|
752
|
+
}
|
753
|
+
}
|
740
754
|
}
|
755
|
+
console.log(chalk.green("Local configuration push completed successfully!"));
|
756
|
+
}
|
757
|
+
catch (error) {
|
758
|
+
console.error(chalk.red("Failed to push local configuration:"), error);
|
759
|
+
throw error;
|
741
760
|
}
|
742
|
-
console.log(chalk.green("Database sync completed."));
|
743
761
|
}
|
744
762
|
async synchronizeConfigurations() {
|
745
763
|
console.log(chalk.blue("Synchronizing configurations..."));
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "appwrite-utils-cli",
|
3
3
|
"description": "Appwrite Utility Functions to help with database management, data conversion, data import, migrations, and much more. Meant to be used as a CLI tool, I do not recommend installing this in frontend environments.",
|
4
|
-
"version": "0.10.
|
4
|
+
"version": "0.10.03",
|
5
5
|
"main": "src/main.ts",
|
6
6
|
"type": "module",
|
7
7
|
"repository": {
|
@@ -129,31 +129,68 @@ async function wipeDocumentsFromCollection(
|
|
129
129
|
databaseId: string,
|
130
130
|
collectionId: string
|
131
131
|
) {
|
132
|
-
|
133
|
-
|
134
|
-
collectionId,
|
135
|
-
[Query.limit(1000)]
|
136
|
-
);
|
137
|
-
let documents = initialDocuments.documents;
|
138
|
-
while (documents.length === 1000) {
|
139
|
-
const docsResponse = await database.listDocuments(
|
132
|
+
try {
|
133
|
+
const initialDocuments = await database.listDocuments(
|
140
134
|
databaseId,
|
141
135
|
collectionId,
|
142
136
|
[Query.limit(1000)]
|
143
137
|
);
|
144
|
-
documents =
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
138
|
+
let documents = initialDocuments.documents;
|
139
|
+
let totalDocuments = documents.length;
|
140
|
+
|
141
|
+
while (documents.length === 1000) {
|
142
|
+
const docsResponse = await database.listDocuments(
|
143
|
+
databaseId,
|
144
|
+
collectionId,
|
145
|
+
[Query.limit(1000)]
|
146
|
+
);
|
147
|
+
documents = documents.concat(docsResponse.documents);
|
148
|
+
totalDocuments = documents.length;
|
149
|
+
}
|
150
|
+
|
151
|
+
console.log(`Found ${totalDocuments} documents to delete`);
|
152
|
+
|
153
|
+
const maxStackSize = 25; // Reduced batch size
|
154
|
+
for (let i = 0; i < documents.length; i += maxStackSize) {
|
155
|
+
const batch = documents.slice(i, i + maxStackSize);
|
156
|
+
const deletePromises = batch.map(async (doc) => {
|
157
|
+
try {
|
158
|
+
await database.deleteDocument(databaseId, collectionId, doc.$id);
|
159
|
+
} catch (error: any) {
|
160
|
+
// Skip if document doesn't exist or other non-critical errors
|
161
|
+
if (
|
162
|
+
!error.message?.includes(
|
163
|
+
"Document with the requested ID could not be found"
|
164
|
+
)
|
165
|
+
) {
|
166
|
+
console.error(
|
167
|
+
`Failed to delete document ${doc.$id}:`,
|
168
|
+
error.message
|
169
|
+
);
|
170
|
+
}
|
171
|
+
}
|
172
|
+
});
|
173
|
+
|
174
|
+
await Promise.all(deletePromises);
|
175
|
+
await delay(250); // Increased delay between batches
|
176
|
+
|
177
|
+
console.log(
|
178
|
+
`Deleted batch of ${batch.length} documents (${
|
179
|
+
i + batch.length
|
180
|
+
}/${totalDocuments})`
|
181
|
+
);
|
182
|
+
}
|
183
|
+
|
184
|
+
console.log(
|
185
|
+
`Completed deletion of ${totalDocuments} documents from collection ${collectionId}`
|
186
|
+
);
|
187
|
+
} catch (error) {
|
188
|
+
console.error(
|
189
|
+
`Error wiping documents from collection ${collectionId}:`,
|
190
|
+
error
|
191
|
+
);
|
192
|
+
throw error;
|
153
193
|
}
|
154
|
-
console.log(
|
155
|
-
`Deleted ${documents.length} documents from collection ${collectionId}`
|
156
|
-
);
|
157
194
|
}
|
158
195
|
|
159
196
|
export const wipeDatabase = async (
|
package/src/interactiveCLI.ts
CHANGED
@@ -977,44 +977,73 @@ export class InteractiveCLI {
|
|
977
977
|
}
|
978
978
|
|
979
979
|
private async syncDb(): Promise<void> {
|
980
|
-
console.log(chalk.
|
981
|
-
|
980
|
+
console.log(chalk.blue("Pushing local configuration to Appwrite..."));
|
981
|
+
|
982
982
|
const databases = await this.selectDatabases(
|
983
|
-
|
984
|
-
chalk.blue("Select databases to
|
983
|
+
this.getLocalDatabases(),
|
984
|
+
chalk.blue("Select local databases to push:"),
|
985
985
|
true
|
986
986
|
);
|
987
|
+
|
988
|
+
if (!databases.length) {
|
989
|
+
console.log(
|
990
|
+
chalk.yellow("No databases selected. Skipping database sync.")
|
991
|
+
);
|
992
|
+
return;
|
993
|
+
}
|
994
|
+
|
987
995
|
const collections = await this.selectCollections(
|
988
996
|
databases[0],
|
989
997
|
this.controller!.database!,
|
990
|
-
chalk.blue("Select collections to
|
998
|
+
chalk.blue("Select local collections to push:"),
|
991
999
|
true,
|
992
1000
|
true // prefer local
|
993
1001
|
);
|
994
|
-
|
1002
|
+
|
1003
|
+
const { syncFunctions } = await inquirer.prompt([
|
995
1004
|
{
|
996
1005
|
type: "confirm",
|
997
1006
|
name: "syncFunctions",
|
998
|
-
message: "Do you want to
|
1007
|
+
message: "Do you want to push local functions to remote?",
|
999
1008
|
default: false,
|
1000
1009
|
},
|
1001
1010
|
]);
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
true,
|
1006
|
-
true // prefer local
|
1007
|
-
);
|
1011
|
+
|
1012
|
+
try {
|
1013
|
+
// First sync databases and collections
|
1008
1014
|
await this.controller!.syncDb(databases, collections);
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1015
|
+
console.log(chalk.green("Database and collections pushed successfully"));
|
1016
|
+
|
1017
|
+
// Then handle functions if requested
|
1018
|
+
if (syncFunctions && this.controller!.config?.functions?.length) {
|
1019
|
+
const functions = await this.selectFunctions(
|
1020
|
+
chalk.blue("Select local functions to push:"),
|
1021
|
+
true,
|
1022
|
+
true // prefer local
|
1014
1023
|
);
|
1024
|
+
|
1025
|
+
for (const func of functions) {
|
1026
|
+
try {
|
1027
|
+
await this.controller!.deployFunction(func.name);
|
1028
|
+
console.log(
|
1029
|
+
chalk.green(`Function ${func.name} deployed successfully`)
|
1030
|
+
);
|
1031
|
+
} catch (error) {
|
1032
|
+
console.error(
|
1033
|
+
chalk.red(`Failed to deploy function ${func.name}:`),
|
1034
|
+
error
|
1035
|
+
);
|
1036
|
+
}
|
1037
|
+
}
|
1015
1038
|
}
|
1039
|
+
|
1040
|
+
console.log(
|
1041
|
+
chalk.green("Local configuration push completed successfully!")
|
1042
|
+
);
|
1043
|
+
} catch (error) {
|
1044
|
+
console.error(chalk.red("Failed to push local configuration:"), error);
|
1045
|
+
throw error;
|
1016
1046
|
}
|
1017
|
-
console.log(chalk.green("Database sync completed."));
|
1018
1047
|
}
|
1019
1048
|
|
1020
1049
|
private async synchronizeConfigurations(): Promise<void> {
|
@@ -1,21 +0,0 @@
|
|
1
|
-
import { AppwriteRequest, AppwriteResponse } from "appwrite-utils";
|
2
|
-
/**
|
3
|
-
* Main function to handle document counting requests.
|
4
|
-
* @param {Object} params - The function parameters.
|
5
|
-
* @param {Object} params.req - The request object.
|
6
|
-
* @param {Object} params.res - The response object.
|
7
|
-
* @param {Function} params.log - Logging function.
|
8
|
-
* @param {Function} params.error - Error logging function.
|
9
|
-
* @returns {Promise<Object>} JSON response with count or error message.
|
10
|
-
*/
|
11
|
-
declare const _default: ({ req, res, log, error }: {
|
12
|
-
req: AppwriteRequest;
|
13
|
-
res: AppwriteResponse;
|
14
|
-
log: (message: string) => void;
|
15
|
-
error: (message: string) => void;
|
16
|
-
}) => Promise<{
|
17
|
-
body: Uint8Array;
|
18
|
-
statusCode: number;
|
19
|
-
headers: Record<string, string>;
|
20
|
-
}>;
|
21
|
-
export default _default;
|
@@ -1,114 +0,0 @@
|
|
1
|
-
import { Client, Databases, Query } from "node-appwrite";
|
2
|
-
import { AppwriteRequest, AppwriteResponse } from "appwrite-utils";
|
3
|
-
import { requestSchema } from "./request.js";
|
4
|
-
/**
|
5
|
-
* Main function to handle document counting requests.
|
6
|
-
* @param {Object} params - The function parameters.
|
7
|
-
* @param {Object} params.req - The request object.
|
8
|
-
* @param {Object} params.res - The response object.
|
9
|
-
* @param {Function} params.log - Logging function.
|
10
|
-
* @param {Function} params.error - Error logging function.
|
11
|
-
* @returns {Promise<Object>} JSON response with count or error message.
|
12
|
-
*/
|
13
|
-
export default async ({ req, res, log, error }) => {
|
14
|
-
// Initialize Appwrite client
|
15
|
-
const client = new Client()
|
16
|
-
.setEndpoint(process.env["APPWRITE_FUNCTION_ENDPOINT"])
|
17
|
-
.setProject(process.env["APPWRITE_FUNCTION_PROJECT_ID"])
|
18
|
-
.setKey(req.headers["x-appwrite-key"] || "");
|
19
|
-
const databases = new Databases(client);
|
20
|
-
try {
|
21
|
-
if (req.method === "POST") {
|
22
|
-
// Parse request body
|
23
|
-
const body = requestSchema.safeParse(typeof req.body === "string" ? JSON.parse(req.body) : req.body);
|
24
|
-
if (!body.success) {
|
25
|
-
return res.json({ success: false, error: body.error }, 400);
|
26
|
-
}
|
27
|
-
const { databaseId, collectionId, queries = [] } = body.data;
|
28
|
-
log(`Queries: ${JSON.stringify(queries)}`);
|
29
|
-
// Count documents in the specified collection
|
30
|
-
const count = await countAllDocuments(log, databases, databaseId, collectionId, queries);
|
31
|
-
// Return successful response with document count
|
32
|
-
return res.json({
|
33
|
-
success: true,
|
34
|
-
count: count,
|
35
|
-
});
|
36
|
-
}
|
37
|
-
else {
|
38
|
-
// Return error for non-POST requests
|
39
|
-
return res.json({ success: false, error: "Method not allowed" }, 405);
|
40
|
-
}
|
41
|
-
}
|
42
|
-
catch (err) {
|
43
|
-
// Log and return any errors
|
44
|
-
error(`Error processing request: ${err}`);
|
45
|
-
return res.json({ success: false, error: err.message }, 500);
|
46
|
-
}
|
47
|
-
};
|
48
|
-
/**
|
49
|
-
* Counts all documents in a collection, handling large collections efficiently.
|
50
|
-
* @param {Function} log - Logging function.
|
51
|
-
* @param {Databases} databases - Appwrite Databases instance.
|
52
|
-
* @param {string} databaseId - ID of the database.
|
53
|
-
* @param {string} collectionId - ID of the collection.
|
54
|
-
* @param {string[]} queries - Array of query strings to filter documents.
|
55
|
-
* @param {number} batchSize - Size of batches for processing (default: 1000).
|
56
|
-
* @returns {Promise<number>} Total count of documents.
|
57
|
-
*/
|
58
|
-
async function countAllDocuments(log, databases, databaseId, collectionId, queries = [], batchSize = 1000) {
|
59
|
-
// Filter out limit and offset queries
|
60
|
-
const initialQueries = queries.filter((q) => !(q.includes("limit") || q.includes("offset")));
|
61
|
-
// Initial query to check if total is less than 5000
|
62
|
-
const initialResponse = await databases.listDocuments(databaseId, collectionId, [...initialQueries, Query.limit(1)]);
|
63
|
-
if (initialResponse.total < 5000) {
|
64
|
-
log(`Total documents (from initial response): ${initialResponse.total}`);
|
65
|
-
return initialResponse.total;
|
66
|
-
}
|
67
|
-
// If total is 5000 or more, we need to count manually
|
68
|
-
let bound = 5000;
|
69
|
-
// Exponential search to find an upper bound
|
70
|
-
while (true) {
|
71
|
-
log(`Querying for offset ${bound}`);
|
72
|
-
try {
|
73
|
-
const response = await databases.listDocuments(databaseId, collectionId, [
|
74
|
-
...initialQueries,
|
75
|
-
Query.limit(1),
|
76
|
-
Query.offset(bound),
|
77
|
-
]);
|
78
|
-
if (response.documents.length === 0) {
|
79
|
-
break;
|
80
|
-
}
|
81
|
-
bound *= 2;
|
82
|
-
}
|
83
|
-
catch (error) {
|
84
|
-
break;
|
85
|
-
}
|
86
|
-
}
|
87
|
-
// Binary search to find the exact count
|
88
|
-
let low = Math.floor(bound / 2);
|
89
|
-
let high = bound;
|
90
|
-
let lastValidCount = low;
|
91
|
-
while (low <= high) {
|
92
|
-
const mid = Math.floor((low + high) / 2);
|
93
|
-
log(`Binary search: Querying for offset ${mid}`);
|
94
|
-
try {
|
95
|
-
const response = await databases.listDocuments(databaseId, collectionId, [
|
96
|
-
...initialQueries,
|
97
|
-
Query.limit(1),
|
98
|
-
Query.offset(mid),
|
99
|
-
]);
|
100
|
-
if (response.documents.length > 0) {
|
101
|
-
lastValidCount = mid + 1; // +1 because offset is 0-based
|
102
|
-
low = mid + 1;
|
103
|
-
}
|
104
|
-
else {
|
105
|
-
high = mid - 1;
|
106
|
-
}
|
107
|
-
}
|
108
|
-
catch (error) {
|
109
|
-
high = mid - 1;
|
110
|
-
}
|
111
|
-
}
|
112
|
-
log(`Total documents: ${lastValidCount}`);
|
113
|
-
return lastValidCount;
|
114
|
-
}
|
@@ -1,15 +0,0 @@
|
|
1
|
-
import { z } from "zod";
|
2
|
-
export declare const requestSchema: z.ZodObject<{
|
3
|
-
databaseId: z.ZodString;
|
4
|
-
collectionId: z.ZodString;
|
5
|
-
queries: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
6
|
-
}, "strip", z.ZodTypeAny, {
|
7
|
-
collectionId: string;
|
8
|
-
databaseId: string;
|
9
|
-
queries?: string[] | undefined;
|
10
|
-
}, {
|
11
|
-
collectionId: string;
|
12
|
-
databaseId: string;
|
13
|
-
queries?: string[] | undefined;
|
14
|
-
}>;
|
15
|
-
export type Request = z.infer<typeof requestSchema>;
|
@@ -1,11 +0,0 @@
|
|
1
|
-
import { AppwriteRequest, AppwriteResponse } from "appwrite-utils";
|
2
|
-
export default function ({ req, res, log, error, }: {
|
3
|
-
req: AppwriteRequest;
|
4
|
-
res: AppwriteResponse;
|
5
|
-
log: (message: string) => void;
|
6
|
-
error: (message: string) => void;
|
7
|
-
}): Promise<{
|
8
|
-
body: Uint8Array;
|
9
|
-
statusCode: number;
|
10
|
-
headers: Record<string, string>;
|
11
|
-
}>;
|
@@ -1,11 +0,0 @@
|
|
1
|
-
import { Client } from "node-appwrite";
|
2
|
-
import { AppwriteRequest, AppwriteResponse } from "appwrite-utils";
|
3
|
-
export default async function ({ req, res, log, error, }) {
|
4
|
-
const client = new Client()
|
5
|
-
.setEndpoint(process.env["APPWRITE_FUNCTION_ENDPOINT"])
|
6
|
-
.setProject(process.env["APPWRITE_FUNCTION_PROJECT_ID"])
|
7
|
-
.setKey(req.headers["x-appwrite-key"] || "");
|
8
|
-
return res.json({
|
9
|
-
message: "Hello from TypeScript function!",
|
10
|
-
});
|
11
|
-
}
|