Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0ea1898
reboot: support the `PUT` method for custom HTTP routes
rjhuijsman Jul 5, 2026
82c3e35
reboot: run library `pre_run` hooks in the test harness
rjhuijsman Jul 5, 2026
22b37df
`reboot/std`: add a `Blob` state machine
rjhuijsman Jul 7, 2026
862581c
rbt: run the filesystem blob data plane under `dev`/`serve run`
rjhuijsman Jul 7, 2026
d760224
reboot/std/react: add browser helpers for blob upload and download
rjhuijsman Jul 5, 2026
a9e9966
reboot/examples/chat-room: support message attachments
rjhuijsman Jul 5, 2026
169da9f
`reboot`: let a library contribute plain gRPC servicers
rjhuijsman Sep 10, 2026
43624f0
`reboot`: remove an untrusted caller ID on every route, not one
rjhuijsman Sep 11, 2026
3bf4f37
`reboot/std`: give the filesystem store's metadata a type
rjhuijsman Sep 9, 2026
fdb86a4
`reboot/std`: stop writing part uploads from the event loop
rjhuijsman Sep 9, 2026
8b72152
`reboot/std`: make the filesystem blob data plane part of the applica…
rjhuijsman Sep 10, 2026
91c22ef
`reboot/std`: say absent rather than empty in the `Blob` API
rjhuijsman Sep 11, 2026
cd6f821
`tests`: wait on blob state reactively rather than by polling
rjhuijsman Sep 11, 2026
caa3204
`documentation`: address review comments
rjhuijsman Sep 11, 2026
e8b9282
`documentation`: close the `from_react` snippet's `if`
rjhuijsman Sep 11, 2026
23f8e48
Address review comments
rjhuijsman Sep 14, 2026
a51d06e
Address review comments
rjhuijsman Sep 14, 2026
0edeb5b
`reboot/examples/chat-room`: say how `Send` holds its lock
rjhuijsman Sep 14, 2026
0eb85a4
`reboot/std`: move the filesystem data plane's bookkeeping into its s…
rjhuijsman Sep 15, 2026
1d14968
`reboot/std`: serve `BlobDataPlane` with one servicer for every store
rjhuijsman Sep 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .claude/rules/python-annotate-return-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
paths:
- "**/*.py"
---

# Annotate every function's return type

Every Python function and method you write or touch gets a return
type annotation, `-> None` included, wherever one can be written. The
same goes for parameters: annotate them unless the type genuinely
cannot be named. Generated gRPC servicer methods are no exception —
annotate `request` with its message type and the method with its
response type.

**Why:** A missing return annotation makes `mypy` treat the function
as returning `Any`, which silently switches off type checking for
everything the result flows into. It also leaves the reader to
reconstruct from the body what a method hands back — a method named
`_caller` that returned an `ExternalContext` went unnoticed in review
until someone asked.

