Skip to content

pavelgj/zrpc-dart

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ZRPC

Typesafe APIs for Dart, inspired by tRPC and powered by Schemantic.

Features

  • End-to-end typesafety: Share your schema and procedure definitions between client and server.
  • Request Batching: Automatic batching of queries and mutations into a single HTTP request.
  • Streaming: Built-in support for streaming queries and mutations with valid NDJSON responses.
  • Server Agnostic: Run on top of Shelf, bare dart:io, or any other compatible HTTP framework.
  • Runtime validation: Inputs and outputs are validated automatically at runtime.
  • Autocompletion: IDE support for your API routes.

Usage

1. Define your Schema & Procedures (Shared)

Create a shared library containing your data schemas and procedure definitions.

// shared.dart
import 'package:zrpc/zrpc.dart';

part 'shared.g.dart';

@Schematic()
abstract class UserSchema {
  String get id;
  String get name;
  bool get isAdmin;
}

// Define procedures with their input/output types
final greeting = ZRpc.query(
  'greeting',
  input: stringType(),
  output: stringType(),
);

final updateUser = ZRpc.mutation(
  'updateUser',
  input: UserType,
  output: UserType,
);

final streamTicker = ZRpc.streamQuery(
  'ticker',
  input: intType(), // tick count
  output: intType(), // current tick
);

2. Create the Router (Server)

Implement your API procedures.

// server.dart
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:zrpc/zrpc.dart';
import 'package:zrpc/adapters/shelf.dart';
import 'shared.dart';

class Context {
  final Request request;
  Context({required this.request});
}

final appRouter = router<Context>()
  .query(greeting, (ctx, name) async {
    return 'Hello, $name!';
  })
  .mutation(updateUser, (ctx, user) async {
    // Perform update...
    return user;
  })
  .streamQuery(streamTicker, (ctx, count) async* {
    for (int i = 0; i < count; i++) {
      await Future.delayed(Duration(seconds: 1));
      yield i;
    }
  });

void main() async {
  final handler = zrpcShelfHandler(
    router: appRouter,
    createContext: (request) => Context(request: request),
  );

  await shelf_io.serve(handler, 'localhost', 8080);
  print('Server running on localhost:8080');
}

3. Use the Client

Use the shared definitions to call the API in a typesafe way. Queries executed in the same microtask are automatically batched!

// client.dart
import 'package:zrpc/client.dart';
import 'shared.dart';

void main() async {
  final client = ZRpcClient('http://localhost:8080');

  // Fully typed based on the definition!
  final result = await client.query(greeting, 'World');
  print(result); // "Hello, World!"

  // Complex types work automatically
  final user = await client.mutation(
    updateUser,
    User.from(id: '1', name: 'New Name', isAdmin: false),
  );
  print(user.name);

  // Streaming works too!
  await for (final tick in client.streamQuery(streamTicker, 5)) {
    print('Tick: $tick');
  }
}

Request Batching

ZRpc automatically groups queries and mutations made within the same microtask execution into a single HTTP request (POST /batch).

  • Reduced Overhead: Fewer HTTP connections.
  • Concurrent Execution: The server processes batched requests concurrently.
  • Smart Streaming: Responses are streamed back via NDJSON as they complete, preventing head-of-line blocking.
// These two requests will be sent in a ONE HTTP call
Future.wait([
  client.query(greeting, 'One'),
  client.query(greeting, 'Two'),
]);

Installation

dart pub add zrpc

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages