@xrystal/core 3.26.3 → 3.26.5

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.5",
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
  }
@@ -17,6 +18,7 @@ export interface CustomResponse {
17
18
  status: (code: number) => CustomResponse;
18
19
  send: (data: any) => any;
19
20
  json: (data: any) => any;
21
+ cookie: (name: string, value: string, options?: any) => CustomResponse;
20
22
  locals: Record<string, any>;
21
23
  }
22
24
  export declare const controllerContextStorage: AsyncLocalStorage<{
@@ -32,6 +34,10 @@ export declare const controllerContextStorage: AsyncLocalStorage<{
32
34
  _isRaw?: boolean;
33
35
  _parsedBody?: any;
34
36
  _parsedQuery?: any;
37
+ _cookiesToSet?: Record<string, {
38
+ value: string;
39
+ options?: any;
40
+ }>;
35
41
  };
36
42
  }>;
37
43
  export declare const getControllerCtx: () => {
@@ -47,6 +53,10 @@ export declare const getControllerCtx: () => {
47
53
  _isRaw?: boolean;
48
54
  _parsedBody?: any;
49
55
  _parsedQuery?: any;
56
+ _cookiesToSet?: Record<string, {
57
+ value: string;
58
+ options?: any;
59
+ }>;
50
60
  };
51
61
  };
52
62
  export declare abstract class BaseController {
@@ -67,6 +77,10 @@ export declare abstract class BaseController {
67
77
  _isRaw?: boolean;
68
78
  _parsedBody?: any;
69
79
  _parsedQuery?: any;
80
+ _cookiesToSet?: Record<string, {
81
+ value: string;
82
+ options?: any;
83
+ }>;
70
84
  };
71
85
  };
72
86
  protected get req(): CustomRequest;
@@ -76,9 +90,40 @@ export default abstract class Controller extends BaseController implements IProv
76
90
  constructor();
77
91
  onInit(): Promise<void>;
78
92
  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>;
93
+ schema<B = any, Q = any, P = any>({ checks, logic, response }: {
94
+ checks?: (args: {
95
+ req: CustomRequest<B, Q, P>;
96
+ res: CustomResponse;
97
+ body: B;
98
+ query: Q;
99
+ queries: Q;
100
+ params: P;
101
+ cookies: Record<string, any>;
102
+ locals: Record<string, any>;
103
+ t: Function;
104
+ }) => Promise<any>;
105
+ logic?: (args: {
106
+ req: CustomRequest<B, Q, P>;
107
+ res: CustomResponse;
108
+ body: B;
109
+ query: Q;
110
+ queries: Q;
111
+ params: P;
112
+ cookies: Record<string, any>;
113
+ locals: Record<string, any>;
114
+ t: Function;
115
+ }) => Promise<any>;
116
+ response?: (args: {
117
+ req: CustomRequest<B, Q, P>;
118
+ res: CustomResponse;
119
+ body: B;
120
+ query: Q;
121
+ queries: Q;
122
+ params: P;
123
+ cookies: Record<string, any>;
124
+ locals: Record<string, any>;
125
+ t: Function;
126
+ logicResult: any;
127
+ }) => Promise<any>;
83
128
  }): Promise<any>;
84
129
  }
@@ -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,26 @@ export class BaseController {
36
36
  ...(ctx.request?.params || {}),
37
37
  ...(ctx.request?.routeParams || {})
38
38
  };
39
+ const headers = ctx.headers || req.headers || ctx.request?.headers || {};
40
+ let cookies = ctx.cookies || ctx.cookie || req.cookies || ctx.request?.cookie || ctx.request?.cookies || {};
41
+ if (Object.keys(cookies).length === 0 && headers.cookie) {
42
+ cookies = headers.cookie.split(';').reduce((acc, v) => {
43
+ const parts = v.split('=');
44
+ if (parts.length === 2)
45
+ acc[decodeURIComponent(parts[0].trim())] = decodeURIComponent(parts[1].trim());
46
+ return acc;
47
+ }, {});
48
+ }
39
49
  return {
40
50
  url: urlStr,
41
- method: ctx.method || req.method || ctx.request?.method || '',
42
- headers: ctx.headers || req.headers || ctx.request?.headers || {},
51
+ method: (ctx.method || req.method || ctx.request?.method || "").toUpperCase(),
52
+ headers,
43
53
  body,
44
54
  query: query || {},
45
55
  params: params || {},
46
- lang: ctx.lang || req.lang || 'en',
56
+ cookies: cookies || {},
57
+ ip: getClientIp(req, ctx),
58
+ lang: ctx.lang || req.lang || "en",
47
59
  t: ctx.t || req.t || identityT,
48
60
  accounts: ctx.accounts || req.accounts || ctx.user
49
61
  };
