appwrite-utils-cli 1.2.29 → 1.3.0

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,47 @@
1
+ {
2
+ "name": "appwrite-utils-cli",
3
+ "technologies": [
4
+ "Node.js",
5
+ "TypeScript"
6
+ ],
7
+ "dependencies": [
8
+ "@types/inquirer",
9
+ "@types/json-schema",
10
+ "@types/yargs",
11
+ "appwrite-utils",
12
+ "chalk",
13
+ "cli-progress",
14
+ "commander",
15
+ "es-toolkit",
16
+ "ignore",
17
+ "inquirer",
18
+ "js-yaml",
19
+ "luxon",
20
+ "nanostores",
21
+ "node-appwrite",
22
+ "p-limit",
23
+ "tar",
24
+ "tsx",
25
+ "ulidx",
26
+ "winston",
27
+ "yargs",
28
+ "zod",
29
+ "@types/cli-progress",
30
+ "@types/js-yaml",
31
+ "@types/lodash",
32
+ "@types/luxon",
33
+ "typescript"
34
+ ],
35
+ "entryPoints": [
36
+ "src/main.ts"
37
+ ],
38
+ "configFiles": [
39
+ "package.json",
40
+ "tsconfig.json"
41
+ ],
42
+ "buildOutputs": [
43
+ "dist"
44
+ ],
45
+ "description": "Appwrite Utility Functions to help with database management, data conversion, data import, migrations, and much more. Meant to be used as a CLI tool, I do not recommend installing this in frontend environments.",
46
+ "version": "1.2.30"
47
+ }
@@ -0,0 +1,132 @@
1
+ 📁 appwrite-utils-cli
2
+ 📁 .appwrite
3
+ 📁 .yaml_schemas
4
+ 📄 appwrite-config.schema.json
5
+ 📄 collection.schema.json
6
+ 📁 collections
7
+ 📄 Categories.yaml
8
+ 📄 ExampleCollection.yaml
9
+ 📄 Posts.yaml
10
+ 📄 Users.yaml
11
+ 📄 config.yaml
12
+ 📁 import
13
+ 📄 README.md
14
+ 📄 categories-import.yaml
15
+ 📄 posts-import.yaml
16
+ 📄 users-import.yaml
17
+ 📁 importData
18
+ 📄 categories.json
19
+ 📄 posts.json
20
+ 📄 users.json
21
+ 📁 schemas
22
+ 📄 categories.json
23
+ 📄 exampleCollection.json
24
+ 📄 posts.json
25
+ 📄 users.json
26
+ 📄 .gitignore
27
+ 📄 .npmignore
28
+ 📄 README.md
29
+ 📁 dist
30
+ 📁 node_modules
31
+ 📄 package.json
32
+ 📁 src
33
+ 📁 collections
34
+ 📄 attributes.ts
35
+ 📄 indexes.ts
36
+ 📄 methods.ts
37
+ 📁 config
38
+ 📄 yamlConfig.ts
39
+ 📁 databases
40
+ 📄 methods.ts
41
+ 📄 setup.ts
42
+ 📁 functions
43
+ 📄 deployments.ts
44
+ 📄 methods.ts
45
+ 📄 openapi.ts
46
+ 📁 templates
47
+ 📁 count-docs-in-collection
48
+ 📄 README.md
49
+ 📄 package.json
50
+ 📁 src
51
+ 📄 main.ts
52
+ 📄 request.ts
53
+ 📄 tsconfig.json
54
+ 📁 typescript-node
55
+ 📄 README.md
56
+ 📄 package.json
57
+ 📁 src
58
+ 📄 index.ts
59
+ 📄 tsconfig.json
60
+ 📁 uv
61
+ 📄 README.md
62
+ 📄 pyproject.toml
63
+ 📁 src
64
+ 📄 __init__.py
65
+ 📄 index.py
66
+ 📄 init.ts
67
+ 📄 interactiveCLI.ts
68
+ 📄 main.ts
69
+ 📁 migrations
70
+ 📄 afterImportActions.ts
71
+ 📄 appwriteToX.ts
72
+ 📄 comprehensiveTransfer.ts
73
+ 📄 dataLoader.ts
74
+ 📄 importController.ts
75
+ 📄 importDataActions.ts
76
+ 📄 relationships.ts
77
+ 📁 services
78
+ 📄 DataTransformationService.ts
79
+ 📄 FileHandlerService.ts
80
+ 📄 ImportOrchestrator.ts
81
+ 📄 RateLimitManager.ts
82
+ 📄 RelationshipResolver.ts
83
+ 📄 UserMappingService.ts
84
+ 📄 ValidationService.ts
85
+ 📄 transfer.ts
86
+ 📁 yaml
87
+ 📄 YamlImportConfigLoader.ts
88
+ 📄 YamlImportIntegration.ts
89
+ 📄 generateImportSchemas.ts
90
+ 📁 schemas
91
+ 📄 authUser.ts
92
+ 📄 setup.ts
93
+ 📄 setupCommands.ts
94
+ 📄 setupController.ts
95
+ 📁 shared
96
+ 📄 attributeManager.ts
97
+ 📄 confirmationDialogs.ts
98
+ 📄 functionManager.ts
99
+ 📄 indexManager.ts
100
+ 📄 jsonSchemaGenerator.ts
101
+ 📄 logging.ts
102
+ 📄 messageFormatter.ts
103
+ 📄 migrationHelpers.ts
104
+ 📄 operationLogger.ts
105
+ 📄 operationQueue.ts
106
+ 📄 progressManager.ts
107
+ 📄 schemaGenerator.ts
108
+ 📁 storage
109
+ 📄 methods.ts
110
+ 📄 schemas.ts
111
+ 📄 types.ts
112
+ 📁 users
113
+ 📄 methods.ts
114
+ 📁 utils
115
+ 📄 configMigration.ts
116
+ 📄 constantsGenerator.ts
117
+ 📄 dataConverters.ts
118
+ 📄 getClientFromConfig.ts
119
+ 📄 helperFunctions.ts
120
+ 📄 index.ts
121
+ 📄 loadConfigs.ts
122
+ 📄 retryFailedPromises.ts
123
+ 📄 schemaStrings.ts
124
+ 📄 setupFiles.ts
125
+ 📄 validationRules.ts
126
+ 📄 yamlConverter.ts
127
+ 📄 utilsController.ts
128
+ 📄 tsconfig.json
129
+ 📁 zlogs
130
+ 📄 error.log
131
+ 📄 info.log
132
+ 📄 warn.log
package/README.md CHANGED
@@ -327,6 +327,58 @@ This updated CLI ensures that developers have robust tools at their fingertips t
327
327
 
