@graffiti-garden/implementation-local 0.2.7 → 0.2.8

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.
@@ -1,2 +1,2 @@
1
- "use strict";var B=Object.create;var v=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var P=Object.getPrototypeOf,k=Object.prototype.hasOwnProperty;var C=(l,i)=>{for(var e in i)v(l,e,{get:i[e],enumerable:!0})},S=(l,i,e,a)=>{if(i&&typeof i=="object"||typeof i=="function")for(let t of _(i))!k.call(l,t)&&t!==e&&v(l,t,{get:()=>i[t],enumerable:!(a=R(i,t))||a.enumerable});return l};var M=(l,i,e)=>(e=l!=null?B(P(l)):{},S(i||!l||!l.__esModule?v(e,"default",{value:l,enumerable:!0}):e,l)),I=l=>S(v({},"__esModule",{value:!0}),l);var E={};C(E,{GraffitiLocalDatabase:()=>U});module.exports=I(E);var u=require("@graffiti-garden/api"),x=M(require("pouchdb"),1),c=require("./utilities"),g=require("@repeaterjs/repeater"),D=M(require("ajv-draft-04"),1),L=require("fast-json-patch");class U{db;source="local";tombstoneRetention=864e5;ajv;constructor(i,e){this.ajv=e??new D.default({strict:!1}),this.source=i?.sourceName??this.source,this.tombstoneRetention=i?.tombstoneRetention??this.tombstoneRetention;const a={name:"graffitiDb",...i?.pouchDBOptions};this.db=new x.default(a.name,a),this.db.put({_id:"_design/indexes",views:{objectsPerChannelAndLastModified:{map:function(t){const o=t.lastModified.toString().padStart(15,"0");t.channels.forEach(function(s){const n=encodeURIComponent(s)+"/"+o;emit(n)})}.toString()},orphansPerActorAndLastModified:{map:function(t){if(t.channels.length===0){const o=t.lastModified.toString().padStart(15,"0"),s=encodeURIComponent(t.actor)+"/"+o;emit(s)}}.toString()},channelStatsPerActor:{map:function(t){t.tombstone||t.channels.forEach(function(o){const s=encodeURIComponent(t.actor)+"/"+encodeURIComponent(o);emit(s,t.lastModified)})}.toString(),reduce:"_stats"}}}).catch(t=>{if(!(t&&typeof t=="object"&&"name"in t&&t.name==="conflict"))throw t})}async queryByLocation(i){const e=(0,c.locationToUri)(i)+"/";return(await this.db.allDocs({startkey:e,endkey:e+"\uFFFF",include_docs:!0})).rows.map(o=>o.doc).reduce((o,s)=>(s&&o.push(s),o),[])}docId(i){return(0,c.locationToUri)(i)+"/"+(0,c.randomBase64)()}get=async(...i)=>{const[e,a,t]=i,{location:o}=(0,c.unpackLocationOrUri)(e),n=(await this.queryByLocation(o)).filter(f=>(0,c.isActorAllowedGraffitiObject)(f,t));if(!n.length)throw new u.GraffitiErrorNotFound;const d=n.reduce((f,h)=>(0,c.isObjectNewer)(f,h)?f:h),{_id:y,_rev:p,...r}=d;if((0,c.maskGraffitiObject)(r,[],t),!(0,c.attemptAjvCompile)(this.ajv,a)(r))throw new u.GraffitiErrorSchemaMismatch;return r};async deleteAtLocation(i,e=!1){const t=(await this.queryByLocation(i)).filter(r=>!r.tombstone);if(!t.length)return;const o=t.map(r=>r.lastModified).reduce((r,m)=>r>m?r:m),s=t.filter(r=>!e||r.lastModified<o),n=t.filter(r=>e&&r.lastModified===o);if(n.length){const r=n.map(f=>f._id).reduce((f,h)=>f>h?f:h),m=n.filter(f=>f._id!==r);s.push(...m)}const d=e?o:new Date().getTime(),y=await this.db.bulkDocs(s.map(r=>({...r,tombstone:!0,lastModified:d})));let p;for(const r of y)if("ok"in r){const{id:m}=r,f=s.find(h=>h._id===m);if(f){const{_id:h,_rev:G,_conflicts:j,_attachments:b,...w}=f;p={...w,tombstone:!0,lastModified:d};break}}return p}delete=async(...i)=>{const[e,a]=i,{location:t}=(0,c.unpackLocationOrUri)(e);if(t.actor!==a.actor)throw new u.GraffitiErrorForbidden;const o=await this.deleteAtLocation(t);if(!o)throw new u.GraffitiErrorNotFound;return o};put=async(...i)=>{const[e,a]=i;if(e.actor&&e.actor!==a.actor)throw new u.GraffitiErrorForbidden;if(e.source&&e.source!==this.source)throw new u.GraffitiErrorForbidden("Putting an object that does not match this source");const t={value:e.value,channels:e.channels,allowed:e.allowed,name:e.name??(0,c.randomBase64)(),source:this.source,actor:a.actor,tombstone:!1,lastModified:new Date().getTime()};await this.db.put({_id:this.docId(t),...t});const o=await this.deleteAtLocation(t,!0);return o||{...t,value:{},channels:[],allowed:void 0,tombstone:!0}};patch=async(...i)=>{const[e,a,t]=i,{location:o}=(0,c.unpackLocationOrUri)(a);if(o.actor!==t.actor)throw new u.GraffitiErrorForbidden;const s=await this.get(a,{},t);if(s.tombstone)throw new u.GraffitiErrorNotFound("The object you are trying to patch has been deleted");const n={...s};for(const d of["value","channels","allowed"])(0,c.applyGraffitiPatch)(L.applyPatch,d,e,n);if(typeof n.value!="object"||Array.isArray(n.value)||!n.value)throw new u.GraffitiErrorPatchError("value is no longer an object");if(!Array.isArray(n.channels)||!n.channels.every(d=>typeof d=="string"))throw new u.GraffitiErrorPatchError("channels are no longer an array of strings");if(n.allowed&&(!Array.isArray(n.allowed)||!n.allowed.every(d=>typeof d=="string")))throw new u.GraffitiErrorPatchError("allowed list is not an array of strings");return n.lastModified=new Date().getTime(),await this.db.put({...n,_id:this.docId(n)}),await this.deleteAtLocation(n,!0),{...s,tombstone:!0,lastModified:n.lastModified}};queryLastModifiedSuffixes(i){let e="",a="\uFFFF";const t=i.properties?.lastModified;if(t?.minimum){let o=Math.ceil(t.minimum);o===t.minimum&&t.exclusiveMinimum&&o++,e=o.toString().padStart(15,"0")}if(t?.maximum){let o=Math.floor(t.maximum);o===t.maximum&&t.exclusiveMaximum&&o--,a=o.toString().padStart(15,"0")}return{startKeySuffix:e,endKeySuffix:a}}discover=(...i)=>{const[e,a,t]=i,o=(0,c.attemptAjvCompile)(this.ajv,a),{startKeySuffix:s,endKeySuffix:n}=this.queryLastModifiedSuffixes(a);return new g.Repeater(async(y,p)=>{const r=new Set;for(const m of e){const f=encodeURIComponent(m)+"/",h=f+s,G=f+n,j=await this.db.query("indexes/objectsPerChannelAndLastModified",{startkey:h,endkey:G,include_docs:!0});for(const b of j.rows){const w=b.doc;if(!w)continue;const{_id:A,_rev:T,...O}=w;r.has(A)||(r.add(A),(0,c.isActorAllowedGraffitiObject)(w,t)&&((0,c.maskGraffitiObject)(O,e,t),o(O)&&await y({value:O})))}}return p(),{tombstoneRetention:this.tombstoneRetention}})};recoverOrphans=(i,e)=>{const a=(0,c.attemptAjvCompile)(this.ajv,i),{startKeySuffix:t,endKeySuffix:o}=this.queryLastModifiedSuffixes(i),s=encodeURIComponent(e.actor)+"/",n=s+t,d=s+o;return new g.Repeater(async(p,r)=>{const m=await this.db.query("indexes/orphansPerActorAndLastModified",{startkey:n,endkey:d,include_docs:!0});for(const f of m.rows){const h=f.doc;if(!h)continue;const{_id:G,_rev:j,...b}=h;a(b)&&await p({value:b})}return r(),{tombstoneRetention:this.tombstoneRetention}})};channelStats=i=>new g.Repeater(async(a,t)=>{const o=encodeURIComponent(i.actor)+"/",s=await this.db.query("indexes/channelStatsPerActor",{startkey:o,endkey:o+"\uFFFF",reduce:!0,group:!0});for(const n of s.rows){const d=n.key.split("/")[1];if(typeof d!="string")continue;const{count:y,max:p}=n.value;typeof y!="number"||typeof p!="number"||await a({value:{channel:decodeURIComponent(d),count:y,lastModified:p}})}t()})}
1
+ "use strict";var B=Object.create;var v=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var P=Object.getPrototypeOf,k=Object.prototype.hasOwnProperty;var C=(l,i)=>{for(var e in i)v(l,e,{get:i[e],enumerable:!0})},S=(l,i,e,a)=>{if(i&&typeof i=="object"||typeof i=="function")for(let t of _(i))!k.call(l,t)&&t!==e&&v(l,t,{get:()=>i[t],enumerable:!(a=R(i,t))||a.enumerable});return l};var M=(l,i,e)=>(e=l!=null?B(P(l)):{},S(i||!l||!l.__esModule?v(e,"default",{value:l,enumerable:!0}):e,l)),I=l=>S(v({},"__esModule",{value:!0}),l);var E={};C(E,{GraffitiLocalDatabase:()=>U});module.exports=I(E);var u=require("@graffiti-garden/api"),x=M(require("pouchdb"),1),c=require("./utilities.js"),g=require("@repeaterjs/repeater"),D=M(require("ajv-draft-04"),1),L=require("fast-json-patch");class U{db;source="local";tombstoneRetention=864e5;ajv;constructor(i,e){this.ajv=e??new D.default({strict:!1}),this.source=i?.sourceName??this.source,this.tombstoneRetention=i?.tombstoneRetention??this.tombstoneRetention;const a={name:"graffitiDb",...i?.pouchDBOptions};this.db=new x.default(a.name,a),this.db.put({_id:"_design/indexes",views:{objectsPerChannelAndLastModified:{map:function(t){const o=t.lastModified.toString().padStart(15,"0");t.channels.forEach(function(s){const n=encodeURIComponent(s)+"/"+o;emit(n)})}.toString()},orphansPerActorAndLastModified:{map:function(t){if(t.channels.length===0){const o=t.lastModified.toString().padStart(15,"0"),s=encodeURIComponent(t.actor)+"/"+o;emit(s)}}.toString()},channelStatsPerActor:{map:function(t){t.tombstone||t.channels.forEach(function(o){const s=encodeURIComponent(t.actor)+"/"+encodeURIComponent(o);emit(s,t.lastModified)})}.toString(),reduce:"_stats"}}}).catch(t=>{if(!(t&&typeof t=="object"&&"name"in t&&t.name==="conflict"))throw t})}async queryByLocation(i){const e=(0,c.locationToUri)(i)+"/";return(await this.db.allDocs({startkey:e,endkey:e+"\uFFFF",include_docs:!0})).rows.map(o=>o.doc).reduce((o,s)=>(s&&o.push(s),o),[])}docId(i){return(0,c.locationToUri)(i)+"/"+(0,c.randomBase64)()}get=async(...i)=>{const[e,a,t]=i,{location:o}=(0,c.unpackLocationOrUri)(e),n=(await this.queryByLocation(o)).filter(f=>(0,c.isActorAllowedGraffitiObject)(f,t));if(!n.length)throw new u.GraffitiErrorNotFound;const d=n.reduce((f,h)=>(0,c.isObjectNewer)(f,h)?f:h),{_id:y,_rev:p,...r}=d;if((0,c.maskGraffitiObject)(r,[],t),!(0,c.attemptAjvCompile)(this.ajv,a)(r))throw new u.GraffitiErrorSchemaMismatch;return r};async deleteAtLocation(i,e=!1){const t=(await this.queryByLocation(i)).filter(r=>!r.tombstone);if(!t.length)return;const o=t.map(r=>r.lastModified).reduce((r,m)=>r>m?r:m),s=t.filter(r=>!e||r.lastModified<o),n=t.filter(r=>e&&r.lastModified===o);if(n.length){const r=n.map(f=>f._id).reduce((f,h)=>f>h?f:h),m=n.filter(f=>f._id!==r);s.push(...m)}const d=e?o:new Date().getTime(),y=await this.db.bulkDocs(s.map(r=>({...r,tombstone:!0,lastModified:d})));let p;for(const r of y)if("ok"in r){const{id:m}=r,f=s.find(h=>h._id===m);if(f){const{_id:h,_rev:G,_conflicts:j,_attachments:b,...w}=f;p={...w,tombstone:!0,lastModified:d};break}}return p}delete=async(...i)=>{const[e,a]=i,{location:t}=(0,c.unpackLocationOrUri)(e);if(t.actor!==a.actor)throw new u.GraffitiErrorForbidden;const o=await this.deleteAtLocation(t);if(!o)throw new u.GraffitiErrorNotFound;return o};put=async(...i)=>{const[e,a]=i;if(e.actor&&e.actor!==a.actor)throw new u.GraffitiErrorForbidden;if(e.source&&e.source!==this.source)throw new u.GraffitiErrorForbidden("Putting an object that does not match this source");const t={value:e.value,channels:e.channels,allowed:e.allowed,name:e.name??(0,c.randomBase64)(),source:this.source,actor:a.actor,tombstone:!1,lastModified:new Date().getTime()};await this.db.put({_id:this.docId(t),...t});const o=await this.deleteAtLocation(t,!0);return o||{...t,value:{},channels:[],allowed:void 0,tombstone:!0}};patch=async(...i)=>{const[e,a,t]=i,{location:o}=(0,c.unpackLocationOrUri)(a);if(o.actor!==t.actor)throw new u.GraffitiErrorForbidden;const s=await this.get(a,{},t);if(s.tombstone)throw new u.GraffitiErrorNotFound("The object you are trying to patch has been deleted");const n={...s};for(const d of["value","channels","allowed"])(0,c.applyGraffitiPatch)(L.applyPatch,d,e,n);if(typeof n.value!="object"||Array.isArray(n.value)||!n.value)throw new u.GraffitiErrorPatchError("value is no longer an object");if(!Array.isArray(n.channels)||!n.channels.every(d=>typeof d=="string"))throw new u.GraffitiErrorPatchError("channels are no longer an array of strings");if(n.allowed&&(!Array.isArray(n.allowed)||!n.allowed.every(d=>typeof d=="string")))throw new u.GraffitiErrorPatchError("allowed list is not an array of strings");return n.lastModified=new Date().getTime(),await this.db.put({...n,_id:this.docId(n)}),await this.deleteAtLocation(n,!0),{...s,tombstone:!0,lastModified:n.lastModified}};queryLastModifiedSuffixes(i){let e="",a="\uFFFF";const t=i.properties?.lastModified;if(t?.minimum){let o=Math.ceil(t.minimum);o===t.minimum&&t.exclusiveMinimum&&o++,e=o.toString().padStart(15,"0")}if(t?.maximum){let o=Math.floor(t.maximum);o===t.maximum&&t.exclusiveMaximum&&o--,a=o.toString().padStart(15,"0")}return{startKeySuffix:e,endKeySuffix:a}}discover=(...i)=>{const[e,a,t]=i,o=(0,c.attemptAjvCompile)(this.ajv,a),{startKeySuffix:s,endKeySuffix:n}=this.queryLastModifiedSuffixes(a);return new g.Repeater(async(y,p)=>{const r=new Set;for(const m of e){const f=encodeURIComponent(m)+"/",h=f+s,G=f+n,j=await this.db.query("indexes/objectsPerChannelAndLastModified",{startkey:h,endkey:G,include_docs:!0});for(const b of j.rows){const w=b.doc;if(!w)continue;const{_id:A,_rev:T,...O}=w;r.has(A)||(r.add(A),(0,c.isActorAllowedGraffitiObject)(w,t)&&((0,c.maskGraffitiObject)(O,e,t),o(O)&&await y({value:O})))}}return p(),{tombstoneRetention:this.tombstoneRetention}})};recoverOrphans=(i,e)=>{const a=(0,c.attemptAjvCompile)(this.ajv,i),{startKeySuffix:t,endKeySuffix:o}=this.queryLastModifiedSuffixes(i),s=encodeURIComponent(e.actor)+"/",n=s+t,d=s+o;return new g.Repeater(async(p,r)=>{const m=await this.db.query("indexes/orphansPerActorAndLastModified",{startkey:n,endkey:d,include_docs:!0});for(const f of m.rows){const h=f.doc;if(!h)continue;const{_id:G,_rev:j,...b}=h;a(b)&&await p({value:b})}return r(),{tombstoneRetention:this.tombstoneRetention}})};channelStats=i=>new g.Repeater(async(a,t)=>{const o=encodeURIComponent(i.actor)+"/",s=await this.db.query("indexes/channelStatsPerActor",{startkey:o,endkey:o+"\uFFFF",reduce:!0,group:!0});for(const n of s.rows){const d=n.key.split("/")[1];if(typeof d!="string")continue;const{count:y,max:p}=n.value;typeof y!="number"||typeof p!="number"||await a({value:{channel:decodeURIComponent(d),count:y,lastModified:p}})}t()})}
2
2
  //# sourceMappingURL=database.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/database.ts"],
