@graffiti-garden/api 1.0.6 → 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.
- package/dist/browser/index.js +1 -1
- package/dist/browser/index.js.map +2 -2
- package/dist/cjs/{1-api.cjs → 1-api.js} +1 -1
- package/dist/cjs/1-api.js.map +7 -0
- package/dist/cjs/{2-types.cjs → 2-types.js} +1 -1
- package/dist/cjs/{3-errors.cjs → 3-errors.js} +1 -1
- package/dist/cjs/{4-utilities.cjs → 4-utilities.js} +3 -3
- package/dist/cjs/4-utilities.js.map +7 -0
- package/dist/cjs/{5-runtime-types.cjs → 5-runtime-types.js} +1 -1
- package/dist/cjs/5-runtime-types.js.map +7 -0
- package/dist/cjs/{index.cjs → index.js} +6 -6
- package/dist/cjs/index.js.map +7 -0
- package/dist/esm/{1-api.mjs → 1-api.js} +1 -1
- package/dist/esm/1-api.js.map +7 -0
- package/dist/esm/{2-types.mjs → 2-types.js} +1 -1
- package/dist/esm/{3-errors.mjs → 3-errors.js} +1 -1
- package/dist/esm/{4-utilities.mjs → 4-utilities.js} +3 -3
- package/dist/esm/4-utilities.js.map +7 -0
- package/dist/esm/{5-runtime-types.mjs → 5-runtime-types.js} +1 -1
- package/dist/esm/5-runtime-types.js.map +7 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +7 -0
- package/dist/src/1-api.d.ts +1 -1
- package/dist/src/1-api.d.ts.map +1 -1
- package/dist/src/4-utilities.d.ts +1 -1
- package/dist/src/4-utilities.d.ts.map +1 -1
- package/dist/src/5-runtime-types.d.ts +2 -2
- package/dist/src/5-runtime-types.d.ts.map +1 -1
- package/dist/src/index.d.ts +5 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/tests/index.d.ts +4 -4
- package/dist/tests/index.d.ts.map +1 -1
- package/dist/tests.mjs.map +1 -1
- package/package.json +5 -5
- package/src/1-api.ts +1 -1
- package/src/4-utilities.ts +3 -3
- package/src/5-runtime-types.ts +2 -2
- package/src/index.ts +5 -5
- package/tests/crud.ts +1 -1
- package/tests/discover.ts +1 -1
- package/tests/index.ts +4 -4
- package/tests/media.ts +1 -1
- package/dist/cjs/1-api.cjs.map +0 -7
- package/dist/cjs/4-utilities.cjs.map +0 -7
- package/dist/cjs/5-runtime-types.cjs.map +0 -7
- package/dist/cjs/index.cjs.map +0 -7
- package/dist/esm/1-api.mjs.map +0 -7
- package/dist/esm/4-utilities.mjs.map +0 -7
- package/dist/esm/5-runtime-types.mjs.map +0 -7
- package/dist/esm/index.mjs +0 -6
- package/dist/esm/index.mjs.map +0 -7
- /package/dist/cjs/{2-types.cjs.map → 2-types.js.map} +0 -0
- /package/dist/cjs/{3-errors.cjs.map → 3-errors.js.map} +0 -0
- /package/dist/esm/{2-types.mjs.map → 2-types.js.map} +0 -0
- /package/dist/esm/{3-errors.mjs.map → 3-errors.js.map} +0 -0
package/dist/tests.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../tests/crud.ts", "../tests/utils.ts", "../tests/discover.ts", "../tests/media.ts"],
|
|
4
|
-
"sourcesContent": ["import { it, expect, describe, beforeAll } from \"vitest\";\nimport type {\n Graffiti,\n GraffitiSession,\n JSONSchema,\n} from \"@graffiti-garden/api\";\nimport {\n GraffitiErrorNotFound,\n GraffitiErrorSchemaMismatch,\n GraffitiErrorInvalidSchema,\n GraffitiErrorForbidden,\n} from \"@graffiti-garden/api\";\nimport { randomString, randomUrl } from \"./utils\";\n\nexport const graffitiCRUDTests = (\n useGraffiti: () => Pick<Graffiti, \"post\" | \"get\" | \"delete\">,\n useSession1: () => GraffitiSession | Promise<GraffitiSession>,\n useSession2: () => GraffitiSession | Promise<GraffitiSession>,\n) => {\n describe.concurrent(\n \"CRUD\",\n {\n timeout: 20000,\n },\n () => {\n let graffiti: ReturnType<typeof useGraffiti>;\n let session: GraffitiSession;\n let session1: GraffitiSession;\n let session2: GraffitiSession;\n beforeAll(async () => {\n graffiti = useGraffiti();\n session1 = await useSession1();\n session = session1;\n session2 = await useSession2();\n });\n\n it(\"get nonexistant object\", async () => {\n await expect(graffiti.get(randomUrl(), {})).rejects.toThrow(\n GraffitiErrorNotFound,\n );\n });\n\n it(\"post, get, delete\", async () => {\n const value = {\n something: \"hello, world~ c:\",\n };\n const channels = [randomString(), randomString()];\n\n // Post the object\n const previous = await graffiti.post<{}>({ value, channels }, session);\n expect(previous.value).toEqual(value);\n expect(previous.channels).toEqual(channels);\n expect(previous.allowed).toEqual(undefined);\n expect(previous.actor).toEqual(session.actor);\n\n // Get it back\n const gotten = await graffiti.get(previous, {});\n expect(gotten.value).toEqual(value);\n expect(gotten.channels).toEqual([]);\n expect(gotten.allowed).toBeUndefined();\n expect(gotten.url).toEqual(previous.url);\n expect(gotten.actor).toEqual(previous.actor);\n\n // Delete it\n const deleted = await graffiti.delete(gotten, session);\n expect(deleted.value).toEqual(value);\n expect(deleted.channels).toEqual(channels);\n expect(deleted.allowed).toBeUndefined();\n expect(deleted.actor).toEqual(session.actor);\n expect(deleted.url).toEqual(previous.url);\n\n // Get is not found\n await expect(graffiti.get(gotten, {})).rejects.toBeInstanceOf(\n GraffitiErrorNotFound,\n );\n\n // Delete it again\n await expect(graffiti.delete(gotten, session)).rejects.toThrow(\n GraffitiErrorNotFound,\n );\n });\n\n it(\"post then delete with wrong actor\", async () => {\n const posted = await graffiti.post<{}>(\n { value: {}, channels: [] },\n session2,\n );\n\n await expect(graffiti.delete(posted, session1)).rejects.toThrow(\n GraffitiErrorForbidden,\n );\n });\n\n it(\"post and get with schema\", async () => {\n const schema = {\n properties: {\n value: {\n properties: {\n something: {\n type: \"string\",\n },\n another: {\n type: \"array\",\n items: {\n type: \"number\",\n },\n },\n deeper: {\n type: \"object\",\n properties: {\n deepProp: {\n type: \"string\",\n },\n },\n required: [\"deepProp\"],\n },\n },\n required: [\"another\", \"deeper\"],\n },\n },\n } as const satisfies JSONSchema;\n\n const goodValue = {\n something: \"hello\",\n another: [1, 2, 3],\n deeper: {\n deepProp: \"hello\",\n },\n };\n\n const posted = await graffiti.post<typeof schema>(\n {\n value: goodValue,\n channels: [],\n },\n session,\n );\n const gotten = await graffiti.get(posted, schema);\n\n expect(gotten.value.something).toEqual(goodValue.something);\n expect(gotten.value.another).toEqual(goodValue.another);\n expect(gotten.value.another[0]).toEqual(1);\n expect(gotten.value.deeper.deepProp).toEqual(goodValue.deeper.deepProp);\n });\n\n it(\"post and get with invalid schema\", async () => {\n const posted = await graffiti.post<{}>(\n { value: {}, channels: [] },\n session,\n );\n await expect(\n graffiti.get(posted, {\n properties: {\n value: {\n //@ts-ignore\n type: \"asdf\",\n },\n },\n }),\n ).rejects.toThrow(GraffitiErrorInvalidSchema);\n });\n\n it(\"post and get with wrong schema\", async () => {\n const posted = await graffiti.post<{}>(\n {\n value: {\n hello: \"world\",\n },\n channels: [],\n },\n session,\n );\n\n await expect(\n graffiti.get(posted, {\n properties: {\n value: {\n properties: {\n hello: {\n type: \"number\",\n },\n },\n },\n },\n }),\n ).rejects.toThrow(GraffitiErrorSchemaMismatch);\n });\n\n it(\"post and get with empty access control\", async () => {\n const value = {\n um: \"hi\",\n };\n const allowed = [randomUrl()];\n const channels = [randomString()];\n const posted = await graffiti.post<{}>(\n { value, allowed, channels },\n session1,\n );\n\n // Get it with authenticated session\n const gotten = await graffiti.get(posted, {}, session1);\n expect(gotten.url).toEqual(posted.url);\n expect(gotten.actor).toEqual(session1.actor);\n expect(gotten.value).toEqual(value);\n expect(gotten.allowed).toEqual(allowed);\n expect(gotten.channels).toEqual(channels);\n\n // But not without session\n await expect(graffiti.get(posted, {})).rejects.toBeInstanceOf(\n GraffitiErrorNotFound,\n );\n\n // Or the wrong session\n await expect(graffiti.get(posted, {}, session2)).rejects.toBeInstanceOf(\n GraffitiErrorNotFound,\n );\n });\n\n it(\"post and get with specific access control\", async () => {\n const value = {\n um: \"hi\",\n };\n const allowed = [randomUrl(), session2.actor, randomUrl()];\n const channels = [randomString()];\n const posted = await graffiti.post<{}>(\n {\n value,\n allowed,\n channels,\n },\n session1,\n );\n\n // Get it with authenticated session\n const gotten = await graffiti.get(posted, {}, session1);\n expect(gotten.url).toEqual(posted.url);\n expect(gotten.actor).toEqual(session1.actor);\n expect(gotten.value).toEqual(value);\n expect(gotten.allowed).toEqual(allowed);\n expect(gotten.channels).toEqual(channels);\n\n // But not without session\n await expect(graffiti.get(posted, {})).rejects.toBeInstanceOf(\n GraffitiErrorNotFound,\n );\n\n const gotten2 = await graffiti.get(posted, {}, session2);\n expect(gotten.url).toEqual(posted.url);\n expect(gotten.actor).toEqual(session1.actor);\n expect(gotten2.value).toEqual(value);\n // They should only see that is is private to them\n expect(gotten2.allowed).toEqual([session2.actor]);\n // And not see any channels\n expect(gotten2.channels).toEqual([]);\n });\n },\n );\n};\n", "import { assert } from \"vitest\";\nimport type {\n GraffitiPostObject,\n GraffitiObjectStream,\n JSONSchema,\n GraffitiObject,\n GraffitiObjectStreamReturn,\n GraffitiObjectStreamContinue,\n Graffiti,\n GraffitiSession,\n} from \"@graffiti-garden/api\";\n\nexport function randomString(): string {\n const array = new Uint8Array(16);\n crypto.getRandomValues(array);\n const str = Array.from(array)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n\n // check for unicode support\n return str + \"\uD83D\uDC69\uD83C\uDFFD\u200D\u2764\uFE0F\u200D\uD83D\uDC8B\u200D\uD83D\uDC69\uD83C\uDFFB\uD83E\uDEF1\uD83C\uDFFC\u200D\uD83E\uDEF2\uD83C\uDFFF\";\n}\nexport function randomUrl(): string {\n return \"test:\" + randomString();\n}\n\nexport function randomValue() {\n return {\n [randomString()]: randomString(),\n };\n}\n\nexport function randomPostObject(): GraffitiPostObject<{}> {\n return {\n value: randomValue(),\n channels: [randomString(), randomString()],\n };\n}\n\nexport async function nextStreamValue<Schema extends JSONSchema>(\n iterator: GraffitiObjectStream<Schema>,\n): Promise<GraffitiObject<Schema>> {\n const result = await iterator.next();\n assert(!result.done && !result.value.error, \"result has no value\");\n assert(!result.value.tombstone, \"result has been deleted!\");\n return result.value.object;\n}\n\nexport function continueStream<Schema extends JSONSchema>(\n graffiti: Pick<Graffiti, \"continueDiscover\">,\n streamReturn: GraffitiObjectStreamReturn<Schema>,\n type: \"cursor\" | \"continue\",\n session?: GraffitiSession | null,\n): GraffitiObjectStreamContinue<Schema> {\n if (type === \"cursor\") {\n return graffiti.continueDiscover(\n streamReturn.cursor,\n session,\n ) as unknown as GraffitiObjectStreamContinue<Schema>;\n } else {\n return streamReturn.continue();\n }\n}\n", "import { it, expect, describe, assert, beforeAll } from \"vitest\";\nimport type {\n Graffiti,\n GraffitiSession,\n JSONSchema,\n} from \"@graffiti-garden/api\";\nimport {\n GraffitiErrorCursorExpired,\n GraffitiErrorForbidden,\n GraffitiErrorInvalidSchema,\n} from \"@graffiti-garden/api\";\nimport {\n randomString,\n nextStreamValue,\n randomPostObject,\n continueStream,\n randomUrl,\n} from \"./utils\";\n\nexport const graffitiDiscoverTests = (\n useGraffiti: () => Pick<\n Graffiti,\n \"discover\" | \"post\" | \"delete\" | \"continueDiscover\"\n >,\n useSession1: () => GraffitiSession | Promise<GraffitiSession>,\n useSession2: () => GraffitiSession | Promise<GraffitiSession>,\n) => {\n describe.concurrent(\"discover\", { timeout: 20000 }, () => {\n let graffiti: ReturnType<typeof useGraffiti>;\n let session: GraffitiSession;\n let session1: GraffitiSession;\n let session2: GraffitiSession;\n beforeAll(async () => {\n graffiti = useGraffiti();\n session1 = await useSession1();\n session = session1;\n session2 = await useSession2();\n });\n\n it(\"discover nothing\", async () => {\n const iterator = graffiti.discover([], {});\n expect(await iterator.next()).toHaveProperty(\"done\", true);\n });\n\n it(\"discover single\", async () => {\n const object = randomPostObject();\n\n const posted = await graffiti.post<{}>(object, session);\n\n const queryChannels = [randomString(), object.channels[0]];\n const iterator = graffiti.discover<{}>(queryChannels, {});\n const value = await nextStreamValue<{}>(iterator);\n expect(value.url).toEqual(posted.url);\n expect(value.value).toEqual(object.value);\n expect(value.channels).toEqual([object.channels[0]]);\n expect(value.allowed).toBeUndefined();\n expect(value.actor).toEqual(session.actor);\n const result2 = await iterator.next();\n expect(result2.done).toBe(true);\n });\n\n it(\"discover wrong channel\", async () => {\n const object = randomPostObject();\n await graffiti.post<{}>(object, session);\n const iterator = graffiti.discover([randomString()], {});\n await expect(iterator.next()).resolves.toHaveProperty(\"done\", true);\n });\n\n it(\"discover not allowed\", async () => {\n const object = randomPostObject();\n object.allowed = [randomUrl(), randomUrl()];\n const posted = await graffiti.post<{}>(object, session1);\n\n const iteratorSession1 = graffiti.discover<{}>(\n object.channels,\n {},\n session1,\n );\n const value = await nextStreamValue<{}>(iteratorSession1);\n expect(value.url).toEqual(posted.url);\n expect(value.value).toEqual(object.value);\n expect(value.channels).toEqual(object.channels);\n expect(value.allowed).toEqual(object.allowed);\n expect(value.actor).toEqual(session1.actor);\n\n const iteratorSession2 = graffiti.discover(object.channels, {}, session2);\n expect(await iteratorSession2.next()).toHaveProperty(\"done\", true);\n\n const iteratorNoSession = graffiti.discover(object.channels, {});\n expect(await iteratorNoSession.next()).toHaveProperty(\"done\", true);\n });\n\n it(\"discover allowed\", async () => {\n const object = randomPostObject();\n object.allowed = [randomUrl(), session2.actor, randomUrl()];\n const posted = await graffiti.post<{}>(object, session1);\n\n const iteratorSession2 = graffiti.discover<{}>(\n object.channels,\n {},\n session2,\n );\n const value = await nextStreamValue<{}>(iteratorSession2);\n expect(value.url).toEqual(posted.url);\n expect(value.value).toEqual(object.value);\n expect(value.allowed).toEqual([session2.actor]);\n expect(value.channels).toEqual(object.channels);\n expect(value.actor).toEqual(session1.actor);\n });\n\n it(\"discover bad schema\", async () => {\n const iterator = graffiti.discover([], {\n properties: {\n value: {\n //@ts-ignore\n type: \"asdf\",\n },\n },\n });\n\n await expect(iterator.next()).rejects.toThrow(GraffitiErrorInvalidSchema);\n });\n\n it(\"discover for actor\", async () => {\n const object1 = randomPostObject();\n const posted1 = await graffiti.post<{}>(object1, session1);\n\n const object2 = randomPostObject();\n object2.channels = object1.channels;\n const posted2 = await graffiti.post<{}>(object2, session2);\n\n const iterator = graffiti.discover<{}>(object1.channels, {\n properties: {\n actor: { const: posted1.actor },\n },\n });\n\n const value = await nextStreamValue<{}>(iterator);\n expect(value.url).toEqual(posted1.url);\n expect(value.url).not.toEqual(posted2.url);\n expect(value.value).toEqual(object1.value);\n await expect(iterator.next()).resolves.toHaveProperty(\"done\", true);\n });\n\n it(\"discover schema allowed, as and not as owner\", async () => {\n const object = randomPostObject();\n object.allowed = [randomUrl(), session2.actor, randomUrl()];\n await graffiti.post<{}>(object, session1);\n\n const iteratorSession1 = graffiti.discover<{}>(\n object.channels,\n {\n properties: {\n allowed: {\n minItems: 3,\n // Make sure session2.actor is in the allow list\n not: {\n items: {\n not: { const: session2.actor },\n },\n },\n },\n },\n },\n session1,\n );\n const value = await nextStreamValue<{}>(iteratorSession1);\n expect(value.value).toEqual(object.value);\n await expect(iteratorSession1.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n\n const iteratorSession2BigAllow = graffiti.discover(\n object.channels,\n {\n properties: {\n allowed: {\n minItems: 3,\n },\n },\n },\n session2,\n );\n await expect(iteratorSession2BigAllow.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n const iteratorSession2PeekOther = graffiti.discover(\n object.channels,\n {\n properties: {\n allowed: {\n not: {\n items: {\n not: { const: object.allowed[0] },\n },\n },\n },\n },\n },\n session2,\n );\n await expect(iteratorSession2PeekOther.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n const iteratorSession2SmallAllowPeekSelf = graffiti.discover<{}>(\n object.channels,\n {\n properties: {\n allowed: {\n maxItems: 1,\n not: {\n items: {\n not: { const: session2.actor },\n },\n },\n },\n },\n },\n session2,\n );\n const value2 = await nextStreamValue<{}>(\n iteratorSession2SmallAllowPeekSelf,\n );\n expect(value2.value).toEqual(object.value);\n await expect(\n iteratorSession2SmallAllowPeekSelf.next(),\n ).resolves.toHaveProperty(\"done\", true);\n });\n\n it(\"discover schema channels, as and not as owner\", async () => {\n const object = randomPostObject();\n object.channels = [randomString(), randomString(), randomString()];\n await graffiti.post<{}>(object, session1);\n\n const iteratorSession1 = graffiti.discover<{}>(\n [object.channels[0], object.channels[2]],\n {\n properties: {\n channels: {\n minItems: 3,\n // Make sure channel 1 is in the allow list\n not: {\n items: {\n not: { const: object.channels[1] },\n },\n },\n },\n },\n },\n session1,\n );\n const value = await nextStreamValue<{}>(iteratorSession1);\n expect(value.value).toEqual(object.value);\n await expect(iteratorSession1.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n\n const iteratorSession2BigAllow = graffiti.discover(\n [object.channels[0], object.channels[2]],\n {\n properties: {\n channels: {\n minItems: 3,\n },\n },\n },\n session2,\n );\n await expect(iteratorSession2BigAllow.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n const iteratorSession2PeekOther = graffiti.discover(\n [object.channels[0], object.channels[2]],\n {\n properties: {\n channels: {\n not: {\n items: {\n not: { const: object.channels[1] },\n },\n },\n },\n },\n },\n session2,\n );\n await expect(iteratorSession2PeekOther.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n const iteratorSession2SmallAllowPeekSelf = graffiti.discover<{}>(\n [object.channels[0], object.channels[2]],\n {\n properties: {\n allowed: {\n maxItems: 2,\n not: {\n items: {\n not: { const: object.channels[2] },\n },\n },\n },\n },\n },\n session2,\n );\n const value2 = await nextStreamValue<{}>(\n iteratorSession2SmallAllowPeekSelf,\n );\n expect(value2.value).toEqual(object.value);\n await expect(\n iteratorSession2SmallAllowPeekSelf.next(),\n ).resolves.toHaveProperty(\"done\", true);\n });\n\n it(\"discover query for empty allowed\", async () => {\n const publicO = randomPostObject();\n\n const publicSchema = {\n not: {\n required: [\"allowed\"],\n },\n } satisfies JSONSchema;\n\n await graffiti.post<{}>(publicO, session1);\n const iterator = graffiti.discover<{}>(\n publicO.channels,\n publicSchema,\n session1,\n );\n const value = await nextStreamValue<{}>(iterator);\n expect(value.value).toEqual(publicO.value);\n expect(value.allowed).toBeUndefined();\n await expect(iterator.next()).resolves.toHaveProperty(\"done\", true);\n\n const restricted = randomPostObject();\n restricted.allowed = [];\n await graffiti.post<{}>(restricted, session1);\n const iterator2 = graffiti.discover(\n restricted.channels,\n publicSchema,\n session1,\n );\n await expect(iterator2.next()).resolves.toHaveProperty(\"done\", true);\n });\n\n it(\"discover query for values\", async () => {\n const object1 = randomPostObject();\n object1.value = { test: randomString() };\n await graffiti.post<{}>(object1, session);\n\n const object2 = randomPostObject();\n object2.channels = object1.channels;\n object2.value = { test: randomString(), something: randomString() };\n await graffiti.post<{}>(object2, session);\n\n const object3 = randomPostObject();\n object3.channels = object1.channels;\n object3.value = { other: randomString(), something: randomString() };\n await graffiti.post<{}>(object3, session);\n\n const counts = new Map<string, number>();\n for (const property of [\"test\", \"something\", \"other\"] as const) {\n let count = 0;\n for await (const result of graffiti.discover(object1.channels, {\n properties: {\n value: {\n required: [property],\n },\n },\n })) {\n assert(!result.error, \"result has error\");\n if (property in result.object.value) {\n count++;\n }\n }\n counts.set(property, count);\n }\n\n expect(counts.get(\"test\")).toBe(2);\n expect(counts.get(\"something\")).toBe(2);\n expect(counts.get(\"other\")).toBe(1);\n });\n\n for (const continueType of [\"cursor\", \"continue\"] as const) {\n describe(`continue discover with ${continueType}`, () => {\n it(\"discover for deleted content\", async () => {\n const object = randomPostObject();\n\n const posted = await graffiti.post<{}>(object, session);\n\n const iterator1 = graffiti.discover<{}>(object.channels, {});\n const value1 = await nextStreamValue<{}>(iterator1);\n expect(value1.value).toEqual(object.value);\n const returnValue = await iterator1.next();\n assert(returnValue.done, \"value2 is not done\");\n\n await graffiti.delete(posted, session);\n\n const iterator = graffiti.discover(object.channels, {});\n await expect(iterator.next()).resolves.toHaveProperty(\"done\", true);\n\n const tombIterator = continueStream<{}>(\n graffiti,\n returnValue.value,\n continueType,\n );\n const value = await tombIterator.next();\n assert(!value.done && !value.value.error, \"value is done\");\n assert(value.value.tombstone, \"value is not tombstone\");\n expect(value.value.object.url).toEqual(posted.url);\n const returnValue2 = await tombIterator.next();\n assert(returnValue2.done, \"value2 is not done\");\n\n // Post another object\n const posted2 = await graffiti.post<{}>(object, session);\n const doubleContinueIterator = continueStream<{}>(\n graffiti,\n returnValue2.value,\n continueType,\n );\n const value2 = await doubleContinueIterator.next();\n assert(!value2.done && !value2.value.error, \"value2 is done\");\n assert(!value2.value.tombstone, \"value2 is tombstone\");\n expect(value2.value.object.url).toEqual(posted2.url);\n await expect(doubleContinueIterator.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n });\n\n it(\"continue with wrong actor\", async () => {\n const iterator = graffiti.discover<{}>([], {}, session1);\n const result = await iterator.next();\n assert(result.done, \"iterator is not done\");\n\n const continuation = continueStream<{}>(\n graffiti,\n result.value,\n continueType,\n session2,\n );\n await expect(continuation.next()).rejects.toThrow(\n GraffitiErrorForbidden,\n );\n });\n });\n }\n\n it(\"lookup non-existant cursor\", async () => {\n const iterator = graffiti.continueDiscover(randomString());\n await expect(iterator.next()).rejects.toThrow(GraffitiErrorCursorExpired);\n });\n });\n};\n", "import {\n GraffitiErrorNotAcceptable,\n GraffitiErrorNotFound,\n GraffitiErrorTooLarge,\n type Graffiti,\n type GraffitiSession,\n} from \"@graffiti-garden/api\";\nimport { it, expect, describe, beforeAll } from \"vitest\";\nimport { randomString, randomUrl } from \"./utils\";\n\nexport const graffitiMediaTests = (\n useGraffiti: () => Pick<Graffiti, \"postMedia\" | \"getMedia\" | \"deleteMedia\">,\n useSession1: () => GraffitiSession | Promise<GraffitiSession>,\n useSession2: () => GraffitiSession | Promise<GraffitiSession>,\n) => {\n describe.concurrent(\n \"media\",\n {\n timeout: 20000,\n },\n () => {\n let graffiti: ReturnType<typeof useGraffiti>;\n let session: GraffitiSession;\n let session1: GraffitiSession;\n let session2: GraffitiSession;\n beforeAll(async () => {\n graffiti = useGraffiti();\n session1 = await useSession1();\n session = session1;\n session2 = await useSession2();\n });\n\n it(\"post, get, delete media\", async () => {\n // Post media\n const text = randomString();\n const data = new Blob([text], { type: \"text/plain\" });\n const mediaUrl = await graffiti.postMedia({ data }, session);\n\n // Get the media back\n const media = await graffiti.getMedia(mediaUrl, {});\n expect(await media.data.text()).toEqual(text);\n expect(media.data.type).toEqual(\"text/plain\");\n expect(media.allowed).toBeUndefined();\n expect(media.actor).toBe(session.actor);\n\n // Delete the media\n await graffiti.deleteMedia(mediaUrl, session);\n\n // Try to get the media again\n await expect(graffiti.getMedia(mediaUrl, {})).rejects.toThrow(\n GraffitiErrorNotFound,\n );\n });\n\n it(\"acceptable type\", async () => {\n const text = randomString();\n const data = new Blob([text], { type: \"text/plain\" });\n const mediaUrl = await graffiti.postMedia({ data }, session);\n\n const media = await graffiti.getMedia(mediaUrl, {\n types: [\"application/json\", \"text/*\"],\n });\n expect(await media.data.text()).toEqual(text);\n expect(media.data.type).toEqual(\"text/plain\");\n expect(media.allowed).toBeUndefined();\n expect(media.actor).toBe(session.actor);\n });\n\n it(\"unacceptable type\", async () => {\n const text = randomString();\n const data = new Blob([text], { type: \"text/plain\" });\n const mediaUrl = await graffiti.postMedia({ data }, session);\n\n await expect(\n graffiti.getMedia(mediaUrl, {\n types: [\"image/*\"],\n }),\n ).rejects.toThrow(GraffitiErrorNotAcceptable);\n });\n\n it(\"acceptable size\", async () => {\n const text = randomString();\n const data = new Blob([text], { type: \"text/plain\" });\n const mediaUrl = await graffiti.postMedia({ data }, session);\n\n const media = await graffiti.getMedia(mediaUrl, {\n maxBytes: data.size,\n });\n expect(await media.data.text()).toEqual(text);\n expect(media.data.type).toEqual(\"text/plain\");\n expect(media.allowed).toBeUndefined();\n expect(media.actor).toBe(session.actor);\n });\n\n it(\"unacceptable size\", async () => {\n const text = randomString();\n const data = new Blob([text], { type: \"text/plain\" });\n const mediaUrl = await graffiti.postMedia({ data }, session);\n\n await expect(\n graffiti.getMedia(mediaUrl, {\n maxBytes: data.size - 1,\n }),\n ).rejects.toThrow(GraffitiErrorTooLarge);\n });\n\n it(\"empty allowed\", async () => {\n const text = randomString();\n const data = new Blob([text], { type: \"text/plain\" });\n const allowed: string[] = [];\n const mediaUrl = await graffiti.postMedia({ data, allowed }, session1);\n\n // Get it with the authorized user\n const media = await graffiti.getMedia(mediaUrl, {}, session1);\n expect(await media.data.text()).toEqual(text);\n expect(media.data.type).toEqual(\"text/plain\");\n expect(media.allowed).toEqual([]);\n expect(media.actor).toBe(session1.actor);\n\n // Get it with the unauthorized user\n await expect(graffiti.getMedia(mediaUrl, {}, session2)).rejects.toThrow(\n GraffitiErrorNotFound,\n );\n\n // Get it without authorization\n await expect(graffiti.getMedia(mediaUrl, {})).rejects.toThrow(\n GraffitiErrorNotFound,\n );\n });\n\n it(\"allowed\", async () => {\n const text = randomString();\n const data = new Blob([text], { type: \"text/plain\" });\n const allowed = [randomUrl(), session2.actor, randomUrl()];\n const mediaUrl = await graffiti.postMedia({ data, allowed }, session1);\n\n // Get it with the authorized user\n const media = await graffiti.getMedia(mediaUrl, {}, session1);\n expect(await media.data.text()).toEqual(text);\n expect(media.data.type).toEqual(\"text/plain\");\n expect(media.allowed).toEqual(allowed);\n expect(media.actor).toBe(session1.actor);\n\n // Get it with the allowed user\n const media2 = await graffiti.getMedia(mediaUrl, {}, session2);\n expect(await media2.data.text()).toEqual(text);\n expect(media2.data.type).toEqual(\"text/plain\");\n expect(media2.allowed).toEqual([session2.actor]);\n expect(media2.actor).toBe(session1.actor);\n\n // Get it without authorization\n await expect(graffiti.getMedia(mediaUrl, {})).rejects.toThrow(\n GraffitiErrorNotFound,\n );\n });\n },\n );\n};\n"],
|
|
4
|
+
"sourcesContent": ["import { it, expect, describe, beforeAll } from \"vitest\";\nimport type {\n Graffiti,\n GraffitiSession,\n JSONSchema,\n} from \"@graffiti-garden/api\";\nimport {\n GraffitiErrorNotFound,\n GraffitiErrorSchemaMismatch,\n GraffitiErrorInvalidSchema,\n GraffitiErrorForbidden,\n} from \"@graffiti-garden/api\";\nimport { randomString, randomUrl } from \"./utils.js\";\n\nexport const graffitiCRUDTests = (\n useGraffiti: () => Pick<Graffiti, \"post\" | \"get\" | \"delete\">,\n useSession1: () => GraffitiSession | Promise<GraffitiSession>,\n useSession2: () => GraffitiSession | Promise<GraffitiSession>,\n) => {\n describe.concurrent(\n \"CRUD\",\n {\n timeout: 20000,\n },\n () => {\n let graffiti: ReturnType<typeof useGraffiti>;\n let session: GraffitiSession;\n let session1: GraffitiSession;\n let session2: GraffitiSession;\n beforeAll(async () => {\n graffiti = useGraffiti();\n session1 = await useSession1();\n session = session1;\n session2 = await useSession2();\n });\n\n it(\"get nonexistant object\", async () => {\n await expect(graffiti.get(randomUrl(), {})).rejects.toThrow(\n GraffitiErrorNotFound,\n );\n });\n\n it(\"post, get, delete\", async () => {\n const value = {\n something: \"hello, world~ c:\",\n };\n const channels = [randomString(), randomString()];\n\n // Post the object\n const previous = await graffiti.post<{}>({ value, channels }, session);\n expect(previous.value).toEqual(value);\n expect(previous.channels).toEqual(channels);\n expect(previous.allowed).toEqual(undefined);\n expect(previous.actor).toEqual(session.actor);\n\n // Get it back\n const gotten = await graffiti.get(previous, {});\n expect(gotten.value).toEqual(value);\n expect(gotten.channels).toEqual([]);\n expect(gotten.allowed).toBeUndefined();\n expect(gotten.url).toEqual(previous.url);\n expect(gotten.actor).toEqual(previous.actor);\n\n // Delete it\n const deleted = await graffiti.delete(gotten, session);\n expect(deleted.value).toEqual(value);\n expect(deleted.channels).toEqual(channels);\n expect(deleted.allowed).toBeUndefined();\n expect(deleted.actor).toEqual(session.actor);\n expect(deleted.url).toEqual(previous.url);\n\n // Get is not found\n await expect(graffiti.get(gotten, {})).rejects.toBeInstanceOf(\n GraffitiErrorNotFound,\n );\n\n // Delete it again\n await expect(graffiti.delete(gotten, session)).rejects.toThrow(\n GraffitiErrorNotFound,\n );\n });\n\n it(\"post then delete with wrong actor\", async () => {\n const posted = await graffiti.post<{}>(\n { value: {}, channels: [] },\n session2,\n );\n\n await expect(graffiti.delete(posted, session1)).rejects.toThrow(\n GraffitiErrorForbidden,\n );\n });\n\n it(\"post and get with schema\", async () => {\n const schema = {\n properties: {\n value: {\n properties: {\n something: {\n type: \"string\",\n },\n another: {\n type: \"array\",\n items: {\n type: \"number\",\n },\n },\n deeper: {\n type: \"object\",\n properties: {\n deepProp: {\n type: \"string\",\n },\n },\n required: [\"deepProp\"],\n },\n },\n required: [\"another\", \"deeper\"],\n },\n },\n } as const satisfies JSONSchema;\n\n const goodValue = {\n something: \"hello\",\n another: [1, 2, 3],\n deeper: {\n deepProp: \"hello\",\n },\n };\n\n const posted = await graffiti.post<typeof schema>(\n {\n value: goodValue,\n channels: [],\n },\n session,\n );\n const gotten = await graffiti.get(posted, schema);\n\n expect(gotten.value.something).toEqual(goodValue.something);\n expect(gotten.value.another).toEqual(goodValue.another);\n expect(gotten.value.another[0]).toEqual(1);\n expect(gotten.value.deeper.deepProp).toEqual(goodValue.deeper.deepProp);\n });\n\n it(\"post and get with invalid schema\", async () => {\n const posted = await graffiti.post<{}>(\n { value: {}, channels: [] },\n session,\n );\n await expect(\n graffiti.get(posted, {\n properties: {\n value: {\n //@ts-ignore\n type: \"asdf\",\n },\n },\n }),\n ).rejects.toThrow(GraffitiErrorInvalidSchema);\n });\n\n it(\"post and get with wrong schema\", async () => {\n const posted = await graffiti.post<{}>(\n {\n value: {\n hello: \"world\",\n },\n channels: [],\n },\n session,\n );\n\n await expect(\n graffiti.get(posted, {\n properties: {\n value: {\n properties: {\n hello: {\n type: \"number\",\n },\n },\n },\n },\n }),\n ).rejects.toThrow(GraffitiErrorSchemaMismatch);\n });\n\n it(\"post and get with empty access control\", async () => {\n const value = {\n um: \"hi\",\n };\n const allowed = [randomUrl()];\n const channels = [randomString()];\n const posted = await graffiti.post<{}>(\n { value, allowed, channels },\n session1,\n );\n\n // Get it with authenticated session\n const gotten = await graffiti.get(posted, {}, session1);\n expect(gotten.url).toEqual(posted.url);\n expect(gotten.actor).toEqual(session1.actor);\n expect(gotten.value).toEqual(value);\n expect(gotten.allowed).toEqual(allowed);\n expect(gotten.channels).toEqual(channels);\n\n // But not without session\n await expect(graffiti.get(posted, {})).rejects.toBeInstanceOf(\n GraffitiErrorNotFound,\n );\n\n // Or the wrong session\n await expect(graffiti.get(posted, {}, session2)).rejects.toBeInstanceOf(\n GraffitiErrorNotFound,\n );\n });\n\n it(\"post and get with specific access control\", async () => {\n const value = {\n um: \"hi\",\n };\n const allowed = [randomUrl(), session2.actor, randomUrl()];\n const channels = [randomString()];\n const posted = await graffiti.post<{}>(\n {\n value,\n allowed,\n channels,\n },\n session1,\n );\n\n // Get it with authenticated session\n const gotten = await graffiti.get(posted, {}, session1);\n expect(gotten.url).toEqual(posted.url);\n expect(gotten.actor).toEqual(session1.actor);\n expect(gotten.value).toEqual(value);\n expect(gotten.allowed).toEqual(allowed);\n expect(gotten.channels).toEqual(channels);\n\n // But not without session\n await expect(graffiti.get(posted, {})).rejects.toBeInstanceOf(\n GraffitiErrorNotFound,\n );\n\n const gotten2 = await graffiti.get(posted, {}, session2);\n expect(gotten.url).toEqual(posted.url);\n expect(gotten.actor).toEqual(session1.actor);\n expect(gotten2.value).toEqual(value);\n // They should only see that is is private to them\n expect(gotten2.allowed).toEqual([session2.actor]);\n // And not see any channels\n expect(gotten2.channels).toEqual([]);\n });\n },\n );\n};\n", "import { assert } from \"vitest\";\nimport type {\n GraffitiPostObject,\n GraffitiObjectStream,\n JSONSchema,\n GraffitiObject,\n GraffitiObjectStreamReturn,\n GraffitiObjectStreamContinue,\n Graffiti,\n GraffitiSession,\n} from \"@graffiti-garden/api\";\n\nexport function randomString(): string {\n const array = new Uint8Array(16);\n crypto.getRandomValues(array);\n const str = Array.from(array)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n\n // check for unicode support\n return str + \"\uD83D\uDC69\uD83C\uDFFD\u200D\u2764\uFE0F\u200D\uD83D\uDC8B\u200D\uD83D\uDC69\uD83C\uDFFB\uD83E\uDEF1\uD83C\uDFFC\u200D\uD83E\uDEF2\uD83C\uDFFF\";\n}\nexport function randomUrl(): string {\n return \"test:\" + randomString();\n}\n\nexport function randomValue() {\n return {\n [randomString()]: randomString(),\n };\n}\n\nexport function randomPostObject(): GraffitiPostObject<{}> {\n return {\n value: randomValue(),\n channels: [randomString(), randomString()],\n };\n}\n\nexport async function nextStreamValue<Schema extends JSONSchema>(\n iterator: GraffitiObjectStream<Schema>,\n): Promise<GraffitiObject<Schema>> {\n const result = await iterator.next();\n assert(!result.done && !result.value.error, \"result has no value\");\n assert(!result.value.tombstone, \"result has been deleted!\");\n return result.value.object;\n}\n\nexport function continueStream<Schema extends JSONSchema>(\n graffiti: Pick<Graffiti, \"continueDiscover\">,\n streamReturn: GraffitiObjectStreamReturn<Schema>,\n type: \"cursor\" | \"continue\",\n session?: GraffitiSession | null,\n): GraffitiObjectStreamContinue<Schema> {\n if (type === \"cursor\") {\n return graffiti.continueDiscover(\n streamReturn.cursor,\n session,\n ) as unknown as GraffitiObjectStreamContinue<Schema>;\n } else {\n return streamReturn.continue();\n }\n}\n", "import { it, expect, describe, assert, beforeAll } from \"vitest\";\nimport type {\n Graffiti,\n GraffitiSession,\n JSONSchema,\n} from \"@graffiti-garden/api\";\nimport {\n GraffitiErrorCursorExpired,\n GraffitiErrorForbidden,\n GraffitiErrorInvalidSchema,\n} from \"@graffiti-garden/api\";\nimport {\n randomString,\n nextStreamValue,\n randomPostObject,\n continueStream,\n randomUrl,\n} from \"./utils.js\";\n\nexport const graffitiDiscoverTests = (\n useGraffiti: () => Pick<\n Graffiti,\n \"discover\" | \"post\" | \"delete\" | \"continueDiscover\"\n >,\n useSession1: () => GraffitiSession | Promise<GraffitiSession>,\n useSession2: () => GraffitiSession | Promise<GraffitiSession>,\n) => {\n describe.concurrent(\"discover\", { timeout: 20000 }, () => {\n let graffiti: ReturnType<typeof useGraffiti>;\n let session: GraffitiSession;\n let session1: GraffitiSession;\n let session2: GraffitiSession;\n beforeAll(async () => {\n graffiti = useGraffiti();\n session1 = await useSession1();\n session = session1;\n session2 = await useSession2();\n });\n\n it(\"discover nothing\", async () => {\n const iterator = graffiti.discover([], {});\n expect(await iterator.next()).toHaveProperty(\"done\", true);\n });\n\n it(\"discover single\", async () => {\n const object = randomPostObject();\n\n const posted = await graffiti.post<{}>(object, session);\n\n const queryChannels = [randomString(), object.channels[0]];\n const iterator = graffiti.discover<{}>(queryChannels, {});\n const value = await nextStreamValue<{}>(iterator);\n expect(value.url).toEqual(posted.url);\n expect(value.value).toEqual(object.value);\n expect(value.channels).toEqual([object.channels[0]]);\n expect(value.allowed).toBeUndefined();\n expect(value.actor).toEqual(session.actor);\n const result2 = await iterator.next();\n expect(result2.done).toBe(true);\n });\n\n it(\"discover wrong channel\", async () => {\n const object = randomPostObject();\n await graffiti.post<{}>(object, session);\n const iterator = graffiti.discover([randomString()], {});\n await expect(iterator.next()).resolves.toHaveProperty(\"done\", true);\n });\n\n it(\"discover not allowed\", async () => {\n const object = randomPostObject();\n object.allowed = [randomUrl(), randomUrl()];\n const posted = await graffiti.post<{}>(object, session1);\n\n const iteratorSession1 = graffiti.discover<{}>(\n object.channels,\n {},\n session1,\n );\n const value = await nextStreamValue<{}>(iteratorSession1);\n expect(value.url).toEqual(posted.url);\n expect(value.value).toEqual(object.value);\n expect(value.channels).toEqual(object.channels);\n expect(value.allowed).toEqual(object.allowed);\n expect(value.actor).toEqual(session1.actor);\n\n const iteratorSession2 = graffiti.discover(object.channels, {}, session2);\n expect(await iteratorSession2.next()).toHaveProperty(\"done\", true);\n\n const iteratorNoSession = graffiti.discover(object.channels, {});\n expect(await iteratorNoSession.next()).toHaveProperty(\"done\", true);\n });\n\n it(\"discover allowed\", async () => {\n const object = randomPostObject();\n object.allowed = [randomUrl(), session2.actor, randomUrl()];\n const posted = await graffiti.post<{}>(object, session1);\n\n const iteratorSession2 = graffiti.discover<{}>(\n object.channels,\n {},\n session2,\n );\n const value = await nextStreamValue<{}>(iteratorSession2);\n expect(value.url).toEqual(posted.url);\n expect(value.value).toEqual(object.value);\n expect(value.allowed).toEqual([session2.actor]);\n expect(value.channels).toEqual(object.channels);\n expect(value.actor).toEqual(session1.actor);\n });\n\n it(\"discover bad schema\", async () => {\n const iterator = graffiti.discover([], {\n properties: {\n value: {\n //@ts-ignore\n type: \"asdf\",\n },\n },\n });\n\n await expect(iterator.next()).rejects.toThrow(GraffitiErrorInvalidSchema);\n });\n\n it(\"discover for actor\", async () => {\n const object1 = randomPostObject();\n const posted1 = await graffiti.post<{}>(object1, session1);\n\n const object2 = randomPostObject();\n object2.channels = object1.channels;\n const posted2 = await graffiti.post<{}>(object2, session2);\n\n const iterator = graffiti.discover<{}>(object1.channels, {\n properties: {\n actor: { const: posted1.actor },\n },\n });\n\n const value = await nextStreamValue<{}>(iterator);\n expect(value.url).toEqual(posted1.url);\n expect(value.url).not.toEqual(posted2.url);\n expect(value.value).toEqual(object1.value);\n await expect(iterator.next()).resolves.toHaveProperty(\"done\", true);\n });\n\n it(\"discover schema allowed, as and not as owner\", async () => {\n const object = randomPostObject();\n object.allowed = [randomUrl(), session2.actor, randomUrl()];\n await graffiti.post<{}>(object, session1);\n\n const iteratorSession1 = graffiti.discover<{}>(\n object.channels,\n {\n properties: {\n allowed: {\n minItems: 3,\n // Make sure session2.actor is in the allow list\n not: {\n items: {\n not: { const: session2.actor },\n },\n },\n },\n },\n },\n session1,\n );\n const value = await nextStreamValue<{}>(iteratorSession1);\n expect(value.value).toEqual(object.value);\n await expect(iteratorSession1.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n\n const iteratorSession2BigAllow = graffiti.discover(\n object.channels,\n {\n properties: {\n allowed: {\n minItems: 3,\n },\n },\n },\n session2,\n );\n await expect(iteratorSession2BigAllow.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n const iteratorSession2PeekOther = graffiti.discover(\n object.channels,\n {\n properties: {\n allowed: {\n not: {\n items: {\n not: { const: object.allowed[0] },\n },\n },\n },\n },\n },\n session2,\n );\n await expect(iteratorSession2PeekOther.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n const iteratorSession2SmallAllowPeekSelf = graffiti.discover<{}>(\n object.channels,\n {\n properties: {\n allowed: {\n maxItems: 1,\n not: {\n items: {\n not: { const: session2.actor },\n },\n },\n },\n },\n },\n session2,\n );\n const value2 = await nextStreamValue<{}>(\n iteratorSession2SmallAllowPeekSelf,\n );\n expect(value2.value).toEqual(object.value);\n await expect(\n iteratorSession2SmallAllowPeekSelf.next(),\n ).resolves.toHaveProperty(\"done\", true);\n });\n\n it(\"discover schema channels, as and not as owner\", async () => {\n const object = randomPostObject();\n object.channels = [randomString(), randomString(), randomString()];\n await graffiti.post<{}>(object, session1);\n\n const iteratorSession1 = graffiti.discover<{}>(\n [object.channels[0], object.channels[2]],\n {\n properties: {\n channels: {\n minItems: 3,\n // Make sure channel 1 is in the allow list\n not: {\n items: {\n not: { const: object.channels[1] },\n },\n },\n },\n },\n },\n session1,\n );\n const value = await nextStreamValue<{}>(iteratorSession1);\n expect(value.value).toEqual(object.value);\n await expect(iteratorSession1.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n\n const iteratorSession2BigAllow = graffiti.discover(\n [object.channels[0], object.channels[2]],\n {\n properties: {\n channels: {\n minItems: 3,\n },\n },\n },\n session2,\n );\n await expect(iteratorSession2BigAllow.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n const iteratorSession2PeekOther = graffiti.discover(\n [object.channels[0], object.channels[2]],\n {\n properties: {\n channels: {\n not: {\n items: {\n not: { const: object.channels[1] },\n },\n },\n },\n },\n },\n session2,\n );\n await expect(iteratorSession2PeekOther.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n const iteratorSession2SmallAllowPeekSelf = graffiti.discover<{}>(\n [object.channels[0], object.channels[2]],\n {\n properties: {\n allowed: {\n maxItems: 2,\n not: {\n items: {\n not: { const: object.channels[2] },\n },\n },\n },\n },\n },\n session2,\n );\n const value2 = await nextStreamValue<{}>(\n iteratorSession2SmallAllowPeekSelf,\n );\n expect(value2.value).toEqual(object.value);\n await expect(\n iteratorSession2SmallAllowPeekSelf.next(),\n ).resolves.toHaveProperty(\"done\", true);\n });\n\n it(\"discover query for empty allowed\", async () => {\n const publicO = randomPostObject();\n\n const publicSchema = {\n not: {\n required: [\"allowed\"],\n },\n } satisfies JSONSchema;\n\n await graffiti.post<{}>(publicO, session1);\n const iterator = graffiti.discover<{}>(\n publicO.channels,\n publicSchema,\n session1,\n );\n const value = await nextStreamValue<{}>(iterator);\n expect(value.value).toEqual(publicO.value);\n expect(value.allowed).toBeUndefined();\n await expect(iterator.next()).resolves.toHaveProperty(\"done\", true);\n\n const restricted = randomPostObject();\n restricted.allowed = [];\n await graffiti.post<{}>(restricted, session1);\n const iterator2 = graffiti.discover(\n restricted.channels,\n publicSchema,\n session1,\n );\n await expect(iterator2.next()).resolves.toHaveProperty(\"done\", true);\n });\n\n it(\"discover query for values\", async () => {\n const object1 = randomPostObject();\n object1.value = { test: randomString() };\n await graffiti.post<{}>(object1, session);\n\n const object2 = randomPostObject();\n object2.channels = object1.channels;\n object2.value = { test: randomString(), something: randomString() };\n await graffiti.post<{}>(object2, session);\n\n const object3 = randomPostObject();\n object3.channels = object1.channels;\n object3.value = { other: randomString(), something: randomString() };\n await graffiti.post<{}>(object3, session);\n\n const counts = new Map<string, number>();\n for (const property of [\"test\", \"something\", \"other\"] as const) {\n let count = 0;\n for await (const result of graffiti.discover(object1.channels, {\n properties: {\n value: {\n required: [property],\n },\n },\n })) {\n assert(!result.error, \"result has error\");\n if (property in result.object.value) {\n count++;\n }\n }\n counts.set(property, count);\n }\n\n expect(counts.get(\"test\")).toBe(2);\n expect(counts.get(\"something\")).toBe(2);\n expect(counts.get(\"other\")).toBe(1);\n });\n\n for (const continueType of [\"cursor\", \"continue\"] as const) {\n describe(`continue discover with ${continueType}`, () => {\n it(\"discover for deleted content\", async () => {\n const object = randomPostObject();\n\n const posted = await graffiti.post<{}>(object, session);\n\n const iterator1 = graffiti.discover<{}>(object.channels, {});\n const value1 = await nextStreamValue<{}>(iterator1);\n expect(value1.value).toEqual(object.value);\n const returnValue = await iterator1.next();\n assert(returnValue.done, \"value2 is not done\");\n\n await graffiti.delete(posted, session);\n\n const iterator = graffiti.discover(object.channels, {});\n await expect(iterator.next()).resolves.toHaveProperty(\"done\", true);\n\n const tombIterator = continueStream<{}>(\n graffiti,\n returnValue.value,\n continueType,\n );\n const value = await tombIterator.next();\n assert(!value.done && !value.value.error, \"value is done\");\n assert(value.value.tombstone, \"value is not tombstone\");\n expect(value.value.object.url).toEqual(posted.url);\n const returnValue2 = await tombIterator.next();\n assert(returnValue2.done, \"value2 is not done\");\n\n // Post another object\n const posted2 = await graffiti.post<{}>(object, session);\n const doubleContinueIterator = continueStream<{}>(\n graffiti,\n returnValue2.value,\n continueType,\n );\n const value2 = await doubleContinueIterator.next();\n assert(!value2.done && !value2.value.error, \"value2 is done\");\n assert(!value2.value.tombstone, \"value2 is tombstone\");\n expect(value2.value.object.url).toEqual(posted2.url);\n await expect(doubleContinueIterator.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n });\n\n it(\"continue with wrong actor\", async () => {\n const iterator = graffiti.discover<{}>([], {}, session1);\n const result = await iterator.next();\n assert(result.done, \"iterator is not done\");\n\n const continuation = continueStream<{}>(\n graffiti,\n result.value,\n continueType,\n session2,\n );\n await expect(continuation.next()).rejects.toThrow(\n GraffitiErrorForbidden,\n );\n });\n });\n }\n\n it(\"lookup non-existant cursor\", async () => {\n const iterator = graffiti.continueDiscover(randomString());\n await expect(iterator.next()).rejects.toThrow(GraffitiErrorCursorExpired);\n });\n });\n};\n", "import {\n GraffitiErrorNotAcceptable,\n GraffitiErrorNotFound,\n GraffitiErrorTooLarge,\n type Graffiti,\n type GraffitiSession,\n} from \"@graffiti-garden/api\";\nimport { it, expect, describe, beforeAll } from \"vitest\";\nimport { randomString, randomUrl } from \"./utils.js\";\n\nexport const graffitiMediaTests = (\n useGraffiti: () => Pick<Graffiti, \"postMedia\" | \"getMedia\" | \"deleteMedia\">,\n useSession1: () => GraffitiSession | Promise<GraffitiSession>,\n useSession2: () => GraffitiSession | Promise<GraffitiSession>,\n) => {\n describe.concurrent(\n \"media\",\n {\n timeout: 20000,\n },\n () => {\n let graffiti: ReturnType<typeof useGraffiti>;\n let session: GraffitiSession;\n let session1: GraffitiSession;\n let session2: GraffitiSession;\n beforeAll(async () => {\n graffiti = useGraffiti();\n session1 = await useSession1();\n session = session1;\n session2 = await useSession2();\n });\n\n it(\"post, get, delete media\", async () => {\n // Post media\n const text = randomString();\n const data = new Blob([text], { type: \"text/plain\" });\n const mediaUrl = await graffiti.postMedia({ data }, session);\n\n // Get the media back\n const media = await graffiti.getMedia(mediaUrl, {});\n expect(await media.data.text()).toEqual(text);\n expect(media.data.type).toEqual(\"text/plain\");\n expect(media.allowed).toBeUndefined();\n expect(media.actor).toBe(session.actor);\n\n // Delete the media\n await graffiti.deleteMedia(mediaUrl, session);\n\n // Try to get the media again\n await expect(graffiti.getMedia(mediaUrl, {})).rejects.toThrow(\n GraffitiErrorNotFound,\n );\n });\n\n it(\"acceptable type\", async () => {\n const text = randomString();\n const data = new Blob([text], { type: \"text/plain\" });\n const mediaUrl = await graffiti.postMedia({ data }, session);\n\n const media = await graffiti.getMedia(mediaUrl, {\n types: [\"application/json\", \"text/*\"],\n });\n expect(await media.data.text()).toEqual(text);\n expect(media.data.type).toEqual(\"text/plain\");\n expect(media.allowed).toBeUndefined();\n expect(media.actor).toBe(session.actor);\n });\n\n it(\"unacceptable type\", async () => {\n const text = randomString();\n const data = new Blob([text], { type: \"text/plain\" });\n const mediaUrl = await graffiti.postMedia({ data }, session);\n\n await expect(\n graffiti.getMedia(mediaUrl, {\n types: [\"image/*\"],\n }),\n ).rejects.toThrow(GraffitiErrorNotAcceptable);\n });\n\n it(\"acceptable size\", async () => {\n const text = randomString();\n const data = new Blob([text], { type: \"text/plain\" });\n const mediaUrl = await graffiti.postMedia({ data }, session);\n\n const media = await graffiti.getMedia(mediaUrl, {\n maxBytes: data.size,\n });\n expect(await media.data.text()).toEqual(text);\n expect(media.data.type).toEqual(\"text/plain\");\n expect(media.allowed).toBeUndefined();\n expect(media.actor).toBe(session.actor);\n });\n\n it(\"unacceptable size\", async () => {\n const text = randomString();\n const data = new Blob([text], { type: \"text/plain\" });\n const mediaUrl = await graffiti.postMedia({ data }, session);\n\n await expect(\n graffiti.getMedia(mediaUrl, {\n maxBytes: data.size - 1,\n }),\n ).rejects.toThrow(GraffitiErrorTooLarge);\n });\n\n it(\"empty allowed\", async () => {\n const text = randomString();\n const data = new Blob([text], { type: \"text/plain\" });\n const allowed: string[] = [];\n const mediaUrl = await graffiti.postMedia({ data, allowed }, session1);\n\n // Get it with the authorized user\n const media = await graffiti.getMedia(mediaUrl, {}, session1);\n expect(await media.data.text()).toEqual(text);\n expect(media.data.type).toEqual(\"text/plain\");\n expect(media.allowed).toEqual([]);\n expect(media.actor).toBe(session1.actor);\n\n // Get it with the unauthorized user\n await expect(graffiti.getMedia(mediaUrl, {}, session2)).rejects.toThrow(\n GraffitiErrorNotFound,\n );\n\n // Get it without authorization\n await expect(graffiti.getMedia(mediaUrl, {})).rejects.toThrow(\n GraffitiErrorNotFound,\n );\n });\n\n it(\"allowed\", async () => {\n const text = randomString();\n const data = new Blob([text], { type: \"text/plain\" });\n const allowed = [randomUrl(), session2.actor, randomUrl()];\n const mediaUrl = await graffiti.postMedia({ data, allowed }, session1);\n\n // Get it with the authorized user\n const media = await graffiti.getMedia(mediaUrl, {}, session1);\n expect(await media.data.text()).toEqual(text);\n expect(media.data.type).toEqual(\"text/plain\");\n expect(media.allowed).toEqual(allowed);\n expect(media.actor).toBe(session1.actor);\n\n // Get it with the allowed user\n const media2 = await graffiti.getMedia(mediaUrl, {}, session2);\n expect(await media2.data.text()).toEqual(text);\n expect(media2.data.type).toEqual(\"text/plain\");\n expect(media2.allowed).toEqual([session2.actor]);\n expect(media2.actor).toBe(session1.actor);\n\n // Get it without authorization\n await expect(graffiti.getMedia(mediaUrl, {})).rejects.toThrow(\n GraffitiErrorNotFound,\n );\n });\n },\n );\n};\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["it", "expect", "describe", "assert", "beforeAll", "GraffitiErrorForbidden", "GraffitiErrorInvalidSchema", "describe", "beforeAll", "it", "expect", "GraffitiErrorInvalidSchema", "assert", "GraffitiErrorForbidden", "GraffitiErrorNotFound", "it", "expect", "describe", "beforeAll", "describe", "beforeAll", "it", "expect", "GraffitiErrorNotFound"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graffiti-garden/api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "The heart of Graffiti",
|
|
5
5
|
"types": "./dist/src/index.d.ts",
|
|
6
|
-
"module": "./dist/esm/index.
|
|
7
|
-
"main": "./dist/cjs/index.
|
|
6
|
+
"module": "./dist/esm/index.js",
|
|
7
|
+
"main": "./dist/cjs/index.js",
|
|
8
8
|
"browser": "./dist/browser/index.js",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
11
|
"import": {
|
|
12
12
|
"types": "./dist/src/index.d.ts",
|
|
13
|
-
"default": "./dist/esm/index.
|
|
13
|
+
"default": "./dist/esm/index.js"
|
|
14
14
|
},
|
|
15
15
|
"require": {
|
|
16
16
|
"types": "./dist/src/index.d.ts",
|
|
17
|
-
"default": "./dist/cjs/index.
|
|
17
|
+
"default": "./dist/cjs/index.js"
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"./tests": {
|
package/src/1-api.ts
CHANGED
package/src/4-utilities.ts
CHANGED
|
@@ -3,8 +3,8 @@ import type {
|
|
|
3
3
|
GraffitiObjectBase,
|
|
4
4
|
GraffitiObjectUrl,
|
|
5
5
|
GraffitiSession,
|
|
6
|
-
} from "./2-types";
|
|
7
|
-
import { GraffitiErrorInvalidSchema } from "./3-errors";
|
|
6
|
+
} from "./2-types.js";
|
|
7
|
+
import { GraffitiErrorInvalidSchema } from "./3-errors.js";
|
|
8
8
|
import type { JSONSchema } from "json-schema-to-ts";
|
|
9
9
|
import type Ajv from "ajv";
|
|
10
10
|
|
|
@@ -77,7 +77,7 @@ export async function compileGraffitiObjectSchema<Schema extends JSONSchema>(
|
|
|
77
77
|
schema: Schema,
|
|
78
78
|
) {
|
|
79
79
|
if (!ajv) {
|
|
80
|
-
const { Ajv } = await import("ajv");
|
|
80
|
+
const { default: Ajv } = await import("ajv");
|
|
81
81
|
ajv = new Ajv({ strict: false });
|
|
82
82
|
}
|
|
83
83
|
|
package/src/5-runtime-types.ts
CHANGED
|
@@ -12,14 +12,14 @@ import {
|
|
|
12
12
|
nonnegative,
|
|
13
13
|
extend,
|
|
14
14
|
} from "zod/mini";
|
|
15
|
-
import type { Graffiti } from "./1-api";
|
|
15
|
+
import type { Graffiti } from "./1-api.js";
|
|
16
16
|
import type {
|
|
17
17
|
GraffitiObject,
|
|
18
18
|
GraffitiObjectStream,
|
|
19
19
|
GraffitiObjectStreamContinue,
|
|
20
20
|
GraffitiPostObject,
|
|
21
21
|
GraffitiSession,
|
|
22
|
-
} from "./2-types";
|
|
22
|
+
} from "./2-types.js";
|
|
23
23
|
import type { JSONSchema } from "json-schema-to-ts";
|
|
24
24
|
|
|
25
25
|
export const GraffitiPostObjectSchema = looseObject({
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export * from "./1-api";
|
|
2
|
-
export * from "./2-types";
|
|
3
|
-
export * from "./3-errors";
|
|
4
|
-
export * from "./4-utilities";
|
|
5
|
-
export * from "./5-runtime-types";
|
|
1
|
+
export * from "./1-api.js";
|
|
2
|
+
export * from "./2-types.js";
|
|
3
|
+
export * from "./3-errors.js";
|
|
4
|
+
export * from "./4-utilities.js";
|
|
5
|
+
export * from "./5-runtime-types.js";
|
|
6
6
|
export type { JSONSchema } from "json-schema-to-ts";
|
package/tests/crud.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
GraffitiErrorInvalidSchema,
|
|
11
11
|
GraffitiErrorForbidden,
|
|
12
12
|
} from "@graffiti-garden/api";
|
|
13
|
-
import { randomString, randomUrl } from "./utils";
|
|
13
|
+
import { randomString, randomUrl } from "./utils.js";
|
|
14
14
|
|
|
15
15
|
export const graffitiCRUDTests = (
|
|
16
16
|
useGraffiti: () => Pick<Graffiti, "post" | "get" | "delete">,
|
package/tests/discover.ts
CHANGED
package/tests/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from "./crud";
|
|
2
|
-
export * from "./discover";
|
|
3
|
-
export * from "./utils";
|
|
4
|
-
export * from "./media";
|
|
1
|
+
export * from "./crud.js";
|
|
2
|
+
export * from "./discover.js";
|
|
3
|
+
export * from "./utils.js";
|
|
4
|
+
export * from "./media.js";
|
package/tests/media.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
type GraffitiSession,
|
|
7
7
|
} from "@graffiti-garden/api";
|
|
8
8
|
import { it, expect, describe, beforeAll } from "vitest";
|
|
9
|
-
import { randomString, randomUrl } from "./utils";
|
|
9
|
+
import { randomString, randomUrl } from "./utils.js";
|
|
10
10
|
|
|
11
11
|
export const graffitiMediaTests = (
|
|
12
12
|
useGraffiti: () => Pick<Graffiti, "postMedia" | "getMedia" | "deleteMedia">,
|
package/dist/cjs/1-api.cjs.map
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/1-api.ts"],
|
|
4
|
-
"sourcesContent": ["import type {\n GraffitiObjectUrl,\n GraffitiObject,\n GraffitiObjectBase,\n GraffitiSession,\n GraffitiPostObject,\n GraffitiObjectStream,\n GraffitiObjectStreamContinue,\n GraffitiMedia,\n GraffitiPostMedia,\n GraffitiMediaAccept,\n} from \"./2-types\";\nimport type { JSONSchema } from \"json-schema-to-ts\";\n\n/**\n * This API describes a small but powerful set of methods that\n * can be used to create many different kinds of social applications,\n * from applications like Twitter, to Messenger, to Wikipedia, to many more new designs.\n * See the [Graffiti project website](https://graffiti.garden)\n * for links to example applications. Additionally, apps built on top\n * of the API interoperate with each other so you can seamlessly switch\n * between apps without losing your friends or data.\n *\n * These API methods should satisfy all of an application's needs for\n * the communication, storage, and access management of social data.\n * The rest of the application can be built with standard client-side\n * user interface tools to present and interact with that data\u2014no server code necessary!\n *\n * The Typescript code for this API is [open source on Github](https://github.com/graffiti-garden/api).\n *\n * There are several different implementations of this Graffiti API available,\n * including a [federated implementation](https://github.com/graffiti-garden/implementation-remote),\n * that lets people choose where their data is stored (you do not need to host your own server)\n * and a [local implementation](https://github.com/graffiti-garden/implementation-local)\n * that can be used for testing and development. Different implementations can\n * be swapped-in in the future without changing the API or any of the apps built on\n * top of it. In fact, we're working on an end-to-end encrypted version now!\n * [Follow Theia on BlueSky for updates](https://bsky.app/profile/theias.place).\n *\n * On the other side of the stack, there is [Vue plugin](https://vue.graffiti.garden/variables/GraffitiPlugin.html)\n * that wraps around this API to provide reactivity. Other plugin frameworks\n * and high-level libraries will be available in the future.\n *\n * ## API Overview\n *\n * The Graffiti API provides applications with methods for {@link login} and {@link logout},\n * methods to interact with data objects using standard database operations ({@link post}, {@link get}, and {@link delete}),\n * and a method to {@link discover} data objects created by others.\n * These data objects have a couple structured properties:\n * - {@link GraffitiObjectBase.url | `url`} (string): A globally unique identifier and locator for the object.\n * - {@link GraffitiObjectBase.actor | `actor`} (string): An unforgeable identifier for the creator of the object.\n * - {@link GraffitiObjectBase.allowed | `allowed`} (string[] | undefined): An array of the actors who are allowed to access the object (undefined for public objects).\n * - {@link GraffitiObjectBase.channels | `channels`} (string[]): An array of the *contexts* in which the object should appear.\n *\n * All other data is stored in the object's unstructured {@link GraffitiObjectBase.value | `value`} property.\n * This data can be used to represent social artifacts (e.g. posts, profiles) and activities (e.g. likes, follows).\n * For example, a post might have the value:\n\n * ```js\n * {\n * title: \"My First Post\",\n * content: \"Hello, world!\",\n * published: 1630483200000\n * }\n * ```\n *\n * a profile might have the value:\n *\n * ```js\n * {\n * name: \"Theia Henderson\",\n * pronouns: \"she/her\",\n * describes: \"did:web:theias.place\" // Theia's actor ID\n * }\n * ```\n *\n * and a \"Like\" might have the value:\n *\n * ```js\n * {\n * activity: \"Like\",\n * target: \"graffiti:remote:pod.graffiti.garden/12345\" // The URL of the graffiti object being liked\n * }\n * ```\n *\n * New social artifacts and activities can be easily created, simply\n * by creating new objects with appropriate properties. Despite the lack of\n * structure, we expect Graffiti object properties to adhere to a \"[folksonomy](https://en.wikipedia.org/wiki/Folksonomy)\",\n * similar to hashtags. Any string can be used as a hashtag on Twitter,\n * but there is social value in using the same hashtags at other people and\n * so a structure naturally emerges. Similarly, Graffiti objects\n * can have arbitrary properties but if people use the same properties as each other,\n * their apps will interoperate, which has social value.\n *\n * For a more complete and detailed overview of Graffiti's design, please\n * refer to [this section of the Graffiti paper](https://dl.acm.org/doi/10.1145/3746059.3747627#sec-3),\n * published in ACM UIST 2025. The paper also overviews {@link GraffitiObjectBase.channels | `channels`},\n * which are Graffiti's means of organizing data contextually, and a concept called \"total reification\",\n * which handles explains how moderation, collaboration, and other interactions are managed.\n *\n * @groupDescription 1 - Single-Object Methods\n * Methods for {@link post | creating}, {@link get | reading},\n * and {@link delete | deleting} {@link GraffitiObjectBase | Graffiti objects}.\n * @groupDescription 2 - Multi-Object Methods\n * Methods that retrieve or accumulate information about multiple {@link GraffitiObjectBase | Graffiti objects} at a time.\n * @groupDescription 3 - Media Methods\n * Methods for {@link postMedia | creating}, {@link getMedia | reading},\n * and {@link deleteMedia | deleting} media data.\n * @groupDescription 4 - Identity Methods\n * Methods and properties for logging in and out.\n */\nexport abstract class Graffiti {\n /**\n * Creates a new {@link GraffitiObjectBase | object}.\n *\n * @returns Returns the object that has been posted, complete with its\n * assigned {@link GraffitiObjectBase.url | `url`} and\n * {@link GraffitiObjectBase.actor | `actor`}.\n *\n * @group 1 - Single-Object Methods\n */\n abstract post<Schema extends JSONSchema>(\n /**\n * An object to post, minus its {@link GraffitiObjectBase.url | `url`} and\n * {@link GraffitiObjectBase.actor | `actor`}, which will be assigned once posted.\n * This object is statically type-checked against the [JSON schema](https://json-schema.org/) that can be optionally provided\n * as the generic type parameter. It is recommended to use a schema to\n * ensure that the posted object matches subsequent {@link get} or {@link discover}\n * methods.\n */\n partialObject: GraffitiPostObject<Schema>,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<GraffitiObject<Schema>>;\n\n /**\n * Retrieves an object from a given {@link GraffitiObjectBase.url | `url`} matching\n * the provided `schema`.\n *\n * If the retreiving {@link GraffitiObjectBase.actor | `actor`} is not\n * the object's `actor`,\n * the object's {@link GraffitiObjectBase.allowed | `allowed`} and\n * {@link GraffitiObjectBase.channels | `channels`} properties are\n * not revealed, similar to a BCC email.\n *\n * @returns Returns the retrieved object.\n *\n * @throws {@link GraffitiErrorNotFound} if the object does not exist, has been deleted, or the actor is not\n * {@link GraffitiObjectBase.allowed | `allowed`} to access it.\n *\n * @throws {@link GraffitiErrorSchemaMismatch} if the retrieved object does not match the provided schema.\n *\n * @throws {@link GraffitiErrorInvalidSchema} If an invalid schema is provided.\n *\n * @group 1 - Single-Object Methods\n */\n abstract get<Schema extends JSONSchema>(\n /**\n * The location of the object to get.\n */\n url: string | GraffitiObjectUrl,\n /**\n * The JSON schema to validate the retrieved object against.\n */\n schema: Schema,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}. If no `session` is provided,\n * the retrieved object's {@link GraffitiObjectBase.allowed | `allowed`}\n * property must be `undefined`.\n */\n session?: GraffitiSession | null,\n ): Promise<GraffitiObject<Schema>>;\n\n /**\n * Deletes an object from a given {@link GraffitiObjectBase.url | `url`}\n * that had previously been {@link post | `post`ed}.\n * The deleting {@link GraffitiObjectBase.actor | `actor`} must be the same as the\n * `actor` that created the object.\n *\n * @throws {@link GraffitiErrorNotFound} if the object does not exist, has already been deleted,\n * or the actor is not {@link GraffitiObjectBase.allowed | `allowed`} to access it.\n *\n * @throws {@link GraffitiErrorForbidden} if the {@link GraffitiObjectBase.actor | `actor`}\n * is not the same `actor` as the one who created the object.\n *\n * @group 1 - Single-Object Methods\n */\n abstract delete(\n /**\n * The location of the object to delete.\n */\n url: string | GraffitiObjectUrl,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<GraffitiObjectBase>;\n\n /**\n * Discovers objects created by any actor that are contained\n * in at least one of the given {@link GraffitiObjectBase.channels | `channels`}\n * and match the given [JSON Schema](https://json-schema.org).\n *\n * Objects are returned asynchronously as they are discovered but the stream\n * will end once all leads have been exhausted.\n * The {@link GraffitiObjectStream} ends by returning a\n * {@link GraffitiObjectStreamReturn.continue | `continue`} method and a\n * {@link GraffitiObjectStreamReturn.cursor | `cursor`} string,\n * each of which can be be used to poll for new objects.\n * The `continue` method preserves the type safety of the stream and the `cursor`\n * string can be serialized to continue the stream after an application is closed\n * and reopened.\n *\n * `discover` will not return objects that the querying {@link GraffitiObjectBase.actor | `actor`}\n * is not {@link GraffitiObjectBase.allowed | `allowed`} to access.\n * If the `actor` is not the creator of a discovered object,\n * the allowed list will be masked to only contain the querying actor if the\n * allowed list is not `undefined` (public). Additionally, if the actor is not the\n * creator of a discovered object, any {@link GraffitiObjectBase.channels | `channels`}\n * not specified by the `discover` method will not be revealed. This masking happens\n * before the object is validated against the supplied `schema`.\n *\n * Since different implementations may fetch data from multiple sources there is\n * no guarentee on the order that objects are returned in.\n *\n * @throws {@link GraffitiErrorInvalidSchema} if an invalid schema is provided.\n * Discovery is lazy and will not throw until the iterator is consumed.\n *\n * @returns Returns a stream of objects that match the given {@link GraffitiObjectBase.channels | `channels`}\n * and [JSON Schema](https://json-schema.org).\n *\n * @group 2 - Multi-Object Methods\n */\n abstract discover<Schema extends JSONSchema>(\n /**\n * The {@link GraffitiObjectBase.channels | `channels`} that objects must be associated with.\n */\n channels: string[],\n /**\n * A [JSON Schema](https://json-schema.org) that objects must satisfy.\n */\n schema: Schema,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}. If no `session` is provided,\n * only objects that have no {@link GraffitiObjectBase.allowed | `allowed`}\n * property will be returned.\n */\n session?: GraffitiSession | null,\n ): GraffitiObjectStream<Schema>;\n\n /**\n * Continues a {@link GraffitiObjectStream} from a given\n * {@link GraffitiObjectStreamReturn.cursor | `cursor`} string.\n * The continuation will return new objects that have been {@link post | `post`ed}\n * that match the original stream, and also returns the\n * {@link GraffitiObjectBase.url | `url`}s of objects that\n * have been {@link delete | `delete`d}, as marked by a `tombstone`.\n *\n * The `cursor` allows the client to\n * serialize the state of the stream and continue it later.\n * However this method loses any typing information that was\n * present in the original stream. For better type safety\n * and when serializing is not necessary, use the\n * {@link GraffitiObjectStreamReturn.continue | `continue`} method\n * instead, which is returned along with the `cursor` at the\n * end of the original stream.\n *\n * @throws {@link GraffitiErrorNotFound} upon iteration\n * if the cursor is invalid or expired.\n *\n * @throws {@link GraffitiErrorForbidden} upon iteration\n * if the {@link GraffitiObjectBase.actor | `actor`}\n * provided in the `session` is not the same as the `actor`\n * that initiated the original stream.\n *\n * @group 2 - Multi-Object Methods\n */\n abstract continueDiscover(\n cursor: string,\n session?: GraffitiSession | null,\n ): GraffitiObjectStreamContinue<{}>;\n\n /**\n * Uploads media data, such as an image or video.\n *\n * Unlike structured {@link GraffitiObjectBase | objects},\n * media is not indexed for {@link discover | `discover`y} and\n * must be retrieved by its exact URL using {@link getMedia}\n *\n * @returns The URL that the media was posted to.\n *\n * @group 3 - Media Methods\n */\n abstract postMedia(\n /**\n * The media data to upload, and optionally\n * an {@link GraffitiObjectBase.allowed | `allowed`}\n * list of actors that can view it.\n */\n media: GraffitiPostMedia,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<string>;\n\n /**\n * Deletes media previously {@link postMedia | `post`ed} to a given URL.\n *\n * @throws {@link GraffitiErrorNotFound} if no media at that URL exists.\n *\n * @throws {@link GraffitiErrorForbidden} if the {@link GraffitiObjectBase.actor | `actor`}\n * provided in the `session` is not the same as the `actor` that {@link postMedia | `post`ed}\n * the media.\n *\n * @group 3 - Media Methods\n */\n abstract deleteMedia(\n /**\n * A globally unique identifier and locator for the media.\n */\n url: string,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<void>;\n\n /**\n * Retrieves media from the given media URL, adhering to the given requirements.\n *\n * @throws {@link GraffitiErrorNotFound} if no media at that URL exists.\n *\n * @throws {@link GraffitiErrorTooLarge} if the media exceeds the given `maxBytes`.\n *\n * @throws {@link GraffitiErrorNotAcceptable} if the media does not match the given\n * `accept` specification.\n *\n * @returns The URL of the retrieved media, as a [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob)\n * and the {@link GraffitiObjectBase.actor | `actor`} that posted it.\n *\n * @group 3 - Media Methods\n */\n abstract getMedia(\n /**\n * A globally unique identifier and locator for the media.\n */\n mediaUrl: string,\n /**\n * A specification for what types and sizes of media are acceptable.\n */\n accept: GraffitiMediaAccept,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session?: GraffitiSession | null,\n ): Promise<GraffitiMedia>;\n\n /**\n * Begins the login process. Depending on the implementation, this may\n * involve redirecting to a login page or opening a popup,\n * so it should always be called in response to a gesture, such as clicking\n * a button, due to the [feature-gating browser security feature](https://developer.mozilla.org/en-US/docs/Web/Security/User_activation).\n *\n * The {@link GraffitiSession | session} object is returned\n * asynchronously via {@link Graffiti.sessionEvents | sessionEvents}\n * as a {@link GraffitiLoginEvent} with event type `login`.\n *\n * @group 4 - Identity Methods\n */\n abstract login(\n /**\n * A suggested actor to login as. For example, if a user tries to\n * edit a post but are not logged in, the interface can infer that\n * they might want to log in as the actor who created the post\n * they are attempting to edit.\n *\n * Even if provided, the implementation should allow the user\n * to log in as a different actor if they choose.\n */\n actor?: string | null,\n ): Promise<void>;\n\n /**\n * Begins the logout process for a particular {@link GraffitiSession | session}. Depending on the implementation, this may\n * involve redirecting the user to a logout page or opening a popup,\n * so it should always be called in response to a gesture, such as clicking\n * a button, due to the [feature-gating browser security feature](https://developer.mozilla.org/en-US/docs/Web/Security/User_activation).\n *\n * A confirmation will be returned asynchronously via\n * {@link Graffiti.sessionEvents | sessionEvents}\n * as a {@link GraffitiLogoutEvent} as event type `logout`.\n *\n * @group 4 - Identity Methods\n */\n abstract logout(\n /**\n * The {@link GraffitiSession | session} object to logout.\n */\n session: GraffitiSession,\n ): Promise<void>;\n\n /**\n * An event target that can be used to listen for the following\n * events and their corresponding event types:\n * - `login` - {@link GraffitiLoginEvent}\n * - `logout` - {@link GraffitiLogoutEvent}\n * - `initialized` - {@link GraffitiSessionInitializedEvent}\n *\n * @group 4 - Identity Methods\n */\n abstract readonly sessionEvents: EventTarget;\n\n /**\n * Retrieves the human-readable handle associated\n * with the given actor. The handle may change over time\n * and so it should be used for display purposes only.\n *\n * The inverse of {@link handleToActor}.\n *\n * @throws {@link GraffitiErrorNotFound} if a handle cannot be\n * found for the given actor.\n *\n * @returns A human-readable handle for the given actor.\n *\n * @group 4 - Identity Methods\n */\n abstract actorToHandle(actor: string): Promise<string>;\n\n /**\n * Retrieves the actor ID associated with the given handle.\n *\n * The inverse of {@link actorToHandle}.\n *\n * @throws {@link GraffitiErrorNotFound} if there is no actor\n * with the given handle.\n *\n * @returns The actor ID for the given handle.\n *\n * @group 4 - Identity Methods\n */\n abstract handleToActor(handle: string): Promise<string>;\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA+GO,MAAe,SAAS;AAoV/B;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/4-utilities.ts"],
|
|
4
|
-
"sourcesContent": ["import type {\n GraffitiObject,\n GraffitiObjectBase,\n GraffitiObjectUrl,\n GraffitiSession,\n} from \"./2-types\";\nimport { GraffitiErrorInvalidSchema } from \"./3-errors\";\nimport type { JSONSchema } from \"json-schema-to-ts\";\nimport type Ajv from \"ajv\";\n\nexport function unpackObjectUrl(url: string | GraffitiObjectUrl) {\n return typeof url === \"string\" ? url : url.url;\n}\n\nexport function isActorAllowedGraffitiObject(\n object: GraffitiObjectBase,\n session?: GraffitiSession | null,\n) {\n return (\n // If there is no allowed list, the actor is allowed.\n !Array.isArray(object.allowed) ||\n // Otherwise...\n (typeof session?.actor === \"string\" &&\n // The actor must be the creator of the object\n (object.actor === session.actor ||\n // Or be on the allowed list\n object.allowed.includes(session.actor)))\n );\n}\n\nexport function maskGraffitiObject(\n object: GraffitiObjectBase,\n channels: string[],\n actor?: string | null,\n): GraffitiObjectBase {\n // If the actor is the creator, return the object as is\n if (actor === object.actor) return object;\n\n // If there is an allowed list, mask it to only include the actor\n // (This assumes the actor is already allowed to access the object)\n const allowedMasked = object.allowed && actor ? [actor] : undefined;\n // Mask the channels to only include the channels that are being queried\n const channelsMasked = object.channels.filter((c) => channels.includes(c));\n\n return {\n ...object,\n allowed: allowedMasked,\n channels: channelsMasked,\n };\n}\n\nexport function isMediaAcceptable(\n mediaType: string,\n acceptableMediaTypes: string[],\n): boolean {\n const [type, subtype] = mediaType.toLowerCase().split(\";\")[0].split(\"/\");\n\n if (!type || !subtype) return false;\n\n return acceptableMediaTypes.some((acceptable) => {\n const [accType, accSubtype] = acceptable\n .toLowerCase()\n .split(\";\")[0]\n .split(\"/\");\n\n if (!accType || !accSubtype) return false;\n\n return (\n (accType === type || accType === \"*\") &&\n (accSubtype === subtype || accSubtype === \"*\")\n );\n });\n}\n\nlet ajv: Ajv | undefined = undefined;\nexport async function compileGraffitiObjectSchema<Schema extends JSONSchema>(\n schema: Schema,\n) {\n if (!ajv) {\n const { Ajv } = await import(\"ajv\");\n ajv = new Ajv({ strict: false });\n }\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 : String(error),\n );\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,oBAA2C;AAIpC,SAAS,gBAAgB,KAAiC;AAC/D,SAAO,OAAO,QAAQ,WAAW,MAAM,IAAI;AAC7C;AAEO,SAAS,6BACd,QACA,SACA;AACA;AAAA;AAAA,IAEE,CAAC,MAAM,QAAQ,OAAO,OAAO;AAAA,IAE5B,OAAO,SAAS,UAAU;AAAA,KAExB,OAAO,UAAU,QAAQ;AAAA,IAExB,OAAO,QAAQ,SAAS,QAAQ,KAAK;AAAA;AAE7C;AAEO,SAAS,mBACd,QACA,UACA,OACoB;AAEpB,MAAI,UAAU,OAAO,MAAO,QAAO;AAInC,QAAM,gBAAgB,OAAO,WAAW,QAAQ,CAAC,KAAK,IAAI;AAE1D,QAAM,iBAAiB,OAAO,SAAS,OAAO,CAAC,MAAM,SAAS,SAAS,CAAC,CAAC;AAEzE,SAAO;AAAA,IACL,GAAG;AAAA,IACH,SAAS;AAAA,IACT,UAAU;AAAA,EACZ;AACF;AAEO,SAAS,kBACd,WACA,sBACS;AACT,QAAM,CAAC,MAAM,OAAO,IAAI,UAAU,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG;AAEvE,MAAI,CAAC,QAAQ,CAAC,QAAS,QAAO;AAE9B,SAAO,qBAAqB,KAAK,CAAC,eAAe;AAC/C,UAAM,CAAC,SAAS,UAAU,IAAI,WAC3B,YAAY,EACZ,MAAM,GAAG,EAAE,CAAC,EACZ,MAAM,GAAG;AAEZ,QAAI,CAAC,WAAW,CAAC,WAAY,QAAO;AAEpC,YACG,YAAY,QAAQ,YAAY,SAChC,eAAe,WAAW,eAAe;AAAA,EAE9C,CAAC;AACH;AAEA,IAAI,MAAuB;AAC3B,eAAsB,4BACpB,QACA;AACA,MAAI,CAAC,KAAK;AACR,UAAM,EAAE,IAAI,IAAI,MAAM,OAAO,KAAK;AAClC,UAAM,IAAI,IAAI,EAAE,QAAQ,MAAM,CAAC;AAAA,EACjC;AAEA,MAAI;AAKF,WAAO,IAAI,QAAQ,MAAM;AAAA,EAG3B,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IACvD;AAAA,EACF;AACF;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/5-runtime-types.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n looseObject,\n array,\n string,\n url,\n union,\n instanceof as instanceof_,\n int,\n tuple,\n nullable,\n optional,\n nonnegative,\n extend,\n} from \"zod/mini\";\nimport type { Graffiti } from \"./1-api\";\nimport type {\n GraffitiObject,\n GraffitiObjectStream,\n GraffitiObjectStreamContinue,\n GraffitiPostObject,\n GraffitiSession,\n} from \"./2-types\";\nimport type { JSONSchema } from \"json-schema-to-ts\";\n\nexport const GraffitiPostObjectSchema = looseObject({\n value: looseObject({}),\n channels: array(string()),\n allowed: optional(nullable(array(url()))),\n});\nexport const GraffitiObjectSchema = extend(GraffitiPostObjectSchema, {\n url: url(),\n actor: url(),\n});\n\nexport const GraffitiObjectUrlSchema = union([\n looseObject({\n url: url(),\n }),\n url(),\n]);\n\nexport const GraffitiSessionSchema = looseObject({\n actor: url(),\n});\nexport const GraffitiOptionalSessionSchema = optional(\n nullable(GraffitiSessionSchema),\n);\n\nexport const GraffitiPostMediaSchema = looseObject({\n data: instanceof_(Blob),\n allowed: optional(nullable(array(url()))),\n});\nexport const GraffitiMediaSchema = extend(GraffitiPostMediaSchema, {\n actor: url(),\n});\nexport const GraffitiMediaAcceptSchema = looseObject({\n types: optional(array(string())),\n maxBytes: optional(int().check(nonnegative())),\n});\n\nasync function* wrapGraffitiStream<Schema extends JSONSchema>(\n stream: GraffitiObjectStream<Schema>,\n): GraffitiObjectStream<Schema> {\n while (true) {\n const next = await stream.next();\n if (next.done) {\n const { cursor, continue: continue_ } = next.value;\n return {\n cursor,\n continue: (...args) => {\n const typedArgs = tuple([GraffitiOptionalSessionSchema]).parse(args);\n return continue_(...typedArgs);\n },\n };\n } else {\n yield next.value;\n }\n }\n}\nasync function* wrapGraffitiContinueStream<Schema extends JSONSchema>(\n stream: GraffitiObjectStreamContinue<Schema>,\n): GraffitiObjectStreamContinue<Schema> {\n while (true) {\n const next = await stream.next();\n if (next.done) {\n const { cursor, continue: continue_ } = next.value;\n return {\n cursor,\n continue: (...args) => {\n const typedArgs = tuple([GraffitiOptionalSessionSchema]).parse(args);\n return continue_(...typedArgs);\n },\n };\n } else {\n yield next.value;\n }\n }\n}\n\n// @ts-ignore - infinite nesting issue\nexport class GraffitiRuntimeTypes implements Graffiti {\n sessionEvents: Graffiti[\"sessionEvents\"];\n constructor(protected readonly graffiti: Graffiti) {\n this.sessionEvents = this.graffiti.sessionEvents;\n }\n\n login: Graffiti[\"login\"] = (...args) => {\n const typedArgs = tuple([optional(url())]).parse(args);\n return this.graffiti.login(...typedArgs);\n };\n\n logout: Graffiti[\"logout\"] = (...args) => {\n const typedArgs = tuple([GraffitiSessionSchema]).parse(args);\n return this.graffiti.logout(...typedArgs);\n };\n\n handleToActor: Graffiti[\"handleToActor\"] = (...args) => {\n const typedArgs = tuple([string()]).parse(args);\n return this.graffiti.handleToActor(...typedArgs);\n };\n\n actorToHandle: Graffiti[\"actorToHandle\"] = (...args) => {\n const typedArgs = tuple([url()]).parse(args);\n return this.graffiti.actorToHandle(...typedArgs);\n };\n\n async post<Schema extends JSONSchema>(\n partialObject: GraffitiPostObject<Schema>,\n session: GraffitiSession,\n ): Promise<GraffitiObject<Schema>> {\n const typedArgs = tuple([\n GraffitiPostObjectSchema,\n GraffitiSessionSchema,\n ]).parse([partialObject, session]);\n\n return (await this.graffiti.post<{}>(\n ...typedArgs,\n )) as GraffitiObject<Schema>;\n }\n\n get: Graffiti[\"get\"] = (...args) => {\n const typedArgs = tuple([\n GraffitiObjectUrlSchema,\n looseObject({}),\n GraffitiOptionalSessionSchema,\n ]).parse(args);\n\n return this.graffiti.get(...typedArgs) as Promise<\n GraffitiObject<(typeof args)[1]>\n >;\n };\n\n delete: Graffiti[\"delete\"] = (...args) => {\n const typedArgs = tuple([\n GraffitiObjectUrlSchema,\n GraffitiSessionSchema,\n ]).parse(args);\n return this.graffiti.delete(...typedArgs);\n };\n\n postMedia: Graffiti[\"postMedia\"] = (...args) => {\n const typedArgs = tuple([\n GraffitiPostMediaSchema,\n GraffitiSessionSchema,\n ]).parse(args);\n return this.graffiti.postMedia(...typedArgs);\n };\n\n getMedia: Graffiti[\"getMedia\"] = (...args) => {\n const typedArgs = tuple([\n url(),\n GraffitiMediaAcceptSchema,\n GraffitiOptionalSessionSchema,\n ]).parse(args);\n\n return this.graffiti.getMedia(...typedArgs);\n };\n\n deleteMedia: Graffiti[\"deleteMedia\"] = (...args) => {\n const typedArgs = tuple([url(), GraffitiSessionSchema]).parse(args);\n\n return this.graffiti.deleteMedia(...typedArgs);\n };\n\n discover: Graffiti[\"discover\"] = (...args) => {\n const typedArgs = tuple([\n array(string()),\n looseObject({}),\n GraffitiOptionalSessionSchema,\n ]).parse(args);\n const stream = this.graffiti.discover(...typedArgs) as GraffitiObjectStream<\n (typeof args)[1]\n >;\n return wrapGraffitiStream(stream);\n };\n\n continueDiscover: Graffiti[\"continueDiscover\"] = (...args) => {\n const typedArgs = tuple([string(), GraffitiOptionalSessionSchema]).parse(\n args,\n );\n\n const stream = this.graffiti.continueDiscover(...typedArgs);\n return wrapGraffitiContinueStream(stream);\n };\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["instanceof_"]
|
|
7
|
-
}
|
package/dist/cjs/index.cjs.map
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["export * from \"./1-api\";\nexport * from \"./2-types\";\nexport * from \"./3-errors\";\nexport * from \"./4-utilities\";\nexport * from \"./5-runtime-types\";\nexport type { JSONSchema } from \"json-schema-to-ts\";\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;AAAA;AAAA;AAAA,0BAAc,oBAAd;AACA,0BAAc,sBADd;AAEA,0BAAc,uBAFd;AAGA,0BAAc,0BAHd;AAIA,0BAAc,8BAJd;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
package/dist/esm/1-api.mjs.map
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/1-api.ts"],
|
|
4
|
-
"sourcesContent": ["import type {\n GraffitiObjectUrl,\n GraffitiObject,\n GraffitiObjectBase,\n GraffitiSession,\n GraffitiPostObject,\n GraffitiObjectStream,\n GraffitiObjectStreamContinue,\n GraffitiMedia,\n GraffitiPostMedia,\n GraffitiMediaAccept,\n} from \"./2-types\";\nimport type { JSONSchema } from \"json-schema-to-ts\";\n\n/**\n * This API describes a small but powerful set of methods that\n * can be used to create many different kinds of social applications,\n * from applications like Twitter, to Messenger, to Wikipedia, to many more new designs.\n * See the [Graffiti project website](https://graffiti.garden)\n * for links to example applications. Additionally, apps built on top\n * of the API interoperate with each other so you can seamlessly switch\n * between apps without losing your friends or data.\n *\n * These API methods should satisfy all of an application's needs for\n * the communication, storage, and access management of social data.\n * The rest of the application can be built with standard client-side\n * user interface tools to present and interact with that data\u2014no server code necessary!\n *\n * The Typescript code for this API is [open source on Github](https://github.com/graffiti-garden/api).\n *\n * There are several different implementations of this Graffiti API available,\n * including a [federated implementation](https://github.com/graffiti-garden/implementation-remote),\n * that lets people choose where their data is stored (you do not need to host your own server)\n * and a [local implementation](https://github.com/graffiti-garden/implementation-local)\n * that can be used for testing and development. Different implementations can\n * be swapped-in in the future without changing the API or any of the apps built on\n * top of it. In fact, we're working on an end-to-end encrypted version now!\n * [Follow Theia on BlueSky for updates](https://bsky.app/profile/theias.place).\n *\n * On the other side of the stack, there is [Vue plugin](https://vue.graffiti.garden/variables/GraffitiPlugin.html)\n * that wraps around this API to provide reactivity. Other plugin frameworks\n * and high-level libraries will be available in the future.\n *\n * ## API Overview\n *\n * The Graffiti API provides applications with methods for {@link login} and {@link logout},\n * methods to interact with data objects using standard database operations ({@link post}, {@link get}, and {@link delete}),\n * and a method to {@link discover} data objects created by others.\n * These data objects have a couple structured properties:\n * - {@link GraffitiObjectBase.url | `url`} (string): A globally unique identifier and locator for the object.\n * - {@link GraffitiObjectBase.actor | `actor`} (string): An unforgeable identifier for the creator of the object.\n * - {@link GraffitiObjectBase.allowed | `allowed`} (string[] | undefined): An array of the actors who are allowed to access the object (undefined for public objects).\n * - {@link GraffitiObjectBase.channels | `channels`} (string[]): An array of the *contexts* in which the object should appear.\n *\n * All other data is stored in the object's unstructured {@link GraffitiObjectBase.value | `value`} property.\n * This data can be used to represent social artifacts (e.g. posts, profiles) and activities (e.g. likes, follows).\n * For example, a post might have the value:\n\n * ```js\n * {\n * title: \"My First Post\",\n * content: \"Hello, world!\",\n * published: 1630483200000\n * }\n * ```\n *\n * a profile might have the value:\n *\n * ```js\n * {\n * name: \"Theia Henderson\",\n * pronouns: \"she/her\",\n * describes: \"did:web:theias.place\" // Theia's actor ID\n * }\n * ```\n *\n * and a \"Like\" might have the value:\n *\n * ```js\n * {\n * activity: \"Like\",\n * target: \"graffiti:remote:pod.graffiti.garden/12345\" // The URL of the graffiti object being liked\n * }\n * ```\n *\n * New social artifacts and activities can be easily created, simply\n * by creating new objects with appropriate properties. Despite the lack of\n * structure, we expect Graffiti object properties to adhere to a \"[folksonomy](https://en.wikipedia.org/wiki/Folksonomy)\",\n * similar to hashtags. Any string can be used as a hashtag on Twitter,\n * but there is social value in using the same hashtags at other people and\n * so a structure naturally emerges. Similarly, Graffiti objects\n * can have arbitrary properties but if people use the same properties as each other,\n * their apps will interoperate, which has social value.\n *\n * For a more complete and detailed overview of Graffiti's design, please\n * refer to [this section of the Graffiti paper](https://dl.acm.org/doi/10.1145/3746059.3747627#sec-3),\n * published in ACM UIST 2025. The paper also overviews {@link GraffitiObjectBase.channels | `channels`},\n * which are Graffiti's means of organizing data contextually, and a concept called \"total reification\",\n * which handles explains how moderation, collaboration, and other interactions are managed.\n *\n * @groupDescription 1 - Single-Object Methods\n * Methods for {@link post | creating}, {@link get | reading},\n * and {@link delete | deleting} {@link GraffitiObjectBase | Graffiti objects}.\n * @groupDescription 2 - Multi-Object Methods\n * Methods that retrieve or accumulate information about multiple {@link GraffitiObjectBase | Graffiti objects} at a time.\n * @groupDescription 3 - Media Methods\n * Methods for {@link postMedia | creating}, {@link getMedia | reading},\n * and {@link deleteMedia | deleting} media data.\n * @groupDescription 4 - Identity Methods\n * Methods and properties for logging in and out.\n */\nexport abstract class Graffiti {\n /**\n * Creates a new {@link GraffitiObjectBase | object}.\n *\n * @returns Returns the object that has been posted, complete with its\n * assigned {@link GraffitiObjectBase.url | `url`} and\n * {@link GraffitiObjectBase.actor | `actor`}.\n *\n * @group 1 - Single-Object Methods\n */\n abstract post<Schema extends JSONSchema>(\n /**\n * An object to post, minus its {@link GraffitiObjectBase.url | `url`} and\n * {@link GraffitiObjectBase.actor | `actor`}, which will be assigned once posted.\n * This object is statically type-checked against the [JSON schema](https://json-schema.org/) that can be optionally provided\n * as the generic type parameter. It is recommended to use a schema to\n * ensure that the posted object matches subsequent {@link get} or {@link discover}\n * methods.\n */\n partialObject: GraffitiPostObject<Schema>,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<GraffitiObject<Schema>>;\n\n /**\n * Retrieves an object from a given {@link GraffitiObjectBase.url | `url`} matching\n * the provided `schema`.\n *\n * If the retreiving {@link GraffitiObjectBase.actor | `actor`} is not\n * the object's `actor`,\n * the object's {@link GraffitiObjectBase.allowed | `allowed`} and\n * {@link GraffitiObjectBase.channels | `channels`} properties are\n * not revealed, similar to a BCC email.\n *\n * @returns Returns the retrieved object.\n *\n * @throws {@link GraffitiErrorNotFound} if the object does not exist, has been deleted, or the actor is not\n * {@link GraffitiObjectBase.allowed | `allowed`} to access it.\n *\n * @throws {@link GraffitiErrorSchemaMismatch} if the retrieved object does not match the provided schema.\n *\n * @throws {@link GraffitiErrorInvalidSchema} If an invalid schema is provided.\n *\n * @group 1 - Single-Object Methods\n */\n abstract get<Schema extends JSONSchema>(\n /**\n * The location of the object to get.\n */\n url: string | GraffitiObjectUrl,\n /**\n * The JSON schema to validate the retrieved object against.\n */\n schema: Schema,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}. If no `session` is provided,\n * the retrieved object's {@link GraffitiObjectBase.allowed | `allowed`}\n * property must be `undefined`.\n */\n session?: GraffitiSession | null,\n ): Promise<GraffitiObject<Schema>>;\n\n /**\n * Deletes an object from a given {@link GraffitiObjectBase.url | `url`}\n * that had previously been {@link post | `post`ed}.\n * The deleting {@link GraffitiObjectBase.actor | `actor`} must be the same as the\n * `actor` that created the object.\n *\n * @throws {@link GraffitiErrorNotFound} if the object does not exist, has already been deleted,\n * or the actor is not {@link GraffitiObjectBase.allowed | `allowed`} to access it.\n *\n * @throws {@link GraffitiErrorForbidden} if the {@link GraffitiObjectBase.actor | `actor`}\n * is not the same `actor` as the one who created the object.\n *\n * @group 1 - Single-Object Methods\n */\n abstract delete(\n /**\n * The location of the object to delete.\n */\n url: string | GraffitiObjectUrl,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<GraffitiObjectBase>;\n\n /**\n * Discovers objects created by any actor that are contained\n * in at least one of the given {@link GraffitiObjectBase.channels | `channels`}\n * and match the given [JSON Schema](https://json-schema.org).\n *\n * Objects are returned asynchronously as they are discovered but the stream\n * will end once all leads have been exhausted.\n * The {@link GraffitiObjectStream} ends by returning a\n * {@link GraffitiObjectStreamReturn.continue | `continue`} method and a\n * {@link GraffitiObjectStreamReturn.cursor | `cursor`} string,\n * each of which can be be used to poll for new objects.\n * The `continue` method preserves the type safety of the stream and the `cursor`\n * string can be serialized to continue the stream after an application is closed\n * and reopened.\n *\n * `discover` will not return objects that the querying {@link GraffitiObjectBase.actor | `actor`}\n * is not {@link GraffitiObjectBase.allowed | `allowed`} to access.\n * If the `actor` is not the creator of a discovered object,\n * the allowed list will be masked to only contain the querying actor if the\n * allowed list is not `undefined` (public). Additionally, if the actor is not the\n * creator of a discovered object, any {@link GraffitiObjectBase.channels | `channels`}\n * not specified by the `discover` method will not be revealed. This masking happens\n * before the object is validated against the supplied `schema`.\n *\n * Since different implementations may fetch data from multiple sources there is\n * no guarentee on the order that objects are returned in.\n *\n * @throws {@link GraffitiErrorInvalidSchema} if an invalid schema is provided.\n * Discovery is lazy and will not throw until the iterator is consumed.\n *\n * @returns Returns a stream of objects that match the given {@link GraffitiObjectBase.channels | `channels`}\n * and [JSON Schema](https://json-schema.org).\n *\n * @group 2 - Multi-Object Methods\n */\n abstract discover<Schema extends JSONSchema>(\n /**\n * The {@link GraffitiObjectBase.channels | `channels`} that objects must be associated with.\n */\n channels: string[],\n /**\n * A [JSON Schema](https://json-schema.org) that objects must satisfy.\n */\n schema: Schema,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}. If no `session` is provided,\n * only objects that have no {@link GraffitiObjectBase.allowed | `allowed`}\n * property will be returned.\n */\n session?: GraffitiSession | null,\n ): GraffitiObjectStream<Schema>;\n\n /**\n * Continues a {@link GraffitiObjectStream} from a given\n * {@link GraffitiObjectStreamReturn.cursor | `cursor`} string.\n * The continuation will return new objects that have been {@link post | `post`ed}\n * that match the original stream, and also returns the\n * {@link GraffitiObjectBase.url | `url`}s of objects that\n * have been {@link delete | `delete`d}, as marked by a `tombstone`.\n *\n * The `cursor` allows the client to\n * serialize the state of the stream and continue it later.\n * However this method loses any typing information that was\n * present in the original stream. For better type safety\n * and when serializing is not necessary, use the\n * {@link GraffitiObjectStreamReturn.continue | `continue`} method\n * instead, which is returned along with the `cursor` at the\n * end of the original stream.\n *\n * @throws {@link GraffitiErrorNotFound} upon iteration\n * if the cursor is invalid or expired.\n *\n * @throws {@link GraffitiErrorForbidden} upon iteration\n * if the {@link GraffitiObjectBase.actor | `actor`}\n * provided in the `session` is not the same as the `actor`\n * that initiated the original stream.\n *\n * @group 2 - Multi-Object Methods\n */\n abstract continueDiscover(\n cursor: string,\n session?: GraffitiSession | null,\n ): GraffitiObjectStreamContinue<{}>;\n\n /**\n * Uploads media data, such as an image or video.\n *\n * Unlike structured {@link GraffitiObjectBase | objects},\n * media is not indexed for {@link discover | `discover`y} and\n * must be retrieved by its exact URL using {@link getMedia}\n *\n * @returns The URL that the media was posted to.\n *\n * @group 3 - Media Methods\n */\n abstract postMedia(\n /**\n * The media data to upload, and optionally\n * an {@link GraffitiObjectBase.allowed | `allowed`}\n * list of actors that can view it.\n */\n media: GraffitiPostMedia,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<string>;\n\n /**\n * Deletes media previously {@link postMedia | `post`ed} to a given URL.\n *\n * @throws {@link GraffitiErrorNotFound} if no media at that URL exists.\n *\n * @throws {@link GraffitiErrorForbidden} if the {@link GraffitiObjectBase.actor | `actor`}\n * provided in the `session` is not the same as the `actor` that {@link postMedia | `post`ed}\n * the media.\n *\n * @group 3 - Media Methods\n */\n abstract deleteMedia(\n /**\n * A globally unique identifier and locator for the media.\n */\n url: string,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<void>;\n\n /**\n * Retrieves media from the given media URL, adhering to the given requirements.\n *\n * @throws {@link GraffitiErrorNotFound} if no media at that URL exists.\n *\n * @throws {@link GraffitiErrorTooLarge} if the media exceeds the given `maxBytes`.\n *\n * @throws {@link GraffitiErrorNotAcceptable} if the media does not match the given\n * `accept` specification.\n *\n * @returns The URL of the retrieved media, as a [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob)\n * and the {@link GraffitiObjectBase.actor | `actor`} that posted it.\n *\n * @group 3 - Media Methods\n */\n abstract getMedia(\n /**\n * A globally unique identifier and locator for the media.\n */\n mediaUrl: string,\n /**\n * A specification for what types and sizes of media are acceptable.\n */\n accept: GraffitiMediaAccept,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session?: GraffitiSession | null,\n ): Promise<GraffitiMedia>;\n\n /**\n * Begins the login process. Depending on the implementation, this may\n * involve redirecting to a login page or opening a popup,\n * so it should always be called in response to a gesture, such as clicking\n * a button, due to the [feature-gating browser security feature](https://developer.mozilla.org/en-US/docs/Web/Security/User_activation).\n *\n * The {@link GraffitiSession | session} object is returned\n * asynchronously via {@link Graffiti.sessionEvents | sessionEvents}\n * as a {@link GraffitiLoginEvent} with event type `login`.\n *\n * @group 4 - Identity Methods\n */\n abstract login(\n /**\n * A suggested actor to login as. For example, if a user tries to\n * edit a post but are not logged in, the interface can infer that\n * they might want to log in as the actor who created the post\n * they are attempting to edit.\n *\n * Even if provided, the implementation should allow the user\n * to log in as a different actor if they choose.\n */\n actor?: string | null,\n ): Promise<void>;\n\n /**\n * Begins the logout process for a particular {@link GraffitiSession | session}. Depending on the implementation, this may\n * involve redirecting the user to a logout page or opening a popup,\n * so it should always be called in response to a gesture, such as clicking\n * a button, due to the [feature-gating browser security feature](https://developer.mozilla.org/en-US/docs/Web/Security/User_activation).\n *\n * A confirmation will be returned asynchronously via\n * {@link Graffiti.sessionEvents | sessionEvents}\n * as a {@link GraffitiLogoutEvent} as event type `logout`.\n *\n * @group 4 - Identity Methods\n */\n abstract logout(\n /**\n * The {@link GraffitiSession | session} object to logout.\n */\n session: GraffitiSession,\n ): Promise<void>;\n\n /**\n * An event target that can be used to listen for the following\n * events and their corresponding event types:\n * - `login` - {@link GraffitiLoginEvent}\n * - `logout` - {@link GraffitiLogoutEvent}\n * - `initialized` - {@link GraffitiSessionInitializedEvent}\n *\n * @group 4 - Identity Methods\n */\n abstract readonly sessionEvents: EventTarget;\n\n /**\n * Retrieves the human-readable handle associated\n * with the given actor. The handle may change over time\n * and so it should be used for display purposes only.\n *\n * The inverse of {@link handleToActor}.\n *\n * @throws {@link GraffitiErrorNotFound} if a handle cannot be\n * found for the given actor.\n *\n * @returns A human-readable handle for the given actor.\n *\n * @group 4 - Identity Methods\n */\n abstract actorToHandle(actor: string): Promise<string>;\n\n /**\n * Retrieves the actor ID associated with the given handle.\n *\n * The inverse of {@link actorToHandle}.\n *\n * @throws {@link GraffitiErrorNotFound} if there is no actor\n * with the given handle.\n *\n * @returns The actor ID for the given handle.\n *\n * @group 4 - Identity Methods\n */\n abstract handleToActor(handle: string): Promise<string>;\n}\n"],
|
|
5
|
-
"mappings": "AA+GO,MAAe,SAAS;AAoV/B;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/4-utilities.ts"],
|
|
4
|
-
"sourcesContent": ["import type {\n GraffitiObject,\n GraffitiObjectBase,\n GraffitiObjectUrl,\n GraffitiSession,\n} from \"./2-types\";\nimport { GraffitiErrorInvalidSchema } from \"./3-errors\";\nimport type { JSONSchema } from \"json-schema-to-ts\";\nimport type Ajv from \"ajv\";\n\nexport function unpackObjectUrl(url: string | GraffitiObjectUrl) {\n return typeof url === \"string\" ? url : url.url;\n}\n\nexport function isActorAllowedGraffitiObject(\n object: GraffitiObjectBase,\n session?: GraffitiSession | null,\n) {\n return (\n // If there is no allowed list, the actor is allowed.\n !Array.isArray(object.allowed) ||\n // Otherwise...\n (typeof session?.actor === \"string\" &&\n // The actor must be the creator of the object\n (object.actor === session.actor ||\n // Or be on the allowed list\n object.allowed.includes(session.actor)))\n );\n}\n\nexport function maskGraffitiObject(\n object: GraffitiObjectBase,\n channels: string[],\n actor?: string | null,\n): GraffitiObjectBase {\n // If the actor is the creator, return the object as is\n if (actor === object.actor) return object;\n\n // If there is an allowed list, mask it to only include the actor\n // (This assumes the actor is already allowed to access the object)\n const allowedMasked = object.allowed && actor ? [actor] : undefined;\n // Mask the channels to only include the channels that are being queried\n const channelsMasked = object.channels.filter((c) => channels.includes(c));\n\n return {\n ...object,\n allowed: allowedMasked,\n channels: channelsMasked,\n };\n}\n\nexport function isMediaAcceptable(\n mediaType: string,\n acceptableMediaTypes: string[],\n): boolean {\n const [type, subtype] = mediaType.toLowerCase().split(\";\")[0].split(\"/\");\n\n if (!type || !subtype) return false;\n\n return acceptableMediaTypes.some((acceptable) => {\n const [accType, accSubtype] = acceptable\n .toLowerCase()\n .split(\";\")[0]\n .split(\"/\");\n\n if (!accType || !accSubtype) return false;\n\n return (\n (accType === type || accType === \"*\") &&\n (accSubtype === subtype || accSubtype === \"*\")\n );\n });\n}\n\nlet ajv: Ajv | undefined = undefined;\nexport async function compileGraffitiObjectSchema<Schema extends JSONSchema>(\n schema: Schema,\n) {\n if (!ajv) {\n const { Ajv } = await import(\"ajv\");\n ajv = new Ajv({ strict: false });\n }\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 : String(error),\n );\n }\n}\n"],
|
|
5
|
-
"mappings": "AAMA,SAAS,kCAAkC;AAIpC,SAAS,gBAAgB,KAAiC;AAC/D,SAAO,OAAO,QAAQ,WAAW,MAAM,IAAI;AAC7C;AAEO,SAAS,6BACd,QACA,SACA;AACA;AAAA;AAAA,IAEE,CAAC,MAAM,QAAQ,OAAO,OAAO;AAAA,IAE5B,OAAO,SAAS,UAAU;AAAA,KAExB,OAAO,UAAU,QAAQ;AAAA,IAExB,OAAO,QAAQ,SAAS,QAAQ,KAAK;AAAA;AAE7C;AAEO,SAAS,mBACd,QACA,UACA,OACoB;AAEpB,MAAI,UAAU,OAAO,MAAO,QAAO;AAInC,QAAM,gBAAgB,OAAO,WAAW,QAAQ,CAAC,KAAK,IAAI;AAE1D,QAAM,iBAAiB,OAAO,SAAS,OAAO,CAAC,MAAM,SAAS,SAAS,CAAC,CAAC;AAEzE,SAAO;AAAA,IACL,GAAG;AAAA,IACH,SAAS;AAAA,IACT,UAAU;AAAA,EACZ;AACF;AAEO,SAAS,kBACd,WACA,sBACS;AACT,QAAM,CAAC,MAAM,OAAO,IAAI,UAAU,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG;AAEvE,MAAI,CAAC,QAAQ,CAAC,QAAS,QAAO;AAE9B,SAAO,qBAAqB,KAAK,CAAC,eAAe;AAC/C,UAAM,CAAC,SAAS,UAAU,IAAI,WAC3B,YAAY,EACZ,MAAM,GAAG,EAAE,CAAC,EACZ,MAAM,GAAG;AAEZ,QAAI,CAAC,WAAW,CAAC,WAAY,QAAO;AAEpC,YACG,YAAY,QAAQ,YAAY,SAChC,eAAe,WAAW,eAAe;AAAA,EAE9C,CAAC;AACH;AAEA,IAAI,MAAuB;AAC3B,eAAsB,4BACpB,QACA;AACA,MAAI,CAAC,KAAK;AACR,UAAM,EAAE,IAAI,IAAI,MAAM,OAAO,KAAK;AAClC,UAAM,IAAI,IAAI,EAAE,QAAQ,MAAM,CAAC;AAAA,EACjC;AAEA,MAAI;AAKF,WAAO,IAAI,QAAQ,MAAM;AAAA,EAG3B,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IACvD;AAAA,EACF;AACF;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/5-runtime-types.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n looseObject,\n array,\n string,\n url,\n union,\n instanceof as instanceof_,\n int,\n tuple,\n nullable,\n optional,\n nonnegative,\n extend,\n} from \"zod/mini\";\nimport type { Graffiti } from \"./1-api\";\nimport type {\n GraffitiObject,\n GraffitiObjectStream,\n GraffitiObjectStreamContinue,\n GraffitiPostObject,\n GraffitiSession,\n} from \"./2-types\";\nimport type { JSONSchema } from \"json-schema-to-ts\";\n\nexport const GraffitiPostObjectSchema = looseObject({\n value: looseObject({}),\n channels: array(string()),\n allowed: optional(nullable(array(url()))),\n});\nexport const GraffitiObjectSchema = extend(GraffitiPostObjectSchema, {\n url: url(),\n actor: url(),\n});\n\nexport const GraffitiObjectUrlSchema = union([\n looseObject({\n url: url(),\n }),\n url(),\n]);\n\nexport const GraffitiSessionSchema = looseObject({\n actor: url(),\n});\nexport const GraffitiOptionalSessionSchema = optional(\n nullable(GraffitiSessionSchema),\n);\n\nexport const GraffitiPostMediaSchema = looseObject({\n data: instanceof_(Blob),\n allowed: optional(nullable(array(url()))),\n});\nexport const GraffitiMediaSchema = extend(GraffitiPostMediaSchema, {\n actor: url(),\n});\nexport const GraffitiMediaAcceptSchema = looseObject({\n types: optional(array(string())),\n maxBytes: optional(int().check(nonnegative())),\n});\n\nasync function* wrapGraffitiStream<Schema extends JSONSchema>(\n stream: GraffitiObjectStream<Schema>,\n): GraffitiObjectStream<Schema> {\n while (true) {\n const next = await stream.next();\n if (next.done) {\n const { cursor, continue: continue_ } = next.value;\n return {\n cursor,\n continue: (...args) => {\n const typedArgs = tuple([GraffitiOptionalSessionSchema]).parse(args);\n return continue_(...typedArgs);\n },\n };\n } else {\n yield next.value;\n }\n }\n}\nasync function* wrapGraffitiContinueStream<Schema extends JSONSchema>(\n stream: GraffitiObjectStreamContinue<Schema>,\n): GraffitiObjectStreamContinue<Schema> {\n while (true) {\n const next = await stream.next();\n if (next.done) {\n const { cursor, continue: continue_ } = next.value;\n return {\n cursor,\n continue: (...args) => {\n const typedArgs = tuple([GraffitiOptionalSessionSchema]).parse(args);\n return continue_(...typedArgs);\n },\n };\n } else {\n yield next.value;\n }\n }\n}\n\n// @ts-ignore - infinite nesting issue\nexport class GraffitiRuntimeTypes implements Graffiti {\n sessionEvents: Graffiti[\"sessionEvents\"];\n constructor(protected readonly graffiti: Graffiti) {\n this.sessionEvents = this.graffiti.sessionEvents;\n }\n\n login: Graffiti[\"login\"] = (...args) => {\n const typedArgs = tuple([optional(url())]).parse(args);\n return this.graffiti.login(...typedArgs);\n };\n\n logout: Graffiti[\"logout\"] = (...args) => {\n const typedArgs = tuple([GraffitiSessionSchema]).parse(args);\n return this.graffiti.logout(...typedArgs);\n };\n\n handleToActor: Graffiti[\"handleToActor\"] = (...args) => {\n const typedArgs = tuple([string()]).parse(args);\n return this.graffiti.handleToActor(...typedArgs);\n };\n\n actorToHandle: Graffiti[\"actorToHandle\"] = (...args) => {\n const typedArgs = tuple([url()]).parse(args);\n return this.graffiti.actorToHandle(...typedArgs);\n };\n\n async post<Schema extends JSONSchema>(\n partialObject: GraffitiPostObject<Schema>,\n session: GraffitiSession,\n ): Promise<GraffitiObject<Schema>> {\n const typedArgs = tuple([\n GraffitiPostObjectSchema,\n GraffitiSessionSchema,\n ]).parse([partialObject, session]);\n\n return (await this.graffiti.post<{}>(\n ...typedArgs,\n )) as GraffitiObject<Schema>;\n }\n\n get: Graffiti[\"get\"] = (...args) => {\n const typedArgs = tuple([\n GraffitiObjectUrlSchema,\n looseObject({}),\n GraffitiOptionalSessionSchema,\n ]).parse(args);\n\n return this.graffiti.get(...typedArgs) as Promise<\n GraffitiObject<(typeof args)[1]>\n >;\n };\n\n delete: Graffiti[\"delete\"] = (...args) => {\n const typedArgs = tuple([\n GraffitiObjectUrlSchema,\n GraffitiSessionSchema,\n ]).parse(args);\n return this.graffiti.delete(...typedArgs);\n };\n\n postMedia: Graffiti[\"postMedia\"] = (...args) => {\n const typedArgs = tuple([\n GraffitiPostMediaSchema,\n GraffitiSessionSchema,\n ]).parse(args);\n return this.graffiti.postMedia(...typedArgs);\n };\n\n getMedia: Graffiti[\"getMedia\"] = (...args) => {\n const typedArgs = tuple([\n url(),\n GraffitiMediaAcceptSchema,\n GraffitiOptionalSessionSchema,\n ]).parse(args);\n\n return this.graffiti.getMedia(...typedArgs);\n };\n\n deleteMedia: Graffiti[\"deleteMedia\"] = (...args) => {\n const typedArgs = tuple([url(), GraffitiSessionSchema]).parse(args);\n\n return this.graffiti.deleteMedia(...typedArgs);\n };\n\n discover: Graffiti[\"discover\"] = (...args) => {\n const typedArgs = tuple([\n array(string()),\n looseObject({}),\n GraffitiOptionalSessionSchema,\n ]).parse(args);\n const stream = this.graffiti.discover(...typedArgs) as GraffitiObjectStream<\n (typeof args)[1]\n >;\n return wrapGraffitiStream(stream);\n };\n\n continueDiscover: Graffiti[\"continueDiscover\"] = (...args) => {\n const typedArgs = tuple([string(), GraffitiOptionalSessionSchema]).parse(\n args,\n );\n\n const stream = this.graffiti.continueDiscover(...typedArgs);\n return wrapGraffitiContinueStream(stream);\n };\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
package/dist/esm/index.mjs
DELETED
package/dist/esm/index.mjs.map
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["export * from \"./1-api\";\nexport * from \"./2-types\";\nexport * from \"./3-errors\";\nexport * from \"./4-utilities\";\nexport * from \"./5-runtime-types\";\nexport type { JSONSchema } from \"json-schema-to-ts\";\n"],
|
|
5
|
-
"mappings": "AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|