@graffiti-garden/implementation-local 0.6.4 → 1.0.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.
Files changed (67) hide show
  1. package/README.md +0 -1
  2. package/dist/browser/ajv-IY2ZY7VT.js +9 -0
  3. package/dist/browser/ajv-IY2ZY7VT.js.map +7 -0
  4. package/dist/browser/{chunk-KNUPPOQC.js → chunk-GE6AZATH.js} +2 -2
  5. package/dist/browser/{chunk-KNUPPOQC.js.map → chunk-GE6AZATH.js.map} +1 -1
  6. package/dist/browser/{index-browser.es-G37SKL53.js → index-browser.es-UXYPGJ2M.js} +2 -2
  7. package/dist/browser/{index-browser.es-G37SKL53.js.map → index-browser.es-UXYPGJ2M.js.map} +1 -1
  8. package/dist/browser/index.js +11 -2
  9. package/dist/browser/index.js.map +4 -4
  10. package/dist/cjs/identity.js +112 -0
  11. package/dist/cjs/identity.js.map +7 -0
  12. package/dist/cjs/index.js +43 -22
  13. package/dist/cjs/index.js.map +2 -2
  14. package/dist/cjs/media.js +111 -0
  15. package/dist/cjs/media.js.map +7 -0
  16. package/dist/cjs/objects.js +307 -0
  17. package/dist/cjs/objects.js.map +7 -0
  18. package/dist/cjs/tests.spec.js +1 -2
  19. package/dist/cjs/tests.spec.js.map +2 -2
  20. package/dist/cjs/utilities.js +68 -43
  21. package/dist/cjs/utilities.js.map +2 -2
  22. package/dist/esm/identity.js +92 -0
  23. package/dist/esm/identity.js.map +7 -0
  24. package/dist/esm/index.js +43 -24
  25. package/dist/esm/index.js.map +2 -2
  26. package/dist/esm/media.js +91 -0
  27. package/dist/esm/media.js.map +7 -0
  28. package/dist/esm/objects.js +285 -0
  29. package/dist/esm/objects.js.map +7 -0
  30. package/dist/esm/tests.spec.js +2 -4
  31. package/dist/esm/tests.spec.js.map +2 -2
  32. package/dist/esm/utilities.js +69 -48
  33. package/dist/esm/utilities.js.map +2 -2
  34. package/dist/{session-manager.d.ts → identity.d.ts} +7 -5
  35. package/dist/identity.d.ts.map +1 -0
  36. package/dist/index.d.ts +15 -13
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/media.d.ts +9 -0
  39. package/dist/media.d.ts.map +1 -0
  40. package/dist/objects.d.ts +63 -0
  41. package/dist/objects.d.ts.map +1 -0
  42. package/dist/utilities.d.ts +19 -8
  43. package/dist/utilities.d.ts.map +1 -1
  44. package/package.json +31 -19
  45. package/src/identity.ts +131 -0
  46. package/src/index.ts +44 -29
  47. package/src/media.ts +106 -0
  48. package/src/objects.ts +431 -0
  49. package/src/tests.spec.ts +2 -4
  50. package/src/utilities.ts +67 -87
  51. package/dist/browser/ajv-6AI3HK2A.js +0 -9
  52. package/dist/browser/ajv-6AI3HK2A.js.map +0 -7
  53. package/dist/browser/fast-json-patch-ZE7SZEYK.js +0 -19
  54. package/dist/browser/fast-json-patch-ZE7SZEYK.js.map +0 -7
  55. package/dist/cjs/database.js +0 -626
  56. package/dist/cjs/database.js.map +0 -7
  57. package/dist/cjs/session-manager.js +0 -107
  58. package/dist/cjs/session-manager.js.map +0 -7
  59. package/dist/database.d.ts +0 -106
  60. package/dist/database.d.ts.map +0 -1
  61. package/dist/esm/database.js +0 -608
  62. package/dist/esm/database.js.map +0 -7
  63. package/dist/esm/session-manager.js +0 -87
  64. package/dist/esm/session-manager.js.map +0 -7
  65. package/dist/session-manager.d.ts.map +0 -1
  66. package/src/database.ts +0 -921
  67. package/src/session-manager.ts +0 -123
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var identity_exports = {};
20
+ __export(identity_exports, {
21
+ GraffitiLocalIdentity: () => GraffitiLocalIdentity
22
+ });
23
+ module.exports = __toCommonJS(identity_exports);
24
+ var import_utilities = require("./utilities");
25
+ const DID_LOCAL_PREFIX = "did:local:";
26
+ class GraffitiLocalIdentity {
27
+ sessionEvents = new EventTarget();
28
+ handleToActor = async (handle) => {
29
+ const bytes = new TextEncoder().encode(handle);
30
+ const base64 = (0, import_utilities.encodeBase64)(bytes);
31
+ return `${DID_LOCAL_PREFIX}${base64}`;
32
+ };
33
+ actorToHandle = async (actor) => {
34
+ if (!actor.startsWith(DID_LOCAL_PREFIX)) {
35
+ throw new Error(`actor must start with ${DID_LOCAL_PREFIX}`);
36
+ }
37
+ const base64 = actor.slice(DID_LOCAL_PREFIX.length);
38
+ const bytes = (0, import_utilities.decodeBase64)(base64);
39
+ return new TextDecoder().decode(bytes);
40
+ };
41
+ constructor() {
42
+ const sessionRestorer = async () => {
43
+ await Promise.resolve();
44
+ for (const handle of this.getLoggedInHandles()) {
45
+ const event2 = new CustomEvent("login", {
46
+ detail: { session: { actor: await this.handleToActor(handle) } }
47
+ });
48
+ this.sessionEvents.dispatchEvent(event2);
49
+ }
50
+ const event = new CustomEvent(
51
+ "initialized",
52
+ { detail: {} }
53
+ );
54
+ this.sessionEvents.dispatchEvent(event);
55
+ };
56
+ sessionRestorer();
57
+ }
58
+ loggedInHandles = [];
59
+ getLoggedInHandles() {
60
+ if (typeof window !== "undefined") {
61
+ const handlesString = window.localStorage.getItem("graffiti-handles");
62
+ return handlesString ? handlesString.split(",").map(decodeURIComponent) : [];
63
+ } else {
64
+ return this.loggedInHandles;
65
+ }
66
+ }
67
+ setLoggedInHandles(handles) {
68
+ if (typeof window !== "undefined") {
69
+ window.localStorage.setItem("graffiti-handles", handles.join(","));
70
+ } else {
71
+ this.loggedInHandles = handles;
72
+ }
73
+ }
74
+ login = async (actor) => {
75
+ await new Promise((resolve) => setTimeout(resolve, 0));
76
+ let handle = actor ? await this.actorToHandle(actor) : void 0;
77
+ if (typeof window !== "undefined") {
78
+ const response = window.prompt("Choose a username to log in.", handle);
79
+ handle = response ?? void 0;
80
+ }
81
+ if (!handle) {
82
+ const detail = {
83
+ error: new Error("No handle provided to login")
84
+ };
85
+ const event = new CustomEvent("login", { detail });
86
+ this.sessionEvents.dispatchEvent(event);
87
+ } else {
88
+ const existingHandles = this.getLoggedInHandles();
89
+ if (!existingHandles.includes(handle)) {
90
+ this.setLoggedInHandles([...existingHandles, handle]);
91
+ }
92
+ window.location.reload();
93
+ }
94
+ };
95
+ logout = async (session) => {
96
+ const handle = await this.actorToHandle(session.actor);
97
+ const existingHandles = this.getLoggedInHandles();
98
+ const exists = existingHandles.includes(handle);
99
+ if (exists) {
100
+ this.setLoggedInHandles(existingHandles.filter((h) => h !== handle));
101
+ }
102
+ const detail = exists ? {
103
+ actor: session.actor
104
+ } : {
105
+ actor: session.actor,
106
+ error: new Error("Not logged in with that actor")
107
+ };
108
+ const event = new CustomEvent("logout", { detail });
109
+ this.sessionEvents.dispatchEvent(event);
110
+ };
111
+ }
112
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/identity.ts"],
4
+ "sourcesContent": ["import type {\n Graffiti,\n GraffitiLoginEvent,\n GraffitiLogoutEvent,\n GraffitiSessionInitializedEvent,\n} from \"@graffiti-garden/api\";\nimport { decodeBase64, encodeBase64 } from \"./utilities\";\n\nconst DID_LOCAL_PREFIX = \"did:local:\";\n\n/**\n * A class that implements the login methods\n * of the [Graffiti API]() for use in the browser.\n * It is completely insecure and should only be used\n * for testing and demonstrations.\n *\n * It uses `localStorage` to store login state and\n * window prompts rather than an oauth flow for log in.\n * It can be used in node.js but will not persist\n * login state and a proposed username must be provided.\n */\nexport class GraffitiLocalIdentity {\n sessionEvents: Graffiti[\"sessionEvents\"] = new EventTarget();\n\n handleToActor: Graffiti[\"handleToActor\"] = async (handle: string) => {\n const bytes = new TextEncoder().encode(handle);\n const base64 = encodeBase64(bytes);\n return `${DID_LOCAL_PREFIX}${base64}`;\n };\n\n actorToHandle: Graffiti[\"actorToHandle\"] = async (actor: string) => {\n if (!actor.startsWith(DID_LOCAL_PREFIX)) {\n throw new Error(`actor must start with ${DID_LOCAL_PREFIX}`);\n }\n const base64 = actor.slice(DID_LOCAL_PREFIX.length);\n const bytes = decodeBase64(base64);\n return new TextDecoder().decode(bytes);\n };\n\n constructor() {\n // Look for any existing sessions\n const sessionRestorer = async () => {\n // Allow listeners to be added first\n await Promise.resolve();\n\n // Restore previous sessions\n for (const handle of this.getLoggedInHandles()) {\n const event: GraffitiLoginEvent = new CustomEvent(\"login\", {\n detail: { session: { actor: await this.handleToActor(handle) } },\n });\n this.sessionEvents.dispatchEvent(event);\n }\n\n const event: GraffitiSessionInitializedEvent = new CustomEvent(\n \"initialized\",\n { detail: {} },\n );\n this.sessionEvents.dispatchEvent(event);\n };\n sessionRestorer();\n }\n\n loggedInHandles: string[] = [];\n\n protected getLoggedInHandles(): string[] {\n if (typeof window !== \"undefined\") {\n const handlesString = window.localStorage.getItem(\"graffiti-handles\");\n return handlesString\n ? handlesString.split(\",\").map(decodeURIComponent)\n : [];\n } else {\n return this.loggedInHandles;\n }\n }\n\n protected setLoggedInHandles(handles: string[]) {\n if (typeof window !== \"undefined\") {\n window.localStorage.setItem(\"graffiti-handles\", handles.join(\",\"));\n } else {\n this.loggedInHandles = handles;\n }\n }\n\n login: Graffiti[\"login\"] = async (actor) => {\n // Wait a tick for the browser to update the UI\n await new Promise((resolve) => setTimeout(resolve, 0));\n\n let handle = actor ? await this.actorToHandle(actor) : undefined;\n\n if (typeof window !== \"undefined\") {\n const response = window.prompt(\"Choose a username to log in.\", handle);\n handle = response ?? undefined;\n }\n\n if (!handle) {\n const detail: GraffitiLoginEvent[\"detail\"] = {\n error: new Error(\"No handle provided to login\"),\n };\n const event: GraffitiLoginEvent = new CustomEvent(\"login\", { detail });\n this.sessionEvents.dispatchEvent(event);\n } else {\n const existingHandles = this.getLoggedInHandles();\n if (!existingHandles.includes(handle)) {\n this.setLoggedInHandles([...existingHandles, handle]);\n }\n // Refresh the page to simulate oauth\n window.location.reload();\n }\n };\n\n logout: Graffiti[\"logout\"] = async (session) => {\n const handle = await this.actorToHandle(session.actor);\n const existingHandles = this.getLoggedInHandles();\n const exists = existingHandles.includes(handle);\n if (exists) {\n this.setLoggedInHandles(existingHandles.filter((h) => h !== handle));\n }\n\n const detail: GraffitiLogoutEvent[\"detail\"] = exists\n ? {\n actor: session.actor,\n }\n : {\n actor: session.actor,\n error: new Error(\"Not logged in with that actor\"),\n };\n\n const event: GraffitiLogoutEvent = new CustomEvent(\"logout\", { detail });\n this.sessionEvents.dispatchEvent(event);\n };\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,uBAA2C;AAE3C,MAAM,mBAAmB;AAalB,MAAM,sBAAsB;AAAA,EACjC,gBAA2C,IAAI,YAAY;AAAA,EAE3D,gBAA2C,OAAO,WAAmB;AACnE,UAAM,QAAQ,IAAI,YAAY,EAAE,OAAO,MAAM;AAC7C,UAAM,aAAS,+BAAa,KAAK;AACjC,WAAO,GAAG,gBAAgB,GAAG,MAAM;AAAA,EACrC;AAAA,EAEA,gBAA2C,OAAO,UAAkB;AAClE,QAAI,CAAC,MAAM,WAAW,gBAAgB,GAAG;AACvC,YAAM,IAAI,MAAM,yBAAyB,gBAAgB,EAAE;AAAA,IAC7D;AACA,UAAM,SAAS,MAAM,MAAM,iBAAiB,MAAM;AAClD,UAAM,YAAQ,+BAAa,MAAM;AACjC,WAAO,IAAI,YAAY,EAAE,OAAO,KAAK;AAAA,EACvC;AAAA,EAEA,cAAc;AAEZ,UAAM,kBAAkB,YAAY;AAElC,YAAM,QAAQ,QAAQ;AAGtB,iBAAW,UAAU,KAAK,mBAAmB,GAAG;AAC9C,cAAMA,SAA4B,IAAI,YAAY,SAAS;AAAA,UACzD,QAAQ,EAAE,SAAS,EAAE,OAAO,MAAM,KAAK,cAAc,MAAM,EAAE,EAAE;AAAA,QACjE,CAAC;AACD,aAAK,cAAc,cAAcA,MAAK;AAAA,MACxC;AAEA,YAAM,QAAyC,IAAI;AAAA,QACjD;AAAA,QACA,EAAE,QAAQ,CAAC,EAAE;AAAA,MACf;AACA,WAAK,cAAc,cAAc,KAAK;AAAA,IACxC;AACA,oBAAgB;AAAA,EAClB;AAAA,EAEA,kBAA4B,CAAC;AAAA,EAEnB,qBAA+B;AACvC,QAAI,OAAO,WAAW,aAAa;AACjC,YAAM,gBAAgB,OAAO,aAAa,QAAQ,kBAAkB;AACpE,aAAO,gBACH,cAAc,MAAM,GAAG,EAAE,IAAI,kBAAkB,IAC/C,CAAC;AAAA,IACP,OAAO;AACL,aAAO,KAAK;AAAA,IACd;AAAA,EACF;AAAA,EAEU,mBAAmB,SAAmB;AAC9C,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,aAAa,QAAQ,oBAAoB,QAAQ,KAAK,GAAG,CAAC;AAAA,IACnE,OAAO;AACL,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AAAA,EAEA,QAA2B,OAAO,UAAU;AAE1C,UAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,CAAC,CAAC;AAErD,QAAI,SAAS,QAAQ,MAAM,KAAK,cAAc,KAAK,IAAI;AAEvD,QAAI,OAAO,WAAW,aAAa;AACjC,YAAM,WAAW,OAAO,OAAO,gCAAgC,MAAM;AACrE,eAAS,YAAY;AAAA,IACvB;AAEA,QAAI,CAAC,QAAQ;AACX,YAAM,SAAuC;AAAA,QAC3C,OAAO,IAAI,MAAM,6BAA6B;AAAA,MAChD;AACA,YAAM,QAA4B,IAAI,YAAY,SAAS,EAAE,OAAO,CAAC;AACrE,WAAK,cAAc,cAAc,KAAK;AAAA,IACxC,OAAO;AACL,YAAM,kBAAkB,KAAK,mBAAmB;AAChD,UAAI,CAAC,gBAAgB,SAAS,MAAM,GAAG;AACrC,aAAK,mBAAmB,CAAC,GAAG,iBAAiB,MAAM,CAAC;AAAA,MACtD;AAEA,aAAO,SAAS,OAAO;AAAA,IACzB;AAAA,EACF;AAAA,EAEA,SAA6B,OAAO,YAAY;AAC9C,UAAM,SAAS,MAAM,KAAK,cAAc,QAAQ,KAAK;AACrD,UAAM,kBAAkB,KAAK,mBAAmB;AAChD,UAAM,SAAS,gBAAgB,SAAS,MAAM;AAC9C,QAAI,QAAQ;AACV,WAAK,mBAAmB,gBAAgB,OAAO,CAAC,MAAM,MAAM,MAAM,CAAC;AAAA,IACrE;AAEA,UAAM,SAAwC,SAC1C;AAAA,MACE,OAAO,QAAQ;AAAA,IACjB,IACA;AAAA,MACE,OAAO,QAAQ;AAAA,MACf,OAAO,IAAI,MAAM,+BAA+B;AAAA,IAClD;AAEJ,UAAM,QAA6B,IAAI,YAAY,UAAU,EAAE,OAAO,CAAC;AACvE,SAAK,cAAc,cAAc,KAAK;AAAA,EACxC;AACF;",
6
+ "names": ["event"]
7
+ }
package/dist/cjs/index.js CHANGED
@@ -22,32 +22,53 @@ __export(index_exports, {
22
22
  });
