Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
1962a31
feat(platform): commit the live updates AsyncAPI document and a sync …
amitbardos Oct 9, 2026
682d6a1
feat(platform): generate the event types from the AsyncAPI document
amitbardos Oct 9, 2026
4a3c3cb
ci(platform): fail when the generated event types drift from asyncapi…
amitbardos Oct 9, 2026
bbec256
docs: run the Mintlify docs pipeline for @base44/platform
amitbardos Oct 9, 2026
a77eb5e
docs(platform): rewrite the JSDoc in the app SDK's voice
amitbardos Oct 9, 2026
09ff322
docs: order reference nav groups by the category map
amitbardos Oct 9, 2026
2c7f292
docs(platform): shape the reference like the app SDK's
amitbardos Oct 9, 2026
b5a370e
chore(platform): lock json-schema-to-typescript from the public registry
amitbardos Oct 9, 2026
5af19dd
docs(platform): use one session route in examples and drop the guesse…
amitbardos Oct 9, 2026
5705430
feat(platform): tie the server's error codes to the generated events
amitbardos Oct 9, 2026
98287c9
docs(platform): tell onEvent handlers to ignore event types they don'…
amitbardos Oct 9, 2026
823dcc0
docs: describe page-sections.json and nav group overrides in writing-…
amitbardos Oct 9, 2026
3f95610
docs: stop class pages from listing their properties twice
amitbardos Oct 9, 2026
85b09ea
docs(platform): link the builder property to its module page, like th…
amitbardos Oct 9, 2026
bad0a8f
docs: keep the return type of a method that returns a methods-only in…
amitbardos Oct 9, 2026
a6a7c2b
docs(platform): group error codes by where they arrive
amitbardos Oct 9, 2026
d03b25a
docs(platform): title the error codes section and link session and ev…
amitbardos Oct 9, 2026
f3e1897
docs(platform): explain where PlatformSocketError arrives and how cod…
amitbardos Oct 9, 2026
170b7da
docs: link a Returns type to the section of the same page that docume…
amitbardos Oct 9, 2026
46bcc75
docs: render a class page's properties as fields, without separators
amitbardos Oct 9, 2026
66ff066
docs(platform): name BuilderSession in its description
amitbardos Oct 9, 2026
eb985bb
feat(platform)!: replace Base44PlatformClient with createPlatformClie…
amitbardos Oct 10, 2026
5a411a0
feat(platform)!: rename serverUrl to socketUrl
amitbardos Oct 10, 2026
e6861e0
docs(platform): shorten the getSessionToken description
amitbardos Oct 10, 2026
fffdebc
docs(platform): drop the internals sentence from createPlatformClient
amitbardos Oct 10, 2026
446cd2c
docs: say a linked Returns type's description once, without separators
amitbardos Oct 10, 2026
577a912
docs(platform): say which onError gets which codes, and drop Extends …
amitbardos Oct 10, 2026
be82e6f
docs(platform): drop the What to do label from the error codes
amitbardos Oct 10, 2026
fed76b1
docs: remove pipeline code the platform docs no longer use
amitbardos Oct 10, 2026
3090447
docs(platform): render the builder events page in the SDK's docs pipe…
amitbardos Oct 10, 2026
dd8b394
fix(platform): PlatformSnapshot's queue is optional, as decodeSnapsho…
amitbardos Oct 12, 2026
419e113
docs(platform): leave the Socket messages page to mintlify-docs
amitbardos Oct 12, 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
3 changes: 3 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,8 @@ jobs:
- name: Run unit tests
run: npm run test:unit -w @base44/sdk

- name: Check the Platform SDK event types match asyncapi.json
run: npm run check:events -w @base44/platform

- name: Test the Platform SDK
run: npm test -w @base44/platform
104 changes: 104 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

101 changes: 47 additions & 54 deletions packages/platform/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ shared socket. It is independent of the app SDK (`@base44/sdk`).
The workspace must have white-label sockets enabled; this SDK does not open sessions.

