appwrite-utils-cli 1.7.8 → 1.8.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.
Files changed (111) hide show
  1. package/CHANGELOG.md +14 -199
  2. package/README.md +87 -30
  3. package/dist/adapters/AdapterFactory.js +5 -25
  4. package/dist/adapters/DatabaseAdapter.d.ts +17 -2
  5. package/dist/adapters/LegacyAdapter.d.ts +2 -1
  6. package/dist/adapters/LegacyAdapter.js +212 -16
  7. package/dist/adapters/TablesDBAdapter.d.ts +2 -12
  8. package/dist/adapters/TablesDBAdapter.js +261 -57
  9. package/dist/cli/commands/databaseCommands.js +10 -10
  10. package/dist/cli/commands/functionCommands.js +17 -8
  11. package/dist/collections/attributes.js +447 -125
  12. package/dist/collections/methods.js +197 -186
  13. package/dist/collections/tableOperations.d.ts +86 -0
  14. package/dist/collections/tableOperations.js +434 -0
  15. package/dist/collections/transferOperations.d.ts +3 -2
  16. package/dist/collections/transferOperations.js +93 -12
  17. package/dist/config/services/ConfigLoaderService.d.ts +7 -0
  18. package/dist/config/services/ConfigLoaderService.js +47 -1
  19. package/dist/config/yamlConfig.d.ts +221 -88
  20. package/dist/examples/yamlTerminologyExample.d.ts +1 -1
  21. package/dist/examples/yamlTerminologyExample.js +6 -3
  22. package/dist/functions/deployments.js +5 -23
  23. package/dist/functions/fnConfigDiscovery.d.ts +3 -0
  24. package/dist/functions/fnConfigDiscovery.js +108 -0
  25. package/dist/functions/methods.js +4 -2
  26. package/dist/functions/pathResolution.d.ts +37 -0
  27. package/dist/functions/pathResolution.js +185 -0
  28. package/dist/functions/templates/count-docs-in-collection/README.md +54 -0
  29. package/dist/functions/templates/count-docs-in-collection/package.json +25 -0
  30. package/dist/functions/templates/count-docs-in-collection/src/main.ts +159 -0
  31. package/dist/functions/templates/count-docs-in-collection/src/request.ts +9 -0
  32. package/dist/functions/templates/count-docs-in-collection/tsconfig.json +28 -0
  33. package/dist/functions/templates/hono-typescript/README.md +286 -0
  34. package/dist/functions/templates/hono-typescript/package.json +26 -0
  35. package/dist/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
  36. package/dist/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
  37. package/dist/functions/templates/hono-typescript/src/app.ts +180 -0
  38. package/dist/functions/templates/hono-typescript/src/context.ts +103 -0
  39. package/dist/functions/templates/hono-typescript/src/index.ts +54 -0
  40. package/dist/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
  41. package/dist/functions/templates/hono-typescript/tsconfig.json +20 -0
  42. package/dist/functions/templates/typescript-node/README.md +32 -0
  43. package/dist/functions/templates/typescript-node/package.json +25 -0
  44. package/dist/functions/templates/typescript-node/src/context.ts +103 -0
  45. package/dist/functions/templates/typescript-node/src/index.ts +29 -0
  46. package/dist/functions/templates/typescript-node/tsconfig.json +28 -0
  47. package/dist/functions/templates/uv/README.md +31 -0
  48. package/dist/functions/templates/uv/pyproject.toml +30 -0
  49. package/dist/functions/templates/uv/src/__init__.py +0 -0
  50. package/dist/functions/templates/uv/src/context.py +125 -0
  51. package/dist/functions/templates/uv/src/index.py +46 -0
  52. package/dist/interactiveCLI.js +18 -15
  53. package/dist/main.js +219 -81
  54. package/dist/migrations/appwriteToX.d.ts +88 -23
  55. package/dist/migrations/comprehensiveTransfer.d.ts +2 -0
  56. package/dist/migrations/comprehensiveTransfer.js +83 -6
  57. package/dist/migrations/dataLoader.d.ts +227 -69
  58. package/dist/migrations/dataLoader.js +3 -3
  59. package/dist/migrations/importController.js +3 -3
  60. package/dist/migrations/relationships.d.ts +8 -2
  61. package/dist/migrations/services/ImportOrchestrator.js +3 -3
  62. package/dist/migrations/transfer.js +159 -37
  63. package/dist/shared/attributeMapper.d.ts +20 -0
  64. package/dist/shared/attributeMapper.js +203 -0
  65. package/dist/shared/selectionDialogs.d.ts +1 -1
  66. package/dist/shared/selectionDialogs.js +39 -11
  67. package/dist/storage/schemas.d.ts +354 -92
  68. package/dist/utils/configDiscovery.js +4 -3
  69. package/dist/utils/versionDetection.d.ts +0 -4
  70. package/dist/utils/versionDetection.js +41 -173
  71. package/dist/utils/yamlConverter.js +89 -16
  72. package/dist/utils/yamlLoader.d.ts +1 -1
  73. package/dist/utils/yamlLoader.js +6 -2
  74. package/dist/utilsController.d.ts +2 -1
  75. package/dist/utilsController.js +151 -22
  76. package/package.json +7 -5
  77. package/scripts/copy-templates.ts +23 -0
  78. package/src/adapters/AdapterFactory.ts +119 -143
  79. package/src/adapters/DatabaseAdapter.ts +18 -3
  80. package/src/adapters/LegacyAdapter.ts +236 -105
  81. package/src/adapters/TablesDBAdapter.ts +773 -643
  82. package/src/cli/commands/databaseCommands.ts +19 -19
  83. package/src/cli/commands/functionCommands.ts +23 -14
  84. package/src/collections/attributes.ts +2054 -1611
  85. package/src/collections/methods.ts +208 -293
  86. package/src/collections/tableOperations.ts +506 -0
  87. package/src/collections/transferOperations.ts +218 -144
  88. package/src/config/services/ConfigLoaderService.ts +62 -1
  89. package/src/examples/yamlTerminologyExample.ts +10 -5
  90. package/src/functions/deployments.ts +10 -35
  91. package/src/functions/fnConfigDiscovery.ts +103 -0
  92. package/src/functions/methods.ts +4 -2
  93. package/src/functions/pathResolution.ts +227 -0
  94. package/src/interactiveCLI.ts +25 -20
  95. package/src/main.ts +557 -202
  96. package/src/migrations/comprehensiveTransfer.ts +126 -50
  97. package/src/migrations/dataLoader.ts +3 -3
  98. package/src/migrations/importController.ts +3 -3
  99. package/src/migrations/services/ImportOrchestrator.ts +3 -3
  100. package/src/migrations/transfer.ts +148 -131
  101. package/src/shared/attributeMapper.ts +229 -0
  102. package/src/shared/selectionDialogs.ts +65 -32
  103. package/src/utils/configDiscovery.ts +9 -3
  104. package/src/utils/versionDetection.ts +74 -228
  105. package/src/utils/yamlConverter.ts +94 -17
  106. package/src/utils/yamlLoader.ts +11 -4
  107. package/src/utilsController.ts +202 -36
  108. package/dist/utils/schemaStrings.d.ts +0 -14
  109. package/dist/utils/schemaStrings.js +0 -428
  110. package/dist/utils/sessionPreservationExample.d.ts +0 -1666
  111. package/dist/utils/sessionPreservationExample.js +0 -101
