@graffiti-garden/implementation-local 0.4.3 → 0.5.1

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/database.ts"],
4
- "sourcesContent": ["import type {\n Graffiti,\n GraffitiObjectBase,\n GraffitiLocation,\n JSONSchema,\n} from \"@graffiti-garden/api\";\nimport {\n GraffitiErrorNotFound,\n GraffitiErrorSchemaMismatch,\n GraffitiErrorForbidden,\n GraffitiErrorPatchError,\n} from \"@graffiti-garden/api\";\nimport {\n locationToUri,\n unpackLocationOrUri,\n randomBase64,\n applyGraffitiPatch,\n maskGraffitiObject,\n isActorAllowedGraffitiObject,\n isObjectNewer,\n compileGraffitiObjectSchema,\n} from \"./utilities.js\";\nimport { Repeater } from \"@repeaterjs/repeater\";\nimport type Ajv from \"ajv\";\nimport type { applyPatch } from \"fast-json-patch\";\n\n/**\n * Constructor options for the GraffitiPoubchDB class.\n */\nexport interface GraffitiLocalOptions {\n /**\n * Options to pass to the PouchDB constructor.\n * Defaults to `{ name: \"graffitiDb\" }`.\n *\n * See the [PouchDB documentation](https://pouchdb.com/api.html#create_database)\n * for available options.\n */\n pouchDBOptions?: PouchDB.Configuration.DatabaseConfiguration;\n /**\n * Defines the name of the {@link https://api.graffiti.garden/interfaces/GraffitiObjectBase.html#source | `source` }\n * under which to store objects.\n * Defaults to `\"local\"`.\n */\n sourceName?: string;\n /**\n * Whether to allow putting objects with a different than the\n * default source name. Defaults to `false`.\n *\n * Allows this implementation to be used as a client-side cache\n * for remote sources.\n */\n allowOtherSources?: boolean;\n /**\n * Whether to allow the user to set the lastModified field\n * when putting objects. Defaults to `false`.\n *\n * Allows this implementation to be used as a client-side cache\n * for remote sources.\n */\n allowSettinngLastModified?: boolean;\n /**\n * The time in milliseconds to keep tombstones before deleting them.\n * See the {@link https://api.graffiti.garden/classes/Graffiti.html#discover | `discover` }\n * documentation for more information.\n */\n tombstoneRetention?: number;\n /**\n * An optional Ajv instance to use for schema validation.\n * If not provided, an internal instance will be created.\n */\n ajv?: Ajv;\n}\n\nconst DEFAULT_TOMBSTONE_RETENTION = 86400000; // 1 day in milliseconds\nconst DEFAULT_SOURCE_NAME = \"local\";\n\n/**\n * An implementation of only the database operations of the\n * GraffitiAPI without synchronization or session management.\n */\nexport class GraffitiLocalDatabase\n implements\n Pick<\n Graffiti,\n | \"get\"\n | \"put\"\n | \"patch\"\n | \"delete\"\n | \"discover\"\n | \"recoverOrphans\"\n | \"channelStats\"\n >\n{\n protected db_: Promise<PouchDB.Database<GraffitiObjectBase>> | undefined;\n protected applyPatch_: Promise<typeof applyPatch> | 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<GraffitiObjectBase>(\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: GraffitiObjectBase) {\n const paddedLastModified = object.lastModified\n .toString()\n .padStart(15, \"0\");\n object.channels.forEach(function (channel) {\n const id =\n encodeURIComponent(channel) + \"/\" + paddedLastModified;\n //@ts-ignore\n emit(id);\n });\n }.toString(),\n },\n orphansPerActorAndLastModified: {\n map: function (object: GraffitiObjectBase) {\n if (object.channels.length === 0) {\n const paddedLastModified = object.lastModified\n .toString()\n .padStart(15, \"0\");\n const id =\n encodeURIComponent(object.actor) +\n \"/\" +\n paddedLastModified;\n //@ts-ignore\n emit(id);\n }\n }.toString(),\n },\n channelStatsPerActor: {\n map: function (object: GraffitiObjectBase) {\n if (object.tombstone) return;\n object.channels.forEach(function (channel) {\n const id =\n encodeURIComponent(object.actor) +\n \"/\" +\n encodeURIComponent(channel);\n //@ts-ignore\n emit(id, object.lastModified);\n });\n }.toString(),\n reduce: \"_stats\",\n },\n },\n })\n //@ts-ignore\n .catch((error) => {\n if (\n error &&\n typeof error === \"object\" &&\n \"name\" in error &&\n error.name === \"conflict\"\n ) {\n // Design document already exists\n return;\n } else {\n throw error;\n }\n });\n return db;\n })();\n }\n return this.db_;\n }\n\n get applyPatch() {\n if (!this.applyPatch_) {\n this.applyPatch_ = (async () => {\n const { applyPatch } = await import(\"fast-json-patch\");\n return applyPatch;\n })();\n }\n return this.applyPatch_;\n }\n\n get ajv() {\n if (!this.ajv_) {\n this.ajv_ = this.options.ajv\n ? Promise.resolve(this.options.ajv)\n : (async () => {\n const { default: Ajv } = await import(\"ajv\");\n return new Ajv({ strict: false });\n })();\n }\n return this.ajv_;\n }\n\n constructor(options?: GraffitiLocalOptions) {\n this.options = options ?? {};\n }\n\n protected async queryByLocation(location: GraffitiLocation) {\n const uri = locationToUri(location) + \"/\";\n const results = await (\n await this.db\n ).allDocs({\n startkey: uri,\n endkey: uri + \"\\uffff\", // \\uffff is the last unicode character\n include_docs: true,\n });\n const docs = results.rows\n .map((row) => row.doc)\n // Remove undefined docs\n .reduce<\n PouchDB.Core.ExistingDocument<\n GraffitiObjectBase & PouchDB.Core.AllDocsMeta\n >[]\n >((acc, doc) => {\n if (doc) acc.push(doc);\n return acc;\n }, []);\n return docs;\n }\n\n protected docId(location: GraffitiLocation) {\n return locationToUri(location) + \"/\" + randomBase64();\n }\n\n get: Graffiti[\"get\"] = async (...args) => {\n const [locationOrUri, schema, session] = args;\n const { location } = unpackLocationOrUri(locationOrUri);\n\n const docsAll = await this.queryByLocation(location);\n\n // Filter out ones not allowed\n const docs = docsAll.filter((doc) =>\n isActorAllowedGraffitiObject(doc, session),\n );\n if (!docs.length) throw new GraffitiErrorNotFound();\n\n // Get the most recent document\n const doc = docs.reduce((a, b) => (isObjectNewer(a, b) ? a : b));\n\n // Strip out the _id and _rev\n const { _id, _rev, _conflicts, _attachments, ...object } = doc;\n\n // Mask out the allowed list and channels\n // if the user is not the owner\n maskGraffitiObject(object, [], session);\n\n const validate = compileGraffitiObjectSchema(await this.ajv, schema);\n if (!validate(object)) {\n throw new GraffitiErrorSchemaMismatch();\n }\n return object;\n };\n\n /**\n * Deletes all docs at a particular location.\n * If the `keepLatest` flag is set to true,\n * the doc with the most recent timestamp will be\n * spared. If there are multiple docs with the same\n * timestamp, the one with the highest `_id` will be\n * spared.\n */\n protected async deleteAtLocation(\n location: GraffitiLocation,\n keepLatest: boolean = false,\n ) {\n const docsAtLocationAll = await this.queryByLocation(location);\n const docsAtLocation = docsAtLocationAll.filter((doc) => !doc.tombstone);\n if (!docsAtLocation.length) return undefined;\n\n // Get the most recent lastModified timestamp.\n const latestModified = docsAtLocation\n .map((doc) => doc.lastModified)\n .reduce((a, b) => (a > b ? a : b));\n\n // Delete all old docs\n const docsToDelete = docsAtLocation.filter(\n (doc) => !keepLatest || doc.lastModified < latestModified,\n );\n\n // For docs with the same timestamp,\n // keep the one with the highest _id\n // to break concurrency ties\n const concurrentDocsAll = docsAtLocation.filter(\n (doc) => keepLatest && doc.lastModified === latestModified,\n );\n if (concurrentDocsAll.length) {\n const keepDocId = concurrentDocsAll\n .map((doc) => doc._id)\n .reduce((a, b) => (a > b ? a : b));\n const concurrentDocsToDelete = concurrentDocsAll.filter(\n (doc) => doc._id !== keepDocId,\n );\n docsToDelete.push(...concurrentDocsToDelete);\n }\n\n const lastModified = keepLatest ? latestModified : new Date().getTime();\n\n const deleteResults = await (\n await this.db\n ).bulkDocs<GraffitiObjectBase>(\n docsToDelete.map((doc) => ({\n ...doc,\n tombstone: true,\n lastModified,\n })),\n );\n\n // Get one of the docs that was deleted\n let deletedObject: GraffitiObjectBase | undefined = undefined;\n for (const resultOrError of deleteResults) {\n if (\"ok\" in resultOrError) {\n const { id } = resultOrError;\n const deletedDoc = docsToDelete.find((doc) => doc._id === id);\n if (deletedDoc) {\n const { _id, _rev, _conflicts, _attachments, ...object } = deletedDoc;\n deletedObject = {\n ...object,\n tombstone: true,\n lastModified,\n };\n break;\n }\n }\n }\n\n return deletedObject;\n }\n\n delete: Graffiti[\"delete\"] = async (...args) => {\n const [locationOrUri, session] = args;\n const { location } = unpackLocationOrUri(locationOrUri);\n if (location.actor !== session.actor) {\n throw new GraffitiErrorForbidden();\n }\n\n const deletedObject = await this.deleteAtLocation(location);\n if (!deletedObject) {\n throw new GraffitiErrorNotFound();\n }\n return deletedObject;\n };\n\n put: Graffiti[\"put\"] = async (...args) => {\n const [objectPartial, session] = args;\n if (objectPartial.actor && objectPartial.actor !== session.actor) {\n throw new GraffitiErrorForbidden();\n }\n if (\n objectPartial.source &&\n objectPartial.source !==\n (this.options.sourceName ?? DEFAULT_SOURCE_NAME) &&\n !(this.options.allowOtherSources ?? false)\n ) {\n throw new GraffitiErrorForbidden(\n \"Putting an object that does not match this source\",\n );\n }\n\n const lastModified =\n ((this.options.allowSettinngLastModified ?? false) &&\n objectPartial.lastModified) ||\n new Date().getTime();\n\n const object: GraffitiObjectBase = {\n value: objectPartial.value,\n channels: objectPartial.channels,\n allowed: objectPartial.allowed,\n name: objectPartial.name ?? randomBase64(),\n source:\n objectPartial.source ?? this.options.sourceName ?? DEFAULT_SOURCE_NAME,\n actor: session.actor,\n tombstone: false,\n lastModified,\n };\n\n await (\n await this.db\n ).put({\n _id: this.docId(object),\n ...object,\n });\n\n // Delete the old object\n const previousObject = await this.deleteAtLocation(object, true);\n if (previousObject) {\n return previousObject;\n } else {\n return {\n ...object,\n value: {},\n channels: [],\n allowed: undefined,\n tombstone: true,\n };\n }\n };\n\n patch: Graffiti[\"patch\"] = async (...args) => {\n const [patch, locationOrUri, session] = args;\n const { location } = unpackLocationOrUri(locationOrUri);\n if (location.actor !== session.actor) {\n throw new GraffitiErrorForbidden();\n }\n const originalObject = await this.get(locationOrUri, {}, session);\n if (originalObject.tombstone) {\n throw new GraffitiErrorNotFound(\n \"The object you are trying to patch has been deleted\",\n );\n }\n\n // Patch it outside of the database\n const patchObject: GraffitiObjectBase = { ...originalObject };\n for (const prop of [\"value\", \"channels\", \"allowed\"] as const) {\n applyGraffitiPatch(await this.applyPatch, prop, patch, patchObject);\n }\n\n // Make sure the value is an object\n if (\n typeof patchObject.value !== \"object\" ||\n Array.isArray(patchObject.value) ||\n !patchObject.value\n ) {\n throw new GraffitiErrorPatchError(\"value is no longer an object\");\n }\n\n // Make sure the channels are an array of strings\n if (\n !Array.isArray(patchObject.channels) ||\n !patchObject.channels.every((channel) => typeof channel === \"string\")\n ) {\n throw new GraffitiErrorPatchError(\n \"channels are no longer an array of strings\",\n );\n }\n\n // Make sure the allowed list is an array of strings or undefined\n if (\n patchObject.allowed &&\n (!Array.isArray(patchObject.allowed) ||\n !patchObject.allowed.every((allowed) => typeof allowed === \"string\"))\n ) {\n throw new GraffitiErrorPatchError(\n \"allowed list is not an array of strings\",\n );\n }\n\n patchObject.lastModified = new Date().getTime();\n await (\n await this.db\n ).put({\n ...patchObject,\n _id: this.docId(patchObject),\n });\n\n // Delete the old object\n await this.deleteAtLocation(patchObject, true);\n\n return {\n ...originalObject,\n tombstone: true,\n lastModified: patchObject.lastModified,\n };\n };\n\n protected queryLastModifiedSuffixes(schema: JSONSchema) {\n // Use the index for queries over ranges of lastModified\n let startKeySuffix = \"\";\n let endKeySuffix = \"\\uffff\";\n if (\n typeof schema === \"object\" &&\n schema.properties?.lastModified &&\n typeof schema.properties.lastModified === \"object\"\n ) {\n const lastModifiedSchema = schema.properties.lastModified;\n\n const minimum = lastModifiedSchema.minimum;\n const exclusiveMinimum = lastModifiedSchema.exclusiveMinimum;\n\n let intMinimum: number | undefined;\n if (exclusiveMinimum !== undefined) {\n intMinimum = Math.ceil(exclusiveMinimum);\n intMinimum === exclusiveMinimum && intMinimum++;\n } else if (minimum !== undefined) {\n intMinimum = Math.ceil(minimum);\n }\n\n if (intMinimum !== undefined) {\n startKeySuffix = intMinimum.toString().padStart(15, \"0\");\n }\n\n const maximum = lastModifiedSchema.maximum;\n const exclusiveMaximum = lastModifiedSchema.exclusiveMaximum;\n\n let intMaximum: number | undefined;\n if (exclusiveMaximum !== undefined) {\n intMaximum = Math.floor(exclusiveMaximum);\n intMaximum === exclusiveMaximum && intMaximum--;\n } else if (maximum !== undefined) {\n intMaximum = Math.floor(maximum);\n }\n\n if (intMaximum !== undefined) {\n endKeySuffix = intMaximum.toString().padStart(15, \"0\");\n }\n }\n return {\n startKeySuffix,\n endKeySuffix,\n };\n }\n\n discover: Graffiti[\"discover\"] = (...args) => {\n const [channels, schema, session] = args;\n\n const { startKeySuffix, endKeySuffix } =\n this.queryLastModifiedSuffixes(schema);\n\n const repeater: ReturnType<\n typeof Graffiti.prototype.discover<typeof schema>\n > = new Repeater(async (push, stop) => {\n const validate = compileGraffitiObjectSchema(await this.ajv, schema);\n\n const processedIds = new Set<string>();\n\n for (const channel of channels) {\n const keyPrefix = encodeURIComponent(channel) + \"/\";\n const startkey = keyPrefix + startKeySuffix;\n const endkey = keyPrefix + endKeySuffix;\n\n const result = await (\n await this.db\n ).query<GraffitiObjectBase>(\n \"indexes/objectsPerChannelAndLastModified\",\n { startkey, endkey, include_docs: true },\n );\n\n for (const row of result.rows) {\n const doc = row.doc;\n if (!doc) continue;\n\n const { _id, _rev, ...object } = doc;\n\n // Don't double return the same object\n // (which can happen if it's in multiple channels)\n if (processedIds.has(_id)) continue;\n processedIds.add(_id);\n\n // Make sure the user is allowed to see it\n if (!isActorAllowedGraffitiObject(doc, session)) continue;\n\n // Mask out the allowed list and channels\n // if the user is not the owner\n maskGraffitiObject(object, channels, session);\n\n // Check that it matches the schema\n if (validate(object)) {\n await push({ value: object });\n }\n }\n }\n stop();\n return {\n tombstoneRetention:\n this.options.tombstoneRetention ?? DEFAULT_TOMBSTONE_RETENTION,\n };\n });\n\n return repeater;\n };\n\n recoverOrphans: Graffiti[\"recoverOrphans\"] = (schema, session) => {\n const { startKeySuffix, endKeySuffix } =\n this.queryLastModifiedSuffixes(schema);\n const keyPrefix = encodeURIComponent(session.actor) + \"/\";\n const startkey = keyPrefix + startKeySuffix;\n const endkey = keyPrefix + endKeySuffix;\n\n const repeater: ReturnType<\n typeof Graffiti.prototype.recoverOrphans<typeof schema>\n > = new Repeater(async (push, stop) => {\n const validate = compileGraffitiObjectSchema(await this.ajv, schema);\n\n const result = await (\n await this.db\n ).query<GraffitiObjectBase>(\"indexes/orphansPerActorAndLastModified\", {\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 // No masking/access necessary because\n // the objects are all owned by the querier\n\n const { _id, _rev, ...object } = doc;\n if (validate(object)) {\n await push({ value: object });\n }\n }\n stop();\n return {\n tombstoneRetention:\n this.options.tombstoneRetention ?? DEFAULT_TOMBSTONE_RETENTION,\n };\n });\n\n return repeater;\n };\n\n channelStats: Graffiti[\"channelStats\"] = (session) => {\n const repeater: ReturnType<typeof Graffiti.prototype.channelStats> =\n new Repeater(async (push, stop) => {\n const keyPrefix = encodeURIComponent(session.actor) + \"/\";\n const result = await (\n await this.db\n ).query(\"indexes/channelStatsPerActor\", {\n startkey: keyPrefix,\n endkey: keyPrefix + \"\\uffff\",\n reduce: true,\n group: true,\n });\n for (const row of result.rows) {\n const channelEncoded = row.key.split(\"/\")[1];\n if (typeof channelEncoded !== \"string\") continue;\n const { count, max: lastModified } = row.value;\n if (typeof count !== \"number\" || typeof lastModified !== \"number\")\n continue;\n await push({\n value: {\n channel: decodeURIComponent(channelEncoded),\n count,\n lastModified,\n },\n });\n }\n stop();\n });\n\n return repeater;\n };\n}\n"],
5
- "mappings": "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",
6
- "names": ["database_exports", "__export", "GraffitiLocalDatabase", "__toCommonJS", "import_api", "import_utilities", "import_repeater", "DEFAULT_TOMBSTONE_RETENTION", "DEFAULT_SOURCE_NAME", "PouchDB", "pouchDbOptions", "db", "object", "paddedLastModified", "channel", "id", "error", "applyPatch", "Ajv", "options", "location", "uri", "row", "acc", "doc", "args", "locationOrUri", "schema", "session", "docs", "a", "_id", "_rev", "_conflicts", "_attachments", "keepLatest", "docsAtLocation", "latestModified", "b", "docsToDelete", "concurrentDocsAll", "keepDocId", "concurrentDocsToDelete", "lastModified", "deleteResults", "deletedObject", "resultOrError", "deletedDoc", "objectPartial", "previousObject", "patch", "originalObject", "patchObject", "prop", "allowed", "startKeySuffix", "endKeySuffix", "lastModifiedSchema", "minimum", "exclusiveMinimum", "intMinimum", "maximum", "exclusiveMaximum", "intMaximum", "channels", "push", "stop", "validate", "processedIds", "keyPrefix", "startkey", "endkey", "result", "channelEncoded", "count"]
4
+ "sourcesContent": ["import type {\n Graffiti,\n GraffitiObjectBase,\n GraffitiObjectUrl,\n JSONSchema,\n GraffitiSession,\n} from \"@graffiti-garden/api\";\nimport {\n GraffitiErrorNotFound,\n GraffitiErrorSchemaMismatch,\n GraffitiErrorForbidden,\n GraffitiErrorPatchError,\n} from \"@graffiti-garden/api\";\nimport {\n randomBase64,\n applyGraffitiPatch,\n maskGraffitiObject,\n isActorAllowedGraffitiObject,\n isObjectNewer,\n compileGraffitiObjectSchema,\n unpackLocationOrUri,\n} from \"./utilities.js\";\nimport { Repeater } from \"@repeaterjs/repeater\";\nimport type Ajv from \"ajv\";\nimport type { applyPatch } from \"fast-json-patch\";\n\n/**\n * Constructor options for the GraffitiPoubchDB class.\n */\nexport interface GraffitiLocalOptions {\n /**\n * Options to pass to the PouchDB constructor.\n * Defaults to `{ name: \"graffitiDb\" }`.\n *\n * See the [PouchDB documentation](https://pouchdb.com/api.html#create_database)\n * for available options.\n */\n pouchDBOptions?: PouchDB.Configuration.DatabaseConfiguration;\n /**\n * Includes the scheme and other information (possibly domain name)\n * to prefix prefixes all URIs put in the system. Defaults to `graffiti:local`.\n */\n origin?: string;\n /**\n * Whether to allow putting objects at arbtirary URIs, i.e.\n * URIs that are *not* prefixed with the origin or not generated\n * by the system. Defaults to `false`.\n *\n * Allows this implementation to be used as a client-side cache\n * for remote sources.\n */\n allowSettingArbitraryUris?: boolean;\n /**\n * Whether to allow the user to set the lastModified field\n * when putting objects. Defaults to `false`.\n *\n * Allows this implementation to be used as a client-side cache\n * for remote sources.\n */\n allowSettinngLastModified?: boolean;\n /**\n * The time in milliseconds to keep tombstones before deleting them.\n * See the {@link https://api.graffiti.garden/classes/Graffiti.html#discover | `discover` }\n * documentation for more information.\n */\n tombstoneRetention?: number;\n /**\n * An optional Ajv instance to use for schema validation.\n * If not provided, an internal instance will be created.\n */\n ajv?: Ajv;\n}\n\nconst DEFAULT_TOMBSTONE_RETENTION = 86400000; // 1 day in milliseconds\nconst DEFAULT_ORIGIN = \"graffiti:local:\";\n\n/**\n * An implementation of only the database operations of the\n * GraffitiAPI without synchronization or session management.\n */\nexport class GraffitiLocalDatabase\n implements\n Pick<\n Graffiti,\n | \"get\"\n | \"put\"\n | \"patch\"\n | \"delete\"\n | \"discover\"\n | \"recoverOrphans\"\n | \"channelStats\"\n >\n{\n protected db_: Promise<PouchDB.Database<GraffitiObjectBase>> | undefined;\n protected applyPatch_: Promise<typeof applyPatch> | undefined;\n protected ajv_: Promise<Ajv> | undefined;\n protected readonly options: GraffitiLocalOptions;\n protected readonly origin: string;\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<GraffitiObjectBase>(\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: GraffitiObjectBase) {\n const paddedLastModified = object.lastModified\n .toString()\n .padStart(15, \"0\");\n object.channels.forEach(function (channel) {\n const id =\n encodeURIComponent(channel) + \"/\" + paddedLastModified;\n //@ts-ignore\n emit(id);\n });\n }.toString(),\n },\n orphansPerActorAndLastModified: {\n map: function (object: GraffitiObjectBase) {\n if (object.channels.length === 0) {\n const paddedLastModified = object.lastModified\n .toString()\n .padStart(15, \"0\");\n const id =\n encodeURIComponent(object.actor) +\n \"/\" +\n paddedLastModified;\n //@ts-ignore\n emit(id);\n }\n }.toString(),\n },\n channelStatsPerActor: {\n map: function (object: GraffitiObjectBase) {\n if (object.tombstone) return;\n object.channels.forEach(function (channel) {\n const id =\n encodeURIComponent(object.actor) +\n \"/\" +\n encodeURIComponent(channel);\n //@ts-ignore\n emit(id, object.lastModified);\n });\n }.toString(),\n reduce: \"_stats\",\n },\n },\n })\n //@ts-ignore\n .catch((error) => {\n if (\n error &&\n typeof error === \"object\" &&\n \"name\" in error &&\n error.name === \"conflict\"\n ) {\n // Design document already exists\n return;\n } else {\n throw error;\n }\n });\n return db;\n })();\n }\n return this.db_;\n }\n\n get applyPatch() {\n if (!this.applyPatch_) {\n this.applyPatch_ = (async () => {\n const { applyPatch } = await import(\"fast-json-patch\");\n return applyPatch;\n })();\n }\n return this.applyPatch_;\n }\n\n get ajv() {\n if (!this.ajv_) {\n this.ajv_ = this.options.ajv\n ? Promise.resolve(this.options.ajv)\n : (async () => {\n const { default: Ajv } = await import(\"ajv\");\n return new Ajv({ strict: false });\n })();\n }\n return this.ajv_;\n }\n\n constructor(options?: GraffitiLocalOptions) {\n this.options = options ?? {};\n this.origin = this.options.origin ?? DEFAULT_ORIGIN;\n if (!this.origin.endsWith(\":\") && !this.origin.endsWith(\"/\")) {\n this.origin += \"/\";\n }\n }\n\n protected async allDocsAtLocation(locationOrUri: GraffitiObjectUrl | string) {\n const uri = unpackLocationOrUri(locationOrUri) + \"/\";\n const results = await (\n await this.db\n ).allDocs({\n startkey: uri,\n endkey: uri + \"\\uffff\", // \\uffff is the last unicode character\n include_docs: true,\n });\n const docs = results.rows\n .map((row) => row.doc)\n // Remove undefined docs\n .reduce<\n PouchDB.Core.ExistingDocument<\n GraffitiObjectBase & PouchDB.Core.AllDocsMeta\n >[]\n >((acc, doc) => {\n if (doc) acc.push(doc);\n return acc;\n }, []);\n return docs;\n }\n\n protected docId(location: GraffitiObjectUrl) {\n return location.url + \"/\" + randomBase64();\n }\n\n get: Graffiti[\"get\"] = async (...args) => {\n const [locationOrUri, schema, session] = args;\n\n const docsAll = await this.allDocsAtLocation(locationOrUri);\n\n // Filter out ones not allowed\n const docs = docsAll.filter((doc) =>\n isActorAllowedGraffitiObject(doc, session),\n );\n if (!docs.length)\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 // Get the most recent document\n const doc = docs.reduce((a, b) => (isObjectNewer(a, b) ? a : b));\n\n // Strip out the _id and _rev\n const { _id, _rev, _conflicts, _attachments, ...object } = doc;\n\n // Mask out the allowed list and channels\n // if the user is not the owner\n maskGraffitiObject(object, [], session);\n\n const validate = compileGraffitiObjectSchema(await this.ajv, schema);\n if (!validate(object)) {\n throw new GraffitiErrorSchemaMismatch();\n }\n return object;\n };\n\n /**\n * Deletes all docs at a particular location.\n * If the `keepLatest` flag is set to true,\n * the doc with the most recent timestamp will be\n * spared. If there are multiple docs with the same\n * timestamp, the one with the highest `_id` will be\n * spared.\n */\n protected async deleteAtLocation(\n locationOrUri: GraffitiObjectUrl | string,\n options: {\n keepLatest?: boolean;\n session?: GraffitiSession;\n } = {\n keepLatest: false,\n },\n ) {\n const docsAtLocationAll = await this.allDocsAtLocation(locationOrUri);\n const docsAtLocationAllowed = options.session\n ? docsAtLocationAll.filter((doc) =>\n isActorAllowedGraffitiObject(doc, options.session),\n )\n : docsAtLocationAll;\n if (!docsAtLocationAllowed.length) {\n throw new GraffitiErrorNotFound(\n \"The object you are trying to delete either does not exist or you are not allowed to see it\",\n );\n } else if (\n options.session &&\n docsAtLocationAllowed.some((doc) => doc.actor !== options.session?.actor)\n ) {\n throw new GraffitiErrorForbidden(\n \"You cannot delete an object owned by another actor\",\n );\n }\n const docsAtLocation = docsAtLocationAllowed.filter(\n (doc) => !doc.tombstone,\n );\n if (!docsAtLocation.length) return undefined;\n\n // Get the most recent lastModified timestamp.\n const latestModified = docsAtLocation\n .map((doc) => doc.lastModified)\n .reduce((a, b) => (a > b ? a : b));\n\n // Delete all old docs\n const docsToDelete = docsAtLocation.filter(\n (doc) => !options.keepLatest || doc.lastModified < latestModified,\n );\n\n // For docs with the same timestamp,\n // keep the one with the highest _id\n // to break concurrency ties\n const concurrentDocsAll = docsAtLocation.filter(\n (doc) => options.keepLatest && doc.lastModified === latestModified,\n );\n if (concurrentDocsAll.length) {\n const keepDocId = concurrentDocsAll\n .map((doc) => doc._id)\n .reduce((a, b) => (a > b ? a : b));\n const concurrentDocsToDelete = concurrentDocsAll.filter(\n (doc) => doc._id !== keepDocId,\n );\n docsToDelete.push(...concurrentDocsToDelete);\n }\n\n const lastModified = options.keepLatest\n ? latestModified\n : new Date().getTime();\n\n const deleteResults = await (\n await this.db\n ).bulkDocs<GraffitiObjectBase>(\n docsToDelete.map((doc) => ({\n ...doc,\n tombstone: true,\n lastModified,\n })),\n );\n\n // Get one of the docs that was deleted\n let deletedObject: GraffitiObjectBase | undefined = undefined;\n for (const resultOrError of deleteResults) {\n if (\"ok\" in resultOrError) {\n const { id } = resultOrError;\n const deletedDoc = docsToDelete.find((doc) => doc._id === id);\n if (deletedDoc) {\n const { _id, _rev, _conflicts, _attachments, ...object } = deletedDoc;\n deletedObject = {\n ...object,\n tombstone: true,\n lastModified,\n };\n break;\n }\n }\n }\n\n return deletedObject;\n }\n\n delete: Graffiti[\"delete\"] = async (...args) => {\n const [locationOrUri, session] = args;\n const deletedObject = await this.deleteAtLocation(locationOrUri, {\n session,\n });\n if (!deletedObject) {\n throw new GraffitiErrorNotFound(\"The object has already been deleted\");\n }\n return deletedObject;\n };\n\n put: Graffiti[\"put\"] = async (...args) => {\n const [objectPartial, session] = args;\n if (objectPartial.actor && objectPartial.actor !== session.actor) {\n throw new GraffitiErrorForbidden(\n \"Cannot put an object with a different actor than the session actor\",\n );\n }\n\n if (objectPartial.url) {\n let oldObject: GraffitiObjectBase | undefined;\n try {\n oldObject = await this.get(objectPartial.url, {}, session);\n } catch (e) {\n if (e instanceof GraffitiErrorNotFound) {\n if (!this.options.allowSettingArbitraryUris) {\n throw new GraffitiErrorNotFound(\n \"The object you are trying to replace does not exist or you are not allowed to see it\",\n );\n }\n } else {\n throw e;\n }\n }\n if (oldObject?.actor !== session.actor) {\n throw new GraffitiErrorForbidden(\n \"The object you are trying to replace is owned by another actor\",\n );\n }\n }\n\n const lastModified =\n ((this.options.allowSettinngLastModified ?? false) &&\n objectPartial.lastModified) ||\n new Date().getTime();\n\n const object: GraffitiObjectBase = {\n value: objectPartial.value,\n channels: objectPartial.channels,\n allowed: objectPartial.allowed,\n url: objectPartial.url ?? this.origin + randomBase64(),\n actor: session.actor,\n tombstone: false,\n lastModified,\n };\n\n await (\n await this.db\n ).put({\n _id: this.docId(object),\n ...object,\n });\n\n // Delete the old object\n const previousObject = await this.deleteAtLocation(object, {\n keepLatest: true,\n });\n if (previousObject) {\n return previousObject;\n } else {\n return {\n ...object,\n value: {},\n channels: [],\n allowed: [],\n tombstone: true,\n };\n }\n };\n\n patch: Graffiti[\"patch\"] = async (...args) => {\n const [patch, locationOrUri, session] = args;\n let originalObject: GraffitiObjectBase;\n try {\n originalObject = await this.get(locationOrUri, {}, session);\n } catch (e) {\n if (e instanceof GraffitiErrorNotFound) {\n throw new GraffitiErrorNotFound(\n \"The object you are trying to patch does not exist or you are not allowed to see it\",\n );\n } else {\n throw e;\n }\n }\n if (originalObject.actor !== session.actor) {\n throw new GraffitiErrorForbidden(\n \"The object you are trying to patch is owned by another actor\",\n );\n } else if (originalObject.tombstone) {\n throw new GraffitiErrorNotFound(\n \"The object you are trying to patch has been deleted\",\n );\n }\n\n // Patch it outside of the database\n const patchObject: GraffitiObjectBase = { ...originalObject };\n for (const prop of [\"value\", \"channels\", \"allowed\"] as const) {\n applyGraffitiPatch(await this.applyPatch, prop, patch, patchObject);\n }\n\n // Make sure the value is an object\n if (\n typeof patchObject.value !== \"object\" ||\n Array.isArray(patchObject.value) ||\n !patchObject.value\n ) {\n throw new GraffitiErrorPatchError(\"value is no longer an object\");\n }\n\n // Make sure the channels are an array of strings\n if (\n !Array.isArray(patchObject.channels) ||\n !patchObject.channels.every((channel) => typeof channel === \"string\")\n ) {\n throw new GraffitiErrorPatchError(\n \"channels are no longer an array of strings\",\n );\n }\n\n // Make sure the allowed list is an array of strings or undefined\n if (\n patchObject.allowed &&\n (!Array.isArray(patchObject.allowed) ||\n !patchObject.allowed.every((allowed) => typeof allowed === \"string\"))\n ) {\n throw new GraffitiErrorPatchError(\n \"allowed list is not an array of strings\",\n );\n }\n\n patchObject.lastModified = new Date().getTime();\n await (\n await this.db\n ).put({\n ...patchObject,\n _id: this.docId(patchObject),\n });\n\n // Delete the old object\n await this.deleteAtLocation(patchObject, {\n keepLatest: true,\n });\n\n return {\n ...originalObject,\n tombstone: true,\n lastModified: patchObject.lastModified,\n };\n };\n\n protected queryLastModifiedSuffixes(schema: JSONSchema) {\n // Use the index for queries over ranges of lastModified\n let startKeySuffix = \"\";\n let endKeySuffix = \"\\uffff\";\n if (\n typeof schema === \"object\" &&\n schema.properties?.lastModified &&\n typeof schema.properties.lastModified === \"object\"\n ) {\n const lastModifiedSchema = schema.properties.lastModified;\n\n const minimum = lastModifiedSchema.minimum;\n const exclusiveMinimum = lastModifiedSchema.exclusiveMinimum;\n\n let intMinimum: number | undefined;\n if (exclusiveMinimum !== undefined) {\n intMinimum = Math.ceil(exclusiveMinimum);\n intMinimum === exclusiveMinimum && intMinimum++;\n } else if (minimum !== undefined) {\n intMinimum = Math.ceil(minimum);\n }\n\n if (intMinimum !== undefined) {\n startKeySuffix = intMinimum.toString().padStart(15, \"0\");\n }\n\n const maximum = lastModifiedSchema.maximum;\n const exclusiveMaximum = lastModifiedSchema.exclusiveMaximum;\n\n let intMaximum: number | undefined;\n if (exclusiveMaximum !== undefined) {\n intMaximum = Math.floor(exclusiveMaximum);\n intMaximum === exclusiveMaximum && intMaximum--;\n } else if (maximum !== undefined) {\n intMaximum = Math.floor(maximum);\n }\n\n if (intMaximum !== undefined) {\n endKeySuffix = intMaximum.toString().padStart(15, \"0\");\n }\n }\n return {\n startKeySuffix,\n endKeySuffix,\n };\n }\n\n discover: Graffiti[\"discover\"] = (...args) => {\n const [channels, schema, session] = args;\n\n const { startKeySuffix, endKeySuffix } =\n this.queryLastModifiedSuffixes(schema);\n\n const repeater: ReturnType<\n typeof Graffiti.prototype.discover<typeof schema>\n > = new Repeater(async (push, stop) => {\n const validate = compileGraffitiObjectSchema(await this.ajv, schema);\n\n const processedIds = new Set<string>();\n\n for (const channel of channels) {\n const keyPrefix = encodeURIComponent(channel) + \"/\";\n const startkey = keyPrefix + startKeySuffix;\n const endkey = keyPrefix + endKeySuffix;\n\n const result = await (\n await this.db\n ).query<GraffitiObjectBase>(\n \"indexes/objectsPerChannelAndLastModified\",\n { startkey, endkey, include_docs: true },\n );\n\n for (const row of result.rows) {\n const doc = row.doc;\n if (!doc) continue;\n\n const { _id, _rev, ...object } = doc;\n\n // Don't double return the same object\n // (which can happen if it's in multiple channels)\n if (processedIds.has(_id)) continue;\n processedIds.add(_id);\n\n // Make sure the user is allowed to see it\n if (!isActorAllowedGraffitiObject(doc, session)) continue;\n\n // Mask out the allowed list and channels\n // if the user is not the owner\n maskGraffitiObject(object, channels, session);\n\n // Check that it matches the schema\n if (validate(object)) {\n await push({ value: object });\n }\n }\n }\n stop();\n return {\n tombstoneRetention:\n this.options.tombstoneRetention ?? DEFAULT_TOMBSTONE_RETENTION,\n };\n });\n\n return repeater;\n };\n\n recoverOrphans: Graffiti[\"recoverOrphans\"] = (schema, session) => {\n const { startKeySuffix, endKeySuffix } =\n this.queryLastModifiedSuffixes(schema);\n const keyPrefix = encodeURIComponent(session.actor) + \"/\";\n const startkey = keyPrefix + startKeySuffix;\n const endkey = keyPrefix + endKeySuffix;\n\n const repeater: ReturnType<\n typeof Graffiti.prototype.recoverOrphans<typeof schema>\n > = new Repeater(async (push, stop) => {\n const validate = compileGraffitiObjectSchema(await this.ajv, schema);\n\n const result = await (\n await this.db\n ).query<GraffitiObjectBase>(\"indexes/orphansPerActorAndLastModified\", {\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 // No masking/access necessary because\n // the objects are all owned by the querier\n\n const { _id, _rev, ...object } = doc;\n if (validate(object)) {\n await push({ value: object });\n }\n }\n stop();\n return {\n tombstoneRetention:\n this.options.tombstoneRetention ?? DEFAULT_TOMBSTONE_RETENTION,\n };\n });\n\n return repeater;\n };\n\n channelStats: Graffiti[\"channelStats\"] = (session) => {\n const repeater: ReturnType<typeof Graffiti.prototype.channelStats> =\n new Repeater(async (push, stop) => {\n const keyPrefix = encodeURIComponent(session.actor) + \"/\";\n const result = await (\n await this.db\n ).query(\"indexes/channelStatsPerActor\", {\n startkey: keyPrefix,\n endkey: keyPrefix + \"\\uffff\",\n reduce: true,\n group: true,\n });\n for (const row of result.rows) {\n const channelEncoded = row.key.split(\"/\")[1];\n if (typeof channelEncoded !== \"string\") continue;\n const { count, max: lastModified } = row.value;\n if (typeof count !== \"number\" || typeof lastModified !== \"number\")\n continue;\n await push({\n value: {\n channel: decodeURIComponent(channelEncoded),\n count,\n lastModified,\n },\n });\n }\n stop();\n });\n\n return repeater;\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["doc"]
7
7
  }
