@mxweb/core 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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -0
  3. package/dist/application.d.ts +402 -0
  4. package/dist/application.js +1 -0
  5. package/dist/application.mjs +1 -0
  6. package/dist/common.d.ts +323 -0
  7. package/dist/common.js +1 -0
  8. package/dist/common.mjs +1 -0
  9. package/dist/config.d.ts +258 -0
  10. package/dist/config.js +1 -0
  11. package/dist/config.mjs +1 -0
  12. package/dist/context.d.ts +48 -0
  13. package/dist/context.js +1 -0
  14. package/dist/context.mjs +1 -0
  15. package/dist/controller.d.ts +238 -0
  16. package/dist/controller.js +1 -0
  17. package/dist/controller.mjs +1 -0
  18. package/dist/decorator.d.ts +349 -0
  19. package/dist/decorator.js +1 -0
  20. package/dist/decorator.mjs +1 -0
  21. package/dist/error.d.ts +301 -0
  22. package/dist/error.js +1 -0
  23. package/dist/error.mjs +1 -0
  24. package/dist/execute.d.ts +469 -0
  25. package/dist/execute.js +1 -0
  26. package/dist/execute.mjs +1 -0
  27. package/dist/feature.d.ts +239 -0
  28. package/dist/feature.js +1 -0
  29. package/dist/feature.mjs +1 -0
  30. package/dist/hooks.d.ts +251 -0
  31. package/dist/hooks.js +1 -0
  32. package/dist/hooks.mjs +1 -0
  33. package/dist/index.d.ts +14 -0
  34. package/dist/index.js +1 -0
  35. package/dist/index.mjs +1 -0
  36. package/dist/logger.d.ts +360 -0
  37. package/dist/logger.js +1 -0
  38. package/dist/logger.mjs +1 -0
  39. package/dist/response.d.ts +665 -0
  40. package/dist/response.js +1 -0
  41. package/dist/response.mjs +1 -0
  42. package/dist/route.d.ts +298 -0
  43. package/dist/route.js +1 -0
  44. package/dist/route.mjs +1 -0
  45. package/dist/router.d.ts +205 -0
  46. package/dist/router.js +1 -0
  47. package/dist/router.mjs +1 -0
  48. package/dist/service.d.ts +261 -0
  49. package/dist/service.js +1 -0
  50. package/dist/service.mjs +1 -0
  51. package/package.json +168 -0
