@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
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@hiliosai/sdk",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": "./src/index.ts"
7
+ },
8
+ "scripts": {
9
+ "build": "tsup",
10
+ "format": "bunx prettier --write ./**/*.{ts,json,md,yaml}"
11
+ },
12
+ "dependencies": {
13
+ "@keyv/redis": "5.1.5",
14
+ "@ltv/env": "4.0.3",
15
+ "keyv": "5.5.5",
16
+ "lodash": "4.17.21",
17
+ "moleculer": "0.14.35"
18
+ },
19
+ "devDependencies": {
20
+ "@hiliosai/prettier": "workspace:*",
21
+ "@hiliosai/typescript": "workspace:*",
22
+ "@pkg/dev-utils": "workspace:*",
23
+ "@types/lodash": "4.17.21",
24
+ "bun-types": "latest"
25
+ },
26
+ "prettier": "@hiliosai/prettier"
27
+ }
@@ -0,0 +1 @@
1
+ export * from './permissions';
@@ -0,0 +1,109 @@
1
+ // Permission constants
2
+ export const PERMISSIONS = {
3
+ // Authentication required
4
+ AUTHENTICATED: 'authenticated',
5
+
6
+ // Role-based permissions
7
+ OWNER: 'OWNER',
8
+ ADMIN: 'ADMIN',
9
+ MANAGER: 'MANAGER',
10
+ AGENT: 'AGENT',
11
+ VIEWER: 'VIEWER',
12
+ DEVELOPER: 'DEVELOPER',
13
+
14
+ // Entity-specific permissions
15
+ TENANT_OWNER: 'tenant.owner',
16
+ TENANT_MEMBER: 'tenant.member',
17
+
18
+ // Resource permissions
19
+ 'users.read': 'users.read',
20
+ 'users.write': 'users.write',
21
+ 'users.delete': 'users.delete',
22
+ 'tenants.read': 'tenants.read',
23
+ 'tenants.write': 'tenants.write',
24
+ 'tenants.delete': 'tenants.delete',
25
+ 'conversations.read': 'conversations.read',
26
+ 'conversations.write': 'conversations.write',
27
+ 'conversations.delete': 'conversations.delete',
28
+ 'messages.read': 'messages.read',
29
+ 'messages.write': 'messages.write',
30
+ 'settings.read': 'settings.read',
31
+ 'settings.write': 'settings.write',
32
+ 'config.read': 'config.read',
33
+ 'config.write': 'config.write',
34
+ 'billing.read': 'billing.read',
35
+ 'billing.write': 'billing.write',
36
+ } as const;
37
+
38
+ // Role hierarchy and permissions mapping
39
+ export const ROLE_PERMISSIONS: Record<string, string[]> = {
40
+ [PERMISSIONS.OWNER]: [
41
+ PERMISSIONS.AUTHENTICATED,
42
+ PERMISSIONS['users.read'],
43
+ PERMISSIONS['users.write'],
44
+ PERMISSIONS['users.delete'],
45
+ PERMISSIONS['tenants.read'],
46
+ PERMISSIONS['tenants.write'],
47
+ PERMISSIONS['tenants.delete'],
48
+ PERMISSIONS['conversations.read'],
49
+ PERMISSIONS['conversations.write'],
50
+ PERMISSIONS['conversations.delete'],
51
+ PERMISSIONS['messages.read'],
52
+ PERMISSIONS['messages.write'],
53
+ PERMISSIONS['settings.read'],
54
+ PERMISSIONS['settings.write'],
55
+ PERMISSIONS['config.read'],
56
+ PERMISSIONS['config.write'],
57
+ PERMISSIONS['billing.read'],
58
+ PERMISSIONS['billing.write'],
59
+ ],
60
+ [PERMISSIONS.ADMIN]: [
61
+ PERMISSIONS.AUTHENTICATED,
62
+ PERMISSIONS['users.read'],
63
+ PERMISSIONS['users.write'],
64
+ PERMISSIONS['users.delete'],
65
+ PERMISSIONS['tenants.read'],
66
+ PERMISSIONS['tenants.write'],
67
+ PERMISSIONS['tenants.delete'],
68
+ PERMISSIONS['conversations.read'],
69
+ PERMISSIONS['conversations.write'],
70
+ PERMISSIONS['conversations.delete'],
71
+ PERMISSIONS['messages.read'],
72
+ PERMISSIONS['messages.write'],
73
+ PERMISSIONS['settings.read'],
74
+ PERMISSIONS['settings.write'],
75
+ PERMISSIONS['config.read'],
76
+ PERMISSIONS['config.write'],
77
+ PERMISSIONS['billing.read'],
78
+ ],
79
+ [PERMISSIONS.MANAGER]: [
80
+ PERMISSIONS.AUTHENTICATED,
81
+ PERMISSIONS['users.read'],
82
+ PERMISSIONS['users.write'],
83
+ PERMISSIONS['conversations.read'],
84
+ PERMISSIONS['conversations.write'],
85
+ PERMISSIONS['messages.read'],
86
+ PERMISSIONS['messages.write'],
87
+ PERMISSIONS['settings.read'],
88
+ PERMISSIONS['settings.write'],
89
+ ],
90
+ [PERMISSIONS.AGENT]: [
91
+ PERMISSIONS.AUTHENTICATED,
92
+ PERMISSIONS['conversations.read'],
93
+ PERMISSIONS['conversations.write'],
94
+ PERMISSIONS['messages.read'],
95
+ PERMISSIONS['messages.write'],
96
+ ],
97
+ [PERMISSIONS.VIEWER]: [
98
+ PERMISSIONS.AUTHENTICATED,
99
+ PERMISSIONS['conversations.read'],
100
+ PERMISSIONS['messages.read'],
101
+ ],
102
+ [PERMISSIONS.DEVELOPER]: [
103
+ PERMISSIONS.AUTHENTICATED,
104
+ // Only specific debug/development permissions, not full access
105
+ 'system.debug',
106
+ 'health.check',
107
+ PERMISSIONS['config.read'],
108
+ ],
109
+ };
package/src/env.ts ADDED
@@ -0,0 +1,12 @@
1
+ import env from '@ltv/env';
2
+
3
+ const nodeEnv = env.string('NODE_ENV', 'development');
4
+
5
+ export const isDev = nodeEnv === 'development';
6
+ export const isTest = nodeEnv === 'test';
7
+ export const isProd = nodeEnv === 'production';
8
+ export const REDIS_URL = env.string('REDIS_URL');
9
+
10
+ export default env;
11
+ export type Env = typeof env;
12
+ export {nodeEnv};
@@ -0,0 +1,33 @@
1
+ export class AuthenticationError extends Error {
2
+ public readonly code = 'AUTH_REQUIRED';
3
+ public readonly statusCode = 401;
4
+ public readonly data: Record<string, unknown> | undefined;
5
+
6
+ constructor(message: string, data?: Record<string, unknown>) {
7
+ super(message);
8
+ this.name = 'AuthenticationError';
9
+ this.data = data;
10
+
11
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
12
+ if (Error.captureStackTrace) {
13
+ Error.captureStackTrace(this, AuthenticationError);
14
+ }
15
+ }
16
+ }
17
+
18
+ export class TenantError extends Error {
19
+ public readonly code = 'TENANT_REQUIRED';
20
+ public readonly statusCode = 401;
21
+ public readonly data: Record<string, unknown> | undefined;
22
+
23
+ constructor(message: string, data?: Record<string, unknown>) {
24
+ super(message);
25
+ this.name = 'TenantError';
26
+ this.data = data;
27
+
28
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
29
+ if (Error.captureStackTrace) {
30
+ Error.captureStackTrace(this, TenantError);
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,2 @@
1
+ export * from './permission.error';
2
+ export * from './auth.error';
@@ -0,0 +1,17 @@
1
+ export class PermissionError extends Error {
2
+ public readonly code = 'PERMISSION_DENIED';
3
+ public readonly statusCode = 403;
4
+ public readonly data: Record<string, unknown> | undefined;
5
+
6
+ constructor(message: string, data?: Record<string, unknown>) {
7
+ super(message);
8
+ this.name = 'PermissionError';
9
+ this.data = data;
10
+
11
+ // Maintains proper stack trace for where our error was thrown (only available on V8)
12
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
13
+ if (Error.captureStackTrace) {
14
+ Error.captureStackTrace(this, PermissionError);
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,293 @@
1
+ /**
2
+ * Example of how to use the cache middleware in a Moleculer service
3
+ * This file demonstrates various caching patterns and best practices
4
+ */
5
+
6
+ import type {AppContext} from '../types';
7
+ import {CacheMiddleware} from '../middlewares/cache.middleware';
8
+ import {REDIS_URL} from '../env';
9
+
10
+ // Initialize cache on application start
11
+ export function initializeCache() {
12
+ CacheMiddleware.init({
13
+ redisUrl: REDIS_URL, // Falls back to in-memory if not set
14
+ ttl: 10 * 60 * 1000, // 10 minutes default TTL
15
+ namespace: 'app', // Namespace for cache keys
16
+ });
17
+ }
18
+
19
+ /**
20
+ * Example 1: Simple get/set pattern
21
+ */
22
+ export async function getUserWithCache(ctx: AppContext, userId: string) {
23
+ const cacheKey = `user:${userId}`;
24
+
25
+ // Try to get from cache
26
+ const cached = await ctx.cache.get(cacheKey);
27
+ if (cached) {
28
+ return cached;
29
+ }
30
+
31
+ // Cache miss - fetch from database
32
+ const user = await fetchUserFromDatabase(userId);
33
+
34
+ // Store in cache for 5 minutes
35
+ await ctx.cache.set(cacheKey, user, 5 * 60 * 1000);
36
+
37
+ return user;
38
+ }
39
+
40
+ /**
41
+ * Example 2: Using the wrap pattern (recommended)
42
+ * Automatically handles get/set logic
43
+ */
44
+ export async function getUserWithWrap(ctx: AppContext, userId: string) {
45
+ return ctx.cache.wrap(
46
+ `user:${userId}`,
47
+ async () => {
48
+ // This function only runs if cache miss
49
+ return fetchUserFromDatabase(userId);
50
+ },
51
+ 5 * 60 * 1000 // TTL
52
+ );
53
+ }
54
+
55
+ /**
56
+ * Example 3: Caching complex calculations
57
+ */
58
+ export async function getUserPermissions(ctx: AppContext, userId: string) {
59
+ return ctx.cache.wrap(
60
+ `permissions:${userId}`,
61
+ async () => {
62
+ // Complex permission calculation
63
+ const user = await fetchUserFromDatabase(userId);
64
+ const roles = await fetchUserRoles(userId);
65
+ const permissions = calculatePermissions(user, roles);
66
+ return permissions;
67
+ },
68
+ 10 * 60 * 1000 // Cache for 10 minutes
69
+ );
70
+ }
71
+
72
+ /**
73
+ * Example 4: Batch operations
74
+ */
75
+ export async function getUsersBatch(ctx: AppContext, userIds: string[]) {
76
+ // Generate cache keys
77
+ const cacheKeys = userIds.map((id) => `user:${id}`);
78
+
79
+ // Get all from cache at once
80
+ const cachedUsers = await ctx.cache.mget(...cacheKeys);
81
+
82
+ // Find missing users
83
+ const result: Array<Record<string, unknown>> = [];
84
+ const missingIds: string[] = [];
85
+
86
+ cachedUsers.forEach((user, index) => {
87
+ if (user !== undefined && user !== null) {
88
+ result[index] = user as Record<string, unknown>;
89
+ } else {
90
+ const userId = userIds[index];
91
+ if (userId !== undefined) {
92
+ missingIds.push(userId);
93
+ }
94
+ }
95
+ });
96
+
97
+ // Fetch missing users from database
98
+ if (missingIds.length > 0) {
99
+ const missingUsers = await fetchUsersFromDatabase(missingIds);
100
+
101
+ // Prepare batch cache update
102
+ const cacheEntries: Record<string, unknown> = {};
103
+ missingUsers.forEach((user) => {
104
+ cacheEntries[`user:${user.id}`] = user;
105
+ // Add to result
106
+ const index = userIds.indexOf(user.id);
107
+ if (index !== -1) {
108
+ result[index] = user;
109
+ }
110
+ });
111
+
112
+ // Set all at once
113
+ await ctx.cache.mset(cacheEntries, 5 * 60 * 1000);
114
+ }
115
+
116
+ return result;
117
+ }
118
+
119
+ /**
120
+ * Example 5: Cache invalidation after update
121
+ */
122
+ export async function updateUser(
123
+ ctx: AppContext,
124
+ userId: string,
125
+ updates: Record<string, unknown>
126
+ ) {
127
+ // Update in database
128
+ const updatedUser = await updateUserInDatabase(userId, updates);
129
+
130
+ // Invalidate all related caches
131
+ await ctx.cache.mdel(
132
+ `user:${userId}`,
133
+ `permissions:${userId}`,
134
+ `profile:${userId}`
135
+ );
136
+
137
+ // Optionally, pre-populate cache with new data
138
+ await ctx.cache.set(`user:${userId}`, updatedUser, 5 * 60 * 1000);
139
+
140
+ return updatedUser;
141
+ }
142
+
143
+ /**
144
+ * Example 6: Check if key exists
145
+ */
146
+ export async function isUserCached(ctx: AppContext, userId: string) {
147
+ return ctx.cache.has(`user:${userId}`);
148
+ }
149
+
150
+ /**
151
+ * Example 7: Clear entire cache namespace
152
+ */
153
+ export async function clearAllCache(ctx: AppContext) {
154
+ await ctx.cache.clear();
155
+ }
156
+
157
+ /**
158
+ * Example 8: Cache with dynamic TTL based on data
159
+ */
160
+ interface CacheableData {
161
+ type: 'static' | 'volatile' | 'normal';
162
+ [key: string]: unknown;
163
+ }
164
+
165
+ export async function cacheWithDynamicTTL(
166
+ ctx: AppContext,
167
+ key: string,
168
+ data: CacheableData
169
+ ) {
170
+ // Determine TTL based on data characteristics
171
+ let ttl = 5 * 60 * 1000; // 5 minutes default
172
+
173
+ if (data.type === 'static') {
174
+ ttl = 60 * 60 * 1000; // 1 hour for static data
175
+ } else if (data.type === 'volatile') {
176
+ ttl = 60 * 1000; // 1 minute for volatile data
177
+ }
178
+
179
+ await ctx.cache.set(key, data, ttl);
180
+ }
181
+
182
+ /**
183
+ * Example 9: Cache status check
184
+ */
185
+ export async function checkCacheHealth(ctx: AppContext) {
186
+ const testKey = 'health:check';
187
+ const testValue = {timestamp: Date.now(), random: Math.random()};
188
+
189
+ try {
190
+ // Test set
191
+ const setResult = await ctx.cache.set(testKey, testValue, 1000);
192
+
193
+ // Test get
194
+ const getResult = await ctx.cache.get(testKey);
195
+
196
+ // Test has
197
+ const hasResult = await ctx.cache.has(testKey);
198
+
199
+ // Test delete
200
+ const deleteResult = await ctx.cache.delete(testKey);
201
+
202
+ return {
203
+ healthy: true,
204
+ type: REDIS_URL ? 'Redis' : 'In-Memory',
205
+ operations: {
206
+ set: setResult,
207
+ get: JSON.stringify(getResult) === JSON.stringify(testValue),
208
+ has: hasResult,
209
+ delete: deleteResult,
210
+ },
211
+ };
212
+ } catch (error) {
213
+ return {
214
+ healthy: false,
215
+ error: error instanceof Error ? error.message : 'Unknown error',
216
+ };
217
+ }
218
+ }
219
+
220
+ // Mock database functions for example purposes
221
+ async function fetchUserFromDatabase(userId: string) {
222
+ return {
223
+ id: userId,
224
+ name: `User ${userId}`,
225
+ email: `user${userId}@example.com`,
226
+ createdAt: new Date(),
227
+ };
228
+ }
229
+
230
+ async function fetchUsersFromDatabase(userIds: string[]) {
231
+ return userIds.map((id) => ({
232
+ id,
233
+ name: `User ${id}`,
234
+ email: `user${id}@example.com`,
235
+ createdAt: new Date(),
236
+ }));
237
+ }
238
+
239
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
240
+ async function fetchUserRoles(_userId: string) {
241
+ return ['user', 'member'];
242
+ }
243
+
244
+ interface User {
245
+ id: string;
246
+ name: string;
247
+ email: string;
248
+ createdAt: Date;
249
+ }
250
+
251
+ function calculatePermissions(_user: User, roles: string[]) {
252
+ return {
253
+ canRead: true,
254
+ canWrite: roles.includes('admin'),
255
+ canDelete: roles.includes('admin'),
256
+ };
257
+ }
258
+
259
+ async function updateUserInDatabase(
260
+ userId: string,
261
+ updates: Record<string, unknown>
262
+ ) {
263
+ return {
264
+ id: userId,
265
+ ...updates,
266
+ updatedAt: new Date(),
267
+ };
268
+ }
269
+
270
+ /**
271
+ * Usage in a Moleculer service:
272
+ *
273
+ * export default {
274
+ * name: 'users',
275
+ * middlewares: [CacheMiddleware],
276
+ *
277
+ * started() {
278
+ * initializeCache();
279
+ * },
280
+ *
281
+ * stopped() {
282
+ * return CacheMiddleware.stopped();
283
+ * },
284
+ *
285
+ * actions: {
286
+ * get: {
287
+ * async handler(ctx) {
288
+ * return getUserWithWrap(ctx, ctx.params.id);
289
+ * }
290
+ * }
291
+ * }
292
+ * }
293
+ */
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './middlewares';
2
+ export * from './service/define-service';
3
+ export * from './types';
4
+ export * from './configs';
5
+ export * from './env';