appwrite-utils-cli 1.9.5 → 1.9.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.
@@ -7,14 +7,14 @@
7
7
  */
8
8
 
9
9
  import type { AppwriteConfig } from "appwrite-utils";
10
- import { detectAppwriteVersionCached, isVersionAtLeast, type ApiMode, type VersionDetectionResult } from "../utils/versionDetection.js";
11
- import type { DatabaseAdapter } from './DatabaseAdapter.js';
12
- import { TablesDBAdapter } from './TablesDBAdapter.js';
13
- import { LegacyAdapter } from './LegacyAdapter.js';
14
- import { logger } from '../shared/logging.js';
15
- import { isValidSessionCookie } from '../utils/sessionAuth.js';
16
- import { MessageFormatter } from '../shared/messageFormatter.js';
17
- import { Client } from 'node-appwrite';
10
+ import { detectAppwriteVersionCached, isVersionAtLeast, type ApiMode, type VersionDetectionResult } from "../utils/versionDetection.js";
11
+ import { AdapterError, type DatabaseAdapter } from './DatabaseAdapter.js';
12
+ import { TablesDBAdapter } from './TablesDBAdapter.js';
13
+ import { LegacyAdapter } from './LegacyAdapter.js';
14
+ import { logger } from '../shared/logging.js';
15
+ import { isValidSessionCookie } from '../utils/sessionAuth.js';
16
+ import { MessageFormatter } from '../shared/messageFormatter.js';
17
+ import { Client } from 'node-appwrite';
18
18
 
