@hiliosai/sdk 0.1.0

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.
Files changed (35) hide show
  1. package/package.json +27 -0
  2. package/src/configs/index.ts +1 -0
  3. package/src/configs/permissions.ts +109 -0
  4. package/src/env.ts +12 -0
  5. package/src/errors/auth.error.ts +33 -0
  6. package/src/errors/index.ts +2 -0
  7. package/src/errors/permission.error.ts +17 -0
  8. package/src/examples/cache-usage.example.ts +293 -0
  9. package/src/index.ts +5 -0
  10. package/src/middlewares/cache.middleware.ts +278 -0
  11. package/src/middlewares/context-helpers.middleware.ts +159 -0
  12. package/src/middlewares/datasource.middleware.ts +71 -0
  13. package/src/middlewares/health.middleware.ts +134 -0
  14. package/src/middlewares/index.ts +6 -0
  15. package/src/middlewares/memoize.middleware.ts +33 -0
  16. package/src/middlewares/permissions.middleware.ts +162 -0
  17. package/src/service/define-integration.ts +237 -0
  18. package/src/service/define-service.ts +77 -0
  19. package/src/service/example-user/datasources/index.ts +8 -0
  20. package/src/service/example-user/datasources/user.datasource.ts +7 -0
  21. package/src/service/example-user/user.service.ts +31 -0
  22. package/src/service/example-user/utils.ts +0 -0
  23. package/src/types/context.ts +41 -0
  24. package/src/types/datasource.ts +23 -0
  25. package/src/types/index.ts +8 -0
  26. package/src/types/integration.ts +48 -0
  27. package/src/types/message.ts +95 -0
  28. package/src/types/platform.ts +39 -0
  29. package/src/types/service.ts +208 -0
  30. package/src/types/tenant.ts +4 -0
  31. package/src/types/user.ts +16 -0
  32. package/src/utils/context-cache.ts +70 -0
  33. package/src/utils/permission-calculator.ts +62 -0
  34. package/tsconfig.json +13 -0
  35. package/tsup.config.ts +6 -0
