@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.
@@ -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
- const config = await readConfig();
629
- const profile = profileForEnvironment(config, environment);
630
- const context = profile.contexts?.[key];
631
- const credential = context?.credential;
632
- if (!credential || credential.kind !== "access_token") return credential ?? null;
633
- const oauthClientId = context.oauthClientId ?? profile.oauthClientId;
634
- if (!credential.refreshToken || !oauthClientId) return credential;
635
- const now = Math.floor(Date.now() / 1e3);
636
- if (!options.force && credential.expiresAt && credential.expiresAt - REFRESH_SKEW_SECONDS > now) return credential;
637
- const serviceUrls = resolveServiceUrls(profile.serviceUrls, environment);
638
- if (environment === "local" && !serviceUrls?.auth) {
639
- throw new TopoloSdkError(
640
- "environment_service_url_required",
641
- "Local Topolo context refresh requires an explicit local Auth service origin."
642
- );
643
- }
644
- try {
645
- const oauth = new TopoloOAuth({
646
- serviceUrls
647
- });
648
- const tokens = await oauth.refreshToken({
649
- clientId: oauthClientId,
650
- refreshToken: credential.refreshToken
651
- });
652
- const refreshed = {
653
- kind: "access_token",
654
- accessToken: tokens.access_token,
655
- ...tokens.refresh_token ? { refreshToken: tokens.refresh_token } : { refreshToken: credential.refreshToken },
656
- expiresAt: Math.floor(Date.now() / 1e3) + tokens.expires_in
657
- };
658
- await writeConfig(writeProfileConfig(config, environment, {
659
- ...profile.activeContext === key ? { credential: refreshed } : {},
660
- contexts: {
661
- ...profile.contexts ?? {},
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
- return refreshed;
670
- } catch {
671
- return credential;
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
- const config = await readConfig();
582
- const profile = profileForEnvironment(config, environment);
583
- const context = profile.contexts?.[key];
584
- const credential = context?.credential;
585
- if (!credential || credential.kind !== "access_token") return credential ?? null;
586
- const oauthClientId = context.oauthClientId ?? profile.oauthClientId;
587
- if (!credential.refreshToken || !oauthClientId) return credential;
588
- const now = Math.floor(Date.now() / 1e3);
589
- if (!options.force && credential.expiresAt && credential.expiresAt - REFRESH_SKEW_SECONDS > now) return credential;
590
- const serviceUrls = resolveServiceUrls(profile.serviceUrls, environment);
591
- if (environment === "local" && !serviceUrls?.auth) {
592
- throw new TopoloSdkError(
593
- "environment_service_url_required",
594
- "Local Topolo context refresh requires an explicit local Auth service origin."
595
- );
596
- }
597
- try {
598
- const oauth = new TopoloOAuth({
599
- serviceUrls
600
- });
601
- const tokens = await oauth.refreshToken({
602
- clientId: oauthClientId,
603
- refreshToken: credential.refreshToken
604
- });
605
- const refreshed = {
606
- kind: "access_token",
607
- accessToken: tokens.access_token,
608
- ...tokens.refresh_token ? { refreshToken: tokens.refresh_token } : { refreshToken: credential.refreshToken },
609
- expiresAt: Math.floor(Date.now() / 1e3) + tokens.expires_in
610
- };
611
- await writeConfig(writeProfileConfig(config, environment, {
612
- ...profile.activeContext === key ? { credential: refreshed } : {},
613
- contexts: {
614
- ...profile.contexts ?? {},
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
- return refreshed;
623
- } catch {
624
- return credential;
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.10.5",
4
- "topoloPublishedVersion": "0.10.5",
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",