Skip to content

Conversation

@ChiragAgg5k
Copy link
Member

@ChiragAgg5k ChiragAgg5k commented Jan 16, 2026

Summary by CodeRabbit

  • New Features

    • Added a generate CLI command to produce type-safe SDKs from project schemas
    • Supports automatic language detection or explicit language selection (-l/--language)
    • TypeScript SDK generation with generated types and usage hints; configurable output dir (-o/--output)
  • Refactor

    • Replaced legacy DB command with a generator-based architecture for extensible SDK generation
  • Bug Fixes / Validation

    • Improved configuration validation and schema handling to catch cross-database and attribute issues earlier

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 16, 2026

Warning

Rate limit exceeded

@ChiragAgg5k has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 14 minutes and 32 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 2cf10eb and ff6acdf.

📒 Files selected for processing (1)
  • templates/cli/lib/commands/config.ts

Walkthrough

The PR removes the previous monolithic CLI db.ts generator and adds a new generate CLI command plus a pluggable generator framework. It introduces generator base types, language detection, a TypeScript-specific generator that emits types/databases/index files, validation helpers for config schemas, string utilities, and updates CLI registration and schema types to support relationships and new exports.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: appwrite generate' accurately describes the main change: introducing a new generate command for the Appwrite CLI that can auto-detect project language and generate type-safe SDK files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@templates/cli/lib/commands/db.ts`:
- Around line 452-459: The printed success/log lines (success, console.log, log)
currently list appwrite.ts, appwrite.db.ts, appwrite.types.ts and an import of
"./${outputDir}/appwrite.js" which doesn't match what writeFiles actually
creates (appwrite/index.ts, appwrite/db.ts, appwrite/types.ts); update the
console output to show the real generated paths (e.g., path.join(outputDir,
"index.ts"), path.join(outputDir, "db.ts"), path.join(outputDir, "types.ts"))
and change the import example to reference the directory (e.g., import {
databases } from "./${outputDir}"; or "./${outputDir}/index" without a .js
extension) so displayed file names and import sample reflect the actual output.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3cf7f6f and 127d23b.

📒 Files selected for processing (2)
  • templates/cli/cli.ts.twig
  • templates/cli/lib/commands/db.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: build (8.3, WebNode)
  • GitHub Check: build (8.3, Python311)
  • GitHub Check: build (8.3, Python39)
  • GitHub Check: build (8.3, Python313)
  • GitHub Check: build (8.3, Ruby27)
  • GitHub Check: build (8.3, Python312)
  • GitHub Check: build (8.3, Node18)
  • GitHub Check: build (8.3, DotNet60)
  • GitHub Check: build (8.3, KotlinJava8)
  • GitHub Check: build (8.3, AppleSwift56)
  • GitHub Check: build (8.3, CLIBun13)
  • GitHub Check: build (8.3, Go118)
  • GitHub Check: build (8.3, CLIBun11)
  • GitHub Check: kotlin (server)
  • GitHub Check: build (8.3, Android5Java17)
  • GitHub Check: swift (server)
  • GitHub Check: apple (client)
  • GitHub Check: android (client)
🔇 Additional comments (2)
templates/cli/cli.ts.twig (1)

26-26: No issues found.

Also applies to: 126-126

templates/cli/lib/commands/db.ts (1)

171-180: Quote or sanitize entity names used as property keys.

Line 174 and Line 259 use entity.name as an unquoted key in generated types and runtime objects. If a table/collection name contains spaces, hyphens, or reserved words (which Appwrite names commonly allow), the generated TS/JS will be invalid and fail to compile. Prefer quoting string keys (and update usage docs to bracket access) or sanitize to a safe identifier consistently.