package/dist/cjs/index.js CHANGED
@@ -1,2 +1,51 @@
1
- "use strict";var s=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var h=Object.getOwnPropertyNames;var p=Object.prototype.hasOwnProperty;var d=(a,t)=>{for(var i in t)s(a,i,{get:t[i],enumerable:!0})},g=(a,t,i,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of h(t))!p.call(a,o)&&o!==i&&s(a,o,{get:()=>t[o],enumerable:!(r=l(t,o))||r.enumerable});return a};var G=a=>g(s({},"__esModule",{value:!0}),a);var b={};d(b,{GraffitiLocal:()=>L});module.exports=G(b);var n=require("@graffiti-garden/api"),c=require("./session-manager.js"),f=require("./database.js"),e=require("./utilities.js");class L extends n.Graffiti{locationToUri=e.locationToUri;uriToLocation=e.uriToLocation;sessionManagerLocal=new c.GraffitiLocalSessionManager;login=this.sessionManagerLocal.login.bind(this.sessionManagerLocal);logout=this.sessionManagerLocal.logout.bind(this.sessionManagerLocal);sessionEvents=this.sessionManagerLocal.sessionEvents;put;get;patch;delete;discover;recoverOrphans;channelStats;constructor(t){super();const i=new f.GraffitiLocalDatabase(t);this.put=i.put.bind(i),this.get=i.get.bind(i),this.patch=i.patch.bind(i),this.delete=i.delete.bind(i),this.discover=i.discover.bind(i),this.recoverOrphans=i.recoverOrphans.bind(i),this.channelStats=i.channelStats.bind(i)}}
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 index_exports = {};
20
+ __export(index_exports, {
21
+ GraffitiLocal: () => GraffitiLocal
22
+ });
23
+ module.exports = __toCommonJS(index_exports);
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;
33
+ get;
34
+ patch;
35
+ delete;
36
+ discover;
37
+ recoverOrphans;
38
+ channelStats;
39
+ constructor(options) {
40
+ super();
41
+ const graffitiPouchDbBase = new import_database.GraffitiLocalDatabase(options);
42
+ this.put = graffitiPouchDbBase.put.bind(graffitiPouchDbBase);
43
+ this.get = graffitiPouchDbBase.get.bind(graffitiPouchDbBase);
44
+ this.patch = graffitiPouchDbBase.patch.bind(graffitiPouchDbBase);
45
+ this.delete = graffitiPouchDbBase.delete.bind(graffitiPouchDbBase);
46
+ this.discover = graffitiPouchDbBase.discover.bind(graffitiPouchDbBase);
47
+ this.recoverOrphans = graffitiPouchDbBase.recoverOrphans.bind(graffitiPouchDbBase);
48
+ this.channelStats = graffitiPouchDbBase.channelStats.bind(graffitiPouchDbBase);
49
+ }
50
+ }
2
51
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/index.ts"],
4
- "sourcesContent": ["import { Graffiti } from \"@graffiti-garden/api\";\nimport { GraffitiLocalSessionManager } from \"./session-manager.js\";\nimport {\n GraffitiLocalDatabase,\n type GraffitiLocalOptions,\n} from \"./database.js\";\nimport { locationToUri, uriToLocation } from \"./utilities.js\";\n\nexport type { GraffitiLocalOptions };\n\n/**\n * A local implementation of the [Graffiti API](https://api.graffiti.garden/classes/Graffiti.html)\n * based on [PouchDB](https://pouchdb.com/). PouchDb will automatically persist data in a local\n * database, either in the browser or in Node.js.\n * It can also be configured to work with an external [CouchDB](https://couchdb.apache.org/) server,\n * although using it with a remote server will not be secure.\n */\nexport class GraffitiLocal extends Graffiti {\n locationToUri = locationToUri;\n uriToLocation = uriToLocation;\n\n protected sessionManagerLocal = new GraffitiLocalSessionManager();\n login = this.sessionManagerLocal.login.bind(this.sessionManagerLocal);\n logout = this.sessionManagerLocal.logout.bind(this.sessionManagerLocal);\n sessionEvents = this.sessionManagerLocal.sessionEvents;\n\n put: Graffiti[\"put\"];\n get: Graffiti[\"get\"];\n patch: Graffiti[\"patch\"];\n delete: Graffiti[\"delete\"];\n discover: Graffiti[\"discover\"];\n recoverOrphans: Graffiti[\"recoverOrphans\"];\n channelStats: Graffiti[\"channelStats\"];\n\n constructor(options?: GraffitiLocalOptions) {\n super();\n\n const graffitiPouchDbBase = new GraffitiLocalDatabase(options);\n\n this.put = graffitiPouchDbBase.put.bind(graffitiPouchDbBase);\n this.get = graffitiPouchDbBase.get.bind(graffitiPouchDbBase);\n this.patch = graffitiPouchDbBase.patch.bind(graffitiPouchDbBase);\n this.delete = graffitiPouchDbBase.delete.bind(graffitiPouchDbBase);\n this.discover = graffitiPouchDbBase.discover.bind(graffitiPouchDbBase);\n this.recoverOrphans =\n graffitiPouchDbBase.recoverOrphans.bind(graffitiPouchDbBase);\n this.channelStats =\n graffitiPouchDbBase.channelStats.bind(graffitiPouchDbBase);\n }\n}\n"],
5
- "mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,mBAAAE,IAAA,eAAAC,EAAAH,GAAA,IAAAI,EAAyB,gCACzBC,EAA4C,gCAC5CC,EAGO,yBACPC,EAA6C,0BAWtC,MAAML,UAAsB,UAAS,CAC1C,cAAgB,gBAChB,cAAgB,gBAEN,oBAAsB,IAAI,8BACpC,MAAQ,KAAK,oBAAoB,MAAM,KAAK,KAAK,mBAAmB,EACpE,OAAS,KAAK,oBAAoB,OAAO,KAAK,KAAK,mBAAmB,EACtE,cAAgB,KAAK,oBAAoB,cAEzC,IACA,IACA,MACA,OACA,SACA,eACA,aAEA,YAAYM,EAAgC,CAC1C,MAAM,EAEN,MAAMC,EAAsB,IAAI,wBAAsBD,CAAO,EAE7D,KAAK,IAAMC,EAAoB,IAAI,KAAKA,CAAmB,EAC3D,KAAK,IAAMA,EAAoB,IAAI,KAAKA,CAAmB,EAC3D,KAAK,MAAQA,EAAoB,MAAM,KAAKA,CAAmB,EAC/D,KAAK,OAASA,EAAoB,OAAO,KAAKA,CAAmB,EACjE,KAAK,SAAWA,EAAoB,SAAS,KAAKA,CAAmB,EACrE,KAAK,eACHA,EAAoB,eAAe,KAAKA,CAAmB,EAC7D,KAAK,aACHA,EAAoB,aAAa,KAAKA,CAAmB,CAC7D,CACF",
6
- "names": ["index_exports", "__export", "GraffitiLocal", "__toCommonJS", "import_api", "import_session_manager", "import_database", "import_utilities", "options", "graffitiPouchDbBase"]
4
+ "sourcesContent": ["import { Graffiti } 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\n constructor(options?: GraffitiLocalOptions) {\n super();\n\n const graffitiPouchDbBase = new GraffitiLocalDatabase(options);\n\n this.put = graffitiPouchDbBase.put.bind(graffitiPouchDbBase);\n this.get = graffitiPouchDbBase.get.bind(graffitiPouchDbBase);\n this.patch = graffitiPouchDbBase.patch.bind(graffitiPouchDbBase);\n this.delete = graffitiPouchDbBase.delete.bind(graffitiPouchDbBase);\n this.discover = graffitiPouchDbBase.discover.bind(graffitiPouchDbBase);\n this.recoverOrphans =\n graffitiPouchDbBase.recoverOrphans.bind(graffitiPouchDbBase);\n this.channelStats =\n graffitiPouchDbBase.channelStats.bind(graffitiPouchDbBase);\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAAyB;AACzB,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,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;AAAA,EAC7D;AACF;",
6
+ "names": []
7
7
  }
