sporades 0.1.0 → 0.2.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.
Files changed (39) hide show
  1. package/CHANGES.md +43 -0
  2. package/Dockerfile.base +23 -2
  3. package/README.md +187 -234
  4. package/bin/sporades-host-helper.js +167 -4
  5. package/bin/sporades.js +703 -14
  6. package/dist/cli/cli-help.d.ts +3 -3
  7. package/dist/cli/cli-help.d.ts.map +1 -1
  8. package/dist/cli/cli-help.js +3 -1
  9. package/dist/cli/cli-help.js.map +1 -1
  10. package/dist/cli/host-helper-release-files.d.ts.map +1 -1
  11. package/dist/cli/host-helper-release-files.js +4 -0
  12. package/dist/cli/host-helper-release-files.js.map +1 -1
  13. package/dist/cli/host-helper-requests.d.ts +12 -2
  14. package/dist/cli/host-helper-requests.d.ts.map +1 -1
  15. package/dist/cli/host-request-builders.d.ts.map +1 -1
  16. package/dist/cli/host-request-builders.js +11 -0
  17. package/dist/cli/host-request-builders.js.map +1 -1
  18. package/dist/cli/sporades-host-helper.js +159 -1
  19. package/dist/cli/sporades-host-helper.js.map +1 -1
  20. package/dist/cli/sporades.js +402 -8
  21. package/dist/cli/sporades.js.map +1 -1
  22. package/dist/client.d.ts +1 -0
  23. package/dist/client.d.ts.map +1 -1
  24. package/dist/client.js +1 -0
  25. package/dist/client.js.map +1 -1
  26. package/dist/server-runtime-source.d.ts +24 -0
  27. package/dist/server-runtime-source.d.ts.map +1 -1
  28. package/dist/server-runtime-source.js +232 -2
  29. package/dist/server-runtime-source.js.map +1 -1
  30. package/dist/server.d.ts +17 -0
  31. package/dist/server.d.ts.map +1 -1
  32. package/dist/server.js +48 -1
  33. package/dist/server.js.map +1 -1
  34. package/dist/templates/client-runtime-template.d.ts.map +1 -1
  35. package/dist/templates/client-runtime-template.js +15 -0
  36. package/dist/templates/client-runtime-template.js.map +1 -1
  37. package/package.json +8 -1
  38. package/src/types/client.d.ts +113 -0
  39. package/src/types/server.d.ts +141 -0
