@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/access-slot-only.d.ts +3 -0
- package/lib/access-slot-only.js +23 -0
- package/lib/apply.d.ts +156 -6
- package/lib/apply.js +740 -72
- package/lib/create.d.ts +12 -2
- package/lib/create.js +29 -2
- package/lib/errors.d.ts +129 -10
- package/lib/errors.js +258 -25
- package/lib/index.d.ts +9 -5
- package/lib/index.js +6 -3
- package/lib/observation-winner.d.ts +19 -0
- package/lib/observation-winner.js +48 -0
- 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/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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
7
|
-
#docID;
|
|
24
|
+
*/ export class WriteAccessDeniedError extends KubunMutationError {
|
|
8
25
|
constructor(docID){
|
|
9
|
-
super(
|
|
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
|
|
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
|
|
29
|
-
#docID;
|
|
48
|
+
*/ export class DocumentIDMismatchError extends KubunMutationError {
|
|
30
49
|
constructor(docID){
|
|
31
|
-
super(
|
|
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
|
|
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
|
|
70
|
+
*/ export class HLCDriftError extends KubunMutationError {
|
|
49
71
|
#driftMS;
|
|
50
72
|
#maxDriftMS;
|
|
51
73
|
constructor(driftMS, maxDriftMS){
|
|
52
|
-
super(
|
|
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
|
|
74
|
-
#docID;
|
|
271
|
+
*/ export class OwnerChangeError extends KubunMutationError {
|
|
75
272
|
constructor(docID, fromOwner, toOwner){
|
|
76
|
-
super(
|
|
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
|
|
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
|
|
292
|
+
*/ export class UnsupportedMutationTypeError extends KubunMutationError {
|
|
93
293
|
#mutationType;
|
|
94
294
|
constructor(mutationType){
|
|
95
|
-
super(
|
|
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
|
|
113
|
-
#docID;
|
|
315
|
+
*/ export class MutationSchemaValidationError extends KubunMutationError {
|
|
114
316
|
constructor(docID, options){
|
|
115
|
-
super(
|
|
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
|
|
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
|
|
3
|
-
export {
|
|
4
|
-
export
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
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';
|