@base44-preview/sdk 0.8.48-pr.286.f67ed26 → 0.8.48-pr.287.fd215cb
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 +0 -9
- package/dist/index.d.ts +1 -1
- package/dist/modules/entities.js +64 -27
- package/dist/modules/entities.types.d.ts +291 -8
- package/package.json +5 -40
- package/dist/platform/client/client.d.ts +0 -35
- package/dist/platform/client/client.js +0 -218
- package/dist/platform/client/errors.d.ts +0 -11
- package/dist/platform/client/errors.js +0 -10
- package/dist/platform/client/events.d.ts +0 -159
- package/dist/platform/client/events.js +0 -1
- package/dist/platform/client/index.d.ts +0 -6
- package/dist/platform/client/index.js +0 -3
- package/dist/platform/client/protocol.d.ts +0 -11
- package/dist/platform/client/protocol.js +0 -39
- package/dist/platform/client/subscription.d.ts +0 -23
- package/dist/platform/client/subscription.js +0 -69
- package/dist/platform/client/types.d.ts +0 -33
- package/dist/platform/client/types.js +0 -1
package/README.md
CHANGED
|
@@ -151,12 +151,3 @@ 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 subscribes to public builder
|
|
158
|
-
updates through the white-label socket. It supports typed events, bounded delivery,
|
|
159
|
-
and reconnect replay using browser credentials supplied by your backend.
|
|
160
|
-
See [setup, public contract and recovery](platform-docs/client.md) and the
|
|
161
|
-
[TypeScript example](examples/platform-client.ts). Backend token integration and
|
|
162
|
-
workspace rollout are prerequisites; never use an API key in the browser.
|
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, CreateClientAnalyticsConfig, 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, EntityAggregateResult, EntityAggregateSpec, EntityDateBucketUnit, EntityDistinctOptions, EntityFilterOperators, EntityFilterQuery, EntityFilterValue, EntityHandler, EntityListOptions, EntityPage, EntityRecord, EntityTypeRegistry, EntityUpsertOptions, EntityUpsertResult, 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";
|
package/dist/modules/entities.js
CHANGED
|
@@ -41,6 +41,10 @@ function parseRealtimeMessage(dataStr) {
|
|
|
41
41
|
return null;
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
+
const DEFAULT_PAGE_LIMIT = 100;
|
|
45
|
+
function isPageOptions(value) {
|
|
46
|
+
return typeof value === "object" && value !== null;
|
|
47
|
+
}
|
|
44
48
|
/**
|
|
45
49
|
* Creates a handler for a specific entity.
|
|
46
50
|
*
|
|
@@ -53,34 +57,51 @@ function parseRealtimeMessage(dataStr) {
|
|
|
53
57
|
*/
|
|
54
58
|
function createEntityHandler(axios, appId, entityName, getSocket) {
|
|
55
59
|
const baseURL = `/apps/${appId}/entities/${entityName}`;
|
|
60
|
+
const fieldsParam = (fields) => Array.isArray(fields) ? fields.join(",") : fields;
|
|
61
|
+
// GET /{entity}: the array form shared by list() and filter()
|
|
62
|
+
const readArray = (sort, limit, skip, fields, query) => {
|
|
63
|
+
const params = {};
|
|
64
|
+
if (query)
|
|
65
|
+
params.q = JSON.stringify(query);
|
|
66
|
+
if (sort)
|
|
67
|
+
params.sort = sort;
|
|
68
|
+
if (limit)
|
|
69
|
+
params.limit = limit;
|
|
70
|
+
if (skip)
|
|
71
|
+
params.skip = skip;
|
|
72
|
+
if (fields)
|
|
73
|
+
params.fields = fieldsParam(fields);
|
|
74
|
+
return axios.get(baseURL, { params });
|
|
75
|
+
};
|
|
76
|
+
// GET /{entity}/v2/list: one cursor page of records or distinct values, shared by list(options) and filter(query, options)
|
|
77
|
+
const readPage = (options, query) => {
|
|
78
|
+
const params = {};
|
|
79
|
+
if (query)
|
|
80
|
+
params.q = JSON.stringify(query);
|
|
81
|
+
params.limit = options.limit || DEFAULT_PAGE_LIMIT;
|
|
82
|
+
if (options.cursor)
|
|
83
|
+
params.cursor = options.cursor;
|
|
84
|
+
if ("distinct" in options) {
|
|
85
|
+
params.distinct = options.distinct;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
if (options.sort)
|
|
89
|
+
params.sort = options.sort;
|
|
90
|
+
if (options.fields)
|
|
91
|
+
params.fields = fieldsParam(options.fields);
|
|
92
|
+
}
|
|
93
|
+
return axios.get(`${baseURL}/v2/list`, { params });
|
|
94
|
+
};
|
|
56
95
|
return {
|
|
57
|
-
//
|
|
58
|
-
async list(
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (fields)
|
|
67
|
-
params.fields = Array.isArray(fields) ? fields.join(",") : fields;
|
|
68
|
-
return axios.get(baseURL, { params });
|
|
69
|
-
},
|
|
70
|
-
// Filter entities based on query
|
|
71
|
-
async filter(query, sort, limit, skip, fields) {
|
|
72
|
-
const params = {
|
|
73
|
-
q: JSON.stringify(query),
|
|
74
|
-
};
|
|
75
|
-
if (sort)
|
|
76
|
-
params.sort = sort;
|
|
77
|
-
if (limit)
|
|
78
|
-
params.limit = limit;
|
|
79
|
-
if (skip)
|
|
80
|
-
params.skip = skip;
|
|
81
|
-
if (fields)
|
|
82
|
-
params.fields = Array.isArray(fields) ? fields.join(",") : fields;
|
|
83
|
-
return axios.get(baseURL, { params });
|
|
96
|
+
// list(sort, limit, skip, fields) returns an array; list(options) returns one cursor page.
|
|
97
|
+
async list(...args) {
|
|
98
|
+
const [sort, limit, skip, fields] = args;
|
|
99
|
+
return isPageOptions(sort) ? readPage(sort) : readArray(sort, limit, skip, fields);
|
|
100
|
+
},
|
|
101
|
+
// filter(query, sort, limit, skip, fields) returns an array; filter(query, options) returns one cursor page.
|
|
102
|
+
async filter(query, ...args) {
|
|
103
|
+
const [sort, limit, skip, fields] = args;
|
|
104
|
+
return isPageOptions(sort) ? readPage(sort, query) : readArray(sort, limit, skip, fields, query);
|
|
84
105
|
},
|
|
85
106
|
// Get entity by ID
|
|
86
107
|
async get(id) {
|
|
@@ -110,6 +131,22 @@ function createEntityHandler(axios, appId, entityName, getSocket) {
|
|
|
110
131
|
async updateMany(query, data) {
|
|
111
132
|
return axios.patch(`${baseURL}/update-many`, { query, data });
|
|
112
133
|
},
|
|
134
|
+
// Count entities matching a query
|
|
135
|
+
async count(query) {
|
|
136
|
+
const params = {};
|
|
137
|
+
if (query)
|
|
138
|
+
params.q = JSON.stringify(query);
|
|
139
|
+
const result = await axios.get(`${baseURL}/count`, { params });
|
|
140
|
+
return result.count;
|
|
141
|
+
},
|
|
142
|
+
// Server-side group-by aggregation
|
|
143
|
+
async aggregate(spec) {
|
|
144
|
+
return axios.post(`${baseURL}/aggregate`, spec);
|
|
145
|
+
},
|
|
146
|
+
// Create or update by a natural key
|
|
147
|
+
async upsert(records, options) {
|
|
148
|
+
return axios.post(`${baseURL}/upsert`, { records, key: options.key });
|
|
149
|
+
},
|
|
113
150
|
// Update multiple entities by ID, each with its own update data
|
|
114
151
|
async bulkUpdate(data) {
|
|
115
152
|
return axios.put(`${baseURL}/bulk`, data);
|
|
@@ -50,6 +50,130 @@ export interface UpdateManyResult {
|
|
|
50
50
|
/** Whether there are more entities matching the query that were not updated in this batch. When `true`, call `updateMany` again with the same query to update the next batch. */
|
|
51
51
|
has_more: boolean;
|
|
52
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Options object accepted by {@linkcode EntityHandler.list | list()} and
|
|
55
|
+
* {@linkcode EntityHandler.filter | filter()} to read one cursor page.
|
|
56
|
+
*
|
|
57
|
+
* @typeParam T - Entity record type.
|
|
58
|
+
* @typeParam K - The fields to include in each record.
|
|
59
|
+
*/
|
|
60
|
+
export interface EntityListOptions<T, K extends keyof T = keyof T> {
|
|
61
|
+
/** Sort parameter, such as `'-created_date'` for descending. Defaults to `'-created_date'`. */
|
|
62
|
+
sort?: SortField<T>;
|
|
63
|
+
/** Maximum number of records per page, up to 5,000. Defaults to 100. */
|
|
64
|
+
limit?: number;
|
|
65
|
+
/**
|
|
66
|
+
* `next_cursor` from the previous page. Omit or pass `null` for the first page.
|
|
67
|
+
*
|
|
68
|
+
* The token carries the query, sort and fields of the walk, so a later page needs only
|
|
69
|
+
* `cursor` and `limit`. Passing a different query, sort or fields with a cursor is an error.
|
|
70
|
+
*/
|
|
71
|
+
cursor?: string | null;
|
|
72
|
+
/** Array of field names to include in each record. Defaults to all fields. */
|
|
73
|
+
fields?: K[];
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Options object accepted by {@linkcode EntityHandler.list | list()} and
|
|
77
|
+
* {@linkcode EntityHandler.filter | filter()} to read the distinct values of one field
|
|
78
|
+
* instead of records.
|
|
79
|
+
*
|
|
80
|
+
* @typeParam T - Entity record type.
|
|
81
|
+
* @typeParam K - The field whose distinct values to read.
|
|
82
|
+
*/
|
|
83
|
+
export interface EntityDistinctOptions<T, K extends keyof T = keyof T> {
|
|
84
|
+
/** Field whose distinct values to return, in ascending order. Array fields contribute each element. */
|
|
85
|
+
distinct: K;
|
|
86
|
+
/** Maximum number of values per page, up to 1,000. Defaults to 100. */
|
|
87
|
+
limit?: number;
|
|
88
|
+
/** `next_cursor` from the previous page. Omit or pass `null` for the first page. The token carries the query and field. */
|
|
89
|
+
cursor?: string | null;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* One page of records, returned by {@linkcode EntityHandler.list | list()} and
|
|
93
|
+
* {@linkcode EntityHandler.filter | filter()} when called with an options object.
|
|
94
|
+
*
|
|
95
|
+
* @typeParam T - Record type of the items.
|
|
96
|
+
*/
|
|
97
|
+
export interface EntityPage<T> {
|
|
98
|
+
/** The page's records in the requested sort order, or the distinct values in ascending order. */
|
|
99
|
+
items: T[];
|
|
100
|
+
/** Pass as `cursor` to get the next page. `null` on the last page. */
|
|
101
|
+
next_cursor: string | null;
|
|
102
|
+
/** Whether records remain after this page. */
|
|
103
|
+
has_more: boolean;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Time unit for {@linkcode EntityAggregateSpec.dateBucket | dateBucket}.
|
|
107
|
+
*/
|
|
108
|
+
export type EntityDateBucketUnit = "day" | "week" | "month" | "year";
|
|
109
|
+
/**
|
|
110
|
+
* Describes what {@linkcode EntityHandler.aggregate | aggregate()} computes.
|
|
111
|
+
*
|
|
112
|
+
* Name the fields to group by and the measures to compute; the server does the work and
|
|
113
|
+
* returns one row per group. Field names are the entity's own field names.
|
|
114
|
+
*
|
|
115
|
+
* @typeParam T - Entity record type.
|
|
116
|
+
*/
|
|
117
|
+
export interface EntityAggregateSpec<T> {
|
|
118
|
+
/** Filter applied before grouping, in the same form {@linkcode EntityHandler.filter | filter()} accepts. Defaults to all records. */
|
|
119
|
+
query?: EntityFilterQuery<T>;
|
|
120
|
+
/** Field, or up to four fields, to group by. Omit to get one total row. */
|
|
121
|
+
groupBy?: (keyof T & string) | (keyof T & string)[];
|
|
122
|
+
/** Group by a time bucket of a date field. `created_date` and `updated_date` support every unit; date fields of your schema support `day`, `month` and `year`. */
|
|
123
|
+
dateBucket?: {
|
|
124
|
+
field: keyof T & string;
|
|
125
|
+
unit: EntityDateBucketUnit;
|
|
126
|
+
};
|
|
127
|
+
/** Whether to include the number of records per group as `count`. Defaults to `true`. */
|
|
128
|
+
count?: boolean;
|
|
129
|
+
/** Field, or fields, to sum. Each appears in the rows as `sum_<field>`. */
|
|
130
|
+
sum?: (keyof T & string) | (keyof T & string)[];
|
|
131
|
+
/** Field, or fields, to average. Each appears in the rows as `avg_<field>`. */
|
|
132
|
+
avg?: (keyof T & string) | (keyof T & string)[];
|
|
133
|
+
/** Field, or fields, to take the minimum of. Each appears in the rows as `min_<field>`. */
|
|
134
|
+
min?: (keyof T & string) | (keyof T & string)[];
|
|
135
|
+
/** Field, or fields, to take the maximum of. Each appears in the rows as `max_<field>`. */
|
|
136
|
+
max?: (keyof T & string) | (keyof T & string)[];
|
|
137
|
+
/** Field whose distinct values to count per group, returned as `count_distinct_<field>`. */
|
|
138
|
+
countDistinct?: keyof T & string;
|
|
139
|
+
/** Filter on the computed fields, applied after grouping. For example `{ count: { $gt: 1 } }` keeps only duplicated groups. */
|
|
140
|
+
having?: Record<string, any>;
|
|
141
|
+
/** Computed or group field to sort the rows by, with a `-` prefix for descending. For example `'-count'`. */
|
|
142
|
+
sort?: string;
|
|
143
|
+
/** Maximum number of rows, up to 1,000. Defaults to 1,000. */
|
|
144
|
+
limit?: number;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Rows returned by {@linkcode EntityHandler.aggregate | aggregate()}.
|
|
148
|
+
*/
|
|
149
|
+
export interface EntityAggregateResult {
|
|
150
|
+
/** One row per group: the group fields by name, then `count`, `sum_<field>`, `avg_<field>`, `min_<field>`, `max_<field>` or `count_distinct_<field>`. */
|
|
151
|
+
rows: Record<string, any>[];
|
|
152
|
+
/** `true` when more groups exist than `limit` allowed. */
|
|
153
|
+
truncated: boolean;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Options for {@linkcode EntityHandler.upsert | upsert()}.
|
|
157
|
+
*
|
|
158
|
+
* @typeParam T - Entity record type.
|
|
159
|
+
*/
|
|
160
|
+
export interface EntityUpsertOptions<T> {
|
|
161
|
+
/** Field, or fields, that identify a record. A record whose key values match an existing record updates it; any other record is created. */
|
|
162
|
+
key: (keyof T & string) | (keyof T & string)[];
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Result returned by {@linkcode EntityHandler.upsert | upsert()}.
|
|
166
|
+
*
|
|
167
|
+
* @typeParam T - Entity record type.
|
|
168
|
+
*/
|
|
169
|
+
export interface EntityUpsertResult<T = any> {
|
|
170
|
+
/** Number of records that were created. */
|
|
171
|
+
created: number;
|
|
172
|
+
/** Number of existing records that were updated. */
|
|
173
|
+
updated: number;
|
|
174
|
+
/** The written records, created and updated, as they now exist. */
|
|
175
|
+
records: T[];
|
|
176
|
+
}
|
|
53
177
|
/**
|
|
54
178
|
* Result returned when importing entities from a file.
|
|
55
179
|
*
|
|
@@ -211,14 +335,18 @@ export interface EntityHandler<T = any> {
|
|
|
211
335
|
* Retrieves all records of this type with support for sorting,
|
|
212
336
|
* pagination, and field selection.
|
|
213
337
|
*
|
|
214
|
-
* **Note:** The maximum limit is 5,000 items per request.
|
|
338
|
+
* **Note:** The maximum limit is 5,000 items per request. To read more than
|
|
339
|
+
* one page, pass an {@linkcode EntityListOptions | options object} with a
|
|
340
|
+
* `cursor` instead of `skip`: every page costs the same however deep you are,
|
|
341
|
+
* and records deleted between pages never shift the boundary. `skip` is kept
|
|
342
|
+
* for existing code and is deprecated for loops.
|
|
215
343
|
*
|
|
216
344
|
* @typeParam K - The fields to include in the response. Defaults to all fields.
|
|
217
345
|
* @param sort - Sort parameter, such as `'-created_date'` for descending. Defaults to `'-created_date'`.
|
|
218
|
-
* @param limit - Maximum number of results to return. Defaults to `
|
|
219
|
-
* @param skip - Number of results to skip for pagination. Defaults to `0`.
|
|
346
|
+
* @param limit - Maximum number of results to return. Defaults to `5000`.
|
|
347
|
+
* @param skip - Number of results to skip for pagination. Defaults to `0`. Deprecated for loops; use a cursor.
|
|
220
348
|
* @param fields - Array of field names to include in the response. Defaults to all fields.
|
|
221
|
-
* @returns Promise resolving to an array of records with selected fields.
|
|
349
|
+
* @returns Promise resolving to an array of records with selected fields. When called with an options object, resolves instead to an {@linkcode EntityPage | EntityPage} with `items`, `next_cursor` and `has_more`; with a `distinct` option the items are the field's values.
|
|
222
350
|
*
|
|
223
351
|
* @example
|
|
224
352
|
* ```typescript
|
|
@@ -244,15 +372,39 @@ export interface EntityHandler<T = any> {
|
|
|
244
372
|
* // Get only specific fields
|
|
245
373
|
* const fields = await base44.entities.MyEntity.list('-created_date', 10, 0, ['name', 'status']);
|
|
246
374
|
* ```
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* ```typescript
|
|
378
|
+
* // Walk every record with a cursor. Pass an options object instead of
|
|
379
|
+
* // positional arguments to get a page with `next_cursor` and `has_more`.
|
|
380
|
+
* let page = await base44.entities.MyEntity.list({ sort: '-created_date', limit: 1000 });
|
|
381
|
+
* await exportRows(page.items);
|
|
382
|
+
* while (page.has_more) {
|
|
383
|
+
* page = await base44.entities.MyEntity.list({ cursor: page.next_cursor, limit: 1000 });
|
|
384
|
+
* await exportRows(page.items);
|
|
385
|
+
* }
|
|
386
|
+
* ```
|
|
387
|
+
*
|
|
388
|
+
* @example
|
|
389
|
+
* ```typescript
|
|
390
|
+
* // Distinct values of one field, instead of records
|
|
391
|
+
* const { items: categories } = await base44.entities.Product.list({ distinct: 'category' });
|
|
392
|
+
* ```
|
|
247
393
|
*/
|
|
248
394
|
list<K extends keyof T = keyof T>(sort?: SortField<T>, limit?: number, skip?: number, fields?: K[]): Promise<Pick<T, K>[]>;
|
|
395
|
+
list<K extends keyof T>(options: EntityDistinctOptions<T, K>): Promise<EntityPage<T[K]>>;
|
|
396
|
+
list<K extends keyof T = keyof T>(options: EntityListOptions<T, K>): Promise<EntityPage<Pick<T, K>>>;
|
|
249
397
|
/**
|
|
250
398
|
* Filters records based on a query.
|
|
251
399
|
*
|
|
252
400
|
* Retrieves records that match specific criteria with support for
|
|
253
401
|
* sorting, pagination, and field selection.
|
|
254
402
|
*
|
|
255
|
-
* **Note:** The maximum limit is 5,000 items per request.
|
|
403
|
+
* **Note:** The maximum limit is 5,000 items per request. To read more than
|
|
404
|
+
* one page, pass an {@linkcode EntityListOptions | options object} with a
|
|
405
|
+
* `cursor` instead of `skip`: every page costs the same however deep you are,
|
|
406
|
+
* and records deleted between pages never shift the boundary. `skip` is kept
|
|
407
|
+
* for existing code and is deprecated for loops.
|
|
256
408
|
*
|
|
257
409
|
* @typeParam K - The fields to include in the response. Defaults to all fields.
|
|
258
410
|
* @param query - Query object with field-value pairs. Each key should be a field name
|
|
@@ -261,10 +413,10 @@ export interface EntityHandler<T = any> {
|
|
|
261
413
|
* for exact matches, `null` for null values, arrays as shorthand for matching any of the
|
|
262
414
|
* provided values, or documented MongoDB query operators for advanced filtering.
|
|
263
415
|
* @param sort - Sort parameter, such as `'-created_date'` for descending. Defaults to `'-created_date'`.
|
|
264
|
-
* @param limit - Maximum number of results to return. Defaults to `
|
|
265
|
-
* @param skip - Number of results to skip for pagination. Defaults to `0`.
|
|
416
|
+
* @param limit - Maximum number of results to return. Defaults to `5000`.
|
|
417
|
+
* @param skip - Number of results to skip for pagination. Defaults to `0`. Deprecated for loops; use a cursor.
|
|
266
418
|
* @param fields - Array of field names to include in the response. Defaults to all fields.
|
|
267
|
-
* @returns Promise resolving to an array of filtered records with selected fields.
|
|
419
|
+
* @returns Promise resolving to an array of filtered records with selected fields. When called with an options object, resolves instead to an {@linkcode EntityPage | EntityPage} with `items`, `next_cursor` and `has_more`; with a `distinct` option the items are the field's values.
|
|
268
420
|
*
|
|
269
421
|
* @example
|
|
270
422
|
* ```typescript
|
|
@@ -341,8 +493,34 @@ export interface EntityHandler<T = any> {
|
|
|
341
493
|
* ['name', 'priority']
|
|
342
494
|
* );
|
|
343
495
|
* ```
|
|
496
|
+
*
|
|
497
|
+
* @example
|
|
498
|
+
* ```typescript
|
|
499
|
+
* // Walk all matching records with a cursor. Pass an options object as the
|
|
500
|
+
* // second argument to get a page with `next_cursor` and `has_more`.
|
|
501
|
+
* let page = await base44.entities.Order.filter(
|
|
502
|
+
* { status: 'open' },
|
|
503
|
+
* { sort: '-created_date', limit: 1000 }
|
|
504
|
+
* );
|
|
505
|
+
* await exportRows(page.items);
|
|
506
|
+
* while (page.has_more) {
|
|
507
|
+
* page = await base44.entities.Order.filter({ status: 'open' }, { cursor: page.next_cursor, limit: 1000 });
|
|
508
|
+
* await exportRows(page.items);
|
|
509
|
+
* }
|
|
510
|
+
* ```
|
|
511
|
+
*
|
|
512
|
+
* @example
|
|
513
|
+
* ```typescript
|
|
514
|
+
* // Distinct values of one field among the matching records
|
|
515
|
+
* const { items: agents } = await base44.entities.Order.filter(
|
|
516
|
+
* { status: 'open' },
|
|
517
|
+
* { distinct: 'agent_id' }
|
|
518
|
+
* );
|
|
519
|
+
* ```
|
|
344
520
|
*/
|
|
345
521
|
filter<K extends keyof T = keyof T>(query: EntityFilterQuery<T>, sort?: SortField<T>, limit?: number, skip?: number, fields?: K[]): Promise<Pick<T, K>[]>;
|
|
522
|
+
filter<K extends keyof T>(query: EntityFilterQuery<T>, options: EntityDistinctOptions<T, K>): Promise<EntityPage<T[K]>>;
|
|
523
|
+
filter<K extends keyof T = keyof T>(query: EntityFilterQuery<T>, options: EntityListOptions<T, K>): Promise<EntityPage<Pick<T, K>>>;
|
|
346
524
|
/**
|
|
347
525
|
* Gets a single record by ID.
|
|
348
526
|
*
|
|
@@ -549,6 +727,111 @@ export interface EntityHandler<T = any> {
|
|
|
549
727
|
* ```
|
|
550
728
|
*/
|
|
551
729
|
updateMany(query: Partial<T>, data: Record<string, Record<string, any>>): Promise<UpdateManyResult>;
|
|
730
|
+
/**
|
|
731
|
+
* Counts the records that match a query.
|
|
732
|
+
*
|
|
733
|
+
* Returns the number of records the current user can read, without fetching them.
|
|
734
|
+
* Use it for totals, badges and "page N of M" instead of listing records and
|
|
735
|
+
* measuring the array.
|
|
736
|
+
*
|
|
737
|
+
* @param query - Filter query, in the same form {@linkcode filter | filter()} accepts. Defaults to all records.
|
|
738
|
+
* @returns Promise resolving to the number of matching records.
|
|
739
|
+
*
|
|
740
|
+
* @example
|
|
741
|
+
* ```typescript
|
|
742
|
+
* // How many tasks are still open?
|
|
743
|
+
* const open = await base44.entities.Task.count({ status: 'open' });
|
|
744
|
+
* ```
|
|
745
|
+
*
|
|
746
|
+
* @example
|
|
747
|
+
* ```typescript
|
|
748
|
+
* // Total records in the entity
|
|
749
|
+
* const total = await base44.entities.Task.count();
|
|
750
|
+
* ```
|
|
751
|
+
*/
|
|
752
|
+
count(query?: EntityFilterQuery<T>): Promise<number>;
|
|
753
|
+
/**
|
|
754
|
+
* Computes counts, sums, averages, minimums, maximums or distinct counts, grouped by fields.
|
|
755
|
+
*
|
|
756
|
+
* Use it for dashboards, leaderboards and reports instead of loading every record
|
|
757
|
+
* and adding up in the browser. The server groups the records you can read and
|
|
758
|
+
* returns one row per group, up to 1,000 rows.
|
|
759
|
+
*
|
|
760
|
+
* @param spec - What to group by and what to compute. See {@linkcode EntityAggregateSpec | EntityAggregateSpec}.
|
|
761
|
+
* @returns Promise resolving to the rows and a `truncated` flag.
|
|
762
|
+
*
|
|
763
|
+
* @example
|
|
764
|
+
* ```typescript
|
|
765
|
+
* // Sales per agent this month, biggest first
|
|
766
|
+
* const { rows } = await base44.entities.Sale.aggregate({
|
|
767
|
+
* query: { sale_date: { $gte: '2026-09-01' } },
|
|
768
|
+
* groupBy: 'agent_id',
|
|
769
|
+
* sum: 'amount',
|
|
770
|
+
* sort: '-sum_amount'
|
|
771
|
+
* });
|
|
772
|
+
* // rows: [{ agent_id: 'a1', count: 42, sum_amount: 18250 }, ...]
|
|
773
|
+
* ```
|
|
774
|
+
*
|
|
775
|
+
* @example
|
|
776
|
+
* ```typescript
|
|
777
|
+
* // Records created per day
|
|
778
|
+
* const { rows } = await base44.entities.Visit.aggregate({
|
|
779
|
+
* dateBucket: { field: 'created_date', unit: 'day' }
|
|
780
|
+
* });
|
|
781
|
+
* ```
|
|
782
|
+
*
|
|
783
|
+
* @example
|
|
784
|
+
* ```typescript
|
|
785
|
+
* // Find duplicated external ids
|
|
786
|
+
* const { rows } = await base44.entities.Contact.aggregate({
|
|
787
|
+
* groupBy: 'external_id',
|
|
788
|
+
* having: { count: { $gt: 1 } }
|
|
789
|
+
* });
|
|
790
|
+
* ```
|
|
791
|
+
*
|
|
792
|
+
* @example
|
|
793
|
+
* ```typescript
|
|
794
|
+
* // Unique visitors per page
|
|
795
|
+
* const { rows } = await base44.entities.PageView.aggregate({
|
|
796
|
+
* groupBy: 'path',
|
|
797
|
+
* countDistinct: 'session_id'
|
|
798
|
+
* });
|
|
799
|
+
* ```
|
|
800
|
+
*/
|
|
801
|
+
aggregate(spec: EntityAggregateSpec<T>): Promise<EntityAggregateResult>;
|
|
802
|
+
/**
|
|
803
|
+
* Creates or updates records by a key of your own.
|
|
804
|
+
*
|
|
805
|
+
* Use this when you sync data from another system: name the field, or fields, that
|
|
806
|
+
* identify a record, and the server updates the records whose key already exists and
|
|
807
|
+
* creates the rest, in one call. You no longer need to list existing records to check
|
|
808
|
+
* for duplicates before writing.
|
|
809
|
+
*
|
|
810
|
+
* You can upsert up to 500 records per request. When two records in one call share a
|
|
811
|
+
* key, the last one wins. Updates merge the given fields into the existing record, like
|
|
812
|
+
* {@linkcode update | update()}.
|
|
813
|
+
*
|
|
814
|
+
* @param records - Array of record data objects. Each must carry a value for every key field.
|
|
815
|
+
* @param options - The key field or fields. See {@linkcode EntityUpsertOptions | EntityUpsertOptions}.
|
|
816
|
+
* @returns Promise resolving to the counts and the written records.
|
|
817
|
+
*
|
|
818
|
+
* @example
|
|
819
|
+
* ```typescript
|
|
820
|
+
* // Sync contacts from a CRM by their CRM id
|
|
821
|
+
* const result = await base44.entities.Contact.upsert(
|
|
822
|
+
* crmContacts.map(c => ({ crm_id: c.id, name: c.name, email: c.email })),
|
|
823
|
+
* { key: 'crm_id' }
|
|
824
|
+
* );
|
|
825
|
+
* console.log(`${result.created} new, ${result.updated} updated`);
|
|
826
|
+
* ```
|
|
827
|
+
*
|
|
828
|
+
* @example
|
|
829
|
+
* ```typescript
|
|
830
|
+
* // Compound key
|
|
831
|
+
* await base44.entities.Inventory.upsert(rows, { key: ['sku', 'warehouse'] });
|
|
832
|
+
* ```
|
|
833
|
+
*/
|
|
834
|
+
upsert(records: Partial<T>[], options: EntityUpsertOptions<T>): Promise<EntityUpsertResult<T>>;
|
|
552
835
|
/**
|
|
553
836
|
* Updates the specified records in a single request, each with its own data.
|
|
554
837
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@base44-preview/sdk",
|
|
3
|
-
"version": "0.8.48-pr.
|
|
3
|
+
"version": "0.8.48-pr.287.fd215cb",
|
|
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": "
|
|
13
|
-
"lint": "eslint src
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"lint": "eslint src",
|
|
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,11 +23,7 @@
|
|
|
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"
|
|
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"
|
|
26
|
+
"create-docs:process": "node scripts/mintlify-post-processing/file-processing/file-processing.js"
|
|
31
27
|
},
|
|
32
28
|
"dependencies": {
|
|
33
29
|
"axios": "^1.18.1",
|
|
@@ -67,36 +63,5 @@
|
|
|
67
63
|
"bugs": {
|
|
68
64
|
"url": "https://github.com/base44/javascript-sdk/issues"
|
|
69
65
|
},
|
|
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
|
-
}
|
|
66
|
+
"homepage": "https://github.com/base44/javascript-sdk#readme"
|
|
102
67
|
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type { PlatformClientOptions, PlatformSubscription, SubscriptionOptions } from "./types.js";
|
|
2
|
-
/** Browser client for read-only platform app events. Constructing it opens no connection. */
|
|
3
|
-
export declare class Base44PlatformClient {
|
|
4
|
-
private readonly socket;
|
|
5
|
-
private readonly subscriptions;
|
|
6
|
-
private readonly options;
|
|
7
|
-
private closed;
|
|
8
|
-
private needsFreshConnection;
|
|
9
|
-
private generation;
|
|
10
|
-
private authAttempt;
|
|
11
|
-
private cancelAuth?;
|
|
12
|
-
private connecting?;
|
|
13
|
-
private resolveConnect?;
|
|
14
|
-
private rejectConnect?;
|
|
15
|
-
/** Configure a dedicated connection. API keys belong exclusively on your backend. */
|
|
16
|
-
constructor(options: PlatformClientOptions);
|
|
17
|
-
/** Connect using a freshly obtained token. Resolves on CONNECT, not on app replay completion.
|
|
18
|
-
* Unexpected transport loss retries up to five times and rejoins active subscriptions.
|
|
19
|
-
* Call again after addressing a connection/auth failure; concurrent calls share one attempt.
|
|
20
|
-
*/
|
|
21
|
-
connect(): Promise<void>;
|
|
22
|
-
/** Subscribe before or after connecting. One subscription per app, maximum eight.
|
|
23
|
-
* Events and boundary callbacks are awaited in order per app. Failed application,
|
|
24
|
-
* invalid frames or server errors end the subscription without advancing its cursor.
|
|
25
|
-
*/
|
|
26
|
-
subscribe(appId: string, options: SubscriptionOptions): PlatformSubscription;
|
|
27
|
-
/** Stop delivery, cancel reconnection and release all listeners. Idempotent and terminal. */
|
|
28
|
-
close(): void;
|
|
29
|
-
private join;
|
|
30
|
-
private authenticate;
|
|
31
|
-
private clearConnecting;
|
|
32
|
-
private connectionError;
|
|
33
|
-
private serverError;
|
|
34
|
-
private protocolError;
|
|
35
|
-
}
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
import { io } from "socket.io-client";
|
|
2
|
-
import { PlatformSocketError } from "./errors.js";
|
|
3
|
-
import { appFromRoom, appPattern, decode, decodeJoined, errorCodes, eventApp, eventNames, object, roomFor } from "./protocol.js";
|
|
4
|
-
import { notify, Subscription } from "./subscription.js";
|
|
5
|
-
/** Browser client for read-only platform app events. Constructing it opens no connection. */
|
|
6
|
-
export class Base44PlatformClient {
|
|
7
|
-
/** Configure a dedicated connection. API keys belong exclusively on your backend. */
|
|
8
|
-
constructor(options) {
|
|
9
|
-
this.subscriptions = new Map();
|
|
10
|
-
this.closed = false;
|
|
11
|
-
this.needsFreshConnection = false;
|
|
12
|
-
this.generation = 0;
|
|
13
|
-
this.authAttempt = 0;
|
|
14
|
-
const url = new URL(options.serverUrl);
|
|
15
|
-
if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.search || url.hash || url.pathname !== "/") {
|
|
16
|
-
throw new TypeError("serverUrl must be an HTTP(S) origin without credentials, path, query or fragment");
|
|
17
|
-
}
|
|
18
|
-
this.options = { ...options };
|
|
19
|
-
this.socket = io(`${url.origin}/partner`, {
|
|
20
|
-
path: "/ws-whitelabel/socket.io/", transports: ["websocket"], autoConnect: false,
|
|
21
|
-
forceNew: true, reconnectionAttempts: 5, reconnectionDelay: 1000, reconnectionDelayMax: 10000,
|
|
22
|
-
timeout: 20000,
|
|
23
|
-
auth: (callback) => { void this.authenticate(callback); },
|
|
24
|
-
});
|
|
25
|
-
this.socket.on("connect", () => {
|
|
26
|
-
var _a;
|
|
27
|
-
const generation = ++this.generation;
|
|
28
|
-
this.needsFreshConnection = false;
|
|
29
|
-
for (const subscription of this.subscriptions.values())
|
|
30
|
-
this.join(subscription, generation);
|
|
31
|
-
(_a = this.resolveConnect) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
32
|
-
this.clearConnecting();
|
|
33
|
-
});
|
|
34
|
-
this.socket.on("disconnect", (reason) => {
|
|
35
|
-
++this.generation;
|
|
36
|
-
++this.authAttempt;
|
|
37
|
-
if (reason === "io server disconnect")
|
|
38
|
-
this.connectionError(new PlatformSocketError("connection_failed"));
|
|
39
|
-
});
|
|
40
|
-
this.socket.on("connect_error", (error) => {
|
|
41
|
-
var _a;
|
|
42
|
-
this.connectionError(new PlatformSocketError(((_a = error.data) === null || _a === void 0 ? void 0 : _a.code) === "connection_denied" ? "connection_denied" : "connection_failed"));
|
|
43
|
-
});
|
|
44
|
-
this.socket.io.on("reconnect_failed", () => this.connectionError(new PlatformSocketError("connection_failed")));
|
|
45
|
-
this.socket.on("joined", (raw) => {
|
|
46
|
-
var _a;
|
|
47
|
-
try {
|
|
48
|
-
const joined = decodeJoined(raw);
|
|
49
|
-
(_a = this.subscriptions.get(appFromRoom(joined.room))) === null || _a === void 0 ? void 0 : _a.joined(joined);
|
|
50
|
-
}
|
|
51
|
-
catch (_b) {
|
|
52
|
-
this.protocolError(raw);
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
this.socket.on("error", (raw) => this.serverError(raw));
|
|
56
|
-
for (const type of eventNames)
|
|
57
|
-
this.socket.on(type, (raw) => {
|
|
58
|
-
var _a;
|
|
59
|
-
try {
|
|
60
|
-
const appId = eventApp(type, raw);
|
|
61
|
-
if (!appId)
|
|
62
|
-
throw new Error("Invalid app");
|
|
63
|
-
(_a = this.subscriptions.get(appId)) === null || _a === void 0 ? void 0 : _a.event(decode(type, appId, raw));
|
|
64
|
-
}
|
|
65
|
-
catch (_b) {
|
|
66
|
-
this.protocolError(raw);
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
/** Connect using a freshly obtained token. Resolves on CONNECT, not on app replay completion.
|
|
71
|
-
* Unexpected transport loss retries up to five times and rejoins active subscriptions.
|
|
72
|
-
* Call again after addressing a connection/auth failure; concurrent calls share one attempt.
|
|
73
|
-
*/
|
|
74
|
-
connect() {
|
|
75
|
-
if (this.closed)
|
|
76
|
-
return Promise.reject(new PlatformSocketError("client_closed"));
|
|
77
|
-
if (this.socket.connected)
|
|
78
|
-
return Promise.resolve();
|
|
79
|
-
if (this.connecting)
|
|
80
|
-
return this.connecting;
|
|
81
|
-
const promise = new Promise((resolve, reject) => {
|
|
82
|
-
this.resolveConnect = resolve;
|
|
83
|
-
this.rejectConnect = reject;
|
|
84
|
-
});
|
|
85
|
-
this.connecting = promise;
|
|
86
|
-
this.socket.connect();
|
|
87
|
-
return promise;
|
|
88
|
-
}
|
|
89
|
-
/** Subscribe before or after connecting. One subscription per app, maximum eight.
|
|
90
|
-
* Events and boundary callbacks are awaited in order per app. Failed application,
|
|
91
|
-
* invalid frames or server errors end the subscription without advancing its cursor.
|
|
92
|
-
*/
|
|
93
|
-
subscribe(appId, options) {
|
|
94
|
-
if (this.closed)
|
|
95
|
-
throw new PlatformSocketError("client_closed");
|
|
96
|
-
if (!appPattern.test(appId))
|
|
97
|
-
throw new TypeError("appId must be 24 lowercase hexadecimal characters");
|
|
98
|
-
if (options.afterSeq !== undefined && (typeof options.afterSeq !== "string" || !options.afterSeq))
|
|
99
|
-
throw new TypeError("afterSeq must be a nonempty opaque cursor");
|
|
100
|
-
if (this.subscriptions.has(appId))
|
|
101
|
-
throw new TypeError("An app may only have one subscription per client");
|
|
102
|
-
if (this.subscriptions.size >= 8)
|
|
103
|
-
throw new PlatformSocketError("subscription_limit", appId);
|
|
104
|
-
const subscription = new Subscription(appId, { ...options }, () => {
|
|
105
|
-
this.subscriptions.delete(appId);
|
|
106
|
-
this.needsFreshConnection = true;
|
|
107
|
-
if (this.socket.connected)
|
|
108
|
-
this.socket.emit("leave", roomFor(appId));
|
|
109
|
-
});
|
|
110
|
-
this.subscriptions.set(appId, subscription);
|
|
111
|
-
if (this.socket.connected && this.needsFreshConnection) {
|
|
112
|
-
// Leave has no acknowledgement; a new transport fences late events from retired streams.
|
|
113
|
-
this.socket.disconnect();
|
|
114
|
-
void this.connect().catch(() => { }); // Connection errors are delivered through onError.
|
|
115
|
-
}
|
|
116
|
-
else if (this.socket.connected) {
|
|
117
|
-
this.join(subscription, this.generation);
|
|
118
|
-
}
|
|
119
|
-
return subscription;
|
|
120
|
-
}
|
|
121
|
-
/** Stop delivery, cancel reconnection and release all listeners. Idempotent and terminal. */
|
|
122
|
-
close() {
|
|
123
|
-
var _a, _b;
|
|
124
|
-
if (this.closed)
|
|
125
|
-
return;
|
|
126
|
-
this.closed = true;
|
|
127
|
-
++this.authAttempt;
|
|
128
|
-
(_a = this.cancelAuth) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
129
|
-
++this.generation;
|
|
130
|
-
for (const subscription of this.subscriptions.values())
|
|
131
|
-
subscription.unsubscribe();
|
|
132
|
-
(_b = this.rejectConnect) === null || _b === void 0 ? void 0 : _b.call(this, new PlatformSocketError("client_closed"));
|
|
133
|
-
this.clearConnecting();
|
|
134
|
-
this.socket.removeAllListeners();
|
|
135
|
-
this.socket.io.removeAllListeners();
|
|
136
|
-
this.socket.disconnect();
|
|
137
|
-
}
|
|
138
|
-
join(subscription, generation) {
|
|
139
|
-
subscription.join((cursor) => {
|
|
140
|
-
if (this.socket.connected && generation === this.generation) {
|
|
141
|
-
this.socket.emit("join", roomFor(subscription.appId), cursor === undefined ? {} : { after_seq: cursor });
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
async authenticate(callback) {
|
|
146
|
-
var _a;
|
|
147
|
-
const attempt = ++this.authAttempt;
|
|
148
|
-
(_a = this.cancelAuth) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
149
|
-
let timer;
|
|
150
|
-
const timeout = new Promise((_, reject) => {
|
|
151
|
-
timer = setTimeout(() => reject(new Error("Token timeout")), 20000);
|
|
152
|
-
this.cancelAuth = () => { clearTimeout(timer); reject(new Error("Cancelled")); };
|
|
153
|
-
});
|
|
154
|
-
try {
|
|
155
|
-
const token = await Promise.race([Promise.resolve().then(() => this.options.getToken()), timeout]);
|
|
156
|
-
if (this.closed || attempt !== this.authAttempt)
|
|
157
|
-
return;
|
|
158
|
-
if (typeof token !== "string" || !token.trim())
|
|
159
|
-
throw new Error("Missing token");
|
|
160
|
-
callback({ token });
|
|
161
|
-
}
|
|
162
|
-
catch (_b) {
|
|
163
|
-
if (this.closed || attempt !== this.authAttempt)
|
|
164
|
-
return;
|
|
165
|
-
this.socket.disconnect();
|
|
166
|
-
this.connectionError(new PlatformSocketError("token_unavailable"));
|
|
167
|
-
}
|
|
168
|
-
finally {
|
|
169
|
-
clearTimeout(timer);
|
|
170
|
-
if (attempt === this.authAttempt)
|
|
171
|
-
this.cancelAuth = undefined;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
clearConnecting() {
|
|
175
|
-
this.connecting = undefined;
|
|
176
|
-
this.resolveConnect = undefined;
|
|
177
|
-
this.rejectConnect = undefined;
|
|
178
|
-
}
|
|
179
|
-
connectionError(error) {
|
|
180
|
-
var _a;
|
|
181
|
-
(_a = this.rejectConnect) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
|
182
|
-
this.clearConnecting();
|
|
183
|
-
if (!this.closed)
|
|
184
|
-
notify(this.options.onError, error);
|
|
185
|
-
}
|
|
186
|
-
serverError(raw) {
|
|
187
|
-
var _a;
|
|
188
|
-
try {
|
|
189
|
-
const frame = object(raw);
|
|
190
|
-
const code = errorCodes.find((code) => code === frame.code);
|
|
191
|
-
if (!code)
|
|
192
|
-
throw new Error("Unknown error");
|
|
193
|
-
const appId = appFromRoom(frame.room);
|
|
194
|
-
if (appId)
|
|
195
|
-
(_a = this.subscriptions.get(appId)) === null || _a === void 0 ? void 0 : _a.fail(code);
|
|
196
|
-
else if (frame.room === null)
|
|
197
|
-
this.connectionError(new PlatformSocketError(code));
|
|
198
|
-
else
|
|
199
|
-
throw new Error("Invalid room");
|
|
200
|
-
}
|
|
201
|
-
catch (_b) {
|
|
202
|
-
this.protocolError(raw);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
protocolError(raw) {
|
|
206
|
-
var _a, _b;
|
|
207
|
-
const frame = raw && typeof raw === "object" ? raw : {};
|
|
208
|
-
const appId = (_a = appFromRoom(frame.room)) !== null && _a !== void 0 ? _a : (typeof frame.app_id === "string" && appPattern.test(frame.app_id) ? frame.app_id : undefined);
|
|
209
|
-
if (appId)
|
|
210
|
-
(_b = this.subscriptions.get(appId)) === null || _b === void 0 ? void 0 : _b.fail("protocol_error");
|
|
211
|
-
else {
|
|
212
|
-
// Unknown routing means no app cursor can safely advance past this frame.
|
|
213
|
-
for (const subscription of [...this.subscriptions.values()])
|
|
214
|
-
subscription.fail("protocol_error");
|
|
215
|
-
this.connectionError(new PlatformSocketError("protocol_error"));
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/** Server subscription failures and client transport/processing failures. */
|
|
2
|
-
export type PlatformSocketErrorCode = "invalid_room" | "invalid_cursor" | "access_denied" | "subscription_limit" | "resync_required" | "stream_unavailable" | "connection_denied" | "connection_failed" | "token_unavailable" | "protocol_error" | "handler_failed" | "client_closed";
|
|
3
|
-
/** Sanitized failure. Original token-provider, handler and server exceptions are not retained. */
|
|
4
|
-
export declare class PlatformSocketError extends Error {
|
|
5
|
-
/** Stable machine-readable category. */
|
|
6
|
-
readonly code: PlatformSocketErrorCode;
|
|
7
|
-
/** Associated app, when the server identifies a valid room. */
|
|
8
|
-
readonly appId?: string;
|
|
9
|
-
/** Create a sanitized error with no credential-bearing cause or payload. */
|
|
10
|
-
constructor(code: PlatformSocketErrorCode, appId?: string);
|
|
11
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/** Sanitized failure. Original 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
|
-
}
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
/** Public progress of an existing builder tool; arguments and results are withheld. */
|
|
2
|
-
export interface ToolCall {
|
|
3
|
-
/** Stable tool call identifier, when included in the update. */
|
|
4
|
-
id?: string;
|
|
5
|
-
/** Tool name displayed by the builder. */
|
|
6
|
-
name?: string;
|
|
7
|
-
/** Current execution state. */
|
|
8
|
-
status?: "running" | "success" | "error" | "stopped" | "waiting_for_user_input";
|
|
9
|
-
/** Whether the tool needs a user response through the partner backend. */
|
|
10
|
-
requires_user_input?: boolean;
|
|
11
|
-
/** Existing serialized interaction category; no raw interaction payload. */
|
|
12
|
-
waiting_on?: {
|
|
13
|
-
/** Kind of response expected. */
|
|
14
|
-
kind?: "approval" | "choice" | "input" | null;
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
/** Public message replacement. Omitted properties are not synthesized by the SDK. */
|
|
18
|
-
export interface ChatMessage {
|
|
19
|
-
/** Existing message identifier; replace a message with the same identifier. */
|
|
20
|
-
id?: string;
|
|
21
|
-
/** Public message author category. System messages are never delivered. */
|
|
22
|
-
role?: "user" | "assistant";
|
|
23
|
-
/** Generated or user-authored text. Structural filtering is not prose redaction. */
|
|
24
|
-
content?: string | null;
|
|
25
|
-
/** Attached file URLs. */
|
|
26
|
-
file_urls?: string[] | null;
|
|
27
|
-
/** Public tool progress, without arguments or results. */
|
|
28
|
-
tool_calls?: ToolCall[] | null;
|
|
29
|
-
/** Message timestamp, without author identity. */
|
|
30
|
-
metadata?: {
|
|
31
|
-
/** Existing timestamp string. */
|
|
32
|
-
created_date?: string | null;
|
|
33
|
-
} | null;
|
|
34
|
-
/** Existing checkpoint reference; mutations remain on the partner backend. */
|
|
35
|
-
checkpoint_id?: string | null;
|
|
36
|
-
}
|
|
37
|
-
/** Partial app update. Omitted keys mean unchanged; explicit null means clear. */
|
|
38
|
-
export interface AppUpdate {
|
|
39
|
-
/** Public builder state, without error diagnostics or billing context. */
|
|
40
|
-
status?: {
|
|
41
|
-
/** Current builder state. */
|
|
42
|
-
state?: "ready" | "processing" | "error";
|
|
43
|
-
/** Existing state timestamp. */
|
|
44
|
-
last_updated_date?: string | null;
|
|
45
|
-
} | null;
|
|
46
|
-
/** Whole-message replacement by identifier, not a recursive message patch. */
|
|
47
|
-
_last_msg?: ChatMessage | null;
|
|
48
|
-
/** Conversation containing the replacement message. */
|
|
49
|
-
_last_msg_conversation_id?: string | null;
|
|
50
|
-
/** Existing branch scope, if supplied by the producer. */
|
|
51
|
-
_scope_branch_id?: string | null;
|
|
52
|
-
/** Whether to reload the app preview. */
|
|
53
|
-
sandbox_should_reload?: boolean | null;
|
|
54
|
-
/** Existing preview navigation target. */
|
|
55
|
-
navigate_preview_to?: string | null;
|
|
56
|
-
/** Existing forced preview navigation target. */
|
|
57
|
-
navigate_preview_force_to?: string | null;
|
|
58
|
-
}
|
|
59
|
-
/** Public queued builder request. */
|
|
60
|
-
export interface QueueItem {
|
|
61
|
-
/** Stable queue item identifier. */
|
|
62
|
-
id: string;
|
|
63
|
-
/** User-authored request text. */
|
|
64
|
-
content: string;
|
|
65
|
-
/** Attached file URLs. */
|
|
66
|
-
file_urls?: string[] | null;
|
|
67
|
-
/** Existing creation timestamp. */
|
|
68
|
-
created_at: string;
|
|
69
|
-
/** Existing branch scope. */
|
|
70
|
-
branch_id?: string | null;
|
|
71
|
-
}
|
|
72
|
-
/** Full public queue snapshot, replacing the previous queue. */
|
|
73
|
-
export interface QueueUpdate {
|
|
74
|
-
/** App owning this queue. */
|
|
75
|
-
app_id: string;
|
|
76
|
-
/** Existing branch scope. */
|
|
77
|
-
branch_id?: string | null;
|
|
78
|
-
/** Current pending items. */
|
|
79
|
-
items: QueueItem[];
|
|
80
|
-
/** Whether queue processing is paused. */
|
|
81
|
-
is_paused: boolean;
|
|
82
|
-
/** Identifier of the item just processed, when supplied. */
|
|
83
|
-
processed_item_id?: string | null;
|
|
84
|
-
}
|
|
85
|
-
/** Public tool task progress. */
|
|
86
|
-
export interface TaskUpdate {
|
|
87
|
-
/** Existing task lifecycle event. */
|
|
88
|
-
event_type: "task_started" | "task_progress" | "task_completed" | "task_failed" | "task_cancelled";
|
|
89
|
-
/** Associated tool call. */
|
|
90
|
-
tool_call_id?: string | null;
|
|
91
|
-
/** Associated chat message. */
|
|
92
|
-
message_id?: string | null;
|
|
93
|
-
/** Existing branch scope. */
|
|
94
|
-
branch_id?: string | null;
|
|
95
|
-
/** Numeric progress only; diagnostic/free-text messages are withheld. */
|
|
96
|
-
progress?: {
|
|
97
|
-
/** Completed work units. */
|
|
98
|
-
current?: number | null;
|
|
99
|
-
/** Total work units, when known. */
|
|
100
|
-
total?: number | null;
|
|
101
|
-
/** Producer-supplied percentage. */
|
|
102
|
-
percentage?: number | null;
|
|
103
|
-
} | null;
|
|
104
|
-
}
|
|
105
|
-
/** Placeholder resolution or image-generation completion. */
|
|
106
|
-
export interface ImageReady {
|
|
107
|
-
/** Placeholder being resolved. */
|
|
108
|
-
placeholder_url: string;
|
|
109
|
-
/** Existing generation state. */
|
|
110
|
-
status: "pending" | "completed" | "failed";
|
|
111
|
-
/** Resolved image URL, or null when unavailable. */
|
|
112
|
-
image_url?: string | null;
|
|
113
|
-
}
|
|
114
|
-
/** Invalidation notice; fetch current state through the partner backend. */
|
|
115
|
-
export interface Directive {
|
|
116
|
-
/** Canonical app room. */
|
|
117
|
-
room: string;
|
|
118
|
-
/** Public invalidation category. */
|
|
119
|
-
type: "conversation_changed" | "app_files_changed";
|
|
120
|
-
/** Existing branch scope. */
|
|
121
|
-
branch_id?: string | null;
|
|
122
|
-
}
|
|
123
|
-
/** Mapping of wire event names to decoded public payloads. */
|
|
124
|
-
export interface PlatformEventMap {
|
|
125
|
-
/** Partial builder/app update. */
|
|
126
|
-
update_model: AppUpdate;
|
|
127
|
-
/** Conversation or file invalidation. */
|
|
128
|
-
directive: Directive;
|
|
129
|
-
/** Full queue snapshot. */
|
|
130
|
-
queue_update: QueueUpdate;
|
|
131
|
-
/** Numeric tool progress. */
|
|
132
|
-
task_update: TaskUpdate;
|
|
133
|
-
/** Image placeholder resolution. */
|
|
134
|
-
image_ready: ImageReady;
|
|
135
|
-
}
|
|
136
|
-
/** Ordered delivery with decoded data and the original event name and cursor. */
|
|
137
|
-
export type PlatformEvent = {
|
|
138
|
-
[K in keyof PlatformEventMap]: {
|
|
139
|
-
/** Original socket event name; narrows the payload type. */
|
|
140
|
-
type: K;
|
|
141
|
-
/** Authorized app receiving this event. */
|
|
142
|
-
appId: string;
|
|
143
|
-
/** Opaque replay cursor. Never parse, compare or increment it. */
|
|
144
|
-
seq: string;
|
|
145
|
-
/** Decoded payload; existing field names and omission/null semantics are retained. */
|
|
146
|
-
data: PlatformEventMap[K];
|
|
147
|
-
};
|
|
148
|
-
}[keyof PlatformEventMap];
|
|
149
|
-
/** Server replay boundary, delivered after all retained events through that boundary. */
|
|
150
|
-
export interface Joined {
|
|
151
|
-
/** Canonical app room. */
|
|
152
|
-
room: string;
|
|
153
|
-
/** Opaque boundary cursor; not an initial app snapshot. */
|
|
154
|
-
seq: string;
|
|
155
|
-
/** Server retention limit (currently 2,000 events per app). */
|
|
156
|
-
max_entries: number;
|
|
157
|
-
/** Server inactivity expiry (currently 3,600 seconds). */
|
|
158
|
-
inactivity_expiry_seconds: number;
|
|
159
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/** Read-only browser platform subscriptions, 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.js";
|
|
5
|
-
export type { PlatformClientOptions, PlatformSubscription, SubscriptionOptions } from "./types.js";
|
|
6
|
-
export type { AppUpdate, ChatMessage, ToolCall, QueueItem, QueueUpdate, TaskUpdate, ImageReady, Directive, PlatformEventMap, PlatformEvent, Joined } from "./events.js";
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { Joined, PlatformEvent, PlatformEventMap } from "./events.js";
|
|
2
|
-
export declare const eventNames: readonly ["update_model", "directive", "queue_update", "task_update", "image_ready"];
|
|
3
|
-
export declare const errorCodes: readonly ["invalid_room", "invalid_cursor", "access_denied", "subscription_limit", "resync_required", "stream_unavailable"];
|
|
4
|
-
export declare const appPattern: RegExp;
|
|
5
|
-
export declare const roomFor: (appId: string) => string;
|
|
6
|
-
export declare function object(value: unknown): Record<string, unknown>;
|
|
7
|
-
export declare function string(value: unknown): string;
|
|
8
|
-
export declare function appFromRoom(value: unknown): string | undefined;
|
|
9
|
-
export declare function eventApp(type: keyof PlatformEventMap, raw: unknown): string | undefined;
|
|
10
|
-
export declare function decode(type: keyof PlatformEventMap, appId: string, raw: unknown): PlatformEvent;
|
|
11
|
-
export declare function decodeJoined(raw: unknown): Joined;
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
export const eventNames = ["update_model", "directive", "queue_update", "task_update", "image_ready"];
|
|
2
|
-
export const errorCodes = ["invalid_room", "invalid_cursor", "access_denied", "subscription_limit", "resync_required", "stream_unavailable"];
|
|
3
|
-
export const appPattern = /^[a-f0-9]{24}$/;
|
|
4
|
-
export const roomFor = (appId) => `/apps/${appId}`;
|
|
5
|
-
export function object(value) {
|
|
6
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
7
|
-
throw new Error("Invalid frame");
|
|
8
|
-
return value;
|
|
9
|
-
}
|
|
10
|
-
export function string(value) {
|
|
11
|
-
if (typeof value !== "string" || !value)
|
|
12
|
-
throw new Error("Invalid string");
|
|
13
|
-
return value;
|
|
14
|
-
}
|
|
15
|
-
export function appFromRoom(value) {
|
|
16
|
-
return typeof value === "string" && /^\/apps\/[a-f0-9]{24}$/.test(value) ? value.slice(6) : undefined;
|
|
17
|
-
}
|
|
18
|
-
export function eventApp(type, raw) {
|
|
19
|
-
const frame = object(raw);
|
|
20
|
-
return type === "queue_update"
|
|
21
|
-
? typeof frame.app_id === "string" && appPattern.test(frame.app_id) ? frame.app_id : undefined
|
|
22
|
-
: appFromRoom(frame.room);
|
|
23
|
-
}
|
|
24
|
-
export function decode(type, appId, raw) {
|
|
25
|
-
const frame = object(raw);
|
|
26
|
-
const seq = string(frame.seq);
|
|
27
|
-
const wrapped = type === "update_model" || type === "task_update" || type === "image_ready";
|
|
28
|
-
const { seq: _, ...flat } = frame;
|
|
29
|
-
const data = wrapped ? object(JSON.parse(string(frame.data))) : flat;
|
|
30
|
-
// Payload schemas are owned by the service; only decode/validate the transport envelope here.
|
|
31
|
-
return { type, appId, seq, data };
|
|
32
|
-
}
|
|
33
|
-
export function decodeJoined(raw) {
|
|
34
|
-
const frame = object(raw);
|
|
35
|
-
if (!appFromRoom(frame.room) || !Number.isInteger(frame.max_entries) || !Number.isInteger(frame.inactivity_expiry_seconds))
|
|
36
|
-
throw new Error("Invalid boundary");
|
|
37
|
-
string(frame.seq);
|
|
38
|
-
return frame;
|
|
39
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { Joined, PlatformEvent } from "./events.js";
|
|
2
|
-
import { PlatformSocketError, type PlatformSocketErrorCode } from "./errors.js";
|
|
3
|
-
import type { PlatformSubscription, SubscriptionOptions } from "./types.js";
|
|
4
|
-
/** @internal */
|
|
5
|
-
export declare function notify(callback: (error: PlatformSocketError) => void, error: PlatformSocketError): void;
|
|
6
|
-
/** @internal */
|
|
7
|
-
export declare class Subscription implements PlatformSubscription {
|
|
8
|
-
readonly appId: string;
|
|
9
|
-
private options;
|
|
10
|
-
private remove;
|
|
11
|
-
cursor: string | undefined;
|
|
12
|
-
active: boolean;
|
|
13
|
-
private ready;
|
|
14
|
-
private pending;
|
|
15
|
-
private tail;
|
|
16
|
-
constructor(appId: string, options: SubscriptionOptions, remove: () => void);
|
|
17
|
-
enqueue(work: () => void | Promise<void>): void;
|
|
18
|
-
join(send: (cursor?: string) => void): void;
|
|
19
|
-
event(event: PlatformEvent): void;
|
|
20
|
-
joined(joined: Joined): void;
|
|
21
|
-
fail(code: PlatformSocketErrorCode): void;
|
|
22
|
-
unsubscribe(): void;
|
|
23
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
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.ready = false;
|
|
17
|
-
this.pending = 0;
|
|
18
|
-
this.tail = Promise.resolve();
|
|
19
|
-
this.cursor = options.afterSeq;
|
|
20
|
-
}
|
|
21
|
-
enqueue(work) {
|
|
22
|
-
if (!this.active)
|
|
23
|
-
return;
|
|
24
|
-
if (this.pending >= 1000) {
|
|
25
|
-
this.fail("resync_required");
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
this.pending++;
|
|
29
|
-
this.tail = this.tail.then(async () => {
|
|
30
|
-
if (this.active)
|
|
31
|
-
await work();
|
|
32
|
-
}).catch(() => this.fail("handler_failed")).finally(() => { this.pending--; });
|
|
33
|
-
}
|
|
34
|
-
join(send) {
|
|
35
|
-
this.enqueue(() => { this.ready = false; send(this.cursor); });
|
|
36
|
-
}
|
|
37
|
-
event(event) {
|
|
38
|
-
this.enqueue(async () => {
|
|
39
|
-
// A fresh subscription starts at joined, not at any old in-flight room events.
|
|
40
|
-
if ((!this.ready && this.cursor === undefined) || event.seq === this.cursor)
|
|
41
|
-
return;
|
|
42
|
-
await this.options.onEvent(event);
|
|
43
|
-
if (this.active)
|
|
44
|
-
this.cursor = event.seq;
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
joined(joined) {
|
|
48
|
-
this.enqueue(async () => {
|
|
49
|
-
var _a, _b;
|
|
50
|
-
await ((_b = (_a = this.options).onJoined) === null || _b === void 0 ? void 0 : _b.call(_a, joined));
|
|
51
|
-
if (this.active) {
|
|
52
|
-
this.cursor = joined.seq;
|
|
53
|
-
this.ready = true;
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
fail(code) {
|
|
58
|
-
if (!this.active)
|
|
59
|
-
return;
|
|
60
|
-
this.unsubscribe();
|
|
61
|
-
notify(this.options.onError, new PlatformSocketError(code, this.appId));
|
|
62
|
-
}
|
|
63
|
-
unsubscribe() {
|
|
64
|
-
if (!this.active)
|
|
65
|
-
return;
|
|
66
|
-
this.active = false;
|
|
67
|
-
this.remove();
|
|
68
|
-
}
|
|
69
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { Joined, PlatformEvent } from "./events.js";
|
|
2
|
-
import type { PlatformSocketError } from "./errors.js";
|
|
3
|
-
/** Configuration for the browser platform connection. Never supply an API key. */
|
|
4
|
-
export interface PlatformClientOptions {
|
|
5
|
-
/** Origin of the platform service, e.g. https://base44.app. No path/query/credentials. */
|
|
6
|
-
serverUrl: string;
|
|
7
|
-
/** Fetch a browser credential from your backend. Called for every connection attempt. */
|
|
8
|
-
getToken: () => string | Promise<string>;
|
|
9
|
-
/** Connection-level error notification. Errors never include tokens or server exception text. */
|
|
10
|
-
onError: (error: PlatformSocketError) => void;
|
|
11
|
-
}
|
|
12
|
-
/** One app subscription; at most eight may be active per client. */
|
|
13
|
-
export interface SubscriptionOptions {
|
|
14
|
-
/** Last successfully applied cursor for this app; omit for a fresh live boundary. */
|
|
15
|
-
afterSeq?: string;
|
|
16
|
-
/** Apply each event. Delivery is serial per app; rejection pauses this subscription. */
|
|
17
|
-
onEvent: (event: PlatformEvent) => void | Promise<void>;
|
|
18
|
-
/** Handle subscription errors. Reconcile on resync_required; never silently reset a cursor. */
|
|
19
|
-
onError: (error: PlatformSocketError) => void;
|
|
20
|
-
/** Optional replay-complete notification, awaited before advancing to the boundary cursor. */
|
|
21
|
-
onJoined?: (joined: Joined) => void | Promise<void>;
|
|
22
|
-
}
|
|
23
|
-
/** Subscription lifetime and last successfully applied cursor. */
|
|
24
|
-
export interface PlatformSubscription {
|
|
25
|
-
/** App identifier. */
|
|
26
|
-
readonly appId: string;
|
|
27
|
-
/** Last applied event/boundary cursor; persist alongside the state it describes. */
|
|
28
|
-
readonly cursor: string | undefined;
|
|
29
|
-
/** True while subscribed; false after an error or explicit unsubscribe. */
|
|
30
|
-
readonly active: boolean;
|
|
31
|
-
/** Stop this app's delivery and release its subscription slot. Idempotent. */
|
|
32
|
-
unsubscribe(): void;
|
|
33
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|