@kubun/mutation 0.3.3 → 0.5.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 CHANGED
@@ -3,12 +3,18 @@ import type { KubunDB } from '@kubun/db';
3
3
  import type { ChangeDocumentMutation, DocumentData, DocumentMutation, DocumentNode, SetDocumentMutation } from '@kubun/protocol';
4
4
  export type DocumentValidator = Validator<DocumentData>;
5
5
  export type ValidatorsRecord = Record<string, Promise<DocumentValidator>>;
6
+ export type AccessChecker = (doc: {
7
+ id: string;
8
+ model: string;
9
+ owner: string;
10
+ data: DocumentData | null;
11
+ }, permissionType: 'read' | 'write') => Promise<boolean>;
6
12
  export type MutationContext = {
7
13
  db: KubunDB;
8
14
  validators: ValidatorsRecord;
15
+ accessChecker?: AccessChecker;
9
16
  };
10
17
  export declare function getDocumentValidator(ctx: MutationContext, id: string): Promise<DocumentValidator>;
11
18
  export declare function applyChangeMutation(ctx: MutationContext, mutation: ChangeDocumentMutation): Promise<DocumentNode>;
12
19
  export declare function applySetMutation(ctx: MutationContext, mutation: SetDocumentMutation): Promise<DocumentNode>;
13
20
  export declare function applyMutation(ctx: MutationContext, mutation: DocumentMutation): Promise<DocumentNode>;