4
- "sourcesContent": ["import type {\n Graffiti,\n GraffitiObjectBase,\n GraffitiLocation,\n JSONSchema4,\n} from \"@graffiti-garden/api\";\nimport {\n GraffitiErrorNotFound,\n GraffitiErrorSchemaMismatch,\n GraffitiErrorForbidden,\n GraffitiErrorPatchError,\n} from \"@graffiti-garden/api\";\nimport PouchDB from \"pouchdb\";\nimport {\n locationToUri,\n unpackLocationOrUri,\n randomBase64,\n applyGraffitiPatch,\n attemptAjvCompile,\n maskGraffitiObject,\n isActorAllowedGraffitiObject,\n isObjectNewer,\n} from \"./utilities\";\nimport { Repeater } from \"@repeaterjs/repeater\";\nimport Ajv from \"ajv-draft-04\";\nimport { applyPatch } from \"fast-json-patch\";\n\n/**\n * Constructor options for the GraffitiPoubchDB class.\n */\nexport interface GraffitiLocalOptions {\n /**\n * Options to pass to the PouchDB constructor.\n * Defaults to `{ name: \"graffitiDb\" }`.\n *\n * See the [PouchDB documentation](https://pouchdb.com/api.html#create_database)\n * for available options.\n */\n pouchDBOptions?: PouchDB.Configuration.DatabaseConfiguration;\n /**\n * Defines the name of the {@link https://api.graffiti.garden/interfaces/GraffitiObjectBase.html#source | `source` }\n * under which to store objects.\n * Defaults to `\"local\"`.\n */\n sourceName?: string;\n /**\n * The time in milliseconds to keep tombstones before deleting them.\n * See the {@link https://api.graffiti.garden/classes/Graffiti.html#discover | `discover` }\n * documentation for more information.\n */\n tombstoneRetention?: number;\n}\n\n/**\n * An implementation of only the database operations of the\n * GraffitiAPI without synchronization or session management.\n */\nexport class GraffitiLocalDatabase\n implements\n Pick<\n Graffiti,\n | \"get\"\n | \"put\"\n | \"patch\"\n | \"delete\"\n | \"discover\"\n | \"recoverOrphans\"\n | \"channelStats\"\n >\n{\n protected readonly db: PouchDB.Database<GraffitiObjectBase>;\n protected readonly source: string = \"local\";\n protected readonly tombstoneRetention: number = 86400000; // 1 day in ms\n protected readonly ajv: Ajv;\n\n constructor(options?: GraffitiLocalOptions, ajv?: Ajv) {\n this.ajv = ajv ?? new Ajv({ strict: false });\n this.source = options?.sourceName ?? this.source;\n this.tombstoneRetention =\n options?.tombstoneRetention ?? this.tombstoneRetention;\n const pouchDbOptions = {\n name: \"graffitiDb\",\n ...options?.pouchDBOptions,\n };\n this.db = new PouchDB<GraffitiObjectBase>(\n pouchDbOptions.name,\n pouchDbOptions,\n );\n\n this.db\n //@ts-ignore\n .put({\n _id: \"_design/indexes\",\n views: {\n objectsPerChannelAndLastModified: {\n map: function (object: GraffitiObjectBase) {\n const paddedLastModified = object.lastModified\n .toString()\n .padStart(15, \"0\");\n object.channels.forEach(function (channel) {\n const id =\n encodeURIComponent(channel) + \"/\" + paddedLastModified;\n //@ts-ignore\n emit(id);\n });\n }.toString(),\n },\n orphansPerActorAndLastModified: {\n map: function (object: GraffitiObjectBase) {\n if (object.channels.length === 0) {\n const paddedLastModified = object.lastModified\n .toString()\n .padStart(15, \"0\");\n const id =\n encodeURIComponent(object.actor) + \"/\" + paddedLastModified;\n //@ts-ignore\n emit(id);\n }\n }.toString(),\n },\n channelStatsPerActor: {\n map: function (object: GraffitiObjectBase) {\n if (object.tombstone) return;\n object.channels.forEach(function (channel) {\n const id =\n encodeURIComponent(object.actor) +\n \"/\" +\n encodeURIComponent(channel);\n //@ts-ignore\n emit(id, object.lastModified);\n });\n }.toString(),\n reduce: \"_stats\",\n },\n },\n })\n //@ts-ignore\n .catch((error) => {\n if (\n error &&\n typeof error === \"object\" &&\n \"name\" in error &&\n error.name === \"conflict\"\n ) {\n // Design document already exists\n return;\n } else {\n throw error;\n }\n });\n }\n\n protected async queryByLocation(location: GraffitiLocation) {\n const uri = locationToUri(location) + \"/\";\n const results = await this.db.allDocs({\n startkey: uri,\n endkey: uri + \"\\uffff\", // \\uffff is the last unicode character\n include_docs: true,\n });\n const docs = results.rows\n .map((row) => row.doc)\n // Remove undefined docs\n .reduce<\n PouchDB.Core.ExistingDocument<\n GraffitiObjectBase & PouchDB.Core.AllDocsMeta\n >[]\n >((acc, doc) => {\n if (doc) acc.push(doc);\n return acc;\n }, []);\n return docs;\n }\n\n protected docId(location: GraffitiLocation) {\n return locationToUri(location) + \"/\" + randomBase64();\n }\n\n get: Graffiti[\"get\"] = async (...args) => {\n const [locationOrUri, schema, session] = args;\n const { location } = unpackLocationOrUri(locationOrUri);\n\n const docsAll = await this.queryByLocation(location);\n\n // Filter out ones not allowed\n const docs = docsAll.filter((doc) =>\n isActorAllowedGraffitiObject(doc, session),\n );\n if (!docs.length) throw new GraffitiErrorNotFound();\n\n // Get the most recent document\n const doc = docs.reduce((a, b) => (isObjectNewer(a, b) ? a : b));\n\n // Strip out the _id and _rev\n const { _id, _rev, ...object } = doc;\n\n // Mask out the allowed list and channels\n // if the user is not the owner\n maskGraffitiObject(object, [], session);\n\n const validate = attemptAjvCompile(this.ajv, schema);\n if (!validate(object)) {\n throw new GraffitiErrorSchemaMismatch();\n }\n return object;\n };\n\n /**\n * Deletes all docs at a particular location.\n * If the `keepLatest` flag is set to true,\n * the doc with the most recent timestamp will be\n * spared. If there are multiple docs with the same\n * timestamp, the one with the highest `_id` will be\n * spared.\n */\n protected async deleteAtLocation(\n location: GraffitiLocation,\n keepLatest: boolean = false,\n ) {\n const docsAtLocationAll = await this.queryByLocation(location);\n const docsAtLocation = docsAtLocationAll.filter((doc) => !doc.tombstone);\n if (!docsAtLocation.length) return undefined;\n\n // Get the most recent lastModified timestamp.\n const latestModified = docsAtLocation\n .map((doc) => doc.lastModified)\n .reduce((a, b) => (a > b ? a : b));\n\n // Delete all old docs\n const docsToDelete = docsAtLocation.filter(\n (doc) => !keepLatest || doc.lastModified < latestModified,\n );\n\n // For docs with the same timestamp,\n // keep the one with the highest _id\n // to break concurrency ties\n const concurrentDocsAll = docsAtLocation.filter(\n (doc) => keepLatest && doc.lastModified === latestModified,\n );\n if (concurrentDocsAll.length) {\n const keepDocId = concurrentDocsAll\n .map((doc) => doc._id)\n .reduce((a, b) => (a > b ? a : b));\n const concurrentDocsToDelete = concurrentDocsAll.filter(\n (doc) => doc._id !== keepDocId,\n );\n docsToDelete.push(...concurrentDocsToDelete);\n }\n\n const lastModified = keepLatest ? latestModified : new Date().getTime();\n\n const deleteResults = await this.db.bulkDocs<GraffitiObjectBase>(\n docsToDelete.map((doc) => ({\n ...doc,\n tombstone: true,\n lastModified,\n })),\n );\n\n // Get one of the docs that was deleted\n let deletedObject: GraffitiObjectBase | undefined = undefined;\n for (const resultOrError of deleteResults) {\n if (\"ok\" in resultOrError) {\n const { id } = resultOrError;\n const deletedDoc = docsToDelete.find((doc) => doc._id === id);\n if (deletedDoc) {\n const { _id, _rev, _conflicts, _attachments, ...object } = deletedDoc;\n deletedObject = {\n ...object,\n tombstone: true,\n lastModified,\n };\n break;\n }\n }\n }\n\n return deletedObject;\n }\n\n delete: Graffiti[\"delete\"] = async (...args) => {\n const [locationOrUri, session] = args;\n const { location } = unpackLocationOrUri(locationOrUri);\n if (location.actor !== session.actor) {\n throw new GraffitiErrorForbidden();\n }\n\n const deletedObject = await this.deleteAtLocation(location);\n if (!deletedObject) {\n throw new GraffitiErrorNotFound();\n }\n return deletedObject;\n };\n\n put: Graffiti[\"put\"] = async (...args) => {\n const [objectPartial, session] = args;\n if (objectPartial.actor && objectPartial.actor !== session.actor) {\n throw new GraffitiErrorForbidden();\n }\n if (objectPartial.source && objectPartial.source !== this.source) {\n throw new GraffitiErrorForbidden(\n \"Putting an object that does not match this source\",\n );\n }\n\n const object: GraffitiObjectBase = {\n value: objectPartial.value,\n channels: objectPartial.channels,\n allowed: objectPartial.allowed,\n name: objectPartial.name ?? randomBase64(),\n source: this.source,\n actor: session.actor,\n tombstone: false,\n lastModified: new Date().getTime(),\n };\n\n await this.db.put({\n _id: this.docId(object),\n ...object,\n });\n\n // Delete the old object\n const previousObject = await this.deleteAtLocation(object, true);\n if (previousObject) {\n return previousObject;\n } else {\n return {\n ...object,\n value: {},\n channels: [],\n allowed: undefined,\n tombstone: true,\n };\n }\n };\n\n patch: Graffiti[\"patch\"] = async (...args) => {\n const [patch, locationOrUri, session] = args;\n const { location } = unpackLocationOrUri(locationOrUri);\n if (location.actor !== session.actor) {\n throw new GraffitiErrorForbidden();\n }\n const originalObject = await this.get(locationOrUri, {}, session);\n if (originalObject.tombstone) {\n throw new GraffitiErrorNotFound(\n \"The object you are trying to patch has been deleted\",\n );\n }\n\n // Patch it outside of the database\n const patchObject: GraffitiObjectBase = { ...originalObject };\n for (const prop of [\"value\", \"channels\", \"allowed\"] as const) {\n applyGraffitiPatch(applyPatch, prop, patch, patchObject);\n }\n\n // Make sure the value is an object\n if (\n typeof patchObject.value !== \"object\" ||\n Array.isArray(patchObject.value) ||\n !patchObject.value\n ) {\n throw new GraffitiErrorPatchError(\"value is no longer an object\");\n }\n\n // Make sure the channels are an array of strings\n if (\n !Array.isArray(patchObject.channels) ||\n !patchObject.channels.every((channel) => typeof channel === \"string\")\n ) {\n throw new GraffitiErrorPatchError(\n \"channels are no longer an array of strings\",\n );\n }\n\n // Make sure the allowed list is an array of strings or undefined\n if (\n patchObject.allowed &&\n (!Array.isArray(patchObject.allowed) ||\n !patchObject.allowed.every((allowed) => typeof allowed === \"string\"))\n ) {\n throw new GraffitiErrorPatchError(\n \"allowed list is not an array of strings\",\n );\n }\n\n patchObject.lastModified = new Date().getTime();\n await this.db.put({\n ...patchObject,\n _id: this.docId(patchObject),\n });\n\n // Delete the old object\n await this.deleteAtLocation(patchObject, true);\n\n return {\n ...originalObject,\n tombstone: true,\n lastModified: patchObject.lastModified,\n };\n };\n\n protected queryLastModifiedSuffixes(schema: JSONSchema4) {\n // Use the index for queries over ranges of lastModified\n let startKeySuffix = \"\";\n let endKeySuffix = \"\\uffff\";\n const lastModifiedSchema = schema.properties?.lastModified;\n if (lastModifiedSchema?.minimum) {\n let minimum = Math.ceil(lastModifiedSchema.minimum);\n minimum === lastModifiedSchema.minimum &&\n lastModifiedSchema.exclusiveMinimum &&\n minimum++;\n startKeySuffix = minimum.toString().padStart(15, \"0\");\n }\n if (lastModifiedSchema?.maximum) {\n let maximum = Math.floor(lastModifiedSchema.maximum);\n maximum === lastModifiedSchema.maximum &&\n lastModifiedSchema.exclusiveMaximum &&\n maximum--;\n endKeySuffix = maximum.toString().padStart(15, \"0\");\n }\n return {\n startKeySuffix,\n endKeySuffix,\n };\n }\n\n discover: Graffiti[\"discover\"] = (...args) => {\n const [channels, schema, session] = args;\n const validate = attemptAjvCompile(this.ajv, schema);\n\n const { startKeySuffix, endKeySuffix } =\n this.queryLastModifiedSuffixes(schema);\n\n const repeater: ReturnType<\n typeof Graffiti.prototype.discover<typeof schema>\n > = new Repeater(async (push, stop) => {\n const processedIds = new Set<string>();\n\n for (const channel of channels) {\n const keyPrefix = encodeURIComponent(channel) + \"/\";\n const startkey = keyPrefix + startKeySuffix;\n const endkey = keyPrefix + endKeySuffix;\n\n const result = await this.db.query<GraffitiObjectBase>(\n \"indexes/objectsPerChannelAndLastModified\",\n { startkey, endkey, include_docs: true },\n );\n\n for (const row of result.rows) {\n const doc = row.doc;\n if (!doc) continue;\n\n const { _id, _rev, ...object } = doc;\n\n // Don't double return the same object\n // (which can happen if it's in multiple channels)\n if (processedIds.has(_id)) continue;\n processedIds.add(_id);\n\n // Make sure the user is allowed to see it\n if (!isActorAllowedGraffitiObject(doc, session)) continue;\n\n // Mask out the allowed list and channels\n // if the user is not the owner\n maskGraffitiObject(object, channels, session);\n\n // Check that it matches the schema\n if (validate(object)) {\n await push({ value: object });\n }\n }\n }\n stop();\n return {\n tombstoneRetention: this.tombstoneRetention,\n };\n });\n\n return repeater;\n };\n\n recoverOrphans: Graffiti[\"recoverOrphans\"] = (schema, session) => {\n const validate = attemptAjvCompile(this.ajv, schema);\n\n const { startKeySuffix, endKeySuffix } =\n this.queryLastModifiedSuffixes(schema);\n const keyPrefix = encodeURIComponent(session.actor) + \"/\";\n const startkey = keyPrefix + startKeySuffix;\n const endkey = keyPrefix + endKeySuffix;\n\n const repeater: ReturnType<\n typeof Graffiti.prototype.recoverOrphans<typeof schema>\n > = new Repeater(async (push, stop) => {\n const result = await this.db.query<GraffitiObjectBase>(\n \"indexes/orphansPerActorAndLastModified\",\n { startkey, endkey, include_docs: true },\n );\n\n for (const row of result.rows) {\n const doc = row.doc;\n if (!doc) continue;\n\n // No masking/access necessary because\n // the objects are all owned by the querier\n\n const { _id, _rev, ...object } = doc;\n if (validate(object)) {\n await push({ value: object });\n }\n }\n stop();\n return {\n tombstoneRetention: this.tombstoneRetention,\n };\n });\n\n return repeater;\n };\n\n channelStats: Graffiti[\"channelStats\"] = (session) => {\n const repeater: ReturnType<typeof Graffiti.prototype.channelStats> =\n new Repeater(async (push, stop) => {\n const keyPrefix = encodeURIComponent(session.actor) + \"/\";\n const result = await this.db.query(\"indexes/channelStatsPerActor\", {\n startkey: keyPrefix,\n endkey: keyPrefix + \"\\uffff\",\n reduce: true,\n group: true,\n });\n for (const row of result.rows) {\n const channelEncoded = row.key.split(\"/\")[1];\n if (typeof channelEncoded !== \"string\") continue;\n const { count, max: lastModified } = row.value;\n if (typeof count !== \"number\" || typeof lastModified !== \"number\")\n continue;\n await push({\n value: {\n channel: decodeURIComponent(channelEncoded),\n count,\n lastModified,\n },\n });\n }\n stop();\n });\n\n return repeater;\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type {\n Graffiti,\n GraffitiObjectBase,\n GraffitiLocation,\n JSONSchema4,\n} from \"@graffiti-garden/api\";\nimport {\n GraffitiErrorNotFound,\n GraffitiErrorSchemaMismatch,\n GraffitiErrorForbidden,\n GraffitiErrorPatchError,\n} from \"@graffiti-garden/api\";\nimport PouchDB from \"pouchdb\";\nimport {\n locationToUri,\n unpackLocationOrUri,\n randomBase64,\n applyGraffitiPatch,\n attemptAjvCompile,\n maskGraffitiObject,\n isActorAllowedGraffitiObject,\n isObjectNewer,\n} from \"./utilities.js\";\nimport { Repeater } from \"@repeaterjs/repeater\";\nimport Ajv from \"ajv-draft-04\";\nimport { applyPatch } from \"fast-json-patch\";\n\n/**\n * Constructor options for the GraffitiPoubchDB class.\n */\nexport interface GraffitiLocalOptions {\n /**\n * Options to pass to the PouchDB constructor.\n * Defaults to `{ name: \"graffitiDb\" }`.\n *\n * See the [PouchDB documentation](https://pouchdb.com/api.html#create_database)\n * for available options.\n */\n pouchDBOptions?: PouchDB.Configuration.DatabaseConfiguration;\n /**\n * Defines the name of the {@link https://api.graffiti.garden/interfaces/GraffitiObjectBase.html#source | `source` }\n * under which to store objects.\n * Defaults to `\"local\"`.\n */\n sourceName?: string;\n /**\n * The time in milliseconds to keep tombstones before deleting them.\n * See the {@link https://api.graffiti.garden/classes/Graffiti.html#discover | `discover` }\n * documentation for more information.\n */\n tombstoneRetention?: number;\n}\n\n/**\n * An implementation of only the database operations of the\n * GraffitiAPI without synchronization or session management.\n */\nexport class GraffitiLocalDatabase\n implements\n Pick<\n Graffiti,\n | \"get\"\n | \"put\"\n | \"patch\"\n | \"delete\"\n | \"discover\"\n | \"recoverOrphans\"\n | \"channelStats\"\n >\n{\n protected readonly db: PouchDB.Database<GraffitiObjectBase>;\n protected readonly source: string = \"local\";\n protected readonly tombstoneRetention: number = 86400000; // 1 day in ms\n protected readonly ajv: Ajv;\n\n constructor(options?: GraffitiLocalOptions, ajv?: Ajv) {\n this.ajv = ajv ?? new Ajv({ strict: false });\n this.source = options?.sourceName ?? this.source;\n this.tombstoneRetention =\n options?.tombstoneRetention ?? this.tombstoneRetention;\n const pouchDbOptions = {\n name: \"graffitiDb\",\n ...options?.pouchDBOptions,\n };\n this.db = new PouchDB<GraffitiObjectBase>(\n pouchDbOptions.name,\n pouchDbOptions,\n );\n\n this.db\n //@ts-ignore\n .put({\n _id: \"_design/indexes\",\n views: {\n objectsPerChannelAndLastModified: {\n map: function (object: GraffitiObjectBase) {\n const paddedLastModified = object.lastModified\n .toString()\n .padStart(15, \"0\");\n object.channels.forEach(function (channel) {\n const id =\n encodeURIComponent(channel) + \"/\" + paddedLastModified;\n //@ts-ignore\n emit(id);\n });\n }.toString(),\n },\n orphansPerActorAndLastModified: {\n map: function (object: GraffitiObjectBase) {\n if (object.channels.length === 0) {\n const paddedLastModified = object.lastModified\n .toString()\n .padStart(15, \"0\");\n const id =\n encodeURIComponent(object.actor) + \"/\" + paddedLastModified;\n //@ts-ignore\n emit(id);\n }\n }.toString(),\n },\n channelStatsPerActor: {\n map: function (object: GraffitiObjectBase) {\n if (object.tombstone) return;\n object.channels.forEach(function (channel) {\n const id =\n encodeURIComponent(object.actor) +\n \"/\" +\n encodeURIComponent(channel);\n //@ts-ignore\n emit(id, object.lastModified);\n });\n }.toString(),\n reduce: \"_stats\",\n },\n },\n })\n //@ts-ignore\n .catch((error) => {\n if (\n error &&\n typeof error === \"object\" &&\n \"name\" in error &&\n error.name === \"conflict\"\n ) {\n // Design document already exists\n return;\n } else {\n throw error;\n }\n });\n }\n\n protected async queryByLocation(location: GraffitiLocation) {\n const uri = locationToUri(location) + \"/\";\n const results = await this.db.allDocs({\n startkey: uri,\n endkey: uri + \"\\uffff\", // \\uffff is the last unicode character\n include_docs: true,\n });\n const docs = results.rows\n .map((row) => row.doc)\n // Remove undefined docs\n .reduce<\n PouchDB.Core.ExistingDocument<\n GraffitiObjectBase & PouchDB.Core.AllDocsMeta\n >[]\n >((acc, doc) => {\n if (doc) acc.push(doc);\n return acc;\n }, []);\n return docs;\n }\n\n protected docId(location: GraffitiLocation) {\n return locationToUri(location) + \"/\" + randomBase64();\n }\n\n get: Graffiti[\"get\"] = async (...args) => {\n const [locationOrUri, schema, session] = args;\n const { location } = unpackLocationOrUri(locationOrUri);\n\n const docsAll = await this.queryByLocation(location);\n\n // Filter out ones not allowed\n const docs = docsAll.filter((doc) =>\n isActorAllowedGraffitiObject(doc, session),\n );\n if (!docs.length) throw new GraffitiErrorNotFound();\n\n // Get the most recent document\n const doc = docs.reduce((a, b) => (isObjectNewer(a, b) ? a : b));\n\n // Strip out the _id and _rev\n const { _id, _rev, ...object } = doc;\n\n // Mask out the allowed list and channels\n // if the user is not the owner\n maskGraffitiObject(object, [], session);\n\n const validate = attemptAjvCompile(this.ajv, schema);\n if (!validate(object)) {\n throw new GraffitiErrorSchemaMismatch();\n }\n return object;\n };\n\n /**\n * Deletes all docs at a particular location.\n * If the `keepLatest` flag is set to true,\n * the doc with the most recent timestamp will be\n * spared. If there are multiple docs with the same\n * timestamp, the one with the highest `_id` will be\n * spared.\n */\n protected async deleteAtLocation(\n location: GraffitiLocation,\n keepLatest: boolean = false,\n ) {\n const docsAtLocationAll = await this.queryByLocation(location);\n const docsAtLocation = docsAtLocationAll.filter((doc) => !doc.tombstone);\n if (!docsAtLocation.length) return undefined;\n\n // Get the most recent lastModified timestamp.\n const latestModified = docsAtLocation\n .map((doc) => doc.lastModified)\n .reduce((a, b) => (a > b ? a : b));\n\n // Delete all old docs\n const docsToDelete = docsAtLocation.filter(\n (doc) => !keepLatest || doc.lastModified < latestModified,\n );\n\n // For docs with the same timestamp,\n // keep the one with the highest _id\n // to break concurrency ties\n const concurrentDocsAll = docsAtLocation.filter(\n (doc) => keepLatest && doc.lastModified === latestModified,\n );\n if (concurrentDocsAll.length) {\n const keepDocId = concurrentDocsAll\n .map((doc) => doc._id)\n .reduce((a, b) => (a > b ? a : b));\n const concurrentDocsToDelete = concurrentDocsAll.filter(\n (doc) => doc._id !== keepDocId,\n );\n docsToDelete.push(...concurrentDocsToDelete);\n }\n\n const lastModified = keepLatest ? latestModified : new Date().getTime();\n\n const deleteResults = await this.db.bulkDocs<GraffitiObjectBase>(\n docsToDelete.map((doc) => ({\n ...doc,\n tombstone: true,\n lastModified,\n })),\n );\n\n // Get one of the docs that was deleted\n let deletedObject: GraffitiObjectBase | undefined = undefined;\n for (const resultOrError of deleteResults) {\n if (\"ok\" in resultOrError) {\n const { id } = resultOrError;\n const deletedDoc = docsToDelete.find((doc) => doc._id === id);\n if (deletedDoc) {\n const { _id, _rev, _conflicts, _attachments, ...object } = deletedDoc;\n deletedObject = {\n ...object,\n tombstone: true,\n lastModified,\n };\n break;\n }\n }\n }\n\n return deletedObject;\n }\n\n delete: Graffiti[\"delete\"] = async (...args) => {\n const [locationOrUri, session] = args;\n const { location } = unpackLocationOrUri(locationOrUri);\n if (location.actor !== session.actor) {\n throw new GraffitiErrorForbidden();\n }\n\n const deletedObject = await this.deleteAtLocation(location);\n if (!deletedObject) {\n throw new GraffitiErrorNotFound();\n }\n return deletedObject;\n };\n\n put: Graffiti[\"put\"] = async (...args) => {\n const [objectPartial, session] = args;\n if (objectPartial.actor && objectPartial.actor !== session.actor) {\n throw new GraffitiErrorForbidden();\n }\n if (objectPartial.source && objectPartial.source !== this.source) {\n throw new GraffitiErrorForbidden(\n \"Putting an object that does not match this source\",\n );\n }\n\n const object: GraffitiObjectBase = {\n value: objectPartial.value,\n channels: objectPartial.channels,\n allowed: objectPartial.allowed,\n name: objectPartial.name ?? randomBase64(),\n source: this.source,\n actor: session.actor,\n tombstone: false,\n lastModified: new Date().getTime(),\n };\n\n await this.db.put({\n _id: this.docId(object),\n ...object,\n });\n\n // Delete the old object\n const previousObject = await this.deleteAtLocation(object, true);\n if (previousObject) {\n return previousObject;\n } else {\n return {\n ...object,\n value: {},\n channels: [],\n allowed: undefined,\n tombstone: true,\n };\n }\n };\n\n patch: Graffiti[\"patch\"] = async (...args) => {\n const [patch, locationOrUri, session] = args;\n const { location } = unpackLocationOrUri(locationOrUri);\n if (location.actor !== session.actor) {\n throw new GraffitiErrorForbidden();\n }\n const originalObject = await this.get(locationOrUri, {}, session);\n if (originalObject.tombstone) {\n throw new GraffitiErrorNotFound(\n \"The object you are trying to patch has been deleted\",\n );\n }\n\n // Patch it outside of the database\n const patchObject: GraffitiObjectBase = { ...originalObject };\n for (const prop of [\"value\", \"channels\", \"allowed\"] as const) {\n applyGraffitiPatch(applyPatch, prop, patch, patchObject);\n }\n\n // Make sure the value is an object\n if (\n typeof patchObject.value !== \"object\" ||\n Array.isArray(patchObject.value) ||\n !patchObject.value\n ) {\n throw new GraffitiErrorPatchError(\"value is no longer an object\");\n }\n\n // Make sure the channels are an array of strings\n if (\n !Array.isArray(patchObject.channels) ||\n !patchObject.channels.every((channel) => typeof channel === \"string\")\n ) {\n throw new GraffitiErrorPatchError(\n \"channels are no longer an array of strings\",\n );\n }\n\n // Make sure the allowed list is an array of strings or undefined\n if (\n patchObject.allowed &&\n (!Array.isArray(patchObject.allowed) ||\n !patchObject.allowed.every((allowed) => typeof allowed === \"string\"))\n ) {\n throw new GraffitiErrorPatchError(\n \"allowed list is not an array of strings\",\n );\n }\n\n patchObject.lastModified = new Date().getTime();\n await this.db.put({\n ...patchObject,\n _id: this.docId(patchObject),\n });\n\n // Delete the old object\n await this.deleteAtLocation(patchObject, true);\n\n return {\n ...originalObject,\n tombstone: true,\n lastModified: patchObject.lastModified,\n };\n };\n\n protected queryLastModifiedSuffixes(schema: JSONSchema4) {\n // Use the index for queries over ranges of lastModified\n let startKeySuffix = \"\";\n let endKeySuffix = \"\\uffff\";\n const lastModifiedSchema = schema.properties?.lastModified;\n if (lastModifiedSchema?.minimum) {\n let minimum = Math.ceil(lastModifiedSchema.minimum);\n minimum === lastModifiedSchema.minimum &&\n lastModifiedSchema.exclusiveMinimum &&\n minimum++;\n startKeySuffix = minimum.toString().padStart(15, \"0\");\n }\n if (lastModifiedSchema?.maximum) {\n let maximum = Math.floor(lastModifiedSchema.maximum);\n maximum === lastModifiedSchema.maximum &&\n lastModifiedSchema.exclusiveMaximum &&\n maximum--;\n endKeySuffix = maximum.toString().padStart(15, \"0\");\n }\n return {\n startKeySuffix,\n endKeySuffix,\n };\n }\n\n discover: Graffiti[\"discover\"] = (...args) => {\n const [channels, schema, session] = args;\n const validate = attemptAjvCompile(this.ajv, schema);\n\n const { startKeySuffix, endKeySuffix } =\n this.queryLastModifiedSuffixes(schema);\n\n const repeater: ReturnType<\n typeof Graffiti.prototype.discover<typeof schema>\n > = new Repeater(async (push, stop) => {\n const processedIds = new Set<string>();\n\n for (const channel of channels) {\n const keyPrefix = encodeURIComponent(channel) + \"/\";\n const startkey = keyPrefix + startKeySuffix;\n const endkey = keyPrefix + endKeySuffix;\n\n const result = await this.db.query<GraffitiObjectBase>(\n \"indexes/objectsPerChannelAndLastModified\",\n { startkey, endkey, include_docs: true },\n );\n\n for (const row of result.rows) {\n const doc = row.doc;\n if (!doc) continue;\n\n const { _id, _rev, ...object } = doc;\n\n // Don't double return the same object\n // (which can happen if it's in multiple channels)\n if (processedIds.has(_id)) continue;\n processedIds.add(_id);\n\n // Make sure the user is allowed to see it\n if (!isActorAllowedGraffitiObject(doc, session)) continue;\n\n // Mask out the allowed list and channels\n // if the user is not the owner\n maskGraffitiObject(object, channels, session);\n\n // Check that it matches the schema\n if (validate(object)) {\n await push({ value: object });\n }\n }\n }\n stop();\n return {\n tombstoneRetention: this.tombstoneRetention,\n };\n });\n\n return repeater;\n };\n\n recoverOrphans: Graffiti[\"recoverOrphans\"] = (schema, session) => {\n const validate = attemptAjvCompile(this.ajv, schema);\n\n const { startKeySuffix, endKeySuffix } =\n this.queryLastModifiedSuffixes(schema);\n const keyPrefix = encodeURIComponent(session.actor) + \"/\";\n const startkey = keyPrefix + startKeySuffix;\n const endkey = keyPrefix + endKeySuffix;\n\n const repeater: ReturnType<\n typeof Graffiti.prototype.recoverOrphans<typeof schema>\n > = new Repeater(async (push, stop) => {\n const result = await this.db.query<GraffitiObjectBase>(\n \"indexes/orphansPerActorAndLastModified\",\n { startkey, endkey, include_docs: true },\n );\n\n for (const row of result.rows) {\n const doc = row.doc;\n if (!doc) continue;\n\n // No masking/access necessary because\n // the objects are all owned by the querier\n\n const { _id, _rev, ...object } = doc;\n if (validate(object)) {\n await push({ value: object });\n }\n }\n stop();\n return {\n tombstoneRetention: this.tombstoneRetention,\n };\n });\n\n return repeater;\n };\n\n channelStats: Graffiti[\"channelStats\"] = (session) => {\n const repeater: ReturnType<typeof Graffiti.prototype.channelStats> =\n new Repeater(async (push, stop) => {\n const keyPrefix = encodeURIComponent(session.actor) + \"/\";\n const result = await this.db.query(\"indexes/channelStatsPerActor\", {\n startkey: keyPrefix,\n endkey: keyPrefix + \"\\uffff\",\n reduce: true,\n group: true,\n });\n for (const row of result.rows) {\n const channelEncoded = row.key.split(\"/\")[1];\n if (typeof channelEncoded !== \"string\") continue;\n const { count, max: lastModified } = row.value;\n if (typeof count !== \"number\" || typeof lastModified !== \"number\")\n continue;\n await push({\n value: {\n channel: decodeURIComponent(channelEncoded),\n count,\n lastModified,\n },\n });\n }\n stop();\n });\n\n return repeater;\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["database_exports", "__export", "GraffitiLocalDatabase", "__toCommonJS", "import_api", "import_pouchdb", "import_utilities", "import_repeater", "import_ajv_draft_04", "import_fast_json_patch", "options", "ajv", "Ajv", "pouchDbOptions", "PouchDB", "object", "paddedLastModified", "channel", "id", "error", "location", "uri", "row", "acc", "doc", "args", "locationOrUri", "schema", "session", "docs", "a", "b", "_id", "_rev", "keepLatest", "docsAtLocation", "latestModified", "docsToDelete", "concurrentDocsAll", "keepDocId", "concurrentDocsToDelete", "lastModified", "deleteResults", "deletedObject", "resultOrError", "deletedDoc", "_conflicts", "_attachments", "objectPartial", "previousObject", "patch", "originalObject", "patchObject", "prop", "allowed", "startKeySuffix", "endKeySuffix", "lastModifiedSchema", "minimum", "maximum", "channels", "validate", "push", "stop", "processedIds", "keyPrefix", "startkey", "endkey", "result", "channelEncoded", "count"]
7
7
  }