@@ -1,4 +1,107 @@
1
- "use strict";var r=Object.defineProperty;var c=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var f=Object.prototype.hasOwnProperty;var d=(n,t)=>{for(var e in t)r(n,e,{get:t[e],enumerable:!0})},l=(n,t,e,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of g(t))!f.call(n,i)&&i!==e&&r(n,i,{get:()=>t[i],enumerable:!(o=c(t,i))||o.enumerable});return n};var v=n=>l(r({},"__esModule",{value:!0}),n);var E={};d(E,{GraffitiLocalSessionManager:()=>p});module.exports=v(E);class p{sessionEvents=new EventTarget;constructor(){(async()=>{await Promise.resolve();for(const o of this.getLoggedInActors()){const i=new CustomEvent("login",{detail:{session:{actor:o}}});this.sessionEvents.dispatchEvent(i)}const e=new CustomEvent("initialized",{detail:{}});this.sessionEvents.dispatchEvent(e)})()}loggedInActors=[];getLoggedInActors(){if(typeof window<"u"){const t=window.localStorage.getItem("graffiti-actor");return t?t.split(",").map(decodeURIComponent):[]}else return this.loggedInActors}setLoggedInActors(t){typeof window<"u"?window.localStorage.setItem("graffiti-actor",t.map(encodeURIComponent).join(",")):this.loggedInActors=t}login=async t=>{let e=t?.actor;if(!e&&typeof window<"u"){const s=window.prompt(`This is an insecure implementation of the Graffiti API for *demo purposes only*. Do not store any sensitive information here.
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 session_manager_exports = {};
20
+ __export(session_manager_exports, {
21
+ GraffitiLocalSessionManager: () => GraffitiLocalSessionManager
22
+ });
23
+ module.exports = __toCommonJS(session_manager_exports);
24
+ class GraffitiLocalSessionManager {
25
+ sessionEvents = new EventTarget();
26
+ constructor() {
27
+ const sessionRestorer = async () => {
28
+ await Promise.resolve();
29
+ for (const actor of this.getLoggedInActors()) {
30
+ const event2 = new CustomEvent("login", {
31
+ detail: { session: { actor } }
32
+ });
33
+ this.sessionEvents.dispatchEvent(event2);
34
+ }
35
+ const event = new CustomEvent(
36
+ "initialized",
37
+ { detail: {} }
38
+ );
39
+ this.sessionEvents.dispatchEvent(event);
40
+ };
41
+ sessionRestorer();
42
+ }
43
+ loggedInActors = [];
44
+ getLoggedInActors() {
45
+ if (typeof window !== "undefined") {
46
+ const actorsString = window.localStorage.getItem("graffiti-actor");
47
+ return actorsString ? actorsString.split(",").map(decodeURIComponent) : [];
48
+ } else {
49
+ return this.loggedInActors;
50
+ }
51
+ }
52
+ setLoggedInActors(actors) {
53
+ if (typeof window !== "undefined") {
54
+ window.localStorage.setItem(
55
+ "graffiti-actor",
56
+ actors.map(encodeURIComponent).join(",")
57
+ );
58
+ } else {
59
+ this.loggedInActors = actors;
60
+ }
61
+ }
62
+ login = async (proposal) => {
63
+ let actor = proposal?.actor;
64
+ if (!actor && typeof window !== "undefined") {
65
+ const response = window.prompt(
66
+ `This is an insecure implementation of the Graffiti API for *demo purposes only*. Do not store any sensitive information here.
2
67
 
3
- Simply choose a username to log in.`);s&&(e=s)}let o;if(!e)o={error:new Error("No actor ID provided to login")};else{const s=this.getLoggedInActors();s.includes(e)||this.setLoggedInActors([...s,e]),o={session:{actor:e}}}const i=new CustomEvent("login",{detail:o});this.sessionEvents.dispatchEvent(i)};logout=async t=>{const e=this.getLoggedInActors(),o=e.includes(t.actor);o&&this.setLoggedInActors(e.filter(a=>a!==t.actor));const i=o?{actor:t.actor}:{actor:t.actor,error:new Error("Not logged in with that actor")},s=new CustomEvent("logout",{detail:i});this.sessionEvents.dispatchEvent(s)}}
68
+ Simply choose a username to log in.`
69
+ );
70
+ if (response) actor = response;
71
+ }
72
+ let detail;
73
+ if (!actor) {
74
+ detail = {
75
+ error: new Error("No actor ID provided to login")
76
+ };
77
+ } else {
78
+ const existingActors = this.getLoggedInActors();
79
+ if (!existingActors.includes(actor)) {
80
+ this.setLoggedInActors([...existingActors, actor]);
81
+ }
82
+ detail = {
83
+ session: { actor }
84
+ };
85
+ }
86
+ const event = new CustomEvent("login", { detail });
87
+ this.sessionEvents.dispatchEvent(event);
88
+ };
89
+ logout = async (session) => {
90
+ const existingActors = this.getLoggedInActors();
91
+ const exists = existingActors.includes(session.actor);
92
+ if (exists) {
93
+ this.setLoggedInActors(
94
+ existingActors.filter((actor) => actor !== session.actor)
95
+ );
96
+ }
97
+ const detail = exists ? {
98
+ actor: session.actor
99
+ } : {
100
+ actor: session.actor,
101
+ error: new Error("Not logged in with that actor")
102
+ };
103
+ const event = new CustomEvent("logout", { detail });
104
+ this.sessionEvents.dispatchEvent(event);
105
+ };
106
+ }
4
107
  //# sourceMappingURL=session-manager.js.map