23
23
  module.exports = __toCommonJS(index_exports);
24
24
  var import_api = require("@graffiti-garden/api");
25
- var import_session_manager = require("./session-manager.js");
26
- var import_database = require("./database.js");
27
- class GraffitiLocal extends import_api.Graffiti {
28
- sessionManagerLocal = new import_session_manager.GraffitiLocalSessionManager();
29
- login = this.sessionManagerLocal.login.bind(this.sessionManagerLocal);
30
- logout = this.sessionManagerLocal.logout.bind(this.sessionManagerLocal);
31
- sessionEvents = this.sessionManagerLocal.sessionEvents;
32
- put;
25
+ var import_identity = require("./identity");
26
+ var import_objects = require("./objects");
27
+ var import_media = require("./media");
28
+ class GraffitiLocal {
29
+ graffitiLocalIdentity = new import_identity.GraffitiLocalIdentity();
30
+ login = this.graffitiLocalIdentity.login.bind(this.graffitiLocalIdentity);
31
+ logout = this.graffitiLocalIdentity.logout.bind(this.graffitiLocalIdentity);
32
+ handleToActor = this.graffitiLocalIdentity.handleToActor.bind(
33
+ this.graffitiLocalIdentity
34
+ );
35
+ actorToHandle = this.graffitiLocalIdentity.actorToHandle.bind(
36
+ this.graffitiLocalIdentity
37
+ );
38
+ sessionEvents = this.graffitiLocalIdentity.sessionEvents;
39
+ graffitiLocalObjects;
40
+ post;
33
41
  get;
34
- patch;
35
42
  delete;
36
43
  discover;
37
- recoverOrphans;
38
- channelStats;
39
- continueObjectStream;
44
+ continueDiscover;
45
+ graffitiLocalMedia;
46
+ postMedia;
47
+ getMedia;
48
+ deleteMedia;
40
49
  constructor(options) {
41
- super();
42
- const graffitiPouchDbBase = new import_database.GraffitiLocalDatabase(options);
43
- this.put = graffitiPouchDbBase.put.bind(graffitiPouchDbBase);
44
- this.get = graffitiPouchDbBase.get.bind(graffitiPouchDbBase);
45
- this.patch = graffitiPouchDbBase.patch.bind(graffitiPouchDbBase);
46
- this.delete = graffitiPouchDbBase.delete.bind(graffitiPouchDbBase);
47
- this.discover = graffitiPouchDbBase.discover.bind(graffitiPouchDbBase);
48
- this.recoverOrphans = graffitiPouchDbBase.recoverOrphans.bind(graffitiPouchDbBase);
49
- this.channelStats = graffitiPouchDbBase.channelStats.bind(graffitiPouchDbBase);
50
- this.continueObjectStream = graffitiPouchDbBase.continueObjectStream.bind(graffitiPouchDbBase);
50
+ this.graffitiLocalObjects = new import_objects.GraffitiLocalObjects(options);
51
+ this.post = this.graffitiLocalObjects.post.bind(this.graffitiLocalObjects);
52
+ this.get = this.graffitiLocalObjects.get.bind(this.graffitiLocalObjects);
53
+ this.delete = this.graffitiLocalObjects.delete.bind(
54
+ this.graffitiLocalObjects
55
+ );
56
+ this.discover = this.graffitiLocalObjects.discover.bind(
57
+ this.graffitiLocalObjects
58
+ );
59
+ this.continueDiscover = this.graffitiLocalObjects.continueDiscover.bind(
60
+ this.graffitiLocalObjects
61
+ );
62
+ this.graffitiLocalMedia = new import_media.GraffitiLocalMedia(this.graffitiLocalObjects);
63
+ this.postMedia = this.graffitiLocalMedia.postMedia.bind(
64
+ this.graffitiLocalMedia
65
+ );
66
+ this.getMedia = this.graffitiLocalMedia.getMedia.bind(
67
+ this.graffitiLocalMedia
68
+ );
69
+ this.deleteMedia = this.graffitiLocalMedia.deleteMedia.bind(
70
+ this.graffitiLocalMedia
71
+ );
51
72
  }
52
73
  }
53
74
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/index.ts"],
4
- "sourcesContent": ["import { Graffiti, type GraffitiSession } from \"@graffiti-garden/api\";\nimport { GraffitiLocalSessionManager } from \"./session-manager.js\";\nimport {\n GraffitiLocalDatabase,\n type GraffitiLocalOptions,\n} from \"./database.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 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 continueObjectStream: Graffiti[\"continueObjectStream\"];\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 this.continueObjectStream =\n graffitiPouchDbBase.continueObjectStream.bind(graffitiPouchDbBase);\n }\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAA+C;AAC/C,6BAA4C;AAC5C,sBAGO;AAWA,MAAM,sBAAsB,oBAAS;AAAA,EAChC,sBAAsB,IAAI,mDAA4B;AAAA,EAChE,QAAQ,KAAK,oBAAoB,MAAM,KAAK,KAAK,mBAAmB;AAAA,EACpE,SAAS,KAAK,oBAAoB,OAAO,KAAK,KAAK,mBAAmB;AAAA,EACtE,gBAAgB,KAAK,oBAAoB;AAAA,EAEzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA,YAAY,SAAgC;AAC1C,UAAM;AAEN,UAAM,sBAAsB,IAAI,sCAAsB,OAAO;AAE7D,SAAK,MAAM,oBAAoB,IAAI,KAAK,mBAAmB;AAC3D,SAAK,MAAM,oBAAoB,IAAI,KAAK,mBAAmB;AAC3D,SAAK,QAAQ,oBAAoB,MAAM,KAAK,mBAAmB;AAC/D,SAAK,SAAS,oBAAoB,OAAO,KAAK,mBAAmB;AACjE,SAAK,WAAW,oBAAoB,SAAS,KAAK,mBAAmB;AACrE,SAAK,iBACH,oBAAoB,eAAe,KAAK,mBAAmB;AAC7D,SAAK,eACH,oBAAoB,aAAa,KAAK,mBAAmB;AAC3D,SAAK,uBACH,oBAAoB,qBAAqB,KAAK,mBAAmB;AAAA,EACrE;AACF;",
4
+ "sourcesContent": ["import { Graffiti, type GraffitiSession } from \"@graffiti-garden/api\";\nimport { GraffitiLocalIdentity } from \"./identity\";\nimport { GraffitiLocalObjects, type GraffitiLocalOptions } from \"./objects\";\nimport { GraffitiLocalMedia } from \"./media\";\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 implements Graffiti {\n protected graffitiLocalIdentity = new GraffitiLocalIdentity();\n login = this.graffitiLocalIdentity.login.bind(this.graffitiLocalIdentity);\n logout = this.graffitiLocalIdentity.logout.bind(this.graffitiLocalIdentity);\n handleToActor = this.graffitiLocalIdentity.handleToActor.bind(\n this.graffitiLocalIdentity,\n );\n actorToHandle = this.graffitiLocalIdentity.actorToHandle.bind(\n this.graffitiLocalIdentity,\n );\n sessionEvents = this.graffitiLocalIdentity.sessionEvents;\n\n protected graffitiLocalObjects: GraffitiLocalObjects;\n post: Graffiti[\"post\"];\n get: Graffiti[\"get\"];\n delete: Graffiti[\"delete\"];\n discover: Graffiti[\"discover\"];\n continueDiscover: Graffiti[\"continueDiscover\"];\n\n protected graffitiLocalMedia: GraffitiLocalMedia;\n postMedia: Graffiti[\"postMedia\"];\n getMedia: Graffiti[\"getMedia\"];\n deleteMedia: Graffiti[\"deleteMedia\"];\n\n constructor(options?: GraffitiLocalOptions) {\n this.graffitiLocalObjects = new GraffitiLocalObjects(options);\n this.post = this.graffitiLocalObjects.post.bind(this.graffitiLocalObjects);\n this.get = this.graffitiLocalObjects.get.bind(this.graffitiLocalObjects);\n this.delete = this.graffitiLocalObjects.delete.bind(\n this.graffitiLocalObjects,\n );\n this.discover = this.graffitiLocalObjects.discover.bind(\n this.graffitiLocalObjects,\n );\n this.continueDiscover = this.graffitiLocalObjects.continueDiscover.bind(\n this.graffitiLocalObjects,\n );\n\n this.graffitiLocalMedia = new GraffitiLocalMedia(this.graffitiLocalObjects);\n this.postMedia = this.graffitiLocalMedia.postMedia.bind(\n this.graffitiLocalMedia,\n );\n this.getMedia = this.graffitiLocalMedia.getMedia.bind(\n this.graffitiLocalMedia,\n );\n this.deleteMedia = this.graffitiLocalMedia.deleteMedia.bind(\n this.graffitiLocalMedia,\n );\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAA+C;AAC/C,sBAAsC;AACtC,qBAAgE;AAChE,mBAAmC;AAW5B,MAAM,cAAkC;AAAA,EACnC,wBAAwB,IAAI,sCAAsB;AAAA,EAC5D,QAAQ,KAAK,sBAAsB,MAAM,KAAK,KAAK,qBAAqB;AAAA,EACxE,SAAS,KAAK,sBAAsB,OAAO,KAAK,KAAK,qBAAqB;AAAA,EAC1E,gBAAgB,KAAK,sBAAsB,cAAc;AAAA,IACvD,KAAK;AAAA,EACP;AAAA,EACA,gBAAgB,KAAK,sBAAsB,cAAc;AAAA,IACvD,KAAK;AAAA,EACP;AAAA,EACA,gBAAgB,KAAK,sBAAsB;AAAA,EAEjC;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EAEA,YAAY,SAAgC;AAC1C,SAAK,uBAAuB,IAAI,oCAAqB,OAAO;AAC5D,SAAK,OAAO,KAAK,qBAAqB,KAAK,KAAK,KAAK,oBAAoB;AACzE,SAAK,MAAM,KAAK,qBAAqB,IAAI,KAAK,KAAK,oBAAoB;AACvE,SAAK,SAAS,KAAK,qBAAqB,OAAO;AAAA,MAC7C,KAAK;AAAA,IACP;AACA,SAAK,WAAW,KAAK,qBAAqB,SAAS;AAAA,MACjD,KAAK;AAAA,IACP;AACA,SAAK,mBAAmB,KAAK,qBAAqB,iBAAiB;AAAA,MACjE,KAAK;AAAA,IACP;AAEA,SAAK,qBAAqB,IAAI,gCAAmB,KAAK,oBAAoB;AAC1E,SAAK,YAAY,KAAK,mBAAmB,UAAU;AAAA,MACjD,KAAK;AAAA,IACP;AACA,SAAK,WAAW,KAAK,mBAAmB,SAAS;AAAA,MAC/C,KAAK;AAAA,IACP;AACA,SAAK,cAAc,KAAK,mBAAmB,YAAY;AAAA,MACrD,KAAK;AAAA,IACP;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var media_exports = {};
30
+ __export(media_exports, {
31
+ GraffitiLocalMedia: () => GraffitiLocalMedia
32
+ });
33
+ module.exports = __toCommonJS(media_exports);
34
+ var import_api = require("@graffiti-garden/api");
35
+ var import_utilities = require("./utilities");
36
+ var import_negotiator = __toESM(require("negotiator"));
37
+ const MEDIA_OBJECT_SCHEMA = {
38
+ properties: {
39
+ value: {
40
+ properties: {
41
+ dataBase64: { type: "string" },
42
+ type: { type: "string" },
43
+ size: { type: "number" }
44
+ },
45
+ required: ["dataBase64", "type", "size"]
46
+ }
47
+ }
48
+ };
49
+ class GraffitiLocalMedia {
50
+ db;
51
+ constructor(db) {
52
+ this.db = db;
53
+ }
54
+ postMedia = async (...args) => {
55
+ const [media, session] = args;
56
+ const dataBase64 = await (0, import_utilities.blobToBase64)(media.data);
57
+ const type = media.data.type;
58
+ const { url } = await this.db.post(
59
+ {
60
+ value: {
61
+ dataBase64,
62
+ type,
63
+ size: media.data.size
64
+ },
65
+ channels: [],
66
+ allowed: media.allowed
67
+ },
68
+ session
69
+ );
70
+ const { actor, id } = (0, import_utilities.decodeObjectUrl)(url);
71
+ return (0, import_utilities.encodeMediaUrl)(actor, id);
72
+ };
73
+ getMedia = async (...args) => {
74
+ const [mediaUrl, requirements, session] = args;
75
+ const { actor, id } = (0, import_utilities.decodeMediaUrl)(mediaUrl);
76
+ const objectUrl = (0, import_utilities.encodeObjectUrl)(actor, id);
77
+ const object = await this.db.get(
78
+ objectUrl,
79
+ MEDIA_OBJECT_SCHEMA,
80
+ session
81
+ );
82
+ const { dataBase64, type, size } = object.value;
83
+ if (requirements?.maxBytes && size > requirements.maxBytes) {
84
+ throw new import_api.GraffitiErrorTooLarge("File size exceeds limit");
85
+ }
86
+ if (requirements?.accept) {
87
+ const negotiator = new import_negotiator.default({
88
+ headers: { accept: requirements.accept }
89
+ });
90
+ if (negotiator.mediaType([type]) !== type) {
91
+ throw new import_api.GraffitiErrorNotAcceptable(`Unsupported media type, ${type}`);
92
+ }
93
+ }
94
+ const data = await (0, import_utilities.base64ToBlob)(dataBase64);
95
+ if (data.size !== size || data.type !== type) {
96
+ throw new Error("Invalid data");
97
+ }
98
+ return {
99
+ data,
100
+ actor: object.actor,
101
+ allowed: object.allowed
102
+ };
103
+ };
104
+ deleteMedia = async (...args) => {
105
+ const [mediaUrl, session] = args;
106
+ const { actor, id } = (0, import_utilities.decodeMediaUrl)(mediaUrl);
107
+ const objectUrl = (0, import_utilities.encodeObjectUrl)(actor, id);
108
+ await this.db.delete(objectUrl, session);
109
+ };
110
+ }
111
+ //# sourceMappingURL=media.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/media.ts"],
4
+ "sourcesContent": ["import {\n GraffitiErrorNotAcceptable,\n GraffitiErrorTooLarge,\n type Graffiti,\n type JSONSchema,\n} from \"@graffiti-garden/api\";\nimport {\n decodeObjectUrl,\n encodeObjectUrl,\n decodeMediaUrl,\n encodeMediaUrl,\n blobToBase64,\n base64ToBlob,\n} from \"./utilities\";\nimport Negotiator from \"negotiator\";\n\nconst MEDIA_OBJECT_SCHEMA = {\n properties: {\n value: {\n properties: {\n dataBase64: { type: \"string\" },\n type: { type: \"string\" },\n size: { type: \"number\" },\n },\n required: [\"dataBase64\", \"type\", \"size\"],\n },\n },\n} as const satisfies JSONSchema;\n\nexport class GraffitiLocalMedia {\n protected db: Pick<Graffiti, \"post\" | \"get\" | \"delete\">;\n\n constructor(db: Pick<Graffiti, \"post\" | \"get\" | \"delete\">) {\n this.db = db;\n }\n\n postMedia: Graffiti[\"postMedia\"] = async (...args) => {\n const [media, session] = args;\n\n const dataBase64 = await blobToBase64(media.data);\n const type = media.data.type;\n\n const { url } = await this.db.post<typeof MEDIA_OBJECT_SCHEMA>(\n {\n value: {\n dataBase64,\n type,\n size: media.data.size,\n },\n channels: [],\n allowed: media.allowed,\n },\n session,\n );\n\n const { actor, id } = decodeObjectUrl(url);\n return encodeMediaUrl(actor, id);\n };\n\n getMedia: Graffiti[\"getMedia\"] = async (...args) => {\n const [mediaUrl, requirements, session] = args;\n const { actor, id } = decodeMediaUrl(mediaUrl);\n const objectUrl = encodeObjectUrl(actor, id);\n\n const object = await this.db.get<typeof MEDIA_OBJECT_SCHEMA>(\n objectUrl,\n MEDIA_OBJECT_SCHEMA,\n session,\n );\n\n const { dataBase64, type, size } = object.value;\n\n if (requirements?.maxBytes && size > requirements.maxBytes) {\n throw new GraffitiErrorTooLarge(\"File size exceeds limit\");\n }\n\n // Make sure it adheres to requirements.accept\n if (requirements?.accept) {\n const negotiator = new Negotiator({\n headers: { accept: requirements.accept },\n });\n if (negotiator.mediaType([type]) !== type) {\n throw new GraffitiErrorNotAcceptable(`Unsupported media type, ${type}`);\n }\n }\n\n const data = await base64ToBlob(dataBase64);\n if (data.size !== size || data.type !== type) {\n throw new Error(\"Invalid data\");\n }\n\n return {\n data,\n actor: object.actor,\n allowed: object.allowed,\n };\n };\n\n deleteMedia: Graffiti[\"deleteMedia\"] = async (...args) => {\n const [mediaUrl, session] = args;\n const { actor, id } = decodeMediaUrl(mediaUrl);\n const objectUrl = encodeObjectUrl(actor, id);\n\n await this.db.delete(objectUrl, session);\n };\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAKO;AACP,uBAOO;AACP,wBAAuB;AAEvB,MAAM,sBAAsB;AAAA,EAC1B,YAAY;AAAA,IACV,OAAO;AAAA,MACL,YAAY;AAAA,QACV,YAAY,EAAE,MAAM,SAAS;AAAA,QAC7B,MAAM,EAAE,MAAM,SAAS;AAAA,QACvB,MAAM,EAAE,MAAM,SAAS;AAAA,MACzB;AAAA,MACA,UAAU,CAAC,cAAc,QAAQ,MAAM;AAAA,IACzC;AAAA,EACF;AACF;AAEO,MAAM,mBAAmB;AAAA,EACpB;AAAA,EAEV,YAAY,IAA+C;AACzD,SAAK,KAAK;AAAA,EACZ;AAAA,EAEA,YAAmC,UAAU,SAAS;AACpD,UAAM,CAAC,OAAO,OAAO,IAAI;AAEzB,UAAM,aAAa,UAAM,+BAAa,MAAM,IAAI;AAChD,UAAM,OAAO,MAAM,KAAK;AAExB,UAAM,EAAE,IAAI,IAAI,MAAM,KAAK,GAAG;AAAA,MAC5B;AAAA,QACE,OAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA,MAAM,MAAM,KAAK;AAAA,QACnB;AAAA,QACA,UAAU,CAAC;AAAA,QACX,SAAS,MAAM;AAAA,MACjB;AAAA,MACA;AAAA,IACF;AAEA,UAAM,EAAE,OAAO,GAAG,QAAI,kCAAgB,GAAG;AACzC,eAAO,iCAAe,OAAO,EAAE;AAAA,EACjC;AAAA,EAEA,WAAiC,UAAU,SAAS;AAClD,UAAM,CAAC,UAAU,cAAc,OAAO,IAAI;AAC1C,UAAM,EAAE,OAAO,GAAG,QAAI,iCAAe,QAAQ;AAC7C,UAAM,gBAAY,kCAAgB,OAAO,EAAE;AAE3C,UAAM,SAAS,MAAM,KAAK,GAAG;AAAA,MAC3B;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,UAAM,EAAE,YAAY,MAAM,KAAK,IAAI,OAAO;AAE1C,QAAI,cAAc,YAAY,OAAO,aAAa,UAAU;AAC1D,YAAM,IAAI,iCAAsB,yBAAyB;AAAA,IAC3D;AAGA,QAAI,cAAc,QAAQ;AACxB,YAAM,aAAa,IAAI,kBAAAA,QAAW;AAAA,QAChC,SAAS,EAAE,QAAQ,aAAa,OAAO;AAAA,MACzC,CAAC;AACD,UAAI,WAAW,UAAU,CAAC,IAAI,CAAC,MAAM,MAAM;AACzC,cAAM,IAAI,sCAA2B,2BAA2B,IAAI,EAAE;AAAA,MACxE;AAAA,IACF;AAEA,UAAM,OAAO,UAAM,+BAAa,UAAU;AAC1C,QAAI,KAAK,SAAS,QAAQ,KAAK,SAAS,MAAM;AAC5C,YAAM,IAAI,MAAM,cAAc;AAAA,IAChC;AAEA,WAAO;AAAA,MACL;AAAA,MACA,OAAO,OAAO;AAAA,MACd,SAAS,OAAO;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,cAAuC,UAAU,SAAS;AACxD,UAAM,CAAC,UAAU,OAAO,IAAI;AAC5B,UAAM,EAAE,OAAO,GAAG,QAAI,iCAAe,QAAQ;AAC7C,UAAM,gBAAY,kCAAgB,OAAO,EAAE;AAE3C,UAAM,KAAK,GAAG,OAAO,WAAW,OAAO;AAAA,EACzC;AACF;",
6
+ "names": ["Negotiator"]
7
+ }
@@ -0,0 +1,307 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var objects_exports = {};
30
+ __export(objects_exports, {
31
+ GraffitiLocalObjects: () => GraffitiLocalObjects
32
+ });
33
+ module.exports = __toCommonJS(objects_exports);
34
+ var import_api = require("@graffiti-garden/api");
35
+ var import_utilities = require("./utilities.js");
36
+ class GraffitiLocalObjects {
37
+ db_;
38
+ ajv_;
39
+ options;
40
+ operationClock = 0;
41
+ get db() {
42
+ if (!this.db_) {
43
+ this.db_ = (async () => {
44
+ const { default: PouchDB } = await import("pouchdb");
45
+ const pouchDbOptions = {
46
+ name: "graffitiDb",
47
+ ...this.options.pouchDBOptions
48
+ };
49
+ const db = new PouchDB(
50
+ pouchDbOptions.name,
51
+ pouchDbOptions
52
+ );
53
+ await db.put({
54
+ _id: "_design/indexes",
55
+ views: {
56
+ objectsPerChannelAndLastModified: {
57
+ map: function(object) {
58
+ const paddedLastModified = object.lastModified.toString().padStart(15, "0");
59
+ object.channels.forEach(function(channel) {
60
+ const id = encodeURIComponent(channel) + "/" + paddedLastModified;
61
+ emit(id);
62
+ });
63
+ }.toString()
64
+ }
65
+ }
66
+ }).catch((error) => {
67
+ if (error && typeof error === "object" && "name" in error && error.name === "conflict") {
68
+ return;
69
+ } else {
70
+ throw error;
71
+ }
72
+ });
73
+ return db;
74
+ })();
75
+ }
76
+ return this.db_;
77
+ }
78
+ get ajv() {
79
+ if (!this.ajv_) {
80
+ this.ajv_ = (async () => {
81
+ const { default: Ajv } = await import("ajv");
82
+ return new Ajv({ strict: false });
83
+ })();
84
+ }
85
+ return this.ajv_;
86
+ }
87
+ constructor(options) {
88
+ this.options = options ?? {};
89
+ }
90
+ get = async (...args) => {
91
+ const [urlObject, schema, session] = args;
92
+ const url = (0, import_api.unpackObjectUrl)(urlObject);
93
+ let doc;
94
+ try {
95
+ doc = await (await this.db).get(url);
96
+ } catch (error) {
97
+ throw new import_api.GraffitiErrorNotFound(
98
+ "The object you are trying to get either does not exist or you are not allowed to see it"
99
+ );
100
+ }
101
+ if (doc.tombstone) {
102
+ throw new import_api.GraffitiErrorNotFound(
103
+ "The object you are trying to get either does not exist or you are not allowed to see it"
104
+ );
105
+ }
106
+ const { actor } = (0, import_utilities.decodeObjectUrl)(url);
107
+ const { value, channels, allowed } = doc;
108
+ const object = {
109
+ value,
110
+ channels,
111
+ allowed,
112
+ url,
113
+ actor
114
+ };
115
+ if (!(0, import_api.isActorAllowedGraffitiObject)(object, session)) {
116
+ throw new import_api.GraffitiErrorNotFound(
117
+ "The object you are trying to get either does not exist or you are not allowed to see it"
118
+ );
119
+ }
120
+ (0, import_api.maskGraffitiObject)(object, [], session);
121
+ const validate = (0, import_api.compileGraffitiObjectSchema)(await this.ajv, schema);
122
+ if (!validate(object)) {
123
+ throw new import_api.GraffitiErrorSchemaMismatch();
124
+ }
125
+ return object;
126
+ };
127
+ delete = async (...args) => {
128
+ const [urlObject, session] = args;
129
+ const url = (0, import_api.unpackObjectUrl)(urlObject);
130
+ const { actor } = (0, import_utilities.decodeObjectUrl)(url);
131
+ if (actor !== session.actor) {
132
+ throw new import_api.GraffitiErrorForbidden(
133
+ "You cannot delete an object that you did not create."
134
+ );
135
+ }
136
+ let doc;
137
+ try {
138
+ doc = await (await this.db).get(url);
139
+ } catch {
140
+ throw new import_api.GraffitiErrorNotFound("Object not found.");
141
+ }
142
+ if (doc.tombstone) {
143
+ throw new import_api.GraffitiErrorNotFound("Object not found.");
144
+ }
145
+ doc.tombstone = true;
146
+ doc.lastModified = this.operationClock;
147
+ try {
148
+ await (await this.db).put(doc);
149
+ } catch {
150
+ throw new import_api.GraffitiErrorNotFound("Object not found.");
151
+ }
152
+ this.operationClock++;
153
+ return;
154
+ };
155
+ post = async (...args) => {
156
+ const [objectPartial, session] = args;
157
+ const actor = session.actor;
158
+ const id = (0, import_utilities.randomBase64)();
159
+ const url = (0, import_utilities.encodeObjectUrl)(actor, id);
160
+ const { value, channels, allowed } = objectPartial;
161
+ const object = {
162
+ value,
163
+ channels,
164
+ allowed,
165
+ lastModified: this.operationClock,
166
+ tombstone: false
167
+ };
168
+ await (await this.db).put({
169
+ _id: url,
170
+ ...object
171
+ });
172
+ this.operationClock++;
173
+ return {
174
+ ...objectPartial,
175
+ actor,
176
+ url
177
+ };
178
+ };
179
+ async *discoverMeta(args, continueParams) {
180
+ if (continueParams) {
181
+ const continueBuffer = this.options.continueBuffer ?? 2e3;
182
+ const timeElapsedSinceLastDiscover = Date.now() - continueParams.lastDiscovered;
183
+ if (timeElapsedSinceLastDiscover < continueBuffer) {
184
+ await new Promise(
185
+ (resolve) => setTimeout(resolve, continueBuffer - timeElapsedSinceLastDiscover)
186
+ );
187
+ }
188
+ }
189
+ const [discoverChannels, schema, session] = args;
190
+ const validate = (0, import_api.compileGraffitiObjectSchema)(await this.ajv, schema);
191
+ const startKeySuffix = continueParams ? continueParams.ifModifiedSince.toString().padStart(15, "0") : "";
192
+ const endKeySuffix = "\uFFFF";
193
+ const processedUrls = /* @__PURE__ */ new Set();
194
+ const startTime = this.operationClock;
195
+ for (const channel of discoverChannels) {
196
+ const keyPrefix = encodeURIComponent(channel) + "/";
197
+ const startkey = keyPrefix + startKeySuffix;
198
+ const endkey = keyPrefix + endKeySuffix;
199
+ const result = await (await this.db).query("indexes/objectsPerChannelAndLastModified", {
200
+ startkey,
201
+ endkey,
202
+ include_docs: true
203
+ });
204
+ for (const row of result.rows) {
205
+ const doc = row.doc;
206
+ if (!doc) continue;
207
+ const url = doc._id;
208
+ if (processedUrls.has(url)) continue;
209
+ processedUrls.add(url);
210
+ if (!continueParams && doc.tombstone) continue;
211
+ const { tombstone, value, channels, allowed } = doc;
212
+ const { actor } = (0, import_utilities.decodeObjectUrl)(url);
213
+ const object = {
214
+ url,
215
+ value,
216
+ allowed,
217
+ channels,
218
+ actor
219
+ };
220
+ if (!(0, import_api.isActorAllowedGraffitiObject)(object, session)) continue;
221
+ (0, import_api.maskGraffitiObject)(object, discoverChannels, session);
222
+ if (!validate(object)) continue;
223
+ yield tombstone ? {
224
+ tombstone: true,
225
+ object: { url }
226
+ } : { object };
227
+ }
228
+ }
229
+ return {
230
+ lastDiscovered: Date.now(),
231
+ ifModifiedSince: startTime
232
+ };
233
+ }
234
+ discoverCursor(args, continueParams) {
235
+ const [channels, schema, session] = args;
236
+ return "discover:" + JSON.stringify({
237
+ channels,
238
+ schema,
239
+ continueParams,
240
+ actor: session?.actor
241
+ });
242
+ }
243
+ async *discoverContinue(args, continueParams, session) {
244
+ if (session?.actor !== args[2]?.actor) {
245
+ throw new import_api.GraffitiErrorForbidden(
246
+ "Cannot continue a cursor started by another actor"
247
+ );
248
+ }
249
+ const iterator = this.discoverMeta(args, continueParams);
250
+ while (true) {
251
+ const result = await iterator.next();
252
+ if (result.done) {
253
+ return {
254
+ continue: (session2) => this.discoverContinue(args, result.value, session2),
255
+ cursor: this.discoverCursor(args, result.value)
256
+ };
257
+ }
258
+ yield result.value;
259
+ }
260
+ }
261
+ discover = (...args) => {
262
+ const [channels, schema, session] = args;
263
+ const iterator = this.discoverMeta([
264
+ channels,
265
+ schema,
266
+ session
267
+ ]);
268
+ const this_ = this;
269
+ return (async function* () {
270
+ while (true) {
271
+ const result = await iterator.next();
272
+ if (result.done) {
273
+ return {
274
+ continue: (session2) => this_.discoverContinue(
275
+ args,
276
+ result.value,
277
+ session2
278
+ ),
279
+ cursor: this_.discoverCursor(args, result.value)
280
+ };
281
+ }
282
+ if (result.value.tombstone) continue;
283
+ yield result.value;
284
+ }
285
+ })();
286
+ };
287
+ continueDiscover = (...args) => {
288
+ const [cursor, session] = args;
289
+ if (cursor.startsWith("discover:")) {
290
+ const { channels, schema, actor, continueParams } = JSON.parse(
291
+ cursor.slice("discover:".length)
292
+ );
293
+ if (actor && actor !== session?.actor) {
294
+ throw new import_api.GraffitiErrorForbidden(
295
+ "Cannot continue a cursor started by another actor"
296
+ );
297
+ }
298
+ return this.discoverContinue(
299
+ [channels, schema, session],
300
+ continueParams
301
+ );
302
+ } else {
303
+ throw new import_api.GraffitiErrorNotFound("Cursor not found");
304
+ }
305
+ };
306
+ }
307
+ //# sourceMappingURL=objects.js.map