@topolo/sdk 0.1.1 → 0.2.1
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 +77 -6
- package/dist/index.d.cts +73 -11
- package/dist/index.d.ts +73 -11
- package/dist/index.js +77 -6
- package/package.json +4 -2
- package/src/client.test.ts +84 -0
- package/src/client.ts +92 -2
- package/src/index.ts +1 -0
- package/src/services.generated.ts +34 -0
- package/src/services.ts +17 -19
package/dist/index.cjs
CHANGED
|
@@ -93,18 +93,41 @@ var TopoloHttpError = class extends TopoloSdkError {
|
|
|
93
93
|
}
|
|
94
94
|
};
|
|
95
95
|
|
|
96
|
-
// src/services.ts
|
|
97
|
-
var
|
|
96
|
+
// src/services.generated.ts
|
|
97
|
+
var SERVICES = {
|
|
98
|
+
agent: "https://topolo-agent-worker.topolo.workers.dev",
|
|
98
99
|
auth: "https://auth.topolo.app",
|
|
99
|
-
|
|
100
|
+
bugfix: "https://bugfix.topolo.app",
|
|
101
|
+
bytes: "https://r2-media-manager-worker.topolo.workers.dev",
|
|
102
|
+
chat: "https://chat.topolo.app",
|
|
103
|
+
commerce: "https://topolo-commerce-api.topolo.workers.dev",
|
|
104
|
+
crm: "https://crm-api.topolo.app",
|
|
105
|
+
forecast: "https://forecast-worker-production.topolo.workers.dev",
|
|
106
|
+
learn: "https://topololearn-api.topolo.workers.dev",
|
|
107
|
+
mdm: "https://nodo-mdm-api.topolo.app",
|
|
108
|
+
messaging: "https://messages.topolo.app",
|
|
109
|
+
nexus: "https://topolo-nexus-gateway.topolo.workers.dev",
|
|
110
|
+
one: "https://topoloone-worker.topolo.workers.dev",
|
|
111
|
+
pay: "https://topolo-pay.topolo.workers.dev",
|
|
112
|
+
"quro.api": "https://api.ol0.me",
|
|
113
|
+
"quro.redirect": "https://ol0.me",
|
|
114
|
+
roadmapper: "https://roadmapper-api.topolo.app",
|
|
115
|
+
social_studio: "https://topolo-social-studio-api.topolo.workers.dev",
|
|
116
|
+
socialize: "https://socialize-prod-api.topolo.workers.dev",
|
|
117
|
+
support: "https://support.topolo.app",
|
|
118
|
+
"web.api": "https://web-api.topolo.app",
|
|
119
|
+
"web.runtime": "https://sites.topolo.app"
|
|
100
120
|
};
|
|
101
121
|
var PLATFORM_SERVICE_IDS = {
|
|
102
122
|
crm: "srv_iCwM4jGXcwlj"
|
|
103
123
|
};
|
|
124
|
+
|
|
125
|
+
// src/services.ts
|
|
126
|
+
var DEFAULT_SERVICE_URLS = SERVICES;
|
|
104
127
|
function resolveServiceUrl(service, overrides) {
|
|
105
128
|
const override = overrides?.[service];
|
|
106
129
|
if (override) return override;
|
|
107
|
-
const envKey = `TOPOLO_SERVICE_URL_${service.toUpperCase()}`;
|
|
130
|
+
const envKey = `TOPOLO_SERVICE_URL_${service.replace(/\./g, "_").toUpperCase()}`;
|
|
108
131
|
const envValue = typeof process !== "undefined" ? process.env?.[envKey] : void 0;
|
|
109
132
|
if (envValue) return envValue;
|
|
110
133
|
return DEFAULT_SERVICE_URLS[service];
|
|
@@ -119,6 +142,7 @@ var TopoloClient = class {
|
|
|
119
142
|
requireConfirmForWrites;
|
|
120
143
|
timeoutMs;
|
|
121
144
|
fetchImpl;
|
|
145
|
+
debug;
|
|
122
146
|
constructor(options) {
|
|
123
147
|
if (!options.credential) throw new TopoloAuthError("credential is required");
|
|
124
148
|
if (!options.agent?.clientName) throw new TopoloAuthError("agent.clientName is required");
|
|
@@ -128,6 +152,14 @@ var TopoloClient = class {
|
|
|
128
152
|
this.requireConfirmForWrites = options.requireConfirmForWrites !== false;
|
|
129
153
|
this.timeoutMs = options.timeoutMs ?? 3e4;
|
|
130
154
|
this.fetchImpl = options.fetch ?? fetch;
|
|
155
|
+
this.debug = options.debug;
|
|
156
|
+
}
|
|
157
|
+
emit(event) {
|
|
158
|
+
if (!this.debug) return;
|
|
159
|
+
try {
|
|
160
|
+
this.debug(event);
|
|
161
|
+
} catch {
|
|
162
|
+
}
|
|
131
163
|
}
|
|
132
164
|
/**
|
|
133
165
|
* Low-level JSON request. Prefer the typed module helpers (identity, crm, ...)
|
|
@@ -154,27 +186,56 @@ var TopoloClient = class {
|
|
|
154
186
|
const platformServiceId = PLATFORM_SERVICE_IDS[opts.service];
|
|
155
187
|
if (platformServiceId) headers.set("X-Service-ID", platformServiceId);
|
|
156
188
|
applyAuthHeaders(headers, this.credential);
|
|
157
|
-
|
|
189
|
+
const requestId = generateRequestId();
|
|
190
|
+
applyAuditHeaders(headers, this.agent, requestId);
|
|
158
191
|
if (opts.headers) {
|
|
159
192
|
for (const [k, v] of Object.entries(opts.headers)) headers.set(k, v);
|
|
160
193
|
}
|
|
161
194
|
const controller = new AbortController();
|
|
162
195
|
const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
163
196
|
const signal = opts.signal ? mergeSignals(opts.signal, controller.signal) : controller.signal;
|
|
197
|
+
const urlStr = url.toString();
|
|
198
|
+
const startedAt = Date.now();
|
|
199
|
+
this.emit({ phase: "request", method, service: opts.service, path: opts.path, url: urlStr, requestId });
|
|
164
200
|
let res;
|
|
165
201
|
try {
|
|
166
|
-
res = await this.fetchImpl(
|
|
202
|
+
res = await this.fetchImpl(urlStr, {
|
|
167
203
|
method,
|
|
168
204
|
headers,
|
|
169
205
|
body: opts.body !== void 0 ? JSON.stringify(opts.body) : null,
|
|
170
206
|
signal
|
|
171
207
|
});
|
|
208
|
+
} catch (err) {
|
|
209
|
+
this.emit({
|
|
210
|
+
phase: "error",
|
|
211
|
+
method,
|
|
212
|
+
service: opts.service,
|
|
213
|
+
path: opts.path,
|
|
214
|
+
url: urlStr,
|
|
215
|
+
requestId,
|
|
216
|
+
durationMs: Date.now() - startedAt,
|
|
217
|
+
error: err instanceof Error ? err.message : String(err)
|
|
218
|
+
});
|
|
219
|
+
throw err;
|
|
172
220
|
} finally {
|
|
173
221
|
clearTimeout(timeoutId);
|
|
174
222
|
}
|
|
175
223
|
const contentType = res.headers.get("Content-Type") ?? "";
|
|
176
224
|
const parsed = contentType.includes("application/json") ? await res.json().catch(() => null) : await res.text().catch(() => null);
|
|
177
225
|
if (!res.ok) {
|
|
226
|
+
const durationMs = Date.now() - startedAt;
|
|
227
|
+
const message = describeError(parsed, `HTTP ${res.status}`);
|
|
228
|
+
this.emit({
|
|
229
|
+
phase: "error",
|
|
230
|
+
method,
|
|
231
|
+
service: opts.service,
|
|
232
|
+
path: opts.path,
|
|
233
|
+
url: urlStr,
|
|
234
|
+
requestId,
|
|
235
|
+
durationMs,
|
|
236
|
+
error: message,
|
|
237
|
+
status: res.status
|
|
238
|
+
});
|
|
178
239
|
if (res.status === 401) throw new TopoloAuthError(describeError(parsed, "Unauthorized"));
|
|
179
240
|
if (res.status === 403) {
|
|
180
241
|
throw new TopoloPermissionError(
|
|
@@ -184,6 +245,16 @@ var TopoloClient = class {
|
|
|
184
245
|
}
|
|
185
246
|
throw new TopoloHttpError(opts.service, opts.path, res.status, parsed);
|
|
186
247
|
}
|
|
248
|
+
this.emit({
|
|
249
|
+
phase: "response",
|
|
250
|
+
method,
|
|
251
|
+
service: opts.service,
|
|
252
|
+
path: opts.path,
|
|
253
|
+
url: urlStr,
|
|
254
|
+
requestId,
|
|
255
|
+
status: res.status,
|
|
256
|
+
durationMs: Date.now() - startedAt
|
|
257
|
+
});
|
|
187
258
|
return parsed;
|
|
188
259
|
}
|
|
189
260
|
/**
|
package/dist/index.d.cts
CHANGED
|
@@ -31,24 +31,50 @@ interface AgentIdentity {
|
|
|
31
31
|
agentName?: string;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
declare const SERVICES: {
|
|
35
|
+
readonly agent: "https://topolo-agent-worker.topolo.workers.dev";
|
|
36
|
+
readonly auth: "https://auth.topolo.app";
|
|
37
|
+
readonly bugfix: "https://bugfix.topolo.app";
|
|
38
|
+
readonly bytes: "https://r2-media-manager-worker.topolo.workers.dev";
|
|
39
|
+
readonly chat: "https://chat.topolo.app";
|
|
40
|
+
readonly commerce: "https://topolo-commerce-api.topolo.workers.dev";
|
|
41
|
+
readonly crm: "https://crm-api.topolo.app";
|
|
42
|
+
readonly forecast: "https://forecast-worker-production.topolo.workers.dev";
|
|
43
|
+
readonly learn: "https://topololearn-api.topolo.workers.dev";
|
|
44
|
+
readonly mdm: "https://nodo-mdm-api.topolo.app";
|
|
45
|
+
readonly messaging: "https://messages.topolo.app";
|
|
46
|
+
readonly nexus: "https://topolo-nexus-gateway.topolo.workers.dev";
|
|
47
|
+
readonly one: "https://topoloone-worker.topolo.workers.dev";
|
|
48
|
+
readonly pay: "https://topolo-pay.topolo.workers.dev";
|
|
49
|
+
readonly "quro.api": "https://api.ol0.me";
|
|
50
|
+
readonly "quro.redirect": "https://ol0.me";
|
|
51
|
+
readonly roadmapper: "https://roadmapper-api.topolo.app";
|
|
52
|
+
readonly social_studio: "https://topolo-social-studio-api.topolo.workers.dev";
|
|
53
|
+
readonly socialize: "https://socialize-prod-api.topolo.workers.dev";
|
|
54
|
+
readonly support: "https://support.topolo.app";
|
|
55
|
+
readonly "web.api": "https://web-api.topolo.app";
|
|
56
|
+
readonly "web.runtime": "https://sites.topolo.app";
|
|
57
|
+
};
|
|
58
|
+
type ServiceId = keyof typeof SERVICES;
|
|
59
|
+
declare const PLATFORM_SERVICE_IDS: Partial<Record<ServiceId, string>>;
|
|
60
|
+
|
|
34
61
|
/**
|
|
35
|
-
* Service registry — base URLs for
|
|
62
|
+
* Service registry — base URLs for every Topolo platform service.
|
|
36
63
|
*
|
|
37
|
-
*
|
|
38
|
-
* `
|
|
64
|
+
* Source of truth lives in each app's `topolo.cloudcontrol.json`. The
|
|
65
|
+
* `SERVICES` map is regenerated from those files via
|
|
66
|
+
* `scripts/generate-services.mjs`; edit the JSON, not the generated file.
|
|
67
|
+
*
|
|
68
|
+
* Any entry can be overridden per-call via `TopoloClient` options, or globally
|
|
69
|
+
* via env vars like `TOPOLO_SERVICE_URL_CRM` or `TOPOLO_SERVICE_URL_WEB_API`
|
|
70
|
+
* (dots in service IDs become underscores in env vars).
|
|
39
71
|
*
|
|
40
72
|
* IMPORTANT: The SDK NEVER accepts `orgId` as a parameter. Every request is
|
|
41
73
|
* scoped to the organization embedded in the auth credential (JWT claim or
|
|
42
74
|
* API-key binding). This is the load-bearing cross-org isolation guarantee.
|
|
43
75
|
*/
|
|
44
|
-
|
|
76
|
+
|
|
45
77
|
declare const DEFAULT_SERVICE_URLS: Record<ServiceId, string>;
|
|
46
|
-
/**
|
|
47
|
-
* Service IDs as registered in TopoloAuth's service catalog. These are sent in
|
|
48
|
-
* the `X-Service-ID` header when the platform needs to route an API-key
|
|
49
|
-
* introspection or permission check.
|
|
50
|
-
*/
|
|
51
|
-
declare const PLATFORM_SERVICE_IDS: Partial<Record<ServiceId, string>>;
|
|
52
78
|
declare function resolveServiceUrl(service: ServiceId, overrides?: Partial<Record<ServiceId, string>>): string;
|
|
53
79
|
|
|
54
80
|
interface TopoloClientOptions {
|
|
@@ -65,7 +91,41 @@ interface TopoloClientOptions {
|
|
|
65
91
|
timeoutMs?: number;
|
|
66
92
|
/** Injected fetch, for testing. Defaults to global fetch. */
|
|
67
93
|
fetch?: typeof fetch;
|
|
94
|
+
/**
|
|
95
|
+
* Observability hook. Fires once per request lifecycle (`request` at start,
|
|
96
|
+
* then exactly one of `response` or `error`). Safe to leave unset — off by
|
|
97
|
+
* default. Intended for CLI/MCP hosts to surface request-level diagnostics
|
|
98
|
+
* and for consumers building their own logging/tracing.
|
|
99
|
+
*/
|
|
100
|
+
debug?: (event: TopoloDebugEvent) => void;
|
|
68
101
|
}
|
|
102
|
+
type TopoloDebugEvent = {
|
|
103
|
+
phase: 'request';
|
|
104
|
+
method: string;
|
|
105
|
+
service: ServiceId;
|
|
106
|
+
path: string;
|
|
107
|
+
url: string;
|
|
108
|
+
requestId: string;
|
|
109
|
+
} | {
|
|
110
|
+
phase: 'response';
|
|
111
|
+
method: string;
|
|
112
|
+
service: ServiceId;
|
|
113
|
+
path: string;
|
|
114
|
+
url: string;
|
|
115
|
+
requestId: string;
|
|
116
|
+
status: number;
|
|
117
|
+
durationMs: number;
|
|
118
|
+
} | {
|
|
119
|
+
phase: 'error';
|
|
120
|
+
method: string;
|
|
121
|
+
service: ServiceId;
|
|
122
|
+
path: string;
|
|
123
|
+
url: string;
|
|
124
|
+
requestId: string;
|
|
125
|
+
durationMs: number;
|
|
126
|
+
error: string;
|
|
127
|
+
status?: number;
|
|
128
|
+
};
|
|
69
129
|
interface RequestOptions {
|
|
70
130
|
service: ServiceId;
|
|
71
131
|
path: string;
|
|
@@ -85,7 +145,9 @@ declare class TopoloClient {
|
|
|
85
145
|
private readonly requireConfirmForWrites;
|
|
86
146
|
private readonly timeoutMs;
|
|
87
147
|
private readonly fetchImpl;
|
|
148
|
+
private readonly debug?;
|
|
88
149
|
constructor(options: TopoloClientOptions);
|
|
150
|
+
private emit;
|
|
89
151
|
/**
|
|
90
152
|
* Low-level JSON request. Prefer the typed module helpers (identity, crm, ...)
|
|
91
153
|
* for anything a caller would reach for; this stays exported for escape-hatch
|
|
@@ -274,4 +336,4 @@ declare function createTopolo(options: TopoloClientOptions): {
|
|
|
274
336
|
};
|
|
275
337
|
type Topolo = ReturnType<typeof createTopolo>;
|
|
276
338
|
|
|
277
|
-
export { type AgentIdentity, type CredentialIntrospection, type CrmContactSummary, CrmModule, DEFAULT_SERVICE_URLS, type DeviceAuthorizationResponse, IdentityModule, type ListContactsOptions, type ListContactsResult, type OAuthHelperOptions, PLATFORM_SERVICE_IDS, type RequestOptions, type ServiceId, type TokenResponse, type Topolo, TopoloAuthError, TopoloClient, type TopoloClientOptions, type TopoloCredential, TopoloHttpError, TopoloOAuth, TopoloPermissionError, TopoloSdkError, createTopolo, resolveServiceUrl };
|
|
339
|
+
export { type AgentIdentity, type CredentialIntrospection, type CrmContactSummary, CrmModule, DEFAULT_SERVICE_URLS, type DeviceAuthorizationResponse, IdentityModule, type ListContactsOptions, type ListContactsResult, type OAuthHelperOptions, PLATFORM_SERVICE_IDS, type RequestOptions, type ServiceId, type TokenResponse, type Topolo, TopoloAuthError, TopoloClient, type TopoloClientOptions, type TopoloCredential, type TopoloDebugEvent, TopoloHttpError, TopoloOAuth, TopoloPermissionError, TopoloSdkError, createTopolo, resolveServiceUrl };
|
package/dist/index.d.ts
CHANGED
|
@@ -31,24 +31,50 @@ interface AgentIdentity {
|
|
|
31
31
|
agentName?: string;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
declare const SERVICES: {
|
|
35
|
+
readonly agent: "https://topolo-agent-worker.topolo.workers.dev";
|
|
36
|
+
readonly auth: "https://auth.topolo.app";
|
|
37
|
+
readonly bugfix: "https://bugfix.topolo.app";
|
|
38
|
+
readonly bytes: "https://r2-media-manager-worker.topolo.workers.dev";
|
|
39
|
+
readonly chat: "https://chat.topolo.app";
|
|
40
|
+
readonly commerce: "https://topolo-commerce-api.topolo.workers.dev";
|
|
41
|
+
readonly crm: "https://crm-api.topolo.app";
|
|
42
|
+
readonly forecast: "https://forecast-worker-production.topolo.workers.dev";
|
|
43
|
+
readonly learn: "https://topololearn-api.topolo.workers.dev";
|
|
44
|
+
readonly mdm: "https://nodo-mdm-api.topolo.app";
|
|
45
|
+
readonly messaging: "https://messages.topolo.app";
|
|
46
|
+
readonly nexus: "https://topolo-nexus-gateway.topolo.workers.dev";
|
|
47
|
+
readonly one: "https://topoloone-worker.topolo.workers.dev";
|
|
48
|
+
readonly pay: "https://topolo-pay.topolo.workers.dev";
|
|
49
|
+
readonly "quro.api": "https://api.ol0.me";
|
|
50
|
+
readonly "quro.redirect": "https://ol0.me";
|
|
51
|
+
readonly roadmapper: "https://roadmapper-api.topolo.app";
|
|
52
|
+
readonly social_studio: "https://topolo-social-studio-api.topolo.workers.dev";
|
|
53
|
+
readonly socialize: "https://socialize-prod-api.topolo.workers.dev";
|
|
54
|
+
readonly support: "https://support.topolo.app";
|
|
55
|
+
readonly "web.api": "https://web-api.topolo.app";
|
|
56
|
+
readonly "web.runtime": "https://sites.topolo.app";
|
|
57
|
+
};
|
|
58
|
+
type ServiceId = keyof typeof SERVICES;
|
|
59
|
+
declare const PLATFORM_SERVICE_IDS: Partial<Record<ServiceId, string>>;
|
|
60
|
+
|
|
34
61
|
/**
|
|
35
|
-
* Service registry — base URLs for
|
|
62
|
+
* Service registry — base URLs for every Topolo platform service.
|
|
36
63
|
*
|
|
37
|
-
*
|
|
38
|
-
* `
|
|
64
|
+
* Source of truth lives in each app's `topolo.cloudcontrol.json`. The
|
|
65
|
+
* `SERVICES` map is regenerated from those files via
|
|
66
|
+
* `scripts/generate-services.mjs`; edit the JSON, not the generated file.
|
|
67
|
+
*
|
|
68
|
+
* Any entry can be overridden per-call via `TopoloClient` options, or globally
|
|
69
|
+
* via env vars like `TOPOLO_SERVICE_URL_CRM` or `TOPOLO_SERVICE_URL_WEB_API`
|
|
70
|
+
* (dots in service IDs become underscores in env vars).
|
|
39
71
|
*
|
|
40
72
|
* IMPORTANT: The SDK NEVER accepts `orgId` as a parameter. Every request is
|
|
41
73
|
* scoped to the organization embedded in the auth credential (JWT claim or
|
|
42
74
|
* API-key binding). This is the load-bearing cross-org isolation guarantee.
|
|
43
75
|
*/
|
|
44
|
-
|
|
76
|
+
|
|
45
77
|
declare const DEFAULT_SERVICE_URLS: Record<ServiceId, string>;
|
|
46
|
-
/**
|
|
47
|
-
* Service IDs as registered in TopoloAuth's service catalog. These are sent in
|
|
48
|
-
* the `X-Service-ID` header when the platform needs to route an API-key
|
|
49
|
-
* introspection or permission check.
|
|
50
|
-
*/
|
|
51
|
-
declare const PLATFORM_SERVICE_IDS: Partial<Record<ServiceId, string>>;
|
|
52
78
|
declare function resolveServiceUrl(service: ServiceId, overrides?: Partial<Record<ServiceId, string>>): string;
|
|
53
79
|
|
|
54
80
|
interface TopoloClientOptions {
|
|
@@ -65,7 +91,41 @@ interface TopoloClientOptions {
|
|
|
65
91
|
timeoutMs?: number;
|
|
66
92
|
/** Injected fetch, for testing. Defaults to global fetch. */
|
|
67
93
|
fetch?: typeof fetch;
|
|
94
|
+
/**
|
|
95
|
+
* Observability hook. Fires once per request lifecycle (`request` at start,
|
|
96
|
+
* then exactly one of `response` or `error`). Safe to leave unset — off by
|
|
97
|
+
* default. Intended for CLI/MCP hosts to surface request-level diagnostics
|
|
98
|
+
* and for consumers building their own logging/tracing.
|
|
99
|
+
*/
|
|
100
|
+
debug?: (event: TopoloDebugEvent) => void;
|
|
68
101
|
}
|
|
102
|
+
type TopoloDebugEvent = {
|
|
103
|
+
phase: 'request';
|
|
104
|
+
method: string;
|
|
105
|
+
service: ServiceId;
|
|
106
|
+
path: string;
|
|
107
|
+
url: string;
|
|
108
|
+
requestId: string;
|
|
109
|
+
} | {
|
|
110
|
+
phase: 'response';
|
|
111
|
+
method: string;
|
|
112
|
+
service: ServiceId;
|
|
113
|
+
path: string;
|
|
114
|
+
url: string;
|
|
115
|
+
requestId: string;
|
|
116
|
+
status: number;
|
|
117
|
+
durationMs: number;
|
|
118
|
+
} | {
|
|
119
|
+
phase: 'error';
|
|
120
|
+
method: string;
|
|
121
|
+
service: ServiceId;
|
|
122
|
+
path: string;
|
|
123
|
+
url: string;
|
|
124
|
+
requestId: string;
|
|
125
|
+
durationMs: number;
|
|
126
|
+
error: string;
|
|
127
|
+
status?: number;
|
|
128
|
+
};
|
|
69
129
|
interface RequestOptions {
|
|
70
130
|
service: ServiceId;
|
|
71
131
|
path: string;
|
|
@@ -85,7 +145,9 @@ declare class TopoloClient {
|
|
|
85
145
|
private readonly requireConfirmForWrites;
|
|
86
146
|
private readonly timeoutMs;
|
|
87
147
|
private readonly fetchImpl;
|
|
148
|
+
private readonly debug?;
|
|
88
149
|
constructor(options: TopoloClientOptions);
|
|
150
|
+
private emit;
|
|
89
151
|
/**
|
|
90
152
|
* Low-level JSON request. Prefer the typed module helpers (identity, crm, ...)
|
|
91
153
|
* for anything a caller would reach for; this stays exported for escape-hatch
|
|
@@ -274,4 +336,4 @@ declare function createTopolo(options: TopoloClientOptions): {
|
|
|
274
336
|
};
|
|
275
337
|
type Topolo = ReturnType<typeof createTopolo>;
|
|
276
338
|
|
|
277
|
-
export { type AgentIdentity, type CredentialIntrospection, type CrmContactSummary, CrmModule, DEFAULT_SERVICE_URLS, type DeviceAuthorizationResponse, IdentityModule, type ListContactsOptions, type ListContactsResult, type OAuthHelperOptions, PLATFORM_SERVICE_IDS, type RequestOptions, type ServiceId, type TokenResponse, type Topolo, TopoloAuthError, TopoloClient, type TopoloClientOptions, type TopoloCredential, TopoloHttpError, TopoloOAuth, TopoloPermissionError, TopoloSdkError, createTopolo, resolveServiceUrl };
|
|
339
|
+
export { type AgentIdentity, type CredentialIntrospection, type CrmContactSummary, CrmModule, DEFAULT_SERVICE_URLS, type DeviceAuthorizationResponse, IdentityModule, type ListContactsOptions, type ListContactsResult, type OAuthHelperOptions, PLATFORM_SERVICE_IDS, type RequestOptions, type ServiceId, type TokenResponse, type Topolo, TopoloAuthError, TopoloClient, type TopoloClientOptions, type TopoloCredential, type TopoloDebugEvent, TopoloHttpError, TopoloOAuth, TopoloPermissionError, TopoloSdkError, createTopolo, resolveServiceUrl };
|
package/dist/index.js
CHANGED
|
@@ -56,18 +56,41 @@ var TopoloHttpError = class extends TopoloSdkError {
|
|
|
56
56
|
}
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
// src/services.ts
|
|
60
|
-
var
|
|
59
|
+
// src/services.generated.ts
|
|
60
|
+
var SERVICES = {
|
|
61
|
+
agent: "https://topolo-agent-worker.topolo.workers.dev",
|
|
61
62
|
auth: "https://auth.topolo.app",
|
|
62
|
-
|
|
63
|
+
bugfix: "https://bugfix.topolo.app",
|
|
64
|
+
bytes: "https://r2-media-manager-worker.topolo.workers.dev",
|
|
65
|
+
chat: "https://chat.topolo.app",
|
|
66
|
+
commerce: "https://topolo-commerce-api.topolo.workers.dev",
|
|
67
|
+
crm: "https://crm-api.topolo.app",
|
|
68
|
+
forecast: "https://forecast-worker-production.topolo.workers.dev",
|
|
69
|
+
learn: "https://topololearn-api.topolo.workers.dev",
|
|
70
|
+
mdm: "https://nodo-mdm-api.topolo.app",
|
|
71
|
+
messaging: "https://messages.topolo.app",
|
|
72
|
+
nexus: "https://topolo-nexus-gateway.topolo.workers.dev",
|
|
73
|
+
one: "https://topoloone-worker.topolo.workers.dev",
|
|
74
|
+
pay: "https://topolo-pay.topolo.workers.dev",
|
|
75
|
+
"quro.api": "https://api.ol0.me",
|
|
76
|
+
"quro.redirect": "https://ol0.me",
|
|
77
|
+
roadmapper: "https://roadmapper-api.topolo.app",
|
|
78
|
+
social_studio: "https://topolo-social-studio-api.topolo.workers.dev",
|
|
79
|
+
socialize: "https://socialize-prod-api.topolo.workers.dev",
|
|
80
|
+
support: "https://support.topolo.app",
|
|
81
|
+
"web.api": "https://web-api.topolo.app",
|
|
82
|
+
"web.runtime": "https://sites.topolo.app"
|
|
63
83
|
};
|
|
64
84
|
var PLATFORM_SERVICE_IDS = {
|
|
65
85
|
crm: "srv_iCwM4jGXcwlj"
|
|
66
86
|
};
|
|
87
|
+
|
|
88
|
+
// src/services.ts
|
|
89
|
+
var DEFAULT_SERVICE_URLS = SERVICES;
|
|
67
90
|
function resolveServiceUrl(service, overrides) {
|
|
68
91
|
const override = overrides?.[service];
|
|
69
92
|
if (override) return override;
|
|
70
|
-
const envKey = `TOPOLO_SERVICE_URL_${service.toUpperCase()}`;
|
|
93
|
+
const envKey = `TOPOLO_SERVICE_URL_${service.replace(/\./g, "_").toUpperCase()}`;
|
|
71
94
|
const envValue = typeof process !== "undefined" ? process.env?.[envKey] : void 0;
|
|
72
95
|
if (envValue) return envValue;
|
|
73
96
|
return DEFAULT_SERVICE_URLS[service];
|
|
@@ -82,6 +105,7 @@ var TopoloClient = class {
|
|
|
82
105
|
requireConfirmForWrites;
|
|
83
106
|
timeoutMs;
|
|
84
107
|
fetchImpl;
|
|
108
|
+
debug;
|
|
85
109
|
constructor(options) {
|
|
86
110
|
if (!options.credential) throw new TopoloAuthError("credential is required");
|
|
87
111
|
if (!options.agent?.clientName) throw new TopoloAuthError("agent.clientName is required");
|
|
@@ -91,6 +115,14 @@ var TopoloClient = class {
|
|
|
91
115
|
this.requireConfirmForWrites = options.requireConfirmForWrites !== false;
|
|
92
116
|
this.timeoutMs = options.timeoutMs ?? 3e4;
|
|
93
117
|
this.fetchImpl = options.fetch ?? fetch;
|
|
118
|
+
this.debug = options.debug;
|
|
119
|
+
}
|
|
120
|
+
emit(event) {
|
|
121
|
+
if (!this.debug) return;
|
|
122
|
+
try {
|
|
123
|
+
this.debug(event);
|
|
124
|
+
} catch {
|
|
125
|
+
}
|
|
94
126
|
}
|
|
95
127
|
/**
|
|
96
128
|
* Low-level JSON request. Prefer the typed module helpers (identity, crm, ...)
|
|
@@ -117,27 +149,56 @@ var TopoloClient = class {
|
|
|
117
149
|
const platformServiceId = PLATFORM_SERVICE_IDS[opts.service];
|
|
118
150
|
if (platformServiceId) headers.set("X-Service-ID", platformServiceId);
|
|
119
151
|
applyAuthHeaders(headers, this.credential);
|
|
120
|
-
|
|
152
|
+
const requestId = generateRequestId();
|
|
153
|
+
applyAuditHeaders(headers, this.agent, requestId);
|
|
121
154
|
if (opts.headers) {
|
|
122
155
|
for (const [k, v] of Object.entries(opts.headers)) headers.set(k, v);
|
|
123
156
|
}
|
|
124
157
|
const controller = new AbortController();
|
|
125
158
|
const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
126
159
|
const signal = opts.signal ? mergeSignals(opts.signal, controller.signal) : controller.signal;
|
|
160
|
+
const urlStr = url.toString();
|
|
161
|
+
const startedAt = Date.now();
|
|
162
|
+
this.emit({ phase: "request", method, service: opts.service, path: opts.path, url: urlStr, requestId });
|
|
127
163
|
let res;
|
|
128
164
|
try {
|
|
129
|
-
res = await this.fetchImpl(
|
|
165
|
+
res = await this.fetchImpl(urlStr, {
|
|
130
166
|
method,
|
|
131
167
|
headers,
|
|
132
168
|
body: opts.body !== void 0 ? JSON.stringify(opts.body) : null,
|
|
133
169
|
signal
|
|
134
170
|
});
|
|
171
|
+
} catch (err) {
|
|
172
|
+
this.emit({
|
|
173
|
+
phase: "error",
|
|
174
|
+
method,
|
|
175
|
+
service: opts.service,
|
|
176
|
+
path: opts.path,
|
|
177
|
+
url: urlStr,
|
|
178
|
+
requestId,
|
|
179
|
+
durationMs: Date.now() - startedAt,
|
|
180
|
+
error: err instanceof Error ? err.message : String(err)
|
|
181
|
+
});
|
|
182
|
+
throw err;
|
|
135
183
|
} finally {
|
|
136
184
|
clearTimeout(timeoutId);
|
|
137
185
|
}
|
|
138
186
|
const contentType = res.headers.get("Content-Type") ?? "";
|
|
139
187
|
const parsed = contentType.includes("application/json") ? await res.json().catch(() => null) : await res.text().catch(() => null);
|
|
140
188
|
if (!res.ok) {
|
|
189
|
+
const durationMs = Date.now() - startedAt;
|
|
190
|
+
const message = describeError(parsed, `HTTP ${res.status}`);
|
|
191
|
+
this.emit({
|
|
192
|
+
phase: "error",
|
|
193
|
+
method,
|
|
194
|
+
service: opts.service,
|
|
195
|
+
path: opts.path,
|
|
196
|
+
url: urlStr,
|
|
197
|
+
requestId,
|
|
198
|
+
durationMs,
|
|
199
|
+
error: message,
|
|
200
|
+
status: res.status
|
|
201
|
+
});
|
|
141
202
|
if (res.status === 401) throw new TopoloAuthError(describeError(parsed, "Unauthorized"));
|
|
142
203
|
if (res.status === 403) {
|
|
143
204
|
throw new TopoloPermissionError(
|
|
@@ -147,6 +208,16 @@ var TopoloClient = class {
|
|
|
147
208
|
}
|
|
148
209
|
throw new TopoloHttpError(opts.service, opts.path, res.status, parsed);
|
|
149
210
|
}
|
|
211
|
+
this.emit({
|
|
212
|
+
phase: "response",
|
|
213
|
+
method,
|
|
214
|
+
service: opts.service,
|
|
215
|
+
path: opts.path,
|
|
216
|
+
url: urlStr,
|
|
217
|
+
requestId,
|
|
218
|
+
status: res.status,
|
|
219
|
+
durationMs: Date.now() - startedAt
|
|
220
|
+
});
|
|
150
221
|
return parsed;
|
|
151
222
|
}
|
|
152
223
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topolo/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Typed client SDK for the Topolo platform. Used by TopoloCli, TopoloMCP, and third-party agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,9 +18,11 @@
|
|
|
18
18
|
"src"
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
|
+
"generate:services": "node scripts/generate-services.mjs",
|
|
22
|
+
"prebuild": "node scripts/generate-services.mjs",
|
|
21
23
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
22
24
|
"dev": "tsup src/index.ts --format esm,cjs --dts --watch",
|
|
23
|
-
"typecheck": "tsc --noEmit",
|
|
25
|
+
"typecheck": "node scripts/generate-services.mjs && tsc --noEmit",
|
|
24
26
|
"test": "vitest run",
|
|
25
27
|
"test:watch": "vitest"
|
|
26
28
|
},
|
package/src/client.test.ts
CHANGED
|
@@ -167,5 +167,89 @@ describe('TopoloClient.request', () => {
|
|
|
167
167
|
});
|
|
168
168
|
});
|
|
169
169
|
|
|
170
|
+
describe('TopoloClient.debug hook', () => {
|
|
171
|
+
it('fires request → response on a successful call with stable requestId + duration', async () => {
|
|
172
|
+
const { impl } = stubFetch([{ body: { ok: true } }]);
|
|
173
|
+
const events: unknown[] = [];
|
|
174
|
+
const client = new TopoloClient({
|
|
175
|
+
credential: { kind: 'api_key', apiKey: 'x' },
|
|
176
|
+
agent: AGENT,
|
|
177
|
+
fetch: impl,
|
|
178
|
+
debug: (e) => events.push(e),
|
|
179
|
+
});
|
|
180
|
+
await client.request({ service: 'auth', path: '/api/me' });
|
|
181
|
+
expect(events).toHaveLength(2);
|
|
182
|
+
const typed = events as Array<{
|
|
183
|
+
phase: string;
|
|
184
|
+
method: string;
|
|
185
|
+
service: string;
|
|
186
|
+
path: string;
|
|
187
|
+
url: string;
|
|
188
|
+
requestId: string;
|
|
189
|
+
status?: number;
|
|
190
|
+
durationMs?: number;
|
|
191
|
+
}>;
|
|
192
|
+
const req = typed[0]!;
|
|
193
|
+
const res = typed[1]!;
|
|
194
|
+
expect(req.phase).toBe('request');
|
|
195
|
+
expect(req.method).toBe('GET');
|
|
196
|
+
expect(req.service).toBe('auth');
|
|
197
|
+
expect(req.path).toBe('/api/me');
|
|
198
|
+
expect(req.requestId).toMatch(/.+/);
|
|
199
|
+
expect(res.phase).toBe('response');
|
|
200
|
+
expect(res.status).toBe(200);
|
|
201
|
+
expect(res.requestId).toBe(req.requestId);
|
|
202
|
+
expect(typeof res.durationMs).toBe('number');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('fires request → error on 4xx with the unwrapped message and status', async () => {
|
|
206
|
+
const { impl } = stubFetch([
|
|
207
|
+
{ status: 401, body: { error: { code: 'auth_error', message: 'Expired' } } },
|
|
208
|
+
]);
|
|
209
|
+
const events: Array<{ phase: string; error?: string; status?: number }> = [];
|
|
210
|
+
const client = new TopoloClient({
|
|
211
|
+
credential: { kind: 'api_key', apiKey: 'x' },
|
|
212
|
+
agent: AGENT,
|
|
213
|
+
fetch: impl,
|
|
214
|
+
debug: (e) => events.push(e),
|
|
215
|
+
});
|
|
216
|
+
await expect(client.request({ service: 'auth', path: '/api/me' })).rejects.toThrow(TopoloAuthError);
|
|
217
|
+
expect(events.map((e) => e.phase)).toEqual(['request', 'error']);
|
|
218
|
+
expect(events[1]!.status).toBe(401);
|
|
219
|
+
expect(events[1]!.error).toBe('Expired');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('fires request → error on a thrown fetch (network failure)', async () => {
|
|
223
|
+
const impl = (vi.fn(async () => {
|
|
224
|
+
throw new Error('ECONNREFUSED');
|
|
225
|
+
}) as unknown) as typeof fetch;
|
|
226
|
+
const events: Array<{ phase: string; error?: string; status?: number }> = [];
|
|
227
|
+
const client = new TopoloClient({
|
|
228
|
+
credential: { kind: 'api_key', apiKey: 'x' },
|
|
229
|
+
agent: AGENT,
|
|
230
|
+
fetch: impl,
|
|
231
|
+
debug: (e) => events.push(e),
|
|
232
|
+
});
|
|
233
|
+
await expect(client.request({ service: 'auth', path: '/api/me' })).rejects.toThrow('ECONNREFUSED');
|
|
234
|
+
expect(events.map((e) => e.phase)).toEqual(['request', 'error']);
|
|
235
|
+
expect(events[1]!.error).toBe('ECONNREFUSED');
|
|
236
|
+
expect(events[1]!.status).toBeUndefined();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('swallows throws from the debug callback without affecting the request path', async () => {
|
|
240
|
+
const { impl } = stubFetch([{ body: { ok: true } }]);
|
|
241
|
+
const client = new TopoloClient({
|
|
242
|
+
credential: { kind: 'api_key', apiKey: 'x' },
|
|
243
|
+
agent: AGENT,
|
|
244
|
+
fetch: impl,
|
|
245
|
+
debug: () => {
|
|
246
|
+
throw new Error('callback blew up');
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
// Returns normally despite the callback throwing on both events.
|
|
250
|
+
await expect(client.request({ service: 'auth', path: '/api/me' })).resolves.toBeDefined();
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
170
254
|
// Silence unused-import warning if the bundler later tree-shakes these
|
|
171
255
|
void TopoloHttpError;
|
package/src/client.ts
CHANGED
|
@@ -26,8 +26,46 @@ export interface TopoloClientOptions {
|
|
|
26
26
|
timeoutMs?: number;
|
|
27
27
|
/** Injected fetch, for testing. Defaults to global fetch. */
|
|
28
28
|
fetch?: typeof fetch;
|
|
29
|
+
/**
|
|
30
|
+
* Observability hook. Fires once per request lifecycle (`request` at start,
|
|
31
|
+
* then exactly one of `response` or `error`). Safe to leave unset — off by
|
|
32
|
+
* default. Intended for CLI/MCP hosts to surface request-level diagnostics
|
|
33
|
+
* and for consumers building their own logging/tracing.
|
|
34
|
+
*/
|
|
35
|
+
debug?: (event: TopoloDebugEvent) => void;
|
|
29
36
|
}
|
|
30
37
|
|
|
38
|
+
export type TopoloDebugEvent =
|
|
39
|
+
| {
|
|
40
|
+
phase: 'request';
|
|
41
|
+
method: string;
|
|
42
|
+
service: ServiceId;
|
|
43
|
+
path: string;
|
|
44
|
+
url: string;
|
|
45
|
+
requestId: string;
|
|
46
|
+
}
|
|
47
|
+
| {
|
|
48
|
+
phase: 'response';
|
|
49
|
+
method: string;
|
|
50
|
+
service: ServiceId;
|
|
51
|
+
path: string;
|
|
52
|
+
url: string;
|
|
53
|
+
requestId: string;
|
|
54
|
+
status: number;
|
|
55
|
+
durationMs: number;
|
|
56
|
+
}
|
|
57
|
+
| {
|
|
58
|
+
phase: 'error';
|
|
59
|
+
method: string;
|
|
60
|
+
service: ServiceId;
|
|
61
|
+
path: string;
|
|
62
|
+
url: string;
|
|
63
|
+
requestId: string;
|
|
64
|
+
durationMs: number;
|
|
65
|
+
error: string;
|
|
66
|
+
status?: number;
|
|
67
|
+
};
|
|
68
|
+
|
|
31
69
|
export interface RequestOptions {
|
|
32
70
|
service: ServiceId;
|
|
33
71
|
path: string;
|
|
@@ -50,6 +88,7 @@ export class TopoloClient {
|
|
|
50
88
|
private readonly requireConfirmForWrites: boolean;
|
|
51
89
|
private readonly timeoutMs: number;
|
|
52
90
|
private readonly fetchImpl: typeof fetch;
|
|
91
|
+
private readonly debug?: (event: TopoloDebugEvent) => void;
|
|
53
92
|
|
|
54
93
|
constructor(options: TopoloClientOptions) {
|
|
55
94
|
if (!options.credential) throw new TopoloAuthError('credential is required');
|
|
@@ -60,6 +99,16 @@ export class TopoloClient {
|
|
|
60
99
|
this.requireConfirmForWrites = options.requireConfirmForWrites !== false;
|
|
61
100
|
this.timeoutMs = options.timeoutMs ?? 30_000;
|
|
62
101
|
this.fetchImpl = options.fetch ?? fetch;
|
|
102
|
+
this.debug = options.debug;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private emit(event: TopoloDebugEvent): void {
|
|
106
|
+
if (!this.debug) return;
|
|
107
|
+
try {
|
|
108
|
+
this.debug(event);
|
|
109
|
+
} catch {
|
|
110
|
+
// Never let a debug consumer crash the request path.
|
|
111
|
+
}
|
|
63
112
|
}
|
|
64
113
|
|
|
65
114
|
/**
|
|
@@ -91,7 +140,8 @@ export class TopoloClient {
|
|
|
91
140
|
if (platformServiceId) headers.set('X-Service-ID', platformServiceId);
|
|
92
141
|
|
|
93
142
|
applyAuthHeaders(headers, this.credential);
|
|
94
|
-
|
|
143
|
+
const requestId = generateRequestId();
|
|
144
|
+
applyAuditHeaders(headers, this.agent, requestId);
|
|
95
145
|
|
|
96
146
|
if (opts.headers) {
|
|
97
147
|
for (const [k, v] of Object.entries(opts.headers)) headers.set(k, v);
|
|
@@ -103,14 +153,30 @@ export class TopoloClient {
|
|
|
103
153
|
? mergeSignals(opts.signal, controller.signal)
|
|
104
154
|
: controller.signal;
|
|
105
155
|
|
|
156
|
+
const urlStr = url.toString();
|
|
157
|
+
const startedAt = Date.now();
|
|
158
|
+
this.emit({ phase: 'request', method, service: opts.service, path: opts.path, url: urlStr, requestId });
|
|
159
|
+
|
|
106
160
|
let res: Response;
|
|
107
161
|
try {
|
|
108
|
-
res = await this.fetchImpl(
|
|
162
|
+
res = await this.fetchImpl(urlStr, {
|
|
109
163
|
method,
|
|
110
164
|
headers,
|
|
111
165
|
body: opts.body !== undefined ? JSON.stringify(opts.body) : null,
|
|
112
166
|
signal,
|
|
113
167
|
});
|
|
168
|
+
} catch (err) {
|
|
169
|
+
this.emit({
|
|
170
|
+
phase: 'error',
|
|
171
|
+
method,
|
|
172
|
+
service: opts.service,
|
|
173
|
+
path: opts.path,
|
|
174
|
+
url: urlStr,
|
|
175
|
+
requestId,
|
|
176
|
+
durationMs: Date.now() - startedAt,
|
|
177
|
+
error: err instanceof Error ? err.message : String(err),
|
|
178
|
+
});
|
|
179
|
+
throw err;
|
|
114
180
|
} finally {
|
|
115
181
|
clearTimeout(timeoutId);
|
|
116
182
|
}
|
|
@@ -121,6 +187,19 @@ export class TopoloClient {
|
|
|
121
187
|
: await res.text().catch(() => null);
|
|
122
188
|
|
|
123
189
|
if (!res.ok) {
|
|
190
|
+
const durationMs = Date.now() - startedAt;
|
|
191
|
+
const message = describeError(parsed, `HTTP ${res.status}`);
|
|
192
|
+
this.emit({
|
|
193
|
+
phase: 'error',
|
|
194
|
+
method,
|
|
195
|
+
service: opts.service,
|
|
196
|
+
path: opts.path,
|
|
197
|
+
url: urlStr,
|
|
198
|
+
requestId,
|
|
199
|
+
durationMs,
|
|
200
|
+
error: message,
|
|
201
|
+
status: res.status,
|
|
202
|
+
});
|
|
124
203
|
if (res.status === 401) throw new TopoloAuthError(describeError(parsed, 'Unauthorized'));
|
|
125
204
|
if (res.status === 403) {
|
|
126
205
|
throw new TopoloPermissionError(
|
|
@@ -131,6 +210,17 @@ export class TopoloClient {
|
|
|
131
210
|
throw new TopoloHttpError(opts.service, opts.path, res.status, parsed);
|
|
132
211
|
}
|
|
133
212
|
|
|
213
|
+
this.emit({
|
|
214
|
+
phase: 'response',
|
|
215
|
+
method,
|
|
216
|
+
service: opts.service,
|
|
217
|
+
path: opts.path,
|
|
218
|
+
url: urlStr,
|
|
219
|
+
requestId,
|
|
220
|
+
status: res.status,
|
|
221
|
+
durationMs: Date.now() - startedAt,
|
|
222
|
+
});
|
|
223
|
+
|
|
134
224
|
return parsed as T;
|
|
135
225
|
}
|
|
136
226
|
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// AUTO-GENERATED by scripts/generate-services.mjs. Do not edit by hand.
|
|
2
|
+
// Source of truth: topolo.cloudcontrol.json in every app repo.
|
|
3
|
+
// Regenerate: npm run generate:services
|
|
4
|
+
|
|
5
|
+
export const SERVICES = {
|
|
6
|
+
agent: "https://topolo-agent-worker.topolo.workers.dev",
|
|
7
|
+
auth: "https://auth.topolo.app",
|
|
8
|
+
bugfix: "https://bugfix.topolo.app",
|
|
9
|
+
bytes: "https://r2-media-manager-worker.topolo.workers.dev",
|
|
10
|
+
chat: "https://chat.topolo.app",
|
|
11
|
+
commerce: "https://topolo-commerce-api.topolo.workers.dev",
|
|
12
|
+
crm: "https://crm-api.topolo.app",
|
|
13
|
+
forecast: "https://forecast-worker-production.topolo.workers.dev",
|
|
14
|
+
learn: "https://topololearn-api.topolo.workers.dev",
|
|
15
|
+
mdm: "https://nodo-mdm-api.topolo.app",
|
|
16
|
+
messaging: "https://messages.topolo.app",
|
|
17
|
+
nexus: "https://topolo-nexus-gateway.topolo.workers.dev",
|
|
18
|
+
one: "https://topoloone-worker.topolo.workers.dev",
|
|
19
|
+
pay: "https://topolo-pay.topolo.workers.dev",
|
|
20
|
+
"quro.api": "https://api.ol0.me",
|
|
21
|
+
"quro.redirect": "https://ol0.me",
|
|
22
|
+
roadmapper: "https://roadmapper-api.topolo.app",
|
|
23
|
+
social_studio: "https://topolo-social-studio-api.topolo.workers.dev",
|
|
24
|
+
socialize: "https://socialize-prod-api.topolo.workers.dev",
|
|
25
|
+
support: "https://support.topolo.app",
|
|
26
|
+
"web.api": "https://web-api.topolo.app",
|
|
27
|
+
"web.runtime": "https://sites.topolo.app",
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
export type ServiceId = keyof typeof SERVICES;
|
|
31
|
+
|
|
32
|
+
export const PLATFORM_SERVICE_IDS: Partial<Record<ServiceId, string>> = {
|
|
33
|
+
crm: "srv_iCwM4jGXcwlj",
|
|
34
|
+
};
|
package/src/services.ts
CHANGED
|
@@ -1,30 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Service registry — base URLs for
|
|
2
|
+
* Service registry — base URLs for every Topolo platform service.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* `
|
|
4
|
+
* Source of truth lives in each app's `topolo.cloudcontrol.json`. The
|
|
5
|
+
* `SERVICES` map is regenerated from those files via
|
|
6
|
+
* `scripts/generate-services.mjs`; edit the JSON, not the generated file.
|
|
7
|
+
*
|
|
8
|
+
* Any entry can be overridden per-call via `TopoloClient` options, or globally
|
|
9
|
+
* via env vars like `TOPOLO_SERVICE_URL_CRM` or `TOPOLO_SERVICE_URL_WEB_API`
|
|
10
|
+
* (dots in service IDs become underscores in env vars).
|
|
6
11
|
*
|
|
7
12
|
* IMPORTANT: The SDK NEVER accepts `orgId` as a parameter. Every request is
|
|
8
13
|
* scoped to the organization embedded in the auth credential (JWT claim or
|
|
9
14
|
* API-key binding). This is the load-bearing cross-org isolation guarantee.
|
|
10
15
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
import {
|
|
17
|
+
SERVICES,
|
|
18
|
+
PLATFORM_SERVICE_IDS,
|
|
19
|
+
type ServiceId,
|
|
20
|
+
} from './services.generated.js';
|
|
14
21
|
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
crm: 'https://topolo-crm-worker.topolo.workers.dev',
|
|
18
|
-
};
|
|
22
|
+
export { SERVICES, PLATFORM_SERVICE_IDS };
|
|
23
|
+
export type { ServiceId };
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
* Service IDs as registered in TopoloAuth's service catalog. These are sent in
|
|
22
|
-
* the `X-Service-ID` header when the platform needs to route an API-key
|
|
23
|
-
* introspection or permission check.
|
|
24
|
-
*/
|
|
25
|
-
export const PLATFORM_SERVICE_IDS: Partial<Record<ServiceId, string>> = {
|
|
26
|
-
crm: 'srv_iCwM4jGXcwlj',
|
|
27
|
-
};
|
|
25
|
+
export const DEFAULT_SERVICE_URLS: Record<ServiceId, string> = SERVICES;
|
|
28
26
|
|
|
29
27
|
export function resolveServiceUrl(
|
|
30
28
|
service: ServiceId,
|
|
@@ -33,7 +31,7 @@ export function resolveServiceUrl(
|
|
|
33
31
|
const override = overrides?.[service];
|
|
34
32
|
if (override) return override;
|
|
35
33
|
|
|
36
|
-
const envKey = `TOPOLO_SERVICE_URL_${service.toUpperCase()}`;
|
|
34
|
+
const envKey = `TOPOLO_SERVICE_URL_${service.replace(/\./g, '_').toUpperCase()}`;
|
|
37
35
|
const envValue = typeof process !== 'undefined' ? process.env?.[envKey] : undefined;
|
|
38
36
|
if (envValue) return envValue;
|
|
39
37
|
|