@@ -0,0 +1,95 @@
1
+ import type {IntegrationPlatform} from './platform';
2
+
3
+ export interface MessageParticipant {
4
+ id: string;
5
+ name?: string;
6
+ avatar?: string;
7
+ metadata?: Record<string, any>;
8
+ }
9
+
10
+ export enum MessageContentType {
11
+ TEXT = 'text',
12
+ IMAGE = 'image',
13
+ VIDEO = 'video',
14
+ AUDIO = 'audio',
15
+ FILE = 'file',
16
+ LOCATION = 'location',
17
+ BUTTONS = 'buttons',
18
+ CAROUSEL = 'carousel',
19
+ REACTION = 'reaction',
20
+ }
21
+
22
+ export interface MessageContent {
23
+ type: MessageContentType;
24
+ text?: string;
25
+ media?: {
26
+ url: string;
27
+ mimeType?: string;
28
+ size?: number;
29
+ thumbnail?: string;
30
+ };
31
+ location?: {
32
+ latitude: number;
33
+ longitude: number;
34
+ address?: string;
35
+ };
36
+ buttons?: MessageButton[];
37
+ carousel?: CarouselItem[];
38
+ reaction?: {
39
+ emoji: string;
40
+ messageId: string;
41
+ };
42
+ metadata?: Record<string, any>;
43
+ }
44
+
45
+ export interface MessageButton {
46
+ id: string;
47
+ text: string;
48
+ type: 'postback' | 'url' | 'call';
49
+ payload?: string;
50
+ url?: string;
51
+ phoneNumber?: string;
52
+ }
53
+
54
+ export interface CarouselItem {
55
+ title: string;
56
+ subtitle?: string;
57
+ imageUrl?: string;
58
+ buttons?: MessageButton[];
59
+ }
60
+
61
+ export interface NormalizedMessage {
62
+ id: string;
63
+ conversationId: string;
64
+ from: MessageParticipant;
65
+ to: MessageParticipant;
66
+ content: MessageContent;
67
+ timestamp: number;
68
+ platform: IntegrationPlatform;
69
+ replyTo?: string;
70
+ threadId?: string;
71
+ metadata?: Record<string, any>;
72
+ }
73
+
74
+ export interface PlatformMessage {
75
+ platform: IntegrationPlatform;
76
+ rawPayload: any;
77
+ }
78
+
79
+ export interface WebhookEvent {
80
+ tenantId: string;
81
+ channelId: string;
82
+ integrationId: string;
83
+ platform: IntegrationPlatform;
84
+ rawPayload: any;
85
+ rawBody?: string;
86
+ headers: Record<string, string>;
87
+ timestamp: number;
88
+ }
89
+
90
+ export interface SendResult {
91
+ success: boolean;
92
+ messageId?: string;
93
+ error?: Error;
94
+ metadata?: Record<string, any>;
95
+ }
@@ -0,0 +1,39 @@
1
+ export enum IntegrationPlatform {
2
+ WHATSAPP = 'whatsapp',
3
+ TELEGRAM = 'telegram',
4
+ SLACK = 'slack',
5
+ EMAIL = 'email',
6
+ SMS = 'sms',
7
+ INSTAGRAM = 'instagram',
8
+ FACEBOOK = 'facebook',
9
+ DISCORD = 'discord',
10
+ WEBCHAT = 'webchat',
11
+ CUSTOM = 'custom',
12
+ }
13
+
14
+ export enum IntegrationStatus {
15
+ CONFIGURED = 'CONFIGURED',
16
+ ACTIVE = 'ACTIVE',
17
+ INACTIVE = 'INACTIVE',
18
+ ERROR = 'ERROR',
19
+ SUSPENDED = 'SUSPENDED',
20
+ }
21
+
22
+ export enum IntegrationCapability {
23
+ SEND_MESSAGE = 'send_message',
24
+ RECEIVE_MESSAGE = 'receive_message',
25
+ SEND_IMAGE = 'send_image',
26
+ SEND_VIDEO = 'send_video',
27
+ SEND_AUDIO = 'send_audio',
28
+ SEND_FILE = 'send_file',
29
+ SEND_LOCATION = 'send_location',
30
+ SEND_BUTTONS = 'send_buttons',
31
+ SEND_CAROUSEL = 'send_carousel',
32
+ TYPING_INDICATOR = 'typing_indicator',
33
+ READ_RECEIPT = 'read_receipt',
34
+ GROUP_CHAT = 'group_chat',
35
+ REACTIONS = 'reactions',
36
+ THREADS = 'threads',
37
+ VOICE_CALL = 'voice_call',
38
+ VIDEO_CALL = 'video_call',
39
+ }
@@ -0,0 +1,208 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import type {
3
+ ActionSchema,
4
+ ServiceSchema as MoleculerServiceSchema,
5
+ ServiceEvents,
6
+ ServiceHooks,
7
+ ServiceMethods,
8
+ } from 'moleculer';
9
+
10
+ import type {DatasourceConstructorRegistry} from '../middlewares/datasource.middleware';
11
+ import type {CacheOptions} from '../middlewares/cache.middleware';
12
+ import type {AppContext} from './context';
13
+ import type {BaseIntegration, IntegrationConfig} from './integration';
14
+ import type {
15
+ NormalizedMessage,
16
+ PlatformMessage,
17
+ SendResult,
18
+ WebhookEvent,
19
+ } from './message';
20
+
21
+ // Type to infer TypeScript types from Moleculer parameter schemas
22
+ type InferParamsType<T> = T extends Record<string, any>
23
+ ? {
24
+ [K in keyof T]: T[K] extends 'string'
25
+ ? string
26
+ : T[K] extends 'number'
27
+ ? number
28
+ : T[K] extends 'boolean'
29
+ ? boolean
30
+ : T[K] extends 'array'
31
+ ? any[]
32
+ : T[K] extends 'object'
33
+ ? Record<string, any>
34
+ : T[K] extends {type: 'string'}
35
+ ? string
36
+ : T[K] extends {type: 'number'}
37
+ ? number
38
+ : T[K] extends {type: 'boolean'}
39
+ ? boolean
40
+ : T[K] extends {type: 'array'}
41
+ ? any[]
42
+ : T[K] extends {type: 'object'}
43
+ ? Record<string, any>
44
+ : unknown;
45
+ }
46
+ : unknown;
47
+
48
+ // Action schema with automatic parameter type inference
49
+ export type ActionSchemaWithContext<
50
+ TDatasources = unknown,
51
+ TParams = unknown
52
+ > = Pick<
53
+ ActionSchema,
54
+ | 'name'
55
+ | 'rest'
56
+ | 'visibility'
57
+ | 'service'
58
+ | 'cache'
59
+ | 'tracing'
60
+ | 'bulkhead'
61
+ | 'circuitBreaker'
62
+ | 'retryPolicy'
63
+ | 'fallback'
64
+ | 'hooks'
65
+ > & {
66
+ params?: TParams;
67
+ handler: (
68
+ ctx: AppContext<TDatasources, InferParamsType<TParams>>
69
+ ) => Promise<unknown> | unknown;
70
+ permissions?: string | string[];
71
+ };
72
+
73
+ // Direct action handler type
74
+ export type ActionHandler<TDatasources = unknown> = (
75
+ ctx: AppContext<TDatasources>
76
+ ) => Promise<unknown> | unknown;
77
+
78
+ // Service actions - individual action type will handle inference
79
+ export type ServiceActionsSchema<TDatasources = unknown> = {
80
+ [key: string]:
81
+ | ({
82
+ params?: any;
83
+ handler: (
84
+ ctx: AppContext<TDatasources, any>
85
+ ) => Promise<unknown> | unknown;
86
+ permissions?: string | string[];
87
+ } & Pick<
88
+ ActionSchema,
89
+ | 'name'
90
+ | 'rest'
91
+ | 'visibility'
92
+ | 'service'
93
+ | 'cache'
94
+ | 'tracing'
95
+ | 'bulkhead'
96
+ | 'circuitBreaker'
97
+ | 'retryPolicy'
98
+ | 'fallback'
99
+ | 'hooks'
100
+ >)
101
+ | ActionHandler<TDatasources>
102
+ | false;
103
+ };
104
+
105
+ // Custom ServiceSchema that uses our action schema
106
+ export interface ServiceSchema<TSettings = unknown, TDatasources = unknown>
107
+ extends Omit<MoleculerServiceSchema<TSettings>, 'actions'> {
108
+ name: string;
109
+ version?: string | number;
110
+ settings?: TSettings;
111
+ actions?: ServiceActionsSchema<TDatasources>;
112
+ events?: ServiceEvents;
113
+ methods?: ServiceMethods;
114
+ hooks?: ServiceHooks;
115
+ dependencies?: string | string[];
116
+ metadata?: Record<string, any>;
117
+ created?: (this: MoleculerServiceSchema<TSettings>) => void | Promise<void>;
118
+ started?: (this: MoleculerServiceSchema<TSettings>) => void | Promise<void>;
119
+ stopped?: (this: MoleculerServiceSchema<TSettings>) => void | Promise<void>;
120
+ }
121
+
122
+ // ServiceConfig is what users provide to defineService
123
+ export type ServiceConfig<
124
+ TSettings = unknown,
125
+ TDatasources = unknown
126
+ > = ServiceSchema<TSettings, TDatasources> & {
127
+ datasources?: DatasourceConstructorRegistry;
128
+ cache?: CacheOptions;
129
+ };
130
+
131
+ // Integration-specific types
132
+ export interface IntegrationServiceConfig<
133
+ TPlatformMessage extends PlatformMessage = PlatformMessage,
134
+ TSettings = unknown,
135
+ TContext extends AppContext = AppContext
136
+ > extends Omit<ServiceConfig<TSettings>, 'name'> {
137
+ name: string;
138
+ integration: BaseIntegration;
139
+
140
+ // Core integration methods
141
+ normalize(webhook: WebhookEvent): Promise<NormalizedMessage[]>;
142
+ transform?(
143
+ message: NormalizedMessage,
144
+ config: IntegrationConfig
145
+ ): Promise<TPlatformMessage>;
146
+ validateWebhook?(webhook: WebhookEvent): boolean;
147
+ sendMessage(
148
+ ctx: TContext,
149
+ message: NormalizedMessage,
150
+ config: IntegrationConfig
151
+ ): Promise<SendResult>;
152
+
153
+ // Optional webhook verification
154
+ verifyWebhook?(params: {
155
+ mode: string;
156
+ token: string;
157
+ challenge: string;
158
+ }): string | null;
159
+
160
+ // Optional health check
161
+ checkHealth?(
162
+ ctx: TContext,
163
+ config: IntegrationConfig
164
+ ): Promise<{
165
+ status: 'healthy' | 'unhealthy' | 'degraded';
166
+ message?: string;
167
+ details?: Record<string, any>;
168
+ }>;
169
+
170
+ // Optional credential validation
171
+ validateCredentials?(credentials: Record<string, string>): Promise<boolean>;
172
+
173
+ // Optional signature validation
174
+ validateSignature?(webhook: WebhookEvent): boolean;
175
+ }
176
+
177
+ export interface IntegrationServiceSchema<
178
+ TPlatformMessage extends PlatformMessage = PlatformMessage,
179
+ TSettings = unknown
180
+ > extends MoleculerServiceSchema<TSettings> {
181
+ integration: BaseIntegration;
182
+ normalize(webhook: WebhookEvent): Promise<NormalizedMessage[]>;
183
+ transform?(
184
+ message: NormalizedMessage,
185
+ config: IntegrationConfig
186
+ ): Promise<TPlatformMessage>;
187
+ validateWebhook?(webhook: WebhookEvent): boolean;
188
+ sendMessage(
189
+ ctx: AppContext,
190
+ message: NormalizedMessage,
191
+ config: IntegrationConfig
192
+ ): Promise<SendResult>;
193
+ verifyWebhook?(params: {
194
+ mode: string;
195
+ token: string;
196
+ challenge: string;
197
+ }): string | null;
198
+ checkHealth?(
199
+ ctx: AppContext,
200
+ config: IntegrationConfig
201
+ ): Promise<{
202
+ status: 'healthy' | 'unhealthy' | 'degraded';
203
+ message?: string;
204
+ details?: Record<string, any>;
205
+ }>;
206
+ validateCredentials?(credentials: Record<string, string>): Promise<boolean>;
207
+ validateSignature?(webhook: WebhookEvent): boolean;
208
+ }
@@ -0,0 +1,4 @@
1
+ export interface Tenant {
2
+ id: string;
3
+ name: string;
4
+ }
@@ -0,0 +1,16 @@
1
+ export const UserRole = {
2
+ OWNER: 'OWNER',
3
+ ADMIN: 'ADMIN',
4
+ MANAGER: 'MANAGER',
5
+ AGENT: 'AGENT',
6
+ VIEWER: 'VIEWER'
7
+ } as const
8
+
9
+ export interface User {
10
+ id: string;
11
+ email: string;
12
+ roles: string[];
13
+ permissions: string[];
14
+ tenantId: string;
15
+ name: string;
16
+ }
@@ -0,0 +1,70 @@
1
+ interface CacheEntry {
2
+ value: unknown;
3
+ timestamp: number;
4
+ }
5
+
6
+ export class ContextCache {
7
+ private static instance: ContextCache;
8
+ private memoryCache = new Map<string, CacheEntry>();
9
+ private readonly TTL = 5 * 60 * 1000; // 5 minutes
10
+ private cleanupInterval: NodeJS.Timeout;
11
+
12
+ private constructor() {
13
+ // Cleanup expired entries every minute
14
+ this.cleanupInterval = setInterval(() => this.cleanup(), 60 * 1000);
15
+ }
16
+
17
+ static getInstance(): ContextCache {
18
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
19
+ if (!ContextCache.instance) {
20
+ ContextCache.instance = new ContextCache();
21
+ }
22
+ return ContextCache.instance;
23
+ }
24
+
25
+ async get<T>(key: string, factory: () => Promise<T> | T): Promise<T> {
26
+ const cached = this.memoryCache.get(key);
27
+
28
+ if (cached && Date.now() - cached.timestamp < this.TTL) {
29
+ return cached.value as T;
30
+ }
31
+
32
+ const value = await factory();
33
+ this.memoryCache.set(key, {value, timestamp: Date.now()});
34
+
35
+ return value;
36
+ }
37
+
38
+ set<T>(key: string, value: T): void {
39
+ this.memoryCache.set(key, {value, timestamp: Date.now()});
40
+ }
41
+
42
+ delete(key: string): boolean {
43
+ return this.memoryCache.delete(key);
44
+ }
45
+
46
+ clear(): void {
47
+ this.memoryCache.clear();
48
+ }
49
+
50
+ private cleanup(): void {
51
+ const now = Date.now();
52
+ const keysToDelete: string[] = [];
53
+
54
+ this.memoryCache.forEach((entry, key) => {
55
+ if (now - entry.timestamp > this.TTL) {
56
+ keysToDelete.push(key);
57
+ }
58
+ });
59
+
60
+ keysToDelete.forEach((key) => this.memoryCache.delete(key));
61
+ }
62
+
63
+ destroy(): void {
64
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
65
+ if (this.cleanupInterval) {
66
+ clearInterval(this.cleanupInterval);
67
+ }
68
+ this.clear();
69
+ }
70
+ }
@@ -0,0 +1,62 @@
1
+ import {ROLE_PERMISSIONS} from '../configs';
2
+ import type {User} from '../types';
3
+
4
+ export class PermissionCalculator {
5
+ static calculateUserPermissions(user: User): string[] {
6
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
7
+ if (!user || !Array.isArray(user.roles)) {
8
+ return [];
9
+ }
10
+
11
+ // Combine explicit permissions with role-based permissions
12
+ const explicitPermissions = Array.isArray(user.permissions)
13
+ ? user.permissions
14
+ : [];
15
+
16
+ // Get all permissions from user's roles
17
+ const rolePermissions = user.roles.flatMap((role: string) => {
18
+ return ROLE_PERMISSIONS[role] ?? [];
19
+ });
20
+
21
+ // Remove duplicates and return
22
+ const allPermissions = [...explicitPermissions, ...rolePermissions];
23
+ const uniquePermissions: string[] = [];
24
+
25
+ allPermissions.forEach((permission) => {
26
+ if (!uniquePermissions.includes(permission)) {
27
+ uniquePermissions.push(permission);
28
+ }
29
+ });
30
+
31
+ return uniquePermissions;
32
+ }
33
+
34
+ static hasPermission(user: User, permission: string): boolean {
35
+ if (typeof permission !== 'string' || !permission.trim()) {
36
+ return false;
37
+ }
38
+
39
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
40
+ if (!user || typeof user !== 'object') {
41
+ return false;
42
+ }
43
+
44
+ if (!Array.isArray(user.roles)) {
45
+ return false;
46
+ }
47
+
48
+ // Check explicit permissions first
49
+ if (
50
+ Array.isArray(user.permissions) &&
51
+ user.permissions.includes(permission)
52
+ ) {
53
+ return true;
54
+ }
55
+
56
+ // Check role-based permissions
57
+ return user.roles.some((role: string) => {
58
+ const rolePermissions = ROLE_PERMISSIONS[role] ?? [];
59
+ return rolePermissions.includes(permission);
60
+ });
61
+ }
62
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "@hiliosai/typescript/base.json",
3
+ "compilerOptions": {
4
+ "incremental": true,
5
+ "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
6
+ "paths": {
7
+ "@pkg/*": ["../*/dist"]
8
+ },
9
+ "baseUrl": "."
10
+ },
11
+ "include": ["*.ts", "src", "src/types/**/*"],
12
+ "exclude": ["node_modules"]
13
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,6 @@
1
+
2
+ import {defineConfig} from '@pkg/dev-utils';
3
+
4
+ export default defineConfig({
5
+ entry: ['src/index.ts'],
6
+ });