feat(server): studysets, the first synced table (migration pending)
Adopts serverpod_offline_sync v0.0.8 (ADR 0001): its module (nickname offline_sync) and the experimental database: sync in generator.yaml, its server and client packages, and overrides that keep the four Serverpod core packages on v4.0.2, since the package pins v4.0.0 exactly and the spike showed it works on v4.0.2. The app's new dependencies come along because the workspace shares one lock file. Studyset is the first synced model. Its space owns it, so it has no owner column, and only the envelope is a column: keyId and nonce stay null (no end-to-end encryption; ADR 0002, rule 6), and payload holds the content as versioned UTF-8 JSON, so a new content field changes the payload's schema version instead of this table, whose every migration locks older app versions out of syncing (rule 3). The audience column of the original design is gone: it served key selection, and the engine's spaces decide who reads a row. The generated server and client code is included. The migration isn't: it is created together with the move to Serverpod v4.0.3 and reviewed first, like the client's local migration (the client's lib/migrations is still empty). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
158a746ecc
commit
6769598df5
18 changed files with 1817 additions and 20 deletions
16
halkyon_learn_client/lib/migrations/migration_registry.dart
Normal file
16
halkyon_learn_client/lib/migrations/migration_registry.dart
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/// AUTOGENERATED BY SERVERPOD generate - do not remove.
|
||||
/// Merging: resolve conflicts like server migration_registry.txt.
|
||||
/// Updated when you run `serverpod create-migration` for the client.
|
||||
library;
|
||||
|
||||
import 'package:serverpod_database/serverpod_database.dart';
|
||||
|
||||
/// Migration registry for the client-side database.
|
||||
class MigrationRegistry {
|
||||
/// Ordered list of all client-side database migrations.
|
||||
/// New migrations are always appended at the end.
|
||||
static final List<MigrationVersionSql> migrations = [];
|
||||
|
||||
/// List of all client-side database migration versions.
|
||||
static List<String> get versions => migrations.map((m) => m.version).toList();
|
||||
}
|
||||
|
|
@ -19,7 +19,12 @@ 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 'protocol.dart' as _il2as5qe;
|
||||
import 'sync_tables.dart' as _ii3f3u05;
|
||||
import 'package:halkyon_learn_client/migrations/migration_registry.dart';
|
||||
|
||||
/// Signs people in anonymously; the app calls it on first launch. Creating an
|
||||
/// account is rate-limited per IP address (see `auth_config.dart`).
|
||||
|
|
@ -78,11 +83,14 @@ class EndpointDevices extends _isc.EndpointRef {
|
|||
class Modules {
|
||||
Modules(Client client) {
|
||||
serverpod_auth_idp = _iaic.Caller(client);
|
||||
offline_sync = _ipulbpi2.Caller(client);
|
||||
serverpod_auth_core = _iacc.Caller(client);
|
||||
}
|
||||
|
||||
late final _iaic.Caller serverpod_auth_idp;
|
||||
|
||||
late final _ipulbpi2.Caller offline_sync;
|
||||
|
||||
late final _iacc.Caller serverpod_auth_core;
|
||||
}
|
||||
|
||||
|
|
@ -133,6 +141,60 @@ class Client extends _isc.ServerpodClientShared {
|
|||
@override
|
||||
Map<String, _isc.ModuleEndpointCaller> get moduleLookup => {
|
||||
'serverpod_auth_idp': modules.serverpod_auth_idp,
|
||||
'offline_sync': modules.offline_sync,
|
||||
'serverpod_auth_core': modules.serverpod_auth_core,
|
||||
};
|
||||
|
||||
/// Creates a new client-side database session for the given path.
|
||||
///
|
||||
/// The [path] is the file path to the SQLite database file. Since SQLite uses
|
||||
/// WAL mode, note that `[path]-shm` and `[path]-wal` files might also exist
|
||||
/// transiently for the database while the session is open.
|
||||
///
|
||||
/// If [runMigrations] is true, pending migrations will be applied when
|
||||
/// opening the database. Be careful when setting this to false, as it might
|
||||
/// lead to inconsistencies between the models and the database.
|
||||
///
|
||||
/// If [isDebugMode] is true, the database integrity will be verified after
|
||||
/// the migrations are applied to provide feedback of possible issues. On a
|
||||
/// Flutter application, this should be set to [kDebugMode].
|
||||
_ida.Future<_isd.ClientDatabaseSession> createSession(
|
||||
String path, {
|
||||
bool runMigrations = true,
|
||||
bool isDebugMode = false,
|
||||
}) async {
|
||||
return await _isd.ClientDatabaseSession.open(
|
||||
path,
|
||||
_il2as5qe.Protocol(),
|
||||
clientMigrations: MigrationRegistry.migrations,
|
||||
runMigrations: runMigrations,
|
||||
isDebugMode: isDebugMode,
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a new client-side database session for the given path, wrapped
|
||||
/// with the `serverpod_offline_sync` engine for the tables declared with
|
||||
/// `database: sync`. See [createSession] for the [path], [runMigrations] and
|
||||
/// [isDebugMode] parameters.
|
||||
///
|
||||
/// The [persistentUserId] is the user all local operations belong to. When
|
||||
/// omitted, the user must be passed through the transaction.
|
||||
_ida.Future<_ipulbpi2.OfflineSyncDatabaseSession> createSyncSession(
|
||||
String path, {
|
||||
bool runMigrations = true,
|
||||
bool isDebugMode = false,
|
||||
_isc.UuidValue? persistentUserId,
|
||||
}) async {
|
||||
final session = _ipulbpi2.OfflineSyncDatabaseSession.wraps(
|
||||
await createSession(
|
||||
path,
|
||||
runMigrations: runMigrations,
|
||||
isDebugMode: isDebugMode,
|
||||
),
|
||||
syncTables: _ii3f3u05.syncTables,
|
||||
persistentUserId: persistentUserId,
|
||||
);
|
||||
await session.db.initialize();
|
||||
return session;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,19 +18,93 @@ 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 'auth/device.dart' as _i6c5ny7n;
|
||||
import 'common/not_found_exception.dart' as _i8oee73b;
|
||||
import 'studyset/studyset.dart' as _ikbclmae;
|
||||
export 'auth/device.dart';
|
||||
export 'common/not_found_exception.dart';
|
||||
export 'studyset/studyset.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: 'studyset',
|
||||
dartName: 'Studyset',
|
||||
schema: 'public',
|
||||
module: 'halkyon_learn',
|
||||
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: 'studyset_fk_0',
|
||||
columns: ['spaceId'],
|
||||
referenceTable: 'offline_sync_spaces',
|
||||
referenceTableSchema: 'public',
|
||||
referenceColumns: ['id'],
|
||||
onUpdate: _isd.ForeignKeyAction.noAction,
|
||||
onDelete: _isd.ForeignKeyAction.cascade,
|
||||
matchType: null,
|
||||
),
|
||||
],
|
||||
indexes: [],
|
||||
managed: true,
|
||||
),
|
||||
..._iaic.Protocol() is _isd.DatabaseSerializationManager
|
||||
? (_iaic.Protocol() as _isd.DatabaseSerializationManager)
|
||||
.getTargetTableDefinitions()
|
||||
: [],
|
||||
..._ipulbpi2.Protocol() is _isd.DatabaseSerializationManager
|
||||
? (_ipulbpi2.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?;
|
||||
|
|
@ -64,6 +138,9 @@ class Protocol extends _isc.SerializationManager {
|
|||
if (t == _i8oee73b.NotFoundException) {
|
||||
return _i8oee73b.NotFoundException.fromJson(data) as T;
|
||||
}
|
||||
if (t == _ikbclmae.Studyset) {
|
||||
return _ikbclmae.Studyset.fromJson(data) as T;
|
||||
}
|
||||
if (t == _isc.getType<_i6c5ny7n.Device?>()) {
|
||||
return (data != null ? _i6c5ny7n.Device.fromJson(data) : null) as T;
|
||||
}
|
||||
|
|
@ -71,6 +148,9 @@ class Protocol extends _isc.SerializationManager {
|
|||
return (data != null ? _i8oee73b.NotFoundException.fromJson(data) : null)
|
||||
as T;
|
||||
}
|
||||
if (t == _isc.getType<_ikbclmae.Studyset?>()) {
|
||||
return (data != null ? _ikbclmae.Studyset.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == List<_ieqh4yr9.Device>) {
|
||||
return (data as List)
|
||||
.map((e) => deserialize<_ieqh4yr9.Device>(e))
|
||||
|
|
@ -80,6 +160,9 @@ class Protocol extends _isc.SerializationManager {
|
|||
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 _iacc.Protocol().deserialize<T>(data, t);
|
||||
} on _isc.DeserializationTypeNotFoundException catch (_) {}
|
||||
|
|
@ -90,6 +173,7 @@ class Protocol extends _isc.SerializationManager {
|
|||
return switch (type) {
|
||||
_i6c5ny7n.Device => 'Device',
|
||||
_i8oee73b.NotFoundException => 'NotFoundException',
|
||||
_ikbclmae.Studyset => 'Studyset',
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
|
@ -111,6 +195,8 @@ class Protocol extends _isc.SerializationManager {
|
|||
return 'Device';
|
||||
case _i8oee73b.NotFoundException():
|
||||
return 'NotFoundException';
|
||||
case _ikbclmae.Studyset():
|
||||
return 'Studyset';
|
||||
}
|
||||
className = _iaic.Protocol().getClassNameForObject(data);
|
||||
if (className != null) {
|
||||
|
|
@ -118,6 +204,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 = _iacc.Protocol().getClassNameForObject(data);
|
||||
if (className != null) {
|
||||
return className.contains('.')
|
||||
|
|
@ -139,10 +231,17 @@ class Protocol extends _isc.SerializationManager {
|
|||
if (dataClassName == 'NotFoundException') {
|
||||
return deserialize<_i8oee73b.NotFoundException>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'Studyset') {
|
||||
return deserialize<_ikbclmae.Studyset>(data['data']);
|
||||
}
|
||||
if (dataClassName.startsWith('serverpod_auth_idp.')) {
|
||||
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('serverpod_auth_core.')) {
|
||||
data['className'] = dataClassName.substring(20);
|
||||
return _iacc.Protocol().deserializeByClassName(data);
|
||||
|
|
@ -152,9 +251,50 @@ class Protocol extends _isc.SerializationManager {
|
|||
|
||||
void _registerHostProtocols() {
|
||||
_iaic.Protocol().registerHostProtocol('halkyon_learn', this);
|
||||
_ipulbpi2.Protocol().registerHostProtocol('halkyon_learn', this);
|
||||
_iacc.Protocol().registerHostProtocol('halkyon_learn', this);
|
||||
}
|
||||
|
||||
@override
|
||||
_isd.Table? getTableForType(Type t) {
|
||||
{
|
||||
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 = _iacc.Protocol();
|
||||
var table = protocol is _isd.DatabaseSerializationManager
|
||||
? (protocol as _isd.DatabaseSerializationManager).getTableForType(t)
|
||||
: null;
|
||||
if (table != null) {
|
||||
return table;
|
||||
}
|
||||
}
|
||||
switch (t) {
|
||||
case _ikbclmae.Studyset:
|
||||
return _ikbclmae.Studyset.t;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
List<_isd.TableDefinition> getTargetTableDefinitions() =>
|
||||
targetTableDefinitions;
|
||||
|
||||
@override
|
||||
String getModuleName() => 'halkyon_learn';
|
||||
|
||||
|
|
@ -170,6 +310,9 @@ class Protocol extends _isc.SerializationManager {
|
|||
try {
|
||||
return _iaic.Protocol().mapRecordToJson(record);
|
||||
} catch (_) {}
|
||||
try {
|
||||
return _ipulbpi2.Protocol().mapRecordToJson(record);
|
||||
} catch (_) {}
|
||||
try {
|
||||
return _iacc.Protocol().mapRecordToJson(record);
|
||||
} catch (_) {}
|
||||
|
|
|
|||
674
halkyon_learn_client/lib/src/protocol/studyset/studyset.dart
Normal file
674
halkyon_learn_client/lib/src/protocol/studyset/studyset.dart
Normal file
|
|
@ -0,0 +1,674 @@
|
|||
/* 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:typed_data' as _idt;
|
||||
import 'package:serverpod_client/serverpod_client.dart' as _isc;
|
||||
import 'package:serverpod_database/serverpod_database.dart' as _isd;
|
||||
|
||||
/// A studyset: the cards, notes and paths that belong together; what people study and
|
||||
/// share. Synced between a person's devices; its space owns it, so it has no owner column.
|
||||
///
|
||||
/// Only the envelope is a column: the content (title, description, …) lives in the
|
||||
/// payload, so a new content field changes the payload's schema version instead of this
|
||||
/// synced table, and older app versions keep syncing (ADR 0002, rule 3).
|
||||
abstract class Studyset
|
||||
implements _isd.TableRow<_isc.UuidValue?>, _isc.ProtocolSerialization {
|
||||
Studyset._({
|
||||
this.id,
|
||||
this.spaceId,
|
||||
this.keyId,
|
||||
required this.payload,
|
||||
this.nonce,
|
||||
});
|
||||
|
||||
factory Studyset({
|
||||
_isc.UuidValue? id,
|
||||
int? spaceId,
|
||||
String? keyId,
|
||||
required _idt.ByteData payload,
|
||||
_idt.ByteData? nonce,
|
||||
}) = _StudysetImpl;
|
||||
|
||||
factory Studyset.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return Studyset(
|
||||
id: jsonSerialization['id'] == null
|
||||
? null
|
||||
: _isc.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
|
||||
spaceId: jsonSerialization['spaceId'] as int?,
|
||||
keyId: jsonSerialization['keyId'] as String?,
|
||||
payload: _isc.ByteDataJsonExtension.fromJson(
|
||||
jsonSerialization['payload'],
|
||||
),
|
||||
nonce: jsonSerialization['nonce'] == null
|
||||
? null
|
||||
: _isc.ByteDataJsonExtension.fromJson(jsonSerialization['nonce']),
|
||||
);
|
||||
}
|
||||
|
||||
static final t = StudysetTable();
|
||||
|
||||
static const db = StudysetRepository._();
|
||||
|
||||
@override
|
||||
_isc.UuidValue? id;
|
||||
|
||||
/// The space owning this row. Maintained by the sync engine.
|
||||
int? spaceId;
|
||||
|
||||
/// The encryption key's id; always null, since content isn't end-to-end encrypted
|
||||
/// (ADR 0002). Kept so opt-in encryption of single studysets stays possible.
|
||||
String? keyId;
|
||||
|
||||
/// The content as UTF-8 JSON: {"schemaVersion", "title", "description"}.
|
||||
_idt.ByteData payload;
|
||||
|
||||
/// The encryption nonce; always null, like keyId.
|
||||
_idt.ByteData? nonce;
|
||||
|
||||
@override
|
||||
_isd.Table<_isc.UuidValue?> get table => t;
|
||||
|
||||
/// Returns a shallow copy of this [Studyset]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
Studyset copyWith({
|
||||
_isc.UuidValue? id,
|
||||
int? spaceId,
|
||||
String? keyId,
|
||||
_idt.ByteData? payload,
|
||||
_idt.ByteData? nonce,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'Studyset',
|
||||
if (id != null) 'id': id?.toJson(),
|
||||
if (spaceId != null) 'spaceId': spaceId,
|
||||
if (keyId != null) 'keyId': keyId,
|
||||
'payload': payload.toJson(),
|
||||
if (nonce != null) 'nonce': nonce?.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'Studyset',
|
||||
if (id != null) 'id': id?.toJson(),
|
||||
if (spaceId != null) 'spaceId': spaceId,
|
||||
if (keyId != null) 'keyId': keyId,
|
||||
'payload': payload.toJson(),
|
||||
if (nonce != null) 'nonce': nonce?.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
static StudysetInclude include() {
|
||||
return StudysetInclude._();
|
||||
}
|
||||
|
||||
static StudysetIncludeList includeList({
|
||||
_isd.WhereExpressionBuilder<StudysetTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_isd.OrderByBuilder<StudysetTable>? orderBy,
|
||||
_isd.OrderByListBuilder<StudysetTable>? orderByList,
|
||||
StudysetInclude? include,
|
||||
}) {
|
||||
return StudysetIncludeList._(
|
||||
where: where,
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(Studyset.t),
|
||||
orderByList: orderByList?.call(Studyset.t),
|
||||
include: include,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _isc.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _StudysetImpl extends Studyset {
|
||||
_StudysetImpl({
|
||||
_isc.UuidValue? id,
|
||||
int? spaceId,
|
||||
String? keyId,
|
||||
required _idt.ByteData payload,
|
||||
_idt.ByteData? nonce,
|
||||
}) : super._(
|
||||
id: id,
|
||||
spaceId: spaceId,
|
||||
keyId: keyId,
|
||||
payload: payload,
|
||||
nonce: nonce,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [Studyset]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
@override
|
||||
Studyset copyWith({
|
||||
Object? id = _Undefined,
|
||||
Object? spaceId = _Undefined,
|
||||
Object? keyId = _Undefined,
|
||||
_idt.ByteData? payload,
|
||||
Object? nonce = _Undefined,
|
||||
}) {
|
||||
return Studyset(
|
||||
id: id is _isc.UuidValue? ? id : this.id,
|
||||
spaceId: spaceId is int? ? spaceId : this.spaceId,
|
||||
keyId: keyId is String? ? keyId : this.keyId,
|
||||
payload: payload ?? this.payload.clone(),
|
||||
nonce: nonce is _idt.ByteData? ? nonce : this.nonce?.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StudysetUpdateTable extends _isd.UpdateTable<StudysetTable> {
|
||||
StudysetUpdateTable(super.table);
|
||||
|
||||
_isd.ColumnValue<int, int> spaceId(int? value) => _isd.ColumnValue(
|
||||
table.spaceId,
|
||||
value,
|
||||
);
|
||||
|
||||
_isd.ColumnValue<String, String> keyId(String? value) => _isd.ColumnValue(
|
||||
table.keyId,
|
||||
value,
|
||||
);
|
||||
|
||||
_isd.ColumnValue<_idt.ByteData, _idt.ByteData> payload(_idt.ByteData value) =>
|
||||
_isd.ColumnValue(
|
||||
table.payload,
|
||||
value,
|
||||
);
|
||||
|
||||
_isd.ColumnValue<_idt.ByteData, _idt.ByteData> nonce(_idt.ByteData? value) =>
|
||||
_isd.ColumnValue(
|
||||
table.nonce,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
class StudysetTable extends _isd.Table<_isc.UuidValue?> {
|
||||
StudysetTable({super.tableRelation}) : super(tableName: 'studyset') {
|
||||
updateTable = StudysetUpdateTable(this);
|
||||
spaceId = _isd.ColumnInt(
|
||||
'spaceId',
|
||||
this,
|
||||
);
|
||||
keyId = _isd.ColumnString(
|
||||
'keyId',
|
||||
this,
|
||||
);
|
||||
payload = _isd.ColumnByteData(
|
||||
'payload',
|
||||
this,
|
||||
);
|
||||
nonce = _isd.ColumnByteData(
|
||||
'nonce',
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
late final StudysetUpdateTable updateTable;
|
||||
|
||||
/// The space owning this row. Maintained by the sync engine.
|
||||
late final _isd.ColumnInt spaceId;
|
||||
|
||||
/// The encryption key's id; always null, since content isn't end-to-end encrypted
|
||||
/// (ADR 0002). Kept so opt-in encryption of single studysets stays possible.
|
||||
late final _isd.ColumnString keyId;
|
||||
|
||||
/// The content as UTF-8 JSON: {"schemaVersion", "title", "description"}.
|
||||
late final _isd.ColumnByteData payload;
|
||||
|
||||
/// The encryption nonce; always null, like keyId.
|
||||
late final _isd.ColumnByteData nonce;
|
||||
|
||||
@override
|
||||
List<_isd.Column> get columns => [
|
||||
id,
|
||||
spaceId,
|
||||
keyId,
|
||||
payload,
|
||||
nonce,
|
||||
];
|
||||
}
|
||||
|
||||
class StudysetInclude extends _isd.IncludeObject {
|
||||
StudysetInclude._();
|
||||
|
||||
@override
|
||||
Map<String, _isd.Include?> get includes => {};
|
||||
|
||||
@override
|
||||
_isd.Table<_isc.UuidValue?> get table => Studyset.t;
|
||||
}
|
||||
|
||||
class StudysetIncludeList extends _isd.IncludeList {
|
||||
StudysetIncludeList._({
|
||||
_isd.WhereExpressionBuilder<StudysetTable>? where,
|
||||
super.limit,
|
||||
super.offset,
|
||||
super.orderBy,
|
||||
super.orderByList,
|
||||
super.include,
|
||||
}) {
|
||||
super.where = where?.call(Studyset.t);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, _isd.Include?> get includes => include?.includes ?? {};
|
||||
|
||||
@override
|
||||
_isd.Table<_isc.UuidValue?> get table => Studyset.t;
|
||||
}
|
||||
|
||||
class StudysetRepository {
|
||||
const StudysetRepository._();
|
||||
|
||||
/// Returns a list of [Studyset]s matching the given query parameters.
|
||||
///
|
||||
/// Use [where] to specify which items to include in the return value.
|
||||
/// If none is specified, all items will be returned.
|
||||
///
|
||||
/// To specify the order of the items use [orderBy] or [orderByList]
|
||||
/// when sorting by multiple columns.
|
||||
///
|
||||
/// The maximum number of items can be set by [limit]. If no limit is set,
|
||||
/// all items matching the query will be returned.
|
||||
///
|
||||
/// [offset] defines how many items to skip, after which [limit] (or all)
|
||||
/// items are read from the database.
|
||||
///
|
||||
/// ```dart
|
||||
/// var persons = await Persons.db.find(
|
||||
/// session,
|
||||
/// where: (t) => t.lastName.equals('Jones'),
|
||||
/// orderBy: (t) => t.firstName,
|
||||
/// limit: 100,
|
||||
/// );
|
||||
/// ```
|
||||
Future<List<Studyset>> find(
|
||||
_isd.DatabaseSession session, {
|
||||
_isd.WhereExpressionBuilder<StudysetTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_isd.OrderByBuilder<StudysetTable>? orderBy,
|
||||
_isd.OrderByListBuilder<StudysetTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
_isd.LockMode? lockMode,
|
||||
_isd.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.find<Studyset>(
|
||||
where: where?.call(Studyset.t),
|
||||
orderBy: orderBy?.call(Studyset.t),
|
||||
orderByList: orderByList?.call(Studyset.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the first matching [Studyset] matching the given query parameters.
|
||||
///
|
||||
/// Use [where] to specify which items to include in the return value.
|
||||
/// If none is specified, all items will be returned.
|
||||
///
|
||||
/// To specify the order use [orderBy] or [orderByList]
|
||||
/// when sorting by multiple columns.
|
||||
///
|
||||
/// [offset] defines how many items to skip, after which the next one will be picked.
|
||||
///
|
||||
/// ```dart
|
||||
/// var youngestPerson = await Persons.db.findFirstRow(
|
||||
/// session,
|
||||
/// where: (t) => t.lastName.equals('Jones'),
|
||||
/// orderBy: (t) => t.age,
|
||||
/// );
|
||||
/// ```
|
||||
Future<Studyset?> findFirstRow(
|
||||
_isd.DatabaseSession session, {
|
||||
_isd.WhereExpressionBuilder<StudysetTable>? where,
|
||||
int? offset,
|
||||
_isd.OrderByBuilder<StudysetTable>? orderBy,
|
||||
_isd.OrderByListBuilder<StudysetTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
_isd.LockMode? lockMode,
|
||||
_isd.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findFirstRow<Studyset>(
|
||||
where: where?.call(Studyset.t),
|
||||
orderBy: orderBy?.call(Studyset.t),
|
||||
orderByList: orderByList?.call(Studyset.t),
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Finds a single [Studyset] by its [id] or null if no such row exists.
|
||||
Future<Studyset?> findById(
|
||||
_isd.DatabaseSession session,
|
||||
_isc.UuidValue id, {
|
||||
_isd.Transaction? transaction,
|
||||
_isd.LockMode? lockMode,
|
||||
_isd.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findById<Studyset>(
|
||||
id,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts all [Studyset]s in the list and returns the inserted rows.
|
||||
///
|
||||
/// The returned [Studyset]s will have their `id` fields set.
|
||||
///
|
||||
/// This is an atomic operation, meaning that if one of the rows fails to
|
||||
/// insert, none of the rows will be inserted.
|
||||
///
|
||||
/// If [ignoreConflicts] is set to `true`, rows that conflict with existing
|
||||
/// rows are silently skipped, and only the successfully inserted rows are
|
||||
/// returned.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the inserted rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<Studyset>> insert(
|
||||
_isd.DatabaseSession session,
|
||||
List<Studyset> rows, {
|
||||
_isd.Transaction? transaction,
|
||||
bool ignoreConflicts = false,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.insert<Studyset>(
|
||||
rows,
|
||||
transaction: transaction,
|
||||
ignoreConflicts: ignoreConflicts,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts a single [Studyset] and returns the inserted row.
|
||||
///
|
||||
/// The returned [Studyset] will have its `id` field set.
|
||||
Future<Studyset> insertRow(
|
||||
_isd.DatabaseSession session,
|
||||
Studyset row, {
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.insertRow<Studyset>(
|
||||
row,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts all [Studyset]s in the list and returns the resulting rows.
|
||||
///
|
||||
/// If a row conflicts on the given [conflictColumns], the existing row is
|
||||
/// updated with the new values. Otherwise, a new row is inserted.
|
||||
///
|
||||
/// If [updateColumns] is provided, only those columns will be updated on
|
||||
/// conflict. If null, all non-conflict, non-id columns are updated.
|
||||
///
|
||||
/// If [updateWhere] is provided, the update only applies to rows matching the
|
||||
/// given expression. Conflicting rows that don't match are skipped and not
|
||||
/// returned, so the resulting list may be shorter than [rows].
|
||||
///
|
||||
/// The returned [Studyset]s will have their `id` fields set.
|
||||
///
|
||||
/// This is an atomic operation, meaning that if one of the rows fails,
|
||||
/// none of the rows will be affected.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the resulting rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<Studyset>> upsert(
|
||||
_isd.DatabaseSession session,
|
||||
List<Studyset> rows, {
|
||||
required _isd.ColumnSelections<StudysetTable> conflictColumns,
|
||||
_isd.ColumnSelections<StudysetTable>? updateColumns,
|
||||
_isd.WhereExpressionBuilder<StudysetTable>? updateWhere,
|
||||
_isd.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.upsert<Studyset>(
|
||||
rows,
|
||||
conflictColumns: conflictColumns(Studyset.t),
|
||||
updateColumns: updateColumns?.call(Studyset.t),
|
||||
updateWhere: updateWhere?.call(Studyset.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts a single [Studyset] and returns the resulting row.
|
||||
///
|
||||
/// If the row conflicts on the given [conflictColumns], the existing row is
|
||||
/// updated. Otherwise, a new row is inserted.
|
||||
///
|
||||
/// If [updateColumns] is provided, only those columns will be updated on
|
||||
/// conflict. If null, all non-conflict, non-id columns are updated.
|
||||
///
|
||||
/// If [updateWhere] is provided, the update only applies when the existing
|
||||
/// row matches the expression. Returns `null` if no row was affected — for
|
||||
/// example when [updateWhere] does not match the conflicting row.
|
||||
///
|
||||
/// The returned [Studyset] will have its `id` field set.
|
||||
Future<Studyset?> upsertRow(
|
||||
_isd.DatabaseSession session,
|
||||
Studyset row, {
|
||||
required _isd.ColumnSelections<StudysetTable> conflictColumns,
|
||||
_isd.ColumnSelections<StudysetTable>? updateColumns,
|
||||
_isd.WhereExpressionBuilder<StudysetTable>? updateWhere,
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.upsertRow<Studyset>(
|
||||
row,
|
||||
conflictColumns: conflictColumns(Studyset.t),
|
||||
updateColumns: updateColumns?.call(Studyset.t),
|
||||
updateWhere: updateWhere?.call(Studyset.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [Studyset]s in the list and returns the updated rows. If
|
||||
/// [columns] is provided, only those columns will be updated. Defaults to
|
||||
/// all columns.
|
||||
/// This is an atomic operation, meaning that if one of the rows fails to
|
||||
/// update, none of the rows will be updated.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the updated rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<Studyset>> update(
|
||||
_isd.DatabaseSession session,
|
||||
List<Studyset> rows, {
|
||||
_isd.ColumnSelections<StudysetTable>? columns,
|
||||
_isd.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.update<Studyset>(
|
||||
rows,
|
||||
columns: columns?.call(Studyset.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [Studyset]. The row needs to have its id set.
|
||||
/// Optionally, a list of [columns] can be provided to only update those
|
||||
/// columns. Defaults to all columns.
|
||||
Future<Studyset> updateRow(
|
||||
_isd.DatabaseSession session,
|
||||
Studyset row, {
|
||||
_isd.ColumnSelections<StudysetTable>? columns,
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateRow<Studyset>(
|
||||
row,
|
||||
columns: columns?.call(Studyset.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [Studyset] by its [id] with the specified [columnValues].
|
||||
/// Returns the updated row or null if no row with the given id exists.
|
||||
Future<Studyset?> updateById(
|
||||
_isd.DatabaseSession session,
|
||||
_isc.UuidValue id, {
|
||||
required _isd.ColumnValueListBuilder<StudysetUpdateTable> columnValues,
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateById<Studyset>(
|
||||
id,
|
||||
columnValues: columnValues(Studyset.t.updateTable),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [Studyset]s matching the [where] expression with the specified [columnValues].
|
||||
/// Returns the list of updated rows.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the updated rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<Studyset>> updateWhere(
|
||||
_isd.DatabaseSession session, {
|
||||
required _isd.ColumnValueListBuilder<StudysetUpdateTable> columnValues,
|
||||
required _isd.WhereExpressionBuilder<StudysetTable> where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_isd.OrderByBuilder<StudysetTable>? orderBy,
|
||||
_isd.OrderByListBuilder<StudysetTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.updateWhere<Studyset>(
|
||||
columnValues: columnValues(Studyset.t.updateTable),
|
||||
where: where(Studyset.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(Studyset.t),
|
||||
orderByList: orderByList?.call(Studyset.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes all [Studyset]s in the list and returns the deleted rows.
|
||||
///
|
||||
/// To specify the order of the returned rows use [orderBy] or [orderByList]
|
||||
/// when sorting by multiple columns.
|
||||
///
|
||||
/// This is an atomic operation, meaning that if one of the rows fail to
|
||||
/// be deleted, none of the rows will be deleted.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the deleted rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<Studyset>> delete(
|
||||
_isd.DatabaseSession session,
|
||||
List<Studyset> rows, {
|
||||
_isd.OrderByBuilder<StudysetTable>? orderBy,
|
||||
_isd.OrderByListBuilder<StudysetTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.delete<Studyset>(
|
||||
rows,
|
||||
orderBy: orderBy?.call(Studyset.t),
|
||||
orderByList: orderByList?.call(Studyset.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes a single [Studyset].
|
||||
Future<Studyset> deleteRow(
|
||||
_isd.DatabaseSession session,
|
||||
Studyset row, {
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.deleteRow<Studyset>(
|
||||
row,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes all rows matching the [where] expression.
|
||||
///
|
||||
/// To specify the order of the returned rows use [orderBy] or [orderByList]
|
||||
/// when sorting by multiple columns.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the deleted rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<Studyset>> deleteWhere(
|
||||
_isd.DatabaseSession session, {
|
||||
required _isd.WhereExpressionBuilder<StudysetTable> where,
|
||||
_isd.OrderByBuilder<StudysetTable>? orderBy,
|
||||
_isd.OrderByListBuilder<StudysetTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.deleteWhere<Studyset>(
|
||||
where: where(Studyset.t),
|
||||
orderBy: orderBy?.call(Studyset.t),
|
||||
orderByList: orderByList?.call(Studyset.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Counts the number of rows matching the [where] expression. If omitted,
|
||||
/// will return the count of all rows in the table.
|
||||
Future<int> count(
|
||||
_isd.DatabaseSession session, {
|
||||
_isd.WhereExpressionBuilder<StudysetTable>? where,
|
||||
int? limit,
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.count<Studyset>(
|
||||
where: where?.call(Studyset.t),
|
||||
limit: limit,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Acquires row-level locks on [Studyset] rows matching the [where] expression.
|
||||
Future<void> lockRows(
|
||||
_isd.DatabaseSession session, {
|
||||
required _isd.WhereExpressionBuilder<StudysetTable> where,
|
||||
required _isd.LockMode lockMode,
|
||||
required _isd.Transaction transaction,
|
||||
_isd.LockBehavior lockBehavior = _isd.LockBehavior.wait,
|
||||
}) async {
|
||||
return session.db.lockRows<Studyset>(
|
||||
where: where(Studyset.t),
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
}
|
||||
19
halkyon_learn_client/lib/src/protocol/sync_tables.dart
Normal file
19
halkyon_learn_client/lib/src/protocol/sync_tables.dart
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/* 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_database/serverpod_database.dart' as _isd;
|
||||
import 'studyset/studyset.dart' as _ikbclmae;
|
||||
|
||||
/// The tables synchronized between client and server by the
|
||||
/// `serverpod_offline_sync` package, including the ones owned by modules and
|
||||
/// shared packages.
|
||||
final List<_isd.Table> syncTables = [_ikbclmae.Studyset.t];
|
||||
|
|
@ -9,3 +9,4 @@ resolution: workspace
|
|||
dependencies:
|
||||
serverpod_auth_idp_client: 4.0.2
|
||||
serverpod_client: 4.0.2
|
||||
serverpod_offline_sync_client: 0.0.8
|
||||
|
|
|
|||
|
|
@ -38,10 +38,14 @@ dependencies:
|
|||
halkyon_learn_client:
|
||||
path: ../halkyon_learn_client
|
||||
material_ui: ^1.5.0
|
||||
path: ^1.9.1
|
||||
path_provider: ^2.1.6
|
||||
serverpod_auth_idp_flutter: 4.0.2
|
||||
serverpod_flutter: 4.0.2
|
||||
serverpod_offline_sync_client: 0.0.8
|
||||
|
||||
dev_dependencies:
|
||||
fake_async: ^1.3.3
|
||||
# Used for agents to drive the app through the Dart MCP
|
||||
flutter_driver:
|
||||
sdk: flutter
|
||||
|
|
|
|||
|
|
@ -2,3 +2,12 @@ type: server
|
|||
|
||||
client_package_path: ../halkyon_learn_client
|
||||
server_test_tools_path: test/integration/test_tools
|
||||
|
||||
# Private and shared data sync through serverpod_offline_sync (ADR 0001): its module,
|
||||
# and the experimental `database: sync` for the models it syncs.
|
||||
modules:
|
||||
serverpod_offline_sync:
|
||||
nickname: offline_sync
|
||||
|
||||
experimental_features:
|
||||
databaseSync: true
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import 'package:serverpod_auth_core_server/serverpod_auth_core_server.dart'
|
|||
as _iacs;
|
||||
import 'package:serverpod_auth_idp_server/serverpod_auth_idp_server.dart'
|
||||
as _iais;
|
||||
import 'package:serverpod_offline_sync_server/serverpod_offline_sync_server.dart'
|
||||
as _izehhkf5;
|
||||
import '../auth/anonymous_idp_endpoint.dart' as _in0zita6;
|
||||
import '../auth/devices_endpoint.dart' as _iduct7ue;
|
||||
|
||||
|
|
@ -98,6 +100,8 @@ class Endpoints extends _is.EndpointDispatch {
|
|||
);
|
||||
modules['serverpod_auth_idp'] = _iais.Endpoints()
|
||||
..initializeEndpoints(server);
|
||||
modules['serverpod_offline_sync'] = _izehhkf5.Endpoints()
|
||||
..initializeEndpoints(server);
|
||||
modules['serverpod_auth_core'] = _iacs.Endpoints()
|
||||
..initializeEndpoints(server);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,10 +19,15 @@ import 'package:serverpod_auth_core_server/serverpod_auth_core_server.dart'
|
|||
as _iacs;
|
||||
import 'package:serverpod_auth_idp_server/serverpod_auth_idp_server.dart'
|
||||
as _iais;
|
||||
import 'package:serverpod_offline_sync_server/serverpod_offline_sync_server.dart'
|
||||
as _izehhkf5;
|
||||
import 'auth/device.dart' as _i6c5ny7n;
|
||||
import 'common/not_found_exception.dart' as _i8oee73b;
|
||||
import 'studyset/studyset.dart' as _ikbclmae;
|
||||
export 'auth/device.dart';
|
||||
export 'common/not_found_exception.dart';
|
||||
export 'studyset/studyset.dart';
|
||||
export 'sync_tables.dart';
|
||||
|
||||
class Protocol extends _is.DatabaseSerializationManager {
|
||||
Protocol._();
|
||||
|
|
@ -32,7 +37,61 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
static final Protocol _instance = Protocol._().._registerHostProtocols();
|
||||
|
||||
static List<_isp.TableDefinition> get targetTableDefinitions => [
|
||||
_isp.TableDefinition(
|
||||
name: 'studyset',
|
||||
dartName: 'Studyset',
|
||||
schema: 'public',
|
||||
module: 'halkyon_learn',
|
||||
columns: [
|
||||
_isp.ColumnDefinition(
|
||||
name: 'id',
|
||||
columnType: _isp.ColumnType.uuid,
|
||||
isNullable: false,
|
||||
dartType: 'UuidValue?',
|
||||
columnDefault: 'random_v7',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'spaceId',
|
||||
columnType: _isp.ColumnType.bigint,
|
||||
isNullable: true,
|
||||
dartType: 'int?',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'keyId',
|
||||
columnType: _isp.ColumnType.text,
|
||||
isNullable: true,
|
||||
dartType: 'String?',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'payload',
|
||||
columnType: _isp.ColumnType.bytea,
|
||||
isNullable: false,
|
||||
dartType: 'dart:typed_data:ByteData',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'nonce',
|
||||
columnType: _isp.ColumnType.bytea,
|
||||
isNullable: true,
|
||||
dartType: 'dart:typed_data:ByteData?',
|
||||
),
|
||||
],
|
||||
foreignKeys: [
|
||||
_isp.ForeignKeyDefinition(
|
||||
constraintName: 'studyset_fk_0',
|
||||
columns: ['spaceId'],
|
||||
referenceTable: 'offline_sync_spaces',
|
||||
referenceTableSchema: 'public',
|
||||
referenceColumns: ['id'],
|
||||
onUpdate: _isp.ForeignKeyAction.noAction,
|
||||
onDelete: _isp.ForeignKeyAction.cascade,
|
||||
matchType: null,
|
||||
),
|
||||
],
|
||||
indexes: [],
|
||||
managed: true,
|
||||
),
|
||||
..._iais.Protocol.targetTableDefinitions,
|
||||
..._izehhkf5.Protocol.targetTableDefinitions,
|
||||
..._iacs.Protocol.targetTableDefinitions,
|
||||
..._isp.Protocol.targetTableDefinitions,
|
||||
];
|
||||
|
|
@ -70,6 +129,9 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
if (t == _i8oee73b.NotFoundException) {
|
||||
return _i8oee73b.NotFoundException.fromJson(data) as T;
|
||||
}
|
||||
if (t == _ikbclmae.Studyset) {
|
||||
return _ikbclmae.Studyset.fromJson(data) as T;
|
||||
}
|
||||
if (t == _is.getType<_i6c5ny7n.Device?>()) {
|
||||
return (data != null ? _i6c5ny7n.Device.fromJson(data) : null) as T;
|
||||
}
|
||||
|
|
@ -77,6 +139,9 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
return (data != null ? _i8oee73b.NotFoundException.fromJson(data) : null)
|
||||
as T;
|
||||
}
|
||||
if (t == _is.getType<_ikbclmae.Studyset?>()) {
|
||||
return (data != null ? _ikbclmae.Studyset.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == List<_ipdmo8cq.Device>) {
|
||||
return (data as List)
|
||||
.map((e) => deserialize<_ipdmo8cq.Device>(e))
|
||||
|
|
@ -86,6 +151,9 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
try {
|
||||
return _iais.Protocol().deserialize<T>(data, t);
|
||||
} on _is.DeserializationTypeNotFoundException catch (_) {}
|
||||
try {
|
||||
return _izehhkf5.Protocol().deserialize<T>(data, t);
|
||||
} on _is.DeserializationTypeNotFoundException catch (_) {}
|
||||
try {
|
||||
return _iacs.Protocol().deserialize<T>(data, t);
|
||||
} on _is.DeserializationTypeNotFoundException catch (_) {}
|
||||
|
|
@ -99,6 +167,7 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
return switch (type) {
|
||||
_i6c5ny7n.Device => 'Device',
|
||||
_i8oee73b.NotFoundException => 'NotFoundException',
|
||||
_ikbclmae.Studyset => 'Studyset',
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
|
@ -120,6 +189,8 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
return 'Device';
|
||||
case _i8oee73b.NotFoundException():
|
||||
return 'NotFoundException';
|
||||
case _ikbclmae.Studyset():
|
||||
return 'Studyset';
|
||||
}
|
||||
className = _iais.Protocol().getClassNameForObject(data);
|
||||
if (className != null) {
|
||||
|
|
@ -127,6 +198,12 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
? className
|
||||
: 'serverpod_auth_idp.$className';
|
||||
}
|
||||
className = _izehhkf5.Protocol().getClassNameForObject(data);
|
||||
if (className != null) {
|
||||
return className.contains('.')
|
||||
? className
|
||||
: 'serverpod_offline_sync.$className';
|
||||
}
|
||||
className = _iacs.Protocol().getClassNameForObject(data);
|
||||
if (className != null) {
|
||||
return className.contains('.')
|
||||
|
|
@ -152,10 +229,17 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
if (dataClassName == 'NotFoundException') {
|
||||
return deserialize<_i8oee73b.NotFoundException>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'Studyset') {
|
||||
return deserialize<_ikbclmae.Studyset>(data['data']);
|
||||
}
|
||||
if (dataClassName.startsWith('serverpod_auth_idp.')) {
|
||||
data['className'] = dataClassName.substring(19);
|
||||
return _iais.Protocol().deserializeByClassName(data);
|
||||
}
|
||||
if (dataClassName.startsWith('serverpod_offline_sync.')) {
|
||||
data['className'] = dataClassName.substring(23);
|
||||
return _izehhkf5.Protocol().deserializeByClassName(data);
|
||||
}
|
||||
if (dataClassName.startsWith('serverpod_auth_core.')) {
|
||||
data['className'] = dataClassName.substring(20);
|
||||
return _iacs.Protocol().deserializeByClassName(data);
|
||||
|
|
@ -169,6 +253,7 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
|
||||
void _registerHostProtocols() {
|
||||
_iais.Protocol().registerHostProtocol('halkyon_learn', this);
|
||||
_izehhkf5.Protocol().registerHostProtocol('halkyon_learn', this);
|
||||
_iacs.Protocol().registerHostProtocol('halkyon_learn', this);
|
||||
}
|
||||
|
||||
|
|
@ -180,6 +265,12 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
return table;
|
||||
}
|
||||
}
|
||||
{
|
||||
var table = _izehhkf5.Protocol().getTableForType(t);
|
||||
if (table != null) {
|
||||
return table;
|
||||
}
|
||||
}
|
||||
{
|
||||
var table = _iacs.Protocol().getTableForType(t);
|
||||
if (table != null) {
|
||||
|
|
@ -192,6 +283,10 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
return table;
|
||||
}
|
||||
}
|
||||
switch (t) {
|
||||
case _ikbclmae.Studyset:
|
||||
return _ikbclmae.Studyset.t;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -214,6 +309,9 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
try {
|
||||
return _iais.Protocol().mapRecordToJson(record);
|
||||
} catch (_) {}
|
||||
try {
|
||||
return _izehhkf5.Protocol().mapRecordToJson(record);
|
||||
} catch (_) {}
|
||||
try {
|
||||
return _iacs.Protocol().mapRecordToJson(record);
|
||||
} catch (_) {}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,11 @@
|
|||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:io' as _idi;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
import 'package:serverpod_offline_sync_server/serverpod_offline_sync_server.dart'
|
||||
as _izehhkf5;
|
||||
import 'endpoints.dart' as _iavctuc6;
|
||||
import 'protocol.dart' as _il2as5qe;
|
||||
import 'sync_tables.dart' as _ii3f3u05;
|
||||
export 'package:serverpod/serverpod.dart' hide Serverpod;
|
||||
|
||||
/// The Serverpod server for this project.
|
||||
|
|
@ -25,6 +28,10 @@ export 'package:serverpod/serverpod.dart' hide Serverpod;
|
|||
/// ```dart
|
||||
/// final pod = Serverpod(args);
|
||||
/// ```
|
||||
///
|
||||
/// The `serverpod_offline_sync` engine is initialized with the tables
|
||||
/// declared with `database: sync`, wrapping any provided
|
||||
/// [databaseInterceptor] with `offlineSyncDatabaseInterceptor`.
|
||||
class Serverpod extends _is.Serverpod {
|
||||
Serverpod(
|
||||
List<String> args, {
|
||||
|
|
@ -55,6 +62,19 @@ class Serverpod extends _is.Serverpod {
|
|||
securityContextConfig: securityContextConfig,
|
||||
experimentalFeatures: experimentalFeatures,
|
||||
runtimeParametersBuilder: runtimeParametersBuilder,
|
||||
databaseInterceptor: databaseInterceptor,
|
||||
);
|
||||
databaseInterceptor:
|
||||
(
|
||||
session,
|
||||
inner,
|
||||
) => _izehhkf5.offlineSyncDatabaseInterceptor(
|
||||
session,
|
||||
databaseInterceptor?.call(
|
||||
session,
|
||||
inner,
|
||||
) ??
|
||||
inner,
|
||||
),
|
||||
) {
|
||||
initializeOfflineSync(syncTables: _ii3f3u05.syncTables);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
671
halkyon_learn_server/lib/src/generated/studyset/studyset.dart
Normal file
671
halkyon_learn_server/lib/src/generated/studyset/studyset.dart
Normal file
|
|
@ -0,0 +1,671 @@
|
|||
/* 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:typed_data' as _idt;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
|
||||
/// A studyset: the cards, notes and paths that belong together; what people study and
|
||||
/// share. Synced between a person's devices; its space owns it, so it has no owner column.
|
||||
///
|
||||
/// Only the envelope is a column: the content (title, description, …) lives in the
|
||||
/// payload, so a new content field changes the payload's schema version instead of this
|
||||
/// synced table, and older app versions keep syncing (ADR 0002, rule 3).
|
||||
abstract class Studyset
|
||||
implements _is.TableRow<_is.UuidValue?>, _is.ProtocolSerialization {
|
||||
Studyset._({
|
||||
this.id,
|
||||
this.spaceId,
|
||||
this.keyId,
|
||||
required this.payload,
|
||||
this.nonce,
|
||||
});
|
||||
|
||||
factory Studyset({
|
||||
_is.UuidValue? id,
|
||||
int? spaceId,
|
||||
String? keyId,
|
||||
required _idt.ByteData payload,
|
||||
_idt.ByteData? nonce,
|
||||
}) = _StudysetImpl;
|
||||
|
||||
factory Studyset.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return Studyset(
|
||||
id: jsonSerialization['id'] == null
|
||||
? null
|
||||
: _is.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
|
||||
spaceId: jsonSerialization['spaceId'] as int?,
|
||||
keyId: jsonSerialization['keyId'] as String?,
|
||||
payload: _is.ByteDataJsonExtension.fromJson(jsonSerialization['payload']),
|
||||
nonce: jsonSerialization['nonce'] == null
|
||||
? null
|
||||
: _is.ByteDataJsonExtension.fromJson(jsonSerialization['nonce']),
|
||||
);
|
||||
}
|
||||
|
||||
static final t = StudysetTable();
|
||||
|
||||
static const db = StudysetRepository._();
|
||||
|
||||
@override
|
||||
_is.UuidValue? id;
|
||||
|
||||
/// The space owning this row. Maintained by the sync engine.
|
||||
int? spaceId;
|
||||
|
||||
/// The encryption key's id; always null, since content isn't end-to-end encrypted
|
||||
/// (ADR 0002). Kept so opt-in encryption of single studysets stays possible.
|
||||
String? keyId;
|
||||
|
||||
/// The content as UTF-8 JSON: {"schemaVersion", "title", "description"}.
|
||||
_idt.ByteData payload;
|
||||
|
||||
/// The encryption nonce; always null, like keyId.
|
||||
_idt.ByteData? nonce;
|
||||
|
||||
@override
|
||||
_is.Table<_is.UuidValue?> get table => t;
|
||||
|
||||
/// Returns a shallow copy of this [Studyset]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
Studyset copyWith({
|
||||
_is.UuidValue? id,
|
||||
int? spaceId,
|
||||
String? keyId,
|
||||
_idt.ByteData? payload,
|
||||
_idt.ByteData? nonce,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'Studyset',
|
||||
if (id != null) 'id': id?.toJson(),
|
||||
if (spaceId != null) 'spaceId': spaceId,
|
||||
if (keyId != null) 'keyId': keyId,
|
||||
'payload': payload.toJson(),
|
||||
if (nonce != null) 'nonce': nonce?.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'Studyset',
|
||||
if (id != null) 'id': id?.toJson(),
|
||||
if (spaceId != null) 'spaceId': spaceId,
|
||||
if (keyId != null) 'keyId': keyId,
|
||||
'payload': payload.toJson(),
|
||||
if (nonce != null) 'nonce': nonce?.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
static StudysetInclude include() {
|
||||
return StudysetInclude._();
|
||||
}
|
||||
|
||||
static StudysetIncludeList includeList({
|
||||
_is.WhereExpressionBuilder<StudysetTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<StudysetTable>? orderBy,
|
||||
_is.OrderByListBuilder<StudysetTable>? orderByList,
|
||||
StudysetInclude? include,
|
||||
}) {
|
||||
return StudysetIncludeList._(
|
||||
where: where,
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(Studyset.t),
|
||||
orderByList: orderByList?.call(Studyset.t),
|
||||
include: include,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _is.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _StudysetImpl extends Studyset {
|
||||
_StudysetImpl({
|
||||
_is.UuidValue? id,
|
||||
int? spaceId,
|
||||
String? keyId,
|
||||
required _idt.ByteData payload,
|
||||
_idt.ByteData? nonce,
|
||||
}) : super._(
|
||||
id: id,
|
||||
spaceId: spaceId,
|
||||
keyId: keyId,
|
||||
payload: payload,
|
||||
nonce: nonce,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [Studyset]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
Studyset copyWith({
|
||||
Object? id = _Undefined,
|
||||
Object? spaceId = _Undefined,
|
||||
Object? keyId = _Undefined,
|
||||
_idt.ByteData? payload,
|
||||
Object? nonce = _Undefined,
|
||||
}) {
|
||||
return Studyset(
|
||||
id: id is _is.UuidValue? ? id : this.id,
|
||||
spaceId: spaceId is int? ? spaceId : this.spaceId,
|
||||
keyId: keyId is String? ? keyId : this.keyId,
|
||||
payload: payload ?? this.payload.clone(),
|
||||
nonce: nonce is _idt.ByteData? ? nonce : this.nonce?.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StudysetUpdateTable extends _is.UpdateTable<StudysetTable> {
|
||||
StudysetUpdateTable(super.table);
|
||||
|
||||
_is.ColumnValue<int, int> spaceId(int? value) => _is.ColumnValue(
|
||||
table.spaceId,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<String, String> keyId(String? value) => _is.ColumnValue(
|
||||
table.keyId,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<_idt.ByteData, _idt.ByteData> payload(_idt.ByteData value) =>
|
||||
_is.ColumnValue(
|
||||
table.payload,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<_idt.ByteData, _idt.ByteData> nonce(_idt.ByteData? value) =>
|
||||
_is.ColumnValue(
|
||||
table.nonce,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
class StudysetTable extends _is.Table<_is.UuidValue?> {
|
||||
StudysetTable({super.tableRelation}) : super(tableName: 'studyset') {
|
||||
updateTable = StudysetUpdateTable(this);
|
||||
spaceId = _is.ColumnInt(
|
||||
'spaceId',
|
||||
this,
|
||||
);
|
||||
keyId = _is.ColumnString(
|
||||
'keyId',
|
||||
this,
|
||||
);
|
||||
payload = _is.ColumnByteData(
|
||||
'payload',
|
||||
this,
|
||||
);
|
||||
nonce = _is.ColumnByteData(
|
||||
'nonce',
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
late final StudysetUpdateTable updateTable;
|
||||
|
||||
/// The space owning this row. Maintained by the sync engine.
|
||||
late final _is.ColumnInt spaceId;
|
||||
|
||||
/// The encryption key's id; always null, since content isn't end-to-end encrypted
|
||||
/// (ADR 0002). Kept so opt-in encryption of single studysets stays possible.
|
||||
late final _is.ColumnString keyId;
|
||||
|
||||
/// The content as UTF-8 JSON: {"schemaVersion", "title", "description"}.
|
||||
late final _is.ColumnByteData payload;
|
||||
|
||||
/// The encryption nonce; always null, like keyId.
|
||||
late final _is.ColumnByteData nonce;
|
||||
|
||||
@override
|
||||
List<_is.Column> get columns => [
|
||||
id,
|
||||
spaceId,
|
||||
keyId,
|
||||
payload,
|
||||
nonce,
|
||||
];
|
||||
}
|
||||
|
||||
class StudysetInclude extends _is.IncludeObject {
|
||||
StudysetInclude._();
|
||||
|
||||
@override
|
||||
Map<String, _is.Include?> get includes => {};
|
||||
|
||||
@override
|
||||
_is.Table<_is.UuidValue?> get table => Studyset.t;
|
||||
}
|
||||
|
||||
class StudysetIncludeList extends _is.IncludeList {
|
||||
StudysetIncludeList._({
|
||||
_is.WhereExpressionBuilder<StudysetTable>? where,
|
||||
super.limit,
|
||||
super.offset,
|
||||
super.orderBy,
|
||||
super.orderByList,
|
||||
super.include,
|
||||
}) {
|
||||
super.where = where?.call(Studyset.t);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, _is.Include?> get includes => include?.includes ?? {};
|
||||
|
||||
@override
|
||||
_is.Table<_is.UuidValue?> get table => Studyset.t;
|
||||
}
|
||||
|
||||
class StudysetRepository {
|
||||
const StudysetRepository._();
|
||||
|
||||
/// Returns a list of [Studyset]s matching the given query parameters.
|
||||
///
|
||||
/// Use [where] to specify which items to include in the return value.
|
||||
/// If none is specified, all items will be returned.
|
||||
///
|
||||
/// To specify the order of the items use [orderBy] or [orderByList]
|
||||
/// when sorting by multiple columns.
|
||||
///
|
||||
/// The maximum number of items can be set by [limit]. If no limit is set,
|
||||
/// all items matching the query will be returned.
|
||||
///
|
||||
/// [offset] defines how many items to skip, after which [limit] (or all)
|
||||
/// items are read from the database.
|
||||
///
|
||||
/// ```dart
|
||||
/// var persons = await Persons.db.find(
|
||||
/// session,
|
||||
/// where: (t) => t.lastName.equals('Jones'),
|
||||
/// orderBy: (t) => t.firstName,
|
||||
/// limit: 100,
|
||||
/// );
|
||||
/// ```
|
||||
Future<List<Studyset>> find(
|
||||
_is.DatabaseSession session, {
|
||||
_is.WhereExpressionBuilder<StudysetTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<StudysetTable>? orderBy,
|
||||
_is.OrderByListBuilder<StudysetTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.find<Studyset>(
|
||||
where: where?.call(Studyset.t),
|
||||
orderBy: orderBy?.call(Studyset.t),
|
||||
orderByList: orderByList?.call(Studyset.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the first matching [Studyset] matching the given query parameters.
|
||||
///
|
||||
/// Use [where] to specify which items to include in the return value.
|
||||
/// If none is specified, all items will be returned.
|
||||
///
|
||||
/// To specify the order use [orderBy] or [orderByList]
|
||||
/// when sorting by multiple columns.
|
||||
///
|
||||
/// [offset] defines how many items to skip, after which the next one will be picked.
|
||||
///
|
||||
/// ```dart
|
||||
/// var youngestPerson = await Persons.db.findFirstRow(
|
||||
/// session,
|
||||
/// where: (t) => t.lastName.equals('Jones'),
|
||||
/// orderBy: (t) => t.age,
|
||||
/// );
|
||||
/// ```
|
||||
Future<Studyset?> findFirstRow(
|
||||
_is.DatabaseSession session, {
|
||||
_is.WhereExpressionBuilder<StudysetTable>? where,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<StudysetTable>? orderBy,
|
||||
_is.OrderByListBuilder<StudysetTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findFirstRow<Studyset>(
|
||||
where: where?.call(Studyset.t),
|
||||
orderBy: orderBy?.call(Studyset.t),
|
||||
orderByList: orderByList?.call(Studyset.t),
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Finds a single [Studyset] by its [id] or null if no such row exists.
|
||||
Future<Studyset?> findById(
|
||||
_is.DatabaseSession session,
|
||||
_is.UuidValue id, {
|
||||
_is.Transaction? transaction,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findById<Studyset>(
|
||||
id,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts all [Studyset]s in the list and returns the inserted rows.
|
||||
///
|
||||
/// The returned [Studyset]s will have their `id` fields set.
|
||||
///
|
||||
/// This is an atomic operation, meaning that if one of the rows fails to
|
||||
/// insert, none of the rows will be inserted.
|
||||
///
|
||||
/// If [ignoreConflicts] is set to `true`, rows that conflict with existing
|
||||
/// rows are silently skipped, and only the successfully inserted rows are
|
||||
/// returned.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the inserted rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<Studyset>> insert(
|
||||
_is.DatabaseSession session,
|
||||
List<Studyset> rows, {
|
||||
_is.Transaction? transaction,
|
||||
bool ignoreConflicts = false,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.insert<Studyset>(
|
||||
rows,
|
||||
transaction: transaction,
|
||||
ignoreConflicts: ignoreConflicts,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts a single [Studyset] and returns the inserted row.
|
||||
///
|
||||
/// The returned [Studyset] will have its `id` field set.
|
||||
Future<Studyset> insertRow(
|
||||
_is.DatabaseSession session,
|
||||
Studyset row, {
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.insertRow<Studyset>(
|
||||
row,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts all [Studyset]s in the list and returns the resulting rows.
|
||||
///
|
||||
/// If a row conflicts on the given [conflictColumns], the existing row is
|
||||
/// updated with the new values. Otherwise, a new row is inserted.
|
||||
///
|
||||
/// If [updateColumns] is provided, only those columns will be updated on
|
||||
/// conflict. If null, all non-conflict, non-id columns are updated.
|
||||
///
|
||||
/// If [updateWhere] is provided, the update only applies to rows matching the
|
||||
/// given expression. Conflicting rows that don't match are skipped and not
|
||||
/// returned, so the resulting list may be shorter than [rows].
|
||||
///
|
||||
/// The returned [Studyset]s will have their `id` fields set.
|
||||
///
|
||||
/// This is an atomic operation, meaning that if one of the rows fails,
|
||||
/// none of the rows will be affected.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the resulting rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<Studyset>> upsert(
|
||||
_is.DatabaseSession session,
|
||||
List<Studyset> rows, {
|
||||
required _is.ColumnSelections<StudysetTable> conflictColumns,
|
||||
_is.ColumnSelections<StudysetTable>? updateColumns,
|
||||
_is.WhereExpressionBuilder<StudysetTable>? updateWhere,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.upsert<Studyset>(
|
||||
rows,
|
||||
conflictColumns: conflictColumns(Studyset.t),
|
||||
updateColumns: updateColumns?.call(Studyset.t),
|
||||
updateWhere: updateWhere?.call(Studyset.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts a single [Studyset] and returns the resulting row.
|
||||
///
|
||||
/// If the row conflicts on the given [conflictColumns], the existing row is
|
||||
/// updated. Otherwise, a new row is inserted.
|
||||
///
|
||||
/// If [updateColumns] is provided, only those columns will be updated on
|
||||
/// conflict. If null, all non-conflict, non-id columns are updated.
|
||||
///
|
||||
/// If [updateWhere] is provided, the update only applies when the existing
|
||||
/// row matches the expression. Returns `null` if no row was affected — for
|
||||
/// example when [updateWhere] does not match the conflicting row.
|
||||
///
|
||||
/// The returned [Studyset] will have its `id` field set.
|
||||
Future<Studyset?> upsertRow(
|
||||
_is.DatabaseSession session,
|
||||
Studyset row, {
|
||||
required _is.ColumnSelections<StudysetTable> conflictColumns,
|
||||
_is.ColumnSelections<StudysetTable>? updateColumns,
|
||||
_is.WhereExpressionBuilder<StudysetTable>? updateWhere,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.upsertRow<Studyset>(
|
||||
row,
|
||||
conflictColumns: conflictColumns(Studyset.t),
|
||||
updateColumns: updateColumns?.call(Studyset.t),
|
||||
updateWhere: updateWhere?.call(Studyset.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [Studyset]s in the list and returns the updated rows. If
|
||||
/// [columns] is provided, only those columns will be updated. Defaults to
|
||||
/// all columns.
|
||||
/// This is an atomic operation, meaning that if one of the rows fails to
|
||||
/// update, none of the rows will be updated.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the updated rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<Studyset>> update(
|
||||
_is.DatabaseSession session,
|
||||
List<Studyset> rows, {
|
||||
_is.ColumnSelections<StudysetTable>? columns,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.update<Studyset>(
|
||||
rows,
|
||||
columns: columns?.call(Studyset.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [Studyset]. The row needs to have its id set.
|
||||
/// Optionally, a list of [columns] can be provided to only update those
|
||||
/// columns. Defaults to all columns.
|
||||
Future<Studyset> updateRow(
|
||||
_is.DatabaseSession session,
|
||||
Studyset row, {
|
||||
_is.ColumnSelections<StudysetTable>? columns,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateRow<Studyset>(
|
||||
row,
|
||||
columns: columns?.call(Studyset.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [Studyset] by its [id] with the specified [columnValues].
|
||||
/// Returns the updated row or null if no row with the given id exists.
|
||||
Future<Studyset?> updateById(
|
||||
_is.DatabaseSession session,
|
||||
_is.UuidValue id, {
|
||||
required _is.ColumnValueListBuilder<StudysetUpdateTable> columnValues,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateById<Studyset>(
|
||||
id,
|
||||
columnValues: columnValues(Studyset.t.updateTable),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [Studyset]s matching the [where] expression with the specified [columnValues].
|
||||
/// Returns the list of updated rows.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the updated rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<Studyset>> updateWhere(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.ColumnValueListBuilder<StudysetUpdateTable> columnValues,
|
||||
required _is.WhereExpressionBuilder<StudysetTable> where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<StudysetTable>? orderBy,
|
||||
_is.OrderByListBuilder<StudysetTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.updateWhere<Studyset>(
|
||||
columnValues: columnValues(Studyset.t.updateTable),
|
||||
where: where(Studyset.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(Studyset.t),
|
||||
orderByList: orderByList?.call(Studyset.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes all [Studyset]s in the list and returns the deleted rows.
|
||||
///
|
||||
/// To specify the order of the returned rows use [orderBy] or [orderByList]
|
||||
/// when sorting by multiple columns.
|
||||
///
|
||||
/// This is an atomic operation, meaning that if one of the rows fail to
|
||||
/// be deleted, none of the rows will be deleted.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the deleted rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<Studyset>> delete(
|
||||
_is.DatabaseSession session,
|
||||
List<Studyset> rows, {
|
||||
_is.OrderByBuilder<StudysetTable>? orderBy,
|
||||
_is.OrderByListBuilder<StudysetTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.delete<Studyset>(
|
||||
rows,
|
||||
orderBy: orderBy?.call(Studyset.t),
|
||||
orderByList: orderByList?.call(Studyset.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes a single [Studyset].
|
||||
Future<Studyset> deleteRow(
|
||||
_is.DatabaseSession session,
|
||||
Studyset row, {
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.deleteRow<Studyset>(
|
||||
row,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes all rows matching the [where] expression.
|
||||
///
|
||||
/// To specify the order of the returned rows use [orderBy] or [orderByList]
|
||||
/// when sorting by multiple columns.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the deleted rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<Studyset>> deleteWhere(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.WhereExpressionBuilder<StudysetTable> where,
|
||||
_is.OrderByBuilder<StudysetTable>? orderBy,
|
||||
_is.OrderByListBuilder<StudysetTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.deleteWhere<Studyset>(
|
||||
where: where(Studyset.t),
|
||||
orderBy: orderBy?.call(Studyset.t),
|
||||
orderByList: orderByList?.call(Studyset.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Counts the number of rows matching the [where] expression. If omitted,
|
||||
/// will return the count of all rows in the table.
|
||||
Future<int> count(
|
||||
_is.DatabaseSession session, {
|
||||
_is.WhereExpressionBuilder<StudysetTable>? where,
|
||||
int? limit,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.count<Studyset>(
|
||||
where: where?.call(Studyset.t),
|
||||
limit: limit,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Acquires row-level locks on [Studyset] rows matching the [where] expression.
|
||||
Future<void> lockRows(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.WhereExpressionBuilder<StudysetTable> where,
|
||||
required _is.LockMode lockMode,
|
||||
required _is.Transaction transaction,
|
||||
_is.LockBehavior lockBehavior = _is.LockBehavior.wait,
|
||||
}) async {
|
||||
return session.db.lockRows<Studyset>(
|
||||
where: where(Studyset.t),
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
}
|
||||
19
halkyon_learn_server/lib/src/generated/sync_tables.dart
Normal file
19
halkyon_learn_server/lib/src/generated/sync_tables.dart
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/* 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/serverpod.dart' as _is;
|
||||
import 'studyset/studyset.dart' as _ikbclmae;
|
||||
|
||||
/// The tables synchronized between client and server by the
|
||||
/// `serverpod_offline_sync` package, including the ones owned by modules and
|
||||
/// shared packages.
|
||||
final List<_is.Table> syncTables = [_ikbclmae.Studyset.t];
|
||||
17
halkyon_learn_server/lib/src/studyset/studyset.spy.yaml
Normal file
17
halkyon_learn_server/lib/src/studyset/studyset.spy.yaml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
### A studyset: the cards, notes and paths that belong together; what people study and
|
||||
### share. Synced between a person's devices; its space owns it, so it has no owner column.
|
||||
###
|
||||
### Only the envelope is a column: the content (title, description, …) lives in the
|
||||
### payload, so a new content field changes the payload's schema version instead of this
|
||||
### synced table, and older app versions keep syncing (ADR 0002, rule 3).
|
||||
class: Studyset
|
||||
table: studyset
|
||||
database: sync
|
||||
fields:
|
||||
### The encryption key's id; always null, since content isn't end-to-end encrypted
|
||||
### (ADR 0002). Kept so opt-in encryption of single studysets stays possible.
|
||||
keyId: String?
|
||||
### The content as UTF-8 JSON: {"schemaVersion", "title", "description"}.
|
||||
payload: ByteData
|
||||
### The encryption nonce; always null, like keyId.
|
||||
nonce: ByteData?
|
||||
|
|
@ -11,6 +11,7 @@ resolution: workspace
|
|||
dependencies:
|
||||
serverpod: 4.0.2
|
||||
serverpod_auth_idp_server: 4.0.2
|
||||
serverpod_offline_sync_server: 0.0.8
|
||||
dev_dependencies:
|
||||
serverpod_test: 4.0.2
|
||||
test: ^1.25.5
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _ida;
|
||||
import 'dart:io' as _idi;
|
||||
|
||||
import 'package:halkyon_learn_server/src/generated/auth/device.dart'
|
||||
as _ipdmo8cq;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
|
|
@ -199,10 +198,12 @@ class _AnonymousIdpEndpoint {
|
|||
parameters: _ist.testObjectToJson({'token': token}),
|
||||
serializationManager: _serializationManager,
|
||||
);
|
||||
var _localReturnValue = await (_localCallContext.method.call(
|
||||
_localUniqueSession,
|
||||
_localCallContext.arguments,
|
||||
) as _ida.Future<_iacs.AuthSuccess>);
|
||||
var _localReturnValue =
|
||||
await (_localCallContext.method.call(
|
||||
_localUniqueSession,
|
||||
_localCallContext.arguments,
|
||||
)
|
||||
as _ida.Future<_iacs.AuthSuccess>);
|
||||
return _localReturnValue;
|
||||
} finally {
|
||||
await _localUniqueSession.close();
|
||||
|
|
@ -238,10 +239,12 @@ class _DevicesEndpoint {
|
|||
parameters: _ist.testObjectToJson({}),
|
||||
serializationManager: _serializationManager,
|
||||
);
|
||||
var _localReturnValue = await (_localCallContext.method.call(
|
||||
_localUniqueSession,
|
||||
_localCallContext.arguments,
|
||||
) as _ida.Future<List<_ipdmo8cq.Device>>);
|
||||
var _localReturnValue =
|
||||
await (_localCallContext.method.call(
|
||||
_localUniqueSession,
|
||||
_localCallContext.arguments,
|
||||
)
|
||||
as _ida.Future<List<_ipdmo8cq.Device>>);
|
||||
return _localReturnValue;
|
||||
} finally {
|
||||
await _localUniqueSession.close();
|
||||
|
|
@ -267,10 +270,12 @@ class _DevicesEndpoint {
|
|||
parameters: _ist.testObjectToJson({'sessionId': sessionId}),
|
||||
serializationManager: _serializationManager,
|
||||
);
|
||||
var _localReturnValue = await (_localCallContext.method.call(
|
||||
_localUniqueSession,
|
||||
_localCallContext.arguments,
|
||||
) as _ida.Future<void>);
|
||||
var _localReturnValue =
|
||||
await (_localCallContext.method.call(
|
||||
_localUniqueSession,
|
||||
_localCallContext.arguments,
|
||||
)
|
||||
as _ida.Future<void>);
|
||||
return _localReturnValue;
|
||||
} finally {
|
||||
await _localUniqueSession.close();
|
||||
|
|
|
|||
32
pubspec.lock
32
pubspec.lock
|
|
@ -1111,7 +1111,7 @@ packages:
|
|||
source: hosted
|
||||
version: "3.0.1"
|
||||
serverpod:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: serverpod
|
||||
sha256: "5de0b0c9ac5ebcba3d71e171382f4051352cf4af5ff475c5fa660d325d52c237"
|
||||
|
|
@ -1167,7 +1167,7 @@ packages:
|
|||
source: hosted
|
||||
version: "4.0.2"
|
||||
serverpod_client:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: serverpod_client
|
||||
sha256: "85ed6639f7b4acd7d39eba63e8213195561eed48a3333dc9a146281db6a189ae"
|
||||
|
|
@ -1175,7 +1175,7 @@ packages:
|
|||
source: hosted
|
||||
version: "4.0.2"
|
||||
serverpod_database:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: serverpod_database
|
||||
sha256: b27f7182251c5aca49d5285b9885025b5520498ff447cd101f06b9ca6dff1a17
|
||||
|
|
@ -1214,8 +1214,32 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
serverpod_serialization:
|
||||
serverpod_offline_sync:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: serverpod_offline_sync
|
||||
sha256: abf99fdc78dfe02e21960d4618acb3cbb71976262731b6f17c2c29f2782450c4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.8"
|
||||
serverpod_offline_sync_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: serverpod_offline_sync_client
|
||||
sha256: e517b3161dc1a07f5bed4801aa971349a550774ae6f0b85472d0d273c88a3198
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.8"
|
||||
serverpod_offline_sync_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: serverpod_offline_sync_server
|
||||
sha256: fcb313b21a72b04602793de0719bca7aeaf929f97b90f5bfe77a82a91e5eaf36
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.8"
|
||||
serverpod_serialization:
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: serverpod_serialization
|
||||
sha256: c9882640744d2308081b3e7c1b4bf962f502105b14754317e002ccbf748850f2
|
||||
|
|
|
|||
10
pubspec.yaml
10
pubspec.yaml
|
|
@ -14,3 +14,13 @@ workspace:
|
|||
dev_dependencies:
|
||||
# Referenced by analysis_options.yaml in this directory.
|
||||
leancode_lint: ^27.0.0
|
||||
|
||||
# serverpod_offline_sync v0.0.8 pins Serverpod v4.0.0 exactly. The sync spike showed the
|
||||
# pin is conservative (serverpod_database v4.0.0 and v4.0.2 have identical lib/, and sync
|
||||
# works on v4.0.2), so these four keep the whole workspace on the Serverpod we run. Every
|
||||
# Serverpod upgrade re-checks them until the package widens its constraints (ADR 0001).
|
||||
dependency_overrides:
|
||||
serverpod: 4.0.2
|
||||
serverpod_client: 4.0.2
|
||||
serverpod_database: 4.0.2
|
||||
serverpod_serialization: 4.0.2
|
||||
|
|
|
|||
Loading…
Reference in a new issue