package/CHANGES.md ADDED
@@ -0,0 +1,43 @@
1
+ # Changes
2
+
3
+ ## Unreleased - 2026-07-07
4
+
5
+ Changes since v0.1.0.
6
+
7
+ ### 🚀 Features
8
+
9
+ - Add packaging scripts (31a33c5).
10
+ - Add consistent change-log generation via skill (0b8bc4f).
11
+ - Enable Hosted Capsule SSH access (d59abaa).
12
+ - Enable local Container SSH access (8720842).
13
+ - Add current-user preferences SDK (570d769).
14
+ - Allow dependency install for local repository working (a35db4c).
15
+ - Add requireAuth handler helper as the canonical auth gate (7bb7ee6).
16
+
17
+ ### 🔧 Improvements
18
+
19
+ - Mark user preferences coherence issue done (5e49203).
20
+ - Mark user preferences SDK issue done (9119388).
21
+ - Mark built-in-auth-helper issue 01 done (2d6d470).
22
+ - Rebuild bundle after merging requireAuth helper (160ce44).
23
+
24
+ ### 📝 Documentation
25
+
26
+ - Update docs (e5e42d6).
27
+ - Rewording (aee6676).
28
+ - Document Container SSH access (62d9f54).
29
+ - Define Container SSH access contract (d23386a).
30
+ - Document public SDK API (f135dd0).
31
+ - Persist user prefs from anonymous to a signed-up user on registration (d2d9d6c).
32
+ - Update docs (7469398).
33
+ - Update preference table inspection coverage (790e49c).
34
+ - Keep user preferences coherent across sessions (703e826).
35
+ - Plan recommended roadmap features (4975e8d).
36
+ - Tidy up working files (f29fff5).
37
+ - Record service-backed vector path (pgvector) as follow-up (e1dfa7a).
38
+ - Promote auth helper and SQLite vector features to ready (ea1610b).
39
+ - Update documentation files in the working tree: docs/api/functions/client.createHooks.html, docs/api/functions/client.isAuthenticated.html, docs/api/functions/client.onMessage.html, docs/api/functions/client.sendMessage.html, docs/api/functions/server.Boolean.html, docs/api/functions/server.capsule.html, docs/api/functions/server.Date.html, docs/api/functions/server.endpoint.html, docs/api/functions/server.Json.html, docs/api/functions/server.message.html, docs/api/functions/server.mutation.html, docs/api/functions/server.Number.html, docs/api/functions/server.query.html, docs/api/functions/server.Reference.html, docs/api/functions/server.requireAuth.html, docs/api/functions/server.String.html, docs/api/functions/server.table.html, docs/api/types/client.AppMessage.html, docs/api/types/client.AppMessageStream.html, docs/api/types/client.AuthApi.html, docs/api/types/client.AuthProviders.html, docs/api/types/client.AuthState.html, docs/api/types/client.EmailCredentials.html, docs/api/types/client.FileMetadata.html, docs/api/types/client.FileReference.html, docs/api/types/client.FilesApi.html, docs/api/types/client.HookPrimitives.html, docs/api/types/client.JsonObject.html, docs/api/types/client.JsonValue.html, docs/api/types/client.MutationState.html, docs/api/types/client.PreferencesApi.html, docs/api/types/client.PreferencesResult.html, docs/api/types/client.ProviderState.html, docs/api/types/client.PublicFileUrl.html, docs/api/types/client.PublicUrlOptions.html, docs/api/types/client.QueryState.html, docs/api/types/client.SporadesError.html, docs/api/types/client.SporadesHooks.html, docs/api/types/client.SporadesResult.html, docs/api/types/client.Subscription.html, docs/api/types/client.UploadCompleteEvent.html, docs/api/types/client.UploadOptions.html, docs/api/types/client.UploadProgressEvent.html, docs/api/types/client.UseAuthState.html, docs/api/types/server.AclDatabaseHelpers.html, docs/api/types/server.AclHelpers.html, docs/api/types/server.AclStorageFileMetadata.html, docs/api/types/server.AclStorageHelpers.html, docs/api/types/server.AnyFieldDefinition.html, docs/api/types/server.AuthContext.html, docs/api/types/server.AutoFields.html, docs/api/types/server.Capsule.html, docs/api/types/server.CapsuleContext.html, docs/api/types/server.CapsuleDefinition.html, docs/api/types/server.CapsuleHooks.html, docs/api/types/server.ContextKind.html, docs/api/types/server.ContextMiddleware.html, docs/api/types/server.DatabaseFromSchema.html, docs/api/types/server.EndpointContext.html, docs/api/types/server.EndpointDefinition.html, docs/api/types/server.EndpointHandler.html, docs/api/types/server.EndpointOptions.html, docs/api/types/server.EndpointRequest.html, docs/api/types/server.FieldBuilder.html, docs/api/types/server.FieldDefinition.html, docs/api/types/server.FieldKind.html, docs/api/types/server.FieldValue.html, docs/api/types/server.InsertValues.html, docs/api/types/server.JsonValue.html, docs/api/types/server.Logger.html, docs/api/types/server.MaybePromise.html, docs/api/types/server.MessageApi.html, docs/api/types/server.MessageDefinition.html, docs/api/types/server.MessageHandler.html, docs/api/types/server.MessageScope.html, docs/api/types/server.MiddlewareContext.html, docs/api/types/server.MutationDefinition.html, docs/api/types/server.MutationHandler.html, docs/api/types/server.MutationHook.html, docs/api/types/server.MutationHookEvent.html, docs/api/types/server.MutationResult.html, docs/api/types/server.OrderDirection.html, docs/api/types/server.QueryDefinition.html, docs/api/types/server.QueryHandler.html, docs/api/types/server.ReferenceFieldBuilder.html, docs/api/types/server.ReferenceFieldDefinition.html, docs/api/types/server.RequireAuthOptions.html, docs/api/types/server.RowFromFields.html, docs/api/types/server.SchemaDefinition.html, docs/api/types/server.TableAclContext.html, docs/api/types/server.TableAclOperation.html, docs/api/types/server.TableAclRule.html, docs/api/types/server.TableAclRuleInput.html, docs/api/types/server.TableAclRules.html, docs/api/types/server.TableApi.html, docs/api/types/server.TableDefinition.html, docs/api/types/server.UpdateValues.html, docs/api/variables/client.auth.html, docs/api/variables/client.files.html, docs/api/variables/client.preferences.html.
40
+
41
+ ### 📦 Packaging
42
+
43
+ - Update packaging and release flow (6a5cd20).
package/Dockerfile.base CHANGED
@@ -8,9 +8,30 @@ LABEL com.sporades.base-image.name="sporades-base"
8
8
  LABEL com.sporades.base-image.version="0.1.0-node22-alpine"
