[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-80460":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":16,"stars7d":16,"stars30d":16,"stars90d":15,"forks30d":15,"starsTrendScore":17,"compositeScore":18,"rankGlobal":10,"rankLanguage":10,"license":19,"archived":20,"fork":20,"defaultBranch":21,"hasWiki":20,"hasPages":20,"topics":22,"createdAt":10,"pushedAt":10,"updatedAt":23,"readmeContent":24,"aiSummary":25,"trendingCount":15,"starSnapshotCount":15,"syncStatus":26,"lastSyncTime":27,"discoverSource":28},80460,"EdgeGist","xream\u002FEdgeGist","xream","Minimal GitHub Gist-compatible API service running on Cloudflare's edge network, backed by D1 and packaged for Cloudflare Pages.","",null,"TypeScript",52,12,51,0,1,3,3.34,"GNU General Public License v3.0",false,"main",[],"2026-06-12 02:04:02","# EdgeGist\n\n\u003Cp align=\"center\">\n  \u003Cpicture>\n    \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\"public\u002Ficons\u002Fedgegist-dark-192.png\">\n    \u003Cimg src=\"public\u002Ficons\u002Fedgegist-192.png\" alt=\"EdgeGist app icon\" width=\"96\" height=\"96\">\n  \u003C\u002Fpicture>\n\u003C\u002Fp>\n\n[简体中文](README.zh-CN.md)\n\nMinimal GitHub Gist-compatible API service running on Cloudflare's edge network, backed by D1 and deployed on Cloudflare Workers with static assets.\n\nWorks perfectly with the [Sub-Store](https:\u002F\u002Fgithub.com\u002Fsub-store-org\u002FSub-Store) Gist sharing and backup features.\n\nEdgeGist is API-first: deploy it, configure your owner token, and point Gist API clients at your own base URL instead of `https:\u002F\u002Fapi.github.com`. It also ships a single-owner Web UI at `\u002F\u003Cowner>` for browsing, editing, import\u002Fexport, and Cloudflare usage checks. The root path `\u002F` intentionally returns `404` instead of redirecting, so the configured owner route is not exposed.\n\n## Documentation\n\n- [EdgeGist Automated Sync and Secure Deployment Guide (GitHub Actions)](doc\u002Fauto_deploy)  - thanks to [lockcp](https:\u002F\u002Fgithub.com\u002Flockcp) for providing this document.\n\n## Community\n\nJoin the community for discussion and updates.\n\n👥 Group [折腾啥](https:\u002F\u002Ft.me\u002Fzhetengsha_group) · 📢 Channel [折腾啥](https:\u002F\u002Ft.me\u002Fzhetengsha)\n\n## Screenshots\n\nThe Web UI supports English and Simplified Chinese. The screenshots below use Simplified Chinese so the project only needs to maintain one screenshot set.\n\n\u003Ctable>\n  \u003Ctr>\n    \u003Ctd width=\"50%\" valign=\"top\" align=\"center\">\n      \u003Cimg src=\"screenshots\u002Freadme\u002Flist.png\" alt=\"Gist list with search, filters, pagination, stars, and highlighted content matches\" width=\"100%\">\n      \u003Cbr>\n      \u003Csub>Server-side search across ids, descriptions, filenames, and file contents, with filters, sorting, pagination, stars, and syntax-highlighted content matches.\u003C\u002Fsub>\n    \u003C\u002Ftd>\n    \u003Ctd width=\"50%\" valign=\"top\" align=\"center\">\n      \u003Cimg src=\"screenshots\u002Freadme\u002Fdetail.png\" alt=\"Wide gist detail dashboard with file tree, diff view, and history panels\" width=\"100%\">\n      \u003Cbr>\n      \u003Csub>Responsive gist detail dashboard with a file tree, syntax-highlighted content, file history, file-set changes, and configurable diffs.\u003C\u002Fsub>\n    \u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n    \u003Ctd width=\"50%\" valign=\"top\" align=\"center\">\n      \u003Cimg src=\"screenshots\u002Freadme\u002Fdiff.png\" alt=\"Compact gist detail diff view with split layout and diff controls\" width=\"100%\">\n      \u003Cbr>\n      \u003Csub>Diff view with current and revision raw URLs, automatic\u002Fsplit\u002Funified\u002Fstacked layouts, inline-change modes, line wrapping, line numbers, backgrounds, and collapsible unchanged lines.\u003C\u002Fsub>\n    \u003C\u002Ftd>\n    \u003Ctd width=\"50%\" valign=\"top\" align=\"center\">\n      \u003Cimg src=\"screenshots\u002Freadme\u002Fusage.png\" alt=\"Cloudflare usage and quota dashboard on a compact viewport\" width=\"100%\">\n      \u003Cbr>\n      \u003Csub>Cached and refreshable Cloudflare Workers request, D1 row, and D1 storage usage.\u003C\u002Fsub>\n    \u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n    \u003Ctd width=\"50%\" valign=\"top\" align=\"center\">\n      \u003Cimg src=\"screenshots\u002Freadme\u002Flogin.png\" alt=\"Owner login with username, password, remember-me, and Cloudflare Turnstile\" width=\"100%\">\n      \u003Cbr>\n      \u003Csub>Owner login with username\u002Fpassword, optional remember-me, and optional Cloudflare Turnstile.\u003C\u002Fsub>\n    \u003C\u002Ftd>\n    \u003Ctd width=\"50%\" valign=\"top\">\u003C\u002Ftd>\n  \u003C\u002Ftr>\n\u003C\u002Ftable>\n\n## Current Scope\n\n- GitHub Gist-shaped API for core gist CRUD and retained revisions.\n- Single-owner authentication: bearer token for API clients, password + optional Turnstile + signed cookie for the Web UI.\n- Public and secret-link visibility handling. Secret gists are hidden from anonymous list APIs but remain readable by direct URL; retained revisions follow the current gist visibility.\n- D1-backed current files, retained history snapshots, and settings.\n- Latest-N history retention for each file and each gist's file-change feed.\n- GitHub Gist-style Web UI at `\u002F\u003Cowner>`, `\u002F\u003Cowner>\u002Fnew`, `\u002F\u003Cowner>\u002F\u003Cgist_id>`, and `\u002F\u003Cowner>\u002F\u003Cgist_id>\u002F\u003Csha>` with anonymous public browsing, owner management, gist editing, file history, diff view, stars, import\u002Fexport, i18n, themes, PWA install support, and Cloudflare usage\u002Fquota views.\n- Root `\u002F` returns `404` and does not redirect to the owner route. Anonymous users need to know `\u002F\u003Cowner>` to browse public gists.\n- Real single-owner star support; fork and comment surfaces remain compatibility mocks with zero social data.\n- Release packaging for Cloudflare Workers, including prebuilt Worker assets.\n\nNot in the first implementation: git repository transport, multi-user collaboration, and real social features.\n\n## API Behavior Notes\n\n- Owner API clients should send `Authorization: Bearer \u003CEDGEGIST_OWNER_TOKEN>`.\n- Anonymous list APIs return `public` gists only. `secret` gists are omitted from anonymous lists but can still be read anonymously when the caller knows the URL or gist id.\n- Retained revisions do not have separate visibility. If the current gist is readable by direct URL, its retained revisions are readable by direct URL.\n- `PATCH \u002Fgists\u002F{gist_id}` treats `null`, empty content, and an empty file spec as file deletion. Deleting every file deletes the gist.\n- Raw file endpoints serve content as `text\u002Fplain` with `nosniff`, so HTML gist files are shown as inert text.\n\n## Development\n\nRequirements: Bun and Node.js 22 or newer. This repository includes `.node-version` because Wrangler requires a modern Node runtime. If you use mise, run `mise install` once and your shell will pick up the project Node version automatically when you enter the repository.\n\n```sh\nbun install\nbun run dev\n```\n\n`bun run dev` prepares the local environment, applies local D1 migrations, and starts the API at `http:\u002F\u002F127.0.0.1:8787\u002F` and the Web UI at `http:\u002F\u002F127.0.0.1:8787\u002F\u003Cowner>`. Root `\u002F` returns `404` by design.\n\nOn first run it will:\n\n- create `wrangler.jsonc` from `wrangler.example.jsonc` when missing;\n- create or append `.dev.vars` with local development defaults;\n- set `EDGEGIST_BASE_URL` to `http:\u002F\u002F127.0.0.1:8787`;\n- skip Turnstile on localhost\u002Floopback dev hosts even if Turnstile keys are configured;\n- persist local D1 data under `.wrangler\u002Fstate\u002Fv3`.\n\nUseful development commands:\n\n```sh\nbun run dev:prepare\nbun run dev:server\nbun run test\nbun run build\n```\n\nUse `bun run dev:prepare` when you only want to create local config and apply local D1 migrations. Use `bun run dev:server` when local D1 is already prepared and you only want to restart the server. `bun run build` creates the client assets, Worker script, and Workers Assets ignore file under `dist\u002F`.\n\nIf you used an older development build before the schema stabilized and local D1 starts failing, delete `.wrangler\u002Fstate\u002Fv3` and run `bun run dev:prepare` again. EdgeGist keeps source migrations clean for new installs and does not carry compatibility migrations for stale development data.\n\n## Configuration\n\n`wrangler.jsonc` is the deployment source of truth. Copy `wrangler.example.jsonc` to `wrangler.jsonc` and fill in the project-specific values. Do not commit `wrangler.jsonc` when it contains real credentials or account-specific IDs.\n\n### Worker and asset fields\n\n| Field | Required | Value |\n| --- | --- | --- |\n| `name` | Yes | Worker script name. For this deployment it is usually `edge-gist`. The Usage page's `Worker name` field should match this value when you want script-level request usage. |\n| `compatibility_date` | Yes | Cloudflare Workers compatibility date. Keep the example value unless you intentionally update runtime behavior. |\n| `main` | Yes | `.\u002Fdist\u002F_worker.js`. The build outputs the Worker script here. |\n| `assets.directory` | Yes | `.\u002Fdist`. Static files are uploaded as Workers Assets from this directory. |\n| `assets.binding` | Yes for this project | `ASSETS`. Keep the binding name unless the Worker code is changed to use a different assets binding. |\n\nThe build copies `.assetsignore` into `dist` so `_worker.js` is not served as a static asset.\n\n### Application variables in `vars`\n\n| Field | Required | Value |\n| --- | --- | --- |\n| `EDGEGIST_OWNER_USERNAME` | Yes | Owner login username. |\n| `EDGEGIST_OWNER_PASSWORD` | Yes | Owner login password. |\n| `EDGEGIST_OWNER_TOKEN` | Yes | Owner access token for API\u002Fclient operations. Keep it secret. |\n| `EDGEGIST_BASE_URL` | Yes | Public origin with protocol, for example `https:\u002F\u002Fedge-gist.sbfm.eu.org`. Use the final Workers custom domain, not a Pages URL. |\n| `EDGEGIST_HISTORY_MAX_VERSIONS` | Optional | Number of retained history entries per file and file-change records per gist. Defaults to `100`. |\n| `EDGEGIST_TURNSTILE_SITE_KEY` | Optional | Cloudflare Turnstile site key. Leave blank to disable Turnstile. |\n| `EDGEGIST_TURNSTILE_SECRET_KEY` | Optional | Cloudflare Turnstile secret key. Required only when the site key is set. |\n\nLocal development can use `.dev.vars`. Production reads values from `wrangler.jsonc` `vars` during deploy.\n\n### D1 binding in `d1_databases`\n\n| Field | Required | Value |\n| --- | --- | --- |\n| `binding` | Yes | Must be `DB`. The backend reads the database from `c.env.DB`. |\n| `database_name` | Yes | D1 database display name, usually `edge-gist`. |\n| `database_id` | Yes | D1 database UUID from `wrangler d1 create` or the Cloudflare dashboard. This is an ID, not the database name. |\n\nUse the same D1 database UUID in the app's Cloudflare Usage settings when you want D1 usage and quota data.\n\n### Custom domain\n\nA custom domain can be attached in either place:\n\n1. In `wrangler.jsonc` before deploy:\n\n```jsonc\n{\n  \"routes\": [\n    { \"pattern\": \"edge-gist.sbfm.eu.org\", \"custom_domain\": true }\n  ]\n}\n```\n\n2. Manually in the Cloudflare dashboard: Workers & Pages -> select the Worker -> Settings -> Domains & Routes -> Add -> Custom Domain.\n\nCloudflare requires the hostname to be in a Cloudflare zone you control. If Wrangler fails to create the custom domain because of DNS or token permissions, deploy the Worker first, then attach the domain manually in the dashboard.\n\nNo KV, R2, Queues, or Workers Sites configuration is required for this project.\n## Command-Line Deployment\n\nThis project deploys to Cloudflare Workers with Workers Assets. Use `wrangler deploy`; do not use `wrangler pages deploy`.\n\n### Fresh deployment\n\nPrerequisites: Bun, Node.js compatible with this project, Wrangler 4+, and access to the target Cloudflare account.\n\n1. Install dependencies:\n\n```bash\nbun install\n```\n\n2. Create the D1 database and record the returned UUID:\n\n```bash\nbun run db:create\n```\n\n3. Create the production Wrangler config:\n\n```bash\ncp wrangler.example.jsonc wrangler.jsonc\n```\n\n4. Edit `wrangler.jsonc`:\n\n| Area | What to set |\n| --- | --- |\n| Worker | `name`, normally `edge-gist`. |\n| Owner auth | `EDGEGIST_OWNER_USERNAME`, `EDGEGIST_OWNER_PASSWORD`, and `EDGEGIST_OWNER_TOKEN`. |\n| Public URL | `EDGEGIST_BASE_URL`, for example `https:\u002F\u002Fedge-gist.sbfm.eu.org`. |\n| D1 | `d1_databases[0].database_id` with the D1 UUID. Keep `binding` as `DB`. |\n| Custom domain | Optional `routes` entry with `{ \"pattern\": \"your-domain.example.com\", \"custom_domain\": true }`. |\n| Turnstile | Optional site key and secret key. Set both or leave both blank. |\n\n5. Apply database migrations to the remote D1 database:\n\n```bash\nbun run db:migrate:remote\n```\n\n6. Build the Worker and static assets:\n\n```bash\nbun run build\n```\n\n7. Deploy:\n\n```bash\nbun run deploy\n```\n\nIf you are not logged in with Wrangler, provide a token for this command only:\n\n```bash\nCLOUDFLARE_API_TOKEN=\u003Ctoken> bun run deploy\n```\n\n8. If you did not configure `routes`, attach the custom domain manually in the Cloudflare dashboard. After the domain is active, make sure `EDGEGIST_BASE_URL` matches that URL and redeploy if you changed it.\n\n### Manual Cloudflare Workers deployment from a release package\n\nUse this path when you download `edgegist-package.zip` and do not want to build locally.\n\n1. Unzip `edgegist-package.zip`.\n2. Create or select a D1 database in Cloudflare.\n3. Run the SQL files in `migrations\u002F` against that D1 database, in filename order.\n4. Copy `wrangler.example.jsonc` to `wrangler.jsonc` inside the extracted package.\n5. Fill in the same fields described in the Configuration section: owner auth, `EDGEGIST_BASE_URL`, D1 `database_id`, optional Turnstile, and optional `routes`.\n6. Deploy with Wrangler 4+:\n\n```bash\nwrangler deploy\n```\n\nOr, without a globally installed Wrangler:\n\n```bash\nnpx wrangler@^4 deploy\n```\n\nThe release package already contains `dist\u002F_worker.js` and static assets, so no build command is required.\n\n### Manual Cloudflare dashboard steps\n\nSome operations are intentionally manual or account-specific:\n\n| Operation | Where |\n| --- | --- |\n| Create API tokens | Cloudflare dashboard -> My Profile -> API Tokens. |\n| Create D1 database | Cloudflare dashboard -> Workers & Pages -> D1, or `bun run db:create`. |\n| Apply migrations manually | Cloudflare dashboard -> D1 -> database -> Console. |\n| Attach custom domain | Cloudflare dashboard -> Workers & Pages -> Worker -> Settings -> Domains & Routes. |\n| Configure Usage page fields | Edge Gist app -> Settings -> Cloudflare Usage. |\n\n## Usage And Quota\n\nThe Cloudflare Usage page is configured from the app UI and stored in D1. These fields are not deployment variables in `wrangler.jsonc`.\n\n### Required Cloudflare fields in the app\n\n| UI field | Required | Value |\n| --- | --- | --- |\n| `Account ID` | Yes | Cloudflare account ID that owns the Worker and D1 database. |\n| `API token` | Yes | Cloudflare API token used only for reading usage data. Required permissions are `Account Analytics Read` and `D1 Read`. |\n| `Worker name` | Optional for account total, required for current-Worker usage | Worker script name, usually the `name` from `wrangler.jsonc`, for example `edge-gist`. If blank or wrong, account total usage can still load, but the `This Worker requests` value will be unavailable or zero. |\n| `D1 database ID` | Yes for D1 usage | D1 database UUID. The database name is not accepted here. |\n| `Workers plan` | Yes | Select `Free` or `Paid` so the UI can use the correct Workers request quota. |\n| `D1 plan` | Yes | Select `Free` or `Paid` so the UI can use the correct D1 row\u002Fstorage quotas. |\n\n### What the Usage page shows\n\n| Section | Meaning |\n| --- | --- |\n| Workers requests | Account-level Workers request quota usage for the displayed usage window. The total includes Workers script invocations and legacy Pages Functions invocations when Cloudflare reports them, because Cloudflare's quota dashboard can include both. |\n| This Worker requests | Requests for the configured `Worker name` only. This is useful for seeing how much of the account total came from this app. |\n| Workers account requests | Account-level Workers script invocations. |\n| Pages Functions requests | Legacy Pages Functions requests, shown only when Cloudflare reports a non-zero value. |\n| Errors | Combined error count for the Workers\u002FPages Functions usage window. |\n| D1 database usage | Read queries, write queries, rows read, rows written, and database size for the configured D1 database ID. |\n\nUsage windows are displayed with the browser's `toLocaleString()` formatting. The Usage page caches the last successful response in D1 so the dashboard can still show recent data if Cloudflare's API is temporarily unavailable.\n\nCloudflare analytics can lag behind real time. Small differences from the Cloudflare dashboard are expected when the dashboard and the app use slightly different aggregation windows or when Cloudflare has not finished processing the latest data.\n\n### Cloudflare Workers limits that affect EdgeGist\n\nLimits below were checked against Cloudflare's official docs on 2026-05-11. Cloudflare can change these values; use the linked docs as the source of truth: [Workers limits](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fworkers\u002Fplatform\u002Flimits\u002F), [Workers pricing](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fworkers\u002Fplatform\u002Fpricing\u002F), [Workers Static Assets billing](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fworkers\u002Fstatic-assets\u002Fbilling-and-limitations\u002F), [D1 limits](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fd1\u002Fplatform\u002Flimits\u002F), and [D1 pricing](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fd1\u002Fplatform\u002Fpricing\u002F).\n\n| Workers limit | Free | Paid \u002F Standard | EdgeGist consequence |\n| --- | --- | --- | --- |\n| Worker requests | 100,000\u002Fday | No fixed hard limit; Standard includes 10 million\u002Fmonth, then overage billing applies | Dynamic routes such as API calls, `\u002F\u003Cowner>`, gist detail pages, login, imports, exports, and usage refreshes count as Worker requests. When the Free daily limit is exhausted, dynamic routes can fail. Static asset requests are free and unlimited when they do not invoke the Worker; this project routes `\u002Fstatic\u002F*`, `\u002Ficons\u002F*`, and `\u002Fscreenshots\u002F*` directly to Assets. |\n| CPU time per HTTP request | 10 ms | 30 seconds by default, configurable up to 5 minutes | Free-plan deployments should stay personal and light. Large searches, imports, exports, syntax payload preparation, or huge history reads may need Workers Paid even when D1 storage is still available. |\n| Memory per isolate | 128 MB | 128 MB | Very large request\u002Fresponse bodies, full export\u002Fimport payloads, or many large files in one view can exhaust runtime memory before D1 storage is full. |\n| Subrequests per invocation | 50 | 10,000 | D1 operations and external Cloudflare API calls consume the same per-request budget. EdgeGist chunks D1 writes around the 100-parameter limit, but one API call still cannot perform unlimited D1 work. |\n| Simultaneous outgoing connections per invocation | 6 | 6 | The Cloudflare Usage page makes only a small number of parallel Cloudflare API calls. Avoid adding fan-out behavior that waits on more than six new outbound connections at once. |\n| URL size | 16 KB | 16 KB | Raw URLs include owner, gist id, optional revision sha, and filename. Extremely long filenames or query strings can be rejected before reaching EdgeGist. |\n| Request headers \u002F response headers | 128 KB total \u002F 128 KB total | 128 KB total \u002F 128 KB total | Owner tokens and cookies must stay small. Do not add large metadata to headers. |\n| Request body size | Cloudflare account Free\u002FPro: 100 MB; Business: 200 MB; Enterprise: 500 MB by default | Same Cloudflare account-plan limits | This is the outer upload\u002Fimport ceiling. EdgeGist's per-file D1 limit is much smaller, so a request can be under Cloudflare's body limit and still fail validation. |\n| Response body size | No Worker-enforced limit; CDN cache object limits still apply: 512 MB on Free\u002FPro\u002FBusiness accounts and 5 GB on Enterprise | Same Cloudflare account-plan limits | Large exports can stream back through Workers, but practical CPU, memory, client connection, and D1 query limits still apply. |\n| Incoming HTTP wall time | No hard limit while the client remains connected; `waitUntil()` can extend work up to 30 seconds after disconnect | Same | Long imports\u002Fexports\u002Fsearches depend on the client connection staying open. EdgeGist does not have a background queue, so do not rely on a request continuing indefinitely after the client disconnects. |\n| Environment variables | 64 variables\u002FWorker, 5 KB each | 128 variables\u002FWorker, 5 KB each | Keep `vars` limited to owner auth, base URL, retention, and optional Turnstile. Do not store large configuration blobs or token lists in Worker variables. |\n| Worker bundle size | 3 MB gzip, 64 MB uncompressed | 10 MB gzip, 64 MB uncompressed | The generated `dist\u002F_worker.js` must stay small enough for the target plan. Large libraries should remain in static assets or be avoided. |\n| Startup time | 1 second | 1 second | Avoid expensive global-scope initialization; deployment can fail if the Worker cannot parse and initialize quickly. |\n| Workers per account | 100 | 500 | EdgeGist normally uses one Worker. This only matters when the same Cloudflare account hosts many Workers. |\n| Cron Triggers per account | 5 | 250 | EdgeGist does not require Cron Triggers. Future background jobs would consume this account-level quota. |\n| Routes and custom domains | 1,000 routes\u002Fzone, 100 custom domains\u002Fzone, 1,000 routed zones\u002FWorker | Same | A normal EdgeGist deployment uses one custom domain or one route. Very large multi-domain setups should use wildcard routes or a different architecture. |\n| Cache API | 512 MB max object, 50 calls\u002Frequest | 512 MB max object, 1,000 calls\u002Frequest | EdgeGist does not require Cache API for correctness. Do not rely on Cache API as primary gist storage. |\n| Logs | 256 KB log data\u002Frequest | 256 KB log data\u002Frequest | Do not log gist file contents, import\u002Fexport payloads, owner tokens, or Cloudflare API responses. Logs can be truncated and may expose secrets. |\n| Static Assets | 20,000 files\u002FWorker version, 25 MiB\u002Ffile, 100 `_headers` rules, 2,000 characters per `_headers` line, 2,000 static `_redirects`, 100 dynamic `_redirects`, 2,100 redirects total, 1,000 characters per redirect rule | 100,000 files\u002FWorker version, same per-file\u002Fheader\u002Fredirect limits | The built `dist\u002F` assets and release package must stay below these limits. A single generated client asset over 25 MiB cannot be uploaded as a Worker asset. EdgeGist does not currently depend on `_redirects`. |\n| Legacy Bundled \u002F Unbound usage models | Deprecated for new accounts | Deprecated for new accounts | If an old account still uses Bundled, expect Paid-like limits except Bundled keeps 50 subrequests\u002Frequest, 50 ms HTTP CPU, 50 ms Cron CPU, and 50 Cache API calls\u002Frequest. Move to Standard when possible. |\n\n### Cloudflare D1 limits that affect EdgeGist\n\n| D1 limit | Free | Paid | EdgeGist consequence |\n| --- | --- | --- | --- |\n| Databases per account | 10 | 50,000 | EdgeGist needs one D1 database. Extra staging or per-tenant databases count against the same account quota. |\n| Maximum database size | 500 MB | 10 GB | EdgeGist stores current files, retained revision snapshots, settings, and usage cache in D1. Large files and high `EDGEGIST_HISTORY_MAX_VERSIONS` values consume this quickly. The paid 10 GB per-database hard limit cannot be increased. |\n| Maximum storage per account | 5 GB | 1 TB | Multiple D1 databases share this account-level cap. On Paid, D1 billing includes the first 5 GB and then charges for additional stored GB-months. |\n| Rows read \u002F rows written included usage | 5 million rows read\u002Fday; 100,000 rows written\u002Fday | First 25 billion rows read\u002Fmonth and 50 million rows written\u002Fmonth included, then overage billing | Listing, searching, importing, exporting, editing, retention cleanup, and usage-cache writes all consume D1 rows. D1 counts rows scanned, not just rows returned, so content search can consume many reads when the database grows. |\n| Time Travel duration | 7 days | 30 days | D1 Time Travel is a database backup window, not the same as EdgeGist retained revisions. EdgeGist history is controlled by `EDGEGIST_HISTORY_MAX_VERSIONS`. |\n| Time Travel restore rate | 10 restores \u002F 10 minutes \u002F database | Same | Disaster recovery via D1 restore is rate-limited. |\n| Queries per Worker invocation | 50 | 1,000 | One API request cannot issue unlimited D1 queries. Large import\u002Fexport\u002Fhistory operations should be kept bounded and may require Workers Paid. |\n| Columns per table | 100 | 100 | Current EdgeGist tables are far below this. Future schema changes should avoid wide metadata tables. |\n| Rows per table | Unlimited, subject to storage | Unlimited, subject to storage | Row count is not the direct cap; storage, query duration, row reads, and single-database throughput become the practical limits. |\n| String, `BLOB`, or table row size | 2,000,000 bytes | 2,000,000 bytes | EdgeGist rejects file content larger than 2,000,000 bytes. Because D1 also caps full row size, keep individual files comfortably below 2 MB when filenames or metadata are large. |\n| SQL statement length | 100 KB | 100 KB | Generated queries and migrations must stay compact. EdgeGist chunks multi-row statements instead of generating one huge statement. |\n| Bound parameters per query | 100 | 100 | EdgeGist chunks file\u002Fversion inserts and deletes to stay under this D1 cap. Requests with many files can still require many D1 statements. |\n| SQL function arguments | 32 | 32 | Avoid future features that generate large variadic SQL functions. |\n| `LIKE` \u002F `GLOB` pattern length | 50 bytes | 50 bytes | EdgeGist search uses `LIKE '%query%'`; keep search terms short. Escaping and the surrounding `%` characters also count toward the pattern. |\n| D1 bindings per Worker script | Approximately 5,000 | Approximately 5,000 | EdgeGist uses one binding named `DB`. This is not a practical limit unless the architecture changes to many bound databases. |\n| SQL query duration | 30 seconds | 30 seconds | Full content scans, large exports, and retention cleanup must finish quickly. If they do not, split the work or reduce stored history\u002Fdata size. |\n| `wrangler d1 execute` import file size | 5 GB | 5 GB | Manual migration\u002Fimport files run through Wrangler must stay below 5 GB. EdgeGist release migrations are expected to be much smaller. |\n| Batch statements | Individual query limits still apply to each statement inside a batch | Same | A batch does not bypass the 100-parameter, 100 KB SQL, row-size, or query-duration limits. |\n| Per-database concurrency | One D1 database processes queries one at a time and queues excess work | Same | EdgeGist is best suited for personal or low-concurrency single-owner usage. Heavy concurrent edits, imports, exports, or content searches can queue and eventually return D1 overloaded errors. |\n\n> [!NOTE]\n> For Gist-compatible sync tools that do not need full GitHub response metadata, use the `\u002Flite` API prefix to reduce response size and avoid hydrating response history. For example, if a client currently points at `https:\u002F\u002Fapi.example.com`, configure it to use `https:\u002F\u002Fapi.example.com\u002Flite`. The same Gist paths continue to work under the prefix.\n>\n> If you are close to D1 storage, row-write, or Worker CPU limits and do not need EdgeGist retained revisions, set `EDGEGIST_HISTORY_MAX_VERSIONS` to `0`. With `0`, EdgeGist does not record history versions for new create\u002Fupdate requests.\n\n### Practical EdgeGist limits\n\n- Keep each file below 2 MB. The app enforces `2,000,000` bytes per file content because D1 caps strings and rows at that size.\n- Keep search terms short. D1 caps `LIKE` patterns at 50 bytes, and EdgeGist wraps the search query in `%...%`.\n- Keep the retained history count intentional. Every retained version stores file snapshots in D1, so storage grows with `file size * retained versions`, plus current files and change metadata. Set `EDGEGIST_HISTORY_MAX_VERSIONS=0` only when you intentionally want new writes to skip EdgeGist history recording.\n- On Workers Free, treat EdgeGist as a personal deployment: 100,000 dynamic Worker requests\u002Fday, 10 ms CPU\u002Frequest, 50 subrequests\u002Frequest, 500 MB maximum D1 database size, 5 million rows read\u002Fday, and 100,000 rows written\u002Fday.\n- On Workers Paid, the important ceilings become cost and per-database scale: 10 million Worker requests\u002Fmonth included, 30 million CPU milliseconds\u002Fmonth included, 10 GB maximum D1 database size, 25 billion rows read\u002Fmonth included, and 50 million rows written\u002Fmonth included.\n- On D1 Free, exceeding daily read\u002Fwrite limits stops D1 queries until the daily reset; hitting the storage limit requires deleting data or upgrading before new writes\u002Fschema changes can continue. On D1 Paid, usage above included reads, writes, or storage is billed.\n- Static assets are not the same as dynamic app\u002FAPI routes. `\u002Fstatic\u002F*`, `\u002Ficons\u002F*`, and `\u002Fscreenshots\u002F*` are served as assets, but the API and owner pages invoke the Worker and count toward Worker limits.\n- EdgeGist stores data only in D1. It does not use R2\u002FKV for large object storage, so it is not a good fit for binary blobs, huge archives, or Git-style repository transport.\n\n## Updating\n\n### Git-based deployment\n\n1. Pull the latest source.\n2. Run `bun install` if dependencies changed.\n3. Review `wrangler.example.jsonc` for newly added fields and copy them into your private `wrangler.jsonc` without overwriting real credentials or IDs.\n4. Run any new D1 migrations with `bun run db:migrate:remote`.\n5. Run `bun run build`.\n6. Deploy with `bun run deploy`.\n\n### Release-package deployment\n\n1. Download and unzip the latest `edgegist-package.zip`.\n2. Copy your existing `wrangler.jsonc` into the extracted package.\n3. Review `wrangler.example.jsonc` for newly added fields and add missing values to your copied config.\n4. Run any new SQL files in `migrations\u002F` against your D1 database.\n5. Deploy with `wrangler deploy` or `npx wrangler@^4 deploy`.\n\nAfter updates that touch Cloudflare usage, open Settings -> Cloudflare Usage and confirm the saved `Account ID`, `Worker name`, `D1 database ID`, and plan selections still match your Cloudflare account.\n\n## Cloudflare Account API Token For Deployment\n\nFor local or CI deployment with `CLOUDFLARE_API_TOKEN`, create a Cloudflare API token scoped to the target account.\n\nMinimum permissions for the deploy flow:\n\n| Permission | Why |\n| --- | --- |\n| `Workers Scripts Edit` | Upload and update the Worker script and assets with `wrangler deploy`. |\n| `D1 Edit` | Create D1 databases and apply remote D1 migrations when using Wrangler commands. |\n\nIf the same token is also used in the app's Cloudflare Usage settings, add:\n\n| Permission | Why |\n| --- | --- |\n| `Account Analytics Read` | Read Workers and D1 analytics through Cloudflare GraphQL. |\n| `D1 Read` | Read D1 database metadata, including database size. |\n\nFor custom domains configured through `routes`, the token must also be allowed to operate on the Cloudflare zone that owns the hostname. If that fails, deploy the Worker without `routes` and add the custom domain manually in the Cloudflare dashboard.\n\nRecommended CI secrets:\n\n| Secret | Value |\n| --- | --- |\n| `CLOUDFLARE_API_TOKEN` | Deployment token. |\n| `CLOUDFLARE_ACCOUNT_ID` | Target Cloudflare account ID. |\n\nDo not put Cloudflare API tokens in `wrangler.jsonc`, README files, or committed source files.\n## GitHub Releases\n\nBump `package.json` version and merge or push that change to the default branch to cut a release. The Release workflow can also be run manually from GitHub Actions.\n\nThe workflow reads `package.json` for the package name and version, uses `v${version}` as the release tag, fails if that release already exists or if the tag points at a different commit, then runs tests, builds, packages, generates conventional release notes, creates the tag when needed, and publishes:\n\n- `edgegist-package.zip`: README files, migrations, example config, prebuilt Worker script, and static assets.\n- `SHA256SUMS`: checksums for release assets.\n\n## API Compatibility\n\nSupported GitHub Gist-compatible REST surface:\n\n- `GET \u002Fgists`, `GET \u002Fgists\u002Fpublic`, and `GET \u002Fusers\u002F{username}\u002Fgists`.\n- `POST \u002Fgists`, `GET \u002Fgists\u002F{gist_id}`, `PATCH \u002Fgists\u002F{gist_id}`, and `DELETE \u002Fgists\u002F{gist_id}`.\n- `GET \u002Fgists\u002F{gist_id}\u002Fcommits` and `GET \u002Fgists\u002F{gist_id}\u002F{sha}` for retained file revisions.\n- Current raw files through `GET \u002Fgists\u002F{gist_id}\u002Fraw\u002F{filename}` and GitHub-style `GET \u002F{owner}\u002F{gist_id}\u002Fraw\u002F{filename}`.\n- Retained raw files through `GET \u002Fgists\u002F{gist_id}\u002Fraw\u002F{sha}\u002F{filename}` and GitHub-style `GET \u002F{owner}\u002F{gist_id}\u002Fraw\u002F{sha}\u002F{filename}`.\n- Star endpoints: `GET \u002Fgists\u002Fstarred`, `GET \u002Fgists\u002F{gist_id}\u002Fstar`, `PUT \u002Fgists\u002F{gist_id}\u002Fstar`, and `DELETE \u002Fgists\u002F{gist_id}\u002Fstar`.\n\nCompatibility mocks:\n\n- `GET \u002Fgists\u002F{gist_id}\u002Fcomments`, comment mutation endpoints, and fork endpoints exist for client compatibility, but return empty or no-op responses.\n\nUnsupported:\n\n- Git transport (`git clone`, `git push`, `git pull` against a gist repository) is not implemented.\n\n## Related Projects\n\n- [LiteGist](https:\u002F\u002Fgithub.com\u002Flockcp\u002FLiteGist)\n\n  > LiteGist is an extremely lightweight, experience-focused personal standalone pastebin service. Designed with a full-screen editor philosophy, it supports Markdown rendering, code highlighting, password protection, Gist-compatible multi-file management, and PWA support. It aims to provide you with a high-performance \"Private Gist\" sharing experience.\n","EdgeGist 是一个与 GitHub Gist 兼容的 API 服务，运行在 Cloudflare 的边缘网络上，并使用 D1 数据库作为后端。项目采用 TypeScript 编写，通过 Cloudflare Workers 部署，支持静态资源托管。其核心功能包括提供与 GitHub Gist 相同的 API 接口，用户可以配置自己的基础 URL 并设置拥有者令牌以实现个性化部署。此外，EdgeGist 还提供了一个单所有者的 Web 界面，用于浏览、编辑、导入导出 Gists 以及监控 Cloudflare 使用情况。此项目特别适合需要在边缘计算环境中快速部署和管理代码片段分享服务的场景，同时也能很好地与 Sub-Store 的 Gist 分享和备份功能集成。",2,"2026-06-11 04:00:50","CREATED_QUERY"]