@serwist/streams 8.4.4 → 9.0.0-preview.1
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/_types.d.ts +1 -0
- package/dist/_types.d.ts.map +1 -0
- package/dist/concatenate.d.ts +1 -0
- package/dist/concatenate.d.ts.map +1 -0
- package/dist/concatenateToResponse.d.ts +1 -0
- package/dist/concatenateToResponse.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -85
- package/dist/isSupported.d.ts +1 -0
- package/dist/isSupported.d.ts.map +1 -0
- package/dist/strategy.d.ts +1 -0
- package/dist/strategy.d.ts.map +1 -0
- package/dist/utils/createHeaders.d.ts +1 -0
- package/dist/utils/createHeaders.d.ts.map +1 -0
- package/package.json +19 -17
- package/src/_types.ts +9 -0
- package/src/concatenate.ts +131 -0
- package/src/concatenateToResponse.ts +36 -0
- package/{dist/index.d.cts → src/index.ts} +11 -0
- package/src/isSupported.ts +24 -0
- package/src/strategy.ts +75 -0
- package/src/utils/createHeaders.ts +29 -0
- package/dist/index.cjs +0 -233
package/dist/_types.d.ts
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"_types.d.ts","sourceRoot":"","sources":["../src/_types.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,cAAc,GAAG,QAAQ,CAAC"}
|
package/dist/concatenate.d.ts
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"concatenate.d.ts","sourceRoot":"","sources":["../src/concatenate.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAyBhD;;;;;;;;;;GAUG;AACH,iBAAS,WAAW,CAAC,cAAc,EAAE,OAAO,CAAC,YAAY,CAAC,EAAE,GAAG;IAC7D,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC;CACxB,CA+EA;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"concatenateToResponse.d.ts","sourceRoot":"","sources":["../src/concatenateToResponse.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIhD;;;;;;;;;;;;;GAaG;AACH,iBAAS,qBAAqB,CAAC,cAAc,EAAE,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,WAAW,EAAE,WAAW,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAO7I;AAED,OAAO,EAAE,qBAAqB,EAAE,CAAC"}
|
package/dist/index.d.ts
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,WAAW,EAAE,qBAAqB,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;AAErE,YAAY,EAAE,sBAAsB,EAAE,CAAC;AAEvC,cAAc,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
@@ -1,16 +1,7 @@
|
|
1
1
|
import { assert, Deferred, logger, SerwistError, canConstructReadableStream } from '@serwist/core/internal';
|
2
2
|
|
3
|
-
|
4
|
-
* Takes either a Response, a ReadableStream, or a
|
5
|
-
* [BodyInit](https://fetch.spec.whatwg.org/#bodyinit) and returns the
|
6
|
-
* ReadableStreamReader object associated with it.
|
7
|
-
*
|
8
|
-
* @param source
|
9
|
-
* @returns
|
10
|
-
* @private
|
11
|
-
*/ function _getReaderFromSource(source) {
|
3
|
+
function _getReaderFromSource(source) {
|
12
4
|
if (source instanceof Response) {
|
13
|
-
// See https://github.com/GoogleChrome/workbox/issues/2998
|
14
5
|
if (source.body) {
|
15
6
|
return source.body.getReader();
|
16
7
|
}
|
@@ -23,17 +14,7 @@ import { assert, Deferred, logger, SerwistError, canConstructReadableStream } fr
|
|
23
14
|
}
|
24
15
|
return new Response(source).body.getReader();
|
25
16
|
}
|
26
|
-
|
27
|
-
* Takes multiple source Promises, each of which could resolve to a Response, a
|
28
|
-
* ReadableStream, or a [BodyInit](https://fetch.spec.whatwg.org/#bodyinit).
|
29
|
-
*
|
30
|
-
* Returns an object exposing a ReadableStream with each individual stream's
|
31
|
-
* data returned in sequence, along with a Promise which signals when the
|
32
|
-
* stream is finished (useful for passing to a FetchEvent's waitUntil()).
|
33
|
-
*
|
34
|
-
* @param sourcePromises
|
35
|
-
* @returns
|
36
|
-
*/ function concatenate(sourcePromises) {
|
17
|
+
function concatenate(sourcePromises) {
|
37
18
|
if (process.env.NODE_ENV !== "production") {
|
38
19
|
assert.isArray(sourcePromises, {
|
39
20
|
moduleName: "@serwist/streams",
|
@@ -66,7 +47,6 @@ import { assert, Deferred, logger, SerwistError, canConstructReadableStream } fr
|
|
66
47
|
}
|
67
48
|
i++;
|
68
49
|
if (i >= readerPromises.length) {
|
69
|
-
// Log all the messages in the group at once in a single group.
|
70
50
|
if (process.env.NODE_ENV !== "production") {
|
71
51
|
logger.groupCollapsed(`Concatenating ${readerPromises.length} sources.`);
|
72
52
|
for (const message of logMessages){
|
@@ -83,7 +63,6 @@ import { assert, Deferred, logger, SerwistError, canConstructReadableStream } fr
|
|
83
63
|
streamDeferred.resolve();
|
84
64
|
return;
|
85
65
|
}
|
86
|
-
// The `pull` method is defined because we're inside it.
|
87
66
|
return this.pull(controller);
|
88
67
|
}
|
89
68
|
controller.enqueue(result?.value);
|
@@ -108,24 +87,7 @@ import { assert, Deferred, logger, SerwistError, canConstructReadableStream } fr
|
|
108
87
|
};
|
109
88
|
}
|
110
89
|
|
111
|
-
|
112
|
-
Copyright 2018 Google LLC
|
113
|
-
|
114
|
-
Use of this source code is governed by an MIT-style
|
115
|
-
license that can be found in the LICENSE file or at
|
116
|
-
https://opensource.org/licenses/MIT.
|
117
|
-
*/ /**
|
118
|
-
* This is a utility method that determines whether the current browser supports
|
119
|
-
* the features required to create streamed responses. Currently, it checks if
|
120
|
-
* [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream)
|
121
|
-
* is available.
|
122
|
-
*
|
123
|
-
* @private
|
124
|
-
* @param headersInit If there's no `Content-Type` specified, `'text/html'` will be used by default.
|
125
|
-
* @returns `true`, if the current browser meets the requirements for
|
126
|
-
* streaming responses, and `false` otherwise.
|
127
|
-
*/ function createHeaders(headersInit = {}) {
|
128
|
-
// See https://github.com/GoogleChrome/workbox/issues/1461
|
90
|
+
function createHeaders(headersInit = {}) {
|
129
91
|
const headers = new Headers(headersInit);
|
130
92
|
if (!headers.has("content-type")) {
|
131
93
|
headers.set("content-type", "text/html");
|
@@ -133,20 +95,7 @@ import { assert, Deferred, logger, SerwistError, canConstructReadableStream } fr
|
|
133
95
|
return headers;
|
134
96
|
}
|
135
97
|
|
136
|
-
|
137
|
-
* Takes multiple source Promises, each of which could resolve to a Response, a
|
138
|
-
* ReadableStream, or a [BodyInit](https://fetch.spec.whatwg.org/#bodyinit),
|
139
|
-
* along with a
|
140
|
-
* [HeadersInit](https://fetch.spec.whatwg.org/#typedefdef-headersinit).
|
141
|
-
*
|
142
|
-
* Returns an object exposing a Response whose body consists of each individual
|
143
|
-
* stream's data returned in sequence, along with a Promise which signals when
|
144
|
-
* the stream is finished (useful for passing to a FetchEvent's waitUntil()).
|
145
|
-
*
|
146
|
-
* @param sourcePromises
|
147
|
-
* @param headersInit If there's no `Content-Type` specified,`'text/html'` will be used by default.
|
148
|
-
* @returns
|
149
|
-
*/ function concatenateToResponse(sourcePromises, headersInit) {
|
98
|
+
function concatenateToResponse(sourcePromises, headersInit) {
|
150
99
|
const { done, stream } = concatenate(sourcePromises);
|
151
100
|
const headers = createHeaders(headersInit);
|
152
101
|
const response = new Response(stream, {
|
@@ -158,33 +107,13 @@ import { assert, Deferred, logger, SerwistError, canConstructReadableStream } fr
|
|
158
107
|
};
|
159
108
|
}
|
160
109
|
|
161
|
-
|
162
|
-
* This is a utility method that determines whether the current browser supports
|
163
|
-
* the features required to create streamed responses. Currently, it checks if
|
164
|
-
* [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream)
|
165
|
-
* can be created.
|
166
|
-
*
|
167
|
-
* @returns `true`, if the current browser meets the requirements for
|
168
|
-
* streaming responses, and `false` otherwise.
|
169
|
-
*/ function isSupported() {
|
110
|
+
function isSupported() {
|
170
111
|
return canConstructReadableStream();
|
171
112
|
}
|
172
113
|
|
173
|
-
|
174
|
-
* A shortcut to create a strategy that could be dropped-in to Serwist's router.
|
175
|
-
*
|
176
|
-
* On browsers that do not support constructing new `ReadableStream`s, this
|
177
|
-
* strategy will automatically wait for all the `sourceFunctions` to complete,
|
178
|
-
* and create a final response that concatenates their values together.
|
179
|
-
*
|
180
|
-
* @param sourceFunctions An array of functions similar to `@serwist/routing.handlerCallback`
|
181
|
-
* but that instead return a `@serwist/streams.StreamSource` (or a Promise which resolves to one).
|
182
|
-
* @param headersInit If there's no `Content-Type` specified, `'text/html'` will be used by default.
|
183
|
-
* @returns
|
184
|
-
*/ function strategy(sourceFunctions, headersInit) {
|
114
|
+
function strategy(sourceFunctions, headersInit) {
|
185
115
|
return async ({ event, request, url, params })=>{
|
186
116
|
const sourcePromises = sourceFunctions.map((fn)=>{
|
187
|
-
// Ensure the return value of the function is always a promise.
|
188
117
|
return Promise.resolve(fn({
|
189
118
|
event,
|
190
119
|
request,
|
@@ -202,23 +131,15 @@ import { assert, Deferred, logger, SerwistError, canConstructReadableStream } fr
|
|
202
131
|
if (process.env.NODE_ENV !== "production") {
|
203
132
|
logger.log(`The current browser doesn't support creating response ` + "streams. Falling back to non-streaming response instead.");
|
204
133
|
}
|
205
|
-
// Fallback to waiting for everything to finish, and concatenating the
|
206
|
-
// responses.
|
207
134
|
const blobPartsPromises = sourcePromises.map(async (sourcePromise)=>{
|
208
135
|
const source = await sourcePromise;
|
209
136
|
if (source instanceof Response) {
|
210
137
|
return source.blob();
|
211
138
|
}
|
212
|
-
// Technically, a `StreamSource` object can include any valid
|
213
|
-
// `BodyInit` type, including `FormData` and `URLSearchParams`, which
|
214
|
-
// cannot be passed to the Blob constructor directly, so we have to
|
215
|
-
// convert them to actual Blobs first.
|
216
139
|
return new Response(source).blob();
|
217
140
|
});
|
218
141
|
const blobParts = await Promise.all(blobPartsPromises);
|
219
142
|
const headers = createHeaders(headersInit);
|
220
|
-
// Constructing a new Response from a Blob source is well-supported.
|
221
|
-
// So is constructing a new Blob from multiple source Blobs or strings.
|
222
143
|
return new Response(new Blob(blobParts), {
|
223
144
|
headers
|
224
145
|
});
|
package/dist/isSupported.d.ts
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"isSupported.d.ts","sourceRoot":"","sources":["../src/isSupported.ts"],"names":[],"mappings":"AAUA;;;;;;;;GAQG;AACH,iBAAS,WAAW,IAAI,OAAO,CAE9B;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
package/dist/strategy.d.ts
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"strategy.d.ts","sourceRoot":"","sources":["../src/strategy.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAGvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,MAAM,WAAW,sBAAsB;IACrC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,2BAA2B,GAAG,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;CACtG;AAED;;;;;;;;;;;GAWG;AACH,iBAAS,QAAQ,CAAC,eAAe,EAAE,sBAAsB,EAAE,EAAE,WAAW,EAAE,WAAW,GAAG,oBAAoB,CAwC3G;AAED,OAAO,EAAE,QAAQ,EAAE,CAAC"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"createHeaders.d.ts","sourceRoot":"","sources":["../../src/utils/createHeaders.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;GAUG;AACH,iBAAS,aAAa,CAAC,WAAW,KAAK,GAAG,OAAO,CAOhD;AAED,OAAO,EAAE,aAAa,EAAE,CAAC"}
|
package/package.json
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
{
|
2
2
|
"name": "@serwist/streams",
|
3
|
-
"version": "
|
3
|
+
"version": "9.0.0-preview.1",
|
4
4
|
"type": "module",
|
5
5
|
"description": "A library that makes it easier to work with Streams in the browser.",
|
6
6
|
"files": [
|
7
|
-
"
|
8
|
-
"
|
7
|
+
"src",
|
8
|
+
"dist"
|
9
9
|
],
|
10
10
|
"keywords": [
|
11
11
|
"serwist",
|
@@ -20,29 +20,31 @@
|
|
20
20
|
"repository": "serwist/serwist",
|
21
21
|
"bugs": "https://github.com/serwist/serwist/issues",
|
22
22
|
"homepage": "https://serwist.pages.dev",
|
23
|
-
"
|
24
|
-
"main": "./dist/index.cjs",
|
23
|
+
"main": "./dist/index.js",
|
25
24
|
"types": "./dist/index.d.ts",
|
26
25
|
"exports": {
|
27
26
|
".": {
|
28
|
-
"
|
29
|
-
|
30
|
-
"default": "./dist/index.js"
|
31
|
-
},
|
32
|
-
"require": {
|
33
|
-
"types": "./dist/index.d.cts",
|
34
|
-
"default": "./dist/index.cjs"
|
35
|
-
}
|
27
|
+
"types": "./dist/index.d.ts",
|
28
|
+
"default": "./dist/index.js"
|
36
29
|
},
|
37
30
|
"./package.json": "./package.json"
|
38
31
|
},
|
39
32
|
"dependencies": {
|
40
|
-
"@serwist/core": "
|
41
|
-
"@serwist/routing": "
|
33
|
+
"@serwist/core": "9.0.0-preview.1",
|
34
|
+
"@serwist/routing": "9.0.0-preview.1"
|
42
35
|
},
|
43
36
|
"devDependencies": {
|
44
|
-
"rollup": "4.9.
|
45
|
-
"
|
37
|
+
"rollup": "4.9.6",
|
38
|
+
"typescript": "5.4.0-dev.20240203",
|
39
|
+
"@serwist/constants": "9.0.0-preview.1"
|
40
|
+
},
|
41
|
+
"peerDependencies": {
|
42
|
+
"typescript": ">=5.0.0"
|
43
|
+
},
|
44
|
+
"peerDependenciesMeta": {
|
45
|
+
"typescript": {
|
46
|
+
"optional": true
|
47
|
+
}
|
46
48
|
},
|
47
49
|
"scripts": {
|
48
50
|
"build": "rimraf dist && cross-env NODE_ENV=production rollup --config rollup.config.js",
|
package/src/_types.ts
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
/*
|
2
|
+
Copyright 2018 Google LLC
|
3
|
+
|
4
|
+
Use of this source code is governed by an MIT-style
|
5
|
+
license that can be found in the LICENSE file or at
|
6
|
+
https://opensource.org/licenses/MIT.
|
7
|
+
*/
|
8
|
+
|
9
|
+
import { assert, Deferred, SerwistError, logger } from "@serwist/core/internal";
|
10
|
+
|
11
|
+
import type { StreamSource } from "./_types.js";
|
12
|
+
|
13
|
+
/**
|
14
|
+
* Takes either a Response, a ReadableStream, or a
|
15
|
+
* [BodyInit](https://fetch.spec.whatwg.org/#bodyinit) and returns the
|
16
|
+
* ReadableStreamReader object associated with it.
|
17
|
+
*
|
18
|
+
* @param source
|
19
|
+
* @returns
|
20
|
+
* @private
|
21
|
+
*/
|
22
|
+
function _getReaderFromSource(source: StreamSource): ReadableStreamReader<unknown> {
|
23
|
+
if (source instanceof Response) {
|
24
|
+
// See https://github.com/GoogleChrome/workbox/issues/2998
|
25
|
+
if (source.body) {
|
26
|
+
return source.body.getReader();
|
27
|
+
}
|
28
|
+
throw new SerwistError("opaque-streams-source", { type: source.type });
|
29
|
+
}
|
30
|
+
if (source instanceof ReadableStream) {
|
31
|
+
return source.getReader();
|
32
|
+
}
|
33
|
+
return new Response(source as BodyInit).body!.getReader();
|
34
|
+
}
|
35
|
+
|
36
|
+
/**
|
37
|
+
* Takes multiple source Promises, each of which could resolve to a Response, a
|
38
|
+
* ReadableStream, or a [BodyInit](https://fetch.spec.whatwg.org/#bodyinit).
|
39
|
+
*
|
40
|
+
* Returns an object exposing a ReadableStream with each individual stream's
|
41
|
+
* data returned in sequence, along with a Promise which signals when the
|
42
|
+
* stream is finished (useful for passing to a FetchEvent's waitUntil()).
|
43
|
+
*
|
44
|
+
* @param sourcePromises
|
45
|
+
* @returns
|
46
|
+
*/
|
47
|
+
function concatenate(sourcePromises: Promise<StreamSource>[]): {
|
48
|
+
done: Promise<void>;
|
49
|
+
stream: ReadableStream;
|
50
|
+
} {
|
51
|
+
if (process.env.NODE_ENV !== "production") {
|
52
|
+
assert!.isArray(sourcePromises, {
|
53
|
+
moduleName: "@serwist/streams",
|
54
|
+
funcName: "concatenate",
|
55
|
+
paramName: "sourcePromises",
|
56
|
+
});
|
57
|
+
}
|
58
|
+
|
59
|
+
const readerPromises = sourcePromises.map((sourcePromise) => {
|
60
|
+
return Promise.resolve(sourcePromise).then((source) => {
|
61
|
+
return _getReaderFromSource(source);
|
62
|
+
});
|
63
|
+
});
|
64
|
+
|
65
|
+
const streamDeferred: Deferred<void> = new Deferred();
|
66
|
+
|
67
|
+
let i = 0;
|
68
|
+
const logMessages: any[] = [];
|
69
|
+
const stream = new ReadableStream({
|
70
|
+
pull(controller: ReadableStreamDefaultController<any>) {
|
71
|
+
return readerPromises[i]
|
72
|
+
.then((reader) => {
|
73
|
+
if (reader instanceof ReadableStreamDefaultReader) {
|
74
|
+
return reader.read();
|
75
|
+
}
|
76
|
+
return;
|
77
|
+
})
|
78
|
+
.then((result) => {
|
79
|
+
if (result?.done) {
|
80
|
+
if (process.env.NODE_ENV !== "production") {
|
81
|
+
logMessages.push(["Reached the end of source:", sourcePromises[i]]);
|
82
|
+
}
|
83
|
+
|
84
|
+
i++;
|
85
|
+
if (i >= readerPromises.length) {
|
86
|
+
// Log all the messages in the group at once in a single group.
|
87
|
+
if (process.env.NODE_ENV !== "production") {
|
88
|
+
logger.groupCollapsed(`Concatenating ${readerPromises.length} sources.`);
|
89
|
+
for (const message of logMessages) {
|
90
|
+
if (Array.isArray(message)) {
|
91
|
+
logger.log(...message);
|
92
|
+
} else {
|
93
|
+
logger.log(message);
|
94
|
+
}
|
95
|
+
}
|
96
|
+
logger.log("Finished reading all sources.");
|
97
|
+
logger.groupEnd();
|
98
|
+
}
|
99
|
+
|
100
|
+
controller.close();
|
101
|
+
streamDeferred.resolve();
|
102
|
+
return;
|
103
|
+
}
|
104
|
+
|
105
|
+
// The `pull` method is defined because we're inside it.
|
106
|
+
return this.pull!(controller);
|
107
|
+
}
|
108
|
+
controller.enqueue(result?.value);
|
109
|
+
})
|
110
|
+
.catch((error) => {
|
111
|
+
if (process.env.NODE_ENV !== "production") {
|
112
|
+
logger.error("An error occurred:", error);
|
113
|
+
}
|
114
|
+
streamDeferred.reject(error);
|
115
|
+
throw error;
|
116
|
+
});
|
117
|
+
},
|
118
|
+
|
119
|
+
cancel() {
|
120
|
+
if (process.env.NODE_ENV !== "production") {
|
121
|
+
logger.warn("The ReadableStream was cancelled.");
|
122
|
+
}
|
123
|
+
|
124
|
+
streamDeferred.resolve();
|
125
|
+
},
|
126
|
+
});
|
127
|
+
|
128
|
+
return { done: streamDeferred.promise, stream };
|
129
|
+
}
|
130
|
+
|
131
|
+
export { concatenate };
|
@@ -0,0 +1,36 @@
|
|
1
|
+
/*
|
2
|
+
Copyright 2018 Google LLC
|
3
|
+
|
4
|
+
Use of this source code is governed by an MIT-style
|
5
|
+
license that can be found in the LICENSE file or at
|
6
|
+
https://opensource.org/licenses/MIT.
|
7
|
+
*/
|
8
|
+
|
9
|
+
import type { StreamSource } from "./_types.js";
|
10
|
+
import { concatenate } from "./concatenate.js";
|
11
|
+
import { createHeaders } from "./utils/createHeaders.js";
|
12
|
+
|
13
|
+
/**
|
14
|
+
* Takes multiple source Promises, each of which could resolve to a Response, a
|
15
|
+
* ReadableStream, or a [BodyInit](https://fetch.spec.whatwg.org/#bodyinit),
|
16
|
+
* along with a
|
17
|
+
* [HeadersInit](https://fetch.spec.whatwg.org/#typedefdef-headersinit).
|
18
|
+
*
|
19
|
+
* Returns an object exposing a Response whose body consists of each individual
|
20
|
+
* stream's data returned in sequence, along with a Promise which signals when
|
21
|
+
* the stream is finished (useful for passing to a FetchEvent's waitUntil()).
|
22
|
+
*
|
23
|
+
* @param sourcePromises
|
24
|
+
* @param headersInit If there's no `Content-Type` specified,`'text/html'` will be used by default.
|
25
|
+
* @returns
|
26
|
+
*/
|
27
|
+
function concatenateToResponse(sourcePromises: Promise<StreamSource>[], headersInit: HeadersInit): { done: Promise<void>; response: Response } {
|
28
|
+
const { done, stream } = concatenate(sourcePromises);
|
29
|
+
|
30
|
+
const headers = createHeaders(headersInit);
|
31
|
+
const response = new Response(stream, { headers });
|
32
|
+
|
33
|
+
return { done, response };
|
34
|
+
}
|
35
|
+
|
36
|
+
export { concatenateToResponse };
|
@@ -1,8 +1,19 @@
|
|
1
|
+
/*
|
2
|
+
Copyright 2018 Google LLC
|
3
|
+
|
4
|
+
Use of this source code is governed by an MIT-style
|
5
|
+
license that can be found in the LICENSE file or at
|
6
|
+
https://opensource.org/licenses/MIT.
|
7
|
+
*/
|
8
|
+
|
1
9
|
import { concatenate } from "./concatenate.js";
|
2
10
|
import { concatenateToResponse } from "./concatenateToResponse.js";
|
3
11
|
import { isSupported } from "./isSupported.js";
|
4
12
|
import type { StreamsHandlerCallback } from "./strategy.js";
|
5
13
|
import { strategy } from "./strategy.js";
|
14
|
+
|
6
15
|
export { concatenate, concatenateToResponse, isSupported, strategy };
|
16
|
+
|
7
17
|
export type { StreamsHandlerCallback };
|
18
|
+
|
8
19
|
export * from "./_types.js";
|
@@ -0,0 +1,24 @@
|
|
1
|
+
/*
|
2
|
+
Copyright 2018 Google LLC
|
3
|
+
|
4
|
+
Use of this source code is governed by an MIT-style
|
5
|
+
license that can be found in the LICENSE file or at
|
6
|
+
https://opensource.org/licenses/MIT.
|
7
|
+
*/
|
8
|
+
|
9
|
+
import { canConstructReadableStream } from "@serwist/core/internal";
|
10
|
+
|
11
|
+
/**
|
12
|
+
* This is a utility method that determines whether the current browser supports
|
13
|
+
* the features required to create streamed responses. Currently, it checks if
|
14
|
+
* [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream)
|
15
|
+
* can be created.
|
16
|
+
*
|
17
|
+
* @returns `true`, if the current browser meets the requirements for
|
18
|
+
* streaming responses, and `false` otherwise.
|
19
|
+
*/
|
20
|
+
function isSupported(): boolean {
|
21
|
+
return canConstructReadableStream();
|
22
|
+
}
|
23
|
+
|
24
|
+
export { isSupported };
|
package/src/strategy.ts
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
/*
|
2
|
+
Copyright 2018 Google LLC
|
3
|
+
|
4
|
+
Use of this source code is governed by an MIT-style
|
5
|
+
license that can be found in the LICENSE file or at
|
6
|
+
https://opensource.org/licenses/MIT.
|
7
|
+
*/
|
8
|
+
|
9
|
+
import type { RouteHandlerCallback, RouteHandlerCallbackOptions } from "@serwist/core";
|
10
|
+
import { logger } from "@serwist/core/internal";
|
11
|
+
|
12
|
+
import type { StreamSource } from "./_types.js";
|
13
|
+
import { concatenateToResponse } from "./concatenateToResponse.js";
|
14
|
+
import { isSupported } from "./isSupported.js";
|
15
|
+
import { createHeaders } from "./utils/createHeaders.js";
|
16
|
+
|
17
|
+
export interface StreamsHandlerCallback {
|
18
|
+
({ url, request, event, params }: RouteHandlerCallbackOptions): Promise<StreamSource> | StreamSource;
|
19
|
+
}
|
20
|
+
|
21
|
+
/**
|
22
|
+
* A shortcut to create a strategy that could be dropped-in to Serwist's router.
|
23
|
+
*
|
24
|
+
* On browsers that do not support constructing new `ReadableStream`s, this
|
25
|
+
* strategy will automatically wait for all the `sourceFunctions` to complete,
|
26
|
+
* and create a final response that concatenates their values together.
|
27
|
+
*
|
28
|
+
* @param sourceFunctions An array of functions similar to `@serwist/routing.handlerCallback`
|
29
|
+
* but that instead return a `@serwist/streams.StreamSource` (or a Promise which resolves to one).
|
30
|
+
* @param headersInit If there's no `Content-Type` specified, `'text/html'` will be used by default.
|
31
|
+
* @returns
|
32
|
+
*/
|
33
|
+
function strategy(sourceFunctions: StreamsHandlerCallback[], headersInit: HeadersInit): RouteHandlerCallback {
|
34
|
+
return async ({ event, request, url, params }: RouteHandlerCallbackOptions) => {
|
35
|
+
const sourcePromises = sourceFunctions.map((fn) => {
|
36
|
+
// Ensure the return value of the function is always a promise.
|
37
|
+
return Promise.resolve(fn({ event, request, url, params }));
|
38
|
+
});
|
39
|
+
|
40
|
+
if (isSupported()) {
|
41
|
+
const { done, response } = concatenateToResponse(sourcePromises, headersInit);
|
42
|
+
|
43
|
+
if (event) {
|
44
|
+
event.waitUntil(done);
|
45
|
+
}
|
46
|
+
return response;
|
47
|
+
}
|
48
|
+
|
49
|
+
if (process.env.NODE_ENV !== "production") {
|
50
|
+
logger.log(`The current browser doesn't support creating response ` + "streams. Falling back to non-streaming response instead.");
|
51
|
+
}
|
52
|
+
|
53
|
+
// Fallback to waiting for everything to finish, and concatenating the
|
54
|
+
// responses.
|
55
|
+
const blobPartsPromises = sourcePromises.map(async (sourcePromise) => {
|
56
|
+
const source = await sourcePromise;
|
57
|
+
if (source instanceof Response) {
|
58
|
+
return source.blob();
|
59
|
+
}
|
60
|
+
// Technically, a `StreamSource` object can include any valid
|
61
|
+
// `BodyInit` type, including `FormData` and `URLSearchParams`, which
|
62
|
+
// cannot be passed to the Blob constructor directly, so we have to
|
63
|
+
// convert them to actual Blobs first.
|
64
|
+
return new Response(source).blob();
|
65
|
+
});
|
66
|
+
const blobParts = await Promise.all(blobPartsPromises);
|
67
|
+
const headers = createHeaders(headersInit);
|
68
|
+
|
69
|
+
// Constructing a new Response from a Blob source is well-supported.
|
70
|
+
// So is constructing a new Blob from multiple source Blobs or strings.
|
71
|
+
return new Response(new Blob(blobParts), { headers });
|
72
|
+
};
|
73
|
+
}
|
74
|
+
|
75
|
+
export { strategy };
|
@@ -0,0 +1,29 @@
|
|
1
|
+
/*
|
2
|
+
Copyright 2018 Google LLC
|
3
|
+
|
4
|
+
Use of this source code is governed by an MIT-style
|
5
|
+
license that can be found in the LICENSE file or at
|
6
|
+
https://opensource.org/licenses/MIT.
|
7
|
+
*/
|
8
|
+
|
9
|
+
/**
|
10
|
+
* This is a utility method that determines whether the current browser supports
|
11
|
+
* the features required to create streamed responses. Currently, it checks if
|
12
|
+
* [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream)
|
13
|
+
* is available.
|
14
|
+
*
|
15
|
+
* @private
|
16
|
+
* @param headersInit If there's no `Content-Type` specified, `'text/html'` will be used by default.
|
17
|
+
* @returns `true`, if the current browser meets the requirements for
|
18
|
+
* streaming responses, and `false` otherwise.
|
19
|
+
*/
|
20
|
+
function createHeaders(headersInit = {}): Headers {
|
21
|
+
// See https://github.com/GoogleChrome/workbox/issues/1461
|
22
|
+
const headers = new Headers(headersInit);
|
23
|
+
if (!headers.has("content-type")) {
|
24
|
+
headers.set("content-type", "text/html");
|
25
|
+
}
|
26
|
+
return headers;
|
27
|
+
}
|
28
|
+
|
29
|
+
export { createHeaders };
|
package/dist/index.cjs
DELETED
@@ -1,233 +0,0 @@
|
|
1
|
-
'use strict';
|
2
|
-
|
3
|
-
var internal = require('@serwist/core/internal');
|
4
|
-
|
5
|
-
/**
|
6
|
-
* Takes either a Response, a ReadableStream, or a
|
7
|
-
* [BodyInit](https://fetch.spec.whatwg.org/#bodyinit) and returns the
|
8
|
-
* ReadableStreamReader object associated with it.
|
9
|
-
*
|
10
|
-
* @param source
|
11
|
-
* @returns
|
12
|
-
* @private
|
13
|
-
*/ function _getReaderFromSource(source) {
|
14
|
-
if (source instanceof Response) {
|
15
|
-
// See https://github.com/GoogleChrome/workbox/issues/2998
|
16
|
-
if (source.body) {
|
17
|
-
return source.body.getReader();
|
18
|
-
}
|
19
|
-
throw new internal.SerwistError("opaque-streams-source", {
|
20
|
-
type: source.type
|
21
|
-
});
|
22
|
-
}
|
23
|
-
if (source instanceof ReadableStream) {
|
24
|
-
return source.getReader();
|
25
|
-
}
|
26
|
-
return new Response(source).body.getReader();
|
27
|
-
}
|
28
|
-
/**
|
29
|
-
* Takes multiple source Promises, each of which could resolve to a Response, a
|
30
|
-
* ReadableStream, or a [BodyInit](https://fetch.spec.whatwg.org/#bodyinit).
|
31
|
-
*
|
32
|
-
* Returns an object exposing a ReadableStream with each individual stream's
|
33
|
-
* data returned in sequence, along with a Promise which signals when the
|
34
|
-
* stream is finished (useful for passing to a FetchEvent's waitUntil()).
|
35
|
-
*
|
36
|
-
* @param sourcePromises
|
37
|
-
* @returns
|
38
|
-
*/ function concatenate(sourcePromises) {
|
39
|
-
if (process.env.NODE_ENV !== "production") {
|
40
|
-
internal.assert.isArray(sourcePromises, {
|
41
|
-
moduleName: "@serwist/streams",
|
42
|
-
funcName: "concatenate",
|
43
|
-
paramName: "sourcePromises"
|
44
|
-
});
|
45
|
-
}
|
46
|
-
const readerPromises = sourcePromises.map((sourcePromise)=>{
|
47
|
-
return Promise.resolve(sourcePromise).then((source)=>{
|
48
|
-
return _getReaderFromSource(source);
|
49
|
-
});
|
50
|
-
});
|
51
|
-
const streamDeferred = new internal.Deferred();
|
52
|
-
let i = 0;
|
53
|
-
const logMessages = [];
|
54
|
-
const stream = new ReadableStream({
|
55
|
-
pull (controller) {
|
56
|
-
return readerPromises[i].then((reader)=>{
|
57
|
-
if (reader instanceof ReadableStreamDefaultReader) {
|
58
|
-
return reader.read();
|
59
|
-
}
|
60
|
-
return;
|
61
|
-
}).then((result)=>{
|
62
|
-
if (result?.done) {
|
63
|
-
if (process.env.NODE_ENV !== "production") {
|
64
|
-
logMessages.push([
|
65
|
-
"Reached the end of source:",
|
66
|
-
sourcePromises[i]
|
67
|
-
]);
|
68
|
-
}
|
69
|
-
i++;
|
70
|
-
if (i >= readerPromises.length) {
|
71
|
-
// Log all the messages in the group at once in a single group.
|
72
|
-
if (process.env.NODE_ENV !== "production") {
|
73
|
-
internal.logger.groupCollapsed(`Concatenating ${readerPromises.length} sources.`);
|
74
|
-
for (const message of logMessages){
|
75
|
-
if (Array.isArray(message)) {
|
76
|
-
internal.logger.log(...message);
|
77
|
-
} else {
|
78
|
-
internal.logger.log(message);
|
79
|
-
}
|
80
|
-
}
|
81
|
-
internal.logger.log("Finished reading all sources.");
|
82
|
-
internal.logger.groupEnd();
|
83
|
-
}
|
84
|
-
controller.close();
|
85
|
-
streamDeferred.resolve();
|
86
|
-
return;
|
87
|
-
}
|
88
|
-
// The `pull` method is defined because we're inside it.
|
89
|
-
return this.pull(controller);
|
90
|
-
}
|
91
|
-
controller.enqueue(result?.value);
|
92
|
-
}).catch((error)=>{
|
93
|
-
if (process.env.NODE_ENV !== "production") {
|
94
|
-
internal.logger.error("An error occurred:", error);
|
95
|
-
}
|
96
|
-
streamDeferred.reject(error);
|
97
|
-
throw error;
|
98
|
-
});
|
99
|
-
},
|
100
|
-
cancel () {
|
101
|
-
if (process.env.NODE_ENV !== "production") {
|
102
|
-
internal.logger.warn("The ReadableStream was cancelled.");
|
103
|
-
}
|
104
|
-
streamDeferred.resolve();
|
105
|
-
}
|
106
|
-
});
|
107
|
-
return {
|
108
|
-
done: streamDeferred.promise,
|
109
|
-
stream
|
110
|
-
};
|
111
|
-
}
|
112
|
-
|
113
|
-
/*
|
114
|
-
Copyright 2018 Google LLC
|
115
|
-
|
116
|
-
Use of this source code is governed by an MIT-style
|
117
|
-
license that can be found in the LICENSE file or at
|
118
|
-
https://opensource.org/licenses/MIT.
|
119
|
-
*/ /**
|
120
|
-
* This is a utility method that determines whether the current browser supports
|
121
|
-
* the features required to create streamed responses. Currently, it checks if
|
122
|
-
* [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream)
|
123
|
-
* is available.
|
124
|
-
*
|
125
|
-
* @private
|
126
|
-
* @param headersInit If there's no `Content-Type` specified, `'text/html'` will be used by default.
|
127
|
-
* @returns `true`, if the current browser meets the requirements for
|
128
|
-
* streaming responses, and `false` otherwise.
|
129
|
-
*/ function createHeaders(headersInit = {}) {
|
130
|
-
// See https://github.com/GoogleChrome/workbox/issues/1461
|
131
|
-
const headers = new Headers(headersInit);
|
132
|
-
if (!headers.has("content-type")) {
|
133
|
-
headers.set("content-type", "text/html");
|
134
|
-
}
|
135
|
-
return headers;
|
136
|
-
}
|
137
|
-
|
138
|
-
/**
|
139
|
-
* Takes multiple source Promises, each of which could resolve to a Response, a
|
140
|
-
* ReadableStream, or a [BodyInit](https://fetch.spec.whatwg.org/#bodyinit),
|
141
|
-
* along with a
|
142
|
-
* [HeadersInit](https://fetch.spec.whatwg.org/#typedefdef-headersinit).
|
143
|
-
*
|
144
|
-
* Returns an object exposing a Response whose body consists of each individual
|
145
|
-
* stream's data returned in sequence, along with a Promise which signals when
|
146
|
-
* the stream is finished (useful for passing to a FetchEvent's waitUntil()).
|
147
|
-
*
|
148
|
-
* @param sourcePromises
|
149
|
-
* @param headersInit If there's no `Content-Type` specified,`'text/html'` will be used by default.
|
150
|
-
* @returns
|
151
|
-
*/ function concatenateToResponse(sourcePromises, headersInit) {
|
152
|
-
const { done, stream } = concatenate(sourcePromises);
|
153
|
-
const headers = createHeaders(headersInit);
|
154
|
-
const response = new Response(stream, {
|
155
|
-
headers
|
156
|
-
});
|
157
|
-
return {
|
158
|
-
done,
|
159
|
-
response
|
160
|
-
};
|
161
|
-
}
|
162
|
-
|
163
|
-
/**
|
164
|
-
* This is a utility method that determines whether the current browser supports
|
165
|
-
* the features required to create streamed responses. Currently, it checks if
|
166
|
-
* [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream)
|
167
|
-
* can be created.
|
168
|
-
*
|
169
|
-
* @returns `true`, if the current browser meets the requirements for
|
170
|
-
* streaming responses, and `false` otherwise.
|
171
|
-
*/ function isSupported() {
|
172
|
-
return internal.canConstructReadableStream();
|
173
|
-
}
|
174
|
-
|
175
|
-
/**
|
176
|
-
* A shortcut to create a strategy that could be dropped-in to Serwist's router.
|
177
|
-
*
|
178
|
-
* On browsers that do not support constructing new `ReadableStream`s, this
|
179
|
-
* strategy will automatically wait for all the `sourceFunctions` to complete,
|
180
|
-
* and create a final response that concatenates their values together.
|
181
|
-
*
|
182
|
-
* @param sourceFunctions An array of functions similar to `@serwist/routing.handlerCallback`
|
183
|
-
* but that instead return a `@serwist/streams.StreamSource` (or a Promise which resolves to one).
|
184
|
-
* @param headersInit If there's no `Content-Type` specified, `'text/html'` will be used by default.
|
185
|
-
* @returns
|
186
|
-
*/ function strategy(sourceFunctions, headersInit) {
|
187
|
-
return async ({ event, request, url, params })=>{
|
188
|
-
const sourcePromises = sourceFunctions.map((fn)=>{
|
189
|
-
// Ensure the return value of the function is always a promise.
|
190
|
-
return Promise.resolve(fn({
|
191
|
-
event,
|
192
|
-
request,
|
193
|
-
url,
|
194
|
-
params
|
195
|
-
}));
|
196
|
-
});
|
197
|
-
if (isSupported()) {
|
198
|
-
const { done, response } = concatenateToResponse(sourcePromises, headersInit);
|
199
|
-
if (event) {
|
200
|
-
event.waitUntil(done);
|
201
|
-
}
|
202
|
-
return response;
|
203
|
-
}
|
204
|
-
if (process.env.NODE_ENV !== "production") {
|
205
|
-
internal.logger.log(`The current browser doesn't support creating response ` + "streams. Falling back to non-streaming response instead.");
|
206
|
-
}
|
207
|
-
// Fallback to waiting for everything to finish, and concatenating the
|
208
|
-
// responses.
|
209
|
-
const blobPartsPromises = sourcePromises.map(async (sourcePromise)=>{
|
210
|
-
const source = await sourcePromise;
|
211
|
-
if (source instanceof Response) {
|
212
|
-
return source.blob();
|
213
|
-
}
|
214
|
-
// Technically, a `StreamSource` object can include any valid
|
215
|
-
// `BodyInit` type, including `FormData` and `URLSearchParams`, which
|
216
|
-
// cannot be passed to the Blob constructor directly, so we have to
|
217
|
-
// convert them to actual Blobs first.
|
218
|
-
return new Response(source).blob();
|
219
|
-
});
|
220
|
-
const blobParts = await Promise.all(blobPartsPromises);
|
221
|
-
const headers = createHeaders(headersInit);
|
222
|
-
// Constructing a new Response from a Blob source is well-supported.
|
223
|
-
// So is constructing a new Blob from multiple source Blobs or strings.
|
224
|
-
return new Response(new Blob(blobParts), {
|
225
|
-
headers
|
226
|
-
});
|
227
|
-
};
|
228
|
-
}
|
229
|
-
|
230
|
-
exports.concatenate = concatenate;
|
231
|
-
exports.concatenateToResponse = concatenateToResponse;
|
232
|
-
exports.isSupported = isSupported;
|
233
|
-
exports.strategy = strategy;
|