Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -2295,9 +2295,31 @@ try {
}
```

`uploadToSignedUrl` and `uploadBinaryToSignedUrl` take the signal in the positional slot the
controller used to occupy. `download` and `downloadStream` gain the parameter; on the stream the
abort surfaces as a `RequestAbortedException` error.
`download` and `downloadStream` gain the parameter; on the stream the abort surfaces as a
`RequestAbortedException` error.

### Signed URL uploads take named options

`uploadToSignedUrl` and `uploadBinaryToSignedUrl` took their options as optional positional
parameters. They are named now, like on every other upload method, so passing one no longer
means passing `null` for the ones before it.

```dart
// Before
await supabase.storage
.from('avatars')
.uploadToSignedUrl(path, token, file, const FileOptions(upsert: true));

// After
await supabase.storage
.from('avatars')
.uploadToSignedUrl(
path,
token,
file,
fileOptions: const FileOptions(upsert: true),
);
```

### Storage uploads send the raw file body

Expand Down
22 changes: 2 additions & 20 deletions packages/supabase_common/lib/src/idle_timeout.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import 'dart:async';
import 'dart:math';
import 'dart:typed_data';

import 'package:http/http.dart';

import 'http.dart';
import 'request_body.dart';

/// Aborts a request once it makes no progress for [duration].
///
Expand Down Expand Up @@ -65,29 +64,12 @@ final class IdleTimeout {
/// a single chunk also shows its progress while it is being sent.
Stream<List<int>> watchRequestBody(Stream<List<int>> body) {
if (duration == null) return body;
return body.asyncExpand(_slices).map((chunk) {
return sliceRequestBody(body).map((chunk) {
_restart();
return chunk;
});
}

static const _sliceLength = 64 * 1024;

static Stream<List<int>> _slices(List<int> chunk) {
if (chunk.length <= _sliceLength) return Stream.value(chunk);
return Stream.fromIterable([
for (var start = 0; start < chunk.length; start += _sliceLength)
if (chunk is Uint8List)
Uint8List.sublistView(
chunk,
start,
min(start + _sliceLength, chunk.length),
)
else
chunk.sublist(start, min(start + _sliceLength, chunk.length)),
]);
}

/// Sends [request] over [httpClient] and returns a response whose body
/// stream is watched as well.
///
Expand Down
24 changes: 24 additions & 0 deletions packages/supabase_common/lib/src/request_body.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import 'dart:math';
import 'dart:typed_data';

const _sliceLength = 64 * 1024;

/// Passes [body] through with every chunk larger than 64 KiB split into
/// slices of at most that size, so a body held in memory as a single chunk
/// is also handed to the HTTP client a bit at a time.
Stream<List<int>> sliceRequestBody(Stream<List<int>> body) =>
body.asyncExpand(_slices);

Stream<List<int>> _slices(List<int> chunk) {
if (chunk.length <= _sliceLength) return Stream.value(chunk);
return Stream.fromIterable(_lazySlices(chunk));
}

Iterable<List<int>> _lazySlices(List<int> chunk) sync* {
for (var start = 0; start < chunk.length; start += _sliceLength) {
final end = min(start + _sliceLength, chunk.length);
yield chunk is Uint8List
? Uint8List.sublistView(chunk, start, end)
: chunk.sublist(start, end);
}
}
1 change: 1 addition & 0 deletions packages/supabase_common/lib/supabase_common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export 'src/platform/platform_info.dart';
export 'src/random.dart';
export 'src/redaction.dart';
export 'src/replay_subject.dart';
export 'src/request_body.dart';
export 'src/retry.dart';
export 'src/retry_options.dart';
export 'src/snake_case.dart';
Expand Down
38 changes: 38 additions & 0 deletions packages/supabase_common/test/request_body_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'dart:typed_data';

import 'package:supabase_common/supabase_common.dart';
import 'package:test/test.dart';

void main() {
group('sliceRequestBody', () {
test('splits a large chunk into views of at most 64 KiB', () async {
final chunk = Uint8List(150 * 1024);

final slices = await sliceRequestBody(Stream.value(chunk)).toList();

expect(slices.map((slice) => slice.length), [
64 * 1024,
64 * 1024,
22 * 1024,
]);
expect(slices.first, isA<Uint8List>());
expect((slices.first as Uint8List).buffer, chunk.buffer);
});

test('passes a small chunk through unchanged', () async {
final chunk = [1, 2, 3];

final slices = await sliceRequestBody(Stream.value(chunk)).toList();

expect(slices.single, same(chunk));
});

test('splits a chunk that is not a Uint8List', () async {
final chunk = List.filled(64 * 1024 + 1, 0);

final slices = await sliceRequestBody(Stream.value(chunk)).toList();

expect(slices.map((slice) => slice.length), [64 * 1024, 1]);
});
});
}
Loading
Loading