@@ -0,0 +1,261 @@
1
+ /**
2
+ * @fileoverview Service layer with dependency injection support.
3
+ *
4
+ * This module provides the base `Service` class and `service()` factory function
5
+ * for creating service classes with dependency injection. Services encapsulate
6
+ * business logic and can:
7
+ * - Access the current request context via `this.context`
8
+ * - Inject dependencies from the Application's injection container
9
+ * - Be injected into Controllers
10
+ *
11
+ * @module service
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { service, Service } from "@mxweb/core";
16
+ *
17
+ * // Simple service without custom injections
18
+ * export class UtilityService extends Service {
19
+ * formatDate(date: Date): string {
20
+ * return date.toISOString();
21
+ * }
22
+ * }
23
+ *
24
+ * // Service with dependency injection
25
+ * const ProductServiceBase = service((injects) => ({
26
+ * db: injects.get("db") as DatabaseConnection,
27
+ * cache: injects.get("cache") as CacheService,
28
+ * }));
29
+ *
30
+ * export class ProductService extends ProductServiceBase {
31
+ * async findAll() {
32
+ * const cached = await this.cache.get("products");
33
+ * if (cached) return cached;
34
+ *
35
+ * const products = await this.db.query("SELECT * FROM products");
36
+ * await this.cache.set("products", products);
37
+ * return products;
38
+ * }
39
+ * }
40
+ * ```
41
+ */
42
+ import { ApplicationInject } from "./common";
43
+ import { ExecuteContext } from "./execute";
44
+ /**
45
+ * Type for service injection configuration.
46
+ *
47
+ * Can be either:
48
+ * - A plain object with injection mappings
49
+ * - An async function that receives the injection container and returns mappings
50
+ *
51
+ * @typeParam T - The shape of the injection object
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * // Plain object (static dependencies)
56
+ * const map: ServiceInjectMap<{ util: UtilService }> = {
57
+ * util: new UtilService()
58
+ * };
59
+ *
60
+ * // Async function (dynamic dependencies from container)
61
+ * const map: ServiceInjectMap<{ db: Database }> = async (injects) => ({
62
+ * db: injects.get("db") as Database
63
+ * });
64
+ * ```
65
+ */
66
+ type ServiceInjectMap<T extends Record<string, unknown>> = T | ((injects: Map<string, unknown>) => T | Promise<T>);
67
+ /**
68
+ * Transforms an injection map type into readonly instance properties.
69
+ * Used to provide type-safe access to injected dependencies.
70
+ *
71
+ * @typeParam T - The injection map object type
72
+ * @internal
73
+ */
74
+ type InjectPropertiesByInstance<T extends Record<string, unknown>> = {
75
+ readonly [K in keyof T]: T[K];
76
+ };
77
+ /**
78
+ * Base class for all services in the application.
79
+ *
80
+ * Services encapsulate business logic and provide reusable functionality
81
+ * that can be shared across controllers. Features:
82
+ * - Access to the current request context (`this.context`)
83
+ * - Access to application-level injections (`this.getInject()`)
84
+ * - Lazy initialization of async dependencies
85
+ *
86
+ * @remarks
87
+ * For services without custom injections, extend `Service` directly.
88
+ * For services with injections, use the `service()` factory function.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * // Simple service
93
+ * class EmailService extends Service {
94
+ * async sendEmail(to: string, subject: string, body: string) {
95
+ * // Access request context if needed
96
+ * const userId = this.context.user?.id;
97
+ *
98
+ * // Access application-level inject
99
+ * const mailer = this.getInject<Mailer>("mailer");
100
+ *
101
+ * return mailer?.send({ to, subject, body, from: userId });
102
+ * }
103
+ * }
104
+ * ```
105
+ */
106
+ declare class BaseService {
107
+ /** Flag to track if async dependencies have been initialized */
108
+ private _initialized;
109
+ /**
110
+ * Access the current request's ExecuteContext.
111
+ *
112
+ * Provides access to request, response, params, query, body, and more.
113
+ * Uses AsyncLocalStorage to get the context for the current request.
114
+ *
115
+ * @returns The ExecuteContext for the current request
116
+ *
117
+ * @example
118
+ * ```ts
119
+ * class MyService extends Service {
120
+ * async doSomething() {
121
+ * const userId = this.context.params.id;
122
+ * const query = this.context.query;
123
+ * const body = await this.context.body();
124
+ * return { userId, query, body };
125
+ * }
126
+ * }
127
+ * ```
128
+ */
129
+ protected get context(): ExecuteContext;
130
+ /**
131
+ * Gets an application-level inject by name.
132
+ *
133
+ * @typeParam T - The type of the inject (defaults to ApplicationInject)
134
+ * @param name - The name of the inject to retrieve
135
+ * @returns The inject instance or undefined if not found
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * class MyService extends Service {
140
+ * async query(sql: string) {
141
+ * const db = this.getInject<Database>("db");
142
+ * if (!db) throw new Error("Database not configured");
143
+ * return db.query(sql);
144
+ * }
145
+ * }
146
+ * ```
147
+ */
148
+ protected getInject<T extends ApplicationInject = ApplicationInject>(name: string): T | undefined;
149
+ /**
150
+ * Initializes async dependencies for this service.
151
+ *
152
+ * Called automatically before the service is used. Resolves the injection
153
+ * map (if defined) and assigns dependencies to instance properties.
154
+ *
155
+ * @remarks
156
+ * This method is idempotent - calling it multiple times has no effect
157
+ * after the first initialization. Results are cached per service class.
158
+ *
159
+ * @internal
160
+ */
161
+ _initDependencies(): Promise<void>;
162
+ }
163
+ /**
164
+ * Factory function to create a Service base class with dependency injection.
165
+ *
166
+ * Use this function when your service needs access to specific dependencies
167
+ * from the application's injection container. The function receives the
168
+ * injection map and returns dependencies as typed instance properties.
169
+ *
170
+ * @typeParam T - The shape of the injection object
171
+ * @param map - Injection configuration (object or async function)
172
+ * @returns A new class that extends BaseService with typed injection properties
173
+ *
174
+ * @example
175
+ * ```ts
176
+ * // Create a base with database and cache injections
177
+ * const ProductServiceBase = service((injects) => ({
178
+ * db: injects.get("db") as DatabaseConnection,
179
+ * cache: injects.get("cache") as CacheService,
180
+ * }));
181
+ *
182
+ * // Extend the base to create your service
183
+ * export class ProductService extends ProductServiceBase {
184
+ * async findAll() {
185
+ * // TypeScript knows this.db and this.cache types
186
+ * const cached = await this.cache.get("products");
187
+ * if (cached) return cached;
188
+ *
189
+ * const products = await this.db.query("SELECT * FROM products");
190
+ * await this.cache.set("products", products, 3600);
191
+ * return products;
192
+ * }
193
+ *
194
+ * async findById(id: string) {
195
+ * return this.db.query("SELECT * FROM products WHERE id = ?", [id]);
196
+ * }
197
+ * }
198
+ * ```
199
+ *
200
+ * @example
201
+ * ```ts
202
+ * // Using static object mapping
203
+ * const UtilServiceBase = service({
204
+ * logger: new Logger(),
205
+ * validator: new Validator(),
206
+ * });
207
+ *
208
+ * export class UtilService extends UtilServiceBase {
209
+ * log(message: string) {
210
+ * this.logger.info(message);
211
+ * }
212
+ * }
213
+ * ```
214
+ */
215
+ export declare function service<T extends Record<string, unknown>>(map: ServiceInjectMap<T>): new () => BaseService & InjectPropertiesByInstance<T>;
216
+ /**
217
+ * Base Service class without custom injections.
218
+ *
219
+ * Extend this class directly when your service doesn't need
220
+ * dependency injection beyond what's available via `this.context`
221
+ * and `this.getInject()`.
222
+ *
223
+ * @example
224
+ * ```ts
225
+ * import { Service } from "@mxweb/core";
226
+ *
227
+ * export class ValidationService extends Service {
228
+ * isValidEmail(email: string): boolean {
229
+ * return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
230
+ * }
231
+ *
232
+ * isValidPhone(phone: string): boolean {
233
+ * return /^\+?[\d\s-]{10,}$/.test(phone);
234
+ * }
235
+ *
236
+ * async validateRequest() {
237
+ * const body = await this.context.body<{ email: string }>();
238
+ * return this.isValidEmail(body.email);
239
+ * }
240
+ * }
241
+ * ```
242
+ */
243
+ export declare const Service: typeof BaseService;
244
+ /**
245
+ * Type representing a Service class constructor.
246
+ *
247
+ * Used for typing service classes in injection configurations.
248
+ *
249
+ * @typeParam T - The service instance type
250
+ *
251
+ * @example
252
+ * ```ts
253
+ * function createService<T extends BaseService>(
254
+ * ServiceClass: ServiceConstructor<T>
255
+ * ): T {
256
+ * return new ServiceClass();
257
+ * }
258
+ * ```
259
+ */
260
+ export type ServiceConstructor<T extends BaseService> = new () => T;
261
+ export {};
@@ -0,0 +1 @@
1
+ "use strict";var t=require("./context.js");const e=new WeakMap;class i{constructor(){this._initialized=!1}get context(){return t.executeContext}getInject(e){return t.executeContext.getInject(e)}async _initDependencies(){if(this._initialized)return;const i=this.constructor.injectMap;if(!i)return void(this._initialized=!0);let n;e.has(this.constructor)?n=e.get(this.constructor):(n="function"==typeof i?await i(t.executeContext.injections):i,e.set(this.constructor,n));for(const[t,e]of Object.entries(n))this[t]=e;this._initialized=!0}}const n=i;exports.Service=n,exports.service=function(t){var e;return(e=class extends i{}).injectMap=t,e};
@@ -0,0 +1 @@
1
+ import{executeContext as t}from"./context.mjs";const i=new WeakMap;class n{constructor(){this._initialized=!1}get context(){return t}getInject(i){return t.getInject(i)}async _initDependencies(){if(this._initialized)return;const n=this.constructor.injectMap;if(!n)return void(this._initialized=!0);let e;i.has(this.constructor)?e=i.get(this.constructor):(e="function"==typeof n?await n(t.injections):n,i.set(this.constructor,e));for(const[t,i]of Object.entries(e))this[t]=i;this._initialized=!0}}function e(t){var i;return(i=class extends n{}).injectMap=t,i}const s=n;export{s as Service,e as service};
package/package.json ADDED
@@ -0,0 +1,168 @@
1
+ {
2
+ "name": "@mxweb/core",
3
+ "version": "1.0.0",
4
+ "description": "A NestJS-inspired backend framework for Next.js App Router with dependency injection, guards, interceptors, and modular architecture",
5
+ "keywords": [
6
+ "nextjs",
7
+ "next",
8
+ "nestjs",
9
+ "framework",
10
+ "backend",
11
+ "api",
12
+ "rest",
13
+ "dependency-injection",
14
+ "guards",
15
+ "interceptors",
16
+ "pipes",
17
+ "filters",
18
+ "decorators",
19
+ "controllers",
20
+ "services",
21
+ "app-router",
22
+ "typescript"
23
+ ],
24
+ "main": "dist/index.js",
25
+ "module": "dist/index.mjs",
26
+ "types": "dist/index.d.ts",
27
+ "license": "MIT",
28
+ "author": "MXWeb Team <mxwebio@gmail.com>",
29
+ "homepage": "https://docs.mxweb.io/core",
30
+ "sideEffects": false,
31
+ "files": [
32
+ "dist",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "engines": {
40
+ "node": ">=14.0.0"
41
+ },
42
+ "devDependencies": {
43
+ "@mxweb/utils": "^0.0.4",
44
+ "@rollup/plugin-babel": "^6.1.0",
45
+ "@rollup/plugin-commonjs": "^29.0.0",
46
+ "@rollup/plugin-node-resolve": "^16.0.3",
47
+ "@rollup/plugin-terser": "^0.4.4",
48
+ "@rollup/plugin-typescript": "^12.3.0",
49
+ "@types/node": "^20",
50
+ "@typescript-eslint/eslint-plugin": "^8.49.0",
51
+ "@typescript-eslint/parser": "^8.49.0",
52
+ "eslint": "^9.39.2",
53
+ "eslint-config-prettier": "^10.1.8",
54
+ "eslint-plugin-prettier": "^5.5.4",
55
+ "glob": "^13.0.0",
56
+ "next": "16.0.10",
57
+ "prettier": "^3.7.4",
58
+ "rimraf": "^6.1.2",
59
+ "rollup": "^4.53.4",
60
+ "typescript": "^5"
61
+ },
62
+ "peerDependencies": {
63
+ "@mxweb/utils": "^0.0.4",
64
+ "next": ">=16.0.10"
65
+ },
66
+ "scripts": {
67
+ "clean": "rimraf dist",
68
+ "build": "yarn clean && yarn lint && rollup -c",
69
+ "build:watch": "rollup -c -w",
70
+ "lint": "eslint \"src/**/*.{ts,tsx}\"",
71
+ "lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix",
72
+ "format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"",
73
+ "format:check": "prettier --check \"src/**/*.{ts,tsx,json,md}\"",
74
+ "prepublishOnly": "yarn build"
75
+ },
76
+ "exports": {
77
+ ".": {
78
+ "types": "./dist/index.d.ts",
79
+ "import": "./dist/index.mjs",
80
+ "require": "./dist/index.js",
81
+ "default": "./dist/index.mjs"
82
+ },
83
+ "./application": {
84
+ "types": "./dist/application.d.ts",
85
+ "import": "./dist/application.mjs",
86
+ "require": "./dist/application.js",
87
+ "default": "./dist/application.mjs"
88
+ },
89
+ "./common": {
90
+ "types": "./dist/common.d.ts",
91
+ "import": "./dist/common.mjs",
92
+ "require": "./dist/common.js",
93
+ "default": "./dist/common.mjs"
94
+ },
95
+ "./config": {
96
+ "types": "./dist/config.d.ts",
97
+ "import": "./dist/config.mjs",
98
+ "require": "./dist/config.js",
99
+ "default": "./dist/config.mjs"
100
+ },
101
+ "./controller": {
102
+ "types": "./dist/controller.d.ts",
103
+ "import": "./dist/controller.mjs",
104
+ "require": "./dist/controller.js",
105
+ "default": "./dist/controller.mjs"
106
+ },
107
+ "./decorator": {
108
+ "types": "./dist/decorator.d.ts",
109
+ "import": "./dist/decorator.mjs",
110
+ "require": "./dist/decorator.js",
111
+ "default": "./dist/decorator.mjs"
112
+ },
113
+ "./error": {
114
+ "types": "./dist/error.d.ts",
115
+ "import": "./dist/error.mjs",
116
+ "require": "./dist/error.js",
117
+ "default": "./dist/error.mjs"
118
+ },
119
+ "./execute": {
120
+ "types": "./dist/execute.d.ts",
121
+ "import": "./dist/execute.mjs",
122
+ "require": "./dist/execute.js",
123
+ "default": "./dist/execute.mjs"
124
+ },
125
+ "./feature": {
126
+ "types": "./dist/feature.d.ts",
127
+ "import": "./dist/feature.mjs",
128
+ "require": "./dist/feature.js",
129
+ "default": "./dist/feature.mjs"
130
+ },
131
+ "./hooks": {
132
+ "types": "./dist/hooks.d.ts",
133
+ "import": "./dist/hooks.mjs",
134
+ "require": "./dist/hooks.js",
135
+ "default": "./dist/hooks.mjs"
136
+ },
137
+ "./logger": {
138
+ "types": "./dist/logger.d.ts",
139
+ "import": "./dist/logger.mjs",
140
+ "require": "./dist/logger.js",
141
+ "default": "./dist/logger.mjs"
142
+ },
143
+ "./response": {
144
+ "types": "./dist/response.d.ts",
145
+ "import": "./dist/response.mjs",
146
+ "require": "./dist/response.js",
147
+ "default": "./dist/response.mjs"
148
+ },
149
+ "./route": {
150
+ "types": "./dist/route.d.ts",
151
+ "import": "./dist/route.mjs",
152
+ "require": "./dist/route.js",
153
+ "default": "./dist/route.mjs"
154
+ },
155
+ "./router": {
156
+ "types": "./dist/router.d.ts",
157
+ "import": "./dist/router.mjs",
158
+ "require": "./dist/router.js",
159
+ "default": "./dist/router.mjs"
160
+ },
161
+ "./service": {
162
+ "types": "./dist/service.d.ts",
163
+ "import": "./dist/service.mjs",
164
+ "require": "./dist/service.js",
165
+ "default": "./dist/service.mjs"
166
+ }
167
+ }
168
+ }