@base44-preview/sdk 0.8.40-pr.212.c163c94 → 0.8.40-pr.212.f89ce7f
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/dist/client.js +21 -32
- package/dist/client.types.d.ts +6 -20
- package/dist/modules/actors.d.ts +21 -20
- package/dist/modules/actors.js +142 -93
- package/dist/modules/actors.types.d.ts +38 -26
- package/dist/modules/ai-gateway.types.d.ts +70 -19
- package/dist/modules/sso.types.d.ts +28 -22
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -12,7 +12,7 @@ import { createAppLogsModule } from "./modules/app-logs.js";
|
|
|
12
12
|
import { createUsersModule } from "./modules/users.js";
|
|
13
13
|
import { RoomsSocket } from "./utils/socket-utils.js";
|
|
14
14
|
import { createAnalyticsModule } from "./modules/analytics.js";
|
|
15
|
-
import { createActorsModule } from "./modules/actors.js";
|
|
15
|
+
import { createActorsModule, resolveActorsWsUrl } from "./modules/actors.js";
|
|
16
16
|
/**
|
|
17
17
|
* Creates a Base44 client.
|
|
18
18
|
*
|
|
@@ -51,28 +51,18 @@ import { createActorsModule } from "./modules/actors.js";
|
|
|
51
51
|
* ```
|
|
52
52
|
*/
|
|
53
53
|
export function createClient(config) {
|
|
54
|
-
var _a, _b;
|
|
55
|
-
const { serverUrl = "https://base44.app", appId, token, serviceToken, requiresAuth = false, appBaseUrl, options, functionsVersion, headers: optionalHeaders,
|
|
54
|
+
var _a, _b, _c, _d;
|
|
55
|
+
const { serverUrl = "https://base44.app", appId, token, serviceToken, requiresAuth = false, appBaseUrl, options, functionsVersion, headers: optionalHeaders, actorsWsUrl, } = config;
|
|
56
56
|
// Normalize appBaseUrl to always be a string (empty if not provided or invalid)
|
|
57
57
|
const normalizedAppBaseUrl = typeof appBaseUrl === "string" ? appBaseUrl : "";
|
|
58
|
-
//
|
|
59
|
-
// the
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (dispatcherWsUrl)
|
|
67
|
-
return dispatcherWsUrl.replace(/\/$/, "");
|
|
68
|
-
const appOrigin = normalizedAppBaseUrl ||
|
|
69
|
-
// React Native has a bare `window` with no `location`, so guard both.
|
|
70
|
-
(typeof window !== "undefined" ? (_b = (_a = window.location) === null || _a === void 0 ? void 0 : _a.origin) !== null && _b !== void 0 ? _b : "" : "");
|
|
71
|
-
return (appOrigin || serverUrl)
|
|
72
|
-
.replace(/\/$/, "")
|
|
73
|
-
.replace(/^https:\/\//, "wss://")
|
|
74
|
-
.replace(/^http:\/\//, "ws://");
|
|
75
|
-
})();
|
|
58
|
+
// Same-origin with the running app (it proxies /parties to the backend), not
|
|
59
|
+
// the API host. React Native has a bare `window` with no `location`, so guard both.
|
|
60
|
+
const resolvedActorsWsUrl = resolveActorsWsUrl({
|
|
61
|
+
actorsWsUrl,
|
|
62
|
+
appBaseUrl: normalizedAppBaseUrl,
|
|
63
|
+
browserOrigin: typeof window !== "undefined" ? (_b = (_a = window.location) === null || _a === void 0 ? void 0 : _a.origin) !== null && _b !== void 0 ? _b : "" : "",
|
|
64
|
+
serverUrl,
|
|
65
|
+
});
|
|
76
66
|
const socketConfig = {
|
|
77
67
|
serverUrl,
|
|
78
68
|
mountPath: "/ws-user-apps/socket.io/",
|
|
@@ -142,6 +132,12 @@ export function createClient(config) {
|
|
|
142
132
|
userAuthModule.setToken(accessToken);
|
|
143
133
|
}
|
|
144
134
|
}
|
|
135
|
+
const actorsModule = createActorsModule({
|
|
136
|
+
appId,
|
|
137
|
+
actorsWsUrl: resolvedActorsWsUrl,
|
|
138
|
+
functionsVersion,
|
|
139
|
+
getAuthToken: () => token || getAccessToken(),
|
|
140
|
+
});
|
|
145
141
|
const userModules = {
|
|
146
142
|
entities: createEntitiesModule({
|
|
147
143
|
axios: axiosClient,
|
|
@@ -161,7 +157,7 @@ export function createClient(config) {
|
|
|
161
157
|
}
|
|
162
158
|
return headers;
|
|
163
159
|
},
|
|
164
|
-
baseURL: (
|
|
160
|
+
baseURL: (_c = functionsAxiosClient.defaults) === null || _c === void 0 ? void 0 : _c.baseURL,
|
|
165
161
|
}),
|
|
166
162
|
agents: createAgentsModule({
|
|
167
163
|
axios: axiosClient,
|
|
@@ -179,17 +175,10 @@ export function createClient(config) {
|
|
|
179
175
|
appId,
|
|
180
176
|
userAuthModule,
|
|
181
177
|
}),
|
|
182
|
-
actors:
|
|
183
|
-
appId,
|
|
184
|
-
dispatcherWsUrl: resolvedDispatcherWsUrl,
|
|
185
|
-
webSocketImpl,
|
|
186
|
-
functionsVersion,
|
|
187
|
-
// Same credential as function calls; the platform proxy authenticates the
|
|
188
|
-
// WS connection with it (anonymous when absent) — no pre-connect token mint.
|
|
189
|
-
getAuthToken: () => token || getAccessToken(),
|
|
190
|
-
}),
|
|
178
|
+
actors: actorsModule.module,
|
|
191
179
|
cleanup: () => {
|
|
192
180
|
userModules.analytics.cleanup();
|
|
181
|
+
actorsModule.closeAll();
|
|
193
182
|
if (socket) {
|
|
194
183
|
socket.disconnect();
|
|
195
184
|
}
|
|
@@ -213,7 +202,7 @@ export function createClient(config) {
|
|
|
213
202
|
}
|
|
214
203
|
return headers;
|
|
215
204
|
},
|
|
216
|
-
baseURL: (
|
|
205
|
+
baseURL: (_d = serviceRoleFunctionsAxiosClient.defaults) === null || _d === void 0 ? void 0 : _d.baseURL,
|
|
217
206
|
}),
|
|
218
207
|
agents: createAgentsModule({
|
|
219
208
|
axios: serviceRoleAxiosClient,
|
package/dist/client.types.d.ts
CHANGED
|
@@ -75,27 +75,15 @@ export interface CreateClientConfig {
|
|
|
75
75
|
*/
|
|
76
76
|
options?: CreateClientOptions;
|
|
77
77
|
/**
|
|
78
|
-
* Base WebSocket URL for
|
|
78
|
+
* Base WebSocket URL for Actor connections.
|
|
79
79
|
*
|
|
80
80
|
* Defaults to the app's own origin (`appBaseUrl`, else the browser's
|
|
81
81
|
* `window.location.origin`, else `serverUrl`) with `https://` replaced by
|
|
82
|
-
* `wss://` (or `http://` by `ws://`) — so the
|
|
83
|
-
* with the running app, which proxies `/parties` to the backend.
|
|
84
|
-
* Override when the
|
|
82
|
+
* `wss://` (or `http://` by `ws://`) — so the Actor socket is same-origin
|
|
83
|
+
* with the running app, which proxies `/parties` to the backend dispatcher.
|
|
84
|
+
* Override only when the Actor host differs from the app origin.
|
|
85
85
|
*/
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* WebSocket implementation for realtime subscriptions in environments
|
|
89
|
-
* without a global `WebSocket` (Node.js < 22). Browsers and Node ≥ 22
|
|
90
|
-
* don't need this.
|
|
91
|
-
*
|
|
92
|
-
* @example
|
|
93
|
-
* ```typescript
|
|
94
|
-
* import WS from "ws";
|
|
95
|
-
* const base44 = createClient({ appId, webSocketImpl: WS });
|
|
96
|
-
* ```
|
|
97
|
-
*/
|
|
98
|
-
webSocketImpl?: unknown;
|
|
86
|
+
actorsWsUrl?: string;
|
|
99
87
|
}
|
|
100
88
|
/**
|
|
101
89
|
* The Base44 client instance.
|
|
@@ -164,9 +152,7 @@ export interface Base44Client {
|
|
|
164
152
|
functions: FunctionsModule;
|
|
165
153
|
/** {@link IntegrationsModule | Integrations module} with elevated permissions. */
|
|
166
154
|
integrations: IntegrationsModule;
|
|
167
|
-
/** {@link SsoModule | SSO module} for generating SSO tokens.
|
|
168
|
-
* @internal
|
|
169
|
-
*/
|
|
155
|
+
/** {@link SsoModule | SSO module} for generating SSO tokens. */
|
|
170
156
|
sso: SsoModule;
|
|
171
157
|
/** Cleanup function to disconnect WebSocket connections. */
|
|
172
158
|
cleanup: () => void;
|
package/dist/modules/actors.d.ts
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ActorRoom } from "./actors.types.js";
|
|
2
|
+
interface ActorsConfig {
|
|
2
3
|
appId: string;
|
|
3
|
-
/** Current user access token, if authenticated. Rides the WS query
|
|
4
|
-
*
|
|
5
|
-
* connection like a backend-function call; anonymous connects omit it. */
|
|
4
|
+
/** Current user access token, if authenticated. Rides the WS query so the
|
|
5
|
+
* platform proxy can authenticate the connection; anonymous connects omit it. */
|
|
6
6
|
getAuthToken(): string | null | undefined;
|
|
7
7
|
/** Same semantics as function calls: editors with a non-prod version get the
|
|
8
8
|
* draft actor script; everyone else gets the published one. */
|
|
9
9
|
functionsVersion?: string;
|
|
10
|
-
|
|
11
|
-
/** WebSocket implementation for runtimes without a global one (Node < 22). */
|
|
12
|
-
webSocketImpl?: unknown;
|
|
13
|
-
}): Record<string, ActorClient>;
|
|
14
|
-
/** Handle for an active actor subscription. */
|
|
15
|
-
interface ActorSubscription {
|
|
16
|
-
/** This connection's id — the same value the actor receives as `conn.id`. */
|
|
17
|
-
id: string;
|
|
18
|
-
/** Close the subscription and its underlying socket. */
|
|
19
|
-
unsubscribe(): void;
|
|
20
|
-
}
|
|
21
|
-
interface ActorClient {
|
|
22
|
-
subscribe(instanceId: string, callback: (data: unknown) => void, options?: {
|
|
23
|
-
id?: string;
|
|
24
|
-
}): ActorSubscription;
|
|
25
|
-
send(instanceId: string, data: unknown): void;
|
|
10
|
+
actorsWsUrl: string;
|
|
26
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Base WebSocket URL for Actor connections. An explicit `actorsWsUrl` wins;
|
|
14
|
+
* otherwise the app's own origin (`appBaseUrl`, else the browser origin, else
|
|
15
|
+
* `serverUrl`) with `https://`→`wss://` / `http://`→`ws://` and no trailing
|
|
16
|
+
* slash — the app proxies `/parties` to the backend dispatcher.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveActorsWsUrl(opts: {
|
|
19
|
+
actorsWsUrl?: string;
|
|
20
|
+
appBaseUrl?: string;
|
|
21
|
+
browserOrigin?: string;
|
|
22
|
+
serverUrl: string;
|
|
23
|
+
}): string;
|
|
24
|
+
export declare function createActorsModule(config: ActorsConfig): {
|
|
25
|
+
module: Record<string, (instanceId: string) => ActorRoom>;
|
|
26
|
+
closeAll: () => void;
|
|
27
|
+
};
|
|
27
28
|
export {};
|
package/dist/modules/actors.js
CHANGED
|
@@ -1,100 +1,149 @@
|
|
|
1
1
|
import PartySocket from "partysocket";
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
// Heartbeat / half-open detection: PartySocket only reconnects on a close/error
|
|
3
|
+
// event, so ping periodically and force a reconnect if nothing returns in DEAD_MS.
|
|
4
|
+
const PING_MS = 1000;
|
|
5
|
+
const DEAD_MS = 3000;
|
|
6
|
+
class Room {
|
|
7
|
+
constructor(actorName, instanceId, config, onClose) {
|
|
8
|
+
this.actorName = actorName;
|
|
9
|
+
this.instanceId = instanceId;
|
|
10
|
+
this.config = config;
|
|
11
|
+
this.onClose = onClose;
|
|
12
|
+
this.ws = null;
|
|
13
|
+
this.listeners = new Set();
|
|
14
|
+
this.heartbeat = null;
|
|
15
|
+
this.connId = null;
|
|
16
|
+
}
|
|
17
|
+
get id() {
|
|
18
|
+
if (!this.connId) {
|
|
19
|
+
throw new Error(`${this.actorName}:${this.instanceId}: connect() before reading id`);
|
|
20
|
+
}
|
|
21
|
+
return this.connId;
|
|
22
|
+
}
|
|
23
|
+
connect(options) {
|
|
24
|
+
var _a;
|
|
25
|
+
if (this.ws)
|
|
26
|
+
return this;
|
|
27
|
+
// The client picks its own conn id; it becomes _pk → the actor's conn.id.
|
|
28
|
+
const connId = (_a = options === null || options === void 0 ? void 0 : options.id) !== null && _a !== void 0 ? _a : crypto.randomUUID();
|
|
29
|
+
this.connId = connId;
|
|
30
|
+
const ws = new PartySocket({
|
|
31
|
+
host: this.config.actorsWsUrl,
|
|
32
|
+
party: this.actorName,
|
|
33
|
+
room: this.instanceId,
|
|
34
|
+
id: connId,
|
|
35
|
+
// Re-read on every (re)connect so a login/logout is picked up.
|
|
36
|
+
query: () => {
|
|
37
|
+
const token = this.config.getAuthToken();
|
|
38
|
+
return {
|
|
39
|
+
app_id: this.config.appId,
|
|
40
|
+
handler: this.actorName,
|
|
41
|
+
...(token ? { token } : {}),
|
|
42
|
+
...(this.config.functionsVersion ? { fv: this.config.functionsVersion } : {}),
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
this.ws = ws;
|
|
47
|
+
let lastMsg = Date.now();
|
|
48
|
+
const bumpAlive = () => { lastMsg = Date.now(); };
|
|
49
|
+
ws.addEventListener("open", bumpAlive);
|
|
50
|
+
ws.addEventListener("message", (ev) => {
|
|
51
|
+
bumpAlive();
|
|
52
|
+
let data;
|
|
53
|
+
try {
|
|
54
|
+
data = JSON.parse(ev.data);
|
|
55
|
+
}
|
|
56
|
+
catch (_a) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const msgType = data && typeof data === "object" ? data.type : undefined;
|
|
60
|
+
if (msgType === "__pong")
|
|
61
|
+
return;
|
|
62
|
+
for (const listener of this.listeners)
|
|
63
|
+
listener(data);
|
|
64
|
+
});
|
|
65
|
+
this.heartbeat = setInterval(() => {
|
|
66
|
+
if (Date.now() - lastMsg > DEAD_MS) {
|
|
67
|
+
bumpAlive(); // avoid a reconnect storm while the new socket comes up
|
|
68
|
+
ws.reconnect();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
// The deployed shim echoes __ping → __pong (base44-userapp-bundler
|
|
73
|
+
// shim/actor.ts); without that, an idle room reconnects every DEAD_MS.
|
|
74
|
+
ws.send(JSON.stringify({ type: "__ping" }));
|
|
75
|
+
}
|
|
76
|
+
catch (_a) {
|
|
77
|
+
// not open; the watchdog above will reconnect
|
|
78
|
+
}
|
|
79
|
+
}, PING_MS);
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
subscribe(callback) {
|
|
83
|
+
if (!this.ws) {
|
|
84
|
+
throw new Error(`${this.actorName}:${this.instanceId}: connect() before subscribe()`);
|
|
85
|
+
}
|
|
86
|
+
this.listeners.add(callback);
|
|
87
|
+
return {
|
|
88
|
+
unsubscribe: () => { this.listeners.delete(callback); },
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
send(data) {
|
|
92
|
+
if (!this.ws) {
|
|
93
|
+
throw new Error(`${this.actorName}:${this.instanceId}: connect() before send()`);
|
|
94
|
+
}
|
|
95
|
+
this.ws.send(JSON.stringify(data));
|
|
96
|
+
}
|
|
97
|
+
close() {
|
|
98
|
+
var _a, _b;
|
|
99
|
+
if (this.heartbeat) {
|
|
100
|
+
clearInterval(this.heartbeat);
|
|
101
|
+
this.heartbeat = null;
|
|
102
|
+
}
|
|
103
|
+
this.listeners.clear();
|
|
104
|
+
(_a = this.ws) === null || _a === void 0 ? void 0 : _a.close();
|
|
105
|
+
this.ws = null;
|
|
106
|
+
this.connId = null;
|
|
107
|
+
(_b = this.onClose) === null || _b === void 0 ? void 0 : _b.call(this);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Base WebSocket URL for Actor connections. An explicit `actorsWsUrl` wins;
|
|
112
|
+
* otherwise the app's own origin (`appBaseUrl`, else the browser origin, else
|
|
113
|
+
* `serverUrl`) with `https://`→`wss://` / `http://`→`ws://` and no trailing
|
|
114
|
+
* slash — the app proxies `/parties` to the backend dispatcher.
|
|
115
|
+
*/
|
|
116
|
+
export function resolveActorsWsUrl(opts) {
|
|
117
|
+
if (opts.actorsWsUrl)
|
|
118
|
+
return opts.actorsWsUrl.replace(/\/$/, "");
|
|
119
|
+
const appOrigin = opts.appBaseUrl || opts.browserOrigin || "";
|
|
120
|
+
return (appOrigin || opts.serverUrl)
|
|
121
|
+
.replace(/\/$/, "")
|
|
122
|
+
.replace(/^https:\/\//, "wss://")
|
|
123
|
+
.replace(/^http:\/\//, "ws://");
|
|
6
124
|
}
|
|
7
125
|
export function createActorsModule(config) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// No pre-connect token mint: the platform proxy authenticates the
|
|
22
|
-
// connection itself, exactly like a backend-function call. `handler`
|
|
23
|
-
// carries the case-preserved actor name (the `party` path segment is
|
|
24
|
-
// lowercased by PartySocket). query as fn: re-read on every (re)connect
|
|
25
|
-
// so a login/logout between reconnects is picked up.
|
|
26
|
-
const ws = new PartySocket({
|
|
27
|
-
host: config.dispatcherWsUrl,
|
|
28
|
-
party: actorName,
|
|
29
|
-
room: instanceId,
|
|
30
|
-
id: connId,
|
|
31
|
-
...(config.webSocketImpl ? { WebSocket: config.webSocketImpl } : {}),
|
|
32
|
-
query: () => {
|
|
33
|
-
const token = config.getAuthToken();
|
|
34
|
-
return {
|
|
35
|
-
app_id: config.appId,
|
|
36
|
-
handler: actorName,
|
|
37
|
-
...(token ? { token } : {}),
|
|
38
|
-
...(config.functionsVersion ? { fv: config.functionsVersion } : {}),
|
|
39
|
-
};
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
activeSockets.set(key, ws);
|
|
43
|
-
// Heartbeat / half-open detection. PartySocket only reconnects on a
|
|
44
|
-
// browser close/error event, so a silently-dead connection (TCP alive,
|
|
45
|
-
// no data — common behind proxies/LBs) hangs until the OS idle timeout
|
|
46
|
-
// (~60s). We ping periodically and force a reconnect if nothing comes
|
|
47
|
-
// back within DEAD_MS, cutting detection from ~60s to a few seconds.
|
|
48
|
-
const PING_MS = 1000;
|
|
49
|
-
const DEAD_MS = 3000;
|
|
50
|
-
let lastMsg = Date.now();
|
|
51
|
-
const bumpAlive = () => { lastMsg = Date.now(); };
|
|
52
|
-
ws.addEventListener("open", bumpAlive);
|
|
53
|
-
ws.addEventListener("message", (ev) => {
|
|
54
|
-
bumpAlive();
|
|
55
|
-
let data;
|
|
56
|
-
try {
|
|
57
|
-
data = JSON.parse(ev.data);
|
|
58
|
-
}
|
|
59
|
-
catch (_a) {
|
|
60
|
-
return; // ignore malformed
|
|
61
|
-
}
|
|
62
|
-
// Swallow platform messages — never surface them to the app.
|
|
63
|
-
const msgType = data && typeof data === "object" ? data.type : undefined;
|
|
64
|
-
if (msgType === "__pong")
|
|
65
|
-
return;
|
|
66
|
-
callback(data);
|
|
67
|
-
});
|
|
68
|
-
const heartbeat = setInterval(() => {
|
|
69
|
-
if (Date.now() - lastMsg > DEAD_MS) {
|
|
70
|
-
bumpAlive(); // avoid a reconnect storm while the new socket comes up
|
|
71
|
-
ws.reconnect();
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
try {
|
|
75
|
-
ws.send(JSON.stringify({ type: "__ping" }));
|
|
76
|
-
}
|
|
77
|
-
catch (_a) {
|
|
78
|
-
// socket not open; the watchdog above will force a reconnect
|
|
79
|
-
}
|
|
80
|
-
}, PING_MS);
|
|
81
|
-
return {
|
|
82
|
-
id: connId, // the connection id (same value the actor sees as conn.id)
|
|
83
|
-
unsubscribe() {
|
|
84
|
-
clearInterval(heartbeat);
|
|
85
|
-
activeSockets.delete(key);
|
|
86
|
-
ws.close();
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
},
|
|
90
|
-
send(instanceId, data) {
|
|
91
|
-
const key = socketKey(actorName, instanceId);
|
|
92
|
-
const ws = activeSockets.get(key);
|
|
93
|
-
if (!ws)
|
|
94
|
-
throw new Error(`No active subscription for ${actorName}:${instanceId}`);
|
|
95
|
-
ws.send(JSON.stringify(data));
|
|
96
|
-
},
|
|
126
|
+
// Live rooms this client opened, so client.cleanup() can reclaim any the app
|
|
127
|
+
// forgot to close() (each room removes itself here on close).
|
|
128
|
+
const rooms = new Set();
|
|
129
|
+
const module = new Proxy({}, {
|
|
130
|
+
get(_, key) {
|
|
131
|
+
// Symbols and `then` resolve to undefined (so the module isn't mistaken
|
|
132
|
+
// for a thenable when awaited); any string key is an actor name.
|
|
133
|
+
if (typeof key !== "string" || key === "then")
|
|
134
|
+
return undefined;
|
|
135
|
+
return (instanceId) => {
|
|
136
|
+
const room = new Room(key, instanceId, config, () => rooms.delete(room));
|
|
137
|
+
rooms.add(room);
|
|
138
|
+
return room;
|
|
97
139
|
};
|
|
98
140
|
},
|
|
99
141
|
});
|
|
142
|
+
return {
|
|
143
|
+
module,
|
|
144
|
+
closeAll: () => {
|
|
145
|
+
for (const room of [...rooms])
|
|
146
|
+
room.close();
|
|
147
|
+
},
|
|
148
|
+
};
|
|
100
149
|
}
|
|
@@ -32,43 +32,55 @@ type ToClientFor<N extends string> = N extends keyof ActorRegistry ? ActorRegist
|
|
|
32
32
|
type ToServerFor<N extends string> = N extends keyof ActorRegistry ? ActorRegistry[N] extends {
|
|
33
33
|
toServer: infer O;
|
|
34
34
|
} ? O : unknown : unknown;
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
* Typed automatically when the actor is registered in {@link ActorRegistry}.
|
|
38
|
-
*/
|
|
39
|
-
export interface ActorClient<N extends string = string> {
|
|
35
|
+
/** Options for {@link ActorRoom.connect}. */
|
|
36
|
+
export interface ActorConnectOptions {
|
|
40
37
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* Pass `options.id` to control the connection id (e.g. a stable per-tab id so a
|
|
45
|
-
* reconnect reuses the same server-side connection); omit it for an auto-generated
|
|
46
|
-
* per-connection id.
|
|
38
|
+
* The connection id — becomes the actor's `conn.id`. Supply a stable value
|
|
39
|
+
* (e.g. persisted per tab) so a reconnect reuses the same server-side
|
|
40
|
+
* identity; omit for an auto-generated per-connection id.
|
|
47
41
|
*/
|
|
48
|
-
|
|
49
|
-
id?: string;
|
|
50
|
-
}): ActorSubscription;
|
|
51
|
-
/** Send a message over the open socket. Throws if not subscribed. */
|
|
52
|
-
send(instanceId: string, data: ToServerFor<N>): void;
|
|
42
|
+
id?: string;
|
|
53
43
|
}
|
|
54
|
-
/** Handle for
|
|
44
|
+
/** Handle for one listener registered via {@link ActorRoom.subscribe}. */
|
|
55
45
|
export interface ActorSubscription {
|
|
56
|
-
/**
|
|
57
|
-
id: string;
|
|
58
|
-
/** Close the subscription and its underlying socket. */
|
|
46
|
+
/** Remove this listener; other listeners and the socket stay live. */
|
|
59
47
|
unsubscribe(): void;
|
|
60
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* A single actor room. Obtained from {@link ActorClient} (`actors.MyActor(id)`)
|
|
51
|
+
* and made live with {@link connect}. The handle IS the connection: one socket,
|
|
52
|
+
* any number of {@link subscribe} listeners.
|
|
53
|
+
*/
|
|
54
|
+
export interface ActorRoom<N extends string = string> {
|
|
55
|
+
/** The connection id (the value the actor sees as `conn.id`). Throws before {@link connect}. */
|
|
56
|
+
readonly id: string;
|
|
57
|
+
/** Open the WebSocket (required before subscribe/send). Idempotent; returns this. */
|
|
58
|
+
connect(options?: ActorConnectOptions): this;
|
|
59
|
+
/** Register a message listener. Multiple are allowed; returns a per-listener unsubscribe. */
|
|
60
|
+
subscribe(callback: (data: ToClientFor<N>) => void): ActorSubscription;
|
|
61
|
+
/** Send a message. Throws before {@link connect}; buffered by the socket once connecting. */
|
|
62
|
+
send(data: ToServerFor<N>): void;
|
|
63
|
+
/** Tear down the socket, heartbeat, and all listeners. */
|
|
64
|
+
close(): void;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Client for a single named Actor — call it with a room id to get an
|
|
68
|
+
* {@link ActorRoom}. Typed automatically when the actor is registered in
|
|
69
|
+
* {@link ActorRegistry}.
|
|
70
|
+
*/
|
|
71
|
+
export interface ActorClient<N extends string = string> {
|
|
72
|
+
(instanceId: string): ActorRoom<N>;
|
|
73
|
+
}
|
|
61
74
|
/**
|
|
62
75
|
* The actors module provides access to Cloudflare Durable Object-backed
|
|
63
76
|
* Actors deployed by the Base44 platform.
|
|
64
77
|
*
|
|
65
|
-
* Actor names are accessed as dynamic properties on this module:
|
|
66
78
|
* ```typescript
|
|
67
|
-
* const
|
|
68
|
-
*
|
|
69
|
-
* });
|
|
70
|
-
*
|
|
71
|
-
*
|
|
79
|
+
* const room = base44.actors.MyActor("room-1").connect();
|
|
80
|
+
* const sub = room.subscribe((msg) => console.log(msg)); // typed via ActorRegistry
|
|
81
|
+
* room.send({ type: "message", text: "hi" });
|
|
82
|
+
* sub.unsubscribe();
|
|
83
|
+
* room.close();
|
|
72
84
|
* ```
|
|
73
85
|
*/
|
|
74
86
|
export type ActorsModule = {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Contains the base URL and bearer token to use with any OpenAI-compatible
|
|
5
|
-
* client pointed at the Base44 AI Gateway.
|
|
2
|
+
* Connection details for the Base44 AI Gateway.
|
|
6
3
|
*/
|
|
7
4
|
export interface AiGatewayConnection {
|
|
8
|
-
/** Base URL of the gateway's OpenAI-compatible endpoint. */
|
|
5
|
+
/** Base URL of the gateway's OpenAI-compatible Chat Completions endpoint. */
|
|
9
6
|
baseURL: string;
|
|
10
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Bearer token that authenticates the request. Empty string when the caller is
|
|
9
|
+
* unauthenticated.
|
|
10
|
+
*/
|
|
11
11
|
token: string;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
@@ -25,13 +25,45 @@ export interface AiGatewayModuleConfig {
|
|
|
25
25
|
/**
|
|
26
26
|
* AI Gateway module for calling Base44's managed AI models from your own code.
|
|
27
27
|
*
|
|
28
|
-
*
|
|
29
|
-
* OpenAI-compatible
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* `connection()` hands you a `baseURL` and `token` that authenticate as your
|
|
29
|
+
* Base44 app. An OpenAI-compatible client is any library, such as the `openai`
|
|
30
|
+
* SDK or the Vercel AI SDK, that has the same request and response format
|
|
31
|
+
* as OpenAI's Chat Completions API and lets you point it at a custom `baseURL`
|
|
32
|
+
* instead of OpenAI's own servers. Pass `connection()`'s values to one of
|
|
33
|
+
* these clients and it works against Base44's gateway exactly as it would
|
|
34
|
+
* against the provider directly, no separate account, API key, or billing
|
|
35
|
+
* setup with the underlying model provider required.
|
|
36
|
+
*
|
|
37
|
+
* Call `connection()` from a backend function rather than the browser. This
|
|
38
|
+
* keeps your instructions, tools, and business logic server-side, and lets
|
|
39
|
+
* you enforce your own auth, rate, and spend limits around the call. The
|
|
40
|
+
* `token` it returns is the caller's regular session token, the same one
|
|
41
|
+
* used for every other SDK call.
|
|
42
|
+
*
|
|
43
|
+
* ## Models
|
|
44
|
+
*
|
|
45
|
+
* You can use any of the [models available through `InvokeLLM`](/developers/references/sdk/docs/type-aliases/integrations#invokellm).
|
|
46
|
+
* Pass `'automatic'` to let Base44 choose one, or pin a specific model such
|
|
47
|
+
* as `'claude_sonnet_4_6'`, `'gpt_5_5'`, or `'gemini_3_1_pro'`.
|
|
48
|
+
*
|
|
49
|
+
* ## Authentication Modes
|
|
50
|
+
*
|
|
51
|
+
* There's no permission difference between modes. Both just determine which
|
|
52
|
+
* token `connection()` returns:
|
|
53
|
+
*
|
|
54
|
+
* - **User authentication** (`base44.aiGateway`): Returns the signed-in app user's token.
|
|
55
|
+
* - **Service role authentication** (`base44.asServiceRole.aiGateway`): Returns the service-role token instead, for calling the gateway when there's no signed-in user, such as from a scheduled automation.
|
|
56
|
+
*
|
|
57
|
+
* ## Billing and limits
|
|
58
|
+
*
|
|
59
|
+
* Requests are billed to your app's credit quota, which is the same shared
|
|
60
|
+
* quota your app's built-in AI features use, and isn't split per user. If the
|
|
61
|
+
* app runs out of credits, the gateway stops working for every user of the
|
|
62
|
+
* app until the quota resets. A request is rejected before the model runs if
|
|
63
|
+
* the app is out of credits. If you need to cap usage per user, build that
|
|
64
|
+
* check yourself, for example by tracking calls per user in your own entity.
|
|
32
65
|
*
|
|
33
|
-
*
|
|
34
|
-
* service-role token via `base44.asServiceRole.aiGateway`.
|
|
66
|
+
* Streaming responses aren't supported yet, so leave `stream` unset on your requests.
|
|
35
67
|
*/
|
|
36
68
|
export interface AiGatewayModule {
|
|
37
69
|
/**
|
|
@@ -39,19 +71,38 @@ export interface AiGatewayModule {
|
|
|
39
71
|
*
|
|
40
72
|
* Returns the `baseURL` and `token` to pass to any OpenAI-compatible client.
|
|
41
73
|
*
|
|
42
|
-
* The `token` is the current caller's bearer token: the app user's token for
|
|
43
|
-
* `base44.aiGateway`, or the service-role token for `base44.asServiceRole.aiGateway`.
|
|
44
|
-
* When the caller is unauthenticated, `token` is an empty string.
|
|
45
|
-
*
|
|
46
74
|
* @returns The gateway {@linkcode AiGatewayConnection | connection} (`baseURL` and `token`).
|
|
47
75
|
*
|
|
48
76
|
* @example
|
|
49
77
|
* ```typescript
|
|
78
|
+
* // Call a model directly
|
|
79
|
+
* import { createClientFromRequest } from "@base44/sdk";
|
|
80
|
+
* import OpenAI from "openai";
|
|
81
|
+
*
|
|
82
|
+
* // Runs inside a backend function
|
|
83
|
+
* const base44 = createClientFromRequest(request);
|
|
84
|
+
* const { baseURL, token } = base44.aiGateway.connection();
|
|
85
|
+
* const openai = new OpenAI({ baseURL, apiKey: token });
|
|
86
|
+
*
|
|
87
|
+
* const response = await openai.chat.completions.create({
|
|
88
|
+
* model: "automatic",
|
|
89
|
+
* messages: [{ role: "user", content: "Summarize this week's top support tickets." }],
|
|
90
|
+
* });
|
|
91
|
+
*
|
|
92
|
+
* console.log(response.choices[0].message.content);
|
|
93
|
+
* ```
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```typescript
|
|
97
|
+
* // Use a tool-calling agent
|
|
98
|
+
* import { createClientFromRequest } from "@base44/sdk";
|
|
50
99
|
* import { ToolLoopAgent, tool, stepCountIs, hasToolCall } from "ai";
|
|
51
100
|
* import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
52
101
|
* import { z } from "zod";
|
|
53
102
|
*
|
|
54
|
-
*
|
|
103
|
+
* // Runs inside a backend function, reviewing a return request
|
|
104
|
+
* const base44 = createClientFromRequest(request);
|
|
105
|
+
* const returnRequest = await base44.entities.ReturnRequest.get(returnId);
|
|
55
106
|
* const { baseURL, token } = base44.aiGateway.connection();
|
|
56
107
|
* // Point any OpenAI-compatible client at `baseURL` with `apiKey: token`.
|
|
57
108
|
* const models = createOpenAICompatible({ name: "base44", baseURL, apiKey: token });
|
|
@@ -66,7 +117,7 @@ export interface AiGatewayModule {
|
|
|
66
117
|
* description: "This customer's past orders, optionally filtered by status",
|
|
67
118
|
* inputSchema: z.object({ status: z.string().optional() }),
|
|
68
119
|
* execute: ({ status }) => {
|
|
69
|
-
* const query = { customer_email:
|
|
120
|
+
* const query = { customer_email: returnRequest.customer_email };
|
|
70
121
|
* if (status) query.status = status;
|
|
71
122
|
* return base44.entities.Order.filter(query, "-created_date", 50);
|
|
72
123
|
* },
|
|
@@ -81,7 +132,7 @@ export interface AiGatewayModule {
|
|
|
81
132
|
* stopWhen: [stepCountIs(8), hasToolCall("submitVerdict")],
|
|
82
133
|
* });
|
|
83
134
|
*
|
|
84
|
-
* await agent.generate({ prompt: `Review this return request: ${JSON.stringify(
|
|
135
|
+
* await agent.generate({ prompt: `Review this return request: ${JSON.stringify(returnRequest)}` });
|
|
85
136
|
* ```
|
|
86
137
|
*/
|
|
87
138
|
connection(): AiGatewayConnection;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Response from SSO access token endpoint.
|
|
3
|
-
* @internal
|
|
4
3
|
*/
|
|
5
4
|
export interface SsoAccessTokenResponse {
|
|
6
5
|
access_token: string;
|
|
@@ -13,46 +12,53 @@ export interface SsoAccessTokenResponse {
|
|
|
13
12
|
* services.
|
|
14
13
|
*
|
|
15
14
|
* This module is only available to use with a client in service role authentication mode, which means it can only be used in backend environments.
|
|
16
|
-
*
|
|
17
|
-
* @internal
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```typescript
|
|
21
|
-
* // Access SSO module with service role
|
|
22
|
-
* const response = await base44.asServiceRole.sso.getAccessToken('user_123');
|
|
23
|
-
* console.log(response.data.access_token);
|
|
24
|
-
* ```
|
|
25
15
|
*/
|
|
26
16
|
export interface SsoModule {
|
|
27
17
|
/**
|
|
28
|
-
* Gets SSO access token for
|
|
18
|
+
* Gets an SSO access token for the user who made the current request.
|
|
29
19
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
20
|
+
* Use this token to authenticate the user with external systems or services.
|
|
21
|
+
* This only works for that same user. Create the client with
|
|
22
|
+
* {@link createClientFromRequest} so it acts on behalf of the request's user,
|
|
23
|
+
* then pass that user's ID as `userid`. If `userid` is any other user, the
|
|
24
|
+
* call fails. An expired token is refreshed automatically when a refresh token
|
|
25
|
+
* is available.
|
|
32
26
|
*
|
|
33
|
-
* @param userid - The user
|
|
27
|
+
* @param userid - The ID of the user who made the current request, such as the
|
|
28
|
+
* `id` returned by {@link AuthModule | base44.auth.me()}.
|
|
34
29
|
* @returns Promise resolving to the SSO access token response.
|
|
35
30
|
*
|
|
36
31
|
* @example
|
|
37
32
|
* ```typescript
|
|
38
|
-
* // Get SSO access token
|
|
39
|
-
*
|
|
40
|
-
*
|
|
33
|
+
* // Get the user's SSO access token to call an external system
|
|
34
|
+
* import { createClientFromRequest } from 'npm:@base44/sdk';
|
|
35
|
+
*
|
|
36
|
+
* Deno.serve(async (req) => {
|
|
37
|
+
* const base44 = createClientFromRequest(req);
|
|
38
|
+
* const user = await base44.auth.me();
|
|
39
|
+
* const { access_token } = await base44.asServiceRole.sso.getAccessToken(user.id);
|
|
40
|
+
*
|
|
41
|
+
* return Response.json({ access_token });
|
|
42
|
+
* });
|
|
41
43
|
* ```
|
|
42
44
|
*/
|
|
43
45
|
getAccessToken(userid: string): Promise<SsoAccessTokenResponse>;
|
|
44
46
|
/**
|
|
45
|
-
* Gets the stored SSO OIDC ID token for the current
|
|
47
|
+
* Gets the stored SSO OIDC ID token for the user who made the current request.
|
|
46
48
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
49
|
+
* This only works for that same user, not for arbitrary users. Create the
|
|
50
|
+
* client with {@link createClientFromRequest} so it acts on behalf of the
|
|
51
|
+
* request's user, then pass that user's ID as `userid`. If `userid` is any
|
|
52
|
+
* other user, the call fails. The stored token is returned as-is and is never
|
|
53
|
+
* refreshed, so the call fails if the token has already expired.
|
|
50
54
|
*
|
|
51
|
-
* @param userid - The
|
|
55
|
+
* @param userid - The ID of the user who made the current request, such as the
|
|
56
|
+
* `id` returned by {@link AuthModule | base44.auth.me()}.
|
|
52
57
|
* @returns Promise resolving to the raw ID-token string.
|
|
53
58
|
*
|
|
54
59
|
* @example
|
|
55
60
|
* ```typescript
|
|
61
|
+
* // Get the user's ID token to read identity claims such as email
|
|
56
62
|
* import { createClientFromRequest } from 'npm:@base44/sdk';
|
|
57
63
|
*
|
|
58
64
|
* Deno.serve(async (req) => {
|