@@ -51,32 +63,63 @@ export class BaseController {
51
63
  get res() {
52
64
  const store = this.currentStore;
53
65
  const self = this;
54
- const fallbackRes = { status: function () { return this; }, send: (d) => d, json: function (d) { return this.send(d); }, locals: {} };
66
+ const fallbackRes = { status: function () { return this; }, send: (d) => d, json: function (d) { return this.send(d); }, cookie: function () { return this; }, locals: {} };
55
67
  if (!store)
56
68
  return fallbackRes;
57
69
  if (!store.metadata)
58
- store.metadata = { locals: {} };
70
+ store.metadata = { locals: {}, _cookiesToSet: {} };
59
71
  return {
60
72
  get locals() { return store.metadata.locals; },
61
73
  status(code) {
62
74
  store.metadata._code = code;
63
75
  return this;
64
76
  },
77
+ cookie(name, value, options = {}) {
78
+ if (!store.metadata._cookiesToSet)
79
+ store.metadata._cookiesToSet = {};
80
+ store.metadata._cookiesToSet[name] = { value, options };
81
+ const ctx = store.ctx;
82
+ if (ctx?.res?.cookie)
83
+ ctx.res.cookie(name, value, options);
84
+ return this;
85
+ },
65
86
  send(data) {
66
87
  if (self.protocol === ProtocolEnum.WEBSOCKET)
67
88
  return data;
68
89
  if (data instanceof Response)
69
90
  return data;
70
- const isBinary = data instanceof Blob || data instanceof Uint8Array || data instanceof ArrayBuffer || (typeof data?.pipe === 'function');
91
+ const isBinary = data instanceof Blob || data instanceof Uint8Array || data instanceof ArrayBuffer || (typeof data?.pipe === "function");
71
92
  const isRaw = store.metadata?._isRaw || isBinary;
72
- let contentType = store.metadata?._contentType || (isRaw ? 'application/octet-stream' : 'application/json');
93
+ let contentType = store.metadata?._contentType || (isRaw ? "application/octet-stream" : "application/json");
73
94
  let bizCode = store.metadata._code || 200;
74
95
  let httpStatus = store.metadata._statusCode;
75
96
  if (!httpStatus) {
76
97
  httpStatus = (bizCode >= 100 && bizCode <= 101) || (bizCode >= 200 && bizCode <= 599) ? bizCode : (bizCode === 0 || (bizCode >= 200 && bizCode <= 299) ? 200 : 400);
77
98
  }
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 } });
99
+ const responseHeaders = new Headers();
100
+ responseHeaders.set("content-type", contentType);
101
+ if (store.metadata._cookiesToSet) {
102
+ Object.entries(store.metadata._cookiesToSet).forEach(([name, { value, options }]) => {
103
+ let cookieStr = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
104
+ if (options.expires)
105
+ cookieStr += `; Expires=${options.expires.toUTCString()}`;
106
+ if (options.maxAge)
107
+ cookieStr += `; Max-Age=${options.maxAge}`;
108
+ if (options.domain)
109
+ cookieStr += `; Domain=${options.domain}`;
110
+ if (options.path)
111
+ cookieStr += `; Path=${options.path}`;
112
+ if (options.secure)
113
+ cookieStr += `; Secure`;
114
+ if (options.httpOnly)
115
+ cookieStr += `; HttpOnly`;
116
+ if (options.sameSite)
117
+ cookieStr += `; SameSite=${options.sameSite}`;
118
+ responseHeaders.append("set-cookie", cookieStr);
119
+ });
120
+ }
121
+ const body = isRaw || typeof data === "string" ? data : JSON.stringify(data?.getResponse ? data.getResponse : data);
122
+ return new Response(body, { status: httpStatus, headers: responseHeaders });
80
123
  },
81
124
  json(data) { return this.send(data); }
82
125
  };
@@ -89,45 +132,42 @@ export default class Controller extends BaseController {
89
132
  this.supportedProtocols = Array.isArray(protocols) ? protocols : [protocols || ProtocolEnum.HTTP];
90
133
  }
