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.
Files changed (50) hide show
  1. package/dist/cli/commands/databaseCommands.js +7 -8
  2. package/dist/config/services/ConfigLoaderService.d.ts +7 -0
  3. package/dist/config/services/ConfigLoaderService.js +47 -1
  4. package/dist/functions/deployments.js +5 -23
  5. package/dist/functions/methods.js +4 -2
  6. package/dist/functions/pathResolution.d.ts +37 -0
  7. package/dist/functions/pathResolution.js +185 -0
  8. package/dist/functions/templates/count-docs-in-collection/README.md +54 -0
  9. package/dist/functions/templates/count-docs-in-collection/package.json +25 -0
  10. package/dist/functions/templates/count-docs-in-collection/src/main.ts +159 -0
  11. package/dist/functions/templates/count-docs-in-collection/src/request.ts +9 -0
  12. package/dist/functions/templates/count-docs-in-collection/tsconfig.json +28 -0
  13. package/dist/functions/templates/hono-typescript/README.md +286 -0
  14. package/dist/functions/templates/hono-typescript/package.json +26 -0
  15. package/dist/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
  16. package/dist/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
  17. package/dist/functions/templates/hono-typescript/src/app.ts +180 -0
  18. package/dist/functions/templates/hono-typescript/src/context.ts +103 -0
  19. package/dist/functions/templates/hono-typescript/src/index.ts +54 -0
  20. package/dist/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
  21. package/dist/functions/templates/hono-typescript/tsconfig.json +20 -0
  22. package/dist/functions/templates/typescript-node/README.md +32 -0
  23. package/dist/functions/templates/typescript-node/package.json +25 -0
  24. package/dist/functions/templates/typescript-node/src/context.ts +103 -0
  25. package/dist/functions/templates/typescript-node/src/index.ts +29 -0
  26. package/dist/functions/templates/typescript-node/tsconfig.json +28 -0
  27. package/dist/functions/templates/uv/README.md +31 -0
  28. package/dist/functions/templates/uv/pyproject.toml +30 -0
  29. package/dist/functions/templates/uv/src/__init__.py +0 -0
  30. package/dist/functions/templates/uv/src/context.py +125 -0
  31. package/dist/functions/templates/uv/src/index.py +46 -0
  32. package/dist/main.js +8 -8
  33. package/dist/shared/selectionDialogs.d.ts +1 -1
  34. package/dist/shared/selectionDialogs.js +31 -7
  35. package/dist/utilsController.d.ts +2 -1
  36. package/dist/utilsController.js +111 -19
  37. package/package.json +4 -2
  38. package/scripts/copy-templates.ts +23 -0
  39. package/src/cli/commands/databaseCommands.ts +7 -8
  40. package/src/config/services/ConfigLoaderService.ts +62 -1
  41. package/src/functions/deployments.ts +10 -35
  42. package/src/functions/methods.ts +4 -2
  43. package/src/functions/pathResolution.ts +227 -0
  44. package/src/main.ts +8 -8
  45. package/src/shared/selectionDialogs.ts +36 -7
  46. package/src/utilsController.ts +138 -22
  47. package/dist/utils/schemaStrings.d.ts +0 -14
  48. package/dist/utils/schemaStrings.js +0 -428
  49. package/dist/utils/sessionPreservationExample.d.ts +0 -1666
  50. 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
- // Prompt about existing configuration
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: !syncExisting });
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("Sync operation cancelled by user", { prefix: "Database" });
88
+ MessageFormatter.info("Push operation cancelled by user", { prefix: "Database" });
90
89
  return;
91
90
  }
92
- // Perform selective sync using the controller
93
- MessageFormatter.progress("Starting selective sync...", { prefix: "Database" });
94
- await cli.controller.selectiveSync(databaseSelections, bucketSelections);
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: partialConfig.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
- const findFunctionDirectory = (basePath, functionName) => {
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 resolvedPath = functionPath ||
127
- functionConfig.dirPath ||
128
- findFunctionDirectory(process.cwd(), functionName) ||
129
- join(process.cwd(), "functions", functionName.toLowerCase().replace(/\s+/g, "-"));
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.toLowerCase().replace(/\s+/g, "-"));
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 functionPath = join(basePath, functionName);
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
+ }