package/dist/cjs/index.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var v=Object.create;var s=Object.defineProperty;var d=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var y=Object.getPrototypeOf,b=Object.prototype.hasOwnProperty;var z=(t,n)=>{for(var o in n)s(t,o,{get:n[o],enumerable:!0})},c=(t,n,o,e)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of g(n))!b.call(t,r)&&r!==o&&s(t,r,{get:()=>n[r],enumerable:!(e=d(n,r))||e.enumerable});return t};var u=(t,n,o)=>(o=t!=null?v(y(t)):{},c(n||!t||!t.__esModule?s(o,"default",{value:t,enumerable:!0}):o,t)),m=t=>c(s({},"__esModule",{value:!0}),t);var L={};z(L,{GraffitiLocal:()=>O});module.exports=m(L);var f=require("@graffiti-garden/api"),h=u(require("ajv-draft-04"),1),p=require("./session-manager"),l=require("./database"),G=require("./synchronize"),a=require("./utilities");class O extends f.Graffiti{locationToUri=a.locationToUri;uriToLocation=a.uriToLocation;login;logout;sessionEvents;put;get;patch;delete;discover;recoverOrphans;synchronizeGet;synchronizeDiscover;synchronizeRecoverOrphans;channelStats;constructor(n){super();const o=new p.GraffitiLocalSessionManager;this.login=o.login.bind(o),this.logout=o.logout.bind(o),this.sessionEvents=o.sessionEvents;const e=new h.default({strict:!1}),r=new l.GraffitiLocalDatabase(n,e),i=new G.GraffitiSynchronize(r,e);this.put=i.put.bind(i),this.get=i.get.bind(i),this.patch=i.patch.bind(i),this.delete=i.delete.bind(i),this.discover=i.discover.bind(i),this.recoverOrphans=i.recoverOrphans.bind(r),this.channelStats=r.channelStats.bind(r),this.synchronizeDiscover=i.synchronizeDiscover.bind(i),this.synchronizeGet=i.synchronizeGet.bind(i),this.synchronizeRecoverOrphans=i.synchronizeRecoverOrphans.bind(i)}}
1
+ "use strict";var v=Object.create;var s=Object.defineProperty;var d=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var y=Object.getPrototypeOf,b=Object.prototype.hasOwnProperty;var z=(t,n)=>{for(var o in n)s(t,o,{get:n[o],enumerable:!0})},c=(t,n,o,e)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of g(n))!b.call(t,r)&&r!==o&&s(t,r,{get:()=>n[r],enumerable:!(e=d(n,r))||e.enumerable});return t};var u=(t,n,o)=>(o=t!=null?v(y(t)):{},c(n||!t||!t.__esModule?s(o,"default",{value:t,enumerable:!0}):o,t)),m=t=>c(s({},"__esModule",{value:!0}),t);var L={};z(L,{GraffitiLocal:()=>O});module.exports=m(L);var f=require("@graffiti-garden/api"),h=u(require("ajv-draft-04"),1),p=require("./session-manager.js"),l=require("./database.js"),G=require("./synchronize.js"),a=require("./utilities.js");class O extends f.Graffiti{locationToUri=a.locationToUri;uriToLocation=a.uriToLocation;login;logout;sessionEvents;put;get;patch;delete;discover;recoverOrphans;synchronizeGet;synchronizeDiscover;synchronizeRecoverOrphans;channelStats;constructor(n){super();const o=new p.GraffitiLocalSessionManager;this.login=o.login.bind(o),this.logout=o.logout.bind(o),this.sessionEvents=o.sessionEvents;const e=new h.default({strict:!1}),r=new l.GraffitiLocalDatabase(n,e),i=new G.GraffitiSynchronize(r,e);this.put=i.put.bind(i),this.get=i.get.bind(i),this.patch=i.patch.bind(i),this.delete=i.delete.bind(i),this.discover=i.discover.bind(i),this.recoverOrphans=i.recoverOrphans.bind(r),this.channelStats=r.channelStats.bind(r),this.synchronizeDiscover=i.synchronizeDiscover.bind(i),this.synchronizeGet=i.synchronizeGet.bind(i),this.synchronizeRecoverOrphans=i.synchronizeRecoverOrphans.bind(i)}}
2
2
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/index.ts"],
4
- "sourcesContent": ["import { Graffiti } from \"@graffiti-garden/api\";\nimport Ajv from \"ajv-draft-04\";\nimport { GraffitiLocalSessionManager } from \"./session-manager\";\nimport { GraffitiLocalDatabase, type GraffitiLocalOptions } from \"./database\";\nimport { GraffitiSynchronize } from \"./synchronize\";\nimport { locationToUri, uriToLocation } from \"./utilities\";\n\nexport type { GraffitiLocalOptions };\n\n/**\n * A local implementation of the [Graffiti API](https://api.graffiti.garden/classes/Graffiti.html)\n * based on [PouchDB](https://pouchdb.com/). PouchDb will automatically persist data in a local\n * database, either in the browser or in Node.js.\n * It can also be configured to work with an external [CouchDB](https://couchdb.apache.org/) server,\n * although using it with a remote server will not be secure.\n */\nexport class GraffitiLocal extends Graffiti {\n locationToUri = locationToUri;\n uriToLocation = uriToLocation;\n\n login: Graffiti[\"login\"];\n logout: Graffiti[\"logout\"];\n sessionEvents: Graffiti[\"sessionEvents\"];\n put: Graffiti[\"put\"];\n get: Graffiti[\"get\"];\n patch: Graffiti[\"patch\"];\n delete: Graffiti[\"delete\"];\n discover: Graffiti[\"discover\"];\n recoverOrphans: Graffiti[\"recoverOrphans\"];\n synchronizeGet: Graffiti[\"synchronizeGet\"];\n synchronizeDiscover: Graffiti[\"synchronizeDiscover\"];\n synchronizeRecoverOrphans: Graffiti[\"synchronizeRecoverOrphans\"];\n channelStats: Graffiti[\"channelStats\"];\n\n constructor(options?: GraffitiLocalOptions) {\n super();\n\n const sessionManagerLocal = new GraffitiLocalSessionManager();\n this.login = sessionManagerLocal.login.bind(sessionManagerLocal);\n this.logout = sessionManagerLocal.logout.bind(sessionManagerLocal);\n this.sessionEvents = sessionManagerLocal.sessionEvents;\n\n const ajv = new Ajv({ strict: false });\n const graffitiPouchDbBase = new GraffitiLocalDatabase(options, ajv);\n const graffitiSynchronize = new GraffitiSynchronize(\n graffitiPouchDbBase,\n ajv,\n );\n\n this.put = graffitiSynchronize.put.bind(graffitiSynchronize);\n this.get = graffitiSynchronize.get.bind(graffitiSynchronize);\n this.patch = graffitiSynchronize.patch.bind(graffitiSynchronize);\n this.delete = graffitiSynchronize.delete.bind(graffitiSynchronize);\n this.discover = graffitiSynchronize.discover.bind(graffitiSynchronize);\n this.recoverOrphans =\n graffitiSynchronize.recoverOrphans.bind(graffitiPouchDbBase);\n this.channelStats =\n graffitiPouchDbBase.channelStats.bind(graffitiPouchDbBase);\n this.synchronizeDiscover =\n graffitiSynchronize.synchronizeDiscover.bind(graffitiSynchronize);\n this.synchronizeGet =\n graffitiSynchronize.synchronizeGet.bind(graffitiSynchronize);\n this.synchronizeRecoverOrphans =\n graffitiSynchronize.synchronizeRecoverOrphans.bind(graffitiSynchronize);\n }\n}\n"],
5
- "mappings": "0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,mBAAAE,IAAA,eAAAC,EAAAH,GAAA,IAAAI,EAAyB,gCACzBC,EAAgB,6BAChBC,EAA4C,6BAC5CC,EAAiE,sBACjEC,EAAoC,yBACpCC,EAA6C,uBAWtC,MAAMP,UAAsB,UAAS,CAC1C,cAAgB,gBAChB,cAAgB,gBAEhB,MACA,OACA,cACA,IACA,IACA,MACA,OACA,SACA,eACA,eACA,oBACA,0BACA,aAEA,YAAYQ,EAAgC,CAC1C,MAAM,EAEN,MAAMC,EAAsB,IAAI,8BAChC,KAAK,MAAQA,EAAoB,MAAM,KAAKA,CAAmB,EAC/D,KAAK,OAASA,EAAoB,OAAO,KAAKA,CAAmB,EACjE,KAAK,cAAgBA,EAAoB,cAEzC,MAAMC,EAAM,IAAI,EAAAC,QAAI,CAAE,OAAQ,EAAM,CAAC,EAC/BC,EAAsB,IAAI,wBAAsBJ,EAASE,CAAG,EAC5DG,EAAsB,IAAI,sBAC9BD,EACAF,CACF,EAEA,KAAK,IAAMG,EAAoB,IAAI,KAAKA,CAAmB,EAC3D,KAAK,IAAMA,EAAoB,IAAI,KAAKA,CAAmB,EAC3D,KAAK,MAAQA,EAAoB,MAAM,KAAKA,CAAmB,EAC/D,KAAK,OAASA,EAAoB,OAAO,KAAKA,CAAmB,EACjE,KAAK,SAAWA,EAAoB,SAAS,KAAKA,CAAmB,EACrE,KAAK,eACHA,EAAoB,eAAe,KAAKD,CAAmB,EAC7D,KAAK,aACHA,EAAoB,aAAa,KAAKA,CAAmB,EAC3D,KAAK,oBACHC,EAAoB,oBAAoB,KAAKA,CAAmB,EAClE,KAAK,eACHA,EAAoB,eAAe,KAAKA,CAAmB,EAC7D,KAAK,0BACHA,EAAoB,0BAA0B,KAAKA,CAAmB,CAC1E,CACF",
4
+ "sourcesContent": ["import { Graffiti } from \"@graffiti-garden/api\";\nimport Ajv from \"ajv-draft-04\";\nimport { GraffitiLocalSessionManager } from \"./session-manager.js\";\nimport {\n GraffitiLocalDatabase,\n type GraffitiLocalOptions,\n} from \"./database.js\";\nimport { GraffitiSynchronize } from \"./synchronize.js\";\nimport { locationToUri, uriToLocation } from \"./utilities.js\";\n\nexport type { GraffitiLocalOptions };\n\n/**\n * A local implementation of the [Graffiti API](https://api.graffiti.garden/classes/Graffiti.html)\n * based on [PouchDB](https://pouchdb.com/). PouchDb will automatically persist data in a local\n * database, either in the browser or in Node.js.\n * It can also be configured to work with an external [CouchDB](https://couchdb.apache.org/) server,\n * although using it with a remote server will not be secure.\n */\nexport class GraffitiLocal extends Graffiti {\n locationToUri = locationToUri;\n uriToLocation = uriToLocation;\n\n login: Graffiti[\"login\"];\n logout: Graffiti[\"logout\"];\n sessionEvents: Graffiti[\"sessionEvents\"];\n put: Graffiti[\"put\"];\n get: Graffiti[\"get\"];\n patch: Graffiti[\"patch\"];\n delete: Graffiti[\"delete\"];\n discover: Graffiti[\"discover\"];\n recoverOrphans: Graffiti[\"recoverOrphans\"];\n synchronizeGet: Graffiti[\"synchronizeGet\"];\n synchronizeDiscover: Graffiti[\"synchronizeDiscover\"];\n synchronizeRecoverOrphans: Graffiti[\"synchronizeRecoverOrphans\"];\n channelStats: Graffiti[\"channelStats\"];\n\n constructor(options?: GraffitiLocalOptions) {\n super();\n\n const sessionManagerLocal = new GraffitiLocalSessionManager();\n this.login = sessionManagerLocal.login.bind(sessionManagerLocal);\n this.logout = sessionManagerLocal.logout.bind(sessionManagerLocal);\n this.sessionEvents = sessionManagerLocal.sessionEvents;\n\n const ajv = new Ajv({ strict: false });\n const graffitiPouchDbBase = new GraffitiLocalDatabase(options, ajv);\n const graffitiSynchronize = new GraffitiSynchronize(\n graffitiPouchDbBase,\n ajv,\n );\n\n this.put = graffitiSynchronize.put.bind(graffitiSynchronize);\n this.get = graffitiSynchronize.get.bind(graffitiSynchronize);\n this.patch = graffitiSynchronize.patch.bind(graffitiSynchronize);\n this.delete = graffitiSynchronize.delete.bind(graffitiSynchronize);\n this.discover = graffitiSynchronize.discover.bind(graffitiSynchronize);\n this.recoverOrphans =\n graffitiSynchronize.recoverOrphans.bind(graffitiPouchDbBase);\n this.channelStats =\n graffitiPouchDbBase.channelStats.bind(graffitiPouchDbBase);\n this.synchronizeDiscover =\n graffitiSynchronize.synchronizeDiscover.bind(graffitiSynchronize);\n this.synchronizeGet =\n graffitiSynchronize.synchronizeGet.bind(graffitiSynchronize);\n this.synchronizeRecoverOrphans =\n graffitiSynchronize.synchronizeRecoverOrphans.bind(graffitiSynchronize);\n }\n}\n"],
5
+ "mappings": "0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,mBAAAE,IAAA,eAAAC,EAAAH,GAAA,IAAAI,EAAyB,gCACzBC,EAAgB,6BAChBC,EAA4C,gCAC5CC,EAGO,yBACPC,EAAoC,4BACpCC,EAA6C,0BAWtC,MAAMP,UAAsB,UAAS,CAC1C,cAAgB,gBAChB,cAAgB,gBAEhB,MACA,OACA,cACA,IACA,IACA,MACA,OACA,SACA,eACA,eACA,oBACA,0BACA,aAEA,YAAYQ,EAAgC,CAC1C,MAAM,EAEN,MAAMC,EAAsB,IAAI,8BAChC,KAAK,MAAQA,EAAoB,MAAM,KAAKA,CAAmB,EAC/D,KAAK,OAASA,EAAoB,OAAO,KAAKA,CAAmB,EACjE,KAAK,cAAgBA,EAAoB,cAEzC,MAAMC,EAAM,IAAI,EAAAC,QAAI,CAAE,OAAQ,EAAM,CAAC,EAC/BC,EAAsB,IAAI,wBAAsBJ,EAASE,CAAG,EAC5DG,EAAsB,IAAI,sBAC9BD,EACAF,CACF,EAEA,KAAK,IAAMG,EAAoB,IAAI,KAAKA,CAAmB,EAC3D,KAAK,IAAMA,EAAoB,IAAI,KAAKA,CAAmB,EAC3D,KAAK,MAAQA,EAAoB,MAAM,KAAKA,CAAmB,EAC/D,KAAK,OAASA,EAAoB,OAAO,KAAKA,CAAmB,EACjE,KAAK,SAAWA,EAAoB,SAAS,KAAKA,CAAmB,EACrE,KAAK,eACHA,EAAoB,eAAe,KAAKD,CAAmB,EAC7D,KAAK,aACHA,EAAoB,aAAa,KAAKA,CAAmB,EAC3D,KAAK,oBACHC,EAAoB,oBAAoB,KAAKA,CAAmB,EAClE,KAAK,eACHA,EAAoB,eAAe,KAAKA,CAAmB,EAC7D,KAAK,0BACHA,EAAoB,0BAA0B,KAAKA,CAAmB,CAC1E,CACF",
6
6
  "names": ["index_exports", "__export", "GraffitiLocal", "__toCommonJS", "import_api", "import_ajv_draft_04", "import_session_manager", "import_database", "import_synchronize", "import_utilities", "options", "sessionManagerLocal", "ajv", "Ajv", "graffitiPouchDbBase", "graffitiSynchronize"]
