@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.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/index.js +5 -2
- package/dist/cjs/mixins/OxyServices.topics.js +123 -0
- package/dist/cjs/mixins/OxyServices.user.js +33 -3
- package/dist/cjs/mixins/index.js +2 -0
- package/dist/cjs/models/Topic.js +16 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/mixins/OxyServices.topics.js +120 -0
- package/dist/esm/mixins/OxyServices.user.js +33 -3
- package/dist/esm/mixins/index.js +2 -0
- package/dist/esm/models/Topic.js +13 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/mixins/OxyServices.topics.d.ts +106 -0
- package/dist/types/mixins/OxyServices.user.d.ts +25 -2
- package/dist/types/models/Topic.d.ts +32 -0
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/mixins/OxyServices.topics.ts +135 -0
- package/src/mixins/OxyServices.user.ts +45 -3
- package/src/mixins/index.ts +2 -0
- package/src/models/Topic.ts +35 -0
|
@@ -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
|
-
*
|
|
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
|
-
|
|
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
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
|
-
*
|
|
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
|
|
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',
|
|
148
|
+
return await this.makeRequest('GET', '/profiles/recommendations', params, { cache: true });
|
|
107
149
|
}, 'getProfileRecommendations');
|
|
108
150
|
}
|
|
109
151
|
|
package/src/mixins/index.ts
CHANGED
|
@@ -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
|
+
}
|