appwrite-utils-cli 1.7.8 → 1.7.9
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/cli/commands/databaseCommands.js +7 -8
- package/dist/config/services/ConfigLoaderService.d.ts +7 -0
- package/dist/config/services/ConfigLoaderService.js +47 -1
- package/dist/functions/deployments.js +5 -23
- package/dist/functions/methods.js +4 -2
- package/dist/functions/pathResolution.d.ts +37 -0
- package/dist/functions/pathResolution.js +185 -0
- package/dist/functions/templates/count-docs-in-collection/README.md +54 -0
- package/dist/functions/templates/count-docs-in-collection/package.json +25 -0
- package/dist/functions/templates/count-docs-in-collection/src/main.ts +159 -0
- package/dist/functions/templates/count-docs-in-collection/src/request.ts +9 -0
- package/dist/functions/templates/count-docs-in-collection/tsconfig.json +28 -0
- package/dist/functions/templates/hono-typescript/README.md +286 -0
- package/dist/functions/templates/hono-typescript/package.json +26 -0
- package/dist/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
- package/dist/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
- package/dist/functions/templates/hono-typescript/src/app.ts +180 -0
- package/dist/functions/templates/hono-typescript/src/context.ts +103 -0
- package/dist/functions/templates/hono-typescript/src/index.ts +54 -0
- package/dist/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
- package/dist/functions/templates/hono-typescript/tsconfig.json +20 -0
- package/dist/functions/templates/typescript-node/README.md +32 -0
- package/dist/functions/templates/typescript-node/package.json +25 -0
- package/dist/functions/templates/typescript-node/src/context.ts +103 -0
- package/dist/functions/templates/typescript-node/src/index.ts +29 -0
- package/dist/functions/templates/typescript-node/tsconfig.json +28 -0
- package/dist/functions/templates/uv/README.md +31 -0
- package/dist/functions/templates/uv/pyproject.toml +30 -0
- package/dist/functions/templates/uv/src/__init__.py +0 -0
- package/dist/functions/templates/uv/src/context.py +125 -0
- package/dist/functions/templates/uv/src/index.py +46 -0
- package/dist/main.js +8 -8
- package/dist/shared/selectionDialogs.d.ts +1 -1
- package/dist/shared/selectionDialogs.js +31 -7
- package/dist/utilsController.d.ts +2 -1
- package/dist/utilsController.js +111 -19
- package/package.json +4 -2
- package/scripts/copy-templates.ts +23 -0
- package/src/cli/commands/databaseCommands.ts +7 -8
- package/src/config/services/ConfigLoaderService.ts +62 -1
- package/src/functions/deployments.ts +10 -35
- package/src/functions/methods.ts +4 -2
- package/src/functions/pathResolution.ts +227 -0
- package/src/main.ts +8 -8
- package/src/shared/selectionDialogs.ts +36 -7
- package/src/utilsController.ts +138 -22
- package/dist/utils/schemaStrings.d.ts +0 -14
- package/dist/utils/schemaStrings.js +0 -428
- package/dist/utils/sessionPreservationExample.d.ts +0 -1666
- package/dist/utils/sessionPreservationExample.js +0 -101
|
@@ -19,10 +19,9 @@ export const databaseCommands = {
|
|
|
19
19
|
const configuredDatabases = cli.controller.config?.databases || [];
|
|
20
20
|
// Get local collections for selection
|
|
21
21
|
const localCollections = cli.getLocalCollections();
|
|
22
|
-
//
|
|
23
|
-
const { syncExisting, modifyConfiguration } = await SelectionDialogs.promptForExistingConfig(configuredDatabases);
|
|
22
|
+
// Push operations always use local configuration as source of truth
|
|
24
23
|
// Select databases
|
|
25
|
-
const selectedDatabaseIds = await SelectionDialogs.selectDatabases(availableDatabases, configuredDatabases, { showSelectAll: true, allowNewOnly:
|
|
24
|
+
const selectedDatabaseIds = await SelectionDialogs.selectDatabases(availableDatabases, configuredDatabases, { showSelectAll: true, allowNewOnly: false });
|
|
26
25
|
if (selectedDatabaseIds.length === 0) {
|
|
27
26
|
MessageFormatter.warning("No databases selected. Skipping database sync.", { prefix: "Database" });
|
|
28
27
|
return;
|
|
@@ -84,14 +83,14 @@ export const databaseCommands = {
|
|
|
84
83
|
const databaseSelections = SelectionDialogs.createDatabaseSelection(selectedDatabaseIds, availableDatabases, tableSelectionsMap, configuredDatabases, availableTablesMap);
|
|
85
84
|
// Show confirmation summary
|
|
86
85
|
const selectionSummary = SelectionDialogs.createSyncSelectionSummary(databaseSelections, bucketSelections);
|
|
87
|
-
const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary);
|
|
86
|
+
const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary, 'push');
|
|
88
87
|
if (!confirmed) {
|
|
89
|
-
MessageFormatter.info("
|
|
88
|
+
MessageFormatter.info("Push operation cancelled by user", { prefix: "Database" });
|
|
90
89
|
return;
|
|
91
90
|
}
|
|
92
|
-
// Perform selective
|
|
93
|
-
MessageFormatter.progress("Starting selective
|
|
94
|
-
await cli.controller.
|
|
91
|
+
// Perform selective push using the controller
|
|
92
|
+
MessageFormatter.progress("Starting selective push...", { prefix: "Database" });
|
|
93
|
+
await cli.controller.selectivePush(databaseSelections, bucketSelections);
|
|
95
94
|
MessageFormatter.success("\n✅ All database configurations pushed successfully!", { prefix: "Database" });
|
|
96
95
|
// Then handle functions if requested
|
|
97
96
|
const { syncFunctions } = await inquirer.prompt([
|
|
@@ -30,6 +30,13 @@ export interface CollectionLoadOptions {
|
|
|
30
30
|
* - Validates and normalizes configuration data
|
|
31
31
|
*/
|
|
32
32
|
export declare class ConfigLoaderService {
|
|
33
|
+
/**
|
|
34
|
+
* Normalizes function dirPath to absolute path
|
|
35
|
+
* @param func Function configuration object
|
|
36
|
+
* @param configDir Directory containing the config file
|
|
37
|
+
* @returns Function with normalized dirPath
|
|
38
|
+
*/
|
|
39
|
+
private normalizeFunctionPath;
|
|
33
40
|
/**
|
|
34
41
|
* Loads configuration from a discovered path, auto-detecting the type
|
|
35
42
|
* @param configPath Path to the configuration file
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { resolve as resolvePath, dirname, isAbsolute } from "node:path";
|
|
3
4
|
import yaml from "js-yaml";
|
|
4
5
|
import { register } from "tsx/esm/api";
|
|
5
6
|
import { pathToFileURL } from "node:url";
|
|
@@ -8,6 +9,7 @@ import { normalizeYamlData } from "../../utils/yamlConverter.js";
|
|
|
8
9
|
import { loadYamlConfig } from "../yamlConfig.js";
|
|
9
10
|
import { loadAppwriteProjectConfig, projectConfigToAppwriteConfig, getCollectionsFromProject } from "../../utils/projectConfig.js";
|
|
10
11
|
import { loadYamlCollection, loadYamlTable, } from "../../utils/configDiscovery.js";
|
|
12
|
+
import { expandTildePath } from "../../functions/pathResolution.js";
|
|
11
13
|
/**
|
|
12
14
|
* Service for loading and parsing Appwrite configuration files.
|
|
13
15
|
*
|
|
@@ -25,6 +27,31 @@ import { loadYamlCollection, loadYamlTable, } from "../../utils/configDiscovery.
|
|
|
25
27
|
* - Validates and normalizes configuration data
|
|
26
28
|
*/
|
|
27
29
|
export class ConfigLoaderService {
|
|
30
|
+
/**
|
|
31
|
+
* Normalizes function dirPath to absolute path
|
|
32
|
+
* @param func Function configuration object
|
|
33
|
+
* @param configDir Directory containing the config file
|
|
34
|
+
* @returns Function with normalized dirPath
|
|
35
|
+
*/
|
|
36
|
+
normalizeFunctionPath(func, configDir) {
|
|
37
|
+
if (!func.dirPath) {
|
|
38
|
+
return func;
|
|
39
|
+
}
|
|
40
|
+
// Expand tilde first
|
|
41
|
+
const expandedPath = expandTildePath(func.dirPath);
|
|
42
|
+
// If already absolute, return as-is
|
|
43
|
+
if (isAbsolute(expandedPath)) {
|
|
44
|
+
return {
|
|
45
|
+
...func,
|
|
46
|
+
dirPath: expandedPath
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// Resolve relative to config directory
|
|
50
|
+
return {
|
|
51
|
+
...func,
|
|
52
|
+
dirPath: resolvePath(configDir, expandedPath)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
28
55
|
/**
|
|
29
56
|
* Loads configuration from a discovered path, auto-detecting the type
|
|
30
57
|
* @param configPath Path to the configuration file
|
|
@@ -46,6 +73,11 @@ export class ConfigLoaderService {
|
|
|
46
73
|
if (!partialConfig.appwriteEndpoint || !partialConfig.appwriteProject) {
|
|
47
74
|
throw new Error("JSON project config must contain at minimum 'endpoint' and 'projectId' fields");
|
|
48
75
|
}
|
|
76
|
+
const configDir = path.dirname(configPath);
|
|
77
|
+
// Normalize function paths
|
|
78
|
+
const normalizedFunctions = partialConfig.functions
|
|
79
|
+
? partialConfig.functions.map(func => this.normalizeFunctionPath(func, configDir))
|
|
80
|
+
: [];
|
|
49
81
|
return {
|
|
50
82
|
appwriteEndpoint: partialConfig.appwriteEndpoint,
|
|
51
83
|
appwriteProject: partialConfig.appwriteProject,
|
|
@@ -73,7 +105,7 @@ export class ConfigLoaderService {
|
|
|
73
105
|
},
|
|
74
106
|
databases: partialConfig.databases || [],
|
|
75
107
|
buckets: partialConfig.buckets || [],
|
|
76
|
-
functions:
|
|
108
|
+
functions: normalizedFunctions,
|
|
77
109
|
collections: partialConfig.collections || [],
|
|
78
110
|
sessionCookie: partialConfig.sessionCookie,
|
|
79
111
|
authMethod: partialConfig.authMethod || "auto",
|
|
@@ -114,6 +146,10 @@ export class ConfigLoaderService {
|
|
|
114
146
|
}
|
|
115
147
|
// Load collections and tables from their respective directories
|
|
116
148
|
const configDir = path.dirname(yamlPath);
|
|
149
|
+
// Normalize function paths
|
|
150
|
+
if (config.functions) {
|
|
151
|
+
config.functions = config.functions.map(func => this.normalizeFunctionPath(func, configDir));
|
|
152
|
+
}
|
|
117
153
|
const collectionsDir = path.join(configDir, config.schemaConfig?.collectionsDirectory || "collections");
|
|
118
154
|
const tablesDir = path.join(configDir, config.schemaConfig?.tablesDirectory || "tables");
|
|
119
155
|
// Detect API mode to determine priority order
|
|
@@ -175,6 +211,11 @@ export class ConfigLoaderService {
|
|
|
175
211
|
if (!config) {
|
|
176
212
|
throw new Error(`Failed to load TypeScript config from: ${tsPath}`);
|
|
177
213
|
}
|
|
214
|
+
// Normalize function paths
|
|
215
|
+
const configDir = path.dirname(tsPath);
|
|
216
|
+
if (config.functions) {
|
|
217
|
+
config.functions = config.functions.map(func => this.normalizeFunctionPath(func, configDir));
|
|
218
|
+
}
|
|
178
219
|
MessageFormatter.success(`Loaded TypeScript config from: ${tsPath}`, {
|
|
179
220
|
prefix: "Config",
|
|
180
221
|
});
|
|
@@ -210,6 +251,11 @@ export class ConfigLoaderService {
|
|
|
210
251
|
if (collections.length > 0) {
|
|
211
252
|
appwriteConfig.collections = collections;
|
|
212
253
|
}
|
|
254
|
+
// Normalize function paths
|
|
255
|
+
const configDir = path.dirname(jsonPath);
|
|
256
|
+
if (appwriteConfig.functions) {
|
|
257
|
+
appwriteConfig.functions = appwriteConfig.functions.map(func => this.normalizeFunctionPath(func, configDir));
|
|
258
|
+
}
|
|
213
259
|
MessageFormatter.success(`Loaded project config from: ${jsonPath}`, {
|
|
214
260
|
prefix: "Config",
|
|
215
261
|
});
|
|
@@ -11,23 +11,7 @@ import { execSync } from "child_process";
|
|
|
11
11
|
import { createFunction, getFunction, updateFunction, updateFunctionSpecifications, } from "./methods.js";
|
|
12
12
|
import ignore from "ignore";
|
|
13
13
|
import { MessageFormatter } from "../shared/messageFormatter.js";
|
|
14
|
-
|
|
15
|
-
const normalizedName = functionName.toLowerCase().replace(/\s+/g, "-");
|
|
16
|
-
const dirs = fs.readdirSync(basePath, { withFileTypes: true });
|
|
17
|
-
for (const dir of dirs) {
|
|
18
|
-
if (dir.isDirectory()) {
|
|
19
|
-
const fullPath = join(basePath, dir.name);
|
|
20
|
-
if (dir.name.toLowerCase() === normalizedName) {
|
|
21
|
-
return fullPath;
|
|
22
|
-
}
|
|
23
|
-
const nestedResult = findFunctionDirectory(fullPath, functionName);
|
|
24
|
-
if (nestedResult) {
|
|
25
|
-
return nestedResult;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return undefined;
|
|
30
|
-
};
|
|
14
|
+
import { resolveFunctionDirectory, validateFunctionDirectory } from './pathResolution.js';
|
|
31
15
|
export const deployFunction = async (client, functionId, codePath, activate = true, entrypoint = "index.js", commands = "npm install", ignored = [
|
|
32
16
|
"node_modules",
|
|
33
17
|
".git",
|
|
@@ -123,12 +107,10 @@ export const deployLocalFunction = async (client, functionName, functionConfig,
|
|
|
123
107
|
catch (error) {
|
|
124
108
|
functionExists = false;
|
|
125
109
|
}
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (!fs.existsSync(resolvedPath)) {
|
|
131
|
-
throw new Error(`Function directory not found at ${resolvedPath}`);
|
|
110
|
+
const configDirPath = process.cwd(); // TODO: This should be passed from caller
|
|
111
|
+
const resolvedPath = resolveFunctionDirectory(functionName, configDirPath, functionConfig.dirPath, functionPath);
|
|
112
|
+
if (!validateFunctionDirectory(resolvedPath)) {
|
|
113
|
+
throw new Error(`Function directory is invalid or missing required files: ${resolvedPath}`);
|
|
132
114
|
}
|
|
133
115
|
if (functionConfig.predeployCommands?.length) {
|
|
134
116
|
MessageFormatter.processing("Executing predeploy commands...", { prefix: "Deployment" });
|
|
@@ -6,6 +6,7 @@ import {} from "appwrite-utils";
|
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
import { extract as extractTar } from "tar";
|
|
8
8
|
import { MessageFormatter } from "../shared/messageFormatter.js";
|
|
9
|
+
import { expandTildePath, normalizeFunctionName } from "./pathResolution.js";
|
|
9
10
|
/**
|
|
10
11
|
* Validates and filters events array for Appwrite functions
|
|
11
12
|
* - Filters out empty/invalid strings
|
|
@@ -41,7 +42,7 @@ export const downloadLatestFunctionDeployment = async (client, functionId, baseP
|
|
|
41
42
|
const latestDeployment = functionDeployments.deployments[0];
|
|
42
43
|
const deploymentData = await functions.getDeploymentDownload(functionId, latestDeployment.$id);
|
|
43
44
|
// Create function directory using provided basePath
|
|
44
|
-
const functionDir = join(basePath, functionInfo.name
|
|
45
|
+
const functionDir = join(basePath, normalizeFunctionName(functionInfo.name));
|
|
45
46
|
await fs.promises.mkdir(functionDir, { recursive: true });
|
|
46
47
|
// Create temporary file for tar extraction
|
|
47
48
|
const tarPath = join(functionDir, "temp.tar.gz");
|
|
@@ -119,7 +120,8 @@ export const updateFunction = async (client, functionConfig) => {
|
|
|
119
120
|
return functionResponse;
|
|
120
121
|
};
|
|
121
122
|
export const createFunctionTemplate = async (templateType, functionName, basePath = "./functions") => {
|
|
122
|
-
const
|
|
123
|
+
const expandedBasePath = expandTildePath(basePath);
|
|
124
|
+
const functionPath = join(expandedBasePath, functionName);
|
|
123
125
|
const currentFileUrl = import.meta.url;
|
|
124
126
|
const currentDir = dirname(fileURLToPath(currentFileUrl));
|
|
125
127
|
const templatesPath = join(currentDir, "templates", templateType);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expands tilde (~) in paths to the user's home directory
|
|
3
|
+
* @param pathStr - Path string that may contain ~
|
|
4
|
+
* @returns Expanded path with home directory
|
|
5
|
+
*/
|
|
6
|
+
export declare function expandTildePath(pathStr: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Normalizes function name to standard format (lowercase, dashes instead of spaces)
|
|
9
|
+
* @param name - Function name to normalize
|
|
10
|
+
* @returns Normalized function name
|
|
11
|
+
*/
|
|
12
|
+
export declare function normalizeFunctionName(name: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Validates that a directory exists and contains function markers
|
|
15
|
+
* @param dirPath - Directory path to validate
|
|
16
|
+
* @returns True if directory is a valid function directory
|
|
17
|
+
*/
|
|
18
|
+
export declare function validateFunctionDirectory(dirPath: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Helper function to search for function in standard locations
|
|
21
|
+
* @param configDirPath - Directory where config file is located
|
|
22
|
+
* @param normalizedName - Normalized function name
|
|
23
|
+
* @returns First valid function directory path or undefined
|
|
24
|
+
*/
|
|
25
|
+
export declare function findFunctionInStandardLocations(configDirPath: string, normalizedName: string): string | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Resolves the absolute path to a function directory
|
|
28
|
+
* Handles multiple resolution strategies with proper priority
|
|
29
|
+
*
|
|
30
|
+
* @param functionName - Name of the function
|
|
31
|
+
* @param configDirPath - Directory where config file is located
|
|
32
|
+
* @param dirPath - Optional explicit dirPath from config
|
|
33
|
+
* @param explicitPath - Optional path passed as parameter (highest priority)
|
|
34
|
+
* @returns Absolute path to the function directory
|
|
35
|
+
* @throws Error if function directory cannot be found or is invalid
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveFunctionDirectory(functionName: string, configDirPath: string, dirPath?: string, explicitPath?: string): string;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { existsSync, statSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join, resolve, isAbsolute } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { MessageFormatter } from '../shared/messageFormatter.js';
|
|
5
|
+
import { logger } from '../shared/logging.js';
|
|
6
|
+
/**
|
|
7
|
+
* Expands tilde (~) in paths to the user's home directory
|
|
8
|
+
* @param pathStr - Path string that may contain ~
|
|
9
|
+
* @returns Expanded path with home directory
|
|
10
|
+
*/
|
|
11
|
+
export function expandTildePath(pathStr) {
|
|
12
|
+
if (!pathStr)
|
|
13
|
+
return pathStr;
|
|
14
|
+
if (pathStr.startsWith('~/') || pathStr === '~') {
|
|
15
|
+
const expandedPath = pathStr.replace(/^~(?=$|\/|\\)/, homedir());
|
|
16
|
+
logger.debug('Expanded tilde path', { original: pathStr, expanded: expandedPath });
|
|
17
|
+
return expandedPath;
|
|
18
|
+
}
|
|
19
|
+
return pathStr;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Normalizes function name to standard format (lowercase, dashes instead of spaces)
|
|
23
|
+
* @param name - Function name to normalize
|
|
24
|
+
* @returns Normalized function name
|
|
25
|
+
*/
|
|
26
|
+
export function normalizeFunctionName(name) {
|
|
27
|
+
if (!name)
|
|
28
|
+
return name;
|
|
29
|
+
const normalized = name.toLowerCase().replace(/\s+/g, '-');
|
|
30
|
+
if (normalized !== name) {
|
|
31
|
+
logger.debug('Normalized function name', { original: name, normalized });
|
|
32
|
+
}
|
|
33
|
+
return normalized;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Validates that a directory exists and contains function markers
|
|
37
|
+
* @param dirPath - Directory path to validate
|
|
38
|
+
* @returns True if directory is a valid function directory
|
|
39
|
+
*/
|
|
40
|
+
export function validateFunctionDirectory(dirPath) {
|
|
41
|
+
try {
|
|
42
|
+
// Check if directory exists
|
|
43
|
+
if (!existsSync(dirPath)) {
|
|
44
|
+
logger.debug('Directory does not exist', { dirPath });
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
// Check if it's actually a directory
|
|
48
|
+
const stats = statSync(dirPath);
|
|
49
|
+
if (!stats.isDirectory()) {
|
|
50
|
+
logger.debug('Path is not a directory', { dirPath });
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
// Check for function markers
|
|
54
|
+
const contents = readdirSync(dirPath);
|
|
55
|
+
const hasPackageJson = contents.includes('package.json');
|
|
56
|
+
const hasPyprojectToml = contents.includes('pyproject.toml');
|
|
57
|
+
const hasSrcDir = contents.includes('src');
|
|
58
|
+
const isValid = hasPackageJson || hasPyprojectToml || hasSrcDir;
|
|
59
|
+
logger.debug('Function directory validation', {
|
|
60
|
+
dirPath,
|
|
61
|
+
isValid,
|
|
62
|
+
markers: {
|
|
63
|
+
hasPackageJson,
|
|
64
|
+
hasPyprojectToml,
|
|
65
|
+
hasSrcDir
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return isValid;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
logger.debug('Error validating function directory', {
|
|
72
|
+
dirPath,
|
|
73
|
+
error: error instanceof Error ? error.message : String(error)
|
|
74
|
+
});
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Helper function to search for function in standard locations
|
|
80
|
+
* @param configDirPath - Directory where config file is located
|
|
81
|
+
* @param normalizedName - Normalized function name
|
|
82
|
+
* @returns First valid function directory path or undefined
|
|
83
|
+
*/
|
|
84
|
+
export function findFunctionInStandardLocations(configDirPath, normalizedName) {
|
|
85
|
+
const searchPaths = [
|
|
86
|
+
// Same directory as config
|
|
87
|
+
join(configDirPath, 'functions', normalizedName),
|
|
88
|
+
// Parent directory of config
|
|
89
|
+
join(configDirPath, '..', 'functions', normalizedName),
|
|
90
|
+
// Current working directory
|
|
91
|
+
join(process.cwd(), 'functions', normalizedName),
|
|
92
|
+
];
|
|
93
|
+
logger.debug('Searching for function in standard locations', {
|
|
94
|
+
normalizedName,
|
|
95
|
+
configDirPath,
|
|
96
|
+
searchPaths
|
|
97
|
+
});
|
|
98
|
+
for (const searchPath of searchPaths) {
|
|
99
|
+
const resolvedPath = resolve(searchPath);
|
|
100
|
+
logger.debug('Checking search path', { searchPath, resolvedPath });
|
|
101
|
+
if (validateFunctionDirectory(resolvedPath)) {
|
|
102
|
+
logger.debug('Found function in standard location', { resolvedPath });
|
|
103
|
+
return resolvedPath;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
logger.debug('Function not found in any standard location', { normalizedName });
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Resolves the absolute path to a function directory
|
|
111
|
+
* Handles multiple resolution strategies with proper priority
|
|
112
|
+
*
|
|
113
|
+
* @param functionName - Name of the function
|
|
114
|
+
* @param configDirPath - Directory where config file is located
|
|
115
|
+
* @param dirPath - Optional explicit dirPath from config
|
|
116
|
+
* @param explicitPath - Optional path passed as parameter (highest priority)
|
|
117
|
+
* @returns Absolute path to the function directory
|
|
118
|
+
* @throws Error if function directory cannot be found or is invalid
|
|
119
|
+
*/
|
|
120
|
+
export function resolveFunctionDirectory(functionName, configDirPath, dirPath, explicitPath) {
|
|
121
|
+
logger.debug('Resolving function directory', {
|
|
122
|
+
functionName,
|
|
123
|
+
configDirPath,
|
|
124
|
+
dirPath,
|
|
125
|
+
explicitPath
|
|
126
|
+
});
|
|
127
|
+
const normalizedName = normalizeFunctionName(functionName);
|
|
128
|
+
// Priority 1: Explicit path parameter (highest priority)
|
|
129
|
+
if (explicitPath) {
|
|
130
|
+
logger.debug('Using explicit path parameter');
|
|
131
|
+
const expandedPath = expandTildePath(explicitPath);
|
|
132
|
+
const resolvedPath = isAbsolute(expandedPath)
|
|
133
|
+
? expandedPath
|
|
134
|
+
: resolve(process.cwd(), expandedPath);
|
|
135
|
+
if (!validateFunctionDirectory(resolvedPath)) {
|
|
136
|
+
const errorMsg = `Explicit path is not a valid function directory: ${resolvedPath}`;
|
|
137
|
+
logger.error(errorMsg);
|
|
138
|
+
MessageFormatter.error('Invalid function directory', errorMsg, { prefix: 'Path Resolution' });
|
|
139
|
+
throw new Error(errorMsg);
|
|
140
|
+
}
|
|
141
|
+
logger.debug('Resolved using explicit path', { resolvedPath });
|
|
142
|
+
MessageFormatter.debug(`Resolved function directory using explicit path: ${resolvedPath}`, undefined, { prefix: 'Path Resolution' });
|
|
143
|
+
return resolvedPath;
|
|
144
|
+
}
|
|
145
|
+
// Priority 2: dirPath from config (relative to config location)
|
|
146
|
+
if (dirPath) {
|
|
147
|
+
logger.debug('Using dirPath from config');
|
|
148
|
+
const expandedPath = expandTildePath(dirPath);
|
|
149
|
+
const resolvedPath = isAbsolute(expandedPath)
|
|
150
|
+
? expandedPath
|
|
151
|
+
: resolve(configDirPath, expandedPath);
|
|
152
|
+
if (!validateFunctionDirectory(resolvedPath)) {
|
|
153
|
+
const errorMsg = `Config dirPath is not a valid function directory: ${resolvedPath}`;
|
|
154
|
+
logger.error(errorMsg);
|
|
155
|
+
MessageFormatter.error('Invalid function directory', errorMsg, { prefix: 'Path Resolution' });
|
|
156
|
+
throw new Error(errorMsg);
|
|
157
|
+
}
|
|
158
|
+
logger.debug('Resolved using config dirPath', { resolvedPath });
|
|
159
|
+
MessageFormatter.debug(`Resolved function directory using config dirPath: ${resolvedPath}`, undefined, { prefix: 'Path Resolution' });
|
|
160
|
+
return resolvedPath;
|
|
161
|
+
}
|
|
162
|
+
// Priority 3: Search standard locations
|
|
163
|
+
logger.debug('Searching standard locations for function');
|
|
164
|
+
const foundPath = findFunctionInStandardLocations(configDirPath, normalizedName);
|
|
165
|
+
if (foundPath) {
|
|
166
|
+
logger.debug('Resolved using standard location search', { foundPath });
|
|
167
|
+
MessageFormatter.debug(`Found function directory in standard location: ${foundPath}`, undefined, { prefix: 'Path Resolution' });
|
|
168
|
+
return foundPath;
|
|
169
|
+
}
|
|
170
|
+
// Priority 4: Not found - throw error
|
|
171
|
+
const searchedLocations = [
|
|
172
|
+
join(configDirPath, 'functions', normalizedName),
|
|
173
|
+
join(configDirPath, '..', 'functions', normalizedName),
|
|
174
|
+
join(process.cwd(), 'functions', normalizedName),
|
|
175
|
+
];
|
|
176
|
+
const errorMsg = `Function directory not found for '${functionName}' (normalized: '${normalizedName}'). ` +
|
|
177
|
+
`Searched locations:\n${searchedLocations.map(p => ` - ${p}`).join('\n')}`;
|
|
178
|
+
logger.error('Function directory not found', {
|
|
179
|
+
functionName,
|
|
180
|
+
normalizedName,
|
|
181
|
+
searchedLocations
|
|
182
|
+
});
|
|
183
|
+
MessageFormatter.error('Function directory not found', errorMsg, { prefix: 'Path Resolution' });
|
|
184
|
+
throw new Error(errorMsg);
|
|
185
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Count Documents in Collection Function
|
|
2
|
+
|
|
3
|
+
A utility function that accurately counts documents in an Appwrite collection, even when there are more than 5,000 documents.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- Handles collections with any number of documents
|
|
7
|
+
- Supports filtering using Appwrite queries
|
|
8
|
+
- Uses efficient binary search algorithm for large collections
|
|
9
|
+
- Provides detailed logging during the counting process
|
|
10
|
+
|
|
11
|
+
## Structure
|
|
12
|
+
- `src/main.ts`: Main function implementation with counting logic
|
|
13
|
+
- `src/request.ts`: Request validation schema using Zod
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
Send a POST request with:
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"databaseId": "your-database-id",
|
|
20
|
+
"collectionId": "your-collection-id",
|
|
21
|
+
"queries": [Query.orderDesc("$createdAt"), Query.contains("name", "John")] // Or put the string array from after this, they are the same
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Response
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"success": true,
|
|
29
|
+
"count": 12345
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Development
|
|
34
|
+
1. Install dependencies: `npm|yarn|bun install`
|
|
35
|
+
2. Build: `npm|yarn|bun run build`
|
|
36
|
+
3. Deploy: Function will be built automatically during deployment
|
|
37
|
+
|
|
38
|
+
## Deployment
|
|
39
|
+
Make sure it's inside `appwriteConfig.ts` functions array, and if you want to build it FIRST, before Appwrite (using your system), you can
|
|
40
|
+
add the `predeployCommands` to the function in `appwriteConfig.ts`.
|
|
41
|
+
|
|
42
|
+
## Example Config
|
|
43
|
+
```typescript
|
|
44
|
+
{
|
|
45
|
+
$id: 'count-docs',
|
|
46
|
+
name: 'Count Documents',
|
|
47
|
+
runtime: 'node-18.0',
|
|
48
|
+
path: 'functions/count-docs',
|
|
49
|
+
entrypoint: './main.js',
|
|
50
|
+
execute: ['any'],
|
|
51
|
+
predeployCommands: ['npm install', 'npm run build'],
|
|
52
|
+
deployDir: './dist'
|
|
53
|
+
}
|
|
54
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{functionName}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Appwrite function to count documents in a collection",
|
|
5
|
+
"main": "src/main.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/main.js",
|
|
10
|
+
"dev": "tsx src/main.ts"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"node-appwrite": "^13.0.0",
|
|
14
|
+
"appwrite-utils": "latest",
|
|
15
|
+
"zod": "^3.23.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^20.0.0",
|
|
19
|
+
"typescript": "^5.0.0",
|
|
20
|
+
"tsx": "^4.0.0"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18.0.0"
|
|
24
|
+
}
|
|
25
|
+
}
|