@graffiti-garden/implementation-local 1.0.4 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/objects.ts"],
4
- "sourcesContent": ["import type {\n Graffiti,\n GraffitiObjectBase,\n JSONSchema,\n GraffitiSession,\n GraffitiObjectStreamContinue,\n GraffitiObjectStreamContinueEntry,\n} from \"@graffiti-garden/api\";\nimport {\n GraffitiErrorNotFound,\n GraffitiErrorSchemaMismatch,\n GraffitiErrorForbidden,\n unpackObjectUrl,\n maskGraffitiObject,\n isActorAllowedGraffitiObject,\n} from \"@graffiti-garden/api\";\nimport {\n randomBase64,\n decodeObjectUrl,\n encodeObjectUrl,\n compileGraffitiObjectSchema,\n} from \"./utilities.js\";\nimport type Ajv from \"ajv\";\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 * Wait at least this long (in milliseconds) before continuing a stream.\n * A basic form of rate limiting. Defaults to 2 seconds.\n */\n continueBuffer?: number;\n}\n\ntype GraffitiObjectData = {\n tombstone: boolean;\n value: {};\n channels: string[];\n allowed?: string[] | null;\n lastModified: number;\n};\n\ntype ContinueDiscoverParams = {\n lastDiscovered: number;\n ifModifiedSince: number;\n};\n\n/**\n * An implementation of only the database operations of the\n * GraffitiAPI without synchronization or session management.\n */\nexport class GraffitiLocalObjects {\n protected db_: Promise<PouchDB.Database<GraffitiObjectData>> | undefined;\n protected ajv_: Promise<Ajv> | undefined;\n protected readonly options: GraffitiLocalOptions;\n\n get db() {\n if (!this.db_) {\n this.db_ = (async () => {\n const { default: PouchDB } = await import(\"pouchdb\");\n const pouchDbOptions = {\n name: \"graffitiDb\",\n ...this.options.pouchDBOptions,\n };\n const db = new PouchDB<GraffitiObjectData>(\n pouchDbOptions.name,\n pouchDbOptions,\n );\n await db\n //@ts-ignore\n .put({\n _id: \"_design/indexes\",\n views: {\n objectsPerChannelAndLastModified: {\n map: function (object: GraffitiObjectData) {\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 },\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 return db;\n })();\n }\n return this.db_;\n }\n\n protected get ajv() {\n if (!this.ajv_) {\n this.ajv_ = (async () => {\n const { default: Ajv } = await import(\"ajv\");\n return new Ajv({ strict: false });\n })();\n }\n return this.ajv_;\n }\n\n protected async getOperationClock() {\n return Number((await (await this.db).info()).update_seq);\n }\n\n constructor(options?: GraffitiLocalOptions) {\n this.options = options ?? {};\n }\n\n get: Graffiti[\"get\"] = async (...args) => {\n const [urlObject, schema, session] = args;\n const url = unpackObjectUrl(urlObject);\n\n let doc: GraffitiObjectData;\n try {\n doc = await (await this.db).get(url);\n } catch (error) {\n throw new GraffitiErrorNotFound(\n \"The object you are trying to get either does not exist or you are not allowed to see it\",\n );\n }\n\n if (doc.tombstone) {\n throw new GraffitiErrorNotFound(\n \"The object you are trying to get either does not exist or you are not allowed to see it\",\n );\n }\n\n const { actor } = decodeObjectUrl(url);\n const { value, channels, allowed } = doc;\n const object: GraffitiObjectBase = {\n value,\n channels,\n allowed,\n url,\n actor,\n };\n\n if (!isActorAllowedGraffitiObject(object, session)) {\n throw new GraffitiErrorNotFound(\n \"The object you are trying to get either does not exist or you are not allowed to see it\",\n );\n }\n\n // Mask out the allowed list and channels\n // if the user is not the owner\n const masked = maskGraffitiObject(object, [], session?.actor);\n\n const validate = compileGraffitiObjectSchema(await this.ajv, schema);\n if (!validate(masked)) {\n throw new GraffitiErrorSchemaMismatch();\n }\n return masked;\n };\n\n delete: Graffiti[\"delete\"] = async (...args) => {\n const [urlObject, session] = args;\n\n const url = unpackObjectUrl(urlObject);\n const { actor } = decodeObjectUrl(url);\n if (actor !== session.actor) {\n throw new GraffitiErrorForbidden(\n \"You cannot delete an object that you did not create.\",\n );\n }\n\n let doc: GraffitiObjectData & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta;\n try {\n doc = await (await this.db).get(url);\n } catch {\n throw new GraffitiErrorNotFound(\"Object not found.\");\n }\n\n if (doc.tombstone) {\n throw new GraffitiErrorNotFound(\"Object not found.\");\n }\n\n // Set the tombstone and update lastModified\n doc.tombstone = true;\n doc.lastModified = await this.getOperationClock();\n try {\n await (await this.db).put(doc);\n } catch {\n throw new GraffitiErrorNotFound(\"Object not found.\");\n }\n\n // Return the output\n const { value, channels, allowed } = doc;\n const object: GraffitiObjectBase = {\n value,\n channels,\n allowed,\n url,\n actor,\n };\n return object;\n };\n\n post: Graffiti[\"post\"] = async (...args) => {\n const [objectPartial, session] = args;\n\n const actor = session.actor;\n const id = randomBase64();\n const url = encodeObjectUrl(actor, id);\n\n const { value, channels, allowed } = objectPartial;\n const object: GraffitiObjectData = {\n value,\n channels,\n allowed,\n lastModified: await this.getOperationClock(),\n tombstone: false,\n };\n\n await (\n await this.db\n ).put({\n _id: url,\n ...object,\n });\n\n return {\n ...objectPartial,\n actor,\n url,\n };\n };\n\n protected async *discoverMeta<Schema extends JSONSchema>(\n args: Parameters<typeof Graffiti.prototype.discover<Schema>>,\n continueParams?: {\n lastDiscovered: number;\n ifModifiedSince: number;\n },\n ): AsyncGenerator<\n GraffitiObjectStreamContinueEntry<Schema>,\n ContinueDiscoverParams\n > {\n // If we are continuing a discover, make sure to wait at\n // least 2 seconds since the last poll to start a new one.\n if (continueParams) {\n const continueBuffer = this.options.continueBuffer ?? 2000;\n const timeElapsedSinceLastDiscover =\n Date.now() - continueParams.lastDiscovered;\n if (timeElapsedSinceLastDiscover < continueBuffer) {\n // Continue was called too soon,\n // wait a bit before continuing\n await new Promise((resolve) =>\n setTimeout(resolve, continueBuffer - timeElapsedSinceLastDiscover),\n );\n }\n }\n\n const [discoverChannels, schema, session] = args;\n const validate = compileGraffitiObjectSchema(await this.ajv, schema);\n const startKeySuffix = continueParams\n ? continueParams.ifModifiedSince.toString().padStart(15, \"0\")\n : \"\";\n const endKeySuffix = \"\\uffff\";\n\n const processedUrls = new Set<string>();\n\n const startTime = await this.getOperationClock();\n\n for (const channel of discoverChannels) {\n const keyPrefix = encodeURIComponent(channel) + \"/\";\n const startkey = keyPrefix + startKeySuffix;\n const endkey = keyPrefix + endKeySuffix;\n\n const result = await (\n await this.db\n ).query<GraffitiObjectData>(\"indexes/objectsPerChannelAndLastModified\", {\n startkey,\n endkey,\n include_docs: true,\n });\n\n for (const row of result.rows) {\n const doc = row.doc;\n if (!doc) continue;\n\n const url = doc._id;\n\n if (processedUrls.has(url)) continue;\n processedUrls.add(url);\n\n // If this is not a continuation, skip tombstones\n if (!continueParams && doc.tombstone) continue;\n\n const { tombstone, value, channels, allowed } = doc;\n const { actor } = decodeObjectUrl(url);\n\n const object: GraffitiObjectBase = {\n url,\n value,\n allowed,\n channels,\n actor,\n };\n\n if (!isActorAllowedGraffitiObject(object, session)) continue;\n\n const masked = maskGraffitiObject(\n object,\n discoverChannels,\n session?.actor,\n );\n\n if (!validate(masked)) continue;\n\n yield tombstone\n ? {\n tombstone: true,\n object: { url },\n }\n : { object: masked };\n }\n }\n\n return {\n lastDiscovered: Date.now(),\n ifModifiedSince: startTime,\n };\n }\n\n protected discoverCursor(\n args: Parameters<typeof Graffiti.prototype.discover<{}>>,\n continueParams: {\n lastDiscovered: number;\n ifModifiedSince: number;\n },\n ): string {\n const [channels, schema, session] = args;\n return (\n \"discover:\" +\n JSON.stringify({\n channels,\n schema,\n continueParams,\n actor: session?.actor,\n })\n );\n }\n\n protected async *discoverContinue<Schema extends JSONSchema>(\n args: Parameters<typeof Graffiti.prototype.discover<Schema>>,\n continueParams: {\n lastDiscovered: number;\n ifModifiedSince: number;\n },\n session?: GraffitiSession | null,\n ): GraffitiObjectStreamContinue<Schema> {\n if (session?.actor !== args[2]?.actor) {\n throw new GraffitiErrorForbidden(\n \"Cannot continue a cursor started by another actor\",\n );\n }\n const iterator = this.discoverMeta<Schema>(args, continueParams);\n\n while (true) {\n const result = await iterator.next();\n if (result.done) {\n return {\n continue: (session) =>\n this.discoverContinue<Schema>(args, result.value, session),\n cursor: this.discoverCursor(args, result.value),\n };\n }\n yield result.value;\n }\n }\n\n discover: Graffiti[\"discover\"] = (...args) => {\n const [channels, schema, session] = args;\n const iterator = this.discoverMeta<(typeof args)[1]>([\n channels,\n schema,\n session,\n ]);\n\n const this_ = this;\n return (async function* () {\n while (true) {\n const result = await iterator.next();\n if (result.done) {\n return {\n continue: (session) =>\n this_.discoverContinue<(typeof args)[1]>(\n args,\n result.value,\n session,\n ),\n cursor: this_.discoverCursor(args, result.value),\n };\n }\n // Make sure to filter out tombstones\n if (result.value.tombstone) continue;\n yield result.value;\n }\n })();\n };\n\n continueDiscover: Graffiti[\"continueDiscover\"] = (...args) => {\n const [cursor, session] = args;\n if (cursor.startsWith(\"discover:\")) {\n // TODO: use AJV here\n const { channels, schema, actor, continueParams } = JSON.parse(\n cursor.slice(\"discover:\".length),\n );\n if (actor && actor !== session?.actor) {\n return (async function* () {\n throw new GraffitiErrorForbidden(\n \"Cannot continue a cursor started by another actor\",\n );\n })();\n }\n return this.discoverContinue<{}>(\n [channels, schema, session],\n continueParams,\n );\n } else {\n return (async function* () {\n throw new GraffitiErrorNotFound(\"Cursor not found\");\n })();\n }\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type {\n Graffiti,\n GraffitiObjectBase,\n JSONSchema,\n GraffitiSession,\n GraffitiObjectStreamContinue,\n GraffitiObjectStreamContinueEntry,\n} from \"@graffiti-garden/api\";\nimport {\n GraffitiErrorNotFound,\n GraffitiErrorSchemaMismatch,\n GraffitiErrorForbidden,\n unpackObjectUrl,\n maskGraffitiObject,\n isActorAllowedGraffitiObject,\n compileGraffitiObjectSchema,\n GraffitiErrorCursorExpired,\n} from \"@graffiti-garden/api\";\nimport { randomBase64, decodeObjectUrl, encodeObjectUrl } from \"./utilities.js\";\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 * Wait at least this long (in milliseconds) before continuing a stream.\n * A basic form of rate limiting. Defaults to 2 seconds.\n */\n continueBuffer?: number;\n}\n\ntype GraffitiObjectData = {\n tombstone: boolean;\n value: {};\n channels: string[];\n allowed?: string[] | null;\n lastModified: number;\n};\n\ntype ContinueDiscoverParams = {\n lastDiscovered: number;\n ifModifiedSince: number;\n};\n\n/**\n * An implementation of only the database operations of the\n * GraffitiAPI without synchronization or session management.\n */\nexport class GraffitiLocalObjects {\n protected db_: Promise<PouchDB.Database<GraffitiObjectData>> | undefined;\n protected readonly options: GraffitiLocalOptions;\n\n get db() {\n if (!this.db_) {\n this.db_ = (async () => {\n const { default: PouchDB } = await import(\"pouchdb\");\n const pouchDbOptions = {\n name: \"graffitiDb\",\n ...this.options.pouchDBOptions,\n };\n const db = new PouchDB<GraffitiObjectData>(\n pouchDbOptions.name,\n pouchDbOptions,\n );\n await db\n //@ts-ignore\n .put({\n _id: \"_design/indexes\",\n views: {\n objectsPerChannelAndLastModified: {\n map: function (object: GraffitiObjectData) {\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 },\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 return db;\n })();\n }\n return this.db_;\n }\n\n protected async getOperationClock() {\n return Number((await (await this.db).info()).update_seq);\n }\n\n constructor(options?: GraffitiLocalOptions) {\n this.options = options ?? {};\n }\n\n get: Graffiti[\"get\"] = async (...args) => {\n const [urlObject, schema, session] = args;\n const url = unpackObjectUrl(urlObject);\n\n let doc: GraffitiObjectData;\n try {\n doc = await (await this.db).get(url);\n } catch (error) {\n throw new GraffitiErrorNotFound(\n \"The object you are trying to get either does not exist or you are not allowed to see it\",\n );\n }\n\n if (doc.tombstone) {\n throw new GraffitiErrorNotFound(\n \"The object you are trying to get either does not exist or you are not allowed to see it\",\n );\n }\n\n const { actor } = decodeObjectUrl(url);\n const { value, channels, allowed } = doc;\n const object: GraffitiObjectBase = {\n value,\n channels,\n allowed,\n url,\n actor,\n };\n\n if (!isActorAllowedGraffitiObject(object, session)) {\n throw new GraffitiErrorNotFound(\n \"The object you are trying to get either does not exist or you are not allowed to see it\",\n );\n }\n\n // Mask out the allowed list and channels\n // if the user is not the owner\n const masked = maskGraffitiObject(object, [], session?.actor);\n\n const validate = await compileGraffitiObjectSchema(schema);\n if (!validate(masked)) {\n throw new GraffitiErrorSchemaMismatch();\n }\n return masked;\n };\n\n delete: Graffiti[\"delete\"] = async (...args) => {\n const [urlObject, session] = args;\n\n const url = unpackObjectUrl(urlObject);\n const { actor } = decodeObjectUrl(url);\n if (actor !== session.actor) {\n throw new GraffitiErrorForbidden(\n \"You cannot delete an object that you did not create.\",\n );\n }\n\n let doc: GraffitiObjectData & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta;\n try {\n doc = await (await this.db).get(url);\n } catch {\n throw new GraffitiErrorNotFound(\"Object not found.\");\n }\n\n if (doc.tombstone) {\n throw new GraffitiErrorNotFound(\"Object not found.\");\n }\n\n // Set the tombstone and update lastModified\n doc.tombstone = true;\n doc.lastModified = await this.getOperationClock();\n try {\n await (await this.db).put(doc);\n } catch {\n throw new GraffitiErrorNotFound(\"Object not found.\");\n }\n\n // Return the output\n const { value, channels, allowed } = doc;\n const object: GraffitiObjectBase = {\n value,\n channels,\n allowed,\n url,\n actor,\n };\n return object;\n };\n\n post: Graffiti[\"post\"] = async (...args) => {\n const [objectPartial, session] = args;\n\n const actor = session.actor;\n const id = randomBase64();\n const url = encodeObjectUrl(actor, id);\n\n const { value, channels, allowed } = objectPartial;\n const object: GraffitiObjectData = {\n value,\n channels,\n allowed,\n lastModified: await this.getOperationClock(),\n tombstone: false,\n };\n\n await (\n await this.db\n ).put({\n _id: url,\n ...object,\n });\n\n return {\n ...objectPartial,\n actor,\n url,\n };\n };\n\n protected async *discoverMeta<Schema extends JSONSchema>(\n args: Parameters<typeof Graffiti.prototype.discover<Schema>>,\n continueParams?: {\n lastDiscovered: number;\n ifModifiedSince: number;\n },\n ): AsyncGenerator<\n GraffitiObjectStreamContinueEntry<Schema>,\n ContinueDiscoverParams\n > {\n // If we are continuing a discover, make sure to wait at\n // least 2 seconds since the last poll to start a new one.\n if (continueParams) {\n const continueBuffer = this.options.continueBuffer ?? 2000;\n const timeElapsedSinceLastDiscover =\n Date.now() - continueParams.lastDiscovered;\n if (timeElapsedSinceLastDiscover < continueBuffer) {\n // Continue was called too soon,\n // wait a bit before continuing\n await new Promise((resolve) =>\n setTimeout(resolve, continueBuffer - timeElapsedSinceLastDiscover),\n );\n }\n }\n\n const [discoverChannels, schema, session] = args;\n const validate = await compileGraffitiObjectSchema(schema);\n const startKeySuffix = continueParams\n ? continueParams.ifModifiedSince.toString().padStart(15, \"0\")\n : \"\";\n const endKeySuffix = \"\\uffff\";\n\n const processedUrls = new Set<string>();\n\n const startTime = await this.getOperationClock();\n\n for (const channel of discoverChannels) {\n const keyPrefix = encodeURIComponent(channel) + \"/\";\n const startkey = keyPrefix + startKeySuffix;\n const endkey = keyPrefix + endKeySuffix;\n\n const result = await (\n await this.db\n ).query<GraffitiObjectData>(\"indexes/objectsPerChannelAndLastModified\", {\n startkey,\n endkey,\n include_docs: true,\n });\n\n for (const row of result.rows) {\n const doc = row.doc;\n if (!doc) continue;\n\n const url = doc._id;\n\n if (processedUrls.has(url)) continue;\n processedUrls.add(url);\n\n // If this is not a continuation, skip tombstones\n if (!continueParams && doc.tombstone) continue;\n\n const { tombstone, value, channels, allowed } = doc;\n const { actor } = decodeObjectUrl(url);\n\n const object: GraffitiObjectBase = {\n url,\n value,\n allowed,\n channels,\n actor,\n };\n\n if (!isActorAllowedGraffitiObject(object, session)) continue;\n\n const masked = maskGraffitiObject(\n object,\n discoverChannels,\n session?.actor,\n );\n\n if (!validate(masked)) continue;\n\n yield tombstone\n ? {\n tombstone: true,\n object: { url },\n }\n : { object: masked };\n }\n }\n\n return {\n lastDiscovered: Date.now(),\n ifModifiedSince: startTime,\n };\n }\n\n protected discoverCursor(\n args: Parameters<typeof Graffiti.prototype.discover<{}>>,\n continueParams: {\n lastDiscovered: number;\n ifModifiedSince: number;\n },\n ): string {\n const [channels, schema, session] = args;\n return (\n \"discover:\" +\n JSON.stringify({\n channels,\n schema,\n continueParams,\n actor: session?.actor,\n })\n );\n }\n\n protected async *discoverContinue<Schema extends JSONSchema>(\n args: Parameters<typeof Graffiti.prototype.discover<Schema>>,\n continueParams: {\n lastDiscovered: number;\n ifModifiedSince: number;\n },\n session?: GraffitiSession | null,\n ): GraffitiObjectStreamContinue<Schema> {\n if (session?.actor !== args[2]?.actor) {\n throw new GraffitiErrorForbidden(\n \"Cannot continue a cursor started by another actor\",\n );\n }\n const iterator = this.discoverMeta<Schema>(args, continueParams);\n\n while (true) {\n const result = await iterator.next();\n if (result.done) {\n return {\n continue: (session) =>\n this.discoverContinue<Schema>(args, result.value, session),\n cursor: this.discoverCursor(args, result.value),\n };\n }\n yield result.value;\n }\n }\n\n discover: Graffiti[\"discover\"] = (...args) => {\n const [channels, schema, session] = args;\n const iterator = this.discoverMeta<(typeof args)[1]>([\n channels,\n schema,\n session,\n ]);\n\n const this_ = this;\n return (async function* () {\n while (true) {\n const result = await iterator.next();\n if (result.done) {\n return {\n continue: (session) =>\n this_.discoverContinue<(typeof args)[1]>(\n args,\n result.value,\n session,\n ),\n cursor: this_.discoverCursor(args, result.value),\n };\n }\n // Make sure to filter out tombstones\n if (result.value.tombstone) continue;\n yield result.value;\n }\n })();\n };\n\n continueDiscover: Graffiti[\"continueDiscover\"] = (...args) => {\n const [cursor, session] = args;\n if (cursor.startsWith(\"discover:\")) {\n // TODO: use AJV here\n const { channels, schema, actor, continueParams } = JSON.parse(\n cursor.slice(\"discover:\".length),\n );\n if (actor && actor !== session?.actor) {\n return (async function* () {\n throw new GraffitiErrorForbidden(\n \"Cannot continue a cursor started by another actor\",\n );\n })();\n }\n return this.discoverContinue<{}>(\n [channels, schema, session],\n continueParams,\n );\n } else {\n return (async function* () {\n throw new GraffitiErrorCursorExpired(\"Cursor not found\");\n })();\n }\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["session"]
7
7
  }
