@base44-preview/sdk 0.8.27-pr.171.bb56537 → 0.8.28-pr.173.186369a
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 +10 -6
- package/dist/client.types.d.ts +5 -1
- package/dist/index.d.ts +1 -1
- package/dist/modules/entities.d.ts +4 -1
- package/dist/modules/entities.js +231 -35
- package/dist/modules/entities.types.d.ts +36 -0
- package/dist/utils/socket-utils.js +3 -1
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -122,11 +122,19 @@ export function createClient(config) {
|
|
|
122
122
|
userAuthModule.setToken(accessToken);
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
|
+
const userAnalyticsModule = createAnalyticsModule({
|
|
126
|
+
axiosClient,
|
|
127
|
+
serverUrl,
|
|
128
|
+
appId,
|
|
129
|
+
userAuthModule,
|
|
130
|
+
});
|
|
125
131
|
const userModules = {
|
|
126
132
|
entities: createEntitiesModule({
|
|
127
133
|
axios: axiosClient,
|
|
128
134
|
appId,
|
|
129
135
|
getSocket,
|
|
136
|
+
subscriptionOptions: options === null || options === void 0 ? void 0 : options.entitySubscriptions,
|
|
137
|
+
trackSubscriptionEvent: userAnalyticsModule.track,
|
|
130
138
|
}),
|
|
131
139
|
integrations: createIntegrationsModule(axiosClient, appId),
|
|
132
140
|
connectors: createUserConnectorsModule(axiosClient, appId),
|
|
@@ -152,12 +160,7 @@ export function createClient(config) {
|
|
|
152
160
|
}),
|
|
153
161
|
appLogs: createAppLogsModule(axiosClient, appId),
|
|
154
162
|
users: createUsersModule(axiosClient, appId),
|
|
155
|
-
analytics:
|
|
156
|
-
axiosClient,
|
|
157
|
-
serverUrl,
|
|
158
|
-
appId,
|
|
159
|
-
userAuthModule,
|
|
160
|
-
}),
|
|
163
|
+
analytics: userAnalyticsModule,
|
|
161
164
|
cleanup: () => {
|
|
162
165
|
userModules.analytics.cleanup();
|
|
163
166
|
if (socket) {
|
|
@@ -170,6 +173,7 @@ export function createClient(config) {
|
|
|
170
173
|
axios: serviceRoleAxiosClient,
|
|
171
174
|
appId,
|
|
172
175
|
getSocket,
|
|
176
|
+
subscriptionOptions: options === null || options === void 0 ? void 0 : options.entitySubscriptions,
|
|
173
177
|
}),
|
|
174
178
|
integrations: createIntegrationsModule(serviceRoleAxiosClient, appId),
|
|
175
179
|
sso: createSsoModule(serviceRoleAxiosClient, appId),
|
package/dist/client.types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EntitiesModule } from "./modules/entities.types.js";
|
|
1
|
+
import type { EntitiesModule, EntitySubscriptionOptions } from "./modules/entities.types.js";
|
|
2
2
|
import type { IntegrationsModule } from "./modules/integrations.types.js";
|
|
3
3
|
import type { AuthModule } from "./modules/auth.types.js";
|
|
4
4
|
import type { SsoModule } from "./modules/sso.types.js";
|
|
@@ -15,6 +15,10 @@ export interface CreateClientOptions {
|
|
|
15
15
|
* Optional error handler that will be called whenever an API error occurs.
|
|
16
16
|
*/
|
|
17
17
|
onError?: (error: Error) => void;
|
|
18
|
+
/**
|
|
19
|
+
* Client-side controls for realtime entity subscriptions.
|
|
20
|
+
*/
|
|
21
|
+
entitySubscriptions?: EntitySubscriptionOptions;
|
|
18
22
|
}
|
|
19
23
|
/**
|
|
20
24
|
* Configuration for creating a Base44 client.
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { getAccessToken, saveAccessToken, removeAccessToken, getLoginUrl } from
|
|
|
4
4
|
export { createClient, createClientFromRequest, Base44Error, getAccessToken, saveAccessToken, removeAccessToken, getLoginUrl, };
|
|
5
5
|
export type { Base44Client, CreateClientConfig, CreateClientOptions, Base44ErrorJSON, };
|
|
6
6
|
export * from "./types.js";
|
|
7
|
-
export type { DeleteManyResult, DeleteResult, EntitiesModule, EntityFilterOperators, EntityFilterQuery, EntityFilterValue, EntityHandler, EntityRecord, EntityTypeRegistry, ImportResult, RealtimeEventType, RealtimeEvent, RealtimeCallback, SortField, UpdateManyResult, } from "./modules/entities.types.js";
|
|
7
|
+
export type { DeleteManyResult, DeleteResult, EntitiesModule, EntityFilterOperators, EntityFilterQuery, EntityFilterValue, EntityHandler, EntityRecord, EntitySubscriptionOptions, EntityTypeRegistry, ImportResult, RealtimeEventType, RealtimeEvent, RealtimeCallback, SortField, UpdateManyResult, } from "./modules/entities.types.js";
|
|
8
8
|
export type { AuthModule, LoginResponse, RegisterParams, VerifyOtpParams, ChangePasswordParams, ResetPasswordParams, User, } from "./modules/auth.types.js";
|
|
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";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AxiosInstance } from "axios";
|
|
2
|
-
import { EntitiesModule } from "./entities.types";
|
|
2
|
+
import { EntitiesModule, EntitySubscriptionOptions } from "./entities.types";
|
|
3
|
+
import type { TrackEventParams } from "./analytics.types";
|
|
3
4
|
import { RoomsSocket } from "../utils/socket-utils.js";
|
|
4
5
|
/**
|
|
5
6
|
* Configuration for the entities module.
|
|
@@ -9,6 +10,8 @@ export interface EntitiesModuleConfig {
|
|
|
9
10
|
axios: AxiosInstance;
|
|
10
11
|
appId: string;
|
|
11
12
|
getSocket: () => ReturnType<typeof RoomsSocket>;
|
|
13
|
+
subscriptionOptions?: EntitySubscriptionOptions;
|
|
14
|
+
trackSubscriptionEvent?: (params: TrackEventParams) => void;
|
|
12
15
|
}
|
|
13
16
|
/**
|
|
14
17
|
* Creates the entities module for the Base44 SDK.
|
package/dist/modules/entities.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
const DEFAULT_MAX_ACTIVE_ENTITY_SUBSCRIPTIONS = 100;
|
|
2
|
+
const DEFAULT_SUBSCRIPTION_CHURN_WARNING_THRESHOLD = 20;
|
|
3
|
+
const DEFAULT_SUBSCRIPTION_CHURN_WINDOW_MS = 60000;
|
|
4
|
+
const DEFAULT_EMPTY_ROOM_GRACE_MS = 1000;
|
|
5
|
+
const ENTITY_SUBSCRIPTION_WARNING_EVENT_NAME = "__entity_subscription_warning__";
|
|
1
6
|
/**
|
|
2
7
|
* Creates the entities module for the Base44 SDK.
|
|
3
8
|
*
|
|
@@ -7,6 +12,13 @@
|
|
|
7
12
|
*/
|
|
8
13
|
export function createEntitiesModule(config) {
|
|
9
14
|
const { axios, appId, getSocket } = config;
|
|
15
|
+
const entityHandlers = new Map();
|
|
16
|
+
const subscriptionManager = createEntitySubscriptionManager({
|
|
17
|
+
appId,
|
|
18
|
+
getSocket,
|
|
19
|
+
options: config.subscriptionOptions,
|
|
20
|
+
trackSubscriptionEvent: config.trackSubscriptionEvent,
|
|
21
|
+
});
|
|
10
22
|
// Using Proxy to dynamically handle entity names
|
|
11
23
|
return new Proxy({}, {
|
|
12
24
|
get(target, entityName) {
|
|
@@ -16,8 +28,14 @@ export function createEntitiesModule(config) {
|
|
|
16
28
|
entityName.startsWith("_")) {
|
|
17
29
|
return undefined;
|
|
18
30
|
}
|
|
31
|
+
const cachedHandler = entityHandlers.get(entityName);
|
|
32
|
+
if (cachedHandler) {
|
|
33
|
+
return cachedHandler;
|
|
34
|
+
}
|
|
19
35
|
// Create entity handler
|
|
20
|
-
|
|
36
|
+
const handler = createEntityHandler(axios, appId, entityName, subscriptionManager);
|
|
37
|
+
entityHandlers.set(entityName, handler);
|
|
38
|
+
return handler;
|
|
21
39
|
},
|
|
22
40
|
});
|
|
23
41
|
}
|
|
@@ -41,17 +59,226 @@ function parseRealtimeMessage(dataStr) {
|
|
|
41
59
|
return null;
|
|
42
60
|
}
|
|
43
61
|
}
|
|
62
|
+
function normalizeEntitySubscriptionOptions(options) {
|
|
63
|
+
return {
|
|
64
|
+
maxActiveSubscriptions: normalizePositiveInteger(options === null || options === void 0 ? void 0 : options.maxActiveSubscriptions, DEFAULT_MAX_ACTIVE_ENTITY_SUBSCRIPTIONS),
|
|
65
|
+
churnWarningThreshold: normalizePositiveInteger(options === null || options === void 0 ? void 0 : options.churnWarningThreshold, DEFAULT_SUBSCRIPTION_CHURN_WARNING_THRESHOLD),
|
|
66
|
+
churnWindowMs: normalizePositiveInteger(options === null || options === void 0 ? void 0 : options.churnWindowMs, DEFAULT_SUBSCRIPTION_CHURN_WINDOW_MS),
|
|
67
|
+
emptyRoomGraceMs: normalizeNonNegativeInteger(options === null || options === void 0 ? void 0 : options.emptyRoomGraceMs, DEFAULT_EMPTY_ROOM_GRACE_MS),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function normalizePositiveInteger(value, fallback) {
|
|
71
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
72
|
+
return fallback;
|
|
73
|
+
}
|
|
74
|
+
return Math.floor(value);
|
|
75
|
+
}
|
|
76
|
+
function normalizeNonNegativeInteger(value, fallback) {
|
|
77
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
78
|
+
return fallback;
|
|
79
|
+
}
|
|
80
|
+
return Math.floor(value);
|
|
81
|
+
}
|
|
82
|
+
function createEntitySubscriptionManager({ appId, getSocket, options, trackSubscriptionEvent, }) {
|
|
83
|
+
const normalizedOptions = normalizeEntitySubscriptionOptions(options);
|
|
84
|
+
const activeSubscriptions = new Map();
|
|
85
|
+
const churnByRoom = new Map();
|
|
86
|
+
const roomsWarnedForCap = new Set();
|
|
87
|
+
let nextCallbackId = 1;
|
|
88
|
+
function makeRoom(entityName) {
|
|
89
|
+
return `entities:${appId}:${entityName}`;
|
|
90
|
+
}
|
|
91
|
+
function emitWarning(message, properties) {
|
|
92
|
+
console.warn(message);
|
|
93
|
+
try {
|
|
94
|
+
trackSubscriptionEvent === null || trackSubscriptionEvent === void 0 ? void 0 : trackSubscriptionEvent({
|
|
95
|
+
eventName: ENTITY_SUBSCRIPTION_WARNING_EVENT_NAME,
|
|
96
|
+
properties,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch (_a) {
|
|
100
|
+
// Diagnostics should never break application code.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function recordSubscriptionActivity(room, entityName, action) {
|
|
104
|
+
var _a;
|
|
105
|
+
const now = Date.now();
|
|
106
|
+
const churnState = (_a = churnByRoom.get(room)) !== null && _a !== void 0 ? _a : {
|
|
107
|
+
events: [],
|
|
108
|
+
lastWarningAt: null,
|
|
109
|
+
};
|
|
110
|
+
churnState.events = churnState.events.filter((event) => now - event.timestamp <= normalizedOptions.churnWindowMs);
|
|
111
|
+
churnState.events.push({ action, timestamp: now });
|
|
112
|
+
churnByRoom.set(room, churnState);
|
|
113
|
+
const subscribeCount = churnState.events.filter((event) => event.action === "subscribe").length;
|
|
114
|
+
const unsubscribeCount = churnState.events.length - subscribeCount;
|
|
115
|
+
if (churnState.events.length < normalizedOptions.churnWarningThreshold ||
|
|
116
|
+
subscribeCount === 0 ||
|
|
117
|
+
unsubscribeCount === 0 ||
|
|
118
|
+
(churnState.lastWarningAt !== null &&
|
|
119
|
+
now - churnState.lastWarningAt < normalizedOptions.churnWindowMs)) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
churnState.lastWarningAt = now;
|
|
123
|
+
emitWarning(`[Base44 SDK] entities.${entityName}.subscribe() is being created and cleaned up repeatedly ` +
|
|
124
|
+
`(${churnState.events.length} subscribe/unsubscribe operations in ` +
|
|
125
|
+
`${normalizedOptions.churnWindowMs}ms). Keep realtime subscriptions in a stable lifecycle to avoid socket churn.`, {
|
|
126
|
+
reason: "subscription_churn",
|
|
127
|
+
app_id: appId,
|
|
128
|
+
entity: entityName,
|
|
129
|
+
room,
|
|
130
|
+
action,
|
|
131
|
+
activity_count: churnState.events.length,
|
|
132
|
+
subscribe_count: subscribeCount,
|
|
133
|
+
unsubscribe_count: unsubscribeCount,
|
|
134
|
+
churn_window_ms: normalizedOptions.churnWindowMs,
|
|
135
|
+
empty_room_grace_ms: normalizedOptions.emptyRoomGraceMs,
|
|
136
|
+
churn_warning_threshold: normalizedOptions.churnWarningThreshold,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
function warnForSubscriptionCap(room, entityName) {
|
|
140
|
+
if (roomsWarnedForCap.has(room)) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
roomsWarnedForCap.add(room);
|
|
144
|
+
emitWarning(`[Base44 SDK] Realtime entity subscription cap reached ` +
|
|
145
|
+
`(${normalizedOptions.maxActiveSubscriptions} active entities per SDK client/tab). ` +
|
|
146
|
+
`Skipping entities.${entityName}.subscribe(). Unsubscribe from unused entity subscriptions before subscribing to more entities.`, {
|
|
147
|
+
reason: "active_subscription_cap",
|
|
148
|
+
app_id: appId,
|
|
149
|
+
entity: entityName,
|
|
150
|
+
room,
|
|
151
|
+
active_subscription_count: activeSubscriptions.size,
|
|
152
|
+
max_active_subscriptions: normalizedOptions.maxActiveSubscriptions,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function closeRoomSubscription(state) {
|
|
156
|
+
clearPendingClose(state);
|
|
157
|
+
state.unsubscribeFromRoom();
|
|
158
|
+
activeSubscriptions.delete(state.room);
|
|
159
|
+
if (activeSubscriptions.size < normalizedOptions.maxActiveSubscriptions) {
|
|
160
|
+
roomsWarnedForCap.clear();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function clearPendingClose(state) {
|
|
164
|
+
if (!state.closeTimer) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
clearTimeout(state.closeTimer);
|
|
168
|
+
state.closeTimer = null;
|
|
169
|
+
}
|
|
170
|
+
function scheduleRoomClose(state) {
|
|
171
|
+
var _a;
|
|
172
|
+
if (state.closeTimer) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (normalizedOptions.emptyRoomGraceMs === 0) {
|
|
176
|
+
closeRoomSubscription(state);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const closeTimer = setTimeout(() => {
|
|
180
|
+
state.closeTimer = null;
|
|
181
|
+
if (state.callbacks.size === 0 &&
|
|
182
|
+
activeSubscriptions.get(state.room) === state) {
|
|
183
|
+
closeRoomSubscription(state);
|
|
184
|
+
}
|
|
185
|
+
}, normalizedOptions.emptyRoomGraceMs);
|
|
186
|
+
(_a = closeTimer.unref) === null || _a === void 0 ? void 0 : _a.call(closeTimer);
|
|
187
|
+
state.closeTimer = closeTimer;
|
|
188
|
+
}
|
|
189
|
+
function dispatchRealtimeMessage(state, dataStr) {
|
|
190
|
+
var _a;
|
|
191
|
+
if (state.callbacks.size === 0) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const event = parseRealtimeMessage(dataStr);
|
|
195
|
+
if (!event) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
// Server signals oversize broadcasts with `_oversize: true` on
|
|
199
|
+
// `data`. The wire payload was slimmed to fit under the realtime
|
|
200
|
+
// transport cap, so big string fields arrive as empty strings (or
|
|
201
|
+
// the whole record collapses to a stub). Surface this to the
|
|
202
|
+
// developer console so they know to fetch the full record on
|
|
203
|
+
// demand (e.g. a follow-up entities.X.get(id) call) instead of
|
|
204
|
+
// rendering the slimmed payload directly. Skip on delete events
|
|
205
|
+
// — the record no longer exists.
|
|
206
|
+
if (event.type !== "delete" && ((_a = event.data) === null || _a === void 0 ? void 0 : _a._oversize)) {
|
|
207
|
+
console.error(`[Base44 SDK] Realtime broadcast for ${state.entityName}#${event.id} was oversize and got slimmed for transport. ` +
|
|
208
|
+
`Fields >10 KB are empty and the rest of the record may be a stub. ` +
|
|
209
|
+
`Call \`entities.${state.entityName}.get("${event.id}")\` to fetch the full record.`);
|
|
210
|
+
}
|
|
211
|
+
Array.from(state.callbacks.values()).forEach((callback) => {
|
|
212
|
+
try {
|
|
213
|
+
callback(event);
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
console.error("[Base44 SDK] Subscription callback error:", error);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
function openRoomSubscription(entityName, room) {
|
|
221
|
+
const state = {
|
|
222
|
+
room,
|
|
223
|
+
entityName,
|
|
224
|
+
callbacks: new Map(),
|
|
225
|
+
unsubscribeFromRoom: () => { },
|
|
226
|
+
closeTimer: null,
|
|
227
|
+
};
|
|
228
|
+
const socket = getSocket();
|
|
229
|
+
state.unsubscribeFromRoom = socket.subscribeToRoom(room, {
|
|
230
|
+
update_model: (msg) => {
|
|
231
|
+
dispatchRealtimeMessage(state, msg.data);
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
activeSubscriptions.set(room, state);
|
|
235
|
+
return state;
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
subscribe(entityName, callback) {
|
|
239
|
+
const room = makeRoom(entityName);
|
|
240
|
+
recordSubscriptionActivity(room, entityName, "subscribe");
|
|
241
|
+
let state = activeSubscriptions.get(room);
|
|
242
|
+
if (!state) {
|
|
243
|
+
if (activeSubscriptions.size >=
|
|
244
|
+
normalizedOptions.maxActiveSubscriptions) {
|
|
245
|
+
warnForSubscriptionCap(room, entityName);
|
|
246
|
+
return () => { };
|
|
247
|
+
}
|
|
248
|
+
state = openRoomSubscription(entityName, room);
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
clearPendingClose(state);
|
|
252
|
+
}
|
|
253
|
+
const callbackId = nextCallbackId++;
|
|
254
|
+
state.callbacks.set(callbackId, callback);
|
|
255
|
+
let unsubscribed = false;
|
|
256
|
+
return () => {
|
|
257
|
+
if (unsubscribed) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
unsubscribed = true;
|
|
261
|
+
recordSubscriptionActivity(room, entityName, "unsubscribe");
|
|
262
|
+
state.callbacks.delete(callbackId);
|
|
263
|
+
if (state.callbacks.size === 0 &&
|
|
264
|
+
activeSubscriptions.get(room) === state) {
|
|
265
|
+
scheduleRoomClose(state);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}
|
|
44
271
|
/**
|
|
45
272
|
* Creates a handler for a specific entity.
|
|
46
273
|
*
|
|
47
274
|
* @param axios - Axios instance
|
|
48
275
|
* @param appId - Application ID
|
|
49
276
|
* @param entityName - Entity name
|
|
50
|
-
* @param
|
|
277
|
+
* @param subscriptionManager - Shared realtime subscription manager
|
|
51
278
|
* @returns Entity handler with CRUD methods
|
|
52
279
|
* @internal
|
|
53
280
|
*/
|
|
54
|
-
function createEntityHandler(axios, appId, entityName,
|
|
281
|
+
function createEntityHandler(axios, appId, entityName, subscriptionManager) {
|
|
55
282
|
const baseURL = `/apps/${appId}/entities/${entityName}`;
|
|
56
283
|
return {
|
|
57
284
|
// List entities with optional pagination and sorting
|
|
@@ -126,38 +353,7 @@ function createEntityHandler(axios, appId, entityName, getSocket) {
|
|
|
126
353
|
},
|
|
127
354
|
// Subscribe to realtime updates
|
|
128
355
|
subscribe(callback) {
|
|
129
|
-
|
|
130
|
-
// Get the socket and subscribe to the room
|
|
131
|
-
const socket = getSocket();
|
|
132
|
-
const unsubscribe = socket.subscribeToRoom(room, {
|
|
133
|
-
update_model: (msg) => {
|
|
134
|
-
var _a;
|
|
135
|
-
const event = parseRealtimeMessage(msg.data);
|
|
136
|
-
if (!event) {
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
// Server signals oversize broadcasts with `_oversize: true` on
|
|
140
|
-
// `data`. The wire payload was slimmed to fit under the realtime
|
|
141
|
-
// transport cap, so big string fields arrive as empty strings (or
|
|
142
|
-
// the whole record collapses to a stub). Surface this to the
|
|
143
|
-
// developer console so they know to fetch the full record on
|
|
144
|
-
// demand (e.g. a follow-up entities.X.get(id) call) instead of
|
|
145
|
-
// rendering the slimmed payload directly. Skip on delete events
|
|
146
|
-
// — the record no longer exists.
|
|
147
|
-
if (event.type !== "delete" && ((_a = event.data) === null || _a === void 0 ? void 0 : _a._oversize)) {
|
|
148
|
-
console.error(`[Base44 SDK] Realtime broadcast for ${entityName}#${event.id} was oversize and got slimmed for transport. ` +
|
|
149
|
-
`Fields >10 KB are empty and the rest of the record may be a stub. ` +
|
|
150
|
-
`Call \`entities.${entityName}.get("${event.id}")\` to fetch the full record.`);
|
|
151
|
-
}
|
|
152
|
-
try {
|
|
153
|
-
callback(event);
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
console.error("[Base44 SDK] Subscription callback error:", error);
|
|
157
|
-
}
|
|
158
|
-
},
|
|
159
|
-
});
|
|
160
|
-
return unsubscribe;
|
|
356
|
+
return subscriptionManager.subscribe(entityName, callback);
|
|
161
357
|
},
|
|
162
358
|
};
|
|
163
359
|
}
|
|
@@ -23,6 +23,42 @@ export interface RealtimeEvent<T = any> {
|
|
|
23
23
|
* @typeParam T - The entity type for the event data. Defaults to `any`.
|
|
24
24
|
*/
|
|
25
25
|
export type RealtimeCallback<T = any> = (event: RealtimeEvent<T>) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Client-side controls for realtime entity subscriptions.
|
|
28
|
+
*/
|
|
29
|
+
export interface EntitySubscriptionOptions {
|
|
30
|
+
/**
|
|
31
|
+
* Maximum number of distinct active entity realtime subscriptions allowed
|
|
32
|
+
* for one SDK client instance. Repeated subscriptions to the same entity
|
|
33
|
+
* share one active entity subscription and do not count again.
|
|
34
|
+
*
|
|
35
|
+
* @defaultValue `100`
|
|
36
|
+
*/
|
|
37
|
+
maxActiveSubscriptions?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Number of subscribe/unsubscribe operations for the same entity within
|
|
40
|
+
* `churnWindowMs` before the SDK logs a warning and emits diagnostic
|
|
41
|
+
* telemetry.
|
|
42
|
+
*
|
|
43
|
+
* @defaultValue `20`
|
|
44
|
+
*/
|
|
45
|
+
churnWarningThreshold?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Time window, in milliseconds, used to detect repeated subscribe/unsubscribe
|
|
48
|
+
* churn for one entity.
|
|
49
|
+
*
|
|
50
|
+
* @defaultValue `60000`
|
|
51
|
+
*/
|
|
52
|
+
churnWindowMs?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Grace period, in milliseconds, before the SDK leaves an entity realtime
|
|
55
|
+
* room after its last local callback unsubscribes. A new subscription to
|
|
56
|
+
* the same entity during this window reuses the existing room membership.
|
|
57
|
+
*
|
|
58
|
+
* @defaultValue `1000`
|
|
59
|
+
*/
|
|
60
|
+
emptyRoomGraceMs?: number;
|
|
61
|
+
}
|
|
26
62
|
/**
|
|
27
63
|
* Result returned when deleting a single entity.
|
|
28
64
|
*/
|
|
@@ -88,7 +88,8 @@ export function RoomsSocket({ config }) {
|
|
|
88
88
|
return (_a = handlers.update_model) === null || _a === void 0 ? void 0 : _a.call(handlers, { room, data: dataStr });
|
|
89
89
|
}
|
|
90
90
|
function getListeners(room) {
|
|
91
|
-
|
|
91
|
+
var _a;
|
|
92
|
+
return (_a = roomsToListeners[room]) !== null && _a !== void 0 ? _a : [];
|
|
92
93
|
}
|
|
93
94
|
const subscribeToRoom = (room, handlers) => {
|
|
94
95
|
if (!roomsToListeners[room]) {
|
|
@@ -102,6 +103,7 @@ export function RoomsSocket({ config }) {
|
|
|
102
103
|
(_b = (_a = roomsToListeners[room]) === null || _a === void 0 ? void 0 : _a.filter((listener) => listener !== handlers)) !== null && _b !== void 0 ? _b : [];
|
|
103
104
|
if (roomsToListeners[room].length === 0) {
|
|
104
105
|
leaveRoom(room);
|
|
106
|
+
delete roomsToListeners[room];
|
|
105
107
|
}
|
|
106
108
|
};
|
|
107
109
|
};
|