opinionated-machine 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 +139 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/AbstractController.d.ts +5 -0
- package/dist/lib/AbstractController.js +3 -0
- package/dist/lib/AbstractController.js.map +1 -0
- package/dist/lib/AbstractModule.d.ts +9 -0
- package/dist/lib/AbstractModule.js +3 -0
- package/dist/lib/AbstractModule.js.map +1 -0
- package/dist/lib/DIContext.d.ts +26 -0
- package/dist/lib/DIContext.js +62 -0
- package/dist/lib/DIContext.js.map +1 -0
- package/dist/lib/diConfigUtils.d.ts +6 -0
- package/dist/lib/diConfigUtils.js +41 -0
- package/dist/lib/diConfigUtils.js.map +1 -0
- package/dist/lib/resolverFunctions.d.ts +19 -0
- package/dist/lib/resolverFunctions.js +48 -0
- package/dist/lib/resolverFunctions.js.map +1 -0
- package/dist/lib/typeUtils.d.ts +19 -0
- package/dist/lib/typeUtils.js +2 -0
- package/dist/lib/typeUtils.js.map +1 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Igor Savin
|
|
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,139 @@
|
|
|
1
|
+
# opinionated-machine
|
|
2
|
+
Very opinionated DI framework for fastify, built on top of awilix
|
|
3
|
+
|
|
4
|
+
## Basic usage
|
|
5
|
+
|
|
6
|
+
Define a module, or several modules, that will be used for resolving dependency graphs, using awilix:
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { AbstractModule, asSingletonClass, asMessageQueueHandlerClass, asJobWorkerClass, asJobQueueClass, asControllerClass } from 'opinionated-machine'
|
|
10
|
+
|
|
11
|
+
export type ModuleDependencies = {
|
|
12
|
+
service: Service
|
|
13
|
+
messageQueueConsumer: MessageQueueConsumer
|
|
14
|
+
jobWorker: JobWorker
|
|
15
|
+
queueManager: QueueManager
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class MyModule extends AbstractModule<ModuleDependencies, ExternalDependencies> {
|
|
19
|
+
resolveDependencies(
|
|
20
|
+
diOptions: DependencyInjectionOptions,
|
|
21
|
+
_externalDependencies: ExternalDependencies,
|
|
22
|
+
): MandatoryNameAndRegistrationPair<ModuleDependencies> {
|
|
23
|
+
return {
|
|
24
|
+
service: asSingletonClass(Service),
|
|
25
|
+
|
|
26
|
+
// by default init and disposal methods from `message-queue-toolkit` consumers
|
|
27
|
+
// will be assumed. If different values are necessary, pass second config object
|
|
28
|
+
// and specify "asyncInit" and "asyncDispose" fields
|
|
29
|
+
messageQueueConsumer: asMessageQueueHandlerClass(MessageQueueConsumer, {
|
|
30
|
+
queueName: MessageQueueConsumer.QUEUE_ID,
|
|
31
|
+
diOptions,
|
|
32
|
+
}),
|
|
33
|
+
|
|
34
|
+
// by default init and disposal methods from `background-jobs-commons` job workers
|
|
35
|
+
// will be assumed. If different values are necessary, pass second config object
|
|
36
|
+
// and specify "asyncInit" and "asyncDispose" fields
|
|
37
|
+
jobWorker: asJobWorkerClass(JobWorker, {
|
|
38
|
+
queueName: JobWorker.QUEUE_ID,
|
|
39
|
+
diOptions,
|
|
40
|
+
}),
|
|
41
|
+
|
|
42
|
+
// by default disposal methods from `background-jobs-commons` job queue manager
|
|
43
|
+
// will be assumed. If different values are necessary, specify "asyncDispose" fields
|
|
44
|
+
// in the second config object
|
|
45
|
+
queueManager: asJobQueueClass(
|
|
46
|
+
QueueManager,
|
|
47
|
+
{
|
|
48
|
+
diOptions,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
asyncInit: (manager) => manager.start(resolveJobQueuesEnabled(options)),
|
|
52
|
+
},
|
|
53
|
+
),
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// controllers will be automatically registered on fastify app
|
|
58
|
+
resolveControllers() {
|
|
59
|
+
return {
|
|
60
|
+
controller: asControllerClass(MyController),
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Defining controllers
|
|
67
|
+
|
|
68
|
+
Controllers require using fastify-api-contracts and allow to define application routes.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { buildFastifyNoPayloadRoute } from '@lokalise/fastify-api-contracts'
|
|
72
|
+
import { buildDeleteRoute } from '@lokalise/universal-ts-utils/api-contracts/apiContracts'
|
|
73
|
+
import { z } from 'zod'
|
|
74
|
+
import { AbstractController } from 'opinionated-machine'
|
|
75
|
+
|
|
76
|
+
const BODY_SCHEMA = z.object({})
|
|
77
|
+
const PATH_PARAMS_SCHEMA = z.object({
|
|
78
|
+
userId: z.string(),
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const contract = buildDeleteRoute({
|
|
82
|
+
successResponseBodySchema: BODY_SCHEMA,
|
|
83
|
+
requestPathParamsSchema: PATH_PARAMS_SCHEMA,
|
|
84
|
+
pathResolver: (pathParams) => `/users/${pathParams.userId}`,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
export class MyController extends AbstractController<typeof MyController.contracts> {
|
|
88
|
+
public static contracts = { deleteItem: contract } as const
|
|
89
|
+
|
|
90
|
+
public buildRoutes() {
|
|
91
|
+
return {
|
|
92
|
+
deleteItem: buildFastifyNoPayloadRoute(
|
|
93
|
+
MyController.contracts.deleteItem,
|
|
94
|
+
async (req, reply) => {
|
|
95
|
+
req.log.info(req.params.userId)
|
|
96
|
+
await reply.status(204).send()
|
|
97
|
+
},
|
|
98
|
+
),
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Putting it all together
|
|
105
|
+
|
|
106
|
+
Typical usage with a fastify app looks like this:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { serializerCompiler, validatorCompiler } from 'fastify-type-provider-zod'
|
|
110
|
+
import { createContainer } from 'awilix'
|
|
111
|
+
import { fastify } from 'fastify'
|
|
112
|
+
import { DIContext } from 'opinionated-machine'
|
|
113
|
+
|
|
114
|
+
const module = new MyModule()
|
|
115
|
+
const container = createContainer({
|
|
116
|
+
injectionMode: 'PROXY',
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const context = new DIContext<ModuleDependencies>(container, {
|
|
120
|
+
messageQueueConsumersEnabled: [MessageQueueConsumer.QUEUE_ID],
|
|
121
|
+
jobQueuesEnabled: false,
|
|
122
|
+
jobWorkersEnabled: false,
|
|
123
|
+
periodicJobsEnabled: false,
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
context.registerDependencies({
|
|
127
|
+
modules: [module],
|
|
128
|
+
{} // dependency overrides if necessary, usually for testing purposes
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
const app = fastify()
|
|
132
|
+
app.setValidatorCompiler(validatorCompiler)
|
|
133
|
+
app.setSerializerCompiler(serializerCompiler)
|
|
134
|
+
|
|
135
|
+
app.after(() => {
|
|
136
|
+
context.registerRoutes(app)
|
|
137
|
+
})
|
|
138
|
+
await app.ready()
|
|
139
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { DIContext, type DependencyInjectionOptions, type registerDependenciesParams, } from './lib/DIContext.js';
|
|
2
|
+
export { AbstractModule } from './lib/AbstractModule.js';
|
|
3
|
+
export { ENABLE_ALL, resolveJobQueuesEnabled, isJobQueueEnabled, isMessageQueueConsumerEnabled, isJobWorkersEnabled, } from './lib/diConfigUtils.js';
|
|
4
|
+
export { AbstractController } from './lib/AbstractController.js';
|
|
5
|
+
export { asJobQueueClass, asJobWorkerClass, asMessageQueueHandlerClass, asControllerClass, asSingletonClass, } from './lib/resolverFunctions.js';
|
|
6
|
+
export type { JobQueueModuleOptions, MessageQueueConsumerModuleOptions, JobWorkerModuleOptions, } from './lib/resolverFunctions.js';
|
|
7
|
+
export type { InferRequestFromContract } from './lib/typeUtils.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { DIContext, } from './lib/DIContext.js';
|
|
2
|
+
export { AbstractModule } from './lib/AbstractModule.js';
|
|
3
|
+
export { ENABLE_ALL, resolveJobQueuesEnabled, isJobQueueEnabled, isMessageQueueConsumerEnabled, isJobWorkersEnabled, } from './lib/diConfigUtils.js';
|
|
4
|
+
export { AbstractController } from './lib/AbstractController.js';
|
|
5
|
+
export { asJobQueueClass, asJobWorkerClass, asMessageQueueHandlerClass, asControllerClass, asSingletonClass, } from './lib/resolverFunctions.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,GAGV,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,EACL,UAAU,EACV,uBAAuB,EACvB,iBAAiB,EACjB,6BAA6B,EAC7B,mBAAmB,GACpB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AAChE,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,0BAA0B,EAC1B,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,4BAA4B,CAAA"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RouteType } from '@lokalise/fastify-api-contracts';
|
|
2
|
+
import type { DeleteRouteDefinition, GetRouteDefinition, PayloadRouteDefinition } from '@lokalise/universal-ts-utils/api-contracts/apiContracts';
|
|
3
|
+
export declare abstract class AbstractController<APIContracts extends Record<string, DeleteRouteDefinition<any, any, any> | GetRouteDefinition<any, any, any> | PayloadRouteDefinition<any, any, any>>> {
|
|
4
|
+
abstract buildRoutes(): Record<keyof APIContracts, RouteType>;
|
|
5
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AbstractController.js","sourceRoot":"","sources":["../../lib/AbstractController.ts"],"names":[],"mappings":"AAOA,MAAM,OAAgB,kBAAkB;CAYvC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Resolver } from 'awilix';
|
|
2
|
+
import type { DependencyInjectionOptions } from './DIContext.js';
|
|
3
|
+
export type MandatoryNameAndRegistrationPair<T> = {
|
|
4
|
+
[U in keyof T]: Resolver<T[U]>;
|
|
5
|
+
};
|
|
6
|
+
export declare abstract class AbstractModule<ModuleDependencies, ExternalDependencies = never> {
|
|
7
|
+
abstract resolveDependencies(diOptions?: DependencyInjectionOptions, externalDependencies?: ExternalDependencies): MandatoryNameAndRegistrationPair<ModuleDependencies>;
|
|
8
|
+
abstract resolveControllers(): MandatoryNameAndRegistrationPair<unknown>;
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AbstractModule.js","sourceRoot":"","sources":["../../lib/AbstractModule.ts"],"names":[],"mappings":"AAOA,MAAM,OAAgB,cAAc;CAOnC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AwilixContainer, NameAndRegistrationPair } from 'awilix';
|
|
2
|
+
import { AwilixManager } from 'awilix-manager';
|
|
3
|
+
import type { FastifyInstance } from 'fastify';
|
|
4
|
+
import type { AbstractModule } from './AbstractModule.js';
|
|
5
|
+
import type { ENABLE_ALL } from './diConfigUtils.js';
|
|
6
|
+
export type registerDependenciesParams<Dependencies, ExternalDependencies> = {
|
|
7
|
+
modules: readonly AbstractModule<unknown, ExternalDependencies>[];
|
|
8
|
+
dependencyOverrides?: NameAndRegistrationPair<Dependencies>;
|
|
9
|
+
};
|
|
10
|
+
export type DependencyInjectionOptions = {
|
|
11
|
+
jobQueuesEnabled?: false | typeof ENABLE_ALL | string[];
|
|
12
|
+
jobWorkersEnabled?: false | typeof ENABLE_ALL | string[];
|
|
13
|
+
messageQueueConsumersEnabled?: false | typeof ENABLE_ALL | string[];
|
|
14
|
+
periodicJobsEnabled?: false | typeof ENABLE_ALL;
|
|
15
|
+
};
|
|
16
|
+
export declare class DIContext<Dependencies extends object, ExternalDependencies = never> {
|
|
17
|
+
private readonly options;
|
|
18
|
+
readonly awilixManager: AwilixManager;
|
|
19
|
+
readonly diContainer: AwilixContainer<Dependencies>;
|
|
20
|
+
private readonly controllerResolvers;
|
|
21
|
+
constructor(diContainer: AwilixContainer, options: DependencyInjectionOptions);
|
|
22
|
+
registerDependencies(params: registerDependenciesParams<Dependencies, ExternalDependencies>, externalDependencies?: ExternalDependencies): void;
|
|
23
|
+
registerRoutes(app: FastifyInstance): void;
|
|
24
|
+
destroy(): Promise<void>;
|
|
25
|
+
init(): Promise<void>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { AwilixManager } from 'awilix-manager';
|
|
2
|
+
export class DIContext {
|
|
3
|
+
options;
|
|
4
|
+
awilixManager;
|
|
5
|
+
diContainer;
|
|
6
|
+
// biome-ignore lint/suspicious/noExplicitAny: all controllers are controllers
|
|
7
|
+
controllerResolvers;
|
|
8
|
+
constructor(diContainer, options) {
|
|
9
|
+
this.options = options;
|
|
10
|
+
this.diContainer = diContainer;
|
|
11
|
+
this.awilixManager = new AwilixManager({
|
|
12
|
+
asyncDispose: true,
|
|
13
|
+
asyncInit: true,
|
|
14
|
+
diContainer,
|
|
15
|
+
eagerInject: true,
|
|
16
|
+
strictBooleanEnforced: true,
|
|
17
|
+
});
|
|
18
|
+
this.controllerResolvers = [];
|
|
19
|
+
}
|
|
20
|
+
registerDependencies(params, externalDependencies) {
|
|
21
|
+
const _dependencyOverrides = params.dependencyOverrides ?? {};
|
|
22
|
+
const diConfig = {};
|
|
23
|
+
for (const module of params.modules) {
|
|
24
|
+
const resolvedDIConfig = module.resolveDependencies(this.options, externalDependencies);
|
|
25
|
+
for (const key in resolvedDIConfig) {
|
|
26
|
+
// @ts-expect-error we can't really ensure type-safety here
|
|
27
|
+
diConfig[key] = resolvedDIConfig[key];
|
|
28
|
+
}
|
|
29
|
+
this.controllerResolvers.push(...Object.values(module.resolveControllers()));
|
|
30
|
+
}
|
|
31
|
+
this.diContainer.register(diConfig);
|
|
32
|
+
for (const [dependencyKey, _dependencyValue] of Object.entries(_dependencyOverrides)) {
|
|
33
|
+
const dependencyValue = { ..._dependencyValue };
|
|
34
|
+
// preserve lifetime from original resolver
|
|
35
|
+
const originalResolver = this.diContainer.getRegistration(dependencyKey);
|
|
36
|
+
// @ts-ignore
|
|
37
|
+
if (dependencyValue.lifetime !== originalResolver.lifetime) {
|
|
38
|
+
// @ts-ignore
|
|
39
|
+
dependencyValue.lifetime = originalResolver.lifetime;
|
|
40
|
+
}
|
|
41
|
+
this.diContainer.register(dependencyKey, dependencyValue);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
registerRoutes(app) {
|
|
45
|
+
for (const controllerResolver of this.controllerResolvers) {
|
|
46
|
+
// biome-ignore lint/suspicious/noExplicitAny: any controller works here
|
|
47
|
+
const controller = controllerResolver.resolve(this.diContainer);
|
|
48
|
+
const routes = controller.buildRoutes();
|
|
49
|
+
for (const route of Object.values(routes)) {
|
|
50
|
+
app.route(route);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async destroy() {
|
|
55
|
+
await this.awilixManager.executeDispose();
|
|
56
|
+
await this.diContainer.dispose();
|
|
57
|
+
}
|
|
58
|
+
async init() {
|
|
59
|
+
await this.awilixManager.executeInit();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=DIContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DIContext.js","sourceRoot":"","sources":["../../lib/DIContext.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAkB9C,MAAM,OAAO,SAAS;IACH,OAAO,CAA4B;IACpC,aAAa,CAAe;IAC5B,WAAW,CAA+B;IAC1D,8EAA8E;IAC7D,mBAAmB,CAAiB;IAErD,YAAY,WAA4B,EAAE,OAAmC;QAC3E,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAA;QAC9B,IAAI,CAAC,aAAa,GAAG,IAAI,aAAa,CAAC;YACrC,YAAY,EAAE,IAAI;YAClB,SAAS,EAAE,IAAI;YACf,WAAW;YACX,WAAW,EAAE,IAAI;YACjB,qBAAqB,EAAE,IAAI;SAC5B,CAAC,CAAA;QACF,IAAI,CAAC,mBAAmB,GAAG,EAAE,CAAA;IAC/B,CAAC;IAED,oBAAoB,CAClB,MAAsE,EACtE,oBAA2C;QAE3C,MAAM,oBAAoB,GAAG,MAAM,CAAC,mBAAmB,IAAI,EAAE,CAAA;QAC7D,MAAM,QAAQ,GAA0C,EAAE,CAAA;QAE1D,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACpC,MAAM,gBAAgB,GAAG,MAAM,CAAC,mBAAmB,CAAC,IAAI,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAA;YAEvF,KAAK,MAAM,GAAG,IAAI,gBAAgB,EAAE,CAAC;gBACnC,2DAA2D;gBAC3D,QAAQ,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAA;YACvC,CAAC;YAED,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAC3B,GAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAyB,CACvE,CAAA;QACH,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;QAEnC,KAAK,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,EAAE,CAAC;YACrF,MAAM,eAAe,GAAG,EAAE,GAAI,gBAAsC,EAAE,CAAA;YAEtE,2CAA2C;YAC3C,MAAM,gBAAgB,GAAG,IAAI,CAAC,WAAW,CAAC,eAAe,CAAC,aAAa,CAAC,CAAA;YACxE,aAAa;YACb,IAAI,eAAe,CAAC,QAAQ,KAAK,gBAAgB,CAAC,QAAQ,EAAE,CAAC;gBAC3D,aAAa;gBACb,eAAe,CAAC,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,CAAA;YACtD,CAAC;YAED,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,aAAa,EAAE,eAAe,CAAC,CAAA;QAC3D,CAAC;IACH,CAAC;IAED,cAAc,CAAC,GAAoB;QACjC,KAAK,MAAM,kBAAkB,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC1D,wEAAwE;YACxE,MAAM,UAAU,GAA4B,kBAAkB,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;YACxF,MAAM,MAAM,GAAG,UAAU,CAAC,WAAW,EAAE,CAAA;YACvC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC1C,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;YAClB,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,IAAI,CAAC,aAAa,CAAC,cAAc,EAAE,CAAA;QACzC,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAA;IAClC,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,CAAA;IACxC,CAAC;CACF"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DependencyInjectionOptions } from './DIContext.js';
|
|
2
|
+
export declare const ENABLE_ALL: unique symbol;
|
|
3
|
+
export declare const resolveJobQueuesEnabled: (options: DependencyInjectionOptions) => boolean | string[];
|
|
4
|
+
export declare const isJobWorkersEnabled: (enabled?: false | typeof ENABLE_ALL | string[], name?: string) => boolean;
|
|
5
|
+
export declare const isJobQueueEnabled: (enabled?: false | typeof ENABLE_ALL | string[], name?: string) => boolean;
|
|
6
|
+
export declare const isMessageQueueConsumerEnabled: (messageQueueConsumersEnabled?: false | typeof ENABLE_ALL | string[], name?: string) => boolean;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export const ENABLE_ALL = Symbol.for('ENABLE_ALL');
|
|
2
|
+
export const resolveJobQueuesEnabled = (options) => {
|
|
3
|
+
const { jobQueuesEnabled } = options;
|
|
4
|
+
if (!jobQueuesEnabled) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
if (jobQueuesEnabled === ENABLE_ALL) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
if (Array.isArray(jobQueuesEnabled)) {
|
|
11
|
+
return jobQueuesEnabled.length ? jobQueuesEnabled : false;
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
};
|
|
15
|
+
export const isJobWorkersEnabled = (enabled, name) => isEnabled(enabled, name);
|
|
16
|
+
export const isJobQueueEnabled = (enabled, name) => {
|
|
17
|
+
if (!enabled) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if (Array.isArray(enabled) && (!name || enabled.includes(name))) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
if (enabled === ENABLE_ALL) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
};
|
|
28
|
+
export const isMessageQueueConsumerEnabled = (messageQueueConsumersEnabled, name) => isEnabled(messageQueueConsumersEnabled, name);
|
|
29
|
+
const isEnabled = (option, name) => {
|
|
30
|
+
if (!option) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if (name && Array.isArray(option)) {
|
|
34
|
+
return option.includes(name);
|
|
35
|
+
}
|
|
36
|
+
if (option === ENABLE_ALL) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=diConfigUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diConfigUtils.js","sourceRoot":"","sources":["../../lib/diConfigUtils.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;AAElD,MAAM,CAAC,MAAM,uBAAuB,GAAG,CACrC,OAAmC,EACf,EAAE;IACtB,MAAM,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAA;IACpC,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,OAAO,KAAK,CAAA;IACd,CAAC;IACD,IAAI,gBAAgB,KAAK,UAAU,EAAE,CAAC;QACpC,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACpC,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,KAAK,CAAA;IAC3D,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,OAA8C,EAC9C,IAAa,EACJ,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;AAEtC,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,OAA8C,EAC9C,IAAa,EACJ,EAAE;IACX,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QAChE,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAC3C,4BAAmE,EACnE,IAAa,EACJ,EAAE,CAAC,SAAS,CAAC,4BAA4B,EAAE,IAAI,CAAC,CAAA;AAE3D,MAAM,SAAS,GAAG,CAChB,MAAwD,EACxD,IAAa,EACJ,EAAE;IACX,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,KAAK,CAAA;IACd,CAAC;IACD,IAAI,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClC,OAAO,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC9B,CAAC;IAED,IAAI,MAAM,KAAK,UAAU,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAA;IACb,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC,CAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { BuildResolver, BuildResolverOptions, Constructor, DisposableResolver } from 'awilix';
|
|
2
|
+
import type { DependencyInjectionOptions } from './DIContext.js';
|
|
3
|
+
export declare function asSingletonClass<T = object>(Type: Constructor<T>, opts?: BuildResolverOptions<T>): BuildResolver<T> & DisposableResolver<T>;
|
|
4
|
+
export declare function asControllerClass<T = object>(Type: Constructor<T>, opts?: BuildResolverOptions<T>): BuildResolver<T> & DisposableResolver<T>;
|
|
5
|
+
export type MessageQueueConsumerModuleOptions = {
|
|
6
|
+
queueName: string;
|
|
7
|
+
diOptions: DependencyInjectionOptions;
|
|
8
|
+
};
|
|
9
|
+
export declare function asMessageQueueHandlerClass<T = object>(Type: Constructor<T>, mqOptions: MessageQueueConsumerModuleOptions, opts?: BuildResolverOptions<T>): BuildResolver<T> & DisposableResolver<T>;
|
|
10
|
+
export type JobWorkerModuleOptions = {
|
|
11
|
+
queueName: string;
|
|
12
|
+
diOptions: DependencyInjectionOptions;
|
|
13
|
+
};
|
|
14
|
+
export declare function asJobWorkerClass<T = object>(Type: Constructor<T>, workerOptions: JobWorkerModuleOptions, opts?: BuildResolverOptions<T>): BuildResolver<T> & DisposableResolver<T>;
|
|
15
|
+
export type JobQueueModuleOptions = {
|
|
16
|
+
queueName?: string;
|
|
17
|
+
diOptions: DependencyInjectionOptions;
|
|
18
|
+
};
|
|
19
|
+
export declare function asJobQueueClass<T = object>(Type: Constructor<T>, queueOptions: JobQueueModuleOptions, opts?: BuildResolverOptions<T>): BuildResolver<T> & DisposableResolver<T>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { asClass } from 'awilix';
|
|
2
|
+
import { isJobQueueEnabled, isJobWorkersEnabled, isMessageQueueConsumerEnabled, } from './diConfigUtils.js';
|
|
3
|
+
export function asSingletonClass(Type, opts) {
|
|
4
|
+
return asClass(Type, {
|
|
5
|
+
...opts,
|
|
6
|
+
lifetime: 'SINGLETON',
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
export function asControllerClass(Type, opts) {
|
|
10
|
+
return asClass(Type, {
|
|
11
|
+
...opts,
|
|
12
|
+
lifetime: 'SINGLETON',
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export function asMessageQueueHandlerClass(Type, mqOptions, opts) {
|
|
16
|
+
return asClass(Type, {
|
|
17
|
+
// these follow message-queue-toolkit conventions
|
|
18
|
+
asyncInit: 'start',
|
|
19
|
+
asyncDispose: 'close',
|
|
20
|
+
asyncDisposePriority: 10,
|
|
21
|
+
enabled: isMessageQueueConsumerEnabled(mqOptions.diOptions.messageQueueConsumersEnabled, mqOptions.queueName),
|
|
22
|
+
lifetime: 'SINGLETON',
|
|
23
|
+
...opts,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export function asJobWorkerClass(Type, workerOptions, opts) {
|
|
27
|
+
return asClass(Type, {
|
|
28
|
+
// these follow background-jobs-common conventions
|
|
29
|
+
asyncInit: 'start',
|
|
30
|
+
asyncDispose: 'dispose',
|
|
31
|
+
asyncDisposePriority: 10,
|
|
32
|
+
enabled: isJobWorkersEnabled(workerOptions.diOptions.jobWorkersEnabled, workerOptions.queueName),
|
|
33
|
+
lifetime: 'SINGLETON',
|
|
34
|
+
...opts,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
export function asJobQueueClass(Type, queueOptions, opts) {
|
|
38
|
+
return asClass(Type, {
|
|
39
|
+
// these follow background-jobs-common conventions
|
|
40
|
+
asyncInit: 'start',
|
|
41
|
+
asyncDispose: 'dispose',
|
|
42
|
+
asyncDisposePriority: 20,
|
|
43
|
+
enabled: isJobQueueEnabled(queueOptions.diOptions.jobQueuesEnabled, queueOptions.queueName),
|
|
44
|
+
lifetime: 'SINGLETON',
|
|
45
|
+
...opts,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=resolverFunctions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolverFunctions.js","sourceRoot":"","sources":["../../lib/resolverFunctions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AAGhC,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,6BAA6B,GAC9B,MAAM,oBAAoB,CAAA;AAE3B,MAAM,UAAU,gBAAgB,CAC9B,IAAoB,EACpB,IAA8B;IAE9B,OAAO,OAAO,CAAC,IAAI,EAAE;QACnB,GAAG,IAAI;QACP,QAAQ,EAAE,WAAW;KACtB,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,IAAoB,EACpB,IAA8B;IAE9B,OAAO,OAAO,CAAC,IAAI,EAAE;QACnB,GAAG,IAAI;QACP,QAAQ,EAAE,WAAW;KACtB,CAAC,CAAA;AACJ,CAAC;AAOD,MAAM,UAAU,0BAA0B,CACxC,IAAoB,EACpB,SAA4C,EAC5C,IAA8B;IAE9B,OAAO,OAAO,CAAC,IAAI,EAAE;QACnB,iDAAiD;QACjD,SAAS,EAAE,OAAO;QAClB,YAAY,EAAE,OAAO;QACrB,oBAAoB,EAAE,EAAE;QAExB,OAAO,EAAE,6BAA6B,CACpC,SAAS,CAAC,SAAS,CAAC,4BAA4B,EAChD,SAAS,CAAC,SAAS,CACpB;QACD,QAAQ,EAAE,WAAW;QACrB,GAAG,IAAI;KACR,CAAC,CAAA;AACJ,CAAC;AAOD,MAAM,UAAU,gBAAgB,CAC9B,IAAoB,EACpB,aAAqC,EACrC,IAA8B;IAE9B,OAAO,OAAO,CAAC,IAAI,EAAE;QACnB,kDAAkD;QAClD,SAAS,EAAE,OAAO;QAClB,YAAY,EAAE,SAAS;QACvB,oBAAoB,EAAE,EAAE;QAExB,OAAO,EAAE,mBAAmB,CAC1B,aAAa,CAAC,SAAS,CAAC,iBAAiB,EACzC,aAAa,CAAC,SAAS,CACxB;QACD,QAAQ,EAAE,WAAW;QACrB,GAAG,IAAI;KACR,CAAC,CAAA;AACJ,CAAC;AAOD,MAAM,UAAU,eAAe,CAC7B,IAAoB,EACpB,YAAmC,EACnC,IAA8B;IAE9B,OAAO,OAAO,CAAC,IAAI,EAAE;QACnB,kDAAkD;QAClD,SAAS,EAAE,OAAO;QAClB,YAAY,EAAE,SAAS;QACvB,oBAAoB,EAAE,EAAE;QAExB,OAAO,EAAE,iBAAiB,CAAC,YAAY,CAAC,SAAS,CAAC,gBAAgB,EAAE,YAAY,CAAC,SAAS,CAAC;QAC3F,QAAQ,EAAE,WAAW;QACrB,GAAG,IAAI;KACR,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FastifyRequest } from 'fastify';
|
|
2
|
+
import type { z } from 'zod';
|
|
3
|
+
type InferredOptionalSchema<Schema> = Schema extends z.Schema ? z.infer<Schema> : never;
|
|
4
|
+
/**
|
|
5
|
+
* Infer fastify request type from a contract
|
|
6
|
+
*/
|
|
7
|
+
export type InferRequestFromContract<Contract extends {
|
|
8
|
+
requestHeaderSchema?: z.Schema;
|
|
9
|
+
requestPathParamsSchema?: z.Schema;
|
|
10
|
+
requestQuerySchema?: z.Schema;
|
|
11
|
+
successResponseBodySchema: z.Schema;
|
|
12
|
+
}> = FastifyRequest<{
|
|
13
|
+
Body: never;
|
|
14
|
+
Headers: InferredOptionalSchema<Contract['requestHeaderSchema']>;
|
|
15
|
+
Params: InferredOptionalSchema<Contract['requestPathParamsSchema']>;
|
|
16
|
+
Querystring: InferredOptionalSchema<Contract['requestQuerySchema']>;
|
|
17
|
+
Reply: InferredOptionalSchema<Contract['successResponseBodySchema']>;
|
|
18
|
+
}>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typeUtils.js","sourceRoot":"","sources":["../../lib/typeUtils.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opinionated-machine",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Very opinionated DI framework for fastify, built on top of awilix ",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"maintainers": [
|
|
9
|
+
{
|
|
10
|
+
"name": "Igor Savin",
|
|
11
|
+
"email": "kibertoad@gmail.com"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc -p tsconfig.build.json",
|
|
16
|
+
"lint": "biome check . && tsc",
|
|
17
|
+
"lint:fix": "biome check --write .",
|
|
18
|
+
"test": "vitest --coverage",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/kibertoad/opinionated-machine.git"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@lokalise/fastify-api-contracts": ">=1.4.4",
|
|
27
|
+
"@lokalise/universal-ts-utils": ">=4.2.0",
|
|
28
|
+
"awilix": ">=12.0.0",
|
|
29
|
+
"awilix-manager": ">=6.0.0",
|
|
30
|
+
"fastify": ">=5.0.0",
|
|
31
|
+
"fastify-type-provider-zod": ">=4.0.2",
|
|
32
|
+
"zod": ">=3.24.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^22.13.13",
|
|
36
|
+
"@biomejs/biome": "1.9.4",
|
|
37
|
+
"@lokalise/biome-config": "^1.6.1",
|
|
38
|
+
"@lokalise/fastify-api-contracts": "^1.4.4",
|
|
39
|
+
"@lokalise/tsconfig": "^1.1.0",
|
|
40
|
+
"@lokalise/universal-ts-utils": "^4.2.3",
|
|
41
|
+
"@vitest/coverage-v8": "^3.0.9",
|
|
42
|
+
"awilix": "^12.0.5",
|
|
43
|
+
"awilix-manager": "^6.1.0",
|
|
44
|
+
"fastify": "^5.2.2",
|
|
45
|
+
"fastify-type-provider-zod": "^4.0.2",
|
|
46
|
+
"vitest": "^3.0.9",
|
|
47
|
+
"typescript": "^5.8.2",
|
|
48
|
+
"zod": "^3.24.2"
|
|
49
|
+
},
|
|
50
|
+
"private": false,
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"keywords": ["dependency", "injection", "opinionated", "awilix", "di"],
|
|
55
|
+
"homepage": "https://github.com/kibertoad/opinionated-machine",
|
|
56
|
+
"files": ["README.md", "LICENSE", "dist/*"]
|
|
57
|
+
}
|