appwrite-cli 13.0.1 → 13.1.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +2 -2
  3. package/cli.ts +2 -0
  4. package/dist/bundle.cjs +17551 -16864
  5. package/dist/cli.js +2 -0
  6. package/dist/cli.js.map +1 -1
  7. package/dist/lib/commands/config-validations.d.ts +53 -0
  8. package/dist/lib/commands/config-validations.d.ts.map +1 -0
  9. package/dist/lib/commands/config-validations.js +130 -0
  10. package/dist/lib/commands/config-validations.js.map +1 -0
  11. package/dist/lib/commands/config.d.ts +241 -325
  12. package/dist/lib/commands/config.d.ts.map +1 -1
  13. package/dist/lib/commands/config.js +86 -107
  14. package/dist/lib/commands/config.js.map +1 -1
  15. package/dist/lib/commands/generate.d.ts +7 -0
  16. package/dist/lib/commands/generate.d.ts.map +1 -0
  17. package/dist/lib/commands/generate.js +92 -0
  18. package/dist/lib/commands/generate.js.map +1 -0
  19. package/dist/lib/commands/generators/base.d.ts +58 -0
  20. package/dist/lib/commands/generators/base.d.ts.map +1 -0
  21. package/dist/lib/commands/generators/base.js +27 -0
  22. package/dist/lib/commands/generators/base.js.map +1 -0
  23. package/dist/lib/commands/generators/index.d.ts +26 -0
  24. package/dist/lib/commands/generators/index.d.ts.map +1 -0
  25. package/dist/lib/commands/generators/index.js +55 -0
  26. package/dist/lib/commands/generators/index.js.map +1 -0
  27. package/dist/lib/commands/generators/language-detector.d.ts +42 -0
  28. package/dist/lib/commands/generators/language-detector.d.ts.map +1 -0
  29. package/dist/lib/commands/generators/language-detector.js +127 -0
  30. package/dist/lib/commands/generators/language-detector.js.map +1 -0
  31. package/dist/lib/commands/generators/typescript/databases.d.ts +36 -0
  32. package/dist/lib/commands/generators/typescript/databases.d.ts.map +1 -0
  33. package/dist/lib/commands/generators/typescript/databases.js +489 -0
  34. package/dist/lib/commands/generators/typescript/databases.js.map +1 -0
  35. package/dist/lib/commands/pull.js +3 -3
  36. package/dist/lib/commands/pull.js.map +1 -1
  37. package/dist/lib/commands/push.d.ts +6 -3
  38. package/dist/lib/commands/push.d.ts.map +1 -1
  39. package/dist/lib/commands/push.js +57 -37
  40. package/dist/lib/commands/push.js.map +1 -1
  41. package/dist/lib/commands/schema.d.ts +2 -2
  42. package/dist/lib/commands/schema.d.ts.map +1 -1
  43. package/dist/lib/commands/schema.js +2 -2
  44. package/dist/lib/commands/schema.js.map +1 -1
  45. package/dist/lib/commands/utils/attributes.d.ts +5 -0
  46. package/dist/lib/commands/utils/attributes.d.ts.map +1 -1
  47. package/dist/lib/commands/utils/attributes.js +20 -10
  48. package/dist/lib/commands/utils/attributes.js.map +1 -1
  49. package/dist/lib/commands/utils/pools.d.ts +0 -3
  50. package/dist/lib/commands/utils/pools.d.ts.map +1 -1
  51. package/dist/lib/commands/utils/pools.js +0 -34
  52. package/dist/lib/commands/utils/pools.js.map +1 -1
  53. package/dist/lib/config.d.ts.map +1 -1
  54. package/dist/lib/config.js +28 -131
  55. package/dist/lib/config.js.map +1 -1
  56. package/dist/lib/constants.d.ts +1 -1
  57. package/dist/lib/constants.d.ts.map +1 -1
  58. package/dist/lib/constants.js +1 -1
  59. package/dist/lib/constants.js.map +1 -1
  60. package/dist/lib/utils.d.ts +3 -0
  61. package/dist/lib/utils.d.ts.map +1 -1
  62. package/dist/lib/utils.js +26 -0
  63. package/dist/lib/utils.js.map +1 -1
  64. package/dist/package.json +1 -1
  65. package/install.ps1 +2 -2
  66. package/install.sh +1 -1
  67. package/lib/commands/config-validations.ts +199 -0
  68. package/lib/commands/config.ts +94 -125
  69. package/lib/commands/generate.ts +142 -0
  70. package/lib/commands/generators/base.ts +91 -0
  71. package/lib/commands/generators/index.ts +87 -0
  72. package/lib/commands/generators/language-detector.ts +163 -0
  73. package/lib/commands/generators/typescript/databases.ts +590 -0
  74. package/lib/commands/pull.ts +4 -4
  75. package/lib/commands/push.ts +97 -58
  76. package/lib/commands/schema.ts +3 -3
  77. package/lib/commands/utils/attributes.ts +31 -11
  78. package/lib/commands/utils/pools.ts +0 -67
  79. package/lib/config.ts +43 -131
  80. package/lib/constants.ts +1 -1
  81. package/lib/utils.ts +32 -0
  82. package/package.json +1 -1
  83. package/scoop/appwrite.config.json +3 -3
  84. package/dist/lib/commands/db.d.ts +0 -34
  85. package/dist/lib/commands/db.d.ts.map +0 -1
  86. package/dist/lib/commands/db.js +0 -247
  87. package/dist/lib/commands/db.js.map +0 -1
  88. package/lib/commands/db.ts +0 -324
