appwrite-utils-cli 1.5.2 → 1.6.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.
- package/CHANGELOG.md +199 -0
- package/README.md +251 -29
- package/dist/adapters/AdapterFactory.d.ts +10 -3
- package/dist/adapters/AdapterFactory.js +213 -17
- package/dist/adapters/TablesDBAdapter.js +60 -17
- package/dist/backups/operations/bucketBackup.d.ts +19 -0
- package/dist/backups/operations/bucketBackup.js +197 -0
- package/dist/backups/operations/collectionBackup.d.ts +30 -0
- package/dist/backups/operations/collectionBackup.js +201 -0
- package/dist/backups/operations/comprehensiveBackup.d.ts +25 -0
- package/dist/backups/operations/comprehensiveBackup.js +238 -0
- package/dist/backups/schemas/bucketManifest.d.ts +93 -0
- package/dist/backups/schemas/bucketManifest.js +33 -0
- package/dist/backups/schemas/comprehensiveManifest.d.ts +108 -0
- package/dist/backups/schemas/comprehensiveManifest.js +32 -0
- package/dist/backups/tracking/centralizedTracking.d.ts +34 -0
- package/dist/backups/tracking/centralizedTracking.js +274 -0
- package/dist/cli/commands/configCommands.d.ts +8 -0
- package/dist/cli/commands/configCommands.js +160 -0
- package/dist/cli/commands/databaseCommands.d.ts +13 -0
- package/dist/cli/commands/databaseCommands.js +479 -0
- package/dist/cli/commands/functionCommands.d.ts +7 -0
- package/dist/cli/commands/functionCommands.js +289 -0
- package/dist/cli/commands/schemaCommands.d.ts +7 -0
- package/dist/cli/commands/schemaCommands.js +134 -0
- package/dist/cli/commands/transferCommands.d.ts +5 -0
- package/dist/cli/commands/transferCommands.js +384 -0
- package/dist/collections/attributes.d.ts +5 -4
- package/dist/collections/attributes.js +539 -246
- package/dist/collections/indexes.js +39 -37
- package/dist/collections/methods.d.ts +2 -16
- package/dist/collections/methods.js +90 -538
- package/dist/collections/transferOperations.d.ts +7 -0
- package/dist/collections/transferOperations.js +331 -0
- package/dist/collections/wipeOperations.d.ts +16 -0
- package/dist/collections/wipeOperations.js +328 -0
- package/dist/config/configMigration.d.ts +87 -0
- package/dist/config/configMigration.js +390 -0
- package/dist/config/configValidation.d.ts +66 -0
- package/dist/config/configValidation.js +358 -0
- package/dist/config/yamlConfig.d.ts +455 -1
- package/dist/config/yamlConfig.js +145 -52
- package/dist/databases/methods.js +3 -2
- package/dist/databases/setup.d.ts +1 -2
- package/dist/databases/setup.js +9 -87
- package/dist/examples/yamlTerminologyExample.d.ts +42 -0
- package/dist/examples/yamlTerminologyExample.js +269 -0
- package/dist/functions/deployments.js +11 -10
- package/dist/functions/methods.d.ts +1 -1
- package/dist/functions/methods.js +5 -4
- package/dist/init.js +9 -9
- package/dist/interactiveCLI.d.ts +8 -17
- package/dist/interactiveCLI.js +209 -1172
- package/dist/main.js +364 -21
- package/dist/migrations/afterImportActions.js +22 -30
- package/dist/migrations/appwriteToX.js +71 -25
- package/dist/migrations/dataLoader.js +35 -26
- package/dist/migrations/importController.js +29 -30
- package/dist/migrations/relationships.js +13 -12
- package/dist/migrations/services/ImportOrchestrator.js +16 -19
- package/dist/migrations/transfer.js +46 -46
- package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +3 -1
- package/dist/migrations/yaml/YamlImportConfigLoader.js +6 -3
- package/dist/migrations/yaml/YamlImportIntegration.d.ts +9 -3
- package/dist/migrations/yaml/YamlImportIntegration.js +22 -11
- package/dist/migrations/yaml/generateImportSchemas.d.ts +14 -1
- package/dist/migrations/yaml/generateImportSchemas.js +736 -7
- package/dist/schemas/authUser.d.ts +1 -1
- package/dist/setupController.js +3 -2
- package/dist/shared/backupMetadataSchema.d.ts +94 -0
- package/dist/shared/backupMetadataSchema.js +38 -0
- package/dist/shared/backupTracking.d.ts +18 -0
- package/dist/shared/backupTracking.js +176 -0
- package/dist/shared/confirmationDialogs.js +15 -15
- package/dist/shared/errorUtils.d.ts +54 -0
- package/dist/shared/errorUtils.js +95 -0
- package/dist/shared/functionManager.js +20 -19
- package/dist/shared/indexManager.js +12 -11
- package/dist/shared/jsonSchemaGenerator.js +10 -26
- package/dist/shared/logging.d.ts +51 -0
- package/dist/shared/logging.js +70 -0
- package/dist/shared/messageFormatter.d.ts +2 -0
- package/dist/shared/messageFormatter.js +10 -0
- package/dist/shared/migrationHelpers.d.ts +6 -16
- package/dist/shared/migrationHelpers.js +24 -21
- package/dist/shared/operationLogger.d.ts +8 -1
- package/dist/shared/operationLogger.js +11 -24
- package/dist/shared/operationQueue.d.ts +28 -1
- package/dist/shared/operationQueue.js +268 -66
- package/dist/shared/operationsTable.d.ts +26 -0
- package/dist/shared/operationsTable.js +286 -0
- package/dist/shared/operationsTableSchema.d.ts +48 -0
- package/dist/shared/operationsTableSchema.js +35 -0
- package/dist/shared/relationshipExtractor.d.ts +56 -0
- package/dist/shared/relationshipExtractor.js +138 -0
- package/dist/shared/schemaGenerator.d.ts +19 -1
- package/dist/shared/schemaGenerator.js +56 -75
- package/dist/storage/backupCompression.d.ts +20 -0
- package/dist/storage/backupCompression.js +67 -0
- package/dist/storage/methods.d.ts +16 -2
- package/dist/storage/methods.js +98 -14
- package/dist/users/methods.js +9 -8
- package/dist/utils/configDiscovery.d.ts +78 -0
- package/dist/utils/configDiscovery.js +430 -0
- package/dist/utils/directoryUtils.d.ts +22 -0
- package/dist/utils/directoryUtils.js +59 -0
- package/dist/utils/getClientFromConfig.d.ts +17 -8
- package/dist/utils/getClientFromConfig.js +162 -17
- package/dist/utils/helperFunctions.d.ts +16 -2
- package/dist/utils/helperFunctions.js +19 -5
- package/dist/utils/loadConfigs.d.ts +34 -9
- package/dist/utils/loadConfigs.js +236 -316
- package/dist/utils/pathResolvers.d.ts +53 -0
- package/dist/utils/pathResolvers.js +72 -0
- package/dist/utils/projectConfig.d.ts +119 -0
- package/dist/utils/projectConfig.js +171 -0
- package/dist/utils/retryFailedPromises.js +4 -2
- package/dist/utils/sessionAuth.d.ts +48 -0
- package/dist/utils/sessionAuth.js +164 -0
- package/dist/utils/sessionPreservationExample.d.ts +1666 -0
- package/dist/utils/sessionPreservationExample.js +101 -0
- package/dist/utils/setupFiles.js +301 -41
- package/dist/utils/typeGuards.d.ts +35 -0
- package/dist/utils/typeGuards.js +57 -0
- package/dist/utils/versionDetection.js +145 -9
- package/dist/utils/yamlConverter.d.ts +53 -3
- package/dist/utils/yamlConverter.js +232 -13
- package/dist/utils/yamlLoader.d.ts +70 -0
- package/dist/utils/yamlLoader.js +263 -0
- package/dist/utilsController.d.ts +36 -3
- package/dist/utilsController.js +186 -56
- package/package.json +12 -2
- package/src/adapters/AdapterFactory.ts +263 -35
- package/src/adapters/TablesDBAdapter.ts +225 -36
- package/src/backups/operations/bucketBackup.ts +277 -0
- package/src/backups/operations/collectionBackup.ts +310 -0
- package/src/backups/operations/comprehensiveBackup.ts +342 -0
- package/src/backups/schemas/bucketManifest.ts +78 -0
- package/src/backups/schemas/comprehensiveManifest.ts +76 -0
- package/src/backups/tracking/centralizedTracking.ts +352 -0
- package/src/cli/commands/configCommands.ts +194 -0
- package/src/cli/commands/databaseCommands.ts +635 -0
- package/src/cli/commands/functionCommands.ts +379 -0
- package/src/cli/commands/schemaCommands.ts +163 -0
- package/src/cli/commands/transferCommands.ts +457 -0
- package/src/collections/attributes.ts +900 -621
- package/src/collections/attributes.ts.backup +1555 -0
- package/src/collections/indexes.ts +116 -114
- package/src/collections/methods.ts +295 -968
- package/src/collections/transferOperations.ts +516 -0
- package/src/collections/wipeOperations.ts +501 -0
- package/src/config/README.md +274 -0
- package/src/config/configMigration.ts +575 -0
- package/src/config/configValidation.ts +445 -0
- package/src/config/yamlConfig.ts +168 -55
- package/src/databases/methods.ts +3 -2
- package/src/databases/setup.ts +11 -138
- package/src/examples/yamlTerminologyExample.ts +341 -0
- package/src/functions/deployments.ts +14 -12
- package/src/functions/methods.ts +11 -11
- package/src/functions/templates/hono-typescript/README.md +286 -0
- package/src/functions/templates/hono-typescript/package.json +26 -0
- package/src/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
- package/src/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
- package/src/functions/templates/hono-typescript/src/app.ts +180 -0
- package/src/functions/templates/hono-typescript/src/context.ts +103 -0
- package/src/functions/templates/hono-typescript/src/index.ts +54 -0
- package/src/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
- package/src/functions/templates/hono-typescript/tsconfig.json +20 -0
- package/src/functions/templates/typescript-node/package.json +2 -1
- package/src/functions/templates/typescript-node/src/context.ts +103 -0
- package/src/functions/templates/typescript-node/src/index.ts +18 -12
- package/src/functions/templates/uv/pyproject.toml +1 -0
- package/src/functions/templates/uv/src/context.py +125 -0
- package/src/functions/templates/uv/src/index.py +35 -5
- package/src/init.ts +9 -11
- package/src/interactiveCLI.ts +274 -1563
- package/src/main.ts +418 -24
- package/src/migrations/afterImportActions.ts +71 -44
- package/src/migrations/appwriteToX.ts +100 -34
- package/src/migrations/dataLoader.ts +48 -34
- package/src/migrations/importController.ts +44 -39
- package/src/migrations/relationships.ts +28 -18
- package/src/migrations/services/ImportOrchestrator.ts +24 -27
- package/src/migrations/transfer.ts +159 -121
- package/src/migrations/yaml/YamlImportConfigLoader.ts +11 -4
- package/src/migrations/yaml/YamlImportIntegration.ts +47 -20
- package/src/migrations/yaml/generateImportSchemas.ts +751 -12
- package/src/setupController.ts +3 -2
- package/src/shared/backupMetadataSchema.ts +93 -0
- package/src/shared/backupTracking.ts +211 -0
- package/src/shared/confirmationDialogs.ts +19 -19
- package/src/shared/errorUtils.ts +110 -0
- package/src/shared/functionManager.ts +21 -20
- package/src/shared/indexManager.ts +12 -11
- package/src/shared/jsonSchemaGenerator.ts +38 -52
- package/src/shared/logging.ts +75 -0
- package/src/shared/messageFormatter.ts +14 -1
- package/src/shared/migrationHelpers.ts +45 -38
- package/src/shared/operationLogger.ts +11 -36
- package/src/shared/operationQueue.ts +322 -93
- package/src/shared/operationsTable.ts +338 -0
- package/src/shared/operationsTableSchema.ts +60 -0
- package/src/shared/relationshipExtractor.ts +214 -0
- package/src/shared/schemaGenerator.ts +179 -219
- package/src/storage/backupCompression.ts +88 -0
- package/src/storage/methods.ts +131 -34
- package/src/users/methods.ts +11 -9
- package/src/utils/configDiscovery.ts +502 -0
- package/src/utils/directoryUtils.ts +61 -0
- package/src/utils/getClientFromConfig.ts +205 -22
- package/src/utils/helperFunctions.ts +23 -5
- package/src/utils/loadConfigs.ts +313 -345
- package/src/utils/pathResolvers.ts +81 -0
- package/src/utils/projectConfig.ts +299 -0
- package/src/utils/retryFailedPromises.ts +4 -2
- package/src/utils/sessionAuth.ts +230 -0
- package/src/utils/setupFiles.ts +322 -54
- package/src/utils/typeGuards.ts +65 -0
- package/src/utils/versionDetection.ts +218 -64
- package/src/utils/yamlConverter.ts +296 -13
- package/src/utils/yamlLoader.ts +364 -0
- package/src/utilsController.ts +314 -110
- package/tests/README.md +497 -0
- package/tests/adapters/AdapterFactory.test.ts +277 -0
- package/tests/integration/syncOperations.test.ts +463 -0
- package/tests/jest.config.js +25 -0
- package/tests/migration/configMigration.test.ts +546 -0
- package/tests/setup.ts +62 -0
- package/tests/testUtils.ts +340 -0
- package/tests/utils/loadConfigs.test.ts +350 -0
- package/tests/validation/configValidation.test.ts +412 -0
- package/src/utils/schemaStrings.ts +0 -517
@@ -0,0 +1,286 @@
|
|
1
|
+
# {{functionName}} - Hono + Appwrite Function
|
2
|
+
|
3
|
+
This is an Appwrite TypeScript function built with the [Hono](https://hono.dev) web framework. Hono provides a fast, lightweight, and modern way to build web APIs with excellent TypeScript support.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- 🚀 **Ultra-fast routing** with Hono framework
|
8
|
+
- 🔒 **Built-in Appwrite integration** with context injection
|
9
|
+
- 📝 **Full TypeScript support** with type safety
|
10
|
+
- 🛠️ **Comprehensive middleware** for logging, error handling, and Appwrite context
|
11
|
+
- 🔧 **Request/Response adapters** for seamless integration
|
12
|
+
- 📊 **Example API endpoints** with database operations
|
13
|
+
|
14
|
+
## Project Structure
|
15
|
+
|
16
|
+
```
|
17
|
+
src/
|
18
|
+
├── index.ts # Main Appwrite function entry point
|
19
|
+
├── app.ts # Hono application with routes
|
20
|
+
├── context.ts # Appwrite context type definitions
|
21
|
+
├── adapters/
|
22
|
+
│ ├── request.ts # Appwrite → Hono request conversion
|
23
|
+
│ └── response.ts # Hono → Appwrite response conversion
|
24
|
+
└── middleware/
|
25
|
+
└── appwrite.ts # Appwrite context middleware
|
26
|
+
```
|
27
|
+
|
28
|
+
## Getting Started
|
29
|
+
|
30
|
+
### 1. Install Dependencies
|
31
|
+
|
32
|
+
```bash
|
33
|
+
npm install
|
34
|
+
```
|
35
|
+
|
36
|
+
### 2. Available Routes
|
37
|
+
|
38
|
+
- `GET /` - Welcome message with function info
|
39
|
+
- `GET /health` - Health check endpoint
|
40
|
+
- `GET /api/user` - Get current authenticated user (requires user session)
|
41
|
+
- `POST /api/webhook` - Generic webhook handler
|
42
|
+
- `GET /api/databases` - List databases (requires API key)
|
43
|
+
- `POST /api/data/:databaseId/:collectionId` - Create document
|
44
|
+
- `GET /api/data/:databaseId/:collectionId` - List documents
|
45
|
+
|
46
|
+
### 3. Development
|
47
|
+
|
48
|
+
```bash
|
49
|
+
npm run dev
|
50
|
+
```
|
51
|
+
|
52
|
+
### 4. Build
|
53
|
+
|
54
|
+
```bash
|
55
|
+
npm run build
|
56
|
+
```
|
57
|
+
|
58
|
+
## Adding New Routes
|
59
|
+
|
60
|
+
You can easily add new routes to your Hono app in `src/app.ts`:
|
61
|
+
|
62
|
+
```typescript
|
63
|
+
// GET endpoint
|
64
|
+
app.get("/api/hello/:name", (c) => {
|
65
|
+
const name = c.req.param("name");
|
66
|
+
c.log(`Hello endpoint called for ${name}`);
|
67
|
+
|
68
|
+
return c.json({
|
69
|
+
message: `Hello, ${name}!`,
|
70
|
+
timestamp: new Date().toISOString(),
|
71
|
+
});
|
72
|
+
});
|
73
|
+
|
74
|
+
// POST endpoint with JSON body
|
75
|
+
app.post("/api/items", async (c) => {
|
76
|
+
const data = await c.req.json();
|
77
|
+
|
78
|
+
// Access Appwrite context
|
79
|
+
const { databases } = getAppwriteClient(c);
|
80
|
+
|
81
|
+
try {
|
82
|
+
const document = await databases.createDocument(
|
83
|
+
"database-id",
|
84
|
+
"collection-id",
|
85
|
+
"unique()",
|
86
|
+
data
|
87
|
+
);
|
88
|
+
|
89
|
+
c.log("Item created successfully");
|
90
|
+
return c.json({ item: document }, 201);
|
91
|
+
} catch (error) {
|
92
|
+
c.error(`Failed to create item: ${error}`);
|
93
|
+
return c.json({ error: "Failed to create item" }, 500);
|
94
|
+
}
|
95
|
+
});
|
96
|
+
```
|
97
|
+
|
98
|
+
## Middleware
|
99
|
+
|
100
|
+
The template includes several built-in middleware:
|
101
|
+
|
102
|
+
### Appwrite Context Middleware
|
103
|
+
Automatically injects Appwrite context into every Hono request:
|
104
|
+
|
105
|
+
```typescript
|
106
|
+
// Access Appwrite context in any route handler
|
107
|
+
app.get("/api/example", (c) => {
|
108
|
+
const appwriteContext = c.get("appwriteContext");
|
109
|
+
|
110
|
+
// Use Appwrite logging
|
111
|
+
c.log("This will appear in Appwrite function logs");
|
112
|
+
c.error("This will appear as an error in Appwrite logs");
|
113
|
+
|
114
|
+
// Access Appwrite headers
|
115
|
+
const userId = c.appwrite.userId;
|
116
|
+
const isAuthenticated = c.appwrite.isUserAuthenticated();
|
117
|
+
|
118
|
+
return c.json({ userId, isAuthenticated });
|
119
|
+
});
|
120
|
+
```
|
121
|
+
|
122
|
+
### Request Logging
|
123
|
+
Automatically logs incoming requests and responses with timing:
|
124
|
+
|
125
|
+
```
|
126
|
+
→ GET /api/user
|
127
|
+
← GET /api/user 200 (45ms)
|
128
|
+
```
|
129
|
+
|
130
|
+
### Error Handling
|
131
|
+
Catches and properly formats errors:
|
132
|
+
|
133
|
+
```typescript
|
134
|
+
app.get("/api/error-example", (c) => {
|
135
|
+
throw new Error("Something went wrong");
|
136
|
+
// This will be automatically caught and returned as:
|
137
|
+
// { error: "Internal Server Error", message: "Something went wrong", ... }
|
138
|
+
});
|
139
|
+
```
|
140
|
+
|
141
|
+
## Appwrite Integration
|
142
|
+
|
143
|
+
### Authentication
|
144
|
+
|
145
|
+
Check if a user is authenticated:
|
146
|
+
|
147
|
+
```typescript
|
148
|
+
app.get("/api/protected", (c) => {
|
149
|
+
if (!c.appwrite.isUserAuthenticated()) {
|
150
|
+
return c.json({ error: "Authentication required" }, 401);
|
151
|
+
}
|
152
|
+
|
153
|
+
const userId = c.appwrite.userId;
|
154
|
+
return c.json({ message: `Hello user ${userId}` });
|
155
|
+
});
|
156
|
+
```
|
157
|
+
|
158
|
+
### API Key Access
|
159
|
+
|
160
|
+
Check if request has valid API key:
|
161
|
+
|
162
|
+
```typescript
|
163
|
+
app.get("/api/admin", (c) => {
|
164
|
+
if (!c.appwrite.isApiKeyRequest()) {
|
165
|
+
return c.json({ error: "API key required" }, 401);
|
166
|
+
}
|
167
|
+
|
168
|
+
// Admin operations here
|
169
|
+
return c.json({ message: "Admin access granted" });
|
170
|
+
});
|
171
|
+
```
|
172
|
+
|
173
|
+
### Database Operations
|
174
|
+
|
175
|
+
```typescript
|
176
|
+
app.post("/api/posts", async (c) => {
|
177
|
+
const data = await c.req.json();
|
178
|
+
const { databases } = getAppwriteClient(c);
|
179
|
+
|
180
|
+
try {
|
181
|
+
const post = await databases.createDocument(
|
182
|
+
"blog-db",
|
183
|
+
"posts",
|
184
|
+
"unique()",
|
185
|
+
{
|
186
|
+
title: data.title,
|
187
|
+
content: data.content,
|
188
|
+
authorId: c.appwrite.userId,
|
189
|
+
createdAt: new Date().toISOString(),
|
190
|
+
}
|
191
|
+
);
|
192
|
+
|
193
|
+
return c.json({ post }, 201);
|
194
|
+
} catch (error) {
|
195
|
+
c.error(`Failed to create post: ${error}`);
|
196
|
+
return c.json({ error: "Failed to create post" }, 500);
|
197
|
+
}
|
198
|
+
});
|
199
|
+
```
|
200
|
+
|
201
|
+
## Environment Variables
|
202
|
+
|
203
|
+
The following environment variables are automatically available:
|
204
|
+
|
205
|
+
- `APPWRITE_FUNCTION_ENDPOINT` - Appwrite server endpoint
|
206
|
+
- `APPWRITE_FUNCTION_PROJECT_ID` - Current project ID
|
207
|
+
- `APPWRITE_FUNCTION_API_KEY` - Function API key
|
208
|
+
- `APPWRITE_FUNCTION_ID` - Current function ID
|
209
|
+
- `APPWRITE_FUNCTION_NAME` - Function name
|
210
|
+
|
211
|
+
## Response Types
|
212
|
+
|
213
|
+
Hono provides several response helpers:
|
214
|
+
|
215
|
+
```typescript
|
216
|
+
// JSON response
|
217
|
+
return c.json({ data: "value" });
|
218
|
+
|
219
|
+
// Text response
|
220
|
+
return c.text("Hello World");
|
221
|
+
|
222
|
+
// HTML response
|
223
|
+
return c.html("<h1>Hello</h1>");
|
224
|
+
|
225
|
+
// Redirect
|
226
|
+
return c.redirect("/new-url");
|
227
|
+
|
228
|
+
// Custom status
|
229
|
+
return c.json({ error: "Not found" }, 404);
|
230
|
+
|
231
|
+
// Custom headers
|
232
|
+
return c.json({ data: "value" }, 200, {
|
233
|
+
"X-Custom-Header": "value"
|
234
|
+
});
|
235
|
+
```
|
236
|
+
|
237
|
+
## Advanced Usage
|
238
|
+
|
239
|
+
### Custom Middleware
|
240
|
+
|
241
|
+
```typescript
|
242
|
+
const authMiddleware = () => {
|
243
|
+
return async (c, next) => {
|
244
|
+
const token = c.req.header("Authorization");
|
245
|
+
|
246
|
+
if (!token) {
|
247
|
+
return c.json({ error: "Missing token" }, 401);
|
248
|
+
}
|
249
|
+
|
250
|
+
// Validate token logic here
|
251
|
+
c.set("user", { id: "123", email: "user@example.com" });
|
252
|
+
|
253
|
+
await next();
|
254
|
+
};
|
255
|
+
};
|
256
|
+
|
257
|
+
// Apply to specific routes
|
258
|
+
app.use("/api/protected/*", authMiddleware());
|
259
|
+
```
|
260
|
+
|
261
|
+
### Path Parameters and Query Strings
|
262
|
+
|
263
|
+
```typescript
|
264
|
+
// Path parameters
|
265
|
+
app.get("/api/users/:id/posts/:postId", (c) => {
|
266
|
+
const userId = c.req.param("id");
|
267
|
+
const postId = c.req.param("postId");
|
268
|
+
|
269
|
+
return c.json({ userId, postId });
|
270
|
+
});
|
271
|
+
|
272
|
+
// Query parameters
|
273
|
+
app.get("/api/search", (c) => {
|
274
|
+
const query = c.req.query("q");
|
275
|
+
const page = parseInt(c.req.query("page") || "1");
|
276
|
+
const limit = parseInt(c.req.query("limit") || "10");
|
277
|
+
|
278
|
+
return c.json({ query, page, limit });
|
279
|
+
});
|
280
|
+
```
|
281
|
+
|
282
|
+
## Learn More
|
283
|
+
|
284
|
+
- [Hono Documentation](https://hono.dev/docs)
|
285
|
+
- [Appwrite Functions Documentation](https://appwrite.io/docs/functions)
|
286
|
+
- [TypeScript Documentation](https://www.typescriptlang.org/docs/)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
{
|
2
|
+
"name": "{{functionName}}",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"description": "Appwrite TypeScript function with Hono web framework",
|
5
|
+
"main": "src/index.ts",
|
6
|
+
"type": "module",
|
7
|
+
"scripts": {
|
8
|
+
"build": "tsc",
|
9
|
+
"start": "node dist/index.js",
|
10
|
+
"dev": "tsx src/index.ts"
|
11
|
+
},
|
12
|
+
"dependencies": {
|
13
|
+
"node-appwrite": "^13.0.0",
|
14
|
+
"appwrite-utils": "latest",
|
15
|
+
"hono": "^4.0.0",
|
16
|
+
"zod": "^3.22.0"
|
17
|
+
},
|
18
|
+
"devDependencies": {
|
19
|
+
"@types/node": "^20.0.0",
|
20
|
+
"typescript": "^5.0.0",
|
21
|
+
"tsx": "^4.0.0"
|
22
|
+
},
|
23
|
+
"engines": {
|
24
|
+
"node": ">=18.0.0"
|
25
|
+
}
|
26
|
+
}
|
@@ -0,0 +1,74 @@
|
|
1
|
+
import type { AppwriteRequest } from "../context.js";
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Convert Appwrite request to Web API Request for Hono
|
5
|
+
*/
|
6
|
+
export function convertAppwriteToWebRequest(appwriteReq: AppwriteRequest): Request {
|
7
|
+
// Construct the URL
|
8
|
+
const protocol = appwriteReq.scheme || "https";
|
9
|
+
const host = appwriteReq.host || "localhost";
|
10
|
+
const port = appwriteReq.port ? `:${appwriteReq.port}` : "";
|
11
|
+
const path = appwriteReq.path || "/";
|
12
|
+
const queryString = appwriteReq.queryString ? `?${appwriteReq.queryString}` : "";
|
13
|
+
|
14
|
+
const url = `${protocol}://${host}${port}${path}${queryString}`;
|
15
|
+
|
16
|
+
// Prepare headers
|
17
|
+
const headers = new Headers();
|
18
|
+
if (appwriteReq.headers) {
|
19
|
+
Object.entries(appwriteReq.headers).forEach(([key, value]) => {
|
20
|
+
if (value) {
|
21
|
+
headers.set(key, value);
|
22
|
+
}
|
23
|
+
});
|
24
|
+
}
|
25
|
+
|
26
|
+
// Prepare body for POST/PUT/PATCH requests
|
27
|
+
let body: string | undefined;
|
28
|
+
if (appwriteReq.method !== "GET" && appwriteReq.method !== "HEAD") {
|
29
|
+
if (appwriteReq.bodyText) {
|
30
|
+
body = appwriteReq.bodyText;
|
31
|
+
} else if (appwriteReq.bodyJson) {
|
32
|
+
body = typeof appwriteReq.bodyJson === "string"
|
33
|
+
? appwriteReq.bodyJson
|
34
|
+
: JSON.stringify(appwriteReq.bodyJson);
|
35
|
+
if (!headers.has("content-type")) {
|
36
|
+
headers.set("content-type", "application/json");
|
37
|
+
}
|
38
|
+
} else if (appwriteReq.body) {
|
39
|
+
body = typeof appwriteReq.body === "string"
|
40
|
+
? appwriteReq.body
|
41
|
+
: JSON.stringify(appwriteReq.body);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
// Create Web API Request
|
46
|
+
const request = new Request(url, {
|
47
|
+
method: appwriteReq.method,
|
48
|
+
headers,
|
49
|
+
body,
|
50
|
+
});
|
51
|
+
|
52
|
+
return request;
|
53
|
+
}
|
54
|
+
|
55
|
+
/**
|
56
|
+
* Extract path parameters from Hono context for Appwrite compatibility
|
57
|
+
*/
|
58
|
+
export function extractPathParams(honoParams: Record<string, string>): Record<string, string> {
|
59
|
+
return honoParams;
|
60
|
+
}
|
61
|
+
|
62
|
+
/**
|
63
|
+
* Extract query parameters from URL for Appwrite compatibility
|
64
|
+
*/
|
65
|
+
export function extractQueryParams(url: string): Record<string, string> {
|
66
|
+
const urlObj = new URL(url);
|
67
|
+
const params: Record<string, string> = {};
|
68
|
+
|
69
|
+
urlObj.searchParams.forEach((value, key) => {
|
70
|
+
params[key] = value;
|
71
|
+
});
|
72
|
+
|
73
|
+
return params;
|
74
|
+
}
|
@@ -0,0 +1,106 @@
|
|
1
|
+
import type { AppwriteResponse } from "../context.js";
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Convert Hono Response to Appwrite response format
|
5
|
+
*/
|
6
|
+
export async function convertWebResponseToAppwrite(
|
7
|
+
honoResponse: Response,
|
8
|
+
appwriteRes: AppwriteResponse
|
9
|
+
): Promise<void> {
|
10
|
+
const status = honoResponse.status;
|
11
|
+
const headers: Record<string, string> = {};
|
12
|
+
|
13
|
+
// Extract headers from Hono response
|
14
|
+
honoResponse.headers.forEach((value, key) => {
|
15
|
+
headers[key] = value;
|
16
|
+
});
|
17
|
+
|
18
|
+
// Get content type to determine response handling
|
19
|
+
const contentType = honoResponse.headers.get("content-type") || "";
|
20
|
+
|
21
|
+
try {
|
22
|
+
// Handle different response types based on content-type
|
23
|
+
if (contentType.includes("application/json")) {
|
24
|
+
const data = await honoResponse.json();
|
25
|
+
return appwriteRes.json(data, status, headers);
|
26
|
+
} else if (contentType.includes("text/")) {
|
27
|
+
const text = await honoResponse.text();
|
28
|
+
return appwriteRes.text(text, status, headers);
|
29
|
+
} else if (contentType.includes("application/octet-stream") || contentType.includes("image/") || contentType.includes("video/")) {
|
30
|
+
const arrayBuffer = await honoResponse.arrayBuffer();
|
31
|
+
const bytes = new Uint8Array(arrayBuffer);
|
32
|
+
return appwriteRes.binary(bytes);
|
33
|
+
} else {
|
34
|
+
// Default to text for unknown content types
|
35
|
+
const text = await honoResponse.text();
|
36
|
+
return appwriteRes.text(text, status, headers);
|
37
|
+
}
|
38
|
+
} catch (error) {
|
39
|
+
// If response body parsing fails, return empty response
|
40
|
+
console.error("Error converting Hono response:", error);
|
41
|
+
return appwriteRes.empty();
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
/**
|
46
|
+
* Helper function to create common response types for Hono handlers
|
47
|
+
*/
|
48
|
+
export class AppwriteResponseHelpers {
|
49
|
+
/**
|
50
|
+
* Create a JSON response helper
|
51
|
+
*/
|
52
|
+
static json(data: any, status: number = 200, headers?: Record<string, string>) {
|
53
|
+
const responseHeaders = new Headers(headers);
|
54
|
+
responseHeaders.set("content-type", "application/json");
|
55
|
+
|
56
|
+
return new Response(JSON.stringify(data), {
|
57
|
+
status,
|
58
|
+
headers: responseHeaders,
|
59
|
+
});
|
60
|
+
}
|
61
|
+
|
62
|
+
/**
|
63
|
+
* Create a text response helper
|
64
|
+
*/
|
65
|
+
static text(text: string, status: number = 200, headers?: Record<string, string>) {
|
66
|
+
const responseHeaders = new Headers(headers);
|
67
|
+
responseHeaders.set("content-type", "text/plain");
|
68
|
+
|
69
|
+
return new Response(text, {
|
70
|
+
status,
|
71
|
+
headers: responseHeaders,
|
72
|
+
});
|
73
|
+
}
|
74
|
+
|
75
|
+
/**
|
76
|
+
* Create an HTML response helper
|
77
|
+
*/
|
78
|
+
static html(html: string, status: number = 200, headers?: Record<string, string>) {
|
79
|
+
const responseHeaders = new Headers(headers);
|
80
|
+
responseHeaders.set("content-type", "text/html");
|
81
|
+
|
82
|
+
return new Response(html, {
|
83
|
+
status,
|
84
|
+
headers: responseHeaders,
|
85
|
+
});
|
86
|
+
}
|
87
|
+
|
88
|
+
/**
|
89
|
+
* Create a redirect response helper
|
90
|
+
*/
|
91
|
+
static redirect(url: string, status: number = 302) {
|
92
|
+
return new Response(null, {
|
93
|
+
status,
|
94
|
+
headers: {
|
95
|
+
location: url,
|
96
|
+
},
|
97
|
+
});
|
98
|
+
}
|
99
|
+
|
100
|
+
/**
|
101
|
+
* Create an empty response helper
|
102
|
+
*/
|
103
|
+
static empty(status: number = 204) {
|
104
|
+
return new Response(null, { status });
|
105
|
+
}
|
106
|
+
}
|
@@ -0,0 +1,180 @@
|
|
1
|
+
import { Hono } from "hono";
|
2
|
+
import { Client, Databases, Storage, Users } from "node-appwrite";
|
3
|
+
import { appwriteMiddleware, requestLogger, errorHandler, appwriteHeaders } from "./middleware/appwrite.js";
|
4
|
+
|
5
|
+
// Create the Hono app
|
6
|
+
export const app = new Hono();
|
7
|
+
|
8
|
+
// Global middleware - Applied to all routes
|
9
|
+
app.use("*", errorHandler());
|
10
|
+
app.use("*", requestLogger());
|
11
|
+
app.use("*", appwriteHeaders());
|
12
|
+
|
13
|
+
// Helper function to get initialized Appwrite client
|
14
|
+
function getAppwriteClient(c: any) {
|
15
|
+
const appwriteContext = c.get("appwriteContext");
|
16
|
+
|
17
|
+
const client = new Client()
|
18
|
+
.setEndpoint(process.env["APPWRITE_FUNCTION_ENDPOINT"]!)
|
19
|
+
.setProject(process.env["APPWRITE_FUNCTION_PROJECT_ID"]!)
|
20
|
+
.setKey(appwriteContext.req.headers["x-appwrite-key"] || process.env["APPWRITE_FUNCTION_API_KEY"]!);
|
21
|
+
|
22
|
+
return {
|
23
|
+
client,
|
24
|
+
databases: new Databases(client),
|
25
|
+
storage: new Storage(client),
|
26
|
+
users: new Users(client),
|
27
|
+
};
|
28
|
+
}
|
29
|
+
|
30
|
+
// Routes
|
31
|
+
|
32
|
+
// Health check endpoint
|
33
|
+
app.get("/", (c) => {
|
34
|
+
c.log("Health check requested");
|
35
|
+
return c.json({
|
36
|
+
message: "Hello from {{functionName}} with Hono!",
|
37
|
+
functionName: "{{functionName}}",
|
38
|
+
timestamp: new Date().toISOString(),
|
39
|
+
method: c.req.method,
|
40
|
+
path: c.req.path,
|
41
|
+
appwriteTrigger: c.appwrite.trigger,
|
42
|
+
isAuthenticated: c.appwrite.isUserAuthenticated(),
|
43
|
+
});
|
44
|
+
});
|
45
|
+
|
46
|
+
// API health endpoint
|
47
|
+
app.get("/health", (c) => {
|
48
|
+
return c.json({
|
49
|
+
status: "healthy",
|
50
|
+
timestamp: new Date().toISOString(),
|
51
|
+
version: "1.0.0",
|
52
|
+
environment: {
|
53
|
+
nodeVersion: process.version,
|
54
|
+
appwriteEndpoint: process.env["APPWRITE_FUNCTION_ENDPOINT"],
|
55
|
+
appwriteProject: process.env["APPWRITE_FUNCTION_PROJECT_ID"],
|
56
|
+
appwriteRuntime: process.env["APPWRITE_FUNCTION_RUNTIME_NAME"],
|
57
|
+
},
|
58
|
+
});
|
59
|
+
});
|
60
|
+
|
61
|
+
// Example API endpoints
|
62
|
+
|
63
|
+
// GET /api/user - Get current user info
|
64
|
+
app.get("/api/user", async (c) => {
|
65
|
+
if (!c.appwrite.isUserAuthenticated()) {
|
66
|
+
return c.json({ error: "User not authenticated" }, 401);
|
67
|
+
}
|
68
|
+
|
69
|
+
try {
|
70
|
+
const { users } = getAppwriteClient(c);
|
71
|
+
const user = await users.get(c.appwrite.userId!);
|
72
|
+
|
73
|
+
c.log(`Retrieved user info for ${user.email}`);
|
74
|
+
return c.json({ user });
|
75
|
+
} catch (error) {
|
76
|
+
c.error(`Failed to get user: ${error}`);
|
77
|
+
return c.json({ error: "Failed to retrieve user" }, 500);
|
78
|
+
}
|
79
|
+
});
|
80
|
+
|
81
|
+
// POST /api/webhook - Generic webhook handler
|
82
|
+
app.post("/api/webhook", async (c) => {
|
83
|
+
const data = await c.req.json();
|
84
|
+
|
85
|
+
c.log(`Webhook received: ${JSON.stringify(data)}`);
|
86
|
+
|
87
|
+
// Process webhook data here
|
88
|
+
// Example: Save to database, send notifications, etc.
|
89
|
+
|
90
|
+
return c.json({
|
91
|
+
message: "Webhook processed successfully",
|
92
|
+
received: data,
|
93
|
+
timestamp: new Date().toISOString(),
|
94
|
+
event: c.appwrite.event,
|
95
|
+
});
|
96
|
+
});
|
97
|
+
|
98
|
+
// GET /api/databases - List databases (requires API key)
|
99
|
+
app.get("/api/databases", async (c) => {
|
100
|
+
if (!c.appwrite.isApiKeyRequest()) {
|
101
|
+
return c.json({ error: "API key required" }, 401);
|
102
|
+
}
|
103
|
+
|
104
|
+
try {
|
105
|
+
const { databases } = getAppwriteClient(c);
|
106
|
+
const databasesList = await databases.list();
|
107
|
+
|
108
|
+
c.log(`Listed ${databasesList.databases.length} databases`);
|
109
|
+
return c.json({ databases: databasesList.databases });
|
110
|
+
} catch (error) {
|
111
|
+
c.error(`Failed to list databases: ${error}`);
|
112
|
+
return c.json({ error: "Failed to list databases" }, 500);
|
113
|
+
}
|
114
|
+
});
|
115
|
+
|
116
|
+
// POST /api/data/:databaseId/:collectionId - Create document
|
117
|
+
app.post("/api/data/:databaseId/:collectionId", async (c) => {
|
118
|
+
const databaseId = c.req.param("databaseId");
|
119
|
+
const collectionId = c.req.param("collectionId");
|
120
|
+
const data = await c.req.json();
|
121
|
+
|
122
|
+
try {
|
123
|
+
const { databases } = getAppwriteClient(c);
|
124
|
+
const document = await databases.createDocument(
|
125
|
+
databaseId,
|
126
|
+
collectionId,
|
127
|
+
"unique()",
|
128
|
+
data
|
129
|
+
);
|
130
|
+
|
131
|
+
c.log(`Created document in ${databaseId}/${collectionId}`);
|
132
|
+
return c.json({ document }, 201);
|
133
|
+
} catch (error) {
|
134
|
+
c.error(`Failed to create document: ${error}`);
|
135
|
+
return c.json({ error: "Failed to create document" }, 500);
|
136
|
+
}
|
137
|
+
});
|
138
|
+
|
139
|
+
// GET /api/data/:databaseId/:collectionId - List documents
|
140
|
+
app.get("/api/data/:databaseId/:collectionId", async (c) => {
|
141
|
+
const databaseId = c.req.param("databaseId");
|
142
|
+
const collectionId = c.req.param("collectionId");
|
143
|
+
const limit = parseInt(c.req.query("limit") || "25");
|
144
|
+
const offset = parseInt(c.req.query("offset") || "0");
|
145
|
+
|
146
|
+
try {
|
147
|
+
const { databases } = getAppwriteClient(c);
|
148
|
+
const documents = await databases.listDocuments(
|
149
|
+
databaseId,
|
150
|
+
collectionId,
|
151
|
+
undefined,
|
152
|
+
limit,
|
153
|
+
offset
|
154
|
+
);
|
155
|
+
|
156
|
+
c.log(`Listed ${documents.documents.length} documents from ${databaseId}/${collectionId}`);
|
157
|
+
return c.json({ documents: documents.documents, total: documents.total });
|
158
|
+
} catch (error) {
|
159
|
+
c.error(`Failed to list documents: ${error}`);
|
160
|
+
return c.json({ error: "Failed to list documents" }, 500);
|
161
|
+
}
|
162
|
+
});
|
163
|
+
|
164
|
+
// Handle 404s
|
165
|
+
app.notFound((c) => {
|
166
|
+
c.log(`Route not found: ${c.req.method} ${c.req.path}`);
|
167
|
+
return c.json({
|
168
|
+
error: "Not Found",
|
169
|
+
message: `Route ${c.req.method} ${c.req.path} not found`,
|
170
|
+
availableRoutes: [
|
171
|
+
"GET /",
|
172
|
+
"GET /health",
|
173
|
+
"GET /api/user",
|
174
|
+
"POST /api/webhook",
|
175
|
+
"GET /api/databases",
|
176
|
+
"POST /api/data/:databaseId/:collectionId",
|
177
|
+
"GET /api/data/:databaseId/:collectionId",
|
178
|
+
],
|
179
|
+
}, 404);
|
180
|
+
});
|