appwrite-utils-cli 0.0.268 → 0.0.270

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.
@@ -0,0 +1,69 @@
1
+ import {
2
+ OpenAPIRegistry,
3
+ OpenApiGeneratorV3,
4
+ OpenApiGeneratorV31,
5
+ } from "@asteasolutions/zod-to-openapi";
6
+ import {
7
+ attributeSchema,
8
+ collectionSchema,
9
+ type AppwriteConfig,
10
+ type Attribute,
11
+ type Collection,
12
+ type CollectionCreate,
13
+ } from "./schema.js";
14
+ import { z } from "zod";
15
+ import { writeFileSync } from "fs";
16
+
17
+ const registry = new OpenAPIRegistry();
18
+
19
+ export const generateOpenApi = async (config: AppwriteConfig) => {
20
+ for (const collection of config.collections) {
21
+ // Transform and register each attribute schema
22
+ const attributeSchemas = collection.attributes.map((attribute) => {
23
+ return transformTypeToOpenApi(attributeSchema);
24
+ });
25
+
26
+ // Create and register the collection schema with descriptions
27
+ const updatedCollectionSchema = collectionSchema
28
+ .extend({
29
+ // @ts-ignore
30
+ attributes: z.array(z.union(attributeSchemas)),
31
+ })
32
+ .openapi(collection.description ?? "No description");
33
+
34
+ // Register the updated collection schema under the collection name
35
+ registry.register(collection.name, updatedCollectionSchema);
36
+ }
37
+
38
+ // Convert the registry to OpenAPI JSON
39
+ // @ts-ignore
40
+ const openApiSpec = registry.toOpenAPI();
41
+
42
+ // Output the OpenAPI spec to a file
43
+ writeFileSync(
44
+ "./appwrite/openapi/openapi.json",
45
+ JSON.stringify(openApiSpec, null, 2)
46
+ );
47
+ };
48
+
49
+ export function transformTypeToOpenApi<T extends z.ZodTypeAny>(
50
+ schema: T
51
+ ): z.infer<T> {
52
+ return schema.transform((data) => {
53
+ let finalData = data;
54
+ if (data._def.attributes) {
55
+ finalData._def.attributes = data._def.attributes.map(
56
+ (attribute: typeof attributeSchema) => {
57
+ if (attribute.description) {
58
+ return attribute.openapi(attribute.description);
59
+ }
60
+ return attribute;
61
+ }
62
+ );
63
+ }
64
+ if (schema.description) {
65
+ finalData.openapi(schema.description);
66
+ }
67
+ return finalData;
68
+ });
69
+ }
@@ -30,6 +30,14 @@ const stringAttributeSchema = z.object({
30
30
  .boolean()
31
31
  .optional()
32
32
  .describe("Whether the attribute is encrypted or not"),
33
+ format: z.string().nullish().describe("The format of the attribute"),
34
+ description: z
35
+ .string()
36
+ .or(z.record(z.string()))
37
+ .nullish()
38
+ .describe(
39
+ "The description of the attribute, also used for OpenApi Generation"
40
+ ),
33
41
  });
34
42
 
35
43
  type StringAttribute = z.infer<typeof stringAttributeSchema>;
@@ -68,6 +76,13 @@ const integerAttributeSchema = z.object({
68
76
  .int()
69
77
  .nullish()
70
78
  .describe("The default value of the attribute"),
79
+ description: z
80
+ .string()
81
+ .or(z.record(z.string()))
82
+ .nullish()
83
+ .describe(
84
+ "The description of the attribute, also used for OpenApi Generation"
85
+ ),
71
86
  });
72
87
 
73
88
  type IntegerAttribute = z.infer<typeof integerAttributeSchema>;
@@ -94,6 +109,13 @@ const floatAttributeSchema = z.object({
94
109
  min: z.number().optional().describe("The minimum value of the attribute"),
95
110
  max: z.number().optional().describe("The maximum value of the attribute"),
96
111
  xdefault: z.number().nullish().describe("The default value of the attribute"),
112
+ description: z
113
+ .string()
114
+ .or(z.record(z.string()))
115
+ .nullish()
116
+ .describe(
117
+ "The description of the attribute, also used for OpenApi Generation"
118
+ ),
97
119
  });
