jax-hono 1.0.20 → 1.0.21

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/core/app.ts CHANGED
@@ -1,207 +1,207 @@
1
- import { createHono, createApi } from './hono';
2
- import { registerCrudRoutes } from '../helpers/crud';
3
- import config from '../config';
4
- import type { Config } from '../config';
5
- import { requireGeneratedModule, type GeneratedModels, type GeneratedPlugins } from './generated';
6
- import { runWithJaxContext } from './controller';
7
- import { startCronJobs, stopCronJobs } from './cron';
8
- import { requestLog } from '../middleware/request-log';
9
- import type { Hono } from 'hono';
10
- import type { JaxHono } from './hono';
11
-
12
- type Models = GeneratedModels;
13
- type CreateServices = (ctx: AppContext) => Record<string, any>;
14
- type Services = ReturnType<CreateServices>;
15
- type Plugins = GeneratedPlugins;
16
-
17
- type MaybePromise<T> = T | Promise<T>;
18
-
19
- export type AppContext = {
20
- app: Hono;
21
- config: Config;
22
- model: Models;
23
- service: Services;
24
- plugin: Plugins & Record<string, any>;
25
- };
26
-
27
- export type AppRuntimeDeps = {
28
- models: Models;
29
- createServices: CreateServices;
30
- };
31
-
32
- export type AppPluginObject = {
33
- setup?: (ctx: AppContext, options?: unknown) => MaybePromise<void>;
34
- ready?: (ctx: AppContext, options?: unknown) => MaybePromise<void>;
35
- close?: (ctx: AppContext, options?: unknown) => MaybePromise<void>;
36
- };
37
-
38
- export type AppPlugin =
39
- | ((ctx: AppContext, options?: unknown) => MaybePromise<void>)
40
- | AppPluginObject;
41
-
42
- type ModuleItem<T> = {
43
- name: string;
44
- module?: T;
45
- load?: () => Promise<{ default?: T } | T>;
46
- enable?: (ctx: AppContext) => boolean;
47
- options?: unknown;
48
- };
49
-
50
- type ActivePlugin = {
51
- name: string;
52
- plugin: AppPluginObject;
53
- options?: unknown;
54
- };
55
-
56
- export function createApp(deps: AppRuntimeDeps): JaxHono {
57
- const app = createHono();
58
- const { pluginModules } = requireGeneratedModule<{ pluginModules: readonly ModuleItem<AppPlugin>[] }>(
59
- 'plugins.generated.ts'
60
- );
61
- const { cronModules } = requireGeneratedModule<{ cronModules: Parameters<typeof startCronJobs>[0] }>(
62
- 'crons.generated.ts'
63
- );
64
-
65
- const ctx: AppContext = {
66
- app,
67
- config,
68
- model: deps.models,
69
- service: undefined as unknown as Services,
70
- plugin: {} as Plugins
71
- };
72
- const activePlugins: ActivePlugin[] = [];
73
- let activeCrons: ReturnType<typeof startCronJobs> = [];
74
-
75
- ctx.service = deps.createServices(ctx);
76
- app.jax = ctx;
77
-
78
- app.use('*', requestLog);
79
-
80
- app.use('*', async (c, next) => {
81
- const state = {};
82
-
83
- c.state = state;
84
- c.model = ctx.model;
85
- c.service = ctx.service;
86
-
87
- c.set('state', state);
88
- c.set('model', ctx.model);
89
- c.set('service', ctx.service);
90
-
91
- await runWithJaxContext(c, next);
92
- });
93
-
94
- const ready = Promise.all(
95
- (pluginModules as readonly ModuleItem<AppPlugin>[]).map((item) => setupPluginSafely(item, ctx))
96
- ).then(async (plugins) => {
97
- activePlugins.push(...plugins.filter((plugin): plugin is ActivePlugin => Boolean(plugin)));
98
-
99
- printEnabledPlugins(activePlugins);
100
-
101
- await Promise.all(activePlugins.map((plugin) => runPluginReadySafely(plugin, ctx)));
102
-
103
- activeCrons = startCronJobs(cronModules, ctx);
104
- });
105
-
106
- app.ready = ready;
107
- app.close = async () => {
108
- await ready.catch(() => undefined);
109
- stopCronJobs(activeCrons);
110
- await closePluginsSafely(activePlugins, ctx);
111
- };
112
-
113
- return app;
114
- }
115
-
116
- async function setupPlugin(
117
- item: ModuleItem<AppPlugin>,
118
- ctx: AppContext
119
- ): Promise<ActivePlugin | undefined> {
120
- if (item.enable && !item.enable(ctx)) {
121
- return;
122
- }
123
-
124
- const loadedPlugin = item.module ?? (await loadPlugin(item));
125
- const plugin =
126
- loadedPlugin && typeof loadedPlugin === 'object' && 'default' in loadedPlugin
127
- ? loadedPlugin.default
128
- : loadedPlugin;
129
-
130
- if (typeof plugin === 'function') {
131
- await plugin(ctx, item.options);
132
- return;
133
- }
134
-
135
- if (isPluginObject(plugin)) {
136
- await plugin.setup?.(ctx, item.options);
137
- return {
138
- name: item.name,
139
- plugin,
140
- options: item.options
141
- };
142
- }
143
-
144
- throw new TypeError(`Plugin ${item.name} must export a function or lifecycle object.`);
145
- }
146
-
147
- function isPluginObject(plugin: unknown): plugin is AppPluginObject {
148
- if (!plugin || typeof plugin !== 'object') {
149
- return false;
150
- }
151
-
152
- return ['setup', 'ready', 'close'].some((key) => key in plugin);
153
- }
154
-
155
- async function loadPlugin(item: ModuleItem<AppPlugin>) {
156
- if (!item.load) {
157
- throw new TypeError(`Plugin ${item.name} must define module or load.`);
158
- }
159
-
160
- return item.load();
161
- }
162
-
163
- async function setupPluginSafely(item: ModuleItem<AppPlugin>, ctx: AppContext) {
164
- try {
165
- return await setupPlugin(item, ctx);
166
- } catch (error) {
167
- console.error(`Jax plugin setup failed [${item.name}]:`, error);
168
- return undefined;
169
- }
170
- }
171
-
172
- async function runPluginReadySafely(item: ActivePlugin, ctx: AppContext) {
173
- if (!item.plugin.ready) {
174
- return;
175
- }
176
-
177
- try {
178
- await item.plugin.ready(ctx, item.options);
179
- } catch (error) {
180
- console.error(`Jax plugin ready failed [${item.name}]:`, error);
181
- }
182
- }
183
-
184
- function printEnabledPlugins(plugins: ActivePlugin[]) {
185
- if (plugins.length === 0) {
186
- console.log('[plugins] enabled: none');
187
- return;
188
- }
189
-
190
- console.log(`[plugins] enabled: ${plugins.map((plugin) => plugin.name).join(', ')}`);
191
- }
192
-
193
- async function closePluginsSafely(plugins: ActivePlugin[], ctx: AppContext) {
194
- for (const item of [...plugins].reverse()) {
195
- if (!item.plugin.close) {
196
- continue;
197
- }
198
-
199
- try {
200
- await item.plugin.close(ctx, item.options);
201
- } catch (error) {
202
- console.error(`Jax plugin close failed [${item.name}]:`, error);
203
- }
204
- }
205
- }
206
-
207
- export { createApi, createHono, registerCrudRoutes };
1
+ import { createHono, createApi } from './hono';
2
+ import { registerCrudRoutes } from '../helpers/crud';
3
+ import config from '../config';
4
+ import type { Config } from '../config';
5
+ import { requireGeneratedModule, type GeneratedModels, type GeneratedPlugins } from './generated';
6
+ import { runWithJaxContext } from './controller';
7
+ import { startCronJobs, stopCronJobs } from './cron';
8
+ import { requestLog } from '../middleware/request-log';
9
+ import type { Hono } from 'hono';
10
+ import type { JaxHono } from './hono';
11
+
12
+ type Models = GeneratedModels;
13
+ type CreateServices = (ctx: AppContext) => Record<string, any>;
14
+ type Services = ReturnType<CreateServices>;
15
+ type Plugins = GeneratedPlugins;
16
+
17
+ type MaybePromise<T> = T | Promise<T>;
18
+
19
+ export type AppContext = {
20
+ app: Hono;
21
+ config: Config;
22
+ model: Models;
23
+ service: Services;
24
+ plugin: Plugins & Record<string, any>;
25
+ };
26
+
27
+ export type AppRuntimeDeps = {
28
+ models: Models;
29
+ createServices: CreateServices;
30
+ };
31
+
32
+ export type AppPluginObject = {
33
+ setup?: (ctx: AppContext, options?: unknown) => MaybePromise<void>;
34
+ ready?: (ctx: AppContext, options?: unknown) => MaybePromise<void>;
35
+ close?: (ctx: AppContext, options?: unknown) => MaybePromise<void>;
36
+ };
37
+
38
+ export type AppPlugin =
39
+ | ((ctx: AppContext, options?: unknown) => MaybePromise<void>)
40
+ | AppPluginObject;
41
+
42
+ type ModuleItem<T> = {
43
+ name: string;
44
+ module?: T;
45
+ load?: () => Promise<{ default?: T } | T>;
46
+ enable?: (ctx: AppContext) => boolean;
47
+ options?: unknown;
48
+ };
49
+
50
+ type ActivePlugin = {
51
+ name: string;
52
+ plugin: AppPluginObject;
53
+ options?: unknown;
54
+ };
55
+
56
+ export function createApp(deps: AppRuntimeDeps): JaxHono {
57
+ const app = createHono();
58
+ const { pluginModules } = requireGeneratedModule<{ pluginModules: readonly ModuleItem<AppPlugin>[] }>(
59
+ 'plugins.generated.ts'
60
+ );
61
+ const { cronModules } = requireGeneratedModule<{ cronModules: Parameters<typeof startCronJobs>[0] }>(
62
+ 'crons.generated.ts'
63
+ );
64
+
65
+ const ctx: AppContext = {
66
+ app,
67
+ config,
68
+ model: deps.models,
69
+ service: undefined as unknown as Services,
70
+ plugin: {} as Plugins
71
+ };
72
+ const activePlugins: ActivePlugin[] = [];
73
+ let activeCrons: ReturnType<typeof startCronJobs> = [];
74
+
75
+ ctx.service = deps.createServices(ctx);
76
+ app.jax = ctx;
77
+
78
+ app.use('*', requestLog);
79
+
80
+ app.use('*', async (c, next) => {
81
+ const state = {};
82
+
83
+ c.state = state;
84
+ c.model = ctx.model;
85
+ c.service = ctx.service;
86
+
87
+ c.set('state', state);
88
+ c.set('model', ctx.model);
89
+ c.set('service', ctx.service);
90
+
91
+ await runWithJaxContext(c, next);
92
+ });
93
+
94
+ const ready = Promise.all(
95
+ (pluginModules as readonly ModuleItem<AppPlugin>[]).map((item) => setupPluginSafely(item, ctx))
96
+ ).then(async (plugins) => {
97
+ activePlugins.push(...plugins.filter((plugin): plugin is ActivePlugin => Boolean(plugin)));
98
+
99
+ printEnabledPlugins(activePlugins);
100
+
101
+ await Promise.all(activePlugins.map((plugin) => runPluginReadySafely(plugin, ctx)));
102
+
103
+ activeCrons = startCronJobs(cronModules, ctx);
104
+ });
105
+
106
+ app.ready = ready;
107
+ app.close = async () => {
108
+ await ready.catch(() => undefined);
109
+ stopCronJobs(activeCrons);
110
+ await closePluginsSafely(activePlugins, ctx);
111
+ };
112
+
113
+ return app;
114
+ }
115
+
116
+ async function setupPlugin(
117
+ item: ModuleItem<AppPlugin>,
118
+ ctx: AppContext
119
+ ): Promise<ActivePlugin | undefined> {
120
+ if (item.enable && !item.enable(ctx)) {
121
+ return;
122
+ }
123
+
124
+ const loadedPlugin = item.module ?? (await loadPlugin(item));
125
+ const plugin =
126
+ loadedPlugin && typeof loadedPlugin === 'object' && 'default' in loadedPlugin
127
+ ? loadedPlugin.default
128
+ : loadedPlugin;
129
+
130
+ if (typeof plugin === 'function') {
131
+ await plugin(ctx, item.options);
132
+ return;
133
+ }
134
+
135
+ if (isPluginObject(plugin)) {
136
+ await plugin.setup?.(ctx, item.options);
137
+ return {
138
+ name: item.name,
139
+ plugin,
140
+ options: item.options
141
+ };
142
+ }
143
+
144
+ throw new TypeError(`Plugin ${item.name} must export a function or lifecycle object.`);
145
+ }
146
+
147
+ function isPluginObject(plugin: unknown): plugin is AppPluginObject {
148
+ if (!plugin || typeof plugin !== 'object') {
149
+ return false;
150
+ }
151
+
152
+ return ['setup', 'ready', 'close'].some((key) => key in plugin);
153
+ }
154
+
155
+ async function loadPlugin(item: ModuleItem<AppPlugin>) {
156
+ if (!item.load) {
157
+ throw new TypeError(`Plugin ${item.name} must define module or load.`);
158
+ }
159
+
160
+ return item.load();
161
+ }
162
+
163
+ async function setupPluginSafely(item: ModuleItem<AppPlugin>, ctx: AppContext) {
164
+ try {
165
+ return await setupPlugin(item, ctx);
166
+ } catch (error) {
167
+ console.error(`Jax plugin setup failed [${item.name}]:`, error);
168
+ return undefined;
169
+ }
170
+ }
171
+
172
+ async function runPluginReadySafely(item: ActivePlugin, ctx: AppContext) {
173
+ if (!item.plugin.ready) {
174
+ return;
175
+ }
176
+
177
+ try {
178
+ await item.plugin.ready(ctx, item.options);
179
+ } catch (error) {
180
+ console.error(`Jax plugin ready failed [${item.name}]:`, error);
181
+ }
182
+ }
183
+
184
+ function printEnabledPlugins(plugins: ActivePlugin[]) {
185
+ if (plugins.length === 0) {
186
+ console.log('[plugins] enabled: none');
187
+ return;
188
+ }
189
+
190
+ console.log(`[plugins] enabled: ${plugins.map((plugin) => plugin.name).join(', ')}`);
191
+ }
192
+
193
+ async function closePluginsSafely(plugins: ActivePlugin[], ctx: AppContext) {
194
+ for (const item of [...plugins].reverse()) {
195
+ if (!item.plugin.close) {
196
+ continue;
197
+ }
198
+
199
+ try {
200
+ await item.plugin.close(ctx, item.options);
201
+ } catch (error) {
202
+ console.error(`Jax plugin close failed [${item.name}]:`, error);
203
+ }
204
+ }
205
+ }
206
+
207
+ export { createApi, createHono, registerCrudRoutes };
@@ -1,85 +1,85 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks';
2
- import type { Context } from 'hono';
3
- import type { GeneratedModels, GeneratedServices } from './generated';
4
-
5
- const controllerContextStorage = new AsyncLocalStorage<Context>();
6
- type ControllerConstructor<T extends Controller> = new (...args: any[]) => T;
7
- type ControllerExport<T extends Controller> = T | ControllerConstructor<T>;
8
-
9
- export function runWithJaxContext<T>(c: Context, callback: () => T) {
10
- return controllerContextStorage.run(c, callback);
11
- }
12
-
13
- export function getJaxContext() {
14
- const context = controllerContextStorage.getStore();
15
-
16
- if (!context) {
17
- throw new Error('Jax request context is not available.');
18
- }
19
-
20
- return context;
21
- }
22
-
23
- export function createController<T extends Controller>(controller: ControllerExport<T>) {
24
- return typeof controller === 'function' ? new controller() : controller;
25
- }
26
-
27
- export abstract class Controller {
28
- constructor() {
29
- this.bindMethods();
30
- }
31
-
32
- protected get context() {
33
- return getJaxContext();
34
- }
35
-
36
- protected get ctx() {
37
- return this.context;
38
- }
39
-
40
- protected get state() {
41
- return this.context.get('state');
42
- }
43
-
44
- protected get model(): GeneratedModels {
45
- return this.context.get('model');
46
- }
47
-
48
- protected get service(): GeneratedServices {
49
- return this.context.get('service');
50
- }
51
-
52
- protected get session() {
53
- return this.context.get('session');
54
- }
55
-
56
- private bindMethods() {
57
- let prototype = Object.getPrototypeOf(this);
58
-
59
- while (prototype && prototype !== Controller.prototype) {
60
- for (const name of Object.getOwnPropertyNames(prototype)) {
61
- if (name === 'constructor') {
62
- continue;
63
- }
64
-
65
- const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
66
-
67
- if (typeof descriptor?.value !== 'function') {
68
- continue;
69
- }
70
-
71
- if (Object.prototype.hasOwnProperty.call(this, name)) {
72
- continue;
73
- }
74
-
75
- Object.defineProperty(this, name, {
76
- configurable: true,
77
- writable: true,
78
- value: descriptor.value.bind(this),
79
- });
80
- }
81
-
82
- prototype = Object.getPrototypeOf(prototype);
83
- }
84
- }
85
- }
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import type { Context } from 'hono';
3
+ import type { GeneratedModels, GeneratedServices } from './generated';
4
+
5
+ const controllerContextStorage = new AsyncLocalStorage<Context>();
6
+ type ControllerConstructor<T extends Controller> = new (...args: any[]) => T;
7
+ type ControllerExport<T extends Controller> = T | ControllerConstructor<T>;
8
+
9
+ export function runWithJaxContext<T>(c: Context, callback: () => T) {
10
+ return controllerContextStorage.run(c, callback);
11
+ }
12
+
13
+ export function getJaxContext() {
14
+ const context = controllerContextStorage.getStore();
15
+
16
+ if (!context) {
17
+ throw new Error('Jax request context is not available.');
18
+ }
19
+
20
+ return context;
21
+ }
22
+
23
+ export function createController<T extends Controller>(controller: ControllerExport<T>) {
24
+ return typeof controller === 'function' ? new controller() : controller;
25
+ }
26
+
27
+ export abstract class Controller {
28
+ constructor() {
29
+ this.bindMethods();
30
+ }
31
+
32
+ protected get context() {
33
+ return getJaxContext();
34
+ }
35
+
36
+ protected get ctx() {
37
+ return this.context;
38
+ }
39
+
40
+ protected get state() {
41
+ return this.context.get('state');
42
+ }
43
+
44
+ protected get model(): GeneratedModels {
45
+ return this.context.get('model');
46
+ }
47
+
48
+ protected get service(): GeneratedServices {
49
+ return this.context.get('service');
50
+ }
51
+
52
+ protected get session() {
53
+ return this.context.get('session');
54
+ }
55
+
56
+ private bindMethods() {
57
+ let prototype = Object.getPrototypeOf(this);
58
+
59
+ while (prototype && prototype !== Controller.prototype) {
60
+ for (const name of Object.getOwnPropertyNames(prototype)) {
61
+ if (name === 'constructor') {
62
+ continue;
63
+ }
64
+
65
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
66
+
67
+ if (typeof descriptor?.value !== 'function') {
68
+ continue;
69
+ }
70
+
71
+ if (Object.prototype.hasOwnProperty.call(this, name)) {
72
+ continue;
73
+ }
74
+
75
+ Object.defineProperty(this, name, {
76
+ configurable: true,
77
+ writable: true,
78
+ value: descriptor.value.bind(this),
79
+ });
80
+ }
81
+
82
+ prototype = Object.getPrototypeOf(prototype);
83
+ }
84
+ }
85
+ }