appwrite-utils-cli 1.7.8 → 1.7.9
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/dist/cli/commands/databaseCommands.js +7 -8
- package/dist/config/services/ConfigLoaderService.d.ts +7 -0
- package/dist/config/services/ConfigLoaderService.js +47 -1
- package/dist/functions/deployments.js +5 -23
- package/dist/functions/methods.js +4 -2
- package/dist/functions/pathResolution.d.ts +37 -0
- package/dist/functions/pathResolution.js +185 -0
- package/dist/functions/templates/count-docs-in-collection/README.md +54 -0
- package/dist/functions/templates/count-docs-in-collection/package.json +25 -0
- package/dist/functions/templates/count-docs-in-collection/src/main.ts +159 -0
- package/dist/functions/templates/count-docs-in-collection/src/request.ts +9 -0
- package/dist/functions/templates/count-docs-in-collection/tsconfig.json +28 -0
- package/dist/functions/templates/hono-typescript/README.md +286 -0
- package/dist/functions/templates/hono-typescript/package.json +26 -0
- package/dist/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
- package/dist/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
- package/dist/functions/templates/hono-typescript/src/app.ts +180 -0
- package/dist/functions/templates/hono-typescript/src/context.ts +103 -0
- package/dist/functions/templates/hono-typescript/src/index.ts +54 -0
- package/dist/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
- package/dist/functions/templates/hono-typescript/tsconfig.json +20 -0
- package/dist/functions/templates/typescript-node/README.md +32 -0
- package/dist/functions/templates/typescript-node/package.json +25 -0
- package/dist/functions/templates/typescript-node/src/context.ts +103 -0
- package/dist/functions/templates/typescript-node/src/index.ts +29 -0
- package/dist/functions/templates/typescript-node/tsconfig.json +28 -0
- package/dist/functions/templates/uv/README.md +31 -0
- package/dist/functions/templates/uv/pyproject.toml +30 -0
- package/dist/functions/templates/uv/src/__init__.py +0 -0
- package/dist/functions/templates/uv/src/context.py +125 -0
- package/dist/functions/templates/uv/src/index.py +46 -0
- package/dist/main.js +8 -8
- package/dist/shared/selectionDialogs.d.ts +1 -1
- package/dist/shared/selectionDialogs.js +31 -7
- package/dist/utilsController.d.ts +2 -1
- package/dist/utilsController.js +111 -19
- package/package.json +4 -2
- package/scripts/copy-templates.ts +23 -0
- package/src/cli/commands/databaseCommands.ts +7 -8
- package/src/config/services/ConfigLoaderService.ts +62 -1
- package/src/functions/deployments.ts +10 -35
- package/src/functions/methods.ts +4 -2
- package/src/functions/pathResolution.ts +227 -0
- package/src/main.ts +8 -8
- package/src/shared/selectionDialogs.ts +36 -7
- package/src/utilsController.ts +138 -22
- package/dist/utils/schemaStrings.d.ts +0 -14
- package/dist/utils/schemaStrings.js +0 -428
- package/dist/utils/sessionPreservationExample.d.ts +0 -1666
- package/dist/utils/sessionPreservationExample.js +0 -101
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { Client, Databases, Query } from "node-appwrite";
|
|
2
|
+
import { AppwriteRequest, type AppwriteResponse } from "appwrite-utils";
|
|
3
|
+
import { requestSchema } from "./request.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Main function to handle document counting requests.
|
|
7
|
+
* @param {Object} params - The function parameters.
|
|
8
|
+
* @param {Object} params.req - The request object.
|
|
9
|
+
* @param {Object} params.res - The response object.
|
|
10
|
+
* @param {Function} params.log - Logging function.
|
|
11
|
+
* @param {Function} params.error - Error logging function.
|
|
12
|
+
* @returns {Promise<Object>} JSON response with count or error message.
|
|
13
|
+
*/
|
|
14
|
+
export default async ({
|
|
15
|
+
req,
|
|
16
|
+
res,
|
|
17
|
+
log,
|
|
18
|
+
error,
|
|
19
|
+
}: {
|
|
20
|
+
req: AppwriteRequest;
|
|
21
|
+
res: AppwriteResponse;
|
|
22
|
+
log: (message: string) => void;
|
|
23
|
+
error: (message: string) => void;
|
|
24
|
+
}) => {
|
|
25
|
+
// Initialize Appwrite client
|
|
26
|
+
const client = new Client()
|
|
27
|
+
.setEndpoint(process.env["APPWRITE_FUNCTION_ENDPOINT"]!)
|
|
28
|
+
.setProject(process.env["APPWRITE_FUNCTION_PROJECT_ID"]!)
|
|
29
|
+
.setKey(req.headers["x-appwrite-key"] || "");
|
|
30
|
+
|
|
31
|
+
const databases = new Databases(client);
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
if (req.method === "POST") {
|
|
35
|
+
// Parse request body
|
|
36
|
+
const body = requestSchema.safeParse(
|
|
37
|
+
typeof req.body === "string" ? JSON.parse(req.body) : req.body
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (!body.success) {
|
|
41
|
+
return res.json({ success: false, error: body.error }, 400);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { databaseId, collectionId, queries = [] } = body.data;
|
|
45
|
+
|
|
46
|
+
log(`Queries: ${JSON.stringify(queries)}`);
|
|
47
|
+
|
|
48
|
+
// Count documents in the specified collection
|
|
49
|
+
const count = await countAllDocuments(
|
|
50
|
+
log,
|
|
51
|
+
databases,
|
|
52
|
+
databaseId,
|
|
53
|
+
collectionId,
|
|
54
|
+
queries
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Return successful response with document count
|
|
58
|
+
return res.json({
|
|
59
|
+
success: true,
|
|
60
|
+
count: count,
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
// Return error for non-POST requests
|
|
64
|
+
return res.json({ success: false, error: "Method not allowed" }, 405);
|
|
65
|
+
}
|
|
66
|
+
} catch (err) {
|
|
67
|
+
// Log and return any errors
|
|
68
|
+
error(`Error processing request: ${err}`);
|
|
69
|
+
return res.json({ success: false, error: (err as Error).message }, 500);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Counts all documents in a collection, handling large collections efficiently.
|
|
75
|
+
* @param {Function} log - Logging function.
|
|
76
|
+
* @param {Databases} databases - Appwrite Databases instance.
|
|
77
|
+
* @param {string} databaseId - ID of the database.
|
|
78
|
+
* @param {string} collectionId - ID of the collection.
|
|
79
|
+
* @param {string[]} queries - Array of query strings to filter documents.
|
|
80
|
+
* @param {number} batchSize - Size of batches for processing (default: 1000).
|
|
81
|
+
* @returns {Promise<number>} Total count of documents.
|
|
82
|
+
*/
|
|
83
|
+
async function countAllDocuments(
|
|
84
|
+
log: any,
|
|
85
|
+
databases: Databases,
|
|
86
|
+
databaseId: string,
|
|
87
|
+
collectionId: string,
|
|
88
|
+
queries: string[] = [],
|
|
89
|
+
batchSize: number = 1000
|
|
90
|
+
): Promise<number> {
|
|
91
|
+
// Filter out limit and offset queries
|
|
92
|
+
const initialQueries = queries.filter(
|
|
93
|
+
(q) => !(q.includes("limit") || q.includes("offset"))
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Initial query to check if total is less than 5000
|
|
97
|
+
const initialResponse = await databases.listDocuments(
|
|
98
|
+
databaseId,
|
|
99
|
+
collectionId,
|
|
100
|
+
[...initialQueries, Query.limit(1)]
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (initialResponse.total < 5000) {
|
|
104
|
+
log(`Total documents (from initial response): ${initialResponse.total}`);
|
|
105
|
+
return initialResponse.total;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// If total is 5000 or more, we need to count manually
|
|
109
|
+
let bound = 5000;
|
|
110
|
+
|
|
111
|
+
// Exponential search to find an upper bound
|
|
112
|
+
while (true) {
|
|
113
|
+
log(`Querying for offset ${bound}`);
|
|
114
|
+
try {
|
|
115
|
+
const response = await databases.listDocuments(databaseId, collectionId, [
|
|
116
|
+
...initialQueries,
|
|
117
|
+
Query.limit(1),
|
|
118
|
+
Query.offset(bound),
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
if (response.documents.length === 0) {
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
bound *= 2;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Binary search to find the exact count
|
|
131
|
+
let low = Math.floor(bound / 2);
|
|
132
|
+
let high = bound;
|
|
133
|
+
let lastValidCount = low;
|
|
134
|
+
|
|
135
|
+
while (low <= high) {
|
|
136
|
+
const mid = Math.floor((low + high) / 2);
|
|
137
|
+
log(`Binary search: Querying for offset ${mid}`);
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const response = await databases.listDocuments(databaseId, collectionId, [
|
|
141
|
+
...initialQueries,
|
|
142
|
+
Query.limit(1),
|
|
143
|
+
Query.offset(mid),
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
if (response.documents.length > 0) {
|
|
147
|
+
lastValidCount = mid + 1; // +1 because offset is 0-based
|
|
148
|
+
low = mid + 1;
|
|
149
|
+
} else {
|
|
150
|
+
high = mid - 1;
|
|
151
|
+
}
|
|
152
|
+
} catch (error) {
|
|
153
|
+
high = mid - 1;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
log(`Total documents: ${lastValidCount}`);
|
|
158
|
+
return lastValidCount;
|
|
159
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"allowSyntheticDefaultImports": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"allowJs": true,
|
|
9
|
+
"checkJs": false,
|
|
10
|
+
"outDir": "./dist",
|
|
11
|
+
"rootDir": "./src",
|
|
12
|
+
"strict": true,
|
|
13
|
+
"noImplicitAny": true,
|
|
14
|
+
"strictNullChecks": true,
|
|
15
|
+
"strictFunctionTypes": true,
|
|
16
|
+
"noImplicitThis": true,
|
|
17
|
+
"noImplicitReturns": true,
|
|
18
|
+
"noFallthroughCasesInSwitch": true,
|
|
19
|
+
"moduleDetection": "force",
|
|
20
|
+
"resolveJsonModule": true,
|
|
21
|
+
"isolatedModules": true,
|
|
22
|
+
"verbatimModuleSyntax": false,
|
|
23
|
+
"skipLibCheck": true,
|
|
24
|
+
"forceConsistentCasingInFileNames": true
|
|
25
|
+
},
|
|
26
|
+
"include": ["src/**/*"],
|
|
27
|
+
"exclude": ["node_modules", "dist"]
|
|
28
|
+
}
|
|
@@ -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
|
+
}
|