diff --git a/src/SDK/Language/CLI.php b/src/SDK/Language/CLI.php index 3bd0d77a4..5c6abdc36 100644 --- a/src/SDK/Language/CLI.php +++ b/src/SDK/Language/CLI.php @@ -122,6 +122,51 @@ public function getFiles(): array 'destination' => 'lib/sdks.js', 'template' => 'cli/lib/sdks.js.twig', ], + [ + 'scope' => 'default', + 'destination' => 'lib/type-generation/attribute.js', + 'template' => 'cli/lib/type-generation/attribute.js.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'lib/type-generation/collection.js', + 'template' => 'cli/lib/type-generation/collection.js.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'lib/type-generation/languages/language.js', + 'template' => 'cli/lib/type-generation/languages/language.js.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'lib/type-generation/languages/php.js', + 'template' => 'cli/lib/type-generation/languages/php.js.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'lib/type-generation/languages/typescript.js', + 'template' => 'cli/lib/type-generation/languages/typescript.js.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'lib/type-generation/languages/python.js', + 'template' => 'cli/lib/type-generation/languages/python.js.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'lib/type-generation/languages/kotlin.js', + 'template' => 'cli/lib/type-generation/languages/kotlin.js.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'lib/type-generation/languages/swift.js', + 'template' => 'cli/lib/type-generation/languages/swift.js.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'lib/type-generation/languages/java.js', + 'template' => 'cli/lib/type-generation/languages/java.js.twig', + ], [ 'scope' => 'default', 'destination' => 'lib/questions.js', @@ -176,6 +221,11 @@ public function getFiles(): array 'scope' => 'default', 'destination' => 'lib/commands/generic.js', 'template' => 'cli/lib/commands/generic.js.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'lib/commands/types.js', + 'template' => 'cli/lib/commands/types.js.twig', ] ]; } diff --git a/src/SDK/Language/Python.php b/src/SDK/Language/Python.php index 891d0d67d..8cb5c9b25 100644 --- a/src/SDK/Language/Python.php +++ b/src/SDK/Language/Python.php @@ -200,13 +200,18 @@ public function getFiles(): array 'destination' => '.travis.yml', 'template' => 'python/.travis.yml.twig', ], + [ + 'scope' => 'definition', + 'destination' => '{{ spec.title | caseSnake}}/models/{{ definition.name | caseSnake }}.py', + 'template' => 'python/package/models/model.py.twig', + ], [ 'scope' => 'enum', 'destination' => '{{ spec.title | caseSnake}}/enums/{{ enum.name | caseSnake }}.py', 'template' => 'python/package/enums/enum.py.twig', ], ]; - } +} /** * @param array $parameter @@ -344,12 +349,75 @@ public function getParamExample(array $param): string return $output; } + protected function getPropertyType(array $property, array $spec, ?string $generic = 'T'): string + { + if (\array_key_exists('sub_schema', $property)) { + $type = $this->toPascalCase($property['sub_schema']); + + if ($this->hasGenericType($property['sub_schema'], $spec)) { + $type .= '[' . $generic . ']'; + } + + if ($property['type'] === 'array') { + $type = 'List[' . $type . ']'; + } + } else { + $type = $this->getTypeName($property); + } + + if (!$property['required']) { + $type = 'Optional[' . $type . '] = None'; + } + + return $type; + } + + protected function hasGenericType(?string $model, array $spec): string + { + if (empty($model) || $model === 'any') { + return false; + } + + $model = $spec['definitions'][$model]; + + if ($model['additionalProperties']) { + return true; + } + + foreach ($model['properties'] as $property) { + if (!\array_key_exists('sub_schema', $property) || !$property['sub_schema']) { + continue; + } + + return $this->hasGenericType($property['sub_schema'], $spec); + } + + return false; + } + + protected function getModelType(array $definition, array $spec, ?string $generic = 'T'): string + { + if ($this->hasGenericType($definition['name'], $spec)) { + return $this->toPascalCase($definition['name']) . '(Generic[' . $generic . '])'; + } + return $this->toPascalCase($definition['name']); + } + public function getFilters(): array { return [ new TwigFilter('caseEnumKey', function (string $value) { return $this->toUpperSnakeCase($value); }), + new TwigFilter('hasGenericType', function (string $model, array $spec) { + return $this->hasGenericType($model, $spec); + }), + new TwigFilter('modelType', function (array $definition, array $spec, ?string $generic = 'T') { + return $this->getModelType($definition, $spec, $generic); + }), + new TwigFilter('propertyType', function (array $property, array $spec, ?string $generic = 'T') { + return $this->getPropertyType($property, $spec, $generic); + }), ]; } } diff --git a/templates/cli/index.js.twig b/templates/cli/index.js.twig index d4415db72..e8a466772 100644 --- a/templates/cli/index.js.twig +++ b/templates/cli/index.js.twig @@ -13,6 +13,7 @@ const { client } = require("./lib/commands/generic"); {% if sdk.test != "true" %} const { login, logout } = require("./lib/commands/generic"); const { init } = require("./lib/commands/init"); +const { types } = require("./lib/commands/types"); const { deploy } = require("./lib/commands/deploy"); {% endif %} {% for service in spec.services %} @@ -38,6 +39,7 @@ program {% if sdk.test != "true" %} .addCommand(login) .addCommand(init) + .addCommand(types) .addCommand(deploy) .addCommand(logout) {% endif %} diff --git a/templates/cli/lib/commands/types.js.twig b/templates/cli/lib/commands/types.js.twig new file mode 100644 index 000000000..d7c1b4efc --- /dev/null +++ b/templates/cli/lib/commands/types.js.twig @@ -0,0 +1,123 @@ +const ejs = require("ejs"); +const fs = require("fs"); +const path = require("path"); +const { LanguageMeta, detectLanguage } = require("../type-generation/languages/language"); +const { Command, Option, Argument } = require("commander"); +const { localConfig } = require("../config"); +const { success, log, actionRunner } = require("../parser"); +const { PHP } = require("../type-generation/languages/php"); +const { TypeScript } = require("../type-generation/languages/typescript"); +const { Python } = require("../type-generation/languages/python"); +const { Kotlin } = require("../type-generation/languages/kotlin"); +const { Swift } = require("../type-generation/languages/swift"); +const { Java } = require("../type-generation/languages/java"); + +/** + * @param {string} language + * @returns {import("../type-generation/languages/language").LanguageMeta} + */ +function createLanguageMeta(language) { + switch (language) { + case "ts": + return new TypeScript(); + case "php": + return new PHP(); + case "python": + return new Python(); + case "kotlin": + return new Kotlin(); + case "swift": + return new Swift(); + case "java": + return new Java(); + default: + throw new Error(`Language '${language}' is not supported`); + } +} + +const templateHelpers = { + toPascalCase: LanguageMeta.toPascalCase, + toCamelCase: LanguageMeta.toCamelCase, + toSnakeCase: LanguageMeta.toSnakeCase, + toKebabCase: LanguageMeta.toKebabCase, + toUpperSnakeCase: LanguageMeta.toUpperSnakeCase +} + +const typesOutputArgument = new Argument( + "", + "The directory to write the types to" +); + +const typesLanguageOption = new Option( + "-l, --language ", + "The language of the types" +) + .choices(["ts", "php", "python", "kotlin", "swift", "java"]) + .default("auto"); + +const typesCommand = actionRunner(async (rawOutputDirectory, {language}) => { + if (language === "auto") { + language = detectLanguage(); + log(`Detected language: ${language}`); + } + + const meta = createLanguageMeta(language); + + const outputDirectory = path.resolve(rawOutputDirectory); + if (!fs.existsSync(outputDirectory)) { + log(`Directory: ${outputDirectory} does not exist, creating...`); + fs.mkdirSync(outputDirectory, { recursive: true }); + } + + if (!fs.existsSync("appwrite.json")) { + throw new Error("appwrite.json not found in current directory"); + } + + const collections = localConfig.getCollections(); + if (collections.length === 0) { + throw new Error("No collections found in appwrite.json"); + } + + log(`Found ${collections.length} collections: ${collections.map(c => c.name).join(", ")}`); + + const totalAttributes = collections.reduce((count, collection) => count + collection.attributes.length, 0); + log(`Found ${totalAttributes} attributes across all collections`); + + const templater = ejs.compile(meta.getTemplate()); + + if (meta.isSingleFile()) { + const content = templater({ + collections, + ...templateHelpers, + getType: meta.getType + }); + + const destination = path.join(outputDirectory, meta.getFileName()); + + fs.writeFileSync(destination, content); + log(`Added types to ${destination}`); + } else { + for (const collection of collections) { + const content = templater({ + collection, + ...templateHelpers, + getType: meta.getType + }); + + const destination = path.join(outputDirectory, meta.getFileName(collection)); + + fs.writeFileSync(destination, content); + log(`Added types for ${collection.name} to ${destination}`); + } + } + + success(); +}); + +const types = new Command("types") + .description("Generate types for your Appwrite project") + .addArgument(typesOutputArgument) + .addOption(typesLanguageOption) + .action(actionRunner(typesCommand)); + +module.exports = { types }; diff --git a/templates/cli/lib/type-generation/attribute.js.twig b/templates/cli/lib/type-generation/attribute.js.twig new file mode 100644 index 000000000..e357b0d40 --- /dev/null +++ b/templates/cli/lib/type-generation/attribute.js.twig @@ -0,0 +1,27 @@ +const AttributeType = { + STRING: "string", + INTEGER: "integer", + FLOAT: "float", + BOOLEAN: "boolean", + DATETIME: "datetime", + EMAIL: "email", + IP: "ip", + URL: "url", + ENUM: "enum", + RELATIONSHIP: "relationship", +}; + +/** + * @typedef {Object} Attribute + * @property {string} key - The unique identifier of the attribute. + * @property {"string"|"integer"|"float"|"boolean"|"datetime"|"email"|"ip"|"url"|"enum"|"relationship"} type - The type of the attribute. + * @property {string} status - The status of the attribute. + * @property {boolean} required - The required status of the attribute. + * @property {boolean} array - The array status of the attribute. + * @property {number} size - The size of the attribute. + * @property {string} default - The default value of the attribute. + */ + + module.exports = { + AttributeType, + }; \ No newline at end of file diff --git a/templates/cli/lib/type-generation/collection.js.twig b/templates/cli/lib/type-generation/collection.js.twig new file mode 100644 index 000000000..6fcb0dd71 --- /dev/null +++ b/templates/cli/lib/type-generation/collection.js.twig @@ -0,0 +1,13 @@ +/** + * Represents a collection within a database. + * + * @typedef {Object} Collection + * @property {string} $id - The unique identifier of the collection. + * @property {string[]} $permissions - The permissions for accessing the collection. + * @property {string} databaseId - The identifier of the database this collection belongs to. + * @property {string} name - The name of the collection. + * @property {boolean} enabled - Indicates if the collection is enabled. + * @property {boolean} documentSecurity - Indicates if document-level security is enabled for the collection. + * @property {import('./attribute.js).Attribute[]} attributes - The attributes (fields) defined in the collection. + * @property {unknown[]} indexes - The indexes defined on the collection for optimized query performance. + */ \ No newline at end of file diff --git a/templates/cli/lib/type-generation/languages/java.js.twig b/templates/cli/lib/type-generation/languages/java.js.twig new file mode 100644 index 000000000..a16e06540 --- /dev/null +++ b/templates/cli/lib/type-generation/languages/java.js.twig @@ -0,0 +1,55 @@ +/** @typedef {import('../attribute').Attribute} Attribute */ +const { AttributeType } = require('../attribute'); +const { LanguageMeta } = require("./language"); + +class Java extends LanguageMeta { + getType(attribute) { + let type = ""; + switch (attribute.type) { + case AttributeType.STRING: + case AttributeType.EMAIL: + case AttributeType.DATETIME: + case AttributeType.ENUM: + type = "String"; + break; + case AttributeType.INTEGER: + type = "Int"; + break; + case AttributeType.FLOAT: + type = "Float"; + break; + case AttributeType.BOOLEAN: + type = "Boolean"; + break; + case AttributeType.RELATIONSHIP: + type = "Map"; + default: + throw new Error(`Unknown attribute type: ${attribute.type}`); + } + if (attribute.array) { + type = "List<" + type + ">"; + } + if (attribute.required) { + type += "?"; + } + return type; + } + + getTemplate() { + return `package io.appwrite.models; + +import java.util.*; + +public record <%- toPascalCase(collection.name) %>( +<% for (const [index, attribute] of Object.entries(collection.attributes)) { -%> + <%= getType(attribute) %> <%= toCamelCase(attribute.key) %><%- index < collection.attributes.length - 1 ? ',' : '' %> +<% } -%> +) { }`; + } + + getFileName(collection) { + return LanguageMeta.toPascalCase(collection.name) + ".java"; + } +} + +module.exports = { Java }; \ No newline at end of file diff --git a/templates/cli/lib/type-generation/languages/kotlin.js.twig b/templates/cli/lib/type-generation/languages/kotlin.js.twig new file mode 100644 index 000000000..dec3052c9 --- /dev/null +++ b/templates/cli/lib/type-generation/languages/kotlin.js.twig @@ -0,0 +1,53 @@ +/** @typedef {import('../attribute').Attribute} Attribute */ +const { AttributeType } = require('../attribute'); +const { LanguageMeta } = require("./language"); + +class Kotlin extends LanguageMeta { + getType(attribute) { + let type = ""; + switch (attribute.type) { + case AttributeType.STRING: + case AttributeType.EMAIL: + case AttributeType.DATETIME: + case AttributeType.ENUM: + type = "String"; + break; + case AttributeType.INTEGER: + type = "Int"; + break; + case AttributeType.FLOAT: + type = "Float"; + break; + case AttributeType.BOOLEAN: + type = "Boolean"; + break; + case AttributeType.RELATIONSHIP: + type = "Map"; + default: + throw new Error(`Unknown attribute type: ${attribute.type}`); + } + if (attribute.array) { + type = "List<" + type + ">"; + } + if (attribute.required) { + type += "?"; + } + return type; + } + + getTemplate() { + return `package io.appwrite.models + +data class <%- toPascalCase(collection.name) %>( +<% for (const attribute of collection.attributes) { -%> + val <%- toCamelCase(attribute.key) %>: <%- getType(attribute) %>, +<% } -%> +)`; + } + + getFileName(collection) { + return LanguageMeta.toPascalCase(collection.name) + ".kt"; + } +} + +module.exports = { Kotlin }; \ No newline at end of file diff --git a/templates/cli/lib/type-generation/languages/language.js.twig b/templates/cli/lib/type-generation/languages/language.js.twig new file mode 100644 index 000000000..2b6c93d65 --- /dev/null +++ b/templates/cli/lib/type-generation/languages/language.js.twig @@ -0,0 +1,122 @@ +/** @typedef {import('../attribute').Attribute} Attribute */ +/** @typedef {import('../collection').Collection} Collection */ + +const fs = require("fs"); +const path = require("path"); + +class LanguageMeta { + constructor() { + if (new.target === LanguageMeta) { + throw new TypeError("Abstract classes can't be instantiated."); + } + } + + static toKebabCase(string) { + return string + .replace(/[^a-zA-Z0-9\s-_]/g, "") // Remove invalid characters + .replace(/([a-z])([A-Z])/g, "$1-$2") // Add hyphen between camelCase + .replace(/([A-Z])([A-Z][a-z])/g, "$1-$2") // Add hyphen between PascalCase + .replace(/[_\s]+/g, "-") // Replace spaces and underscores with hyphens + .replace(/^-+|-+$/g, "") // Remove leading and trailing hyphens + .replace(/--+/g, "-") // Replace multiple hyphens with a single hyphen + .toLowerCase(); + } + + static toSnakeCase(string) { + return this.toKebabCase(string).replace(/-/g, "_"); + } + + static toUpperSnakeCase(string) { + return this.toSnakeCase(string).toUpperCase(); + } + + static toCamelCase(string) { + return this.toKebabCase(string).replace(/-([a-z])/g, (g) => + g[1].toUpperCase() + ); + } + + static toPascalCase(string) { + return this.toCamelCase(string).replace(/^./, (g) => g.toUpperCase()); + } + + /** + * Get the type literal of the given attribute. + * + * @abstract + * @param {Attribute} attribute + * @return {string} + */ + getType(attribute) { + throw new TypeError("Stub."); + } + + /** + * Returns true if the language uses a single file for all types. + * + * @returns {boolean} + */ + isSingleFile() { + return false; + } + + /** + * Get the EJS template used to generate the types for this language. + * + * @abstract + * @returns {string} + */ + getTemplate() { + throw new TypeError("Stub."); + } + + /** + * Get the file extension used by files of this language. + * + * @abstract + * @param {Collection|undefined} collection + * @returns {string} + */ + getFileName(collection) { + throw new TypeError("Stub."); + } +} + +const existsFiles = (...files) => + files.some((file) => fs.existsSync(path.join(process.cwd(), file))); + +/** + * @returns {string} + */ +function detectLanguage() { + if (existsFiles("package.json", "tsconfig.json", "deno.json")) { + return "ts"; + } + if (existsFiles("composer.json")) { + return "php"; + } + if (existsFiles("requirements.txt", "Pipfile", "pyproject.toml")) { + return "python"; + } + if (existsFiles("Gemfile", "Rakefile")) { + return "ruby"; + } + if (existsFiles("build.gradle.kts")) { + return "kotlin"; + } + if (existsFiles("build.gradle", "pom.xml")) { + return "java"; + } + if (existsFiles("*.csproj")) { + return "dotnet"; + } + if (existsFiles("Package.swift")) { + return "swift"; + } + if (existsFiles("pubspec.yaml")) { + return "dart"; + } + throw new Error("Could not detect language, please specify with -l"); +} + +module.exports = { LanguageMeta, detectLanguage }; diff --git a/templates/cli/lib/type-generation/languages/php.js.twig b/templates/cli/lib/type-generation/languages/php.js.twig new file mode 100644 index 000000000..bb5074e24 --- /dev/null +++ b/templates/cli/lib/type-generation/languages/php.js.twig @@ -0,0 +1,56 @@ +/** @typedef {import('../attribute').Attribute} Attribute */ +const { AttributeType } = require('../attribute'); +const { LanguageMeta } = require("./language"); + +class PHP extends LanguageMeta { + getType(attribute) { + if (attribute.array) { + return "array"; + } + let type = "" + switch (attribute.type) { + case AttributeType.STRING: + case AttributeType.EMAIL: + case AttributeType.DATETIME: + case AttributeType.ENUM: + type = "string"; + break; + case AttributeType.INTEGER: + type = "int"; + break; + case AttributeType.FLOAT: + type = "float"; + break; + case AttributeType.BOOLEAN: + type = "bool"; + break; + case AttributeType.RELATIONSHIP: + type = "mixed"; + default: + throw new Error(`Unknown attribute type: ${attribute.type}`); + } + if (attribute.required) { + type += "|null"; + } + return type; + } + + getTemplate() { + return ` extends Document { +<% for (const attribute of collection.attributes ){ -%> + public <%- getType(attribute) %> $<%- toCamelCase(attribute.key) %>; +<% } -%> +}`; + } + + getFileName(collection) { + return LanguageMeta.toPascalCase(collection.name) + ".php"; + } +} + +module.exports = { PHP }; \ No newline at end of file diff --git a/templates/cli/lib/type-generation/languages/python.js.twig b/templates/cli/lib/type-generation/languages/python.js.twig new file mode 100644 index 000000000..f71f65e77 --- /dev/null +++ b/templates/cli/lib/type-generation/languages/python.js.twig @@ -0,0 +1,59 @@ +/** @typedef {import('../attribute').Attribute} Attribute */ +const { AttributeType } = require('../attribute'); +const { LanguageMeta } = require("./language"); + +class Python extends LanguageMeta { + getType(attribute) { + let type = "" + switch (attribute.type) { + case AttributeType.STRING: + case AttributeType.EMAIL: + case AttributeType.DATETIME: + case AttributeType.ENUM: + type = "str"; + break; + case AttributeType.INTEGER: + type = "int"; + break; + case AttributeType.FLOAT: + type = "float"; + break; + case AttributeType.BOOLEAN: + type = "bool"; + break; + case AttributeType.RELATIONSHIP: + type = "typing.Any"; + default: + throw new Error(`Unknown attribute type: ${attribute.type}`); + } + if (attribute.array) { + type = `typing.List[${type}]`; + } + if (attribute.required) { + type += " | None"; + } + return type; + } + + isSingleFile() { + return true; + } + + getTemplate() { + return `import typing + +<% for (const collection of collections) { %> +class <%- toPascalCase(collection.name) %>(typing.TypedDict): +<% for (const attribute of collection.attributes) { -%> + <%- toSnakeCase(attribute.key) %>: <%- getType(attribute) %> +<% } -%> + +<% } -%>`; + } + + getFileName(_) { + return "collections.py"; + } +} + +module.exports = { Python }; \ No newline at end of file diff --git a/templates/cli/lib/type-generation/languages/swift.js.twig b/templates/cli/lib/type-generation/languages/swift.js.twig new file mode 100644 index 000000000..5262ea0df --- /dev/null +++ b/templates/cli/lib/type-generation/languages/swift.js.twig @@ -0,0 +1,59 @@ +/** @typedef {import('../attribute').Attribute} Attribute */ +const { AttributeType } = require('../attribute'); +const { LanguageMeta } = require("./language"); + +class Swift extends LanguageMeta { + getType(attribute) { + let type = ""; + switch (attribute.type) { + case AttributeType.STRING: + case AttributeType.EMAIL: + case AttributeType.DATETIME: + case AttributeType.ENUM: + type = "String"; + break; + case AttributeType.INTEGER: + type = "Int"; + break; + case AttributeType.FLOAT: + type = "Double"; + break; + case AttributeType.BOOLEAN: + type = "Bool"; + break; + case AttributeType.RELATIONSHIP: + return "Map"; + default: + throw new Error(`Unknown attribute type: ${attribute.type}`); + } + if (attribute.array) { + type = "[" + type + "]"; + } + if (attribute.required) { + type += "?"; + } + return type; + } + + getTemplate() { + return `import Foundation +import Codable + +public class <%- toPascalCase(collection.name) %>: Codable { +<% for (const attribute of collection.attributes) { -%> + public var <%- toCamelCase(attribute.key) %>: <%- getType(attribute) %> +<% } %> + enum CodingKeys: String, CodingKey { +<% for (const attribute of collection.attributes) { -%> + case <%- toCamelCase(attribute.key) %> +<% } -%> + } +}`; + } + + getFileName(collection) { + return LanguageMeta.toPascalCase(collection.name) + ".swift"; + } +} + +module.exports = { Swift }; \ No newline at end of file diff --git a/templates/cli/lib/type-generation/languages/typescript.js.twig b/templates/cli/lib/type-generation/languages/typescript.js.twig new file mode 100644 index 000000000..69e738bf2 --- /dev/null +++ b/templates/cli/lib/type-generation/languages/typescript.js.twig @@ -0,0 +1,75 @@ +/** @typedef {import('../attribute').Attribute} Attribute */ +const fs = require("fs"); +const path = require("path"); + +const { AttributeType } = require('../attribute'); +const { LanguageMeta } = require("./language"); + +class TypeScript extends LanguageMeta { + getType(attribute) { + let type = "" + switch (attribute.type) { + case AttributeType.STRING: + case AttributeType.EMAIL: + case AttributeType.DATETIME: + case AttributeType.ENUM: + type = "string"; + break; + case AttributeType.INTEGER: + type = "number"; + break; + case AttributeType.FLOAT: + type = "number"; + break; + case AttributeType.BOOLEAN: + type = "boolean"; + break; + case AttributeType.RELATIONSHIP: + type = "any"; + default: + throw new Error(`Unknown attribute type: ${attribute.type}`); + } + if (attribute.array) { + type += "[]"; + } + if (attribute.required) { + type += " | null"; + } + return type; + } + + isSingleFile() { + return true; + } + + _getAppwriteDependency() { + if (fs.existsSync(path.resolve(process.cwd(), 'package.json'))) { + const packageJsonRaw = fs.readFileSync(path.resolve(process.cwd(), 'package.json')); + const packageJson = JSON.parse(packageJsonRaw.toString('utf-8')); + return packageJson.dependencies['node-appwrite'] ? 'node-appwrite' : 'appwrite'; + } + + if (fs.existsSync(path.resolve(process.cwd(), 'deno.json'))) { + return "https://deno.land/x/appwrite/mod.ts"; + } + + return "appwrite"; + } + + getTemplate() { + return `import { Models } from '${this._getAppwriteDependency()}'; +<% for (const collection of collections) { %> +export type <%- toPascalCase(collection.name) %> = Models.Document & { +<% for (const attribute of collection.attributes) { -%> + <%- toCamelCase(attribute.key) %>: <%- getType(attribute) %>; +<% } -%> +} +<% } %>`; + } + + getFileName(_) { + return "appwrite.d.ts"; + } +} + +module.exports = { TypeScript }; \ No newline at end of file diff --git a/templates/cli/package.json.twig b/templates/cli/package.json.twig index 1392ff294..6016bdb85 100644 --- a/templates/cli/package.json.twig +++ b/templates/cli/package.json.twig @@ -23,6 +23,7 @@ }, "dependencies": { "undici": "^5.28.2", + "ejs": "^3.1.9", "chalk": "4.1.2", "cli-table3": "^0.6.2", "commander": "^9.2.0", diff --git a/templates/python/package/models/model.py.twig b/templates/python/package/models/model.py.twig new file mode 100644 index 000000000..e02d2d8a5 --- /dev/null +++ b/templates/python/package/models/model.py.twig @@ -0,0 +1,37 @@ +from typing import Optional, List, Dict, Any, Union, TypeVar, Generic +{% for property in definition.properties %} +{%~ if property.sub_schema %} +from .{{ property.sub_schema | caseSnake }} import {{ property.sub_schema | caseUcfirst }} +{% endif -%} +{% endfor -%} + +{% if definition.additionalProperties %} +T = TypeVar('T') +{% else -%} +{% for property in definition.properties -%} +{% if property.sub_schema -%} +T = TypeVar('T') +{% endif -%} +{% endfor -%} +{% endif %} + +""" +{{ definition.description }} +""" +class {{ definition | modelType(spec) | raw }}: + {%~ for property in definition.properties %} + """ {{ property.description }} """ + {{ property.name | removeDollarSign }}: {{ property | propertyType(spec) | raw }} + + {%~ endfor %} + {%~ if definition.additionalProperties %} + data: T + {%~ endif %} + + def __init__(self, {% for property in definition.properties %}{{ property.name | removeDollarSign }}: {{ property | propertyType(spec) | raw }}{% if not loop.last %}, {% endif %}{% endfor %}{% if definition.additionalProperties %}, data: T{% endif %}): + {%~ for property in definition.properties %} + self.{{ property.name | removeDollarSign }} = {{ property.name | removeDollarSign }} + {%~ endfor %} + {%~ if definition.additionalProperties %} + self.data = data + {%~ endif %} \ No newline at end of file diff --git a/templates/web/src/models.ts.twig b/templates/web/src/models.ts.twig index 4b0f63f8b..216478bba 100644 --- a/templates/web/src/models.ts.twig +++ b/templates/web/src/models.ts.twig @@ -12,6 +12,6 @@ export namespace Models { {% endfor %} {% if definition.additionalProperties %} [key: string]: any; {% endif %} - } + } {% endfor %} }