```ts
import { Base44PlatformClient } from "@base44/platform";
import { createPlatformClient } from "@base44/platform";

const client = new Base44PlatformClient({
serverUrl: "https://base44.app",
const client = createPlatformClient({
socketUrl, // the session's socket_url, from POST /api/service/socket-sessions
async getSessionToken() {
const response = await fetch("/api/builder-socket-session", { method: "POST" });
if (!response.ok) throw new Error("Session request failed");
Expand All @@ -39,7 +39,7 @@ builder.close();
`/api/builder-socket-session` is your backend's route, not an SDK endpoint. It opens a
socket session with its workspace key (`apps:watch` scope) through
`POST /api/service/socket-sessions` with `{ app_ids }`, and returns only the
`session_token` to the browser. Never pass the workspace key or any other server
`session_token` and `socket_url` to the browser. Pass `socket_url` as `socketUrl`. Never pass the workspace key or any other server
credential to this client. Subscribe only to apps on the session's allowlist; your
backend adds or removes apps with `PUT`/`DELETE /api/service/socket-sessions/{session_id}/rooms/{app_id}`.

Expand All @@ -55,7 +55,7 @@ older one, which stops with `session_replaced`. Open one session per page.

## Lifecycle

`new Base44PlatformClient({ serverUrl, getSessionToken })` creates a lightweight module
`createPlatformClient({ socketUrl, getSessionToken })` creates a lightweight module
container: no Socket.IO instance, timers, token request or network activity. Its
`builder` module follows the server SDK's module-factory pattern.

Expand Down Expand Up @@ -94,60 +94,53 @@ their exceptions are isolated so they cannot interrupt another app's delivery.

## Public shapes

- `PlatformClientOptions` (`client.types.ts`): `serverUrl`, `getSessionToken`.
- `PlatformClientOptions` (`client.types.ts`): `socketUrl`, `getSessionToken`.
- `BuilderModule` (`modules/builder.types.ts`): lazy `init(options)` factory.
- `BuilderInitOptions`: connection-level `onError` observer.
- `BuilderSession`: `connect()`, `subscribe(appId, options)`, `close()`.
- `SubscriptionOptions`: required `onSnapshot`, `onEvent` and `onError`.
- `PlatformSubscription`: read-only `appId`, `active`, and `unsubscribe()`.
- Event/message models live in `modules/builder.events.types.ts`; error codes in `errors.types.ts`.

Every exported shape and field has JSDoc. Run `npm run docs` for
validated reference pages in `docs`. `PlatformEvent` is a
**discriminated union**: switch on `event.type` to narrow `event.data`. Each delivery
contains `type`, `appId` and `data`. On the wire every event is `{room, data}`; the SDK
routes by `room` and validates the envelope, while payload schemas and private-field
filtering are the server's. Omitted keys mean unchanged and explicit null clears.
Events carrying `branch_id` belong to that branch; null or absent is main. Unknown
event names are not forwarded.

| Event | Data contract |
| --- | --- |
| `message.updated` | `MessageUpdated`: the whole `message`, optional `conversation_id`. Replace the message with the same `id`. |
| `message.removed` | `MessageRemoved`: `message_id`, optional `conversation_id`. Sent when a message is deleted or hidden; an unknown ID is a no-op. |
| `app.status_changed` | `AppStatusChanged`: `status` (`AppStatus`), null clears it. |
| `preview.reload_requested` | Reload the app preview. |
| `preview.navigation_requested` | `PreviewNavigationRequested`: `path`, and `force` to navigate even when the user moved away. |
| `queue.updated` | `QueueUpdated`: `items`, `is_paused`, optional `processed_item_id`. Replaces the entire queue. |
| `task.progressed` | `TaskProgressed`: optional `tool_call_id`, `message_id`, `event_type`, numeric `progress`. |
| `image.resolved` | `ImageResolved`: `placeholder_url`, `status` (`pending`, `completed`, `failed`), nullable `image_url`. Replace the placeholder wherever it appears. |
| `conversation.changed` | The conversation was rewritten. On main, a fresh snapshot follows. |
| `files.changed`, `branch.deleted`, `repository.changed`, `pull_request.changed` | Re-read signals with no content. |

`ChatMessage` has optional `id`, `role` (`user`/`assistant`), text/null `content`,
`tool_calls`, timestamp-only `metadata.created_date`, `checkpoint_id`, and scalar
`additional_message_params` (`client_creation_id`, `plan_mode`, `plan_approved`,
`skip_ai_response`, `system_message_type`). `ToolCall` has optional `id`, `name`,
`status`, `requires_user_input`, `auto_approved`, `mutation_applied` and
`waiting_on.kind` (`approval`/`choice`/`input`). Each builder tool and guard declares
which of its parts are public; a tool with no declaration shows only these fields.

| Field | Public contract |
| --- | --- |
| `arguments` | One of `ToolQuestionArguments`, `ToolSecretArguments`, `ToolPackageArguments`, `ToolPlanArguments`, `ToolPrdArguments` (`generate_prd`, the plan in plan mode) or `ToolMediaArguments`, narrowed by the tool's `name`. Absent for other tools and partial streaming arguments. |
| `display` | File activity has `file_paths` and optional `content_empty`; execution activity has `summary` and `writes_entities`; entity activity has `entity_name` and `record_count`. |
| `user_input` | `ToolQuestionInput` only, for clarifying-question answers (`selected_label` for single-select, `selected_labels` for multi-select). Secret-form values are never exposed. |
| `results` | Once the call is not waiting on a guard: `ToolMediaResult` for image generation, or a URL string for game images and backgrounds (the placeholder `image.resolved` replaces) and videos (the finished video). |
| `approval` | While a call waits on a safety guard: `ToolGuardApproval` with `guard`, and `reason` and `details` where the guard declares them (shell-command approvals carry only `guard`). |

In plan mode the message `content` beside a `generate_prd` call can be empty; render the
plan from its arguments. Approving a plan, answering a question and approving a
guarded call are HTTP actions of your backend. `Snapshot` contains `room`, `status`
(`AppStatus`: optional `state` of `ready`/`processing`/`error`, nullable
`last_updated_date`, and nullable `turn_id`, the user message that started the turn),
`messages`, and `queue` (`SnapshotQueue`: the main branch's `items` and `is_paused`;
absent from older servers). Dates remain wire strings. Structural filtering
does not promise redaction of generated prose or user content.
- `PlatformEvent`, `PlatformEventMap`, `PlatformSnapshot`, `ToolCall`, `GuardApproval`
(`modules/builder.events.types.ts`), derived from the generated event types.
- Error codes in `errors.types.ts`.

`PlatformEvent` is a **discriminated union**: switch on `event.type` to narrow
`event.data`. Each delivery contains `type`, `appId` and `data`. On the wire every
event is `{room, data}`; the SDK routes by `room` and validates the envelope, while
payload schemas and private-field filtering are the server's. Omitted keys mean
unchanged and explicit null clears. Events carrying `branch_id` belong to that branch;
null or absent is main. Unknown event names are not forwarded. `PlatformSnapshot` is
the service's `Snapshot` plus the app's `room`. Structural filtering does not promise
redaction of generated prose or user content.

## Event types

The socket protocol is defined by the service's AsyncAPI document, committed as
`asyncapi.json`. `src/modules/builder.events.generated.ts` is generated from it with
`json-schema-to-typescript`: one type per schema, named after the backend model
(`Message`, `StatusObject`, `QueueState`, one `*Call` per tool, one `*Approval` per
guard), plus `ServerEventMap` and `ClientMessageMap`. Never edit it by hand.

To pick up a protocol change:

```sh
# Production serves only beta and GA events, so point at a dev backend while events are alpha.
npm run sync:asyncapi -w @base44/platform -- http://localhost:8000/api/asyncapi.json
npm run gen:events -w @base44/platform
```

`sync:asyncapi` also reads `ASYNCAPI_URL`, defaults to production, and refuses a
document with no messages. CI runs `npm run check:events` and fails when the
generated file differs from what `asyncapi.json` produces.

## Reference docs

Every exported shape and field has JSDoc. `npm run docs` validates it into `docs`.
`npm run create-docs` runs the app SDK's TypeDoc and Mintlify pipeline for this package,
and `npm run copy-docs-local -- --target <mintlify-docs>` copies the result into
`developers/references/platform-sdk/docs`. The pipeline's config for this package lives
in `scripts/mintlify-post-processing/`. The Socket messages page is built in mintlify-docs from
the service's AsyncAPI document, not here.

`PlatformSocketError` extends `Error` with `code` and optional `appId`. Original
exceptions, payloads and credentials are never attached.
Expand Down
Loading
Loading