@base44-preview/sdk 0.8.35-pr.212.2fe0031 → 0.8.35-pr.212.36b2685

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 CHANGED
@@ -7,6 +7,7 @@ import { createConnectorsModule, createUserConnectorsModule, } from "./modules/c
7
7
  import { getAccessToken } from "./utils/auth-utils.js";
8
8
  import { createFunctionsModule } from "./modules/functions.js";
9
9
  import { createAgentsModule } from "./modules/agents.js";
10
+ import { createAiGatewayModule } from "./modules/ai-gateway.js";
10
11
  import { createAppLogsModule } from "./modules/app-logs.js";
11
12
  import { createUsersModule } from "./modules/users.js";
12
13
  import { RoomsSocket } from "./utils/socket-utils.js";
@@ -161,6 +162,7 @@ export function createClient(config) {
161
162
  serverUrl,
162
163
  token,
163
164
  }),
165
+ aiGateway: createAiGatewayModule({ serverUrl, appBaseUrl: normalizedAppBaseUrl, token }),
164
166
  appLogs: createAppLogsModule(axiosClient, appId),
165
167
  users: createUsersModule(axiosClient, appId),
166
168
  analytics: createAnalyticsModule({
@@ -172,9 +174,15 @@ export function createClient(config) {
172
174
  realtime: createRealtimeModule({
173
175
  appId,
174
176
  dispatcherWsUrl: resolvedDispatcherWsUrl,
175
- getToken: async (handlerName, instanceId) => {
176
- // axiosClient interceptors unwrap response.data, so the result is the body directly
177
- const data = await axiosClient.post(`/apps/${appId}/realtime-token`, { handler_name: handlerName, instance_id: instanceId });
177
+ getToken: async (handlerName, instanceId, connId) => {
178
+ // axiosClient interceptors unwrap response.data, so the result is the body directly.
179
+ // conn_id rides inside the signed token (not a WS query param) so it survives
180
+ // proxies that strip params; the dispatcher forwards it as the handler's conn.id.
181
+ // Base44-Functions-Version rides along (like function calls) so live apps get
182
+ // tokens for the *published* realtime script and previews get the draft.
183
+ const data = await axiosClient.post(`/apps/${appId}/realtime-token`, { handler_name: handlerName, instance_id: instanceId, conn_id: connId }, functionsVersion
184
+ ? { headers: { "Base44-Functions-Version": functionsVersion } }
185
+ : undefined);
178
186
  return data.token;
179
187
  },
180
188
  }),
@@ -212,6 +220,7 @@ export function createClient(config) {
212
220
  serverUrl,
213
221
  token,
214
222
  }),
223
+ aiGateway: createAiGatewayModule({ serverUrl, appBaseUrl: normalizedAppBaseUrl, token: serviceToken }),
215
224
  appLogs: createAppLogsModule(serviceRoleAxiosClient, appId),
216
225
  cleanup: () => {
217
226
  if (socket) {
@@ -361,6 +370,7 @@ export function createClientFromRequest(request) {
361
370
  const serviceRoleAuthHeader = request.headers.get("Base44-Service-Authorization");
362
371
  const appId = request.headers.get("Base44-App-Id");
363
372
  const serverUrlHeader = request.headers.get("Base44-Api-Url");
373
+ const appBaseUrlHeader = request.headers.get("Base44-App-Base-Url");
364
374
  const functionsVersion = request.headers.get("Base44-Functions-Version");
365
375
  const stateHeader = request.headers.get("Base44-State");
366
376
  if (!appId) {
@@ -392,6 +402,7 @@ export function createClientFromRequest(request) {
392
402
  }
393
403
  return createClient({
394
404
  serverUrl: serverUrlHeader || "https://base44.app",
405
+ appBaseUrl: appBaseUrlHeader !== null && appBaseUrlHeader !== void 0 ? appBaseUrlHeader : undefined,
395
406
  appId,
396
407
  token: userToken,
397
408
  serviceToken: serviceRoleToken,
@@ -5,6 +5,7 @@ import type { SsoModule } from "./modules/sso.types.js";
5
5
  import type { ConnectorsModule, UserConnectorsModule } from "./modules/connectors.types.js";
6
6
  import type { FunctionsModule } from "./modules/functions.types.js";
7
7
  import type { AgentsModule } from "./modules/agents.types.js";
8
+ import type { AiGatewayModule } from "./modules/ai-gateway.types.js";
8
9
  import type { AppLogsModule } from "./modules/app-logs.types.js";
9
10
  import type { AnalyticsModule } from "./modules/analytics.types.js";
10
11
  import type { RealtimeModule } from "./modules/realtime.types.js";
@@ -89,6 +90,8 @@ export interface CreateClientConfig {
89
90
  export interface Base44Client {
90
91
  /** {@link AgentsModule | Agents module} for managing AI agent conversations. */
91
92
  agents: AgentsModule;
93
+ /** {@link AiGatewayModule | AI Gateway module} for connecting to the Base44 AI Gateway with your own SDK. */
94
+ aiGateway: AiGatewayModule;
92
95
  /** {@link AnalyticsModule | Analytics module} for tracking custom events in your app. */
93
96
  analytics: AnalyticsModule;
94
97
  /** {@link AppLogsModule | App logs module} for tracking app usage. */
@@ -134,6 +137,8 @@ export interface Base44Client {
134
137
  readonly asServiceRole: {
135
138
  /** {@link AgentsModule | Agents module} with elevated permissions. */
136
139
  agents: AgentsModule;
140
+ /** {@link AiGatewayModule | AI Gateway module} with the service-role token. */
141
+ aiGateway: AiGatewayModule;
137
142
  /** {@link AppLogsModule | App logs module} with elevated permissions. */
138
143
  appLogs: AppLogsModule;
139
144
  /** {@link ConnectorsModule | Connectors module} for OAuth token retrieval. */
package/dist/index.d.ts CHANGED
@@ -9,8 +9,9 @@ export type { AuthModule, LoginResponse, RegisterParams, VerifyOtpParams, Change
9
9
  export type { IntegrationsModule, IntegrationEndpointFunction, CoreIntegrations, InvokeLLMParams, GenerateImageParams, GenerateImageResult, UploadFileParams, UploadFileResult, SendEmailParams, SendEmailResult, ExtractDataFromUploadedFileParams, ExtractDataFromUploadedFileResult, UploadPrivateFileParams, UploadPrivateFileResult, CreateFileSignedUrlParams, CreateFileSignedUrlResult, } from "./modules/integrations.types.js";
10
10
  export type { FunctionsModule, FunctionName, FunctionNameRegistry, } from "./modules/functions.types.js";
11
11
  export type { AgentsModule, AgentName, AgentNameRegistry, AgentConversation, AgentMessage, AgentMessageReasoning, AgentMessageToolCall, AgentMessageUsage, AgentMessageCustomContext, AgentMessageMetadata, CreateConversationParams, } from "./modules/agents.types.js";
12
+ export type { AiGatewayModule, AiGatewayConnection, } from "./modules/ai-gateway.types.js";
12
13
  export type { AppLogsModule } from "./modules/app-logs.types.js";
13
- export type { RealtimeModule, RealtimeHandlerClient, RealtimeSubscription, } from "./modules/realtime.types.js";
14
+ export type { RealtimeModule, RealtimeHandlerClient, RealtimeHandlerNameRegistry, RealtimeHandlerRegistry, } from "./modules/realtime.types.js";
14
15
  export type { SsoModule, SsoAccessTokenResponse } from "./modules/sso.types.js";
15
16
  export { RealtimeHandler, type Conn } from "./realtime-handler.js";
16
17
  export type { ConnectorsModule, UserConnectorsModule, } from "./modules/connectors.types.js";
@@ -0,0 +1,2 @@
1
+ import { AiGatewayModule, AiGatewayModuleConfig } from "./ai-gateway.types.js";
2
+ export declare function createAiGatewayModule({ serverUrl, appBaseUrl, token, }: AiGatewayModuleConfig): AiGatewayModule;
@@ -0,0 +1,14 @@
1
+ import { getAccessToken } from "../utils/auth-utils.js";
2
+ export function createAiGatewayModule({ serverUrl, appBaseUrl, token, }) {
3
+ const gatewayOrigin = appBaseUrl || serverUrl;
4
+ const connection = () => {
5
+ var _a;
6
+ return ({
7
+ baseURL: `${gatewayOrigin}/api/ai/openai/v1`,
8
+ token: (_a = token !== null && token !== void 0 ? token : getAccessToken()) !== null && _a !== void 0 ? _a : "",
9
+ });
10
+ };
11
+ return {
12
+ connection,
13
+ };
14
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * A connection to the Base44 AI Gateway.
3
+ *
4
+ * Contains the base URL and bearer token to use with any OpenAI-compatible client
5
+ * (OpenAI SDK, Mastra, Vercel AI SDK, and others) pointed at the Base44 AI
6
+ * Gateway.
7
+ */
8
+ export interface AiGatewayConnection {
9
+ /** Base URL of the gateway's OpenAI-compatible endpoint. */
10
+ baseURL: string;
11
+ /** Bearer token used to authenticate requests to the gateway. */
12
+ token: string;
13
+ }
14
+ /**
15
+ * Configuration for the AI Gateway module.
16
+ * @internal
17
+ */
18
+ export interface AiGatewayModuleConfig {
19
+ /** Server URL */
20
+ serverUrl?: string;
21
+ /** The app's own public base URL (e.g. https://my-app.base44.app). */
22
+ appBaseUrl?: string;
23
+ /** Authentication token */
24
+ token?: string;
25
+ }
26
+ /**
27
+ * AI Gateway module for calling Base44's managed AI models from your own code.
28
+ *
29
+ * The gateway exposes an OpenAI-compatible Chat Completions endpoint, so any
30
+ * OpenAI-compatible SDK works against it:
31
+ * - Build custom AI agents with agent SDKs such as Mastra or the Vercel AI SDK
32
+ * - Uses your app's models, billing, and credit quota, no API key to manage
33
+ *
34
+ * Available in user authentication mode (`base44.aiGateway`) and with the
35
+ * service-role token via `base44.asServiceRole.aiGateway`.
36
+ */
37
+ export interface AiGatewayModule {
38
+ /**
39
+ * Gets the connection details for the Base44 AI Gateway.
40
+ *
41
+ * Returns the `baseURL` and `token` to pass to any OpenAI-compatible client.
42
+ *
43
+ * The `token` is the current caller's bearer token: the app user's token for
44
+ * `base44.aiGateway`, or the service-role token for `base44.asServiceRole.aiGateway`.
45
+ * When the caller is unauthenticated, `token` is an empty string.
46
+ *
47
+ * @returns The gateway {@linkcode AiGatewayConnection | connection} (`baseURL` and `token`).
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * // Build an AI agent with Mastra on top of the gateway, inside a backend function
52
+ * import { createClientFromRequest } from 'npm:@base44/sdk';
53
+ * import { Agent } from 'npm:@mastra/core/agent';
54
+ * import { createTool } from 'npm:@mastra/core/tools';
55
+ * import { createOpenAICompatible } from 'npm:@ai-sdk/openai-compatible';
56
+ * import { z } from 'npm:zod';
57
+ *
58
+ * Deno.serve(async (req) => {
59
+ * const base44 = createClientFromRequest(req);
60
+ * const { baseURL, token } = base44.aiGateway.connection();
61
+ * const models = createOpenAICompatible({ name: 'base44', baseURL, apiKey: token });
62
+ *
63
+ * const agent = new Agent({
64
+ * id: 'order-helper',
65
+ * name: 'order-helper',
66
+ * instructions: 'Help the user with their orders.',
67
+ * model: models('claude_sonnet_4_6'),
68
+ * tools: {
69
+ * lookupOrder: createTool({
70
+ * id: 'lookup-order',
71
+ * description: 'Fetch an order by id',
72
+ * inputSchema: z.object({ orderId: z.string() }),
73
+ * execute: async ({ orderId }) => base44.entities.Order.get(orderId),
74
+ * }),
75
+ * },
76
+ * });
77
+ *
78
+ * const { text } = await agent.generate('Where is order 123?');
79
+ * return Response.json({ text });
80
+ * });
81
+ * ```
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * // Call a model directly with the OpenAI SDK
86
+ * import { createClientFromRequest } from 'npm:@base44/sdk';
87
+ * import OpenAI from 'npm:openai';
88
+ *
89
+ * Deno.serve(async (req) => {
90
+ * const base44 = createClientFromRequest(req);
91
+ * const { baseURL, token } = base44.aiGateway.connection();
92
+ *
93
+ * const openai = new OpenAI({ baseURL, apiKey: token });
94
+ * const res = await openai.chat.completions.create({
95
+ * model: 'claude_sonnet_4_6',
96
+ * messages: [{ role: 'user', content: 'Hello!' }],
97
+ * });
98
+ * return Response.json({ text: res.choices[0].message.content });
99
+ * });
100
+ * ```
101
+ */
102
+ connection(): AiGatewayConnection;
103
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,13 +1,19 @@
1
1
  export declare function createRealtimeModule(config: {
2
2
  appId: string;
3
- getToken(handlerName: string, instanceId: string): Promise<string>;
3
+ getToken(handlerName: string, instanceId: string, connId: string): Promise<string>;
4
4
  dispatcherWsUrl: string;
5
5
  }): Record<string, RealtimeHandler>;
6
+ /** Handle for an active realtime subscription. */
7
+ interface RealtimeSubscription {
8
+ /** This connection's id — the same value the handler receives as `conn.id`. */
9
+ id: string;
10
+ /** Close the subscription and its underlying socket. */
11
+ unsubscribe(): void;
12
+ }
6
13
  interface RealtimeHandler {
7
- subscribe(instanceId: string, callback: (data: unknown) => void): Promise<{
8
- send(data: unknown): void;
9
- close(): void;
10
- }>;
14
+ subscribe(instanceId: string, callback: (data: unknown) => void, options?: {
15
+ id?: string;
16
+ }): RealtimeSubscription;
11
17
  send(instanceId: string, data: unknown): void;
12
18
  }
13
19
  export {};
@@ -8,44 +8,69 @@ export function createRealtimeModule(config) {
8
8
  return new Proxy({}, {
9
9
  get(_, handlerName) {
10
10
  return {
11
- async subscribe(instanceId, callback) {
12
- var _a;
11
+ subscribe(instanceId, callback, options) {
12
+ var _a, _b;
13
13
  const key = socketKey(handlerName, instanceId);
14
14
  // close existing if any
15
15
  (_a = activeSockets.get(key)) === null || _a === void 0 ? void 0 : _a.close();
16
- const token = await config.getToken(handlerName, instanceId);
16
+ // Connection id: caller-supplied (stable — reuse across reconnects/tabs as
17
+ // you see fit) or auto-generated per subscription. It travels INSIDE the
18
+ // signed realtime token (never as a WS query param, which proxies strip);
19
+ // the dispatcher forwards the verified claim as partyserver's _pk, so the
20
+ // handler sees this exact value as conn.id. Reconnects re-mint the token
21
+ // with the same id, so conn.id is stable across reconnects.
22
+ const connId = (_b = options === null || options === void 0 ? void 0 : options.id) !== null && _b !== void 0 ? _b : crypto.randomUUID();
23
+ // query as async fn: called on every (re)connect, fetches a fresh token each time
17
24
  const ws = new PartySocket({
18
25
  host: config.dispatcherWsUrl,
19
26
  party: handlerName,
20
27
  room: instanceId,
21
- query: { token },
28
+ query: () => config.getToken(handlerName, instanceId, connId).then((token) => ({ token })),
22
29
  });
23
30
  activeSockets.set(key, ws);
31
+ // Heartbeat / half-open detection. PartySocket only reconnects on a
32
+ // browser close/error event, so a silently-dead connection (TCP alive,
33
+ // no data — common behind proxies/LBs) hangs until the OS idle timeout
34
+ // (~60s). We ping periodically and force a reconnect if nothing comes
35
+ // back within DEAD_MS, cutting detection from ~60s to a few seconds.
36
+ // Pairs with the handler's setWebSocketAutoResponse("__ping"→"__pong"),
37
+ // so idle handlers (no app broadcasts) still keep the connection proven.
38
+ const PING_MS = 1000;
39
+ const DEAD_MS = 3000;
40
+ let lastMsg = Date.now();
41
+ const bumpAlive = () => { lastMsg = Date.now(); };
42
+ ws.addEventListener("open", bumpAlive);
24
43
  ws.addEventListener("message", (ev) => {
44
+ bumpAlive();
45
+ let data;
25
46
  try {
26
- callback(JSON.parse(ev.data));
47
+ data = JSON.parse(ev.data);
27
48
  }
28
49
  catch (_a) {
29
- // ignore malformed
50
+ return; // ignore malformed
30
51
  }
52
+ // Swallow heartbeat acks — never surface them to the app.
53
+ if (data && typeof data === "object" && data.type === "__pong")
54
+ return;
55
+ callback(data);
31
56
  });
32
- // Re-fetch token on reconnect
33
- ws.addEventListener("close", async () => {
34
- if (activeSockets.get(key) !== ws)
35
- return; // replaced
57
+ const heartbeat = setInterval(() => {
58
+ if (Date.now() - lastMsg > DEAD_MS) {
59
+ bumpAlive(); // avoid a reconnect storm while the new socket comes up
60
+ ws.reconnect();
61
+ return;
62
+ }
36
63
  try {
37
- const newToken = await config.getToken(handlerName, instanceId);
38
- ws.updateProperties({ query: { token: newToken } });
64
+ ws.send(JSON.stringify({ type: "__ping" }));
39
65
  }
40
66
  catch (_a) {
41
- // ignore token refresh failure
67
+ // socket not open; the watchdog above will force a reconnect
42
68
  }
43
- });
69
+ }, PING_MS);
44
70
  return {
45
- send(data) {
46
- ws.send(JSON.stringify(data));
47
- },
48
- close() {
71
+ id: connId, // the connection id (same value the handler sees as conn.id)
72
+ unsubscribe() {
73
+ clearInterval(heartbeat);
49
74
  activeSockets.delete(key);
50
75
  ws.close();
51
76
  },
@@ -1,32 +1,62 @@
1
1
  /**
2
- * A subscription handle returned by {@link RealtimeHandlerClient.subscribe}.
2
+ * Extend this interface to add typed `subscribe` callbacks and `send` payloads
3
+ * for your deployed RealtimeHandlers.
4
+ *
5
+ * This is separate from {@link RealtimeHandlerNameRegistry} (which is auto-generated
6
+ * by `base44 types generate`), so there are no conflicts.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * declare module "@base44/sdk" {
11
+ * interface RealtimeHandlerRegistry {
12
+ * ChatRoom: {
13
+ * toClient: { type: "joined" | "left" | "message"; userId?: string; from?: string; text?: string };
14
+ * toServer: { type: "message"; text: string };
15
+ * };
16
+ * }
17
+ * }
18
+ * ```
3
19
  */
4
- export interface RealtimeSubscription {
5
- /** Send a message to all subscribers of this instance. */
6
- send(data: unknown): void;
7
- /** Close the WebSocket connection and remove the subscription. */
8
- close(): void;
20
+ export interface RealtimeHandlerRegistry {
9
21
  }
22
+ /**
23
+ * Auto-populated by `base44 types generate` with the names of your deployed handlers.
24
+ * Do not edit this interface manually — use {@link RealtimeHandlerRegistry} for message types.
25
+ */
26
+ export interface RealtimeHandlerNameRegistry {
27
+ }
28
+ type AllHandlerNames = keyof RealtimeHandlerRegistry | keyof RealtimeHandlerNameRegistry;
29
+ type ToClientFor<N extends string> = N extends keyof RealtimeHandlerRegistry ? RealtimeHandlerRegistry[N] extends {
30
+ toClient: infer I;
31
+ } ? I : unknown : unknown;
32
+ type ToServerFor<N extends string> = N extends keyof RealtimeHandlerRegistry ? RealtimeHandlerRegistry[N] extends {
33
+ toServer: infer O;
34
+ } ? O : unknown : unknown;
10
35
  /**
11
36
  * Client for a single named RealtimeHandler.
37
+ * Typed automatically when the handler is registered in {@link RealtimeHandlerRegistry}.
12
38
  */
13
- export interface RealtimeHandlerClient {
14
- /**
15
- * Subscribe to messages from a specific RealtimeHandler instance.
16
- *
17
- * @param instanceId - The instance ID of the Durable Object.
18
- * @param callback - Called with each parsed message payload.
19
- * @returns A subscription handle with `send` and `close` methods.
20
- */
21
- subscribe(instanceId: string, callback: (data: unknown) => void): Promise<RealtimeSubscription>;
39
+ export interface RealtimeHandlerClient<N extends string = string> {
22
40
  /**
23
- * Send a message to an existing active subscription.
41
+ * Open a WebSocket subscription. Returns a {@link RealtimeSubscription} with the
42
+ * connection `id` (same value the handler sees as `conn.id`) and an `unsubscribe()` method.
24
43
  *
25
- * @param instanceId - The instance ID of the Durable Object.
26
- * @param data - The data to send (will be JSON-serialized).
27
- * @throws {Error} When no active subscription exists for this handler/instance pair.
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.
28
47
  */
29
- send(instanceId: string, data: unknown): void;
48
+ subscribe(instanceId: string, callback: (data: ToClientFor<N>) => void, options?: {
49
+ id?: string;
50
+ }): RealtimeSubscription;
51
+ /** Send a message over the open socket. Throws if not subscribed. */
52
+ send(instanceId: string, data: ToServerFor<N>): void;
53
+ }
54
+ /** Handle for an active realtime subscription. */
55
+ export interface RealtimeSubscription {
56
+ /** This connection's id — the same value the handler receives as `conn.id`. */
57
+ id: string;
58
+ /** Close the subscription and its underlying socket. */
59
+ unsubscribe(): void;
30
60
  }
31
61
  /**
32
62
  * The realtime module provides access to Cloudflare Durable Object-backed
@@ -35,10 +65,13 @@ export interface RealtimeHandlerClient {
35
65
  * Handler names are accessed as dynamic properties on this module:
36
66
  * ```typescript
37
67
  * const sub = await base44.realtime.MyHandler.subscribe("room-1", (msg) => {
38
- * console.log(msg);
68
+ * console.log(msg); // typed if MyHandler is in RealtimeHandlerRegistry
39
69
  * });
40
- * sub.send({ text: "hello" });
41
- * sub.close();
70
+ * const { id, unsubscribe } = sub;
71
+ * unsubscribe();
42
72
  * ```
43
73
  */
44
- export type RealtimeModule = Record<string, RealtimeHandlerClient>;
74
+ export type RealtimeModule = {
75
+ [K in AllHandlerNames]: K extends keyof RealtimeHandlerRegistry ? RealtimeHandlerClient<string & K> : RealtimeHandlerClient;
76
+ } & Record<string, RealtimeHandlerClient>;
77
+ export {};
@@ -1,4 +1,5 @@
1
1
  export * from "./app.types.js";
2
2
  export * from "./agents.types.js";
3
+ export * from "./ai-gateway.types.js";
3
4
  export * from "./connectors.types.js";
4
5
  export * from "./analytics.types.js";
@@ -1,4 +1,5 @@
1
1
  export * from "./app.types.js";
2
2
  export * from "./agents.types.js";
3
+ export * from "./ai-gateway.types.js";
3
4
  export * from "./connectors.types.js";
4
5
  export * from "./analytics.types.js";
@@ -8,20 +8,65 @@
8
8
  * At deploy time the bundler replaces this import with the compiled
9
9
  * Cloudflare Durable Object implementation — this file provides types only.
10
10
  */
11
- export interface Conn {
11
+ import type { Base44Client } from "./client.types.js";
12
+ /**
13
+ * A single client connection. `Send` is the message type this connection accepts
14
+ * via {@link send} — the handler's *outgoing* (server→client) messages.
15
+ */
16
+ export interface Conn<Send = unknown> {
17
+ /** Unique per-connection id (one per socket/tab), the same value the client
18
+ * receives from `subscribe()`. Use this — not userId — to identify a distinct
19
+ * client, so multiple tabs of the same user are separate connections. */
20
+ id: string;
12
21
  userId: string;
13
22
  appId: string;
14
23
  instanceId: string;
15
- send(data: unknown): void;
24
+ send(data: Send): void;
16
25
  reject(code: number, reason: string): void;
17
26
  }
18
- export declare abstract class RealtimeHandler<_State = unknown, Message = unknown> {
19
- abstract handleConnect(conn: Conn): void | Promise<void>;
20
- abstract handleMessage(conn: Conn, msg: Message): void | Promise<void>;
21
- abstract handleClose(conn: Conn): void | Promise<void>;
27
+ export interface Storage {
28
+ get<T>(key: string): Promise<T | undefined>;
29
+ put(key: string, value: unknown): Promise<void>;
30
+ delete(key: string): Promise<boolean>;
31
+ }
32
+ /**
33
+ * Base class for a Realtime Handler.
34
+ *
35
+ * @typeParam Incoming - messages this handler *receives* from clients
36
+ * (`handleMessage`'s `msg`) — the schema's `toServer` section.
37
+ * @typeParam Outgoing - messages this handler *sends* to clients
38
+ * (`conn.send`/`broadcast`) — the schema's `toClient` section.
39
+ *
40
+ * With a generated `schema.jsonc`, wire both from the registry so they can't drift
41
+ * from the client's types:
42
+ * ```ts
43
+ * type Reg = RealtimeHandlerRegistry["MyHandler"];
44
+ * class MyHandler extends RealtimeHandler<Reg["toServer"], Reg["toClient"]> { ... }
45
+ * ```
46
+ */
47
+ export declare abstract class RealtimeHandler<Incoming = unknown, Outgoing = unknown> {
48
+ abstract handleConnect(conn: Conn<Outgoing>): void | Promise<void>;
49
+ abstract handleMessage(conn: Conn<Outgoing>, msg: Incoming): void | Promise<void>;
50
+ abstract handleClose(conn: Conn<Outgoing>): void | Promise<void>;
22
51
  abstract handleTick(): void | Promise<void>;
23
- protected broadcast(_data: unknown): void;
24
- protected getConnections(): Conn[];
52
+ onStart(): void | Promise<void>;
53
+ /**
54
+ * Managed ticker (opt-in). Override {@link shouldTick} and the platform runs
55
+ * {@link handleTick} on a timer of {@link tickIntervalMs} while it returns true,
56
+ * and stops (letting the Durable Object hibernate — no compute cost) when it
57
+ * returns false. The platform owns scheduling, rescheduling, self-heal, and
58
+ * error-safety — you don't call {@link startLoop}/{@link stopLoop}.
59
+ *
60
+ * Re-evaluated after every connect/message/close and on every tick, so keep it
61
+ * cheap and pure (no async, no side effects). Example: `return this.players >= 2`.
62
+ */
63
+ protected tickIntervalMs: number;
64
+ protected shouldTick?(): boolean;
65
+ protected broadcast(_data: Outgoing): void;
66
+ protected getConnections(): Conn<Outgoing>[];
25
67
  protected startLoop(_ms: number): Promise<void>;
26
68
  protected stopLoop(): Promise<void>;
69
+ protected get instanceId(): string;
70
+ protected get storage(): Storage;
71
+ protected createServiceClient(): Base44Client;
27
72
  }
@@ -8,7 +8,36 @@
8
8
  * At deploy time the bundler replaces this import with the compiled
9
9
  * Cloudflare Durable Object implementation — this file provides types only.
10
10
  */
11
+ /**
12
+ * Base class for a Realtime Handler.
13
+ *
14
+ * @typeParam Incoming - messages this handler *receives* from clients
15
+ * (`handleMessage`'s `msg`) — the schema's `toServer` section.
16
+ * @typeParam Outgoing - messages this handler *sends* to clients
17
+ * (`conn.send`/`broadcast`) — the schema's `toClient` section.
18
+ *
19
+ * With a generated `schema.jsonc`, wire both from the registry so they can't drift
20
+ * from the client's types:
21
+ * ```ts
22
+ * type Reg = RealtimeHandlerRegistry["MyHandler"];
23
+ * class MyHandler extends RealtimeHandler<Reg["toServer"], Reg["toClient"]> { ... }
24
+ * ```
25
+ */
11
26
  export class RealtimeHandler {
27
+ constructor() {
28
+ /**
29
+ * Managed ticker (opt-in). Override {@link shouldTick} and the platform runs
30
+ * {@link handleTick} on a timer of {@link tickIntervalMs} while it returns true,
31
+ * and stops (letting the Durable Object hibernate — no compute cost) when it
32
+ * returns false. The platform owns scheduling, rescheduling, self-heal, and
33
+ * error-safety — you don't call {@link startLoop}/{@link stopLoop}.
34
+ *
35
+ * Re-evaluated after every connect/message/close and on every tick, so keep it
36
+ * cheap and pure (no async, no side effects). Example: `return this.players >= 2`.
37
+ */
38
+ this.tickIntervalMs = 100;
39
+ }
40
+ onStart() { }
12
41
  broadcast(_data) {
13
42
  throw new Error("RealtimeHandler.broadcast() is only available inside a deployed handler");
14
43
  }
@@ -21,4 +50,13 @@ export class RealtimeHandler {
21
50
  stopLoop() {
22
51
  throw new Error("RealtimeHandler.stopLoop() is only available inside a deployed handler");
23
52
  }
53
+ get instanceId() {
54
+ throw new Error("RealtimeHandler.instanceId is only available inside a deployed handler");
55
+ }
56
+ get storage() {
57
+ throw new Error("RealtimeHandler.storage is only available inside a deployed handler");
58
+ }
59
+ createServiceClient() {
60
+ throw new Error("RealtimeHandler.createServiceClient() is only available inside a deployed handler");
61
+ }
24
62
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base44-preview/sdk",
3
- "version": "0.8.35-pr.212.2fe0031",
3
+ "version": "0.8.35-pr.212.36b2685",
4
4
  "description": "JavaScript SDK for Base44 API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",