Skip to content
Open
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
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,9 @@ not wrap untyped builders. Each method returns a copy holding an immutable `Post
that describes the query as structured values (operation, columns, filter, orderings, payload, and
`PostgrestRequestOptions` for headers, retry, timeout and abort signal). Awaiting the builder hands
the request to a `PostgrestTableExecutor` and converts the `PostgrestTableResult` into the table's
`Row` type. `PostgrestHttpTableExecutor` renders the request through the untyped builders at
execution time, so both APIs send the same wire request. These types are internal to the postgrest
package and not exported.
`Row` type. The default `PostgrestHttpTableExecutor` renders the request through the untyped
builders at execution time, so both APIs send the same wire request; another executor (a
`SupabaseClientInterceptor` can wrap it) may cache, queue or answer from a local store instead.

### Realtime Architecture

Expand Down
6 changes: 5 additions & 1 deletion MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -2342,6 +2342,10 @@ are checked against the column types at compile time, and writes only accept the
and update types. `from()` keeps working, and both can be used on one client, so a codebase can move
over one table at a time.

Requests made through `table()` run through the executor that `SupabaseClientInterceptor`s wrap, so
features built as interceptors, such as offline support, only apply to them. Requests made through
`from()` bypass every interceptor.

The typed API is marked `@experimental` while it settles. Referencing an experimental type such as
`PostgrestFilter` directly reports `experimental_member_use`, which an
`// ignore_for_file: experimental_member_use` silences; the generated file already carries it.
Expand Down Expand Up @@ -2716,7 +2720,7 @@ await supabase
```

The generated table definition also lists `primaryKey` and `relations`. Neither changes how a
request is sent; they describe the table to code that handles requests itself, such as a plugin
request is sent; they describe the table to code that handles requests itself, such as an interceptor
that caches rows and needs to tell them apart, or one that resolves an embed from local data.

### Realtime streams
Expand Down
12 changes: 1 addition & 11 deletions packages/postgrest/lib/postgrest.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,6 @@ export 'package:yet_another_json_isolate/yet_another_json_isolate.dart'

export 'src/postgrest.dart';
export 'src/postgrest_builder.dart';
export 'src/postgrest_typed_builder.dart'
hide
PostgrestExplainOptions,
PostgrestHttpTableExecutor,
PostgrestRequestOptions,
PostgrestResultShape,
PostgrestTableExecutor,
PostgrestTableOperation,
PostgrestTableRequest,
PostgrestTableResult,
renderPostgrestQueryValue;
export 'src/postgrest_typed_builder.dart' hide renderPostgrestQueryValue;
export 'src/types.dart';
export 'package:http/http.dart' show RequestAbortedException;
15 changes: 12 additions & 3 deletions packages/postgrest/lib/src/postgrest.dart
Original file line number Diff line number Diff line change
Expand Up @@ -165,12 +165,21 @@ class PostgrestClient {
/// .select()
/// .where(Books.id.gt(10));
/// ```
///
/// The builder collects a [PostgrestTableRequest] and hands it to
/// [executor] when awaited; without one the request is sent to PostgREST
/// through this client by a [PostgrestHttpTableExecutor].
@experimental
PostgrestTypedQueryBuilder<Row, Insert, Update> table<Row, Insert, Update>(
PostgrestTable<Row, Insert, Update> table,
) {
PostgrestTable<Row, Insert, Update> table, {
PostgrestTableExecutor? executor,
}) {
final schema = _schemaIsExplicit ? _schema : (table.schema ?? _schema);
return PostgrestTypedQueryBuilder(table, client: this, schema: schema);
return PostgrestTypedQueryBuilder(
table,
executor: executor ?? PostgrestHttpTableExecutor(this),
schema: schema,
);
}

/// Select a schema to query or perform an function (rpc) call.
Expand Down
72 changes: 40 additions & 32 deletions packages/postgrest/lib/src/postgrest_embedded_scope.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,72 +22,80 @@ part of 'postgrest_typed_builder.dart';
@experimental
final class PostgrestEmbeddedScope<Row> {
PostgrestEmbeddedScope._(
this._relation, {
this.relation, {
List<PostgrestOrdering<Row>> orderings = const [],
int? limit,
int? offset,
this.rowLimit,
this.rowOffset,
List<PostgrestEmbeddedScope<Object?>> scopes = const [],
}) : _orderings = List.unmodifiable(orderings),
_limit = limit,
_offset = offset,
_scopes = List.unmodifiable(scopes);
}) : orderings = List.unmodifiable(orderings),
scopes = List.unmodifiable(scopes);

final PostgrestToManyRelation<Object?, Row> _relation;
final List<PostgrestOrdering<Row>> _orderings;
final int? _limit;
final int? _offset;
final List<PostgrestEmbeddedScope<Object?>> _scopes;
/// The embed whose rows the scope applies to.
final PostgrestToManyRelation<Object?, Row> relation;

