[OCaml] High Intensity Training Online
docs apply STE100 fixes to prose comments
Rewrite doc comments and prose code comments across the app, import, and persistence libraries for Simplified Technical English: split semicolons and multi-clause sentences, convert passive voice to active, remove phrasal verbs and nominalized leads, and replace non-inclusive terms. Documentation-only STE100 compliance pass, no behavior change.
Changed files
- lib/app/catalog.mli
- lib/app/repository.ml
- lib/app/repository.mli
- lib/app/service.ml
- lib/app/service.mli
- lib/import/hevy_csv.ml
- lib/import/hevy_csv.mli
- lib/persistence/codec.ml
- lib/persistence/codec.mli
- lib/persistence/import_codec.ml
- lib/persistence/import_codec.mli
- lib/persistence/migration_data.ml
- lib/persistence/migrations.ml
- lib/persistence/migrations.mli
- lib/persistence/sqlite_repo.ml
- lib/persistence/sqlite_repo.mli
lib/app/catalog.mli
@@ -1,6 +1,6 @@
1
Removed:
(** The routine catalog: the presets the book supports. Shared by every
2
Removed:
repository adapter, and not trainee-scoped — a routine is authored, not
3
Removed:
owned. *)
1
Added:
(** The routine catalog holds the presets the book supports. The catalog is
2
Added:
shared by every repository adapter. It is not trainee-scoped, because a
3
Added:
routine is authored, not owned. *)
4
4
5
5
val routines : (Repository.routine_id * Prescription.Routine.t) list
6
6
(** Seeded with HD1's Ideal Routine, the one preset the book supports. *)
lib/app/repository.ml
@@ -4,7 +4,7 @@
4
4
let routine_id s = s
5
5
let workout_id s = s
6
6
7
Removed:
(* Fields are reached through the module type, not from here. *)
7
Added:
(* Callers reach these fields through the module type, not from here. *)
8
8
type record = { id : workout_id; workout : Evidence.Workout.t }
9
9
[@@warning "-69"]
10
10
lib/app/repository.mli
@@ -1,13 +1,13 @@
1
1
(** Persistence port. A pure module type: no database or web framework.
2
2
3
Removed:
Identity lives here rather than in the core, which carries none — a routine,
3
Added:
Identity lives here rather than in the core, which carries none. A routine,
4
4
a stored workout, or a trainee needs a name only once something has to
5
5
remember it.
6
6
7
Removed:
Every operation is scoped to a {!Trainee.id}. There is no server-wide state:
8
Removed:
the active routine and the workout in progress belong to a trainee and are
9
Removed:
stored, so nothing is lost across a restart and no two trainees share a
10
Removed:
slot. *)
7
Added:
Every operation is scoped to a {!Trainee.id}. The port holds no server-wide
8
Added:
state. The active routine and the workout in progress belong to a trainee
9
Added:
and are stored. Nothing is therefore lost across a restart, and no two
10
Added:
trainees share a slot. *)
11
11
12
12
type routine_id = private string
13
13
type workout_id = private string
@@ -102,7 +102,7 @@
102
102
val finish_workout : t -> Trainee.id -> Evidence.Workout.t -> record Lwt.t
103
103
(** Store a finished workout and clear the in-progress slot as one unit. An
104
104
adapter with transactions performs both in a single transaction, so a
105
Removed:
workout is never both saved to history and still shown as in progress. *)
105
Added:
workout never appears both in history and in progress at once. *)
106
106
107
107
val find : t -> Trainee.id -> workout_id -> record option Lwt.t
108
108
lib/app/service.ml
@@ -7,8 +7,8 @@
7
7
8
8
(* Error types are part of the flat public interface, so they live at the top
9
9
level of the functor. The implementation below is grouped into focused
10
Removed:
use-case modules over the shared [t]. The public names are re-exported at
11
Removed:
the end, so callers and {!Service.mli} see one flat service. *)
10
Added:
use-case modules over the shared [t]. The end of the functor re-exports the
11
Added:
public names, so callers and {!Service.mli} see one flat service. *)
12
12
13
13
type register_error = [ `Username of Trainee.username_error | `Username_taken ]
14
14
@@ -82,9 +82,9 @@
82
82
finished later governs, so an imported workout gates rest exactly as a
83
83
native one does. A tie favours the import: it is the more recent evidence
84
84
the trainee entered. When the import governs, HD1's baseline 48h interval
85
Removed:
applies, because a bare import carries no cycle position to compute a
86
Removed:
tail rest from — the routine's [recovery_after] needs a prescription an
87
Removed:
import does not have. Provenance never affects the judgment.
85
Added:
applies. A bare import carries no cycle position to compute a tail rest
86
Added:
from, because the routine's [recovery_after] needs a prescription that an
87
Added:
import lacks. Provenance never affects the judgment.
88
88
89
89
[recommended] is the native recommendation, already computed from the
90
90
routine and log by the caller. *)
@@ -310,8 +310,8 @@
310
310
imported alike. Provenance never changes the verdict: an observation is
311
311
an observation. Merge the two sources, keep only the requested exercise,
312
312
and order oldest-first by [performed_at] so {!Progress.assess} sees a
313
Removed:
chronological series. Ties order native before imported — an arbitrary
314
Removed:
but fixed rule that keeps the merge deterministic. *)
313
Added:
chronological series. Ties order native before imported. That rule is
314
Added:
arbitrary but fixed, so it keeps the merge deterministic. *)
315
315
let merged_observations ~log ~records exercise =
316
316
let native =
317
317
Evidence.Log.observations log exercise |> List.map (fun o -> (0, o))
@@ -351,7 +351,7 @@
351
351
workout's prescription, its volume, its clearance, and its extensions.
352
352
A promoted import carries none of that structure: it is a bare series of
353
353
failure observations with no prescription volume or recovery clearance
354
Removed:
to measure against. Folding imports in would fabricate habits the record
354
Added:
to measure against. An import fold would fabricate habits the record
355
355
cannot support. *)
356
356
let diagnostics t trainee =
357
357
R.log t.repo trainee >|= fun log -> Progress.diagnose log
@@ -458,9 +458,9 @@
458
458
459
459
(* Promote one workout. On [Promoted], write the canonical record first,
460
460
then save the batch that now remembers the promotion. The record upsert
461
Removed:
is atomic and carries every observation, so a crash between the two
462
Removed:
writes is harmless: re-running promotion is deterministic and replaces
463
Removed:
the same record keyed by [(trainee, workout_id)], never duplicating
461
Added:
is atomic and carries every observation. A crash between the two writes
462
Added:
is therefore harmless. Re-running promotion is deterministic: it replaces
463
Added:
the same record keyed by [(trainee, workout_id)] and never duplicates
464
464
observations. [Already_promoted] and [Blocked] leave the record store
465
465
untouched; the batch is saved only when [promote_workout] revised it. *)
466
466
let promote_import_workout t trainee ~batch ~workout =
lib/app/service.mli
@@ -1,16 +1,17 @@
1
1
(** Application service: orchestrates the core over a {!Repository.S}. The API a
2
Removed:
client calls — no HTML or serialization.
2
Added:
client calls. It carries no HTML or serialization.
3
3
4
4
Every call is scoped to a {!Trainee.id}. The service holds no mutable state
5
Removed:
of its own: the active routine and the workout in progress live in the
6
Removed:
repository, so a restart loses nothing and two trainees never share a slot.
5
Added:
of its own. The active routine and the workout in progress live in the
6
Added:
repository. A restart therefore loses nothing, and two trainees never share
7
Added:
a slot.
7
8
8
9
Recovery gating lives here, not in the client. {!Evidence.Workout.start}
9
Removed:
demands a {!Recovery.clearance}, and this module is the only thing that
10
Removed:
decides how one is obtained: earned by having rested, or taken deliberately
11
Removed:
through {!begin_workout}'s [override] acknowledgment. Putting that policy
12
Removed:
here means a native client cannot quietly adopt looser rules than the web
13
Removed:
one. *)
10
Added:
demands a {!Recovery.clearance}, and this module alone decides how a client
11
Added:
obtains one. A client earns clearance by having rested, or takes it
12
Added:
deliberately through {!begin_workout}'s [override] acknowledgment. This
13
Added:
policy lives here so a native client cannot quietly adopt looser rules than
14
Added:
the web one. *)
14
15
15
16
module Make (R : Repository.S) (_ : Password_hash.S) : sig
16
17
type t
@@ -21,8 +22,8 @@
21
22
(** {2 Accounts} *)
22
23
23
24
type register_error = [ `Username of Trainee.username_error | `Username_taken ]
24
Removed:
(** Why registration was refused: a malformed username or a username already
25
Removed:
in use. Passwords carry no policy, so they never refuse registration. *)
25
Added:
(** Registration fails for a malformed username or a username already in use.
26
Added:
Passwords carry no policy, so they never refuse registration. *)
26
27
27
28
val register :
28
29
t ->
@@ -38,8 +39,8 @@
38
39
39
40
type change_username_error =
40
41
[ `Username of Trainee.username_error | `Username_taken | `Unknown ]
41
Removed:
(** Why a rename was refused: a malformed name, a name already in use, or an
42
Removed:
unknown account. *)
42
Added:
(** A rename fails for a malformed name, a name already in use, or an unknown
43
Added:
account. *)
43
44
44
45
val change_username :
45
46
t ->
@@ -50,7 +51,7 @@
50
51
Returns the updated trainee on success. *)
51
52
52
53
type change_password_error = [ `Incorrect_password | `Unknown ]
53
Removed:
(** Why a password change was refused: the current password did not verify, or
54
Added:
(** A password change fails when the current password did not verify, or when
54
55
the account is unknown. *)
55
56
56
57
val change_password :
@@ -219,7 +220,8 @@
219
220
(** {2 Application feedback} *)
220
221
221
222
type app_feedback_error = [ `Empty_message | `Unknown_feedback ]
222
Removed:
(** Why an application feedback operation was refused. *)
223
Added:
(** An application feedback operation fails for an empty message or an unknown
224
Added:
feedback item. *)
223
225
224
226
val record_app_feedback :
225
227
t ->
@@ -250,18 +252,18 @@
250
252
251
253
(** {2 Workout import}
252
254
253
Removed:
Bringing a third-party export into hito is a two-stage flow. A batch is
254
Removed:
cleaned — exercises mapped, normal sets acknowledged — then promoted.
255
Removed:
Promotion turns a cleaned workout into canonical evidence stored as an
256
Removed:
{!Repository.imported_record}, independent of the provisional batch it
257
Removed:
came from. Import evidence gates recovery and feeds progression, but never
258
Removed:
advances the routine cycle. *)
255
Added:
Bringing a third-party export into hito is a two-stage flow. First, a
256
Added:
client cleans a batch: it maps exercises and acknowledges normal sets.
257
Added:
Then it promotes the batch. Promotion turns a cleaned workout into
258
Added:
canonical evidence stored as an {!Repository.imported_record}, independent
259
Added:
of the provisional batch it came from. Import evidence gates recovery and
260
Added:
feeds progression. It never advances the routine cycle. *)
259
261
260
262
type import_error =
261
263
[ `Unknown_batch | `Duplicate_source | `Import of Workout_import.error ]
262
Removed:
(** Why an import operation was refused: no such stored batch, another stored
263
Removed:
batch already carries the same source fingerprint, or the cleaning or
264
Removed:
promotion step itself failed. *)
264
Added:
(** An import operation fails when no such stored batch exists, when another
265
Added:
stored batch already carries the same source fingerprint, or when the
266
Added:
cleaning or promotion step itself failed. *)
265
267
266
268
val store_import_batch :
267
269
t ->
@@ -318,13 +320,14 @@
318
320
batch:Workout_import.batch_id ->
319
321
workout:Workout_import.workout_id ->
320
322
(Workout_import.workout_result, import_error) result Lwt.t
321
Removed:
(** Promote one cleaned workout. On success, one {!Repository.imported_record}
322
Removed:
is written carrying every observation of the workout, and the batch is
323
Removed:
saved so it remembers the promotion. The record upsert is atomic and keyed
324
Removed:
by [(trainee, workout_id)], so re-running promotion after a crash between
325
Removed:
the record write and the batch save replaces the same record
326
Removed:
deterministically and never duplicates observations. An already-promoted
327
Removed:
or blocked workout writes no evidence. *)
323
Added:
(** Promote one cleaned workout. On success, this writes one
324
Added:
{!Repository.imported_record} carrying every observation of the workout,
325
Added:
and saves the batch so it remembers the promotion. The record upsert is
326
Added:
atomic and keyed by [(trainee, workout_id)]. A crash can happen between
327
Added:
the record write and the batch save. Re-running promotion after such a
328
Added:
crash replaces the same record deterministically and never duplicates
329
Added:
observations. An already-promoted or blocked workout writes no evidence.
330
Added:
*)
328
331
329
332
val promote_import_batch :
330
333
t ->
lib/import/hevy_csv.ml
@@ -4,8 +4,8 @@
4
4
failing early on a missing column. Then it walks the data rows once: each
5
5
row becomes a set for some workout, or a batch warning. Rows group into
6
6
workouts by their exact [(title, start_time, end_time)] triple in
7
Removed:
first-appearance order, and warnings stay in source order. The source text
8
Removed:
is never rebuilt — it flows verbatim into the batch. *)
7
Added:
first-appearance order, and warnings stay in source order. The adapter never
8
Added:
rebuilds the source text. The text flows verbatim into the batch. *)
9
9
10
10
open Hito_app
11
11
module WI = Workout_import
@@ -73,9 +73,9 @@
73
73
74
74
Hevy writes local wall time as [DD Mon YYYY, HH:MM] with an English month
75
75
abbreviation. The adapter converts to UTC seconds using the caller's fixed
76
Removed:
offset: UTC = civil - offset. The calendar date is validated by a round-trip
77
Removed:
through [days_from_civil], so an impossible date such as 31 Feb is rejected
78
Removed:
rather than normalised. *)
76
Added:
offset: UTC = civil - offset. A round-trip through [days_from_civil]
77
Added:
validates the calendar date, so the adapter rejects an impossible date such
78
Added:
as 31 Feb rather than normalising it. *)
79
79
80
80
let months =
81
81
[|
@@ -317,9 +317,9 @@
317
317
(List.rev !groups_rev, List.rev !warnings_rev)
318
318
319
319
(* Builds one imported workout from a group. [make_workout] can still reject the
320
Removed:
times with [Invalid_time]; the classifier already dropped reversed rows, so a
321
Removed:
surviving group should not hit it, but the adapter converts any such rejection
322
Removed:
into a batch warning rather than raising. *)
320
Added:
times with [Invalid_time]. The classifier already dropped reversed rows, so a
321
Added:
surviving group should not reach that rejection. The adapter still converts
322
Added:
any such rejection into a batch warning rather than raising. *)
323
323
let build_workout ~batch_id ~index (g : group) : (WI.workout, WI.warning) result
324
324
=
325
325
let id =
@@ -343,8 +343,8 @@
343
343
}
344
344
345
345
let parse ~batch_id ~fingerprint ~imported_at ~utc_offset_seconds source =
346
Removed:
(* Parse the CSV text. [strip:false] keeps RFC4180 fidelity; quoted commas and
347
Removed:
newlines are preserved. A malformed export raises [Csv.Failure]. *)
346
Added:
(* Parse the CSV text. [strip:false] keeps RFC4180 fidelity. It preserves
347
Added:
quoted commas and newlines. A malformed export raises [Csv.Failure]. *)
348
348
match
349
349
try Ok (Csv.input_all (Csv.of_string ~strip:false source))
350
350
with Csv.Failure (record, field, detail) ->
lib/import/hevy_csv.mli
@@ -10,9 +10,9 @@
10
10
The adapter enforces the strict native schema. The header must carry all 14
11
11
verified columns, in any order. Rows the adapter cannot promote — warmups,
12
12
non-strength data, unknown set types, malformed fields — become
13
Removed:
{!Workout_import.warning} values, not sets. A row is dropped, never guessed
14
Removed:
at. Rows group into workouts by the exact [(title, start_time, end_time)]
15
Removed:
triple, in first-appearance order. *)
13
Added:
{!Workout_import.warning} values, not sets. The adapter drops a row, never
14
Added:
guesses at it. Rows group into workouts by the exact
15
Added:
[(title, start_time, end_time)] triple, in first-appearance order. *)
16
16
17
17
type error =
18
18
| Malformed_csv of { record : int; field : int; detail : string }
lib/persistence/codec.ml
@@ -97,7 +97,7 @@
97
97
| _ -> Error (Malformed "effort arity")
98
98
99
99
(* Decode a stimulus and the slot it filled. The current format prefixes the
100
Removed:
slot. A record from before slots were stored omits it, and replays in
100
Added:
slot. A record from before hito stored slots omits it, and replays in
101
101
performance order through [add_stimulus] instead. *)
102
102
let decode_stimulus fields =
103
103
let delivery = function
@@ -133,7 +133,7 @@
133
133
(Recovery.duration_to_seconds rested)
134
134
(Recovery.duration_to_seconds recommended)
135
135
136
Removed:
(* Recovery exposes only hours/days constructors, so derive an arbitrary
136
Added:
(* Recovery exposes only hour and day constructors, so derive an arbitrary
137
137
second-valued duration through elapsed: since=0, now=secs yields exactly
138
138
[secs] seconds. *)
139
139
let seconds_duration secs =
@@ -242,8 +242,8 @@
242
242
Ok { acc with a_stimuli_rev = s :: acc.a_stimuli_rev }
243
243
| fields -> Error (Malformed (String.concat "|" fields))
244
244
245
Removed:
(* Parse the whole text into a typed header, or fail. Mandatory fields are
246
Removed:
checked here so replay never sees a partial record. *)
245
Added:
(* Parse the whole text into a typed header, or fail. This checks the mandatory
246
Added:
fields here, so replay never sees a partial record. *)
247
247
let parse text =
248
248
let lines =
249
249
String.split_on_char '\n' text
lib/persistence/codec.mli
@@ -1,15 +1,15 @@
1
1
(** Serialization of performed facts, and their replay through the core.
2
2
3
Removed:
The core types are opaque and carry no serializers, by design: the logbook
4
Removed:
records, it never interprets. This module is the trust boundary for stored
3
Added:
The core types are opaque and carry no serializers, by design. The logbook
4
Added:
records. It never interprets. This module is the trust boundary for stored
5
5
data. It captures only what was performed — exercise, load, reps, outcome,
6
6
timestamps, and the basis on which a workout was begun — and rebuilds a
7
7
{!Evidence.Workout.t} by driving the same constructors a live session does.
8
8
9
Removed:
A workout's prescription is not stored. It is found again by name in the
10
Removed:
routine catalog, so a stored workout stays valid only as long as its routine
11
Removed:
and workout names do. That is the intended coupling: prescriptions are
12
Removed:
authored, not recorded. *)
9
Added:
A workout's prescription is not stored. The decoder finds it again by name
10
Added:
in the routine catalog, so a stored workout stays valid only as long as its
11
Added:
routine and workout names do. That is the intended coupling: prescriptions
12
Added:
are authored, not recorded. *)
13
13
14
14
type error =
15
15
| Malformed of string (** The stored text does not parse. *)
lib/persistence/import_codec.ml
@@ -10,10 +10,10 @@
10
10
11
11
(* --- length-prefixed writer ---
12
12
13
Removed:
Every field is written as its byte length in decimal, a colon, then its raw
14
Removed:
bytes. The length frames the bytes exactly, so a field may hold any byte,
15
Removed:
including newlines, commas, and colons, and the reader never has to escape or
16
Removed:
guess where a field ends. *)
13
Added:
The writer emits every field as its byte length in decimal, a colon, then
14
Added:
its raw bytes. The length frames the bytes exactly. A field may therefore
15
Added:
hold any byte, including newlines, commas, and colons, and the reader never
16
Added:
has to escape or guess where a field ends. *)
17
17
18
18
module Writer = struct
19
19
let create () = Buffer.create 256
@@ -108,8 +108,8 @@
108
108
go [] n
109
109
110
110
(* Resolve an exercise id string into an [Exercise.id]. The id type is
111
Removed:
private, so a valid id can only come from the catalog. A stored id the
112
Removed:
catalog no longer names is reported as [Unknown]. *)
111
Added:
private, so a valid id can only come from the catalog. The reader reports a
112
Added:
stored id the catalog no longer names as [Unknown]. *)
113
113
let exercise_id r =
114
114
let raw = field r in
115
115
match Exercise.find_id raw with
@@ -253,10 +253,10 @@
253
253
Writer.list write_warning buf s.warnings;
254
254
Writer.contents buf
255
255
256
Removed:
(* Reading a snapshot builds the parser-free {!WI.snapshot}, keeping every
257
Removed:
exercise id as a raw string. Restoration through {!WI.restore} resolves the
258
Removed:
ids and rebuilds the opaque batch, so an unknown id or an invalid time is
259
Removed:
reported once, by the use case. *)
256
Added:
(* The reader builds the parser-free {!WI.snapshot}, keeping every exercise id
257
Added:
as a raw string. Restoration through {!WI.restore} resolves the ids and
258
Added:
rebuilds the opaque batch. The use case therefore reports an unknown id or an
259
Added:
invalid time once. *)
260
260
let read_promotion r : WI.promoted_snapshot =
261
261
let title = Reader.string r in
262
262
let started_at = Reader.timestamp r in
lib/persistence/import_codec.mli
@@ -8,11 +8,12 @@
8
8
evidence.
9
9
10
10
The wire format is a length-prefixed field framing: every field is written
11
Removed:
as its byte length, a colon, then its raw bytes. Because the length is
12
Removed:
counted in bytes and the bytes are copied verbatim, a field may hold
13
Removed:
arbitrary UTF-8, newlines, commas, or colons — the export source, an
14
Removed:
acknowledgement, a title, or an unsupported-row reason is preserved exactly.
15
Removed:
No {!Marshal}, no {!Obj}: the format is textual and self-describing.
11
Added:
as its byte length, a colon, then its raw bytes. The writer counts the
12
Added:
length in bytes and copies the bytes verbatim. A field may therefore hold
13
Added:
arbitrary UTF-8, newlines, commas, or colons. The format preserves the
14
Added:
export source, an acknowledgement, a title, or an unsupported-row reason
15
Added:
exactly. No {!Marshal}, no {!Obj}: the format is textual and
16
Added:
self-describing.
16
17
17
18
Timestamps reuse {!Timestamp}. Exercise ids resolve through
18
19
{!Hito_core.Exercise.find_id}. A decode failure is a module-specific
lib/persistence/migration_data.ml
@@ -1,14 +1,14 @@
1
Removed:
(* The SQL schema migrations, as data. This module holds no logic — only the
2
Removed:
ordered list of migrations and the ledger table that records which ones a
1
Added:
(* The SQL schema migrations, as data. This module holds no logic. It holds only
2
Added:
the ordered list of migrations and the ledger table that records which ones a
3
3
database file already carries. {!Migrations} reads this and applies it.
4
4
5
5
Keeping the SQL here, apart from the apply logic, means a schema change edits
6
6
one place and never touches OCaml control flow.
7
7
8
Removed:
The seven incremental migrations that grew this schema were consolidated into
9
Removed:
a single initial migration when the deployed database was reset: there was no
10
Removed:
file left to migrate, so the drop-and-recreate churn of the old chain served
11
Removed:
no purpose. Add the next schema change as version 2. *)
8
Added:
Seven incremental migrations first grew this schema. A reset of the deployed
9
Added:
database consolidated them into a single initial migration. No file was left
10
Added:
to migrate, so the drop-and-recreate churn of the old chain served no
11
Added:
purpose. Add the next schema change as version 2. *)
12
12
13
13
type t = { version : int; name : string; statements : string list }
14
14
(** One migration: a version, a human name, and the ordered statements it runs.
@@ -86,8 +86,8 @@
86
86
(* An import batch is provisional: it holds the raw export source and
87
87
the cleaning done so far, encoded as one snapshot blob. A promoted
88
88
record is canonical evidence with an independent lifecycle, so it
89
Removed:
carries no foreign key to its batch — deleting the batch must never
90
Removed:
cascade to the record. The batch's [imported_at] is a column so
89
Added:
carries no foreign key to its batch. Deleting the batch must never
90
Added:
cascade to the record. The batch's [imported_at] is a column, so
91
91
listing can order without decoding every blob. A record is keyed by
92
92
(trainee, workout) so a re-promotion replaces it. *)
93
93
{|CREATE TABLE IF NOT EXISTS import_batch (
lib/persistence/migrations.ml
@@ -1,7 +1,7 @@
1
Removed:
(* Applies the versioned schema migrations. The migrations themselves — the SQL
2
Removed:
and the ledger table — live in {!Migration_data}; this module holds only the
3
Removed:
logic that runs them: the ledger check that makes applying idempotent, and
4
Removed:
the per-migration transaction. *)
1
Added:
(* Applies the versioned schema migrations. The migrations themselves live in
2
Added:
{!Migration_data}: the SQL and the ledger table. This module holds only the
3
Added:
logic that runs them. That logic is the ledger check that makes applying
4
Added:
idempotent, and the per-migration transaction. *)
5
5
6
6
type migration = Migration_data.t = {
7
7
version : int;
lib/persistence/migrations.mli
@@ -1,9 +1,9 @@
1
1
(** Versioned schema migrations for the SQLite store.
2
2
3
3
Each migration is a numbered, named set of statements. A [schema_migrations]
4
Removed:
ledger records which versions a database file already carries, so applying
5
Removed:
on connect runs only the migrations the file is missing and never repeats
6
Removed:
one. Every statement still uses [IF NOT EXISTS], so a database created by an
4
Added:
ledger records which versions a database file already carries. On connect,
5
Added:
hito runs only the migrations the file is missing and never repeats one.
6
Added:
Every statement still uses [IF NOT EXISTS], so a database created by an
7
7
earlier, ledger-less build stays safe.
8
8
9
9
Includes the [dream_session] table {!Dream.sql_sessions} expects. *)
lib/persistence/sqlite_repo.ml
@@ -254,8 +254,9 @@
254
254
| None -> None
255
255
256
256
(* Rename in one transaction: check the name is free for anyone else, then
257
Removed:
update. Running both on the same connection keeps the check and the write
258
Removed:
from racing another rename. The trainee's own current name is a no-op. *)
257
Added:
update. Both statements run on the same connection, so the check and the
258
Added:
write cannot race another rename. The trainee's own current name is a no-op.
259
Added:
*)
259
260
let update_username t id (username : Trainee.username) =
260
261
let trainee = Trainee.id_to_string id in
261
262
let username_s = Trainee.username_to_string username in
lib/persistence/sqlite_repo.mli
@@ -1,9 +1,9 @@
1
1
(** Durable {!Repository.S} backed by SQLite through Caqti. The production
2
2
store.
3
3
4
Removed:
All performed facts are stored through {!Codec}: the adapter never inspects
5
Removed:
domain shape, it stores and returns opaque encoded strings. Prescriptions
6
Removed:
are not stored — a workout resolves its prescription against the shared
4
Added:
All performed facts are stored through {!Codec}. The adapter never inspects
5
Added:
domain shape. It stores and returns opaque encoded strings. Prescriptions
6
Added:
are not stored. A workout resolves its prescription against the shared
7
7
{!Catalog} on read.
8
8
9
9
Identity is assigned here: trainees and workouts get string ids the adapter
@@ -13,14 +13,14 @@
13
13
include Repository.S
14
14
15
15
exception Corrupt of Codec.error
16
Removed:
(** Raised when a stored workout cannot be decoded — a corrupted database or a
17
Removed:
catalog that no longer names a stored routine or workout. This is a fault,
18
Removed:
not a normal outcome. *)
16
Added:
(** Raised when a stored workout cannot be decoded. The cause is a corrupted
17
Added:
database, or a catalog that no longer names a stored routine or workout.
18
Added:
This is a fault, not a normal outcome. *)
19
19
20
20
exception Import_corrupt of Import_codec.error
21
Removed:
(** Raised when a stored import batch or promoted record cannot be decoded — a
22
Removed:
corrupted database or a catalog that no longer names a stored exercise. This
23
Removed:
is a fault, not a normal outcome. *)
21
Added:
(** Raised when a stored import batch or promoted record cannot be decoded. The
22
Added:
cause is a corrupted database, or a catalog that no longer names a stored
23
Added:
exercise. This is a fault, not a normal outcome. *)
24
24
25
25
val connect : string -> (t, Caqti_error.t) result Lwt.t
26
26
(** [connect uri] opens a connection pool and applies {!Migrations}. [uri] is a