@graffiti-garden/api 0.1.11 → 0.2.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/dist/src/1-api.d.ts +29 -2
- package/dist/src/1-api.d.ts.map +1 -1
- package/dist/src/2-types.d.ts +2 -2
- package/dist/src/2-types.d.ts.map +1 -1
- package/dist/tests/index.js +1 -1
- package/dist/tests/src/synchronize.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/1-api.ts +31 -2
- package/src/2-types.ts +9 -5
- package/tests/src/synchronize.ts +107 -13
package/dist/src/1-api.d.ts
CHANGED
|
@@ -480,9 +480,9 @@ export declare abstract class Graffiti {
|
|
|
480
480
|
* all instance's of that friend's name in the user's application instantly,
|
|
481
481
|
* providing a consistent user experience.
|
|
482
482
|
*
|
|
483
|
-
* @group
|
|
483
|
+
* @group Synchronize Methods
|
|
484
484
|
*/
|
|
485
|
-
abstract
|
|
485
|
+
abstract synchronizeDiscover<Schema extends JSONSchema4>(
|
|
486
486
|
/**
|
|
487
487
|
* The {@link GraffitiObjectBase.channels | `channels`} that the objects must be associated with.
|
|
488
488
|
*/
|
|
@@ -498,6 +498,33 @@ export declare abstract class Graffiti {
|
|
|
498
498
|
* property will be returned.
|
|
499
499
|
*/
|
|
500
500
|
session?: GraffitiSession | null): GraffitiStream<GraffitiObject<Schema>>;
|
|
501
|
+
/**
|
|
502
|
+
* This method has the same signature as {@link get} but, like {@link synchronizeDiscover},
|
|
503
|
+
* it listens for changes made via {@link put}, {@link patch}, and {@link delete} or
|
|
504
|
+
* fetched from {@link get} or {@link discover} and then streams appropriate
|
|
505
|
+
* changes to provide a responsive and consistent user experience.
|
|
506
|
+
*
|
|
507
|
+
* Unlike {@link get}, which returns a single result, this method continuously
|
|
508
|
+
* listens for changes which are output as an asynchronous {@link GraffitiStream}.
|
|
509
|
+
*
|
|
510
|
+
* @group Synchronize Methods
|
|
511
|
+
*/
|
|
512
|
+
abstract synchronizeGet<Schema extends JSONSchema4>(
|
|
513
|
+
/**
|
|
514
|
+
* The location of the object to get.
|
|
515
|
+
*/
|
|
516
|
+
locationOrUri: GraffitiLocation | string,
|
|
517
|
+
/**
|
|
518
|
+
* The JSON schema to validate the retrieved object against.
|
|
519
|
+
*/
|
|
520
|
+
schema: Schema,
|
|
521
|
+
/**
|
|
522
|
+
* An implementation-specific object with information to authenticate the
|
|
523
|
+
* {@link GraffitiObjectBase.actor | `actor`}. If no `session` is provided,
|
|
524
|
+
* the retrieved object's {@link GraffitiObjectBase.allowed | `allowed`}
|
|
525
|
+
* property must be `undefined`.
|
|
526
|
+
*/
|
|
527
|
+
session?: GraffitiSession | null): GraffitiStream<GraffitiObject<Schema>>;
|
|
501
528
|
/**
|
|
502
529
|
* Returns a list of all {@link GraffitiObjectBase.channels | `channels`}
|
|
503
530
|
* that an {@link GraffitiObjectBase.actor | `actor`} has posted to.
|
package/dist/src/1-api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"1-api.d.ts","sourceRoot":"","sources":["../../src/1-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,cAAc,EACf,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2OG;AACH,8BAAsB,QAAQ;IAC5B;;;;;;;;;OASG;IACH,QAAQ,CAAC,aAAa,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM;IAE1D;;;;;;;;OAQG;IACH,QAAQ,CAAC,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB;IAErD;;;;OAIG;IACH,WAAW,CAAC,MAAM,EAAE,kBAAkB;IAItC;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,GAAG,CAAC,MAAM;IACjB;;;;;OAKG;IACH,MAAM,EAAE,iBAAiB,CAAC,MAAM,CAAC;IACjC;;;OAGG;IACH,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,kBAAkB,CAAC;IAE9B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,GAAG,CAAC,MAAM,SAAS,WAAW;IACrC;;OAEG;IACH,aAAa,EAAE,gBAAgB,GAAG,MAAM;IACxC;;OAEG;IACH,MAAM,EAAE,MAAM;IACd;;;;;OAKG;IACH,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,GAC/B,OAAO,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IAElC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,KAAK;IACZ;;;OAGG;IACH,KAAK,EAAE,aAAa;IACpB;;OAEG;IACH,aAAa,EAAE,gBAAgB,GAAG,MAAM;IACxC;;;OAGG;IACH,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,kBAAkB,CAAC;IAE9B;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,MAAM;IACb;;OAEG;IACH,aAAa,EAAE,gBAAgB,GAAG,MAAM;IACxC;;;OAGG;IACH,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,kBAAkB,CAAC;IAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+DG;IACH,QAAQ,CAAC,QAAQ,CAAC,MAAM,SAAS,WAAW;IAC1C;;OAEG;IACH,QAAQ,EAAE,MAAM,EAAE;IAClB;;OAEG;IACH,MAAM,EAAE,MAAM;IACd;;;;;OAKG;IACH,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,GAC/B,cAAc,CACf,cAAc,CAAC,MAAM,CAAC,EACtB;QACE,kBAAkB,EAAE,MAAM,CAAC;KAC5B,CACF;IAED;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"1-api.d.ts","sourceRoot":"","sources":["../../src/1-api.ts"],"names":[],"mappings":"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"}
|
package/dist/src/2-types.d.ts
CHANGED
|
@@ -57,7 +57,7 @@ export interface GraffitiObjectBase {
|
|
|
57
57
|
* the sender should put their object in the channel of the recipient's {@link actor | `actor`} URI to notify them of the message and also add
|
|
58
58
|
* the recipient's {@link actor | `actor`} URI to the `allowed` array to prevent others from seeing the message.
|
|
59
59
|
*/
|
|
60
|
-
allowed?: string[];
|
|
60
|
+
allowed?: string[] | null;
|
|
61
61
|
/**
|
|
62
62
|
* The URI of the `actor` that {@link Graffiti.put | created } the object. This `actor` also has the unique permission to
|
|
63
63
|
* {@link Graffiti.patch | modify} or {@link Graffiti.delete | delete} the object.
|
|
@@ -280,5 +280,5 @@ export type GraffitiLogoutEvent = CustomEvent<{
|
|
|
280
280
|
export type GraffitiSessionInitializedEvent = CustomEvent<{
|
|
281
281
|
error?: Error;
|
|
282
282
|
href?: string;
|
|
283
|
-
}>;
|
|
283
|
+
} | null | undefined>;
|
|
284
284
|
//# sourceMappingURL=2-types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"2-types.d.ts","sourceRoot":"","sources":["../../src/2-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,IAAI,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;;OAKG;IACH,KAAK,EAAE,EAAE,CAAC;IAEV;;;;;;;;;;;;OAYG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAC;IAEnB;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"2-types.d.ts","sourceRoot":"","sources":["../../src/2-types.ts"],"names":[],"mappings":"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"}
|
package/dist/tests/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{assert as e,describe as a,it as t,expect as o}from"vitest";import{GraffitiErrorInvalidUri as n,GraffitiErrorNotFound as l,GraffitiErrorForbidden as s,GraffitiErrorInvalidSchema as i,GraffitiErrorSchemaMismatch as r,GraffitiErrorPatchTestFailed as c,GraffitiErrorPatchError as u}from"@graffiti-garden/api";function d(){const e=new Uint8Array(16);return crypto.getRandomValues(e),Array.from(e).map((e=>e.toString(16).padStart(2,"0"))).join("")}function v(){return{value:{[d()]:d()},channels:[d(),d()]}}async function h(a){const t=await a.next();return e(!t.done&&!t.value.error,"result has no value"),t.value.value}const w=e=>{a("URI and location conversion",(()=>{t("location to uri and back",(async()=>{const a=e(),t={name:d(),actor:d(),source:d()},n=a.locationToUri(t),l=a.uriToLocation(n);o(t).toEqual(l)})),t("collision resistance",(async()=>{const a=e(),t={name:d(),actor:d(),source:d()};for(const e of["name","actor","source"]){const n={...t,[e]:d()},l=a.locationToUri(t),s=a.locationToUri(n);o(l).not.toEqual(s)}})),t("random URI should not be a valid location",(async()=>{const a=e();o((()=>a.uriToLocation(""))).toThrow(n)}))}))},p=(e,n,h)=>{a("CRUD",{timeout:2e4},(()=>{t("put, get, delete",(async()=>{const a=e(),t=n(),s={something:"hello, world~ c:"},i=[d(),d()],r=await a.put({value:s,channels:i},t);o(r.value).toEqual({}),o(r.channels).toEqual([]),o(r.allowed).toBeUndefined(),o(r.actor).toEqual(t.actor);const c=await a.get(r,{});o(c.value).toEqual(s),o(c.channels).toEqual([]),o(c.allowed).toBeUndefined(),o(c.name).toEqual(r.name),o(c.actor).toEqual(r.actor),o(c.source).toEqual(r.source),o(c.lastModified).toEqual(r.lastModified);const u={something:"goodbye, world~ :c"},v=await a.put({...r,value:u,channels:[]},t);o(v.value).toEqual(s),o(v.tombstone).toEqual(!0),o(v.name).toEqual(r.name),o(v.actor).toEqual(r.actor),o(v.source).toEqual(r.source),o(v.lastModified).toBeGreaterThanOrEqual(c.lastModified);const h=await a.get(r,{});o(h.value).toEqual(u),o(h.lastModified).toEqual(v.lastModified),o(h.tombstone).toEqual(!1);const w=await a.delete(h,t);o(w.tombstone).toEqual(!0),o(w.value).toEqual(u),o(w.lastModified).toBeGreaterThanOrEqual(v.lastModified),await o(a.get(h,{})).rejects.toThrow(l)})),t("put, get, delete with wrong actor",(async()=>{const a=e(),t=n(),l=h();await o(a.put({value:{},channels:[],actor:l.actor},t)).rejects.toThrow(s);const i=await a.put({value:{},channels:[]},l);await o(a.delete(i,t)).rejects.toThrow(s),await o(a.patch({},i,t)).rejects.toThrow(s)})),t("put and get with schema",(async()=>{const a=e(),t=n(),l={something:"hello",another:42},s=await a.put({value:l,channels:[]},t),i=await a.get(s,{properties:{value:{properties:{something:{type:"string"},another:{type:"integer"}}}}});o(i.value.something).toEqual(l.something),o(i.value.another).toEqual(l.another)})),t("put and get with invalid schema",(async()=>{const a=e(),t=n(),l=await a.put({value:{},channels:[]},t);await o(a.get(l,{properties:{value:{type:"asdf"}}})).rejects.toThrow(i)})),t("put and get with wrong schema",(async()=>{const a=e(),t=n(),l=await a.put({value:{hello:"world"},channels:[]},t);await o(a.get(l,{properties:{value:{properties:{hello:{type:"number"}}}}})).rejects.toThrow(r)})),t("put and get with empty access control",(async()=>{const a=e(),t=n(),l=h(),s={um:"hi"},i=[d()],r=[d()],c=await a.put({value:s,allowed:i,channels:r},t),u=await a.get(c,{},t);o(u.value).toEqual(s),o(u.allowed).toEqual(i),o(u.channels).toEqual(r),await o(a.get(c,{})).rejects.toThrow(),await o(a.get(c,{},l)).rejects.toThrow()})),t("put and get with specific access control",(async()=>{const a=e(),t=n(),l=h(),s={um:"hi"},i=[d(),l.actor,d()],r=[d()],c=await a.put({value:s,allowed:i,channels:r},t),u=await a.get(c,{},t);o(u.value).toEqual(s),o(u.allowed).toEqual(i),o(u.channels).toEqual(r),await o(a.get(c,{})).rejects.toThrow();const v=await a.get(c,{},l);o(v.value).toEqual(s),o(v.allowed).toEqual([l.actor]),o(v.channels).toEqual([])})),t("patch value",(async()=>{const a=e(),t=n(),l={something:"hello, world~ c:"},s=await a.put({value:l,channels:[]},t),i=await a.patch({value:[{op:"replace",path:"/something",value:"goodbye, world~ :c"}]},s,t);o(i.value).toEqual(l),o(i.tombstone).toBe(!0);const r=await a.get(s,{});o(r.value).toEqual({something:"goodbye, world~ :c"}),o(i.lastModified).toBe(r.lastModified),await a.delete(s,t)})),t("deep patch",(async()=>{const a=e(),t=n(),l={something:{another:{somethingElse:"hello"}}},s=await a.put({value:l,channels:[]},t),i=await a.patch({value:[{op:"replace",path:"/something/another/somethingElse",value:"goodbye"}]},s,t),r=await a.get(s,{});o(i.value).toEqual(l),o(r.value).toEqual({something:{another:{somethingElse:"goodbye"}}})})),t("patch channels",(async()=>{const a=e(),t=n(),l=[d()],s=[d()],i=await a.put({value:{},channels:l},t),r={channels:[{op:"replace",path:"/0",value:s[0]}]},c=await a.patch(r,i,t);o(c.channels).toEqual(l);const u=await a.get(i,{},t);o(u.channels).toEqual(s),await a.delete(i,t)})),t("patch 'increment' with test",(async()=>{const a=e(),t=n(),l=await a.put({value:{counter:1},channels:[]},t),s=await a.patch({value:[{op:"test",path:"/counter",value:1},{op:"replace",path:"/counter",value:2}]},l,t);o(s.value).toEqual({counter:1});const i=await a.get(s,{properties:{value:{properties:{counter:{type:"integer"}}}}});o(i.value.counter).toEqual(2),await o(a.patch({value:[{op:"test",path:"/counter",value:1},{op:"replace",path:"/counter",value:3}]},l,t)).rejects.toThrow(c)})),t("invalid patch",(async()=>{const a=e(),t=n(),l=v(),s=await a.put(l,t);await o(a.patch({value:[{op:"add",path:"/root",value:[]},{op:"add",path:"/root/2",value:2}]},s,t)).rejects.toThrow(u)})),t("patch channels to be wrong",(async()=>{const a=e(),t=n(),l=v();l.allowed=[d()];const s=await a.put(l,t),i=[{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(const e of i)await o(a.patch(e,s,t)).rejects.toThrow(u);const r=await a.get(s,{},t);o(r.value).toEqual(l.value),o(r.channels).toEqual(l.channels),o(r.allowed).toEqual(l.allowed),o(r.lastModified).toEqual(s.lastModified)}))}))},m=(e,n,l)=>{a("synchronize",(()=>{t("get",(async()=>{const a=e(),t=n(),l=v(),s=l.channels.slice(1),i=await a.put(l,t),r=e(),c=r.synchronize(s,{}).next(),u=await r.get(i,{},t),d=(await c).value;if(!d||d.error)throw new Error("Error in synchronize");o(d.value.value).toEqual(l.value),o(d.value.channels).toEqual(s),o(d.value.tombstone).toBe(!1),o(d.value.lastModified).toEqual(u.lastModified)})),t("put",(async()=>{const a=e(),t=n(),l=d(),s=d(),i=d(),r={hello:"world"},c=[l,i],u=await a.put({value:r,channels:c},t),v=a.synchronize([l],{}).next(),h=a.synchronize([s],{}).next(),w=a.synchronize([i],{}).next(),p={goodbye:"world"},m=[s,i];await a.put({...u,value:p,channels:m},t);const E=(await v).value,q=(await h).value,y=(await w).value;if(!E||E.error||!q||q.error||!y||y.error)throw new Error("Error in synchronize");o(E.value.value).toEqual(r),o(E.value.channels).toEqual([l]),o(E.value.tombstone).toBe(!0),o(q.value.value).toEqual(p),o(q.value.channels).toEqual([s]),o(q.value.tombstone).toBe(!1),o(y.value.value).toEqual(p),o(y.value.channels).toEqual([i]),o(y.value.tombstone).toBe(!1),o(E.value.lastModified).toEqual(q.value.lastModified),o(y.value.lastModified).toEqual(q.value.lastModified)})),t("patch",(async()=>{const a=e(),t=n(),l=d(),s=d(),i=d(),r={hello:"world"},c=[l,i],u=await a.put({value:r,channels:c},t),v=a.synchronize([l],{}).next(),h=a.synchronize([s],{}).next(),w=a.synchronize([i],{}).next();await a.patch({value:[{op:"add",path:"/something",value:"new value"}],channels:[{op:"add",path:"/-",value:s},{op:"remove",path:`/${c.indexOf(l)}`}]},u,t);const p=(await v).value,m=(await h).value,E=(await w).value;if(!p||p.error||!m||m.error||!E||E.error)throw new Error("Error in synchronize");const q={...r,something:"new value"};o(p.value.value).toEqual(r),o(p.value.channels).toEqual([l]),o(p.value.tombstone).toBe(!0),o(m.value.value).toEqual(q),o(m.value.channels).toEqual([s]),o(m.value.tombstone).toBe(!1),o(E.value.value).toEqual(q),o(E.value.channels).toEqual([i]),o(E.value.tombstone).toBe(!1),o(p.value.lastModified).toEqual(m.value.lastModified),o(E.value.lastModified).toEqual(m.value.lastModified)})),t("delete",(async()=>{const a=e(),t=n(),l=[d(),d(),d()],s={hello:"world"},i=[d(),...l.slice(1)],r=await a.put({value:s,channels:i},t),c=a.synchronize(l,{}).next();a.delete(r,t);const u=(await c).value;if(!u||u.error)throw new Error("Error in synchronize");o(u.value.tombstone).toBe(!0),o(u.value.value).toEqual(s),o(u.value.channels).toEqual(l.filter((e=>i.includes(e))))})),t("not allowed",(async()=>{const a=e(),t=n(),s=l(),i=[d(),d(),d()],r=i.slice(1),c=a.synchronize(r,{},t).next(),u=a.synchronize(r,{},s).next(),v=a.synchronize(r,{}).next(),h={hello:"world"},w=[d(),s.actor];await a.put({value:h,channels:i,allowed:w},t),await o(Promise.race([v,new Promise(((e,a)=>setTimeout(a,100,"Timeout")))])).rejects.toThrow("Timeout");const p=(await c).value,m=(await u).value;if(!p||p.error||!m||m.error)throw new Error("Error in synchronize");o(p.value.value).toEqual(h),o(p.value.allowed).toEqual(w),o(p.value.channels).toEqual(i),o(m.value.value).toEqual(h),o(m.value.allowed).toEqual([s.actor]),o(m.value.channels).toEqual(r)}))}))},E=(n,l,s)=>{a("discover",{timeout:2e4},(()=>{t("discover nothing",(async()=>{const e=n().discover([],{});o(await e.next()).toHaveProperty("done",!0)})),t("discover single",(async()=>{const e=n(),a=l(),t=v(),s=await e.put(t,a),i=[d(),t.channels[0]],r=e.discover(i,{}),c=await h(r);o(c.value).toEqual(t.value),o(c.channels).toEqual([t.channels[0]]),o(c.allowed).toBeUndefined(),o(c.actor).toEqual(a.actor),o(c.tombstone).toBe(!1),o(c.lastModified).toEqual(s.lastModified);const u=await r.next();o(u.done).toBe(!0)})),t("discover wrong channel",(async()=>{const e=n(),a=l(),t=v();await e.put(t,a);const s=e.discover([d()],{});await o(s.next()).resolves.toHaveProperty("done",!0)})),t("discover not allowed",(async()=>{const e=n(),a=l(),t=s(),i=v();i.allowed=[d(),d()];const r=await e.put(i,a),c=e.discover(i.channels,{},a),u=await h(c);o(u.value).toEqual(i.value),o(u.channels).toEqual(i.channels),o(u.allowed).toEqual(i.allowed),o(u.actor).toEqual(a.actor),o(u.tombstone).toBe(!1),o(u.lastModified).toEqual(r.lastModified);const w=e.discover(i.channels,{},t);o(await w.next()).toHaveProperty("done",!0);const p=e.discover(i.channels,{});o(await p.next()).toHaveProperty("done",!0)})),t("discover allowed",(async()=>{const e=n(),a=l(),t=s(),i=v();i.allowed=[d(),t.actor,d()];const r=await e.put(i,a),c=e.discover(i.channels,{},t),u=await h(c);o(u.value).toEqual(i.value),o(u.allowed).toEqual([t.actor]),o(u.channels).toEqual(i.channels),o(u.actor).toEqual(a.actor),o(u.tombstone).toBe(!1),o(u.lastModified).toEqual(r.lastModified)}));for(const e of["name","actor","lastModified"])t(`discover for ${e}`,(async()=>{const a=n(),t=l(),i=s(),r=v(),c=await a.put(r,t),u=v();u.channels=r.channels,await new Promise((e=>setTimeout(e,20)));const d=await a.put(u,i),w=a.discover(r.channels,{properties:{[e]:{enum:[c[e]]}}}),p=await h(w);o(p.name).toEqual(c.name),o(p.name).not.toEqual(d.name),o(p.value).toEqual(r.value),await o(w.next()).resolves.toHaveProperty("done",!0)}));t("discover with lastModified range",(async()=>{const e=n(),a=l(),t=v(),s=await e.put(t,a);await new Promise((e=>setTimeout(e,20)));const i=await e.put(t,a);o(s.name).not.toEqual(i.name),o(s.lastModified).toBeLessThan(i.lastModified);const r=e.discover([t.channels[0]],{properties:{lastModified:{minimum:i.lastModified,exclusiveMinimum:!0}}});o(await r.next()).toHaveProperty("done",!0);const c=e.discover([t.channels[0]],{properties:{lastModified:{minimum:i.lastModified-.1,exclusiveMinimum:!0}}}),u=await h(c);o(u.name).toEqual(i.name),o(await c.next()).toHaveProperty("done",!0);const d=e.discover(t.channels,{properties:{value:{},lastModified:{minimum:i.lastModified}}}),w=await h(d);o(w.name).toEqual(i.name),o(await d.next()).toHaveProperty("done",!0);const p=e.discover(t.channels,{properties:{lastModified:{minimum:i.lastModified+.1}}});o(await p.next()).toHaveProperty("done",!0);const m=e.discover(t.channels,{properties:{lastModified:{maximum:s.lastModified,exclusiveMaximum:!0}}});o(await m.next()).toHaveProperty("done",!0);const E=e.discover(t.channels,{properties:{lastModified:{maximum:s.lastModified+.1,exclusiveMaximum:!0}}}),q=await h(E);o(q.name).toEqual(s.name),o(await E.next()).toHaveProperty("done",!0);const y=e.discover(t.channels,{properties:{lastModified:{maximum:s.lastModified}}}),f=await h(y);o(f.name).toEqual(s.name),o(await y.next()).toHaveProperty("done",!0);const g=e.discover(t.channels,{properties:{lastModified:{maximum:s.lastModified-.1}}});o(await g.next()).toHaveProperty("done",!0)})),t("discover schema allowed, as and not as owner",(async()=>{const e=n(),a=l(),t=s(),i=v();i.allowed=[d(),t.actor,d()],await e.put(i,a);const r=e.discover(i.channels,{properties:{allowed:{minItems:3,not:{items:{not:{enum:[t.actor]}}}}}},a),c=await h(r);o(c.value).toEqual(i.value),await o(r.next()).resolves.toHaveProperty("done",!0);const u=e.discover(i.channels,{properties:{allowed:{minItems:3}}},t);await o(u.next()).resolves.toHaveProperty("done",!0);const w=e.discover(i.channels,{properties:{allowed:{not:{items:{not:{enum:[i.channels[0]]}}}}}},t);await o(w.next()).resolves.toHaveProperty("done",!0);const p=e.discover(i.channels,{properties:{allowed:{maxItems:1,not:{items:{not:{enum:[t.actor]}}}}}},t),m=await h(p);o(m.value).toEqual(i.value),await o(p.next()).resolves.toHaveProperty("done",!0)})),t("discover schema channels, as and not as owner",(async()=>{const e=n(),a=l(),t=s(),i=v();i.channels=[d(),d(),d()],await e.put(i,a);const r=e.discover([i.channels[0],i.channels[2]],{properties:{channels:{minItems:3,not:{items:{not:{enum:[i.channels[1]]}}}}}},a),c=await h(r);o(c.value).toEqual(i.value),await o(r.next()).resolves.toHaveProperty("done",!0);const u=e.discover([i.channels[0],i.channels[2]],{properties:{channels:{minItems:3}}},t);await o(u.next()).resolves.toHaveProperty("done",!0);const w=e.discover([i.channels[0],i.channels[2]],{properties:{channels:{not:{items:{not:{enum:[i.channels[1]]}}}}}},t);await o(w.next()).resolves.toHaveProperty("done",!0);const p=e.discover([i.channels[0],i.channels[2]],{properties:{allowed:{maxItems:2,not:{items:{not:{enum:[i.channels[2]]}}}}}},t),m=await h(p);o(m.value).toEqual(i.value),await o(p.next()).resolves.toHaveProperty("done",!0)})),t("discover query for empty allowed",(async()=>{const e=n(),a=l(),t=v(),s={not:{required:["allowed"]}};await e.put(t,a);const i=e.discover(t.channels,s,a),r=await h(i);o(r.value).toEqual(t.value),o(r.allowed).toBeUndefined(),await o(i.next()).resolves.toHaveProperty("done",!0);const c=v();c.allowed=[],await e.put(c,a);const u=e.discover(c.channels,s,a);await o(u.next()).resolves.toHaveProperty("done",!0)})),t("discover query for values",(async()=>{const a=n(),t=l(),s=v();s.value={test:d()},await a.put(s,t);const i=v();i.channels=s.channels,i.value={test:d(),something:d()},await a.put(i,t);const r=v();r.channels=s.channels,r.value={other:d(),something:d()},await a.put(r,t);const c=new Map;for(const t of["test","something","other"]){let o=0;for await(const n of a.discover(s.channels,{properties:{value:{required:[t]}}}))e(!n.error,"result has error"),t in n.value.value&&o++;c.set(t,o)}o(c.get("test")).toBe(2),o(c.get("something")).toBe(2),o(c.get("other")).toBe(1)})),t("discover for deleted content",(async()=>{const e=n(),a=l(),t=v(),s=await e.put(t,a),i=await e.delete(s,a),r=e.discover(t.channels,{}),c=await h(r);o(c.tombstone).toBe(!0),o(c.value).toEqual(t.value),o(c.channels).toEqual(t.channels),o(c.actor).toEqual(a.actor),o(c.lastModified).toEqual(i.lastModified),await o(r.next()).resolves.toHaveProperty("done",!0)})),t("discover for replaced channels",(async()=>{for(let e=0;e<10;e++){const e=n(),a=l(),t=v(),s=await e.put(t,a),i=v(),r=await e.put({...s,...i},a),c=e.discover(t.channels,{}),u=await h(c);await o(c.next()).resolves.toHaveProperty("done",!0);const d=e.discover(i.channels,{}),w=await h(d);await o(d.next()).resolves.toHaveProperty("done",!0),s.lastModified===r.lastModified?(o(u.tombstone||w.tombstone).toBe(!0),o(u.tombstone&&w.tombstone).toBe(!1)):(o(u.tombstone).toBe(!0),o(u.value).toEqual(t.value),o(u.channels).toEqual(t.channels),o(u.lastModified).toEqual(r.lastModified),o(w.tombstone).toBe(!1),o(w.value).toEqual(i.value),o(w.channels).toEqual(i.channels),o(w.lastModified).toEqual(r.lastModified))}})),t("discover for patched allowed",(async()=>{const e=n(),a=l(),t=v(),s=await e.put(t,a);await e.patch({allowed:[{op:"add",path:"",value:[]}]},s,a);const i=e.discover(t.channels,{}),r=await h(i);o(r.tombstone).toBe(!0),o(r.value).toEqual(t.value),o(r.channels).toEqual(t.channels),o(r.allowed).toBeUndefined(),await o(i.next()).resolves.toHaveProperty("done",!0)})),t("put concurrently and discover one",(async()=>{const a=n(),t=l(),s=v();s.name=d();const i=Array(100).fill(0).map((()=>a.put(s,t)));await Promise.all(i);const r=a.discover(s.channels,{});let c=0,u=0;for await(const a of r)e(!a.error,"result has error"),a.value.tombstone?c++:u++;o(c).toBe(99),o(u).toBe(1)}))}))};export{p as graffitiCRUDTests,E as graffitiDiscoverTests,w as graffitiLocationTests,m as graffitiSynchronizeTests};
|
|
1
|
+
import{assert as e,describe as a,it as t,expect as o}from"vitest";import{GraffitiErrorInvalidUri as n,GraffitiErrorNotFound as l,GraffitiErrorForbidden as s,GraffitiErrorInvalidSchema as i,GraffitiErrorSchemaMismatch as r,GraffitiErrorPatchTestFailed as c,GraffitiErrorPatchError as u}from"@graffiti-garden/api";function d(){const e=new Uint8Array(16);return crypto.getRandomValues(e),Array.from(e).map((e=>e.toString(16).padStart(2,"0"))).join("")}function v(){return{value:{[d()]:d()},channels:[d(),d()]}}async function h(a){const t=await a.next();return e(!t.done&&!t.value.error,"result has no value"),t.value.value}const w=e=>{a("URI and location conversion",(()=>{t("location to uri and back",(async()=>{const a=e(),t={name:d(),actor:d(),source:d()},n=a.locationToUri(t),l=a.uriToLocation(n);o(t).toEqual(l)})),t("collision resistance",(async()=>{const a=e(),t={name:d(),actor:d(),source:d()};for(const e of["name","actor","source"]){const n={...t,[e]:d()},l=a.locationToUri(t),s=a.locationToUri(n);o(l).not.toEqual(s)}})),t("random URI should not be a valid location",(async()=>{const a=e();o((()=>a.uriToLocation(""))).toThrow(n)}))}))},p=(e,n,h)=>{a("CRUD",{timeout:2e4},(()=>{t("put, get, delete",(async()=>{const a=e(),t=n(),s={something:"hello, world~ c:"},i=[d(),d()],r=await a.put({value:s,channels:i},t);o(r.value).toEqual({}),o(r.channels).toEqual([]),o(r.allowed).toBeUndefined(),o(r.actor).toEqual(t.actor);const c=await a.get(r,{});o(c.value).toEqual(s),o(c.channels).toEqual([]),o(c.allowed).toBeUndefined(),o(c.name).toEqual(r.name),o(c.actor).toEqual(r.actor),o(c.source).toEqual(r.source),o(c.lastModified).toEqual(r.lastModified);const u={something:"goodbye, world~ :c"},v=await a.put({...r,value:u,channels:[]},t);o(v.value).toEqual(s),o(v.tombstone).toEqual(!0),o(v.name).toEqual(r.name),o(v.actor).toEqual(r.actor),o(v.source).toEqual(r.source),o(v.lastModified).toBeGreaterThanOrEqual(c.lastModified);const h=await a.get(r,{});o(h.value).toEqual(u),o(h.lastModified).toEqual(v.lastModified),o(h.tombstone).toEqual(!1);const w=await a.delete(h,t);o(w.tombstone).toEqual(!0),o(w.value).toEqual(u),o(w.lastModified).toBeGreaterThanOrEqual(v.lastModified),await o(a.get(h,{})).rejects.toThrow(l)})),t("put, get, delete with wrong actor",(async()=>{const a=e(),t=n(),l=h();await o(a.put({value:{},channels:[],actor:l.actor},t)).rejects.toThrow(s);const i=await a.put({value:{},channels:[]},l);await o(a.delete(i,t)).rejects.toThrow(s),await o(a.patch({},i,t)).rejects.toThrow(s)})),t("put and get with schema",(async()=>{const a=e(),t=n(),l={something:"hello",another:42},s=await a.put({value:l,channels:[]},t),i=await a.get(s,{properties:{value:{properties:{something:{type:"string"},another:{type:"integer"}}}}});o(i.value.something).toEqual(l.something),o(i.value.another).toEqual(l.another)})),t("put and get with invalid schema",(async()=>{const a=e(),t=n(),l=await a.put({value:{},channels:[]},t);await o(a.get(l,{properties:{value:{type:"asdf"}}})).rejects.toThrow(i)})),t("put and get with wrong schema",(async()=>{const a=e(),t=n(),l=await a.put({value:{hello:"world"},channels:[]},t);await o(a.get(l,{properties:{value:{properties:{hello:{type:"number"}}}}})).rejects.toThrow(r)})),t("put and get with empty access control",(async()=>{const a=e(),t=n(),l=h(),s={um:"hi"},i=[d()],r=[d()],c=await a.put({value:s,allowed:i,channels:r},t),u=await a.get(c,{},t);o(u.value).toEqual(s),o(u.allowed).toEqual(i),o(u.channels).toEqual(r),await o(a.get(c,{})).rejects.toThrow(),await o(a.get(c,{},l)).rejects.toThrow()})),t("put and get with specific access control",(async()=>{const a=e(),t=n(),l=h(),s={um:"hi"},i=[d(),l.actor,d()],r=[d()],c=await a.put({value:s,allowed:i,channels:r},t),u=await a.get(c,{},t);o(u.value).toEqual(s),o(u.allowed).toEqual(i),o(u.channels).toEqual(r),await o(a.get(c,{})).rejects.toThrow();const v=await a.get(c,{},l);o(v.value).toEqual(s),o(v.allowed).toEqual([l.actor]),o(v.channels).toEqual([])})),t("patch value",(async()=>{const a=e(),t=n(),l={something:"hello, world~ c:"},s=await a.put({value:l,channels:[]},t),i=await a.patch({value:[{op:"replace",path:"/something",value:"goodbye, world~ :c"}]},s,t);o(i.value).toEqual(l),o(i.tombstone).toBe(!0);const r=await a.get(s,{});o(r.value).toEqual({something:"goodbye, world~ :c"}),o(i.lastModified).toBe(r.lastModified),await a.delete(s,t)})),t("deep patch",(async()=>{const a=e(),t=n(),l={something:{another:{somethingElse:"hello"}}},s=await a.put({value:l,channels:[]},t),i=await a.patch({value:[{op:"replace",path:"/something/another/somethingElse",value:"goodbye"}]},s,t),r=await a.get(s,{});o(i.value).toEqual(l),o(r.value).toEqual({something:{another:{somethingElse:"goodbye"}}})})),t("patch channels",(async()=>{const a=e(),t=n(),l=[d()],s=[d()],i=await a.put({value:{},channels:l},t),r={channels:[{op:"replace",path:"/0",value:s[0]}]},c=await a.patch(r,i,t);o(c.channels).toEqual(l);const u=await a.get(i,{},t);o(u.channels).toEqual(s),await a.delete(i,t)})),t("patch 'increment' with test",(async()=>{const a=e(),t=n(),l=await a.put({value:{counter:1},channels:[]},t),s=await a.patch({value:[{op:"test",path:"/counter",value:1},{op:"replace",path:"/counter",value:2}]},l,t);o(s.value).toEqual({counter:1});const i=await a.get(s,{properties:{value:{properties:{counter:{type:"integer"}}}}});o(i.value.counter).toEqual(2),await o(a.patch({value:[{op:"test",path:"/counter",value:1},{op:"replace",path:"/counter",value:3}]},l,t)).rejects.toThrow(c)})),t("invalid patch",(async()=>{const a=e(),t=n(),l=v(),s=await a.put(l,t);await o(a.patch({value:[{op:"add",path:"/root",value:[]},{op:"add",path:"/root/2",value:2}]},s,t)).rejects.toThrow(u)})),t("patch channels to be wrong",(async()=>{const a=e(),t=n(),l=v();l.allowed=[d()];const s=await a.put(l,t),i=[{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(const e of i)await o(a.patch(e,s,t)).rejects.toThrow(u);const r=await a.get(s,{},t);o(r.value).toEqual(l.value),o(r.channels).toEqual(l.channels),o(r.allowed).toEqual(l.allowed),o(r.lastModified).toEqual(s.lastModified)}))}))},m=(n,l,s)=>{a("synchronizeDiscover",(()=>{t("get",(async()=>{const e=n(),a=l(),t=v(),s=t.channels.slice(1),i=await e.put(t,a),r=n(),c=r.synchronizeDiscover(s,{}).next(),u=await r.get(i,{},a),d=(await c).value;if(!d||d.error)throw new Error("Error in synchronize");o(d.value.value).toEqual(t.value),o(d.value.channels).toEqual(s),o(d.value.tombstone).toBe(!1),o(d.value.lastModified).toEqual(u.lastModified)})),t("put",(async()=>{const e=n(),a=l(),t=d(),s=d(),i=d(),r={hello:"world"},c=[t,i],u=await e.put({value:r,channels:c},a),v=e.synchronizeDiscover([t],{}).next(),h=e.synchronizeDiscover([s],{}).next(),w=e.synchronizeDiscover([i],{}).next(),p={goodbye:"world"},m=[s,i];await e.put({...u,value:p,channels:m},a);const E=(await v).value,q=(await h).value,y=(await w).value;if(!E||E.error||!q||q.error||!y||y.error)throw new Error("Error in synchronize");o(E.value.value).toEqual(r),o(E.value.channels).toEqual([t]),o(E.value.tombstone).toBe(!0),o(q.value.value).toEqual(p),o(q.value.channels).toEqual([s]),o(q.value.tombstone).toBe(!1),o(y.value.value).toEqual(p),o(y.value.channels).toEqual([i]),o(y.value.tombstone).toBe(!1),o(E.value.lastModified).toEqual(q.value.lastModified),o(y.value.lastModified).toEqual(q.value.lastModified)})),t("patch",(async()=>{const e=n(),a=l(),t=d(),s=d(),i=d(),r={hello:"world"},c=[t,i],u=await e.put({value:r,channels:c},a),v=e.synchronizeDiscover([t],{}).next(),h=e.synchronizeDiscover([s],{}).next(),w=e.synchronizeDiscover([i],{}).next();await e.patch({value:[{op:"add",path:"/something",value:"new value"}],channels:[{op:"add",path:"/-",value:s},{op:"remove",path:`/${c.indexOf(t)}`}]},u,a);const p=(await v).value,m=(await h).value,E=(await w).value;if(!p||p.error||!m||m.error||!E||E.error)throw new Error("Error in synchronize");const q={...r,something:"new value"};o(p.value.value).toEqual(r),o(p.value.channels).toEqual([t]),o(p.value.tombstone).toBe(!0),o(m.value.value).toEqual(q),o(m.value.channels).toEqual([s]),o(m.value.tombstone).toBe(!1),o(E.value.value).toEqual(q),o(E.value.channels).toEqual([i]),o(E.value.tombstone).toBe(!1),o(p.value.lastModified).toEqual(m.value.lastModified),o(E.value.lastModified).toEqual(m.value.lastModified)})),t("delete",(async()=>{const e=n(),a=l(),t=[d(),d(),d()],s={hello:"world"},i=[d(),...t.slice(1)],r=await e.put({value:s,channels:i},a),c=e.synchronizeDiscover(t,{}).next();e.delete(r,a);const u=(await c).value;if(!u||u.error)throw new Error("Error in synchronize");o(u.value.tombstone).toBe(!0),o(u.value.value).toEqual(s),o(u.value.channels).toEqual(t.filter((e=>i.includes(e))))})),t("not allowed",(async()=>{const e=n(),a=l(),t=s(),i=[d(),d(),d()],r=i.slice(1),c=e.synchronizeDiscover(r,{},a).next(),u=e.synchronizeDiscover(r,{},t).next(),v=e.synchronizeDiscover(r,{}).next(),h={hello:"world"},w=[d(),t.actor];await e.put({value:h,channels:i,allowed:w},a),await o(Promise.race([v,new Promise(((e,a)=>setTimeout(a,100,"Timeout")))])).rejects.toThrow("Timeout");const p=(await c).value,m=(await u).value;if(!p||p.error||!m||m.error)throw new Error("Error in synchronize");o(p.value.value).toEqual(h),o(p.value.allowed).toEqual(w),o(p.value.channels).toEqual(i),o(m.value.value).toEqual(h),o(m.value.allowed).toEqual([t.actor]),o(m.value.channels).toEqual(r)}))})),a("synchronizeGet",(()=>{t("replace, delete",(async()=>{const a=n(),t=l(),s=v(),i=await a.put(s,t),r=a.synchronizeGet(i,{}),c=r.next(),u={goodbye:"world"},d=await a.put({...i,value:u},t),h=(await c).value;e(h&&!h.error),o(h.value.value).toEqual(u),o(h.value.actor).toEqual(t.actor),o(h.value.channels).toEqual([]),o(h.value.tombstone).toBe(!1),o(h.value.lastModified).toEqual(d.lastModified),o(h.value.allowed).toBeUndefined();const w=await a.delete(d,t),p=(await r.next()).value;e(p&&!p.error),o(p.value.tombstone).toBe(!0),o(p.value.lastModified).toEqual(w.lastModified),await a.put(v(),t),await o(Promise.race([r.next(),new Promise(((e,a)=>setTimeout(a,100,"Timeout")))])).rejects.toThrow("Timeout")})),t("not allowed",(async()=>{const a=n(),t=l(),i=s(),r=v(),c=await a.put(r,t),u=a.synchronizeGet(c,{},t),d=a.synchronizeGet(c,{},i),h=u.next(),w=d.next(),p={goodbye:"world"},m=await a.put({...c,...r,allowed:[],value:p},t),E=(await h).value,q=(await w).value;e(E&&!E.error),e(q&&!q.error),o(E.value.value).toEqual(p),o(q.value.value).toEqual(r.value),o(E.value.actor).toEqual(t.actor),o(q.value.actor).toEqual(t.actor),o(E.value.channels).toEqual(r.channels),o(q.value.channels).toEqual([]),o(E.value.tombstone).toBe(!1),o(q.value.tombstone).toBe(!0),o(E.value.lastModified).toEqual(m.lastModified),o(q.value.lastModified).toEqual(m.lastModified)}))}))},E=(n,l,s)=>{a("discover",{timeout:2e4},(()=>{t("discover nothing",(async()=>{const e=n().discover([],{});o(await e.next()).toHaveProperty("done",!0)})),t("discover single",(async()=>{const e=n(),a=l(),t=v(),s=await e.put(t,a),i=[d(),t.channels[0]],r=e.discover(i,{}),c=await h(r);o(c.value).toEqual(t.value),o(c.channels).toEqual([t.channels[0]]),o(c.allowed).toBeUndefined(),o(c.actor).toEqual(a.actor),o(c.tombstone).toBe(!1),o(c.lastModified).toEqual(s.lastModified);const u=await r.next();o(u.done).toBe(!0)})),t("discover wrong channel",(async()=>{const e=n(),a=l(),t=v();await e.put(t,a);const s=e.discover([d()],{});await o(s.next()).resolves.toHaveProperty("done",!0)})),t("discover not allowed",(async()=>{const e=n(),a=l(),t=s(),i=v();i.allowed=[d(),d()];const r=await e.put(i,a),c=e.discover(i.channels,{},a),u=await h(c);o(u.value).toEqual(i.value),o(u.channels).toEqual(i.channels),o(u.allowed).toEqual(i.allowed),o(u.actor).toEqual(a.actor),o(u.tombstone).toBe(!1),o(u.lastModified).toEqual(r.lastModified);const w=e.discover(i.channels,{},t);o(await w.next()).toHaveProperty("done",!0);const p=e.discover(i.channels,{});o(await p.next()).toHaveProperty("done",!0)})),t("discover allowed",(async()=>{const e=n(),a=l(),t=s(),i=v();i.allowed=[d(),t.actor,d()];const r=await e.put(i,a),c=e.discover(i.channels,{},t),u=await h(c);o(u.value).toEqual(i.value),o(u.allowed).toEqual([t.actor]),o(u.channels).toEqual(i.channels),o(u.actor).toEqual(a.actor),o(u.tombstone).toBe(!1),o(u.lastModified).toEqual(r.lastModified)}));for(const e of["name","actor","lastModified"])t(`discover for ${e}`,(async()=>{const a=n(),t=l(),i=s(),r=v(),c=await a.put(r,t),u=v();u.channels=r.channels,await new Promise((e=>setTimeout(e,20)));const d=await a.put(u,i),w=a.discover(r.channels,{properties:{[e]:{enum:[c[e]]}}}),p=await h(w);o(p.name).toEqual(c.name),o(p.name).not.toEqual(d.name),o(p.value).toEqual(r.value),await o(w.next()).resolves.toHaveProperty("done",!0)}));t("discover with lastModified range",(async()=>{const e=n(),a=l(),t=v(),s=await e.put(t,a);await new Promise((e=>setTimeout(e,20)));const i=await e.put(t,a);o(s.name).not.toEqual(i.name),o(s.lastModified).toBeLessThan(i.lastModified);const r=e.discover([t.channels[0]],{properties:{lastModified:{minimum:i.lastModified,exclusiveMinimum:!0}}});o(await r.next()).toHaveProperty("done",!0);const c=e.discover([t.channels[0]],{properties:{lastModified:{minimum:i.lastModified-.1,exclusiveMinimum:!0}}}),u=await h(c);o(u.name).toEqual(i.name),o(await c.next()).toHaveProperty("done",!0);const d=e.discover(t.channels,{properties:{value:{},lastModified:{minimum:i.lastModified}}}),w=await h(d);o(w.name).toEqual(i.name),o(await d.next()).toHaveProperty("done",!0);const p=e.discover(t.channels,{properties:{lastModified:{minimum:i.lastModified+.1}}});o(await p.next()).toHaveProperty("done",!0);const m=e.discover(t.channels,{properties:{lastModified:{maximum:s.lastModified,exclusiveMaximum:!0}}});o(await m.next()).toHaveProperty("done",!0);const E=e.discover(t.channels,{properties:{lastModified:{maximum:s.lastModified+.1,exclusiveMaximum:!0}}}),q=await h(E);o(q.name).toEqual(s.name),o(await E.next()).toHaveProperty("done",!0);const y=e.discover(t.channels,{properties:{lastModified:{maximum:s.lastModified}}}),f=await h(y);o(f.name).toEqual(s.name),o(await y.next()).toHaveProperty("done",!0);const g=e.discover(t.channels,{properties:{lastModified:{maximum:s.lastModified-.1}}});o(await g.next()).toHaveProperty("done",!0)})),t("discover schema allowed, as and not as owner",(async()=>{const e=n(),a=l(),t=s(),i=v();i.allowed=[d(),t.actor,d()],await e.put(i,a);const r=e.discover(i.channels,{properties:{allowed:{minItems:3,not:{items:{not:{enum:[t.actor]}}}}}},a),c=await h(r);o(c.value).toEqual(i.value),await o(r.next()).resolves.toHaveProperty("done",!0);const u=e.discover(i.channels,{properties:{allowed:{minItems:3}}},t);await o(u.next()).resolves.toHaveProperty("done",!0);const w=e.discover(i.channels,{properties:{allowed:{not:{items:{not:{enum:[i.channels[0]]}}}}}},t);await o(w.next()).resolves.toHaveProperty("done",!0);const p=e.discover(i.channels,{properties:{allowed:{maxItems:1,not:{items:{not:{enum:[t.actor]}}}}}},t),m=await h(p);o(m.value).toEqual(i.value),await o(p.next()).resolves.toHaveProperty("done",!0)})),t("discover schema channels, as and not as owner",(async()=>{const e=n(),a=l(),t=s(),i=v();i.channels=[d(),d(),d()],await e.put(i,a);const r=e.discover([i.channels[0],i.channels[2]],{properties:{channels:{minItems:3,not:{items:{not:{enum:[i.channels[1]]}}}}}},a),c=await h(r);o(c.value).toEqual(i.value),await o(r.next()).resolves.toHaveProperty("done",!0);const u=e.discover([i.channels[0],i.channels[2]],{properties:{channels:{minItems:3}}},t);await o(u.next()).resolves.toHaveProperty("done",!0);const w=e.discover([i.channels[0],i.channels[2]],{properties:{channels:{not:{items:{not:{enum:[i.channels[1]]}}}}}},t);await o(w.next()).resolves.toHaveProperty("done",!0);const p=e.discover([i.channels[0],i.channels[2]],{properties:{allowed:{maxItems:2,not:{items:{not:{enum:[i.channels[2]]}}}}}},t),m=await h(p);o(m.value).toEqual(i.value),await o(p.next()).resolves.toHaveProperty("done",!0)})),t("discover query for empty allowed",(async()=>{const e=n(),a=l(),t=v(),s={not:{required:["allowed"]}};await e.put(t,a);const i=e.discover(t.channels,s,a),r=await h(i);o(r.value).toEqual(t.value),o(r.allowed).toBeUndefined(),await o(i.next()).resolves.toHaveProperty("done",!0);const c=v();c.allowed=[],await e.put(c,a);const u=e.discover(c.channels,s,a);await o(u.next()).resolves.toHaveProperty("done",!0)})),t("discover query for values",(async()=>{const a=n(),t=l(),s=v();s.value={test:d()},await a.put(s,t);const i=v();i.channels=s.channels,i.value={test:d(),something:d()},await a.put(i,t);const r=v();r.channels=s.channels,r.value={other:d(),something:d()},await a.put(r,t);const c=new Map;for(const t of["test","something","other"]){let o=0;for await(const n of a.discover(s.channels,{properties:{value:{required:[t]}}}))e(!n.error,"result has error"),t in n.value.value&&o++;c.set(t,o)}o(c.get("test")).toBe(2),o(c.get("something")).toBe(2),o(c.get("other")).toBe(1)})),t("discover for deleted content",(async()=>{const e=n(),a=l(),t=v(),s=await e.put(t,a),i=await e.delete(s,a),r=e.discover(t.channels,{}),c=await h(r);o(c.tombstone).toBe(!0),o(c.value).toEqual(t.value),o(c.channels).toEqual(t.channels),o(c.actor).toEqual(a.actor),o(c.lastModified).toEqual(i.lastModified),await o(r.next()).resolves.toHaveProperty("done",!0)})),t("discover for replaced channels",(async()=>{for(let e=0;e<10;e++){const e=n(),a=l(),t=v(),s=await e.put(t,a),i=v(),r=await e.put({...s,...i},a),c=e.discover(t.channels,{}),u=await h(c);await o(c.next()).resolves.toHaveProperty("done",!0);const d=e.discover(i.channels,{}),w=await h(d);await o(d.next()).resolves.toHaveProperty("done",!0),s.lastModified===r.lastModified?(o(u.tombstone||w.tombstone).toBe(!0),o(u.tombstone&&w.tombstone).toBe(!1)):(o(u.tombstone).toBe(!0),o(u.value).toEqual(t.value),o(u.channels).toEqual(t.channels),o(u.lastModified).toEqual(r.lastModified),o(w.tombstone).toBe(!1),o(w.value).toEqual(i.value),o(w.channels).toEqual(i.channels),o(w.lastModified).toEqual(r.lastModified))}})),t("discover for patched allowed",(async()=>{const e=n(),a=l(),t=v(),s=await e.put(t,a);await e.patch({allowed:[{op:"add",path:"",value:[]}]},s,a);const i=e.discover(t.channels,{}),r=await h(i);o(r.tombstone).toBe(!0),o(r.value).toEqual(t.value),o(r.channels).toEqual(t.channels),o(r.allowed).toBeUndefined(),await o(i.next()).resolves.toHaveProperty("done",!0)})),t("put concurrently and discover one",(async()=>{const a=n(),t=l(),s=v();s.name=d();const i=Array(100).fill(0).map((()=>a.put(s,t)));await Promise.all(i);const r=a.discover(s.channels,{});let c=0,u=0;for await(const a of r)e(!a.error,"result has error"),a.value.tombstone?c++:u++;o(c).toBe(99),o(u).toBe(1)}))}))};export{p as graffitiCRUDTests,E as graffitiDiscoverTests,w as graffitiLocationTests,m as graffitiSynchronizeTests};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"synchronize.d.ts","sourceRoot":"","sources":["../../../tests/src/synchronize.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG7E,eAAO,MAAM,wBAAwB,gBACtB,eAAe,eACf,MAAM,eAAe,eACrB,MAAM,eAAe,
|
|
1
|
+
{"version":3,"file":"synchronize.d.ts","sourceRoot":"","sources":["../../../tests/src/synchronize.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG7E,eAAO,MAAM,wBAAwB,gBACtB,eAAe,eACf,MAAM,eAAe,eACrB,MAAM,eAAe,SAyVnC,CAAC"}
|
package/package.json
CHANGED
package/src/1-api.ts
CHANGED
|
@@ -507,9 +507,9 @@ export abstract class Graffiti {
|
|
|
507
507
|
* all instance's of that friend's name in the user's application instantly,
|
|
508
508
|
* providing a consistent user experience.
|
|
509
509
|
*
|
|
510
|
-
* @group
|
|
510
|
+
* @group Synchronize Methods
|
|
511
511
|
*/
|
|
512
|
-
abstract
|
|
512
|
+
abstract synchronizeDiscover<Schema extends JSONSchema4>(
|
|
513
513
|
/**
|
|
514
514
|
* The {@link GraffitiObjectBase.channels | `channels`} that the objects must be associated with.
|
|
515
515
|
*/
|
|
@@ -527,6 +527,35 @@ export abstract class Graffiti {
|
|
|
527
527
|
session?: GraffitiSession | null,
|
|
528
528
|
): GraffitiStream<GraffitiObject<Schema>>;
|
|
529
529
|
|
|
530
|
+
/**
|
|
531
|
+
* This method has the same signature as {@link get} but, like {@link synchronizeDiscover},
|
|
532
|
+
* it listens for changes made via {@link put}, {@link patch}, and {@link delete} or
|
|
533
|
+
* fetched from {@link get} or {@link discover} and then streams appropriate
|
|
534
|
+
* changes to provide a responsive and consistent user experience.
|
|
535
|
+
*
|
|
536
|
+
* Unlike {@link get}, which returns a single result, this method continuously
|
|
537
|
+
* listens for changes which are output as an asynchronous {@link GraffitiStream}.
|
|
538
|
+
*
|
|
539
|
+
* @group Synchronize Methods
|
|
540
|
+
*/
|
|
541
|
+
abstract synchronizeGet<Schema extends JSONSchema4>(
|
|
542
|
+
/**
|
|
543
|
+
* The location of the object to get.
|
|
544
|
+
*/
|
|
545
|
+
locationOrUri: GraffitiLocation | string,
|
|
546
|
+
/**
|
|
547
|
+
* The JSON schema to validate the retrieved object against.
|
|
548
|
+
*/
|
|
549
|
+
schema: Schema,
|
|
550
|
+
/**
|
|
551
|
+
* An implementation-specific object with information to authenticate the
|
|
552
|
+
* {@link GraffitiObjectBase.actor | `actor`}. If no `session` is provided,
|
|
553
|
+
* the retrieved object's {@link GraffitiObjectBase.allowed | `allowed`}
|
|
554
|
+
* property must be `undefined`.
|
|
555
|
+
*/
|
|
556
|
+
session?: GraffitiSession | null,
|
|
557
|
+
): GraffitiStream<GraffitiObject<Schema>>;
|
|
558
|
+
|
|
530
559
|
/**
|
|
531
560
|
* Returns a list of all {@link GraffitiObjectBase.channels | `channels`}
|
|
532
561
|
* that an {@link GraffitiObjectBase.actor | `actor`} has posted to.
|
package/src/2-types.ts
CHANGED
|
@@ -60,7 +60,7 @@ export interface GraffitiObjectBase {
|
|
|
60
60
|
* the sender should put their object in the channel of the recipient's {@link actor | `actor`} URI to notify them of the message and also add
|
|
61
61
|
* the recipient's {@link actor | `actor`} URI to the `allowed` array to prevent others from seeing the message.
|
|
62
62
|
*/
|
|
63
|
-
allowed?: string[];
|
|
63
|
+
allowed?: string[] | null;
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* The URI of the `actor` that {@link Graffiti.put | created } the object. This `actor` also has the unique permission to
|
|
@@ -314,7 +314,11 @@ export type GraffitiLogoutEvent = CustomEvent<
|
|
|
314
314
|
* if there were any redirects during the restoration process.
|
|
315
315
|
* The event name to listen for is `initialized`.
|
|
316
316
|
*/
|
|
317
|
-
export type GraffitiSessionInitializedEvent = CustomEvent<
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
317
|
+
export type GraffitiSessionInitializedEvent = CustomEvent<
|
|
318
|
+
| {
|
|
319
|
+
error?: Error;
|
|
320
|
+
href?: string;
|
|
321
|
+
}
|
|
322
|
+
| null
|
|
323
|
+
| undefined
|
|
324
|
+
>;
|
package/tests/src/synchronize.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { it, expect, describe } from "vitest";
|
|
1
|
+
import { it, expect, describe, assert } from "vitest";
|
|
2
2
|
import type { GraffitiFactory, GraffitiSession } from "@graffiti-garden/api";
|
|
3
3
|
import { randomPutObject, randomString } from "./utils";
|
|
4
4
|
|
|
@@ -7,7 +7,7 @@ export const graffitiSynchronizeTests = (
|
|
|
7
7
|
useSession1: () => GraffitiSession,
|
|
8
8
|
useSession2: () => GraffitiSession,
|
|
9
9
|
) => {
|
|
10
|
-
describe("
|
|
10
|
+
describe("synchronizeDiscover", () => {
|
|
11
11
|
it("get", async () => {
|
|
12
12
|
const graffiti1 = useGraffiti();
|
|
13
13
|
const session = useSession1();
|
|
@@ -17,7 +17,7 @@ export const graffitiSynchronizeTests = (
|
|
|
17
17
|
const putted = await graffiti1.put(object, session);
|
|
18
18
|
|
|
19
19
|
const graffiti2 = useGraffiti();
|
|
20
|
-
const next = graffiti2.
|
|
20
|
+
const next = graffiti2.synchronizeDiscover(channels, {}).next();
|
|
21
21
|
const gotten = await graffiti2.get(putted, {}, session);
|
|
22
22
|
|
|
23
23
|
const result = (await next).value;
|
|
@@ -49,9 +49,9 @@ export const graffitiSynchronizeTests = (
|
|
|
49
49
|
);
|
|
50
50
|
|
|
51
51
|
// Start listening for changes...
|
|
52
|
-
const before = graffiti.
|
|
53
|
-
const after = graffiti.
|
|
54
|
-
const shared = graffiti.
|
|
52
|
+
const before = graffiti.synchronizeDiscover([beforeChannel], {}).next();
|
|
53
|
+
const after = graffiti.synchronizeDiscover([afterChannel], {}).next();
|
|
54
|
+
const shared = graffiti.synchronizeDiscover([sharedChannel], {}).next();
|
|
55
55
|
|
|
56
56
|
// Replace the object
|
|
57
57
|
const newValue = { goodbye: "world" };
|
|
@@ -115,9 +115,9 @@ export const graffitiSynchronizeTests = (
|
|
|
115
115
|
);
|
|
116
116
|
|
|
117
117
|
// Start listening for changes...
|
|
118
|
-
const before = graffiti.
|
|
119
|
-
const after = graffiti.
|
|
120
|
-
const shared = graffiti.
|
|
118
|
+
const before = graffiti.synchronizeDiscover([beforeChannel], {}).next();
|
|
119
|
+
const after = graffiti.synchronizeDiscover([afterChannel], {}).next();
|
|
120
|
+
const shared = graffiti.synchronizeDiscover([sharedChannel], {}).next();
|
|
121
121
|
|
|
122
122
|
await graffiti.patch(
|
|
123
123
|
{
|
|
@@ -193,7 +193,7 @@ export const graffitiSynchronizeTests = (
|
|
|
193
193
|
session,
|
|
194
194
|
);
|
|
195
195
|
|
|
196
|
-
const next = graffiti.
|
|
196
|
+
const next = graffiti.synchronizeDiscover(channels, {}).next();
|
|
197
197
|
|
|
198
198
|
graffiti.delete(putted, session);
|
|
199
199
|
|
|
@@ -216,9 +216,13 @@ export const graffitiSynchronizeTests = (
|
|
|
216
216
|
const allChannels = [randomString(), randomString(), randomString()];
|
|
217
217
|
const channels = allChannels.slice(1);
|
|
218
218
|
|
|
219
|
-
const creatorNext = graffiti
|
|
220
|
-
|
|
221
|
-
|
|
219
|
+
const creatorNext = graffiti
|
|
220
|
+
.synchronizeDiscover(channels, {}, session1)
|
|
221
|
+
.next();
|
|
222
|
+
const allowedNext = graffiti
|
|
223
|
+
.synchronizeDiscover(channels, {}, session2)
|
|
224
|
+
.next();
|
|
225
|
+
const noSession = graffiti.synchronizeDiscover(channels, {}).next();
|
|
222
226
|
|
|
223
227
|
const value = {
|
|
224
228
|
hello: "world",
|
|
@@ -256,4 +260,94 @@ export const graffitiSynchronizeTests = (
|
|
|
256
260
|
expect(allowedResult.value.channels).toEqual(channels);
|
|
257
261
|
});
|
|
258
262
|
});
|
|
263
|
+
|
|
264
|
+
describe("synchronizeGet", () => {
|
|
265
|
+
it("replace, delete", async () => {
|
|
266
|
+
const graffiti = useGraffiti();
|
|
267
|
+
const session = useSession1();
|
|
268
|
+
|
|
269
|
+
const object = randomPutObject();
|
|
270
|
+
const putted = await graffiti.put(object, session);
|
|
271
|
+
|
|
272
|
+
const iterator = graffiti.synchronizeGet(putted, {});
|
|
273
|
+
const next = iterator.next();
|
|
274
|
+
|
|
275
|
+
// Change the object
|
|
276
|
+
const newValue = { goodbye: "world" };
|
|
277
|
+
const putted2 = await graffiti.put(
|
|
278
|
+
{
|
|
279
|
+
...putted,
|
|
280
|
+
value: newValue,
|
|
281
|
+
},
|
|
282
|
+
session,
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
const result = (await next).value;
|
|
286
|
+
assert(result && !result.error);
|
|
287
|
+
|
|
288
|
+
expect(result.value.value).toEqual(newValue);
|
|
289
|
+
expect(result.value.actor).toEqual(session.actor);
|
|
290
|
+
expect(result.value.channels).toEqual([]);
|
|
291
|
+
expect(result.value.tombstone).toBe(false);
|
|
292
|
+
expect(result.value.lastModified).toEqual(putted2.lastModified);
|
|
293
|
+
expect(result.value.allowed).toBeUndefined();
|
|
294
|
+
|
|
295
|
+
// Delete the object
|
|
296
|
+
const deleted = await graffiti.delete(putted2, session);
|
|
297
|
+
const result2 = (await iterator.next()).value;
|
|
298
|
+
assert(result2 && !result2.error);
|
|
299
|
+
expect(result2.value.tombstone).toBe(true);
|
|
300
|
+
expect(result2.value.lastModified).toEqual(deleted.lastModified);
|
|
301
|
+
|
|
302
|
+
// Put something else
|
|
303
|
+
await graffiti.put(randomPutObject(), session);
|
|
304
|
+
await expect(
|
|
305
|
+
Promise.race([
|
|
306
|
+
iterator.next(),
|
|
307
|
+
new Promise((resolve, reject) => setTimeout(reject, 100, "Timeout")),
|
|
308
|
+
]),
|
|
309
|
+
).rejects.toThrow("Timeout");
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("not allowed", async () => {
|
|
313
|
+
const graffiti = useGraffiti();
|
|
314
|
+
const session1 = useSession1();
|
|
315
|
+
const session2 = useSession2();
|
|
316
|
+
|
|
317
|
+
const object = randomPutObject();
|
|
318
|
+
const putted = await graffiti.put(object, session1);
|
|
319
|
+
|
|
320
|
+
const iterator1 = graffiti.synchronizeGet(putted, {}, session1);
|
|
321
|
+
const iterator2 = graffiti.synchronizeGet(putted, {}, session2);
|
|
322
|
+
|
|
323
|
+
const next1 = iterator1.next();
|
|
324
|
+
const next2 = iterator2.next();
|
|
325
|
+
|
|
326
|
+
const newValue = { goodbye: "world" };
|
|
327
|
+
const putted2 = await graffiti.put(
|
|
328
|
+
{
|
|
329
|
+
...putted,
|
|
330
|
+
...object,
|
|
331
|
+
allowed: [],
|
|
332
|
+
value: newValue,
|
|
333
|
+
},
|
|
334
|
+
session1,
|
|
335
|
+
);
|
|
336
|
+
const result1 = (await next1).value;
|
|
337
|
+
const result2 = (await next2).value;
|
|
338
|
+
assert(result1 && !result1.error);
|
|
339
|
+
assert(result2 && !result2.error);
|
|
340
|
+
|
|
341
|
+
expect(result1.value.value).toEqual(newValue);
|
|
342
|
+
expect(result2.value.value).toEqual(object.value);
|
|
343
|
+
expect(result1.value.actor).toEqual(session1.actor);
|
|
344
|
+
expect(result2.value.actor).toEqual(session1.actor);
|
|
345
|
+
expect(result1.value.channels).toEqual(object.channels);
|
|
346
|
+
expect(result2.value.channels).toEqual([]);
|
|
347
|
+
expect(result1.value.tombstone).toBe(false);
|
|
348
|
+
expect(result2.value.tombstone).toBe(true);
|
|
349
|
+
expect(result1.value.lastModified).toEqual(putted2.lastModified);
|
|
350
|
+
expect(result2.value.lastModified).toEqual(putted2.lastModified);
|
|
351
|
+
});
|
|
352
|
+
});
|
|
259
353
|
};
|