@@ -2,6 +2,6 @@
2
2
  "version": 3,
3
3
  "sources": ["../../src/session-manager.ts"],
4
4
  "sourcesContent": ["import type {\n Graffiti,\n GraffitiLoginEvent,\n GraffitiLogoutEvent,\n GraffitiSessionInitializedEvent,\n} from \"@graffiti-garden/api\";\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 GraffitiLocalSessionManager {\n sessionEvents: Graffiti[\"sessionEvents\"] = new EventTarget();\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 actor of this.getLoggedInActors()) {\n const event: GraffitiLoginEvent = new CustomEvent(\"login\", {\n detail: { session: { actor } },\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 loggedInActors: string[] = [];\n\n protected getLoggedInActors(): string[] {\n if (typeof window !== \"undefined\") {\n const actorsString = window.localStorage.getItem(\"graffiti-actor\");\n return actorsString\n ? actorsString.split(\",\").map(decodeURIComponent)\n : [];\n } else {\n return this.loggedInActors;\n }\n }\n\n protected setLoggedInActors(actors: string[]) {\n if (typeof window !== \"undefined\") {\n window.localStorage.setItem(\n \"graffiti-actor\",\n actors.map(encodeURIComponent).join(\",\"),\n );\n } else {\n this.loggedInActors = actors;\n }\n }\n\n login: Graffiti[\"login\"] = async (proposal) => {\n let actor = proposal?.actor;\n if (!actor && typeof window !== \"undefined\") {\n const response = window.prompt(\n `This is an insecure implementation of the Graffiti API \\\n for *demo purposes only*. Do not store any sensitive information \\\n here.\\\n \\n\\n\\\n Simply choose a username to log in.`,\n );\n if (response) actor = response;\n }\n\n let detail: GraffitiLoginEvent[\"detail\"];\n if (!actor) {\n detail = {\n error: new Error(\"No actor ID provided to login\"),\n };\n } else {\n const existingActors = this.getLoggedInActors();\n if (!existingActors.includes(actor)) {\n this.setLoggedInActors([...existingActors, actor]);\n }\n\n detail = {\n session: { actor },\n };\n }\n\n const event: GraffitiLoginEvent = new CustomEvent(\"login\", { detail });\n this.sessionEvents.dispatchEvent(event);\n };\n\n logout: Graffiti[\"logout\"] = async (session) => {\n const existingActors = this.getLoggedInActors();\n const exists = existingActors.includes(session.actor);\n if (exists) {\n this.setLoggedInActors(\n existingActors.filter((actor) => actor !== session.actor),\n );\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": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,iCAAAE,IAAA,eAAAC,EAAAH,GAkBO,MAAME,CAA4B,CACvC,cAA2C,IAAI,YAE/C,aAAc,EAEY,SAAY,CAElC,MAAM,QAAQ,QAAQ,EAGtB,UAAWE,KAAS,KAAK,kBAAkB,EAAG,CAC5C,MAAMC,EAA4B,IAAI,YAAY,QAAS,CACzD,OAAQ,CAAE,QAAS,CAAE,MAAAD,CAAM,CAAE,CAC/B,CAAC,EACD,KAAK,cAAc,cAAcC,CAAK,CACxC,CAEA,MAAMA,EAAyC,IAAI,YACjD,cACA,CAAE,OAAQ,CAAC,CAAE,CACf,EACA,KAAK,cAAc,cAAcA,CAAK,CACxC,GACgB,CAClB,CAEA,eAA2B,CAAC,EAElB,mBAA8B,CACtC,GAAI,OAAO,OAAW,IAAa,CACjC,MAAMC,EAAe,OAAO,aAAa,QAAQ,gBAAgB,EACjE,OAAOA,EACHA,EAAa,MAAM,GAAG,EAAE,IAAI,kBAAkB,EAC9C,CAAC,CACP,KACE,QAAO,KAAK,cAEhB,CAEU,kBAAkBC,EAAkB,CACxC,OAAO,OAAW,IACpB,OAAO,aAAa,QAClB,iBACAA,EAAO,IAAI,kBAAkB,EAAE,KAAK,GAAG,CACzC,EAEA,KAAK,eAAiBA,CAE1B,CAEA,MAA2B,MAAOC,GAAa,CAC7C,IAAIJ,EAAQI,GAAU,MACtB,GAAI,CAACJ,GAAS,OAAO,OAAW,IAAa,CAC3C,MAAMK,EAAW,OAAO,OACtB;AAAA;AAAA,sCAKF,EACIA,IAAUL,EAAQK,EACxB,CAEA,IAAIC,EACJ,GAAI,CAACN,EACHM,EAAS,CACP,MAAO,IAAI,MAAM,+BAA+B,CAClD,MACK,CACL,MAAMC,EAAiB,KAAK,kBAAkB,EACzCA,EAAe,SAASP,CAAK,GAChC,KAAK,kBAAkB,CAAC,GAAGO,EAAgBP,CAAK,CAAC,EAGnDM,EAAS,CACP,QAAS,CAAE,MAAAN,CAAM,CACnB,CACF,CAEA,MAAMC,EAA4B,IAAI,YAAY,QAAS,CAAE,OAAAK,CAAO,CAAC,EACrE,KAAK,cAAc,cAAcL,CAAK,CACxC,EAEA,OAA6B,MAAOO,GAAY,CAC9C,MAAMD,EAAiB,KAAK,kBAAkB,EACxCE,EAASF,EAAe,SAASC,EAAQ,KAAK,EAChDC,GACF,KAAK,kBACHF,EAAe,OAAQP,GAAUA,IAAUQ,EAAQ,KAAK,CAC1D,EAGF,MAAMF,EAAwCG,EAC1C,CACE,MAAOD,EAAQ,KACjB,EACA,CACE,MAAOA,EAAQ,MACf,MAAO,IAAI,MAAM,+BAA+B,CAClD,EAEEP,EAA6B,IAAI,YAAY,SAAU,CAAE,OAAAK,CAAO,CAAC,EACvE,KAAK,cAAc,cAAcL,CAAK,CACxC,CACF",
6
- "names": ["session_manager_exports", "__export", "GraffitiLocalSessionManager", "__toCommonJS", "actor", "event", "actorsString", "actors", "proposal", "response", "detail", "existingActors", "session", "exists"]
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAkBO,MAAM,4BAA4B;AAAA,EACvC,gBAA2C,IAAI,YAAY;AAAA,EAE3D,cAAc;AAEZ,UAAM,kBAAkB,YAAY;AAElC,YAAM,QAAQ,QAAQ;AAGtB,iBAAW,SAAS,KAAK,kBAAkB,GAAG;AAC5C,cAAMA,SAA4B,IAAI,YAAY,SAAS;AAAA,UACzD,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE;AAAA,QAC/B,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,iBAA2B,CAAC;AAAA,EAElB,oBAA8B;AACtC,QAAI,OAAO,WAAW,aAAa;AACjC,YAAM,eAAe,OAAO,aAAa,QAAQ,gBAAgB;AACjE,aAAO,eACH,aAAa,MAAM,GAAG,EAAE,IAAI,kBAAkB,IAC9C,CAAC;AAAA,IACP,OAAO;AACL,aAAO,KAAK;AAAA,IACd;AAAA,EACF;AAAA,EAEU,kBAAkB,QAAkB;AAC5C,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,aAAa;AAAA,QAClB;AAAA,QACA,OAAO,IAAI,kBAAkB,EAAE,KAAK,GAAG;AAAA,MACzC;AAAA,IACF,OAAO;AACL,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,QAA2B,OAAO,aAAa;AAC7C,QAAI,QAAQ,UAAU;AACtB,QAAI,CAAC,SAAS,OAAO,WAAW,aAAa;AAC3C,YAAM,WAAW,OAAO;AAAA,QACtB;AAAA;AAAA;AAAA,MAKF;AACA,UAAI,SAAU,SAAQ;AAAA,IACxB;AAEA,QAAI;AACJ,QAAI,CAAC,OAAO;AACV,eAAS;AAAA,QACP,OAAO,IAAI,MAAM,+BAA+B;AAAA,MAClD;AAAA,IACF,OAAO;AACL,YAAM,iBAAiB,KAAK,kBAAkB;AAC9C,UAAI,CAAC,eAAe,SAAS,KAAK,GAAG;AACnC,aAAK,kBAAkB,CAAC,GAAG,gBAAgB,KAAK,CAAC;AAAA,MACnD;AAEA,eAAS;AAAA,QACP,SAAS,EAAE,MAAM;AAAA,MACnB;AAAA,IACF;AAEA,UAAM,QAA4B,IAAI,YAAY,SAAS,EAAE,OAAO,CAAC;AACrE,SAAK,cAAc,cAAc,KAAK;AAAA,EACxC;AAAA,EAEA,SAA6B,OAAO,YAAY;AAC9C,UAAM,iBAAiB,KAAK,kBAAkB;AAC9C,UAAM,SAAS,eAAe,SAAS,QAAQ,KAAK;AACpD,QAAI,QAAQ;AACV,WAAK;AAAA,QACH,eAAe,OAAO,CAAC,UAAU,UAAU,QAAQ,KAAK;AAAA,MAC1D;AAAA,IACF;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
7
  }
