TableRateLimits#
Extend TableRateLimits<S, R> in <table>_rate_limits.dart. Pass the schema ref to
super() and override methods as needed. Unoverridden operations use the default policy (100 req/min per IP).
import 'package:my_app/src/schemas/posts.dart';
import 'package:zonai_schema/zonai_schema.dart';
final class PostRateLimits extends TableRateLimits<PostTable, Post> {
PostRateLimits() : super(posts);
@override
Future<RateLimitPolicy?> createPolicy() async =>
const RateLimitPolicy(maxRequests: 10, window: Duration(minutes: 1));
@override
Future<RateLimitPolicy?> getPolicy() async =>
const RateLimitPolicy(maxRequests: 1000, window: Duration(minutes: 1));
@override
Future<RateLimitPolicy?> limitPolicy() async =>
const RateLimitPolicy(maxRequests: 500, window: Duration(minutes: 1));
}
PostRateLimits main() => PostRateLimits();
RateLimitPolicy#
RateLimitPolicy(maxRequests: N, window: Duration(...))
-
maxRequests— number of requests allowed per IP, per table and operation, within one window -
window— the length of one fixed window. It starts at the first counted request and resetswindowlater; refused requests do not extend it. See How the Window Works.
RateLimitPolicy.defaultPolicy is the built-in 100 requests per minute. Return it (.defaultPolicy) to keep the default for one case while overriding others.
Per-Operation Methods#
All methods are async and return Future<RateLimitPolicy?>. Paths use a JSON
table in the body (or ?body=), not a path segment.
| Method | Endpoints |
|---|---|
createPolicy() | POST /db, POST /db/many |
updatePolicy() | PATCH /db, PATCH /db/many |
deletePolicy() | DELETE /db, DELETE /db/many |
getPolicy() | GET /db, GET /db/stream |
limitPolicy() |
GET /db/list, GET /db/stream/list |
countPolicy() |
GET /db/count, GET /db/stream/count |
customPolicy(operation) |
PATCH /db/custom/:operation, PATCH /db/custom/:operation/many |
Streaming shares the read policies above. Details: Streaming.
A /many request counts as one request against the policy, however many rows it carries.
Methods you do not override keep the default: PostRateLimits above overrides only createPolicy,
getPolicy and limitPolicy, so count, update, delete
and custom operations on posts stay at 100 per minute.
customPolicy buckets separately per operation name (fill and reserve
on the same table get independent counters), but only for a name that's actually registered in that table's rules — an unrecognized
:operation is rejected with 404 before it ever reaches the rate limiter:
@override
Future<RateLimitPolicy?> customPolicy(String? operation) async {
return switch (operation) {
'fill' => const RateLimitPolicy(maxRequests: 20, window: Duration(minutes: 1)),
// Name unavailable -- one counter shared by every custom operation.
null => const RateLimitPolicy(maxRequests: 60, window: Duration(minutes: 1)),
_ => .defaultPolicy,
};
}
When operation is null#
Validating the name requires the table's rules, and the server can only read those without an IPC round-trip when rules are linked into the binary. When they aren't — a binary built without a project link, or
ZONAI_FORCE_WORKERS=1 — the name arrives unvalidated, and trusting it would hand callers a bypass: every new name they invent starts on a fresh counter.
So in that case the server drops the name rather than the limit. operation is null, all custom operations on the table share one counter, and the
404 for an unregistered name is skipped — the rules layer still denies it, just later in the request. Handle
null if you want that shared counter to differ from defaultPolicy.
Disabling Rate Limiting for an Operation#
Return null to remove rate limiting for that operation entirely:
@override
Future<RateLimitPolicy?> getPolicy() async => null; // No limit on view requests
Example: Different Limits for Reads and Writes#
import 'package:my_app/src/schemas/articles.dart';
import 'package:zonai_schema/zonai_schema.dart';
final class ArticleRateLimits extends TableRateLimits<ArticleTable, Article> {
ArticleRateLimits() : super(articles);
// Strict limits for writes
@override
Future<RateLimitPolicy?> createPolicy() async =>
const RateLimitPolicy(maxRequests: 5, window: Duration(minutes: 1));
@override
Future<RateLimitPolicy?> updatePolicy() async =>
const RateLimitPolicy(maxRequests: 20, window: Duration(minutes: 1));
@override
Future<RateLimitPolicy?> deletePolicy() async =>
const RateLimitPolicy(maxRequests: 5, window: Duration(minutes: 1));
// Relaxed limits for reads
@override
Future<RateLimitPolicy?> getPolicy() async =>
const RateLimitPolicy(maxRequests: 2000, window: Duration(minutes: 1));
@override
Future<RateLimitPolicy?> limitPolicy() async =>
const RateLimitPolicy(maxRequests: 1000, window: Duration(minutes: 1));
}
ArticleRateLimits main() => ArticleRateLimits();