@@ -5,8 +5,8 @@ import {
5
5
  } from "@graffiti-garden/api/tests";
6
6
  import { GraffitiLocal } from "./index";
7
7
  const useGraffiti = () => new GraffitiLocal();
8
- const useSession1 = () => ({ actor: "someone" });
9
- const useSession2 = () => ({ actor: "someoneelse" });
8
+ const useSession1 = () => ({ actor: "did:example:someone" });
9
+ const useSession2 = () => ({ actor: "did:example:someoneelse" });
10
10
  graffitiCRUDTests(useGraffiti, useSession1, useSession2);
11
11
  graffitiDiscoverTests(useGraffiti, useSession1, useSession2);
12
12
  graffitiMediaTests(useGraffiti, useSession1, useSession2);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/tests.spec.ts"],
4
- "sourcesContent": ["import {\n graffitiCRUDTests,\n graffitiDiscoverTests,\n graffitiMediaTests,\n} from \"@graffiti-garden/api/tests\";\nimport { GraffitiLocal } from \"./index\";\n\nconst useGraffiti = () => new GraffitiLocal();\nconst useSession1 = () => ({ actor: \"someone\" });\nconst useSession2 = () => ({ actor: \"someoneelse\" });\n\ngraffitiCRUDTests(useGraffiti, useSession1, useSession2);\ngraffitiDiscoverTests(useGraffiti, useSession1, useSession2);\ngraffitiMediaTests(useGraffiti, useSession1, useSession2);\n"],
5
- "mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,qBAAqB;AAE9B,MAAM,cAAc,MAAM,IAAI,cAAc;AAC5C,MAAM,cAAc,OAAO,EAAE,OAAO,UAAU;AAC9C,MAAM,cAAc,OAAO,EAAE,OAAO,cAAc;AAElD,kBAAkB,aAAa,aAAa,WAAW;AACvD,sBAAsB,aAAa,aAAa,WAAW;AAC3D,mBAAmB,aAAa,aAAa,WAAW;",
4
+ "sourcesContent": ["import {\n graffitiCRUDTests,\n graffitiDiscoverTests,\n graffitiMediaTests,\n} from \"@graffiti-garden/api/tests\";\nimport { GraffitiLocal } from \"./index\";\n\nconst useGraffiti = () => new GraffitiLocal();\nconst useSession1 = () => ({ actor: \"did:example:someone\" });\nconst useSession2 = () => ({ actor: \"did:example:someoneelse\" });\n\n// @ts-ignore\ngraffitiCRUDTests(useGraffiti, useSession1, useSession2);\ngraffitiDiscoverTests(useGraffiti, useSession1, useSession2);\ngraffitiMediaTests(useGraffiti, useSession1, useSession2);\n"],
5
+ "mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,qBAAqB;AAE9B,MAAM,cAAc,MAAM,IAAI,cAAc;AAC5C,MAAM,cAAc,OAAO,EAAE,OAAO,sBAAsB;AAC1D,MAAM,cAAc,OAAO,EAAE,OAAO,0BAA0B;AAG9D,kBAAkB,aAAa,aAAa,WAAW;AACvD,sBAAsB,aAAa,aAAa,WAAW;AAC3D,mBAAmB,aAAa,aAAa,WAAW;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,4 @@
1
- import {
2
- GraffitiErrorInvalidSchema,
3
- GraffitiErrorNotFound
4
- } from "@graffiti-garden/api";
1
+ import { GraffitiErrorNotFound } from "@graffiti-garden/api";
5
2
  function encodeBase64(bytes) {
6
3
  const base64 = btoa(String.fromCodePoint(...bytes));
7
4
  return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/\=+$/, "");
