UNPKG

appwrite-utils-cli

Version:

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.

1,136 lines (1,129 loc) 53.2 kB
import { mkdirSync, writeFileSync, existsSync } from "node:fs"; import path from "node:path"; import { findAppwriteConfig } from "./loadConfigs.js"; import { loadYamlConfig } from "../config/yamlConfig.js"; import { fetchServerVersion, isVersionAtLeast } from "./versionDetection.js"; import { findYamlConfig } from "../config/yamlConfig.js"; import { ID } from "node-appwrite"; import { ulid } from "ulidx"; import { generateYamlConfigTemplate } from "../config/yamlConfig.js"; import { loadAppwriteProjectConfig, findAppwriteProjectConfig, getProjectDirectoryName, isTablesDBProject } from "./projectConfig.js"; import { hasSessionAuth, getSessionAuth } from "./sessionAuth.js"; import { MessageFormatter } from "../shared/messageFormatter.js"; // Example base configuration using types from appwrite-utils const baseConfig = { appwriteEndpoint: "https://cloud.appwrite.io/v1", appwriteProject: "YOUR_PROJECT_ID", appwriteKey: "YOUR_API_KEY", appwriteClient: null, apiMode: "auto", // Enable dual API support - auto-detect TablesDB vs legacy authMethod: "auto", // Default to auto-detect authentication method logging: { enabled: false, level: "info", console: false, }, enableBackups: true, backupInterval: 3600, backupRetention: 30, enableBackupCleanup: true, enableMockData: false, documentBucketId: "documents", usersCollectionName: "Members", databases: [ { $id: "main", name: "Main", bucket: { $id: "main_bucket", name: "Main Bucket", enabled: true, maximumFileSize: 30000000, allowedFileExtensions: [], encryption: true, antivirus: true, }, }, { $id: "staging", name: "Staging", bucket: { $id: "staging_bucket", name: "Staging Bucket", enabled: true, maximumFileSize: 30000000, allowedFileExtensions: [], encryption: true, antivirus: true, }, }, { $id: "dev", name: "Development", bucket: { $id: "dev_bucket", name: "Development Bucket", enabled: true, maximumFileSize: 30000000, allowedFileExtensions: [], encryption: true, antivirus: true, }, }, ], buckets: [ { $id: "global_bucket", name: "Global Bucket", enabled: true, maximumFileSize: 30000000, allowedFileExtensions: [], encryption: true, antivirus: true, }, ], }; const collectionsConfig = [ { name: "ExampleCollection", content: `import type { CollectionCreate } from "appwrite-utils"; const ExampleCollection: Partial<CollectionCreate> = { name: 'ExampleCollection', $id: '${ulid()}', documentSecurity: false, enabled: true, $permissions: [ { permission: 'read', target: 'any' }, { permission: 'create', target: 'users' }, { permission: 'update', target: 'users' }, { permission: 'delete', target: 'users' } ], attributes: [ { key: 'alterEgoName', type: 'string', size: 255, required: false }, // Add more attributes here ], indexes: [ { key: 'alterEgoName_search', type: 'fulltext', attributes: ['alterEgoName'] } ], importDefs: [ // Define import definitions here ] }; export default ExampleCollection;`, }, // Add more collections here ]; // Define our YAML files // Define our YAML files const configFileExample = `d`; export const customDefinitionsFile = `import type { ConverterFunctions, ValidationRules, AfterImportActions } from "appwrite-utils"; export const customConverterFunctions: ConverterFunctions = { // Add your custom converter functions here } export const customValidationRules: ValidationRules = { // Add your custom validation rules here } export const customAfterImportActions: AfterImportActions = { // Add your custom after import actions here }`; export const createEmptyCollection = (collectionName) => { const currentDir = process.cwd(); // Check for YAML config first (preferred) const yamlConfigPath = findYamlConfig(currentDir); const tsConfigPath = findAppwriteConfig(currentDir); let configDir; let isYamlProject = false; if (yamlConfigPath) { configDir = path.dirname(yamlConfigPath); isYamlProject = true; } else if (tsConfigPath) { configDir = path.dirname(tsConfigPath); isYamlProject = false; } else { // No config found - assume .appwrite directory and use YAML as default configDir = path.join(currentDir, ".appwrite"); isYamlProject = true; // Create .appwrite directory if it doesn't exist if (!existsSync(configDir)) { mkdirSync(configDir, { recursive: true }); } } const collectionsFolder = path.join(configDir, "collections"); if (!existsSync(collectionsFolder)) { mkdirSync(collectionsFolder, { recursive: true }); } if (isYamlProject) { // Create YAML collection const yamlCollection = `# yaml-language-server: $schema=../.yaml_schemas/collection.schema.json # Collection Definition: ${collectionName} name: ${collectionName} id: ${ulid()} documentSecurity: false enabled: true permissions: - permission: read target: any - permission: create target: users - permission: update target: users - permission: delete target: users attributes: # Add your attributes here # Example: # - key: title # type: string # size: 255 # required: true # description: "The title of the item" indexes: # Add your indexes here # Example: # - key: title_search # type: fulltext # attributes: # - title importDefs: [] `; const collectionFilePath = path.join(collectionsFolder, `${collectionName}.yaml`); writeFileSync(collectionFilePath, yamlCollection); MessageFormatter.success(`Created YAML collection: ${collectionFilePath}`, { prefix: "Setup" }); } else { // Create TypeScript collection const emptyCollection = `import type { CollectionCreate } from "appwrite-utils"; const ${collectionName}: Partial<CollectionCreate> = { $id: '${ulid()}', documentSecurity: false, enabled: true, name: '${collectionName}', $permissions: [ { permission: 'read', target: 'any' }, { permission: 'create', target: 'users' }, { permission: 'update', target: 'users' }, { permission: 'delete', target: 'users' } ], attributes: [ // Add more attributes here ], indexes: [ // Add more indexes here ], }; export default ${collectionName};`; const collectionFilePath = path.join(collectionsFolder, `${collectionName}.ts`); writeFileSync(collectionFilePath, emptyCollection); MessageFormatter.success(`Created TypeScript collection: ${collectionFilePath}`, { prefix: "Setup" }); } }; export const generateYamlConfig = (currentDir, useAppwriteDir = true) => { const basePath = currentDir || process.cwd(); const configDir = useAppwriteDir ? path.join(basePath, ".appwrite") : basePath; if (!existsSync(configDir)) { mkdirSync(configDir, { recursive: true }); } const configPath = path.join(configDir, "config.yaml"); generateYamlConfigTemplate(configPath); MessageFormatter.success(`Generated YAML config template at: ${configPath}`, { prefix: "Setup" }); MessageFormatter.info("Please update the configuration with your Appwrite project details.", { prefix: "Setup" }); return configPath; }; export const setupDirsFiles = async (example = false, currentDir, useYaml = true) => { const basePath = currentDir || process.cwd(); // Create .appwrite folder directly in project root const appwriteFolder = path.join(basePath, ".appwrite"); const appwriteConfigFile = path.join(appwriteFolder, "appwriteConfig.ts"); const appwriteCustomDefsFile = path.join(appwriteFolder, "customDefinitions.ts"); const appwriteSchemaFolder = path.join(appwriteFolder, "schemas"); const appwriteYamlSchemaFolder = path.join(appwriteFolder, ".yaml_schemas"); const appwriteDataFolder = path.join(appwriteFolder, "importData"); // Enhanced version detection with multiple sources let useTables = false; let detectionSource = "default"; try { // Priority 1: Check for existing appwrite.json project config const projectConfigPath = findAppwriteProjectConfig(basePath); if (projectConfigPath) { const projectConfig = loadAppwriteProjectConfig(projectConfigPath); if (projectConfig) { useTables = isTablesDBProject(projectConfig); detectionSource = "appwrite.json"; MessageFormatter.info(`Detected ${useTables ? 'TablesDB' : 'Collections'} project from ${projectConfigPath}`, { prefix: "Setup" }); } } // Priority 2: Try reading existing YAML config for version detection if (!useTables && detectionSource === "default") { const yamlPath = findYamlConfig(basePath); if (yamlPath) { const cfg = await loadYamlConfig(yamlPath); if (cfg) { // Try session auth first, then API key for version detection let endpoint = cfg.appwriteEndpoint; let projectId = cfg.appwriteProject; if (hasSessionAuth(endpoint, projectId)) { MessageFormatter.info("Using session authentication for version detection", { prefix: "Setup" }); } const ver = await fetchServerVersion(endpoint); if (isVersionAtLeast(ver || undefined, '1.8.0')) { useTables = true; detectionSource = "server-version"; MessageFormatter.info(`Detected TablesDB support (Appwrite ${ver})`, { prefix: "Setup" }); } else { MessageFormatter.info(`Using Collections API (Appwrite ${ver || 'unknown'})`, { prefix: "Setup" }); } } } } } catch (error) { MessageFormatter.warning(`Version detection failed, defaulting to Collections API: ${error instanceof Error ? error.message : String(error)}`, { prefix: "Setup" }); } const targetFolderName = useTables ? "tables" : "collections"; const collectionsFolder = path.join(appwriteFolder, targetFolderName); // Create directory structure if (!existsSync(appwriteFolder)) { mkdirSync(appwriteFolder, { recursive: true }); } if (!existsSync(collectionsFolder)) { mkdirSync(collectionsFolder, { recursive: true }); } // Handle configuration file creation - YAML is now default if (useYaml) { // Generate YAML config in .appwrite directory const configPath = path.join(appwriteFolder, "config.yaml"); generateYamlConfigTemplate(configPath); } else if (!existsSync(appwriteConfigFile)) { if (example) { writeFileSync(appwriteConfigFile, configFileExample); } else { const baseConfigContent = `import { type AppwriteConfig } from "appwrite-utils"; const appwriteConfig: AppwriteConfig = ${JSON.stringify(baseConfig, null, 2)}; export default appwriteConfig; `; writeFileSync(appwriteConfigFile, baseConfigContent); } } // Create TypeScript files for each collection only if not using YAML if (!useYaml) { collectionsConfig.forEach((collection) => { const collectionFilePath = path.join(collectionsFolder, `${collection.name}.ts`); writeFileSync(collectionFilePath, collection.content); }); } // Create YAML collection/table example if using YAML config if (useYaml) { const terminology = useTables ? { container: "table", fields: "columns", security: "rowSecurity", schemaRef: "table.schema.json", containerName: "Table", fieldName: "Column" } : { container: "collection", fields: "attributes", security: "documentSecurity", schemaRef: "collection.schema.json", containerName: "Collection", fieldName: "Attribute" }; const yamlExample = `# yaml-language-server: $schema=../.yaml_schemas/${terminology.schemaRef} # Example ${terminology.containerName} Definition name: Example${terminology.containerName} id: example_${terminology.container}_${Date.now()} ${terminology.security}: false enabled: true permissions: - permission: read target: any - permission: create target: users - permission: update target: users - permission: delete target: users ${terminology.fields}: - key: title type: string size: 255 required: true description: "The title of the item" - key: description type: string size: 1000 required: false description: "A longer description" - key: isActive type: boolean required: false default: true${useTables ? ` - key: uniqueCode type: string size: 50 required: false unique: true description: "Unique identifier code (TablesDB feature)"` : ''} indexes: - key: title_search type: fulltext attributes: - title importDefs: [] `; const yamlExamplePath = path.join(collectionsFolder, `Example${terminology.containerName}.yaml`); writeFileSync(yamlExamplePath, yamlExample); MessageFormatter.info(`Created example ${terminology.container} definition with ${terminology.fields} terminology`, { prefix: "Setup" }); // Create JSON schema for collection/table definitions const containerJsonSchema = useTables ? { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://appwrite-utils.dev/schemas/table.schema.json", "title": "Appwrite Table Definition", "description": "Schema for defining Appwrite tables in YAML (TablesDB API)", "type": "object", "properties": { "name": { "type": "string", "description": "The name of the table" }, "id": { "type": "string", "description": "The ID of the table (optional, auto-generated if not provided)", "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$" }, "rowSecurity": { "type": "boolean", "default": false, "description": "Enable row-level permissions" }, "enabled": { "type": "boolean", "default": true, "description": "Whether the table is enabled" }, "permissions": { "type": "array", "description": "Table-level permissions", "items": { "type": "object", "properties": { "permission": { "type": "string", "enum": ["read", "create", "update", "delete"], "description": "The permission type" }, "target": { "type": "string", "description": "Permission target (e.g., 'any', 'users', 'users/verified', 'label:admin')" } }, "required": ["permission", "target"], "additionalProperties": false } }, "columns": { "type": "array", "description": "Table columns (fields)", "items": { "type": "object", "properties": { "key": { "type": "string", "description": "Column name", "pattern": "^[a-zA-Z][a-zA-Z0-9]*$" }, "type": { "type": "string", "enum": ["string", "integer", "double", "boolean", "datetime", "email", "ip", "url", "enum", "relationship"], "description": "Column data type" }, "size": { "type": "number", "description": "Maximum size for string columns", "minimum": 1, "maximum": 1073741824 }, "required": { "type": "boolean", "default": false, "description": "Whether the column is required" }, "array": { "type": "boolean", "default": false, "description": "Whether the column is an array" }, "unique": { "type": "boolean", "default": false, "description": "Whether the column values must be unique (TablesDB feature)" }, "default": { "description": "Default value for the column" }, "description": { "type": "string", "description": "Column description" }, "encrypt": { "type": "boolean", "description": "Whether the column should be encrypted" }, "format": { "type": "string", "description": "Format for string columns" }, "min": { "type": "number", "description": "Minimum value for numeric columns" }, "max": { "type": "number", "description": "Maximum value for numeric columns" }, "elements": { "type": "array", "items": { "type": "string" }, "description": "Allowed values for enum columns" }, "relatedCollection": { "type": "string", "description": "Related table name for relationship columns" }, "relationType": { "type": "string", "enum": ["oneToOne", "oneToMany", "manyToOne", "manyToMany"], "description": "Type of relationship" }, "twoWay": { "type": "boolean", "description": "Whether the relationship is bidirectional" }, "twoWayKey": { "type": "string", "description": "Key name for the reverse relationship" }, "onDelete": { "type": "string", "enum": ["cascade", "restrict", "setNull"], "description": "Action to take when related row is deleted" }, "side": { "type": "string", "enum": ["parent", "child"], "description": "Side of the relationship" } }, "required": ["key", "type"], "additionalProperties": false, "allOf": [ { "if": { "properties": { "type": { "const": "enum" } } }, "then": { "required": ["elements"] } }, { "if": { "properties": { "type": { "const": "relationship" } } }, "then": { "required": ["relatedCollection", "relationType"] } } ] } }, "indexes": { "type": "array", "description": "Database indexes for the table", "items": { "type": "object", "properties": { "key": { "type": "string", "description": "Index name" }, "type": { "type": "string", "enum": ["key", "fulltext", "unique"], "description": "Index type" }, "attributes": { "type": "array", "items": { "type": "string" }, "description": "Columns to index", "minItems": 1 }, "orders": { "type": "array", "items": { "type": "string", "enum": ["ASC", "DESC"] }, "description": "Sort order for each column" } }, "required": ["key", "type", "attributes"], "additionalProperties": false } }, "importDefs": { "type": "array", "description": "Import definitions for data migration", "default": [] } }, "required": ["name"], "additionalProperties": false } : { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://appwrite-utils.dev/schemas/collection.schema.json", "title": "Appwrite Collection Definition", "description": "Schema for defining Appwrite collections in YAML", "type": "object", "properties": { "name": { "type": "string", "description": "The name of the collection" }, "id": { "type": "string", "description": "The ID of the collection (optional, auto-generated if not provided)", "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$" }, "documentSecurity": { "type": "boolean", "default": false, "description": "Enable document-level permissions" }, "enabled": { "type": "boolean", "default": true, "description": "Whether the collection is enabled" }, "permissions": { "type": "array", "description": "Collection-level permissions", "items": { "type": "object", "properties": { "permission": { "type": "string", "enum": ["read", "create", "update", "delete"], "description": "The permission type" }, "target": { "type": "string", "description": "Permission target (e.g., 'any', 'users', 'users/verified', 'label:admin')" } }, "required": ["permission", "target"], "additionalProperties": false } }, "attributes": { "type": "array", "description": "Collection attributes (fields)", "items": { "type": "object", "properties": { "key": { "type": "string", "description": "Attribute name", "pattern": "^[a-zA-Z][a-zA-Z0-9]*$" }, "type": { "type": "string", "enum": ["string", "integer", "double", "boolean", "datetime", "email", "ip", "url", "enum", "relationship"], "description": "Attribute data type" }, "size": { "type": "number", "description": "Maximum size for string attributes", "minimum": 1, "maximum": 1073741824 }, "required": { "type": "boolean", "default": false, "description": "Whether the attribute is required" }, "array": { "type": "boolean", "default": false, "description": "Whether the attribute is an array" }, "default": { "description": "Default value for the attribute" }, "description": { "type": "string", "description": "Attribute description" }, "encrypt": { "type": "boolean", "description": "Whether the attribute should be encrypted" }, "format": { "type": "string", "description": "Format for string attributes" }, "min": { "type": "number", "description": "Minimum value for numeric attributes" }, "max": { "type": "number", "description": "Maximum value for numeric attributes" }, "elements": { "type": "array", "items": { "type": "string" }, "description": "Allowed values for enum attributes" }, "relatedCollection": { "type": "string", "description": "Related collection name for relationship attributes" }, "relationType": { "type": "string", "enum": ["oneToOne", "oneToMany", "manyToOne", "manyToMany"], "description": "Type of relationship" }, "twoWay": { "type": "boolean", "description": "Whether the relationship is bidirectional" }, "twoWayKey": { "type": "string", "description": "Key name for the reverse relationship" }, "onDelete": { "type": "string", "enum": ["cascade", "restrict", "setNull"], "description": "Action to take when related document is deleted" }, "side": { "type": "string", "enum": ["parent", "child"], "description": "Side of the relationship" } }, "required": ["key", "type"], "additionalProperties": false, "allOf": [ { "if": { "properties": { "type": { "const": "enum" } } }, "then": { "required": ["elements"] } }, { "if": { "properties": { "type": { "const": "relationship" } } }, "then": { "required": ["relatedCollection", "relationType"] } } ] } }, "indexes": { "type": "array", "description": "Database indexes for the collection", "items": { "type": "object", "properties": { "key": { "type": "string", "description": "Index name" }, "type": { "type": "string", "enum": ["key", "fulltext", "unique"], "description": "Index type" }, "attributes": { "type": "array", "items": { "type": "string" }, "description": "Attributes to index", "minItems": 1 }, "orders": { "type": "array", "items": { "type": "string", "enum": ["ASC", "DESC"] }, "description": "Sort order for each attribute" } }, "required": ["key", "type", "attributes"], "additionalProperties": false } }, "importDefs": { "type": "array", "description": "Import definitions for data migration", "default": [] } }, "required": ["name"], "additionalProperties": false }; // Ensure YAML schemas directory exists before writing schema if (!existsSync(appwriteYamlSchemaFolder)) { mkdirSync(appwriteYamlSchemaFolder, { recursive: true }); } const schemaFileName = useTables ? "table.schema.json" : "collection.schema.json"; const containerSchemaPath = path.join(appwriteYamlSchemaFolder, schemaFileName); writeFileSync(containerSchemaPath, JSON.stringify(containerJsonSchema, null, 2)); // Create JSON schema for appwriteConfig.yaml const configJsonSchema = { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://appwrite-utils.dev/schemas/appwrite-config.schema.json", "title": "Appwrite Configuration", "description": "Schema for Appwrite project configuration in YAML", "type": "object", "properties": { "appwrite": { "type": "object", "description": "Appwrite connection settings", "properties": { "endpoint": { "type": "string", "default": "https://cloud.appwrite.io/v1", "description": "Appwrite server endpoint URL" }, "project": { "type": "string", "description": "Appwrite project ID" }, "key": { "type": "string", "description": "Appwrite API key with appropriate permissions" } }, "required": ["project", "key"], "additionalProperties": false }, "logging": { "type": "object", "description": "Logging configuration", "properties": { "enabled": { "type": "boolean", "default": false, "description": "Enable file logging" }, "level": { "type": "string", "enum": ["error", "warn", "info", "debug"], "default": "info", "description": "Logging level" }, "directory": { "type": "string", "description": "Custom log directory path (optional)" }, "console": { "type": "boolean", "default": false, "description": "Enable console logging" } }, "additionalProperties": false }, "backups": { "type": "object", "description": "Backup configuration", "properties": { "enabled": { "type": "boolean", "default": true, "description": "Enable automatic backups" }, "interval": { "type": "number", "default": 3600, "description": "Backup interval in seconds" }, "retention": { "type": "number", "default": 30, "description": "Backup retention in days" }, "cleanup": { "type": "boolean", "default": true, "description": "Enable automatic backup cleanup" } }, "additionalProperties": false }, "data": { "type": "object", "description": "Data management settings", "properties": { "enableMockData": { "type": "boolean", "default": false, "description": "Enable mock data generation" }, "documentBucketId": { "type": "string", "default": "documents", "description": "Default bucket ID for document attachments" }, "usersCollectionName": { "type": "string", "default": "Members", "description": "Name of the users/members collection" }, "importDirectory": { "type": "string", "default": "importData", "description": "Directory containing import data files" } }, "additionalProperties": false }, "schemas": { "type": "object", "description": "Schema generation settings", "properties": { "outputDirectory": { "type": "string", "default": "schemas", "description": "Directory where generated schemas are saved" }, "yamlSchemaDirectory": { "type": "string", "default": ".yaml_schemas", "description": "Directory containing YAML validation schemas" } }, "additionalProperties": false }, "migrations": { "type": "object", "description": "Migration settings", "properties": { "enabled": { "type": "boolean", "default": true, "description": "Enable migration tracking database" } }, "additionalProperties": false }, "databases": { "type": "array", "description": "Database configurations", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Database ID", "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$" }, "name": { "type": "string", "description": "Database display name" }, "bucket": { "type": "object", "description": "Associated storage bucket", "properties": { "id": { "type": "string", "description": "Bucket ID" }, "name": { "type": "string", "description": "Bucket display name" }, "permissions": { "type": "array", "items": { "type": "string" }, "description": "Bucket permissions" }, "fileSecurity": { "type": "boolean", "description": "Enable file-level security" }, "enabled": { "type": "boolean", "default": true, "description": "Enable the bucket" }, "maximumFileSize": { "type": "number", "default": 30000000, "description": "Maximum file size in bytes" }, "allowedFileExtensions": { "type": "array", "items": { "type": "string" }, "description": "Allowed file extensions (empty = all allowed)" }, "compression": { "type": "string", "enum": ["none", "gzip", "zstd"], "default": "none", "description": "Compression algorithm" }, "encryption": { "type": "boolean", "default": false, "description": "Enable file encryption" }, "antivirus": { "type": "boolean", "default": false, "description": "Enable antivirus scanning" } }, "required": ["id", "name"], "additionalProperties": false } }, "required": ["id", "name"], "additionalProperties": false } }, "buckets": { "type": "array", "description": "Global storage buckets", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Bucket ID" }, "name": { "type": "string", "description": "Bucket display name" }, "permissions": { "type": "array", "items": { "type": "string" }, "description": "Bucket permissions" }, "fileSecurity": { "type": "boolean", "description": "Enable file-level security" }, "enabled": { "type": "boolean", "default": true, "description": "Enable the bucket" }, "maximumFileSize": { "type": "number", "default": 30000000, "description": "Maximum file size in bytes" }, "allowedFileExtensions": { "type": "array", "items": { "type": "string" }, "description": "Allowed file extensions (empty = all allowed)" }, "compression": { "type": "string", "enum": ["none", "gzip", "zstd"], "default": "none", "description": "Compression algorithm" }, "encryption": { "type": "boolean", "default": false, "description": "Enable file encryption" }, "antivirus": { "type": "boolean", "default": false, "description": "Enable antivirus scanning" } }, "required": ["id", "name"], "additionalProperties": false } }, "functions": { "type": "array", "description": "Appwrite Functions", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Function ID" }, "name": { "type": "string", "description": "Function name" }, "runtime": { "type": "string", "description": "Runtime environment" }, "execute": { "type": "array", "items": { "type": "string" }, "description": "Execution permissions" }, "events": { "type": "array", "items": { "type": "string" }, "description": "Event triggers" }, "schedule": { "type": "string", "description": "Cron schedule" }, "timeout": { "type": "number", "default": 15, "description": "Execution timeout in seconds" }, "enabled": { "type": "boolean", "default": true, "description": "Enable the function" }