9
9
  LABEL com.sporades.base-image.node="22"
10
10
 
11
- RUN addgroup -g 10001 -S sporades \
11
+ RUN apk add --no-cache openssh-server \
12
+ && addgroup -g 10001 -S sporades \
12
13
  && adduser -u 10001 -S sporades -G sporades \
13
- && mkdir -p /app /app/data \
14
+ && mkdir -p /app /app/data /run/sshd \
15
+ && ssh-keygen -A \
16
+ && printf '%s\n' \
17
+ '#!/bin/sh' \
18
+ 'set -eu' \
19
+ 'if [ -n "${SPORADES_SSH_AUTHORIZED_KEYS_PATH:-}" ] && [ -s "${SPORADES_SSH_AUTHORIZED_KEYS_PATH}" ]; then' \
20
+ ' target="${SPORADES_SSH_AUTHORIZED_KEYS_TARGET:-/app/data/ssh/authorized_keys}"' \
21
+ ' ssh_dir="$(dirname "$target")"' \
22
+ ' mkdir -p "$ssh_dir"' \
23
+ ' cp "${SPORADES_SSH_AUTHORIZED_KEYS_PATH}" "$target"' \
24
+ ' if [ ! -s "$ssh_dir/ssh_host_ed25519_key" ]; then' \
25
+ ' ssh-keygen -t ed25519 -f "$ssh_dir/ssh_host_ed25519_key" -N "" >/dev/null' \
26
+ ' fi' \
27
+ ' chmod 700 "$ssh_dir"' \
28
+ ' chmod 600 "$target"' \
29
+ ' chmod 600 "$ssh_dir/ssh_host_ed25519_key"' \
30
+ ' /usr/sbin/sshd -f /etc/ssh/sshd_config -o PasswordAuthentication=no -o PermitRootLogin=no -o AllowUsers=sporades -o AuthorizedKeysFile="$target" -o HostKey="$ssh_dir/ssh_host_ed25519_key" -o PidFile=/tmp/sporades-sshd.pid' \
31
+ 'fi' \
32
+ 'exec node /app/server.mjs' \
33
+ > /usr/local/bin/sporades-start \
34
+ && chmod 0755 /usr/local/bin/sporades-start \
14
35
  && chown -R 10001:10001 /app
15
36
 
16
37
  WORKDIR /app
package/README.md CHANGED
@@ -1,289 +1,242 @@
1
1
  # Sporades
2
2
 
