@entity-access/server-pages 1.0.43 → 1.1.3

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.
Files changed (60) hide show
  1. package/dist/Page.d.ts +6 -13
  2. package/dist/Page.d.ts.map +1 -1
  3. package/dist/Page.js +10 -44
  4. package/dist/Page.js.map +1 -1
  5. package/dist/ServerPages.d.ts.map +1 -1
  6. package/dist/ServerPages.js +14 -2
  7. package/dist/ServerPages.js.map +1 -1
  8. package/dist/core/CacheProperty.d.ts +5 -0
  9. package/dist/core/CacheProperty.d.ts.map +1 -0
  10. package/dist/core/CacheProperty.js +19 -0
  11. package/dist/core/CacheProperty.js.map +1 -0
  12. package/dist/core/Executor.d.ts +5 -0
  13. package/dist/core/Executor.d.ts.map +1 -0
  14. package/dist/core/Executor.js +13 -0
  15. package/dist/core/Executor.js.map +1 -0
  16. package/dist/core/RouteTree.d.ts.map +1 -1
  17. package/dist/core/RouteTree.js +15 -6
  18. package/dist/core/RouteTree.js.map +1 -1
  19. package/dist/core/SessionUser.d.ts +7 -5
  20. package/dist/core/SessionUser.d.ts.map +1 -1
  21. package/dist/core/SessionUser.js +36 -0
  22. package/dist/core/SessionUser.js.map +1 -1
  23. package/dist/core/Wrapped.d.ts +14 -16
  24. package/dist/core/Wrapped.d.ts.map +1 -1
  25. package/dist/core/Wrapped.js +157 -234
  26. package/dist/core/Wrapped.js.map +1 -1
  27. package/dist/decorators/Prepare.d.ts +14 -0
  28. package/dist/decorators/Prepare.d.ts.map +1 -0
  29. package/dist/decorators/Prepare.js +111 -0
  30. package/dist/decorators/Prepare.js.map +1 -0
  31. package/dist/routes/api/entity/index.d.ts.map +1 -1
  32. package/dist/routes/api/entity/index.js +9 -3
  33. package/dist/routes/api/entity/index.js.map +1 -1
  34. package/dist/routes/api/entity/model/get.d.ts.map +1 -1
  35. package/dist/routes/api/entity/model/get.js.map +1 -1
  36. package/dist/routes/api/entity/query/get.d.ts.map +1 -1
  37. package/dist/routes/api/entity/query/get.js +9 -3
  38. package/dist/routes/api/entity/query/get.js.map +1 -1
  39. package/dist/services/CookieService.d.ts.map +1 -1
  40. package/dist/services/CookieService.js +8 -0
  41. package/dist/services/CookieService.js.map +1 -1
  42. package/dist/tsconfig.tsbuildinfo +1 -1
  43. package/package.json +1 -1
  44. package/src/Page.tsx +11 -53
  45. package/src/ServerPages.ts +14 -2
  46. package/src/core/CacheProperty.ts +18 -0
  47. package/src/core/Executor.ts +16 -0
  48. package/src/core/RouteTree.ts +7 -2
  49. package/src/core/SessionUser.ts +21 -5
  50. package/src/core/Wrapped.ts +215 -269
  51. package/src/decorators/Prepare.ts +132 -0
  52. package/src/routes/api/entity/index.tsx +4 -1
  53. package/src/routes/api/entity/model/get.tsx +1 -1
  54. package/src/routes/api/entity/query/get.ts +4 -1
  55. package/src/services/CookieService.ts +9 -1
  56. package/dist/decorators/Authorize.d.ts +0 -2
  57. package/dist/decorators/Authorize.d.ts.map +0 -1
  58. package/dist/decorators/Authorize.js +0 -3
  59. package/dist/decorators/Authorize.js.map +0 -1
  60. package/src/decorators/Authorize.ts +0 -3
@@ -10,28 +10,39 @@ import { ServiceProvider } from "@entity-access/entity-access/dist/di/di.js";
10
10
  import CookieService from "../services/CookieService.js";
11
11
  import { stat } from "fs/promises";
