@entity-access/server-pages 1.1.348 → 1.1.350

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 (83) hide show
  1. package/dist/Content.d.ts +17 -31
  2. package/dist/Content.d.ts.map +1 -1
  3. package/dist/Content.js +117 -97
  4. package/dist/Content.js.map +1 -1
  5. package/dist/Page.d.ts +2 -4
  6. package/dist/Page.d.ts.map +1 -1
  7. package/dist/Page.js +32 -24
  8. package/dist/Page.js.map +1 -1
  9. package/dist/ServerPages.d.ts.map +1 -1
  10. package/dist/ServerPages.js +9 -3
  11. package/dist/ServerPages.js.map +1 -1
  12. package/dist/core/Compression.d.ts +5 -2
  13. package/dist/core/Compression.d.ts.map +1 -1
  14. package/dist/core/Compression.js +11 -3
  15. package/dist/core/Compression.js.map +1 -1
  16. package/dist/core/Executor.d.ts +1 -1
  17. package/dist/core/RouteTree.js +1 -2
  18. package/dist/core/RouteTree.js.map +1 -1
  19. package/dist/core/Utf8Readable.d.ts +6 -0
  20. package/dist/core/Utf8Readable.d.ts.map +1 -0
  21. package/dist/core/Utf8Readable.js +12 -0
  22. package/dist/core/Utf8Readable.js.map +1 -0
  23. package/dist/core/Wrapped.d.ts +2 -4
  24. package/dist/core/Wrapped.d.ts.map +1 -1
  25. package/dist/core/Wrapped.js +115 -108
  26. package/dist/core/Wrapped.js.map +1 -1
  27. package/dist/html/HtmlComment.d.ts +1 -0
  28. package/dist/html/HtmlComment.d.ts.map +1 -1
  29. package/dist/html/HtmlComment.js +9 -0
  30. package/dist/html/HtmlComment.js.map +1 -1
  31. package/dist/html/HtmlDocument.d.ts.map +1 -1
  32. package/dist/html/HtmlDocument.js +4 -0
  33. package/dist/html/HtmlDocument.js.map +1 -1
  34. package/dist/html/XNode.d.ts +1 -0
  35. package/dist/html/XNode.d.ts.map +1 -1
  36. package/dist/html/XNode.js +38 -0
  37. package/dist/html/XNode.js.map +1 -1
  38. package/dist/routes/api/entity/expand/[entity]/[property]/[keys]/get.js +2 -2
  39. package/dist/routes/api/entity/expand/[entity]/[property]/[keys]/get.js.map +1 -1
  40. package/dist/routes/api/entity/index.d.ts +1 -1
  41. package/dist/routes/api/entity/invoke/[entityName]/[methodName]/post.d.ts +2 -2
  42. package/dist/routes/api/entity/invoke/[entityName]/[methodName]/post.d.ts.map +1 -1
  43. package/dist/routes/api/entity/invoke/[entityName]/[methodName]/post.js +9 -3
  44. package/dist/routes/api/entity/invoke/[entityName]/[methodName]/post.js.map +1 -1
  45. package/dist/routes/api/entity/model.ts/get.d.ts +2 -1
  46. package/dist/routes/api/entity/model.ts/get.d.ts.map +1 -1
  47. package/dist/routes/api/entity/model.ts/get.js +6 -1
  48. package/dist/routes/api/entity/model.ts/get.js.map +1 -1
  49. package/dist/routes/api/entity/query/[entity]/get.js +2 -2
  50. package/dist/routes/api/entity/query/[entity]/get.js.map +1 -1
  51. package/dist/routes/api/entity/run/[entity]/[methodName]/index.d.ts +2 -2
  52. package/dist/routes/api/entity/run/[entity]/[methodName]/index.d.ts.map +1 -1
  53. package/dist/routes/api/entity/run/[entity]/[methodName]/index.js +7 -3
  54. package/dist/routes/api/entity/run/[entity]/[methodName]/index.js.map +1 -1
  55. package/dist/services/DbJsonService.d.ts +5 -5
  56. package/dist/services/DbJsonService.d.ts.map +1 -1
  57. package/dist/services/DbJsonService.js +13 -7
  58. package/dist/services/DbJsonService.js.map +1 -1
  59. package/dist/services/EntityAccessServer.d.ts +1 -2
  60. package/dist/services/EntityAccessServer.d.ts.map +1 -1
  61. package/dist/services/EntityAccessServer.js +1 -8
  62. package/dist/services/EntityAccessServer.js.map +1 -1
  63. package/dist/tests/logger/index.js +1 -1
  64. package/dist/tsconfig.tsbuildinfo +1 -1
  65. package/package.json +2 -2
  66. package/src/Content.tsx +149 -120
  67. package/src/Page.tsx +34 -24
  68. package/src/ServerPages.ts +15 -6
  69. package/src/core/Compression.ts +14 -3
  70. package/src/core/RouteTree.ts +1 -1
  71. package/src/core/Utf8Readable.ts +15 -0
  72. package/src/core/Wrapped.ts +121 -111
  73. package/src/html/HtmlComment.ts +10 -0
  74. package/src/html/HtmlDocument.tsx +5 -0
  75. package/src/html/XNode.ts +58 -0
  76. package/src/routes/api/entity/expand/[entity]/[property]/[keys]/get.ts +2 -2
  77. package/src/routes/api/entity/invoke/[entityName]/[methodName]/post.tsx +9 -3
  78. package/src/routes/api/entity/model.ts/get.tsx +7 -1
  79. package/src/routes/api/entity/query/[entity]/get.ts +2 -2
  80. package/src/routes/api/entity/run/[entity]/[methodName]/index.ts +7 -5
  81. package/src/services/DbJsonService.ts +13 -8
  82. package/src/services/EntityAccessServer.ts +1 -10
  83. package/src/tests/logger/index.tsx +1 -1
