@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 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
- export declare function applyChangeMutation(ctx: MutationContext, mutation: ChangeDocumentMutation): Promise<DocumentNode | null>;
70
- export declare function applySetMutation(ctx: MutationContext, mutation: SetDocumentMutation): Promise<DocumentNode | null>;
71
- export declare function applyMutation(ctx: MutationContext, mutation: DocumentMutation): Promise<DocumentNode | null>;
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>;