@@ -1,2 +1,11 @@
1
- "use strict";var s=require("@graffiti-garden/api/tests"),o=require("./index");const t=()=>new o.GraffitiLocal,i=()=>({actor:"someone"}),e=()=>({actor:"someoneelse"});(0,s.graffitiLocationTests)(t),(0,s.graffitiCRUDTests)(t,i,e),(0,s.graffitiDiscoverTests)(t,i,e),(0,s.graffitiOrphanTests)(t,i,e),(0,s.graffitiChannelStatsTests)(t,i,e);
1
+ "use strict";
2
+ var import_tests = require("@graffiti-garden/api/tests");
3
+ var import_index = require("./index");
4
+ const useGraffiti = () => new import_index.GraffitiLocal();
5
+ const useSession1 = () => ({ actor: "someone" });
6
+ const useSession2 = () => ({ actor: "someoneelse" });
7
+ (0, import_tests.graffitiCRUDTests)(useGraffiti, useSession1, useSession2);
8
+ (0, import_tests.graffitiDiscoverTests)(useGraffiti, useSession1, useSession2);
9
+ (0, import_tests.graffitiOrphanTests)(useGraffiti, useSession1, useSession2);
10
+ (0, import_tests.graffitiChannelStatsTests)(useGraffiti, useSession1, useSession2);
2
11
  //# sourceMappingURL=tests.spec.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/tests.spec.ts"],
