Initial commit (by create-cloudflare CLI)

Details:
  C3 = create-cloudflare@2.49.0
  project name = bluesky-alt-text-worker
  package manager = npm@10.2.3
  wrangler = wrangler@4.19.1
  git = 2.48.1
This commit is contained in:
Index 2025-06-06 22:17:32 -05:00
commit bbc7b5bf9b
14 changed files with 7899 additions and 0 deletions

172
.gitignore vendored Normal file
View file

@ -0,0 +1,172 @@
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
\*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
\*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
\*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
\*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.\*
# wrangler project
.dev.vars
.wrangler/

5
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,5 @@
{
"files.associations": {
"wrangler.json": "jsonc"
}
}

25
README.md Normal file
View file

@ -0,0 +1,25 @@
# Cloudflare Workers OpenAPI 3.1
This is a Cloudflare Worker with OpenAPI 3.1 using [chanfana](https://github.com/cloudflare/chanfana) and [Hono](https://github.com/honojs/hono).
This is an example project made to be used as a quick start into building OpenAPI compliant Workers that generates the
`openapi.json` schema automatically from code and validates the incoming request to the defined parameters or request body.
## Get started
1. Sign up for [Cloudflare Workers](https://workers.dev). The free tier is more than enough for most use cases.
2. Clone this project and install dependencies with `npm install`
3. Run `wrangler login` to login to your Cloudflare account in wrangler
4. Run `wrangler deploy` to publish the API to Cloudflare Workers
## Project structure
1. Your main router is defined in `src/index.ts`.
2. Each endpoint has its own file in `src/endpoints/`.
3. For more information read the [chanfana documentation](https://chanfana.pages.dev/) and [Hono documentation](https://hono.dev/docs).
## Development
1. Run `wrangler dev` to start a local instance of the API.
2. Open `http://localhost:8787/` in your browser to see the Swagger interface where you can try the endpoints.
3. Changes made in the `src/` folder will automatically trigger the server to reload, you only need to refresh the Swagger interface.

1541
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

21
package.json Normal file
View file

@ -0,0 +1,21 @@
{
"name": "cloudflare-workers-openapi",
"version": "0.0.1",
"private": true,
"scripts": {
"deploy": "wrangler deploy",
"dev": "wrangler dev",
"start": "wrangler dev",
"cf-typegen": "wrangler types"
},
"dependencies": {
"chanfana": "^2.6.3",
"hono": "^4.6.20",
"zod": "^3.24.1"
},
"devDependencies": {
"@types/node": "22.13.0",
"@types/service-worker-mock": "^2.0.4",
"wrangler": "^4.19.1"
}
}

View file

@ -0,0 +1,58 @@
import { Bool, OpenAPIRoute } from "chanfana";
import { z } from "zod";
import { type AppContext, Task } from "../types";
export class TaskCreate extends OpenAPIRoute {
schema = {
tags: ["Tasks"],
summary: "Create a new Task",
request: {
body: {
content: {
"application/json": {
schema: Task,
},
},
},
},
responses: {
"200": {
description: "Returns the created task",
content: {
"application/json": {
schema: z.object({
series: z.object({
success: Bool(),
result: z.object({
task: Task,
}),
}),
}),
},
},
},
},
};
async handle(c: AppContext) {
// Get validated data
const data = await this.getValidatedData<typeof this.schema>();
// Retrieve the validated request body
const taskToCreate = data.body;
// Implement your own object insertion here
// return the new task
return {
success: true,
task: {
name: taskToCreate.name,
slug: taskToCreate.slug,
description: taskToCreate.description,
completed: taskToCreate.completed,
due_date: taskToCreate.due_date,
},
};
}
}

View file

@ -0,0 +1,56 @@
import { Bool, OpenAPIRoute, Str } from "chanfana";
import { z } from "zod";
import { type AppContext, Task } from "../types";
export class TaskDelete extends OpenAPIRoute {
schema = {
tags: ["Tasks"],
summary: "Delete a Task",
request: {
params: z.object({
taskSlug: Str({ description: "Task slug" }),
}),
},
responses: {
"200": {
description: "Returns if the task was deleted successfully",
content: {
"application/json": {
schema: z.object({
series: z.object({
success: Bool(),
result: z.object({
task: Task,
}),
}),
}),
},
},
},
},
};
async handle(c: AppContext) {
// Get validated data
const data = await this.getValidatedData<typeof this.schema>();
// Retrieve the validated slug
const { taskSlug } = data.params;
// Implement your own object deletion here
// Return the deleted task for confirmation
return {
result: {
task: {
name: "Build something awesome with Cloudflare Workers",
slug: taskSlug,
description: "Lorem Ipsum",
completed: true,
due_date: "2022-12-24",
},
},
success: true,
};
}
}

View file

@ -0,0 +1,81 @@
import { Bool, OpenAPIRoute, Str } from "chanfana";
import { z } from "zod";
import { type AppContext, Task } from "../types";
export class TaskFetch extends OpenAPIRoute {
schema = {
tags: ["Tasks"],
summary: "Get a single Task by slug",
request: {
params: z.object({
taskSlug: Str({ description: "Task slug" }),
}),
},
responses: {
"200": {
description: "Returns a single task if found",
content: {
"application/json": {
schema: z.object({
series: z.object({
success: Bool(),
result: z.object({
task: Task,
}),
}),
}),
},
},
},
"404": {
description: "Task not found",
content: {
"application/json": {
schema: z.object({
series: z.object({
success: Bool(),
error: Str(),
}),
}),
},
},
},
},
};
async handle(c: AppContext) {
// Get validated data
const data = await this.getValidatedData<typeof this.schema>();
// Retrieve the validated slug
const { taskSlug } = data.params;
// Implement your own object fetch here
const exists = true;
// @ts-ignore: check if the object exists
if (exists === false) {
return Response.json(
{
success: false,
error: "Object not found",
},
{
status: 404,
},
);
}
return {
success: true,
task: {
name: "my task",
slug: taskSlug,
description: "this needs to be done",
completed: false,
due_date: new Date().toISOString().slice(0, 10),
},
};
}
}

69
src/endpoints/taskList.ts Normal file
View file

@ -0,0 +1,69 @@
import { Bool, Num, OpenAPIRoute } from "chanfana";
import { z } from "zod";
import { type AppContext, Task } from "../types";
export class TaskList extends OpenAPIRoute {
schema = {
tags: ["Tasks"],
summary: "List Tasks",
request: {
query: z.object({
page: Num({
description: "Page number",
default: 0,
}),
isCompleted: Bool({
description: "Filter by completed flag",
required: false,
}),
}),
},
responses: {
"200": {
description: "Returns a list of tasks",
content: {
"application/json": {
schema: z.object({
series: z.object({
success: Bool(),
result: z.object({
tasks: Task.array(),
}),
}),
}),
},
},
},
},
};
async handle(c: AppContext) {
// Get validated data
const data = await this.getValidatedData<typeof this.schema>();
// Retrieve the validated parameters
const { page, isCompleted } = data.query;
// Implement your own object list here
return {
success: true,
tasks: [
{
name: "Clean my room",
slug: "clean-room",
description: null,
completed: false,
due_date: "2025-01-05",
},
{
name: "Build something awesome with Cloudflare Workers",
slug: "cloudflare-workers",
description: "Lorem Ipsum",
completed: true,
due_date: "2022-12-24",
},
],
};
}
}

26
src/index.ts Normal file
View file

@ -0,0 +1,26 @@
import { fromHono } from "chanfana";
import { Hono } from "hono";
import { TaskCreate } from "./endpoints/taskCreate";
import { TaskDelete } from "./endpoints/taskDelete";
import { TaskFetch } from "./endpoints/taskFetch";
import { TaskList } from "./endpoints/taskList";
// Start a Hono app
const app = new Hono<{ Bindings: Env }>();
// Setup OpenAPI registry
const openapi = fromHono(app, {
docs_url: "/",
});
// Register OpenAPI endpoints
openapi.get("/api/tasks", TaskList);
openapi.post("/api/tasks", TaskCreate);
openapi.get("/api/tasks/:taskSlug", TaskFetch);
openapi.delete("/api/tasks/:taskSlug", TaskDelete);
// You may also register routes for non OpenAPI directly on Hono
// app.get('/test', (c) => c.text('Hono!'))
// Export the Hono app
export default app;

13
src/types.ts Normal file
View file

@ -0,0 +1,13 @@
import { DateTime, Str } from "chanfana";
import type { Context } from "hono";
import { z } from "zod";
export type AppContext = Context<{ Bindings: Env }>;
export const Task = z.object({
name: Str({ example: "lorem" }),
slug: Str(),
description: Str({ required: false }),
completed: z.boolean().default(false),
due_date: DateTime(),
});

30
tsconfig.json Normal file
View file

@ -0,0 +1,30 @@
{
"compilerOptions": {
/* Base Options: */
"esModuleInterop": true,
"skipLibCheck": true,
"target": "es2022",
"verbatimModuleSyntax": false,
"allowJs": true,
"resolveJsonModule": true,
"moduleDetection": "force",
/* Strictness */
"noImplicitAny": false,
"noImplicitThis": true,
"strictNullChecks": false,
"strict": true,
"noUncheckedIndexedAccess": true,
/* If NOT transpiling with TypeScript: */
"moduleResolution": "Bundler",
"module": "es2022",
"noEmit": true,
"lib": ["es2022"],
"types": [
"./worker-configuration.d.ts",
"@types/node",
"@types/service-worker-mock"
]
},
"exclude": ["node_modules", "dist", "tests"],
"include": ["src", "worker-configuration.d.ts"]
}

5755
worker-configuration.d.ts vendored Normal file

File diff suppressed because it is too large Load diff

47
wrangler.jsonc Normal file
View file

@ -0,0 +1,47 @@
/**
* For more details on how to configure Wrangler, refer to:
* https://developers.cloudflare.com/workers/wrangler/configuration/
*/
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "bluesky-alt-text-worker",
"main": "src/index.ts",
"compatibility_date": "2025-06-07",
"observability": {
"enabled": true
}
/**
* Smart Placement
* Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
*/
// "placement": { "mode": "smart" },
/**
* Bindings
* Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including
* databases, object storage, AI inference, real-time communication and more.
* https://developers.cloudflare.com/workers/runtime-apis/bindings/
*/
/**
* Environment Variables
* https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
*/
// "vars": { "MY_VARIABLE": "production_value" },
/**
* Note: Use secrets to store sensitive data.
* https://developers.cloudflare.com/workers/configuration/secrets/
*/
/**
* Static Assets
* https://developers.cloudflare.com/workers/static-assets/binding/
*/
// "assets": { "directory": "./public/", "binding": "ASSETS" },
/**
* Service Bindings (communicate between multiple Workers)
* https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
*/
// "services": [{ "binding": "MY_SERVICE", "service": "my-service" }]
}