/// The sort keys of the embedded rows, first key first.
final List<PostgrestOrdering<Row>> orderings;

/// The maximum number of embedded rows per parent row, set by [limit] and
/// [range].
final int? rowLimit;

/// The number of embedded rows to skip per parent row, set by [range].
final int? rowOffset;

/// The scopes of embeds nested in this one.
final List<PostgrestEmbeddedScope<Object?>> scopes;

PostgrestEmbeddedScope<Row> _copyWith({
List<PostgrestOrdering<Row>>? orderings,
int? limit,
int? offset,
List<PostgrestEmbeddedScope<Object?>>? scopes,
List<PostgrestOrdering<Row>>? sortKeys,
int? limitCount,
int? offsetCount,
List<PostgrestEmbeddedScope<Object?>>? nested,
}) => PostgrestEmbeddedScope._(
_relation,
orderings: orderings ?? _orderings,
limit: limit ?? _limit,
offset: offset ?? _offset,
scopes: scopes ?? _scopes,
relation,
orderings: sortKeys ?? orderings,
rowLimit: limitCount ?? rowLimit,
rowOffset: offsetCount ?? rowOffset,
scopes: nested ?? scopes,
);

/// Sorts the embedded rows by [ordering].
///
/// Repeated calls append, so the second key breaks ties in the first.
PostgrestEmbeddedScope<Row> order(PostgrestOrdering<Row> ordering) =>
_copyWith(orderings: [..._orderings, ordering]);
_copyWith(sortKeys: [...orderings, ordering]);

/// Limits the embedded rows of each parent row to [count].
PostgrestEmbeddedScope<Row> limit(int count) => _copyWith(limit: count);
PostgrestEmbeddedScope<Row> limit(int count) => _copyWith(limitCount: count);

/// Limits the embedded rows of each parent row to the ones within the
/// specified range, inclusive.
PostgrestEmbeddedScope<Row> range(int from, int to) =>
_copyWith(offset: from, limit: to - from + 1);
_copyWith(offsetCount: from, limitCount: to - from + 1);

/// Applies [scope] to the rows of [relation], an embed nested in this one,
/// Applies [scope] to the rows of [embed], an embed nested in this one,
/// under a dotted path such as `books.reviews.limit=3`.
PostgrestEmbeddedScope<Row> embedded<Target>(
PostgrestToManyRelation<Row, Target> relation,
PostgrestToManyRelation<Row, Target> embed,
PostgrestEmbeddedScope<Target> Function(
PostgrestEmbeddedScope<Target> scope,
)
scope,
) => _copyWith(
scopes: [..._scopes, scope(PostgrestEmbeddedScope._(relation))],
nested: [...scopes, scope(PostgrestEmbeddedScope._(embed))],
);

