feat(todo): the sync engine and Todo's first models (ADR 0033, D5, D6)

Todo's server syncs with serverpod_offline_sync as Learn's and Train's
do, and holds two synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account) and project
(the Inbox or one of the person's own, changed when edited). A person's
space goes with them, with everything in it.

The migration only adds: the two tables, each indexed on spaceId and
cascading with its space (ON DELETE CASCADE to offline_sync_spaces), the
sync engine's own tables (crdt_*, offline_sync_*) with their foreign
keys, the gen_random_uuid_v7() function their ids default to, and the
modules' version rows. Nothing is dropped or changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-01 01:00:53 +01:00
commit 3876e30681
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -18,15 +18,174 @@ import 'package:serverpod_auth_core_client/serverpod_auth_core_client.dart'
import 'package:serverpod_auth_idp_client/serverpod_auth_idp_client.dart'
as _iaic;
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import 'package:serverpod_database/serverpod_database.dart' as _isd;
import 'package:serverpod_offline_sync_client/serverpod_offline_sync_client.dart'
as _ipulbpi2;
import 'privacy/content_key.dart' as _imed3x5d;
import 'project/project.dart' as _i9c44spp;
export 'privacy/content_key.dart';
export 'project/project.dart';
export 'client.dart';
export 'sync_tables.dart';
class Protocol extends _isc.SerializationManager {
class Protocol extends _isd.DatabaseSerializationManager {
Protocol._();
factory Protocol() => _instance;
static final Protocol _instance = Protocol._().._registerHostProtocols();
static List<_isd.TableDefinition> get targetTableDefinitions => [
_isd.TableDefinition(
name: 'content_key',
dartName: 'ContentKey',
schema: 'public',
module: 'halkyon_todo',
columns: [
_isd.ColumnDefinition(
name: 'id',
columnType: _isd.ColumnType.uuid,
isNullable: false,
dartType: 'UuidValue?',
columnDefault: 'random_v7',
),
_isd.ColumnDefinition(
name: 'spaceId',
columnType: _isd.ColumnType.bigint,
isNullable: true,
dartType: 'int?',
),
_isd.ColumnDefinition(
name: 'dataKeyId',
columnType: _isd.ColumnType.text,
isNullable: false,
dartType: 'String',
),
_isd.ColumnDefinition(
name: 'sealedKey',
columnType: _isd.ColumnType.bytea,
isNullable: false,
dartType: 'dart:typed_data:ByteData',
),
],
foreignKeys: [
_isd.ForeignKeyDefinition(
constraintName: 'content_key_fk_0',
columns: ['spaceId'],
referenceTable: 'offline_sync_spaces',
referenceTableSchema: 'public',
referenceColumns: ['id'],
onUpdate: _isd.ForeignKeyAction.noAction,
onDelete: _isd.ForeignKeyAction.cascade,
matchType: null,
),
],
indexes: [
_isd.IndexDefinition(
indexName: 'content_key_space_idx',
tableSpace: null,
elements: [
_isd.IndexElementDefinition(
type: _isd.IndexElementDefinitionType.column,
definition: 'spaceId',
),
],
type: 'btree',
isUnique: false,
isPrimary: false,
),
],
managed: true,
),
_isd.TableDefinition(
name: 'project',
dartName: 'Project',
schema: 'public',
module: 'halkyon_todo',
columns: [
_isd.ColumnDefinition(
name: 'id',
columnType: _isd.ColumnType.uuid,
isNullable: false,
dartType: 'UuidValue?',
columnDefault: 'random_v7',
),
_isd.ColumnDefinition(
name: 'spaceId',
columnType: _isd.ColumnType.bigint,
isNullable: true,
dartType: 'int?',
),
_isd.ColumnDefinition(
name: 'keyId',
columnType: _isd.ColumnType.text,
isNullable: true,
dartType: 'String?',
),
_isd.ColumnDefinition(
name: 'payload',
columnType: _isd.ColumnType.bytea,
isNullable: false,
dartType: 'dart:typed_data:ByteData',
),
_isd.ColumnDefinition(
name: 'nonce',
columnType: _isd.ColumnType.bytea,
isNullable: true,
dartType: 'dart:typed_data:ByteData?',
),
],
foreignKeys: [
_isd.ForeignKeyDefinition(
constraintName: 'project_fk_0',
columns: ['spaceId'],
referenceTable: 'offline_sync_spaces',
referenceTableSchema: 'public',
referenceColumns: ['id'],
onUpdate: _isd.ForeignKeyAction.noAction,
onDelete: _isd.ForeignKeyAction.cascade,
matchType: null,
),
],
indexes: [
_isd.IndexDefinition(
indexName: 'project_space_idx',
tableSpace: null,
elements: [
_isd.IndexElementDefinition(
type: _isd.IndexElementDefinitionType.column,
definition: 'spaceId',
),
],
type: 'btree',
isUnique: false,
isPrimary: false,
),
],
managed: true,
),
..._ihk4p5t7.Protocol() is _isd.DatabaseSerializationManager
? (_ihk4p5t7.Protocol() as _isd.DatabaseSerializationManager)
.getTargetTableDefinitions()
: [],
..._iaic.Protocol() is _isd.DatabaseSerializationManager
? (_iaic.Protocol() as _isd.DatabaseSerializationManager)
.getTargetTableDefinitions()
: [],
..._ipulbpi2.Protocol() is _isd.DatabaseSerializationManager
? (_ipulbpi2.Protocol() as _isd.DatabaseSerializationManager)
.getTargetTableDefinitions()
: [],
..._ibkpwzgl.Protocol() is _isd.DatabaseSerializationManager
? (_ibkpwzgl.Protocol() as _isd.DatabaseSerializationManager)
.getTargetTableDefinitions()
: [],
..._iacc.Protocol() is _isd.DatabaseSerializationManager
? (_iacc.Protocol() as _isd.DatabaseSerializationManager)
.getTargetTableDefinitions()
: [],
];
static String? getClassNameFromObjectJson(dynamic data) {
if (data is! Map) return null;
final className = data['__className__'] as String?;
@ -54,12 +213,27 @@ class Protocol extends _isc.SerializationManager {
}
}
if (t == _imed3x5d.ContentKey) {
return _imed3x5d.ContentKey.fromJson(data) as T;
}
if (t == _i9c44spp.Project) {
return _i9c44spp.Project.fromJson(data) as T;
}
if (t == _isc.getType<_imed3x5d.ContentKey?>()) {
return (data != null ? _imed3x5d.ContentKey.fromJson(data) : null) as T;
}
if (t == _isc.getType<_i9c44spp.Project?>()) {
return (data != null ? _i9c44spp.Project.fromJson(data) : null) as T;
}
try {
return _ihk4p5t7.Protocol().deserialize<T>(data, t);
} on _isc.DeserializationTypeNotFoundException catch (_) {}
try {
return _iaic.Protocol().deserialize<T>(data, t);
} on _isc.DeserializationTypeNotFoundException catch (_) {}
try {
return _ipulbpi2.Protocol().deserialize<T>(data, t);
} on _isc.DeserializationTypeNotFoundException catch (_) {}
try {
return _ibkpwzgl.Protocol().deserialize<T>(data, t);
} on _isc.DeserializationTypeNotFoundException catch (_) {}
@ -71,6 +245,8 @@ class Protocol extends _isc.SerializationManager {
static String? getClassNameForType(Type type) {
return switch (type) {
_imed3x5d.ContentKey => 'ContentKey',
_i9c44spp.Project => 'Project',
_ => null,
};
}
@ -87,6 +263,12 @@ class Protocol extends _isc.SerializationManager {
);
}
switch (data) {
case _imed3x5d.ContentKey():
return 'ContentKey';
case _i9c44spp.Project():
return 'Project';
}
className = _ihk4p5t7.Protocol().getClassNameForObject(data);
if (className != null) {
return className.contains('.') ? className : 'halkyon_base.$className';
@ -97,6 +279,12 @@ class Protocol extends _isc.SerializationManager {
? className
: 'serverpod_auth_idp.$className';
}
className = _ipulbpi2.Protocol().getClassNameForObject(data);
if (className != null) {
return className.contains('.')
? className
: 'serverpod_offline_sync.$className';
}
className = _ibkpwzgl.Protocol().getClassNameForObject(data);
if (className != null) {
return className.contains('.') ? className : 'halkyon_oidc.$className';
@ -116,6 +304,12 @@ class Protocol extends _isc.SerializationManager {
if (dataClassName is! String) {
return super.deserializeByClassName(data);
}
if (dataClassName == 'ContentKey') {
return deserialize<_imed3x5d.ContentKey>(data['data']);
}
if (dataClassName == 'Project') {
return deserialize<_i9c44spp.Project>(data['data']);
}
if (dataClassName.startsWith('halkyon_base.')) {
data['className'] = dataClassName.substring(13);
return _ihk4p5t7.Protocol().deserializeByClassName(data);
@ -124,6 +318,10 @@ class Protocol extends _isc.SerializationManager {
data['className'] = dataClassName.substring(19);
return _iaic.Protocol().deserializeByClassName(data);
}
if (dataClassName.startsWith('serverpod_offline_sync.')) {
data['className'] = dataClassName.substring(23);
return _ipulbpi2.Protocol().deserializeByClassName(data);
}
if (dataClassName.startsWith('halkyon_oidc.')) {
data['className'] = dataClassName.substring(13);
return _ibkpwzgl.Protocol().deserializeByClassName(data);
@ -138,10 +336,71 @@ class Protocol extends _isc.SerializationManager {
void _registerHostProtocols() {
_ihk4p5t7.Protocol().registerHostProtocol('halkyon_todo', this);
_iaic.Protocol().registerHostProtocol('halkyon_todo', this);
_ipulbpi2.Protocol().registerHostProtocol('halkyon_todo', this);
_ibkpwzgl.Protocol().registerHostProtocol('halkyon_todo', this);
_iacc.Protocol().registerHostProtocol('halkyon_todo', this);
}
@override
_isd.Table? getTableForType(Type t) {
{
var protocol = _ihk4p5t7.Protocol();
var table = protocol is _isd.DatabaseSerializationManager
? (protocol as _isd.DatabaseSerializationManager).getTableForType(t)
: null;
if (table != null) {
return table;
}
}
{
var protocol = _iaic.Protocol();
var table = protocol is _isd.DatabaseSerializationManager
? (protocol as _isd.DatabaseSerializationManager).getTableForType(t)
: null;
if (table != null) {
return table;
}
}
{
var protocol = _ipulbpi2.Protocol();
var table = protocol is _isd.DatabaseSerializationManager
? (protocol as _isd.DatabaseSerializationManager).getTableForType(t)
: null;
if (table != null) {
return table;
}
}
{
var protocol = _ibkpwzgl.Protocol();
var table = protocol is _isd.DatabaseSerializationManager
? (protocol as _isd.DatabaseSerializationManager).getTableForType(t)
: null;
if (table != null) {
return table;
}
}
{
var protocol = _iacc.Protocol();
var table = protocol is _isd.DatabaseSerializationManager
? (protocol as _isd.DatabaseSerializationManager).getTableForType(t)
: null;
if (table != null) {
return table;
}
}
switch (t) {
case _imed3x5d.ContentKey:
return _imed3x5d.ContentKey.t;
case _i9c44spp.Project:
return _i9c44spp.Project.t;
}
return null;
}
@override
List<_isd.TableDefinition> getTargetTableDefinitions() =>
targetTableDefinitions;
@override
String getModuleName() => 'halkyon_todo';
@ -160,6 +419,9 @@ class Protocol extends _isc.SerializationManager {
try {
return _iaic.Protocol().mapRecordToJson(record);
} catch (_) {}
try {
return _ipulbpi2.Protocol().mapRecordToJson(record);
} catch (_) {}
try {
return _ibkpwzgl.Protocol().mapRecordToJson(record);
} catch (_) {}