@base44-preview/platform 0.0.0 → 0.1.0-pr.316.3f570db
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/LICENSE +21 -0
- package/README.md +171 -0
- package/dist/client/client.d.ts +9 -0
- package/dist/client/client.js +9 -0
- package/dist/client/client.types.d.ts +12 -0
- package/dist/client/client.types.js +1 -0
- package/dist/client/errors.d.ts +10 -0
- package/dist/client/errors.js +10 -0
- package/dist/client/errors.types.d.ts +2 -0
- package/dist/client/errors.types.js +1 -0
- package/dist/client/index.d.ts +7 -0
- package/dist/client/index.js +3 -0
- package/dist/client/modules/builder-protocol.d.ts +15 -0
- package/dist/client/modules/builder-protocol.js +34 -0
- package/dist/client/modules/builder-socket.d.ts +42 -0
- package/dist/client/modules/builder-socket.js +252 -0
- package/dist/client/modules/builder-subscription.d.ts +23 -0
- package/dist/client/modules/builder-subscription.js +55 -0
- package/dist/client/modules/builder.d.ts +4 -0
- package/dist/client/modules/builder.events.types.d.ts +368 -0
- package/dist/client/modules/builder.events.types.js +1 -0
- package/dist/client/modules/builder.js +7 -0
- package/dist/client/modules/builder.types.d.ts +47 -0
- package/dist/client/modules/builder.types.js +1 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +2 -0
- package/dist/server/server.d.ts +3 -0
- package/dist/server/server.js +8 -0
- package/dist/server/server.types.d.ts +10 -0
- package/dist/server/server.types.js +1 -0
- package/dist/shared/server-url.d.ts +2 -0
- package/dist/shared/server-url.js +8 -0
- package/package.json +56 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 base44
|
|
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,171 @@
|
|
|
1
|
+
# @base44/platform
|
|
2
|
+
|
|
3
|
+
```sh
|
|
4
|
+
npm install @base44/platform
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
This package implements the read-only white-label builder socket contract: a
|
|
8
|
+
partner's browser watches the builder chat of up to eight apps over the platform's
|
|
9
|
+
shared socket. It is independent of the app SDK (`@base44/sdk`).
|
|
10
|
+
The workspace must have white-label sockets enabled; this SDK does not open sessions.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { Base44PlatformClient } from "@base44/platform";
|
|
14
|
+
|
|
15
|
+
const client = new Base44PlatformClient({
|
|
16
|
+
serverUrl: "https://base44.app",
|
|
17
|
+
async getSessionToken() {
|
|
18
|
+
const response = await fetch("/api/builder-socket-session", { method: "POST" });
|
|
19
|
+
if (!response.ok) throw new Error("Session request failed");
|
|
20
|
+
return (await response.json()).session_token;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
const builder = client.builder.init({
|
|
24
|
+
onError(error) { showConnectionError(error.code); },
|
|
25
|
+
});
|
|
26
|
+
const subscription = builder.subscribe(appId, {
|
|
27
|
+
onSnapshot(snapshot) { replaceAppState(snapshot); },
|
|
28
|
+
async onEvent(event) { await applyEvent(event); }, // awaited before the next event
|
|
29
|
+
onError(error) { showSubscriptionError(error.code); },
|
|
30
|
+
});
|
|
31
|
+
await builder.connect();
|
|
32
|
+
// On view teardown:
|
|
33
|
+
subscription.unsubscribe();
|
|
34
|
+
builder.close();
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Sessions
|
|
38
|
+
|
|
39
|
+
`/api/builder-socket-session` is your backend's route, not an SDK endpoint. It opens a
|
|
40
|
+
socket session with its workspace key (`apps:watch` scope) through
|
|
41
|
+
`POST /api/service/socket-sessions` with `{ app_ids }`, and returns only the
|
|
42
|
+
`session_token` to the browser. Never pass the workspace key or any other server
|
|
43
|
+
credential to this client. Subscribe only to apps on the session's allowlist; your
|
|
44
|
+
backend adds or removes apps with `PUT`/`DELETE /api/service/socket-sessions/{session_id}/rooms/{app_id}`.
|
|
45
|
+
|
|
46
|
+
A session lasts one hour and is never extended. The token goes exclusively in
|
|
47
|
+
CONNECT `auth.session_token`, never a URL, and the SDK keeps it in memory only. It
|
|
48
|
+
is reused across automatic reconnects. `getSessionToken` is called when a builder
|
|
49
|
+
session first connects, and again only when the server rejects the cached token or
|
|
50
|
+
expires the session; the SDK then reconnects once with the new token. Retrieval has
|
|
51
|
+
a twenty-second timeout and reports `session_unavailable` on failure.
|
|
52
|
+
|
|
53
|
+
One live socket per session: a newer connection with the same token replaces the
|
|
54
|
+
older one, which stops with `session_replaced`. Open one session per page.
|
|
55
|
+
|
|
56
|
+
## Lifecycle
|
|
57
|
+
|
|
58
|
+
`new Base44PlatformClient({ serverUrl, getSessionToken })` creates a lightweight module
|
|
59
|
+
container: no Socket.IO instance, timers, token request or network activity. Its
|
|
60
|
+
`builder` module follows the server SDK's module-factory pattern.
|
|
61
|
+
|
|
62
|
+
`client.builder.init({ onError })` synchronously creates an independent `BuilderSession`
|
|
63
|
+
without connecting. Repeated calls create separate sessions, each owning its own
|
|
64
|
+
socket, listeners, subscriptions and cleanup. Close the returned session when its
|
|
65
|
+
view is disposed; the root client and other sessions remain usable.
|
|
66
|
+
|
|
67
|
+
`builder.connect()` resolves on CONNECT, before any snapshot. The socket uses the
|
|
68
|
+
default namespace on `/ws/socket.io/`, WebSocket only, with a dedicated manager.
|
|
69
|
+
Transport reconnection uses five attempts, starting at one second and capped at ten
|
|
70
|
+
seconds with jitter; each attempt has a twenty-second timeout. A temporary server
|
|
71
|
+
refusal is retried the same way. After exhaustion, a refusal or `session_replaced`,
|
|
72
|
+
fix the cause and call `builder.connect()`.
|
|
73
|
+
|
|
74
|
+
`builder.subscribe(appId, options)` returns a handle with `appId`, `active` and
|
|
75
|
+
idempotent `unsubscribe()`. App IDs are 24 lowercase hexadecimal characters. One
|
|
76
|
+
subscription per app and at most eight subscriptions are allowed per builder session.
|
|
77
|
+
Subscriptions can be created before connecting. `builder.close()` is terminal for that
|
|
78
|
+
session; call `client.builder.init(...)` again to start another.
|
|
79
|
+
|
|
80
|
+
## Snapshots and events
|
|
81
|
+
|
|
82
|
+
There is no replay cursor. Every join, including each rejoin after a reconnect, is
|
|
83
|
+
answered with a `Snapshot`: the app's current `status` and its last 50 public
|
|
84
|
+
messages. After a main-conversation rewrite (undo, restore, sync), the SDK delivers
|
|
85
|
+
`conversation.changed` and rejoins the room, so a fresh snapshot follows. Treat a
|
|
86
|
+
snapshot as the app's current state. The app's live events follow it, including
|
|
87
|
+
those that landed while it was read. It holds only the last 50 messages, so merge
|
|
88
|
+
by `id` if you keep longer history.
|
|
89
|
+
|
|
90
|
+
Delivery is serialized **per app**, snapshots included; a slow app does not block
|
|
91
|
+
others. Callbacks must settle; they cannot be forcibly cancelled. The client bounds
|
|
92
|
+
pending delivery to 1,000 items per app. Error observers are synchronous notifications;
|
|
93
|
+
their exceptions are isolated so they cannot interrupt another app's delivery.
|
|
94
|
+
|
|
95
|
+
## Public shapes
|
|
96
|
+
|
|
97
|
+
- `PlatformClientOptions` (`client.types.ts`): `serverUrl`, `getSessionToken`.
|
|
98
|
+
- `BuilderModule` (`modules/builder.types.ts`): lazy `init(options)` factory.
|
|
99
|
+
- `BuilderInitOptions`: connection-level `onError` observer.
|
|
100
|
+
- `BuilderSession`: `connect()`, `subscribe(appId, options)`, `close()`.
|
|
101
|
+
- `SubscriptionOptions`: required `onSnapshot`, `onEvent` and `onError`.
|
|
102
|
+
- `PlatformSubscription`: read-only `appId`, `active`, and `unsubscribe()`.
|
|
103
|
+
- Event/message models live in `modules/builder.events.types.ts`; error codes in `errors.types.ts`.
|
|
104
|
+
|
|
105
|
+
Every exported shape and field has JSDoc. Run `npm run docs` for
|
|
106
|
+
validated reference pages in `docs`. `PlatformEvent` is a
|
|
107
|
+
**discriminated union**: switch on `event.type` to narrow `event.data`. Each delivery
|
|
108
|
+
contains `type`, `appId` and `data`. On the wire every event is `{room, data}`; the SDK
|
|
109
|
+
routes by `room` and validates the envelope, while payload schemas and private-field
|
|
110
|
+
filtering are the server's. Omitted keys mean unchanged and explicit null clears.
|
|
111
|
+
Events carrying `branch_id` belong to that branch; null or absent is main. Unknown
|
|
112
|
+
event names are not forwarded.
|
|
113
|
+
|
|
114
|
+
| Event | Data contract |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| `message.updated` | `MessageUpdated`: the whole `message`, optional `conversation_id`. Replace the message with the same `id`. |
|
|
117
|
+
| `message.removed` | `MessageRemoved`: `message_id`, optional `conversation_id`. Sent when a message is deleted or hidden; an unknown ID is a no-op. |
|
|
118
|
+
| `app.status_changed` | `AppStatusChanged`: `status` (`AppStatus`), null clears it. |
|
|
119
|
+
| `preview.reload_requested` | Reload the app preview. |
|
|
120
|
+
| `preview.navigation_requested` | `PreviewNavigationRequested`: `path`, and `force` to navigate even when the user moved away. |
|
|
121
|
+
| `queue.updated` | `QueueUpdated`: `items`, `is_paused`, optional `processed_item_id`. Replaces the entire queue. |
|
|
122
|
+
| `task.progressed` | `TaskProgressed`: optional `tool_call_id`, `message_id`, `event_type`, numeric `progress`. |
|
|
123
|
+
| `image.resolved` | `ImageResolved`: `placeholder_url`, `status` (`pending`, `completed`, `failed`), nullable `image_url`. Replace the placeholder wherever it appears. |
|
|
124
|
+
| `conversation.changed` | The conversation was rewritten. On main, a fresh snapshot follows. |
|
|
125
|
+
| `files.changed`, `branch.deleted`, `repository.changed`, `pull_request.changed` | Re-read signals with no content. |
|
|
126
|
+
|
|
127
|
+
`ChatMessage` has optional `id`, `role` (`user`/`assistant`), text/null `content`,
|
|
128
|
+
`tool_calls`, timestamp-only `metadata.created_date`, `checkpoint_id`, and scalar
|
|
129
|
+
`additional_message_params` (`client_creation_id`, `plan_mode`, `plan_approved`,
|
|
130
|
+
`skip_ai_response`, `system_message_type`). `ToolCall` has optional `id`, `name`,
|
|
131
|
+
`status`, `requires_user_input`, `auto_approved`, `mutation_applied` and
|
|
132
|
+
`waiting_on.kind` (`approval`/`choice`/`input`). Each builder tool and guard declares
|
|
133
|
+
which of its parts are public; a tool with no declaration shows only these fields.
|
|
134
|
+
|
|
135
|
+
| Field | Public contract |
|
|
136
|
+
| --- | --- |
|
|
137
|
+
| `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. |
|
|
138
|
+
| `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`. |
|
|
139
|
+
| `user_input` | `ToolQuestionInput` only, for clarifying-question answers (`selected_label` for single-select, `selected_labels` for multi-select). Secret-form values are never exposed. |
|
|
140
|
+
| `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). |
|
|
141
|
+
| `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`). |
|
|
142
|
+
|
|
143
|
+
In plan mode the message `content` beside a `generate_prd` call can be empty; render the
|
|
144
|
+
plan from its arguments. Approving a plan, answering a question and approving a
|
|
145
|
+
guarded call are HTTP actions of your backend. `Snapshot` contains `room`, `status`
|
|
146
|
+
(`AppStatus`: optional `state` of `ready`/`processing`/`error`, nullable
|
|
147
|
+
`last_updated_date`, and nullable `turn_id`, the user message that started the turn),
|
|
148
|
+
`messages`, and `queue` (`SnapshotQueue`: the main branch's `items` and `is_paused`;
|
|
149
|
+
absent from older servers). Dates remain wire strings. Structural filtering
|
|
150
|
+
does not promise redaction of generated prose or user content.
|
|
151
|
+
|
|
152
|
+
`PlatformSocketError` extends `Error` with `code` and optional `appId`. Original
|
|
153
|
+
exceptions, payloads and credentials are never attached.
|
|
154
|
+
|
|
155
|
+
| Error code | Action |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| `connection_denied` | The session was rejected even with a fresh token: the workspace, key or session is not usable. |
|
|
158
|
+
| `session_unavailable` | `getSessionToken` failed or timed out; no provider details are forwarded. |
|
|
159
|
+
| `session_replaced` | Another connection with the same session took over. Stop, or open a new session. |
|
|
160
|
+
| `connection_failed` | Transport failure or exhausted retries; call `connect()` after fixing connectivity. |
|
|
161
|
+
| `access_denied` | The join was refused: the app is not on the session's allowlist, or joins were rate limited. |
|
|
162
|
+
| `access_revoked` | The app left the session's allowlist; do not resubscribe. |
|
|
163
|
+
| `snapshot_unavailable` | Joined, but the snapshot failed. Live events still flow; the subscription stays active. |
|
|
164
|
+
| `subscription_limit` | Release a subscription before adding another. |
|
|
165
|
+
| `delivery_overflow` | 1,000 deliveries queued for the app; subscribe again for a fresh snapshot. |
|
|
166
|
+
| `protocol_error` | Invalid routing or envelope. Stop and investigate. |
|
|
167
|
+
| `handler_failed` | An application callback failed; fix it and subscribe again. |
|
|
168
|
+
| `client_closed` | The builder session was explicitly closed; initialize another session. |
|
|
169
|
+
|
|
170
|
+
No SDK dependency, package version or lockfile changes are required. Socket.IO
|
|
171
|
+
remains the existing SDK dependency. Chat commands and HTTP APIs are outside this entry point.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PlatformClientOptions } from "./client.types.js";
|
|
2
|
+
import type { BuilderModule } from "./modules/builder.types.js";
|
|
3
|
+
/** Browser platform client. Construction creates no sockets, timers or network requests. */
|
|
4
|
+
export declare class Base44PlatformClient {
|
|
5
|
+
/** Lazy builder subscriptions with independent session lifecycles. */
|
|
6
|
+
readonly builder: BuilderModule;
|
|
7
|
+
/** Configure shared service/auth settings; each module initializes its own resources. */
|
|
8
|
+
constructor(options: PlatformClientOptions);
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { normalizeServerUrl } from "../shared/server-url.js";
|
|
2
|
+
import { createBuilder } from "./modules/builder.js";
|
|
3
|
+
/** Browser platform client. Construction creates no sockets, timers or network requests. */
|
|
4
|
+
export class Base44PlatformClient {
|
|
5
|
+
/** Configure shared service/auth settings; each module initializes its own resources. */
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.builder = Object.freeze(createBuilder({ ...options, serverUrl: normalizeServerUrl(options.serverUrl) }));
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Shared configuration for browser platform modules. Never supply an API key. */
|
|
2
|
+
export interface PlatformClientOptions {
|
|
3
|
+
/** Origin of the platform service, e.g. https://base44.app. No path/query/credentials. */
|
|
4
|
+
serverUrl: string;
|
|
5
|
+
/**
|
|
6
|
+
* Return a socket-session token from your backend, which opens the session with its
|
|
7
|
+
* workspace key (`POST /api/service/socket-sessions`) for the apps this page watches.
|
|
8
|
+
* Called when a builder session first connects, and again only after the server
|
|
9
|
+
* rejects or expires the current session. Sessions last one hour.
|
|
10
|
+
*/
|
|
11
|
+
getSessionToken: () => string | Promise<string>;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PlatformSocketErrorCode } from "./errors.types.js";
|
|
2
|
+
/** Sanitized failure. Original session-token provider, handler and server exceptions are not retained. */
|
|
3
|
+
export declare class PlatformSocketError extends Error {
|
|
4
|
+
/** Stable machine-readable category. */
|
|
5
|
+
readonly code: PlatformSocketErrorCode;
|
|
6
|
+
/** Associated app, when the server identifies a valid room. */
|
|
7
|
+
readonly appId?: string;
|
|
8
|
+
/** Create a sanitized error with no credential-bearing cause or payload. */
|
|
9
|
+
constructor(code: PlatformSocketErrorCode, appId?: string);
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Sanitized failure. Original session-token provider, handler and server exceptions are not retained. */
|
|
2
|
+
export class PlatformSocketError extends Error {
|
|
3
|
+
/** Create a sanitized error with no credential-bearing cause or payload. */
|
|
4
|
+
constructor(code, appId) {
|
|
5
|
+
super(`Platform socket: ${code}`);
|
|
6
|
+
this.name = "PlatformSocketError";
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.appId = appId;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/** Server refusals and client transport/processing failures. */
|
|
2
|
+
export type PlatformSocketErrorCode = "connection_denied" | "connection_failed" | "session_unavailable" | "session_revoked" | "session_replaced" | "access_denied" | "access_revoked" | "snapshot_unavailable" | "subscription_limit" | "delivery_overflow" | "protocol_error" | "handler_failed" | "client_closed";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Browser platform modules, separate from the runtime and server SDKs. */
|
|
2
|
+
export { Base44PlatformClient } from "./client.js";
|
|
3
|
+
export { PlatformSocketError } from "./errors.js";
|
|
4
|
+
export type { PlatformSocketErrorCode } from "./errors.types.js";
|
|
5
|
+
export type { PlatformClientOptions } from "./client.types.js";
|
|
6
|
+
export type { BuilderModule, BuilderInitOptions, BuilderSession, PlatformSubscription, SubscriptionOptions } from "./modules/builder.types.js";
|
|
7
|
+
export type { AppStatus, AppStatusChanged, BranchScoped, ChatMessage, ImageResolved, MessageParams, MessageRemoved, MessageUpdated, PlatformEvent, PlatformEventMap, PreviewNavigationRequested, QueueItem, QueueUpdated, Snapshot, SnapshotQueue, TaskProgressed, ToolArguments, ToolCall, ToolDisplay, ToolEntityMutationArguments, ToolGuardApproval, ToolMediaArguments, ToolMediaResult, ToolPackageArguments, ToolPackageOperation, ToolPlanArguments, ToolPlanUpdate, ToolPrdArguments, ToolQuestion, ToolQuestionAnswer, ToolQuestionArguments, ToolQuestionInput, ToolQuestionOption, ToolSecretArguments, ToolSecretField, } from "./modules/builder.events.types.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PlatformEvent, PlatformEventMap, Snapshot } from "./builder.events.types.js";
|
|
2
|
+
export declare const eventNames: readonly ["message.updated", "message.removed", "app.status_changed", "preview.reload_requested", "preview.navigation_requested", "queue.updated", "task.progressed", "image.resolved", "conversation.changed", "files.changed", "branch.deleted", "repository.changed", "pull_request.changed"];
|
|
3
|
+
/** Notices that name an app room, by the error code each one reports. */
|
|
4
|
+
export declare const roomNotices: {
|
|
5
|
+
readonly "room.access_denied": "access_denied";
|
|
6
|
+
readonly "room.access_revoked": "access_revoked";
|
|
7
|
+
readonly "room.snapshot_unavailable": "snapshot_unavailable";
|
|
8
|
+
};
|
|
9
|
+
export declare const appPattern: RegExp;
|
|
10
|
+
export declare const roomFor: (appId: string) => string;
|
|
11
|
+
export declare function object(value: unknown): Record<string, unknown>;
|
|
12
|
+
export declare function appFromRoom(value: unknown): string | undefined;
|
|
13
|
+
/** Every event is `{room, data}`; payload schemas are the service's, so only the envelope is validated. */
|
|
14
|
+
export declare function decode(type: keyof PlatformEventMap, appId: string, raw: unknown): PlatformEvent;
|
|
15
|
+
export declare function decodeSnapshot(raw: unknown): Snapshot;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const eventNames = [
|
|
2
|
+
"message.updated", "message.removed", "app.status_changed", "preview.reload_requested",
|
|
3
|
+
"preview.navigation_requested", "queue.updated", "task.progressed", "image.resolved",
|
|
4
|
+
"conversation.changed", "files.changed", "branch.deleted", "repository.changed", "pull_request.changed",
|
|
5
|
+
];
|
|
6
|
+
/** Notices that name an app room, by the error code each one reports. */
|
|
7
|
+
export const roomNotices = {
|
|
8
|
+
"room.access_denied": "access_denied",
|
|
9
|
+
"room.access_revoked": "access_revoked",
|
|
10
|
+
"room.snapshot_unavailable": "snapshot_unavailable",
|
|
11
|
+
};
|
|
12
|
+
export const appPattern = /^[a-f0-9]{24}$/;
|
|
13
|
+
export const roomFor = (appId) => `/apps/${appId}`;
|
|
14
|
+
export function object(value) {
|
|
15
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
16
|
+
throw new Error("Invalid frame");
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
export function appFromRoom(value) {
|
|
20
|
+
return typeof value === "string" && /^\/apps\/[a-f0-9]{24}$/.test(value) ? value.slice(6) : undefined;
|
|
21
|
+
}
|
|
22
|
+
/** Every event is `{room, data}`; payload schemas are the service's, so only the envelope is validated. */
|
|
23
|
+
export function decode(type, appId, raw) {
|
|
24
|
+
return { type, appId, data: object(object(raw).data) };
|
|
25
|
+
}
|
|
26
|
+
export function decodeSnapshot(raw) {
|
|
27
|
+
const frame = object(raw);
|
|
28
|
+
const data = object(frame.data);
|
|
29
|
+
if (!appFromRoom(frame.room) || !Array.isArray(data.messages) || (data.status !== null && typeof data.status !== "object")) {
|
|
30
|
+
throw new Error("Invalid snapshot");
|
|
31
|
+
}
|
|
32
|
+
const queue = data.queue !== null && typeof data.queue === "object" ? { queue: data.queue } : {};
|
|
33
|
+
return { room: frame.room, status: data.status, messages: data.messages, ...queue };
|
|
34
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { PlatformClientOptions } from "../client.types.js";
|
|
2
|
+
import type { BuilderInitOptions, BuilderSession, PlatformSubscription, SubscriptionOptions } from "./builder.types.js";
|
|
3
|
+
/** @internal */
|
|
4
|
+
export declare class BuilderSocket implements BuilderSession {
|
|
5
|
+
private readonly socket;
|
|
6
|
+
private readonly subscriptions;
|
|
7
|
+
private readonly options;
|
|
8
|
+
private closed;
|
|
9
|
+
private token?;
|
|
10
|
+
private tokenFresh;
|
|
11
|
+
private ending?;
|
|
12
|
+
private denialRetries;
|
|
13
|
+
private retryTimer?;
|
|
14
|
+
private authAttempt;
|
|
15
|
+
private cancelAuth?;
|
|
16
|
+
private connecting?;
|
|
17
|
+
private resolveConnect?;
|
|
18
|
+
private rejectConnect?;
|
|
19
|
+
constructor(config: PlatformClientOptions, options: BuilderInitOptions);
|
|
20
|
+
/** Connect with the session token. Resolves on CONNECT, before snapshots arrive.
|
|
21
|
+
* Transport loss retries up to five times and rejoins active subscriptions.
|
|
22
|
+
* Call again after a failure or `session_replaced`; concurrent calls share one attempt.
|
|
23
|
+
*/
|
|
24
|
+
connect(): Promise<void>;
|
|
25
|
+
/** Subscribe before or after connecting. One subscription per app, maximum eight, each on the
|
|
26
|
+
* session's allowlist. Snapshots and events are awaited in order per app; a failed callback,
|
|
27
|
+
* an invalid frame or a server refusal ends the subscription.
|
|
28
|
+
*/
|
|
29
|
+
subscribe(appId: string, options: SubscriptionOptions): PlatformSubscription;
|
|
30
|
+
/** Stop delivery, cancel reconnection and release all listeners. Idempotent and terminal. */
|
|
31
|
+
close(): void;
|
|
32
|
+
private join;
|
|
33
|
+
private authenticate;
|
|
34
|
+
/** A connect the server refused. Transport errors are Socket.IO's to retry. */
|
|
35
|
+
private refused;
|
|
36
|
+
private retryDenied;
|
|
37
|
+
private clearConnecting;
|
|
38
|
+
private connectionError;
|
|
39
|
+
private sessionEnded;
|
|
40
|
+
private roomNotice;
|
|
41
|
+
private protocolError;
|
|
42
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { io } from "socket.io-client";
|
|
2
|
+
import { PlatformSocketError } from "../errors.js";
|
|
3
|
+
import { appFromRoom, appPattern, decode, decodeSnapshot, eventNames, object, roomFor, roomNotices } from "./builder-protocol.js";
|
|
4
|
+
import { notify, Subscription } from "./builder-subscription.js";
|
|
5
|
+
const MAX_DENIAL_RETRIES = 5;
|
|
6
|
+
/** @internal */
|
|
7
|
+
export class BuilderSocket {
|
|
8
|
+
constructor(config, options) {
|
|
9
|
+
this.subscriptions = new Map();
|
|
10
|
+
this.closed = false;
|
|
11
|
+
this.tokenFresh = false;
|
|
12
|
+
this.denialRetries = 0;
|
|
13
|
+
this.authAttempt = 0;
|
|
14
|
+
this.options = { ...config, onError: options.onError };
|
|
15
|
+
this.socket = io(config.serverUrl, {
|
|
16
|
+
path: "/ws/socket.io/", transports: ["websocket"], autoConnect: false,
|
|
17
|
+
forceNew: true, reconnectionAttempts: 5, reconnectionDelay: 1000, reconnectionDelayMax: 10000,
|
|
18
|
+
timeout: 20000,
|
|
19
|
+
auth: (callback) => { void this.authenticate(callback); },
|
|
20
|
+
});
|
|
21
|
+
this.socket.on("connect", () => {
|
|
22
|
+
var _a;
|
|
23
|
+
this.tokenFresh = false;
|
|
24
|
+
this.denialRetries = 0;
|
|
25
|
+
for (const subscription of this.subscriptions.values())
|
|
26
|
+
this.join(subscription);
|
|
27
|
+
(_a = this.resolveConnect) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
28
|
+
this.clearConnecting();
|
|
29
|
+
});
|
|
30
|
+
this.socket.on("disconnect", (reason) => {
|
|
31
|
+
++this.authAttempt;
|
|
32
|
+
if (this.closed || reason !== "io server disconnect")
|
|
33
|
+
return; // Transport loss reconnects by itself.
|
|
34
|
+
const ending = this.ending;
|
|
35
|
+
this.ending = undefined;
|
|
36
|
+
if (ending === "session_expired")
|
|
37
|
+
this.socket.connect(); // Renew through getSessionToken.
|
|
38
|
+
else
|
|
39
|
+
this.connectionError(new PlatformSocketError(ending !== null && ending !== void 0 ? ending : "connection_failed"));
|
|
40
|
+
});
|
|
41
|
+
this.socket.on("connect_error", (error) => this.refused(error));
|
|
42
|
+
this.socket.io.on("reconnect_failed", () => this.connectionError(new PlatformSocketError("connection_failed")));
|
|
43
|
+
this.socket.on("app.snapshot", (raw) => {
|
|
44
|
+
var _a;
|
|
45
|
+
try {
|
|
46
|
+
const snapshot = decodeSnapshot(raw);
|
|
47
|
+
(_a = this.subscriptions.get(appFromRoom(snapshot.room))) === null || _a === void 0 ? void 0 : _a.snapshot(snapshot);
|
|
48
|
+
}
|
|
49
|
+
catch (_b) {
|
|
50
|
+
this.protocolError(raw);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
this.socket.on("session.ended", (raw) => this.sessionEnded(raw));
|
|
54
|
+
for (const [name, code] of Object.entries(roomNotices))
|
|
55
|
+
this.socket.on(name, (raw) => this.roomNotice(code, raw));
|
|
56
|
+
for (const type of eventNames)
|
|
57
|
+
this.socket.on(type, (raw) => {
|
|
58
|
+
try {
|
|
59
|
+
const appId = appFromRoom(object(raw).room);
|
|
60
|
+
if (!appId)
|
|
61
|
+
throw new Error("Invalid app");
|
|
62
|
+
const event = decode(type, appId, raw);
|
|
63
|
+
const subscription = this.subscriptions.get(appId);
|
|
64
|
+
subscription === null || subscription === void 0 ? void 0 : subscription.event(event);
|
|
65
|
+
// The server does not resend a snapshot after a rewrite of main: rejoining asks for one.
|
|
66
|
+
if (subscription && event.type === "conversation.changed" && event.data.branch_id == null)
|
|
67
|
+
this.join(subscription);
|
|
68
|
+
}
|
|
69
|
+
catch (_a) {
|
|
70
|
+
this.protocolError(raw);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** Connect with the session token. Resolves on CONNECT, before snapshots arrive.
|
|
75
|
+
* Transport loss retries up to five times and rejoins active subscriptions.
|
|
76
|
+
* Call again after a failure or `session_replaced`; concurrent calls share one attempt.
|
|
77
|
+
*/
|
|
78
|
+
connect() {
|
|
79
|
+
if (this.closed)
|
|
80
|
+
return Promise.reject(new PlatformSocketError("client_closed"));
|
|
81
|
+
if (this.socket.connected)
|
|
82
|
+
return Promise.resolve();
|
|
83
|
+
if (this.connecting)
|
|
84
|
+
return this.connecting;
|
|
85
|
+
const promise = new Promise((resolve, reject) => {
|
|
86
|
+
this.resolveConnect = resolve;
|
|
87
|
+
this.rejectConnect = reject;
|
|
88
|
+
});
|
|
89
|
+
this.connecting = promise;
|
|
90
|
+
this.socket.connect();
|
|
91
|
+
return promise;
|
|
92
|
+
}
|
|
93
|
+
/** Subscribe before or after connecting. One subscription per app, maximum eight, each on the
|
|
94
|
+
* session's allowlist. Snapshots and events are awaited in order per app; a failed callback,
|
|
95
|
+
* an invalid frame or a server refusal ends the subscription.
|
|
96
|
+
*/
|
|
97
|
+
subscribe(appId, options) {
|
|
98
|
+
if (this.closed)
|
|
99
|
+
throw new PlatformSocketError("client_closed");
|
|
100
|
+
if (!appPattern.test(appId))
|
|
101
|
+
throw new TypeError("appId must be 24 lowercase hexadecimal characters");
|
|
102
|
+
if (this.subscriptions.has(appId))
|
|
103
|
+
throw new TypeError("An app may only have one subscription per builder session");
|
|
104
|
+
if (this.subscriptions.size >= 8)
|
|
105
|
+
throw new PlatformSocketError("subscription_limit", appId);
|
|
106
|
+
const subscription = new Subscription(appId, { ...options }, () => {
|
|
107
|
+
this.subscriptions.delete(appId);
|
|
108
|
+
if (this.socket.connected)
|
|
109
|
+
this.socket.emit("leave", roomFor(appId));
|
|
110
|
+
});
|
|
111
|
+
this.subscriptions.set(appId, subscription);
|
|
112
|
+
if (this.socket.connected)
|
|
113
|
+
this.join(subscription);
|
|
114
|
+
return subscription;
|
|
115
|
+
}
|
|
116
|
+
/** Stop delivery, cancel reconnection and release all listeners. Idempotent and terminal. */
|
|
117
|
+
close() {
|
|
118
|
+
var _a, _b;
|
|
119
|
+
if (this.closed)
|
|
120
|
+
return;
|
|
121
|
+
this.closed = true;
|
|
122
|
+
++this.authAttempt;
|
|
123
|
+
(_a = this.cancelAuth) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
124
|
+
clearTimeout(this.retryTimer);
|
|
125
|
+
for (const subscription of this.subscriptions.values())
|
|
126
|
+
subscription.unsubscribe();
|
|
127
|
+
(_b = this.rejectConnect) === null || _b === void 0 ? void 0 : _b.call(this, new PlatformSocketError("client_closed"));
|
|
128
|
+
this.clearConnecting();
|
|
129
|
+
this.socket.removeAllListeners();
|
|
130
|
+
this.socket.io.removeAllListeners();
|
|
131
|
+
this.socket.disconnect();
|
|
132
|
+
}
|
|
133
|
+
join(subscription) {
|
|
134
|
+
this.socket.emit("join", roomFor(subscription.appId));
|
|
135
|
+
}
|
|
136
|
+
async authenticate(callback) {
|
|
137
|
+
var _a;
|
|
138
|
+
const attempt = ++this.authAttempt;
|
|
139
|
+
(_a = this.cancelAuth) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
140
|
+
let timer;
|
|
141
|
+
const timeout = new Promise((_, reject) => {
|
|
142
|
+
timer = setTimeout(() => reject(new Error("Session token timeout")), 20000);
|
|
143
|
+
this.cancelAuth = () => { clearTimeout(timer); reject(new Error("Cancelled")); };
|
|
144
|
+
});
|
|
145
|
+
try {
|
|
146
|
+
if (!this.token) {
|
|
147
|
+
const token = await Promise.race([Promise.resolve().then(() => this.options.getSessionToken()), timeout]);
|
|
148
|
+
if (this.closed || attempt !== this.authAttempt)
|
|
149
|
+
return;
|
|
150
|
+
if (typeof token !== "string" || !token.trim())
|
|
151
|
+
throw new Error("Missing session token");
|
|
152
|
+
this.token = token;
|
|
153
|
+
this.tokenFresh = true;
|
|
154
|
+
}
|
|
155
|
+
callback({ session_token: this.token });
|
|
156
|
+
}
|
|
157
|
+
catch (_b) {
|
|
158
|
+
if (this.closed || attempt !== this.authAttempt)
|
|
159
|
+
return;
|
|
160
|
+
this.socket.disconnect();
|
|
161
|
+
this.connectionError(new PlatformSocketError("session_unavailable"));
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
clearTimeout(timer);
|
|
165
|
+
if (attempt === this.authAttempt)
|
|
166
|
+
this.cancelAuth = undefined;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/** A connect the server refused. Transport errors are Socket.IO's to retry. */
|
|
170
|
+
refused(error) {
|
|
171
|
+
var _a;
|
|
172
|
+
if (this.closed || error.message !== "connection_denied")
|
|
173
|
+
return;
|
|
174
|
+
if (((_a = error.data) === null || _a === void 0 ? void 0 : _a.retryable) === true) {
|
|
175
|
+
this.retryDenied();
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
// A token this attempt did not just fetch may have expired: renew it once.
|
|
179
|
+
const renew = !this.tokenFresh;
|
|
180
|
+
this.token = undefined;
|
|
181
|
+
if (renew)
|
|
182
|
+
this.socket.connect();
|
|
183
|
+
else
|
|
184
|
+
this.connectionError(new PlatformSocketError("connection_denied"));
|
|
185
|
+
}
|
|
186
|
+
retryDenied() {
|
|
187
|
+
if (++this.denialRetries > MAX_DENIAL_RETRIES) {
|
|
188
|
+
this.denialRetries = 0;
|
|
189
|
+
this.connectionError(new PlatformSocketError("connection_failed"));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const delay = Math.min(1000 * 2 ** (this.denialRetries - 1), 10000) * (0.5 + Math.random() / 2);
|
|
193
|
+
this.retryTimer = setTimeout(() => { this.retryTimer = undefined; if (!this.closed)
|
|
194
|
+
this.socket.connect(); }, delay);
|
|
195
|
+
}
|
|
196
|
+
clearConnecting() {
|
|
197
|
+
this.connecting = undefined;
|
|
198
|
+
this.resolveConnect = undefined;
|
|
199
|
+
this.rejectConnect = undefined;
|
|
200
|
+
}
|
|
201
|
+
connectionError(error) {
|
|
202
|
+
var _a;
|
|
203
|
+
(_a = this.rejectConnect) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
|
204
|
+
this.clearConnecting();
|
|
205
|
+
if (!this.closed)
|
|
206
|
+
notify(this.options.onError, error);
|
|
207
|
+
}
|
|
208
|
+
sessionEnded(raw) {
|
|
209
|
+
try {
|
|
210
|
+
const reason = object(object(raw).data).reason;
|
|
211
|
+
const ending = reason === "expired" ? "session_expired"
|
|
212
|
+
: reason === "revoked" ? "session_revoked"
|
|
213
|
+
: reason === "replaced" ? "session_replaced" : undefined;
|
|
214
|
+
if (!ending)
|
|
215
|
+
throw new Error("Unknown reason");
|
|
216
|
+
// The server disconnects next; the disconnect handler renews or stops.
|
|
217
|
+
this.token = undefined;
|
|
218
|
+
this.ending = ending;
|
|
219
|
+
}
|
|
220
|
+
catch (_a) {
|
|
221
|
+
this.protocolError(raw);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
roomNotice(code, raw) {
|
|
225
|
+
try {
|
|
226
|
+
const appId = appFromRoom(object(raw).room);
|
|
227
|
+
if (!appId)
|
|
228
|
+
throw new Error("Invalid app");
|
|
229
|
+
const subscription = this.subscriptions.get(appId);
|
|
230
|
+
if (code === "snapshot_unavailable")
|
|
231
|
+
subscription === null || subscription === void 0 ? void 0 : subscription.warn(code);
|
|
232
|
+
else
|
|
233
|
+
subscription === null || subscription === void 0 ? void 0 : subscription.fail(code);
|
|
234
|
+
}
|
|
235
|
+
catch (_a) {
|
|
236
|
+
this.protocolError(raw);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
protocolError(raw) {
|
|
240
|
+
var _a;
|
|
241
|
+
const frame = raw && typeof raw === "object" ? raw : {};
|
|
242
|
+
const appId = appFromRoom(frame.room);
|
|
243
|
+
if (appId)
|
|
244
|
+
(_a = this.subscriptions.get(appId)) === null || _a === void 0 ? void 0 : _a.fail("protocol_error");
|
|
245
|
+
else {
|
|
246
|
+
// Unknown routing: no app can be told which update it missed.
|
|
247
|
+
for (const subscription of [...this.subscriptions.values()])
|
|
248
|
+
subscription.fail("protocol_error");
|
|
249
|
+
this.connectionError(new PlatformSocketError("protocol_error"));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PlatformEvent, Snapshot } from "./builder.events.types.js";
|
|
2
|
+
import { PlatformSocketError } from "../errors.js";
|
|
3
|
+
import type { PlatformSocketErrorCode } from "../errors.types.js";
|
|
4
|
+
import type { PlatformSubscription, SubscriptionOptions } from "./builder.types.js";
|
|
5
|
+
/** @internal */
|
|
6
|
+
export declare function notify(callback: (error: PlatformSocketError) => void, error: PlatformSocketError): void;
|
|
7
|
+
/** @internal */
|
|
8
|
+
export declare class Subscription implements PlatformSubscription {
|
|
9
|
+
readonly appId: string;
|
|
10
|
+
private options;
|
|
11
|
+
private remove;
|
|
12
|
+
active: boolean;
|
|
13
|
+
private pending;
|
|
14
|
+
private tail;
|
|
15
|
+
constructor(appId: string, options: SubscriptionOptions, remove: () => void);
|
|
16
|
+
private enqueue;
|
|
17
|
+
event(event: PlatformEvent): void;
|
|
18
|
+
snapshot(snapshot: Snapshot): void;
|
|
19
|
+
/** A non-fatal problem; the subscription keeps receiving events. */
|
|
20
|
+
warn(code: PlatformSocketErrorCode): void;
|
|
21
|
+
fail(code: PlatformSocketErrorCode): void;
|
|
22
|
+
unsubscribe(): void;
|
|
23
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { PlatformSocketError } from "../errors.js";
|
|
2
|
+
/** @internal */
|
|
3
|
+
export function notify(callback, error) {
|
|
4
|
+
try {
|
|
5
|
+
callback(error);
|
|
6
|
+
}
|
|
7
|
+
catch ( /* An error observer cannot interrupt other app subscriptions. */_a) { /* An error observer cannot interrupt other app subscriptions. */ }
|
|
8
|
+
}
|
|
9
|
+
/** @internal */
|
|
10
|
+
export class Subscription {
|
|
11
|
+
constructor(appId, options, remove) {
|
|
12
|
+
this.appId = appId;
|
|
13
|
+
this.options = options;
|
|
14
|
+
this.remove = remove;
|
|
15
|
+
this.active = true;
|
|
16
|
+
this.pending = 0;
|
|
17
|
+
this.tail = Promise.resolve();
|
|
18
|
+
}
|
|
19
|
+
enqueue(work) {
|
|
20
|
+
if (!this.active)
|
|
21
|
+
return;
|
|
22
|
+
if (this.pending >= 1000) {
|
|
23
|
+
this.fail("delivery_overflow");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
this.pending++;
|
|
27
|
+
this.tail = this.tail.then(async () => {
|
|
28
|
+
if (this.active)
|
|
29
|
+
await work();
|
|
30
|
+
}).catch(() => this.fail("handler_failed")).finally(() => { this.pending--; });
|
|
31
|
+
}
|
|
32
|
+
event(event) {
|
|
33
|
+
this.enqueue(() => this.options.onEvent(event));
|
|
34
|
+
}
|
|
35
|
+
snapshot(snapshot) {
|
|
36
|
+
this.enqueue(() => this.options.onSnapshot(snapshot));
|
|
37
|
+
}
|
|
38
|
+
/** A non-fatal problem; the subscription keeps receiving events. */
|
|
39
|
+
warn(code) {
|
|
40
|
+
if (this.active)
|
|
41
|
+
notify(this.options.onError, new PlatformSocketError(code, this.appId));
|
|
42
|
+
}
|
|
43
|
+
fail(code) {
|
|
44
|
+
if (!this.active)
|
|
45
|
+
return;
|
|
46
|
+
this.unsubscribe();
|
|
47
|
+
notify(this.options.onError, new PlatformSocketError(code, this.appId));
|
|
48
|
+
}
|
|
49
|
+
unsubscribe() {
|
|
50
|
+
if (!this.active)
|
|
51
|
+
return;
|
|
52
|
+
this.active = false;
|
|
53
|
+
this.remove();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/** Activity a file, execution or entity tool declared public. */
|
|
2
|
+
export interface ToolDisplay {
|
|
3
|
+
/** Changed file paths for a file operation. Source bodies and diffs are never included. */
|
|
4
|
+
file_paths?: string[];
|
|
5
|
+
/** Whether a file write intentionally used empty content. */
|
|
6
|
+
content_empty?: boolean;
|
|
7
|
+
/** Reviewed execution activity summary. Commands and execution output are never included. */
|
|
8
|
+
summary?: string;
|
|
9
|
+
/** Whether a reviewed execution action changed entity data. */
|
|
10
|
+
writes_entities?: boolean;
|
|
11
|
+
/** Entity type affected by an entity operation. Records and query values are never included. */
|
|
12
|
+
entity_name?: string;
|
|
13
|
+
/** Number of records affected when supplied by the producer. */
|
|
14
|
+
record_count?: number;
|
|
15
|
+
}
|
|
16
|
+
/** A selectable answer to a builder question. */
|
|
17
|
+
export interface ToolQuestionOption {
|
|
18
|
+
/** Visible option label. */
|
|
19
|
+
label?: string;
|
|
20
|
+
}
|
|
21
|
+
/** A builder question. Image/HTML source and private design context are excluded. */
|
|
22
|
+
export interface ToolQuestion {
|
|
23
|
+
/** Visible question text. */
|
|
24
|
+
question?: string;
|
|
25
|
+
/** Question category. */
|
|
26
|
+
type?: string;
|
|
27
|
+
/** Optional visible supporting text. */
|
|
28
|
+
description?: string;
|
|
29
|
+
/** Whether more than one answer may be selected. */
|
|
30
|
+
multi_select?: boolean;
|
|
31
|
+
/** Plan section the question covers. */
|
|
32
|
+
covers?: string;
|
|
33
|
+
/** Selectable options; a bare string option is its label. */
|
|
34
|
+
options?: (ToolQuestionOption | string)[];
|
|
35
|
+
}
|
|
36
|
+
/** `ask_clarifying_questions` and `ask_plan_questions` arguments. */
|
|
37
|
+
export interface ToolQuestionArguments {
|
|
38
|
+
/** Questions presented to the user. */
|
|
39
|
+
questions?: ToolQuestion[];
|
|
40
|
+
}
|
|
41
|
+
/** A requested secret field. The secret value is never sent over the socket. */
|
|
42
|
+
export interface ToolSecretField {
|
|
43
|
+
/** Requested secret name. */
|
|
44
|
+
secretName?: string;
|
|
45
|
+
/** Optional explanation of where to obtain it. */
|
|
46
|
+
description?: string;
|
|
47
|
+
}
|
|
48
|
+
/** `set_secrets` arguments. */
|
|
49
|
+
export interface ToolSecretArguments {
|
|
50
|
+
/** Requested secret fields. */
|
|
51
|
+
secrets_schema?: ToolSecretField[];
|
|
52
|
+
}
|
|
53
|
+
/** A package operation. Versions and package-manager output are excluded. */
|
|
54
|
+
export interface ToolPackageOperation {
|
|
55
|
+
/** Package name. */
|
|
56
|
+
name?: string;
|
|
57
|
+
/** Requested package operation. */
|
|
58
|
+
action?: string;
|
|
59
|
+
}
|
|
60
|
+
/** `install_npm_package` arguments. */
|
|
61
|
+
export interface ToolPackageArguments {
|
|
62
|
+
/** Requested package operations. */
|
|
63
|
+
packages?: ToolPackageOperation[];
|
|
64
|
+
}
|
|
65
|
+
/** An add-only builder plan update. */
|
|
66
|
+
export interface ToolPlanUpdate {
|
|
67
|
+
/** Update action. */
|
|
68
|
+
action?: string;
|
|
69
|
+
/** Plan section key. */
|
|
70
|
+
section?: string;
|
|
71
|
+
/** Optional user-facing section label. */
|
|
72
|
+
section_label?: string;
|
|
73
|
+
/** Plan point. */
|
|
74
|
+
text?: string;
|
|
75
|
+
}
|
|
76
|
+
/** `update_plan` arguments. */
|
|
77
|
+
export interface ToolPlanArguments {
|
|
78
|
+
/** Plan updates in their emitted order. */
|
|
79
|
+
updates?: ToolPlanUpdate[];
|
|
80
|
+
/** Base plan sections the builder considers sufficiently specified. */
|
|
81
|
+
sections_with_enough?: string[];
|
|
82
|
+
}
|
|
83
|
+
/** `generate_prd` arguments: the user's own plan. In plan mode the message `content` beside it can be empty. */
|
|
84
|
+
export interface ToolPrdArguments {
|
|
85
|
+
/** App or feature name. */
|
|
86
|
+
app_name?: string;
|
|
87
|
+
/** What is being built and why. */
|
|
88
|
+
intent_and_goal?: string;
|
|
89
|
+
/** Who uses it and their roles. */
|
|
90
|
+
audience_and_roles?: string;
|
|
91
|
+
/** End-to-end user flows. */
|
|
92
|
+
core_flows?: string[];
|
|
93
|
+
/** Specific technical requirements. */
|
|
94
|
+
technical_requirements?: string;
|
|
95
|
+
/** Design preferences. */
|
|
96
|
+
design_preferences?: string;
|
|
97
|
+
/** Build or plan declaration, when the builder asks for one. */
|
|
98
|
+
prd_type?: string;
|
|
99
|
+
/** Store platform settled in planning, for e-commerce plans. */
|
|
100
|
+
store_platform?: string;
|
|
101
|
+
/** Engine settled in planning, for game plans. */
|
|
102
|
+
game_engine?: string;
|
|
103
|
+
}
|
|
104
|
+
/** Image, game-image and video generation arguments. */
|
|
105
|
+
export interface ToolMediaArguments {
|
|
106
|
+
/** Visible media label. */
|
|
107
|
+
label?: string;
|
|
108
|
+
/** Requested image or video aspect ratio. */
|
|
109
|
+
aspect_ratio?: string;
|
|
110
|
+
}
|
|
111
|
+
/** `update_entities` and `delete_entities` arguments; the entity is in `display.entity_name`. */
|
|
112
|
+
export interface ToolEntityMutationArguments {
|
|
113
|
+
/** The agent's summary of the change. Queries and record values are never included. */
|
|
114
|
+
summary?: string;
|
|
115
|
+
}
|
|
116
|
+
/** The public arguments of a tool that declares them; narrow by the tool call's `name`. */
|
|
117
|
+
export type ToolArguments = ToolQuestionArguments | ToolSecretArguments | ToolPackageArguments | ToolPlanArguments | ToolPrdArguments | ToolMediaArguments | ToolEntityMutationArguments;
|
|
118
|
+
/** Generated-media state. Raw generation prompts and IDs are never included. */
|
|
119
|
+
export interface ToolMediaResult {
|
|
120
|
+
/** Placeholder URL, used to associate an `image.resolved` event with this tool. */
|
|
121
|
+
placeholder_url?: string;
|
|
122
|
+
/** Current media-generation status. */
|
|
123
|
+
status?: "pending" | "completed" | "failed";
|
|
124
|
+
/** Generated asset URL, once available. */
|
|
125
|
+
image_url?: string | null;
|
|
126
|
+
}
|
|
127
|
+
/** An answer to a clarifying question. Secret-form input is never included. */
|
|
128
|
+
export interface ToolQuestionAnswer {
|
|
129
|
+
/** Zero-based question index. */
|
|
130
|
+
question_index?: number;
|
|
131
|
+
/** Label selected for a single-select question. */
|
|
132
|
+
selected_label?: string;
|
|
133
|
+
/** Labels selected for a multi-select question. */
|
|
134
|
+
selected_labels?: string[];
|
|
135
|
+
/** User-provided free-text answer. Structural filtering does not redact prose. */
|
|
136
|
+
custom_text?: string;
|
|
137
|
+
}
|
|
138
|
+
/** Clarifying-question answers. */
|
|
139
|
+
export interface ToolQuestionInput {
|
|
140
|
+
/** Answers supplied to the question card. */
|
|
141
|
+
answers?: ToolQuestionAnswer[];
|
|
142
|
+
}
|
|
143
|
+
/** Why a safety guard parked a call for approval. Only present while the call waits. */
|
|
144
|
+
export interface ToolGuardApproval {
|
|
145
|
+
/** Guard identifier, e.g. `exec_tool_send_email` or `bash_approval`. */
|
|
146
|
+
guard: string;
|
|
147
|
+
/** Human-readable reason, for guards whose reason never quotes input (not shell commands). */
|
|
148
|
+
reason?: string;
|
|
149
|
+
/** The details the guard declares public; commands and record data are never included. */
|
|
150
|
+
details?: {
|
|
151
|
+
/** Entity method the code calls, e.g. `delete`. */
|
|
152
|
+
method?: string;
|
|
153
|
+
/** The agent's summary of what the code does. */
|
|
154
|
+
summary?: string;
|
|
155
|
+
/** Entity whose access rules change. */
|
|
156
|
+
entity_name?: string;
|
|
157
|
+
/** How the access rules change. */
|
|
158
|
+
change_type?: string;
|
|
159
|
+
/** Access operations whose rules change. */
|
|
160
|
+
changed_ops?: string[];
|
|
161
|
+
/** Backend function under test. */
|
|
162
|
+
function_name?: string;
|
|
163
|
+
/** The guard could not verify the function's source. */
|
|
164
|
+
inspection_failed?: boolean;
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/** Public progress of a builder tool call. Only what the tool and its guard declare public is present. */
|
|
168
|
+
export interface ToolCall {
|
|
169
|
+
/** Stable tool call identifier. */
|
|
170
|
+
id?: string;
|
|
171
|
+
/** Tool name. */
|
|
172
|
+
name?: string;
|
|
173
|
+
/** Current execution state. */
|
|
174
|
+
status?: "running" | "success" | "error" | "stopped" | "waiting_for_user_input";
|
|
175
|
+
/** Whether the tool needs a user response through the partner backend. */
|
|
176
|
+
requires_user_input?: boolean;
|
|
177
|
+
/** Whether the builder auto-approved the operation. */
|
|
178
|
+
auto_approved?: boolean;
|
|
179
|
+
/** Whether the mutation was applied. */
|
|
180
|
+
mutation_applied?: boolean | null;
|
|
181
|
+
/** Kind of response a parked call waits for. */
|
|
182
|
+
waiting_on?: {
|
|
183
|
+
/** Kind of response expected. */
|
|
184
|
+
kind?: "approval" | "choice" | "input";
|
|
185
|
+
};
|
|
186
|
+
/** The tool's public arguments; absent for tools that declare none and for partial streaming arguments. */
|
|
187
|
+
arguments?: ToolArguments;
|
|
188
|
+
/** What a file, execution or entity tool touched. */
|
|
189
|
+
display?: ToolDisplay;
|
|
190
|
+
/** Clarifying-question answers. */
|
|
191
|
+
user_input?: ToolQuestionInput;
|
|
192
|
+
/** Generated media, once the call is not waiting on a guard: the image tool's state, or a URL alone
|
|
193
|
+
* (a game asset's placeholder, which `image.resolved` replaces, or a finished video). */
|
|
194
|
+
results?: ToolMediaResult | string;
|
|
195
|
+
/** A parked call's guard, and its reason and details where the guard declares them. */
|
|
196
|
+
approval?: ToolGuardApproval;
|
|
197
|
+
}
|
|
198
|
+
/** Scalar parameters a chat UI needs while a turn runs. */
|
|
199
|
+
export interface MessageParams {
|
|
200
|
+
/** Identifier the sending client attached, to match its optimistic message. */
|
|
201
|
+
client_creation_id?: string;
|
|
202
|
+
/** The message was sent in plan mode. */
|
|
203
|
+
plan_mode?: boolean;
|
|
204
|
+
/** The message approves a plan. */
|
|
205
|
+
plan_approved?: boolean;
|
|
206
|
+
/** The message expects no AI reply. */
|
|
207
|
+
skip_ai_response?: boolean;
|
|
208
|
+
/** System message category. */
|
|
209
|
+
system_message_type?: string;
|
|
210
|
+
}
|
|
211
|
+
/** A public chat message. Replace the message with the same `id` as a whole. */
|
|
212
|
+
export interface ChatMessage {
|
|
213
|
+
/** Message identifier. */
|
|
214
|
+
id?: string;
|
|
215
|
+
/** Author category. System and hidden messages are never delivered. */
|
|
216
|
+
role?: "user" | "assistant";
|
|
217
|
+
/** Message text; structured content is withheld. Structural filtering is not prose redaction. */
|
|
218
|
+
content?: string | null;
|
|
219
|
+
/** Public tool progress. */
|
|
220
|
+
tool_calls?: ToolCall[] | null;
|
|
221
|
+
/** Message timestamp, without author identity. */
|
|
222
|
+
metadata?: {
|
|
223
|
+
/** ISO timestamp string. */
|
|
224
|
+
created_date?: string;
|
|
225
|
+
};
|
|
226
|
+
/** Checkpoint reference; mutations remain on the partner backend. */
|
|
227
|
+
checkpoint_id?: string | null;
|
|
228
|
+
/** Scalar message parameters. */
|
|
229
|
+
additional_message_params?: MessageParams;
|
|
230
|
+
}
|
|
231
|
+
/** Public builder state, without error diagnostics or billing context. */
|
|
232
|
+
export interface AppStatus {
|
|
233
|
+
/** Current builder state. */
|
|
234
|
+
state?: "ready" | "processing" | "error";
|
|
235
|
+
/** State timestamp. */
|
|
236
|
+
last_updated_date?: string | null;
|
|
237
|
+
/** The user message that started the turn, when a turn set this state. Quote it to Base44 support. */
|
|
238
|
+
turn_id?: string | null;
|
|
239
|
+
}
|
|
240
|
+
/** Branch scope carried by app events. Null or absent is the main branch. */
|
|
241
|
+
export interface BranchScoped {
|
|
242
|
+
/** Branch the change belongs to; null is main. */
|
|
243
|
+
branch_id?: string | null;
|
|
244
|
+
}
|
|
245
|
+
/** `message.updated`: a chat message was added or replaced. */
|
|
246
|
+
export interface MessageUpdated extends BranchScoped {
|
|
247
|
+
/** The whole message; replace the one with the same `id`. */
|
|
248
|
+
message: ChatMessage;
|
|
249
|
+
/** Conversation containing the message. */
|
|
250
|
+
conversation_id?: string | null;
|
|
251
|
+
}
|
|
252
|
+
/** `message.removed`: a message was removed or hidden. An `id` the viewer never saw is a no-op. */
|
|
253
|
+
export interface MessageRemoved extends BranchScoped {
|
|
254
|
+
/** Identifier of the message to remove. */
|
|
255
|
+
message_id: string;
|
|
256
|
+
/** Conversation that contained the message. */
|
|
257
|
+
conversation_id?: string | null;
|
|
258
|
+
}
|
|
259
|
+
/** `app.status_changed`: a turn started, finished or failed. */
|
|
260
|
+
export interface AppStatusChanged extends BranchScoped {
|
|
261
|
+
/** The new state; null clears it. */
|
|
262
|
+
status: AppStatus | null;
|
|
263
|
+
}
|
|
264
|
+
/** `preview.navigation_requested`: show this page of the app in the preview. */
|
|
265
|
+
export interface PreviewNavigationRequested extends BranchScoped {
|
|
266
|
+
/** App path to show. */
|
|
267
|
+
path: string;
|
|
268
|
+
/** Navigate even when the user has moved away from the page. */
|
|
269
|
+
force: boolean;
|
|
270
|
+
}
|
|
271
|
+
/** A queued builder request. */
|
|
272
|
+
export interface QueueItem {
|
|
273
|
+
/** Queue item identifier. */
|
|
274
|
+
id?: string;
|
|
275
|
+
/** User-authored request text. */
|
|
276
|
+
content?: string;
|
|
277
|
+
/** Creation timestamp. */
|
|
278
|
+
created_at?: string;
|
|
279
|
+
/** Branch the request belongs to. */
|
|
280
|
+
branch_id?: string | null;
|
|
281
|
+
}
|
|
282
|
+
/** `queue.updated`: the whole prompt queue, replacing the previous one. */
|
|
283
|
+
export interface QueueUpdated extends BranchScoped {
|
|
284
|
+
/** Pending items. */
|
|
285
|
+
items?: QueueItem[];
|
|
286
|
+
/** Whether queue processing is paused. */
|
|
287
|
+
is_paused?: boolean;
|
|
288
|
+
/** Identifier of the item just processed, when supplied. */
|
|
289
|
+
processed_item_id?: string | null;
|
|
290
|
+
}
|
|
291
|
+
/** `task.progressed`: progress of a long-running tool. */
|
|
292
|
+
export interface TaskProgressed extends BranchScoped {
|
|
293
|
+
/** Associated tool call. */
|
|
294
|
+
tool_call_id?: string | null;
|
|
295
|
+
/** Associated chat message. */
|
|
296
|
+
message_id?: string | null;
|
|
297
|
+
/** Task lifecycle event, e.g. `task_progress`. */
|
|
298
|
+
event_type?: string | null;
|
|
299
|
+
/** Numeric progress only; diagnostic text is withheld. */
|
|
300
|
+
progress?: {
|
|
301
|
+
/** Completed work units. */
|
|
302
|
+
current?: number | null;
|
|
303
|
+
/** Total work units, when known. */
|
|
304
|
+
total?: number | null;
|
|
305
|
+
/** Producer-supplied percentage. */
|
|
306
|
+
percentage?: number | null;
|
|
307
|
+
} | null;
|
|
308
|
+
}
|
|
309
|
+
/** `image.resolved`: a generated image finished or failed. Replace its `placeholder_url` wherever it appears. */
|
|
310
|
+
export type ImageResolved = ToolMediaResult;
|
|
311
|
+
/** Mapping of public event names to their payloads. */
|
|
312
|
+
export interface PlatformEventMap {
|
|
313
|
+
/** A chat message was added or replaced. */
|
|
314
|
+
"message.updated": MessageUpdated;
|
|
315
|
+
/** A chat message was removed or hidden. */
|
|
316
|
+
"message.removed": MessageRemoved;
|
|
317
|
+
/** The builder started, finished or failed a turn. */
|
|
318
|
+
"app.status_changed": AppStatusChanged;
|
|
319
|
+
/** Reload the app preview. */
|
|
320
|
+
"preview.reload_requested": BranchScoped;
|
|
321
|
+
/** Show a page of the app in the preview. */
|
|
322
|
+
"preview.navigation_requested": PreviewNavigationRequested;
|
|
323
|
+
/** The whole prompt queue. */
|
|
324
|
+
"queue.updated": QueueUpdated;
|
|
325
|
+
/** Progress of a long-running tool. */
|
|
326
|
+
"task.progressed": TaskProgressed;
|
|
327
|
+
/** A generated image finished or failed. */
|
|
328
|
+
"image.resolved": ImageResolved;
|
|
329
|
+
/** The conversation was rewritten (undo, restore, sync). On main, the SDK rejoins for a fresh snapshot. */
|
|
330
|
+
"conversation.changed": BranchScoped;
|
|
331
|
+
/** The app's files changed outside a chat turn. Re-read them. */
|
|
332
|
+
"files.changed": BranchScoped;
|
|
333
|
+
/** A branch was deleted. */
|
|
334
|
+
"branch.deleted": BranchScoped;
|
|
335
|
+
/** The imported app's repository changed. */
|
|
336
|
+
"repository.changed": BranchScoped;
|
|
337
|
+
/** The imported app's pull request changed. */
|
|
338
|
+
"pull_request.changed": BranchScoped;
|
|
339
|
+
}
|
|
340
|
+
/** Ordered delivery of one public event. */
|
|
341
|
+
export type PlatformEvent = {
|
|
342
|
+
[K in keyof PlatformEventMap]: {
|
|
343
|
+
/** Public event name; narrows the payload type. */
|
|
344
|
+
type: K;
|
|
345
|
+
/** Authorized app receiving this event. */
|
|
346
|
+
appId: string;
|
|
347
|
+
/** Event payload. Omitted keys mean unchanged; explicit null clears. */
|
|
348
|
+
data: PlatformEventMap[K];
|
|
349
|
+
};
|
|
350
|
+
}[keyof PlatformEventMap];
|
|
351
|
+
/** An app's current state: sent after every join and rejoin, and after a main-conversation rewrite. */
|
|
352
|
+
export interface Snapshot {
|
|
353
|
+
/** Canonical app room. */
|
|
354
|
+
room: string;
|
|
355
|
+
/** Current builder state. */
|
|
356
|
+
status: AppStatus | null;
|
|
357
|
+
/** The last 50 public messages, oldest first. */
|
|
358
|
+
messages: ChatMessage[];
|
|
359
|
+
/** The main branch's prompt queue. Replace it, as with `queue.updated`. Absent from older servers. */
|
|
360
|
+
queue?: SnapshotQueue;
|
|
361
|
+
}
|
|
362
|
+
/** The prompt queue in a snapshot. */
|
|
363
|
+
export interface SnapshotQueue {
|
|
364
|
+
/** Pending items. */
|
|
365
|
+
items: QueueItem[];
|
|
366
|
+
/** Whether queue processing is paused. */
|
|
367
|
+
is_paused: boolean;
|
|
368
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { PlatformEvent, Snapshot } from "./builder.events.types.js";
|
|
2
|
+
import type { PlatformSocketError } from "../errors.js";
|
|
3
|
+
/** Options for an independent builder socket session. */
|
|
4
|
+
export interface BuilderInitOptions {
|
|
5
|
+
/** Connection-level errors, sanitized to exclude tokens and server exception text. */
|
|
6
|
+
onError: (error: PlatformSocketError) => void;
|
|
7
|
+
}
|
|
8
|
+
/** Lazy builder module; no socket exists until init is called. */
|
|
9
|
+
export interface BuilderModule {
|
|
10
|
+
/** Create an independent session without connecting. Call connect on the returned session. */
|
|
11
|
+
init(options: BuilderInitOptions): BuilderSession;
|
|
12
|
+
}
|
|
13
|
+
/** One builder socket session. Owns its subscriptions and connection lifecycle. */
|
|
14
|
+
export interface BuilderSession {
|
|
15
|
+
/** Connect with the session token; resolves on CONNECT, before snapshots arrive.
|
|
16
|
+
* Transport loss retries five times and rejoins active subscriptions, each with a fresh
|
|
17
|
+
* snapshot. An expired or rejected session is replaced once through `getSessionToken`.
|
|
18
|
+
* Call again after a failure or `session_replaced`. Concurrent calls share one attempt.
|
|
19
|
+
*/
|
|
20
|
+
connect(): Promise<void>;
|
|
21
|
+
/** Subscribe before or after connecting. One subscription per app, up to eight per session,
|
|
22
|
+
* and each app must be on the session's allowlist. Callbacks run serially per app.
|
|
23
|
+
*/
|
|
24
|
+
subscribe(appId: string, options: SubscriptionOptions): PlatformSubscription;
|
|
25
|
+
/** Stop this session, its subscriptions and reconnection. Idempotent and terminal. */
|
|
26
|
+
close(): void;
|
|
27
|
+
}
|
|
28
|
+
/** One app subscription; at most eight may be active per builder session. */
|
|
29
|
+
export interface SubscriptionOptions {
|
|
30
|
+
/** Replace this app's state. Arrives after every join and rejoin, and after a main-conversation
|
|
31
|
+
* rewrite (undo, restore, sync). Live events follow it; it holds only the last 50 messages.
|
|
32
|
+
*/
|
|
33
|
+
onSnapshot: (snapshot: Snapshot) => void | Promise<void>;
|
|
34
|
+
/** Apply each live event. Delivery is serial per app; a rejection ends this subscription. */
|
|
35
|
+
onEvent: (event: PlatformEvent) => void | Promise<void>;
|
|
36
|
+
/** Subscription errors. Only `snapshot_unavailable` leaves the subscription active. */
|
|
37
|
+
onError: (error: PlatformSocketError) => void;
|
|
38
|
+
}
|
|
39
|
+
/** One app subscription's lifetime. */
|
|
40
|
+
export interface PlatformSubscription {
|
|
41
|
+
/** App identifier. */
|
|
42
|
+
readonly appId: string;
|
|
43
|
+
/** True while subscribed; false after an error or explicit unsubscribe. */
|
|
44
|
+
readonly active: boolean;
|
|
45
|
+
/** Stop this app's delivery and release its subscription slot. Idempotent. */
|
|
46
|
+
unsubscribe(): void;
|
|
47
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Base44PlatformServer, PlatformServerOptions } from "./server.types.js";
|
|
2
|
+
/** Create a server platform client, authenticated with a workspace API key. Performs no network requests. */
|
|
3
|
+
export declare function createBase44PlatformServer(options: PlatformServerOptions): Base44PlatformServer;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { normalizeServerUrl } from "../shared/server-url.js";
|
|
2
|
+
/** Create a server platform client, authenticated with a workspace API key. Performs no network requests. */
|
|
3
|
+
export function createBase44PlatformServer(options) {
|
|
4
|
+
if (!options.apiKey)
|
|
5
|
+
throw new TypeError("apiKey is required");
|
|
6
|
+
normalizeServerUrl(options.serverUrl);
|
|
7
|
+
return Object.freeze({});
|
|
8
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Shared configuration for server platform modules. */
|
|
2
|
+
export interface PlatformServerOptions {
|
|
3
|
+
/** Origin of the platform service, e.g. https://base44.app. No path/query/credentials. */
|
|
4
|
+
serverUrl: string;
|
|
5
|
+
/** Workspace API key. Keep it on your server; never ship it to a browser. */
|
|
6
|
+
apiKey: string;
|
|
7
|
+
}
|
|
8
|
+
/** Server platform client, returned by `createBase44PlatformServer`. Modules are added as properties. */
|
|
9
|
+
export interface Base44PlatformServer {
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Validate a platform origin and return it normalized. Shared by the client and server entrypoints. */
|
|
2
|
+
export function normalizeServerUrl(serverUrl) {
|
|
3
|
+
const url = new URL(serverUrl);
|
|
4
|
+
if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.search || url.hash || url.pathname !== "/") {
|
|
5
|
+
throw new TypeError("serverUrl must be an HTTP(S) origin without credentials, path, query or fragment");
|
|
6
|
+
}
|
|
7
|
+
return url.origin;
|
|
8
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,60 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@base44-preview/platform",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.0-pr.316.3f570db",
|
|
4
|
+
"description": "Client and server SDK for building on the Base44 platform",
|
|
5
|
+
"main": "dist/client/index.js",
|
|
6
|
+
"types": "dist/client/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/client/index.d.ts",
|
|
11
|
+
"default": "./dist/client/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./server": {
|
|
14
|
+
"types": "./dist/server/index.d.ts",
|
|
15
|
+
"default": "./dist/server/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"docs": "typedoc",
|
|
28
|
+
"test": "npm run test:types && npm run test:unit && npm run docs && npm run test:package",
|
|
29
|
+
"test:types": "tsc --noEmit -p tsconfig.type-tests.json",
|
|
30
|
+
"test:unit": "vitest run",
|
|
31
|
+
"test:package": "npm run build && node --test tests/package.test.mjs",
|
|
32
|
+
"prepublishOnly": "npm run build"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"socket.io-client": "^4.8.3"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"base44",
|
|
39
|
+
"platform",
|
|
40
|
+
"white-label"
|
|
41
|
+
],
|
|
42
|
+
"author": "Base44",
|
|
5
43
|
"license": "MIT",
|
|
6
|
-
"repository": {
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/base44/javascript-sdk.git",
|
|
47
|
+
"directory": "packages/platform"
|
|
48
|
+
},
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/base44/javascript-sdk/issues"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://github.com/base44/javascript-sdk/tree/main/packages/platform#readme",
|
|
53
|
+
"typesVersions": {
|
|
54
|
+
"*": {
|
|
55
|
+
"server": [
|
|
56
|
+
"dist/server/index.d.ts"
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
7
60
|
}
|