@graffiti-garden/implementation-local 0.2.12 → 0.3.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/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Graffiti } from "@graffiti-garden/api";
2
+ import { GraffitiLocalSessionManager } from "./session-manager.js";
2
3
  import { type GraffitiLocalOptions } from "./database.js";
3
4
  export type { GraffitiLocalOptions };
4
5
  /**
@@ -11,18 +12,19 @@ export type { GraffitiLocalOptions };
11
12
  export declare class GraffitiLocal extends Graffiti {
12
13
  locationToUri: (location: import("@graffiti-garden/api").GraffitiLocation) => string;
13
14
  uriToLocation: (uri: string) => import("@graffiti-garden/api").GraffitiLocation;
14
- login: Graffiti["login"];
15
- logout: Graffiti["logout"];
16
- sessionEvents: Graffiti["sessionEvents"];
15
+ protected sessionManagerLocal: GraffitiLocalSessionManager;
16
+ login: (proposal?: {
17
+ actor?: string;
18
+ scope?: {};
19
+ }) => Promise<void>;
20
+ logout: (session: import("@graffiti-garden/api").GraffitiSession) => Promise<void>;
21
+ sessionEvents: EventTarget;
17
22
  put: Graffiti["put"];
18
23
  get: Graffiti["get"];
19
24
  patch: Graffiti["patch"];
20
25
  delete: Graffiti["delete"];
21
26
  discover: Graffiti["discover"];
22
27
  recoverOrphans: Graffiti["recoverOrphans"];
23
- synchronizeGet: Graffiti["synchronizeGet"];
24
- synchronizeDiscover: Graffiti["synchronizeDiscover"];
25
- synchronizeRecoverOrphans: Graffiti["synchronizeRecoverOrphans"];
26
28
  channelStats: Graffiti["channelStats"];
27
29
  constructor(options?: GraffitiLocalOptions);
28
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGhD,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,eAAe,CAAC;AAIvB,YAAY,EAAE,oBAAoB,EAAE,CAAC;AAErC;;;;;;GAMG;AACH,qBAAa,aAAc,SAAQ,QAAQ;IACzC,aAAa,wEAAiB;IAC9B,aAAa,mEAAiB;IAE9B,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IACzB,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC3B,aAAa,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;IACzC,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IACrB,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IACrB,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IACzB,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC3B,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC/B,cAAc,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAC3C,cAAc,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAC3C,mBAAmB,EAAE,QAAQ,CAAC,qBAAqB,CAAC,CAAC;IACrD,yBAAyB,EAAE,QAAQ,CAAC,2BAA2B,CAAC,CAAC;IACjE,YAAY,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;gBAE3B,OAAO,CAAC,EAAE,oBAAoB;CA+B3C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD,OAAO,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,eAAe,CAAC;AAGvB,YAAY,EAAE,oBAAoB,EAAE,CAAC;AAErC;;;;;;GAMG;AACH,qBAAa,aAAc,SAAQ,QAAQ;IACzC,aAAa,wEAAiB;IAC9B,aAAa,mEAAiB;IAE9B,SAAS,CAAC,mBAAmB,8BAAqC;IAClE,KAAK;aA6By+sB,CAAC;aAA6iB,CAAC;wBA7Bv9tB;IACtE,MAAM,6EAAkE;IACxE,aAAa,cAA0C;IAEvD,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IACrB,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IACrB,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IACzB,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC3B,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC/B,cAAc,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAC3C,YAAY,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;gBAE3B,OAAO,CAAC,EAAE,oBAAoB;CAgB3C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graffiti-garden/implementation-local",
3
- "version": "0.2.12",
3
+ "version": "0.3.0",
4
4
  "description": "A local implementation of the Graffiti API using PouchDB",
5
5
  "types": "./dist/index.d.ts",
6
6
  "module": "./dist/esm/index.js",
@@ -37,16 +37,6 @@
37
37
  "default": "./dist/cjs/session-manager.js"
38
38
  }
39
39
  },
40
- "./synchronize": {
41
- "import": {
42
- "types": "./dist/synchronize.d.ts",
43
- "default": "./dist/esm/synchronize.js"
44
- },
45
- "require": {
46
- "types": "./dist/synchronize.d.ts",
47
- "default": "./dist/cjs/synchronize.js"
48
- }
49
- },
50
40
  "./utilities": {
51
41
  "import": {
52
42
  "types": "./dist/utilities.d.ts",
@@ -89,7 +79,7 @@
89
79
  "vitest": "^2.1.8"
90
80
  },
91
81
  "dependencies": {
92
- "@graffiti-garden/api": "^0.2.11",
82
+ "@graffiti-garden/api": "^0.3.0",
93
83
  "@repeaterjs/repeater": "^3.0.6",
94
84
  "@types/pouchdb": "^6.4.2",
95
85
  "ajv": "^8.17.1",
package/src/index.ts CHANGED
@@ -5,7 +5,6 @@ import {
5
5
  GraffitiLocalDatabase,
6
6
  type GraffitiLocalOptions,
7
7
  } from "./database.js";
8
- import { GraffitiSynchronize } from "./synchronize.js";
9
8
  import { locationToUri, uriToLocation } from "./utilities.js";
10
9
 
11
10
  export type { GraffitiLocalOptions };
@@ -21,49 +20,33 @@ export class GraffitiLocal extends Graffiti {
21
20
  locationToUri = locationToUri;
22
21
  uriToLocation = uriToLocation;
23
22
 
24
- login: Graffiti["login"];
25
- logout: Graffiti["logout"];
26
- sessionEvents: Graffiti["sessionEvents"];
23
+ protected sessionManagerLocal = new GraffitiLocalSessionManager();
24
+ login = this.sessionManagerLocal.login.bind(this.sessionManagerLocal);
25
+ logout = this.sessionManagerLocal.logout.bind(this.sessionManagerLocal);
26
+ sessionEvents = this.sessionManagerLocal.sessionEvents;
27
+
27
28
  put: Graffiti["put"];
28
29
  get: Graffiti["get"];
29
30
  patch: Graffiti["patch"];
30
31
  delete: Graffiti["delete"];
31
32
  discover: Graffiti["discover"];
32
33
  recoverOrphans: Graffiti["recoverOrphans"];
33
- synchronizeGet: Graffiti["synchronizeGet"];
34
- synchronizeDiscover: Graffiti["synchronizeDiscover"];
35
- synchronizeRecoverOrphans: Graffiti["synchronizeRecoverOrphans"];
36
34
  channelStats: Graffiti["channelStats"];
37
35
 
38
36
  constructor(options?: GraffitiLocalOptions) {
39
37
  super();
40
38
 
41
- const sessionManagerLocal = new GraffitiLocalSessionManager();
42
- this.login = sessionManagerLocal.login.bind(sessionManagerLocal);
43
- this.logout = sessionManagerLocal.logout.bind(sessionManagerLocal);
44
- this.sessionEvents = sessionManagerLocal.sessionEvents;
45
-
46
39
  const ajv = new Ajv({ strict: false });
47
40
  const graffitiPouchDbBase = new GraffitiLocalDatabase(options, ajv);
48
- const graffitiSynchronize = new GraffitiSynchronize(
49
- graffitiPouchDbBase,
50
- ajv,
51
- );
52
41
 
53
- this.put = graffitiSynchronize.put.bind(graffitiSynchronize);
54
- this.get = graffitiSynchronize.get.bind(graffitiSynchronize);
55
- this.patch = graffitiSynchronize.patch.bind(graffitiSynchronize);
56
- this.delete = graffitiSynchronize.delete.bind(graffitiSynchronize);
57
- this.discover = graffitiSynchronize.discover.bind(graffitiSynchronize);
42
+ this.put = graffitiPouchDbBase.put.bind(graffitiPouchDbBase);
43
+ this.get = graffitiPouchDbBase.get.bind(graffitiPouchDbBase);
44
+ this.patch = graffitiPouchDbBase.patch.bind(graffitiPouchDbBase);
45
+ this.delete = graffitiPouchDbBase.delete.bind(graffitiPouchDbBase);
46
+ this.discover = graffitiPouchDbBase.discover.bind(graffitiPouchDbBase);
58
47
  this.recoverOrphans =
59
- graffitiSynchronize.recoverOrphans.bind(graffitiPouchDbBase);
48
+ graffitiPouchDbBase.recoverOrphans.bind(graffitiPouchDbBase);
60
49
  this.channelStats =
61
50
  graffitiPouchDbBase.channelStats.bind(graffitiPouchDbBase);
62
- this.synchronizeDiscover =
63
- graffitiSynchronize.synchronizeDiscover.bind(graffitiSynchronize);
64
- this.synchronizeGet =
65
- graffitiSynchronize.synchronizeGet.bind(graffitiSynchronize);
66
- this.synchronizeRecoverOrphans =
67
- graffitiSynchronize.synchronizeRecoverOrphans.bind(graffitiSynchronize);
68
51
  }
69
52
  }
package/src/tests.spec.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  graffitiLocationTests,
3
3
  graffitiCRUDTests,
4
- graffitiSynchronizeTests,
5
4
  graffitiDiscoverTests,
6
5
  graffitiOrphanTests,
7
6
  graffitiChannelStatsTests,
@@ -14,7 +13,6 @@ const useSession2 = () => ({ actor: "someoneelse" });
14
13
 
15
14
  graffitiLocationTests(useGraffiti);
16
15
  graffitiCRUDTests(useGraffiti, useSession1, useSession2);
17
- graffitiSynchronizeTests(useGraffiti, useSession1, useSession2);
18
16
  graffitiDiscoverTests(useGraffiti, useSession1, useSession2);
19
17
  graffitiOrphanTests(useGraffiti, useSession1, useSession2);
20
18
  graffitiChannelStatsTests(useGraffiti, useSession1, useSession2);
@@ -1,2 +0,0 @@
1
- "use strict";var O=Object.create;var h=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var z=Object.getPrototypeOf,g=Object.prototype.hasOwnProperty;var S=(r,e)=>{for(var t in e)h(r,t,{get:e[t],enumerable:!0})},d=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of w(e))!g.call(r,a)&&a!==t&&h(r,a,{get:()=>e[a],enumerable:!(i=v(e,a))||i.enumerable});return r};var k=(r,e,t)=>(t=r!=null?O(z(r)):{},d(e||!r||!r.__esModule?h(t,"default",{value:r,enumerable:!0}):t,r)),D=r=>d(h({},"__esModule",{value:!0}),r);var R={};S(R,{GraffitiSynchronize:()=>B});module.exports=D(R);var u=k(require("ajv-draft-04")),y=require("@repeaterjs/repeater"),m=require("fast-json-patch"),c=require("./utilities.js");class B{ajv;graffiti;callbacks=new Set;constructor(e,t){this.ajv=t??new u.default({strict:!1}),this.graffiti=e}async synchronizeDispatch(e,t,i=!1){for(const a of this.callbacks)a(e,t);i&&await new Promise(a=>setTimeout(a,0))}get=async(...e)=>{const t=await this.graffiti.get(...e);return this.synchronizeDispatch(t),t};put=async(...e)=>{const t=await this.graffiti.put(...e),i=e[0],a={...t,value:i.value,channels:i.channels,allowed:i.allowed,tombstone:!1};return await this.synchronizeDispatch(t,a,!0),t};patch=async(...e)=>{const t=await this.graffiti.patch(...e),i={...t};i.tombstone=!1;for(const a of["value","channels","allowed"])(0,c.applyGraffitiPatch)(m.applyPatch,a,e[0],i);return await this.synchronizeDispatch(t,i,!0),t};delete=async(...e)=>{const t=await this.graffiti.delete(...e);return await this.synchronizeDispatch(t,void 0,!0),t};objectStream(e){const t=this.synchronizeDispatch.bind(this);return async function*(){let a=await e.next();for(;!a.done;)a.value.error||t(a.value.value),yield a.value,a=await e.next();return a.value}()}discover=(...e)=>{const t=this.graffiti.discover(...e);return this.objectStream(t)};recoverOrphans=(...e)=>{const t=this.graffiti.recoverOrphans(...e);return this.objectStream(t)};synchronize(e,t,i,a){const s=(0,c.attemptAjvCompile)(this.ajv,i);return new y.Repeater(async(n,l)=>{const b=(j,G)=>{for(const f of[G,j])if(f&&e(f)&&(0,c.isActorAllowedGraffitiObject)(f,a)){const p={...f};if((0,c.maskGraffitiObject)(p,t,a),s(p)){n({value:p});break}}};this.callbacks.add(b),await l,this.callbacks.delete(b)})}synchronizeDiscover=(...e)=>{const[t,i,a]=e;function s(o){return o.channels.some(n=>t.includes(n))}return this.synchronize(s,t,i,a)};synchronizeGet=(...e)=>{const[t,i,a]=e;function s(o){const n=(0,c.locationToUri)(o),{uri:l}=(0,c.unpackLocationOrUri)(t);return n===l}return this.synchronize(s,[],i,a)};synchronizeRecoverOrphans=(...e)=>{const[t,i]=e;function a(s){return s.actor===i.actor&&s.channels.length===0}return this.synchronize(a,[],t,i)}}
2
- //# sourceMappingURL=synchronize.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/synchronize.ts"],
4
- "sourcesContent": ["import Ajv from \"ajv-draft-04\";\nimport type {\n Graffiti,\n GraffitiSession,\n JSONSchema4,\n} from \"@graffiti-garden/api\";\nimport type { GraffitiObjectBase } from \"@graffiti-garden/api\";\nimport { Repeater } from \"@repeaterjs/repeater\";\nimport { applyPatch } from \"fast-json-patch\";\nimport {\n applyGraffitiPatch,\n attemptAjvCompile,\n isActorAllowedGraffitiObject,\n locationToUri,\n maskGraffitiObject,\n unpackLocationOrUri,\n} from \"./utilities.js\";\n\ntype GraffitiDatabaseMethods = Pick<\n Graffiti,\n \"get\" | \"put\" | \"patch\" | \"delete\" | \"discover\" | \"recoverOrphans\"\n>;\n\ntype Callback = (\n oldObject: GraffitiObjectBase,\n newObject?: GraffitiObjectBase,\n) => void;\n\n/**\n * Wraps a partial implementation of the [Graffiti API](https://api.graffiti.garden/classes/Graffiti.html)\n * to provide the [`synchronize`](https://api.graffiti.garden/classes/Graffiti.html#synchronize) method.\n * The partial implementation must include the primary database methods:\n * `get`, `put`, `patch`, `delete`, and `discover`.\n */\nexport class GraffitiSynchronize\n implements\n Pick<\n Graffiti,\n | \"put\"\n | \"get\"\n | \"patch\"\n | \"delete\"\n | \"discover\"\n | \"recoverOrphans\"\n | \"synchronizeDiscover\"\n | \"synchronizeGet\"\n | \"synchronizeRecoverOrphans\"\n >\n{\n protected readonly ajv: Ajv;\n protected readonly graffiti: GraffitiDatabaseMethods;\n protected readonly callbacks = new Set<Callback>();\n\n // Pass in the ajv instance\n // and database methods to wrap\n constructor(graffiti: GraffitiDatabaseMethods, ajv?: Ajv) {\n this.ajv = ajv ?? new Ajv({ strict: false });\n this.graffiti = graffiti;\n }\n\n protected async synchronizeDispatch(\n oldObject: GraffitiObjectBase,\n newObject?: GraffitiObjectBase,\n macroTask = false,\n ) {\n for (const callback of this.callbacks) {\n callback(oldObject, newObject);\n }\n if (macroTask) {\n // Wait for one macro task cycle...\n // This way the yields, triggered by the callbacks,\n // will resolve first. Unfortunately, if we try to\n // use @repeaterjs's `await push` in the callback,\n // the promise won't resolve until the next\n // .next() call of the iterator, so if only\n // one .next() is called, this dispatch will hang.\n //\n // Only mutators (put, patch, delete) should bother\n // using this, so that they can ensure the application\n // state has been updated everywhere before returning,\n // for proper feedback.\n await new Promise((resolve) => setTimeout(resolve, 0));\n }\n }\n\n get: Graffiti[\"get\"] = async (...args) => {\n const object = await this.graffiti.get(...args);\n this.synchronizeDispatch(object);\n return object;\n };\n\n put: Graffiti[\"put\"] = async (...args) => {\n const oldObject = await this.graffiti.put(...args);\n const partialObject = args[0];\n const newObject: GraffitiObjectBase = {\n ...oldObject,\n value: partialObject.value,\n channels: partialObject.channels,\n allowed: partialObject.allowed,\n tombstone: false,\n };\n await this.synchronizeDispatch(oldObject, newObject, true);\n return oldObject;\n };\n\n patch: Graffiti[\"patch\"] = async (...args) => {\n const oldObject = await this.graffiti.patch(...args);\n const newObject: GraffitiObjectBase = { ...oldObject };\n newObject.tombstone = false;\n for (const prop of [\"value\", \"channels\", \"allowed\"] as const) {\n applyGraffitiPatch(applyPatch, prop, args[0], newObject);\n }\n await this.synchronizeDispatch(oldObject, newObject, true);\n return oldObject;\n };\n\n delete: Graffiti[\"delete\"] = async (...args) => {\n const oldObject = await this.graffiti.delete(...args);\n await this.synchronizeDispatch(oldObject, undefined, true);\n return oldObject;\n };\n\n protected objectStream<Schema extends JSONSchema4>(\n iterator: ReturnType<typeof Graffiti.prototype.discover<Schema>>,\n ) {\n const dispatch = this.synchronizeDispatch.bind(this);\n const wrapper = async function* () {\n let result = await iterator.next();\n while (!result.done) {\n if (!result.value.error) {\n dispatch(result.value.value);\n }\n yield result.value;\n result = await iterator.next();\n }\n return result.value;\n };\n return wrapper();\n }\n\n discover: Graffiti[\"discover\"] = (...args) => {\n const iterator = this.graffiti.discover(...args);\n return this.objectStream(iterator);\n };\n\n recoverOrphans: Graffiti[\"recoverOrphans\"] = (...args) => {\n const iterator = this.graffiti.recoverOrphans(...args);\n return this.objectStream(iterator);\n };\n\n protected synchronize<Schema extends JSONSchema4>(\n matchObject: (object: GraffitiObjectBase) => boolean,\n channels: string[],\n schema: Schema,\n session?: GraffitiSession | null,\n ) {\n const validate = attemptAjvCompile(this.ajv, schema);\n\n const repeater: ReturnType<\n typeof Graffiti.prototype.synchronizeDiscover<typeof schema>\n > = new Repeater(async (push, stop) => {\n const callback: Callback = (oldObjectRaw, newObjectRaw) => {\n for (const objectRaw of [newObjectRaw, oldObjectRaw]) {\n if (\n objectRaw &&\n matchObject(objectRaw) &&\n isActorAllowedGraffitiObject(objectRaw, session)\n ) {\n const object = { ...objectRaw };\n maskGraffitiObject(object, channels, session);\n if (validate(object)) {\n push({ value: object });\n break;\n }\n }\n }\n };\n\n this.callbacks.add(callback);\n await stop;\n this.callbacks.delete(callback);\n });\n\n return repeater;\n }\n\n synchronizeDiscover: Graffiti[\"synchronizeDiscover\"] = (...args) => {\n const [channels, schema, session] = args;\n function matchObject(object: GraffitiObjectBase) {\n return object.channels.some((channel) => channels.includes(channel));\n }\n return this.synchronize(matchObject, channels, schema, session);\n };\n\n synchronizeGet: Graffiti[\"synchronizeGet\"] = (...args) => {\n const [locationOrUri, schema, session] = args;\n function matchObject(object: GraffitiObjectBase) {\n const objectUri = locationToUri(object);\n const { uri } = unpackLocationOrUri(locationOrUri);\n return objectUri === uri;\n }\n return this.synchronize(matchObject, [], schema, session);\n };\n\n synchronizeRecoverOrphans: Graffiti[\"synchronizeRecoverOrphans\"] = (\n ...args\n ) => {\n const [schema, session] = args;\n function matchObject(object: GraffitiObjectBase) {\n return object.actor === session.actor && object.channels.length === 0;\n }\n return this.synchronize(matchObject, [], schema, session);\n };\n}\n"],
5
- "mappings": "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",
6
- "names": ["synchronize_exports", "__export", "GraffitiSynchronize", "__toCommonJS", "import_ajv_draft_04", "import_repeater", "import_fast_json_patch", "import_utilities", "graffiti", "ajv", "Ajv", "oldObject", "newObject", "macroTask", "callback", "resolve", "args", "object", "partialObject", "prop", "iterator", "dispatch", "result", "matchObject", "channels", "schema", "session", "validate", "push", "stop", "oldObjectRaw", "newObjectRaw", "objectRaw", "channel", "locationOrUri", "objectUri", "uri"]
7
- }
@@ -1,2 +0,0 @@
1
- import b from"ajv-draft-04";import{Repeater as d}from"@repeaterjs/repeater";import{applyPatch as u}from"fast-json-patch";import{applyGraffitiPatch as y,attemptAjvCompile as m,isActorAllowedGraffitiObject as j,locationToUri as G,maskGraffitiObject as O,unpackLocationOrUri as v}from"./utilities.js";class D{ajv;graffiti;callbacks=new Set;constructor(e,t){this.ajv=t??new b({strict:!1}),this.graffiti=e}async synchronizeDispatch(e,t,i=!1){for(const a of this.callbacks)a(e,t);i&&await new Promise(a=>setTimeout(a,0))}get=async(...e)=>{const t=await this.graffiti.get(...e);return this.synchronizeDispatch(t),t};put=async(...e)=>{const t=await this.graffiti.put(...e),i=e[0],a={...t,value:i.value,channels:i.channels,allowed:i.allowed,tombstone:!1};return await this.synchronizeDispatch(t,a,!0),t};patch=async(...e)=>{const t=await this.graffiti.patch(...e),i={...t};i.tombstone=!1;for(const a of["value","channels","allowed"])y(u,a,e[0],i);return await this.synchronizeDispatch(t,i,!0),t};delete=async(...e)=>{const t=await this.graffiti.delete(...e);return await this.synchronizeDispatch(t,void 0,!0),t};objectStream(e){const t=this.synchronizeDispatch.bind(this);return async function*(){let a=await e.next();for(;!a.done;)a.value.error||t(a.value.value),yield a.value,a=await e.next();return a.value}()}discover=(...e)=>{const t=this.graffiti.discover(...e);return this.objectStream(t)};recoverOrphans=(...e)=>{const t=this.graffiti.recoverOrphans(...e);return this.objectStream(t)};synchronize(e,t,i,a){const r=m(this.ajv,i);return new d(async(c,o)=>{const h=(l,p)=>{for(const n of[p,l])if(n&&e(n)&&j(n,a)){const f={...n};if(O(f,t,a),r(f)){c({value:f});break}}};this.callbacks.add(h),await o,this.callbacks.delete(h)})}synchronizeDiscover=(...e)=>{const[t,i,a]=e;function r(s){return s.channels.some(c=>t.includes(c))}return this.synchronize(r,t,i,a)};synchronizeGet=(...e)=>{const[t,i,a]=e;function r(s){const c=G(s),{uri:o}=v(t);return c===o}return this.synchronize(r,[],i,a)};synchronizeRecoverOrphans=(...e)=>{const[t,i]=e;function a(r){return r.actor===i.actor&&r.channels.length===0}return this.synchronize(a,[],t,i)}}export{D as GraffitiSynchronize};
2
- //# sourceMappingURL=synchronize.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/synchronize.ts"],
4
- "sourcesContent": ["import Ajv from \"ajv-draft-04\";\nimport type {\n Graffiti,\n GraffitiSession,\n JSONSchema4,\n} from \"@graffiti-garden/api\";\nimport type { GraffitiObjectBase } from \"@graffiti-garden/api\";\nimport { Repeater } from \"@repeaterjs/repeater\";\nimport { applyPatch } from \"fast-json-patch\";\nimport {\n applyGraffitiPatch,\n attemptAjvCompile,\n isActorAllowedGraffitiObject,\n locationToUri,\n maskGraffitiObject,\n unpackLocationOrUri,\n} from \"./utilities.js\";\n\ntype GraffitiDatabaseMethods = Pick<\n Graffiti,\n \"get\" | \"put\" | \"patch\" | \"delete\" | \"discover\" | \"recoverOrphans\"\n>;\n\ntype Callback = (\n oldObject: GraffitiObjectBase,\n newObject?: GraffitiObjectBase,\n) => void;\n\n/**\n * Wraps a partial implementation of the [Graffiti API](https://api.graffiti.garden/classes/Graffiti.html)\n * to provide the [`synchronize`](https://api.graffiti.garden/classes/Graffiti.html#synchronize) method.\n * The partial implementation must include the primary database methods:\n * `get`, `put`, `patch`, `delete`, and `discover`.\n */\nexport class GraffitiSynchronize\n implements\n Pick<\n Graffiti,\n | \"put\"\n | \"get\"\n | \"patch\"\n | \"delete\"\n | \"discover\"\n | \"recoverOrphans\"\n | \"synchronizeDiscover\"\n | \"synchronizeGet\"\n | \"synchronizeRecoverOrphans\"\n >\n{\n protected readonly ajv: Ajv;\n protected readonly graffiti: GraffitiDatabaseMethods;\n protected readonly callbacks = new Set<Callback>();\n\n // Pass in the ajv instance\n // and database methods to wrap\n constructor(graffiti: GraffitiDatabaseMethods, ajv?: Ajv) {\n this.ajv = ajv ?? new Ajv({ strict: false });\n this.graffiti = graffiti;\n }\n\n protected async synchronizeDispatch(\n oldObject: GraffitiObjectBase,\n newObject?: GraffitiObjectBase,\n macroTask = false,\n ) {\n for (const callback of this.callbacks) {\n callback(oldObject, newObject);\n }\n if (macroTask) {\n // Wait for one macro task cycle...\n // This way the yields, triggered by the callbacks,\n // will resolve first. Unfortunately, if we try to\n // use @repeaterjs's `await push` in the callback,\n // the promise won't resolve until the next\n // .next() call of the iterator, so if only\n // one .next() is called, this dispatch will hang.\n //\n // Only mutators (put, patch, delete) should bother\n // using this, so that they can ensure the application\n // state has been updated everywhere before returning,\n // for proper feedback.\n await new Promise((resolve) => setTimeout(resolve, 0));\n }\n }\n\n get: Graffiti[\"get\"] = async (...args) => {\n const object = await this.graffiti.get(...args);\n this.synchronizeDispatch(object);\n return object;\n };\n\n put: Graffiti[\"put\"] = async (...args) => {\n const oldObject = await this.graffiti.put(...args);\n const partialObject = args[0];\n const newObject: GraffitiObjectBase = {\n ...oldObject,\n value: partialObject.value,\n channels: partialObject.channels,\n allowed: partialObject.allowed,\n tombstone: false,\n };\n await this.synchronizeDispatch(oldObject, newObject, true);\n return oldObject;\n };\n\n patch: Graffiti[\"patch\"] = async (...args) => {\n const oldObject = await this.graffiti.patch(...args);\n const newObject: GraffitiObjectBase = { ...oldObject };\n newObject.tombstone = false;\n for (const prop of [\"value\", \"channels\", \"allowed\"] as const) {\n applyGraffitiPatch(applyPatch, prop, args[0], newObject);\n }\n await this.synchronizeDispatch(oldObject, newObject, true);\n return oldObject;\n };\n\n delete: Graffiti[\"delete\"] = async (...args) => {\n const oldObject = await this.graffiti.delete(...args);\n await this.synchronizeDispatch(oldObject, undefined, true);\n return oldObject;\n };\n\n protected objectStream<Schema extends JSONSchema4>(\n iterator: ReturnType<typeof Graffiti.prototype.discover<Schema>>,\n ) {\n const dispatch = this.synchronizeDispatch.bind(this);\n const wrapper = async function* () {\n let result = await iterator.next();\n while (!result.done) {\n if (!result.value.error) {\n dispatch(result.value.value);\n }\n yield result.value;\n result = await iterator.next();\n }\n return result.value;\n };\n return wrapper();\n }\n\n discover: Graffiti[\"discover\"] = (...args) => {\n const iterator = this.graffiti.discover(...args);\n return this.objectStream(iterator);\n };\n\n recoverOrphans: Graffiti[\"recoverOrphans\"] = (...args) => {\n const iterator = this.graffiti.recoverOrphans(...args);\n return this.objectStream(iterator);\n };\n\n protected synchronize<Schema extends JSONSchema4>(\n matchObject: (object: GraffitiObjectBase) => boolean,\n channels: string[],\n schema: Schema,\n session?: GraffitiSession | null,\n ) {\n const validate = attemptAjvCompile(this.ajv, schema);\n\n const repeater: ReturnType<\n typeof Graffiti.prototype.synchronizeDiscover<typeof schema>\n > = new Repeater(async (push, stop) => {\n const callback: Callback = (oldObjectRaw, newObjectRaw) => {\n for (const objectRaw of [newObjectRaw, oldObjectRaw]) {\n if (\n objectRaw &&\n matchObject(objectRaw) &&\n isActorAllowedGraffitiObject(objectRaw, session)\n ) {\n const object = { ...objectRaw };\n maskGraffitiObject(object, channels, session);\n if (validate(object)) {\n push({ value: object });\n break;\n }\n }\n }\n };\n\n this.callbacks.add(callback);\n await stop;\n this.callbacks.delete(callback);\n });\n\n return repeater;\n }\n\n synchronizeDiscover: Graffiti[\"synchronizeDiscover\"] = (...args) => {\n const [channels, schema, session] = args;\n function matchObject(object: GraffitiObjectBase) {\n return object.channels.some((channel) => channels.includes(channel));\n }\n return this.synchronize(matchObject, channels, schema, session);\n };\n\n synchronizeGet: Graffiti[\"synchronizeGet\"] = (...args) => {\n const [locationOrUri, schema, session] = args;\n function matchObject(object: GraffitiObjectBase) {\n const objectUri = locationToUri(object);\n const { uri } = unpackLocationOrUri(locationOrUri);\n return objectUri === uri;\n }\n return this.synchronize(matchObject, [], schema, session);\n };\n\n synchronizeRecoverOrphans: Graffiti[\"synchronizeRecoverOrphans\"] = (\n ...args\n ) => {\n const [schema, session] = args;\n function matchObject(object: GraffitiObjectBase) {\n return object.actor === session.actor && object.channels.length === 0;\n }\n return this.synchronize(matchObject, [], schema, session);\n };\n}\n"],
5
- "mappings": "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",
6
- "names": ["Ajv", "Repeater", "applyPatch", "applyGraffitiPatch", "attemptAjvCompile", "isActorAllowedGraffitiObject", "locationToUri", "maskGraffitiObject", "unpackLocationOrUri", "GraffitiSynchronize", "graffiti", "ajv", "oldObject", "newObject", "macroTask", "callback", "resolve", "args", "object", "partialObject", "prop", "iterator", "dispatch", "result", "matchObject", "channels", "schema", "session", "validate", "push", "stop", "oldObjectRaw", "newObjectRaw", "objectRaw", "channel", "locationOrUri", "objectUri", "uri"]
7
- }
@@ -1,39 +0,0 @@
1
- import Ajv from "ajv-draft-04";
2
- import type { Graffiti, GraffitiSession, JSONSchema4 } from "@graffiti-garden/api";
3
- import type { GraffitiObjectBase } from "@graffiti-garden/api";
4
- type GraffitiDatabaseMethods = Pick<Graffiti, "get" | "put" | "patch" | "delete" | "discover" | "recoverOrphans">;
5
- type Callback = (oldObject: GraffitiObjectBase, newObject?: GraffitiObjectBase) => void;
6
- /**
7
- * Wraps a partial implementation of the [Graffiti API](https://api.graffiti.garden/classes/Graffiti.html)
8
- * to provide the [`synchronize`](https://api.graffiti.garden/classes/Graffiti.html#synchronize) method.
9
- * The partial implementation must include the primary database methods:
10
- * `get`, `put`, `patch`, `delete`, and `discover`.
11
- */
12
- export declare class GraffitiSynchronize implements Pick<Graffiti, "put" | "get" | "patch" | "delete" | "discover" | "recoverOrphans" | "synchronizeDiscover" | "synchronizeGet" | "synchronizeRecoverOrphans"> {
13
- protected readonly ajv: Ajv;
14
- protected readonly graffiti: GraffitiDatabaseMethods;
15
- protected readonly callbacks: Set<Callback>;
16
- constructor(graffiti: GraffitiDatabaseMethods, ajv?: Ajv);
17
- protected synchronizeDispatch(oldObject: GraffitiObjectBase, newObject?: GraffitiObjectBase, macroTask?: boolean): Promise<void>;
18
- get: Graffiti["get"];
19
- put: Graffiti["put"];
20
- patch: Graffiti["patch"];
21
- delete: Graffiti["delete"];
22
- protected objectStream<Schema extends JSONSchema4>(iterator: ReturnType<typeof Graffiti.prototype.discover<Schema>>): AsyncGenerator<{
23
- error: Error;
24
- source: string;
25
- } | {
26
- error?: undefined;
27
- value: import("@graffiti-garden/api").GraffitiObject<Schema>;
28
- }, {
29
- tombstoneRetention: number;
30
- }, unknown>;
31
- discover: Graffiti["discover"];
32
- recoverOrphans: Graffiti["recoverOrphans"];
33
- protected synchronize<Schema extends JSONSchema4>(matchObject: (object: GraffitiObjectBase) => boolean, channels: string[], schema: Schema, session?: GraffitiSession | null): import("@graffiti-garden/api").GraffitiStream<import("@graffiti-garden/api").GraffitiObject<Schema>>;
34
- synchronizeDiscover: Graffiti["synchronizeDiscover"];
35
- synchronizeGet: Graffiti["synchronizeGet"];
36
- synchronizeRecoverOrphans: Graffiti["synchronizeRecoverOrphans"];
37
- }
38
- export {};
39
- //# sourceMappingURL=synchronize.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"synchronize.d.ts","sourceRoot":"","sources":["../src/synchronize.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,cAAc,CAAC;AAC/B,OAAO,KAAK,EACV,QAAQ,EACR,eAAe,EACf,WAAW,EACZ,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAY/D,KAAK,uBAAuB,GAAG,IAAI,CACjC,QAAQ,EACR,KAAK,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,gBAAgB,CACnE,CAAC;AAEF,KAAK,QAAQ,GAAG,CACd,SAAS,EAAE,kBAAkB,EAC7B,SAAS,CAAC,EAAE,kBAAkB,KAC3B,IAAI,CAAC;AAEV;;;;;GAKG;AACH,qBAAa,mBACX,YACE,IAAI,CACF,QAAQ,EACN,KAAK,GACL,KAAK,GACL,OAAO,GACP,QAAQ,GACR,UAAU,GACV,gBAAgB,GAChB,qBAAqB,GACrB,gBAAgB,GAChB,2BAA2B,CAC9B;IAEH,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;IAC5B,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,uBAAuB,CAAC;IACrD,SAAS,CAAC,QAAQ,CAAC,SAAS,gBAAuB;gBAIvC,QAAQ,EAAE,uBAAuB,EAAE,GAAG,CAAC,EAAE,GAAG;cAKxC,mBAAmB,CACjC,SAAS,EAAE,kBAAkB,EAC7B,SAAS,CAAC,EAAE,kBAAkB,EAC9B,SAAS,UAAQ;IAsBnB,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CAIlB;IAEF,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CAYlB;IAEF,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,CAStB;IAEF,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAIxB;IAEF,SAAS,CAAC,YAAY,CAAC,MAAM,SAAS,WAAW,EAC/C,QAAQ,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;;;;;;;;;IAiBlE,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAG5B;IAEF,cAAc,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAGxC;IAEF,SAAS,CAAC,WAAW,CAAC,MAAM,SAAS,WAAW,EAC9C,WAAW,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,OAAO,EACpD,QAAQ,EAAE,MAAM,EAAE,EAClB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI;IAgClC,mBAAmB,EAAE,QAAQ,CAAC,qBAAqB,CAAC,CAMlD;IAEF,cAAc,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAQxC;IAEF,yBAAyB,EAAE,QAAQ,CAAC,2BAA2B,CAAC,CAQ9D;CACH"}
@@ -1,214 +0,0 @@
1
- import Ajv from "ajv-draft-04";
2
- import type {
3
- Graffiti,
4
- GraffitiSession,
5
- JSONSchema4,
6
- } from "@graffiti-garden/api";
7
- import type { GraffitiObjectBase } from "@graffiti-garden/api";
8
- import { Repeater } from "@repeaterjs/repeater";
9
- import { applyPatch } from "fast-json-patch";
10
- import {
11
- applyGraffitiPatch,
12
- attemptAjvCompile,
13
- isActorAllowedGraffitiObject,
14
- locationToUri,
15
- maskGraffitiObject,
16
- unpackLocationOrUri,
17
- } from "./utilities.js";
18
-
19
- type GraffitiDatabaseMethods = Pick<
20
- Graffiti,
21
- "get" | "put" | "patch" | "delete" | "discover" | "recoverOrphans"
22
- >;
23
-
24
- type Callback = (
25
- oldObject: GraffitiObjectBase,
26
- newObject?: GraffitiObjectBase,
27
- ) => void;
28
-
29
- /**
30
- * Wraps a partial implementation of the [Graffiti API](https://api.graffiti.garden/classes/Graffiti.html)
31
- * to provide the [`synchronize`](https://api.graffiti.garden/classes/Graffiti.html#synchronize) method.
32
- * The partial implementation must include the primary database methods:
33
- * `get`, `put`, `patch`, `delete`, and `discover`.
34
- */
35
- export class GraffitiSynchronize
36
- implements
37
- Pick<
38
- Graffiti,
39
- | "put"
40
- | "get"
41
- | "patch"
42
- | "delete"
43
- | "discover"
44
- | "recoverOrphans"
45
- | "synchronizeDiscover"
46
- | "synchronizeGet"
47
- | "synchronizeRecoverOrphans"
48
- >
49
- {
50
- protected readonly ajv: Ajv;
51
- protected readonly graffiti: GraffitiDatabaseMethods;
52
- protected readonly callbacks = new Set<Callback>();
53
-
54
- // Pass in the ajv instance
55
- // and database methods to wrap
56
- constructor(graffiti: GraffitiDatabaseMethods, ajv?: Ajv) {
57
- this.ajv = ajv ?? new Ajv({ strict: false });
58
- this.graffiti = graffiti;
59
- }
60
-
61
- protected async synchronizeDispatch(
62
- oldObject: GraffitiObjectBase,
63
- newObject?: GraffitiObjectBase,
64
- macroTask = false,
65
- ) {
66
- for (const callback of this.callbacks) {
67
- callback(oldObject, newObject);
68
- }
69
- if (macroTask) {
70
- // Wait for one macro task cycle...
71
- // This way the yields, triggered by the callbacks,
72
- // will resolve first. Unfortunately, if we try to
73
- // use @repeaterjs's `await push` in the callback,
74
- // the promise won't resolve until the next
75
- // .next() call of the iterator, so if only
76
- // one .next() is called, this dispatch will hang.
77
- //
78
- // Only mutators (put, patch, delete) should bother
79
- // using this, so that they can ensure the application
80
- // state has been updated everywhere before returning,
81
- // for proper feedback.
82
- await new Promise((resolve) => setTimeout(resolve, 0));
83
- }
84
- }
85
-
86
- get: Graffiti["get"] = async (...args) => {
87
- const object = await this.graffiti.get(...args);
88
- this.synchronizeDispatch(object);
89
- return object;
90
- };
91
-
92
- put: Graffiti["put"] = async (...args) => {
93
- const oldObject = await this.graffiti.put(...args);
94
- const partialObject = args[0];
95
- const newObject: GraffitiObjectBase = {
96
- ...oldObject,
97
- value: partialObject.value,
98
- channels: partialObject.channels,
99
- allowed: partialObject.allowed,
100
- tombstone: false,
101
- };
102
- await this.synchronizeDispatch(oldObject, newObject, true);
103
- return oldObject;
104
- };
105
-
106
- patch: Graffiti["patch"] = async (...args) => {
107
- const oldObject = await this.graffiti.patch(...args);
108
- const newObject: GraffitiObjectBase = { ...oldObject };
109
- newObject.tombstone = false;
110
- for (const prop of ["value", "channels", "allowed"] as const) {
111
- applyGraffitiPatch(applyPatch, prop, args[0], newObject);
112
- }
113
- await this.synchronizeDispatch(oldObject, newObject, true);
114
- return oldObject;
115
- };
116
-
117
- delete: Graffiti["delete"] = async (...args) => {
118
- const oldObject = await this.graffiti.delete(...args);
119
- await this.synchronizeDispatch(oldObject, undefined, true);
120
- return oldObject;
121
- };
122
-
123
- protected objectStream<Schema extends JSONSchema4>(
124
- iterator: ReturnType<typeof Graffiti.prototype.discover<Schema>>,
125
- ) {
126
- const dispatch = this.synchronizeDispatch.bind(this);
127
- const wrapper = async function* () {
128
- let result = await iterator.next();
129
- while (!result.done) {
130
- if (!result.value.error) {
131
- dispatch(result.value.value);
132
- }
133
- yield result.value;
134
- result = await iterator.next();
135
- }
136
- return result.value;
137
- };
138
- return wrapper();
139
- }
140
-
141
- discover: Graffiti["discover"] = (...args) => {
142
- const iterator = this.graffiti.discover(...args);
143
- return this.objectStream(iterator);
144
- };
145
-
146
- recoverOrphans: Graffiti["recoverOrphans"] = (...args) => {
147
- const iterator = this.graffiti.recoverOrphans(...args);
148
- return this.objectStream(iterator);
149
- };
150
-
151
- protected synchronize<Schema extends JSONSchema4>(
152
- matchObject: (object: GraffitiObjectBase) => boolean,
153
- channels: string[],
154
- schema: Schema,
155
- session?: GraffitiSession | null,
156
- ) {
157
- const validate = attemptAjvCompile(this.ajv, schema);
158
-
159
- const repeater: ReturnType<
160
- typeof Graffiti.prototype.synchronizeDiscover<typeof schema>
161
- > = new Repeater(async (push, stop) => {
162
- const callback: Callback = (oldObjectRaw, newObjectRaw) => {
163
- for (const objectRaw of [newObjectRaw, oldObjectRaw]) {
164
- if (
165
- objectRaw &&
166
- matchObject(objectRaw) &&
167
- isActorAllowedGraffitiObject(objectRaw, session)
168
- ) {
169
- const object = { ...objectRaw };
170
- maskGraffitiObject(object, channels, session);
171
- if (validate(object)) {
172
- push({ value: object });
173
- break;
174
- }
175
- }
176
- }
177
- };
178
-
179
- this.callbacks.add(callback);
180
- await stop;
181
- this.callbacks.delete(callback);
182
- });
183
-
184
- return repeater;
185
- }
186
-
187
- synchronizeDiscover: Graffiti["synchronizeDiscover"] = (...args) => {
188
- const [channels, schema, session] = args;
189
- function matchObject(object: GraffitiObjectBase) {
190
- return object.channels.some((channel) => channels.includes(channel));
191
- }
192
- return this.synchronize(matchObject, channels, schema, session);
193
- };
194
-
195
- synchronizeGet: Graffiti["synchronizeGet"] = (...args) => {
196
- const [locationOrUri, schema, session] = args;
197
- function matchObject(object: GraffitiObjectBase) {
198
- const objectUri = locationToUri(object);
199
- const { uri } = unpackLocationOrUri(locationOrUri);
200
- return objectUri === uri;
201
- }
202
- return this.synchronize(matchObject, [], schema, session);
203
- };
204
-
205
- synchronizeRecoverOrphans: Graffiti["synchronizeRecoverOrphans"] = (
206
- ...args
207
- ) => {
208
- const [schema, session] = args;
209
- function matchObject(object: GraffitiObjectBase) {
210
- return object.actor === session.actor && object.channels.length === 0;
211
- }
212
- return this.synchronize(matchObject, [], schema, session);
213
- };
214
- }