@@ -0,0 +1,590 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { z } from "zod";
4
+ import { ConfigType, AttributeSchema } from "../../config.js";
5
+ import { toPascalCase, sanitizeEnumKey } from "../../../utils.js";
6
+ import { SDK_TITLE, EXECUTABLE_NAME } from "../../../constants.js";
7
+ import {
8
+ BaseDatabasesGenerator,
9
+ GenerateResult,
10
+ SupportedLanguage,
11
+ } from "../base.js";
12
+
13
+ type Entity =
14
+ | NonNullable<ConfigType["tables"]>[number]
15
+ | NonNullable<ConfigType["collections"]>[number];
16
+ type Entities =
17
+ | NonNullable<ConfigType["tables"]>
18
+ | NonNullable<ConfigType["collections"]>;
19
+
20
+ /**
21
+ * TypeScript-specific database generator.
22
+ * Generates type-safe SDK files for TypeScript/JavaScript projects.
23
+ */
24
+ export class TypeScriptDatabasesGenerator extends BaseDatabasesGenerator {
25
+ readonly language: SupportedLanguage = "typescript";
26
+ readonly fileExtension = "ts";
27
+
28
+ private getType(
29
+ attribute: z.infer<typeof AttributeSchema>,
30
+ collections: NonNullable<ConfigType["collections"]>,
31
+ entityName: string,
32
+ ): string {
33
+ let type = "";
34
+
35
+ switch (attribute.type) {
36
+ case "string":
37
+ case "datetime":
38
+ type = "string";
39
+ if (attribute.format === "enum") {
40
+ type = toPascalCase(entityName) + toPascalCase(attribute.key);
41
+ }
42
+ break;
43
+ case "integer":
44
+ case "double":
45
+ type = "number";
46
+ break;
47
+ case "boolean":
48
+ type = "boolean";
49
+ break;
50
+ case "relationship": {
51
+ // Handle both collections (relatedCollection) and tables (relatedTable)
52
+ const relatedId = attribute.relatedCollection ?? attribute.relatedTable;
53
+ const relatedEntity = collections.find(
54
+ (c) => c.$id === relatedId || c.name === relatedId,
55
+ );
56
+ if (!relatedEntity) {
57
+ throw new Error(`Related entity with ID '${relatedId}' not found.`);
58
+ }
59
+ type = toPascalCase(relatedEntity.name);
60
+ if (
61
+ (attribute.relationType === "oneToMany" &&
62
+ attribute.side === "parent") ||
63
+ (attribute.relationType === "manyToOne" &&
64
+ attribute.side === "child") ||
65
+ attribute.relationType === "manyToMany"
66
+ ) {
67
+ type = `${type}[]`;
68
+ }
69
+ break;
70
+ }
71
+ default:
72
+ throw new Error(`Unknown attribute type: ${attribute.type}`);
73
+ }
74
+
75
+ if (attribute.array) {
76
+ type += "[]";
77
+ }
78
+
79
+ if (!attribute.required && attribute.default === null) {
80
+ type += " | null";
81
+ }
82
+
83
+ return type;
84
+ }
85
+
86
+ private getFields(
87
+ entity: Entity,
88
+ ): z.infer<typeof AttributeSchema>[] | undefined {
89
+ return "columns" in entity
90
+ ? (entity as NonNullable<ConfigType["tables"]>[number]).columns
91
+ : (entity as NonNullable<ConfigType["collections"]>[number]).attributes;
92
+ }
93
+
94
+ /**
95
+ * Checks if an entity has relationship columns.
96
+ * Used to disable bulk methods for tables with relationships.
97
+ *
98
+ * TODO: Remove this restriction when bulk operations support relationships.
99
+ * To enable bulk methods for all tables, simply return false here:
100
+ * return false;
101
+ */
102
+ private hasRelationshipColumns(entity: Entity): boolean {
103
+ const fields = this.getFields(entity);
104
+ if (!fields) return false;
105
+ return fields.some((field) => field.type === "relationship");
106
+ }
107
+
108
+ private generateTableType(entity: Entity, entities: Entities): string {
109
+ const fields = this.getFields(entity);
110
+ if (!fields) return "";
111
+
112
+ const typeName = toPascalCase(entity.name);
113
+ const attributes = fields
114
+ .map(
115
+ (attr) =>
116
+ ` ${JSON.stringify(attr.key)}${attr.required ? "" : "?"}: ${this.getType(attr, entities as any, entity.name)};`,
117
+ )
118
+ .join("\n");
119
+
120
+ return `export type ${typeName} = Models.Row & {\n${attributes}\n}`;
121
+ }
122
+
123
+ private generateEnums(entities: Entities): string {
124
+ const enumTypes: string[] = [];
125
+
126
+ for (const entity of entities) {
127
+ const fields = this.getFields(entity);
128
+ if (!fields) continue;
129
+
130
+ for (const field of fields) {
131
+ if (field.format === "enum" && field.elements) {
132
+ const enumName = toPascalCase(entity.name) + toPascalCase(field.key);
133
+ const usedKeys = new Set<string>();
134
+ const enumValues = field.elements
135
+ .map((element: string, index: number) => {
136
+ let key = sanitizeEnumKey(element);
137
+ if (usedKeys.has(key)) {
138
+ let disambiguator = 1;
139
+ while (usedKeys.has(`${key}_${disambiguator}`)) {
140
+ disambiguator++;
141
+ }
142
+ key = `${key}_${disambiguator}`;
143
+ }
144
+ usedKeys.add(key);
145
+ const isLast = index === field.elements!.length - 1;
146
+ return ` ${key} = ${JSON.stringify(element)}${isLast ? "" : ","}`;
147
+ })
148
+ .join("\n");
149
+
150
+ enumTypes.push(`export enum ${enumName} {\n${enumValues}\n}`);
151
+ }
152
+ }
153
+ }
154
+
155
+ return enumTypes.join("\n\n");
156
+ }
157
+
158
+ private groupEntitiesByDb(entities: Entities): Map<string, Entity[]> {
159
+ const entitiesByDb = new Map<string, Entity[]>();
160
+ for (const entity of entities) {
161
+ const dbId = entity.databaseId;
162
+ if (!entitiesByDb.has(dbId)) {
163
+ entitiesByDb.set(dbId, []);
164
+ }
165
+ entitiesByDb.get(dbId)!.push(entity);
166
+ }
167
+ return entitiesByDb;
168
+ }
169
+
170
+ private getAppwriteDependency(): string {
171
+ const cwd = process.cwd();
172
+
173
+ if (fs.existsSync(path.resolve(cwd, "package.json"))) {
174
+ try {
175
+ const packageJsonRaw = fs.readFileSync(
176
+ path.resolve(cwd, "package.json"),
177
+ "utf-8",
178
+ );
179
+ const packageJson = JSON.parse(packageJsonRaw);
180
+ const deps = packageJson.dependencies ?? {};
181
+
182
+ if (deps["@appwrite.io/console"]) {
183
+ return "@appwrite.io/console";
184
+ }
185
+ if (deps["react-native-appwrite"]) {
186
+ return "react-native-appwrite";
187
+ }
188
+ if (deps["appwrite"]) {
189
+ return "appwrite";
190
+ }
191
+ if (deps["node-appwrite"]) {
192
+ return "node-appwrite";
193
+ }
194
+ } catch {
195
+ // Fallback if package.json is invalid
196
+ }
197
+ }
198
+
199
+ if (fs.existsSync(path.resolve(cwd, "deno.json"))) {
200
+ return "npm:node-appwrite";
201
+ }
202
+
203
+ return "appwrite";
204
+ }
205
+
206
+ private supportsBulkMethods(appwriteDep: string): boolean {
207
+ return (
208
+ appwriteDep === "node-appwrite" ||
209
+ appwriteDep === "npm:node-appwrite" ||
210
+ appwriteDep === "@appwrite.io/console"
211
+ );
212
+ }
213
+
214
+ private generateQueryBuilderType(): string {
215
+ return `export type QueryValue = string | number | boolean;
216
+
217
+ export type ExtractQueryValue<T> = T extends (infer U)[]
218
+ ? U extends QueryValue ? U : never
219
+ : T extends QueryValue | null ? NonNullable<T> : never;
220
+
221
+ export type QueryableKeys<T> = {
222
+ [K in keyof T]: ExtractQueryValue<T[K]> extends never ? never : K;
223
+ }[keyof T];
224
+
225
+ export type QueryBuilder<T> = {
226
+ equal: <K extends QueryableKeys<T>>(field: K, value: ExtractQueryValue<T[K]>) => string;
227
+ notEqual: <K extends QueryableKeys<T>>(field: K, value: ExtractQueryValue<T[K]>) => string;
228
+ lessThan: <K extends QueryableKeys<T>>(field: K, value: ExtractQueryValue<T[K]>) => string;
229
+ lessThanEqual: <K extends QueryableKeys<T>>(field: K, value: ExtractQueryValue<T[K]>) => string;
230
+ greaterThan: <K extends QueryableKeys<T>>(field: K, value: ExtractQueryValue<T[K]>) => string;
231
+ greaterThanEqual: <K extends QueryableKeys<T>>(field: K, value: ExtractQueryValue<T[K]>) => string;
232
+ contains: <K extends QueryableKeys<T>>(field: K, value: ExtractQueryValue<T[K]>) => string;
233
+ search: <K extends QueryableKeys<T>>(field: K, value: string) => string;
234
+ isNull: <K extends QueryableKeys<T>>(field: K) => string;
235
+ isNotNull: <K extends QueryableKeys<T>>(field: K) => string;
236
+ startsWith: <K extends QueryableKeys<T>>(field: K, value: string) => string;
237
+ endsWith: <K extends QueryableKeys<T>>(field: K, value: string) => string;
238
+ between: <K extends QueryableKeys<T>>(field: K, start: ExtractQueryValue<T[K]>, end: ExtractQueryValue<T[K]>) => string;
239
+ select: <K extends keyof T>(fields: K[]) => string;
240
+ orderAsc: <K extends keyof T>(field: K) => string;
241
+ orderDesc: <K extends keyof T>(field: K) => string;
242
+ limit: (value: number) => string;
243
+ offset: (value: number) => string;
244
+ cursorAfter: (documentId: string) => string;
245
+ cursorBefore: (documentId: string) => string;
246
+ or: (...queries: string[]) => string;
247
+ and: (...queries: string[]) => string;
248
+ }`;
249
+ }
250
+
251
+ private generateDatabaseTablesType(
252
+ entitiesByDb: Map<string, Entity[]>,
253
+ appwriteDep: string,
254
+ ): string {
255
+ const supportsBulk = this.supportsBulkMethods(appwriteDep);
256
+ const dbReturnTypes = Array.from(entitiesByDb.entries())
257
+ .map(([dbId, dbEntities]) => {
258
+ const tableTypes = dbEntities
259
+ .map((entity) => {
260
+ const typeName = toPascalCase(entity.name);
261
+ const baseMethods = ` create: (data: Omit<${typeName}, keyof Models.Row>, options?: { rowId?: string; permissions?: Permission[]; transactionId?: string }) => Promise<${typeName}>;
262
+ get: (id: string) => Promise<${typeName}>;
263
+ update: (id: string, data: Partial<Omit<${typeName}, keyof Models.Row>>, options?: { permissions?: Permission[]; transactionId?: string }) => Promise<${typeName}>;
264
+ delete: (id: string, options?: { transactionId?: string }) => Promise<void>;
265
+ list: (options?: { queries?: (q: QueryBuilder<${typeName}>) => string[] }) => Promise<{ total: number; rows: ${typeName}[] }>;`;
266
+
267
+ // Bulk methods not supported for tables with relationship columns (see hasRelationshipColumns)
268
+ const canUseBulkMethods =
269
+ supportsBulk && !this.hasRelationshipColumns(entity);
270
+ const bulkMethods = canUseBulkMethods
271
+ ? `
272
+ createMany: (rows: Array<Omit<${typeName}, keyof Models.Row> & { $id?: string; $permissions?: string[] }>, options?: { transactionId?: string }) => Promise<{ total: number; rows: ${typeName}[] }>;
273
+ updateMany: (data: Partial<Omit<${typeName}, keyof Models.Row>>, options?: { queries?: (q: QueryBuilder<${typeName}>) => string[]; transactionId?: string }) => Promise<{ total: number; rows: ${typeName}[] }>;
274
+ deleteMany: (options?: { queries?: (q: QueryBuilder<${typeName}>) => string[]; transactionId?: string }) => Promise<{ total: number; rows: ${typeName}[] }>;`
275
+ : "";
276
+
277
+ return ` '${entity.name}': {\n${baseMethods}${bulkMethods}\n }`;
278
+ })
279
+ .join(";\n");
280
+ return ` '${dbId}': {\n${tableTypes}\n }`;
281
+ })
282
+ .join(";\n");
283
+
284
+ return `export type DatabaseTables = {\n${dbReturnTypes}\n}`;
285
+ }
286
+
287
+ private generateTypesFile(config: ConfigType): string {
288
+ const entities = config.tables?.length ? config.tables : config.collections;
289
+
290
+ if (!entities || entities.length === 0) {
291
+ return "// No tables or collections found in configuration\n";
292
+ }
293
+
294
+ const appwriteDep = this.getAppwriteDependency();
295
+ const enums = this.generateEnums(entities);
296
+ const types = entities
297
+ .map((entity: Entity) => this.generateTableType(entity, entities))
298
+ .join("\n\n");
299
+ const entitiesByDb = this.groupEntitiesByDb(entities);
300
+ const dbIds = Array.from(entitiesByDb.keys());
301
+ const dbIdType = dbIds.map((id) => `'${id}'`).join(" | ");
302
+
303
+ const parts = [
304
+ `import { type Models, Permission } from '${appwriteDep}';`,
305
+ "",
306
+ ];
307
+
308
+ if (enums) {
309
+ parts.push(enums);
310
+ parts.push("");
311
+ }
312
+
313
+ parts.push(types);
314
+ parts.push("");
315
+ parts.push(this.generateQueryBuilderType());
316
+ parts.push("");
317
+ parts.push(`export type DatabaseId = ${dbIdType};`);
318
+ parts.push("");
319
+ parts.push(this.generateDatabaseTablesType(entitiesByDb, appwriteDep));
320
+ parts.push("");
321
+
322
+ return parts.join("\n");
323
+ }
324
+
325
+ private generateQueryBuilder(): string {
326
+ return `const createQueryBuilder = <T>(): QueryBuilder<T> => ({
327
+ equal: (field, value) => Query.equal(String(field), value as any),
328
+ notEqual: (field, value) => Query.notEqual(String(field), value as any),
329
+ lessThan: (field, value) => Query.lessThan(String(field), value as any),
330
+ lessThanEqual: (field, value) => Query.lessThanEqual(String(field), value as any),
331
+ greaterThan: (field, value) => Query.greaterThan(String(field), value as any),
332
+ greaterThanEqual: (field, value) => Query.greaterThanEqual(String(field), value as any),
333
+ contains: (field, value) => Query.contains(String(field), value as any),
334
+ search: (field, value) => Query.search(String(field), value),
335
+ isNull: (field) => Query.isNull(String(field)),
336
+ isNotNull: (field) => Query.isNotNull(String(field)),
337
+ startsWith: (field, value) => Query.startsWith(String(field), value),
338
+ endsWith: (field, value) => Query.endsWith(String(field), value),
339
+ between: (field, start, end) => Query.between(String(field), start as any, end as any),
340
+ select: (fields) => Query.select(fields.map(String)),
341
+ orderAsc: (field) => Query.orderAsc(String(field)),
342
+ orderDesc: (field) => Query.orderDesc(String(field)),
343
+ limit: (value) => Query.limit(value),
344
+ offset: (value) => Query.offset(value),
345
+ cursorAfter: (documentId) => Query.cursorAfter(documentId),
346
+ cursorBefore: (documentId) => Query.cursorBefore(documentId),
347
+ or: (...queries) => Query.or(queries),
348
+ and: (...queries) => Query.and(queries),
349
+ })`;
350
+ }
351
+
352
+ private generateTableIdMap(entitiesByDb: Map<string, Entity[]>): string {
353
+ const lines: string[] = [
354
+ "const tableIdMap: Record<string, Record<string, string>> = Object.create(null);",
355
+ ];
356
+
357
+ for (const [dbId, dbEntities] of entitiesByDb.entries()) {
358
+ lines.push(`tableIdMap[${JSON.stringify(dbId)}] = Object.create(null);`);
359
+ for (const entity of dbEntities) {
360
+ lines.push(
361
+ `tableIdMap[${JSON.stringify(dbId)}][${JSON.stringify(entity.name)}] = ${JSON.stringify(entity.$id)};`,
362
+ );
363
+ }
364
+ }
365
+
366
+ return lines.join("\n");
367
+ }
368
+
369
+ private generateTablesWithRelationships(
370
+ entitiesByDb: Map<string, Entity[]>,
371
+ ): string {
372
+ const tablesWithRelationships: string[] = [];
373
+
374
+ for (const [dbId, dbEntities] of entitiesByDb.entries()) {
375
+ for (const entity of dbEntities) {
376
+ if (this.hasRelationshipColumns(entity)) {
377
+ tablesWithRelationships.push(`'${dbId}:${entity.name}'`);
378
+ }
379
+ }
380
+ }
381
+
382
+ if (tablesWithRelationships.length === 0) {
383
+ return `const tablesWithRelationships = new Set<string>()`;
384
+ }
385
+
386
+ return `const tablesWithRelationships = new Set<string>([${tablesWithRelationships.join(", ")}])`;
387
+ }
388
+
389
+ private generateDatabasesFile(config: ConfigType): string {
390
+ const entities = config.tables?.length ? config.tables : config.collections;
391
+
392
+ if (!entities || entities.length === 0) {
393
+ return "// No tables or collections found in configuration\n";
394
+ }
395
+
396
+ const entitiesByDb = this.groupEntitiesByDb(entities);
397
+ const appwriteDep = this.getAppwriteDependency();
398
+ const supportsBulk = this.supportsBulkMethods(appwriteDep);
399
+
400
+ const bulkMethodsCode = supportsBulk
401
+ ? `
402
+ createMany: (rows: any[], options?: { transactionId?: string }) =>
403
+ tablesDB.createRows({
404
+ databaseId,
405
+ tableId,
406
+ rows,
407
+ transactionId: options?.transactionId,
408
+ }),
409
+ updateMany: (data: any, options?: { queries?: (q: any) => string[]; transactionId?: string }) =>
410
+ tablesDB.updateRows({
411
+ databaseId,
412
+ tableId,
413
+ data,
414
+ queries: options?.queries?.(createQueryBuilder()),
415
+ transactionId: options?.transactionId,
416
+ }),
417
+ deleteMany: (options?: { queries?: (q: any) => string[]; transactionId?: string }) =>
418
+ tablesDB.deleteRows({
419
+ databaseId,
420
+ tableId,
421
+ queries: options?.queries?.(createQueryBuilder()),
422
+ transactionId: options?.transactionId,
423
+ }),`
424
+ : "";
425
+
426
+ const hasBulkCheck = supportsBulk
427
+ ? `const hasBulkMethods = (dbId: string, tableName: string) => !tablesWithRelationships.has(\`\${dbId}:\${tableName}\`);`
428
+ : "";
429
+
430
+ return `import { Client, TablesDB, ID, Query, type Models, Permission } from '${appwriteDep}';
431
+ import type { DatabaseId, DatabaseTables, QueryBuilder } from './types.js';
432
+
433
+ ${this.generateQueryBuilder()};
434
+
435
+ ${this.generateTableIdMap(entitiesByDb)};
436
+
437
+ ${this.generateTablesWithRelationships(entitiesByDb)};
438
+
439
+ function createTableApi<T extends Models.Row>(
440
+ tablesDB: TablesDB,
441
+ databaseId: string,
442
+ tableId: string,
443
+ ) {
444
+ return {
445
+ create: (data: any, options?: { rowId?: string; permissions?: Permission[]; transactionId?: string }) =>
446
+ tablesDB.createRow<T>({
447
+ databaseId,
448
+ tableId,
449
+ rowId: options?.rowId ?? ID.unique(),
450
+ data,
451
+ permissions: options?.permissions?.map((p) => p.toString()),
452
+ transactionId: options?.transactionId,
453
+ }),
454
+ get: (id: string) =>
455
+ tablesDB.getRow<T>({
456
+ databaseId,
457
+ tableId,
458
+ rowId: id,
459
+ }),
460
+ update: (id: string, data: any, options?: { permissions?: Permission[]; transactionId?: string }) =>
461
+ tablesDB.updateRow<T>({
462
+ databaseId,
463
+ tableId,
464
+ rowId: id,
465
+ data,
466
+ ...(options?.permissions ? { permissions: options.permissions.map((p) => p.toString()) } : {}),
467
+ transactionId: options?.transactionId,
468
+ }),
469
+ delete: async (id: string, options?: { transactionId?: string }) => {
470
+ await tablesDB.deleteRow({
471
+ databaseId,
472
+ tableId,
473
+ rowId: id,
474
+ transactionId: options?.transactionId,
475
+ });
476
+ },
477
+ list: (options?: { queries?: (q: any) => string[] }) =>
478
+ tablesDB.listRows<T>({
479
+ databaseId,
480
+ tableId,
481
+ queries: options?.queries?.(createQueryBuilder<T>()),
482
+ }),${bulkMethodsCode}
483
+ };
484
+ }
485
+
486
+ ${hasBulkCheck}
487
+
488
+ const hasOwn = (obj: unknown, key: string): boolean =>
489
+ obj != null && Object.prototype.hasOwnProperty.call(obj, key);
490
+
491
+ function createDatabaseProxy<D extends DatabaseId>(
492
+ tablesDB: TablesDB,
493
+ databaseId: D,
494
+ ): DatabaseTables[D] {
495
+ const tableApiCache = new Map<string, ReturnType<typeof createTableApi>>();
496
+ const dbMap = tableIdMap[databaseId];
497
+
498
+ return new Proxy({} as DatabaseTables[D], {
499
+ get(_target, tableName: string) {
500
+ if (typeof tableName === 'symbol') return undefined;
501
+
502
+ if (!tableApiCache.has(tableName)) {
503
+ if (!hasOwn(dbMap, tableName)) return undefined;
504
+ const tableId = dbMap[tableName];
505
+
506
+ const api = createTableApi(tablesDB, databaseId, tableId);
507
+ ${
508
+ supportsBulk
509
+ ? `
510
+ // Remove bulk methods for tables with relationships
511
+ if (!hasBulkMethods(databaseId, tableName)) {
512
+ delete (api as any).createMany;
513
+ delete (api as any).updateMany;
514
+ delete (api as any).deleteMany;
515
+ }`
516
+ : ""
517
+ }
518
+ tableApiCache.set(tableName, api);
519
+ }
520
+ return tableApiCache.get(tableName);
521
+ },
522
+ has(_target, tableName: string) {
523
+ return typeof tableName === 'string' && hasOwn(dbMap, tableName);
524
+ },
525
+ });
526
+ }
527
+
528
+ export const createDatabases = (client: Client) => {
529
+ const tablesDB = new TablesDB(client);
530
+ const dbCache = new Map<DatabaseId, DatabaseTables[DatabaseId]>();
531
+
532
+ return {
533
+ from: <T extends DatabaseId>(databaseId: T): DatabaseTables[T] => {
534
+ if (!dbCache.has(databaseId)) {
535
+ dbCache.set(databaseId, createDatabaseProxy(tablesDB, databaseId));
536
+ }
537
+ return dbCache.get(databaseId) as DatabaseTables[T];
538
+ },
539
+ };
540
+ };
541
+ `;
542
+ }
543
+
544
+ private generateIndexFile(): string {
545
+ return `/**
546
+ * ${SDK_TITLE} Generated SDK
547
+ *
548
+ * This file is auto-generated. Do not edit manually.
549
+ * Re-run \`${EXECUTABLE_NAME} generate\` to regenerate.
550
+ */
551
+
552
+ export { createDatabases } from "./databases.js";
553
+ export * from "./types.js";
554
+ `;
555
+ }
556
+
557
+ async generate(config: ConfigType): Promise<GenerateResult> {
558
+ if (!config.projectId) {
559
+ throw new Error("Project ID is required in configuration");
560
+ }
561
+
562
+ const files = new Map<string, string>();
563
+
564
+ const hasEntities =
565
+ (config.tables && config.tables.length > 0) ||
566
+ (config.collections && config.collections.length > 0);
567
+
568
+ if (!hasEntities) {
569
+ console.log(
570
+ "No tables or collections found in configuration. Skipping database generation.",
571
+ );
572
+ files.set(
573
+ "databases.ts",
574
+ "// No tables or collections found in configuration\n",
575
+ );
576
+ files.set(
577
+ "types.ts",
578
+ "// No tables or collections found in configuration\n",
579
+ );
580
+ files.set("index.ts", this.generateIndexFile());
581
+ return { files };
582
+ }
583
+
584
+ files.set("types.ts", this.generateTypesFile(config));
585
+ files.set("databases.ts", this.generateDatabasesFile(config));
586
+ files.set("index.ts", this.generateIndexFile());
587
+
588
+ return { files };
589
+ }
590
+ }
@@ -39,8 +39,8 @@ import {
39
39
  import type { ConfigType } from "./config.js";
40
40
  import {
41
41
  DatabaseSchema,
42
- TablesDBSchemaBase,
43
- ColumnSchemaBase,
42
+ TableSchema,
43
+ ColumnSchema,
44
44
  BucketSchema,
45
45
  TopicSchema,
46
46
  } from "./config.js";
@@ -542,11 +542,11 @@ export class Pull {
542
542
  for (const table of tables) {
543
543
  // Filter columns to only include schema-defined fields
544
544
  const filteredColumns = table.columns?.map((col: any) =>
545
- filterBySchema(col, ColumnSchemaBase),
545
+ filterBySchema(col, ColumnSchema),
546
546
  );
547
547
 
548
548
  allTables.push({
549
- ...filterBySchema(table, TablesDBSchemaBase),
549
+ ...filterBySchema(table, TableSchema),
550
550
  columns: filteredColumns || [],
551
551
  });
552
552
  }