appwrite-utils-cli 0.9.993 → 0.9.995
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 +1 -2
- package/dist/collections/methods.js +6 -4
- package/dist/functions/deployments.d.ts +4 -0
- package/dist/functions/deployments.js +114 -0
- package/dist/functions/methods.d.ts +13 -8
- package/dist/functions/methods.js +74 -20
- package/dist/functions/templates/count-docs-in-collection/src/main.d.ts +21 -0
- package/dist/functions/templates/count-docs-in-collection/src/main.js +114 -0
- package/dist/functions/templates/count-docs-in-collection/src/request.d.ts +15 -0
- package/dist/functions/templates/count-docs-in-collection/src/request.js +6 -0
- package/dist/functions/templates/typescript-node/src/index.d.ts +11 -0
- package/dist/functions/templates/typescript-node/src/index.js +11 -0
- package/dist/interactiveCLI.d.ts +6 -0
- package/dist/interactiveCLI.js +437 -32
- package/dist/main.js +0 -0
- package/dist/migrations/appwriteToX.js +32 -1
- package/dist/migrations/schemaStrings.d.ts +1 -0
- package/dist/migrations/schemaStrings.js +74 -2
- package/dist/migrations/transfer.js +112 -123
- package/dist/utils/loadConfigs.d.ts +1 -0
- package/dist/utils/loadConfigs.js +19 -0
- package/dist/utils/schemaStrings.js +27 -0
- package/dist/utilsController.d.ts +5 -1
- package/dist/utilsController.js +54 -2
- package/package.json +57 -55
- package/src/collections/methods.ts +29 -10
- package/src/functions/deployments.ts +190 -0
- package/src/functions/methods.ts +295 -235
- package/src/functions/templates/count-docs-in-collection/README.md +54 -0
- package/src/functions/templates/count-docs-in-collection/src/main.ts +159 -0
- package/src/functions/templates/count-docs-in-collection/src/request.ts +9 -0
- package/src/functions/templates/poetry/README.md +30 -0
- package/src/functions/templates/poetry/pyproject.toml +16 -0
- package/src/functions/templates/poetry/src/__init__.py +0 -0
- package/src/functions/templates/poetry/src/index.py +16 -0
- package/src/functions/templates/typescript-node/README.md +32 -0
- package/src/functions/templates/typescript-node/src/index.ts +23 -0
- package/src/interactiveCLI.ts +606 -47
- package/src/migrations/appwriteToX.ts +44 -1
- package/src/migrations/schemaStrings.ts +83 -2
- package/src/migrations/transfer.ts +280 -207
- package/src/setupController.ts +41 -41
- package/src/utils/loadConfigs.ts +24 -0
- package/src/utils/schemaStrings.ts +27 -0
- package/src/utilsController.ts +63 -3
- package/tsconfig.json +8 -1
package/README.md
CHANGED
@@ -86,8 +86,6 @@ Available options:
|
|
86
86
|
- `--functionId`: Function ID to update
|
87
87
|
- `--specification`: New function specification (one of: s-0.5vcpu-512mb, s-1vcpu-1gb, s-2vcpu-2gb, s-2vcpu-4gb, s-4vcpu-4gb, s-4vcpu-8gb, s-8vcpu-4gb, s-8vcpu-8gb)
|
88
88
|
|
89
|
-
|
90
|
-
|
91
89
|
## Examples
|
92
90
|
|
93
91
|
### Transfer Databases
|
@@ -149,6 +147,7 @@ This updated CLI ensures that developers have robust tools at their fingertips t
|
|
149
147
|
|
150
148
|
## Changelog
|
151
149
|
|
150
|
+
- 0.9.994: Added function deployment management, in BETA, and fixed document transfer between databases
|
152
151
|
- 0.9.993: Fixed `updateFunctionSpecifications` resetting functions to default with undefined values (oops)
|
153
152
|
- 0.9.992: Added `updateFunctionSpecifications` which lists functions and specifications to allow you to update your functions max CPU and RAM usage per-function
|
154
153
|
- 0.9.990: Fixed `transferFilesLocalToLocal` and `remote` if a document exists with that `$id`, also fixed wipe `"all"` option also wiping the associated buckets
|
@@ -3,7 +3,7 @@ import { nameToIdMapping, processQueue } from "../migrations/queue.js";
|
|
3
3
|
import { createUpdateCollectionAttributes } from "./attributes.js";
|
4
4
|
import { createOrUpdateIndexes } from "./indexes.js";
|
5
5
|
import _ from "lodash";
|
6
|
-
import { SchemaGenerator } from "../
|
6
|
+
import { SchemaGenerator } from "../migrations/schemaStrings.js";
|
7
7
|
import { delay, tryAwaitWithRetry } from "../utils/helperFunctions.js";
|
8
8
|
export const documentExists = async (db, dbId, targetCollectionId, toCreateObject) => {
|
9
9
|
// Had to do this because kept running into issues with type checking arrays so, sorry 40ms
|
@@ -93,7 +93,7 @@ async function wipeDocumentsFromCollection(database, databaseId, collectionId) {
|
|
93
93
|
const docsResponse = await database.listDocuments(databaseId, collectionId, [Query.limit(1000)]);
|
94
94
|
documents = documents.concat(docsResponse.documents);
|
95
95
|
}
|
96
|
-
const batchDeletePromises = documents.map(doc => database.deleteDocument(databaseId, collectionId, doc.$id));
|
96
|
+
const batchDeletePromises = documents.map((doc) => database.deleteDocument(databaseId, collectionId, doc.$id));
|
97
97
|
const maxStackSize = 100;
|
98
98
|
for (let i = 0; i < batchDeletePromises.length; i += maxStackSize) {
|
99
99
|
await Promise.all(batchDeletePromises.slice(i, i + maxStackSize));
|
@@ -117,7 +117,9 @@ export const wipeDatabase = async (database, databaseId) => {
|
|
117
117
|
return collectionsDeleted;
|
118
118
|
};
|
119
119
|
export const wipeCollection = async (database, databaseId, collectionId) => {
|
120
|
-
const collections = await database.listCollections(databaseId, [
|
120
|
+
const collections = await database.listCollections(databaseId, [
|
121
|
+
Query.equal("$id", collectionId),
|
122
|
+
]);
|
121
123
|
if (collections.total === 0) {
|
122
124
|
console.log(`Collection ${collectionId} not found`);
|
123
125
|
return;
|
@@ -141,7 +143,7 @@ export const createOrUpdateCollections = async (database, databaseId, config, de
|
|
141
143
|
const permissions = [];
|
142
144
|
if (collection.$permissions && collection.$permissions.length > 0) {
|
143
145
|
for (const permission of collection.$permissions) {
|
144
|
-
if (typeof permission ===
|
146
|
+
if (typeof permission === "string") {
|
145
147
|
permissions.push(permission);
|
146
148
|
}
|
147
149
|
else {
|
@@ -0,0 +1,4 @@
|
|
1
|
+
import { Client } from "node-appwrite";
|
2
|
+
import { type AppwriteFunction } from "appwrite-utils";
|
3
|
+
export declare const deployFunction: (client: Client, functionId: string, codePath: string, activate?: boolean, entrypoint?: string, commands?: string) => Promise<import("node-appwrite").Models.Deployment>;
|
4
|
+
export declare const deployLocalFunction: (client: Client, functionName: string, functionConfig: AppwriteFunction, functionPath?: string) => Promise<import("node-appwrite").Models.Deployment>;
|
@@ -0,0 +1,114 @@
|
|
1
|
+
import { Client, Functions, Runtime } from "node-appwrite";
|
2
|
+
import { InputFile } from "node-appwrite/file";
|
3
|
+
import { create as createTarball } from "tar";
|
4
|
+
import { join } from "node:path";
|
5
|
+
import fs from "node:fs";
|
6
|
+
import {} from "appwrite-utils";
|
7
|
+
import chalk from "chalk";
|
8
|
+
import cliProgress from "cli-progress";
|
9
|
+
import { execSync } from "child_process";
|
10
|
+
import { createFunction, getFunction, updateFunctionSpecifications, } from "./methods.js";
|
11
|
+
const findFunctionDirectory = (basePath, functionName) => {
|
12
|
+
const normalizedName = functionName.toLowerCase().replace(/\s+/g, "-");
|
13
|
+
const dirs = fs.readdirSync(basePath, { withFileTypes: true });
|
14
|
+
for (const dir of dirs) {
|
15
|
+
if (dir.isDirectory()) {
|
16
|
+
const fullPath = join(basePath, dir.name);
|
17
|
+
if (dir.name.toLowerCase() === normalizedName) {
|
18
|
+
return fullPath;
|
19
|
+
}
|
20
|
+
const nestedResult = findFunctionDirectory(fullPath, functionName);
|
21
|
+
if (nestedResult) {
|
22
|
+
return nestedResult;
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
return undefined;
|
27
|
+
};
|
28
|
+
export const deployFunction = async (client, functionId, codePath, activate = true, entrypoint = "index.js", commands = "npm install") => {
|
29
|
+
const functions = new Functions(client);
|
30
|
+
console.log(chalk.blue("📦 Preparing function deployment..."));
|
31
|
+
const progressBar = new cliProgress.SingleBar({
|
32
|
+
format: "Uploading |" +
|
33
|
+
chalk.cyan("{bar}") +
|
34
|
+
"| {percentage}% | {value}/{total} Chunks",
|
35
|
+
barCompleteChar: "█",
|
36
|
+
barIncompleteChar: "░",
|
37
|
+
hideCursor: true,
|
38
|
+
});
|
39
|
+
const tarPath = join(process.cwd(), `function-${functionId}.tar.gz`);
|
40
|
+
await createTarball({
|
41
|
+
gzip: true,
|
42
|
+
file: tarPath,
|
43
|
+
cwd: codePath,
|
44
|
+
}, ["."]);
|
45
|
+
const fileBuffer = await fs.promises.readFile(tarPath);
|
46
|
+
const fileObject = InputFile.fromBuffer(new Uint8Array(fileBuffer), `function-${functionId}.tar.gz`);
|
47
|
+
try {
|
48
|
+
console.log(chalk.blue("🚀 Creating deployment..."));
|
49
|
+
const functionResponse = await functions.createDeployment(functionId, fileObject, activate, entrypoint, commands, (progress) => {
|
50
|
+
const chunks = progress.chunksUploaded;
|
51
|
+
const total = progress.chunksTotal;
|
52
|
+
if (chunks && total) {
|
53
|
+
if (chunks === 0) {
|
54
|
+
progressBar.start(total, 0);
|
55
|
+
}
|
56
|
+
else if (chunks === total) {
|
57
|
+
progressBar.update(total);
|
58
|
+
progressBar.stop();
|
59
|
+
console.log(chalk.green("✅ Upload complete!"));
|
60
|
+
}
|
61
|
+
else {
|
62
|
+
progressBar.update(chunks);
|
63
|
+
}
|
64
|
+
}
|
65
|
+
});
|
66
|
+
await fs.promises.unlink(tarPath);
|
67
|
+
console.log(chalk.green("✨ Function deployed successfully!"));
|
68
|
+
return functionResponse;
|
69
|
+
}
|
70
|
+
catch (error) {
|
71
|
+
progressBar.stop();
|
72
|
+
console.error(chalk.red("❌ Deployment failed:"), error);
|
73
|
+
throw error;
|
74
|
+
}
|
75
|
+
};
|
76
|
+
export const deployLocalFunction = async (client, functionName, functionConfig, functionPath) => {
|
77
|
+
let functionExists = true;
|
78
|
+
try {
|
79
|
+
await getFunction(client, functionConfig.$id);
|
80
|
+
}
|
81
|
+
catch (error) {
|
82
|
+
functionExists = false;
|
83
|
+
}
|
84
|
+
const resolvedPath = functionPath ||
|
85
|
+
functionConfig.dirPath ||
|
86
|
+
findFunctionDirectory(process.cwd(), functionName) ||
|
87
|
+
join(process.cwd(), "functions", functionName.toLowerCase().replace(/\s+/g, "-"));
|
88
|
+
if (functionConfig.predeployCommands?.length) {
|
89
|
+
console.log(chalk.blue("Executing predeploy commands..."));
|
90
|
+
for (const command of functionConfig.predeployCommands) {
|
91
|
+
try {
|
92
|
+
console.log(chalk.gray(`Executing: ${command}`));
|
93
|
+
execSync(command, {
|
94
|
+
cwd: resolvedPath,
|
95
|
+
stdio: "inherit",
|
96
|
+
});
|
97
|
+
}
|
98
|
+
catch (error) {
|
99
|
+
console.error(chalk.red(`Failed to execute predeploy command: ${command}`));
|
100
|
+
throw error;
|
101
|
+
}
|
102
|
+
}
|
103
|
+
}
|
104
|
+
if (!functionExists) {
|
105
|
+
await createFunction(client, functionConfig.$id, functionConfig.name, functionConfig.runtime, functionConfig.execute, functionConfig.events, functionConfig.schedule, functionConfig.timeout, functionConfig.enabled, functionConfig.logging, functionConfig.entrypoint, functionConfig.commands);
|
106
|
+
}
|
107
|
+
if (functionConfig.specification) {
|
108
|
+
await updateFunctionSpecifications(client, functionConfig.$id, functionConfig.specification);
|
109
|
+
}
|
110
|
+
const deployPath = functionConfig.deployDir
|
111
|
+
? join(resolvedPath, functionConfig.deployDir)
|
112
|
+
: resolvedPath;
|
113
|
+
return deployFunction(client, functionConfig.$id, deployPath, true, functionConfig.entrypoint, functionConfig.commands);
|
114
|
+
};
|
@@ -1,10 +1,15 @@
|
|
1
|
-
import { Client, Runtime
|
1
|
+
import { Client, Runtime } from "node-appwrite";
|
2
2
|
import { type Specification } from "appwrite-utils";
|
3
|
-
export declare const listFunctions: (client: Client, queries?: string[], search?: string) => Promise<Models.FunctionList>;
|
4
|
-
export declare const getFunction: (client: Client, functionId: string) => Promise<Models.Function>;
|
3
|
+
export declare const listFunctions: (client: Client, queries?: string[], search?: string) => Promise<import("node-appwrite").Models.FunctionList>;
|
4
|
+
export declare const getFunction: (client: Client, functionId: string) => Promise<import("node-appwrite").Models.Function>;
|
5
|
+
export declare const downloadLatestFunctionDeployment: (client: Client, functionId: string, basePath?: string) => Promise<{
|
6
|
+
path: string;
|
7
|
+
function: import("node-appwrite").Models.Function;
|
8
|
+
deployment: import("node-appwrite").Models.Deployment;
|
9
|
+
}>;
|
5
10
|
export declare const deleteFunction: (client: Client, functionId: string) => Promise<{}>;
|
6
|
-
export declare const createFunction: (client: Client, functionId: string, name: string, runtime: Runtime, execute?: string[], events?: string[], schedule?: string, timeout?: number, enabled?: boolean, logging?: boolean, entrypoint?: string, commands?: string, scopes?: string[], installationId?: string, providerRepositoryId?: string, providerBranch?: string, providerSilentMode?: boolean, providerRootDirectory?: string, templateRepository?: string, templateOwner?: string, templateRootDirectory?: string, templateVersion?: string, specification?: string) => Promise<Models.Function>;
|
7
|
-
export declare const updateFunctionSpecifications: (client: Client, functionId: string, specification: Specification) => Promise<Models.Function | undefined>;
|
8
|
-
export declare const listSpecifications: (client: Client) => Promise<Models.SpecificationList>;
|
9
|
-
export declare const updateFunction: (client: Client, functionId: string, name: string, runtime?: Runtime, execute?: string[], events?: string[], schedule?: string, timeout?: number, enabled?: boolean, logging?: boolean, entrypoint?: string, commands?: string, scopes?: string[], installationId?: string, providerRepositoryId?: string, providerBranch?: string, providerSilentMode?: boolean, providerRootDirectory?: string, specification?: Specification) => Promise<Models.Function>;
|
10
|
-
export declare const
|
11
|
+
export declare const createFunction: (client: Client, functionId: string, name: string, runtime: Runtime, execute?: string[], events?: string[], schedule?: string, timeout?: number, enabled?: boolean, logging?: boolean, entrypoint?: string, commands?: string, scopes?: string[], installationId?: string, providerRepositoryId?: string, providerBranch?: string, providerSilentMode?: boolean, providerRootDirectory?: string, templateRepository?: string, templateOwner?: string, templateRootDirectory?: string, templateVersion?: string, specification?: string) => Promise<import("node-appwrite").Models.Function>;
|
12
|
+
export declare const updateFunctionSpecifications: (client: Client, functionId: string, specification: Specification) => Promise<import("node-appwrite").Models.Function | undefined>;
|
13
|
+
export declare const listSpecifications: (client: Client) => Promise<import("node-appwrite").Models.SpecificationList>;
|
14
|
+
export declare const updateFunction: (client: Client, functionId: string, name: string, runtime?: Runtime, execute?: string[], events?: string[], schedule?: string, timeout?: number, enabled?: boolean, logging?: boolean, entrypoint?: string, commands?: string, scopes?: string[], installationId?: string, providerRepositoryId?: string, providerBranch?: string, providerSilentMode?: boolean, providerRootDirectory?: string, specification?: Specification) => Promise<import("node-appwrite").Models.Function>;
|
15
|
+
export declare const createFunctionTemplate: (templateType: "typescript-node" | "poetry" | "count-docs-in-collection", functionName: string, basePath?: string) => Promise<string>;
|
@@ -1,10 +1,9 @@
|
|
1
1
|
import { AppwriteException, Client, Functions, Query, Runtime, } from "node-appwrite";
|
2
|
-
import { InputFile } from "node-appwrite/file";
|
3
|
-
import { create as createTarball } from "tar";
|
4
2
|
import { join } from "node:path";
|
5
3
|
import fs from "node:fs";
|
6
4
|
import {} from "appwrite-utils";
|
7
5
|
import chalk from "chalk";
|
6
|
+
import { extract as extractTar } from "tar";
|
8
7
|
export const listFunctions = async (client, queries, search) => {
|
9
8
|
const functions = new Functions(client);
|
10
9
|
const functionsList = await functions.list(queries, search);
|
@@ -15,6 +14,42 @@ export const getFunction = async (client, functionId) => {
|
|
15
14
|
const functionResponse = await functions.get(functionId);
|
16
15
|
return functionResponse;
|
17
16
|
};
|
17
|
+
export const downloadLatestFunctionDeployment = async (client, functionId, basePath = process.cwd()) => {
|
18
|
+
const functions = new Functions(client);
|
19
|
+
const functionInfo = await getFunction(client, functionId);
|
20
|
+
const functionDeployments = await functions.listDeployments(functionId, [
|
21
|
+
Query.orderDesc("$createdAt"),
|
22
|
+
]);
|
23
|
+
if (functionDeployments.deployments.length === 0) {
|
24
|
+
throw new Error("No deployments found for function");
|
25
|
+
}
|
26
|
+
const latestDeployment = functionDeployments.deployments[0];
|
27
|
+
const deploymentData = await functions.getDeploymentDownload(functionId, latestDeployment.$id);
|
28
|
+
// Create function directory using provided basePath
|
29
|
+
const functionDir = join(basePath, functionInfo.name.toLowerCase().replace(/\s+/g, "-"));
|
30
|
+
await fs.promises.mkdir(functionDir, { recursive: true });
|
31
|
+
// Create temporary file for tar extraction
|
32
|
+
const tarPath = join(functionDir, "temp.tar.gz");
|
33
|
+
const uint8Array = new Uint8Array(deploymentData);
|
34
|
+
await fs.promises.writeFile(tarPath, uint8Array);
|
35
|
+
try {
|
36
|
+
// Extract tar file
|
37
|
+
extractTar({
|
38
|
+
C: functionDir,
|
39
|
+
file: tarPath,
|
40
|
+
sync: true,
|
41
|
+
});
|
42
|
+
return {
|
43
|
+
path: functionDir,
|
44
|
+
function: functionInfo,
|
45
|
+
deployment: latestDeployment,
|
46
|
+
};
|
47
|
+
}
|
48
|
+
finally {
|
49
|
+
// Clean up tar file
|
50
|
+
await fs.promises.unlink(tarPath).catch(() => { });
|
51
|
+
}
|
52
|
+
};
|
18
53
|
export const deleteFunction = async (client, functionId) => {
|
19
54
|
const functions = new Functions(client);
|
20
55
|
const functionResponse = await functions.delete(functionId);
|
@@ -58,22 +93,41 @@ export const updateFunction = async (client, functionId, name, runtime, execute,
|
|
58
93
|
const functionResponse = await functions.update(functionId, name, runtime, execute, events, schedule, timeout, enabled, logging, entrypoint, commands, scopes, installationId, providerRepositoryId, providerBranch, providerSilentMode, providerRootDirectory, specification);
|
59
94
|
return functionResponse;
|
60
95
|
};
|
61
|
-
export const
|
62
|
-
const
|
63
|
-
|
64
|
-
|
65
|
-
await
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
96
|
+
export const createFunctionTemplate = async (templateType, functionName, basePath = "./functions") => {
|
97
|
+
const functionPath = join(basePath, functionName);
|
98
|
+
const templatesPath = join(__dirname, "templates", templateType);
|
99
|
+
// Create function directory
|
100
|
+
await fs.promises.mkdir(functionPath, { recursive: true });
|
101
|
+
// Copy template files recursively
|
102
|
+
const copyTemplateFiles = async (sourcePath, targetPath) => {
|
103
|
+
const entries = await fs.promises.readdir(sourcePath, {
|
104
|
+
withFileTypes: true,
|
105
|
+
});
|
106
|
+
for (const entry of entries) {
|
107
|
+
const srcPath = join(sourcePath, entry.name);
|
108
|
+
const destPath = join(targetPath, entry.name);
|
109
|
+
if (entry.isDirectory()) {
|
110
|
+
await fs.promises.mkdir(destPath, { recursive: true });
|
111
|
+
await copyTemplateFiles(srcPath, destPath);
|
112
|
+
}
|
113
|
+
else {
|
114
|
+
let content = await fs.promises.readFile(srcPath, "utf-8");
|
115
|
+
// Replace template variables
|
116
|
+
content = content
|
117
|
+
.replace(/\{\{functionName\}\}/g, functionName)
|
118
|
+
.replace(/\{\{databaseId\}\}/g, "{{databaseId}}")
|
119
|
+
.replace(/\{\{collectionId\}\}/g, "{{collectionId}}");
|
120
|
+
await fs.promises.writeFile(destPath, content);
|
121
|
+
}
|
122
|
+
}
|
123
|
+
};
|
124
|
+
try {
|
125
|
+
await copyTemplateFiles(templatesPath, functionPath);
|
126
|
+
console.log(chalk.green(`✨ Created ${templateType} function template at ${functionPath}`));
|
127
|
+
}
|
128
|
+
catch (error) {
|
129
|
+
console.error(chalk.red(`Failed to create function template: ${error}`));
|
130
|
+
throw error;
|
131
|
+
}
|
132
|
+
return functionPath;
|
79
133
|
};
|
@@ -0,0 +1,21 @@
|
|
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;
|
@@ -0,0 +1,114 @@
|
|
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
|
+
}
|
@@ -0,0 +1,15 @@
|
|
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>;
|
@@ -0,0 +1,11 @@
|
|
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
|
+
}>;
|
@@ -0,0 +1,11 @@
|
|
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
|
+
}
|
package/dist/interactiveCLI.d.ts
CHANGED
@@ -6,6 +6,12 @@ export declare class InteractiveCLI {
|
|
6
6
|
private initControllerIfNeeded;
|
7
7
|
private selectDatabases;
|
8
8
|
private selectCollections;
|
9
|
+
private createFunction;
|
10
|
+
private findFunctionInSubdirectories;
|
11
|
+
private deployFunction;
|
12
|
+
private deleteFunction;
|
13
|
+
private selectFunctions;
|
14
|
+
private getLocalFunctions;
|
9
15
|
private selectBuckets;
|
10
16
|
private createCollectionConfig;
|
11
17
|
private configureBuckets;
|