@open-tomato/rafa 0.45.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +255 -23
- package/dist/PROMPT.md +18 -1
- package/dist/adapters/logger/settings.d.ts +132 -0
- package/dist/bugs/closest.d.ts +19 -0
- package/dist/bugs/fenced.d.ts +8 -0
- package/dist/bugs/places.d.ts +20 -0
- package/dist/bugs/record.d.ts +145 -0
- package/dist/bugs/title.d.ts +83 -0
- package/dist/bundled/agents/doc-updater.md +17 -3
- package/dist/bundled/agents/loop-implementer.md +14 -2
- package/dist/bundled/operators/skills/rafa-stretch-pit-stop/SKILL.md +10 -3
- package/dist/bundled/operators/skills/rafa-stretch-sweep/SKILL.md +5 -0
- package/dist/bundled/skills/dev-planner/SKILL.md +101 -18
- package/dist/cli.js +74726 -62960
- package/dist/config/entry.d.ts +119 -0
- package/dist/config/index.d.ts +36 -0
- package/dist/config/index.js +51 -0
- package/dist/config/shape.d.ts +232 -0
- package/dist/config-registry.d.ts +127 -0
- package/dist/config-schema-board-project.d.ts +157 -0
- package/dist/config-schema-bugs.d.ts +71 -0
- package/dist/config-schema-errors.d.ts +54 -0
- package/dist/config-schema-features.d.ts +64 -0
- package/dist/config-schema-hub.d.ts +150 -0
- package/dist/config-schema-issues.d.ts +95 -0
- package/dist/config-schema-logger.d.ts +91 -0
- package/dist/config-schema-loop-continue.d.ts +122 -0
- package/dist/config-schema-loop-retries.d.ts +94 -0
- package/dist/config-schema-release.d.ts +307 -0
- package/dist/config-schema-tests.d.ts +170 -0
- package/dist/config-schema-triage.d.ts +142 -0
- package/dist/config-schema-wrap-up.d.ts +96 -0
- package/dist/core/command/command.d.ts +228 -0
- package/dist/core/command/confirm.d.ts +67 -0
- package/dist/core/command/index.d.ts +112 -0
- package/dist/core/command/lazy.d.ts +54 -0
- package/dist/core/command/registry-types.d.ts +96 -0
- package/dist/core/command/running.d.ts +96 -0
- package/dist/core/command/spends.d.ts +39 -0
- package/dist/core/command/types.d.ts +92 -0
- package/dist/core/command/version.d.ts +4 -0
- package/dist/core/config/branch-template.d.ts +20 -0
- package/dist/core/config/config-items.d.ts +86 -0
- package/dist/core/config/config-load.d.ts +47 -0
- package/dist/core/config/config-origins.d.ts +57 -0
- package/dist/core/config/config-readers.d.ts +167 -0
- package/dist/core/config/config-schema.d.ts +207 -0
- package/dist/core/config/config-sections.d.ts +493 -0
- package/dist/core/config/config.d.ts +320 -0
- package/dist/core/config/core-section.d.ts +39 -0
- package/dist/core/config/dangerous.d.ts +58 -0
- package/dist/core/config/default-routing.d.ts +16 -0
- package/dist/core/config/index.d.ts +141 -0
- package/dist/core/config/scope.d.ts +42 -0
- package/dist/core/config/section.d.ts +206 -0
- package/dist/core/config/sections.d.ts +45 -0
- package/dist/core/config/standard-schema.d.ts +27 -0
- package/dist/core/git/commit.d.ts +232 -0
- package/dist/core/git/gh-runner.d.ts +24 -0
- package/dist/core/git/git.d.ts +22 -0
- package/dist/core/git/index.d.ts +85 -0
- package/dist/core/loop/claude.d.ts +388 -0
- package/dist/core/loop/hop-record.d.ts +52 -0
- package/dist/core/loop/index.d.ts +179 -0
- package/dist/core/loop/session-env.d.ts +20 -0
- package/dist/core/loop/session-record-parse.d.ts +63 -0
- package/dist/core/loop/sessions.d.ts +408 -0
- package/dist/core/loop/suite-failure.d.ts +25 -0
- package/dist/core/output/active.d.ts +100 -0
- package/dist/core/output/stream.d.ts +20 -0
- package/dist/core/output/text.d.ts +55 -0
- package/dist/core/plan/block-reader.d.ts +46 -0
- package/dist/core/plan/blocks.d.ts +139 -0
- package/dist/core/plan/declaration.d.ts +366 -0
- package/dist/core/plan/plan-stamp.d.ts +75 -0
- package/dist/core/runtime/identity.d.ts +21 -0
- package/dist/core/runtime/index.d.ts +46 -0
- package/dist/effort/store/bring-forward.d.ts +131 -0
- package/dist/effort/store/check-rows.d.ts +31 -0
- package/dist/effort/store/commit-stats.d.ts +42 -0
- package/dist/effort/store/copy.d.ts +35 -0
- package/dist/effort/store/development-build.d.ts +105 -0
- package/dist/effort/store/effort-levels.d.ts +13 -0
- package/dist/effort/store/findings.d.ts +214 -0
- package/dist/effort/store/fix-schema.d.ts +184 -0
- package/dist/effort/store/index.js +10 -7
- package/dist/effort/store/location.d.ts +44 -0
- package/dist/effort/store/merge-commit-gaps.d.ts +68 -0
- package/dist/effort/store/merge-conflicts.d.ts +91 -0
- package/dist/effort/store/merge-rules.d.ts +82 -0
- package/dist/effort/store/merge-store.d.ts +211 -0
- package/dist/effort/store/merge-union.d.ts +92 -0
- package/dist/effort/store/migrate.d.ts +146 -0
- package/dist/effort/store/migrations.d.ts +92 -0
- package/dist/effort/store/ndjson-files.d.ts +114 -0
- package/dist/effort/store/origins.d.ts +116 -0
- package/dist/effort/store/plan-stubs.d.ts +79 -0
- package/dist/effort/store/rebuild-aside.d.ts +172 -0
- package/dist/effort/store/schema-plan.d.ts +102 -0
- package/dist/effort/store/session-row-types.d.ts +64 -0
- package/dist/effort/store/settings.d.ts +13 -0
- package/dist/effort/store/sqlite.d.ts +340 -0
- package/dist/effort/store/store-generation.d.ts +17 -0
- package/dist/effort/store/store-identity.d.ts +220 -0
- package/dist/effort/store/store-meta.d.ts +305 -0
- package/dist/effort/store/store-replaced.d.ts +72 -0
- package/dist/effort/store/types.d.ts +223 -0
- package/dist/errors/codes.d.ts +56 -0
- package/dist/errors/rafa-codes.d.ts +36 -0
- package/dist/index-2a7nb3an.js +125 -0
- package/dist/{index-r28fzsvc.js → index-39kbcw68.js} +350 -165
- package/dist/{index-nv34ptht.js → index-77ajfffn.js} +14 -9
- package/dist/index-cy98y87p.js +2597 -0
- package/dist/index-gqz4jn6j.js +13447 -0
- package/dist/index-gvek4r8m.js +710 -0
- package/dist/index-j4k2vdz3.js +1125 -0
- package/dist/index-jh1r4kcn.js +1067 -0
- package/dist/{index-mgh2s9bj.js → index-ykggr043.js} +58 -455
- package/dist/index-z1w83f81.js +4 -0
- package/dist/index.js +14715 -21516
- package/dist/learning/index.js +5 -6
- package/dist/plan/index.js +12 -11
- package/dist/plan-prompt.md +3 -1
- package/dist/ports/index.d.ts +732 -0
- package/dist/ports/learning.d.ts +101 -0
- package/dist/ports/log-level.d.ts +13 -0
- package/dist/ports/spec-review.d.ts +66 -0
- package/dist/pr/checks.d.ts +133 -0
- package/dist/pr/types.d.ts +422 -0
- package/dist/ready-4f8edezt.js +2016 -0
- package/dist/release-templates/release-bot-fragment.yml +153 -0
- package/dist/release-templates/release-publish.yml +110 -0
- package/dist/release-templates/release-settle.yml +122 -0
- package/dist/release-templates/release-tag.yml +102 -0
- package/dist/report/parse.d.ts +415 -0
- package/dist/schema/project-id.d.ts +79 -0
- package/dist/schema/tiers.d.ts +107 -0
- package/package.json +41 -3
- package/dist/index-hf2k497d.js +0 -16
- package/dist/index-nsr78ct2.js +0 -8219
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The findings writer: one row in the store's `findings` table for each
|
|
3
|
+
* entry of a task report's findings list.
|
|
4
|
+
*
|
|
5
|
+
* A task session ends its output with a `rafa:report` block,
|
|
6
|
+
* `parseReport` reads its findings list entry for entry, and
|
|
7
|
+
* {@link writeFindings} stores it. Each row is the `Sighting` a later
|
|
8
|
+
* phase promotes instincts from, and what the loop renders
|
|
9
|
+
* `progress.txt` from at dispatch.
|
|
10
|
+
*
|
|
11
|
+
* ## The row
|
|
12
|
+
*
|
|
13
|
+
* | Column | From |
|
|
14
|
+
* | --- | --- |
|
|
15
|
+
* | `id` | generated, `randomUUID` unless a seam replaces it |
|
|
16
|
+
* | `session_id`, `plan_stub`, `task_line` | the dispatch |
|
|
17
|
+
* | `kind`, `trigger`, `what`, `cause`, `resolution`, `artifact`, `signal` | the report entry, as parsed |
|
|
18
|
+
* | `outcome` | the loop: `done`, `blocked` or `failed` |
|
|
19
|
+
* | `tracker_ref` | null on every row this writer writes; `tracker-refs.ts` sets it |
|
|
20
|
+
* | `collected_at` | the write's time, ISO 8601, one per write |
|
|
21
|
+
* | `origin_store`, `origin_seq` | the store's origin and the row's own `seq`, NULL in both when unminted (`origins.ts`) |
|
|
22
|
+
*
|
|
23
|
+
* `seq` comes first, the append order, as in every table of the store.
|
|
24
|
+
* An entry's `extras` are not stored: the table has a column for each
|
|
25
|
+
* field the report defines, and none for a key it does not.
|
|
26
|
+
*
|
|
27
|
+
* ## Where the table lives, and why it is not a kind
|
|
28
|
+
*
|
|
29
|
+
* In the SQLite store's file, created by the second entry of
|
|
30
|
+
* `SQLITE_MIGRATIONS` and written through `writeSqliteStore`, as every
|
|
31
|
+
* kind's append is. It is written there whichever backend the `store`
|
|
32
|
+
* setting selects: the NDJSON backend has no findings file. The table
|
|
33
|
+
* stays outside the port's row map on purpose, because a finding breaks
|
|
34
|
+
* three things the port holds of a kind:
|
|
35
|
+
*
|
|
36
|
+
* - A kind is deduplicated by one key, which is also what a collector
|
|
37
|
+
* skips by. A finding is deduplicated by one of two keys, and nothing
|
|
38
|
+
* collects it.
|
|
39
|
+
* - A kind's row is stored whole as JSON, so both backends hold it
|
|
40
|
+
* byte for byte. A finding has a column per field, which is what the
|
|
41
|
+
* spec names and what a query filters on.
|
|
42
|
+
* - A kind's row is the same however often it is collected. A finding
|
|
43
|
+
* row carries a generated id and the time it was written.
|
|
44
|
+
*
|
|
45
|
+
* ## Deduplication
|
|
46
|
+
*
|
|
47
|
+
* Within one session, an entry duplicates an earlier one when it carries
|
|
48
|
+
* the same `artifact`, or, carrying none, the same `trigger` and `what`.
|
|
49
|
+
* The earlier row stays, whether it is on disk or earlier in the same
|
|
50
|
+
* write, and nothing of the later entry is kept: two entries differing
|
|
51
|
+
* only in `resolution` are one row.
|
|
52
|
+
*
|
|
53
|
+
* - The match is exact: byte for byte, no trimming, no case folding.
|
|
54
|
+
* `artifact` is the string a recurrence is matched on, and a looser
|
|
55
|
+
* match here would merge findings the report kept apart.
|
|
56
|
+
* - An entry with an artifact never duplicates one without, even with
|
|
57
|
+
* the same trigger and what. Each is keyed by its own rule, and the
|
|
58
|
+
* two keys never meet.
|
|
59
|
+
* - The scope is the SESSION, never the table. Promotion keys on one
|
|
60
|
+
* artifact sighted by different tasks or sessions, and phase 1
|
|
61
|
+
* comments on the issue a recurrence in a later task already filed.
|
|
62
|
+
* A table-wide dedupe would keep one row per artifact ever, and
|
|
63
|
+
* nothing could recur. Within a session, a report written twice
|
|
64
|
+
* writes its rows once.
|
|
65
|
+
*
|
|
66
|
+
* Two partial unique indexes enforce it, one per rule, and the insert
|
|
67
|
+
* names both as conflict targets. Measured with bun 1.3.14 on SQLite
|
|
68
|
+
* 3.51.0:
|
|
69
|
+
*
|
|
70
|
+
* - A target has to repeat its index's `WHERE`. Without it the insert
|
|
71
|
+
* throws `ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE
|
|
72
|
+
* constraint`.
|
|
73
|
+
* - Only those two conflicts are absorbed. An id generated twice
|
|
74
|
+
* throws `UNIQUE constraint failed: findings.id` and rolls the whole
|
|
75
|
+
* write back, where a bare `ON CONFLICT DO NOTHING` would drop the
|
|
76
|
+
* second entry as if it were a duplicate.
|
|
77
|
+
*
|
|
78
|
+
* ## What is refused, and at which level
|
|
79
|
+
*
|
|
80
|
+
* The loop's own inputs refuse the WHOLE write: an empty session id, an
|
|
81
|
+
* outcome outside {@link FINDING_OUTCOMES}, or dispatch text holding a
|
|
82
|
+
* lone UTF-16 surrogate. The write throws before the store is opened,
|
|
83
|
+
* so it leaves no file behind and changes no byte of one that exists.
|
|
84
|
+
* Code supplies these, and a wrong one is a bug to surface, not a row to
|
|
85
|
+
* store. The check runs even when the findings list is empty.
|
|
86
|
+
*
|
|
87
|
+
* A report ENTRY is refused alone, and the rest are written. A model
|
|
88
|
+
* wrote the entries, and one it got wrong must not cost the findings it
|
|
89
|
+
* got right. That is where this writer departs from the port, which
|
|
90
|
+
* refuses a whole batch for one keyless row. A refused entry is never
|
|
91
|
+
* stored, and is answered in {@link FindingsWriteResult.rejected}:
|
|
92
|
+
*
|
|
93
|
+
* - `unstorable-field`: a value `parseReport` never answers. A text
|
|
94
|
+
* field that is not a string, is blank, or holds a lone UTF-16
|
|
95
|
+
* surrogate, or a kind or signal outside its closed set. A blank key
|
|
96
|
+
* would merge every blank one, and a lone surrogate is bound lossily
|
|
97
|
+
* (see `sqlite.ts`), merging distinct keys.
|
|
98
|
+
* - `unkeyed`: no artifact, and no trigger or no what. Nothing could
|
|
99
|
+
* deduplicate the row, so every rewrite would add it again.
|
|
100
|
+
*
|
|
101
|
+
* The table refuses those rows itself, through its CHECK constraints, so
|
|
102
|
+
* a row written from outside cannot hold what this writer rejects. The
|
|
103
|
+
* kind and signal sets are spelled in the migration, which never changes
|
|
104
|
+
* once shipped: widening either is a new migration, and the suite pins
|
|
105
|
+
* the migration's sets to `FINDING_KINDS` and `FINDING_SIGNALS`.
|
|
106
|
+
*
|
|
107
|
+
* `outcome` has no CHECK. The spec adds the CI verdict to it once one is
|
|
108
|
+
* known, and SQLite cannot change a CHECK in place: measured, `ALTER
|
|
109
|
+
* TABLE ... ADD CHECK` is a syntax error, so a widened set would mean
|
|
110
|
+
* rebuilding the table. The closed set is enforced here instead.
|
|
111
|
+
*
|
|
112
|
+
* ## Writing nothing writes nothing, and still checks the schema
|
|
113
|
+
*
|
|
114
|
+
* A write left with no entry to insert, because the list was empty or
|
|
115
|
+
* every entry was refused, inserts no row. On a store that does not
|
|
116
|
+
* exist it opens nothing, and creates no file and no directory. On one
|
|
117
|
+
* that exists it still opens the store, through `writeSqliteStore`, so
|
|
118
|
+
* the schema check runs as it does for a write with rows: a store past
|
|
119
|
+
* this rafa's version is refused with no byte changed, one below it is
|
|
120
|
+
* brought forward, and one already at this version changes no byte.
|
|
121
|
+
*/
|
|
122
|
+
import type { ReportFinding } from '../../report/parse.js';
|
|
123
|
+
/**
|
|
124
|
+
* What the loop made of a task, as its findings rows record it. The
|
|
125
|
+
* report's own `status` is the session's claim; this is the loop's.
|
|
126
|
+
*/
|
|
127
|
+
export declare const FINDING_OUTCOMES: readonly ["done", "blocked", "failed"];
|
|
128
|
+
/** One of the loop's outcomes. */
|
|
129
|
+
export type FindingOutcome = (typeof FINDING_OUTCOMES)[number];
|
|
130
|
+
/** The dispatch a report came from. */
|
|
131
|
+
export interface FindingsDispatch {
|
|
132
|
+
/** The task session's id. Non-empty: it scopes deduplication. */
|
|
133
|
+
readonly sessionId: string;
|
|
134
|
+
/** The plan's stub, or null when the dispatch resolved none. */
|
|
135
|
+
readonly planStub: string | null;
|
|
136
|
+
/** The task line, as the dispatch quoted it. */
|
|
137
|
+
readonly taskLine: string;
|
|
138
|
+
}
|
|
139
|
+
/** One write: a report's findings, where they came from, and the outcome. */
|
|
140
|
+
export interface FindingsWrite {
|
|
141
|
+
readonly dispatch: FindingsDispatch;
|
|
142
|
+
readonly outcome: FindingOutcome;
|
|
143
|
+
/** The report's findings list, as `parseReport` answers it. */
|
|
144
|
+
readonly findings: readonly ReportFinding[];
|
|
145
|
+
}
|
|
146
|
+
/** Seams for the two values a write generates. */
|
|
147
|
+
export interface FindingsWriterSeams {
|
|
148
|
+
/** The write's time. Called once per write. Defaults to the clock. */
|
|
149
|
+
readonly now?: () => Date;
|
|
150
|
+
/** One row's id. Called once per stored entry. Defaults to `randomUUID`. */
|
|
151
|
+
readonly newId?: () => string;
|
|
152
|
+
}
|
|
153
|
+
/** Why one entry was not written. */
|
|
154
|
+
export type FindingRejectionReason =
|
|
155
|
+
/** A field holds a value `parseReport` never answers. */
|
|
156
|
+
'unstorable-field'
|
|
157
|
+
/** No artifact, and no trigger or no what, to deduplicate it by. */
|
|
158
|
+
| 'unkeyed';
|
|
159
|
+
/** One entry that was not written, and why. */
|
|
160
|
+
export interface FindingRejection {
|
|
161
|
+
/** The entry's index in the findings list. */
|
|
162
|
+
readonly index: number;
|
|
163
|
+
readonly reason: FindingRejectionReason;
|
|
164
|
+
/** The field at fault, by its report key, or null for `unkeyed`. */
|
|
165
|
+
readonly field: string | null;
|
|
166
|
+
/** One sentence for an operator to read. */
|
|
167
|
+
readonly text: string;
|
|
168
|
+
}
|
|
169
|
+
/** What one write did. */
|
|
170
|
+
export interface FindingsWriteResult {
|
|
171
|
+
/** The store's file, whether or not anything was written to it. */
|
|
172
|
+
readonly path: string;
|
|
173
|
+
/** Rows written. */
|
|
174
|
+
readonly appended: number;
|
|
175
|
+
/** Entries the session already held, on disk or earlier in the write. */
|
|
176
|
+
readonly skipped: number;
|
|
177
|
+
/**
|
|
178
|
+
* Entries refused, in list order. With these, `appended + skipped +
|
|
179
|
+
* rejected.length` is the findings list's length.
|
|
180
|
+
*/
|
|
181
|
+
readonly rejected: readonly FindingRejection[];
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* A value as a refusal quotes it. Never serialises an object. Exported
|
|
185
|
+
* for the triage writer, so its refusals quote a value the same way.
|
|
186
|
+
*/
|
|
187
|
+
export declare function describeValue(value: unknown): string;
|
|
188
|
+
/**
|
|
189
|
+
* Why a text value cannot be stored, or null when it is null or can be.
|
|
190
|
+
* Exported for the triage writer, whose text fields are held to it.
|
|
191
|
+
*/
|
|
192
|
+
export declare function textProblem(value: unknown): string | null;
|
|
193
|
+
/**
|
|
194
|
+
* Throws unless a dispatch and an outcome can be stored on every row of
|
|
195
|
+
* one write. `subject` names the write in the refusal: `findings` here.
|
|
196
|
+
*
|
|
197
|
+
* Exported for the triage writer, so every table one report fills
|
|
198
|
+
* refuses a dispatch by the same rule and in the same words. A writer
|
|
199
|
+
* whose table has no outcome column passes null, and only the dispatch
|
|
200
|
+
* is checked; `changes.ts` is the one that does.
|
|
201
|
+
*/
|
|
202
|
+
export declare function checkDispatch(subject: string, dispatch: FindingsDispatch, outcome: FindingOutcome | null): void;
|
|
203
|
+
/**
|
|
204
|
+
* Writes one report's findings for one dispatch, one row per entry the
|
|
205
|
+
* session does not already hold.
|
|
206
|
+
*
|
|
207
|
+
* Throws, having opened nothing, when the dispatch or the outcome cannot
|
|
208
|
+
* be stored. Throws the store's own refusal of a schema past this rafa's
|
|
209
|
+
* version whenever the store exists, even with nothing to insert. An
|
|
210
|
+
* entry that cannot be stored is left out and answered in `rejected`,
|
|
211
|
+
* and the others are written. See the module note for the dedupe rule
|
|
212
|
+
* and each refusal.
|
|
213
|
+
*/
|
|
214
|
+
export declare function writeFindings(repoRoot: string, write: FindingsWrite, seams?: FindingsWriterSeams): FindingsWriteResult;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The repair `rafa effort fix-schema` runs: a SQLite store this rafa
|
|
3
|
+
* refuses, rebuilt at the migrations this rafa knows beside the live
|
|
4
|
+
* file, checked, and swapped in with the original kept as a backup.
|
|
5
|
+
*
|
|
6
|
+
* ## Which stores it repairs
|
|
7
|
+
*
|
|
8
|
+
* The decision is `planSchema`'s (`schema-plan.ts`), asked for a write
|
|
9
|
+
* from an ordinary open, as `rafa effort schema` asks it: a runtime that
|
|
10
|
+
* reads and writes the store is refused whatever a read alone would be.
|
|
11
|
+
* The store's `user_version` and migration log are read on a read-only
|
|
12
|
+
* connection, so a store that needs no repair keeps its bytes.
|
|
13
|
+
*
|
|
14
|
+
* - A store the plan lets this rafa use is left alone: `current` when
|
|
15
|
+
* nothing is to be adopted or applied, `behind` when the next
|
|
16
|
+
* ordinary open brings it forward. A store logging migrations this
|
|
17
|
+
* rafa does not know, each additive, is one of these, since this
|
|
18
|
+
* rafa already reads and writes through them.
|
|
19
|
+
* - Five refusals are repaired by a rebuild ({@link REPAIRABLE_REASONS}):
|
|
20
|
+
* `pre-log-unreleased`, a store a branch build migrated past the
|
|
21
|
+
* legacy entries with no log; `gate-mismatch`, a logged store a
|
|
22
|
+
* build older than the log migrated; `edited`, a logged checksum
|
|
23
|
+
* this rafa holds otherwise; and `unknown-breaks-readers` and
|
|
24
|
+
* `unknown-breaks-writers`, a logged migration this rafa does not
|
|
25
|
+
* know which breaks it.
|
|
26
|
+
* - The other two, `breaking-out-of-order` and `breaking-pending`, are
|
|
27
|
+
* about migrations this rafa knows and has not applied, which a
|
|
28
|
+
* rebuild at those same migrations does not answer. They are refused
|
|
29
|
+
* with the plan's own text, which ends with the command to run next.
|
|
30
|
+
*
|
|
31
|
+
* Installing a rafa that knows the store's migrations comes first
|
|
32
|
+
* wherever it can: a rebuild leaves what only the newer schema holds in
|
|
33
|
+
* the backup.
|
|
34
|
+
*
|
|
35
|
+
* ## The rebuild
|
|
36
|
+
*
|
|
37
|
+
* A parallel file, `<store>.fix-<stamp>`, is brought to this rafa's
|
|
38
|
+
* migrations by the log-aware apply (`bringForward`, `bring-forward.ts`),
|
|
39
|
+
* so it holds exactly the tables this rafa writes and a migration log
|
|
40
|
+
* with a row for every id this rafa knows, each `applied_by` naming this
|
|
41
|
+
* runtime ({@link appliedByName}: its version, with `+dev:<checkout>`
|
|
42
|
+
* for a development build). The live store is attached, and every table
|
|
43
|
+
* the parallel file holds, the log apart, is filled from the live table
|
|
44
|
+
* of the same name over the columns the parallel table has. A table or
|
|
45
|
+
* column this rafa does not know is not copied, and is reported as left
|
|
46
|
+
* behind with how many rows or values it held. So are the unknown
|
|
47
|
+
* migrations the live log names: they stay in the backup only, since an
|
|
48
|
+
* object copied into the rebuild with no log row would make the release
|
|
49
|
+
* that ships its migration fail on `CREATE TABLE`. The rebuild is
|
|
50
|
+
* refused when the live store lacks a table or a column this rafa
|
|
51
|
+
* knows, since copying around it would lose what this rafa writes.
|
|
52
|
+
*
|
|
53
|
+
* The parallel file is checked before anything else happens: the row
|
|
54
|
+
* count of every table copied must equal the live one, SQLite's
|
|
55
|
+
* `integrity_check` must answer `ok`, and `planSchema` must find it a
|
|
56
|
+
* store this rafa uses as it is. Under `dryRun` it is then deleted, so a
|
|
57
|
+
* dry run can be repeated and leaves the directory as it found it.
|
|
58
|
+
* Otherwise the live store is written out to
|
|
59
|
+
* `<store>.v<user_version>-<stamp>.bak` with `VACUUM INTO`, its identity
|
|
60
|
+
* is carried onto the parallel file (`carryStoreIdentity`,
|
|
61
|
+
* `store-meta.ts`), and the parallel file is renamed into its place.
|
|
62
|
+
* The rebuild keeps the live store's origin, so the rows it writes next
|
|
63
|
+
* carry the same store id; a store whose next write would have minted
|
|
64
|
+
* anyway is carried nothing and mints after the swap. The backup holds
|
|
65
|
+
* the whole original, left-behind data included, and restoring it is
|
|
66
|
+
* renaming it back. It is a copy with an inode of its own, so once
|
|
67
|
+
* renamed back over the store its first writing open mints a new
|
|
68
|
+
* origin (`store-identity.ts`).
|
|
69
|
+
*
|
|
70
|
+
* The live file is only ever read. A rollback journal or write-ahead log
|
|
71
|
+
* beside it means a write in flight or interrupted, so the repair is
|
|
72
|
+
* refused while either is there. Any failure while building removes the
|
|
73
|
+
* parallel file and leaves the live one untouched.
|
|
74
|
+
*
|
|
75
|
+
* Those steps, from the in-flight refusal to the swap, are the
|
|
76
|
+
* build-aside convention of `rebuild-aside.ts`, which this module runs
|
|
77
|
+
* with its own file names and its own build. `SchemaFixRefusal` is that
|
|
78
|
+
* module's `RebuildRefusal` under the name the command catches.
|
|
79
|
+
*
|
|
80
|
+
* ## A development build never swaps
|
|
81
|
+
*
|
|
82
|
+
* A development build (`readRuntimeIdentity`,
|
|
83
|
+
* `packages/rafa/src/core/runtime/identity.ts`) builds, checks and deletes a rebuild
|
|
84
|
+
* under `dryRun`, and is refused the swap before anything is built: its
|
|
85
|
+
* migrations may be a branch's, which no installed runtime knows. The
|
|
86
|
+
* parallel file is brought forward with `builtAside`, so the open's own development-build question, which
|
|
87
|
+
* would refuse a file in the project's store directory, is not asked of
|
|
88
|
+
* it; this refusal is that question, asked of the swap.
|
|
89
|
+
*
|
|
90
|
+
* It opens the store itself rather than through `withSqliteStore`, so it
|
|
91
|
+
* runs the test guard (`guardTestProcess`, `location.ts`) itself too,
|
|
92
|
+
* before it reads or makes anything.
|
|
93
|
+
*/
|
|
94
|
+
import type { SqliteMigration } from './migrations.js';
|
|
95
|
+
import type { TableRows } from './rebuild-aside.js';
|
|
96
|
+
import type { LoggedMigration, StoreSchema } from './schema-plan.js';
|
|
97
|
+
import type { RuntimeIdentity } from '../../core/runtime/index.js';
|
|
98
|
+
import { Database } from 'bun:sqlite';
|
|
99
|
+
/** What the repair found, and what it did about it. */
|
|
100
|
+
export type FixSchemaStatus =
|
|
101
|
+
/** No store file: nothing to repair, and none is created. */
|
|
102
|
+
'missing'
|
|
103
|
+
/** This rafa uses it as it is: nothing to repair. */
|
|
104
|
+
| 'current'
|
|
105
|
+
/** This rafa uses it once the next ordinary open brings it forward: nothing to repair. */
|
|
106
|
+
| 'behind'
|
|
107
|
+
/** Refused for a reason a rebuild repairs, under `dryRun`: the rebuild was made, checked and deleted. */
|
|
108
|
+
| 'would-rebuild'
|
|
109
|
+
/** Refused for a reason a rebuild repairs: the rebuild was swapped in and the original kept as a backup. */
|
|
110
|
+
| 'rebuilt';
|
|
111
|
+
/** The `planSchema` refusals a rebuild at this rafa's migrations repairs, in the order they are tried. */
|
|
112
|
+
export declare const REPAIRABLE_REASONS: readonly ['pre-log-unreleased', 'gate-mismatch', 'edited', 'unknown-breaks-readers', 'unknown-breaks-writers'];
|
|
113
|
+
/** A refusal a rebuild repairs. */
|
|
114
|
+
export type RepairReason = typeof REPAIRABLE_REASONS[number];
|
|
115
|
+
/** A table the rebuild copies, with the rows it holds. */
|
|
116
|
+
export type KeptTable = TableRows;
|
|
117
|
+
/** A table only the newer schema knows, with the rows the backup keeps. */
|
|
118
|
+
export interface LeftTable {
|
|
119
|
+
readonly table: string;
|
|
120
|
+
readonly rows: number;
|
|
121
|
+
}
|
|
122
|
+
/** A column only the newer schema knows, with its non-null values the backup keeps. */
|
|
123
|
+
export interface LeftColumn {
|
|
124
|
+
readonly table: string;
|
|
125
|
+
readonly column: string;
|
|
126
|
+
readonly values: number;
|
|
127
|
+
}
|
|
128
|
+
/** The outcome of one repair. */
|
|
129
|
+
export interface FixSchemaResult {
|
|
130
|
+
/** The live store's file. */
|
|
131
|
+
readonly path: string;
|
|
132
|
+
readonly status: FixSchemaStatus;
|
|
133
|
+
/** The live store's `user_version`, or null when there is no file. */
|
|
134
|
+
readonly storeVersion: number | null;
|
|
135
|
+
/** Whether the live store holds a migration log. */
|
|
136
|
+
readonly logged: boolean;
|
|
137
|
+
/** The refusal a rebuild repairs, or null when the store needs none. */
|
|
138
|
+
readonly reason: RepairReason | null;
|
|
139
|
+
/** That refusal's text without its way out, since the repair is that way out, or null. */
|
|
140
|
+
readonly refusal: string | null;
|
|
141
|
+
/** The ids this rafa knows, in catalogue order: the ones a rebuild logs. */
|
|
142
|
+
readonly known: readonly string[];
|
|
143
|
+
/** Who a rebuild's log rows name as applying them. */
|
|
144
|
+
readonly appliedBy: string;
|
|
145
|
+
/** What the next ordinary open records, for `behind`: the log first when it adopts, then each pending id. */
|
|
146
|
+
readonly pending: readonly string[];
|
|
147
|
+
/**
|
|
148
|
+
* Logged migrations this rafa does not know. For `current` and
|
|
149
|
+
* `behind` each is one this rafa uses the store through; for a
|
|
150
|
+
* rebuild they stay in the backup only.
|
|
151
|
+
*/
|
|
152
|
+
readonly unknown: readonly LoggedMigration[];
|
|
153
|
+
/** Every table copied; empty unless a rebuild was made. */
|
|
154
|
+
readonly kept: readonly KeptTable[];
|
|
155
|
+
readonly leftTables: readonly LeftTable[];
|
|
156
|
+
readonly leftColumns: readonly LeftColumn[];
|
|
157
|
+
/** Where the original went, for `rebuilt` only. */
|
|
158
|
+
readonly backupPath: string | null;
|
|
159
|
+
}
|
|
160
|
+
/** What one repair is handed. */
|
|
161
|
+
export interface FixSchemaOptions {
|
|
162
|
+
/** The live store's file. */
|
|
163
|
+
readonly path: string;
|
|
164
|
+
/** Build and check the rebuild, then delete it instead of swapping it in. */
|
|
165
|
+
readonly dryRun: boolean;
|
|
166
|
+
/** Names the parallel and backup files, so two runs never collide. */
|
|
167
|
+
readonly stamp: string;
|
|
168
|
+
/** The migrations to rebuild at: this build's unless another list is passed. */
|
|
169
|
+
readonly migrations?: readonly SqliteMigration[];
|
|
170
|
+
/** Which build runs; `readRuntimeIdentity()` by default. */
|
|
171
|
+
readonly identity?: RuntimeIdentity;
|
|
172
|
+
/** The clock a rebuild's `applied_at` is read from. */
|
|
173
|
+
readonly now?: () => Date;
|
|
174
|
+
}
|
|
175
|
+
/** A repair refused before the live store was changed: the build-aside steps' refusal. */
|
|
176
|
+
export { RebuildRefusal as SchemaFixRefusal } from './rebuild-aside.js';
|
|
177
|
+
/** The user tables one attached schema holds, by name, the migration log apart. */
|
|
178
|
+
export declare function tableNames(db: Database, schema: string): string[];
|
|
179
|
+
/** The file's `user_version` and migration log, read on a read-only connection. */
|
|
180
|
+
export declare function readOnlySchema(path: string): StoreSchema;
|
|
181
|
+
/** Refuses a rebuild `planSchema` would not let this rafa use as it is. */
|
|
182
|
+
export declare function refuseUnusable(db: Database, parallelPath: string, migrations: readonly SqliteMigration[]): void;
|
|
183
|
+
/** Repairs the store at `options.path`. See the module note. */
|
|
184
|
+
export declare function fixStoreSchema(options: FixSchemaOptions): FixSchemaResult;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
|
-
EFFORT_KEY_PROJECTIONS,
|
|
4
|
-
TRUSTED_PERMISSIONS,
|
|
5
3
|
WIRE_FORMAT,
|
|
6
4
|
WIRE_VERSION,
|
|
7
5
|
WireExportRefusal,
|
|
@@ -12,14 +10,19 @@ import {
|
|
|
12
10
|
materialiseWirePayload,
|
|
13
11
|
mergeStore,
|
|
14
12
|
openNdjsonStore,
|
|
15
|
-
openSqliteStore,
|
|
16
13
|
selectEffortStore
|
|
17
|
-
} from "../../index-
|
|
14
|
+
} from "../../index-jh1r4kcn.js";
|
|
15
|
+
import {
|
|
16
|
+
EFFORT_KEY_PROJECTIONS,
|
|
17
|
+
TRUSTED_PERMISSIONS,
|
|
18
|
+
openSqliteStore
|
|
19
|
+
} from "../../index-cy98y87p.js";
|
|
20
|
+
import"../../index-2a7nb3an.js";
|
|
21
|
+
import"../../index-gvek4r8m.js";
|
|
18
22
|
import {
|
|
19
23
|
STORE_BACKENDS
|
|
20
|
-
} from "../../index-
|
|
21
|
-
import"../../index-
|
|
22
|
-
import"../../index-hf2k497d.js";
|
|
24
|
+
} from "../../index-ykggr043.js";
|
|
25
|
+
import"../../index-z1w83f81.js";
|
|
23
26
|
export {
|
|
24
27
|
selectEffortStore,
|
|
25
28
|
openSqliteStore,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** The variable that moves the store of one command line. */
|
|
2
|
+
export declare const EFFORT_DIR_VARIABLE = "RAFA_EFFORT_DIR";
|
|
3
|
+
/** The variable that marks a process as a test's, whatever its entry. */
|
|
4
|
+
export declare const TEST_PROCESS_VARIABLE = "RAFA_TEST";
|
|
5
|
+
/** An environment, as `process.env` holds one. */
|
|
6
|
+
export type StoreEnvironment = Readonly<Record<string, string | undefined>>;
|
|
7
|
+
/** What the test guard reads about the process it runs in. */
|
|
8
|
+
export interface TestProbe {
|
|
9
|
+
/** The entry file, `Bun.main` by default. */
|
|
10
|
+
readonly main: string;
|
|
11
|
+
/** The environment, `process.env` by default. */
|
|
12
|
+
readonly env: StoreEnvironment;
|
|
13
|
+
/** The temporary directory, `tmpdir()` by default. */
|
|
14
|
+
readonly tempDir: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The directory `RAFA_EFFORT_DIR` names, or null when it is unset or
|
|
18
|
+
* empty. Throws when it is relative, or names the store under
|
|
19
|
+
* `repoRoot`; see the module note.
|
|
20
|
+
*/
|
|
21
|
+
export declare function readEffortDirOverride(repoRoot: string, env?: StoreEnvironment): string | null;
|
|
22
|
+
/**
|
|
23
|
+
* The directory both backends keep the store under `repoRoot` in:
|
|
24
|
+
* `RAFA_EFFORT_DIR` when it is set, and `<root>/.rafa/effort` otherwise.
|
|
25
|
+
* Throws as {@link readEffortDirOverride} does.
|
|
26
|
+
*/
|
|
27
|
+
export declare function effortStoreDir(repoRoot: string, env?: StoreEnvironment): string;
|
|
28
|
+
/**
|
|
29
|
+
* True for a test process: an entry file ending in `.test.ts`, or
|
|
30
|
+
* `RAFA_TEST` set to exactly `1`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isTestProcess(main?: string, env?: StoreEnvironment): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* True when `path` is the temporary directory `tempDir` or lies under
|
|
35
|
+
* it, as spelled or with both resolved to their real paths; a `path`
|
|
36
|
+
* not made yet resolves through its nearest existing ancestor.
|
|
37
|
+
*/
|
|
38
|
+
export declare function isUnderTempDir(path: string, tempDir?: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Throws when a test process opens a store at `path` outside the
|
|
41
|
+
* temporary directory, and does nothing otherwise. Called before any
|
|
42
|
+
* file or directory is made; see the module note.
|
|
43
|
+
*/
|
|
44
|
+
export declare function guardTestProcess(path: string, probe?: TestProbe): void;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The merge's commit gaps: once the union (`merge-union.ts`) has brought
|
|
3
|
+
* another store's commits in, each one's `minutesSincePrevious` and that
|
|
4
|
+
* of the commit right after it in time are recomputed from the
|
|
5
|
+
* timestamps the store holds.
|
|
6
|
+
*
|
|
7
|
+
* ## Why only those rows
|
|
8
|
+
*
|
|
9
|
+
* A commit's gap is the minutes since the commit before it in time
|
|
10
|
+
* (`assignElapsedMinutes`, `src/effort/commits.ts`), measured with
|
|
11
|
+
* {@link minutesBetween}. A commit brought in between two commits here
|
|
12
|
+
* changes exactly two gaps: its own, which the other store measured
|
|
13
|
+
* against its own neighbours, and that of the commit after it, which now
|
|
14
|
+
* follows it rather than the commit before it. Every other gap keeps its
|
|
15
|
+
* predecessor. So each commit brought in touches at most two rows, and a
|
|
16
|
+
* row two of them name is written once. Where the store held the gaps a
|
|
17
|
+
* full recompute over its commits gives, it holds them over the union
|
|
18
|
+
* afterwards too; a gap a collection measured over a narrower range,
|
|
19
|
+
* such as the null of an incremental run's first commit, is left as it
|
|
20
|
+
* was unless a commit brought in lands right before it.
|
|
21
|
+
*
|
|
22
|
+
* There is no git call: the order and every gap come from the
|
|
23
|
+
* `timestamp` in each row's `row_json`.
|
|
24
|
+
*
|
|
25
|
+
* ## The order
|
|
26
|
+
*
|
|
27
|
+
* Commits are ordered by `Date.parse` of their timestamp, as
|
|
28
|
+
* `assignElapsedMinutes` orders them, and two at the same instant by
|
|
29
|
+
* sha, so a merge run from either side orders a tie alike. A row whose
|
|
30
|
+
* `row_json` does not parse, or whose timestamp does not, is left out of
|
|
31
|
+
* the order as `assignElapsedMinutes` leaves it out: it is no one's
|
|
32
|
+
* predecessor, and one brought in takes a null gap. The first commit in
|
|
33
|
+
* the order takes null.
|
|
34
|
+
*
|
|
35
|
+
* ## The write
|
|
36
|
+
*
|
|
37
|
+
* The gap lives inside `row_json`, so a recompute rewrites that column,
|
|
38
|
+
* `JSON.stringify` of the row with the one key replaced, which keeps the
|
|
39
|
+
* row's other keys and their order. A row whose gap already reads the
|
|
40
|
+
* recomputed value is not written. `MERGE_RULES` (`merge-rules.ts`)
|
|
41
|
+
* declares `commits.row_json` edited under {@link RECOMPUTED} for this
|
|
42
|
+
* statement, and `merge-conflicts.ts` compares two rows of one commit
|
|
43
|
+
* without the gap through {@link withoutGap}.
|
|
44
|
+
*/
|
|
45
|
+
import type { Database } from 'bun:sqlite';
|
|
46
|
+
/** The key of `commits.row_json` a merge recomputes. */
|
|
47
|
+
export declare const GAP_FIELD = "minutesSincePrevious";
|
|
48
|
+
/** One commit row whose gap the recompute rewrote. */
|
|
49
|
+
export interface GapRewrite {
|
|
50
|
+
readonly seq: number;
|
|
51
|
+
readonly sha: string;
|
|
52
|
+
readonly before: number | null;
|
|
53
|
+
readonly after: number | null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* `row_json` without its gap, for comparing two rows of one commit:
|
|
57
|
+
* the text unchanged when it is not a JSON object.
|
|
58
|
+
*/
|
|
59
|
+
export declare function withoutGap(rowJson: string): string;
|
|
60
|
+
/**
|
|
61
|
+
* Recomputes, in `db`, the gap of each commit whose local `seq` is in
|
|
62
|
+
* `inserted` and of the commit right after it in time, as the module
|
|
63
|
+
* note says, in one transaction. Answers each row it rewrote, in time
|
|
64
|
+
* order; a row whose gap was already right, or whose `row_json` is not
|
|
65
|
+
* a JSON object, is not written and not answered. A `seq` no commit
|
|
66
|
+
* holds is ignored.
|
|
67
|
+
*/
|
|
68
|
+
export declare function recomputeCommitGaps(db: Database, inserted: readonly number[]): GapRewrite[];
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The merge's second step: each pair of rows the union matched
|
|
3
|
+
* (`merge-union.ts`) is compared, an edited field the two stores
|
|
4
|
+
* disagree on is settled by its rule, and any difference no rule settles
|
|
5
|
+
* is recorded in `merge_conflicts`, both rows kept.
|
|
6
|
+
*
|
|
7
|
+
* ## Comparing a pair
|
|
8
|
+
*
|
|
9
|
+
* The two rows are compared on every column but `seq` and the origin
|
|
10
|
+
* pair. `seq` is local order, and the pair may differ in a match on
|
|
11
|
+
* identity by design: a row with a NULL origin on one side, or a commit
|
|
12
|
+
* two devices collected under an origin each, is the same row.
|
|
13
|
+
*
|
|
14
|
+
* - **Equal** content: nothing is written, and the pair is skipped.
|
|
15
|
+
* - **Differing only in edited fields** (`MERGE_RULES`, `merge-rules.ts`):
|
|
16
|
+
* each field follows its rule. Under {@link SET_ONCE} a filled value
|
|
17
|
+
* beats NULL from either side: NULL here takes the other store's value,
|
|
18
|
+
* and a value here is kept against NULL there; the pair is then
|
|
19
|
+
* skipped. Two different filled values mean one finding was filed
|
|
20
|
+
* twice: the value here is kept, and the incoming row is recorded with
|
|
21
|
+
* `field` naming the column. Under {@link RECOMPUTED} the two values
|
|
22
|
+
* are compared without the part the merge recomputes, through the
|
|
23
|
+
* field's entry in {@link RECOMPUTED_COMPARISONS}: `commits.row_json`
|
|
24
|
+
* without its `minutesSincePrevious` (`merge-commit-gaps.ts`). Equal
|
|
25
|
+
* so, the pair is skipped and the row here kept; the gap of a commit
|
|
26
|
+
* brought in is the merge's to rewrite, not a matched one's. Unequal,
|
|
27
|
+
* the rows differ outside every edited field, below.
|
|
28
|
+
* - **Any other difference**: the incoming row is recorded with `field`
|
|
29
|
+
* NULL, and the row here is left as it is, edited fields included. The
|
|
30
|
+
* same `(origin_store, origin_seq)` holding different content can only
|
|
31
|
+
* come from a copy the open did not catch, such as a cloned disk, and
|
|
32
|
+
* one UUID holding different content is as unexplained.
|
|
33
|
+
*
|
|
34
|
+
* A recorded conflict keeps the incoming row as the JSON of all its
|
|
35
|
+
* columns, its `seq` in the other store and its origin pair included,
|
|
36
|
+
* beside the local row's `seq`. Nothing blocks on it: the next pair is
|
|
37
|
+
* compared. A merge run again meets the same pair and records it again
|
|
38
|
+
* under its own merge id.
|
|
39
|
+
*
|
|
40
|
+
* ## The fill statements
|
|
41
|
+
*
|
|
42
|
+
* A fill is an `UPDATE` of a merged table, which `merge-rules.sweep.test.ts`
|
|
43
|
+
* reads from source and holds to `MERGE_RULES`; it can only read a
|
|
44
|
+
* statement whose table and column are spelled out. So each set-once
|
|
45
|
+
* field has its statement in {@link SET_ONCE_FILLS}, and a set-once
|
|
46
|
+
* field `MERGE_RULES` gains without one there makes the settling throw.
|
|
47
|
+
*
|
|
48
|
+
* The union's collisions, rows it refused on another UNIQUE key, are
|
|
49
|
+
* not matches and are not read here.
|
|
50
|
+
*/
|
|
51
|
+
import type { TableUnion } from './merge-union.js';
|
|
52
|
+
import type { Database } from 'bun:sqlite';
|
|
53
|
+
/** The merge these conflicts are recorded under. */
|
|
54
|
+
export interface ConflictTrail {
|
|
55
|
+
/** The `merges.id` of the merge. */
|
|
56
|
+
readonly mergeId: string;
|
|
57
|
+
/** When the conflicts are recorded, as an ISO timestamp. */
|
|
58
|
+
readonly recordedAt: string;
|
|
59
|
+
}
|
|
60
|
+
/** An edited field this store took from the other store. */
|
|
61
|
+
export interface FieldFill {
|
|
62
|
+
readonly localSeq: number;
|
|
63
|
+
readonly field: string;
|
|
64
|
+
}
|
|
65
|
+
/** A pair recorded in `merge_conflicts`. */
|
|
66
|
+
export interface RecordedConflict {
|
|
67
|
+
readonly localSeq: number;
|
|
68
|
+
/** The incoming row's `seq` in the other store. */
|
|
69
|
+
readonly incomingSeq: number;
|
|
70
|
+
/** The edited field whose two values differ, or null when the rows differ outside every edited field. */
|
|
71
|
+
readonly field: string | null;
|
|
72
|
+
}
|
|
73
|
+
/** What the conflict step did with one merged table's matched pairs. */
|
|
74
|
+
export interface TableSettlement {
|
|
75
|
+
readonly table: string;
|
|
76
|
+
/** The local `seq` of each pair left with equal content, before or after its fills. */
|
|
77
|
+
readonly skipped: readonly number[];
|
|
78
|
+
readonly filled: readonly FieldFill[];
|
|
79
|
+
readonly conflicts: readonly RecordedConflict[];
|
|
80
|
+
}
|
|
81
|
+
/** The statement filling one set-once field of a row left NULL, by `<table>.<column>`. */
|
|
82
|
+
export declare const SET_ONCE_FILLS: Readonly<Record<string, string>>;
|
|
83
|
+
/** The comparable form of one recomputed field's value, by `<table>.<column>`. */
|
|
84
|
+
export declare const RECOMPUTED_COMPARISONS: Readonly<Record<string, (value: string) => string>>;
|
|
85
|
+
/**
|
|
86
|
+
* Settles every pair `unions` matched, as the module note says: fills
|
|
87
|
+
* the set-once fields `local` holds NULL in, and records each conflict
|
|
88
|
+
* in `local`'s `merge_conflicts` under `trail`, in one transaction.
|
|
89
|
+
* `other` is only read. Answers one entry per union entry, in its order.
|
|
90
|
+
*/
|
|
91
|
+
export declare function settleMatches(local: Database, other: Database, unions: readonly TableUnion[], trail: ConflictTrail): TableSettlement[];
|