@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.
@@ -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: { service_id: 'service_auth' } }), {
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
- serviceIds: { auth: 'service_auth' },
51
+ appIds: { auth: 'service_auth' },
52
52
  });
53
53
  }
54
54
 
55
55
  beforeEach(() => {
56
- TopoloClient.clearServiceIdCache();
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: 'svc_1', email: 'svc@example.com' },
96
+ user: { id: 'usr_1', email: 'user@example.com' },
97
97
  organization: { id: 'org_2', slug: 'beta' },
98
98
  },
99
99
  },
@@ -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: { service_id: 'service_p2p' } }), {
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
- service_id: 'srv_p2p',
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: ['srv_p2p.directory:read'],
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.clearServiceIdCache();
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',
@@ -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,
@@ -93,14 +93,14 @@ describe('service registry', () => {
93
93
  Object.values(APPLICATIONS).flatMap((application) => [...application.services]),
94
94
  );
95
95
 
96
- for (const serviceId of Object.keys(DEFAULT_SERVICE_URLS)) {
97
- expect(applicationServices.has(serviceId)).toBe(true);
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
- serviceId: 'srv_crm',
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
- serviceId: 'srv_crm',
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
- serviceId: 'srv_future',
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 serviceId of application.services) {
141
- expect(registeredServices.has(serviceId)).toBe(true);
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.serviceId) expect(registeredServices.has(target.serviceId)).toBe(true);
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')?.serviceId,
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')?.serviceId,
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')?.serviceId,
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.serviceId)
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')?.serviceId,
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')?.serviceId,
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')?.serviceId,
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
- serviceId?: string | null;
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.serviceId].filter(
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>();
@@ -13,7 +13,7 @@ import {
13
13
  } from "./widgets.js";
14
14
 
15
15
  const BASE = {
16
- serviceId: "service_x",
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
- serviceId: "service_bytes",
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
- serviceId: "service_bytes",
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
- serviceId: "service_platform",
63
+ appId: "service_platform",
64
64
  serviceName: "Platform",
65
65
  now: "2026-04-25T00:00:00.000Z",
66
66
  widgets: [
67
67
  {
68
- serviceId: "service_crm",
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
- serviceId: "service_crm",
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
- serviceId: "service_crm",
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
- serviceId: "service_admin",
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
- serviceId: "",
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].serviceId must be a non-empty string",
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
- serviceId: "service_x",
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
- serviceId: "service_x",
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
- serviceId: "s",
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
- serviceId: "service_bad",
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
- serviceId: string;
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
- "serviceId" | "serviceName" | "lastUpdated"
117
+ "appId" | "serviceName" | "lastUpdated"
118
118
  > &
119
- Partial<Pick<TopoloWidgetBase, "serviceId" | "serviceName" | "lastUpdated">>;
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
- serviceId: string;
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
- serviceId: string;
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
- serviceId,
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
- serviceId: widget.serviceId?.trim() || serviceId,
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.serviceId, `${path}.serviceId`, errors);
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);