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
@@ -0,0 +1,125 @@
1
+ from collections.abc import Callable
2
+ from typing import Any, Literal, Protocol
3
+
4
+ from pydantic import BaseModel, Field
5
+
6
+
7
+ class AppwriteHeaders(BaseModel):
8
+ """Appwrite request headers"""
9
+
10
+ x_appwrite_trigger: Literal["http", "schedule", "event"] | None = Field(
11
+ None, alias="x-appwrite-trigger"
12
+ )
13
+ x_appwrite_event: str | None = Field(None, alias="x-appwrite-event")
14
+ x_appwrite_key: str | None = Field(None, alias="x-appwrite-key")
15
+ x_appwrite_user_id: str | None = Field(None, alias="x-appwrite-user-id")
16
+ x_appwrite_user_jwt: str | None = Field(None, alias="x-appwrite-user-jwt")
17
+ x_appwrite_country_code: str | None = Field(None, alias="x-appwrite-country-code")
18
+ x_appwrite_continent_code: str | None = Field(
19
+ None, alias="x-appwrite-continent-code"
20
+ )
21
+ x_appwrite_continent_eu: str | None = Field(None, alias="x-appwrite-continent-eu")
22
+
23
+ # Allow additional headers
24
+ model_config = {"extra": "allow", "populate_by_name": True}
25
+
26
+
27
+ class AppwriteEnv(BaseModel):
28
+ """Appwrite environment variables"""
29
+
30
+ APPWRITE_FUNCTION_API_ENDPOINT: str
31
+ APPWRITE_VERSION: str
32
+ APPWRITE_REGION: str
33
+ APPWRITE_FUNCTION_API_KEY: str | None = None
34
+ APPWRITE_FUNCTION_ID: str
35
+ APPWRITE_FUNCTION_NAME: str
36
+ APPWRITE_FUNCTION_DEPLOYMENT: str
37
+ APPWRITE_FUNCTION_PROJECT_ID: str
38
+ APPWRITE_FUNCTION_RUNTIME_NAME: str
39
+ APPWRITE_FUNCTION_RUNTIME_VERSION: str
40
+
41
+
42
+ class AppwriteRequest(Protocol):
43
+ """Appwrite function request object with proper callable typing"""
44
+
45
+ bodyText: str | None
46
+ bodyJson: dict[str, Any] | str | None
47
+ body: Any
48
+ headers: dict[str, str]
49
+ scheme: Literal["http", "https"] | None
50
+ method: Literal["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"]
51
+ url: str | None
52
+ host: str | None
53
+ port: int | str | None
54
+ path: str | None
55
+ queryString: str | None
56
+ query: dict[str, str | list[str]] | None
57
+ variables: dict[str, str] | None
58
+ payload: str | None
59
+
60
+ async def text(self) -> str: ...
61
+
62
+
63
+ class AppwriteResponse(Protocol):
64
+ """Appwrite function response object with proper callable typing"""
65
+
66
+ def json(
67
+ self,
68
+ data: Any,
69
+ status: int | None = None,
70
+ headers: dict[str, str] | None = None,
71
+ ) -> None: ...
72
+
73
+ def text(
74
+ self,
75
+ body: str,
76
+ status: int | None = None,
77
+ headers: dict[str, str] | None = None,
78
+ ) -> None: ...
79
+
80
+ def empty(self) -> None: ...
81
+
82
+ def binary(self, data: bytes) -> None: ...
83
+
84
+ def redirect(self, url: str, status: int | None = None) -> None: ...
85
+
86
+
87
+ class AppwriteContext(Protocol):
88
+ """Complete Appwrite function context with proper typing"""
89
+
90
+ req: AppwriteRequest
91
+ res: AppwriteResponse
92
+
93
+ def log(self, message: str) -> None: ...
94
+
95
+ def error(self, message: str) -> None: ...
96
+
97
+
98
+ # Alternative: BaseModel version for cases where you need Pydantic features
99
+ # but want proper typing hints
100
+
101
+
102
+ class AppwriteRequestModel(BaseModel):
103
+ """Pydantic model for request validation (if needed)"""
104
+
105
+ bodyText: str | None = None
106
+ bodyJson: dict[str, Any] | str | None = None
107
+ body: Any = None
108
+ headers: dict[str, str] = Field(default_factory=dict)
109
+ scheme: Literal["http", "https"] | None = None
110
+ method: Literal["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"]
111
+ url: str | None = None
112
+ host: str | None = None
113
+ port: int | str | None = None
114
+ path: str | None = None
115
+ queryString: str | None = None
116
+ query: dict[str, str | list[str]] | None = None
117
+ variables: dict[str, str] | None = None
118
+ payload: str | None = None
119
+
120
+ model_config = {"arbitrary_types_allowed": True}
121
+
122
+
123
+ # Type alias for the log/error functions
124
+ LogFunction = Callable[[str], None]
125
+ ErrorFunction = Callable[[str], None]
@@ -0,0 +1,46 @@
1
+ import os
2
+ from appwrite.client import Client
3
+ from context import AppwriteContext, AppwriteHeaders, AppwriteRequestModel
4
+
5
+ def main(context: AppwriteContext):
6
+ req = context.req
7
+ res = context.res
8
+ log = context.log
9
+ error = context.error
10
+
11
+ # Optional: Validate request headers using Pydantic
12
+ try:
13
+ headers = AppwriteHeaders.model_validate(req.headers)
14
+ log("Headers validation successful")
15
+ except Exception as validation_error:
16
+ error(f"Headers validation failed: {validation_error}")
17
+
18
+ # Optional: Validate full request using Pydantic model
19
+ try:
20
+ request_model = AppwriteRequestModel.model_validate({
21
+ 'method': req.method,
22
+ 'headers': req.headers,
23
+ 'path': req.path,
24
+ 'url': req.url,
25
+ 'body': req.body,
26
+ 'bodyText': req.bodyText,
27
+ 'bodyJson': req.bodyJson
28
+ })
29
+ log("Request validation successful")
30
+ except Exception as validation_error:
31
+ error(f"Request validation failed: {validation_error}")
32
+
33
+ client = Client()
34
+ client.set_endpoint(os.getenv('APPWRITE_FUNCTION_ENDPOINT'))
35
+ client.set_project(os.getenv('APPWRITE_FUNCTION_PROJECT_ID'))
36
+ client.set_key(os.getenv('APPWRITE_FUNCTION_API_KEY'))
37
+
38
+ log(f"Processing {req.method} request to {req.path}")
39
+
40
+ return res.json({
41
+ 'message': 'Hello from Python function!',
42
+ 'functionName': '{{functionName}}',
43
+ 'method': req.method,
44
+ 'path': req.path,
45
+ 'headers': req.headers
46
+ })
@@ -2,7 +2,7 @@ import inquirer from "inquirer";
2
2
  import { UtilsController } from "./utilsController.js";
