@kubun/mutation 0.16.0 → 0.17.1
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/lib/apply.d.ts +139 -4
- package/lib/apply.js +541 -46
- package/lib/create.d.ts +12 -2
- package/lib/create.js +29 -2
- package/lib/errors.d.ts +103 -0
- package/lib/errors.js +165 -1
- package/lib/index.d.ts +6 -5
- package/lib/index.js +4 -3
- package/lib/operations.d.ts +15 -2
- package/lib/operations.js +21 -4
- package/lib/state-digest.d.ts +16 -0
- package/lib/state-digest.js +21 -0
- package/package.json +6 -7
package/lib/apply.d.ts
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
import { HLC } from '@kubun/hlc';
|
|
2
2
|
import type { DocumentModelID } from '@kubun/id';
|
|
3
3
|
import { DocumentID } from '@kubun/id';
|
|
4
|
-
import type { ChangeDocumentMutation, DocumentData, DocumentModel, DocumentMutation, DocumentNode, SetDocumentMutation } from '@kubun/protocol';
|
|
4
|
+
import type { ChangeDocumentMutation, DocumentData, DocumentModel, DocumentMutation, DocumentNode, SetDocumentMutation, TombstoneDocumentMutation } from '@kubun/protocol';
|
|
5
5
|
import { type Validator } from '@sozai/schema';
|
|
6
6
|
export type DocumentValidator = Validator<DocumentData>;
|
|
7
7
|
export type ValidatorsRecord = Record<string, Promise<DocumentValidator>>;
|
|
8
|
+
/**
|
|
9
|
+
* Reserved key holding a merge-policy model's winning-live fetch-order
|
|
10
|
+
* register for its provider group: `{ hlc, digest }`, the greatest
|
|
11
|
+
* `sourceFetchHLC` observed for a live candidate, paired with that
|
|
12
|
+
* candidate's state digest. The bare `hlc` also lands in `field_hlcs` under
|
|
13
|
+
* this same key (mirroring `SET_KEY`) for the total-order comparison;
|
|
14
|
+
* `field_values` carries the full cell so `winnerStateDigestOf` can read the
|
|
15
|
+
* digest without a second store round trip.
|
|
16
|
+
*/
|
|
17
|
+
export declare const SET_FETCH_KEY = "$setFetch";
|
|
18
|
+
/**
|
|
19
|
+
* Reserved key holding a merge-policy model's winning-delete fetch-order
|
|
20
|
+
* register: `{ hlc }`, the greatest deleting `sourceFetchHLC` observed. Same
|
|
21
|
+
* `field_hlcs`/`field_values` split as `SET_FETCH_KEY`.
|
|
22
|
+
*/
|
|
23
|
+
export declare const DELETE_FETCH_KEY = "/fetch";
|
|
8
24
|
/**
|
|
9
25
|
* Post-state access gate evaluated AFTER the post-apply `DocumentNode` is
|
|
10
26
|
* computed in memory but BEFORE any persist call. Returning `false` instructs
|
|
@@ -65,7 +81,126 @@ export declare function getDocumentValidator(ctx: MutationContext, id: string):
|
|
|
65
81
|
* lands whenever it lands.
|
|
66
82
|
*/
|
|
67
83
|
export declare const DEFAULT_MAX_DRIFT_MS = 3600000;
|
|
84
|
+
export declare function validateFetchTokenDrift(token: string, maxDriftMS: number): void;
|
|
68
85
|
export declare function getTopLevelField(path: string): string;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
86
|
+
/**
|
|
87
|
+
* A model's provider merge policy, or `null` for an ordinary model. Read
|
|
88
|
+
* straight from the store on every call (no cache) since it gates which
|
|
89
|
+
* branch a SET/tombstone apply takes. `requiredGroupFields` is the subset of
|
|
90
|
+
* `group` the model schema's own `required` array names — the fields a
|
|
91
|
+
* complete-group SET may never omit (an omitted OPTIONAL group field is
|
|
92
|
+
* legitimate: it clears that field's stored value on a winning SET instead).
|
|
93
|
+
*/
|
|
94
|
+
export declare function resolveModelPolicy(ctx: MutationContext, modelID: DocumentModelID | string): Promise<{
|
|
95
|
+
group: Array<string>;
|
|
96
|
+
requiredGroupFields: ReadonlySet<string>;
|
|
97
|
+
} | null>;
|
|
98
|
+
/**
|
|
99
|
+
* The winning side of a merge-policy document's fetch-order registers: its
|
|
100
|
+
* token, its state digest (the live `$setFetch` digest, or the constant
|
|
101
|
+
* `DELETED_STATE_DIGEST` when the delete side wins), and whether that side is
|
|
102
|
+
* live. Shared by `winnerStateDigestOf` (the digest-only public read) and the
|
|
103
|
+
* SET classifier/register-winner logic below, which also need the frontier's
|
|
104
|
+
* own token and liveness to compare a candidate against it.
|
|
105
|
+
*/
|
|
106
|
+
export type FetchFrontier = {
|
|
107
|
+
hlc: string;
|
|
108
|
+
digest: string;
|
|
109
|
+
alive: boolean;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Pure merge-policy liveness read: the winning side's state digest alone —
|
|
113
|
+
* the live `$setFetch` digest, or `DELETED_STATE_DIGEST` when the delete side
|
|
114
|
+
* wins, or `null` when neither register is present. This is what lets
|
|
115
|
+
* `@kubun/store-graph`'s `readWinnerStateDigest` reuse the comparison after
|
|
116
|
+
* its own single-column read instead of also threading `field_hlcs`.
|
|
117
|
+
*/
|
|
118
|
+
export declare function winnerStateDigestOf(fieldValues: Record<string, unknown>): string | null;
|
|
119
|
+
/**
|
|
120
|
+
* The diagnostic outcome of applying a candidate against a merge-policy
|
|
121
|
+
* document's provider group. `applied` and `dominated` are pure functions of
|
|
122
|
+
* arrival order and state (never authorization); `nonconforming` flags an
|
|
123
|
+
* exact-token disagreement the total order still resolves deterministically.
|
|
124
|
+
*/
|
|
125
|
+
export type MergeOutcome = 'applied' | 'dominated' | 'nonconforming';
|
|
126
|
+
/**
|
|
127
|
+
* The result of applying a mutation. `document` is the thin compat surface
|
|
128
|
+
* every caller reads. The remaining fields are present together only for a
|
|
129
|
+
* non-null application against a merge-policy provider group's SET or
|
|
130
|
+
* tombstone path: `outcome` is `classifySetOutcome`/`classifyTombstoneOutcome`'s
|
|
131
|
+
* diagnostic label, `candidateDigest` the incoming candidate's own state
|
|
132
|
+
* digest, `winnerDigest` the register winner's state digest after this apply,
|
|
133
|
+
* and `winnerChanged` whether that winner digest moved from before to after.
|
|
134
|
+
*
|
|
135
|
+
* `outcome` and `winnerChanged` are independent signals, not the same
|
|
136
|
+
* question asked twice. `outcome` classifies the candidate's standing
|
|
137
|
+
* against the total order: `applied` means the candidate is a valid member
|
|
138
|
+
* of the current frontier, which includes an idempotent re-projection of
|
|
139
|
+
* content the frontier already holds (an exact-token, equal-digest replay —
|
|
140
|
+
* `winnerChanged` is `false` there, since the register's digest never
|
|
141
|
+
* moved). `nonconforming` flags an exact-token disagreement in content; the
|
|
142
|
+
* total order still resolves it deterministically to one side or the other,
|
|
143
|
+
* so a `nonconforming` candidate can itself either win (`winnerChanged:
|
|
144
|
+
* true`, its digest becomes the new winner) or lose (`winnerChanged:
|
|
145
|
+
* false`) — the label alone does not say which. Whether the register's
|
|
146
|
+
* winning digest actually moved is answered by `winnerChanged` alone, never
|
|
147
|
+
* inferred from the `outcome` label. Outcome and digest equality are a
|
|
148
|
+
* third, separate independent axis: a `dominated` candidate can carry
|
|
149
|
+
* either an equal or unequal digest against the winner. An ordinary-model
|
|
150
|
+
* application, a CHANGE application, and a dropped (gate-denied or
|
|
151
|
+
* null-document) application carry none of the four — just `{ document }`.
|
|
152
|
+
*/
|
|
153
|
+
export type MutationApplyResult = {
|
|
154
|
+
document: DocumentNode | null;
|
|
155
|
+
outcome?: MergeOutcome;
|
|
156
|
+
candidateDigest?: string;
|
|
157
|
+
winnerDigest?: string;
|
|
158
|
+
winnerChanged?: boolean;
|
|
159
|
+
};
|
|
160
|
+
/**
|
|
161
|
+
* Fetch-token-first diagnostic classification of an incoming live SET
|
|
162
|
+
* candidate against the stored frontier (`F_hlc` = the frontier's own token,
|
|
163
|
+
* equal to `max(setFetchHLC, deleteFetchHLC)` by construction — whichever
|
|
164
|
+
* register wins carries the greater of the two). Discriminates on the
|
|
165
|
+
* candidate's token alone; state/digest is inspected only at an exact token
|
|
166
|
+
* tie, so this is exclusive and total: a below-frontier candidate is never
|
|
167
|
+
* simultaneously `dominated` (hash irrelevant beneath the frontier) and
|
|
168
|
+
* `nonconforming` (hash only inspected at an exact tie).
|
|
169
|
+
*
|
|
170
|
+
* This is the diagnostic label only — separate from which side the register
|
|
171
|
+
* actually keeps (`setWinsFrontier` below). At an exact tie with unequal
|
|
172
|
+
* digests the register still picks the lexically greater one deterministically,
|
|
173
|
+
* even though the label stays `nonconforming` for both directions: the label
|
|
174
|
+
* flags the disagreement, it does not report which side won it.
|
|
175
|
+
*/
|
|
176
|
+
export declare function classifySetOutcome(token: string, candidateDigest: string, frontier: FetchFrontier | null): MergeOutcome;
|
|
177
|
+
/**
|
|
178
|
+
* Fetch-token-first diagnostic classification of an incoming tombstone
|
|
179
|
+
* candidate against the stored frontier, mirroring `classifySetOutcome`. At
|
|
180
|
+
* an exact token tie the register always keeps the delete (fail-safe), so a
|
|
181
|
+
* tie against an already-deleted frontier is `applied` (idempotent
|
|
182
|
+
* re-tombstoning) while a tie against a live frontier is `nonconforming` —
|
|
183
|
+
* the label flags the disagreement even though the register still resolves
|
|
184
|
+
* it deterministically to the delete.
|
|
185
|
+
*/
|
|
186
|
+
export declare function classifyTombstoneOutcome(token: string, frontier: FetchFrontier | null): MergeOutcome;
|
|
187
|
+
export declare function applyChangeMutation(ctx: MutationContext, mutation: ChangeDocumentMutation): Promise<MutationApplyResult>;
|
|
188
|
+
export declare function applySetMutation(ctx: MutationContext, mutation: SetDocumentMutation): Promise<MutationApplyResult>;
|
|
189
|
+
/**
|
|
190
|
+
* A fetch-token-carrying deletion on a merge-policy document's `/fetch`
|
|
191
|
+
* liveness register. Unlike `applyChangeMutation`'s delete branch, a
|
|
192
|
+
* tombstone may arrive before the document exists at all — it carries the
|
|
193
|
+
* full SET-equivalent creation-and-authorization surface (`aud`/`unq`) so a
|
|
194
|
+
* first-seen tombstone can establish a `data: null` row exactly as a SET
|
|
195
|
+
* creates state from `owner`+`unq`. Ordering is governed by the same total
|
|
196
|
+
* key as the set side (`deleteWinsFrontier`), so sets and deletes commute
|
|
197
|
+
* regardless of arrival order.
|
|
198
|
+
*
|
|
199
|
+
* A tombstone is a merge-policy-only concept — an ordinary model deletes via
|
|
200
|
+
* the `/`→null CHANGE path, never a tombstone — so a model with no
|
|
201
|
+
* `providerMergePolicy` rejects outright rather than running the fetch-order
|
|
202
|
+
* registers against a document whose liveness is governed by plain
|
|
203
|
+
* `$set`/`/` HLC registers instead.
|
|
204
|
+
*/
|
|
205
|
+
export declare function applyTombstoneMutation(ctx: MutationContext, mutation: TombstoneDocumentMutation): Promise<MutationApplyResult>;
|
|
206
|
+
export declare function applyMutation(ctx: MutationContext, mutation: DocumentMutation): Promise<MutationApplyResult>;
|