/// The order keys, limit and offset of this scope and every nested one,
/// each under the dotted path of its embed, with [prefix] in front.
Iterable<_ScopedTransforms> _flattened([String prefix = '']) sync* {
final path = '$prefix${_relation._reference}';
final path = '$prefix${relation._reference}';
yield (
path: path,
orderKeys: [for (final ordering in _orderings) ordering.orderKey],
limit: _limit,
offset: _offset,
orderKeys: [for (final ordering in orderings) ordering.orderKey],
limit: rowLimit,
offset: rowOffset,
);
for (final scope in _scopes) {
for (final scope in scopes) {
yield* scope._flattened('$path.');
}
}
Expand Down
1 change: 0 additions & 1 deletion packages/postgrest/lib/src/postgrest_table_request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,6 @@ final class PostgrestRequestOptions {
/// else, against a local store for example, because the filters, orderings
/// and payload are still structured values rather than URL text.
@experimental
@internal
final class PostgrestTableRequest {
PostgrestTableRequest({
required this.table,
Expand Down
24 changes: 13 additions & 11 deletions packages/postgrest/lib/src/postgrest_typed_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -231,22 +231,24 @@ extension PostgrestTypedRowsBuilder<Row, Element>

/// A typed PostgREST request that can be awaited.
///
/// Holds the request the builder methods have collected so far. Awaiting the
/// builder executes the request and converts the result into [T], so
/// awaiting it never exposes raw `Map<String, dynamic>` data.
/// Holds the [request] the builder methods have collected so far and the
/// [PostgrestTableExecutor] that runs it. Awaiting the builder executes the
/// request and converts the result into [T], so awaiting it never exposes raw
/// `Map<String, dynamic>` data.
@experimental
class PostgrestTypedBuilder<T> implements Future<T> {
const PostgrestTypedBuilder._(this._request, this._executor, this._convert);
const PostgrestTypedBuilder._(this.request, this._executor, this._convert);

final PostgrestTableRequest _request;
/// The request awaiting this builder executes.
final PostgrestTableRequest request;

final PostgrestTableExecutor _executor;
final _ResultConverter<T> _convert;

/// Runs [_request] on the executor, routing a synchronous throw into the
/// Runs [request] on the executor, routing a synchronous throw into the
/// returned future so the [Future] contract holds for every executor.
Future<T> _execute() =>
Future.sync(() => _executor.execute(_request)).then(_convert);
Future.sync(() => _executor.execute(request)).then(_convert);

PostgrestTypedBuilder<T> _with(PostgrestTableRequest changed) =>
PostgrestTypedBuilder._(changed, _executor, _convert);
Expand All @@ -255,15 +257,15 @@ class PostgrestTypedBuilder<T> implements Future<T> {
///
/// See [PostgrestBuilder.retry] for [enabled] and [count].
PostgrestTypedBuilder<T> retry({bool enabled = true, int? count}) =>
_with(_request._retry(enabled, count));
_with(request._retry(enabled, count));

/// Bounds how long a single attempt of this request may go without
/// progress, overriding the timeout configured on [PostgrestClient].
///
/// Unlike [timeout], which only stops waiting for the result, this cancels
/// the attempt. See [PostgrestBuilder.requestTimeout].
PostgrestTypedBuilder<T> requestTimeout(Duration timeout) =>
_with(_request._requestTimeout(timeout));
_with(request._requestTimeout(timeout));

/// Cancels the request when [abortSignal] completes, throwing a
/// [RequestAbortedException] and stopping any retries.
Expand All @@ -278,11 +280,11 @@ class PostgrestTypedBuilder<T> implements Future<T> {
///
/// See [PostgrestBuilder.abortSignal].
PostgrestTypedBuilder<T> abortSignal(Future<void> abortSignal) =>
_with(_request._abortSignal(abortSignal));
_with(request._abortSignal(abortSignal));

/// Sets [key] to [value] in the headers of this request.
PostgrestTypedBuilder<T> setHeader(String key, String value) =>
_with(_request._header(key, value));
_with(request._header(key, value));

/// A broadcast stream of the one result. The request runs when the first
/// listener subscribes, so a listener added later still receives it.
Expand Down
18 changes: 9 additions & 9 deletions packages/postgrest/lib/src/postgrest_typed_filter_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ part of 'postgrest_typed_builder.dart';
class PostgrestTypedFilterBuilder<Row, T>
extends PostgrestTypedTransformBuilder<Row, T> {
const PostgrestTypedFilterBuilder._(
super._request,
super.request,
super.executor,
super.convert,
super.rowFromJson,
) : super._();

PostgrestTypedFilterBuilder<Row, T> _filtered(
PostgrestTableRequest changed,
PostgrestTableRequest request,
) => PostgrestTypedFilterBuilder._(
changed,
request,
_executor,
_convert,
_rowFromJson,
Expand All @@ -27,19 +27,19 @@ class PostgrestTypedFilterBuilder<Row, T>
PostgrestTypedFilterBuilder<Row, T> retry({
bool enabled = true,
int? count,
}) => _filtered(_request._retry(enabled, count));
}) => _filtered(request._retry(enabled, count));

@override
PostgrestTypedFilterBuilder<Row, T> requestTimeout(Duration timeout) =>
_filtered(_request._requestTimeout(timeout));
_filtered(request._requestTimeout(timeout));

@override
PostgrestTypedFilterBuilder<Row, T> abortSignal(Future<void> abortSignal) =>
_filtered(_request._abortSignal(abortSignal));
_filtered(request._abortSignal(abortSignal));

@override
PostgrestTypedFilterBuilder<Row, T> setHeader(String key, String value) =>
_filtered(_request._header(key, value));
_filtered(request._header(key, value));

/// Only rows satisfying [filter].
///
Expand All @@ -65,9 +65,9 @@ class PostgrestTypedFilterBuilder<Row, T>
///
/// Repeated [where] calls combine with logical AND.
PostgrestTypedFilterBuilder<Row, T> where(PostgrestFilter<Row> filter) {
final current = _request.filter;
final current = request.filter;
return _filtered(
_request.copyWith(filter: current == null ? filter : current & filter),
request.copyWith(filter: current == null ? filter : current & filter),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ part of 'postgrest_typed_builder.dart';
@experimental
class PostgrestTypedQueryBuilder<Row, Insert, Update> {
/// {@macro postgrest_typed_query_builder}
PostgrestTypedQueryBuilder(
const PostgrestTypedQueryBuilder(
this.table, {
required PostgrestClient client,
required PostgrestTableExecutor executor,
String? schema,
}) : _executor = PostgrestHttpTableExecutor(client),
}) : _executor = executor,
_schema = schema,
_options = null;

Expand Down
Loading
Loading