14
- //# sourceMappingURL=apply.d.ts.map
package/lib/apply.js CHANGED
@@ -1,100 +1 @@
1
- import * as A from '@automerge/automerge/slim';
2
- import { fromB64 } from '@enkaku/codec';
3
- import { asType, createValidator } from '@enkaku/schema';
4
- import { DocumentID } from '@kubun/id';
5
- import { automergeReady, automergeToData } from './automerge.js';
6
- export function getDocumentValidator(ctx, id) {
7
- if (ctx.validators[id] == null) {
8
- ctx.validators[id] = ctx.db.getDocumentModel(id).then((model)=>createValidator({
9
- ...model.schema,
10
- $id: id
11
- }));
12
- }
13
- return ctx.validators[id];
14
- }
15
- export async function applyChangeMutation(ctx, mutation) {
16
- const id = DocumentID.fromString(mutation.sub);
17
- const docID = id.toString();
18
- const doc = await ctx.db.getDocument(id);
19
- if (doc == null) {
20
- throw new Error(`Document not found: ${docID}`);
21
- }
22
- if (mutation.iss !== doc.owner) {
23
- // TODO: verify capabilities if issuer is not owner
24
- throw new Error('Invalid mutation issuer');
25
- }
26
- if (mutation.data === null) {
27
- return await ctx.db.saveDocument({
28
- id,
29
- existing: doc,
30
- data: null,
31
- state: null
32
- });
33
- }
34
- if (doc.data === null) {
35
- throw new Error(`Cannot apply changes to empty document: ${docID}`);
36
- }
37
- const [docStates, validator] = await Promise.all([
38
- ctx.db.getDocumentStates([
39
- docID
40
- ]),
41
- getDocumentValidator(ctx, id.model.toString()),
42
- automergeReady
43
- ]);
44
- // Create doc from data object if state is not present
45
- const currentDoc = docStates[docID] ? A.load(docStates[docID]) : A.from(doc.data);
46
- const changes = fromB64(mutation.data);
47
- // Apply incremental changes or full merge
48
- const newDoc = mutation.inc ? A.loadIncremental(currentDoc, changes) : A.merge(currentDoc, A.load(changes));
49
- // Validate merged data
50
- const data = asType(validator, automergeToData(newDoc));
51
- return await ctx.db.saveDocument({
52
- id,
53
- existing: doc,
54
- data,
55
- state: A.save(newDoc)
56
- });
57
- }
58
- export async function applySetMutation(ctx, mutation) {
59
- const owner = mutation.aud ?? mutation.iss;
60
- if (mutation.iss !== owner) {
61
- // TODO: verify capabilities if issuer is not owner
62
- throw new Error('Invalid mutation issuer');
63
- }
64
- const id = DocumentID.fromString(mutation.sub);
65
- const [doc, validator] = await Promise.all([
66
- ctx.db.getDocument(id),
67
- getDocumentValidator(ctx, id.model.toString()),
68
- automergeReady
69
- ]);
70
- const mergeDoc = mutation.data === null ? null : A.load(fromB64(mutation.data));
71
- const data = mergeDoc ? asType(validator, automergeToData(mergeDoc)) : null;
72
- if (doc === null) {
73
- return await ctx.db.createDocument({
74
- id,
75
- owner,
76
- data,
77
- state: mergeDoc ? A.save(mergeDoc) : null,
78
- unique: fromB64(mutation.unq)
79
- });
80
- }
81
- if (doc.owner !== owner) {
82
- throw new Error(`Cannot change owner from ${doc.owner} to ${owner} in document: ${id.toString()}`);
83
- }
84
- return await ctx.db.saveDocument({
85
- id,
86
- existing: doc,
87
- data,
88
- state: mergeDoc ? A.save(mergeDoc) : null
89
- });
90
- }
91
- export async function applyMutation(ctx, mutation) {
92
- switch(mutation.typ){
93
- case 'change':
94
- return await applyChangeMutation(ctx, mutation);
95
- case 'set':
96
- return await applySetMutation(ctx, mutation);
97
- default:
98
- throw new Error('Unsupported mutation type');
99
- }
100
- }
1
+ import*as t from"@automerge/automerge/slim";import{fromB64 as e}from"@enkaku/codec";import{asType as a,createValidator as r}from"@enkaku/schema";import{DocumentID as o}from"@kubun/id";import{automergeReady as n,automergeToData as i}from"./automerge.js";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]}export async function applyChangeMutation(r,u){let s=o.fromString(u.sub),l=s.toString(),c=await r.db.getDocument(s);if(null==c)throw Error(`Document not found: ${l}`);if(u.iss!==c.owner)throw Error("Invalid mutation issuer");if(r.accessChecker&&!await r.accessChecker(c,"write"))throw Error(`Access denied: cannot write document ${l}`);if(null===u.data)return await r.db.saveDocument({id:s,existing:c,data:null,state:null});if(null===c.data)throw Error(`Cannot apply changes to empty document: ${l}`);let[m,d]=await Promise.all([r.db.getDocumentStates([l]),getDocumentValidator(r,s.model.toString()),n]),w=m[l]?t.load(m[l]):t.from(c.data),g=e(u.data),p=u.inc?t.loadIncremental(w,g):t.merge(w,t.load(g)),f=a(d,i(p));return await r.db.saveDocument({id:s,existing:c,data:f,state:t.save(p)})}export async function applySetMutation(r,u){let s=u.aud??u.iss;if(u.iss!==s)throw Error("Invalid mutation issuer");let l=o.fromString(u.sub),[c,m]=await Promise.all([r.db.getDocument(l),getDocumentValidator(r,l.model.toString()),n]),d=null===u.data?null:t.load(e(u.data)),w=d?a(m,i(d)):null;if(null===c)return await r.db.createDocument({id:l,owner:s,data:w,state:d?t.save(d):null,unique:e(u.unq)});if(c.owner!==s)throw Error(`Cannot change owner from ${c.owner} to ${s} in document: ${l.toString()}`);if(r.accessChecker&&!await r.accessChecker(c,"write"))throw Error(`Access denied: cannot write document ${l.toString()}`);return await r.db.saveDocument({id:l,existing:c,data:w,state:d?t.save(d):null})}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")}}
@@ -2,4 +2,3 @@ import * as A from '@automerge/automerge/slim';
2
2
  import type { DocumentData } from '@kubun/protocol';
3
3
  export declare const automergeReady: import("@enkaku/async").LazyPromise<void>;
4
4
  export declare function automergeToData(doc: A.Doc<DocumentData>): DocumentData;
5
- //# sourceMappingURL=automerge.d.ts.map
package/lib/automerge.js CHANGED
@@ -1,7 +1 @@
1
- import { automergeWasmBase64 } from '@automerge/automerge/automerge.wasm.base64';
2
- import * as A from '@automerge/automerge/slim';
3
- import { lazy } from '@enkaku/async';
4
- export const automergeReady = lazy(()=>A.initializeBase64Wasm(automergeWasmBase64));
5
- export function automergeToData(doc) {
6
- return JSON.parse(JSON.stringify(doc));
7
- }
1
+ import{automergeWasmBase64 as e}from"@automerge/automerge/automerge.wasm.base64";import*as a from"@automerge/automerge/slim";import{lazy as r}from"@enkaku/async";export const automergeReady=r(()=>a.initializeBase64Wasm(e));export function automergeToData(e){return JSON.parse(JSON.stringify(e))}
package/lib/create.d.ts CHANGED
@@ -22,4 +22,3 @@ export type CreateRemoveMutationParams = {
22
22
  issuer: string;
23
23
  };
