@orpc/standard-server 1.14.10 → 1.14.13

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/dist/index.d.mts CHANGED
@@ -19,7 +19,9 @@ interface EventDecoderOptions {
19
19
  }
20
20
  declare class EventDecoder {
21
21
  private options;
22
- private incomplete;
22
+ private pending;
23
+ private tail;
24
+ private discardLeadingLF;
23
25
  constructor(options?: EventDecoderOptions);
24
26
  feed(chunk: string): void;
25
27
  end(): void;
package/dist/index.d.ts CHANGED
@@ -19,7 +19,9 @@ interface EventDecoderOptions {
19
19
  }
20
20
  declare class EventDecoder {
21
21
  private options;
22
- private incomplete;
22
+ private pending;
23
+ private tail;
24
+ private discardLeadingLF;
23
25
  constructor(options?: EventDecoderOptions);
24
26
  feed(chunk: string): void;
25
27
  end(): void;
package/dist/index.mjs CHANGED
@@ -12,8 +12,13 @@ class ErrorEvent extends Error {
12
12
  }
13
13
  }
14
14
 
15
+ const LINE_ENDING_REGEX$1 = /\r\n|\r(?!\n)|\n/;
16
+ const MESSAGE_DELIMITER_REGEX = /(?:\r\n|\r(?!\n)|\n){2}/;
17
+ const MESSAGE_DELIMITER_GLOBAL_REGEX = /(?:\r\n|\r(?!\n)|\n){2}/g;
18
+ const CR = 13;
19
+ const LF = 10;
20
+ const SPACE = 32;
15
21
  function decodeEventMessage(encoded) {
16
- const lines = encoded.replace(/\n+$/, "").split(/\n/);
17
22
  const message = {
18
23
  data: void 0,
19
24
  event: void 0,
@@ -21,54 +26,94 @@ function decodeEventMessage(encoded) {
21
26
  retry: void 0,
22
27
  comments: []
23
28
  };
24
- for (const line of lines) {
29
+ for (const line of encoded.split(LINE_ENDING_REGEX$1)) {
30
+ if (line === "") {
31
+ continue;
32
+ }
25
33
  const index = line.indexOf(":");
26
- const key = index === -1 ? line : line.slice(0, index);
27
- const value = index === -1 ? "" : line.slice(index + 1).replace(/^\s/, "");
34
+ const value = index === -1 ? "" : line.slice(line.charCodeAt(index + 1) === SPACE ? index + 2 : index + 1);
28
35
  if (index === 0) {
29
36
  message.comments.push(value);
30
- } else if (key === "data") {
31
- message.data ??= "";
32
- message.data += `${value}
33
- `;
34
- } else if (key === "event") {
35
- message.event = value;
36
- } else if (key === "id") {
37
- message.id = value;
38
- } else if (key === "retry") {
39
- const maybeInteger = Number.parseInt(value);
40
- if (Number.isInteger(maybeInteger) && maybeInteger >= 0 && maybeInteger.toString() === value) {
41
- message.retry = maybeInteger;
37
+ continue;
38
+ }
39
+ switch (index === -1 ? line : line.slice(0, index)) {
40
+ case "data":
41
+ message.data = message.data === void 0 ? value : `${message.data}
42
+ ${value}`;
43
+ break;
44
+ case "event":
45
+ message.event = value;
46
+ break;
47
+ case "id":
48
+ message.id = value;
49
+ break;
50
+ case "retry": {
51
+ const maybeInteger = Number.parseInt(value, 10);
52
+ if (maybeInteger >= 0 && maybeInteger.toString() === value) {
53
+ message.retry = maybeInteger;
54
+ }
55
+ break;
42
56
  }
43
57
  }
44
58
  }
45
- message.data = message.data?.replace(/\n$/, "");
46
59
  return message;
47
60
  }
48
61
  class EventDecoder {
49
62
  constructor(options = {}) {
50
63
  this.options = options;
51
64
  }
52
- incomplete = "";
65
+ pending = [];
66
+ // Last up-to-3 characters of the pending buffer, prefixed to the next chunk
67
+ // so a delimiter straddling the boundary is still found.
68
+ tail = "";
69
+ // Set when a chunk-ending '\r' was already consumed as a line ending, so a
70
+ // leading '\n' in the next chunk is the second half of that CRLF pair.
71
+ discardLeadingLF = false;
53
72
  feed(chunk) {
54
- this.incomplete += chunk;
55
- const lastCompleteIndex = this.incomplete.lastIndexOf("\n\n");
56
- if (lastCompleteIndex === -1) {
73
+ if (chunk === "") {
57
74
  return;
58
75
  }
59
- const completes = this.incomplete.slice(0, lastCompleteIndex).split(/\n\n/);
60
- this.incomplete = this.incomplete.slice(lastCompleteIndex + 2);
61
- for (const encoded of completes) {
62
- const message = decodeEventMessage(`${encoded}
63
-
64
- `);
76
+ if (this.discardLeadingLF) {
77
+ this.discardLeadingLF = false;
78
+ if (chunk.charCodeAt(0) === LF) {
79
+ chunk = chunk.slice(1);
80
+ if (chunk === "") {
81
+ return;
82
+ }
83
+ }
84
+ }
85
+ const scan = this.tail + chunk;
86
+ if (!MESSAGE_DELIMITER_REGEX.test(scan)) {
87
+ this.pending.push(chunk);
88
+ this.tail = scan.slice(-3);
89
+ return;
90
+ }
91
+ this.pending.push(chunk);
92
+ const buffered = this.pending.length === 1 ? chunk : this.pending.join("");
93
+ const offset = buffered.length - scan.length;
94
+ const parts = [];
95
+ let start = 0;
96
+ for (const match of scan.matchAll(MESSAGE_DELIMITER_GLOBAL_REGEX)) {
97
+ parts.push(buffered.slice(start, offset + match.index));
98
+ start = offset + match.index + match[0].length;
99
+ }
100
+ const incomplete = buffered.slice(start);
101
+ this.pending.length = 0;
102
+ this.tail = incomplete.slice(-3);
103
+ if (incomplete === "") {
104
+ this.discardLeadingLF = chunk.charCodeAt(chunk.length - 1) === CR;
105
+ } else {
106
+ this.pending.push(incomplete);
107
+ }
108
+ for (const encoded of parts) {
109
+ const message = decodeEventMessage(encoded);
65
110
  if (this.options.onEvent) {
66
111
  this.options.onEvent(message);
67
112
  }
68
113
  }
69
114
  }
70
115
  end() {
71
- if (this.incomplete) {
116
+ if (this.pending.length !== 0) {
72
117
  throw new EventDecoderError("Event Iterator ended before complete");
73
118
  }
74
119
  }
@@ -94,14 +139,19 @@ class EventDecoderStream extends TransformStream {
94
139
  }
95
140
  }
96
141
 
142
+ const LINE_ENDING_REGEX = /\r\n|[\n\r]/;
143
+ const LINE_ENDING_GLOBAL_REGEX = /\r\n|[\n\r]/g;
144
+ function containsLineBreak(value) {
145
+ return LINE_ENDING_REGEX.test(value);
146
+ }
97
147
  function assertEventId(id) {
98
- if (id.includes("\n")) {
99
- throw new EventEncoderError("Event's id must not contain a newline character");
148
+ if (containsLineBreak(id)) {
149
+ throw new EventEncoderError("Event's id must not contain a carriage return or newline character");
100
150
  }
101
151
  }
102
152
  function assertEventName(event) {
103
- if (event.includes("\n")) {
104
- throw new EventEncoderError("Event's event must not contain a newline character");
153
+ if (containsLineBreak(event)) {
154
+ throw new EventEncoderError("Event's event must not contain a carriage return or newline character");
105
155
  }
106
156
  }
107
157
  function assertEventRetry(retry) {
@@ -110,18 +160,16 @@ function assertEventRetry(retry) {
110
160
  }
111
161
  }
112
162
  function assertEventComment(comment) {
113
- if (comment.includes("\n")) {
114
- throw new EventEncoderError("Event's comment must not contain a newline character");
163
+ if (containsLineBreak(comment)) {
164
+ throw new EventEncoderError("Event's comment must not contain a carriage return or newline character");
115
165
  }
116
166
  }
117
167
  function encodeEventData(data) {
118
- const lines = data?.split(/\n/) ?? [];
119
- let output = "";
120
- for (const line of lines) {
121
- output += `data: ${line}
122
- `;
168
+ if (data === void 0) {
169
+ return "";
123
170
  }
124
- return output;
171
+ return `data: ${data.replace(LINE_ENDING_GLOBAL_REGEX, "\ndata: ")}
172
+ `;
125
173
  }
126
174
  function encodeEventComments(comments) {
127
175
  let output = "";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@orpc/standard-server",
3
3
  "type": "module",
4
- "version": "1.14.10",
4
+ "version": "1.14.13",
5
5
  "license": "MIT",
6
6
  "homepage": "https://orpc.dev",
7
7
  "repository": {
@@ -29,7 +29,7 @@
29
29
  "dist"
30
30
  ],
31
31
  "dependencies": {
32
- "@orpc/shared": "1.14.10"
32
+ "@orpc/shared": "1.14.13"
33
33
  },
34
34
  "scripts": {
35
35
  "build": "unbuild",