From 5bbb5a1bad35d0f29098d9808d9aab0ce2ea25da Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 6 Mar 2024 10:46:29 +0100 Subject: [PATCH 01/17] feat: initial draft types generator --- src/SDK/Language/CLI.php | 25 +++++ templates/cli/lib/commands/types.js.twig | 76 +++++++++++++++ .../lib/type-generation/attributes.js.twig | 23 +++++ .../languages/language.js.twig | 93 +++++++++++++++++++ .../lib/type-generation/languages/php.js.twig | 58 ++++++++++++ .../languages/typescript.js.twig | 56 +++++++++++ 6 files changed, 331 insertions(+) create mode 100644 templates/cli/lib/commands/types.js.twig create mode 100644 templates/cli/lib/type-generation/attributes.js.twig create mode 100644 templates/cli/lib/type-generation/languages/language.js.twig create mode 100644 templates/cli/lib/type-generation/languages/php.js.twig create mode 100644 templates/cli/lib/type-generation/languages/typescript.js.twig diff --git a/src/SDK/Language/CLI.php b/src/SDK/Language/CLI.php index 3bd0d77a4..d28e645ed 100644 --- a/src/SDK/Language/CLI.php +++ b/src/SDK/Language/CLI.php @@ -122,6 +122,26 @@ public function getFiles(): array 'destination' => 'lib/sdks.js', 'template' => 'cli/lib/sdks.js.twig', ], + [ + 'scope' => 'default', + 'destination' => 'lib/type-generation/attributes.js', + 'template' => 'cli/lib/type-generation/attributes.js.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'lib/type-generation/languages/langauge.js', + 'template' => 'cli/lib/type-generation/languages/langauge.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/questions.js', @@ -176,6 +196,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/templates/cli/lib/commands/types.js.twig b/templates/cli/lib/commands/types.js.twig new file mode 100644 index 000000000..d7ba8bfa3 --- /dev/null +++ b/templates/cli/lib/commands/types.js.twig @@ -0,0 +1,76 @@ +const { PHP } = require("../type-generation/languages/php"); +const { TypeScript } = require("../type-generation/languages/typescript"); + +const fs = require("fs"); +const path = require("path"); +const { Command, Option } = require("commander"); +const { localConfig } = require("../config"); +const { success, log, actionRunner } = require("../parser"); +const ejs = require("ejs"); + +const typesLanguageOption = new Option( + "-l, --language ", + "The language of the types" +) + .choices(["ts", "php"]) + .default("auto"); + +const typesOutputOption = new Option( + "-o, --output ", + "The output path of the types" +).default("auto"); + +const languages = { + ts: new TypeScript(), + php: new PHP(), +}; + +function detectLanguage() { + if (fs.existsSync(path.join(process.cwd(), "package.json"))) { + return "ts"; + } + if (fs.existsSync(path.join(process.cwd(), "composer.json"))) { + return "php"; + } + throw new Error("Could not detect language, please specify with -l"); +} + +const typesCommand = actionRunner(async (options) => { + let { language, output } = options; + + if (language === "auto") { + language = detectLanguage(); + log(`Detected language: ${language}`); + } + + const generator = languages[language]; + + if (output === "auto") { + output = generator.getOutputPath(); + log(`Using default output path: ${output}`); + } + + const collections = localConfig.getCollections(); + + const templateContent = generator.getTemplate(); + const templater = ejs.compile(templateContent); + const typesContent = templater({ collections }); + + const outputDir = path.dirname(output); + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + + fs.writeFileSync(output, typesContent); + log(`Types written to ${output}`); + + success(); +}); + +const types = new Command("types") + .description("Generate types for your Appwrite project") + .addOption(typesLanguageOption) + .addOption(typesOutputOption) + .action(actionRunner(typesCommand)); + +export { types }; diff --git a/templates/cli/lib/type-generation/attributes.js.twig b/templates/cli/lib/type-generation/attributes.js.twig new file mode 100644 index 000000000..24409725c --- /dev/null +++ b/templates/cli/lib/type-generation/attributes.js.twig @@ -0,0 +1,23 @@ +export 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. + */ \ 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..1111710ed --- /dev/null +++ b/templates/cli/lib/type-generation/languages/language.js.twig @@ -0,0 +1,93 @@ +/** @typedef {import('../attributes').Attribute} Attribute */ + +export class Language { + constructor() { + if (new.target === Language) { + throw new TypeError("Abstract classes can't be instantiated."); + } + } + + /** + * @param {string} string + * @returns {string} + */ + static toKebabCase(string) { + return string + .split("") + .map((letter) => { + if (/[A-Z]/.test(letter)) { + return ` ${letter.toLowerCase()}`; + } + return letter; + }) + .join("") + .trim() + .replace(/[_\s]+/g, "-"); + } + + /** + * @param {string} string + * @returns {string} + */ + static toSnakeCase(string) { + return this.toKebabCase(string).replace(/-/g, "_"); + } + + /** + * @param {string} string + * @returns {string} + */ + static toUpperSnakeCase(string) { + return this.toSnakeCase(string).toUpperCase(); + } + + /** + * @param {string} string + * @returns {string} + */ + static toPascalCase(string) { + return this.toKebabCase(string) + .split("-") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(""); + } + + /** + * @param {string} string + * @returns {string} + */ + static toCamelCase(string) { + return this.toPascalCase(string).charAt(0).toLowerCase() + string.slice(1); + } + + /** + * Get the type literal of the given attribute. + * + * @abstract + * @param {Attribute} attribute + * @return {string} + */ + static getType(attribute) { + throw new TypeError("Stub."); + } + + /** + * Get the EJS template used to generate the types for this language. + * + * @abstract + * @returns {string} + */ + static getTemplate() { + throw new TypeError("Stub."); + } + + /** + * Get the file extension used by files of this language. + * + * @abstract + * @returns {string} + */ + static getOutputPath() { + throw new TypeError("Stub."); + } +} 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..b7954bfc9 --- /dev/null +++ b/templates/cli/lib/type-generation/languages/php.js.twig @@ -0,0 +1,58 @@ +/** @typedef {import('../attributes').Attribute} Attribute */ +const { AttributeType } = require('../attributes'); +const { Language } = require("./language"); + +export class PHP extends Language { + /** + * @param {Attribute} attribute + */ + static 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 = "bool"; + break; + case AttributeType.RELATIONSHIP: + type = "mixed"; + default: + throw new Error(`Unknown attribute type: ${attribute.type}`); + } + if (attribute.array) { + type += "[]"; + } + if (attribute.required) { + type += "|null"; + } + return type; + } + + static getTemplate() { + return ` +class <%= toPascalCase(collection.key) %> extends Document { + <% for (const attribute of collection.attributes ){ %> + public <%= toCamelCase(attribute.key) %>: <%= getType(attribute) %>; + <% } %> +} + +<% } %>`; + } + + static getOutputPath() { + return "./generated/Appwrite.php"; + } +} 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..df63fd688 --- /dev/null +++ b/templates/cli/lib/type-generation/languages/typescript.js.twig @@ -0,0 +1,56 @@ +/** @typedef {import('../attributes').Attribute} Attribute */ +const { AttributeType } = require('../attributes'); +const { Language } = require("./language"); + +export class TypeScript extends Language { + /** + * @param {Attribute} attribute + */ + static 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; + } + + static getTemplate() { + return ` +<% for (const collection of collections.attributes ){ %> +export type <%= toPascalCase(collection.key) %> = { + <% for (const attribute of collection.attributes) { %> + <%= toCamelCase(attribute.key) %>: <%= getType(attribute) %>; + <% } %> +} + +<% } %>`; + } + + static getOutputPath() { + return "./generated/appwrite.d.ts"; + } +} From 3a7250448948702cb9e53ab67718f463a81b69a8 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 6 Mar 2024 12:39:29 +0100 Subject: [PATCH 02/17] feat: typo in template name --- src/SDK/Language/CLI.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SDK/Language/CLI.php b/src/SDK/Language/CLI.php index d28e645ed..9d3e063e8 100644 --- a/src/SDK/Language/CLI.php +++ b/src/SDK/Language/CLI.php @@ -129,8 +129,8 @@ public function getFiles(): array ], [ 'scope' => 'default', - 'destination' => 'lib/type-generation/languages/langauge.js', - 'template' => 'cli/lib/type-generation/languages/langauge.js.twig', + 'destination' => 'lib/type-generation/languages/language.js', + 'template' => 'cli/lib/type-generation/languages/language.js.twig', ], [ 'scope' => 'default', From 3d501a5aeb4bfecd7451c2da7e78fad876b471ed Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 6 Mar 2024 14:35:49 +0100 Subject: [PATCH 03/17] fix: use cjs --- templates/cli/index.js.twig | 2 ++ templates/cli/lib/commands/types.js.twig | 2 +- templates/cli/lib/type-generation/attributes.js.twig | 8 ++++++-- .../cli/lib/type-generation/languages/language.js.twig | 4 +++- templates/cli/lib/type-generation/languages/php.js.twig | 4 +++- .../cli/lib/type-generation/languages/typescript.js.twig | 4 +++- 6 files changed, 18 insertions(+), 6 deletions(-) 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 index d7ba8bfa3..177120b24 100644 --- a/templates/cli/lib/commands/types.js.twig +++ b/templates/cli/lib/commands/types.js.twig @@ -73,4 +73,4 @@ const types = new Command("types") .addOption(typesOutputOption) .action(actionRunner(typesCommand)); -export { types }; +module.exports = { types }; diff --git a/templates/cli/lib/type-generation/attributes.js.twig b/templates/cli/lib/type-generation/attributes.js.twig index 24409725c..e357b0d40 100644 --- a/templates/cli/lib/type-generation/attributes.js.twig +++ b/templates/cli/lib/type-generation/attributes.js.twig @@ -1,4 +1,4 @@ -export const AttributeType = { +const AttributeType = { STRING: "string", INTEGER: "integer", FLOAT: "float", @@ -20,4 +20,8 @@ export const AttributeType = { * @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. - */ \ No newline at end of file + */ + + module.exports = { + AttributeType, + }; \ 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 index 1111710ed..53e7829a8 100644 --- a/templates/cli/lib/type-generation/languages/language.js.twig +++ b/templates/cli/lib/type-generation/languages/language.js.twig @@ -1,6 +1,6 @@ /** @typedef {import('../attributes').Attribute} Attribute */ -export class Language { +class Language { constructor() { if (new.target === Language) { throw new TypeError("Abstract classes can't be instantiated."); @@ -91,3 +91,5 @@ export class Language { throw new TypeError("Stub."); } } + +module.exports = { Language }; \ No newline at end of file diff --git a/templates/cli/lib/type-generation/languages/php.js.twig b/templates/cli/lib/type-generation/languages/php.js.twig index b7954bfc9..31f9c22d7 100644 --- a/templates/cli/lib/type-generation/languages/php.js.twig +++ b/templates/cli/lib/type-generation/languages/php.js.twig @@ -2,7 +2,7 @@ const { AttributeType } = require('../attributes'); const { Language } = require("./language"); -export class PHP extends Language { +class PHP extends Language { /** * @param {Attribute} attribute */ @@ -56,3 +56,5 @@ class <%= toPascalCase(collection.key) %> extends Document { return "./generated/Appwrite.php"; } } + +module.exports = { PHP }; \ 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 index df63fd688..8e16c717c 100644 --- a/templates/cli/lib/type-generation/languages/typescript.js.twig +++ b/templates/cli/lib/type-generation/languages/typescript.js.twig @@ -2,7 +2,7 @@ const { AttributeType } = require('../attributes'); const { Language } = require("./language"); -export class TypeScript extends Language { +class TypeScript extends Language { /** * @param {Attribute} attribute */ @@ -54,3 +54,5 @@ export type <%= toPascalCase(collection.key) %> = { return "./generated/appwrite.d.ts"; } } + +module.exports = { TypeScript }; \ No newline at end of file From b058588f547e67d7d4eead6c92b84cdc56955ff6 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 6 Mar 2024 14:38:05 +0100 Subject: [PATCH 04/17] feat: add ejs --- templates/cli/package.json.twig | 1 + 1 file changed, 1 insertion(+) 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", From 1ef2bdc0c1fa1b863910c59bdbd4d234e5284ece Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:27:29 +0100 Subject: [PATCH 05/17] feat: more stuff --- src/SDK/Language/CLI.php | 9 +- templates/cli/lib/commands/types.js.twig | 123 +++++++++++------- .../{attributes.js.twig => attribute.js.twig} | 0 .../lib/type-generation/collection.js.twig | 13 ++ .../languages/language.js.twig | 99 +++++++------- .../lib/type-generation/languages/php.js.twig | 38 +++--- .../languages/typescript.js.twig | 71 +++++++--- templates/web/src/models.ts.twig | 2 +- 8 files changed, 222 insertions(+), 133 deletions(-) rename templates/cli/lib/type-generation/{attributes.js.twig => attribute.js.twig} (100%) create mode 100644 templates/cli/lib/type-generation/collection.js.twig diff --git a/src/SDK/Language/CLI.php b/src/SDK/Language/CLI.php index 9d3e063e8..2c9826a73 100644 --- a/src/SDK/Language/CLI.php +++ b/src/SDK/Language/CLI.php @@ -124,8 +124,13 @@ public function getFiles(): array ], [ 'scope' => 'default', - 'destination' => 'lib/type-generation/attributes.js', - 'template' => 'cli/lib/type-generation/attributes.js.twig', + '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', diff --git a/templates/cli/lib/commands/types.js.twig b/templates/cli/lib/commands/types.js.twig index 177120b24..d14d60fac 100644 --- a/templates/cli/lib/commands/types.js.twig +++ b/templates/cli/lib/commands/types.js.twig @@ -1,12 +1,40 @@ -const { PHP } = require("../type-generation/languages/php"); -const { TypeScript } = require("../type-generation/languages/typescript"); - +const ejs = require("ejs"); const fs = require("fs"); const path = require("path"); -const { Command, Option } = require("commander"); +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 ejs = require("ejs"); +const { PHP } = require("../type-generation/languages/php"); +const { TypeScript } = require("../type-generation/languages/typescript"); + +/** + * @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(); + default: + throw new Error(`Unknown language: ${language}`); + } +} + +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 ", @@ -15,62 +43,69 @@ const typesLanguageOption = new Option( .choices(["ts", "php"]) .default("auto"); -const typesOutputOption = new Option( - "-o, --output ", - "The output path of the types" -).default("auto"); - -const languages = { - ts: new TypeScript(), - php: new PHP(), -}; - -function detectLanguage() { - if (fs.existsSync(path.join(process.cwd(), "package.json"))) { - return "ts"; - } - if (fs.existsSync(path.join(process.cwd(), "composer.json"))) { - return "php"; - } - throw new Error("Could not detect language, please specify with -l"); -} - -const typesCommand = actionRunner(async (options) => { - let { language, output } = options; - +const typesCommand = actionRunner(async (rawOutputDirectory, {language}) => { if (language === "auto") { language = detectLanguage(); log(`Detected language: ${language}`); } - const generator = languages[language]; + const meta = createLanguageMeta(language); - if (output === "auto") { - output = generator.getOutputPath(); - log(`Using default output path: ${output}`); + const outputDirectory = path.resolve(rawOutputDirectory); + if (!fs.existsSync(outputDirectory)) { + log(`Directory: ${outputDirectory} does not exist, creating...`); + fs.mkdirSync(outputDirectory, { recursive: true }); } - const collections = localConfig.getCollections(); - - const templateContent = generator.getTemplate(); - const templater = ejs.compile(templateContent); - const typesContent = templater({ collections }); - - const outputDir = path.dirname(output); - if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); + if (!fs.statSync("appwrite.json").isFile()) { + throw new Error("appwrite.json not found in current directory"); } - fs.writeFileSync(output, typesContent); - log(`Types written to ${output}`); + 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) - .addOption(typesOutputOption) .action(actionRunner(typesCommand)); module.exports = { types }; diff --git a/templates/cli/lib/type-generation/attributes.js.twig b/templates/cli/lib/type-generation/attribute.js.twig similarity index 100% rename from templates/cli/lib/type-generation/attributes.js.twig rename to templates/cli/lib/type-generation/attribute.js.twig 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/language.js.twig b/templates/cli/lib/type-generation/languages/language.js.twig index 53e7829a8..86b758349 100644 --- a/templates/cli/lib/type-generation/languages/language.js.twig +++ b/templates/cli/lib/type-generation/languages/language.js.twig @@ -1,63 +1,43 @@ -/** @typedef {import('../attributes').Attribute} Attribute */ +/** @typedef {import('../attribute').Attribute} Attribute */ +/** @typedef {import('../collection').Collection} Collection */ -class Language { +const fs = require("fs"); +const path = require("path"); + +class LanguageMeta { constructor() { - if (new.target === Language) { + if (new.target === LanguageMeta) { throw new TypeError("Abstract classes can't be instantiated."); } } - /** - * @param {string} string - * @returns {string} - */ static toKebabCase(string) { return string - .split("") - .map((letter) => { - if (/[A-Z]/.test(letter)) { - return ` ${letter.toLowerCase()}`; - } - return letter; - }) - .join("") - .trim() - .replace(/[_\s]+/g, "-"); + .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(); } - /** - * @param {string} string - * @returns {string} - */ static toSnakeCase(string) { return this.toKebabCase(string).replace(/-/g, "_"); } - /** - * @param {string} string - * @returns {string} - */ static toUpperSnakeCase(string) { return this.toSnakeCase(string).toUpperCase(); } - /** - * @param {string} string - * @returns {string} - */ - static toPascalCase(string) { - return this.toKebabCase(string) - .split("-") - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(""); + static toCamelCase(string) { + return this.toKebabCase(string).replace(/-([a-z])/g, (g) => + g[1].toUpperCase() + ); } - /** - * @param {string} string - * @returns {string} - */ - static toCamelCase(string) { - return this.toPascalCase(string).charAt(0).toLowerCase() + string.slice(1); + static toPascalCase(string) { + return this.toCamelCase(string).replace(/^./, (g) => g.toUpperCase()); } /** @@ -67,29 +47,56 @@ class Language { * @param {Attribute} attribute * @return {string} */ - static getType(attribute) { + getType(attribute) { throw new TypeError("Stub."); } + /** + * Should types be generated in a single file? + * + * @returns {boolean} + */ + isSingleFile() { + return false; + } + /** * Get the EJS template used to generate the types for this language. - * + * * @abstract * @returns {string} */ - static getTemplate() { + getTemplate() { throw new TypeError("Stub."); } /** * Get the file extension used by files of this language. - * + * * @abstract + * @param {Collection|undefined} collection * @returns {string} */ - static getOutputPath() { + getFileName(collection) { throw new TypeError("Stub."); } } -module.exports = { Language }; \ No newline at end of file +/** + * @returns {string} + */ +function detectLanguage() { + if ( + fs.existsSync(path.join(process.cwd(), "package.json")) || + fs.existsSync(path.join(process.cwd(), "tsconfig.json")) || + fs.existsSync(path.join(process.cwd(), "deno.json")) + ) { + return "ts"; + } + if (fs.existsSync(path.join(process.cwd(), "composer.json"))) { + return "php"; + } + 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 index 31f9c22d7..1459d7002 100644 --- a/templates/cli/lib/type-generation/languages/php.js.twig +++ b/templates/cli/lib/type-generation/languages/php.js.twig @@ -1,12 +1,9 @@ -/** @typedef {import('../attributes').Attribute} Attribute */ -const { AttributeType } = require('../attributes'); -const { Language } = require("./language"); - -class PHP extends Language { - /** - * @param {Attribute} attribute - */ - static getType(attribute) { +/** @typedef {import('../attribute').Attribute} Attribute */ +const { AttributeType } = require('../attribute'); +const { LanguageMeta } = require("./language"); + +class PHP extends LanguageMeta { + getType(attribute) { let type = "" switch (attribute.type) { case AttributeType.STRING: @@ -38,22 +35,23 @@ class PHP extends Language { return type; } - static getTemplate() { + + + getTemplate() { return ` -class <%= toPascalCase(collection.key) %> extends Document { - <% for (const attribute of collection.attributes ){ %> - public <%= toCamelCase(attribute.key) %>: <%= getType(attribute) %>; - <% } %> -} +use Appwrite\\Models\\Document; -<% } %>`; +class <%- toPascalCase(collection.name) %> extends Document { + <%_ for (const attribute of collection.attributes ){ -%> + public <%- toCamelCase(attribute.key) %>: <%- getType(attribute) %>; + <%_ } %> +}`; } - static getOutputPath() { - return "./generated/Appwrite.php"; + getFileName(collection) { + return LanguageMeta.toPascalCase(collection.name) + ".php"; } } diff --git a/templates/cli/lib/type-generation/languages/typescript.js.twig b/templates/cli/lib/type-generation/languages/typescript.js.twig index 8e16c717c..df8b43433 100644 --- a/templates/cli/lib/type-generation/languages/typescript.js.twig +++ b/templates/cli/lib/type-generation/languages/typescript.js.twig @@ -1,12 +1,12 @@ -/** @typedef {import('../attributes').Attribute} Attribute */ -const { AttributeType } = require('../attributes'); -const { Language } = require("./language"); - -class TypeScript extends Language { - /** - * @param {Attribute} attribute - */ - static getType(attribute) { +/** @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: @@ -33,25 +33,56 @@ class TypeScript extends Language { type += "[]"; } if (attribute.required) { - type += "|null"; + type += " | null"; } return type; } - static getTemplate() { - return ` -<% for (const collection of collections.attributes ){ %> -export type <%= toPascalCase(collection.key) %> = { - <% for (const attribute of collection.attributes) { %> - <%= toCamelCase(attribute.key) %>: <%= getType(attribute) %>; - <% } %> + isSingleFile() { + return true; + } + + _usingNodeAppwrite() { + if (fs.existsSync(path.resolve(process.cwd(), 'package.json'))) { + + return + } + if (fs.existsSync(path.resolve(process.cwd(), 'deno.json'))) { + const denoJsonRaw = fs.readFileSync(path.resolve(process.cwd(), 'deno.json')); + const denoJson = JSON.parse(denoJsonRaw.toString('utf-8')); + return denoJson.dependencies['appwrite'] ? true : false; + } + } + + _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) %>; + <%_ } -%> } -<% } %>`; +<% } -%>`; } - static getOutputPath() { - return "./generated/appwrite.d.ts"; + getFileName(_) { + return "appwrite.d.ts"; } } 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 %} } From ec07a0991348da88c70729e271c6acd7a35e2bfb Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:47:48 +0100 Subject: [PATCH 06/17] feat: python --- src/SDK/Language/CLI.php | 5 ++ templates/cli/lib/commands/types.js.twig | 3 + .../lib/type-generation/languages/php.js.twig | 2 - .../type-generation/languages/python.js.twig | 58 +++++++++++++++++++ .../languages/typescript.js.twig | 12 ---- 5 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 templates/cli/lib/type-generation/languages/python.js.twig diff --git a/src/SDK/Language/CLI.php b/src/SDK/Language/CLI.php index 2c9826a73..dfc6e0609 100644 --- a/src/SDK/Language/CLI.php +++ b/src/SDK/Language/CLI.php @@ -147,6 +147,11 @@ public function getFiles(): array '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/questions.js', diff --git a/templates/cli/lib/commands/types.js.twig b/templates/cli/lib/commands/types.js.twig index d14d60fac..58a6f6aa4 100644 --- a/templates/cli/lib/commands/types.js.twig +++ b/templates/cli/lib/commands/types.js.twig @@ -7,6 +7,7 @@ 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"); /** * @param {string} language @@ -18,6 +19,8 @@ function createLanguageMeta(language) { return new TypeScript(); case "php": return new PHP(); + case "python": + return new Python(); default: throw new Error(`Unknown language: ${language}`); } diff --git a/templates/cli/lib/type-generation/languages/php.js.twig b/templates/cli/lib/type-generation/languages/php.js.twig index 1459d7002..b1b486858 100644 --- a/templates/cli/lib/type-generation/languages/php.js.twig +++ b/templates/cli/lib/type-generation/languages/php.js.twig @@ -35,8 +35,6 @@ class PHP extends LanguageMeta { return type; } - - getTemplate() { return ` +class <%= toPascalCase(collection.name) %>(typing.TypedDict): + <% for (const attribute of collection.attributes) { -%> + <%= attribute.name %>: <%= getType(attribute) %> + <% } -%> +<% } -%>`; + } + + getFileName(_) { + return "collections.py"; + } +} + +module.exports = { Python }; \ 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 index df8b43433..98be0485a 100644 --- a/templates/cli/lib/type-generation/languages/typescript.js.twig +++ b/templates/cli/lib/type-generation/languages/typescript.js.twig @@ -42,18 +42,6 @@ class TypeScript extends LanguageMeta { return true; } - _usingNodeAppwrite() { - if (fs.existsSync(path.resolve(process.cwd(), 'package.json'))) { - - return - } - if (fs.existsSync(path.resolve(process.cwd(), 'deno.json'))) { - const denoJsonRaw = fs.readFileSync(path.resolve(process.cwd(), 'deno.json')); - const denoJson = JSON.parse(denoJsonRaw.toString('utf-8')); - return denoJson.dependencies['appwrite'] ? true : false; - } - } - _getAppwriteDependency() { if (fs.existsSync(path.resolve(process.cwd(), 'package.json'))) { const packageJsonRaw = fs.readFileSync(path.resolve(process.cwd(), 'package.json')); From 0163bfa2bac7682175569120c91692d6ed8afc59 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:49:10 +0100 Subject: [PATCH 07/17] chore: fmt fixes --- templates/cli/lib/type-generation/languages/php.js.twig | 2 +- templates/cli/lib/type-generation/languages/python.js.twig | 2 +- templates/cli/lib/type-generation/languages/typescript.js.twig | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/cli/lib/type-generation/languages/php.js.twig b/templates/cli/lib/type-generation/languages/php.js.twig index b1b486858..56d15b11a 100644 --- a/templates/cli/lib/type-generation/languages/php.js.twig +++ b/templates/cli/lib/type-generation/languages/php.js.twig @@ -43,7 +43,7 @@ use Appwrite\\Models\\Document; class <%- toPascalCase(collection.name) %> extends Document { <%_ for (const attribute of collection.attributes ){ -%> - public <%- toCamelCase(attribute.key) %>: <%- getType(attribute) %>; + public <%= toCamelCase(attribute.key) %>: <%= getType(attribute) %>; <%_ } %> }`; } diff --git a/templates/cli/lib/type-generation/languages/python.js.twig b/templates/cli/lib/type-generation/languages/python.js.twig index 91710117c..2f5e5727a 100644 --- a/templates/cli/lib/type-generation/languages/python.js.twig +++ b/templates/cli/lib/type-generation/languages/python.js.twig @@ -44,7 +44,7 @@ class Python extends LanguageMeta { <% for (const collection of collections) { -%> class <%= toPascalCase(collection.name) %>(typing.TypedDict): - <% for (const attribute of collection.attributes) { -%> + <%_ for (const attribute of collection.attributes) { -%> <%= attribute.name %>: <%= getType(attribute) %> <% } -%> <% } -%>`; diff --git a/templates/cli/lib/type-generation/languages/typescript.js.twig b/templates/cli/lib/type-generation/languages/typescript.js.twig index 98be0485a..82a2172f8 100644 --- a/templates/cli/lib/type-generation/languages/typescript.js.twig +++ b/templates/cli/lib/type-generation/languages/typescript.js.twig @@ -50,7 +50,7 @@ class TypeScript extends LanguageMeta { } if (fs.existsSync(path.resolve(process.cwd(), 'deno.json'))) { - return "https://deno.land/x/appwrite/mod.ts" + return "https://deno.land/x/appwrite/mod.ts"; } return "appwrite"; From 29cbd6bc25c6d3815e6c760b165da0636b0ad761 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:53:29 +0100 Subject: [PATCH 08/17] fix: php, python --- templates/cli/lib/commands/types.js.twig | 2 +- templates/cli/lib/type-generation/languages/php.js.twig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/cli/lib/commands/types.js.twig b/templates/cli/lib/commands/types.js.twig index 58a6f6aa4..dee1b0080 100644 --- a/templates/cli/lib/commands/types.js.twig +++ b/templates/cli/lib/commands/types.js.twig @@ -43,7 +43,7 @@ const typesLanguageOption = new Option( "-l, --language ", "The language of the types" ) - .choices(["ts", "php"]) + .choices(["ts", "php", "python"]) .default("auto"); const typesCommand = actionRunner(async (rawOutputDirectory, {language}) => { diff --git a/templates/cli/lib/type-generation/languages/php.js.twig b/templates/cli/lib/type-generation/languages/php.js.twig index 56d15b11a..2e9797391 100644 --- a/templates/cli/lib/type-generation/languages/php.js.twig +++ b/templates/cli/lib/type-generation/languages/php.js.twig @@ -43,7 +43,7 @@ use Appwrite\\Models\\Document; class <%- toPascalCase(collection.name) %> extends Document { <%_ for (const attribute of collection.attributes ){ -%> - public <%= toCamelCase(attribute.key) %>: <%= getType(attribute) %>; + public <%= getType(attribute) %> $<%= toCamelCase(attribute.key) %>; <%_ } %> }`; } From 08689e1b1c585d2c69f3a6c06ede078d744aea41 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 7 Mar 2024 16:00:18 +0100 Subject: [PATCH 09/17] fix: python, php --- templates/cli/lib/type-generation/languages/php.js.twig | 3 ++- templates/cli/lib/type-generation/languages/python.js.twig | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/templates/cli/lib/type-generation/languages/php.js.twig b/templates/cli/lib/type-generation/languages/php.js.twig index 2e9797391..f6d9f15e8 100644 --- a/templates/cli/lib/type-generation/languages/php.js.twig +++ b/templates/cli/lib/type-generation/languages/php.js.twig @@ -44,7 +44,8 @@ use Appwrite\\Models\\Document; class <%- toPascalCase(collection.name) %> extends Document { <%_ for (const attribute of collection.attributes ){ -%> public <%= getType(attribute) %> $<%= toCamelCase(attribute.key) %>; - <%_ } %> + + <%_ } %-> }`; } diff --git a/templates/cli/lib/type-generation/languages/python.js.twig b/templates/cli/lib/type-generation/languages/python.js.twig index 2f5e5727a..a5c76021c 100644 --- a/templates/cli/lib/type-generation/languages/python.js.twig +++ b/templates/cli/lib/type-generation/languages/python.js.twig @@ -45,7 +45,7 @@ class Python extends LanguageMeta { <% for (const collection of collections) { -%> class <%= toPascalCase(collection.name) %>(typing.TypedDict): <%_ for (const attribute of collection.attributes) { -%> - <%= attribute.name %>: <%= getType(attribute) %> + <%= toSnakeCase(attribute.key) %>: <%= getType(attribute) %> <% } -%> <% } -%>`; } From f5ce7c471efc99a55181b07e0b2d85e61b655eca Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 7 Mar 2024 16:04:18 +0100 Subject: [PATCH 10/17] fix: python formatting --- templates/cli/lib/type-generation/languages/python.js.twig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/templates/cli/lib/type-generation/languages/python.js.twig b/templates/cli/lib/type-generation/languages/python.js.twig index a5c76021c..94f9e524f 100644 --- a/templates/cli/lib/type-generation/languages/python.js.twig +++ b/templates/cli/lib/type-generation/languages/python.js.twig @@ -42,11 +42,13 @@ class Python extends LanguageMeta { 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) %> - <% } -%> + <%_ } -%> + <% } -%>`; } From fe9ed0e7ba289eaa36515ef8fa23bda418f301d5 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 7 Mar 2024 16:12:47 +0100 Subject: [PATCH 11/17] fix: formatting --- .../cli/lib/type-generation/languages/php.js.twig | 6 +++--- .../cli/lib/type-generation/languages/python.js.twig | 12 ++++++------ .../lib/type-generation/languages/typescript.js.twig | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/templates/cli/lib/type-generation/languages/php.js.twig b/templates/cli/lib/type-generation/languages/php.js.twig index f6d9f15e8..88ba60d80 100644 --- a/templates/cli/lib/type-generation/languages/php.js.twig +++ b/templates/cli/lib/type-generation/languages/php.js.twig @@ -42,10 +42,10 @@ namespace Appwrite\\Models; use Appwrite\\Models\\Document; class <%- toPascalCase(collection.name) %> extends Document { - <%_ for (const attribute of collection.attributes ){ -%> - public <%= getType(attribute) %> $<%= toCamelCase(attribute.key) %>; +<% for (const attribute of collection.attributes ){ %> + public <%- getType(attribute) %> $<%- toCamelCase(attribute.key) %>; - <%_ } %-> +<% } %> }`; } diff --git a/templates/cli/lib/type-generation/languages/python.js.twig b/templates/cli/lib/type-generation/languages/python.js.twig index 94f9e524f..e8b69762d 100644 --- a/templates/cli/lib/type-generation/languages/python.js.twig +++ b/templates/cli/lib/type-generation/languages/python.js.twig @@ -43,13 +43,13 @@ class Python extends LanguageMeta { 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) %> - <%_ } -%> +<% for (const collection of collections) { %> +class <%- toPascalCase(collection.name) %>(typing.TypedDict): +<% for (const attribute of collection.attributes) { %> + <%- toSnakeCase(attribute.key) %>: <%- getType(attribute) %> -<% } -%>`; +<% } %> +<% } %>`; } getFileName(_) { diff --git a/templates/cli/lib/type-generation/languages/typescript.js.twig b/templates/cli/lib/type-generation/languages/typescript.js.twig index 82a2172f8..77a862b66 100644 --- a/templates/cli/lib/type-generation/languages/typescript.js.twig +++ b/templates/cli/lib/type-generation/languages/typescript.js.twig @@ -59,11 +59,11 @@ class TypeScript extends LanguageMeta { getTemplate() { return `import { Models } from '${this._getAppwriteDependency()}'; -<% for (const collection of collections) { -%> +<% for (const collection of collections) { %> export type <%- toPascalCase(collection.name) %> = Models.Document & { - <%_ for (const attribute of collection.attributes) { -%> +<% for (const attribute of collection.attributes) { %> <%- toCamelCase(attribute.key) %>: <%- getType(attribute) %>; - <%_ } -%> +<% } %> } <% } -%>`; From d3c471fc8602bc4431787a6af5051393755a021b Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 7 Mar 2024 16:16:39 +0100 Subject: [PATCH 12/17] fix: formatting --- templates/cli/lib/type-generation/languages/php.js.twig | 1 - templates/cli/lib/type-generation/languages/python.js.twig | 1 - .../cli/lib/type-generation/languages/typescript.js.twig | 4 ++-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/templates/cli/lib/type-generation/languages/php.js.twig b/templates/cli/lib/type-generation/languages/php.js.twig index 88ba60d80..cd6aa3a08 100644 --- a/templates/cli/lib/type-generation/languages/php.js.twig +++ b/templates/cli/lib/type-generation/languages/php.js.twig @@ -44,7 +44,6 @@ use Appwrite\\Models\\Document; class <%- toPascalCase(collection.name) %> extends Document { <% for (const attribute of collection.attributes ){ %> public <%- getType(attribute) %> $<%- toCamelCase(attribute.key) %>; - <% } %> }`; } diff --git a/templates/cli/lib/type-generation/languages/python.js.twig b/templates/cli/lib/type-generation/languages/python.js.twig index e8b69762d..e9e39044f 100644 --- a/templates/cli/lib/type-generation/languages/python.js.twig +++ b/templates/cli/lib/type-generation/languages/python.js.twig @@ -47,7 +47,6 @@ class Python extends LanguageMeta { class <%- toPascalCase(collection.name) %>(typing.TypedDict): <% for (const attribute of collection.attributes) { %> <%- toSnakeCase(attribute.key) %>: <%- getType(attribute) %> - <% } %> <% } %>`; } diff --git a/templates/cli/lib/type-generation/languages/typescript.js.twig b/templates/cli/lib/type-generation/languages/typescript.js.twig index 77a862b66..6bf002333 100644 --- a/templates/cli/lib/type-generation/languages/typescript.js.twig +++ b/templates/cli/lib/type-generation/languages/typescript.js.twig @@ -61,9 +61,9 @@ class TypeScript extends LanguageMeta { <% for (const collection of collections) { %> export type <%- toPascalCase(collection.name) %> = Models.Document & { -<% for (const attribute of collection.attributes) { %> +<% for (const attribute of collection.attributes) { %> <%- toCamelCase(attribute.key) %>: <%- getType(attribute) %>; -<% } %> +<% } -%> } <% } -%>`; From 731dcf8fe9dbe037ae4fe4e889b1883d9c339bb1 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 7 Mar 2024 16:22:59 +0100 Subject: [PATCH 13/17] fix: formatting 2 --- templates/cli/lib/type-generation/languages/php.js.twig | 2 +- templates/cli/lib/type-generation/languages/python.js.twig | 5 +++-- .../cli/lib/type-generation/languages/typescript.js.twig | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/templates/cli/lib/type-generation/languages/php.js.twig b/templates/cli/lib/type-generation/languages/php.js.twig index cd6aa3a08..ec8f75a70 100644 --- a/templates/cli/lib/type-generation/languages/php.js.twig +++ b/templates/cli/lib/type-generation/languages/php.js.twig @@ -42,7 +42,7 @@ namespace Appwrite\\Models; use Appwrite\\Models\\Document; class <%- toPascalCase(collection.name) %> extends Document { -<% for (const attribute of collection.attributes ){ %> +<% for (const attribute of collection.attributes ){ -%> public <%- getType(attribute) %> $<%- toCamelCase(attribute.key) %>; <% } %> }`; diff --git a/templates/cli/lib/type-generation/languages/python.js.twig b/templates/cli/lib/type-generation/languages/python.js.twig index e9e39044f..d092dab09 100644 --- a/templates/cli/lib/type-generation/languages/python.js.twig +++ b/templates/cli/lib/type-generation/languages/python.js.twig @@ -45,9 +45,10 @@ class Python extends LanguageMeta { <% for (const collection of collections) { %> class <%- toPascalCase(collection.name) %>(typing.TypedDict): -<% for (const attribute of collection.attributes) { %> +<% for (const attribute of collection.attributes) { -%> <%- toSnakeCase(attribute.key) %>: <%- getType(attribute) %> -<% } %> +<% } -%> + <% } %>`; } diff --git a/templates/cli/lib/type-generation/languages/typescript.js.twig b/templates/cli/lib/type-generation/languages/typescript.js.twig index 6bf002333..b78d4570a 100644 --- a/templates/cli/lib/type-generation/languages/typescript.js.twig +++ b/templates/cli/lib/type-generation/languages/typescript.js.twig @@ -61,12 +61,12 @@ class TypeScript extends LanguageMeta { <% for (const collection of collections) { %> export type <%- toPascalCase(collection.name) %> = Models.Document & { -<% for (const attribute of collection.attributes) { %> +<% for (const attribute of collection.attributes) { -%> <%- toCamelCase(attribute.key) %>: <%- getType(attribute) %>; <% } -%> } -<% } -%>`; +<% } %>`; } getFileName(_) { From 6f067aa766688a02c306d10942945a1044d5b87f Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 7 Mar 2024 19:34:15 +0100 Subject: [PATCH 14/17] feat: swift, kotlin --- src/SDK/Language/CLI.php | 10 ++++ templates/cli/lib/commands/types.js.twig | 12 +++- .../type-generation/languages/kotlin.js.twig | 53 +++++++++++++++++ .../languages/language.js.twig | 34 ++++++++--- .../lib/type-generation/languages/php.js.twig | 2 +- .../type-generation/languages/python.js.twig | 3 +- .../type-generation/languages/swift.js.twig | 59 +++++++++++++++++++ .../languages/typescript.js.twig | 2 - 8 files changed, 160 insertions(+), 15 deletions(-) create mode 100644 templates/cli/lib/type-generation/languages/kotlin.js.twig create mode 100644 templates/cli/lib/type-generation/languages/swift.js.twig diff --git a/src/SDK/Language/CLI.php b/src/SDK/Language/CLI.php index dfc6e0609..e8de4555d 100644 --- a/src/SDK/Language/CLI.php +++ b/src/SDK/Language/CLI.php @@ -152,6 +152,16 @@ public function getFiles(): array '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/questions.js', diff --git a/templates/cli/lib/commands/types.js.twig b/templates/cli/lib/commands/types.js.twig index dee1b0080..5643927ff 100644 --- a/templates/cli/lib/commands/types.js.twig +++ b/templates/cli/lib/commands/types.js.twig @@ -8,6 +8,8 @@ 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"); /** * @param {string} language @@ -21,8 +23,12 @@ function createLanguageMeta(language) { return new PHP(); case "python": return new Python(); + case "kotlin": + return new Kotlin(); + case "swift": + return new Swift(); default: - throw new Error(`Unknown language: ${language}`); + throw new Error(`Language '${language}' is not supported`); } } @@ -43,7 +49,7 @@ const typesLanguageOption = new Option( "-l, --language ", "The language of the types" ) - .choices(["ts", "php", "python"]) + .choices(["ts", "php", "python", "kotlin", "swift"]) .default("auto"); const typesCommand = actionRunner(async (rawOutputDirectory, {language}) => { @@ -60,7 +66,7 @@ const typesCommand = actionRunner(async (rawOutputDirectory, {language}) => { fs.mkdirSync(outputDirectory, { recursive: true }); } - if (!fs.statSync("appwrite.json").isFile()) { + if (!fs.existsSync("appwrite.json")) { throw new Error("appwrite.json not found in current directory"); } 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 index 86b758349..2b6c93d65 100644 --- a/templates/cli/lib/type-generation/languages/language.js.twig +++ b/templates/cli/lib/type-generation/languages/language.js.twig @@ -52,7 +52,7 @@ class LanguageMeta { } /** - * Should types be generated in a single file? + * Returns true if the language uses a single file for all types. * * @returns {boolean} */ @@ -82,20 +82,40 @@ class LanguageMeta { } } +const existsFiles = (...files) => + files.some((file) => fs.existsSync(path.join(process.cwd(), file))); + /** * @returns {string} */ function detectLanguage() { - if ( - fs.existsSync(path.join(process.cwd(), "package.json")) || - fs.existsSync(path.join(process.cwd(), "tsconfig.json")) || - fs.existsSync(path.join(process.cwd(), "deno.json")) - ) { + if (existsFiles("package.json", "tsconfig.json", "deno.json")) { return "ts"; } - if (fs.existsSync(path.join(process.cwd(), "composer.json"))) { + 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"); } diff --git a/templates/cli/lib/type-generation/languages/php.js.twig b/templates/cli/lib/type-generation/languages/php.js.twig index ec8f75a70..01a7bf930 100644 --- a/templates/cli/lib/type-generation/languages/php.js.twig +++ b/templates/cli/lib/type-generation/languages/php.js.twig @@ -44,7 +44,7 @@ use Appwrite\\Models\\Document; class <%- toPascalCase(collection.name) %> extends Document { <% for (const attribute of collection.attributes ){ -%> public <%- getType(attribute) %> $<%- toCamelCase(attribute.key) %>; -<% } %> +<% } -%> }`; } diff --git a/templates/cli/lib/type-generation/languages/python.js.twig b/templates/cli/lib/type-generation/languages/python.js.twig index d092dab09..f71f65e77 100644 --- a/templates/cli/lib/type-generation/languages/python.js.twig +++ b/templates/cli/lib/type-generation/languages/python.js.twig @@ -42,14 +42,13 @@ class Python extends LanguageMeta { 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(_) { 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 index b78d4570a..69e738bf2 100644 --- a/templates/cli/lib/type-generation/languages/typescript.js.twig +++ b/templates/cli/lib/type-generation/languages/typescript.js.twig @@ -58,14 +58,12 @@ class TypeScript extends LanguageMeta { 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) %>; <% } -%> } - <% } %>`; } From d3c2134b04a62b0e87c26d612bce38786eadc9b3 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 8 Mar 2024 11:01:01 +0100 Subject: [PATCH 15/17] feat: java --- src/SDK/Language/CLI.php | 5 ++ templates/cli/lib/commands/types.js.twig | 5 +- .../type-generation/languages/java.js.twig | 55 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 templates/cli/lib/type-generation/languages/java.js.twig diff --git a/src/SDK/Language/CLI.php b/src/SDK/Language/CLI.php index e8de4555d..5c6abdc36 100644 --- a/src/SDK/Language/CLI.php +++ b/src/SDK/Language/CLI.php @@ -162,6 +162,11 @@ public function getFiles(): array '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', diff --git a/templates/cli/lib/commands/types.js.twig b/templates/cli/lib/commands/types.js.twig index 5643927ff..d7c1b4efc 100644 --- a/templates/cli/lib/commands/types.js.twig +++ b/templates/cli/lib/commands/types.js.twig @@ -10,6 +10,7 @@ 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 @@ -27,6 +28,8 @@ function createLanguageMeta(language) { return new Kotlin(); case "swift": return new Swift(); + case "java": + return new Java(); default: throw new Error(`Language '${language}' is not supported`); } @@ -49,7 +52,7 @@ const typesLanguageOption = new Option( "-l, --language ", "The language of the types" ) - .choices(["ts", "php", "python", "kotlin", "swift"]) + .choices(["ts", "php", "python", "kotlin", "swift", "java"]) .default("auto"); const typesCommand = actionRunner(async (rawOutputDirectory, {language}) => { 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 From 69e401f2e6014b0117cd3a07f98c59c8a2d39967 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 8 Mar 2024 11:54:02 +0100 Subject: [PATCH 16/17] feat: php fix --- templates/cli/lib/type-generation/languages/php.js.twig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/cli/lib/type-generation/languages/php.js.twig b/templates/cli/lib/type-generation/languages/php.js.twig index 01a7bf930..bb5074e24 100644 --- a/templates/cli/lib/type-generation/languages/php.js.twig +++ b/templates/cli/lib/type-generation/languages/php.js.twig @@ -4,6 +4,9 @@ const { LanguageMeta } = require("./language"); class PHP extends LanguageMeta { getType(attribute) { + if (attribute.array) { + return "array"; + } let type = "" switch (attribute.type) { case AttributeType.STRING: @@ -26,9 +29,6 @@ class PHP extends LanguageMeta { default: throw new Error(`Unknown attribute type: ${attribute.type}`); } - if (attribute.array) { - type += "[]"; - } if (attribute.required) { type += "|null"; } From 61e475dd3b0a2898d2e74bc63b7a8ffd8090f7b5 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Sat, 9 Mar 2024 11:07:31 +0100 Subject: [PATCH 17/17] feat: python models --- src/SDK/Language/Python.php | 70 ++++++++++++++++++- templates/python/package/models/model.py.twig | 37 ++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 templates/python/package/models/model.py.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/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