@variance-authority/tribunal 0.1.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.
Files changed (271) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/LICENSE +21 -0
  3. package/README.md +506 -0
  4. package/dist/__fixtures__/review.d.ts +47 -0
  5. package/dist/__fixtures__/review.js +145 -0
  6. package/dist/__fixtures__/review.js.map +1 -0
  7. package/dist/bindings.d.ts +134 -0
  8. package/dist/bindings.js +88 -0
  9. package/dist/bindings.js.map +1 -0
  10. package/dist/changelog.d.ts +132 -0
  11. package/dist/changelog.js +129 -0
  12. package/dist/changelog.js.map +1 -0
  13. package/dist/history-queries.d.ts +82 -0
  14. package/dist/history-queries.js +200 -0
  15. package/dist/history-queries.js.map +1 -0
  16. package/dist/history-rows.d.ts +42 -0
  17. package/dist/history-rows.js +148 -0
  18. package/dist/history-rows.js.map +1 -0
  19. package/dist/history.d.ts +40 -0
  20. package/dist/history.js +186 -0
  21. package/dist/history.js.map +1 -0
  22. package/dist/index.d.ts +13 -0
  23. package/dist/index.js +12 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/migration-steps.d.ts +32 -0
  26. package/dist/migration-steps.js +303 -0
  27. package/dist/migration-steps.js.map +1 -0
  28. package/dist/migrations.d.ts +58 -0
  29. package/dist/migrations.js +283 -0
  30. package/dist/migrations.js.map +1 -0
  31. package/dist/next.d.ts +94 -0
  32. package/dist/next.js +44 -0
  33. package/dist/next.js.map +1 -0
  34. package/dist/node/bin.d.ts +120 -0
  35. package/dist/node/bin.js +337 -0
  36. package/dist/node/bin.js.map +1 -0
  37. package/dist/node/bucket.d.ts +58 -0
  38. package/dist/node/bucket.js +142 -0
  39. package/dist/node/bucket.js.map +1 -0
  40. package/dist/node/database.d.ts +33 -0
  41. package/dist/node/database.js +157 -0
  42. package/dist/node/database.js.map +1 -0
  43. package/dist/node/index.d.ts +43 -0
  44. package/dist/node/index.js +43 -0
  45. package/dist/node/index.js.map +1 -0
  46. package/dist/node/serve.d.ts +81 -0
  47. package/dist/node/serve.js +193 -0
  48. package/dist/node/serve.js.map +1 -0
  49. package/dist/node/ui-assets.d.ts +47 -0
  50. package/dist/node/ui-assets.js +60 -0
  51. package/dist/node/ui-assets.js.map +1 -0
  52. package/dist/review-ingest.d.ts +24 -0
  53. package/dist/review-ingest.js +184 -0
  54. package/dist/review-ingest.js.map +1 -0
  55. package/dist/review-read.d.ts +87 -0
  56. package/dist/review-read.js +361 -0
  57. package/dist/review-read.js.map +1 -0
  58. package/dist/review-rows.d.ts +30 -0
  59. package/dist/review-rows.js +67 -0
  60. package/dist/review-rows.js.map +1 -0
  61. package/dist/review-types.d.ts +414 -0
  62. package/dist/review-types.js +2 -0
  63. package/dist/review-types.js.map +1 -0
  64. package/dist/review-write.d.ts +35 -0
  65. package/dist/review-write.js +86 -0
  66. package/dist/review-write.js.map +1 -0
  67. package/dist/review.d.ts +53 -0
  68. package/dist/review.fixtures.d.ts +47 -0
  69. package/dist/review.fixtures.js +145 -0
  70. package/dist/review.fixtures.js.map +1 -0
  71. package/dist/review.js +245 -0
  72. package/dist/review.js.map +1 -0
  73. package/dist/schema.d.ts +36 -0
  74. package/dist/schema.js +39 -0
  75. package/dist/schema.js.map +1 -0
  76. package/dist/store.d.ts +58 -0
  77. package/dist/store.js +278 -0
  78. package/dist/store.js.map +1 -0
  79. package/dist/testing.d.ts +38 -0
  80. package/dist/testing.js +92 -0
  81. package/dist/testing.js.map +1 -0
  82. package/dist/ui/__probe.d.ts +3 -0
  83. package/dist/ui/__probe.js +5 -0
  84. package/dist/ui/__probe.js.map +1 -0
  85. package/dist/ui/attribution.d.ts +46 -0
  86. package/dist/ui/attribution.js +79 -0
  87. package/dist/ui/attribution.js.map +1 -0
  88. package/dist/ui/because.d.ts +51 -0
  89. package/dist/ui/because.js +52 -0
  90. package/dist/ui/because.js.map +1 -0
  91. package/dist/ui/browser-entry.d.ts +2 -0
  92. package/dist/ui/browser-entry.js +34 -0
  93. package/dist/ui/browser-entry.js.map +1 -0
  94. package/dist/ui/builds.d.ts +43 -0
  95. package/dist/ui/builds.js +79 -0
  96. package/dist/ui/builds.js.map +1 -0
  97. package/dist/ui/carried.d.ts +73 -0
  98. package/dist/ui/carried.js +63 -0
  99. package/dist/ui/carried.js.map +1 -0
  100. package/dist/ui/cause.d.ts +73 -0
  101. package/dist/ui/cause.js +133 -0
  102. package/dist/ui/cause.js.map +1 -0
  103. package/dist/ui/change-story.d.ts +153 -0
  104. package/dist/ui/change-story.js +253 -0
  105. package/dist/ui/change-story.js.map +1 -0
  106. package/dist/ui/change.d.ts +51 -0
  107. package/dist/ui/change.js +125 -0
  108. package/dist/ui/change.js.map +1 -0
  109. package/dist/ui/client.d.ts +74 -0
  110. package/dist/ui/client.js +88 -0
  111. package/dist/ui/client.js.map +1 -0
  112. package/dist/ui/consumers.d.ts +80 -0
  113. package/dist/ui/consumers.js +125 -0
  114. package/dist/ui/consumers.js.map +1 -0
  115. package/dist/ui/control.d.ts +63 -0
  116. package/dist/ui/control.js +39 -0
  117. package/dist/ui/control.js.map +1 -0
  118. package/dist/ui/crossing.d.ts +55 -0
  119. package/dist/ui/crossing.js +74 -0
  120. package/dist/ui/crossing.js.map +1 -0
  121. package/dist/ui/declarations.d.ts +36 -0
  122. package/dist/ui/declarations.js +73 -0
  123. package/dist/ui/declarations.js.map +1 -0
  124. package/dist/ui/distance.d.ts +154 -0
  125. package/dist/ui/distance.js +180 -0
  126. package/dist/ui/distance.js.map +1 -0
  127. package/dist/ui/divergence.d.ts +40 -0
  128. package/dist/ui/divergence.js +176 -0
  129. package/dist/ui/divergence.js.map +1 -0
  130. package/dist/ui/docket.d.ts +39 -0
  131. package/dist/ui/docket.js +172 -0
  132. package/dist/ui/docket.js.map +1 -0
  133. package/dist/ui/findings.d.ts +42 -0
  134. package/dist/ui/findings.js +110 -0
  135. package/dist/ui/findings.js.map +1 -0
  136. package/dist/ui/foreseen.d.ts +55 -0
  137. package/dist/ui/foreseen.js +91 -0
  138. package/dist/ui/foreseen.js.map +1 -0
  139. package/dist/ui/glance.d.ts +101 -0
  140. package/dist/ui/glance.js +101 -0
  141. package/dist/ui/glance.js.map +1 -0
  142. package/dist/ui/grouping.d.ts +158 -0
  143. package/dist/ui/grouping.js +187 -0
  144. package/dist/ui/grouping.js.map +1 -0
  145. package/dist/ui/handed.d.ts +60 -0
  146. package/dist/ui/handed.js +122 -0
  147. package/dist/ui/handed.js.map +1 -0
  148. package/dist/ui/history.d.ts +88 -0
  149. package/dist/ui/history.js +170 -0
  150. package/dist/ui/history.js.map +1 -0
  151. package/dist/ui/holding.d.ts +84 -0
  152. package/dist/ui/holding.js +90 -0
  153. package/dist/ui/holding.js.map +1 -0
  154. package/dist/ui/impact.d.ts +31 -0
  155. package/dist/ui/impact.js +132 -0
  156. package/dist/ui/impact.js.map +1 -0
  157. package/dist/ui/index.d.ts +21 -0
  158. package/dist/ui/index.js +21 -0
  159. package/dist/ui/index.js.map +1 -0
  160. package/dist/ui/lead.d.ts +35 -0
  161. package/dist/ui/lead.js +34 -0
  162. package/dist/ui/lead.js.map +1 -0
  163. package/dist/ui/look.d.ts +109 -0
  164. package/dist/ui/look.js +248 -0
  165. package/dist/ui/look.js.map +1 -0
  166. package/dist/ui/mark.d.ts +11 -0
  167. package/dist/ui/mark.js +13 -0
  168. package/dist/ui/mark.js.map +1 -0
  169. package/dist/ui/moved.d.ts +121 -0
  170. package/dist/ui/moved.js +143 -0
  171. package/dist/ui/moved.js.map +1 -0
  172. package/dist/ui/navigation.d.ts +26 -0
  173. package/dist/ui/navigation.js +53 -0
  174. package/dist/ui/navigation.js.map +1 -0
  175. package/dist/ui/order.d.ts +113 -0
  176. package/dist/ui/order.js +226 -0
  177. package/dist/ui/order.js.map +1 -0
  178. package/dist/ui/origins.d.ts +42 -0
  179. package/dist/ui/origins.js +149 -0
  180. package/dist/ui/origins.js.map +1 -0
  181. package/dist/ui/outcome.d.ts +93 -0
  182. package/dist/ui/outcome.js +162 -0
  183. package/dist/ui/outcome.js.map +1 -0
  184. package/dist/ui/parted.d.ts +68 -0
  185. package/dist/ui/parted.js +101 -0
  186. package/dist/ui/parted.js.map +1 -0
  187. package/dist/ui/rail.d.ts +31 -0
  188. package/dist/ui/rail.js +21 -0
  189. package/dist/ui/rail.js.map +1 -0
  190. package/dist/ui/reach.d.ts +66 -0
  191. package/dist/ui/reach.js +185 -0
  192. package/dist/ui/reach.js.map +1 -0
  193. package/dist/ui/regions.d.ts +82 -0
  194. package/dist/ui/regions.js +67 -0
  195. package/dist/ui/regions.js.map +1 -0
  196. package/dist/ui/review.bundle.js +69 -0
  197. package/dist/ui/review.d.ts +97 -0
  198. package/dist/ui/review.js +39 -0
  199. package/dist/ui/review.js.map +1 -0
  200. package/dist/ui/root.d.ts +72 -0
  201. package/dist/ui/root.js +102 -0
  202. package/dist/ui/root.js.map +1 -0
  203. package/dist/ui/route.d.ts +95 -0
  204. package/dist/ui/route.js +114 -0
  205. package/dist/ui/route.js.map +1 -0
  206. package/dist/ui/run.d.ts +26 -0
  207. package/dist/ui/run.js +12 -0
  208. package/dist/ui/run.js.map +1 -0
  209. package/dist/ui/sense.d.ts +187 -0
  210. package/dist/ui/sense.js +222 -0
  211. package/dist/ui/sense.js.map +1 -0
  212. package/dist/ui/settled.d.ts +43 -0
  213. package/dist/ui/settled.js +59 -0
  214. package/dist/ui/settled.js.map +1 -0
  215. package/dist/ui/shell.d.ts +76 -0
  216. package/dist/ui/shell.js +65 -0
  217. package/dist/ui/shell.js.map +1 -0
  218. package/dist/ui/shift.d.ts +52 -0
  219. package/dist/ui/shift.js +98 -0
  220. package/dist/ui/shift.js.map +1 -0
  221. package/dist/ui/styles-docket.d.ts +18 -0
  222. package/dist/ui/styles-docket.js +281 -0
  223. package/dist/ui/styles-docket.js.map +1 -0
  224. package/dist/ui/styles-stage.d.ts +18 -0
  225. package/dist/ui/styles-stage.js +92 -0
  226. package/dist/ui/styles-stage.js.map +1 -0
  227. package/dist/ui/styles.d.ts +12 -0
  228. package/dist/ui/styles.js +482 -0
  229. package/dist/ui/styles.js.map +1 -0
  230. package/dist/ui/subject.d.ts +36 -0
  231. package/dist/ui/subject.js +138 -0
  232. package/dist/ui/subject.js.map +1 -0
  233. package/dist/ui/text.d.ts +129 -0
  234. package/dist/ui/text.js +164 -0
  235. package/dist/ui/text.js.map +1 -0
  236. package/dist/ui/variations.d.ts +37 -0
  237. package/dist/ui/variations.js +60 -0
  238. package/dist/ui/variations.js.map +1 -0
  239. package/dist/ui/viewer.d.ts +67 -0
  240. package/dist/ui/viewer.js +246 -0
  241. package/dist/ui/viewer.js.map +1 -0
  242. package/dist/worker-auth.d.ts +48 -0
  243. package/dist/worker-auth.js +74 -0
  244. package/dist/worker-auth.js.map +1 -0
  245. package/dist/worker-entry.d.ts +58 -0
  246. package/dist/worker-entry.js +36 -0
  247. package/dist/worker-entry.js.map +1 -0
  248. package/dist/worker-http.d.ts +39 -0
  249. package/dist/worker-http.js +111 -0
  250. package/dist/worker-http.js.map +1 -0
  251. package/dist/worker-input.d.ts +81 -0
  252. package/dist/worker-input.js +296 -0
  253. package/dist/worker-input.js.map +1 -0
  254. package/dist/worker.d.ts +91 -0
  255. package/dist/worker.js +287 -0
  256. package/dist/worker.js.map +1 -0
  257. package/mark.svg +30 -0
  258. package/migrations/0001_initial.sql +193 -0
  259. package/migrations/0002_changelog.sql +30 -0
  260. package/migrations/0003_accessibility.sql +8 -0
  261. package/migrations/0004_variations.sql +19 -0
  262. package/migrations/0005_reach.sql +26 -0
  263. package/migrations/0006_baseline-size.sql +7 -0
  264. package/migrations/0007_declarations.sql +7 -0
  265. package/migrations/0008_green-because.sql +7 -0
  266. package/migrations/0009_moved-bands.sql +6 -0
  267. package/migrations/0010_composition.sql +15 -0
  268. package/migrations/0011_movements.sql +21 -0
  269. package/migrations/0012_control-size.sql +6 -0
  270. package/package.json +110 -0
  271. package/wrangler.jsonc +45 -0