7
7
  }
@@ -1,2 +1,2 @@
1
- "use strict";var G=Object.create;var h=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var g=Object.getPrototypeOf,S=Object.prototype.hasOwnProperty;var E=(n,t)=>{for(var e in t)h(n,e,{get:t[e],enumerable:!0})},y=(n,t,e,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of w(t))!S.call(n,i)&&i!==e&&h(n,i,{get:()=>t[i],enumerable:!(r=z(t,i))||r.enumerable});return n};var D=(n,t,e)=>(e=n!=null?G(g(n)):{},y(t||!n||!n.__esModule?h(e,"default",{value:n,enumerable:!0}):e,n)),B=n=>y(h({},"__esModule",{value:!0}),n);var k={};E(k,{GraffitiSynchronize:()=>R});module.exports=B(k);var d=D(require("ajv-draft-04"),1),b=require("@repeaterjs/repeater"),u=require("fast-json-patch"),a=require("./utilities");class R{synchronizeEvents=new EventTarget;ajv;graffiti;constructor(t,e){this.ajv=e??new d.default({strict:!1}),this.graffiti=t}synchronizeDispatch(t,e){const r=new CustomEvent("change",{detail:{oldObject:t,newObject:e}});this.synchronizeEvents.dispatchEvent(r)}get=async(...t)=>{const e=await this.graffiti.get(...t);return this.synchronizeDispatch(e),e};put=async(...t)=>{const e=await this.graffiti.put(...t),r=t[0],i={...e,value:r.value,channels:r.channels,allowed:r.allowed,tombstone:!1};return this.synchronizeDispatch(e,i),e};patch=async(...t)=>{const e=await this.graffiti.patch(...t),r={...e};r.tombstone=!1;for(const i of["value","channels","allowed"])(0,a.applyGraffitiPatch)(u.applyPatch,i,t[0],r);return this.synchronizeDispatch(e,r),e};delete=async(...t)=>{const e=await this.graffiti.delete(...t);return this.synchronizeDispatch(e),e};objectStream(t){const e=this.synchronizeDispatch.bind(this);return async function*(){let i=await t.next();for(;!i.done;)i.value.error||e(i.value.value),yield i.value,i=await t.next();return i.value}()}discover=(...t)=>{const e=this.graffiti.discover(...t);return this.objectStream(e)};recoverOrphans=(...t)=>{const e=this.graffiti.recoverOrphans(...t);return this.objectStream(e)};synchronize(t,e,r,i){const c=(0,a.attemptAjvCompile)(this.ajv,r);return new b.Repeater(async(s,p)=>{const v=j=>{const{oldObject:O,newObject:m}=j.detail;for(const f of[m,O])if(f&&t(f)&&(0,a.isActorAllowedGraffitiObject)(f,i)){const l={...f};if((0,a.maskGraffitiObject)(l,e,i),c(l)){s({value:l});break}}};this.synchronizeEvents.addEventListener("change",v),await p,this.synchronizeEvents.removeEventListener("change",v)})}synchronizeDiscover=(...t)=>{const[e,r,i]=t;function c(o){return o.channels.some(s=>e.includes(s))}return this.synchronize(c,e,r,i)};synchronizeGet=(...t)=>{const[e,r,i]=t;function c(o){const s=(0,a.locationToUri)(o),{uri:p}=(0,a.unpackLocationOrUri)(e);return s===p}return this.synchronize(c,[],r,i)};synchronizeRecoverOrphans=(...t)=>{const[e,r]=t;function i(c){return c.actor===r.actor&&c.channels.length===0}return this.synchronize(i,[],e,r)}}
1
+ "use strict";var G=Object.create;var h=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var g=Object.getPrototypeOf,S=Object.prototype.hasOwnProperty;var E=(n,t)=>{for(var e in t)h(n,e,{get:t[e],enumerable:!0})},y=(n,t,e,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of w(t))!S.call(n,i)&&i!==e&&h(n,i,{get:()=>t[i],enumerable:!(r=z(t,i))||r.enumerable});return n};var D=(n,t,e)=>(e=n!=null?G(g(n)):{},y(t||!n||!n.__esModule?h(e,"default",{value:n,enumerable:!0}):e,n)),B=n=>y(h({},"__esModule",{value:!0}),n);var k={};E(k,{GraffitiSynchronize:()=>R});module.exports=B(k);var d=D(require("ajv-draft-04"),1),b=require("@repeaterjs/repeater"),u=require("fast-json-patch"),a=require("./utilities.js");class R{synchronizeEvents=new EventTarget;ajv;graffiti;constructor(t,e){this.ajv=e??new d.default({strict:!1}),this.graffiti=t}synchronizeDispatch(t,e){const r=new CustomEvent("change",{detail:{oldObject:t,newObject:e}});this.synchronizeEvents.dispatchEvent(r)}get=async(...t)=>{const e=await this.graffiti.get(...t);return this.synchronizeDispatch(e),e};put=async(...t)=>{const e=await this.graffiti.put(...t),r=t[0],i={...e,value:r.value,channels:r.channels,allowed:r.allowed,tombstone:!1};return this.synchronizeDispatch(e,i),e};patch=async(...t)=>{const e=await this.graffiti.patch(...t),r={...e};r.tombstone=!1;for(const i of["value","channels","allowed"])(0,a.applyGraffitiPatch)(u.applyPatch,i,t[0],r);return this.synchronizeDispatch(e,r),e};delete=async(...t)=>{const e=await this.graffiti.delete(...t);return this.synchronizeDispatch(e),e};objectStream(t){const e=this.synchronizeDispatch.bind(this);return async function*(){let i=await t.next();for(;!i.done;)i.value.error||e(i.value.value),yield i.value,i=await t.next();return i.value}()}discover=(...t)=>{const e=this.graffiti.discover(...t);return this.objectStream(e)};recoverOrphans=(...t)=>{const e=this.graffiti.recoverOrphans(...t);return this.objectStream(e)};synchronize(t,e,r,i){const c=(0,a.attemptAjvCompile)(this.ajv,r);return new b.Repeater(async(s,p)=>{const v=j=>{const{oldObject:O,newObject:m}=j.detail;for(const f of[m,O])if(f&&t(f)&&(0,a.isActorAllowedGraffitiObject)(f,i)){const l={...f};if((0,a.maskGraffitiObject)(l,e,i),c(l)){s({value:l});break}}};this.synchronizeEvents.addEventListener("change",v),await p,this.synchronizeEvents.removeEventListener("change",v)})}synchronizeDiscover=(...t)=>{const[e,r,i]=t;function c(o){return o.channels.some(s=>e.includes(s))}return this.synchronize(c,e,r,i)};synchronizeGet=(...t)=>{const[e,r,i]=t;function c(o){const s=(0,a.locationToUri)(o),{uri:p}=(0,a.unpackLocationOrUri)(e);return s===p}return this.synchronize(c,[],r,i)};synchronizeRecoverOrphans=(...t)=>{const[e,r]=t;function i(c){return c.actor===r.actor&&c.channels.length===0}return this.synchronize(i,[],e,r)}}
2
2
  //# sourceMappingURL=synchronize.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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\";\n\ntype SynchronizeEvent = CustomEvent<{\n oldObject: GraffitiObjectBase;\n newObject?: GraffitiObjectBase;\n}>;\n\ntype GraffitiDatabaseMethods = Pick<\n Graffiti,\n \"get\" | \"put\" | \"patch\" | \"delete\" | \"discover\" | \"recoverOrphans\"\n>;\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 synchronizeEvents = new EventTarget();\n protected readonly ajv: Ajv;\n protected readonly graffiti: GraffitiDatabaseMethods;\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 synchronizeDispatch(\n oldObject: GraffitiObjectBase,\n newObject?: GraffitiObjectBase,\n ) {\n const event: SynchronizeEvent = new CustomEvent(\"change\", {\n detail: {\n oldObject,\n newObject,\n },\n });\n this.synchronizeEvents.dispatchEvent(event);\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 this.synchronizeDispatch(oldObject, newObject);\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 this.synchronizeDispatch(oldObject, newObject);\n return oldObject;\n };\n\n delete: Graffiti[\"delete\"] = async (...args) => {\n const oldObject = await this.graffiti.delete(...args);\n this.synchronizeDispatch(oldObject);\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 = (event: SynchronizeEvent) => {\n const { oldObject: oldObjectRaw, newObject: newObjectRaw } =\n event.detail;\n\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.synchronizeEvents.addEventListener(\n \"change\",\n callback as EventListener,\n );\n await stop;\n this.synchronizeEvents.removeEventListener(\n \"change\",\n callback as EventListener,\n );\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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",
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 SynchronizeEvent = CustomEvent<{\n oldObject: GraffitiObjectBase;\n newObject?: GraffitiObjectBase;\n}>;\n\ntype GraffitiDatabaseMethods = Pick<\n Graffiti,\n \"get\" | \"put\" | \"patch\" | \"delete\" | \"discover\" | \"recoverOrphans\"\n>;\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 synchronizeEvents = new EventTarget();\n protected readonly ajv: Ajv;\n protected readonly graffiti: GraffitiDatabaseMethods;\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 synchronizeDispatch(\n oldObject: GraffitiObjectBase,\n newObject?: GraffitiObjectBase,\n ) {\n const event: SynchronizeEvent = new CustomEvent(\"change\", {\n detail: {\n oldObject,\n newObject,\n },\n });\n this.synchronizeEvents.dispatchEvent(event);\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 this.synchronizeDispatch(oldObject, newObject);\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 this.synchronizeDispatch(oldObject, newObject);\n return oldObject;\n };\n\n delete: Graffiti[\"delete\"] = async (...args) => {\n const oldObject = await this.graffiti.delete(...args);\n this.synchronizeDispatch(oldObject);\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 = (event: SynchronizeEvent) => {\n const { oldObject: oldObjectRaw, newObject: newObjectRaw } =\n event.detail;\n\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.synchronizeEvents.addEventListener(\n \"change\",\n callback as EventListener,\n );\n await stop;\n this.synchronizeEvents.removeEventListener(\n \"change\",\n callback as EventListener,\n );\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
6
  "names": ["synchronize_exports", "__export", "GraffitiSynchronize", "__toCommonJS", "import_ajv_draft_04", "import_repeater", "import_fast_json_patch", "import_utilities", "graffiti", "ajv", "Ajv", "oldObject", "newObject", "event", "args", "object", "partialObject", "prop", "iterator", "dispatch", "result", "matchObject", "channels", "schema", "session", "validate", "push", "stop", "callback", "oldObjectRaw", "newObjectRaw", "objectRaw", "channel", "locationOrUri", "objectUri", "uri"]
7
7
  }
@@ -1,2 +1,2 @@
1
- "use strict";var s=require("@graffiti-garden/api/tests"),o=require("./index");const t=()=>new o.GraffitiLocal,i=()=>({actor:"someone"}),e=()=>({actor:"someoneelse"});(0,s.graffitiLocationTests)(t);(0,s.graffitiCRUDTests)(t,i,e);(0,s.graffitiSynchronizeTests)(t,i,e);(0,s.graffitiDiscoverTests)(t,i,e);(0,s.graffitiOrphanTests)(t,i,e);(0,s.graffitiChannelStatsTests)(t,i,e);
1
+ "use strict";var s=require("@graffiti-garden/api/tests"),o=require("./index");const t=()=>new o.GraffitiLocal,i=()=>({actor:"someone"}),e=()=>({actor:"someoneelse"});(0,s.graffitiLocationTests)(t),(0,s.graffitiCRUDTests)(t,i,e),(0,s.graffitiSynchronizeTests)(t,i,e),(0,s.graffitiDiscoverTests)(t,i,e),(0,s.graffitiOrphanTests)(t,i,e),(0,s.graffitiChannelStatsTests)(t,i,e);
2
2
  //# sourceMappingURL=tests.spec.js.map