**How to apply:** Before committing, scan the diff for `def` lines
without `->`. When a function returns nothing, write `-> None`. When
the natural return type needs an import (a `_pb2` message, an
`Optional[...]`), add the import rather than leaving the annotation
off. The only acceptable omission is a signature whose type cannot be
expressed without a `# type: ignore`, and that deserves a comment.
49 changes: 38 additions & 11 deletions documentation/docs/call/from_react.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ export const DashboardApp: FC<DashboardConfig> = ({ personalizedMessage }) => {
You can call a `reader` _reactively_ very simply:

<!-- MARKDOWN-AUTO-DOCS:START
(CODE:src=../../../reboot/examples/chat-room/frontend/web/src/App.tsx&&lines=33-34) -->
(CODE:src=../../../reboot/examples/chat-room/frontend/web/src/App.tsx&&lines=160-161) -->
<!-- The below code snippet is automatically added from ../../../reboot/examples/chat-room/frontend/web/src/App.tsx -->

```tsx
Expand Down Expand Up @@ -332,7 +332,7 @@ and [`transaction`](/define/methods#kinds) methods
are both callable from React.

<!-- MARKDOWN-AUTO-DOCS:START
(CODE:src=../../../reboot/examples/chat-room/frontend/web/src/App.tsx&&lines=33-33) -->
(CODE:src=../../../reboot/examples/chat-room/frontend/web/src/App.tsx&&lines=160-160) -->
<!-- The below code snippet is automatically added from ../../../reboot/examples/chat-room/frontend/web/src/App.tsx -->

```tsx
Expand All @@ -349,11 +349,17 @@ This line calls the
definition, using its lower camel case name:

<!-- MARKDOWN-AUTO-DOCS:START
(CODE:src=../../../reboot/examples/chat-room/frontend/web/src/App.tsx&&lines=37-37) -->
(CODE:src=../../../reboot/examples/chat-room/frontend/web/src/App.tsx&&lines=181-187) -->
<!-- The below code snippet is automatically added from ../../../reboot/examples/chat-room/frontend/web/src/App.tsx -->

```tsx
const { aborted } = await send({ message: message });
const { response, aborted } = await send({
message: message,
attachments:
file !== null
? [{ contentType: file.type, sizeBytes: BigInt(file.size) }]
: [],
});
```

<!-- MARKDOWN-AUTO-DOCS:END -->
Expand All @@ -370,12 +376,17 @@ Reboot attaches all in-flight mutations to a `.pending` property of every mutato
facilitate this, for example:

<!-- MARKDOWN-AUTO-DOCS:START
(CODE:src=../../../reboot/examples/chat-room/frontend/web/src/App.tsx&&lines=81-83) -->
(CODE:src=../../../reboot/examples/chat-room/frontend/web/src/App.tsx&&lines=306-313) -->
<!-- The below code snippet is automatically added from ../../../reboot/examples/chat-room/frontend/web/src/App.tsx -->

```tsx
{send.pending.map(({ request: { message }, isLoading }) => (
<PendingMessage text={message} isLoading={isLoading} key={message} />
{send.pending.map(({ request, isLoading }, index) => (
<PendingMessage
text={request.message ?? ""}
attachmentCount={request.attachments?.length ?? 0}
isLoading={isLoading}
key={index}
/>
))}
```

Expand All @@ -402,14 +413,30 @@ Every call to a mutator returns both a `response` and an `aborted`; successful c
[Learn more about Reboot errors and error types.](/errors)

<!-- MARKDOWN-AUTO-DOCS:START
(CODE:src=../../../reboot/examples/chat-room/frontend/web/src/App.tsx&&lines=37-41) -->
(CODE:src=../../../reboot/examples/chat-room/frontend/web/src/App.tsx&&lines=181-201) -->
<!-- The below code snippet is automatically added from ../../../reboot/examples/chat-room/frontend/web/src/App.tsx -->

```tsx
const { aborted } = await send({ message: message });
const { response, aborted } = await send({
message: message,
attachments:
file !== null
? [{ contentType: file.type, sizeBytes: BigInt(file.size) }]
: [],
});
if (aborted !== undefined) {
console.warn(aborted.error.getType());
console.warn(aborted.message);
// Surface the failure to the user. The backend's
// `AttachmentTooLarge` error carries the limit, so we can say
// exactly what it is.
if (aborted.error instanceof AttachmentTooLarge) {
const maxMebibytes = Number(aborted.error.maxSizeBytes) / (1024 * 1024);
setError(
`That attachment is too large. The maximum is ${maxMebibytes} MiB.`
);
} else {
setError(`Couldn't send your message: ${aborted.message}`);
}
return;
}
```

Expand Down
14 changes: 6 additions & 8 deletions documentation/docs/call/from_within_your_app.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -84,18 +84,16 @@ via `write()` — see
<Tabs groupId="language">
<TabItem value="python" label="Python" default>
<!-- MARKDOWN-AUTO-DOCS:START
(CODE:src=../../../reboot/examples/chat-room/backend/src/chat_room_servicer.py&lines=24-32) -->
(CODE:src=../../../reboot/examples/chat-room/backend/src/chat_room_servicer.py&lines=23-28) -->
<!-- The below code snippet is automatically added from ../../../reboot/examples/chat-room/backend/src/chat_room_servicer.py -->

```py
async def send(
async def messages(
self,
context: WriterContext,
request: SendRequest,
) -> SendResponse:
message = request.message
self.state.messages.extend([message])
return SendResponse()
context: ReaderContext,
request: MessagesRequest,
) -> MessagesResponse:
return MessagesResponse(messages=self.state.messages)
Comment thread
reboot-dev-bot marked this conversation as resolved.
```

<!-- MARKDOWN-AUTO-DOCS:END -->
Expand Down
30 changes: 23 additions & 7 deletions documentation/docs/implement/application.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,17 @@ Your entrypoint will construct an `Application` and then `run` it:
<Tabs groupId="language">
<TabItem value="python" label="Python">
<!-- MARKDOWN-AUTO-DOCS:START
(CODE:src=../../../reboot/examples/chat-room/backend/src/main.py&lines=25-33) -->
(CODE:src=../../../reboot/examples/chat-room/backend/src/main.py&lines=26-32) -->
<!-- The below code snippet is automatically added from ../../../reboot/examples/chat-room/backend/src/main.py -->

```py
async def main():
await Application(
servicers=[ChatRoomServicer],
# Message attachments are stored as blobs.
libraries=[blob_library()],
initialize=initialize,
).run()