@@ -0,0 +1,148 @@
1
+ import { BANDS as FREQUENCY_BANDS, profileById, } from '@variance-authority/core';
2
+ import { BANDS, } from '@variance-authority/history';
3
+ /**
4
+ * An instant as a comparable number.
5
+ *
6
+ * Refuses rather than defaults. A row whose `at` cannot be parsed would land with
7
+ * a NULL ordering key, sort ahead of or behind everything depending on the query,
8
+ * and turn `12px → 20px` into `20px → 12px` — a confident sentence that is exactly
9
+ * backwards.
10
+ */
11
+ export function instant(at, what) {
12
+ const parsed = Date.parse(at);
13
+ if (Number.isNaN(parsed)) {
14
+ throw new Error(`${what} carries "${at}", which is not an ISO-8601 instant`);
15
+ }
16
+ return parsed;
17
+ }
18
+ export function text(row, column, what) {
19
+ const value = row[column];
20
+ if (typeof value !== 'string') {
21
+ throw new Error(`${what} has a \`${column}\` that is not text: ${describe(value)}`);
22
+ }
23
+ return value;
24
+ }
25
+ function optionalText(row, column, what) {
26
+ const value = row[column];
27
+ if (value === null || value === undefined)
28
+ return undefined;
29
+ if (typeof value !== 'string') {
30
+ throw new Error(`${what} has a \`${column}\` that is neither text nor null: ${describe(value)}`);
31
+ }
32
+ return value;
33
+ }
34
+ export function number(row, column, what) {
35
+ const value = row[column];
36
+ if (typeof value === 'bigint')
37
+ return Number(value);
38
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
39
+ throw new Error(`${what} has a \`${column}\` that is not a number: ${describe(value)}`);
40
+ }
41
+ return value;
42
+ }
43
+ function band(row, what) {
44
+ const value = text(row, 'band', what);
45
+ if (!BANDS.includes(value))
46
+ throw new Error(`${what} has an unknown band "${value}"`);
47
+ return value;
48
+ }
49
+ /** Checked against `core`'s table, so a new tier does not become a rejected row. */
50
+ function profile(row, what) {
51
+ const value = text(row, 'profile', what);
52
+ const known = profileById(value);
53
+ if (known === undefined)
54
+ throw new Error(`${what} has an unknown profile "${value}"`);
55
+ return value;
56
+ }
57
+ export function toObservation(row) {
58
+ const what = 'a stored observation';
59
+ const file = optionalText(row, 'file', what);
60
+ return {
61
+ project: text(row, 'project', what),
62
+ subject: text(row, 'subject', what),
63
+ component: text(row, 'component', what),
64
+ band: band(row, what),
65
+ hash: text(row, 'hash', what),
66
+ profile: profile(row, what),
67
+ commit: text(row, 'commit', what),
68
+ run: text(row, 'run', what),
69
+ at: text(row, 'at', what),
70
+ accepted: number(row, 'accepted', what) !== 0,
71
+ ...(file !== undefined ? { file } : {}),
72
+ };
73
+ }
74
+ export function toRunRecord(row) {
75
+ const what = 'a stored run';
76
+ const swept = row['swept'];
77
+ return {
78
+ project: text(row, 'project', what),
79
+ run: text(row, 'run', what),
80
+ commit: text(row, 'commit', what),
81
+ profile: profile(row, what),
82
+ at: text(row, 'at', what),
83
+ // Null stays absent. A run recorded before the column existed never said what
84
+ // it examined, and "did not sweep" is a claim — one that would make an old
85
+ // history look like a suite nobody ever swept.
86
+ ...(swept === null || swept === undefined ? {} : { swept: number(row, 'swept', what) !== 0 }),
87
+ };
88
+ }
89
+ /**
90
+ * One occurrence of a subject failing to read the same way twice.
91
+ *
92
+ * Transcribed from `server/sqlite-rows.ts`, nullability included: a component
93
+ * that could not be named comes back absent rather than as an empty string,
94
+ * because empty would read as a component whose name is nothing.
95
+ */
96
+ export function toInstability(row) {
97
+ const what = 'a stored instability';
98
+ const component = optionalText(row, 'component', what);
99
+ const stored = optionalText(row, 'band', what);
100
+ const absorbedBy = optionalText(row, 'absorbed_by', what);
101
+ return {
102
+ project: text(row, 'project', what),
103
+ subject: text(row, 'subject', what),
104
+ ...(component !== undefined ? { component } : {}),
105
+ ...(stored !== undefined ? { band: frequencyBand(stored, what) } : {}),
106
+ profile: profile(row, what),
107
+ commit: text(row, 'commit', what),
108
+ run: text(row, 'run', what),
109
+ at: text(row, 'at', what),
110
+ ...(absorbedBy !== undefined ? { absorbedBy } : {}),
111
+ };
112
+ }
113
+ /** A frequency band — how often that kind of thing changes — not a hash band. */
114
+ function frequencyBand(value, what) {
115
+ if (!FREQUENCY_BANDS.includes(value)) {
116
+ throw new Error(`${what} has an unknown frequency band "${value}"`);
117
+ }
118
+ return value;
119
+ }
120
+ export function toApproval(row) {
121
+ const what = 'a stored approval';
122
+ const by = optionalText(row, 'approver', what);
123
+ return {
124
+ project: text(row, 'project', what),
125
+ subject: text(row, 'subject', what),
126
+ run: text(row, 'run', what),
127
+ at: text(row, 'at', what),
128
+ ...(by !== undefined ? { by } : {}),
129
+ };
130
+ }
131
+ export function toTokenValue(row) {
132
+ const what = 'a stored token value';
133
+ return {
134
+ project: text(row, 'project', what),
135
+ token: text(row, 'token', what),
136
+ value: text(row, 'value', what),
137
+ commit: text(row, 'commit', what),
138
+ at: text(row, 'at', what),
139
+ };
140
+ }
141
+ function describe(value) {
142
+ if (value === undefined)
143
+ return 'absent';
144
+ if (value === null)
145
+ return 'null';
146
+ return `${typeof value} (${String(value).slice(0, 60)})`;
147
+ }
148
+ //# sourceMappingURL=history-rows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history-rows.js","sourceRoot":"","sources":["../src/history-rows.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BANDS as FREQUENCY_BANDS,\n profileById,\n type Digest,\n type ProfileId,\n} from '@variance-authority/core';\nimport {\n BANDS,\n type Approval,\n type Band,\n type FrequencyBand,\n type Instability,\n type Observation,\n type RunRecord,\n type TokenValue,\n} from '@variance-authority/history';\n\n/**\n * Reading a row back is validation, not a cast.\n *\n * SQLite is typed per value rather than per column, and `STRICT` only constrains\n * what this build writes. A row written by a future version, or by a person at a\n * `wrangler d1 execute` prompt, arrives here as whatever it is; casting it would\n * let a number become a component name and a NULL become the string \"null\" three\n * layers later.\n *\n * That argument is the whole of this file, and it is why it is a file: every\n * reader below is one refusal, and the same handful of them is used by the append\n * path, the filters, the slices and the reach query alike. `instant` sits with\n * them because it is the same refusal applied to a bound rather than to a column.\n * Nothing here knows any SQL, and [`history.ts`](./history.ts) knows nothing\n * about what a stored value is allowed to be.\n */\n\nexport type Row = Record<string, unknown>;\n\n/**\n * An instant as a comparable number.\n *\n * Refuses rather than defaults. A row whose `at` cannot be parsed would land with\n * a NULL ordering key, sort ahead of or behind everything depending on the query,\n * and turn `12px → 20px` into `20px → 12px` — a confident sentence that is exactly\n * backwards.\n */\nexport function instant(at: string, what: string): number {\n const parsed = Date.parse(at);\n if (Number.isNaN(parsed)) {\n throw new Error(`${what} carries \"${at}\", which is not an ISO-8601 instant`);\n }\n return parsed;\n}\n\nexport function text(row: Row, column: string, what: string): string {\n const value = row[column];\n if (typeof value !== 'string') {\n throw new Error(`${what} has a \\`${column}\\` that is not text: ${describe(value)}`);\n }\n return value;\n}\n\nfunction optionalText(row: Row, column: string, what: string): string | undefined {\n const value = row[column];\n if (value === null || value === undefined) return undefined;\n if (typeof value !== 'string') {\n throw new Error(`${what} has a \\`${column}\\` that is neither text nor null: ${describe(value)}`);\n }\n return value;\n}\n\nexport function number(row: Row, column: string, what: string): number {\n const value = row[column];\n if (typeof value === 'bigint') return Number(value);\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new Error(`${what} has a \\`${column}\\` that is not a number: ${describe(value)}`);\n }\n return value;\n}\n\nfunction band(row: Row, what: string): Band {\n const value = text(row, 'band', what);\n if (!BANDS.includes(value as Band)) throw new Error(`${what} has an unknown band \"${value}\"`);\n return value as Band;\n}\n\n/** Checked against `core`'s table, so a new tier does not become a rejected row. */\nfunction profile(row: Row, what: string): ProfileId {\n const value = text(row, 'profile', what);\n const known: unknown = profileById(value as ProfileId);\n if (known === undefined) throw new Error(`${what} has an unknown profile \"${value}\"`);\n return value as ProfileId;\n}\n\nexport function toObservation(row: Row): Observation {\n const what = 'a stored observation';\n const file = optionalText(row, 'file', what);\n\n return {\n project: text(row, 'project', what),\n subject: text(row, 'subject', what),\n component: text(row, 'component', what),\n band: band(row, what),\n hash: text(row, 'hash', what) as Digest,\n profile: profile(row, what),\n commit: text(row, 'commit', what),\n run: text(row, 'run', what),\n at: text(row, 'at', what),\n accepted: number(row, 'accepted', what) !== 0,\n ...(file !== undefined ? { file } : {}),\n };\n}\n\nexport function toRunRecord(row: Row): RunRecord {\n const what = 'a stored run';\n const swept = row['swept'];\n\n return {\n project: text(row, 'project', what),\n run: text(row, 'run', what),\n commit: text(row, 'commit', what),\n profile: profile(row, what),\n at: text(row, 'at', what),\n // Null stays absent. A run recorded before the column existed never said what\n // it examined, and \"did not sweep\" is a claim — one that would make an old\n // history look like a suite nobody ever swept.\n ...(swept === null || swept === undefined ? {} : { swept: number(row, 'swept', what) !== 0 }),\n };\n}\n\n/**\n * One occurrence of a subject failing to read the same way twice.\n *\n * Transcribed from `server/sqlite-rows.ts`, nullability included: a component\n * that could not be named comes back absent rather than as an empty string,\n * because empty would read as a component whose name is nothing.\n */\nexport function toInstability(row: Row): Instability {\n const what = 'a stored instability';\n const component = optionalText(row, 'component', what);\n const stored = optionalText(row, 'band', what);\n const absorbedBy = optionalText(row, 'absorbed_by', what);\n\n return {\n project: text(row, 'project', what),\n subject: text(row, 'subject', what),\n ...(component !== undefined ? { component } : {}),\n ...(stored !== undefined ? { band: frequencyBand(stored, what) } : {}),\n profile: profile(row, what),\n commit: text(row, 'commit', what),\n run: text(row, 'run', what),\n at: text(row, 'at', what),\n ...(absorbedBy !== undefined ? { absorbedBy } : {}),\n };\n}\n\n/** A frequency band — how often that kind of thing changes — not a hash band. */\nfunction frequencyBand(value: string, what: string): FrequencyBand {\n if (!FREQUENCY_BANDS.includes(value as FrequencyBand)) {\n throw new Error(`${what} has an unknown frequency band \"${value}\"`);\n }\n return value as FrequencyBand;\n}\n\nexport function toApproval(row: Row): Approval {\n const what = 'a stored approval';\n const by = optionalText(row, 'approver', what);\n\n return {\n project: text(row, 'project', what),\n subject: text(row, 'subject', what),\n run: text(row, 'run', what),\n at: text(row, 'at', what),\n ...(by !== undefined ? { by } : {}),\n };\n}\n\nexport function toTokenValue(row: Row): TokenValue {\n const what = 'a stored token value';\n return {\n project: text(row, 'project', what),\n token: text(row, 'token', what),\n value: text(row, 'value', what),\n commit: text(row, 'commit', what),\n at: text(row, 'at', what),\n };\n}\n\nfunction describe(value: unknown): string {\n if (value === undefined) return 'absent';\n if (value === null) return 'null';\n return `${typeof value} (${String(value).slice(0, 60)})`;\n}\n"]}
@@ -0,0 +1,40 @@
1
+ import { type HistoryBackend } from '@variance-authority/server';
2
+ import { type D1Like } from './bindings.js';
3
+ /**
4
+ * The history record, on D1.
5
+ *
6
+ * A second implementation of `HistoryBackend`, and deliberately a *transcription*
7
+ * of [`server/backend-sqlite.ts`](../../server/src/backend-sqlite.ts) rather than
8
+ * a fresh design. The seam exists so that replacing the engine changes one file
9
+ * and not what a row means; the way to honour that is for the two files to be
10
+ * boringly alike — same filters, same limit arithmetic, same correlated subquery
11
+ * for `arrived`, same refusal to cast a row it read back.
12
+ *
13
+ * **No arithmetic happens here.** Churn, journeys and reach are computed by
14
+ * `churnFrom`/`journeyFrom`/`reachFrom` in `@variance-authority/server`, over the
15
+ * rows this returns. That is what stops two backends disagreeing about what a
16
+ * number means, and it is why this file has no idea what a rate is.
17
+ *
18
+ * ## The two places D1 is not SQLite-in-a-process
19
+ *
20
+ * **No `BEGIN IMMEDIATE`.** Atomicity comes from `batch`, which D1 documents as
21
+ * one implicit transaction that rolls back entirely on failure. Every write here
22
+ * is one batch, so a run and its rows still land together or not at all.
23
+ *
24
+ * **No serialization around a read-then-write.** The SQLite backend takes the
25
+ * write lock before checking whether a run id is already registered; two Workers
26
+ * cannot. So the check is not what enforces lineage — the `WHERE NOT EXISTS`
27
+ * guard on the insert is, and the unique index behind it. The read survives only
28
+ * to produce a sentence naming both commits, because a caller who gets
29
+ * `UNIQUE constraint failed` has to come and read this file to find out what
30
+ * happened.
31
+ *
32
+ * ## What is next door
33
+ *
34
+ * Reading a stored value back is [`history-rows.ts`](./history-rows.ts), and it
35
+ * is a separate file for the same reason no arithmetic happens here: what a row
36
+ * is allowed to be is not a property of any one query, and this file knows
37
+ * nothing about it.
38
+ */
39
+ export declare function createD1Backend(db: D1Like): HistoryBackend;
40
+ //# sourceMappingURL=history.d.ts.map
@@ -0,0 +1,186 @@
1
+ import { HistoryWriteConflict, } from '@variance-authority/server';
2
+ import { requireD1 } from './bindings.js';
3
+ import { areaFilter, componentFilter, currentRows, currentTokenRows, reachRows, slice, subjectFilter, tokenFilter, windowFilter, } from './history-queries.js';
4
+ import { instant, text, toApproval, toInstability, toObservation, toRunRecord, toTokenValue, } from './history-rows.js';
5
+ /**
6
+ * The history record, on D1.
7
+ *
8
+ * A second implementation of `HistoryBackend`, and deliberately a *transcription*
9
+ * of [`server/backend-sqlite.ts`](../../server/src/backend-sqlite.ts) rather than
10
+ * a fresh design. The seam exists so that replacing the engine changes one file
11
+ * and not what a row means; the way to honour that is for the two files to be
12
+ * boringly alike — same filters, same limit arithmetic, same correlated subquery
13
+ * for `arrived`, same refusal to cast a row it read back.
14
+ *
15
+ * **No arithmetic happens here.** Churn, journeys and reach are computed by
16
+ * `churnFrom`/`journeyFrom`/`reachFrom` in `@variance-authority/server`, over the
17
+ * rows this returns. That is what stops two backends disagreeing about what a
18
+ * number means, and it is why this file has no idea what a rate is.
19
+ *
20
+ * ## The two places D1 is not SQLite-in-a-process
21
+ *
22
+ * **No `BEGIN IMMEDIATE`.** Atomicity comes from `batch`, which D1 documents as
23
+ * one implicit transaction that rolls back entirely on failure. Every write here
24
+ * is one batch, so a run and its rows still land together or not at all.
25
+ *
26
+ * **No serialization around a read-then-write.** The SQLite backend takes the
27
+ * write lock before checking whether a run id is already registered; two Workers
28
+ * cannot. So the check is not what enforces lineage — the `WHERE NOT EXISTS`
29
+ * guard on the insert is, and the unique index behind it. The read survives only
30
+ * to produce a sentence naming both commits, because a caller who gets
31
+ * `UNIQUE constraint failed` has to come and read this file to find out what
32
+ * happened.
33
+ *
34
+ * ## What is next door
35
+ *
36
+ * Reading a stored value back is [`history-rows.ts`](./history-rows.ts), and it
37
+ * is a separate file for the same reason no arithmetic happens here: what a row
38
+ * is allowed to be is not a property of any one query, and this file knows
39
+ * nothing about it.
40
+ */
41
+ export function createD1Backend(db) {
42
+ requireD1(db);
43
+ return {
44
+ async append(run, observations, tokens, instabilities = []) {
45
+ const registered = await registeredCommit(db, run);
46
+ if (registered !== null && registered !== run.commit)
47
+ throw conflict(run, registered);
48
+ const shipped = carriesAnApproval(observations);
49
+ const statements = [
50
+ // One statement, and it is the lineage rule rather than a convenience.
51
+ // If a row already exists for this `(project, run, profile)` with *this*
52
+ // commit, the `NOT EXISTS` is false and nothing is inserted — re-appending
53
+ // a run is legitimate, because a caller whose components were approved
54
+ // separately writes twice, and a run counted twice halves every rate
55
+ // derived from it forever.
56
+ //
57
+ // If a row exists with a *different* commit, the `NOT EXISTS` is true, the
58
+ // insert proceeds, and `runs_identity` refuses it — which aborts the whole
59
+ // batch. That is the only part of this that holds under two concurrent
60
+ // Workers, and it is why the check above is a message rather than a guard.
61
+ db
62
+ .prepare(`INSERT INTO runs (project, run, "commit", profile, at, at_ms, swept)
63
+ SELECT ?, ?, ?, ?, ?, ?, ?
64
+ WHERE NOT EXISTS (
65
+ SELECT 1 FROM runs
66
+ WHERE project = ? AND run = ? AND profile = ? AND "commit" = ?)`)
67
+ .bind(run.project, run.run, run.commit, run.profile, run.at, instant(run.at, 'run'),
68
+ // NULL when the caller did not say. Transcribed with its reason: a
69
+ // run stored as having examined nothing puts a denominator under a
70
+ // flake rate that nobody measured.
71
+ run.swept === undefined ? null : run.swept ? 1 : 0, run.project, run.run, run.profile, run.commit),
72
+ ];
73
+ for (const row of observations) {
74
+ statements.push(db
75
+ .prepare(`INSERT INTO observations
76
+ (project, subject, component, band, hash, profile, "commit", run, at, at_ms,
77
+ accepted, file)
78
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
79
+ .bind(row.project, row.subject, row.component, row.band, row.hash, row.profile, row.commit, row.run, row.at, instant(row.at, 'observation'), row.accepted ? 1 : 0, row.file ?? null));
80
+ }
81
+ for (const row of tokens) {
82
+ statements.push(db
83
+ .prepare(`INSERT INTO token_values (project, token, value, "commit", at, at_ms, run, accepted)
84
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
85
+ .bind(row.project, row.token, row.value, row.commit, row.at, instant(row.at, 'token value'), run.run, shipped ? 1 : 0));
86
+ }
87
+ for (const row of instabilities) {
88
+ statements.push(db
89
+ .prepare(`INSERT INTO instabilities
90
+ (project, subject, component, band, profile, "commit", run, at, at_ms, absorbed_by)
91
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
92
+ .bind(row.project, row.subject,
93
+ // Null, not the empty string: readings that could not be resolved
94
+ // to a component named nobody, which is not the same as the
95
+ // disagreement belonging to no component.
96
+ row.component ?? null, row.band ?? null, row.profile, row.commit, row.run, row.at, instant(row.at, 'instability'), row.absorbedBy ?? null));
97
+ }
98
+ try {
99
+ await db.batch(statements);
100
+ }
101
+ catch (error) {
102
+ // The guard above fired between the check and the write. Re-read so the
103
+ // operator gets the two commits rather than an index name.
104
+ const current = await registeredCommit(db, run).catch(() => null);
105
+ if (current !== null && current !== run.commit)
106
+ throw conflict(run, current);
107
+ throw error;
108
+ }
109
+ },
110
+ async appendApprovals(approvals) {
111
+ if (approvals.length === 0)
112
+ return;
113
+ // `OR IGNORE` against `approvals_identity`, transcribed: approving twice is
114
+ // approving once, and a refusal would turn a second click into a failure.
115
+ await db.batch(approvals.map((approval) => db
116
+ .prepare(`INSERT OR IGNORE INTO approvals (project, subject, run, at, at_ms, approver)
117
+ VALUES (?, ?, ?, ?, ?, ?)`)
118
+ .bind(approval.project, approval.subject, approval.run, approval.at, instant(approval.at, 'approval'), approval.by ?? null)));
119
+ },
120
+ async approvalsOf(query) {
121
+ return slice(db, 'approvals', windowFilter(query), query.limit, toApproval);
122
+ },
123
+ async currentOf(query) {
124
+ return currentRows(db, query);
125
+ },
126
+ async currentTokens(query) {
127
+ return currentTokenRows(db, query);
128
+ },
129
+ async lastObservation(query) {
130
+ const filter = areaFilter(query);
131
+ const row = await db
132
+ .prepare(`SELECT * FROM observations WHERE 1 = 1${filter.sql} ORDER BY at_ms DESC, rowid DESC LIMIT 1`)
133
+ .bind(...filter.params)
134
+ .first();
135
+ return row === null ? null : toObservation(row);
136
+ },
137
+ async runsIn(query) {
138
+ return slice(db, 'runs', windowFilter(query), query.limit, toRunRecord);
139
+ },
140
+ async observationsOf(query) {
141
+ return slice(db, 'observations', componentFilter(query), query.limit, toObservation);
142
+ },
143
+ async instabilitiesOf(query) {
144
+ return slice(db, 'instabilities', subjectFilter(query), query.limit, toInstability);
145
+ },
146
+ async valuesOf(query) {
147
+ return slice(db, 'token_values', tokenFilter(query), query.limit, toTokenValue);
148
+ },
149
+ async reachOf(query) {
150
+ return reachRows(db, query);
151
+ },
152
+ /**
153
+ * Nothing to close.
154
+ *
155
+ * A binding is not a handle this code opened, and a Worker that closed one
156
+ * would break every other request sharing the isolate. Present because the
157
+ * interface has it, and empty for the same reason a stateless service has no
158
+ * shutdown.
159
+ */
160
+ async close() { },
161
+ };
162
+ }
163
+ async function registeredCommit(db, run) {
164
+ const row = await db
165
+ .prepare('SELECT "commit" FROM runs WHERE project = ? AND run = ? AND profile = ?')
166
+ .bind(run.project, run.run, run.profile)
167
+ .first();
168
+ return row === null ? null : text(row, 'commit', 'a stored run');
169
+ }
170
+ function conflict(run, recorded) {
171
+ return new HistoryWriteConflict(`run "${run.run}" of project "${run.project}" under ${run.profile} is already recorded ` +
172
+ `at commit ${recorded}; this write claims ${run.commit}. A run id that points at two ` +
173
+ 'commits makes every question asked by lineage unanswerable, so the write is refused ' +
174
+ 'rather than appended alongside it');
175
+ }
176
+ /**
177
+ * Whether the write itself already carried an approval.
178
+ *
179
+ * Transcribed from the SQLite backend, and provenance about the write in both.
180
+ * Which values a journey is made of is decided by `journeyFrom`, which both
181
+ * backends share and which asks the approvals table.
182
+ */
183
+ function carriesAnApproval(observations) {
184
+ return observations.length === 0 || observations.some((row) => row.accepted);
185
+ }
186
+ //# sourceMappingURL=history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.js","sourceRoot":"","sources":["../src/history.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n Approval,\n Instability,\n Observation,\n RunRecord,\n TokenValue,\n} from '@variance-authority/history';\nimport {\n HistoryWriteConflict,\n type HistoryBackend,\n type ReachRows,\n type Slice,\n} from '@variance-authority/server';\nimport { requireD1, type D1Like, type D1PreparedLike } from './bindings.js';\nimport {\n areaFilter,\n componentFilter,\n currentRows,\n currentTokenRows,\n reachRows,\n slice,\n subjectFilter,\n tokenFilter,\n windowFilter,\n} from './history-queries.js';\nimport {\n instant,\n text,\n toApproval,\n toInstability,\n toObservation,\n toRunRecord,\n toTokenValue,\n type Row,\n} from './history-rows.js';\n\n/**\n * The history record, on D1.\n *\n * A second implementation of `HistoryBackend`, and deliberately a *transcription*\n * of [`server/backend-sqlite.ts`](../../server/src/backend-sqlite.ts) rather than\n * a fresh design. The seam exists so that replacing the engine changes one file\n * and not what a row means; the way to honour that is for the two files to be\n * boringly alike — same filters, same limit arithmetic, same correlated subquery\n * for `arrived`, same refusal to cast a row it read back.\n *\n * **No arithmetic happens here.** Churn, journeys and reach are computed by\n * `churnFrom`/`journeyFrom`/`reachFrom` in `@variance-authority/server`, over the\n * rows this returns. That is what stops two backends disagreeing about what a\n * number means, and it is why this file has no idea what a rate is.\n *\n * ## The two places D1 is not SQLite-in-a-process\n *\n * **No `BEGIN IMMEDIATE`.** Atomicity comes from `batch`, which D1 documents as\n * one implicit transaction that rolls back entirely on failure. Every write here\n * is one batch, so a run and its rows still land together or not at all.\n *\n * **No serialization around a read-then-write.** The SQLite backend takes the\n * write lock before checking whether a run id is already registered; two Workers\n * cannot. So the check is not what enforces lineage — the `WHERE NOT EXISTS`\n * guard on the insert is, and the unique index behind it. The read survives only\n * to produce a sentence naming both commits, because a caller who gets\n * `UNIQUE constraint failed` has to come and read this file to find out what\n * happened.\n *\n * ## What is next door\n *\n * Reading a stored value back is [`history-rows.ts`](./history-rows.ts), and it\n * is a separate file for the same reason no arithmetic happens here: what a row\n * is allowed to be is not a property of any one query, and this file knows\n * nothing about it.\n */\n\nexport function createD1Backend(db: D1Like): HistoryBackend {\n requireD1(db);\n\n return {\n async append(run, observations, tokens, instabilities = []): Promise<void> {\n const registered = await registeredCommit(db, run);\n if (registered !== null && registered !== run.commit) throw conflict(run, registered);\n\n const shipped = carriesAnApproval(observations);\n const statements: D1PreparedLike[] = [\n // One statement, and it is the lineage rule rather than a convenience.\n // If a row already exists for this `(project, run, profile)` with *this*\n // commit, the `NOT EXISTS` is false and nothing is inserted — re-appending\n // a run is legitimate, because a caller whose components were approved\n // separately writes twice, and a run counted twice halves every rate\n // derived from it forever.\n //\n // If a row exists with a *different* commit, the `NOT EXISTS` is true, the\n // insert proceeds, and `runs_identity` refuses it — which aborts the whole\n // batch. That is the only part of this that holds under two concurrent\n // Workers, and it is why the check above is a message rather than a guard.\n db\n .prepare(\n `INSERT INTO runs (project, run, \"commit\", profile, at, at_ms, swept)\n SELECT ?, ?, ?, ?, ?, ?, ?\n WHERE NOT EXISTS (\n SELECT 1 FROM runs\n WHERE project = ? AND run = ? AND profile = ? AND \"commit\" = ?)`,\n )\n .bind(\n run.project,\n run.run,\n run.commit,\n run.profile,\n run.at,\n instant(run.at, 'run'),\n // NULL when the caller did not say. Transcribed with its reason: a\n // run stored as having examined nothing puts a denominator under a\n // flake rate that nobody measured.\n run.swept === undefined ? null : run.swept ? 1 : 0,\n run.project,\n run.run,\n run.profile,\n run.commit,\n ),\n ];\n\n for (const row of observations) {\n statements.push(\n db\n .prepare(\n `INSERT INTO observations\n (project, subject, component, band, hash, profile, \"commit\", run, at, at_ms,\n accepted, file)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .bind(\n row.project,\n row.subject,\n row.component,\n row.band,\n row.hash,\n row.profile,\n row.commit,\n row.run,\n row.at,\n instant(row.at, 'observation'),\n row.accepted ? 1 : 0,\n row.file ?? null,\n ),\n );\n }\n\n for (const row of tokens) {\n statements.push(\n db\n .prepare(\n `INSERT INTO token_values (project, token, value, \"commit\", at, at_ms, run, accepted)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .bind(\n row.project,\n row.token,\n row.value,\n row.commit,\n row.at,\n instant(row.at, 'token value'),\n run.run,\n shipped ? 1 : 0,\n ),\n );\n }\n\n for (const row of instabilities) {\n statements.push(\n db\n .prepare(\n `INSERT INTO instabilities\n (project, subject, component, band, profile, \"commit\", run, at, at_ms, absorbed_by)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .bind(\n row.project,\n row.subject,\n // Null, not the empty string: readings that could not be resolved\n // to a component named nobody, which is not the same as the\n // disagreement belonging to no component.\n row.component ?? null,\n row.band ?? null,\n row.profile,\n row.commit,\n row.run,\n row.at,\n instant(row.at, 'instability'),\n row.absorbedBy ?? null,\n ),\n );\n }\n\n try {\n await db.batch(statements);\n } catch (error) {\n // The guard above fired between the check and the write. Re-read so the\n // operator gets the two commits rather than an index name.\n const current = await registeredCommit(db, run).catch(() => null);\n if (current !== null && current !== run.commit) throw conflict(run, current);\n throw error;\n }\n },\n\n async appendApprovals(approvals): Promise<void> {\n if (approvals.length === 0) return;\n\n // `OR IGNORE` against `approvals_identity`, transcribed: approving twice is\n // approving once, and a refusal would turn a second click into a failure.\n await db.batch(\n approvals.map((approval) =>\n db\n .prepare(\n `INSERT OR IGNORE INTO approvals (project, subject, run, at, at_ms, approver)\n VALUES (?, ?, ?, ?, ?, ?)`,\n )\n .bind(\n approval.project,\n approval.subject,\n approval.run,\n approval.at,\n instant(approval.at, 'approval'),\n approval.by ?? null,\n ),\n ),\n );\n },\n\n async approvalsOf(query): Promise<Slice<Approval>> {\n return slice(db, 'approvals', windowFilter(query), query.limit, toApproval);\n },\n\n async currentOf(query): Promise<readonly Observation[]> {\n return currentRows(db, query);\n },\n\n async currentTokens(query): Promise<readonly TokenValue[]> {\n return currentTokenRows(db, query);\n },\n\n async lastObservation(query): Promise<Observation | null> {\n const filter = areaFilter(query);\n const row = await db\n .prepare(\n `SELECT * FROM observations WHERE 1 = 1${filter.sql} ORDER BY at_ms DESC, rowid DESC LIMIT 1`,\n )\n .bind(...filter.params)\n .first<Row>();\n\n return row === null ? null : toObservation(row);\n },\n\n async runsIn(query): Promise<Slice<RunRecord>> {\n return slice(db, 'runs', windowFilter(query), query.limit, toRunRecord);\n },\n\n async observationsOf(query): Promise<Slice<Observation>> {\n return slice(db, 'observations', componentFilter(query), query.limit, toObservation);\n },\n\n async instabilitiesOf(query): Promise<Slice<Instability>> {\n return slice(db, 'instabilities', subjectFilter(query), query.limit, toInstability);\n },\n\n async valuesOf(query): Promise<Slice<TokenValue>> {\n return slice(db, 'token_values', tokenFilter(query), query.limit, toTokenValue);\n },\n\n async reachOf(query): Promise<ReachRows> {\n return reachRows(db, query);\n },\n\n /**\n * Nothing to close.\n *\n * A binding is not a handle this code opened, and a Worker that closed one\n * would break every other request sharing the isolate. Present because the\n * interface has it, and empty for the same reason a stateless service has no\n * shutdown.\n */\n async close(): Promise<void> {},\n };\n}\n\nasync function registeredCommit(db: D1Like, run: RunRecord): Promise<string | null> {\n const row = await db\n .prepare('SELECT \"commit\" FROM runs WHERE project = ? AND run = ? AND profile = ?')\n .bind(run.project, run.run, run.profile)\n .first<Row>();\n\n return row === null ? null : text(row, 'commit', 'a stored run');\n}\n\nfunction conflict(run: RunRecord, recorded: string): HistoryWriteConflict {\n return new HistoryWriteConflict(\n `run \"${run.run}\" of project \"${run.project}\" under ${run.profile} is already recorded ` +\n `at commit ${recorded}; this write claims ${run.commit}. A run id that points at two ` +\n 'commits makes every question asked by lineage unanswerable, so the write is refused ' +\n 'rather than appended alongside it',\n );\n}\n\n/**\n * Whether the write itself already carried an approval.\n *\n * Transcribed from the SQLite backend, and provenance about the write in both.\n * Which values a journey is made of is decided by `journeyFrom`, which both\n * backends share and which asks the approvals table.\n */\nfunction carriesAnApproval(observations: readonly Observation[]): boolean {\n return observations.length === 0 || observations.some((row) => row.accepted);\n}\n"]}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The contract half — what a deployment is made of, with nothing that runs.
3
+ *
4
+ * The entrypoint an operator imports to declare their bindings and apply their
5
+ * schema. Everything that touches a request is behind `./store`, `./history`,
6
+ * `./review` and `./worker`, so a build script that only needs the SQL does not
7
+ * pull in the router, and a Worker that only serves baselines does not pull in
8
+ * React.
9
+ */
10
+ export type { D1Like, D1PreparedLike, D1Value, R2Like, R2ObjectLike, TribunalBindings, } from './bindings.js';
11
+ export { base64Of, bytesOf } from './bindings.js';
12
+ export { INITIAL_VERSION, MIGRATIONS, SCHEMA, SCHEMA_VERSION, applySchema } from './schema.js';
13
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The contract half — what a deployment is made of, with nothing that runs.
3
+ *
4
+ * The entrypoint an operator imports to declare their bindings and apply their
5
+ * schema. Everything that touches a request is behind `./store`, `./history`,
6
+ * `./review` and `./worker`, so a build script that only needs the SQL does not
7
+ * pull in the router, and a Worker that only serves baselines does not pull in
8
+ * React.
9
+ */
10
+ export { base64Of, bytesOf } from './bindings.js';
11
+ export { INITIAL_VERSION, MIGRATIONS, SCHEMA, SCHEMA_VERSION, applySchema } from './schema.js';
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAUH,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC","sourcesContent":["/**\n * The contract half — what a deployment is made of, with nothing that runs.\n *\n * The entrypoint an operator imports to declare their bindings and apply their\n * schema. Everything that touches a request is behind `./store`, `./history`,\n * `./review` and `./worker`, so a build script that only needs the SQL does not\n * pull in the router, and a Worker that only serves baselines does not pull in\n * React.\n */\n\nexport type {\n D1Like,\n D1PreparedLike,\n D1Value,\n R2Like,\n R2ObjectLike,\n TribunalBindings,\n} from './bindings.js';\nexport { base64Of, bytesOf } from './bindings.js';\nexport { INITIAL_VERSION, MIGRATIONS, SCHEMA, SCHEMA_VERSION, applySchema } from './schema.js';\n"]}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Every shape this database has taken since it shipped, one entry per version.
3
+ *
4
+ * Apart from [`migrations.ts`](./migrations.js), which holds the frozen initial
5
+ * set, because the two are edited under opposite rules: that file must never
6
+ * change again, and this one only ever grows. Keeping them in one module meant
7
+ * every new table was appended a screen below a block carrying a comment saying
8
+ * it was frozen.
9
+ *
10
+ * The reading of both — which version they land on, and the batch that applies
11
+ * them — is in [`schema.ts`](./schema.js).
12
+ */
13
+ /**
14
+ * One entry per version after `INITIAL_VERSION`, in order.
15
+ *
16
+ * A database that is already deployed cannot be given a new table by editing the
17
+ * statements that created it — `wrangler d1 migrations apply` tracks which files
18
+ * it has run, and a rewritten `0001` is a file it will never run again. So the
19
+ * initial set is frozen at the version it shipped at and every later shape is a
20
+ * step, generated into its own `.sql` beside it.
21
+ *
22
+ * Each step ends by writing its own version, so a database is never at a version
23
+ * whose shape it does not have — and a fresh database applying the initial set
24
+ * and every step in order arrives at exactly the same place as one that was
25
+ * deployed three versions ago.
26
+ *
27
+ * Additive only. A step that dropped or rewrote a column would be asking an
28
+ * append-only store to forget something, which is the one thing every trigger in
29
+ * this file exists to refuse.
30
+ */
31
+ export declare const MIGRATIONS: readonly (readonly string[])[];
32
+ //# sourceMappingURL=migration-steps.d.ts.map