@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/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
@@ -39,6 +39,109 @@ export declare class HLCDriftError extends Error {
39
39
  get driftMS(): number;
40
40
  get maxDriftMS(): number;
41
41
  }
42
+ /**
43
+ * Thrown when a fetch token cannot be parsed as a valid HLC timestamp. Detects
44
+ * malformed tokens or NaN values that would slip past `HLC.parse` but fail
45
+ * `HLC.tryParse` validation. Tagged `KB16` so the RPC handler can translate the
46
+ * throw into a structured wire response.
47
+ */
48
+ export declare class InvalidFetchTokenError extends Error {
49
+ #private;
50
+ constructor(token: string);
51
+ get code(): 'KB16';
52
+ get token(): string;
53
+ }
54
+ /**
55
+ * Thrown when a mutation's minted HLC timestamp predates the supplied fetch
56
+ * token (sourceFetchHLC). Since HLC is a fixed property of a signed mutation,
57
+ * this reject is permanent — resending cannot move the HLC forward. Tagged
58
+ * `KB17` so the RPC handler can translate the throw into a structured wire
59
+ * response.
60
+ */
61
+ export declare class FetchTokenOrderError extends Error {
62
+ constructor();
63
+ get code(): 'KB17';
64
+ }
65
+ /**
66
+ * Thrown by the set apply path when a merge-policy SET omits a schema-required
67
+ * field of its model's provider group. A merge-policy SET must carry the
68
+ * complete group so a winning higher-key SET can atomically replace it (an
69
+ * omitted OPTIONAL group field is legitimate — it clears that field's stored
70
+ * value instead of throwing). Tagged `KB18` so the RPC handler can translate
71
+ * the throw into a structured wire response.
72
+ */
73
+ export declare class IncompleteProviderGroupError extends Error {
74
+ #private;
75
+ constructor(docID: string, field: string);
76
+ get code(): 'KB18';
77
+ get docID(): string;
78
+ get field(): string;
79
+ }
80
+ /**
81
+ * Thrown by the tombstone apply path when the target document's model
82
+ * declares no `providerMergePolicy`. A tombstone advances the fetch-order
83
+ * `/fetch` liveness register, which only a merge-policy document's `assemble`
84
+ * consults — an ordinary model's liveness is governed by the plain `$set`/`/`
85
+ * HLC registers and deletes via the `/`→null CHANGE path instead. Tagged
86
+ * `KB19` so the RPC handler can translate the throw into a structured wire
87
+ * response.
88
+ */
89
+ export declare class TombstoneRequiresMergePolicyError extends Error {
90
+ #private;
91
+ constructor(docID: string);
92
+ get code(): 'KB19';
93
+ get docID(): string;
94
+ }
95
+ /**
96
+ * Thrown by the change apply path when a CHANGE mutation targets a
97
+ * merge-policy model's provider-authoritative group. The group is single
98
+ * authority — every write to it is a fetch-token-ordered provider projection
99
+ * (a complete-group SET or a tombstone) — so a local HLC-ordered CHANGE
100
+ * against it would mix two incompatible orderings on the same fields. A
101
+ * CHANGE to a field outside the group is unaffected. Tagged `KB20` so the RPC
102
+ * handler can translate the throw into a structured wire response.
103
+ */
104
+ export declare class ProviderGroupChangeRejectedError extends Error {
105
+ #private;
106
+ constructor(docID: string, field: string);
107
+ get code(): 'KB20';
108
+ get docID(): string;
109
+ get field(): string;
110
+ }
111
+ /**
112
+ * Thrown by the set and tombstone apply paths when a merge-policy model's
113
+ * single-authority write carries no `sourceFetchHLC`. No conforming producer
114
+ * emits a tokenless merge-policy write — `createSetMutation`/
115
+ * `createTombstoneMutation` mint one, and the tombstone wire schema requires
116
+ * it — so this is a defensive gate: it keeps the incoming token always
117
+ * present by the time a merge-policy candidate reaches classification. Tagged
118
+ * `KB21` so the RPC handler can translate the throw into a structured wire
119
+ * response.
120
+ */
121
+ export declare class MissingFetchTokenError extends Error {
122
+ #private;
123
+ constructor(docID: string);
124
+ get code(): 'KB21';
125
+ get docID(): string;
126
+ }
127
+ /**
128
+ * Thrown by the change apply path when a generic `/`→null delete CHANGE targets
129
+ * a merge-policy model. A merge-policy document's liveness lives in the
130
+ * fetch-order registers (`$setFetch`/`/fetch`), which a delete CHANGE never
131
+ * advances — it would null the visible data while leaving the live register
132
+ * standing, so a later provider SET or any outside-group CHANGE would resurrect
133
+ * the document with stale group data. Such a deletion must flow through the
134
+ * fetch-token tombstone path instead, which carries the token that advances
135
+ * `/fetch`. The rejection is deterministic and permanent — the same mutation
136
+ * always fails. Tagged `KB22` so the RPC handler can translate the throw into a
137
+ * structured wire response.
138
+ */
139
+ export declare class MergePolicyRemoveRequiresTombstoneError extends Error {
140
+ #private;
141
+ constructor(docID: string);
142
+ get code(): 'KB22';
143
+ get docID(): string;
144
+ }
42
145
  /**
43
146
  * Thrown by the set apply path when a mutation would reassign an existing
44
147
  * document's owner. Ownership is immutable once a document exists, so this is a
package/lib/errors.js CHANGED
@@ -64,6 +64,163 @@
64
64
  return this.#maxDriftMS;
65
65
  }
66
66
  }
67
+ /**
68
+ * Thrown when a fetch token cannot be parsed as a valid HLC timestamp. Detects
69
+ * malformed tokens or NaN values that would slip past `HLC.parse` but fail
70
+ * `HLC.tryParse` validation. Tagged `KB16` so the RPC handler can translate the
71
+ * throw into a structured wire response.
72
+ */ export class InvalidFetchTokenError extends Error {
73
+ #token;
74
+ constructor(token){
75
+ super(`Invalid fetch token: malformed HLC timestamp '${token}'`);
76
+ this.name = 'InvalidFetchTokenError';
77
+ this.#token = token;
78
+ }
79
+ get code() {
80
+ return 'KB16';
81
+ }
82
+ get token() {
83
+ return this.#token;
84
+ }
85
+ }
86
+ /**
87
+ * Thrown when a mutation's minted HLC timestamp predates the supplied fetch
88
+ * token (sourceFetchHLC). Since HLC is a fixed property of a signed mutation,
89
+ * this reject is permanent — resending cannot move the HLC forward. Tagged
90
+ * `KB17` so the RPC handler can translate the throw into a structured wire
91
+ * response.
92
+ */ export class FetchTokenOrderError extends Error {
93
+ constructor(){
94
+ super('Fetch token ordering violated: mutation HLC must be >= sourceFetchHLC');
95
+ this.name = 'FetchTokenOrderError';
96
+ }
97
+ get code() {
98
+ return 'KB17';
99
+ }
100
+ }
101
+ /**
102
+ * Thrown by the set apply path when a merge-policy SET omits a schema-required
103
+ * field of its model's provider group. A merge-policy SET must carry the
104
+ * complete group so a winning higher-key SET can atomically replace it (an
105
+ * omitted OPTIONAL group field is legitimate — it clears that field's stored
106
+ * value instead of throwing). Tagged `KB18` so the RPC handler can translate
107
+ * the throw into a structured wire response.
108
+ */ export class IncompleteProviderGroupError extends Error {
109
+ #docID;
110
+ #field;
111
+ constructor(docID, field){
112
+ super(`Incomplete provider group: document ${docID} SET omits required field '${field}'`);
113
+ this.name = 'IncompleteProviderGroupError';
114
+ this.#docID = docID;
115
+ this.#field = field;
116
+ }
117
+ get code() {
118
+ return 'KB18';
119
+ }
120
+ get docID() {
121
+ return this.#docID;
122
+ }
123
+ get field() {
124
+ return this.#field;
125
+ }
126
+ }
127
+ /**
128
+ * Thrown by the tombstone apply path when the target document's model
129
+ * declares no `providerMergePolicy`. A tombstone advances the fetch-order
130
+ * `/fetch` liveness register, which only a merge-policy document's `assemble`
131
+ * consults — an ordinary model's liveness is governed by the plain `$set`/`/`
132
+ * HLC registers and deletes via the `/`→null CHANGE path instead. Tagged
133
+ * `KB19` so the RPC handler can translate the throw into a structured wire
134
+ * response.
135
+ */ export class TombstoneRequiresMergePolicyError extends Error {
136
+ #docID;
137
+ constructor(docID){
138
+ super(`Tombstone requires a provider-merge-policy model: document ${docID}`);
139
+ this.name = 'TombstoneRequiresMergePolicyError';
140
+ this.#docID = docID;
141
+ }
142
+ get code() {
143
+ return 'KB19';
144
+ }
145
+ get docID() {
146
+ return this.#docID;
147
+ }
148
+ }
149
+ /**
150
+ * Thrown by the change apply path when a CHANGE mutation targets a
151
+ * merge-policy model's provider-authoritative group. The group is single
152
+ * authority — every write to it is a fetch-token-ordered provider projection
153
+ * (a complete-group SET or a tombstone) — so a local HLC-ordered CHANGE
154
+ * against it would mix two incompatible orderings on the same fields. A
155
+ * CHANGE to a field outside the group is unaffected. Tagged `KB20` so the RPC
156
+ * handler can translate the throw into a structured wire response.
157
+ */ export class ProviderGroupChangeRejectedError extends Error {
158
+ #docID;
159
+ #field;
160
+ constructor(docID, field){
161
+ super(`Provider group CHANGE rejected: document ${docID} CHANGE targets provider group field '${field}'`);
162
+ this.name = 'ProviderGroupChangeRejectedError';
163
+ this.#docID = docID;
164
+ this.#field = field;
165
+ }
166
+ get code() {
167
+ return 'KB20';
168
+ }
169
+ get docID() {
170
+ return this.#docID;
171
+ }
172
+ get field() {
173
+ return this.#field;
174
+ }
175
+ }
176
+ /**
177
+ * Thrown by the set and tombstone apply paths when a merge-policy model's
178
+ * single-authority write carries no `sourceFetchHLC`. No conforming producer
179
+ * emits a tokenless merge-policy write — `createSetMutation`/
180
+ * `createTombstoneMutation` mint one, and the tombstone wire schema requires
181
+ * it — so this is a defensive gate: it keeps the incoming token always
182
+ * present by the time a merge-policy candidate reaches classification. Tagged
183
+ * `KB21` so the RPC handler can translate the throw into a structured wire
184
+ * response.
185
+ */ export class MissingFetchTokenError extends Error {
186
+ #docID;
187
+ constructor(docID){
188
+ super(`Missing fetch token: document ${docID} merge-policy write carries no sourceFetchHLC`);
189
+ this.name = 'MissingFetchTokenError';
190
+ this.#docID = docID;
191
+ }
192
+ get code() {
193
+ return 'KB21';
194
+ }
195
+ get docID() {
196
+ return this.#docID;
197
+ }
198
+ }
199
+ /**
200
+ * Thrown by the change apply path when a generic `/`→null delete CHANGE targets
201
+ * a merge-policy model. A merge-policy document's liveness lives in the
202
+ * fetch-order registers (`$setFetch`/`/fetch`), which a delete CHANGE never
203
+ * advances — it would null the visible data while leaving the live register
204
+ * standing, so a later provider SET or any outside-group CHANGE would resurrect
205
+ * the document with stale group data. Such a deletion must flow through the
206
+ * fetch-token tombstone path instead, which carries the token that advances
207
+ * `/fetch`. The rejection is deterministic and permanent — the same mutation
208
+ * always fails. Tagged `KB22` so the RPC handler can translate the throw into a
209
+ * structured wire response.
210
+ */ export class MergePolicyRemoveRequiresTombstoneError extends Error {
211
+ #docID;
212
+ constructor(docID){
213
+ super(`Merge-policy remove requires a tombstone: document ${docID} must be deleted via the fetch-token tombstone path`);
214
+ this.name = 'MergePolicyRemoveRequiresTombstoneError';
215
+ this.#docID = docID;
216
+ }
217
+ get code() {
218
+ return 'KB22';
219
+ }
220
+ get docID() {
221
+ return this.#docID;
222
+ }
223
+ }
67
224
  /**
68
225
  * Thrown by the set apply path when a mutation would reassign an existing
69
226
  * document's owner. Ownership is immutable once a document exists, so this is a
@@ -142,7 +299,14 @@ const PERMANENT_APPLY_ERROR_CODES = new Set([
142
299
  'KB10',
143
300
  'KB11',
144
301
  'KB12',
145
- 'KB13'
302
+ 'KB13',
303
+ 'KB16',
304
+ 'KB17',
305
+ 'KB18',
306
+ 'KB19',
307
+ 'KB20',
308
+ 'KB21',
309
+ 'KB22'
146
310
  ]);
147
311
  /**
148
312
  * True when `err` carries a protocol error code identifying a deterministically
package/lib/index.d.ts CHANGED
@@ -1,8 +1,9 @@
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 type { DocumentValidator, FetchFrontier, MergeOutcome, MutationApplyResult, MutationContext, MutationDB, PostStateGate, ValidatorsRecord, } from './apply.js';
3
+ export { applyChangeMutation, applyMutation, applySetMutation, applyTombstoneMutation, classifySetOutcome, classifyTombstoneOutcome, DEFAULT_MAX_DRIFT_MS, DELETE_FETCH_KEY, getTopLevelField, resolveModelPolicy, SET_FETCH_KEY, validateFetchTokenDrift, winnerStateDigestOf, } from './apply.js';
4
+ export type { CreateChangeMutationParams, CreateRemoveMutationParams, CreateSetMutationParams, CreateTombstoneMutationParams, } from './create.js';
5
+ export { createChangeMutation, createRemoveMutation, createSetMutation, createTombstoneMutation, } from './create.js';
6
+ export { DocumentIDMismatchError, FetchTokenOrderError, HLCDriftError, IncompleteProviderGroupError, InvalidAccessPermissionsError, InvalidFetchTokenError, isPermanentApplyError, MergePolicyRemoveRequiresTombstoneError, MissingFetchTokenError, MutationSchemaValidationError, OwnerChangeError, ProviderGroupChangeRejectedError, TombstoneRequiresMergePolicyError, UnsupportedMutationTypeError, WriteAccessDeniedError, } from './errors.js';
7
7
  export type { CreateDocumentParams, CreateMutationOperationsParams, MutationOperations, RemoveDocumentParams, SetDocumentParams, UpdateDocumentParams, } from './operations.js';
8
8
  export { convertPatchInput, createMutationOperations } from './operations.js';
9
+ export { DELETED_STATE_DIGEST, type StateDigestInput, stateDigest } from './state-digest.js';
package/lib/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { HLC } from '@kubun/hlc';
2
- export { applyChangeMutation, applyMutation, applySetMutation, DEFAULT_MAX_DRIFT_MS, getTopLevelField } from './apply.js';
3
- export { createChangeMutation, createRemoveMutation, createSetMutation } from './create.js';
4
- export { DocumentIDMismatchError, HLCDriftError, InvalidAccessPermissionsError, isPermanentApplyError, MutationSchemaValidationError, OwnerChangeError, UnsupportedMutationTypeError, WriteAccessDeniedError } from './errors.js';
2
+ export { applyChangeMutation, applyMutation, applySetMutation, applyTombstoneMutation, classifySetOutcome, classifyTombstoneOutcome, DEFAULT_MAX_DRIFT_MS, DELETE_FETCH_KEY, getTopLevelField, resolveModelPolicy, SET_FETCH_KEY, validateFetchTokenDrift, winnerStateDigestOf } from './apply.js';
3
+ export { createChangeMutation, createRemoveMutation, createSetMutation, createTombstoneMutation } from './create.js';
4
+ export { DocumentIDMismatchError, FetchTokenOrderError, HLCDriftError, IncompleteProviderGroupError, InvalidAccessPermissionsError, InvalidFetchTokenError, isPermanentApplyError, MergePolicyRemoveRequiresTombstoneError, MissingFetchTokenError, MutationSchemaValidationError, OwnerChangeError, ProviderGroupChangeRejectedError, TombstoneRequiresMergePolicyError, UnsupportedMutationTypeError, WriteAccessDeniedError } from './errors.js';
5
5
  export { convertPatchInput, createMutationOperations } from './operations.js';
6
+ export { DELETED_STATE_DIGEST, stateDigest } from './state-digest.js';
@@ -1,6 +1,5 @@
1
- import type { PatchOperation } from '@kubun/graphql';
2
1
  import type { HLC } from '@kubun/hlc';
3
- import type { ChangeDocumentMutation, DocumentData, SetDocumentMutation } from '@kubun/protocol';
2
+ import type { ChangeDocumentMutation, DocumentData, PatchOperation, SetDocumentMutation, TombstoneDocumentMutation } from '@kubun/protocol';
4
3
  import type { GetRandomValues } from '@sozai/runtime';
5
4
  export type CreateMutationOperationsParams<T> = {
6
5
  issuer: string;
@@ -10,6 +9,7 @@ export type CreateMutationOperationsParams<T> = {
10
9
  cap?: string | Array<string>;
11
10
  processSetMutation(mutation: SetDocumentMutation, transactionID?: string | null): Promise<T>;
12
11
  processChangeMutation(mutation: ChangeDocumentMutation, transactionID?: string | null): Promise<T>;
12
+ processTombstoneMutation?(mutation: TombstoneDocumentMutation, transactionID?: string | null): Promise<T>;
13
13
  };
14
14
  export type CreateDocumentParams = {
15
15
  modelID: string;
@@ -31,6 +31,7 @@ export type SetDocumentParams = {
31
31
  * Omitted means owner = the ops-level owner (or the signer when unset).
32
32
  */