24
24
  export declare function createRemoveMutation(params: CreateRemoveMutationParams): ChangeDocumentMutation;
25
- //# sourceMappingURL=create.d.ts.map
package/lib/create.js CHANGED
@@ -1,52 +1 @@
1
- import * as A from '@automerge/automerge/slim';
2
- import { toB64 } from '@enkaku/codec';
3
- import { applyPatches } from '@enkaku/patch';
4
- import { DocumentID } from '@kubun/id';
5
- import { automergeReady } from './automerge.js';
6
- export async function createSetMutation(params) {
7
- const issuer = params.issuer;
8
- const owner = params.owner ?? issuer;
9
- const [docID] = await Promise.all([
10
- DocumentID.create(params.modelID, owner, params.unique),
11
- automergeReady
12
- ]);
13
- return {
14
- typ: 'set',
15
- iss: issuer,
16
- aud: owner,
17
- sub: docID.toString(),
18
- data: params.data == null ? null : toB64(A.save(A.from(params.data))),
19
- unq: toB64(params.unique)
20
- };
21
- }
22
- export async function createChangeMutation(params) {
23
- const docID = DocumentID.from(params.docID).toString();
24
- // Load current state of document from DB
25
- const [state] = await Promise.all([
26
- params.loadState(docID),
27
- automergeReady
28
- ]);
29
- const loadedDoc = state ? A.load(state) : null;
30
- // Apply patches to loaded doc or locally created one
31
- const newDoc = A.change(loadedDoc ?? A.from(params.from ?? {}), (proxy)=>{
32
- applyPatches(proxy, params.patch);
33
- });
34
- // Save incremental or full data state
35
- const data = loadedDoc ? A.saveSince(newDoc, A.getHeads(loadedDoc)) : A.save(newDoc);
36
- return {
37
- typ: 'change',
38
- iss: params.issuer,
39
- sub: docID,
40
- data: toB64(data),
41
- inc: loadedDoc != null
42
- };
43
- }
44
- export function createRemoveMutation(params) {
45
- return {
46
- typ: 'change',
47
- iss: params.issuer,
48
- sub: params.docID,
49
- data: null,
50
- inc: false
51
- };
52
- }
1
+ import*as t from"@automerge/automerge/slim";import{toB64 as e}from"@enkaku/codec";import{applyPatches as a}from"@enkaku/patch";import{DocumentID as o}from"@kubun/id";import{automergeReady as r}from"./automerge.js";export async function createSetMutation(a){let n=a.issuer,u=a.owner??n,[i]=await Promise.all([o.create(a.modelID,u,a.unique),r]);return{typ:"set",iss:n,aud:u,sub:i.toString(),data:null==a.data?null:e(t.save(t.from(a.data))),unq:e(a.unique)}}export async function createChangeMutation(n){let u=o.from(n.docID).toString(),[i]=await Promise.all([n.loadState(u),r]),s=i?t.load(i):null,m=t.change(s??t.from(n.from??{}),t=>{a(t,n.patch)}),c=s?t.saveSince(m,t.getHeads(s)):t.save(m);return{typ:"change",iss:n.issuer,sub:u,data:e(c),inc:null!=s}}export function createRemoveMutation(t){return{typ:"change",iss:t.issuer,sub:t.docID,data:null,inc:!1}}
package/lib/index.d.ts CHANGED
@@ -2,4 +2,3 @@ export type { DocumentValidator, MutationContext, ValidatorsRecord } from './app
2
2
  export { applyChangeMutation, applyMutation, applySetMutation } from './apply.js';
3
3
  export type { CreateChangeMutationParams, CreateRemoveMutationParams, CreateSetMutationParams, } from './create.js';
4
4
  export { createChangeMutation, createRemoveMutation, createSetMutation } from './create.js';
5
- //# sourceMappingURL=index.d.ts.map
package/lib/index.js CHANGED
@@ -1,2 +1 @@
1
- export { applyChangeMutation, applyMutation, applySetMutation } from './apply.js';
2
- export { createChangeMutation, createRemoveMutation, createSetMutation } from './create.js';
1
+ export{applyChangeMutation,applyMutation,applySetMutation}from"./apply.js";export{createChangeMutation,createRemoveMutation,createSetMutation}from"./create.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubun/mutation",
3
- "version": "0.3.3",
3
+ "version": "0.5.0",
4
4
  "license": "see LICENSE.md",
5
5
  "keywords": [],
