@xrystal/core 3.26.3 → 3.26.4

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,7 +1,7 @@
1
1
  {
2
2
  "author": "Yusuf Yasir KAYGUSUZ",
3
3
  "name": "@xrystal/core",
4
- "version": "3.26.3",
4
+ "version": "3.26.4",
5
5
  "description": "Project core for xrystal",
6
6
  "publishConfig": {
7
7
  "access": "public",
@@ -2,14 +2,15 @@ import { AsyncLocalStorage } from 'node:async_hooks';
2
2
  import Logger from '../logger';
3
3
  import System from '../system';
4
4
  import { IProvide, ProtocolEnum } from '../../utils/index';
5
- export interface CustomRequest {
6
- accounts?: any;
7
- url: string;
8
- method: string;
5
+ export interface CustomRequest<B = any, Q = any, P = any> {
9
6
  headers: Record<string, any>;
10
- body?: any;
11
- params: Record<string, any>;
12
- query: Record<string, any>;
7
+ method: string;
8
+ url: string;
9
+ body: B;
10
+ params: P;
11
+ query: Q;
12
+ cookies: Record<string, any>;
13
+ ip: string;
13
14
  lang: string;
14
15
  t: (k: string, args?: any) => string;
15
16
  }
@@ -76,9 +77,40 @@ export default abstract class Controller extends BaseController implements IProv
76
77
  constructor();
77
78
  onInit(): Promise<void>;
78
79
  protected parseMessage(msg: any, t: Function, isError?: boolean): string;
79
- schema({ checks, logic, response }: {
80
- checks?: (args: any) => Promise<any>;
81
- logic?: (args: any) => Promise<any>;
82
- response?: (args: any) => Promise<any>;
80
+ schema<B = any, Q = any, P = any>({ checks, logic, response }: {
81
+ checks?: (args: {
82
+ req: CustomRequest<B, Q, P>;
83
+ res: CustomResponse;
84
+ body: B;
85
+ query: Q;
86
+ queries: Q;
87
+ params: P;
88
+ cookies: Record<string, any>;
89
+ locals: Record<string, any>;
90
+ t: Function;
91
+ }) => Promise<any>;
92
+ logic?: (args: {
93
+ req: CustomRequest<B, Q, P>;
94
+ res: CustomResponse;
95
+ body: B;
96
+ query: Q;
97
+ queries: Q;
98
+ params: P;
99
+ cookies: Record<string, any>;
100
+ locals: Record<string, any>;
101
+ t: Function;
102
+ }) => Promise<any>;
103
+ response?: (args: {
104
+ req: CustomRequest<B, Q, P>;
105
+ res: CustomResponse;
106
+ body: B;
107
+ query: Q;
108
+ queries: Q;
109
+ params: P;
110
+ cookies: Record<string, any>;
111
+ locals: Record<string, any>;
112
+ t: Function;
113
+ logicResult: any;
114
+ }) => Promise<any>;
83
115
  }): Promise<any>;
84
116
  }
@@ -2,7 +2,7 @@ import qs from 'qs';
2
2
  import { AsyncLocalStorage } from 'node:async_hooks';
3
3
  import Logger from '../logger';
4
4
  import System from '../system';
5
- import { LoggerLayerEnum, ProtocolEnum, responseMessageHelper, ResponseSchema, x } from '../../utils/index';
5
+ import { getClientIp, LoggerLayerEnum, ProtocolEnum, responseMessageHelper, ResponseSchema, x } from '../../utils/index';
6
6
  export const controllerContextStorage = new AsyncLocalStorage();
7
7
  export const getControllerCtx = () => controllerContextStorage.getStore();
8
8
  export class BaseController {
@@ -22,10 +22,10 @@ export class BaseController {
22
22
  const identityT = (k) => k;
23
23
  const body = store?.metadata?._parsedBody || ctx.body || req.body || ctx.request?.body || {};
24
24
  let query = store?.metadata?._parsedQuery || ctx.query || req.query || ctx.request?.query || {};
25
- const urlStr = ctx.request?.url || req.url || ctx.url || '';
26
- if ((!query || Object.keys(query).length === 0) && urlStr && typeof urlStr === 'string') {
27
- if (urlStr.includes('?')) {
28
- const queryString = urlStr.split('?')[1];
25
+ const urlStr = ctx.request?.url || req.url || ctx.url || "";
26
+ if ((!query || Object.keys(query).length === 0) && urlStr && typeof urlStr === "string") {
27
+ if (urlStr.includes("?")) {
28
+ const queryString = urlStr.split("?")[1];
29
29
  if (queryString)
30
30
  query = qs.parse(queryString);
31
31
  }
@@ -36,14 +36,17 @@ export class BaseController {
36
36
  ...(ctx.request?.params || {}),
37
37
  ...(ctx.request?.routeParams || {})
38
38
  };
39
+ const cookies = ctx.cookie || req.cookies || ctx.request?.cookie || ctx.request?.cookies || {};
39
40
  return {
40
41
  url: urlStr,
41
- method: ctx.method || req.method || ctx.request?.method || '',
42
+ method: (ctx.method || req.method || ctx.request?.method || "").toUpperCase(),
42
43
  headers: ctx.headers || req.headers || ctx.request?.headers || {},
43
44
  body,
44
45
  query: query || {},
45
46
  params: params || {},
46
- lang: ctx.lang || req.lang || 'en',
47
+ cookies: cookies || {},
48
+ ip: getClientIp(req, ctx),
49
+ lang: ctx.lang || req.lang || "en",
47
50
  t: ctx.t || req.t || identityT,
48
51
  accounts: ctx.accounts || req.accounts || ctx.user
49
52
  };
@@ -67,16 +70,16 @@ export class BaseController {
67
70
  return data;
68
71
  if (data instanceof Response)
69
72
  return data;
70
- const isBinary = data instanceof Blob || data instanceof Uint8Array || data instanceof ArrayBuffer || (typeof data?.pipe === 'function');
73
+ const isBinary = data instanceof Blob || data instanceof Uint8Array || data instanceof ArrayBuffer || (typeof data?.pipe === "function");
71
74
  const isRaw = store.metadata?._isRaw || isBinary;
72
- let contentType = store.metadata?._contentType || (isRaw ? 'application/octet-stream' : 'application/json');
75
+ let contentType = store.metadata?._contentType || (isRaw ? "application/octet-stream" : "application/json");
73
76
  let bizCode = store.metadata._code || 200;
74
77
  let httpStatus = store.metadata._statusCode;
75
78
  if (!httpStatus) {
76
79
  httpStatus = (bizCode >= 100 && bizCode <= 101) || (bizCode >= 200 && bizCode <= 599) ? bizCode : (bizCode === 0 || (bizCode >= 200 && bizCode <= 299) ? 200 : 400);
77
80
  }
78
- const body = isRaw || typeof data === 'string' ? data : JSON.stringify(data?.getResponse ? data.getResponse : data);
79
- return new Response(body, { status: httpStatus, headers: { 'content-type': contentType } });
81
+ const body = isRaw || typeof data === "string" ? data : JSON.stringify(data?.getResponse ? data.getResponse : data);
82
+ return new Response(body, { status: httpStatus, headers: { "content-type": contentType } });
80
83
  },
81
84
  json(data) { return this.send(data); }
82
85
  };
@@ -89,45 +92,42 @@ export default class Controller extends BaseController {
89
92
  this.supportedProtocols = Array.isArray(protocols) ? protocols : [protocols || ProtocolEnum.HTTP];
90
93
  }
91
94
  parseMessage(msg, t, isError = false) {
92
- const content = String(msg || '');
95
+ const content = String(msg || "");
93
96
  if (!content)
94
- return isError ? responseMessageHelper.unsuccessful('', '', t) : responseMessageHelper.successfully('', '', t);
95
- let method = isError ? 'unsuccessful' : 'successfully';
96
- let payloadString = content;
97
- if (content.startsWith('@')) {
98
- const parts = content.split(' ');
97
+ return isError ? responseMessageHelper.unsuccessful("", "", t) : responseMessageHelper.successfully("", "", t);
98
+ let method = isError ? "unsuccessful" : "successfully";
99
+ let payloadString = "";
100
+ if (content.startsWith("@")) {
101
+ const parts = content.split(" ");
99
102
  method = parts[0].substring(1);
100
- payloadString = parts.slice(1).join(' ');
103
+ payloadString = parts.slice(1).join(" ");
101
104
  }
102
- const params = payloadString.split('-').map(p => p.trim()).filter(p => p !== '');
103
- const p1 = params[0] || '';
104
- const p2 = params.slice(1).join(' ');
105
+ else {
106
+ payloadString = content;
107
+ }
108
+ const params = payloadString ? payloadString.split("-").map(p => p.trim()).filter(p => p !== "") : [];
109
+ const p1 = params.length > 0 ? params[0] : (content.startsWith("@") ? undefined : "");
110
+ const p2 = params.length > 1 ? params.slice(1).join(" ") : (content.startsWith("@") ? undefined : "");
105
111
  if (responseMessageHelper[method]) {
106
112
  return responseMessageHelper[method](p1, p2, t);
107
113
  }
108
- return isError ? responseMessageHelper.unsuccessful(p1, p2, t) : responseMessageHelper.successfully(p1, p2, t);
114
+ return isError ? responseMessageHelper.unsuccessful(p1 || "", p2 || "", t) : responseMessageHelper.successfully(p1 || "", p2 || "", t);
109
115
  }
110
116
  async schema({ checks, logic, response }) {
111
117
  const currentRes = this.res;
112
118
  const store = this.currentStore;
113
119
  try {
114
120
  const rawReq = store?.req || store?.ctx?.request || store?.ctx?.req;
115
- let parsedBody = this.req.body;
116
- const contentType = (this.req.headers['content-type'] || '').toLowerCase();
117
- const consumeBody = async (bodySource) => {
118
- let text = '';
119
- if (bodySource instanceof ReadableStream || (bodySource && typeof bodySource.getReader === 'function')) {
120
- text = await new Response(bodySource).text();
121
- }
122
- else if (typeof bodySource === 'string') {
123
- text = bodySource;
124
- }
125
- else {
126
- return bodySource;
127
- }
121
+ const currentReqBase = this.req;
122
+ const method = currentReqBase.method;
123
+ const headers = currentReqBase.headers;
124
+ const contentType = (headers["content-type"] || "").toLowerCase();
125
+ const contentLength = parseInt(headers["content-length"] || "-1");
126
+ let parsedBody = currentReqBase.body;
127
+ const parseText = (text) => {
128
128
  if (!text)
129
129
  return {};
130
- if (contentType.includes('application/json')) {
130
+ if (contentType.includes("application/json")) {
131
131
  try {
132
132
  return JSON.parse(text);
133
133
  }
@@ -135,77 +135,72 @@ export default class Controller extends BaseController {
135
135
  return text;
136
136
  }
137
137
  }
138
- else if (contentType.includes('application/x-www-form-urlencoded')) {
138
+ else if (contentType.includes("application/x-www-form-urlencoded")) {
139
139
  return qs.parse(text);
140
140
  }
141
- else {
141
+ try {
142
+ return JSON.parse(text);
143
+ }
144
+ catch {
142
145
  try {
143
- return JSON.parse(text);
146
+ return qs.parse(text);
144
147
  }
145
148
  catch {
146
- try {
147
- return qs.parse(text);
148
- }
149
- catch {
150
- return text;
151
- }
149
+ return text;
152
150
  }
153
151
  }
154
152
  };
155
- if (this.protocol !== ProtocolEnum.WEBSOCKET) {
156
- const isStream = parsedBody instanceof ReadableStream || (parsedBody && typeof parsedBody.getReader === 'function');
157
- const isProcessed = !isStream && parsedBody && typeof parsedBody === 'object' && Object.keys(parsedBody).length > 0 && parsedBody.constructor?.name !== 'ReadableStream';
158
- if (!isProcessed) {
159
- if (isStream || typeof parsedBody === 'string') {
160
- parsedBody = await consumeBody(parsedBody);
153
+ const isStream = (obj) => obj instanceof ReadableStream || (obj && typeof obj.getReader === "function");
154
+ const isProcessed = parsedBody && typeof parsedBody === "object" && !isStream(parsedBody) && Object.keys(parsedBody).length > 0;
155
+ if (this.protocol !== ProtocolEnum.WEBSOCKET && !["GET", "HEAD", "OPTIONS"].includes(method) && !isProcessed) {
156
+ const isModern = rawReq && typeof rawReq.json === "function" && typeof rawReq.text === "function";
157
+ const isNode = rawReq && typeof rawReq.on === "function";
158
+ if (isModern && (isStream(parsedBody) || contentLength > 0)) {
159
+ try {
160
+ const cloned = rawReq.clone();
161
+ parsedBody = parseText(await cloned.text());
161
162
  }
162
- else if (rawReq) {
163
- if (typeof rawReq.json === 'function') {
164
- try {
165
- const cloned = rawReq.clone ? rawReq.clone() : rawReq;
166
- parsedBody = await consumeBody(await cloned.text());
167
- }
168
- catch {
169
- parsedBody = {};
170
- }
171
- }
172
- else if (typeof rawReq.on === 'function') {
173
- parsedBody = await new Promise((resolve) => {
174
- let bodyStr = '';
175
- rawReq.on('data', (chunk) => { bodyStr += chunk; });
176
- rawReq.on('end', async () => resolve(await consumeBody(bodyStr)));
177
- rawReq.on('error', () => resolve({}));
178
- });
179
- }
163
+ catch {
164
+ if (typeof parsedBody === "string")
165
+ parsedBody = parseText(parsedBody);
180
166
  }
181
167
  }
182
- }
183
- else if (typeof parsedBody === 'string') {
184
- try {
185
- parsedBody = JSON.parse(parsedBody);
168
+ else if (isNode && contentLength !== 0) {
169
+ parsedBody = await new Promise((resolve) => {
170
+ let bodyStr = "";
171
+ const timer = setTimeout(() => resolve({}), 200);
172
+ rawReq.on("data", (chunk) => { bodyStr += chunk; });
173
+ rawReq.on("end", () => { clearTimeout(timer); resolve(parseText(bodyStr)); });
174
+ rawReq.on("error", () => { clearTimeout(timer); resolve({}); });
175
+ });
176
+ }
177
+ else if (typeof parsedBody === "string" && parsedBody.length > 0) {
178
+ parsedBody = parseText(parsedBody);
186
179
  }
187
- catch { }
188
180
  }
189
- if (parsedBody && typeof parsedBody === 'object' && parsedBody.constructor?.name === 'FormData') {
181
+ if (parsedBody && typeof parsedBody === "object" && parsedBody.constructor?.name === "FormData") {
190
182
  parsedBody = Object.fromEntries(parsedBody.entries());
191
183
  }
192
184
  if (store) {
193
185
  if (!store.metadata)
194
186
  store.metadata = { locals: {} };
195
187
  store.metadata._parsedBody = parsedBody || {};
196
- store.metadata._parsedQuery = this.req.query;
188
+ store.metadata._parsedQuery = currentReqBase.query;
197
189
  }
198
190
  const currentReq = this.req;
199
191
  const p = {
200
192
  req: currentReq,
201
193
  res: currentRes,
202
194
  body: currentReq.body,
195
+ query: currentReq.query,
203
196
  queries: currentReq.query,
204
197
  params: currentReq.params,
198
+ cookies: currentReq.cookies,
199
+ locals: currentRes.locals,
205
200
  t: currentReq.t,
206
201
  };
207
202
  const extractMeta = (obj) => {
208
- if (!obj || typeof obj !== 'object')
203
+ if (!obj || typeof obj !== "object")
209
204
  return;
210
205
  if (obj.code !== undefined && store?.metadata)
211
206
  store.metadata._code = obj.code;
@@ -229,9 +224,9 @@ export default class Controller extends BaseController {
229
224
  }
230
225
  let logicResult = logic ? await logic(p) : {};
231
226
  extractMeta(logicResult);
232
- if (logicResult instanceof Response || logicResult?.constructor?.name === 'Response')
227
+ if (logicResult instanceof Response || logicResult?.constructor?.name === "Response")
233
228
  return logicResult;
234
- if (logicResult && typeof logicResult === 'object' && typeof logicResult.response === 'function') {
229
+ if (logicResult && typeof logicResult === "object" && typeof logicResult.response === "function") {
235
230
  logicResult = await logicResult.response();
236
231
  extractMeta(logicResult);
237
232
  }
@@ -245,17 +240,17 @@ export default class Controller extends BaseController {
245
240
  return currentRes.status(200).send(resResult);
246
241
  let finalData = undefined;
247
242
  let finalExtraData = undefined;
248
- let messageSource = 'success_process';
243
+ let messageSource = "success_process";
249
244
  const extractData = (obj) => {
250
- if (!obj || typeof obj !== 'object')
245
+ if (!obj || typeof obj !== "object")
251
246
  return;
252
- if (obj.payload && typeof obj.payload === 'object') {
247
+ if (obj.payload && typeof obj.payload === "object") {
253
248
  finalData = obj.payload.data !== undefined ? obj.payload.data : obj.payload;
254
249
  finalExtraData = obj.payload.extraData;
255
250
  }
256
251
  else if (obj.data !== undefined || obj.extraData !== undefined) {
257
252
  finalData = obj.data;
258
- finalExtraData = obj.extraData;
253
+ finalExtraData = obj.extraAddr;
259
254
  }
260
255
  else {
261
256
  const hasMeta = obj.status !== undefined || obj.message !== undefined || obj.code !== undefined || obj.statusCode !== undefined;
@@ -265,7 +260,7 @@ export default class Controller extends BaseController {
265
260
  if (obj.message)
266
261
  messageSource = obj.message;
267
262
  };
268
- if (typeof resResult === 'string') {
263
+ if (typeof resResult === "string") {
269
264
  messageSource = resResult;
270
265
  extractData(logicResult);
271
266
  }
@@ -1 +1 @@
1
- export declare const getClientIp: (req: any) => any;
1
+ export declare const getClientIp: (req: any, ctx?: any) => any;
@@ -1,3 +1,21 @@
1
- export const getClientIp = (req) => {
2
- return req.headers["x-forwarded-for"]?.split(",")[0] || req.socket.remoteAddress;
1
+ export const getClientIp = (req, ctx = {}) => {
2
+ try {
3
+ const forwarded = req?.headers?.["x-forwarded-for"] || ctx?.headers?.["x-forwarded-for"];
4
+ if (forwarded)
5
+ return forwarded.split(",")[0].trim().replace("::ffff:", "");
6
+ if (ctx?.ip)
7
+ return ctx.ip.replace("::ffff:", "");
8
+ if (req?.ip)
9
+ return req.ip.replace("::ffff:", "");
10
+ const remoteAddr = req?.socket?.remoteAddress ||
11
+ ctx?.req?.socket?.remoteAddress ||
12
+ req?.remoteAddress ||
13
+ ctx?.address;
14
+ if (remoteAddr)
15
+ return remoteAddr.replace("::ffff:", "");
16
+ return "127.0.0.1";
17
+ }
18
+ catch (e) {
19
+ return "127.0.0.1";
20
+ }
3
21
  };