@unboundcx/sdk 4.6.3 → 4.8.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/index.js CHANGED
@@ -30,6 +30,8 @@ import { FaxService } from './services/fax.js';
30
30
  import { DocumentsService } from './services/documents.js';
31
31
  import { PermissionsService } from './services/permissions.js';
32
32
  import { TriggersService } from './services/triggers.js';
33
+ import { InboxService } from './services/inbox.js';
34
+ import { DirectoryService } from './services/directory.js';
33
35
 
34
36
  class UnboundSDK extends BaseSDK {
35
37
  constructor(options = {}) {
@@ -101,6 +103,8 @@ class UnboundSDK extends BaseSDK {
101
103
  this.documents = new DocumentsService(this);
102
104
  this.permissions = new PermissionsService(this);
103
105
  this.triggers = new TriggersService(this);
106
+ this.inbox = new InboxService(this);
107
+ this.directory = new DirectoryService(this);
104
108
 
105
109
  // Add additional services that might be missing
106
110
  this._initializeAdditionalServices();
@@ -281,4 +285,6 @@ export { WorkerService } from './services/taskRouter/WorkerService.js';
281
285
  export { KnowledgeBaseService } from './services/knowledgeBase.js';
282
286
  export { FaxService } from './services/fax.js';
283
287
  export { PermissionsService } from './services/permissions.js';
288
+ export { InboxService } from './services/inbox.js';
289
+ export { DirectoryService } from './services/directory.js';
284
290
  export { BaseSDK } from './base.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unboundcx/sdk",
3
- "version": "4.6.3",
3
+ "version": "4.8.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",
@@ -0,0 +1,14 @@
1
+ import { z } from 'zod';
2
+ import { WidgetSection } from './widgetSection.js';
3
+
4
+ // Home-dashboard layout doc (Phase 2). Validated via validateLayoutDoc when
5
+ // type === 'home' — see validate.js. No objectName: home layouts are not
6
+ // object-scoped (assignments use objectName: '' by convention, matching the
7
+ // table's existing wildcard pattern for recordTypeId/audienceId).
8
+ export const HomeLayoutDoc = z.object({
9
+ schemaVersion: z.literal(2).default(2),
10
+ type: z.literal('home'),
11
+ name: z.string().default('Home'),
12
+ tabIcon: z.string().default('fa-home'),
13
+ sections: z.array(WidgetSection).default([]),
14
+ });
@@ -6,9 +6,11 @@ export * from './field.js';
6
6
  export * from './kanban.js';
7
7
  export * from './relatedList.js';
8
8
  export * from './action.js';
9
+ export * from './widgetSection.js';
9
10
  export * from './section.js';
10
11
  export * from './compact.js';
11
12
  export * from './layoutDoc.js';
13
+ export * from './homeLayoutDoc.js';
12
14
  export { validateLayoutDoc } from './validate.js';
13
15
  export {
14
16
  migrateLayoutSchema, migrateToLatest, MIGRATIONS, CURRENT_SCHEMA_VERSION,
@@ -9,6 +9,7 @@ import { JoinSpec } from './join.js';
9
9
  import { FormatType } from './format.js';
10
10
  import { KanbanConfigSpec } from './kanban.js';
11
11
  import { RelatedListSpec } from './relatedList.js';
12
+ import { WidgetSection } from './widgetSection.js';
12
13
 
13
14
  const TableFieldSpec = z.object({
14
15
  field: z.string().min(1),
@@ -100,5 +101,5 @@ const TableKanbanSection = BaseSection.extend({
100
101
  // "delete TableEditor's legacy code path" work a schema-level forcing
101
102
  // function.
102
103
  export const SectionSpec = z.discriminatedUnion('type', [
103
- ContentSection, TableSection, KanbanSection, TableKanbanSection,
104
+ ContentSection, TableSection, KanbanSection, TableKanbanSection, WidgetSection,
104
105
  ]);
@@ -1,11 +1,17 @@
1
1
  import { LayoutDoc } from './layoutDoc.js';
2
2
  import { CompactLayoutDoc } from './compact.js';
3
+ import { HomeLayoutDoc } from './homeLayoutDoc.js';
3
4
 
4
5
  // type: explicit override; falls back to doc.type. Compact docs (type:'compact')
5
- // validate against CompactLayoutDoc; everything else against LayoutDoc.
6
+ // validate against CompactLayoutDoc; home docs (type:'home') against
7
+ // HomeLayoutDoc; everything else against LayoutDoc.
6
8
  export function validateLayoutDoc(rawDoc, { type } = {}) {
7
9
  const docType = type || rawDoc?.type;
8
- const schema = docType === 'compact' ? CompactLayoutDoc : LayoutDoc;
10
+ const schema = docType === 'compact'
11
+ ? CompactLayoutDoc
12
+ : docType === 'home'
13
+ ? HomeLayoutDoc
14
+ : LayoutDoc;
9
15
  const result = schema.safeParse(rawDoc);
10
16
  if (result.success) {
11
17
  return { valid: true, errors: [], data: result.data };
@@ -0,0 +1,16 @@
1
+ import { z } from 'zod';
2
+
3
+ // Home-dashboard widget placement (Phase 2 'home' layout kind). Standalone
4
+ // importable schema, also folded into SectionSpec's discriminated union
5
+ // (section.js) as an open widget kind alongside content/table/kanban.
6
+ export const WidgetSection = z.object({
7
+ id: z.string().min(1),
8
+ type: z.literal('widget'),
9
+ widgetId: z.string().min(1),
10
+ x: z.number().int().min(0).max(11),
11
+ y: z.number().int().min(0),
12
+ w: z.number().int().min(1).max(12),
13
+ h: z.number().int().min(1).max(8),
14
+ title: z.string().optional(),
15
+ settings: z.record(z.any()).default({}),
16
+ });
@@ -0,0 +1,124 @@
1
+ export class DirectoryService {
2
+ constructor(sdk) {
3
+ this.sdk = sdk;
4
+ }
5
+
6
+ /**
7
+ * List the current user's directory favorites, hydrated with the
8
+ * favorited record and its channels.
9
+ *
10
+ * @returns {Promise<{favorites: Object[]}>}
11
+ */
12
+ async listFavorites() {
13
+ const result = await this.sdk._fetch('/directory/favorites', 'GET');
14
+ return result;
15
+ }
16
+
17
+ /**
18
+ * Add a person or company to directory favorites. Reactivates a
19
+ * soft-deleted favorite on unique conflict.
20
+ *
21
+ * @param {Object} params
22
+ * @param {string} params.objectType - 'person' or 'company' (required).
23
+ * @param {string} params.objectId - Id of the favorited record (required).
24
+ * @param {string[]} [params.channelIds] - Channel ids to pin for this favorite.
25
+ * @returns {Promise<Object>} The created (or reactivated) favorite.
26
+ */
27
+ async addFavorite({ objectType, objectId, channelIds }) {
28
+ this.sdk.validateParams(
29
+ { objectType, objectId, channelIds },
30
+ {
31
+ objectType: { type: 'string', required: true },
32
+ objectId: { type: 'string', required: true },
33
+ channelIds: { type: 'array', required: false },
34
+ },
35
+ );
36
+
37
+ const body = { objectType, objectId };
38
+ if (channelIds !== undefined) body.channelIds = channelIds;
39
+
40
+ const params = { body };
41
+
42
+ const result = await this.sdk._fetch('/directory/favorites', 'POST', params);
43
+ return result;
44
+ }
45
+
46
+ /**
47
+ * Update a directory favorite's pinned channels and/or sort order.
48
+ *
49
+ * @param {string} favoriteId - Id of the favorite to update (required).
50
+ * @param {Object} params
51
+ * @param {string[]} [params.channelIds]
52
+ * @param {number} [params.sortOrder]
53
+ * @returns {Promise<Object>} The updated favorite.
54
+ */
55
+ async updateFavorite(favoriteId, { channelIds, sortOrder } = {}) {
56
+ this.sdk.validateParams(
57
+ { favoriteId, channelIds, sortOrder },
58
+ {
59
+ favoriteId: { type: 'string', required: true },
60
+ channelIds: { type: 'array', required: false },
61
+ sortOrder: { type: 'number', required: false },
62
+ },
63
+ );
64
+
65
+ const body = {};
66
+ if (channelIds !== undefined) body.channelIds = channelIds;
67
+ if (sortOrder !== undefined) body.sortOrder = sortOrder;
68
+
69
+ const params = { body };
70
+
71
+ const result = await this.sdk._fetch(
72
+ `/directory/favorites/${favoriteId}`,
73
+ 'PUT',
74
+ params,
75
+ );
76
+ return result;
77
+ }
78
+
79
+ /**
80
+ * Soft delete a directory favorite.
81
+ *
82
+ * @param {string} favoriteId - Id of the favorite to remove (required).
83
+ * @returns {Promise<Object>}
84
+ */
85
+ async removeFavorite(favoriteId) {
86
+ this.sdk.validateParams(
87
+ { favoriteId },
88
+ {
89
+ favoriteId: { type: 'string', required: true },
90
+ },
91
+ );
92
+
93
+ const result = await this.sdk._fetch(
94
+ `/directory/favorites/${favoriteId}`,
95
+ 'DELETE',
96
+ );
97
+ return result;
98
+ }
99
+
100
+ /**
101
+ * Reorder directory favorites.
102
+ *
103
+ * @param {Object} params
104
+ * @param {string[]} params.order - Favorite ids in the desired order (required).
105
+ * @returns {Promise<{ok: boolean}>}
106
+ */
107
+ async reorderFavorites({ order }) {
108
+ this.sdk.validateParams(
109
+ { order },
110
+ {
111
+ order: { type: 'array', required: true },
112
+ },
113
+ );
114
+
115
+ const params = { body: { order } };
116
+
117
+ const result = await this.sdk._fetch(
118
+ '/directory/favorites/reorder',
119
+ 'PUT',
120
+ params,
121
+ );
122
+ return result;
123
+ }
124
+ }
@@ -0,0 +1,83 @@
1
+ export class InboxService {
2
+ constructor(sdk) {
3
+ this.sdk = sdk;
4
+ }
5
+
6
+ /**
7
+ * List unified inbox items merged across call/voicemail/fax/sms sources,
8
+ * sorted descending by timestamp.
9
+ *
10
+ * @param {Object} params
11
+ * @param {string|string[]} [params.types] - Source kinds to include
12
+ * ('call','voicemail','fax','sms'). Pass an array or a comma-joined
13
+ * string; omit to include all kinds.
14
+ * @param {number} [params.limit] - Max items to return.
15
+ * @param {string} [params.before] - UTC cursor 'YYYY-MM-DD HH:mm:ss';
16
+ * only items strictly older than this are returned.
17
+ * @param {boolean} [params.unreadOnly] - When true, only unread items.
18
+ * @returns {Promise<{items: Object[], nextCursor: string|null}>}
19
+ */
20
+ async list({ types, limit, before, unreadOnly } = {}) {
21
+ this.sdk.validateParams(
22
+ { limit, before, unreadOnly },
23
+ {
24
+ limit: { type: 'number', required: false },
25
+ before: { type: 'string', required: false },
26
+ unreadOnly: { type: 'boolean', required: false },
27
+ },
28
+ );
29
+
30
+ if (
31
+ types !== undefined &&
32
+ typeof types !== 'string' &&
33
+ !Array.isArray(types)
34
+ ) {
35
+ throw new Error('types must be a string or an array of strings');
36
+ }
37
+
38
+ const query = {};
39
+ if (types !== undefined) {
40
+ query.types = Array.isArray(types) ? types.join(',') : types;
41
+ }
42
+ if (limit !== undefined) query.limit = limit;
43
+ if (before !== undefined) query.before = before;
44
+ if (unreadOnly !== undefined) query.unreadOnly = unreadOnly;
45
+
46
+ const params = { query };
47
+
48
+ const result = await this.sdk._fetch('/inbox', 'GET', params);
49
+ return result;
50
+ }
51
+
52
+ /**
53
+ * Fetch a paginated SMS/MMS thread for a phone number + counterparty pair.
54
+ *
55
+ * @param {Object} params
56
+ * @param {string} params.phoneNumberId - Our phone number id (required).
57
+ * @param {string} params.counterparty - Counterparty phone number (required).
58
+ * @param {number} [params.limit] - Max messages to return.
59
+ * @param {string} [params.before] - UTC cursor 'YYYY-MM-DD HH:mm:ss';
60
+ * only messages strictly older than this are returned.
61
+ * @returns {Promise<{messages: Object[], nextCursor: string|null}>}
62
+ */
63
+ async smsThread({ phoneNumberId, counterparty, limit, before }) {
64
+ this.sdk.validateParams(
65
+ { phoneNumberId, counterparty, limit, before },
66
+ {
67
+ phoneNumberId: { type: 'string', required: true },
68
+ counterparty: { type: 'string', required: true },
69
+ limit: { type: 'number', required: false },
70
+ before: { type: 'string', required: false },
71
+ },
72
+ );
73
+
74
+ const query = { phoneNumberId, counterparty };
75
+ if (limit !== undefined) query.limit = limit;
76
+ if (before !== undefined) query.before = before;
77
+
78
+ const params = { query };
79
+
80
+ const result = await this.sdk._fetch('/inbox/smsThread', 'GET', params);
81
+ return result;
82
+ }
83
+ }
@@ -97,21 +97,29 @@ export class LayoutsService {
97
97
  return result;
98
98
  }
99
99
 
100
- async resolve({ object, kind, recordId, recordTypeId, asUser } = {}) {
100
+ // `object` is required for object-scoped kinds ('list'/'detail'/'compact')
101
+ // but omitted for kind:'home' (home layouts are not object-scoped).
102
+ async resolve({
103
+ object, kind, recordId, recordTypeId, asUser, preset,
104
+ } = {}) {
101
105
  this.sdk.validateParams(
102
- { object, kind },
106
+ { object, kind, preset },
103
107
  {
104
- object: { type: 'string', required: true },
108
+ object: { type: 'string', required: kind !== 'home' },
105
109
  kind: { type: 'string', required: true },
106
110
  recordId: { type: 'string', required: false },
107
111
  recordTypeId: { type: 'string', required: false },
108
112
  asUser: { type: 'string', required: false },
113
+ preset: { type: 'string', required: false },
109
114
  },
110
115
  );
111
116
 
112
- const params = {
113
- query: { object, kind, recordId, recordTypeId, asUser },
114
- };
117
+ const query = { kind, recordId, recordTypeId, asUser, preset };
118
+ if (object) {
119
+ query.object = object;
120
+ }
121
+
122
+ const params = { query };
115
123
 
116
124
  const result = await this.sdk._fetch('/layouts/resolve', 'GET', params);
117
125
  return result;
@@ -174,9 +182,13 @@ export class LayoutAssignmentsService {
174
182
  this.sdk = sdk;
175
183
  }
176
184
 
185
+ // objectName defaults to '' for kind:'home' — matches the assignments
186
+ // table's existing "empty string = wildcard" convention for
187
+ // recordTypeId/audienceId; not a new pattern.
177
188
  async list({ objectName, kind } = {}) {
189
+ const resolvedObjectName = objectName ?? (kind === 'home' ? '' : objectName);
178
190
  this.sdk.validateParams(
179
- { objectName, kind },
191
+ { objectName: resolvedObjectName, kind },
180
192
  {
181
193
  objectName: { type: 'string', required: true },
182
194
  kind: { type: 'string', required: true },
@@ -184,7 +196,7 @@ export class LayoutAssignmentsService {
184
196
  );
185
197
 
186
198
  const params = {
187
- query: { objectName, kind },
199
+ query: { objectName: resolvedObjectName, kind },
188
200
  };
189
201
 
190
202
  const result = await this.sdk._fetch('/layouts/assignments', 'GET', params);
@@ -192,8 +204,9 @@ export class LayoutAssignmentsService {
192
204
  }
193
205
 
194
206
  async create({ objectName, kind, recordTypeId, audienceType, audienceId, layoutId, priority } = {}) {
207
+ const resolvedObjectName = objectName ?? (kind === 'home' ? '' : objectName);
195
208
  this.sdk.validateParams(
196
- { objectName, kind, audienceType, layoutId },
209
+ { objectName: resolvedObjectName, kind, audienceType, layoutId },
197
210
  {
198
211
  objectName: { type: 'string', required: true },
199
212
  kind: { type: 'string', required: true },
@@ -203,7 +216,9 @@ export class LayoutAssignmentsService {
203
216
  );
204
217
 
205
218
  const params = {
206
- body: { objectName, kind, recordTypeId, audienceType, audienceId, layoutId, priority },
219
+ body: {
220
+ objectName: resolvedObjectName, kind, recordTypeId, audienceType, audienceId, layoutId, priority,
221
+ },
207
222
  };
208
223
 
209
224
  const result = await this.sdk._fetch('/layouts/assignments', 'POST', params);