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.
- package/CHANGELOG.md +6 -0
- package/README.md +2 -2
- package/cli.ts +2 -0
- package/dist/bundle.cjs +17551 -16864
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/lib/commands/config-validations.d.ts +53 -0
- package/dist/lib/commands/config-validations.d.ts.map +1 -0
- package/dist/lib/commands/config-validations.js +130 -0
- package/dist/lib/commands/config-validations.js.map +1 -0
- package/dist/lib/commands/config.d.ts +241 -325
- package/dist/lib/commands/config.d.ts.map +1 -1
- package/dist/lib/commands/config.js +86 -107
- package/dist/lib/commands/config.js.map +1 -1
- package/dist/lib/commands/generate.d.ts +7 -0
- package/dist/lib/commands/generate.d.ts.map +1 -0
- package/dist/lib/commands/generate.js +92 -0
- package/dist/lib/commands/generate.js.map +1 -0
- package/dist/lib/commands/generators/base.d.ts +58 -0
- package/dist/lib/commands/generators/base.d.ts.map +1 -0
- package/dist/lib/commands/generators/base.js +27 -0
- package/dist/lib/commands/generators/base.js.map +1 -0
- package/dist/lib/commands/generators/index.d.ts +26 -0
- package/dist/lib/commands/generators/index.d.ts.map +1 -0
- package/dist/lib/commands/generators/index.js +55 -0
- package/dist/lib/commands/generators/index.js.map +1 -0
- package/dist/lib/commands/generators/language-detector.d.ts +42 -0
- package/dist/lib/commands/generators/language-detector.d.ts.map +1 -0
- package/dist/lib/commands/generators/language-detector.js +127 -0
- package/dist/lib/commands/generators/language-detector.js.map +1 -0
- package/dist/lib/commands/generators/typescript/databases.d.ts +36 -0
- package/dist/lib/commands/generators/typescript/databases.d.ts.map +1 -0
- package/dist/lib/commands/generators/typescript/databases.js +489 -0
- package/dist/lib/commands/generators/typescript/databases.js.map +1 -0
- package/dist/lib/commands/pull.js +3 -3
- package/dist/lib/commands/pull.js.map +1 -1
- package/dist/lib/commands/push.d.ts +6 -3
- package/dist/lib/commands/push.d.ts.map +1 -1
- package/dist/lib/commands/push.js +57 -37
- package/dist/lib/commands/push.js.map +1 -1
- package/dist/lib/commands/schema.d.ts +2 -2
- package/dist/lib/commands/schema.d.ts.map +1 -1
- package/dist/lib/commands/schema.js +2 -2
- package/dist/lib/commands/schema.js.map +1 -1
- package/dist/lib/commands/utils/attributes.d.ts +5 -0
- package/dist/lib/commands/utils/attributes.d.ts.map +1 -1
- package/dist/lib/commands/utils/attributes.js +20 -10
- package/dist/lib/commands/utils/attributes.js.map +1 -1
- package/dist/lib/commands/utils/pools.d.ts +0 -3
- package/dist/lib/commands/utils/pools.d.ts.map +1 -1
- package/dist/lib/commands/utils/pools.js +0 -34
- package/dist/lib/commands/utils/pools.js.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +28 -131
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/constants.d.ts +1 -1
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +1 -1
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +26 -0
- package/dist/lib/utils.js.map +1 -1
- package/dist/package.json +1 -1
- package/install.ps1 +2 -2
- package/install.sh +1 -1
- package/lib/commands/config-validations.ts +199 -0
- package/lib/commands/config.ts +94 -125
- package/lib/commands/generate.ts +142 -0
- package/lib/commands/generators/base.ts +91 -0
- package/lib/commands/generators/index.ts +87 -0
- package/lib/commands/generators/language-detector.ts +163 -0
- package/lib/commands/generators/typescript/databases.ts +590 -0
- package/lib/commands/pull.ts +4 -4
- package/lib/commands/push.ts +97 -58
- package/lib/commands/schema.ts +3 -3
- package/lib/commands/utils/attributes.ts +31 -11
- package/lib/commands/utils/pools.ts +0 -67
- package/lib/config.ts +43 -131
- package/lib/constants.ts +1 -1
- package/lib/utils.ts +32 -0
- package/package.json +1 -1
- package/scoop/appwrite.config.json +3 -3
- package/dist/lib/commands/db.d.ts +0 -34
- package/dist/lib/commands/db.d.ts.map +0 -1
- package/dist/lib/commands/db.js +0 -247
- package/dist/lib/commands/db.js.map +0 -1
- 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
|
+
}
|
package/lib/commands/pull.ts
CHANGED
|
@@ -39,8 +39,8 @@ import {
|
|
|
39
39
|
import type { ConfigType } from "./config.js";
|
|
40
40
|
import {
|
|
41
41
|
DatabaseSchema,
|
|
42
|
-
|
|
43
|
-
|
|
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,
|
|
545
|
+
filterBySchema(col, ColumnSchema),
|
|
546
546
|
);
|
|
547
547
|
|
|
548
548
|
allTables.push({
|
|
549
|
-
...filterBySchema(table,
|
|
549
|
+
...filterBySchema(table, TableSchema),
|
|
550
550
|
columns: filteredColumns || [],
|
|
551
551
|
});
|
|
552
552
|
}
|