@graffiti-garden/implementation-local 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -64,8 +64,6 @@ Pieces of this implementation can be pulled out to use in other implementations.
64
64
  ```typescript
65
65
  // The basic database interface based on PouchDB
66
66
  import { GraffitiLocalDatabase } from "@graffiti-garden/implementation-local/database";
67
- // A wrapper around any implementation of the database methods that provides synchronize
68
- import { GraffitiSynchronize } from "@graffiti-garden/implementation-local/synchronize";
69
67
  // The log in and out methods and events - insecure but useful for testing
70
68
  import { GraffitiLocalSessionManager } from "@graffiti-garden/implementation-local/session-manager";
71
69
  // Various utilities for implementing the Graffiti API
@@ -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")),c=require("./utilities.js"),g=require("@repeaterjs/repeater"),D=M(require("ajv-draft-04")),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 g=Object.defineProperty;var _=Object.getOwnPropertyDescriptor;var R=Object.getOwnPropertyNames;var P=Object.getPrototypeOf,k=Object.prototype.hasOwnProperty;var C=(u,i)=>{for(var e in i)g(u,e,{get:i[e],enumerable:!0})},S=(u,i,e,o)=>{if(i&&typeof i=="object"||typeof i=="function")for(let t of R(i))!k.call(u,t)&&t!==e&&g(u,t,{get:()=>i[t],enumerable:!(o=_(i,t))||o.enumerable});return u};var A=(u,i,e)=>(e=u!=null?B(P(u)):{},S(i||!u||!u.__esModule?g(e,"default",{value:u,enumerable:!0}):e,u)),I=u=>S(g({},"__esModule",{value:!0}),u);var E={};C(E,{GraffitiLocalDatabase:()=>U});module.exports=I(E);var h=require("@graffiti-garden/api"),x=A(require("pouchdb")),s=require("./utilities.js"),j=require("@repeaterjs/repeater"),D=A(require("ajv")),L=require("fast-json-patch");class U{db;source="local";tombstoneRetention=864e5;ajv;constructor(i){this.ajv=new D.default({strict:!1}),this.source=i?.sourceName??this.source,this.tombstoneRetention=i?.tombstoneRetention??this.tombstoneRetention;const e={name:"graffitiDb",...i?.pouchDBOptions};this.db=new x.default(e.name,e),this.db.put({_id:"_design/indexes",views:{objectsPerChannelAndLastModified:{map:function(o){const t=o.lastModified.toString().padStart(15,"0");o.channels.forEach(function(a){const r=encodeURIComponent(a)+"/"+t;emit(r)})}.toString()},orphansPerActorAndLastModified:{map:function(o){if(o.channels.length===0){const t=o.lastModified.toString().padStart(15,"0"),a=encodeURIComponent(o.actor)+"/"+t;emit(a)}}.toString()},channelStatsPerActor:{map:function(o){o.tombstone||o.channels.forEach(function(t){const a=encodeURIComponent(o.actor)+"/"+encodeURIComponent(t);emit(a,o.lastModified)})}.toString(),reduce:"_stats"}}}).catch(o=>{if(!(o&&typeof o=="object"&&"name"in o&&o.name==="conflict"))throw o})}async queryByLocation(i){const e=(0,s.locationToUri)(i)+"/";return(await this.db.allDocs({startkey:e,endkey:e+"\uFFFF",include_docs:!0})).rows.map(a=>a.doc).reduce((a,r)=>(r&&a.push(r),a),[])}docId(i){return(0,s.locationToUri)(i)+"/"+(0,s.randomBase64)()}get=async(...i)=>{const[e,o,t]=i,{location:a}=(0,s.unpackLocationOrUri)(e),n=(await this.queryByLocation(a)).filter(b=>(0,s.isActorAllowedGraffitiObject)(b,t));if(!n.length)throw new h.GraffitiErrorNotFound;const f=n.reduce((b,w)=>(0,s.isObjectNewer)(b,w)?b:w),{_id:p,_rev:l,_conflicts:c,_attachments:m,...d}=f;if((0,s.maskGraffitiObject)(d,[],t),!(0,s.compileGraffitiObjectSchema)(this.ajv,o)(d))throw new h.GraffitiErrorSchemaMismatch;return d};async deleteAtLocation(i,e=!1){const t=(await this.queryByLocation(i)).filter(c=>!c.tombstone);if(!t.length)return;const a=t.map(c=>c.lastModified).reduce((c,m)=>c>m?c:m),r=t.filter(c=>!e||c.lastModified<a),n=t.filter(c=>e&&c.lastModified===a);if(n.length){const c=n.map(d=>d._id).reduce((d,y)=>d>y?d:y),m=n.filter(d=>d._id!==c);r.push(...m)}const f=e?a:new Date().getTime(),p=await this.db.bulkDocs(r.map(c=>({...c,tombstone:!0,lastModified:f})));let l;for(const c of p)if("ok"in c){const{id:m}=c,d=r.find(y=>y._id===m);if(d){const{_id:y,_rev:b,_conflicts:w,_attachments:v,...G}=d;l={...G,tombstone:!0,lastModified:f};break}}return l}delete=async(...i)=>{const[e,o]=i,{location:t}=(0,s.unpackLocationOrUri)(e);if(t.actor!==o.actor)throw new h.GraffitiErrorForbidden;const a=await this.deleteAtLocation(t);if(!a)throw new h.GraffitiErrorNotFound;return a};put=async(...i)=>{const[e,o]=i;if(e.actor&&e.actor!==o.actor)throw new h.GraffitiErrorForbidden;if(e.source&&e.source!==this.source)throw new h.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,s.randomBase64)(),source:this.source,actor:o.actor,tombstone:!1,lastModified:new Date().getTime()};await this.db.put({_id:this.docId(t),...t});const a=await this.deleteAtLocation(t,!0);return a||{...t,value:{},channels:[],allowed:void 0,tombstone:!0}};patch=async(...i)=>{const[e,o,t]=i,{location:a}=(0,s.unpackLocationOrUri)(o);if(a.actor!==t.actor)throw new h.GraffitiErrorForbidden;const r=await this.get(o,{},t);if(r.tombstone)throw new h.GraffitiErrorNotFound("The object you are trying to patch has been deleted");const n={...r};for(const f of["value","channels","allowed"])(0,s.applyGraffitiPatch)(L.applyPatch,f,e,n);if(typeof n.value!="object"||Array.isArray(n.value)||!n.value)throw new h.GraffitiErrorPatchError("value is no longer an object");if(!Array.isArray(n.channels)||!n.channels.every(f=>typeof f=="string"))throw new h.GraffitiErrorPatchError("channels are no longer an array of strings");if(n.allowed&&(!Array.isArray(n.allowed)||!n.allowed.every(f=>typeof f=="string")))throw new h.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),{...r,tombstone:!0,lastModified:n.lastModified}};queryLastModifiedSuffixes(i){let e="",o="\uFFFF";if(typeof i=="object"&&i.properties?.lastModified&&typeof i.properties.lastModified=="object"){const t=i.properties.lastModified,a=t.minimum,r=t.exclusiveMinimum;let n;r!==void 0?(n=Math.ceil(r),n===r&&n++):a!==void 0&&(n=Math.ceil(a)),n!==void 0&&(e=n.toString().padStart(15,"0"));const f=t.maximum,p=t.exclusiveMaximum;let l;p!==void 0?(l=Math.floor(p),l===p&&l--):f!==void 0&&(l=Math.floor(f)),l!==void 0&&(o=l.toString().padStart(15,"0"))}return{startKeySuffix:e,endKeySuffix:o}}discover=(...i)=>{const[e,o,t]=i,a=(0,s.compileGraffitiObjectSchema)(this.ajv,o),{startKeySuffix:r,endKeySuffix:n}=this.queryLastModifiedSuffixes(o);return new j.Repeater(async(p,l)=>{const c=new Set;for(const m of e){const d=encodeURIComponent(m)+"/",y=d+r,b=d+n,w=await this.db.query("indexes/objectsPerChannelAndLastModified",{startkey:y,endkey:b,include_docs:!0});for(const v of w.rows){const G=v.doc;if(!G)continue;const{_id:O,_rev:T,...M}=G;c.has(O)||(c.add(O),(0,s.isActorAllowedGraffitiObject)(G,t)&&((0,s.maskGraffitiObject)(M,e,t),a(M)&&await p({value:M})))}}return l(),{tombstoneRetention:this.tombstoneRetention}})};recoverOrphans=(i,e)=>{const o=(0,s.compileGraffitiObjectSchema)(this.ajv,i),{startKeySuffix:t,endKeySuffix:a}=this.queryLastModifiedSuffixes(i),r=encodeURIComponent(e.actor)+"/",n=r+t,f=r+a;return new j.Repeater(async(l,c)=>{const m=await this.db.query("indexes/orphansPerActorAndLastModified",{startkey:n,endkey:f,include_docs:!0});for(const d of m.rows){const y=d.doc;if(!y)continue;const{_id:b,_rev:w,...v}=y;o(v)&&await l({value:v})}return c(),{tombstoneRetention:this.tombstoneRetention}})};channelStats=i=>new j.Repeater(async(o,t)=>{const a=encodeURIComponent(i.actor)+"/",r=await this.db.query("indexes/channelStatsPerActor",{startkey:a,endkey:a+"\uFFFF",reduce:!0,group:!0});for(const n of r.rows){const f=n.key.split("/")[1];if(typeof f!="string")continue;const{count:p,max:l}=n.value;typeof p!="number"||typeof l!="number"||await o({value:{channel:decodeURIComponent(f),count:p,lastModified:l}})}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.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
- "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"]
4
+ "sourcesContent": ["import type {\n Graffiti,\n GraffitiObjectBase,\n GraffitiLocation,\n JSONSchema,\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 maskGraffitiObject,\n isActorAllowedGraffitiObject,\n isObjectNewer,\n compileGraffitiObjectSchema,\n} from \"./utilities.js\";\nimport { Repeater } from \"@repeaterjs/repeater\";\nimport Ajv from \"ajv\";\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) {\n this.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, _conflicts, _attachments, ...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 = compileGraffitiObjectSchema(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: JSONSchema) {\n // Use the index for queries over ranges of lastModified\n let startKeySuffix = \"\";\n let endKeySuffix = \"\\uffff\";\n if (\n typeof schema === \"object\" &&\n schema.properties?.lastModified &&\n typeof schema.properties.lastModified === \"object\"\n ) {\n const lastModifiedSchema = schema.properties.lastModified;\n\n const minimum = lastModifiedSchema.minimum;\n const exclusiveMinimum = lastModifiedSchema.exclusiveMinimum;\n\n let intMinimum: number | undefined;\n if (exclusiveMinimum !== undefined) {\n intMinimum = Math.ceil(exclusiveMinimum);\n intMinimum === exclusiveMinimum && intMinimum++;\n } else if (minimum !== undefined) {\n intMinimum = Math.ceil(minimum);\n }\n\n if (intMinimum !== undefined) {\n startKeySuffix = intMinimum.toString().padStart(15, \"0\");\n }\n\n const maximum = lastModifiedSchema.maximum;\n const exclusiveMaximum = lastModifiedSchema.exclusiveMaximum;\n\n let intMaximum: number | undefined;\n if (exclusiveMaximum !== undefined) {\n intMaximum = Math.floor(exclusiveMaximum);\n intMaximum === exclusiveMaximum && intMaximum--;\n } else if (maximum !== undefined) {\n intMaximum = Math.floor(maximum);\n }\n\n if (intMaximum !== undefined) {\n endKeySuffix = intMaximum.toString().padStart(15, \"0\");\n }\n }\n return {\n startKeySuffix,\n endKeySuffix,\n };\n }\n\n discover: Graffiti[\"discover\"] = (...args) => {\n const [channels, schema, session] = args;\n const validate = compileGraffitiObjectSchema(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 = compileGraffitiObjectSchema(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
+ "names": ["database_exports", "__export", "GraffitiLocalDatabase", "__toCommonJS", "import_api", "import_pouchdb", "import_utilities", "import_repeater", "import_ajv", "import_fast_json_patch", "options", "Ajv", "pouchDbOptions", "PouchDB", "object", "paddedLastModified", "channel", "id", "error", "location", "uri", "row", "acc", "doc", "args", "locationOrUri", "schema", "session", "docs", "a", "b", "_id", "_rev", "_conflicts", "_attachments", "keepLatest", "docsAtLocation", "latestModified", "docsToDelete", "concurrentDocsAll", "keepDocId", "concurrentDocsToDelete", "lastModified", "deleteResults", "deletedObject", "resultOrError", "deletedDoc", "objectPartial", "previousObject", "patch", "originalObject", "patchObject", "prop", "allowed", "startKeySuffix", "endKeySuffix", "lastModifiedSchema", "minimum", "exclusiveMinimum", "intMinimum", "maximum", "exclusiveMaximum", "intMaximum", "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 h=Object.create;var e=Object.defineProperty;var d=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var G=Object.getPrototypeOf,L=Object.prototype.hasOwnProperty;var v=(t,a)=>{for(var o in a)e(t,o,{get:a[o],enumerable:!0})},n=(t,a,o,i)=>{if(a&&typeof a=="object"||typeof a=="function")for(let s of g(a))!L.call(t,s)&&s!==o&&e(t,s,{get:()=>a[s],enumerable:!(i=d(a,s))||i.enumerable});return t};var b=(t,a,o)=>(o=t!=null?h(G(t)):{},n(a||!t||!t.__esModule?e(o,"default",{value:t,enumerable:!0}):o,t)),u=t=>n(e({},"__esModule",{value:!0}),t);var M={};v(M,{GraffitiLocal:()=>m});module.exports=u(M);var c=require("@graffiti-garden/api"),f=b(require("ajv-draft-04")),l=require("./session-manager.js"),p=require("./database.js"),r=require("./utilities.js");class m extends c.Graffiti{locationToUri=r.locationToUri;uriToLocation=r.uriToLocation;sessionManagerLocal=new l.GraffitiLocalSessionManager;login=this.sessionManagerLocal.login.bind(this.sessionManagerLocal);logout=this.sessionManagerLocal.logout.bind(this.sessionManagerLocal);sessionEvents=this.sessionManagerLocal.sessionEvents;put;get;patch;delete;discover;recoverOrphans;channelStats;constructor(a){super();const o=new f.default({strict:!1}),i=new p.GraffitiLocalDatabase(a,o);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(i),this.channelStats=i.channelStats.bind(i)}}
1
+ "use strict";var s=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var h=Object.getOwnPropertyNames;var p=Object.prototype.hasOwnProperty;var d=(a,t)=>{for(var i in t)s(a,i,{get:t[i],enumerable:!0})},g=(a,t,i,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of h(t))!p.call(a,o)&&o!==i&&s(a,o,{get:()=>t[o],enumerable:!(r=l(t,o))||r.enumerable});return a};var G=a=>g(s({},"__esModule",{value:!0}),a);var b={};d(b,{GraffitiLocal:()=>L});module.exports=G(b);var n=require("@graffiti-garden/api"),c=require("./session-manager.js"),f=require("./database.js"),e=require("./utilities.js");class L extends n.Graffiti{locationToUri=e.locationToUri;uriToLocation=e.uriToLocation;sessionManagerLocal=new c.GraffitiLocalSessionManager;login=this.sessionManagerLocal.login.bind(this.sessionManagerLocal);logout=this.sessionManagerLocal.logout.bind(this.sessionManagerLocal);sessionEvents=this.sessionManagerLocal.sessionEvents;put;get;patch;delete;discover;recoverOrphans;channelStats;constructor(t){super();const i=new f.GraffitiLocalDatabase(t);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(i),this.channelStats=i.channelStats.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.js\";\nimport {\n GraffitiLocalDatabase,\n type GraffitiLocalOptions,\n} from \"./database.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 protected sessionManagerLocal = new GraffitiLocalSessionManager();\n login = this.sessionManagerLocal.login.bind(this.sessionManagerLocal);\n logout = this.sessionManagerLocal.logout.bind(this.sessionManagerLocal);\n sessionEvents = this.sessionManagerLocal.sessionEvents;\n\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 channelStats: Graffiti[\"channelStats\"];\n\n constructor(options?: GraffitiLocalOptions) {\n super();\n\n const ajv = new Ajv({ strict: false });\n const graffitiPouchDbBase = new GraffitiLocalDatabase(options, ajv);\n\n this.put = graffitiPouchDbBase.put.bind(graffitiPouchDbBase);\n this.get = graffitiPouchDbBase.get.bind(graffitiPouchDbBase);\n this.patch = graffitiPouchDbBase.patch.bind(graffitiPouchDbBase);\n this.delete = graffitiPouchDbBase.delete.bind(graffitiPouchDbBase);\n this.discover = graffitiPouchDbBase.discover.bind(graffitiPouchDbBase);\n this.recoverOrphans =\n graffitiPouchDbBase.recoverOrphans.bind(graffitiPouchDbBase);\n this.channelStats =\n graffitiPouchDbBase.channelStats.bind(graffitiPouchDbBase);\n }\n}\n"],
5
- "mappings": "0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,mBAAAE,IAAA,eAAAC,EAAAH,GAAA,IAAAI,EAAyB,gCACzBC,EAAgB,2BAChBC,EAA4C,gCAC5CC,EAGO,yBACPC,EAA6C,0BAWtC,MAAMN,UAAsB,UAAS,CAC1C,cAAgB,gBAChB,cAAgB,gBAEN,oBAAsB,IAAI,8BACpC,MAAQ,KAAK,oBAAoB,MAAM,KAAK,KAAK,mBAAmB,EACpE,OAAS,KAAK,oBAAoB,OAAO,KAAK,KAAK,mBAAmB,EACtE,cAAgB,KAAK,oBAAoB,cAEzC,IACA,IACA,MACA,OACA,SACA,eACA,aAEA,YAAYO,EAAgC,CAC1C,MAAM,EAEN,MAAMC,EAAM,IAAI,EAAAC,QAAI,CAAE,OAAQ,EAAM,CAAC,EAC/BC,EAAsB,IAAI,wBAAsBH,EAASC,CAAG,EAElE,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,KAAKA,CAAmB,EAC7D,KAAK,aACHA,EAAoB,aAAa,KAAKA,CAAmB,CAC7D,CACF",
6
- "names": ["index_exports", "__export", "GraffitiLocal", "__toCommonJS", "import_api", "import_ajv_draft_04", "import_session_manager", "import_database", "import_utilities", "options", "ajv", "Ajv", "graffitiPouchDbBase"]
4
+ "sourcesContent": ["import { Graffiti } from \"@graffiti-garden/api\";\nimport { GraffitiLocalSessionManager } from \"./session-manager.js\";\nimport {\n GraffitiLocalDatabase,\n type GraffitiLocalOptions,\n} from \"./database.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 protected sessionManagerLocal = new GraffitiLocalSessionManager();\n login = this.sessionManagerLocal.login.bind(this.sessionManagerLocal);\n logout = this.sessionManagerLocal.logout.bind(this.sessionManagerLocal);\n sessionEvents = this.sessionManagerLocal.sessionEvents;\n\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 channelStats: Graffiti[\"channelStats\"];\n\n constructor(options?: GraffitiLocalOptions) {\n super();\n\n const graffitiPouchDbBase = new GraffitiLocalDatabase(options);\n\n this.put = graffitiPouchDbBase.put.bind(graffitiPouchDbBase);\n this.get = graffitiPouchDbBase.get.bind(graffitiPouchDbBase);\n this.patch = graffitiPouchDbBase.patch.bind(graffitiPouchDbBase);\n this.delete = graffitiPouchDbBase.delete.bind(graffitiPouchDbBase);\n this.discover = graffitiPouchDbBase.discover.bind(graffitiPouchDbBase);\n this.recoverOrphans =\n graffitiPouchDbBase.recoverOrphans.bind(graffitiPouchDbBase);\n this.channelStats =\n graffitiPouchDbBase.channelStats.bind(graffitiPouchDbBase);\n }\n}\n"],
5
+ "mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,mBAAAE,IAAA,eAAAC,EAAAH,GAAA,IAAAI,EAAyB,gCACzBC,EAA4C,gCAC5CC,EAGO,yBACPC,EAA6C,0BAWtC,MAAML,UAAsB,UAAS,CAC1C,cAAgB,gBAChB,cAAgB,gBAEN,oBAAsB,IAAI,8BACpC,MAAQ,KAAK,oBAAoB,MAAM,KAAK,KAAK,mBAAmB,EACpE,OAAS,KAAK,oBAAoB,OAAO,KAAK,KAAK,mBAAmB,EACtE,cAAgB,KAAK,oBAAoB,cAEzC,IACA,IACA,MACA,OACA,SACA,eACA,aAEA,YAAYM,EAAgC,CAC1C,MAAM,EAEN,MAAMC,EAAsB,IAAI,wBAAsBD,CAAO,EAE7D,KAAK,IAAMC,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,KAAKA,CAAmB,EAC7D,KAAK,aACHA,EAAoB,aAAa,KAAKA,CAAmB,CAC7D,CACF",
6
+ "names": ["index_exports", "__export", "GraffitiLocal", "__toCommonJS", "import_api", "import_session_manager", "import_database", "import_utilities", "options", "graffitiPouchDbBase"]
7
7
  }
@@ -1,2 +1,2 @@
1
- "use strict";var f=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var d=Object.getOwnPropertyNames;var m=Object.prototype.hasOwnProperty;var l=(e,t)=>{for(var r in t)f(e,r,{get:t[r],enumerable:!0})},u=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of d(t))!m.call(e,i)&&i!==r&&f(e,i,{get:()=>t[i],enumerable:!(o=p(t,i))||o.enumerable});return e};var G=e=>u(f({},"__esModule",{value:!0}),e);var E={};l(E,{applyGraffitiPatch:()=>g,attemptAjvCompile:()=>P,isActorAllowedGraffitiObject:()=>x,isObjectNewer:()=>y,locationToUri:()=>c,maskGraffitiObject:()=>S,randomBase64:()=>h,unpackLocationOrUri:()=>w,uriToLocation:()=>s});module.exports=G(E);var n=require("@graffiti-garden/api");const c=e=>`${e.source}/${encodeURIComponent(e.actor)}/${encodeURIComponent(e.name)}`,s=e=>{const t=e.split("/"),r=t.pop(),o=t.pop();if(!r||!o||!t.length)throw new n.GraffitiErrorInvalidUri;return{name:decodeURIComponent(r),actor:decodeURIComponent(o),source:t.join("/")}};function h(e=16){const t=new Uint8Array(e);return crypto.getRandomValues(t),btoa(String.fromCodePoint(...t)).replace(/\+/g,"-").replace(/\//g,"_").replace(/\=+$/,"")}function w(e){return typeof e=="string"?{location:s(e),uri:e}:{location:{name:e.name,actor:e.actor,source:e.source},uri:c(e)}}function y(e,t){return e.lastModified>t.lastModified||e.lastModified===t.lastModified&&!e.tombstone&&t.tombstone}function g(e,t,r,o){const i=r[t];if(!(!i||!i.length))try{o[t]=e(o[t],i,!0,!1).newDocument}catch(a){throw typeof a=="object"&&a&&"name"in a&&typeof a.name=="string"&&"message"in a&&typeof a.message=="string"?a.name==="TEST_OPERATION_FAILED"?new n.GraffitiErrorPatchTestFailed(a.message):new n.GraffitiErrorPatchError(a.name+": "+a.message):a}}function P(e,t){try{return e.compile(t)}catch(r){throw new n.GraffitiErrorInvalidSchema(r instanceof Error?r.message:void 0)}}function S(e,t,r){e.actor!==r?.actor&&(e.allowed=e.allowed&&r?[r.actor]:void 0,e.channels=e.channels.filter(o=>t.includes(o)))}function x(e,t){return e.allowed===void 0||e.allowed===null||!!t?.actor&&(e.actor===t.actor||e.allowed.includes(t.actor))}
1
+ "use strict";var f=Object.defineProperty;var d=Object.getOwnPropertyDescriptor;var p=Object.getOwnPropertyNames;var m=Object.prototype.hasOwnProperty;var l=(e,t)=>{for(var r in t)f(e,r,{get:t[r],enumerable:!0})},u=(e,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of p(t))!m.call(e,o)&&o!==r&&f(e,o,{get:()=>t[o],enumerable:!(i=d(t,o))||i.enumerable});return e};var G=e=>u(f({},"__esModule",{value:!0}),e);var P={};l(P,{applyGraffitiPatch:()=>g,compileGraffitiObjectSchema:()=>b,isActorAllowedGraffitiObject:()=>O,isObjectNewer:()=>y,locationToUri:()=>c,maskGraffitiObject:()=>S,randomBase64:()=>h,unpackLocationOrUri:()=>w,uriToLocation:()=>s});module.exports=G(P);var n=require("@graffiti-garden/api");const c=e=>`${e.source}/${encodeURIComponent(e.actor)}/${encodeURIComponent(e.name)}`,s=e=>{const t=e.split("/"),r=t.pop(),i=t.pop();if(!r||!i||!t.length)throw new n.GraffitiErrorInvalidUri;return{name:decodeURIComponent(r),actor:decodeURIComponent(i),source:t.join("/")}};function h(e=16){const t=new Uint8Array(e);return crypto.getRandomValues(t),btoa(String.fromCodePoint(...t)).replace(/\+/g,"-").replace(/\//g,"_").replace(/\=+$/,"")}function w(e){return typeof e=="string"?{location:s(e),uri:e}:{location:{name:e.name,actor:e.actor,source:e.source},uri:c(e)}}function y(e,t){return e.lastModified>t.lastModified||e.lastModified===t.lastModified&&!e.tombstone&&t.tombstone}function g(e,t,r,i){const o=r[t];if(!(!o||!o.length))try{i[t]=e(i[t],o,!0,!1).newDocument}catch(a){throw typeof a=="object"&&a&&"name"in a&&typeof a.name=="string"&&"message"in a&&typeof a.message=="string"?a.name==="TEST_OPERATION_FAILED"?new n.GraffitiErrorPatchTestFailed(a.message):new n.GraffitiErrorPatchError(a.name+": "+a.message):a}}function b(e,t){try{return e.compile(t)}catch(r){throw new n.GraffitiErrorInvalidSchema(r instanceof Error?r.message:void 0)}}function S(e,t,r){e.actor!==r?.actor&&(e.allowed=e.allowed&&r?[r.actor]:void 0,e.channels=e.channels.filter(i=>t.includes(i)))}function O(e,t){return e.allowed===void 0||e.allowed===null||!!t?.actor&&(e.actor===t.actor||e.allowed.includes(t.actor))}
2
2
  //# sourceMappingURL=utilities.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utilities.ts"],
4
- "sourcesContent": ["import {\n GraffitiErrorInvalidSchema,\n GraffitiErrorInvalidUri,\n GraffitiErrorPatchError,\n GraffitiErrorPatchTestFailed,\n} from \"@graffiti-garden/api\";\nimport type {\n Graffiti,\n GraffitiObjectBase,\n GraffitiLocation,\n GraffitiPatch,\n JSONSchema4,\n GraffitiSession,\n} from \"@graffiti-garden/api\";\nimport type { Ajv } from \"ajv\";\nimport type { applyPatch } from \"fast-json-patch\";\n\nexport const locationToUri: Graffiti[\"locationToUri\"] = (location) => {\n return `${location.source}/${encodeURIComponent(location.actor)}/${encodeURIComponent(location.name)}`;\n};\n\nexport const uriToLocation: Graffiti[\"uriToLocation\"] = (uri) => {\n const parts = uri.split(\"/\");\n const nameEncoded = parts.pop();\n const webIdEncoded = parts.pop();\n if (!nameEncoded || !webIdEncoded || !parts.length) {\n throw new GraffitiErrorInvalidUri();\n }\n return {\n name: decodeURIComponent(nameEncoded),\n actor: decodeURIComponent(webIdEncoded),\n source: parts.join(\"/\"),\n };\n};\n\nexport function randomBase64(numBytes: number = 16) {\n const bytes = new Uint8Array(numBytes);\n crypto.getRandomValues(bytes);\n // Convert it to base64\n const base64 = btoa(String.fromCodePoint(...bytes));\n // Make sure it is url safe\n return base64.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/\\=+$/, \"\");\n}\n\nexport function unpackLocationOrUri(locationOrUri: GraffitiLocation | string) {\n if (typeof locationOrUri === \"string\") {\n return {\n location: uriToLocation(locationOrUri),\n uri: locationOrUri,\n };\n } else {\n return {\n location: {\n name: locationOrUri.name,\n actor: locationOrUri.actor,\n source: locationOrUri.source,\n },\n uri: locationToUri(locationOrUri),\n };\n }\n}\n\nexport function isObjectNewer(\n left: GraffitiObjectBase,\n right: GraffitiObjectBase,\n) {\n return (\n left.lastModified > right.lastModified ||\n (left.lastModified === right.lastModified &&\n !left.tombstone &&\n right.tombstone)\n );\n}\n\nexport function applyGraffitiPatch<Prop extends keyof GraffitiPatch>(\n apply: typeof applyPatch,\n prop: Prop,\n patch: GraffitiPatch,\n object: GraffitiObjectBase,\n): void {\n const ops = patch[prop];\n if (!ops || !ops.length) return;\n try {\n object[prop] = apply(object[prop], ops, true, false).newDocument;\n } catch (e) {\n if (\n typeof e === \"object\" &&\n e &&\n \"name\" in e &&\n typeof e.name === \"string\" &&\n \"message\" in e &&\n typeof e.message === \"string\"\n ) {\n if (e.name === \"TEST_OPERATION_FAILED\") {\n throw new GraffitiErrorPatchTestFailed(e.message);\n } else {\n throw new GraffitiErrorPatchError(e.name + \": \" + e.message);\n }\n } else {\n throw e;\n }\n }\n}\n\nexport function attemptAjvCompile<Schema extends JSONSchema4>(\n ajv: Ajv,\n schema: Schema,\n) {\n try {\n return ajv.compile(schema);\n } catch (error) {\n throw new GraffitiErrorInvalidSchema(\n error instanceof Error ? error.message : undefined,\n );\n }\n}\n\nexport function maskGraffitiObject(\n object: GraffitiObjectBase,\n channels: string[],\n session?: GraffitiSession | null,\n): void {\n if (object.actor !== session?.actor) {\n object.allowed = object.allowed && session ? [session.actor] : undefined;\n object.channels = object.channels.filter((channel) =>\n channels.includes(channel),\n );\n }\n}\nexport function isActorAllowedGraffitiObject(\n object: GraffitiObjectBase,\n session?: GraffitiSession | null,\n) {\n return (\n object.allowed === undefined ||\n object.allowed === null ||\n (!!session?.actor &&\n (object.actor === session.actor ||\n object.allowed.includes(session.actor)))\n );\n}\n"],
5
- "mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,wBAAAE,EAAA,sBAAAC,EAAA,iCAAAC,EAAA,kBAAAC,EAAA,kBAAAC,EAAA,uBAAAC,EAAA,iBAAAC,EAAA,wBAAAC,EAAA,kBAAAC,IAAA,eAAAC,EAAAX,GAAA,IAAAY,EAKO,gCAYA,MAAMN,EAA4CO,GAChD,GAAGA,EAAS,MAAM,IAAI,mBAAmBA,EAAS,KAAK,CAAC,IAAI,mBAAmBA,EAAS,IAAI,CAAC,GAGzFH,EAA4CI,GAAQ,CAC/D,MAAMC,EAAQD,EAAI,MAAM,GAAG,EACrBE,EAAcD,EAAM,IAAI,EACxBE,EAAeF,EAAM,IAAI,EAC/B,GAAI,CAACC,GAAe,CAACC,GAAgB,CAACF,EAAM,OAC1C,MAAM,IAAI,0BAEZ,MAAO,CACL,KAAM,mBAAmBC,CAAW,EACpC,MAAO,mBAAmBC,CAAY,EACtC,OAAQF,EAAM,KAAK,GAAG,CACxB,CACF,EAEO,SAASP,EAAaU,EAAmB,GAAI,CAClD,MAAMC,EAAQ,IAAI,WAAWD,CAAQ,EACrC,cAAO,gBAAgBC,CAAK,EAEb,KAAK,OAAO,cAAc,GAAGA,CAAK,CAAC,EAEpC,QAAQ,MAAO,GAAG,EAAE,QAAQ,MAAO,GAAG,EAAE,QAAQ,OAAQ,EAAE,CAC1E,CAEO,SAASV,EAAoBW,EAA0C,CAC5E,OAAI,OAAOA,GAAkB,SACpB,CACL,SAAUV,EAAcU,CAAa,EACrC,IAAKA,CACP,EAEO,CACL,SAAU,CACR,KAAMA,EAAc,KACpB,MAAOA,EAAc,MACrB,OAAQA,EAAc,MACxB,EACA,IAAKd,EAAcc,CAAa,CAClC,CAEJ,CAEO,SAASf,EACdgB,EACAC,EACA,CACA,OACED,EAAK,aAAeC,EAAM,cACzBD,EAAK,eAAiBC,EAAM,cAC3B,CAACD,EAAK,WACNC,EAAM,SAEZ,CAEO,SAASpB,EACdqB,EACAC,EACAC,EACAC,EACM,CACN,MAAMC,EAAMF,EAAMD,CAAI,EACtB,GAAI,GAACG,GAAO,CAACA,EAAI,QACjB,GAAI,CACFD,EAAOF,CAAI,EAAID,EAAMG,EAAOF,CAAI,EAAGG,EAAK,GAAM,EAAK,EAAE,WACvD,OAASC,EAAG,CACV,MACE,OAAOA,GAAM,UACbA,GACA,SAAUA,GACV,OAAOA,EAAE,MAAS,UAClB,YAAaA,GACb,OAAOA,EAAE,SAAY,SAEjBA,EAAE,OAAS,wBACP,IAAI,+BAA6BA,EAAE,OAAO,EAE1C,IAAI,0BAAwBA,EAAE,KAAO,KAAOA,EAAE,OAAO,EAGvDA,CAEV,CACF,CAEO,SAASzB,EACd0B,EACAC,EACA,CACA,GAAI,CACF,OAAOD,EAAI,QAAQC,CAAM,CAC3B,OAASC,EAAO,CACd,MAAM,IAAI,6BACRA,aAAiB,MAAQA,EAAM,QAAU,MAC3C,CACF,CACF,CAEO,SAASxB,EACdmB,EACAM,EACAC,EACM,CACFP,EAAO,QAAUO,GAAS,QAC5BP,EAAO,QAAUA,EAAO,SAAWO,EAAU,CAACA,EAAQ,KAAK,EAAI,OAC/DP,EAAO,SAAWA,EAAO,SAAS,OAAQQ,GACxCF,EAAS,SAASE,CAAO,CAC3B,EAEJ,CACO,SAAS9B,EACdsB,EACAO,EACA,CACA,OACEP,EAAO,UAAY,QACnBA,EAAO,UAAY,MAClB,CAAC,CAACO,GAAS,QACTP,EAAO,QAAUO,EAAQ,OACxBP,EAAO,QAAQ,SAASO,EAAQ,KAAK,EAE7C",
6
- "names": ["utilities_exports", "__export", "applyGraffitiPatch", "attemptAjvCompile", "isActorAllowedGraffitiObject", "isObjectNewer", "locationToUri", "maskGraffitiObject", "randomBase64", "unpackLocationOrUri", "uriToLocation", "__toCommonJS", "import_api", "location", "uri", "parts", "nameEncoded", "webIdEncoded", "numBytes", "bytes", "locationOrUri", "left", "right", "apply", "prop", "patch", "object", "ops", "e", "ajv", "schema", "error", "channels", "session", "channel"]
4
+ "sourcesContent": ["import {\n GraffitiErrorInvalidSchema,\n GraffitiErrorInvalidUri,\n GraffitiErrorPatchError,\n GraffitiErrorPatchTestFailed,\n} from \"@graffiti-garden/api\";\nimport type {\n Graffiti,\n GraffitiObject,\n GraffitiObjectBase,\n GraffitiLocation,\n GraffitiPatch,\n JSONSchema,\n GraffitiSession,\n} from \"@graffiti-garden/api\";\nimport type { Ajv } from \"ajv\";\nimport type { applyPatch } from \"fast-json-patch\";\n\nexport const locationToUri: Graffiti[\"locationToUri\"] = (location) => {\n return `${location.source}/${encodeURIComponent(location.actor)}/${encodeURIComponent(location.name)}`;\n};\n\nexport const uriToLocation: Graffiti[\"uriToLocation\"] = (uri) => {\n const parts = uri.split(\"/\");\n const nameEncoded = parts.pop();\n const webIdEncoded = parts.pop();\n if (!nameEncoded || !webIdEncoded || !parts.length) {\n throw new GraffitiErrorInvalidUri();\n }\n return {\n name: decodeURIComponent(nameEncoded),\n actor: decodeURIComponent(webIdEncoded),\n source: parts.join(\"/\"),\n };\n};\n\nexport function randomBase64(numBytes: number = 16) {\n const bytes = new Uint8Array(numBytes);\n crypto.getRandomValues(bytes);\n // Convert it to base64\n const base64 = btoa(String.fromCodePoint(...bytes));\n // Make sure it is url safe\n return base64.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/\\=+$/, \"\");\n}\n\nexport function unpackLocationOrUri(locationOrUri: GraffitiLocation | string) {\n if (typeof locationOrUri === \"string\") {\n return {\n location: uriToLocation(locationOrUri),\n uri: locationOrUri,\n };\n } else {\n return {\n location: {\n name: locationOrUri.name,\n actor: locationOrUri.actor,\n source: locationOrUri.source,\n },\n uri: locationToUri(locationOrUri),\n };\n }\n}\n\nexport function isObjectNewer(\n left: GraffitiObjectBase,\n right: GraffitiObjectBase,\n) {\n return (\n left.lastModified > right.lastModified ||\n (left.lastModified === right.lastModified &&\n !left.tombstone &&\n right.tombstone)\n );\n}\n\nexport function applyGraffitiPatch<Prop extends keyof GraffitiPatch>(\n apply: typeof applyPatch,\n prop: Prop,\n patch: GraffitiPatch,\n object: GraffitiObjectBase,\n): void {\n const ops = patch[prop];\n if (!ops || !ops.length) return;\n try {\n object[prop] = apply(object[prop], ops, true, false).newDocument;\n } catch (e) {\n if (\n typeof e === \"object\" &&\n e &&\n \"name\" in e &&\n typeof e.name === \"string\" &&\n \"message\" in e &&\n typeof e.message === \"string\"\n ) {\n if (e.name === \"TEST_OPERATION_FAILED\") {\n throw new GraffitiErrorPatchTestFailed(e.message);\n } else {\n throw new GraffitiErrorPatchError(e.name + \": \" + e.message);\n }\n } else {\n throw e;\n }\n }\n}\n\nexport function compileGraffitiObjectSchema<Schema extends JSONSchema>(\n ajv: Ajv,\n schema: Schema,\n) {\n try {\n // Force the validation guard because\n // it is too big for the type checker.\n // Fortunately json-schema-to-ts is\n // well tested against ajv.\n return ajv.compile(schema) as (\n data: GraffitiObjectBase,\n ) => data is GraffitiObject<Schema>;\n } catch (error) {\n throw new GraffitiErrorInvalidSchema(\n error instanceof Error ? error.message : undefined,\n );\n }\n}\n\nexport function maskGraffitiObject(\n object: GraffitiObjectBase,\n channels: string[],\n session?: GraffitiSession | null,\n): void {\n if (object.actor !== session?.actor) {\n object.allowed = object.allowed && session ? [session.actor] : undefined;\n object.channels = object.channels.filter((channel) =>\n channels.includes(channel),\n );\n }\n}\nexport function isActorAllowedGraffitiObject(\n object: GraffitiObjectBase,\n session?: GraffitiSession | null,\n) {\n return (\n object.allowed === undefined ||\n object.allowed === null ||\n (!!session?.actor &&\n (object.actor === session.actor ||\n object.allowed.includes(session.actor)))\n );\n}\n"],
5
+ "mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,wBAAAE,EAAA,gCAAAC,EAAA,iCAAAC,EAAA,kBAAAC,EAAA,kBAAAC,EAAA,uBAAAC,EAAA,iBAAAC,EAAA,wBAAAC,EAAA,kBAAAC,IAAA,eAAAC,EAAAX,GAAA,IAAAY,EAKO,gCAaA,MAAMN,EAA4CO,GAChD,GAAGA,EAAS,MAAM,IAAI,mBAAmBA,EAAS,KAAK,CAAC,IAAI,mBAAmBA,EAAS,IAAI,CAAC,GAGzFH,EAA4CI,GAAQ,CAC/D,MAAMC,EAAQD,EAAI,MAAM,GAAG,EACrBE,EAAcD,EAAM,IAAI,EACxBE,EAAeF,EAAM,IAAI,EAC/B,GAAI,CAACC,GAAe,CAACC,GAAgB,CAACF,EAAM,OAC1C,MAAM,IAAI,0BAEZ,MAAO,CACL,KAAM,mBAAmBC,CAAW,EACpC,MAAO,mBAAmBC,CAAY,EACtC,OAAQF,EAAM,KAAK,GAAG,CACxB,CACF,EAEO,SAASP,EAAaU,EAAmB,GAAI,CAClD,MAAMC,EAAQ,IAAI,WAAWD,CAAQ,EACrC,cAAO,gBAAgBC,CAAK,EAEb,KAAK,OAAO,cAAc,GAAGA,CAAK,CAAC,EAEpC,QAAQ,MAAO,GAAG,EAAE,QAAQ,MAAO,GAAG,EAAE,QAAQ,OAAQ,EAAE,CAC1E,CAEO,SAASV,EAAoBW,EAA0C,CAC5E,OAAI,OAAOA,GAAkB,SACpB,CACL,SAAUV,EAAcU,CAAa,EACrC,IAAKA,CACP,EAEO,CACL,SAAU,CACR,KAAMA,EAAc,KACpB,MAAOA,EAAc,MACrB,OAAQA,EAAc,MACxB,EACA,IAAKd,EAAcc,CAAa,CAClC,CAEJ,CAEO,SAASf,EACdgB,EACAC,EACA,CACA,OACED,EAAK,aAAeC,EAAM,cACzBD,EAAK,eAAiBC,EAAM,cAC3B,CAACD,EAAK,WACNC,EAAM,SAEZ,CAEO,SAASpB,EACdqB,EACAC,EACAC,EACAC,EACM,CACN,MAAMC,EAAMF,EAAMD,CAAI,EACtB,GAAI,GAACG,GAAO,CAACA,EAAI,QACjB,GAAI,CACFD,EAAOF,CAAI,EAAID,EAAMG,EAAOF,CAAI,EAAGG,EAAK,GAAM,EAAK,EAAE,WACvD,OAASC,EAAG,CACV,MACE,OAAOA,GAAM,UACbA,GACA,SAAUA,GACV,OAAOA,EAAE,MAAS,UAClB,YAAaA,GACb,OAAOA,EAAE,SAAY,SAEjBA,EAAE,OAAS,wBACP,IAAI,+BAA6BA,EAAE,OAAO,EAE1C,IAAI,0BAAwBA,EAAE,KAAO,KAAOA,EAAE,OAAO,EAGvDA,CAEV,CACF,CAEO,SAASzB,EACd0B,EACAC,EACA,CACA,GAAI,CAKF,OAAOD,EAAI,QAAQC,CAAM,CAG3B,OAASC,EAAO,CACd,MAAM,IAAI,6BACRA,aAAiB,MAAQA,EAAM,QAAU,MAC3C,CACF,CACF,CAEO,SAASxB,EACdmB,EACAM,EACAC,EACM,CACFP,EAAO,QAAUO,GAAS,QAC5BP,EAAO,QAAUA,EAAO,SAAWO,EAAU,CAACA,EAAQ,KAAK,EAAI,OAC/DP,EAAO,SAAWA,EAAO,SAAS,OAAQQ,GACxCF,EAAS,SAASE,CAAO,CAC3B,EAEJ,CACO,SAAS9B,EACdsB,EACAO,EACA,CACA,OACEP,EAAO,UAAY,QACnBA,EAAO,UAAY,MAClB,CAAC,CAACO,GAAS,QACTP,EAAO,QAAUO,EAAQ,OACxBP,EAAO,QAAQ,SAASO,EAAQ,KAAK,EAE7C",
6
+ "names": ["utilities_exports", "__export", "applyGraffitiPatch", "compileGraffitiObjectSchema", "isActorAllowedGraffitiObject", "isObjectNewer", "locationToUri", "maskGraffitiObject", "randomBase64", "unpackLocationOrUri", "uriToLocation", "__toCommonJS", "import_api", "location", "uri", "parts", "nameEncoded", "webIdEncoded", "numBytes", "bytes", "locationOrUri", "left", "right", "apply", "prop", "patch", "object", "ops", "e", "ajv", "schema", "error", "channels", "session", "channel"]
7
7
  }
@@ -1,5 +1,5 @@
1
- import type { Graffiti, GraffitiObjectBase, GraffitiLocation, JSONSchema4 } from "@graffiti-garden/api";
2
- import Ajv from "ajv-draft-04";
1
+ import type { Graffiti, GraffitiObjectBase, GraffitiLocation, JSONSchema } from "@graffiti-garden/api";
2
+ import Ajv from "ajv";
3
3
  /**
4
4
  * Constructor options for the GraffitiPoubchDB class.
5
5
  */
@@ -34,7 +34,7 @@ export declare class GraffitiLocalDatabase implements Pick<Graffiti, "get" | "pu
34
34
  protected readonly source: string;
35
35
  protected readonly tombstoneRetention: number;
36
36
  protected readonly ajv: Ajv;
37
- constructor(options?: GraffitiLocalOptions, ajv?: Ajv);
37
+ constructor(options?: GraffitiLocalOptions);
38
38
  protected queryByLocation(location: GraffitiLocation): Promise<PouchDB.Core.ExistingDocument<GraffitiObjectBase & PouchDB.Core.AllDocsMeta>[]>;
39
39
  protected docId(location: GraffitiLocation): string;
40
40
  get: Graffiti["get"];
@@ -50,7 +50,7 @@ export declare class GraffitiLocalDatabase implements Pick<Graffiti, "get" | "pu
50
50
  delete: Graffiti["delete"];
51
51
  put: Graffiti["put"];
52
52
  patch: Graffiti["patch"];
53
- protected queryLastModifiedSuffixes(schema: JSONSchema4): {
53
+ protected queryLastModifiedSuffixes(schema: JSONSchema): {
54
54
  startKeySuffix: string;
55
55
  endKeySuffix: string;
56
56
  };
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EACR,kBAAkB,EAClB,gBAAgB,EAChB,WAAW,EACZ,MAAM,sBAAsB,CAAC;AAmB9B,OAAO,GAAG,MAAM,cAAc,CAAC;AAG/B;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,qBAAqB,CAAC;IAC7D;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;GAGG;AACH,qBAAa,qBACX,YACE,IAAI,CACF,QAAQ,EACN,KAAK,GACL,KAAK,GACL,OAAO,GACP,QAAQ,GACR,UAAU,GACV,gBAAgB,GAChB,cAAc,CACjB;IAEH,SAAS,CAAC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IAC5D,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAW;IAC5C,SAAS,CAAC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAY;IACzD,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;gBAEhB,OAAO,CAAC,EAAE,oBAAoB,EAAE,GAAG,CAAC,EAAE,GAAG;cA6ErC,eAAe,CAAC,QAAQ,EAAE,gBAAgB;IAqB1D,SAAS,CAAC,KAAK,CAAC,QAAQ,EAAE,gBAAgB;IAI1C,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CA2BlB;IAEF;;;;;;;OAOG;cACa,gBAAgB,CAC9B,QAAQ,EAAE,gBAAgB,EAC1B,UAAU,GAAE,OAAe;IA+D7B,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAYxB;IAEF,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CAwClB;IAEF,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,CA+DtB;IAEF,SAAS,CAAC,yBAAyB,CAAC,MAAM,EAAE,WAAW;;;;IAyBvD,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAqD5B;IAEF,cAAc,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAoCxC;IAEF,YAAY,EAAE,QAAQ,CAAC,cAAc,CAAC,CA4BpC;CACH"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EACR,kBAAkB,EAClB,gBAAgB,EAChB,UAAU,EACX,MAAM,sBAAsB,CAAC;AAmB9B,OAAO,GAAG,MAAM,KAAK,CAAC;AAGtB;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,qBAAqB,CAAC;IAC7D;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;GAGG;AACH,qBAAa,qBACX,YACE,IAAI,CACF,QAAQ,EACN,KAAK,GACL,KAAK,GACL,OAAO,GACP,QAAQ,GACR,UAAU,GACV,gBAAgB,GAChB,cAAc,CACjB;IAEH,SAAS,CAAC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IAC5D,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAW;IAC5C,SAAS,CAAC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAY;IACzD,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;gBAEhB,OAAO,CAAC,EAAE,oBAAoB;cA6E1B,eAAe,CAAC,QAAQ,EAAE,gBAAgB;IAqB1D,SAAS,CAAC,KAAK,CAAC,QAAQ,EAAE,gBAAgB;IAI1C,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CA2BlB;IAEF;;;;;;;OAOG;cACa,gBAAgB,CAC9B,QAAQ,EAAE,gBAAgB,EAC1B,UAAU,GAAE,OAAe;IA+D7B,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAYxB;IAEF,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CAwClB;IAEF,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,CA+DtB;IAEF,SAAS,CAAC,yBAAyB,CAAC,MAAM,EAAE,UAAU;;;;IA+CtD,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAqD5B;IAEF,cAAc,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAoCxC;IAEF,YAAY,EAAE,QAAQ,CAAC,cAAc,CAAC,CA4BpC;CACH"}
@@ -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.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};
1
+ import{GraffitiErrorNotFound as G,GraffitiErrorSchemaMismatch as B,GraffitiErrorForbidden as w,GraffitiErrorPatchError as g}from"@graffiti-garden/api";import _ from"pouchdb";import{locationToUri as A,unpackLocationOrUri as j,randomBase64 as x,applyGraffitiPatch as R,maskGraffitiObject as D,isActorAllowedGraffitiObject as L,isObjectNewer as P,compileGraffitiObjectSchema as M}from"./utilities.js";import{Repeater as O}from"@repeaterjs/repeater";import k from"ajv";import{applyPatch as C}from"fast-json-patch";class J{db;source="local";tombstoneRetention=864e5;ajv;constructor(a){this.ajv=new k({strict:!1}),this.source=a?.sourceName??this.source,this.tombstoneRetention=a?.tombstoneRetention??this.tombstoneRetention;const n={name:"graffitiDb",...a?.pouchDBOptions};this.db=new _(n.name,n),this.db.put({_id:"_design/indexes",views:{objectsPerChannelAndLastModified:{map:function(t){const e=t.lastModified.toString().padStart(15,"0");t.channels.forEach(function(i){const r=encodeURIComponent(i)+"/"+e;emit(r)})}.toString()},orphansPerActorAndLastModified:{map:function(t){if(t.channels.length===0){const e=t.lastModified.toString().padStart(15,"0"),i=encodeURIComponent(t.actor)+"/"+e;emit(i)}}.toString()},channelStatsPerActor:{map:function(t){t.tombstone||t.channels.forEach(function(e){const i=encodeURIComponent(t.actor)+"/"+encodeURIComponent(e);emit(i,t.lastModified)})}.toString(),reduce:"_stats"}}}).catch(t=>{if(!(t&&typeof t=="object"&&"name"in t&&t.name==="conflict"))throw t})}async queryByLocation(a){const n=A(a)+"/";return(await this.db.allDocs({startkey:n,endkey:n+"\uFFFF",include_docs:!0})).rows.map(i=>i.doc).reduce((i,r)=>(r&&i.push(r),i),[])}docId(a){return A(a)+"/"+x()}get=async(...a)=>{const[n,t,e]=a,{location:i}=j(n),o=(await this.queryByLocation(i)).filter(p=>L(p,e));if(!o.length)throw new G;const c=o.reduce((p,m)=>P(p,m)?p:m),{_id:l,_rev:d,_conflicts:s,_attachments:u,...f}=c;if(D(f,[],e),!M(this.ajv,t)(f))throw new B;return f};async deleteAtLocation(a,n=!1){const e=(await this.queryByLocation(a)).filter(s=>!s.tombstone);if(!e.length)return;const i=e.map(s=>s.lastModified).reduce((s,u)=>s>u?s:u),r=e.filter(s=>!n||s.lastModified<i),o=e.filter(s=>n&&s.lastModified===i);if(o.length){const s=o.map(f=>f._id).reduce((f,h)=>f>h?f:h),u=o.filter(f=>f._id!==s);r.push(...u)}const c=n?i:new Date().getTime(),l=await this.db.bulkDocs(r.map(s=>({...s,tombstone:!0,lastModified:c})));let d;for(const s of l)if("ok"in s){const{id:u}=s,f=r.find(h=>h._id===u);if(f){const{_id:h,_rev:p,_conflicts:m,_attachments:y,...b}=f;d={...b,tombstone:!0,lastModified:c};break}}return d}delete=async(...a)=>{const[n,t]=a,{location:e}=j(n);if(e.actor!==t.actor)throw new w;const i=await this.deleteAtLocation(e);if(!i)throw new G;return i};put=async(...a)=>{const[n,t]=a;if(n.actor&&n.actor!==t.actor)throw new w;if(n.source&&n.source!==this.source)throw new w("Putting an object that does not match this source");const e={value:n.value,channels:n.channels,allowed:n.allowed,name:n.name??x(),source:this.source,actor:t.actor,tombstone:!1,lastModified:new Date().getTime()};await this.db.put({_id:this.docId(e),...e});const i=await this.deleteAtLocation(e,!0);return i||{...e,value:{},channels:[],allowed:void 0,tombstone:!0}};patch=async(...a)=>{const[n,t,e]=a,{location:i}=j(t);if(i.actor!==e.actor)throw new w;const r=await this.get(t,{},e);if(r.tombstone)throw new G("The object you are trying to patch has been deleted");const o={...r};for(const c of["value","channels","allowed"])R(C,c,n,o);if(typeof o.value!="object"||Array.isArray(o.value)||!o.value)throw new g("value is no longer an object");if(!Array.isArray(o.channels)||!o.channels.every(c=>typeof c=="string"))throw new g("channels are no longer an array of strings");if(o.allowed&&(!Array.isArray(o.allowed)||!o.allowed.every(c=>typeof c=="string")))throw new g("allowed list is not an array of strings");return o.lastModified=new Date().getTime(),await this.db.put({...o,_id:this.docId(o)}),await this.deleteAtLocation(o,!0),{...r,tombstone:!0,lastModified:o.lastModified}};queryLastModifiedSuffixes(a){let n="",t="\uFFFF";if(typeof a=="object"&&a.properties?.lastModified&&typeof a.properties.lastModified=="object"){const e=a.properties.lastModified,i=e.minimum,r=e.exclusiveMinimum;let o;r!==void 0?(o=Math.ceil(r),o===r&&o++):i!==void 0&&(o=Math.ceil(i)),o!==void 0&&(n=o.toString().padStart(15,"0"));const c=e.maximum,l=e.exclusiveMaximum;let d;l!==void 0?(d=Math.floor(l),d===l&&d--):c!==void 0&&(d=Math.floor(c)),d!==void 0&&(t=d.toString().padStart(15,"0"))}return{startKeySuffix:n,endKeySuffix:t}}discover=(...a)=>{const[n,t,e]=a,i=M(this.ajv,t),{startKeySuffix:r,endKeySuffix:o}=this.queryLastModifiedSuffixes(t);return new O(async(l,d)=>{const s=new Set;for(const u of n){const f=encodeURIComponent(u)+"/",h=f+r,p=f+o,m=await this.db.query("indexes/objectsPerChannelAndLastModified",{startkey:h,endkey:p,include_docs:!0});for(const y of m.rows){const b=y.doc;if(!b)continue;const{_id:S,_rev:U,...v}=b;s.has(S)||(s.add(S),L(b,e)&&(D(v,n,e),i(v)&&await l({value:v})))}}return d(),{tombstoneRetention:this.tombstoneRetention}})};recoverOrphans=(a,n)=>{const t=M(this.ajv,a),{startKeySuffix:e,endKeySuffix:i}=this.queryLastModifiedSuffixes(a),r=encodeURIComponent(n.actor)+"/",o=r+e,c=r+i;return new O(async(d,s)=>{const u=await this.db.query("indexes/orphansPerActorAndLastModified",{startkey:o,endkey:c,include_docs:!0});for(const f of u.rows){const h=f.doc;if(!h)continue;const{_id:p,_rev:m,...y}=h;t(y)&&await d({value:y})}return s(),{tombstoneRetention:this.tombstoneRetention}})};channelStats=a=>new O(async(t,e)=>{const i=encodeURIComponent(a.actor)+"/",r=await this.db.query("indexes/channelStatsPerActor",{startkey:i,endkey:i+"\uFFFF",reduce:!0,group:!0});for(const o of r.rows){const c=o.key.split("/")[1];if(typeof c!="string")continue;const{count:l,max:d}=o.value;typeof l!="number"||typeof d!="number"||await t({value:{channel:decodeURIComponent(c),count:l,lastModified:d}})}e()})}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.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
- "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"]
4
+ "sourcesContent": ["import type {\n Graffiti,\n GraffitiObjectBase,\n GraffitiLocation,\n JSONSchema,\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 maskGraffitiObject,\n isActorAllowedGraffitiObject,\n isObjectNewer,\n compileGraffitiObjectSchema,\n} from \"./utilities.js\";\nimport { Repeater } from \"@repeaterjs/repeater\";\nimport Ajv from \"ajv\";\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) {\n this.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, _conflicts, _attachments, ...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 = compileGraffitiObjectSchema(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: JSONSchema) {\n // Use the index for queries over ranges of lastModified\n let startKeySuffix = \"\";\n let endKeySuffix = \"\\uffff\";\n if (\n typeof schema === \"object\" &&\n schema.properties?.lastModified &&\n typeof schema.properties.lastModified === \"object\"\n ) {\n const lastModifiedSchema = schema.properties.lastModified;\n\n const minimum = lastModifiedSchema.minimum;\n const exclusiveMinimum = lastModifiedSchema.exclusiveMinimum;\n\n let intMinimum: number | undefined;\n if (exclusiveMinimum !== undefined) {\n intMinimum = Math.ceil(exclusiveMinimum);\n intMinimum === exclusiveMinimum && intMinimum++;\n } else if (minimum !== undefined) {\n intMinimum = Math.ceil(minimum);\n }\n\n if (intMinimum !== undefined) {\n startKeySuffix = intMinimum.toString().padStart(15, \"0\");\n }\n\n const maximum = lastModifiedSchema.maximum;\n const exclusiveMaximum = lastModifiedSchema.exclusiveMaximum;\n\n let intMaximum: number | undefined;\n if (exclusiveMaximum !== undefined) {\n intMaximum = Math.floor(exclusiveMaximum);\n intMaximum === exclusiveMaximum && intMaximum--;\n } else if (maximum !== undefined) {\n intMaximum = Math.floor(maximum);\n }\n\n if (intMaximum !== undefined) {\n endKeySuffix = intMaximum.toString().padStart(15, \"0\");\n }\n }\n return {\n startKeySuffix,\n endKeySuffix,\n };\n }\n\n discover: Graffiti[\"discover\"] = (...args) => {\n const [channels, schema, session] = args;\n const validate = compileGraffitiObjectSchema(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 = compileGraffitiObjectSchema(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
+ "names": ["GraffitiErrorNotFound", "GraffitiErrorSchemaMismatch", "GraffitiErrorForbidden", "GraffitiErrorPatchError", "PouchDB", "locationToUri", "unpackLocationOrUri", "randomBase64", "applyGraffitiPatch", "maskGraffitiObject", "isActorAllowedGraffitiObject", "isObjectNewer", "compileGraffitiObjectSchema", "Repeater", "Ajv", "applyPatch", "GraffitiLocalDatabase", "options", "pouchDbOptions", "object", "paddedLastModified", "channel", "id", "error", "location", "uri", "row", "acc", "doc", "args", "locationOrUri", "schema", "session", "docs", "a", "b", "_id", "_rev", "_conflicts", "_attachments", "keepLatest", "docsAtLocation", "latestModified", "docsToDelete", "concurrentDocsAll", "keepDocId", "concurrentDocsToDelete", "lastModified", "deleteResults", "deletedObject", "resultOrError", "deletedDoc", "objectPartial", "previousObject", "patch", "originalObject", "patchObject", "prop", "allowed", "startKeySuffix", "endKeySuffix", "lastModifiedSchema", "minimum", "exclusiveMinimum", "intMinimum", "maximum", "exclusiveMaximum", "intMaximum", "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 o}from"@graffiti-garden/api";import s from"ajv-draft-04";import{GraffitiLocalSessionManager as e}from"./session-manager.js";import{GraffitiLocalDatabase as r}from"./database.js";import{locationToUri as n,uriToLocation as c}from"./utilities.js";class G extends o{locationToUri=n;uriToLocation=c;sessionManagerLocal=new e;login=this.sessionManagerLocal.login.bind(this.sessionManagerLocal);logout=this.sessionManagerLocal.logout.bind(this.sessionManagerLocal);sessionEvents=this.sessionManagerLocal.sessionEvents;put;get;patch;delete;discover;recoverOrphans;channelStats;constructor(t){super();const a=new s({strict:!1}),i=new r(t,a);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(i),this.channelStats=i.channelStats.bind(i)}}export{G as GraffitiLocal};
1
+ import{Graffiti as a}from"@graffiti-garden/api";import{GraffitiLocalSessionManager as o}from"./session-manager.js";import{GraffitiLocalDatabase as e}from"./database.js";import{locationToUri as s,uriToLocation as r}from"./utilities.js";class p extends a{locationToUri=s;uriToLocation=r;sessionManagerLocal=new o;login=this.sessionManagerLocal.login.bind(this.sessionManagerLocal);logout=this.sessionManagerLocal.logout.bind(this.sessionManagerLocal);sessionEvents=this.sessionManagerLocal.sessionEvents;put;get;patch;delete;discover;recoverOrphans;channelStats;constructor(t){super();const i=new e(t);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(i),this.channelStats=i.channelStats.bind(i)}}export{p 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.js\";\nimport {\n GraffitiLocalDatabase,\n type GraffitiLocalOptions,\n} from \"./database.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 protected sessionManagerLocal = new GraffitiLocalSessionManager();\n login = this.sessionManagerLocal.login.bind(this.sessionManagerLocal);\n logout = this.sessionManagerLocal.logout.bind(this.sessionManagerLocal);\n sessionEvents = this.sessionManagerLocal.sessionEvents;\n\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 channelStats: Graffiti[\"channelStats\"];\n\n constructor(options?: GraffitiLocalOptions) {\n super();\n\n const ajv = new Ajv({ strict: false });\n const graffitiPouchDbBase = new GraffitiLocalDatabase(options, ajv);\n\n this.put = graffitiPouchDbBase.put.bind(graffitiPouchDbBase);\n this.get = graffitiPouchDbBase.get.bind(graffitiPouchDbBase);\n this.patch = graffitiPouchDbBase.patch.bind(graffitiPouchDbBase);\n this.delete = graffitiPouchDbBase.delete.bind(graffitiPouchDbBase);\n this.discover = graffitiPouchDbBase.discover.bind(graffitiPouchDbBase);\n this.recoverOrphans =\n graffitiPouchDbBase.recoverOrphans.bind(graffitiPouchDbBase);\n this.channelStats =\n graffitiPouchDbBase.channelStats.bind(graffitiPouchDbBase);\n }\n}\n"],
5
- "mappings": "AAAA,OAAS,YAAAA,MAAgB,uBACzB,OAAOC,MAAS,eAChB,OAAS,+BAAAC,MAAmC,uBAC5C,OACE,yBAAAC,MAEK,gBACP,OAAS,iBAAAC,EAAe,iBAAAC,MAAqB,iBAWtC,MAAMC,UAAsBN,CAAS,CAC1C,cAAgBI,EAChB,cAAgBC,EAEN,oBAAsB,IAAIH,EACpC,MAAQ,KAAK,oBAAoB,MAAM,KAAK,KAAK,mBAAmB,EACpE,OAAS,KAAK,oBAAoB,OAAO,KAAK,KAAK,mBAAmB,EACtE,cAAgB,KAAK,oBAAoB,cAEzC,IACA,IACA,MACA,OACA,SACA,eACA,aAEA,YAAYK,EAAgC,CAC1C,MAAM,EAEN,MAAMC,EAAM,IAAIP,EAAI,CAAE,OAAQ,EAAM,CAAC,EAC/BQ,EAAsB,IAAIN,EAAsBI,EAASC,CAAG,EAElE,KAAK,IAAMC,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,KAAKA,CAAmB,EAC7D,KAAK,aACHA,EAAoB,aAAa,KAAKA,CAAmB,CAC7D,CACF",
6
- "names": ["Graffiti", "Ajv", "GraffitiLocalSessionManager", "GraffitiLocalDatabase", "locationToUri", "uriToLocation", "GraffitiLocal", "options", "ajv", "graffitiPouchDbBase"]
4
+ "sourcesContent": ["import { Graffiti } from \"@graffiti-garden/api\";\nimport { GraffitiLocalSessionManager } from \"./session-manager.js\";\nimport {\n GraffitiLocalDatabase,\n type GraffitiLocalOptions,\n} from \"./database.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 protected sessionManagerLocal = new GraffitiLocalSessionManager();\n login = this.sessionManagerLocal.login.bind(this.sessionManagerLocal);\n logout = this.sessionManagerLocal.logout.bind(this.sessionManagerLocal);\n sessionEvents = this.sessionManagerLocal.sessionEvents;\n\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 channelStats: Graffiti[\"channelStats\"];\n\n constructor(options?: GraffitiLocalOptions) {\n super();\n\n const graffitiPouchDbBase = new GraffitiLocalDatabase(options);\n\n this.put = graffitiPouchDbBase.put.bind(graffitiPouchDbBase);\n this.get = graffitiPouchDbBase.get.bind(graffitiPouchDbBase);\n this.patch = graffitiPouchDbBase.patch.bind(graffitiPouchDbBase);\n this.delete = graffitiPouchDbBase.delete.bind(graffitiPouchDbBase);\n this.discover = graffitiPouchDbBase.discover.bind(graffitiPouchDbBase);\n this.recoverOrphans =\n graffitiPouchDbBase.recoverOrphans.bind(graffitiPouchDbBase);\n this.channelStats =\n graffitiPouchDbBase.channelStats.bind(graffitiPouchDbBase);\n }\n}\n"],
5
+ "mappings": "AAAA,OAAS,YAAAA,MAAgB,uBACzB,OAAS,+BAAAC,MAAmC,uBAC5C,OACE,yBAAAC,MAEK,gBACP,OAAS,iBAAAC,EAAe,iBAAAC,MAAqB,iBAWtC,MAAMC,UAAsBL,CAAS,CAC1C,cAAgBG,EAChB,cAAgBC,EAEN,oBAAsB,IAAIH,EACpC,MAAQ,KAAK,oBAAoB,MAAM,KAAK,KAAK,mBAAmB,EACpE,OAAS,KAAK,oBAAoB,OAAO,KAAK,KAAK,mBAAmB,EACtE,cAAgB,KAAK,oBAAoB,cAEzC,IACA,IACA,MACA,OACA,SACA,eACA,aAEA,YAAYK,EAAgC,CAC1C,MAAM,EAEN,MAAMC,EAAsB,IAAIL,EAAsBI,CAAO,EAE7D,KAAK,IAAMC,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,KAAKA,CAAmB,EAC7D,KAAK,aACHA,EAAoB,aAAa,KAAKA,CAAmB,CAC7D,CACF",
6
+ "names": ["Graffiti", "GraffitiLocalSessionManager", "GraffitiLocalDatabase", "locationToUri", "uriToLocation", "GraffitiLocal", "options", "graffitiPouchDbBase"]
7
7
  }