@xrystal/core 3.26.2 → 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.2",
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
- params,
46
- lang: ctx.lang || req.lang || 'en',
46
+ params: params || {},
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,46 +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
- let parsedQuery = this.req.query;
117
- const contentType = (this.req.headers['content-type'] || '').toLowerCase();
118
- const consumeBody = async (bodySource) => {
119
- let text = '';
120
- if (bodySource instanceof ReadableStream || (bodySource && typeof bodySource.getReader === 'function')) {
121
- text = await new Response(bodySource).text();
122
- }
123
- else if (typeof bodySource === 'string') {
124
- text = bodySource;
125
- }
126
- else {
127
- return bodySource;
128
- }
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) => {
129
128
  if (!text)
130
129
  return {};
131
- if (contentType.includes('application/json')) {
130
+ if (contentType.includes("application/json")) {
132
131
  try {
133
132
  return JSON.parse(text);
134
133
  }
@@ -136,70 +135,72 @@ export default class Controller extends BaseController {
136
135
  return text;
137
136
  }
138
137
  }
139
- else if (contentType.includes('application/x-www-form-urlencoded')) {
138
+ else if (contentType.includes("application/x-www-form-urlencoded")) {
140
139
  return qs.parse(text);
141
140
  }
142
- else {
141
+ try {
142
+ return JSON.parse(text);
143
+ }
144
+ catch {
143
145
  try {
144
- return JSON.parse(text);
146
+ return qs.parse(text);
145
147
  }
146
148
  catch {
147
- try {
148
- return qs.parse(text);
149
- }
150
- catch {
151
- return text;
152
- }
149
+ return text;
153
150
  }
154
151
  }
155
152
  };
156
- if (this.protocol !== ProtocolEnum.WEBSOCKET) {
157
- const isStream = parsedBody instanceof ReadableStream || (parsedBody && typeof parsedBody.getReader === 'function');
158
- const isProcessed = !isStream && parsedBody && typeof parsedBody === 'object' && Object.keys(parsedBody).length > 0 && parsedBody.constructor?.name !== 'ReadableStream';
159
- if (!isProcessed) {
160
- if (isStream || typeof parsedBody === 'string') {
161
- 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());
162
162
  }
163
- else if (rawReq) {
164
- if (typeof rawReq.json === 'function') {
165
- try {
166
- const cloned = rawReq.clone ? rawReq.clone() : rawReq;
167
- parsedBody = await consumeBody(await cloned.text());
168
- }
169
- catch {
170
- parsedBody = {};
171
- }
172
- }
173
- else if (typeof rawReq.on === 'function') {
174
- parsedBody = await new Promise((resolve) => {
175
- let bodyStr = '';
176
- rawReq.on('data', (chunk) => { bodyStr += chunk; });
177
- rawReq.on('end', async () => resolve(await consumeBody(bodyStr)));
178
- rawReq.on('error', () => resolve({}));
179
- });
180
- }
163
+ catch {
164
+ if (typeof parsedBody === "string")
165
+ parsedBody = parseText(parsedBody);
181
166
  }
182
167
  }
183
- }
184
- else if (typeof parsedBody === 'string') {
185
- try {
186
- 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);
187
179
  }
188
- catch { }
189
180
  }
190
- if (parsedBody && typeof parsedBody === 'object' && parsedBody.constructor?.name === 'FormData') {
181
+ if (parsedBody && typeof parsedBody === "object" && parsedBody.constructor?.name === "FormData") {
191
182
  parsedBody = Object.fromEntries(parsedBody.entries());
192
183
  }
193
184
  if (store) {
194
185
  if (!store.metadata)
195
186
  store.metadata = { locals: {} };
196
187
  store.metadata._parsedBody = parsedBody || {};
197
- store.metadata._parsedQuery = parsedQuery || {};
188
+ store.metadata._parsedQuery = currentReqBase.query;
198
189
  }
199
190
  const currentReq = this.req;
200
- const p = { req: currentReq, res: currentRes, body: currentReq.body, query: currentReq.query, params: currentReq.params, t: currentReq.t, accounts: currentReq.accounts };
191
+ const p = {
192
+ req: currentReq,
193
+ res: currentRes,
194
+ body: currentReq.body,
195
+ query: currentReq.query,
196
+ queries: currentReq.query,
197
+ params: currentReq.params,
198
+ cookies: currentReq.cookies,
199
+ locals: currentRes.locals,
200
+ t: currentReq.t,
201
+ };
201
202
  const extractMeta = (obj) => {
202
- if (!obj || typeof obj !== 'object')
203
+ if (!obj || typeof obj !== "object")
203
204
  return;
204
205
  if (obj.code !== undefined && store?.metadata)
205
206
  store.metadata._code = obj.code;
@@ -223,9 +224,9 @@ export default class Controller extends BaseController {
223
224
  }
224
225
  let logicResult = logic ? await logic(p) : {};
225
226
  extractMeta(logicResult);
226
- if (logicResult instanceof Response || logicResult?.constructor?.name === 'Response')
227
+ if (logicResult instanceof Response || logicResult?.constructor?.name === "Response")
227
228
  return logicResult;
228
- if (logicResult && typeof logicResult === 'object' && typeof logicResult.response === 'function') {
229
+ if (logicResult && typeof logicResult === "object" && typeof logicResult.response === "function") {
229
230
  logicResult = await logicResult.response();
230
231
  extractMeta(logicResult);
231
232
  }
@@ -239,17 +240,17 @@ export default class Controller extends BaseController {
239
240
  return currentRes.status(200).send(resResult);
240
241
  let finalData = undefined;
241
242
  let finalExtraData = undefined;
242
- let messageSource = 'success_process';
243
+ let messageSource = "success_process";
243
244
  const extractData = (obj) => {
244
- if (!obj || typeof obj !== 'object')
245
+ if (!obj || typeof obj !== "object")
245
246
  return;
246
- if (obj.payload && typeof obj.payload === 'object') {
247
+ if (obj.payload && typeof obj.payload === "object") {
247
248
  finalData = obj.payload.data !== undefined ? obj.payload.data : obj.payload;
248
249
  finalExtraData = obj.payload.extraData;
249
250
  }
250
251
  else if (obj.data !== undefined || obj.extraData !== undefined) {
251
252
  finalData = obj.data;
252
- finalExtraData = obj.extraData;
253
+ finalExtraData = obj.extraAddr;
253
254
  }
254
255
  else {
255
256
  const hasMeta = obj.status !== undefined || obj.message !== undefined || obj.code !== undefined || obj.statusCode !== undefined;
@@ -259,7 +260,7 @@ export default class Controller extends BaseController {
259
260
  if (obj.message)
260
261
  messageSource = obj.message;
261
262
  };
262
- if (typeof resResult === 'string') {
263
+ if (typeof resResult === "string") {
263
264
  messageSource = resResult;
264
265
  extractData(logicResult);
265
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
  };