3
3
  import { fetchAllCollections } from "./collections/methods.js";
4
4
  import { listBuckets, createBucket } from "./storage/methods.js";
5
- import { Databases, Storage, Client, Compression, Query, Functions, } from "node-appwrite";
5
+ import { Databases, Storage, Client, Compression, Query, Functions, DatabaseType, } from "node-appwrite";
6
6
  import { PermissionToAppwritePermission, RuntimeSchema, permissionSchema, } from "appwrite-utils";
7
7
  import { ulid } from "ulidx";
8
8
  import chalk from "chalk";
@@ -192,10 +192,10 @@ export class InteractiveCLI {
192
192
  const configDatabases = this.getLocalDatabases();
193
193
  const allDatabases = [...databases, ...configDatabases]
194
194
  .reduce((acc, db) => {
195
- // Local config takes precedence - if a database with same name exists, use local version
196
- const existingIndex = acc.findIndex((d) => d.name === db.name);
195
+ // Local config takes precedence - if a database with same name or ID exists, use local version
196
+ const existingIndex = acc.findIndex((d) => d.name === db.name || d.$id === db.$id);
197
197
  if (existingIndex >= 0) {
198
- if (configDatabases.some((cdb) => cdb.name === db.name)) {
198
+ if (configDatabases.some((cdb) => cdb.name === db.name || cdb.$id === db.$id)) {
199
199
  acc[existingIndex] = db; // Replace with local version
200
200
  }
201
201
  }
@@ -204,14 +204,14 @@ export class InteractiveCLI {
204
204
  }
205
205
  return acc;
206
206
  }, []);
207
- const hasLocalAndRemote = allDatabases.some((db) => configDatabases.some((c) => c.name === db.name)) &&
208
- allDatabases.some((db) => !configDatabases.some((c) => c.name === db.name));
207
+ const hasLocalAndRemote = allDatabases.some((db) => configDatabases.some((c) => c.name === db.name || c.$id === db.$id)) &&
208
+ allDatabases.some((db) => !configDatabases.some((c) => c.name === db.name || c.$id === db.$id));
209
209
  const choices = allDatabases
210
210
  .sort((a, b) => a.name.localeCompare(b.name))
211
211
  .map((db) => ({
212
212
  name: db.name +
213
213
  (hasLocalAndRemote
214
- ? configDatabases.some((c) => c.name === db.name)
214
+ ? configDatabases.some((c) => c.name === db.name || c.$id === db.$id)
215
215
  ? " (Local)"
216
216
  : " (Remote)"
217
217
  : ""),
@@ -245,20 +245,20 @@ export class InteractiveCLI {
245
245
  }
246
246
  let allCollections = preferLocal
247
247
  ? remoteCollections.reduce((acc, remoteCollection) => {
248
- if (!acc.some((c) => c.name === remoteCollection.name)) {
248
+ if (!acc.some((c) => c.name === remoteCollection.name || c.$id === remoteCollection.$id)) {
249
249
  acc.push(remoteCollection);
250
250
  }
251
251
  return acc;
252
252
  }, [...configCollections])
253
253
  : [
254
254
  ...remoteCollections,
255
- ...configCollections.filter((c) => !remoteCollections.some((rc) => rc.name === c.name)),
255
+ ...configCollections.filter((c) => !remoteCollections.some((rc) => rc.name === c.name || rc.$id === c.$id)),
256
256
  ];
257
257
  if (shouldFilterByDatabase) {
258
258
  // Show collections that EITHER exist in the remote database OR have matching local databaseId metadata
259
259
  allCollections = allCollections.filter((c) => {
260
260
  // Include if it exists remotely in this database
261
- const existsInRemoteDb = remoteCollections.some((rc) => rc.name === c.name);
261
+ const existsInRemoteDb = remoteCollections.some((rc) => rc.name === c.name || rc.$id === c.$id);
262
262
  // Include if local metadata claims it belongs to this database
263
263
  const hasMatchingLocalMetadata = c.databaseId === database.$id;
264
264
  return existsInRemoteDb || hasMatchingLocalMetadata;
@@ -266,8 +266,8 @@ export class InteractiveCLI {
266
266
  }
267
267
  // Filter out system tables (those starting with underscore)
268
268
  allCollections = allCollections.filter((collection) => !collection.$id.startsWith('_'));
269
- const hasLocalAndRemote = allCollections.some((coll) => configCollections.some((c) => c.name === coll.name)) &&
270
- allCollections.some((coll) => !configCollections.some((c) => c.name === coll.name));
269
+ const hasLocalAndRemote = allCollections.some((coll) => configCollections.some((c) => c.name === coll.name || c.$id === coll.$id)) &&
270
+ allCollections.some((coll) => !configCollections.some((c) => c.name === coll.name || c.$id === coll.$id));
271
271
  // Enhanced choice display with type indicators
272
272
  const choices = allCollections
273
273
  .sort((a, b) => {
@@ -280,7 +280,7 @@ export class InteractiveCLI {
280
280
  return a.name.localeCompare(b.name);
281
281
  })
282
282
  .map((collection) => {
283
- const localCollection = configCollections.find((c) => c.name === collection.name);
283
+ const localCollection = configCollections.find((c) => c.name === collection.name || c.$id === collection.$id);
284
284
  const isLocal = !!localCollection;
285
285
  const isTable = localCollection?._isFromTablesDir || collection._isFromTablesDir || false;
286
286
  const sourceFolder = localCollection?._sourceFolder || collection._sourceFolder || 'collections';
@@ -350,6 +350,8 @@ export class InteractiveCLI {
350
350
  else if (tablesCount > 0) {
351
351
  MessageFormatter.info(`📊 ${tablesCount} tables available from tables/ folder`, { prefix: "Collections" });
352
352
  }
353
+ // Show current database context clearly before view mode selection
354
+ MessageFormatter.info(`DB: ${database.name}`, { prefix: "Collections" });
353
355
  // Ask user if they want to filter by database or show all
354
356
  const { filterChoice } = await inquirer.prompt([
355
357
  {
@@ -499,7 +501,7 @@ export class InteractiveCLI {
499
501
  // Combine functions, preferring local ones
500
502
  const allFunctions = [
501
503
  ...localFunctions,
502
- ...remoteFunctions.functions.filter((rf) => !localFunctions.some((lf) => lf.name === rf.name)),
504
+ ...remoteFunctions.functions.filter((rf) => !localFunctions.some((lf) => lf.name === rf.name || lf.$id === rf.$id)),
503
505
  ];
504
506
  const { selectedFunctions } = await inquirer.prompt([
505
507
  {
@@ -507,7 +509,7 @@ export class InteractiveCLI {
507
509
  name: "selectedFunctions",
508
510
  message,
509
511
  choices: allFunctions.map((f) => ({
510
- name: `${f.name} (${f.$id})${localFunctions.some((lf) => lf.name === f.name)
512
+ name: `${f.name} (${f.$id})${localFunctions.some((lf) => lf.name === f.name || lf.$id === f.$id)
511
513
  ? " (Local)"
512
514
  : " (Remote)"}`,
513
515
  value: f,
@@ -773,6 +775,7 @@ export class InteractiveCLI {
773
775
  $updatedAt: DateTime.now().toISO(),
774
776
  name: db.name,
775
777
  enabled: true,
778
+ type: "tablesdb",
776
779
  }));
777
780
  }
778
781
  /**