@oxyhq/core 1.10.0 → 1.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.
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Topics Methods Mixin
3
+ *
4
+ * Provides methods for topic discovery and management
5
+ */
6
+ import type { OxyServicesBase } from '../OxyServices.base';
7
+ import type { TopicData, TopicTranslation } from '../models/Topic';
8
+ export declare function OxyServicesTopicsMixin<T extends typeof OxyServicesBase>(Base: T): {
9
+ new (...args: any[]): {
10
+ /**
11
+ * Get top-level topic categories
12
+ * @param locale - Optional locale for translated results
13
+ * @returns List of category topics
14
+ */
15
+ getTopicCategories(locale?: string): Promise<TopicData[]>;
16
+ /**
17
+ * Search topics by query string
18
+ * @param query - Search query
19
+ * @param limit - Optional result limit
20
+ * @returns Matching topics
21
+ */
22
+ searchTopics(query: string, limit?: number): Promise<TopicData[]>;
23
+ /**
24
+ * List topics with optional filters
25
+ * @param options - Filter and pagination options
26
+ * @returns List of topics
27
+ */
28
+ listTopics(options?: {
29
+ type?: string;
30
+ q?: string;
31
+ limit?: number;
32
+ offset?: number;
33
+ locale?: string;
34
+ }): Promise<TopicData[]>;
35
+ /**
36
+ * Get a single topic by slug
37
+ * @param slug - Topic slug
38
+ * @returns Topic data
39
+ */
40
+ getTopicBySlug(slug: string): Promise<TopicData>;
41
+ /**
42
+ * Resolve an array of topic names to existing or newly created topics
43
+ * @param names - Array of { name, type } objects to resolve
44
+ * @returns Resolved topic data
45
+ */
46
+ resolveTopicNames(names: Array<{
47
+ name: string;
48
+ type: string;
49
+ }>): Promise<TopicData[]>;
50
+ /**
51
+ * Update metadata for a topic
52
+ * @param slug - Topic slug
53
+ * @param data - Metadata fields to update
54
+ * @returns Updated topic data
55
+ */
56
+ updateTopicMetadata(slug: string, data: {
57
+ description?: string;
58
+ translations?: Record<string, TopicTranslation>;
59
+ }): Promise<TopicData>;
60
+ httpService: import("../HttpService").HttpService;
61
+ cloudURL: string;
62
+ config: import("../OxyServices.base").OxyConfig;
63
+ __resetTokensForTests(): void;
64
+ makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
65
+ getBaseURL(): string;
66
+ getClient(): import("../HttpService").HttpService;
67
+ getMetrics(): {
68
+ totalRequests: number;
69
+ successfulRequests: number;
70
+ failedRequests: number;
71
+ cacheHits: number;
72
+ cacheMisses: number;
73
+ averageResponseTime: number;
74
+ };
75
+ clearCache(): void;
76
+ clearCacheEntry(key: string): void;
77
+ getCacheStats(): {
78
+ size: number;
79
+ hits: number;
80
+ misses: number;
81
+ hitRate: number;
82
+ };
83
+ getCloudURL(): string;
84
+ setTokens(accessToken: string, refreshToken?: string): void;
85
+ clearTokens(): void;
86
+ _cachedUserId: string | null | undefined;
87
+ _cachedAccessToken: string | null;
88
+ getCurrentUserId(): string | null;
89
+ hasValidToken(): boolean;
90
+ getAccessToken(): string | null;
91
+ waitForAuth(timeoutMs?: number): Promise<boolean>;
92
+ withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
93
+ maxRetries?: number;
94
+ retryDelay?: number;
95
+ authTimeoutMs?: number;
96
+ }): Promise<T_1>;
97
+ validate(): Promise<boolean>;
98
+ handleError(error: unknown): Error;
99
+ healthCheck(): Promise<{
100
+ status: string;
101
+ users?: number;
102
+ timestamp?: string;
103
+ [key: string]: any;
104
+ }>;
105
+ };
106
+ } & T;
@@ -15,9 +15,32 @@ export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(B
15
15
  */
16
16
  searchProfiles(query: string, pagination?: PaginationParams): Promise<SearchProfilesResponse>;
17
17
  /**
18
- * Get profile recommendations
18
+ * Resolve a fediverse handle to an Oxy user profile.
19
+ * Performs WebFinger discovery and returns the user, or null if not found.
20
+ * @param handle - Fediverse handle (e.g. "@user@mastodon.social" or "user@domain")
19
21
  */
20
- getProfileRecommendations(): Promise<Array<{
22
+ resolveProfile(handle: string): Promise<User | null>;
23
+ /**
24
+ * Resolve (find or create) a non-local user. All user creation for
25
+ * external accounts (federated, agent, automated) goes through this
26
+ * method — calling services never write user data directly.
27
+ */
28
+ resolveExternalUser(data: {
29
+ type: "federated" | "agent" | "automated";
30
+ username: string;
31
+ actorUri?: string;
32
+ domain?: string;
33
+ displayName?: string;
34
+ avatar?: string;
35
+ bio?: string;
36
+ ownerId?: string;
37
+ }): Promise<User>;
38
+ /**
39
+ * Get profile recommendations, optionally filtering out specific user types.
40
+ */
41
+ getProfileRecommendations(options?: {
42
+ excludeTypes?: Array<"federated" | "agent" | "automated">;
43
+ }): Promise<Array<{
21
44
  id: string;
22
45
  username: string;
23
46
  name?: {
@@ -0,0 +1,32 @@
1
+ export declare enum TopicType {
2
+ CATEGORY = "category",
3
+ TOPIC = "topic",
4
+ ENTITY = "entity"
5
+ }
6
+ export declare enum TopicSource {
7
+ SEED = "seed",
8
+ AI = "ai",
9
+ MANUAL = "manual",
10
+ SYSTEM = "system"
11
+ }
12
+ export interface TopicTranslation {
13
+ displayName: string;
14
+ description?: string;
15
+ }
16
+ export interface TopicData {
17
+ _id: string;
18
+ name: string;
19
+ slug: string;
20
+ displayName: string;
21
+ description: string;
22
+ type: TopicType;
23
+ source: TopicSource;
24
+ aliases: string[];
25
+ parentTopicId?: string;
26
+ icon?: string;
27
+ image?: string;
28
+ isActive: boolean;
29
+ translations?: Record<string, TopicTranslation>;
30
+ createdAt: string;
31
+ updatedAt: string;
32
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/core",
3
- "version": "1.10.0",
3
+ "version": "1.11.1",
4
4
  "description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
package/src/index.ts CHANGED
@@ -40,6 +40,8 @@ export type { KeyPair, SignedMessage, AuthChallenge, RecoveryPhraseResult } from
40
40
  // --- Models & Types ---
41
41
  export * from './models/interfaces';
42
42
  export * from './models/session';
43
+ export type { TopicData, TopicTranslation } from './models/Topic';
44
+ export { TopicType, TopicSource } from './models/Topic';
43
45
 
44
46
  // --- Device Management ---
45
47
  export { DeviceManager } from './utils/deviceManager';
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Topics Methods Mixin
3
+ *
4
+ * Provides methods for topic discovery and management
5
+ */
6
+ import type { OxyServicesBase } from '../OxyServices.base';
7
+ import type { TopicData, TopicTranslation } from '../models/Topic';
8
+ import { CACHE_TIMES } from './mixinHelpers';
9
+
10
+ export function OxyServicesTopicsMixin<T extends typeof OxyServicesBase>(Base: T) {
11
+ return class extends Base {
12
+ constructor(...args: any[]) {
13
+ super(...(args as [any]));
14
+ }
15
+
16
+ /**
17
+ * Get top-level topic categories
18
+ * @param locale - Optional locale for translated results
19
+ * @returns List of category topics
20
+ */
21
+ async getTopicCategories(locale?: string): Promise<TopicData[]> {
22
+ try {
23
+ const params: Record<string, string> = {};
24
+ if (locale) params.locale = locale;
25
+ return await this.makeRequest('GET', '/topics/categories', params, {
26
+ cache: true,
27
+ cacheTTL: CACHE_TIMES.EXTRA_LONG,
28
+ });
29
+ } catch (error) {
30
+ throw this.handleError(error);
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Search topics by query string
36
+ * @param query - Search query
37
+ * @param limit - Optional result limit
38
+ * @returns Matching topics
39
+ */
40
+ async searchTopics(query: string, limit?: number): Promise<TopicData[]> {
41
+ try {
42
+ const params: Record<string, string | number> = { q: query };
43
+ if (limit) params.limit = limit;
44
+ return await this.makeRequest('GET', '/topics/search', params, {
45
+ cache: false,
46
+ });
47
+ } catch (error) {
48
+ throw this.handleError(error);
49
+ }
50
+ }
51
+
52
+ /**
53
+ * List topics with optional filters
54
+ * @param options - Filter and pagination options
55
+ * @returns List of topics
56
+ */
57
+ async listTopics(options?: {
58
+ type?: string;
59
+ q?: string;
60
+ limit?: number;
61
+ offset?: number;
62
+ locale?: string;
63
+ }): Promise<TopicData[]> {
64
+ try {
65
+ const params: Record<string, string | number> = {};
66
+ if (options?.type) params.type = options.type;
67
+ if (options?.q) params.q = options.q;
68
+ if (options?.limit) params.limit = options.limit;
69
+ if (options?.offset) params.offset = options.offset;
70
+ if (options?.locale) params.locale = options.locale;
71
+ return await this.makeRequest('GET', '/topics', params, {
72
+ cache: true,
73
+ cacheTTL: CACHE_TIMES.SHORT,
74
+ });
75
+ } catch (error) {
76
+ throw this.handleError(error);
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Get a single topic by slug
82
+ * @param slug - Topic slug
83
+ * @returns Topic data
84
+ */
85
+ async getTopicBySlug(slug: string): Promise<TopicData> {
86
+ try {
87
+ return await this.makeRequest('GET', `/topics/${slug}`, undefined, {
88
+ cache: true,
89
+ cacheTTL: CACHE_TIMES.LONG,
90
+ });
91
+ } catch (error) {
92
+ throw this.handleError(error);
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Resolve an array of topic names to existing or newly created topics
98
+ * @param names - Array of { name, type } objects to resolve
99
+ * @returns Resolved topic data
100
+ */
101
+ async resolveTopicNames(
102
+ names: Array<{ name: string; type: string }>
103
+ ): Promise<TopicData[]> {
104
+ try {
105
+ return await this.makeRequest('POST', '/topics/resolve', { names }, {
106
+ cache: false,
107
+ });
108
+ } catch (error) {
109
+ throw this.handleError(error);
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Update metadata for a topic
115
+ * @param slug - Topic slug
116
+ * @param data - Metadata fields to update
117
+ * @returns Updated topic data
118
+ */
119
+ async updateTopicMetadata(
120
+ slug: string,
121
+ data: {
122
+ description?: string;
123
+ translations?: Record<string, TopicTranslation>;
124
+ }
125
+ ): Promise<TopicData> {
126
+ try {
127
+ return await this.makeRequest('PATCH', `/topics/${slug}`, data, {
128
+ cache: false,
129
+ });
130
+ } catch (error) {
131
+ throw this.handleError(error);
132
+ }
133
+ }
134
+ };
135
+ }
@@ -86,9 +86,48 @@ export function OxyServicesUserMixin<T extends typeof OxyServicesBase>(Base: T)
86
86
  }
87
87
 
88
88
  /**
89
- * Get profile recommendations
89
+ * Resolve a fediverse handle to an Oxy user profile.
90
+ * Performs WebFinger discovery and returns the user, or null if not found.
91
+ * @param handle - Fediverse handle (e.g. "@user@mastodon.social" or "user@domain")
90
92
  */
91
- async getProfileRecommendations(): Promise<Array<{
93
+ async resolveProfile(handle: string): Promise<User | null> {
94
+ try {
95
+ const result = await this.makeRequest<{ data: User | null }>('GET', '/profiles/resolve', {
96
+ handle,
97
+ }, {
98
+ cache: true,
99
+ cacheTTL: 24 * 60 * 60 * 1000, // 24h cache — matches server-side staleness window
100
+ });
101
+ return result.data ?? null;
102
+ } catch {
103
+ return null;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Resolve (find or create) a non-local user. All user creation for
109
+ * external accounts (federated, agent, automated) goes through this
110
+ * method — calling services never write user data directly.
111
+ */
112
+ async resolveExternalUser(data: {
113
+ type: 'federated' | 'agent' | 'automated';
114
+ username: string;
115
+ actorUri?: string;
116
+ domain?: string;
117
+ displayName?: string;
118
+ avatar?: string;
119
+ bio?: string;
120
+ ownerId?: string;
121
+ }): Promise<User> {
122
+ return this.makeRequest<User>('PUT', '/users/resolve', data);
123
+ }
124
+
125
+ /**
126
+ * Get profile recommendations, optionally filtering out specific user types.
127
+ */
128
+ async getProfileRecommendations(options?: {
129
+ excludeTypes?: Array<'federated' | 'agent' | 'automated'>;
130
+ }): Promise<Array<{
92
131
  id: string;
93
132
  username: string;
94
133
  name?: { first?: string; last?: string; full?: string };
@@ -102,8 +141,11 @@ export function OxyServicesUserMixin<T extends typeof OxyServicesBase>(Base: T)
102
141
  _count?: { followers: number; following: number };
103
142
  [key: string]: unknown;
104
143
  }>> {
144
+ const params = options?.excludeTypes?.length
145
+ ? { excludeTypes: options.excludeTypes.join(',') }
146
+ : undefined;
105
147
  return this.withAuthRetry(async () => {
106
- return await this.makeRequest('GET', '/profiles/recommendations', undefined, { cache: true });
148
+ return await this.makeRequest('GET', '/profiles/recommendations', params, { cache: true });
107
149
  }, 'getProfileRecommendations');
108
150
  }
109
151
 
@@ -23,6 +23,7 @@ import { OxyServicesDevicesMixin } from './OxyServices.devices';
23
23
  import { OxyServicesSecurityMixin } from './OxyServices.security';
24
24
  import { OxyServicesUtilityMixin } from './OxyServices.utility';
25
25
  import { OxyServicesFeaturesMixin } from './OxyServices.features';
26
+ import { OxyServicesTopicsMixin } from './OxyServices.topics';
26
27
 
27
28
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
29
  type MixinFunction = (Base: any) => any;
@@ -66,6 +67,7 @@ const MIXIN_PIPELINE: MixinFunction[] = [
66
67
  OxyServicesDevicesMixin,
67
68
  OxyServicesSecurityMixin,
68
69
  OxyServicesFeaturesMixin,
70
+ OxyServicesTopicsMixin,
69
71
 
70
72
  // Utility (last, can use all above)
71
73
  OxyServicesUtilityMixin,
@@ -0,0 +1,35 @@
1
+ export enum TopicType {
2
+ CATEGORY = 'category',
3
+ TOPIC = 'topic',
4
+ ENTITY = 'entity',
5
+ }
6
+
7
+ export enum TopicSource {
8
+ SEED = 'seed',
9
+ AI = 'ai',
10
+ MANUAL = 'manual',
11
+ SYSTEM = 'system',
12
+ }
13
+
14
+ export interface TopicTranslation {
15
+ displayName: string;
16
+ description?: string;
17
+ }
18
+
19
+ export interface TopicData {
20
+ _id: string;
21
+ name: string;
22
+ slug: string;
23
+ displayName: string;
24
+ description: string;
25
+ type: TopicType;
26
+ source: TopicSource;
27
+ aliases: string[];
28
+ parentTopicId?: string;
29
+ icon?: string;
30
+ image?: string;
31
+ isActive: boolean;
32
+ translations?: Record<string, TopicTranslation>;
33
+ createdAt: string;
34
+ updatedAt: string;
35
+ }