frame-master-plugin-cloudflare-pages-functions-action
v0.2.1
Published
[](https://www.npmjs.com/package/frame-master-plugin-cloudflare-pages-functions-action) [
}),
// ... other plugins
],
} satisfies FrameMasterConfig;2. Create a Server Action
Create a file at src/actions/user/profile.ts:
import { getContext } from "frame-master-plugin-cloudflare-pages-functions-action/context";
export async function GET() {
const ctx = getContext(arguments);
// Access Cloudflare environment
const user = await ctx.env.KV.get("current-user");
return {
name: "John Doe",
email: "[email protected]",
} as const;
}
export async function POST(
userId: string,
data: { name: string; email: string }
) {
const ctx = getContext(arguments);
// Save to Cloudflare KV
await ctx.env.KV.put(`user:${userId}`, JSON.stringify(data));
return {
success: true,
userId,
} as const;
}3. Call from Client
Import and use the action in your client code:
import {
GET as getProfile,
POST as updateProfile,
} from "src/actions/user/profile";
// Fully type-safe function calls
const profile = await getProfile();
console.log(profile.name); // TypeScript knows this exists!
const result = await updateProfile("123", {
name: "Jane Doe",
email: "[email protected]",
});
console.log(result.success); // Type-safe!📖 Detailed Usage
HTTP Methods
Export functions with HTTP method names to create different endpoints:
export async function GET() {
// Handle GET requests
}
export async function POST(...args) {
// Handle POST requests
}
export async function PUT(...args) {
// Handle PUT requests
}
export async function DELETE(...args) {
// Handle DELETE requests
}
export async function PATCH(...args) {
// Handle PATCH requests
}Accessing Cloudflare Context
Use the getContext() helper to access the Cloudflare environment:
import { getContext } from "frame-master-plugin-cloudflare-pages-functions-action/context";
import type { EventContext } from "@cloudflare/workers-types";
export async function POST(userId: string) {
const ctx = getContext<Env, string, Data>(arguments);
// Access environment bindings
await ctx.env.KV.put("key", "value");
await ctx.env.DB.prepare("SELECT * FROM users").all();
// Access request context
const country = ctx.request.cf?.country;
// Access data passed through Cloudflare
console.log(ctx.data);
return { success: true };
}Data Types Support
JSON Data
// Server
export async function POST(user: { name: string; age: number }) {
return { received: user };
}
// Client
const result = await POST({ name: "Alice", age: 30 });File Upload
// Server
export async function POST(file: File) {
const ctx = getContext(arguments);
const buffer = await file.arrayBuffer();
await ctx.env.R2.put(`uploads/${file.name}`, buffer);
return { filename: file.name, size: file.size };
}
// Client
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
const result = await POST(file);Multiple Files
// Server
export async function POST(files: File[]) {
return { count: files.length };
}
// Client
const files = Array.from(fileInput.files);
const result = await POST(files);FormData
// Server
export async function POST(formData: FormData) {
const name = formData.get("name");
const file = formData.get("avatar") as File;
return { name, hasFile: !!file };
}
// Client
const formData = new FormData();
formData.append("name", "John");
formData.append("avatar", file);
const result = await POST(formData);Returning Files/Blobs
// Server
export async function GET() {
const ctx = getContext(arguments);
const file = await ctx.env.R2.get("document.pdf");
return new File([await file.arrayBuffer()], "document.pdf", {
type: "application/pdf",
});
}
// Client
const file = await GET();
const url = URL.createObjectURL(file);Bypass Plugin feature
since 0.2.0 no-action directive can be used to bypass the Plugin behaviors and use the file as a normal function page
// actions/index.ts
"no-action";
onRequestGet(ctx: EventContext) {
// can be called as a normal API
}
File Organization
Actions follow a Next.js-style file-based routing:
src/actions/
├── user/
│ ├── profile.ts → /user/profile
│ └── settings.ts → /user/settings
├── auth/
│ ├── login.ts → /auth/login
│ └── logout.ts → /auth/logout
└── api/
└── data.ts → /api/data⚙️ Configuration Options
| Option | Type | Required | Default | Description |
| ---------------- | -------- | -------- | ------- | -------------------------------------- |
| actionBasePath | string | ✅ | - | Directory containing your action files |
| outDir | string | ✅ | - | Build output directory |
| serverPort | number | ❌ | 8787 | Wrangler dev server port |
🔧 Development
Local Development
During development, the plugin:
- Watches your action files for changes
- Automatically rebuilds modified actions
- Proxies requests to Wrangler dev server
- Provides hot reload functionality
Building for Production
When you build your Frame Master project, the plugin:
- Scans all action files
- Generates type-safe client wrappers
- Compiles server-side functions
- Outputs to the configured
outDir
📚 Examples
Authentication Flow
// src/actions/auth/login.ts
import { getContext } from "frame-master-plugin-cloudflare-pages-functions-action/context";
export async function POST(email: string, password: string) {
const ctx = getContext(arguments);
// Verify credentials
const user = await ctx.env.DB.prepare("SELECT * FROM users WHERE email = ?")
.bind(email)
.first();
if (!user || user.password !== password) {
throw new Error("Invalid credentials");
}
// Create session
const sessionId = crypto.randomUUID();
await ctx.env.KV.put(`session:${sessionId}`, user.id, {
expirationTtl: 86400, // 24 hours
});
return {
success: true,
sessionId,
user: { id: user.id, email: user.email },
} as const;
}// Client usage
import { POST as login } from "src/actions/auth/login";
async function handleLogin(email: string, password: string) {
try {
const result = await login(email, password);
localStorage.setItem("sessionId", result.sessionId);
console.log("Logged in as:", result.user.email);
} catch (error) {
console.error("Login failed:", error);
}
}File Upload with Progress
// src/actions/upload/image.ts
export async function POST(
file: File,
metadata: { title: string; tags: string[] }
) {
const ctx = getContext(arguments);
// Upload to R2
await ctx.env.R2.put(`images/${file.name}`, file);
// Save metadata to D1
await ctx.env.DB.prepare(
"INSERT INTO images (filename, title, tags) VALUES (?, ?, ?)"
)
.bind(file.name, metadata.title, JSON.stringify(metadata.tags))
.run();
return {
url: `/images/${file.name}`,
size: file.size,
} as const;
}// Client usage
import { POST as uploadImage } from "src/actions/upload/image";
async function handleUpload(file: File) {
const result = await uploadImage(file, {
title: "My Image",
tags: ["vacation", "summer"],
});
console.log("Uploaded to:", result.url);
}🛠️ TypeScript Support
The plugin provides full TypeScript support with proper type exports:
import type { CloudFlareWorkerActionPluginOptions } from "frame-master-plugin-cloudflare-pages-functions-action/types";🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
📝 License
This project follows the same license as Frame Master.
🔗 Related
- Frame Master - The main framework
- Cloudflare Pages - Deployment platform
- Cloudflare Workers - Serverless platform
📖 Documentation
For more information about Frame Master and its plugin system, visit the Frame Master documentation.
🐛 Issues
If you encounter any issues, please open an issue on GitHub.
Made with ❤️ for the Frame Master ecosystem
