appwrite-utils-cli 1.6.6 → 1.6.7

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.
@@ -224,9 +224,9 @@ export const createOrUpdateCollections = async (database, databaseId, config, de
224
224
  attributes);
225
225
  // Add delay after creating attributes
226
226
  await delay(250);
227
- // ALWAYS use indexes from local config, NEVER from server
227
+ // Prefer local config indexes, but fall back to collection's own indexes if no local config exists
228
228
  const localCollectionConfig = config.collections?.find(c => c.name === collectionData.name || c.$id === collectionData.$id);
229
- const indexesToUse = localCollectionConfig?.indexes || [];
229
+ const indexesToUse = localCollectionConfig?.indexes ?? indexes ?? [];
230
230
  MessageFormatter.progress("Creating Indexes", { prefix: "Collections" });
231
231
  await createOrUpdateIndexesWithStatusCheck(databaseId, database, collectionToUse.$id, collectionToUse, indexesToUse);
232
232
  // Delete indexes that exist on server but not in local config
@@ -428,9 +428,9 @@ export const createOrUpdateCollectionsViaAdapter = async (adapter, databaseId, c
428
428
  }
429
429
  }
430
430
  }
431
- // ALWAYS use indexes from local config, NEVER from server (TablesDB path)
431
+ // Prefer local config indexes, but fall back to collection's own indexes if no local config exists (TablesDB path)
432
432
  const localTableConfig = config.collections?.find(c => c.name === collectionData.name || c.$id === collectionData.$id);