@@ -1,2 +1,2 @@
1
- import{GraffitiErrorNotFound as g,GraffitiErrorSchemaMismatch as B,GraffitiErrorForbidden as y,GraffitiErrorPatchError as G}from"@graffiti-garden/api";import R from"pouchdb";import{locationToUri as M,unpackLocationOrUri as j,randomBase64 as x,applyGraffitiPatch as _,attemptAjvCompile as O,maskGraffitiObject as D,isActorAllowedGraffitiObject as L,isObjectNewer as P}from"./utilities";import{Repeater as A}from"@repeaterjs/repeater";import k from"ajv-draft-04";import{applyPatch as C}from"fast-json-patch";class J{db;source="local";tombstoneRetention=864e5;ajv;constructor(n,o){this.ajv=o??new k({strict:!1}),this.source=n?.sourceName??this.source,this.tombstoneRetention=n?.tombstoneRetention??this.tombstoneRetention;const r={name:"graffitiDb",...n?.pouchDBOptions};this.db=new R(r.name,r),this.db.put({_id:"_design/indexes",views:{objectsPerChannelAndLastModified:{map:function(t){const e=t.lastModified.toString().padStart(15,"0");t.channels.forEach(function(s){const i=encodeURIComponent(s)+"/"+e;emit(i)})}.toString()},orphansPerActorAndLastModified:{map:function(t){if(t.channels.length===0){const e=t.lastModified.toString().padStart(15,"0"),s=encodeURIComponent(t.actor)+"/"+e;emit(s)}}.toString()},channelStatsPerActor:{map:function(t){t.tombstone||t.channels.forEach(function(e){const s=encodeURIComponent(t.actor)+"/"+encodeURIComponent(e);emit(s,t.lastModified)})}.toString(),reduce:"_stats"}}}).catch(t=>{if(!(t&&typeof t=="object"&&"name"in t&&t.name==="conflict"))throw t})}async queryByLocation(n){const o=M(n)+"/";return(await this.db.allDocs({startkey:o,endkey:o+"\uFFFF",include_docs:!0})).rows.map(e=>e.doc).reduce((e,s)=>(s&&e.push(s),e),[])}docId(n){return M(n)+"/"+x()}get=async(...n)=>{const[o,r,t]=n,{location:e}=j(o),i=(await this.queryByLocation(e)).filter(c=>L(c,t));if(!i.length)throw new g;const f=i.reduce((c,d)=>P(c,d)?c:d),{_id:h,_rev:u,...a}=f;if(D(a,[],t),!O(this.ajv,r)(a))throw new B;return a};async deleteAtLocation(n,o=!1){const t=(await this.queryByLocation(n)).filter(a=>!a.tombstone);if(!t.length)return;const e=t.map(a=>a.lastModified).reduce((a,l)=>a>l?a:l),s=t.filter(a=>!o||a.lastModified<e),i=t.filter(a=>o&&a.lastModified===e);if(i.length){const a=i.map(c=>c._id).reduce((c,d)=>c>d?c:d),l=i.filter(c=>c._id!==a);s.push(...l)}const f=o?e:new Date().getTime(),h=await this.db.bulkDocs(s.map(a=>({...a,tombstone:!0,lastModified:f})));let u;for(const a of h)if("ok"in a){const{id:l}=a,c=s.find(d=>d._id===l);if(c){const{_id:d,_rev:b,_conflicts:w,_attachments:m,...p}=c;u={...p,tombstone:!0,lastModified:f};break}}return u}delete=async(...n)=>{const[o,r]=n,{location:t}=j(o);if(t.actor!==r.actor)throw new y;const e=await this.deleteAtLocation(t);if(!e)throw new g;return e};put=async(...n)=>{const[o,r]=n;if(o.actor&&o.actor!==r.actor)throw new y;if(o.source&&o.source!==this.source)throw new y("Putting an object that does not match this source");const t={value:o.value,channels:o.channels,allowed:o.allowed,name:o.name??x(),source:this.source,actor:r.actor,tombstone:!1,lastModified:new Date().getTime()};await this.db.put({_id:this.docId(t),...t});const e=await this.deleteAtLocation(t,!0);return e||{...t,value:{},channels:[],allowed:void 0,tombstone:!0}};patch=async(...n)=>{const[o,r,t]=n,{location:e}=j(r);if(e.actor!==t.actor)throw new y;const s=await this.get(r,{},t);if(s.tombstone)throw new g("The object you are trying to patch has been deleted");const i={...s};for(const f of["value","channels","allowed"])_(C,f,o,i);if(typeof i.value!="object"||Array.isArray(i.value)||!i.value)throw new G("value is no longer an object");if(!Array.isArray(i.channels)||!i.channels.every(f=>typeof f=="string"))throw new G("channels are no longer an array of strings");if(i.allowed&&(!Array.isArray(i.allowed)||!i.allowed.every(f=>typeof f=="string")))throw new G("allowed list is not an array of strings");return i.lastModified=new Date().getTime(),await this.db.put({...i,_id:this.docId(i)}),await this.deleteAtLocation(i,!0),{...s,tombstone:!0,lastModified:i.lastModified}};queryLastModifiedSuffixes(n){let o="",r="\uFFFF";const t=n.properties?.lastModified;if(t?.minimum){let e=Math.ceil(t.minimum);e===t.minimum&&t.exclusiveMinimum&&e++,o=e.toString().padStart(15,"0")}if(t?.maximum){let e=Math.floor(t.maximum);e===t.maximum&&t.exclusiveMaximum&&e--,r=e.toString().padStart(15,"0")}return{startKeySuffix:o,endKeySuffix:r}}discover=(...n)=>{const[o,r,t]=n,e=O(this.ajv,r),{startKeySuffix:s,endKeySuffix:i}=this.queryLastModifiedSuffixes(r);return new A(async(h,u)=>{const a=new Set;for(const l of o){const c=encodeURIComponent(l)+"/",d=c+s,b=c+i,w=await this.db.query("indexes/objectsPerChannelAndLastModified",{startkey:d,endkey:b,include_docs:!0});for(const m of w.rows){const p=m.doc;if(!p)continue;const{_id:S,_rev:U,...v}=p;a.has(S)||(a.add(S),L(p,t)&&(D(v,o,t),e(v)&&await h({value:v})))}}return u(),{tombstoneRetention:this.tombstoneRetention}})};recoverOrphans=(n,o)=>{const r=O(this.ajv,n),{startKeySuffix:t,endKeySuffix:e}=this.queryLastModifiedSuffixes(n),s=encodeURIComponent(o.actor)+"/",i=s+t,f=s+e;return new A(async(u,a)=>{const l=await this.db.query("indexes/orphansPerActorAndLastModified",{startkey:i,endkey:f,include_docs:!0});for(const c of l.rows){const d=c.doc;if(!d)continue;const{_id:b,_rev:w,...m}=d;r(m)&&await u({value:m})}return a(),{tombstoneRetention:this.tombstoneRetention}})};channelStats=n=>new A(async(r,t)=>{const e=encodeURIComponent(n.actor)+"/",s=await this.db.query("indexes/channelStatsPerActor",{startkey:e,endkey:e+"\uFFFF",reduce:!0,group:!0});for(const i of s.rows){const f=i.key.split("/")[1];if(typeof f!="string")continue;const{count:h,max:u}=i.value;typeof h!="number"||typeof u!="number"||await r({value:{channel:decodeURIComponent(f),count:h,lastModified:u}})}t()})}export{J as GraffitiLocalDatabase};
1
+ import{GraffitiErrorNotFound as g,GraffitiErrorSchemaMismatch as B,GraffitiErrorForbidden as y,GraffitiErrorPatchError as G}from"@graffiti-garden/api";import R from"pouchdb";import{locationToUri as M,unpackLocationOrUri as j,randomBase64 as x,applyGraffitiPatch as _,attemptAjvCompile as O,maskGraffitiObject as D,isActorAllowedGraffitiObject as L,isObjectNewer as P}from"./utilities.js";import{Repeater as A}from"@repeaterjs/repeater";import k from"ajv-draft-04";import{applyPatch as C}from"fast-json-patch";class J{db;source="local";tombstoneRetention=864e5;ajv;constructor(n,o){this.ajv=o??new k({strict:!1}),this.source=n?.sourceName??this.source,this.tombstoneRetention=n?.tombstoneRetention??this.tombstoneRetention;const r={name:"graffitiDb",...n?.pouchDBOptions};this.db=new R(r.name,r),this.db.put({_id:"_design/indexes",views:{objectsPerChannelAndLastModified:{map:function(t){const e=t.lastModified.toString().padStart(15,"0");t.channels.forEach(function(s){const i=encodeURIComponent(s)+"/"+e;emit(i)})}.toString()},orphansPerActorAndLastModified:{map:function(t){if(t.channels.length===0){const e=t.lastModified.toString().padStart(15,"0"),s=encodeURIComponent(t.actor)+"/"+e;emit(s)}}.toString()},channelStatsPerActor:{map:function(t){t.tombstone||t.channels.forEach(function(e){const s=encodeURIComponent(t.actor)+"/"+encodeURIComponent(e);emit(s,t.lastModified)})}.toString(),reduce:"_stats"}}}).catch(t=>{if(!(t&&typeof t=="object"&&"name"in t&&t.name==="conflict"))throw t})}async queryByLocation(n){const o=M(n)+"/";return(await this.db.allDocs({startkey:o,endkey:o+"\uFFFF",include_docs:!0})).rows.map(e=>e.doc).reduce((e,s)=>(s&&e.push(s),e),[])}docId(n){return M(n)+"/"+x()}get=async(...n)=>{const[o,r,t]=n,{location:e}=j(o),i=(await this.queryByLocation(e)).filter(c=>L(c,t));if(!i.length)throw new g;const f=i.reduce((c,d)=>P(c,d)?c:d),{_id:h,_rev:u,...a}=f;if(D(a,[],t),!O(this.ajv,r)(a))throw new B;return a};async deleteAtLocation(n,o=!1){const t=(await this.queryByLocation(n)).filter(a=>!a.tombstone);if(!t.length)return;const e=t.map(a=>a.lastModified).reduce((a,l)=>a>l?a:l),s=t.filter(a=>!o||a.lastModified<e),i=t.filter(a=>o&&a.lastModified===e);if(i.length){const a=i.map(c=>c._id).reduce((c,d)=>c>d?c:d),l=i.filter(c=>c._id!==a);s.push(...l)}const f=o?e:new Date().getTime(),h=await this.db.bulkDocs(s.map(a=>({...a,tombstone:!0,lastModified:f})));let u;for(const a of h)if("ok"in a){const{id:l}=a,c=s.find(d=>d._id===l);if(c){const{_id:d,_rev:b,_conflicts:w,_attachments:m,...p}=c;u={...p,tombstone:!0,lastModified:f};break}}return u}delete=async(...n)=>{const[o,r]=n,{location:t}=j(o);if(t.actor!==r.actor)throw new y;const e=await this.deleteAtLocation(t);if(!e)throw new g;return e};put=async(...n)=>{const[o,r]=n;if(o.actor&&o.actor!==r.actor)throw new y;if(o.source&&o.source!==this.source)throw new y("Putting an object that does not match this source");const t={value:o.value,channels:o.channels,allowed:o.allowed,name:o.name??x(),source:this.source,actor:r.actor,tombstone:!1,lastModified:new Date().getTime()};await this.db.put({_id:this.docId(t),...t});const e=await this.deleteAtLocation(t,!0);return e||{...t,value:{},channels:[],allowed:void 0,tombstone:!0}};patch=async(...n)=>{const[o,r,t]=n,{location:e}=j(r);if(e.actor!==t.actor)throw new y;const s=await this.get(r,{},t);if(s.tombstone)throw new g("The object you are trying to patch has been deleted");const i={...s};for(const f of["value","channels","allowed"])_(C,f,o,i);if(typeof i.value!="object"||Array.isArray(i.value)||!i.value)throw new G("value is no longer an object");if(!Array.isArray(i.channels)||!i.channels.every(f=>typeof f=="string"))throw new G("channels are no longer an array of strings");if(i.allowed&&(!Array.isArray(i.allowed)||!i.allowed.every(f=>typeof f=="string")))throw new G("allowed list is not an array of strings");return i.lastModified=new Date().getTime(),await this.db.put({...i,_id:this.docId(i)}),await this.deleteAtLocation(i,!0),{...s,tombstone:!0,lastModified:i.lastModified}};queryLastModifiedSuffixes(n){let o="",r="\uFFFF";const t=n.properties?.lastModified;if(t?.minimum){let e=Math.ceil(t.minimum);e===t.minimum&&t.exclusiveMinimum&&e++,o=e.toString().padStart(15,"0")}if(t?.maximum){let e=Math.floor(t.maximum);e===t.maximum&&t.exclusiveMaximum&&e--,r=e.toString().padStart(15,"0")}return{startKeySuffix:o,endKeySuffix:r}}discover=(...n)=>{const[o,r,t]=n,e=O(this.ajv,r),{startKeySuffix:s,endKeySuffix:i}=this.queryLastModifiedSuffixes(r);return new A(async(h,u)=>{const a=new Set;for(const l of o){const c=encodeURIComponent(l)+"/",d=c+s,b=c+i,w=await this.db.query("indexes/objectsPerChannelAndLastModified",{startkey:d,endkey:b,include_docs:!0});for(const m of w.rows){const p=m.doc;if(!p)continue;const{_id:S,_rev:U,...v}=p;a.has(S)||(a.add(S),L(p,t)&&(D(v,o,t),e(v)&&await h({value:v})))}}return u(),{tombstoneRetention:this.tombstoneRetention}})};recoverOrphans=(n,o)=>{const r=O(this.ajv,n),{startKeySuffix:t,endKeySuffix:e}=this.queryLastModifiedSuffixes(n),s=encodeURIComponent(o.actor)+"/",i=s+t,f=s+e;return new A(async(u,a)=>{const l=await this.db.query("indexes/orphansPerActorAndLastModified",{startkey:i,endkey:f,include_docs:!0});for(const c of l.rows){const d=c.doc;if(!d)continue;const{_id:b,_rev:w,...m}=d;r(m)&&await u({value:m})}return a(),{tombstoneRetention:this.tombstoneRetention}})};channelStats=n=>new A(async(r,t)=>{const e=encodeURIComponent(n.actor)+"/",s=await this.db.query("indexes/channelStatsPerActor",{startkey:e,endkey:e+"\uFFFF",reduce:!0,group:!0});for(const i of s.rows){const f=i.key.split("/")[1];if(typeof f!="string")continue;const{count:h,max:u}=i.value;typeof h!="number"||typeof u!="number"||await r({value:{channel:decodeURIComponent(f),count:h,lastModified:u}})}t()})}export{J as GraffitiLocalDatabase};
2
2
  //# sourceMappingURL=database.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/database.ts"],
