-
Notifications
You must be signed in to change notification settings - Fork 0
feat(fetch): add runtime-backed adapters and native fetch primitives #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
45 commits
Select commit
Hold shift + click to select a range
edf5b40
refactor(fetch): split headers into native, web, and io implementations
medz 277db3c
feat(internal): add stream tee utility
medz e1c2f9a
feat(fetch): split Blob into native, web, and io implementations
medz d47b5f5
refactor(fetch): narrow File parts to BlobPart
medz 5d75a07
feat(fetch): add native Body implementation
medz 0114088
test(fetch): rename body native test
medz c993610
refactor(fetch): move web utils under internal
medz 5fe8f43
feat(fetch): split URLSearchParams into native and web implementations
medz 7de23ce
feat(fetch): support native web URLSearchParams host
medz 9cf3971
feat(fetch): add native FormData baseline
medz 149fc27
feat(fetch): add native FormData parsing for urlencoded bodies
medz 0334f23
chore(wip): checkpoint remaining request refactor work
medz 66305a0
feat(fetch): parse native multipart form data
medz 7a7d7fe
refactor(fetch): rename native multipart value types
medz 71a4a1c
feat(fetch): add native FormData multipart encoding
medz ae08e2f
feat(fetch): complete native Request implementation
medz 0697b04
feat(fetch): complete native Response implementation
medz b9f8c3e
refactor(fetch): switch public surface to native fetch types
medz 9d40962
feat(fetch): add native Response MDN factories
medz 2c0b0c7
feat(fetch): add io-backed Request implementation
medz 0c4ba9a
feat(fetch): add js-backed Request implementation
medz d82b588
feat(fetch): add js-backed Response implementation
medz 54ccaf5
feat(fetch): add io-backed Response implementation
medz 6ee5f78
perf(fetch): share web host read helpers
medz bbace41
perf(fetch): remove redundant body list copy
medz a752a47
docs(readme): simplify API overview
medz 57a4252
docs(readme): refresh fetch API examples
medz 10d16d9
fix(fetch): iterate native web headers entries correctly
medz a8bfc7f
fix(fetch): preserve response clone metadata
medz a0347ce
fix(fetch): clone stream-backed bodies on copy
medz 4f4f86e
fix(fetch): preserve MIME type when reading web blobs
medz 1287a6c
fix(fetch): align js set-cookie header reads
medz 87f6ce8
refactor(fetch): make body streams non-nullable
medz 9b93688
style(test): format browser fetch tests
medz eacd0c3
fix(fetch): preserve web file lastModified metadata
medz 2c8a371
fix(fetch): normalize js set-cookie header lookups
medz 5eeaa1e
test(fetch): lock file-backed blob size semantics
medz bcd6a41
style(test): format web fetch utils browser test
medz 7705c37
fix(fetch): preserve FormData set ordering
medz 1f5af8b
fix(fetch): parse quoted multipart parameters safely
medz 89964a7
fix(fetch): avoid CRLF unescaping in multipart params
medz 7adf2e0
style(test): format FormData native tests
medz 35b6e9b
docs(fetch): note blob io upstream migration path
medz 3931884
test(fetch): cover multipart quote escape roundtrip
medz f2d8ec8
style(test): format multipart parser tests
medz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import 'dart:async'; | ||
|
|
||
| import 'package:async/async.dart'; | ||
|
|
||
| /// Splits [source] into two output streams. | ||
| /// | ||
| /// Both branches observe the same source events and can be consumed | ||
| /// independently as single-subscription streams. | ||
| (Stream<T>, Stream<T>) streamTee<T>(Stream<T> source) { | ||
| final streams = StreamSplitter.splitFrom(source, 2); | ||
| return (streams[0], streams[1]); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| @JS() | ||
| library; | ||
|
|
||
| import 'dart:js_interop'; | ||
| import 'dart:typed_data'; | ||
|
|
||
| import 'package:web/web.dart' as web; | ||
|
|
||
| import '../fetch/blob.dart'; | ||
| import '../fetch/file.dart'; | ||
| import '../fetch/form_data.native.dart'; | ||
| import 'web_utils.dart' as web_utils; | ||
|
|
||
| Future<Uint8List> bytesFromWebPromise(JSPromise<JSUint8Array> promise) { | ||
| return promise.toDart.then((value) => value.toDart); | ||
| } | ||
|
|
||
| Future<String> textFromWebPromise(JSPromise<JSString> promise) { | ||
| return promise.toDart.then((value) => value.toDart); | ||
| } | ||
|
|
||
| Future<Blob> blobFromWebPromise( | ||
| JSPromise<web.Blob> promise, { | ||
| String? type, | ||
| }) async { | ||
| final hostBlob = await promise.toDart; | ||
| return Blob(<Object>[hostBlob], type ?? hostBlob.type); | ||
| } | ||
|
|
||
| FormData formDataFromWebHost(web.FormData host) { | ||
| final formData = FormData(); | ||
| final iterator = web_utils.FormData.fromHost(host).entries(); | ||
|
|
||
| while (true) { | ||
| final result = iterator.next(); | ||
| if (result.done) break; | ||
| final value = result.value; | ||
| if (value == null || value.isUndefinedOrNull) { | ||
| continue; | ||
| } | ||
|
|
||
| final [name, entry] = (value as JSArray<JSAny?>).toDart; | ||
| final key = (name as JSString).toDart; | ||
| if (entry == null || entry.isUndefinedOrNull) { | ||
| continue; | ||
| } | ||
|
|
||
| if (entry.typeofEquals('string')) { | ||
| formData.append(key, Multipart.text((entry as JSString).toDart)); | ||
| continue; | ||
| } | ||
|
|
||
| if (entry case final web.File file) { | ||
| formData.append( | ||
| key, | ||
| Multipart.blob( | ||
| File( | ||
| <Object>[file], | ||
| file.name, | ||
| type: file.type, | ||
| lastModified: file.lastModified, | ||
| ), | ||
| ), | ||
| ); | ||
| continue; | ||
| } | ||
medz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if (entry case final web.Blob blob) { | ||
| formData.append(key, Multipart.blob(Blob(<Object>[blob], blob.type))); | ||
| } | ||
| } | ||
|
|
||
| return formData; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| @JS() | ||
| library; | ||
|
|
||
| import 'dart:js_interop'; | ||
| import 'dart:typed_data'; | ||
|
|
||
| import 'package:web/web.dart' as web; | ||
|
|
||
| Stream<Uint8List> dartByteStreamFromWebReadableStream( | ||
| web.ReadableStream stream, | ||
| ) async* { | ||
| final reader = stream.getReader() as web.ReadableStreamDefaultReader; | ||
|
|
||
| try { | ||
| while (true) { | ||
| final result = await reader.read().toDart; | ||
| if (result.done) { | ||
| break; | ||
| } | ||
|
|
||
| final value = result.value; | ||
| if (value == null || value.isUndefinedOrNull) { | ||
| continue; | ||
| } | ||
|
|
||
| final bytes = (value as JSUint8Array).toDart; | ||
| if (bytes.isEmpty) { | ||
| continue; | ||
| } | ||
|
|
||
| yield bytes; | ||
| } | ||
| } finally { | ||
| reader.releaseLock(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| @JS() | ||
| library; | ||
|
|
||
| import 'dart:js_interop'; | ||
|
|
||
| import 'package:web/web.dart' as web; | ||
|
|
||
| extension type IteratorReturnResult(JSObject _) implements JSObject { | ||
| external bool get done; | ||
| external JSAny? get value; | ||
| } | ||
|
|
||
| extension type ArrayIterator(JSObject _) implements JSObject { | ||
| external IteratorReturnResult next(); | ||
| } | ||
|
|
||
| extension type Headers._(JSObject _) implements web.Headers { | ||
| external factory Headers([web.HeadersInit init]); | ||
| external ArrayIterator entries(); | ||
| external ArrayIterator keys(); | ||
| external ArrayIterator values(); | ||
|
|
||
| factory Headers.fromEntries(Iterable<MapEntry<String, String>> entries) { | ||
| final headers = Headers(); | ||
| for (final MapEntry(key: name, :value) in entries) { | ||
| headers.append(name, value); | ||
| } | ||
| return headers; | ||
| } | ||
|
|
||
| factory Headers.fromMap(Map<String, String> map) => | ||
| Headers.fromEntries(map.entries); | ||
|
|
||
| factory Headers.fromMultiValueMap(Map<String, Iterable<String>> map) { | ||
| final headers = Headers(); | ||
| for (final MapEntry(:key, value: values) in map.entries) { | ||
| for (final value in values) { | ||
| headers.append(key, value); | ||
| } | ||
| } | ||
| return headers; | ||
| } | ||
|
|
||
| factory Headers.fromStringPairs(Iterable<Iterable<String>> pairs) { | ||
| final headers = Headers(); | ||
| for (final kv in pairs) { | ||
| if (kv.length != 2) { | ||
| throw ArgumentError.value( | ||
| kv, | ||
| 'pairs', | ||
| 'Header pairs must contain exactly two string items.', | ||
| ); | ||
| } | ||
|
|
||
| headers.append(kv.elementAt(0), kv.elementAt(1)); | ||
| } | ||
|
|
||
| return headers; | ||
| } | ||
|
|
||
| factory Headers.fromRecordPairs(Iterable<(String, String)> pairs) { | ||
| final headers = Headers(); | ||
| for (final (name, value) in pairs) { | ||
| headers.append(name, value); | ||
| } | ||
| return headers; | ||
| } | ||
|
|
||
| factory Headers.fromRecordMultiPairs( | ||
| Iterable<(String, Iterable<String>)> pairs, | ||
| ) { | ||
| final headers = Headers(); | ||
| for (final (name, values) in pairs) { | ||
| for (final value in values) { | ||
| headers.append(name, value); | ||
| } | ||
| } | ||
| return headers; | ||
| } | ||
| } | ||
|
|
||
| extension type Array<T extends JSAny?>._(JSAny _) { | ||
| external static bool isArray(JSAny _); | ||
| } | ||
|
|
||
| extension type URLSearchParams._(JSObject _) implements web.URLSearchParams { | ||
| external factory URLSearchParams([JSAny init]); | ||
|
|
||
| external ArrayIterator entries(); | ||
| external ArrayIterator keys(); | ||
| external ArrayIterator values(); | ||
|
|
||
| @JS('toString') | ||
| external String stringify(); | ||
|
|
||
| factory URLSearchParams.fromEntries( | ||
| Iterable<MapEntry<String, String>> entries, | ||
| ) { | ||
| final params = URLSearchParams(); | ||
| for (final MapEntry(key: name, :value) in entries) { | ||
| params.append(name, value); | ||
| } | ||
| return params; | ||
| } | ||
|
|
||
| factory URLSearchParams.fromMap(Map<String, String> map) => | ||
| URLSearchParams.fromEntries(map.entries); | ||
| } | ||
|
|
||
| extension type FormData._(JSObject _) implements web.FormData { | ||
| external factory FormData([ | ||
| web.HTMLFormElement form, | ||
| web.HTMLElement? submitter, | ||
| ]); | ||
|
|
||
| factory FormData.fromHost(web.FormData host) => FormData._(host); | ||
|
|
||
| external ArrayIterator entries(); | ||
| external ArrayIterator keys(); | ||
| external ArrayIterator values(); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.