19
19
  export interface AdapterFactoryConfig {
20
20
  appwriteEndpoint: string;
@@ -201,134 +201,153 @@ export class AdapterFactory {
201
201
  /**
202
202
  * Create TablesDB adapter with dynamic import
203
203
  */
204
- private static async createTablesDBAdapter(
205
- config: AdapterFactoryConfig
206
- ): Promise<{ adapter: DatabaseAdapter; client: any }> {
207
- const startTime = Date.now();
208
-
209
- try {
210
- logger.info('Creating TablesDB adapter (static SDK imports)', {
211
- endpoint: config.appwriteEndpoint,
212
- operation: 'createTablesDBAdapter'
213
- });
214
-
215
- // Use pre-configured client or create session-aware client with TablesDB Client
216
- let client: any;
217
- if (config.preConfiguredClient) {
218
- client = config.preConfiguredClient;
219
- } else {
220
- client = new Client()
221
- .setEndpoint(config.appwriteEndpoint)
222
- .setProject(config.appwriteProject);
223
-
224
- // Set authentication method with mode headers
225
- // Prefer session with admin mode, fallback to API key with default mode
226
- if (config.sessionCookie && isValidSessionCookie(config.sessionCookie)) {
227
- client.setSession(config.sessionCookie);
228
- client.headers['X-Appwrite-Mode'] = 'admin';
229
- logger.debug('Using session authentication for TablesDB adapter', {
230
- project: config.appwriteProject,
231
- operation: 'createTablesDBAdapter'
232
- });
233
- } else if (config.appwriteKey) {
234
- client.setKey(config.appwriteKey);
235
- client.headers['X-Appwrite-Mode'] = 'default';
236
- logger.debug('Using API key authentication for TablesDB adapter', {
237
- project: config.appwriteProject,
238
- operation: 'createTablesDBAdapter'
239
- });
240
- } else {
241
- throw new Error("No authentication available for adapter");
242
- }
243
- }
244
-
245
- const adapter = new TablesDBAdapter(client);
246
-
247
- const totalDuration = Date.now() - startTime;
248
- logger.info('TablesDB adapter created successfully', {
249
- totalDuration,
250
- endpoint: config.appwriteEndpoint,
251
- operation: 'createTablesDBAdapter'
252
- });
253
-
254
- return { adapter, client };
255
-
256
- } catch (error) {
257
- const errorDuration = Date.now() - startTime;
258
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
259
-
260
- MessageFormatter.warning('Failed to create TablesDB adapter - falling back to legacy', { prefix: "Adapter" });
261
-
262
- logger.warn('TablesDB adapter creation failed, falling back to legacy', {
263
- error: errorMessage,
264
- errorDuration,
204
+ private static async createTablesDBAdapter(
205
+ config: AdapterFactoryConfig
206
+ ): Promise<{ adapter: DatabaseAdapter; client: any }> {
207
+ const startTime = Date.now();
208
+
209
+ try {
210
+ logger.info('Creating TablesDB adapter (static SDK imports)', {
265
211
  endpoint: config.appwriteEndpoint,
266
212
  operation: 'createTablesDBAdapter'
267
213
  });
268
214
 
269
- // Fallback to legacy adapter if TablesDB creation fails
270
- return this.createLegacyAdapter(config);
215
+ let client = new Client()
216
+ .setEndpoint(config.appwriteEndpoint)
217
+ .setProject(config.appwriteProject);
218
+
219
+ // Set authentication method with mode headers
220
+ // Prefer session with admin mode, fallback to API key with default mode
221
+ if (config.sessionCookie && isValidSessionCookie(config.sessionCookie)) {
222
+ client.setSession(config.sessionCookie);
223
+ client.headers['X-Appwrite-Mode'] = 'admin';
224
+ logger.debug('Using session authentication for TablesDB adapter', {
225
+ project: config.appwriteProject,
226
+ operation: 'createTablesDBAdapter'
227
+ });
228
+ } else if (config.appwriteKey) {
229
+ client.setKey(config.appwriteKey);
230
+ client.headers['X-Appwrite-Mode'] = 'default';
231
+ logger.debug('Using API key authentication for TablesDB adapter', {
232
+ project: config.appwriteProject,
233
+ operation: 'createTablesDBAdapter'
234
+ });
235
+ } else {
236
+ throw new Error("No authentication available for adapter");
237
+ }
238
+
239
+ const adapter = new TablesDBAdapter(client);
240
+
241
+ const totalDuration = Date.now() - startTime;
242
+ logger.info('TablesDB adapter created successfully', {
243
+ totalDuration,
244
+ endpoint: config.appwriteEndpoint,
245
+ operation: 'createTablesDBAdapter'
246
+ });
247
+
248
+ return { adapter, client };
249
+
250
+ } catch (error) {
251
+ const errorDuration = Date.now() - startTime;
252
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
253
+
254
+ // Analyze the error to determine if fallback is appropriate
255
+ const isAuthError = errorMessage.toLowerCase().includes('unauthorized') ||
256
+ errorMessage.toLowerCase().includes('forbidden') ||
257
+ errorMessage.toLowerCase().includes('invalid') ||
258
+ errorMessage.toLowerCase().includes('authentication');
259
+
260
+ const isVersionError = errorMessage.toLowerCase().includes('not found') ||
261
+ errorMessage.toLowerCase().includes('unsupported') ||
262
+ errorMessage.toLowerCase().includes('tablesdb');
263
+
264
+ // Only fallback to legacy if this is genuinely a TablesDB support issue
265
+ if (isVersionError) {
266
+ MessageFormatter.warning('TablesDB not supported on this server - using legacy adapter', { prefix: "Adapter" });
267
+ logger.warn('TablesDB not supported, falling back to legacy', {
268
+ error: errorMessage,
269
+ errorDuration,
270
+ endpoint: config.appwriteEndpoint,
271
+ operation: 'createTablesDBAdapter'
272
+ });
273
+ return this.createLegacyAdapter(config);
274
+ } else {
275
+ // For auth or other errors, re-throw to surface the real problem
276
+ logger.error('TablesDB adapter creation failed with non-version error', {
277
+ error: errorMessage,
278
+ errorDuration,
279
+ endpoint: config.appwriteEndpoint,
280
+ operation: 'createTablesDBAdapter',
281
+ isAuthError
282
+ });
283
+
284
+ throw new AdapterError(
285
+ `TablesDB adapter creation failed: ${errorMessage}`,
286
+ 'TABLESDB_ADAPTER_CREATION_FAILED',
287
+ error instanceof Error ? error : undefined
288
+ );
289
+ }
271
290
  }
272
291
  }
273
292
 
274
293
  /**
275
294
  * Create Legacy adapter with dynamic import
276
295
  */
277
- private static async createLegacyAdapter(
278
- config: AdapterFactoryConfig
279
- ): Promise<{ adapter: DatabaseAdapter; client: any }> {
280
- const startTime = Date.now();
281
-
282
- try {
283
- logger.info('Creating legacy adapter (static SDK imports)', {
284
- endpoint: config.appwriteEndpoint,
285
- operation: 'createLegacyAdapter'
286
- });
287
-
288
- // Use pre-configured client or create session-aware client with Legacy Client
289
- let client: any;
290
- if (config.preConfiguredClient) {
291
- client = config.preConfiguredClient;
292
- } else {
293
- client = new Client()
294
- .setEndpoint(config.appwriteEndpoint)
295
- .setProject(config.appwriteProject);
296
-
297
- // Set authentication method with mode headers
298
- // Prefer session with admin mode, fallback to API key with default mode
299
- if (config.sessionCookie && isValidSessionCookie(config.sessionCookie)) {
300
- (client as any).setSession(config.sessionCookie);
301
- client.headers['X-Appwrite-Mode'] = 'admin';
302
- logger.debug('Using session authentication for Legacy adapter', {
303
- project: config.appwriteProject,
304
- operation: 'createLegacyAdapter'
305
- });
306
- } else if (config.appwriteKey) {
307
- client.setKey(config.appwriteKey);
308
- client.headers['X-Appwrite-Mode'] = 'default';
309
- logger.debug('Using API key authentication for Legacy adapter', {
310
- project: config.appwriteProject,
311
- operation: 'createLegacyAdapter'
312
- });
313
- } else {
314
- throw new Error("No authentication available for adapter");
315
- }
316
- }
317
-
318
- const adapter = new LegacyAdapter(client);
319
-
320
- const totalDuration = Date.now() - startTime;
321
- logger.info('Legacy adapter created successfully', {
322
- totalDuration,
323
- endpoint: config.appwriteEndpoint,
324
- operation: 'createLegacyAdapter'
325
- });
326
-
327
- return { adapter, client };
328
-
329
- } catch (error) {
330
- const errorDuration = Date.now() - startTime;
331
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
296
+ private static async createLegacyAdapter(
297
+ config: AdapterFactoryConfig
298
+ ): Promise<{ adapter: DatabaseAdapter; client: any }> {
299
+ const startTime = Date.now();
300
+
301
+ try {
302
+ logger.info('Creating legacy adapter (static SDK imports)', {
303
+ endpoint: config.appwriteEndpoint,
304
+ operation: 'createLegacyAdapter'
305
+ });
306
+
307
+ // Use pre-configured client or create session-aware client with Legacy Client
308
+ let client: any;
309
+ if (config.preConfiguredClient) {
310
+ client = config.preConfiguredClient;
311
+ } else {
312
+ client = new Client()
313
+ .setEndpoint(config.appwriteEndpoint)
314
+ .setProject(config.appwriteProject);
315
+
316
+ // Set authentication method with mode headers
317
+ // Prefer session with admin mode, fallback to API key with default mode
318
+ if (config.sessionCookie && isValidSessionCookie(config.sessionCookie)) {
319
+ (client as any).setSession(config.sessionCookie);
320
+ client.headers['X-Appwrite-Mode'] = 'admin';
321
+ logger.debug('Using session authentication for Legacy adapter', {
322
+ project: config.appwriteProject,
323
+ operation: 'createLegacyAdapter'
324
+ });
325
+ } else if (config.appwriteKey) {
326
+ client.setKey(config.appwriteKey);
327
+ client.headers['X-Appwrite-Mode'] = 'default';
328
+ logger.debug('Using API key authentication for Legacy adapter', {
329
+ project: config.appwriteProject,
330
+ operation: 'createLegacyAdapter'
331
+ });
332
+ } else {
333
+ throw new Error("No authentication available for adapter");
334
+ }
335
+ }
336
+
337
+ const adapter = new LegacyAdapter(client);
338
+
339
+ const totalDuration = Date.now() - startTime;
340
+ logger.info('Legacy adapter created successfully', {
341
+ totalDuration,
342
+ endpoint: config.appwriteEndpoint,
343
+ operation: 'createLegacyAdapter'
344
+ });
345
+
346
+ return { adapter, client };
347
+
348
+ } catch (error) {
349
+ const errorDuration = Date.now() - startTime;
350
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
332
351
 
333
352
  logger.error('Failed to load legacy Appwrite SDK', {
334
353
  error: errorMessage,
@@ -507,4 +526,4 @@ export async function getApiCapabilities(
507
526
  terminology: metadata.terminology,
508
527
  capabilities
509
528
  };
510
- }
529
+ }
@@ -101,6 +101,7 @@ export interface CreateIndexParams {
101
101
  type: string;
102
102
  attributes: string[];
103
103
  orders?: string[];
104
+ lengths?: number[];
104
105
  }
105
106
 
106
107
  export interface ListIndexesParams {
@@ -597,12 +597,15 @@ export class LegacyAdapter extends BaseAdapter {
597
597
 
598
598
  case 'enum':
599
599
  const enumAttr = existingAttr as Models.AttributeEnum;
600
- console.log('Updating enum attribute with params:', params);
600
+ // Choose elements to send only when provided, otherwise preserve existing
601
+ const provided = params.elements;
602
+ const existing = (enumAttr as any)?.elements;
603
+ const nextElements = (Array.isArray(provided) && provided.length > 0) ? provided : existing;
601
604
  result = await this.databases.updateEnumAttribute({
602
605
  databaseId: params.databaseId,
603
606
  collectionId: params.tableId,
604
607
  key: params.key,
605
- elements: enumAttr.elements || [],
608
+ elements: nextElements,
606
609
  required: params.required ?? enumAttr.required,
607
610
  xdefault: params.default !== undefined ? params.default : enumAttr.default
608
611
  });
@@ -33,6 +33,7 @@ import {
33
33
  type AdapterMetadata,
34
34
  AdapterError
35
35
  } from './DatabaseAdapter.js';
36
+ import { type Column } from 'appwrite-utils';
36
37
  import { TablesDB, Client } from "node-appwrite";
37
38
 
38
39
  /**
@@ -256,8 +257,14 @@ export class TablesDBAdapter extends BaseAdapter {
256
257
  async listIndexes(params: ListIndexesParams): Promise<ApiResponse> {
257
258
  try {
258
259
  const result = await this.tablesDB.listIndexes(params);
260
+ // Normalize TablesDB response to expose a consistent 'attributes' array
261
+ const normalized = (result.indexes || []).map((idx: any) => ({
262
+ ...idx,
263
+ attributes: Array.isArray(idx?.attributes) ? idx.attributes : (Array.isArray(idx?.columns) ? idx.columns : []),
264
+ orders: Array.isArray(idx?.orders) ? idx.orders : (Array.isArray(idx?.directions) ? idx.directions : []),
265
+ }));
259
266
  return {
260
- data: result.indexes,
267
+ data: normalized,
261
268
  total: result.total
262
269
  };
263
270
  } catch (error) {
@@ -271,14 +278,15 @@ export class TablesDBAdapter extends BaseAdapter {
271
278
 
272
279
  async createIndex(params: CreateIndexParams): Promise<ApiResponse> {
273
280
  try {
274
- const result = await this.tablesDB.createIndex(
275
- params.databaseId,
276
- params.tableId,
277
- params.key,
278
- params.type as IndexType,
279
- params.attributes,
280
- params.orders || []
281
- );
281
+ const result = await this.tablesDB.createIndex({
282
+ databaseId: params.databaseId,
283
+ tableId: params.tableId,
284
+ key: params.key,
285
+ type: params.type as IndexType,
286
+ columns: params.attributes,
287
+ orders: params.orders || [],
288
+ lengths: params.lengths || [],
289
+ });
282
290
  return { data: result };
283
291
  } catch (error) {
284
292
  throw new AdapterError(
@@ -494,7 +502,7 @@ export class TablesDBAdapter extends BaseAdapter {
494
502
 
495
503
  // Use the appropriate updateXColumn method based on the column type
496
504
  // Cast column to proper Models type to access its specific properties
497
- const columnType = (column as any).type;
505
+ const columnType = (column.type === 'string' && !('format' in column)) || column.type !== 'string' ? column.type : 'enum';
498
506
 
499
507
  switch (columnType) {
500
508
  case 'string':
@@ -651,13 +651,6 @@ const updateLegacyAttribute = async (
651
651
  collectionId: string,
652
652
  attribute: Attribute
653
653
  ): Promise<void> => {
654
- console.log(`DEBUG updateLegacyAttribute before normalizeMinMaxValues:`, {
655
- key: attribute.key,
656
- type: attribute.type,
657
- min: (attribute as any).min,
658
- max: (attribute as any).max
659
- });
660
-
661
654
  const { min: normalizedMin, max: normalizedMax } =
662
655
  normalizeMinMaxValues(attribute);
663
656
 
@@ -1515,37 +1508,10 @@ export const createOrUpdateAttribute = async (
1515
1508
  // `Updating attribute with same key ${attribute.key} but different values`
1516
1509
  // );
1517
1510
 
1518
- // DEBUG: Log before object merge to detect corruption
1519
- if ((attribute.key === 'conversationType' || attribute.key === 'messageStreakCount')) {
1520
- console.log(`[DEBUG] MERGE - key="${attribute.key}"`, {
1521
- found: {
1522
- elements: (foundAttribute as any)?.elements,
1523
- min: (foundAttribute as any)?.min,
1524
- max: (foundAttribute as any)?.max
1525
- },
1526
- desired: {
1527
- elements: (attribute as any)?.elements,
1528
- min: (attribute as any)?.min,
1529
- max: (attribute as any)?.max
1530
- }
1531
- });
1532
- }
1533
-
1534
1511
  finalAttribute = {
1535
1512
  ...foundAttribute,
1536
1513
  ...attribute,
1537
1514
  };
1538
-
1539
- // DEBUG: Log after object merge to detect corruption
1540
- if ((finalAttribute.key === 'conversationType' || finalAttribute.key === 'messageStreakCount')) {
1541
- console.log(`[DEBUG] AFTER_MERGE - key="${finalAttribute.key}"`, {
1542
- merged: {
1543
- elements: finalAttribute?.elements,
1544
- min: (finalAttribute as any)?.min,
1545
- max: (finalAttribute as any)?.max
1546
- }
1547
- });
1548
- }
1549
1515
  action = "update";
1550
1516
  } else if (
1551
1517
  !updateEnabled &&