@hiliosai/sdk 0.1.12 → 0.1.15

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 (53) hide show
  1. package/dist/index.d.ts +904 -0
  2. package/dist/index.js +1809 -0
  3. package/package.json +10 -2
  4. package/src/configs/constants.ts +0 -135
  5. package/src/configs/index.ts +0 -2
  6. package/src/configs/moleculer/bulkhead.ts +0 -8
  7. package/src/configs/moleculer/channels.ts +0 -102
  8. package/src/configs/moleculer/circuit-breaker.ts +0 -17
  9. package/src/configs/moleculer/index.ts +0 -98
  10. package/src/configs/moleculer/logger.ts +0 -17
  11. package/src/configs/moleculer/metrics.ts +0 -20
  12. package/src/configs/moleculer/registry.ts +0 -7
  13. package/src/configs/moleculer/retry-policy.ts +0 -17
  14. package/src/configs/moleculer/tracing.ts +0 -6
  15. package/src/configs/moleculer/tracking.ts +0 -6
  16. package/src/configs/permissions.ts +0 -109
  17. package/src/datasources/base.datasource.ts +0 -111
  18. package/src/datasources/extensions/index.ts +0 -11
  19. package/src/datasources/extensions/retry.extension.ts +0 -91
  20. package/src/datasources/extensions/soft-delete.extension.ts +0 -114
  21. package/src/datasources/extensions/tenant.extension.ts +0 -105
  22. package/src/datasources/index.ts +0 -3
  23. package/src/datasources/prisma.datasource.ts +0 -317
  24. package/src/env.ts +0 -12
  25. package/src/errors/auth.error.ts +0 -33
  26. package/src/errors/index.ts +0 -2
  27. package/src/errors/permission.error.ts +0 -17
  28. package/src/index.ts +0 -10
  29. package/src/middlewares/context-helpers.middleware.ts +0 -162
  30. package/src/middlewares/datasource.middleware.ts +0 -73
  31. package/src/middlewares/health.middleware.ts +0 -134
  32. package/src/middlewares/index.ts +0 -5
  33. package/src/middlewares/memoize.middleware.ts +0 -33
  34. package/src/middlewares/permissions.middleware.ts +0 -162
  35. package/src/mixins/datasource.mixin.ts +0 -111
  36. package/src/mixins/index.ts +0 -1
  37. package/src/service/define-integration.ts +0 -404
  38. package/src/service/define-service.ts +0 -58
  39. package/src/types/channels.ts +0 -60
  40. package/src/types/context.ts +0 -64
  41. package/src/types/datasource.ts +0 -23
  42. package/src/types/index.ts +0 -9
  43. package/src/types/integration.ts +0 -28
  44. package/src/types/message.ts +0 -128
  45. package/src/types/platform.ts +0 -39
  46. package/src/types/service.ts +0 -209
  47. package/src/types/tenant.ts +0 -4
  48. package/src/types/user.ts +0 -16
  49. package/src/utils/context-cache.ts +0 -70
  50. package/src/utils/index.ts +0 -8
  51. package/src/utils/permission-calculator.ts +0 -62
  52. package/tsconfig.json +0 -13
  53. package/tsup.config.ts +0 -5
