@gjsify/eventsource 0.3.13 → 0.3.15
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/lib/esm/index.js +230 -227
- package/lib/esm/register.js +10 -10
- package/package.json +5 -5
package/lib/esm/index.js
CHANGED
|
@@ -1,232 +1,235 @@
|
|
|
1
1
|
import "@gjsify/web-streams/register";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
MessageEvent as DomMessageEvent
|
|
6
|
-
} from "@gjsify/dom-events";
|
|
2
|
+
import { Event, EventTarget, MessageEvent } from "@gjsify/dom-events";
|
|
3
|
+
|
|
4
|
+
//#region src/index.ts
|
|
7
5
|
const CONNECTING = 0;
|
|
8
6
|
const OPEN = 1;
|
|
9
7
|
const CLOSED = 2;
|
|
10
|
-
const _Event = typeof globalThis.Event === "function" ? globalThis.Event :
|
|
11
|
-
const _EventTarget = typeof globalThis.EventTarget === "function" ? globalThis.EventTarget :
|
|
12
|
-
const _MessageEvent = typeof globalThis.MessageEvent === "function" ? globalThis.MessageEvent :
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
static CLOSED = CLOSED;
|
|
53
|
-
CONNECTING = CONNECTING;
|
|
54
|
-
OPEN = OPEN;
|
|
55
|
-
CLOSED = CLOSED;
|
|
56
|
-
#abortController = new AbortController();
|
|
57
|
-
#reconnectionTimerId;
|
|
58
|
-
#reconnectionTime = 5e3;
|
|
59
|
-
#lastEventId = "";
|
|
60
|
-
#readyState = CONNECTING;
|
|
61
|
-
#url;
|
|
62
|
-
#withCredentials;
|
|
63
|
-
// Event handler attributes
|
|
64
|
-
onopen = null;
|
|
65
|
-
onmessage = null;
|
|
66
|
-
onerror = null;
|
|
67
|
-
constructor(url, eventSourceInitDict) {
|
|
68
|
-
super();
|
|
69
|
-
const urlStr = String(url);
|
|
70
|
-
try {
|
|
71
|
-
this.#url = new URL(urlStr).href;
|
|
72
|
-
} catch {
|
|
73
|
-
throw new DOMException(`Failed to construct 'EventSource': ${urlStr} is not a valid URL`, "SyntaxError");
|
|
74
|
-
}
|
|
75
|
-
this.#withCredentials = eventSourceInitDict?.withCredentials ?? false;
|
|
76
|
-
this.#loop();
|
|
77
|
-
}
|
|
78
|
-
get readyState() {
|
|
79
|
-
return this.#readyState;
|
|
80
|
-
}
|
|
81
|
-
get url() {
|
|
82
|
-
return this.#url;
|
|
83
|
-
}
|
|
84
|
-
get withCredentials() {
|
|
85
|
-
return this.#withCredentials;
|
|
86
|
-
}
|
|
87
|
-
close() {
|
|
88
|
-
this.#abortController.abort();
|
|
89
|
-
this.#readyState = CLOSED;
|
|
90
|
-
if (this.#reconnectionTimerId !== void 0) {
|
|
91
|
-
clearTimeout(this.#reconnectionTimerId);
|
|
92
|
-
this.#reconnectionTimerId = void 0;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
dispatchEvent(event) {
|
|
96
|
-
const type = event.type;
|
|
97
|
-
if (type === "open" && this.onopen) {
|
|
98
|
-
this.onopen.call(this, event);
|
|
99
|
-
} else if (type === "message" && this.onmessage) {
|
|
100
|
-
this.onmessage.call(this, event);
|
|
101
|
-
} else if (type === "error" && this.onerror) {
|
|
102
|
-
this.onerror.call(this, event);
|
|
103
|
-
}
|
|
104
|
-
return super.dispatchEvent(event);
|
|
105
|
-
}
|
|
106
|
-
async #loop() {
|
|
107
|
-
const headers = {
|
|
108
|
-
"Accept": "text/event-stream",
|
|
109
|
-
"Cache-Control": "no-cache"
|
|
110
|
-
};
|
|
111
|
-
if (this.#lastEventId) {
|
|
112
|
-
headers["Last-Event-ID"] = this.#lastEventId;
|
|
113
|
-
}
|
|
114
|
-
let res;
|
|
115
|
-
try {
|
|
116
|
-
res = await fetch(this.#url, {
|
|
117
|
-
headers,
|
|
118
|
-
signal: this.#abortController.signal
|
|
119
|
-
});
|
|
120
|
-
} catch {
|
|
121
|
-
this.#reestablishConnection();
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
const contentType = res.headers.get("content-type") || "";
|
|
125
|
-
if (res.status !== 200 || !contentType.toLowerCase().includes("text/event-stream")) {
|
|
126
|
-
this.#failConnection();
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
if (this.#readyState === CLOSED) {
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
this.#readyState = OPEN;
|
|
133
|
-
this.dispatchEvent(new _Event("open"));
|
|
134
|
-
let data = "";
|
|
135
|
-
let eventType = "";
|
|
136
|
-
let lastEventId = this.#lastEventId;
|
|
137
|
-
try {
|
|
138
|
-
const body = res.body;
|
|
139
|
-
if (!body) {
|
|
140
|
-
this.#reestablishConnection();
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
const lineStream = body.pipeThrough(new TextDecoderStream()).pipeThrough(new TextLineStream());
|
|
144
|
-
const reader = lineStream.getReader();
|
|
145
|
-
while (true) {
|
|
146
|
-
const { value: line, done } = await reader.read();
|
|
147
|
-
if (done) break;
|
|
148
|
-
if (line === "") {
|
|
149
|
-
this.#lastEventId = lastEventId;
|
|
150
|
-
if (data === "") {
|
|
151
|
-
eventType = "";
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
if (data.endsWith("\n")) {
|
|
155
|
-
data = data.slice(0, -1);
|
|
156
|
-
}
|
|
157
|
-
const event = new _MessageEvent(eventType || "message", {
|
|
158
|
-
data,
|
|
159
|
-
origin: this.#url,
|
|
160
|
-
lastEventId: this.#lastEventId
|
|
161
|
-
});
|
|
162
|
-
data = "";
|
|
163
|
-
eventType = "";
|
|
164
|
-
if (this.#readyState !== CLOSED) {
|
|
165
|
-
this.dispatchEvent(event);
|
|
166
|
-
}
|
|
167
|
-
} else if (line.startsWith(":")) {
|
|
168
|
-
continue;
|
|
169
|
-
} else {
|
|
170
|
-
let field = line;
|
|
171
|
-
let value = "";
|
|
172
|
-
const colonIndex = line.indexOf(":");
|
|
173
|
-
if (colonIndex !== -1) {
|
|
174
|
-
field = line.slice(0, colonIndex);
|
|
175
|
-
value = line.slice(colonIndex + 1);
|
|
176
|
-
if (value.startsWith(" ")) {
|
|
177
|
-
value = value.slice(1);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
switch (field) {
|
|
181
|
-
case "event":
|
|
182
|
-
eventType = value;
|
|
183
|
-
break;
|
|
184
|
-
case "data":
|
|
185
|
-
data += value + "\n";
|
|
186
|
-
break;
|
|
187
|
-
case "id":
|
|
188
|
-
if (!value.includes("\0")) {
|
|
189
|
-
lastEventId = value;
|
|
190
|
-
}
|
|
191
|
-
break;
|
|
192
|
-
case "retry": {
|
|
193
|
-
const ms = Number(value);
|
|
194
|
-
if (!Number.isNaN(ms) && Number.isFinite(ms)) {
|
|
195
|
-
this.#reconnectionTime = ms;
|
|
196
|
-
}
|
|
197
|
-
break;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
} catch {
|
|
203
|
-
}
|
|
204
|
-
this.#reestablishConnection();
|
|
205
|
-
}
|
|
206
|
-
#reestablishConnection() {
|
|
207
|
-
if (this.#readyState === CLOSED) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
this.#readyState = CONNECTING;
|
|
211
|
-
this.dispatchEvent(new _Event("error"));
|
|
212
|
-
this.#reconnectionTimerId = setTimeout(() => {
|
|
213
|
-
if (this.#readyState !== CONNECTING) {
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
this.#abortController = new AbortController();
|
|
217
|
-
this.#loop();
|
|
218
|
-
}, this.#reconnectionTime);
|
|
219
|
-
}
|
|
220
|
-
#failConnection() {
|
|
221
|
-
if (this.#readyState !== CLOSED) {
|
|
222
|
-
this.#readyState = CLOSED;
|
|
223
|
-
this.dispatchEvent(new _Event("error"));
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
var index_default = EventSource;
|
|
228
|
-
export {
|
|
229
|
-
EventSource,
|
|
230
|
-
TextLineStream,
|
|
231
|
-
index_default as default
|
|
8
|
+
const _Event = typeof globalThis.Event === "function" ? globalThis.Event : Event;
|
|
9
|
+
const _EventTarget = typeof globalThis.EventTarget === "function" ? globalThis.EventTarget : EventTarget;
|
|
10
|
+
const _MessageEvent = typeof globalThis.MessageEvent === "function" ? globalThis.MessageEvent : MessageEvent;
|
|
11
|
+
/**
|
|
12
|
+
* TextLineStream splits a string stream into individual lines.
|
|
13
|
+
* Handles \n, \r\n, and standalone \r line endings.
|
|
14
|
+
*/
|
|
15
|
+
var TextLineStream = class extends TransformStream {
|
|
16
|
+
#buf = "";
|
|
17
|
+
constructor() {
|
|
18
|
+
super({
|
|
19
|
+
transform: (chunk, controller) => this.#handle(chunk, controller),
|
|
20
|
+
flush: (controller) => {
|
|
21
|
+
if (this.#buf.length > 0) {
|
|
22
|
+
controller.enqueue(this.#buf);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
#handle(chunk, controller) {
|
|
28
|
+
chunk = this.#buf + chunk;
|
|
29
|
+
for (;;) {
|
|
30
|
+
const lfIndex = chunk.indexOf("\n");
|
|
31
|
+
if (lfIndex !== -1) {
|
|
32
|
+
let crOrLfIndex = lfIndex;
|
|
33
|
+
if (chunk[lfIndex - 1] === "\r") {
|
|
34
|
+
crOrLfIndex--;
|
|
35
|
+
}
|
|
36
|
+
controller.enqueue(chunk.slice(0, crOrLfIndex));
|
|
37
|
+
chunk = chunk.slice(lfIndex + 1);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const crIndex = chunk.indexOf("\r");
|
|
41
|
+
if (crIndex !== -1 && crIndex !== chunk.length - 1) {
|
|
42
|
+
controller.enqueue(chunk.slice(0, crIndex));
|
|
43
|
+
chunk = chunk.slice(crIndex + 1);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
this.#buf = chunk;
|
|
49
|
+
}
|
|
232
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* EventSource — W3C Server-Sent Events API.
|
|
53
|
+
*
|
|
54
|
+
* Connects to an SSE endpoint via fetch, pipes the response through
|
|
55
|
+
* TextDecoderStream and TextLineStream, then parses SSE fields.
|
|
56
|
+
*/
|
|
57
|
+
var EventSource = class extends _EventTarget {
|
|
58
|
+
static CONNECTING = CONNECTING;
|
|
59
|
+
static OPEN = OPEN;
|
|
60
|
+
static CLOSED = CLOSED;
|
|
61
|
+
CONNECTING = CONNECTING;
|
|
62
|
+
OPEN = OPEN;
|
|
63
|
+
CLOSED = CLOSED;
|
|
64
|
+
#abortController = new AbortController();
|
|
65
|
+
#reconnectionTimerId;
|
|
66
|
+
#reconnectionTime = 5e3;
|
|
67
|
+
#lastEventId = "";
|
|
68
|
+
#readyState = CONNECTING;
|
|
69
|
+
#url;
|
|
70
|
+
#withCredentials;
|
|
71
|
+
onopen = null;
|
|
72
|
+
onmessage = null;
|
|
73
|
+
onerror = null;
|
|
74
|
+
constructor(url, eventSourceInitDict) {
|
|
75
|
+
super();
|
|
76
|
+
const urlStr = String(url);
|
|
77
|
+
try {
|
|
78
|
+
this.#url = new URL(urlStr).href;
|
|
79
|
+
} catch {
|
|
80
|
+
throw new DOMException(`Failed to construct 'EventSource': ${urlStr} is not a valid URL`, "SyntaxError");
|
|
81
|
+
}
|
|
82
|
+
this.#withCredentials = eventSourceInitDict?.withCredentials ?? false;
|
|
83
|
+
this.#loop();
|
|
84
|
+
}
|
|
85
|
+
get readyState() {
|
|
86
|
+
return this.#readyState;
|
|
87
|
+
}
|
|
88
|
+
get url() {
|
|
89
|
+
return this.#url;
|
|
90
|
+
}
|
|
91
|
+
get withCredentials() {
|
|
92
|
+
return this.#withCredentials;
|
|
93
|
+
}
|
|
94
|
+
close() {
|
|
95
|
+
this.#abortController.abort();
|
|
96
|
+
this.#readyState = CLOSED;
|
|
97
|
+
if (this.#reconnectionTimerId !== undefined) {
|
|
98
|
+
clearTimeout(this.#reconnectionTimerId);
|
|
99
|
+
this.#reconnectionTimerId = undefined;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
dispatchEvent(event) {
|
|
103
|
+
const type = event.type;
|
|
104
|
+
if (type === "open" && this.onopen) {
|
|
105
|
+
this.onopen.call(this, event);
|
|
106
|
+
} else if (type === "message" && this.onmessage) {
|
|
107
|
+
this.onmessage.call(this, event);
|
|
108
|
+
} else if (type === "error" && this.onerror) {
|
|
109
|
+
this.onerror.call(this, event);
|
|
110
|
+
}
|
|
111
|
+
return super.dispatchEvent(event);
|
|
112
|
+
}
|
|
113
|
+
async #loop() {
|
|
114
|
+
const headers = {
|
|
115
|
+
"Accept": "text/event-stream",
|
|
116
|
+
"Cache-Control": "no-cache"
|
|
117
|
+
};
|
|
118
|
+
if (this.#lastEventId) {
|
|
119
|
+
headers["Last-Event-ID"] = this.#lastEventId;
|
|
120
|
+
}
|
|
121
|
+
let res;
|
|
122
|
+
try {
|
|
123
|
+
res = await fetch(this.#url, {
|
|
124
|
+
headers,
|
|
125
|
+
signal: this.#abortController.signal
|
|
126
|
+
});
|
|
127
|
+
} catch {
|
|
128
|
+
this.#reestablishConnection();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const contentType = res.headers.get("content-type") || "";
|
|
132
|
+
if (res.status !== 200 || !contentType.toLowerCase().includes("text/event-stream")) {
|
|
133
|
+
this.#failConnection();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (this.#readyState === CLOSED) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
this.#readyState = OPEN;
|
|
140
|
+
this.dispatchEvent(new _Event("open"));
|
|
141
|
+
let data = "";
|
|
142
|
+
let eventType = "";
|
|
143
|
+
let lastEventId = this.#lastEventId;
|
|
144
|
+
try {
|
|
145
|
+
const body = res.body;
|
|
146
|
+
if (!body) {
|
|
147
|
+
this.#reestablishConnection();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const lineStream = body.pipeThrough(new TextDecoderStream()).pipeThrough(new TextLineStream());
|
|
151
|
+
const reader = lineStream.getReader();
|
|
152
|
+
while (true) {
|
|
153
|
+
const { value: line, done } = await reader.read();
|
|
154
|
+
if (done) break;
|
|
155
|
+
if (line === "") {
|
|
156
|
+
this.#lastEventId = lastEventId;
|
|
157
|
+
if (data === "") {
|
|
158
|
+
eventType = "";
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (data.endsWith("\n")) {
|
|
162
|
+
data = data.slice(0, -1);
|
|
163
|
+
}
|
|
164
|
+
const event = new _MessageEvent(eventType || "message", {
|
|
165
|
+
data,
|
|
166
|
+
origin: this.#url,
|
|
167
|
+
lastEventId: this.#lastEventId
|
|
168
|
+
});
|
|
169
|
+
data = "";
|
|
170
|
+
eventType = "";
|
|
171
|
+
if (this.#readyState !== CLOSED) {
|
|
172
|
+
this.dispatchEvent(event);
|
|
173
|
+
}
|
|
174
|
+
} else if (line.startsWith(":")) {
|
|
175
|
+
continue;
|
|
176
|
+
} else {
|
|
177
|
+
let field = line;
|
|
178
|
+
let value = "";
|
|
179
|
+
const colonIndex = line.indexOf(":");
|
|
180
|
+
if (colonIndex !== -1) {
|
|
181
|
+
field = line.slice(0, colonIndex);
|
|
182
|
+
value = line.slice(colonIndex + 1);
|
|
183
|
+
if (value.startsWith(" ")) {
|
|
184
|
+
value = value.slice(1);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
switch (field) {
|
|
188
|
+
case "event":
|
|
189
|
+
eventType = value;
|
|
190
|
+
break;
|
|
191
|
+
case "data":
|
|
192
|
+
data += value + "\n";
|
|
193
|
+
break;
|
|
194
|
+
case "id":
|
|
195
|
+
if (!value.includes("\0")) {
|
|
196
|
+
lastEventId = value;
|
|
197
|
+
}
|
|
198
|
+
break;
|
|
199
|
+
case "retry": {
|
|
200
|
+
const ms = Number(value);
|
|
201
|
+
if (!Number.isNaN(ms) && Number.isFinite(ms)) {
|
|
202
|
+
this.#reconnectionTime = ms;
|
|
203
|
+
}
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
} catch {}
|
|
210
|
+
this.#reestablishConnection();
|
|
211
|
+
}
|
|
212
|
+
#reestablishConnection() {
|
|
213
|
+
if (this.#readyState === CLOSED) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
this.#readyState = CONNECTING;
|
|
217
|
+
this.dispatchEvent(new _Event("error"));
|
|
218
|
+
this.#reconnectionTimerId = setTimeout(() => {
|
|
219
|
+
if (this.#readyState !== CONNECTING) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
this.#abortController = new AbortController();
|
|
223
|
+
this.#loop();
|
|
224
|
+
}, this.#reconnectionTime);
|
|
225
|
+
}
|
|
226
|
+
#failConnection() {
|
|
227
|
+
if (this.#readyState !== CLOSED) {
|
|
228
|
+
this.#readyState = CLOSED;
|
|
229
|
+
this.dispatchEvent(new _Event("error"));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
//#endregion
|
|
235
|
+
export { EventSource, EventSource as default, TextLineStream };
|
package/lib/esm/register.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "@gjsify/dom-events";
|
|
6
|
-
import EventSource from "./index.js";
|
|
1
|
+
import { EventSource } from "./index.js";
|
|
2
|
+
import { Event, EventTarget, MessageEvent } from "@gjsify/dom-events";
|
|
3
|
+
|
|
4
|
+
//#region src/register.ts
|
|
7
5
|
if (typeof globalThis.Event === "undefined") {
|
|
8
|
-
|
|
6
|
+
globalThis.Event = Event;
|
|
9
7
|
}
|
|
10
8
|
if (typeof globalThis.EventTarget === "undefined") {
|
|
11
|
-
|
|
9
|
+
globalThis.EventTarget = EventTarget;
|
|
12
10
|
}
|
|
13
11
|
if (typeof globalThis.MessageEvent === "undefined") {
|
|
14
|
-
|
|
12
|
+
globalThis.MessageEvent = MessageEvent;
|
|
15
13
|
}
|
|
16
14
|
if (typeof globalThis.EventSource === "undefined") {
|
|
17
|
-
|
|
15
|
+
globalThis.EventSource = EventSource;
|
|
18
16
|
}
|
|
17
|
+
|
|
18
|
+
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/eventsource",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.15",
|
|
4
4
|
"description": "W3C EventSource (Server-Sent Events) for GJS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "lib/esm/index.js",
|
|
@@ -39,12 +39,12 @@
|
|
|
39
39
|
"server-sent-events"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@gjsify/dom-events": "^0.3.
|
|
43
|
-
"@gjsify/web-streams": "^0.3.
|
|
42
|
+
"@gjsify/dom-events": "^0.3.15",
|
|
43
|
+
"@gjsify/web-streams": "^0.3.15"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@gjsify/cli": "^0.3.
|
|
47
|
-
"@gjsify/unit": "^0.3.
|
|
46
|
+
"@gjsify/cli": "^0.3.15",
|
|
47
|
+
"@gjsify/unit": "^0.3.15",
|
|
48
48
|
"@types/node": "^25.6.0",
|
|
49
49
|
"typescript": "^6.0.3"
|
|
50
50
|
}
|