91
134
  parseMessage(msg, t, isError = false) {
92
- const content = String(msg || '');
135
+ const content = String(msg || "");
93
136
  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(' ');
137
+ return isError ? responseMessageHelper.unsuccessful("", "", t) : responseMessageHelper.successfully("", "", t);
138
+ let method = isError ? "unsuccessful" : "successfully";
139
+ let payloadString = "";
140
+ if (content.startsWith("@")) {
141
+ const parts = content.split(" ");
99
142
  method = parts[0].substring(1);
100
- payloadString = parts.slice(1).join(' ');
143
+ payloadString = parts.slice(1).join(" ");
144
+ }
145
+ else {
146
+ payloadString = content;
101
147
  }
102
- const params = payloadString.split('-').map(p => p.trim()).filter(p => p !== '');
103
- const p1 = params[0] || '';
104
- const p2 = params.slice(1).join(' ');
148
+ const params = payloadString ? payloadString.split("-").map(p => p.trim()).filter(p => p !== "") : [];
149
+ const p1 = params.length > 0 ? params[0] : (content.startsWith("@") ? undefined : "");
150
+ const p2 = params.length > 1 ? params.slice(1).join(" ") : (content.startsWith("@") ? undefined : "");
105
151
  if (responseMessageHelper[method]) {
106
152
  return responseMessageHelper[method](p1, p2, t);
107
153
  }
108
- return isError ? responseMessageHelper.unsuccessful(p1, p2, t) : responseMessageHelper.successfully(p1, p2, t);
154
+ return isError ? responseMessageHelper.unsuccessful(p1 || "", p2 || "", t) : responseMessageHelper.successfully(p1 || "", p2 || "", t);
109
155
  }