4
- "sourcesContent": ["import type {\n Graffiti,\n GraffitiObjectBase,\n GraffitiLocation,\n JSONSchema4,\n} from \"@graffiti-garden/api\";\nimport {\n GraffitiErrorNotFound,\n GraffitiErrorSchemaMismatch,\n GraffitiErrorForbidden,\n GraffitiErrorPatchError,\n} from \"@graffiti-garden/api\";\nimport PouchDB from \"pouchdb\";\nimport {\n locationToUri,\n unpackLocationOrUri,\n randomBase64,\n applyGraffitiPatch,\n attemptAjvCompile,\n maskGraffitiObject,\n isActorAllowedGraffitiObject,\n isObjectNewer,\n} from \"./utilities\";\nimport { Repeater } from \"@repeaterjs/repeater\";\nimport Ajv from \"ajv-draft-04\";\nimport { applyPatch } from \"fast-json-patch\";\n\n/**\n * Constructor options for the GraffitiPoubchDB class.\n */\nexport interface GraffitiLocalOptions {\n /**\n * Options to pass to the PouchDB constructor.\n * Defaults to `{ name: \"graffitiDb\" }`.\n *\n * See the [PouchDB documentation](https://pouchdb.com/api.html#create_database)\n * for available options.\n */\n pouchDBOptions?: PouchDB.Configuration.DatabaseConfiguration;\n /**\n * Defines the name of the {@link https://api.graffiti.garden/interfaces/GraffitiObjectBase.html#source | `source` }\n * under which to store objects.\n * Defaults to `\"local\"`.\n */\n sourceName?: string;\n /**\n * The time in milliseconds to keep tombstones before deleting them.\n * See the {@link https://api.graffiti.garden/classes/Graffiti.html#discover | `discover` }\n * documentation for more information.\n */\n tombstoneRetention?: number;\n}\n\n/**\n * An implementation of only the database operations of the\n * GraffitiAPI without synchronization or session management.\n */\nexport class GraffitiLocalDatabase\n implements\n Pick<\n Graffiti,\n | \"get\"\n | \"put\"\n | \"patch\"\n | \"delete\"\n | \"discover\"\n | \"recoverOrphans\"\n | \"channelStats\"\n >\n{\n protected readonly db: PouchDB.Database<GraffitiObjectBase>;\n protected readonly source: string = \"local\";\n protected readonly tombstoneRetention: number = 86400000; // 1 day in ms\n protected readonly ajv: Ajv;\n\n constructor(options?: GraffitiLocalOptions, ajv?: Ajv) {\n this.ajv = ajv ?? new Ajv({ strict: false });\n this.source = options?.sourceName ?? this.source;\n this.tombstoneRetention =\n options?.tombstoneRetention ?? this.tombstoneRetention;\n const pouchDbOptions = {\n name: \"graffitiDb\",\n ...options?.pouchDBOptions,\n };\n this.db = new PouchDB<GraffitiObjectBase>(\n pouchDbOptions.name,\n pouchDbOptions,\n );\n\n this.db\n //@ts-ignore\n .put({\n _id: \"_design/indexes\",\n views: {\n objectsPerChannelAndLastModified: {\n map: function (object: GraffitiObjectBase) {\n const paddedLastModified = object.lastModified\n .toString()\n .padStart(15, \"0\");\n object.channels.forEach(function (channel) {\n const id =\n encodeURIComponent(channel) + \"/\" + paddedLastModified;\n //@ts-ignore\n emit(id);\n });\n }.toString(),\n },\n orphansPerActorAndLastModified: {\n map: function (object: GraffitiObjectBase) {\n if (object.channels.length === 0) {\n const paddedLastModified = object.lastModified\n .toString()\n .padStart(15, \"0\");\n const id =\n encodeURIComponent(object.actor) + \"/\" + paddedLastModified;\n //@ts-ignore\n emit(id);\n }\n }.toString(),\n },\n channelStatsPerActor: {\n map: function (object: GraffitiObjectBase) {\n if (object.tombstone) return;\n object.channels.forEach(function (channel) {\n const id =\n encodeURIComponent(object.actor) +\n \"/\" +\n encodeURIComponent(channel);\n //@ts-ignore\n emit(id, object.lastModified);\n });\n }.toString(),\n reduce: \"_stats\",\n },\n },\n })\n //@ts-ignore\n .catch((error) => {\n if (\n error &&\n typeof error === \"object\" &&\n \"name\" in error &&\n error.name === \"conflict\"\n ) {\n // Design document already exists\n return;\n } else {\n throw error;\n }\n });\n }\n\n protected async queryByLocation(location: GraffitiLocation) {\n const uri = locationToUri(location) + \"/\";\n const results = await this.db.allDocs({\n startkey: uri,\n endkey: uri + \"\\uffff\", // \\uffff is the last unicode character\n include_docs: true,\n });\n const docs = results.rows\n .map((row) => row.doc)\n // Remove undefined docs\n .reduce<\n PouchDB.Core.ExistingDocument<\n GraffitiObjectBase & PouchDB.Core.AllDocsMeta\n >[]\n >((acc, doc) => {\n if (doc) acc.push(doc);\n return acc;\n }, []);\n return docs;\n }\n\n protected docId(location: GraffitiLocation) {\n return locationToUri(location) + \"/\" + randomBase64();\n }\n\n get: Graffiti[\"get\"] = async (...args) => {\n const [locationOrUri, schema, session] = args;\n const { location } = unpackLocationOrUri(locationOrUri);\n\n const docsAll = await this.queryByLocation(location);\n\n // Filter out ones not allowed\n const docs = docsAll.filter((doc) =>\n isActorAllowedGraffitiObject(doc, session),\n );\n if (!docs.length) throw new GraffitiErrorNotFound();\n\n // Get the most recent document\n const doc = docs.reduce((a, b) => (isObjectNewer(a, b) ? a : b));\n\n // Strip out the _id and _rev\n const { _id, _rev, ...object } = doc;\n\n // Mask out the allowed list and channels\n // if the user is not the owner\n maskGraffitiObject(object, [], session);\n\n const validate = attemptAjvCompile(this.ajv, schema);\n if (!validate(object)) {\n throw new GraffitiErrorSchemaMismatch();\n }\n return object;\n };\n\n /**\n * Deletes all docs at a particular location.\n * If the `keepLatest` flag is set to true,\n * the doc with the most recent timestamp will be\n * spared. If there are multiple docs with the same\n * timestamp, the one with the highest `_id` will be\n * spared.\n */\n protected async deleteAtLocation(\n location: GraffitiLocation,\n keepLatest: boolean = false,\n ) {\n const docsAtLocationAll = await this.queryByLocation(location);\n const docsAtLocation = docsAtLocationAll.filter((doc) => !doc.tombstone);\n if (!docsAtLocation.length) return undefined;\n\n // Get the most recent lastModified timestamp.\n const latestModified = docsAtLocation\n .map((doc) => doc.lastModified)\n .reduce((a, b) => (a > b ? a : b));\n\n // Delete all old docs\n const docsToDelete = docsAtLocation.filter(\n (doc) => !keepLatest || doc.lastModified < latestModified,\n );\n\n // For docs with the same timestamp,\n // keep the one with the highest _id\n // to break concurrency ties\n const concurrentDocsAll = docsAtLocation.filter(\n (doc) => keepLatest && doc.lastModified === latestModified,\n );\n if (concurrentDocsAll.length) {\n const keepDocId = concurrentDocsAll\n .map((doc) => doc._id)\n .reduce((a, b) => (a > b ? a : b));\n const concurrentDocsToDelete = concurrentDocsAll.filter(\n (doc) => doc._id !== keepDocId,\n );\n docsToDelete.push(...concurrentDocsToDelete);\n }\n\n const lastModified = keepLatest ? latestModified : new Date().getTime();\n\n const deleteResults = await this.db.bulkDocs<GraffitiObjectBase>(\n docsToDelete.map((doc) => ({\n ...doc,\n tombstone: true,\n lastModified,\n })),\n );\n\n // Get one of the docs that was deleted\n let deletedObject: GraffitiObjectBase | undefined = undefined;\n for (const resultOrError of deleteResults) {\n if (\"ok\" in resultOrError) {\n const { id } = resultOrError;\n const deletedDoc = docsToDelete.find((doc) => doc._id === id);\n if (deletedDoc) {\n const { _id, _rev, _conflicts, _attachments, ...object } = deletedDoc;\n deletedObject = {\n ...object,\n tombstone: true,\n lastModified,\n };\n break;\n }\n }\n }\n\n return deletedObject;\n }\n\n delete: Graffiti[\"delete\"] = async (...args) => {\n const [locationOrUri, session] = args;\n const { location } = unpackLocationOrUri(locationOrUri);\n if (location.actor !== session.actor) {\n throw new GraffitiErrorForbidden();\n }\n\n const deletedObject = await this.deleteAtLocation(location);\n if (!deletedObject) {\n throw new GraffitiErrorNotFound();\n }\n return deletedObject;\n };\n\n put: Graffiti[\"put\"] = async (...args) => {\n const [objectPartial, session] = args;\n if (objectPartial.actor && objectPartial.actor !== session.actor) {\n throw new GraffitiErrorForbidden();\n }\n if (objectPartial.source && objectPartial.source !== this.source) {\n throw new GraffitiErrorForbidden(\n \"Putting an object that does not match this source\",\n );\n }\n\n const object: GraffitiObjectBase = {\n value: objectPartial.value,\n channels: objectPartial.channels,\n allowed: objectPartial.allowed,\n name: objectPartial.name ?? randomBase64(),\n source: this.source,\n actor: session.actor,\n tombstone: false,\n lastModified: new Date().getTime(),\n };\n\n await this.db.put({\n _id: this.docId(object),\n ...object,\n });\n\n // Delete the old object\n const previousObject = await this.deleteAtLocation(object, true);\n if (previousObject) {\n return previousObject;\n } else {\n return {\n ...object,\n value: {},\n channels: [],\n allowed: undefined,\n tombstone: true,\n };\n }\n };\n\n patch: Graffiti[\"patch\"] = async (...args) => {\n const [patch, locationOrUri, session] = args;\n const { location } = unpackLocationOrUri(locationOrUri);\n if (location.actor !== session.actor) {\n throw new GraffitiErrorForbidden();\n }\n const originalObject = await this.get(locationOrUri, {}, session);\n if (originalObject.tombstone) {\n throw new GraffitiErrorNotFound(\n \"The object you are trying to patch has been deleted\",\n );\n }\n\n // Patch it outside of the database\n const patchObject: GraffitiObjectBase = { ...originalObject };\n for (const prop of [\"value\", \"channels\", \"allowed\"] as const) {\n applyGraffitiPatch(applyPatch, prop, patch, patchObject);\n }\n\n // Make sure the value is an object\n if (\n typeof patchObject.value !== \"object\" ||\n Array.isArray(patchObject.value) ||\n !patchObject.value\n ) {\n throw new GraffitiErrorPatchError(\"value is no longer an object\");\n }\n\n // Make sure the channels are an array of strings\n if (\n !Array.isArray(patchObject.channels) ||\n !patchObject.channels.every((channel) => typeof channel === \"string\")\n ) {\n throw new GraffitiErrorPatchError(\n \"channels are no longer an array of strings\",\n );\n }\n\n // Make sure the allowed list is an array of strings or undefined\n if (\n patchObject.allowed &&\n (!Array.isArray(patchObject.allowed) ||\n !patchObject.allowed.every((allowed) => typeof allowed === \"string\"))\n ) {\n throw new GraffitiErrorPatchError(\n \"allowed list is not an array of strings\",\n );\n }\n\n patchObject.lastModified = new Date().getTime();\n await this.db.put({\n ...patchObject,\n _id: this.docId(patchObject),\n });\n\n // Delete the old object\n await this.deleteAtLocation(patchObject, true);\n\n return {\n ...originalObject,\n tombstone: true,\n lastModified: patchObject.lastModified,\n };\n };\n\n protected queryLastModifiedSuffixes(schema: JSONSchema4) {\n // Use the index for queries over ranges of lastModified\n let startKeySuffix = \"\";\n let endKeySuffix = \"\\uffff\";\n const lastModifiedSchema = schema.properties?.lastModified;\n if (lastModifiedSchema?.minimum) {\n let minimum = Math.ceil(lastModifiedSchema.minimum);\n minimum === lastModifiedSchema.minimum &&\n lastModifiedSchema.exclusiveMinimum &&\n minimum++;\n startKeySuffix = minimum.toString().padStart(15, \"0\");\n }\n if (lastModifiedSchema?.maximum) {\n let maximum = Math.floor(lastModifiedSchema.maximum);\n maximum === lastModifiedSchema.maximum &&\n lastModifiedSchema.exclusiveMaximum &&\n maximum--;\n endKeySuffix = maximum.toString().padStart(15, \"0\");\n }\n return {\n startKeySuffix,\n endKeySuffix,\n };\n }\n\n discover: Graffiti[\"discover\"] = (...args) => {\n const [channels, schema, session] = args;\n const validate = attemptAjvCompile(this.ajv, schema);\n\n const { startKeySuffix, endKeySuffix } =\n this.queryLastModifiedSuffixes(schema);\n\n const repeater: ReturnType<\n typeof Graffiti.prototype.discover<typeof schema>\n > = new Repeater(async (push, stop) => {\n const processedIds = new Set<string>();\n\n for (const channel of channels) {\n const keyPrefix = encodeURIComponent(channel) + \"/\";\n const startkey = keyPrefix + startKeySuffix;\n const endkey = keyPrefix + endKeySuffix;\n\n const result = await this.db.query<GraffitiObjectBase>(\n \"indexes/objectsPerChannelAndLastModified\",\n { startkey, endkey, include_docs: true },\n );\n\n for (const row of result.rows) {\n const doc = row.doc;\n if (!doc) continue;\n\n const { _id, _rev, ...object } = doc;\n\n // Don't double return the same object\n // (which can happen if it's in multiple channels)\n if (processedIds.has(_id)) continue;\n processedIds.add(_id);\n\n // Make sure the user is allowed to see it\n if (!isActorAllowedGraffitiObject(doc, session)) continue;\n\n // Mask out the allowed list and channels\n // if the user is not the owner\n maskGraffitiObject(object, channels, session);\n\n // Check that it matches the schema\n if (validate(object)) {\n await push({ value: object });\n }\n }\n }\n stop();\n return {\n tombstoneRetention: this.tombstoneRetention,\n };\n });\n\n return repeater;\n };\n\n recoverOrphans: Graffiti[\"recoverOrphans\"] = (schema, session) => {\n const validate = attemptAjvCompile(this.ajv, schema);\n\n const { startKeySuffix, endKeySuffix } =\n this.queryLastModifiedSuffixes(schema);\n const keyPrefix = encodeURIComponent(session.actor) + \"/\";\n const startkey = keyPrefix + startKeySuffix;\n const endkey = keyPrefix + endKeySuffix;\n\n const repeater: ReturnType<\n typeof Graffiti.prototype.recoverOrphans<typeof schema>\n > = new Repeater(async (push, stop) => {\n const result = await this.db.query<GraffitiObjectBase>(\n \"indexes/orphansPerActorAndLastModified\",\n { startkey, endkey, include_docs: true },\n );\n\n for (const row of result.rows) {\n const doc = row.doc;\n if (!doc) continue;\n\n // No masking/access necessary because\n // the objects are all owned by the querier\n\n const { _id, _rev, ...object } = doc;\n if (validate(object)) {\n await push({ value: object });\n }\n }\n stop();\n return {\n tombstoneRetention: this.tombstoneRetention,\n };\n });\n\n return repeater;\n };\n\n channelStats: Graffiti[\"channelStats\"] = (session) => {\n const repeater: ReturnType<typeof Graffiti.prototype.channelStats> =\n new Repeater(async (push, stop) => {\n const keyPrefix = encodeURIComponent(session.actor) + \"/\";\n const result = await this.db.query(\"indexes/channelStatsPerActor\", {\n startkey: keyPrefix,\n endkey: keyPrefix + \"\\uffff\",\n reduce: true,\n group: true,\n });\n for (const row of result.rows) {\n const channelEncoded = row.key.split(\"/\")[1];\n if (typeof channelEncoded !== \"string\") continue;\n const { count, max: lastModified } = row.value;\n if (typeof count !== \"number\" || typeof lastModified !== \"number\")\n continue;\n await push({\n value: {\n channel: decodeURIComponent(channelEncoded),\n count,\n lastModified,\n },\n });\n }\n stop();\n });\n\n return repeater;\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type {\n Graffiti,\n GraffitiObjectBase,\n GraffitiLocation,\n JSONSchema4,\n} from \"@graffiti-garden/api\";\nimport {\n GraffitiErrorNotFound,\n GraffitiErrorSchemaMismatch,\n GraffitiErrorForbidden,\n GraffitiErrorPatchError,\n} from \"@graffiti-garden/api\";\nimport PouchDB from \"pouchdb\";\nimport {\n locationToUri,\n unpackLocationOrUri,\n randomBase64,\n applyGraffitiPatch,\n attemptAjvCompile,\n maskGraffitiObject,\n isActorAllowedGraffitiObject,\n isObjectNewer,\n} from \"./utilities.js\";\nimport { Repeater } from \"@repeaterjs/repeater\";\nimport Ajv from \"ajv-draft-04\";\nimport { applyPatch } from \"fast-json-patch\";\n\n/**\n * Constructor options for the GraffitiPoubchDB class.\n */\nexport interface GraffitiLocalOptions {\n /**\n * Options to pass to the PouchDB constructor.\n * Defaults to `{ name: \"graffitiDb\" }`.\n *\n * See the [PouchDB documentation](https://pouchdb.com/api.html#create_database)\n * for available options.\n */\n pouchDBOptions?: PouchDB.Configuration.DatabaseConfiguration;\n /**\n * Defines the name of the {@link https://api.graffiti.garden/interfaces/GraffitiObjectBase.html#source | `source` }\n * under which to store objects.\n * Defaults to `\"local\"`.\n */\n sourceName?: string;\n /**\n * The time in milliseconds to keep tombstones before deleting them.\n * See the {@link https://api.graffiti.garden/classes/Graffiti.html#discover | `discover` }\n * documentation for more information.\n */\n tombstoneRetention?: number;\n}\n\n/**\n * An implementation of only the database operations of the\n * GraffitiAPI without synchronization or session management.\n */\nexport class GraffitiLocalDatabase\n implements\n Pick<\n Graffiti,\n | \"get\"\n | \"put\"\n | \"patch\"\n | \"delete\"\n | \"discover\"\n | \"recoverOrphans\"\n | \"channelStats\"\n >\n{\n protected readonly db: PouchDB.Database<GraffitiObjectBase>;\n protected readonly source: string = \"local\";\n protected readonly tombstoneRetention: number = 86400000; // 1 day in ms\n protected readonly ajv: Ajv;\n\n constructor(options?: GraffitiLocalOptions, ajv?: Ajv) {\n this.ajv = ajv ?? new Ajv({ strict: false });\n this.source = options?.sourceName ?? this.source;\n this.tombstoneRetention =\n options?.tombstoneRetention ?? this.tombstoneRetention;\n const pouchDbOptions = {\n name: \"graffitiDb\",\n ...options?.pouchDBOptions,\n };\n this.db = new PouchDB<GraffitiObjectBase>(\n pouchDbOptions.name,\n pouchDbOptions,\n );\n\n this.db\n //@ts-ignore\n .put({\n _id: \"_design/indexes\",\n views: {\n objectsPerChannelAndLastModified: {\n map: function (object: GraffitiObjectBase) {\n const paddedLastModified = object.lastModified\n .toString()\n .padStart(15, \"0\");\n object.channels.forEach(function (channel) {\n const id =\n encodeURIComponent(channel) + \"/\" + paddedLastModified;\n //@ts-ignore\n emit(id);\n });\n }.toString(),\n },\n orphansPerActorAndLastModified: {\n map: function (object: GraffitiObjectBase) {\n if (object.channels.length === 0) {\n const paddedLastModified = object.lastModified\n .toString()\n .padStart(15, \"0\");\n const id =\n encodeURIComponent(object.actor) + \"/\" + paddedLastModified;\n //@ts-ignore\n emit(id);\n }\n }.toString(),\n },\n channelStatsPerActor: {\n map: function (object: GraffitiObjectBase) {\n if (object.tombstone) return;\n object.channels.forEach(function (channel) {\n const id =\n encodeURIComponent(object.actor) +\n \"/\" +\n encodeURIComponent(channel);\n //@ts-ignore\n emit(id, object.lastModified);\n });\n }.toString(),\n reduce: \"_stats\",\n },\n },\n })\n //@ts-ignore\n .catch((error) => {\n if (\n error &&\n typeof error === \"object\" &&\n \"name\" in error &&\n error.name === \"conflict\"\n ) {\n // Design document already exists\n return;\n } else {\n throw error;\n }\n });\n }\n\n protected async queryByLocation(location: GraffitiLocation) {\n const uri = locationToUri(location) + \"/\";\n const results = await this.db.allDocs({\n startkey: uri,\n endkey: uri + \"\\uffff\", // \\uffff is the last unicode character\n include_docs: true,\n });\n const docs = results.rows\n .map((row) => row.doc)\n // Remove undefined docs\n .reduce<\n PouchDB.Core.ExistingDocument<\n GraffitiObjectBase & PouchDB.Core.AllDocsMeta\n >[]\n >((acc, doc) => {\n if (doc) acc.push(doc);\n return acc;\n }, []);\n return docs;\n }\n\n protected docId(location: GraffitiLocation) {\n return locationToUri(location) + \"/\" + randomBase64();\n }\n\n get: Graffiti[\"get\"] = async (...args) => {\n const [locationOrUri, schema, session] = args;\n const { location } = unpackLocationOrUri(locationOrUri);\n\n const docsAll = await this.queryByLocation(location);\n\n // Filter out ones not allowed\n const docs = docsAll.filter((doc) =>\n isActorAllowedGraffitiObject(doc, session),\n );\n if (!docs.length) throw new GraffitiErrorNotFound();\n\n // Get the most recent document\n const doc = docs.reduce((a, b) => (isObjectNewer(a, b) ? a : b));\n\n // Strip out the _id and _rev\n const { _id, _rev, ...object } = doc;\n\n // Mask out the allowed list and channels\n // if the user is not the owner\n maskGraffitiObject(object, [], session);\n\n const validate = attemptAjvCompile(this.ajv, schema);\n if (!validate(object)) {\n throw new GraffitiErrorSchemaMismatch();\n }\n return object;\n };\n\n /**\n * Deletes all docs at a particular location.\n * If the `keepLatest` flag is set to true,\n * the doc with the most recent timestamp will be\n * spared. If there are multiple docs with the same\n * timestamp, the one with the highest `_id` will be\n * spared.\n */\n protected async deleteAtLocation(\n location: GraffitiLocation,\n keepLatest: boolean = false,\n ) {\n const docsAtLocationAll = await this.queryByLocation(location);\n const docsAtLocation = docsAtLocationAll.filter((doc) => !doc.tombstone);\n if (!docsAtLocation.length) return undefined;\n\n // Get the most recent lastModified timestamp.\n const latestModified = docsAtLocation\n .map((doc) => doc.lastModified)\n .reduce((a, b) => (a > b ? a : b));\n\n // Delete all old docs\n const docsToDelete = docsAtLocation.filter(\n (doc) => !keepLatest || doc.lastModified < latestModified,\n );\n\n // For docs with the same timestamp,\n // keep the one with the highest _id\n // to break concurrency ties\n const concurrentDocsAll = docsAtLocation.filter(\n (doc) => keepLatest && doc.lastModified === latestModified,\n );\n if (concurrentDocsAll.length) {\n const keepDocId = concurrentDocsAll\n .map((doc) => doc._id)\n .reduce((a, b) => (a > b ? a : b));\n const concurrentDocsToDelete = concurrentDocsAll.filter(\n (doc) => doc._id !== keepDocId,\n );\n docsToDelete.push(...concurrentDocsToDelete);\n }\n\n const lastModified = keepLatest ? latestModified : new Date().getTime();\n\n const deleteResults = await this.db.bulkDocs<GraffitiObjectBase>(\n docsToDelete.map((doc) => ({\n ...doc,\n tombstone: true,\n lastModified,\n })),\n );\n\n // Get one of the docs that was deleted\n let deletedObject: GraffitiObjectBase | undefined = undefined;\n for (const resultOrError of deleteResults) {\n if (\"ok\" in resultOrError) {\n const { id } = resultOrError;\n const deletedDoc = docsToDelete.find((doc) => doc._id === id);\n if (deletedDoc) {\n const { _id, _rev, _conflicts, _attachments, ...object } = deletedDoc;\n deletedObject = {\n ...object,\n tombstone: true,\n lastModified,\n };\n break;\n }\n }\n }\n\n return deletedObject;\n }\n\n delete: Graffiti[\"delete\"] = async (...args) => {\n const [locationOrUri, session] = args;\n const { location } = unpackLocationOrUri(locationOrUri);\n if (location.actor !== session.actor) {\n throw new GraffitiErrorForbidden();\n }\n\n const deletedObject = await this.deleteAtLocation(location);\n if (!deletedObject) {\n throw new GraffitiErrorNotFound();\n }\n return deletedObject;\n };\n\n put: Graffiti[\"put\"] = async (...args) => {\n const [objectPartial, session] = args;\n if (objectPartial.actor && objectPartial.actor !== session.actor) {\n throw new GraffitiErrorForbidden();\n }\n if (objectPartial.source && objectPartial.source !== this.source) {\n throw new GraffitiErrorForbidden(\n \"Putting an object that does not match this source\",\n );\n }\n\n const object: GraffitiObjectBase = {\n value: objectPartial.value,\n channels: objectPartial.channels,\n allowed: objectPartial.allowed,\n name: objectPartial.name ?? randomBase64(),\n source: this.source,\n actor: session.actor,\n tombstone: false,\n lastModified: new Date().getTime(),\n };\n\n await this.db.put({\n _id: this.docId(object),\n ...object,\n });\n\n // Delete the old object\n const previousObject = await this.deleteAtLocation(object, true);\n if (previousObject) {\n return previousObject;\n } else {\n return {\n ...object,\n value: {},\n channels: [],\n allowed: undefined,\n tombstone: true,\n };\n }\n };\n\n patch: Graffiti[\"patch\"] = async (...args) => {\n const [patch, locationOrUri, session] = args;\n const { location } = unpackLocationOrUri(locationOrUri);\n if (location.actor !== session.actor) {\n throw new GraffitiErrorForbidden();\n }\n const originalObject = await this.get(locationOrUri, {}, session);\n if (originalObject.tombstone) {\n throw new GraffitiErrorNotFound(\n \"The object you are trying to patch has been deleted\",\n );\n }\n\n // Patch it outside of the database\n const patchObject: GraffitiObjectBase = { ...originalObject };\n for (const prop of [\"value\", \"channels\", \"allowed\"] as const) {\n applyGraffitiPatch(applyPatch, prop, patch, patchObject);\n }\n\n // Make sure the value is an object\n if (\n typeof patchObject.value !== \"object\" ||\n Array.isArray(patchObject.value) ||\n !patchObject.value\n ) {\n throw new GraffitiErrorPatchError(\"value is no longer an object\");\n }\n\n // Make sure the channels are an array of strings\n if (\n !Array.isArray(patchObject.channels) ||\n !patchObject.channels.every((channel) => typeof channel === \"string\")\n ) {\n throw new GraffitiErrorPatchError(\n \"channels are no longer an array of strings\",\n );\n }\n\n // Make sure the allowed list is an array of strings or undefined\n if (\n patchObject.allowed &&\n (!Array.isArray(patchObject.allowed) ||\n !patchObject.allowed.every((allowed) => typeof allowed === \"string\"))\n ) {\n throw new GraffitiErrorPatchError(\n \"allowed list is not an array of strings\",\n );\n }\n\n patchObject.lastModified = new Date().getTime();\n await this.db.put({\n ...patchObject,\n _id: this.docId(patchObject),\n });\n\n // Delete the old object\n await this.deleteAtLocation(patchObject, true);\n\n return {\n ...originalObject,\n tombstone: true,\n lastModified: patchObject.lastModified,\n };\n };\n\n protected queryLastModifiedSuffixes(schema: JSONSchema4) {\n // Use the index for queries over ranges of lastModified\n let startKeySuffix = \"\";\n let endKeySuffix = \"\\uffff\";\n const lastModifiedSchema = schema.properties?.lastModified;\n if (lastModifiedSchema?.minimum) {\n let minimum = Math.ceil(lastModifiedSchema.minimum);\n minimum === lastModifiedSchema.minimum &&\n lastModifiedSchema.exclusiveMinimum &&\n minimum++;\n startKeySuffix = minimum.toString().padStart(15, \"0\");\n }\n if (lastModifiedSchema?.maximum) {\n let maximum = Math.floor(lastModifiedSchema.maximum);\n maximum === lastModifiedSchema.maximum &&\n lastModifiedSchema.exclusiveMaximum &&\n maximum--;\n endKeySuffix = maximum.toString().padStart(15, \"0\");\n }\n return {\n startKeySuffix,\n endKeySuffix,\n };\n }\n\n discover: Graffiti[\"discover\"] = (...args) => {\n const [channels, schema, session] = args;\n const validate = attemptAjvCompile(this.ajv, schema);\n\n const { startKeySuffix, endKeySuffix } =\n this.queryLastModifiedSuffixes(schema);\n\n const repeater: ReturnType<\n typeof Graffiti.prototype.discover<typeof schema>\n > = new Repeater(async (push, stop) => {\n const processedIds = new Set<string>();\n\n for (const channel of channels) {\n const keyPrefix = encodeURIComponent(channel) + \"/\";\n const startkey = keyPrefix + startKeySuffix;\n const endkey = keyPrefix + endKeySuffix;\n\n const result = await this.db.query<GraffitiObjectBase>(\n \"indexes/objectsPerChannelAndLastModified\",\n { startkey, endkey, include_docs: true },\n );\n\n for (const row of result.rows) {\n const doc = row.doc;\n if (!doc) continue;\n\n const { _id, _rev, ...object } = doc;\n\n // Don't double return the same object\n // (which can happen if it's in multiple channels)\n if (processedIds.has(_id)) continue;\n processedIds.add(_id);\n\n // Make sure the user is allowed to see it\n if (!isActorAllowedGraffitiObject(doc, session)) continue;\n\n // Mask out the allowed list and channels\n // if the user is not the owner\n maskGraffitiObject(object, channels, session);\n\n // Check that it matches the schema\n if (validate(object)) {\n await push({ value: object });\n }\n }\n }\n stop();\n return {\n tombstoneRetention: this.tombstoneRetention,\n };\n });\n\n return repeater;\n };\n\n recoverOrphans: Graffiti[\"recoverOrphans\"] = (schema, session) => {\n const validate = attemptAjvCompile(this.ajv, schema);\n\n const { startKeySuffix, endKeySuffix } =\n this.queryLastModifiedSuffixes(schema);\n const keyPrefix = encodeURIComponent(session.actor) + \"/\";\n const startkey = keyPrefix + startKeySuffix;\n const endkey = keyPrefix + endKeySuffix;\n\n const repeater: ReturnType<\n typeof Graffiti.prototype.recoverOrphans<typeof schema>\n > = new Repeater(async (push, stop) => {\n const result = await this.db.query<GraffitiObjectBase>(\n \"indexes/orphansPerActorAndLastModified\",\n { startkey, endkey, include_docs: true },\n );\n\n for (const row of result.rows) {\n const doc = row.doc;\n if (!doc) continue;\n\n // No masking/access necessary because\n // the objects are all owned by the querier\n\n const { _id, _rev, ...object } = doc;\n if (validate(object)) {\n await push({ value: object });\n }\n }\n stop();\n return {\n tombstoneRetention: this.tombstoneRetention,\n };\n });\n\n return repeater;\n };\n\n channelStats: Graffiti[\"channelStats\"] = (session) => {\n const repeater: ReturnType<typeof Graffiti.prototype.channelStats> =\n new Repeater(async (push, stop) => {\n const keyPrefix = encodeURIComponent(session.actor) + \"/\";\n const result = await this.db.query(\"indexes/channelStatsPerActor\", {\n startkey: keyPrefix,\n endkey: keyPrefix + \"\\uffff\",\n reduce: true,\n group: true,\n });\n for (const row of result.rows) {\n const channelEncoded = row.key.split(\"/\")[1];\n if (typeof channelEncoded !== \"string\") continue;\n const { count, max: lastModified } = row.value;\n if (typeof count !== \"number\" || typeof lastModified !== \"number\")\n continue;\n await push({\n value: {\n channel: decodeURIComponent(channelEncoded),\n count,\n lastModified,\n },\n });\n }\n stop();\n });\n\n return repeater;\n };\n}\n"],
5
+ "mappings": "AAMA,OACE,yBAAAA,EACA,+BAAAC,EACA,0BAAAC,EACA,2BAAAC,MACK,uBACP,OAAOC,MAAa,UACpB,OACE,iBAAAC,EACA,uBAAAC,EACA,gBAAAC,EACA,sBAAAC,EACA,qBAAAC,EACA,sBAAAC,EACA,gCAAAC,EACA,iBAAAC,MACK,iBACP,OAAS,YAAAC,MAAgB,uBACzB,OAAOC,MAAS,eAChB,OAAS,cAAAC,MAAkB,kBAgCpB,MAAMC,CAYb,CACqB,GACA,OAAiB,QACjB,mBAA6B,MAC7B,IAEnB,YAAYC,EAAgCC,EAAW,CACrD,KAAK,IAAMA,GAAO,IAAIJ,EAAI,CAAE,OAAQ,EAAM,CAAC,EAC3C,KAAK,OAASG,GAAS,YAAc,KAAK,OAC1C,KAAK,mBACHA,GAAS,oBAAsB,KAAK,mBACtC,MAAME,EAAiB,CACrB,KAAM,aACN,GAAGF,GAAS,cACd,EACA,KAAK,GAAK,IAAIb,EACZe,EAAe,KACfA,CACF,EAEA,KAAK,GAEF,IAAI,CACH,IAAK,kBACL,MAAO,CACL,iCAAkC,CAChC,IAAK,SAAUC,EAA4B,CACzC,MAAMC,EAAqBD,EAAO,aAC/B,SAAS,EACT,SAAS,GAAI,GAAG,EACnBA,EAAO,SAAS,QAAQ,SAAUE,EAAS,CACzC,MAAMC,EACJ,mBAAmBD,CAAO,EAAI,IAAMD,EAEtC,KAAKE,CAAE,CACT,CAAC,CACH,EAAE,SAAS,CACb,EACA,+BAAgC,CAC9B,IAAK,SAAUH,EAA4B,CACzC,GAAIA,EAAO,SAAS,SAAW,EAAG,CAChC,MAAMC,EAAqBD,EAAO,aAC/B,SAAS,EACT,SAAS,GAAI,GAAG,EACbG,EACJ,mBAAmBH,EAAO,KAAK,EAAI,IAAMC,EAE3C,KAAKE,CAAE,CACT,CACF,EAAE,SAAS,CACb,EACA,qBAAsB,CACpB,IAAK,SAAUH,EAA4B,CACrCA,EAAO,WACXA,EAAO,SAAS,QAAQ,SAAUE,EAAS,CACzC,MAAMC,EACJ,mBAAmBH,EAAO,KAAK,EAC/B,IACA,mBAAmBE,CAAO,EAE5B,KAAKC,EAAIH,EAAO,YAAY,CAC9B,CAAC,CACH,EAAE,SAAS,EACX,OAAQ,QACV,CACF,CACF,CAAC,EAEA,MAAOI,GAAU,CAChB,GACE,EAAAA,GACA,OAAOA,GAAU,UACjB,SAAUA,GACVA,EAAM,OAAS,YAKf,MAAMA,CAEV,CAAC,CACL,CAEA,MAAgB,gBAAgBC,EAA4B,CAC1D,MAAMC,EAAMrB,EAAcoB,CAAQ,EAAI,IAiBtC,OAhBgB,MAAM,KAAK,GAAG,QAAQ,CACpC,SAAUC,EACV,OAAQA,EAAM,SACd,aAAc,EAChB,CAAC,GACoB,KAClB,IAAKC,GAAQA,EAAI,GAAG,EAEpB,OAIC,CAACC,EAAKC,KACFA,GAAKD,EAAI,KAAKC,CAAG,EACdD,GACN,CAAC,CAAC,CAET,CAEU,MAAMH,EAA4B,CAC1C,OAAOpB,EAAcoB,CAAQ,EAAI,IAAMlB,EAAa,CACtD,CAEA,IAAuB,SAAUuB,IAAS,CACxC,KAAM,CAACC,EAAeC,EAAQC,CAAO,EAAIH,EACnC,CAAE,SAAAL,CAAS,EAAInB,EAAoByB,CAAa,EAKhDG,GAHU,MAAM,KAAK,gBAAgBT,CAAQ,GAG9B,OAAQI,GAC3BlB,EAA6BkB,EAAKI,CAAO,CAC3C,EACA,GAAI,CAACC,EAAK,OAAQ,MAAM,IAAIlC,EAG5B,MAAM6B,EAAMK,EAAK,OAAO,CAACC,EAAGC,IAAOxB,EAAcuB,EAAGC,CAAC,EAAID,EAAIC,CAAE,EAGzD,CAAE,IAAAC,EAAK,KAAAC,EAAM,GAAGlB,CAAO,EAAIS,EAOjC,GAHAnB,EAAmBU,EAAQ,CAAC,EAAGa,CAAO,EAGlC,CADaxB,EAAkB,KAAK,IAAKuB,CAAM,EACrCZ,CAAM,EAClB,MAAM,IAAInB,EAEZ,OAAOmB,CACT,EAUA,MAAgB,iBACdK,EACAc,EAAsB,GACtB,CAEA,MAAMC,GADoB,MAAM,KAAK,gBAAgBf,CAAQ,GACpB,OAAQI,GAAQ,CAACA,EAAI,SAAS,EACvE,GAAI,CAACW,EAAe,OAAQ,OAG5B,MAAMC,EAAiBD,EACpB,IAAKX,GAAQA,EAAI,YAAY,EAC7B,OAAO,CAAC,EAAGO,IAAO,EAAIA,EAAI,EAAIA,CAAE,EAG7BM,EAAeF,EAAe,OACjCX,GAAQ,CAACU,GAAcV,EAAI,aAAeY,CAC7C,EAKME,EAAoBH,EAAe,OACtCX,GAAQU,GAAcV,EAAI,eAAiBY,CAC9C,EACA,GAAIE,EAAkB,OAAQ,CAC5B,MAAMC,EAAYD,EACf,IAAKd,GAAQA,EAAI,GAAG,EACpB,OAAO,CAACM,EAAGC,IAAOD,EAAIC,EAAID,EAAIC,CAAE,EAC7BS,EAAyBF,EAAkB,OAC9Cd,GAAQA,EAAI,MAAQe,CACvB,EACAF,EAAa,KAAK,GAAGG,CAAsB,CAC7C,CAEA,MAAMC,EAAeP,EAAaE,EAAiB,IAAI,KAAK,EAAE,QAAQ,EAEhEM,EAAgB,MAAM,KAAK,GAAG,SAClCL,EAAa,IAAKb,IAAS,CACzB,GAAGA,EACH,UAAW,GACX,aAAAiB,CACF,EAAE,CACJ,EAGA,IAAIE,EACJ,UAAWC,KAAiBF,EAC1B,GAAI,OAAQE,EAAe,CACzB,KAAM,CAAE,GAAA1B,CAAG,EAAI0B,EACTC,EAAaR,EAAa,KAAMb,GAAQA,EAAI,MAAQN,CAAE,EAC5D,GAAI2B,EAAY,CACd,KAAM,CAAE,IAAAb,EAAK,KAAAC,EAAM,WAAAa,EAAY,aAAAC,EAAc,GAAGhC,CAAO,EAAI8B,EAC3DF,EAAgB,CACd,GAAG5B,EACH,UAAW,GACX,aAAA0B,CACF,EACA,KACF,CACF,CAGF,OAAOE,CACT,CAEA,OAA6B,SAAUlB,IAAS,CAC9C,KAAM,CAACC,EAAeE,CAAO,EAAIH,EAC3B,CAAE,SAAAL,CAAS,EAAInB,EAAoByB,CAAa,EACtD,GAAIN,EAAS,QAAUQ,EAAQ,MAC7B,MAAM,IAAI/B,EAGZ,MAAM8C,EAAgB,MAAM,KAAK,iBAAiBvB,CAAQ,EAC1D,GAAI,CAACuB,EACH,MAAM,IAAIhD,EAEZ,OAAOgD,CACT,EAEA,IAAuB,SAAUlB,IAAS,CACxC,KAAM,CAACuB,EAAepB,CAAO,EAAIH,EACjC,GAAIuB,EAAc,OAASA,EAAc,QAAUpB,EAAQ,MACzD,MAAM,IAAI/B,EAEZ,GAAImD,EAAc,QAAUA,EAAc,SAAW,KAAK,OACxD,MAAM,IAAInD,EACR,mDACF,EAGF,MAAMkB,EAA6B,CACjC,MAAOiC,EAAc,MACrB,SAAUA,EAAc,SACxB,QAASA,EAAc,QACvB,KAAMA,EAAc,MAAQ9C,EAAa,EACzC,OAAQ,KAAK,OACb,MAAO0B,EAAQ,MACf,UAAW,GACX,aAAc,IAAI,KAAK,EAAE,QAAQ,CACnC,EAEA,MAAM,KAAK,GAAG,IAAI,CAChB,IAAK,KAAK,MAAMb,CAAM,EACtB,GAAGA,CACL,CAAC,EAGD,MAAMkC,EAAiB,MAAM,KAAK,iBAAiBlC,EAAQ,EAAI,EAC/D,OAAIkC,GAGK,CACL,GAAGlC,EACH,MAAO,CAAC,EACR,SAAU,CAAC,EACX,QAAS,OACT,UAAW,EACb,CAEJ,EAEA,MAA2B,SAAUU,IAAS,CAC5C,KAAM,CAACyB,EAAOxB,EAAeE,CAAO,EAAIH,EAClC,CAAE,SAAAL,CAAS,EAAInB,EAAoByB,CAAa,EACtD,GAAIN,EAAS,QAAUQ,EAAQ,MAC7B,MAAM,IAAI/B,EAEZ,MAAMsD,EAAiB,MAAM,KAAK,IAAIzB,EAAe,CAAC,EAAGE,CAAO,EAChE,GAAIuB,EAAe,UACjB,MAAM,IAAIxD,EACR,qDACF,EAIF,MAAMyD,EAAkC,CAAE,GAAGD,CAAe,EAC5D,UAAWE,IAAQ,CAAC,QAAS,WAAY,SAAS,EAChDlD,EAAmBO,EAAY2C,EAAMH,EAAOE,CAAW,EAIzD,GACE,OAAOA,EAAY,OAAU,UAC7B,MAAM,QAAQA,EAAY,KAAK,GAC/B,CAACA,EAAY,MAEb,MAAM,IAAItD,EAAwB,8BAA8B,EAIlE,GACE,CAAC,MAAM,QAAQsD,EAAY,QAAQ,GACnC,CAACA,EAAY,SAAS,MAAOnC,GAAY,OAAOA,GAAY,QAAQ,EAEpE,MAAM,IAAInB,EACR,4CACF,EAIF,GACEsD,EAAY,UACX,CAAC,MAAM,QAAQA,EAAY,OAAO,GACjC,CAACA,EAAY,QAAQ,MAAOE,GAAY,OAAOA,GAAY,QAAQ,GAErE,MAAM,IAAIxD,EACR,yCACF,EAGF,OAAAsD,EAAY,aAAe,IAAI,KAAK,EAAE,QAAQ,EAC9C,MAAM,KAAK,GAAG,IAAI,CAChB,GAAGA,EACH,IAAK,KAAK,MAAMA,CAAW,CAC7B,CAAC,EAGD,MAAM,KAAK,iBAAiBA,EAAa,EAAI,EAEtC,CACL,GAAGD,EACH,UAAW,GACX,aAAcC,EAAY,YAC5B,CACF,EAEU,0BAA0BzB,EAAqB,CAEvD,IAAI4B,EAAiB,GACjBC,EAAe,SACnB,MAAMC,EAAqB9B,EAAO,YAAY,aAC9C,GAAI8B,GAAoB,QAAS,CAC/B,IAAIC,EAAU,KAAK,KAAKD,EAAmB,OAAO,EAClDC,IAAYD,EAAmB,SAC7BA,EAAmB,kBACnBC,IACFH,EAAiBG,EAAQ,SAAS,EAAE,SAAS,GAAI,GAAG,CACtD,CACA,GAAID,GAAoB,QAAS,CAC/B,IAAIE,EAAU,KAAK,MAAMF,EAAmB,OAAO,EACnDE,IAAYF,EAAmB,SAC7BA,EAAmB,kBACnBE,IACFH,EAAeG,EAAQ,SAAS,EAAE,SAAS,GAAI,GAAG,CACpD,CACA,MAAO,CACL,eAAAJ,EACA,aAAAC,CACF,CACF,CAEA,SAAiC,IAAI/B,IAAS,CAC5C,KAAM,CAACmC,EAAUjC,EAAQC,CAAO,EAAIH,EAC9BoC,EAAWzD,EAAkB,KAAK,IAAKuB,CAAM,EAE7C,CAAE,eAAA4B,EAAgB,aAAAC,CAAa,EACnC,KAAK,0BAA0B7B,CAAM,EA+CvC,OA3CI,IAAInB,EAAS,MAAOsD,EAAMC,IAAS,CACrC,MAAMC,EAAe,IAAI,IAEzB,UAAW/C,KAAW2C,EAAU,CAC9B,MAAMK,EAAY,mBAAmBhD,CAAO,EAAI,IAC1CiD,EAAWD,EAAYV,EACvBY,EAASF,EAAYT,EAErBY,EAAS,MAAM,KAAK,GAAG,MAC3B,2CACA,CAAE,SAAAF,EAAU,OAAAC,EAAQ,aAAc,EAAK,CACzC,EAEA,UAAW7C,KAAO8C,EAAO,KAAM,CAC7B,MAAM5C,EAAMF,EAAI,IAChB,GAAI,CAACE,EAAK,SAEV,KAAM,CAAE,IAAAQ,EAAK,KAAAC,EAAM,GAAGlB,CAAO,EAAIS,EAI7BwC,EAAa,IAAIhC,CAAG,IACxBgC,EAAa,IAAIhC,CAAG,EAGf1B,EAA6BkB,EAAKI,CAAO,IAI9CvB,EAAmBU,EAAQ6C,EAAUhC,CAAO,EAGxCiC,EAAS9C,CAAM,GACjB,MAAM+C,EAAK,CAAE,MAAO/C,CAAO,CAAC,GAEhC,CACF,CACA,OAAAgD,EAAK,EACE,CACL,mBAAoB,KAAK,kBAC3B,CACF,CAAC,CAGH,EAEA,eAA6C,CAACpC,EAAQC,IAAY,CAChE,MAAMiC,EAAWzD,EAAkB,KAAK,IAAKuB,CAAM,EAE7C,CAAE,eAAA4B,EAAgB,aAAAC,CAAa,EACnC,KAAK,0BAA0B7B,CAAM,EACjCsC,EAAY,mBAAmBrC,EAAQ,KAAK,EAAI,IAChDsC,EAAWD,EAAYV,EACvBY,EAASF,EAAYT,EA4B3B,OAxBI,IAAIhD,EAAS,MAAOsD,EAAMC,IAAS,CACrC,MAAMK,EAAS,MAAM,KAAK,GAAG,MAC3B,yCACA,CAAE,SAAAF,EAAU,OAAAC,EAAQ,aAAc,EAAK,CACzC,EAEA,UAAW7C,KAAO8C,EAAO,KAAM,CAC7B,MAAM5C,EAAMF,EAAI,IAChB,GAAI,CAACE,EAAK,SAKV,KAAM,CAAE,IAAAQ,EAAK,KAAAC,EAAM,GAAGlB,CAAO,EAAIS,EAC7BqC,EAAS9C,CAAM,GACjB,MAAM+C,EAAK,CAAE,MAAO/C,CAAO,CAAC,CAEhC,CACA,OAAAgD,EAAK,EACE,CACL,mBAAoB,KAAK,kBAC3B,CACF,CAAC,CAGH,EAEA,aAA0CnC,GAEtC,IAAIpB,EAAS,MAAOsD,EAAMC,IAAS,CACjC,MAAME,EAAY,mBAAmBrC,EAAQ,KAAK,EAAI,IAChDwC,EAAS,MAAM,KAAK,GAAG,MAAM,+BAAgC,CACjE,SAAUH,EACV,OAAQA,EAAY,SACpB,OAAQ,GACR,MAAO,EACT,CAAC,EACD,UAAW3C,KAAO8C,EAAO,KAAM,CAC7B,MAAMC,EAAiB/C,EAAI,IAAI,MAAM,GAAG,EAAE,CAAC,EAC3C,GAAI,OAAO+C,GAAmB,SAAU,SACxC,KAAM,CAAE,MAAAC,EAAO,IAAK7B,CAAa,EAAInB,EAAI,MACrC,OAAOgD,GAAU,UAAY,OAAO7B,GAAiB,UAEzD,MAAMqB,EAAK,CACT,MAAO,CACL,QAAS,mBAAmBO,CAAc,EAC1C,MAAAC,EACA,aAAA7B,CACF,CACF,CAAC,CACH,CACAsB,EAAK,CACP,CAAC,CAIP",
6
6
  "names": ["GraffitiErrorNotFound", "GraffitiErrorSchemaMismatch", "GraffitiErrorForbidden", "GraffitiErrorPatchError", "PouchDB", "locationToUri", "unpackLocationOrUri", "randomBase64", "applyGraffitiPatch", "attemptAjvCompile", "maskGraffitiObject", "isActorAllowedGraffitiObject", "isObjectNewer", "Repeater", "Ajv", "applyPatch", "GraffitiLocalDatabase", "options", "ajv", "pouchDbOptions", "object", "paddedLastModified", "channel", "id", "error", "location", "uri", "row", "acc", "doc", "args", "locationOrUri", "schema", "session", "docs", "a", "b", "_id", "_rev", "keepLatest", "docsAtLocation", "latestModified", "docsToDelete", "concurrentDocsAll", "keepDocId", "concurrentDocsToDelete", "lastModified", "deleteResults", "deletedObject", "resultOrError", "deletedDoc", "_conflicts", "_attachments", "objectPartial", "previousObject", "patch", "originalObject", "patchObject", "prop", "allowed", "startKeySuffix", "endKeySuffix", "lastModifiedSchema", "minimum", "maximum", "channels", "validate", "push", "stop", "processedIds", "keyPrefix", "startkey", "endkey", "result", "channelEncoded", "count"]