12
12
  import TokenService from "../services/TokenService.js";
13
+ import { CacheProperty } from "./CacheProperty.js";
13
14
 
14
15
 
15
16
  type UnwrappedRequest = IncomingMessage | Http2ServerRequest;
16
17
 
18
+ type UnwrappedResponse = ServerResponse | Http2ServerResponse;
19
+
17
20
  export interface IFormData {
18
21
  fields: { [key: string]: string};
19
22
  files: LocalFile[];
20
23
  }
21
24
 
25
+ const extendedSymbol = Symbol("extended");
26
+
22
27
  export interface IWrappedRequest {
23
28
 
29
+ headers?: any;
30
+
31
+ disposables?: Disposable[];
32
+
33
+ response?: WrappedResponse;
34
+
24
35
  get host(): string;
25
36
 
26
37
  get path(): string;
27
38
 
28
- get asyncSessionUser(): Promise<SessionUser>;
39
+ get sessionUser(): SessionUser;
29
40
 
30
- get asyncJsonBody(): Promise<any>;
41
+ get body(): any;
31
42
 
32
- get asyncForm(): Promise<IFormData>;
43
+ get form(): IFormData;
33
44
 
34
- get asyncParams(): Promise<any>;
45
+ get params(): any;
35
46
 
36
47
  get query(): { [key: string]: string};
37
48
 
@@ -45,12 +56,17 @@ export interface IWrappedRequest {
45
56
  accepts(... types: string[]): boolean;
46
57
  }
47
58
 
59
+
48
60
  export interface IWrappedResponse {
49
61
 
62
+ request?: WrappedRequest;
63
+
50
64
  asyncEnd();
51
65
 
52
66
  asyncWrite(buffer: Buffer): Promise<void>;
53
67
 
68
+ setHeader(name: string, value: string);
69
+
54
70
  send(data: Buffer | string | Blob, status?: number): Promise<void>;
55
71
 
56
72
  sendRedirect(url: string, permanent?: boolean): void;
@@ -70,305 +86,235 @@ export interface IWrappedResponse {
70
86
 
71
87
  }
72
88
 
73
- export type WrappedRequest = UnwrappedRequest & IWrappedRequest & {
74
- scope: ServiceProvider;
75
- response: WrappedResponse;
76
- disposables: Disposable[];
77
- };
89
+ export type WrappedRequest = IWrappedRequest & UnwrappedRequest;
78
90
 
79
- type UnwrappedResponse = ServerResponse | Http2ServerResponse;
91
+ export type WrappedResponse = IWrappedResponse & UnwrappedResponse;
80
92
 
81
- export type WrappedResponse = UnwrappedResponse & IWrappedResponse & {
82
- request: WrappedRequest
83
- };
93
+ const extendRequest = (A: typeof IncomingMessage | typeof Http2ServerRequest) => {
84
94
 
85
- const requestMethods: { [P in keyof IWrappedRequest]: (this: WrappedRequest) => any} = {
86
- remoteIPAddress(this: UnwrappedRequest) {
87
- return this.socket?.remoteAddress;
88
- },
95
+ let c = A[extendedSymbol];
96
+ if (!c) {
97
+ c = class IntermediateRequest extends A implements IWrappedRequest{
89
98
 
90
- accepts(this: UnwrappedRequest) {
91
- const accepts = (this.headers.accept ?? "").split(";");
92
- return (...types: string[]) => {
93
- if (types.length > 0) {
94
- for (const type of types) {
95
- for (const iterator of accepts) {
96
- if (iterator.includes(type)) {
97
- return true;
98
- }
99
- }
100
- }
101
- return false;
102
- }
103
- return accepts;
104
- };
105
- },
99
+ scope: ServiceProvider;
100
+ disposables: Disposable[];
106
101
 
107
- URL(this: UnwrappedRequest) {
108
- const w = this as WrappedRequest;
109
- return new URL(this.url, `http://${w.host}`);
110
- },
111
-
112
- path(this: WrappedRequest) {
113
- return this.URL.pathname;
114
- },
115
-
116
- host(this: UnwrappedRequest) {
117
- return this.headers[":authority"] ?? this.headers["host"];
118
- },
119
- query(this: WrappedRequest) {
120
- const u = this.URL;
121
- const items = {};
122
- for (const [key, value] of u.searchParams.entries()) {
123
- items[key] = value;
124
- }
125
- return items;
126
- },
127
- cookies(this: UnwrappedRequest) {
128
- const cookie = this.headers.cookie;
129
- const cookies = parse(cookie);
130
- return cookies;
131
- },
102
+ get host(): string {
103
+ const r = this as any as (Http2ServerRequest | IncomingMessage);
104
+ const host = (r as Http2ServerRequest).authority || r.headers[":authority"] || r.headers.host || null;
105
+ return CacheProperty.value(this, "host", host);
106
+ }
107
+ get path(): string {
108
+ return this.URL.pathname;
109
+ }
110
+ get cookies(): { [key: string]: string; } {
111
+ const cookie = (this as any as UnwrappedRequest).headers.cookie;
112
+ const cookies = parse(cookie);
113
+ return CacheProperty.value(this, "cookies", cookies);
114
+ }
115
+ get URL(): URL {
116
+ const r = this as any as (Http2ServerRequest | IncomingMessage);
117
+ const url = new URL(r.url, `https:${this.host}`);
118
+ return CacheProperty.value(this, "URL", url);
119
+ }
120
+ get remoteIPAddress(): string {
121
+ const r = this as any as (Http2ServerRequest | IncomingMessage);
122
+ return CacheProperty.value(this, "remoteIPAddress", r.socket.remoteAddress);
123
+ }
132
124
 
133
- async asyncParams(this: WrappedRequest) {
134
- return {
135
- ... this.query,
136
- ... this.asyncJsonBody,
137
- ... this.asyncForm
138
- };
139
- },
125
+ accepts(... types: string[]): any {
126
+ const h = this as any as IncomingMessage;
127
+ const accepts = (h.headers.accept ?? "").split(";");
128
+ const value = (...types: string[]) => {
129
+ if (types.length > 0) {
130
+ for (const type of types) {
131
+ for (const iterator of accepts) {
132
+ if (iterator.includes(type)) {
133
+ return true;
134
+ }
135
+ }
136
+ }
137
+ return false;
138
+ }
139
+ return accepts;
140
+ };
140
141
 
141
- async asyncJsonBody(this: WrappedRequest) {
142
- const req = this;
143
- let buffer = null as Buffer;
144
- let encoding = this.headers["content-encoding"] ?? "utf-8";
145
- const contentType = this.headers["content-type"];
146
- if (!/\/json/i.test(contentType)) {
147
- return {};
148
- }
149
- await new Promise<void>((resolve, reject) => {
150
- req.pipe(new Writable({
151
- write(chunk, enc, callback) {
152
- encoding ||= enc;
153
- let b = typeof chunk === "string"
154
- ? Buffer.from(chunk)
155
- : chunk as Buffer;
156
- buffer = buffer
157
- ? Buffer.concat([buffer, b])
158
- : b;
159
- callback();
160
- },
161
- final(callback) {
162
- resolve();
163
- callback();
164
- },
165
- }), { end: true });
166
- });
167
- const text = buffer.toString(encoding as any);
168
- return JSON.parse(text);
169
- },
142
+ Object.defineProperty(this, "accepts", {
143
+ value,
144
+ enumerable: true,
145
+ configurable: true
146
+ });
170
147
 
171
- async asyncSessionUser(this: WrappedRequest) {
172
- try {
173
- const cookieService = this.scope.resolve(CookieService);
174
- const tokenService = this.scope.resolve(TokenService);
175
- const cookie = this.cookies[tokenService.authCookieName];
176
- const sessionUser = await cookieService.createSessionUserFromCookie(cookie, this.remoteIPAddress);
177
- sessionUser.resp = this.response;
178
- return sessionUser;
179
- } catch (error) {
180
- console.error(error);
181
- const su = this.scope.resolve(SessionUser);
182
- su.resp = this.response;
183
- return su;
148
+ return value( ... types);
149
+ }
150
+
151
+ get query(): any {
152
+ throw new Error("Please decorate `Ensure.parseQuery` callee or call `await Ensure.parseQuery(this)` before accessing this member");
153
+ }
154
+
155
+ get body(): any {
156
+ throw new Error("Please decorate `Ensure.parseBody` callee or call `await Ensure.parseBody(this)` before accessing this member");
157
+ }
158
+
159
+ get form(): any {
160
+ throw new Error("Please decorate `Ensure.parseForm` callee or call `await Ensure.parseForm(this)` before accessing this member");
161
+ }
162
+
163
+ get params(): any {
164
+ throw new Error("Please decorate `Ensure.parseAll` callee or call `await Ensure.parseAll(this)` before accessing this member");
165
+ }
166
+
167
+ get sessionUser(): any {
168
+ throw new Error("Please decorate `Ensure.authorize` callee or call `await Ensure.authorize(this)` before accessing this member");
169
+ }
170
+
184
171
  }
185
- },
186
-
187
- async asyncForm(this: WrappedRequest) {
188
- let tempFolder: TempFolder;
189
- const result: IFormData = {
190
- fields: {},
191
- files: []
192
- };
193
- const req = this;
194
- const bb = busboy({ headers: req.headers, defParamCharset: "utf8" });
195
- const tasks = [];
196
- await new Promise((resolve, reject) => {
197
-
198
- bb.on("field", (name, value) => {
199
- result.fields[name] = value;
200
- });
201
-
202
- bb.on("file", (name, file, info) => {
203
- if (!tempFolder) {
204
- tempFolder = new TempFolder();
205
- this.disposables.push(tempFolder);
206
- }
207
- const tf = tempFolder.get(info.filename, info.mimeType);
208
- tasks.push(tf.writeAll(file).then(() => {
209
- result.files.push(tf);
210
- }));
211
- });
212
- bb.on("error", reject);
213
- bb.on("close", resolve);
214
- req.pipe(bb);
215
- });
216
- await Promise.all(tasks);
217
- return result;
172
+ A[extendedSymbol] = c;
218
173
  }
174
+ return c;
219
175
  };
220
176
 
221
- const responseMethods: { [P in keyof IWrappedResponse]: (this: WrappedResponse) => any} = {
177
+ const extendResponse = (A: typeof ServerResponse | typeof Http2ServerResponse) => {
178
+ let c = A[extendedSymbol];
179
+ if (!c) {
180
+ c = class WrappedResponse extends A implements IWrappedResponse {
222
181
 
223
- asyncEnd() {
224
- return () => new Promise<void>((resolve) => this.end(resolve));
225
- },
226
-
227
- asyncWrite() {
228
- return (buffer: Buffer, start?: number, length?: number) => {
229
- return new Promise((resolve) =>
230
- this.write(buffer, resolve)
231
- );
232
- };
233
- },
182
+ statusCode: number;
234
183
 
235
- cookie() {
236
- return (name: string, value: string, options = {}) => {
237
- const cv = this.getHeaders()["set-cookie"];
238
- const cookies = Array.isArray(cv) ? cv : [cv];
239
- const nk = cookies.filter((x) => !x.startsWith(name + "="));
240
- nk.push(serialize(name, value, options));
241
- this.setHeader("set-cookie", nk);
242
- }
243
- },
244
-
245
- send(this: WrappedResponse) {
246
- return async (data: Buffer | string, status: number = 200) => {
247
- try {
248
- this.statusCode = status;
249
- this.writeHead(this.statusCode, this.getHeaders());
250
- await new Promise<void>((resolve, reject) => {
251
- this.write(data, (error) => error ? reject(error) : resolve());
252
- });
253
- return this.asyncEnd();
254
- } catch (error) {
255
- console.error(error);
184
+ asyncEnd(this: UnwrappedResponse) {
185
+ return new Promise<void>((resolve) => this.end(resolve));
256
186
  }
257
- };
258
- },
259
- sendRedirect() {
260
- return (location: string, permanent = false) => {
261
- this.statusCode = 301;
262
- this.writeHead(this.statusCode, {
263
- location
264
- });
265
- return this.asyncEnd();
266
- }
267
- },
268
- sendFile() {
269
- return async (filePath: string, options?: {
270
- acceptRanges?: boolean,
271
- cacheControl?: boolean,
272
- maxAge?: number,
273
- etag?: boolean,
274
- immutable?: boolean,
275
- headers?: { [key: string]: string},
276
- lastModified?: boolean
277
- }) => {
278
- /** Calculate Size of file */
279
- const { size } = await stat(filePath);
280
- const range = this.request.headers.range;
281
-
282
- const lf = new LocalFile(filePath);
283
-
284
- /** Check for Range header */
285
- if (!range) {
286
- this.writeHead(200, {
287
- "Content-Length": size,
288
- "Content-Type": "video/mp4"
289
- });
290
-
291
- await lf.writeTo(this);
292
-
293
- return this.asyncEnd();
187
+
188
+ asyncWrite(this: UnwrappedResponse, buffer: Buffer, start?: number, length?: number) {
189
+ return new Promise<void>((resolve, reject) =>
190
+ this.write(buffer, (error) => error ? reject(error) : resolve())
191
+ );
192
+ }
193
+
194
+ cookie(this: UnwrappedResponse, name: string, value: string, options = {}) {
195
+ const headers = this.getHeaders();
196
+ const cv = headers["set-cookie"];
197
+ const cookies = Array.isArray(cv)
198
+ ? cv
199
+ : (cv ? [cv] : []);
200
+ const nk = cookies.filter((x) => !x.startsWith(name + "="));
201
+ nk.push(serialize(name, value, options));
202
+ headers["set-cookie"] = nk;
294
203
  }
295
204
 
296
- /** Extracting Start and End value from Range Header */
297
- let [start, end] = range.replace(/bytes=/, "").split("-") as any[];
298
- start = parseInt(start, 10);
299
- end = end ? parseInt(end, 10) : size - 1;
300
-
301
- if (!isNaN(start) && isNaN(end)) {
302
- start = start;
303
- end = size - 1;
205
+ setHeader(this: UnwrappedResponse, name: string, value: string) {
206
+ const headers = this.getHeaders();
207
+ headers[name] = value;
304
208
  }
305
- if (isNaN(start) && !isNaN(end)) {
306
- start = size - end;
307
- end = size - 1;
209
+
210
+ async send(this: UnwrappedResponse, data: Buffer | string, status: number = 200) {
211
+ try {
212
+ const wrapped = (this as any as WrappedResponse);
213
+ wrapped.statusCode = status;
214
+ this.writeHead(wrapped.statusCode, this.getHeaders());
215
+ await new Promise<void>((resolve, reject) => {
216
+ this.write(data, (error) => error ? reject(error) : resolve());
217
+ });
218
+ return (this as any).asyncEnd();
219
+ } catch (error) {
220
+ console.error(error);
221
+ }
308
222
  }
309
223
 
310
- // Handle unavailable range request
311
- if (start >= size || end >= size) {
312
- // Return the 416 Range Not Satisfiable.
313
- this.writeHead(416, {
314
- "Content-Range": `bytes */${size}`
224
+ async sendRedirect(this: UnwrappedResponse, location: string, permanent = false) {
225
+ this.statusCode = 301;
226
+ this.writeHead(this.statusCode, {
227
+ location
315
228
  });
316
- return this.asyncEnd();
229
+ return (this as any as IWrappedResponse).asyncEnd();
317
230
  }
318
231
 
319
- /** Sending Partial Content With HTTP Code 206 */
320
- this.writeHead(206, {
321
- "Content-Range": `bytes ${start}-${end}/${size}`,
322
- "Accept-Ranges": "bytes",
323
- "Content-Length": end - start + 1,
324
- "Content-Type": "video/mp4"
325
- });
326
-
327
- await lf.writeTo(this, start, end);
328
-
232
+ async sendFile(this: UnwrappedResponse, filePath: string, options?: {
233
+ acceptRanges?: boolean,
234
+ cacheControl?: boolean,
235
+ maxAge?: number,
236
+ etag?: boolean,
237
+ immutable?: boolean,
238
+ headers?: { [key: string]: string},
239
+ lastModified?: boolean
240
+ }) {
241
+ /** Calculate Size of file */
242
+ const { size } = await stat(filePath);
243
+ const range = (this as any as IWrappedResponse).request.headers.range;
244
+
245
+ const lf = new LocalFile(filePath);
246
+
247
+ /** Check for Range header */
248
+ if (!range) {
249
+ this.writeHead(200, {
250
+ "Content-Length": size,
251
+ "Content-Type": "video/mp4"
252
+ });
253
+
254
+ await lf.writeTo(this);
255
+
256
+ return (this as any).asyncEnd();
257
+ }
258
+
259
+ /** Extracting Start and End value from Range Header */
260
+ let [start, end] = range.replace(/bytes=/, "").split("-") as any[];
261
+ start = parseInt(start, 10);
262
+ end = end ? parseInt(end, 10) : size - 1;
263
+
264
+ if (!isNaN(start) && isNaN(end)) {
265
+ start = start;
266
+ end = size - 1;
267
+ }
268
+ if (isNaN(start) && !isNaN(end)) {
269
+ start = size - end;
270
+ end = size - 1;
271
+ }
272
+
273
+ // Handle unavailable range request
274
+ if (start >= size || end >= size) {
275
+ // Return the 416 Range Not Satisfiable.
276
+ this.writeHead(416, {
277
+ "Content-Range": `bytes */${size}`
278
+ });
279
+ return (this as any).asyncEnd();
280
+ }
281
+
282
+ /** Sending Partial Content With HTTP Code 206 */
283
+ this.writeHead(206, {
284
+ "Content-Range": `bytes ${start}-${end}/${size}`,
285
+ "Accept-Ranges": "bytes",
286
+ "Content-Length": end - start + 1,
287
+ "Content-Type": "video/mp4"
288
+ });
289
+
290
+ await lf.writeTo(this, start, end);
291
+
292
+ }
329
293
  }
330
- },
331
- };
294
+ }
295
+ return c;
296
+ }
297
+
332
298
 
333
299
  export const Wrapped = {
334
300
  request: (req: UnwrappedRequest) => {
335
- for (const key in requestMethods) {
336
- if (Object.prototype.hasOwnProperty.call(requestMethods, key)) {
337
- const element = requestMethods[key];
338
- Object.defineProperty(req, key, {
339
- get() {
340
- const value = element.call(this);
341
- Object.defineProperty(this, key, { value, enumerable: true, writable: false });
342
- return value;
343
- },
344
- enumerable: true,
345
- configurable: true
346
- });
347
- }
348
- }
301
+ let prototype = Object.getPrototypeOf(req);
302
+ const { constructor } = prototype;
303
+ prototype = extendRequest(constructor);
304
+ Object.setPrototypeOf(req, prototype);
349
305
  const wr = req as WrappedRequest;
350
306
  wr.disposables = [];
351
- return wr;
307
+ return req;
352
308
  },
353
309
 
354
310
  response: (req: WrappedRequest, res: UnwrappedResponse) => {
355
- for (const key in responseMethods) {
356
- if (Object.prototype.hasOwnProperty.call(responseMethods, key)) {
357
- const element = responseMethods[key];
358
- Object.defineProperty(res, key, {
359
- get() {
360
- const value = element.call(this);
361
- Object.defineProperty(this, key, { value, enumerable: true, writable: false });
362
- return value;
363
- },
364
- enumerable: true,
365
- configurable: true
366
- });
367
- }
368
- }
311
+ let prototype = Object.getPrototypeOf(res);
312
+ const { constructor } = prototype;
313
+ prototype = extendResponse(constructor);
314
+ Object.setPrototypeOf(res, prototype);
369
315
  const wr = res as WrappedResponse;
370
316
  wr.request = req;
371
317
  req.response = wr;
372
- return wr;
318
+ return res;
373
319
  }
374
320
  }