package/src/Content.tsx CHANGED
@@ -6,43 +6,71 @@ import { LocalFile } from "./core/LocalFile.js";
6
6
  import { SessionUser } from "./core/SessionUser.js";
7
7
  import { WrappedResponse } from "./core/Wrapped.js";
8
8
  import { OutgoingHttpHeaders } from "http";
9
- import JsonReadable from "@entity-access/entity-access/dist/common/JsonReadable.js";
9
+ import { Readable } from "stream";
10
+ import Utf8Readable from "./core/Utf8Readable.js";
10
11
 
11
- export abstract class PageResult {
12
- abstract send(res: WrappedResponse, user?: SessionUser): Promise<any>;
13
- }
12
+ const EmptyReader = Readable.from([]);
13
+
14
+ export default class Content {
15
+
16
+ public readonly reader: Readable;
17
+ public readonly status: number = 200;
18
+ public readonly headers: OutgoingHttpHeaders;
19
+
20
+ public suppressLog: boolean;
14
21
 
15
- export class JsonReaderResult extends PageResult {
16
22
  constructor(
17
- public readonly reader: JsonReadable,
18
- public readonly status = 200,
19
- public readonly headers?: OutgoingHttpHeaders
23
+ p: Partial<Content>
20
24
  ) {
21
- super();
25
+ Object.setPrototypeOf(p, Content.prototype);
26
+ return p as Content;
22
27
  }
23
28
 
24
29
  send(res: WrappedResponse, user?: SessionUser): Promise<any> {
25
- return res.asyncPipe(this.status, this.headers, this.reader);
30
+ return res.sendReader(this.status, this.headers, this.reader);
26
31
  }
27
32
 
28
- }
33
+ static readable(readable: Readable, { status = 200, headers = void 0 as OutgoingHttpHeaders }) {
34
+ return new Content({
35
+ reader: readable,
36
+ status,
37
+ headers
38
+ });
39
+ }
29
40
 
30
- export class StatusResult extends PageResult {
31
41
 
32
- constructor(
33
- public readonly status,
34
- public readonly headers?: OutgoingHttpHeaders
35
- ) {
36
- super();
37
- }
42
+ static text(
43
+ text: string | Iterable<string> | XNode,
44
+ {
45
+ status = 200,
46
+ headers = void 0 as OutgoingHttpHeaders,
47
+ suppressLog = false
48
+ } = {
49
+ }) {
50
+
51
+ let reader: Readable;
52
+
53
+ headers ??= {};
54
+ headers["content-type"] ??= "text/html; charset=utf-8"
55
+
56
+ if (typeof text === "string") {
57
+ reader = Readable.from([ Buffer.from(text, "utf8") ]);
58
+ } else if (text instanceof XNode) {
59
+ reader = Utf8Readable.from(text.readable());
60
+ } else {
61
+ reader = Utf8Readable.from(text);
62
+ }
38
63
 
39
- send(res: WrappedResponse, user?: SessionUser): Promise<any> {
40
- return res.sendStatus(this.status, this.headers);
64
+ return new Content({
65
+ reader,
66
+ status,
67
+ headers,
68
+ suppressLog
69
+ });
41
70
  }
42
-
43
71
  }
44
72
 
45
- export class FileResult extends PageResult {
73
+ export class FileResult extends Content {
46
74
 
47
75
  public contentDisposition: "inline" | "attachment" = "inline";
48
76
  public cacheControl = true;
@@ -64,7 +92,7 @@ export class FileResult extends PageResult {
64
92
  headers
65
93
  }: Partial<FileResult> = {}
66
94
  ) {
67
- super();
95
+ super({});
68
96
  this.contentDisposition = contentDisposition;
69
97
  this.cacheControl = cacheControl;
70
98
  this.maxAge = maxAge;
@@ -103,109 +131,110 @@ export class TempFileResult extends FileResult {
103
131
 
104
132
 
105
133
 
106
- export class Redirect extends PageResult {
134
+ export class Redirect extends Content {
107
135
 
108
- constructor(public location: string, public status = 301, public headers = void 0 as OutgoingHttpHeaders) {
109
- super();
136
+ constructor(public location: string, status = 301, headers = void 0 as OutgoingHttpHeaders) {
137
+ super({ status, headers });
110
138
  }
111
139
 
140
+
112
141
  async send(res: WrappedResponse) {
113
142
  return res.sendRedirect(this.location, this.status, this.headers);
114
143
  }
115
144
 
116
145
  }
117
146
 
118
- export default class Content extends PageResult {
119
-
120
- public static json(json: any, status = 200) {
121
- return new Content({
122
- body: JSON.stringify(json),
123
- contentType: "application/json",
124
- status,
125
- compress: "gzip"
126
- });
127
- }
128
-
129
- public static html(html, status = 200) {
130
- return new Content({
131
- body: html,
132
- contentType: "text/html",
133
- status,
134
- compress: "gzip"
135
- });
136
- }
137
-
138
- public static create(
139
- body: Partial<Content>
140
- ) {
141
- return new Content(body);
142
- }
143
-
144
-
145
- public status: number;
146
-
147
- public contentType: string;
148
-
149
- public suppressLog: boolean;
150
-
151
- public body: string | Buffer | XNode;
152
-
153
- public headers: OutgoingHttpHeaders;
154
-
155
- public compress: "gzip" | "deflate" | null = null;
156
-
157
- private constructor(p: Partial<Content>) {
158
- super();
159
- Object.setPrototypeOf(p, Content.prototype);
160
- p.contentType ??= "text/plain";
161
- p.status ??= 200;
162
- if (p.body === void 0) {
163
- throw new Error(`Body cannot be undefined`);
164
- }
165
- return p as Content;
166
- }
167
-
168
- public async send(res: WrappedResponse, user?: SessionUser) {
169
- const { status, body, contentType } = this;
170
- const { headers } = this;
171
- if (headers) {
172
- for (const key in headers) {
173
- if (Object.hasOwn(headers, key)) {
174
- const element = headers[key];
175
- res.setHeader(key, element);
176
- }
177
- }
178
- }
179
-
180
- res.compress = this.compress;
181
-
182
- res.setHeader("content-type", contentType);
183
- res.statusCode = status;
184
- if (typeof body === "string") {
185
- if (status >= 300 && !this.suppressLog) {
186
- const u = user ? `User: ${user.userID},${user.userName}` : "User: Anonymous";
187
- console.error(`${res.req.method} ${res.req.url}\n${status}\n${u}\n${body}`);
188
- } else {
189
- res.compress ||= "gzip";
190
- }
191
- res.send(body);
192
- return;
193
- }
194
- if (body instanceof XNode) {
195
- const text = body.render();
196
- if (status >= 300 && !this.suppressLog) {
197
- console.error(`${res.req.method} ${res.req.url}\n${status}\n${text}`);
198
- } else {
199
- res.compress ||= "gzip";
200
- }
201
- res.send(text);
202
- return;
203
- }
204
- if (status >= 300 && !this.suppressLog) {
205
- console.error(`${res.req.method} ${res.req.url}\n${status}\nBINARY DATA`);
206
- }
207
- res.send(body);
208
- return;
209
- }
210
-
211
- }
147
+ // export default class Content extends PageResult {
148
+
149
+ // // public static json(json: any, status = 200) {
150
+ // // return new Content({
151
+ // // body: JSON.stringify(json),
152
+ // // contentType: "application/json",
153
+ // // status,
154
+ // // compress: "gzip"
155
+ // // });
156
+ // // }
157
+
158
+ // // public static html(html, status = 200) {
159
+ // // return new Content({
160
+ // // body: html,
161
+ // // contentType: "text/html",
162
+ // // status,
163
+ // // compress: "gzip"
164
+ // // });
165
+ // // }
166
+
167
+ // public static create(
168
+ // body: Partial<Content>
169
+ // ) {
170
+ // return new Content(body);
171
+ // }
172
+
173
+
174
+ // public status: number;
175
+
176
+ // public contentType: string;
177
+
178
+ // public suppressLog: boolean;
179
+
180
+ // public body: string | Buffer | XNode;
181
+
182
+ // public headers: OutgoingHttpHeaders;
183
+
184
+ // public compress: "gzip" | "deflate" | null = null;
185
+
186
+ // private constructor(p: Partial<Content>) {
187
+ // super();
188
+ // Object.setPrototypeOf(p, Content.prototype);
189
+ // p.contentType ??= "text/plain";
190
+ // p.status ??= 200;
191
+ // if (p.body === void 0) {
192
+ // throw new Error(`Body cannot be undefined`);
193
+ // }
194
+ // return p as Content;
195
+ // }
196
+
197
+ // public async send(res: WrappedResponse, user?: SessionUser) {
198
+ // const { status, body, contentType } = this;
199
+ // const { headers } = this;
200
+ // if (headers) {
201
+ // for (const key in headers) {
202
+ // if (Object.hasOwn(headers, key)) {
203
+ // const element = headers[key];
204
+ // res.setHeader(key, element);
205
+ // }
206
+ // }
207
+ // }
208
+
209
+ // res.compress = this.compress;
210
+
211
+ // res.setHeader("content-type", contentType);
212
+ // res.statusCode = status;
213
+ // if (typeof body === "string") {
214
+ // if (status >= 300 && !this.suppressLog) {
215
+ // const u = user ? `User: ${user.userID},${user.userName}` : "User: Anonymous";
216
+ // console.error(`${res.req.method} ${res.req.url}\n${status}\n${u}\n${body}`);
217
+ // } else {
218
+ // res.compress ||= "gzip";
219
+ // }
220
+ // res.sendReader(status, headers, Readable.from([ body ]));
221
+ // return;
222
+ // }
223
+ // if (body instanceof XNode) {
224
+ // const text = body.render();
225
+ // if (status >= 300 && !this.suppressLog) {
226
+ // console.error(`${res.req.method} ${res.req.url}\n${status}\n${text}`);
227
+ // } else {
228
+ // res.compress ||= "gzip";
229
+ // }
230
+ // res.send(text);
231
+ // return;
232
+ // }
233
+ // if (status >= 300 && !this.suppressLog) {
234
+ // console.error(`${res.req.method} ${res.req.url}\n${status}\nBINARY DATA`);
235
+ // }
236
+ // res.send(body);
237
+ // return;
238
+ // }
239
+
240
+ // }
package/src/Page.tsx CHANGED
@@ -1,12 +1,14 @@
1
1
  import busboy from "busboy";
2
2
  import HtmlDocument from "./html/HtmlDocument.js";
3
3
  import XNode from "./html/XNode.js";
4
- import Content, { PageResult, Redirect } from "./Content.js";
4
+ // import Content, { PageResult, Redirect } from "./Content.js";
5
5
  import { LocalFile } from "./core/LocalFile.js";
6
6
  import { WrappedRequest, WrappedResponse } from "./core/Wrapped.js";
7
7
  import { ServiceProvider } from "@entity-access/entity-access/dist/di/di.js";
8
8
  import { IClassOf } from "@entity-access/entity-access/dist/decorators/IClassOf.js";
9
9
  import { OutgoingHttpHeaders } from "http";
10
+ import Content, { Redirect } from "./Content.js";
11
+ import JsonGenerator from "@entity-access/entity-access/dist/common/JsonGenerator.js";
10
12
 
11
13
  export const isPage = Symbol("isPage");
12
14
 
@@ -94,7 +96,7 @@ export default abstract class Page<TInput = any, TQuery = any> {
94
96
  this.cacheControl = "no-cache, no-store, max-age=0";
95
97
  }
96
98
 
97
- abstract run(): PageResult | Promise<PageResult>;
99
+ abstract run(): Content | Promise<Content>;
98
100
 
99
101
  resolve<T>(c: IClassOf<T>): T {
100
102
  return ServiceProvider.resolve(this, c);
@@ -104,22 +106,27 @@ export default abstract class Page<TInput = any, TQuery = any> {
104
106
  console.error(error);
105
107
  }
106
108
 
107
- protected content(h: Partial<Content>): Content;
108
- protected content(body: string, status?: number, contentType?: string, headers?: OutgoingHttpHeaders): Content;
109
- protected content(body: string | Partial<Content>, status?: number, contentType?: string, headers?: OutgoingHttpHeaders) {
110
- if (typeof body !== "object") {
111
- body = { body, status, contentType, headers};
112
- }
113
- body.status ??= 200;
114
- body.contentType ??= "text/html";
115
- return Content.create(body);
116
- }
109
+ // protected content(h: Partial<Content>): Content;
110
+ // protected content(body: string, status?: number, contentType?: string, headers?: OutgoingHttpHeaders): Content;
111
+ // protected content(body: string | Partial<Content>, status?: number, contentType?: string, headers?: OutgoingHttpHeaders) {
112
+ // if (typeof body !== "object") {
113
+ // body = { body, status, contentType, headers};
114
+ // }
115
+ // body.status ??= 200;
116
+ // body.contentType ??= "text/html";
117
+ // return Content.create(body);
118
+ // }
117
119
 
118
120
  protected json(o: any, indent = 0, headers = void 0 as OutgoingHttpHeaders) {
119
- const content = indent
120
- ? JSON.stringify(o, undefined, indent)
121
- : JSON.stringify(o);
122
- return this.content(content, 200, "application/json", headers);
121
+ // const content = indent
122
+ // ? JSON.stringify(o, undefined, indent)
123
+ // : JSON.stringify(o);
124
+ const jsr = new JsonGenerator(this);
125
+ headers ??= {};
126
+ headers["content-type"] = "application/json; charset=utf8";
127
+ return Content.readable(jsr.reader(o), {
128
+ headers
129
+ });
123
130
  }
124
131
 
125
132
  protected redirect(location: string) {
@@ -127,7 +134,7 @@ export default abstract class Page<TInput = any, TQuery = any> {
127
134
  }
128
135
 
129
136
  protected notFound(suppressLog = true): Content | Promise<Content> {
130
- const c = Content.html(<HtmlDocument>
137
+ return Content.text(<HtmlDocument>
131
138
  <head>
132
139
  <title>Not found</title>
133
140
  </head>
@@ -136,15 +143,16 @@ export default abstract class Page<TInput = any, TQuery = any> {
136
143
  <pre>{this.url} not found</pre>
137
144
  </body>
138
145
  </HtmlDocument>,
139
- 404
146
+ {
147
+ status: 404,
148
+ suppressLog
149
+ }
140
150
  );
141
- c.suppressLog = suppressLog;
142
- return c;
143
151
  }
144
152
 
145
153
  protected serverError(error, status = 500): Content | Promise<Content> {
146
- return Content.create({
147
- body: <HtmlDocument>
154
+ return Content.text(
155
+ <HtmlDocument>
148
156
  <head>
149
157
  <title>Server Error</title>
150
158
  </head>
@@ -153,7 +161,9 @@ export default abstract class Page<TInput = any, TQuery = any> {
153
161
  <pre>{error.stack ?? error}</pre>
154
162
  </body>
155
163
  </HtmlDocument>,
156
- status
157
- });
164
+ {
165
+ status
166
+ }
167
+ );
158
168
  }
159
169
  }
@@ -21,6 +21,7 @@ import { WebSocket } from "ws";
21
21
  import { UrlParser } from "./core/UrlParser.js";
22
22
  import { createConnection } from "node:net";
23
23
  import HttpIPCProxyReceiver from "./core/HttpIPCProxyReceiver.js";
24
+ import JsonGenerator from "@entity-access/entity-access/dist/common/JsonGenerator.js";
24
25
 
25
26
  export const wsData = Symbol("wsData");
26
27
 
@@ -338,17 +339,25 @@ export default class ServerPages {
338
339
  try {
339
340
 
340
341
  if (acceptJson || error.errorModel) {
341
- await Content.json(
342
+
343
+ const json = new JsonGenerator(this);
344
+ const reader = json.reader({
345
+ details: error.stack ?? error,
346
+ ... error.errorModel ?? {},
347
+ message: error.message ?? error,
348
+ })
349
+
350
+ await new Content(
342
351
  {
343
- details: error.stack ?? error,
344
- ... error.errorModel ?? {},
345
- message: error.message ?? error,
352
+ reader,
353
+ status: error.errorModel?.status ?? 500
346
354
  }
347
- , error.errorModel?.status ?? 500).send(resp);
355
+ ,).send(resp);
348
356
  return;
349
357
  }
350
358
 
351
- const content = Content.html(`<!DOCTYPE html>\n<html><body><pre>Server Error for ${req.url}\r\n${error?.stack ?? error}</pre></body></html>`, 500);
359
+ const content = Content.text(`<!DOCTYPE html>\n<html><body><pre>Server Error for ${req.url}\r\n${error?.stack ?? error}</pre></body></html>`,
360
+ { status: 500});
352
361
  await content.send(resp);
353
362
  } catch (e1) {
354
363
  e1 = e1.stack ?? e1.toString();
@@ -1,15 +1,26 @@
1
- import { deflateSync, gzipSync } from "zlib";
1
+ import { Readable } from "stream";
2
+ import { createGzip, deflateSync, gzipSync, createDeflate } from "zlib";
2
3
 
3
4
  export default class Compression {
4
5
 
5
- public static gzip(data: Buffer | string) {
6
+ public static gzip(readable: Readable) {
7
+ const stream = createGzip();
8
+ return readable.pipe(stream);
9
+ }
10
+
11
+ public static deflate(readable: Readable) {
12
+ const stream = createDeflate();
13
+ return readable.pipe(stream);
14
+ }
15
+
16
+ public static gzipSync(data: Buffer | string) {
6
17
  if (typeof data === "string") {
7
18
  data = Buffer.from(data, "utf-8");
8
19
  }
9
20
  return gzipSync(data);
10
21
  }
11
22
 
12
- public static deflate(data: Buffer | string) {
23
+ public static deflateSync(data: Buffer | string) {
13
24
  if (typeof data === "string") {
14
25
  data = Buffer.from(data, "utf-8");
15
26
  }
@@ -179,7 +179,7 @@ export default class RouteTree {
179
179
 
180
180
  async run() {
181
181
  const r = await pageClass.call(this);
182
- return Content.create(r);
182
+ return r;
183
183
  }
184
184
  }
185
185
  return c;
@@ -0,0 +1,15 @@
1
+ import { Readable } from "stream";
2
+
3
+ export default class Utf8Readable {
4
+
5
+ static from(text: Iterable<string>) {
6
+ return Readable.from( this.toUtf8Iterable(text));
7
+ }
8
+
9
+ private static *toUtf8Iterable(text: Iterable<string>) {
10
+ for (const element of text) {
11
+ yield Buffer.from(element, "utf8");
12
+ }
13
+ }
14
+
15
+ }