@unboundcx/sdk 4.4.0 → 4.5.0
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 +36 -0
- package/package.json +3 -3
- package/schemas/layouts/primitives.js +6 -0
- package/schemas/layouts/section.js +6 -2
- package/services/liveQuery.js +26 -5
- package/services/objects.js +2 -0
package/README.md
CHANGED
|
@@ -206,6 +206,42 @@ row-evaluable). Heartbeats, reconnect-resubscribe, and sequence-gap resync
|
|
|
206
206
|
are handled internally. Server caps: 25 subscriptions per socket, 500 per
|
|
207
207
|
account.
|
|
208
208
|
|
|
209
|
+
**UOQL form** — pass a `uoql` string instead of `object`/`filter`/`fields`/
|
|
210
|
+
`recordTypeId` (mutually exclusive; the SDK throws synchronously if both, or
|
|
211
|
+
neither, are given). The server parses and classifies the query for you:
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
// Simple single-object query with a flat WHERE -> classifies FINE, same
|
|
215
|
+
// row-level 'enter'/'change'/'leave' events as the object/filter form above.
|
|
216
|
+
const handle = await api.objects.liveQuery({
|
|
217
|
+
socket,
|
|
218
|
+
uoql: "SELECT id, name, status FROM contacts WHERE companyId = 'company-123'",
|
|
219
|
+
onEvent: (frame) => {},
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Relationship paths / aggregates -> classifies COARSE: you only get
|
|
223
|
+
// debounced 'refresh' hints (no row-level frames), so re-run the query
|
|
224
|
+
// yourself each time onEvent fires with frame.type === 'refresh'.
|
|
225
|
+
// (UOQL expresses joins as dot-notation relationship paths, not JOIN syntax.)
|
|
226
|
+
const relatedUoql =
|
|
227
|
+
"SELECT name, companyId.name FROM contacts WHERE companyId.industry = 'tech'";
|
|
228
|
+
const relatedHandle = await api.objects.liveQuery({
|
|
229
|
+
socket,
|
|
230
|
+
uoql: relatedUoql,
|
|
231
|
+
onEvent: async (frame) => {
|
|
232
|
+
if (frame.type === 'refresh' || frame.type === 'resync') {
|
|
233
|
+
const results = await api.objects.queryV2({ query: relatedUoql });
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
relatedHandle.unsubscribe();
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Resubscribe-on-reconnect re-sends the same `uoql` string verbatim, so the
|
|
242
|
+
server re-classifies it fresh each time (mode can't drift out from under a
|
|
243
|
+
long-lived handle).
|
|
244
|
+
|
|
209
245
|
#### Messaging (`api.messaging`)
|
|
210
246
|
|
|
211
247
|
```javascript
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unboundcx/sdk",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"description": "Official JavaScript SDK for the Unbound API - A comprehensive toolkit for integrating with Unbound's communication, AI, and data management services",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -76,9 +76,9 @@
|
|
|
76
76
|
"zod": "^3.23.8"
|
|
77
77
|
},
|
|
78
78
|
"optionalDependencies": {
|
|
79
|
-
"mime-types": "^2.1.35",
|
|
80
79
|
"@grpc/grpc-js": "^1.14.1",
|
|
81
|
-
"@grpc/proto-loader": "^0.7.15"
|
|
80
|
+
"@grpc/proto-loader": "^0.7.15",
|
|
81
|
+
"mime-types": "^2.1.35"
|
|
82
82
|
},
|
|
83
83
|
"peerDependencies": {
|
|
84
84
|
"socket.io-client": "^4.0.0"
|
|
@@ -26,6 +26,12 @@ export const OrderBySpec = z.object({
|
|
|
26
26
|
direction: z.enum(['asc', 'desc']).default('asc'),
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
+
// Multi-column ordering (2026-08-15): a single {field, direction} object
|
|
30
|
+
// (every stored layout to date) OR an ordered array of them. Consumers
|
|
31
|
+
// normalize via a "wrap in array if not one" step; the builder writes the
|
|
32
|
+
// array shape going forward.
|
|
33
|
+
export const OrderByListSpec = z.union([OrderBySpec, z.array(OrderBySpec)]);
|
|
34
|
+
|
|
29
35
|
export const HeaderSpec = z.object({
|
|
30
36
|
value: z.string().default(''),
|
|
31
37
|
collapsedValue: z.string().optional(),
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
HeaderSpec,
|
|
4
|
+
ConditionSpec,
|
|
5
|
+
OrderByListSpec,
|
|
6
|
+
} from './primitives.js';
|
|
3
7
|
import { FieldSpec } from './field.js';
|
|
4
8
|
import { JoinSpec } from './join.js';
|
|
5
9
|
import { FormatType } from './format.js';
|
|
@@ -36,7 +40,7 @@ const TableSpec = z.object({
|
|
|
36
40
|
hideOnCreate: z.boolean().default(false),
|
|
37
41
|
cardClick: z.enum(['tab', 'modal']).default('tab'),
|
|
38
42
|
}).default({}),
|
|
39
|
-
orderBy:
|
|
43
|
+
orderBy: OrderByListSpec.optional(),
|
|
40
44
|
additionalWhere: z.record(z.string()).optional(),
|
|
41
45
|
hideOnCreate: z.boolean().default(false),
|
|
42
46
|
});
|
package/services/liveQuery.js
CHANGED
|
@@ -117,15 +117,20 @@ class LiveQueryHandle {
|
|
|
117
117
|
filter,
|
|
118
118
|
fields,
|
|
119
119
|
recordTypeId,
|
|
120
|
+
uoql,
|
|
120
121
|
onEvent,
|
|
121
122
|
onStateChange,
|
|
122
123
|
}) {
|
|
123
124
|
this.manager = manager;
|
|
124
125
|
this.socket = socket;
|
|
125
|
-
|
|
126
|
+
// Internal bookkeeping/log labels need an object-name-shaped string even
|
|
127
|
+
// in uoql mode - use the literal 'uoql' there (no single object name
|
|
128
|
+
// exists yet, that's resolved server-side by analyze()).
|
|
129
|
+
this.object = uoql !== undefined ? 'uoql' : object;
|
|
126
130
|
this.filter = filter;
|
|
127
131
|
this.fields = fields;
|
|
128
132
|
this.recordTypeId = recordTypeId;
|
|
133
|
+
this.uoql = uoql;
|
|
129
134
|
this.onEvent = onEvent;
|
|
130
135
|
this.onStateChange = onStateChange;
|
|
131
136
|
|
|
@@ -136,7 +141,10 @@ class LiveQueryHandle {
|
|
|
136
141
|
}
|
|
137
142
|
|
|
138
143
|
async _subscribe() {
|
|
139
|
-
const payload =
|
|
144
|
+
const payload =
|
|
145
|
+
this.uoql !== undefined
|
|
146
|
+
? { uoql: this.uoql }
|
|
147
|
+
: { objectName: this.object, filter: this.filter, fields: this.fields };
|
|
140
148
|
if (this.recordTypeId !== undefined) payload.recordTypeId = this.recordTypeId;
|
|
141
149
|
|
|
142
150
|
const ack = await new Promise((resolve, reject) => {
|
|
@@ -249,6 +257,11 @@ class LiveQueryHandle {
|
|
|
249
257
|
* this account; falls back to `sdk.socket` if the sdk instance holds one.
|
|
250
258
|
* - object, filter, fields, recordTypeId: subscribe-time query, same shape
|
|
251
259
|
* as `sdk.objects.query`.
|
|
260
|
+
* - uoql: subscribe-time query as a UOQL string instead - mutually exclusive
|
|
261
|
+
* with object/filter/fields/recordTypeId (throws synchronously if both, or
|
|
262
|
+
* neither, are given). Sent to the server as `{ uoql }`; the server runs
|
|
263
|
+
* uoql analyze() to resolve it to a fine or coarse subscription. Also
|
|
264
|
+
* re-sent verbatim on reconnect resubscribe.
|
|
252
265
|
* - onEvent(frame): called for every 'enter'|'change'|'leave'|'refresh'|
|
|
253
266
|
* 'resync'|'revoked' frame (resync frames are also synthesized locally on
|
|
254
267
|
* seq gaps and on reconnect).
|
|
@@ -257,7 +270,7 @@ class LiveQueryHandle {
|
|
|
257
270
|
* Resolves to { subscriptionId, mode, unsubscribe() }.
|
|
258
271
|
*/
|
|
259
272
|
export async function liveQuery(sdk, args = {}) {
|
|
260
|
-
const { socket: providedSocket, object, filter, fields, recordTypeId, onEvent, onStateChange } = args;
|
|
273
|
+
const { socket: providedSocket, object, filter, fields, recordTypeId, uoql, onEvent, onStateChange } = args;
|
|
261
274
|
|
|
262
275
|
const socket = providedSocket || sdk.socket;
|
|
263
276
|
if (!socket || typeof socket.emit !== 'function' || typeof socket.on !== 'function') {
|
|
@@ -267,8 +280,15 @@ export async function liveQuery(sdk, args = {}) {
|
|
|
267
280
|
'deviation from the locked liveQuery contract, see plan Phase 3',
|
|
268
281
|
);
|
|
269
282
|
}
|
|
270
|
-
|
|
271
|
-
|
|
283
|
+
|
|
284
|
+
const hasObjectForm = object !== undefined || filter !== undefined || fields !== undefined || recordTypeId !== undefined;
|
|
285
|
+
if (uoql !== undefined && hasObjectForm) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
'liveQuery :: init :: uoql is mutually exclusive with object/filter/fields/recordTypeId',
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
if (uoql === undefined && !object) {
|
|
291
|
+
throw new Error('liveQuery :: init :: either uoql or object is required');
|
|
272
292
|
}
|
|
273
293
|
|
|
274
294
|
const manager = getSocketManager(socket);
|
|
@@ -279,6 +299,7 @@ export async function liveQuery(sdk, args = {}) {
|
|
|
279
299
|
filter,
|
|
280
300
|
fields,
|
|
281
301
|
recordTypeId,
|
|
302
|
+
uoql,
|
|
282
303
|
onEvent,
|
|
283
304
|
onStateChange,
|
|
284
305
|
});
|
package/services/objects.js
CHANGED
|
@@ -30,6 +30,8 @@ export class ObjectsService {
|
|
|
30
30
|
* re-subscribe, revoked teardown).
|
|
31
31
|
*
|
|
32
32
|
* sdk.objects.liveQuery({ socket, object, filter, fields, recordTypeId, onEvent, onStateChange })
|
|
33
|
+
* sdk.objects.liveQuery({ socket, uoql, onEvent, onStateChange }) // uoql is mutually
|
|
34
|
+
* exclusive with object/filter/fields/recordTypeId
|
|
33
35
|
* -> Promise<{ subscriptionId, mode, unsubscribe() }>
|
|
34
36
|
*/
|
|
35
37
|
liveQuery(args) {
|