@@ -1,12 +1,13 @@
1
1
  import { Client, Databases, Query, Storage, Users, } from "node-appwrite";
2
2
  import {} from "appwrite-utils";
3
- import { loadConfig, loadConfigWithPath, findAppwriteConfig, findFunctionsDir, } from "./utils/loadConfigs.js";
3
+ import { findAppwriteConfig, findFunctionsDir, } from "./utils/loadConfigs.js";
4
+ import { normalizeFunctionName, validateFunctionDirectory } from './functions/pathResolution.js';
4
5
  import { UsersController } from "./users/methods.js";
5
6
  import { AppwriteToX } from "./migrations/appwriteToX.js";
6
7
  import { ImportController } from "./migrations/importController.js";
7
8
  import { ImportDataActions } from "./migrations/importDataActions.js";
8
9
  import { ensureDatabasesExist, wipeOtherDatabases, ensureCollectionsExist, } from "./databases/setup.js";
9
- import { createOrUpdateCollections, wipeDatabase, generateSchemas, fetchAllCollections, wipeCollection, } from "./collections/methods.js";
10
+ import { createOrUpdateCollections, createOrUpdateCollectionsViaAdapter, wipeDatabase, generateSchemas, fetchAllCollections, wipeCollection, } from "./collections/methods.js";
10
11
  import { wipeAllTables, wipeTableRows } from "./collections/methods.js";
