@unboundcx/sdk-internal 2.0.2 → 2.2.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
@@ -8,6 +8,8 @@ import { InternalServersService } from './services/servers.js';
8
8
  import { InternalSocketService } from './services/socket.js';
9
9
  import { InternalMasterAuthService } from './services/masterAuth.js';
10
10
  import { InternalBrandingService } from './services/branding.js';
11
+ import { InternalPhoneNumbersService } from './services/phoneNumbers.js';
12
+ import { buildObjectEventV2 } from './utils/buildObjectEventV2.js';
11
13
 
12
14
  export class InternalSDK {
13
15
  constructor(sdk, buildMasterApiAuthFn) {
@@ -22,6 +24,7 @@ export class InternalSDK {
22
24
  this.socket = new InternalSocketService(sdk);
23
25
  this.masterAuth = new InternalMasterAuthService(sdk);
24
26
  this.branding = new InternalBrandingService(sdk);
27
+ this.phoneNumbers = new InternalPhoneNumbersService(sdk);
25
28
 
26
29
  // Proxy all base SDK properties and methods
27
30
  this._proxyBaseSDK();
@@ -123,3 +126,5 @@ export {
123
126
  export { InternalSocketService } from './services/socket.js';
124
127
  export { InternalMasterAuthService } from './services/masterAuth.js';
125
128
  export { InternalBrandingService } from './services/branding.js';
129
+ export { InternalPhoneNumbersService } from './services/phoneNumbers.js';
130
+ export { buildObjectEventV2 } from './utils/buildObjectEventV2.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unboundcx/sdk-internal",
3
- "version": "2.0.2",
3
+ "version": "2.2.0",
4
4
  "description": "Internal SDK extension for Unbound - Provides access to internal APIs and administrative functions",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -32,6 +32,7 @@
32
32
  "files": [
33
33
  "*.js",
34
34
  "services/**/*.js",
35
+ "utils/**/*.js",
35
36
  "types/**/*.d.ts",
36
37
  "README.md",
37
38
  "LICENSE"
@@ -43,6 +44,9 @@
43
44
  },
44
45
  "./services/*": {
45
46
  "import": "./services/*.js"
47
+ },
48
+ "./utils/*": {
49
+ "import": "./utils/*.js"
46
50
  }
47
51
  },
48
52
  "scripts": {
@@ -32,4 +32,37 @@ export class InternalMasterAuthService {
32
32
  );
33
33
  return result;
34
34
  }
35
+
36
+ /**
37
+ * Mint an internal service token — the kind baked into pods as BOOT_TOKEN.
38
+ *
39
+ * Requires internal auth (the caller must already hold a valid internal
40
+ * token). Intended for manual/operational use: the returned token is stored
41
+ * in Zeus connection storage and injected into the services that need it.
42
+ *
43
+ * @param {object} [opts]
44
+ * @param {string} [opts.service='internal'] - value for the `service` claim
45
+ * @param {number} [opts.expiresIn] - lifetime in seconds; omit for a
46
+ * non-expiring token (matches the
47
+ * legacy BOOT_TOKEN)
48
+ */
49
+ async createInternalToken({ service = 'internal', expiresIn } = {}) {
50
+ this.sdk.validateParams(
51
+ { service, expiresIn },
52
+ {
53
+ service: { type: 'string', required: false },
54
+ expiresIn: { type: 'number', required: false },
55
+ },
56
+ );
57
+
58
+ const body = { service };
59
+ if (expiresIn !== undefined) body.expiresIn = expiresIn;
60
+
61
+ const result = await this.sdk._fetch(
62
+ '/internal/masterAuth/internal-token',
63
+ 'POST',
64
+ { body },
65
+ );
66
+ return result;
67
+ }
35
68
  }
@@ -0,0 +1,31 @@
1
+ export class InternalPhoneNumbersService {
2
+ constructor(sdk) {
3
+ this.sdk = sdk;
4
+ }
5
+
6
+ /**
7
+ * Claim a number from Telnyx into the global phoneNumbers inventory as an
8
+ * unassigned row. Requires an internal auth token — this bypasses normal
9
+ * ordering/billing and is not part of the public SDK.
10
+ *
11
+ * @param {Object} params
12
+ * @param {string} params.phoneNumber - +E.164 number that already exists on the Telnyx account
13
+ */
14
+ async createInventory({ phoneNumber }) {
15
+ this.sdk.validateParams(
16
+ { phoneNumber },
17
+ {
18
+ phoneNumber: { type: 'string', required: true },
19
+ },
20
+ );
21
+
22
+ const result = await this.sdk._fetch(
23
+ '/internal/phoneNumbers/inventory',
24
+ 'POST',
25
+ {
26
+ body: { phoneNumber },
27
+ },
28
+ );
29
+ return result;
30
+ }
31
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Build a spec-valid `objects.event.v2` envelope for non-api writers
3
+ * (task-router, cdr-user-status, ...) that mutate object-backed tables with
4
+ * raw SQL and need to emit live-update events without an HTTP round-trip
5
+ * through app1-api.
6
+ *
7
+ * Pure helper — no NATS/publish side effects. Callers publish the returned
8
+ * `{ subject, payload }` themselves via their own service's NATS client and
9
+ * subject-prefix handling.
10
+ *
11
+ * @param {Object} params
12
+ * @param {'create'|'update'|'delete'} params.action
13
+ * @param {string} params.objectName - plural object/table name, e.g. 'tasks'
14
+ * @param {string} params.accountId
15
+ * @param {string|number} params.recordTypeId
16
+ * @param {Object|null} [params.oldRow] - full pre-mutation row image; null for 'create'
17
+ * @param {Object|null} [params.newRow] - full post-mutation row image; null for 'delete'
18
+ * @param {number} [params.now] - ms timestamp used for rowVersion/ts; defaults to Date.now()
19
+ * @returns {{ subject: string, payload: Object }}
20
+ */
21
+ export function buildObjectEventV2({
22
+ action,
23
+ objectName,
24
+ accountId,
25
+ recordTypeId,
26
+ oldRow = null,
27
+ newRow = null,
28
+ now,
29
+ }) {
30
+ const changedFields =
31
+ oldRow && newRow ? diffChangedFields(oldRow, newRow) : [];
32
+
33
+ const ts = now ?? Date.now();
34
+
35
+ return {
36
+ subject: 'objects.event.v2',
37
+ payload: {
38
+ action,
39
+ objectName,
40
+ accountId,
41
+ recordTypeId,
42
+ oldRow,
43
+ newRow,
44
+ changedFields,
45
+ rowVersion: ts,
46
+ ts,
47
+ sourceCluster: process.env.CLUSTER_NAME || 'unknown',
48
+ },
49
+ };
50
+ }
51
+
52
+ function diffChangedFields(oldRow, newRow) {
53
+ const fields = new Set([...Object.keys(oldRow), ...Object.keys(newRow)]);
54
+ const changed = [];
55
+ for (const field of fields) {
56
+ if (!isEqualValue(oldRow[field], newRow[field])) {
57
+ changed.push(field);
58
+ }
59
+ }
60
+ return changed;
61
+ }
62
+
63
+ function isEqualValue(a, b) {
64
+ if (a === b) return true;
65
+ if (a == null || b == null) return a === b;
66
+ if (typeof a === 'object' && typeof b === 'object') {
67
+ try {
68
+ return JSON.stringify(a) === JSON.stringify(b);
69
+ } catch {
70
+ return false;
71
+ }
72
+ }
73
+ return false;
74
+ }