chore: scaffold the Serverpod 4 build

Deviations from a stock `serverpod create`:
- Dropped Flutter web support, for two reasons:
  - Appflowy editor had issues in the past, potentially related to the
    Flutter-Web IME bridge. Upstream has also open bugs in relation to
that:
    - AppFlowy-IO/appflowy-editor#1029 [Bug][Web] Unwanted space on click
       and overall space bugs (open)
    - AppFlowy-IO/appflowy-editor#782 [Bug] Editor auto-scrolling when
       pressing space too quickly if placed inside a scrollable (open)
  - Offline-first is a required feature. serverpod_offline_sync's SQLite
    support in browsers, maybe via sqlite3.wasm + OPFS, is not
documented.
- SDK constraints raised to ^3.13.0, instead of ^3.12.2, so enjoy the
  newest language features.
- Pinned to Flutter 3.47.4 via FVM.

Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-09-16 19:24:59 +02:00
commit e17d99fa51
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
182 changed files with 17563 additions and 0 deletions

3
.fvmrc Normal file
View file

@ -0,0 +1,3 @@
{
"flutter": "3.47.4"
}

25
.github/workflows/analyze.yml vendored Normal file
View file

@ -0,0 +1,25 @@
name: Analyze
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.44.4'
- name: Install dependencies
run: flutter pub get
- name: Analyze
working-directory: halkyon_learn_server
run: dart analyze --fatal-infos

25
.github/workflows/format.yml vendored Normal file
View file

@ -0,0 +1,25 @@
name: Format
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.44.4'
- name: Install dependencies
run: flutter pub get
- name: Check formatting
working-directory: halkyon_learn_server
run: dart format --set-exit-if-changed .

45
.github/workflows/tests.yml vendored Normal file
View file

@ -0,0 +1,45 @@
name: Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
env:
VERSION: '4.0.0'
SERVERPOD_PASSWORD_database: 'h8hg2NM2Ho1N7Zv-WxwJyhfJC76hxy6h'
SERVERPOD_PASSWORD_redis: 'JRcMj86Jrhg4s6soZ_DShJqQilJGzEJo'
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.44.4'
- name: Start containers
working-directory: halkyon_learn_server
run: docker compose up --quiet-pull --build -d
- name: Install dependencies
run: flutter pub get
- name: Install Serverpod CLI
run: dart install serverpod_cli $VERSION
- name: Run Serverpod generator
working-directory: halkyon_learn_server
run: serverpod generate
- name: Run tests
working-directory: halkyon_learn_server
run: dart test
- name: Stop containers
if: always()
working-directory: halkyon_learn_server
run: docker compose down -v

22
.gitignore vendored Normal file
View file

@ -0,0 +1,22 @@
# Files and directories created by pub
.dart_tool/
.packages
# Pubspec overrides
pubspec_overrides.yaml
# Agents
.dart_skills/
.agents/
.claude/
.gemini/
.cursor/
.vscode/mcp.json
.codex/
.mcp.json
# Serverpod Cloud CLI
.scloud/
# FVM Version Cache
.fvm/

43
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,43 @@
{
"version": "0.2.0",
"configurations": [
// If the server pubspec configures multiple Flutter apps, add an attach
// configuration like this for each app and add every configuration name
// to the compound below. Use the VM-service info file pattern
// `${workspaceFolder}/halkyon_learn_server/.dart_tool/serverpod/flutter-vm-service-info-<appId>.json`,
// where `<appId>` is the app's key under `serverpod.flutter_apps` in the
// server pubspec.
{
// Attaches to the Flutter app `serverpod start` spawns. Don't
// launch a second instance here - it would race for the port.
"name": "halkyon_learn_flutter",
"type": "dart",
"request": "attach",
"vmServiceInfoFile": "${workspaceFolder}/halkyon_learn_server/.dart_tool/serverpod/flutter-vm-service-info-halkyon_learn.json",
"preLaunchTask": "serverpod_start",
"internalConsoleOptions": "neverOpen"
},
{
"name": "halkyon_learn_server",
"type": "dart",
"request": "attach",
"vmServiceInfoFile": "${workspaceFolder}/halkyon_learn_server/.dart_tool/serverpod/vm-service-info.json",
"preLaunchTask": "serverpod_start",
"internalConsoleOptions": "neverOpen"
}
],
"compounds": [
{
"name": "halkyon_learn (full stack)",
"configurations": [
"halkyon_learn_server",
"halkyon_learn_flutter"
],
// Stopping one session stops both; the orphan blocks re-attach.
"stopAll": true,
"presentation": {
"order": 1
}
}
]
}

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"dart.flutterSdkPath": ".fvm/versions/3.47.4"
}