11
12
  import { backupDatabase, ensureDatabaseConfigBucketsExist, wipeDocumentStorage, } from "./storage/methods.js";
12
13
  import path from "path";
@@ -39,6 +40,26 @@ export class UtilsController {
39
40
  * Get the UtilsController singleton instance
40
41
  */
41
42
  static getInstance(currentUserDir, directConfig) {
43
+ // Clear instance if currentUserDir has changed
44
+ if (UtilsController.instance &&
45
+ UtilsController.instance.currentUserDir !== currentUserDir) {
46
+ logger.debug(`Clearing singleton: currentUserDir changed from ${UtilsController.instance.currentUserDir} to ${currentUserDir}`, { prefix: "UtilsController" });
47
+ UtilsController.clearInstance();
48
+ }
49
+ // Clear instance if directConfig endpoint or project has changed
50
+ if (UtilsController.instance && directConfig) {
51
+ const existingConfig = UtilsController.instance.config;
52
+ if (existingConfig) {
53
+ const endpointChanged = directConfig.appwriteEndpoint &&
54
+ existingConfig.appwriteEndpoint !== directConfig.appwriteEndpoint;
55
+ const projectChanged = directConfig.appwriteProject &&
56
+ existingConfig.appwriteProject !== directConfig.appwriteProject;
57
+ if (endpointChanged || projectChanged) {
58
+ logger.debug("Clearing singleton: endpoint or project changed", { prefix: "UtilsController" });
59
+ UtilsController.clearInstance();
60
+ }
61
+ }
62
+ }
42
63
  if (!UtilsController.instance) {
43
64
  UtilsController.instance = new UtilsController(currentUserDir, directConfig);
44
65
  }
@@ -151,13 +172,19 @@ export class UtilsController {
151
172
  this.appwriteServer = client;
152
173
  this.adapter = adapter;
153
174
  this.config = config;
175
+ // Update config.apiMode from adapter if it's auto or not set
176
+ if (adapter && (!config.apiMode || config.apiMode === 'auto')) {
177
+ this.config.apiMode = adapter.getApiMode();
178
+ logger.debug(`Updated config.apiMode from adapter during init: ${this.config.apiMode}`, { prefix: "UtilsController" });
179
+ }
154
180
  this.database = new Databases(this.appwriteServer);
155
181
  this.storage = new Storage(this.appwriteServer);
156
182
  this.config.appwriteClient = this.appwriteServer;
157
183
  // Log only on FIRST initialization to avoid spam
158
184
  if (!this.isInitialized) {
159
185
  const apiMode = adapter.getApiMode();
160
- MessageFormatter.info(`Database adapter initialized (apiMode: ${apiMode})`, { prefix: "Adapter" });
186
+ const configApiMode = this.config.apiMode;
187
+ MessageFormatter.info(`Database adapter initialized (apiMode: ${apiMode}, config.apiMode: ${configApiMode})`, { prefix: "Adapter" });
161
188
  this.isInitialized = true;
162
189
  }
163
190
  else {
@@ -295,9 +322,14 @@ export class UtilsController {
295
322
  for (const entry of entries) {
296
323
  if (entry.isDirectory()) {
297
324
  const functionPath = path.join(functionsDir, entry.name);
298
- // Match with config functions by name
325
+ // Validate it's a function directory
326
+ if (!validateFunctionDirectory(functionPath)) {
327
+ continue; // Skip invalid directories
328
+ }
329
+ // Match with config functions using normalized names
299
330
  if (this.config?.functions) {
300
- const matchingFunc = this.config.functions.find((f) => f.name.toLowerCase() === entry.name.toLowerCase());
331
+ const normalizedEntryName = normalizeFunctionName(entry.name);
332
+ const matchingFunc = this.config.functions.find((f) => normalizeFunctionName(f.name) === normalizedEntryName);
301
333
  if (matchingFunc) {
302
334
  functionDirMap.set(matchingFunc.name, functionPath);
303
335
  }
@@ -417,25 +449,44 @@ export class UtilsController {
417
449
  await this.init();
418
450
  if (!this.database || !this.config)
419
451
  throw new Error("Database or config not initialized");
420
- await createOrUpdateCollections(this.database, database.$id, this.config, deletedCollections, collections);
452
+ // Ensure apiMode is properly set from adapter
453
+ if (this.adapter && (!this.config.apiMode || this.config.apiMode === 'auto')) {
454
+ this.config.apiMode = this.adapter.getApiMode();
455
+ logger.debug(`Updated config.apiMode from adapter: ${this.config.apiMode}`, { prefix: "UtilsController" });
456
+ }
457
+ // Always prefer adapter path for unified behavior. LegacyAdapter internally translates when needed.
458
+ if (this.adapter) {
459
+ logger.debug("Using adapter for createOrUpdateCollections (unified path)", {
460
+ prefix: "UtilsController",
461
+ apiMode: this.adapter.getApiMode()
462
+ });
463
+ await createOrUpdateCollectionsViaAdapter(this.adapter, database.$id, this.config, deletedCollections, collections);
464
+ }
465
+ else {
466
+ // Fallback if adapter is unavailable for some reason
467
+ logger.debug("Adapter unavailable, falling back to legacy Databases path", { prefix: "UtilsController" });
468
+ await createOrUpdateCollections(this.database, database.$id, this.config, deletedCollections, collections);
469
+ }
421
470
  }
422
471
  async generateSchemas() {
423
472
  // Schema generation doesn't need Appwrite connection, just config
424
473
  if (!this.config) {
425
- if (this.appwriteFolderPath && this.appwriteConfigPath) {
426
- MessageFormatter.progress("Loading config from file...", { prefix: "Config" });
427
- try {
428
- const { config, actualConfigPath } = await loadConfigWithPath(this.appwriteFolderPath, { validate: false, strictMode: false, reportValidation: false });
429
- this.config = config;
430
- MessageFormatter.info(`Loaded config from: ${actualConfigPath}`, { prefix: "Config" });
431
- }
432
- catch (error) {
433
- MessageFormatter.error("Failed to load config from file", error instanceof Error ? error : undefined, { prefix: "Config" });
434
- return;
474
+ MessageFormatter.progress("Loading config from ConfigManager...", { prefix: "Config" });
475
+ try {
476
+ const configManager = ConfigManager.getInstance();
477
+ // Load config if not already loaded
478
+ if (!configManager.hasConfig()) {
479
+ await configManager.loadConfig({
480
+ configDir: this.currentUserDir,
481
+ validate: false,
482
+ strictMode: false,
483
+ });
435
484
  }
485
+ this.config = configManager.getConfig();
486
+ MessageFormatter.info("Config loaded successfully from ConfigManager", { prefix: "Config" });
436
487
  }
437
- else {
438
- MessageFormatter.error("No configuration available", undefined, { prefix: "Controller" });
488
+ catch (error) {
489
+ MessageFormatter.error("Failed to load config", error instanceof Error ? error : undefined, { prefix: "Config" });
439
490
  return;
440
491
  }
441
492
  }
@@ -521,13 +572,13 @@ export class UtilsController {
521
572
  logger.warn("Schema regeneration failed during sync:", error);
522
573
  }
523
574
  }
524
- async selectiveSync(databaseSelections, bucketSelections) {
575
+ async selectivePull(databaseSelections, bucketSelections) {
525
576
  await this.init();
526
577
  if (!this.database) {
527
578
  MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
528
579
  return;
529
580
  }
530
- MessageFormatter.progress("Starting selective sync...", { prefix: "Controller" });
581
+ MessageFormatter.progress("Starting selective pull (Appwrite → local config)...", { prefix: "Controller" });
531
582
  // Convert database selections to Models.Database format
532
583
  const selectedDatabases = [];
533
584
  for (const dbSelection of databaseSelections) {
@@ -547,7 +598,7 @@ export class UtilsController {
547
598
  }
548
599
  }
549
600
  if (selectedDatabases.length === 0) {
550
- MessageFormatter.warning("No valid databases selected for sync", { prefix: "Controller" });
601
+ MessageFormatter.warning("No valid databases selected for pull", { prefix: "Controller" });
551
602
  return;
552
603
  }
553
604
  // Log bucket selections if provided
@@ -560,7 +611,85 @@ export class UtilsController {
560
611
  }
561
612
  // Perform selective sync using the enhanced synchronizeConfigurations method
562
613
  await this.synchronizeConfigurations(selectedDatabases, this.config, databaseSelections, bucketSelections);
563
- MessageFormatter.success("Selective sync completed successfully!", { prefix: "Controller" });
614
+ MessageFormatter.success("Selective pull completed successfully! Remote config pulled to local.", { prefix: "Controller" });
615
+ }
616
+ async selectivePush(databaseSelections, bucketSelections) {
617
+ await this.init();
618
+ if (!this.database) {
619
+ MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
620
+ return;
621
+ }
622
+ MessageFormatter.progress("Starting selective push (local config → Appwrite)...", { prefix: "Controller" });
623
+ // Convert database selections to Models.Database format
624
+ const selectedDatabases = [];
625
+ for (const dbSelection of databaseSelections) {
626
+ // Get the full database object from the controller
627
+ const databases = await fetchAllDatabases(this.database);
628
+ const database = databases.find(db => db.$id === dbSelection.databaseId);
629
+ if (database) {
630
+ selectedDatabases.push(database);
631
+ MessageFormatter.info(`Selected database: ${database.name} (${database.$id})`, { prefix: "Controller" });
632
+ // Log selected tables for this database
633
+ if (dbSelection.tableIds && dbSelection.tableIds.length > 0) {
634
+ MessageFormatter.info(` Tables: ${dbSelection.tableIds.join(', ')}`, { prefix: "Controller" });
635
+ }
636
+ }
637
+ else {
638
+ MessageFormatter.warning(`Database with ID ${dbSelection.databaseId} not found`, { prefix: "Controller" });
639
+ }
640
+ }
641
+ if (selectedDatabases.length === 0) {
642
+ MessageFormatter.warning("No valid databases selected for push", { prefix: "Controller" });
643
+ return;
644
+ }
645
+ // Log bucket selections if provided
646
+ if (bucketSelections && bucketSelections.length > 0) {
647
+ MessageFormatter.info(`Selected ${bucketSelections.length} buckets:`, { prefix: "Controller" });
648
+ for (const bucketSelection of bucketSelections) {
649
+ const dbInfo = bucketSelection.databaseId ? ` (DB: ${bucketSelection.databaseId})` : '';
650
+ MessageFormatter.info(` - ${bucketSelection.bucketName} (${bucketSelection.bucketId})${dbInfo}`, { prefix: "Controller" });
651
+ }
652
+ }
653
+ // PUSH OPERATION: Push local configuration to Appwrite
654
+ // Build database-specific collection mappings from databaseSelections
655
+ const databaseCollectionsMap = new Map();
656
+ // Get all collections/tables from config (they're at the root level, not nested in databases)
657
+ const allCollections = this.config?.collections || this.config?.tables || [];
658
+ // Create database-specific collection mapping to preserve relationships
659
+ for (const dbSelection of databaseSelections) {
660
+ const collectionsForDatabase = [];
661
+ MessageFormatter.info(`Processing collections for database: ${dbSelection.databaseId}`, { prefix: "Controller" });
662
+ // Filter collections that were selected for THIS specific database
663
+ for (const collection of allCollections) {
664
+ const collectionId = collection.$id || collection.id;
665
+ // Check if this collection was selected for THIS database
666
+ if (dbSelection.tableIds.includes(collectionId)) {
667
+ collectionsForDatabase.push(collection);
668
+ MessageFormatter.info(` - Selected collection: ${collection.name || collectionId} for database ${dbSelection.databaseId}`, { prefix: "Controller" });
669
+ }
670
+ }
671
+ databaseCollectionsMap.set(dbSelection.databaseId, collectionsForDatabase);
672
+ MessageFormatter.info(`Database ${dbSelection.databaseId}: ${collectionsForDatabase.length} collections selected`, { prefix: "Controller" });
673
+ }
674
+ // Calculate total collections for logging
675
+ const totalSelectedCollections = Array.from(databaseCollectionsMap.values())
676
+ .reduce((total, collections) => total + collections.length, 0);
677
+ MessageFormatter.info(`Pushing ${totalSelectedCollections} selected tables/collections to ${databaseCollectionsMap.size} databases`, { prefix: "Controller" });
678
+ // Ensure databases exist
679
+ await this.ensureDatabasesExist(selectedDatabases);
680
+ await this.ensureDatabaseConfigBucketsExist(selectedDatabases);
681
+ // Create/update collections with database-specific context
682
+ for (const database of selectedDatabases) {
683
+ const collectionsForThisDatabase = databaseCollectionsMap.get(database.$id) || [];
684
+ if (collectionsForThisDatabase.length > 0) {
685
+ MessageFormatter.info(`Pushing ${collectionsForThisDatabase.length} collections to database ${database.$id} (${database.name})`, { prefix: "Controller" });
686
+ await this.createOrUpdateCollections(database, undefined, collectionsForThisDatabase);
687
+ }
688
+ else {
689
+ MessageFormatter.info(`No collections selected for database ${database.$id} (${database.name})`, { prefix: "Controller" });
690
+ }
691
+ }
692
+ MessageFormatter.success("Selective push completed successfully! Local config pushed to Appwrite.", { prefix: "Controller" });
564
693
  }
565
694
  async syncDb(databases = [], collections = []) {
566
695
  await this.init();
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.7.8",
4
+ "version": "1.8.1",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -24,7 +24,9 @@
24
24
  "appwrite-migrate": "./dist/main.js"
25
25
  },
26
26
  "scripts": {
27
- "build": "bun run tsc",
27
+ "build": "bun run tsc && bun run copy-templates",
28
+ "prebuild": "rm -rf dist",
29
+ "copy-templates": "tsx scripts/copy-templates.ts",
28
30
  "start": "tsx --no-cache src/main.ts",
29
31
  "deploy": "bun run build && npm publish --access public",
30
32
  "test": "jest",
@@ -37,10 +39,11 @@
37
39
  "@types/inquirer": "^9.0.8",
38
40
  "@types/json-schema": "^7.0.15",
39
41
  "@types/yargs": "^17.0.33",
40
- "appwrite-utils": "^1.6.1",
42
+ "appwrite-utils": "file:../appwrite-utils",
41
43
  "chalk": "^5.4.1",
42
44
  "cli-progress": "^3.12.0",
43
45
  "commander": "^12.1.0",
46
+ "decimal.js": "^10.6.0",
44
47
  "es-toolkit": "^1.39.4",
45
48
  "ignore": "^6.0.2",
46
49
  "inquirer": "^9.3.7",
@@ -48,8 +51,7 @@
48
51
  "jszip": "^3.10.1",
49
52
  "luxon": "^3.6.1",
50
53
  "nanostores": "^0.10.3",
51
- "node-appwrite": "^17",
52
- "node-appwrite-tablesdb": "npm:node-appwrite@^18.0.0",
54
+ "node-appwrite": "^20.2.1",
53
55
  "p-limit": "^6.2.0",
54
56
  "tar": "^7.4.3",
55
57
  "tsx": "^4.20.3",
@@ -0,0 +1,23 @@
1
+ import { cpSync, mkdirSync, existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ const src = join(process.cwd(), 'src', 'functions', 'templates');
5
+ const dest = join(process.cwd(), 'dist', 'functions', 'templates');
6
+
7
+ // Verify source exists
8
+ if (!existsSync(src)) {
9
+ console.error('❌ Error: Template source directory not found:', src);
10
+ process.exit(1);
11
+ }
12
+
13
+ // Create destination directory
14
+ mkdirSync(dest, { recursive: true });
15
+
16
+ // Copy templates recursively
17
+ try {
18
+ cpSync(src, dest, { recursive: true });
19
+ console.log('✓ Templates copied to dist/functions/templates/');
20
+ } catch (error) {
21
+ console.error('❌ Failed to copy templates:', error);
22
+ process.exit(1);
23
+ }
@@ -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 { getClientWithAuth } from '../utils/getClientFromConfig.js';
16
- import { isValidSessionCookie } from '../utils/sessionAuth.js';
17
- import { MessageFormatter } from '../shared/messageFormatter.js';
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';
18
18
 
19
19
  export interface AdapterFactoryConfig {
20
20
  appwriteEndpoint: string;
@@ -201,73 +201,61 @@ 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('Loading TablesDB SDK', {
211
- endpoint: config.appwriteEndpoint,
212
- operation: 'createTablesDBAdapter'
213
- });
214
-
215
- // Dynamic import of TablesDB SDK
216
- const importStartTime = Date.now();
217
- const { Client, TablesDB } = await import('node-appwrite-tablesdb');
218
- const importDuration = Date.now() - importStartTime;
219
-
220
- logger.debug('TablesDB SDK import successful', {
221
- importDuration,
222
- operation: 'createTablesDBAdapter'
223
- });
224
-
225
- // Use pre-configured client or create session-aware client with TablesDB Client
226
- let client: any;
227
- if (config.preConfiguredClient) {
228
- client = config.preConfiguredClient;
229
- } else {
230
- client = new Client()
231
- .setEndpoint(config.appwriteEndpoint)
232
- .setProject(config.appwriteProject);
233
-
234
- // Set authentication method with mode headers
235
- // Prefer session with admin mode, fallback to API key with default mode
236
- if (config.sessionCookie && isValidSessionCookie(config.sessionCookie)) {
237
- client.setSession(config.sessionCookie);
238
- client.headers['X-Appwrite-Mode'] = 'admin';
239
- logger.debug('Using session authentication for TablesDB adapter', {
240
- project: config.appwriteProject,
241
- operation: 'createTablesDBAdapter'
242
- });
243
- } else if (config.appwriteKey) {
244
- client.setKey(config.appwriteKey);
245
- client.headers['X-Appwrite-Mode'] = 'default';
246
- logger.debug('Using API key authentication for TablesDB adapter', {
247
- project: config.appwriteProject,
248
- operation: 'createTablesDBAdapter'
249
- });
250
- } else {
251
- throw new Error("No authentication available for adapter");
252
- }
253
- }
254
-
255
- const tablesDB = new TablesDB(client);
256
- const adapter = new TablesDBAdapter(tablesDB);
257
-
258
- const totalDuration = Date.now() - startTime;
259
- logger.info('TablesDB adapter created successfully', {
260
- totalDuration,
261
- importDuration,
262
- endpoint: config.appwriteEndpoint,
263
- operation: 'createTablesDBAdapter'
264
- });
265
-
266
- return { adapter, client };
267
-
268
- } catch (error) {
269
- const errorDuration = Date.now() - startTime;
270
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
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';
271
259
 
272
260
  MessageFormatter.warning('Failed to create TablesDB adapter - falling back to legacy', { prefix: "Adapter" });
273
261
 
@@ -286,73 +274,61 @@ export class AdapterFactory {
286
274
  /**
287
275
  * Create Legacy adapter with dynamic import
288
276
  */
289
- private static async createLegacyAdapter(
290
- config: AdapterFactoryConfig
291
- ): Promise<{ adapter: DatabaseAdapter; client: any }> {
292
- const startTime = Date.now();
293
-
294
- try {
295
- logger.info('Loading legacy Appwrite SDK', {
296
- endpoint: config.appwriteEndpoint,
297
- operation: 'createLegacyAdapter'
298
- });
299
-
300
- // Dynamic import of legacy SDK
301
- const importStartTime = Date.now();
302
- const { Client, Databases } = await import('node-appwrite');
303
- const importDuration = Date.now() - importStartTime;
304
-
305
- logger.debug('Legacy SDK import successful', {
306
- importDuration,
307
- operation: 'createLegacyAdapter'
308
- });
309
-
310
- // Use pre-configured client or create session-aware client with Legacy Client
311
- let client: any;
312
- if (config.preConfiguredClient) {
313
- client = config.preConfiguredClient;
314
- } else {
315
- client = new Client()
316
- .setEndpoint(config.appwriteEndpoint)
317
- .setProject(config.appwriteProject);
318
-
319
- // Set authentication method with mode headers
320
- // Prefer session with admin mode, fallback to API key with default mode
321
- if (config.sessionCookie && isValidSessionCookie(config.sessionCookie)) {
322
- (client as any).setSession(config.sessionCookie);
323
- client.headers['X-Appwrite-Mode'] = 'admin';
324
- logger.debug('Using session authentication for Legacy adapter', {
325
- project: config.appwriteProject,
326
- operation: 'createLegacyAdapter'
327
- });
328
- } else if (config.appwriteKey) {
329
- client.setKey(config.appwriteKey);
330
- client.headers['X-Appwrite-Mode'] = 'default';
331
- logger.debug('Using API key authentication for Legacy adapter', {
332
- project: config.appwriteProject,
333
- operation: 'createLegacyAdapter'
334
- });
335
- } else {
336
- throw new Error("No authentication available for adapter");
337
- }
338
- }
339
-
340
- const databases = new Databases(client);
341
- const adapter = new LegacyAdapter(databases);
342
-
343
- const totalDuration = Date.now() - startTime;
344
- logger.info('Legacy adapter created successfully', {
345
- totalDuration,
346
- importDuration,
347
- endpoint: config.appwriteEndpoint,
348
- operation: 'createLegacyAdapter'
349
- });
350
-
351
- return { adapter, client };
352
-
353
- } catch (error) {
354
- const errorDuration = Date.now() - startTime;
355
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
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';
356
332
 
357
333
  logger.error('Failed to load legacy Appwrite SDK', {
358
334
  error: errorMessage,
@@ -531,4 +507,4 @@ export async function getApiCapabilities(
531
507
  terminology: metadata.terminology,
532
508
  capabilities
533
509
  };
534
- }
510
+ }