@treatwell/moleculer-call-wrapper 1.0.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.
- package/LICENSE +21 -0
- package/README.md +216 -0
- package/dist/index.cjs +698 -0
- package/dist/index.d.cts +38 -0
- package/dist/index.d.mts +38 -0
- package/dist/index.mjs +694 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Treatwell
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# moleculer-call-wrapper
|
|
2
|
+
|
|
3
|
+
[](https://treatwell.com/tech)
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@treatwell/moleculer-call-wrapper)
|
|
6
|
+
|
|
7
|
+
This plugin intends to generate a TS file exporting a `call` function to replace `ctx.call` in your [moleculer](https://github.com/moleculerjs/moleculer) project
|
|
8
|
+
when using the [`@treatwell/moleculer-essentials`](https://github.com/treatwell/moleculer-essentials) package.
|
|
9
|
+
|
|
10
|
+
## Purpose
|
|
11
|
+
|
|
12
|
+
In _moleculer_, when you want to call an action from another service, you use `ctx.call('service.action', params)`.
|
|
13
|
+
With TypeScript, you don't have any type safety on the action name or the params you pass to it.
|
|
14
|
+
|
|
15
|
+
By using `@treatwell/moleculer-essentials`, you can safely define your actions with types, but
|
|
16
|
+
you still don't have type safety when calling them.
|
|
17
|
+
|
|
18
|
+
This package solves this by generating a `call` function with the correct types for each action in your project.
|
|
19
|
+
Here is an example of how it looks like:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
// test.service.ts
|
|
23
|
+
import type { Context } from 'moleculer';
|
|
24
|
+
import { wrapService } from '@treatwell/moleculer-essentials';
|
|
25
|
+
|
|
26
|
+
export default wrapService({
|
|
27
|
+
name: 'test',
|
|
28
|
+
actions: {
|
|
29
|
+
simpleaction: {
|
|
30
|
+
async handler(ctx: Context<void>): Promise<void> {
|
|
31
|
+
// ...
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
withparams: {
|
|
35
|
+
async handler(ctx: Context<{ id: number }>): Promise<void> {},
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
withresponse: {
|
|
39
|
+
async handler(ctx: Context<void>): Promise<{ name: string }> {
|
|
40
|
+
return { name: 'test' };
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
withboth: {
|
|
45
|
+
async handler(ctx: Context<{ id: number }>): Promise<{ name: string }> {
|
|
46
|
+
return { name: 'test' };
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
withtemplate: {
|
|
51
|
+
async handler<T extends 'test' | 'other'>(
|
|
52
|
+
ctx: Context<{ id: number; type: T }>,
|
|
53
|
+
): Promise<{ name: T }> {
|
|
54
|
+
return { name: 'test' as T };
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/no-unused-vars */
|
|
63
|
+
import type * as m from 'moleculer';
|
|
64
|
+
|
|
65
|
+
interface Actions {
|
|
66
|
+
'test.withparams': [{ id: number }, unknown];
|
|
67
|
+
'test.withboth': [{ id: number }, { name: string }];
|
|
68
|
+
}
|
|
69
|
+
interface ActionsU {
|
|
70
|
+
'test.simpleaction': void;
|
|
71
|
+
'test.withresponse': { name: string };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function call<N extends keyof Actions>(
|
|
75
|
+
ctx: m.Context,
|
|
76
|
+
action: N,
|
|
77
|
+
params: Actions[N][0],
|
|
78
|
+
meta?: m.CallingOptions,
|
|
79
|
+
): Promise<Actions[N][1]>;
|
|
80
|
+
export function call<N extends keyof ActionsU>(
|
|
81
|
+
ctx: m.Context,
|
|
82
|
+
action: N,
|
|
83
|
+
params?: undefined,
|
|
84
|
+
meta?: m.CallingOptions,
|
|
85
|
+
): Promise<ActionsU[N]>;
|
|
86
|
+
export function call(
|
|
87
|
+
ctx: m.Context,
|
|
88
|
+
action: string,
|
|
89
|
+
params: unknown,
|
|
90
|
+
meta?: m.CallingOptions,
|
|
91
|
+
): Promise<unknown> {
|
|
92
|
+
return ctx.call(action, params, meta);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function callT<
|
|
96
|
+
T extends 'test' | 'other',
|
|
97
|
+
N extends string = 'test.withtemplate',
|
|
98
|
+
>(
|
|
99
|
+
ctx: m.Context,
|
|
100
|
+
action: N,
|
|
101
|
+
params: N extends 'test.withtemplate' ? { id: number; type: T } : never,
|
|
102
|
+
meta?: m.CallingOptions,
|
|
103
|
+
): Promise<{ name: T }>;
|
|
104
|
+
export function callT(
|
|
105
|
+
ctx: m.Context,
|
|
106
|
+
action: string,
|
|
107
|
+
params: unknown,
|
|
108
|
+
meta?: m.CallingOptions,
|
|
109
|
+
): Promise<unknown> {
|
|
110
|
+
return ctx.call(action, params, meta);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Then you can just use it like this:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { call, callT } from './call';
|
|
118
|
+
// ...
|
|
119
|
+
|
|
120
|
+
const res = await call(ctx, 'test.withresponse');
|
|
121
|
+
|
|
122
|
+
const tRes = await callT(ctx, 'test.withtemplate', {
|
|
123
|
+
id: 1,
|
|
124
|
+
type: 'test',
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Installation
|
|
129
|
+
|
|
130
|
+
Install `moleculer-call-wrapper` with your package manager:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
yarn add -D @treatwell/moleculer-call-wrapper
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Usage
|
|
137
|
+
|
|
138
|
+
To generate the wrapper file, you need to call the `createWrapperCall` function exported by the package and provide:
|
|
139
|
+
|
|
140
|
+
- `wrapperPath`: the path where you want to generate the file (e.g. `src/call.ts`)
|
|
141
|
+
- `services`: An array of _moleculer_ services (result from `broker.loadService(file)` for example)
|
|
142
|
+
- `svcFiles`: An array of those services file paths (**MUST** be in the same order as `services`)
|
|
143
|
+
- `additionalBuiltins`: An array of functions allowing you to add additional actions manually (see below)
|
|
144
|
+
|
|
145
|
+
Example:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import * as path from 'path';
|
|
149
|
+
import * as fs from 'fs';
|
|
150
|
+
import fg from 'fast-glob';
|
|
151
|
+
import {
|
|
152
|
+
createServiceBroker,
|
|
153
|
+
HealthCheckMiddleware,
|
|
154
|
+
createLoggerConfig,
|
|
155
|
+
defaultLogger,
|
|
156
|
+
getMetadataFromService,
|
|
157
|
+
isServiceSelected,
|
|
158
|
+
Selector,
|
|
159
|
+
} from '@treatwell/moleculer-essentials';
|
|
160
|
+
|
|
161
|
+
async function run() {
|
|
162
|
+
// In your case, you would probably use glob or similar to find your service files
|
|
163
|
+
const serviceFiles = ['src/services/test.service.ts'];
|
|
164
|
+
|
|
165
|
+
const broker = createServiceBroker({});
|
|
166
|
+
const services = serviceFiles.map(f => broker.loadService(f));
|
|
167
|
+
|
|
168
|
+
// This should be done on dev mode only, not in production
|
|
169
|
+
if (process.env.MOLECULER_CALL_WRAPPER === 'yes') {
|
|
170
|
+
import('@treatwell/moleculer-call-wrapper')
|
|
171
|
+
.then(async ({ createWrapperCall }) => {
|
|
172
|
+
return createWrapperCall(
|
|
173
|
+
'./lib/call.ts',
|
|
174
|
+
services,
|
|
175
|
+
serviceFiles,
|
|
176
|
+
additionalBuiltins,
|
|
177
|
+
);
|
|
178
|
+
})
|
|
179
|
+
.catch(err => {
|
|
180
|
+
broker.logger.error('Error while creating call wrapper', err);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
await broker.start();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
run().catch(err => {
|
|
188
|
+
defaultLogger.error('Error while starting server', { err });
|
|
189
|
+
process.exit(1);
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Builtins
|
|
194
|
+
|
|
195
|
+
Mixins can't be understood by the plugin natively. As a workaround,
|
|
196
|
+
it will try to match known mixins and generate related types for them.
|
|
197
|
+
|
|
198
|
+
It **only** concerns mixins that generates actions.
|
|
199
|
+
|
|
200
|
+
Mixins (namely the DatabaseMixin) present in the `@treatwell/moleculer-essentials` package are automatically included.
|
|
201
|
+
|
|
202
|
+
### Create your own builtins
|
|
203
|
+
|
|
204
|
+
To create your own builtins, you can take a look at this [file](./src/builtins/db-mixin-v2.ts).
|
|
205
|
+
|
|
206
|
+
The idea is to first check if the service is using the related mixin.
|
|
207
|
+
If it is, the builtin will have to:
|
|
208
|
+
|
|
209
|
+
- Fill the related action (in the `actions` array) with TS types.
|
|
210
|
+
- Add any imports used in those TS types with the `addDepToImports` function to the `context.imports` map.
|
|
211
|
+
|
|
212
|
+
To help you with TS factory and AST, you can use https://ts-ast-viewer.com/
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
[MIT](https://choosealicense.com/licenses/mit/)
|