@oino-ts/common 0.17.2 → 0.17.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.
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OINOHeaders = void 0;
4
+ /**
5
+ * Type for HTTP style headers that just guarantees keys are normalized to lowercase.
6
+ *
7
+ */
8
+ class OINOHeaders {
9
+ constructor(init) {
10
+ this.setHeaders(init ?? {});
11
+ }
12
+ get(key) {
13
+ return this[key.toLowerCase()];
14
+ }
15
+ set(key, value) {
16
+ this[key.toLowerCase()] = value;
17
+ }
18
+ setHeaders(init) {
19
+ if (init instanceof OINOHeaders) {
20
+ for (const key of Object.keys(init)) {
21
+ this.set(key, init.get(key));
22
+ }
23
+ }
24
+ else if (init instanceof Map) {
25
+ for (const [key, value] of init.entries()) {
26
+ this.set(key, value);
27
+ }
28
+ }
29
+ else if (Array.isArray(init)) {
30
+ for (const [key, value] of init) {
31
+ this.set(key, value);
32
+ }
33
+ }
34
+ else if (init && typeof init === "object") {
35
+ for (const key in init) {
36
+ this.set(key, init[key]);
37
+ }
38
+ }
39
+ }
40
+ clear() {
41
+ for (const key of Object.keys(this)) {
42
+ delete this[key];
43
+ }
44
+ }
45
+ }
46
+ exports.OINOHeaders = OINOHeaders;
@@ -6,8 +6,8 @@
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.OINOHttpRequest = exports.OINORequest = void 0;
9
+ const node_buffer_1 = require("node:buffer");
9
10
  const _1 = require(".");
10
- const index_js_1 = require("./index.js");
11
11
  /**
12
12
  * OINO API request result object with returned data and/or http status code/message and
13
13
  * error / warning messages.
@@ -25,14 +25,6 @@ class OINORequest {
25
25
  constructor(init) {
26
26
  this.params = init?.params ?? {};
27
27
  }
28
- /**
29
- * Copy values from different result.
30
- *
31
- * @param request source value
32
- */
33
- copy(request) {
34
- this.params = { ...request.params };
35
- }
36
28
  }
37
29
  exports.OINORequest = OINORequest;
38
30
  /**
@@ -58,7 +50,7 @@ class OINOHttpRequest extends OINORequest {
58
50
  super(init);
59
51
  this.url = init.url;
60
52
  this.method = init.method ?? "GET";
61
- this.headers = init.headers ?? {};
53
+ this.headers = new _1.OINOHeaders(init.headers);
62
54
  this.data = init.data ?? "";
63
55
  this.multipartBoundary = "";
64
56
  this.lastModified = init.lastModified;
@@ -69,7 +61,7 @@ class OINOHttpRequest extends OINORequest {
69
61
  this.requestType = init.requestType;
70
62
  }
71
63
  else {
72
- const request_type_param = this.url?.searchParams.get(index_js_1.OINO_REQUEST_TYPE_PARAM) || this.headers["content-type"]; // content-type header can be overridden by query parameter
64
+ const request_type_param = this.url?.searchParams.get(_1.OINO_REQUEST_TYPE_PARAM) || this.headers.get("content-type"); // content-type header can be overridden by query parameter
73
65
  if (request_type_param == _1.OINOContentType.csv) {
74
66
  this.requestType = _1.OINOContentType.csv;
75
67
  }
@@ -90,7 +82,7 @@ class OINOHttpRequest extends OINORequest {
90
82
  this.responseType = init.responseType;
91
83
  }
92
84
  else {
93
- const response_type_param = this.url?.searchParams.get(index_js_1.OINO_RESPONSE_TYPE_PARAM) || this.headers["accept"]; // accept header can be overridden by query parameter
85
+ const response_type_param = this.url?.searchParams.get(_1.OINO_RESPONSE_TYPE_PARAM) || this.headers.get("accept"); // accept header can be overridden by query parameter
94
86
  const accept_types = response_type_param?.split(', ') || [];
95
87
  let response_type = undefined;
96
88
  for (let i = 0; i < accept_types.length; i++) {
@@ -101,23 +93,57 @@ class OINOHttpRequest extends OINORequest {
101
93
  }
102
94
  this.responseType = response_type ?? _1.OINOContentType.json;
103
95
  }
104
- const last_modified = this.headers["if-modified-since"];
96
+ const last_modified = this.headers.get("if-modified-since");
105
97
  if (last_modified) {
106
98
  this.lastModified = new Date(last_modified).getTime();
107
99
  }
108
- const etags = this.headers["if-none-match"]?.split(',').map(e => e.trim());
100
+ const etags = this.headers.get("if-none-match")?.split(',').map(e => e.trim());
109
101
  if (etags) {
110
102
  this.etags = etags;
111
103
  }
112
104
  }
113
- static async fromRequest(request) {
105
+ static async fromFetchRequest(request) {
114
106
  const body = await request.arrayBuffer();
115
107
  return new OINOHttpRequest({
116
108
  url: new URL(request.url),
117
109
  method: request.method,
118
110
  headers: Object.fromEntries(request.headers),
119
- data: Buffer.from(body),
111
+ data: node_buffer_1.Buffer.from(body),
120
112
  });
121
113
  }
114
+ dataAsText() {
115
+ if (this.data instanceof Uint8Array) {
116
+ return new TextDecoder().decode(this.data);
117
+ }
118
+ else if (this.data instanceof Object) {
119
+ return JSON.stringify(this.data);
120
+ }
121
+ else {
122
+ return this.data?.toString() || "";
123
+ }
124
+ }
125
+ dataAsParsedJson() {
126
+ return this.data ? JSON.parse(this.dataAsText()) : {};
127
+ }
128
+ dataAsFormData() {
129
+ return new URLSearchParams(this.dataAsText() || "");
130
+ }
131
+ dataAsBuffer() {
132
+ if (this.data === null) {
133
+ return node_buffer_1.Buffer.alloc(0);
134
+ }
135
+ else if (this.data instanceof node_buffer_1.Buffer) {
136
+ return this.data;
137
+ }
138
+ else if (this.data instanceof Uint8Array) {
139
+ return node_buffer_1.Buffer.from(this.data);
140
+ }
141
+ else if (this.data instanceof Object) {
142
+ return node_buffer_1.Buffer.from(JSON.stringify(this.data), "utf-8");
143
+ }
144
+ else {
145
+ return node_buffer_1.Buffer.from(this.data, "utf-8");
146
+ }
147
+ }
122
148
  }
123
149
  exports.OINOHttpRequest = OINOHttpRequest;
@@ -133,19 +133,19 @@ class OINOResult {
133
133
  for (let i = 0; i < this.messages.length; i++) {
134
134
  const message = this.messages[i].replaceAll("\r", " ").replaceAll("\n", " ");
135
135
  if (copyErrors && message.startsWith(_1.OINO_ERROR_PREFIX)) {
136
- headers.append('X-OINO-MESSAGE-' + j, message);
136
+ headers.set('X-OINO-MESSAGE-' + j, message);
137
137
  j++;
138
138
  }
139
139
  if (copyWarnings && message.startsWith(_1.OINO_WARNING_PREFIX)) {
140
- headers.append('X-OINO-MESSAGE-' + j, message);
140
+ headers.set('X-OINO-MESSAGE-' + j, message);
141
141
  j++;
142
142
  }
143
143
  if (copyInfos && message.startsWith(_1.OINO_INFO_PREFIX)) {
144
- headers.append('X-OINO-MESSAGE-' + j, message);
144
+ headers.set('X-OINO-MESSAGE-' + j, message);
145
145
  j++;
146
146
  }
147
147
  if (copyDebug && message.startsWith(_1.OINO_DEBUG_PREFIX)) {
148
- headers.append('X-OINO-MESSAGE-' + j, message);
148
+ headers.set('X-OINO-MESSAGE-' + j, message);
149
149
  j++;
150
150
  }
151
151
  }
@@ -166,6 +166,8 @@ class OINOHttpResult extends OINOResult {
166
166
  _etag;
167
167
  /** HTTP body data */