328
328
  ## Changelog
329
329
 
330
+ ### 1.3.0 - Zod v4 Upgrade & Collection Management Fixes
331
+
332
+ **🎉 Major Release - Zod v4 Compatibility & Reliability Improvements**
333
+
334
+ #### Breaking Changes
335
+ - **Zod v4 Upgrade**: Updated to Zod v4.0.0 for enhanced schema validation
336
+ - ⚠️ **Potential Breaking Change**: Some schema validations may behave differently
337
+ - Function event types are now more flexible to accommodate Zod v4 changes
338
+ - Review your validation schemas if you experience issues
339
+
340
+ #### Major Bug Fixes
341
+ - **Fixed Duplicate Attribute Creation**: Resolved issue where attributes were being created multiple times
342
+ - Implemented intelligent filtering to only process new or changed attributes
343
+ - Enhanced status monitoring and error handling
344
+ - Significantly improved sync performance and reliability
345
+
346
+ - **Fixed Index Creation Issues**: Resolved indexes not being created from collection configurations
347
+ - Added proper null checks for index arrays from different collection sources
348
+ - Enhanced index creation with comprehensive status monitoring
349
+ - Improved error handling and retry logic for stuck indexes
350
+
351
+ #### Enhanced Collection Management
352
+ - **Smart Attribute Processing**: Attributes are now only created/updated when needed
353
+ - Compares existing vs. config attributes before processing
354
+ - Skips unchanged attributes with clear logging
355
+ - Better handling of edge cases and error conditions
356
+
357
+ - **Improved Index Handling**: More robust index creation from collection configs
358
+ - Proper fallback logic when indexes are undefined
359
+ - Enhanced compatibility with different collection sources
360
+ - Better error reporting and debugging information
361
+
362
+ #### Performance Improvements
363
+ - **Optimized Sync Operations**: Collections now process only necessary changes
364
+ - **Enhanced Status Monitoring**: Real-time feedback for attribute and index operations
365
+ - **Better Resource Management**: Reduced API calls through intelligent filtering
366
+
367
+ #### Developer Experience
368
+ - **Build Stability**: Resolved TypeScript compilation issues with function schemas
369
+ - **Type Safety**: Maintained strict typing while accommodating Zod v4 changes
370
+ - **Enhanced Logging**: Better progress reporting and error messages
371
+
372
+ #### Migration Guide
373
+ 1. **Update Dependencies**: Ensure compatibility with Zod v4.0.0
374
+ 2. **Test Collection Operations**: Verify that attribute and index creation works as expected
375
+ 3. **Review Validations**: Check any custom validation schemas for potential breaking changes
376
+ 4. **Function Events**: Function event arrays are now `string[]` for enhanced flexibility
377
+
378
+ **Integration Note**: This version significantly improves collection management reliability and provides full Zod v4 compatibility. The fixes address core synchronization issues that could cause duplicate resources or missing indexes.
379
+
380
+ ---
381
+
330
382
  ### 1.1.0 - Enhanced Transfer System with Fault Tolerance
331
383
 
332
384
  **🔧 Robust Transfer Operations with Status Monitoring**
@@ -277,7 +277,8 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
277
277
  nameToIdMapping.set(finalAttribute.relatedCollection, relatedCollectionId);
278
278
  }
279
279
  }
