feat: notes in studysets, synced and merged as cards are, with tags

Server: three synced tables, only additions (migration
20261002191122122-notes): `note` (its studyset, a fractional position),
`note_revision` (append-only, as a card's: parents, author, report,
the payload {schemaVersion, title, body}) and `note_tag` (one row per
assignment, never updated, as `card_tag`). Each indexes `spaceId`, and
everything goes with its space, studyset, note or tag. A migration of
synced tables locks app builds from before it out of syncing (ADR 0002,
rule 3): cheap now, before the first release.

App, `feature/note`:
- `NoteContent` is a title and rich text, merged apart (ADR 0003): a
  title changed on one device and the body on another both survive, a
  body changed on two is a conflict.
- `NoteRepository` creates a note at the end of its studyset, saves an
  edit on the revisions it started from (`saveEdit`, as the card
  composer now does), says when a note was last edited (its newest
  head's UUID v7), counts notes per studyset, and deletes a note's
  revisions blanked first (ADR 0002, rule 2).
- Notes share the person's tags: `setTagsOfNote`, and folding tags of
  one name or undoing a tag's deletion covers notes too.
- The delayed deletion with its Undo moves to `core/async`
  (`DelayedDeletions`): `StudysetDeletions` builds on it and now takes
  a studyset's notes too; `NoteDeletions` deletes a single note.

Tests: content, merge, payload, repository, deletions, tags, the device
migration guard, and sync tests against the server (four).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-02 21:53:56 +02:00
commit f3b56f2c0c
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
14 changed files with 25469 additions and 131 deletions

View file

@ -0,0 +1,724 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: dead_code, unnecessary_null_comparison
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:halkyon_learn_server/src/generated/protocol.dart' as _i5lsenq5;
import 'package:serverpod/serverpod.dart' as _is;
import '../studyset/studyset.dart' as _ie5k1jc2;
/// A note in a studyset: rich text the person writes and reads, never studied.
/// Its content lives in its revisions (NoteRevision), never in this row: content
/// is append-only (ADR 0001, rule 2), as a card's is. Synced; its space owns it.
abstract class Note
implements _is.TableRow<_is.UuidValue?>, _is.ProtocolSerialization {
Note._({
this.id,
this.spaceId,
required this.studysetId,
this.studyset,
required this.position,
});
factory Note({
_is.UuidValue? id,
int? spaceId,
required _is.UuidValue studysetId,
_ie5k1jc2.Studyset? studyset,
required String position,
}) = _NoteImpl;
factory Note.fromJson(Map<String, dynamic> jsonSerialization) {
return Note(
id: jsonSerialization['id'] == null
? null
: _is.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
spaceId: jsonSerialization['spaceId'] as int?,
studysetId: _is.UuidValueJsonExtension.fromJson(
jsonSerialization['studysetId'],
),
studyset: jsonSerialization['studyset'] == null
? null
: _i5lsenq5.Protocol().deserialize<_ie5k1jc2.Studyset>(
jsonSerialization['studyset'],
),
position: jsonSerialization['position'] as String,
);
}
static final t = NoteTable();
static const db = NoteRepository._();
@override
_is.UuidValue? id;
/// The space owning this row. Maintained by the sync engine.
int? spaceId;
_is.UuidValue studysetId;
/// The studyset the note belongs to; the note goes with it.
_ie5k1jc2.Studyset? studyset;
/// Where the note stands among its studyset's notes: a fractional index, as a
/// card's position; last writer wins.
String position;
@override
_is.Table<_is.UuidValue?> get table => t;
/// Returns a shallow copy of this [Note]
/// with some or all fields replaced by the given arguments.
@_is.useResult
Note copyWith({
_is.UuidValue? id,
int? spaceId,
_is.UuidValue? studysetId,
_ie5k1jc2.Studyset? studyset,
String? position,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'Note',
if (id != null) 'id': id?.toJson(),
if (spaceId != null) 'spaceId': spaceId,
'studysetId': studysetId.toJson(),
if (studyset != null) 'studyset': studyset?.toJson(),
'position': position,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'Note',
if (id != null) 'id': id?.toJson(),
if (spaceId != null) 'spaceId': spaceId,
'studysetId': studysetId.toJson(),
if (studyset != null) 'studyset': studyset?.toJsonForProtocol(),
'position': position,
};
}
static NoteInclude include({_ie5k1jc2.StudysetInclude? studyset}) {
return NoteInclude._(studyset: studyset);
}
static NoteIncludeList includeList({
_is.WhereExpressionBuilder<NoteTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<NoteTable>? orderBy,
_is.OrderByListBuilder<NoteTable>? orderByList,
NoteInclude? include,
}) {
return NoteIncludeList._(
where: where,
limit: limit,
offset: offset,
orderBy: orderBy?.call(Note.t),
orderByList: orderByList?.call(Note.t),
include: include,
);
}
@override
String toString() {
return _is.SerializationManager.encode(this);
}
}
class _Undefined {}
class _NoteImpl extends Note {
_NoteImpl({
_is.UuidValue? id,
int? spaceId,
required _is.UuidValue studysetId,
_ie5k1jc2.Studyset? studyset,
required String position,
}) : super._(
id: id,
spaceId: spaceId,
studysetId: studysetId,
studyset: studyset,
position: position,
);
/// Returns a shallow copy of this [Note]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@override
Note copyWith({
Object? id = _Undefined,
Object? spaceId = _Undefined,
_is.UuidValue? studysetId,
Object? studyset = _Undefined,
String? position,
}) {
return Note(
id: id is _is.UuidValue? ? id : this.id,
spaceId: spaceId is int? ? spaceId : this.spaceId,
studysetId: studysetId ?? this.studysetId,
studyset: studyset is _ie5k1jc2.Studyset?
? studyset
: this.studyset?.copyWith(),
position: position ?? this.position,
);
}
}
class NoteUpdateTable extends _is.UpdateTable<NoteTable> {
NoteUpdateTable(super.table);
_is.ColumnValue<int, int> spaceId(int? value) => _is.ColumnValue(
table.spaceId,
value,
);
_is.ColumnValue<_is.UuidValue, _is.UuidValue> studysetId(
_is.UuidValue value,
) => _is.ColumnValue(
table.studysetId,
value,
);
_is.ColumnValue<String, String> position(String value) => _is.ColumnValue(
table.position,
value,
);
}
class NoteTable extends _is.Table<_is.UuidValue?> {
NoteTable({super.tableRelation}) : super(tableName: 'note') {
updateTable = NoteUpdateTable(this);
spaceId = _is.ColumnInt(
'spaceId',
this,
);
studysetId = _is.ColumnUuid(
'studysetId',
this,
);
position = _is.ColumnString(
'position',
this,
);
}
late final NoteUpdateTable updateTable;
/// The space owning this row. Maintained by the sync engine.
late final _is.ColumnInt spaceId;
late final _is.ColumnUuid studysetId;
/// The studyset the note belongs to; the note goes with it.
_ie5k1jc2.StudysetTable? _studyset;
/// Where the note stands among its studyset's notes: a fractional index, as a
/// card's position; last writer wins.
late final _is.ColumnString position;
_ie5k1jc2.StudysetTable get studyset {
if (_studyset != null) return _studyset!;
_studyset = _is.createRelationTable(
relationFieldName: 'studyset',
field: Note.t.studysetId,
foreignField: _ie5k1jc2.Studyset.t.id,
tableRelation: tableRelation,
createTable: (foreignTableRelation) =>
_ie5k1jc2.StudysetTable(tableRelation: foreignTableRelation),
);
return _studyset!;
}
@override
List<_is.Column> get columns => [
id,
spaceId,
studysetId,
position,
];
@override
_is.Table? getRelationTable(String relationField) {
if (relationField == 'studyset') {
return studyset;
}
return null;
}
}
class NoteInclude extends _is.IncludeObject {
NoteInclude._({_ie5k1jc2.StudysetInclude? studyset}) {
_studyset = studyset;
}
_ie5k1jc2.StudysetInclude? _studyset;
@override
Map<String, _is.Include?> get includes => {'studyset': _studyset};
@override
_is.Table<_is.UuidValue?> get table => Note.t;
}
class NoteIncludeList extends _is.IncludeList {
NoteIncludeList._({
_is.WhereExpressionBuilder<NoteTable>? where,
super.limit,
super.offset,
super.orderBy,
super.orderByList,
super.include,
}) {
super.where = where?.call(Note.t);
}
@override
Map<String, _is.Include?> get includes => include?.includes ?? {};
@override
_is.Table<_is.UuidValue?> get table => Note.t;
}
class NoteRepository {
const NoteRepository._();
final attachRow = const NoteAttachRowRepository._();
/// Returns a list of [Note]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<Note>> find(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<NoteTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<NoteTable>? orderBy,
_is.OrderByListBuilder<NoteTable>? orderByList,
_is.Transaction? transaction,
NoteInclude? include,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.find<Note>(
where: where?.call(Note.t),
orderBy: orderBy?.call(Note.t),
orderByList: orderByList?.call(Note.t),
limit: limit,
offset: offset,
transaction: transaction,
include: include,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Returns the first matching [Note] 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<Note?> findFirstRow(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<NoteTable>? where,
int? offset,
_is.OrderByBuilder<NoteTable>? orderBy,
_is.OrderByListBuilder<NoteTable>? orderByList,
_is.Transaction? transaction,
NoteInclude? include,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findFirstRow<Note>(
where: where?.call(Note.t),
orderBy: orderBy?.call(Note.t),
orderByList: orderByList?.call(Note.t),
offset: offset,
transaction: transaction,
include: include,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Finds a single [Note] by its [id] or null if no such row exists.
Future<Note?> findById(
_is.DatabaseSession session,
_is.UuidValue id, {
_is.Transaction? transaction,
NoteInclude? include,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findById<Note>(
id,
transaction: transaction,
include: include,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Inserts all [Note]s in the list and returns the inserted rows.
///
/// The returned [Note]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<Note>> insert(
_is.DatabaseSession session,
List<Note> rows, {
_is.Transaction? transaction,
bool ignoreConflicts = false,
bool noReturn = false,
}) async {
return session.db.insert<Note>(
rows,
transaction: transaction,
ignoreConflicts: ignoreConflicts,
noReturn: noReturn,
);
}
/// Inserts a single [Note] and returns the inserted row.
///
/// The returned [Note] will have its `id` field set.
Future<Note> insertRow(
_is.DatabaseSession session,
Note row, {
_is.Transaction? transaction,
}) async {
return session.db.insertRow<Note>(
row,
transaction: transaction,
);
}
/// Upserts all [Note]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 [Note]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<Note>> upsert(
_is.DatabaseSession session,
List<Note> rows, {
required _is.ColumnSelections<NoteTable> conflictColumns,
_is.ColumnSelections<NoteTable>? updateColumns,
_is.WhereExpressionBuilder<NoteTable>? updateWhere,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.upsert<Note>(
rows,
conflictColumns: conflictColumns(Note.t),
updateColumns: updateColumns?.call(Note.t),
updateWhere: updateWhere?.call(Note.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Upserts a single [Note] 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 [Note] will have its `id` field set.
Future<Note?> upsertRow(
_is.DatabaseSession session,
Note row, {
required _is.ColumnSelections<NoteTable> conflictColumns,
_is.ColumnSelections<NoteTable>? updateColumns,
_is.WhereExpressionBuilder<NoteTable>? updateWhere,
_is.Transaction? transaction,
}) async {
return session.db.upsertRow<Note>(
row,
conflictColumns: conflictColumns(Note.t),
updateColumns: updateColumns?.call(Note.t),
updateWhere: updateWhere?.call(Note.t),
transaction: transaction,
);
}
/// Updates all [Note]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<Note>> update(
_is.DatabaseSession session,
List<Note> rows, {
_is.ColumnSelections<NoteTable>? columns,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.update<Note>(
rows,
columns: columns?.call(Note.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Updates a single [Note]. 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<Note> updateRow(
_is.DatabaseSession session,
Note row, {
_is.ColumnSelections<NoteTable>? columns,
_is.Transaction? transaction,
}) async {
return session.db.updateRow<Note>(
row,
columns: columns?.call(Note.t),
transaction: transaction,
);
}
/// Updates a single [Note] by its [id] with the specified [columnValues].
/// Returns the updated row or null if no row with the given id exists.
Future<Note?> updateById(
_is.DatabaseSession session,
_is.UuidValue id, {
required _is.ColumnValueListBuilder<NoteUpdateTable> columnValues,
_is.Transaction? transaction,
}) async {
return session.db.updateById<Note>(
id,
columnValues: columnValues(Note.t.updateTable),
transaction: transaction,
);
}
/// Updates all [Note]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<Note>> updateWhere(
_is.DatabaseSession session, {
required _is.ColumnValueListBuilder<NoteUpdateTable> columnValues,
required _is.WhereExpressionBuilder<NoteTable> where,
int? limit,
int? offset,
_is.OrderByBuilder<NoteTable>? orderBy,
_is.OrderByListBuilder<NoteTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.updateWhere<Note>(
columnValues: columnValues(Note.t.updateTable),
where: where(Note.t),
limit: limit,
offset: offset,
orderBy: orderBy?.call(Note.t),
orderByList: orderByList?.call(Note.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes all [Note]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<Note>> delete(
_is.DatabaseSession session,
List<Note> rows, {
_is.OrderByBuilder<NoteTable>? orderBy,
_is.OrderByListBuilder<NoteTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.delete<Note>(
rows,
orderBy: orderBy?.call(Note.t),
orderByList: orderByList?.call(Note.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes a single [Note].
Future<Note> deleteRow(
_is.DatabaseSession session,
Note row, {
_is.Transaction? transaction,
}) async {
return session.db.deleteRow<Note>(
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<Note>> deleteWhere(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<NoteTable> where,
_is.OrderByBuilder<NoteTable>? orderBy,
_is.OrderByListBuilder<NoteTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.deleteWhere<Note>(
where: where(Note.t),
orderBy: orderBy?.call(Note.t),
orderByList: orderByList?.call(Note.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<NoteTable>? where,
int? limit,
_is.Transaction? transaction,
}) async {
return session.db.count<Note>(
where: where?.call(Note.t),
limit: limit,
transaction: transaction,
);
}
/// Acquires row-level locks on [Note] rows matching the [where] expression.
Future<void> lockRows(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<NoteTable> where,
required _is.LockMode lockMode,
required _is.Transaction transaction,
_is.LockBehavior lockBehavior = _is.LockBehavior.wait,
}) async {
return session.db.lockRows<Note>(
where: where(Note.t),
lockMode: lockMode,
lockBehavior: lockBehavior,
transaction: transaction,
);
}
}
class NoteAttachRowRepository {
const NoteAttachRowRepository._();
/// Creates a relation between the given [Note] and [Studyset]
/// by setting the [Note]'s foreign key `studysetId` to refer to the [Studyset].
Future<void> studyset(
_is.DatabaseSession session,
Note note,
_ie5k1jc2.Studyset studyset, {
_is.Transaction? transaction,
}) async {
if (note.id == null) {
throw ArgumentError.notNull('note.id');
}
if (studyset.id == null) {
throw ArgumentError.notNull('studyset.id');
}
var $note = note.copyWith(studysetId: studyset.id);
await session.db.updateRow<Note>(
$note,
columns: [Note.t.studysetId],
transaction: transaction,
);
}
}

View file

@ -0,0 +1,869 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: dead_code, unnecessary_null_comparison
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:typed_data' as _idt;
import 'package:halkyon_learn_server/src/generated/protocol.dart' as _i5lsenq5;
import 'package:serverpod/serverpod.dart' as _is;
import '../note/note.dart' as _iu6jc5sx;
/// One version of a note's content, never changed once written (ADR 0001,
/// rule 2), merged as a card's revisions are (ADR 0003): editing appends a
/// revision whose parents are the versions the edit started from, and a note's
/// current versions are the revisions no other replaces. Deleting a note blanks
/// every revision's payload first (ADR 0002, rule 2).
abstract class NoteRevision
implements _is.TableRow<_is.UuidValue?>, _is.ProtocolSerialization {
NoteRevision._({
this.id,
this.spaceId,
required this.noteId,
this.note,
required this.parents,
required this.authorId,
this.reportId,
this.keyId,
required this.payload,
this.nonce,
});
factory NoteRevision({
_is.UuidValue? id,
int? spaceId,
required _is.UuidValue noteId,
_iu6jc5sx.Note? note,
required List<_is.UuidValue> parents,
required _is.UuidValue authorId,
_is.UuidValue? reportId,
String? keyId,
required _idt.ByteData payload,
_idt.ByteData? nonce,
}) = _NoteRevisionImpl;
factory NoteRevision.fromJson(Map<String, dynamic> jsonSerialization) {
return NoteRevision(
id: jsonSerialization['id'] == null
? null
: _is.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
spaceId: jsonSerialization['spaceId'] as int?,
noteId: _is.UuidValueJsonExtension.fromJson(jsonSerialization['noteId']),
note: jsonSerialization['note'] == null
? null
: _i5lsenq5.Protocol().deserialize<_iu6jc5sx.Note>(
jsonSerialization['note'],
),
parents: _i5lsenq5.Protocol().deserialize<List<_is.UuidValue>>(
jsonSerialization['parents'],
),
authorId: _is.UuidValueJsonExtension.fromJson(
jsonSerialization['authorId'],
),
reportId: jsonSerialization['reportId'] == null
? null
: _is.UuidValueJsonExtension.fromJson(jsonSerialization['reportId']),
keyId: jsonSerialization['keyId'] as String?,
payload: _is.ByteDataJsonExtension.fromJson(jsonSerialization['payload']),
nonce: jsonSerialization['nonce'] == null
? null
: _is.ByteDataJsonExtension.fromJson(jsonSerialization['nonce']),
);
}
static final t = NoteRevisionTable();
static const db = NoteRevisionRepository._();
@override
_is.UuidValue? id;
/// The space owning this row. Maintained by the sync engine.
int? spaceId;
_is.UuidValue noteId;
/// The note this is a version of; its revisions go with it.
_iu6jc5sx.Note? note;
/// The revisions this one replaces: none for a note's first, several when it
/// resolves a conflict. Plain ids, since revisions can arrive in any order.
List<_is.UuidValue> parents;
/// Who wrote it: the AuthUser's id (a synced table can't relate to the
/// server-only AuthUser).
_is.UuidValue authorId;
/// The report this revision answers, once reports exist; null otherwise.
_is.UuidValue? reportId;
/// The encryption key's id; always null (ADR 0002, rule 6).
String? keyId;
/// The content as UTF-8 JSON: {"schemaVersion", "title", "body"}.
_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 [NoteRevision]
/// with some or all fields replaced by the given arguments.
@_is.useResult
NoteRevision copyWith({
_is.UuidValue? id,
int? spaceId,
_is.UuidValue? noteId,
_iu6jc5sx.Note? note,
List<_is.UuidValue>? parents,
_is.UuidValue? authorId,
_is.UuidValue? reportId,
String? keyId,
_idt.ByteData? payload,
_idt.ByteData? nonce,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'NoteRevision',
if (id != null) 'id': id?.toJson(),
if (spaceId != null) 'spaceId': spaceId,
'noteId': noteId.toJson(),
if (note != null) 'note': note?.toJson(),
'parents': parents.toJson(valueToJson: (v) => v.toJson()),
'authorId': authorId.toJson(),
if (reportId != null) 'reportId': reportId?.toJson(),
if (keyId != null) 'keyId': keyId,
'payload': payload.toJson(),
if (nonce != null) 'nonce': nonce?.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'NoteRevision',
if (id != null) 'id': id?.toJson(),
if (spaceId != null) 'spaceId': spaceId,
'noteId': noteId.toJson(),
if (note != null) 'note': note?.toJsonForProtocol(),
'parents': parents.toJson(valueToJson: (v) => v.toJson()),
'authorId': authorId.toJson(),
if (reportId != null) 'reportId': reportId?.toJson(),
if (keyId != null) 'keyId': keyId,
'payload': payload.toJson(),
if (nonce != null) 'nonce': nonce?.toJson(),
};
}
static NoteRevisionInclude include({_iu6jc5sx.NoteInclude? note}) {
return NoteRevisionInclude._(note: note);
}
static NoteRevisionIncludeList includeList({
_is.WhereExpressionBuilder<NoteRevisionTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<NoteRevisionTable>? orderBy,
_is.OrderByListBuilder<NoteRevisionTable>? orderByList,
NoteRevisionInclude? include,
}) {
return NoteRevisionIncludeList._(
where: where,
limit: limit,
offset: offset,
orderBy: orderBy?.call(NoteRevision.t),
orderByList: orderByList?.call(NoteRevision.t),
include: include,
);
}
@override
String toString() {
return _is.SerializationManager.encode(this);
}
}
class _Undefined {}
class _NoteRevisionImpl extends NoteRevision {
_NoteRevisionImpl({
_is.UuidValue? id,
int? spaceId,
required _is.UuidValue noteId,
_iu6jc5sx.Note? note,
required List<_is.UuidValue> parents,
required _is.UuidValue authorId,
_is.UuidValue? reportId,
String? keyId,
required _idt.ByteData payload,
_idt.ByteData? nonce,
}) : super._(
id: id,
spaceId: spaceId,
noteId: noteId,
note: note,
parents: parents,
authorId: authorId,
reportId: reportId,
keyId: keyId,
payload: payload,
nonce: nonce,
);
/// Returns a shallow copy of this [NoteRevision]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@override
NoteRevision copyWith({
Object? id = _Undefined,
Object? spaceId = _Undefined,
_is.UuidValue? noteId,
Object? note = _Undefined,
List<_is.UuidValue>? parents,
_is.UuidValue? authorId,
Object? reportId = _Undefined,
Object? keyId = _Undefined,
_idt.ByteData? payload,
Object? nonce = _Undefined,
}) {
return NoteRevision(
id: id is _is.UuidValue? ? id : this.id,
spaceId: spaceId is int? ? spaceId : this.spaceId,
noteId: noteId ?? this.noteId,
note: note is _iu6jc5sx.Note? ? note : this.note?.copyWith(),
parents: parents ?? this.parents.map((e0) => e0).toList(),
authorId: authorId ?? this.authorId,
reportId: reportId is _is.UuidValue? ? reportId : this.reportId,
keyId: keyId is String? ? keyId : this.keyId,
payload: payload ?? this.payload.clone(),
nonce: nonce is _idt.ByteData? ? nonce : this.nonce?.clone(),
);
}
}
class NoteRevisionUpdateTable extends _is.UpdateTable<NoteRevisionTable> {
NoteRevisionUpdateTable(super.table);
_is.ColumnValue<int, int> spaceId(int? value) => _is.ColumnValue(
table.spaceId,
value,
);
_is.ColumnValue<_is.UuidValue, _is.UuidValue> noteId(_is.UuidValue value) =>
_is.ColumnValue(
table.noteId,
value,
);
_is.ColumnValue<List<_is.UuidValue>, List<_is.UuidValue>> parents(
List<_is.UuidValue> value,
) => _is.ColumnValue(
table.parents,
value,
);
_is.ColumnValue<_is.UuidValue, _is.UuidValue> authorId(_is.UuidValue value) =>
_is.ColumnValue(
table.authorId,
value,
);
_is.ColumnValue<_is.UuidValue, _is.UuidValue> reportId(
_is.UuidValue? value,
) => _is.ColumnValue(
table.reportId,
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 NoteRevisionTable extends _is.Table<_is.UuidValue?> {
NoteRevisionTable({super.tableRelation}) : super(tableName: 'note_revision') {
updateTable = NoteRevisionUpdateTable(this);
spaceId = _is.ColumnInt(
'spaceId',
this,
);
noteId = _is.ColumnUuid(
'noteId',
this,
);
parents = _is.ColumnSerializable<List<_is.UuidValue>>(
'parents',
this,
);
authorId = _is.ColumnUuid(
'authorId',
this,
);
reportId = _is.ColumnUuid(
'reportId',
this,
);
keyId = _is.ColumnString(
'keyId',
this,
);
payload = _is.ColumnByteData(
'payload',
this,
);
nonce = _is.ColumnByteData(
'nonce',
this,
);
}
late final NoteRevisionUpdateTable updateTable;
/// The space owning this row. Maintained by the sync engine.
late final _is.ColumnInt spaceId;
late final _is.ColumnUuid noteId;
/// The note this is a version of; its revisions go with it.
_iu6jc5sx.NoteTable? _note;
/// The revisions this one replaces: none for a note's first, several when it
/// resolves a conflict. Plain ids, since revisions can arrive in any order.
late final _is.ColumnSerializable<List<_is.UuidValue>> parents;
/// Who wrote it: the AuthUser's id (a synced table can't relate to the
/// server-only AuthUser).
late final _is.ColumnUuid authorId;
/// The report this revision answers, once reports exist; null otherwise.
late final _is.ColumnUuid reportId;
/// The encryption key's id; always null (ADR 0002, rule 6).
late final _is.ColumnString keyId;
/// The content as UTF-8 JSON: {"schemaVersion", "title", "body"}.
late final _is.ColumnByteData payload;
/// The encryption nonce; always null, like keyId.
late final _is.ColumnByteData nonce;
_iu6jc5sx.NoteTable get note {
if (_note != null) return _note!;
_note = _is.createRelationTable(
relationFieldName: 'note',
field: NoteRevision.t.noteId,
foreignField: _iu6jc5sx.Note.t.id,
tableRelation: tableRelation,
createTable: (foreignTableRelation) =>
_iu6jc5sx.NoteTable(tableRelation: foreignTableRelation),
);
return _note!;
}
@override
List<_is.Column> get columns => [
id,
spaceId,
noteId,
parents,
authorId,
reportId,
keyId,
payload,
nonce,
];
@override
_is.Table? getRelationTable(String relationField) {
if (relationField == 'note') {
return note;
}
return null;
}
}
class NoteRevisionInclude extends _is.IncludeObject {
NoteRevisionInclude._({_iu6jc5sx.NoteInclude? note}) {
_note = note;
}
_iu6jc5sx.NoteInclude? _note;
@override
Map<String, _is.Include?> get includes => {'note': _note};
@override
_is.Table<_is.UuidValue?> get table => NoteRevision.t;
}
class NoteRevisionIncludeList extends _is.IncludeList {
NoteRevisionIncludeList._({
_is.WhereExpressionBuilder<NoteRevisionTable>? where,
super.limit,
super.offset,
super.orderBy,
super.orderByList,
super.include,
}) {
super.where = where?.call(NoteRevision.t);
}
@override
Map<String, _is.Include?> get includes => include?.includes ?? {};
@override
_is.Table<_is.UuidValue?> get table => NoteRevision.t;
}
class NoteRevisionRepository {
const NoteRevisionRepository._();
final attachRow = const NoteRevisionAttachRowRepository._();
/// Returns a list of [NoteRevision]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<NoteRevision>> find(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<NoteRevisionTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<NoteRevisionTable>? orderBy,
_is.OrderByListBuilder<NoteRevisionTable>? orderByList,
_is.Transaction? transaction,
NoteRevisionInclude? include,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.find<NoteRevision>(
where: where?.call(NoteRevision.t),
orderBy: orderBy?.call(NoteRevision.t),
orderByList: orderByList?.call(NoteRevision.t),
limit: limit,
offset: offset,
transaction: transaction,
include: include,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Returns the first matching [NoteRevision] 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<NoteRevision?> findFirstRow(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<NoteRevisionTable>? where,
int? offset,
_is.OrderByBuilder<NoteRevisionTable>? orderBy,
_is.OrderByListBuilder<NoteRevisionTable>? orderByList,
_is.Transaction? transaction,
NoteRevisionInclude? include,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findFirstRow<NoteRevision>(
where: where?.call(NoteRevision.t),
orderBy: orderBy?.call(NoteRevision.t),
orderByList: orderByList?.call(NoteRevision.t),
offset: offset,
transaction: transaction,
include: include,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Finds a single [NoteRevision] by its [id] or null if no such row exists.
Future<NoteRevision?> findById(
_is.DatabaseSession session,
_is.UuidValue id, {
_is.Transaction? transaction,
NoteRevisionInclude? include,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findById<NoteRevision>(
id,
transaction: transaction,
include: include,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Inserts all [NoteRevision]s in the list and returns the inserted rows.
///
/// The returned [NoteRevision]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<NoteRevision>> insert(
_is.DatabaseSession session,
List<NoteRevision> rows, {
_is.Transaction? transaction,
bool ignoreConflicts = false,
bool noReturn = false,
}) async {
return session.db.insert<NoteRevision>(
rows,
transaction: transaction,
ignoreConflicts: ignoreConflicts,
noReturn: noReturn,
);
}
/// Inserts a single [NoteRevision] and returns the inserted row.
///
/// The returned [NoteRevision] will have its `id` field set.
Future<NoteRevision> insertRow(
_is.DatabaseSession session,
NoteRevision row, {
_is.Transaction? transaction,
}) async {
return session.db.insertRow<NoteRevision>(
row,
transaction: transaction,
);
}
/// Upserts all [NoteRevision]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 [NoteRevision]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<NoteRevision>> upsert(
_is.DatabaseSession session,
List<NoteRevision> rows, {
required _is.ColumnSelections<NoteRevisionTable> conflictColumns,
_is.ColumnSelections<NoteRevisionTable>? updateColumns,
_is.WhereExpressionBuilder<NoteRevisionTable>? updateWhere,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.upsert<NoteRevision>(
rows,
conflictColumns: conflictColumns(NoteRevision.t),
updateColumns: updateColumns?.call(NoteRevision.t),
updateWhere: updateWhere?.call(NoteRevision.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Upserts a single [NoteRevision] 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 [NoteRevision] will have its `id` field set.
Future<NoteRevision?> upsertRow(
_is.DatabaseSession session,
NoteRevision row, {
required _is.ColumnSelections<NoteRevisionTable> conflictColumns,
_is.ColumnSelections<NoteRevisionTable>? updateColumns,
_is.WhereExpressionBuilder<NoteRevisionTable>? updateWhere,
_is.Transaction? transaction,
}) async {
return session.db.upsertRow<NoteRevision>(
row,
conflictColumns: conflictColumns(NoteRevision.t),
updateColumns: updateColumns?.call(NoteRevision.t),
updateWhere: updateWhere?.call(NoteRevision.t),
transaction: transaction,
);
}
/// Updates all [NoteRevision]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<NoteRevision>> update(
_is.DatabaseSession session,
List<NoteRevision> rows, {
_is.ColumnSelections<NoteRevisionTable>? columns,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.update<NoteRevision>(
rows,
columns: columns?.call(NoteRevision.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Updates a single [NoteRevision]. 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<NoteRevision> updateRow(
_is.DatabaseSession session,
NoteRevision row, {
_is.ColumnSelections<NoteRevisionTable>? columns,
_is.Transaction? transaction,
}) async {
return session.db.updateRow<NoteRevision>(
row,
columns: columns?.call(NoteRevision.t),
transaction: transaction,
);
}
/// Updates a single [NoteRevision] by its [id] with the specified [columnValues].
/// Returns the updated row or null if no row with the given id exists.
Future<NoteRevision?> updateById(
_is.DatabaseSession session,
_is.UuidValue id, {
required _is.ColumnValueListBuilder<NoteRevisionUpdateTable> columnValues,
_is.Transaction? transaction,
}) async {
return session.db.updateById<NoteRevision>(
id,
columnValues: columnValues(NoteRevision.t.updateTable),
transaction: transaction,
);
}
/// Updates all [NoteRevision]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<NoteRevision>> updateWhere(
_is.DatabaseSession session, {
required _is.ColumnValueListBuilder<NoteRevisionUpdateTable> columnValues,
required _is.WhereExpressionBuilder<NoteRevisionTable> where,
int? limit,
int? offset,
_is.OrderByBuilder<NoteRevisionTable>? orderBy,
_is.OrderByListBuilder<NoteRevisionTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.updateWhere<NoteRevision>(
columnValues: columnValues(NoteRevision.t.updateTable),
where: where(NoteRevision.t),
limit: limit,
offset: offset,
orderBy: orderBy?.call(NoteRevision.t),
orderByList: orderByList?.call(NoteRevision.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes all [NoteRevision]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<NoteRevision>> delete(
_is.DatabaseSession session,
List<NoteRevision> rows, {
_is.OrderByBuilder<NoteRevisionTable>? orderBy,
_is.OrderByListBuilder<NoteRevisionTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.delete<NoteRevision>(
rows,
orderBy: orderBy?.call(NoteRevision.t),
orderByList: orderByList?.call(NoteRevision.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes a single [NoteRevision].
Future<NoteRevision> deleteRow(
_is.DatabaseSession session,
NoteRevision row, {
_is.Transaction? transaction,
}) async {
return session.db.deleteRow<NoteRevision>(
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<NoteRevision>> deleteWhere(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<NoteRevisionTable> where,
_is.OrderByBuilder<NoteRevisionTable>? orderBy,
_is.OrderByListBuilder<NoteRevisionTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.deleteWhere<NoteRevision>(
where: where(NoteRevision.t),
orderBy: orderBy?.call(NoteRevision.t),
orderByList: orderByList?.call(NoteRevision.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<NoteRevisionTable>? where,
int? limit,
_is.Transaction? transaction,
}) async {
return session.db.count<NoteRevision>(
where: where?.call(NoteRevision.t),
limit: limit,
transaction: transaction,
);
}
/// Acquires row-level locks on [NoteRevision] rows matching the [where] expression.
Future<void> lockRows(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<NoteRevisionTable> where,
required _is.LockMode lockMode,
required _is.Transaction transaction,
_is.LockBehavior lockBehavior = _is.LockBehavior.wait,
}) async {
return session.db.lockRows<NoteRevision>(
where: where(NoteRevision.t),
lockMode: lockMode,
lockBehavior: lockBehavior,
transaction: transaction,
);
}
}
class NoteRevisionAttachRowRepository {
const NoteRevisionAttachRowRepository._();
/// Creates a relation between the given [NoteRevision] and [Note]
/// by setting the [NoteRevision]'s foreign key `noteId` to refer to the [Note].
Future<void> note(
_is.DatabaseSession session,
NoteRevision noteRevision,
_iu6jc5sx.Note note, {
_is.Transaction? transaction,
}) async {
if (noteRevision.id == null) {
throw ArgumentError.notNull('noteRevision.id');
}
if (note.id == null) {
throw ArgumentError.notNull('note.id');
}
var $noteRevision = noteRevision.copyWith(noteId: note.id);
await session.db.updateRow<NoteRevision>(
$noteRevision,
columns: [NoteRevision.t.noteId],
transaction: transaction,
);
}
}

View file

@ -32,10 +32,13 @@ import 'card/card.dart' as _ipiw1cmx;
import 'card/card_revision.dart' as _ipbyuef1;
import 'common/not_found_exception.dart' as _i8oee73b;
import 'instance/instance_info.dart' as _iskrg2g3;
import 'note/note.dart' as _i9njjhyv;
import 'note/note_revision.dart' as _i6j5dng4;
import 'review/review_log.dart' as _irzjbdqz;
import 'settings/person_settings.dart' as _irnhx5e5;
import 'studyset/studyset.dart' as _ikbclmae;
import 'tag/card_tag.dart' as _i6jz6nyq;
import 'tag/note_tag.dart' as _itkfn57x;
import 'tag/tag.dart' as _ie8t3ar4;
export 'asset/asset.dart';
export 'asset/file_allowance.dart';
@ -48,10 +51,13 @@ export 'card/card.dart';
export 'card/card_revision.dart';
export 'common/not_found_exception.dart';
export 'instance/instance_info.dart';
export 'note/note.dart';
export 'note/note_revision.dart';
export 'review/review_log.dart';
export 'settings/person_settings.dart';
export 'studyset/studyset.dart';
export 'tag/card_tag.dart';
export 'tag/note_tag.dart';
export 'tag/tag.dart';
export 'sync_tables.dart';
@ -528,6 +534,319 @@ class Protocol extends _is.DatabaseSerializationManager {
],
managed: true,
),
_isp.TableDefinition(
name: 'note',
dartName: 'Note',
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: 'studysetId',
columnType: _isp.ColumnType.uuid,
isNullable: false,
dartType: 'UuidValue',
),
_isp.ColumnDefinition(
name: 'position',
columnType: _isp.ColumnType.text,
isNullable: false,
dartType: 'String',
),
],
foreignKeys: [
_isp.ForeignKeyDefinition(
constraintName: 'note_fk_0',
columns: ['spaceId'],
referenceTable: 'offline_sync_spaces',
referenceTableSchema: 'public',
referenceColumns: ['id'],
onUpdate: _isp.ForeignKeyAction.noAction,
onDelete: _isp.ForeignKeyAction.cascade,
matchType: null,
),
_isp.ForeignKeyDefinition(
constraintName: 'note_fk_1',
columns: ['studysetId'],
referenceTable: 'studyset',
referenceTableSchema: 'public',
referenceColumns: ['id'],
onUpdate: _isp.ForeignKeyAction.noAction,
onDelete: _isp.ForeignKeyAction.cascade,
matchType: null,
deferrable: _isp.DeferrableConstraint.initiallyDeferred,
),
],
indexes: [
_isp.IndexDefinition(
indexName: 'note_space_idx',
tableSpace: null,
elements: [
_isp.IndexElementDefinition(
type: _isp.IndexElementDefinitionType.column,
definition: 'spaceId',
),
],
type: 'btree',
isUnique: false,
isPrimary: false,
),
_isp.IndexDefinition(
indexName: 'note_studyset_idx',
tableSpace: null,
elements: [
_isp.IndexElementDefinition(
type: _isp.IndexElementDefinitionType.column,
definition: 'studysetId',
),
_isp.IndexElementDefinition(
type: _isp.IndexElementDefinitionType.column,
definition: 'position',
),
],
type: 'btree',
isUnique: false,
isPrimary: false,
),
],
managed: true,
),
_isp.TableDefinition(
name: 'note_revision',
dartName: 'NoteRevision',
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: 'noteId',
columnType: _isp.ColumnType.uuid,
isNullable: false,
dartType: 'UuidValue',
),
_isp.ColumnDefinition(
name: 'parents',
columnType: _isp.ColumnType.json,
isNullable: false,
dartType: 'List<UuidValue>',
),
_isp.ColumnDefinition(
name: 'authorId',
columnType: _isp.ColumnType.uuid,
isNullable: false,
dartType: 'UuidValue',
),
_isp.ColumnDefinition(
name: 'reportId',
columnType: _isp.ColumnType.uuid,
isNullable: true,
dartType: 'UuidValue?',
),
_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: 'note_revision_fk_0',
columns: ['spaceId'],
referenceTable: 'offline_sync_spaces',
referenceTableSchema: 'public',
referenceColumns: ['id'],
onUpdate: _isp.ForeignKeyAction.noAction,
onDelete: _isp.ForeignKeyAction.cascade,
matchType: null,
),
_isp.ForeignKeyDefinition(
constraintName: 'note_revision_fk_1',
columns: ['noteId'],
referenceTable: 'note',
referenceTableSchema: 'public',
referenceColumns: ['id'],
onUpdate: _isp.ForeignKeyAction.noAction,
onDelete: _isp.ForeignKeyAction.cascade,
matchType: null,
deferrable: _isp.DeferrableConstraint.initiallyDeferred,
),
],
indexes: [
_isp.IndexDefinition(
indexName: 'note_revision_space_idx',
tableSpace: null,
elements: [
_isp.IndexElementDefinition(
type: _isp.IndexElementDefinitionType.column,
definition: 'spaceId',
),
],
type: 'btree',
isUnique: false,
isPrimary: false,
),
_isp.IndexDefinition(
indexName: 'note_revision_note_idx',
tableSpace: null,
elements: [
_isp.IndexElementDefinition(
type: _isp.IndexElementDefinitionType.column,
definition: 'noteId',
),
],
type: 'btree',
isUnique: false,
isPrimary: false,
),
],
managed: true,
),
_isp.TableDefinition(
name: 'note_tag',
dartName: 'NoteTag',
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: 'noteId',
columnType: _isp.ColumnType.uuid,
isNullable: false,
dartType: 'UuidValue',
),
_isp.ColumnDefinition(
name: 'tagId',
columnType: _isp.ColumnType.uuid,
isNullable: false,
dartType: 'UuidValue',
),
],
foreignKeys: [
_isp.ForeignKeyDefinition(
constraintName: 'note_tag_fk_0',
columns: ['spaceId'],
referenceTable: 'offline_sync_spaces',
referenceTableSchema: 'public',
referenceColumns: ['id'],
onUpdate: _isp.ForeignKeyAction.noAction,
onDelete: _isp.ForeignKeyAction.cascade,
matchType: null,
),
_isp.ForeignKeyDefinition(
constraintName: 'note_tag_fk_1',
columns: ['noteId'],
referenceTable: 'note',
referenceTableSchema: 'public',
referenceColumns: ['id'],
onUpdate: _isp.ForeignKeyAction.noAction,
onDelete: _isp.ForeignKeyAction.cascade,
matchType: null,
deferrable: _isp.DeferrableConstraint.initiallyDeferred,
),
_isp.ForeignKeyDefinition(
constraintName: 'note_tag_fk_2',
columns: ['tagId'],
referenceTable: 'tag',
referenceTableSchema: 'public',
referenceColumns: ['id'],
onUpdate: _isp.ForeignKeyAction.noAction,
onDelete: _isp.ForeignKeyAction.cascade,
matchType: null,
deferrable: _isp.DeferrableConstraint.initiallyDeferred,
),
],
indexes: [
_isp.IndexDefinition(
indexName: 'note_tag_space_idx',
tableSpace: null,
elements: [
_isp.IndexElementDefinition(
type: _isp.IndexElementDefinitionType.column,
definition: 'spaceId',
),
],
type: 'btree',
isUnique: false,
isPrimary: false,
),
_isp.IndexDefinition(
indexName: 'note_tag_note_idx',
tableSpace: null,
elements: [
_isp.IndexElementDefinition(
type: _isp.IndexElementDefinitionType.column,
definition: 'noteId',
),
],
type: 'btree',
isUnique: false,
isPrimary: false,
),
_isp.IndexDefinition(
indexName: 'note_tag_tag_idx',
tableSpace: null,
elements: [
_isp.IndexElementDefinition(
type: _isp.IndexElementDefinitionType.column,
definition: 'tagId',
),
],
type: 'btree',
isUnique: false,
isPrimary: false,
),
],
managed: true,
),
_isp.TableDefinition(
name: 'person_settings',
dartName: 'PersonSettings',
@ -962,6 +1281,12 @@ class Protocol extends _is.DatabaseSerializationManager {
if (t == _iskrg2g3.InstanceInfo) {
return _iskrg2g3.InstanceInfo.fromJson(data) as T;
}
if (t == _i9njjhyv.Note) {
return _i9njjhyv.Note.fromJson(data) as T;
}
if (t == _i6j5dng4.NoteRevision) {
return _i6j5dng4.NoteRevision.fromJson(data) as T;
}
if (t == _irzjbdqz.ReviewLog) {
return _irzjbdqz.ReviewLog.fromJson(data) as T;
}
@ -974,6 +1299,9 @@ class Protocol extends _is.DatabaseSerializationManager {
if (t == _i6jz6nyq.CardTag) {
return _i6jz6nyq.CardTag.fromJson(data) as T;
}
if (t == _itkfn57x.NoteTag) {
return _itkfn57x.NoteTag.fromJson(data) as T;
}
if (t == _ie8t3ar4.Tag) {
return _ie8t3ar4.Tag.fromJson(data) as T;
}
@ -1021,6 +1349,12 @@ class Protocol extends _is.DatabaseSerializationManager {
if (t == _is.getType<_iskrg2g3.InstanceInfo?>()) {
return (data != null ? _iskrg2g3.InstanceInfo.fromJson(data) : null) as T;
}
if (t == _is.getType<_i9njjhyv.Note?>()) {
return (data != null ? _i9njjhyv.Note.fromJson(data) : null) as T;
}
if (t == _is.getType<_i6j5dng4.NoteRevision?>()) {
return (data != null ? _i6j5dng4.NoteRevision.fromJson(data) : null) as T;
}
if (t == _is.getType<_irzjbdqz.ReviewLog?>()) {
return (data != null ? _irzjbdqz.ReviewLog.fromJson(data) : null) as T;
}
@ -1034,6 +1368,9 @@ class Protocol extends _is.DatabaseSerializationManager {
if (t == _is.getType<_i6jz6nyq.CardTag?>()) {
return (data != null ? _i6jz6nyq.CardTag.fromJson(data) : null) as T;
}
if (t == _is.getType<_itkfn57x.NoteTag?>()) {
return (data != null ? _itkfn57x.NoteTag.fromJson(data) : null) as T;
}
if (t == _is.getType<_ie8t3ar4.Tag?>()) {
return (data != null ? _ie8t3ar4.Tag.fromJson(data) : null) as T;
}
@ -1075,10 +1412,13 @@ class Protocol extends _is.DatabaseSerializationManager {
_ipbyuef1.CardRevision => 'CardRevision',
_i8oee73b.NotFoundException => 'NotFoundException',
_iskrg2g3.InstanceInfo => 'InstanceInfo',
_i9njjhyv.Note => 'Note',
_i6j5dng4.NoteRevision => 'NoteRevision',
_irzjbdqz.ReviewLog => 'ReviewLog',
_irnhx5e5.PersonSettings => 'PersonSettings',
_ikbclmae.Studyset => 'Studyset',
_i6jz6nyq.CardTag => 'CardTag',
_itkfn57x.NoteTag => 'NoteTag',
_ie8t3ar4.Tag => 'Tag',
_ => null,
};
@ -1119,6 +1459,10 @@ class Protocol extends _is.DatabaseSerializationManager {
return 'NotFoundException';
case _iskrg2g3.InstanceInfo():
return 'InstanceInfo';
case _i9njjhyv.Note():
return 'Note';
case _i6j5dng4.NoteRevision():
return 'NoteRevision';
case _irzjbdqz.ReviewLog():
return 'ReviewLog';
case _irnhx5e5.PersonSettings():
@ -1127,6 +1471,8 @@ class Protocol extends _is.DatabaseSerializationManager {
return 'Studyset';
case _i6jz6nyq.CardTag():
return 'CardTag';
case _itkfn57x.NoteTag():
return 'NoteTag';
case _ie8t3ar4.Tag():
return 'Tag';
}
@ -1194,6 +1540,12 @@ class Protocol extends _is.DatabaseSerializationManager {
if (dataClassName == 'InstanceInfo') {
return deserialize<_iskrg2g3.InstanceInfo>(data['data']);
}
if (dataClassName == 'Note') {
return deserialize<_i9njjhyv.Note>(data['data']);
}
if (dataClassName == 'NoteRevision') {
return deserialize<_i6j5dng4.NoteRevision>(data['data']);
}
if (dataClassName == 'ReviewLog') {
return deserialize<_irzjbdqz.ReviewLog>(data['data']);
}
@ -1206,6 +1558,9 @@ class Protocol extends _is.DatabaseSerializationManager {
if (dataClassName == 'CardTag') {
return deserialize<_i6jz6nyq.CardTag>(data['data']);
}
if (dataClassName == 'NoteTag') {
return deserialize<_itkfn57x.NoteTag>(data['data']);
}
if (dataClassName == 'Tag') {
return deserialize<_ie8t3ar4.Tag>(data['data']);
}
@ -1269,6 +1624,10 @@ class Protocol extends _is.DatabaseSerializationManager {
return _ipiw1cmx.Card.t;
case _ipbyuef1.CardRevision:
return _ipbyuef1.CardRevision.t;
case _i9njjhyv.Note:
return _i9njjhyv.Note.t;
case _i6j5dng4.NoteRevision:
return _i6j5dng4.NoteRevision.t;
case _irzjbdqz.ReviewLog:
return _irzjbdqz.ReviewLog.t;
case _irnhx5e5.PersonSettings:
@ -1277,6 +1636,8 @@ class Protocol extends _is.DatabaseSerializationManager {
return _ikbclmae.Studyset.t;
case _i6jz6nyq.CardTag:
return _i6jz6nyq.CardTag.t;
case _itkfn57x.NoteTag:
return _itkfn57x.NoteTag.t;
case _ie8t3ar4.Tag:
return _ie8t3ar4.Tag.t;
}

View file

@ -14,10 +14,13 @@ import 'package:serverpod/serverpod.dart' as _is;
import 'asset/asset.dart' as _ipb7vkxr;
import 'card/card.dart' as _ipiw1cmx;
import 'card/card_revision.dart' as _ipbyuef1;
import 'note/note.dart' as _i9njjhyv;
import 'note/note_revision.dart' as _i6j5dng4;
import 'review/review_log.dart' as _irzjbdqz;
import 'settings/person_settings.dart' as _irnhx5e5;
import 'studyset/studyset.dart' as _ikbclmae;
import 'tag/card_tag.dart' as _i6jz6nyq;
import 'tag/note_tag.dart' as _itkfn57x;
import 'tag/tag.dart' as _ie8t3ar4;
/// The tables synchronized between client and server by the
@ -28,6 +31,9 @@ final List<_is.Table> syncTables = [
_ipiw1cmx.Card.t,
_ipbyuef1.CardRevision.t,
_i6jz6nyq.CardTag.t,
_i9njjhyv.Note.t,
_i6j5dng4.NoteRevision.t,
_itkfn57x.NoteTag.t,
_irnhx5e5.PersonSettings.t,
_irzjbdqz.ReviewLog.t,
_ikbclmae.Studyset.t,

View file

@ -0,0 +1,792 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: dead_code, unnecessary_null_comparison
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:halkyon_learn_server/src/generated/protocol.dart' as _i5lsenq5;
import 'package:serverpod/serverpod.dart' as _is;
import '../note/note.dart' as _iu6jc5sx;
import '../tag/tag.dart' as _isqed5gg;
/// A note having a tag, as CardTag is for cards (ADR 0004): one row per
/// assignment, never updated, so concurrent edits merge without losing either;
/// a note with the same tag twice counts once. Notes and cards share the
/// person's tags. Synced; goes with its note and its tag.
abstract class NoteTag
implements _is.TableRow<_is.UuidValue?>, _is.ProtocolSerialization {
NoteTag._({
this.id,
this.spaceId,
required this.noteId,
this.note,
required this.tagId,
this.tag,
});
factory NoteTag({
_is.UuidValue? id,
int? spaceId,
required _is.UuidValue noteId,
_iu6jc5sx.Note? note,
required _is.UuidValue tagId,
_isqed5gg.Tag? tag,
}) = _NoteTagImpl;
factory NoteTag.fromJson(Map<String, dynamic> jsonSerialization) {
return NoteTag(
id: jsonSerialization['id'] == null
? null
: _is.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
spaceId: jsonSerialization['spaceId'] as int?,
noteId: _is.UuidValueJsonExtension.fromJson(jsonSerialization['noteId']),
note: jsonSerialization['note'] == null
? null
: _i5lsenq5.Protocol().deserialize<_iu6jc5sx.Note>(
jsonSerialization['note'],
),
tagId: _is.UuidValueJsonExtension.fromJson(jsonSerialization['tagId']),
tag: jsonSerialization['tag'] == null
? null
: _i5lsenq5.Protocol().deserialize<_isqed5gg.Tag>(
jsonSerialization['tag'],
),
);
}
static final t = NoteTagTable();
static const db = NoteTagRepository._();
@override
_is.UuidValue? id;
/// The space owning this row. Maintained by the sync engine.
int? spaceId;
_is.UuidValue noteId;
/// The note tagged.
_iu6jc5sx.Note? note;
_is.UuidValue tagId;
/// The tag it has.
_isqed5gg.Tag? tag;
@override
_is.Table<_is.UuidValue?> get table => t;
/// Returns a shallow copy of this [NoteTag]
/// with some or all fields replaced by the given arguments.
@_is.useResult
NoteTag copyWith({
_is.UuidValue? id,
int? spaceId,
_is.UuidValue? noteId,
_iu6jc5sx.Note? note,
_is.UuidValue? tagId,
_isqed5gg.Tag? tag,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'NoteTag',
if (id != null) 'id': id?.toJson(),
if (spaceId != null) 'spaceId': spaceId,
'noteId': noteId.toJson(),
if (note != null) 'note': note?.toJson(),
'tagId': tagId.toJson(),
if (tag != null) 'tag': tag?.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'NoteTag',
if (id != null) 'id': id?.toJson(),
if (spaceId != null) 'spaceId': spaceId,
'noteId': noteId.toJson(),
if (note != null) 'note': note?.toJsonForProtocol(),
'tagId': tagId.toJson(),
if (tag != null) 'tag': tag?.toJsonForProtocol(),
};
}
static NoteTagInclude include({
_iu6jc5sx.NoteInclude? note,
_isqed5gg.TagInclude? tag,
}) {
return NoteTagInclude._(
note: note,
tag: tag,
);
}
static NoteTagIncludeList includeList({
_is.WhereExpressionBuilder<NoteTagTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<NoteTagTable>? orderBy,
_is.OrderByListBuilder<NoteTagTable>? orderByList,
NoteTagInclude? include,
}) {
return NoteTagIncludeList._(
where: where,
limit: limit,
offset: offset,
orderBy: orderBy?.call(NoteTag.t),
orderByList: orderByList?.call(NoteTag.t),
include: include,
);
}
@override
String toString() {
return _is.SerializationManager.encode(this);
}
}
class _Undefined {}
class _NoteTagImpl extends NoteTag {
_NoteTagImpl({
_is.UuidValue? id,
int? spaceId,
required _is.UuidValue noteId,
_iu6jc5sx.Note? note,
required _is.UuidValue tagId,
_isqed5gg.Tag? tag,
}) : super._(
id: id,
spaceId: spaceId,
noteId: noteId,
note: note,
tagId: tagId,
tag: tag,
);
/// Returns a shallow copy of this [NoteTag]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@override
NoteTag copyWith({
Object? id = _Undefined,
Object? spaceId = _Undefined,
_is.UuidValue? noteId,
Object? note = _Undefined,
_is.UuidValue? tagId,
Object? tag = _Undefined,
}) {
return NoteTag(
id: id is _is.UuidValue? ? id : this.id,
spaceId: spaceId is int? ? spaceId : this.spaceId,
noteId: noteId ?? this.noteId,
note: note is _iu6jc5sx.Note? ? note : this.note?.copyWith(),
tagId: tagId ?? this.tagId,
tag: tag is _isqed5gg.Tag? ? tag : this.tag?.copyWith(),
);
}
}
class NoteTagUpdateTable extends _is.UpdateTable<NoteTagTable> {
NoteTagUpdateTable(super.table);
_is.ColumnValue<int, int> spaceId(int? value) => _is.ColumnValue(
table.spaceId,
value,
);
_is.ColumnValue<_is.UuidValue, _is.UuidValue> noteId(_is.UuidValue value) =>
_is.ColumnValue(
table.noteId,
value,
);
_is.ColumnValue<_is.UuidValue, _is.UuidValue> tagId(_is.UuidValue value) =>
_is.ColumnValue(
table.tagId,
value,
);
}
class NoteTagTable extends _is.Table<_is.UuidValue?> {
NoteTagTable({super.tableRelation}) : super(tableName: 'note_tag') {
updateTable = NoteTagUpdateTable(this);
spaceId = _is.ColumnInt(
'spaceId',
this,
);
noteId = _is.ColumnUuid(
'noteId',
this,
);
tagId = _is.ColumnUuid(
'tagId',
this,
);
}
late final NoteTagUpdateTable updateTable;
/// The space owning this row. Maintained by the sync engine.
late final _is.ColumnInt spaceId;
late final _is.ColumnUuid noteId;
/// The note tagged.
_iu6jc5sx.NoteTable? _note;
late final _is.ColumnUuid tagId;
/// The tag it has.
_isqed5gg.TagTable? _tag;
_iu6jc5sx.NoteTable get note {
if (_note != null) return _note!;
_note = _is.createRelationTable(
relationFieldName: 'note',
field: NoteTag.t.noteId,
foreignField: _iu6jc5sx.Note.t.id,
tableRelation: tableRelation,
createTable: (foreignTableRelation) =>
_iu6jc5sx.NoteTable(tableRelation: foreignTableRelation),
);
return _note!;
}
_isqed5gg.TagTable get tag {
if (_tag != null) return _tag!;
_tag = _is.createRelationTable(
relationFieldName: 'tag',
field: NoteTag.t.tagId,
foreignField: _isqed5gg.Tag.t.id,
tableRelation: tableRelation,
createTable: (foreignTableRelation) =>
_isqed5gg.TagTable(tableRelation: foreignTableRelation),
);
return _tag!;
}
@override
List<_is.Column> get columns => [
id,
spaceId,
noteId,
tagId,
];
@override
_is.Table? getRelationTable(String relationField) {
if (relationField == 'note') {
return note;
}
if (relationField == 'tag') {
return tag;
}
return null;
}
}
class NoteTagInclude extends _is.IncludeObject {
NoteTagInclude._({
_iu6jc5sx.NoteInclude? note,
_isqed5gg.TagInclude? tag,
}) {
_note = note;
_tag = tag;
}
_iu6jc5sx.NoteInclude? _note;
_isqed5gg.TagInclude? _tag;
@override
Map<String, _is.Include?> get includes => {
'note': _note,
'tag': _tag,
};
@override
_is.Table<_is.UuidValue?> get table => NoteTag.t;
}
class NoteTagIncludeList extends _is.IncludeList {
NoteTagIncludeList._({
_is.WhereExpressionBuilder<NoteTagTable>? where,
super.limit,
super.offset,
super.orderBy,
super.orderByList,
super.include,
}) {
super.where = where?.call(NoteTag.t);
}
@override
Map<String, _is.Include?> get includes => include?.includes ?? {};
@override
_is.Table<_is.UuidValue?> get table => NoteTag.t;
}
class NoteTagRepository {
const NoteTagRepository._();
final attachRow = const NoteTagAttachRowRepository._();
/// Returns a list of [NoteTag]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<NoteTag>> find(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<NoteTagTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<NoteTagTable>? orderBy,
_is.OrderByListBuilder<NoteTagTable>? orderByList,
_is.Transaction? transaction,
NoteTagInclude? include,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.find<NoteTag>(
where: where?.call(NoteTag.t),
orderBy: orderBy?.call(NoteTag.t),
orderByList: orderByList?.call(NoteTag.t),
limit: limit,
offset: offset,
transaction: transaction,
include: include,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Returns the first matching [NoteTag] 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<NoteTag?> findFirstRow(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<NoteTagTable>? where,
int? offset,
_is.OrderByBuilder<NoteTagTable>? orderBy,
_is.OrderByListBuilder<NoteTagTable>? orderByList,
_is.Transaction? transaction,
NoteTagInclude? include,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findFirstRow<NoteTag>(
where: where?.call(NoteTag.t),
orderBy: orderBy?.call(NoteTag.t),
orderByList: orderByList?.call(NoteTag.t),
offset: offset,
transaction: transaction,
include: include,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Finds a single [NoteTag] by its [id] or null if no such row exists.
Future<NoteTag?> findById(
_is.DatabaseSession session,
_is.UuidValue id, {
_is.Transaction? transaction,
NoteTagInclude? include,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findById<NoteTag>(
id,
transaction: transaction,
include: include,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Inserts all [NoteTag]s in the list and returns the inserted rows.
///
/// The returned [NoteTag]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<NoteTag>> insert(
_is.DatabaseSession session,
List<NoteTag> rows, {
_is.Transaction? transaction,
bool ignoreConflicts = false,
bool noReturn = false,
}) async {
return session.db.insert<NoteTag>(
rows,
transaction: transaction,
ignoreConflicts: ignoreConflicts,
noReturn: noReturn,
);
}
/// Inserts a single [NoteTag] and returns the inserted row.
///
/// The returned [NoteTag] will have its `id` field set.
Future<NoteTag> insertRow(
_is.DatabaseSession session,
NoteTag row, {
_is.Transaction? transaction,
}) async {
return session.db.insertRow<NoteTag>(
row,
transaction: transaction,
);
}
/// Upserts all [NoteTag]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 [NoteTag]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<NoteTag>> upsert(
_is.DatabaseSession session,
List<NoteTag> rows, {
required _is.ColumnSelections<NoteTagTable> conflictColumns,
_is.ColumnSelections<NoteTagTable>? updateColumns,
_is.WhereExpressionBuilder<NoteTagTable>? updateWhere,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.upsert<NoteTag>(
rows,
conflictColumns: conflictColumns(NoteTag.t),
updateColumns: updateColumns?.call(NoteTag.t),
updateWhere: updateWhere?.call(NoteTag.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Upserts a single [NoteTag] 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 [NoteTag] will have its `id` field set.
Future<NoteTag?> upsertRow(
_is.DatabaseSession session,
NoteTag row, {
required _is.ColumnSelections<NoteTagTable> conflictColumns,
_is.ColumnSelections<NoteTagTable>? updateColumns,
_is.WhereExpressionBuilder<NoteTagTable>? updateWhere,
_is.Transaction? transaction,
}) async {
return session.db.upsertRow<NoteTag>(
row,
conflictColumns: conflictColumns(NoteTag.t),
updateColumns: updateColumns?.call(NoteTag.t),
updateWhere: updateWhere?.call(NoteTag.t),
transaction: transaction,
);
}
/// Updates all [NoteTag]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<NoteTag>> update(
_is.DatabaseSession session,
List<NoteTag> rows, {
_is.ColumnSelections<NoteTagTable>? columns,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.update<NoteTag>(
rows,
columns: columns?.call(NoteTag.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Updates a single [NoteTag]. 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<NoteTag> updateRow(
_is.DatabaseSession session,
NoteTag row, {
_is.ColumnSelections<NoteTagTable>? columns,
_is.Transaction? transaction,
}) async {
return session.db.updateRow<NoteTag>(
row,
columns: columns?.call(NoteTag.t),
transaction: transaction,
);
}
/// Updates a single [NoteTag] by its [id] with the specified [columnValues].
/// Returns the updated row or null if no row with the given id exists.
Future<NoteTag?> updateById(
_is.DatabaseSession session,
_is.UuidValue id, {
required _is.ColumnValueListBuilder<NoteTagUpdateTable> columnValues,
_is.Transaction? transaction,
}) async {
return session.db.updateById<NoteTag>(
id,
columnValues: columnValues(NoteTag.t.updateTable),
transaction: transaction,
);
}
/// Updates all [NoteTag]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<NoteTag>> updateWhere(
_is.DatabaseSession session, {
required _is.ColumnValueListBuilder<NoteTagUpdateTable> columnValues,
required _is.WhereExpressionBuilder<NoteTagTable> where,
int? limit,
int? offset,
_is.OrderByBuilder<NoteTagTable>? orderBy,
_is.OrderByListBuilder<NoteTagTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.updateWhere<NoteTag>(
columnValues: columnValues(NoteTag.t.updateTable),
where: where(NoteTag.t),
limit: limit,
offset: offset,
orderBy: orderBy?.call(NoteTag.t),
orderByList: orderByList?.call(NoteTag.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes all [NoteTag]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<NoteTag>> delete(
_is.DatabaseSession session,
List<NoteTag> rows, {
_is.OrderByBuilder<NoteTagTable>? orderBy,
_is.OrderByListBuilder<NoteTagTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.delete<NoteTag>(
rows,
orderBy: orderBy?.call(NoteTag.t),
orderByList: orderByList?.call(NoteTag.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes a single [NoteTag].
Future<NoteTag> deleteRow(
_is.DatabaseSession session,
NoteTag row, {
_is.Transaction? transaction,
}) async {
return session.db.deleteRow<NoteTag>(
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<NoteTag>> deleteWhere(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<NoteTagTable> where,
_is.OrderByBuilder<NoteTagTable>? orderBy,
_is.OrderByListBuilder<NoteTagTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.deleteWhere<NoteTag>(
where: where(NoteTag.t),
orderBy: orderBy?.call(NoteTag.t),
orderByList: orderByList?.call(NoteTag.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<NoteTagTable>? where,
int? limit,
_is.Transaction? transaction,
}) async {
return session.db.count<NoteTag>(
where: where?.call(NoteTag.t),
limit: limit,
transaction: transaction,
);
}
/// Acquires row-level locks on [NoteTag] rows matching the [where] expression.
Future<void> lockRows(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<NoteTagTable> where,
required _is.LockMode lockMode,
required _is.Transaction transaction,
_is.LockBehavior lockBehavior = _is.LockBehavior.wait,
}) async {
return session.db.lockRows<NoteTag>(
where: where(NoteTag.t),
lockMode: lockMode,
lockBehavior: lockBehavior,
transaction: transaction,
);
}
}
class NoteTagAttachRowRepository {
const NoteTagAttachRowRepository._();
/// Creates a relation between the given [NoteTag] and [Note]
/// by setting the [NoteTag]'s foreign key `noteId` to refer to the [Note].
Future<void> note(
_is.DatabaseSession session,
NoteTag noteTag,
_iu6jc5sx.Note note, {
_is.Transaction? transaction,
}) async {
if (noteTag.id == null) {
throw ArgumentError.notNull('noteTag.id');
}
if (note.id == null) {
throw ArgumentError.notNull('note.id');
}
var $noteTag = noteTag.copyWith(noteId: note.id);
await session.db.updateRow<NoteTag>(
$noteTag,
columns: [NoteTag.t.noteId],
transaction: transaction,
);
}
/// Creates a relation between the given [NoteTag] and [Tag]
/// by setting the [NoteTag]'s foreign key `tagId` to refer to the [Tag].
Future<void> tag(
_is.DatabaseSession session,
NoteTag noteTag,
_isqed5gg.Tag tag, {
_is.Transaction? transaction,
}) async {
if (noteTag.id == null) {
throw ArgumentError.notNull('noteTag.id');
}
if (tag.id == null) {
throw ArgumentError.notNull('tag.id');
}
var $noteTag = noteTag.copyWith(tagId: tag.id);
await session.db.updateRow<NoteTag>(
$noteTag,
columns: [NoteTag.t.tagId],
transaction: transaction,
);
}
}

View file

@ -0,0 +1,19 @@
### A note in a studyset: rich text the person writes and reads, never studied.
### Its content lives in its revisions (NoteRevision), never in this row: content
### is append-only (ADR 0001, rule 2), as a card's is. Synced; its space owns it.
class: Note
table: note
database: sync
fields:
### The studyset the note belongs to; the note goes with it.
studyset: Studyset?, relation(onDelete=Cascade, deferred)
### Where the note stands among its studyset's notes: a fractional index, as a
### card's position; last writer wins.
position: String
indexes:
# Deleting a space finds its rows through this index (see studyset.spy.yaml).
note_space_idx:
fields: spaceId
# A studyset's notes, in order.
note_studyset_idx:
fields: studysetId, position

View file

@ -0,0 +1,32 @@
### One version of a note's content, never changed once written (ADR 0001,
### rule 2), merged as a card's revisions are (ADR 0003): editing appends a
### revision whose parents are the versions the edit started from, and a note's
### current versions are the revisions no other replaces. Deleting a note blanks
### every revision's payload first (ADR 0002, rule 2).
class: NoteRevision
table: note_revision
database: sync
fields:
### The note this is a version of; its revisions go with it.
note: Note?, relation(onDelete=Cascade, deferred)
### The revisions this one replaces: none for a note's first, several when it
### resolves a conflict. Plain ids, since revisions can arrive in any order.
parents: List<UuidValue>
### Who wrote it: the AuthUser's id (a synced table can't relate to the
### server-only AuthUser).
authorId: UuidValue
### The report this revision answers, once reports exist; null otherwise.
reportId: UuidValue?
### The encryption key's id; always null (ADR 0002, rule 6).
keyId: String?
### The content as UTF-8 JSON: {"schemaVersion", "title", "body"}.
payload: ByteData
### The encryption nonce; always null, like keyId.
nonce: ByteData?
indexes:
# Deleting a space finds its rows through this index (see studyset.spy.yaml).
note_revision_space_idx:
fields: spaceId
# A note's revisions.
note_revision_note_idx:
fields: noteId

View file

@ -0,0 +1,22 @@
### A note having a tag, as CardTag is for cards (ADR 0004): one row per
### assignment, never updated, so concurrent edits merge without losing either;
### a note with the same tag twice counts once. Notes and cards share the
### person's tags. Synced; goes with its note and its tag.
class: NoteTag
table: note_tag
database: sync
fields:
### The note tagged.
note: Note?, relation(onDelete=Cascade, deferred)
### The tag it has.
tag: Tag?, relation(onDelete=Cascade, deferred)
indexes:
# Deleting a space finds its rows through this index (see studyset.spy.yaml).
note_tag_space_idx:
fields: spaceId
# A note's tags.
note_tag_note_idx:
fields: noteId
# A tag's notes.
note_tag_tag_idx:
fields: tagId

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,405 @@
{
"__className__": "serverpod.DatabaseMigration",
"actions": [
{
"__className__": "serverpod.DatabaseMigrationAction",
"type": "createTable",
"createTable": {
"__className__": "serverpod.TableDefinition",
"name": "note",
"dartName": "Note",
"module": "halkyon_learn",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "studysetId",
"fieldName": "studysetId",
"columnType": 7,
"isNullable": false,
"dartType": "UuidValue"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "position",
"fieldName": "position",
"columnType": 0,
"isNullable": false,
"dartType": "String"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "note_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
},
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "note_fk_1",
"columns": [
"studysetId"
],
"referenceTable": "studyset",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4,
"deferrable": "initiallyDeferred"
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "note_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
},
{
"__className__": "serverpod.IndexDefinition",
"indexName": "note_studyset_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "studysetId"
},
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "position"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
}
},
{
"__className__": "serverpod.DatabaseMigrationAction",
"type": "createTable",
"createTable": {
"__className__": "serverpod.TableDefinition",
"name": "note_revision",
"dartName": "NoteRevision",
"module": "halkyon_learn",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "noteId",
"fieldName": "noteId",
"columnType": 7,
"isNullable": false,
"dartType": "UuidValue"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "parents",
"fieldName": "parents",
"columnType": 8,
"isNullable": false,
"dartType": "List<UuidValue>"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "authorId",
"fieldName": "authorId",
"columnType": 7,
"isNullable": false,
"dartType": "UuidValue"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "reportId",
"fieldName": "reportId",
"columnType": 7,
"isNullable": true,
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "keyId",
"fieldName": "keyId",
"columnType": 0,
"isNullable": true,
"dartType": "String?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "payload",
"fieldName": "payload",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "nonce",
"fieldName": "nonce",
"columnType": 5,
"isNullable": true,
"dartType": "dart:typed_data:ByteData?"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "note_revision_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
},
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "note_revision_fk_1",
"columns": [
"noteId"
],
"referenceTable": "note",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4,
"deferrable": "initiallyDeferred"
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "note_revision_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
},
{
"__className__": "serverpod.IndexDefinition",
"indexName": "note_revision_note_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "noteId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
}
},
{
"__className__": "serverpod.DatabaseMigrationAction",
"type": "createTable",
"createTable": {
"__className__": "serverpod.TableDefinition",
"name": "note_tag",
"dartName": "NoteTag",
"module": "halkyon_learn",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "noteId",
"fieldName": "noteId",
"columnType": 7,
"isNullable": false,
"dartType": "UuidValue"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "tagId",
"fieldName": "tagId",
"columnType": 7,
"isNullable": false,
"dartType": "UuidValue"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "note_tag_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
},
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "note_tag_fk_1",
"columns": [
"noteId"
],
"referenceTable": "note",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4,
"deferrable": "initiallyDeferred"
},
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "note_tag_fk_2",
"columns": [
"tagId"
],
"referenceTable": "tag",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4,
"deferrable": "initiallyDeferred"
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "note_tag_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
},
{
"__className__": "serverpod.IndexDefinition",
"indexName": "note_tag_note_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "noteId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
},
{
"__className__": "serverpod.IndexDefinition",
"indexName": "note_tag_tag_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "tagId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
}
}
],
"warnings": [],
"migrationApiVersion": 1
}

View file

@ -0,0 +1,180 @@
BEGIN;
--
-- Function: gen_random_uuid_v7()
-- Source: https://gist.github.com/kjmph/5bd772b2c2df145aa645b837da7eca74
-- License: MIT (copyright notice included on the generator source code).
--
create or replace function gen_random_uuid_v7()
returns uuid
as $$
begin
-- use random v4 uuid as starting point (which has the same variant we need)
-- then overlay timestamp
-- then set version 7 by flipping the 2 and 1 bit in the version 4 string
return encode(
set_bit(
set_bit(
overlay(uuid_send(gen_random_uuid())
placing substring(int8send(floor(extract(epoch from clock_timestamp()) * 1000)::bigint) from 3)
from 1 for 6
),
52, 1
),
53, 1
),
'hex')::uuid;
end
$$
language plpgsql
volatile;
--
-- ACTION CREATE TABLE
--
CREATE TABLE "note" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"spaceId" bigint,
"studysetId" uuid NOT NULL,
"position" text NOT NULL
);
-- Indexes
CREATE INDEX "note_space_idx" ON "note" USING btree ("spaceId");
CREATE INDEX "note_studyset_idx" ON "note" USING btree ("studysetId", "position");
--
-- ACTION CREATE TABLE
--
CREATE TABLE "note_revision" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"spaceId" bigint,
"noteId" uuid NOT NULL,
"parents" json NOT NULL,
"authorId" uuid NOT NULL,
"reportId" uuid,
"keyId" text,
"payload" bytea NOT NULL,
"nonce" bytea
);
-- Indexes
CREATE INDEX "note_revision_space_idx" ON "note_revision" USING btree ("spaceId");
CREATE INDEX "note_revision_note_idx" ON "note_revision" USING btree ("noteId");
--
-- ACTION CREATE TABLE
--
CREATE TABLE "note_tag" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"spaceId" bigint,
"noteId" uuid NOT NULL,
"tagId" uuid NOT NULL
);
-- Indexes
CREATE INDEX "note_tag_space_idx" ON "note_tag" USING btree ("spaceId");
CREATE INDEX "note_tag_note_idx" ON "note_tag" USING btree ("noteId");
CREATE INDEX "note_tag_tag_idx" ON "note_tag" USING btree ("tagId");
--
-- ACTION CREATE FOREIGN KEY
--
ALTER TABLE ONLY "note"
ADD CONSTRAINT "note_fk_0"
FOREIGN KEY("spaceId")
REFERENCES "offline_sync_spaces"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
ALTER TABLE ONLY "note"
ADD CONSTRAINT "note_fk_1"
FOREIGN KEY("studysetId")
REFERENCES "studyset"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION
DEFERRABLE INITIALLY DEFERRED;
--
-- ACTION CREATE FOREIGN KEY
--
ALTER TABLE ONLY "note_revision"
ADD CONSTRAINT "note_revision_fk_0"
FOREIGN KEY("spaceId")
REFERENCES "offline_sync_spaces"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
ALTER TABLE ONLY "note_revision"
ADD CONSTRAINT "note_revision_fk_1"
FOREIGN KEY("noteId")
REFERENCES "note"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION
DEFERRABLE INITIALLY DEFERRED;
--
-- ACTION CREATE FOREIGN KEY
--
ALTER TABLE ONLY "note_tag"
ADD CONSTRAINT "note_tag_fk_0"
FOREIGN KEY("spaceId")
REFERENCES "offline_sync_spaces"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
ALTER TABLE ONLY "note_tag"
ADD CONSTRAINT "note_tag_fk_1"
FOREIGN KEY("noteId")
REFERENCES "note"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION
DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE ONLY "note_tag"
ADD CONSTRAINT "note_tag_fk_2"
FOREIGN KEY("tagId")
REFERENCES "tag"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION
DEFERRABLE INITIALLY DEFERRED;
--
-- MIGRATION VERSION FOR halkyon_learn
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('halkyon_learn', '20261002191122122-notes', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20261002191122122-notes', "timestamp" = now();
--
-- MIGRATION VERSION FOR serverpod
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('serverpod', '20260824182259319', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20260824182259319', "timestamp" = now();
--
-- MIGRATION VERSION FOR serverpod_auth_idp
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('serverpod_auth_idp', '20260924105404509', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20260924105404509', "timestamp" = now();
--
-- MIGRATION VERSION FOR serverpod_offline_sync
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('serverpod_offline_sync', '20260914143806119', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20260914143806119', "timestamp" = now();
--
-- MIGRATION VERSION FOR serverpod_auth_core
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('serverpod_auth_core', '20260924105232991', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20260924105232991', "timestamp" = now();
COMMIT;

View file

@ -10,3 +10,4 @@
20260930145136285-reviews
20261001064926911-tree-tags-assets
20261002035419149-file-allowance
20261002191122122-notes