3
- ## Documentation
3
+ <p align="center">
4
+ <a href="https://www.npmjs.com/package/sporades"><img alt="npm version" src="https://img.shields.io/npm/v/sporades.svg"></a>
5
+ <a href="https://www.npmjs.com/package/sporades"><img alt="npm downloads" src="https://img.shields.io/npm/dm/sporades.svg"></a>
6
+ <a href="https://nodejs.org/"><img alt="Node.js >=22" src="https://img.shields.io/badge/node-%3E%3D22-339933?logo=node.js&logoColor=white"></a>
7
+ <a href="https://github.com/mgscox/sporades/blob/main/LICENSE"><img alt="MIT license" src="https://img.shields.io/npm/l/sporades.svg"></a>
8
+ <a href="https://github.com/mgscox/sporades"><img alt="GitHub repository" src="https://img.shields.io/badge/GitHub-mgscox%2Fsporades-181717?logo=github"></a>
9
+ </p>
4
10
 
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
- ```
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/sporades">[npm package]</a>
13
+ </p>
32
14
 
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.
15
+ Designed as Agents-first, Sporades is a CLI-first platform for building, running, inspecting, and hosting
16
+ small full-stack web Capsules.
36
17
 
37
- Configure Google OAuth from explicit credentials:
18
+ It is intended for developers and coding Agents who want deterministic commands
19
+ instead of dashboards: scaffold a Capsule, run it locally, test the bundled
20
+ runtime in Docker, then publish it to a private or cloud Host server.
38
21
 
39
22
  ```sh
40
- sporades auth set google --client-id <id> --client-secret <secret>
23
+ # install sporades globally
24
+ npm install -g sporades
25
+ sporades -h
41
26
  ```
42
27
 
43
- Or pass a downloaded Google OAuth client JSON file:
28
+ Create a 'todo' app and spin-up a live-refresh dev server, a staging server on local docker service, and deploy to a remote production server:
44
29
 
45
30
  ```sh
46
- sporades auth set google --client-json ./client_secret_google.json
31
+ sporades create notes --template todo
32
+ cd notes
33
+ sporades dev
34
+ sporades deploy
35
+ sporades host push --restart
47
36
  ```
48
37
 
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`.
38
+ ## Why Sporades?
39
+
40
+ - **One command loop for humans and agents**: create, run, inspect, deploy, and
41
+ repair Capsules from the terminal.
42
+ - **Full-stack without the usual wiring**: server schema, queries, mutations,
43
+ auth, file uploads, custom endpoints, app messages, and browser hooks live
44
+ behind a small authoring API.
45
+ - **Same bundle everywhere**: Dev sessions, local Docker Container sessions, and
46
+ Hosted Capsules all run the same bundled server and client output.
47
+ - **Private-host friendly**: Host servers use boring, inspectable pieces:
48
+ Docker, Caddy, Node, SQLite, filesystem storage, and SSH.
49
+ - **Agent-operable by default**: structured JSON output, actionable error hints,
50
+ logs, database inspection, and non-interactive commands are first-class.
51
+ - **Comprehensive SDK**: built-in authentication, database, user preferences,
52
+ and storage.
53
+ - **Realtime everything**: configuration free - automatic notifications, even for Postgress and AWS-S3
54
+
55
+ ## Documentation
56
+
57
+ - [User guide](docs/user-guide.md): build, run, inspect, deploy, auth,
58
+ preferences, files, endpoints, messages, and common workflows.
59
+ - [Architecture](docs/architecture.md): platform model, runtime modes, Host
60
+ server design, and ownership boundaries.
61
+ - [Runtime layout](docs/runtime-layout.md): generated files, mounts, Host
62
+ directories, and persistent data locations.
63
+ - [Host server installation](docs/server-installation.md): prepare a Linux Host
64
+ server for Hosted Capsules.
65
+ - [Product requirements](docs/PRD.md): implemented scope, deferred scope, and
66
+ core product principles.
67
+ - [Roadmap](docs/ROADMAP.md): candidate features and promotion status.
52
68
 
53
- `sporades auth status --json` reports enabled providers and configuration state
54
- without printing OAuth client IDs or secrets.
69
+ ## A Tiny Capsule - the entire server code for a real-time 'to-do' app
55
70
 
