@base44-preview/sdk 0.8.51-pr.294.a7ae332 → 0.8.52-pr.286.2dbb896

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_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 { AppUpdate, AppStatus, ChatMessage, MessageParams, ToolCall, ToolDisplayProjection, ToolQuestionOption, ToolQuestion, ToolQuestionArguments, ToolSecretField, ToolSecretArguments, ToolPackageOperation, ToolPackageArguments, ToolPlanUpdate, ToolPlanArguments, ToolPrdArguments, ToolMediaArguments, ToolMediaResult, ToolQuestionAnswer, ToolQuestionInput, ToolOutcome, ToolGuardApproval, ToolResult, QueueItem, QueueUpdate, TaskUpdate, ImageReady, Directive, PlatformEventMap, PlatformEvent, Snapshot } 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,11 @@
1
+ import type { PlatformEvent, PlatformEventMap, Snapshot } from "./builder.events.types.js";
2
+ export declare const eventNames: readonly ["update_model", "directive", "queue_update", "task_update", "image_ready"];
3
+ /** Error frames that name an app room. */
4
+ export declare const roomErrorCodes: readonly ["access_denied", "access_revoked", "snapshot_unavailable"];
5
+ export declare const appPattern: RegExp;
6
+ export declare const roomFor: (appId: string) => string;
7
+ export declare function object(value: unknown): Record<string, unknown>;
8
+ export declare function string(value: unknown): string;
9
+ export declare function appFromRoom(value: unknown): string | undefined;
10
+ export declare function decode(type: keyof PlatformEventMap, appId: string, raw: unknown): PlatformEvent;
11
+ export declare function decodeSnapshot(raw: unknown): Snapshot;
@@ -0,0 +1,32 @@
1
+ export const eventNames = ["update_model", "directive", "queue_update", "task_update", "image_ready"];
2
+ /** Error frames that name an app room. */
3
+ export const roomErrorCodes = ["access_denied", "access_revoked", "snapshot_unavailable"];
4
+ export const appPattern = /^[a-f0-9]{24}$/;
5
+ export const roomFor = (appId) => `/apps/${appId}`;
6
+ export function object(value) {
7
+ if (!value || typeof value !== "object" || Array.isArray(value))
8
+ throw new Error("Invalid frame");
9
+ return value;
10
+ }
11
+ export function string(value) {
12
+ if (typeof value !== "string" || !value)
13
+ throw new Error("Invalid string");
14
+ return value;
15
+ }
16
+ export function appFromRoom(value) {
17
+ return typeof value === "string" && /^\/apps\/[a-f0-9]{24}$/.test(value) ? value.slice(6) : undefined;
18
+ }
19
+ export function decode(type, appId, raw) {
20
+ const frame = object(raw);
21
+ const wrapped = type === "update_model" || type === "task_update" || type === "image_ready";
22
+ const data = wrapped ? object(JSON.parse(string(frame.data))) : frame;
23
+ // Payload schemas are owned by the service; only decode/validate the transport envelope here.
24
+ return { type, appId, data };
25
+ }
26
+ export function decodeSnapshot(raw) {
27
+ const frame = object(raw);
28
+ if (!appFromRoom(frame.room) || !Array.isArray(frame.messages) || (frame.status !== null && typeof frame.status !== "object")) {
29
+ throw new Error("Invalid snapshot");
30
+ }
31
+ return frame;
32
+ }
@@ -0,0 +1,41 @@
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 serverError;
40
+ private protocolError;
41
+ }
@@ -0,0 +1,238 @@
1
+ import { io } from "socket.io-client";
2
+ import { PlatformSocketError } from "../errors.js";
3
+ import { appFromRoom, appPattern, decode, decodeSnapshot, eventNames, object, roomErrorCodes, roomFor } 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 === "session_replaced" ? "session_replaced" : "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("joined", (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("error", (raw) => this.serverError(raw));
54
+ for (const type of eventNames)
55
+ this.socket.on(type, (raw) => {
56
+ var _a;
57
+ try {
58
+ const appId = appFromRoom(object(raw).room);
59
+ if (!appId)
60
+ throw new Error("Invalid app");
61
+ (_a = this.subscriptions.get(appId)) === null || _a === void 0 ? void 0 : _a.event(decode(type, appId, raw));
62
+ }
63
+ catch (_b) {
64
+ this.protocolError(raw);
65
+ }
66
+ });
67
+ }
68
+ /** Connect with the session token. Resolves on CONNECT, before snapshots arrive.
69
+ * Transport loss retries up to five times and rejoins active subscriptions.
70
+ * Call again after a failure or `session_replaced`; concurrent calls share one attempt.
71
+ */
72
+ connect() {
73
+ if (this.closed)
74
+ return Promise.reject(new PlatformSocketError("client_closed"));
75
+ if (this.socket.connected)
76
+ return Promise.resolve();
77
+ if (this.connecting)
78
+ return this.connecting;
79
+ const promise = new Promise((resolve, reject) => {
80
+ this.resolveConnect = resolve;
81
+ this.rejectConnect = reject;
82
+ });
83
+ this.connecting = promise;
84
+ this.socket.connect();
85
+ return promise;
86
+ }
87
+ /** Subscribe before or after connecting. One subscription per app, maximum eight, each on the
88
+ * session's allowlist. Snapshots and events are awaited in order per app; a failed callback,
89
+ * an invalid frame or a server refusal ends the subscription.
90
+ */
91
+ subscribe(appId, options) {
92
+ if (this.closed)
93
+ throw new PlatformSocketError("client_closed");
94
+ if (!appPattern.test(appId))
95
+ throw new TypeError("appId must be 24 lowercase hexadecimal characters");
96
+ if (this.subscriptions.has(appId))
97
+ throw new TypeError("An app may only have one subscription per builder session");
98
+ if (this.subscriptions.size >= 8)
99
+ throw new PlatformSocketError("subscription_limit", appId);
100
+ const subscription = new Subscription(appId, { ...options }, () => {
101
+ this.subscriptions.delete(appId);
102
+ if (this.socket.connected)
103
+ this.socket.emit("leave", roomFor(appId));
104
+ });
105
+ this.subscriptions.set(appId, subscription);
106
+ if (this.socket.connected)
107
+ this.join(subscription);
108
+ return subscription;
109
+ }
110
+ /** Stop delivery, cancel reconnection and release all listeners. Idempotent and terminal. */
111
+ close() {
112
+ var _a, _b;
113
+ if (this.closed)
114
+ return;
115
+ this.closed = true;
116
+ ++this.authAttempt;
117
+ (_a = this.cancelAuth) === null || _a === void 0 ? void 0 : _a.call(this);
118
+ clearTimeout(this.retryTimer);
119
+ for (const subscription of this.subscriptions.values())
120
+ subscription.unsubscribe();
121
+ (_b = this.rejectConnect) === null || _b === void 0 ? void 0 : _b.call(this, new PlatformSocketError("client_closed"));
122
+ this.clearConnecting();
123
+ this.socket.removeAllListeners();
124
+ this.socket.io.removeAllListeners();
125
+ this.socket.disconnect();
126
+ }
127
+ join(subscription) {
128
+ this.socket.emit("join", roomFor(subscription.appId));
129
+ }
130
+ async authenticate(callback) {
131
+ var _a;
132
+ const attempt = ++this.authAttempt;
133
+ (_a = this.cancelAuth) === null || _a === void 0 ? void 0 : _a.call(this);
134
+ let timer;
135
+ const timeout = new Promise((_, reject) => {
136
+ timer = setTimeout(() => reject(new Error("Session token timeout")), 20000);
137
+ this.cancelAuth = () => { clearTimeout(timer); reject(new Error("Cancelled")); };
138
+ });
139
+ try {
140
+ if (!this.token) {
141
+ const token = await Promise.race([Promise.resolve().then(() => this.options.getSessionToken()), timeout]);
142
+ if (this.closed || attempt !== this.authAttempt)
143
+ return;
144
+ if (typeof token !== "string" || !token.trim())
145
+ throw new Error("Missing session token");
146
+ this.token = token;
147
+ this.tokenFresh = true;
148
+ }
149
+ callback({ session_token: this.token });
150
+ }
151
+ catch (_b) {
152
+ if (this.closed || attempt !== this.authAttempt)
153
+ return;
154
+ this.socket.disconnect();
155
+ this.connectionError(new PlatformSocketError("session_unavailable"));
156
+ }
157
+ finally {
158
+ clearTimeout(timer);
159
+ if (attempt === this.authAttempt)
160
+ this.cancelAuth = undefined;
161
+ }
162
+ }
163
+ /** A connect the server refused. Transport errors are Socket.IO's to retry. */
164
+ refused(error) {
165
+ var _a;
166
+ if (this.closed || error.message !== "connection_denied")
167
+ return;
168
+ if (((_a = error.data) === null || _a === void 0 ? void 0 : _a.retryable) === true) {
169
+ this.retryDenied();
170
+ return;
171
+ }
172
+ // A token this attempt did not just fetch may have expired: renew it once.
173
+ const renew = !this.tokenFresh;
174
+ this.token = undefined;
175
+ if (renew)
176
+ this.socket.connect();
177
+ else
178
+ this.connectionError(new PlatformSocketError("connection_denied"));
179
+ }
180
+ retryDenied() {
181
+ if (++this.denialRetries > MAX_DENIAL_RETRIES) {
182
+ this.denialRetries = 0;
183
+ this.connectionError(new PlatformSocketError("connection_failed"));
184
+ return;
185
+ }
186
+ const delay = Math.min(1000 * 2 ** (this.denialRetries - 1), 10000) * (0.5 + Math.random() / 2);
187
+ this.retryTimer = setTimeout(() => { this.retryTimer = undefined; if (!this.closed)
188
+ this.socket.connect(); }, delay);
189
+ }
190
+ clearConnecting() {
191
+ this.connecting = undefined;
192
+ this.resolveConnect = undefined;
193
+ this.rejectConnect = undefined;
194
+ }
195
+ connectionError(error) {
196
+ var _a;
197
+ (_a = this.rejectConnect) === null || _a === void 0 ? void 0 : _a.call(this, error);
198
+ this.clearConnecting();
199
+ if (!this.closed)
200
+ notify(this.options.onError, error);
201
+ }
202
+ serverError(raw) {
203
+ try {
204
+ const frame = object(raw);
205
+ if (frame.room === null && (frame.code === "session_expired" || frame.code === "session_replaced")) {
206
+ // The server disconnects next; the disconnect handler renews or stops.
207
+ this.token = undefined;
208
+ this.ending = frame.code;
209
+ return;
210
+ }
211
+ const appId = appFromRoom(frame.room);
212
+ const code = roomErrorCodes.find((code) => code === frame.code);
213
+ if (!appId || !code)
214
+ throw new Error("Unknown error");
215
+ const subscription = this.subscriptions.get(appId);
216
+ if (code === "snapshot_unavailable")
217
+ subscription === null || subscription === void 0 ? void 0 : subscription.warn(code);
218
+ else
219
+ subscription === null || subscription === void 0 ? void 0 : subscription.fail(code);
220
+ }
221
+ catch (_a) {
222
+ this.protocolError(raw);
223
+ }
224
+ }
225
+ protocolError(raw) {
226
+ var _a;
227
+ const frame = raw && typeof raw === "object" ? raw : {};
228
+ const appId = appFromRoom(frame.room);
229
+ if (appId)
230
+ (_a = this.subscriptions.get(appId)) === null || _a === void 0 ? void 0 : _a.fail("protocol_error");
231
+ else {
232
+ // Unknown routing: no app can be told which update it missed.
233
+ for (const subscription of [...this.subscriptions.values()])
234
+ subscription.fail("protocol_error");
235
+ this.connectionError(new PlatformSocketError("protocol_error"));
236
+ }
237
+ }
238
+ }
@@ -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,348 @@
1
+ /** A reviewed file, execution, or entity activity summary. */
2
+ export interface ToolDisplayProjection {
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 reviewed selectable answer to a builder question. */
17
+ export interface ToolQuestionOption {
18
+ /** Visible option label. */
19
+ label: string;
20
+ }
21
+ /** A reviewed builder question. Image/HTML source and private design context are excluded. */
22
+ export interface ToolQuestion {
23
+ /** Visible question text. */
24
+ question?: string;
25
+ /** Existing 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
+ /** Optional existing plan section identifier. */
32
+ covers?: string;
33
+ /** Reviewed selectable options. */
34
+ options?: ToolQuestionOption[];
35
+ }
36
+ /** Reviewed question arguments, serialized in `ToolCall.arguments_string`. */
37
+ export interface ToolQuestionArguments {
38
+ /** Questions presented by a clarifying-question tool. */
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
+ /** Reviewed secret-form arguments, serialized in `ToolCall.arguments_string`. */
49
+ export interface ToolSecretArguments {
50
+ /** Requested secret fields. */
51
+ secrets_schema: ToolSecretField[];
52
+ }
53
+ /** A reviewed 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
+ /** Reviewed package arguments, serialized in `ToolCall.arguments_string`. */
61
+ export interface ToolPackageArguments {
62
+ /** Requested package operations. */
63
+ packages: ToolPackageOperation[];
64
+ }
65
+ /** A reviewed add-only builder plan update. */
66
+ export interface ToolPlanUpdate {
67
+ /** Existing update action. */
68
+ action?: string;
69
+ /** Plan section key. */
70
+ section?: string;
71
+ /** Optional user-facing section label. */
72
+ section_label?: string;
73
+ /** Reviewed plan point. */
74
+ text?: string;
75
+ }
76
+ /** Reviewed plan arguments, serialized in `ToolCall.arguments_string`. */
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
+ /** A reviewed plan, serialized in `generate_prd`'s `ToolCall.arguments_string`. The plan is the
84
+ * user's own spec; in plan mode the message `content` beside it can be empty. */
85
+ export interface ToolPrdArguments {
86
+ /** App or feature name. */
87
+ app_name?: string;
88
+ /** What is being built and why. */
89
+ intent_and_goal?: string;
90
+ /** Who uses it and their roles. */
91
+ audience_and_roles?: string;
92
+ /** End-to-end user flows. */
93
+ core_flows?: string[];
94
+ /** Specific technical requirements. */
95
+ technical_requirements?: string;
96
+ /** Design preferences. */
97
+ design_preferences?: string;
98
+ /** Build or plan declaration, when the builder asks for one. */
99
+ prd_type?: string;
100
+ /** Store platform settled in planning, for e-commerce plans. */
101
+ store_platform?: string;
102
+ /** Engine settled in planning, for game plans. */
103
+ game_engine?: string;
104
+ }
105
+ /** Reviewed generated-media arguments, serialized in `ToolCall.arguments_string`. */
106
+ export interface ToolMediaArguments {
107
+ /** Visible media label. */
108
+ label?: string;
109
+ /** Requested image or video aspect ratio. */
110
+ aspect_ratio?: string;
111
+ }
112
+ /** Reviewed state of generated media. Raw generation prompts and IDs are never included. */
113
+ export interface ToolMediaResult {
114
+ /** Existing placeholder URL, used to associate an `image_ready` event with this tool. */
115
+ placeholder_url: string;
116
+ /** Current media-generation status. */
117
+ status: "pending" | "completed" | "failed";
118
+ /** Approved generated asset URL, once available. */
119
+ image_url: string | null;
120
+ }
121
+ /** A reviewed answer to a clarifying question. Secret-form input is never included. */
122
+ export interface ToolQuestionAnswer {
123
+ /** Zero-based question index. */
124
+ question_index?: number;
125
+ /** Labels selected by the user. */
126
+ selected_labels?: string[];
127
+ /** User-provided free-text answer. Structural filtering does not redact prose. */
128
+ custom_text?: string;
129
+ }
130
+ /** Reviewed clarifying-question input. */
131
+ export interface ToolQuestionInput {
132
+ /** Answers supplied to the question card. */
133
+ answers: ToolQuestionAnswer[];
134
+ }
135
+ /** Fixed reviewed completion text. Raw tool results and errors are never included. */
136
+ export type ToolOutcome = "Secret configuration completed." | "Package installation completed." | "Plan updated.";
137
+ /** Why a safety guard parked a call for approval. Only present while the call waits. */
138
+ export interface ToolGuardApproval {
139
+ /** Guard identifier, e.g. `exec_tool_send_email` or `bash_approval`. */
140
+ guard: string;
141
+ /** Human-readable reason. Omitted for shell commands, whose reason can quote the command. */
142
+ reason?: string;
143
+ /** Reviewed details for the guard; commands and record data are never included. */
144
+ details?: {
145
+ /** Entity method the code calls, e.g. `delete`. */
146
+ method?: string;
147
+ /** The agent's summary of what the code does. */
148
+ summary?: string;
149
+ /** Entity whose access rules change. */
150
+ entity_name?: string;
151
+ /** How the access rules change. */
152
+ change_type?: string;
153
+ /** Access operations whose rules change. */
154
+ changed_ops?: string[];
155
+ /** Backend function under test. */
156
+ function_name?: string;
157
+ /** The guard could not verify the function's source. */
158
+ inspection_failed?: boolean;
159
+ };
160
+ }
161
+ /** A fixed reviewed outcome, reviewed generated-media state, or a guard's approval reason. */
162
+ export type ToolResult = ToolOutcome | ToolMediaResult | ToolGuardApproval;
163
+ /** Public progress of an existing builder tool. */
164
+ export interface ToolCall {
165
+ /** Stable tool call identifier, when included in the update. */
166
+ id?: string;
167
+ /** Tool name displayed by the builder. */
168
+ name?: string;
169
+ /** Current execution state. */
170
+ status?: "running" | "success" | "error" | "stopped" | "waiting_for_user_input";
171
+ /** Whether the tool needs a user response through the partner backend. */
172
+ requires_user_input?: boolean;
173
+ /** Whether the builder auto-approved the reviewed operation. */
174
+ auto_approved?: boolean | null;
175
+ /** Whether the reviewed mutation was applied. */
176
+ mutation_applied?: boolean | null;
177
+ /** Existing serialized interaction category; no raw interaction payload. */
178
+ waiting_on?: {
179
+ /** Kind of response expected. */
180
+ kind?: "approval" | "choice" | "input" | null;
181
+ } | null;
182
+ /**
183
+ * JSON containing one reviewed argument shape: ToolQuestionArguments, ToolSecretArguments,
184
+ * ToolPackageArguments, ToolPlanArguments, ToolPrdArguments, or ToolMediaArguments.
185
+ * It is omitted for all other tools and malformed/partial streaming arguments.
186
+ */
187
+ arguments_string?: string;
188
+ /** Reviewed activity metadata for file, execution, or entity tools. */
189
+ display_projection?: ToolDisplayProjection;
190
+ /** Reviewed clarifying-question answers only. */
191
+ user_input?: ToolQuestionInput;
192
+ /** Fixed reviewed success outcome, generated-media state, or a parked call's guard reason. */
193
+ results?: ToolResult;
194
+ }
195
+ /** Public message replacement. Omitted properties are not synthesized by the SDK. */
196
+ export interface ChatMessage {
197
+ /** Existing message identifier; replace a message with the same identifier. */
198
+ id?: string;
199
+ /** Public message author category. System messages are never delivered. */
200
+ role?: "user" | "assistant";
201
+ /** Generated or user-authored text. Structural filtering is not prose redaction. */
202
+ content?: string | null;
203
+ /** Public tool progress with optional reviewed interaction details. */
204
+ tool_calls?: ToolCall[] | null;
205
+ /** Message timestamp, without author identity. */
206
+ metadata?: {
207
+ /** Existing timestamp string. */
208
+ created_date?: string | null;
209
+ } | null;
210
+ /** Existing checkpoint reference; mutations remain on the partner backend. */
211
+ checkpoint_id?: string | null;
212
+ /** Reviewed scalar message parameters. */
213
+ additional_message_params?: MessageParams;
214
+ }
215
+ /** Reviewed scalar parameters a chat UI needs while a turn runs. */
216
+ export interface MessageParams {
217
+ /** Identifier the sending client attached, to match its optimistic message. */
218
+ client_creation_id?: string;
219
+ /** The message was sent in plan mode. */
220
+ plan_mode?: boolean;
221
+ /** The message approves a plan. */
222
+ plan_approved?: boolean;
223
+ /** The message expects no AI reply. */
224
+ skip_ai_response?: boolean;
225
+ /** System message category. */
226
+ system_message_type?: string;
227
+ }
228
+ /** Public builder state, without error diagnostics or billing context. */
229
+ export interface AppStatus {
230
+ /** Current builder state. */
231
+ state?: "ready" | "processing" | "error";
232
+ /** Existing state timestamp. */
233
+ last_updated_date?: string | null;
234
+ }
235
+ /** Partial app update. Omitted keys mean unchanged; explicit null means clear. */
236
+ export interface AppUpdate {
237
+ /** Public builder state. */
238
+ status?: AppStatus | null;
239
+ /** Whole-message replacement by identifier, not a recursive message patch. */
240
+ _last_msg?: ChatMessage | null;
241
+ /** Conversation containing the replacement message. */
242
+ _last_msg_conversation_id?: string | null;
243
+ /** Existing branch scope, if supplied by the producer. */
244
+ _scope_branch_id?: string | null;
245
+ /** Whether to reload the app preview. */
246
+ sandbox_should_reload?: boolean | null;
247
+ /** Existing preview navigation target. */
248
+ navigate_preview_to?: string | null;
249
+ /** Existing forced preview navigation target. */
250
+ navigate_preview_force_to?: string | null;
251
+ }
252
+ /** Public queued builder request. */
253
+ export interface QueueItem {
254
+ /** Stable queue item identifier. */
255
+ id: string;
256
+ /** User-authored request text. */
257
+ content: string;
258
+ /** Existing creation timestamp. */
259
+ created_at: string;
260
+ /** Existing branch scope. */
261
+ branch_id?: string | null;
262
+ }
263
+ /** Full public queue snapshot, replacing the previous queue. */
264
+ export interface QueueUpdate {
265
+ /** Canonical app room. */
266
+ room: string;
267
+ /** App owning this queue. */
268
+ app_id: string;
269
+ /** Existing branch scope. */
270
+ branch_id?: string | null;
271
+ /** Current pending items. */
272
+ items: QueueItem[];
273
+ /** Whether queue processing is paused. */
274
+ is_paused: boolean;
275
+ /** Identifier of the item just processed, when supplied. */
276
+ processed_item_id?: string | null;
277
+ }
278
+ /** Public tool task progress. */
279
+ export interface TaskUpdate {
280
+ /** Existing task lifecycle event. */
281
+ event_type: "task_started" | "task_progress" | "task_completed" | "task_failed" | "task_cancelled";
282
+ /** Associated tool call. */
283
+ tool_call_id?: string | null;
284
+ /** Associated chat message. */
285
+ message_id?: string | null;
286
+ /** Existing branch scope. */
287
+ branch_id?: string | null;
288
+ /** Numeric progress only; diagnostic/free-text messages are withheld. */
289
+ progress?: {
290
+ /** Completed work units. */
291
+ current?: number | null;
292
+ /** Total work units, when known. */
293
+ total?: number | null;
294
+ /** Producer-supplied percentage. */
295
+ percentage?: number | null;
296
+ } | null;
297
+ }
298
+ /** Placeholder resolution or image-generation completion. */
299
+ export interface ImageReady {
300
+ /** Placeholder being resolved. */
301
+ placeholder_url: string;
302
+ /** Existing generation state. */
303
+ status: "pending" | "completed" | "failed";
304
+ /** Resolved image URL, or null when unavailable. */
305
+ image_url?: string | null;
306
+ }
307
+ /** Invalidation notice. A main-conversation `conversation_changed` is followed by a snapshot. */
308
+ export interface Directive {
309
+ /** Canonical app room. */
310
+ room: string;
311
+ /** Public invalidation category. */
312
+ type: "conversation_changed" | "app_files_changed" | "branch_deleted" | "imported_git_changed" | "imported_pull_request_changed";
313
+ /** Existing branch scope. */
314
+ branch_id?: string | null;
315
+ }
316
+ /** Mapping of wire event names to decoded public payloads. */
317
+ export interface PlatformEventMap {
318
+ /** Partial builder/app update. */
319
+ update_model: AppUpdate;
320
+ /** Conversation or file invalidation. */
321
+ directive: Directive;
322
+ /** Full queue snapshot. */
323
+ queue_update: QueueUpdate;
324
+ /** Numeric tool progress. */
325
+ task_update: TaskUpdate;
326
+ /** Image placeholder resolution. */
327
+ image_ready: ImageReady;
328
+ }
329
+ /** Ordered delivery with decoded data and the original event name. */
330
+ export type PlatformEvent = {
331
+ [K in keyof PlatformEventMap]: {
332
+ /** Original socket event name; narrows the payload type. */
333
+ type: K;
334
+ /** Authorized app receiving this event. */
335
+ appId: string;
336
+ /** Decoded payload; existing field names and omission/null semantics are retained. */
337
+ data: PlatformEventMap[K];
338
+ };
339
+ }[keyof PlatformEventMap];
340
+ /** An app's current state: sent after each join and rejoin, and after a main-conversation rewrite. */
341
+ export interface Snapshot {
342
+ /** Canonical app room. */
343
+ room: string;
344
+ /** Current builder state. */
345
+ status: AppStatus | null;
346
+ /** The last 50 public messages, oldest first. */
347
+ messages: ChatMessage[];
348
+ }
@@ -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 may precede it; merge its messages by `id`.
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.2dbb896",
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
  }