@topolo/sdk 0.2.6 → 0.2.8
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/dist/index.cjs +111 -0
- package/dist/index.d.cts +87 -15
- package/dist/index.d.ts +87 -15
- package/dist/index.js +111 -0
- package/package.json +1 -1
- package/src/applications.generated.ts +71 -0
- package/src/index.ts +18 -11
- package/src/services.generated.ts +5 -0
- package/src/services.test.ts +3 -0
- package/src/widgets.test.ts +88 -49
- package/src/widgets.ts +115 -43
package/dist/index.js
CHANGED
|
@@ -64,8 +64,10 @@ var SERVICES = {
|
|
|
64
64
|
bugfix: "https://bugfix.topolo.app",
|
|
65
65
|
bytes: "https://r2-media-manager-worker.topolo.workers.dev",
|
|
66
66
|
calendar: "https://calendar.topolo.app",
|
|
67
|
+
capacity: "https://topolo-capacity.topolo.workers.dev",
|
|
67
68
|
chat: "https://chat.topolo.app",
|
|
68
69
|
commerce: "https://topolo-commerce-api.topolo.workers.dev",
|
|
70
|
+
compose: "https://compose.topolo.app",
|
|
69
71
|
crm: "https://crm-api.topolo.app",
|
|
70
72
|
customer_success: "https://customer-success.topolo.app",
|
|
71
73
|
feed: "https://topolo-feed-api.topolo.app",
|
|
@@ -79,6 +81,7 @@ var SERVICES = {
|
|
|
79
81
|
mdm: "https://topolo-mdm-api.topolo.app",
|
|
80
82
|
messaging: "https://messages.topolo.app",
|
|
81
83
|
nexus: "https://topolo-nexus-gateway.topolo.workers.dev",
|
|
84
|
+
notify: "https://notify.topolo.app",
|
|
82
85
|
one: "https://one.topolo.app",
|
|
83
86
|
pay: "https://topolo-pay.topolo.workers.dev",
|
|
84
87
|
people: "https://people.topolo.app",
|
|
@@ -95,12 +98,14 @@ var SERVICES = {
|
|
|
95
98
|
};
|
|
96
99
|
var PLATFORM_SERVICE_IDS = {
|
|
97
100
|
books: "srv_topolo_books",
|
|
101
|
+
compose: "srv_topolo_compose",
|
|
98
102
|
crm: "srv_iCwM4jGXcwlj",
|
|
99
103
|
customer_success: "srv_topolo_customer_success",
|
|
100
104
|
flow: "srv_topolo_flow",
|
|
101
105
|
forms: "srv_topolo_forms",
|
|
102
106
|
insights: "srv_topolo_insights",
|
|
103
107
|
inventory: "srv_topolo_inventory",
|
|
108
|
+
notify: "svc_topolo_notify",
|
|
104
109
|
people: "srv_topolo_people",
|
|
105
110
|
sign: "srv_topolo_sign",
|
|
106
111
|
survey: "srv_topolo_survey",
|
|
@@ -279,6 +284,29 @@ var APPLICATIONS = {
|
|
|
279
284
|
}
|
|
280
285
|
]
|
|
281
286
|
},
|
|
287
|
+
"capacity": {
|
|
288
|
+
"id": "capacity",
|
|
289
|
+
"appDir": "TopoloCapacity",
|
|
290
|
+
"name": "topolo-capacity",
|
|
291
|
+
"packageName": "topolo-capacity",
|
|
292
|
+
"productionUrl": "https://capacity.topolo.app",
|
|
293
|
+
"services": [
|
|
294
|
+
"capacity"
|
|
295
|
+
],
|
|
296
|
+
"deployTargets": [
|
|
297
|
+
{
|
|
298
|
+
"name": "topolo-capacity",
|
|
299
|
+
"kind": "worker",
|
|
300
|
+
"apiUrl": "https://topolo-capacity.topolo.workers.dev",
|
|
301
|
+
"domains": [
|
|
302
|
+
"capacity.topolo.app",
|
|
303
|
+
"topolo-capacity.topolo.workers.dev"
|
|
304
|
+
],
|
|
305
|
+
"routes": [],
|
|
306
|
+
"serviceId": "capacity"
|
|
307
|
+
}
|
|
308
|
+
]
|
|
309
|
+
},
|
|
282
310
|
"chat": {
|
|
283
311
|
"id": "chat",
|
|
284
312
|
"appDir": "TopoloChat",
|
|
@@ -376,6 +404,30 @@ var APPLICATIONS = {
|
|
|
376
404
|
}
|
|
377
405
|
]
|
|
378
406
|
},
|
|
407
|
+
"compose": {
|
|
408
|
+
"id": "compose",
|
|
409
|
+
"appDir": "TopoloCompose",
|
|
410
|
+
"name": "topolo-compose",
|
|
411
|
+
"packageName": "@topolo/compose",
|
|
412
|
+
"productionUrl": "https://compose.topolo.app",
|
|
413
|
+
"services": [
|
|
414
|
+
"compose"
|
|
415
|
+
],
|
|
416
|
+
"deployTargets": [
|
|
417
|
+
{
|
|
418
|
+
"name": "topolo-compose",
|
|
419
|
+
"kind": "worker",
|
|
420
|
+
"apiUrl": "https://compose.topolo.app",
|
|
421
|
+
"domains": [
|
|
422
|
+
"compose.topolo.app"
|
|
423
|
+
],
|
|
424
|
+
"routes": [
|
|
425
|
+
"compose.topolo.app"
|
|
426
|
+
],
|
|
427
|
+
"serviceId": "compose"
|
|
428
|
+
}
|
|
429
|
+
]
|
|
430
|
+
},
|
|
379
431
|
"crm": {
|
|
380
432
|
"id": "crm",
|
|
381
433
|
"appDir": "TopoloCRM",
|
|
@@ -793,6 +845,30 @@ var APPLICATIONS = {
|
|
|
793
845
|
}
|
|
794
846
|
]
|
|
795
847
|
},
|
|
848
|
+
"notify": {
|
|
849
|
+
"id": "notify",
|
|
850
|
+
"appDir": "TopoloNotify",
|
|
851
|
+
"name": "topolo-notify",
|
|
852
|
+
"packageName": "@topolo/notify",
|
|
853
|
+
"productionUrl": "https://notify.topolo.app",
|
|
854
|
+
"services": [
|
|
855
|
+
"notify"
|
|
856
|
+
],
|
|
857
|
+
"deployTargets": [
|
|
858
|
+
{
|
|
859
|
+
"name": "topolo-notify",
|
|
860
|
+
"kind": "worker",
|
|
861
|
+
"apiUrl": "https://notify.topolo.app",
|
|
862
|
+
"domains": [
|
|
863
|
+
"notify.topolo.app"
|
|
864
|
+
],
|
|
865
|
+
"routes": [
|
|
866
|
+
"notify.topolo.app"
|
|
867
|
+
],
|
|
868
|
+
"serviceId": "notify"
|
|
869
|
+
}
|
|
870
|
+
]
|
|
871
|
+
},
|
|
796
872
|
"one": {
|
|
797
873
|
"id": "one",
|
|
798
874
|
"appDir": "TopoloOne",
|
|
@@ -1969,6 +2045,7 @@ function validateWidget(widget, path, errors) {
|
|
|
1969
2045
|
validateNonEmptyString(widget.serviceId, `${path}.serviceId`, errors);
|
|
1970
2046
|
validateNonEmptyString(widget.serviceName, `${path}.serviceName`, errors);
|
|
1971
2047
|
validateNonEmptyString(widget.lastUpdated, `${path}.lastUpdated`, errors);
|
|
2048
|
+
validateSnapshotMetadata(widget.snapshot, `${path}.snapshot`, errors);
|
|
1972
2049
|
switch (widget.type) {
|
|
1973
2050
|
case "stats":
|
|
1974
2051
|
validateArray(widget.stats, `${path}.stats`, errors);
|
|
@@ -1999,6 +2076,40 @@ function validateWidget(widget, path, errors) {
|
|
|
1999
2076
|
errors.push(`${path}.type must be a supported Topolo widget type`);
|
|
2000
2077
|
}
|
|
2001
2078
|
}
|
|
2079
|
+
function validateSnapshotMetadata(snapshot, path, errors) {
|
|
2080
|
+
if (snapshot === void 0) {
|
|
2081
|
+
return;
|
|
2082
|
+
}
|
|
2083
|
+
if (!isRecord(snapshot)) {
|
|
2084
|
+
errors.push(`${path} must be an object when provided`);
|
|
2085
|
+
return;
|
|
2086
|
+
}
|
|
2087
|
+
if (snapshot.defaultMetricKeys !== void 0 && !Array.isArray(snapshot.defaultMetricKeys)) {
|
|
2088
|
+
errors.push(`${path}.defaultMetricKeys must be an array when provided`);
|
|
2089
|
+
}
|
|
2090
|
+
if (snapshot.highlights !== void 0 && !Array.isArray(snapshot.highlights)) {
|
|
2091
|
+
errors.push(`${path}.highlights must be an array when provided`);
|
|
2092
|
+
}
|
|
2093
|
+
if (snapshot.availableMetrics === void 0) {
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
2096
|
+
if (!Array.isArray(snapshot.availableMetrics)) {
|
|
2097
|
+
errors.push(`${path}.availableMetrics must be an array when provided`);
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
snapshot.availableMetrics.forEach((metric, index) => {
|
|
2101
|
+
const metricPath = `${path}.availableMetrics[${index}]`;
|
|
2102
|
+
if (!isRecord(metric)) {
|
|
2103
|
+
errors.push(`${metricPath} must be an object`);
|
|
2104
|
+
return;
|
|
2105
|
+
}
|
|
2106
|
+
validateNonEmptyString(metric.key, `${metricPath}.key`, errors);
|
|
2107
|
+
validateNonEmptyString(metric.label, `${metricPath}.label`, errors);
|
|
2108
|
+
if (typeof metric.value !== "number" || Number.isNaN(metric.value)) {
|
|
2109
|
+
errors.push(`${metricPath}.value must be a number`);
|
|
2110
|
+
}
|
|
2111
|
+
});
|
|
2112
|
+
}
|
|
2002
2113
|
function validateNonEmptyString(value, path, errors) {
|
|
2003
2114
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
2004
2115
|
errors.push(`${path} must be a non-empty string`);
|
package/package.json
CHANGED
|
@@ -173,6 +173,29 @@ export const APPLICATIONS = {
|
|
|
173
173
|
}
|
|
174
174
|
]
|
|
175
175
|
},
|
|
176
|
+
"capacity": {
|
|
177
|
+
"id": "capacity",
|
|
178
|
+
"appDir": "TopoloCapacity",
|
|
179
|
+
"name": "topolo-capacity",
|
|
180
|
+
"packageName": "topolo-capacity",
|
|
181
|
+
"productionUrl": "https://capacity.topolo.app",
|
|
182
|
+
"services": [
|
|
183
|
+
"capacity"
|
|
184
|
+
],
|
|
185
|
+
"deployTargets": [
|
|
186
|
+
{
|
|
187
|
+
"name": "topolo-capacity",
|
|
188
|
+
"kind": "worker",
|
|
189
|
+
"apiUrl": "https://topolo-capacity.topolo.workers.dev",
|
|
190
|
+
"domains": [
|
|
191
|
+
"capacity.topolo.app",
|
|
192
|
+
"topolo-capacity.topolo.workers.dev"
|
|
193
|
+
],
|
|
194
|
+
"routes": [],
|
|
195
|
+
"serviceId": "capacity"
|
|
196
|
+
}
|
|
197
|
+
]
|
|
198
|
+
},
|
|
176
199
|
"chat": {
|
|
177
200
|
"id": "chat",
|
|
178
201
|
"appDir": "TopoloChat",
|
|
@@ -270,6 +293,30 @@ export const APPLICATIONS = {
|
|
|
270
293
|
}
|
|
271
294
|
]
|
|
272
295
|
},
|
|
296
|
+
"compose": {
|
|
297
|
+
"id": "compose",
|
|
298
|
+
"appDir": "TopoloCompose",
|
|
299
|
+
"name": "topolo-compose",
|
|
300
|
+
"packageName": "@topolo/compose",
|
|
301
|
+
"productionUrl": "https://compose.topolo.app",
|
|
302
|
+
"services": [
|
|
303
|
+
"compose"
|
|
304
|
+
],
|
|
305
|
+
"deployTargets": [
|
|
306
|
+
{
|
|
307
|
+
"name": "topolo-compose",
|
|
308
|
+
"kind": "worker",
|
|
309
|
+
"apiUrl": "https://compose.topolo.app",
|
|
310
|
+
"domains": [
|
|
311
|
+
"compose.topolo.app"
|
|
312
|
+
],
|
|
313
|
+
"routes": [
|
|
314
|
+
"compose.topolo.app"
|
|
315
|
+
],
|
|
316
|
+
"serviceId": "compose"
|
|
317
|
+
}
|
|
318
|
+
]
|
|
319
|
+
},
|
|
273
320
|
"crm": {
|
|
274
321
|
"id": "crm",
|
|
275
322
|
"appDir": "TopoloCRM",
|
|
@@ -687,6 +734,30 @@ export const APPLICATIONS = {
|
|
|
687
734
|
}
|
|
688
735
|
]
|
|
689
736
|
},
|
|
737
|
+
"notify": {
|
|
738
|
+
"id": "notify",
|
|
739
|
+
"appDir": "TopoloNotify",
|
|
740
|
+
"name": "topolo-notify",
|
|
741
|
+
"packageName": "@topolo/notify",
|
|
742
|
+
"productionUrl": "https://notify.topolo.app",
|
|
743
|
+
"services": [
|
|
744
|
+
"notify"
|
|
745
|
+
],
|
|
746
|
+
"deployTargets": [
|
|
747
|
+
{
|
|
748
|
+
"name": "topolo-notify",
|
|
749
|
+
"kind": "worker",
|
|
750
|
+
"apiUrl": "https://notify.topolo.app",
|
|
751
|
+
"domains": [
|
|
752
|
+
"notify.topolo.app"
|
|
753
|
+
],
|
|
754
|
+
"routes": [
|
|
755
|
+
"notify.topolo.app"
|
|
756
|
+
],
|
|
757
|
+
"serviceId": "notify"
|
|
758
|
+
}
|
|
759
|
+
]
|
|
760
|
+
},
|
|
690
761
|
"one": {
|
|
691
762
|
"id": "one",
|
|
692
763
|
"appDir": "TopoloOne",
|
package/src/index.ts
CHANGED
|
@@ -4,8 +4,8 @@ export {
|
|
|
4
4
|
type RequestOptions,
|
|
5
5
|
type CredentialIntrospection,
|
|
6
6
|
type TopoloDebugEvent,
|
|
7
|
-
} from
|
|
8
|
-
export type { TopoloCredential, AgentIdentity } from
|
|
7
|
+
} from "./client.js";
|
|
8
|
+
export type { TopoloCredential, AgentIdentity } from "./auth.js";
|
|
9
9
|
export {
|
|
10
10
|
DEFAULT_SERVICE_URLS,
|
|
11
11
|
APPLICATIONS,
|
|
@@ -29,21 +29,26 @@ export {
|
|
|
29
29
|
type ApplicationRequirementStatus,
|
|
30
30
|
type ApplicationRequirementsAuditReport,
|
|
31
31
|
type ServiceId,
|
|
32
|
-
} from
|
|
32
|
+
} from "./services.js";
|
|
33
33
|
export {
|
|
34
34
|
TopoloSdkError,
|
|
35
35
|
TopoloAuthError,
|
|
36
36
|
TopoloPermissionError,
|
|
37
37
|
TopoloHttpError,
|
|
38
|
-
} from
|
|
39
|
-
export {
|
|
40
|
-
|
|
38
|
+
} from "./errors.js";
|
|
39
|
+
export {
|
|
40
|
+
CrmModule,
|
|
41
|
+
type CrmContactSummary,
|
|
42
|
+
type ListContactsOptions,
|
|
43
|
+
type ListContactsResult,
|
|
44
|
+
} from "./modules/crm.js";
|
|
45
|
+
export { IdentityModule } from "./modules/identity.js";
|
|
41
46
|
export {
|
|
42
47
|
TopoloOAuth,
|
|
43
48
|
type OAuthHelperOptions,
|
|
44
49
|
type DeviceAuthorizationResponse,
|
|
45
50
|
type TokenResponse,
|
|
46
|
-
} from
|
|
51
|
+
} from "./oauth.js";
|
|
47
52
|
export {
|
|
48
53
|
TOPOLO_WIDGET_ENDPOINT,
|
|
49
54
|
TOPOLO_WIDGET_REFRESH_INTERVAL_MS,
|
|
@@ -70,13 +75,15 @@ export {
|
|
|
70
75
|
type TopoloWidgetMetric,
|
|
71
76
|
type TopoloWidgetNotificationItem,
|
|
72
77
|
type TopoloWidgetResponseOptions,
|
|
78
|
+
type TopoloWidgetSnapshotMetadata,
|
|
79
|
+
type TopoloWidgetSnapshotMetric,
|
|
73
80
|
type TopoloWidgetType,
|
|
74
81
|
type TopoloWidgetValidationResult,
|
|
75
|
-
} from
|
|
82
|
+
} from "./widgets.js";
|
|
76
83
|
|
|
77
|
-
import { TopoloClient, type TopoloClientOptions } from
|
|
78
|
-
import { CrmModule } from
|
|
79
|
-
import { IdentityModule } from
|
|
84
|
+
import { TopoloClient, type TopoloClientOptions } from "./client.js";
|
|
85
|
+
import { CrmModule } from "./modules/crm.js";
|
|
86
|
+
import { IdentityModule } from "./modules/identity.js";
|
|
80
87
|
|
|
81
88
|
/**
|
|
82
89
|
* Convenience factory that returns a ready-to-use typed Topolo client plus
|
|
@@ -9,8 +9,10 @@ export const SERVICES = {
|
|
|
9
9
|
bugfix: "https://bugfix.topolo.app",
|
|
10
10
|
bytes: "https://r2-media-manager-worker.topolo.workers.dev",
|
|
11
11
|
calendar: "https://calendar.topolo.app",
|
|
12
|
+
capacity: "https://topolo-capacity.topolo.workers.dev",
|
|
12
13
|
chat: "https://chat.topolo.app",
|
|
13
14
|
commerce: "https://topolo-commerce-api.topolo.workers.dev",
|
|
15
|
+
compose: "https://compose.topolo.app",
|
|
14
16
|
crm: "https://crm-api.topolo.app",
|
|
15
17
|
customer_success: "https://customer-success.topolo.app",
|
|
16
18
|
feed: "https://topolo-feed-api.topolo.app",
|
|
@@ -24,6 +26,7 @@ export const SERVICES = {
|
|
|
24
26
|
mdm: "https://topolo-mdm-api.topolo.app",
|
|
25
27
|
messaging: "https://messages.topolo.app",
|
|
26
28
|
nexus: "https://topolo-nexus-gateway.topolo.workers.dev",
|
|
29
|
+
notify: "https://notify.topolo.app",
|
|
27
30
|
one: "https://one.topolo.app",
|
|
28
31
|
pay: "https://topolo-pay.topolo.workers.dev",
|
|
29
32
|
people: "https://people.topolo.app",
|
|
@@ -43,12 +46,14 @@ export type ServiceId = keyof typeof SERVICES;
|
|
|
43
46
|
|
|
44
47
|
export const PLATFORM_SERVICE_IDS: Partial<Record<ServiceId, string>> = {
|
|
45
48
|
books: "srv_topolo_books",
|
|
49
|
+
compose: "srv_topolo_compose",
|
|
46
50
|
crm: "srv_iCwM4jGXcwlj",
|
|
47
51
|
customer_success: "srv_topolo_customer_success",
|
|
48
52
|
flow: "srv_topolo_flow",
|
|
49
53
|
forms: "srv_topolo_forms",
|
|
50
54
|
insights: "srv_topolo_insights",
|
|
51
55
|
inventory: "srv_topolo_inventory",
|
|
56
|
+
notify: "svc_topolo_notify",
|
|
52
57
|
people: "srv_topolo_people",
|
|
53
58
|
sign: "srv_topolo_sign",
|
|
54
59
|
survey: "srv_topolo_survey",
|
package/src/services.test.ts
CHANGED
|
@@ -18,10 +18,12 @@ const EXPECTED_APPLICATION_IDS = [
|
|
|
18
18
|
'bugfix',
|
|
19
19
|
'bytes',
|
|
20
20
|
'calendar',
|
|
21
|
+
'capacity',
|
|
21
22
|
'chat',
|
|
22
23
|
'cli',
|
|
23
24
|
'cloudcontrol',
|
|
24
25
|
'commerce',
|
|
26
|
+
'compose',
|
|
25
27
|
'crm',
|
|
26
28
|
'customer_success',
|
|
27
29
|
'developers',
|
|
@@ -38,6 +40,7 @@ const EXPECTED_APPLICATION_IDS = [
|
|
|
38
40
|
'mdm',
|
|
39
41
|
'messaging',
|
|
40
42
|
'nexus',
|
|
43
|
+
'notify',
|
|
41
44
|
'one',
|
|
42
45
|
'pay',
|
|
43
46
|
'people',
|
package/src/widgets.test.ts
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
2
|
import {
|
|
3
3
|
createTopoloWidgetError,
|
|
4
4
|
createTopoloWidgetResponse,
|
|
5
5
|
TOPOLO_WIDGET_ENDPOINT,
|
|
6
6
|
TOPOLO_WIDGET_REFRESH_INTERVAL_MS,
|
|
7
7
|
validateTopoloWidgetResponse,
|
|
8
|
-
} from
|
|
8
|
+
} from "./widgets.js";
|
|
9
9
|
|
|
10
|
-
describe(
|
|
11
|
-
it(
|
|
12
|
-
expect(TOPOLO_WIDGET_ENDPOINT).toBe(
|
|
10
|
+
describe("Topolo widget contract", () => {
|
|
11
|
+
it("exports the platform widget endpoint defaults", () => {
|
|
12
|
+
expect(TOPOLO_WIDGET_ENDPOINT).toBe("/api/widget");
|
|
13
13
|
expect(TOPOLO_WIDGET_REFRESH_INTERVAL_MS).toBe(60_000);
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
it(
|
|
16
|
+
it("stamps service metadata and timestamp onto widget drafts", () => {
|
|
17
17
|
const response = createTopoloWidgetResponse({
|
|
18
|
-
serviceId:
|
|
19
|
-
serviceName:
|
|
20
|
-
now:
|
|
18
|
+
serviceId: "srv_bytes",
|
|
19
|
+
serviceName: "Bytes",
|
|
20
|
+
now: "2026-04-25T00:00:00.000Z",
|
|
21
21
|
widgets: [
|
|
22
22
|
{
|
|
23
|
-
type:
|
|
24
|
-
stats: [{ label:
|
|
23
|
+
type: "stats",
|
|
24
|
+
stats: [{ key: "files", label: "Files", value: 12 }],
|
|
25
25
|
},
|
|
26
26
|
],
|
|
27
27
|
});
|
|
@@ -30,63 +30,63 @@ describe('Topolo widget contract', () => {
|
|
|
30
30
|
success: true,
|
|
31
31
|
widgets: [
|
|
32
32
|
{
|
|
33
|
-
serviceId:
|
|
34
|
-
serviceName:
|
|
35
|
-
lastUpdated:
|
|
36
|
-
type:
|
|
37
|
-
stats: [{ label:
|
|
33
|
+
serviceId: "srv_bytes",
|
|
34
|
+
serviceName: "Bytes",
|
|
35
|
+
lastUpdated: "2026-04-25T00:00:00.000Z",
|
|
36
|
+
type: "stats",
|
|
37
|
+
stats: [{ key: "files", label: "Files", value: 12 }],
|
|
38
38
|
},
|
|
39
39
|
],
|
|
40
40
|
});
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
it(
|
|
43
|
+
it("keeps explicit widget metadata when an app needs multiple service cards", () => {
|
|
44
44
|
const response = createTopoloWidgetResponse({
|
|
45
|
-
serviceId:
|
|
46
|
-
serviceName:
|
|
47
|
-
now:
|
|
45
|
+
serviceId: "srv_platform",
|
|
46
|
+
serviceName: "Platform",
|
|
47
|
+
now: "2026-04-25T00:00:00.000Z",
|
|
48
48
|
widgets: [
|
|
49
49
|
{
|
|
50
|
-
serviceId:
|
|
51
|
-
serviceName:
|
|
52
|
-
lastUpdated:
|
|
53
|
-
type:
|
|
54
|
-
stats: [{ label:
|
|
50
|
+
serviceId: "srv_crm",
|
|
51
|
+
serviceName: "CRM",
|
|
52
|
+
lastUpdated: "2026-04-24T23:00:00.000Z",
|
|
53
|
+
type: "stats",
|
|
54
|
+
stats: [{ label: "Contacts", value: 3 }],
|
|
55
55
|
},
|
|
56
56
|
],
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
expect(response.widgets[0]).toMatchObject({
|
|
60
|
-
serviceId:
|
|
61
|
-
serviceName:
|
|
62
|
-
lastUpdated:
|
|
60
|
+
serviceId: "srv_crm",
|
|
61
|
+
serviceName: "CRM",
|
|
62
|
+
lastUpdated: "2026-04-24T23:00:00.000Z",
|
|
63
63
|
});
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
it(
|
|
67
|
-
expect(createTopoloWidgetError(
|
|
66
|
+
it("returns a stable error response shape", () => {
|
|
67
|
+
expect(createTopoloWidgetError("not configured")).toEqual({
|
|
68
68
|
success: false,
|
|
69
69
|
widgets: [],
|
|
70
|
-
error:
|
|
70
|
+
error: "not configured",
|
|
71
71
|
});
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
it(
|
|
74
|
+
it("validates a compliant response", () => {
|
|
75
75
|
const response = createTopoloWidgetResponse({
|
|
76
|
-
serviceId:
|
|
77
|
-
serviceName:
|
|
78
|
-
now:
|
|
76
|
+
serviceId: "srv_crm",
|
|
77
|
+
serviceName: "CRM",
|
|
78
|
+
now: "2026-04-25T00:00:00.000Z",
|
|
79
79
|
widgets: [
|
|
80
80
|
{
|
|
81
|
-
type:
|
|
81
|
+
type: "notifications",
|
|
82
82
|
unreadCount: 1,
|
|
83
83
|
notifications: [
|
|
84
84
|
{
|
|
85
|
-
id:
|
|
86
|
-
title:
|
|
87
|
-
message:
|
|
88
|
-
type:
|
|
89
|
-
timestamp:
|
|
85
|
+
id: "n_1",
|
|
86
|
+
title: "New lead",
|
|
87
|
+
message: "A lead was created.",
|
|
88
|
+
type: "info",
|
|
89
|
+
timestamp: "2026-04-25T00:00:00.000Z",
|
|
90
90
|
read: false,
|
|
91
91
|
},
|
|
92
92
|
],
|
|
@@ -100,24 +100,63 @@ describe('Topolo widget contract', () => {
|
|
|
100
100
|
});
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
-
it(
|
|
103
|
+
it("keeps optional snapshot metadata for configurable TopoloOne datapoints", () => {
|
|
104
|
+
const response = createTopoloWidgetResponse({
|
|
105
|
+
serviceId: "srv_admin",
|
|
106
|
+
serviceName: "Admin",
|
|
107
|
+
now: "2026-04-28T00:00:00.000Z",
|
|
108
|
+
widgets: [
|
|
109
|
+
{
|
|
110
|
+
type: "stats",
|
|
111
|
+
stats: [{ label: "Team Members", value: 5 }],
|
|
112
|
+
snapshot: {
|
|
113
|
+
presetKey: "admin_morning_snapshot",
|
|
114
|
+
defaultMetricKeys: ["team_members"],
|
|
115
|
+
availableMetrics: [
|
|
116
|
+
{
|
|
117
|
+
key: "team_members",
|
|
118
|
+
label: "Team Members",
|
|
119
|
+
value: 5,
|
|
120
|
+
unit: "count",
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
expect(validateTopoloWidgetResponse(response)).toEqual({
|
|
129
|
+
ok: true,
|
|
130
|
+
errors: [],
|
|
131
|
+
});
|
|
132
|
+
expect(response.widgets[0]).toMatchObject({
|
|
133
|
+
snapshot: {
|
|
134
|
+
defaultMetricKeys: ["team_members"],
|
|
135
|
+
availableMetrics: [
|
|
136
|
+
{ key: "team_members", label: "Team Members", value: 5 },
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("reports missing metadata and unsupported widget types", () => {
|
|
104
143
|
const result = validateTopoloWidgetResponse({
|
|
105
144
|
success: true,
|
|
106
145
|
widgets: [
|
|
107
146
|
{
|
|
108
|
-
serviceId:
|
|
109
|
-
serviceName:
|
|
110
|
-
lastUpdated:
|
|
111
|
-
type:
|
|
147
|
+
serviceId: "",
|
|
148
|
+
serviceName: "Bad App",
|
|
149
|
+
lastUpdated: "",
|
|
150
|
+
type: "calendar",
|
|
112
151
|
},
|
|
113
152
|
],
|
|
114
153
|
});
|
|
115
154
|
|
|
116
155
|
expect(result.ok).toBe(false);
|
|
117
156
|
expect(result.errors).toEqual([
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
157
|
+
"widgets[0].serviceId must be a non-empty string",
|
|
158
|
+
"widgets[0].lastUpdated must be a non-empty string",
|
|
159
|
+
"widgets[0].type must be a supported Topolo widget type",
|
|
121
160
|
]);
|
|
122
161
|
});
|
|
123
162
|
});
|