48
.vscode/tasks.json vendored Normal file
View file

@ -0,0 +1,48 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "serverpod_start",
"type": "shell",
"command": "serverpod",
"args": ["start"],
"options": {
"cwd": "${workspaceFolder}/halkyon_learn_server",
"env": {
"SERVERPOD_PASSWORD_database": "zLZiS0hYW86MNDWF3fTB9iYudoEQ4rZG",
// Marks a `serverpod start` launched from the IDE, which is the
// only kind that honors the Flutter device picked in VS Code. A run
// started from a plain terminal carries no marker, so a device file
// left behind by a session that was killed cannot retarget it.
"SERVERPOD_LAUNCHED_FROM_IDE": "true"
}
},
"isBackground": true,
"presentation": {
"reveal": "always",
"panel": "dedicated"
},
// The TUI captures stdout, so we can't string-match for "ready". The
// attach config polls vm-service-info.json for the actual readiness
// signal; this matcher exists only to satisfy VS Code's requirement
// that background tasks declare one. The pattern intentionally never
// matches; begin/end patterns mark the task active+ready immediately.
"problemMatcher": {
"owner": "serverpod",
"pattern": [
{
"regexp": "^(SERVERPOD_NEVER_MATCH):(\\d+):(.+)$",
"file": 1,
"line": 2,
"message": 3
}
],
"background": {
"activeOnStart": true,
"beginsPattern": ".",
"endsPattern": "."
}
}
}
]
}

50
AGENTS.md Normal file
View file

@ -0,0 +1,50 @@
# Flutter & Serverpod project
This project is a Flutter app (frontend) backed by a Serverpod server (backend). Always build the app's backend with Serverpod.
Build for multiple users, use Serverpod's built-in authentication, which is already set up in `lib/server.dart`.
The user starts the server and Flutter app with `serverpod start`. There is no need to check if the server is running: make the changes and call the `serverpod` MCP tools as needed. If the server is not running, an informative error message will be received from the MCP server. Then STOP and ask the user to start it. NEVER start the server yourself. The Flutter app is started along with it, or can be launched from the MCP tool `spawn_flutter_app`.
While running, `serverpod start` watches for file changes to run incremental code generation and hot reload both the server and the Flutter app.
Calling `serverpod generate` directly is not needed, but might be useful to troubleshoot when an incremental generation fails.
ALWAYS use the MCP server instead of the command line. Use the MCP server to:
- `create_migration` and `apply_migrations` for database (after you change data models).
- `create_repair_migration` if the database has drifted out of sync with the migrations.
- `tail_server_logs` to read logs from the server.
- `tail_flutter_logs` to read the raw stdout/stderr of the Flutter app.
- `hot_reload` / `hot_restart` to reload or restart the server and the Flutter app. ALWAYS call `hot_restart` after doing changes in the Flutter app that may not work with normal hot reload (which is automatically applied).
- `spawn_flutter_app` to start a Flutter app declared under `serverpod: flutter_apps:` in the server `pubspec.yaml`.
- `get_flutter_app_dtd` (Dart tooling daemon) for connecting to the app through the `dart` MCP.
NEVER edit generated code. The server's `lib/src/generated/` directory and the whole `halkyon_learn_client` package are rewritten by the code generator. Change the `.spy.yaml` models, the endpoints, or `lib/server.dart` instead.
Migrations are a narrow exception: the `migration.sql` of a generated migration MAY be edited by hand when the generated SQL would lose data — to add a data transformation, or to reach a destructive change through non-destructive steps. Never touch the other files in the migration directory, and keep the schema the SQL ends up with identical to `definition.sql` — new databases are created from that file and never run `migration.sql`.
Only when the server cannot be started at all, fall back to the CLI in the server package:
- `serverpod generate` to regenerate the client and the generated server code.
- `serverpod create-migration` after changing a model with a `table` (add `--force` for destructive changes). It only writes the migration; `serverpod start` applies pending migrations when it boots the server.
Tests need no Docker. `config/test.yaml` sets `database.dataPath`, so Serverpod starts and manages the test database (an embedded PostgreSQL) itself, and the project's `docker-compose.yaml` is not used for it. Just run `dart test` in the server package.
Checklist after doing changes, in this order:
- `dart analyze` (CLI)
- `dart format` (CLI)
- `create_migration` and `apply_migrations` (MCP - only if necessary)
- Do `serverpod` MCP `hot_restart` if required (hot reload is done automatically). Will also hot restart Flutter app
- Run tests, if applicable (`dart test` in the server package)
- Check `serverpod` MCP `tail_server_logs` and `tail_flutter_logs` for any issues.
If the user asks you to test the app:
1. Use `get_flutter_app_dtd` (`serverpod` MCP) to get the Flutter app's DTD
2. Pass the DTD to `connect_dart_tooling_daemon` (`dart` MCP) to connect to the app
3. Use `flutter_driver` (`dart` MCP) to navigate through the app
The app is launched from `halkyon_learn_flutter/lib/driver.dart`, which starts the Flutter driver extension with text entry emulation turned off so the app stays usable by hand. To let the driver type, set `enableTextEntryEmulation: true` there and `hot_restart` the app.
IMPORTANT: After building the first version of the app, update this AGENTS.md file with information about the app we're building. KEEP the info about the MCP server and the checklist. Remove this paragraph.

