@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,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
- }
@@ -1 +0,0 @@
1
- export * from './datasource.mixin';
@@ -1,404 +0,0 @@
1
- import crypto from 'crypto';
2
-
3
- import {CHANNEL_CONFIG, INTEGRATION_CHANNELS} from '../configs/constants';
4
- import type {DatasourceConstructorRegistry} from '../middlewares/datasource.middleware';
5
- import {
6
- type IntegrationMessageFailedPayload,
7
- type IntegrationMessageReceivedPayload,
8
- type IntegrationMessageSentPayload,
9
- } from '../types/channels';
10
- import type {AppContext} from '../types/context';
11
- import type {IntegrationConfig} from '../types/integration';
12
- import type {Message, SendResult, WebhookEvent} from '../types/message';
13
- import type {DatasourceInstanceTypes} from '../types/datasource';
14
- import type {
15
- IntegrationServiceConfig,
16
- IntegrationServiceSchema,
17
- } from '../types/service';
18
- import {defineService} from './define-service';
19
-
20
- /**
21
- * Security helpers for webhook validation
22
- */
23
- const SecurityHelpers = {
24
- /**
25
- * Secure comparison using Node.js crypto.timingSafeEqual
26
- */
27
- secureCompare(a: string, b: string): boolean {
28
- try {
29
- return crypto.timingSafeEqual(
30
- Buffer.from(a, 'utf8'),
31
- Buffer.from(b, 'utf8')
32
- );
33
- } catch {
34
- return false;
35
- }
36
- },
37
-
38
- /**
39
- * Validate webhook timestamp to prevent replay attacks
40
- */
41
- validateTimestamp(timestamp: number, maxAgeMs = 5 * 60 * 1000): boolean {
42
- return Date.now() - timestamp <= maxAgeMs;
43
- },
44
- };
45
-
46
- /**
47
- * Retry mechanism with exponential backoff
48
- */
49
- async function executeWithRetry<T>(
50
- operation: () => Promise<T>,
51
- maxRetries = 3,
52
- baseDelayMs = 1000,
53
- context = 'operation'
54
- ): Promise<T> {
55
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
56
- try {
57
- return await operation();
58
- } catch (error: unknown) {
59
- if (attempt === maxRetries) {
60
- const err = error instanceof Error ? error : new Error(String(error));
61
- throw new Error(
62
- `${context} failed after ${maxRetries} retries: ${err.message}`
63
- );
64
- }
65
-
66
- const delay = baseDelayMs * Math.pow(2, attempt);
67
- const jitter = Math.random() * 1000;
68
- await new Promise((resolve) => setTimeout(resolve, delay + jitter));
69
- }
70
- }
71
- throw new Error('Retry logic error');
72
- }
73
-
74
- export function defineIntegration<
75
- TSettings = unknown,
76
- TDatasourceConstructors extends DatasourceConstructorRegistry = DatasourceConstructorRegistry
77
- >(
78
- config: IntegrationServiceConfig<TSettings, TDatasourceConstructors>
79
- ): IntegrationServiceSchema<TSettings> {
80
- // Type alias for inferred datasource instances
81
- type TDatasources = DatasourceInstanceTypes<TDatasourceConstructors>;
82
-
83
- // Create actions from integration methods
84
- const actions = {
85
- i_receiveWebhook: {
86
- rest: {
87
- method: 'POST' as const,
88
- path: '/:channelId',
89
- },
90
- params: {
91
- channelId: 'string',
92
- payload: 'object',
93
- headers: 'object',
94
- timestamp: 'number',
95
- },
96
- async handler(
97
- ctx: AppContext<
98
- TDatasources,
99
- {
100
- channelId: string;
101
- payload: object;
102
- headers: object;
103
- timestamp: number;
104
- }
105
- >
106
- ): Promise<{success: boolean; messages: number; failed: number}> {
107
- const {channelId, payload, headers, timestamp} = ctx.params;
108
-
109
- // TODO: Look up channel configuration by channelId
110
- // const channel = await getChannelConfig(channelId);
111
- // For now, we'll construct webhook from meta
112
-
113
- const webhook: WebhookEvent = {
114
- tenantId: ctx.meta.tenantId ?? 'unknown', // Should come from channel lookup
115
- channelId,
116
- platform: config.spec.platform,
117
- payload, // Raw webhook payload from gateway
118
- headers: headers as Record<string, string>,
119
- timestamp,
120
- };
121
-
122
- // Validate timestamp to prevent replay attacks
123
- if (!SecurityHelpers.validateTimestamp(webhook.timestamp)) {
124
- throw new Error('Webhook timestamp too old - possible replay attack');
125
- }
126
-
127
- // Validate webhook if method exists
128
- if (config.validateWebhook) {
129
- const isValid = await config.validateWebhook(webhook);
130
- if (!isValid) {
131
- throw new Error('Invalid webhook payload format');
132
- }
133
- }
134
-
135
- // Validate signature if method exists
136
- if (config.validateSignature) {
137
- const isValidSignature = config.validateSignature(webhook);
138
- if (!isValidSignature) {
139
- throw new Error('Webhook signature validation failed');
140
- }
141
- }
142
-
143
- // Normalize the message
144
- const normalizedMessages = await config.normalize(webhook);
145
-
146
- // Process each message with reliable delivery (parallel)
147
- const results = await Promise.allSettled(
148
- normalizedMessages.map(async (message) => {
149
- const payload: IntegrationMessageReceivedPayload = {
150
- tenantId: webhook.tenantId,
151
- channelId: webhook.channelId,
152
- platform: webhook.platform,
153
- message,
154
- timestamp: Date.now(),
155
- };
156
-
157
- // Send to reliable message channel (NATS JetStream)
158
- return ctx.broker.sendToChannel(
159
- INTEGRATION_CHANNELS.MESSAGE_RECEIVED,
160
- payload,
161
- CHANNEL_CONFIG.HIGH_PRIORITY
162
- );
163
- })
164
- );
165
-
166
- // Count successful vs failed messages
167
- const successful = results.filter(
168
- (result) => result.status === 'fulfilled'
169
- ).length;
170
- const failed = results.filter(
171
- (result) => result.status === 'rejected'
172
- ).length;
173
-
174
- // Log failures for monitoring (but don't fail the entire webhook)
175
- if (failed > 0) {
176
- const failures = results
177
- .filter((result) => result.status === 'rejected')
178
- .map((result) => (result as PromiseRejectedResult).reason);
179
-
180
- ctx.broker.logger.warn(
181
- `Webhook processing partial failure: ${failed}/${normalizedMessages.length} messages failed`,
182
- {failures}
183
- );
184
- }
185
-
186
- return {success: true, messages: successful, failed};
187
- },
188
- },
189
-
190
- i_sendMessage: {
191
- rest: {
192
- method: 'POST' as const,
193
- path: '/send',
194
- },
195
- params: {
196
- message: 'object',
197
- channelId: 'string',
198
- },
199
- async handler(
200
- ctx: AppContext<
201
- TDatasources,
202
- {
203
- message: Message;
204
- channelId: string;
205
- }
206
- >
207
- ): Promise<SendResult> {
208
- const {message, channelId} = ctx.params;
209
-
210
- // Load channel configuration
211
- const integrationConfig = await config.getChannelConfig(ctx, channelId);
212
- if (!integrationConfig) {
213
- throw new Error(`Channel configuration not found: ${channelId}`);
214
- }
215
-
216
- // Send the message with retry mechanism (transformation happens inside sendMessage)
217
- const result: SendResult = await executeWithRetry(
218
- () => config.sendMessage(ctx, message, integrationConfig),
219
- 3,
220
- 1000,
221
- `Send message via ${config.spec.platform}`
222
- );
223
-
224
- // Send reliable events based on result (with error handling)
225
- try {
226
- if (result.success) {
227
- const sentPayload: IntegrationMessageSentPayload = {
228
- tenantId: integrationConfig.tenantId,
229
- channelId,
230
- platform: config.spec.platform,
231
- messageId: result.messageId,
232
- metadata: result.metadata,
233
- timestamp: Date.now(),
234
- };
235
-
236
- await ctx.broker.sendToChannel(
237
- INTEGRATION_CHANNELS.MESSAGE_SENT,
238
- sentPayload,
239
- CHANNEL_CONFIG.DEFAULTS
240
- );
241
- } else {
242
- const failedPayload: IntegrationMessageFailedPayload = {
243
- tenantId: integrationConfig.tenantId,
244
- channelId,
245
- platform: config.spec.platform,
246
- error: result.error?.message ?? 'Unknown error',
247
- message,
248
- timestamp: Date.now(),
249
- };
250
-
251
- await ctx.broker.sendToChannel(
252
- INTEGRATION_CHANNELS.MESSAGE_FAILED,
253
- failedPayload,
254
- CHANNEL_CONFIG.DEFAULTS
255
- );
256
- }
257
- } catch (channelError: unknown) {
258
- // Log channel error but don't fail the send operation
259
- const err =
260
- channelError instanceof Error
261
- ? channelError
262
- : new Error(String(channelError));
263
- ctx.broker.logger.warn('Failed to send message event to channel', {
264
- error: err.message,
265
- messageId: result.messageId,
266
- platform: config.spec.platform,
267
- });
268
- }
269
-
270
- return result;
271
- },
272
- },
273
-
274
- i_healthCheck: {
275
- rest: {
276
- method: 'GET' as const,
277
- path: '/health',
278
- },
279
- params: {
280
- config: {type: 'object', optional: true},
281
- },
282
- async handler(
283
- ctx: AppContext<TDatasources, {config?: IntegrationConfig}>
284
- ): Promise<unknown> {
285
- try {
286
- if (config.checkHealth) {
287
- const integrationConfig = ctx.params.config;
288
- if (integrationConfig) {
289
- return await config.checkHealth(ctx, integrationConfig);
290
- }
291
- }
292
-
293
- return {
294
- status: 'healthy',
295
- message: `${config.spec.name} integration is running`,
296
- details: {
297
- id: config.spec.id,
298
- version: config.spec.version,
299
- timestamp: new Date().toISOString(),
300
- capabilities: config.spec.capabilities,
301
- },
302
- };
303
- } catch (error: unknown) {
304
- const err = error instanceof Error ? error : new Error(String(error));
305
- return {
306
- status: 'unhealthy',
307
- message: err.message,
308
- details: {
309
- id: config.spec.id,
310
- timestamp: new Date().toISOString(),
311
- },
312
- };
313
- }
314
- },
315
- },
316
-
317
- i_verifyWebhook: {
318
- rest: {
319
- method: 'GET' as const,
320
- path: '/:tenantId',
321
- },
322
- params: {
323
- tenantId: 'string',
324
- mode: 'string',
325
- token: 'string',
326
- challenge: 'string',
327
- },
328
- handler(
329
- ctx: AppContext<
330
- TDatasources,
331
- {tenantId: string; mode: string; token: string; challenge: string}
332
- >
333
- ): string {
334
- if (config.verifyWebhook) {
335
- const result: string | null = config.verifyWebhook(ctx.params);
336
- return result ?? '';
337
- }
338
- return ctx.params.challenge;
339
- },
340
- },
341
-
342
- i_validateCredentials: {
343
- params: {
344
- credentials: 'object',
345
- },
346
- async handler(
347
- ctx: AppContext<TDatasources, {credentials: Record<string, string>}>
348
- ): Promise<boolean> {
349
- if (config.validateCredentials) {
350
- return await config.validateCredentials(ctx.params.credentials);
351
- }
352
- return true;
353
- },
354
- },
355
- };
356
-
357
- // Add any custom actions
358
- if (config.actions) {
359
- Object.assign(actions, config.actions);
360
- }
361
-
362
- // Use defineService to get all the mixins and proper setup
363
- const baseService = defineService<TSettings, TDatasourceConstructors>({
364
- name: config.name,
365
- version: config.version,
366
- settings: config.settings as TSettings,
367
- dependencies: config.dependencies,
368
- datasources: config.datasources,
369
- metadata: {
370
- ...config.metadata,
371
- spec: config.spec,
372
- },
373
- actions,
374
- events: config.events ?? {},
375
- methods: {
376
- ...(config.methods ?? {}),
377
- // Add integration-specific methods to the service methods (filter out undefined)
378
- // Required methods - no need for conditional
379
- normalize: config.normalize,
380
- getChannelConfig: config.getChannelConfig,
381
- sendMessage: config.sendMessage,
382
- // Optional methods
383
- ...(config.validateWebhook && {validateWebhook: config.validateWebhook}),
384
- ...(config.verifyWebhook && {verifyWebhook: config.verifyWebhook}),
385
- ...(config.checkHealth && {checkHealth: config.checkHealth}),
386
- ...(config.validateCredentials && {
387
- validateCredentials: config.validateCredentials,
388
- }),
389
- ...(config.validateSignature && {
390
- validateSignature: config.validateSignature,
391
- }),
392
- },
393
- created: config.created,
394
- started: config.started,
395
- stopped: config.stopped,
396
- });
397
-
398
- // Return the service schema - integration methods are available via service methods
399
- return {
400
- ...baseService,
401
- // Only add the integration spec
402
- spec: config.spec,
403
- } as IntegrationServiceSchema<TSettings>;
404
- }
@@ -1,58 +0,0 @@
1
- import type {ServiceSchema as MoleculerServiceSchema} from 'moleculer';
2
-
3
- import {MemoizeMixin} from '../middlewares';
4
- import type {DatasourceConstructorRegistry} from '../middlewares/datasource.middleware';
5
- import {DatasourceMixin} from '../mixins';
6
- import type {ServiceConfig} from '../types/service';
7
- import {omit} from '../utils';
8
-
9
- /**
10
- * Define a service
11
- *
12
- * @example
13
- * ```typescript
14
- * export default defineService({
15
- * name: 'user',
16
- *
17
- * actions: {
18
- * // Action with schema
19
- * getUser: {
20
- * params: {
21
- * id: 'string'
22
- * },
23
- * handler(ctx) {
24
- * const { tenantId } = ctx.meta;
25
- *
26
- * return { id: ctx.params.id, tenantId };
27
- * }
28
- * },
29
- *
30
- * // Direct handler
31
- * listUsers(ctx) {
32
- * return [];
33
- * }
34
- * }
35
- * });
36
- * ```
37
- */
38
- export function defineService<
39
- TSettings = unknown,
40
- TDatasourceConstructors extends DatasourceConstructorRegistry = DatasourceConstructorRegistry
41
- >(
42
- config: ServiceConfig<TSettings, TDatasourceConstructors>
43
- ): MoleculerServiceSchema<TSettings> {
44
- const propsToOmit = ['datasources'];
45
- const serviceSchema = omit(
46
- config,
47
- propsToOmit
48
- ) as unknown as MoleculerServiceSchema<TSettings>;
49
-
50
- return {
51
- ...serviceSchema,
52
- mixins: [
53
- DatasourceMixin(config.datasources),
54
- MemoizeMixin(),
55
- ...(serviceSchema.mixins ?? []),
56
- ],
57
- };
58
- }
@@ -1,60 +0,0 @@
1
- import type {IntegrationPlatform} from './platform';
2
- import type {Message} from './message';
3
-
4
- /**
5
- * Channel event payload types for reliable messaging via @moleculer/channels
6
- */
7
-
8
- // Integration message events
9
- export interface IntegrationMessageReceivedPayload {
10
- tenantId: string;
11
- channelId: string;
12
- platform: IntegrationPlatform;
13
- message: Message;
14
- timestamp: number;
15
- metadata?: Record<string, unknown>;
16
- }
17
-
18
- export interface IntegrationMessageSentPayload {
19
- tenantId: string;
20
- channelId: string;
21
- platform: IntegrationPlatform;
22
- messageId?: string;
23
- metadata?: Record<string, unknown>;
24
- timestamp: number;
25
- }
26
-
27
- export interface IntegrationMessageFailedPayload {
28
- tenantId: string;
29
- channelId: string;
30
- platform: IntegrationPlatform;
31
- error: string; // Serialized error message
32
- message?: Message;
33
- timestamp: number;
34
- retryCount?: number;
35
- metadata?: Record<string, unknown>;
36
- }
37
-
38
- // System events
39
- export interface IntegrationRegisteredPayload {
40
- tenantId: string;
41
- channelId: string;
42
- platform: IntegrationPlatform;
43
- config: Record<string, unknown>;
44
- timestamp: number;
45
- }
46
-
47
- export interface IntegrationUnregisteredPayload {
48
- tenantId: string;
49
- channelId: string;
50
- platform: IntegrationPlatform;
51
- timestamp: number;
52
- }
53
-
54
- // Re-export constants to avoid duplication
55
- export {
56
- CHANNELS,
57
- INTEGRATION_CHANNELS,
58
- NAMESPACE,
59
- type IntegrationChannelName,
60
- } from '../configs/constants';
@@ -1,64 +0,0 @@
1
- import type {Context} from 'moleculer';
2
- import type {Tenant} from './tenant';
3
- import type {User} from './user';
4
-
5
- // Moleculer Channels types
6
- export interface ChannelSendOptions {
7
- group?: string;
8
- maxInFlight?: number;
9
- maxRetries?: number;
10
- deadLettering?: {
11
- enabled?: boolean;
12
- queueName?: string;
13
- };
14
- }
15
-
16
- export interface SendToChannelMethod {
17
- (
18
- channelName: string,
19
- payload: unknown,
20
- options?: ChannelSendOptions
21
- ): Promise<void>;
22
- }
23
-
24
- export interface AppMeta {
25
- user?: User;
26
- tenantId?: string;
27
- tenantName?: string;
28
- userId?: string;
29
- channelId?: string;
30
- requestId?: string;
31
- userAgent?: string;
32
- clientIP?: string;
33
- [key: string]: unknown;
34
- }
35
-
36
- export interface PermissionHelpers {
37
- hasPermission(permission: string): boolean;
38
- hasRole(role: string): boolean;
39
- isTenantMember(): boolean;
40
- isTenantOwner(): boolean;
41
- ensureUser(): User;
42
- ensureTenant(): Tenant;
43
- // New enhanced helpers
44
- getUserPermissions(): Promise<string[]>;
45
- auditLog(
46
- action: string,
47
- resource?: unknown,
48
- metadata?: Record<string, unknown>
49
- ): void;
50
- createError(message: string, code: string, statusCode?: number): Error;
51
- }
52
-
53
- export type AppContext<
54
- TDatasources = unknown,
55
- TParams = unknown,
56
- TMeta extends AppMeta = AppMeta,
57
- TLocals = unknown
58
- > = Context<TParams, TMeta, TLocals> &
59
- PermissionHelpers & {
60
- datasources: TDatasources;
61
- broker: Context['broker'] & {
62
- sendToChannel: SendToChannelMethod;
63
- };
64
- };