110
156
  async schema({ checks, logic, response }) {
111
157
  const currentRes = this.res;
112
158
  const store = this.currentStore;
113
159
  try {
114
160
  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
- }
161
+ const currentReqBase = this.req;
162
+ const method = currentReqBase.method;
163
+ const headers = currentReqBase.headers;
164
+ const contentType = (headers["content-type"] || "").toLowerCase();
165
+ const contentLength = parseInt(headers["content-length"] || "-1");
166
+ let parsedBody = currentReqBase.body;
167
+ const parseText = (text) => {
128
168
  if (!text)
129
169
  return {};
130
- if (contentType.includes('application/json')) {
170
+ if (contentType.includes("application/json")) {
131
171
  try {
132
172
  return JSON.parse(text);
133
173
  }
@@ -135,77 +175,72 @@ export default class Controller extends BaseController {
135
175
  return text;
136
176
  }
137
177
  }
138
- else if (contentType.includes('application/x-www-form-urlencoded')) {
178
+ else if (contentType.includes("application/x-www-form-urlencoded")) {
139
179
  return qs.parse(text);
140
180
  }
141
- else {
181
+ try {
182
+ return JSON.parse(text);
183
+ }
184
+ catch {
142
185
  try {
143
- return JSON.parse(text);
186
+ return qs.parse(text);
144
187
  }
145
188
  catch {
146
- try {
147
- return qs.parse(text);
148
- }
149
- catch {
150
- return text;
151
- }
189
+ return text;
152
190
  }
153
191
  }
154
192
  };
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);
193
+ const isStream = (obj) => obj instanceof ReadableStream || (obj && typeof obj.getReader === "function");
194
+ const isProcessed = parsedBody && typeof parsedBody === "object" && !isStream(parsedBody) && Object.keys(parsedBody).length > 0;
195
+ if (this.protocol !== ProtocolEnum.WEBSOCKET && !["GET", "HEAD", "OPTIONS"].includes(method) && !isProcessed) {
196
+ const isModern = rawReq && typeof rawReq.json === "function" && typeof rawReq.text === "function";
197
+ const isNode = rawReq && typeof rawReq.on === "function";
198
+ if (isModern && (isStream(parsedBody) || contentLength > 0)) {
199
+ try {
200
+ const cloned = rawReq.clone();
201
+ parsedBody = parseText(await cloned.text());
161
202
  }
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
- }
203
+ catch {
204
+ if (typeof parsedBody === "string")
205
+ parsedBody = parseText(parsedBody);
180
206
  }
181
207
  }
182
- }
183
- else if (typeof parsedBody === 'string') {
184
- try {
185
- parsedBody = JSON.parse(parsedBody);
208
+ else if (isNode && contentLength !== 0) {
209
+ parsedBody = await new Promise((resolve) => {
210
+ let bodyStr = "";
211
+ const timer = setTimeout(() => resolve({}), 200);
212
+ rawReq.on("data", (chunk) => { bodyStr += chunk; });
213
+ rawReq.on("end", () => { clearTimeout(timer); resolve(parseText(bodyStr)); });
214
+ rawReq.on("error", () => { clearTimeout(timer); resolve({}); });
215
+ });
216
+ }
217
+ else if (typeof parsedBody === "string" && parsedBody.length > 0) {
218
+ parsedBody = parseText(parsedBody);
186
219
  }
187
- catch { }
188
220
  }
189
- if (parsedBody && typeof parsedBody === 'object' && parsedBody.constructor?.name === 'FormData') {
221
+ if (parsedBody && typeof parsedBody === "object" && parsedBody.constructor?.name === "FormData") {
190
222
  parsedBody = Object.fromEntries(parsedBody.entries());
191
223
  }
192
224
  if (store) {
193
225
  if (!store.metadata)
194
226
  store.metadata = { locals: {} };
195
227
  store.metadata._parsedBody = parsedBody || {};
196
- store.metadata._parsedQuery = this.req.query;
228
+ store.metadata._parsedQuery = currentReqBase.query;
197
229
  }
198
230
  const currentReq = this.req;
199
231
  const p = {
200
232
  req: currentReq,
201
233
  res: currentRes,
202
234
  body: currentReq.body,
235
+ query: currentReq.query,
203
236
  queries: currentReq.query,
204
237
  params: currentReq.params,
238
+ cookies: currentReq.cookies,
239
+ locals: currentRes.locals,
205
240
  t: currentReq.t,
206
241
  };
207
242
  const extractMeta = (obj) => {
208
- if (!obj || typeof obj !== 'object')
243
+ if (!obj || typeof obj !== "object")
209
244
  return;
210
245
  if (obj.code !== undefined && store?.metadata)
211
246
  store.metadata._code = obj.code;
@@ -229,9 +264,9 @@ export default class Controller extends BaseController {
229
264
  }
230
265
  let logicResult = logic ? await logic(p) : {};
231
266
  extractMeta(logicResult);
232
- if (logicResult instanceof Response || logicResult?.constructor?.name === 'Response')
267
+ if (logicResult instanceof Response || logicResult?.constructor?.name === "Response")
233
268
  return logicResult;
234
- if (logicResult && typeof logicResult === 'object' && typeof logicResult.response === 'function') {
269
+ if (logicResult && typeof logicResult === "object" && typeof logicResult.response === "function") {
235
270
  logicResult = await logicResult.response();
236
271
  extractMeta(logicResult);
237
272
  }
@@ -245,17 +280,17 @@ export default class Controller extends BaseController {
245
280
  return currentRes.status(200).send(resResult);
246
281
  let finalData = undefined;
247
282
  let finalExtraData = undefined;
248
- let messageSource = 'success_process';
283
+ let messageSource = "success_process";
249
284
  const extractData = (obj) => {
250
- if (!obj || typeof obj !== 'object')
285
+ if (!obj || typeof obj !== "object")
251
286
  return;
252
- if (obj.payload && typeof obj.payload === 'object') {
287
+ if (obj.payload && typeof obj.payload === "object") {
253
288
  finalData = obj.payload.data !== undefined ? obj.payload.data : obj.payload;
254
289
  finalExtraData = obj.payload.extraData;
255
290
  }
256
291
  else if (obj.data !== undefined || obj.extraData !== undefined) {
257
292
  finalData = obj.data;
258
- finalExtraData = obj.extraData;
293
+ finalExtraData = obj.extraAddr;
259
294
  }
260
295
  else {
261
296
  const hasMeta = obj.status !== undefined || obj.message !== undefined || obj.code !== undefined || obj.statusCode !== undefined;
@@ -265,7 +300,7 @@ export default class Controller extends BaseController {
265
300
  if (obj.message)
266
301
  messageSource = obj.message;
267
302
  };
268
- if (typeof resResult === 'string') {
303
+ if (typeof resResult === "string") {
269
304
  messageSource = resResult;
270
305
  extractData(logicResult);
271
306
  }
@@ -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
  };