@@ -1,17 +0,0 @@
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
- }
package/src/index.ts DELETED
@@ -1,10 +0,0 @@
1
- export * from './middlewares';
2
- export * from './service/define-service';
3
- export * from './service/define-integration';
4
- export * from './types';
5
- export * from './configs';
6
- export * from './env';
7
- export * from './datasources';
8
- export * from './mixins';
9
- export * from './utils';
10
- export {default as env} from './env';
@@ -1,162 +0,0 @@
1
- import type {AppContext, Tenant, User, UserRole} from '../types';
2
- import {AuthenticationError, TenantError} from '../errors';
3
- import {ContextCache} from '../utils/context-cache';
4
- import {PermissionCalculator} from '../utils/permission-calculator';
5
-
6
- export const ContextHelpersMiddleware = {
7
- // Add helper functions to context before action handlers
8
- localAction(handler: (...args: unknown[]) => unknown) {
9
- return function ContextHelpersWrapper(this: unknown, ctx: AppContext) {
10
- const cache = ContextCache.getInstance();
11
-
12
- // Memoized permission checking with caching
13
- const memoizedPermissions = new Map<string, boolean>();
14
-
15
- ctx.hasPermission = function (permission: string): boolean {
16
- // Check in-request memoization first
17
- if (memoizedPermissions.has(permission)) {
18
- const cachedResult = memoizedPermissions.get(permission);
19
- return cachedResult === true;
20
- }
21
-
22
- const user = ctx.meta.user;
23
- if (!user) {
24
- memoizedPermissions.set(permission, false);
25
- return false;
26
- }
27
-
28
- // Use optimized permission calculator
29
- const result = PermissionCalculator.hasPermission(user, permission);
30
- memoizedPermissions.set(permission, result);
31
- return result;
32
- };
33
-
34
- // Cached user permissions getter
35
- ctx.getUserPermissions = async function (): Promise<string[]> {
36
- const user = ctx.meta.user;
37
- if (!user) return [];
38
-
39
- const cacheKey = `permissions:${user.id}:${JSON.stringify(user.roles)}`;
40
- return cache.get(cacheKey, () => {
41
- return PermissionCalculator.calculateUserPermissions(user);
42
- });
43
- };
44
-
45
- ctx.hasRole = function (role: keyof typeof UserRole): boolean {
46
- const user = ctx.ensureUser();
47
- return Array.isArray(user.roles) && user.roles.includes(role);
48
- };
49
-
50
- ctx.isTenantMember = function (): boolean {
51
- const user = ctx.ensureUser();
52
- return !!(
53
- user.tenantId &&
54
- ctx.meta.tenantId &&
55
- user.tenantId === ctx.meta.tenantId
56
- );
57
- };
58
-
59
- ctx.isTenantOwner = function (): boolean {
60
- return ctx.isTenantMember() && ctx.hasRole('OWNER');
61
- };
62
-
63
- ctx.ensureUser = (): User => {
64
- if (!ctx.meta.user) {
65
- ctx.broker.logger.error('Authentication required', {
66
- action: ctx.action?.name,
67
- requestId: ctx.meta.requestId,
68
- userAgent: ctx.meta.userAgent,
69
- ip: ctx.meta.clientIP,
70
- });
71
-
72
- throw new AuthenticationError('Authentication required', {
73
- code: 'AUTH_REQUIRED',
74
- statusCode: 401,
75
- requestId: ctx.meta.requestId,
76
- });
77
- }
78
- return ctx.meta.user;
79
- };
80
-
81
- ctx.ensureTenant = (): Tenant => {
82
- if (!ctx.meta.tenantId) {
83
- ctx.broker.logger.error('Tenant required', {
84
- action: ctx.action?.name,
85
- userId: ctx.meta.user?.id,
86
- requestId: ctx.meta.requestId,
87
- });
88
-
89
- throw new TenantError('Tenant required', {
90
- code: 'TENANT_REQUIRED',
91
- statusCode: 401,
92
- tenantId: ctx.meta.tenantId,
93
- requestId: ctx.meta.requestId,
94
- });
95
- }
96
-
97
- // Return a proper Tenant object with the ID
98
- return {
99
- id: ctx.meta.tenantId,
100
- name: ctx.meta.tenantName ?? '',
101
- } as Tenant;
102
- };
103
-
104
- // Enhanced audit logging function
105
- ctx.auditLog = function (
106
- action: string,
107
- resource?: unknown,
108
- metadata?: Record<string, unknown>
109
- ): void {
110
- ctx.broker.logger.info('Audit log', {
111
- action,
112
- resource: resource
113
- ? {
114
- type: typeof resource,
115
- id: (resource as Record<string, unknown>).id,
116
- }
117
- : undefined,
118
- userId: ctx.meta.user?.id,
119
- tenantId: ctx.meta.tenantId,
120
- requestId: ctx.meta.requestId,
121
- timestamp: new Date().toISOString(),
122
- ...metadata,
123
- });
124
- };
125
-
126
- // Enhanced error creation with context
127
- ctx.createError = function (
128
- message: string,
129
- code: string,
130
- statusCode = 400
131
- ): Error {
132
- const errorData = {
133
- code,
134
- statusCode,
135
- userId: ctx.meta.user?.id,
136
- tenantId: ctx.meta.tenantId,
137
- requestId: ctx.meta.requestId,
138
- action: ctx.action?.name,
139
- timestamp: new Date().toISOString(),
140
- };
141
-
142
- ctx.broker.logger.warn('Context error created', {
143
- message,
144
- ...errorData,
145
- });
146
-
147
- if (code === 'AUTH_REQUIRED') {
148
- return new AuthenticationError(message, errorData);
149
- }
150
-
151
- if (code === 'TENANT_REQUIRED') {
152
- return new TenantError(message, errorData);
153
- }
154
-
155
- return new Error(message);
156
- };
157
-
158
- // Call the original handler
159
- return handler.call(this, ctx);
160
- };
161
- },
162
- };
@@ -1,73 +0,0 @@
1
- import type {Context} from 'moleculer';
2
- import type {BaseDatasource} from '../datasources';
3
-
4
- /**
5
- * Datasource constructor registry type
6
- * All datasources should implement BaseDatasource interface
7
- */
8
- export interface DatasourceConstructorRegistry {
9
- [key: string]: new () => BaseDatasource | object;
10
- }
11
-
12
- /**
13
- * Datasource instance registry type
14
- */
15
- export interface DatasourceInstanceRegistry {
16
- [key: string]: object;
17
- }
18
-
19
- /**
20
- * Datasource context extension
21
- */
22
- export interface DatasourceContext extends Context {
23
- datasources: DatasourceInstanceRegistry;
24
- }
25
-
26
- /**
27
- * Initialize all datasources
28
- */
29
- function initializeDatasources(
30
- constructorRegistry: DatasourceConstructorRegistry
31
- ): DatasourceInstanceRegistry {
32
- const initializedDatasources: DatasourceInstanceRegistry = {};
33
-
34
- for (const [key, DatasourceClass] of Object.entries(constructorRegistry)) {
35
- initializedDatasources[key] = new DatasourceClass();
36
- }
37
-
38
- return initializedDatasources;
39
- }
40
-
41
- /**
42
- * Datasource middleware that injects datasources into the context
43
- */
44
- export function createDatasourceMiddleware(
45
- datasources: DatasourceConstructorRegistry
46
- ) {
47
- const initializedDatasources = initializeDatasources(datasources);
48
-
49
- return {
50
- localAction(handler: (...args: unknown[]) => unknown) {
51
- return function DatasourceWrapper(this: unknown, ctx: Context) {
52
- // Inject datasources into context
53
- (ctx as DatasourceContext).datasources = initializedDatasources;
54
- return handler.call(this, ctx);
55
- };
56
- },
57
-
58
- remoteAction(handler: (...args: unknown[]) => unknown) {
59
- return function DatasourceWrapper(this: unknown, ctx: Context) {
60
- // Inject datasources into context for remote actions too
61
- (ctx as DatasourceContext).datasources = initializedDatasources;
62
- return handler.call(this, ctx);
63
- };
64
- },
65
- };
66
- }
67
-
68
- /**
69
- * Type helper for services that use datasources
70
- */
71
- export type ServiceWithDatasources<T extends DatasourceInstanceRegistry> = {
72
- datasources: T;
73
- };
@@ -1,134 +0,0 @@
1
- import env from '@ltv/env';
2
- import http from 'http';
3
- import type {Service} from 'moleculer';
4
-
5
- const HEALTH_CHECK_PORT = env.int('HEALTH_CHECK_PORT', 3301);
6
- const HEALTH_CHECK_READINESS_PATH = env.string(
7
- 'HEALTH_CHECK_READINESS_PATH',
8
- '/readyz'
9
- );
10
- const HEALTH_CHECK_LIVENESS_PATH = env.string(
11
- 'HEALTH_CHECK_LIVENESS_PATH',
12
- '/livez'
13
- );
14
-
15
- interface HealthCheckOptions {
16
- port?: number;
17
- readiness?: {
18
- path?: string;
19
- };
20
- liveness?: {
21
- path?: string;
22
- };
23
- }
24
-
25
- interface HealthCheckConfig {
26
- port: number;
27
- readiness: {
28
- path: string;
29
- };
30
- liveness: {
31
- path: string;
32
- };
33
- }
34
-
35
- // Default configuration constants
36
- export const HEALTH_CHECK_DEFAULTS = {
37
- PORT: HEALTH_CHECK_PORT,
38
- READINESS_PATH: HEALTH_CHECK_READINESS_PATH,
39
- LIVENESS_PATH: HEALTH_CHECK_LIVENESS_PATH,
40
- } as const;
41
-
42
- export function CreateHealthCheckMiddleware(opts: HealthCheckOptions = {}) {
43
- // Merge user options with defaults
44
- const config: HealthCheckConfig = {
45
- port: opts.port ?? HEALTH_CHECK_DEFAULTS.PORT,
46
- readiness: {
47
- path: opts.readiness?.path ?? HEALTH_CHECK_DEFAULTS.READINESS_PATH,
48
- },
49
- liveness: {
50
- path: opts.liveness?.path ?? HEALTH_CHECK_DEFAULTS.LIVENESS_PATH,
51
- },
52
- };
53
-
54
- type HealthState = 'down' | 'starting' | 'up' | 'stopping';
55
-
56
- let state: HealthState = 'down';
57
- let server: http.Server;
58
-
59
- function handler(req: http.IncomingMessage, res: http.ServerResponse) {
60
- // Prevent headers from being sent multiple times
61
- if (res.headersSent) {
62
- return;
63
- }
64
-
65
- if (req.url === config.readiness.path || req.url === config.liveness.path) {
66
- const resHeader = {
67
- 'Content-Type': 'application/json; charset=utf-8',
68
- };
69
-
70
- const content = {
71
- state,
72
- uptime: process.uptime(),
73
- timestamp: Date.now(),
74
- };
75
-
76
- if (req.url === config.readiness.path) {
77
- // Readiness if the broker started successfully.
78
- // https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-readiness-probes
79
- res.writeHead(state === 'up' ? 200 : 503, resHeader);
80
- } else {
81
- // Liveness if the broker is not stopped.
82
- // https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-a-liveness-command
83
- res.writeHead(state !== 'down' ? 200 : 503, resHeader);
84
- }
85
-
86
- res.end(JSON.stringify(content, null, 2));
87
- } else {
88
- res.writeHead(404, http.STATUS_CODES[404] ?? 'Not Found', {});
89
- res.end();
90
- }
91
- }
92
-
93
- return {
94
- created(broker: Service) {
95
- state = 'starting';
96
-
97
- server = http.createServer(handler);
98
- server.listen(config.port, (err?: Error | undefined) => {
99
- if (err) {
100
- return broker.logger.error(
101
- 'Unable to start health-check server',
102
- err
103
- );
104
- }
105
-
106
- broker.logger.info('');
107
- broker.logger.info('K8s health-check server listening on');
108
- broker.logger.info(
109
- ` http://localhost:${config.port}${config.readiness.path}`
110
- );
111
- broker.logger.info(
112
- ` http://localhost:${config.port}${config.liveness.path}`
113
- );
114
- broker.logger.info('');
115
- });
116
- },
117
-
118
- // After broker started
119
- started() {
120
- state = 'up';
121
- },
122
-
123
- // Before broker stopping
124
- stopping() {
125
- state = 'stopping';
126
- },
127
-
128
- // After broker stopped
129
- stopped() {
130
- state = 'down';
131
- server.close();
132
- },
133
- };
134
- }
@@ -1,5 +0,0 @@
1
- export * from './datasource.middleware';
2
- export * from './memoize.middleware';
3
- export * from './health.middleware';
4
- export * from './permissions.middleware';
5
- export * from './context-helpers.middleware';
@@ -1,33 +0,0 @@
1
- import type {ServiceSchema, ServiceSettingSchema} from 'moleculer';
2
-
3
- export interface MemoizeMixinOptions {
4
- ttl?: number;
5
- }
6
-
7
- export function MemoizeMixin(
8
- options?: MemoizeMixinOptions
9
- ): ServiceSchema<ServiceSettingSchema> {
10
- return {
11
- name: '',
12
- methods: {
13
- async memoize(name: string, params: unknown, fn: () => Promise<unknown>) {
14
- if (!this.broker.cacher) return fn();
15
-
16
- const key = this.broker.cacher.defaultKeygen(
17
- `${this.name}:memoize-${name}`,
18
- params as object | null,
19
- {},
20
- []
21
- );
22
-
23
- let res = await this.broker.cacher.get(key);
24
- if (res) return res;
25
-
26
- res = (await fn()) as unknown as object;
27
- this.broker.cacher.set(key, res, options?.ttl);
28
-
29
- return res;
30
- },
31
- },
32
- };
33
- }
@@ -1,162 +0,0 @@
1
- import type {ActionSchema, Service} from 'moleculer';
2
-
3
- import {PERMISSIONS, ROLE_PERMISSIONS} from '../configs';
4
- import {isDev} from '../env';
5
-
6
- import {PermissionError} from '../errors';
7
- import type {AppContext} from '../types';
8
-
9
- export type Permission =
10
- | keyof typeof PERMISSIONS
11
- | string
12
- | ((ctx: AppContext, action: ActionSchema) => Promise<boolean> | boolean);
13
-
14
- // Middleware interface for type safety
15
- export interface ActionWithPermissions extends ActionSchema {
16
- permissions?: Permission | Permission[];
17
- }
18
-
19
- const permissionHandlers = {
20
- [PERMISSIONS.AUTHENTICATED]: async (ctx: AppContext) => !!ctx.meta.user?.id,
21
-
22
- [PERMISSIONS.TENANT_OWNER]: async (ctx: AppContext) =>
23
- ctx.meta.user?.roles.includes(PERMISSIONS.OWNER) ?? false,
24
-
25
- [PERMISSIONS.TENANT_MEMBER]: async (ctx: AppContext) =>
26
- !!(
27
- ctx.meta.user?.tenantId &&
28
- ctx.meta.tenantId &&
29
- ctx.meta.user.tenantId === ctx.meta.tenantId
30
- ),
31
- };
32
-
33
- export const PermissionsMiddleware = {
34
- // Wrap local action handlers
35
- localAction(
36
- handler: (...args: unknown[]) => unknown,
37
- action: ActionWithPermissions
38
- ) {
39
- // If permissions are not defined, return original handler
40
- if (!action.permissions) {
41
- return handler;
42
- }
43
-
44
- const permissions = Array.isArray(action.permissions)
45
- ? action.permissions
46
- : [action.permissions];
47
-
48
- const permissionNames: string[] = [];
49
- const permissionFunctions: Array<
50
- (ctx: AppContext, action: ActionSchema) => Promise<boolean> | boolean
51
- > = [];
52
-
53
- // Process each permission
54
- permissions.forEach((permission) => {
55
- if (typeof permission === 'function') {
56
- // Add custom permission function
57
- permissionFunctions.push(permission);
58
- return;
59
- }
60
-
61
- if (typeof permission === 'string') {
62
- // Check if it's a built-in permission handler
63
- if (permission in permissionHandlers) {
64
- const handler =
65
- permissionHandlers[permission as keyof typeof permissionHandlers];
66
- permissionFunctions.push(handler);
67
- return;
68
- }
69
-
70
- // Otherwise, treat as a permission name to check against user roles
71
- permissionNames.push(permission);
72
- return;
73
- }
74
- });
75
-
76
- return async function CheckPermissionsMiddleware(
77
- this: Service,
78
- ctx: AppContext
79
- ) {
80
- let hasAccess = false;
81
-
82
- // Check if user has OWNER role (super admin within tenant)
83
- if (ctx.meta.user?.roles.includes(PERMISSIONS.OWNER)) {
84
- hasAccess = true;
85
- }
86
-
87
- // REMOVED: DEVELOPER role bypass - security vulnerability
88
- // Developers must have explicit permissions like any other role
89
-
90
- // Check custom permission functions
91
- if (!hasAccess && permissionFunctions.length > 0) {
92
- const results = await Promise.allSettled(
93
- permissionFunctions.map((fn) => fn(ctx, action))
94
- );
95
-
96
- hasAccess = results.some(
97
- (result) => result.status === 'fulfilled' && !!result.value
98
- );
99
-
100
- // Log failed permissions without exposing details
101
- const failures = results.filter((r) => r.status === 'rejected');
102
- if (failures.length > 0) {
103
- ctx.broker.logger.warn(
104
- `${failures.length} permission functions failed`,
105
- {
106
- action: action.name,
107
- userId: ctx.meta.user?.id,
108
- }
109
- );
110
- }
111
- }
112
-
113
- // Check named permissions against user roles
114
- if (!hasAccess && permissionNames.length > 0) {
115
- const userRoles = ctx.meta.user?.roles ?? [];
116
-
117
- // Check if user has any role that includes the required permissions
118
- hasAccess = userRoles.some((role: string) => {
119
- const rolePermissions = ROLE_PERMISSIONS[role] ?? [];
120
- return permissionNames.some((permName) =>
121
- rolePermissions.includes(permName)
122
- );
123
- });
124
- }
125
-
126
- // Throw error if access denied
127
- if (!hasAccess) {
128
- const user = ctx.meta.user;
129
- ctx.broker.logger.warn('Access denied:', {
130
- action: action.name,
131
- userId: user?.id,
132
- userRoles: user?.roles,
133
- tenantId: ctx.meta.tenantId,
134
- requiredPermissions: permissions,
135
- });
136
-
137
- // Sanitize error details for production
138
- const errorDetails = isDev
139
- ? {
140
- action: action.name,
141
- requiredPermissions: permissions.map((p) =>
142
- typeof p === 'function' ? '[Function]' : String(p)
143
- ),
144
- userRoles: user?.roles ?? [],
145
- userId: user?.id,
146
- tenantId: ctx.meta.tenantId,
147
- }
148
- : {
149
- action: action.name,
150
- };
151
-
152
- throw new PermissionError(
153
- 'You do not have permission to perform this action',
154
- errorDetails
155
- );
156
- }
157
-
158
- // Call the original handler
159
- return handler.call(this, ctx);
160
- };
161
- },
162
- };