void 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{account-cmd-CdUll_35.mjs → account-cmd-B4i71m7k.mjs} +3 -3
- package/dist/{application-analysis-fEqoSeMp.mjs → application-analysis-DYdVoMAY.mjs} +8 -6
- package/dist/{application-routing-DAhNP4u3.mjs → application-routing-C8EqRDfv.mjs} +3 -1
- package/dist/{auth-link-C87vGXM-.mjs → auth-link-BKwHPbdv.mjs} +3 -3
- package/dist/{auth-router-6I81r-ix.mjs → auth-router-DqLx8E0F.mjs} +2 -2
- package/dist/{better-auth-shared-D-6z0q0f.mjs → better-auth-shared-7FM3Uk2N.mjs} +48 -2
- package/dist/{build-cmd-ex1g-j_e.mjs → build-cmd-Dy4psnt1.mjs} +1 -1
- package/dist/{cache-oHzrnY3z.mjs → cache-DV37S6dl.mjs} +1 -1
- package/dist/{cancel-deploy-viAMD0Q_.mjs → cancel-deploy-DMPyu1Vt.mjs} +1 -1
- package/dist/{cf-build-output-BhVxldCw.mjs → cf-build-output-Bapzc_8Z.mjs} +102 -1
- package/dist/cf-build-output-DlBkWdNU.mjs +1 -0
- package/dist/cli/cli.mjs +41 -32
- package/dist/cli/cloudflare-operation-process.mjs +27 -6
- package/dist/cli/env-schema-probe.mjs +2 -2
- package/dist/{client-BEkdj1kx.mjs → client-BEMtySaV.mjs} +8 -1
- package/dist/client-DyxPQnF_.mjs +1 -0
- package/dist/{cloudflare-cmd-DMzCv12s.mjs → cloudflare-cmd-BaqIigwI.mjs} +1 -1
- package/dist/{cloudflare-connect-CCRTVg-I.mjs → cloudflare-connect-pNLQmr_e.mjs} +1 -1
- package/dist/{cloudflare-operations-DsSaw2BT.mjs → cloudflare-operations-PCM4wucv.mjs} +1 -1
- package/dist/{cloudflare-operations-Dh9dEHJj.mjs → cloudflare-operations-vP3b5CQf.mjs} +3 -3
- package/dist/config-BTu1o3Dc.mjs +49 -0
- package/dist/{config-DQfHOKh9.d.mts → config-Du88-7Y8.d.mts} +2 -1
- package/dist/config-entry.d.mts +1 -1
- package/dist/{connect-k7R2rPVA.mjs → connect-DwVsBvVh.mjs} +4 -4
- package/dist/{create-project-DTuy-l0z.mjs → create-project-CgrnvzyA.mjs} +1 -1
- package/dist/{create-project-DV71ol5d.mjs → create-project-CjzbBlTv.mjs} +1 -1
- package/dist/{db-DVOkLH5X.mjs → db-rvo64snS.mjs} +13 -13
- package/dist/{delete-BbP5eopu.mjs → delete-DWX6dP4M.mjs} +1 -1
- package/dist/deploy-CtHE2A3R.mjs +2 -0
- package/dist/{deploy-7H32EnUs.mjs → deploy-hRPvw8ci.mjs} +229 -69
- package/dist/{deploy-managed-static-DlvcvnQT.mjs → deploy-managed-static-CDYA2p3L.mjs} +30 -14
- package/dist/{deploy-transport-BzGEj0Ge.mjs → deploy-transport-JCxw7QDi.mjs} +3 -3
- package/dist/deploy.mjs +4 -3
- package/dist/{dispatch-DoUnxTNo.mjs → dispatch-OVLeWW8K.mjs} +71 -2
- package/dist/{domain-CA2wBgra.mjs → domain-CH5iBD9Y.mjs} +2 -2
- package/dist/{email-DXpCc77w.mjs → email-CbTO1l78.mjs} +58 -9
- package/dist/{email-cloudflare-D03g2TkS.mjs → email-cloudflare-B8-pq_-W.mjs} +106 -5
- package/dist/{env-DgOQ9_8S.mjs → env-BBdw236G.mjs} +3 -3
- package/dist/{env-BJjULvL0.mjs → env-BaMz0j7H.mjs} +6 -1
- package/dist/{env-validation-BmsdCZEk.mjs → env-validation-BfBVvBNe.mjs} +1 -1
- package/dist/{env-validation-Dwq3PJzK.mjs → env-validation-XcukUlFM.mjs} +1 -1
- package/dist/{gen-BfmcIi2g.mjs → gen-CCx3t2Ju.mjs} +1 -1
- package/dist/gen-TgXy93Rr.mjs +1 -0
- package/dist/{github-cmd-CY9LUqLh.mjs → github-cmd-BzkRLkbg.mjs} +1 -1
- package/dist/{handler-BbkSPzGF.d.mts → handler-CQv5YDS0.d.mts} +1 -1
- package/dist/{help-CxSlPpzJ.mjs → help-CKbrhZha.mjs} +16 -1
- package/dist/help-qOZr3wng.mjs +1 -0
- package/dist/index.d.mts +2 -4
- package/dist/index.mjs +317 -122
- package/dist/{info-7VDlhtyy.mjs → info-oBm8yK8J.mjs} +2 -2
- package/dist/{init-DxYyOs4-.mjs → init-CBp_uMve.mjs} +217 -197
- package/dist/{link-Ddmod1dZ.mjs → link-Dt_QXmM4.mjs} +2 -2
- package/dist/{list-MogaugiO.mjs → list-DF6asTdt.mjs} +2 -2
- package/dist/{live-Bt8Y_0bg.d.mts → live-CXJ9yC98.d.mts} +1 -1
- package/dist/{local-d1-BHBnKWRb.mjs → local-d1-CUhH3qtj.mjs} +1 -1
- package/dist/{login-Cttnagmm.mjs → login-BjSyhXlT.mjs} +2 -2
- package/dist/login-Cdo8Aco1.mjs +1 -0
- package/dist/{logs-CAszUWZI.mjs → logs-DmvBl6xA.mjs} +2 -2
- package/dist/{migrate-DyT2jJ8y.mjs → migrate-BXRHpYwR.mjs} +1 -1
- package/dist/migrate-COPncCGx.mjs +1 -0
- package/dist/nitro.mjs +2 -2
- package/dist/{node-WB7c2N4L.mjs → node-DtD7ZiPf.mjs} +4 -4
- package/dist/{operator-args-UnE8Y2eK.mjs → operator-args-BsJIYXaT.mjs} +8 -1
- package/dist/operator-cmd-DDiX7Y3f.mjs +1 -0
- package/dist/{operator-cmd-skshjMGl.mjs → operator-cmd-DjZKGGgN.mjs} +4 -3
- package/dist/pages/client.d.mts +17 -4
- package/dist/pages/client.mjs +161 -29
- package/dist/pages/index.d.mts +18 -2
- package/dist/pages/index.mjs +2 -52
- package/dist/pages/islands-plugin.d.mts +1 -1
- package/dist/pages/islands-plugin.mjs +9 -3
- package/dist/pages/protocol.d.mts +2 -2
- package/dist/platform-args-BC8_hsDj.mjs +1 -0
- package/dist/{platform-args-DVbVClZl.mjs → platform-args-BMD2gjc_.mjs} +1 -1
- package/dist/{platform-auth-config-C-pm5Qzx.mjs → platform-auth-config-BgnuMbml.mjs} +3 -3
- package/dist/{platform-auth-protection-SHhvim1L.mjs → platform-auth-protection-NjRejttU.mjs} +2 -2
- package/dist/{platform-auth-recovery-DXVWs-wA.mjs → platform-auth-recovery-Ug12d9W5.mjs} +2 -2
- package/dist/{platform-cmd-PiNRkopU.mjs → platform-cmd-BYKLMPCn.mjs} +1 -1
- package/dist/{platform-cmd-D5I1beUX.mjs → platform-cmd-HByuZAO7.mjs} +4 -4
- package/dist/{platform-domain-jpmBfZeb.mjs → platform-domain-DLSQ8GF8.mjs} +2 -2
- package/dist/{platform-lifecycle-DyKmuNY6.mjs → platform-lifecycle-3dwFJmnq.mjs} +102 -40
- package/dist/{platform-lifecycle-DH3Yr1qQ.mjs → platform-lifecycle-BRqcxerG.mjs} +1 -1
- package/dist/{platform-management-7m-PLaPJ.mjs → platform-management-ZK6XcbCo.mjs} +1 -1
- package/dist/{platform-management--vDwSn35.mjs → platform-management-ZZ6bQM5j.mjs} +1 -1
- package/dist/{platform-recovery-BiNpZZab.mjs → platform-recovery-8vNE5dNN.mjs} +2 -2
- package/dist/{plugin-inference-DUjCd55H.mjs → plugin-inference-CeiB6BG5.mjs} +53 -18
- package/dist/{prepare-BcAE5Sdj.mjs → prepare-5BR7Splm.mjs} +1 -1
- package/dist/prepare-B3VrSF1Z.mjs +1 -0
- package/dist/{prepare-DA-17DhO.mjs → prepare-Dseww74N.mjs} +4 -4
- package/dist/{prerender-render-Cijv7UFo.mjs → prerender-render-CaShbHJ3.mjs} +13 -1
- package/dist/prerender-render.d.mts +1 -0
- package/dist/prerender-render.mjs +1 -1
- package/dist/{project-cmd-DA4K5faU.mjs → project-cmd-CjLNNxwe.mjs} +15 -15
- package/dist/{project-team-CQBNveX3.mjs → project-team-DE5drjcl.mjs} +1 -1
- package/dist/{project-token-CCalFyQf.mjs → project-token-XEYEXfiJ.mjs} +1 -1
- package/dist/{project-zero-trust-DPnkiEuT.mjs → project-zero-trust-6VPU8zmV.mjs} +1 -1
- package/dist/{protocol-BBDpJz3G.d.mts → protocol-BekmFxCI.d.mts} +2 -2
- package/dist/{provision-DtROpNNI.mjs → provision-B7UFrBH7.mjs} +1 -1
- package/dist/{provision-BFLCrnUp.mjs → provision-Chq0NRAV.mjs} +1 -1
- package/dist/{requests-BpH0kImL.mjs → requests-CG-Pj8ze.mjs} +1 -1
- package/dist/{rollback-CXS1tAa-.mjs → rollback-CIrm2txZ.mjs} +2 -2
- package/dist/{route-types-BZi26DVS.mjs → route-types--PAOzHlH.mjs} +49 -28
- package/dist/routes-stub.d.mts +22 -13
- package/dist/runtime/better-auth-mysql.mjs +1 -1
- package/dist/runtime/better-auth-pg.mjs +1 -1
- package/dist/runtime/better-auth.mjs +1 -1
- package/dist/runtime/handler.d.mts +1 -1
- package/dist/runtime/live-client.d.mts +1 -1
- package/dist/runtime/live.d.mts +1 -1
- package/dist/runtime/log.mjs +19 -1
- package/dist/runtime/routing.d.mts +2 -2
- package/dist/runtime/routing.mjs +4 -3
- package/dist/runtime/validator.d.mts +1 -1
- package/dist/runtime/ws-server.d.mts +1 -1
- package/dist/runtime/ws.d.mts +1 -1
- package/dist/{scan-8IzYXiU_.mjs → scan-N9WzdcAl.mjs} +302 -23
- package/dist/{secret-BbXHfLAt.mjs → secret-BL6g7mF5.mjs} +3 -3
- package/dist/static-asset-marker-Btf-6oCD.mjs +32 -0
- package/dist/{types-7AIz0OZu.d.mts → types-w72dljaA.d.mts} +6 -1
- package/dist/{version-metadata-tS39iW09.mjs → version-metadata-DmaZNTYW.mjs} +1 -1
- package/dist/worker-conditions-AWFRcFxR.mjs +11 -0
- package/dist/{wrangler-F96NnLyb.mjs → wrangler-CDiEBCdS.mjs} +2 -1
- package/dist/wrangler-DOPr4jyI.mjs +1 -0
- package/dist/{wrapper-D3sZyh2C.mjs → wrapper-CEiDeCqD.mjs} +6 -10
- package/dist/{ws-BH-im_tX.d.mts → ws-D1zWiB_P.d.mts} +1 -1
- package/docs/guide/auth.md +3 -1
- package/docs/guide/edge/static-assets.md +10 -0
- package/docs/guide/email/domains.md +1 -1
- package/docs/guide/email/receiving.md +2 -0
- package/docs/guide/email/sending.md +1 -1
- package/docs/guide/pages-routing/actions-and-forms.md +19 -13
- package/docs/guide/pages-routing/markdown.md +2 -0
- package/docs/guide/pages-routing/overview.md +33 -2
- package/docs/guide/platform/administration/email.md +18 -0
- package/docs/guide/platform/installation/prerequisites.md +9 -4
- package/docs/guide/platform/installation/uninstall.md +2 -0
- package/docs/guide/queues.md +13 -1
- package/docs/guide/server-routing.md +38 -2
- package/docs/guide/type-safety.md +7 -7
- package/docs/guide/websockets.md +4 -2
- package/docs/reference/api/pages.md +4 -4
- package/docs/reference/cli/email.md +13 -0
- package/docs/reference/cli/platform-email.md +2 -2
- package/docs/reference/cli.md +1 -0
- package/docs/reference/structure.md +3 -1
- package/package.json +8 -7
- package/templates/assets/icons.svg +49 -0
- package/templates/assets/react.svg +1 -0
- package/templates/assets/solid.svg +1 -0
- package/templates/assets/stack.svg +35 -0
- package/templates/assets/svelte.svg +1 -0
- package/templates/assets/void.svg +5 -0
- package/templates/assets/vue.svg +1 -0
- package/dist/cf-build-output-BJHuSsVN.mjs +0 -1
- package/dist/client-BiRDZTSd.mjs +0 -1
- package/dist/config-CyUJzi3q.mjs +0 -32
- package/dist/deploy-CE0Ev0Ob.mjs +0 -2
- package/dist/gen-NrOlWZaG.mjs +0 -1
- package/dist/help-BeAFFE5C.mjs +0 -1
- package/dist/login-DgDwlKc_.mjs +0 -1
- package/dist/migrate-n49ZeS1X.mjs +0 -1
- package/dist/operator-cmd-d2MvJOrl.mjs +0 -1
- package/dist/parse-filename-DioPHiR9.mjs +0 -77
- package/dist/platform-args-kQrgxScY.mjs +0 -1
- package/dist/prepare-C4I0f--h.mjs +0 -1
- package/dist/wrangler-BLb3_luP.mjs +0 -1
|
@@ -84,7 +84,7 @@ You can send to at most 50 recipients across `to`, `cc`, and `bcc`. Use standard
|
|
|
84
84
|
|
|
85
85
|
`Address` accepts either a string (`"hello@acme.dev"` or `"Name <hello@acme.dev>"`) or an object (`{ email, name? }`). Unicode display names are supported.
|
|
86
86
|
|
|
87
|
-
On the platform the sender is pinned to your project. `from` must be your project's own platform address — `<project-slug>@<mail-domain>` or `<project-slug>+<tag>@<mail-domain>`, optionally with a display name — or any address on a domain registered with `void email domain add` (see [Your own domain on the platform](./domains.md#your-own-domain-on-the-platform)). For example, if your platform's mail domain is `mail.example.com`, you can use `Acme <acme+noreply@mail.example.com>`. Anything else is rejected with `INVALID_FROM`. Omit `from` and Void fills in `<project-slug>+noreply@<mail-domain>` for you.
|
|
87
|
+
On the platform the sender is pinned to your project. `from` must be your project's own platform address — `<project-slug>@<mail-domain>` or `<project-slug>+<tag>@<mail-domain>`, optionally with a display name — or any address on a domain registered with `void email domain add` (see [Your own domain on the platform](./domains.md#your-own-domain-on-the-platform)). For example, if your platform's mail domain is `mail.example.com`, you can use `Acme <acme+noreply@mail.example.com>`. Anything else is rejected with `INVALID_FROM`. Omit `from` and Void fills in `<project-slug>+noreply@<mail-domain>` for you. If the project owner set an [email alias](../../reference/cli/email.md#void-email-alias), `<alias>@<mail-domain>` and `<alias>+<tag>@<mail-domain>` are accepted too; the default stays the slug address.
|
|
88
88
|
|
|
89
89
|
On your own Cloudflare account, `from` defaults to `email.from` from `void.config.ts` and must be on a domain your account can send from; Cloudflare rejects any other sender and `sendEmail` reports it as `INVALID_FROM`.
|
|
90
90
|
|
|
@@ -147,19 +147,23 @@ The types are inferred from your action's `withValidator()` schema in the compan
|
|
|
147
147
|
|
|
148
148
|
`useForm` returns a reactive object with:
|
|
149
149
|
|
|
150
|
-
| Property / Method | Purpose
|
|
151
|
-
| -------------------------------- |
|
|
152
|
-
| `data` / `setData` | Current form values, typed to match the action's body schema.
|
|
153
|
-
| `errors` | Field-level validation errors, keys typed to body field names.
|
|
154
|
-
| `error` | Action error, or `null`.
|
|
155
|
-
| `post`, `put`, `patch`, `delete` | Submit the form with that method. In React these are native form action callbacks.
|
|
156
|
-
| `pending` | `true` while the submission is in flight.
|
|
157
|
-
| `hasChanges` | `true` if form data differs from
|
|
158
|
-
| `wasSuccessful` | `true` after a successful submission. Stays `true` until the next submission.
|
|
159
|
-
| `recentlySuccessful` | `true` for 2 seconds after a successful submission. Useful for flash messages.
|
|
160
|
-
| `reset(...fields)` | Reset form data to
|
|
161
|
-
| `
|
|
162
|
-
| `
|
|
150
|
+
| Property / Method | Purpose |
|
|
151
|
+
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
152
|
+
| `data` / `setData` | Current form values, typed to match the action's body schema. |
|
|
153
|
+
| `errors` | Field-level validation errors, keys typed to body field names. |
|
|
154
|
+
| `error` | Action error, or `null`. |
|
|
155
|
+
| `post`, `put`, `patch`, `delete` | Submit the form with that method. In React these are native form action callbacks. |
|
|
156
|
+
| `pending` | `true` while the submission is in flight. |
|
|
157
|
+
| `hasChanges` | `true` if form data differs from the current defaults. After a successful submit, defaults update to the submitted values so `hasChanges` becomes `false`. |
|
|
158
|
+
| `wasSuccessful` | `true` after a successful submission. Stays `true` until the next submission. |
|
|
159
|
+
| `recentlySuccessful` | `true` for 2 seconds after a successful submission. Useful for flash messages. |
|
|
160
|
+
| `reset(...fields)` | Reset form data to the current defaults (last submitted values after success). Field names autocomplete. Pass `{ initial: true }` first to reset to the values passed to `useForm`. |
|
|
161
|
+
| `resetOnSuccess` (option) | When `true`, a successful submit resets both form data and defaults to the mount-time values. Useful for create forms that should clear after save. |
|
|
162
|
+
| `setError(field, message)` | Set a client-side validation error on a field. |
|
|
163
|
+
| `clearErrors(...fields)` | Clear validation errors. Field names autocomplete. |
|
|
164
|
+
| `clearError()` | Clear `error`. |
|
|
165
|
+
|
|
166
|
+
Use `setError()` to display errors from your own client-side validation. Setting an error preserves errors on other fields. Changing a field clears that field's validation error automatically (`setData` in React and Solid, or assigning to `data` in Vue and Svelte).
|
|
163
167
|
|
|
164
168
|
For a native React form, use `form.post`, `form.put`, `form.patch`, or `form.delete` as a native form action. For an awaitable mutation, use `action()`. In Vue, Svelte, and Solid, form submissions return `Promise<void>`; catch thrown errors or use your framework's error handling.
|
|
165
169
|
|
|
@@ -171,6 +175,8 @@ const form = useForm('/users/:id', { name: '' }, { params: { id: '42' } });
|
|
|
171
175
|
<Form form={form} method="put">{/* submits to /users/42 */}</Form>
|
|
172
176
|
```
|
|
173
177
|
|
|
178
|
+
Create forms often need empty fields again after a successful save. Either pass `resetOnSuccess: true` in the third argument, or call `form.reset({ initial: true })` when `form.wasSuccessful` becomes `true`.
|
|
179
|
+
|
|
174
180
|
## Named Actions
|
|
175
181
|
|
|
176
182
|
When a page needs multiple mutations, such as updating and deleting a user on the same page, export `actions` (plural) instead of `action`:
|
|
@@ -241,6 +241,8 @@ import pages from '@void/md/pages';
|
|
|
241
241
|
// [{ path: "/docs/getting-started", title: "Getting Started", frontmatter: {...}, headings: [...] }, ...]
|
|
242
242
|
```
|
|
243
243
|
|
|
244
|
+
The list holds only the pages for the current [environment](./overview.md#environment-only-pages): `.dev` pages on the dev server, `.prod` pages in builds. The suffix is dropped from `path`: `debug.dev.md` is listed as `/debug`.
|
|
245
|
+
|
|
244
246
|
Each entry has this shape:
|
|
245
247
|
|
|
246
248
|
```ts
|
|
@@ -94,13 +94,44 @@ Pages can be flat files such as `about.vue` or directory-based routes such as `a
|
|
|
94
94
|
|
|
95
95
|
<PagesFileTree />
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
Keep one file per route. Void reports an error when two page files resolve to the same path, such as `about.vue` and `about/index.vue`, or the same page in two route groups.
|
|
98
|
+
|
|
99
|
+
Each page can have a companion `.server.ts`, `.server.tsx`, `.server.js`, `.server.jsx`, `.server.mts`, or `.server.mjs` file that runs exclusively on the server. Use `.server.tsx` or `.server.jsx` when the companion contains JSX (see [JSX handlers](../server-routing.md#jsx-handlers) for the JSX runtime). A companion belongs to its matching page and does not create a separate route:
|
|
100
|
+
|
|
101
|
+
| File | Role |
|
|
102
|
+
| ---------------------- | ------------------------------ |
|
|
103
|
+
| `pages/foo.tsx` | Page component at `/foo` |
|
|
104
|
+
| `pages/foo.server.tsx` | Server companion for that page |
|
|
105
|
+
|
|
106
|
+
A companion can export:
|
|
98
107
|
|
|
99
108
|
- A [**loader**](./loaders), which runs on `GET` and returns the data that becomes the page component's props
|
|
100
109
|
- [**Actions**](./actions-and-forms), which handle mutations from forms and programmatic calls. Export a single `action` or multiple [named actions](./actions-and-forms#named-actions) when a page has several mutations
|
|
101
110
|
- `ssr = false` to opt a route out of server-rendered component HTML while keeping server loaders and client-side routing
|
|
102
111
|
|
|
103
|
-
File-based routing rules are the same as [server routing](../server-routing.md): `[param]` for dynamic segments, `[...param]` for catch-all, `(group)/` for route groups.
|
|
112
|
+
File-based routing rules are the same as [server routing](../server-routing.md): `[param]` for dynamic segments, `[...param]` for catch-all, `(group)/` for route groups, and a `.dev` or `.prod` suffix for [environment-only pages](#environment-only-pages).
|
|
113
|
+
|
|
114
|
+
### Environment-only pages
|
|
115
|
+
|
|
116
|
+
Add a `.dev` or `.prod` suffix before the extension to include a page in only one environment. The suffix is not part of the URL:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
pages/
|
|
120
|
+
about.tsx → /about, always
|
|
121
|
+
debug.dev.tsx → /debug, development only
|
|
122
|
+
stats.prod.tsx → /stats, production only
|
|
123
|
+
home.dev.tsx → /home in development
|
|
124
|
+
home.prod.tsx → /home in production
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`.dev` pages run only on the dev server; `.prod` pages run in builds, deployments, and production previews.
|
|
128
|
+
|
|
129
|
+
- A `.dev` page and a `.prod` page can share a URL. A plain page and a suffixed page for the same URL is an error wherever both run: `home.tsx` with `home.dev.tsx` fails on the dev server, and `home.tsx` with `home.prod.tsx` fails the build.
|
|
130
|
+
- The suffix goes **before** `.island`: `chart.dev.island.tsx` is a development-only island page. `chart.island.dev.tsx` is not an island page.
|
|
131
|
+
- A companion repeats the suffix: `home.dev.server.ts` belongs to `home.dev.tsx`.
|
|
132
|
+
- Suffixes apply to page files only. Layouts take no suffix.
|
|
133
|
+
- A resource used only by `.dev` pages and their companions is not provisioned for production.
|
|
134
|
+
- The dev server and builds generate types for their own environment. `void prepare` generates types for both.
|
|
104
135
|
|
|
105
136
|
## How Navigation Works
|
|
106
137
|
|
|
@@ -10,6 +10,8 @@ These commands apply to a platform that enables email. Every project can send fr
|
|
|
10
10
|
|
|
11
11
|
Shared inbound mail uses one exact routing rule per receiving project and enables Cloudflare’s per-zone plus addressing. Creating a project or deploying an app that only sends mail consumes no inbound rule. Existing rules and the apex catch-all remain in place. Cloudflare normally allows [200 routing rules per domain](https://developers.cloudflare.com/email-service/platform/limits/); remove unused rules or request a higher limit if the shared domain reaches capacity. An unrelated rule for a project’s base address or a plus address blocks its setup until that conflict is removed.
|
|
12
12
|
|
|
13
|
+
A project owner can give the shared address one permanent, shorter name with `void email alias <name>`. A receiving project with an alias uses two rules, one for the slug and one for the alias, because the slug address keeps working. Setting an alias is refused while a conflicting rule exists or the domain is at its rule limit, and the alias is released only when the project is deleted.
|
|
14
|
+
|
|
13
15
|
If recipient creation is interrupted, inspect its retained intent:
|
|
14
16
|
|
|
15
17
|
```sh
|
|
@@ -150,3 +152,19 @@ Run recovery with the same platform version that created the persisted intent.
|
|
|
150
152
|
`applied` continues without replaying the write; `not-applied` permits that exact
|
|
151
153
|
step to retry. Secret rotation keeps its staged generation, and removal resumes
|
|
152
154
|
from the unresolved cleanup step. Age alone never authorizes a retry.
|
|
155
|
+
|
|
156
|
+
### Sending from a domain that already receives mail
|
|
157
|
+
|
|
158
|
+
If an apex already receives mail with another provider, such as Microsoft 365 or Google Workspace, register it for sending only:
|
|
159
|
+
|
|
160
|
+
```sh
|
|
161
|
+
void platform email domain-add brand.example --project hr-portal --outbound-only
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The project can then send from any address on the apex, such as `support@brand.example`. It cannot receive mail there: the MX records, the apex SPF record, and incoming mail stay with the current provider.
|
|
165
|
+
|
|
166
|
+
- Cloudflare Email Sending must be available, which needs Workers Paid. Without it, setup is blocked instead of falling back to verified destinations only. Upgrade, then run `domain-sync`.
|
|
167
|
+
- Email Sending adds its own records on `cf-bounce.<domain>` and `_dmarc.<domain>`. If Cloudflare reports that existing records conflict with them, for example several DMARC records, setup is blocked and the error lists each record. Fix them, then run `domain-sync`.
|
|
168
|
+
- Only administrators can register an outbound-only domain, and it must be a zone apex. The platform's shared mail zone cannot be outbound-only.
|
|
169
|
+
- `domain-rotate-secret` does not apply. `domain-remove` stops sending through the platform and leaves the MX records alone.
|
|
170
|
+
- To switch an outbound-only domain to platform-managed inbound mail, or back, remove it first and add it again.
|
|
@@ -13,16 +13,17 @@ To run a Void platform, you need:
|
|
|
13
13
|
|
|
14
14
|
Void creates the Workers, storage, queues, routing, and database tables through the CLI.
|
|
15
15
|
|
|
16
|
-
Workers for Platforms requires
|
|
16
|
+
Workers for Platforms requires its own paid plan. Analytics Engine is available on Workers Free, but you must enable it for the account. Review [Analytics Engine pricing and allowances](https://developers.cloudflare.com/analytics/analytics-engine/pricing/). Your database and Cloudflare usage are billed separately.
|
|
17
17
|
|
|
18
18
|
## Prepare Your Cloudflare Account and Domain
|
|
19
19
|
|
|
20
20
|
In the [Cloudflare dashboard](https://dash.cloudflare.com/), select the account where you want the platform to live:
|
|
21
21
|
|
|
22
22
|
1. Open **Workers & Pages** once to let Cloudflare provision Workers for the account.
|
|
23
|
-
2. Open **Workers for Platforms** and enable its plan. Review [Workers for Platforms pricing](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/
|
|
23
|
+
2. Open **Workers for Platforms** and enable its plan. Review [Workers for Platforms pricing](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/reference/pricing/) before confirming.
|
|
24
24
|
3. Open **R2 Object Storage** and complete its activation. Void creates the bucket later.
|
|
25
|
-
4.
|
|
25
|
+
4. Open **Analytics Engine** and select **Enable** if prompted. You do not need to create a dataset manually. If the page has no **Enable** action and installation reports code 10089, follow the [steps below](#if-cloudflare-rejects-analytics-engine).
|
|
26
|
+
5. For a domain installation, choose a domain you own, such as `example.app`. If you need one, register it with your preferred registrar. Use a spare domain's root: apps will be served at `my-app.example.app`. For workers.dev testing, skip this step and the DNS setup below.
|
|
26
27
|
|
|
27
28
|
If that domain is already in this Cloudflare account, use its existing zone. Otherwise, [add the domain to Cloudflare](https://developers.cloudflare.com/dns/zone-setups/full-setup/setup/) and follow the nameserver instructions until the zone is active. A zone is Cloudflare's DNS configuration for a domain; creating one does not buy the domain.
|
|
28
29
|
|
|
@@ -30,7 +31,9 @@ You can also let Void create the zone during installation. Setting it up now let
|
|
|
30
31
|
|
|
31
32
|
### If Cloudflare rejects Analytics Engine
|
|
32
33
|
|
|
33
|
-
The platform stores request metrics and logs in Analytics Engine. If installation stops with "You need to enable Analytics Engine" (code 10089),
|
|
34
|
+
The platform stores request metrics and logs in Analytics Engine. If installation stops with "You need to enable Analytics Engine" (code 10089), open the Analytics Engine dashboard link in the error and select **Enable** for that account. Activation is available on Workers Free; this error does not require a Workers plan upgrade or a replacement API token. Then run the printed `void platform install --resume` command.
|
|
35
|
+
|
|
36
|
+
If your account's dashboard has no **Enable** action, create a disposable Hello World Worker, add an Analytics Engine binding under **Settings** > **Bindings**, and deploy it once from the dashboard. Then resume installation. Use a separate Worker for this step; leave the platform's own Workers unchanged.
|
|
34
37
|
|
|
35
38
|
## Install the CLI and Preview
|
|
36
39
|
|
|
@@ -82,6 +85,8 @@ Use any PostgreSQL provider that accepts connections from your computer and Clou
|
|
|
82
85
|
| [PlanetScale Postgres](https://planetscale.com/docs/postgres/connecting) | Choose **Postgres**, not Vitess/MySQL. Open **Connect**, create role credentials, and use the direct primary connection on port `5432`, not PgBouncer on `6432`. |
|
|
83
86
|
| [Neon](https://neon.com/docs/get-started-with-neon/connect-neon) | Open **Connect**, select the database and owner role, and turn **connection pooling off**. |
|
|
84
87
|
|
|
88
|
+
When sizing your database, include the connection pools for every platform installation and application sharing that server. Adjust each Hyperdrive's [origin connection limit](https://developers.cloudflare.com/hyperdrive/configuration/tune-connection-pool/) to fit the server's capacity, leaving room for migrations, administration, provider-reserved connections, and temporary bursts.
|
|
89
|
+
|
|
85
90
|
Once installation claims the database, continue using that same database for resume and maintenance commands. If installation stops because it cannot use the database, for example because the database is not empty, set `VOID_PLATFORM_DATABASE_URL` to another database's URL before you resume. Uninstall never deletes external PostgreSQL.
|
|
86
91
|
|
|
87
92
|
### Use a provider CLI
|
|
@@ -91,6 +91,8 @@ If installation configured Cloudflare Access, the uninstall plan lists its recor
|
|
|
91
91
|
|
|
92
92
|
Uninstall does not remove the applications created for [project Zero Trust](/guide/platform/administration/zero-trust). While Zero Trust is enabled or still has Access applications, uninstall and `--plan` stop before any change and list those applications. Run `void platform zero-trust disable` first. Uninstall reads this from the installation database, so it also stops when the database cannot be reached.
|
|
93
93
|
|
|
94
|
+
If the installation database is permanently gone and cannot be restored, there is no uninstall command that can clear the local installation record or list its Cloudflare resources. Once you have decided not to recover that installation, delete `~/.void/platforms/<account-id>/<installation-id>.json` and any sibling `<installation-id>.recovery.enc` or `<installation-id>.secrets.enc` files. This removes only local state and encrypted credentials; it does not delete Cloudflare resources. Review and remove those resources manually using [Remove retained resources](#remove-retained-resources).
|
|
95
|
+
|
|
94
96
|
If no platform user exists yet, for example because first administrator setup did
|
|
95
97
|
not finish, nobody can run `zero-trust disable`. Uninstall then turns off project
|
|
96
98
|
Zero Trust and deletes its applications itself. Run it on the computer that installed
|
package/docs/guide/queues.md
CHANGED
|
@@ -10,7 +10,7 @@ Use queues to process work asynchronously, such as sending emails or handling up
|
|
|
10
10
|
|
|
11
11
|
Create a consumer in `queues/`. TypeScript and JavaScript files are supported (`.ts`, `.mts`, `.js`, `.mjs`). Its path determines the queue name: `queues/emails.ts` creates `emails`, and `queues/order/notifications.ts` creates `order-notifications`. Each queue must have a unique name.
|
|
12
12
|
|
|
13
|
-
The resulting name must follow Cloudflare's queue naming rules: 1–63 characters, only letters, digits, and `-`, beginning and ending with a letter or digit.
|
|
13
|
+
The resulting name must follow Cloudflare's queue naming rules: 1–63 characters, only letters, digits, and `-`, beginning and ending with a letter or digit. On `void deploy --platform cloudflare`, the Cloudflare queue also carries the Worker name, so the limit applies to that full name; see [Cloudflare queue names](#cloudflare-queue-names).
|
|
14
14
|
|
|
15
15
|
Export a default handler wrapped with [`defineQueue<T>`](../reference/api/handlers.md#definequeue-t-handler). `T` defines the message body for both the consumer and calls to `send()`:
|
|
16
16
|
|
|
@@ -116,6 +116,18 @@ deletes messages that exhaust their retries without one; see
|
|
|
116
116
|
|
|
117
117
|
Void provisions queues and connects their producers and consumers when you deploy.
|
|
118
118
|
|
|
119
|
+
### Cloudflare queue names
|
|
120
|
+
|
|
121
|
+
Queue names are unique per Cloudflare account. So `void deploy --platform cloudflare` names each queue after the Worker: in a Worker named `app-staging`, `queues/jobs.ts` uses the Cloudflare queue `app-staging-jobs` (a `_` in the Worker name becomes `-`). Two Workers in one account, such as `app-staging` and `app-production`, get separate queues. Your code keeps using the name from `queues/` (`queues.jobs.send()`), and `vp dev` uses that name too.
|
|
122
|
+
|
|
123
|
+
The deploy stops before creating anything when:
|
|
124
|
+
|
|
125
|
+
- a new queue's full name is longer than 63 characters. Shorten the Worker name or the file name.
|
|
126
|
+
- one queue's full name is the name of another file in `queues/`, such as `queues/jobs.ts` and `queues/app-jobs.ts` in Worker `app`. Rename one of the files.
|
|
127
|
+
- another Worker already consumes the Cloudflare queue the deploy would use. A Cloudflare queue has one consumer.
|
|
128
|
+
|
|
129
|
+
A queue that the Worker already consumes keeps its name, so an app deployed before Worker-named queues keeps its queue (for example `jobs`) and the messages in it. The deploy prints a note when it keeps a name. To move to the Worker-named queue, remove the Worker's consumer from the old queue in the Cloudflare dashboard, then deploy again. Messages that reach the old queue before that deploy finishes stay there, so do this when the app is not sending messages, and check that the old queue is empty before you delete it.
|
|
130
|
+
|
|
119
131
|
## Local development
|
|
120
132
|
|
|
121
133
|
In native Void apps, sending a message through a queue binding automatically invokes the consumer in Miniflare. Local delivery uses the same handler as production.
|
|
@@ -8,7 +8,7 @@ Create a file in `routes/` to add an endpoint, and export a handler for each HTT
|
|
|
8
8
|
|
|
9
9
|
## Route Files
|
|
10
10
|
|
|
11
|
-
Create route handlers in `routes
|
|
11
|
+
Create route handlers in `routes/` using `.ts`, `.tsx`, `.js`, `.jsx`, `.mts`, or `.mjs`. Each file maps to a URL path based on its location in the filesystem:
|
|
12
12
|
|
|
13
13
|
<RoutesFileTree />
|
|
14
14
|
|
|
@@ -23,11 +23,13 @@ routes/
|
|
|
23
23
|
api/health.ts → always
|
|
24
24
|
api/debug.dev.ts → development only
|
|
25
25
|
api/metrics.prod.ts → production only
|
|
26
|
+
preview.dev.tsx → development only, with JSX
|
|
27
|
+
report.prod.jsx → production only, with JSX
|
|
26
28
|
```
|
|
27
29
|
|
|
28
30
|
`.dev` routes run only in development; `.prod` routes run in builds, deployments, and production previews. A resource used only by `.dev` routes is not provisioned for production.
|
|
29
31
|
|
|
30
|
-
Suffixes apply
|
|
32
|
+
Suffixes apply in `routes/` and [`pages/`](./pages-routing/overview.md#environment-only-pages), not in `middleware/`, `crons/`, or `queues/`. `void prepare` generates types for both environments.
|
|
31
33
|
|
|
32
34
|
Each file exports named HTTP method constants to handle specific methods:
|
|
33
35
|
|
|
@@ -61,6 +63,40 @@ export const POST = defineHandler(async (c) => {
|
|
|
61
63
|
|
|
62
64
|
See [Database](./database.md) for queries with D1, PostgreSQL, or MySQL.
|
|
63
65
|
|
|
66
|
+
### JSX handlers
|
|
67
|
+
|
|
68
|
+
Use `.tsx` or `.jsx` when a server handler contains JSX. Void doesn't choose a JSX runtime: route files compile with the same JSX settings as the rest of your Vite app. In a React or Solid app, that is the framework's JSX, so render it with the framework's server renderer and return HTML. For example, in a React app:
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
// routes/hello.tsx → GET /hello
|
|
72
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
73
|
+
import { defineHandler } from 'void';
|
|
74
|
+
|
|
75
|
+
export const GET = defineHandler((c) => c.html(renderToStaticMarkup(<h1>Hello!</h1>)));
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
In an app without a JSX framework (API only, Vue, or Svelte), use [Hono's JSX](https://hono.dev/docs/guides/jsx). Your app needs `hono` in its own dependencies (new Void apps have it). Set the runtime in `vite.config.ts`, so it applies to `.tsx` and `.jsx` files alike:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
// vite.config.ts
|
|
82
|
+
import { defineConfig } from 'vite-plus';
|
|
83
|
+
import { voidPlugin } from 'void';
|
|
84
|
+
|
|
85
|
+
export default defineConfig({
|
|
86
|
+
oxc: { jsx: { runtime: 'automatic', importSource: 'hono/jsx' } },
|
|
87
|
+
plugins: [voidPlugin()],
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`c.html()` then takes the JSX directly:
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
// routes/hello.tsx → GET /hello
|
|
95
|
+
import { defineHandler } from 'void';
|
|
96
|
+
|
|
97
|
+
export const GET = defineHandler((c) => c.html(<h1>Hello!</h1>));
|
|
98
|
+
```
|
|
99
|
+
|
|
64
100
|
## `defineHandler`
|
|
65
101
|
|
|
66
102
|
The handler receives a Hono `Context` with typed Cloudflare bindings on `c.env` (see [Cloudflare](../integrations/cloudflare.md)). You can use the full Hono API (`c.json()`, `c.text()`, `c.header()`, etc.).
|
|
@@ -104,13 +104,13 @@ The `action()` helper gets the same type checking. See [Actions & Forms](./pages
|
|
|
104
104
|
|
|
105
105
|
## What Gets Checked
|
|
106
106
|
|
|
107
|
-
| Layer | What's type-checked
|
|
108
|
-
| ------------ |
|
|
109
|
-
| Database | Table columns, value types, insert/update shapes from Drizzle schema
|
|
110
|
-
| Handlers | `c.env` bindings, validator input, return type
|
|
111
|
-
| Fetch client | Route paths, HTTP methods, `body`/`query`/`params`, response type
|
|
112
|
-
| `useForm` | Action URLs, `data` fields, `errors` keys, `reset()` args, `params`
|
|
113
|
-
| `action()` | Action URLs, `data` payload, `params`
|
|
107
|
+
| Layer | What's type-checked |
|
|
108
|
+
| ------------ | --------------------------------------------------------------------------------------------------------------------- |
|
|
109
|
+
| Database | Table columns, value types, insert/update shapes from Drizzle schema |
|
|
110
|
+
| Handlers | `c.env` bindings, validator input, return type |
|
|
111
|
+
| Fetch client | Route paths, HTTP methods, `body`/`query`/`params`, response type |
|
|
112
|
+
| `useForm` | Action URLs, `data` fields, `errors` keys, `reset()` args (including `{ initial: true }`), `params`, `resetOnSuccess` |
|
|
113
|
+
| `action()` | Action URLs, `data` payload, `params` |
|
|
114
114
|
|
|
115
115
|
## Serialization
|
|
116
116
|
|
package/docs/guide/websockets.md
CHANGED
|
@@ -14,7 +14,7 @@ For example, `/rooms/[id]` gives each chat room its own instance. Use it for cha
|
|
|
14
14
|
|
|
15
15
|
## Route files
|
|
16
16
|
|
|
17
|
-
Create WebSocket routes in `routes/`
|
|
17
|
+
Create WebSocket routes in `routes/` using `.ws.ts`, `.ws.tsx`, `.ws.js`, `.ws.jsx`, `.ws.mts`, or `.ws.mjs`:
|
|
18
18
|
|
|
19
19
|
```text
|
|
20
20
|
routes/
|
|
@@ -22,6 +22,8 @@ routes/
|
|
|
22
22
|
notifications.ws.ts
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
Use `.ws.tsx` or `.ws.jsx` when a handler contains JSX, such as rendering HTML fragments to send to connected clients. See [JSX handlers](./server-routing.md#jsx-handlers) for the JSX runtime.
|
|
26
|
+
|
|
25
27
|
Filename rules match regular server routes:
|
|
26
28
|
|
|
27
29
|
- `index.ws.ts` becomes the parent path
|
|
@@ -30,7 +32,7 @@ Filename rules match regular server routes:
|
|
|
30
32
|
- route groups like `(marketing)/chat.ws.ts` are ignored in the URL
|
|
31
33
|
- `chat.dev.ws.ts` / `chat.prod.ws.ts` restrict the route to one environment
|
|
32
34
|
|
|
33
|
-
The environment suffix goes **before** `.ws`. `chat.ws.dev.ts` is not
|
|
35
|
+
The environment suffix goes **before** `.ws`. `chat.ws.dev.ts` is not a WebSocket route.
|
|
34
36
|
|
|
35
37
|
## `defineRoom()`
|
|
36
38
|
|
|
@@ -49,7 +49,7 @@ Imported from `"@void/vue"`.
|
|
|
49
49
|
| `useRouter()` | Returns the Void Router with current route state (`url`, `path`, `query`, `params`) and navigation methods: `visit`, `refresh`, awaitable `prefetch`, `flush`, `flushAll`. |
|
|
50
50
|
| `useParams()` | Returns dynamic route params for the page currently being rendered. |
|
|
51
51
|
| `useNavigation()` | Returns pending navigation state: `{ state, location, method }`, where `state` is `"idle"`, `"loading"`, or `"submitting"` and `location` is the pending destination. |
|
|
52
|
-
| `useForm(url, defaults, options?)` | Typed reactive form helper bound to a page action URL. Returns `{ data, post, put, patch, delete, pending, errors, error, hasChanges, wasSuccessful, recentlySuccessful, reset, clearErrors, clearError }`.
|
|
52
|
+
| `useForm(url, defaults, options?)` | Typed reactive form helper bound to a page action URL. Returns `{ data, post, put, patch, delete, pending, errors, error, hasChanges, wasSuccessful, recentlySuccessful, reset, setError, clearErrors, clearError }`. |
|
|
53
53
|
| `useIslandForm(defaults)` | Form helper for island components. Same form state as `useForm()`, plus `submit(url, method)`. `post(url)`, `put(url)`, `patch(url)`, and `delete(url)` take the action URL and submit with `fetch()`. |
|
|
54
54
|
| `action(url, options?)` | Awaitable one-shot page action helper. Uses `POST` by default and accepts `{ data, method, params }`, where `method` can be `"PUT"`, `"PATCH"`, or `"DELETE"`. Returns an `ActionResult`. |
|
|
55
55
|
| `useShared()` | Returns shared data injected by middleware via `c.set("shared", {...})`. |
|
|
@@ -101,7 +101,7 @@ Imported from `"@void/react"`.
|
|
|
101
101
|
| `useRouter()` | Returns the Void Router with current route state (`url`, `path`, `query`, `params`) and navigation methods: `visit`, `refresh`, awaitable `prefetch`, `flush`, `flushAll`. React navigations are scheduled as transitions. |
|
|
102
102
|
| `useParams()` | Returns dynamic route params for the page currently being rendered. |
|
|
103
103
|
| `useNavigation()` | Returns pending navigation state: `{ state, location, method }`, where `state` is `"idle"`, `"loading"`, or `"submitting"` and `location` is the pending destination. |
|
|
104
|
-
| `useForm(url, defaults, options?)` | Form helper hook. Returns `{ data, setData, post, put, patch, delete, pending, errors, error, hasChanges, wasSuccessful, recentlySuccessful, reset, clearErrors, clearError }`.
|
|
104
|
+
| `useForm(url, defaults, options?)` | Form helper hook. Returns `{ data, setData, post, put, patch, delete, pending, errors, error, hasChanges, wasSuccessful, recentlySuccessful, reset, setError, clearErrors, clearError }`. |
|
|
105
105
|
| `useIslandForm(defaults)` | Form helper hook for island components. Same form state as `useForm()`. `post(url)`, `put(url)`, `patch(url)`, and `delete(url)` take the action URL and submit with `fetch()`. |
|
|
106
106
|
| `action(url, options?)` | Awaitable one-shot page action helper. Uses `POST` by default and accepts `{ data, method, params }`, where `method` can be `"PUT"`, `"PATCH"`, or `"DELETE"`. Returns an `ActionResult`. |
|
|
107
107
|
| `useShared()` | Returns shared data injected by middleware via `c.set("shared", {...})`. |
|
|
@@ -158,7 +158,7 @@ Imported from `"@void/svelte"`.
|
|
|
158
158
|
| Export | Description |
|
|
159
159
|
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
160
160
|
| `Link` | Svelte component for SPA navigation. Renders `<a>` for GET, `<button>` for non-GET methods. Props: `href`, `method`, `data`, `prefetch`, `cacheFor`, `preserveScroll`, `preserveState`, `replace`, `reloadDocument`, `viewTransition`, `onNavigate`. |
|
|
161
|
-
| `useForm(url, defaults, options?)` | Form helper using Svelte 5 runes. Returns `{ data, post, put, patch, delete, pending, errors, error, hasChanges, wasSuccessful, recentlySuccessful, reset, clearErrors, clearError }`.
|
|
161
|
+
| `useForm(url, defaults, options?)` | Form helper using Svelte 5 runes. Returns `{ data, post, put, patch, delete, pending, errors, error, hasChanges, wasSuccessful, recentlySuccessful, reset, setError, clearErrors, clearError }`. |
|
|
162
162
|
| `useIslandForm(defaults)` | Form helper for island components. Same form state as `useForm()`, plus `submit(url, method)`. `post(url)`, `put(url)`, `patch(url)`, and `delete(url)` take the action URL and submit with `fetch()`. |
|
|
163
163
|
| `action(url, options?)` | Awaitable one-shot page action helper. Uses `POST` by default and accepts `{ data, method, params }`, where `method` can be `"PUT"`, `"PATCH"`, or `"DELETE"`. Returns an `ActionResult`. |
|
|
164
164
|
| `useShared()` | Returns shared data injected by middleware via `c.set("shared", {...})`. |
|
|
@@ -208,7 +208,7 @@ Imported from `"@void/solid"`.
|
|
|
208
208
|
| Export | Description |
|
|
209
209
|
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
210
210
|
| `Link` | Solid component for SPA navigation. Renders `<a>` for GET, `<button>` for non-GET methods. Props: `href`, `method`, `data`, `prefetch`, `cacheFor`, `preserveScroll`, `preserveState`, `replace`, `reloadDocument`, `viewTransition`, `onNavigate`. |
|
|
211
|
-
| `useForm(url, defaults, options?)` | Form helper using Solid stores. Returns `{ data, setData, post, put, patch, delete, pending, errors, error, hasChanges, wasSuccessful, recentlySuccessful, reset, clearErrors, clearError }`.
|
|
211
|
+
| `useForm(url, defaults, options?)` | Form helper using Solid stores. Returns `{ data, setData, post, put, patch, delete, pending, errors, error, hasChanges, wasSuccessful, recentlySuccessful, reset, setError, clearErrors, clearError }`. |
|
|
212
212
|
| `useIslandForm(defaults)` | Form helper for island components. Same form state as `useForm()`. `post(url)`, `put(url)`, `patch(url)`, and `delete(url)` take the action URL and submit with `fetch()`. |
|
|
213
213
|
| `action(url, options?)` | Awaitable one-shot page action helper. Uses `POST` by default and accepts `{ data, method, params }`, where `method` can be `"PUT"`, `"PATCH"`, or `"DELETE"`. Returns an `ActionResult`. |
|
|
214
214
|
| `useShared()` | Returns shared data injected by middleware via `c.set("shared", {...})`. |
|
|
@@ -50,6 +50,19 @@ void email disallow <address> [--project <name>]
|
|
|
50
50
|
|
|
51
51
|
Remove one recipient from the project's destination list. New sends to that address are refused immediately; previously admitted attempts may finish. No deploy is involved.
|
|
52
52
|
|
|
53
|
+
## `void email alias` {#void-email-alias}
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
void email alias [--project <name>]
|
|
57
|
+
void email alias <name> [--yes] [--project <name>]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
With no name, show the project's address on the platform's shared mail domain and whether inbound mail reaches it.
|
|
61
|
+
|
|
62
|
+
With a name, give that address a shorter name: `void email alias acme` makes `acme@<mail domain>` and `acme+<tag>@<mail domain>` reach the project and lets it send from them. Only the project owner can set it. The name follows the same rules as a project slug, must not be used by any other project's slug or alias, and names of five characters or fewer need a plan that allows short names.
|
|
63
|
+
|
|
64
|
+
The alias is permanent: it cannot be changed or removed, and it is freed only when the project is deleted. The slug address keeps working for sending and receiving, so nothing that already uses it breaks. The CLI asks for confirmation; pass `--yes` to skip the prompt, which is required outside an interactive terminal. Inbound mail at the alias starts once the project has `email/` handlers deployed and its recipient rule is ready; run `void email alias` to check. Redeploy with the current `void` before you share the alias: an app built with an older version sees `message.to` with the alias still in front (`acme+support@…` instead of `support@…`).
|
|
65
|
+
|
|
53
66
|
## `void email domain` {#void-email-domain}
|
|
54
67
|
|
|
55
68
|
```
|
|
@@ -66,14 +66,14 @@ Register and maintain email domains for projects whose owners hold no Cloudflare
|
|
|
66
66
|
|
|
67
67
|
```sh
|
|
68
68
|
void platform email domains [--project <id|slug>]
|
|
69
|
-
void platform email domain-add <domain> --project <id|slug> [--token-stdin]
|
|
69
|
+
void platform email domain-add <domain> --project <id|slug> [--token-stdin] [--outbound-only]
|
|
70
70
|
void platform email domain-status <domain>
|
|
71
71
|
void platform email domain-sync <domain>
|
|
72
72
|
void platform email domain-rotate-secret <domain>
|
|
73
73
|
void platform email domain-remove <domain> [--token-stdin]
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
`domain-add` uses the platform's Cloudflare credential for zones in its account. For another account, pipe a scoped Cloudflare API token on standard input with `--token-stdin --yes`. Managed custom inbound email requires the Cloudflare zone apex (`example.com`). If its MX records already belong to another mail provider, use the project's shared email address or choose another unused zone. Native Cloudflare deployments also support literal recipient addresses on subdomains; see [Your own Cloudflare account](../../guide/email/domains.md#your-own-cloudflare-account).
|
|
76
|
+
`domain-add` uses the platform's Cloudflare credential for zones in its account. For another account, pipe a scoped Cloudflare API token on standard input with `--token-stdin --yes`. Managed custom inbound email requires the Cloudflare zone apex (`example.com`). If its MX records already belong to another mail provider, such as Microsoft 365 or Google Workspace, add `--outbound-only` to send from the apex while that provider keeps receiving its mail; see [Sending from a domain that already receives mail](../../guide/platform/administration/email.md#sending-from-a-domain-that-already-receives-mail). Otherwise use the project's shared email address or choose another unused zone. Native Cloudflare deployments also support literal recipient addresses on subdomains; see [Your own Cloudflare account](../../guide/email/domains.md#your-own-cloudflare-account).
|
|
77
77
|
|
|
78
78
|
`domain-status` shows inbound, outbound, and credential-management readiness with the latest operation. A blocked operation resumes through `domain-sync`; a blocked rotation resumes through `domain-rotate-secret`, preserving already confirmed steps and its staged credential. An uncertain operation remains stopped until read-back proves the result or an administrator uses `operation-resolve`. Domains an administrator adds show `managed_by: admin`; their owners can list and inspect them but use these commands for `sync`, `domain-rotate-secret`, and `remove`.
|
|
79
79
|
|
package/docs/reference/cli.md
CHANGED
|
@@ -73,6 +73,7 @@ Use this page as a command reference. If you are setting up a project for the fi
|
|
|
73
73
|
| `void email destinations` | List verified recipient addresses |
|
|
74
74
|
| `void email allow <address>` | Add a recipient and send a verification email |
|
|
75
75
|
| `void email disallow <address>` | Remove a recipient from the allowlist |
|
|
76
|
+
| `void email alias [name]` | Show the shared mail address, or give it a shorter permanent name |
|
|
76
77
|
| `void email domain` | Send and receive at your own domain on a Cloudflare zone |
|
|
77
78
|
| `void init` | Set up a new or existing project, including coding agents |
|
|
78
79
|
| `void migrate` | Convert legacy `void.json` and root Wrangler JSON/JSONC into `void.config.ts` |
|
|
@@ -67,6 +67,7 @@ File-based HTTP API endpoints built on [Hono](https://hono.dev). Each file expor
|
|
|
67
67
|
<RoutesFileTree />
|
|
68
68
|
|
|
69
69
|
- **Named exports**: `export const GET = defineHandler(...)`, `export const POST = ...`
|
|
70
|
+
- **Extensions**: `.ts`, `.tsx`, `.js`, `.jsx`, `.mts`, or `.mjs`; use `.tsx` or `.jsx` for JSX handlers
|
|
70
71
|
- **Dynamic segments**: `[id]` becomes `:id` route parameter
|
|
71
72
|
- **Catch-all**: `[...slug]` matches the remaining path
|
|
72
73
|
- **Route groups**: `(admin)/` organizes files without affecting URL paths
|
|
@@ -81,9 +82,10 @@ Full-stack server-rendered pages with co-located data loading. Available with Vu
|
|
|
81
82
|
|
|
82
83
|
<PagesFileTree />
|
|
83
84
|
|
|
84
|
-
- **`.server.ts` files** export `loader` for `GET` data and `action` for mutations, paired with the page component of the same name
|
|
85
|
+
- **`.server.ts` (or `.tsx`, `.js`, `.jsx`, `.mts`, `.mjs`) files** export `loader` for `GET` data and `action` for mutations, paired with the page component of the same name
|
|
85
86
|
- **Layouts** nest automatically and persist across navigations
|
|
86
87
|
- **Markdown** pages are supported with frontmatter
|
|
88
|
+
- **Environment suffix**: `debug.dev.tsx` is served in development only, `stats.prod.tsx` in production only
|
|
87
89
|
|
|
88
90
|
See [Pages Routing](/guide/pages-routing/overview) for the full guide.
|
|
89
91
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "void",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Full-stack framework and deployment CLI for Cloudflare Workers, powered by Vite.",
|
|
5
5
|
"homepage": "https://void.cloud",
|
|
6
6
|
"bugs": {
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"docs",
|
|
21
21
|
"schema.json",
|
|
22
22
|
"skills",
|
|
23
|
+
"templates",
|
|
23
24
|
"AGENTS_TEMPLATE.md",
|
|
24
25
|
"README.md",
|
|
25
26
|
"sandbox.Dockerfile"
|
|
@@ -348,11 +349,11 @@
|
|
|
348
349
|
"@cloudflare/workers-auth": "^0.14.1",
|
|
349
350
|
"@cloudflare/workers-types": "^5.20261019.1",
|
|
350
351
|
"@napi-rs/keyring": "^2.1.2",
|
|
351
|
-
"@void/deploy-cloudflare": "1.
|
|
352
|
-
"@void/deploy-core": "1.
|
|
353
|
-
"@void/edge": "1.
|
|
354
|
-
"@void/isr": "1.
|
|
355
|
-
"@void/platform": "1.
|
|
352
|
+
"@void/deploy-cloudflare": "1.3.0",
|
|
353
|
+
"@void/deploy-core": "1.3.0",
|
|
354
|
+
"@void/edge": "1.3.0",
|
|
355
|
+
"@void/isr": "1.3.0",
|
|
356
|
+
"@void/platform": "1.3.0",
|
|
356
357
|
"better-auth": "^1.7.11",
|
|
357
358
|
"better-sqlite3": "^13.0.3",
|
|
358
359
|
"blake3-jit": "^1.1.0",
|
|
@@ -401,7 +402,7 @@
|
|
|
401
402
|
"zod": "^4.6.7"
|
|
402
403
|
},
|
|
403
404
|
"peerDependencies": {
|
|
404
|
-
"@void/md": "1.
|
|
405
|
+
"@void/md": "1.3.0",
|
|
405
406
|
"arktype": ">=2.0.0",
|
|
406
407
|
"valibot": ">=1.0.0-beta.7",
|
|
407
408
|
"vite": "^8.0.0",
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Portions of the starter UI and assets are adapted from Vite.
|
|
3
|
+
|
|
4
|
+
MIT License
|
|
5
|
+
|
|
6
|
+
Copyright (c) 2019-present, VoidZero Inc. and Vite contributors
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
in the Software without restriction, including without limitation the rights
|
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
SOFTWARE.
|
|
25
|
+
-->
|
|
26
|
+
<svg xmlns="http://www.w3.org/2000/svg">
|
|
27
|
+
<symbol id="bluesky-icon" viewBox="0 0 16 17">
|
|
28
|
+
<g clip-path="url(#bluesky-clip)"><path fill="currentColor" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
|
|
29
|
+
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
|
|
30
|
+
</symbol>
|
|
31
|
+
<symbol id="discord-icon" viewBox="0 0 20 19">
|
|
32
|
+
<path fill="currentColor" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
|
|
33
|
+
</symbol>
|
|
34
|
+
<symbol id="documentation-icon" viewBox="0 0 21 20">
|
|
35
|
+
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
|
|
36
|
+
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
|
|
37
|
+
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
|
|
38
|
+
</symbol>
|
|
39
|
+
<symbol id="github-icon" viewBox="0 0 19 19">
|
|
40
|
+
<path fill="currentColor" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
|
|
41
|
+
</symbol>
|
|
42
|
+
<symbol id="social-icon" viewBox="0 0 20 20">
|
|
43
|
+
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
|
|
44
|
+
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
|
|
45
|
+
</symbol>
|
|
46
|
+
<symbol id="x-icon" viewBox="0 0 19 19">
|
|
47
|
+
<path fill="currentColor" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
|
|
48
|
+
</symbol>
|
|
49
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 166 155.3"><path d="M163 35S110-4 69 5l-3 1c-6 2-11 5-14 9l-2 3-15 26 26 5c11 7 25 10 38 7l46 9 18-30z" fill="#76b3e1"/><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="27.5" y1="3" x2="152" y2="63.5"><stop offset=".1" stop-color="#76b3e1"/><stop offset=".3" stop-color="#dcf2fd"/><stop offset="1" stop-color="#76b3e1"/></linearGradient><path d="M163 35S110-4 69 5l-3 1c-6 2-11 5-14 9l-2 3-15 26 26 5c11 7 25 10 38 7l46 9 18-30z" opacity=".3" fill="url(#a)"/><path d="M52 35l-4 1c-17 5-22 21-13 35 10 13 31 20 48 15l62-21S92 26 52 35z" fill="#518ac8"/><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="95.8" y1="32.6" x2="74" y2="105.2"><stop offset="0" stop-color="#76b3e1"/><stop offset=".5" stop-color="#4377bb"/><stop offset="1" stop-color="#1f3b77"/></linearGradient><path d="M52 35l-4 1c-17 5-22 21-13 35 10 13 31 20 48 15l62-21S92 26 52 35z" opacity=".3" fill="url(#b)"/><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="18.4" y1="64.2" x2="144.3" y2="149.8"><stop offset="0" stop-color="#315aa9"/><stop offset=".5" stop-color="#518ac8"/><stop offset="1" stop-color="#315aa9"/></linearGradient><path d="M134 80a45 45 0 00-48-15L24 85 4 120l112 19 20-36c4-7 3-15-2-23z" fill="url(#c)"/><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="75.2" y1="74.5" x2="24.4" y2="260.8"><stop offset="0" stop-color="#4377bb"/><stop offset=".5" stop-color="#1a336b"/><stop offset="1" stop-color="#1a336b"/></linearGradient><path d="M114 115a45 45 0 00-48-15L4 120s53 40 94 30l3-1c17-5 23-21 13-34z" fill="url(#d)"/></svg>
|