@@ -16,17 +13,21 @@ function randomBase64(numBytes = 32) {
16
13
  crypto.getRandomValues(bytes);
17
14
  return encodeBase64(bytes);
18
15
  }
19
- const OBJECT_URL_PREFIX = "graffiti:object:";
20
- const MEDIA_URL_PREFIX = "graffiti:media:";
16
+ const OBJECT_URL_PREFIX = "graffiti:";
17
+ function encodeObjectUrlComponent(value) {
18
+ const replaced = value.replace(/:/g, "!").replace(/\//g, "~");
19
+ return encodeURIComponent(replaced);
20
+ }
21
+ function decodeObjectUrlComponent(value) {
22
+ const decoded = decodeURIComponent(value);
23
+ return decoded.replace(/!/g, ":").replace(/~/g, "/");
24
+ }
21
25
  function encodeGraffitiUrl(actor, id, prefix) {
22
- return `${prefix}${encodeURIComponent(actor)}:${encodeURIComponent(id)}`;
26
+ return `${prefix}${encodeObjectUrlComponent(actor)}:${encodeObjectUrlComponent(id)}`;
23
27
  }
24
28
  function encodeObjectUrl(actor, id) {
25
29
  return encodeGraffitiUrl(actor, id, OBJECT_URL_PREFIX);
26
30
  }
27
- function encodeMediaUrl(actor, id) {
28
- return encodeGraffitiUrl(actor, id, MEDIA_URL_PREFIX);
29
- }
30
31
  function decodeGraffitiUrl(url, prefix) {
31
32
  if (!url.startsWith(prefix)) {
32
33
  throw new GraffitiErrorNotFound(`URL does not start with ${prefix}`);
@@ -35,15 +36,12 @@ function decodeGraffitiUrl(url, prefix) {
35
36
  if (slices.length !== 2) {
36
37
  throw new GraffitiErrorNotFound("URL has too many colon-seperated parts");
37
38
  }
38
- const [actor, id] = slices.map(decodeURIComponent);
39
+ const [actor, id] = slices.map(decodeObjectUrlComponent);
39
40
  return { actor, id };
40
41
  }
41
42
  function decodeObjectUrl(url) {
42
43
  return decodeGraffitiUrl(url, OBJECT_URL_PREFIX);
43
44
  }
44
- function decodeMediaUrl(url) {
45
- return decodeGraffitiUrl(url, MEDIA_URL_PREFIX);
46
- }
47
45
  async function blobToBase64(blob) {
48
46
  if (typeof FileReader !== "undefined") {
49
47
  return new Promise((resolve, reject) => {
@@ -69,27 +67,17 @@ async function base64ToBlob(dataUrl) {
69
67
  const response = await fetch(dataUrl);
70
68
  return await response.blob();
71
69
  }
72
- function compileGraffitiObjectSchema(ajv, schema) {
73
- try {
74
- return ajv.compile(schema);
75
- } catch (error) {
76
- throw new GraffitiErrorInvalidSchema(
77
- error instanceof Error ? error.message : void 0
78
- );
79
- }
80
- }
81
70
  export {
82
71
  base64ToBlob,
83
72
  blobToBase64,
84
- compileGraffitiObjectSchema,
85
73
  decodeBase64,
86
74
  decodeGraffitiUrl,
87
- decodeMediaUrl,
88
75
  decodeObjectUrl,
76
+ decodeObjectUrlComponent,
89
77
  encodeBase64,
90
78
  encodeGraffitiUrl,
91
- encodeMediaUrl,
92
79
  encodeObjectUrl,
80
+ encodeObjectUrlComponent,
93
81
  randomBase64
94
82
  };
95
83
  //# 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 GraffitiErrorNotFound,\n type GraffitiObject,\n type GraffitiObjectBase,\n type JSONSchema,\n} from \"@graffiti-garden/api\";\nimport type Ajv from \"ajv\";\n\nexport function encodeBase64(bytes: Uint8Array): string {\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 decodeBase64(base64Url: string): Uint8Array {\n // Undo url-safe base64\n let base64 = base64Url.replace(/-/g, \"+\").replace(/_/g, \"/\");\n // Add padding if necessary\n while (base64.length % 4 !== 0) base64 += \"=\";\n // Decode\n return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));\n}\n\nexport function randomBase64(numBytes: number = 32): string {\n // Generate random bytes\n const bytes = new Uint8Array(numBytes);\n crypto.getRandomValues(bytes);\n return encodeBase64(bytes);\n}\n\nconst OBJECT_URL_PREFIX = \"graffiti:object:\";\nconst MEDIA_URL_PREFIX = \"graffiti:media:\";\n\nexport function encodeGraffitiUrl(actor: string, id: string, prefix: string) {\n return `${prefix}${encodeURIComponent(actor)}:${encodeURIComponent(id)}`;\n}\nexport function encodeObjectUrl(actor: string, id: string) {\n return encodeGraffitiUrl(actor, id, OBJECT_URL_PREFIX);\n}\nexport function encodeMediaUrl(actor: string, id: string) {\n return encodeGraffitiUrl(actor, id, MEDIA_URL_PREFIX);\n}\n\nexport function decodeGraffitiUrl(url: string, prefix: string) {\n if (!url.startsWith(prefix)) {\n throw new GraffitiErrorNotFound(`URL does not start with ${prefix}`);\n }\n const slices = url.slice(prefix.length).split(\":\");\n if (slices.length !== 2) {\n throw new GraffitiErrorNotFound(\"URL has too many colon-seperated parts\");\n }\n const [actor, id] = slices.map(decodeURIComponent);\n return { actor, id };\n}\nexport function decodeObjectUrl(url: string) {\n return decodeGraffitiUrl(url, OBJECT_URL_PREFIX);\n}\nexport function decodeMediaUrl(url: string) {\n return decodeGraffitiUrl(url, MEDIA_URL_PREFIX);\n}\n\nexport async function blobToBase64(blob: Blob): Promise<string> {\n if (typeof FileReader !== \"undefined\") {\n return new Promise((resolve, reject) => {\n const r = new FileReader();\n r.onload = () => {\n if (typeof r.result === \"string\") {\n resolve(r.result);\n } else {\n reject(new Error(\"Unexpected result type\"));\n }\n };\n r.onerror = reject;\n r.readAsDataURL(blob);\n });\n }\n\n if (typeof Buffer !== \"undefined\") {\n const ab = await blob.arrayBuffer();\n return `data:${blob.type};base64,${Buffer.from(ab).toString(\"base64\")}`;\n }\n\n throw new Error(\"Unsupported environment\");\n}\n\nexport async function base64ToBlob(dataUrl: string) {\n const response = await fetch(dataUrl);\n return await response.blob();\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"],
5
- "mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AAGA,SAAS,aAAa,OAA2B;AAEtD,QAAM,SAAS,KAAK,OAAO,cAAc,GAAG,KAAK,CAAC;AAElD,SAAO,OAAO,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,QAAQ,EAAE;AAC1E;AAEO,SAAS,aAAa,WAA+B;AAE1D,MAAI,SAAS,UAAU,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AAE3D,SAAO,OAAO,SAAS,MAAM,EAAG,WAAU;AAE1C,SAAO,WAAW,KAAK,KAAK,MAAM,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAC7D;AAEO,SAAS,aAAa,WAAmB,IAAY;AAE1D,QAAM,QAAQ,IAAI,WAAW,QAAQ;AACrC,SAAO,gBAAgB,KAAK;AAC5B,SAAO,aAAa,KAAK;AAC3B;AAEA,MAAM,oBAAoB;AAC1B,MAAM,mBAAmB;AAElB,SAAS,kBAAkB,OAAe,IAAY,QAAgB;AAC3E,SAAO,GAAG,MAAM,GAAG,mBAAmB,KAAK,CAAC,IAAI,mBAAmB,EAAE,CAAC;AACxE;AACO,SAAS,gBAAgB,OAAe,IAAY;AACzD,SAAO,kBAAkB,OAAO,IAAI,iBAAiB;AACvD;AACO,SAAS,eAAe,OAAe,IAAY;AACxD,SAAO,kBAAkB,OAAO,IAAI,gBAAgB;AACtD;AAEO,SAAS,kBAAkB,KAAa,QAAgB;AAC7D,MAAI,CAAC,IAAI,WAAW,MAAM,GAAG;AAC3B,UAAM,IAAI,sBAAsB,2BAA2B,MAAM,EAAE;AAAA,EACrE;AACA,QAAM,SAAS,IAAI,MAAM,OAAO,MAAM,EAAE,MAAM,GAAG;AACjD,MAAI,OAAO,WAAW,GAAG;AACvB,UAAM,IAAI,sBAAsB,wCAAwC;AAAA,EAC1E;AACA,QAAM,CAAC,OAAO,EAAE,IAAI,OAAO,IAAI,kBAAkB;AACjD,SAAO,EAAE,OAAO,GAAG;AACrB;AACO,SAAS,gBAAgB,KAAa;AAC3C,SAAO,kBAAkB,KAAK,iBAAiB;AACjD;AACO,SAAS,eAAe,KAAa;AAC1C,SAAO,kBAAkB,KAAK,gBAAgB;AAChD;AAEA,eAAsB,aAAa,MAA6B;AAC9D,MAAI,OAAO,eAAe,aAAa;AACrC,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,IAAI,IAAI,WAAW;AACzB,QAAE,SAAS,MAAM;AACf,YAAI,OAAO,EAAE,WAAW,UAAU;AAChC,kBAAQ,EAAE,MAAM;AAAA,QAClB,OAAO;AACL,iBAAO,IAAI,MAAM,wBAAwB,CAAC;AAAA,QAC5C;AAAA,MACF;AACA,QAAE,UAAU;AACZ,QAAE,cAAc,IAAI;AAAA,IACtB,CAAC;AAAA,EACH;AAEA,MAAI,OAAO,WAAW,aAAa;AACjC,UAAM,KAAK,MAAM,KAAK,YAAY;AAClC,WAAO,QAAQ,KAAK,IAAI,WAAW,OAAO,KAAK,EAAE,EAAE,SAAS,QAAQ,CAAC;AAAA,EACvE;AAEA,QAAM,IAAI,MAAM,yBAAyB;AAC3C;AAEA,eAAsB,aAAa,SAAiB;AAClD,QAAM,WAAW,MAAM,MAAM,OAAO;AACpC,SAAO,MAAM,SAAS,KAAK;AAC7B;AAEO,SAAS,4BACd,KACA,QACA;AACA,MAAI;AAKF,WAAO,IAAI,QAAQ,MAAM;AAAA,EAG3B,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR,iBAAiB,QAAQ,MAAM,UAAU;AAAA,IAC3C;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { GraffitiErrorNotFound } from \"@graffiti-garden/api\";\n\nexport function encodeBase64(bytes: Uint8Array): string {\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 decodeBase64(base64Url: string): Uint8Array {\n // Undo url-safe base64\n let base64 = base64Url.replace(/-/g, \"+\").replace(/_/g, \"/\");\n // Add padding if necessary\n while (base64.length % 4 !== 0) base64 += \"=\";\n // Decode\n return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));\n}\n\nexport function randomBase64(numBytes: number = 32): string {\n // Generate random bytes\n const bytes = new Uint8Array(numBytes);\n crypto.getRandomValues(bytes);\n return encodeBase64(bytes);\n}\n\nconst OBJECT_URL_PREFIX = \"graffiti:\";\n\nexport function encodeObjectUrlComponent(value: string) {\n const replaced = value.replace(/:/g, \"!\").replace(/\\//g, \"~\");\n return encodeURIComponent(replaced);\n}\nexport function decodeObjectUrlComponent(value: string) {\n const decoded = decodeURIComponent(value);\n return decoded.replace(/!/g, \":\").replace(/~/g, \"/\");\n}\nexport function encodeGraffitiUrl(actor: string, id: string, prefix: string) {\n return `${prefix}${encodeObjectUrlComponent(actor)}:${encodeObjectUrlComponent(id)}`;\n}\nexport function encodeObjectUrl(actor: string, id: string) {\n return encodeGraffitiUrl(actor, id, OBJECT_URL_PREFIX);\n}\n\nexport function decodeGraffitiUrl(url: string, prefix: string) {\n if (!url.startsWith(prefix)) {\n throw new GraffitiErrorNotFound(`URL does not start with ${prefix}`);\n }\n const slices = url.slice(prefix.length).split(\":\");\n if (slices.length !== 2) {\n throw new GraffitiErrorNotFound(\"URL has too many colon-seperated parts\");\n }\n const [actor, id] = slices.map(decodeObjectUrlComponent);\n return { actor, id };\n}\nexport function decodeObjectUrl(url: string) {\n return decodeGraffitiUrl(url, OBJECT_URL_PREFIX);\n}\n\nexport async function blobToBase64(blob: Blob): Promise<string> {\n if (typeof FileReader !== \"undefined\") {\n return new Promise((resolve, reject) => {\n const r = new FileReader();\n r.onload = () => {\n if (typeof r.result === \"string\") {\n resolve(r.result);\n } else {\n reject(new Error(\"Unexpected result type\"));\n }\n };\n r.onerror = reject;\n r.readAsDataURL(blob);\n });\n }\n\n if (typeof Buffer !== \"undefined\") {\n const ab = await blob.arrayBuffer();\n return `data:${blob.type};base64,${Buffer.from(ab).toString(\"base64\")}`;\n }\n\n throw new Error(\"Unsupported environment\");\n}\n\nexport async function base64ToBlob(dataUrl: string) {\n const response = await fetch(dataUrl);\n return await response.blob();\n}\n"],
5
+ "mappings": "AAAA,SAAS,6BAA6B;AAE/B,SAAS,aAAa,OAA2B;AAEtD,QAAM,SAAS,KAAK,OAAO,cAAc,GAAG,KAAK,CAAC;AAElD,SAAO,OAAO,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,QAAQ,EAAE;AAC1E;AAEO,SAAS,aAAa,WAA+B;AAE1D,MAAI,SAAS,UAAU,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AAE3D,SAAO,OAAO,SAAS,MAAM,EAAG,WAAU;AAE1C,SAAO,WAAW,KAAK,KAAK,MAAM,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAC7D;AAEO,SAAS,aAAa,WAAmB,IAAY;AAE1D,QAAM,QAAQ,IAAI,WAAW,QAAQ;AACrC,SAAO,gBAAgB,KAAK;AAC5B,SAAO,aAAa,KAAK;AAC3B;AAEA,MAAM,oBAAoB;AAEnB,SAAS,yBAAyB,OAAe;AACtD,QAAM,WAAW,MAAM,QAAQ,MAAM,GAAG,EAAE,QAAQ,OAAO,GAAG;AAC5D,SAAO,mBAAmB,QAAQ;AACpC;AACO,SAAS,yBAAyB,OAAe;AACtD,QAAM,UAAU,mBAAmB,KAAK;AACxC,SAAO,QAAQ,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AACrD;AACO,SAAS,kBAAkB,OAAe,IAAY,QAAgB;AAC3E,SAAO,GAAG,MAAM,GAAG,yBAAyB,KAAK,CAAC,IAAI,yBAAyB,EAAE,CAAC;AACpF;AACO,SAAS,gBAAgB,OAAe,IAAY;AACzD,SAAO,kBAAkB,OAAO,IAAI,iBAAiB;AACvD;AAEO,SAAS,kBAAkB,KAAa,QAAgB;AAC7D,MAAI,CAAC,IAAI,WAAW,MAAM,GAAG;AAC3B,UAAM,IAAI,sBAAsB,2BAA2B,MAAM,EAAE;AAAA,EACrE;AACA,QAAM,SAAS,IAAI,MAAM,OAAO,MAAM,EAAE,MAAM,GAAG;AACjD,MAAI,OAAO,WAAW,GAAG;AACvB,UAAM,IAAI,sBAAsB,wCAAwC;AAAA,EAC1E;AACA,QAAM,CAAC,OAAO,EAAE,IAAI,OAAO,IAAI,wBAAwB;AACvD,SAAO,EAAE,OAAO,GAAG;AACrB;AACO,SAAS,gBAAgB,KAAa;AAC3C,SAAO,kBAAkB,KAAK,iBAAiB;AACjD;AAEA,eAAsB,aAAa,MAA6B;AAC9D,MAAI,OAAO,eAAe,aAAa;AACrC,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,IAAI,IAAI,WAAW;AACzB,QAAE,SAAS,MAAM;AACf,YAAI,OAAO,EAAE,WAAW,UAAU;AAChC,kBAAQ,EAAE,MAAM;AAAA,QAClB,OAAO;AACL,iBAAO,IAAI,MAAM,wBAAwB,CAAC;AAAA,QAC5C;AAAA,MACF;AACA,QAAE,UAAU;AACZ,QAAE,cAAc,IAAI;AAAA,IACtB,CAAC;AAAA,EACH;AAEA,MAAI,OAAO,WAAW,aAAa;AACjC,UAAM,KAAK,MAAM,KAAK,YAAY;AAClC,WAAO,QAAQ,KAAK,IAAI,WAAW,OAAO,KAAK,EAAE,EAAE,SAAS,QAAQ,CAAC;AAAA,EACvE;AAEA,QAAM,IAAI,MAAM,yBAAyB;AAC3C;AAEA,eAAsB,aAAa,SAAiB;AAClD,QAAM,WAAW,MAAM,MAAM,OAAO;AACpC,SAAO,MAAM,SAAS,KAAK;AAC7B;",
6
6
  "names": []
7
7
  }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,5 @@
1
- import { Graffiti, type GraffitiSession } from "@graffiti-garden/api";
2
- import { GraffitiLocalIdentity } from "./identity";
3
- import { GraffitiLocalObjects, type GraffitiLocalOptions } from "./objects";
4
- import { GraffitiLocalMedia } from "./media";
1
+ import { GraffitiRuntimeTypes } from "@graffiti-garden/api";
2
+ import { type GraffitiLocalOptions } from "./objects";
5
3
  export type { GraffitiLocalOptions };
6
4
  /**
7
5
  * A local implementation of the [Graffiti API](https://api.graffiti.garden/classes/Graffiti.html)
@@ -10,23 +8,7 @@ export type { GraffitiLocalOptions };
10
8
  * It can also be configured to work with an external [CouchDB](https://couchdb.apache.org/) server,
11
9
  * although using it with a remote server will not be secure.
12
10
  */
13
- export declare class GraffitiLocal implements Graffiti {
14
- protected graffitiLocalIdentity: GraffitiLocalIdentity;
15
- login: (actor?: string) => Promise<void>;
16
- logout: (session: GraffitiSession) => Promise<void>;
17
- handleToActor: (handle: string) => Promise<string>;
18
- actorToHandle: (actor: string) => Promise<string>;
19
- sessionEvents: EventTarget;
20
- protected graffitiLocalObjects: GraffitiLocalObjects;
21
- post: Graffiti["post"];
22
- get: Graffiti["get"];
23
- delete: Graffiti["delete"];
24
- discover: Graffiti["discover"];
25
- continueDiscover: Graffiti["continueDiscover"];
26
- protected graffitiLocalMedia: GraffitiLocalMedia;
27
- postMedia: Graffiti["postMedia"];
28
- getMedia: Graffiti["getMedia"];
29
- deleteMedia: Graffiti["deleteMedia"];
11
+ export declare class GraffitiLocal extends GraffitiRuntimeTypes {
30
12
  constructor(options?: GraffitiLocalOptions);
31
13
  }
32
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,KAAK,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAE7C,YAAY,EAAE,oBAAoB,EAAE,CAAC;AAErC;;;;;;GAMG;AACH,qBAAa,aAAc,YAAW,QAAQ;IAC5C,SAAS,CAAC,qBAAqB,wBAA+B;IAC9D,KAAK,oCAAqE;IAC1E,MAAM,8CAAsE;IAC5E,aAAa,sCAEX;IACF,aAAa,qCAEX;IACF,aAAa,cAA4C;IAEzD,SAAS,CAAC,oBAAoB,EAAE,oBAAoB,CAAC;IACrD,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACvB,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IACrB,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC3B,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC/B,gBAAgB,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IAE/C,SAAS,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;IACjD,SAAS,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;IACjC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC/B,WAAW,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAC;gBAEzB,OAAO,CAAC,EAAE,oBAAoB;CAyB3C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEtE,OAAO,EAAwB,KAAK,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAG5E,YAAY,EAAE,oBAAoB,EAAE,CAAC;AAErC;;;;;;GAMG;AACH,qBAAa,aAAc,SAAQ,oBAAoB;gBACzC,OAAO,CAAC,EAAE,oBAAoB;CAI3C"}
@@ -1 +1 @@
1
- {"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../src/media.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,QAAQ,EAEd,MAAM,sBAAsB,CAAC;AAuB9B,qBAAa,kBAAkB;IAC7B,SAAS,CAAC,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC,CAAC;gBAE5C,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAIzD,SAAS,EAAE,QAAQ,CAAC,WAAW,CAAC,CAqB9B;IAEF,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAoC5B;IAEF,WAAW,EAAE,QAAQ,CAAC,aAAa,CAAC,CAMlC;CACH"}
1
+ {"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../src/media.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,QAAQ,EAEd,MAAM,sBAAsB,CAAC;AAgB9B,qBAAa,kBAAkB;IAC7B,SAAS,CAAC,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC,CAAC;gBAE5C,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAIzD,SAAS,EAAE,QAAQ,CAAC,WAAW,CAAC,CAoB9B;IAEF,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAkC5B;IAEF,WAAW,EAAE,QAAQ,CAAC,aAAa,CAAC,CAIlC;CACH"}
package/dist/objects.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import type { Graffiti, JSONSchema, GraffitiSession, GraffitiObjectStreamContinue, GraffitiObjectStreamContinueEntry } from "@graffiti-garden/api";
2
- import type Ajv from "ajv";
3
2
  /**
4
3
  * Constructor options for the GraffitiPoubchDB class.
5
4
  */
@@ -35,10 +34,8 @@ type ContinueDiscoverParams = {
35
34
  */
36
35
  export declare class GraffitiLocalObjects {
37
36
  protected db_: Promise<PouchDB.Database<GraffitiObjectData>> | undefined;
38
- protected ajv_: Promise<Ajv> | undefined;
39
37
  protected readonly options: GraffitiLocalOptions;
40
38
  get db(): Promise<PouchDB.Database<GraffitiObjectData>>;
41
- protected get ajv(): Promise<Ajv>;
42
39
  protected getOperationClock(): Promise<number>;
43
40
  constructor(options?: GraffitiLocalOptions);
44
41
  get: Graffiti["get"];
@@ -1 +1 @@
1
- {"version":3,"file":"objects.d.ts","sourceRoot":"","sources":["../src/objects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EAER,UAAU,EACV,eAAe,EACf,4BAA4B,EAC5B,iCAAiC,EAClC,MAAM,sBAAsB,CAAC;AAe9B,OAAO,KAAK,GAAG,MAAM,KAAK,CAAC;AAE3B;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,qBAAqB,CAAC;IAC7D;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,KAAK,kBAAkB,GAAG;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,EAAE,CAAC;IACV,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;GAGG;AACH,qBAAa,oBAAoB;IAC/B,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC,GAAG,SAAS,CAAC;IACzE,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;IACzC,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC;IAEjD,IAAI,EAAE,kDAkDL;IAED,SAAS,KAAK,GAAG,iBAQhB;cAEe,iBAAiB;gBAIrB,OAAO,CAAC,EAAE,oBAAoB;IAI1C,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CA4ClB;IAEF,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAyCxB;IAEF,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CA4BpB;cAEe,YAAY,CAAC,MAAM,SAAS,UAAU,EACrD,IAAI,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAC5D,cAAc,CAAC,EAAE;QACf,cAAc,EAAE,MAAM,CAAC;QACvB,eAAe,EAAE,MAAM,CAAC;KACzB,GACA,cAAc,CACf,iCAAiC,CAAC,MAAM,CAAC,EACzC,sBAAsB,CACvB;IAwFD,SAAS,CAAC,cAAc,CACtB,IAAI,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,EACxD,cAAc,EAAE;QACd,cAAc,EAAE,MAAM,CAAC;QACvB,eAAe,EAAE,MAAM,CAAC;KACzB,GACA,MAAM;cAaQ,gBAAgB,CAAC,MAAM,SAAS,UAAU,EACzD,IAAI,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAC5D,cAAc,EAAE;QACd,cAAc,EAAE,MAAM,CAAC;QACvB,eAAe,EAAE,MAAM,CAAC;KACzB,EACD,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,GAC/B,4BAA4B,CAAC,MAAM,CAAC;IAqBvC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CA4B5B;IAEF,gBAAgB,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAuB5C;CACH"}
1
+ {"version":3,"file":"objects.d.ts","sourceRoot":"","sources":["../src/objects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EAER,UAAU,EACV,eAAe,EACf,4BAA4B,EAC5B,iCAAiC,EAClC,MAAM,sBAAsB,CAAC;AAa9B;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,qBAAqB,CAAC;IAC7D;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,KAAK,kBAAkB,GAAG;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,EAAE,CAAC;IACV,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;GAGG;AACH,qBAAa,oBAAoB;IAC/B,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC,GAAG,SAAS,CAAC;IACzE,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC;IAEjD,IAAI,EAAE,kDAkDL;cAEe,iBAAiB;gBAIrB,OAAO,CAAC,EAAE,oBAAoB;IAI1C,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CA4ClB;IAEF,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAyCxB;IAEF,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CA4BpB;cAEe,YAAY,CAAC,MAAM,SAAS,UAAU,EACrD,IAAI,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAC5D,cAAc,CAAC,EAAE;QACf,cAAc,EAAE,MAAM,CAAC;QACvB,eAAe,EAAE,MAAM,CAAC;KACzB,GACA,cAAc,CACf,iCAAiC,CAAC,MAAM,CAAC,EACzC,sBAAsB,CACvB;IAwFD,SAAS,CAAC,cAAc,CACtB,IAAI,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,EACxD,cAAc,EAAE;QACd,cAAc,EAAE,MAAM,CAAC;QACvB,eAAe,EAAE,MAAM,CAAC;KACzB,GACA,MAAM;cAaQ,gBAAgB,CAAC,MAAM,SAAS,UAAU,EACzD,IAAI,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAC5D,cAAc,EAAE;QACd,cAAc,EAAE,MAAM,CAAC;QACvB,eAAe,EAAE,MAAM,CAAC;KACzB,EACD,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,GAC/B,4BAA4B,CAAC,MAAM,CAAC;IAqBvC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CA4B5B;IAEF,gBAAgB,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAuB5C;CACH"}
@@ -1,11 +1,10 @@
1
- import { type GraffitiObject, type GraffitiObjectBase, type JSONSchema } from "@graffiti-garden/api";
2
- import type Ajv from "ajv";
3
1
  export declare function encodeBase64(bytes: Uint8Array): string;
4
2
  export declare function decodeBase64(base64Url: string): Uint8Array;
5
3
  export declare function randomBase64(numBytes?: number): string;
4
+ export declare function encodeObjectUrlComponent(value: string): string;
5
+ export declare function decodeObjectUrlComponent(value: string): string;
6
6
  export declare function encodeGraffitiUrl(actor: string, id: string, prefix: string): string;
7
7
  export declare function encodeObjectUrl(actor: string, id: string): string;
8
- export declare function encodeMediaUrl(actor: string, id: string): string;
9
8
  export declare function decodeGraffitiUrl(url: string, prefix: string): {
10
9
  actor: string;
11
10
  id: string;
@@ -14,11 +13,6 @@ export declare function decodeObjectUrl(url: string): {
14
13
  actor: string;
15
14
  id: string;
16
15
  };
17
- export declare function decodeMediaUrl(url: string): {
18
- actor: string;
19
- id: string;
20
- };
21
16
  export declare function blobToBase64(blob: Blob): Promise<string>;
22
17
  export declare function base64ToBlob(dataUrl: string): Promise<Blob>;
23
- export declare function compileGraffitiObjectSchema<Schema extends JSONSchema>(ajv: Ajv, schema: Schema): (data: GraffitiObjectBase) => data is GraffitiObject<Schema>;
24
18
  //# sourceMappingURL=utilities.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,UAAU,EAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,GAAG,MAAM,KAAK,CAAC;AAE3B,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAKtD;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,CAO1D;AAED,wBAAgB,YAAY,CAAC,QAAQ,GAAE,MAAW,GAAG,MAAM,CAK1D;AAKD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAE1E;AACD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,UAExD;AACD,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,UAEvD;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;;EAU5D;AACD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM;;;EAE1C;AACD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM;;;EAEzC;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAsB9D;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,iBAGjD;AAED,wBAAgB,2BAA2B,CAAC,MAAM,SAAS,UAAU,EACnE,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,MAAM,UAQJ,kBAAkB,KACrB,IAAI,IAAI,cAAc,CAAC,MAAM,CAAC,CAMtC"}
1
+ {"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"AAEA,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAKtD;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,CAO1D;AAED,wBAAgB,YAAY,CAAC,QAAQ,GAAE,MAAW,GAAG,MAAM,CAK1D;AAID,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,UAGrD;AACD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,UAGrD;AACD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAE1E;AACD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,UAExD;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;;EAU5D;AACD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM;;;EAE1C;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAsB9D;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,iBAGjD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graffiti-garden/implementation-local",
3
- "version": "1.0.4",
3
+ "version": "1.0.8",
4
4
  "description": "A local implementation of the Graffiti API using PouchDB",
5
5
  "types": "./dist/index.d.ts",
6
6
  "module": "./dist/esm/index.js",
@@ -94,8 +94,7 @@
94
94
  "vitest": "^4.0.17"
95
95
  },
96
96
  "dependencies": {
97
- "@graffiti-garden/api": "^1.0.4",
98
- "ajv": "^8.17.1",
97
+ "@graffiti-garden/api": "^1.0.8",
99
98
  "pouchdb": "^9.0.0"
100
99
  }
101
100
  }
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Graffiti, type GraffitiSession } from "@graffiti-garden/api";
1
+ import { Graffiti, GraffitiRuntimeTypes } from "@graffiti-garden/api";
2
2
  import { GraffitiLocalIdentity } from "./identity";
3
3
  import { GraffitiLocalObjects, type GraffitiLocalOptions } from "./objects";
4
4
  import { GraffitiLocalMedia } from "./media";
@@ -12,7 +12,14 @@ export type { GraffitiLocalOptions };
12
12
  * It can also be configured to work with an external [CouchDB](https://couchdb.apache.org/) server,
13
13
  * although using it with a remote server will not be secure.
14
14
  */
15
- export class GraffitiLocal implements Graffiti {
15
+ export class GraffitiLocal extends GraffitiRuntimeTypes {
16
+ constructor(options?: GraffitiLocalOptions) {
17
+ const graffiti = new GraffitiLocal_(options);
18
+ super(graffiti);
19
+ }
20
+ }
21
+
22
+ class GraffitiLocal_ implements Graffiti {
16
23
  protected graffitiLocalIdentity = new GraffitiLocalIdentity();
17
24
  login = this.graffitiLocalIdentity.login.bind(this.graffitiLocalIdentity);
18
25
  logout = this.graffitiLocalIdentity.logout.bind(this.graffitiLocalIdentity);
package/src/media.ts CHANGED
@@ -5,14 +5,7 @@ import {
5
5
  type Graffiti,
6
6
  type JSONSchema,
7
7
  } from "@graffiti-garden/api";
8
- import {
9
- decodeObjectUrl,
10
- encodeObjectUrl,
11
- decodeMediaUrl,
12
- encodeMediaUrl,
13
- blobToBase64,
14
- base64ToBlob,
15
- } from "./utilities";
8
+ import { blobToBase64, base64ToBlob } from "./utilities";
16
9
 
17
10
  const MEDIA_OBJECT_SCHEMA = {
18
11
  properties: {
@@ -53,17 +46,14 @@ export class GraffitiLocalMedia {
53
46
  session,
54
47
  );
55
48
 
56
- const { actor, id } = decodeObjectUrl(url);
57
- return encodeMediaUrl(actor, id);
49
+ return url;
58
50
  };
59
51
 
60
52
  getMedia: Graffiti["getMedia"] = async (...args) => {
61
53
  const [mediaUrl, accept, session] = args;
62
- const { actor, id } = decodeMediaUrl(mediaUrl);
63
- const objectUrl = encodeObjectUrl(actor, id);
64
54
 
65
55
  const object = await this.db.get<typeof MEDIA_OBJECT_SCHEMA>(
66
- objectUrl,
56
+ mediaUrl,
67
57
  MEDIA_OBJECT_SCHEMA,
68
58
  session,
69
59
  );
@@ -97,9 +87,7 @@ export class GraffitiLocalMedia {
97
87
 
98
88
  deleteMedia: Graffiti["deleteMedia"] = async (...args) => {
99
89
  const [mediaUrl, session] = args;
100
- const { actor, id } = decodeMediaUrl(mediaUrl);
101
- const objectUrl = encodeObjectUrl(actor, id);
102
90
 
103
- await this.db.delete(objectUrl, session);
91
+ await this.db.delete(mediaUrl, session);
104
92
  };
105
93
  }
package/src/objects.ts CHANGED
@@ -13,14 +13,10 @@ import {
13
13
  unpackObjectUrl,
14
14
  maskGraffitiObject,
15
15
  isActorAllowedGraffitiObject,
16
- } from "@graffiti-garden/api";
17
- import {
18
- randomBase64,
19
- decodeObjectUrl,
20
- encodeObjectUrl,
21
16
  compileGraffitiObjectSchema,
22
- } from "./utilities.js";
23
- import type Ajv from "ajv";
17
+ GraffitiErrorCursorExpired,
18
+ } from "@graffiti-garden/api";
19
+ import { randomBase64, decodeObjectUrl, encodeObjectUrl } from "./utilities.js";
24
20
 
25
21
  /**
26
22
  * Constructor options for the GraffitiPoubchDB class.
@@ -60,7 +56,6 @@ type ContinueDiscoverParams = {
60
56
  */
61
57
  export class GraffitiLocalObjects {
62
58
  protected db_: Promise<PouchDB.Database<GraffitiObjectData>> | undefined;
63
- protected ajv_: Promise<Ajv> | undefined;
64
59
  protected readonly options: GraffitiLocalOptions;
65
60
 
66
61
  get db() {
@@ -115,16 +110,6 @@ export class GraffitiLocalObjects {
115
110
  return this.db_;
116
111
  }
117
112
 
118
- protected get ajv() {
119
- if (!this.ajv_) {
120
- this.ajv_ = (async () => {
121
- const { default: Ajv } = await import("ajv");
122
- return new Ajv({ strict: false });
123
- })();
124
- }
125
- return this.ajv_;
126
- }
127
-
128
113
  protected async getOperationClock() {
129
114
  return Number((await (await this.db).info()).update_seq);
130
115
  }
@@ -172,7 +157,7 @@ export class GraffitiLocalObjects {
172
157
  // if the user is not the owner
173
158
  const masked = maskGraffitiObject(object, [], session?.actor);
174
159
 
175
- const validate = compileGraffitiObjectSchema(await this.ajv, schema);
160
+ const validate = await compileGraffitiObjectSchema(schema);
176
161
  if (!validate(masked)) {
177
162
  throw new GraffitiErrorSchemaMismatch();
178
163
  }
@@ -278,7 +263,7 @@ export class GraffitiLocalObjects {
278
263
  }
279
264
 
280
265
  const [discoverChannels, schema, session] = args;
281
- const validate = compileGraffitiObjectSchema(await this.ajv, schema);
266
+ const validate = await compileGraffitiObjectSchema(schema);
282
267
  const startKeySuffix = continueParams
283
268
  ? continueParams.ifModifiedSince.toString().padStart(15, "0")
284
269
  : "";
@@ -446,7 +431,7 @@ export class GraffitiLocalObjects {
446
431
  );
447
432
  } else {
448
433
  return (async function* () {
449
- throw new GraffitiErrorNotFound("Cursor not found");
434
+ throw new GraffitiErrorCursorExpired("Cursor not found");
450
435
  })();
451
436
  }
452
437
  };
package/src/tests.spec.ts CHANGED
@@ -6,9 +6,10 @@ import {
6
6
  import { GraffitiLocal } from "./index";
7
7
 
8
8
  const useGraffiti = () => new GraffitiLocal();
9
- const useSession1 = () => ({ actor: "someone" });
10
- const useSession2 = () => ({ actor: "someoneelse" });
9
+ const useSession1 = () => ({ actor: "did:example:someone" });
10
+ const useSession2 = () => ({ actor: "did:example:someoneelse" });
11
11
 
12
+ // @ts-ignore
12
13
  graffitiCRUDTests(useGraffiti, useSession1, useSession2);
13
14
  graffitiDiscoverTests(useGraffiti, useSession1, useSession2);
14
15
  graffitiMediaTests(useGraffiti, useSession1, useSession2);
package/src/utilities.ts CHANGED
@@ -1,11 +1,4 @@
1
- import {
2
- GraffitiErrorInvalidSchema,
3
- GraffitiErrorNotFound,
4
- type GraffitiObject,
5
- type GraffitiObjectBase,
6
- type JSONSchema,
7
- } from "@graffiti-garden/api";
8
- import type Ajv from "ajv";
1
+ import { GraffitiErrorNotFound } from "@graffiti-garden/api";
9
2
 
10
3
  export function encodeBase64(bytes: Uint8Array): string {
11
4
  // Convert it to base64
@@ -30,18 +23,22 @@ export function randomBase64(numBytes: number = 32): string {
30
23
  return encodeBase64(bytes);
31
24
  }
32
25
 
33
- const OBJECT_URL_PREFIX = "graffiti:object:";
34
- const MEDIA_URL_PREFIX = "graffiti:media:";
26
+ const OBJECT_URL_PREFIX = "graffiti:";
35
27
 
28
+ export function encodeObjectUrlComponent(value: string) {
29
+ const replaced = value.replace(/:/g, "!").replace(/\//g, "~");
30
+ return encodeURIComponent(replaced);
31
+ }
32
+ export function decodeObjectUrlComponent(value: string) {
33
+ const decoded = decodeURIComponent(value);
34
+ return decoded.replace(/!/g, ":").replace(/~/g, "/");
35
+ }
36
36
  export function encodeGraffitiUrl(actor: string, id: string, prefix: string) {
37
- return `${prefix}${encodeURIComponent(actor)}:${encodeURIComponent(id)}`;
37
+ return `${prefix}${encodeObjectUrlComponent(actor)}:${encodeObjectUrlComponent(id)}`;
38
38
  }
39
39
  export function encodeObjectUrl(actor: string, id: string) {
40
40
  return encodeGraffitiUrl(actor, id, OBJECT_URL_PREFIX);
41
41
  }
42
- export function encodeMediaUrl(actor: string, id: string) {
43
- return encodeGraffitiUrl(actor, id, MEDIA_URL_PREFIX);
44
- }
45
42
 
46
43
  export function decodeGraffitiUrl(url: string, prefix: string) {
47
44
  if (!url.startsWith(prefix)) {
@@ -51,15 +48,12 @@ export function decodeGraffitiUrl(url: string, prefix: string) {
51
48
  if (slices.length !== 2) {
52
49
  throw new GraffitiErrorNotFound("URL has too many colon-seperated parts");
53
50
  }
54
- const [actor, id] = slices.map(decodeURIComponent);
51
+ const [actor, id] = slices.map(decodeObjectUrlComponent);
55
52
  return { actor, id };
56
53
  }
57
54
  export function decodeObjectUrl(url: string) {
58
55
  return decodeGraffitiUrl(url, OBJECT_URL_PREFIX);
59
56
  }
60
- export function decodeMediaUrl(url: string) {
61
- return decodeGraffitiUrl(url, MEDIA_URL_PREFIX);
62
- }
63
57
 
64
58
  export async function blobToBase64(blob: Blob): Promise<string> {
65
59
  if (typeof FileReader !== "undefined") {
@@ -89,22 +83,3 @@ export async function base64ToBlob(dataUrl: string) {
89
83
  const response = await fetch(dataUrl);
90
84
  return await response.blob();
91
85
  }
92
-
93
- export function compileGraffitiObjectSchema<Schema extends JSONSchema>(
94
- ajv: Ajv,
95
- schema: Schema,
96
- ) {
97
- try {
98
- // Force the validation guard because
99
- // it is too big for the type checker.
100
- // Fortunately json-schema-to-ts is
101
- // well tested against ajv.
102
- return ajv.compile(schema) as (
103
- data: GraffitiObjectBase,
104
- ) => data is GraffitiObject<Schema>;
105
- } catch (error) {
106
- throw new GraffitiErrorInvalidSchema(
107
- error instanceof Error ? error.message : undefined,
108
- );
109
- }
110
- }