@thomasfosterau/pulpit 0.0.0 → 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.
- package/LICENSE +21 -0
- package/README.md +1271 -1
- package/dist/blobs.d.ts +249 -0
- package/dist/blobs.d.ts.map +1 -0
- package/dist/blobs.js +193 -0
- package/dist/blobs.js.map +1 -0
- package/dist/budgets.d.ts +255 -0
- package/dist/budgets.d.ts.map +1 -0
- package/dist/budgets.js +211 -0
- package/dist/budgets.js.map +1 -0
- package/dist/cache.d.ts +154 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +118 -0
- package/dist/cache.js.map +1 -0
- package/dist/collections.d.ts +242 -0
- package/dist/collections.d.ts.map +1 -0
- package/dist/collections.js +215 -0
- package/dist/collections.js.map +1 -0
- package/dist/comments.d.ts +212 -0
- package/dist/comments.d.ts.map +1 -0
- package/dist/comments.js +192 -0
- package/dist/comments.js.map +1 -0
- package/dist/content.d.ts +231 -0
- package/dist/content.d.ts.map +1 -0
- package/dist/content.js +204 -0
- package/dist/content.js.map +1 -0
- package/dist/documents.d.ts +184 -0
- package/dist/documents.d.ts.map +1 -0
- package/dist/documents.js +190 -0
- package/dist/documents.js.map +1 -0
- package/dist/edges.d.ts +645 -0
- package/dist/edges.d.ts.map +1 -0
- package/dist/edges.js +558 -0
- package/dist/edges.js.map +1 -0
- package/dist/filter.d.ts +461 -0
- package/dist/filter.d.ts.map +1 -0
- package/dist/filter.js +387 -0
- package/dist/filter.js.map +1 -0
- package/dist/index.d.ts +133 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +133 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/filter-columns.d.ts +8 -0
- package/dist/internal/filter-columns.d.ts.map +1 -0
- package/dist/internal/filter-columns.js +43 -0
- package/dist/internal/filter-columns.js.map +1 -0
- package/dist/internal/list-order.d.ts +26 -0
- package/dist/internal/list-order.d.ts.map +1 -0
- package/dist/internal/list-order.js +92 -0
- package/dist/internal/list-order.js.map +1 -0
- package/dist/internal/owned-columns.d.ts +93 -0
- package/dist/internal/owned-columns.d.ts.map +1 -0
- package/dist/internal/owned-columns.js +98 -0
- package/dist/internal/owned-columns.js.map +1 -0
- package/dist/internal/plain-rows.d.ts +32 -0
- package/dist/internal/plain-rows.d.ts.map +1 -0
- package/dist/internal/plain-rows.js +32 -0
- package/dist/internal/plain-rows.js.map +1 -0
- package/dist/internal/reference-columns.d.ts +80 -0
- package/dist/internal/reference-columns.d.ts.map +1 -0
- package/dist/internal/reference-columns.js +102 -0
- package/dist/internal/reference-columns.js.map +1 -0
- package/dist/moderation.d.ts +392 -0
- package/dist/moderation.d.ts.map +1 -0
- package/dist/moderation.js +375 -0
- package/dist/moderation.js.map +1 -0
- package/dist/paths.d.ts +204 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +165 -0
- package/dist/paths.js.map +1 -0
- package/dist/proposals.d.ts +523 -0
- package/dist/proposals.d.ts.map +1 -0
- package/dist/proposals.js +505 -0
- package/dist/proposals.js.map +1 -0
- package/dist/publication.d.ts +148 -0
- package/dist/publication.d.ts.map +1 -0
- package/dist/publication.js +183 -0
- package/dist/publication.js.map +1 -0
- package/dist/purge.d.ts +224 -0
- package/dist/purge.d.ts.map +1 -0
- package/dist/purge.js +151 -0
- package/dist/purge.js.map +1 -0
- package/dist/reactions.d.ts +246 -0
- package/dist/reactions.d.ts.map +1 -0
- package/dist/reactions.js +183 -0
- package/dist/reactions.js.map +1 -0
- package/dist/registry.d.ts +316 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +305 -0
- package/dist/registry.js.map +1 -0
- package/dist/search.d.ts +366 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +273 -0
- package/dist/search.js.map +1 -0
- package/dist/social.d.ts +311 -0
- package/dist/social.d.ts.map +1 -0
- package/dist/social.js +205 -0
- package/dist/social.js.map +1 -0
- package/dist/subtype.d.ts +1613 -0
- package/dist/subtype.d.ts.map +1 -0
- package/dist/subtype.js +1300 -0
- package/dist/subtype.js.map +1 -0
- package/dist/testing.d.ts +675 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +1022 -0
- package/dist/testing.js.map +1 -0
- package/dist/version-chain.d.ts +702 -0
- package/dist/version-chain.d.ts.map +1 -0
- package/dist/version-chain.js +489 -0
- package/dist/version-chain.js.map +1 -0
- package/dist/working-copy.d.ts +116 -0
- package/dist/working-copy.d.ts.map +1 -0
- package/dist/working-copy.js +95 -0
- package/dist/working-copy.js.map +1 -0
- package/package.json +138 -4
- package/src/blobs.ts +384 -0
- package/src/budgets.ts +338 -0
- package/src/cache.ts +218 -0
- package/src/collections.ts +427 -0
- package/src/comments.ts +392 -0
- package/src/content.ts +238 -0
- package/src/documents.ts +284 -0
- package/src/edges.ts +1185 -0
- package/src/filter.ts +523 -0
- package/src/index.ts +153 -0
- package/src/internal/filter-columns.ts +50 -0
- package/src/internal/list-order.ts +117 -0
- package/src/internal/owned-columns.ts +140 -0
- package/src/internal/plain-rows.ts +54 -0
- package/src/internal/reference-columns.ts +156 -0
- package/src/moderation.ts +728 -0
- package/src/paths.ts +267 -0
- package/src/proposals.ts +1014 -0
- package/src/publication.ts +225 -0
- package/src/purge.ts +313 -0
- package/src/reactions.ts +347 -0
- package/src/registry.ts +524 -0
- package/src/search.ts +540 -0
- package/src/social.ts +469 -0
- package/src/subtype.ts +3095 -0
- package/src/testing.ts +1873 -0
- package/src/version-chain.ts +1093 -0
- package/src/working-copy.ts +192 -0
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **The node / version chain discipline** — create, append, tombstone,
|
|
3
|
+
* restore, history and field-level compare over an append-only chain,
|
|
4
|
+
* generic in the leaf.
|
|
5
|
+
*
|
|
6
|
+
* Every node carries an append-only chain of `(version, action, timestamp,
|
|
7
|
+
* actor)` rows (`READ-009` / `PUB-003`): each edit appends a row rather than
|
|
8
|
+
* rewriting in place, and "repointing the current version" is nothing but
|
|
9
|
+
* that append — the current version is the chain's maximum, resolved by the
|
|
10
|
+
* current-version view. The behavioural spec is gardeneria's
|
|
11
|
+
* `packages/database/src/nodes.ts` (`createNodeE` / `updateNodeE` /
|
|
12
|
+
* `deleteNodeE`), the leaf-chain half of `apps/api/src/operations/subtype.ts`
|
|
13
|
+
* (`defineVersionOpsE`, the merge / carry-forward writers) and
|
|
14
|
+
* `version-history.ts`; a `#NNN` here names a gardeneria issue.
|
|
15
|
+
*
|
|
16
|
+
* ## The invariants
|
|
17
|
+
*
|
|
18
|
+
* - **Append-only** (`VERSION-CHAIN-001/002`). Every write lands at
|
|
19
|
+
* `N_max + 1`; no existing version row is ever updated, deleted or
|
|
20
|
+
* reordered. A restore is a new version whose content came from history.
|
|
21
|
+
* The one exception is erasure (`./purge`), which deletes a node's whole
|
|
22
|
+
* chain, never part of one.
|
|
23
|
+
* - **Create is never an upsert.** A caller-supplied id that already belongs
|
|
24
|
+
* to a node fails {@link NodeAlreadyExists} (F-OP-02) rather than minting a
|
|
25
|
+
* version onto it.
|
|
26
|
+
* - **Update carries forward.** A patch is tri-state per column: a value
|
|
27
|
+
* sets, `null` clears, an absent (or `undefined`) key carries the previous
|
|
28
|
+
* version's value forward. Never `input.x ?? null`.
|
|
29
|
+
* - **Delete is a tombstone that carries the state forward**, so a `delete`
|
|
30
|
+
* version is itself a complete snapshot — which is what makes restoring
|
|
31
|
+
* *from* a tombstone possible. It is idempotent: on an already tombstoned
|
|
32
|
+
* node it returns the existing tombstone and writes nothing.
|
|
33
|
+
* - **A plain update never resurrects** (`VERSION-CHAIN-006`). Over a
|
|
34
|
+
* tombstone it fails {@link NodeNotFound}; only the explicit `restore` and
|
|
35
|
+
* `restoreVersion` write over one.
|
|
36
|
+
* - **No partial restore.** `restoreVersion` reads the whole snapshot first
|
|
37
|
+
* and fails {@link VersionNotFound} when any level of the chain has no row
|
|
38
|
+
* at that version — before anything is written.
|
|
39
|
+
* - **Every chain read is bounded** (`PERF-DB-001`): `history` takes a
|
|
40
|
+
* `limit`, capped at {@link MAX_HISTORY_LIMIT}.
|
|
41
|
+
* - **An expected version is a precondition on the append.** A write to an
|
|
42
|
+
* existing node (`update`, `restore`, `delete`, `restoreVersion`) may
|
|
43
|
+
* carry the head version its caller last read ({@link Precondition}); a
|
|
44
|
+
* head that has moved fails {@link StaleVersion} and nothing is written.
|
|
45
|
+
* It is checked against the very head the append extends, and the append
|
|
46
|
+
* lands at that head + 1, so a writer that slips in between the read and
|
|
47
|
+
* the append takes the `(id, version)` key first and this one fails
|
|
48
|
+
* {@link VersionConflict}: the store's primary key is what makes the check
|
|
49
|
+
* atomic. Without one, a write extends whatever the head is (last writer
|
|
50
|
+
* wins on a column two patches both set).
|
|
51
|
+
*
|
|
52
|
+
* ## The seam
|
|
53
|
+
*
|
|
54
|
+
* The chain is read and written through a {@link VersionChainStore} the
|
|
55
|
+
* application supplies — in production, a Drizzle store over
|
|
56
|
+
* effect-drizzle-ext's `./versioned-chain` tables and views, which is not yet
|
|
57
|
+
* published (see the package README → Seams for the expected table shape).
|
|
58
|
+
* The operations here own the discipline; the store owns persistence
|
|
59
|
+
* and nothing else. Authorisation is not this module's concern either: the
|
|
60
|
+
* subtype factory (`./subtype`) gates before it calls in.
|
|
61
|
+
*
|
|
62
|
+
* @copyright Thomas Foster
|
|
63
|
+
* @module pulpit/version-chain
|
|
64
|
+
* @since 0.1.0
|
|
65
|
+
*/
|
|
66
|
+
import * as Effect from "effect/Effect";
|
|
67
|
+
import * as Option from "effect/Option";
|
|
68
|
+
import * as Schema from "effect/Schema";
|
|
69
|
+
/**
|
|
70
|
+
* What one version row recorded: the create, an edit (restores included), or a tombstone.
|
|
71
|
+
*
|
|
72
|
+
* @since 0.1.0
|
|
73
|
+
*/
|
|
74
|
+
export declare const VersionAction: Schema.Literals<readonly ["create", "update", "delete"]>;
|
|
75
|
+
/**
|
|
76
|
+
* The {@link VersionAction} literal union: `"create" | "update" | "delete"`.
|
|
77
|
+
*
|
|
78
|
+
* @since 0.1.0
|
|
79
|
+
*/
|
|
80
|
+
export type VersionAction = typeof VersionAction.Type;
|
|
81
|
+
/**
|
|
82
|
+
* One column's value in a leaf's state: a JSON value, or one of the two
|
|
83
|
+
* non-JSON scalars a Drizzle row carries (`Date` for timestamps, `bigint` for
|
|
84
|
+
* `bigint` columns). A content-addressed document is carried as its
|
|
85
|
+
* `DocumentHash` (`./documents`' `documentRef`), never as a copy; a document
|
|
86
|
+
* a leaf keeps inline is its parsed JSON.
|
|
87
|
+
*
|
|
88
|
+
* @since 0.1.0
|
|
89
|
+
*/
|
|
90
|
+
export type ColumnValue = Schema.Json | Date | bigint;
|
|
91
|
+
/**
|
|
92
|
+
* A leaf's domain state at one version: every column of every level of its
|
|
93
|
+
* chain, keyed by column name (the leaf-most level wins on a shared name).
|
|
94
|
+
* Generated columns (a `search_vector`) are not part of it — they regenerate.
|
|
95
|
+
*
|
|
96
|
+
* @since 0.1.0
|
|
97
|
+
*/
|
|
98
|
+
export type ChainState = {
|
|
99
|
+
readonly [column: string]: ColumnValue;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* A tri-state patch over a {@link ChainState}: a value sets, `null` clears,
|
|
103
|
+
* an absent or `undefined` key carries the previous version's value forward.
|
|
104
|
+
*
|
|
105
|
+
* @since 0.1.0
|
|
106
|
+
*/
|
|
107
|
+
export type ChainPatch = {
|
|
108
|
+
readonly [column: string]: ColumnValue | undefined;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* A localised leaf's locale level at one version (pulpit#56, model (b)): each
|
|
112
|
+
* locale the version carries, keyed by its tag (`"en"`, `"fr-CA"`), and that
|
|
113
|
+
* locale's translated columns. The node's shared (untranslated) columns stay
|
|
114
|
+
* in its {@link ChainState}; a translated column lives here only. A store
|
|
115
|
+
* keeps it as the locale level's fact rows, keyed `(id, version, locale)`.
|
|
116
|
+
* A version that carries no locale has none; an absent `locales` and `{}`
|
|
117
|
+
* mean the same.
|
|
118
|
+
*
|
|
119
|
+
* @since 0.1.0
|
|
120
|
+
*/
|
|
121
|
+
export type LocaleStates = {
|
|
122
|
+
readonly [locale: string]: ChainState;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* A tri-state patch over a {@link LocaleStates}, per locale: a
|
|
126
|
+
* {@link ChainPatch} updates that locale's columns (starting from none for a
|
|
127
|
+
* locale the version did not carry), `null` drops the locale, and an absent
|
|
128
|
+
* or `undefined` key carries it forward unchanged.
|
|
129
|
+
*
|
|
130
|
+
* @since 0.1.0
|
|
131
|
+
*/
|
|
132
|
+
export type LocalePatch = {
|
|
133
|
+
readonly [locale: string]: ChainPatch | null | undefined;
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* One entry in a node's append-only version chain.
|
|
137
|
+
*
|
|
138
|
+
* @since 0.1.0
|
|
139
|
+
*/
|
|
140
|
+
export interface VersionChainEntry {
|
|
141
|
+
/** The version number (0 is the create). */
|
|
142
|
+
readonly version: bigint;
|
|
143
|
+
/** The edit action this version recorded. */
|
|
144
|
+
readonly action: VersionAction;
|
|
145
|
+
/** When the version was written. */
|
|
146
|
+
readonly timestamp: Date;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* One version's reconstructed state, for the edits (browse / compare) surface.
|
|
150
|
+
*
|
|
151
|
+
* @since 0.1.0
|
|
152
|
+
*/
|
|
153
|
+
export interface VersionSnapshot extends VersionChainEntry {
|
|
154
|
+
/** The shared (untranslated) columns, or every column of a leaf with no locale level. */
|
|
155
|
+
readonly state: ChainState;
|
|
156
|
+
/**
|
|
157
|
+
* A localised leaf's locale level at this version (pulpit#56); absent, the
|
|
158
|
+
* version carries no locale. A store reconstructs it with the rest of the
|
|
159
|
+
* snapshot, or reports the version absent.
|
|
160
|
+
*/
|
|
161
|
+
readonly locales?: LocaleStates | undefined;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The row a write appends: the entry, the node's leaf type, the acting account, the full state.
|
|
165
|
+
*
|
|
166
|
+
* @since 0.1.0
|
|
167
|
+
*/
|
|
168
|
+
export interface VersionRow extends VersionSnapshot {
|
|
169
|
+
readonly id: string;
|
|
170
|
+
/** The concrete leaf's wire type, threaded onto the node row at version 0 (#864 S3). */
|
|
171
|
+
readonly type: string;
|
|
172
|
+
/** The acting account (#1581) — recorded on the version row, never inferred. */
|
|
173
|
+
readonly actorId: Option.Option<string>;
|
|
174
|
+
/**
|
|
175
|
+
* The contributor whose accepted proposal this version applies
|
|
176
|
+
* (`./proposals`), recorded beside the actor, who is the account that
|
|
177
|
+
* accepted it; none for every other write. A store persists it with the
|
|
178
|
+
* version row (README → Seams → `VersionChainStore`).
|
|
179
|
+
*/
|
|
180
|
+
readonly proposedBy: Option.Option<string>;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* What a chain write returns: the node and the version it landed at.
|
|
184
|
+
*
|
|
185
|
+
* @since 0.1.0
|
|
186
|
+
*/
|
|
187
|
+
export interface ChainWrite {
|
|
188
|
+
readonly id: string;
|
|
189
|
+
readonly version: bigint;
|
|
190
|
+
}
|
|
191
|
+
declare const NodeNotFound_base: Schema.Class<NodeNotFound, Schema.TaggedStruct<"NodeNotFound", {
|
|
192
|
+
readonly type: Schema.String;
|
|
193
|
+
readonly id: Schema.String;
|
|
194
|
+
}>, import("effect/Cause").YieldableError & {
|
|
195
|
+
readonly brand: unique symbol;
|
|
196
|
+
}>;
|
|
197
|
+
/**
|
|
198
|
+
* No such node, or (for a plain update) a tombstoned one: the wire's 404.
|
|
199
|
+
*
|
|
200
|
+
* @since 0.1.0
|
|
201
|
+
*/
|
|
202
|
+
export declare class NodeNotFound extends NodeNotFound_base {
|
|
203
|
+
}
|
|
204
|
+
declare const VersionNotFound_base: Schema.Class<VersionNotFound, Schema.TaggedStruct<"VersionNotFound", {
|
|
205
|
+
readonly type: Schema.String;
|
|
206
|
+
readonly id: Schema.String;
|
|
207
|
+
readonly version: Schema.BigInt;
|
|
208
|
+
}>, import("effect/Cause").YieldableError & {
|
|
209
|
+
readonly brand: unique symbol;
|
|
210
|
+
}>;
|
|
211
|
+
/**
|
|
212
|
+
* No reconstructable version at `(id, version)`: an unknown version, or a partial chain.
|
|
213
|
+
*
|
|
214
|
+
* @since 0.1.0
|
|
215
|
+
*/
|
|
216
|
+
export declare class VersionNotFound extends VersionNotFound_base {
|
|
217
|
+
}
|
|
218
|
+
declare const NodeAlreadyExists_base: Schema.Class<NodeAlreadyExists, Schema.TaggedStruct<"NodeAlreadyExists", {
|
|
219
|
+
readonly type: Schema.String;
|
|
220
|
+
readonly id: Schema.String;
|
|
221
|
+
}>, import("effect/Cause").YieldableError & {
|
|
222
|
+
readonly brand: unique symbol;
|
|
223
|
+
}>;
|
|
224
|
+
/**
|
|
225
|
+
* A create whose caller-supplied id already belongs to a node — never an upsert: the wire's 409.
|
|
226
|
+
*
|
|
227
|
+
* @since 0.1.0
|
|
228
|
+
*/
|
|
229
|
+
export declare class NodeAlreadyExists extends NodeAlreadyExists_base {
|
|
230
|
+
}
|
|
231
|
+
declare const VersionConflict_base: Schema.Class<VersionConflict, Schema.TaggedStruct<"VersionConflict", {
|
|
232
|
+
readonly id: Schema.String;
|
|
233
|
+
readonly version: Schema.BigInt;
|
|
234
|
+
}>, import("effect/Cause").YieldableError & {
|
|
235
|
+
readonly brand: unique symbol;
|
|
236
|
+
}>;
|
|
237
|
+
/**
|
|
238
|
+
* The store refused an append because `(id, version)` is already taken — a
|
|
239
|
+
* concurrent writer got there first. Raised by the store (the `(id, version)`
|
|
240
|
+
* primary key is what enforces append-only under concurrency); never retried
|
|
241
|
+
* here, because a retry would silently base the write on a version the
|
|
242
|
+
* caller never read.
|
|
243
|
+
*
|
|
244
|
+
* @since 0.1.0
|
|
245
|
+
*/
|
|
246
|
+
export declare class VersionConflict extends VersionConflict_base {
|
|
247
|
+
}
|
|
248
|
+
declare const StaleVersion_base: Schema.Class<StaleVersion, Schema.TaggedStruct<"StaleVersion", {
|
|
249
|
+
readonly type: Schema.String;
|
|
250
|
+
readonly id: Schema.String;
|
|
251
|
+
/** The version the caller expected the head to be. */
|
|
252
|
+
readonly expected: Schema.BigInt;
|
|
253
|
+
/** The head version the write found. */
|
|
254
|
+
readonly current: Schema.BigInt;
|
|
255
|
+
}>, import("effect/Cause").YieldableError & {
|
|
256
|
+
readonly brand: unique symbol;
|
|
257
|
+
}>;
|
|
258
|
+
/**
|
|
259
|
+
* An expected-version precondition that no longer holds: the head moved past
|
|
260
|
+
* the version the caller last read, so the write would have overwritten an
|
|
261
|
+
* edit the caller never saw. Nothing was written. Carries the head the write
|
|
262
|
+
* would have extended (`current`), so the caller can re-read and re-apply;
|
|
263
|
+
* the wire's 412 (a failed `If-Match`) or 409.
|
|
264
|
+
*
|
|
265
|
+
* A writer that passes the check but loses the race to the append fails
|
|
266
|
+
* {@link VersionConflict} instead: the same stale read, caught one step later
|
|
267
|
+
* by the store's key.
|
|
268
|
+
*
|
|
269
|
+
* @since 0.1.0
|
|
270
|
+
*/
|
|
271
|
+
export declare class StaleVersion extends StaleVersion_base {
|
|
272
|
+
}
|
|
273
|
+
declare const BrokenChain_base: Schema.Class<BrokenChain, Schema.TaggedStruct<"BrokenChain", {
|
|
274
|
+
readonly type: Schema.String;
|
|
275
|
+
readonly id: Schema.String;
|
|
276
|
+
readonly version: Schema.BigInt;
|
|
277
|
+
}>, import("effect/Cause").YieldableError & {
|
|
278
|
+
readonly brand: unique symbol;
|
|
279
|
+
}>;
|
|
280
|
+
/**
|
|
281
|
+
* The chain's head version has no reconstructable state — the store broke its
|
|
282
|
+
* own contract (a head it reports must be readable). A defect, never a typed
|
|
283
|
+
* failure: there is nothing a caller could do about it.
|
|
284
|
+
*
|
|
285
|
+
* @since 0.1.0
|
|
286
|
+
*/
|
|
287
|
+
export declare class BrokenChain extends BrokenChain_base {
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* The persistence seam the chain operations run over. `E` is the store's own
|
|
291
|
+
* failure (a Drizzle store's `EffectDrizzleQueryError`); `R` its requirements
|
|
292
|
+
* (a Drizzle store's `CurrentTransaction` — the operations never open a
|
|
293
|
+
* transaction themselves: `transact` is always-open and the boundary belongs
|
|
294
|
+
* to the caller, website #1883).
|
|
295
|
+
*
|
|
296
|
+
* Every method reads or writes exactly what it says and applies no policy.
|
|
297
|
+
*
|
|
298
|
+
* @since 0.1.0
|
|
299
|
+
*/
|
|
300
|
+
export interface VersionChainStore<E, R> {
|
|
301
|
+
/** The node's latest version entry, or none for an id with no versions. */
|
|
302
|
+
readonly head: (id: string) => Effect.Effect<Option.Option<VersionChainEntry>, E, R>;
|
|
303
|
+
/**
|
|
304
|
+
* The full state at `(id, version)`, or none when the version does not
|
|
305
|
+
* exist OR any level of the chain has no row there — a version that cannot
|
|
306
|
+
* be fully reconstructed is reported as absent, never as a partial state.
|
|
307
|
+
* On a localised leaf the snapshot carries its locale level too: every
|
|
308
|
+
* `(id, version, locale)` fact row, as `locales` (pulpit#56).
|
|
309
|
+
*/
|
|
310
|
+
readonly snapshot: (id: string, version: bigint) => Effect.Effect<Option.Option<VersionSnapshot>, E, R>;
|
|
311
|
+
/**
|
|
312
|
+
* Append one version row — at version 0 together with the node's identity
|
|
313
|
+
* row, and one locale-level row per locale `row.locales` carries. Fails {@link VersionConflict} when `(id, version)` already exists
|
|
314
|
+
* (at version 0: the id already belongs to a node).
|
|
315
|
+
*/
|
|
316
|
+
readonly append: (row: VersionRow) => Effect.Effect<void, E | VersionConflict, R>;
|
|
317
|
+
/** The chain's entries, oldest first, at most `limit` of them. */
|
|
318
|
+
readonly entries: (id: string, limit: number) => Effect.Effect<ReadonlyArray<VersionChainEntry>, E, R>;
|
|
319
|
+
/** Mint a fresh node id (a UUID) when the caller supplies none. */
|
|
320
|
+
readonly mintId: Effect.Effect<string, E, R>;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* The hard cap on a history read (`PERF-DB-001`): a chain is tiny in practice; this bounds a pathological one.
|
|
324
|
+
*
|
|
325
|
+
* @since 0.1.0
|
|
326
|
+
*/
|
|
327
|
+
export declare const MAX_HISTORY_LIMIT = 1000;
|
|
328
|
+
/**
|
|
329
|
+
* Apply a tri-state patch to a state: a value sets, `null` clears, `undefined` carries forward.
|
|
330
|
+
*
|
|
331
|
+
* @since 0.1.0
|
|
332
|
+
*/
|
|
333
|
+
export declare const applyPatch: (state: ChainState, patch: ChainPatch) => {
|
|
334
|
+
[x: string]: ColumnValue;
|
|
335
|
+
};
|
|
336
|
+
/**
|
|
337
|
+
* Apply a {@link LocalePatch} to a locale level: a patch updates its locale
|
|
338
|
+
* (from nothing for a locale `locales` lacks) with {@link applyPatch}, `null`
|
|
339
|
+
* drops the locale, `undefined` carries it forward. Pure and total.
|
|
340
|
+
*
|
|
341
|
+
* @example
|
|
342
|
+
* ```ts
|
|
343
|
+
* import { deepStrictEqual } from "node:assert";
|
|
344
|
+
* import { applyLocalePatch } from "@thomasfosterau/pulpit/version-chain";
|
|
345
|
+
*
|
|
346
|
+
* const before = { en: { title: "Hello" }, fr: { title: "Bonjour" } };
|
|
347
|
+
* deepStrictEqual(applyLocalePatch(before, { en: { title: "Hi" }, fr: null, de: { title: "Hallo" } }), {
|
|
348
|
+
* en: { title: "Hi" },
|
|
349
|
+
* de: { title: "Hallo" },
|
|
350
|
+
* });
|
|
351
|
+
* ```
|
|
352
|
+
*
|
|
353
|
+
* @since 0.1.0
|
|
354
|
+
*/
|
|
355
|
+
export declare const applyLocalePatch: (locales: LocaleStates, patch: LocalePatch) => LocaleStates;
|
|
356
|
+
/**
|
|
357
|
+
* The locale level a snapshot carries, `{}` for none.
|
|
358
|
+
*
|
|
359
|
+
* @since 0.1.0
|
|
360
|
+
*/
|
|
361
|
+
export declare const localesOf: (snapshot: Pick<VersionSnapshot, "locales">) => LocaleStates;
|
|
362
|
+
/**
|
|
363
|
+
* How one column differs between two states, read from the first state to
|
|
364
|
+
* the second: `added` (absent before, present after), `removed` (present
|
|
365
|
+
* before, absent after) or `changed` (present on both sides, structurally
|
|
366
|
+
* unequal). A `null` is a present value: a column cleared to `null` is
|
|
367
|
+
* `changed`, never `removed`, and a column absent from a state is a column
|
|
368
|
+
* that state does not have, not one it cleared.
|
|
369
|
+
*
|
|
370
|
+
* @since 0.1.0
|
|
371
|
+
*/
|
|
372
|
+
export type AttributeChange = {
|
|
373
|
+
readonly _tag: "added";
|
|
374
|
+
readonly after: ColumnValue;
|
|
375
|
+
} | {
|
|
376
|
+
readonly _tag: "removed";
|
|
377
|
+
readonly before: ColumnValue;
|
|
378
|
+
} | {
|
|
379
|
+
readonly _tag: "changed";
|
|
380
|
+
readonly before: ColumnValue;
|
|
381
|
+
readonly after: ColumnValue;
|
|
382
|
+
};
|
|
383
|
+
/**
|
|
384
|
+
* The field-level change set between two states, keyed by column: one
|
|
385
|
+
* {@link AttributeChange} per column that differs. An unchanged column has
|
|
386
|
+
* no key, so two equal states diff to `{}`.
|
|
387
|
+
*
|
|
388
|
+
* @since 0.1.0
|
|
389
|
+
*/
|
|
390
|
+
export type StateDiff = {
|
|
391
|
+
readonly [column: string]: AttributeChange;
|
|
392
|
+
};
|
|
393
|
+
/**
|
|
394
|
+
* The field-level change set from state `from` to state `to`: every column
|
|
395
|
+
* that is added, removed or changed, with its before and after values;
|
|
396
|
+
* unchanged columns are omitted. Pure and total, with no error channel.
|
|
397
|
+
*
|
|
398
|
+
* Presence is read per key, so the tri-state distinction survives: a column
|
|
399
|
+
* holding `null` is present (a clear), and only a column with no key is
|
|
400
|
+
* absent. Values compare structurally with `Equal.equals`: JSON objects and
|
|
401
|
+
* arrays by content (key order ignored), a `Date` by its instant, a `bigint`
|
|
402
|
+
* by value, and a `Date` never equals the string of its instant. A
|
|
403
|
+
* content-addressed document is held as its `DocumentHash`, so it compares by
|
|
404
|
+
* hash: changed or unchanged, never a textual diff, which is the UI's job.
|
|
405
|
+
*
|
|
406
|
+
* The diff is directed: swapping the arguments swaps `added` and `removed`
|
|
407
|
+
* and each change's `before` and `after`. A state's values are compared as
|
|
408
|
+
* immutable (Effect caches structural hashes), as a {@link ChainState} is.
|
|
409
|
+
*
|
|
410
|
+
* @since 0.1.0
|
|
411
|
+
*/
|
|
412
|
+
export declare const diffStates: (from: ChainState, to: ChainState) => StateDiff;
|
|
413
|
+
/**
|
|
414
|
+
* How one locale differs between two versions' locale levels, read from the
|
|
415
|
+
* first to the second: `added` (the second carries it, the first did not,
|
|
416
|
+
* with its columns), `removed` (the reverse) or `changed` (both carry it, and
|
|
417
|
+
* its columns differ: their {@link StateDiff}).
|
|
418
|
+
*
|
|
419
|
+
* @since 0.1.0
|
|
420
|
+
*/
|
|
421
|
+
export type LocaleChange = {
|
|
422
|
+
readonly _tag: "added";
|
|
423
|
+
readonly after: ChainState;
|
|
424
|
+
} | {
|
|
425
|
+
readonly _tag: "removed";
|
|
426
|
+
readonly before: ChainState;
|
|
427
|
+
} | {
|
|
428
|
+
readonly _tag: "changed";
|
|
429
|
+
readonly changes: StateDiff;
|
|
430
|
+
};
|
|
431
|
+
/**
|
|
432
|
+
* The per-locale change set between two locale levels, keyed by locale: one
|
|
433
|
+
* {@link LocaleChange} per locale that differs. An unchanged locale has no
|
|
434
|
+
* key.
|
|
435
|
+
*
|
|
436
|
+
* @since 0.1.0
|
|
437
|
+
*/
|
|
438
|
+
export type LocaleDiff = {
|
|
439
|
+
readonly [locale: string]: LocaleChange;
|
|
440
|
+
};
|
|
441
|
+
/**
|
|
442
|
+
* The per-locale change set from locale level `from` to locale level `to`
|
|
443
|
+
* (pulpit#56): every locale added, removed, or whose columns changed by
|
|
444
|
+
* {@link diffStates}; an unchanged locale is omitted. Pure and total.
|
|
445
|
+
*
|
|
446
|
+
* @example
|
|
447
|
+
* ```ts
|
|
448
|
+
* import { deepStrictEqual } from "node:assert";
|
|
449
|
+
* import { diffLocales } from "@thomasfosterau/pulpit/version-chain";
|
|
450
|
+
*
|
|
451
|
+
* deepStrictEqual(
|
|
452
|
+
* diffLocales({ en: { title: "Hello" }, fr: { title: "Bonjour" } }, { en: { title: "Hi" }, de: { title: "Hallo" } }),
|
|
453
|
+
* {
|
|
454
|
+
* en: { _tag: "changed", changes: { title: { _tag: "changed", before: "Hello", after: "Hi" } } },
|
|
455
|
+
* fr: { _tag: "removed", before: { title: "Bonjour" } },
|
|
456
|
+
* de: { _tag: "added", after: { title: "Hallo" } },
|
|
457
|
+
* },
|
|
458
|
+
* );
|
|
459
|
+
* ```
|
|
460
|
+
*
|
|
461
|
+
* @since 0.1.0
|
|
462
|
+
*/
|
|
463
|
+
export declare const diffLocales: (from: LocaleStates, to: LocaleStates) => LocaleDiff;
|
|
464
|
+
/**
|
|
465
|
+
* What {@link VersionChainOps.compare} returns: both versions as read
|
|
466
|
+
* (entry and full state) and the {@link StateDiff} from the first to the
|
|
467
|
+
* second.
|
|
468
|
+
*
|
|
469
|
+
* A tombstone is not an attribute: it is the version's `action`, so it shows
|
|
470
|
+
* as `from.action` / `to.action` being `"delete"` and never as a key in
|
|
471
|
+
* `changes`. A tombstone carries its state forward, so a version compared
|
|
472
|
+
* with the tombstone that followed it has no changes; a history view labels
|
|
473
|
+
* the version deleted from its action and lists only what the content did.
|
|
474
|
+
*
|
|
475
|
+
* @since 0.1.0
|
|
476
|
+
*/
|
|
477
|
+
export interface VersionComparison {
|
|
478
|
+
/** The version compared from, with its state. */
|
|
479
|
+
readonly from: VersionSnapshot;
|
|
480
|
+
/** The version compared to, with its state. */
|
|
481
|
+
readonly to: VersionSnapshot;
|
|
482
|
+
/** `diffStates(from.state, to.state)`: the shared columns. */
|
|
483
|
+
readonly changes: StateDiff;
|
|
484
|
+
/**
|
|
485
|
+
* `diffLocales` over the two versions' locale levels (pulpit#56): the
|
|
486
|
+
* per-locale changes, `{}` for a leaf with no locale level. Kept beside
|
|
487
|
+
* `changes` rather than inside it, since a column may itself be named
|
|
488
|
+
* `locales`.
|
|
489
|
+
*/
|
|
490
|
+
readonly locales: LocaleDiff;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* The input to {@link VersionChainOps.create}: the node id, the leaf's
|
|
494
|
+
* initial state, the acting account.
|
|
495
|
+
*
|
|
496
|
+
* @since 0.1.0
|
|
497
|
+
*/
|
|
498
|
+
export interface CreateInput {
|
|
499
|
+
/** A caller-supplied node id (client-generated ids); minted by the store when absent. */
|
|
500
|
+
readonly id: Option.Option<string>;
|
|
501
|
+
/** The leaf's full state at version 0: its shared columns, on a localised leaf. */
|
|
502
|
+
readonly state: ChainState;
|
|
503
|
+
/** A localised leaf's locale level at version 0 (pulpit#56); absent, none. */
|
|
504
|
+
readonly locales?: LocaleStates | undefined;
|
|
505
|
+
/** The acting account, recorded on the version row; none for a write with no session behind it. */
|
|
506
|
+
readonly actorId: Option.Option<string>;
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* An optimistic-concurrency precondition on a write to an existing node: the
|
|
510
|
+
* head version the caller last read. Every write that extends a chain
|
|
511
|
+
* ({@link VersionChainOps.update}, {@link VersionChainOps.restore},
|
|
512
|
+
* {@link VersionChainOps.delete}, {@link VersionChainOps.restoreVersion})
|
|
513
|
+
* accepts one.
|
|
514
|
+
*
|
|
515
|
+
* @since 0.1.0
|
|
516
|
+
*/
|
|
517
|
+
export interface Precondition {
|
|
518
|
+
/**
|
|
519
|
+
* The head version the caller last read. When present, the write fails
|
|
520
|
+
* {@link StaleVersion}, writing nothing, unless the head it extends is
|
|
521
|
+
* exactly this version; absent (or `undefined`), the write extends whatever
|
|
522
|
+
* the head is.
|
|
523
|
+
*/
|
|
524
|
+
readonly expectedVersion?: bigint | undefined;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* The input to {@link VersionChainOps.update} and
|
|
528
|
+
* {@link VersionChainOps.restore}: the node, a tri-state patch, the acting
|
|
529
|
+
* account, and an optional expected version.
|
|
530
|
+
*
|
|
531
|
+
* @since 0.1.0
|
|
532
|
+
*/
|
|
533
|
+
export interface UpdateInput extends Precondition {
|
|
534
|
+
/** The node to append to. */
|
|
535
|
+
readonly id: string;
|
|
536
|
+
/**
|
|
537
|
+
* The tri-state patch over the current state: a value sets, `null` clears,
|
|
538
|
+
* an absent key carries forward.
|
|
539
|
+
*/
|
|
540
|
+
readonly patch: ChainPatch;
|
|
541
|
+
/**
|
|
542
|
+
* A localised leaf's per-locale patch ({@link LocalePatch}, pulpit#56):
|
|
543
|
+
* absent, every locale carries forward. Even a write that touches one
|
|
544
|
+
* locale appends one version of the whole node.
|
|
545
|
+
*/
|
|
546
|
+
readonly locales?: LocalePatch | undefined;
|
|
547
|
+
/** The acting account, recorded on the version row; none for a write with no session behind it. */
|
|
548
|
+
readonly actorId: Option.Option<string>;
|
|
549
|
+
/**
|
|
550
|
+
* The contributor whose proposal this edit applies, recorded on the
|
|
551
|
+
* version row as `proposedBy`; absent (or `undefined`) for an ordinary
|
|
552
|
+
* edit. `./proposals`' `accept` sets it.
|
|
553
|
+
*/
|
|
554
|
+
readonly proposedBy?: Option.Option<string> | undefined;
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* The chain operations for one leaf type, bound to a store.
|
|
558
|
+
*
|
|
559
|
+
* @since 0.1.0
|
|
560
|
+
*/
|
|
561
|
+
export interface VersionChainOps<E, R> {
|
|
562
|
+
/** Insert the node and its version 0 (`create`). Fails {@link NodeAlreadyExists} on a taken id. */
|
|
563
|
+
readonly create: (input: CreateInput) => Effect.Effect<ChainWrite, E | NodeAlreadyExists | VersionConflict, R>;
|
|
564
|
+
/**
|
|
565
|
+
* Append an `update` version carrying the patch over the current state.
|
|
566
|
+
* Fails {@link NodeNotFound} for an unknown id and for a tombstoned node,
|
|
567
|
+
* and {@link StaleVersion} when `input.expectedVersion` is not the head.
|
|
568
|
+
*/
|
|
569
|
+
readonly update: (input: UpdateInput) => Effect.Effect<ChainWrite, E | NodeNotFound | StaleVersion | VersionConflict, R>;
|
|
570
|
+
/**
|
|
571
|
+
* {@link update}, except a tombstone does not fail: the explicit restore
|
|
572
|
+
* surface (`VERSION-CHAIN-006`) — the node comes back live. Fails
|
|
573
|
+
* {@link NodeNotFound} for an unknown id and {@link StaleVersion} when
|
|
574
|
+
* `input.expectedVersion` is not the head.
|
|
575
|
+
*/
|
|
576
|
+
readonly restore: (input: UpdateInput) => Effect.Effect<ChainWrite, E | NodeNotFound | StaleVersion | VersionConflict, R>;
|
|
577
|
+
/**
|
|
578
|
+
* Append a `delete` version carrying the state forward. Idempotent: on a
|
|
579
|
+
* tombstoned node it returns the existing tombstone with
|
|
580
|
+
* `alreadyDeleted: true` and writes nothing, whatever the precondition
|
|
581
|
+
* says (the delete it asked for has happened). Fails {@link NodeNotFound}
|
|
582
|
+
* for an unknown id and {@link StaleVersion} when a live node's head is
|
|
583
|
+
* not `precondition.expectedVersion`.
|
|
584
|
+
*/
|
|
585
|
+
readonly delete: (id: string, actorId: Option.Option<string>, precondition?: Precondition) => Effect.Effect<ChainWrite & {
|
|
586
|
+
readonly alreadyDeleted: boolean;
|
|
587
|
+
}, E | NodeNotFound | StaleVersion | VersionConflict, R>;
|
|
588
|
+
/** The reconstructed state at `(id, version)`. Read-only. */
|
|
589
|
+
readonly getVersion: (id: string, version: bigint) => Effect.Effect<VersionSnapshot, E | VersionNotFound, R>;
|
|
590
|
+
/**
|
|
591
|
+
* The field-level change set from version `from` to version `to` of one
|
|
592
|
+
* node: two {@link VersionChainOps.getVersion} reads and {@link diffStates}.
|
|
593
|
+
* Read-only. Fails {@link VersionNotFound} for whichever version (`from`
|
|
594
|
+
* first) cannot be fully reconstructed. A tombstone on either side is that
|
|
595
|
+
* side's `action`, never an entry in `changes` (see
|
|
596
|
+
* {@link VersionComparison}). Ungated, like `getVersion`: `./subtype` gates
|
|
597
|
+
* history reads (`READ-009`), and its `SubtypeOps.compare` is this behind
|
|
598
|
+
* `getVersion`'s gate.
|
|
599
|
+
*/
|
|
600
|
+
readonly compare: (id: string, from: bigint, to: bigint) => Effect.Effect<VersionComparison, E | VersionNotFound, R>;
|
|
601
|
+
/**
|
|
602
|
+
* Re-apply the full snapshot at `(id, version)` as a NEW `update` version —
|
|
603
|
+
* over a tombstone too. Fails {@link VersionNotFound}, before writing
|
|
604
|
+
* anything, when the version cannot be fully reconstructed, and
|
|
605
|
+
* {@link StaleVersion} when the head is not
|
|
606
|
+
* `precondition.expectedVersion` (the precondition is on the head the
|
|
607
|
+
* restore extends, not on the version it restores).
|
|
608
|
+
*/
|
|
609
|
+
readonly restoreVersion: (id: string, version: bigint, actorId: Option.Option<string>, precondition?: Precondition) => Effect.Effect<ChainWrite, E | VersionNotFound | StaleVersion | VersionConflict, R>;
|
|
610
|
+
/**
|
|
611
|
+
* The chain, oldest first, at most `limit` entries (default and cap
|
|
612
|
+
* {@link MAX_HISTORY_LIMIT}). Fails {@link NodeNotFound} when the id has no
|
|
613
|
+
* versions, so a caller surfaces the 404 a missing resource would.
|
|
614
|
+
*/
|
|
615
|
+
readonly history: (id: string, limit?: number) => Effect.Effect<ReadonlyArray<VersionChainEntry>, E | NodeNotFound, R>;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* The write that is about to append: the chain's `action` vocabulary, with
|
|
619
|
+
* the two explicit restore surfaces named apart (`VERSION-CHAIN-006`), so a
|
|
620
|
+
* hook can tell a resurrection from an edit. `restore` and `restoreVersion`
|
|
621
|
+
* still append an `update` row. `publish` and `unpublish` are `./subtype`'s
|
|
622
|
+
* publication operations, which record a publication fact and append no
|
|
623
|
+
* version; the chain itself never emits them.
|
|
624
|
+
*
|
|
625
|
+
* @since 0.1.0
|
|
626
|
+
*/
|
|
627
|
+
export declare const WriteAction: Schema.Literals<readonly ["create", "update", "delete", "restore", "restoreVersion", "publish", "unpublish"]>;
|
|
628
|
+
/**
|
|
629
|
+
* The {@link WriteAction} literal union.
|
|
630
|
+
*
|
|
631
|
+
* @since 0.1.0
|
|
632
|
+
*/
|
|
633
|
+
export type WriteAction = typeof WriteAction.Type;
|
|
634
|
+
/**
|
|
635
|
+
* What a {@link ChainHooks.beforeAppend} hook is shown: the write, the node,
|
|
636
|
+
* the head it extends, and the patch it is about to apply.
|
|
637
|
+
*
|
|
638
|
+
* - `create` — `prior` is none, and `patch` is the whole initial state;
|
|
639
|
+
* - `update` / `restore` — `prior` is the head, `patch` the caller's tri-state patch;
|
|
640
|
+
* - `delete` — `prior` is the live head, `patch` is `{}` (the tombstone
|
|
641
|
+
* carries the head's state forward); an already tombstoned node writes
|
|
642
|
+
* nothing and runs no hook;
|
|
643
|
+
* - `restoreVersion` — `prior` is the head, `patch` the whole state of the
|
|
644
|
+
* version being restored.
|
|
645
|
+
*
|
|
646
|
+
* The version written is the hook's patch applied to `prior`'s state (to
|
|
647
|
+
* nothing for `create` and `restoreVersion`, whose patch is the whole state).
|
|
648
|
+
*
|
|
649
|
+
* On a localised leaf (pulpit#56) `locales` is the write's locale patch, in
|
|
650
|
+
* the same reading: the whole locale level for `create` and
|
|
651
|
+
* `restoreVersion`, the caller's {@link LocalePatch} for `update` /
|
|
652
|
+
* `restore`, `{}` for `delete`. `beforeAppend`'s patch is the shared
|
|
653
|
+
* columns' alone; `beforeAppendLocales` returns the locale patch the write
|
|
654
|
+
* applies (see {@link ChainHooks}).
|
|
655
|
+
*
|
|
656
|
+
* @since 0.1.0
|
|
657
|
+
*/
|
|
658
|
+
export interface ChainAppendEvent {
|
|
659
|
+
readonly action: WriteAction;
|
|
660
|
+
readonly id: string;
|
|
661
|
+
/** The head the append extends (its full state), or none for a create. */
|
|
662
|
+
readonly prior: Option.Option<VersionSnapshot>;
|
|
663
|
+
readonly patch: ChainPatch;
|
|
664
|
+
/** The write's locale patch (see above); absent or `{}`, every locale carries forward. */
|
|
665
|
+
readonly locales?: LocalePatch | undefined;
|
|
666
|
+
/** The acting account the row will record. */
|
|
667
|
+
readonly actorId: Option.Option<string>;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* The chain's hook point, bound once by {@link defineVersionChain}.
|
|
671
|
+
*
|
|
672
|
+
* @since 0.1.0
|
|
673
|
+
*/
|
|
674
|
+
export interface ChainHooks<E, R> {
|
|
675
|
+
/**
|
|
676
|
+
* Runs after the head is read and before the row is appended, in the
|
|
677
|
+
* caller's transaction: its patch replaces the event's, and a failure
|
|
678
|
+
* (its `E`) fails the write with nothing appended. It sees the very head
|
|
679
|
+
* the append extends, so the expected-version check (`appendNext`) still
|
|
680
|
+
* runs against that head after it. Absent, nothing extra is read.
|
|
681
|
+
*/
|
|
682
|
+
readonly beforeAppend?: ((event: ChainAppendEvent) => Effect.Effect<ChainPatch, E, R>) | undefined;
|
|
683
|
+
/**
|
|
684
|
+
* The locale level's counterpart of `beforeAppend` (pulpit#56): runs at the
|
|
685
|
+
* same point, shown the same event, and its {@link LocalePatch} replaces
|
|
686
|
+
* the event's `locales`, so a hook that validates or transforms a column
|
|
687
|
+
* does so in every locale too. Absent, the event's locale patch is applied
|
|
688
|
+
* as given.
|
|
689
|
+
*/
|
|
690
|
+
readonly beforeAppendLocales?: ((event: ChainAppendEvent) => Effect.Effect<LocalePatch, E, R>) | undefined;
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Bind the chain operations for one leaf `type` to a store. Every produced
|
|
694
|
+
* operation is traced as `${type}.<operation>`. `hooks.beforeAppend`, when
|
|
695
|
+
* given, runs before every append (see {@link ChainHooks}); its failure is
|
|
696
|
+
* each write's `E`.
|
|
697
|
+
*
|
|
698
|
+
* @since 0.1.0
|
|
699
|
+
*/
|
|
700
|
+
export declare const defineVersionChain: <E, R>(type: string, store: VersionChainStore<E, R>, hooks?: ChainHooks<E, R>) => VersionChainOps<E, R>;
|
|
701
|
+
export {};
|
|
702
|
+
//# sourceMappingURL=version-chain.d.ts.map
|