UNPKG

appwrite-utils

Version:
223 lines (222 loc) 10.9 kB
/** * @fileoverview AppwriteUtils sidecar (extension) configuration schema. * * This schema is a STRICT SUPERSET of the official Appwrite CLI config. It * exists so that AppwriteUtils can hold its own per-resource extensions * (importDefs, attributeDisplayOrder, deployDir, predeployCommands, etc.) * WITHOUT mutating the user's `appwrite.config.json` — the file the official * CLI consumes. * * Two source-of-truth modes for the underlying Appwrite resources: * - referenced: `appwriteConfig` points at an external * `appwrite.config.json` file owned by the official CLI. * - owned: `appwrite` embeds the full official config inline. * * If both are provided, the inline `appwrite` block wins — see the * `appwriteConfig`/`appwrite` descriptions below. * * The root object intentionally uses `.passthrough()` (NOT `.strict()`) so * that users can stuff forward-compatible fields without parser errors. The * per-resource extension sub-schemas also default to `passthrough` because * we have not yet enumerated all extension fields. */ import { z } from "zod"; import { AppwriteOfficialConfigSchema } from "./officialConfig.js"; import { FunctionSchema, SiteSchema } from "./officialConfig.js"; import { importDefSchemas } from "./importDef.js"; // ============================================================================ // Per-resource extension sub-schemas // ============================================================================ // // These are intentionally minimal for now. As the AppwriteUtils-specific // extensions are identified per resource (e.g. `deployDir`, // `predeployCommands`, `ignore` for functions/sites; `importDefs`, // `attributeDisplayOrder` for collections/tables), they should be added to // the appropriate schema below. The `.passthrough()` call ensures that // users can already put arbitrary fields here today without the parser // rejecting them. /** * Function-specific extensions (predeployCommands, deployDir, ignore, etc. * still to be formalised). Currently passthrough. * * Known field: `forceOverwriteSecrets` — when false (default), the var-sync * step skips updating any Appwrite-side variable whose `secret` flag is true, * preserving server-managed secrets across deploys. Set to true to opt into * overwriting secrets from this function's local `vars` block. */ export const FunctionExtensionSchema = z .object({ forceOverwriteSecrets: z .boolean() .optional() .describe("When false (default), the var-sync step refuses to overwrite any Appwrite-side variable whose `secret` flag is true. Set true to opt into clobbering secrets from this function's local `vars` block."), }) .passthrough() .describe("AppwriteUtils-specific extension fields for an Appwrite function. Known: forceOverwriteSecrets."); /** * Site-specific extensions (predeployCommands, deployDir, ignore, etc. * still to be formalised). Currently passthrough. */ export const SiteExtensionSchema = z .object({}) .passthrough() .describe("AppwriteUtils-specific extension fields for an Appwrite site. Currently accepts any keys; will be formalised over time."); /** * Bucket-specific extensions (currently passthrough). */ export const BucketExtensionSchema = z .object({}) .passthrough() .describe("AppwriteUtils-specific extension fields for an Appwrite bucket. Currently accepts any keys; will be formalised over time."); /** * Collection-specific extensions. Recognises `importDefs` (typed against the * existing `importDefSchemas`) and `attributeDisplayOrder`. Other fields are * passed through. */ export const CollectionExtensionSchema = z .object({ importDefs: importDefSchemas .optional() .describe("Import definitions for this collection (existing AppwriteUtils feature)."), attributeDisplayOrder: z .array(z.string()) .optional() .describe("Preferred display ordering for this collection's attributes (existing AppwriteUtils feature)."), }) .passthrough() .describe("AppwriteUtils-specific extension fields for a collection. Known: importDefs, attributeDisplayOrder."); /** * Table-specific extensions. Same shape as `CollectionExtensionSchema`, * defined separately so that they can evolve independently. */ export const TableExtensionSchema = z .object({ importDefs: importDefSchemas .optional() .describe("Import definitions for this table (existing AppwriteUtils feature)."), attributeDisplayOrder: z .array(z.string()) .optional() .describe("Preferred display ordering for this table's attributes/columns (existing AppwriteUtils feature)."), }) .passthrough() .describe("AppwriteUtils-specific extension fields for a table. Known: importDefs, attributeDisplayOrder."); /** * Per-function YAML file at `functions/<id>/appwrite-utils.yaml`. Combines * the official function shape with our extension fields in one document. * Unknown keys pass through (FunctionExtensionSchema is itself passthrough). */ export const PerFunctionConfigSchema = z .object({ ...FunctionSchema.shape, ...FunctionExtensionSchema.shape, }) .passthrough() .describe("Per-function YAML config. Official function fields + AppwriteUtils extensions in one document, keyed by $id."); /** * Per-site YAML file at `sites/<id>/appwrite-utils.yaml`. Same pattern as * PerFunctionConfigSchema. */ export const PerSiteConfigSchema = z .object({ ...SiteSchema.shape, ...SiteExtensionSchema.shape, }) .passthrough() .describe("Per-site YAML config. Official site fields + AppwriteUtils extensions in one document, keyed by $id."); // ============================================================================ // Extension container per resource type // ============================================================================ // // Each entry is EITHER: // - a string: a relative path to a separate YAML file holding the // extensions for that resource type (mirrors the official `includes` // semantics, but for our sidecar fields); OR // - a record keyed by the resource `$id`, mapping to the per-resource // extension shape above. const recordOrPath = (schema) => z.union([z.string(), z.record(z.string(), schema)]); export const ResourceExtensionsSchema = z .object({ functions: recordOrPath(FunctionExtensionSchema).optional(), sites: recordOrPath(SiteExtensionSchema).optional(), buckets: recordOrPath(BucketExtensionSchema).optional(), collections: recordOrPath(CollectionExtensionSchema).optional(), tablesDB: recordOrPath(z.object({}).passthrough()).optional(), tables: recordOrPath(TableExtensionSchema).optional(), teams: recordOrPath(z.object({}).passthrough()).optional(), webhooks: recordOrPath(z.object({}).passthrough()).optional(), topics: recordOrPath(z.object({}).passthrough()).optional(), messages: recordOrPath(z.object({}).passthrough()).optional(), }) .passthrough() .describe("Per-resource extension fields keyed by resource $id (or a path to a YAML file containing them)."); // ============================================================================ // Auth block // ============================================================================ export const AuthBlockSchema = z .object({ endpoint: z.string().optional(), projectId: z.string().optional(), apiKey: z.string().optional(), authMode: z .enum(["apiKey", "session", "auto"]) .default("auto") .describe("Authentication mode: apiKey (use apiKey field), session (use sessionCookie field), or auto (detect at runtime)."), sessionCookie: z.string().optional(), }) .passthrough(); // ============================================================================ // Logging block // ============================================================================ export const LoggingBlockSchema = z .object({ enabled: z.boolean().default(false), level: z.enum(["error", "warn", "info", "debug"]).default("info"), logDirectory: z.string().optional(), console: z.boolean().default(false), }) .passthrough(); // ============================================================================ // Schema/data directory configuration // ============================================================================ export const SchemaConfigSchema = z .object({ outputDirectory: z.string().default("schemas"), yamlSchemaDirectory: z.string().default(".yaml_schemas"), importDirectory: z.string().default("importData"), collectionsDirectory: z.string().default("collections"), tablesDirectory: z.string().default("tables"), }) .passthrough(); // ============================================================================ // Top-level AppwriteUtils Extension Config // ============================================================================ export const AppwriteUtilsExtensionSchema = z .object({ // ---- Appwrite source of truth ------------------------------------------ appwriteConfig: z .string() .optional() .describe("Path to an existing appwrite.config.json. Mutually exclusive with `appwrite`; if both are given, `appwrite` wins."), appwrite: AppwriteOfficialConfigSchema.optional().describe("Inline appwrite.config.json. Mutually exclusive with `appwriteConfig`; if both are given, `appwrite` wins."), // ---- Auth --------------------------------------------------------------- auth: AuthBlockSchema.optional().describe("Authentication configuration so users don't have to run `appwrite client` separately."), // ---- Per-resource extensions ------------------------------------------- extensions: ResourceExtensionsSchema.optional().describe("AppwriteUtils-specific extension fields per resource, keyed by $id."), // ---- Project-wide AppwriteUtils settings ------------------------------- apiMode: z .enum(["auto", "legacy", "tablesdb"]) .default("auto") .describe("API mode selection: auto-detect, force legacy Databases API, or force new TablesDB API."), logging: LoggingBlockSchema.optional(), enableBackups: z.boolean().default(true), backupInterval: z.number().default(3600), backupRetention: z.number().default(30), enableBackupCleanup: z.boolean().default(true), enableMockData: z.boolean().default(false), documentBucketId: z.string().default("documents"), usersCollectionName: z.string().default("Members"), schemaConfig: SchemaConfigSchema.optional(), }) .passthrough() .describe("AppwriteUtils sidecar configuration. Holds AppwriteUtils-specific settings and per-resource extensions alongside (referenced) or embedded with (owned) the official Appwrite config.");