98
120
 
99
121
  type FloatAttribute = z.infer<typeof floatAttributeSchema>;
@@ -121,6 +143,13 @@ const booleanAttributeSchema = z.object({
121
143
  .boolean()
122
144
  .nullish()
123
145
  .describe("The default value of the attribute"),
146
+ description: z
147
+ .string()
148
+ .or(z.record(z.string()))
149
+ .nullish()
150
+ .describe(
151
+ "The description of the attribute, also used for OpenApi Generation"
152
+ ),
124
153
  });
125
154
 
126
155
  type BooleanAttribute = z.infer<typeof booleanAttributeSchema>;
@@ -145,6 +174,13 @@ const datetimeAttributeSchema = z.object({
145
174
  .default(false)
146
175
  .describe("Whether the attribute is an array or not"),
147
176
  xdefault: z.string().nullish().describe("The default value of the attribute"),
177
+ description: z
178
+ .string()
179
+ .or(z.record(z.string()))
180
+ .nullish()
181
+ .describe(
182
+ "The description of the attribute, also used for OpenApi Generation"
183
+ ),
148
184
  });
149
185
 
150
186
  type DatetimeAttribute = z.infer<typeof datetimeAttributeSchema>;
@@ -169,6 +205,13 @@ const emailAttributeSchema = z.object({
169
205
  .default(false)
170
206
  .describe("Whether the attribute is an array or not"),
171
207
  xdefault: z.string().nullish().describe("The default value of the attribute"),
208
+ description: z
209
+ .string()
210
+ .or(z.record(z.string()))
211
+ .nullish()
212
+ .describe(
213
+ "The description of the attribute, also used for OpenApi Generation"
214
+ ),
172
215
  });
173
216
 
174
217
  type EmailAttribute = z.infer<typeof emailAttributeSchema>;
@@ -190,6 +233,13 @@ const ipAttributeSchema = z.object({
190
233
  .default(false)
191
234
  .describe("Whether the attribute is an array or not"),
192
235
  xdefault: z.string().nullish().describe("The default value of the attribute"),
236
+ description: z
237
+ .string()
238
+ .or(z.record(z.string()))
239
+ .nullish()
240
+ .describe(
241
+ "The description of the attribute, also used for OpenApi Generation"
242
+ ),
193
243
  });
194
244
 
195
245
  type IpAttribute = z.infer<typeof ipAttributeSchema>;
@@ -211,6 +261,13 @@ const urlAttributeSchema = z.object({
211
261
  .default(false)
212
262
  .describe("Whether the attribute is an array or not"),
213
263
  xdefault: z.string().nullish().describe("The default value of the attribute"),
264
+ description: z
265
+ .string()
266
+ .or(z.record(z.string()))
267
+ .nullish()
268
+ .describe(
269
+ "The description of the attribute, also used for OpenApi Generation"
270
+ ),
214
271
  });
215
272
 
216
273
  type UrlAttribute = z.infer<typeof urlAttributeSchema>;
@@ -236,6 +293,13 @@ const enumAttributeSchema = z.object({
236
293
  .describe("The elements of the enum attribute")
237
294
  .default([]),
238
295
  xdefault: z.string().nullish().describe("The default value of the attribute"),
296
+ description: z
297
+ .string()
298
+ .or(z.record(z.string()))
299
+ .nullish()
300
+ .describe(
301
+ "The description of the attribute, also used for OpenApi Generation"
302
+ ),
239
303
  });
240
304
 
241
305
  type EnumAttribute = z.infer<typeof enumAttributeSchema>;
@@ -291,6 +355,13 @@ const relationshipAttributeSchema = z.object({
291
355
  .describe(
292
356
  "Configuration for mapping and resolving relationships during data import"
293
357
  ),
358
+ description: z
359
+ .string()
360
+ .or(z.record(z.string()))
361
+ .nullish()
362
+ .describe(
363
+ "The description of the attribute, also used for OpenApi Generation"
364
+ ),
294
365
  });
295
366
 
