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,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
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Appwrite Request Headers Schema
|
|
5
|
+
*/
|
|
6
|
+
export const AppwriteHeadersSchema = z.object({
|
|
7
|
+
"x-appwrite-trigger": z.enum(["http", "schedule", "event"]).optional(),
|
|
8
|
+
"x-appwrite-event": z.string().optional(),
|
|
9
|
+
"x-appwrite-key": z.string().optional(),
|
|
10
|
+
"x-appwrite-user-id": z.string().optional(),
|
|
11
|
+
"x-appwrite-user-jwt": z.string().optional(),
|
|
12
|
+
"x-appwrite-country-code": z.string().optional(),
|
|
13
|
+
"x-appwrite-continent-code": z.string().optional(),
|
|
14
|
+
"x-appwrite-continent-eu": z.string().optional(),
|
|
15
|
+
}).catchall(z.string()); // Allow additional headers
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Appwrite Environment Variables Schema
|
|
19
|
+
*/
|
|
20
|
+
export const AppwriteEnvSchema = z.object({
|
|
21
|
+
APPWRITE_FUNCTION_API_ENDPOINT: z.string(),
|
|
22
|
+
APPWRITE_VERSION: z.string(),
|
|
23
|
+
APPWRITE_REGION: z.string(),
|
|
24
|
+
APPWRITE_FUNCTION_API_KEY: z.string().optional(),
|
|
25
|
+
APPWRITE_FUNCTION_ID: z.string(),
|
|
26
|
+
APPWRITE_FUNCTION_NAME: z.string(),
|
|
27
|
+
APPWRITE_FUNCTION_DEPLOYMENT: z.string(),
|
|
28
|
+
APPWRITE_FUNCTION_PROJECT_ID: z.string(),
|
|
29
|
+
APPWRITE_FUNCTION_RUNTIME_NAME: z.string(),
|
|
30
|
+
APPWRITE_FUNCTION_RUNTIME_VERSION: z.string(),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Appwrite Request Schema (full version from docs)
|
|
35
|
+
*/
|
|
36
|
+
export const AppwriteRequestSchema = z.object({
|
|
37
|
+
bodyText: z.string().optional(),
|
|
38
|
+
bodyJson: z.union([z.any(), z.string()]).transform((t) => {
|
|
39
|
+
if (typeof t === "string") {
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(t);
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return t;
|
|
47
|
+
}).optional(),
|
|
48
|
+
body: z.any().optional(),
|
|
49
|
+
headers: AppwriteHeadersSchema,
|
|
50
|
+
scheme: z.enum(["http", "https"]).optional(),
|
|
51
|
+
method: z.enum(["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"]),
|
|
52
|
+
url: z.string().optional(),
|
|
53
|
+
host: z.string().optional(),
|
|
54
|
+
port: z.number().or(z.string()).optional(),
|
|
55
|
+
path: z.string().optional(),
|
|
56
|
+
queryString: z.string().optional(),
|
|
57
|
+
query: z.record(z.string(), z.any()).optional(),
|
|
58
|
+
variables: z.record(z.string(), z.any()).optional(),
|
|
59
|
+
text: z.any().optional(),
|
|
60
|
+
payload: z.string().optional(),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Appwrite Response Schema
|
|
65
|
+
*/
|
|
66
|
+
export const AppwriteResponseSchema = z.object({
|
|
67
|
+
json: z.any(),
|
|
68
|
+
text: z.any(),
|
|
69
|
+
empty: z.any().optional(),
|
|
70
|
+
binary: z.any().optional(),
|
|
71
|
+
redirect: z.any().optional(),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Log Function Schema - Simple function type
|
|
76
|
+
*/
|
|
77
|
+
export const AppwriteLogSchema = z.any();
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Error Function Schema - Simple function type
|
|
81
|
+
*/
|
|
82
|
+
export const AppwriteErrorSchema = z.any();
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Complete Appwrite Context Schema
|
|
86
|
+
*/
|
|
87
|
+
export const AppwriteContextSchema = z.object({
|
|
88
|
+
req: AppwriteRequestSchema,
|
|
89
|
+
res: AppwriteResponseSchema,
|
|
90
|
+
log: AppwriteLogSchema,
|
|
91
|
+
error: AppwriteErrorSchema,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Type inference helpers
|
|
96
|
+
*/
|
|
97
|
+
export type AppwriteHeaders = z.infer<typeof AppwriteHeadersSchema>;
|
|
98
|
+
export type AppwriteEnv = z.infer<typeof AppwriteEnvSchema>;
|
|
99
|
+
export type AppwriteRequest = z.infer<typeof AppwriteRequestSchema>;
|
|
100
|
+
export type AppwriteResponse = z.infer<typeof AppwriteResponseSchema>;
|
|
101
|
+
export type AppwriteLog = z.infer<typeof AppwriteLogSchema>;
|
|
102
|
+
export type AppwriteError = z.infer<typeof AppwriteErrorSchema>;
|
|
103
|
+
export type AppwriteContext = z.infer<typeof AppwriteContextSchema>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { app } from "./app.js";
|
|
2
|
+
import { AppwriteContext, AppwriteContextSchema } from "./context.js";
|
|
3
|
+
import { convertAppwriteToWebRequest } from "./adapters/request.js";
|
|
4
|
+
import { convertWebResponseToAppwrite } from "./adapters/response.js";
|
|
5
|
+
import { appwriteMiddleware } from "./middleware/appwrite.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Main Appwrite function entry point
|
|
9
|
+
* This function receives the Appwrite context and routes it through Hono
|
|
10
|
+
*/
|
|
11
|
+
export default async function (context: AppwriteContext) {
|
|
12
|
+
const { req, res, log, error } = context;
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
// Optional: Validate the context using Zod schema
|
|
16
|
+
AppwriteContextSchema.parse(context);
|
|
17
|
+
log("Context validation successful");
|
|
18
|
+
|
|
19
|
+
// Add Appwrite context middleware to the Hono app
|
|
20
|
+
app.use("*", appwriteMiddleware(context));
|
|
21
|
+
|
|
22
|
+
// Convert Appwrite request to Web API Request for Hono
|
|
23
|
+
const webRequest = convertAppwriteToWebRequest(req);
|
|
24
|
+
|
|
25
|
+
log(`Processing ${req.method} request to ${req.path || "/"}`);
|
|
26
|
+
|
|
27
|
+
// Create execution environment for Hono
|
|
28
|
+
const env = {
|
|
29
|
+
// Add any environment variables or context needed by Hono
|
|
30
|
+
APPWRITE_FUNCTION_ENDPOINT: process.env["APPWRITE_FUNCTION_ENDPOINT"],
|
|
31
|
+
APPWRITE_FUNCTION_PROJECT_ID: process.env["APPWRITE_FUNCTION_PROJECT_ID"],
|
|
32
|
+
APPWRITE_FUNCTION_API_KEY: process.env["APPWRITE_FUNCTION_API_KEY"],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Process request through Hono app
|
|
36
|
+
const honoResponse = await app.fetch(webRequest, env);
|
|
37
|
+
|
|
38
|
+
// Convert Hono response back to Appwrite response format
|
|
39
|
+
await convertWebResponseToAppwrite(honoResponse, res);
|
|
40
|
+
|
|
41
|
+
log(`Request completed with status ${honoResponse.status}`);
|
|
42
|
+
|
|
43
|
+
} catch (validationError) {
|
|
44
|
+
error(`Function execution failed: ${validationError}`);
|
|
45
|
+
|
|
46
|
+
// Return error response
|
|
47
|
+
return res.json({
|
|
48
|
+
error: "Function execution failed",
|
|
49
|
+
message: validationError instanceof Error ? validationError.message : String(validationError),
|
|
50
|
+
functionName: "{{functionName}}",
|
|
51
|
+
timestamp: new Date().toISOString(),
|
|
52
|
+
}, 500);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from "hono";
|
|
2
|
+
import type { AppwriteContext } from "../context.js";
|
|
3
|
+
|
|
4
|
+
// Extend Hono's context to include Appwrite context
|
|
5
|
+
declare module "hono" {
|
|
6
|
+
interface ContextVariableMap {
|
|
7
|
+
appwriteContext: AppwriteContext;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Middleware to inject Appwrite context into Hono
|
|
13
|
+
*/
|
|
14
|
+
export const appwriteMiddleware = (appwriteContext: AppwriteContext): MiddlewareHandler => {
|
|
15
|
+
return async (c, next) => {
|
|
16
|
+
// Inject Appwrite context into Hono context
|
|
17
|
+
c.set("appwriteContext", appwriteContext);
|
|
18
|
+
|
|
19
|
+
// Add logging helper methods to Hono context
|
|
20
|
+
c.log = (message: string) => {
|
|
21
|
+
appwriteContext.log(`[${c.req.method} ${c.req.path}] ${message}`);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
c.error = (message: string) => {
|
|
25
|
+
appwriteContext.error(`[${c.req.method} ${c.req.path}] ${message}`);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
await next();
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Middleware for request logging
|
|
34
|
+
*/
|
|
35
|
+
export const requestLogger = (): MiddlewareHandler => {
|
|
36
|
+
return async (c, next) => {
|
|
37
|
+
const start = Date.now();
|
|
38
|
+
const appwriteContext = c.get("appwriteContext");
|
|
39
|
+
|
|
40
|
+
appwriteContext?.log(`→ ${c.req.method} ${c.req.path}`);
|
|
41
|
+
|
|
42
|
+
await next();
|
|
43
|
+
|
|
44
|
+
const duration = Date.now() - start;
|
|
45
|
+
appwriteContext?.log(`← ${c.req.method} ${c.req.path} ${c.res.status} (${duration}ms)`);
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Middleware for error handling
|
|
51
|
+
*/
|
|
52
|
+
export const errorHandler = (): MiddlewareHandler => {
|
|
53
|
+
return async (c, next) => {
|
|
54
|
+
try {
|
|
55
|
+
await next();
|
|
56
|
+
} catch (err) {
|
|
57
|
+
const appwriteContext = c.get("appwriteContext");
|
|
58
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
59
|
+
|
|
60
|
+
appwriteContext?.error(`Error in ${c.req.method} ${c.req.path}: ${error.message}`);
|
|
61
|
+
|
|
62
|
+
return c.json(
|
|
63
|
+
{
|
|
64
|
+
error: "Internal Server Error",
|
|
65
|
+
message: error.message,
|
|
66
|
+
path: c.req.path,
|
|
67
|
+
method: c.req.method,
|
|
68
|
+
},
|
|
69
|
+
500
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Middleware to extract and validate Appwrite headers
|
|
77
|
+
*/
|
|
78
|
+
export const appwriteHeaders = (): MiddlewareHandler => {
|
|
79
|
+
return async (c, next) => {
|
|
80
|
+
const appwriteContext = c.get("appwriteContext");
|
|
81
|
+
const headers = appwriteContext?.req.headers;
|
|
82
|
+
|
|
83
|
+
// Add helpful getters for common Appwrite headers
|
|
84
|
+
c.appwrite = {
|
|
85
|
+
trigger: headers?.["x-appwrite-trigger"] as "http" | "schedule" | "event" | undefined,
|
|
86
|
+
event: headers?.["x-appwrite-event"],
|
|
87
|
+
key: headers?.["x-appwrite-key"],
|
|
88
|
+
userId: headers?.["x-appwrite-user-id"],
|
|
89
|
+
userJwt: headers?.["x-appwrite-user-jwt"],
|
|
90
|
+
countryCode: headers?.["x-appwrite-country-code"],
|
|
91
|
+
continentCode: headers?.["x-appwrite-continent-code"],
|
|
92
|
+
continentEu: headers?.["x-appwrite-continent-eu"],
|
|
93
|
+
isUserAuthenticated: () => !!headers?.["x-appwrite-user-id"],
|
|
94
|
+
isApiKeyRequest: () => !!headers?.["x-appwrite-key"],
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
await next();
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Extend Hono context to include Appwrite helpers
|
|
102
|
+
declare module "hono" {
|
|
103
|
+
interface Context {
|
|
104
|
+
log: (message: string) => void;
|
|
105
|
+
error: (message: string) => void;
|
|
106
|
+
appwrite: {
|
|
107
|
+
trigger?: "http" | "schedule" | "event";
|
|
108
|
+
event?: string;
|
|
109
|
+
key?: string;
|
|
110
|
+
userId?: string;
|
|
111
|
+
userJwt?: string;
|
|
112
|
+
countryCode?: string;
|
|
113
|
+
continentCode?: string;
|
|
114
|
+
continentEu?: string;
|
|
115
|
+
isUserAuthenticated: () => boolean;
|
|
116
|
+
isApiKeyRequest: () => boolean;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"allowSyntheticDefaultImports": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"allowJs": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"outDir": "./dist",
|
|
14
|
+
"rootDir": "./src",
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"types": ["node"]
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*"],
|
|
19
|
+
"exclude": ["node_modules", "dist"]
|
|
20
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# TypeScript Node.js Function Template
|
|
2
|
+
|
|
3
|
+
This is a TypeScript template for Appwrite Functions using Node.js runtime.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
- `src/index.ts`: Main function entry point
|
|
7
|
+
- `src/appwriteRequest.ts`: Type definitions for Appwrite request object
|
|
8
|
+
- `src/appwriteResponse.ts`: Type definitions for Appwrite response object
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
Your function will receive:
|
|
12
|
+
- `req`: AppwriteRequest object containing request data, headers, and environment variables
|
|
13
|
+
- `res`: AppwriteResponse object for sending responses
|
|
14
|
+
- `log`: Function for logging (shows in your function logs)
|
|
15
|
+
- `error`: Function for error logging
|
|
16
|
+
|
|
17
|
+
## Example Request
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"databaseId": "your-database-id",
|
|
21
|
+
"collectionId": "your-collection-id"
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Development
|
|
26
|
+
1. Install dependencies: `npm|yarn|bun install`
|
|
27
|
+
2. Build: `npm|yarn|bun run build`
|
|
28
|
+
3. Deploy: Function will be built automatically during deployment
|
|
29
|
+
|
|
30
|
+
## Deployment
|
|
31
|
+
Make sure it's inside `appwriteConfig.ts` functions array, and if you want to build it FIRST, before Appwrite (using your system), you can
|
|
32
|
+
add the `predeployCommands` to the function in `appwriteConfig.ts` -- for an example, see `count-docs-in-collection` function.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{functionName}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Appwrite TypeScript Node.js function",
|
|
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
|
+
"zod": "^3.22.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^20.0.0",
|
|
19
|
+
"typescript": "^5.0.0",
|
|
20
|
+
"tsx": "^4.0.0"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18.0.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Appwrite Request Headers Schema
|
|
5
|
+
*/
|
|
6
|
+
export const AppwriteHeadersSchema = z.object({
|
|
7
|
+
"x-appwrite-trigger": z.enum(["http", "schedule", "event"]).optional(),
|
|
8
|
+
"x-appwrite-event": z.string().optional(),
|
|
9
|
+
"x-appwrite-key": z.string().optional(),
|
|
10
|
+
"x-appwrite-user-id": z.string().optional(),
|
|
11
|
+
"x-appwrite-user-jwt": z.string().optional(),
|
|
12
|
+
"x-appwrite-country-code": z.string().optional(),
|
|
13
|
+
"x-appwrite-continent-code": z.string().optional(),
|
|
14
|
+
"x-appwrite-continent-eu": z.string().optional(),
|
|
15
|
+
}).catchall(z.string()); // Allow additional headers
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Appwrite Environment Variables Schema
|
|
19
|
+
*/
|
|
20
|
+
export const AppwriteEnvSchema = z.object({
|
|
21
|
+
APPWRITE_FUNCTION_API_ENDPOINT: z.string(),
|
|
22
|
+
APPWRITE_VERSION: z.string(),
|
|
23
|
+
APPWRITE_REGION: z.string(),
|
|
24
|
+
APPWRITE_FUNCTION_API_KEY: z.string().optional(),
|
|
25
|
+
APPWRITE_FUNCTION_ID: z.string(),
|
|
26
|
+
APPWRITE_FUNCTION_NAME: z.string(),
|
|
27
|
+
APPWRITE_FUNCTION_DEPLOYMENT: z.string(),
|
|
28
|
+
APPWRITE_FUNCTION_PROJECT_ID: z.string(),
|
|
29
|
+
APPWRITE_FUNCTION_RUNTIME_NAME: z.string(),
|
|
30
|
+
APPWRITE_FUNCTION_RUNTIME_VERSION: z.string(),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Appwrite Request Schema (full version from docs)
|
|
35
|
+
*/
|
|
36
|
+
export const AppwriteRequestSchema = z.object({
|
|
37
|
+
bodyText: z.string().optional(),
|
|
38
|
+
bodyJson: z.union([z.any(), z.string()]).transform((t) => {
|
|
39
|
+
if (typeof t === "string") {
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(t);
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return t;
|
|
47
|
+
}).optional(),
|
|
48
|
+
body: z.any().optional(),
|
|
49
|
+
headers: AppwriteHeadersSchema,
|
|
50
|
+
scheme: z.enum(["http", "https"]).optional(),
|
|
51
|
+
method: z.enum(["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"]),
|
|
52
|
+
url: z.string().optional(),
|
|
53
|
+
host: z.string().optional(),
|
|
54
|
+
port: z.number().or(z.string()).optional(),
|
|
55
|
+
path: z.string().optional(),
|
|
56
|
+
queryString: z.string().optional(),
|
|
57
|
+
query: z.record(z.string(), z.any()).optional(),
|
|
58
|
+
variables: z.record(z.string(), z.any()).optional(),
|
|
59
|
+
text: z.any().optional(),
|
|
60
|
+
payload: z.string().optional(),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Appwrite Response Schema
|
|
65
|
+
*/
|
|
66
|
+
export const AppwriteResponseSchema = z.object({
|
|
67
|
+
json: z.any(),
|
|
68
|
+
text: z.any(),
|
|
69
|
+
empty: z.any().optional(),
|
|
70
|
+
binary: z.any().optional(),
|
|
71
|
+
redirect: z.any().optional(),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Log Function Schema - Simple function type
|
|
76
|
+
*/
|
|
77
|
+
export const AppwriteLogSchema = z.any();
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Error Function Schema - Simple function type
|
|
81
|
+
*/
|
|
82
|
+
export const AppwriteErrorSchema = z.any();
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Complete Appwrite Context Schema
|
|
86
|
+
*/
|
|
87
|
+
export const AppwriteContextSchema = z.object({
|
|
88
|
+
req: AppwriteRequestSchema,
|
|
89
|
+
res: AppwriteResponseSchema,
|
|
90
|
+
log: AppwriteLogSchema,
|
|
91
|
+
error: AppwriteErrorSchema,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Type inference helpers
|
|
96
|
+
*/
|
|
97
|
+
export type AppwriteHeaders = z.infer<typeof AppwriteHeadersSchema>;
|
|
98
|
+
export type AppwriteEnv = z.infer<typeof AppwriteEnvSchema>;
|
|
99
|
+
export type AppwriteRequest = z.infer<typeof AppwriteRequestSchema>;
|
|
100
|
+
export type AppwriteResponse = z.infer<typeof AppwriteResponseSchema>;
|
|
101
|
+
export type AppwriteLog = z.infer<typeof AppwriteLogSchema>;
|
|
102
|
+
export type AppwriteError = z.infer<typeof AppwriteErrorSchema>;
|
|
103
|
+
export type AppwriteContext = z.infer<typeof AppwriteContextSchema>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Client } from "node-appwrite";
|
|
2
|
+
import { AppwriteContext, AppwriteContextSchema } from "./context.js";
|
|
3
|
+
|
|
4
|
+
export default async function (context: AppwriteContext) {
|
|
5
|
+
const { req, res, log, error } = context;
|
|
6
|
+
|
|
7
|
+
// Optional: Validate the context using Zod schema
|
|
8
|
+
try {
|
|
9
|
+
AppwriteContextSchema.parse(context);
|
|
10
|
+
log("Context validation successful");
|
|
11
|
+
} catch (validationError) {
|
|
12
|
+
error(`Context validation failed: ${validationError}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const client = new Client()
|
|
16
|
+
.setEndpoint(process.env["APPWRITE_FUNCTION_ENDPOINT"]!)
|
|
17
|
+
.setProject(process.env["APPWRITE_FUNCTION_PROJECT_ID"]!)
|
|
18
|
+
.setKey(req.headers["x-appwrite-key"] || "");
|
|
19
|
+
|
|
20
|
+
log(`Processing ${req.method} request to ${req.path}`);
|
|
21
|
+
|
|
22
|
+
return res.json({
|
|
23
|
+
message: "Hello from TypeScript function!",
|
|
24
|
+
functionName: "{{functionName}}",
|
|
25
|
+
method: req.method,
|
|
26
|
+
path: req.path,
|
|
27
|
+
headers: req.headers,
|
|
28
|
+
});
|
|
29
|
+
}
|