if __name__ == '__main__':
asyncio.run(main())
```

<!-- MARKDOWN-AUTO-DOCS:END -->
Expand All @@ -47,6 +45,24 @@ new Application({
</TabItem>
</Tabs>

### `libraries`

The optional `libraries` argument brings in [library
services](/library_services/overview): state machines that Reboot
implements for you, which your application serves alongside its own.
The example above uses `blob_library()`, which gives the application
the `Blob` state machine its message attachments are stored in.

A library service may need somewhere to keep what it holds. Blobs keep
bytes, and a Python application serves them itself from a data plane
on the local filesystem, under the application's state directory, so
`rbt dev run` and `rbt serve run` need nothing more. Setting
`REBOOT_BLOB_DATA_PLANE_URL` points an application at a different data
plane instead -- on Reboot Cloud, that is how blobs come to live in
object storage. A TypeScript application cannot serve the local data
plane yet, so it needs `REBOOT_BLOB_DATA_PLANE_URL` set, and refuses to
start without it.

### `initialize` functions

The optional `initialize` argument to the `Application` constructor is a function that
Expand All @@ -59,7 +75,7 @@ instances used by your application are created.
<Tabs groupId="language">
<TabItem value="python" label="Python">
<!-- MARKDOWN-AUTO-DOCS:START
(CODE:src=../../../reboot/examples/chat-room/backend/src/main.py&lines=13-20) -->
(CODE:src=../../../reboot/examples/chat-room/backend/src/main.py&lines=14-21) -->
<!-- The below code snippet is automatically added from ../../../reboot/examples/chat-room/backend/src/main.py -->

```py
Expand Down Expand Up @@ -165,7 +181,7 @@ implemented with [Express.js](https://expressjs.com/).

**Limitations of custom HTTP routes**

* Currently only `GET` and `POST` methods are supported.
* Currently only `GET`, `POST`, and `PUT` methods are supported.

* The `/` route is currently used by Reboot itself to
show a helpful page explaining that this is a Reboot
Expand Down
7 changes: 5 additions & 2 deletions documentation/docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,16 @@ async def test_chat_room(self) -> None:
await chat_room.send(context, message="Hello, World")

response: ChatRoom.MessagesResponse = await chat_room.messages(context)
self.assertEqual(response.messages, ["Hello, World"])
self.assertEqual(
[message.text for message in response.messages],
["Hello, World"],
)

await chat_room.send(context, message="Hello, Reboot!")
await chat_room.send(context, message="Hello, Peace of Mind!")
response = await chat_room.messages(context)
self.assertEqual(
response.messages,
[message.text for message in response.messages],
[
"Hello, World",
"Hello, Reboot!",
Expand Down
4 changes: 4 additions & 0 deletions rbt/std/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ ts_project(
},
visibility = ["//visibility:public"],
deps = [
"//rbt/std/blob/v1:blob_js_proto",
"//rbt/std/blob/v1:blob_js_reboot",
"//rbt/std/blob/v1:blob_js_reboot_react",
"//rbt/std/blob/v1:blob_js_reboot_web",
"//rbt/std/ciphertext/v1:ciphertext_js_proto",
"//rbt/std/ciphertext/v1:ciphertext_js_reboot",
"//rbt/std/collections/ordered_map/v1:ordered_map_js_proto",
Expand Down
134 changes: 134 additions & 0 deletions rbt/std/blob/v1/BUILD.bazel
Comment thread
reboot-dev-bot marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
load(
"@com_github_reboot_dev_reboot//reboot:rules.bzl",
"js_proto_library",
"js_reboot_library",
"js_reboot_react_library",
"js_reboot_web_library",
"py_reboot_library",
)
load("@com_google_protobuf//bazel:proto_library.bzl", "proto_library")

proto_library(
name = "blob_proto",
srcs = [
":blob.proto",
],
visibility = ["//visibility:public"],
deps = [
"@com_github_reboot_dev_reboot//rbt/v1alpha1:options_proto",
"@com_google_protobuf//:descriptor_proto",
],
)

# The blob data-plane interface: a plain gRPC service (no Reboot state
# options). Built with `py_reboot_library` so that, besides the plain
# `_pb2`/`_pb2_grpc` modules, it emits the `_rbt` module that lets a
# Reboot application host an implementation via
# `legacy_grpc_servicers`. Python-only: the JS SDK talks to the `Blob`
# control plane, never the data plane directly.
proto_library(
name = "data_plane_proto",
srcs = [
":data_plane.proto",
],
visibility = ["//visibility:public"],
)

py_reboot_library(
name = "data_plane_py_reboot",
proto = "data_plane.proto",
proto_library = ":data_plane_proto",
visibility = ["//visibility:public"],
)

# The filesystem data plane's own metadata state. Python-only: it is
# an implementation detail of one data plane, not part of any client's
# API.
proto_library(
name = "filesystem_proto",
srcs = [
":filesystem.proto",
],
visibility = ["//visibility:public"],
deps = [
"@com_github_reboot_dev_reboot//rbt/v1alpha1:options_proto",
"@com_google_protobuf//:descriptor_proto",
],
)

py_reboot_library(
name = "filesystem_py_reboot",
proto = "filesystem.proto",
proto_library = ":filesystem_proto",
visibility = ["//visibility:public"],
)

py_reboot_library(
name = "blob_py_reboot",
proto = "blob.proto",
proto_library = ":blob_proto",
visibility = ["//visibility:public"],
)

js_proto_library(
name = "blob_js_proto",
package_json = ":package.json",
proto = "blob.proto",
proto_deps = [
":blob_proto",
# ISSUE(https://github.com/reboot-dev/mono/issues/3218): Until we can
# use `create_protoc_plugin_rule` we need to repeat the dependencies of
# the `proto_libraries` here.
"@com_github_reboot_dev_reboot//rbt/v1alpha1:options_proto",
"@com_google_protobuf//:descriptor_proto",
],
visibility = ["//visibility:public"],
)

js_reboot_library(
name = "blob_js_reboot",
srcs = [
":blob_proto",
],
proto = "blob.proto",
visibility = ["//visibility:public"],
deps = [
":blob_js_proto",
],
)

# The browser client: the same API as the React client, but callable
# from anywhere rather than only from inside a component.
js_reboot_web_library(
name = "blob_js_reboot_web",
proto = "blob.proto",
proto_deps = [
":blob_proto",
# ISSUE(https://github.com/reboot-dev/mono/issues/3218): Until we can
# use `create_protoc_plugin_rule` we need to repeat the dependencies of
# the `proto_libraries` here.
"@com_github_reboot_dev_reboot//rbt/v1alpha1:options_proto",
"@com_google_protobuf//:descriptor_proto",
],
visibility = ["//visibility:public"],
deps = [
":blob_js_proto",
],
)

js_reboot_react_library(
name = "blob_js_reboot_react",
srcs = [
":blob_js_proto",
],
proto = "blob.proto",
proto_deps = [
":blob_proto",
# ISSUE(https://github.com/reboot-dev/mono/issues/3218): Until we can
# use `create_protoc_plugin_rule` we need to repeat the dependencies of
# the `proto_libraries` here.
"@com_github_reboot_dev_reboot//rbt/v1alpha1:options_proto",
"@com_google_protobuf//:descriptor_proto",
],
visibility = ["//visibility:public"],
)
Loading
Loading