From 8cc866c7117bdf051dc259b5e6708af9ceaf4efc Mon Sep 17 00:00:00 2001 From: Crazelu Date: Thu, 8 Jan 2026 23:20:32 +0100 Subject: [PATCH 1/4] docs: Type-safe code generated interface for future calls --- .../01-overview.md} | 80 +----------------- docs/06-concepts/14-scheduling/02-setup.md | 82 +++++++++++++++++++ docs/06-concepts/14-scheduling/03-legacy.md | 75 +++++++++++++++++ .../06-concepts/14-scheduling/_category_.json | 4 + docs/06-concepts/17-backward-compatibility.md | 2 + 5 files changed, 165 insertions(+), 78 deletions(-) rename docs/06-concepts/{14-scheduling.md => 14-scheduling/01-overview.md} (52%) create mode 100644 docs/06-concepts/14-scheduling/02-setup.md create mode 100644 docs/06-concepts/14-scheduling/03-legacy.md create mode 100644 docs/06-concepts/14-scheduling/_category_.json diff --git a/docs/06-concepts/14-scheduling.md b/docs/06-concepts/14-scheduling/01-overview.md similarity index 52% rename from docs/06-concepts/14-scheduling.md rename to docs/06-concepts/14-scheduling/01-overview.md index 1206f994..f4cd57d8 100644 --- a/docs/06-concepts/14-scheduling.md +++ b/docs/06-concepts/14-scheduling/01-overview.md @@ -1,92 +1,16 @@ -# Scheduling +# Overview With Serverpod you can schedule future work with the `future call` feature. Future calls are calls that will be invoked at a later time. An example is if you want to send a drip-email campaign after a user signs up. You can schedule a future call for a day, a week, or a month. The calls are stored in the database, so they will persist even if the server is restarted. A future call is guaranteed to only execute once across all your instances that are running, but execution failures are not handled automatically. It is your responsibility to schedule a new future call if the work was not able to complete. -## Future calls - -Creating a future call is simple, extend the `FutureCall` class and override the `invoke` method. The method takes two params the first being the [`Session`](sessions) object and the second being an optional SerializableModel ([See models](models)). - :::info The future call feature is not enabled when running Serverpod in serverless mode. ::: -```dart -import 'package:serverpod/serverpod.dart'; - -class ExampleFutureCall extends FutureCall { - @override - Future invoke(Session session, MyModelEntity? object) async { - // Do something interesting in the future here. - } -} -``` - -To let your Server get access to the future call you have to register it in the main run method in your `server.dart` file. You register the future call by calling `registerFutureCall` on the Serverpod object and giving it an instance of the future call together with a string that gives the future call a name. The name has to be globally unique and is used to later invoke the future call. - -```dart -void run(List args) async { - final pod = Serverpod( - args, - Protocol(), - Endpoints(), - ); - - ... - - pod.registerFutureCall(ExampleFutureCall(), 'exampleFutureCall'); - - ... -} -``` - -You are now able to register a future call to be invoked in the future by calling either `futureCallWithDelay` or `futureCallAtTime` depending on your needs. - -Invoke the future call 1 hour from now by calling `futureCallWithDelay`. - -```dart -await session.serverpod.futureCallWithDelay( - 'exampleFutureCall', - data, - const Duration(hours: 1), -); -``` - -Invoke the future call at a specific time and/or date in the future by calling `futureCallAtTime`. - -```dart -await session.serverpod.futureCallAtTime( - 'exampleFutureCall', - data, - DateTime(2025, 1, 1), -); -``` - -:::note -`data` is an object created from a class defined in one of your yaml files and has to be the same as the one you expect to receive in the future call. in the `model` folder, `data` may also be null if you don't need it. -::: - -When registering a future call it is also possible to give it an `identifier` so that it can be referenced later. The same identifier can be applied to multiple future calls. - -```dart -await session.serverpod.futureCallWithDelay( - 'exampleFutureCall', - data, - const Duration(hours: 1), - identifier: 'an-identifying-string', -); -``` - -This identifier can then be used to cancel all future calls registered with said identifier. - -```dart -await session.serverpod.cancelFutureCall('an-identifying-string'); -``` - ## Configuration -Future calls can be configured using options defined in the configuration files or environment variables. For a detailed list of configuration options, refer to the [Configuration](07-configuration.md) page. +Future calls can be configured using options defined in the configuration files or environment variables. For a detailed list of configuration options, refer to the [Configuration](../configuration) page. Below is an example of how you can configure future calls in a YAML file: diff --git a/docs/06-concepts/14-scheduling/02-setup.md b/docs/06-concepts/14-scheduling/02-setup.md new file mode 100644 index 00000000..b64ab729 --- /dev/null +++ b/docs/06-concepts/14-scheduling/02-setup.md @@ -0,0 +1,82 @@ +# Setup + +To create future calls, extend the `FutureCall` class and define the methods you wish to invoke at a later time. + +```dart +import 'package:serverpod/serverpod.dart'; + +class ExampleFutureCall extends FutureCall { + Future doWork(Session session, String data) async { + // Do something interesting in the future here. + } +} +``` + +:::info +For a method to be recognized by Serverpod as a future call, it must return a `Future` and take at least two parameters. The first parameter must be a [`Session`](../sessions) object. You can pass any serializable types as other parameters, and even use `List`, `Map`, `Set` or Dart records as long as they are typed. +::: + +Next, you need to generate the code for your future calls. You do this by running `serverpod generate` in the server directory of your project: + +```bash +$ cd your_server +$ serverpod generate +``` + +`serverpod generate` will create a type-safe interface for invoking the future calls in the server's `generated/future_calls.dart` file. This interface can be accessed from the Serverpod object. + +The future calls you create are registered by `Serverpod` after the server starts. + +```dart +void run(List args) async { + final pod = Serverpod( + args, + Protocol(), + Endpoints(), + ); + + ... + + await pod.start(); + + ... +} +``` + +You are now able to register a future call to be invoked in the future by calling either `callWithDelay` or `callAtTime` depending on your needs. + +Invoke the future call 1 hour from now by calling `callWithDelay`. + +```dart +await pod.futureCalls + .callWithDelay(const Duration(hours: 1)) + .example + .doWork('1'); +``` + +Invoke the future call at a specific time and/or date in the future by calling `callAtTime`. + +```dart +await pod.futureCalls + .callAtTime(DateTime(2026, 1, 1)) + .example + .doWork('2'); +``` + +When registering a future call, it is also possible to give it an `identifier` so that it can be referenced later. The same identifier can be applied to multiple future calls. + +```dart +await pod.futureCalls + .callWithDelay( + const Duration(hours: 1), + identifier: 'an-identifying-string', + ) + .example + .doWork('1'); +``` + +This identifier can then be used to cancel all future calls registered with said identifier. + +```dart +await pod.futureCalls.cancel('an-identifying-string'); +``` diff --git a/docs/06-concepts/14-scheduling/03-legacy.md b/docs/06-concepts/14-scheduling/03-legacy.md new file mode 100644 index 00000000..bf90cb3b --- /dev/null +++ b/docs/06-concepts/14-scheduling/03-legacy.md @@ -0,0 +1,75 @@ +# Legacy + +Creating a future call is simple, extend the `FutureCall` class and override the `invoke` method. The method takes two params the first being the [`Session`](../sessions) object and the second being an optional SerializableModel ([See models](../models)). + +```dart +import 'package:serverpod/serverpod.dart'; + +class ExampleFutureCall extends FutureCall { + @override + Future invoke(Session session, MyModelEntity? object) async { + // Do something interesting in the future here. + } +} +``` + +To let your Server get access to the future call you have to register it in the main run method in your `server.dart` file. You register the future call by calling `registerFutureCall` on the Serverpod object and giving it an instance of the future call together with a string that gives the future call a name. The name has to be globally unique and is used to later invoke the future call. + +```dart +void run(List args) async { + final pod = Serverpod( + args, + Protocol(), + Endpoints(), + ); + + ... + + pod.registerFutureCall(ExampleFutureCall(), 'exampleFutureCall'); + + ... +} +``` + +You are now able to register a future call to be invoked in the future by calling either `futureCallWithDelay` or `futureCallAtTime` depending on your needs. + +Invoke the future call 1 hour from now by calling `futureCallWithDelay`. + +```dart +await session.serverpod.futureCallWithDelay( + 'exampleFutureCall', + data, + const Duration(hours: 1), +); +``` + +Invoke the future call at a specific time and/or date in the future by calling `futureCallAtTime`. + +```dart +await session.serverpod.futureCallAtTime( + 'exampleFutureCall', + data, + DateTime(2025, 1, 1), +); +``` + +:::note +`data` is an object created from a class defined in one of your yaml files and has to be the same as the one you expect to receive in the future call. in the `model` folder, `data` may also be null if you don't need it. +::: + +When registering a future call, it is also possible to give it an `identifier` so that it can be referenced later. The same identifier can be applied to multiple future calls. + +```dart +await session.serverpod.futureCallWithDelay( + 'exampleFutureCall', + data, + const Duration(hours: 1), + identifier: 'an-identifying-string', +); +``` + +This identifier can then be used to cancel all future calls registered with said identifier. + +```dart +await session.serverpod.cancelFutureCall('an-identifying-string'); +``` diff --git a/docs/06-concepts/14-scheduling/_category_.json b/docs/06-concepts/14-scheduling/_category_.json new file mode 100644 index 00000000..dc0af035 --- /dev/null +++ b/docs/06-concepts/14-scheduling/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Scheduling", + "collapsed": true +} \ No newline at end of file diff --git a/docs/06-concepts/17-backward-compatibility.md b/docs/06-concepts/17-backward-compatibility.md index 8e1f3597..21729d67 100644 --- a/docs/06-concepts/17-backward-compatibility.md +++ b/docs/06-concepts/17-backward-compatibility.md @@ -7,6 +7,8 @@ Following a simple set of rules, your server will stay compatible with older app 1. __Avoid changing parameter names in endpoint methods.__ In the REST API Serverpod generates, the parameters are passed by name. This means that changing the parameter names of the endpoint methods will break backward compatibility. 2. __Do not delete endpoint methods or change their signature.__ Instead, add new methods if you must pass another set of parameters. Technically, you can add new named parameters if they are not required, but creating a new method may still feel cleaner. 3. __Avoid changing or removing fields and types in the serialized classes.__ However, you are free to add new fields as long as they are nullable. +4. __Avoid changing parameter names in future call methods.__ Changing the parameter names of the future call methods will break backward compatibility since parameters are passed by name. +5. __Do not delete future call methods or change their signature.__ Instead, add new methods if you must pass another set of parameters. ## Managing breaking changes with endpoint inheritance From 7837728f36e3312f21192e00025a6d371f5f0d70 Mon Sep 17 00:00:00 2001 From: Crazelu Date: Tue, 20 Jan 2026 00:11:46 +0100 Subject: [PATCH 2/4] docs: Updates future calls documentation --- docs/06-concepts/14-scheduling/01-setup.md | 99 +++++++++++++++++++ .../14-scheduling/02-recurring-task.md | 71 +++++++++++++ docs/06-concepts/14-scheduling/02-setup.md | 82 --------------- .../14-scheduling/03-inheritance.md | 87 ++++++++++++++++ .../{01-overview.md => 04-configuration.md} | 12 +-- .../{03-legacy.md => 05-legacy.md} | 4 + docs/06-concepts/17-backward-compatibility.md | 2 +- 7 files changed, 263 insertions(+), 94 deletions(-) create mode 100644 docs/06-concepts/14-scheduling/01-setup.md create mode 100644 docs/06-concepts/14-scheduling/02-recurring-task.md delete mode 100644 docs/06-concepts/14-scheduling/02-setup.md create mode 100644 docs/06-concepts/14-scheduling/03-inheritance.md rename docs/06-concepts/14-scheduling/{01-overview.md => 04-configuration.md} (74%) rename docs/06-concepts/14-scheduling/{03-legacy.md => 05-legacy.md} (91%) diff --git a/docs/06-concepts/14-scheduling/01-setup.md b/docs/06-concepts/14-scheduling/01-setup.md new file mode 100644 index 00000000..5ceeefba --- /dev/null +++ b/docs/06-concepts/14-scheduling/01-setup.md @@ -0,0 +1,99 @@ +# Setup + +Serverpod supports scheduling future work with the `future call` feature. Future calls are calls that will be invoked at a later time. An example is if you want to send a drip-email campaign after a user signs up. You can schedule a future call for a day, a week, a month, or a [recurring interval](recurring-task). The calls are stored in the database, so they will persist even if the server is restarted. + +A future call is guaranteed to only execute once across all your instances that are running, but execution failures are not handled automatically. It is your responsibility to schedule a new future call if the work was not able to complete. + +To create future calls, extend the `FutureCall` class and define the methods you wish to invoke at a later time. + +```dart +import 'package:serverpod/serverpod.dart'; + +class ExampleFutureCall extends FutureCall { + Future doWork(Session session, String data) async { + // Do something interesting in the future here. + } + + Future doOtherWork(Session session, String data) async { + // Do something interesting in the future here. + } +} +``` + +:::info +For a method to be recognized by Serverpod as a future call, it must return a `Future` and take at least two parameters. The first parameter must be a [`Session`](../sessions) object. You can pass any serializable types as other parameters, and even use `List`, `Map`, `Set` or Dart records as long as they are typed. `Streaming` parameters are not supported. +::: + +Next, you need to generate the code for your future calls: + +```bash +$ serverpod generate +``` + +`serverpod generate` will create a type-safe interface for invoking the future calls in the server's `generated/future_calls.dart` file. This interface can be accessed from the Serverpod object. + +The future calls you create are registered by `Serverpod` after the server starts. + +```dart + import 'package:serverpod/serverpod.dart'; + import 'package:serverpod_auth_idp_server/core.dart'; + + import 'src/generated/protocol.dart'; + import 'src/generated/endpoints.dart'; + + void run(List args) async { + final pod = Serverpod( + args, + Protocol(), + Endpoints(), + ); + + await pod.start(); +} +``` + +You are now able to schedule future calls to be invoked in the future by calling either `callWithDelay` or `callAtTime` depending on your needs. + +:::warning +Scheduling a future call before the server starts will lead to exceptions. +::: + +Invoke a future call 1 hour from now by calling `callWithDelay`. + +```dart +await pod.futureCalls + .callWithDelay(const Duration(hours: 1)) + .example + .doWork('1'); +``` + +Invoke a future call at a specific time and/or date in the future by calling `callAtTime`. + +```dart +await pod.futureCalls + .callAtTime(DateTime(2026, 1, 1)) + .example + .doOtherWork('2'); +``` + +When scheduling a future call, it is also possible to give it an `identifier` so that it can be referenced later. The same identifier can be applied to multiple future calls. + +```dart +await pod.futureCalls + .callWithDelay( + const Duration(hours: 1), + identifier: 'an-identifying-string', + ) + .example + .doWork('1'); +``` + +This identifier can then be used to cancel all future calls scheduled with said identifier. + +```dart +await pod.futureCalls.cancel('an-identifying-string'); +``` + +:::info +The future call feature is not enabled when running Serverpod in serverless mode. +::: diff --git a/docs/06-concepts/14-scheduling/02-recurring-task.md b/docs/06-concepts/14-scheduling/02-recurring-task.md new file mode 100644 index 00000000..0ae25963 --- /dev/null +++ b/docs/06-concepts/14-scheduling/02-recurring-task.md @@ -0,0 +1,71 @@ +# Recurring Task + +The recommended way to achieve cron-like scheduling is by scheduling a future call inside another. +To set this up, extend the `FutureCall` class and define two methods. + +```dart +import 'package:serverpod/serverpod.dart'; + +class ExampleFutureCall extends FutureCall { + Future doWork(Session session, int input) async { + await _doWork(session, input); + } + + Future _doWork(Session session, int input) async { + session.log('Working with input $input'); + } +} +``` + +Next, generate the code for your future call: + +```bash +$ serverpod generate +``` + +:::info +Code is only generated for the public method while the private method contains the logic to be invoked as a recurring task. +::: + +Next, import the generated `endpoints.dart` file and schedule the recurring future call using the generated code: + +```dart +import 'package:serverpod/serverpod.dart'; +import 'generated/endpoints.dart'; + +class ExampleFutureCall extends FutureCall { + Future doWork(Session session, int input) async { + await session.serverpod.futureCalls + .callWithDelay(const Duration(minutes: 20)) + .example + .doWork(input + 1); + + await _doWork(session, input); + } + + Future _doWork(Session session, int input) async { + session.log('Working with input $input'); + } +} +``` + +Now when you schedule the `doWork` future call, it will continously invoke `_doWork` at an interval of 20 minutes. + +```dart +import 'package:serverpod/serverpod.dart'; +import 'package:serverpod_auth_idp_server/core.dart'; + +import 'src/generated/protocol.dart'; +import 'src/generated/endpoints.dart'; + +void run(List args) async { + final pod = Serverpod( + args, + Protocol(), + Endpoints(), + ); + + await pod.start(); + await pod.futureCalls.callWithDelay(Duration(minutes: 20)).example.doWork(2); +} +``` diff --git a/docs/06-concepts/14-scheduling/02-setup.md b/docs/06-concepts/14-scheduling/02-setup.md deleted file mode 100644 index b64ab729..00000000 --- a/docs/06-concepts/14-scheduling/02-setup.md +++ /dev/null @@ -1,82 +0,0 @@ -# Setup - -To create future calls, extend the `FutureCall` class and define the methods you wish to invoke at a later time. - -```dart -import 'package:serverpod/serverpod.dart'; - -class ExampleFutureCall extends FutureCall { - Future doWork(Session session, String data) async { - // Do something interesting in the future here. - } -} -``` - -:::info -For a method to be recognized by Serverpod as a future call, it must return a `Future` and take at least two parameters. The first parameter must be a [`Session`](../sessions) object. You can pass any serializable types as other parameters, and even use `List`, `Map`, `Set` or Dart records as long as they are typed. -::: - -Next, you need to generate the code for your future calls. You do this by running `serverpod generate` in the server directory of your project: - -```bash -$ cd your_server -$ serverpod generate -``` - -`serverpod generate` will create a type-safe interface for invoking the future calls in the server's `generated/future_calls.dart` file. This interface can be accessed from the Serverpod object. - -The future calls you create are registered by `Serverpod` after the server starts. - -```dart -void run(List args) async { - final pod = Serverpod( - args, - Protocol(), - Endpoints(), - ); - - ... - - await pod.start(); - - ... -} -``` - -You are now able to register a future call to be invoked in the future by calling either `callWithDelay` or `callAtTime` depending on your needs. - -Invoke the future call 1 hour from now by calling `callWithDelay`. - -```dart -await pod.futureCalls - .callWithDelay(const Duration(hours: 1)) - .example - .doWork('1'); -``` - -Invoke the future call at a specific time and/or date in the future by calling `callAtTime`. - -```dart -await pod.futureCalls - .callAtTime(DateTime(2026, 1, 1)) - .example - .doWork('2'); -``` - -When registering a future call, it is also possible to give it an `identifier` so that it can be referenced later. The same identifier can be applied to multiple future calls. - -```dart -await pod.futureCalls - .callWithDelay( - const Duration(hours: 1), - identifier: 'an-identifying-string', - ) - .example - .doWork('1'); -``` - -This identifier can then be used to cancel all future calls registered with said identifier. - -```dart -await pod.futureCalls.cancel('an-identifying-string'); -``` diff --git a/docs/06-concepts/14-scheduling/03-inheritance.md b/docs/06-concepts/14-scheduling/03-inheritance.md new file mode 100644 index 00000000..ac1a0284 --- /dev/null +++ b/docs/06-concepts/14-scheduling/03-inheritance.md @@ -0,0 +1,87 @@ +# Inheritance + +Inheritance gives you the possibility to modify the behavior of `FutureCall` classes defined in other Serverpod modules. If the parent `FutureCall` class was marked as `abstract`, no code is generated for it. + +Currently, there are the following possibilities to extend another `FutureCall` class: + +## Inheriting from a `FutureCall` class + +Given an existing `FutureCall` class, it is possible to extend or modify its behavior while retaining the already exposed methods. + +```dart +import 'package:serverpod/serverpod.dart'; + +class Greeter extends FutureCall { + Future hello(Session session, String name) async { + session.log('Hello $name'); + } +} + +class MyGreeter extends Greeter { + Future bye(Session session, String name) async { + session.log('Bye $name'); + } +} +``` + +The generated server code will now be able to access both `Greeter` and `MyGreeter`. +Whereas the `Greeter` only exposes the original `hello` method, `MyGreeter` now exposes both the inherited `hello` and its own `bye` methods. + +## Inheriting from a `FutureCall` class marked `abstract` + +Future calls marked as `abstract` are not added to the server. But if they are subclassed, their methods will be exposed through the subclass. + +```dart +import 'package:serverpod/serverpod.dart'; + +abstract class Greeter extends FutureCall { + Future hello(Session session, String name) async { + session.log('Hello $name'); + } +} + +class MyGreeter extends Greeter {} +``` + +Since `Greeter` is `abstract`, it will not be added to the server. However, `MyGreeter` will expose a single `hello` method. + +### Extending an `abstract` `FutureCall` class + +In the above example, the `MyGreeter` only exposed the inherited `hello` method. It can be further extended with custom methods like this: + +```dart +import 'package:serverpod/serverpod.dart'; + +class MyGreeter extends Greeter { + Future bye(Session session, String name) async { + session.log('Bye $name'); + } +} +``` + +In this case, it will expose both a `hello` and a `bye` method. + +### Overriding future call methods + +It is possible to override methods of the superclass. This can be useful when you want to modify the behavior of specific methods but preserve the rest. + +```dart +import 'package:serverpod/serverpod.dart'; + +abstract class Greeter extends FutureCall { + Future hello(Session session, String name) async { + session.log('Hello $name'); + } +} + +class ExcitedGreeter extends Greeter { + @override + Future hello(Session session, String name) async { + session.log('Hello $name!!!'); + } +} +``` + +Since `Greeter` is `abstract`, it will not be exposed on the server. The `ExcitedGreeter` will expose a single `hello` method, and its implementation will augment the superclass's one by adding `!!!` to the output. + +This way, you can modify the behavior of future call methods while still sharing the implementation through calls to `super`. Be aware that the method signature has to be compatible with the base class per Dart's rules, meaning you can add optional parameters, but can not add required parameters or change the return type. diff --git a/docs/06-concepts/14-scheduling/01-overview.md b/docs/06-concepts/14-scheduling/04-configuration.md similarity index 74% rename from docs/06-concepts/14-scheduling/01-overview.md rename to docs/06-concepts/14-scheduling/04-configuration.md index f4cd57d8..afb7d137 100644 --- a/docs/06-concepts/14-scheduling/01-overview.md +++ b/docs/06-concepts/14-scheduling/04-configuration.md @@ -1,14 +1,4 @@ -# Overview - -With Serverpod you can schedule future work with the `future call` feature. Future calls are calls that will be invoked at a later time. An example is if you want to send a drip-email campaign after a user signs up. You can schedule a future call for a day, a week, or a month. The calls are stored in the database, so they will persist even if the server is restarted. - -A future call is guaranteed to only execute once across all your instances that are running, but execution failures are not handled automatically. It is your responsibility to schedule a new future call if the work was not able to complete. - -:::info -The future call feature is not enabled when running Serverpod in serverless mode. -::: - -## Configuration +# Configuration Future calls can be configured using options defined in the configuration files or environment variables. For a detailed list of configuration options, refer to the [Configuration](../configuration) page. diff --git a/docs/06-concepts/14-scheduling/03-legacy.md b/docs/06-concepts/14-scheduling/05-legacy.md similarity index 91% rename from docs/06-concepts/14-scheduling/03-legacy.md rename to docs/06-concepts/14-scheduling/05-legacy.md index bf90cb3b..0540884c 100644 --- a/docs/06-concepts/14-scheduling/03-legacy.md +++ b/docs/06-concepts/14-scheduling/05-legacy.md @@ -1,5 +1,9 @@ # Legacy +:::warning +This approach is error prone since it involves manually registering and scheduling future calls using string identifiers. The recommended way to interact with the future calls feature is through the [type-safe API](setup). +::: + Creating a future call is simple, extend the `FutureCall` class and override the `invoke` method. The method takes two params the first being the [`Session`](../sessions) object and the second being an optional SerializableModel ([See models](../models)). ```dart diff --git a/docs/06-concepts/17-backward-compatibility.md b/docs/06-concepts/17-backward-compatibility.md index 21729d67..55eaf519 100644 --- a/docs/06-concepts/17-backward-compatibility.md +++ b/docs/06-concepts/17-backward-compatibility.md @@ -6,7 +6,7 @@ Following a simple set of rules, your server will stay compatible with older app 1. __Avoid changing parameter names in endpoint methods.__ In the REST API Serverpod generates, the parameters are passed by name. This means that changing the parameter names of the endpoint methods will break backward compatibility. 2. __Do not delete endpoint methods or change their signature.__ Instead, add new methods if you must pass another set of parameters. Technically, you can add new named parameters if they are not required, but creating a new method may still feel cleaner. -3. __Avoid changing or removing fields and types in the serialized classes.__ However, you are free to add new fields as long as they are nullable. +3. __Avoid changing or removing fields and types in the serialized classes.__ However, you are free to add new fields as long as they are nullable or have a default value. 4. __Avoid changing parameter names in future call methods.__ Changing the parameter names of the future call methods will break backward compatibility since parameters are passed by name. 5. __Do not delete future call methods or change their signature.__ Instead, add new methods if you must pass another set of parameters. From 401e2f716a24955170bb62c591aabc5a3ee6b518 Mon Sep 17 00:00:00 2001 From: Crazelu Date: Tue, 20 Jan 2026 13:45:00 +0100 Subject: [PATCH 3/4] docs: Adds info notes for future call inheritance and UTC timezone resolution --- docs/06-concepts/14-scheduling/01-setup.md | 4 ++++ docs/06-concepts/14-scheduling/03-inheritance.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/docs/06-concepts/14-scheduling/01-setup.md b/docs/06-concepts/14-scheduling/01-setup.md index 5ceeefba..59a9642d 100644 --- a/docs/06-concepts/14-scheduling/01-setup.md +++ b/docs/06-concepts/14-scheduling/01-setup.md @@ -76,6 +76,10 @@ await pod.futureCalls .doOtherWork('2'); ``` +:::info +Scheduling a future call at a specific time/date will always resolve the `DateTime` to UTC. +::: + When scheduling a future call, it is also possible to give it an `identifier` so that it can be referenced later. The same identifier can be applied to multiple future calls. ```dart diff --git a/docs/06-concepts/14-scheduling/03-inheritance.md b/docs/06-concepts/14-scheduling/03-inheritance.md index ac1a0284..9202385b 100644 --- a/docs/06-concepts/14-scheduling/03-inheritance.md +++ b/docs/06-concepts/14-scheduling/03-inheritance.md @@ -45,6 +45,10 @@ class MyGreeter extends Greeter {} Since `Greeter` is `abstract`, it will not be added to the server. However, `MyGreeter` will expose a single `hello` method. +:::info +Serverpod modules can expose future calls to users with `abstract` `FutureCall`. Code is only generated on the current project that extends the abstract future call. +::: + ### Extending an `abstract` `FutureCall` class In the above example, the `MyGreeter` only exposed the inherited `hello` method. It can be further extended with custom methods like this: From 34323f1b4ec0eb7e4fe8d53bd3221ee70b35bb51 Mon Sep 17 00:00:00 2001 From: Marcelo Soares Date: Tue, 20 Jan 2026 12:24:08 -0300 Subject: [PATCH 4/4] chore: Copy the docs over to the version 3.2.0 --- .../06-concepts/14-scheduling.md | 133 ------------------ .../06-concepts/14-scheduling/01-setup.md | 103 ++++++++++++++ .../14-scheduling/02-recurring-task.md | 71 ++++++++++ .../14-scheduling/03-inheritance.md | 91 ++++++++++++ .../14-scheduling/04-configuration.md | 47 +++++++ .../06-concepts/14-scheduling/05-legacy.md | 79 +++++++++++ .../06-concepts/14-scheduling/_category_.json | 4 + .../06-concepts/17-backward-compatibility.md | 6 +- 8 files changed, 399 insertions(+), 135 deletions(-) delete mode 100644 versioned_docs/version-3.2.0/06-concepts/14-scheduling.md create mode 100644 versioned_docs/version-3.2.0/06-concepts/14-scheduling/01-setup.md create mode 100644 versioned_docs/version-3.2.0/06-concepts/14-scheduling/02-recurring-task.md create mode 100644 versioned_docs/version-3.2.0/06-concepts/14-scheduling/03-inheritance.md create mode 100644 versioned_docs/version-3.2.0/06-concepts/14-scheduling/04-configuration.md create mode 100644 versioned_docs/version-3.2.0/06-concepts/14-scheduling/05-legacy.md create mode 100644 versioned_docs/version-3.2.0/06-concepts/14-scheduling/_category_.json diff --git a/versioned_docs/version-3.2.0/06-concepts/14-scheduling.md b/versioned_docs/version-3.2.0/06-concepts/14-scheduling.md deleted file mode 100644 index 1206f994..00000000 --- a/versioned_docs/version-3.2.0/06-concepts/14-scheduling.md +++ /dev/null @@ -1,133 +0,0 @@ -# Scheduling - -With Serverpod you can schedule future work with the `future call` feature. Future calls are calls that will be invoked at a later time. An example is if you want to send a drip-email campaign after a user signs up. You can schedule a future call for a day, a week, or a month. The calls are stored in the database, so they will persist even if the server is restarted. - -A future call is guaranteed to only execute once across all your instances that are running, but execution failures are not handled automatically. It is your responsibility to schedule a new future call if the work was not able to complete. - -## Future calls - -Creating a future call is simple, extend the `FutureCall` class and override the `invoke` method. The method takes two params the first being the [`Session`](sessions) object and the second being an optional SerializableModel ([See models](models)). - -:::info -The future call feature is not enabled when running Serverpod in serverless mode. -::: - -```dart -import 'package:serverpod/serverpod.dart'; - -class ExampleFutureCall extends FutureCall { - @override - Future invoke(Session session, MyModelEntity? object) async { - // Do something interesting in the future here. - } -} -``` - -To let your Server get access to the future call you have to register it in the main run method in your `server.dart` file. You register the future call by calling `registerFutureCall` on the Serverpod object and giving it an instance of the future call together with a string that gives the future call a name. The name has to be globally unique and is used to later invoke the future call. - -```dart -void run(List args) async { - final pod = Serverpod( - args, - Protocol(), - Endpoints(), - ); - - ... - - pod.registerFutureCall(ExampleFutureCall(), 'exampleFutureCall'); - - ... -} -``` - -You are now able to register a future call to be invoked in the future by calling either `futureCallWithDelay` or `futureCallAtTime` depending on your needs. - -Invoke the future call 1 hour from now by calling `futureCallWithDelay`. - -```dart -await session.serverpod.futureCallWithDelay( - 'exampleFutureCall', - data, - const Duration(hours: 1), -); -``` - -Invoke the future call at a specific time and/or date in the future by calling `futureCallAtTime`. - -```dart -await session.serverpod.futureCallAtTime( - 'exampleFutureCall', - data, - DateTime(2025, 1, 1), -); -``` - -:::note -`data` is an object created from a class defined in one of your yaml files and has to be the same as the one you expect to receive in the future call. in the `model` folder, `data` may also be null if you don't need it. -::: - -When registering a future call it is also possible to give it an `identifier` so that it can be referenced later. The same identifier can be applied to multiple future calls. - -```dart -await session.serverpod.futureCallWithDelay( - 'exampleFutureCall', - data, - const Duration(hours: 1), - identifier: 'an-identifying-string', -); -``` - -This identifier can then be used to cancel all future calls registered with said identifier. - -```dart -await session.serverpod.cancelFutureCall('an-identifying-string'); -``` - -## Configuration - -Future calls can be configured using options defined in the configuration files or environment variables. For a detailed list of configuration options, refer to the [Configuration](07-configuration.md) page. - -Below is an example of how you can configure future calls in a YAML file: - -```yaml -futureCallExecutionEnabled: true - -futureCall: - concurrencyLimit: 5 - scanInterval: 2000 -``` - -### Enable or disable future call execution - -This option allows you to enable or disable the execution of future calls. By default, it is set to `true`. You might want to disable future call execution in environments where you don't want background tasks to run, such as during testing or in a staging environment where you want to focus on API behavior without triggering scheduled tasks. - -Example configuration: - -```yaml -futureCallExecutionEnabled: false -``` - -### Concurrency limit - -This option sets the maximum number of future calls that can run concurrently. By default, it is set to `1`. Configuring this is useful if you have resource-intensive tasks and want to avoid overloading your server. For example, in a production environment, you might want to tune this value to ensure that not all of the server's resources are allocated to future calls, leaving room for other critical tasks. - -Setting this value to a negative number or `null` removes the limitation, allowing an unlimited number of concurrent future calls. However, this should be used with caution as it can lead to resource exhaustion. - -Example configuration: - -```yaml -futureCall: - concurrencyLimit: 5 # Adjust this value based on your server's capacity -``` - -### Scan interval - -This option determines how often the system scans for future calls to execute, in milliseconds. The default value is `5000` (5 seconds). Adjusting this interval can help balance responsiveness and resource usage. For example, reducing the interval can make future calls execute closer to their scheduled time, while increasing it can reduce database load in environments with limited resources. - -Example configuration: - -```yaml -futureCall: - scanInterval: 2000 # Adjust this value based on your server's responsiveness needs -``` diff --git a/versioned_docs/version-3.2.0/06-concepts/14-scheduling/01-setup.md b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/01-setup.md new file mode 100644 index 00000000..59a9642d --- /dev/null +++ b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/01-setup.md @@ -0,0 +1,103 @@ +# Setup + +Serverpod supports scheduling future work with the `future call` feature. Future calls are calls that will be invoked at a later time. An example is if you want to send a drip-email campaign after a user signs up. You can schedule a future call for a day, a week, a month, or a [recurring interval](recurring-task). The calls are stored in the database, so they will persist even if the server is restarted. + +A future call is guaranteed to only execute once across all your instances that are running, but execution failures are not handled automatically. It is your responsibility to schedule a new future call if the work was not able to complete. + +To create future calls, extend the `FutureCall` class and define the methods you wish to invoke at a later time. + +```dart +import 'package:serverpod/serverpod.dart'; + +class ExampleFutureCall extends FutureCall { + Future doWork(Session session, String data) async { + // Do something interesting in the future here. + } + + Future doOtherWork(Session session, String data) async { + // Do something interesting in the future here. + } +} +``` + +:::info +For a method to be recognized by Serverpod as a future call, it must return a `Future` and take at least two parameters. The first parameter must be a [`Session`](../sessions) object. You can pass any serializable types as other parameters, and even use `List`, `Map`, `Set` or Dart records as long as they are typed. `Streaming` parameters are not supported. +::: + +Next, you need to generate the code for your future calls: + +```bash +$ serverpod generate +``` + +`serverpod generate` will create a type-safe interface for invoking the future calls in the server's `generated/future_calls.dart` file. This interface can be accessed from the Serverpod object. + +The future calls you create are registered by `Serverpod` after the server starts. + +```dart + import 'package:serverpod/serverpod.dart'; + import 'package:serverpod_auth_idp_server/core.dart'; + + import 'src/generated/protocol.dart'; + import 'src/generated/endpoints.dart'; + + void run(List args) async { + final pod = Serverpod( + args, + Protocol(), + Endpoints(), + ); + + await pod.start(); +} +``` + +You are now able to schedule future calls to be invoked in the future by calling either `callWithDelay` or `callAtTime` depending on your needs. + +:::warning +Scheduling a future call before the server starts will lead to exceptions. +::: + +Invoke a future call 1 hour from now by calling `callWithDelay`. + +```dart +await pod.futureCalls + .callWithDelay(const Duration(hours: 1)) + .example + .doWork('1'); +``` + +Invoke a future call at a specific time and/or date in the future by calling `callAtTime`. + +```dart +await pod.futureCalls + .callAtTime(DateTime(2026, 1, 1)) + .example + .doOtherWork('2'); +``` + +:::info +Scheduling a future call at a specific time/date will always resolve the `DateTime` to UTC. +::: + +When scheduling a future call, it is also possible to give it an `identifier` so that it can be referenced later. The same identifier can be applied to multiple future calls. + +```dart +await pod.futureCalls + .callWithDelay( + const Duration(hours: 1), + identifier: 'an-identifying-string', + ) + .example + .doWork('1'); +``` + +This identifier can then be used to cancel all future calls scheduled with said identifier. + +```dart +await pod.futureCalls.cancel('an-identifying-string'); +``` + +:::info +The future call feature is not enabled when running Serverpod in serverless mode. +::: diff --git a/versioned_docs/version-3.2.0/06-concepts/14-scheduling/02-recurring-task.md b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/02-recurring-task.md new file mode 100644 index 00000000..0ae25963 --- /dev/null +++ b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/02-recurring-task.md @@ -0,0 +1,71 @@ +# Recurring Task + +The recommended way to achieve cron-like scheduling is by scheduling a future call inside another. +To set this up, extend the `FutureCall` class and define two methods. + +```dart +import 'package:serverpod/serverpod.dart'; + +class ExampleFutureCall extends FutureCall { + Future doWork(Session session, int input) async { + await _doWork(session, input); + } + + Future _doWork(Session session, int input) async { + session.log('Working with input $input'); + } +} +``` + +Next, generate the code for your future call: + +```bash +$ serverpod generate +``` + +:::info +Code is only generated for the public method while the private method contains the logic to be invoked as a recurring task. +::: + +Next, import the generated `endpoints.dart` file and schedule the recurring future call using the generated code: + +```dart +import 'package:serverpod/serverpod.dart'; +import 'generated/endpoints.dart'; + +class ExampleFutureCall extends FutureCall { + Future doWork(Session session, int input) async { + await session.serverpod.futureCalls + .callWithDelay(const Duration(minutes: 20)) + .example + .doWork(input + 1); + + await _doWork(session, input); + } + + Future _doWork(Session session, int input) async { + session.log('Working with input $input'); + } +} +``` + +Now when you schedule the `doWork` future call, it will continously invoke `_doWork` at an interval of 20 minutes. + +```dart +import 'package:serverpod/serverpod.dart'; +import 'package:serverpod_auth_idp_server/core.dart'; + +import 'src/generated/protocol.dart'; +import 'src/generated/endpoints.dart'; + +void run(List args) async { + final pod = Serverpod( + args, + Protocol(), + Endpoints(), + ); + + await pod.start(); + await pod.futureCalls.callWithDelay(Duration(minutes: 20)).example.doWork(2); +} +``` diff --git a/versioned_docs/version-3.2.0/06-concepts/14-scheduling/03-inheritance.md b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/03-inheritance.md new file mode 100644 index 00000000..9202385b --- /dev/null +++ b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/03-inheritance.md @@ -0,0 +1,91 @@ +# Inheritance + +Inheritance gives you the possibility to modify the behavior of `FutureCall` classes defined in other Serverpod modules. If the parent `FutureCall` class was marked as `abstract`, no code is generated for it. + +Currently, there are the following possibilities to extend another `FutureCall` class: + +## Inheriting from a `FutureCall` class + +Given an existing `FutureCall` class, it is possible to extend or modify its behavior while retaining the already exposed methods. + +```dart +import 'package:serverpod/serverpod.dart'; + +class Greeter extends FutureCall { + Future hello(Session session, String name) async { + session.log('Hello $name'); + } +} + +class MyGreeter extends Greeter { + Future bye(Session session, String name) async { + session.log('Bye $name'); + } +} +``` + +The generated server code will now be able to access both `Greeter` and `MyGreeter`. +Whereas the `Greeter` only exposes the original `hello` method, `MyGreeter` now exposes both the inherited `hello` and its own `bye` methods. + +## Inheriting from a `FutureCall` class marked `abstract` + +Future calls marked as `abstract` are not added to the server. But if they are subclassed, their methods will be exposed through the subclass. + +```dart +import 'package:serverpod/serverpod.dart'; + +abstract class Greeter extends FutureCall { + Future hello(Session session, String name) async { + session.log('Hello $name'); + } +} + +class MyGreeter extends Greeter {} +``` + +Since `Greeter` is `abstract`, it will not be added to the server. However, `MyGreeter` will expose a single `hello` method. + +:::info +Serverpod modules can expose future calls to users with `abstract` `FutureCall`. Code is only generated on the current project that extends the abstract future call. +::: + +### Extending an `abstract` `FutureCall` class + +In the above example, the `MyGreeter` only exposed the inherited `hello` method. It can be further extended with custom methods like this: + +```dart +import 'package:serverpod/serverpod.dart'; + +class MyGreeter extends Greeter { + Future bye(Session session, String name) async { + session.log('Bye $name'); + } +} +``` + +In this case, it will expose both a `hello` and a `bye` method. + +### Overriding future call methods + +It is possible to override methods of the superclass. This can be useful when you want to modify the behavior of specific methods but preserve the rest. + +```dart +import 'package:serverpod/serverpod.dart'; + +abstract class Greeter extends FutureCall { + Future hello(Session session, String name) async { + session.log('Hello $name'); + } +} + +class ExcitedGreeter extends Greeter { + @override + Future hello(Session session, String name) async { + session.log('Hello $name!!!'); + } +} +``` + +Since `Greeter` is `abstract`, it will not be exposed on the server. The `ExcitedGreeter` will expose a single `hello` method, and its implementation will augment the superclass's one by adding `!!!` to the output. + +This way, you can modify the behavior of future call methods while still sharing the implementation through calls to `super`. Be aware that the method signature has to be compatible with the base class per Dart's rules, meaning you can add optional parameters, but can not add required parameters or change the return type. diff --git a/versioned_docs/version-3.2.0/06-concepts/14-scheduling/04-configuration.md b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/04-configuration.md new file mode 100644 index 00000000..afb7d137 --- /dev/null +++ b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/04-configuration.md @@ -0,0 +1,47 @@ +# Configuration + +Future calls can be configured using options defined in the configuration files or environment variables. For a detailed list of configuration options, refer to the [Configuration](../configuration) page. + +Below is an example of how you can configure future calls in a YAML file: + +```yaml +futureCallExecutionEnabled: true + +futureCall: + concurrencyLimit: 5 + scanInterval: 2000 +``` + +### Enable or disable future call execution + +This option allows you to enable or disable the execution of future calls. By default, it is set to `true`. You might want to disable future call execution in environments where you don't want background tasks to run, such as during testing or in a staging environment where you want to focus on API behavior without triggering scheduled tasks. + +Example configuration: + +```yaml +futureCallExecutionEnabled: false +``` + +### Concurrency limit + +This option sets the maximum number of future calls that can run concurrently. By default, it is set to `1`. Configuring this is useful if you have resource-intensive tasks and want to avoid overloading your server. For example, in a production environment, you might want to tune this value to ensure that not all of the server's resources are allocated to future calls, leaving room for other critical tasks. + +Setting this value to a negative number or `null` removes the limitation, allowing an unlimited number of concurrent future calls. However, this should be used with caution as it can lead to resource exhaustion. + +Example configuration: + +```yaml +futureCall: + concurrencyLimit: 5 # Adjust this value based on your server's capacity +``` + +### Scan interval + +This option determines how often the system scans for future calls to execute, in milliseconds. The default value is `5000` (5 seconds). Adjusting this interval can help balance responsiveness and resource usage. For example, reducing the interval can make future calls execute closer to their scheduled time, while increasing it can reduce database load in environments with limited resources. + +Example configuration: + +```yaml +futureCall: + scanInterval: 2000 # Adjust this value based on your server's responsiveness needs +``` diff --git a/versioned_docs/version-3.2.0/06-concepts/14-scheduling/05-legacy.md b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/05-legacy.md new file mode 100644 index 00000000..0540884c --- /dev/null +++ b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/05-legacy.md @@ -0,0 +1,79 @@ +# Legacy + +:::warning +This approach is error prone since it involves manually registering and scheduling future calls using string identifiers. The recommended way to interact with the future calls feature is through the [type-safe API](setup). +::: + +Creating a future call is simple, extend the `FutureCall` class and override the `invoke` method. The method takes two params the first being the [`Session`](../sessions) object and the second being an optional SerializableModel ([See models](../models)). + +```dart +import 'package:serverpod/serverpod.dart'; + +class ExampleFutureCall extends FutureCall { + @override + Future invoke(Session session, MyModelEntity? object) async { + // Do something interesting in the future here. + } +} +``` + +To let your Server get access to the future call you have to register it in the main run method in your `server.dart` file. You register the future call by calling `registerFutureCall` on the Serverpod object and giving it an instance of the future call together with a string that gives the future call a name. The name has to be globally unique and is used to later invoke the future call. + +```dart +void run(List args) async { + final pod = Serverpod( + args, + Protocol(), + Endpoints(), + ); + + ... + + pod.registerFutureCall(ExampleFutureCall(), 'exampleFutureCall'); + + ... +} +``` + +You are now able to register a future call to be invoked in the future by calling either `futureCallWithDelay` or `futureCallAtTime` depending on your needs. + +Invoke the future call 1 hour from now by calling `futureCallWithDelay`. + +```dart +await session.serverpod.futureCallWithDelay( + 'exampleFutureCall', + data, + const Duration(hours: 1), +); +``` + +Invoke the future call at a specific time and/or date in the future by calling `futureCallAtTime`. + +```dart +await session.serverpod.futureCallAtTime( + 'exampleFutureCall', + data, + DateTime(2025, 1, 1), +); +``` + +:::note +`data` is an object created from a class defined in one of your yaml files and has to be the same as the one you expect to receive in the future call. in the `model` folder, `data` may also be null if you don't need it. +::: + +When registering a future call, it is also possible to give it an `identifier` so that it can be referenced later. The same identifier can be applied to multiple future calls. + +```dart +await session.serverpod.futureCallWithDelay( + 'exampleFutureCall', + data, + const Duration(hours: 1), + identifier: 'an-identifying-string', +); +``` + +This identifier can then be used to cancel all future calls registered with said identifier. + +```dart +await session.serverpod.cancelFutureCall('an-identifying-string'); +``` diff --git a/versioned_docs/version-3.2.0/06-concepts/14-scheduling/_category_.json b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/_category_.json new file mode 100644 index 00000000..dc0af035 --- /dev/null +++ b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Scheduling", + "collapsed": true +} \ No newline at end of file diff --git a/versioned_docs/version-3.2.0/06-concepts/17-backward-compatibility.md b/versioned_docs/version-3.2.0/06-concepts/17-backward-compatibility.md index 8e1f3597..76309a3a 100644 --- a/versioned_docs/version-3.2.0/06-concepts/17-backward-compatibility.md +++ b/versioned_docs/version-3.2.0/06-concepts/17-backward-compatibility.md @@ -6,7 +6,9 @@ Following a simple set of rules, your server will stay compatible with older app 1. __Avoid changing parameter names in endpoint methods.__ In the REST API Serverpod generates, the parameters are passed by name. This means that changing the parameter names of the endpoint methods will break backward compatibility. 2. __Do not delete endpoint methods or change their signature.__ Instead, add new methods if you must pass another set of parameters. Technically, you can add new named parameters if they are not required, but creating a new method may still feel cleaner. -3. __Avoid changing or removing fields and types in the serialized classes.__ However, you are free to add new fields as long as they are nullable. +3. __Avoid changing or removing fields and types in the serialized classes.__ However, you are free to add new fields as long as they are nullable or have a default value. +4. __Avoid changing parameter names in future call methods.__ Changing the parameter names of the future call methods will break backward compatibility since parameters are passed by name. +5. __Do not delete future call methods or change their signature.__ Instead, add new methods if you must pass another set of parameters. ## Managing breaking changes with endpoint inheritance @@ -22,7 +24,7 @@ class TeamEndpoint extends Endpoint { Future join(Session session) async { // … } - + // many more methods, like `leave`, etc. }