diff --git a/packages/appstash/__tests__/config-store.test.ts b/packages/appstash/__tests__/config-store.test.ts index 516b12c..db3f39b 100644 --- a/packages/appstash/__tests__/config-store.test.ts +++ b/packages/appstash/__tests__/config-store.test.ts @@ -255,6 +255,95 @@ describe('createConfigStore', () => { }); }); + describe('multi-target contexts', () => { + it('should create a context with targets', () => { + const store = createStore(); + const ctx = store.createContext('production', { + endpoint: 'https://api.example.com/graphql', + targets: { + auth: { endpoint: 'https://auth.example.com/graphql' }, + members: { endpoint: 'https://members.example.com/graphql' }, + app: { endpoint: 'https://app.example.com/graphql' }, + }, + }); + expect(ctx.targets).toBeDefined(); + expect(ctx.targets!.auth.endpoint).toBe('https://auth.example.com/graphql'); + expect(ctx.targets!.members.endpoint).toBe('https://members.example.com/graphql'); + expect(ctx.targets!.app.endpoint).toBe('https://app.example.com/graphql'); + }); + + it('should load a context with targets', () => { + const store = createStore(); + store.createContext('production', { + endpoint: 'https://api.example.com/graphql', + targets: { + auth: { endpoint: 'https://auth.example.com/graphql' }, + }, + }); + const ctx = store.loadContext('production'); + expect(ctx!.targets!.auth.endpoint).toBe('https://auth.example.com/graphql'); + }); + + it('should create a context without targets (backward compatible)', () => { + const store = createStore(); + const ctx = store.createContext('production', { + endpoint: 'https://api.example.com/graphql', + }); + expect(ctx.targets).toBeUndefined(); + }); + + it('should get target endpoint from multi-target context', () => { + const store = createStore(); + store.createContext('production', { + endpoint: 'https://api.example.com/graphql', + targets: { + auth: { endpoint: 'https://auth.example.com/graphql' }, + app: { endpoint: 'https://app.example.com/graphql' }, + }, + }); + store.setCurrentContext('production'); + expect(store.getTargetEndpoint('auth')).toBe('https://auth.example.com/graphql'); + expect(store.getTargetEndpoint('app')).toBe('https://app.example.com/graphql'); + }); + + it('should fall back to main endpoint for unknown target', () => { + const store = createStore(); + store.createContext('production', { + endpoint: 'https://api.example.com/graphql', + targets: { + auth: { endpoint: 'https://auth.example.com/graphql' }, + }, + }); + store.setCurrentContext('production'); + expect(store.getTargetEndpoint('unknown')).toBe('https://api.example.com/graphql'); + }); + + it('should fall back to main endpoint for single-target context', () => { + const store = createStore(); + store.createContext('production', { + endpoint: 'https://api.example.com/graphql', + }); + store.setCurrentContext('production'); + expect(store.getTargetEndpoint('anything')).toBe('https://api.example.com/graphql'); + }); + + it('should return null when no current context for getTargetEndpoint', () => { + const store = createStore(); + expect(store.getTargetEndpoint('auth')).toBeNull(); + }); + + it('should get target endpoint by explicit context name', () => { + const store = createStore(); + store.createContext('staging', { + endpoint: 'https://staging.example.com/graphql', + targets: { + auth: { endpoint: 'https://auth.staging.example.com/graphql' }, + }, + }); + expect(store.getTargetEndpoint('auth', 'staging')).toBe('https://auth.staging.example.com/graphql'); + }); + }); + describe('full workflow', () => { it('should support the complete context + auth workflow', () => { const store = createStore(); diff --git a/packages/appstash/src/config-store.ts b/packages/appstash/src/config-store.ts index f417f2c..540e132 100644 --- a/packages/appstash/src/config-store.ts +++ b/packages/appstash/src/config-store.ts @@ -2,9 +2,14 @@ import * as fs from 'fs'; import * as path from 'path'; import { appstash, resolve } from './index'; +export interface ContextTargetEndpoint { + endpoint: string; +} + export interface ContextConfig { name: string; endpoint: string; + targets?: Record; createdAt: string; updatedAt: string; } @@ -31,12 +36,13 @@ export interface ConfigStore { loadSettings(): GlobalSettings; saveSettings(settings: GlobalSettings): void; - createContext(name: string, options: { endpoint: string }): ContextConfig; + createContext(name: string, options: { endpoint: string; targets?: Record }): ContextConfig; loadContext(name: string): ContextConfig | null; listContexts(): ContextConfig[]; deleteContext(name: string): boolean; getCurrentContext(): ContextConfig | null; setCurrentContext(name: string): boolean; + getTargetEndpoint(targetName: string, contextName?: string): string | null; setCredentials(contextName: string, creds: ContextCredentials): void; getCredentials(contextName: string): ContextCredentials | null; @@ -97,7 +103,7 @@ export function createConfigStore(toolName: string, options?: ConfigStoreOptions return readJson(contextPath(name), null); } - function createContext(name: string, options: { endpoint: string }): ContextConfig { + function createContext(name: string, options: { endpoint: string; targets?: Record }): ContextConfig { const now = new Date().toISOString(); const context: ContextConfig = { name, @@ -105,6 +111,9 @@ export function createConfigStore(toolName: string, options?: ConfigStoreOptions createdAt: now, updatedAt: now, }; + if (options.targets) { + context.targets = options.targets; + } writeJson(contextPath(name), context); return context; } @@ -203,6 +212,20 @@ export function createConfigStore(toolName: string, options?: ConfigStoreOptions return true; } + function getTargetEndpoint(targetName: string, contextName?: string): string | null { + let ctx: ContextConfig | null; + if (contextName) { + ctx = loadContext(contextName); + } else { + ctx = getCurrentContext(); + } + if (!ctx) return null; + if (ctx.targets && ctx.targets[targetName]) { + return ctx.targets[targetName].endpoint; + } + return ctx.endpoint; + } + return { loadSettings, saveSettings, @@ -212,6 +235,7 @@ export function createConfigStore(toolName: string, options?: ConfigStoreOptions deleteContext, getCurrentContext, setCurrentContext, + getTargetEndpoint, setCredentials, getCredentials, removeCredentials, diff --git a/packages/appstash/src/index.ts b/packages/appstash/src/index.ts index 6070721..a649900 100644 --- a/packages/appstash/src/index.ts +++ b/packages/appstash/src/index.ts @@ -279,6 +279,7 @@ export type { ConfigStoreOptions, ContextConfig, ContextCredentials, + ContextTargetEndpoint, Credentials, GlobalSettings, } from './config-store';