@kubun/mutation 0.7.0 → 0.8.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/apply.d.ts +2 -1
- package/lib/apply.js +1 -1
- package/lib/create.d.ts +1 -1
- package/lib/create.js +1 -1
- package/lib/index.d.ts +3 -3
- package/lib/index.js +1 -1
- package/lib/operations.d.ts +3 -3
- package/lib/operations.js +1 -1
- package/package.json +9 -6
- package/lib/hlc.d.ts +0 -19
- package/lib/hlc.js +0 -1
package/lib/apply.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Validator } from '@enkaku/schema';
|
|
2
2
|
import type { WritableDB } from '@kubun/db';
|
|
3
|
+
import { HLC } from '@kubun/hlc';
|
|
3
4
|
import type { ChangeDocumentMutation, DocumentData, DocumentMutation, DocumentNode, SetDocumentMutation } from '@kubun/protocol';
|
|
4
|
-
import { HLC } from './hlc.js';
|
|
5
5
|
export type DocumentValidator = Validator<DocumentData>;
|
|
6
6
|
export type ValidatorsRecord = Record<string, Promise<DocumentValidator>>;
|
|
7
7
|
export type AccessChecker = (doc: {
|
|
@@ -18,6 +18,7 @@ export type MutationContext = {
|
|
|
18
18
|
maxDriftMS?: number;
|
|
19
19
|
};
|
|
20
20
|
export declare function getDocumentValidator(ctx: MutationContext, id: string): Promise<DocumentValidator>;
|
|
21
|
+
export declare function getTopLevelField(path: string): string;
|
|
21
22
|
export declare function applyChangeMutation(ctx: MutationContext, mutation: ChangeDocumentMutation): Promise<DocumentNode>;
|
|
22
23
|
export declare function applySetMutation(ctx: MutationContext, mutation: SetDocumentMutation): Promise<DocumentNode>;
|
|
23
24
|
export declare function applyMutation(ctx: MutationContext, mutation: DocumentMutation): Promise<DocumentNode>;
|
package/lib/apply.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{fromB64 as t}from"@enkaku/codec";import{asType as
|
|
1
|
+
import{fromB64 as t}from"@enkaku/codec";import{applyPatches as e}from"@enkaku/patch";import{asType as a,createValidator as r}from"@enkaku/schema";import{HLC as i}from"@kubun/hlc";import{DocumentID as n}from"@kubun/id";export function getDocumentValidator(t,e){return null==t.validators[e]&&(t.validators[e]=t.db.getDocumentModel(e).then(t=>r({...t.schema,$id:e}))),t.validators[e]}function o(t,e,a){let r=i.parse(t),n=Date.now(),o=r.wallTime-n;if(o>e)throw Error(`HLC timestamp too far in the future: ${o}ms drift exceeds ${e}ms limit`);a&&a.receive(r)}export function getTopLevelField(t){let e=t.indexOf("/",1);return -1===e?t:t.slice(0,e)}export async function applyChangeMutation(t,r){let i=n.fromString(r.sub),l=i.toString(),u=await t.db.getDocument(i);if(null==u)throw Error(`Document not found: ${l}`);if(r.iss!==u.owner)throw Error("Invalid mutation issuer");if(t.accessChecker&&!await t.accessChecker(u,"write"))throw Error(`Access denied: cannot write document ${l}`);null!=t.maxDriftMS&&o(r.hlc,t.maxDriftMS,t.hlc);let c=i.model.toString(),s=await t.db.getFieldHLCs(l)??{};if(1===r.patch.length&&"replace"===r.patch[0].op&&"/"===r.patch[0].path&&null===r.patch[0].value){let e={...s,"/":r.hlc},a=Object.entries(e).every(([t,e])=>"/"===t||r.hlc>=e);return(await t.db.updateFieldHLCs(i,e),a)?await t.db.saveDocument({id:i,existing:u,data:null}):u}if(null!=s["/"]&&null===u.data)return u;let d=await getDocumentValidator(t,c),h=new Map;for(let t of r.patch){let e=getTopLevelField(t.path),a=h.get(e);null!=a?a.push(t):h.set(e,[t])}let m={...s},p=[];for(let[t,e]of h){let a=s[t];(null==a||r.hlc>a)&&(p.push(...e),m[t]=r.hlc)}if(0===p.length)return u;let f=structuredClone(u.data??{});e(f,p,!1),await t.db.updateFieldHLCs(i,m);let w=a(d,f);return await t.db.saveDocument({id:i,existing:u,data:w})}export async function applySetMutation(e,r){let i=r.aud??r.iss;if(r.iss!==i)throw Error("Invalid mutation issuer");let l=n.fromString(r.sub),u=l.toString(),c=l.model.toString();null!=e.maxDriftMS&&o(r.hlc,e.maxDriftMS,e.hlc);let[s,d]=await Promise.all([e.db.getDocument(l),getDocumentValidator(e,c)]),h=a(d,r.data),m={};for(let t of Object.keys(h))m[`/${t}`]=r.hlc;if(null===s){let a=await e.db.createDocument({id:l,owner:i,data:h,unique:t(r.unq)});return await e.db.updateFieldHLCs(l,m),a}if(s.owner!==i)throw Error(`Cannot change owner from ${s.owner} to ${i} in document: ${l.toString()}`);if(e.accessChecker&&!await e.accessChecker(s,"write"))throw Error(`Access denied: cannot write document ${l.toString()}`);let p=await e.db.getFieldHLCs(u)??{},f={...s.data??{}},w={...p},g=!1;for(let[t,e]of Object.entries(h)){let a=`/${t}`,i=p[a];(null==i||r.hlc>i)&&(f[t]=e,w[a]=r.hlc,g=!0)}if(!g)return s;await e.db.updateFieldHLCs(l,w);let b=a(d,f);return await e.db.saveDocument({id:l,existing:s,data:b})}export async function applyMutation(t,e){switch(e.typ){case"change":return await applyChangeMutation(t,e);case"set":return await applySetMutation(t,e);default:throw Error("Unsupported mutation type")}}
|
package/lib/create.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PatchOperation } from '@kubun/graphql';
|
|
2
|
+
import { HLC } from '@kubun/hlc';
|
|
2
3
|
import { DocumentID, type DocumentModelID } from '@kubun/id';
|
|
3
4
|
import type { ChangeDocumentMutation, DocumentData, SetDocumentMutation } from '@kubun/protocol';
|
|
4
|
-
import { HLC } from './hlc.js';
|
|
5
5
|
export type CreateSetMutationParams<Data extends DocumentData = DocumentData> = {
|
|
6
6
|
data: Data;
|
|
7
7
|
hlc: HLC;
|
package/lib/create.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{toB64 as e}from"@enkaku/codec";import{
|
|
1
|
+
import{toB64 as e}from"@enkaku/codec";import{HLC as t}from"@kubun/hlc";import{DocumentID as r}from"@kubun/id";export async function createSetMutation(n){let a=n.issuer,o=n.owner??a,u=await r.create(n.modelID,o,n.unique);return{typ:"set",iss:a,aud:o,sub:u.toString(),data:n.data,hlc:t.serialize(n.hlc.now()),unq:e(n.unique)}}export function createChangeMutation(e){return{typ:"change",iss:e.issuer,sub:r.from(e.docID).toString(),patch:e.patch,hlc:t.serialize(e.hlc.now())}}export function createRemoveMutation(e){return{typ:"change",iss:e.issuer,sub:e.docID,patch:[{op:"replace",path:"/",value:null}],hlc:t.serialize(e.hlc.now())}}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
export { HLC, type HLCTimestamp } from '@kubun/hlc';
|
|
1
2
|
export type { DocumentValidator, MutationContext, ValidatorsRecord } from './apply.js';
|
|
2
|
-
export { applyChangeMutation, applyMutation, applySetMutation } from './apply.js';
|
|
3
|
+
export { applyChangeMutation, applyMutation, applySetMutation, getTopLevelField } from './apply.js';
|
|
3
4
|
export type { CreateChangeMutationParams, CreateRemoveMutationParams, CreateSetMutationParams, } from './create.js';
|
|
4
5
|
export { createChangeMutation, createRemoveMutation, createSetMutation } from './create.js';
|
|
5
|
-
export {
|
|
6
|
-
export type { CreateDocumentParams, CreateMutationOperationsParams, GetRandomValues, MutationOperations, RemoveDocumentParams, SetDocumentParams, UpdateDocumentParams, } from './operations.js';
|
|
6
|
+
export type { CreateDocumentParams, CreateMutationOperationsParams, MutationOperations, RemoveDocumentParams, SetDocumentParams, UpdateDocumentParams, } from './operations.js';
|
|
7
7
|
export { convertPatchInput, createMutationOperations } from './operations.js';
|
package/lib/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{applyChangeMutation,applyMutation,applySetMutation}from"./apply.js";export{createChangeMutation,createRemoveMutation,createSetMutation}from"./create.js";export{
|
|
1
|
+
export{HLC}from"@kubun/hlc";export{applyChangeMutation,applyMutation,applySetMutation,getTopLevelField}from"./apply.js";export{createChangeMutation,createRemoveMutation,createSetMutation}from"./create.js";export{convertPatchInput,createMutationOperations}from"./operations.js";
|
package/lib/operations.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import type { GetRandomValues } from '@enkaku/runtime';
|
|
1
2
|
import type { PatchOperation } from '@kubun/graphql';
|
|
3
|
+
import type { HLC } from '@kubun/hlc';
|
|
2
4
|
import type { ChangeDocumentMutation, DocumentData, SetDocumentMutation } from '@kubun/protocol';
|
|
3
|
-
import type { HLC } from './hlc.js';
|
|
4
|
-
export type GetRandomValues = <T extends ArrayBufferView>(array: T) => T;
|
|
5
5
|
export type CreateMutationOperationsParams<T> = {
|
|
6
6
|
issuer: string;
|
|
7
7
|
hlc: HLC;
|
|
8
|
-
getRandomValues
|
|
8
|
+
getRandomValues: GetRandomValues;
|
|
9
9
|
processSetMutation(mutation: SetDocumentMutation, transactionID?: string | null): Promise<T>;
|
|
10
10
|
processChangeMutation(mutation: ChangeDocumentMutation, transactionID?: string | null): Promise<T>;
|
|
11
11
|
};
|
package/lib/operations.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createChangeMutation as t,createRemoveMutation as e,createSetMutation as
|
|
1
|
+
import{createChangeMutation as t,createRemoveMutation as e,createSetMutation as r}from"./create.js";export function createMutationOperations(n){let{issuer:a,hlc:c,getRandomValues:u,processSetMutation:o,processChangeMutation:i}=n;return{async createDocument({modelID:t,data:e,transactionID:n}){let i=u(new Uint8Array(12)),m=await r({issuer:a,hlc:c,modelID:t,data:e,unique:i});return await o(m,n)},async setDocument({modelID:t,unique:e,data:n,transactionID:u}){let i=await r({issuer:a,hlc:c,modelID:t,data:n,unique:e});return await o(i,u)},async updateDocument({docID:e,patch:r,transactionID:n}){let u=t({issuer:a,hlc:c,docID:e,patch:r});return await i(u,n)},async removeDocument({docID:t,transactionID:r}){let n=e({docID:t,issuer:a,hlc:c});return await i(n,r)}}}export function convertPatchInput(t){return t.map(t=>{let[e,r]=Object.entries(t)[0];return{...r,op:e}})}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/mutation",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"license": "see LICENSE.md",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"type": "module",
|
|
@@ -16,13 +16,16 @@
|
|
|
16
16
|
"sideEffects": false,
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@enkaku/codec": "^0.14.0",
|
|
19
|
+
"@enkaku/patch": "^0.14.0",
|
|
20
|
+
"@enkaku/runtime": "^0.14.0",
|
|
19
21
|
"@enkaku/schema": "^0.14.0",
|
|
20
|
-
"@kubun/
|
|
22
|
+
"@kubun/hlc": "^0.8.0",
|
|
23
|
+
"@kubun/id": "^0.8.0"
|
|
21
24
|
},
|
|
22
25
|
"devDependencies": {
|
|
23
|
-
"@kubun/
|
|
24
|
-
"@kubun/db": "^0.
|
|
25
|
-
"@kubun/
|
|
26
|
+
"@kubun/graphql": "^0.8.0",
|
|
27
|
+
"@kubun/db": "^0.8.0",
|
|
28
|
+
"@kubun/protocol": "^0.8.0"
|
|
26
29
|
},
|
|
27
30
|
"scripts": {
|
|
28
31
|
"build:clean": "del lib",
|
|
@@ -30,7 +33,7 @@
|
|
|
30
33
|
"build:types": "tsc --emitDeclarationOnly --skipLibCheck",
|
|
31
34
|
"build:types:ci": "tsc --emitDeclarationOnly --declarationMap false",
|
|
32
35
|
"build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
|
|
33
|
-
"test:types": "tsc --noEmit",
|
|
36
|
+
"test:types": "tsc --noEmit -p tsconfig.test.json",
|
|
34
37
|
"test:unit": "vitest run",
|
|
35
38
|
"test": "pnpm run test:types && pnpm run test:unit"
|
|
36
39
|
}
|
package/lib/hlc.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export type HLCTimestamp = {
|
|
2
|
-
wallTime: number;
|
|
3
|
-
counter: number;
|
|
4
|
-
nodeID: string;
|
|
5
|
-
};
|
|
6
|
-
type HLCParams = {
|
|
7
|
-
nodeID: string;
|
|
8
|
-
};
|
|
9
|
-
export declare class HLC {
|
|
10
|
-
#private;
|
|
11
|
-
constructor(params: HLCParams);
|
|
12
|
-
get nodeID(): string;
|
|
13
|
-
now(): HLCTimestamp;
|
|
14
|
-
receive(remote: HLCTimestamp): HLCTimestamp;
|
|
15
|
-
static serialize(ts: HLCTimestamp): string;
|
|
16
|
-
static parse(s: string): HLCTimestamp;
|
|
17
|
-
static compare(a: HLCTimestamp, b: HLCTimestamp): number;
|
|
18
|
-
}
|
|
19
|
-
export {};
|
package/lib/hlc.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export class HLC{#t;#e=0;#l=0;constructor(t){this.#t=t.nodeID}get nodeID(){return this.#t}now(){let t=Date.now();return t>this.#e?(this.#e=t,this.#l=0):this.#l++,{wallTime:this.#e,counter:this.#l,nodeID:this.#t}}receive(t){let e=Date.now();return e>this.#e&&e>t.wallTime?(this.#e=e,this.#l=0):t.wallTime>this.#e?(this.#e=t.wallTime,this.#l=t.counter+1):this.#e>t.wallTime?this.#l++:this.#l=Math.max(this.#l,t.counter)+1,{wallTime:this.#e,counter:this.#l,nodeID:this.#t}}static serialize(t){let e=new Date(t.wallTime).toISOString(),l=t.counter.toString().padStart(4,"0");return`${e}:${l}:${t.nodeID}`}static parse(t){let e=t.indexOf("Z"),l=new Date(t.slice(0,e+1)).getTime(),i=t.slice(e+2),a=i.indexOf(":");return{wallTime:l,counter:Number.parseInt(i.slice(0,a),10),nodeID:i.slice(a+1)}}static compare(t,e){return t.wallTime!==e.wallTime?t.wallTime-e.wallTime:t.counter!==e.counter?t.counter-e.counter:t.nodeID<e.nodeID?-1:+(t.nodeID>e.nodeID)}}
|