@topolo/sdk 0.4.10 → 0.6.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/src/services.ts CHANGED
@@ -1,21 +1,21 @@
1
1
  /**
2
- * Service registry — base URLs for every Topolo platform service.
2
+ * App API registry — base URLs for callable Topolo platform APIs.
3
3
  *
4
4
  * Source of truth lives in each app's `topolo.cloudcontrol.json`. The
5
- * `SERVICES` map is regenerated from those files via
5
+ * `APP_API_URLS` map is regenerated from those files via
6
6
  * `scripts/generate-services.mjs`; edit the JSON, not the generated file.
7
7
  *
8
8
  * Any entry can be overridden per-call via `TopoloClient` options, or globally
9
- * via env vars like `TOPOLO_SERVICE_URL_MAIL` or `TOPOLO_SERVICE_URL_WEB_API`
10
- * (dots in service IDs become underscores in env vars).
9
+ * via env vars like `TOPOLO_APP_URL_MAIL` or `TOPOLO_APP_URL_WEB_API`
10
+ * (dots in app API keys become underscores in env vars).
11
11
  *
12
12
  * IMPORTANT: The SDK NEVER accepts `orgId` as a parameter. Every request is
13
13
  * scoped to the organization embedded in the auth credential (JWT claim or
14
14
  * API-key binding). This is the load-bearing cross-org isolation guarantee.
15
15
  */
16
16
  import {
17
- SERVICES,
18
- type ServiceId,
17
+ APP_API_URLS,
18
+ type AppApiId,
19
19
  } from './services.generated.js';
20
20
  import {
21
21
  APPLICATIONS,
@@ -40,15 +40,15 @@ export {
40
40
  type ApplicationRequirementsAuditReport,
41
41
  } from './application-requirements.js';
42
42
 
43
- export { SERVICES };
44
- export type { ServiceId };
43
+ export { APP_API_URLS };
44
+ export type { AppApiId };
45
45
  export { APPLICATIONS };
46
46
  export type { ApplicationCatalogEntry, ApplicationDeployTarget, ApplicationId };
47
47
 
48
- export const DEFAULT_SERVICE_URLS: Record<ServiceId, string> = SERVICES;
48
+ export const DEFAULT_APP_URLS: Record<AppApiId, string> = APP_API_URLS;
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;
@@ -61,7 +61,7 @@ export function resolveCatalogServiceUrl(
61
61
  entry: ServiceCatalogUrlEntry,
62
62
  options: {
63
63
  serviceKey?: string;
64
- overrides?: Record<string, string> | Partial<Record<ServiceId, string>>;
64
+ overrides?: Record<string, string> | Partial<Record<AppApiId, string>>;
65
65
  } = {},
66
66
  ): string | null {
67
67
  const keys = catalogServiceKeyCandidates(entry, options.serviceKey);
@@ -75,17 +75,17 @@ export function resolveCatalogServiceUrl(
75
75
  }
76
76
 
77
77
  export function resolveServiceUrl(
78
- service: ServiceId,
79
- overrides?: Partial<Record<ServiceId, string>>,
78
+ service: AppApiId,
79
+ overrides?: Partial<Record<AppApiId, string>>,
80
80
  ): string {
81
81
  const override = resolveServiceUrlOverride([service], overrides);
82
82
  if (override) return override;
83
83
 
84
- return DEFAULT_SERVICE_URLS[service];
84
+ return DEFAULT_APP_URLS[service];
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>();
@@ -110,19 +110,19 @@ function serviceKeyCandidates(value: string): string[] {
110
110
 
111
111
  function resolveServiceUrlOverride(
112
112
  keys: string[],
113
- overrides?: Record<string, string> | Partial<Record<ServiceId, string>>,
113
+ overrides?: Record<string, string> | Partial<Record<AppApiId, string>>,
114
114
  ): string | null {
115
115
  for (const key of keys) {
116
- const direct = overrides?.[key as ServiceId];
116
+ const direct = overrides?.[key as AppApiId];
117
117
  if (direct) return direct;
118
118
 
119
119
  const envStyleKey = key.replace(/\./g, '_');
120
- const envStyle = overrides?.[envStyleKey as ServiceId];
120
+ const envStyle = overrides?.[envStyleKey as AppApiId];
121
121
  if (envStyle) return envStyle;
122
122
  }
123
123
 
124
124
  for (const key of keys) {
125
- const envKey = `TOPOLO_SERVICE_URL_${key.replace(/\./g, '_').toUpperCase()}`;
125
+ const envKey = `TOPOLO_APP_URL_${key.replace(/\./g, '_').toUpperCase()}`;
126
126
  const envValue = typeof process !== 'undefined' ? process.env?.[envKey] : undefined;
127
127
  if (envValue) return envValue;
128
128
  }
@@ -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);