Skip to content

fix: type generation templates to include relative path and strict flag #1112

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jul 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions templates/cli/lib/commands/types.js.twig
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ 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 { success, log, warn, actionRunner } = require("../parser");
const { PHP } = require("../type-generation/languages/php");
const { TypeScript } = require("../type-generation/languages/typescript");
const { Kotlin } = require("../type-generation/languages/kotlin");
Expand Down Expand Up @@ -71,7 +71,7 @@ const typesCommand = actionRunner(async (rawOutputDirectory, {language, strict})
}

if (strict) {
log(`Strict mode enabled: Field names will be converted to follow ${language} conventions`);
warn(`Strict mode enabled: Field names will be converted to follow ${language} conventions`);
}

const meta = createLanguageMeta(language);
Expand Down Expand Up @@ -118,7 +118,7 @@ const typesCommand = actionRunner(async (rawOutputDirectory, {language, strict})
collections,
strict,
...templateHelpers,
getType: meta.getType
getType: meta.getType,
});

const destination = singleFileDestination || path.join(outputDirectory, meta.getFileName());
Expand All @@ -128,10 +128,11 @@ const typesCommand = actionRunner(async (rawOutputDirectory, {language, strict})
} else {
for (const collection of collections) {
const content = templater({
collections,
collection,
strict,
...templateHelpers,
getType: meta.getType
getType: meta.getType,
});

const destination = path.join(outputDirectory, meta.getFileName(collection));
Expand Down
26 changes: 13 additions & 13 deletions templates/cli/lib/type-generation/languages/dart.js.twig
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const path = require('path');

class Dart extends LanguageMeta {
getPackageName() {
const pubspecPath = path.join(this.getCurrentDirectory(), 'pubspec.yaml');
const pubspecPath = path.join(process.cwd(), 'pubspec.yaml');
if (fs.existsSync(pubspecPath)) {
const pubspecContent = fs.readFileSync(pubspecPath, 'utf8');
const lines = pubspecContent.split('\n');
Expand Down Expand Up @@ -40,7 +40,7 @@ class Dart extends LanguageMeta {
return 'appwrite';
}

getType(attribute) {
getType(attribute, collections) {
let type = "";
switch (attribute.type) {
case AttributeType.STRING:
Expand All @@ -61,7 +61,11 @@ class Dart extends LanguageMeta {
type = "bool";
break;
case AttributeType.RELATIONSHIP:
type = LanguageMeta.toPascalCase(attribute.relatedCollection);
const relatedCollection = collections.find(c => c.$id === attribute.relatedCollection);
if (!relatedCollection) {
throw new Error(`Related collection with ID '${attribute.relatedCollection}' not found.`);
}
type = LanguageMeta.toPascalCase(relatedCollection.name);
if ((attribute.relationType === 'oneToMany' && attribute.side === 'parent') || (attribute.relationType === 'manyToOne' && attribute.side === 'child') || attribute.relationType === 'manyToMany') {
type = `List<${type}>`;
}
Expand All @@ -78,19 +82,15 @@ class Dart extends LanguageMeta {
return type;
}

getCurrentDirectory() {
return process.cwd();
}

getTemplate() {
return `<% for (const attribute of collection.attributes) { -%>
<% if (attribute.type === 'relationship') { -%>
import '<%- toSnakeCase(attribute.relatedCollection) %>.dart';
import '<%- toSnakeCase(collections.find(c => c.$id === attribute.relatedCollection).name) %>.dart';

<% } -%>
<% } -%>
/// This file is auto-generated by the Appwrite CLI.
/// You can regenerate it by running \`appwrite types -l dart ${this.getCurrentDirectory()}\`.
/// You can regenerate it by running \`appwrite ${process.argv.slice(2).join(' ')}\`.

<% for (const attribute of collection.attributes) { -%>
<% if (attribute.format === 'enum') { -%>
Expand All @@ -104,7 +104,7 @@ enum <%- toPascalCase(attribute.key) %> {
<% } -%>
class <%= toPascalCase(collection.name) %> {
<% for (const [index, attribute] of Object.entries(collection.attributes)) { -%>
<%- getType(attribute) %> <%= strict ? toCamelCase(attribute.key) : attribute.key %>;
<%- getType(attribute, collections) %> <%= strict ? toCamelCase(attribute.key) : attribute.key %>;
<% } -%>

<%= toPascalCase(collection.name) %>({
Expand Down Expand Up @@ -152,11 +152,11 @@ map['<%= attribute.key %>']<% if (!attribute.required) { %> ?? null<% } -%>
<% } -%>
<% } else if (attribute.type === 'relationship') { -%>
<% if ((attribute.relationType === 'oneToMany' && attribute.side === 'parent') || (attribute.relationType === 'manyToOne' && attribute.side === 'child') || attribute.relationType === 'manyToMany') { -%>
(map['<%= attribute.key %>'] as List<dynamic>?)?.map((e) => <%- toPascalCase(attribute.relatedCollection) %>.fromMap(e)).toList()<% if (!attribute.required) { %> ?? []<% } -%>
(map['<%= attribute.key %>'] as List<dynamic>?)?.map((e) => <%- toPascalCase(collections.find(c => c.$id === attribute.relatedCollection).name) %>.fromMap(e)).toList()<% if (!attribute.required) { %> ?? []<% } -%>
<% } else { -%>
<% if (!attribute.required) { -%>
map['<%= attribute.key %>'] != null ? <%- toPascalCase(attribute.relatedCollection) %>.fromMap(map['<%= attribute.key %>']) : null<% } else { -%>
<%- toPascalCase(attribute.relatedCollection) %>.fromMap(map['<%= attribute.key %>'])<% } -%>
map['<%= attribute.key %>'] != null ? <%- toPascalCase(collections.find(c => c.$id === attribute.relatedCollection).name) %>.fromMap(map['<%= attribute.key %>']) : null<% } else { -%>
<%- toPascalCase(collections.find(c => c.$id === attribute.relatedCollection).name) %>.fromMap(map['<%= attribute.key %>'])<% } -%>
<% } -%>
<% } -%><% if (index < collection.attributes.length - 1) { %>,<% } %>
<% } -%>
Expand Down
26 changes: 13 additions & 13 deletions templates/cli/lib/type-generation/languages/java.js.twig
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const { AttributeType } = require('../attribute');
const { LanguageMeta } = require("./language");

class Java extends LanguageMeta {
getType(attribute) {
getType(attribute, collections) {
let type = "";
switch (attribute.type) {
case AttributeType.STRING:
Expand All @@ -24,7 +24,11 @@ class Java extends LanguageMeta {
type = "boolean";
break;
case AttributeType.RELATIONSHIP:
type = LanguageMeta.toPascalCase(attribute.relatedCollection);
const relatedCollection = collections.find(c => c.$id === attribute.relatedCollection);
if (!relatedCollection) {
throw new Error(`Related collection with ID '${attribute.relatedCollection}' not found.`);
}
type = LanguageMeta.toPascalCase(relatedCollection.name);
if ((attribute.relationType === 'oneToMany' && attribute.side === 'parent') || (attribute.relationType === 'manyToOne' && attribute.side === 'child') || attribute.relationType === 'manyToMany') {
type = "List<" + type + ">";
}
Expand All @@ -38,25 +42,21 @@ class Java extends LanguageMeta {
return type;
}

getCurrentDirectory() {
return process.cwd();
}

getTemplate() {
return `package io.appwrite.models;

/**
* This file is auto-generated by the Appwrite CLI.
* You can regenerate it by running \`appwrite types -l java ${this.getCurrentDirectory()}\`.
* You can regenerate it by running \`appwrite ${process.argv.slice(2).join(' ')}\`.
*/

import java.util.Objects;
<% for (const attribute of collection.attributes) { -%>
<% if (attribute.type === 'relationship') { -%>
import io.appwrite.models.<%- toPascalCase(attribute.relatedCollection) %>;

import io.appwrite.models.<%- toPascalCase(collections.find(c => c.$id === attribute.relatedCollection).name) %>;
<% } -%>
<% } -%>

public class <%- toPascalCase(collection.name) %> {
<% for (const attribute of collection.attributes) { -%>
<% if (attribute.format === 'enum') { -%>
Expand All @@ -70,15 +70,15 @@ public class <%- toPascalCase(collection.name) %> {
<% } -%>
<% } -%>
<% for (const attribute of collection.attributes) { -%>
private <%- getType(attribute) %> <%- strict ? toCamelCase(attribute.key) : attribute.key %>;
private <%- getType(attribute, collections) %> <%- strict ? toCamelCase(attribute.key) : attribute.key %>;
<% } -%>

public <%- toPascalCase(collection.name) %>() {
}

public <%- toPascalCase(collection.name) %>(
<% for (const [index, attribute] of Object.entries(collection.attributes)) { -%>
<%- getType(attribute) %> <%= strict ? toCamelCase(attribute.key) : attribute.key %><%- index < collection.attributes.length - 1 ? ',' : '' %>
<%- getType(attribute, collections) %> <%= strict ? toCamelCase(attribute.key) : attribute.key %><%- index < collection.attributes.length - 1 ? ',' : '' %>
<% } -%>
) {
<% for (const attribute of collection.attributes) { -%>
Expand All @@ -87,11 +87,11 @@ public class <%- toPascalCase(collection.name) %> {
}

<% for (const attribute of collection.attributes) { -%>
public <%- getType(attribute) %> get<%- toPascalCase(attribute.key) %>() {
public <%- getType(attribute, collections) %> get<%- toPascalCase(attribute.key) %>() {
return <%= strict ? toCamelCase(attribute.key) : attribute.key %>;
}

public void set<%- toPascalCase(attribute.key) %>(<%- getType(attribute) %> <%= strict ? toCamelCase(attribute.key) : attribute.key %>) {
public void set<%- toPascalCase(attribute.key) %>(<%- getType(attribute, collections) %> <%= strict ? toCamelCase(attribute.key) : attribute.key %>) {
this.<%= strict ? toCamelCase(attribute.key) : attribute.key %> = <%= strict ? toCamelCase(attribute.key) : attribute.key %>;
}

Expand Down
28 changes: 14 additions & 14 deletions templates/cli/lib/type-generation/languages/javascript.js.twig
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const { AttributeType } = require('../attribute');
const { LanguageMeta } = require("./language");

class JavaScript extends LanguageMeta {
getType(attribute) {
getType(attribute, collections) {
let type = ""
switch (attribute.type) {
case AttributeType.STRING:
Expand All @@ -29,7 +29,11 @@ class JavaScript extends LanguageMeta {
type = "boolean";
break;
case AttributeType.RELATIONSHIP:
type = LanguageMeta.toPascalCase(attribute.relatedCollection);
const relatedCollection = collections.find(c => c.$id === attribute.relatedCollection);
if (!relatedCollection) {
throw new Error(`Related collection with ID '${attribute.relatedCollection}' not found.`);
}
type = LanguageMeta.toPascalCase(relatedCollection.name);
if ((attribute.relationType === 'oneToMany' && attribute.side === 'parent') || (attribute.relationType === 'manyToOne' && attribute.side === 'child') || attribute.relationType === 'manyToMany') {
type = `${type}[]`;
}
Expand Down Expand Up @@ -60,19 +64,14 @@ class JavaScript extends LanguageMeta {
return "appwrite";
}

getCurrentDirectory() {
return process.cwd();
}

getTemplate() {
return `
// This file is auto-generated by the Appwrite CLI.
// You can regenerate it by running \`appwrite types -l js ${this.getCurrentDirectory()}\`.

/**
return `/**
* @typedef {import('${this._getAppwriteDependency()}').Models.Document} Document
*/

// This file is auto-generated by the Appwrite CLI.
// You can regenerate it by running \`appwrite ${process.argv.slice(2).join(' ')}\`.

<% for (const collection of collections) { -%>
<% for (const attribute of collection.attributes) { -%>
<% if (attribute.format === 'enum') { -%>
Expand All @@ -83,14 +82,15 @@ class JavaScript extends LanguageMeta {
<% } -%>
<% } -%>
<% } -%>
<% for (const collection of collections) { %>/**
<% for (const [index, collection] of Object.entries(collections)) { %>/**
* @typedef {Document & {
<% for (const attribute of collection.attributes) { -%>
* <%- strict ? toCamelCase(attribute.key) : attribute.key %>: <%- getType(attribute) %>;
* <%- strict ? toCamelCase(attribute.key) : attribute.key %>: <%- getType(attribute, collections) %>;
<% } -%>
* }} <%- toPascalCase(collection.name) %>
*/

<% if (index < collections.length - 1) { %>
<% } -%>
<% } %>`;
}

Expand Down
21 changes: 11 additions & 10 deletions templates/cli/lib/type-generation/languages/kotlin.js.twig
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const { AttributeType } = require('../attribute');
const { LanguageMeta } = require("./language");

class Kotlin extends LanguageMeta {
getType(attribute) {
getType(attribute, collections) {
let type = "";
switch (attribute.type) {
case AttributeType.STRING:
Expand All @@ -24,7 +24,11 @@ class Kotlin extends LanguageMeta {
type = "Boolean";
break;
case AttributeType.RELATIONSHIP:
type = LanguageMeta.toPascalCase(attribute.relatedCollection);
const relatedCollection = collections.find(c => c.$id === attribute.relatedCollection);
if (!relatedCollection) {
throw new Error(`Related collection with ID '${attribute.relatedCollection}' not found.`);
}
type = LanguageMeta.toPascalCase(relatedCollection.name);
if ((attribute.relationType === 'oneToMany' && attribute.side === 'parent') || (attribute.relationType === 'manyToOne' && attribute.side === 'child') || attribute.relationType === 'manyToMany') {
type = `List<${type}>`;
}
Expand All @@ -41,22 +45,18 @@ class Kotlin extends LanguageMeta {
return type;
}

getCurrentDirectory() {
return process.cwd();
}

getTemplate() {
return `package io.appwrite.models

<% for (const attribute of collection.attributes) { -%>
<% if (attribute.type === 'relationship') { -%>
import <%- toPascalCase(attribute.relatedCollection) %>
import <%- toPascalCase(collections.find(c => c.$id === attribute.relatedCollection).name) %>

<% } -%>
<% } -%>
/**
* This file is auto-generated by the Appwrite CLI.
* You can regenerate it by running \`appwrite types -l kotlin ${this.getCurrentDirectory()}\`.
* You can regenerate it by running \`appwrite ${process.argv.slice(2).join(' ')}\`.
*/

<% for (const attribute of collection.attributes) { -%>
Expand All @@ -71,9 +71,10 @@ enum class <%- toPascalCase(attribute.key) %> {
<% } -%>
data class <%- toPascalCase(collection.name) %>(
<% for (const [index, attribute] of Object.entries(collection.attributes)) { -%>
val <%- strict ? toCamelCase(attribute.key) : attribute.key %>: <%- getType(attribute) %><% if (index < collection.attributes.length - 1) { %>,<% } %>
val <%- strict ? toCamelCase(attribute.key) : attribute.key %>: <%- getType(attribute, collections) %><% if (index < collection.attributes.length - 1) { %>,<% } %>
<% } -%>
)`;
)
`;
}

getFileName(collection) {
Expand Down
Loading