1
CLAUDE.md Normal file
View file

@ -0,0 +1 @@
@AGENTS.md

13
halkyon_learn_client/.gitignore vendored Normal file
View file

@ -0,0 +1,13 @@
# Files and directories created by pub
.dart_tool/
.packages
# Omit committing pubspec.lock for library packages:
# https://dart.dev/guides/libraries/private-files#pubspeclock
pubspec.lock
# Conventional directory for build outputs
build/
# Directory created by dartdoc
doc/api/

View file

@ -0,0 +1,3 @@
## 1.0.0
- Initial version, created by Stagehand

View file

@ -0,0 +1,5 @@
# halkyon_learn_client
This is your Serverpod client. The code in here is mostly generated by
Serverpod, but you may want to make changes if you are adding modules to your
project.

View file

@ -0,0 +1,16 @@
# Defines a default set of lint rules enforced for
# projects at Google. For details and rationale,
# see https://github.com/dart-lang/pedantic#enabled-lints.
# For lint rules and documentation, see http://dart-lang.github.io/linter/lints.
# Uncomment to specify additional rules.
# linter:
# rules:
# - camel_case_types
analyzer:
exclude:
- lib/src/protocol/**
formatter:
trailing_commas: preserve

View file

@ -0,0 +1,5 @@
dartdoc:
categories:
"Endpoint":
markdown: doc/endpoint.md
name: Endpoint

View file

@ -0,0 +1,15 @@
# Callable endpoints
Each class contains callable methods that will call a method on the server side. These are normally defined in the `endpoint` directory in your server project. This client sends requests to these endpoints and returns the result.
Example usage:
```dart
// How to use GreetingEndpoint.
client.greeting.hello("world!");
// Generic format.
client.<endpoint>.<method>(...);
```
Please see the full official documentation [here](https://docs.serverpod.dev)

View file

@ -0,0 +1,2 @@
export 'src/protocol/protocol.dart';
export 'package:serverpod_client/serverpod_client.dart';

View file

@ -0,0 +1,331 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _ida;
import 'package:halkyon_learn_client/src/protocol/greetings/greeting.dart'
as _ili0bmg9;
import 'package:http/http.dart' as _i85jenna;
import 'package:serverpod_auth_core_client/serverpod_auth_core_client.dart'
as _iacc;
import 'package:serverpod_auth_idp_client/serverpod_auth_idp_client.dart'
as _iaic;
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import 'protocol.dart' as _il2as5qe;
/// By extending [EmailIdpBaseEndpoint], the email identity provider endpoints
/// are made available on the server and enable the corresponding sign-in widget
/// on the client.
/// {@category Endpoint}
class EndpointEmailIdp extends _iaic.EndpointEmailIdpBase {
EndpointEmailIdp(_isc.EndpointCaller caller) : super(caller);
@override
String get name => 'emailIdp';
/// Logs in the user and returns a new session.
///
/// Throws an [EmailAccountLoginException] in case of errors, with reason:
/// - [EmailAccountLoginExceptionReason.invalidCredentials] if the email or
/// password is incorrect.
/// - [EmailAccountLoginExceptionReason.tooManyAttempts] if there have been
/// too many failed login attempts.
///
/// Throws an [AuthUserBlockedException] if the auth user is blocked.
@override
_ida.Future<_iacc.AuthSuccess> login({
required String email,
required String password,
}) => caller.callServerEndpoint<_iacc.AuthSuccess>(
'emailIdp',
'login',
{
'email': email,
'password': password,
},
);
/// Starts the registration for a new user account with an email-based login
/// associated to it.
///
/// Upon successful completion of this method, an email will have been
/// sent to [email] with a verification link, which the user must open to
/// complete the registration.
///
/// Always returns a account request ID, which can be used to complete the
/// registration. If the email is already registered, the returned ID will not
/// be valid.
@override
_ida.Future<_isc.UuidValue> startRegistration({required String email}) =>
caller.callServerEndpoint<_isc.UuidValue>(
'emailIdp',
'startRegistration',
{'email': email},
);
/// Verifies an account request code and returns a token
/// that can be used to complete the account creation.
///
/// Throws an [EmailAccountRequestException] in case of errors, with reason:
/// - [EmailAccountRequestExceptionReason.expired] if the account request has
/// already expired.
/// - [EmailAccountRequestExceptionReason.policyViolation] if the password
/// does not comply with the password policy.
/// - [EmailAccountRequestExceptionReason.invalid] if no request exists
/// for the given [accountRequestId] or [verificationCode] is invalid.
@override
_ida.Future<String> verifyRegistrationCode({
required _isc.UuidValue accountRequestId,
required String verificationCode,
}) => caller.callServerEndpoint<String>(
'emailIdp',
'verifyRegistrationCode',
{
'accountRequestId': accountRequestId,
'verificationCode': verificationCode,
},
);
/// Completes a new account registration, creating a new auth user with a
/// profile and attaching the given email account to it.
///
/// Throws an [EmailAccountRequestException] in case of errors, with reason:
/// - [EmailAccountRequestExceptionReason.expired] if the account request has
/// already expired.
/// - [EmailAccountRequestExceptionReason.policyViolation] if the password
/// does not comply with the password policy.
/// - [EmailAccountRequestExceptionReason.invalid] if the [registrationToken]
/// is invalid.
///
/// Throws an [AuthUserBlockedException] if the auth user is blocked.
///
/// Returns a session for the newly created user.
@override
_ida.Future<_iacc.AuthSuccess> finishRegistration({
required String registrationToken,
required String password,
}) => caller.callServerEndpoint<_iacc.AuthSuccess>(
'emailIdp',
'finishRegistration',
{
'registrationToken': registrationToken,
'password': password,
},
);
/// Requests a password reset for [email].
///
/// If the email address is registered, an email with reset instructions will
/// be send out. If the email is unknown, this method will have no effect.
///
/// Always returns a password reset request ID, which can be used to complete
/// the reset. If the email is not registered, the returned ID will not be
/// valid.
///
/// Throws an [EmailAccountPasswordResetException] in case of errors, with reason:
/// - [EmailAccountPasswordResetExceptionReason.tooManyAttempts] if the user has
/// made too many attempts trying to request a password reset.
///
@override
_ida.Future<_isc.UuidValue> startPasswordReset({required String email}) =>
caller.callServerEndpoint<_isc.UuidValue>(
'emailIdp',
'startPasswordReset',
{'email': email},
);
/// Verifies a password reset code and returns a finishPasswordResetToken
/// that can be used to finish the password reset.
///
/// Throws an [EmailAccountPasswordResetException] in case of errors, with reason:
/// - [EmailAccountPasswordResetExceptionReason.expired] if the password reset
/// request has already expired.
/// - [EmailAccountPasswordResetExceptionReason.tooManyAttempts] if the user has
/// made too many attempts trying to verify the password reset.
/// - [EmailAccountPasswordResetExceptionReason.invalid] if no request exists
/// for the given [passwordResetRequestId] or [verificationCode] is invalid.
///
/// If multiple steps are required to complete the password reset, this endpoint
/// should be overridden to return credentials for the next step instead
/// of the credentials for setting the password.
@override
_ida.Future<String> verifyPasswordResetCode({
required _isc.UuidValue passwordResetRequestId,
required String verificationCode,
}) => caller.callServerEndpoint<String>(
'emailIdp',
'verifyPasswordResetCode',
{
'passwordResetRequestId': passwordResetRequestId,
'verificationCode': verificationCode,
},
);
/// Completes a password reset request by setting a new password.
///
/// The [verificationCode] returned from [verifyPasswordResetCode] is used to
/// validate the password reset request.
///
/// Throws an [EmailAccountPasswordResetException] in case of errors, with reason:
/// - [EmailAccountPasswordResetExceptionReason.expired] if the password reset
/// request has already expired.
/// - [EmailAccountPasswordResetExceptionReason.policyViolation] if the new
/// password does not comply with the password policy.
/// - [EmailAccountPasswordResetExceptionReason.invalid] if no request exists
/// for the given [passwordResetRequestId] or [verificationCode] is invalid.
///
/// Throws an [AuthUserBlockedException] if the auth user is blocked.
@override
_ida.Future<void> finishPasswordReset({
required String finishPasswordResetToken,
required String newPassword,
}) => caller.callServerEndpoint<void>(
'emailIdp',
'finishPasswordReset',
{
'finishPasswordResetToken': finishPasswordResetToken,
'newPassword': newPassword,
},
);
@override
_ida.Future<bool> hasAccount() => caller.callServerEndpoint<bool>(
'emailIdp',
'hasAccount',
{},
);
}
/// By extending [RefreshJwtTokensEndpoint], the JWT token refresh endpoint
/// is made available on the server and enables automatic token refresh on the client.
/// {@category Endpoint}
class EndpointJwtRefresh extends _iacc.EndpointRefreshJwtTokens {
EndpointJwtRefresh(_isc.EndpointCaller caller) : super(caller);
@override
String get name => 'jwtRefresh';
/// Creates a new token pair for the given [refreshToken].
///
/// If [refreshToken] is omitted, cookie-mode web clients fall back to the
/// configured HttpOnly refresh cookie. When neither source is present this
/// throws [RefreshTokenNotFoundException], the same public "no usable refresh
/// credential" exception used for unknown refresh tokens.
///
/// Can throw the following exceptions:
/// -[RefreshTokenMalformedException]: refresh token is malformed and could
/// not be parsed. Not expected to happen for tokens issued by the server.
/// -[RefreshTokenNotFoundException]: refresh token is unknown to the server.
/// Either the token was deleted or generated by a different server.
/// -[RefreshTokenExpiredException]: refresh token has expired. Will happen
/// only if it has not been used within configured `refreshTokenLifetime`.
/// -[RefreshTokenInvalidSecretException]: refresh token is incorrect, meaning
/// it does not refer to the current secret refresh token. This indicates
/// either a malfunctioning client or a malicious attempt by someone who has
/// obtained the refresh token. In this case the underlying refresh token
/// will be deleted, and access to it will expire fully when the last access
/// token is elapsed.
///
/// This endpoint is unauthenticated, meaning the client won't include any
/// authentication information with the call.
@override
_ida.Future<_iacc.AuthSuccess> refreshAccessToken({String? refreshToken}) =>
caller.callServerEndpoint<_iacc.AuthSuccess>(
'jwtRefresh',
'refreshAccessToken',
{'refreshToken': refreshToken},
authenticated: false,
);
}
/// This is an example endpoint that returns a greeting message through
/// its [hello] method.
/// {@category Endpoint}
class EndpointGreeting extends _isc.EndpointRef {
EndpointGreeting(_isc.EndpointCaller caller) : super(caller);
@override
String get name => 'greeting';
/// Returns a personalized greeting message: "Hello {name}".
_ida.Future<_ili0bmg9.Greeting> hello(String name) =>
caller.callServerEndpoint<_ili0bmg9.Greeting>(
'greeting',
'hello',
{'name': name},
);
}
class Modules {
Modules(Client client) {
serverpod_auth_idp = _iaic.Caller(client);
serverpod_auth_core = _iacc.Caller(client);
}
late final _iaic.Caller serverpod_auth_idp;
late final _iacc.Caller serverpod_auth_core;
}
class Client extends _isc.ServerpodClientShared {
Client(
String host, {
dynamic securityContext,
Duration? streamingConnectionTimeout,
Duration? connectionTimeout,
Function(
_isc.MethodCallContext,
Object,
StackTrace,
)?
onFailedCall,
Function(_isc.MethodCallContext)? onSucceededCall,
bool? disconnectStreamsOnLostInternetConnection,
_i85jenna.Client? httpClientOverride,
}) : super(
host,
_il2as5qe.Protocol(),
securityContext: securityContext,
streamingConnectionTimeout: streamingConnectionTimeout,
connectionTimeout: connectionTimeout,
onFailedCall: onFailedCall,
onSucceededCall: onSucceededCall,
disconnectStreamsOnLostInternetConnection:
disconnectStreamsOnLostInternetConnection,
httpClientOverride: httpClientOverride,
) {
emailIdp = EndpointEmailIdp(this);
jwtRefresh = EndpointJwtRefresh(this);
greeting = EndpointGreeting(this);
modules = Modules(this);
}
late final EndpointEmailIdp emailIdp;
late final EndpointJwtRefresh jwtRefresh;
late final EndpointGreeting greeting;
late final Modules modules;
@override
Map<String, _isc.EndpointRef> get endpointRefLookup => {
'emailIdp': emailIdp,
'jwtRefresh': jwtRefresh,
'greeting': greeting,
};
@override
Map<String, _isc.ModuleEndpointCaller> get moduleLookup => {
'serverpod_auth_idp': modules.serverpod_auth_idp,
'serverpod_auth_core': modules.serverpod_auth_core,
};
}

View file

@ -0,0 +1,109 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_client/serverpod_client.dart' as _isc;
/// A greeting message which can be sent to or from the server.
abstract class Greeting
implements _isc.SerializableModel, _isc.ProtocolSerialization {
Greeting._({
required this.message,
required this.author,
required this.timestamp,
});
factory Greeting({
required String message,
required String author,
required DateTime timestamp,
}) = _GreetingImpl;
factory Greeting.fromJson(Map<String, dynamic> jsonSerialization) {
return Greeting(
message: jsonSerialization['message'] as String,
author: jsonSerialization['author'] as String,
timestamp: _isc.DateTimeJsonExtension.fromJson(
jsonSerialization['timestamp'],
),
);
}
/// The greeting message.
String message;
/// The author of the greeting message.
String author;
/// The time when the message was created.
DateTime timestamp;
/// Returns a shallow copy of this [Greeting]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
Greeting copyWith({
String? message,
String? author,
DateTime? timestamp,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'Greeting',
'message': message,
'author': author,
'timestamp': timestamp.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'Greeting',
'message': message,
'author': author,
'timestamp': timestamp.toJson(),
};
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _GreetingImpl extends Greeting {
_GreetingImpl({
required String message,
required String author,
required DateTime timestamp,
}) : super._(
message: message,
author: author,
timestamp: timestamp,
);
/// Returns a shallow copy of this [Greeting]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
Greeting copyWith({
String? message,
String? author,
DateTime? timestamp,
}) {
return Greeting(
message: message ?? this.message,
author: author ?? this.author,
timestamp: timestamp ?? this.timestamp,
);
}
}

View file

@ -0,0 +1,155 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: dead_code, unnecessary_type_check
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_auth_core_client/serverpod_auth_core_client.dart'
as _iacc;
import 'package:serverpod_auth_idp_client/serverpod_auth_idp_client.dart'
as _iaic;
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import 'greetings/greeting.dart' as _izw8z7ou;
export 'greetings/greeting.dart';
export 'client.dart';
class Protocol extends _isc.SerializationManager {
Protocol._();
factory Protocol() => _instance;
static final Protocol _instance = Protocol._().._registerHostProtocols();
static String? getClassNameFromObjectJson(dynamic data) {
if (data is! Map) return null;
final className = data['__className__'] as String?;
return className;
}
@override
T deserialize<T>(
dynamic data, [
Type? t,
]) {
t ??= T;
final dataClassName = getClassNameFromObjectJson(data);
if (dataClassName != null && dataClassName != getClassNameForType(t)) {
try {
return deserializeByClassName({
'className': dataClassName,
'data': data,
});
} on _isc.DeserializationClassNameNotFoundException catch (_) {
// If the className is not recognized (e.g., older client receiving
// data with a new subtype), fall back to deserializing without the
// className, using the expected type T.
}
}
if (t == _izw8z7ou.Greeting) {
return _izw8z7ou.Greeting.fromJson(data) as T;
}
if (t == _isc.getType<_izw8z7ou.Greeting?>()) {
return (data != null ? _izw8z7ou.Greeting.fromJson(data) : null) as T;
}
try {
return _iaic.Protocol().deserialize<T>(data, t);
} on _isc.DeserializationTypeNotFoundException catch (_) {}
try {
return _iacc.Protocol().deserialize<T>(data, t);
} on _isc.DeserializationTypeNotFoundException catch (_) {}
return super.deserialize<T>(data, t);
}
static String? getClassNameForType(Type type) {
return switch (type) {
_izw8z7ou.Greeting => 'Greeting',
_ => null,
};
}
@override
String? getClassNameForObject(Object? data) {
String? className = super.getClassNameForObject(data);
if (className != null) return className;
if (data is Map<String, dynamic> && data['__className__'] is String) {
return (data['__className__'] as String).replaceFirst(
'halkyon_learn.',
'',
);
}
switch (data) {
case _izw8z7ou.Greeting():
return 'Greeting';
}
className = _iaic.Protocol().getClassNameForObject(data);
if (className != null) {
return className.contains('.')
? className
: 'serverpod_auth_idp.$className';
}
className = _iacc.Protocol().getClassNameForObject(data);
if (className != null) {
return className.contains('.')
? className
: 'serverpod_auth_core.$className';
}
return null;
}
@override
dynamic deserializeByClassName(Map<String, dynamic> data) {
var dataClassName = data['className'];
if (dataClassName is! String) {
return super.deserializeByClassName(data);
}
if (dataClassName == 'Greeting') {
return deserialize<_izw8z7ou.Greeting>(data['data']);
}
if (dataClassName.startsWith('serverpod_auth_idp.')) {
data['className'] = dataClassName.substring(19);
return _iaic.Protocol().deserializeByClassName(data);
}
if (dataClassName.startsWith('serverpod_auth_core.')) {
data['className'] = dataClassName.substring(20);
return _iacc.Protocol().deserializeByClassName(data);
}
return super.deserializeByClassName(data);
}
void _registerHostProtocols() {
_iaic.Protocol().registerHostProtocol('halkyon_learn', this);
_iacc.Protocol().registerHostProtocol('halkyon_learn', this);
}
@override
String getModuleName() => 'halkyon_learn';
/// Maps any `Record`s known to this [Protocol] to their JSON representation
///
/// Throws in case the record type is not known.
///
/// This method will return `null` (only) for `null` inputs.
Map<String, dynamic>? mapRecordToJson(Record? record) {
if (record == null) {
return null;
}
try {
return _iaic.Protocol().mapRecordToJson(record);
} catch (_) {}
try {
return _iacc.Protocol().mapRecordToJson(record);
} catch (_) {}
throw Exception('Unsupported record type ${record.runtimeType}');
}
}

Some files were not shown because too many files have changed in this diff Show more