@unboundcx/sdk 4.6.2 → 4.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unboundcx/sdk",
3
- "version": "4.6.2",
3
+ "version": "4.7.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
+ });
@@ -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);
@@ -486,6 +486,50 @@ export class PermissionsService {
486
486
  );
487
487
  }
488
488
 
489
+ /**
490
+ * Per-user, per-account JSON app state (UI state such as saved tab-sets).
491
+ * Distinct from settings: free-form JSON, not catalog-validated.
492
+ * @param {string|number} userId - User ID (required)
493
+ * @param {string} stateKey - State key, /^[a-zA-Z0-9_-]{1,64}$/ (required)
494
+ * @returns {Promise<Object>} { value } (value is null if unset)
495
+ */
496
+ async getUserAppState(userId, stateKey) {
497
+ userId = String(userId);
498
+ stateKey = String(stateKey);
499
+ this.sdk.validateParams(
500
+ { userId, stateKey },
501
+ {
502
+ userId: { type: 'string', required: true },
503
+ stateKey: { type: 'string', required: true },
504
+ },
505
+ );
506
+ return this.sdk._fetch(
507
+ `/permissions/users/${userId}/app-state/${stateKey}`,
508
+ 'GET',
509
+ );
510
+ }
511
+
512
+ /**
513
+ * Upsert one app-state value for a user (JSON-serializable, ≤256KB serialized).
514
+ * @returns {Promise<Object>} { userId, stateKey, value, updatedAt }
515
+ */
516
+ async setUserAppState(userId, stateKey, value) {
517
+ userId = String(userId);
518
+ stateKey = String(stateKey);
519
+ this.sdk.validateParams(
520
+ { userId, stateKey },
521
+ {
522
+ userId: { type: 'string', required: true },
523
+ stateKey: { type: 'string', required: true },
524
+ },
525
+ );
526
+ return this.sdk._fetch(
527
+ `/permissions/users/${userId}/app-state/${stateKey}`,
528
+ 'PUT',
529
+ { body: { value } },
530
+ );
531
+ }
532
+
489
533
  // ---- Group-assigned skills and queues (§9.5) --------------------------
490
534
  // Set-shaped, not scalar: they union across every group a user belongs to
491
535
  // and never consult group priority. Writes fan out to each member's