433
- const idxs = (localTableConfig?.indexes || []);
433
+ const idxs = (localTableConfig?.indexes ?? indexes ?? []);
434
434
  for (const idx of idxs) {
435
435
  try {
436
436
  await adapter.createIndex({
@@ -90,6 +90,23 @@ export declare class ConfigLoaderService {
90
90
  source2: string;
91
91
  }>;
92
92
  }>;
93
+ /**
94
+ * Loads tables first (higher priority), then collections (backward compatibility)
95
+ * Used for TablesDB projects (>= 1.8.0)
96
+ * @param tablesDir Path to the tables directory
97
+ * @param collectionsDir Path to the collections directory
98
+ * @returns Loading result with items, counts, and conflicts
99
+ */
100
+ loadTablesFirst(tablesDir: string, collectionsDir: string): Promise<{
101
+ items: Collection[];
102
+ fromCollections: number;
103
+ fromTables: number;
104
+ conflicts: Array<{
105
+ name: string;
106
+ source1: string;
107
+ source2: string;
108
+ }>;
109
+ }>;
93
110
  /**
94
111
  * Validates that a configuration file can be loaded
95
112
  * @param configPath Path to the configuration file
@@ -112,6 +112,42 @@ export class ConfigLoaderService {
112
112
  };
113
113
  }
114
114
  }
115
+ // Load collections and tables from their respective directories
116
+ const configDir = path.dirname(yamlPath);
117
+ const collectionsDir = path.join(configDir, config.schemaConfig?.collectionsDirectory || "collections");
118
+ const tablesDir = path.join(configDir, config.schemaConfig?.tablesDirectory || "tables");
119
+ // Detect API mode to determine priority order
120
+ let apiMode = 'legacy';
121
+ try {
122
+ const { detectAppwriteVersionCached } = await import('../../utils/versionDetection.js');
123
+ const detection = await detectAppwriteVersionCached(config.appwriteEndpoint, config.appwriteProject, config.appwriteKey);
124
+ apiMode = detection.apiMode;
125
+ }
126
+ catch {
127
+ // Fallback to legacy if detection fails
128
+ }
129
+ // Load with correct priority based on API mode
130
+ const { items, conflicts, fromCollections, fromTables } = apiMode === 'tablesdb'
131
+ ? await this.loadTablesFirst(tablesDir, collectionsDir)
132
+ : await this.loadCollectionsAndTables(collectionsDir, tablesDir);
133
+ config.collections = items;
134
+ // Report what was loaded
135
+ if (fromTables > 0 && fromCollections > 0) {
136
+ MessageFormatter.success(`Loaded ${items.length} total items: ${fromCollections} from collections/, ${fromTables} from tables/`, { prefix: "Config" });
137
+ }
138
+ else if (fromCollections > 0) {
139
+ MessageFormatter.success(`Loaded ${fromCollections} collections from collections/`, { prefix: "Config" });
140
+ }
141
+ else if (fromTables > 0) {
142
+ MessageFormatter.success(`Loaded ${fromTables} tables from tables/`, { prefix: "Config" });
143
+ }
144
+ // Report conflicts
145
+ if (conflicts.length > 0) {
146
+ MessageFormatter.warning(`Found ${conflicts.length} naming conflicts`, { prefix: "Config" });
147
+ conflicts.forEach(conflict => {
148
+ MessageFormatter.info(` - '${conflict.name}': ${conflict.source1} (used) vs ${conflict.source2} (skipped)`, { prefix: "Config" });
149
+ });
150
+ }
115
151
  MessageFormatter.success(`Loaded YAML config from: ${yamlPath}`, {
116
152
  prefix: "Config",
117
153
  });
@@ -377,6 +413,54 @@ export class ConfigLoaderService {
377
413
  conflicts,
378
414
  };
379
415
  }
416
+ /**
417
+ * Loads tables first (higher priority), then collections (backward compatibility)
418
+ * Used for TablesDB projects (>= 1.8.0)
419
+ * @param tablesDir Path to the tables directory
420
+ * @param collectionsDir Path to the collections directory
421
+ * @returns Loading result with items, counts, and conflicts
422
+ */
423
+ async loadTablesFirst(tablesDir, collectionsDir) {
424
+ const items = [];
425
+ const loadedNames = new Set();
426
+ const conflicts = [];
427
+ // Load from tables/ directory first (HIGHER priority for TablesDB)
428
+ if (fs.existsSync(tablesDir)) {
429
+ const tables = await this.loadTables(tablesDir, { markAsTablesDir: true });
430
+ for (const table of tables) {
431
+ const name = table.name || table.tableId || table.$id || "";
432
+ loadedNames.add(name);
433
+ items.push(table);
434
+ }
435
+ }
436
+ // Load from collections/ directory second (LOWER priority, backward compatibility)
437
+ if (fs.existsSync(collectionsDir)) {
438
+ const collections = await this.loadCollections(collectionsDir);
439
+ for (const collection of collections) {
440
+ const name = collection.name || collection.$id || "";
441
+ // Check for conflicts - tables win
442
+ if (loadedNames.has(name)) {
443
+ conflicts.push({
444
+ name,
445
+ source1: "tables/",
446
+ source2: "collections/",
447
+ });
448
+ }
449
+ else {
450
+ loadedNames.add(name);
451
+ items.push(collection);
452
+ }
453
+ }
454
+ }
455
+ const fromTables = items.filter((item) => item._isFromTablesDir).length;
456
+ const fromCollections = items.filter((item) => !item._isFromTablesDir).length;
457
+ return {
458
+ items,
459
+ fromCollections,
460
+ fromTables,
461
+ conflicts,
462
+ };
463
+ }
380
464
  /**
381
465
  * Validates that a configuration file can be loaded
382
466
  * @param configPath Path to the configuration file
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.6.6",
4
+ "version": "1.6.7",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -339,11 +339,11 @@ export const createOrUpdateCollections = async (
339
339
  // Add delay after creating attributes
340
340
  await delay(250);
341
341
 
342
- // ALWAYS use indexes from local config, NEVER from server
342
+ // Prefer local config indexes, but fall back to collection's own indexes if no local config exists
343
343
  const localCollectionConfig = config.collections?.find(
344
344
  c => c.name === collectionData.name || c.$id === collectionData.$id
345
345
  );
346
- const indexesToUse = localCollectionConfig?.indexes || [];
346
+ const indexesToUse = localCollectionConfig?.indexes ?? indexes ?? [];
347
347
 
348
348
  MessageFormatter.progress("Creating Indexes", { prefix: "Collections" });
349
349
  await createOrUpdateIndexesWithStatusCheck(
@@ -575,11 +575,11 @@ export const createOrUpdateCollectionsViaAdapter = async (
575
575
  }
576
576
  }
577
577
 
578
- // ALWAYS use indexes from local config, NEVER from server (TablesDB path)
578
+ // Prefer local config indexes, but fall back to collection's own indexes if no local config exists (TablesDB path)
579
579
  const localTableConfig = config.collections?.find(
580
580
  c => c.name === collectionData.name || c.$id === collectionData.$id
581
581
  );
582
- const idxs = (localTableConfig?.indexes || []) as any[];
582
+ const idxs = (localTableConfig?.indexes ?? indexes ?? []) as any[];
583
583
  for (const idx of idxs) {
584
584
  try {
585
585
  await adapter.createIndex({
@@ -153,6 +153,64 @@ export class ConfigLoaderService {
153
153
  }
154
154
  }
155
155
 
156
+ // Load collections and tables from their respective directories
157
+ const configDir = path.dirname(yamlPath);
158
+ const collectionsDir = path.join(configDir, config.schemaConfig?.collectionsDirectory || "collections");
159
+ const tablesDir = path.join(configDir, config.schemaConfig?.tablesDirectory || "tables");
160
+
161
+ // Detect API mode to determine priority order
162
+ let apiMode: 'legacy' | 'tablesdb' = 'legacy';
163
+ try {
164
+ const { detectAppwriteVersionCached } = await import('../../utils/versionDetection.js');
165
+ const detection = await detectAppwriteVersionCached(
166
+ config.appwriteEndpoint,
167
+ config.appwriteProject,
168
+ config.appwriteKey
169
+ );
170
+ apiMode = detection.apiMode;
171
+ } catch {
172
+ // Fallback to legacy if detection fails
173
+ }
174
+
175
+ // Load with correct priority based on API mode
176
+ const { items, conflicts, fromCollections, fromTables } = apiMode === 'tablesdb'
177
+ ? await this.loadTablesFirst(tablesDir, collectionsDir)
178
+ : await this.loadCollectionsAndTables(collectionsDir, tablesDir);
179
+
180
+ config.collections = items;
181
+
182
+ // Report what was loaded
183
+ if (fromTables > 0 && fromCollections > 0) {
184
+ MessageFormatter.success(
185
+ `Loaded ${items.length} total items: ${fromCollections} from collections/, ${fromTables} from tables/`,
186
+ { prefix: "Config" }
187
+ );
188
+ } else if (fromCollections > 0) {
189
+ MessageFormatter.success(
190
+ `Loaded ${fromCollections} collections from collections/`,
191
+ { prefix: "Config" }
192
+ );
193
+ } else if (fromTables > 0) {
194
+ MessageFormatter.success(
195
+ `Loaded ${fromTables} tables from tables/`,
196
+ { prefix: "Config" }
197
+ );
198
+ }
199
+
200
+ // Report conflicts
201
+ if (conflicts.length > 0) {
202
+ MessageFormatter.warning(
203
+ `Found ${conflicts.length} naming conflicts`,
204
+ { prefix: "Config" }
205
+ );
206
+ conflicts.forEach(conflict => {
207
+ MessageFormatter.info(
208
+ ` - '${conflict.name}': ${conflict.source1} (used) vs ${conflict.source2} (skipped)`,
209
+ { prefix: "Config" }
210
+ );
211
+ });
212
+ }
213
+
156
214
  MessageFormatter.success(`Loaded YAML config from: ${yamlPath}`, {
157
215
  prefix: "Config",
158
216
  });
@@ -523,6 +581,67 @@ export class ConfigLoaderService {
523
581
  };
524
582
  }
525
583
 
584
+ /**
585
+ * Loads tables first (higher priority), then collections (backward compatibility)
586
+ * Used for TablesDB projects (>= 1.8.0)
587
+ * @param tablesDir Path to the tables directory
588
+ * @param collectionsDir Path to the collections directory
589
+ * @returns Loading result with items, counts, and conflicts
590
+ */
591
+ public async loadTablesFirst(
592
+ tablesDir: string,
593
+ collectionsDir: string
594
+ ): Promise<{
595
+ items: Collection[];
596
+ fromCollections: number;
597
+ fromTables: number;
598
+ conflicts: Array<{ name: string; source1: string; source2: string }>;
599
+ }> {
600
+ const items: Collection[] = [];
601
+ const loadedNames = new Set<string>();
602
+ const conflicts: Array<{ name: string; source1: string; source2: string }> = [];
603
+
604
+ // Load from tables/ directory first (HIGHER priority for TablesDB)
605
+ if (fs.existsSync(tablesDir)) {
606
+ const tables = await this.loadTables(tablesDir, { markAsTablesDir: true });
607
+ for (const table of tables) {
608
+ const name = table.name || table.tableId || table.$id || "";
609
+ loadedNames.add(name);
610
+ items.push(table);
611
+ }
612
+ }
613
+
614
+ // Load from collections/ directory second (LOWER priority, backward compatibility)
615
+ if (fs.existsSync(collectionsDir)) {
616
+ const collections = await this.loadCollections(collectionsDir);
617
+ for (const collection of collections) {
618
+ const name = collection.name || collection.$id || "";
619
+
620
+ // Check for conflicts - tables win
621
+ if (loadedNames.has(name)) {
622
+ conflicts.push({
623
+ name,
624
+ source1: "tables/",
625
+ source2: "collections/",
626
+ });
627
+ } else {
628
+ loadedNames.add(name);
629
+ items.push(collection);
630
+ }
631
+ }
632
+ }
633
+
634
+ const fromTables = items.filter((item: any) => item._isFromTablesDir).length;
635
+ const fromCollections = items.filter((item: any) => !item._isFromTablesDir).length;
636
+
637
+ return {
638
+ items,
639
+ fromCollections,
640
+ fromTables,
641
+ conflicts,
642
+ };
643
+ }
644
+
526
645
  /**
527
646
  * Validates that a configuration file can be loaded
528
647
  * @param configPath Path to the configuration file