appwrite-utils-cli 1.2.29 → 1.3.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/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
  }
@@ -235,7 +235,7 @@ declare const YamlConfigSchema: z.ZodObject<{
235
235
  functions: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
236
236
  id: z.ZodString;
237
237
  name: z.ZodString;
238
- runtime: z.ZodEnum<["node-14.5", "node-16.0", "node-18.0", "node-19.0", "node-20.0", "node-21.0", "node-22", "bun-1.0", "bun-1.1", "deno-1.21", "deno-1.24", "deno-1.35", "deno-1.40", "deno-1.46", "deno-2.0", "go-1.23", "python-3.8", "python-3.9", "python-3.10", "python-3.11", "python-3.12", "python-ml-3.11", "dart-2.15", "dart-2.16", "dart-2.17", "dart-2.18", "dart-3.0", "dart-3.1", "dart-3.3", "dart-3.5", "php-8.0", "php-8.1", "php-8.2", "php-8.3", "ruby-3.0", "ruby-3.1", "ruby-3.2", "ruby-3.3", "dotnet-6.0", "dotnet-7.0", "dotnet-8.0", "java-8.0", "java-11.0", "java-17.0", "java-18.0", "java-21.0", "java-22", "swift-5.5", "swift-5.8", "swift-5.9", "swift-5.10", "kotlin-1.6", "kotlin-1.8", "kotlin-1.9", "kotlin-2.0", "cpp-17", "cpp-20"]>;
238
+ runtime: any;
239
239
  execute: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
240
240
  events: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
241
241
  schedule: z.ZodOptional<z.ZodString>;
@@ -244,7 +244,7 @@ declare const YamlConfigSchema: z.ZodObject<{
244
244
  logging: z.ZodOptional<z.ZodBoolean>;
245
245
  entrypoint: z.ZodOptional<z.ZodString>;
246
246
  commands: z.ZodOptional<z.ZodString>;
247
- scopes: z.ZodOptional<z.ZodArray<z.ZodEnum<["users.read", "users.write", "sessions.read", "sessions.write", "teams.read", "teams.write", "databases.read", "databases.write", "collections.read", "collections.write", "attributes.read", "attributes.write", "indexes.read", "indexes.write", "documents.read", "documents.write", "files.read", "files.write", "buckets.read", "buckets.write", "functions.read", "functions.write", "execution.read", "execution.write", "locale.read", "avatars.read", "health.read", "rules.read", "rules.write", "migrations.read", "migrations.write", "sites.read", "sites.write", "log.read", "log.write", "tokens.read", "tokens.write", "vcs.read", "vcs.write", "assistant.read", "messages.read", "messages.write", "targets.read", "targets.write", "providers.read", "providers.write", "topics.read", "topics.write", "subscribers.read", "subscribers.write"]>, "many">>;
247
+ scopes: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, "users.read" | "users.write" | "sessions.read" | "sessions.write" | "teams.read" | "teams.write" | "databases.read" | "databases.write" | "collections.read" | "collections.write" | "attributes.read" | "attributes.write" | "indexes.read" | "indexes.write" | "documents.read" | "documents.write" | "files.read" | "files.write" | "buckets.read" | "buckets.write" | "functions.read" | "functions.write" | "execution.read" | "execution.write" | "locale.read" | "avatars.read" | "health.read" | "rules.read" | "rules.write" | "migrations.read" | "migrations.write" | "sites.read" | "sites.write" | "log.read" | "log.write" | "tokens.read" | "tokens.write" | "vcs.read" | "vcs.write" | "assistant.read" | "messages.read" | "messages.write" | "targets.read" | "targets.write" | "providers.read" | "providers.write" | "topics.read" | "topics.write" | "subscribers.read" | "subscribers.write", string>, "many">>;
248
248
  installationId: z.ZodOptional<z.ZodString>;
249
249
  providerRepositoryId: z.ZodOptional<z.ZodString>;
250
250
  providerBranch: z.ZodOptional<z.ZodString>;
@@ -254,7 +254,7 @@ declare const YamlConfigSchema: z.ZodObject<{
254
254
  templateOwner: z.ZodOptional<z.ZodString>;
255
255
  templateRootDirectory: z.ZodOptional<z.ZodString>;
256
256
  templateBranch: z.ZodOptional<z.ZodString>;
257
- specification: z.ZodOptional<z.ZodEnum<["s-0.5vcpu-512mb", "s-1vcpu-512mb", "s-1vcpu-1gb", "s-2vcpu-2gb", "s-2vcpu-4gb", "s-4vcpu-4gb", "s-4vcpu-8gb", "s-8vcpu-4gb", "s-8vcpu-8gb"]>>;
257
+ specification: z.ZodOptional<z.ZodEffects<z.ZodString, "s-0.5vcpu-512mb" | "s-1vcpu-512mb" | "s-1vcpu-1gb" | "s-2vcpu-2gb" | "s-2vcpu-4gb" | "s-4vcpu-4gb" | "s-4vcpu-8gb" | "s-8vcpu-4gb" | "s-8vcpu-8gb", string>>;
258
258
  dirPath: z.ZodOptional<z.ZodString>;
259
259
  predeployCommands: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
260
260
  deployDir: z.ZodOptional<z.ZodString>;
@@ -263,9 +263,9 @@ declare const YamlConfigSchema: z.ZodObject<{
263
263
  }, "strip", z.ZodTypeAny, {
264
264
  name: string;
265
265
  id: string;
266
- runtime: "node-14.5" | "node-16.0" | "node-18.0" | "node-19.0" | "node-20.0" | "node-21.0" | "node-22" | "bun-1.0" | "bun-1.1" | "deno-1.21" | "deno-1.24" | "deno-1.35" | "deno-1.40" | "deno-1.46" | "deno-2.0" | "go-1.23" | "python-3.8" | "python-3.9" | "python-3.10" | "python-3.11" | "python-3.12" | "python-ml-3.11" | "dart-2.15" | "dart-2.16" | "dart-2.17" | "dart-2.18" | "dart-3.0" | "dart-3.1" | "dart-3.3" | "dart-3.5" | "php-8.0" | "php-8.1" | "php-8.2" | "php-8.3" | "ruby-3.0" | "ruby-3.1" | "ruby-3.2" | "ruby-3.3" | "dotnet-6.0" | "dotnet-7.0" | "dotnet-8.0" | "java-8.0" | "java-11.0" | "java-17.0" | "java-18.0" | "java-21.0" | "java-22" | "swift-5.5" | "swift-5.8" | "swift-5.9" | "swift-5.10" | "kotlin-1.6" | "kotlin-1.8" | "kotlin-1.9" | "kotlin-2.0" | "cpp-17" | "cpp-20";
267
266
  enabled?: boolean | undefined;
268
267
  logging?: boolean | undefined;
268
+ runtime?: any;
269
269
  execute?: string[] | undefined;
270
270
  events?: string[] | undefined;
271
271
  schedule?: string | undefined;
@@ -291,16 +291,16 @@ declare const YamlConfigSchema: z.ZodObject<{
291
291
  }, {
292
292
  name: string;
293
293
  id: string;
294
- runtime: "node-14.5" | "node-16.0" | "node-18.0" | "node-19.0" | "node-20.0" | "node-21.0" | "node-22" | "bun-1.0" | "bun-1.1" | "deno-1.21" | "deno-1.24" | "deno-1.35" | "deno-1.40" | "deno-1.46" | "deno-2.0" | "go-1.23" | "python-3.8" | "python-3.9" | "python-3.10" | "python-3.11" | "python-3.12" | "python-ml-3.11" | "dart-2.15" | "dart-2.16" | "dart-2.17" | "dart-2.18" | "dart-3.0" | "dart-3.1" | "dart-3.3" | "dart-3.5" | "php-8.0" | "php-8.1" | "php-8.2" | "php-8.3" | "ruby-3.0" | "ruby-3.1" | "ruby-3.2" | "ruby-3.3" | "dotnet-6.0" | "dotnet-7.0" | "dotnet-8.0" | "java-8.0" | "java-11.0" | "java-17.0" | "java-18.0" | "java-21.0" | "java-22" | "swift-5.5" | "swift-5.8" | "swift-5.9" | "swift-5.10" | "kotlin-1.6" | "kotlin-1.8" | "kotlin-1.9" | "kotlin-2.0" | "cpp-17" | "cpp-20";
295
294
  enabled?: boolean | undefined;
296
295
  logging?: boolean | undefined;
296
+ runtime?: any;
297
297
  execute?: string[] | undefined;
298
298
  events?: string[] | undefined;
299
299
  schedule?: string | undefined;
300
300
  timeout?: number | undefined;
301
301
  entrypoint?: string | undefined;
302
302
  commands?: string | undefined;
303
- scopes?: ("users.read" | "users.write" | "sessions.read" | "sessions.write" | "teams.read" | "teams.write" | "databases.read" | "databases.write" | "collections.read" | "collections.write" | "attributes.read" | "attributes.write" | "indexes.read" | "indexes.write" | "documents.read" | "documents.write" | "files.read" | "files.write" | "buckets.read" | "buckets.write" | "functions.read" | "functions.write" | "execution.read" | "execution.write" | "locale.read" | "avatars.read" | "health.read" | "rules.read" | "rules.write" | "migrations.read" | "migrations.write" | "sites.read" | "sites.write" | "log.read" | "log.write" | "tokens.read" | "tokens.write" | "vcs.read" | "vcs.write" | "assistant.read" | "messages.read" | "messages.write" | "targets.read" | "targets.write" | "providers.read" | "providers.write" | "topics.read" | "topics.write" | "subscribers.read" | "subscribers.write")[] | undefined;
303
+ scopes?: string[] | undefined;
304
304
  installationId?: string | undefined;
305
305
  providerRepositoryId?: string | undefined;
306
306
  providerBranch?: string | undefined;
@@ -310,7 +310,7 @@ declare const YamlConfigSchema: z.ZodObject<{
310
310
  templateOwner?: string | undefined;
311
311
  templateRootDirectory?: string | undefined;
312
312
  templateBranch?: string | undefined;
313
- specification?: "s-0.5vcpu-512mb" | "s-1vcpu-512mb" | "s-1vcpu-1gb" | "s-2vcpu-2gb" | "s-2vcpu-4gb" | "s-4vcpu-4gb" | "s-4vcpu-8gb" | "s-8vcpu-4gb" | "s-8vcpu-8gb" | undefined;
313
+ specification?: string | undefined;
314
314
  dirPath?: string | undefined;
315
315
  predeployCommands?: string[] | undefined;
316
316
  deployDir?: string | undefined;
@@ -391,9 +391,9 @@ declare const YamlConfigSchema: z.ZodObject<{
391
391
  functions: {
392
392
  name: string;
393
393
  id: string;
394
- runtime: "node-14.5" | "node-16.0" | "node-18.0" | "node-19.0" | "node-20.0" | "node-21.0" | "node-22" | "bun-1.0" | "bun-1.1" | "deno-1.21" | "deno-1.24" | "deno-1.35" | "deno-1.40" | "deno-1.46" | "deno-2.0" | "go-1.23" | "python-3.8" | "python-3.9" | "python-3.10" | "python-3.11" | "python-3.12" | "python-ml-3.11" | "dart-2.15" | "dart-2.16" | "dart-2.17" | "dart-2.18" | "dart-3.0" | "dart-3.1" | "dart-3.3" | "dart-3.5" | "php-8.0" | "php-8.1" | "php-8.2" | "php-8.3" | "ruby-3.0" | "ruby-3.1" | "ruby-3.2" | "ruby-3.3" | "dotnet-6.0" | "dotnet-7.0" | "dotnet-8.0" | "java-8.0" | "java-11.0" | "java-17.0" | "java-18.0" | "java-21.0" | "java-22" | "swift-5.5" | "swift-5.8" | "swift-5.9" | "swift-5.10" | "kotlin-1.6" | "kotlin-1.8" | "kotlin-1.9" | "kotlin-2.0" | "cpp-17" | "cpp-20";
395
394
  enabled?: boolean | undefined;
396
395
  logging?: boolean | undefined;
396
+ runtime?: any;
397
397
  execute?: string[] | undefined;
398
398
  events?: string[] | undefined;
399
399
  schedule?: string | undefined;
@@ -485,16 +485,16 @@ declare const YamlConfigSchema: z.ZodObject<{
485
485
  functions?: {
486
486
  name: string;
487
487
  id: string;
488
- runtime: "node-14.5" | "node-16.0" | "node-18.0" | "node-19.0" | "node-20.0" | "node-21.0" | "node-22" | "bun-1.0" | "bun-1.1" | "deno-1.21" | "deno-1.24" | "deno-1.35" | "deno-1.40" | "deno-1.46" | "deno-2.0" | "go-1.23" | "python-3.8" | "python-3.9" | "python-3.10" | "python-3.11" | "python-3.12" | "python-ml-3.11" | "dart-2.15" | "dart-2.16" | "dart-2.17" | "dart-2.18" | "dart-3.0" | "dart-3.1" | "dart-3.3" | "dart-3.5" | "php-8.0" | "php-8.1" | "php-8.2" | "php-8.3" | "ruby-3.0" | "ruby-3.1" | "ruby-3.2" | "ruby-3.3" | "dotnet-6.0" | "dotnet-7.0" | "dotnet-8.0" | "java-8.0" | "java-11.0" | "java-17.0" | "java-18.0" | "java-21.0" | "java-22" | "swift-5.5" | "swift-5.8" | "swift-5.9" | "swift-5.10" | "kotlin-1.6" | "kotlin-1.8" | "kotlin-1.9" | "kotlin-2.0" | "cpp-17" | "cpp-20";
489
488
  enabled?: boolean | undefined;
490
489
  logging?: boolean | undefined;
490
+ runtime?: any;
491
491
  execute?: string[] | undefined;
492
492
  events?: string[] | undefined;
493
493
  schedule?: string | undefined;
494
494
  timeout?: number | undefined;
495
495
  entrypoint?: string | undefined;
496
496
  commands?: string | undefined;
497
- scopes?: ("users.read" | "users.write" | "sessions.read" | "sessions.write" | "teams.read" | "teams.write" | "databases.read" | "databases.write" | "collections.read" | "collections.write" | "attributes.read" | "attributes.write" | "indexes.read" | "indexes.write" | "documents.read" | "documents.write" | "files.read" | "files.write" | "buckets.read" | "buckets.write" | "functions.read" | "functions.write" | "execution.read" | "execution.write" | "locale.read" | "avatars.read" | "health.read" | "rules.read" | "rules.write" | "migrations.read" | "migrations.write" | "sites.read" | "sites.write" | "log.read" | "log.write" | "tokens.read" | "tokens.write" | "vcs.read" | "vcs.write" | "assistant.read" | "messages.read" | "messages.write" | "targets.read" | "targets.write" | "providers.read" | "providers.write" | "topics.read" | "topics.write" | "subscribers.read" | "subscribers.write")[] | undefined;
497
+ scopes?: string[] | undefined;
498
498
  installationId?: string | undefined;
499
499
  providerRepositoryId?: string | undefined;
500
500
  providerBranch?: string | undefined;
@@ -504,7 +504,7 @@ declare const YamlConfigSchema: z.ZodObject<{
504
504
  templateOwner?: string | undefined;
505
505
  templateRootDirectory?: string | undefined;
506
506
  templateBranch?: string | undefined;
507
- specification?: "s-0.5vcpu-512mb" | "s-1vcpu-512mb" | "s-1vcpu-1gb" | "s-2vcpu-2gb" | "s-2vcpu-4gb" | "s-4vcpu-4gb" | "s-4vcpu-8gb" | "s-8vcpu-4gb" | "s-8vcpu-8gb" | undefined;
507
+ specification?: string | undefined;
508
508
  dirPath?: string | undefined;
509
509
  predeployCommands?: string[] | undefined;
510
510
  deployDir?: string | undefined;
@@ -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.1",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -33,7 +33,7 @@
33
33
  "@types/inquirer": "^9.0.8",
34
34
  "@types/json-schema": "^7.0.15",
35
35
  "@types/yargs": "^17.0.33",
36
- "appwrite-utils": "^1.2.1",
36
+ "appwrite-utils": "^1.3.1",
37
37
  "chalk": "^5.4.1",
38
38
  "cli-progress": "^3.12.0",
39
39
  "commander": "^12.1.0",
@@ -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
  };