280
- if (!(relatedCollectionId && collectionFoundViaRelatedCollection)) {
280
+ // Only queue relationship attributes that have dependencies
281
+ if (finalAttribute.type === "relationship" && !(relatedCollectionId && collectionFoundViaRelatedCollection)) {
281
282
  // console.log(`Enqueueing operation for attribute: ${finalAttribute.key}`);
282
283
  enqueueOperation({
283
284
  type: "attribute",
@@ -1,7 +1,7 @@
1
1
  import { Client, Databases, ID, Permission, Query, } from "node-appwrite";
2
2
  import { nameToIdMapping, processQueue } from "../shared/operationQueue.js";
3
- import { createUpdateCollectionAttributes } from "./attributes.js";
4
- import { createOrUpdateIndexes } from "./indexes.js";
3
+ import { createUpdateCollectionAttributes, createUpdateCollectionAttributesWithStatusCheck } from "./attributes.js";
4
+ import { createOrUpdateIndexes, createOrUpdateIndexesWithStatusCheck } from "./indexes.js";
5
5
  import { SchemaGenerator } from "../shared/schemaGenerator.js";
6
6
  import { isNull, isUndefined, isNil, isPlainObject, isString, isJSONValue, chunk, } from "es-toolkit";
7
7
  import { delay, tryAwaitWithRetry } from "../utils/helperFunctions.js";
@@ -267,17 +267,17 @@ export const createOrUpdateCollections = async (database, databaseId, config, de
267
267
  await delay(250);
268
268
  // Update attributes and indexes for the collection
269
269
  MessageFormatter.progress("Creating Attributes", { prefix: "Collections" });
270
- await createUpdateCollectionAttributes(database, databaseId, collectionToUse,
270
+ await createUpdateCollectionAttributesWithStatusCheck(database, databaseId, collectionToUse,
271
271
  // @ts-expect-error
272
272
  attributes);
273
273
  // Add delay after creating attributes
274
274
  await delay(250);
275
- const indexesToUse = indexes.length > 0
275
+ const indexesToUse = indexes && indexes.length > 0
276
276
  ? indexes
277
277
  : config.collections?.find((c) => c.$id === collectionToUse.$id)
278
278
  ?.indexes ?? [];
279
279
  MessageFormatter.progress("Creating Indexes", { prefix: "Collections" });
280
- await createOrUpdateIndexes(databaseId, database, collectionToUse.$id, indexesToUse);
280
+ await createOrUpdateIndexesWithStatusCheck(databaseId, database, collectionToUse.$id, collectionToUse, indexesToUse);
281
281
  // Add delay after creating indexes
282
282
  await delay(250);
283
283
  }
@@ -32,14 +32,20 @@ export const attributesSame = (databaseAttribute, configAttribute) => {
32
32
  const configValue = configAttribute[attr];
33
33
  // Use type-specific default values when comparing
34
34
  if (databaseAttribute.type === "integer") {
35
- const defaultMin = attr === "min" ? -2147483647 : undefined;
36
- const defaultMax = attr === "max" ? 2147483647 : undefined;
37
- return (dbValue ?? defaultMin) === (configValue ?? defaultMax);
35
+ if (attr === "min") {
36
+ return (dbValue ?? -2147483647) === (configValue ?? -2147483647);
37
+ }
38
+ else { // attr === "max"
39
+ return (dbValue ?? 2147483647) === (configValue ?? 2147483647);
40
+ }
38
41
  }
39
42
  if (databaseAttribute.type === "double" || databaseAttribute.type === "float") {
40
- const defaultMin = attr === "min" ? -2147483647 : undefined;
41
- const defaultMax = attr === "max" ? 2147483647 : undefined;
42
- return (dbValue ?? defaultMin) === (configValue ?? defaultMax);
43
+ if (attr === "min") {
44
+ return (dbValue ?? -2147483647) === (configValue ?? -2147483647);
45
+ }
46
+ else { // attr === "max"
47
+ return (dbValue ?? 2147483647) === (configValue ?? 2147483647);
48
+ }
43
49
  }
44
50
  }
45
51
  // Check if both objects have the attribute
@@ -354,62 +354,86 @@ export default appwriteConfig;
354
354
  const isArray = detail.isArray ? "array" : "";
355
355
  return [relatedCollectionName, key, isArray];
356
356
  });
357
- let relatedTypes = "";
358
- let relatedTypesLazy = "";
359
- let curNum = 0;
360
- let maxNum = relatedCollections.length;
361
- relatedCollections.forEach((relatedCollection) => {
362
- console.log(relatedCollection);
363
- let relatedPascalName = toPascalCase(relatedCollection[0]);
364
- let relatedCamelName = toCamelCase(relatedCollection[0]);
365
- curNum++;
366
- let endNameTypes = relatedPascalName;
367
- let endNameLazy = `${relatedPascalName}Schema`;
368
- if (relatedCollection[2] === "array") {
369
- endNameTypes += "[]";
370
- endNameLazy += ".array().default([])";
357
+ // Check if we have any relationships - if not, generate simple schema
358
+ const hasRelationships = relatedCollections.length > 0;
359
+ let schemaString = `${imports}\n\n`;
360
+ if (!hasRelationships) {
361
+ // Simple case: no relationships, generate single schema directly
362
+ schemaString += `export const ${pascalName}Schema = z.object({\n`;
363
+ schemaString += ` $id: z.string(),\n`;
364
+ schemaString += ` $createdAt: z.string(),\n`;
365
+ schemaString += ` $updatedAt: z.string(),\n`;
366
+ schemaString += ` $permissions: z.array(z.string()),\n`;
367
+ for (const attribute of attributes) {
368
+ if (attribute.type === "relationship") {
369
+ continue;
370
+ }
371
+ schemaString += ` ${attribute.key}: ${this.typeToZod(attribute)},\n`;
371
372
  }
372
- else if (!(relatedCollection[2] === "array")) {
373
- endNameTypes += " | null";
374
- endNameLazy += ".nullish()";
373
+ schemaString += `});\n\n`;
374
+ schemaString += `export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;\n\n`;
375
+ }
376
+ else {
377
+ // Complex case: has relationships, generate BaseSchema + extended schema pattern
378
+ let relatedTypes = "";
379
+ let relatedTypesLazy = "";
380
+ let curNum = 0;
381
+ let maxNum = relatedCollections.length;
382
+ relatedCollections.forEach((relatedCollection) => {
383
+ console.log(relatedCollection);
384
+ let relatedPascalName = toPascalCase(relatedCollection[0]);
385
+ let relatedCamelName = toCamelCase(relatedCollection[0]);
386
+ curNum++;
387
+ let endNameTypes = relatedPascalName;
388
+ let endNameLazy = `${relatedPascalName}Schema`;
389
+ if (relatedCollection[2] === "array") {
390
+ endNameTypes += "[]";
391
+ endNameLazy += ".array().default([])";
392
+ }
393
+ else if (!(relatedCollection[2] === "array")) {
394
+ endNameTypes += " | null";
395
+ endNameLazy += ".nullish()";
396
+ }
397
+ imports += `import { ${relatedPascalName}Schema, type ${relatedPascalName} } from "./${relatedCamelName}";\n`;
398
+ relatedTypes += `${relatedCollection[1]}?: ${endNameTypes};\n`;
399
+ if (relatedTypes.length > 0 && curNum !== maxNum) {
400
+ relatedTypes += " ";
401
+ }
402
+ relatedTypesLazy += `${relatedCollection[1]}: z.lazy(() => ${endNameLazy}),\n`;
403
+ if (relatedTypesLazy.length > 0 && curNum !== maxNum) {
404
+ relatedTypesLazy += " ";
405
+ }
406
+ });
407
+ // Re-add imports after processing relationships
408
+ schemaString = `${imports}\n\n`;
409
+ schemaString += `export const ${pascalName}SchemaBase = z.object({\n`;
410
+ schemaString += ` $id: z.string(),\n`;
411
+ schemaString += ` $createdAt: z.string(),\n`;
412
+ schemaString += ` $updatedAt: z.string(),\n`;
413
+ schemaString += ` $permissions: z.array(z.string()),\n`;
414
+ for (const attribute of attributes) {
415
+ if (attribute.type === "relationship") {
416
+ continue;
417
+ }
418
+ schemaString += ` ${attribute.key}: ${this.typeToZod(attribute)},\n`;
375
419
  }
376
- imports += `import { ${relatedPascalName}Schema, type ${relatedPascalName} } from "./${relatedCamelName}";\n`;
377
- relatedTypes += `${relatedCollection[1]}?: ${endNameTypes};\n`;
378
- if (relatedTypes.length > 0 && curNum !== maxNum) {
379
- relatedTypes += " ";
420
+ schemaString += `});\n\n`;
421
+ schemaString += `export type ${pascalName}Base = z.infer<typeof ${pascalName}SchemaBase>`;
422
+ if (relatedTypes.length > 0) {
423
+ schemaString += ` & {\n ${relatedTypes}};\n\n`;
380
424
  }
381
- relatedTypesLazy += `${relatedCollection[1]}: z.lazy(() => ${endNameLazy}),\n`;
382
- if (relatedTypesLazy.length > 0 && curNum !== maxNum) {
383
- relatedTypesLazy += " ";
425
+ else {
426
+ schemaString += `;\n\n`;
384
427
  }
385
- });
386
- let schemaString = `${imports}\n\n`;
387
- schemaString += `export const ${pascalName}SchemaBase = z.object({\n`;
388
- schemaString += ` $id: z.string().optional(),\n`;
389
- schemaString += ` $createdAt: z.string().optional(),\n`;
390
- schemaString += ` $updatedAt: z.string().optional(),\n`;
391
- for (const attribute of attributes) {
392
- if (attribute.type === "relationship") {
393
- continue;
428
+ schemaString += `export const ${pascalName}Schema: z.ZodType<${pascalName}Base> = ${pascalName}SchemaBase`;
429
+ if (relatedTypes.length > 0) {
430
+ schemaString += `.extend({\n ${relatedTypesLazy}});\n\n`;
394
431
  }
395
- schemaString += ` ${attribute.key}: ${this.typeToZod(attribute)},\n`;
396
- }
397
- schemaString += `});\n\n`;
398
- schemaString += `export type ${pascalName}Base = z.infer<typeof ${pascalName}SchemaBase>`;
399
- if (relatedTypes.length > 0) {
400
- schemaString += ` & {\n ${relatedTypes}};\n\n`;
401
- }
402
- else {
403
- schemaString += `;\n\n`;
404
- }
405
- schemaString += `export const ${pascalName}Schema: z.ZodType<${pascalName}Base> = ${pascalName}SchemaBase`;
406
- if (relatedTypes.length > 0) {
407
- schemaString += `.extend({\n ${relatedTypesLazy}});\n\n`;
408
- }
409
- else {
410
- schemaString += `;\n`;
432
+ else {
433
+ schemaString += `;\n`;
434
+ }
435
+ schemaString += `export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;\n\n`;
411
436
  }
412
- schemaString += `export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;\n\n`;
413
437
  return schemaString;
414
438
  };
415
439
  typeToZod = (attribute) => {
@@ -233,62 +233,86 @@ export class SchemaGenerator {
233
233
  const isArray = detail.isArray ? "array" : "";
234
234
  return [relatedCollectionName, key, isArray];
235
235
  });
236
- let relatedTypes = "";
237
- let relatedTypesLazy = "";
238
- let curNum = 0;
239
- let maxNum = relatedCollections.length;
240
- relatedCollections.forEach((relatedCollection) => {
241
- console.log(relatedCollection);
242
- let relatedPascalName = toPascalCase(relatedCollection[0]);
243
- let relatedCamelName = toCamelCase(relatedCollection[0]);
244
- curNum++;
245
- let endNameTypes = relatedPascalName;
246
- let endNameLazy = `${relatedPascalName}Schema`;
247
- if (relatedCollection[2] === "array") {
248
- endNameTypes += "[]";
249
- endNameLazy += ".array().default([])";
236
+ // Check if we have any relationships - if not, generate simple schema
237
+ const hasRelationships = relatedCollections.length > 0;
238
+ let schemaString = `${imports}\n\n`;
239
+ if (!hasRelationships) {
240
+ // Simple case: no relationships, generate single schema directly
241
+ schemaString += `export const ${pascalName}Schema = z.object({\n`;
242
+ schemaString += ` $id: z.string(),\n`;
243
+ schemaString += ` $createdAt: z.string(),\n`;
244
+ schemaString += ` $updatedAt: z.string(),\n`;
245
+ schemaString += ` $permissions: z.array(z.string()),\n`;
246
+ for (const attribute of attributes) {
247
+ if (attribute.type === "relationship") {
248
+ continue;
249
+ }
250
+ schemaString += ` ${attribute.key}: ${this.typeToZod(attribute)},\n`;
250
251
  }
251
- else if (!(relatedCollection[2] === "array")) {
252
- endNameTypes += " | null";
253
- endNameLazy += ".nullish()";
252
+ schemaString += `});\n\n`;
253
+ schemaString += `export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;\n\n`;
254
+ }
255
+ else {
256
+ // Complex case: has relationships, generate BaseSchema + extended schema pattern
257
+ let relatedTypes = "";
258
+ let relatedTypesLazy = "";
259
+ let curNum = 0;
260
+ let maxNum = relatedCollections.length;
261
+ relatedCollections.forEach((relatedCollection) => {
262
+ console.log(relatedCollection);
263
+ let relatedPascalName = toPascalCase(relatedCollection[0]);
264
+ let relatedCamelName = toCamelCase(relatedCollection[0]);
265
+ curNum++;
266
+ let endNameTypes = relatedPascalName;
267
+ let endNameLazy = `${relatedPascalName}Schema`;
268
+ if (relatedCollection[2] === "array") {
269
+ endNameTypes += "[]";
270
+ endNameLazy += ".array().default([])";
271
+ }
272
+ else if (!(relatedCollection[2] === "array")) {
273
+ endNameTypes += " | null";
274
+ endNameLazy += ".nullish()";
275
+ }
276
+ imports += `import { ${relatedPascalName}Schema, type ${relatedPascalName} } from "./${relatedCamelName}";\n`;
277
+ relatedTypes += `${relatedCollection[1]}?: ${endNameTypes};\n`;
278
+ if (relatedTypes.length > 0 && curNum !== maxNum) {
279
+ relatedTypes += " ";
280
+ }
281
+ relatedTypesLazy += `${relatedCollection[1]}: z.lazy(() => ${endNameLazy}),\n`;
282
+ if (relatedTypesLazy.length > 0 && curNum !== maxNum) {
283
+ relatedTypesLazy += " ";
284
+ }
285
+ });
286
+ // Re-add imports after processing relationships
287
+ schemaString = `${imports}\n\n`;
288
+ schemaString += `export const ${pascalName}SchemaBase = z.object({\n`;
289
+ schemaString += ` $id: z.string(),\n`;
290
+ schemaString += ` $createdAt: z.string(),\n`;
291
+ schemaString += ` $updatedAt: z.string(),\n`;
292
+ schemaString += ` $permissions: z.array(z.string()),\n`;
293
+ for (const attribute of attributes) {
294
+ if (attribute.type === "relationship") {
295
+ continue;
296
+ }
297
+ schemaString += ` ${attribute.key}: ${this.typeToZod(attribute)},\n`;
254
298
  }
255
- imports += `import { ${relatedPascalName}Schema, type ${relatedPascalName} } from "./${relatedCamelName}";\n`;
256
- relatedTypes += `${relatedCollection[1]}?: ${endNameTypes};\n`;
257
- if (relatedTypes.length > 0 && curNum !== maxNum) {
258
- relatedTypes += " ";
299
+ schemaString += `});\n\n`;
300
+ schemaString += `export type ${pascalName}Base = z.infer<typeof ${pascalName}SchemaBase>`;
301
+ if (relatedTypes.length > 0) {
302
+ schemaString += ` & {\n ${relatedTypes}};\n\n`;
259
303
  }
260
- relatedTypesLazy += `${relatedCollection[1]}: z.lazy(() => ${endNameLazy}),\n`;
261
- if (relatedTypesLazy.length > 0 && curNum !== maxNum) {
262
- relatedTypesLazy += " ";
304
+ else {
305
+ schemaString += `;\n\n`;
263
306
  }
264
- });
265
- let schemaString = `${imports}\n\n`;
266
- schemaString += `export const ${pascalName}SchemaBase = z.object({\n`;
267
- schemaString += ` $id: z.string().optional(),\n`;
268
- schemaString += ` $createdAt: z.string().optional(),\n`;
269
- schemaString += ` $updatedAt: z.string().optional(),\n`;
270
- for (const attribute of attributes) {
271
- if (attribute.type === "relationship") {
272
- continue;
307
+ schemaString += `export const ${pascalName}Schema: z.ZodType<${pascalName}Base> = ${pascalName}SchemaBase`;
308
+ if (relatedTypes.length > 0) {
309
+ schemaString += `.extend({\n ${relatedTypesLazy}});\n\n`;
273
310
  }
274
- schemaString += ` ${attribute.key}: ${this.typeToZod(attribute)},\n`;
275
- }
276
- schemaString += `});\n\n`;
277
- schemaString += `export type ${pascalName}Base = z.infer<typeof ${pascalName}SchemaBase>`;
278
- if (relatedTypes.length > 0) {
279
- schemaString += ` & {\n ${relatedTypes}};\n\n`;
280
- }
281
- else {
282
- schemaString += `;\n\n`;
283
- }
284
- schemaString += `export const ${pascalName}Schema: z.ZodType<${pascalName}Base> = ${pascalName}SchemaBase`;
285
- if (relatedTypes.length > 0) {
286
- schemaString += `.extend({\n ${relatedTypesLazy}});\n\n`;
287
- }
288
- else {
289
- schemaString += `;\n`;
311
+ else {
312
+ schemaString += `;\n`;
313
+ }
314
+ schemaString += `export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;\n\n`;
290
315
  }
291
- schemaString += `export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;\n\n`;
292
316
  return schemaString;
293
317
  };
294
318
  typeToZod = (attribute) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "appwrite-utils-cli",
3
3
  "description": "Appwrite Utility Functions to help with database management, data conversion, data import, migrations, and much more. Meant to be used as a CLI tool, I do not recommend installing this in frontend environments.",
4
- "version": "1.2.29",
4
+ "version": "1.3.0",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -50,7 +50,7 @@
50
50
  "ulidx": "^2.4.1",
51
51
  "winston": "^3.17.0",
52
52
  "yargs": "^18.0.0",
53
- "zod": "^3.25.67"
53
+ "zod": "^4.0.0"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/cli-progress": "^3.11.6",
@@ -437,7 +437,8 @@ export const createOrUpdateAttribute = async (
437
437
  );
438
438
  }
439
439
  }
440
- if (!(relatedCollectionId && collectionFoundViaRelatedCollection)) {
440
+ // Only queue relationship attributes that have dependencies
441
+ if (finalAttribute.type === "relationship" && !(relatedCollectionId && collectionFoundViaRelatedCollection)) {
441
442
  // console.log(`Enqueueing operation for attribute: ${finalAttribute.key}`);
442
443
  enqueueOperation({
443
444
  type: "attribute",
@@ -8,8 +8,8 @@ import {
8
8
  } from "node-appwrite";
9
9
  import type { AppwriteConfig, CollectionCreate, Indexes } from "appwrite-utils";
10
10
  import { nameToIdMapping, processQueue } from "../shared/operationQueue.js";
11
- import { createUpdateCollectionAttributes } from "./attributes.js";
12
- import { createOrUpdateIndexes } from "./indexes.js";
11
+ import { createUpdateCollectionAttributes, createUpdateCollectionAttributesWithStatusCheck } from "./attributes.js";
12
+ import { createOrUpdateIndexes, createOrUpdateIndexesWithStatusCheck } from "./indexes.js";
13
13
  import { SchemaGenerator } from "../shared/schemaGenerator.js";
14
14
  import {
15
15
  isNull,
@@ -426,7 +426,7 @@ export const createOrUpdateCollections = async (
426
426
 
427
427
  // Update attributes and indexes for the collection
428
428
  MessageFormatter.progress("Creating Attributes", { prefix: "Collections" });
429
- await createUpdateCollectionAttributes(
429
+ await createUpdateCollectionAttributesWithStatusCheck(
430
430
  database,
431
431
  databaseId,
432
432
  collectionToUse!,
@@ -438,16 +438,17 @@ export const createOrUpdateCollections = async (
438
438
  await delay(250);
439
439
 
440
440
  const indexesToUse =
441
- indexes.length > 0
441
+ indexes && indexes.length > 0
442
442
  ? indexes
443
443
  : config.collections?.find((c) => c.$id === collectionToUse!.$id)
444
444
  ?.indexes ?? [];
445
445
 
446
446
  MessageFormatter.progress("Creating Indexes", { prefix: "Collections" });
447
- await createOrUpdateIndexes(
447
+ await createOrUpdateIndexesWithStatusCheck(
448
448
  databaseId,
449
449
  database,
450
450
  collectionToUse!.$id,
451
+ collectionToUse!,
451
452
  indexesToUse as Indexes
452
453
  );
453
454
 
@@ -43,14 +43,18 @@ export const attributesSame = (
43
43
 
44
44
  // Use type-specific default values when comparing
45
45
  if (databaseAttribute.type === "integer") {
46
- const defaultMin = attr === "min" ? -2147483647 : undefined;
47
- const defaultMax = attr === "max" ? 2147483647 : undefined;
48
- return (dbValue ?? defaultMin) === (configValue ?? defaultMax);
46
+ if (attr === "min") {
47
+ return (dbValue ?? -2147483647) === (configValue ?? -2147483647);
48
+ } else { // attr === "max"
49
+ return (dbValue ?? 2147483647) === (configValue ?? 2147483647);
50
+ }
49
51
  }
50
52
  if (databaseAttribute.type === "double" || databaseAttribute.type === "float") {
51
- const defaultMin = attr === "min" ? -2147483647 : undefined;
52
- const defaultMax = attr === "max" ? 2147483647 : undefined;
53
- return (dbValue ?? defaultMin) === (configValue ?? defaultMax);
53
+ if (attr === "min") {
54
+ return (dbValue ?? -2147483647) === (configValue ?? -2147483647);
55
+ } else { // attr === "max"
56
+ return (dbValue ?? 2147483647) === (configValue ?? 2147483647);
57
+ }
54
58
  }
55
59
  }
56
60
 
@@ -443,60 +443,87 @@ export default appwriteConfig;
443
443
  return [relatedCollectionName, key, isArray];
444
444
  });
445
445
 
446
- let relatedTypes = "";
447
- let relatedTypesLazy = "";
448
- let curNum = 0;
449
- let maxNum = relatedCollections.length;
450
- relatedCollections.forEach((relatedCollection) => {
451
- console.log(relatedCollection);
452
- let relatedPascalName = toPascalCase(relatedCollection[0]);
453
- let relatedCamelName = toCamelCase(relatedCollection[0]);
454
- curNum++;
455
- let endNameTypes = relatedPascalName;
456
- let endNameLazy = `${relatedPascalName}Schema`;
457
- if (relatedCollection[2] === "array") {
458
- endNameTypes += "[]";
459
- endNameLazy += ".array().default([])";
460
- } else if (!(relatedCollection[2] === "array")) {
461
- endNameTypes += " | null";
462
- endNameLazy += ".nullish()";
446
+ // Check if we have any relationships - if not, generate simple schema
447
+ const hasRelationships = relatedCollections.length > 0;
448
+
449
+ let schemaString = `${imports}\n\n`;
450
+
451
+ if (!hasRelationships) {
452
+ // Simple case: no relationships, generate single schema directly
453
+ schemaString += `export const ${pascalName}Schema = z.object({\n`;
454
+ schemaString += ` $id: z.string(),\n`;
455
+ schemaString += ` $createdAt: z.string(),\n`;
456
+ schemaString += ` $updatedAt: z.string(),\n`;
457
+ schemaString += ` $permissions: z.array(z.string()),\n`;
458
+ for (const attribute of attributes) {
459
+ if (attribute.type === "relationship") {
460
+ continue;
461
+ }
462
+ schemaString += ` ${attribute.key}: ${this.typeToZod(attribute)},\n`;
463
463
  }
464
- imports += `import { ${relatedPascalName}Schema, type ${relatedPascalName} } from "./${relatedCamelName}";\n`;
465
- relatedTypes += `${relatedCollection[1]}?: ${endNameTypes};\n`;
466
- if (relatedTypes.length > 0 && curNum !== maxNum) {
467
- relatedTypes += " ";
464
+ schemaString += `});\n\n`;
465
+ schemaString += `export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;\n\n`;
466
+ } else {
467
+ // Complex case: has relationships, generate BaseSchema + extended schema pattern
468
+ let relatedTypes = "";
469
+ let relatedTypesLazy = "";
470
+ let curNum = 0;
471
+ let maxNum = relatedCollections.length;
472
+
473
+ relatedCollections.forEach((relatedCollection) => {
474
+ console.log(relatedCollection);
475
+ let relatedPascalName = toPascalCase(relatedCollection[0]);
476
+ let relatedCamelName = toCamelCase(relatedCollection[0]);
477
+ curNum++;
478
+ let endNameTypes = relatedPascalName;
479
+ let endNameLazy = `${relatedPascalName}Schema`;
480
+ if (relatedCollection[2] === "array") {
481
+ endNameTypes += "[]";
482
+ endNameLazy += ".array().default([])";
483
+ } else if (!(relatedCollection[2] === "array")) {
484
+ endNameTypes += " | null";
485
+ endNameLazy += ".nullish()";
486
+ }
487
+ imports += `import { ${relatedPascalName}Schema, type ${relatedPascalName} } from "./${relatedCamelName}";\n`;
488
+ relatedTypes += `${relatedCollection[1]}?: ${endNameTypes};\n`;
489
+ if (relatedTypes.length > 0 && curNum !== maxNum) {
490
+ relatedTypes += " ";
491
+ }
492
+ relatedTypesLazy += `${relatedCollection[1]}: z.lazy(() => ${endNameLazy}),\n`;
493
+ if (relatedTypesLazy.length > 0 && curNum !== maxNum) {
494
+ relatedTypesLazy += " ";
495
+ }
496
+ });
497
+
498
+ // Re-add imports after processing relationships
499
+ schemaString = `${imports}\n\n`;
500
+
501
+ schemaString += `export const ${pascalName}SchemaBase = z.object({\n`;
502
+ schemaString += ` $id: z.string(),\n`;
503
+ schemaString += ` $createdAt: z.string(),\n`;
504
+ schemaString += ` $updatedAt: z.string(),\n`;
505
+ schemaString += ` $permissions: z.array(z.string()),\n`;
506
+ for (const attribute of attributes) {
507
+ if (attribute.type === "relationship") {
508
+ continue;
509
+ }
510
+ schemaString += ` ${attribute.key}: ${this.typeToZod(attribute)},\n`;
468
511
  }
469
- relatedTypesLazy += `${relatedCollection[1]}: z.lazy(() => ${endNameLazy}),\n`;
470
- if (relatedTypesLazy.length > 0 && curNum !== maxNum) {
471
- relatedTypesLazy += " ";
512
+ schemaString += `});\n\n`;
513
+ schemaString += `export type ${pascalName}Base = z.infer<typeof ${pascalName}SchemaBase>`;
514
+ if (relatedTypes.length > 0) {
515
+ schemaString += ` & {\n ${relatedTypes}};\n\n`;
516
+ } else {
517
+ schemaString += `;\n\n`;
472
518
  }
473
- });
474
-
475
- let schemaString = `${imports}\n\n`;
476
- schemaString += `export const ${pascalName}SchemaBase = z.object({\n`;
477
- schemaString += ` $id: z.string().optional(),\n`;
478
- schemaString += ` $createdAt: z.string().optional(),\n`;
479
- schemaString += ` $updatedAt: z.string().optional(),\n`;
480
- for (const attribute of attributes) {
481
- if (attribute.type === "relationship") {
482
- continue;
519
+ schemaString += `export const ${pascalName}Schema: z.ZodType<${pascalName}Base> = ${pascalName}SchemaBase`;
520
+ if (relatedTypes.length > 0) {
521
+ schemaString += `.extend({\n ${relatedTypesLazy}});\n\n`;
522
+ } else {
523
+ schemaString += `;\n`;
483
524
  }
484
- schemaString += ` ${attribute.key}: ${this.typeToZod(attribute)},\n`;
485
- }
486
- schemaString += `});\n\n`;
487
- schemaString += `export type ${pascalName}Base = z.infer<typeof ${pascalName}SchemaBase>`;
488
- if (relatedTypes.length > 0) {
489
- schemaString += ` & {\n ${relatedTypes}};\n\n`;
490
- } else {
491
- schemaString += `;\n\n`;
492
- }
493
- schemaString += `export const ${pascalName}Schema: z.ZodType<${pascalName}Base> = ${pascalName}SchemaBase`;
494
- if (relatedTypes.length > 0) {
495
- schemaString += `.extend({\n ${relatedTypesLazy}});\n\n`;
496
- } else {
497
- schemaString += `;\n`;
525
+ schemaString += `export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;\n\n`;
498
526
  }
499
- schemaString += `export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;\n\n`;
500
527
 
501
528
  return schemaString;
502
529
  };
@@ -306,60 +306,87 @@ export class SchemaGenerator {
306
306
  return [relatedCollectionName, key, isArray];
307
307
  });
308
308
 
309
- let relatedTypes = "";
310
- let relatedTypesLazy = "";
311
- let curNum = 0;
312
- let maxNum = relatedCollections.length;
313
- relatedCollections.forEach((relatedCollection) => {
314
- console.log(relatedCollection);
315
- let relatedPascalName = toPascalCase(relatedCollection[0]);
316
- let relatedCamelName = toCamelCase(relatedCollection[0]);
317
- curNum++;
318
- let endNameTypes = relatedPascalName;
319
- let endNameLazy = `${relatedPascalName}Schema`;
320
- if (relatedCollection[2] === "array") {
321
- endNameTypes += "[]";
322
- endNameLazy += ".array().default([])";
323
- } else if (!(relatedCollection[2] === "array")) {
324
- endNameTypes += " | null";
325
- endNameLazy += ".nullish()";
309
+ // Check if we have any relationships - if not, generate simple schema
310
+ const hasRelationships = relatedCollections.length > 0;
311
+
312
+ let schemaString = `${imports}\n\n`;
313
+
314
+ if (!hasRelationships) {
315
+ // Simple case: no relationships, generate single schema directly
316
+ schemaString += `export const ${pascalName}Schema = z.object({\n`;
317
+ schemaString += ` $id: z.string(),\n`;
318
+ schemaString += ` $createdAt: z.string(),\n`;
319
+ schemaString += ` $updatedAt: z.string(),\n`;
320
+ schemaString += ` $permissions: z.array(z.string()),\n`;
321
+ for (const attribute of attributes) {
322
+ if (attribute.type === "relationship") {
323
+ continue;
324
+ }
325
+ schemaString += ` ${attribute.key}: ${this.typeToZod(attribute)},\n`;
326
326
  }
327
- imports += `import { ${relatedPascalName}Schema, type ${relatedPascalName} } from "./${relatedCamelName}";\n`;
328
- relatedTypes += `${relatedCollection[1]}?: ${endNameTypes};\n`;
329
- if (relatedTypes.length > 0 && curNum !== maxNum) {
330
- relatedTypes += " ";
327
+ schemaString += `});\n\n`;
328
+ schemaString += `export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;\n\n`;
329
+ } else {
330
+ // Complex case: has relationships, generate BaseSchema + extended schema pattern
331
+ let relatedTypes = "";
332
+ let relatedTypesLazy = "";
333
+ let curNum = 0;
334
+ let maxNum = relatedCollections.length;
335
+
336
+ relatedCollections.forEach((relatedCollection) => {
337
+ console.log(relatedCollection);
338
+ let relatedPascalName = toPascalCase(relatedCollection[0]);
339
+ let relatedCamelName = toCamelCase(relatedCollection[0]);
340
+ curNum++;
341
+ let endNameTypes = relatedPascalName;
342
+ let endNameLazy = `${relatedPascalName}Schema`;
343
+ if (relatedCollection[2] === "array") {
344
+ endNameTypes += "[]";
345
+ endNameLazy += ".array().default([])";
346
+ } else if (!(relatedCollection[2] === "array")) {
347
+ endNameTypes += " | null";
348
+ endNameLazy += ".nullish()";
349
+ }
350
+ imports += `import { ${relatedPascalName}Schema, type ${relatedPascalName} } from "./${relatedCamelName}";\n`;
351
+ relatedTypes += `${relatedCollection[1]}?: ${endNameTypes};\n`;
352
+ if (relatedTypes.length > 0 && curNum !== maxNum) {
353
+ relatedTypes += " ";
354
+ }
355
+ relatedTypesLazy += `${relatedCollection[1]}: z.lazy(() => ${endNameLazy}),\n`;
356
+ if (relatedTypesLazy.length > 0 && curNum !== maxNum) {
357
+ relatedTypesLazy += " ";
358
+ }
359
+ });
360
+
361
+ // Re-add imports after processing relationships
362
+ schemaString = `${imports}\n\n`;
363
+
364
+ schemaString += `export const ${pascalName}SchemaBase = z.object({\n`;
365
+ schemaString += ` $id: z.string(),\n`;
366
+ schemaString += ` $createdAt: z.string(),\n`;
367
+ schemaString += ` $updatedAt: z.string(),\n`;
368
+ schemaString += ` $permissions: z.array(z.string()),\n`;
369
+ for (const attribute of attributes) {
370
+ if (attribute.type === "relationship") {
371
+ continue;
372
+ }
373
+ schemaString += ` ${attribute.key}: ${this.typeToZod(attribute)},\n`;
331
374
  }
332
- relatedTypesLazy += `${relatedCollection[1]}: z.lazy(() => ${endNameLazy}),\n`;
333
- if (relatedTypesLazy.length > 0 && curNum !== maxNum) {
334
- relatedTypesLazy += " ";
375
+ schemaString += `});\n\n`;
376
+ schemaString += `export type ${pascalName}Base = z.infer<typeof ${pascalName}SchemaBase>`;
377
+ if (relatedTypes.length > 0) {
378
+ schemaString += ` & {\n ${relatedTypes}};\n\n`;
379
+ } else {
380
+ schemaString += `;\n\n`;
335
381
  }
336
- });
337
-
338
- let schemaString = `${imports}\n\n`;
339
- schemaString += `export const ${pascalName}SchemaBase = z.object({\n`;
340
- schemaString += ` $id: z.string().optional(),\n`;
341
- schemaString += ` $createdAt: z.string().optional(),\n`;
342
- schemaString += ` $updatedAt: z.string().optional(),\n`;
343
- for (const attribute of attributes) {
344
- if (attribute.type === "relationship") {
345
- continue;
382
+ schemaString += `export const ${pascalName}Schema: z.ZodType<${pascalName}Base> = ${pascalName}SchemaBase`;
383
+ if (relatedTypes.length > 0) {
384
+ schemaString += `.extend({\n ${relatedTypesLazy}});\n\n`;
385
+ } else {
386
+ schemaString += `;\n`;
346
387
  }
347
- schemaString += ` ${attribute.key}: ${this.typeToZod(attribute)},\n`;
348
- }
349
- schemaString += `});\n\n`;
350
- schemaString += `export type ${pascalName}Base = z.infer<typeof ${pascalName}SchemaBase>`;
351
- if (relatedTypes.length > 0) {
352
- schemaString += ` & {\n ${relatedTypes}};\n\n`;
353
- } else {
354
- schemaString += `;\n\n`;
355
- }
356
- schemaString += `export const ${pascalName}Schema: z.ZodType<${pascalName}Base> = ${pascalName}SchemaBase`;
357
- if (relatedTypes.length > 0) {
358
- schemaString += `.extend({\n ${relatedTypesLazy}});\n\n`;
359
- } else {
360
- schemaString += `;\n`;
388
+ schemaString += `export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;\n\n`;
361
389
  }
362
- schemaString += `export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;\n\n`;
363
390
 
364
391
  return schemaString;
365
392
  };