🔧 Suggested fix (quote keys to guarantee valid output)
-        const tableTypes = dbEntities
-          .map((entity) => {
-            const typeName = this.toPascalCase(entity.name);
-            return `    ${entity.name}: {
+        const tableTypes = dbEntities
+          .map((entity) => {
+            const typeName = this.toPascalCase(entity.name);
+            const entityKey = JSON.stringify(entity.name);
+            return `    ${entityKey}: {
       create: (data: Omit<${typeName}, keyof Models.Row>, options?: { rowId?: string; permissions?: string[] }) => Promise<${typeName}>;
       get: (id: string) => Promise<${typeName}>;
       update: (id: string, data: Partial<Omit<${typeName}, keyof Models.Row>>, options?: { permissions?: string[] }) => Promise<${typeName}>;
       delete: (id: string) => Promise<void>;
       list: (queries?: string[]) => Promise<{ total: number; rows: ${typeName}[] }>;
     }`;
           })
-        .map((entity) => {
-          const entityName = entity.name;
-          const typeName = this.toPascalCase(entity.name);
+        .map((entity) => {
+          const entityName = entity.name;
+          const typeName = this.toPascalCase(entity.name);
+          const entityKey = JSON.stringify(entityName);
 
-          return `    ${entityName}: {
+          return `    ${entityKey}: {
       create: (data: Omit<${typeName}, keyof Models.Row>, options?: { rowId?: string; permissions?: string[] }) =>
         tablesDB.createRow<${typeName}>({
           databaseId: '${dbId}',
           tableId: '${entity.$id}',
           rowId: options?.rowId ?? ID.unique(),
           data,
           permissions: options?.permissions,
         }),
What characters are permitted in Appwrite collection/table names and IDs?

Also applies to: 259-295, 320-322

⛔ Skipped due to learnings
Learnt from: ArnabChatterjee20k
Repo: appwrite/sdk-generator PR: 1266
File: templates/apple/Sources/Channel.swift.twig:13-19
Timestamp: 2025-12-18T18:43:35.419Z
Learning: In the appwrite/sdk-generator repository, helper classes like Channel and Query accept IDs without validation or escaping. These are lightweight helper utilities that construct strings directly from provided parameters without sanitizing or encoding them. This is an intentional design pattern across the SDK.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@ChiragAgg5k ChiragAgg5k force-pushed the feat-appwrite-generate branch from 127d23b to 4f578c4 Compare January 16, 2026 11:49
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
templates/cli/lib/commands/db.ts (1)

306-314: Generated client lacks runtime validation for required environment variables.

The generated code uses non-null assertions (!) on environment variables (lines 310-312). If these aren't set, the SDK will silently pass undefined values, potentially causing cryptic errors downstream rather than clear configuration messages.

🔧 Suggested improvement

Consider generating code that validates env vars at initialization:

 const client = new Client()
-  .setEndpoint(process.env.APPWRITE_ENDPOINT!)
-  .setProject(process.env.APPWRITE_PROJECT_ID!)
-  .setKey(process.env.APPWRITE_API_KEY!);
+  .setEndpoint(process.env.APPWRITE_ENDPOINT ?? throwEnvError('APPWRITE_ENDPOINT'))
+  .setProject(process.env.APPWRITE_PROJECT_ID ?? throwEnvError('APPWRITE_PROJECT_ID'))
+  .setKey(process.env.APPWRITE_API_KEY ?? throwEnvError('APPWRITE_API_KEY'));
+
+function throwEnvError(name: string): never {
+  throw new Error(\`Missing required environment variable: \${name}\`);
+}

Alternatively, add a comment in the generated code instructing users to set these variables.

♻️ Duplicate comments (1)
templates/cli/lib/commands/db.ts (1)

449-456: File paths in success output don't match actual generated files.

This issue was previously identified. The console output shows appwrite.ts, appwrite.db.ts, appwrite.types.ts but writeFiles creates appwrite/index.ts, appwrite/db.ts, appwrite/types.ts. The import example on line 455 also needs correction.

🔧 Suggested fix
     success(`Generated files:`);
-    console.log(`  - ${path.join(outputDir, "appwrite.ts")}`);
-    console.log(`  - ${path.join(outputDir, "appwrite.db.ts")}`);
-    console.log(`  - ${path.join(outputDir, "appwrite.types.ts")}`);
+    console.log(`  - ${path.join(outputDir, "appwrite", "index.ts")}`);
+    console.log(`  - ${path.join(outputDir, "appwrite", "db.ts")}`);
+    console.log(`  - ${path.join(outputDir, "appwrite", "types.ts")}`);
     console.log("");
     log(`Import the generated SDK in your project:`);
-    console.log(`  import { databases } from "./${outputDir}/appwrite.js";`);
+    console.log(`  import { databases } from "./${path.join(outputDir, "appwrite")}";`);
🧹 Nitpick comments (2)
templates/cli/lib/commands/db.ts (2)

99-107: Consider improving type safety instead of using as any cast.

The as any cast on line 102 bypasses TypeScript's type checking. Since both tables and collections share the same attribute structure for type resolution, consider defining a common interface or union type to maintain type safety.

♻️ Suggested approach

Define a shared entity type that both tables and collections conform to:

// At the top of the file or in config.ts
type Entity = NonNullable<ConfigType["tables"]>[number] | NonNullable<ConfigType["collections"]>[number];
type EntityList = NonNullable<ConfigType["tables"]> | NonNullable<ConfigType["collections"]>;

Then update the method signature and remove the cast:

  private generateTableType(
-   entity: NonNullable<ConfigType["tables"]>[number] | NonNullable<ConfigType["collections"]>[number],
-   entities: NonNullable<ConfigType["tables"]> | NonNullable<ConfigType["collections"]>,
+   entity: Entity,
+   entities: EntityList,
  ): string {

This would require updating getType to accept the unified type as well.


154-162: Consider extracting entity grouping logic to reduce duplication.

The logic for grouping entities by databaseId (lines 154-162) is duplicated in generateDbFile (lines 240-247). Extracting this into a reusable helper would improve maintainability.

♻️ Suggested helper
private groupEntitiesByDatabase<T extends { databaseId: string }>(
  entities: T[]
): Map<string, T[]> {
  const entitiesByDb = new Map<string, T[]>();
  for (const entity of entities) {
    const dbId = entity.databaseId;
    if (!entitiesByDb.has(dbId)) {
      entitiesByDb.set(dbId, []);
    }
    entitiesByDb.get(dbId)!.push(entity);
  }
  return entitiesByDb;
}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 127d23b and 4f578c4.

📒 Files selected for processing (2)
  • templates/cli/cli.ts.twig
  • templates/cli/lib/commands/db.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • templates/cli/cli.ts.twig
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: build (8.3, WebChromium)
  • GitHub Check: build (8.3, Python311)
  • GitHub Check: build (8.3, Python313)
  • GitHub Check: build (8.3, AppleSwift56)
  • GitHub Check: build (8.3, Python312)
  • GitHub Check: build (8.3, Ruby30)
  • GitHub Check: build (8.3, Go118)
  • GitHub Check: build (8.3, KotlinJava11)
  • GitHub Check: build (8.3, Swift56)
  • GitHub Check: build (8.3, KotlinJava8)
  • GitHub Check: build (8.3, FlutterStable)
  • GitHub Check: build (8.3, Node16)
  • GitHub Check: build (8.3, Android14Java17)
  • GitHub Check: build (8.3, Android5Java17)
  • GitHub Check: build (8.3, DartBeta)
  • GitHub Check: android (client)
  • GitHub Check: apple (client)
  • GitHub Check: swift (server)
🔇 Additional comments (7)
templates/cli/lib/commands/db.ts (7)

1-12: LGTM!

Imports and interface definition are appropriate for the code generation functionality.


15-71: LGTM!

Type mapping logic correctly handles all attribute types including relationships, arrays, and nullable modifiers.


73-84: LGTM!

String transformation helpers are correctly implemented.


109-138: LGTM!

Enum generation logic is correct. The non-null assertion on line 127 is safe given the truthiness check on line 122.


337-367: LGTM!

The public generate method has proper validation for required configuration and clean separation between generation and file writing.


375-413: LGTM!

File writing logic correctly creates the output structure. The synchronous fs methods are acceptable for a CLI tool where blocking is not a concern.


466-469: LGTM!

Command definition is clean with appropriate description and default value for the output option.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@ChiragAgg5k ChiragAgg5k force-pushed the feat-appwrite-generate branch from 4f578c4 to adab5ed Compare January 16, 2026 12:25
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
templates/cli/lib/commands/db.ts (1)

38-57: Type annotation and relationship lookup mismatch when processing tables.

The getType method parameter is typed as collections: NonNullable<ConfigType["collections"]>, but it's called with entities as any which can be either tables or collections. Since ColumnSchema equals AttributeSchema, tables support relationship attributes. When relationships exist across tables and collections (e.g., a table referencing a collection), the lookup at line 39 will fail to find the related entity and throw an error.

Fix by:

  1. Updating the getType method signature to accept entities: NonNullable<ConfigType["tables"]> | NonNullable<ConfigType["collections"]> instead of collections
  2. Removing the as any cast and letting TypeScript enforce type safety
  3. Ensuring the relationship lookup searches the appropriate entity type
♻️ Duplicate comments (1)
templates/cli/lib/commands/db.ts (1)

450-456: Generated file paths in success output don't match actual output.

The console output lists appwrite.ts, appwrite.db.ts, and appwrite.types.ts, but writeFiles actually creates appwrite/index.ts, appwrite/db.ts, and appwrite/types.ts. The import example also points to the wrong path.

✅ Suggested fix
     success(`Generated files:`);
-    console.log(`  - ${path.join(outputDir, "appwrite.ts")}`);
-    console.log(`  - ${path.join(outputDir, "appwrite.db.ts")}`);
-    console.log(`  - ${path.join(outputDir, "appwrite.types.ts")}`);
+    console.log(`  - ${path.join(outputDir, "appwrite", "index.ts")}`);
+    console.log(`  - ${path.join(outputDir, "appwrite", "db.ts")}`);
+    console.log(`  - ${path.join(outputDir, "appwrite", "types.ts")}`);
     console.log("");
     log(`Import the generated SDK in your project:`);
-    console.log(`  import { databases } from "./${outputDir}/appwrite.js";`);
+    console.log(`  import { databases } from "./${path.join(outputDir, "appwrite")}";`);
🧹 Nitpick comments (1)
templates/cli/lib/commands/db.ts (1)

100-103: Type safety bypass with as any cast.

Casting entities as any defeats TypeScript's type checking and could mask bugs if tables and collections have incompatible structures for the getType method.

Consider refactoring getType to accept the union type directly, or use a type predicate/guard to narrow properly:

♻️ Suggested approach
-      return `    ${attr.key}: ${this.getType(attr, entities as any)};`;
+      // Type assertion is safer when getType is updated to handle both entity types
+      return `    ${attr.key}: ${this.getType(attr, entities)};`;

This requires updating getType's signature to:

private getType(
  attribute: z.infer<typeof AttributeSchema>,
  entities: NonNullable<ConfigType["tables"]> | NonNullable<ConfigType["collections"]>,
): string
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f578c4 and adab5ed.

📒 Files selected for processing (2)
  • templates/cli/cli.ts.twig
  • templates/cli/lib/commands/db.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • templates/cli/cli.ts.twig
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: build (8.3, Node16)
  • GitHub Check: build (8.3, DartBeta)
  • GitHub Check: build (8.3, Android14Java17)
  • GitHub Check: build (8.3, CLIBun11)
  • GitHub Check: build (8.3, DartStable)
  • GitHub Check: apple (client)
  • GitHub Check: android (client)
  • GitHub Check: swift (server)
🔇 Additional comments (10)
templates/cli/lib/commands/db.ts (10)

1-12: LGTM!

Imports and the GenerateResult interface are well-structured. The interface provides a clear contract for the generation output.


73-84: LGTM!

String transformation helpers are correctly implemented for PascalCase and UPPER_SNAKE_CASE conversions.


109-138: LGTM!

The enum generation logic correctly handles both tables and collections, with proper field iteration and enum value formatting.


140-204: LGTM!

The types file generation is well-structured:

  • Properly prioritizes tables over collections
  • Groups entities by database ID for type organization
  • Generates strongly-typed DatabaseId and DatabaseTables types
  • Method signatures in the generated types correctly reference entity types

206-229: LGTM!

Dependency detection logic is reasonable with appropriate fallbacks for different package managers (npm/deno).


338-368: LGTM!

The generate method properly validates configuration, handles both tables and collections, and provides clear feedback when no entities are found.


376-398: LGTM!

File writing logic is straightforward and correct. Uses recursive: true for safe directory creation and proper path joining.


403-414: LGTM!

The main entry file generation is clean with appropriate documentation and ES module-compatible exports.


467-470: LGTM!

The CLI command is properly configured with a clear description and sensible default output directory.


307-323: Verify that TablesDB supports row-level CRUD methods in the generated code.

The generated code calls tablesDB.createRow(), tablesDB.getRow(), tablesDB.updateRow(), tablesDB.deleteRow(), and tablesDB.listRows(). While TablesDB exists in the Appwrite SDK, these specific row-level methods should be confirmed against the target SDK version's API documentation to ensure the generated code will work as intended.

Additionally, the non-null assertions on environment variables (lines 112-114) will throw a runtime error if APPWRITE_ENDPOINT, APPWRITE_PROJECT_ID, or APPWRITE_API_KEY are not set. Consider documenting this requirement or adding validation with more descriptive error messages.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@templates/cli/lib/commands/generators/databases.ts`:
- Around line 38-56: The "relationship" switch case contains lexical
declarations (relatedCollection, type) causing a noSwitchDeclarations lint
error; wrap the entire case body in a block scope to fix it. In the switch
handling the relationship attribute (where relatedCollection is computed from
collections and type is set via toPascalCase(relatedCollection.name) and
potentially suffixed with "[]"), enclose those statements in braces { ... } and
keep the throw, type assignments, and any break/return inside that block so
variables are block-scoped and the lint error is resolved.
- Around line 185-193: The generated object keys use entity.name unquoted which
can produce invalid TypeScript for names with spaces or special characters;
update the template where entity.name is inserted (the map that builds the API
shape using toPascalCase(entity.name), Models.Row, QueryBuilder) to emit the
entity key as a quoted string (e.g., wrap entity.name in single quotes) wherever
it appears (the create/get/update/delete/list block and any other occurrences
such as the later occurrence noted around the same template), matching how dbId
is already quoted so all generated keys are valid TS identifiers.
🧹 Nitpick comments (1)
templates/cli/lib/utils.ts (1)

395-405: Consider consolidating case helpers to avoid divergence.

There are already toPascalCase and toUpperSnakeCase helpers in templates/cli/lib/type-generation/languages/language.ts; a shared utility or re-export would prevent subtle casing mismatches across generators.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
templates/cli/lib/commands/config.ts (1)

209-274: Add validation to enforce exactly one relationship target field

The schema allows both relatedCollection and relatedTable to be optional without validation, creating ambiguous configurations. While some code paths use the ?? operator to handle either field, the type generation code (e.g., typescript.ts line 36-41) only checks relatedCollection and throws if it's missing, making the schema inconsistent. Enforce exactly one of these fields when type === "relationship".

🔧 Suggested refinement
 const AttributeSchema = AttributeSchemaBase.refine(
   (data) => {
     if (data.required === true && data.default !== null) {
       return false;
     }
     return true;
   },
   {
     message: "When 'required' is true, 'default' must be null",
     path: ["default"],
   },
 ).refine(
   (data) => {
     if (
       data.type === "string" &&
       (data.size === undefined || data.size === null)
     ) {
       return false;
     }
     return true;
   },
   {
     message: "When 'type' is 'string', 'size' must be defined",
     path: ["size"],
   },
+).refine(
+  (data) => {
+    if (data.type !== "relationship") return true;
+    const hasCollection = !!data.relatedCollection;
+    const hasTable = !!data.relatedTable;
+    return (hasCollection ? 1 : 0) + (hasTable ? 1 : 0) === 1;
+  },
+  {
+    message:
+      "For relationship attributes, set exactly one of 'relatedCollection' or 'relatedTable'",
+    path: ["relatedCollection"],
+  },
 );
🤖 Fix all issues with AI agents
In `@templates/cli/lib/commands/generators/databases.ts`:
- Around line 17-29: The getType method currently builds enum type names using
only attribute.key which causes global name collisions; update
getType(attribute, collections, entityName) to namespace enum names by
prefixing/suffixing with entityName (e.g.,
`${toPascalCase(entityName)}${toPascalCase(attribute.key)}`) wherever
attribute.format === "enum" and in returned string references; then update all
call sites (notably generateTableType) to pass entity.name into getType; finally
update generateEnums to declare enums using the same namespaced pattern so enum
declarations and usages match (use function/class names getType,
generateTableType, generateEnums and symbols attribute.key and entity.name to
locate places to change).
♻️ Duplicate comments (2)
templates/cli/lib/commands/generators/databases.ts (2)

38-59: Block-scope the relationship case to satisfy noSwitchDeclarations

Biome flags lexical declarations in switch cases; wrap this case in a block so variables are scoped correctly.

🧩 Proposed fix
-      case "relationship":
-        // Handle both collections (relatedCollection) and tables (relatedTable)
-        const relatedId = attribute.relatedCollection ?? attribute.relatedTable;
-        const relatedEntity = collections.find(
-          (c) => c.$id === relatedId || c.name === relatedId,
-        );
-        if (!relatedEntity) {
-          throw new Error(
-            `Related entity with ID '${relatedId}' not found.`,
-          );
-        }
-        type = toPascalCase(relatedEntity.name);
-        if (
-          (attribute.relationType === "oneToMany" &&
-            attribute.side === "parent") ||
-          (attribute.relationType === "manyToOne" &&
-            attribute.side === "child") ||
-          attribute.relationType === "manyToMany"
-        ) {
-          type = `${type}[]`;
-        }
-        break;
+      case "relationship": {
+        // Handle both collections (relatedCollection) and tables (relatedTable)
+        const relatedId = attribute.relatedCollection ?? attribute.relatedTable;
+        const relatedEntity = collections.find(
+          (c) => c.$id === relatedId || c.name === relatedId,
+        );
+        if (!relatedEntity) {
+          throw new Error(
+            `Related entity with ID '${relatedId}' not found.`,
+          );
+        }
+        type = toPascalCase(relatedEntity.name);
+        if (
+          (attribute.relationType === "oneToMany" &&
+            attribute.side === "parent") ||
+          (attribute.relationType === "manyToOne" &&
+            attribute.side === "child") ||
+          attribute.relationType === "manyToMany"
+        ) {
+          type = `${type}[]`;
+        }
+        break;
+      }

183-195: Quote entity name keys in generated types/helpers

Using entity.name as a bare object key can emit invalid TypeScript when names contain spaces or hyphens. Quote the keys in both the type map and helpers.

🧷 Proposed fix
-            return `    ${entity.name}: {
+            return `    '${entity.name}': {
       create: (data: Omit<${typeName}, keyof Models.Row>, options?: { rowId?: string; permissions?: Permission[]; transactionId?: string }) => Promise<${typeName}>;
       get: (id: string) => Promise<${typeName}>;
       update: (id: string, data: Partial<Omit<${typeName}, keyof Models.Row>>, options?: { permissions?: Permission[]; transactionId?: string }) => Promise<${typeName}>;
       delete: (id: string, options?: { transactionId?: string }) => Promise<void>;
       list: (options?: { queries?: (q: QueryBuilder<${typeName}>) => string[] }) => Promise<{ total: number; rows: ${typeName}[] }>;
     }`;
-        return `    ${entityName}: {
+        return `    '${entityName}': {
       create: (data: Omit<${typeName}, keyof Models.Row>, options?: { rowId?: string; permissions?: Permission[]; transactionId?: string }) =>
         tablesDB.createRow<${typeName}>({

Also applies to: 267-313

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@templates/cli/lib/commands/generators/databases.ts`:
- Around line 85-88: The generated type properties are always required; update
the mapping over fields (where typeName and attributes are built, using
toPascalCase and this.getType) to append an optional operator when attr.required
is false or undefined—i.e., change the property key generation from
`${attr.key}: ...` to include `${attr.key}${attr.required ? '' : '?'}: ...` so
non-required attributes are marked optional in the generated types.
♻️ Duplicate comments (3)
templates/cli/lib/commands/generators/databases.ts (3)

38-59: Block‑scope the relationship case to satisfy noSwitchDeclarations.

The const declarations inside this switch case can leak across cases and currently trip Biome’s lint error. Wrap the case in braces.

🧩 Proposed fix
-      case "relationship":
-        // Handle both collections (relatedCollection) and tables (relatedTable)
-        const relatedId = attribute.relatedCollection ?? attribute.relatedTable;
-        const relatedEntity = collections.find(
-          (c) => c.$id === relatedId || c.name === relatedId,
-        );
-        if (!relatedEntity) {
-          throw new Error(
-            `Related entity with ID '${relatedId}' not found.`,
-          );
-        }
-        type = toPascalCase(relatedEntity.name);
-        if (
-          (attribute.relationType === "oneToMany" &&
-            attribute.side === "parent") ||
-          (attribute.relationType === "manyToOne" &&
-            attribute.side === "child") ||
-          attribute.relationType === "manyToMany"
-        ) {
-          type = `${type}[]`;
-        }
-        break;
+      case "relationship": {
+        // Handle both collections (relatedCollection) and tables (relatedTable)
+        const relatedId = attribute.relatedCollection ?? attribute.relatedTable;
+        const relatedEntity = collections.find(
+          (c) => c.$id === relatedId || c.name === relatedId,
+        );
+        if (!relatedEntity) {
+          throw new Error(
+            `Related entity with ID '${relatedId}' not found.`,
+          );
+        }
+        type = toPascalCase(relatedEntity.name);
+        if (
+          (attribute.relationType === "oneToMany" &&
+            attribute.side === "parent") ||
+          (attribute.relationType === "manyToOne" &&
+            attribute.side === "child") ||
+          attribute.relationType === "manyToMany"
+        ) {
+          type = `${type}[]`;
+        }
+        break;
+      }

17-29: Namespace enum type names to avoid cross‑entity collisions.

Enums currently use only field.key, so identical field names across entities collide and break TS compilation. Prefix with the entity name and update getType/generateEnums (and call sites) consistently.

🧩 Proposed fix
-  private getType(
-    attribute: z.infer<typeof AttributeSchema>,
-    collections: NonNullable<ConfigType["collections"]>,
-  ): string {
+  private getType(
+    attribute: z.infer<typeof AttributeSchema>,
+    collections: NonNullable<ConfigType["collections"]>,
+    entityName: string,
+  ): string {
@@
-        if (attribute.format === "enum") {
-          type = toPascalCase(attribute.key);
+        if (attribute.format === "enum") {
+          type = `${toPascalCase(entityName)}${toPascalCase(attribute.key)}`;
         }
         break;
@@
-          const enumName = toPascalCase(field.key);
+          const enumName = `${toPascalCase(entity.name)}${toPascalCase(field.key)}`;
-    const attributes = fields
-      .map((attr) => `    ${attr.key}: ${this.getType(attr, entities as any)};`)
+    const attributes = fields
+      .map((attr) => `    ${attr.key}: ${this.getType(attr, entities as any, entity.name)};`)
       .join("\n");

Also applies to: 93-112


193-214: Quote entity names when emitting object keys.

entity.name can contain characters invalid for unquoted identifiers, yielding invalid TS in both the type map and runtime map. Quote these keys.

🧩 Proposed fix
-            return `    ${entity.name}: {\n${baseMethods}${bulkMethods}\n    }`;
+            return `    '${entity.name}': {\n${baseMethods}${bulkMethods}\n    }`;
@@
-        return `    ${entityName}: {\n${baseMethods}${bulkMethods}\n    }`;
+        return `    '${entityName}': {\n${baseMethods}${bulkMethods}\n    }`;

Also applies to: 283-363

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@templates/cli/lib/commands/generators/databases.ts`:
- Around line 160-162: The code currently detects Deno via
fs.existsSync(path.resolve(cwd, "deno.json")) and returns the old import string
"https://deno.land/x/appwrite/mod.ts"; update that return to use the recommended
Deno npm specifier (for example "npm:node-appwrite") or an alternative JSR
specifier ("jsr:`@appwrite/node`") so generated templates use the supported import
style; locate the check using fs.existsSync(...) and replace the returned URL
string accordingly, ensuring the new specifier is the single exported import
string used by the generator.
♻️ Duplicate comments (5)
templates/cli/lib/commands/generators/databases.ts (5)

38-59: Wrap the relationship case in a block to satisfy noSwitchDeclarations.

The const declarations on lines 40-43 are flagged by Biome because lexical declarations in switch cases can be erroneously accessed by other clauses. Wrap the case body in braces.

🧩 Proposed fix
-      case "relationship":
-        // Handle both collections (relatedCollection) and tables (relatedTable)
-        const relatedId = attribute.relatedCollection ?? attribute.relatedTable;
-        const relatedEntity = collections.find(
-          (c) => c.$id === relatedId || c.name === relatedId,
-        );
+      case "relationship": {
+        // Handle both collections (relatedCollection) and tables (relatedTable)
+        const relatedId = attribute.relatedCollection ?? attribute.relatedTable;
+        const relatedEntity = collections.find(
+          (c) => c.$id === relatedId || c.name === relatedId,
+        );
         if (!relatedEntity) {
           throw new Error(
             `Related entity with ID '${relatedId}' not found.`,
           );
         }
         type = toPascalCase(relatedEntity.name);
         if (
           (attribute.relationType === "oneToMany" &&
             attribute.side === "parent") ||
           (attribute.relationType === "manyToOne" &&
             attribute.side === "child") ||
           attribute.relationType === "manyToMany"
         ) {
           type = `${type}[]`;
         }
         break;
+      }

27-29: Namespace enum names to prevent collisions across entities.

When multiple entities have enum fields with the same key (e.g., status), the generated enum types will collide. The getType method should accept the entity name and prefix the enum type accordingly.

🧩 Proposed fix

Update method signature and enum reference:

  private getType(
    attribute: z.infer<typeof AttributeSchema>,
    collections: NonNullable<ConfigType["collections"]>,
+   entityName: string,
  ): string {
    ...
        if (attribute.format === "enum") {
-          type = toPascalCase(attribute.key);
+          type = `${toPascalCase(entityName)}${toPascalCase(attribute.key)}`;
        }

Then update all call sites to pass the entity name, and update generateEnums (line 102) to use the same naming pattern.


85-88: Add optional operator (?) for non-required fields.

All attributes are currently generated as required properties. When attr.required is false or undefined, the field should be marked optional to allow proper type-checking on create operations.

🧩 Proposed fix
     const attributes = fields
-      .map((attr) => `    ${attr.key}: ${this.getType(attr, entities as any)};`)
+      .map((attr) => {
+        const optional = attr.required ? "" : "?";
+        return `    ${attr.key}${optional}: ${this.getType(attr, entities as any)};`;
+      })
       .join("\n");

226-226: Quote entity names when used as object keys.

entity.name is used directly as an object key without quoting. Appwrite collection names can contain hyphens and other characters that would produce invalid TypeScript (e.g., my-table: {...} is invalid).

🧩 Proposed fix
-            return `    ${entity.name}: {\n${baseMethods}${bulkMethods}\n    }`;
+            return `    '${entity.name}': {\n${baseMethods}${bulkMethods}\n    }`;

377-377: Quote entity names when used as object keys (same issue as line 226).

entityName is used directly without quotes, which can produce invalid TypeScript for names with special characters.

🧩 Proposed fix
-        return `    ${entityName}: {\n${baseMethods}${bulkMethods}\n    }`;
+        return `    '${entityName}': {\n${baseMethods}${bulkMethods}\n    }`;
🧹 Nitpick comments (1)
templates/cli/lib/commands/generators/databases.ts (1)

155-157: Consider logging a warning when package.json parsing fails.

The empty catch block silently swallows errors. While the fallback behavior is fine, a parse failure in package.json might indicate a real problem the user should know about.

♻️ Suggested improvement
       } catch {
-        // Fallback if package.json is invalid
+        // Fallback if package.json is invalid - log for debugging
+        console.warn("Warning: Could not parse package.json, using default Appwrite dependency");
       }

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@templates/cli/lib/commands/generators/databases.ts`:
- Around line 103-110: The enum member identifiers generated in the enum
creation block (see enumName, enumValues, and the toUpperSnakeCase usage) are
not sanitized for special characters or leading digits, which produces invalid
TypeScript identifiers for values like "test@2024" or "123test"; update the
generation to post-process the result of toUpperSnakeCase(element) by
removing/replace any non-alphanumeric characters with underscores, collapsing
consecutive underscores, trimming trailing/leading underscores, and if the first
character is a digit prefix the identifier with an underscore (or otherwise
ensure it begins with a letter/underscore) before emitting it; apply this
sanitized identifier when building enumValues so generated enums are valid
TypeScript.
♻️ Duplicate comments (1)
templates/cli/lib/commands/generators/databases.ts (1)

162-164: Update the Deno import specifier to the current recommended form.

The generator still emits the legacy deno.land/x import. Appwrite’s docs now recommend the npm or JSR specifier for Deno; consider updating to avoid stale/unsupported imports.

🔧 Proposed fix
-    if (fs.existsSync(path.resolve(cwd, "deno.json"))) {
-      return "https://deno.land/x/appwrite/mod.ts";
-    }
+    if (fs.existsSync(path.resolve(cwd, "deno.json"))) {
+      return "npm:node-appwrite";
+      // or: "jsr:`@appwrite/node`"
+    }

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@templates/cli/lib/commands/generators/databases.ts`:
- Around line 123-137: The enum generation can produce duplicate keys after
sanitizeEnumKey (e.g., "test@2024" and "test#2024"), so update the block that
builds enumValues for an enum (where enumName, field.elements and
sanitizeEnumKey are used) to detect collisions: maintain a Set (e.g., usedKeys)
while mapping field.elements, and when sanitizeEnumKey(element) yields an
already-used key, append a deterministic disambiguator (like _1, _2 or _{index})
to the key before adding it and the final `key = "original"` line; ensure you
add the final key to usedKeys and keep comma placement logic unchanged so the
generated enumTypes string compiles.
🧹 Nitpick comments (1)
templates/cli/lib/commands/generators/databases.ts (1)

504-525: Consider using async fs operations for consistency.

The method is declared async but uses synchronous fs operations (existsSync, mkdirSync, writeFileSync). While this works fine for CLI tools, using fs.promises would be more consistent with the async signature.

♻️ Optional refactor
-  async writeFiles(outputDir: string, result: GenerateResult): Promise<void> {
-    const appwriteDir = path.join(outputDir, "appwrite");
-    if (!fs.existsSync(appwriteDir)) {
-      fs.mkdirSync(appwriteDir, { recursive: true });
-    }
-
-    fs.writeFileSync(
-      path.join(appwriteDir, "databases.ts"),
-      result.databasesContent,
-      "utf-8",
-    );
-    fs.writeFileSync(
-      path.join(appwriteDir, "types.ts"),
-      result.typesContent,
-      "utf-8",
-    );
-    fs.writeFileSync(
-      path.join(appwriteDir, "index.ts"),
-      result.indexContent,
-      "utf-8",
-    );
-  }
+  async writeFiles(outputDir: string, result: GenerateResult): Promise<void> {
+    const appwriteDir = path.join(outputDir, "appwrite");
+    await fs.promises.mkdir(appwriteDir, { recursive: true });
+
+    await Promise.all([
+      fs.promises.writeFile(path.join(appwriteDir, "databases.ts"), result.databasesContent, "utf-8"),
+      fs.promises.writeFile(path.join(appwriteDir, "types.ts"), result.typesContent, "utf-8"),
+      fs.promises.writeFile(path.join(appwriteDir, "index.ts"), result.indexContent, "utf-8"),
+    ]);
+  }

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@templates/cli/lib/commands/generators/databases.ts`:
- Around line 419-420: The generated import unconditionally includes TablesDB
which only exists in server SDKs; use the existing supportsBulkMethods() check
to conditionally emit imports and code: when supportsBulkMethods() is true
import TablesDB (and keep existing TablesDB-typed usage such as TablesDB in the
generated code), otherwise import Databases (or omit TablesDB references) and
adjust the generated code to use Databases-compatible APIs and types (e.g.,
replace TablesDB-specific calls with Databases equivalents or alternative
client-friendly implementations), or explicitly abort generation with a clear
message if you intend to support server SDKs only. Ensure the decision point
lives where the return string is produced so the import line and any
TablesDB-typed symbols are only emitted when supportsBulkMethods() is true.
🧹 Nitpick comments (4)
templates/cli/lib/commands/generators/databases.ts (4)

21-25: Consider renaming parameter for clarity.

The parameter collections is used to search for related entities, but it actually receives either tables or collections based on the configuration. Consider renaming to entities to match its actual usage.

♻️ Suggested refactor
  private getType(
    attribute: z.infer<typeof AttributeSchema>,
-   collections: NonNullable<ConfigType["collections"]>,
+   entities: Entities,
    entityName: string,
  ): string {

And update references within the method accordingly.


428-473: Consider reducing any usage in generated runtime helpers.

The createTableApi function uses any for data parameters. While the public API through DatabaseTables is properly typed, these any casts in the implementation could mask runtime type mismatches during development.


542-568: Async method performs only synchronous work.

The generate method is marked async but contains no await expressions and performs no asynchronous operations. Consider making it synchronous for clarity.

♻️ Suggested refactor
- async generate(config: ConfigType): Promise<GenerateResult> {
+ generate(config: ConfigType): GenerateResult {

570-591: Use async file system methods or make method synchronous.

The writeFiles method is marked async but uses synchronous fs methods (existsSync, mkdirSync, writeFileSync). This inconsistency could be confusing. Either use fs.promises methods with await, or make the method synchronous.

♻️ Option 1: Make synchronous
- async writeFiles(outputDir: string, result: GenerateResult): Promise<void> {
+ writeFiles(outputDir: string, result: GenerateResult): void {
♻️ Option 2: Use async fs methods
+ import { promises as fsPromises } from "fs";
...
  async writeFiles(outputDir: string, result: GenerateResult): Promise<void> {
    const appwriteDir = path.join(outputDir, "appwrite");
-   if (!fs.existsSync(appwriteDir)) {
-     fs.mkdirSync(appwriteDir, { recursive: true });
-   }
+   await fsPromises.mkdir(appwriteDir, { recursive: true });

-   fs.writeFileSync(
+   await fsPromises.writeFile(
      path.join(appwriteDir, "databases.ts"),
      result.databasesContent,
      "utf-8",
    );
    // ... similar for other files
  }

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@templates/cli/lib/commands/generators/databases.ts`:
- Around line 345-356: The generated tableIdMap currently builds plain object
literals which are vulnerable to prototype key collisions; update
generateTableIdMap to emit null-prototype objects and safe property checks by
creating the outer map and each DB map with Object.create(null) (e.g. emit
"const tableIdMap: Record<string, Record<string, string>> =
Object.create(null);" and for each DB emit "tableIdMap['dbId'] =
Object.create(null);" and assign properties into those objects), and ensure any
runtime lookup code uses Object.prototype.hasOwnProperty.call(tableIdMap[dbId],
key) or equivalent to check keys safely; make the same change for the other
similar generator block referenced (the block around the generate function at
the later lines).
- Around line 123-140: The enum emission currently injects raw field elements
into string literals (in the block that builds enumValues inside the loop over
fields, using sanitizeEnumKey, usedKeys and enumName), which breaks when values
contain quotes or backslashes; change the emitted RHS to use a properly escaped
string by replacing the inline "${element}" with JSON.stringify(element) (and
remove the manual surrounding quotes), so each enum member line becomes e.g. `  
${key} = ${JSON.stringify(element)}${isLast ? "" : ","}` ensuring values are
safely escaped.
- Around line 101-110: The generated TypeScript type in generateTableType uses
raw attribute keys which fails for keys with hyphens/special chars; update the
mapping that builds attributes (in generateTableType) to quote the attribute
name (e.g., use JSON.stringify(attr.key) or `["..."]`-style string literal) when
emitting `${attr.key}${attr.required ? "" : "?"}: ...` so the key becomes a
valid quoted identifier while preserving the optional marker and using
getType(entity) for the value; check getFields/getType usage remains unchanged
and ensure the output still produces valid TS like "my-key"?: string or
["my-key"]?: string.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@templates/cli/lib/commands/generators/databases.ts`:
- Around line 167-187: The SDK detection only inspects packageJson.dependencies
(variable packageJson and deps) so
devDependencies/peerDependencies/optionalDependencies are ignored; update the
logic in the function in templates/cli/lib/commands/generators/databases.ts to
aggregate all dependency sections (dependencies, devDependencies,
peerDependencies, optionalDependencies) into deps (or check each section when
looking for "@appwrite.io/console", "react-native-appwrite", "appwrite",
"node-appwrite") before returning the detected SDK so packages installed as
dev/peer/optional are correctly detected.
- Around line 271-275: In generateDatabaseTablesType and
generateTablesWithRelationships, escape any embedded entity/db names by emitting
proper JS/TS string literals using JSON.stringify instead of raw
`'${entity.name}'` or `'${dbId}'`; locate the string templates that produce
lines like `    '${entity.name}': {...}` and `  '${dbId}': {...}` and replace
the interpolation with JSON.stringify(entity.name) and JSON.stringify(dbId) (or
equivalent variable) so quotes and backslashes are escaped when generating the
TypeScript output.
- Around line 106-107: Generated type/enum names currently use only entity.name
(via typeName = toPascalCase(entity.name)), causing collisions across databases;
update generation to ensure global uniqueness by either (A) prefixing/suffixing
the derived name with a database identifier (e.g., use toPascalCase(database.id)
+ toPascalCase(entity.name) when computing typeName and any enum names) or (B)
add a validation step before codegen that scans all entities across databases
for duplicate entity.name and throws a clear configuration error; implement one
of these fixes and update all places that reference typeName/enum generation to
use the new unique naming scheme or to short-circuit on validation failure.
♻️ Duplicate comments (3)
templates/cli/lib/commands/generators/databases.ts (3)

106-111: Quote attribute keys in generated types to keep TS valid.

Unquoted keys (e.g., my-key) are invalid in TypeScript type literals. Please emit quoted keys.

🔧 Suggested fix
-    const attributes = fields
-      .map(
-        (attr) =>
-          `    ${attr.key}${attr.required ? "" : "?"}: ${this.getType(attr, entities as any, entity.name)};`,
-      )
-      .join("\n");
+    const attributes = fields
+      .map((attr) => {
+        const optional = attr.required ? "" : "?";
+        const key = JSON.stringify(attr.key);
+        return `    ${key}${optional}: ${this.getType(attr, entities as any, entity.name)};`;
+      })
+      .join("\n");

124-141: Escape enum values before emitting string literals.

Raw enum values containing quotes/backslashes will generate invalid TypeScript.

🔧 Suggested fix
-              return `    ${key} = "${element}"${isLast ? "" : ","}`;
+              return `    ${key} = ${JSON.stringify(element)}${isLast ? "" : ","}`;

346-357: Harden tableIdMap/proxy against prototype keys.

Plain object literals plus in checks can be poisoned by keys like __proto__. Use null‑prototype maps and hasOwnProperty checks.

Also applies to: 488-510

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@templates/cli/lib/commands/generators/databases.ts`:
- Around line 298-299: The dbIdType union is built from raw db IDs and can
produce invalid TypeScript when IDs contain quotes or escape sequences; update
the construction used with entitiesByDb so that dbIds.map(...) uses a proper JS
string literal encoder (e.g., JSON.stringify) instead of `'${id}'` so each id
becomes a safely escaped string literal for the TypeScript union (locate dbIds,
dbIdType and entitiesByDb to apply the change).
♻️ Duplicate comments (4)
templates/cli/lib/commands/generators/databases.ts (4)

106-119: Verify global uniqueness of entity names across databases.

Type names are derived from entity.name alone (line 110). If two databases contain entities with the same name (e.g., both have a "users" table), the generated types will collide, causing TypeScript compilation errors. Consider either:

  1. Prefixing type names with database ID: toPascalCase(entity.databaseId) + toPascalCase(entity.name)
  2. Adding configuration validation to enforce entity name uniqueness across all databases

Run the following script to check if configuration validation exists:

#!/bin/bash
# Search for entity name uniqueness validation in config schema
rg -n "superRefine|refine|unique.*name|name.*unique" templates/cli/lib/commands/config.ts -A 5

168-202: Include dev/peer/optional dependencies in SDK detection.

The function only inspects packageJson.dependencies (line 178), so SDKs installed as devDependencies, peerDependencies, or optionalDependencies won't be detected, causing incorrect fallback to "appwrite".

🔧 Proposed fix
         const packageJson = JSON.parse(packageJsonRaw);
-        const deps = packageJson.dependencies ?? {};
+        const deps = {
+          ...(packageJson.dependencies ?? {}),
+          ...(packageJson.devDependencies ?? {}),
+          ...(packageJson.peerDependencies ?? {}),
+          ...(packageJson.optionalDependencies ?? {}),
+        };

275-279: Escape entity names and database IDs in generated type literals.

Entity names and database IDs are interpolated directly into string literals without escaping. If these contain quotes or backslashes, the generated TypeScript will be invalid.

🔧 Proposed fix
-            return `    '${entity.name}': {\n${baseMethods}${bulkMethods}\n    }`;
+            return `    ${JSON.stringify(entity.name)}: {\n${baseMethods}${bulkMethods}\n    }`;
           })
           .join(";\n");
-        return `  '${dbId}': {\n${tableTypes}\n  }`;
+        return `  ${JSON.stringify(dbId)}: {\n${tableTypes}\n  }`;

367-385: Escape values in tablesWithRelationships Set initialization.

The composite key '${dbId}:${entity.name}' is not escaped, which could produce invalid JavaScript if either value contains quotes.

🔧 Proposed fix
         if (this.hasRelationshipColumns(entity)) {
-          tablesWithRelationships.push(`'${dbId}:${entity.name}'`);
+          tablesWithRelationships.push(JSON.stringify(`${dbId}:${entity.name}`));
         }

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@templates/cli/lib/commands/generate.ts`:
- Around line 41-53: Normalize the incoming options.language value (trim and
toLowerCase) before validating and using it: create a normalized variable from
options.language, call LanguageDetector.isSupported(normalized), use
getSupportedLanguages() for the error message, pass the normalized value to
createGenerator(normalized as SupportedLanguage), and assign detectedLanguage =
normalized; ensure original options.language is not used directly for validation
or generator creation and update log to show the normalized language.

In `@templates/cli/lib/commands/generators/base.ts`:
- Around line 68-83: In writeFiles, guard against path traversal by resolving
and validating each target path before creating directories or writing files:
for each relativePath from result.files, compute the resolvedFilePath (using
path.resolve) against the sdkDir (not outputDir directly), ensure the resolved
path is inside sdkDir (e.g., path.relative(sdkDir, resolvedFilePath) does not
start with '..' and resolvedFilePath is not equal to sdkDir's parent), and throw
or skip with an error if the check fails; only then create fileDir and call
fs.writeFileSync. Reference: function writeFiles, variables relativePath,
sdkDir, filePath (resolvedFilePath) and result.files.
♻️ Duplicate comments (5)
templates/cli/lib/commands/generators/typescript/databases.ts (5)

108-121: Type names can collide across databases.

typeName is derived only from entity.name, so identically named entities in different databases will produce duplicate type/enum names and break compilation. Consider prefixing with databaseId or validating uniqueness upfront.


170-193: Include dev/peer/optional dependencies in SDK detection.

SDKs installed as dev/peer/optional dependencies won’t be detected, leading to incorrect imports.

🛠️ Proposed fix
-        const deps = packageJson.dependencies ?? {};
+        const deps = {
+          ...(packageJson.dependencies ?? {}),
+          ...(packageJson.devDependencies ?? {}),
+          ...(packageJson.peerDependencies ?? {}),
+          ...(packageJson.optionalDependencies ?? {}),
+        };

257-281: Escape database and entity names in generated type keys.

Raw '${entity.name}' and '${dbId}' will break when names contain quotes or backslashes.

🛠️ Proposed fix
-            return `    '${entity.name}': {\n${baseMethods}${bulkMethods}\n    }`;
+            return `    ${JSON.stringify(entity.name)}: {\n${baseMethods}${bulkMethods}\n    }`;
           })
           .join(";\n");
-        return `  '${dbId}': {\n${tableTypes}\n  }`;
+        return `  ${JSON.stringify(dbId)}: {\n${tableTypes}\n  }`;

299-302: Escape database IDs in the literal union.

Unescaped IDs can produce invalid TypeScript if they contain quotes or backslashes.

🛠️ Proposed fix
-    const dbIdType = dbIds.map((id) => `'${id}'`).join(" | ");
+    const dbIdType = dbIds.map((id) => JSON.stringify(id)).join(" | ");

369-386: Escape relationship table identifiers.

Raw '${dbId}:${entity.name}' can emit invalid string literals.

🛠️ Proposed fix
-          tablesWithRelationships.push(`'${dbId}:${entity.name}'`);
+          tablesWithRelationships.push(JSON.stringify(`${dbId}:${entity.name}`));
🧹 Nitpick comments (1)
templates/cli/lib/commands/generators/language-detector.ts (1)

31-43: Consider lowering confidence when only package.json is present.

Using package.json as a primary indicator will mark many JS-only projects as TypeScript with high confidence. Consider moving it to secondary indicators or requiring tsconfig.json for high confidence.

🛠️ Proposed tweak
-    primaryIndicators: ["tsconfig.json", "package.json", "deno.json"],
+    primaryIndicators: ["tsconfig.json", "deno.json"],
     secondaryIndicators: [
+      "package.json",
       ".nvmrc",
       "package-lock.json",
       "yarn.lock",

@ChiragAgg5k ChiragAgg5k force-pushed the feat-appwrite-generate branch from 43a4f36 to 5a9613a Compare January 20, 2026 13:48
@ChiragAgg5k ChiragAgg5k force-pushed the feat-appwrite-generate branch from 5a9613a to 2cf10eb Compare January 20, 2026 13:49
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
templates/cli/lib/commands/config.ts (1)

268-307: ColumnSchema is missing the same required/default & string size validations.

Tables now skip the validations enforced for collection attributes, which can allow invalid configs to pass (e.g., required + non-null default, or string without size). Suggest mirroring the AttributeSchema refinements.

✅ Suggested fix
 const ColumnSchema = z
   .object({
     key: z.string(),
     type: z.enum([
       "string",
@@
     encrypt: z.boolean().optional(),
   })
-  .strict();
+  .strict()
+  .refine(validateRequiredDefault, {
+    message: "When 'required' is true, 'default' must be null",
+    path: ["default"],
+  })
+  .refine(validateStringSize, {
+    message: "When 'type' is 'string', 'size' must be defined",
+    path: ["size"],
+  });
templates/cli/lib/config.ts (1)

46-69: Fix property name for Zod v4.3.5 compatibility in getSchemaKeys.

The project uses Zod v4.3.5, which stores internal schema properties on _def (not def). The current check on line 48 fails to unwrap ZodEffects, causing getSchemaKeys() to return empty sets for TableSchema, CollectionSchema, and TopicSchema—all of which use .superRefine(). This results in empty KeysTable, KeysCollection, and KeysTopics sets, stripping all fields from these resources during validation.

Fix
 function getSchemaKeys(schema: z.ZodTypeAny): Set<string> {
   // For ZodEffects (created by .refine(), .superRefine(), .transform())
-  if ("def" in schema && "schema" in (schema.def as any)) {
-    return getSchemaKeys((schema.def as any).schema);
-  }
+  const def: any = (schema as any)._def;
+  if (def && "schema" in def) {
+    return getSchemaKeys(def.schema);
+  }
   // For ZodObject
   if ("shape" in schema) {
     return new Set(Object.keys((schema as any).shape));
   }
   return new Set();
 }
🧹 Nitpick comments (1)
templates/cli/lib/commands/config-validations.ts (1)

54-98: Consider validating both attributes and columns when both are present.

Right now the function selects either attributes or columns and ignores the other. If mixed data ever appears (e.g., merged configs), duplicates in the second array won’t be caught. Optional, but safer.

♻️ Suggested adjustment
 export const validateContainerDuplicates = (
   data: CollectionOrTableData,
   ctx: z.RefinementCtx,
 ) => {
-  const items = data.attributes || data.columns || [];
-  const itemType = data.attributes ? "Attribute" : "Column";
-  const itemPath = data.attributes ? "attributes" : "columns";
-
-  // Validate duplicate item keys
-  if (items.length > 0) {
-    const seenKeys = new Set<string>();
-
-    items.forEach((item, index) => {
-      if (seenKeys.has(item.key)) {
-        ctx.addIssue({
-          code: z.ZodIssueCode.custom,
-          message: `${itemType} with the key '${item.key}' already exists. ${itemType} keys must be unique, try again with a different key.`,
-          path: [itemPath, index, "key"],
-        });
-      } else {
-        seenKeys.add(item.key);
-      }
-    });
-  }
+  const validateItems = (
+    items: AttributeOrColumn[] | undefined,
+    itemType: "Attribute" | "Column",
+    itemPath: "attributes" | "columns",
+  ) => {
+    if (!items || items.length === 0) return;
+    const seenKeys = new Set<string>();
+    items.forEach((item, index) => {
+      if (seenKeys.has(item.key)) {
+        ctx.addIssue({
+          code: z.ZodIssueCode.custom,
+          message: `${itemType} with the key '${item.key}' already exists. ${itemType} keys must be unique, try again with a different key.`,
+          path: [itemPath, index, "key"],
+        });
+      } else {
+        seenKeys.add(item.key);
+      }
+    });
+  };
+
+  validateItems(data.attributes, "Attribute", "attributes");
+  validateItems(data.columns, "Column", "columns");

@ChiragAgg5k ChiragAgg5k merged commit 2775d59 into master Jan 20, 2026
55 checks passed
@ChiragAgg5k ChiragAgg5k deleted the feat-appwrite-generate branch January 20, 2026 16:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants