Skip to content
12 changes: 10 additions & 2 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,11 @@ export function createClient(config: CreateClientConfig): Base44Client {
);

const userModules = {
entities: createEntitiesModule(axiosClient, appId),
entities: createEntitiesModule({
axios: axiosClient,
appId,
getSocket,
}),
integrations: createIntegrationsModule(axiosClient, appId),
auth: userAuthModule,
functions: createFunctionsModule(functionsAxiosClient, appId),
Expand Down Expand Up @@ -167,7 +171,11 @@ export function createClient(config: CreateClientConfig): Base44Client {
};

const serviceRoleModules = {
entities: createEntitiesModule(serviceRoleAxiosClient, appId),
entities: createEntitiesModule({
axios: serviceRoleAxiosClient,
appId,
getSocket,
}),
integrations: createIntegrationsModule(serviceRoleAxiosClient, appId),
sso: createSsoModule(serviceRoleAxiosClient, appId, token),
connectors: createConnectorsModule(serviceRoleAxiosClient, appId),
Expand Down
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export * from "./types.js";
export type {
EntitiesModule,
EntityHandler,
RealtimeEventType,
RealtimeEvent,
RealtimeCallback,
Subscription,
} from "./modules/entities.types.js";

export type {
Expand Down
76 changes: 69 additions & 7 deletions src/modules/entities.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
import { AxiosInstance } from "axios";
import { EntitiesModule, EntityHandler } from "./entities.types";
import {
EntitiesModule,
EntityHandler,
RealtimeCallback,
RealtimeEvent,
RealtimeEventType,
Subscription,
} from "./entities.types";
import { RoomsSocket } from "../utils/socket-utils.js";

/**
* Configuration for the entities module.
* @internal
*/
export interface EntitiesModuleConfig {
axios: AxiosInstance;
appId: string;
getSocket: () => ReturnType<typeof RoomsSocket>;
}

/**
* Creates the entities module for the Base44 SDK.
*
* @param axios - Axios instance
* @param appId - Application ID
* @param config - Configuration object containing axios, appId, and getSocket
* @returns Entities module with dynamic entity access
* @internal
*/
export function createEntitiesModule(
axios: AxiosInstance,
appId: string
config: EntitiesModuleConfig
): EntitiesModule {
const { axios, appId, getSocket } = config;
// Using Proxy to dynamically handle entity names
return new Proxy(
{},
Expand All @@ -28,25 +45,46 @@ export function createEntitiesModule(
}

// Create entity handler
return createEntityHandler(axios, appId, entityName);
return createEntityHandler(axios, appId, entityName, getSocket);
},
}
) as EntitiesModule;
}

/**
* Parses the realtime message data and extracts event information.
* @internal
*/
function parseRealtimeMessage(dataStr: string): RealtimeEvent | null {
try {
const parsed = JSON.parse(dataStr);
return {
type: parsed.type as RealtimeEventType,
data: parsed.data,
id: parsed.id || parsed.data?.id,
timestamp: parsed.timestamp || new Date().toISOString(),
};
} catch (error) {
console.warn("[Base44 SDK] Failed to parse realtime message:", error);
return null;
}
}

/**
* Creates a handler for a specific entity.
*
* @param axios - Axios instance
* @param appId - Application ID
* @param entityName - Entity name
* @param getSocket - Function to get the socket instance
* @returns Entity handler with CRUD methods
* @internal
*/
function createEntityHandler(
axios: AxiosInstance,
appId: string,
entityName: string
entityName: string,
getSocket: () => ReturnType<typeof RoomsSocket>
): EntityHandler {
const baseURL = `/apps/${appId}/entities/${entityName}`;

Expand Down Expand Up @@ -125,5 +163,29 @@ function createEntityHandler(
},
});
},

// Subscribe to realtime updates
subscribe(callback: RealtimeCallback): Subscription {
const room = `entities:${appId}:${entityName}`;

// Get the socket and subscribe to the room
const socket = getSocket();
const unsubscribe = socket.subscribeToRoom(room, {
update_model: (msg) => {
const event = parseRealtimeMessage(msg.data);
if (!event) {
return;
}

try {
callback(event);
} catch (error) {
console.error("[Base44 SDK] Subscription callback error:", error);
}
},
});

return unsubscribe;
},
};
}
50 changes: 50 additions & 0 deletions src/modules/entities.types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,32 @@
/**
* Event types for realtime entity updates.
*/
export type RealtimeEventType = "create" | "update" | "delete";

/**
* Payload received when a realtime event occurs.
*/
export interface RealtimeEvent {
/** The type of change that occurred */
type: RealtimeEventType;
/** The entity data */
data: any;
/** The unique identifier of the affected entity */
id: string;
/** ISO 8601 timestamp of when the event occurred */
timestamp: string;
}

/**
* Callback function invoked when a realtime event occurs.
*/
export type RealtimeCallback = (event: RealtimeEvent) => void;

/**
* Function returned from subscribe, call it to unsubscribe.
*/
export type Subscription = () => void;

/**
* Entity handler providing CRUD operations for a specific entity type.
*
Expand Down Expand Up @@ -261,6 +290,27 @@ export interface EntityHandler {
* ```
*/
importEntities(file: File): Promise<any>;

/**
* Subscribes to realtime updates for all records of this entity type.
*
* Receives notifications whenever any record is created, updated, or deleted.
*
* @param callback - Function called when an entity changes.
* @returns Unsubscribe function to stop listening.
*
* @example
* ```typescript
* // Subscribe to all Task changes
* const unsubscribe = base44.entities.Task.subscribe((event) => {
* console.log(`Task ${event.id} was ${event.type}d:`, event.data);
* });
*
* // Later, unsubscribe
* unsubscribe();
* ```
*/
subscribe(callback: RealtimeCallback): Subscription;
}

/**
Expand Down
Loading