@topolo/sdk 0.4.10 → 0.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 +1 -1
- package/dist/index.cjs +134 -135
- package/dist/index.d.cts +116 -118
- package/dist/index.d.ts +116 -118
- package/dist/index.js +134 -135
- package/package.json +1 -1
- package/src/application-requirements.ts +3 -3
- package/src/applications.generated.ts +98 -98
- package/src/client.test.ts +49 -49
- package/src/client.ts +40 -40
- package/src/index.test.ts +3 -3
- package/src/modules/crm.test.ts +4 -4
- package/src/modules/identity.test.ts +4 -4
- package/src/modules/p2p.test.ts +4 -8
- package/src/modules/p2p.ts +0 -3
- package/src/services.test.ts +15 -15
- package/src/services.ts +2 -2
- package/src/widgets.test.ts +14 -14
- package/src/widgets.ts +8 -8
|
@@ -9,7 +9,7 @@ function stubFetch(responses: Array<{ status?: number; body?: unknown }>) {
|
|
|
9
9
|
const impl = vi.fn(async (url: string, init: RequestInit) => {
|
|
10
10
|
calls.push({ url, init });
|
|
11
11
|
if (String(url).includes('/api/services/by-slug/')) {
|
|
12
|
-
return new Response(JSON.stringify({ success: true, data: {
|
|
12
|
+
return new Response(JSON.stringify({ success: true, data: { app_id: 'service_auth' } }), {
|
|
13
13
|
status: 200,
|
|
14
14
|
headers: { 'Content-Type': 'application/json' },
|
|
15
15
|
});
|
|
@@ -48,12 +48,12 @@ function clientWith(
|
|
|
48
48
|
credential,
|
|
49
49
|
agent: AGENT,
|
|
50
50
|
fetch: impl,
|
|
51
|
-
|
|
51
|
+
appIds: { auth: 'service_auth' },
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
beforeEach(() => {
|
|
56
|
-
TopoloClient.
|
|
56
|
+
TopoloClient.clearAppIdCache();
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
describe('IdentityModule.whoami', () => {
|
|
@@ -93,7 +93,7 @@ describe('IdentityModule.whoami', () => {
|
|
|
93
93
|
{
|
|
94
94
|
body: {
|
|
95
95
|
credentialType: 'api_key',
|
|
96
|
-
user: { id: '
|
|
96
|
+
user: { id: 'usr_1', email: 'user@example.com' },
|
|
97
97
|
organization: { id: 'org_2', slug: 'beta' },
|
|
98
98
|
},
|
|
99
99
|
},
|
package/src/modules/p2p.test.ts
CHANGED
|
@@ -9,7 +9,7 @@ function stubFetch(responses: Array<{ status?: number; body?: unknown }>) {
|
|
|
9
9
|
const impl = vi.fn(async (url: string, init: RequestInit) => {
|
|
10
10
|
calls.push({ url, init });
|
|
11
11
|
if (String(url).includes('/api/services/by-slug/')) {
|
|
12
|
-
return new Response(JSON.stringify({ success: true, data: {
|
|
12
|
+
return new Response(JSON.stringify({ success: true, data: { app_id: 'service_p2p' } }), {
|
|
13
13
|
status: 200,
|
|
14
14
|
headers: { 'Content-Type': 'application/json' },
|
|
15
15
|
});
|
|
@@ -19,14 +19,14 @@ function stubFetch(responses: Array<{ status?: number; body?: unknown }>) {
|
|
|
19
19
|
data: {
|
|
20
20
|
services: [
|
|
21
21
|
{
|
|
22
|
-
|
|
22
|
+
app_id: 'app_p2p',
|
|
23
23
|
app_slug: 'topolo-p2p',
|
|
24
24
|
name: 'Topolo P2P',
|
|
25
25
|
api_base_url: 'https://p2p.topolo.app',
|
|
26
26
|
launch_url: 'https://p2p.topolo.app',
|
|
27
27
|
surface_kind: 'api',
|
|
28
28
|
status: 'active',
|
|
29
|
-
granted_permissions: ['
|
|
29
|
+
granted_permissions: ['app_p2p.directory:read'],
|
|
30
30
|
},
|
|
31
31
|
],
|
|
32
32
|
},
|
|
@@ -61,7 +61,7 @@ function clientWith(impl: typeof fetch) {
|
|
|
61
61
|
|
|
62
62
|
describe('P2PModule.listDirectory', () => {
|
|
63
63
|
beforeEach(() => {
|
|
64
|
-
TopoloClient.
|
|
64
|
+
TopoloClient.clearAppIdCache();
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
it('GETs /api/directory/capabilities and unwraps the envelope', async () => {
|
|
@@ -75,7 +75,6 @@ describe('P2PModule.listDirectory', () => {
|
|
|
75
75
|
capability_id: 'cap_1',
|
|
76
76
|
publisher_org_id: 'org_a',
|
|
77
77
|
app_id: 'app_x',
|
|
78
|
-
service_id: 'service_x',
|
|
79
78
|
name: 'Demo',
|
|
80
79
|
description: 'A capability',
|
|
81
80
|
category: 'other',
|
|
@@ -117,7 +116,6 @@ describe('P2PModule.publishCapability', () => {
|
|
|
117
116
|
capability_id: 'cap_z',
|
|
118
117
|
publisher_org_id: 'org_p',
|
|
119
118
|
app_id: 'app_x',
|
|
120
|
-
service_id: 'service_x',
|
|
121
119
|
name: 'Demo',
|
|
122
120
|
description: 'A capability',
|
|
123
121
|
category: 'other',
|
|
@@ -140,7 +138,6 @@ describe('P2PModule.publishCapability', () => {
|
|
|
140
138
|
capability_id: 'cap_z',
|
|
141
139
|
publisher_org_id: 'org_p',
|
|
142
140
|
app_id: 'app_x',
|
|
143
|
-
service_id: 'service_x',
|
|
144
141
|
name: 'Demo',
|
|
145
142
|
description: 'A capability',
|
|
146
143
|
category: 'other',
|
|
@@ -174,7 +171,6 @@ describe('P2PModule.publishCapability', () => {
|
|
|
174
171
|
capability_id: 'cap_other',
|
|
175
172
|
publisher_org_id: 'org_target',
|
|
176
173
|
app_id: 'app_x',
|
|
177
|
-
service_id: 'service_x',
|
|
178
174
|
name: 'Demo',
|
|
179
175
|
description: '...',
|
|
180
176
|
category: 'other',
|
package/src/modules/p2p.ts
CHANGED
|
@@ -6,7 +6,6 @@ export interface P2PCapability {
|
|
|
6
6
|
capability_id: string;
|
|
7
7
|
publisher_org_id: string;
|
|
8
8
|
app_id: string;
|
|
9
|
-
service_id: string;
|
|
10
9
|
name: string;
|
|
11
10
|
description: string;
|
|
12
11
|
category: string;
|
|
@@ -26,7 +25,6 @@ export interface PublishCapabilityInput {
|
|
|
26
25
|
capability_id: string;
|
|
27
26
|
publisher_org_id: string;
|
|
28
27
|
app_id: string;
|
|
29
|
-
service_id: string;
|
|
30
28
|
name: string;
|
|
31
29
|
description: string;
|
|
32
30
|
category: string;
|
|
@@ -231,7 +229,6 @@ export class P2PModule {
|
|
|
231
229
|
capability_id: input.capability_id,
|
|
232
230
|
publisher_org_id: input.publisher_org_id,
|
|
233
231
|
app_id: input.app_id,
|
|
234
|
-
service_id: input.service_id,
|
|
235
232
|
name: input.name,
|
|
236
233
|
description: input.description,
|
|
237
234
|
category: input.category,
|
package/src/services.test.ts
CHANGED
|
@@ -93,14 +93,14 @@ describe('service registry', () => {
|
|
|
93
93
|
Object.values(APPLICATIONS).flatMap((application) => [...application.services]),
|
|
94
94
|
);
|
|
95
95
|
|
|
96
|
-
for (const
|
|
97
|
-
expect(applicationServices.has(
|
|
96
|
+
for (const appId of Object.keys(DEFAULT_SERVICE_URLS)) {
|
|
97
|
+
expect(applicationServices.has(appId)).toBe(true);
|
|
98
98
|
}
|
|
99
99
|
});
|
|
100
100
|
|
|
101
101
|
it('resolves services from explicit catalog API URLs', () => {
|
|
102
102
|
expect(resolveCatalogServiceUrl({
|
|
103
|
-
|
|
103
|
+
appId: 'app_crm',
|
|
104
104
|
slug: 'topolo-crm',
|
|
105
105
|
name: 'Topolo CRM',
|
|
106
106
|
launchUrl: 'https://crm.topolo.app',
|
|
@@ -110,7 +110,7 @@ describe('service registry', () => {
|
|
|
110
110
|
|
|
111
111
|
it('does not infer callable URLs from launcher URLs', () => {
|
|
112
112
|
expect(resolveCatalogServiceUrl({
|
|
113
|
-
|
|
113
|
+
appId: 'app_crm',
|
|
114
114
|
slug: 'topolo-crm',
|
|
115
115
|
name: 'Topolo CRM',
|
|
116
116
|
launchUrl: 'https://crm.stg.topolo.us',
|
|
@@ -119,7 +119,7 @@ describe('service registry', () => {
|
|
|
119
119
|
|
|
120
120
|
it('prefers explicit catalog API URLs over launch URLs', () => {
|
|
121
121
|
expect(resolveCatalogServiceUrl({
|
|
122
|
-
|
|
122
|
+
appId: 'app_future',
|
|
123
123
|
slug: 'topolo-future',
|
|
124
124
|
name: 'Future',
|
|
125
125
|
launchUrl: 'https://future.topolo.app',
|
|
@@ -137,11 +137,11 @@ describe('application catalog', () => {
|
|
|
137
137
|
const registeredServices = new Set<string>(Object.keys(DEFAULT_SERVICE_URLS));
|
|
138
138
|
|
|
139
139
|
for (const application of Object.values(APPLICATIONS)) {
|
|
140
|
-
for (const
|
|
141
|
-
expect(registeredServices.has(
|
|
140
|
+
for (const appId of application.services) {
|
|
141
|
+
expect(registeredServices.has(appId)).toBe(true);
|
|
142
142
|
}
|
|
143
143
|
for (const target of application.deployTargets) {
|
|
144
|
-
if (target.
|
|
144
|
+
if (target.appId) expect(registeredServices.has(target.appId)).toBe(true);
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
});
|
|
@@ -152,19 +152,19 @@ describe('application catalog', () => {
|
|
|
152
152
|
// studio web worker (web.topolo.app/api); the API worker is de-routed and
|
|
153
153
|
// no longer carries a public service registration.
|
|
154
154
|
expect(
|
|
155
|
-
APPLICATIONS.web.deployTargets.find((target) => target.name === 'topolo-web-studio')?.
|
|
155
|
+
APPLICATIONS.web.deployTargets.find((target) => target.name === 'topolo-web-studio')?.appId,
|
|
156
156
|
).toBe('web');
|
|
157
157
|
expect(
|
|
158
|
-
APPLICATIONS.web.deployTargets.find((target) => target.name === 'topolo-web-api')?.
|
|
158
|
+
APPLICATIONS.web.deployTargets.find((target) => target.name === 'topolo-web-api')?.appId,
|
|
159
159
|
).toBeNull();
|
|
160
160
|
// topolo-web-runtime (Astro, sites.topolo.app) deploys from its own wrangler
|
|
161
161
|
// config and is intentionally listed as non-callable, so it can never be an
|
|
162
162
|
// agent-callable service.
|
|
163
163
|
expect(
|
|
164
|
-
APPLICATIONS.web.deployTargets.find((target) => target.name === 'topolo-web-runtime')?.
|
|
164
|
+
APPLICATIONS.web.deployTargets.find((target) => target.name === 'topolo-web-runtime')?.appId,
|
|
165
165
|
).toBeNull();
|
|
166
166
|
const callableTargetNames = APPLICATIONS.web.deployTargets
|
|
167
|
-
.filter((target) => target.
|
|
167
|
+
.filter((target) => target.appId)
|
|
168
168
|
.map((target) => target.name);
|
|
169
169
|
expect(callableTargetNames).not.toContain('topolo-web-runtime');
|
|
170
170
|
});
|
|
@@ -174,14 +174,14 @@ describe('application catalog', () => {
|
|
|
174
174
|
// Front-door standardization: the callable `feed` service is served by the
|
|
175
175
|
// feed web worker (feed.topolo.app/api); the API worker is de-routed.
|
|
176
176
|
expect(
|
|
177
|
-
APPLICATIONS.feed.deployTargets.find((target) => target.name === 'topolo-feed-web')?.
|
|
177
|
+
APPLICATIONS.feed.deployTargets.find((target) => target.name === 'topolo-feed-web')?.appId,
|
|
178
178
|
).toBe('feed');
|
|
179
179
|
expect(
|
|
180
|
-
APPLICATIONS.feed.deployTargets.find((target) => target.name === 'topolo-feed-api')?.
|
|
180
|
+
APPLICATIONS.feed.deployTargets.find((target) => target.name === 'topolo-feed-api')?.appId,
|
|
181
181
|
).toBeNull();
|
|
182
182
|
// The analytics ingestion worker keeps its own host and is non-callable.
|
|
183
183
|
expect(
|
|
184
|
-
APPLICATIONS.feed.deployTargets.find((target) => target.name === 'topolo-feed-analytics-api')?.
|
|
184
|
+
APPLICATIONS.feed.deployTargets.find((target) => target.name === 'topolo-feed-analytics-api')?.appId,
|
|
185
185
|
).toBeNull();
|
|
186
186
|
});
|
|
187
187
|
});
|
package/src/services.ts
CHANGED
|
@@ -48,7 +48,7 @@ export type { ApplicationCatalogEntry, ApplicationDeployTarget, ApplicationId };
|
|
|
48
48
|
export const DEFAULT_SERVICE_URLS: Record<ServiceId, string> = SERVICES;
|
|
49
49
|
|
|
50
50
|
export interface ServiceCatalogUrlEntry {
|
|
51
|
-
|
|
51
|
+
appId?: string | null;
|
|
52
52
|
slug?: string | null;
|
|
53
53
|
name?: string | null;
|
|
54
54
|
apiBaseUrl?: string | null;
|
|
@@ -85,7 +85,7 @@ export function resolveServiceUrl(
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
function catalogServiceKeyCandidates(entry: ServiceCatalogUrlEntry, preferred?: string): string[] {
|
|
88
|
-
const values = [preferred, entry.slug, entry.name, entry.
|
|
88
|
+
const values = [preferred, entry.slug, entry.name, entry.appId].filter(
|
|
89
89
|
(value): value is string => typeof value === 'string' && value.trim().length > 0,
|
|
90
90
|
);
|
|
91
91
|
const keys = new Set<string>();
|
package/src/widgets.test.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "./widgets.js";
|
|
14
14
|
|
|
15
15
|
const BASE = {
|
|
16
|
-
|
|
16
|
+
appId: "service_x",
|
|
17
17
|
serviceName: "X",
|
|
18
18
|
lastUpdated: "2026-04-25T00:00:00.000Z",
|
|
19
19
|
snapshot: { summary: "A summary that is long enough to read." },
|
|
@@ -27,7 +27,7 @@ describe("Topolo widget contract", () => {
|
|
|
27
27
|
|
|
28
28
|
it("stamps service metadata and timestamp onto widget drafts", () => {
|
|
29
29
|
const response = createTopoloWidgetResponse({
|
|
30
|
-
|
|
30
|
+
appId: "service_bytes",
|
|
31
31
|
serviceName: "Bytes",
|
|
32
32
|
now: "2026-04-25T00:00:00.000Z",
|
|
33
33
|
widgets: [
|
|
@@ -45,7 +45,7 @@ describe("Topolo widget contract", () => {
|
|
|
45
45
|
success: true,
|
|
46
46
|
widgets: [
|
|
47
47
|
{
|
|
48
|
-
|
|
48
|
+
appId: "service_bytes",
|
|
49
49
|
serviceName: "Bytes",
|
|
50
50
|
lastUpdated: "2026-04-25T00:00:00.000Z",
|
|
51
51
|
type: "stats",
|
|
@@ -60,12 +60,12 @@ describe("Topolo widget contract", () => {
|
|
|
60
60
|
|
|
61
61
|
it("keeps explicit widget metadata when an app needs multiple service cards", () => {
|
|
62
62
|
const response = createTopoloWidgetResponse({
|
|
63
|
-
|
|
63
|
+
appId: "service_platform",
|
|
64
64
|
serviceName: "Platform",
|
|
65
65
|
now: "2026-04-25T00:00:00.000Z",
|
|
66
66
|
widgets: [
|
|
67
67
|
{
|
|
68
|
-
|
|
68
|
+
appId: "service_crm",
|
|
69
69
|
serviceName: "CRM",
|
|
70
70
|
lastUpdated: "2026-04-24T23:00:00.000Z",
|
|
71
71
|
type: "stats",
|
|
@@ -78,7 +78,7 @@ describe("Topolo widget contract", () => {
|
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
expect(response.widgets[0]).toMatchObject({
|
|
81
|
-
|
|
81
|
+
appId: "service_crm",
|
|
82
82
|
serviceName: "CRM",
|
|
83
83
|
lastUpdated: "2026-04-24T23:00:00.000Z",
|
|
84
84
|
});
|
|
@@ -94,7 +94,7 @@ describe("Topolo widget contract", () => {
|
|
|
94
94
|
|
|
95
95
|
it("validates a compliant response", () => {
|
|
96
96
|
const response = createTopoloWidgetResponse({
|
|
97
|
-
|
|
97
|
+
appId: "service_crm",
|
|
98
98
|
serviceName: "CRM",
|
|
99
99
|
now: "2026-04-25T00:00:00.000Z",
|
|
100
100
|
widgets: [
|
|
@@ -126,7 +126,7 @@ describe("Topolo widget contract", () => {
|
|
|
126
126
|
|
|
127
127
|
it("keeps required snapshot metadata for configurable TopoloOne datapoints", () => {
|
|
128
128
|
const response = createTopoloWidgetResponse({
|
|
129
|
-
|
|
129
|
+
appId: "service_admin",
|
|
130
130
|
serviceName: "Admin",
|
|
131
131
|
now: "2026-04-28T00:00:00.000Z",
|
|
132
132
|
widgets: [
|
|
@@ -172,7 +172,7 @@ describe("Topolo widget contract", () => {
|
|
|
172
172
|
success: true,
|
|
173
173
|
widgets: [
|
|
174
174
|
{
|
|
175
|
-
|
|
175
|
+
appId: "",
|
|
176
176
|
serviceName: "Bad App",
|
|
177
177
|
lastUpdated: "",
|
|
178
178
|
type: "calendar",
|
|
@@ -182,7 +182,7 @@ describe("Topolo widget contract", () => {
|
|
|
182
182
|
|
|
183
183
|
expect(result.ok).toBe(false);
|
|
184
184
|
expect(result.errors).toEqual([
|
|
185
|
-
"widgets[0].
|
|
185
|
+
"widgets[0].appId must be a non-empty string",
|
|
186
186
|
"widgets[0].lastUpdated must be a non-empty string",
|
|
187
187
|
"widgets[0].snapshot must be an object",
|
|
188
188
|
"widgets[0].type must be a supported Topolo widget type",
|
|
@@ -213,7 +213,7 @@ describe("Topolo widget contract", () => {
|
|
|
213
213
|
|
|
214
214
|
it("derives lastUpdated from a Date instance passed as now", () => {
|
|
215
215
|
const response = createTopoloWidgetResponse({
|
|
216
|
-
|
|
216
|
+
appId: "service_x",
|
|
217
217
|
serviceName: "X",
|
|
218
218
|
now: new Date("2026-05-01T12:00:00.000Z"),
|
|
219
219
|
widgets: [
|
|
@@ -230,7 +230,7 @@ describe("Topolo widget contract", () => {
|
|
|
230
230
|
it("generates a current timestamp when now is omitted", () => {
|
|
231
231
|
const before = Date.now();
|
|
232
232
|
const response = createTopoloWidgetResponse({
|
|
233
|
-
|
|
233
|
+
appId: "service_x",
|
|
234
234
|
serviceName: "X",
|
|
235
235
|
widgets: [
|
|
236
236
|
{
|
|
@@ -362,7 +362,7 @@ describe("Topolo widget contract", () => {
|
|
|
362
362
|
success: true,
|
|
363
363
|
widgets: [
|
|
364
364
|
{
|
|
365
|
-
|
|
365
|
+
appId: "s",
|
|
366
366
|
serviceName: "n",
|
|
367
367
|
lastUpdated: "2026-04-25T00:00:00.000Z",
|
|
368
368
|
type: "stats",
|
|
@@ -398,7 +398,7 @@ describe("Topolo widget contract", () => {
|
|
|
398
398
|
success: true,
|
|
399
399
|
widgets: [
|
|
400
400
|
{
|
|
401
|
-
|
|
401
|
+
appId: "service_bad",
|
|
402
402
|
serviceName: "Bad App",
|
|
403
403
|
lastUpdated: "2026-04-25T00:00:00.000Z",
|
|
404
404
|
type: "stats",
|
package/src/widgets.ts
CHANGED
|
@@ -9,7 +9,7 @@ export type TopoloWidgetType =
|
|
|
9
9
|
| "chart";
|
|
10
10
|
|
|
11
11
|
export interface TopoloWidgetBase {
|
|
12
|
-
|
|
12
|
+
appId: string;
|
|
13
13
|
serviceName: string;
|
|
14
14
|
serviceIcon?: string;
|
|
15
15
|
type: TopoloWidgetType;
|
|
@@ -114,9 +114,9 @@ export type TopoloWidget =
|
|
|
114
114
|
|
|
115
115
|
type TopoloWidgetWithOptionalMetadata<T extends TopoloWidget> = Omit<
|
|
116
116
|
T,
|
|
117
|
-
"
|
|
117
|
+
"appId" | "serviceName" | "lastUpdated"
|
|
118
118
|
> &
|
|
119
|
-
Partial<Pick<TopoloWidgetBase, "
|
|
119
|
+
Partial<Pick<TopoloWidgetBase, "appId" | "serviceName" | "lastUpdated">>;
|
|
120
120
|
|
|
121
121
|
export type TopoloWidgetDraft = TopoloWidget extends infer Widget
|
|
122
122
|
? Widget extends TopoloWidget
|
|
@@ -131,7 +131,7 @@ export interface TopoloWidgetApiResponse {
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
export interface TopoloWidgetResponseOptions {
|
|
134
|
-
|
|
134
|
+
appId: string;
|
|
135
135
|
serviceName: string;
|
|
136
136
|
widgets: TopoloWidgetDraft[];
|
|
137
137
|
now?: Date | string;
|
|
@@ -143,7 +143,7 @@ export interface TopoloWidgetValidationResult {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
export interface TopoloWidgetConfig {
|
|
146
|
-
|
|
146
|
+
appId: string;
|
|
147
147
|
serviceName: string;
|
|
148
148
|
baseUrl: string;
|
|
149
149
|
widgetEndpoint: string;
|
|
@@ -182,7 +182,7 @@ export function defineTopoloChartWidget(
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
export function createTopoloWidgetResponse({
|
|
185
|
-
|
|
185
|
+
appId,
|
|
186
186
|
serviceName,
|
|
187
187
|
widgets,
|
|
188
188
|
now,
|
|
@@ -193,7 +193,7 @@ export function createTopoloWidgetResponse({
|
|
|
193
193
|
success: true,
|
|
194
194
|
widgets: widgets.map((widget) => ({
|
|
195
195
|
...widget,
|
|
196
|
-
|
|
196
|
+
appId: widget.appId?.trim() || appId,
|
|
197
197
|
serviceName: widget.serviceName?.trim() || serviceName,
|
|
198
198
|
lastUpdated: widget.lastUpdated?.trim() || lastUpdated,
|
|
199
199
|
})) as TopoloWidget[],
|
|
@@ -249,7 +249,7 @@ function validateWidget(widget: unknown, path: string, errors: string[]) {
|
|
|
249
249
|
return;
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
validateNonEmptyString(widget.
|
|
252
|
+
validateNonEmptyString(widget.appId, `${path}.appId`, errors);
|
|
253
253
|
validateNonEmptyString(widget.serviceName, `${path}.serviceName`, errors);
|
|
254
254
|
validateNonEmptyString(widget.lastUpdated, `${path}.lastUpdated`, errors);
|
|
255
255
|
validateSnapshotMetadata(widget.snapshot, `${path}.snapshot`, errors);
|