sporades 0.1.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/Dockerfile.base +19 -0
- package/LICENSE +21 -0
- package/README.md +289 -0
- package/bin/sporades-host-helper.js +4001 -0
- package/bin/sporades.js +14198 -0
- package/dist/base-image.d.ts +43 -0
- package/dist/base-image.d.ts.map +1 -0
- package/dist/base-image.js +53 -0
- package/dist/base-image.js.map +1 -0
- package/dist/bundle-pipeline.d.ts +110 -0
- package/dist/bundle-pipeline.d.ts.map +1 -0
- package/dist/bundle-pipeline.js +369 -0
- package/dist/bundle-pipeline.js.map +1 -0
- package/dist/capsule-services.d.ts +94 -0
- package/dist/capsule-services.d.ts.map +1 -0
- package/dist/capsule-services.js +310 -0
- package/dist/capsule-services.js.map +1 -0
- package/dist/cli/cli-help.d.ts +16 -0
- package/dist/cli/cli-help.d.ts.map +1 -0
- package/dist/cli/cli-help.js +202 -0
- package/dist/cli/cli-help.js.map +1 -0
- package/dist/cli/cli-support.d.ts +17 -0
- package/dist/cli/cli-support.d.ts.map +1 -0
- package/dist/cli/cli-support.js +46 -0
- package/dist/cli/cli-support.js.map +1 -0
- package/dist/cli/github-autodeploy-workflow.d.ts +7 -0
- package/dist/cli/github-autodeploy-workflow.d.ts.map +1 -0
- package/dist/cli/github-autodeploy-workflow.js +185 -0
- package/dist/cli/github-autodeploy-workflow.js.map +1 -0
- package/dist/cli/host-helper-archive.d.ts +9 -0
- package/dist/cli/host-helper-archive.d.ts.map +1 -0
- package/dist/cli/host-helper-archive.js +74 -0
- package/dist/cli/host-helper-archive.js.map +1 -0
- package/dist/cli/host-helper-config.d.ts +15 -0
- package/dist/cli/host-helper-config.d.ts.map +1 -0
- package/dist/cli/host-helper-config.js +111 -0
- package/dist/cli/host-helper-config.js.map +1 -0
- package/dist/cli/host-helper-contract.d.ts +5 -0
- package/dist/cli/host-helper-contract.d.ts.map +1 -0
- package/dist/cli/host-helper-contract.js +5 -0
- package/dist/cli/host-helper-contract.js.map +1 -0
- package/dist/cli/host-helper-docker-types.d.ts +27 -0
- package/dist/cli/host-helper-docker-types.d.ts.map +1 -0
- package/dist/cli/host-helper-docker-types.js +2 -0
- package/dist/cli/host-helper-docker-types.js.map +1 -0
- package/dist/cli/host-helper-json.d.ts +6 -0
- package/dist/cli/host-helper-json.d.ts.map +1 -0
- package/dist/cli/host-helper-json.js +2 -0
- package/dist/cli/host-helper-json.js.map +1 -0
- package/dist/cli/host-helper-release-files.d.ts +4 -0
- package/dist/cli/host-helper-release-files.d.ts.map +1 -0
- package/dist/cli/host-helper-release-files.js +21 -0
- package/dist/cli/host-helper-release-files.js.map +1 -0
- package/dist/cli/host-helper-requests.d.ts +163 -0
- package/dist/cli/host-helper-requests.d.ts.map +1 -0
- package/dist/cli/host-helper-requests.js +2 -0
- package/dist/cli/host-helper-requests.js.map +1 -0
- package/dist/cli/host-helper-validation.d.ts +27 -0
- package/dist/cli/host-helper-validation.d.ts.map +1 -0
- package/dist/cli/host-helper-validation.js +256 -0
- package/dist/cli/host-helper-validation.js.map +1 -0
- package/dist/cli/host-request-builders.d.ts +143 -0
- package/dist/cli/host-request-builders.d.ts.map +1 -0
- package/dist/cli/host-request-builders.js +266 -0
- package/dist/cli/host-request-builders.js.map +1 -0
- package/dist/cli/hosted-capsule-contract.d.ts +128 -0
- package/dist/cli/hosted-capsule-contract.d.ts.map +1 -0
- package/dist/cli/hosted-capsule-contract.js +2 -0
- package/dist/cli/hosted-capsule-contract.js.map +1 -0
- package/dist/cli/sporades-host-helper.d.ts +3 -0
- package/dist/cli/sporades-host-helper.d.ts.map +1 -0
- package/dist/cli/sporades-host-helper.js +3120 -0
- package/dist/cli/sporades-host-helper.js.map +1 -0
- package/dist/cli/sporades.d.ts +3 -0
- package/dist/cli/sporades.d.ts.map +1 -0
- package/dist/cli/sporades.js +4066 -0
- package/dist/cli/sporades.js.map +1 -0
- package/dist/client.d.ts +7 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +9 -0
- package/dist/client.js.map +1 -0
- package/dist/runtime-restart-policy.d.ts +29 -0
- package/dist/runtime-restart-policy.d.ts.map +1 -0
- package/dist/runtime-restart-policy.js +53 -0
- package/dist/runtime-restart-policy.js.map +1 -0
- package/dist/sealed-server-env.d.ts +56 -0
- package/dist/sealed-server-env.d.ts.map +1 -0
- package/dist/sealed-server-env.js +170 -0
- package/dist/sealed-server-env.js.map +1 -0
- package/dist/server-runtime-source.d.ts +1262 -0
- package/dist/server-runtime-source.d.ts.map +1 -0
- package/dist/server-runtime-source.js +6397 -0
- package/dist/server-runtime-source.js.map +1 -0
- package/dist/server.d.ts +58 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +183 -0
- package/dist/server.js.map +1 -0
- package/dist/templates/client-runtime-template.d.ts +2 -0
- package/dist/templates/client-runtime-template.d.ts.map +1 -0
- package/dist/templates/client-runtime-template.js +438 -0
- package/dist/templates/client-runtime-template.js.map +1 -0
- package/dist/templates/scaffold-template.d.ts +19 -0
- package/dist/templates/scaffold-template.d.ts.map +1 -0
- package/dist/templates/scaffold-template.js +1201 -0
- package/dist/templates/scaffold-template.js.map +1 -0
- package/dist/templates/server-bundle-template.d.ts +8 -0
- package/dist/templates/server-bundle-template.d.ts.map +1 -0
- package/dist/templates/server-bundle-template.js +162 -0
- package/dist/templates/server-bundle-template.js.map +1 -0
- package/package.json +44 -0
- package/src/types/client.d.ts +161 -0
- package/src/types/server.d.ts +300 -0
package/Dockerfile.base
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
FROM node:22-alpine
|
|
2
|
+
|
|
3
|
+
LABEL org.opencontainers.image.title="Sporades Base Image"
|
|
4
|
+
LABEL org.opencontainers.image.description="Thin Node 22 Base image for Sporades Container sessions and Hosted Capsules"
|
|
5
|
+
LABEL org.opencontainers.image.version="0.1.0-node22-alpine"
|
|
6
|
+
LABEL org.opencontainers.image.source="https://github.com/sporades/sporades"
|
|
7
|
+
LABEL com.sporades.base-image.name="sporades-base"
|
|
8
|
+
LABEL com.sporades.base-image.version="0.1.0-node22-alpine"
|
|
9
|
+
LABEL com.sporades.base-image.node="22"
|
|
10
|
+
|
|
11
|
+
RUN addgroup -g 10001 -S sporades \
|
|
12
|
+
&& adduser -u 10001 -S sporades -G sporades \
|
|
13
|
+
&& mkdir -p /app /app/data \
|
|
14
|
+
&& chown -R 10001:10001 /app
|
|
15
|
+
|
|
16
|
+
WORKDIR /app
|
|
17
|
+
USER 10001:10001
|
|
18
|
+
|
|
19
|
+
CMD ["node", "/app/server.mjs"]
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matt Cox
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# Sporades
|
|
2
|
+
|
|
3
|
+
## Documentation
|
|
4
|
+
|
|
5
|
+
- [User guide](docs/user-guide.md) - quick start and day-to-day Capsule usage.
|
|
6
|
+
- [Architecture](docs/architecture.md) - platform ethos, runtime layout, Host
|
|
7
|
+
routing, storage, and client transport.
|
|
8
|
+
- [Roadmap](docs/ROADMAP.md) - candidate features and their promotion status.
|
|
9
|
+
- [Runtime layout](docs/runtime-layout.md) - canonical filesystem paths,
|
|
10
|
+
runtime mounts, and Host server directory structures.
|
|
11
|
+
- [Host server installation](docs/server-installation.md) - preparing a Linux
|
|
12
|
+
Host server for Hosted Capsules.
|
|
13
|
+
|
|
14
|
+
## Auth
|
|
15
|
+
|
|
16
|
+
Sporades apps can enable multiple auth providers in `sporades.json`:
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"auth": {
|
|
21
|
+
"providers": {
|
|
22
|
+
"anonymous": true,
|
|
23
|
+
"google": {
|
|
24
|
+
"clientIdEnv": "GOOGLE_CLIENT_ID",
|
|
25
|
+
"clientSecretEnv": "GOOGLE_CLIENT_SECRET"
|
|
26
|
+
},
|
|
27
|
+
"email": true
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`anonymous`, `google`, and `email` are valid provider names. Existing projects
|
|
34
|
+
using `auth.mode` continue to work; `auth.mode: "anonymous"` maps to anonymous
|
|
35
|
+
sessions, and `auth.mode: "google"` enables Google alongside anonymous sessions.
|
|
36
|
+
|
|
37
|
+
Configure Google OAuth from explicit credentials:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
sporades auth set google --client-id <id> --client-secret <secret>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Or pass a downloaded Google OAuth client JSON file:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
sporades auth set google --client-json ./client_secret_google.json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The JSON form reads Google Web application credentials from `web.client_id` and
|
|
50
|
+
`web.client_secret`. Sporades stores the values in `.env.sporades.server` and
|
|
51
|
+
keeps only env var names in `sporades.json`.
|
|
52
|
+
|
|
53
|
+
`sporades auth status --json` reports enabled providers and configuration state
|
|
54
|
+
without printing OAuth client IDs or secrets.
|
|
55
|
+
|
|
56
|
+
List currently connected browser clients for a running dev session:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
sporades auth clients --json
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The JSON response includes safe metadata only: stable client IDs, current auth
|
|
63
|
+
summary, connection time, and last-seen time. It does not include session
|
|
64
|
+
tokens, OAuth credentials, passwords, or raw `localStorage` payloads.
|
|
65
|
+
|
|
66
|
+
For local browser tests and agents, a running dev session can create a simulated
|
|
67
|
+
linked identity and return the same session token shape the SDK stores in
|
|
68
|
+
`localStorage`:
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
sporades auth as email --email mira@example.com --display-name "Mira Vale" --json
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Pass `--client current` to push the simulated session into the most recently
|
|
75
|
+
connected browser client, `--client all` to push it into every connected
|
|
76
|
+
browser client for the app, or `--client <id>` with an ID from
|
|
77
|
+
`sporades auth clients --json` to target one exact browser client:
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
sporades auth as email --email mira@example.com --client current --json
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The JSON response includes `{ localStorage: { key, value }, auth, delivery }`,
|
|
84
|
+
where `key` is `sporades.sessionToken`. `delivery` reports the requested target,
|
|
85
|
+
whether any browser client received the session, and how many clients were
|
|
86
|
+
updated. The `localStorage` payload is always returned as a manual fallback even
|
|
87
|
+
when no browser client is connected. This is local identity simulation only: it
|
|
88
|
+
creates a Sporades-owned dev session for `ctx.auth` testing and does not
|
|
89
|
+
complete OAuth, trust arbitrary JWTs, or authenticate against a real provider.
|
|
90
|
+
`google` can be used as a simulated provider shape for browser tests that need
|
|
91
|
+
Google-like auth metadata. Simulated sessions use the same session lifetime as
|
|
92
|
+
normal Sporades sessions.
|
|
93
|
+
|
|
94
|
+
Sporades session records store creation and expiry metadata. Sessions expire 30
|
|
95
|
+
days after they are created or refreshed. Missing, invalid, or expired session
|
|
96
|
+
tokens resolve to a fresh anonymous session instead of authenticating the old
|
|
97
|
+
user forever. Email sign-up links the current anonymous account and rotates the
|
|
98
|
+
session token; email sign-in rotates the current anonymous token onto the email
|
|
99
|
+
account. Google sign-in links through the server-owned OAuth callback and
|
|
100
|
+
refreshes the current session token's expiry, because the redirect callback does
|
|
101
|
+
not expose a safe client token handoff point.
|
|
102
|
+
|
|
103
|
+
After running `sporades auth set <provider>`, restart any running dev session so
|
|
104
|
+
the server runtime reloads the updated Server env and auth configuration:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
# stop the current dev session, then run:
|
|
108
|
+
sporades dev
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Client code signs users up and in through the provider-neutral auth surface.
|
|
112
|
+
Email auth returns structured results and keeps auth details server-owned:
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
import { auth } from "sporades/client";
|
|
116
|
+
|
|
117
|
+
const signUp = await auth.signUp("email", {
|
|
118
|
+
email: "mira@example.com",
|
|
119
|
+
password: "correct horse battery staple",
|
|
120
|
+
name: "Mira",
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const signIn = await auth.signIn("email", {
|
|
124
|
+
email: "mira@example.com",
|
|
125
|
+
password: "correct horse battery staple",
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (signIn.data?.ok) {
|
|
129
|
+
console.log(signIn.data.auth.provider); // "email"
|
|
130
|
+
} else {
|
|
131
|
+
console.error(signIn.error?.message, signIn.error?.hint);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Google continues to use the same sign-in surface and starts the server-owned
|
|
136
|
+
redirect flow:
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
await auth.signIn("google");
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Client code signs out through the same auth surface. A successful sign-out
|
|
143
|
+
clears the stored Sporades session token and refreshes auth state to a fresh
|
|
144
|
+
anonymous session, so apps can route immediately:
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
import { auth } from "sporades/client";
|
|
148
|
+
|
|
149
|
+
async function signOutAndGoHome(navigate: (path: string) => void) {
|
|
150
|
+
const result = await auth.signOut();
|
|
151
|
+
if (result.data?.ok) {
|
|
152
|
+
navigate("/");
|
|
153
|
+
} else {
|
|
154
|
+
console.error(result.error?.message);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## File uploads
|
|
160
|
+
|
|
161
|
+
Client code uses `files.upload()` from `sporades/client` to upload browser
|
|
162
|
+
`File` or `Blob` values. The SDK first asks the Sporades server for an upload
|
|
163
|
+
URL, transfers the bytes internally, and resolves with Sporades-owned metadata:
|
|
164
|
+
file ID, absolute File path, original filename, MIME type, size, bucket, and
|
|
165
|
+
version. Pass `path: "/images/avatar.png"` to choose an absolute Capsule-scoped
|
|
166
|
+
File path; omitted paths use the browser file name in the Default File bucket,
|
|
167
|
+
falling back to the logical `/default/upload` File path when no file name
|
|
168
|
+
exists. Arrays are accepted as a convenience and are uploaded sequentially.
|
|
169
|
+
|
|
170
|
+
Uploaded bytes are private by default. Ownership and privacy are runtime file
|
|
171
|
+
metadata and ACL behavior; the Default File bucket is only a namespace fallback,
|
|
172
|
+
not a user bucket or policy boundary. Local filesystem storage is the default:
|
|
173
|
+
during Dev sessions, bytes live under `.sporades/files/`; Container sessions
|
|
174
|
+
use the mounted `/app/data` volume. Capsules can declare
|
|
175
|
+
`services.storage.engine: "minio"` to use MinIO as local Capsule service
|
|
176
|
+
storage while keeping the same `files` SDK calls. Metadata, buckets, upload
|
|
177
|
+
records, and public URL records live in the database either way.
|
|
178
|
+
|
|
179
|
+
Private reads go through `files.url(fileReference)` or
|
|
180
|
+
`files.download(fileReference)`, where the File reference is either a File ID or
|
|
181
|
+
an absolute File path.
|
|
182
|
+
Public reads must be created explicitly with exactly one expiry choice:
|
|
183
|
+
`ttlSeconds`, `expires`, or `noExpiry: true`. Replacing a file preserves the file
|
|
184
|
+
ID, creates a new version, and invalidates previously generated private and
|
|
185
|
+
public URLs. Uploading to an existing live File path overwrites that file while
|
|
186
|
+
preserving its File ID; deleting a file frees the path for a later upload with a
|
|
187
|
+
new File ID. Public and private read URLs are Sporades HTTP routes, not
|
|
188
|
+
filesystem paths or presigned MinIO/S3 URLs.
|
|
189
|
+
|
|
190
|
+
## Endpoint handlers
|
|
191
|
+
|
|
192
|
+
Capsules can register custom HTTP endpoints with `endpoint({ method, path }, handler)`.
|
|
193
|
+
The handler receives the same server-owned context concepts as queries and
|
|
194
|
+
mutations:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
endpoint({ method: "POST", path: "/integrations/webhook" }, (ctx) => ({
|
|
198
|
+
status: 202,
|
|
199
|
+
headers: { "x-sporades-endpoint": "accepted" },
|
|
200
|
+
body: {
|
|
201
|
+
method: ctx.request.method,
|
|
202
|
+
path: ctx.request.path,
|
|
203
|
+
query: ctx.request.query,
|
|
204
|
+
body: ctx.request.body,
|
|
205
|
+
userId: ctx.auth.userId,
|
|
206
|
+
},
|
|
207
|
+
}));
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`ctx` includes `ctx.db`, `ctx.auth`, `ctx.env`, `ctx.log`, and `ctx.request`.
|
|
211
|
+
`ctx.request` exposes `method`, `path`, lower-cased `headers`, query parameters,
|
|
212
|
+
and parsed request body data. Structured endpoint responses use
|
|
213
|
+
`{ status, headers, body }`; object bodies are returned as JSON, and string bodies
|
|
214
|
+
are returned as text.
|
|
215
|
+
|
|
216
|
+
## App messages
|
|
217
|
+
|
|
218
|
+
Capsules can declare ephemeral app-level messages with `message(handler)` and
|
|
219
|
+
clients can send them through `sporades/client` without touching raw WebSocket
|
|
220
|
+
objects:
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
// server/index.ts
|
|
224
|
+
import { capsule, message } from "sporades/server";
|
|
225
|
+
|
|
226
|
+
export default capsule({
|
|
227
|
+
messages: {
|
|
228
|
+
typing: message((ctx, data) => {
|
|
229
|
+
const sentToClients = ctx.messages.send({ type: "typing", data });
|
|
230
|
+
return { ok: true, sentToClients };
|
|
231
|
+
}),
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
// client/index.tsx
|
|
238
|
+
import { onMessage, sendMessage } from "sporades/client";
|
|
239
|
+
|
|
240
|
+
await sendMessage("typing", { roomId: "general", active: true });
|
|
241
|
+
|
|
242
|
+
const subscription = onMessage()
|
|
243
|
+
.filter((message) => message.type === "typing")
|
|
244
|
+
.subscribe((message) => {
|
|
245
|
+
console.log(message.data);
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
App and server code use unprefixed type names. Sporades reserves platform
|
|
250
|
+
namespaces such as `app.`, `auth.`, `query.`, `mutation.`, `files.`, and
|
|
251
|
+
`runtime.` for internal transport messages. Client-origin app messages always
|
|
252
|
+
run through declared Capsule handlers; the platform does not directly relay
|
|
253
|
+
arbitrary client messages. `ctx.messages.send()` defaults to the current user's
|
|
254
|
+
connected clients, and handlers can explicitly target users with
|
|
255
|
+
`{ scope: "users", userIds: [...] }`. It returns the number of connected clients
|
|
256
|
+
the message was sent to. App-wide `{ scope: "all" }` broadcasts are not
|
|
257
|
+
available from client-origin handlers.
|
|
258
|
+
|
|
259
|
+
App messages are a real-time escape hatch for JSON-serializable, ephemeral
|
|
260
|
+
events. Use queries and mutations for durable state, auth APIs for identity, and
|
|
261
|
+
file APIs for binary payloads.
|
|
262
|
+
|
|
263
|
+
## Context middleware
|
|
264
|
+
|
|
265
|
+
Capsules can register synchronous context middleware with `middleware`. Each
|
|
266
|
+
function receives the current `ctx` and can return an enriched context for the
|
|
267
|
+
request handler, query, mutation, or mutation hook:
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
export default capsule({
|
|
271
|
+
middleware: [
|
|
272
|
+
(ctx) => ({ ...ctx, tenant: ctx.env.TENANT }),
|
|
273
|
+
(ctx) => {
|
|
274
|
+
if (!ctx.tenant) {
|
|
275
|
+
throw Object.assign(new Error("Missing tenant."), {
|
|
276
|
+
hint: "Set TENANT in .env.sporades.server.",
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
return ctx;
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Middleware runs once per WebSocket query request, WebSocket mutation request, and
|
|
286
|
+
HTTP endpoint request. Functions run in the order they appear in the array, and
|
|
287
|
+
each function receives the context returned by the previous function. Middleware
|
|
288
|
+
receives `ctx.kind` as `"query"`, `"mutation"`, or `"endpoint"`. Throw an error
|
|
289
|
+
with a `hint` property to block the request with a structured Sporades error.
|