296
367
  export type RelationshipAttribute = z.infer<typeof relationshipAttributeSchema>;
@@ -313,16 +384,18 @@ export const createRelationshipAttributes = (
313
384
  });
314
385
  };
315
386
 
316
- export const attributeSchema = stringAttributeSchema
317
- .or(integerAttributeSchema)
318
- .or(floatAttributeSchema)
319
- .or(booleanAttributeSchema)
320
- .or(datetimeAttributeSchema)
321
- .or(emailAttributeSchema)
322
- .or(ipAttributeSchema)
323
- .or(urlAttributeSchema)
324
- .or(enumAttributeSchema)
325
- .or(relationshipAttributeSchema);
387
+ export const attributeSchema = z.discriminatedUnion("type", [
388
+ stringAttributeSchema,
389
+ integerAttributeSchema,
390
+ floatAttributeSchema,
391
+ booleanAttributeSchema,
392
+ datetimeAttributeSchema,
393
+ emailAttributeSchema,
394
+ ipAttributeSchema,
395
+ urlAttributeSchema,
396
+ enumAttributeSchema,
397
+ relationshipAttributeSchema,
398
+ ]);
326
399
 
327
400
  export const parseAttribute = (
328
401
  attribute:
@@ -551,6 +624,12 @@ export const collectionSchema = z.object({
551
624
  .boolean()
552
625
  .default(false)
553
626
  .describe("Whether document security is enabled or not"),
627
+ description: z
628
+ .string()
629
+ .optional()
630
+ .describe(
631
+ "The description of the collection, if any, used to generate OpenAPI documentation"
632
+ ),
554
633
  $createdAt: z.string(),
555
634
  $updatedAt: z.string(),
556
635
  $permissions: z
@@ -218,15 +218,22 @@ export class SchemaGenerator {
218
218
 
219
219
  typeToZod = (attribute: Attribute) => {
220
220
  let baseSchemaCode = "";
221
-
222
- switch (attribute.type) {
221
+ const finalAttribute: Attribute = (
222
+ attribute.type === "string" &&
223
+ attribute.format &&
224
+ attribute.format === "enum" &&
225
+ attribute.type === "string"
226
+ ? { ...attribute, type: attribute.format }
227
+ : attribute
228
+ ) as Attribute;
229
+ switch (finalAttribute.type) {
223
230
  case "string":
224
231
  baseSchemaCode = "z.string()";
225
- if (attribute.size) {
226
- baseSchemaCode += `.max(${attribute.size}, "Maximum length of ${attribute.size} characters exceeded")`;
232
+ if (finalAttribute.size) {
233
+ baseSchemaCode += `.max(${finalAttribute.size}, "Maximum length of ${finalAttribute.size} characters exceeded")`;
227
234
  }
228
- if (attribute.xdefault !== undefined) {
229
- baseSchemaCode += `.default("${attribute.xdefault}")`;
235
+ if (finalAttribute.xdefault !== undefined) {
236
+ baseSchemaCode += `.default("${finalAttribute.xdefault}")`;
230
237
  }
231
238
  if (!attribute.required && !attribute.array) {
232
239
  baseSchemaCode += ".nullish()";
@@ -234,93 +241,93 @@ export class SchemaGenerator {
234
241
  break;
235
242
  case "integer":
236
243
  baseSchemaCode = "z.number().int()";
237
- if (attribute.min !== undefined) {
238
- if (BigInt(attribute.min) === BigInt(-9223372036854776000)) {
239
- delete attribute.min;
244
+ if (finalAttribute.min !== undefined) {
245
+ if (BigInt(finalAttribute.min) === BigInt(-9223372036854776000)) {
246
+ delete finalAttribute.min;
240
247
  } else {
241
- baseSchemaCode += `.min(${attribute.min}, "Minimum value of ${attribute.min} not met")`;
248
+ baseSchemaCode += `.min(${finalAttribute.min}, "Minimum value of ${finalAttribute.min} not met")`;
242
249
  }
243
250
  }
244
- if (attribute.max !== undefined) {
245
- if (BigInt(attribute.max) === BigInt(9223372036854776000)) {
246
- delete attribute.max;
251
+ if (finalAttribute.max !== undefined) {
252
+ if (BigInt(finalAttribute.max) === BigInt(9223372036854776000)) {
253
+ delete finalAttribute.max;
247
254
  } else {
248
- baseSchemaCode += `.max(${attribute.max}, "Maximum value of ${attribute.max} exceeded")`;
255
+ baseSchemaCode += `.max(${finalAttribute.max}, "Maximum value of ${finalAttribute.max} exceeded")`;
249
256
  }
250
257
  }
251
- if (attribute.xdefault !== undefined) {
252
- baseSchemaCode += `.default(${attribute.xdefault})`;
258
+ if (finalAttribute.xdefault !== undefined) {
259
+ baseSchemaCode += `.default(${finalAttribute.xdefault})`;
253
260
  }
254
- if (!attribute.required && !attribute.array) {
261
+ if (!finalAttribute.required && !finalAttribute.array) {
255
262
  baseSchemaCode += ".nullish()";
256
263
  }
257
264
  break;
258
265
  case "float":
259
266
  baseSchemaCode = "z.number()";
260
- if (attribute.min !== undefined) {
261
- baseSchemaCode += `.min(${attribute.min}, "Minimum value of ${attribute.min} not met")`;
267
+ if (finalAttribute.min !== undefined) {
268
+ baseSchemaCode += `.min(${finalAttribute.min}, "Minimum value of ${finalAttribute.min} not met")`;
262
269
  }
263
- if (attribute.max !== undefined) {
264
- baseSchemaCode += `.max(${attribute.max}, "Maximum value of ${attribute.max} exceeded")`;
270
+ if (finalAttribute.max !== undefined) {
271
+ baseSchemaCode += `.max(${finalAttribute.max}, "Maximum value of ${finalAttribute.max} exceeded")`;
265
272
  }
266
- if (attribute.xdefault !== undefined) {
267
- baseSchemaCode += `.default(${attribute.xdefault})`;
273
+ if (finalAttribute.xdefault !== undefined) {
274
+ baseSchemaCode += `.default(${finalAttribute.xdefault})`;
268
275
  }
269
- if (!attribute.required && !attribute.array) {
276
+ if (!finalAttribute.required && !finalAttribute.array) {
270
277
  baseSchemaCode += ".nullish()";
271
278
  }
272
279
  break;
273
280
  case "boolean":
274
281
  baseSchemaCode = "z.boolean()";
275
- if (attribute.xdefault !== undefined) {
276
- baseSchemaCode += `.default(${attribute.xdefault})`;
282
+ if (finalAttribute.xdefault !== undefined) {
283
+ baseSchemaCode += `.default(${finalAttribute.xdefault})`;
277
284
  }
278
- if (!attribute.required && !attribute.array) {
285
+ if (!finalAttribute.required && !finalAttribute.array) {
279
286
  baseSchemaCode += ".nullish()";
280
287
  }
281
288
  break;
282
289
  case "datetime":
283
290
  baseSchemaCode = "z.date()";
284
- if (attribute.xdefault !== undefined) {
285
- baseSchemaCode += `.default(new Date("${attribute.xdefault}"))`;
291
+ if (finalAttribute.xdefault !== undefined) {
292
+ baseSchemaCode += `.default(new Date("${finalAttribute.xdefault}"))`;
286
293
  }
287
- if (!attribute.required && !attribute.array) {
294
+ if (!finalAttribute.required && !finalAttribute.array) {
288
295
  baseSchemaCode += ".nullish()";
289
296
  }
290
297
  break;
291
298
  case "email":
292
299
  baseSchemaCode = "z.string().email()";
293
- if (attribute.xdefault !== undefined) {
294
- baseSchemaCode += `.default("${attribute.xdefault}")`;
300
+ if (finalAttribute.xdefault !== undefined) {
301
+ baseSchemaCode += `.default("${finalAttribute.xdefault}")`;
295
302
  }
296
- if (!attribute.required && !attribute.array) {
303
+ if (!finalAttribute.required && !finalAttribute.array) {
297
304
  baseSchemaCode += ".nullish()";
298
305
  }
299
306
  break;
300
307
  case "ip":
301
308
  baseSchemaCode = "z.string()"; // Add custom validation as needed
302
- if (attribute.xdefault !== undefined) {
303
- baseSchemaCode += `.default("${attribute.xdefault}")`;
309
+ if (finalAttribute.xdefault !== undefined) {
310
+ baseSchemaCode += `.default("${finalAttribute.xdefault}")`;
304
311
  }
305
- if (!attribute.required && !attribute.array) {
312
+ if (!finalAttribute.required && !finalAttribute.array) {
306
313
  baseSchemaCode += ".nullish()";
307
314
  }
308
315
  break;
309
316
  case "url":
310
317
  baseSchemaCode = "z.string().url()";
311
- if (attribute.xdefault !== undefined) {
312
- baseSchemaCode += `.default("${attribute.xdefault}")`;
318
+ if (finalAttribute.xdefault !== undefined) {
319
+ baseSchemaCode += `.default("${finalAttribute.xdefault}")`;
313
320
  }
314
- if (!attribute.required && !attribute.array) {
321
+ if (!finalAttribute.required && !finalAttribute.array) {
315
322
  baseSchemaCode += ".nullish()";
316
323
  }
317
324
  break;
318
325
  case "enum":
319
- baseSchemaCode = `z.enum([${attribute.elements
326
+ baseSchemaCode = `z.enum([${finalAttribute.elements
320
327
  .map((element) => `"${element}"`)
321
328
  .join(", ")}])`;
322
- if (attribute.xdefault !== undefined) {
323
- baseSchemaCode += `.default("${attribute.xdefault}")`;
329
+ if (finalAttribute.xdefault !== undefined) {
330
+ baseSchemaCode += `.default("${finalAttribute.xdefault}")`;
324
331
  }
325
332
  if (!attribute.required && !attribute.array) {
326
333
  baseSchemaCode += ".nullish()";
@@ -49,6 +49,9 @@ export interface SetupOptions {
49
49
  importData: boolean;
50
50
  checkDuplicates: boolean;
51
51
  shouldWriteFile: boolean;
52
+ endpoint?: string;
53
+ project?: string;
54
+ key?: string;
52
55
  }
53
56
 
54
57
  type CollectionConfig = AppwriteConfig["collections"];
@@ -108,14 +111,27 @@ export class UtilsController {
108
111
  // }
109
112
  // }
110
113
 
111
- async init() {
114
+ async init(setupOptions: SetupOptions) {
112
115
  if (!this.config) {
113
116
  console.log("Initializing appwrite client & loading config...");
114
117
  this.config = await loadConfig(this.appwriteConfigPath);
115
- this.appwriteServer = new Client()
116
- .setEndpoint(this.config.appwriteEndpoint)
117
- .setProject(this.config.appwriteProject)
118
- .setKey(this.config.appwriteKey);
118
+ this.appwriteServer = new Client();
119
+ if (setupOptions.endpoint) {
120
+ if (!setupOptions.project || !setupOptions.key) {
121
+ throw new Error(
122
+ "Project ID and API key required when setting endpoint"
123
+ );
124
+ }
125
+ this.appwriteServer
126
+ .setEndpoint(setupOptions.endpoint)
127
+ .setProject(setupOptions.project)
128
+ .setKey(setupOptions.key);
129
+ } else {
130
+ this.appwriteServer
131
+ .setEndpoint(this.config.appwriteEndpoint)
132
+ .setProject(this.config.appwriteProject)
133
+ .setKey(this.config.appwriteKey);
134
+ }
119
135
  this.database = new Databases(this.appwriteServer);
120
136
  this.storage = new Storage(this.appwriteServer);
121
137
  this.config.appwriteClient = this.appwriteServer;
@@ -124,7 +140,7 @@ export class UtilsController {
124
140
  }
125
141
 
126
142
  async run(options: SetupOptions) {
127
- await this.init(); // Ensure initialization is done
143
+ await this.init(options); // Ensure initialization is done
128
144
  if (!this.database || !this.storage || !this.config) {
129
145
  throw new Error("Database or storage not initialized");
130
146
  }