4
- "sourcesContent": ["import {\n graffitiLocationTests,\n graffitiCRUDTests,\n graffitiDiscoverTests,\n graffitiOrphanTests,\n graffitiChannelStatsTests,\n} from \"@graffiti-garden/api/tests\";\nimport { GraffitiLocal } from \"./index\";\n\nconst useGraffiti = () => new GraffitiLocal();\nconst useSession1 = () => ({ actor: \"someone\" });\nconst useSession2 = () => ({ actor: \"someoneelse\" });\n\ngraffitiLocationTests(useGraffiti);\ngraffitiCRUDTests(useGraffiti, useSession1, useSession2);\ngraffitiDiscoverTests(useGraffiti, useSession1, useSession2);\ngraffitiOrphanTests(useGraffiti, useSession1, useSession2);\ngraffitiChannelStatsTests(useGraffiti, useSession1, useSession2);\n"],
5
- "mappings": "aAAA,IAAAA,EAMO,sCACPC,EAA8B,mBAE9B,MAAMC,EAAc,IAAM,IAAI,gBACxBC,EAAc,KAAO,CAAE,MAAO,SAAU,GACxCC,EAAc,KAAO,CAAE,MAAO,aAAc,MAElD,yBAAsBF,CAAW,KACjC,qBAAkBA,EAAaC,EAAaC,CAAW,KACvD,yBAAsBF,EAAaC,EAAaC,CAAW,KAC3D,uBAAoBF,EAAaC,EAAaC,CAAW,KACzD,6BAA0BF,EAAaC,EAAaC,CAAW",
6
- "names": ["import_tests", "import_index", "useGraffiti", "useSession1", "useSession2"]
4
+ "sourcesContent": ["import {\n graffitiCRUDTests,\n graffitiDiscoverTests,\n graffitiOrphanTests,\n graffitiChannelStatsTests,\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);\ngraffitiOrphanTests(useGraffiti, useSession1, useSession2);\ngraffitiChannelStatsTests(useGraffiti, useSession1, useSession2);\n"],
5
+ "mappings": ";AAAA,mBAKO;AACP,mBAA8B;AAE9B,MAAM,cAAc,MAAM,IAAI,2BAAc;AAC5C,MAAM,cAAc,OAAO,EAAE,OAAO,UAAU;AAC9C,MAAM,cAAc,OAAO,EAAE,OAAO,cAAc;AAAA,IAElD,gCAAkB,aAAa,aAAa,WAAW;AAAA,IACvD,oCAAsB,aAAa,aAAa,WAAW;AAAA,IAC3D,kCAAoB,aAAa,aAAa,WAAW;AAAA,IACzD,wCAA0B,aAAa,aAAa,WAAW;",
6
+ "names": []
7
7
  }
