@graffiti-garden/api 0.2.7 → 0.2.9

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/dist/tests.mjs CHANGED
@@ -1,2 +1,1427 @@
1
- import{it as z,expect as C,describe as L}from"vitest";import{GraffitiErrorInvalidUri as J}from"@graffiti-garden/api";import{assert as N}from"vitest";function u(){let h=new Uint8Array(16);return crypto.getRandomValues(h),Array.from(h).map(q=>q.toString(16).padStart(2,"0")).join("")+"\u{1F469}\u{1F3FD}\u200D\u2764\uFE0F\u200D\u{1F48B}\u200D\u{1F469}\u{1F3FB}\u{1FAF1}\u{1F3FC}\u200D\u{1FAF2}\u{1F3FF}"}function F(){return{[u()]:u()}}function w(){return{value:F(),channels:[u(),u()]}}async function y(h){let p=await h.next();return N(!p.done&&!p.value.error,"result has no value"),p.value.value}var st=h=>{L("URI and location conversion",()=>{z("location to uri and back",async()=>{let p=h(),q={name:u(),actor:u(),source:u()},t=p.locationToUri(q),o=p.uriToLocation(t);C(q).toEqual(o)}),z("collision resistance",async()=>{let p=h(),q={name:u(),actor:u(),source:u()};for(let t of["name","actor","source"]){let o={...q,[t]:u()},e=p.locationToUri(q),a=p.locationToUri(o);C(e).not.toEqual(a)}}),z("random URI should not be a valid location",async()=>{let p=h();C(()=>p.uriToLocation("")).toThrow(J)})})};import{it as x,expect as d,describe as $}from"vitest";import{GraffitiErrorNotFound as O,GraffitiErrorSchemaMismatch as K,GraffitiErrorInvalidSchema as Q,GraffitiErrorForbidden as S,GraffitiErrorPatchTestFailed as W,GraffitiErrorPatchError as R}from"@graffiti-garden/api";var ut=(h,p,q)=>{$("CRUD",{timeout:2e4},()=>{x("put, get, delete",async()=>{let t=h(),o=p(),e={something:"hello, world~ c:"},a=[u(),u()],n=await t.put({value:e,channels:a},o);d(n.value).toEqual({}),d(n.channels).toEqual([]),d(n.allowed).toBeUndefined(),d(n.actor).toEqual(o.actor);let r=await t.get(n,{});d(r.value).toEqual(e),d(r.channels).toEqual([]),d(r.allowed).toBeUndefined(),d(r.name).toEqual(n.name),d(r.actor).toEqual(n.actor),d(r.source).toEqual(n.source),d(r.lastModified).toEqual(n.lastModified);let s={something:"goodbye, world~ :c"},l=await t.put({...n,value:s,channels:[]},o);d(l.value).toEqual(e),d(l.tombstone).toEqual(!0),d(l.name).toEqual(n.name),d(l.actor).toEqual(n.actor),d(l.source).toEqual(n.source),d(l.lastModified).toBeGreaterThan(r.lastModified);let i=await t.get(n,{});d(i.value).toEqual(s),d(i.lastModified).toEqual(l.lastModified),d(i.tombstone).toEqual(!1);let m=await t.delete(i,o);d(m.tombstone).toEqual(!0),d(m.value).toEqual(s),d(m.lastModified).toBeGreaterThan(l.lastModified);let f=await t.get(i,{});d(f).toEqual(m)}),x("get non-existant",async()=>{let t=h(),o=p(),e=await t.put(w(),o);await d(t.get({...e,name:u()},{})).rejects.toBeInstanceOf(O)}),x("put, get, delete with wrong actor",async()=>{let t=h(),o=p(),e=q();await d(t.put({value:{},channels:[],actor:e.actor},o)).rejects.toThrow(S);let a=await t.put({value:{},channels:[]},e);await d(t.delete(a,o)).rejects.toThrow(S),await d(t.patch({},a,o)).rejects.toThrow(S)}),x("put and get with schema",async()=>{let t=h(),o=p(),e={properties:{value:{properties:{something:{type:"string"},another:{type:"integer"}}}}},a={something:"hello",another:42},n=await t.put({value:a,channels:[]},o),r=await t.get(n,e);d(r.value.something).toEqual(a.something),d(r.value.another).toEqual(a.another)}),x("put and get with invalid schema",async()=>{let t=h(),o=p(),e=await t.put({value:{},channels:[]},o);await d(t.get(e,{properties:{value:{type:"asdf"}}})).rejects.toThrow(Q)}),x("put and get with wrong schema",async()=>{let t=h(),o=p(),e=await t.put({value:{hello:"world"},channels:[]},o);await d(t.get(e,{properties:{value:{properties:{hello:{type:"number"}}}}})).rejects.toThrow(K)}),x("put and get with empty access control",async()=>{let t=h(),o=p(),e=q(),a={um:"hi"},n=[u()],r=[u()],s=await t.put({value:a,allowed:n,channels:r},o),l=await t.get(s,{},o);d(l.value).toEqual(a),d(l.allowed).toEqual(n),d(l.channels).toEqual(r),await d(t.get(s,{})).rejects.toBeInstanceOf(O),await d(t.get(s,{},e)).rejects.toBeInstanceOf(O)}),x("put and get with specific access control",async()=>{let t=h(),o=p(),e=q(),a={um:"hi"},n=[u(),e.actor,u()],r=[u()],s=await t.put({value:a,allowed:n,channels:r},o),l=await t.get(s,{},o);d(l.value).toEqual(a),d(l.allowed).toEqual(n),d(l.channels).toEqual(r),await d(t.get(s,{})).rejects.toBeInstanceOf(O);let i=await t.get(s,{},e);d(i.value).toEqual(a),d(i.allowed).toEqual([e.actor]),d(i.channels).toEqual([])}),x("patch value",async()=>{let t=h(),o=p(),e={something:"hello, world~ c:"},a=await t.put({value:e,channels:[]},o),n={value:[{op:"replace",path:"/something",value:"goodbye, world~ :c"}]},r=await t.patch(n,a,o);d(r.value).toEqual(e),d(r.tombstone).toBe(!0);let s=await t.get(a,{});d(s.value).toEqual({something:"goodbye, world~ :c"}),d(r.lastModified).toBe(s.lastModified),await t.delete(a,o)}),x("patch deleted object",async()=>{let t=h(),o=p(),e=await t.put(w(),o),a=await t.delete(e,o);await d(t.patch({},e,o)).rejects.toBeInstanceOf(O)}),x("deep patch",async()=>{let t=h(),o=p(),e={something:{another:{somethingElse:"hello"}}},a=await t.put({value:e,channels:[]},o),n=await t.patch({value:[{op:"replace",path:"/something/another/somethingElse",value:"goodbye"}]},a,o),r=await t.get(a,{});d(n.value).toEqual(e),d(r.value).toEqual({something:{another:{somethingElse:"goodbye"}}})}),x("patch channels",async()=>{let t=h(),o=p(),e=[u()],a=[u()],n=await t.put({value:{},channels:e},o),r={channels:[{op:"replace",path:"/0",value:a[0]}]},s=await t.patch(r,n,o);d(s.channels).toEqual(e);let l=await t.get(n,{},o);d(l.channels).toEqual(a),await t.delete(n,o)}),x("patch 'increment' with test",async()=>{let t=h(),o=p(),e=await t.put({value:{counter:1},channels:[]},o),a=await t.patch({value:[{op:"test",path:"/counter",value:1},{op:"replace",path:"/counter",value:2}]},e,o);d(a.value).toEqual({counter:1});let n=await t.get(a,{properties:{value:{properties:{counter:{type:"integer"}}}}});d(n.value.counter).toEqual(2),await d(t.patch({value:[{op:"test",path:"/counter",value:1},{op:"replace",path:"/counter",value:3}]},e,o)).rejects.toThrow(W)}),x("invalid patch",async()=>{let t=h(),o=p(),e=w(),a=await t.put(e,o);await d(t.patch({value:[{op:"add",path:"/root",value:[]},{op:"add",path:"/root/2",value:2}]},a,o)).rejects.toThrow(R)}),x("patch channels to be wrong",async()=>{let t=h(),o=p(),e=w();e.allowed=[u()];let a=await t.put(e,o),n=[{channels:[{op:"replace",path:"",value:null}]},{channels:[{op:"replace",path:"",value:{}}]},{channels:[{op:"replace",path:"",value:["hello",["hi"]]}]},{channels:[{op:"add",path:"/0",value:1}]},{value:[{op:"replace",path:"",value:"not an object"}]},{value:[{op:"replace",path:"",value:null}]},{value:[{op:"replace",path:"",value:[]}]},{allowed:[{op:"replace",path:"",value:{}}]},{allowed:[{op:"replace",path:"",value:["hello",["hi"]]}]}];for(let s of n)await d(t.patch(s,a,o)).rejects.toThrow(R);let r=await t.get(a,{},o);d(r.value).toEqual(e.value),d(r.channels).toEqual(e.channels),d(r.allowed).toEqual(e.allowed),d(r.lastModified).toEqual(a.lastModified)})})};import{it as I,expect as v,describe as D,assert as H}from"vitest";var vt=(h,p,q)=>{D("synchronizeDiscover",()=>{I("get",async()=>{let t=h(),o=p(),e=w(),a=e.channels.slice(1),n=await t.put(e,o),r=h(),s=r.synchronizeDiscover(a,{}).next(),l=await r.get(n,{},o),i=(await s).value;if(!i||i.error)throw new Error("Error in synchronize");v(i.value.value).toEqual(e.value),v(i.value.channels).toEqual(a),v(i.value.tombstone).toBe(!1),v(i.value.lastModified).toEqual(l.lastModified)}),I("put",async()=>{let t=h(),o=p(),e=u(),a=u(),n=u(),r={hello:"world"},s=[e,n],l=await t.put({value:r,channels:s},o),i=t.synchronizeDiscover([e],{}).next(),m=t.synchronizeDiscover([a],{}).next(),f=t.synchronizeDiscover([n],{}).next(),g={goodbye:"world"},E=[a,n];await t.put({...l,value:g,channels:E},o);let b=(await i).value,B=(await m).value,j=(await f).value;if(!b||b.error||!B||B.error||!j||j.error)throw new Error("Error in synchronize");v(b.value.value).toEqual(r),v(b.value.channels).toEqual([e]),v(b.value.tombstone).toBe(!0),v(B.value.value).toEqual(g),v(B.value.channels).toEqual([a]),v(B.value.tombstone).toBe(!1),v(j.value.value).toEqual(g),v(j.value.channels).toEqual([n]),v(j.value.tombstone).toBe(!1),v(b.value.lastModified).toEqual(B.value.lastModified),v(j.value.lastModified).toEqual(B.value.lastModified)}),I("patch",async()=>{let t=h(),o=p(),e=u(),a=u(),n=u(),r={hello:"world"},s=[e,n],l=await t.put({value:r,channels:s},o),i=t.synchronizeDiscover([e],{}).next(),m=t.synchronizeDiscover([a],{}).next(),f=t.synchronizeDiscover([n],{}).next();await t.patch({value:[{op:"add",path:"/something",value:"new value"}],channels:[{op:"add",path:"/-",value:a},{op:"remove",path:`/${s.indexOf(e)}`}]},l,o);let g=(await i).value,E=(await m).value,b=(await f).value;if(!g||g.error||!E||E.error||!b||b.error)throw new Error("Error in synchronize");let B={...r,something:"new value"},j=[n,a];v(g.value.value).toEqual(r),v(g.value.channels).toEqual([e]),v(g.value.tombstone).toBe(!0),v(E.value.value).toEqual(B),v(E.value.channels).toEqual([a]),v(E.value.tombstone).toBe(!1),v(b.value.value).toEqual(B),v(b.value.channels).toEqual([n]),v(b.value.tombstone).toBe(!1),v(g.value.lastModified).toEqual(E.value.lastModified),v(b.value.lastModified).toEqual(E.value.lastModified)}),I("delete",async()=>{let t=h(),o=p(),e=[u(),u(),u()],a={hello:"world"},n=[u(),...e.slice(1)],r=await t.put({value:a,channels:n},o),s=t.synchronizeDiscover(e,{}).next();t.delete(r,o);let l=(await s).value;if(!l||l.error)throw new Error("Error in synchronize");v(l.value.tombstone).toBe(!0),v(l.value.value).toEqual(a),v(l.value.channels).toEqual(e.filter(i=>n.includes(i)))}),I("not allowed",async()=>{let t=h(),o=p(),e=q(),a=[u(),u(),u()],n=a.slice(1),r=t.synchronizeDiscover(n,{},o).next(),s=t.synchronizeDiscover(n,{},e).next(),l=t.synchronizeDiscover(n,{}).next(),i={hello:"world"},m=[u(),e.actor];await t.put({value:i,channels:a,allowed:m},o),await v(Promise.race([l,new Promise((E,b)=>setTimeout(b,100,"Timeout"))])).rejects.toThrow("Timeout");let f=(await r).value,g=(await s).value;if(!f||f.error||!g||g.error)throw new Error("Error in synchronize");v(f.value.value).toEqual(i),v(f.value.allowed).toEqual(m),v(f.value.channels).toEqual(a),v(g.value.value).toEqual(i),v(g.value.allowed).toEqual([e.actor]),v(g.value.channels).toEqual(n)})}),D("synchronizeGet",()=>{I("replace, delete",async()=>{let t=h(),o=p(),e=w(),a=await t.put(e,o),n=t.synchronizeGet(a,{}),r=n.next(),s={goodbye:"world"},l=await t.put({...a,value:s},o),i=(await r).value;H(i&&!i.error),v(i.value.value).toEqual(s),v(i.value.actor).toEqual(o.actor),v(i.value.channels).toEqual([]),v(i.value.tombstone).toBe(!1),v(i.value.lastModified).toEqual(l.lastModified),v(i.value.allowed).toBeUndefined();let m=await t.delete(l,o),f=(await n.next()).value;H(f&&!f.error),v(f.value.tombstone).toBe(!0),v(f.value.lastModified).toEqual(m.lastModified),await t.put(w(),o),await v(Promise.race([n.next(),new Promise((g,E)=>setTimeout(E,100,"Timeout"))])).rejects.toThrow("Timeout")}),I("not allowed",async()=>{let t=h(),o=p(),e=q(),a=w(),n=await t.put(a,o),r=t.synchronizeGet(n,{},o),s=t.synchronizeGet(n,{},e),l=r.next(),i=s.next(),m={goodbye:"world"},f=await t.put({...n,...a,allowed:[],value:m},o),g=(await l).value,E=(await i).value;H(g&&!g.error),H(E&&!E.error),v(g.value.value).toEqual(m),v(E.value.value).toEqual(a.value),v(g.value.actor).toEqual(o.actor),v(E.value.actor).toEqual(o.actor),v(g.value.channels).toEqual(a.channels),v(E.value.channels).toEqual([]),v(g.value.tombstone).toBe(!1),v(E.value.tombstone).toBe(!0),v(g.value.lastModified).toEqual(f.lastModified),v(E.value.lastModified).toEqual(f.lastModified)})})};import{it as M,expect as c,describe as X,assert as U}from"vitest";var gt=(h,p,q)=>{X("discover",{timeout:2e4},()=>{M("discover nothing",async()=>{let o=h().discover([],{});c(await o.next()).toHaveProperty("done",!0)}),M("discover single",async()=>{let t=h(),o=p(),e=w(),a=await t.put(e,o),n=[u(),e.channels[0]],r=t.discover(n,{}),s=await y(r);c(s.value).toEqual(e.value),c(s.channels).toEqual([e.channels[0]]),c(s.allowed).toBeUndefined(),c(s.actor).toEqual(o.actor),c(s.tombstone).toBe(!1),c(s.lastModified).toEqual(a.lastModified);let l=await r.next();c(l.done).toBe(!0)}),M("discover wrong channel",async()=>{let t=h(),o=p(),e=w();await t.put(e,o);let a=t.discover([u()],{});await c(a.next()).resolves.toHaveProperty("done",!0)}),M("discover not allowed",async()=>{let t=h(),o=p(),e=q(),a=w();a.allowed=[u(),u()];let n=await t.put(a,o),r=t.discover(a.channels,{},o),s=await y(r);c(s.value).toEqual(a.value),c(s.channels).toEqual(a.channels),c(s.allowed).toEqual(a.allowed),c(s.actor).toEqual(o.actor),c(s.tombstone).toBe(!1),c(s.lastModified).toEqual(n.lastModified);let l=t.discover(a.channels,{},e);c(await l.next()).toHaveProperty("done",!0);let i=t.discover(a.channels,{});c(await i.next()).toHaveProperty("done",!0)}),M("discover allowed",async()=>{let t=h(),o=p(),e=q(),a=w();a.allowed=[u(),e.actor,u()];let n=await t.put(a,o),r=t.discover(a.channels,{},e),s=await y(r);c(s.value).toEqual(a.value),c(s.allowed).toEqual([e.actor]),c(s.channels).toEqual(a.channels),c(s.actor).toEqual(o.actor),c(s.tombstone).toBe(!1),c(s.lastModified).toEqual(n.lastModified)});for(let t of["name","actor","lastModified"])M(`discover for ${t}`,async()=>{let o=h(),e=p(),a=q(),n=w(),r=await o.put(n,e),s=w();s.channels=n.channels,await new Promise(f=>setTimeout(f,20));let l=await o.put(s,a),i=o.discover(n.channels,{properties:{[t]:{enum:[r[t]]}}}),m=await y(i);c(m.name).toEqual(r.name),c(m.name).not.toEqual(l.name),c(m.value).toEqual(n.value),await c(i.next()).resolves.toHaveProperty("done",!0)});M("discover with lastModified range",async()=>{let t=h(),o=p(),e=w(),a=await t.put(e,o);await new Promise(G=>setTimeout(G,20));let n=await t.put(e,o);c(a.name).not.toEqual(n.name),c(a.lastModified).toBeLessThan(n.lastModified);let r=t.discover([e.channels[0]],{properties:{lastModified:{minimum:n.lastModified,exclusiveMinimum:!0}}});c(await r.next()).toHaveProperty("done",!0);let s=t.discover([e.channels[0]],{properties:{lastModified:{minimum:n.lastModified-.1,exclusiveMinimum:!0}}}),l=await y(s);c(l.name).toEqual(n.name),c(await s.next()).toHaveProperty("done",!0);let i=t.discover(e.channels,{properties:{value:{},lastModified:{minimum:n.lastModified}}}),m=await y(i);c(m.name).toEqual(n.name),c(await i.next()).toHaveProperty("done",!0);let f=t.discover(e.channels,{properties:{lastModified:{minimum:n.lastModified+.1}}});c(await f.next()).toHaveProperty("done",!0);let g=t.discover(e.channels,{properties:{lastModified:{maximum:a.lastModified,exclusiveMaximum:!0}}});c(await g.next()).toHaveProperty("done",!0);let E=t.discover(e.channels,{properties:{lastModified:{maximum:a.lastModified+.1,exclusiveMaximum:!0}}}),b=await y(E);c(b.name).toEqual(a.name),c(await E.next()).toHaveProperty("done",!0);let B=t.discover(e.channels,{properties:{lastModified:{maximum:a.lastModified}}}),j=await y(B);c(j.name).toEqual(a.name),c(await B.next()).toHaveProperty("done",!0);let A=t.discover(e.channels,{properties:{lastModified:{maximum:a.lastModified-.1}}});c(await A.next()).toHaveProperty("done",!0)}),M("discover schema allowed, as and not as owner",async()=>{let t=h(),o=p(),e=q(),a=w();a.allowed=[u(),e.actor,u()],await t.put(a,o);let n=t.discover(a.channels,{properties:{allowed:{minItems:3,not:{items:{not:{enum:[e.actor]}}}}}},o),r=await y(n);c(r.value).toEqual(a.value),await c(n.next()).resolves.toHaveProperty("done",!0);let s=t.discover(a.channels,{properties:{allowed:{minItems:3}}},e);await c(s.next()).resolves.toHaveProperty("done",!0);let l=t.discover(a.channels,{properties:{allowed:{not:{items:{not:{enum:[a.channels[0]]}}}}}},e);await c(l.next()).resolves.toHaveProperty("done",!0);let i=t.discover(a.channels,{properties:{allowed:{maxItems:1,not:{items:{not:{enum:[e.actor]}}}}}},e),m=await y(i);c(m.value).toEqual(a.value),await c(i.next()).resolves.toHaveProperty("done",!0)}),M("discover schema channels, as and not as owner",async()=>{let t=h(),o=p(),e=q(),a=w();a.channels=[u(),u(),u()],await t.put(a,o);let n=t.discover([a.channels[0],a.channels[2]],{properties:{channels:{minItems:3,not:{items:{not:{enum:[a.channels[1]]}}}}}},o),r=await y(n);c(r.value).toEqual(a.value),await c(n.next()).resolves.toHaveProperty("done",!0);let s=t.discover([a.channels[0],a.channels[2]],{properties:{channels:{minItems:3}}},e);await c(s.next()).resolves.toHaveProperty("done",!0);let l=t.discover([a.channels[0],a.channels[2]],{properties:{channels:{not:{items:{not:{enum:[a.channels[1]]}}}}}},e);await c(l.next()).resolves.toHaveProperty("done",!0);let i=t.discover([a.channels[0],a.channels[2]],{properties:{allowed:{maxItems:2,not:{items:{not:{enum:[a.channels[2]]}}}}}},e),m=await y(i);c(m.value).toEqual(a.value),await c(i.next()).resolves.toHaveProperty("done",!0)}),M("discover query for empty allowed",async()=>{let t=h(),o=p(),e=w(),a={not:{required:["allowed"]}};await t.put(e,o);let n=t.discover(e.channels,a,o),r=await y(n);c(r.value).toEqual(e.value),c(r.allowed).toBeUndefined(),await c(n.next()).resolves.toHaveProperty("done",!0);let s=w();s.allowed=[],await t.put(s,o);let l=t.discover(s.channels,a,o);await c(l.next()).resolves.toHaveProperty("done",!0)}),M("discover query for values",async()=>{let t=h(),o=p(),e=w();e.value={test:u()},await t.put(e,o);let a=w();a.channels=e.channels,a.value={test:u(),something:u()},await t.put(a,o);let n=w();n.channels=e.channels,n.value={other:u(),something:u()},await t.put(n,o);let r=new Map;for(let s of["test","something","other"]){let l=0;for await(let i of t.discover(e.channels,{properties:{value:{required:[s]}}}))U(!i.error,"result has error"),s in i.value.value&&l++;r.set(s,l)}c(r.get("test")).toBe(2),c(r.get("something")).toBe(2),c(r.get("other")).toBe(1)}),M("discover for deleted content",async()=>{let t=h(),o=p(),e=w(),a=await t.put(e,o),n=await t.delete(a,o),r=t.discover(e.channels,{}),s=await y(r);c(s.tombstone).toBe(!0),c(s.value).toEqual(e.value),c(s.channels).toEqual(e.channels),c(s.actor).toEqual(o.actor),c(s.lastModified).toEqual(n.lastModified),await c(r.next()).resolves.toHaveProperty("done",!0)}),M("discover for replaced channels",async()=>{for(let t=0;t<10;t++){let o=h(),e=p(),a=w(),n=await o.put(a,e),r=w(),s=await o.put({...n,...r},e),l=o.discover(a.channels,{}),i=await y(l);await c(l.next()).resolves.toHaveProperty("done",!0);let m=o.discover(r.channels,{}),f=await y(m);await c(m.next()).resolves.toHaveProperty("done",!0),n.lastModified===s.lastModified?(c(i.tombstone||f.tombstone).toBe(!0),c(i.tombstone&&f.tombstone).toBe(!1)):(c(i.tombstone).toBe(!0),c(i.value).toEqual(a.value),c(i.channels).toEqual(a.channels),c(i.lastModified).toEqual(s.lastModified),c(f.tombstone).toBe(!1),c(f.value).toEqual(r.value),c(f.channels).toEqual(r.channels),c(f.lastModified).toEqual(s.lastModified))}}),M("discover for patched allowed",async()=>{let t=h(),o=p(),e=w(),a=await t.put(e,o);await t.patch({allowed:[{op:"add",path:"",value:[]}]},a,o);let n=t.discover(e.channels,{}),r=await y(n);c(r.tombstone).toBe(!0),c(r.value).toEqual(e.value),c(r.channels).toEqual(e.channels),c(r.allowed).toBeUndefined(),await c(n.next()).resolves.toHaveProperty("done",!0)}),M("put concurrently and discover one",async()=>{let t=h(),o=p(),e=w();e.name=u();let a=Array(100).fill(0).map(()=>t.put(e,o));await Promise.all(a);let n=t.discover(e.channels,{}),r=0,s=0;for await(let l of n)U(!l.error,"result has error"),l.value.tombstone?r++:s++;c(r).toBe(99),c(s).toBe(1)})})};import{it as V,expect as T,describe as Y}from"vitest";var xt=(h,p,q)=>{Y("recoverOrphans",()=>{V("list orphans",async()=>{let t=h(),o=p(),e=[],a=t.recoverOrphans({},o);for await(let i of a)i.error||e.push(i.value.name);let n=w();n.channels=[];let r=await t.put(n,o),s=t.recoverOrphans({},o),l=0;for await(let i of s)i.error||i.value.name===r.name&&(l++,T(i.value.source).toBe(r.source),T(i.value.lastModified).toBe(r.lastModified));T(l).toBe(1)}),V("replaced orphan, no longer",async()=>{let t=h(),o=p(),e=w();e.channels=[];let a=await t.put(e,o),n=await t.put({...a,...e,channels:[u()]},o);T(n.name).toBe(a.name);let r=t.recoverOrphans({},o),s=0;for await(let l of r)l.error||l.value.name===a.name&&(s++,T(l.value.tombstone).toBe(!0),T(l.value.lastModified).toBe(n.lastModified),T(l.value.channels).toEqual([]));T(s).toBe(1)})})};import{it as k,expect as P,describe as Z,assert as _}from"vitest";var Tt=(h,p,q)=>{Z("channel stats",()=>{k("list channels",async()=>{let t=h(),o=p(),e=new Map,a=t.channelStats(o);for await(let l of a)l.error||e.set(l.value.channel,l.value.count);let n=[u(),u(),u()];for(let l=0;l<3;l++)for(let i=0;i<l+1;i++)await t.put({value:{index:i},channels:n.slice(0,l+1)},o);await t.put({value:{index:3},channels:[n[2]]},o);let r=t.channelStats(o),s=new Map;for await(let l of r)l.error||s.set(l.value.channel,l.value.count);s=new Map(Array.from(s).filter(([l,i])=>!e.has(l))),P(s.size).toBe(3),P(s.get(n[0])).toBe(6),P(s.get(n[1])).toBe(5),P(s.get(n[2])).toBe(4)}),k("list channels with deleted channel",async()=>{let t=h(),o=p(),e=[u(),u(),u()],a=await t.put({value:{index:2},channels:e.slice(1)},o),n=await t.put({value:{index:0},channels:e},o);await t.delete(n,o);let r=await t.put({value:{index:1},channels:e.slice(2)},o),s=t.channelStats(o),l=0,i=0;for await(let m of s){if(m.error)continue;let{channel:f,count:g,lastModified:E}=m.value;_(f!==e[0],"There should not be an object in channel[0]"),f===e[1]?(P(g).toBe(1),P(E).toBe(a.lastModified),l++):f===e[2]&&(P(g).toBe(2),P(E).toBe(r.lastModified),i++)}P(l).toBe(1),P(i).toBe(1)})})};export{ut as graffitiCRUDTests,Tt as graffitiChannelStatsTests,gt as graffitiDiscoverTests,st as graffitiLocationTests,xt as graffitiOrphanTests,vt as graffitiSynchronizeTests};
1
+ // tests/location.ts
2
+ import { it, expect, describe } from "vitest";
3
+ import { GraffitiErrorInvalidUri } from "@graffiti-garden/api";
4
+
5
+ // tests/utils.ts
6
+ import { assert } from "vitest";
7
+ function randomString() {
8
+ const array = new Uint8Array(16);
9
+ crypto.getRandomValues(array);
10
+ const str = Array.from(array).map((b) => b.toString(16).padStart(2, "0")).join("");
11
+ return str + "\u{1F469}\u{1F3FD}\u200D\u2764\uFE0F\u200D\u{1F48B}\u200D\u{1F469}\u{1F3FB}\u{1FAF1}\u{1F3FC}\u200D\u{1FAF2}\u{1F3FF}";
12
+ }
13
+ function randomValue() {
14
+ return {
15
+ [randomString()]: randomString()
16
+ };
17
+ }
18
+ function randomPutObject() {
19
+ return {
20
+ value: randomValue(),
21
+ channels: [randomString(), randomString()]
22
+ };
23
+ }
24
+ async function nextStreamValue(iterator) {
25
+ const result = await iterator.next();
26
+ assert(!result.done && !result.value.error, "result has no value");
27
+ return result.value.value;
28
+ }
29
+
30
+ // tests/location.ts
31
+ var graffitiLocationTests = (useGraffiti) => {
32
+ describe.concurrent("URI and location conversion", () => {
33
+ it("location to uri and back", async () => {
34
+ const graffiti = useGraffiti();
35
+ const location = {
36
+ name: randomString(),
37
+ actor: randomString(),
38
+ source: randomString()
39
+ };
40
+ const uri = graffiti.locationToUri(location);
41
+ const location2 = graffiti.uriToLocation(uri);
42
+ expect(location).toEqual(location2);
43
+ });
44
+ it("collision resistance", async () => {
45
+ const graffiti = useGraffiti();
46
+ const location1 = {
47
+ name: randomString(),
48
+ actor: randomString(),
49
+ source: randomString()
50
+ };
51
+ for (const prop of ["name", "actor", "source"]) {
52
+ const location2 = { ...location1, [prop]: randomString() };
53
+ const uri1 = graffiti.locationToUri(location1);
54
+ const uri2 = graffiti.locationToUri(location2);
55
+ expect(uri1).not.toEqual(uri2);
56
+ }
57
+ });
58
+ it("random URI should not be a valid location", async () => {
59
+ const graffiti = useGraffiti();
60
+ expect(() => graffiti.uriToLocation("")).toThrow(GraffitiErrorInvalidUri);
61
+ });
62
+ });
63
+ };
64
+
65
+ // tests/crud.ts
66
+ import { it as it2, expect as expect2, describe as describe2, beforeEach } from "vitest";
67
+ import {
68
+ GraffitiErrorNotFound,
69
+ GraffitiErrorSchemaMismatch,
70
+ GraffitiErrorInvalidSchema,
71
+ GraffitiErrorForbidden,
72
+ GraffitiErrorPatchTestFailed,
73
+ GraffitiErrorPatchError
74
+ } from "@graffiti-garden/api";
75
+ var graffitiCRUDTests = (useGraffiti, useSession1, useSession2) => {
76
+ describe2.concurrent(
77
+ "CRUD",
78
+ {
79
+ timeout: 2e4
80
+ },
81
+ () => {
82
+ let graffiti;
83
+ let session;
84
+ let session1;
85
+ let session2;
86
+ beforeEach(async () => {
87
+ graffiti = useGraffiti();
88
+ session1 = await useSession1();
89
+ session = session1;
90
+ session2 = await useSession2();
91
+ });
92
+ it2("put, get, delete", async () => {
93
+ const value = {
94
+ something: "hello, world~ c:"
95
+ };
96
+ const channels = [randomString(), randomString()];
97
+ const previous = await graffiti.put({ value, channels }, session);
98
+ expect2(previous.value).toEqual({});
99
+ expect2(previous.channels).toEqual([]);
100
+ expect2(previous.allowed).toBeUndefined();
101
+ expect2(previous.actor).toEqual(session.actor);
102
+ const gotten = await graffiti.get(previous, {});
103
+ expect2(gotten.value).toEqual(value);
104
+ expect2(gotten.channels).toEqual([]);
105
+ expect2(gotten.allowed).toBeUndefined();
106
+ expect2(gotten.name).toEqual(previous.name);
107
+ expect2(gotten.actor).toEqual(previous.actor);
108
+ expect2(gotten.source).toEqual(previous.source);
109
+ expect2(gotten.lastModified).toEqual(previous.lastModified);
110
+ const newValue = {
111
+ something: "goodbye, world~ :c"
112
+ };
113
+ const beforeReplaced = await graffiti.put(
114
+ { ...previous, value: newValue, channels: [] },
115
+ session
116
+ );
117
+ expect2(beforeReplaced.value).toEqual(value);
118
+ expect2(beforeReplaced.tombstone).toEqual(true);
119
+ expect2(beforeReplaced.name).toEqual(previous.name);
120
+ expect2(beforeReplaced.actor).toEqual(previous.actor);
121
+ expect2(beforeReplaced.source).toEqual(previous.source);
122
+ expect2(beforeReplaced.lastModified).toBeGreaterThanOrEqual(
123
+ gotten.lastModified
124
+ );
125
+ const afterReplaced = await graffiti.get(previous, {});
126
+ expect2(afterReplaced.value).toEqual(newValue);
127
+ expect2(afterReplaced.lastModified).toEqual(beforeReplaced.lastModified);
128
+ expect2(afterReplaced.tombstone).toEqual(false);
129
+ const beforeDeleted = await graffiti.delete(afterReplaced, session);
130
+ expect2(beforeDeleted.tombstone).toEqual(true);
131
+ expect2(beforeDeleted.value).toEqual(newValue);
132
+ expect2(beforeDeleted.lastModified).toBeGreaterThanOrEqual(
133
+ beforeReplaced.lastModified
134
+ );
135
+ const final = await graffiti.get(afterReplaced, {});
136
+ expect2(final).toEqual(beforeDeleted);
137
+ });
138
+ it2("get non-existant", async () => {
139
+ const putted = await graffiti.put(randomPutObject(), session);
140
+ await expect2(
141
+ graffiti.get(
142
+ {
143
+ ...putted,
144
+ name: randomString()
145
+ },
146
+ {}
147
+ )
148
+ ).rejects.toBeInstanceOf(GraffitiErrorNotFound);
149
+ });
150
+ it2("put, get, delete with wrong actor", async () => {
151
+ await expect2(
152
+ graffiti.put(
153
+ { value: {}, channels: [], actor: session2.actor },
154
+ session1
155
+ )
156
+ ).rejects.toThrow(GraffitiErrorForbidden);
157
+ const putted = await graffiti.put(
158
+ { value: {}, channels: [] },
159
+ session2
160
+ );
161
+ await expect2(graffiti.delete(putted, session1)).rejects.toThrow(
162
+ GraffitiErrorForbidden
163
+ );
164
+ await expect2(graffiti.patch({}, putted, session1)).rejects.toThrow(
165
+ GraffitiErrorForbidden
166
+ );
167
+ });
168
+ it2("put and get with schema", async () => {
169
+ const schema = {
170
+ properties: {
171
+ value: {
172
+ properties: {
173
+ something: {
174
+ type: "string"
175
+ },
176
+ another: {
177
+ type: "integer"
178
+ }
179
+ }
180
+ }
181
+ }
182
+ };
183
+ const goodValue = {
184
+ something: "hello",
185
+ another: 42
186
+ };
187
+ const putted = await graffiti.put(
188
+ {
189
+ value: goodValue,
190
+ channels: []
191
+ },
192
+ session
193
+ );
194
+ const gotten = await graffiti.get(putted, schema);
195
+ expect2(gotten.value.something).toEqual(goodValue.something);
196
+ expect2(gotten.value.another).toEqual(goodValue.another);
197
+ });
198
+ it2("put and get with invalid schema", async () => {
199
+ const putted = await graffiti.put({ value: {}, channels: [] }, session);
200
+ await expect2(
201
+ graffiti.get(putted, {
202
+ properties: {
203
+ value: {
204
+ //@ts-ignore
205
+ type: "asdf"
206
+ }
207
+ }
208
+ })
209
+ ).rejects.toThrow(GraffitiErrorInvalidSchema);
210
+ });
211
+ it2("put and get with wrong schema", async () => {
212
+ const putted = await graffiti.put(
213
+ {
214
+ value: {
215
+ hello: "world"
216
+ },
217
+ channels: []
218
+ },
219
+ session
220
+ );
221
+ await expect2(
222
+ graffiti.get(putted, {
223
+ properties: {
224
+ value: {
225
+ properties: {
226
+ hello: {
227
+ type: "number"
228
+ }
229
+ }
230
+ }
231
+ }
232
+ })
233
+ ).rejects.toThrow(GraffitiErrorSchemaMismatch);
234
+ });
235
+ it2("put and get with empty access control", async () => {
236
+ const value = {
237
+ um: "hi"
238
+ };
239
+ const allowed = [randomString()];
240
+ const channels = [randomString()];
241
+ const putted = await graffiti.put(
242
+ { value, allowed, channels },
243
+ session1
244
+ );
245
+ const gotten = await graffiti.get(putted, {}, session1);
246
+ expect2(gotten.value).toEqual(value);
247
+ expect2(gotten.allowed).toEqual(allowed);
248
+ expect2(gotten.channels).toEqual(channels);
249
+ await expect2(graffiti.get(putted, {})).rejects.toBeInstanceOf(
250
+ GraffitiErrorNotFound
251
+ );
252
+ await expect2(graffiti.get(putted, {}, session2)).rejects.toBeInstanceOf(
253
+ GraffitiErrorNotFound
254
+ );
255
+ });
256
+ it2("put and get with specific access control", async () => {
257
+ const value = {
258
+ um: "hi"
259
+ };
260
+ const allowed = [randomString(), session2.actor, randomString()];
261
+ const channels = [randomString()];
262
+ const putted = await graffiti.put(
263
+ {
264
+ value,
265
+ allowed,
266
+ channels
267
+ },
268
+ session1
269
+ );
270
+ const gotten = await graffiti.get(putted, {}, session1);
271
+ expect2(gotten.value).toEqual(value);
272
+ expect2(gotten.allowed).toEqual(allowed);
273
+ expect2(gotten.channels).toEqual(channels);
274
+ await expect2(graffiti.get(putted, {})).rejects.toBeInstanceOf(
275
+ GraffitiErrorNotFound
276
+ );
277
+ const gotten2 = await graffiti.get(putted, {}, session2);
278
+ expect2(gotten2.value).toEqual(value);
279
+ expect2(gotten2.allowed).toEqual([session2.actor]);
280
+ expect2(gotten2.channels).toEqual([]);
281
+ });
282
+ it2("patch value", async () => {
283
+ const value = {
284
+ something: "hello, world~ c:"
285
+ };
286
+ const putted = await graffiti.put({ value, channels: [] }, session);
287
+ await new Promise((resolve) => setTimeout(resolve, 10));
288
+ const patch = {
289
+ value: [
290
+ { op: "replace", path: "/something", value: "goodbye, world~ :c" }
291
+ ]
292
+ };
293
+ const beforePatched = await graffiti.patch(patch, putted, session);
294
+ expect2(beforePatched.value).toEqual(value);
295
+ expect2(beforePatched.tombstone).toBe(true);
296
+ expect2(beforePatched.lastModified).toBeGreaterThan(putted.lastModified);
297
+ const gotten = await graffiti.get(putted, {});
298
+ expect2(gotten.value).toEqual({
299
+ something: "goodbye, world~ :c"
300
+ });
301
+ expect2(beforePatched.lastModified).toBe(gotten.lastModified);
302
+ await graffiti.delete(putted, session);
303
+ });
304
+ it2("patch deleted object", async () => {
305
+ const putted = await graffiti.put(randomPutObject(), session);
306
+ const deleted = await graffiti.delete(putted, session);
307
+ await expect2(
308
+ graffiti.patch({}, putted, session)
309
+ ).rejects.toBeInstanceOf(GraffitiErrorNotFound);
310
+ });
311
+ it2("deep patch", async () => {
312
+ const value = {
313
+ something: {
314
+ another: {
315
+ somethingElse: "hello"
316
+ }
317
+ }
318
+ };
319
+ const putted = await graffiti.put(
320
+ { value, channels: [] },
321
+ session
322
+ );
323
+ const beforePatch = await graffiti.patch(
324
+ {
325
+ value: [
326
+ {
327
+ op: "replace",
328
+ path: "/something/another/somethingElse",
329
+ value: "goodbye"
330
+ }
331
+ ]
332
+ },
333
+ putted,
334
+ session
335
+ );
336
+ const gotten = await graffiti.get(putted, {});
337
+ expect2(beforePatch.value).toEqual(value);
338
+ expect2(gotten.value).toEqual({
339
+ something: {
340
+ another: {
341
+ somethingElse: "goodbye"
342
+ }
343
+ }
344
+ });
345
+ });
346
+ it2("patch channels", async () => {
347
+ const channelsBefore = [randomString()];
348
+ const channelsAfter = [randomString()];
349
+ const putted = await graffiti.put(
350
+ { value: {}, channels: channelsBefore },
351
+ session
352
+ );
353
+ const patch = {
354
+ channels: [{ op: "replace", path: "/0", value: channelsAfter[0] }]
355
+ };
356
+ const patched = await graffiti.patch(patch, putted, session);
357
+ expect2(patched.channels).toEqual(channelsBefore);
358
+ const gotten = await graffiti.get(putted, {}, session);
359
+ expect2(gotten.channels).toEqual(channelsAfter);
360
+ await graffiti.delete(putted, session);
361
+ });
362
+ it2("patch 'increment' with test", async () => {
363
+ const putted = await graffiti.put(
364
+ {
365
+ value: {
366
+ counter: 1
367
+ },
368
+ channels: []
369
+ },
370
+ session
371
+ );
372
+ const previous = await graffiti.patch(
373
+ {
374
+ value: [
375
+ { op: "test", path: "/counter", value: 1 },
376
+ { op: "replace", path: "/counter", value: 2 }
377
+ ]
378
+ },
379
+ putted,
380
+ session
381
+ );
382
+ expect2(previous.value).toEqual({ counter: 1 });
383
+ const result = await graffiti.get(previous, {
384
+ properties: {
385
+ value: {
386
+ properties: {
387
+ counter: {
388
+ type: "integer"
389
+ }
390
+ }
391
+ }
392
+ }
393
+ });
394
+ expect2(result.value.counter).toEqual(2);
395
+ await expect2(
396
+ graffiti.patch(
397
+ {
398
+ value: [
399
+ { op: "test", path: "/counter", value: 1 },
400
+ { op: "replace", path: "/counter", value: 3 }
401
+ ]
402
+ },
403
+ putted,
404
+ session
405
+ )
406
+ ).rejects.toThrow(GraffitiErrorPatchTestFailed);
407
+ });
408
+ it2("invalid patch", async () => {
409
+ const object = randomPutObject();
410
+ const putted = await graffiti.put(object, session);
411
+ await expect2(
412
+ graffiti.patch(
413
+ {
414
+ value: [
415
+ { op: "add", path: "/root", value: [] },
416
+ { op: "add", path: "/root/2", value: 2 }
417
+ // out of bounds
418
+ ]
419
+ },
420
+ putted,
421
+ session
422
+ )
423
+ ).rejects.toThrow(GraffitiErrorPatchError);
424
+ });
425
+ it2("patch channels to be wrong", async () => {
426
+ const object = randomPutObject();
427
+ object.allowed = [randomString()];
428
+ const putted = await graffiti.put(object, session);
429
+ const patches = [
430
+ {
431
+ channels: [{ op: "replace", path: "", value: null }]
432
+ },
433
+ {
434
+ channels: [{ op: "replace", path: "", value: {} }]
435
+ },
436
+ {
437
+ channels: [{ op: "replace", path: "", value: ["hello", ["hi"]] }]
438
+ },
439
+ {
440
+ channels: [{ op: "add", path: "/0", value: 1 }]
441
+ },
442
+ {
443
+ value: [{ op: "replace", path: "", value: "not an object" }]
444
+ },
445
+ {
446
+ value: [{ op: "replace", path: "", value: null }]
447
+ },
448
+ {
449
+ value: [{ op: "replace", path: "", value: [] }]
450
+ },
451
+ {
452
+ allowed: [{ op: "replace", path: "", value: {} }]
453
+ },
454
+ {
455
+ allowed: [{ op: "replace", path: "", value: ["hello", ["hi"]] }]
456
+ }
457
+ ];
458
+ for (const patch of patches) {
459
+ await expect2(graffiti.patch(patch, putted, session)).rejects.toThrow(
460
+ GraffitiErrorPatchError
461
+ );
462
+ }
463
+ const gotten = await graffiti.get(putted, {}, session);
464
+ expect2(gotten.value).toEqual(object.value);
465
+ expect2(gotten.channels).toEqual(object.channels);
466
+ expect2(gotten.allowed).toEqual(object.allowed);
467
+ expect2(gotten.lastModified).toEqual(putted.lastModified);
468
+ });
469
+ }
470
+ );
471
+ };
472
+
473
+ // tests/synchronize.ts
474
+ import { it as it3, expect as expect3, describe as describe3, assert as assert2, beforeEach as beforeEach2 } from "vitest";
475
+ var graffitiSynchronizeTests = (useGraffiti, useSession1, useSession2) => {
476
+ describe3.concurrent("synchronizeDiscover", () => {
477
+ let graffiti;
478
+ let session;
479
+ let session1;
480
+ let session2;
481
+ beforeEach2(async () => {
482
+ graffiti = useGraffiti();
483
+ session1 = await useSession1();
484
+ session = session1;
485
+ session2 = await useSession2();
486
+ });
487
+ it3("get", async () => {
488
+ const graffiti1 = useGraffiti();
489
+ const object = randomPutObject();
490
+ const channels = object.channels.slice(1);
491
+ const putted = await graffiti1.put(object, session);
492
+ const graffiti2 = useGraffiti();
493
+ const next = graffiti2.synchronizeDiscover(channels, {}).next();
494
+ const gotten = await graffiti2.get(putted, {}, session);
495
+ const result = (await next).value;
496
+ if (!result || result.error) {
497
+ throw new Error("Error in synchronize");
498
+ }
499
+ expect3(result.value.value).toEqual(object.value);
500
+ expect3(result.value.channels).toEqual(channels);
501
+ expect3(result.value.tombstone).toBe(false);
502
+ expect3(result.value.lastModified).toEqual(gotten.lastModified);
503
+ });
504
+ it3("put", async () => {
505
+ const beforeChannel = randomString();
506
+ const afterChannel = randomString();
507
+ const sharedChannel = randomString();
508
+ const oldValue = { hello: "world" };
509
+ const oldChannels = [beforeChannel, sharedChannel];
510
+ const putted = await graffiti.put(
511
+ {
512
+ value: oldValue,
513
+ channels: oldChannels
514
+ },
515
+ session
516
+ );
517
+ const before = graffiti.synchronizeDiscover([beforeChannel], {}).next();
518
+ const after = graffiti.synchronizeDiscover([afterChannel], {}).next();
519
+ const shared = graffiti.synchronizeDiscover([sharedChannel], {}).next();
520
+ const newValue = { goodbye: "world" };
521
+ const newChannels = [afterChannel, sharedChannel];
522
+ await graffiti.put(
523
+ {
524
+ ...putted,
525
+ value: newValue,
526
+ channels: newChannels
527
+ },
528
+ session
529
+ );
530
+ const beforeResult = (await before).value;
531
+ const afterResult = (await after).value;
532
+ const sharedResult = (await shared).value;
533
+ if (!beforeResult || beforeResult.error || !afterResult || afterResult.error || !sharedResult || sharedResult.error) {
534
+ throw new Error("Error in synchronize");
535
+ }
536
+ expect3(beforeResult.value.value).toEqual(oldValue);
537
+ expect3(beforeResult.value.channels).toEqual([beforeChannel]);
538
+ expect3(beforeResult.value.tombstone).toBe(true);
539
+ expect3(afterResult.value.value).toEqual(newValue);
540
+ expect3(afterResult.value.channels).toEqual([afterChannel]);
541
+ expect3(afterResult.value.tombstone).toBe(false);
542
+ expect3(sharedResult.value.value).toEqual(newValue);
543
+ expect3(sharedResult.value.channels).toEqual([sharedChannel]);
544
+ expect3(sharedResult.value.tombstone).toBe(false);
545
+ expect3(beforeResult.value.lastModified).toEqual(
546
+ afterResult.value.lastModified
547
+ );
548
+ expect3(sharedResult.value.lastModified).toEqual(
549
+ afterResult.value.lastModified
550
+ );
551
+ });
552
+ it3("patch", async () => {
553
+ const beforeChannel = randomString();
554
+ const afterChannel = randomString();
555
+ const sharedChannel = randomString();
556
+ const oldValue = { hello: "world" };
557
+ const oldChannels = [beforeChannel, sharedChannel];
558
+ const putted = await graffiti.put(
559
+ {
560
+ value: oldValue,
561
+ channels: oldChannels
562
+ },
563
+ session
564
+ );
565
+ const before = graffiti.synchronizeDiscover([beforeChannel], {}).next();
566
+ const after = graffiti.synchronizeDiscover([afterChannel], {}).next();
567
+ const shared = graffiti.synchronizeDiscover([sharedChannel], {}).next();
568
+ await graffiti.patch(
569
+ {
570
+ value: [
571
+ {
572
+ op: "add",
573
+ path: "/something",
574
+ value: "new value"
575
+ }
576
+ ],
577
+ channels: [
578
+ {
579
+ op: "add",
580
+ path: "/-",
581
+ value: afterChannel
582
+ },
583
+ {
584
+ op: "remove",
585
+ path: `/${oldChannels.indexOf(beforeChannel)}`
586
+ }
587
+ ]
588
+ },
589
+ putted,
590
+ session
591
+ );
592
+ const beforeResult = (await before).value;
593
+ const afterResult = (await after).value;
594
+ const sharedResult = (await shared).value;
595
+ if (!beforeResult || beforeResult.error || !afterResult || afterResult.error || !sharedResult || sharedResult.error) {
596
+ throw new Error("Error in synchronize");
597
+ }
598
+ const newValue = { ...oldValue, something: "new value" };
599
+ const newChannels = [sharedChannel, afterChannel];
600
+ expect3(beforeResult.value.value).toEqual(oldValue);
601
+ expect3(beforeResult.value.channels).toEqual([beforeChannel]);
602
+ expect3(beforeResult.value.tombstone).toBe(true);
603
+ expect3(afterResult.value.value).toEqual(newValue);
604
+ expect3(afterResult.value.channels).toEqual([afterChannel]);
605
+ expect3(afterResult.value.tombstone).toBe(false);
606
+ expect3(sharedResult.value.value).toEqual(newValue);
607
+ expect3(sharedResult.value.channels).toEqual([sharedChannel]);
608
+ expect3(sharedResult.value.tombstone).toBe(false);
609
+ expect3(beforeResult.value.lastModified).toEqual(
610
+ afterResult.value.lastModified
611
+ );
612
+ expect3(sharedResult.value.lastModified).toEqual(
613
+ afterResult.value.lastModified
614
+ );
615
+ });
616
+ it3("delete", async () => {
617
+ const channels = [randomString(), randomString(), randomString()];
618
+ const oldValue = { hello: "world" };
619
+ const oldChannels = [randomString(), ...channels.slice(1)];
620
+ const putted = await graffiti.put(
621
+ {
622
+ value: oldValue,
623
+ channels: oldChannels
624
+ },
625
+ session
626
+ );
627
+ const next = graffiti.synchronizeDiscover(channels, {}).next();
628
+ graffiti.delete(putted, session);
629
+ const result = (await next).value;
630
+ if (!result || result.error) {
631
+ throw new Error("Error in synchronize");
632
+ }
633
+ expect3(result.value.tombstone).toBe(true);
634
+ expect3(result.value.value).toEqual(oldValue);
635
+ expect3(result.value.channels).toEqual(
636
+ channels.filter((c) => oldChannels.includes(c))
637
+ );
638
+ });
639
+ it3("not allowed", async () => {
640
+ const allChannels = [randomString(), randomString(), randomString()];
641
+ const channels = allChannels.slice(1);
642
+ const creatorNext = graffiti.synchronizeDiscover(channels, {}, session1).next();
643
+ const allowedNext = graffiti.synchronizeDiscover(channels, {}, session2).next();
644
+ const noSession = graffiti.synchronizeDiscover(channels, {}).next();
645
+ const value = {
646
+ hello: "world"
647
+ };
648
+ const allowed = [randomString(), session2.actor];
649
+ await graffiti.put({ value, channels: allChannels, allowed }, session1);
650
+ await expect3(
651
+ Promise.race([
652
+ noSession,
653
+ new Promise(
654
+ (resolve, rejects) => setTimeout(rejects, 100, "Timeout")
655
+ )
656
+ ])
657
+ ).rejects.toThrow("Timeout");
658
+ const creatorResult = (await creatorNext).value;
659
+ const allowedResult = (await allowedNext).value;
660
+ if (!creatorResult || creatorResult.error || !allowedResult || allowedResult.error) {
661
+ throw new Error("Error in synchronize");
662
+ }
663
+ expect3(creatorResult.value.value).toEqual(value);
664
+ expect3(creatorResult.value.allowed).toEqual(allowed);
665
+ expect3(creatorResult.value.channels).toEqual(allChannels);
666
+ expect3(allowedResult.value.value).toEqual(value);
667
+ expect3(allowedResult.value.allowed).toEqual([session2.actor]);
668
+ expect3(allowedResult.value.channels).toEqual(channels);
669
+ });
670
+ });
671
+ describe3.concurrent("synchronizeGet", () => {
672
+ let graffiti;
673
+ let session;
674
+ let session1;
675
+ let session2;
676
+ beforeEach2(async () => {
677
+ graffiti = useGraffiti();
678
+ session1 = await useSession1();
679
+ session = session1;
680
+ session2 = await useSession2();
681
+ });
682
+ it3("replace, delete", async () => {
683
+ const object = randomPutObject();
684
+ const putted = await graffiti.put(object, session);
685
+ const iterator = graffiti.synchronizeGet(putted, {});
686
+ const next = iterator.next();
687
+ const newValue = { goodbye: "world" };
688
+ const putted2 = await graffiti.put(
689
+ {
690
+ ...putted,
691
+ value: newValue
692
+ },
693
+ session
694
+ );
695
+ const result = (await next).value;
696
+ assert2(result && !result.error);
697
+ expect3(result.value.value).toEqual(newValue);
698
+ expect3(result.value.actor).toEqual(session.actor);
699
+ expect3(result.value.channels).toEqual([]);
700
+ expect3(result.value.tombstone).toBe(false);
701
+ expect3(result.value.lastModified).toEqual(putted2.lastModified);
702
+ expect3(result.value.allowed).toBeUndefined();
703
+ const deleted = await graffiti.delete(putted2, session);
704
+ const result2 = (await iterator.next()).value;
705
+ assert2(result2 && !result2.error);
706
+ expect3(result2.value.tombstone).toBe(true);
707
+ expect3(result2.value.lastModified).toEqual(deleted.lastModified);
708
+ await graffiti.put(randomPutObject(), session);
709
+ await expect3(
710
+ Promise.race([
711
+ iterator.next(),
712
+ new Promise((resolve, reject) => setTimeout(reject, 100, "Timeout"))
713
+ ])
714
+ ).rejects.toThrow("Timeout");
715
+ });
716
+ it3("not allowed", async () => {
717
+ const object = randomPutObject();
718
+ const putted = await graffiti.put(object, session1);
719
+ const iterator1 = graffiti.synchronizeGet(putted, {}, session1);
720
+ const iterator2 = graffiti.synchronizeGet(putted, {}, session2);
721
+ const next1 = iterator1.next();
722
+ const next2 = iterator2.next();
723
+ const newValue = { goodbye: "world" };
724
+ const putted2 = await graffiti.put(
725
+ {
726
+ ...putted,
727
+ ...object,
728
+ allowed: [],
729
+ value: newValue
730
+ },
731
+ session1
732
+ );
733
+ const result1 = (await next1).value;
734
+ const result2 = (await next2).value;
735
+ assert2(result1 && !result1.error);
736
+ assert2(result2 && !result2.error);
737
+ expect3(result1.value.value).toEqual(newValue);
738
+ expect3(result2.value.value).toEqual(object.value);
739
+ expect3(result1.value.actor).toEqual(session1.actor);
740
+ expect3(result2.value.actor).toEqual(session1.actor);
741
+ expect3(result1.value.channels).toEqual(object.channels);
742
+ expect3(result2.value.channels).toEqual([]);
743
+ expect3(result1.value.tombstone).toBe(false);
744
+ expect3(result2.value.tombstone).toBe(true);
745
+ expect3(result1.value.lastModified).toEqual(putted2.lastModified);
746
+ expect3(result2.value.lastModified).toEqual(putted2.lastModified);
747
+ });
748
+ });
749
+ };
750
+
751
+ // tests/discover.ts
752
+ import { it as it4, expect as expect4, describe as describe4, assert as assert3, beforeEach as beforeEach3 } from "vitest";
753
+ var graffitiDiscoverTests = (useGraffiti, useSession1, useSession2) => {
754
+ describe4.concurrent("discover", { timeout: 2e4 }, () => {
755
+ let graffiti;
756
+ let session;
757
+ let session1;
758
+ let session2;
759
+ beforeEach3(async () => {
760
+ graffiti = useGraffiti();
761
+ session1 = await useSession1();
762
+ session = session1;
763
+ session2 = await useSession2();
764
+ });
765
+ it4("discover nothing", async () => {
766
+ const iterator = graffiti.discover([], {});
767
+ expect4(await iterator.next()).toHaveProperty("done", true);
768
+ });
769
+ it4("discover single", async () => {
770
+ const object = randomPutObject();
771
+ const putted = await graffiti.put(object, session);
772
+ const queryChannels = [randomString(), object.channels[0]];
773
+ const iterator = graffiti.discover(queryChannels, {});
774
+ const value = await nextStreamValue(iterator);
775
+ expect4(value.value).toEqual(object.value);
776
+ expect4(value.channels).toEqual([object.channels[0]]);
777
+ expect4(value.allowed).toBeUndefined();
778
+ expect4(value.actor).toEqual(session.actor);
779
+ expect4(value.tombstone).toBe(false);
780
+ expect4(value.lastModified).toEqual(putted.lastModified);
781
+ const result2 = await iterator.next();
782
+ expect4(result2.done).toBe(true);
783
+ });
784
+ it4("discover wrong channel", async () => {
785
+ const object = randomPutObject();
786
+ await graffiti.put(object, session);
787
+ const iterator = graffiti.discover([randomString()], {});
788
+ await expect4(iterator.next()).resolves.toHaveProperty("done", true);
789
+ });
790
+ it4("discover not allowed", async () => {
791
+ const object = randomPutObject();
792
+ object.allowed = [randomString(), randomString()];
793
+ const putted = await graffiti.put(object, session1);
794
+ const iteratorSession1 = graffiti.discover(object.channels, {}, session1);
795
+ const value = await nextStreamValue(iteratorSession1);
796
+ expect4(value.value).toEqual(object.value);
797
+ expect4(value.channels).toEqual(object.channels);
798
+ expect4(value.allowed).toEqual(object.allowed);
799
+ expect4(value.actor).toEqual(session1.actor);
800
+ expect4(value.tombstone).toBe(false);
801
+ expect4(value.lastModified).toEqual(putted.lastModified);
802
+ const iteratorSession2 = graffiti.discover(object.channels, {}, session2);
803
+ expect4(await iteratorSession2.next()).toHaveProperty("done", true);
804
+ const iteratorNoSession = graffiti.discover(object.channels, {});
805
+ expect4(await iteratorNoSession.next()).toHaveProperty("done", true);
806
+ });
807
+ it4("discover allowed", async () => {
808
+ const object = randomPutObject();
809
+ object.allowed = [randomString(), session2.actor, randomString()];
810
+ const putted = await graffiti.put(object, session1);
811
+ const iteratorSession2 = graffiti.discover(object.channels, {}, session2);
812
+ const value = await nextStreamValue(iteratorSession2);
813
+ expect4(value.value).toEqual(object.value);
814
+ expect4(value.allowed).toEqual([session2.actor]);
815
+ expect4(value.channels).toEqual(object.channels);
816
+ expect4(value.actor).toEqual(session1.actor);
817
+ expect4(value.tombstone).toBe(false);
818
+ expect4(value.lastModified).toEqual(putted.lastModified);
819
+ });
820
+ for (const prop of ["name", "actor", "lastModified"]) {
821
+ it4(`discover for ${prop}`, async () => {
822
+ const object1 = randomPutObject();
823
+ const putted1 = await graffiti.put(object1, session1);
824
+ const object2 = randomPutObject();
825
+ object2.channels = object1.channels;
826
+ await new Promise((r) => setTimeout(r, 20));
827
+ const putted2 = await graffiti.put(object2, session2);
828
+ const iterator = graffiti.discover(object1.channels, {
829
+ properties: {
830
+ [prop]: {
831
+ enum: [putted1[prop]]
832
+ }
833
+ }
834
+ });
835
+ const value = await nextStreamValue(iterator);
836
+ expect4(value.name).toEqual(putted1.name);
837
+ expect4(value.name).not.toEqual(putted2.name);
838
+ expect4(value.value).toEqual(object1.value);
839
+ await expect4(iterator.next()).resolves.toHaveProperty("done", true);
840
+ });
841
+ }
842
+ it4("discover with lastModified range", async () => {
843
+ const object = randomPutObject();
844
+ const putted1 = await graffiti.put(object, session);
845
+ await new Promise((r) => setTimeout(r, 20));
846
+ const putted2 = await graffiti.put(object, session);
847
+ expect4(putted1.name).not.toEqual(putted2.name);
848
+ expect4(putted1.lastModified).toBeLessThan(putted2.lastModified);
849
+ const gtIterator = graffiti.discover([object.channels[0]], {
850
+ properties: {
851
+ lastModified: {
852
+ minimum: putted2.lastModified,
853
+ exclusiveMinimum: true
854
+ }
855
+ }
856
+ });
857
+ expect4(await gtIterator.next()).toHaveProperty("done", true);
858
+ const gtIteratorEpsilon = graffiti.discover([object.channels[0]], {
859
+ properties: {
860
+ lastModified: {
861
+ minimum: putted2.lastModified - 0.1,
862
+ exclusiveMinimum: true
863
+ }
864
+ }
865
+ });
866
+ const value1 = await nextStreamValue(gtIteratorEpsilon);
867
+ expect4(value1.name).toEqual(putted2.name);
868
+ expect4(await gtIteratorEpsilon.next()).toHaveProperty("done", true);
869
+ const gteIterator = graffiti.discover(object.channels, {
870
+ properties: {
871
+ value: {},
872
+ lastModified: {
873
+ minimum: putted2.lastModified
874
+ }
875
+ }
876
+ });
877
+ const value = await nextStreamValue(gteIterator);
878
+ expect4(value.name).toEqual(putted2.name);
879
+ expect4(await gteIterator.next()).toHaveProperty("done", true);
880
+ const gteIteratorEpsilon = graffiti.discover(object.channels, {
881
+ properties: {
882
+ lastModified: {
883
+ minimum: putted2.lastModified + 0.1
884
+ }
885
+ }
886
+ });
887
+ expect4(await gteIteratorEpsilon.next()).toHaveProperty("done", true);
888
+ const ltIterator = graffiti.discover(object.channels, {
889
+ properties: {
890
+ lastModified: {
891
+ maximum: putted1.lastModified,
892
+ exclusiveMaximum: true
893
+ }
894
+ }
895
+ });
896
+ expect4(await ltIterator.next()).toHaveProperty("done", true);
897
+ const ltIteratorEpsilon = graffiti.discover(object.channels, {
898
+ properties: {
899
+ lastModified: {
900
+ maximum: putted1.lastModified + 0.1,
901
+ exclusiveMaximum: true
902
+ }
903
+ }
904
+ });
905
+ const value3 = await nextStreamValue(ltIteratorEpsilon);
906
+ expect4(value3.name).toEqual(putted1.name);
907
+ expect4(await ltIteratorEpsilon.next()).toHaveProperty("done", true);
908
+ const lteIterator = graffiti.discover(object.channels, {
909
+ properties: {
910
+ lastModified: {
911
+ maximum: putted1.lastModified
912
+ }
913
+ }
914
+ });
915
+ const value2 = await nextStreamValue(lteIterator);
916
+ expect4(value2.name).toEqual(putted1.name);
917
+ expect4(await lteIterator.next()).toHaveProperty("done", true);
918
+ const lteIteratorEpsilon = graffiti.discover(object.channels, {
919
+ properties: {
920
+ lastModified: {
921
+ maximum: putted1.lastModified - 0.1
922
+ }
923
+ }
924
+ });
925
+ expect4(await lteIteratorEpsilon.next()).toHaveProperty("done", true);
926
+ });
927
+ it4("discover schema allowed, as and not as owner", async () => {
928
+ const object = randomPutObject();
929
+ object.allowed = [randomString(), session2.actor, randomString()];
930
+ await graffiti.put(object, session1);
931
+ const iteratorSession1 = graffiti.discover(
932
+ object.channels,
933
+ {
934
+ properties: {
935
+ allowed: {
936
+ minItems: 3,
937
+ // Make sure session2.actor is in the allow list
938
+ not: {
939
+ items: {
940
+ not: {
941
+ enum: [session2.actor]
942
+ }
943
+ }
944
+ }
945
+ }
946
+ }
947
+ },
948
+ session1
949
+ );
950
+ const value = await nextStreamValue(iteratorSession1);
951
+ expect4(value.value).toEqual(object.value);
952
+ await expect4(iteratorSession1.next()).resolves.toHaveProperty(
953
+ "done",
954
+ true
955
+ );
956
+ const iteratorSession2BigAllow = graffiti.discover(
957
+ object.channels,
958
+ {
959
+ properties: {
960
+ allowed: {
961
+ minItems: 3
962
+ }
963
+ }
964
+ },
965
+ session2
966
+ );
967
+ await expect4(iteratorSession2BigAllow.next()).resolves.toHaveProperty(
968
+ "done",
969
+ true
970
+ );
971
+ const iteratorSession2PeekOther = graffiti.discover(
972
+ object.channels,
973
+ {
974
+ properties: {
975
+ allowed: {
976
+ not: {
977
+ items: {
978
+ not: {
979
+ enum: [object.channels[0]]
980
+ }
981
+ }
982
+ }
983
+ }
984
+ }
985
+ },
986
+ session2
987
+ );
988
+ await expect4(iteratorSession2PeekOther.next()).resolves.toHaveProperty(
989
+ "done",
990
+ true
991
+ );
992
+ const iteratorSession2SmallAllowPeekSelf = graffiti.discover(
993
+ object.channels,
994
+ {
995
+ properties: {
996
+ allowed: {
997
+ maxItems: 1,
998
+ not: {
999
+ items: {
1000
+ not: {
1001
+ enum: [session2.actor]
1002
+ }
1003
+ }
1004
+ }
1005
+ }
1006
+ }
1007
+ },
1008
+ session2
1009
+ );
1010
+ const value2 = await nextStreamValue(iteratorSession2SmallAllowPeekSelf);
1011
+ expect4(value2.value).toEqual(object.value);
1012
+ await expect4(
1013
+ iteratorSession2SmallAllowPeekSelf.next()
1014
+ ).resolves.toHaveProperty("done", true);
1015
+ });
1016
+ it4("discover schema channels, as and not as owner", async () => {
1017
+ const object = randomPutObject();
1018
+ object.channels = [randomString(), randomString(), randomString()];
1019
+ await graffiti.put(object, session1);
1020
+ const iteratorSession1 = graffiti.discover(
1021
+ [object.channels[0], object.channels[2]],
1022
+ {
1023
+ properties: {
1024
+ channels: {
1025
+ minItems: 3,
1026
+ // Make sure session2.actor is in the allow list
1027
+ not: {
1028
+ items: {
1029
+ not: {
1030
+ enum: [object.channels[1]]
1031
+ }
1032
+ }
1033
+ }
1034
+ }
1035
+ }
1036
+ },
1037
+ session1
1038
+ );
1039
+ const value = await nextStreamValue(iteratorSession1);
1040
+ expect4(value.value).toEqual(object.value);
1041
+ await expect4(iteratorSession1.next()).resolves.toHaveProperty(
1042
+ "done",
1043
+ true
1044
+ );
1045
+ const iteratorSession2BigAllow = graffiti.discover(
1046
+ [object.channels[0], object.channels[2]],
1047
+ {
1048
+ properties: {
1049
+ channels: {
1050
+ minItems: 3
1051
+ }
1052
+ }
1053
+ },
1054
+ session2
1055
+ );
1056
+ await expect4(iteratorSession2BigAllow.next()).resolves.toHaveProperty(
1057
+ "done",
1058
+ true
1059
+ );
1060
+ const iteratorSession2PeekOther = graffiti.discover(
1061
+ [object.channels[0], object.channels[2]],
1062
+ {
1063
+ properties: {
1064
+ channels: {
1065
+ not: {
1066
+ items: {
1067
+ not: {
1068
+ enum: [object.channels[1]]
1069
+ }
1070
+ }
1071
+ }
1072
+ }
1073
+ }
1074
+ },
1075
+ session2
1076
+ );
1077
+ await expect4(iteratorSession2PeekOther.next()).resolves.toHaveProperty(
1078
+ "done",
1079
+ true
1080
+ );
1081
+ const iteratorSession2SmallAllowPeekSelf = graffiti.discover(
1082
+ [object.channels[0], object.channels[2]],
1083
+ {
1084
+ properties: {
1085
+ allowed: {
1086
+ maxItems: 2,
1087
+ not: {
1088
+ items: {
1089
+ not: {
1090
+ enum: [object.channels[2]]
1091
+ }
1092
+ }
1093
+ }
1094
+ }
1095
+ }
1096
+ },
1097
+ session2
1098
+ );
1099
+ const value2 = await nextStreamValue(iteratorSession2SmallAllowPeekSelf);
1100
+ expect4(value2.value).toEqual(object.value);
1101
+ await expect4(
1102
+ iteratorSession2SmallAllowPeekSelf.next()
1103
+ ).resolves.toHaveProperty("done", true);
1104
+ });
1105
+ it4("discover query for empty allowed", async () => {
1106
+ const publicO = randomPutObject();
1107
+ const publicSchema = {
1108
+ not: {
1109
+ required: ["allowed"]
1110
+ }
1111
+ };
1112
+ await graffiti.put(publicO, session1);
1113
+ const iterator = graffiti.discover(
1114
+ publicO.channels,
1115
+ publicSchema,
1116
+ session1
1117
+ );
1118
+ const value = await nextStreamValue(iterator);
1119
+ expect4(value.value).toEqual(publicO.value);
1120
+ expect4(value.allowed).toBeUndefined();
1121
+ await expect4(iterator.next()).resolves.toHaveProperty("done", true);
1122
+ const restricted = randomPutObject();
1123
+ restricted.allowed = [];
1124
+ await graffiti.put(restricted, session1);
1125
+ const iterator2 = graffiti.discover(
1126
+ restricted.channels,
1127
+ publicSchema,
1128
+ session1
1129
+ );
1130
+ await expect4(iterator2.next()).resolves.toHaveProperty("done", true);
1131
+ });
1132
+ it4("discover query for values", async () => {
1133
+ const object1 = randomPutObject();
1134
+ object1.value = { test: randomString() };
1135
+ await graffiti.put(object1, session);
1136
+ const object2 = randomPutObject();
1137
+ object2.channels = object1.channels;
1138
+ object2.value = { test: randomString(), something: randomString() };
1139
+ await graffiti.put(object2, session);
1140
+ const object3 = randomPutObject();
1141
+ object3.channels = object1.channels;
1142
+ object3.value = { other: randomString(), something: randomString() };
1143
+ await graffiti.put(object3, session);
1144
+ const counts = /* @__PURE__ */ new Map();
1145
+ for (const property of ["test", "something", "other"]) {
1146
+ let count = 0;
1147
+ for await (const result of graffiti.discover(object1.channels, {
1148
+ properties: {
1149
+ value: {
1150
+ required: [property]
1151
+ }
1152
+ }
1153
+ })) {
1154
+ assert3(!result.error, "result has error");
1155
+ if (property in result.value.value) {
1156
+ count++;
1157
+ }
1158
+ }
1159
+ counts.set(property, count);
1160
+ }
1161
+ expect4(counts.get("test")).toBe(2);
1162
+ expect4(counts.get("something")).toBe(2);
1163
+ expect4(counts.get("other")).toBe(1);
1164
+ });
1165
+ it4("discover for deleted content", async () => {
1166
+ const object = randomPutObject();
1167
+ const putted = await graffiti.put(object, session);
1168
+ const deleted = await graffiti.delete(putted, session);
1169
+ const iterator = graffiti.discover(object.channels, {});
1170
+ const value = await nextStreamValue(iterator);
1171
+ expect4(value.tombstone).toBe(true);
1172
+ expect4(value.value).toEqual(object.value);
1173
+ expect4(value.channels).toEqual(object.channels);
1174
+ expect4(value.actor).toEqual(session.actor);
1175
+ expect4(value.lastModified).toEqual(deleted.lastModified);
1176
+ await expect4(iterator.next()).resolves.toHaveProperty("done", true);
1177
+ });
1178
+ it4("discover for replaced channels", async () => {
1179
+ for (let i = 0; i < 10; i++) {
1180
+ const object1 = randomPutObject();
1181
+ const putted = await graffiti.put(object1, session);
1182
+ const object2 = randomPutObject();
1183
+ const replaced = await graffiti.put(
1184
+ {
1185
+ ...putted,
1186
+ ...object2
1187
+ },
1188
+ session
1189
+ );
1190
+ const iterator1 = graffiti.discover(object1.channels, {});
1191
+ const value1 = await nextStreamValue(iterator1);
1192
+ await expect4(iterator1.next()).resolves.toHaveProperty("done", true);
1193
+ const iterator2 = graffiti.discover(object2.channels, {});
1194
+ const value2 = await nextStreamValue(iterator2);
1195
+ await expect4(iterator2.next()).resolves.toHaveProperty("done", true);
1196
+ if (putted.lastModified === replaced.lastModified) {
1197
+ expect4(value1.tombstone || value2.tombstone).toBe(true);
1198
+ expect4(value1.tombstone && value2.tombstone).toBe(false);
1199
+ } else {
1200
+ expect4(value1.tombstone).toBe(true);
1201
+ expect4(value1.value).toEqual(object1.value);
1202
+ expect4(value1.channels).toEqual(object1.channels);
1203
+ expect4(value1.lastModified).toEqual(replaced.lastModified);
1204
+ expect4(value2.tombstone).toBe(false);
1205
+ expect4(value2.value).toEqual(object2.value);
1206
+ expect4(value2.channels).toEqual(object2.channels);
1207
+ expect4(value2.lastModified).toEqual(replaced.lastModified);
1208
+ }
1209
+ }
1210
+ });
1211
+ it4("discover for patched allowed", async () => {
1212
+ const object = randomPutObject();
1213
+ const putted = await graffiti.put(object, session);
1214
+ await graffiti.patch(
1215
+ {
1216
+ allowed: [{ op: "add", path: "", value: [] }]
1217
+ },
1218
+ putted,
1219
+ session
1220
+ );
1221
+ const iterator = graffiti.discover(object.channels, {});
1222
+ const value = await nextStreamValue(iterator);
1223
+ expect4(value.tombstone).toBe(true);
1224
+ expect4(value.value).toEqual(object.value);
1225
+ expect4(value.channels).toEqual(object.channels);
1226
+ expect4(value.allowed).toBeUndefined();
1227
+ await expect4(iterator.next()).resolves.toHaveProperty("done", true);
1228
+ });
1229
+ it4("put concurrently and discover one", async () => {
1230
+ const object = randomPutObject();
1231
+ object.name = randomString();
1232
+ const putPromises = Array(100).fill(0).map(() => graffiti.put(object, session));
1233
+ await Promise.all(putPromises);
1234
+ const iterator = graffiti.discover(object.channels, {});
1235
+ let tombstoneCount = 0;
1236
+ let valueCount = 0;
1237
+ for await (const result of iterator) {
1238
+ assert3(!result.error, "result has error");
1239
+ if (result.value.tombstone) {
1240
+ tombstoneCount++;
1241
+ } else {
1242
+ valueCount++;
1243
+ }
1244
+ }
1245
+ expect4(tombstoneCount).toBe(99);
1246
+ expect4(valueCount).toBe(1);
1247
+ });
1248
+ });
1249
+ };
1250
+
1251
+ // tests/orphans.ts
1252
+ import { it as it5, expect as expect5, describe as describe5, beforeEach as beforeEach4 } from "vitest";
1253
+ var graffitiOrphanTests = (useGraffiti, useSession1, useSession2) => {
1254
+ describe5("recoverOrphans", { timeout: 2e4 }, () => {
1255
+ let graffiti;
1256
+ let session;
1257
+ let session1;
1258
+ let session2;
1259
+ beforeEach4(async () => {
1260
+ graffiti = useGraffiti();
1261
+ session1 = await useSession1();
1262
+ session = session1;
1263
+ session2 = await useSession2();
1264
+ });
1265
+ it5("list orphans", async () => {
1266
+ const existingOrphans = [];
1267
+ const orphanIterator1 = graffiti.recoverOrphans({}, session);
1268
+ for await (const orphan of orphanIterator1) {
1269
+ if (orphan.error) continue;
1270
+ existingOrphans.push(orphan.value.name);
1271
+ }
1272
+ const object = randomPutObject();
1273
+ object.channels = [];
1274
+ const putted = await graffiti.put(object, session);
1275
+ const orphanIterator2 = graffiti.recoverOrphans({}, session);
1276
+ let numResults = 0;
1277
+ for await (const orphan of orphanIterator2) {
1278
+ if (orphan.error) continue;
1279
+ if (orphan.value.name === putted.name) {
1280
+ numResults++;
1281
+ expect5(orphan.value.source).toBe(putted.source);
1282
+ expect5(orphan.value.lastModified).toBe(putted.lastModified);
1283
+ }
1284
+ }
1285
+ expect5(numResults).toBe(1);
1286
+ });
1287
+ it5("replaced orphan, no longer", async () => {
1288
+ const object = randomPutObject();
1289
+ object.channels = [];
1290
+ const putOrphan = await graffiti.put(object, session);
1291
+ await new Promise((resolve) => setTimeout(resolve, 10));
1292
+ const putNotOrphan = await graffiti.put(
1293
+ {
1294
+ ...putOrphan,
1295
+ ...object,
1296
+ channels: [randomString()]
1297
+ },
1298
+ session
1299
+ );
1300
+ expect5(putNotOrphan.name).toBe(putOrphan.name);
1301
+ expect5(putNotOrphan.lastModified).toBeGreaterThan(putOrphan.lastModified);
1302
+ const orphanIterator = graffiti.recoverOrphans({}, session);
1303
+ let numResults = 0;
1304
+ for await (const orphan of orphanIterator) {
1305
+ if (orphan.error) continue;
1306
+ if (orphan.value.name === putOrphan.name) {
1307
+ numResults++;
1308
+ expect5(orphan.value.tombstone).toBe(true);
1309
+ expect5(orphan.value.lastModified).toBe(putNotOrphan.lastModified);
1310
+ expect5(orphan.value.channels).toEqual([]);
1311
+ }
1312
+ }
1313
+ expect5(numResults).toBe(1);
1314
+ });
1315
+ });
1316
+ };
1317
+
1318
+ // tests/channel-stats.ts
1319
+ import { it as it6, expect as expect6, describe as describe6, assert as assert5, beforeEach as beforeEach5 } from "vitest";
1320
+ var graffitiChannelStatsTests = (useGraffiti, useSession1, useSession2) => {
1321
+ describe6("channel stats", { timeout: 2e4 }, () => {
1322
+ let graffiti;
1323
+ let session;
1324
+ let session1;
1325
+ let session2;
1326
+ beforeEach5(async () => {
1327
+ graffiti = useGraffiti();
1328
+ session1 = await useSession1();
1329
+ session = session1;
1330
+ session2 = await useSession2();
1331
+ });
1332
+ it6("list channels", async () => {
1333
+ const existingChannels = /* @__PURE__ */ new Map();
1334
+ const channelIterator1 = graffiti.channelStats(session);
1335
+ for await (const channel of channelIterator1) {
1336
+ if (channel.error) continue;
1337
+ existingChannels.set(channel.value.channel, channel.value.count);
1338
+ }
1339
+ const channels = [randomString(), randomString(), randomString()];
1340
+ for (let i = 0; i < 3; i++) {
1341
+ for (let j = 0; j < i + 1; j++) {
1342
+ await graffiti.put(
1343
+ {
1344
+ value: {
1345
+ index: j
1346
+ },
1347
+ channels: channels.slice(0, i + 1)
1348
+ },
1349
+ session
1350
+ );
1351
+ }
1352
+ }
1353
+ await graffiti.put(
1354
+ { value: { index: 3 }, channels: [channels[2]] },
1355
+ session
1356
+ );
1357
+ const channelIterator2 = graffiti.channelStats(session);
1358
+ let newChannels = /* @__PURE__ */ new Map();
1359
+ for await (const channel of channelIterator2) {
1360
+ if (channel.error) continue;
1361
+ newChannels.set(channel.value.channel, channel.value.count);
1362
+ }
1363
+ newChannels = new Map(
1364
+ Array.from(newChannels).filter(
1365
+ ([channel, count]) => !existingChannels.has(channel)
1366
+ )
1367
+ );
1368
+ expect6(newChannels.size).toBe(3);
1369
+ expect6(newChannels.get(channels[0])).toBe(6);
1370
+ expect6(newChannels.get(channels[1])).toBe(5);
1371
+ expect6(newChannels.get(channels[2])).toBe(4);
1372
+ });
1373
+ it6("list channels with deleted channel", async () => {
1374
+ const channels = [randomString(), randomString(), randomString()];
1375
+ const before = await graffiti.put(
1376
+ {
1377
+ value: { index: 2 },
1378
+ channels: channels.slice(1)
1379
+ },
1380
+ session
1381
+ );
1382
+ const first = await graffiti.put(
1383
+ { value: { index: 0 }, channels },
1384
+ session
1385
+ );
1386
+ await graffiti.delete(first, session);
1387
+ const second = await graffiti.put(
1388
+ {
1389
+ value: { index: 1 },
1390
+ channels: channels.slice(2)
1391
+ },
1392
+ session
1393
+ );
1394
+ const channelIterator = graffiti.channelStats(session);
1395
+ let got1 = 0;
1396
+ let got2 = 0;
1397
+ for await (const result of channelIterator) {
1398
+ if (result.error) continue;
1399
+ const { channel, count, lastModified } = result.value;
1400
+ assert5(
1401
+ channel !== channels[0],
1402
+ "There should not be an object in channel[0]"
1403
+ );
1404
+ if (channel === channels[1]) {
1405
+ expect6(count).toBe(1);
1406
+ expect6(lastModified).toBe(before.lastModified);
1407
+ got1++;
1408
+ } else if (channel === channels[2]) {
1409
+ expect6(count).toBe(2);
1410
+ expect6(lastModified).toBe(second.lastModified);
1411
+ got2++;
1412
+ }
1413
+ }
1414
+ expect6(got1).toBe(1);
1415
+ expect6(got2).toBe(1);
1416
+ });
1417
+ });
1418
+ };
1419
+ export {
1420
+ graffitiCRUDTests,
1421
+ graffitiChannelStatsTests,
1422
+ graffitiDiscoverTests,
1423
+ graffitiLocationTests,
1424
+ graffitiOrphanTests,
1425
+ graffitiSynchronizeTests
1426
+ };
2
1427
  //# sourceMappingURL=tests.mjs.map