Initial commit
This commit is contained in:
commit
0b47e12eec
18 changed files with 6342 additions and 0 deletions
12
.editorconfig
Normal file
12
.editorconfig
Normal file
|
@ -0,0 +1,12 @@
|
|||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
172
.gitignore
vendored
Normal file
172
.gitignore
vendored
Normal 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/
|
6
.prettierrc
Normal file
6
.prettierrc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"printWidth": 140,
|
||||
"singleQuote": true,
|
||||
"semi": true,
|
||||
"useTabs": true
|
||||
}
|
118
CHANGELOG.md
Normal file
118
CHANGELOG.md
Normal file
|
@ -0,0 +1,118 @@
|
|||
# service-worker
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [b459c9c]
|
||||
- Updated dependencies [4077773]
|
||||
- Updated dependencies [f6bcbd1]
|
||||
- Updated dependencies [2739db2]
|
||||
- Updated dependencies [e207079]
|
||||
- Updated dependencies [86edaa3]
|
||||
- @graphql-yoga/common@2.1.0
|
||||
|
||||
## 0.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6750eff]
|
||||
- Updated dependencies [0edf1f8]
|
||||
- Updated dependencies [d414f95]
|
||||
- Updated dependencies [2b6916f]
|
||||
- Updated dependencies [14c93a7]
|
||||
- Updated dependencies [b0b244b]
|
||||
- Updated dependencies [84091d2]
|
||||
- Updated dependencies [d2c2d18]
|
||||
- Updated dependencies [4e3129d]
|
||||
- Updated dependencies [36af58e]
|
||||
- Updated dependencies [433558f]
|
||||
- Updated dependencies [9256319]
|
||||
- Updated dependencies [bea2dcc]
|
||||
- Updated dependencies [fc1f2c7]
|
||||
- Updated dependencies [f2f6202]
|
||||
- Updated dependencies [11e80ea]
|
||||
- Updated dependencies [890e4ec]
|
||||
- Updated dependencies [fb894da]
|
||||
- Updated dependencies [603ccd8]
|
||||
- Updated dependencies [e93e62d]
|
||||
- Updated dependencies [3d54829]
|
||||
- Updated dependencies [5de1acf]
|
||||
- Updated dependencies [8ab60cf]
|
||||
- Updated dependencies [b1facf8]
|
||||
- Updated dependencies [5fba736]
|
||||
- Updated dependencies [b37564e]
|
||||
- Updated dependencies [62e8c07]
|
||||
- Updated dependencies [d078e84]
|
||||
- Updated dependencies [6d60ebf]
|
||||
- Updated dependencies [5d840d9]
|
||||
- Updated dependencies [95e0ac0]
|
||||
- Updated dependencies [2a033fb]
|
||||
- Updated dependencies [0424fe3]
|
||||
- Updated dependencies [d8f8a81]
|
||||
- Updated dependencies [a665e1e]
|
||||
- Updated dependencies [de1693e]
|
||||
- Updated dependencies [d137445]
|
||||
- Updated dependencies [9a9ac0a]
|
||||
- Updated dependencies [2b6916f]
|
||||
- Updated dependencies [dcaea56]
|
||||
- Updated dependencies [daeea82]
|
||||
- Updated dependencies [a10a16c]
|
||||
- @graphql-yoga/common@0.1.0
|
||||
|
||||
## 0.0.1-beta.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2b6916f]
|
||||
- Updated dependencies [6d60ebf]
|
||||
- Updated dependencies [0424fe3]
|
||||
- Updated dependencies [d137445]
|
||||
- Updated dependencies [2b6916f]
|
||||
- @graphql-yoga/common@0.1.0-beta.8
|
||||
|
||||
## 0.0.1-beta.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [14c93a7]
|
||||
- Updated dependencies [8ab60cf]
|
||||
- @graphql-yoga/common@0.1.0-beta.7
|
||||
|
||||
## 0.0.1-beta.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [9a9ac0a]
|
||||
- @graphql-yoga/common@0.1.0-beta.6
|
||||
|
||||
## 0.0.1-beta.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4e3129d]
|
||||
- Updated dependencies [5fba736]
|
||||
- Updated dependencies [2a033fb]
|
||||
- @graphql-yoga/common@0.1.0-beta.5
|
||||
|
||||
## 0.0.1-beta.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [433558f]
|
||||
- @graphql-yoga/common@0.1.0-beta.4
|
||||
|
||||
## 0.0.1-beta.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [62e8c07]
|
||||
- @graphql-yoga/common@0.1.0-beta.3
|
||||
|
||||
## 0.0.1-beta.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [11e80ea]
|
||||
- Updated dependencies [daeea82]
|
||||
- @graphql-yoga/common@0.0.1-beta.2
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 The Guild
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
142
README.md
Normal file
142
README.md
Normal file
|
@ -0,0 +1,142 @@
|
|||
<p align="center"><img src="https://github.com/dotansimha/graphql-yoga/raw/master/website/public/banner.svg" width="350" /></p>
|
||||
|
||||
# GraphQL Yoga for Cloudflare Workers (Wrangler template)
|
||||
|
||||
Fully-featured GraphQL Server with focus on easy setup, performance & great developer experience:
|
||||
|
||||
- **Easiest way to run a GraphQL server:** Sensible defaults & includes everything you need with minimal setup (we also export a platform/env-agnostic handler so you can build your own wrappers easily).
|
||||
- **Includes Subscriptions:** Built-in support for GraphQL subscriptions using **S**erver-**S**ent **E**vents.
|
||||
- **Compatible:** Works with all GraphQL clients (Apollo, Relay...) and fits seamless in your GraphQL workflow.
|
||||
- **WHATWG Fetch API:** the core package depends on [WHATWG Fetch API](https://fetch.spec.whatwg.org/) so it can run and deploy on any environment (Serverless, Workers, Deno, Node).
|
||||
- **Easily Extendable:** New GraphQL-Yoga support all [`envelop`](https://www.envelop.dev) plugins.
|
||||
|
||||
|
||||
<br />
|
||||
|
||||
[**See it in action!**](https://my-yoga-worker.cpolyeng.workers.dev)
|
||||
|
||||
<br />
|
||||
|
||||
[Read the 2.0 announcement blog post](https://www.the-guild.dev/blog/announcing-graphql-yoga-v2)
|
||||
|
||||
[Read the docs](https://www.graphql-yoga.com/docs/quick-start)
|
||||
|
||||
|
||||
[](https://deploy.workers.cloudflare.com/?url=https://github.com/the-guild-org/yoga-cloudflare-workers-template)
|
||||
|
||||
<p> </p>
|
||||
|
||||
----
|
||||
|
||||
<p> </p>
|
||||
|
||||
## Getting started
|
||||
|
||||
|
||||
1. Install and configure wrangler
|
||||
|
||||
```sh
|
||||
npm i @cloudflare/wrangler -g
|
||||
|
||||
wrangler login
|
||||
```
|
||||
|
||||
|
||||
2. Create a new project with the GraphQL Yoga template
|
||||
|
||||
```sh
|
||||
wrangler generate graphql-yoga-worker https://github.com/the-guild-org/yoga-cloudflare-workers-template
|
||||
```
|
||||
|
||||
|
||||
3. Build and deploy your CF Worker GraphQL API
|
||||
|
||||
```sh
|
||||
cd graphql-yoga-worker
|
||||
wrangler build
|
||||
wrangler publish
|
||||
```
|
||||
|
||||
<p> </p>
|
||||
|
||||
----
|
||||
|
||||
<p> </p>
|
||||
|
||||
## Project overview
|
||||
|
||||
### Yoga configuration
|
||||
|
||||
GraphQL Yoga comes with defaults for CORS and error handling:
|
||||
- CORS are enabled by default
|
||||
- Automatically masking unexpected errors and preventing sensitive information leaking to clients.
|
||||
|
||||
Yoga also brings support (with no additional dependency) for subscriptions, file uploads and your favourite schema building library (GraphQL Tools, Pothos, Nexus, TypeGraphQL, SDL first schema-design approaches, graphql-js, Apollo Tools).
|
||||
|
||||
|
||||
More information on all available features [on the official documentation](https://www.graphql-yoga.com/docs/quick-start).
|
||||
|
||||
<p> </p>
|
||||
|
||||
### Envelop Plugins
|
||||
|
||||
GraphQL Yoga is built on top of [Envelop](https://www.envelop.dev/).
|
||||
[Envelop](https://www.envelop.dev/) is a library that helps build GraphQL API faster and flexibly with plugin-based architecture.
|
||||
|
||||
Similar to Express middlewares allowing you to customize requests' behavior, Envelop applies the same idea to GraphQL requests.
|
||||
|
||||
By exposing hooks in all the phases of a GraphQL Request execution, Envelop enables the creation of plugins that simplify the setup of standard API features such as:
|
||||
- Security: Depth limits, Rate limiting
|
||||
- Authentication
|
||||
- Advanced caching
|
||||
- Error handling: Sentry, error masking
|
||||
- Monitoring: Hive
|
||||
- Logging
|
||||
- Tracing: NewRelic, Datadog, StatsD, Apollo Tracing
|
||||
|
||||
More information on [Envelop documentation](https://www.envelop.dev/docs).
|
||||
|
||||
|
||||
_Note: Some Node.js specific plugins such as `useSentry()` are supported in Serverless environments_
|
||||
|
||||
<p> </p>
|
||||
|
||||
### Caching
|
||||
|
||||
GraphQL Yoga is relying on `fetch()` WHATWG Fetch API, allowing you to [leverage Cloudflare Cache](https://developers.cloudflare.com/workers/examples/cache-using-fetch/) when fetching data from external services.
|
||||
|
||||
For more advanced use-cases, please refer to the `useResponseCache()` Envelop plugins, with a Redis cache (memory cache is not supported on Serverless).
|
||||
|
||||
|
||||
<p> </p>
|
||||
|
||||
### Bundle size
|
||||
|
||||
**GraphQL Yoga bundle is 36% lighter than Apollo Cloudflare Server** (_Wrangler bundled script comparison_), leading is a faster startup and deployment time. ⚡️
|
||||
|
||||
|
||||
|
||||
|
||||
<p> </p>
|
||||
|
||||
----
|
||||
|
||||
<p> </p>
|
||||
|
||||
## Going futher
|
||||
|
||||
- [GraphQL Yoga features documentation](https://www.graphql-yoga.com/docs/quick-start)
|
||||
- [GraphQL Yoga on Cloudflare Workers](https://www.graphql-yoga.com/docs/integrations/integration-with-cloudflare-workers)
|
||||
|
||||
|
||||
|
||||
<p> </p>
|
||||
|
||||
----
|
||||
|
||||
<p> </p>
|
||||
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed with the [MIT License](./LICENSE).
|
3230
package-lock.json
generated
Normal file
3230
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
25
package.json
Normal file
25
package.json
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "graphql-yoga-cloudflare-workers",
|
||||
"description": "Fully-featured, simple to set up, performant and extendable GraphQL server",
|
||||
"version": "0.2.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"deploy": "wrangler deploy",
|
||||
"dev": "wrangler dev",
|
||||
"start": "wrangler dev",
|
||||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"graphql": "16.8.1",
|
||||
"graphql-yoga": "5.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/vitest-pool-workers": "0.1.9",
|
||||
"@cloudflare/workers-types": "4.20240329.0",
|
||||
"@types/service-worker-mock": "2.0.4",
|
||||
"service-worker-mock": "2.0.5",
|
||||
"typescript": "5.4.3",
|
||||
"vitest": "1.3.0",
|
||||
"wrangler": "3.41.0"
|
||||
}
|
||||
}
|
1999
pnpm-lock.yaml
generated
Normal file
1999
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load diff
3
renovate.json
Normal file
3
renovate.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": ["github>the-guild-org/shared-config:renovate"]
|
||||
}
|
119
src/durableObject.ts
Normal file
119
src/durableObject.ts
Normal file
|
@ -0,0 +1,119 @@
|
|||
import { DurableObject } from "cloudflare:workers";
|
||||
|
||||
export type Answer = {
|
||||
id: string;
|
||||
text: string;
|
||||
sender_info: {
|
||||
pseudonym: string;
|
||||
name?: string;
|
||||
}
|
||||
}
|
||||
|
||||
export type AnswerResponse = {
|
||||
answers: {
|
||||
cursor: string;
|
||||
answer: Answer;
|
||||
}[],
|
||||
hasPreviousPage: boolean;
|
||||
hasNextPage: boolean;
|
||||
}
|
||||
|
||||
export class EpisodeStorage extends DurableObject {
|
||||
async countQuery(): Promise<Number> {
|
||||
const answers = (await this.ctx.storage.get<Answer[]>("answers", {allowConcurrency: true}))||[];
|
||||
|
||||
return answers.length;
|
||||
}
|
||||
|
||||
makeCursor(idx: number, id: string): string {
|
||||
return btoa(`${idx};;;${id}`)
|
||||
}
|
||||
|
||||
async sendAnswer(text: string, remoteAddr: string, name?: string): Promise<Answer> {
|
||||
const encoder = new TextEncoder();
|
||||
const now = new Date();
|
||||
const data = encoder.encode(`R-A-${remoteAddr}-I-D-${now.getDay()}_${now.getMonth()}_${now.getFullYear()}-E-R`);
|
||||
const hash = await crypto.subtle.digest("SHA-256", data);
|
||||
|
||||
const pseudonym = Buffer.from(hash).toString("base64"); // This is now absolutely useless for PII, but perfect for spam fighting.
|
||||
|
||||
const id = crypto.randomUUID();
|
||||
|
||||
const myAnswer: Answer = {
|
||||
id,
|
||||
text,
|
||||
sender_info: {
|
||||
pseudonym,
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
const answers = (await this.ctx.storage.get<Answer[]>("answers"))||[];
|
||||
answers.push(myAnswer);
|
||||
await this.ctx.storage.put("answers", answers);
|
||||
|
||||
return myAnswer
|
||||
}
|
||||
|
||||
async answersQuery(params: {first: number, after?: string}): Promise<AnswerResponse> {
|
||||
const answers = (await this.ctx.storage.get<Answer[]>("answers", {allowConcurrency: true}))||[];
|
||||
|
||||
const first = params.first;
|
||||
const after = params.after;
|
||||
|
||||
let decodedAfter: {
|
||||
idx: number,
|
||||
id: string
|
||||
}|null = null;
|
||||
|
||||
if(after) {
|
||||
const payload = atob(after)
|
||||
const [idxRaw, id] = payload.split(";;;");
|
||||
|
||||
decodedAfter = {
|
||||
idx: Number.parseInt(idxRaw),
|
||||
id: id
|
||||
}
|
||||
}
|
||||
|
||||
let reading = after == null;
|
||||
let readCounter = 0;
|
||||
|
||||
const response: AnswerResponse = {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: reading,
|
||||
answers: []
|
||||
}
|
||||
|
||||
for (let answerIdx = 0; answerIdx < answers.length; answerIdx++) {
|
||||
const answer = answers[answerIdx];
|
||||
|
||||
if(!reading) {
|
||||
if(decodedAfter?.id == answer.id) {
|
||||
reading = true;
|
||||
continue; // Start reading on next one
|
||||
}
|
||||
|
||||
if(decodedAfter?.idx == answerIdx) {
|
||||
reading = true;
|
||||
continue; // Start reading on next one
|
||||
}
|
||||
|
||||
continue; // Don't read.
|
||||
}
|
||||
|
||||
if(readCounter > first) {
|
||||
response.hasNextPage = true;
|
||||
break;
|
||||
}
|
||||
|
||||
response.answers.push({
|
||||
answer,
|
||||
cursor: this.makeCursor(answerIdx, answer.id)
|
||||
})
|
||||
readCounter++;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
24
src/env.d.ts
vendored
Normal file
24
src/env.d.ts
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { EpisodeStorage } from "./durableObject";
|
||||
|
||||
interface Env {
|
||||
EPISODE_STORAGE: DurableObjectNamespace<EpisodeStorage>;
|
||||
EPISODE_META: KVNamespace;
|
||||
}
|
||||
|
||||
|
||||
/*export interface Env {
|
||||
// Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/
|
||||
// MY_KV_NAMESPACE: KVNamespace;
|
||||
//
|
||||
// Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/
|
||||
// MY_DURABLE_OBJECT: DurableObjectNamespace;
|
||||
//
|
||||
// Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/
|
||||
// MY_BUCKET: R2Bucket;
|
||||
//
|
||||
// Example binding to a Service. Learn more at https://developers.cloudflare.com/workers/runtime-apis/service-bindings/
|
||||
// MY_SERVICE: Fetcher;
|
||||
//
|
||||
// Example binding to a Queue. Learn more at https://developers.cloudflare.com/queues/javascript-apis/
|
||||
// MY_QUEUE: Queue;
|
||||
}*/
|
172
src/index.ts
Normal file
172
src/index.ts
Normal file
|
@ -0,0 +1,172 @@
|
|||
/**
|
||||
* Raider Asks GraphQL-API
|
||||
*/
|
||||
|
||||
import { createSchema, createYoga } from "graphql-yoga";
|
||||
import { Env } from "./env";
|
||||
import { Answer, AnswerResponse, EpisodeStorage } from "./durableObject";
|
||||
|
||||
type Episode = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_PAGE_SIZE = 5;
|
||||
const MAX_PAGE_SIZE = 50;
|
||||
|
||||
const yoga = createYoga<Env>({
|
||||
schema: createSchema({
|
||||
typeDefs: /* GraphQL */ `
|
||||
scalar Cursor
|
||||
|
||||
type Episode {
|
||||
id: ID!
|
||||
title: String!
|
||||
description: String!
|
||||
active: Boolean!
|
||||
|
||||
answerCount: Int!
|
||||
answers(first: Int, after: Cursor): AnswerConnection!
|
||||
}
|
||||
|
||||
type AnswerConnection {
|
||||
pageInfo: PageInfo!
|
||||
edges: [AnswerEdge]
|
||||
}
|
||||
|
||||
type AnswerEdge {
|
||||
cursor: Cursor!
|
||||
node: Answer!
|
||||
}
|
||||
|
||||
type Answer {
|
||||
id: ID!
|
||||
text: String!
|
||||
name: String
|
||||
}
|
||||
|
||||
type PageInfo {
|
||||
hasPreviousPage: Boolean!
|
||||
hasNextPage: Boolean!
|
||||
startCursor: Cursor
|
||||
endCursor: Cursor
|
||||
}
|
||||
type Query {
|
||||
episode(id: ID!): Episode
|
||||
}
|
||||
type Mutation {
|
||||
sendResponse(episode: ID!, text: String!, name: String): Answer
|
||||
}
|
||||
`,
|
||||
resolvers: {
|
||||
Mutation: {
|
||||
sendResponse: async(_, { episode, text, name }, env) => {
|
||||
const episodeObj = await getEpisodeAndStorage(env, episode);
|
||||
if(episodeObj == null)
|
||||
throw "Invalid Episode"; // No such episode is going on.
|
||||
|
||||
const {storage} = episodeObj;
|
||||
|
||||
const remoteAddr = env.request.headers.get("CF-Connecting-IP")||"DUMMY";
|
||||
const answer = await storage.sendAnswer(text, remoteAddr, name);
|
||||
|
||||
return answer;
|
||||
},
|
||||
},
|
||||
Query: {
|
||||
episode: async (_parent, { id }, env) => {
|
||||
const episode = await getEpisodeAndStorage(env, id);
|
||||
if(episode == null)
|
||||
return null; // No such episode is going on.
|
||||
|
||||
const {storage, meta} = episode;
|
||||
|
||||
const countResponse = await storage.countQuery();
|
||||
|
||||
return {
|
||||
id: meta.id,
|
||||
title: meta.title,
|
||||
description: meta.description,
|
||||
active: meta.active,
|
||||
answerCount: countResponse
|
||||
};
|
||||
},
|
||||
},
|
||||
Episode: {
|
||||
answers: async (_parent, { first, after }, env) => {
|
||||
const episode = await getEpisodeAndStorage(env, _parent.id);
|
||||
if(episode == null)
|
||||
throw "Invalid ID"; // No such episode is going on.
|
||||
|
||||
const {storage} = episode;
|
||||
|
||||
first = first||DEFAULT_PAGE_SIZE
|
||||
|
||||
if(first > MAX_PAGE_SIZE)
|
||||
throw "Max Page Size exceeded";
|
||||
|
||||
|
||||
const answerObj = await storage.answersQuery({first, after})
|
||||
|
||||
return {
|
||||
pageInfo: {
|
||||
hasPreviousPage: answerObj.hasPreviousPage,
|
||||
hasNextPage: answerObj.hasNextPage,
|
||||
startCursor: answerObj.answers.length > 0 ? answerObj.answers[0].cursor : null,
|
||||
endCursor: answerObj.answers.length > 0 ? answerObj.answers[answerObj.answers.length - 1].cursor : null,
|
||||
},
|
||||
edges: answerObj.answers.map((answerNode) => {
|
||||
return {
|
||||
cursor: answerNode.cursor,
|
||||
node: {
|
||||
id: answerNode.answer.id,
|
||||
text: answerNode.answer.text,
|
||||
name: answerNode.answer.sender_info.name
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
graphiql: {
|
||||
defaultQuery: /* GraphQL */ `
|
||||
query sampleEpisodeQuery {
|
||||
episode: episode(id: "mog") {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
});
|
||||
|
||||
const getEpisodeAndStorage = async (env: Env, id: string): Promise<null|{meta: Episode, storage: DurableObjectStub<EpisodeStorage>}> => {
|
||||
const episodeMeta = await env.EPISODE_META.get<Episode>(id, "json")
|
||||
if(episodeMeta == null)
|
||||
return null; // No such episode is going on.
|
||||
|
||||
const episodeStorageId = env.EPISODE_STORAGE.idFromName(id)
|
||||
|
||||
console.log(episodeStorageId)
|
||||
|
||||
const episodeStorageStub = env.EPISODE_STORAGE.get(episodeStorageId);
|
||||
|
||||
console.log(episodeMeta, episodeStorageStub, episodeStorageStub.countQuery)
|
||||
|
||||
return {
|
||||
meta: episodeMeta,
|
||||
storage: episodeStorageStub
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
||||
return yoga.fetch(request, env);
|
||||
},
|
||||
};
|
||||
|
||||
export {EpisodeStorage} from './durableObject';
|
75
test/index.spec.ts
Normal file
75
test/index.spec.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
// test/index.spec.ts
|
||||
import { env, createExecutionContext, waitOnExecutionContext } from "cloudflare:test";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import worker from "../src/index";
|
||||
|
||||
// For now, you'll need to do something like this to get a correctly-typed
|
||||
// `Request` to pass to `worker.fetch()`.
|
||||
const IncomingRequest = Request<unknown, IncomingRequestCfProperties>;
|
||||
|
||||
describe("Pokemon API worker", () => {
|
||||
it("responds with Pokemon data for id=1 (unit style)", async () => {
|
||||
const request = new IncomingRequest("https://example.com");
|
||||
// Create an empty context to pass to `worker.fetch()`.
|
||||
const ctx = createExecutionContext();
|
||||
const response = await worker.fetch(request, env, ctx);
|
||||
// Wait for all `Promise`s passed to `ctx.waitUntil()` to settle before running test assertions
|
||||
await waitOnExecutionContext(ctx);
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = await response.json();
|
||||
} catch (error) {
|
||||
console.error("Error parsing response JSON:", error);
|
||||
}
|
||||
|
||||
if (data) {
|
||||
expect(data).toMatchObject({
|
||||
id: expect.any(Number),
|
||||
name: expect.any(String),
|
||||
height: expect.any(Number),
|
||||
weight: expect.any(Number),
|
||||
sprites: {
|
||||
front_default: expect.any(String),
|
||||
front_shiny: expect.any(String),
|
||||
front_female: expect.any(String),
|
||||
front_shiny_female: expect.any(String),
|
||||
back_default: expect.any(String),
|
||||
back_shiny: expect.any(String),
|
||||
back_female: expect.any(String),
|
||||
back_shiny_female: expect.any(String),
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("responds with Pokemon data for id=1 (integration style)", async () => {
|
||||
const response = await worker.fetch(new Request("https://example.com"), env, createExecutionContext());
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = await response.json();
|
||||
} catch (error) {
|
||||
console.error("Error parsing response JSON:", error);
|
||||
}
|
||||
|
||||
if (data) {
|
||||
expect(data).toMatchObject({
|
||||
id: expect.any(Number),
|
||||
name: expect.any(String),
|
||||
height: expect.any(Number),
|
||||
weight: expect.any(Number),
|
||||
sprites: {
|
||||
front_default: expect.any(String),
|
||||
front_shiny: expect.any(String),
|
||||
front_female: expect.any(String),
|
||||
front_shiny_female: expect.any(String),
|
||||
back_default: expect.any(String),
|
||||
back_shiny: expect.any(String),
|
||||
back_female: expect.any(String),
|
||||
back_shiny_female: expect.any(String),
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
11
test/tsconfig.json
Normal file
11
test/tsconfig.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"@cloudflare/workers-types/experimental",
|
||||
"@cloudflare/vitest-pool-workers"
|
||||
]
|
||||
},
|
||||
"include": ["./**/*.ts", "../src/env.d.ts"],
|
||||
"exclude": []
|
||||
}
|
104
tsconfig.json
Normal file
104
tsconfig.json
Normal file
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||
"lib": ["es2021"] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
|
||||
"jsx": "react" /* Specify what JSX code is generated. */,
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
|
||||
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
|
||||
/* Modules */
|
||||
"module": "es2022" /* Specify what module code is generated. */,
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
"moduleResolution": "Bundler" /* Specify how TypeScript looks up a file from a given module specifier. */,
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
|
||||
"types": [
|
||||
"@cloudflare/workers-types/2023-07-01"
|
||||
] /* Specify type package names to be included without being referenced in a source file. */,
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
"resolveJsonModule": true /* Enable importing .json files */,
|
||||
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
"allowJs": true /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */,
|
||||
"checkJs": false /* Enable error reporting in type-checked JavaScript files. */,
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
"noEmit": true /* Disable emitting files from a compilation. */,
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
"isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */,
|
||||
"allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */,
|
||||
// "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
|
||||
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
},
|
||||
"exclude": ["test"]
|
||||
}
|
11
vitest.config.ts
Normal file
11
vitest.config.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config";
|
||||
|
||||
export default defineWorkersConfig({
|
||||
test: {
|
||||
poolOptions: {
|
||||
workers: {
|
||||
wrangler: { configPath: "./wrangler.toml" },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
98
wrangler.toml
Normal file
98
wrangler.toml
Normal file
|
@ -0,0 +1,98 @@
|
|||
name = "raider-asks"
|
||||
main = "src/index.ts"
|
||||
compatibility_date = "2024-03-29"
|
||||
compatibility_flags = ["nodejs_compat"]
|
||||
|
||||
# Variable bindings. These are arbitrary, plaintext strings (similar to environment variables)
|
||||
# Note: Use secrets to store sensitive data.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
|
||||
# [vars]
|
||||
# MY_VARIABLE = "production_value"
|
||||
|
||||
# Bind the Workers AI model catalog. Run machine learning models, powered by serverless GPUs, on Cloudflare’s global network
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#workers-ai
|
||||
# [ai]
|
||||
# binding = "AI"
|
||||
|
||||
# Bind an Analytics Engine dataset. Use Analytics Engine to write analytics within your Pages Function.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#analytics-engine-datasets
|
||||
# [[analytics_engine_datasets]]
|
||||
# binding = "MY_DATASET"
|
||||
|
||||
# Bind a headless browser instance running on Cloudflare's global network.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#browser-rendering
|
||||
# [browser]
|
||||
# binding = "MY_BROWSER"
|
||||
|
||||
# Bind a D1 database. D1 is Cloudflare’s native serverless SQL database.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#d1-databases
|
||||
# [[d1_databases]]
|
||||
# binding = "MY_DB"
|
||||
# database_name = "my-database"
|
||||
# database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
|
||||
# Bind a dispatch namespace. Use Workers for Platforms to deploy serverless functions programmatically on behalf of your customers.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#dispatch-namespace-bindings-workers-for-platforms
|
||||
# [[dispatch_namespaces]]
|
||||
# binding = "MY_DISPATCHER"
|
||||
# namespace = "my-namespace"
|
||||
|
||||
# Bind a Durable Object. Durable objects are a scale-to-zero compute primitive based on the actor model.
|
||||
# Durable Objects can live for as long as needed. Use these when you need a long-running "server", such as in realtime apps.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#durable-objects
|
||||
[[durable_objects.bindings]]
|
||||
name = "EPISODE_STORAGE"
|
||||
class_name = "EpisodeStorage"
|
||||
|
||||
# Durable Object migrations.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#migrations
|
||||
[[migrations]]
|
||||
tag = "v1"
|
||||
new_classes = ["EpisodeStorage"]
|
||||
|
||||
# Bind a Hyperdrive configuration. Use to accelerate access to your existing databases from Cloudflare Workers.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#hyperdrive
|
||||
# [[hyperdrive]]
|
||||
# binding = "MY_HYPERDRIVE"
|
||||
# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
|
||||
# Bind a KV Namespace. Use KV as persistent storage for small key-value pairs.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#kv-namespaces
|
||||
[[kv_namespaces]]
|
||||
binding = "EPISODE_META"
|
||||
id = "bcead1e6393342489ac99d2e504054eb"
|
||||
|
||||
# Bind an mTLS certificate. Use to present a client certificate when communicating with another service.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#mtls-certificates
|
||||
# [[mtls_certificates]]
|
||||
# binding = "MY_CERTIFICATE"
|
||||
# certificate_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
|
||||
# Bind a Queue producer. Use this binding to schedule an arbitrary task that may be processed later by a Queue consumer.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#queues
|
||||
# [[queues.producers]]
|
||||
# binding = "MY_QUEUE"
|
||||
# queue = "my-queue"
|
||||
|
||||
# Bind a Queue consumer. Queue Consumers can retrieve tasks scheduled by Producers to act on them.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#queues
|
||||
# [[queues.consumers]]
|
||||
# queue = "my-queue"
|
||||
|
||||
# Bind an R2 Bucket. Use R2 to store arbitrarily large blobs of data, such as files.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#r2-buckets
|
||||
# [[r2_buckets]]
|
||||
# binding = "MY_BUCKET"
|
||||
# bucket_name = "my-bucket"
|
||||
|
||||
# Bind another Worker service. Use this binding to call another Worker without network overhead.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
|
||||
# [[services]]
|
||||
# binding = "MY_SERVICE"
|
||||
# service = "my-service"
|
||||
|
||||
# Bind a Vectorize index. Use to store and query vector embeddings for semantic search, classification and other vector search use-cases.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#vectorize-indexes
|
||||
# [[vectorize]]
|
||||
# binding = "MY_INDEX"
|
||||
# index_name = "my-index"
|
Loading…
Add table
Reference in a new issue