jax-hono 1.0.21 → 1.0.22

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.
@@ -33,6 +33,10 @@ type EnabledPlugin = {
33
33
  rootDir: string
34
34
  }
35
35
 
36
+ type PluginFile = EnabledPlugin & {
37
+ filePath: string
38
+ }
39
+
36
40
  const controllerGroups: ControllerGroup[] = [
37
41
  { name: 'api', dirs: ['api', 'controllers/api', 'controller/api', 'modules'], suffix: '.api' },
38
42
  { name: 'backend', dirs: ['backend', 'controllers/backend', 'controller/backend', 'modules'], suffix: '.backend' },
@@ -213,6 +217,10 @@ function getEnabledPluginFiles(suffix: string) {
213
217
  })
214
218
  }
215
219
 
220
+ function isPluginFile(item: { filePath: string } | PluginFile): item is PluginFile {
221
+ return 'name' in item && 'rootDir' in item
222
+ }
223
+
216
224
  function isFileInDir(filePath: string, dirName: string) {
217
225
  const rel = relative(join(srcDir, dirName), filePath)
218
226
 
@@ -510,7 +518,7 @@ function generateServicesRegistry() {
510
518
  .map((match) => match[1])
511
519
  .sort((left, right) => left.localeCompare(right))
512
520
  const defaultServiceClass = source.match(/\bexport\s+default\s+class(?:\s+([a-zA-Z_$][a-zA-Z0-9_$]*))?/)
513
- const fallbackServicePath = 'name' in item
521
+ const fallbackServicePath = isPluginFile(item)
514
522
  ? toPluginModulePath(item.name, item.rootDir, filePath, ['.service'])
515
523
  : toModulePath('services', filePath)
516
524
  const defaultServiceName = defaultServiceClass
@@ -827,24 +835,56 @@ function serializePluginOptions(options: unknown) {
827
835
  return options === undefined ? '' : `, options: ${JSON.stringify(options)}`
828
836
  }
829
837
 
838
+ function pluginExportsJaxPluginContext(rootDir: string) {
839
+ const entryFiles = ['index.ts', 'index.js', 'index.mjs', 'index.cjs']
840
+
841
+ for (const fileName of entryFiles) {
842
+ const filePath = join(rootDir, fileName)
843
+
844
+ if (!existsSync(filePath)) {
845
+ continue
846
+ }
847
+
848
+ const content = readFileSync(filePath, 'utf8')
849
+
850
+ if (/\bexport\s+(?:type\s+)?(?:interface|type)\s+JaxPluginContext\b/.test(content)) {
851
+ return true
852
+ }
853
+ }
854
+
855
+ return false
856
+ }
857
+
830
858
  function generatePluginContextRegistry() {
831
859
  const enabledPlugins = getEnabledPluginConfigItems()
832
860
  const imports = enabledPlugins.map(([name, item]) => {
833
861
  return `import ${toLowerCamelIdentifier([name], 'Plugin')} from ${JSON.stringify(item.package)}`
834
862
  })
863
+ const pluginContexts = enabledPlugins
864
+ .filter(([, item]) => pluginExportsJaxPluginContext(resolvePluginPackageDir(item.package)))
865
+ .map(([name, item]) => ({
866
+ importName: toLowerCamelIdentifier([name], 'PluginContext'),
867
+ packageName: item.package,
868
+ }))
869
+ const contextImports = pluginContexts.map(({ importName, packageName }) => {
870
+ return `import type { JaxPluginContext as ${importName} } from ${JSON.stringify(packageName)}`
871
+ })
872
+ const pluginContextTypes = pluginContexts.map(({ importName }) => importName)
835
873
  const exportNames = enabledPlugins.map(([name]) => toLowerCamelIdentifier([name], 'Plugin'))
836
874
  const moduleEntries = enabledPlugins.map(
837
875
  ([name, item]) =>
838
876
  ` { name: ${JSON.stringify(name)}, module: ${toLowerCamelIdentifier([name], 'Plugin')}${serializePluginOptions(item.options)} },`,
839
877
  )
840
878
  const exportBlock = formatExportBlock(exportNames)
879
+ const pluginsType = ['Record<string, unknown>', ...pluginContextTypes].join(' & ')
841
880
 
842
881
  const content = createGeneratedContent([
843
882
  imports.join('\n'),
883
+ contextImports.join('\n'),
844
884
  exportBlock,
845
- `export const plugins = {} as Record<string, unknown>
885
+ `export type Plugins = ${pluginsType}
846
886
 
847
- export type Plugins = typeof plugins
887
+ export const plugins = {} as Plugins
848
888
 
849
889
  declare module 'jax-hono/core/generated' {
850
890
  interface JaxGenerated {
@@ -1,4 +1,9 @@
1
1
  import { Controller } from "./controller";
2
+ import {
3
+ addDateFilter,
4
+ addDateRangeFilter,
5
+ buildMongooseAutoFilter,
6
+ } from "../plugins/mongoose/filter";
2
7
  import { arrayFrom } from "../utils/array";
3
8
  import { escapeRegex } from "../utils/regexp";
4
9
  import type { Context } from "hono";
@@ -32,6 +37,11 @@ export abstract class ApiController extends Controller {
32
37
  return true;
33
38
  }
34
39
 
40
+ // 启用自动过滤器
41
+ protected get enableAutoFilter() {
42
+ return getLegacyIsAllSearch(this) ?? false;
43
+ }
44
+
35
45
  protected get formatConcurrency() {
36
46
  return 50;
37
47
  }
@@ -183,13 +193,37 @@ export abstract class ApiController extends Controller {
183
193
 
184
194
  protected async getFilter(extraFilter?: Record<string, any>) {
185
195
  const query = this.context.req.query();
186
- const filter: Record<string, any> = { ...(extraFilter ?? {}) };
187
- const searchKeys = arrayFrom<string>(
188
- (this as any).searchKey ?? (this as any).searchKeys,
196
+ const filter: Record<string, any> = {};
197
+ const searchKeys = collectKeys(
198
+ (this as any).searchKey,
199
+ (this as any).searchKeys,
189
200
  );
190
- const likeKeys = arrayFrom<string>(
191
- (this as any).likeKey ?? (this as any).likeKeys,
201
+ const likeKeys = collectKeys(
202
+ (this as any).likeKey,
203
+ (this as any).likeKeys,
192
204
  );
205
+ const keywordKeys = collectKeys((this as any).keywordKeys);
206
+
207
+ if (this.enableAutoFilter) {
208
+ Object.assign(filter, buildMongooseAutoFilter(this.Model, query));
209
+ } else {
210
+ addDateFilter(filter, query, "createdAt", "createdAt");
211
+ addDateRangeFilter(
212
+ filter,
213
+ query,
214
+ "createdAt",
215
+ "createdAtStart",
216
+ "createdAtEnd",
217
+ );
218
+ }
219
+
220
+ if (keywordKeys.length && query.keyword) {
221
+ const reg = new RegExp(escapeRegex(query.keyword), "i");
222
+ filter.$or = [
223
+ ...(Array.isArray(filter.$or) ? filter.$or : []),
224
+ ...keywordKeys.map((key) => ({ [key]: reg })),
225
+ ];
226
+ }
193
227
 
194
228
  for (const key of searchKeys) {
195
229
  if (query[key] !== undefined && query[key] !== "") {
@@ -215,6 +249,10 @@ export abstract class ApiController extends Controller {
215
249
  Object.assign(filter, (this as any).filter);
216
250
  }
217
251
 
252
+ if (extraFilter) {
253
+ Object.assign(filter, extraFilter);
254
+ }
255
+
218
256
  return filter;
219
257
  }
220
258
 
@@ -288,3 +326,38 @@ function getCustomMethod(target: object, name: string, baseMethod: Function) {
288
326
 
289
327
  return undefined;
290
328
  }
329
+
330
+ function getLegacyIsAllSearch(target: object) {
331
+ if (Object.prototype.hasOwnProperty.call(target, "isAllSearch")) {
332
+ return Boolean((target as any).isAllSearch);
333
+ }
334
+
335
+ let prototype = Object.getPrototypeOf(target);
336
+
337
+ while (prototype && prototype !== ApiController.prototype) {
338
+ const descriptor = Object.getOwnPropertyDescriptor(
339
+ prototype,
340
+ "isAllSearch",
341
+ );
342
+
343
+ if (descriptor?.get) {
344
+ return Boolean(descriptor.get.call(target));
345
+ }
346
+
347
+ if (
348
+ "value" in (descriptor ?? {}) &&
349
+ descriptor?.value !== undefined &&
350
+ typeof descriptor.value !== "function"
351
+ ) {
352
+ return Boolean(descriptor.value);
353
+ }
354
+
355
+ prototype = Object.getPrototypeOf(prototype);
356
+ }
357
+
358
+ return undefined;
359
+ }
360
+
361
+ function collectKeys(...values: unknown[]) {
362
+ return values.flatMap((value) => arrayFrom<string>(value as any));
363
+ }
package/core/app.ts CHANGED
@@ -1,13 +1,18 @@
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';
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 {
6
+ requireGeneratedModule,
7
+ type GeneratedModels,
8
+ type GeneratedPlugins,
9
+ } from "./generated";
10
+ import { runWithJaxContext } from "./controller";
11
+ import { startCronJobs, stopCronJobs } from "./cron";
12
+ import { logger } from "../middleware/logger";
13
+ import { requestLog } from "../middleware/request-log";
14
+ import type { Hono } from "hono";
15
+ import type { JaxHono } from "./hono";
11
16
 
12
17
  type Models = GeneratedModels;
13
18
  type CreateServices = (ctx: AppContext) => Record<string, any>;
@@ -55,19 +60,19 @@ type ActivePlugin = {
55
60
 
56
61
  export function createApp(deps: AppRuntimeDeps): JaxHono {
57
62
  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
- );
63
+ const { pluginModules } = requireGeneratedModule<{
64
+ pluginModules: readonly ModuleItem<AppPlugin>[];
65
+ }>("plugins.generated.ts");
66
+ const { cronModules } = requireGeneratedModule<{
67
+ cronModules: Parameters<typeof startCronJobs>[0];
68
+ }>("crons.generated.ts");
64
69
 
65
70
  const ctx: AppContext = {
66
71
  app,
67
72
  config,
68
73
  model: deps.models,
69
74
  service: undefined as unknown as Services,
70
- plugin: {} as Plugins
75
+ plugin: {} as Plugins,
71
76
  };
72
77
  const activePlugins: ActivePlugin[] = [];
73
78
  let activeCrons: ReturnType<typeof startCronJobs> = [];
@@ -75,30 +80,38 @@ export function createApp(deps: AppRuntimeDeps): JaxHono {
75
80
  ctx.service = deps.createServices(ctx);
76
81
  app.jax = ctx;
77
82
 
78
- app.use('*', requestLog);
79
-
80
- app.use('*', async (c, next) => {
83
+ app.use("*", async (c, next) => {
81
84
  const state = {};
82
85
 
83
86
  c.state = state;
84
87
  c.model = ctx.model;
85
88
  c.service = ctx.service;
89
+ c.plugin = ctx.plugin;
86
90
 
87
- c.set('state', state);
88
- c.set('model', ctx.model);
89
- c.set('service', ctx.service);
91
+ c.set("state", state);
92
+ c.set("model", ctx.model);
93
+ c.set("service", ctx.service);
94
+ c.set("plugin", ctx.plugin);
90
95
 
91
96
  await runWithJaxContext(c, next);
92
97
  });
93
98
 
99
+ app.use("*", logger);
100
+
94
101
  const ready = Promise.all(
95
- (pluginModules as readonly ModuleItem<AppPlugin>[]).map((item) => setupPluginSafely(item, ctx))
102
+ (pluginModules as readonly ModuleItem<AppPlugin>[]).map((item) =>
103
+ setupPluginSafely(item, ctx),
104
+ ),
96
105
  ).then(async (plugins) => {
97
- activePlugins.push(...plugins.filter((plugin): plugin is ActivePlugin => Boolean(plugin)));
106
+ activePlugins.push(
107
+ ...plugins.filter((plugin): plugin is ActivePlugin => Boolean(plugin)),
108
+ );
98
109
 
99
110
  printEnabledPlugins(activePlugins);
100
111
 
101
- await Promise.all(activePlugins.map((plugin) => runPluginReadySafely(plugin, ctx)));
112
+ await Promise.all(
113
+ activePlugins.map((plugin) => runPluginReadySafely(plugin, ctx)),
114
+ );
102
115
 
103
116
  activeCrons = startCronJobs(cronModules, ctx);
104
117
  });
@@ -115,7 +128,7 @@ export function createApp(deps: AppRuntimeDeps): JaxHono {
115
128
 
116
129
  async function setupPlugin(
117
130
  item: ModuleItem<AppPlugin>,
118
- ctx: AppContext
131
+ ctx: AppContext,
119
132
  ): Promise<ActivePlugin | undefined> {
120
133
  if (item.enable && !item.enable(ctx)) {
121
134
  return;
@@ -123,11 +136,13 @@ async function setupPlugin(
123
136
 
124
137
  const loadedPlugin = item.module ?? (await loadPlugin(item));
125
138
  const plugin =
126
- loadedPlugin && typeof loadedPlugin === 'object' && 'default' in loadedPlugin
139
+ loadedPlugin &&
140
+ typeof loadedPlugin === "object" &&
141
+ "default" in loadedPlugin
127
142
  ? loadedPlugin.default
128
143
  : loadedPlugin;
129
144
 
130
- if (typeof plugin === 'function') {
145
+ if (typeof plugin === "function") {
131
146
  await plugin(ctx, item.options);
132
147
  return;
133
148
  }
@@ -137,19 +152,21 @@ async function setupPlugin(
137
152
  return {
138
153
  name: item.name,
139
154
  plugin,
140
- options: item.options
155
+ options: item.options,
141
156
  };
142
157
  }
143
158
 
144
- throw new TypeError(`Plugin ${item.name} must export a function or lifecycle object.`);
159
+ throw new TypeError(
160
+ `Plugin ${item.name} must export a function or lifecycle object.`,
161
+ );
145
162
  }
146
163
 
147
164
  function isPluginObject(plugin: unknown): plugin is AppPluginObject {
148
- if (!plugin || typeof plugin !== 'object') {
165
+ if (!plugin || typeof plugin !== "object") {
149
166
  return false;
150
167
  }
151
168
 
152
- return ['setup', 'ready', 'close'].some((key) => key in plugin);
169
+ return ["setup", "ready", "close"].some((key) => key in plugin);
153
170
  }
154
171
 
155
172
  async function loadPlugin(item: ModuleItem<AppPlugin>) {
@@ -183,11 +200,13 @@ async function runPluginReadySafely(item: ActivePlugin, ctx: AppContext) {
183
200
 
184
201
  function printEnabledPlugins(plugins: ActivePlugin[]) {
185
202
  if (plugins.length === 0) {
186
- console.log('[plugins] enabled: none');
203
+ console.log("[plugins] enabled: none");
187
204
  return;
188
205
  }
189
206
 
190
- console.log(`[plugins] enabled: ${plugins.map((plugin) => plugin.name).join(', ')}`);
207
+ console.log(
208
+ `[plugins] enabled: ${plugins.map((plugin) => plugin.name).join(", ")}`,
209
+ );
191
210
  }
192
211
 
193
212
  async function closePluginsSafely(plugins: ActivePlugin[], ctx: AppContext) {
@@ -1,6 +1,6 @@
1
1
  import { AsyncLocalStorage } from 'node:async_hooks';
2
2
  import type { Context } from 'hono';
3
- import type { GeneratedModels, GeneratedServices } from './generated';
3
+ import type { GeneratedModels, GeneratedPlugins, GeneratedServices } from './generated';
4
4
 
5
5
  const controllerContextStorage = new AsyncLocalStorage<Context>();
6
6
  type ControllerConstructor<T extends Controller> = new (...args: any[]) => T;
@@ -49,6 +49,10 @@ export abstract class Controller {
49
49
  return this.context.get('service');
50
50
  }
51
51
 
52
+ protected get plugin(): GeneratedPlugins {
53
+ return this.context.get('plugin');
54
+ }
55
+
52
56
  protected get session() {
53
57
  return this.context.get('session');
54
58
  }
package/core/hono.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Hono } from 'hono';
2
2
  import { apiResponse } from '../middleware/api-response';
3
3
  import { registerCrudRoutes, type JaxCrudController } from '../helpers/crud';
4
- import type { GeneratedModels, GeneratedServices } from './generated';
4
+ import type { GeneratedModels, GeneratedPlugins, GeneratedServices } from './generated';
5
5
  import type { JaxState } from '../types/context';
6
6
 
7
7
  declare module 'hono' {
@@ -9,11 +9,13 @@ declare module 'hono' {
9
9
  state: JaxState;
10
10
  model: GeneratedModels;
11
11
  service: GeneratedServices;
12
+ plugin: GeneratedPlugins;
12
13
  }
13
14
 
14
15
  interface ContextVariableMap {
15
16
  model: GeneratedModels;
16
17
  service: GeneratedServices;
18
+ plugin: GeneratedPlugins;
17
19
  state: JaxState;
18
20
  }
19
21
  }
@@ -0,0 +1,3 @@
1
+ import { logger as honoLogger } from "hono/logger";
2
+
3
+ export const logger = honoLogger();
@@ -1,3 +1,114 @@
1
- import { logger } from 'hono/logger';
1
+ import type { Context, MiddlewareHandler } from 'hono';
2
2
 
3
- export const requestLog = logger();
3
+ type ContextWithRequestLog = Context & {
4
+ error?: unknown;
5
+ model?: {
6
+ RequestLog?: {
7
+ create(data: Record<string, unknown>): unknown | Promise<unknown>;
8
+ };
9
+ };
10
+ state?: unknown;
11
+ };
12
+
13
+ export const requestLog: MiddlewareHandler = async (c, next) => {
14
+ const start = Date.now();
15
+ const body = getRequestBody(c);
16
+ let thrownError: unknown;
17
+
18
+ try {
19
+ await next();
20
+ } catch (error) {
21
+ thrownError = error;
22
+ throw error;
23
+ } finally {
24
+ await createRequestLog(c as ContextWithRequestLog, start, body, thrownError);
25
+ }
26
+ };
27
+
28
+ async function createRequestLog(
29
+ c: ContextWithRequestLog,
30
+ start: number,
31
+ body: Promise<unknown>,
32
+ thrownError?: unknown
33
+ ) {
34
+ const RequestLog = c.model?.RequestLog;
35
+
36
+ if (!RequestLog) {
37
+ return;
38
+ }
39
+
40
+ try {
41
+ const data: Record<string, unknown> = {
42
+ ip: c.req.header('x-real-ip'),
43
+ url: c.req.url,
44
+ method: c.req.method,
45
+ body: await body,
46
+ query: c.req.query(),
47
+ headers: c.req.header(),
48
+ responseTime: Date.now() - start,
49
+ status: c.res.status,
50
+ state: c.state
51
+ };
52
+ const error = thrownError ?? c.error;
53
+
54
+ if (error) {
55
+ data.error = error instanceof Error
56
+ ? {
57
+ name: error.name,
58
+ message: error.message,
59
+ stack: error.stack,
60
+ cause: error.cause,
61
+ keys: Object.getOwnPropertyNames(error)
62
+ }
63
+ : {
64
+ message: String(error),
65
+ keys: []
66
+ };
67
+ }
68
+
69
+ if (c.res.status !== 200 && c.res.status !== 304) {
70
+ data.response = await c.res.clone().text().catch(() => undefined);
71
+ } else {
72
+ const response = await c.res.clone().json().catch(() => undefined);
73
+
74
+ if (response && typeof response === 'object' && 'code' in response) {
75
+ data.apiCode = response.code;
76
+ if (response.code !== 0) data.response = JSON.stringify(response);
77
+ }
78
+ }
79
+
80
+ await RequestLog.create(data);
81
+ } catch (error) {
82
+ console.error('Request log create failed:', error);
83
+ }
84
+ }
85
+
86
+ async function getRequestBody(c: Context) {
87
+ if (!hasRequestBody(c.req.method)) {
88
+ return undefined;
89
+ }
90
+
91
+ const contentType = c.req.header('content-type') ?? '';
92
+ const request = c.req.raw.clone();
93
+
94
+ try {
95
+ if (contentType.includes('application/json')) {
96
+ return await request.json();
97
+ }
98
+
99
+ if (
100
+ contentType.includes('application/x-www-form-urlencoded') ||
101
+ contentType.includes('multipart/form-data')
102
+ ) {
103
+ return Object.fromEntries(await request.formData());
104
+ }
105
+
106
+ return await request.text();
107
+ } catch {
108
+ return undefined;
109
+ }
110
+ }
111
+
112
+ function hasRequestBody(method: string) {
113
+ return !['GET', 'HEAD'].includes(method.toUpperCase());
114
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jax-hono",
3
- "version": "1.0.21",
3
+ "version": "1.0.22",
4
4
  "description": "Lightweight framework layer on top of Hono, built for Bun",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -0,0 +1,132 @@
1
+ import { escapeRegex } from "../../utils/regexp";
2
+
3
+ export type MongooseQueryValue = string | number | boolean;
4
+ export type MongooseQuery = Record<string, string>;
5
+
6
+ export type MongooseAutoFilterOptions = {
7
+ excludeKeys?: string[];
8
+ };
9
+
10
+ const defaultExcludeKeys = ["id", "_v", "__v"];
11
+
12
+ export function buildMongooseAutoFilter(
13
+ modelOrSchema: any,
14
+ query: MongooseQuery,
15
+ options: MongooseAutoFilterOptions = {},
16
+ ) {
17
+ const filter: Record<string, any> = {};
18
+ const schema = modelOrSchema?.schema ?? modelOrSchema;
19
+ const tree = schema?.tree ?? schema?.paths ?? {};
20
+ const excludeKeys = new Set([
21
+ ...defaultExcludeKeys,
22
+ ...(options.excludeKeys ?? []),
23
+ ]);
24
+
25
+ for (const key in tree) {
26
+ if (excludeKeys.has(key)) {
27
+ continue;
28
+ }
29
+
30
+ const schemaType = getSchemaPath(schema, key);
31
+
32
+ if (schemaType?.instance === "Date") {
33
+ addDateFilter(filter, query, key);
34
+ addDateRangeFilter(filter, query, key, `${key}Start`, `${key}End`);
35
+ }
36
+ }
37
+
38
+ for (const [key, rawValue] of Object.entries(query)) {
39
+ if (rawValue === undefined || rawValue === "") {
40
+ continue;
41
+ }
42
+
43
+ const schemaType = getSchemaPath(schema, key);
44
+
45
+ if (!schemaType || schemaType.instance === "Date") {
46
+ continue;
47
+ }
48
+
49
+ filter[key] = normalizeSchemaFilterValue(rawValue, schemaType.instance);
50
+ }
51
+
52
+ return filter;
53
+ }
54
+
55
+ export function addDateFilter(
56
+ filter: Record<string, any>,
57
+ query: MongooseQuery,
58
+ targetField: string,
59
+ queryKey = targetField,
60
+ ) {
61
+ if (!query[queryKey]) {
62
+ return;
63
+ }
64
+
65
+ filter[targetField] = {
66
+ $gte: new Date(`${query[queryKey]} 00:00:00`),
67
+ $lte: new Date(`${query[queryKey]} 23:59:59`),
68
+ };
69
+ }
70
+
71
+ export function addDateRangeFilter(
72
+ filter: Record<string, any>,
73
+ query: MongooseQuery,
74
+ targetField = "createdAt",
75
+ startDateField = `${targetField}Start`,
76
+ endDateField = `${targetField}End`,
77
+ ) {
78
+ if (query[startDateField]) {
79
+ filter[targetField] = {
80
+ $gte: new Date(processDateRange(query[startDateField], true)),
81
+ };
82
+ }
83
+
84
+ if (query[endDateField]) {
85
+ filter[targetField] = {
86
+ ...filter[targetField],
87
+ $lte: new Date(processDateRange(query[endDateField], false)),
88
+ };
89
+ }
90
+ }
91
+
92
+ function getSchemaPath(schema: any, key: string) {
93
+ return typeof schema?.path === "function"
94
+ ? schema.path(key)
95
+ : schema?.paths?.[key];
96
+ }
97
+
98
+ function normalizeSchemaFilterValue(
99
+ value: MongooseQueryValue,
100
+ schemaInstance?: string,
101
+ ) {
102
+ switch (schemaInstance) {
103
+ case "String":
104
+ return new RegExp(escapeRegex(value), "i");
105
+ case "ObjectId":
106
+ return value;
107
+ case "Number":
108
+ return Number(value);
109
+ case "Boolean":
110
+ return normalizeBoolean(value);
111
+ default:
112
+ return value;
113
+ }
114
+ }
115
+
116
+ function normalizeBoolean(value: MongooseQueryValue) {
117
+ if (typeof value === "string") {
118
+ return value === "true";
119
+ }
120
+
121
+ if (typeof value === "number") {
122
+ return value === 1;
123
+ }
124
+
125
+ return value;
126
+ }
127
+
128
+ function processDateRange(value: string, isStart: boolean) {
129
+ return value.length === 10
130
+ ? `${value} ${isStart ? "00:00:00" : "23:59:59"}`
131
+ : value;
132
+ }
@@ -55,3 +55,4 @@ export default definePlugin({
55
55
  });
56
56
  export * from './model';
57
57
  export * from './schema';
58
+ export * from './filter';
package/types/context.ts CHANGED
@@ -1,9 +1,10 @@
1
- import type { GeneratedModels, GeneratedServices } from '../core/generated';
1
+ import type { GeneratedModels, GeneratedPlugins, GeneratedServices } from '../core/generated';
2
2
 
3
3
  export type JaxState = Record<string, unknown>;
4
4
 
5
5
  export type JaxContextVariables = {
6
6
  model: GeneratedModels;
7
7
  service: GeneratedServices;
8
+ plugin: GeneratedPlugins;
8
9
  state: JaxState;
9
10
  };