7
7
  }
package/dist/esm/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{Graffiti as e}from"@graffiti-garden/api";import s from"ajv-draft-04";import{GraffitiLocalSessionManager as a}from"./session-manager";import{GraffitiLocalDatabase as c}from"./database";import{GraffitiSynchronize as f}from"./synchronize";import{locationToUri as h,uriToLocation as p}from"./utilities";class z extends e{locationToUri=h;uriToLocation=p;login;logout;sessionEvents;put;get;patch;delete;discover;recoverOrphans;synchronizeGet;synchronizeDiscover;synchronizeRecoverOrphans;channelStats;constructor(r){super();const t=new a;this.login=t.login.bind(t),this.logout=t.logout.bind(t),this.sessionEvents=t.sessionEvents;const n=new s({strict:!1}),o=new c(r,n),i=new f(o,n);this.put=i.put.bind(i),this.get=i.get.bind(i),this.patch=i.patch.bind(i),this.delete=i.delete.bind(i),this.discover=i.discover.bind(i),this.recoverOrphans=i.recoverOrphans.bind(o),this.channelStats=o.channelStats.bind(o),this.synchronizeDiscover=i.synchronizeDiscover.bind(i),this.synchronizeGet=i.synchronizeGet.bind(i),this.synchronizeRecoverOrphans=i.synchronizeRecoverOrphans.bind(i)}}export{z as GraffitiLocal};
1
+ import{Graffiti as e}from"@graffiti-garden/api";import s from"ajv-draft-04";import{GraffitiLocalSessionManager as a}from"./session-manager.js";import{GraffitiLocalDatabase as c}from"./database.js";import{GraffitiSynchronize as f}from"./synchronize.js";import{locationToUri as h,uriToLocation as p}from"./utilities.js";class z extends e{locationToUri=h;uriToLocation=p;login;logout;sessionEvents;put;get;patch;delete;discover;recoverOrphans;synchronizeGet;synchronizeDiscover;synchronizeRecoverOrphans;channelStats;constructor(r){super();const t=new a;this.login=t.login.bind(t),this.logout=t.logout.bind(t),this.sessionEvents=t.sessionEvents;const n=new s({strict:!1}),o=new c(r,n),i=new f(o,n);this.put=i.put.bind(i),this.get=i.get.bind(i),this.patch=i.patch.bind(i),this.delete=i.delete.bind(i),this.discover=i.discover.bind(i),this.recoverOrphans=i.recoverOrphans.bind(o),this.channelStats=o.channelStats.bind(o),this.synchronizeDiscover=i.synchronizeDiscover.bind(i),this.synchronizeGet=i.synchronizeGet.bind(i),this.synchronizeRecoverOrphans=i.synchronizeRecoverOrphans.bind(i)}}export{z as GraffitiLocal};
2
2
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/index.ts"],
4
- "sourcesContent": ["import { Graffiti } from \"@graffiti-garden/api\";\nimport Ajv from \"ajv-draft-04\";\nimport { GraffitiLocalSessionManager } from \"./session-manager\";\nimport { GraffitiLocalDatabase, type GraffitiLocalOptions } from \"./database\";\nimport { GraffitiSynchronize } from \"./synchronize\";\nimport { locationToUri, uriToLocation } from \"./utilities\";\n\nexport type { GraffitiLocalOptions };\n\n/**\n * A local implementation of the [Graffiti API](https://api.graffiti.garden/classes/Graffiti.html)\n * based on [PouchDB](https://pouchdb.com/). PouchDb will automatically persist data in a local\n * database, either in the browser or in Node.js.\n * It can also be configured to work with an external [CouchDB](https://couchdb.apache.org/) server,\n * although using it with a remote server will not be secure.\n */\nexport class GraffitiLocal extends Graffiti {\n locationToUri = locationToUri;\n uriToLocation = uriToLocation;\n\n login: Graffiti[\"login\"];\n logout: Graffiti[\"logout\"];\n sessionEvents: Graffiti[\"sessionEvents\"];\n put: Graffiti[\"put\"];\n get: Graffiti[\"get\"];\n patch: Graffiti[\"patch\"];\n delete: Graffiti[\"delete\"];\n discover: Graffiti[\"discover\"];\n recoverOrphans: Graffiti[\"recoverOrphans\"];\n synchronizeGet: Graffiti[\"synchronizeGet\"];\n synchronizeDiscover: Graffiti[\"synchronizeDiscover\"];\n synchronizeRecoverOrphans: Graffiti[\"synchronizeRecoverOrphans\"];\n channelStats: Graffiti[\"channelStats\"];\n\n constructor(options?: GraffitiLocalOptions) {\n super();\n\n const sessionManagerLocal = new GraffitiLocalSessionManager();\n this.login = sessionManagerLocal.login.bind(sessionManagerLocal);\n this.logout = sessionManagerLocal.logout.bind(sessionManagerLocal);\n this.sessionEvents = sessionManagerLocal.sessionEvents;\n\n const ajv = new Ajv({ strict: false });\n const graffitiPouchDbBase = new GraffitiLocalDatabase(options, ajv);\n const graffitiSynchronize = new GraffitiSynchronize(\n graffitiPouchDbBase,\n ajv,\n );\n\n this.put = graffitiSynchronize.put.bind(graffitiSynchronize);\n this.get = graffitiSynchronize.get.bind(graffitiSynchronize);\n this.patch = graffitiSynchronize.patch.bind(graffitiSynchronize);\n this.delete = graffitiSynchronize.delete.bind(graffitiSynchronize);\n this.discover = graffitiSynchronize.discover.bind(graffitiSynchronize);\n this.recoverOrphans =\n graffitiSynchronize.recoverOrphans.bind(graffitiPouchDbBase);\n this.channelStats =\n graffitiPouchDbBase.channelStats.bind(graffitiPouchDbBase);\n this.synchronizeDiscover =\n graffitiSynchronize.synchronizeDiscover.bind(graffitiSynchronize);\n this.synchronizeGet =\n graffitiSynchronize.synchronizeGet.bind(graffitiSynchronize);\n this.synchronizeRecoverOrphans =\n graffitiSynchronize.synchronizeRecoverOrphans.bind(graffitiSynchronize);\n }\n}\n"],
5
- "mappings": "AAAA,OAAS,YAAAA,MAAgB,uBACzB,OAAOC,MAAS,eAChB,OAAS,+BAAAC,MAAmC,oBAC5C,OAAS,yBAAAC,MAAwD,aACjE,OAAS,uBAAAC,MAA2B,gBACpC,OAAS,iBAAAC,EAAe,iBAAAC,MAAqB,cAWtC,MAAMC,UAAsBP,CAAS,CAC1C,cAAgBK,EAChB,cAAgBC,EAEhB,MACA,OACA,cACA,IACA,IACA,MACA,OACA,SACA,eACA,eACA,oBACA,0BACA,aAEA,YAAYE,EAAgC,CAC1C,MAAM,EAEN,MAAMC,EAAsB,IAAIP,EAChC,KAAK,MAAQO,EAAoB,MAAM,KAAKA,CAAmB,EAC/D,KAAK,OAASA,EAAoB,OAAO,KAAKA,CAAmB,EACjE,KAAK,cAAgBA,EAAoB,cAEzC,MAAMC,EAAM,IAAIT,EAAI,CAAE,OAAQ,EAAM,CAAC,EAC/BU,EAAsB,IAAIR,EAAsBK,EAASE,CAAG,EAC5DE,EAAsB,IAAIR,EAC9BO,EACAD,CACF,EAEA,KAAK,IAAME,EAAoB,IAAI,KAAKA,CAAmB,EAC3D,KAAK,IAAMA,EAAoB,IAAI,KAAKA,CAAmB,EAC3D,KAAK,MAAQA,EAAoB,MAAM,KAAKA,CAAmB,EAC/D,KAAK,OAASA,EAAoB,OAAO,KAAKA,CAAmB,EACjE,KAAK,SAAWA,EAAoB,SAAS,KAAKA,CAAmB,EACrE,KAAK,eACHA,EAAoB,eAAe,KAAKD,CAAmB,EAC7D,KAAK,aACHA,EAAoB,aAAa,KAAKA,CAAmB,EAC3D,KAAK,oBACHC,EAAoB,oBAAoB,KAAKA,CAAmB,EAClE,KAAK,eACHA,EAAoB,eAAe,KAAKA,CAAmB,EAC7D,KAAK,0BACHA,EAAoB,0BAA0B,KAAKA,CAAmB,CAC1E,CACF",
4
+ "sourcesContent": ["import { Graffiti } from \"@graffiti-garden/api\";\nimport Ajv from \"ajv-draft-04\";\nimport { GraffitiLocalSessionManager } from \"./session-manager.js\";\nimport {\n GraffitiLocalDatabase,\n type GraffitiLocalOptions,\n} from \"./database.js\";\nimport { GraffitiSynchronize } from \"./synchronize.js\";\nimport { locationToUri, uriToLocation } from \"./utilities.js\";\n\nexport type { GraffitiLocalOptions };\n\n/**\n * A local implementation of the [Graffiti API](https://api.graffiti.garden/classes/Graffiti.html)\n * based on [PouchDB](https://pouchdb.com/). PouchDb will automatically persist data in a local\n * database, either in the browser or in Node.js.\n * It can also be configured to work with an external [CouchDB](https://couchdb.apache.org/) server,\n * although using it with a remote server will not be secure.\n */\nexport class GraffitiLocal extends Graffiti {\n locationToUri = locationToUri;\n uriToLocation = uriToLocation;\n\n login: Graffiti[\"login\"];\n logout: Graffiti[\"logout\"];\n sessionEvents: Graffiti[\"sessionEvents\"];\n put: Graffiti[\"put\"];\n get: Graffiti[\"get\"];\n patch: Graffiti[\"patch\"];\n delete: Graffiti[\"delete\"];\n discover: Graffiti[\"discover\"];\n recoverOrphans: Graffiti[\"recoverOrphans\"];\n synchronizeGet: Graffiti[\"synchronizeGet\"];\n synchronizeDiscover: Graffiti[\"synchronizeDiscover\"];\n synchronizeRecoverOrphans: Graffiti[\"synchronizeRecoverOrphans\"];\n channelStats: Graffiti[\"channelStats\"];\n\n constructor(options?: GraffitiLocalOptions) {\n super();\n\n const sessionManagerLocal = new GraffitiLocalSessionManager();\n this.login = sessionManagerLocal.login.bind(sessionManagerLocal);\n this.logout = sessionManagerLocal.logout.bind(sessionManagerLocal);\n this.sessionEvents = sessionManagerLocal.sessionEvents;\n\n const ajv = new Ajv({ strict: false });\n const graffitiPouchDbBase = new GraffitiLocalDatabase(options, ajv);\n const graffitiSynchronize = new GraffitiSynchronize(\n graffitiPouchDbBase,\n ajv,\n );\n\n this.put = graffitiSynchronize.put.bind(graffitiSynchronize);\n this.get = graffitiSynchronize.get.bind(graffitiSynchronize);\n this.patch = graffitiSynchronize.patch.bind(graffitiSynchronize);\n this.delete = graffitiSynchronize.delete.bind(graffitiSynchronize);\n this.discover = graffitiSynchronize.discover.bind(graffitiSynchronize);\n this.recoverOrphans =\n graffitiSynchronize.recoverOrphans.bind(graffitiPouchDbBase);\n this.channelStats =\n graffitiPouchDbBase.channelStats.bind(graffitiPouchDbBase);\n this.synchronizeDiscover =\n graffitiSynchronize.synchronizeDiscover.bind(graffitiSynchronize);\n this.synchronizeGet =\n graffitiSynchronize.synchronizeGet.bind(graffitiSynchronize);\n this.synchronizeRecoverOrphans =\n graffitiSynchronize.synchronizeRecoverOrphans.bind(graffitiSynchronize);\n }\n}\n"],
5
+ "mappings": "AAAA,OAAS,YAAAA,MAAgB,uBACzB,OAAOC,MAAS,eAChB,OAAS,+BAAAC,MAAmC,uBAC5C,OACE,yBAAAC,MAEK,gBACP,OAAS,uBAAAC,MAA2B,mBACpC,OAAS,iBAAAC,EAAe,iBAAAC,MAAqB,iBAWtC,MAAMC,UAAsBP,CAAS,CAC1C,cAAgBK,EAChB,cAAgBC,EAEhB,MACA,OACA,cACA,IACA,IACA,MACA,OACA,SACA,eACA,eACA,oBACA,0BACA,aAEA,YAAYE,EAAgC,CAC1C,MAAM,EAEN,MAAMC,EAAsB,IAAIP,EAChC,KAAK,MAAQO,EAAoB,MAAM,KAAKA,CAAmB,EAC/D,KAAK,OAASA,EAAoB,OAAO,KAAKA,CAAmB,EACjE,KAAK,cAAgBA,EAAoB,cAEzC,MAAMC,EAAM,IAAIT,EAAI,CAAE,OAAQ,EAAM,CAAC,EAC/BU,EAAsB,IAAIR,EAAsBK,EAASE,CAAG,EAC5DE,EAAsB,IAAIR,EAC9BO,EACAD,CACF,EAEA,KAAK,IAAME,EAAoB,IAAI,KAAKA,CAAmB,EAC3D,KAAK,IAAMA,EAAoB,IAAI,KAAKA,CAAmB,EAC3D,KAAK,MAAQA,EAAoB,MAAM,KAAKA,CAAmB,EAC/D,KAAK,OAASA,EAAoB,OAAO,KAAKA,CAAmB,EACjE,KAAK,SAAWA,EAAoB,SAAS,KAAKA,CAAmB,EACrE,KAAK,eACHA,EAAoB,eAAe,KAAKD,CAAmB,EAC7D,KAAK,aACHA,EAAoB,aAAa,KAAKA,CAAmB,EAC3D,KAAK,oBACHC,EAAoB,oBAAoB,KAAKA,CAAmB,EAClE,KAAK,eACHA,EAAoB,eAAe,KAAKA,CAAmB,EAC7D,KAAK,0BACHA,EAAoB,0BAA0B,KAAKA,CAAmB,CAC1E,CACF",
6
6
  "names": ["Graffiti", "Ajv", "GraffitiLocalSessionManager", "GraffitiLocalDatabase", "GraffitiSynchronize", "locationToUri", "uriToLocation", "GraffitiLocal", "options", "sessionManagerLocal", "ajv", "graffitiPouchDbBase", "graffitiSynchronize"]
