@hiliosai/sdk 0.1.12 → 0.1.14
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/index.d.ts +904 -0
- package/dist/index.js +1809 -0
- package/package.json +11 -2
- package/src/configs/constants.ts +0 -135
- package/src/configs/index.ts +0 -2
- package/src/configs/moleculer/bulkhead.ts +0 -8
- package/src/configs/moleculer/channels.ts +0 -102
- package/src/configs/moleculer/circuit-breaker.ts +0 -17
- package/src/configs/moleculer/index.ts +0 -98
- package/src/configs/moleculer/logger.ts +0 -17
- package/src/configs/moleculer/metrics.ts +0 -20
- package/src/configs/moleculer/registry.ts +0 -7
- package/src/configs/moleculer/retry-policy.ts +0 -17
- package/src/configs/moleculer/tracing.ts +0 -6
- package/src/configs/moleculer/tracking.ts +0 -6
- package/src/configs/permissions.ts +0 -109
- package/src/datasources/base.datasource.ts +0 -111
- package/src/datasources/extensions/index.ts +0 -11
- package/src/datasources/extensions/retry.extension.ts +0 -91
- package/src/datasources/extensions/soft-delete.extension.ts +0 -114
- package/src/datasources/extensions/tenant.extension.ts +0 -105
- package/src/datasources/index.ts +0 -3
- package/src/datasources/prisma.datasource.ts +0 -317
- package/src/env.ts +0 -12
- package/src/errors/auth.error.ts +0 -33
- package/src/errors/index.ts +0 -2
- package/src/errors/permission.error.ts +0 -17
- package/src/index.ts +0 -10
- package/src/middlewares/context-helpers.middleware.ts +0 -162
- package/src/middlewares/datasource.middleware.ts +0 -73
- package/src/middlewares/health.middleware.ts +0 -134
- package/src/middlewares/index.ts +0 -5
- package/src/middlewares/memoize.middleware.ts +0 -33
- package/src/middlewares/permissions.middleware.ts +0 -162
- package/src/mixins/datasource.mixin.ts +0 -111
- package/src/mixins/index.ts +0 -1
- package/src/service/define-integration.ts +0 -404
- package/src/service/define-service.ts +0 -58
- package/src/types/channels.ts +0 -60
- package/src/types/context.ts +0 -64
- package/src/types/datasource.ts +0 -23
- package/src/types/index.ts +0 -9
- package/src/types/integration.ts +0 -28
- package/src/types/message.ts +0 -128
- package/src/types/platform.ts +0 -39
- package/src/types/service.ts +0 -209
- package/src/types/tenant.ts +0 -4
- package/src/types/user.ts +0 -16
- package/src/utils/context-cache.ts +0 -70
- package/src/utils/index.ts +0 -8
- package/src/utils/permission-calculator.ts +0 -62
- package/tsconfig.json +0 -13
- package/tsup.config.ts +0 -5
|
@@ -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
|
-
}
|
package/src/middlewares/index.ts
DELETED
|
@@ -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
|
-
};
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import type {ServiceSchema} from 'moleculer';
|
|
3
|
-
import type {
|
|
4
|
-
DatasourceConstructorRegistry,
|
|
5
|
-
DatasourceInstanceRegistry,
|
|
6
|
-
} from '../middlewares/datasource.middleware';
|
|
7
|
-
import type {AppContext} from '../types/context';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Creates a Moleculer mixin for datasource injection
|
|
11
|
-
* Simple mixin that just instantiates datasources and injects them into context
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* ```typescript
|
|
15
|
-
* import {DatasourceMixin} from '@pkg/sdk';
|
|
16
|
-
*
|
|
17
|
-
* export default {
|
|
18
|
-
* name: 'users',
|
|
19
|
-
* mixins: [DatasourceMixin({
|
|
20
|
-
* userDb: UserDatasource,
|
|
21
|
-
* cache: CacheDatasource,
|
|
22
|
-
* })],
|
|
23
|
-
* actions: {
|
|
24
|
-
* get: {
|
|
25
|
-
* handler(ctx) {
|
|
26
|
-
* // Access datasources via ctx.datasources
|
|
27
|
-
* return ctx.datasources.userDb.findById(ctx.params.id);
|
|
28
|
-
* }
|
|
29
|
-
* }
|
|
30
|
-
* }
|
|
31
|
-
* }
|
|
32
|
-
* ```
|
|
33
|
-
*/
|
|
34
|
-
export function DatasourceMixin(
|
|
35
|
-
datasourceConstructors: DatasourceConstructorRegistry = {}
|
|
36
|
-
): Partial<ServiceSchema> {
|
|
37
|
-
// Initialize datasources once
|
|
38
|
-
const datasourceInstances: DatasourceInstanceRegistry = {};
|
|
39
|
-
|
|
40
|
-
for (const [key, DatasourceClass] of Object.entries(datasourceConstructors)) {
|
|
41
|
-
datasourceInstances[key] = new DatasourceClass();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
/**
|
|
46
|
-
* Service created lifecycle hook
|
|
47
|
-
* Initialize datasources and store on service
|
|
48
|
-
*/
|
|
49
|
-
async created() {
|
|
50
|
-
// Inject broker into datasources
|
|
51
|
-
for (const [, datasource] of Object.entries(datasourceInstances)) {
|
|
52
|
-
(datasource as any).broker = this.broker;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Call init() on datasources that have it
|
|
56
|
-
for (const [, datasource] of Object.entries(datasourceInstances)) {
|
|
57
|
-
if (typeof (datasource as any).init === 'function') {
|
|
58
|
-
await (datasource as any).init();
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Store instances on the service for access in methods
|
|
63
|
-
(this as any).$datasources = datasourceInstances;
|
|
64
|
-
},
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Service started lifecycle hook
|
|
68
|
-
* Connect datasources that have connect method
|
|
69
|
-
*/
|
|
70
|
-
async started() {
|
|
71
|
-
// Call connect() on datasources that have it
|
|
72
|
-
for (const [, datasource] of Object.entries(datasourceInstances)) {
|
|
73
|
-
if (typeof (datasource as any).connect === 'function') {
|
|
74
|
-
await (datasource as any).connect();
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Service stopped lifecycle hook
|
|
81
|
-
* Disconnect datasources that have disconnect method
|
|
82
|
-
*/
|
|
83
|
-
async stopped() {
|
|
84
|
-
// Call disconnect() on datasources that have it
|
|
85
|
-
for (const [, datasource] of Object.entries(datasourceInstances)) {
|
|
86
|
-
if (typeof (datasource as any).disconnect === 'function') {
|
|
87
|
-
await (datasource as any).disconnect();
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Hooks to inject datasources into context
|
|
94
|
-
*/
|
|
95
|
-
hooks: {
|
|
96
|
-
before: {
|
|
97
|
-
'*': function injectDatasources(ctx) {
|
|
98
|
-
const datasources = (this as any).$datasources ?? {};
|
|
99
|
-
|
|
100
|
-
// Inject current context into all datasources
|
|
101
|
-
for (const [, datasource] of Object.entries(datasources)) {
|
|
102
|
-
(datasource as any).context = ctx;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Inject datasources into the context
|
|
106
|
-
(ctx as AppContext).datasources = datasources;
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
}
|
package/src/mixins/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './datasource.mixin';
|