56
- List currently connected browser clients for a running dev session:
71
+ ```ts
72
+ // Everything we need comes from Sporades server SDK
73
+ import {
74
+ Boolean,
75
+ String,
76
+ capsule,
77
+ mutation,
78
+ query,
79
+ table,
80
+ } from "sporades/server";
81
+
82
+ export default capsule({
83
+ name: "notes",
84
+
85
+ // tables automatically get createdAt, createdBy, and a unique Id
86
+ schema: {
87
+ // tables can have optional ACL - default is "open to all"
88
+ todos: table({
89
+ text: String(),
90
+ done: Boolean().default(false),
91
+ ownerId: String(),
92
+ }),
93
+ },
94
+
95
+ // queries are automatically write-through cached on the server
96
+ queries: {
97
+ // all todo entries for current user (anonymous by default)
98
+ todos: query((ctx) =>
99
+ // context 'ctx' contains user, db, storage, auth, etc.
100
+ ctx.db.todos
101
+ .where("ownerId", ctx.auth.userId)
102
+ .orderBy("createdAt", "desc")
103
+ .all(),
104
+ ),
105
+ },
106
+
107
+ mutations: {
108
+ addTodo: mutation((ctx, text: string) =>
109
+ ctx.db.todos.insert({ text, ownerId: ctx.auth.userId }),
110
+ ),
111
+ },
112
+ });
113
+ ```
114
+
115
+ Client scaffolds wire `sporades/client` into React or Preact hooks so the UI can
116
+ subscribe to queries and run mutations without hand-rolled fetch plumbing.
117
+
118
+ ## What You Get
119
+
120
+ Sporades currently includes:
121
+
122
+ - React and Preact project scaffolds with `blank`, `todo`, `guestbook`, and
123
+ `photo-library` templates.
124
+ - Local Dev sessions with rebuilds, WebSocket reconnects, SQLite persistence,
125
+ logs, database inspection, auth helpers, and file storage.
126
+ - Local Docker Container sessions for production-like staging tests.
127
+ - Hosted Capsules on SSH-reachable Host servers, routed through Caddy and run in
128
+ hardened Docker containers.
129
+ - Runtime-owned auth with anonymous sessions, email auth, Google OAuth, and
130
+ local identity simulation for tests and agents.
131
+ - Runtime-owned current-user preferences exposed through `sporades/client`
132
+ without app preference tables. Preferences follow the Sporades user identity,
133
+ survive Anonymous account linking, and notify same-user connected clients when
134
+ they change.
135
+ - File uploads with private reads, explicit public URLs, replacement versions,
136
+ and local MinIO-backed storage support.
137
+ - Custom HTTP endpoints and app messages over the Sporades client transport.
138
+ - Sealed Server env for public/private key encrypted server-only environemt variables, keeping them out of browser bundles and
139
+ release archives.
140
+
141
+ File reads stay behind Sporades HTTP routes. They are not filesystem paths or presigned MinIO/S3 URLs.
142
+ Capsules can opt into local MinIO storage with
143
+ `services.storage.engine: "minio"`; omitted upload paths use the file name in
144
+ the Default File bucket, falling back to the logical `/default/upload` File path when no file name exists.
145
+ The Default File bucket is only a namespace fallback, not a user bucket or policy boundary.
146
+
147
+ ## Install
148
+
149
+ Sporades requires Node.js 22 or newer. Install the CLI from npm:
57
150
 
58
151
  ```sh
59
- sporades auth clients --json
152
+ npm install --global sporades
153
+ sporades --help
60
154
  ```
61
155
 
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.
156
+ Sporades depends on `esbuild` for Capsule bundling. On newer npm versions, you
157
+ may see an `allow-scripts` warning during global install because `esbuild` uses
158
+ a postinstall script to select the native binary for your platform.
65
159
 
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`:
160
+ If npm blocks that script and bundling later fails, reinstall while explicitly
161
+ allowing the `esbuild` install script:
69
162
 
70
163
  ```sh