7
7
  }
@@ -1,2 +1,2 @@
1
- import y from"ajv-draft-04";import{Repeater as d}from"@repeaterjs/repeater";import{applyPatch as b}from"fast-json-patch";import{applyGraffitiPatch as u,attemptAjvCompile as j,isActorAllowedGraffitiObject as O,locationToUri as m,maskGraffitiObject as G,unpackLocationOrUri as z}from"./utilities";class B{synchronizeEvents=new EventTarget;ajv;graffiti;constructor(t,e){this.ajv=e??new y({strict:!1}),this.graffiti=t}synchronizeDispatch(t,e){const i=new CustomEvent("change",{detail:{oldObject:t,newObject:e}});this.synchronizeEvents.dispatchEvent(i)}get=async(...t)=>{const e=await this.graffiti.get(...t);return this.synchronizeDispatch(e),e};put=async(...t)=>{const e=await this.graffiti.put(...t),i=t[0],r={...e,value:i.value,channels:i.channels,allowed:i.allowed,tombstone:!1};return this.synchronizeDispatch(e,r),e};patch=async(...t)=>{const e=await this.graffiti.patch(...t),i={...e};i.tombstone=!1;for(const r of["value","channels","allowed"])u(b,r,t[0],i);return this.synchronizeDispatch(e,i),e};delete=async(...t)=>{const e=await this.graffiti.delete(...t);return this.synchronizeDispatch(e),e};objectStream(t){const e=this.synchronizeDispatch.bind(this);return async function*(){let r=await t.next();for(;!r.done;)r.value.error||e(r.value.value),yield r.value,r=await t.next();return r.value}()}discover=(...t)=>{const e=this.graffiti.discover(...t);return this.objectStream(e)};recoverOrphans=(...t)=>{const e=this.graffiti.recoverOrphans(...t);return this.objectStream(e)};synchronize(t,e,i,r){const n=j(this.ajv,i);return new d(async(a,o)=>{const h=p=>{const{oldObject:l,newObject:v}=p.detail;for(const s of[v,l])if(s&&t(s)&&O(s,r)){const f={...s};if(G(f,e,r),n(f)){a({value:f});break}}};this.synchronizeEvents.addEventListener("change",h),await o,this.synchronizeEvents.removeEventListener("change",h)})}synchronizeDiscover=(...t)=>{const[e,i,r]=t;function n(c){return c.channels.some(a=>e.includes(a))}return this.synchronize(n,e,i,r)};synchronizeGet=(...t)=>{const[e,i,r]=t;function n(c){const a=m(c),{uri:o}=z(e);return a===o}return this.synchronize(n,[],i,r)};synchronizeRecoverOrphans=(...t)=>{const[e,i]=t;function r(n){return n.actor===i.actor&&n.channels.length===0}return this.synchronize(r,[],e,i)}}export{B as GraffitiSynchronize};
1
+ import y from"ajv-draft-04";import{Repeater as d}from"@repeaterjs/repeater";import{applyPatch as b}from"fast-json-patch";import{applyGraffitiPatch as u,attemptAjvCompile as j,isActorAllowedGraffitiObject as O,locationToUri as m,maskGraffitiObject as G,unpackLocationOrUri as z}from"./utilities.js";class B{synchronizeEvents=new EventTarget;ajv;graffiti;constructor(t,e){this.ajv=e??new y({strict:!1}),this.graffiti=t}synchronizeDispatch(t,e){const i=new CustomEvent("change",{detail:{oldObject:t,newObject:e}});this.synchronizeEvents.dispatchEvent(i)}get=async(...t)=>{const e=await this.graffiti.get(...t);return this.synchronizeDispatch(e),e};put=async(...t)=>{const e=await this.graffiti.put(...t),i=t[0],r={...e,value:i.value,channels:i.channels,allowed:i.allowed,tombstone:!1};return this.synchronizeDispatch(e,r),e};patch=async(...t)=>{const e=await this.graffiti.patch(...t),i={...e};i.tombstone=!1;for(const r of["value","channels","allowed"])u(b,r,t[0],i);return this.synchronizeDispatch(e,i),e};delete=async(...t)=>{const e=await this.graffiti.delete(...t);return this.synchronizeDispatch(e),e};objectStream(t){const e=this.synchronizeDispatch.bind(this);return async function*(){let r=await t.next();for(;!r.done;)r.value.error||e(r.value.value),yield r.value,r=await t.next();return r.value}()}discover=(...t)=>{const e=this.graffiti.discover(...t);return this.objectStream(e)};recoverOrphans=(...t)=>{const e=this.graffiti.recoverOrphans(...t);return this.objectStream(e)};synchronize(t,e,i,r){const n=j(this.ajv,i);return new d(async(a,o)=>{const h=p=>{const{oldObject:l,newObject:v}=p.detail;for(const s of[v,l])if(s&&t(s)&&O(s,r)){const f={...s};if(G(f,e,r),n(f)){a({value:f});break}}};this.synchronizeEvents.addEventListener("change",h),await o,this.synchronizeEvents.removeEventListener("change",h)})}synchronizeDiscover=(...t)=>{const[e,i,r]=t;function n(c){return c.channels.some(a=>e.includes(a))}return this.synchronize(n,e,i,r)};synchronizeGet=(...t)=>{const[e,i,r]=t;function n(c){const a=m(c),{uri:o}=z(e);return a===o}return this.synchronize(n,[],i,r)};synchronizeRecoverOrphans=(...t)=>{const[e,i]=t;function r(n){return n.actor===i.actor&&n.channels.length===0}return this.synchronize(r,[],e,i)}}export{B as GraffitiSynchronize};
2
2
  //# sourceMappingURL=synchronize.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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\";\n\ntype SynchronizeEvent = CustomEvent<{\n oldObject: GraffitiObjectBase;\n newObject?: GraffitiObjectBase;\n}>;\n\ntype GraffitiDatabaseMethods = Pick<\n Graffiti,\n \"get\" | \"put\" | \"patch\" | \"delete\" | \"discover\" | \"recoverOrphans\"\n>;\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 synchronizeEvents = new EventTarget();\n protected readonly ajv: Ajv;\n protected readonly graffiti: GraffitiDatabaseMethods;\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 synchronizeDispatch(\n oldObject: GraffitiObjectBase,\n newObject?: GraffitiObjectBase,\n ) {\n const event: SynchronizeEvent = new CustomEvent(\"change\", {\n detail: {\n oldObject,\n newObject,\n },\n });\n this.synchronizeEvents.dispatchEvent(event);\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 this.synchronizeDispatch(oldObject, newObject);\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 this.synchronizeDispatch(oldObject, newObject);\n return oldObject;\n };\n\n delete: Graffiti[\"delete\"] = async (...args) => {\n const oldObject = await this.graffiti.delete(...args);\n this.synchronizeDispatch(oldObject);\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 = (event: SynchronizeEvent) => {\n const { oldObject: oldObjectRaw, newObject: newObjectRaw } =\n event.detail;\n\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.synchronizeEvents.addEventListener(\n \"change\",\n callback as EventListener,\n );\n await stop;\n this.synchronizeEvents.removeEventListener(\n \"change\",\n callback as EventListener,\n );\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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",
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 SynchronizeEvent = CustomEvent<{\n oldObject: GraffitiObjectBase;\n newObject?: GraffitiObjectBase;\n}>;\n\ntype GraffitiDatabaseMethods = Pick<\n Graffiti,\n \"get\" | \"put\" | \"patch\" | \"delete\" | \"discover\" | \"recoverOrphans\"\n>;\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 synchronizeEvents = new EventTarget();\n protected readonly ajv: Ajv;\n protected readonly graffiti: GraffitiDatabaseMethods;\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 synchronizeDispatch(\n oldObject: GraffitiObjectBase,\n newObject?: GraffitiObjectBase,\n ) {\n const event: SynchronizeEvent = new CustomEvent(\"change\", {\n detail: {\n oldObject,\n newObject,\n },\n });\n this.synchronizeEvents.dispatchEvent(event);\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 this.synchronizeDispatch(oldObject, newObject);\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 this.synchronizeDispatch(oldObject, newObject);\n return oldObject;\n };\n\n delete: Graffiti[\"delete\"] = async (...args) => {\n const oldObject = await this.graffiti.delete(...args);\n this.synchronizeDispatch(oldObject);\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 = (event: SynchronizeEvent) => {\n const { oldObject: oldObjectRaw, newObject: newObjectRaw } =\n event.detail;\n\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.synchronizeEvents.addEventListener(\n \"change\",\n callback as EventListener,\n );\n await stop;\n this.synchronizeEvents.removeEventListener(\n \"change\",\n callback as EventListener,\n );\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
6
  "names": ["Ajv", "Repeater", "applyPatch", "applyGraffitiPatch", "attemptAjvCompile", "isActorAllowedGraffitiObject", "locationToUri", "maskGraffitiObject", "unpackLocationOrUri", "GraffitiSynchronize", "graffiti", "ajv", "oldObject", "newObject", "event", "args", "object", "partialObject", "prop", "iterator", "dispatch", "result", "matchObject", "channels", "schema", "session", "validate", "push", "stop", "callback", "oldObjectRaw", "newObjectRaw", "objectRaw", "channel", "locationOrUri", "objectUri", "uri"]
7
7
  }
@@ -1,2 +1,2 @@
1
- import{graffitiLocationTests as e,graffitiCRUDTests as o,graffitiSynchronizeTests as f,graffitiDiscoverTests as r,graffitiOrphanTests as a,graffitiChannelStatsTests as n}from"@graffiti-garden/api/tests";import{GraffitiLocal as c}from"./index";const s=()=>new c,t=()=>({actor:"someone"}),i=()=>({actor:"someoneelse"});e(s);o(s,t,i);f(s,t,i);r(s,t,i);a(s,t,i);n(s,t,i);
1
+ import{graffitiLocationTests as e,graffitiCRUDTests as o,graffitiSynchronizeTests as f,graffitiDiscoverTests as r,graffitiOrphanTests as a,graffitiChannelStatsTests as n}from"@graffiti-garden/api/tests";import{GraffitiLocal as c}from"./index";const s=()=>new c,t=()=>({actor:"someone"}),i=()=>({actor:"someoneelse"});e(s),o(s,t,i),f(s,t,i),r(s,t,i),a(s,t,i),n(s,t,i);
2
2
  //# sourceMappingURL=tests.spec.js.map