@h3ravel/core 1.3.0 → 1.4.1

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/package.json CHANGED
@@ -1,10 +1,21 @@
1
1
  {
2
2
  "name": "@h3ravel/core",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "description": "Core application container, lifecycle management and service providers for H3ravel.",
5
5
  "type": "module",
6
- "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "module": "./dist/index.js",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs",
13
+ "types": "./dist/index.d.ts"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
8
19
  "publishConfig": {
9
20
  "access": "public"
10
21
  },
@@ -31,7 +42,7 @@
31
42
  "srvx": "^0.8.2",
32
43
  "tslib": "^2.6.0",
33
44
  "dotenv": "^17.2.1",
34
- "@h3ravel/shared": "^0.15.4"
45
+ "@h3ravel/shared": "^0.16.1"
35
46
  },
36
47
  "devDependencies": {
37
48
  "typescript": "^5.4.0"
package/CHANGELOG.md DELETED
@@ -1,183 +0,0 @@
1
- # @h3ravel/core
2
-
3
- ## 1.3.0
4
-
5
- ### Minor Changes
6
-
7
- - feat: implement full IoC container resolution
8
-
9
- ### Patch Changes
10
-
11
- - Updated dependencies
12
- - @h3ravel/shared@0.15.4
13
-
14
- ## 1.2.2
15
-
16
- ### Patch Changes
17
-
18
- - feat: add homepage and repository to all packages.
19
- - Updated dependencies
20
- - @h3ravel/shared@0.15.2
21
-
22
- ## 1.2.1
23
-
24
- ### Patch Changes
25
-
26
- - chore: update readme accros all packages
27
- - Updated dependencies
28
- - @h3ravel/shared@0.15.1
29
-
30
- ## 1.2.0
31
-
32
- ### Minor Changes
33
-
34
- - d07ff49: feat: reserve the `app.` namespace for generic service provider resolution.
35
-
36
- ### Patch Changes
37
-
38
- - Updated dependencies [d07ff49]
39
- - @h3ravel/shared@0.15.0
40
-
41
- ## 1.1.2
42
-
43
- ### Patch Changes
44
-
45
- - chore: require the latest dependencies from the framework
46
-
47
- ## 1.1.1
48
-
49
- ### Patch Changes
50
-
51
- - Updated dependencies
52
- - @h3ravel/shared@0.14.0
53
-
54
- ## 1.1.0
55
-
56
- ### Minor Changes
57
-
58
- - 6e249fe: feat: bind view as a self contained method to render views
59
-
60
- ### Patch Changes
61
-
62
- - Updated dependencies [6e249fe]
63
- - @h3ravel/shared@0.13.0
64
-
65
- ## 1.0.9
66
-
67
- ### Patch Changes
68
-
69
- - chore: regularize all interfaces.
70
- - Updated dependencies
71
- - @h3ravel/shared@0.12.1
72
-
73
- ## 1.0.8
74
-
75
- ### Patch Changes
76
-
77
- - Updated dependencies
78
- - @h3ravel/shared@0.12.0
79
-
80
- ## 1.0.7
81
-
82
- ### Patch Changes
83
-
84
- - Updated dependencies
85
- - @h3ravel/shared@0.11.0
86
-
87
- ## 1.0.6
88
-
89
- ### Patch Changes
90
-
91
- - Updated dependencies
92
- - @h3ravel/shared@0.10.0
93
-
94
- ## 1.0.5
95
-
96
- ### Patch Changes
97
-
98
- - Updated dependencies
99
- - @h3ravel/shared@0.9.0
100
-
101
- ## 1.0.4
102
-
103
- ### Patch Changes
104
-
105
- - Updated dependencies
106
- - @h3ravel/shared@0.8.0
107
-
108
- ## 1.0.3
109
-
110
- ### Patch Changes
111
-
112
- - chore: add download count to readme
113
- - Updated dependencies
114
- - @h3ravel/shared@0.7.1
115
-
116
- ## 1.0.2
117
-
118
- ### Patch Changes
119
-
120
- - Updated dependencies [b0d1b7c]
121
- - @h3ravel/shared@0.7.0
122
-
123
- ## 1.0.0
124
-
125
- ### Major Changes
126
-
127
- - b40caeb: feat: make service providers sortable and unique while only loading the core providers by default.
128
- Service providers are no longer loaded by default, asides the ones provided by @h3ravel/core
129
- Service provides are sorted by an optional order and priority property.
130
-
131
- ### Patch Changes
132
-
133
- - Updated dependencies [b40caeb]
134
- - @h3ravel/shared@0.6.0
135
-
136
- ## 0.5.0
137
-
138
- ### Minor Changes
139
-
140
- - rebuild all dependencies
141
-
142
- ### Patch Changes
143
-
144
- - Updated dependencies
145
- - @h3ravel/shared@0.5.0
146
-
147
- ## 0.4.0
148
-
149
- ### Minor Changes
150
-
151
- - 8ceb2c1: implement the Application class directly since it already implements the IClass contract
152
-
153
- ### Patch Changes
154
-
155
- - a27f452: chore: fix all linting issues.
156
- - c906050: chore: migrate tests suite to jest
157
- - Updated dependencies [8ceb2c1]
158
- - Updated dependencies [a27f452]
159
- - Updated dependencies [c906050]
160
- - @h3ravel/shared@0.4.0
161
-
162
- ## 0.3.0
163
-
164
- ### Minor Changes
165
-
166
- - 3ff97bf: refactor: add a shared package to be extended by others to avoid cyclic dependency issues.
167
-
168
- ### Patch Changes
169
-
170
- - Updated dependencies [3ff97bf]
171
- - @h3ravel/shared@0.3.0
172
-
173
- ## 0.2.0
174
-
175
- ### Minor Changes
176
-
177
- - aea734f: Fix all known bugs and improved interdependecy between packages.
178
-
179
- ### Patch Changes
180
-
181
- - Updated dependencies [aea734f]
182
- - @h3ravel/router@0.2.0
183
- - @h3ravel/shared@0.2.0
@@ -1,215 +0,0 @@
1
- import { IApplication, IPathName, IServiceProvider } from '@h3ravel/shared'
2
-
3
- import { Container } from './Container'
4
- import { PathLoader } from '@h3ravel/shared'
5
- import dotenv from 'dotenv'
6
- import path from 'node:path'
7
-
8
- export class Application extends Container implements IApplication {
9
- paths = new PathLoader()
10
- private booted = false
11
- private versions = { app: '0', ts: '0' }
12
- private basePath: string
13
-
14
- private providers: IServiceProvider[] = []
15
- protected externalProviders: Array<new (_app: Application) => IServiceProvider> = []
16
-
17
- constructor(basePath: string) {
18
- super()
19
- this.basePath = basePath
20
- this.setPath('base', basePath)
21
- this.loadOptions()
22
- this.registerBaseBindings();
23
- dotenv.config({ quiet: true })
24
- }
25
-
26
- /**
27
- * Register core bindings into the container
28
- */
29
- protected registerBaseBindings () {
30
- this.bind(Application, () => this)
31
- this.bind('path.base', () => this.basePath)
32
- this.bind('load.paths', () => this.paths)
33
- }
34
-
35
- /**
36
- * Dynamically register all configured providers
37
- */
38
- public async registerConfiguredProviders () {
39
- const providers = await this.getAllProviders()
40
-
41
- for (const ProviderClass of providers) {
42
- if (!ProviderClass) continue
43
- const provider = new ProviderClass(this)
44
- await this.register(provider)
45
- }
46
- }
47
-
48
- protected async loadOptions () {
49
- const app = await this.safeImport(this.getPath('base', 'package.json'))
50
- const core = await this.safeImport('../package.json')
51
-
52
- if (app && app.dependencies) {
53
- this.versions.app = app.dependencies['@h3ravel/core']
54
- }
55
- if (core && core.devDependencies) {
56
- this.versions.ts = app.devDependencies.typescript
57
- }
58
- }
59
-
60
- /**
61
- * Load default and optional providers dynamically
62
- *
63
- * Auto-Registration Behavior
64
- *
65
- * Minimal App: Loads only core, config, http, router by default.
66
- * Full-Stack App: Installs database, mail, queue, cache → they self-register via their providers.
67
- */
68
- protected async getConfiguredProviders (): Promise<Array<new (_app: Application) => IServiceProvider>> {
69
- return [
70
- (await import('@h3ravel/core')).AppServiceProvider,
71
- (await import('@h3ravel/core')).ViewServiceProvider,
72
- ]
73
- }
74
-
75
- protected async getAllProviders (): Promise<Array<new (_app: Application) => IServiceProvider>> {
76
- const coreProviders = await this.getConfiguredProviders();
77
- const allProviders = [...coreProviders, ...this.externalProviders];
78
-
79
- /**
80
- * Deduplicate by class reference
81
- */
82
- const uniqueProviders = Array.from(new Set(allProviders));
83
-
84
- return this.sortProviders(uniqueProviders);
85
- }
86
-
87
- private sortProviders (providers: Array<new (_app: Application) => IServiceProvider>) {
88
- const priorityMap = new Map<string, number>();
89
-
90
- /**
91
- * Base priority (default 0)
92
- */
93
- providers.forEach((Provider) => {
94
- priorityMap.set(Provider.name, (Provider as any).priority ?? 0);
95
- });
96
-
97
- /**
98
- * Handle before/after adjustments
99
- */
100
- providers.forEach((Provider) => {
101
- const order = (Provider as any).order;
102
- if (!order) return;
103
-
104
- const [direction, target] = order.split(':');
105
- const targetPriority = priorityMap.get(target) ?? 0;
106
-
107
- if (direction === 'before') {
108
- priorityMap.set(Provider.name, targetPriority - 1);
109
- } else if (direction === 'after') {
110
- priorityMap.set(Provider.name, targetPriority + 1);
111
- }
112
- });
113
-
114
- /**
115
- * Sort the service providers based on thier name and priority
116
- */
117
- const sorted = providers.sort(
118
- (A, B) => (priorityMap.get(B.name) ?? 0) - (priorityMap.get(A.name) ?? 0)
119
- );
120
-
121
- /**
122
- * If debug is enabled, let's show the loaded service provider info
123
- */
124
- if (process.env.APP_DEBUG === 'true') {
125
- console.table(
126
- sorted.map((P) => ({
127
- Provider: P.name,
128
- Priority: priorityMap.get(P.name),
129
- Order: (P as any).order || 'N/A',
130
- }))
131
- );
132
- }
133
-
134
- return sorted
135
- }
136
-
137
- registerProviders (providers: Array<new (_app: Application) => IServiceProvider>): void {
138
- this.externalProviders.push(...providers)
139
- }
140
-
141
- /**
142
- * Register a provider
143
- */
144
- public async register (provider: IServiceProvider) {
145
- await provider.register()
146
- this.providers.push(provider)
147
- }
148
-
149
- /**
150
- * Boot all providers after registration
151
- */
152
- public async boot () {
153
- if (this.booted) return
154
-
155
- for (const provider of this.providers) {
156
- if (provider.boot) {
157
- await provider.boot()
158
- }
159
- }
160
-
161
- this.booted = true
162
- }
163
-
164
- /**
165
- * Attempt to dynamically import an optional module
166
- */
167
- private async safeImport (moduleName: string) {
168
- try {
169
- const mod = await import(moduleName)
170
- return mod.default ?? mod ?? {}
171
- } catch {
172
- return null
173
- }
174
- }
175
-
176
- /**
177
- * Get the base path of the app
178
- *
179
- * @returns
180
- */
181
- getBasePath (): string {
182
- return this.basePath
183
- }
184
-
185
- /**
186
- * Dynamically retrieves a path property from the class.
187
- * Any property ending with "Path" is accessible automatically.
188
- *
189
- * @param name - The base name of the path property
190
- * @returns
191
- */
192
- getPath (name: IPathName, pth?: string) {
193
- return path.join(this.paths.getPath(name, this.basePath), pth ?? '')
194
- }
195
-
196
- /**
197
- * Programatically set the paths.
198
- *
199
- * @param name - The base name of the path property
200
- * @param path - The new path
201
- * @returns
202
- */
203
- setPath (name: IPathName, path: string) {
204
- return this.paths.setPath(name, path, this.basePath)
205
- }
206
-
207
- /**
208
- * Returns the installed version of the system core and typescript.
209
- *
210
- * @returns
211
- */
212
- getVersion (key: 'app' | 'ts') {
213
- return this.versions[key]?.replaceAll(/\^|~/g, '')
214
- }
215
- }
package/src/Container.ts DELETED
@@ -1,105 +0,0 @@
1
- import type { Bindings, IContainer, UseKey } from '@h3ravel/shared'
2
-
3
- type IBinding = UseKey | (new (..._args: any[]) => unknown)
4
-
5
- export class Container implements IContainer {
6
- private bindings = new Map<IBinding, () => unknown>()
7
- private singletons = new Map<IBinding, unknown>()
8
-
9
- /**
10
- * Check if the target has any decorators
11
- *
12
- * @param target
13
- * @returns
14
- */
15
- static hasAnyDecorator (target: Function): boolean {
16
- if (Reflect.getMetadataKeys(target).length > 0) return true
17
-
18
- const paramLength = target.length
19
-
20
- for (let i = 0; i < paramLength; i++) {
21
- if (Reflect.getMetadataKeys(target, `__param_${i}`).length > 0) {
22
- return true
23
- }
24
- }
25
-
26
- return false
27
- }
28
-
29
- /**
30
- * Bind a transient service to the container
31
- */
32
- bind<T> (key: new (...args: any[]) => T, factory: () => T): void
33
- bind<T extends UseKey> (key: T, factory: () => Bindings[T]): void
34
- bind<T extends UseKey> (
35
- key: T,
36
- factory: () => Bindings[T] | T
37
- ) {
38
- this.bindings.set(key, factory)
39
- }
40
-
41
- /**
42
- * Bind a singleton service to the container
43
- */
44
- singleton<T extends UseKey> (
45
- key: T | (new (..._args: any[]) => Bindings[T]),
46
- factory: () => Bindings[T]
47
- ) {
48
- this.bindings.set(key, () => {
49
- if (!this.singletons.has(key)) {
50
- this.singletons.set(key, factory())
51
- }
52
- return this.singletons.get(key)!
53
- })
54
- }
55
-
56
- /**
57
- * Resolve a service from the container
58
- */
59
- make<T extends UseKey, X = undefined> (
60
- key: T | (new (..._args: any[]) => Bindings[T])
61
- ): X extends undefined ? Bindings[T] : X {
62
- /**
63
- * Direct factory binding
64
- */
65
- if (this.bindings.has(key)) {
66
- return this.bindings.get(key)!() as Bindings[T]
67
- }
68
-
69
- /**
70
- * If this is a class constructor, auto-resolve via reflection
71
- */
72
- if (typeof key === 'function') {
73
- return this.build(key)
74
- }
75
-
76
- throw new Error(
77
- `No binding found for key: ${typeof key === 'string' ? key : (key as any)?.name}`
78
- )
79
- }
80
-
81
- /**
82
- * Automatically build a class with constructor dependency injection
83
- */
84
- private build<T extends UseKey> (ClassType: new (..._args: any[]) => Bindings[T]): Bindings[T] {
85
- let dependencies: any[] = [];
86
-
87
- if (Array.isArray((ClassType as any).__inject__)) {
88
- dependencies = (ClassType as any).__inject__.map((alias: any) => {
89
- return this.make(alias)
90
- });
91
- } else {
92
- const paramTypes: any[] = Reflect.getMetadata('design:paramtypes', ClassType) || []
93
- dependencies = paramTypes.map((dep) => this.make(dep))
94
- }
95
-
96
- return new ClassType(...dependencies);
97
- }
98
-
99
- /**
100
- * Check if a service is registered
101
- */
102
- has (key: UseKey): boolean {
103
- return this.bindings.has(key)
104
- }
105
- }
File without changes
@@ -1,5 +0,0 @@
1
- import { Application, ServiceProvider } from "..";
2
-
3
- import { IServiceProvider } from "@h3ravel/shared";
4
-
5
- export type ServiceProviderConstructor = (new (app: Application) => ServiceProvider) & IServiceProvider;
package/src/Controller.ts DELETED
@@ -1,20 +0,0 @@
1
- import { HttpContext, IController } from '@h3ravel/shared'
2
-
3
- import { Application } from '.'
4
-
5
- /**
6
- * Base controller class
7
- */
8
- export abstract class Controller implements IController {
9
- protected app: Application
10
-
11
- constructor(app: Application) {
12
- this.app = app
13
- }
14
-
15
- public show (_ctx: HttpContext): any { return }
16
- public index (_ctx: HttpContext): any { return }
17
- public store (_ctx: HttpContext): any { return }
18
- public update (_ctx: HttpContext): any { return }
19
- public destroy (_ctx: HttpContext): any { return }
20
- }
package/src/Di/Inject.ts DELETED
@@ -1,10 +0,0 @@
1
- export function Inject (...dependencies: string[]) {
2
- return function (target: any) {
3
- target.__inject__ = dependencies;
4
- };
5
- }
6
-
7
-
8
- export function Injectable (): ClassDecorator {
9
- return (_arget) => { };
10
- }
@@ -1 +0,0 @@
1
- export default class { }
@@ -1,106 +0,0 @@
1
- import { HttpContext, IMiddleware } from '@h3ravel/shared'
2
-
3
- import type { H3Event } from 'h3'
4
-
5
- /**
6
- * Kernel class handles middleware execution and response transformations.
7
- * It acts as the core middleware pipeline for HTTP requests.
8
- */
9
- export class Kernel {
10
- /**
11
- * @param context - A factory function that converts an H3Event into an HttpContext.
12
- * @param middleware - An array of middleware classes that will be executed in sequence.
13
- */
14
- constructor(
15
- protected context: (event: H3Event) => HttpContext,
16
- protected middleware: IMiddleware[] = [],
17
- ) { }
18
-
19
- /**
20
- * Handles an incoming request and passes it through middleware before invoking the next handler.
21
- *
22
- * @param event - The raw H3 event object.
23
- * @param next - A callback function that represents the next layer (usually the controller or final handler).
24
- * @returns A promise resolving to the result of the request pipeline.
25
- */
26
- async handle (
27
- event: H3Event,
28
- next: (ctx: HttpContext) => Promise<unknown>
29
- ): Promise<unknown> {
30
- /**
31
- * Convert the raw event into a standardized HttpContext
32
- */
33
- const ctx = this.context(event)
34
- const { app } = ctx.request
35
-
36
- /**
37
- * Dynamically bind the view renderer to the service container.
38
- * This allows any part of the request lifecycle to render templates using Edge.
39
- */
40
- app.bind('view', () => async (template: string, params?: Record<string, any>) => {
41
- const edge = app.make('edge')
42
- return ctx.response.html(await edge.render(template, params))
43
- })
44
-
45
- /**
46
- * Run middleware stack and obtain result
47
- */
48
- const result = await this.runMiddleware(ctx, () => next(ctx))
49
-
50
- /**
51
- * If a plain object is returned from a controller or middleware,
52
- * automatically set the JSON Content-Type header for the response.
53
- */
54
- if (result !== undefined && this.isPlainObject(result)) {
55
- event.res.headers.set('Content-Type', 'application/json; charset=UTF-8')
56
- }
57
-
58
- return result
59
- }
60
-
61
- /**
62
- * Sequentially runs middleware in the order they were registered.
63
- *
64
- * @param context - The standardized HttpContext.
65
- * @param next - Callback to execute when middleware completes.
66
- * @returns A promise resolving to the final handler's result.
67
- */
68
- private async runMiddleware (
69
- context: HttpContext,
70
- next: (ctx: HttpContext) => Promise<unknown>
71
- ) {
72
- let index = -1
73
-
74
- const runner = async (i: number): Promise<unknown> => {
75
- if (i <= index) throw new Error('next() called multiple times')
76
- index = i
77
- const middleware = this.middleware[i]
78
-
79
- if (middleware) {
80
- /**
81
- * Execute the current middleware and proceed to the next one
82
- */
83
- return middleware.handle(context, () => runner(i + 1))
84
- } else {
85
- /**
86
- * If no more middleware, call the final handler
87
- */
88
- return next(context)
89
- }
90
- }
91
-
92
- return runner(0)
93
- }
94
-
95
- /**
96
- * Utility function to determine if a value is a plain object or array.
97
- *
98
- * @param value - The value to check.
99
- * @returns True if the value is a plain object or array, otherwise false.
100
- */
101
- private isPlainObject (value: unknown): value is Record<string, unknown> {
102
- return typeof value === 'object' &&
103
- value !== null &&
104
- (value.constructor === Object || value.constructor === Array)
105
- }
106
- }
@@ -1,20 +0,0 @@
1
- import 'reflect-metadata'
2
-
3
- import { ServiceProvider } from '../ServiceProvider'
4
-
5
- /**
6
- * Bootstraps core services and bindings.
7
- *
8
- * Bind essential services to the container (logger, config repository).
9
- * Register app-level singletons.
10
- * Set up exception handling.
11
- *
12
- * Auto-Registered
13
- */
14
- export class AppServiceProvider extends ServiceProvider {
15
- public static priority = 999;
16
-
17
- register () {
18
- // Core bindings
19
- }
20
- }
@@ -1,21 +0,0 @@
1
- import { Edge } from 'edge.js'
2
- import { ServiceProvider } from '../ServiceProvider'
3
-
4
- export class ViewServiceProvider extends ServiceProvider {
5
- public static priority = 995;
6
-
7
- register (): void {
8
- const config = this.app.make('config')
9
- const edge = Edge.create({
10
- cache: process.env.NODE_ENV === 'production'
11
- })
12
-
13
- edge.mount(this.app.getPath('views'))
14
-
15
- edge.global('asset', this.app.make('asset'))
16
- edge.global('config', config.get)
17
- edge.global('app', this.app)
18
-
19
- this.app.bind('edge', () => edge)
20
- }
21
- }
@@ -1,24 +0,0 @@
1
- import { Application } from './Application'
2
- import { IServiceProvider } from '@h3ravel/shared'
3
-
4
- export abstract class ServiceProvider implements IServiceProvider {
5
- public static order?: `before:${string}` | `after:${string}` | string | undefined;
6
- public static priority = 0;
7
- protected app: Application
8
-
9
- constructor(app: Application) {
10
- this.app = app
11
- }
12
-
13
- /**
14
- * Register bindings to the container.
15
- * Runs before boot().
16
- */
17
- abstract register (): void | Promise<void>
18
-
19
- /**
20
- * Perform post-registration booting of services.
21
- * Runs after all providers have been registered.
22
- */
23
- boot?(): void | Promise<void>
24
- }
package/src/index.ts DELETED
@@ -1,14 +0,0 @@
1
- /**
2
- * @file Automatically generated by barrelsby.
3
- */
4
-
5
- export * from './Application';
6
- export * from './Container';
7
- export * from './Controller';
8
- export * from './ServiceProvider';
9
- export * from './Contracts/ServiceProviderConstructor';
10
- export * from './Di/Inject';
11
- export * from './Exceptions/Handler';
12
- export * from './Http/Kernel';
13
- export * from './Providers/AppServiceProvider';
14
- export * from './Providers/ViewServiceProvider';
package/tests/.gitkeep DELETED
File without changes
package/tsconfig.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "../shared/tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "dist",
5
- "paths": {
6
- "@h3ravel/core": ["./src/index.ts"]
7
- }
8
- },
9
- "exclude": ["dist", "node_modules"]
10
- }