@@ -1,2 +1,80 @@
1
- "use strict";var f=Object.defineProperty;var d=Object.getOwnPropertyDescriptor;var p=Object.getOwnPropertyNames;var m=Object.prototype.hasOwnProperty;var l=(e,t)=>{for(var r in t)f(e,r,{get:t[r],enumerable:!0})},u=(e,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of p(t))!m.call(e,o)&&o!==r&&f(e,o,{get:()=>t[o],enumerable:!(i=d(t,o))||i.enumerable});return e};var G=e=>u(f({},"__esModule",{value:!0}),e);var P={};l(P,{applyGraffitiPatch:()=>g,compileGraffitiObjectSchema:()=>b,isActorAllowedGraffitiObject:()=>O,isObjectNewer:()=>y,locationToUri:()=>c,maskGraffitiObject:()=>S,randomBase64:()=>h,unpackLocationOrUri:()=>w,uriToLocation:()=>s});module.exports=G(P);var n=require("@graffiti-garden/api");const c=e=>`${e.source}/${encodeURIComponent(e.actor)}/${encodeURIComponent(e.name)}`,s=e=>{const t=e.split("/"),r=t.pop(),i=t.pop();if(!r||!i||!t.length)throw new n.GraffitiErrorInvalidUri;return{name:decodeURIComponent(r),actor:decodeURIComponent(i),source:t.join("/")}};function h(e=16){const t=new Uint8Array(e);return crypto.getRandomValues(t),btoa(String.fromCodePoint(...t)).replace(/\+/g,"-").replace(/\//g,"_").replace(/\=+$/,"")}function w(e){return typeof e=="string"?{location:s(e),uri:e}:{location:{name:e.name,actor:e.actor,source:e.source},uri:c(e)}}function y(e,t){return e.lastModified>t.lastModified||e.lastModified===t.lastModified&&!e.tombstone&&t.tombstone}function g(e,t,r,i){const o=r[t];if(!(!o||!o.length))try{i[t]=e(i[t],o,!0,!1).newDocument}catch(a){throw typeof a=="object"&&a&&"name"in a&&typeof a.name=="string"&&"message"in a&&typeof a.message=="string"?a.name==="TEST_OPERATION_FAILED"?new n.GraffitiErrorPatchTestFailed(a.message):new n.GraffitiErrorPatchError(a.name+": "+a.message):a}}function b(e,t){try{return e.compile(t)}catch(r){throw new n.GraffitiErrorInvalidSchema(r instanceof Error?r.message:void 0)}}function S(e,t,r){e.actor!==r?.actor&&(e.allowed=e.allowed&&r?[r.actor]:void 0,e.channels=e.channels.filter(i=>t.includes(i)))}function O(e,t){return e.allowed===void 0||e.allowed===null||!!t?.actor&&(e.actor===t.actor||e.allowed.includes(t.actor))}
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 utilities_exports = {};
20
+ __export(utilities_exports, {
21
+ applyGraffitiPatch: () => applyGraffitiPatch,
22
+ compileGraffitiObjectSchema: () => compileGraffitiObjectSchema,
23
+ isActorAllowedGraffitiObject: () => isActorAllowedGraffitiObject,
24
+ isObjectNewer: () => isObjectNewer,
25
+ maskGraffitiObject: () => maskGraffitiObject,
26
+ randomBase64: () => randomBase64,
27
+ unpackLocationOrUri: () => unpackLocationOrUri
28
+ });
29
+ module.exports = __toCommonJS(utilities_exports);
30
+ var import_api = require("@graffiti-garden/api");
31
+ function unpackLocationOrUri(locationOrUri) {
32
+ return typeof locationOrUri === "string" ? locationOrUri : locationOrUri.url;
33
+ }
34
+ function randomBase64(numBytes = 24) {
35
+ const bytes = new Uint8Array(numBytes);
36
+ crypto.getRandomValues(bytes);
37
+ const base64 = btoa(String.fromCodePoint(...bytes));
38
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/\=+$/, "");
39
+ }
40
+ function isObjectNewer(left, right) {
41
+ return left.lastModified > right.lastModified || left.lastModified === right.lastModified && !left.tombstone && right.tombstone;
42
+ }
43
+ function applyGraffitiPatch(apply, prop, patch, object) {
44
+ const ops = patch[prop];
45
+ if (!ops || !ops.length) return;
46
+ try {
47
+ object[prop] = apply(object[prop], ops, true, false).newDocument;
48
+ } catch (e) {
49
+ if (typeof e === "object" && e && "name" in e && typeof e.name === "string" && "message" in e && typeof e.message === "string") {
50
+ if (e.name === "TEST_OPERATION_FAILED") {
51
+ throw new import_api.GraffitiErrorPatchTestFailed(e.message);
52
+ } else {
53
+ throw new import_api.GraffitiErrorPatchError(e.name + ": " + e.message);
54
+ }
55
+ } else {
56
+ throw e;
57
+ }
58
+ }
59
+ }
60
+ function compileGraffitiObjectSchema(ajv, schema) {
61
+ try {
62
+ return ajv.compile(schema);
63
+ } catch (error) {
64
+ throw new import_api.GraffitiErrorInvalidSchema(
65
+ error instanceof Error ? error.message : void 0
66
+ );
67
+ }
68
+ }
69
+ function maskGraffitiObject(object, channels, session) {
70
+ if (object.actor !== session?.actor) {
71
+ object.allowed = object.allowed && session ? [session.actor] : void 0;
72
+ object.channels = object.channels.filter(
73
+ (channel) => channels.includes(channel)
74
+ );
75
+ }
76
+ }
77
+ function isActorAllowedGraffitiObject(object, session) {
78
+ return object.allowed === void 0 || object.allowed === null || !!session?.actor && (object.actor === session.actor || object.allowed.includes(session.actor));
79
+ }
2
80
  //# sourceMappingURL=utilities.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utilities.ts"],
4
- "sourcesContent": ["import {\n GraffitiErrorInvalidSchema,\n GraffitiErrorInvalidUri,\n GraffitiErrorPatchError,\n GraffitiErrorPatchTestFailed,\n} from \"@graffiti-garden/api\";\nimport type {\n Graffiti,\n GraffitiObject,\n GraffitiObjectBase,\n GraffitiLocation,\n GraffitiPatch,\n JSONSchema,\n GraffitiSession,\n} from \"@graffiti-garden/api\";\nimport type { Ajv } from \"ajv\";\nimport type { applyPatch } from \"fast-json-patch\";\n\nexport const locationToUri: Graffiti[\"locationToUri\"] = (location) => {\n return `${location.source}/${encodeURIComponent(location.actor)}/${encodeURIComponent(location.name)}`;\n};\n\nexport const uriToLocation: Graffiti[\"uriToLocation\"] = (uri) => {\n const parts = uri.split(\"/\");\n const nameEncoded = parts.pop();\n const webIdEncoded = parts.pop();\n if (!nameEncoded || !webIdEncoded || !parts.length) {\n throw new GraffitiErrorInvalidUri();\n }\n return {\n name: decodeURIComponent(nameEncoded),\n actor: decodeURIComponent(webIdEncoded),\n source: parts.join(\"/\"),\n };\n};\n\nexport function randomBase64(numBytes: number = 16) {\n const bytes = new Uint8Array(numBytes);\n crypto.getRandomValues(bytes);\n // Convert it to base64\n const base64 = btoa(String.fromCodePoint(...bytes));\n // Make sure it is url safe\n return base64.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/\\=+$/, \"\");\n}\n\nexport function unpackLocationOrUri(locationOrUri: GraffitiLocation | string) {\n if (typeof locationOrUri === \"string\") {\n return {\n location: uriToLocation(locationOrUri),\n uri: locationOrUri,\n };\n } else {\n return {\n location: {\n name: locationOrUri.name,\n actor: locationOrUri.actor,\n source: locationOrUri.source,\n },\n uri: locationToUri(locationOrUri),\n };\n }\n}\n\nexport function isObjectNewer(\n left: GraffitiObjectBase,\n right: GraffitiObjectBase,\n) {\n return (\n left.lastModified > right.lastModified ||\n (left.lastModified === right.lastModified &&\n !left.tombstone &&\n right.tombstone)\n );\n}\n\nexport function applyGraffitiPatch<Prop extends keyof GraffitiPatch>(\n apply: typeof applyPatch,\n prop: Prop,\n patch: GraffitiPatch,\n object: GraffitiObjectBase,\n): void {\n const ops = patch[prop];\n if (!ops || !ops.length) return;\n try {\n object[prop] = apply(object[prop], ops, true, false).newDocument;\n } catch (e) {\n if (\n typeof e === \"object\" &&\n e &&\n \"name\" in e &&\n typeof e.name === \"string\" &&\n \"message\" in e &&\n typeof e.message === \"string\"\n ) {\n if (e.name === \"TEST_OPERATION_FAILED\") {\n throw new GraffitiErrorPatchTestFailed(e.message);\n } else {\n throw new GraffitiErrorPatchError(e.name + \": \" + e.message);\n }\n } else {\n throw e;\n }\n }\n}\n\nexport function compileGraffitiObjectSchema<Schema extends JSONSchema>(\n ajv: Ajv,\n schema: Schema,\n) {\n try {\n // Force the validation guard because\n // it is too big for the type checker.\n // Fortunately json-schema-to-ts is\n // well tested against ajv.\n return ajv.compile(schema) as (\n data: GraffitiObjectBase,\n ) => data is GraffitiObject<Schema>;\n } catch (error) {\n throw new GraffitiErrorInvalidSchema(\n error instanceof Error ? error.message : undefined,\n );\n }\n}\n\nexport function maskGraffitiObject(\n object: GraffitiObjectBase,\n channels: string[],\n session?: GraffitiSession | null,\n): void {\n if (object.actor !== session?.actor) {\n object.allowed = object.allowed && session ? [session.actor] : undefined;\n object.channels = object.channels.filter((channel) =>\n channels.includes(channel),\n );\n }\n}\nexport function isActorAllowedGraffitiObject(\n object: GraffitiObjectBase,\n session?: GraffitiSession | null,\n) {\n return (\n object.allowed === undefined ||\n object.allowed === null ||\n (!!session?.actor &&\n (object.actor === session.actor ||\n object.allowed.includes(session.actor)))\n );\n}\n"],
5
- "mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,wBAAAE,EAAA,gCAAAC,EAAA,iCAAAC,EAAA,kBAAAC,EAAA,kBAAAC,EAAA,uBAAAC,EAAA,iBAAAC,EAAA,wBAAAC,EAAA,kBAAAC,IAAA,eAAAC,EAAAX,GAAA,IAAAY,EAKO,gCAaA,MAAMN,EAA4CO,GAChD,GAAGA,EAAS,MAAM,IAAI,mBAAmBA,EAAS,KAAK,CAAC,IAAI,mBAAmBA,EAAS,IAAI,CAAC,GAGzFH,EAA4CI,GAAQ,CAC/D,MAAMC,EAAQD,EAAI,MAAM,GAAG,EACrBE,EAAcD,EAAM,IAAI,EACxBE,EAAeF,EAAM,IAAI,EAC/B,GAAI,CAACC,GAAe,CAACC,GAAgB,CAACF,EAAM,OAC1C,MAAM,IAAI,0BAEZ,MAAO,CACL,KAAM,mBAAmBC,CAAW,EACpC,MAAO,mBAAmBC,CAAY,EACtC,OAAQF,EAAM,KAAK,GAAG,CACxB,CACF,EAEO,SAASP,EAAaU,EAAmB,GAAI,CAClD,MAAMC,EAAQ,IAAI,WAAWD,CAAQ,EACrC,cAAO,gBAAgBC,CAAK,EAEb,KAAK,OAAO,cAAc,GAAGA,CAAK,CAAC,EAEpC,QAAQ,MAAO,GAAG,EAAE,QAAQ,MAAO,GAAG,EAAE,QAAQ,OAAQ,EAAE,CAC1E,CAEO,SAASV,EAAoBW,EAA0C,CAC5E,OAAI,OAAOA,GAAkB,SACpB,CACL,SAAUV,EAAcU,CAAa,EACrC,IAAKA,CACP,EAEO,CACL,SAAU,CACR,KAAMA,EAAc,KACpB,MAAOA,EAAc,MACrB,OAAQA,EAAc,MACxB,EACA,IAAKd,EAAcc,CAAa,CAClC,CAEJ,CAEO,SAASf,EACdgB,EACAC,EACA,CACA,OACED,EAAK,aAAeC,EAAM,cACzBD,EAAK,eAAiBC,EAAM,cAC3B,CAACD,EAAK,WACNC,EAAM,SAEZ,CAEO,SAASpB,EACdqB,EACAC,EACAC,EACAC,EACM,CACN,MAAMC,EAAMF,EAAMD,CAAI,EACtB,GAAI,GAACG,GAAO,CAACA,EAAI,QACjB,GAAI,CACFD,EAAOF,CAAI,EAAID,EAAMG,EAAOF,CAAI,EAAGG,EAAK,GAAM,EAAK,EAAE,WACvD,OAASC,EAAG,CACV,MACE,OAAOA,GAAM,UACbA,GACA,SAAUA,GACV,OAAOA,EAAE,MAAS,UAClB,YAAaA,GACb,OAAOA,EAAE,SAAY,SAEjBA,EAAE,OAAS,wBACP,IAAI,+BAA6BA,EAAE,OAAO,EAE1C,IAAI,0BAAwBA,EAAE,KAAO,KAAOA,EAAE,OAAO,EAGvDA,CAEV,CACF,CAEO,SAASzB,EACd0B,EACAC,EACA,CACA,GAAI,CAKF,OAAOD,EAAI,QAAQC,CAAM,CAG3B,OAASC,EAAO,CACd,MAAM,IAAI,6BACRA,aAAiB,MAAQA,EAAM,QAAU,MAC3C,CACF,CACF,CAEO,SAASxB,EACdmB,EACAM,EACAC,EACM,CACFP,EAAO,QAAUO,GAAS,QAC5BP,EAAO,QAAUA,EAAO,SAAWO,EAAU,CAACA,EAAQ,KAAK,EAAI,OAC/DP,EAAO,SAAWA,EAAO,SAAS,OAAQQ,GACxCF,EAAS,SAASE,CAAO,CAC3B,EAEJ,CACO,SAAS9B,EACdsB,EACAO,EACA,CACA,OACEP,EAAO,UAAY,QACnBA,EAAO,UAAY,MAClB,CAAC,CAACO,GAAS,QACTP,EAAO,QAAUO,EAAQ,OACxBP,EAAO,QAAQ,SAASO,EAAQ,KAAK,EAE7C",
6
- "names": ["utilities_exports", "__export", "applyGraffitiPatch", "compileGraffitiObjectSchema", "isActorAllowedGraffitiObject", "isObjectNewer", "locationToUri", "maskGraffitiObject", "randomBase64", "unpackLocationOrUri", "uriToLocation", "__toCommonJS", "import_api", "location", "uri", "parts", "nameEncoded", "webIdEncoded", "numBytes", "bytes", "locationOrUri", "left", "right", "apply", "prop", "patch", "object", "ops", "e", "ajv", "schema", "error", "channels", "session", "channel"]
4
+ "sourcesContent": ["import {\n GraffitiErrorInvalidSchema,\n GraffitiErrorInvalidUri,\n GraffitiErrorPatchError,\n GraffitiErrorPatchTestFailed,\n} from \"@graffiti-garden/api\";\nimport type {\n GraffitiObject,\n GraffitiObjectBase,\n GraffitiPatch,\n JSONSchema,\n GraffitiSession,\n GraffitiObjectUrl,\n} from \"@graffiti-garden/api\";\nimport type { Ajv } from \"ajv\";\nimport type { applyPatch } from \"fast-json-patch\";\n\nexport function unpackLocationOrUri(locationOrUri: GraffitiObjectUrl | string) {\n return typeof locationOrUri === \"string\" ? locationOrUri : locationOrUri.url;\n}\n\nexport function randomBase64(numBytes: number = 24) {\n const bytes = new Uint8Array(numBytes);\n crypto.getRandomValues(bytes);\n // Convert it to base64\n const base64 = btoa(String.fromCodePoint(...bytes));\n // Make sure it is url safe\n return base64.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/\\=+$/, \"\");\n}\n\nexport function isObjectNewer(\n left: GraffitiObjectBase,\n right: GraffitiObjectBase,\n) {\n return (\n left.lastModified > right.lastModified ||\n (left.lastModified === right.lastModified &&\n !left.tombstone &&\n right.tombstone)\n );\n}\n\nexport function applyGraffitiPatch<Prop extends keyof GraffitiPatch>(\n apply: typeof applyPatch,\n prop: Prop,\n patch: GraffitiPatch,\n object: GraffitiObjectBase,\n): void {\n const ops = patch[prop];\n if (!ops || !ops.length) return;\n try {\n object[prop] = apply(object[prop], ops, true, false).newDocument;\n } catch (e) {\n if (\n typeof e === \"object\" &&\n e &&\n \"name\" in e &&\n typeof e.name === \"string\" &&\n \"message\" in e &&\n typeof e.message === \"string\"\n ) {\n if (e.name === \"TEST_OPERATION_FAILED\") {\n throw new GraffitiErrorPatchTestFailed(e.message);\n } else {\n throw new GraffitiErrorPatchError(e.name + \": \" + e.message);\n }\n } else {\n throw e;\n }\n }\n}\n\nexport function compileGraffitiObjectSchema<Schema extends JSONSchema>(\n ajv: Ajv,\n schema: Schema,\n) {\n try {\n // Force the validation guard because\n // it is too big for the type checker.\n // Fortunately json-schema-to-ts is\n // well tested against ajv.\n return ajv.compile(schema) as (\n data: GraffitiObjectBase,\n ) => data is GraffitiObject<Schema>;\n } catch (error) {\n throw new GraffitiErrorInvalidSchema(\n error instanceof Error ? error.message : undefined,\n );\n }\n}\n\nexport function maskGraffitiObject(\n object: GraffitiObjectBase,\n channels: string[],\n session?: GraffitiSession | null,\n): void {\n if (object.actor !== session?.actor) {\n object.allowed = object.allowed && session ? [session.actor] : undefined;\n object.channels = object.channels.filter((channel) =>\n channels.includes(channel),\n );\n }\n}\nexport function isActorAllowedGraffitiObject(\n object: GraffitiObjectBase,\n session?: GraffitiSession | null,\n) {\n return (\n object.allowed === undefined ||\n object.allowed === null ||\n (!!session?.actor &&\n (object.actor === session.actor ||\n object.allowed.includes(session.actor)))\n );\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAKO;AAYA,SAAS,oBAAoB,eAA2C;AAC7E,SAAO,OAAO,kBAAkB,WAAW,gBAAgB,cAAc;AAC3E;AAEO,SAAS,aAAa,WAAmB,IAAI;AAClD,QAAM,QAAQ,IAAI,WAAW,QAAQ;AACrC,SAAO,gBAAgB,KAAK;AAE5B,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,cACd,MACA,OACA;AACA,SACE,KAAK,eAAe,MAAM,gBACzB,KAAK,iBAAiB,MAAM,gBAC3B,CAAC,KAAK,aACN,MAAM;AAEZ;AAEO,SAAS,mBACd,OACA,MACA,OACA,QACM;AACN,QAAM,MAAM,MAAM,IAAI;AACtB,MAAI,CAAC,OAAO,CAAC,IAAI,OAAQ;AACzB,MAAI;AACF,WAAO,IAAI,IAAI,MAAM,OAAO,IAAI,GAAG,KAAK,MAAM,KAAK,EAAE;AAAA,EACvD,SAAS,GAAG;AACV,QACE,OAAO,MAAM,YACb,KACA,UAAU,KACV,OAAO,EAAE,SAAS,YAClB,aAAa,KACb,OAAO,EAAE,YAAY,UACrB;AACA,UAAI,EAAE,SAAS,yBAAyB;AACtC,cAAM,IAAI,wCAA6B,EAAE,OAAO;AAAA,MAClD,OAAO;AACL,cAAM,IAAI,mCAAwB,EAAE,OAAO,OAAO,EAAE,OAAO;AAAA,MAC7D;AAAA,IACF,OAAO;AACL,YAAM;AAAA,IACR;AAAA,EACF;AACF;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;AAEO,SAAS,mBACd,QACA,UACA,SACM;AACN,MAAI,OAAO,UAAU,SAAS,OAAO;AACnC,WAAO,UAAU,OAAO,WAAW,UAAU,CAAC,QAAQ,KAAK,IAAI;AAC/D,WAAO,WAAW,OAAO,SAAS;AAAA,MAAO,CAAC,YACxC,SAAS,SAAS,OAAO;AAAA,IAC3B;AAAA,EACF;AACF;AACO,SAAS,6BACd,QACA,SACA;AACA,SACE,OAAO,YAAY,UACnB,OAAO,YAAY,QAClB,CAAC,CAAC,SAAS,UACT,OAAO,UAAU,QAAQ,SACxB,OAAO,QAAQ,SAAS,QAAQ,KAAK;AAE7C;",
6
+ "names": []
7
7
  }