@kubun/mutation 0.17.0 → 0.18.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/lib/create.d.ts CHANGED
@@ -1,7 +1,6 @@
1
- import type { PatchOperation } from '@kubun/graphql';
2
1
  import { HLC } from '@kubun/hlc';
3
2
  import { DocumentID, type DocumentModelID } from '@kubun/id';
4
- import type { ChangeDocumentMutation, DocumentData, SetDocumentMutation } from '@kubun/protocol';
3
+ import type { ChangeDocumentMutation, DocumentData, PatchOperation, SetDocumentMutation, TombstoneDocumentMutation } from '@kubun/protocol';
5
4
  export type CreateSetMutationParams<Data extends DocumentData = DocumentData> = {
6
5
  data: Data;
7
6
  hlc: HLC;
@@ -10,6 +9,7 @@ export type CreateSetMutationParams<Data extends DocumentData = DocumentData> =
10
9
  owner?: string;
11
10
  unique: Uint8Array;
12
11
  cap?: string | Array<string>;
12
+ sourceFetchHLC?: string;
13
13
  };
14
14
  export declare function createSetMutation<Data extends DocumentData = DocumentData>(params: CreateSetMutationParams<Data>): Promise<SetDocumentMutation>;
15
15
  export type CreateChangeMutationParams = {
@@ -27,3 +27,13 @@ export type CreateRemoveMutationParams = {
27
27
  cap?: string | Array<string>;
28
28
  };
29
29
  export declare function createRemoveMutation(params: CreateRemoveMutationParams): ChangeDocumentMutation;
30
+ export type CreateTombstoneMutationParams = {
31
+ hlc: HLC;
32
+ issuer: string;
33
+ modelID: DocumentModelID | string;
34
+ owner?: string;
35
+ unique: Uint8Array;
36
+ sourceFetchHLC: string;
37
+ cap?: string | Array<string>;
38
+ };
39
+ export declare function createTombstoneMutation(params: CreateTombstoneMutationParams): Promise<TombstoneDocumentMutation>;
package/lib/create.js CHANGED
@@ -2,7 +2,7 @@ import { HLC } from '@kubun/hlc';
2
2
  import { DocumentID } from '@kubun/id';
3
3
  import { isValidAccessPermissions } from '@kubun/protocol';
4
4
  import { toB64 } from '@sozai/codec';
5
- import { InvalidAccessPermissionsError } from './errors.js';
5
+ import { FetchTokenOrderError, InvalidAccessPermissionsError } from './errors.js';
6
6
  const ACCESS_PERMISSIONS_FIELD = 'accessPermissions';
7
7
  const ACCESS_PERMISSIONS_PATH = `/${ACCESS_PERMISSIONS_FIELD}`;
8
8
  /**
@@ -30,16 +30,23 @@ export async function createSetMutation(params) {
30
30
  const issuer = params.issuer;
31
31
  const owner = params.owner ?? issuer;
32
32
  const docID = await DocumentID.create(params.modelID, owner, params.unique);
33
+ const hlc = HLC.serialize(params.hlc.now());
34
+ if (params.sourceFetchHLC != null && hlc < params.sourceFetchHLC) {
35
+ throw new FetchTokenOrderError();
36
+ }
33
37
  return {
34
38
  typ: 'set',
35
39
  iss: issuer,
36
40
  aud: owner,
37
41
  sub: docID.toString(),
38
42
  data: params.data,
39
- hlc: HLC.serialize(params.hlc.now()),
43
+ hlc,
40
44
  unq: toB64(params.unique),
41
45
  ...params.cap != null ? {
42
46
  cap: params.cap
47
+ } : {},
48
+ ...params.sourceFetchHLC != null ? {
49
+ sourceFetchHLC: params.sourceFetchHLC
43
50
  } : {}
44
51
  };
45
52
  }
@@ -74,3 +81,23 @@ export function createRemoveMutation(params) {
74
81
  } : {}
75
82
  };
76
83
  }
84
+ export async function createTombstoneMutation(params) {
85
+ const owner = params.owner ?? params.issuer;
86
+ const docID = await DocumentID.create(params.modelID, owner, params.unique);
87
+ const hlc = HLC.serialize(params.hlc.now());
88
+ if (hlc < params.sourceFetchHLC) {
89
+ throw new FetchTokenOrderError();
90
+ }
91
+ return {
92
+ typ: 'tombstone',
93
+ iss: params.issuer,
94
+ aud: owner,
95
+ sub: docID.toString(),
96
+ unq: toB64(params.unique),
97
+ hlc,
98
+ sourceFetchHLC: params.sourceFetchHLC,
99
+ ...params.cap != null ? {
100
+ cap: params.cap
101
+ } : {}
102
+ };
103
+ }
package/lib/errors.d.ts CHANGED
@@ -1,11 +1,23 @@
1
+ export type KubunMutationErrorCode = 'KB07' | 'KB09' | 'KB10' | 'KB11' | 'KB12' | 'KB13' | 'KB16' | 'KB17' | 'KB18' | 'KB19' | 'KB20' | 'KB21' | 'KB22';
2
+ export type KubunMutationErrorParams = {
3
+ message: string;
4
+ code: KubunMutationErrorCode;
5
+ docID?: string;
6
+ cause?: unknown;
7
+ };
8
+ export declare class KubunMutationError extends Error {
9
+ #private;
10
+ constructor(params: KubunMutationErrorParams);
11
+ get code(): KubunMutationErrorCode;
12
+ get docID(): string | undefined;
13
+ }
1
14
  /**
2
15
  * Thrown by the mutation apply path when a non-owner writer's capability
3
16
  * check fails. Tagged with the protocol-level error code `KB07` so callers
4
17
  * — particularly the RPC handler — can translate the throw into a
5
18
  * structured wire response without losing the reason.
6
19
  */
7
- export declare class WriteAccessDeniedError extends Error {
8
- #private;
20
+ export declare class WriteAccessDeniedError extends KubunMutationError {
9
21
  constructor(docID: string);
10
22
  get code(): 'KB07';
11
23
  get docID(): string;
@@ -19,8 +31,7 @@ export declare class WriteAccessDeniedError extends Error {
19
31
  * error code `KB09` so callers — particularly the RPC handler — can translate
20
32
  * the throw into a structured wire response without losing the reason.
21
33
  */
22
- export declare class DocumentIDMismatchError extends Error {
23
- #private;
34
+ export declare class DocumentIDMismatchError extends KubunMutationError {
24
35
  constructor(docID: string);
25
36
  get code(): 'KB09';
26
37
  get docID(): string;
@@ -32,13 +43,113 @@ export declare class DocumentIDMismatchError extends Error {
32
43
  * the bound — the reject is permanent. Tagged `KB10` so the RPC handler can
33
44
  * translate the throw into a structured wire response.
34
45
  */
35
- export declare class HLCDriftError extends Error {
46
+ export declare class HLCDriftError extends KubunMutationError {
36
47
  #private;
37
48
  constructor(driftMS: number, maxDriftMS: number);
38
49
  get code(): 'KB10';
39
50
  get driftMS(): number;
40
51
  get maxDriftMS(): number;
41
52
  }
53
+ /**
54
+ * Thrown when a fetch token cannot be parsed as a valid HLC timestamp. Detects
55
+ * malformed tokens or NaN values that would slip past `HLC.parse` but fail
56
+ * `HLC.tryParse` validation. Tagged `KB16` so the RPC handler can translate the
57
+ * throw into a structured wire response.
58
+ */
59
+ export declare class InvalidFetchTokenError extends KubunMutationError {
60
+ #private;
61
+ constructor(token: string);
62
+ get code(): 'KB16';
63
+ get token(): string;
64
+ }
65
+ /**
66
+ * Thrown when a mutation's minted HLC timestamp predates the supplied fetch
67
+ * token (sourceFetchHLC). Since HLC is a fixed property of a signed mutation,
68
+ * this reject is permanent — resending cannot move the HLC forward. Tagged
69
+ * `KB17` so the RPC handler can translate the throw into a structured wire
70
+ * response.
71
+ */
72
+ export declare class FetchTokenOrderError extends KubunMutationError {
73
+ constructor();
74
+ get code(): 'KB17';
75
+ }
76
+ /**
77
+ * Thrown by the set apply path when a merge-policy SET omits a schema-required
78
+ * field of its model's provider group. A merge-policy SET must carry the
79
+ * complete group so a winning higher-key SET can atomically replace it (an
80
+ * omitted OPTIONAL group field is legitimate — it clears that field's stored
81
+ * value instead of throwing). Tagged `KB18` so the RPC handler can translate
82
+ * the throw into a structured wire response.
83
+ */
84
+ export declare class IncompleteProviderGroupError extends KubunMutationError {
85
+ #private;
86
+ constructor(docID: string, field: string);
87
+ get code(): 'KB18';
88
+ get docID(): string;
89
+ get field(): string;
90
+ }
91
+ /**
92
+ * Thrown by the tombstone apply path when the target document's model
93
+ * declares no `providerMergePolicy`. A tombstone advances the fetch-order
94
+ * `/fetch` liveness register, which only a merge-policy document's `assemble`
95
+ * consults — an ordinary model's liveness is governed by the plain `$set`/`/`
96
+ * HLC registers and deletes via the `/`→null CHANGE path instead. Tagged
97
+ * `KB19` so the RPC handler can translate the throw into a structured wire
98
+ * response.
99
+ */
100
+ export declare class TombstoneRequiresMergePolicyError extends KubunMutationError {
101
+ constructor(docID: string);
102
+ get code(): 'KB19';
103
+ get docID(): string;
104
+ }
105
+ /**
106
+ * Thrown by the change apply path when a CHANGE mutation targets a
107
+ * merge-policy model's provider-authoritative group. The group is single
108
+ * authority — every write to it is a fetch-token-ordered provider projection
109
+ * (a complete-group SET or a tombstone) — so a local HLC-ordered CHANGE
110
+ * against it would mix two incompatible orderings on the same fields. A
111
+ * CHANGE to a field outside the group is unaffected. Tagged `KB20` so the RPC
112
+ * handler can translate the throw into a structured wire response.
113
+ */
114
+ export declare class ProviderGroupChangeRejectedError extends KubunMutationError {
115
+ #private;
116
+ constructor(docID: string, field: string);
117
+ get code(): 'KB20';
118
+ get docID(): string;
119
+ get field(): string;
120
+ }
121
+ /**
122
+ * Thrown by the set and tombstone apply paths when a merge-policy model's
123
+ * single-authority write carries no `sourceFetchHLC`. No conforming producer
124
+ * emits a tokenless merge-policy write — `createSetMutation`/
125
+ * `createTombstoneMutation` mint one, and the tombstone wire schema requires
126
+ * it — so this is a defensive gate: it keeps the incoming token always
127
+ * present by the time a merge-policy candidate reaches classification. Tagged
128
+ * `KB21` so the RPC handler can translate the throw into a structured wire
129
+ * response.
130
+ */
131
+ export declare class MissingFetchTokenError extends KubunMutationError {
132
+ constructor(docID: string);
133
+ get code(): 'KB21';
134
+ get docID(): string;
135
+ }
136
+ /**
137
+ * Thrown by the change apply path when a generic `/`→null delete CHANGE targets
138
+ * a merge-policy model. A merge-policy document's liveness lives in the
139
+ * fetch-order registers (`$setFetch`/`/fetch`), which a delete CHANGE never
140
+ * advances — it would null the visible data while leaving the live register
141
+ * standing, so a later provider SET or any outside-group CHANGE would resurrect
142
+ * the document with stale group data. Such a deletion must flow through the
143
+ * fetch-token tombstone path instead, which carries the token that advances
144
+ * `/fetch`. The rejection is deterministic and permanent — the same mutation
145
+ * always fails. Tagged `KB22` so the RPC handler can translate the throw into a
146
+ * structured wire response.
147
+ */
148
+ export declare class MergePolicyRemoveRequiresTombstoneError extends KubunMutationError {
149
+ constructor(docID: string);
150
+ get code(): 'KB22';
151
+ get docID(): string;
152
+ }
42
153
  /**
43
154
  * Thrown by the set apply path when a mutation would reassign an existing
44
155
  * document's owner. Ownership is immutable once a document exists, so this is a
@@ -46,8 +157,7 @@ export declare class HLCDriftError extends Error {
46
157
  * Tagged `KB11` so the RPC handler can translate the throw into a structured
47
158
  * wire response.
48
159
  */
49
- export declare class OwnerChangeError extends Error {
50
- #private;
160
+ export declare class OwnerChangeError extends KubunMutationError {
51
161
  constructor(docID: string, fromOwner: string, toOwner: string);
52
162
  get code(): 'KB11';
53
163
  get docID(): string;
@@ -58,7 +168,7 @@ export declare class OwnerChangeError extends Error {
58
168
  * deterministic, permanent reject. Tagged `KB12` so the RPC handler can
59
169
  * translate the throw into a structured wire response.
60
170
  */
61
- export declare class UnsupportedMutationTypeError extends Error {
171
+ export declare class UnsupportedMutationTypeError extends KubunMutationError {
62
172
  #private;
63
173
  constructor(mutationType: string);
64
174
  get code(): 'KB12';
@@ -71,8 +181,7 @@ export declare class UnsupportedMutationTypeError extends Error {
71
181
  * preserved as `cause`. Tagged `KB13` so the RPC handler can translate the
72
182
  * throw into a structured wire response.
73
183
  */
74
- export declare class MutationSchemaValidationError extends Error {
75
- #private;
184
+ export declare class MutationSchemaValidationError extends KubunMutationError {
76
185
  constructor(docID: string, options?: {
77
186
  cause?: unknown;
78
187
  });
@@ -90,6 +199,15 @@ export declare class MutationSchemaValidationError extends Error {
90
199
  export declare class InvalidAccessPermissionsError extends Error {
91
200
  constructor(detail?: string);
92
201
  }
202
+ export declare class InvalidDefaultAccessPermissionsError extends KubunMutationError {
203
+ constructor(docID: string);
204
+ }
205
+ type InvalidAccessRulePatchErrorParams = {
206
+ docID: string;
207
+ };
208
+ export declare class InvalidAccessRulePatchError extends KubunMutationError {
209
+ constructor(params: InvalidAccessRulePatchErrorParams);
210
+ }
93
211
  /**
94
212
  * True when `err` carries a protocol error code identifying a deterministically
95
213
  * permanent mutation reject. A permanent reject is one the sender cannot fix by
@@ -98,3 +216,4 @@ export declare class InvalidAccessPermissionsError extends Error {
98
216
  * else — a transient store/db failure — is excluded so it stays retryable.
99
217
  */
100
218
  export declare function isPermanentApplyError(err: unknown): boolean;
219
+ export {};
package/lib/errors.js CHANGED
@@ -1,20 +1,40 @@
1
+ export class KubunMutationError extends Error {
2
+ #code;
3
+ #docID;
4
+ constructor(params){
5
+ super(params.message, Object.hasOwn(params, 'cause') ? {
6
+ cause: params.cause
7
+ } : undefined);
8
+ this.name = 'KubunMutationError';
9
+ this.#code = params.code;
10
+ this.#docID = params.docID;
11
+ }
12
+ get code() {
13
+ return this.#code;
14
+ }
15
+ get docID() {
16
+ return this.#docID;
17
+ }
18
+ }
1
19
  /**
2
20
  * Thrown by the mutation apply path when a non-owner writer's capability
3
21
  * check fails. Tagged with the protocol-level error code `KB07` so callers
4
22
  * — particularly the RPC handler — can translate the throw into a
5
23
  * structured wire response without losing the reason.
6
- */ export class WriteAccessDeniedError extends Error {
7
- #docID;
24
+ */ export class WriteAccessDeniedError extends KubunMutationError {
8
25
  constructor(docID){
9
- super(`Access denied: cannot write document ${docID}`);
26
+ super({
27
+ message: `Access denied: cannot write document ${docID}`,
28
+ code: 'KB07',
29
+ docID
30
+ });
10
31
  this.name = 'WriteAccessDeniedError';
11
- this.#docID = docID;
12
32
  }
13
33
  get code() {
14
34
  return 'KB07';
15
35
  }
16
36
  get docID() {
17
- return this.#docID;
37
+ return super.docID;
18
38
  }
19
39
  }
20
40
  /**
@@ -25,18 +45,20 @@
25
45
  * a victim's deterministic-unique document ID. Tagged with the protocol-level
26
46
  * error code `KB09` so callers — particularly the RPC handler — can translate
27
47
  * the throw into a structured wire response without losing the reason.
28
- */ export class DocumentIDMismatchError extends Error {
29
- #docID;
48
+ */ export class DocumentIDMismatchError extends KubunMutationError {
30
49
  constructor(docID){
31
- super(`Document ID mismatch: ${docID} is not bound to its owner and unique value`);
50
+ super({
51
+ message: `Document ID mismatch: ${docID} is not bound to its owner and unique value`,
52
+ code: 'KB09',
53
+ docID
54
+ });
32
55
  this.name = 'DocumentIDMismatchError';
33
- this.#docID = docID;
34
56
  }
35
57
  get code() {
36
58
  return 'KB09';
37
59
  }
38
60
  get docID() {
39
- return this.#docID;
61
+ return super.docID;
40
62
  }
41
63
  }
42
64
  /**
@@ -45,11 +67,14 @@
45
67
  * is a fixed property of the mutation, so resending cannot move it back under
46
68
  * the bound — the reject is permanent. Tagged `KB10` so the RPC handler can
47
69
  * translate the throw into a structured wire response.
48
- */ export class HLCDriftError extends Error {
70
+ */ export class HLCDriftError extends KubunMutationError {
49
71
  #driftMS;
50
72
  #maxDriftMS;
51
73
  constructor(driftMS, maxDriftMS){
52
- super(`HLC timestamp too far in the future: ${driftMS}ms drift exceeds ${maxDriftMS}ms limit`);
74
+ super({
75
+ message: `HLC timestamp too far in the future: ${driftMS}ms drift exceeds ${maxDriftMS}ms limit`,
76
+ code: 'KB10'
77
+ });
53
78
  this.name = 'HLCDriftError';
54
79
  this.#driftMS = driftMS;
55
80
  this.#maxDriftMS = maxDriftMS;
@@ -64,24 +89,199 @@
64
89
  return this.#maxDriftMS;
65
90
  }
66
91
  }
92
+ /**
93
+ * Thrown when a fetch token cannot be parsed as a valid HLC timestamp. Detects
94
+ * malformed tokens or NaN values that would slip past `HLC.parse` but fail
95
+ * `HLC.tryParse` validation. Tagged `KB16` so the RPC handler can translate the
96
+ * throw into a structured wire response.
97
+ */ export class InvalidFetchTokenError extends KubunMutationError {
98
+ #token;
99
+ constructor(token){
100
+ super({
101
+ message: `Invalid fetch token: malformed HLC timestamp '${token}'`,
102
+ code: 'KB16'
103
+ });
104
+ this.name = 'InvalidFetchTokenError';
105
+ this.#token = token;
106
+ }
107
+ get code() {
108
+ return 'KB16';
109
+ }
110
+ get token() {
111
+ return this.#token;
112
+ }
113
+ }
114
+ /**
115
+ * Thrown when a mutation's minted HLC timestamp predates the supplied fetch
116
+ * token (sourceFetchHLC). Since HLC is a fixed property of a signed mutation,
117
+ * this reject is permanent — resending cannot move the HLC forward. Tagged
118
+ * `KB17` so the RPC handler can translate the throw into a structured wire
119
+ * response.
120
+ */ export class FetchTokenOrderError extends KubunMutationError {
121
+ constructor(){
122
+ super({
123
+ message: 'Fetch token ordering violated: mutation HLC must be >= sourceFetchHLC',
124
+ code: 'KB17'
125
+ });
126
+ this.name = 'FetchTokenOrderError';
127
+ }
128
+ get code() {
129
+ return 'KB17';
130
+ }
131
+ }
132
+ /**
133
+ * Thrown by the set apply path when a merge-policy SET omits a schema-required
134
+ * field of its model's provider group. A merge-policy SET must carry the
135
+ * complete group so a winning higher-key SET can atomically replace it (an
136
+ * omitted OPTIONAL group field is legitimate — it clears that field's stored
137
+ * value instead of throwing). Tagged `KB18` so the RPC handler can translate
138
+ * the throw into a structured wire response.
139
+ */ export class IncompleteProviderGroupError extends KubunMutationError {
140
+ #field;
141
+ constructor(docID, field){
142
+ super({
143
+ message: `Incomplete provider group: document ${docID} SET omits required field '${field}'`,
144
+ code: 'KB18',
145
+ docID
146
+ });
147
+ this.name = 'IncompleteProviderGroupError';
148
+ this.#field = field;
149
+ }
150
+ get code() {
151
+ return 'KB18';
152
+ }
153
+ get docID() {
154
+ return super.docID;
155
+ }
156
+ get field() {
157
+ return this.#field;
158
+ }
159
+ }
160
+ /**
161
+ * Thrown by the tombstone apply path when the target document's model
162
+ * declares no `providerMergePolicy`. A tombstone advances the fetch-order
163
+ * `/fetch` liveness register, which only a merge-policy document's `assemble`
164
+ * consults — an ordinary model's liveness is governed by the plain `$set`/`/`
165
+ * HLC registers and deletes via the `/`→null CHANGE path instead. Tagged
166
+ * `KB19` so the RPC handler can translate the throw into a structured wire
167
+ * response.
168
+ */ export class TombstoneRequiresMergePolicyError extends KubunMutationError {
169
+ constructor(docID){
170
+ super({
171
+ message: `Tombstone requires a provider-merge-policy model: document ${docID}`,
172
+ code: 'KB19',
173
+ docID
174
+ });
175
+ this.name = 'TombstoneRequiresMergePolicyError';
176
+ }
177
+ get code() {
178
+ return 'KB19';
179
+ }
180
+ get docID() {
181
+ return super.docID;
182
+ }
183
+ }
184
+ /**
185
+ * Thrown by the change apply path when a CHANGE mutation targets a
186
+ * merge-policy model's provider-authoritative group. The group is single
187
+ * authority — every write to it is a fetch-token-ordered provider projection
188
+ * (a complete-group SET or a tombstone) — so a local HLC-ordered CHANGE
189
+ * against it would mix two incompatible orderings on the same fields. A
190
+ * CHANGE to a field outside the group is unaffected. Tagged `KB20` so the RPC
191
+ * handler can translate the throw into a structured wire response.
192
+ */ export class ProviderGroupChangeRejectedError extends KubunMutationError {
193
+ #field;
194
+ constructor(docID, field){
195
+ super({
196
+ message: `Provider group CHANGE rejected: document ${docID} CHANGE targets provider group field '${field}'`,
197
+ code: 'KB20',
198
+ docID
199
+ });
200
+ this.name = 'ProviderGroupChangeRejectedError';
201
+ this.#field = field;
202
+ }
203
+ get code() {
204
+ return 'KB20';
205
+ }
206
+ get docID() {
207
+ return super.docID;
208
+ }
209
+ get field() {
210
+ return this.#field;
211
+ }
212
+ }
213
+ /**
214
+ * Thrown by the set and tombstone apply paths when a merge-policy model's
215
+ * single-authority write carries no `sourceFetchHLC`. No conforming producer
216
+ * emits a tokenless merge-policy write — `createSetMutation`/
217
+ * `createTombstoneMutation` mint one, and the tombstone wire schema requires
218
+ * it — so this is a defensive gate: it keeps the incoming token always
219
+ * present by the time a merge-policy candidate reaches classification. Tagged
220
+ * `KB21` so the RPC handler can translate the throw into a structured wire
221
+ * response.
222
+ */ export class MissingFetchTokenError extends KubunMutationError {
223
+ constructor(docID){
224
+ super({
225
+ message: `Missing fetch token: document ${docID} merge-policy write carries no sourceFetchHLC`,
226
+ code: 'KB21',
227
+ docID
228
+ });
229
+ this.name = 'MissingFetchTokenError';
230
+ }
231
+ get code() {
232
+ return 'KB21';
233
+ }
234
+ get docID() {
235
+ return super.docID;
236
+ }
237
+ }
238
+ /**
239
+ * Thrown by the change apply path when a generic `/`→null delete CHANGE targets
240
+ * a merge-policy model. A merge-policy document's liveness lives in the
241
+ * fetch-order registers (`$setFetch`/`/fetch`), which a delete CHANGE never
242
+ * advances — it would null the visible data while leaving the live register
243
+ * standing, so a later provider SET or any outside-group CHANGE would resurrect
244
+ * the document with stale group data. Such a deletion must flow through the
245
+ * fetch-token tombstone path instead, which carries the token that advances
246
+ * `/fetch`. The rejection is deterministic and permanent — the same mutation
247
+ * always fails. Tagged `KB22` so the RPC handler can translate the throw into a
248
+ * structured wire response.
249
+ */ export class MergePolicyRemoveRequiresTombstoneError extends KubunMutationError {
250
+ constructor(docID){
251
+ super({
252
+ message: `Merge-policy remove requires a tombstone: document ${docID} must be deleted via the fetch-token tombstone path`,
253
+ code: 'KB22',
254
+ docID
255
+ });
256
+ this.name = 'MergePolicyRemoveRequiresTombstoneError';
257
+ }
258
+ get code() {
259
+ return 'KB22';
260
+ }
261
+ get docID() {
262
+ return super.docID;
263
+ }
264
+ }
67
265
  /**
68
266
  * Thrown by the set apply path when a mutation would reassign an existing
69
267
  * document's owner. Ownership is immutable once a document exists, so this is a
70
268
  * deterministic, permanent reject — the same mutation will always fail.
71
269
  * Tagged `KB11` so the RPC handler can translate the throw into a structured
72
270
  * wire response.
73
- */ export class OwnerChangeError extends Error {
74
- #docID;
271
+ */ export class OwnerChangeError extends KubunMutationError {
75
272
  constructor(docID, fromOwner, toOwner){
76
- super(`Cannot change owner from ${fromOwner} to ${toOwner} in document: ${docID}`);
273
+ super({
274
+ message: `Cannot change owner from ${fromOwner} to ${toOwner} in document: ${docID}`,
275
+ code: 'KB11',
276
+ docID
277
+ });
77
278
  this.name = 'OwnerChangeError';
78
- this.#docID = docID;
79
279
  }
80
280
  get code() {
81
281
  return 'KB11';
82
282
  }
83
283
  get docID() {
84
- return this.#docID;
284
+ return super.docID;
85
285
  }
86
286
  }
87
287
  /**
@@ -89,10 +289,13 @@
89
289
  * not support. The type is fixed in the signed mutation, so this is a
90
290
  * deterministic, permanent reject. Tagged `KB12` so the RPC handler can
91
291
  * translate the throw into a structured wire response.
92
- */ export class UnsupportedMutationTypeError extends Error {
292
+ */ export class UnsupportedMutationTypeError extends KubunMutationError {
93
293
  #mutationType;
94
294
  constructor(mutationType){
95
- super(`Unsupported mutation type: ${mutationType}`);
295
+ super({
296
+ message: `Unsupported mutation type: ${mutationType}`,
297
+ code: 'KB12'
298
+ });
96
299
  this.name = 'UnsupportedMutationTypeError';
97
300
  this.#mutationType = mutationType;
98
301
  }
@@ -109,18 +312,21 @@
109
312
  * is a deterministic, permanent reject. The original validation error is
110
313
  * preserved as `cause`. Tagged `KB13` so the RPC handler can translate the
111
314
  * throw into a structured wire response.
112
- */ export class MutationSchemaValidationError extends Error {
113
- #docID;
315
+ */ export class MutationSchemaValidationError extends KubunMutationError {
114
316
  constructor(docID, options){
115
- super(`Mutation payload failed model schema validation for document ${docID}`, options);
317
+ super({
318
+ message: `Mutation payload failed model schema validation for document ${docID}`,
319
+ code: 'KB13',
320
+ docID,
321
+ ...options
322
+ });
116
323
  this.name = 'MutationSchemaValidationError';
117
- this.#docID = docID;
118
324
  }
119
325
  get code() {
120
326
  return 'KB13';
121
327
  }
122
328
  get docID() {
123
- return this.#docID;
329
+ return super.docID;
124
330
  }
125
331
  }
126
332
  /**
@@ -136,13 +342,40 @@
136
342
  this.name = 'InvalidAccessPermissionsError';
137
343
  }
138
344
  }
345
+ export class InvalidDefaultAccessPermissionsError extends KubunMutationError {
346
+ constructor(docID){
347
+ super({
348
+ message: 'Invalid default access rule or HLC: expected a rule and HLC no later than the mutation',
349
+ code: 'KB13',
350
+ docID
351
+ });
352
+ this.name = 'InvalidDefaultAccessPermissionsError';
353
+ }
354
+ }
355
+ export class InvalidAccessRulePatchError extends KubunMutationError {
356
+ constructor(params){
357
+ super({
358
+ message: 'Invalid access rule patch: replace or remove a whole slot or read/write rule',
359
+ code: 'KB13',
360
+ docID: params.docID
361
+ });
362
+ this.name = 'InvalidAccessRulePatchError';
363
+ }
364
+ }
139
365
  const PERMANENT_APPLY_ERROR_CODES = new Set([
140
366
  'KB07',
141
367
  'KB09',
142
368
  'KB10',
143
369
  'KB11',
144
370
  'KB12',
145
- 'KB13'
371
+ 'KB13',
372
+ 'KB16',
373
+ 'KB17',
374
+ 'KB18',
375
+ 'KB19',
376
+ 'KB20',
377
+ 'KB21',
378
+ 'KB22'
146
379
  ]);
147
380
  /**
148
381
  * True when `err` carries a protocol error code identifying a deterministically
package/lib/index.d.ts CHANGED
@@ -1,8 +1,12 @@
1
1
  export { HLC, type HLCTimestamp } from '@kubun/hlc';
2
- export type { DocumentValidator, MutationContext, MutationDB, PostStateGate, ValidatorsRecord, } from './apply.js';
3
- export { applyChangeMutation, applyMutation, applySetMutation, DEFAULT_MAX_DRIFT_MS, getTopLevelField, } from './apply.js';
4
- export type { CreateChangeMutationParams, CreateRemoveMutationParams, CreateSetMutationParams, } from './create.js';
5
- export { createChangeMutation, createRemoveMutation, createSetMutation } from './create.js';
6
- export { DocumentIDMismatchError, HLCDriftError, InvalidAccessPermissionsError, isPermanentApplyError, MutationSchemaValidationError, OwnerChangeError, UnsupportedMutationTypeError, WriteAccessDeniedError, } from './errors.js';
2
+ export { isAccessSlotOnlyMutation } from './access-slot-only.js';
3
+ export type { DocumentValidator, FetchFrontier, MergeOutcome, MutationApplyResult, MutationContext, MutationDB, PostStateGate, PostStateGateContext, ValidatorsRecord, } from './apply.js';
4
+ export { applyChangeMutation, applyMutation, applySetMutation, applyTombstoneMutation, assembleDocumentData, classifySetOutcome, classifyTombstoneOutcome, DEFAULT_MAX_DRIFT_MS, DELETE_FETCH_KEY, getTopLevelField, providerGroupDigest, resolveModelPolicy, SET_FETCH_KEY, validateFetchTokenDrift, validateSetData, winnerStateDigestOf, } from './apply.js';
5
+ export type { CreateChangeMutationParams, CreateRemoveMutationParams, CreateSetMutationParams, CreateTombstoneMutationParams, } from './create.js';
6
+ export { createChangeMutation, createRemoveMutation, createSetMutation, createTombstoneMutation, } from './create.js';
7
+ export type { KubunMutationErrorCode, KubunMutationErrorParams } from './errors.js';
8
+ export { DocumentIDMismatchError, FetchTokenOrderError, HLCDriftError, IncompleteProviderGroupError, InvalidAccessPermissionsError, InvalidAccessRulePatchError, InvalidDefaultAccessPermissionsError, InvalidFetchTokenError, isPermanentApplyError, KubunMutationError, MergePolicyRemoveRequiresTombstoneError, MissingFetchTokenError, MutationSchemaValidationError, OwnerChangeError, ProviderGroupChangeRejectedError, TombstoneRequiresMergePolicyError, UnsupportedMutationTypeError, WriteAccessDeniedError, } from './errors.js';
9
+ export { type HeldProviderMutation, type HeldProviderWinner, type SelectHeldProviderWinnerParams, selectHeldProviderWinner, } from './observation-winner.js';
7
10
  export type { CreateDocumentParams, CreateMutationOperationsParams, MutationOperations, RemoveDocumentParams, SetDocumentParams, UpdateDocumentParams, } from './operations.js';
8
11
  export { convertPatchInput, createMutationOperations } from './operations.js';
12
+ export { DELETED_STATE_DIGEST, type StateDigestInput, stateDigest } from './state-digest.js';