6
6
  "type": "module",
@@ -15,17 +15,17 @@
15
15
  ],
16
16
  "sideEffects": false,
17
17
  "dependencies": {
18
- "@automerge/automerge": "^3.1.1",
19
- "@enkaku/async": "^0.12.2",
20
- "@enkaku/codec": "^0.12.0",
21
- "@enkaku/patch": "^0.12.2",
22
- "@enkaku/schema": "^0.12.1",
23
- "@kubun/id": "^0.3.1"
18
+ "@automerge/automerge": "^3.2.3",
19
+ "@enkaku/async": "^0.13.0",
20
+ "@enkaku/codec": "^0.13.0",
21
+ "@enkaku/patch": "^0.13.0",
22
+ "@enkaku/schema": "^0.13.0",
23
+ "@kubun/id": "^0.5.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@kubun/graphql": "^0.3.10",
27
- "@kubun/protocol": "^0.3.6",
28
- "@kubun/db": "^0.3.5"
26
+ "@kubun/graphql": "^0.5.0",
27
+ "@kubun/db": "^0.5.0",
28
+ "@kubun/protocol": "^0.5.0"
29
29
  },
30
30
  "scripts": {
31
31
  "build:clean": "del lib",
@@ -34,7 +34,7 @@
34
34
  "build:types:ci": "tsc --emitDeclarationOnly --declarationMap false",
35
35
  "build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
36
36
  "test:types": "tsc --noEmit",
37
- "test:unit": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js",
37
+ "test:unit": "vitest run",
38
38
  "test": "pnpm run test:types && pnpm run test:unit"
39
39
  }
40
40
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../src/apply.ts"],"names":[],"mappings":"AAEA,OAAO,EAA2B,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAA;AACxE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAExC,OAAO,KAAK,EACV,sBAAsB,EACtB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,mBAAmB,EACpB,MAAM,iBAAiB,CAAA;AAIxB,MAAM,MAAM,iBAAiB,GAAG,SAAS,CAAC,YAAY,CAAC,CAAA;AACvD,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAA;AAEzE,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,OAAO,CAAA;IACX,UAAU,EAAE,gBAAgB,CAAA;CAC7B,CAAA;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAOjG;AAED,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,eAAe,EACpB,QAAQ,EAAE,sBAAsB,GAC/B,OAAO,CAAC,YAAY,CAAC,CAoCvB;AAED,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,eAAe,EACpB,QAAQ,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAqCvB;AAED,wBAAsB,aAAa,CACjC,GAAG,EAAE,eAAe,EACpB,QAAQ,EAAE,gBAAgB,GACzB,OAAO,CAAC,YAAY,CAAC,CASvB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"automerge.d.ts","sourceRoot":"","sources":["../src/automerge.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,2BAA2B,CAAA;AAE9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAEnD,eAAO,MAAM,cAAc,2CAA0D,CAAA;AAErF,wBAAgB,eAAe,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,YAAY,CAEtE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../src/create.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,WAAW,CAAA;AAC5D,OAAO,KAAK,EAAE,sBAAsB,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAIhG,MAAM,MAAM,uBAAuB,CAAC,IAAI,SAAS,YAAY,GAAG,YAAY,IAAI;IAC9E,IAAI,EAAE,IAAI,GAAG,IAAI,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,eAAe,GAAG,MAAM,CAAA;IACjC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,UAAU,CAAA;CACnB,CAAA;AAED,wBAAsB,iBAAiB,CAAC,IAAI,SAAS,YAAY,GAAG,YAAY,EAC9E,MAAM,EAAE,uBAAuB,CAAC,IAAI,CAAC,GACpC,OAAO,CAAC,mBAAmB,CAAC,CAe9B;AAED,MAAM,MAAM,0BAA0B,CAAC,IAAI,SAAS,YAAY,GAAG,YAAY,IAAI;IACjF,KAAK,EAAE,UAAU,GAAG,MAAM,CAAA;IAC1B,IAAI,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAA;IACrD,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC,CAAA;CAC7B,CAAA;AAED,wBAAsB,oBAAoB,CAAC,IAAI,SAAS,YAAY,GAAG,YAAY,EACjF,MAAM,EAAE,0BAA0B,CAAC,IAAI,CAAC,GACvC,OAAO,CAAC,sBAAsB,CAAC,CAkBjC;AAED,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,0BAA0B,GAAG,sBAAsB,CAQ/F"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AACtF,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AACjF,YAAY,EACV,0BAA0B,EAC1B,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA"}