33
33
  owner?: string;
34
+ sourceFetchHLC?: string;
34
35
  };
35
36
  export type UpdateDocumentParams = {
36
37
  docID: string;
@@ -41,11 +42,23 @@ export type RemoveDocumentParams = {
41
42
  docID: string;
42
43
  transactionID?: string | null;
43
44
  };
45
+ export type TombstoneDocumentParams = {
46
+ modelID: string;
47
+ unique: Uint8Array;
48
+ sourceFetchHLC: string;
49
+ transactionID?: string | null;
50
+ /**
51
+ * Per-call document owner, overriding the ops-level owner for this write only.
52
+ * Omitted means owner = the ops-level owner (or the signer when unset).
53
+ */
54
+ owner?: string;
55
+ };
44
56
  export type MutationOperations<T> = {
45
57
  createDocument(params: CreateDocumentParams): Promise<T>;
46
58
  setDocument(params: SetDocumentParams): Promise<T>;
47
59
  updateDocument(params: UpdateDocumentParams): Promise<T>;
48
60
  removeDocument(params: RemoveDocumentParams): Promise<T>;
61
+ tombstoneDocument(params: TombstoneDocumentParams): Promise<T>;
49
62
  };
50
63
  export declare function createMutationOperations<T>(params: CreateMutationOperationsParams<T>): MutationOperations<T>;
51
64
  export declare function convertPatchInput(patch: Array<Record<string, unknown>>): Array<PatchOperation>;
package/lib/operations.js CHANGED
@@ -1,6 +1,6 @@
1
- import { createChangeMutation, createRemoveMutation, createSetMutation } from './create.js';
1
+ import { createChangeMutation, createRemoveMutation, createSetMutation, createTombstoneMutation } from './create.js';
2
2
  export function createMutationOperations(params) {
3
- const { issuer, hlc, getRandomValues, owner, cap, processSetMutation, processChangeMutation } = params;
3
+ const { issuer, hlc, getRandomValues, owner, cap, processSetMutation, processChangeMutation, processTombstoneMutation } = params;
4
4
  return {
5
5
  async createDocument (params) {
6
6
  const { modelID, data, transactionID } = params;
@@ -17,7 +17,7 @@ export function createMutationOperations(params) {
17
17
  return await processSetMutation(mutation, transactionID);
18
18
  },
19
19
  async setDocument (params) {
20
- const { modelID, unique, data, transactionID } = params;
20
+ const { modelID, unique, data, transactionID, sourceFetchHLC } = params;
21
21
  const mutation = await createSetMutation({
22
22
  issuer,
23
23
  hlc,
@@ -25,7 +25,8 @@ export function createMutationOperations(params) {
25
25
  data,
26
26
  unique,
27
27
  owner: params.owner ?? owner,
28
- cap
28
+ cap,
29
+ sourceFetchHLC
29
30
  });
30
31
  return await processSetMutation(mutation, transactionID);
31
32
  },
@@ -47,6 +48,22 @@ export function createMutationOperations(params) {
47
48
  cap
48
49
  });
49
50
  return await processChangeMutation(mutation, transactionID);
51
+ },
52
+ async tombstoneDocument (params) {
53
+ if (processTombstoneMutation == null) {
54
+ throw new Error('tombstoneDocument requires processTombstoneMutation');
55
+ }
56
+ const { modelID, unique, sourceFetchHLC, transactionID } = params;
57
+ const mutation = await createTombstoneMutation({
58
+ issuer,
59
+ hlc,
60
+ modelID,
61
+ unique,
62
+ owner: params.owner ?? owner,
63
+ cap,
64
+ sourceFetchHLC
65
+ });
66
+ return await processTombstoneMutation(mutation, transactionID);
50
67
  }
51
68
  };
52
69
  }
@@ -0,0 +1,16 @@
1
+ /** The candidate's state, folded into one BLAKE3 digest with a liveness marker. */
2
+ export type StateDigestInput = {
3
+ state: 'live';
4
+ group: Record<string, unknown>;
5
+ } | {
6
+ state: 'deleted';
7
+ };
8
+ /**
9
+ * The candidate's state digest as a base64url (no padding) string. One
10
+ * definition serves the equal-token tiebreak, the `$setFetch`/`/fetch`
11
+ * registers, the watch watermarks, and the seam's candidate/winner digests.
12
+ * `group` excludes the fetch token, the digest itself, and every
13
+ * local-annotation field.
14
+ */
15
+ export declare function stateDigest(input: StateDigestInput): string;
16
+ export declare const DELETED_STATE_DIGEST: string;
@@ -0,0 +1,21 @@
1
+ import { digestJSON } from '@kubun/id';
2
+ import { toB64U } from '@sozai/codec';
3
+ /**
4
+ * The candidate's state digest as a base64url (no padding) string. One
5
+ * definition serves the equal-token tiebreak, the `$setFetch`/`/fetch`
6
+ * registers, the watch watermarks, and the seam's candidate/winner digests.
7
+ * `group` excludes the fetch token, the digest itself, and every
8
+ * local-annotation field.
9
+ */ export function stateDigest(input) {
10
+ const canonical = input.state === 'live' ? {
11
+ state: 'live',
12
+ group: input.group
13
+ } : {
14
+ state: 'deleted'
15
+ };
16
+ const bytes = digestJSON(canonical);
17
+ return toB64U(bytes);
18
+ }
19
+ export const DELETED_STATE_DIGEST = stateDigest({
20
+ state: 'deleted'
21
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubun/mutation",
3
- "version": "0.16.0",
3
+ "version": "0.17.1",
4
4
  "keywords": [],
5
5
  "license": "see LICENSE.md",
6
6
  "sideEffects": false,
@@ -15,16 +15,15 @@
15
15
  "LICENSE.md"
16
16
  ],
17
17
  "dependencies": {
18
- "@kubun/hlc": "^0.16.0",
19
- "@kubun/id": "^0.16.0",
18
+ "@kubun/hlc": "^0.17.0",
19
+ "@kubun/id": "^0.17.0",
20
20
  "@sozai/codec": "^0.4.0",
21
21
  "@sozai/patch": "^0.1.3",
22
22
  "@sozai/runtime": "^0.1.0",
23
23
  "@sozai/schema": "^0.1.2"
24
24
  },
25
25
  "devDependencies": {
26
- "@kubun/graphql": "^0.16.0",
27
- "@kubun/protocol": "^0.16.0"
26
+ "@kubun/protocol": "^0.17.1"
28
27
  },
29
28
  "publishConfig": {
30
29
  "access": "public"
@@ -32,8 +31,8 @@
32
31
  "scripts": {
33
32
  "build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
34
33
  "build:clean": "del lib",
35
- "build:js": "swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
36
- "build:types": "tsc --emitDeclarationOnly --skipLibCheck",
34
+ "build:js": "del 'lib/**/*.js' && swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
35
+ "build:types": "del 'lib/**/*.d.ts' 'lib/**/*.d.ts.map' && tsc --emitDeclarationOnly --skipLibCheck",
37
36
  "build:types:ci": "tsc --emitDeclarationOnly --declarationMap false",
38
37
  "test": "pnpm run test:types && pnpm run test:unit",
39
38
  "test:types": "tsc --noEmit -p tsconfig.test.json",