@topolo/sdk 0.3.0 → 0.4.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.
@@ -1,4 +1,4 @@
1
- import { describe, expect, it, vi } from 'vitest';
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
2
  import { TopoloClient } from '../client.js';
3
3
  import { P2PModule } from './p2p.js';
4
4
 
@@ -8,6 +8,12 @@ function stubFetch(responses: Array<{ status?: number; body?: unknown }>) {
8
8
  const calls: Array<{ url: string; init: RequestInit }> = [];
9
9
  const impl = vi.fn(async (url: string, init: RequestInit) => {
10
10
  calls.push({ url, init });
11
+ if (String(url).includes('/api/services/by-slug/')) {
12
+ return new Response(JSON.stringify({ success: true, data: { service_id: 'service_p2p' } }), {
13
+ status: 200,
14
+ headers: { 'Content-Type': 'application/json' },
15
+ });
16
+ }
11
17
  const next = responses.shift() ?? { status: 200, body: { success: true, data: {} } };
12
18
  return new Response(JSON.stringify(next.body ?? { success: true, data: {} }), {
13
19
  status: next.status ?? 200,
@@ -17,6 +23,10 @@ function stubFetch(responses: Array<{ status?: number; body?: unknown }>) {
17
23
  return { impl: impl as unknown as typeof fetch, calls };
18
24
  }
19
25
 
26
+ function requestCalls(calls: Array<{ url: string; init: RequestInit }>) {
27
+ return calls.filter((call) => !call.url.includes('/api/services/by-slug/'));
28
+ }
29
+
20
30
  function clientWith(impl: typeof fetch) {
21
31
  return new TopoloClient({
22
32
  credential: { kind: 'access_token', accessToken: 'tok_test' },
@@ -26,6 +36,10 @@ function clientWith(impl: typeof fetch) {
26
36
  }
27
37
 
28
38
  describe('P2PModule.listDirectory', () => {
39
+ beforeEach(() => {
40
+ TopoloClient.clearServiceIdCache();
41
+ });
42
+
29
43
  it('GETs /api/directory/capabilities and unwraps the envelope', async () => {
30
44
  const { impl, calls } = stubFetch([
31
45
  {
@@ -37,7 +51,7 @@ describe('P2PModule.listDirectory', () => {
37
51
  capability_id: 'cap_1',
38
52
  publisher_org_id: 'org_a',
39
53
  app_id: 'app_x',
40
- service_id: 'srv_x',
54
+ service_id: 'service_x',
41
55
  name: 'Demo',
42
56
  description: 'A capability',
43
57
  category: 'other',
@@ -60,10 +74,11 @@ describe('P2PModule.listDirectory', () => {
60
74
  const capabilities = await p2p.listDirectory({ limit: 10 });
61
75
  expect(capabilities).toHaveLength(1);
62
76
  expect(capabilities[0]!.capability_id).toBe('cap_1');
63
- const url = new URL(calls[0]!.url);
77
+ const call = requestCalls(calls)[0]!;
78
+ const url = new URL(call.url);
64
79
  expect(url.pathname).toBe('/api/directory/capabilities');
65
80
  expect(url.searchParams.get('limit')).toBe('10');
66
- expect((calls[0]!.init.method ?? 'GET').toUpperCase()).toBe('GET');
81
+ expect((call.init.method ?? 'GET').toUpperCase()).toBe('GET');
67
82
  });
68
83
  });
69
84
 
@@ -78,7 +93,7 @@ describe('P2PModule.publishCapability', () => {
78
93
  capability_id: 'cap_z',
79
94
  publisher_org_id: 'org_p',
80
95
  app_id: 'app_x',
81
- service_id: 'srv_x',
96
+ service_id: 'service_x',
82
97
  name: 'Demo',
83
98
  description: 'A capability',
84
99
  category: 'other',
@@ -101,7 +116,7 @@ describe('P2PModule.publishCapability', () => {
101
116
  capability_id: 'cap_z',
102
117
  publisher_org_id: 'org_p',
103
118
  app_id: 'app_x',
104
- service_id: 'srv_x',
119
+ service_id: 'service_x',
105
120
  name: 'Demo',
106
121
  description: 'A capability',
107
122
  category: 'other',
@@ -114,13 +129,14 @@ describe('P2PModule.publishCapability', () => {
114
129
  });
115
130
  expect(capability.capability_id).toBe('cap_z');
116
131
 
117
- const sent = JSON.parse(String(calls[0]!.init.body));
132
+ const call = requestCalls(calls)[0]!;
133
+ const sent = JSON.parse(String(call.init.body));
118
134
  expect(sent.publisher_org_id).toBe('org_p');
119
135
  expect(sent.acting_for_org_id).toBe('org_p');
120
136
  expect(sent.public_directory_visible).toBe(true);
121
137
  expect(sent.requires_human_approval).toBe(false);
122
138
  expect(sent.trust_level).toBe('standard');
123
- const url = new URL(calls[0]!.url);
139
+ const url = new URL(call.url);
124
140
  expect(url.pathname).toBe('/api/p2p/internal/capabilities/sync');
125
141
  expect((calls[0]!.init.method ?? '').toUpperCase()).toBe('POST');
126
142
  });
@@ -134,7 +150,7 @@ describe('P2PModule.publishCapability', () => {
134
150
  capability_id: 'cap_other',
135
151
  publisher_org_id: 'org_target',
136
152
  app_id: 'app_x',
137
- service_id: 'srv_x',
153
+ service_id: 'service_x',
138
154
  name: 'Demo',
139
155
  description: '...',
140
156
  category: 'other',
@@ -144,7 +160,7 @@ describe('P2PModule.publishCapability', () => {
144
160
  source_revision: 'rev_1',
145
161
  acting_for_org_id: 'org_target',
146
162
  });
147
- const sent = JSON.parse(String(calls[0]!.init.body));
163
+ const sent = JSON.parse(String(requestCalls(calls)[0]!.init.body));
148
164
  expect(sent.acting_for_org_id).toBe('org_target');
149
165
  });
150
166
  });
@@ -211,7 +227,7 @@ describe('P2PModule.releaseHold', () => {
211
227
  const result = await p2p.releaseHold('hold_1', 'seller', { reason: 'arbitration' });
212
228
  expect(result.resolution).toBe('seller');
213
229
  expect(result.cancel_entries[0]!.entry_id).toBe('led_1');
214
- const sent = JSON.parse(String(calls[0]!.init.body));
230
+ const sent = JSON.parse(String(requestCalls(calls)[0]!.init.body));
215
231
  expect(sent.resolution).toBe('seller');
216
232
  expect(sent.reason).toBe('arbitration');
217
233
  });
@@ -36,12 +36,14 @@ export const SERVICES = {
36
36
  p2p: "https://p2p.topolo.app",
37
37
  pay: "https://pay.topolo.app",
38
38
  people: "https://people.topolo.app",
39
+ pulse: "https://pulse.topolo.app",
39
40
  "quro.api": "https://api.ol0.me",
40
41
  "quro.redirect": "https://ol0.me",
41
42
  roadmapper: "https://roadmapper-api.topolo.app",
42
43
  sign: "https://sign.topolo.app",
43
44
  social_studio: "https://studio-api.topolo.app",
44
45
  socialize: "https://socialize-api.topolo.app",
46
+ spaces: "https://spaces.topolo.app",
45
47
  status: "https://status.topolo.app",
46
48
  support: "https://support.topolo.app",
47
49
  survey: "https://survey.topolo.app",
@@ -50,35 +52,3 @@ export const SERVICES = {
50
52
  } as const;
51
53
 
52
54
  export type ServiceId = keyof typeof SERVICES;
53
-
54
- /**
55
- * @deprecated since 0.3.0 — opaque service ids are no longer baked into the
56
- * SDK. Use `TopoloClient.getServiceIdBySlug(slug)` which hits
57
- * `GET /api/services/by-slug/:slug` on TopoloAuth at runtime. This map is
58
- * still populated as a build-time snapshot for backward compatibility and
59
- * will be removed in 0.4.0. Stale entries are expected — do not rely on
60
- * presence/absence for catalog membership.
61
- */
62
- export const PLATFORM_SERVICE_IDS: Partial<Record<ServiceId, string>> = {
63
- books: "srv_0g260hbN8oqv",
64
- bugfix: "srv_kQ1t8qUSDgNs",
65
- compose: "srv_aR3ETYmgygmd",
66
- consent: "srv_8FIGR7Q7zP9r",
67
- crm: "srv_iCwM4jGXcwlj",
68
- customer_success: "srv_RWpOxkgZdZbH",
69
- developers: "srv_NjDq9G9qyyjr",
70
- director: "srv_wRCnLLbtvmVT",
71
- flow: "srv_GBCuKkHhnRXv",
72
- forms: "srv_UE7IcGe80J5o",
73
- home: "srv_hE6HBldVzjth",
74
- insights: "srv_DgbBMwWSIhmC",
75
- inventory: "srv_hmvjbv3ZxDnh",
76
- localize: "srv_Ze1g9OmXuZjF",
77
- notify: "srv_9pRdkIjYyKX0",
78
- p2p: "srv_4BzeLqH0wCve",
79
- people: "srv_VDMBAonA5fKb",
80
- sign: "srv_Bd8pRw5ZqRBT",
81
- status: "srv_topolo_status",
82
- survey: "srv_RCOi7CJkwf7R",
83
- voice: "srv_BZANHkshGAK5",
84
- };
@@ -14,20 +14,19 @@ const EXPECTED_APPLICATION_IDS = [
14
14
  'admin',
15
15
  'agent',
16
16
  'auth',
17
+ 'backup',
17
18
  'books',
18
19
  'bugfix',
19
20
  'bytes',
20
21
  'calendar',
21
22
  'capacity',
22
23
  'chat',
23
- 'cli',
24
24
  'cloudcontrol',
25
25
  'commerce',
26
26
  'compose',
27
27
  'consent',
28
28
  'crm',
29
29
  'customer_success',
30
- 'd1_backup',
31
30
  'developers',
32
31
  'director',
33
32
  'docs',
@@ -41,7 +40,6 @@ const EXPECTED_APPLICATION_IDS = [
41
40
  'learn',
42
41
  'localize',
43
42
  'mail',
44
- 'mcp',
45
43
  'mdm',
46
44
  'messaging',
47
45
  'nexus',
@@ -57,6 +55,7 @@ const EXPECTED_APPLICATION_IDS = [
57
55
  'sign',
58
56
  'social_studio',
59
57
  'socialize',
58
+ 'spaces',
60
59
  'status',
61
60
  'support',
62
61
  'survey',
@@ -84,20 +83,10 @@ describe('service registry', () => {
84
83
  expect(DEFAULT_SERVICE_URLS).not.toHaveProperty('web.runtime');
85
84
  });
86
85
 
87
- it('still ships PLATFORM_SERVICE_IDS as a deprecated build-time snapshot', async () => {
88
- // Deprecated in 0.3.0, removed in 0.4.0. Source of truth has moved to
89
- // TopoloAuth's GET /api/services/by-slug/:slug — see
90
- // TopoloClient.getServiceIdBySlug. We only sanity-check that the snapshot
91
- // is non-empty and that the values look like opaque service ids; specific
92
- // ids are no longer load-bearing.
93
- const { PLATFORM_SERVICE_IDS } = await import('./services.js');
94
-
95
- const entries = Object.entries(PLATFORM_SERVICE_IDS);
96
- expect(entries.length).toBeGreaterThan(0);
97
- for (const [, id] of entries) {
98
- expect(typeof id).toBe('string');
99
- expect(id).toMatch(/^srv_/);
100
- }
86
+ it('does not bake opaque Auth service ids into the generated registry', async () => {
87
+ const services = await import('./services.js');
88
+
89
+ expect(services).not.toHaveProperty('PLATFORM_SERVICE_IDS');
101
90
  });
102
91
 
103
92
  it('maps every callable service back to an application catalog entry', () => {
@@ -169,23 +158,17 @@ describe('application requirements', () => {
169
158
  expect(applicationRequirementScopes(APPLICATIONS.mail)).toEqual(
170
159
  expect.arrayContaining(['all', 'browser', 'api', 'agent_surface']),
171
160
  );
172
- expect(applicationRequirementScopes(APPLICATIONS.cli)).toEqual(
173
- expect.arrayContaining(['all', 'tooling', 'agent_surface']),
174
- );
175
161
  });
176
162
 
177
163
  it('filters requirements for a target application', () => {
178
164
  const adminRequirementIds = requirementsForApplication('admin').map((requirement) => requirement.id);
179
165
  const mailRequirementIds = requirementsForApplication('mail').map((requirement) => requirement.id);
180
- const cliRequirementIds = requirementsForApplication('cli').map((requirement) => requirement.id);
181
166
 
182
167
  expect(adminRequirementIds).toContain('shared-shell-and-launcher');
183
168
  expect(adminRequirementIds).not.toContain('organization-scoped-data');
184
169
  expect(mailRequirementIds).toContain('organization-scoped-data');
185
170
  expect(mailRequirementIds).toContain('service-registration-and-scopes');
186
171
  expect(mailRequirementIds).toContain('native-dashboard-widget');
187
- expect(cliRequirementIds).toContain('agent-and-operator-surfaces');
188
- expect(cliRequirementIds).not.toContain('service-registration-and-scopes');
189
172
  });
190
173
 
191
174
  it('audits one application against the catalog-backed requirements', () => {
package/src/services.ts CHANGED
@@ -15,7 +15,6 @@
15
15
  */
16
16
  import {
17
17
  SERVICES,
18
- PLATFORM_SERVICE_IDS,
19
18
  type ServiceId,
20
19
  } from './services.generated.js';
21
20
  import {
@@ -41,7 +40,7 @@ export {
41
40
  type ApplicationRequirementsAuditReport,
42
41
  } from './application-requirements.js';
43
42
 
44
- export { SERVICES, PLATFORM_SERVICE_IDS };
43
+ export { SERVICES };
45
44
  export type { ServiceId };
46
45
  export { APPLICATIONS };
47
46
  export type { ApplicationCatalogEntry, ApplicationDeployTarget, ApplicationId };
@@ -15,7 +15,7 @@ describe("Topolo widget contract", () => {
15
15
 
16
16
  it("stamps service metadata and timestamp onto widget drafts", () => {
17
17
  const response = createTopoloWidgetResponse({
18
- serviceId: "srv_bytes",
18
+ serviceId: "service_bytes",
19
19
  serviceName: "Bytes",
20
20
  now: "2026-04-25T00:00:00.000Z",
21
21
  widgets: [
@@ -30,7 +30,7 @@ describe("Topolo widget contract", () => {
30
30
  success: true,
31
31
  widgets: [
32
32
  {
33
- serviceId: "srv_bytes",
33
+ serviceId: "service_bytes",
34
34
  serviceName: "Bytes",
35
35
  lastUpdated: "2026-04-25T00:00:00.000Z",
36
36
  type: "stats",
@@ -42,12 +42,12 @@ describe("Topolo widget contract", () => {
42
42
 
43
43
  it("keeps explicit widget metadata when an app needs multiple service cards", () => {
44
44
  const response = createTopoloWidgetResponse({
45
- serviceId: "srv_platform",
45
+ serviceId: "service_platform",
46
46
  serviceName: "Platform",
47
47
  now: "2026-04-25T00:00:00.000Z",
48
48
  widgets: [
49
49
  {
50
- serviceId: "srv_crm",
50
+ serviceId: "service_crm",
51
51
  serviceName: "CRM",
52
52
  lastUpdated: "2026-04-24T23:00:00.000Z",
53
53
  type: "stats",
@@ -57,7 +57,7 @@ describe("Topolo widget contract", () => {
57
57
  });
58
58
 
59
59
  expect(response.widgets[0]).toMatchObject({
60
- serviceId: "srv_crm",
60
+ serviceId: "service_crm",
61
61
  serviceName: "CRM",
62
62
  lastUpdated: "2026-04-24T23:00:00.000Z",
63
63
  });
@@ -73,7 +73,7 @@ describe("Topolo widget contract", () => {
73
73
 
74
74
  it("validates a compliant response", () => {
75
75
  const response = createTopoloWidgetResponse({
76
- serviceId: "srv_crm",
76
+ serviceId: "service_crm",
77
77
  serviceName: "CRM",
78
78
  now: "2026-04-25T00:00:00.000Z",
79
79
  widgets: [
@@ -102,7 +102,7 @@ describe("Topolo widget contract", () => {
102
102
 
103
103
  it("keeps optional snapshot metadata for configurable TopoloOne datapoints", () => {
104
104
  const response = createTopoloWidgetResponse({
105
- serviceId: "srv_admin",
105
+ serviceId: "service_admin",
106
106
  serviceName: "Admin",
107
107
  now: "2026-04-28T00:00:00.000Z",
108
108
  widgets: [