71
- sporades auth as email --email mira@example.com --display-name "Mira Vale" --json
164
+ npm install --global sporades --allow-scripts=esbuild
72
165
  ```
73
166
 
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:
167
+ To approve `esbuild` persistently for your user npm config:
78
168
 
79
169
  ```sh
80
- sporades auth as email --email mira@example.com --client current --json
170
+ npm config set allow-scripts=esbuild --location=user
81
171
  ```
82
172
 
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:
173
+ When working in this repository, plain `npm install` uses the project
174
+ `allowScripts` entry in `package.json` to approve the reviewed `esbuild`
175
+ install script. If `esbuild` is upgraded, review and refresh that approval with:
105
176
 
106
177
  ```sh
107
- # stop the current dev session, then run:
108
- sporades dev
178
+ npm approve-scripts esbuild
109
179
  ```
110
180
 
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:
181
+ Generated Capsules use the same `sporades` command for local Dev sessions,
182
+ Container sessions, inspection, and Host operations.
113
183
 
114
- ```tsx
115
- import { auth } from "sporades/client";
116
184
 
117
- const signUp = await auth.signUp("email", {
118
- email: "mira@example.com",
119
- password: "correct horse battery staple",
120
- name: "Mira",
121
- });
185
+ ## Quick Start
122
186
 
123
- const signIn = await auth.signIn("email", {
124
- email: "mira@example.com",
125
- password: "correct horse battery staple",
126
- });
187
+ Create a Capsule:
127
188
 
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
- }
189
+ ```sh
190
+ sporades create notes --template todo
191
+ cd notes
133
192
  ```
134
193
 
135
- Google continues to use the same sign-in surface and starts the server-owned
136
- redirect flow:
194
+ Run a local Dev session:
137
195
 
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
- }
196
+ ```sh
197
+ sporades dev
157
198
  ```
158
199
 
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:
200
+ Inspect it from another terminal:
195
201
 
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
- }));
202
+ ```sh
203
+ sporades logs
204
+ sporades db list
205
+ sporades db dump --json
208
206
  ```
209
207
 
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.
208
+ Test the bundled Capsule in Docker:
215
209
 
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
- });
210
+ ```sh
211
+ sporades deploy
234
212
  ```
235
213
 
236
- ```ts
237
- // client/index.tsx
238
- import { onMessage, sendMessage } from "sporades/client";
214
+ Add a Host profile and publish when you have a Host server ready:
239
215
 
240
- await sendMessage("typing", { roomId: "general", active: true });
216
+ ```sh
217
+ sporades host add personal \
218
+ --server user@example.com \
219
+ --domain apps.example.com
241
220
 
242
- const subscription = onMessage()
243
- .filter((message) => message.type === "typing")
244
- .subscribe((message) => {
245
- console.log(message.data);
246
- });
221
+ sporades host bootstrap --host personal --json
222
+ sporades host register notes --host personal --json
223
+ sporades host push --host personal --subname notes --verify --json
247
224
  ```
248
225
 
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.
226
+ ## Project Status
258
227
 
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.
228
+ Sporades is early, active platform work. It is useful for fast prototypes,
229
+ agent-driven app loops, local production-like testing, and private hosted
230
+ Capsules, but it is not trying to be a full production platform (yet). Check the
231
+ [Roadmap](docs/ROADMAP.md) for in-flight and planned features.
262
232
 
263
- ## Context middleware
233
+ The current focus is keeping the authoring surface small, the runtime
234
+ inspectable, and the operational path friendly to both developers and agents.
235
+ Container SSH access is opt-in for local Container sessions and Hosted
236
+ Capsules through authorized public keys in `sporades.json`. Normal management
237
+ remains the CLI and Host helper surfaces; SSH is a compatibility and emergency
238
+ access path.
264
239
 
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
- ```
240
+ ## License
284
241
 
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.
242
+ MIT