Skip to content

Commit 424ddf6

Browse files
authored
Merge pull request #29 from appinteractive/main
Adding Middleware support
2 parents 4b8569c + eb142eb commit 424ddf6

File tree

5 files changed

+361
-33
lines changed

5 files changed

+361
-33
lines changed

README.md

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,61 @@ You can also authenticate with a token:
9797
await db.authenticate('your-auth-token');
9898
```
9999

100-
### Basic Queries
100+
### Middleware
101+
102+
SurrealDB Flutter client supports middleware that allows you to intercept and modify requests before they are sent to the database. This is particularly useful for implementing token refresh functionality or adding logging.
103+
104+
#### Adding Middleware
105+
106+
You can add middleware using the `addMiddleware` method:
107+
108+
```dart
109+
db.addMiddleware((method, params, next) async {
110+
// Do something before the request
111+
print('Before request: $method with params: $params');
112+
113+
// Execute the request
114+
final result = await next();
115+
116+
// Do something after the request
117+
print('After request: $method with result: $result');
118+
119+
return result;
120+
});
121+
```
122+
123+
Multiple middleware functions can be added, and they will be executed in the order they were added.
101124

125+
#### Token Refresh Example
126+
127+
Here's how you can implement token refresh using middleware:
128+
129+
```dart
130+
db.addMiddleware((method, params, next) async {
131+
try {
132+
// Try to execute the request normally
133+
return await next();
134+
} catch (e) {
135+
// If we get an authentication error
136+
if (e.toString().contains('authentication invalid')) {
137+
// Refresh the token (implement your token refresh logic)
138+
final newToken = await refreshToken();
139+
140+
// Re-authenticate with the new token
141+
await db.authenticate(newToken);
142+
143+
// Retry the original request
144+
return await next();
145+
}
146+
// For other errors, just rethrow
147+
rethrow;
148+
}
149+
});
150+
```
151+
152+
This middleware will catch authentication errors, refresh the token, and retry the original request automatically.
153+
154+
### Basic Queries
102155

103156
SurrealDB allows executing a variety of commands, including CRUD operations (Create, Read, Update, Delete) via queries. Here's how you can perform simple database operations.
104157

lib/src/surrealdb.dart

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,53 @@ import 'package:surrealdb/src/pinger.dart';
44
import 'package:surrealdb/src/ws.dart';
55
import 'package:surrealdb/surrealdb.dart';
66

7+
/// Type definition for middleware function
8+
/// [method] - The method being called
9+
/// [params] - The parameters passed to the method
10+
/// [next] - The function to call to continue the middleware chain
11+
typedef Middleware = Future<Object?> Function(
12+
String method,
13+
List<Object?> params,
14+
Future<Object?> Function() next,
15+
);
16+
17+
/// SurrealDB client for Dart & Flutter
18+
///
19+
/// This client provides an interface to interact with SurrealDB via WebSockets.
20+
/// It supports authentication, database operations, and middleware for request
21+
/// manipulation.
22+
///
23+
/// Example usage with token refresh middleware:
24+
/// ```dart
25+
/// final db = SurrealDB('ws://localhost:8000');
26+
/// db.connect();
27+
/// await db.wait();
28+
///
29+
/// // Add a token refresh middleware
30+
/// db.addMiddleware((method, params, next) async {
31+
/// try {
32+
/// // Try to execute the request normally
33+
/// return await next();
34+
/// } catch (e) {
35+
/// // If we get an authentication error
36+
/// if (e.toString().contains('authentication invalid')) {
37+
/// // Refresh the token
38+
/// final newToken = await refreshToken(); // Your token refresh logic
39+
///
40+
/// // Re-authenticate with the new token
41+
/// await db.authenticate(newToken);
42+
///
43+
/// // Retry the original request
44+
/// return await next();
45+
/// }
46+
/// // For other errors, just rethrow
47+
/// rethrow;
48+
/// }
49+
/// });
50+
///
51+
/// // Now all requests will automatically refresh the token if needed
52+
/// final results = await db.select('users');
53+
/// ```
754
class SurrealDB {
855
SurrealDB(
956
this.url, {
@@ -18,6 +65,9 @@ class SurrealDB {
1865
Pinger? _pinger;
1966
late final WSService _wsService;
2067
final SurrealDBOptions options;
68+
69+
/// List of middleware functions to execute before each request
70+
final List<Middleware> _middlewares = [];
2171

2272
/// Connects to a local or remote database endpoint.
2373
void connect() {
@@ -127,6 +177,25 @@ class SurrealDB {
127177
return _wsService.rpc(Methods.invalidate);
128178
}
129179

180+
/// Adds a middleware function to be executed before each request
181+
///
182+
/// Middleware functions are executed in the order they are added.
183+
/// Each middleware function must call the next function to continue the chain.
184+
///
185+
/// Example:
186+
/// ```dart
187+
/// db.addMiddleware((method, params, next) async {
188+
/// print('Before request: $method');
189+
/// final result = await next();
190+
/// print('After request: $method');
191+
/// return result;
192+
/// });
193+
/// ```
194+
void addMiddleware(Middleware middleware) {
195+
_middlewares.add(middleware);
196+
_wsService.setMiddlewares(_middlewares);
197+
}
198+
130199
/// Authenticates the current connection with a JWT [token].
131200
Future<void> authenticate(String token) {
132201
return _wsService.rpc(Methods.authenticate, [token]);

lib/src/ws.dart

Lines changed: 68 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ import 'package:web_socket_channel/web_socket_channel.dart';
1111
typedef WsFunctionParam = Map<String, dynamic>;
1212
typedef WsFunction = void Function(WsFunctionParam);
1313

14+
typedef Middleware = Future<Object?> Function(
15+
String method,
16+
List<Object?> params,
17+
Future<Object?> Function() next,
18+
);
19+
1420
class WSService {
1521
WSService(this.url, this.options);
1622
final String url;
@@ -23,6 +29,14 @@ class WSService {
2329
var _shouldReconnect = false;
2430

2531
var _reconnectDuration = const Duration(milliseconds: 100);
32+
33+
final List<Middleware> _middlewares = [];
34+
35+
void setMiddlewares(List<Middleware> middlewares) {
36+
_middlewares
37+
..clear()
38+
..addAll(middlewares);
39+
}
2640

2741
Future<void> connect() async {
2842
_shouldReconnect = true;
@@ -87,47 +101,71 @@ class WSService {
87101
String method, [
88102
List<Object?> data = const [],
89103
Duration? timeout,
90-
]) {
91-
final completer = Completer<Object?>();
92-
104+
]) async {
93105
final ws = _ws;
94106

95107
if (ws == null) {
96-
return (completer..completeError('websocket not connected')).future;
108+
throw Exception('websocket not connected');
97109
}
98110

99-
final id = getNextId();
100-
101-
ws.sink.add(
102-
jsonEncode(
103-
{
104-
'method': method,
105-
'id': id,
106-
'params': data,
107-
},
108-
),
109-
);
110-
111-
if (timeout != Duration.zero) {
112-
Future.delayed(
113-
options.timeoutDuration,
114-
() {
115-
if (completer.isCompleted) return;
116-
completer.completeError(TimeoutException('timeout', timeout));
117-
},
111+
// Function to execute the actual RPC call without middleware
112+
Future<Object?> executeActualRpc() async {
113+
final id = getNextId();
114+
final completer = Completer<Object?>();
115+
116+
ws.sink.add(
117+
jsonEncode(
118+
{
119+
'method': method,
120+
'id': id,
121+
'params': data,
122+
},
123+
),
118124
);
125+
126+
if (timeout != Duration.zero) {
127+
Future.delayed(
128+
options.timeoutDuration,
129+
() {
130+
if (completer.isCompleted) return;
131+
completer.completeError(TimeoutException('timeout', timeout));
132+
},
133+
);
134+
}
135+
136+
_methodBus.once<RpcResponse>(id, (rpcResponse) {
137+
if (completer.isCompleted) return;
138+
if (rpcResponse.error != null) {
139+
completer.completeError(rpcResponse.error!);
140+
} else {
141+
completer.complete(rpcResponse.data);
142+
}
143+
});
144+
145+
return completer.future;
119146
}
120147

121-
_methodBus.once<RpcResponse>(id, (rpcResponse) {
122-
if (completer.isCompleted) return;
123-
if (rpcResponse.error != null) {
124-
completer.completeError(rpcResponse.error!);
148+
// If there are no middlewares, just execute the RPC call directly
149+
if (_middlewares.isEmpty) {
150+
return executeActualRpc();
151+
}
152+
153+
// Build the middleware chain
154+
var index = 0;
155+
156+
Future<Object?> executeMiddlewareChain() async {
157+
if (index < _middlewares.length) {
158+
final middleware = _middlewares[index++];
159+
// Execute the middleware and pass the next middleware in the chain
160+
return middleware(method, data, executeMiddlewareChain);
125161
} else {
126-
completer.complete(rpcResponse.data);
162+
// All middlewares executed, perform the actual RPC call
163+
return executeActualRpc();
127164
}
128-
});
165+
}
129166

130-
return completer.future;
167+
// Start executing the middleware chain
168+
return executeMiddlewareChain();
131169
}
132170

133171
Future<void> _handleMessage(Map<String, dynamic> data) async {

pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
name: surrealdb
22
description: SurrealDB client written in pure dart. Auto reconnect, Typed functions
3-
version: 1.0.2
3+
version: 1.1.0
44
repository: https://github.com/duhanbalci/surrealdb_flutter
55

66
environment:
7-
sdk: '>=3.0.0 <4.0.0'
7+
sdk: ">=3.0.0 <4.0.0"
88

99
dependencies:
1010
uuid: ^4.5.0

0 commit comments

Comments
 (0)