@topolo/sdk 0.10.5 → 0.11.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/README.md +8 -28
- package/dist/index.cjs +32 -2241
- package/dist/index.d.cts +52 -1459
- package/dist/index.d.ts +52 -1459
- package/dist/index.js +32 -2231
- package/dist/node/credential-store.cjs +76 -46
- package/dist/node/credential-store.js +77 -47
- package/package.json +5 -2
|
@@ -378,8 +378,7 @@ var TOPOLO_ENVIRONMENT_SERVICE_URLS = {
|
|
|
378
378
|
},
|
|
379
379
|
staging: {
|
|
380
380
|
auth: "https://auth.stg.topolo.us",
|
|
381
|
-
developers: "https://developers.stg.topolo.us"
|
|
382
|
-
seed: "https://seed.stg.topolo.us"
|
|
381
|
+
developers: "https://developers.stg.topolo.us"
|
|
383
382
|
},
|
|
384
383
|
development: {
|
|
385
384
|
auth: "https://auth.topolo.dev",
|
|
@@ -572,6 +571,41 @@ async function resolveCredential(environment = selectedEnvironment()) {
|
|
|
572
571
|
return profileForEnvironment(config, environment).credential ?? null;
|
|
573
572
|
}
|
|
574
573
|
var REFRESH_SKEW_SECONDS = 60;
|
|
574
|
+
var CONFIG_LOCK_RETRY_MS = 25;
|
|
575
|
+
var CONFIG_LOCK_TIMEOUT_MS = 5e3;
|
|
576
|
+
var CONFIG_LOCK_STALE_MS = 12e4;
|
|
577
|
+
async function withConfigWriteLock(operation) {
|
|
578
|
+
const lockPath = `${configPath()}.lock`;
|
|
579
|
+
await (0, import_promises.mkdir)((0, import_node_path.dirname)(lockPath), { recursive: true });
|
|
580
|
+
const startedAt = Date.now();
|
|
581
|
+
while (true) {
|
|
582
|
+
try {
|
|
583
|
+
await (0, import_promises.mkdir)(lockPath);
|
|
584
|
+
break;
|
|
585
|
+
} catch (error) {
|
|
586
|
+
if (error.code !== "EEXIST") throw error;
|
|
587
|
+
try {
|
|
588
|
+
const lock = await (0, import_promises.stat)(lockPath);
|
|
589
|
+
if (Date.now() - lock.mtimeMs > CONFIG_LOCK_STALE_MS) {
|
|
590
|
+
await (0, import_promises.rm)(lockPath, { recursive: true, force: true });
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
} catch (inspectionError) {
|
|
594
|
+
if (inspectionError.code !== "ENOENT") throw inspectionError;
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
if (Date.now() - startedAt >= CONFIG_LOCK_TIMEOUT_MS) {
|
|
598
|
+
throw new Error(`Timed out waiting for credential configuration lock at ${lockPath}.`);
|
|
599
|
+
}
|
|
600
|
+
await new Promise((resolve) => setTimeout(resolve, CONFIG_LOCK_RETRY_MS));
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
try {
|
|
604
|
+
return await operation();
|
|
605
|
+
} finally {
|
|
606
|
+
await (0, import_promises.rm)(lockPath, { recursive: true, force: true });
|
|
607
|
+
}
|
|
608
|
+
}
|
|
575
609
|
async function refreshCredentialIfNeeded(environment = selectedEnvironment(), options = {}) {
|
|
576
610
|
const prefix = envVarPrefix(environment);
|
|
577
611
|
if (process.env[`${prefix}_API_KEY`] || process.env[`${prefix}_ACCESS_TOKEN`] || environment === "production" && process.env.TOPOLO_API_KEY || environment === "production" && process.env.TOPOLO_ACCESS_TOKEN) {
|
|
@@ -625,51 +659,47 @@ async function refreshCredentialIfNeeded(environment = selectedEnvironment(), op
|
|
|
625
659
|
}
|
|
626
660
|
}
|
|
627
661
|
async function refreshContextCredentialIfNeeded(environment, key, options = {}) {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
serviceUrls
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
[key]: {
|
|
663
|
-
...context,
|
|
664
|
-
credential: refreshed,
|
|
665
|
-
oauthClientId
|
|
662
|
+
return withConfigWriteLock(async () => {
|
|
663
|
+
const config = await readConfig();
|
|
664
|
+
const profile = profileForEnvironment(config, environment);
|
|
665
|
+
const context = profile.contexts?.[key];
|
|
666
|
+
const credential = context?.credential;
|
|
667
|
+
if (!credential || credential.kind !== "access_token") return credential ?? null;
|
|
668
|
+
const oauthClientId = context.oauthClientId ?? profile.oauthClientId;
|
|
669
|
+
if (!credential.refreshToken || !oauthClientId) return credential;
|
|
670
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
671
|
+
if (!options.force && credential.expiresAt && credential.expiresAt - REFRESH_SKEW_SECONDS > now) return credential;
|
|
672
|
+
const serviceUrls = resolveServiceUrls(profile.serviceUrls, environment);
|
|
673
|
+
if (environment === "local" && !serviceUrls?.auth) {
|
|
674
|
+
throw new TopoloSdkError(
|
|
675
|
+
"environment_service_url_required",
|
|
676
|
+
"Local Topolo context refresh requires an explicit local Auth service origin."
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
try {
|
|
680
|
+
const oauth = new TopoloOAuth({ serviceUrls });
|
|
681
|
+
const tokens = await oauth.refreshToken({
|
|
682
|
+
clientId: oauthClientId,
|
|
683
|
+
refreshToken: credential.refreshToken
|
|
684
|
+
});
|
|
685
|
+
const refreshed = {
|
|
686
|
+
kind: "access_token",
|
|
687
|
+
accessToken: tokens.access_token,
|
|
688
|
+
...tokens.refresh_token ? { refreshToken: tokens.refresh_token } : { refreshToken: credential.refreshToken },
|
|
689
|
+
expiresAt: Math.floor(Date.now() / 1e3) + tokens.expires_in
|
|
690
|
+
};
|
|
691
|
+
await writeConfig(writeProfileConfig(config, environment, {
|
|
692
|
+
...profile.activeContext === key ? { credential: refreshed } : {},
|
|
693
|
+
contexts: {
|
|
694
|
+
...profile.contexts ?? {},
|
|
695
|
+
[key]: { ...context, credential: refreshed, oauthClientId }
|
|
666
696
|
}
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
}
|
|
697
|
+
}));
|
|
698
|
+
return refreshed;
|
|
699
|
+
} catch {
|
|
700
|
+
return credential;
|
|
701
|
+
}
|
|
702
|
+
});
|
|
673
703
|
}
|
|
674
704
|
function resolveAgentName(configAgentName) {
|
|
675
705
|
return process.env.TOPOLO_AGENT_NAME ?? configAgentName;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/node/credential-store.ts
|
|
2
|
-
import { mkdir, readFile, writeFile, chmod } from "fs/promises";
|
|
2
|
+
import { mkdir, readFile, writeFile, chmod, rm, stat } from "fs/promises";
|
|
3
3
|
import { existsSync } from "fs";
|
|
4
4
|
import { homedir, platform } from "os";
|
|
5
5
|
import { dirname, join } from "path";
|
|
@@ -331,8 +331,7 @@ var TOPOLO_ENVIRONMENT_SERVICE_URLS = {
|
|
|
331
331
|
},
|
|
332
332
|
staging: {
|
|
333
333
|
auth: "https://auth.stg.topolo.us",
|
|
334
|
-
developers: "https://developers.stg.topolo.us"
|
|
335
|
-
seed: "https://seed.stg.topolo.us"
|
|
334
|
+
developers: "https://developers.stg.topolo.us"
|
|
336
335
|
},
|
|
337
336
|
development: {
|
|
338
337
|
auth: "https://auth.topolo.dev",
|
|
@@ -525,6 +524,41 @@ async function resolveCredential(environment = selectedEnvironment()) {
|
|
|
525
524
|
return profileForEnvironment(config, environment).credential ?? null;
|
|
526
525
|
}
|
|
527
526
|
var REFRESH_SKEW_SECONDS = 60;
|
|
527
|
+
var CONFIG_LOCK_RETRY_MS = 25;
|
|
528
|
+
var CONFIG_LOCK_TIMEOUT_MS = 5e3;
|
|
529
|
+
var CONFIG_LOCK_STALE_MS = 12e4;
|
|
530
|
+
async function withConfigWriteLock(operation) {
|
|
531
|
+
const lockPath = `${configPath()}.lock`;
|
|
532
|
+
await mkdir(dirname(lockPath), { recursive: true });
|
|
533
|
+
const startedAt = Date.now();
|
|
534
|
+
while (true) {
|
|
535
|
+
try {
|
|
536
|
+
await mkdir(lockPath);
|
|
537
|
+
break;
|
|
538
|
+
} catch (error) {
|
|
539
|
+
if (error.code !== "EEXIST") throw error;
|
|
540
|
+
try {
|
|
541
|
+
const lock = await stat(lockPath);
|
|
542
|
+
if (Date.now() - lock.mtimeMs > CONFIG_LOCK_STALE_MS) {
|
|
543
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
} catch (inspectionError) {
|
|
547
|
+
if (inspectionError.code !== "ENOENT") throw inspectionError;
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
if (Date.now() - startedAt >= CONFIG_LOCK_TIMEOUT_MS) {
|
|
551
|
+
throw new Error(`Timed out waiting for credential configuration lock at ${lockPath}.`);
|
|
552
|
+
}
|
|
553
|
+
await new Promise((resolve) => setTimeout(resolve, CONFIG_LOCK_RETRY_MS));
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
try {
|
|
557
|
+
return await operation();
|
|
558
|
+
} finally {
|
|
559
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
560
|
+
}
|
|
561
|
+
}
|
|
528
562
|
async function refreshCredentialIfNeeded(environment = selectedEnvironment(), options = {}) {
|
|
529
563
|
const prefix = envVarPrefix(environment);
|
|
530
564
|
if (process.env[`${prefix}_API_KEY`] || process.env[`${prefix}_ACCESS_TOKEN`] || environment === "production" && process.env.TOPOLO_API_KEY || environment === "production" && process.env.TOPOLO_ACCESS_TOKEN) {
|
|
@@ -578,51 +612,47 @@ async function refreshCredentialIfNeeded(environment = selectedEnvironment(), op
|
|
|
578
612
|
}
|
|
579
613
|
}
|
|
580
614
|
async function refreshContextCredentialIfNeeded(environment, key, options = {}) {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
serviceUrls
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
[key]: {
|
|
616
|
-
...context,
|
|
617
|
-
credential: refreshed,
|
|
618
|
-
oauthClientId
|
|
615
|
+
return withConfigWriteLock(async () => {
|
|
616
|
+
const config = await readConfig();
|
|
617
|
+
const profile = profileForEnvironment(config, environment);
|
|
618
|
+
const context = profile.contexts?.[key];
|
|
619
|
+
const credential = context?.credential;
|
|
620
|
+
if (!credential || credential.kind !== "access_token") return credential ?? null;
|
|
621
|
+
const oauthClientId = context.oauthClientId ?? profile.oauthClientId;
|
|
622
|
+
if (!credential.refreshToken || !oauthClientId) return credential;
|
|
623
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
624
|
+
if (!options.force && credential.expiresAt && credential.expiresAt - REFRESH_SKEW_SECONDS > now) return credential;
|
|
625
|
+
const serviceUrls = resolveServiceUrls(profile.serviceUrls, environment);
|
|
626
|
+
if (environment === "local" && !serviceUrls?.auth) {
|
|
627
|
+
throw new TopoloSdkError(
|
|
628
|
+
"environment_service_url_required",
|
|
629
|
+
"Local Topolo context refresh requires an explicit local Auth service origin."
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
try {
|
|
633
|
+
const oauth = new TopoloOAuth({ serviceUrls });
|
|
634
|
+
const tokens = await oauth.refreshToken({
|
|
635
|
+
clientId: oauthClientId,
|
|
636
|
+
refreshToken: credential.refreshToken
|
|
637
|
+
});
|
|
638
|
+
const refreshed = {
|
|
639
|
+
kind: "access_token",
|
|
640
|
+
accessToken: tokens.access_token,
|
|
641
|
+
...tokens.refresh_token ? { refreshToken: tokens.refresh_token } : { refreshToken: credential.refreshToken },
|
|
642
|
+
expiresAt: Math.floor(Date.now() / 1e3) + tokens.expires_in
|
|
643
|
+
};
|
|
644
|
+
await writeConfig(writeProfileConfig(config, environment, {
|
|
645
|
+
...profile.activeContext === key ? { credential: refreshed } : {},
|
|
646
|
+
contexts: {
|
|
647
|
+
...profile.contexts ?? {},
|
|
648
|
+
[key]: { ...context, credential: refreshed, oauthClientId }
|
|
619
649
|
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
}
|
|
650
|
+
}));
|
|
651
|
+
return refreshed;
|
|
652
|
+
} catch {
|
|
653
|
+
return credential;
|
|
654
|
+
}
|
|
655
|
+
});
|
|
626
656
|
}
|
|
627
657
|
function resolveAgentName(configAgentName) {
|
|
628
658
|
return process.env.TOPOLO_AGENT_NAME ?? configAgentName;
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topolo/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"topoloPublishedVersion": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
|
+
"topoloPublishedVersion": "0.11.1",
|
|
5
5
|
"description": "Typed client SDK for the Topolo platform. Used by TopoloCli, TopoloMCP, and third-party agents.",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"author": "Topolo",
|
|
8
|
+
"homepage": "https://docs.topolo.app",
|
|
6
9
|
"type": "module",
|
|
7
10
|
"main": "./dist/index.js",
|
|
8
11
|
"module": "./dist/index.js",
|