168
168
  body;
169
+ /** HTTP headers */
170
+ headers;
169
171
  /** HTTP cache expiration value
170
172
  * Note: default 0 means no expiration and 'Pragma: no-cache' is set.
171
173
  */
@@ -181,6 +183,7 @@ class OINOHttpResult extends OINOResult {
181
183
  constructor(init) {
182
184
  super(init);
183
185
  this.body = init?.body ?? "";
186
+ this.headers = new _1.OINOHeaders(init?.headers);
184
187
  this.expires = init?.expires ?? 0;
185
188
  this.lastModified = init?.lastModified ?? 0;
186
189
  this._etag = "";
@@ -201,14 +204,18 @@ class OINOHttpResult extends OINOResult {
201
204
  *
202
205
  * @param headers HTTP headers (overrides existing values)
203
206
  */
204
- getHttpResponse(headers) {
205
- const result = new Response(this.body, { status: this.status, statusText: this.statusText, headers: headers });
207
+ getFetchResponse(headers) {
208
+ const merged_headers = new _1.OINOHeaders(this.headers);
209
+ if (headers) {
210
+ merged_headers.setHeaders(headers);
211
+ }
212
+ const result = new Response(this.body, { status: this.status, statusText: this.statusText, headers: merged_headers });
206
213
  result.headers.set('Content-Length', this.body.length.toString());
207
- if (this.lastModified > 0) {
214
+ if (merged_headers['Last-Modified'] === undefined && this.lastModified > 0) {
208
215
  result.headers.set('Last-Modified', new Date(this.lastModified).toUTCString());
209
216
  }
210
- if (this.expires >= 0) {
211
- result.headers.set('Expires', Math.round(this.expires).toString());
217
+ if (merged_headers['Expires'] === undefined && this.expires >= 0) {
218
+ result.headers.set('Expires', new Date(Date.now() + Math.round(this.expires)).toUTCString());
212
219
  if (this.expires == 0) {
213
220
  result.headers.set('Pragma', 'no-cache');
214
221
  }
package/dist/cjs/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OINOContentType = exports.OINO_RESPONSE_TYPE_PARAM = exports.OINO_REQUEST_TYPE_PARAM = exports.OINO_DEBUG_PREFIX = exports.OINO_INFO_PREFIX = exports.OINO_WARNING_PREFIX = exports.OINO_ERROR_PREFIX = exports.OINO_EMPTY_FORMATTER = exports.OINOFormatter = exports.OINOHtmlTemplate = exports.OINOStr = exports.OINOHttpRequest = exports.OINORequest = exports.OINOHttpResult = exports.OINOResult = exports.OINOConsoleLog = exports.OINOLogLevel = exports.OINOLog = exports.OINOMemoryBenchmark = exports.OINOBenchmark = void 0;
3
+ exports.OINOContentType = exports.OINO_RESPONSE_TYPE_PARAM = exports.OINO_REQUEST_TYPE_PARAM = exports.OINO_DEBUG_PREFIX = exports.OINO_INFO_PREFIX = exports.OINO_WARNING_PREFIX = exports.OINO_ERROR_PREFIX = exports.OINOHeaders = exports.OINO_EMPTY_FORMATTER = exports.OINOFormatter = exports.OINOHtmlTemplate = exports.OINOStr = exports.OINOHttpRequest = exports.OINORequest = exports.OINOHttpResult = exports.OINOResult = exports.OINOConsoleLog = exports.OINOLogLevel = exports.OINOLog = exports.OINOMemoryBenchmark = exports.OINOBenchmark = void 0;
4
4
  var OINOBenchmark_js_1 = require("./OINOBenchmark.js");
5
5
  Object.defineProperty(exports, "OINOBenchmark", { enumerable: true, get: function () { return OINOBenchmark_js_1.OINOBenchmark; } });
6
6
  Object.defineProperty(exports, "OINOMemoryBenchmark", { enumerable: true, get: function () { return OINOBenchmark_js_1.OINOMemoryBenchmark; } });
@@ -21,6 +21,8 @@ Object.defineProperty(exports, "OINOHtmlTemplate", { enumerable: true, get: func
21
21
  var OINOFormatter_js_1 = require("./OINOFormatter.js");
22
22
  Object.defineProperty(exports, "OINOFormatter", { enumerable: true, get: function () { return OINOFormatter_js_1.OINOFormatter; } });
23
23
  Object.defineProperty(exports, "OINO_EMPTY_FORMATTER", { enumerable: true, get: function () { return OINOFormatter_js_1.OINO_EMPTY_FORMATTER; } });
24
+ var OINOHeaders_js_1 = require("./OINOHeaders.js");
25
+ Object.defineProperty(exports, "OINOHeaders", { enumerable: true, get: function () { return OINOHeaders_js_1.OINOHeaders; } });
24
26
  /** OINO error message prefix */
25
27
  exports.OINO_ERROR_PREFIX = "OINO ERROR";
26
28
  /** OINO warning message prefix */
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Type for HTTP style headers that just guarantees keys are normalized to lowercase.
3
+ *
4
+ */
5
+ export class OINOHeaders {
6
+ constructor(init) {
7
+ this.setHeaders(init ?? {});
8
+ }
9
+ get(key) {
10
+ return this[key.toLowerCase()];
11
+ }
12
+ set(key, value) {
13
+ this[key.toLowerCase()] = value;
14
+ }
15
+ setHeaders(init) {
16
+ if (init instanceof OINOHeaders) {
17
+ for (const key of Object.keys(init)) {
18
+ this.set(key, init.get(key));
19
+ }
20
+ }
21
+ else if (init instanceof Map) {
22
+ for (const [key, value] of init.entries()) {
23
+ this.set(key, value);
24
+ }
25
+ }
26
+ else if (Array.isArray(init)) {
27
+ for (const [key, value] of init) {
28
+ this.set(key, value);
29
+ }
30
+ }
31
+ else if (init && typeof init === "object") {
32
+ for (const key in init) {
33
+ this.set(key, init[key]);
34
+ }
35
+ }
36
+ }
37
+ clear() {
38
+ for (const key of Object.keys(this)) {
39
+ delete this[key];
40
+ }
41
+ }
42
+ }
@@ -3,8 +3,8 @@
3
3
  * License, v. 2.0. If a copy of the MPL was not distributed with this
4
4
  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
5
  */
6
- import { OINOContentType } from ".";
7
- import { OINO_REQUEST_TYPE_PARAM, OINO_RESPONSE_TYPE_PARAM } from "./index.js";
6
+ import { Buffer } from "node:buffer";
7
+ import { OINOContentType, OINO_REQUEST_TYPE_PARAM, OINO_RESPONSE_TYPE_PARAM, OINOHeaders } from ".";
8
8
  /**
9
9
  * OINO API request result object with returned data and/or http status code/message and
10
10
  * error / warning messages.
@@ -22,14 +22,6 @@ export class OINORequest {
22
22
  constructor(init) {
23
23
  this.params = init?.params ?? {};
24
24
  }
25
- /**
26
- * Copy values from different result.
27
- *
28
- * @param request source value
29
- */
30
- copy(request) {
31
- this.params = { ...request.params };
32
- }
33
25
  }
34
26
  /**
35
27
  * Specialized result for HTTP responses.
@@ -54,7 +46,7 @@ export class OINOHttpRequest extends OINORequest {
54
46
  super(init);
55
47
  this.url = init.url;
56
48
  this.method = init.method ?? "GET";
57
- this.headers = init.headers ?? {};
49
+ this.headers = new OINOHeaders(init.headers);
58
50
  this.data = init.data ?? "";
59
51
  this.multipartBoundary = "";
60
52
  this.lastModified = init.lastModified;
@@ -65,7 +57,7 @@ export class OINOHttpRequest extends OINORequest {
65
57
  this.requestType = init.requestType;
66
58
  }
67
59
  else {
68
- const request_type_param = this.url?.searchParams.get(OINO_REQUEST_TYPE_PARAM) || this.headers["content-type"]; // content-type header can be overridden by query parameter
60
+ const request_type_param = this.url?.searchParams.get(OINO_REQUEST_TYPE_PARAM) || this.headers.get("content-type"); // content-type header can be overridden by query parameter
69
61
  if (request_type_param == OINOContentType.csv) {
70
62
  this.requestType = OINOContentType.csv;
71
63
  }
@@ -86,7 +78,7 @@ export class OINOHttpRequest extends OINORequest {
86
78
  this.responseType = init.responseType;
87
79
  }
88
80
  else {
89
- const response_type_param = this.url?.searchParams.get(OINO_RESPONSE_TYPE_PARAM) || this.headers["accept"]; // accept header can be overridden by query parameter
81
+ const response_type_param = this.url?.searchParams.get(OINO_RESPONSE_TYPE_PARAM) || this.headers.get("accept"); // accept header can be overridden by query parameter
90
82
  const accept_types = response_type_param?.split(', ') || [];
91
83
  let response_type = undefined;
92
84
  for (let i = 0; i < accept_types.length; i++) {
@@ -97,16 +89,16 @@ export class OINOHttpRequest extends OINORequest {
97
89
  }
98
90
  this.responseType = response_type ?? OINOContentType.json;
99
91
  }
100
- const last_modified = this.headers["if-modified-since"];
92
+ const last_modified = this.headers.get("if-modified-since");
101
93
  if (last_modified) {
102
94
  this.lastModified = new Date(last_modified).getTime();
103
95
  }
104
- const etags = this.headers["if-none-match"]?.split(',').map(e => e.trim());
96
+ const etags = this.headers.get("if-none-match")?.split(',').map(e => e.trim());
105
97
  if (etags) {
106
98
  this.etags = etags;
107
99
  }
108
100
  }
109
- static async fromRequest(request) {
101
+ static async fromFetchRequest(request) {
110
102
  const body = await request.arrayBuffer();
111
103
  return new OINOHttpRequest({
112
104
  url: new URL(request.url),
@@ -115,4 +107,38 @@ export class OINOHttpRequest extends OINORequest {
115
107
  data: Buffer.from(body),
116
108
  });
117
109
  }
110
+ dataAsText() {
111
+ if (this.data instanceof Uint8Array) {
112
+ return new TextDecoder().decode(this.data);
113
+ }
114
+ else if (this.data instanceof Object) {
115
+ return JSON.stringify(this.data);
116
+ }
117
+ else {
118
+ return this.data?.toString() || "";
119
+ }
120
+ }
121
+ dataAsParsedJson() {
122
+ return this.data ? JSON.parse(this.dataAsText()) : {};
123
+ }
124
+ dataAsFormData() {
125
+ return new URLSearchParams(this.dataAsText() || "");
126
+ }
127
+ dataAsBuffer() {
128
+ if (this.data === null) {
129
+ return Buffer.alloc(0);
130
+ }
131
+ else if (this.data instanceof Buffer) {
132
+ return this.data;
133
+ }
134
+ else if (this.data instanceof Uint8Array) {
135
+ return Buffer.from(this.data);
136
+ }
137
+ else if (this.data instanceof Object) {
138
+ return Buffer.from(JSON.stringify(this.data), "utf-8");
139
+ }
140
+ else {
141
+ return Buffer.from(this.data, "utf-8");
142
+ }
143
+ }
118
144
  }
@@ -4,7 +4,7 @@
4
4
  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
5
  */
6
6
  import { createHash } from "node:crypto";
7
- import { OINO_DEBUG_PREFIX, OINO_ERROR_PREFIX, OINO_INFO_PREFIX, OINO_WARNING_PREFIX } from ".";
7
+ import { OINO_DEBUG_PREFIX, OINO_ERROR_PREFIX, OINO_INFO_PREFIX, OINO_WARNING_PREFIX, OINOHeaders } from ".";
8
8
  /**
9
9
  * OINO API request result object with returned data and/or http status code/message and
10
10
  * error / warning messages.
@@ -130,19 +130,19 @@ export class OINOResult {
130
130
  for (let i = 0; i < this.messages.length; i++) {
131
131
  const message = this.messages[i].replaceAll("\r", " ").replaceAll("\n", " ");
132
132
  if (copyErrors && message.startsWith(OINO_ERROR_PREFIX)) {
133
- headers.append('X-OINO-MESSAGE-' + j, message);
133
+ headers.set('X-OINO-MESSAGE-' + j, message);
134
134
  j++;
135
135
  }
136
136
  if (copyWarnings && message.startsWith(OINO_WARNING_PREFIX)) {
137
- headers.append('X-OINO-MESSAGE-' + j, message);
137
+ headers.set('X-OINO-MESSAGE-' + j, message);
138
138
  j++;
139
139
  }
140
140
  if (copyInfos && message.startsWith(OINO_INFO_PREFIX)) {
141
- headers.append('X-OINO-MESSAGE-' + j, message);
141
+ headers.set('X-OINO-MESSAGE-' + j, message);
142
142
  j++;
143
143
  }
144
144
  if (copyDebug && message.startsWith(OINO_DEBUG_PREFIX)) {
145
- headers.append('X-OINO-MESSAGE-' + j, message);
145
+ headers.set('X-OINO-MESSAGE-' + j, message);
146
146
  j++;
147
147
  }
148
148
  }
@@ -162,6 +162,8 @@ export class OINOHttpResult extends OINOResult {
162
162
  _etag;
163
163
  /** HTTP body data */
164
164
  body;
165
+ /** HTTP headers */
166
+ headers;
165
167
  /** HTTP cache expiration value
166
168
  * Note: default 0 means no expiration and 'Pragma: no-cache' is set.
167
169
  */
@@ -177,6 +179,7 @@ export class OINOHttpResult extends OINOResult {
177
179
  constructor(init) {
178
180
  super(init);
179
181
  this.body = init?.body ?? "";
182
+ this.headers = new OINOHeaders(init?.headers);
180
183
  this.expires = init?.expires ?? 0;
181
184
  this.lastModified = init?.lastModified ?? 0;
182
185
  this._etag = "";
@@ -197,14 +200,18 @@ export class OINOHttpResult extends OINOResult {
197
200
  *
198
201
  * @param headers HTTP headers (overrides existing values)
199
202
  */
200
- getHttpResponse(headers) {
201
- const result = new Response(this.body, { status: this.status, statusText: this.statusText, headers: headers });
203
+ getFetchResponse(headers) {
204
+ const merged_headers = new OINOHeaders(this.headers);
205
+ if (headers) {
206
+ merged_headers.setHeaders(headers);
207
+ }
208
+ const result = new Response(this.body, { status: this.status, statusText: this.statusText, headers: merged_headers });
202
209
  result.headers.set('Content-Length', this.body.length.toString());
203
- if (this.lastModified > 0) {
210
+ if (merged_headers['Last-Modified'] === undefined && this.lastModified > 0) {
204
211
  result.headers.set('Last-Modified', new Date(this.lastModified).toUTCString());
205
212
  }
206
- if (this.expires >= 0) {
207
- result.headers.set('Expires', Math.round(this.expires).toString());
213
+ if (merged_headers['Expires'] === undefined && this.expires >= 0) {
214
+ result.headers.set('Expires', new Date(Date.now() + Math.round(this.expires)).toUTCString());
208
215
  if (this.expires == 0) {
209
216
  result.headers.set('Pragma', 'no-cache');
210
217
  }
package/dist/esm/index.js CHANGED
@@ -5,6 +5,7 @@ export { OINORequest, OINOHttpRequest } from "./OINORequest.js";
5
5
  export { OINOStr } from "./OINOStr.js";
6
6
  export { OINOHtmlTemplate } from "./OINOHtmlTemplate.js";
7
7
  export { OINOFormatter, OINO_EMPTY_FORMATTER } from "./OINOFormatter.js";
8
+ export { OINOHeaders } from "./OINOHeaders.js";
8
9
  /** OINO error message prefix */
9
10
  export const OINO_ERROR_PREFIX = "OINO ERROR";
10
11
  /** OINO warning message prefix */
@@ -0,0 +1,13 @@
1
+ export type OINOHeadersInit = OINOHeaders | Record<string, string> | [string, string][] | Map<string, string>;
2
+ /**
3
+ * Type for HTTP style headers that just guarantees keys are normalized to lowercase.
4
+ *
5
+ */
6
+ export declare class OINOHeaders {
7
+ [key: string]: any;
8
+ constructor(init?: OINOHeadersInit);
9
+ get(key: string): string | undefined;
10
+ set(key: string, value: string): void;
11
+ setHeaders(init?: OINOHeadersInit): void;
12
+ clear(): void;
13
+ }
@@ -1,4 +1,5 @@
1
- import { OINOContentType } from ".";
1
+ import { Buffer } from "node:buffer";
2
+ import { OINOContentType, OINOHeaders, OINOHeadersInit } from ".";
2
3
  export interface OINORequestInit {
3
4
  params?: Record<string, string>;
4
5
  }
@@ -17,17 +18,11 @@ export declare class OINORequest {
17
18
  *
18
19
  */
19
20
  constructor(init?: OINORequestInit);
20
- /**
21
- * Copy values from different result.
22
- *
23
- * @param request source value
24
- */
25
- copy(request: OINORequest): void;
26
21
  }
27
22
  export interface OINOHttpRequestInit extends OINORequestInit {
28
23
  url?: URL;
29
24
  method?: string;
30
- headers?: Record<string, string>;
25
+ headers?: OINOHeadersInit;
31
26
  data?: string | Buffer | Uint8Array | object | null;
32
27
  requestType?: OINOContentType;
33
28
  responseType?: OINOContentType;
@@ -40,7 +35,7 @@ export interface OINOHttpRequestInit extends OINORequestInit {
40
35
  export declare class OINOHttpRequest extends OINORequest {
41
36
  readonly url?: URL;
42
37
  readonly method: string;
43
- readonly headers: Record<string, string>;
38
+ readonly headers: OINOHeaders;
44
39
  readonly data: string | Buffer | Uint8Array | object | null;
45
40
  readonly requestType: OINOContentType;
46
41
  readonly responseType: OINOContentType;
@@ -54,5 +49,9 @@ export declare class OINOHttpRequest extends OINORequest {
54
49
  *
55
50
  */
56
51
  constructor(init: OINOHttpRequestInit);
57
- static fromRequest(request: Request): Promise<OINOHttpRequest>;
52
+ static fromFetchRequest(request: Request): Promise<OINOHttpRequest>;
53
+ dataAsText(): string;
54
+ dataAsParsedJson(): any;
55
+ dataAsFormData(): URLSearchParams;
56
+ dataAsBuffer(): Buffer;
58
57
  }
@@ -1,3 +1,4 @@
1
+ import { OINOHeaders, OINOHeadersInit } from ".";
1
2
  export interface OINOResultInit {
2
3
  success?: boolean;
3
4
  status?: number;
@@ -79,7 +80,7 @@ export declare class OINOResult {
79
80
  * @param copyDebug wether debug messages should be copied (default false)
80
81
  *
81
82
  */
82
- copyMessagesToHeaders(headers: Headers, copyErrors?: boolean, copyWarnings?: boolean, copyInfos?: boolean, copyDebug?: boolean): void;
83
+ copyMessagesToHeaders(headers: OINOHeaders, copyErrors?: boolean, copyWarnings?: boolean, copyInfos?: boolean, copyDebug?: boolean): void;
83
84
  /**
84
85
  * Print result for logging.
85
86
  *
@@ -88,6 +89,7 @@ export declare class OINOResult {
88
89
  }
89
90
  export interface OINOHttpResultInit extends OINOResultInit {
90
91
  body?: string;
92
+ headers?: OINOHeadersInit;
91
93
  expires?: number;
92
94
  lastModified?: number;
93
95
  }
@@ -98,6 +100,8 @@ export declare class OINOHttpResult extends OINOResult {
98
100
  private _etag;
99
101
  /** HTTP body data */
100
102
  readonly body: string;
103
+ /** HTTP headers */
104
+ readonly headers: OINOHeaders;
101
105
  /** HTTP cache expiration value
102
106
  * Note: default 0 means no expiration and 'Pragma: no-cache' is set.
103
107
  */
@@ -121,5 +125,5 @@ export declare class OINOHttpResult extends OINOResult {
121
125
  *
122
126
  * @param headers HTTP headers (overrides existing values)
123
127
  */
124
- getHttpResponse(headers?: Record<string, string>): Response;
128
+ getFetchResponse(headers?: OINOHeadersInit): Response;
125
129
  }
@@ -5,6 +5,7 @@ export { OINORequest, OINOHttpRequest, type OINORequestInit, type OINOHttpReques
5
5
  export { OINOStr } from "./OINOStr.js";
6
6
  export { OINOHtmlTemplate } from "./OINOHtmlTemplate.js";
7
7
  export { OINOFormatter, OINO_EMPTY_FORMATTER } from "./OINOFormatter.js";
8
+ export { OINOHeaders, type OINOHeadersInit } from "./OINOHeaders.js";
8
9
  /** OINO error message prefix */
9
10
  export declare const OINO_ERROR_PREFIX = "OINO ERROR";
10
11
  /** OINO warning message prefix */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oino-ts/common",
3
- "version": "0.17.2",
3
+ "version": "0.17.4",
4
4
  "description": "OINO TS package for common classes.",
5
5
  "author": "Matias Kiviniemi (pragmatta)",
6
6
  "license": "MPL-2.0",
@@ -19,7 +19,7 @@
19
19
  "dependencies": {
20
20
  },
21
21
  "devDependencies": {
22
- "@oino-ts/types": "0.17.2",
22
+ "@oino-ts/types": "0.17.4",
23
23
  "@types/node": "^22.0.0",
24
24
  "typescript": "~5.9.0"
25
25
  },
@@ -0,0 +1,51 @@
1
+ export type OINOHeadersInit = OINOHeaders | Record<string, string> | [string, string][] | Map<string, string>;
2
+
3
+ /**
4
+ * Type for HTTP style headers that just guarantees keys are normalized to lowercase.
5
+ *
6
+ */
7
+
8
+ export class OINOHeaders {
9
+ [key: string]: any
10
+
11
+ constructor(init?: OINOHeadersInit) {
12
+ this.setHeaders(init ?? {})
13
+ }
14
+
15
+ get(key: string): string | undefined {
16
+ return this[key.toLowerCase()]
17
+ }
18
+
19
+ set(key: string, value: string): void {
20
+ this[key.toLowerCase()] = value
21
+ }
22
+
23
+ setHeaders(init?: OINOHeadersInit): void {
24
+ if (init instanceof OINOHeaders) {
25
+ for (const key of Object.keys(init)) {
26
+ this.set(key, init.get(key)!)
27
+ }
28
+
29
+ } else if (init instanceof Map) {
30
+ for (const [key, value] of init.entries()) {
31
+ this.set(key, value)
32
+ }
33
+
34
+ } else if (Array.isArray(init)) {
35
+ for (const [key, value] of init) {
36
+ this.set(key, value)
37
+ }
38
+
39
+ } else if (init && typeof init === "object") {
40
+ for (const key in init as Record<string, string>) {
41
+ this.set(key, init[key])
42
+ }
43
+ }
44
+ }
45
+
46
+ clear(): void {
47
+ for (const key of Object.keys(this)) {
48
+ delete this[key]
49
+ }
50
+ }
51
+ }
@@ -4,8 +4,9 @@
4
4
  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
5
  */
6
6
 
7
- import { OINOContentType } from "."
8
- import { OINO_REQUEST_TYPE_PARAM, OINO_RESPONSE_TYPE_PARAM } from "./index.js"
7
+ import { Buffer } from "node:buffer"
8
+
9
+ import { OINOContentType, OINO_REQUEST_TYPE_PARAM, OINO_RESPONSE_TYPE_PARAM, OINOHeaders, OINOHeadersInit } from "."
9
10
 
10
11
  export interface OINORequestInit {
11
12
  params?: Record<string, string>
@@ -29,21 +30,12 @@ export class OINORequest {
29
30
  constructor (init?: OINORequestInit) {
30
31
  this.params = init?.params ?? {}
31
32
  }
32
-
33
- /**
34
- * Copy values from different result.
35
- *
36
- * @param request source value
37
- */
38
- copy(request: OINORequest) {
39
- this.params = {...request.params}
40
- }
41
33
  }
42
34
 
43
35
  export interface OINOHttpRequestInit extends OINORequestInit {
44
36
  url?: URL
45
37
  method?: string
46
- headers?: Record<string, string>
38
+ headers?: OINOHeadersInit
47
39
  data?: string|Buffer|Uint8Array|object|null
48
40
  requestType?:OINOContentType
49
41
  responseType?:OINOContentType
@@ -57,7 +49,7 @@ export interface OINOHttpRequestInit extends OINORequestInit {
57
49
  export class OINOHttpRequest extends OINORequest {
58
50
  readonly url?: URL
59
51
  readonly method: string
60
- readonly headers: Record<string, string>
52
+ readonly headers: OINOHeaders
61
53
  readonly data: string|Buffer|Uint8Array|object|null
62
54
  readonly requestType:OINOContentType
63
55
  readonly responseType:OINOContentType
@@ -75,7 +67,7 @@ export class OINOHttpRequest extends OINORequest {
75
67
  super(init)
76
68
  this.url = init.url
77
69
  this.method = init.method ?? "GET"
78
- this.headers = init.headers ?? {}
70
+ this.headers = new OINOHeaders(init.headers)
79
71
  this.data = init.data ?? ""
80
72
  this.multipartBoundary = ""
81
73
  this.lastModified = init.lastModified
@@ -86,7 +78,7 @@ export class OINOHttpRequest extends OINORequest {
86
78
  if (init.requestType) {
87
79
  this.requestType = init.requestType
88
80
  } else {
89
- const request_type_param = this.url?.searchParams.get(OINO_REQUEST_TYPE_PARAM) || this.headers["content-type"] // content-type header can be overridden by query parameter
81
+ const request_type_param = this.url?.searchParams.get(OINO_REQUEST_TYPE_PARAM) || this.headers.get("content-type") // content-type header can be overridden by query parameter
90
82
  if (request_type_param == OINOContentType.csv) {
91
83
  this.requestType = OINOContentType.csv
92
84
 
@@ -105,7 +97,7 @@ export class OINOHttpRequest extends OINORequest {
105
97
  if (init.responseType) {
106
98
  this.responseType = init.responseType
107
99
  } else {
108
- const response_type_param = this.url?.searchParams.get(OINO_RESPONSE_TYPE_PARAM) || this.headers["accept"] // accept header can be overridden by query parameter
100
+ const response_type_param = this.url?.searchParams.get(OINO_RESPONSE_TYPE_PARAM) || this.headers.get("accept") // accept header can be overridden by query parameter
109
101
  const accept_types = response_type_param?.split(', ') || []
110
102
  let response_type:OINOContentType|undefined = undefined
111
103
  for (let i=0; i<accept_types.length; i++) {
@@ -116,17 +108,17 @@ export class OINOHttpRequest extends OINORequest {
116
108
  }
117
109
  this.responseType = response_type ?? OINOContentType.json
118
110
  }
119
- const last_modified = this.headers["if-modified-since"]
111
+ const last_modified = this.headers.get("if-modified-since")
120
112
  if (last_modified) {
121
113
  this.lastModified = new Date(last_modified).getTime()
122
114
  }
123
- const etags = this.headers["if-none-match"]?.split(',').map(e => e.trim())
115
+ const etags = this.headers.get("if-none-match")?.split(',').map(e => e.trim())
124
116
  if (etags) {
125
117
  this.etags = etags
126
118
  }
127
119
  }
128
120
 
129
- static async fromRequest(request: Request): Promise<OINOHttpRequest> {
121
+ static async fromFetchRequest(request: Request): Promise<OINOHttpRequest> {
130
122
  const body = await request.arrayBuffer()
131
123
  return new OINOHttpRequest({
132
124
  url: new URL(request.url),
@@ -136,4 +128,42 @@ export class OINOHttpRequest extends OINORequest {
136
128
  })
137
129
  }
138
130
 
131
+ dataAsText(): string {
132
+ if (this.data instanceof Uint8Array) {
133
+ return new TextDecoder().decode(this.data)
134
+
135
+ } else if (this.data instanceof Object) {
136
+ return JSON.stringify(this.data)
137
+
138
+ } else {
139
+ return this.data?.toString() || ""
140
+ }
141
+ }
142
+
143
+ dataAsParsedJson(): any {
144
+ return this.data ? JSON.parse(this.dataAsText()) : {}
145
+ }
146
+
147
+ dataAsFormData(): URLSearchParams {
148
+ return new URLSearchParams(this.dataAsText() || "")
149
+ }
150
+
151
+ dataAsBuffer(): Buffer {
152
+ if (this.data === null) {
153
+ return Buffer.alloc(0)
154
+
155
+ } else if (this.data instanceof Buffer) {
156
+ return this.data
157
+
158
+ } else if (this.data instanceof Uint8Array) {
159
+ return Buffer.from(this.data)
160
+
161
+ } else if (this.data instanceof Object) {
162
+ return Buffer.from(JSON.stringify(this.data), "utf-8")
163
+
164
+ } else {
165
+ return Buffer.from(this.data, "utf-8")
166
+ }
167
+ }
168
+
139
169
  }
package/src/OINOResult.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import { createHash, Hash } from "node:crypto";
8
- import { OINO_DEBUG_PREFIX, OINO_ERROR_PREFIX, OINO_INFO_PREFIX, OINO_WARNING_PREFIX } from ".";
8
+ import { OINO_DEBUG_PREFIX, OINO_ERROR_PREFIX, OINO_INFO_PREFIX, OINO_WARNING_PREFIX, OINOHeaders, OINOHeadersInit } from ".";
9
9
 
10
10
  export interface OINOResultInit {
11
11
  success?: boolean
@@ -144,24 +144,24 @@ export class OINOResult {
144
144
  * @param copyDebug wether debug messages should be copied (default false)
145
145
  *
146
146
  */
147
- copyMessagesToHeaders(headers:Headers, copyErrors:boolean = true, copyWarnings:boolean = false, copyInfos:boolean = false, copyDebug:boolean = false) {
147
+ copyMessagesToHeaders(headers:OINOHeaders, copyErrors:boolean = true, copyWarnings:boolean = false, copyInfos:boolean = false, copyDebug:boolean = false) {
148
148
  let j=1
149
149
  for(let i=0; i<this.messages.length; i++) {
150
150
  const message = this.messages[i].replaceAll("\r", " ").replaceAll("\n", " ")
151
151
  if (copyErrors && message.startsWith(OINO_ERROR_PREFIX)) {
152
- headers.append('X-OINO-MESSAGE-'+j, message)
152
+ headers.set('X-OINO-MESSAGE-'+j, message)
153
153
  j++
154
154
  }
155
155
  if (copyWarnings && message.startsWith(OINO_WARNING_PREFIX)) {
156
- headers.append('X-OINO-MESSAGE-'+j, message)
156
+ headers.set('X-OINO-MESSAGE-'+j, message)
157
157
  j++
158
158
  }
159
159
  if (copyInfos && message.startsWith(OINO_INFO_PREFIX)) {
160
- headers.append('X-OINO-MESSAGE-'+j, message)
160
+ headers.set('X-OINO-MESSAGE-'+j, message)
161
161
  j++
162
162
  }
163
163
  if (copyDebug && message.startsWith(OINO_DEBUG_PREFIX)) {
164
- headers.append('X-OINO-MESSAGE-'+j, message)
164
+ headers.set('X-OINO-MESSAGE-'+j, message)
165
165
  j++
166
166
  }
167
167
  }
@@ -178,6 +178,7 @@ export class OINOResult {
178
178
 
179
179
  export interface OINOHttpResultInit extends OINOResultInit {
180
180
  body?: string
181
+ headers?: OINOHeadersInit
181
182
  expires?: number
182
183
  lastModified?: number
183
184
  }
@@ -190,6 +191,9 @@ export class OINOHttpResult extends OINOResult {
190
191
 
191
192
  /** HTTP body data */
192
193
  readonly body: string
194
+
195
+ /** HTTP headers */
196
+ readonly headers: OINOHeaders
193
197
 
194
198
  /** HTTP cache expiration value
195
199
  * Note: default 0 means no expiration and 'Pragma: no-cache' is set.
@@ -208,6 +212,7 @@ export class OINOHttpResult extends OINOResult {
208
212
  constructor(init?: OINOHttpResultInit) {
209
213
  super(init)
210
214
  this.body = init?.body ?? ""
215
+ this.headers = new OINOHeaders(init?.headers)
211
216
  this.expires = init?.expires ?? 0
212
217
  this.lastModified = init?.lastModified ?? 0
213
218
  this._etag = ""
@@ -230,14 +235,18 @@ export class OINOHttpResult extends OINOResult {
230
235
  *
231
236
  * @param headers HTTP headers (overrides existing values)
232
237
  */
233
- getHttpResponse(headers?:Record<string, string>):Response {
234
- const result:Response = new Response(this.body, {status:this.status, statusText: this.statusText, headers: headers})
238
+ getFetchResponse(headers?:OINOHeadersInit):Response {
239
+ const merged_headers = new OINOHeaders(this.headers)
240
+ if (headers) {
241
+ merged_headers.setHeaders(headers)
242
+ }
243
+ const result: Response = new Response(this.body, { status: this.status, statusText: this.statusText, headers: merged_headers })
235
244
  result.headers.set('Content-Length', this.body.length.toString())
236
- if (this.lastModified > 0) {
245
+ if (merged_headers['Last-Modified'] === undefined && this.lastModified > 0) {
237
246
  result.headers.set('Last-Modified', new Date(this.lastModified).toUTCString())
238
247
  }
239
- if (this.expires >= 0) {
240
- result.headers.set('Expires', Math.round(this.expires).toString())
248
+ if (merged_headers['Expires'] === undefined && this.expires >= 0) {
249
+ result.headers.set('Expires', new Date(Date.now() + Math.round(this.expires)).toUTCString())
241
250
  if (this.expires == 0) {
242
251
  result.headers.set('Pragma', 'no-cache')
243
252
  }
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ export { OINORequest, OINOHttpRequest, type OINORequestInit, type OINOHttpReques
5
5
  export { OINOStr } from "./OINOStr.js"
6
6
  export { OINOHtmlTemplate } from "./OINOHtmlTemplate.js"
7
7
  export { OINOFormatter, OINO_EMPTY_FORMATTER } from "./OINOFormatter.js"
8
+ export { OINOHeaders, type OINOHeadersInit } from "./OINOHeaders.js"
8
9
 
9
10
  /** OINO error message prefix */
10
11
  export const OINO_ERROR_PREFIX = "OINO ERROR"