appwrite-utils-cli 1.6.5 → 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.
@@ -1 +1,484 @@
1
- export {};
1
+ import { mkdirSync, writeFileSync, existsSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { ulid } from "ulidx";
4
+ import { MessageFormatter } from "./shared/messageFormatter.js";
5
+ import { detectAppwriteVersionCached, fetchServerVersion, isVersionAtLeast } from "./utils/versionDetection.js";
6
+ import { loadAppwriteProjectConfig, findAppwriteProjectConfig, isTablesDBProject } from "./utils/projectConfig.js";
7
+ import { findYamlConfig, generateYamlConfigTemplate } from "./config/yamlConfig.js";
8
+ import { loadYamlConfig } from "./config/yamlConfig.js";
9
+ import { hasSessionAuth } from "./utils/sessionAuth.js";
10
+ /**
11
+ * Get terminology configuration based on API mode
12
+ */
13
+ export function getTerminologyConfig(useTables) {
14
+ return useTables
15
+ ? {
16
+ container: "table",
17
+ containerName: "Table",
18
+ fields: "columns",
19
+ fieldName: "Column",
20
+ security: "rowSecurity",
21
+ schemaRef: "table.schema.json",
22
+ items: "rows"
23
+ }
24
+ : {
25
+ container: "collection",
26
+ containerName: "Collection",
27
+ fields: "attributes",
28
+ fieldName: "Attribute",
29
+ security: "documentSecurity",
30
+ schemaRef: "collection.schema.json",
31
+ items: "documents"
32
+ };
33
+ }
34
+ /**
35
+ * Detect API mode using multiple detection sources
36
+ * Priority: appwrite.json > server version > default (collections)
37
+ */
38
+ export async function detectApiMode(basePath) {
39
+ let useTables = false;
40
+ let detectionSource = "default";
41
+ let serverVersion;
42
+ try {
43
+ // Priority 1: Check for existing appwrite.json project config
44
+ const projectConfigPath = findAppwriteProjectConfig(basePath);
45
+ if (projectConfigPath) {
46
+ const projectConfig = loadAppwriteProjectConfig(projectConfigPath);
47
+ if (projectConfig) {
48
+ useTables = isTablesDBProject(projectConfig);
49
+ detectionSource = "appwrite.json";
50
+ MessageFormatter.info(`Detected ${useTables ? 'TablesDB' : 'Collections'} project from ${projectConfigPath}`, { prefix: "Setup" });
51
+ return {
52
+ apiMode: useTables ? 'tablesdb' : 'legacy',
53
+ useTables,
54
+ detectionSource
55
+ };
56
+ }
57
+ }
58
+ // Priority 2: Try reading existing YAML config for version detection
59
+ const yamlPath = findYamlConfig(basePath);
60
+ if (yamlPath) {
61
+ const cfg = await loadYamlConfig(yamlPath);
62
+ if (cfg) {
63
+ const endpoint = cfg.appwriteEndpoint;
64
+ const projectId = cfg.appwriteProject;
65
+ if (hasSessionAuth(endpoint, projectId)) {
66
+ MessageFormatter.info("Using session authentication for version detection", { prefix: "Setup" });
67
+ }
68
+ const ver = await fetchServerVersion(endpoint);
69
+ serverVersion = ver || undefined;
70
+ if (isVersionAtLeast(ver || undefined, '1.8.0')) {
71
+ useTables = true;
72
+ detectionSource = "server-version";
73
+ MessageFormatter.info(`Detected TablesDB support (Appwrite ${ver})`, { prefix: "Setup" });
74
+ }
75
+ else {
76
+ MessageFormatter.info(`Using Collections API (Appwrite ${ver || 'unknown'})`, { prefix: "Setup" });
77
+ }
78
+ return {
79
+ apiMode: useTables ? 'tablesdb' : 'legacy',
80
+ useTables,
81
+ detectionSource,
82
+ serverVersion
83
+ };
84
+ }
85
+ }
86
+ }
87
+ catch (error) {
88
+ MessageFormatter.warning(`Version detection failed, defaulting to Collections API: ${error instanceof Error ? error.message : String(error)}`, { prefix: "Setup" });
89
+ }
90
+ // Default to Collections API
91
+ return {
92
+ apiMode: 'legacy',
93
+ useTables: false,
94
+ detectionSource: 'default'
95
+ };
96
+ }
97
+ /**
98
+ * Create directory structure for Appwrite project
99
+ */
100
+ export function createProjectDirectories(basePath, useTables) {
101
+ const appwriteFolder = path.join(basePath, ".appwrite");
102
+ const containerName = useTables ? "tables" : "collections";
103
+ const containerFolder = path.join(appwriteFolder, containerName);
104
+ const schemaFolder = path.join(appwriteFolder, "schemas");
105
+ const yamlSchemaFolder = path.join(appwriteFolder, ".yaml_schemas");
106
+ const dataFolder = path.join(appwriteFolder, "importData");
107
+ // Create all directories
108
+ for (const dir of [appwriteFolder, containerFolder, schemaFolder, yamlSchemaFolder, dataFolder]) {
109
+ if (!existsSync(dir)) {
110
+ mkdirSync(dir, { recursive: true });
111
+ }
112
+ }
113
+ return {
114
+ appwriteFolder,
115
+ containerFolder,
116
+ schemaFolder,
117
+ yamlSchemaFolder,
118
+ dataFolder
119
+ };
120
+ }
121
+ /**
122
+ * Create example YAML schema file with correct terminology
123
+ */
124
+ export function createExampleSchema(containerFolder, terminology) {
125
+ const yamlExample = `# yaml-language-server: $schema=../.yaml_schemas/${terminology.schemaRef}
126
+ # Example ${terminology.containerName} Definition
127
+ name: Example${terminology.containerName}
128
+ id: example_${terminology.container}_${Date.now()}
129
+ ${terminology.security}: false
130
+ enabled: true
131
+ permissions:
132
+ - permission: read
133
+ target: any
134
+ - permission: create
135
+ target: users
136
+ - permission: update
137
+ target: users
138
+ - permission: delete
139
+ target: users
140
+ ${terminology.fields}:
141
+ - key: title
142
+ type: string
143
+ size: 255
144
+ required: true
145
+ description: "The title of the item"
146
+ - key: description
147
+ type: string
148
+ size: 1000
149
+ required: false
150
+ description: "A longer description"
151
+ - key: isActive
152
+ type: boolean
153
+ required: false
154
+ default: true${terminology.container === 'table' ? `
155
+ - key: uniqueCode
156
+ type: string
157
+ size: 50
158
+ required: false
159
+ unique: true
160
+ description: "Unique identifier code (TablesDB feature)"` : ''}
161
+ indexes:
162
+ - key: title_search
163
+ type: fulltext
164
+ attributes:
165
+ - title
166
+ importDefs: []
167
+ `;
168
+ const examplePath = path.join(containerFolder, `Example${terminology.containerName}.yaml`);
169
+ writeFileSync(examplePath, yamlExample);
170
+ MessageFormatter.info(`Created example ${terminology.container} definition with ${terminology.fields} terminology`, { prefix: "Setup" });
171
+ return examplePath;
172
+ }
173
+ /**
174
+ * Create JSON schema for YAML validation
175
+ */
176
+ export function createYamlValidationSchema(yamlSchemaFolder, useTables) {
177
+ const schemaFileName = useTables ? "table.schema.json" : "collection.schema.json";
178
+ const containerType = useTables ? "Table" : "Collection";
179
+ const fieldsName = useTables ? "columns" : "attributes";
180
+ const fieldsDescription = useTables ? "Table columns (fields)" : "Collection attributes (fields)";
181
+ const securityField = useTables ? "rowSecurity" : "documentSecurity";
182
+ const securityDescription = useTables ? "Enable row-level permissions" : "Enable document-level permissions";
183
+ const itemType = useTables ? "row" : "document";
184
+ const schema = {
185
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
186
+ "$id": `https://appwrite-utils.dev/schemas/${schemaFileName}`,
187
+ "title": `Appwrite ${containerType} Definition`,
188
+ "description": `Schema for defining Appwrite ${useTables ? 'tables' : 'collections'} in YAML${useTables ? ' (TablesDB API)' : ''}`,
189
+ "type": "object",
190
+ "properties": {
191
+ "name": {
192
+ "type": "string",
193
+ "description": `The name of the ${useTables ? 'table' : 'collection'}`
194
+ },
195
+ "id": {
196
+ "type": "string",
197
+ "description": `The ID of the ${useTables ? 'table' : 'collection'} (optional, auto-generated if not provided)`,
198
+ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$"
199
+ },
200
+ [securityField]: {
201
+ "type": "boolean",
202
+ "default": false,
203
+ "description": securityDescription
204
+ },
205
+ "enabled": {
206
+ "type": "boolean",
207
+ "default": true,
208
+ "description": `Whether the ${useTables ? 'table' : 'collection'} is enabled`
209
+ },
210
+ "permissions": {
211
+ "type": "array",
212
+ "description": `${containerType}-level permissions`,
213
+ "items": {
214
+ "type": "object",
215
+ "properties": {
216
+ "permission": {
217
+ "type": "string",
218
+ "enum": ["read", "create", "update", "delete"],
219
+ "description": "The permission type"
220
+ },
221
+ "target": {
222
+ "type": "string",
223
+ "description": "Permission target (e.g., 'any', 'users', 'users/verified', 'label:admin')"
224
+ }
225
+ },
226
+ "required": ["permission", "target"],
227
+ "additionalProperties": false
228
+ }
229
+ },
230
+ [fieldsName]: {
231
+ "type": "array",
232
+ "description": fieldsDescription,
233
+ "items": {
234
+ "type": "object",
235
+ "properties": {
236
+ "key": {
237
+ "type": "string",
238
+ "description": `${useTables ? 'Column' : 'Attribute'} name`,
239
+ "pattern": "^[a-zA-Z][a-zA-Z0-9]*$"
240
+ },
241
+ "type": {
242
+ "type": "string",
243
+ "enum": ["string", "integer", "double", "boolean", "datetime", "email", "ip", "url", "enum", "relationship"],
244
+ "description": `${useTables ? 'Column' : 'Attribute'} data type`
245
+ },
246
+ "size": {
247
+ "type": "number",
248
+ "description": `Maximum size for string ${useTables ? 'columns' : 'attributes'}`,
249
+ "minimum": 1,
250
+ "maximum": 1073741824
251
+ },
252
+ "required": {
253
+ "type": "boolean",
254
+ "default": false,
255
+ "description": `Whether the ${useTables ? 'column' : 'attribute'} is required`
256
+ },
257
+ "array": {
258
+ "type": "boolean",
259
+ "default": false,
260
+ "description": `Whether the ${useTables ? 'column' : 'attribute'} is an array`
261
+ },
262
+ ...(useTables ? {
263
+ "unique": {
264
+ "type": "boolean",
265
+ "default": false,
266
+ "description": "Whether the column values must be unique (TablesDB feature)"
267
+ }
268
+ } : {}),
269
+ "default": {
270
+ "description": `Default value for the ${useTables ? 'column' : 'attribute'}`
271
+ },
272
+ "description": {
273
+ "type": "string",
274
+ "description": `${useTables ? 'Column' : 'Attribute'} description`
275
+ },
276
+ "min": {
277
+ "type": "number",
278
+ "description": `Minimum value for numeric ${useTables ? 'columns' : 'attributes'}`
279
+ },
280
+ "max": {
281
+ "type": "number",
282
+ "description": `Maximum value for numeric ${useTables ? 'columns' : 'attributes'}`
283
+ },
284
+ "elements": {
285
+ "type": "array",
286
+ "items": {
287
+ "type": "string"
288
+ },
289
+ "description": `Allowed values for enum ${useTables ? 'columns' : 'attributes'}`
290
+ },
291
+ "relatedCollection": {
292
+ "type": "string",
293
+ "description": `Related ${useTables ? 'table' : 'collection'} name for relationship ${useTables ? 'columns' : 'attributes'}`
294
+ },
295
+ "relationType": {
296
+ "type": "string",
297
+ "enum": ["oneToOne", "oneToMany", "manyToOne", "manyToMany"],
298
+ "description": "Type of relationship"
299
+ },
300
+ "twoWay": {
301
+ "type": "boolean",
302
+ "description": "Whether the relationship is bidirectional"
303
+ },
304
+ "twoWayKey": {
305
+ "type": "string",
306
+ "description": "Key name for the reverse relationship"
307
+ },
308
+ "onDelete": {
309
+ "type": "string",
310
+ "enum": ["cascade", "restrict", "setNull"],
311
+ "description": `Action to take when related ${itemType} is deleted`
312
+ },
313
+ "side": {
314
+ "type": "string",
315
+ "enum": ["parent", "child"],
316
+ "description": "Side of the relationship"
317
+ }
318
+ },
319
+ "required": ["key", "type"],
320
+ "additionalProperties": false,
321
+ "allOf": [
322
+ {
323
+ "if": {
324
+ "properties": { "type": { "const": "enum" } }
325
+ },
326
+ "then": {
327
+ "required": ["elements"]
328
+ }
329
+ },
330
+ {
331
+ "if": {
332
+ "properties": { "type": { "const": "relationship" } }
333
+ },
334
+ "then": {
335
+ "required": ["relatedCollection", "relationType"]
336
+ }
337
+ }
338
+ ]
339
+ }
340
+ },
341
+ "indexes": {
342
+ "type": "array",
343
+ "description": `Database indexes for the ${useTables ? 'table' : 'collection'}`,
344
+ "items": {
345
+ "type": "object",
346
+ "properties": {
347
+ "key": {
348
+ "type": "string",
349
+ "description": "Index name"
350
+ },
351
+ "type": {
352
+ "type": "string",
353
+ "enum": ["key", "fulltext", "unique"],
354
+ "description": "Index type"
355
+ },
356
+ "attributes": {
357
+ "type": "array",
358
+ "items": {
359
+ "type": "string"
360
+ },
361
+ "description": `${useTables ? 'Columns' : 'Attributes'} to index`,
362
+ "minItems": 1
363
+ },
364
+ "orders": {
365
+ "type": "array",
366
+ "items": {
367
+ "type": "string",
368
+ "enum": ["ASC", "DESC"]
369
+ },
370
+ "description": `Sort order for each ${useTables ? 'column' : 'attribute'}`
371
+ }
372
+ },
373
+ "required": ["key", "type", "attributes"],
374
+ "additionalProperties": false
375
+ }
376
+ },
377
+ "importDefs": {
378
+ "type": "array",
379
+ "description": "Import definitions for data migration",
380
+ "default": []
381
+ }
382
+ },
383
+ "required": ["name"],
384
+ "additionalProperties": false
385
+ };
386
+ const schemaPath = path.join(yamlSchemaFolder, schemaFileName);
387
+ writeFileSync(schemaPath, JSON.stringify(schema, null, 2));
388
+ return schemaPath;
389
+ }
390
+ /**
391
+ * Initialize a new Appwrite project with correct directory structure and terminology
392
+ */
393
+ export async function initProject(basePath, forceApiMode) {
394
+ const projectPath = basePath || process.cwd();
395
+ // Detect API mode
396
+ const detection = forceApiMode
397
+ ? {
398
+ apiMode: forceApiMode,
399
+ useTables: forceApiMode === 'tablesdb',
400
+ detectionSource: 'forced',
401
+ }
402
+ : await detectApiMode(projectPath);
403
+ const { useTables, detectionSource } = detection;
404
+ const terminology = getTerminologyConfig(useTables);
405
+ // Create directory structure
406
+ const dirs = createProjectDirectories(projectPath, useTables);
407
+ // Generate YAML config
408
+ const configPath = path.join(dirs.appwriteFolder, "config.yaml");
409
+ generateYamlConfigTemplate(configPath);
410
+ // Create example schema file
411
+ createExampleSchema(dirs.containerFolder, terminology);
412
+ // Create JSON validation schema
413
+ createYamlValidationSchema(dirs.yamlSchemaFolder, useTables);
414
+ // Success messages
415
+ const containerType = useTables ? "TablesDB" : "Collections";
416
+ MessageFormatter.success(`Created YAML config and setup files/directories in .appwrite/ folder.`, { prefix: "Setup" });
417
+ MessageFormatter.info(`Project configured for ${containerType} API (${detectionSource} detection)`, { prefix: "Setup" });
418
+ MessageFormatter.info("You can now configure your project in .appwrite/config.yaml", { prefix: "Setup" });
419
+ MessageFormatter.info(`${terminology.containerName}s can be defined in .appwrite/${terminology.container}s/ as .yaml files`, { prefix: "Setup" });
420
+ MessageFormatter.info("Schemas will be generated in .appwrite/schemas/", { prefix: "Setup" });
421
+ MessageFormatter.info("Import data can be placed in .appwrite/importData/", { prefix: "Setup" });
422
+ if (useTables) {
423
+ MessageFormatter.info("TablesDB features: unique constraints, enhanced performance, row-level security", { prefix: "Setup" });
424
+ }
425
+ }
426
+ /**
427
+ * Create a new collection or table schema file
428
+ */
429
+ export async function createSchema(name, basePath, forceApiMode) {
430
+ const projectPath = basePath || process.cwd();
431
+ // Detect API mode
432
+ const detection = forceApiMode
433
+ ? {
434
+ apiMode: forceApiMode,
435
+ useTables: forceApiMode === 'tablesdb',
436
+ detectionSource: 'forced',
437
+ }
438
+ : await detectApiMode(projectPath);
439
+ const { useTables } = detection;
440
+ const terminology = getTerminologyConfig(useTables);
441
+ // Find or create container directory
442
+ const appwriteFolder = path.join(projectPath, ".appwrite");
443
+ const containerFolder = path.join(appwriteFolder, useTables ? "tables" : "collections");
444
+ if (!existsSync(containerFolder)) {
445
+ mkdirSync(containerFolder, { recursive: true });
446
+ }
447
+ // Create YAML schema file
448
+ const yamlSchema = `# yaml-language-server: $schema=../.yaml_schemas/${terminology.schemaRef}
449
+ # ${terminology.containerName} Definition: ${name}
450
+ name: ${name}
451
+ id: ${ulid()}
452
+ ${terminology.security}: false
453
+ enabled: true
454
+ permissions:
455
+ - permission: read
456
+ target: any
457
+ - permission: create
458
+ target: users
459
+ - permission: update
460
+ target: users
461
+ - permission: delete
462
+ target: users
463
+ ${terminology.fields}:
464
+ # Add your ${terminology.fields} here
465
+ # Example:
466
+ # - key: title
467
+ # type: string
468
+ # size: 255
469
+ # required: true
470
+ # description: "The title of the item"
471
+ indexes:
472
+ # Add your indexes here
473
+ # Example:
474
+ # - key: title_search
475
+ # type: fulltext
476
+ # attributes:
477
+ # - title
478
+ importDefs: []
479
+ `;
480
+ const schemaPath = path.join(containerFolder, `${name}.yaml`);
481
+ writeFileSync(schemaPath, yamlSchema);
482
+ MessageFormatter.success(`Created ${terminology.container} schema: ${schemaPath}`, { prefix: "Setup" });
483
+ MessageFormatter.info(`Add your ${terminology.fields} to define the ${terminology.container} structure`, { prefix: "Setup" });
484
+ }
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.5",
4
+ "version": "1.6.7",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -5,6 +5,9 @@ import { delay, tryAwaitWithRetry, calculateExponentialBackoff } from "../utils/
5
5
  import { isLegacyDatabases } from "../utils/typeGuards.js";
6
6
  import { MessageFormatter } from "../shared/messageFormatter.js";
7
7
 
8
+ // System attributes that are always available for indexing in Appwrite
9
+ const SYSTEM_ATTRIBUTES = ['$id', '$createdAt', '$updatedAt', '$permissions'];
10
+
8
11
  // Interface for index with status
9
12
  interface IndexWithStatus {
10
13
  key: string;
@@ -116,12 +119,15 @@ export const createOrUpdateIndexWithStatusCheck = async (
116
119
  // First, validate that all required attributes exist
117
120
  const freshCollection = await db.getCollection(dbId, collectionId);
118
121
  const existingAttributeKeys = freshCollection.attributes.map((attr: any) => attr.key);
119
-
120
- const missingAttributes = index.attributes.filter(attr => !existingAttributeKeys.includes(attr));
122
+
123
+ // Include system attributes that are always available
124
+ const allAvailableAttributes = [...existingAttributeKeys, ...SYSTEM_ATTRIBUTES];
125
+
126
+ const missingAttributes = index.attributes.filter(attr => !allAvailableAttributes.includes(attr));
121
127
 
122
128
  if (missingAttributes.length > 0) {
123
129
  MessageFormatter.error(`Index '${index.key}' cannot be created: missing attributes [${missingAttributes.join(', ')}] (type: ${index.type})`);
124
- MessageFormatter.error(`Available attributes: [${existingAttributeKeys.join(', ')}]`);
130
+ MessageFormatter.error(`Available attributes: [${existingAttributeKeys.join(', ')}, ${SYSTEM_ATTRIBUTES.join(', ')}]`);
125
131
  return false; // Don't retry if attributes are missing
126
132
  }
127
133
 
@@ -279,17 +285,35 @@ export const createOrUpdateIndex = async (
279
285
  if (existingIndex.total === 0) {
280
286
  // No existing index, create it
281
287
  createIndex = true;
282
- } else if (
283
- !existingIndex.indexes.some(
284
- (existingIndex) =>
285
- (existingIndex.key === index.key &&
286
- existingIndex.type === index.type &&
287
- existingIndex.attributes === index.attributes)
288
- )
289
- ) {
290
- // Existing index doesn't match, delete and recreate
291
- await db.deleteIndex(dbId, collectionId, existingIndex.indexes[0].key);
292
- createIndex = true;
288
+ } else {
289
+ const existing = existingIndex.indexes[0];
290
+
291
+ // Check key and type
292
+ const keyMatches = existing.key === index.key;
293
+ const typeMatches = existing.type === index.type;
294
+
295
+ // Compare attributes as SETS (order doesn't matter, only content)
296
+ const existingAttrsSet = new Set(existing.attributes);
297
+ const newAttrsSet = new Set(index.attributes);
298
+ const attributesMatch =
299
+ existingAttrsSet.size === newAttrsSet.size &&
300
+ [...existingAttrsSet].every(attr => newAttrsSet.has(attr));
301
+
302
+ // Compare orders as SETS if both exist (order doesn't matter)
303
+ let ordersMatch = true;
304
+ if (index.orders && existing.orders) {
305
+ const existingOrdersSet = new Set(existing.orders);
306
+ const newOrdersSet = new Set(index.orders);
307
+ ordersMatch =
308
+ existingOrdersSet.size === newOrdersSet.size &&
309
+ [...existingOrdersSet].every(ord => newOrdersSet.has(ord));
310
+ }
311
+
312
+ // Only recreate if something genuinely changed
313
+ if (!keyMatches || !typeMatches || !attributesMatch || !ordersMatch) {
314
+ await db.deleteIndex(dbId, collectionId, existing.key);
315
+ createIndex = true;
316
+ }
293
317
  }
294
318
 
295
319
  if (createIndex) {
@@ -339,8 +339,11 @@ export const createOrUpdateCollections = async (
339
339
  // Add delay after creating attributes
340
340
  await delay(250);
341
341
 
342
- // For PUSH operations, only use indexes from local config (not remote)
343
- const indexesToUse = indexes || [];
342
+ // Prefer local config indexes, but fall back to collection's own indexes if no local config exists
343
+ const localCollectionConfig = config.collections?.find(
344
+ c => c.name === collectionData.name || c.$id === collectionData.$id
345
+ );
346
+ const indexesToUse = localCollectionConfig?.indexes ?? indexes ?? [];
344
347
 
345
348
  MessageFormatter.progress("Creating Indexes", { prefix: "Collections" });
346
349
  await createOrUpdateIndexesWithStatusCheck(
@@ -351,6 +354,16 @@ export const createOrUpdateCollections = async (
351
354
  indexesToUse as Indexes
352
355
  );
353
356
 
357
+ // Delete indexes that exist on server but not in local config
358
+ const { deleteObsoleteIndexes } = await import('../shared/indexManager.js');
359
+ await deleteObsoleteIndexes(
360
+ database,
361
+ databaseId,
362
+ collectionToUse!,
363
+ { indexes: indexesToUse } as any,
364
+ { verbose: true }
365
+ );
366
+
354
367
  // Mark this collection as fully processed to prevent re-processing
355
368
  markCollectionProcessed(collectionToUse!.$id, collectionData.name);
356
369
 
@@ -562,8 +575,11 @@ export const createOrUpdateCollectionsViaAdapter = async (
562
575
  }
563
576
  }
564
577
 
565
- // Indexes
566
- const idxs = (indexes || []) as any[];
578
+ // Prefer local config indexes, but fall back to collection's own indexes if no local config exists (TablesDB path)
579
+ const localTableConfig = config.collections?.find(
580
+ c => c.name === collectionData.name || c.$id === collectionData.$id
581
+ );
582
+ const idxs = (localTableConfig?.indexes ?? indexes ?? []) as any[];
567
583
  for (const idx of idxs) {
568
584
  try {
569
585
  await adapter.createIndex({