@base44-preview/sdk 0.8.51-pr.294.a7ae332 → 0.8.52-pr.286.58a3186

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/README.md CHANGED
@@ -151,3 +151,13 @@ npm run create-docs
151
151
  cd docs
152
152
  mintlify dev
153
153
  ```
154
+
155
+ ### Platform browser subscriptions
156
+
157
+ The separate `@base44/sdk/platform/client` entry point watches public builder
158
+ updates for up to eight apps over the platform socket. It supports typed events,
159
+ a snapshot on every join and bounded delivery, using a socket-session token your
160
+ backend opens with its workspace key.
161
+ See [setup, public contract and recovery](platform-docs/client.md) and the
162
+ [TypeScript example](examples/platform-client.ts). White-label sockets must be enabled
163
+ for the workspace; never use an API key in the browser.
@@ -127,7 +127,7 @@ export interface UploadPrivateFileResult {
127
127
  export interface CreateFileSignedUrlParams {
128
128
  /** URI of the uploaded private file. */
129
129
  file_uri: string;
130
- /** How long the signed URL should be valid for, in seconds.
130
+ /** How long the signed URL should be valid for, in seconds. Must be between `60` (1 minute) and `3600` (1 hour).
131
131
  * @default 300 (5 minutes)
132
132
  */
133
133
  expires_in?: number;
@@ -352,7 +352,7 @@ export interface CoreIntegrations {
352
352
  * // Create a signed URL for a private file
353
353
  * const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({
354
354
  * file_uri: "private/user123/document.pdf",
355
- * expires_in: 7200 // URL expires in 2 hours
355
+ * expires_in: 1800 // URL expires in 30 minutes
356
356
  * });
357
357
  * console.log(signed_url); // https://...?signature=...
358
358
  * ```
@@ -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,12 @@
1
+ import { createBuilder } from "./modules/builder.js";
2
+ /** Browser platform client. Construction creates no sockets, timers or network requests. */
3
+ export class Base44PlatformClient {
4
+ /** Configure shared service/auth settings; each module initializes its own resources. */
5
+ constructor(options) {
6
+ const url = new URL(options.serverUrl);
7
+ if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.search || url.hash || url.pathname !== "/") {
8
+ throw new TypeError("serverUrl must be an HTTP(S) origin without credentials, path, query or fragment");
9
+ }
10
+ this.builder = Object.freeze(createBuilder({ ...options, serverUrl: url.origin }));
11
+ }
12
+ }
@@ -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,3 @@
1
+ /** Browser platform modules, separate from the runtime and server SDKs. */
2
+ export { Base44PlatformClient } from "./client.js";
3
+ export { PlatformSocketError } from "./errors.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,4 @@
1
+ import type { PlatformClientOptions } from "../client.types.js";
2
+ import type { BuilderModule } from "./builder.types.js";
3
+ /** @internal */
4
+ export declare function createBuilder(config: PlatformClientOptions): BuilderModule;
@@ -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,7 @@
1
+ import { BuilderSocket } from "./builder-socket.js";
2
+ /** @internal */
3
+ export function createBuilder(config) {
4
+ return {
5
+ init(options) { return new BuilderSocket(config, options); },
6
+ };
7
+ }
@@ -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 {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base44-preview/sdk",
3
- "version": "0.8.51-pr.294.a7ae332",
3
+ "version": "0.8.52-pr.286.58a3186",
4
4
  "description": "JavaScript SDK for Base44 API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -9,8 +9,8 @@
9
9
  "dist"
10
10
  ],
11
11
  "scripts": {
12
- "build": "tsc",
13
- "lint": "eslint src",
12
+ "build": "npm run build:runtime && npm run build:platform",
13
+ "lint": "eslint src platform-src examples/platform-client.ts",
14
14
  "test": "npm run test:types && vitest run",
15
15
  "test:types": "tsc --noEmit -p tsconfig.type-tests.json",
16
16
  "test:unit": "vitest run tests/unit",
@@ -23,7 +23,11 @@
23
23
  "create-docs-local": "npm run create-docs && npm run copy-docs-local",
24
24
  "copy-docs-local": "node scripts/mintlify-post-processing/copy-to-local-docs.js",
25
25
  "create-docs:generate": "typedoc",
26
- "create-docs:process": "node scripts/mintlify-post-processing/file-processing/file-processing.js"
26
+ "create-docs:process": "node scripts/mintlify-post-processing/file-processing/file-processing.js",
27
+ "build:runtime": "tsc",
28
+ "build:platform": "tsc -p tsconfig.platform.json",
29
+ "docs:platform-client": "typedoc --options typedoc.platform-client.json",
30
+ "test:package": "npm run build && node --test tests/package/platform-client.test.mjs"
27
31
  },
28
32
  "dependencies": {
29
33
  "axios": "^1.18.1",
@@ -63,5 +67,36 @@
63
67
  "bugs": {
64
68
  "url": "https://github.com/base44/javascript-sdk/issues"
65
69
  },
66
- "homepage": "https://github.com/base44/javascript-sdk#readme"
70
+ "homepage": "https://github.com/base44/javascript-sdk#readme",
71
+ "exports": {
72
+ ".": {
73
+ "types": "./dist/index.d.ts",
74
+ "default": "./dist/index.js"
75
+ },
76
+ "./dist/*.d.ts": "./dist/*.d.ts",
77
+ "./dist/*.js": {
78
+ "types": "./dist/*.d.ts",
79
+ "default": "./dist/*.js"
80
+ },
81
+ "./dist/*": {
82
+ "types": "./dist/*.d.ts",
83
+ "default": "./dist/*.js"
84
+ },
85
+ "./package.json": "./package.json",
86
+ "./*": "./*",
87
+ "./platform/client": {
88
+ "types": "./dist/platform/client/index.d.ts",
89
+ "default": "./dist/platform/client/index.js"
90
+ }
91
+ },
92
+ "typesVersions": {
93
+ "*": {
94
+ "platform/client": [
95
+ "dist/platform/client/index.d.ts"
96
+ ],
97
+ "*": [
98
+ "*"
99
+ ]
100
+ }
101
+ }
67
102
  }