From 1425b0cd4f5622bbe8ab75c440096a023892e58b Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Tue, 20 Sep 2022 17:39:05 -0500 Subject: [PATCH] New XRPC-based Server and API implementations (#180) * Add xrpc-cli package; implement 'new' and 'gen-md' commands * Add gen-ts-obj to xrpc-cli * Add gen-api command to xrpc-cli * Add gen-server to xrpc-cli * Update codegen from xrpc-cli to run prettier and include a banner comment * Use 'void' instead of 'undefined' for return type * Skip invalid schemas when generating code * Rewrite schemas package to use new simplified schema * Output type definitions in nested folders * Add record schema code-generation * Change xrcp client call() signature to be easier to use * Update client codegen to match new xrpc call signature * Fixes and improvements to generated record APIs * Update generated api to use per-service instances * Add xrpc-cli readme * (WIP) Rewrite server to use xrpc * Add missing references to server/tsconfig.json * Replace api implementation with xrpc generated client * Add /schemas folder with todo.adx * Add /schemas/todo.social * Update api package to include todo.social xrpc and adx schemas * Update server package to include todo.social xrpc and adx schemas * Fix some schema definitions * (WIP) Work toward xrpc update of server * Update auth package to use nsids for collections * Update repo package to use nsids for collections * Update AdxUri to use nsid collection IDs * Bring server CRUD tests to passing state * Move API to simpler xrpc call signature * Apply fixes and improvements to record apis * Update to new generated api with per-service instances * Rename the todo.social.* view methods * Rewrite the views system to use xrpc * Remove paths accidentally added to tsconfig * Remove microblog package * Add simple API readme * todo about deduping ucan prfs * increase postThread default depth to fix failing test. & fix USE_TEST_SERVER config Co-authored-by: dholms --- docs/specs/adx/repo.md | 2 +- packages/api/README.md | 56 +- packages/api/package.json | 8 +- packages/api/src/errors.ts | 40 - packages/api/src/http-types.ts | 62 - packages/api/src/index.ts | 1155 ++++++---- packages/api/src/schemas.ts | 1559 ++++++++++++++ packages/api/src/types.ts | 42 - .../api/src/types/todo/adx/createAccount.ts | 23 + .../api/src/types/todo/adx/createSession.ts | 27 + .../api/src/types/todo/adx/deleteAccount.ts | 27 + .../api/src/types/todo/adx/deleteSession.ts | 27 + packages/api/src/types/todo/adx/getAccount.ts | 27 + packages/api/src/types/todo/adx/getSession.ts | 27 + .../api/src/types/todo/adx/repoBatchWrite.ts | 47 + .../src/types/todo/adx/repoCreateRecord.ts | 31 + .../src/types/todo/adx/repoDeleteRecord.ts | 23 + .../api/src/types/todo/adx/repoDescribe.ts | 30 + .../api/src/types/todo/adx/repoGetRecord.ts | 29 + .../api/src/types/todo/adx/repoListRecords.ts | 34 + .../api/src/types/todo/adx/repoPutRecord.ts | 32 + .../api/src/types/todo/adx/resolveName.ts | 26 + .../api/src/types/todo/adx/syncGetRepo.ts | 23 + .../api/src/types/todo/adx/syncGetRoot.ts | 26 + .../api/src/types/todo/adx/syncUpdateRepo.ts | 22 + packages/api/src/types/todo/social/badge.ts | 31 + packages/api/src/types/todo/social/follow.ts | 9 + .../src/types/todo/social/getFeed.ts} | 31 +- .../api/src/types/todo/social/getLikedBy.ts | 35 + .../src/types/todo/social/getNotifications.ts | 38 + .../src/types/todo/social/getPostThread.ts} | 31 +- .../api/src/types/todo/social/getProfile.ts | 49 + .../src/types/todo/social/getRepostedBy.ts | 35 + .../src/types/todo/social/getUserFollowers.ts | 39 + .../src/types/todo/social/getUserFollows.ts | 39 + packages/api/src/types/todo/social/like.ts | 9 + .../src/types/todo/social/mediaEmbed.ts} | 7 + packages/api/src/types/todo/social/post.ts | 27 + .../src/types/todo/social/profile.ts} | 6 + packages/api/src/types/todo/social/repost.ts | 9 + packages/api/tsconfig.json | 5 +- packages/auth/src/auth-store.ts | 2 +- packages/auth/src/capabilities.ts | 19 +- packages/auth/src/semantics.ts | 28 +- packages/auth/tests/auth.test.ts | 10 +- packages/common/src/network/uri.ts | 30 +- packages/common/tests/uri.test.ts | 95 +- packages/dev-env/README.md | 4 +- packages/dev-env/build.js | 3 +- packages/dev-env/package.json | 1 + packages/dev-env/src/index.ts | 6 +- packages/dev-env/tsconfig.json | 2 + packages/microblog/package.json | 28 - packages/microblog/scripts/_util.js | 23 - packages/microblog/scripts/compile.js | 17 - packages/microblog/scripts/docs.js | 91 - packages/microblog/scripts/ifaces.js | 54 - packages/microblog/scripts/validate.js | 46 - packages/microblog/src/client.ts | 253 --- packages/microblog/src/defs.ts | 1854 ----------------- packages/microblog/src/index.ts | 3 - packages/microblog/src/schemas/Badges.json | 12 - packages/microblog/src/schemas/FeedView.json | 184 -- packages/microblog/src/schemas/Follow.json | 34 - packages/microblog/src/schemas/Follows.json | 12 - packages/microblog/src/schemas/Like.json | 30 - .../microblog/src/schemas/LikedByView.json | 60 - packages/microblog/src/schemas/Likes.json | 12 - .../src/schemas/NotificationsView.json | 50 - packages/microblog/src/schemas/Post.json | 109 - .../microblog/src/schemas/PostThreadView.json | 189 -- packages/microblog/src/schemas/Posts.json | 12 - packages/microblog/src/schemas/Profile.json | 49 - .../microblog/src/schemas/ProfileView.json | 109 - packages/microblog/src/schemas/Profiles.json | 12 - packages/microblog/src/schemas/Repost.json | 30 - .../microblog/src/schemas/RepostedByView.json | 60 - .../src/schemas/UserFollowersView.json | 75 - .../src/schemas/UserFollowsView.json | 75 - packages/microblog/src/types/Badge.ts | 18 - packages/microblog/src/types/Follow.ts | 4 - packages/microblog/src/types/Like.ts | 4 - packages/microblog/src/types/LikedByView.ts | 16 - .../microblog/src/types/NotificationsView.ts | 19 - packages/microblog/src/types/Post.ts | 20 - packages/microblog/src/types/ProfileView.ts | 30 - packages/microblog/src/types/Repost.ts | 4 - .../microblog/src/types/RepostedByView.ts | 16 - .../microblog/src/types/UserFollowersView.ts | 20 - .../microblog/src/types/UserFollowsView.ts | 20 - packages/microblog/src/types/index.ts | 15 - packages/microblog/tsconfig.build.json | 4 - packages/microblog/tsconfig.json | 8 - packages/repo/package.json | 1 + packages/repo/src/collection.ts | 17 +- packages/repo/src/mst/diff.ts | 4 +- packages/repo/src/repo.ts | 12 +- packages/repo/tests/_util.ts | 6 +- packages/repo/tests/repo.test.ts | 2 +- packages/repo/tests/sync.test.ts | 10 +- packages/repo/tsconfig.json | 1 + packages/server/package.json | 2 +- packages/server/src/api/index.ts | 10 + packages/server/src/api/todo/adx/account.ts | 58 + packages/server/src/api/todo/adx/index.ts | 14 + packages/server/src/api/todo/adx/names.ts | 14 + packages/server/src/api/todo/adx/repo.ts | 226 ++ packages/server/src/api/todo/adx/session.ts | 16 + packages/server/src/api/todo/adx/sync.ts | 73 + .../server/src/api/todo/social/getFeed.ts | 152 ++ .../server/src/api/todo/social/getLikedBy.ts | 56 + .../todo/social/getPostThread.ts} | 106 +- .../server/src/api/todo/social/getProfile.ts | 120 ++ .../src/api/todo/social/getRepostedBy.ts | 56 + .../src/api/todo/social/getUserFollowers.ts | 59 + .../src/api/todo/social/getUserFollows.ts | 59 + packages/server/src/api/todo/social/index.ts | 18 + .../src/{db/views => api/todo/social}/util.ts | 6 +- packages/server/src/db/index.ts | 30 +- packages/server/src/db/records/badge.ts | 16 +- packages/server/src/db/records/follow.ts | 16 +- packages/server/src/db/records/like.ts | 17 +- packages/server/src/db/records/post.ts | 16 +- packages/server/src/db/records/profile.ts | 16 +- packages/server/src/db/records/repost.ts | 17 +- packages/server/src/db/schemas.ts | 4 +- packages/server/src/db/types.ts | 14 +- packages/server/src/db/util.ts | 2 +- packages/server/src/db/views/feed.ts | 161 -- packages/server/src/db/views/index.ts | 19 - packages/server/src/db/views/likedBy.ts | 67 - packages/server/src/db/views/profile.ts | 124 -- packages/server/src/db/views/repostedBy.ts | 67 - packages/server/src/db/views/userFollowers.ts | 70 - packages/server/src/db/views/userFollows.ts | 70 - packages/server/src/routes/index.ts | 16 - packages/server/src/routes/v1/account.ts | 74 - packages/server/src/routes/v1/api/index.ts | 10 - packages/server/src/routes/v1/api/repo.ts | 264 --- packages/server/src/routes/v1/api/view.ts | 32 - packages/server/src/routes/v1/data/index.ts | 12 - packages/server/src/routes/v1/data/repo.ts | 61 - packages/server/src/routes/v1/data/root.ts | 23 - .../server/src/routes/v1/data/subscribe.ts | 23 - packages/server/src/routes/v1/session.ts | 28 - packages/server/src/routes/well-known.ts | 13 - packages/server/src/server.ts | 7 +- packages/server/src/xrpc/index.ts | 200 ++ packages/server/src/xrpc/schemas.ts | 1559 ++++++++++++++ .../src/xrpc/types/todo/adx/createAccount.ts | 25 + .../src/xrpc/types/todo/adx/createSession.ts | 29 + .../src/xrpc/types/todo/adx/deleteAccount.ts | 29 + .../src/xrpc/types/todo/adx/deleteSession.ts | 29 + .../src/xrpc/types/todo/adx/getAccount.ts | 29 + .../src/xrpc/types/todo/adx/getSession.ts | 29 + .../src/xrpc/types/todo/adx/repoBatchWrite.ts | 52 + .../xrpc/types/todo/adx/repoCreateRecord.ts | 36 + .../xrpc/types/todo/adx/repoDeleteRecord.ts | 20 + .../src/xrpc/types/todo/adx/repoDescribe.ts | 31 + .../src/xrpc/types/todo/adx/repoGetRecord.ts | 30 + .../xrpc/types/todo/adx/repoListRecords.ts | 35 + .../src/xrpc/types/todo/adx/repoPutRecord.ts | 37 + .../src/xrpc/types/todo/adx/resolveName.ts | 27 + .../src/xrpc/types/todo/adx/syncGetRepo.ts | 24 + .../src/xrpc/types/todo/adx/syncGetRoot.ts | 27 + .../src/xrpc/types/todo/adx/syncUpdateRepo.ts | 22 + .../src/xrpc/types/todo/social/badge.ts | 31 + .../src/xrpc/types/todo/social/follow.ts | 9 + .../src/xrpc/types/todo/social/getFeed.ts | 64 + .../src/xrpc/types/todo/social/getLikedBy.ts | 36 + .../types/todo/social/getNotifications.ts | 39 + .../xrpc/types/todo/social/getPostThread.ts | 64 + .../src/xrpc/types/todo/social/getProfile.ts | 50 + .../xrpc/types/todo/social/getRepostedBy.ts | 36 + .../types/todo/social/getUserFollowers.ts | 40 + .../xrpc/types/todo/social/getUserFollows.ts | 40 + .../server/src/xrpc/types/todo/social/like.ts | 9 + .../src/xrpc/types/todo/social/mediaEmbed.ts | 19 + .../server/src/xrpc/types/todo/social/post.ts | 27 + .../src/xrpc/types/todo/social/profile.ts | 14 + .../src/xrpc/types/todo/social/repost.ts | 9 + packages/server/tests/crud.test.ts | 477 +++-- packages/server/tests/views.test.ts | 466 +++-- packages/server/tsconfig.json | 5 +- packages/xrpc-cli/package.json | 1 - packages/xrpc/jest.config.js | 6 - packages/xrpc/package.json | 1 - schemas/todo.adx/createAccount.json | 18 + schemas/todo.adx/createSession.json | 15 + schemas/todo.adx/deleteAccount.json | 15 + schemas/todo.adx/deleteSession.json | 15 + schemas/todo.adx/getAccount.json | 15 + schemas/todo.adx/getSession.json | 15 + schemas/todo.adx/repoBatchWrite.json | 58 + schemas/todo.adx/repoCreateRecord.json | 26 + schemas/todo.adx/repoDeleteRecord.json | 11 + schemas/todo.adx/repoDescribe.json | 23 + schemas/todo.adx/repoGetRecord.json | 22 + schemas/todo.adx/repoListRecords.json | 34 + schemas/todo.adx/repoPutRecord.json | 26 + schemas/todo.adx/resolveName.json | 19 + schemas/todo.adx/syncGetRepo.json | 13 + schemas/todo.adx/syncGetRoot.json | 19 + schemas/todo.adx/updateRepo.json | 12 + .../todo.social/badge.json | 48 +- schemas/todo.social/follow.json | 13 + schemas/todo.social/getFeed.json | 96 + schemas/todo.social/getLikedBy.json | 37 + schemas/todo.social/getNotifications.json | 46 + schemas/todo.social/getPostThread.json | 95 + schemas/todo.social/getProfile.json | 67 + schemas/todo.social/getRepostedBy.json | 37 + schemas/todo.social/getUserFollowers.json | 49 + schemas/todo.social/getUserFollows.json | 49 + schemas/todo.social/like.json | 12 + .../todo.social/mediaEmbed.json | 31 +- schemas/todo.social/post.json | 44 + schemas/todo.social/profile.json | 28 + schemas/todo.social/repost.json | 12 + tsconfig.json | 1 - yarn.lock | 312 +-- 221 files changed, 8927 insertions(+), 6192 deletions(-) delete mode 100644 packages/api/src/errors.ts delete mode 100644 packages/api/src/http-types.ts create mode 100644 packages/api/src/schemas.ts delete mode 100644 packages/api/src/types.ts create mode 100644 packages/api/src/types/todo/adx/createAccount.ts create mode 100644 packages/api/src/types/todo/adx/createSession.ts create mode 100644 packages/api/src/types/todo/adx/deleteAccount.ts create mode 100644 packages/api/src/types/todo/adx/deleteSession.ts create mode 100644 packages/api/src/types/todo/adx/getAccount.ts create mode 100644 packages/api/src/types/todo/adx/getSession.ts create mode 100644 packages/api/src/types/todo/adx/repoBatchWrite.ts create mode 100644 packages/api/src/types/todo/adx/repoCreateRecord.ts create mode 100644 packages/api/src/types/todo/adx/repoDeleteRecord.ts create mode 100644 packages/api/src/types/todo/adx/repoDescribe.ts create mode 100644 packages/api/src/types/todo/adx/repoGetRecord.ts create mode 100644 packages/api/src/types/todo/adx/repoListRecords.ts create mode 100644 packages/api/src/types/todo/adx/repoPutRecord.ts create mode 100644 packages/api/src/types/todo/adx/resolveName.ts create mode 100644 packages/api/src/types/todo/adx/syncGetRepo.ts create mode 100644 packages/api/src/types/todo/adx/syncGetRoot.ts create mode 100644 packages/api/src/types/todo/adx/syncUpdateRepo.ts create mode 100644 packages/api/src/types/todo/social/badge.ts create mode 100644 packages/api/src/types/todo/social/follow.ts rename packages/{microblog/src/types/FeedView.ts => api/src/types/todo/social/getFeed.ts} (61%) create mode 100644 packages/api/src/types/todo/social/getLikedBy.ts create mode 100644 packages/api/src/types/todo/social/getNotifications.ts rename packages/{microblog/src/types/PostThreadView.ts => api/src/types/todo/social/getPostThread.ts} (60%) create mode 100644 packages/api/src/types/todo/social/getProfile.ts create mode 100644 packages/api/src/types/todo/social/getRepostedBy.ts create mode 100644 packages/api/src/types/todo/social/getUserFollowers.ts create mode 100644 packages/api/src/types/todo/social/getUserFollows.ts create mode 100644 packages/api/src/types/todo/social/like.ts rename packages/{microblog/src/types/EmbeddedMedia.ts => api/src/types/todo/social/mediaEmbed.ts} (62%) create mode 100644 packages/api/src/types/todo/social/post.ts rename packages/{microblog/src/types/Profile.ts => api/src/types/todo/social/profile.ts} (55%) create mode 100644 packages/api/src/types/todo/social/repost.ts delete mode 100644 packages/microblog/package.json delete mode 100644 packages/microblog/scripts/_util.js delete mode 100644 packages/microblog/scripts/compile.js delete mode 100644 packages/microblog/scripts/docs.js delete mode 100644 packages/microblog/scripts/ifaces.js delete mode 100644 packages/microblog/scripts/validate.js delete mode 100644 packages/microblog/src/client.ts delete mode 100644 packages/microblog/src/defs.ts delete mode 100644 packages/microblog/src/index.ts delete mode 100644 packages/microblog/src/schemas/Badges.json delete mode 100644 packages/microblog/src/schemas/FeedView.json delete mode 100644 packages/microblog/src/schemas/Follow.json delete mode 100644 packages/microblog/src/schemas/Follows.json delete mode 100644 packages/microblog/src/schemas/Like.json delete mode 100644 packages/microblog/src/schemas/LikedByView.json delete mode 100644 packages/microblog/src/schemas/Likes.json delete mode 100644 packages/microblog/src/schemas/NotificationsView.json delete mode 100644 packages/microblog/src/schemas/Post.json delete mode 100644 packages/microblog/src/schemas/PostThreadView.json delete mode 100644 packages/microblog/src/schemas/Posts.json delete mode 100644 packages/microblog/src/schemas/Profile.json delete mode 100644 packages/microblog/src/schemas/ProfileView.json delete mode 100644 packages/microblog/src/schemas/Profiles.json delete mode 100644 packages/microblog/src/schemas/Repost.json delete mode 100644 packages/microblog/src/schemas/RepostedByView.json delete mode 100644 packages/microblog/src/schemas/UserFollowersView.json delete mode 100644 packages/microblog/src/schemas/UserFollowsView.json delete mode 100644 packages/microblog/src/types/Badge.ts delete mode 100644 packages/microblog/src/types/Follow.ts delete mode 100644 packages/microblog/src/types/Like.ts delete mode 100644 packages/microblog/src/types/LikedByView.ts delete mode 100644 packages/microblog/src/types/NotificationsView.ts delete mode 100644 packages/microblog/src/types/Post.ts delete mode 100644 packages/microblog/src/types/ProfileView.ts delete mode 100644 packages/microblog/src/types/Repost.ts delete mode 100644 packages/microblog/src/types/RepostedByView.ts delete mode 100644 packages/microblog/src/types/UserFollowersView.ts delete mode 100644 packages/microblog/src/types/UserFollowsView.ts delete mode 100644 packages/microblog/src/types/index.ts delete mode 100644 packages/microblog/tsconfig.build.json delete mode 100644 packages/microblog/tsconfig.json create mode 100644 packages/server/src/api/index.ts create mode 100644 packages/server/src/api/todo/adx/account.ts create mode 100644 packages/server/src/api/todo/adx/index.ts create mode 100644 packages/server/src/api/todo/adx/names.ts create mode 100644 packages/server/src/api/todo/adx/repo.ts create mode 100644 packages/server/src/api/todo/adx/session.ts create mode 100644 packages/server/src/api/todo/adx/sync.ts create mode 100644 packages/server/src/api/todo/social/getFeed.ts create mode 100644 packages/server/src/api/todo/social/getLikedBy.ts rename packages/server/src/{db/views/postThread.ts => api/todo/social/getPostThread.ts} (62%) create mode 100644 packages/server/src/api/todo/social/getProfile.ts create mode 100644 packages/server/src/api/todo/social/getRepostedBy.ts create mode 100644 packages/server/src/api/todo/social/getUserFollowers.ts create mode 100644 packages/server/src/api/todo/social/getUserFollows.ts create mode 100644 packages/server/src/api/todo/social/index.ts rename packages/server/src/{db/views => api/todo/social}/util.ts (83%) delete mode 100644 packages/server/src/db/views/feed.ts delete mode 100644 packages/server/src/db/views/index.ts delete mode 100644 packages/server/src/db/views/likedBy.ts delete mode 100644 packages/server/src/db/views/profile.ts delete mode 100644 packages/server/src/db/views/repostedBy.ts delete mode 100644 packages/server/src/db/views/userFollowers.ts delete mode 100644 packages/server/src/db/views/userFollows.ts delete mode 100644 packages/server/src/routes/index.ts delete mode 100644 packages/server/src/routes/v1/account.ts delete mode 100644 packages/server/src/routes/v1/api/index.ts delete mode 100644 packages/server/src/routes/v1/api/repo.ts delete mode 100644 packages/server/src/routes/v1/api/view.ts delete mode 100644 packages/server/src/routes/v1/data/index.ts delete mode 100644 packages/server/src/routes/v1/data/repo.ts delete mode 100644 packages/server/src/routes/v1/data/root.ts delete mode 100644 packages/server/src/routes/v1/data/subscribe.ts delete mode 100644 packages/server/src/routes/v1/session.ts delete mode 100644 packages/server/src/routes/well-known.ts create mode 100644 packages/server/src/xrpc/index.ts create mode 100644 packages/server/src/xrpc/schemas.ts create mode 100644 packages/server/src/xrpc/types/todo/adx/createAccount.ts create mode 100644 packages/server/src/xrpc/types/todo/adx/createSession.ts create mode 100644 packages/server/src/xrpc/types/todo/adx/deleteAccount.ts create mode 100644 packages/server/src/xrpc/types/todo/adx/deleteSession.ts create mode 100644 packages/server/src/xrpc/types/todo/adx/getAccount.ts create mode 100644 packages/server/src/xrpc/types/todo/adx/getSession.ts create mode 100644 packages/server/src/xrpc/types/todo/adx/repoBatchWrite.ts create mode 100644 packages/server/src/xrpc/types/todo/adx/repoCreateRecord.ts create mode 100644 packages/server/src/xrpc/types/todo/adx/repoDeleteRecord.ts create mode 100644 packages/server/src/xrpc/types/todo/adx/repoDescribe.ts create mode 100644 packages/server/src/xrpc/types/todo/adx/repoGetRecord.ts create mode 100644 packages/server/src/xrpc/types/todo/adx/repoListRecords.ts create mode 100644 packages/server/src/xrpc/types/todo/adx/repoPutRecord.ts create mode 100644 packages/server/src/xrpc/types/todo/adx/resolveName.ts create mode 100644 packages/server/src/xrpc/types/todo/adx/syncGetRepo.ts create mode 100644 packages/server/src/xrpc/types/todo/adx/syncGetRoot.ts create mode 100644 packages/server/src/xrpc/types/todo/adx/syncUpdateRepo.ts create mode 100644 packages/server/src/xrpc/types/todo/social/badge.ts create mode 100644 packages/server/src/xrpc/types/todo/social/follow.ts create mode 100644 packages/server/src/xrpc/types/todo/social/getFeed.ts create mode 100644 packages/server/src/xrpc/types/todo/social/getLikedBy.ts create mode 100644 packages/server/src/xrpc/types/todo/social/getNotifications.ts create mode 100644 packages/server/src/xrpc/types/todo/social/getPostThread.ts create mode 100644 packages/server/src/xrpc/types/todo/social/getProfile.ts create mode 100644 packages/server/src/xrpc/types/todo/social/getRepostedBy.ts create mode 100644 packages/server/src/xrpc/types/todo/social/getUserFollowers.ts create mode 100644 packages/server/src/xrpc/types/todo/social/getUserFollows.ts create mode 100644 packages/server/src/xrpc/types/todo/social/like.ts create mode 100644 packages/server/src/xrpc/types/todo/social/mediaEmbed.ts create mode 100644 packages/server/src/xrpc/types/todo/social/post.ts create mode 100644 packages/server/src/xrpc/types/todo/social/profile.ts create mode 100644 packages/server/src/xrpc/types/todo/social/repost.ts delete mode 100644 packages/xrpc/jest.config.js create mode 100644 schemas/todo.adx/createAccount.json create mode 100644 schemas/todo.adx/createSession.json create mode 100644 schemas/todo.adx/deleteAccount.json create mode 100644 schemas/todo.adx/deleteSession.json create mode 100644 schemas/todo.adx/getAccount.json create mode 100644 schemas/todo.adx/getSession.json create mode 100644 schemas/todo.adx/repoBatchWrite.json create mode 100644 schemas/todo.adx/repoCreateRecord.json create mode 100644 schemas/todo.adx/repoDeleteRecord.json create mode 100644 schemas/todo.adx/repoDescribe.json create mode 100644 schemas/todo.adx/repoGetRecord.json create mode 100644 schemas/todo.adx/repoListRecords.json create mode 100644 schemas/todo.adx/repoPutRecord.json create mode 100644 schemas/todo.adx/resolveName.json create mode 100644 schemas/todo.adx/syncGetRepo.json create mode 100644 schemas/todo.adx/syncGetRoot.json create mode 100644 schemas/todo.adx/updateRepo.json rename packages/microblog/src/schemas/Badge.json => schemas/todo.social/badge.json (52%) create mode 100644 schemas/todo.social/follow.json create mode 100644 schemas/todo.social/getFeed.json create mode 100644 schemas/todo.social/getLikedBy.json create mode 100644 schemas/todo.social/getNotifications.json create mode 100644 schemas/todo.social/getPostThread.json create mode 100644 schemas/todo.social/getProfile.json create mode 100644 schemas/todo.social/getRepostedBy.json create mode 100644 schemas/todo.social/getUserFollowers.json create mode 100644 schemas/todo.social/getUserFollows.json create mode 100644 schemas/todo.social/like.json rename packages/microblog/src/schemas/EmbeddedMedia.json => schemas/todo.social/mediaEmbed.json (50%) create mode 100644 schemas/todo.social/post.json create mode 100644 schemas/todo.social/profile.json create mode 100644 schemas/todo.social/repost.json diff --git a/docs/specs/adx/repo.md b/docs/specs/adx/repo.md index 90db4f8a4a6..3b49c843134 100644 --- a/docs/specs/adx/repo.md +++ b/docs/specs/adx/repo.md @@ -151,7 +151,7 @@ Here is an example schema: { "adx": 1, "id": "com.example.post", - "schema": { + "record": { "type": "object", "required": ["text", "createdAt"], "properties": { diff --git a/packages/api/README.md b/packages/api/README.md index 940301c36d4..3b2da417aff 100644 --- a/packages/api/README.md +++ b/packages/api/README.md @@ -3,43 +3,29 @@ ## Usage ```typescript -import { adx } from '@adxp/api' +import API from '@adxp/api' -// configure the API -adx.configure({ - schemas: [ublogSchema, likeSchema, pollSchema, followSchema, feedViewSchema] -}) - -const alice = adx.user('alice.com') -await alice.collections() // list all of alice's collections -const feed = alice.collection('blueskyweb.xyz:Feed') - -await feed.list('*') // fetch all (apply no validation) -await feed.list('Ublog') // fetch only ublogs -await feed.list(['Ublog', 'Like']) // fetch ublogs and likes -await feed.list(['Ublog', 'Like', '*']) // fetch all, but apply validation on ublogs and likes -await feed.list({type: 'Ublog', ext: 'Poll'}) // fetch only ublogs and support poll extensions - -await feed.get('Ublog', key) // fetch the record and validate as a ublog -await feed.get('*', key) // fetch the record and don't validate - -await feed.create('Ublog', record) // create a record after validating as a ublog -await feed.create({type: 'Ublog', ext: 'Poll'}, record) // create a record after validating as a ublog with the poll extension -await feed.create('*', record) // create a record with no validation - -await feed.put('Ublog', record) // write a record after validating as a ublog -await feed.put({type: 'Ublog', ext: 'Poll'}, record) // write a record after validating as a ublog with the poll extension -await feed.put('*', record) // write a record after no validation +const client = API.service('http://example.com') -await feed.del(record) // delete a record - -await alice.view('FeedView') // fetch the feed view from alice's PDS - -const ublogSchema = adx.schema({type: 'Ublog', ext: 'Poll'}) // create a validator for ublog posts -for (const post in (await alice.view('FeedView')).posts) { - if (ublogSchema.isValid(post)) { - // good to go +// xrpc methods +const res1 = await client.todo.adx.repoCreateRecord( + {did: alice.did, type: 'todo.social.post'}, + { + $type: 'todo.social.post', + text: 'Hello, world!', + createdAt: (new Date()).toISOString() } -} +) +const res2 = await client.todo.adx.repoListRecords({did: alice.did, type: 'todo.social.post'}) + +// repo record methods +const res3 = await client.todo.social.post.create({did: alice.did}, { + text: 'Hello, world!', + createdAt: (new Date()).toISOString() +}) +const res4 = await client.todo.social.post.list({did: alice.did}) ``` +## License + +MIT diff --git a/packages/api/package.json b/packages/api/package.json index c9763611e01..f734f9e6c2a 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -3,6 +3,7 @@ "version": "0.0.1", "main": "src/index.ts", "scripts": { + "codegen": "xrpc-cli gen-api ./src ../../schemas/todo.adx/*", "prettier": "prettier --check src/", "prettier:fix": "prettier --write src/", "lint": "eslint . --ext .ts,.tsx", @@ -14,8 +15,9 @@ }, "license": "MIT", "dependencies": { - "@adxp/auth": "*", - "@adxp/common": "*", - "@adxp/schemas": "*" + "@adxp/xrpc": "*" + }, + "devDependencies": { + "@adxp/xrpc-cli": "*" } } diff --git a/packages/api/src/errors.ts b/packages/api/src/errors.ts deleted file mode 100644 index 78c8cf1c099..00000000000 --- a/packages/api/src/errors.ts +++ /dev/null @@ -1,40 +0,0 @@ -export enum ErrorCode { - NetworkError = 'NetworkError', - DidResolutionFailed = 'DidResolutionFailed', - NameResolutionFailed = 'NameResolutionFailed', -} - -export class NameResolutionFailed extends Error { - code = ErrorCode.NameResolutionFailed - constructor(name: string) { - super(`Failed to resolve name "${name}"`) - } -} - -export class DidResolutionFailed extends Error { - code = ErrorCode.DidResolutionFailed - constructor(did: string) { - super(`Failed to resolve DID "${did}"`) - } -} - -export class WritePermissionError extends Error { - constructor() { - super('No write permissions have been granted for this repo') - } -} - -export class APIResponseError extends Error { - constructor( - public httpStatusCode: number, - public httpStatusText: string, - public httpHeaders?: Record, - public httpResponseBody?: any, - ) { - super(httpResponseBody?.message || `${httpStatusCode} ${httpStatusText}`) - } - - get code(): ErrorCode { - return this.httpResponseBody?.code || 'NetworkError' - } -} diff --git a/packages/api/src/http-types.ts b/packages/api/src/http-types.ts deleted file mode 100644 index 1a9832f3be1..00000000000 --- a/packages/api/src/http-types.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { def as common } from '@adxp/common' -import { def as repo } from '@adxp/repo' -import { z } from 'zod' - -export const getRepoRequest = z.object({ - did: z.string(), - from: common.strToCid.optional(), -}) -export type GetRepoRequest = z.infer - -export const postRepoRequest = z.object({ - did: z.string(), -}) -export type PostRepoRequest = z.infer - -export const describeRepoParams = z.object({ - confirmName: z.boolean().optional(), -}) -export type DescribeRepoParams = z.infer - -export const describeRepoResponse = z.object({ - name: z.string(), - did: z.string(), - didDoc: z.any(), // TODO need full? - collections: z.array(z.string()), - nameIsCorrect: z.boolean().optional(), -}) -export type DescribeRepoResponse = z.infer - -export const listRecordsParams = z.object({ - limit: z.union([z.number(), common.strToInt]).optional(), - before: z.string().optional(), - after: z.string().optional(), - reverse: common.strToBool.optional(), -}) -export type ListRecordsParams = z.infer - -export const listRecordsResponse = z.object({ - records: z.array( - z.object({ - uri: z.string(), - value: z.any(), - }), - ), -}) -export type ListRecordsResponse = z.infer - -export const getRecordResponse = z.object({ - uri: z.string(), - value: z.any(), -}) -export type GetRecordResponse = z.infer - -export const batchWriteParams = z.object({ - writes: z.array(repo.batchWrite), -}) -export type BatchWriteParams = z.infer - -export const createRecordResponse = z.object({ - uri: z.string(), -}) -export type CreateRecordResponse = z.infer diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index bbf6f1971a5..5942e1ba12e 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1,404 +1,817 @@ -import axios, { AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios' -import { AdxUri, resolveName } from '@adxp/common' -import * as auth from '@adxp/auth' -import { AdxSchemas, AdxRecordValidator, AdxViewValidator } from '@adxp/schemas' -import * as t from './types' -import * as err from './errors' -import * as ht from './http-types' - -export type QP = Record | URLSearchParams - -export enum PdsEndpoint { - GetDid, - Account, - Session, - Repo, - RepoCollection, - RepoRecord, - View, -} +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { + Client as XrpcClient, + ServiceClient as XrpcServiceClient, +} from '@adxp/xrpc' +import { methodSchemas, recordSchemas } from './schemas' +import * as TodoAdxCreateAccount from './types/todo/adx/createAccount' +import * as TodoAdxCreateSession from './types/todo/adx/createSession' +import * as TodoAdxDeleteAccount from './types/todo/adx/deleteAccount' +import * as TodoAdxDeleteSession from './types/todo/adx/deleteSession' +import * as TodoAdxGetAccount from './types/todo/adx/getAccount' +import * as TodoAdxGetSession from './types/todo/adx/getSession' +import * as TodoAdxRepoBatchWrite from './types/todo/adx/repoBatchWrite' +import * as TodoAdxRepoCreateRecord from './types/todo/adx/repoCreateRecord' +import * as TodoAdxRepoDeleteRecord from './types/todo/adx/repoDeleteRecord' +import * as TodoAdxRepoDescribe from './types/todo/adx/repoDescribe' +import * as TodoAdxRepoGetRecord from './types/todo/adx/repoGetRecord' +import * as TodoAdxRepoListRecords from './types/todo/adx/repoListRecords' +import * as TodoAdxRepoPutRecord from './types/todo/adx/repoPutRecord' +import * as TodoAdxResolveName from './types/todo/adx/resolveName' +import * as TodoAdxSyncGetRepo from './types/todo/adx/syncGetRepo' +import * as TodoAdxSyncGetRoot from './types/todo/adx/syncGetRoot' +import * as TodoAdxSyncUpdateRepo from './types/todo/adx/syncUpdateRepo' +import * as TodoSocialBadge from './types/todo/social/badge' +import * as TodoSocialFollow from './types/todo/social/follow' +import * as TodoSocialGetFeed from './types/todo/social/getFeed' +import * as TodoSocialGetLikedBy from './types/todo/social/getLikedBy' +import * as TodoSocialGetNotifications from './types/todo/social/getNotifications' +import * as TodoSocialGetPostThread from './types/todo/social/getPostThread' +import * as TodoSocialGetProfile from './types/todo/social/getProfile' +import * as TodoSocialGetRepostedBy from './types/todo/social/getRepostedBy' +import * as TodoSocialGetUserFollowers from './types/todo/social/getUserFollowers' +import * as TodoSocialGetUserFollows from './types/todo/social/getUserFollows' +import * as TodoSocialLike from './types/todo/social/like' +import * as TodoSocialMediaEmbed from './types/todo/social/mediaEmbed' +import * as TodoSocialPost from './types/todo/social/post' +import * as TodoSocialProfile from './types/todo/social/profile' +import * as TodoSocialRepost from './types/todo/social/repost' + +export class Client { + xrpc: XrpcClient = new XrpcClient() + + constructor() { + this.xrpc.addSchemas(methodSchemas) + } -export class AdxClient { - private _mainPds: AdxPdsClient | undefined - - /** - * The default PDS to transact with. Configured with configure(). - */ - get mainPds(): AdxPdsClient { - if (!this._mainPds) { - throw new Error(`No PDS configured`) - } - return this._mainPds - } - - /** - * Schemas used for validating records and views. - */ - schemas = new AdxSchemas() - - constructor(opts: t.AdxClientOpts) { - this.configure(opts) - } - - /** - * Configure the client. - */ - configure(opts: t.AdxClientOpts) { - if (opts.pds) { - this._mainPds = this.pds(opts.pds) - } - if (opts.schemas) { - this.schemas.schemas = new Map() // reset - for (const schema of opts.schemas) { - this.schemas.add(schema) - } - } - if (opts.locale) { - this.schemas.locale = opts.locale - } - } - - /** - * Instantiates an AdxPdsClient object. - */ - pds(url: string) { - return new AdxPdsClient(this, url) - } - - /** - * Instantiates an AdxRepoClient object. - */ - repo(did: string, authStore?: auth.AuthStore): AdxRepoClient { - return this.mainPds.repo(did, authStore) - } - - /** - * Instantiates a validator for one or more schemas. - */ - schema(schema: t.SchemaOpt): AdxRecordValidator { - return getRecordValidator(schema, this) + service(serviceUri: string | URL): ServiceClient { + return new ServiceClient(this, this.xrpc.service(serviceUri)) } } -/** - * ADX API. - */ -export const adx = new AdxClient({}) - -export class AdxPdsClient { - origin: string - private _did: string | undefined - constructor(public client: AdxClient, url: string) { - this.origin = new URL(url).origin - } - - /** - * Instantiates an AdxRepoClient object. - */ - repo(did: string, authStore?: auth.AuthStore): AdxRepoClient { - return new AdxRepoClient(this, did, authStore) - } - - /** - * Registers a repository with a PDS. - */ - async registerRepo(params: t.RegisterRepoParams): Promise { - const reqBody = { - did: params.did, - username: params.username, - } - await axios - .post(this.url(PdsEndpoint.Account), reqBody, requestCfg(params.did)) - .catch(toAPIError) - return new AdxRepoClient(this, params.did) - } - - /** - * Query a view. - */ - async view(view: string, did: string, params: QP) { - const validator = getViewValidator(view, this.client) - // TODO - validate params? - const res = await axios - .get(this.url(PdsEndpoint.View, [view], params), requestCfg(did)) - .catch(toAPIError) - - validator.assertResponseValid(res.data) - return res.data +const defaultInst = new Client() +export default defaultInst + +export class ServiceClient { + _baseClient: Client + xrpc: XrpcServiceClient + todo: TodoNS + + constructor(baseClient: Client, xrpcService: XrpcServiceClient) { + this._baseClient = baseClient + this.xrpc = xrpcService + this.todo = new TodoNS(this) } +} + +export class TodoNS { + _service: ServiceClient + adx: AdxNS + social: SocialNS - /** - * Get the PDS's DID. - */ - async getDid(): Promise { - if (this._did) return this._did - const did = (this._did = await resolveName(new URL(this.origin).hostname)) - return did - } - - /** - * Construct the URL for a known endpoint. - */ - url(endpoint: PdsEndpoint, params?: any[], qp?: QP): string { - let pathname: string - switch (endpoint) { - case PdsEndpoint.GetDid: - if (params?.length) throw new Error('0 URL parameters expected') - pathname = '/.well-known/adx-did' - break - case PdsEndpoint.Account: - if (params?.length) throw new Error('0 URL parameters expected') - pathname = '/.adx/v1/account' - break - case PdsEndpoint.Session: - if (params?.length) throw new Error('0 URL parameters expected') - pathname = '/.adx/v1/session' - break - case PdsEndpoint.Repo: - if (params?.length !== 1) throw new Error('1 URL parameter expected') - pathname = `/.adx/v1/api/repo/${params?.[0]}` - break - case PdsEndpoint.RepoCollection: - if (params?.length !== 2) throw new Error('2 URL parameters expected') - pathname = `/.adx/v1/api/repo/${params?.[0]}/c/${params?.[1]}` - break - case PdsEndpoint.RepoRecord: - if (params?.length !== 3) throw new Error('3 URL parameters expected') - pathname = `/.adx/v1/api/repo/${params?.[0]}/c/${params?.[1]}/r/${params?.[2]}` - break - case PdsEndpoint.View: - if (params?.length !== 1) throw new Error('1 URL parameter expected') - pathname = `/.adx/v1/api/view/${params?.[0]}` - break - default: - throw new Error(`Unsupported endpoint code: ${endpoint}`) - } - let url = this.origin + (pathname || '') - if (qp) { - if (!(qp instanceof URLSearchParams)) { - const safeParams = {} - for (const entry of Object.entries(qp)) { - if (entry[1] !== undefined) { - safeParams[entry[0]] = encodeURIComponent(entry[1]) - } - } - qp = new URLSearchParams(safeParams) - } - url += '?' + qp.toString() - } - return url + constructor(service: ServiceClient) { + this._service = service + this.adx = new AdxNS(service) + this.social = new SocialNS(service) } } -export class AdxRepoClient { - constructor( - public pds: AdxPdsClient, - public did: string, - public authStore?: auth.AuthStore, - ) {} - - get writable() { - return !this.authStore - } - - /** - * Describe the repo. - */ - async describe( - params?: ht.DescribeRepoParams, - ): Promise { - const res = await axios - .get(this.pds.url(PdsEndpoint.Repo, [this.did], params)) - .catch(toAPIError) - return ht.describeRepoResponse.parse(res.data) - } - - /** - * Instantiates a AdxRepoCollectionClient object. - */ - collection(collectionId: string) { - return new AdxRepoCollectionClient(this, collectionId) - } - - /** - * Execute a batch of writes. WARNING: does not validate schemas! - */ - async _batchWrite(writes: t.BatchWrite[]): Promise { - if (!this.writable || !this.authStore) { - throw new err.WritePermissionError() - } - const body = ht.batchWriteParams.parse({ writes }) - await axios - .post(this.pds.url(PdsEndpoint.Repo, [this.did]), body) - .catch(toAPIError) +export class AdxNS { + _service: ServiceClient + + constructor(service: ServiceClient) { + this._service = service + } + + createAccount( + params: TodoAdxCreateAccount.QueryParams, + data?: TodoAdxCreateAccount.InputSchema, + opts?: TodoAdxCreateAccount.CallOptions + ): Promise { + return this._service.xrpc.call('todo.adx.createAccount', params, data, opts) + } + + createSession( + params: TodoAdxCreateSession.QueryParams, + data?: TodoAdxCreateSession.InputSchema, + opts?: TodoAdxCreateSession.CallOptions + ): Promise { + return this._service.xrpc.call('todo.adx.createSession', params, data, opts) + } + + deleteAccount( + params: TodoAdxDeleteAccount.QueryParams, + data?: TodoAdxDeleteAccount.InputSchema, + opts?: TodoAdxDeleteAccount.CallOptions + ): Promise { + return this._service.xrpc.call('todo.adx.deleteAccount', params, data, opts) + } + + deleteSession( + params: TodoAdxDeleteSession.QueryParams, + data?: TodoAdxDeleteSession.InputSchema, + opts?: TodoAdxDeleteSession.CallOptions + ): Promise { + return this._service.xrpc.call('todo.adx.deleteSession', params, data, opts) + } + + getAccount( + params: TodoAdxGetAccount.QueryParams, + data?: TodoAdxGetAccount.InputSchema, + opts?: TodoAdxGetAccount.CallOptions + ): Promise { + return this._service.xrpc.call('todo.adx.getAccount', params, data, opts) + } + + getSession( + params: TodoAdxGetSession.QueryParams, + data?: TodoAdxGetSession.InputSchema, + opts?: TodoAdxGetSession.CallOptions + ): Promise { + return this._service.xrpc.call('todo.adx.getSession', params, data, opts) + } + + repoBatchWrite( + params: TodoAdxRepoBatchWrite.QueryParams, + data?: TodoAdxRepoBatchWrite.InputSchema, + opts?: TodoAdxRepoBatchWrite.CallOptions + ): Promise { + return this._service.xrpc.call( + 'todo.adx.repoBatchWrite', + params, + data, + opts + ) + } + + repoCreateRecord( + params: TodoAdxRepoCreateRecord.QueryParams, + data?: TodoAdxRepoCreateRecord.InputSchema, + opts?: TodoAdxRepoCreateRecord.CallOptions + ): Promise { + return this._service.xrpc.call( + 'todo.adx.repoCreateRecord', + params, + data, + opts + ) + } + + repoDeleteRecord( + params: TodoAdxRepoDeleteRecord.QueryParams, + data?: TodoAdxRepoDeleteRecord.InputSchema, + opts?: TodoAdxRepoDeleteRecord.CallOptions + ): Promise { + return this._service.xrpc.call( + 'todo.adx.repoDeleteRecord', + params, + data, + opts + ) + } + + repoDescribe( + params: TodoAdxRepoDescribe.QueryParams, + data?: TodoAdxRepoDescribe.InputSchema, + opts?: TodoAdxRepoDescribe.CallOptions + ): Promise { + return this._service.xrpc.call('todo.adx.repoDescribe', params, data, opts) + } + + repoGetRecord( + params: TodoAdxRepoGetRecord.QueryParams, + data?: TodoAdxRepoGetRecord.InputSchema, + opts?: TodoAdxRepoGetRecord.CallOptions + ): Promise { + return this._service.xrpc.call('todo.adx.repoGetRecord', params, data, opts) + } + + repoListRecords( + params: TodoAdxRepoListRecords.QueryParams, + data?: TodoAdxRepoListRecords.InputSchema, + opts?: TodoAdxRepoListRecords.CallOptions + ): Promise { + return this._service.xrpc.call( + 'todo.adx.repoListRecords', + params, + data, + opts + ) + } + + repoPutRecord( + params: TodoAdxRepoPutRecord.QueryParams, + data?: TodoAdxRepoPutRecord.InputSchema, + opts?: TodoAdxRepoPutRecord.CallOptions + ): Promise { + return this._service.xrpc.call('todo.adx.repoPutRecord', params, data, opts) + } + + resolveName( + params: TodoAdxResolveName.QueryParams, + data?: TodoAdxResolveName.InputSchema, + opts?: TodoAdxResolveName.CallOptions + ): Promise { + return this._service.xrpc.call('todo.adx.resolveName', params, data, opts) + } + + syncGetRepo( + params: TodoAdxSyncGetRepo.QueryParams, + data?: TodoAdxSyncGetRepo.InputSchema, + opts?: TodoAdxSyncGetRepo.CallOptions + ): Promise { + return this._service.xrpc.call('todo.adx.syncGetRepo', params, data, opts) + } + + syncGetRoot( + params: TodoAdxSyncGetRoot.QueryParams, + data?: TodoAdxSyncGetRoot.InputSchema, + opts?: TodoAdxSyncGetRoot.CallOptions + ): Promise { + return this._service.xrpc.call('todo.adx.syncGetRoot', params, data, opts) + } + + syncUpdateRepo( + params: TodoAdxSyncUpdateRepo.QueryParams, + data?: TodoAdxSyncUpdateRepo.InputSchema, + opts?: TodoAdxSyncUpdateRepo.CallOptions + ): Promise { + return this._service.xrpc.call( + 'todo.adx.syncUpdateRepo', + params, + data, + opts + ) } } -class AdxRepoCollectionClient { - constructor(public repo: AdxRepoClient, public id: string) {} +export class SocialNS { + _service: ServiceClient + badge: BadgeRecord + follow: FollowRecord + like: LikeRecord + mediaEmbed: MediaEmbedRecord + post: PostRecord + profile: ProfileRecord + repost: RepostRecord + + constructor(service: ServiceClient) { + this._service = service + this.badge = new BadgeRecord(service) + this.follow = new FollowRecord(service) + this.like = new LikeRecord(service) + this.mediaEmbed = new MediaEmbedRecord(service) + this.post = new PostRecord(service) + this.profile = new ProfileRecord(service) + this.repost = new RepostRecord(service) + } - /** - * List the records in the repo collection. - */ - async list( - schema: t.SchemaOpt, - params?: ht.ListRecordsParams, - ): Promise { - const url = this.repo.pds.url( - PdsEndpoint.RepoCollection, - [this.repo.did, this.id], + getFeed( + params: TodoSocialGetFeed.QueryParams, + data?: TodoSocialGetFeed.InputSchema, + opts?: TodoSocialGetFeed.CallOptions + ): Promise { + return this._service.xrpc.call('todo.social.getFeed', params, data, opts) + } + + getLikedBy( + params: TodoSocialGetLikedBy.QueryParams, + data?: TodoSocialGetLikedBy.InputSchema, + opts?: TodoSocialGetLikedBy.CallOptions + ): Promise { + return this._service.xrpc.call('todo.social.getLikedBy', params, data, opts) + } + + getNotifications( + params: TodoSocialGetNotifications.QueryParams, + data?: TodoSocialGetNotifications.InputSchema, + opts?: TodoSocialGetNotifications.CallOptions + ): Promise { + return this._service.xrpc.call( + 'todo.social.getNotifications', + params, + data, + opts + ) + } + + getPostThread( + params: TodoSocialGetPostThread.QueryParams, + data?: TodoSocialGetPostThread.InputSchema, + opts?: TodoSocialGetPostThread.CallOptions + ): Promise { + return this._service.xrpc.call( + 'todo.social.getPostThread', + params, + data, + opts + ) + } + + getProfile( + params: TodoSocialGetProfile.QueryParams, + data?: TodoSocialGetProfile.InputSchema, + opts?: TodoSocialGetProfile.CallOptions + ): Promise { + return this._service.xrpc.call('todo.social.getProfile', params, data, opts) + } + + getRepostedBy( + params: TodoSocialGetRepostedBy.QueryParams, + data?: TodoSocialGetRepostedBy.InputSchema, + opts?: TodoSocialGetRepostedBy.CallOptions + ): Promise { + return this._service.xrpc.call( + 'todo.social.getRepostedBy', params, + data, + opts ) - const res = await axios.get(url).catch(toAPIError) - const resSafe = ht.listRecordsResponse.parse(res.data) - if (schema === '*') { - return resSafe - } - const validator = getRecordValidator(schema, this.repo.pds.client) - return { - records: resSafe.records.map((record) => { - const validation = validator.validate(record.value) - return { - uri: record.uri, - value: record.value, - valid: validation.valid, - fullySupported: validation.fullySupported, - compatible: validation.compatible, - error: validation.error, - fallbacks: validation.fallbacks, - } - }), - } - } - - /** - * Get a record in the repo. - */ + } + + getUserFollowers( + params: TodoSocialGetUserFollowers.QueryParams, + data?: TodoSocialGetUserFollowers.InputSchema, + opts?: TodoSocialGetUserFollowers.CallOptions + ): Promise { + return this._service.xrpc.call( + 'todo.social.getUserFollowers', + params, + data, + opts + ) + } + + getUserFollows( + params: TodoSocialGetUserFollows.QueryParams, + data?: TodoSocialGetUserFollows.InputSchema, + opts?: TodoSocialGetUserFollows.CallOptions + ): Promise { + return this._service.xrpc.call( + 'todo.social.getUserFollows', + params, + data, + opts + ) + } +} + +export class BadgeRecord { + _service: ServiceClient + + constructor(service: ServiceClient) { + this._service = service + } + + async list( + params: Omit + ): Promise<{ records: { uri: string, value: TodoSocialBadge.Record }[] }> { + const res = await this._service.xrpc.call('todo.adx.repoListRecords', { + type: 'todo.social.badge', + ...params, + }) + return res.data + } + async get( - schema: t.SchemaOpt, - key: string, - ): Promise { - const url = this.repo.pds.url(PdsEndpoint.RepoRecord, [ - this.repo.did, - this.id, - key, - ]) - const res = await axios.get(url).catch(toAPIError) - const resSafe = ht.getRecordResponse.parse(res.data) - if (schema === '*') { - return resSafe - } - const validator = getRecordValidator(schema, this.repo.pds.client) - const validation = validator.validate(resSafe.value) - return { - uri: resSafe.uri, - value: resSafe.value, - valid: validation.valid, - fullySupported: validation.fullySupported, - compatible: validation.compatible, - error: validation.error, - fallbacks: validation.fallbacks, - } - } - - /** - * Create a new record. - */ + params: Omit + ): Promise<{ uri: string, value: TodoSocialBadge.Record }> { + const res = await this._service.xrpc.call('todo.adx.repoGetRecord', { + type: 'todo.social.badge', + ...params, + }) + return res.data + } + async create( - schema: t.SchemaOpt, - value: any, - validate = true, - ): Promise { - if (!this.repo.writable) { - throw new err.WritePermissionError() - } - if (schema !== '*') { - const validator = getRecordValidator(schema, this.repo.pds.client) - validator.assertValid(value) - } - const url = this.repo.pds.url( - PdsEndpoint.RepoCollection, - [this.repo.did, this.id], - { validate }, + params: Omit, + record: TodoSocialBadge.Record + ): Promise<{ uri: string }> { + record.$type = 'todo.social.badge' + const res = await this._service.xrpc.call( + 'todo.adx.repoCreateRecord', + { type: 'todo.social.badge', ...params }, + record, + { encoding: 'application/json' } ) - const res = await axios.post(url, value).catch(toAPIError) - const { uri } = ht.createRecordResponse.parse(res.data) - return new AdxUri(uri) - } - - /** - * Write the record. - */ - async put(schema: t.SchemaOpt, key: string, value: any, validate = true) { - if (!this.repo.writable) { - throw new err.WritePermissionError() - } - if (schema !== '*') { - const validator = getRecordValidator(schema, this.repo.pds.client) - validator.assertValid(value) - } - const url = this.repo.pds.url( - PdsEndpoint.RepoRecord, - [this.repo.did, this.id, key], - { validate }, + return res.data + } + + async put( + params: Omit, + record: TodoSocialBadge.Record + ): Promise<{ uri: string }> { + record.$type = 'todo.social.badge' + const res = await this._service.xrpc.call( + 'todo.adx.repoPutRecord', + { type: 'todo.social.badge', ...params }, + record, + { encoding: 'application/json' } ) - const res = await axios.put(url, value).catch(toAPIError) - const { uri } = ht.createRecordResponse.parse(res.data) - return new AdxUri(uri) - } - - /** - * Delete the record. - */ - async del(key: string) { - if (!this.repo.writable) { - throw new err.WritePermissionError() - } - const url = this.repo.pds.url( - PdsEndpoint.RepoRecord, - [this.repo.did, this.id, key], - { verified: true }, + return res.data + } + + async delete( + params: Omit + ): Promise { + await this._service.xrpc.call('todo.adx.repoDeleteRecord', { + type: 'todo.social.badge', + ...params, + }) + } +} + +export class FollowRecord { + _service: ServiceClient + + constructor(service: ServiceClient) { + this._service = service + } + + async list( + params: Omit + ): Promise<{ records: { uri: string, value: TodoSocialFollow.Record }[] }> { + const res = await this._service.xrpc.call('todo.adx.repoListRecords', { + type: 'todo.social.follow', + ...params, + }) + return res.data + } + + async get( + params: Omit + ): Promise<{ uri: string, value: TodoSocialFollow.Record }> { + const res = await this._service.xrpc.call('todo.adx.repoGetRecord', { + type: 'todo.social.follow', + ...params, + }) + return res.data + } + + async create( + params: Omit, + record: TodoSocialFollow.Record + ): Promise<{ uri: string }> { + record.$type = 'todo.social.follow' + const res = await this._service.xrpc.call( + 'todo.adx.repoCreateRecord', + { type: 'todo.social.follow', ...params }, + record, + { encoding: 'application/json' } ) - await axios.delete(url).catch(toAPIError) + return res.data + } + + async put( + params: Omit, + record: TodoSocialFollow.Record + ): Promise<{ uri: string }> { + record.$type = 'todo.social.follow' + const res = await this._service.xrpc.call( + 'todo.adx.repoPutRecord', + { type: 'todo.social.follow', ...params }, + record, + { encoding: 'application/json' } + ) + return res.data + } + + async delete( + params: Omit + ): Promise { + await this._service.xrpc.call('todo.adx.repoDeleteRecord', { + type: 'todo.social.follow', + ...params, + }) } } -function requestCfg(did?: string): AxiosRequestConfig { - // @TODO add back in real auth - const headers: Record = {} - if (did) { - headers['Authorization'] = did +export class LikeRecord { + _service: ServiceClient + + constructor(service: ServiceClient) { + this._service = service + } + + async list( + params: Omit + ): Promise<{ records: { uri: string, value: TodoSocialLike.Record }[] }> { + const res = await this._service.xrpc.call('todo.adx.repoListRecords', { + type: 'todo.social.like', + ...params, + }) + return res.data + } + + async get( + params: Omit + ): Promise<{ uri: string, value: TodoSocialLike.Record }> { + const res = await this._service.xrpc.call('todo.adx.repoGetRecord', { + type: 'todo.social.like', + ...params, + }) + return res.data + } + + async create( + params: Omit, + record: TodoSocialLike.Record + ): Promise<{ uri: string }> { + record.$type = 'todo.social.like' + const res = await this._service.xrpc.call( + 'todo.adx.repoCreateRecord', + { type: 'todo.social.like', ...params }, + record, + { encoding: 'application/json' } + ) + return res.data } - return { headers } + async put( + params: Omit, + record: TodoSocialLike.Record + ): Promise<{ uri: string }> { + record.$type = 'todo.social.like' + const res = await this._service.xrpc.call( + 'todo.adx.repoPutRecord', + { type: 'todo.social.like', ...params }, + record, + { encoding: 'application/json' } + ) + return res.data + } + + async delete( + params: Omit + ): Promise { + await this._service.xrpc.call('todo.adx.repoDeleteRecord', { + type: 'todo.social.like', + ...params, + }) + } } -function toAPIError(error: AxiosError): AxiosResponse { - throw new err.APIResponseError( - error.response?.status || 0, - error.response?.statusText || 'Request failed', - error.response?.headers, - error.response?.data, - ) +export class MediaEmbedRecord { + _service: ServiceClient + + constructor(service: ServiceClient) { + this._service = service + } + + async list( + params: Omit + ): Promise<{ + records: { uri: string, value: TodoSocialMediaEmbed.Record }[], + }> { + const res = await this._service.xrpc.call('todo.adx.repoListRecords', { + type: 'todo.social.mediaEmbed', + ...params, + }) + return res.data + } + + async get( + params: Omit + ): Promise<{ uri: string, value: TodoSocialMediaEmbed.Record }> { + const res = await this._service.xrpc.call('todo.adx.repoGetRecord', { + type: 'todo.social.mediaEmbed', + ...params, + }) + return res.data + } + + async create( + params: Omit, + record: TodoSocialMediaEmbed.Record + ): Promise<{ uri: string }> { + record.$type = 'todo.social.mediaEmbed' + const res = await this._service.xrpc.call( + 'todo.adx.repoCreateRecord', + { type: 'todo.social.mediaEmbed', ...params }, + record, + { encoding: 'application/json' } + ) + return res.data + } + + async put( + params: Omit, + record: TodoSocialMediaEmbed.Record + ): Promise<{ uri: string }> { + record.$type = 'todo.social.mediaEmbed' + const res = await this._service.xrpc.call( + 'todo.adx.repoPutRecord', + { type: 'todo.social.mediaEmbed', ...params }, + record, + { encoding: 'application/json' } + ) + return res.data + } + + async delete( + params: Omit + ): Promise { + await this._service.xrpc.call('todo.adx.repoDeleteRecord', { + type: 'todo.social.mediaEmbed', + ...params, + }) + } } -function getRecordValidator(schema: t.SchemaOpt, client: AdxClient) { - return schema instanceof AdxRecordValidator - ? schema - : client.schemas.createRecordValidator(schema) +export class PostRecord { + _service: ServiceClient + + constructor(service: ServiceClient) { + this._service = service + } + + async list( + params: Omit + ): Promise<{ records: { uri: string, value: TodoSocialPost.Record }[] }> { + const res = await this._service.xrpc.call('todo.adx.repoListRecords', { + type: 'todo.social.post', + ...params, + }) + return res.data + } + + async get( + params: Omit + ): Promise<{ uri: string, value: TodoSocialPost.Record }> { + const res = await this._service.xrpc.call('todo.adx.repoGetRecord', { + type: 'todo.social.post', + ...params, + }) + return res.data + } + + async create( + params: Omit, + record: TodoSocialPost.Record + ): Promise<{ uri: string }> { + record.$type = 'todo.social.post' + const res = await this._service.xrpc.call( + 'todo.adx.repoCreateRecord', + { type: 'todo.social.post', ...params }, + record, + { encoding: 'application/json' } + ) + return res.data + } + + async put( + params: Omit, + record: TodoSocialPost.Record + ): Promise<{ uri: string }> { + record.$type = 'todo.social.post' + const res = await this._service.xrpc.call( + 'todo.adx.repoPutRecord', + { type: 'todo.social.post', ...params }, + record, + { encoding: 'application/json' } + ) + return res.data + } + + async delete( + params: Omit + ): Promise { + await this._service.xrpc.call('todo.adx.repoDeleteRecord', { + type: 'todo.social.post', + ...params, + }) + } } -function getViewValidator( - schema: string | AdxViewValidator, - client: AdxClient, -) { - return schema instanceof AdxViewValidator - ? schema - : client.schemas.createViewValidator(schema) +export class ProfileRecord { + _service: ServiceClient + + constructor(service: ServiceClient) { + this._service = service + } + + async list( + params: Omit + ): Promise<{ records: { uri: string, value: TodoSocialProfile.Record }[] }> { + const res = await this._service.xrpc.call('todo.adx.repoListRecords', { + type: 'todo.social.profile', + ...params, + }) + return res.data + } + + async get( + params: Omit + ): Promise<{ uri: string, value: TodoSocialProfile.Record }> { + const res = await this._service.xrpc.call('todo.adx.repoGetRecord', { + type: 'todo.social.profile', + ...params, + }) + return res.data + } + + async create( + params: Omit, + record: TodoSocialProfile.Record + ): Promise<{ uri: string }> { + record.$type = 'todo.social.profile' + const res = await this._service.xrpc.call( + 'todo.adx.repoCreateRecord', + { type: 'todo.social.profile', ...params }, + record, + { encoding: 'application/json' } + ) + return res.data + } + + async put( + params: Omit, + record: TodoSocialProfile.Record + ): Promise<{ uri: string }> { + record.$type = 'todo.social.profile' + const res = await this._service.xrpc.call( + 'todo.adx.repoPutRecord', + { type: 'todo.social.profile', ...params }, + record, + { encoding: 'application/json' } + ) + return res.data + } + + async delete( + params: Omit + ): Promise { + await this._service.xrpc.call('todo.adx.repoDeleteRecord', { + type: 'todo.social.profile', + ...params, + }) + } } -export * as did from '@adxp/did-sdk' -export { resolveName, AdxUri } from '@adxp/common' -export * from './types' -export * from './http-types' -export * from './errors' +export class RepostRecord { + _service: ServiceClient + + constructor(service: ServiceClient) { + this._service = service + } + + async list( + params: Omit + ): Promise<{ records: { uri: string, value: TodoSocialRepost.Record }[] }> { + const res = await this._service.xrpc.call('todo.adx.repoListRecords', { + type: 'todo.social.repost', + ...params, + }) + return res.data + } + + async get( + params: Omit + ): Promise<{ uri: string, value: TodoSocialRepost.Record }> { + const res = await this._service.xrpc.call('todo.adx.repoGetRecord', { + type: 'todo.social.repost', + ...params, + }) + return res.data + } + + async create( + params: Omit, + record: TodoSocialRepost.Record + ): Promise<{ uri: string }> { + record.$type = 'todo.social.repost' + const res = await this._service.xrpc.call( + 'todo.adx.repoCreateRecord', + { type: 'todo.social.repost', ...params }, + record, + { encoding: 'application/json' } + ) + return res.data + } + + async put( + params: Omit, + record: TodoSocialRepost.Record + ): Promise<{ uri: string }> { + record.$type = 'todo.social.repost' + const res = await this._service.xrpc.call( + 'todo.adx.repoPutRecord', + { type: 'todo.social.repost', ...params }, + record, + { encoding: 'application/json' } + ) + return res.data + } + + async delete( + params: Omit + ): Promise { + await this._service.xrpc.call('todo.adx.repoDeleteRecord', { + type: 'todo.social.repost', + ...params, + }) + } +} diff --git a/packages/api/src/schemas.ts b/packages/api/src/schemas.ts new file mode 100644 index 00000000000..67b1ff0c6d7 --- /dev/null +++ b/packages/api/src/schemas.ts @@ -0,0 +1,1559 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { MethodSchema } from '@adxp/xrpc' +import { AdxSchemaDefinition } from '@adxp/schemas' + +export const methodSchemas: MethodSchema[] = [ + { + xrpc: 1, + id: 'todo.adx.createAccount', + type: 'procedure', + description: 'Create an account.', + parameters: {}, + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['username', 'did'], + properties: { + username: { + type: 'string', + }, + did: { + type: 'string', + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.adx.createSession', + type: 'procedure', + description: 'Create an authentication session.', + parameters: {}, + input: { + encoding: '', + schema: {}, + }, + output: { + encoding: '', + schema: {}, + }, + }, + { + xrpc: 1, + id: 'todo.adx.deleteAccount', + type: 'procedure', + description: 'Delete an account.', + parameters: {}, + input: { + encoding: '', + schema: {}, + }, + output: { + encoding: '', + schema: {}, + }, + }, + { + xrpc: 1, + id: 'todo.adx.deleteSession', + type: 'procedure', + description: 'Delete the current session.', + parameters: {}, + input: { + encoding: '', + schema: {}, + }, + output: { + encoding: '', + schema: {}, + }, + }, + { + xrpc: 1, + id: 'todo.adx.getAccount', + type: 'query', + description: 'Get information about an account.', + parameters: {}, + input: { + encoding: '', + schema: {}, + }, + output: { + encoding: '', + schema: {}, + }, + }, + { + xrpc: 1, + id: 'todo.adx.getSession', + type: 'query', + description: 'Get information about the current session.', + parameters: {}, + input: { + encoding: '', + schema: {}, + }, + output: { + encoding: '', + schema: {}, + }, + }, + { + xrpc: 1, + id: 'todo.adx.repoBatchWrite', + type: 'procedure', + description: 'Apply a batch transaction of creates, puts, and deletes.', + parameters: { + did: { + type: 'string', + description: 'The DID of the repo.', + required: true, + }, + validate: { + type: 'boolean', + description: 'Validate the records?', + default: true, + }, + }, + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['writes'], + properties: { + writes: { + type: 'array', + items: { + oneOf: [ + { + type: 'object', + required: ['action', 'collection', 'value'], + properties: { + action: { + type: 'string', + const: 'create', + }, + collection: { + type: 'string', + }, + value: {}, + }, + }, + { + type: 'object', + required: ['action', 'collection', 'tid', 'value'], + properties: { + action: { + type: 'string', + const: 'update', + }, + collection: { + type: 'string', + }, + tid: { + type: 'string', + }, + value: {}, + }, + }, + { + type: 'object', + required: ['action', 'collection', 'tid'], + properties: { + action: { + type: 'string', + const: 'delete', + }, + collection: { + type: 'string', + }, + tid: { + type: 'string', + }, + }, + }, + ], + }, + }, + }, + }, + }, + output: { + encoding: 'application/json', + schema: {}, + }, + }, + { + xrpc: 1, + id: 'todo.adx.repoCreateRecord', + type: 'procedure', + description: 'Create a new record.', + parameters: { + did: { + type: 'string', + description: 'The DID of the repo.', + required: true, + }, + type: { + type: 'string', + description: 'The NSID of the record type.', + required: true, + }, + validate: { + type: 'boolean', + description: 'Validate the record?', + default: true, + }, + }, + input: { + encoding: 'application/json', + schema: {}, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['uri'], + properties: { + uri: { + type: 'string', + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.adx.repoDeleteRecord', + type: 'procedure', + description: 'Delete a record.', + parameters: { + did: { + type: 'string', + description: 'The DID of the repo.', + required: true, + }, + type: { + type: 'string', + description: 'The NSID of the record type.', + required: true, + }, + tid: { + type: 'string', + description: 'The TID of the record.', + required: true, + }, + }, + }, + { + xrpc: 1, + id: 'todo.adx.repoDescribe', + type: 'query', + description: + 'Get information about the repo, including the list of collections.', + parameters: { + nameOrDid: { + type: 'string', + description: 'The username or DID of the repo.', + required: true, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['name', 'did', 'didDoc', 'collections', 'nameIsCorrect'], + properties: { + name: { + type: 'string', + }, + did: { + type: 'string', + }, + didDoc: { + type: 'object', + }, + collections: { + type: 'array', + items: { + type: 'string', + }, + }, + nameIsCorrect: { + type: 'boolean', + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.adx.repoGetRecord', + type: 'query', + description: 'Fetch a record.', + parameters: { + nameOrDid: { + type: 'string', + description: 'The name or DID of the repo.', + required: true, + }, + type: { + type: 'string', + description: 'The NSID of the record type.', + required: true, + }, + tid: { + type: 'string', + description: 'The TID of the record.', + required: true, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['uri', 'value'], + properties: { + uri: { + type: 'string', + }, + value: { + type: 'object', + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.adx.repoListRecords', + type: 'query', + description: 'List a range of records in a collection.', + parameters: { + nameOrDid: { + type: 'string', + description: 'The username or DID of the repo.', + required: true, + }, + type: { + type: 'string', + description: 'The NSID of the record type.', + required: true, + }, + limit: { + type: 'number', + description: 'The number of records to return. TODO-max number?', + default: 50, + minimum: 1, + }, + before: { + type: 'string', + description: 'A TID to filter the range of records returned.', + }, + after: { + type: 'string', + description: 'A TID to filter the range of records returned.', + }, + reverse: { + type: 'boolean', + description: 'Reverse the order of the returned records?', + default: false, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['records'], + properties: { + records: { + type: 'array', + items: { + type: 'object', + required: ['uri', 'value'], + properties: { + uri: { + type: 'string', + }, + value: { + type: 'object', + }, + }, + }, + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.adx.repoPutRecord', + type: 'procedure', + description: 'Write a record.', + parameters: { + did: { + type: 'string', + description: 'The DID of the repo.', + required: true, + }, + type: { + type: 'string', + description: 'The NSID of the record type.', + required: true, + }, + tid: { + type: 'string', + description: 'The TID of the record.', + required: true, + }, + validate: { + type: 'boolean', + description: 'Validate the record?', + default: true, + }, + }, + input: { + encoding: 'application/json', + schema: {}, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['uri'], + properties: { + uri: { + type: 'string', + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.adx.resolveName', + type: 'query', + description: + 'Provides the DID of the repo indicated by the Host parameter.', + parameters: { + name: { + type: 'string', + description: 'The name to resolve.', + required: true, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['did'], + properties: { + did: { + type: 'string', + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.adx.syncGetRepo', + type: 'query', + description: 'Gets the repo state.', + parameters: { + did: { + type: 'string', + description: 'The DID of the repo.', + required: true, + }, + from: { + type: 'string', + description: 'A past commit CID', + }, + }, + output: { + encoding: 'application/cbor', + }, + }, + { + xrpc: 1, + id: 'todo.adx.syncGetRoot', + type: 'query', + description: 'Gets the current root CID of a repo.', + parameters: { + did: { + type: 'string', + description: 'The DID of the repo.', + required: true, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['root'], + properties: { + root: { + type: 'string', + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.adx.syncUpdateRepo', + type: 'procedure', + description: 'Writes commits to a repo.', + parameters: { + did: { + type: 'string', + description: 'The DID of the repo.', + required: true, + }, + }, + input: { + encoding: 'application/cbor', + }, + }, + { + xrpc: 1, + id: 'todo.social.getFeed', + type: 'query', + description: "A computed view of the home feed or a user's feed", + parameters: { + author: { + type: 'string', + }, + limit: { + type: 'number', + maximum: 100, + }, + before: { + type: 'string', + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['feed'], + properties: { + feed: { + type: 'array', + items: { + $ref: '#/$defs/feedItem', + }, + }, + }, + $defs: { + feedItem: { + type: 'object', + required: [ + 'uri', + 'author', + 'record', + 'replyCount', + 'repostCount', + 'likeCount', + 'indexedAt', + ], + properties: { + uri: { + type: 'string', + }, + author: { + $ref: '#/$defs/user', + }, + repostedBy: { + $ref: '#/$defs/user', + }, + record: { + type: 'object', + }, + embed: { + oneOf: [ + { + $ref: '#/$defs/recordEmbed', + }, + { + $ref: '#/$defs/externalEmbed', + }, + { + $ref: '#/$defs/unknownEmbed', + }, + ], + }, + replyCount: { + type: 'number', + }, + repostCount: { + type: 'number', + }, + likeCount: { + type: 'number', + }, + indexedAt: { + type: 'string', + format: 'date-time', + }, + myState: { + type: 'object', + properties: { + repost: { + type: 'string', + }, + like: { + type: 'string', + }, + }, + }, + }, + }, + user: { + type: 'object', + required: ['did', 'name'], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + }, + }, + recordEmbed: { + type: 'object', + required: ['type', 'author', 'record'], + properties: { + type: { + const: 'record', + }, + author: { + $ref: '#/$defs/user', + }, + record: { + type: 'object', + }, + }, + }, + externalEmbed: { + type: 'object', + required: ['type', 'uri', 'title', 'description', 'imageUri'], + properties: { + type: { + const: 'external', + }, + uri: { + type: 'string', + }, + title: { + type: 'string', + }, + description: { + type: 'string', + }, + imageUri: { + type: 'string', + }, + }, + }, + unknownEmbed: { + type: 'object', + required: ['type'], + properties: { + type: { + type: 'string', + not: { + enum: ['record', 'external'], + }, + }, + }, + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.social.getLikedBy', + type: 'query', + parameters: { + uri: { + type: 'string', + required: true, + }, + limit: { + type: 'number', + maximum: 100, + }, + before: { + type: 'string', + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['uri', 'likedBy'], + properties: { + uri: { + type: 'string', + }, + likedBy: { + type: 'array', + items: { + type: 'object', + required: ['did', 'name', 'indexedAt'], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + indexedAt: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.social.getNotifications', + type: 'query', + parameters: { + limit: { + type: 'number', + maximum: 100, + }, + before: { + type: 'string', + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['notifications'], + properties: { + notifications: { + type: 'array', + items: { + $ref: '#/$defs/notification', + }, + }, + }, + $defs: { + notification: { + type: 'object', + required: ['uri', 'author', 'record', 'isRead', 'indexedAt'], + properties: { + uri: { + type: 'string', + format: 'uri', + }, + author: { + type: 'object', + required: ['did', 'name', 'displayName'], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + }, + }, + record: { + type: 'object', + }, + isRead: { + type: 'boolean', + }, + indexedAt: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.social.getPostThread', + type: 'query', + parameters: { + uri: { + type: 'string', + required: true, + }, + depth: { + type: 'number', + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['thread'], + properties: { + thread: { + $ref: '#/$defs/post', + }, + }, + $defs: { + post: { + type: 'object', + required: [ + 'uri', + 'author', + 'record', + 'replyCount', + 'likeCount', + 'repostCount', + 'indexedAt', + ], + properties: { + uri: { + type: 'string', + }, + author: { + $ref: '#/$defs/user', + }, + record: { + type: 'object', + }, + embed: { + oneOf: [ + { + $ref: '#/$defs/recordEmbed', + }, + { + $ref: '#/$defs/externalEmbed', + }, + { + $ref: '#/$defs/unknownEmbed', + }, + ], + }, + parent: { + $ref: '#/$defs/post', + }, + replyCount: { + type: 'number', + }, + replies: { + type: 'array', + items: { + $ref: '#/$defs/post', + }, + }, + likeCount: { + type: 'number', + }, + repostCount: { + type: 'number', + }, + indexedAt: { + type: 'string', + format: 'date-time', + }, + myState: { + type: 'object', + properties: { + repost: { + type: 'string', + }, + like: { + type: 'string', + }, + }, + }, + }, + }, + user: { + type: 'object', + required: ['did', 'name'], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + }, + }, + recordEmbed: { + type: 'object', + required: ['type', 'author', 'record'], + properties: { + type: { + const: 'record', + }, + author: { + $ref: '#/$defs/user', + }, + record: { + type: 'object', + }, + }, + }, + externalEmbed: { + type: 'object', + required: ['type', 'uri', 'title', 'description', 'imageUri'], + properties: { + type: { + const: 'external', + }, + uri: { + type: 'string', + }, + title: { + type: 'string', + }, + description: { + type: 'string', + }, + imageUri: { + type: 'string', + }, + }, + }, + unknownEmbed: { + type: 'object', + required: ['type'], + properties: { + type: { + type: 'string', + not: { + enum: ['record', 'external'], + }, + }, + }, + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.social.getProfile', + type: 'query', + parameters: { + user: { + type: 'string', + required: true, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: [ + 'did', + 'name', + 'followersCount', + 'followsCount', + 'postsCount', + 'badges', + ], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + description: { + type: 'string', + maxLength: 256, + }, + followersCount: { + type: 'number', + }, + followsCount: { + type: 'number', + }, + postsCount: { + type: 'number', + }, + badges: { + type: 'array', + items: { + $ref: '#/$defs/badge', + }, + }, + myState: { + type: 'object', + properties: { + follow: { + type: 'string', + }, + }, + }, + }, + $defs: { + badge: { + type: 'object', + required: ['uri'], + properties: { + uri: { + type: 'string', + }, + error: { + type: 'string', + }, + issuer: { + type: 'object', + required: ['did', 'name', 'displayName'], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + }, + }, + assertion: { + type: 'object', + required: ['type'], + properties: { + type: { + type: 'string', + }, + }, + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.social.getRepostedBy', + type: 'query', + parameters: { + uri: { + type: 'string', + required: true, + }, + limit: { + type: 'number', + maximum: 100, + }, + before: { + type: 'string', + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['uri', 'repostedBy'], + properties: { + uri: { + type: 'string', + }, + repostedBy: { + type: 'array', + items: { + type: 'object', + required: ['did', 'name', 'indexedAt'], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + indexedAt: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.social.getUserFollowers', + type: 'query', + description: 'Who is following a user?', + parameters: { + user: { + type: 'string', + required: true, + }, + limit: { + type: 'number', + maximum: 100, + }, + before: { + type: 'string', + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['subject', 'followers'], + properties: { + subject: { + type: 'object', + required: ['did', 'name'], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + }, + }, + followers: { + type: 'array', + items: { + type: 'object', + required: ['did', 'name', 'indexedAt'], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + indexedAt: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.social.getUserFollows', + type: 'query', + description: 'Who is a user following?', + parameters: { + user: { + type: 'string', + required: true, + }, + limit: { + type: 'number', + maximum: 100, + }, + before: { + type: 'string', + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['subject', 'follows'], + properties: { + subject: { + type: 'object', + required: ['did', 'name'], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + }, + }, + follows: { + type: 'array', + items: { + type: 'object', + required: ['did', 'name', 'indexedAt'], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + indexedAt: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, + }, + }, + }, + }, +] +export const recordSchemas: AdxSchemaDefinition[] = [ + { + adx: 1, + id: 'todo.social.badge', + description: 'An assertion about the subject by this user.', + record: { + type: 'object', + required: ['assertion', 'subject', 'createdAt'], + properties: { + assertion: { + oneOf: [ + { + $ref: '#/$defs/inviteAssertion', + }, + { + $ref: '#/$defs/employeeAssertion', + }, + { + $ref: '#/$defs/tagAssertion', + }, + { + $ref: '#/$defs/unknownAssertion', + }, + ], + }, + subject: { + type: 'string', + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + }, + $defs: { + inviteAssertion: { + type: 'object', + required: ['type'], + properties: { + type: { + const: 'invite', + }, + }, + }, + employeeAssertion: { + type: 'object', + required: ['type'], + properties: { + type: { + const: 'employee', + }, + }, + }, + tagAssertion: { + type: 'object', + required: ['type', 'tag'], + properties: { + type: { + const: 'tag', + }, + tag: { + type: 'string', + maxLength: 64, + }, + }, + }, + unknownAssertion: { + type: 'object', + required: ['type'], + properties: { + type: { + type: 'string', + not: { + enum: ['invite', 'employee', 'tag'], + }, + }, + }, + }, + }, + }, + }, + { + adx: 1, + id: 'todo.social.follow', + description: 'A social follow', + record: { + type: 'object', + required: ['subject', 'createdAt'], + properties: { + subject: { + type: 'string', + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, + { + adx: 1, + id: 'todo.social.like', + record: { + type: 'object', + required: ['subject', 'createdAt'], + properties: { + subject: { + type: 'string', + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, + { + adx: 1, + id: 'todo.social.mediaEmbed', + description: 'A list of media embedded in a post or document.', + record: { + type: 'object', + required: ['media'], + properties: { + media: { + type: 'array', + items: { + $ref: '#/$defs/mediaEmbed', + }, + }, + }, + $defs: { + mediaEmbed: { + type: 'object', + required: ['original'], + properties: { + alt: { + type: 'string', + }, + thumb: { + $ref: '#/$defs/mediaEmbedBlob', + }, + original: { + $ref: '#/$defs/mediaEmbedBlob', + }, + }, + }, + mediaEmbedBlob: { + type: 'object', + required: ['mimeType', 'blobId'], + properties: { + mimeType: { + type: 'string', + }, + blobId: { + type: 'string', + }, + }, + }, + }, + }, + }, + { + adx: 1, + id: 'todo.social.post', + record: { + type: 'object', + required: ['text', 'createdAt'], + properties: { + text: { + type: 'string', + maxLength: 256, + }, + entities: { + $ref: '#/$defs/entity', + }, + reply: { + type: 'object', + required: ['root'], + properties: { + root: { + type: 'string', + }, + parent: { + type: 'string', + }, + }, + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + }, + $defs: { + entity: { + type: 'array', + items: { + type: 'object', + required: ['index', 'type', 'value'], + properties: { + index: { + $ref: '#/$defs/textSlice', + }, + type: { + type: 'string', + $comment: + "Expected values are 'mention', 'hashtag', and 'link'.", + }, + value: { + type: 'string', + }, + }, + }, + }, + textSlice: { + type: 'array', + items: [ + { + type: 'number', + }, + { + type: 'number', + }, + ], + minItems: 2, + maxItems: 2, + }, + }, + }, + }, + { + adx: 1, + id: 'todo.social.profile', + record: { + type: 'object', + required: ['displayName'], + properties: { + displayName: { + type: 'string', + maxLength: 64, + }, + description: { + type: 'string', + maxLength: 256, + }, + badges: { + type: 'array', + items: { + $ref: '#/$defs/badgeRef', + }, + }, + }, + $defs: { + badgeRef: { + type: 'object', + required: ['uri'], + properties: { + uri: { + type: 'string', + }, + }, + }, + }, + }, + }, + { + adx: 1, + id: 'todo.social.repost', + record: { + type: 'object', + required: ['subject', 'createdAt'], + properties: { + subject: { + type: 'string', + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, +] diff --git a/packages/api/src/types.ts b/packages/api/src/types.ts deleted file mode 100644 index 3e746bbf6a3..00000000000 --- a/packages/api/src/types.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { - AdxRecordValidator, - AdxRecordValidatorDescription, -} from '@adxp/schemas' -import { GetRecordResponse } from './http-types.js' - -export type SchemaOpt = - | string - | string[] - | AdxRecordValidator - | AdxRecordValidatorDescription - | '*' - -export interface AdxClientOpts { - pds?: string - locale?: string - schemas?: any[] -} - -export interface RegisterRepoParams { - did: string - username: string -} - -export interface GetRecordResponseValidated extends GetRecordResponse { - valid?: boolean - fullySupported?: boolean - compatible?: boolean - error?: string | undefined - fallbacks?: string[] | undefined -} - -export interface ListRecordsResponseValidated { - records: GetRecordResponseValidated[] -} - -export interface BatchWrite { - action: 'create' | 'put' | 'del' - collection: string - key?: string - value?: any -} diff --git a/packages/api/src/types/todo/adx/createAccount.ts b/packages/api/src/types/todo/adx/createAccount.ts new file mode 100644 index 00000000000..a5b8073586e --- /dev/null +++ b/packages/api/src/types/todo/adx/createAccount.ts @@ -0,0 +1,23 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams {} + +export interface CallOptions { + headers?: Headers; + encoding: 'application/json'; +} + +export interface InputSchema { + username: string; + did: string; +} + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; +} diff --git a/packages/api/src/types/todo/adx/createSession.ts b/packages/api/src/types/todo/adx/createSession.ts new file mode 100644 index 00000000000..a58fc176f33 --- /dev/null +++ b/packages/api/src/types/todo/adx/createSession.ts @@ -0,0 +1,27 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams {} + +export interface CallOptions { + headers?: Headers; + encoding: ''; +} + +export interface InputSchema { + [k: string]: unknown; +} + +export interface OutputSchema { + [k: string]: unknown; +} + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: OutputSchema; +} diff --git a/packages/api/src/types/todo/adx/deleteAccount.ts b/packages/api/src/types/todo/adx/deleteAccount.ts new file mode 100644 index 00000000000..a58fc176f33 --- /dev/null +++ b/packages/api/src/types/todo/adx/deleteAccount.ts @@ -0,0 +1,27 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams {} + +export interface CallOptions { + headers?: Headers; + encoding: ''; +} + +export interface InputSchema { + [k: string]: unknown; +} + +export interface OutputSchema { + [k: string]: unknown; +} + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: OutputSchema; +} diff --git a/packages/api/src/types/todo/adx/deleteSession.ts b/packages/api/src/types/todo/adx/deleteSession.ts new file mode 100644 index 00000000000..a58fc176f33 --- /dev/null +++ b/packages/api/src/types/todo/adx/deleteSession.ts @@ -0,0 +1,27 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams {} + +export interface CallOptions { + headers?: Headers; + encoding: ''; +} + +export interface InputSchema { + [k: string]: unknown; +} + +export interface OutputSchema { + [k: string]: unknown; +} + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: OutputSchema; +} diff --git a/packages/api/src/types/todo/adx/getAccount.ts b/packages/api/src/types/todo/adx/getAccount.ts new file mode 100644 index 00000000000..a58fc176f33 --- /dev/null +++ b/packages/api/src/types/todo/adx/getAccount.ts @@ -0,0 +1,27 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams {} + +export interface CallOptions { + headers?: Headers; + encoding: ''; +} + +export interface InputSchema { + [k: string]: unknown; +} + +export interface OutputSchema { + [k: string]: unknown; +} + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: OutputSchema; +} diff --git a/packages/api/src/types/todo/adx/getSession.ts b/packages/api/src/types/todo/adx/getSession.ts new file mode 100644 index 00000000000..a58fc176f33 --- /dev/null +++ b/packages/api/src/types/todo/adx/getSession.ts @@ -0,0 +1,27 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams {} + +export interface CallOptions { + headers?: Headers; + encoding: ''; +} + +export interface InputSchema { + [k: string]: unknown; +} + +export interface OutputSchema { + [k: string]: unknown; +} + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: OutputSchema; +} diff --git a/packages/api/src/types/todo/adx/repoBatchWrite.ts b/packages/api/src/types/todo/adx/repoBatchWrite.ts new file mode 100644 index 00000000000..4f2a6ee21a3 --- /dev/null +++ b/packages/api/src/types/todo/adx/repoBatchWrite.ts @@ -0,0 +1,47 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams { + did: string; + validate?: boolean; +} + +export interface CallOptions { + headers?: Headers; + encoding: 'application/json'; +} + +export interface InputSchema { + writes: ( + | { + action: 'create', + collection: string, + value: unknown, + } + | { + action: 'update', + collection: string, + tid: string, + value: unknown, + } + | { + action: 'delete', + collection: string, + tid: string, + } + )[]; +} + +export interface OutputSchema { + [k: string]: unknown; +} + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: OutputSchema; +} diff --git a/packages/api/src/types/todo/adx/repoCreateRecord.ts b/packages/api/src/types/todo/adx/repoCreateRecord.ts new file mode 100644 index 00000000000..0c9b9b7d6f0 --- /dev/null +++ b/packages/api/src/types/todo/adx/repoCreateRecord.ts @@ -0,0 +1,31 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams { + did: string; + type: string; + validate?: boolean; +} + +export interface CallOptions { + headers?: Headers; + encoding: 'application/json'; +} + +export interface InputSchema { + [k: string]: unknown; +} + +export interface OutputSchema { + uri: string; +} + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: OutputSchema; +} diff --git a/packages/api/src/types/todo/adx/repoDeleteRecord.ts b/packages/api/src/types/todo/adx/repoDeleteRecord.ts new file mode 100644 index 00000000000..d7ca255b4fa --- /dev/null +++ b/packages/api/src/types/todo/adx/repoDeleteRecord.ts @@ -0,0 +1,23 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams { + did: string; + type: string; + tid: string; +} + +export interface CallOptions { + headers?: Headers; +} + +export type InputSchema = undefined + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; +} diff --git a/packages/api/src/types/todo/adx/repoDescribe.ts b/packages/api/src/types/todo/adx/repoDescribe.ts new file mode 100644 index 00000000000..83dcfd79136 --- /dev/null +++ b/packages/api/src/types/todo/adx/repoDescribe.ts @@ -0,0 +1,30 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams { + nameOrDid: string; +} + +export interface CallOptions { + headers?: Headers; +} + +export type InputSchema = undefined + +export interface OutputSchema { + name: string; + did: string; + didDoc: {}; + collections: string[]; + nameIsCorrect: boolean; +} + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: OutputSchema; +} diff --git a/packages/api/src/types/todo/adx/repoGetRecord.ts b/packages/api/src/types/todo/adx/repoGetRecord.ts new file mode 100644 index 00000000000..0d1da520f49 --- /dev/null +++ b/packages/api/src/types/todo/adx/repoGetRecord.ts @@ -0,0 +1,29 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams { + nameOrDid: string; + type: string; + tid: string; +} + +export interface CallOptions { + headers?: Headers; +} + +export type InputSchema = undefined + +export interface OutputSchema { + uri: string; + value: {}; +} + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: OutputSchema; +} diff --git a/packages/api/src/types/todo/adx/repoListRecords.ts b/packages/api/src/types/todo/adx/repoListRecords.ts new file mode 100644 index 00000000000..20d028a3698 --- /dev/null +++ b/packages/api/src/types/todo/adx/repoListRecords.ts @@ -0,0 +1,34 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams { + nameOrDid: string; + type: string; + limit?: number; + before?: string; + after?: string; + reverse?: boolean; +} + +export interface CallOptions { + headers?: Headers; +} + +export type InputSchema = undefined + +export interface OutputSchema { + records: { + uri: string, + value: {}, + }[]; +} + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: OutputSchema; +} diff --git a/packages/api/src/types/todo/adx/repoPutRecord.ts b/packages/api/src/types/todo/adx/repoPutRecord.ts new file mode 100644 index 00000000000..94a98b6595b --- /dev/null +++ b/packages/api/src/types/todo/adx/repoPutRecord.ts @@ -0,0 +1,32 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams { + did: string; + type: string; + tid: string; + validate?: boolean; +} + +export interface CallOptions { + headers?: Headers; + encoding: 'application/json'; +} + +export interface InputSchema { + [k: string]: unknown; +} + +export interface OutputSchema { + uri: string; +} + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: OutputSchema; +} diff --git a/packages/api/src/types/todo/adx/resolveName.ts b/packages/api/src/types/todo/adx/resolveName.ts new file mode 100644 index 00000000000..2c0bf72f2f8 --- /dev/null +++ b/packages/api/src/types/todo/adx/resolveName.ts @@ -0,0 +1,26 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams { + name: string; +} + +export interface CallOptions { + headers?: Headers; +} + +export type InputSchema = undefined + +export interface OutputSchema { + did: string; +} + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: OutputSchema; +} diff --git a/packages/api/src/types/todo/adx/syncGetRepo.ts b/packages/api/src/types/todo/adx/syncGetRepo.ts new file mode 100644 index 00000000000..eaccb25eff9 --- /dev/null +++ b/packages/api/src/types/todo/adx/syncGetRepo.ts @@ -0,0 +1,23 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams { + did: string; + from?: string; +} + +export interface CallOptions { + headers?: Headers; +} + +export type InputSchema = undefined + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: Uint8Array; +} diff --git a/packages/api/src/types/todo/adx/syncGetRoot.ts b/packages/api/src/types/todo/adx/syncGetRoot.ts new file mode 100644 index 00000000000..babca8434cb --- /dev/null +++ b/packages/api/src/types/todo/adx/syncGetRoot.ts @@ -0,0 +1,26 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams { + did: string; +} + +export interface CallOptions { + headers?: Headers; +} + +export type InputSchema = undefined + +export interface OutputSchema { + root: string; +} + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: OutputSchema; +} diff --git a/packages/api/src/types/todo/adx/syncUpdateRepo.ts b/packages/api/src/types/todo/adx/syncUpdateRepo.ts new file mode 100644 index 00000000000..e5ca83f39b9 --- /dev/null +++ b/packages/api/src/types/todo/adx/syncUpdateRepo.ts @@ -0,0 +1,22 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams { + did: string; +} + +export interface CallOptions { + headers?: Headers; + encoding: 'application/cbor'; +} + +export type InputSchema = string | Uint8Array + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; +} diff --git a/packages/api/src/types/todo/social/badge.ts b/packages/api/src/types/todo/social/badge.ts new file mode 100644 index 00000000000..29ef84969a4 --- /dev/null +++ b/packages/api/src/types/todo/social/badge.ts @@ -0,0 +1,31 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +export interface Record { + assertion: + | InviteAssertion + | EmployeeAssertion + | TagAssertion + | UnknownAssertion; + subject: string; + createdAt: string; + [k: string]: unknown; +} +export interface InviteAssertion { + type: 'invite'; + [k: string]: unknown; +} +export interface EmployeeAssertion { + type: 'employee'; + [k: string]: unknown; +} +export interface TagAssertion { + type: 'tag'; + tag: string; + [k: string]: unknown; +} +export interface UnknownAssertion { + type: string; + [k: string]: unknown; +} diff --git a/packages/api/src/types/todo/social/follow.ts b/packages/api/src/types/todo/social/follow.ts new file mode 100644 index 00000000000..2603e19c186 --- /dev/null +++ b/packages/api/src/types/todo/social/follow.ts @@ -0,0 +1,9 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +export interface Record { + subject: string; + createdAt: string; + [k: string]: unknown; +} diff --git a/packages/microblog/src/types/FeedView.ts b/packages/api/src/types/todo/social/getFeed.ts similarity index 61% rename from packages/microblog/src/types/FeedView.ts rename to packages/api/src/types/todo/social/getFeed.ts index 016df51293a..15ce9f7f5a9 100644 --- a/packages/microblog/src/types/FeedView.ts +++ b/packages/api/src/types/todo/social/getFeed.ts @@ -1,10 +1,22 @@ -export interface Params { +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams { author?: string; limit?: number; before?: string; } -export interface Response { +export interface CallOptions { + headers?: Headers; +} + +export type InputSchema = undefined + +export interface OutputSchema { feed: FeedItem[]; } export interface FeedItem { @@ -18,8 +30,8 @@ export interface FeedItem { likeCount: number; indexedAt: string; myState?: { - repost?: string; - like?: string; + repost?: string, + like?: string, }; } export interface User { @@ -28,12 +40,12 @@ export interface User { displayName?: string; } export interface RecordEmbed { - type: "record"; + type: 'record'; author: User; record: {}; } export interface ExternalEmbed { - type: "external"; + type: 'external'; uri: string; title: string; description: string; @@ -42,3 +54,10 @@ export interface ExternalEmbed { export interface UnknownEmbed { type: string; } + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: OutputSchema; +} diff --git a/packages/api/src/types/todo/social/getLikedBy.ts b/packages/api/src/types/todo/social/getLikedBy.ts new file mode 100644 index 00000000000..8cba668a8ba --- /dev/null +++ b/packages/api/src/types/todo/social/getLikedBy.ts @@ -0,0 +1,35 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams { + uri: string; + limit?: number; + before?: string; +} + +export interface CallOptions { + headers?: Headers; +} + +export type InputSchema = undefined + +export interface OutputSchema { + uri: string; + likedBy: { + did: string, + name: string, + displayName?: string, + createdAt?: string, + indexedAt: string, + }[]; +} + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: OutputSchema; +} diff --git a/packages/api/src/types/todo/social/getNotifications.ts b/packages/api/src/types/todo/social/getNotifications.ts new file mode 100644 index 00000000000..e0e7602b62c --- /dev/null +++ b/packages/api/src/types/todo/social/getNotifications.ts @@ -0,0 +1,38 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams { + limit?: number; + before?: string; +} + +export interface CallOptions { + headers?: Headers; +} + +export type InputSchema = undefined + +export interface OutputSchema { + notifications: Notification[]; +} +export interface Notification { + uri: string; + author: { + did: string, + name: string, + displayName: string, + }; + record: {}; + isRead: boolean; + indexedAt: string; +} + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: OutputSchema; +} diff --git a/packages/microblog/src/types/PostThreadView.ts b/packages/api/src/types/todo/social/getPostThread.ts similarity index 60% rename from packages/microblog/src/types/PostThreadView.ts rename to packages/api/src/types/todo/social/getPostThread.ts index 77dd9ca2b0b..7dbb8139538 100644 --- a/packages/microblog/src/types/PostThreadView.ts +++ b/packages/api/src/types/todo/social/getPostThread.ts @@ -1,9 +1,21 @@ -export interface Params { +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams { uri: string; depth?: number; } -export interface Response { +export interface CallOptions { + headers?: Headers; +} + +export type InputSchema = undefined + +export interface OutputSchema { thread: Post; } export interface Post { @@ -18,8 +30,8 @@ export interface Post { repostCount: number; indexedAt: string; myState?: { - repost?: string; - like?: string; + repost?: string, + like?: string, }; } export interface User { @@ -28,12 +40,12 @@ export interface User { displayName?: string; } export interface RecordEmbed { - type: "record"; + type: 'record'; author: User; record: {}; } export interface ExternalEmbed { - type: "external"; + type: 'external'; uri: string; title: string; description: string; @@ -42,3 +54,10 @@ export interface ExternalEmbed { export interface UnknownEmbed { type: string; } + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: OutputSchema; +} diff --git a/packages/api/src/types/todo/social/getProfile.ts b/packages/api/src/types/todo/social/getProfile.ts new file mode 100644 index 00000000000..160e20337a4 --- /dev/null +++ b/packages/api/src/types/todo/social/getProfile.ts @@ -0,0 +1,49 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams { + user: string; +} + +export interface CallOptions { + headers?: Headers; +} + +export type InputSchema = undefined + +export interface OutputSchema { + did: string; + name: string; + displayName?: string; + description?: string; + followersCount: number; + followsCount: number; + postsCount: number; + badges: Badge[]; + myState?: { + follow?: string, + }; +} +export interface Badge { + uri: string; + error?: string; + issuer?: { + did: string, + name: string, + displayName: string, + }; + assertion?: { + type: string, + }; + createdAt?: string; +} + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: OutputSchema; +} diff --git a/packages/api/src/types/todo/social/getRepostedBy.ts b/packages/api/src/types/todo/social/getRepostedBy.ts new file mode 100644 index 00000000000..a4906d0a871 --- /dev/null +++ b/packages/api/src/types/todo/social/getRepostedBy.ts @@ -0,0 +1,35 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams { + uri: string; + limit?: number; + before?: string; +} + +export interface CallOptions { + headers?: Headers; +} + +export type InputSchema = undefined + +export interface OutputSchema { + uri: string; + repostedBy: { + did: string, + name: string, + displayName?: string, + createdAt?: string, + indexedAt: string, + }[]; +} + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: OutputSchema; +} diff --git a/packages/api/src/types/todo/social/getUserFollowers.ts b/packages/api/src/types/todo/social/getUserFollowers.ts new file mode 100644 index 00000000000..460b493a9a4 --- /dev/null +++ b/packages/api/src/types/todo/social/getUserFollowers.ts @@ -0,0 +1,39 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams { + user: string; + limit?: number; + before?: string; +} + +export interface CallOptions { + headers?: Headers; +} + +export type InputSchema = undefined + +export interface OutputSchema { + subject: { + did: string, + name: string, + displayName?: string, + }; + followers: { + did: string, + name: string, + displayName?: string, + createdAt?: string, + indexedAt: string, + }[]; +} + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: OutputSchema; +} diff --git a/packages/api/src/types/todo/social/getUserFollows.ts b/packages/api/src/types/todo/social/getUserFollows.ts new file mode 100644 index 00000000000..f598d465b41 --- /dev/null +++ b/packages/api/src/types/todo/social/getUserFollows.ts @@ -0,0 +1,39 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { Headers } from '@adxp/xrpc' + +export interface QueryParams { + user: string; + limit?: number; + before?: string; +} + +export interface CallOptions { + headers?: Headers; +} + +export type InputSchema = undefined + +export interface OutputSchema { + subject: { + did: string, + name: string, + displayName?: string, + }; + follows: { + did: string, + name: string, + displayName?: string, + createdAt?: string, + indexedAt: string, + }[]; +} + +export interface Response { + success: boolean; + error: boolean; + headers: Headers; + data: OutputSchema; +} diff --git a/packages/api/src/types/todo/social/like.ts b/packages/api/src/types/todo/social/like.ts new file mode 100644 index 00000000000..2603e19c186 --- /dev/null +++ b/packages/api/src/types/todo/social/like.ts @@ -0,0 +1,9 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +export interface Record { + subject: string; + createdAt: string; + [k: string]: unknown; +} diff --git a/packages/microblog/src/types/EmbeddedMedia.ts b/packages/api/src/types/todo/social/mediaEmbed.ts similarity index 62% rename from packages/microblog/src/types/EmbeddedMedia.ts rename to packages/api/src/types/todo/social/mediaEmbed.ts index 83b79038c76..df9e01c8e4d 100644 --- a/packages/microblog/src/types/EmbeddedMedia.ts +++ b/packages/api/src/types/todo/social/mediaEmbed.ts @@ -1,12 +1,19 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ export interface Record { media: MediaEmbed[]; + [k: string]: unknown; } export interface MediaEmbed { alt?: string; thumb?: MediaEmbedBlob; original: MediaEmbedBlob; + [k: string]: unknown; } export interface MediaEmbedBlob { mimeType: string; blobId: string; + [k: string]: unknown; } diff --git a/packages/api/src/types/todo/social/post.ts b/packages/api/src/types/todo/social/post.ts new file mode 100644 index 00000000000..27060358056 --- /dev/null +++ b/packages/api/src/types/todo/social/post.ts @@ -0,0 +1,27 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +/** + * @minItems 2 + * @maxItems 2 + */ +export type TextSlice = [number, number] +export type Entity = { + index: TextSlice, + type: string, + value: string, + [k: string]: unknown, +}[] + +export interface Record { + text: string; + entities?: Entity; + reply?: { + root: string, + parent?: string, + [k: string]: unknown, + }; + createdAt: string; + [k: string]: unknown; +} diff --git a/packages/microblog/src/types/Profile.ts b/packages/api/src/types/todo/social/profile.ts similarity index 55% rename from packages/microblog/src/types/Profile.ts rename to packages/api/src/types/todo/social/profile.ts index b1ce94628f9..39d755592c5 100644 --- a/packages/microblog/src/types/Profile.ts +++ b/packages/api/src/types/todo/social/profile.ts @@ -1,8 +1,14 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ export interface Record { displayName: string; description?: string; badges?: BadgeRef[]; + [k: string]: unknown; } export interface BadgeRef { uri: string; + [k: string]: unknown; } diff --git a/packages/api/src/types/todo/social/repost.ts b/packages/api/src/types/todo/social/repost.ts new file mode 100644 index 00000000000..2603e19c186 --- /dev/null +++ b/packages/api/src/types/todo/social/repost.ts @@ -0,0 +1,9 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +export interface Record { + subject: string; + createdAt: string; + [k: string]: unknown; +} diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 3d787763cc5..039ef02eecb 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -6,8 +6,7 @@ }, "include": ["./src"], "references": [ - { "path": "../auth/tsconfig.build.json" }, - { "path": "../common/tsconfig.build.json" }, - { "path": "../schemas/tsconfig.build.json" }, + { "path": "../xrpc/tsconfig.build.json" }, + { "path": "../xrpc-cli/tsconfig.build.json" }, ] } \ No newline at end of file diff --git a/packages/auth/src/auth-store.ts b/packages/auth/src/auth-store.ts index ec63a36dcfd..e4d75c71fe8 100644 --- a/packages/auth/src/auth-store.ts +++ b/packages/auth/src/auth-store.ts @@ -114,8 +114,8 @@ export class AuthStore implements Signer { caps: ucan.Capability[], lifetime = MONTH_IN_SEC, ): Promise { + // @TODO make sure to dedupe proofs const proofs: CapWithProof[] = [] - const encodedTokens = new Set() for (const cap of caps) { const proof = await this.vaguestProofForCap(cap) if (proof === null) { diff --git a/packages/auth/src/capabilities.ts b/packages/auth/src/capabilities.ts index 7fa64af3b0a..df6629665b6 100644 --- a/packages/auth/src/capabilities.ts +++ b/packages/auth/src/capabilities.ts @@ -3,16 +3,12 @@ import * as ucan from './ucans/index' export const writeCap = ( did: string, - namespace?: string, - dataset?: string, + collection?: string, record?: string, ): ucan.Capability => { let resource = did - if (namespace) { - resource += '/' + namespace - } - if (dataset) { - resource += '/' + dataset + if (collection) { + resource += '/' + collection } if (record) { resource += '/' + record @@ -27,9 +23,8 @@ export const maintenanceCap = (did: string): ucan.Capability => { export const vaguerCap = (cap: ucan.Capability): ucan.Capability | null => { const rsc = parseAdxResource(cap.with) if (rsc === null) return null - // can't go vaguer than every namespace - if (rsc.namespace === '*') return null - if (rsc.dataset === '*') return writeCap(rsc.did) - if (rsc.record === '*') return writeCap(rsc.did, rsc.namespace) - return writeCap(rsc.did, rsc.namespace, rsc.dataset) + // can't go vaguer than every collection + if (rsc.collection === '*') return null + if (rsc.record === '*') return writeCap(rsc.did) + return writeCap(rsc.did, rsc.collection) } diff --git a/packages/auth/src/semantics.ts b/packages/auth/src/semantics.ts index 8199cffdd23..e2dafc8d26b 100644 --- a/packages/auth/src/semantics.ts +++ b/packages/auth/src/semantics.ts @@ -7,16 +7,14 @@ Resource name: 'adx' - Full permission for account: adx://did:example:userDid/* -- Permission to write to particular application namespace: - adx://did:example:userDid/namespace/* -- Permission to write objects within a particular collection (namespace + dataset): - adx://did:example:userDid/namespace/post/* +- Permission to write to particular application collection: + adx://did:example:userDid/com.foo.post/* - Permission to create a single interaction on user's behalf: - adx://did:example:userDid/namespace/post/234567abcdefg + adx://did:example:userDid/com.foo.post/234567abcdefg Example: { - with: { scheme: "adx", hierPart: "did:example:userDid/microblog/*" }, + with: { scheme: "adx", hierPart: "did:example:userDid/com.foo.post/*" }, can: { namespace: "adx", segments: [ "WRITE" ] } } @@ -67,8 +65,7 @@ export const adxCapability = ( } export interface AdxResourcePointer { did: string - namespace: string - dataset: string + collection: string record: string } @@ -79,15 +76,13 @@ export const parseAdxResource = ( if (pointer.scheme !== 'adx') return null const parts = pointer.hierPart.split('/') - let [did, namespace, dataset, record] = parts + let [did, collection, record] = parts if (!did) return null - if (!namespace) namespace = '*' - if (!dataset) dataset = '*' + if (!collection) collection = '*' if (!record) record = '*' return { did, - namespace, - dataset, + collection, record, } } @@ -100,11 +95,8 @@ export const adxSemantics: ucans.DelegationSemantics = { if (parent == null || child == null) return false if (parent.did !== child.did) return false - if (parent.namespace === '*') return true - if (parent.namespace !== child.namespace) return false - - if (parent.dataset === '*') return true - if (parent.dataset !== child.dataset) return false + if (parent.collection === '*') return true + if (parent.collection !== child.collection) return false if (parent.record === '*') return true diff --git a/packages/auth/tests/auth.test.ts b/packages/auth/tests/auth.test.ts index 2d770f8bca6..02587d09c64 100644 --- a/packages/auth/tests/auth.test.ts +++ b/packages/auth/tests/auth.test.ts @@ -2,8 +2,7 @@ import { writeCap } from '../src/capabilities' import { AuthStore, MemoryStore, Ucan, ucans, verifyAdxUcan } from '../src' describe('tokens for post', () => { - const collection = 'did:example:microblog' - const schema = 'did:example:like' + const collection = 'com.example.microblog' const record = '3iwc-gvs-ehpk-2s' const serverDid = 'did:example:fakeServerDid' @@ -18,7 +17,7 @@ describe('tokens for post', () => { fullUcan = await authStore.claimFull() rootDid = await authStore.did() - cap = writeCap(rootDid, collection, schema, record) + cap = writeCap(rootDid, collection, record) await verifyAdxUcan(fullUcan, fullUcan.payload.aud, cap) }) @@ -31,8 +30,7 @@ describe('tokens for post', () => { it('throws an error for the wrong collection', async () => { const collectionCap = writeCap( rootDid, - 'did:example:otherCollection', - schema, + 'com.example.otherCollection', record, ) try { @@ -44,7 +42,7 @@ describe('tokens for post', () => { }) it('throws an error for the wrong record name', async () => { - const recordCap = writeCap(rootDid, collection, schema, '3iwc-gvs-ehpk-2z') + const recordCap = writeCap(rootDid, collection, '3iwc-gvs-ehpk-2z') try { const res = await verifyAdxUcan(token, serverDid, recordCap) expect(res).toBe(null) diff --git a/packages/common/src/network/uri.ts b/packages/common/src/network/uri.ts index c34218832e2..0efa25f526b 100644 --- a/packages/common/src/network/uri.ts +++ b/packages/common/src/network/uri.ts @@ -59,46 +59,24 @@ export class AdxUri { this.searchParams = new URLSearchParams(v) } - get namespace() { + get collection() { return this.pathname.split('/').filter(Boolean)[0] || '' } - set namespace(v: string) { + set collection(v: string) { const parts = this.pathname.split('/').filter(Boolean) parts[0] = v this.pathname = parts.join('/') } - get dataset() { - return this.pathname.split('/').filter(Boolean)[1] || '' - } - - set dataset(v: string) { - const parts = this.pathname.split('/').filter(Boolean) - parts[1] = v - this.pathname = parts.join('/') - } - - get collection() { - if (!this.namespace && !this.dataset) return '' - return [this.namespace, this.dataset].join('/') - } - - set collection(v: string) { - const [namespace = '', dataset = ''] = v.split('/') - this.namespace = namespace - this.dataset = dataset - } - get recordKey() { - return this.pathname.split('/').filter(Boolean)[2] || '' + return this.pathname.split('/').filter(Boolean)[1] || '' } set recordKey(v: string) { const parts = this.pathname.split('/').filter(Boolean) if (!parts[0]) parts[0] = 'undefined' - if (!parts[1]) parts[1] = 'undefined' - parts[2] = v + parts[1] = v this.pathname = parts.join('/') } diff --git a/packages/common/tests/uri.test.ts b/packages/common/tests/uri.test.ts index 4cca80f5306..90b827b2c86 100644 --- a/packages/common/tests/uri.test.ts +++ b/packages/common/tests/uri.test.ts @@ -23,113 +23,113 @@ describe('Adx Uris', () => { ['adx://foo.com?foo=bar#hash', 'foo.com', '', 'foo=bar', '#hash'], [ - 'did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', - 'did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', + 'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', + 'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', '', '', '', ], [ - 'adx://did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', - 'did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', + 'adx://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', + 'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', '', '', '', ], [ - 'adx://did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/', - 'did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', + 'adx://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/', + 'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', '/', '', '', ], [ - 'adx://did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/foo', - 'did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', + 'adx://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/foo', + 'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', '/foo', '', '', ], [ - 'adx://did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/foo/', - 'did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', + 'adx://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/foo/', + 'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', '/foo/', '', '', ], [ - 'adx://did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/foo/bar', - 'did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', + 'adx://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/foo/bar', + 'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', '/foo/bar', '', '', ], [ - 'adx://did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw?foo=bar', - 'did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', + 'adx://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw?foo=bar', + 'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', '', 'foo=bar', '', ], [ - 'adx://did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw?foo=bar&baz=buux', - 'did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', + 'adx://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw?foo=bar&baz=buux', + 'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', '', 'foo=bar&baz=buux', '', ], [ - 'adx://did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/?foo=bar', - 'did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', + 'adx://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/?foo=bar', + 'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', '/', 'foo=bar', '', ], [ - 'adx://did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/foo?foo=bar', - 'did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', + 'adx://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/foo?foo=bar', + 'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', '/foo', 'foo=bar', '', ], [ - 'adx://did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/foo/?foo=bar', - 'did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', + 'adx://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/foo/?foo=bar', + 'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', '/foo/', 'foo=bar', '', ], [ - 'adx://did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw#hash', - 'did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', + 'adx://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw#hash', + 'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', '', '', '#hash', ], [ - 'adx://did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/#hash', - 'did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', + 'adx://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/#hash', + 'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', '/', '', '#hash', ], [ - 'adx://did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/foo#hash', - 'did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', + 'adx://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/foo#hash', + 'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', '/foo', '', '#hash', ], [ - 'adx://did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/foo/#hash', - 'did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', + 'adx://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/foo/#hash', + 'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', '/foo/', '', '#hash', ], [ - 'adx://did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw?foo=bar#hash', - 'did:ion:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', + 'adx://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw?foo=bar#hash', + 'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw', '', 'foo=bar', '#hash', @@ -261,24 +261,13 @@ describe('Adx Uris', () => { expect(urip.recordKey).toBe('') } { - const urip = new AdxUri('adx://foo.com/namespace') - expect(urip.namespace).toBe('namespace') - expect(urip.dataset).toBe('') - expect(urip.collection).toBe('namespace/') + const urip = new AdxUri('adx://foo.com/com.example.foo') + expect(urip.collection).toBe('com.example.foo') expect(urip.recordKey).toBe('') } { - const urip = new AdxUri('adx://foo.com/namespace/dataset') - expect(urip.namespace).toBe('namespace') - expect(urip.dataset).toBe('dataset') - expect(urip.collection).toBe('namespace/dataset') - expect(urip.recordKey).toBe('') - } - { - const urip = new AdxUri('adx://foo.com/namespace/dataset/123') - expect(urip.namespace).toBe('namespace') - expect(urip.dataset).toBe('dataset') - expect(urip.collection).toBe('namespace/dataset') + const urip = new AdxUri('adx://foo.com/com.example.foo/123') + expect(urip.collection).toBe('com.example.foo') expect(urip.recordKey).toBe('123') } }) @@ -300,16 +289,16 @@ describe('Adx Uris', () => { urip.pathname = 'foo' expect(urip.toString()).toBe('adx://foo.com/foo') - urip.collection = 'namespace/dataset' + urip.collection = 'com.example.foo' urip.recordKey = '123' - expect(urip.toString()).toBe('adx://foo.com/namespace/dataset/123') + expect(urip.toString()).toBe('adx://foo.com/com.example.foo/123') urip.recordKey = '124' - expect(urip.toString()).toBe('adx://foo.com/namespace/dataset/124') - urip.collection = 'other/data' - expect(urip.toString()).toBe('adx://foo.com/other/data/124') + expect(urip.toString()).toBe('adx://foo.com/com.example.foo/124') + urip.collection = 'com.other.foo' + expect(urip.toString()).toBe('adx://foo.com/com.other.foo/124') urip.pathname = '' urip.recordKey = '123' - expect(urip.toString()).toBe('adx://foo.com/undefined/undefined/123') + expect(urip.toString()).toBe('adx://foo.com/undefined/123') urip.pathname = 'foo' urip.search = '?foo=bar' diff --git a/packages/dev-env/README.md b/packages/dev-env/README.md index 5d0d01be164..8ae23fa149a 100644 --- a/packages/dev-env/README.md +++ b/packages/dev-env/README.md @@ -22,6 +22,6 @@ Stop the server at the given port. Create a new user. -### `user(username: string): MicroblogClient` +### `user(username: string): ServiceClient` -Get the `MicroblogClient` for the given user. \ No newline at end of file +Get the `ServiceClient` for the given user. \ No newline at end of file diff --git a/packages/dev-env/build.js b/packages/dev-env/build.js index 109635e4e1e..7d54dcc736d 100644 --- a/packages/dev-env/build.js +++ b/packages/dev-env/build.js @@ -7,8 +7,9 @@ require('esbuild') platform: 'node', external: [ './node_modules/sqlite3/*', + '../server/node_modules/@mapbox/*', + '../../node_modules/sqlite3/*', '../../node_modules/level/*', - '../../../node_modules/level/*', '../../node_modules/classic-level/*', ], }) diff --git a/packages/dev-env/package.json b/packages/dev-env/package.json index cea868d9525..d38a0fe4848 100644 --- a/packages/dev-env/package.json +++ b/packages/dev-env/package.json @@ -14,6 +14,7 @@ "verify:fix": "yarn prettier:fix && yarn lint:fix" }, "dependencies": { + "@adxp/api": "*", "@adxp/common": "*", "@adxp/crypto": "*", "@adxp/did-sdk": "*", diff --git a/packages/dev-env/src/index.ts b/packages/dev-env/src/index.ts index 41dfec1c4d8..911b3397ce2 100644 --- a/packages/dev-env/src/index.ts +++ b/packages/dev-env/src/index.ts @@ -4,7 +4,7 @@ import { MemoryBlockstore } from '@adxp/repo' import PDSServer from '@adxp/server/dist/server.js' import PDSDatabase from '@adxp/server/dist/db/index.js' import * as crypto from '@adxp/crypto' -import { MicroblogClient } from '@adxp/microblog' +import AdxApi, { ServiceClient } from '@adxp/api' import { ServerType, ServerConfig, StartParams } from './types.js' export class DevEnvServer { @@ -72,8 +72,8 @@ export class DevEnvServer { } } - getClient(userDid: string): MicroblogClient { - return new MicroblogClient(`http://localhost:${this.port}`, userDid) + getClient(userDid: string): ServiceClient { + return AdxApi.service(`http://localhost:${this.port}`) } } diff --git a/packages/dev-env/tsconfig.json b/packages/dev-env/tsconfig.json index 3c1edbda0af..efa75b2cd25 100644 --- a/packages/dev-env/tsconfig.json +++ b/packages/dev-env/tsconfig.json @@ -7,8 +7,10 @@ "module": "esnext", "include": ["./src","__tests__/**/**.ts"], "references": [ + { "path": "../api/tsconfig.build.json" }, { "path": "../common/tsconfig.build.json" }, { "path": "../crypto/tsconfig.build.json" }, { "path": "../did-sdk/tsconfig.build.json" }, + { "path": "../server/tsconfig.build.json" }, ] } \ No newline at end of file diff --git a/packages/microblog/package.json b/packages/microblog/package.json deleted file mode 100644 index 2d1cbdd1695..00000000000 --- a/packages/microblog/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "@adxp/microblog", - "version": "0.0.1", - "main": "src/index.ts", - "scripts": { - "schemas-compile": "node ./scripts/compile.js src", - "schemas-docs": "node ./scripts/docs.js", - "schemas-validate": "node ./scripts/validate.js", - "schemas-ifaces": "node ./scripts/ifaces.js src/types", - "schemas-build": "yarn schemas-compile && yarn schemas-ifaces", - "prettier": "prettier --check src/", - "prettier:fix": "prettier --write src/", - "lint": "eslint . --ext .ts,.tsx", - "lint:fix": "yarn lint --fix", - "verify": "run-p prettier lint", - "verify:fix": "yarn prettier:fix && yarn lint:fix", - "build": "esbuild src/index.ts --define:process.env.NODE_ENV=\\\"production\\\" --bundle --platform=node --sourcemap --outfile=dist/index.js", - "postbuild": "tsc --build tsconfig.build.json" - }, - "dependencies": { - "@adxp/schemas": "*" - }, - "devDependencies": { - "ajv": "^8.11.0", - "ajv-formats": "^2.1.1", - "json-schema-to-typescript": "^11.0.2" - } -} diff --git a/packages/microblog/scripts/_util.js b/packages/microblog/scripts/_util.js deleted file mode 100644 index c4a3c656218..00000000000 --- a/packages/microblog/scripts/_util.js +++ /dev/null @@ -1,23 +0,0 @@ -const fs = require('node:fs') -const path = require('node:path') - -exports.listAllNames = () => { - return fs.readdirSync(path.join(__dirname, '..', 'src', 'schemas')) -} - -exports.readAll = () => { - const objs = [] - for (const name of exports.listAllNames()) { - try { - const file = fs.readFileSync( - path.join(__dirname, '..', 'src', 'schemas', name), - 'utf8' - ) - objs.push(JSON.parse(file)) - } catch (e) { - console.error(`Failed to read ${name}`) - throw e - } - } - return objs -} diff --git a/packages/microblog/scripts/compile.js b/packages/microblog/scripts/compile.js deleted file mode 100644 index fb3b6cf7564..00000000000 --- a/packages/microblog/scripts/compile.js +++ /dev/null @@ -1,17 +0,0 @@ -const path = require('path') -const fs = require('fs') -const { readAll } = require('./_util') - - -const schemas = readAll() -const result = `export const schemas = [${schemas.map( - (s) => `${JSON.stringify(s, null, 2)}` - )}]` - -const dirPath = process.argv[2] -if(dirPath) { - const p = path.join(path.resolve(dirPath), 'defs.ts') - fs.writeFileSync(p, result) -}else { - console.log(result) -} \ No newline at end of file diff --git a/packages/microblog/scripts/docs.js b/packages/microblog/scripts/docs.js deleted file mode 100644 index 2748673f7cf..00000000000 --- a/packages/microblog/scripts/docs.js +++ /dev/null @@ -1,91 +0,0 @@ -const { readAll } = require('./_util') -const toTs = require('json-schema-to-typescript') - -;(async () => { - const schemas = readAll() - schemas.sort((a, b) => { - if (a.$type === 'adxs-collection' && b.$type !== 'adxs-collection') { - return -1 - } - if (a.$type !== 'adxs-collection' && b.$type === 'adxs-collection') { - return 1 - } - if (a.$type === 'adxs-record' && b.$type !== 'adxs-record') { - return -1 - } - if (a.$type !== 'adxs-record' && b.$type === 'adxs-record') { - return 1 - } - return a.name.localeCompare(b.name) - }) - - const out = [] - for (const s of schemas) { - out.push(`## ${getType(s)}: ${s.name}`) - out.push('') - out.push(outJson(s, 'Full Definition')) - out.push('') - out.push(s.$comment ? `${s.$comment + '\n'}` : '') - out.push('') - if (s.$type === 'adxs-collection') { - continue - } - out.push('**Interface**') - if (s.parameters) out.push(await outTs(s.parameters, 'Params')) - if (s.response) out.push(await outTs(s.response, 'Response')) - if (s.schema) out.push(await outTs(s.schema, 'Record')) - if (s.$ext?.['adxs-doc']?.examples) { - out.push('**Examples**') - for (const ex of s.$ext?.['adxs-doc']?.examples) { - out.push('') - out.push('```json') - out.push(JSON.stringify(ex, null, 2)) - out.push('```') - out.push('') - } - } - out.push('') - } - console.log(out.join('\n')) -})() - -function getType(schema) { - if (schema.$type === 'adxs-record') { - return 'Record' - } - if (schema.$type === 'adxs-collection') { - return 'Collection' - } - if (schema.$type === 'adxs-view') { - return 'View' - } -} - -function outJson(v, title) { - if (v) { - return `
${title}
${JSON.stringify(
-      v,
-      null,
-      2
-    )}
\n` - } - return '' -} - -async function outTs(v, title) { - if (v) { - if (v['$ref']) { - // HACK to workaround an issue with cyclic refs - Object.assign(v, v['$defs']['post']) - delete v['$ref'] - } - return `\`\`\`typescript\n${await toTs.compile( - Object.assign({ title }, v), - undefined, - { - bannerComment: '', - } - )}\`\`\`` - } - return '' -} diff --git a/packages/microblog/scripts/ifaces.js b/packages/microblog/scripts/ifaces.js deleted file mode 100644 index 8b4c1ea3c53..00000000000 --- a/packages/microblog/scripts/ifaces.js +++ /dev/null @@ -1,54 +0,0 @@ -const path = require('path') -const fs = require('fs') -const { readAll } = require('./_util') -const toTs = require('json-schema-to-typescript') - -let dirPath = process.argv[2] -if (!dirPath) { - throw new Error('Please specify the output folder') -} -dirPath = path.resolve(dirPath) - -const files = [] - -;(async () => { - const schemas = readAll() - schemas.sort((a, b) => { - return a.name.localeCompare(b.name) - }) - - const indexJs = [] - for (const s of schemas) { - if (s.$type === 'adxs-collection') { - continue - } - const out = [] - if (s.parameters) out.push(await outTs(s.parameters, 'Params')) - if (s.response) out.push(await outTs(s.response, 'Response')) - if (s.schema) out.push(await outTs(s.schema, 'Record')) - files.push({ name: `${s.name}.ts`, content: out.join('\n') }) - indexJs.push(`export * as ${s.name} from './${s.name}'`) - } - - console.log('writing', path.join(dirPath, 'index.ts')) - fs.writeFileSync(path.join(dirPath, 'index.ts'), indexJs.join('\n')) - for (const f of files) { - console.log('writing', path.join(dirPath, f.name)) - fs.writeFileSync(path.join(dirPath, f.name), f.content) - } -})() - -async function outTs(v, title) { - if (v) { - if (v['$ref']) { - // HACK to workaround an issue with cyclic refs - Object.assign(v, v['$defs']['post']) - delete v['$ref'] - } - return await toTs.compile(Object.assign({ title }, v), undefined, { - bannerComment: '', - additionalProperties: false, - }) - } - return '' -} diff --git a/packages/microblog/scripts/validate.js b/packages/microblog/scripts/validate.js deleted file mode 100644 index f3d5a64df10..00000000000 --- a/packages/microblog/scripts/validate.js +++ /dev/null @@ -1,46 +0,0 @@ -const Ajv = require('ajv') -const ajvAddFormats = require('ajv-formats') -const { readAll } = require('./_util') - -const ajv = new Ajv() -ajvAddFormats(ajv) - -const schemas = readAll() -for (const schema of schemas) { - // TODO use schemas package for this - if (schema.params) { - try { - ajv.compile(schema.params) - } catch (e) { - console.error(`${schema.name} has an invalid .params`) - throw e - } - } - if (schema.response) { - try { - testExamples(ajv.compile(schema.response), schema) - } catch (e) { - console.error(`${schema.name} has an invalid .response`) - throw e - } - } - if (schema.schema) { - try { - testExamples(ajv.compile(schema.schema), schema) - } catch (e) { - console.error(`${schema.name} has an invalid .schema`) - throw e - } - } -} - -function testExamples(validate, schema) { - const examples = schema.$ext?.['adxs-doc']?.examples || [] - for (const example of examples) { - if (!validate(example)) { - console.error(`Example failed validation`, example) - console.error(validate.errors) - process.exit(1) - } - } -} diff --git a/packages/microblog/src/client.ts b/packages/microblog/src/client.ts deleted file mode 100644 index e19436a6edc..00000000000 --- a/packages/microblog/src/client.ts +++ /dev/null @@ -1,253 +0,0 @@ -import { AdxUri } from '@adxp/common' -import { AdxClient, AdxPdsClient, AdxRepoClient } from '@adxp/api' -import { - Post, - Profile, - Like, - Repost, - Follow, - Badge, - FeedView, - LikedByView, - PostThreadView, - ProfileView, - RepostedByView, - UserFollowersView, - UserFollowsView, -} from '@adxp/microblog' -import { schemas } from './defs' - -export class MicroblogClient { - public client: AdxClient - public pds: AdxPdsClient - public repo: AdxRepoClient - - constructor(public pdsUrl: string, public did: string) { - this.client = new AdxClient({ - pds: pdsUrl, - schemas, - }) - this.pds = this.client.mainPds - this.repo = this.client.repo(did) - } - - config() { - return { - headers: { - Authorization: this.did, - }, - } - } - - async register(username: string) { - await this.pds.registerRepo({ username, did: this.did }) - } - - async createProfile( - displayName: string, - description?: string, - ): Promise { - const uri = await this.repo - .collection('bsky/profile') - .create('blueskyweb.xyz:Profile', { - $type: 'blueskyweb.xyz:Profile', - displayName, - description, - }) - return uri - } - - async createPost(text: string, entities?: Post.Entity[]): Promise { - const uri = await this.repo - .collection('bsky/posts') - .create('blueskyweb.xyz:Post', { - $type: 'blueskyweb.xyz:Post', - text, - entities, - createdAt: new Date().toISOString(), - }) - return uri - } - - async reply(root: AdxUri, parent: AdxUri, text: string): Promise { - const uri = await this.repo - .collection('bsky/posts') - .create('blueskyweb.xyz:Post', { - $type: 'blueskyweb.xyz:Post', - text, - reply: { - root: root.toString(), - parent: parent.toString(), - }, - createdAt: new Date().toISOString(), - }) - return uri - } - - async likePost(uri: AdxUri): Promise { - const likeUri = await this.repo - .collection('bsky/likes') - .create('blueskyweb.xyz:Like', { - $type: 'blueskyweb.xyz:Like', - subject: uri.toString(), - createdAt: new Date().toISOString(), - }) - return likeUri - } - - async repost(uri: AdxUri): Promise { - const repostUri = await this.repo - .collection('bsky/reposts') - .create('blueskyweb.xyz:Repost', { - $type: 'blueskyweb.xyz:Repost', - subject: uri.toString(), - createdAt: new Date().toISOString(), - }) - return repostUri - } - - async followUser(did: string): Promise { - const uri = await this.repo - .collection('bsky/follows') - .create('blueskyweb.xyz:Follow', { - $type: 'blueskyweb.xyz:Follow', - subject: did, - createdAt: new Date().toISOString(), - }) - return uri - } - - async giveBadge(did: string, type: string, tag?: string): Promise { - const uri = await this.repo - .collection('bsky/badges') - .create('blueskyweb.xyz:Badge', { - $type: 'blueskyweb.xyz:Badge', - assertion: { - type, - tag, - }, - subject: did, - createdAt: new Date().toISOString(), - }) - return uri - } - - async acceptBadge(badgeUri: AdxUri): Promise { - const profiles = await this.repo - .collection('bsky/profile') - .list('blueskyweb.xyz:Profile') - // find the first valid profile - const found = profiles.records.find((row) => row.valid) - if (!found) { - throw new Error('Could not accept badge') - } - const recordKey = new AdxUri(found.uri).recordKey - const profile: Profile.Record = found.value - const uri = await this.repo - .collection('bsky/profile') - .put('blueskyweb.xyz:Profile', recordKey, { - ...profile, - badges: [...(profile.badges || []), { uri: badgeUri.toString() }], - }) - return uri - } - - async deleteObject(uri: AdxUri): Promise { - await this.repo.collection(uri.collection).del(uri.recordKey) - } - - async likedByView( - uri: AdxUri, - limit?: number, - before?: string, - ): Promise { - return this.pds.view('blueskyweb.xyz:LikedByView', this.did, { - uri: uri.toString(), - limit, - before, - }) - } - - async repostedByView( - uri: AdxUri, - limit?: number, - before?: string, - ): Promise { - return this.pds.view('blueskyweb.xyz:RepostedByView', this.did, { - uri: uri.toString(), - limit, - before, - }) - } - - async userFollowsView( - user: string, - limit?: string, - before?: string, - ): Promise { - return this.pds.view('blueskyweb.xyz:UserFollowsView', this.did, { - user, - limit, - before, - }) - } - - async userFollowersView( - user: string, - limit?: number, - before?: string, - ): Promise { - return this.pds.view('blueskyweb.xyz:UserFollowersView', this.did, { - user, - limit, - before, - }) - } - - async profileView(user: string): Promise { - return this.pds.view('blueskyweb.xyz:ProfileView', this.did, { - user, - }) - } - - async feedView( - limit?: number, - before?: string, - ): Promise { - const view = await this.pds.view('blueskyweb.xyz:FeedView', this.did, { - limit, - before, - }) - return view.feed - } - - async userFeedView( - author: string, - limit?: number, - before?: string, - ): Promise { - const view = await this.pds.view('blueskyweb.xyz:FeedView', this.did, { - author, - limit, - before, - }) - return view.feed - } - - async postThreadView( - uri: AdxUri, - depth?: number, - ): Promise { - const view = await this.pds.view( - 'blueskyweb.xyz:PostThreadView', - this.did, - { - uri: uri.toString(), - depth, - }, - ) - return view.thread - } -} - -export default MicroblogClient diff --git a/packages/microblog/src/defs.ts b/packages/microblog/src/defs.ts deleted file mode 100644 index 327dfae53ba..00000000000 --- a/packages/microblog/src/defs.ts +++ /dev/null @@ -1,1854 +0,0 @@ -export const schemas = [{ - "$type": "adxs-record", - "author": "blueskyweb.xyz", - "name": "Badge", - "$comment": "An assertion about the subject by this user.", - "locale": { - "en-US": { - "nameSingular": "Badge", - "namePlural": "Badges" - } - }, - "schema": { - "type": "object", - "required": [ - "assertion", - "subject", - "createdAt" - ], - "properties": { - "assertion": { - "oneOf": [ - { - "$ref": "#/$defs/inviteAssertion" - }, - { - "$ref": "#/$defs/employeeAssertion" - }, - { - "$ref": "#/$defs/tagAssertion" - }, - { - "$ref": "#/$defs/unknownAssertion" - } - ] - }, - "subject": { - "type": "string" - }, - "createdAt": { - "type": "string", - "format": "date-time" - } - }, - "$defs": { - "inviteAssertion": { - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "const": "invite" - } - } - }, - "employeeAssertion": { - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "const": "employee" - } - } - }, - "tagAssertion": { - "type": "object", - "required": [ - "type", - "tag" - ], - "properties": { - "type": { - "const": "tag" - }, - "tag": { - "type": "string", - "maxLength": 64 - } - } - }, - "unknownAssertion": { - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string", - "not": { - "enum": [ - "invite", - "employee", - "tag" - ] - } - } - } - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "$type": "blueskyweb.xyz:Badge", - "assertion": { - "type": "employee" - }, - "subject": { - "did": "did:example:1234", - "name": "alice.com" - }, - "createdAt": "2010-01-01T19:23:24Z" - }, - { - "$type": "blueskyweb.xyz:Badge", - "assertion": { - "type": "tag", - "tag": "tech" - }, - "subject": { - "did": "did:example:1234", - "name": "alice.com" - }, - "createdAt": "2010-01-01T19:23:24Z" - }, - { - "$type": "blueskyweb.xyz:Badge", - "assertion": { - "type": "something-else", - "param": "allowed" - }, - "subject": { - "did": "did:example:1234", - "name": "alice.com" - }, - "createdAt": "2010-01-01T19:23:24Z" - } - ] - } - } -},{ - "$type": "adxs-collection", - "author": "blueskyweb.xyz", - "name": "Badges", - "$comment": "Where you put badges.", - "locale": { - "en-US": { - "nameSingular": "Badges", - "namePlural": "Badges Collections" - } - } -},{ - "$type": "adxs-record", - "author": "blueskyweb.xyz", - "name": "EmbeddedMedia", - "$comment": "A list of media embedded in a post or document.", - "locale": { - "en-US": { - "nameSingular": "Embedded Media", - "namePlural": "Embedded Media" - } - }, - "schema": { - "type": "object", - "required": [ - "media" - ], - "properties": { - "media": { - "type": "array", - "items": { - "$ref": "#/$defs/mediaEmbed" - } - } - }, - "$defs": { - "mediaEmbed": { - "type": "object", - "required": [ - "original" - ], - "properties": { - "alt": { - "type": "string" - }, - "thumb": { - "$ref": "#/$defs/mediaEmbedBlob" - }, - "original": { - "$ref": "#/$defs/mediaEmbedBlob" - } - } - }, - "mediaEmbedBlob": { - "type": "object", - "required": [ - "mimeType", - "blobId" - ], - "properties": { - "mimeType": { - "type": "string" - }, - "blobId": { - "type": "string" - } - } - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "$type": "blueskyweb.xyz:EmbeddedMedia", - "media": [ - { - "alt": "Me at the beach", - "thumb": { - "mimeType": "image/png", - "blobId": "1234" - }, - "original": { - "mimeType": "image/png", - "blobId": "1235" - } - } - ] - } - ] - } - } -},{ - "$type": "adxs-view", - "author": "blueskyweb.xyz", - "name": "FeedView", - "$comment": "A computed view of the home feed or a user's feed", - "reads": [ - "blueskyweb.xyz:Feed", - "blueskyweb.xyz:SocialGraph" - ], - "parameters": { - "type": "object", - "properties": { - "author": { - "type": "string" - }, - "limit": { - "type": "number", - "maximum": 100 - }, - "before": { - "type": "string", - "format": "date-time" - } - } - }, - "response": { - "type": "object", - "required": [ - "feed" - ], - "properties": { - "feed": { - "type": "array", - "items": { - "$ref": "#/$defs/feedItem" - } - } - }, - "$defs": { - "feedItem": { - "type": "object", - "required": [ - "uri", - "author", - "record", - "replyCount", - "repostCount", - "likeCount", - "indexedAt" - ], - "properties": { - "uri": { - "type": "string" - }, - "author": { - "$ref": "#/$defs/user" - }, - "repostedBy": { - "$ref": "#/$defs/user" - }, - "record": { - "type": "object" - }, - "embed": { - "oneOf": [ - { - "$ref": "#/$defs/recordEmbed" - }, - { - "$ref": "#/$defs/externalEmbed" - }, - { - "$ref": "#/$defs/unknownEmbed" - } - ] - }, - "replyCount": { - "type": "number" - }, - "repostCount": { - "type": "number" - }, - "likeCount": { - "type": "number" - }, - "indexedAt": { - "type": "string", - "format": "date-time" - }, - "myState": { - "type": "object", - "properties": { - "repost": { - "type": "string" - }, - "like": { - "type": "string" - } - } - } - } - }, - "user": { - "type": "object", - "required": [ - "did", - "name" - ], - "properties": { - "did": { - "type": "string" - }, - "name": { - "type": "string" - }, - "displayName": { - "type": "string", - "maxLength": 64 - } - } - }, - "recordEmbed": { - "type": "object", - "required": [ - "type", - "author", - "record" - ], - "properties": { - "type": { - "const": "record" - }, - "author": { - "$ref": "#/$defs/user" - }, - "record": { - "type": "object" - } - } - }, - "externalEmbed": { - "type": "object", - "required": [ - "type", - "uri", - "title", - "description", - "imageUri" - ], - "properties": { - "type": { - "const": "external" - }, - "uri": { - "type": "string" - }, - "title": { - "type": "string" - }, - "description": { - "type": "string" - }, - "imageUri": { - "type": "string" - } - } - }, - "unknownEmbed": { - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string", - "not": { - "enum": [ - "record", - "external" - ] - } - } - } - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "feed": [ - { - "uri": "adx://alice.com/blueskyweb.xyz:Feed/1", - "author": { - "did": "did:example:1234", - "name": "alice.com", - "displayName": "Alice" - }, - "repostedBy": { - "did": "did:example:1235", - "name": "bob.com", - "displayName": "Bob" - }, - "record": { - "$type": "blueskyweb.xyz:Post", - "text": "Hello, world!", - "createdAt": "2022-07-11T21:55:36.553Z" - }, - "replyCount": 0, - "repostCount": 0, - "likeCount": 0, - "indexedAt": "2022-07-11T21:55:36.553Z" - }, - { - "uri": "adx://alice.com/blueskyweb.xyz:Feed/1", - "author": { - "did": "did:example:1234", - "name": "alice.com", - "displayName": "Alice" - }, - "record": { - "$type": "blueskyweb.xyz:Post", - "text": "This is a link to a post adx://bob.com/blueskyweb.xyz:Feed/1", - "createdAt": "2022-07-11T21:55:36.553Z" - }, - "embed": { - "type": "record", - "author": { - "did": "did:example:1235", - "name": "bob.com", - "displayName": "Bob" - }, - "record": { - "$type": "blueskyweb.xyz:Post", - "text": "Hello, world!", - "createdAt": "2022-07-11T21:55:36.553Z" - } - }, - "replyCount": 0, - "repostCount": 0, - "likeCount": 0, - "indexedAt": "2022-07-11T21:55:36.553Z" - }, - { - "uri": "adx://alice.com/blueskyweb.xyz:Feed/1", - "author": { - "did": "did:example:1234", - "name": "alice.com", - "displayName": "Alice" - }, - "record": { - "$type": "blueskyweb.xyz:Post", - "text": "Check out my website alice.com", - "entities": [ - { - "index": [ - 21, - 30 - ], - "type": "link", - "value": "https://alice.com" - } - ], - "createdAt": "2022-07-11T21:55:36.553Z" - }, - "embed": { - "type": "external", - "uri": "https://alice.com", - "title": "Alice's personal website", - "description": "Just a collection of my thoughts and feelings", - "imageUri": "/cdn/cache/web/https-alice-com.jpeg" - }, - "replyCount": 0, - "repostCount": 0, - "likeCount": 0, - "indexedAt": "2022-07-11T21:55:36.553Z" - }, - { - "uri": "adx://alice.com/blueskyweb.xyz:Feed/1", - "author": { - "did": "did:example:1234", - "name": "alice.com", - "displayName": "Alice" - }, - "record": { - "$type": "blueskyweb.xyz:Post", - "text": "Another test" - }, - "embed": { - "type": "somethingelse", - "embedIsFutureProof": true - }, - "replyCount": 0, - "repostCount": 0, - "likeCount": 0, - "indexedAt": "2022-07-11T21:55:36.553Z" - } - ] - } - ] - } - } -},{ - "$type": "adxs-record", - "author": "blueskyweb.xyz", - "name": "Follow", - "$comment": "A social follow", - "locale": { - "en-US": { - "nameSingular": "Follow", - "namePlural": "Follows" - } - }, - "schema": { - "type": "object", - "required": [ - "subject", - "createdAt" - ], - "properties": { - "subject": { - "type": "string" - }, - "createdAt": { - "type": "string", - "format": "date-time" - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "$type": "blueskyweb.xyz:Follow", - "subject": { - "did": "did:example:1234", - "name": "alice.com" - }, - "createdAt": "2022-07-11T21:55:36.553Z" - } - ] - } - } -},{ - "$type": "adxs-collection", - "author": "blueskyweb.xyz", - "name": "Follows", - "$comment": "Where you put follows.", - "locale": { - "en-US": { - "nameSingular": "Follows", - "namePlural": "Follows Collections" - } - } -},{ - "$type": "adxs-record", - "author": "blueskyweb.xyz", - "name": "Like", - "locale": { - "en-US": { - "nameSingular": "Like", - "namePlural": "Likes" - } - }, - "schema": { - "type": "object", - "required": [ - "subject", - "createdAt" - ], - "properties": { - "subject": { - "type": "string" - }, - "createdAt": { - "type": "string", - "format": "date-time" - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "$type": "blueskyweb.xyz:Like", - "subject": "adx://alice.com/blueskyweb.xyz:Feed/1234", - "createdAt": "2022-07-11T21:55:36.553Z" - } - ] - } - } -},{ - "$type": "adxs-view", - "author": "blueskyweb.xyz", - "name": "LikedByView", - "reads": [ - "blueskyweb.xyz:Feed", - "blueskyweb.xyz:SocialGraph" - ], - "parameters": { - "type": "object", - "required": [ - "uri" - ], - "properties": { - "uri": { - "type": "string" - }, - "limit": { - "type": "number", - "maximum": 100 - }, - "before": { - "type": "string", - "format": "date-time" - } - } - }, - "response": { - "type": "object", - "required": [ - "uri", - "likedBy" - ], - "properties": { - "uri": { - "type": "string" - }, - "likedBy": { - "type": "array", - "items": { - "type": "object", - "required": [ - "did", - "name", - "indexedAt" - ], - "properties": { - "did": { - "type": "string" - }, - "name": { - "type": "string" - }, - "displayName": { - "type": "string", - "maxLength": 64 - }, - "createdAt": { - "type": "string", - "format": "date-time" - }, - "indexedAt": { - "type": "string", - "format": "date-time" - } - } - } - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "uri": "adx://alice.com/blueskyweb.xyz:Feed/1234", - "likedBy": [ - { - "did": "did:example:1234", - "name": "bob.com", - "displayName": "Bob", - "createdAt": "2022-07-11T21:55:36.553Z", - "indexedAt": "2022-07-11T21:55:36.553Z" - } - ] - } - ] - } - } -},{ - "$type": "adxs-collection", - "author": "blueskyweb.xyz", - "name": "Likes", - "$comment": "Where you put likes.", - "locale": { - "en-US": { - "nameSingular": "Likes", - "namePlural": "Likes Collections" - } - } -},{ - "$type": "adxs-view", - "author": "blueskyweb.xyz", - "name": "NotificationsView", - "reads": [ - "blueskyweb.xyz:Feed", - "blueskyweb.xyz:SocialGraph" - ], - "parameters": { - "type": "object", - "properties": { - "limit": { - "type": "number", - "maximum": 100 - }, - "before": { - "type": "string", - "format": "date-time" - } - } - }, - "response": { - "type": "object", - "required": [ - "notifications" - ], - "properties": { - "notifications": { - "type": "array", - "items": { - "$ref": "#/$defs/notification" - } - } - }, - "$defs": { - "notification": { - "type": "object", - "required": [ - "uri", - "author", - "record", - "isRead", - "indexedAt" - ], - "properties": { - "uri": { - "type": "string", - "format": "uri" - }, - "author": { - "type": "object", - "required": [ - "did", - "name", - "displayName" - ], - "properties": { - "did": { - "type": "string" - }, - "name": { - "type": "string" - }, - "displayName": { - "type": "string", - "maxLength": 64 - } - } - }, - "record": { - "type": "object" - }, - "isRead": { - "type": "boolean" - }, - "indexedAt": { - "type": "string", - "format": "date-time" - } - } - } - } - } -},{ - "$type": "adxs-record", - "author": "blueskyweb.xyz", - "name": "Post", - "locale": { - "en-US": { - "nameSingular": "Post", - "namePlural": "Posts" - } - }, - "schema": { - "type": "object", - "required": [ - "text", - "createdAt" - ], - "properties": { - "text": { - "type": "string", - "maxLength": 256 - }, - "entities": { - "$ref": "#/$defs/entity" - }, - "reply": { - "type": "object", - "required": [ - "root" - ], - "properties": { - "root": { - "type": "string" - }, - "parent": { - "type": "string" - } - } - }, - "createdAt": { - "type": "string", - "format": "date-time" - } - }, - "$defs": { - "entity": { - "type": "array", - "items": { - "type": "object", - "required": [ - "index", - "type", - "value" - ], - "properties": { - "index": { - "$ref": "#/$defs/textSlice" - }, - "type": { - "type": "string", - "$comment": "Expected values are 'mention', 'hashtag', and 'link'." - }, - "value": { - "type": "string" - } - } - } - }, - "textSlice": { - "type": "array", - "items": [ - { - "type": "number" - }, - { - "type": "number" - } - ], - "minItems": 2, - "maxItems": 2 - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "$type": "blueskyweb.xyz:Post", - "text": "Hello, world!", - "createdAt": "2022-07-11T21:55:36.553Z" - }, - { - "$type": "blueskyweb.xyz:Post", - "text": "This is a reply to a post", - "reply": { - "root": "adx://alice.com/blueskyweb.xyz:Feed/1", - "parent": "adx://bob.com/blueskyweb.xyz:Feed/9" - }, - "createdAt": "2022-07-11T21:55:36.553Z" - }, - { - "$type": "blueskyweb.xyz:Post", - "text": "Hey @bob.com, are we #CrushingIt or what? Check out my website alice.com", - "entities": [ - { - "index": [ - 4, - 12 - ], - "type": "mention", - "value": "did:example:1234" - }, - { - "index": [ - 21, - 32 - ], - "type": "hashtag", - "value": "CrushingIt" - }, - { - "index": [ - 63, - 72 - ], - "type": "link", - "value": "https://alice.com" - } - ], - "createdAt": "2022-07-11T21:55:36.553Z" - }, - { - "$type": "blueskyweb.xyz:Post", - "text": "This post embeds an image!", - "$ext": { - "blueskyweb.xyz:EmbeddedMedia": { - "media": [ - { - "alt": "Me at the beach", - "thumb": { - "mimeType": "image/png", - "blobId": "1234" - }, - "original": { - "mimeType": "image/png", - "blobId": "1235" - } - } - ] - } - }, - "createdAt": "2022-07-11T21:55:36.553Z" - } - ] - } - } -},{ - "$type": "adxs-view", - "author": "blueskyweb.xyz", - "name": "PostThreadView", - "reads": [ - "blueskyweb.xyz:Feed", - "blueskyweb.xyz:SocialGraph" - ], - "parameters": { - "type": "object", - "required": [ - "uri" - ], - "properties": { - "uri": { - "type": "string" - }, - "depth": { - "type": "number" - } - } - }, - "response": { - "type": "object", - "required": [ - "thread" - ], - "properties": { - "thread": { - "$ref": "#/$defs/post" - } - }, - "$defs": { - "post": { - "type": "object", - "required": [ - "uri", - "author", - "record", - "replyCount", - "likeCount", - "repostCount", - "indexedAt" - ], - "properties": { - "uri": { - "type": "string" - }, - "author": { - "$ref": "#/$defs/user" - }, - "record": { - "type": "object" - }, - "embed": { - "oneOf": [ - { - "$ref": "#/$defs/recordEmbed" - }, - { - "$ref": "#/$defs/externalEmbed" - }, - { - "$ref": "#/$defs/unknownEmbed" - } - ] - }, - "parent": { - "$ref": "#/$defs/post" - }, - "replyCount": { - "type": "number" - }, - "replies": { - "type": "array", - "items": { - "$ref": "#/$defs/post" - } - }, - "likeCount": { - "type": "number" - }, - "repostCount": { - "type": "number" - }, - "indexedAt": { - "type": "string", - "format": "date-time" - }, - "myState": { - "type": "object", - "properties": { - "repost": { - "type": "string" - }, - "like": { - "type": "string" - } - } - } - } - }, - "user": { - "type": "object", - "required": [ - "did", - "name" - ], - "properties": { - "did": { - "type": "string" - }, - "name": { - "type": "string" - }, - "displayName": { - "type": "string", - "maxLength": 64 - } - } - }, - "recordEmbed": { - "type": "object", - "required": [ - "type", - "author", - "record" - ], - "properties": { - "type": { - "const": "record" - }, - "author": { - "$ref": "#/$defs/user" - }, - "record": { - "type": "object" - } - } - }, - "externalEmbed": { - "type": "object", - "required": [ - "type", - "uri", - "title", - "description", - "imageUri" - ], - "properties": { - "type": { - "const": "external" - }, - "uri": { - "type": "string" - }, - "title": { - "type": "string" - }, - "description": { - "type": "string" - }, - "imageUri": { - "type": "string" - } - } - }, - "unknownEmbed": { - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string", - "not": { - "enum": [ - "record", - "external" - ] - } - } - } - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "thread": { - "uri": "adx://alice.com/blueskyweb.xyz:Feed/1", - "author": { - "did": "did:example:1234", - "name": "alice.com", - "displayName": "Alice" - }, - "repostedBy": { - "did": "did:example:1235", - "name": "bob.com", - "displayName": "Bob" - }, - "record": { - "$type": "blueskyweb.xyz:Post", - "text": "Hello, world!", - "createdAt": "2022-07-11T21:55:36.553Z" - }, - "replyCount": 3, - "repostCount": 0, - "likeCount": 0, - "indexedAt": "2022-07-11T21:55:36.553Z", - "replies": [ - { - "uri": "adx://alice.com/blueskyweb.xyz:Feed/2", - "author": { - "did": "did:example:1234", - "name": "alice.com", - "displayName": "Alice" - }, - "record": { - "$type": "blueskyweb.xyz:Post", - "text": "This is a link to a post adx://bob.com/blueskyweb.xyz:Feed/1", - "createdAt": "2022-07-11T21:55:36.553Z" - }, - "embed": { - "type": "record", - "author": { - "did": "did:example:1235", - "name": "bob.com", - "displayName": "Bob" - }, - "record": { - "$type": "blueskyweb.xyz:Post", - "text": "Hello, world!", - "createdAt": "2022-07-11T21:55:36.553Z" - } - }, - "replyCount": 1, - "repostCount": 0, - "likeCount": 0, - "indexedAt": "2022-07-11T21:55:36.553Z", - "replies": [ - { - "uri": "adx://alice.com/blueskyweb.xyz:Feed/3", - "author": { - "did": "did:example:1234", - "name": "alice.com", - "displayName": "Alice" - }, - "record": { - "$type": "blueskyweb.xyz:Post", - "text": "Another test" - }, - "embed": { - "type": "somethingelse", - "embedIsFutureProof": true - }, - "replyCount": 0, - "repostCount": 0, - "likeCount": 0, - "indexedAt": "2022-07-11T21:55:36.553Z" - } - ] - }, - { - "uri": "adx://alice.com/blueskyweb.xyz:Feed/4", - "author": { - "did": "did:example:1234", - "name": "alice.com", - "displayName": "Alice" - }, - "record": { - "$type": "blueskyweb.xyz:Post", - "text": "Check out my website alice.com", - "entities": [ - { - "index": [ - 21, - 30 - ], - "type": "link", - "value": "https://alice.com" - } - ], - "createdAt": "2022-07-11T21:55:36.553Z" - }, - "embed": { - "type": "external", - "uri": "https://alice.com", - "title": "Alice's personal website", - "description": "Just a collection of my thoughts and feelings", - "imageUri": "/cdn/cache/web/https-alice-com.jpeg" - }, - "replyCount": 0, - "repostCount": 0, - "likeCount": 0, - "indexedAt": "2022-07-11T21:55:36.553Z" - } - ] - } - } - ] - } - } -},{ - "$type": "adxs-collection", - "author": "blueskyweb.xyz", - "name": "Posts", - "$comment": "Where you put posts and reposts.", - "locale": { - "en-US": { - "nameSingular": "Posts", - "namePlural": "Posts Collections" - } - } -},{ - "$type": "adxs-record", - "author": "blueskyweb.xyz", - "name": "Profile", - "locale": { - "en-US": { - "nameSingular": "Profile", - "namePlural": "Profiles" - } - }, - "schema": { - "type": "object", - "required": [ - "displayName" - ], - "properties": { - "displayName": { - "type": "string", - "maxLength": 64 - }, - "description": { - "type": "string", - "maxLength": 256 - }, - "badges": { - "type": "array", - "items": { - "$ref": "#/$defs/badgeRef" - } - } - }, - "$defs": { - "badgeRef": { - "type": "object", - "required": [ - "uri" - ], - "properties": { - "uri": { - "type": "string" - } - } - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "$type": "blueskyweb.xyz:Profile", - "displayName": "Alice", - "description": "A cool hacker chick", - "badges": [ - { - "uri": "adx://bob.com/blueskyweb.xyz:Social/1234" - } - ] - } - ] - } - } -},{ - "$type": "adxs-view", - "author": "blueskyweb.xyz", - "name": "ProfileView", - "reads": [ - "blueskyweb.xyz:Feed", - "blueskyweb.xyz:SocialGraph" - ], - "parameters": { - "type": "object", - "required": [ - "user" - ], - "properties": { - "user": { - "type": "string" - } - } - }, - "response": { - "type": "object", - "required": [ - "did", - "name", - "followersCount", - "followsCount", - "postsCount", - "badges" - ], - "properties": { - "did": { - "type": "string" - }, - "name": { - "type": "string" - }, - "displayName": { - "type": "string", - "maxLength": 64 - }, - "description": { - "type": "string", - "maxLength": 256 - }, - "followersCount": { - "type": "number" - }, - "followsCount": { - "type": "number" - }, - "postsCount": { - "type": "number" - }, - "badges": { - "type": "array", - "items": { - "$ref": "#/$defs/badge" - } - }, - "myState": { - "type": "object", - "properties": { - "follow": { - "type": "string" - } - } - } - }, - "$defs": { - "badge": { - "type": "object", - "required": [ - "uri" - ], - "properties": { - "uri": { - "type": "string" - }, - "error": { - "type": "string" - }, - "issuer": { - "type": "object", - "required": [ - "did", - "name", - "displayName" - ], - "properties": { - "did": { - "type": "string" - }, - "name": { - "type": "string" - }, - "displayName": { - "type": "string", - "maxLength": 64 - } - } - }, - "assertion": { - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string" - } - } - }, - "createdAt": { - "type": "string", - "format": "date-time" - } - } - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "did": "did:example:1234", - "name": "alice.com", - "displayName": "Alice", - "description": "A cool hacker chick", - "followersCount": 1000, - "followsCount": 100, - "postsCount": 250, - "badges": [ - { - "uri": "adx://work.com/blueskyweb.xyz:Social/1234", - "issuer": { - "did": "did:example:4321", - "name": "work.com", - "displayName": "Work" - }, - "assertion": { - "type": "employee" - }, - "createdAt": "2010-01-01T19:23:24Z" - }, - { - "uri": "adx://bob.com/blueskyweb.xyz:Social/2222", - "issuer": { - "did": "did:example:5555", - "name": "bob.com", - "displayName": "Bob" - }, - "assertion": { - "type": "tag", - "tag": "tech" - }, - "createdAt": "2010-01-01T19:23:24Z" - } - ] - } - ] - } - } -},{ - "$type": "adxs-collection", - "author": "blueskyweb.xyz", - "name": "Profiles", - "$comment": "Where you put your profile.", - "locale": { - "en-US": { - "nameSingular": "Profiles", - "namePlural": "Profiles Collections" - } - } -},{ - "$type": "adxs-record", - "author": "blueskyweb.xyz", - "name": "Repost", - "locale": { - "en-US": { - "nameSingular": "Repost", - "namePlural": "Reposts" - } - }, - "schema": { - "type": "object", - "required": [ - "subject", - "createdAt" - ], - "properties": { - "subject": { - "type": "string" - }, - "createdAt": { - "type": "string", - "format": "date-time" - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "$type": "blueskyweb.xyz:Repost", - "subject": "adx://alice.com/blueskyweb.xyz:Feed/1234", - "createdAt": "2022-07-11T21:55:36.553Z" - } - ] - } - } -},{ - "$type": "adxs-view", - "author": "blueskyweb.xyz", - "name": "RepostedByView", - "reads": [ - "blueskyweb.xyz:Feed", - "blueskyweb.xyz:SocialGraph" - ], - "parameters": { - "type": "object", - "required": [ - "uri" - ], - "properties": { - "uri": { - "type": "string" - }, - "limit": { - "type": "number", - "maximum": 100 - }, - "before": { - "type": "string", - "format": "date-time" - } - } - }, - "response": { - "type": "object", - "required": [ - "uri", - "repostedBy" - ], - "properties": { - "uri": { - "type": "string" - }, - "repostedBy": { - "type": "array", - "items": { - "type": "object", - "required": [ - "did", - "name", - "indexedAt" - ], - "properties": { - "did": { - "type": "string" - }, - "name": { - "type": "string" - }, - "displayName": { - "type": "string", - "maxLength": 64 - }, - "createdAt": { - "type": "string", - "format": "date-time" - }, - "indexedAt": { - "type": "string", - "format": "date-time" - } - } - } - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "uri": "adx://alice.com/blueskyweb.xyz:Feed/1234", - "repostedBy": [ - { - "did": "did:example:1234", - "name": "bob.com", - "displayName": "Bob", - "createdAt": "2022-07-11T21:55:36.553Z", - "indexedAt": "2022-07-11T21:55:36.553Z" - } - ] - } - ] - } - } -},{ - "$type": "adxs-view", - "author": "blueskyweb.xyz", - "name": "UserFollowersView", - "$comment": "Who is following a user?", - "reads": [ - "blueskyweb.xyz:SocialGraph" - ], - "parameters": { - "type": "object", - "required": [ - "user" - ], - "properties": { - "user": { - "type": "string" - }, - "limit": { - "type": "number", - "maximum": 100 - }, - "before": { - "type": "string", - "format": "date-time" - } - } - }, - "response": { - "type": "object", - "required": [ - "subject", - "followers" - ], - "properties": { - "subject": { - "type": "object", - "required": [ - "did", - "name" - ], - "properties": { - "did": { - "type": "string" - }, - "name": { - "type": "string" - }, - "displayName": { - "type": "string", - "maxLength": 64 - } - } - }, - "followers": { - "type": "array", - "items": { - "type": "object", - "required": [ - "did", - "name", - "indexedAt" - ], - "properties": { - "did": { - "type": "string" - }, - "name": { - "type": "string" - }, - "displayName": { - "type": "string", - "maxLength": 64 - }, - "createdAt": { - "type": "string", - "format": "date-time" - }, - "indexedAt": { - "type": "string", - "format": "date-time" - } - } - } - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "subject": { - "did": "did:example:1235", - "name": "alice.com", - "displayName": "Alice" - }, - "followers": [ - { - "did": "did:example:1234", - "name": "bob.com", - "displayName": "Bob", - "createdAt": "2022-07-11T21:55:36.553Z", - "indexedAt": "2022-07-11T21:55:36.553Z" - } - ] - } - ] - } - } -},{ - "$type": "adxs-view", - "author": "blueskyweb.xyz", - "name": "UserFollowsView", - "$comment": "Who is a user following?", - "reads": [ - "blueskyweb.xyz:SocialGraph" - ], - "parameters": { - "type": "object", - "required": [ - "user" - ], - "properties": { - "user": { - "type": "string" - }, - "limit": { - "type": "number", - "maximum": 100 - }, - "before": { - "type": "string", - "format": "date-time" - } - } - }, - "response": { - "type": "object", - "required": [ - "subject", - "follows" - ], - "properties": { - "subject": { - "type": "object", - "required": [ - "did", - "name" - ], - "properties": { - "did": { - "type": "string" - }, - "name": { - "type": "string" - }, - "displayName": { - "type": "string", - "maxLength": 64 - } - } - }, - "follows": { - "type": "array", - "items": { - "type": "object", - "required": [ - "did", - "name", - "indexedAt" - ], - "properties": { - "did": { - "type": "string" - }, - "name": { - "type": "string" - }, - "displayName": { - "type": "string", - "maxLength": 64 - }, - "createdAt": { - "type": "string", - "format": "date-time" - }, - "indexedAt": { - "type": "string", - "format": "date-time" - } - } - } - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "subject": { - "did": "did:example:1235", - "name": "alice.com", - "displayName": "Alice" - }, - "follows": [ - { - "did": "did:example:1234", - "name": "bob.com", - "displayName": "Bob", - "createdAt": "2022-07-11T21:55:36.553Z", - "indexedAt": "2022-07-11T21:55:36.553Z" - } - ] - } - ] - } - } -}] \ No newline at end of file diff --git a/packages/microblog/src/index.ts b/packages/microblog/src/index.ts deleted file mode 100644 index 8523731c3d0..00000000000 --- a/packages/microblog/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './defs' -export * from './types' -export * from './client' diff --git a/packages/microblog/src/schemas/Badges.json b/packages/microblog/src/schemas/Badges.json deleted file mode 100644 index bbb5ee23bf0..00000000000 --- a/packages/microblog/src/schemas/Badges.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$type": "adxs-collection", - "author": "blueskyweb.xyz", - "name": "Badges", - "$comment": "Where you put badges.", - "locale": { - "en-US": { - "nameSingular": "Badges", - "namePlural": "Badges Collections" - } - } -} \ No newline at end of file diff --git a/packages/microblog/src/schemas/FeedView.json b/packages/microblog/src/schemas/FeedView.json deleted file mode 100644 index 098be5b1ea0..00000000000 --- a/packages/microblog/src/schemas/FeedView.json +++ /dev/null @@ -1,184 +0,0 @@ -{ - "$type": "adxs-view", - "author": "blueskyweb.xyz", - "name": "FeedView", - "$comment": "A computed view of the home feed or a user's feed", - "reads": [ - "blueskyweb.xyz:Feed", - "blueskyweb.xyz:SocialGraph" - ], - "parameters": { - "type": "object", - "properties": { - "author": {"type": "string"}, - "limit": {"type": "number", "maximum": 100}, - "before": {"type": "string", "format": "date-time"} - } - }, - "response": { - "type": "object", - "required": ["feed"], - "properties": { - "feed": { - "type": "array", - "items": {"$ref": "#/$defs/feedItem"} - } - }, - "$defs": { - "feedItem": { - "type": "object", - "required": ["uri", "author", "record", "replyCount", "repostCount", "likeCount", "indexedAt"], - "properties": { - "uri": {"type": "string"}, - "author": {"$ref": "#/$defs/user"}, - "repostedBy": {"$ref": "#/$defs/user"}, - "record": {"type": "object"}, - "embed": { - "oneOf": [ - {"$ref": "#/$defs/recordEmbed"}, - {"$ref": "#/$defs/externalEmbed"}, - {"$ref": "#/$defs/unknownEmbed"} - ] - }, - "replyCount": {"type": "number"}, - "repostCount": {"type": "number"}, - "likeCount": {"type": "number"}, - "indexedAt": {"type": "string", "format": "date-time"}, - "myState": { - "type": "object", - "properties": { - "repost": {"type": "string"}, - "like": {"type": "string"} - } - } - } - }, - "user": { - "type": "object", - "required": ["did", "name"], - "properties": { - "did": {"type": "string"}, - "name": {"type": "string"}, - "displayName": { - "type": "string", - "maxLength": 64 - } - } - }, - "recordEmbed": { - "type": "object", - "required": ["type", "author", "record"], - "properties": { - "type": {"const": "record"}, - "author": {"$ref": "#/$defs/user"}, - "record": {"type": "object"} - } - }, - "externalEmbed": { - "type": "object", - "required": ["type", "uri", "title", "description", "imageUri"], - "properties": { - "type": {"const": "external"}, - "uri": {"type": "string"}, - "title": {"type": "string"}, - "description": {"type": "string"}, - "imageUri": {"type": "string"} - } - }, - "unknownEmbed": { - "type": "object", - "required": ["type"], - "properties": { - "type": { - "type": "string", - "not": {"enum": ["record", "external"]} - } - } - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "feed": [{ - "uri": "adx://alice.com/blueskyweb.xyz:Feed/1", - "author": {"did": "did:example:1234", "name": "alice.com", "displayName": "Alice"}, - "repostedBy": {"did": "did:example:1235", "name": "bob.com", "displayName": "Bob"}, - "record": { - "$type": "blueskyweb.xyz:Post", - "text": "Hello, world!", - "createdAt": "2022-07-11T21:55:36.553Z" - }, - "replyCount": 0, - "repostCount": 0, - "likeCount": 0, - "indexedAt": "2022-07-11T21:55:36.553Z" - }, { - "uri": "adx://alice.com/blueskyweb.xyz:Feed/1", - "author": {"did": "did:example:1234", "name": "alice.com", "displayName": "Alice"}, - "record": { - "$type": "blueskyweb.xyz:Post", - "text": "This is a link to a post adx://bob.com/blueskyweb.xyz:Feed/1", - "createdAt": "2022-07-11T21:55:36.553Z" - }, - "embed": { - "type": "record", - "author": {"did": "did:example:1235", "name": "bob.com", "displayName": "Bob"}, - "record": { - "$type": "blueskyweb.xyz:Post", - "text": "Hello, world!", - "createdAt": "2022-07-11T21:55:36.553Z" - } - }, - "replyCount": 0, - "repostCount": 0, - "likeCount": 0, - "indexedAt": "2022-07-11T21:55:36.553Z" - }, { - "uri": "adx://alice.com/blueskyweb.xyz:Feed/1", - "author": {"did": "did:example:1234", "name": "alice.com", "displayName": "Alice"}, - "record": { - "$type": "blueskyweb.xyz:Post", - "text": "Check out my website alice.com", - "entities": [ - { - "index": [21, 30], - "type": "link", - "value": "https://alice.com" - } - ], - "createdAt": "2022-07-11T21:55:36.553Z" - }, - "embed": { - "type": "external", - "uri": "https://alice.com", - "title": "Alice's personal website", - "description": "Just a collection of my thoughts and feelings", - "imageUri": "/cdn/cache/web/https-alice-com.jpeg" - }, - "replyCount": 0, - "repostCount": 0, - "likeCount": 0, - "indexedAt": "2022-07-11T21:55:36.553Z" - }, { - "uri": "adx://alice.com/blueskyweb.xyz:Feed/1", - "author": {"did": "did:example:1234", "name": "alice.com", "displayName": "Alice"}, - "record": { - "$type": "blueskyweb.xyz:Post", - "text": "Another test" - }, - "embed": { - "type": "somethingelse", - "embedIsFutureProof": true - }, - "replyCount": 0, - "repostCount": 0, - "likeCount": 0, - "indexedAt": "2022-07-11T21:55:36.553Z" - }] - } - ] - } - } -} \ No newline at end of file diff --git a/packages/microblog/src/schemas/Follow.json b/packages/microblog/src/schemas/Follow.json deleted file mode 100644 index e1fddaf051c..00000000000 --- a/packages/microblog/src/schemas/Follow.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "$type": "adxs-record", - "author": "blueskyweb.xyz", - "name": "Follow", - "$comment": "A social follow", - "locale": { - "en-US": { - "nameSingular": "Follow", - "namePlural": "Follows" - } - }, - "schema": { - "type": "object", - "required": ["subject", "createdAt"], - "properties": { - "subject": { "type": "string" }, - "createdAt": {"type": "string", "format": "date-time"} - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "$type": "blueskyweb.xyz:Follow", - "subject": { - "did": "did:example:1234", - "name": "alice.com" - }, - "createdAt": "2022-07-11T21:55:36.553Z" - } - ] - } - } -} \ No newline at end of file diff --git a/packages/microblog/src/schemas/Follows.json b/packages/microblog/src/schemas/Follows.json deleted file mode 100644 index 85e45b421b6..00000000000 --- a/packages/microblog/src/schemas/Follows.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$type": "adxs-collection", - "author": "blueskyweb.xyz", - "name": "Follows", - "$comment": "Where you put follows.", - "locale": { - "en-US": { - "nameSingular": "Follows", - "namePlural": "Follows Collections" - } - } -} \ No newline at end of file diff --git a/packages/microblog/src/schemas/Like.json b/packages/microblog/src/schemas/Like.json deleted file mode 100644 index 1ca49f07ead..00000000000 --- a/packages/microblog/src/schemas/Like.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$type": "adxs-record", - "author": "blueskyweb.xyz", - "name": "Like", - "locale": { - "en-US": { - "nameSingular": "Like", - "namePlural": "Likes" - } - }, - "schema": { - "type": "object", - "required": ["subject", "createdAt"], - "properties": { - "subject": {"type": "string"}, - "createdAt": {"type": "string", "format": "date-time"} - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "$type": "blueskyweb.xyz:Like", - "subject": "adx://alice.com/blueskyweb.xyz:Feed/1234", - "createdAt": "2022-07-11T21:55:36.553Z" - } - ] - } - } -} \ No newline at end of file diff --git a/packages/microblog/src/schemas/LikedByView.json b/packages/microblog/src/schemas/LikedByView.json deleted file mode 100644 index d7c7f5cddbf..00000000000 --- a/packages/microblog/src/schemas/LikedByView.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "$type": "adxs-view", - "author": "blueskyweb.xyz", - "name": "LikedByView", - "reads": [ - "blueskyweb.xyz:Feed", - "blueskyweb.xyz:SocialGraph" - ], - "parameters": { - "type": "object", - "required": ["uri"], - "properties": { - "uri": {"type": "string"}, - "limit": {"type": "number", "maximum": 100}, - "before": {"type": "string", "format": "date-time"} - } - }, - "response": { - "type": "object", - "required": ["uri", "likedBy"], - "properties": { - "uri": {"type": "string"}, - "likedBy": { - "type": "array", - "items": { - "type": "object", - "required": ["did", "name", "indexedAt"], - "properties": { - "did": {"type": "string"}, - "name": {"type": "string"}, - "displayName": { - "type": "string", - "maxLength": 64 - }, - "createdAt": {"type": "string", "format": "date-time"}, - "indexedAt": {"type": "string", "format": "date-time"} - } - } - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "uri": "adx://alice.com/blueskyweb.xyz:Feed/1234", - "likedBy": [ - { - "did": "did:example:1234", - "name": "bob.com", - "displayName": "Bob", - "createdAt": "2022-07-11T21:55:36.553Z", - "indexedAt": "2022-07-11T21:55:36.553Z" - } - ] - } - ] - } - } -} \ No newline at end of file diff --git a/packages/microblog/src/schemas/Likes.json b/packages/microblog/src/schemas/Likes.json deleted file mode 100644 index 4a047a707f3..00000000000 --- a/packages/microblog/src/schemas/Likes.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$type": "adxs-collection", - "author": "blueskyweb.xyz", - "name": "Likes", - "$comment": "Where you put likes.", - "locale": { - "en-US": { - "nameSingular": "Likes", - "namePlural": "Likes Collections" - } - } -} \ No newline at end of file diff --git a/packages/microblog/src/schemas/NotificationsView.json b/packages/microblog/src/schemas/NotificationsView.json deleted file mode 100644 index 6ec5312f3a1..00000000000 --- a/packages/microblog/src/schemas/NotificationsView.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "$type": "adxs-view", - "author": "blueskyweb.xyz", - "name": "NotificationsView", - "reads": [ - "blueskyweb.xyz:Feed", - "blueskyweb.xyz:SocialGraph" - ], - "parameters": { - "type": "object", - "properties": { - "limit": {"type": "number", "maximum": 100}, - "before": {"type": "string", "format": "date-time"} - } - }, - "response": { - "type": "object", - "required": ["notifications"], - "properties": { - "notifications": { - "type": "array", - "items": {"$ref": "#/$defs/notification"} - } - }, - "$defs": { - "notification": { - "type": "object", - "required": ["uri", "author", "record", "isRead", "indexedAt"], - "properties": { - "uri": {"type": "string", "format": "uri"}, - "author": { - "type": "object", - "required": ["did", "name", "displayName"], - "properties": { - "did": {"type": "string"}, - "name": {"type": "string"}, - "displayName": { - "type": "string", - "maxLength": 64 - } - } - }, - "record": {"type": "object"}, - "isRead": {"type": "boolean"}, - "indexedAt": {"type": "string", "format": "date-time"} - } - } - } - } -} \ No newline at end of file diff --git a/packages/microblog/src/schemas/Post.json b/packages/microblog/src/schemas/Post.json deleted file mode 100644 index e1c4a5f1b2c..00000000000 --- a/packages/microblog/src/schemas/Post.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "$type": "adxs-record", - "author": "blueskyweb.xyz", - "name": "Post", - "locale": { - "en-US": { - "nameSingular": "Post", - "namePlural": "Posts" - } - }, - "schema": { - "type": "object", - "required": ["text", "createdAt"], - "properties": { - "text": {"type": "string", "maxLength": 256}, - "entities": {"$ref": "#/$defs/entity"}, - "reply": { - "type": "object", - "required": ["root"], - "properties": { - "root": {"type": "string"}, - "parent": {"type": "string"} - } - }, - "createdAt": {"type": "string", "format": "date-time"} - }, - "$defs": { - "entity": { - "type": "array", - "items": { - "type": "object", - "required": ["index", "type", "value"], - "properties": { - "index": {"$ref": "#/$defs/textSlice"}, - "type": { - "type": "string", - "$comment": "Expected values are 'mention', 'hashtag', and 'link'." - }, - "value": {"type": "string"} - } - } - }, - "textSlice": { - "type": "array", - "items": [{"type": "number"}, {"type": "number"}], - "minItems": 2, - "maxItems": 2 - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "$type": "blueskyweb.xyz:Post", - "text": "Hello, world!", - "createdAt": "2022-07-11T21:55:36.553Z" - }, - { - "$type": "blueskyweb.xyz:Post", - "text": "This is a reply to a post", - "reply": { - "root": "adx://alice.com/blueskyweb.xyz:Feed/1", - "parent": "adx://bob.com/blueskyweb.xyz:Feed/9" - }, - "createdAt": "2022-07-11T21:55:36.553Z" - }, - { - "$type": "blueskyweb.xyz:Post", - "text": "Hey @bob.com, are we #CrushingIt or what? Check out my website alice.com", - "entities": [ - { - "index": [4, 12], - "type": "mention", - "value": "did:example:1234" - }, - { - "index": [21, 32], - "type": "hashtag", - "value": "CrushingIt" - }, - { - "index": [63, 72], - "type": "link", - "value": "https://alice.com" - } - ], - "createdAt": "2022-07-11T21:55:36.553Z" - }, - { - "$type": "blueskyweb.xyz:Post", - "text": "This post embeds an image!", - "$ext": { - "blueskyweb.xyz:EmbeddedMedia": { - "media": [ - { - "alt": "Me at the beach", - "thumb": {"mimeType": "image/png", "blobId": "1234"}, - "original": {"mimeType": "image/png", "blobId": "1235"} - } - ] - } - }, - "createdAt": "2022-07-11T21:55:36.553Z" - } - ] - } - } -} \ No newline at end of file diff --git a/packages/microblog/src/schemas/PostThreadView.json b/packages/microblog/src/schemas/PostThreadView.json deleted file mode 100644 index 07b5b61705f..00000000000 --- a/packages/microblog/src/schemas/PostThreadView.json +++ /dev/null @@ -1,189 +0,0 @@ -{ - "$type": "adxs-view", - "author": "blueskyweb.xyz", - "name": "PostThreadView", - "reads": [ - "blueskyweb.xyz:Feed", - "blueskyweb.xyz:SocialGraph" - ], - "parameters": { - "type": "object", - "required": ["uri"], - "properties": { - "uri": {"type": "string"}, - "depth": {"type": "number"} - } - }, - "response": { - "type": "object", - "required": ["thread"], - "properties": { - "thread": {"$ref": "#/$defs/post"} - }, - "$defs": { - "post": { - "type": "object", - "required": ["uri", "author", "record", "replyCount", "likeCount", "repostCount", "indexedAt"], - "properties": { - "uri": {"type": "string"}, - "author": {"$ref": "#/$defs/user"}, - "record": {"type": "object"}, - "embed": { - "oneOf": [ - {"$ref": "#/$defs/recordEmbed"}, - {"$ref": "#/$defs/externalEmbed"}, - {"$ref": "#/$defs/unknownEmbed"} - ] - }, - "parent": {"$ref": "#/$defs/post"}, - "replyCount": {"type": "number"}, - "replies": { - "type": "array", - "items": {"$ref": "#/$defs/post"} - }, - "likeCount": {"type": "number"}, - "repostCount": {"type": "number"}, - "indexedAt": {"type": "string", "format": "date-time"}, - "myState": { - "type": "object", - "properties": { - "repost": {"type": "string"}, - "like": {"type": "string"} - } - } - } - }, - "user": { - "type": "object", - "required": ["did", "name"], - "properties": { - "did": {"type": "string"}, - "name": {"type": "string"}, - "displayName": { - "type": "string", - "maxLength": 64 - } - } - }, - "recordEmbed": { - "type": "object", - "required": ["type", "author", "record"], - "properties": { - "type": {"const": "record"}, - "author": {"$ref": "#/$defs/user"}, - "record": {"type": "object"} - } - }, - "externalEmbed": { - "type": "object", - "required": ["type", "uri", "title", "description", "imageUri"], - "properties": { - "type": {"const": "external"}, - "uri": {"type": "string"}, - "title": {"type": "string"}, - "description": {"type": "string"}, - "imageUri": {"type": "string"} - } - }, - "unknownEmbed": { - "type": "object", - "required": ["type"], - "properties": { - "type": { - "type": "string", - "not": {"enum": ["record", "external"]} - } - } - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [{ - "thread": { - "uri": "adx://alice.com/blueskyweb.xyz:Feed/1", - "author": {"did": "did:example:1234", "name": "alice.com", "displayName": "Alice"}, - "repostedBy": {"did": "did:example:1235", "name": "bob.com", "displayName": "Bob"}, - "record": { - "$type": "blueskyweb.xyz:Post", - "text": "Hello, world!", - "createdAt": "2022-07-11T21:55:36.553Z" - }, - "replyCount": 3, - "repostCount": 0, - "likeCount": 0, - "indexedAt": "2022-07-11T21:55:36.553Z", - "replies": [ - { - "uri": "adx://alice.com/blueskyweb.xyz:Feed/2", - "author": {"did": "did:example:1234", "name": "alice.com", "displayName": "Alice"}, - "record": { - "$type": "blueskyweb.xyz:Post", - "text": "This is a link to a post adx://bob.com/blueskyweb.xyz:Feed/1", - "createdAt": "2022-07-11T21:55:36.553Z" - }, - "embed": { - "type": "record", - "author": {"did": "did:example:1235", "name": "bob.com", "displayName": "Bob"}, - "record": { - "$type": "blueskyweb.xyz:Post", - "text": "Hello, world!", - "createdAt": "2022-07-11T21:55:36.553Z" - } - }, - "replyCount": 1, - "repostCount": 0, - "likeCount": 0, - "indexedAt": "2022-07-11T21:55:36.553Z", - "replies": [ - { - "uri": "adx://alice.com/blueskyweb.xyz:Feed/3", - "author": {"did": "did:example:1234", "name": "alice.com", "displayName": "Alice"}, - "record": { - "$type": "blueskyweb.xyz:Post", - "text": "Another test" - }, - "embed": { - "type": "somethingelse", - "embedIsFutureProof": true - }, - "replyCount": 0, - "repostCount": 0, - "likeCount": 0, - "indexedAt": "2022-07-11T21:55:36.553Z" - } - ] - }, - { - "uri": "adx://alice.com/blueskyweb.xyz:Feed/4", - "author": {"did": "did:example:1234", "name": "alice.com", "displayName": "Alice"}, - "record": { - "$type": "blueskyweb.xyz:Post", - "text": "Check out my website alice.com", - "entities": [ - { - "index": [21, 30], - "type": "link", - "value": "https://alice.com" - } - ], - "createdAt": "2022-07-11T21:55:36.553Z" - }, - "embed": { - "type": "external", - "uri": "https://alice.com", - "title": "Alice's personal website", - "description": "Just a collection of my thoughts and feelings", - "imageUri": "/cdn/cache/web/https-alice-com.jpeg" - }, - "replyCount": 0, - "repostCount": 0, - "likeCount": 0, - "indexedAt": "2022-07-11T21:55:36.553Z" - } - ] - } - }] - } - } -} \ No newline at end of file diff --git a/packages/microblog/src/schemas/Posts.json b/packages/microblog/src/schemas/Posts.json deleted file mode 100644 index 8103521673f..00000000000 --- a/packages/microblog/src/schemas/Posts.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$type": "adxs-collection", - "author": "blueskyweb.xyz", - "name": "Posts", - "$comment": "Where you put posts and reposts.", - "locale": { - "en-US": { - "nameSingular": "Posts", - "namePlural": "Posts Collections" - } - } -} \ No newline at end of file diff --git a/packages/microblog/src/schemas/Profile.json b/packages/microblog/src/schemas/Profile.json deleted file mode 100644 index fa9e3f4e3b2..00000000000 --- a/packages/microblog/src/schemas/Profile.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "$type": "adxs-record", - "author": "blueskyweb.xyz", - "name": "Profile", - "locale": { - "en-US": { - "nameSingular": "Profile", - "namePlural": "Profiles" - } - }, - "schema": { - "type": "object", - "required": ["displayName"], - "properties": { - "displayName": { - "type": "string", - "maxLength": 64 - }, - "description": { - "type": "string", - "maxLength": 256 - }, - "badges": {"type": "array", "items": {"$ref": "#/$defs/badgeRef"}} - }, - "$defs": { - "badgeRef": { - "type": "object", - "required": ["uri"], - "properties": { - "uri": {"type": "string"} - } - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "$type": "blueskyweb.xyz:Profile", - "displayName": "Alice", - "description": "A cool hacker chick", - "badges": [{ - "uri": "adx://bob.com/blueskyweb.xyz:Social/1234" - }] - } - ] - } - } -} \ No newline at end of file diff --git a/packages/microblog/src/schemas/ProfileView.json b/packages/microblog/src/schemas/ProfileView.json deleted file mode 100644 index 494fb46f599..00000000000 --- a/packages/microblog/src/schemas/ProfileView.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "$type": "adxs-view", - "author": "blueskyweb.xyz", - "name": "ProfileView", - "reads": [ - "blueskyweb.xyz:Feed", - "blueskyweb.xyz:SocialGraph" - ], - "parameters": { - "type": "object", - "required": ["user"], - "properties": { - "user": {"type": "string"} - } - }, - "response": { - "type": "object", - "required": ["did", "name", "followersCount", "followsCount", "postsCount", "badges"], - "properties": { - "did": {"type": "string"}, - "name": {"type": "string"}, - "displayName": { - "type": "string", - "maxLength": 64 - }, - "description": { - "type": "string", - "maxLength": 256 - }, - "followersCount": {"type": "number"}, - "followsCount": {"type": "number"}, - "postsCount": {"type": "number"}, - "badges": {"type": "array", "items": {"$ref":"#/$defs/badge"}}, - "myState": { - "type": "object", - "properties": { - "follow": {"type": "string"} - } - } - }, - "$defs": { - "badge": { - "type": "object", - "required": ["uri"], - "properties": { - "uri": {"type": "string"}, - "error": {"type": "string"}, - "issuer": { - "type": "object", - "required": ["did", "name", "displayName"], - "properties": { - "did": {"type": "string"}, - "name": {"type": "string"}, - "displayName": { - "type": "string", - "maxLength": 64 - } - } - }, - "assertion": { - "type": "object", - "required": ["type"], - "properties": { - "type": {"type": "string"} - } - }, - "createdAt": {"type": "string", "format": "date-time"} - } - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "did": "did:example:1234", - "name": "alice.com", - "displayName": "Alice", - "description": "A cool hacker chick", - "followersCount": 1000, - "followsCount": 100, - "postsCount": 250, - "badges": [ - { - "uri": "adx://work.com/blueskyweb.xyz:Social/1234", - "issuer": { - "did": "did:example:4321", - "name": "work.com", - "displayName": "Work" - }, - "assertion": {"type": "employee"}, - "createdAt": "2010-01-01T19:23:24Z" - }, - { - "uri": "adx://bob.com/blueskyweb.xyz:Social/2222", - "issuer": { - "did": "did:example:5555", - "name": "bob.com", - "displayName": "Bob" - }, - "assertion": {"type": "tag", "tag": "tech"}, - "createdAt": "2010-01-01T19:23:24Z" - } - ] - } - ] - } - } -} \ No newline at end of file diff --git a/packages/microblog/src/schemas/Profiles.json b/packages/microblog/src/schemas/Profiles.json deleted file mode 100644 index 3a66b968956..00000000000 --- a/packages/microblog/src/schemas/Profiles.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$type": "adxs-collection", - "author": "blueskyweb.xyz", - "name": "Profiles", - "$comment": "Where you put your profile.", - "locale": { - "en-US": { - "nameSingular": "Profiles", - "namePlural": "Profiles Collections" - } - } -} \ No newline at end of file diff --git a/packages/microblog/src/schemas/Repost.json b/packages/microblog/src/schemas/Repost.json deleted file mode 100644 index ded35be481b..00000000000 --- a/packages/microblog/src/schemas/Repost.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$type": "adxs-record", - "author": "blueskyweb.xyz", - "name": "Repost", - "locale": { - "en-US": { - "nameSingular": "Repost", - "namePlural": "Reposts" - } - }, - "schema": { - "type": "object", - "required": ["subject", "createdAt"], - "properties": { - "subject": {"type": "string"}, - "createdAt": {"type": "string", "format": "date-time"} - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "$type": "blueskyweb.xyz:Repost", - "subject": "adx://alice.com/blueskyweb.xyz:Feed/1234", - "createdAt": "2022-07-11T21:55:36.553Z" - } - ] - } - } -} \ No newline at end of file diff --git a/packages/microblog/src/schemas/RepostedByView.json b/packages/microblog/src/schemas/RepostedByView.json deleted file mode 100644 index dbc4653e089..00000000000 --- a/packages/microblog/src/schemas/RepostedByView.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "$type": "adxs-view", - "author": "blueskyweb.xyz", - "name": "RepostedByView", - "reads": [ - "blueskyweb.xyz:Feed", - "blueskyweb.xyz:SocialGraph" - ], - "parameters": { - "type": "object", - "required": ["uri"], - "properties": { - "uri": {"type": "string"}, - "limit": {"type": "number", "maximum": 100}, - "before": {"type": "string", "format": "date-time"} - } - }, - "response": { - "type": "object", - "required": ["uri", "repostedBy"], - "properties": { - "uri": {"type": "string"}, - "repostedBy": { - "type": "array", - "items": { - "type": "object", - "required": ["did", "name", "indexedAt"], - "properties": { - "did": {"type": "string"}, - "name": {"type": "string"}, - "displayName": { - "type": "string", - "maxLength": 64 - }, - "createdAt": {"type": "string", "format": "date-time"}, - "indexedAt": {"type": "string", "format": "date-time"} - } - } - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "uri": "adx://alice.com/blueskyweb.xyz:Feed/1234", - "repostedBy": [ - { - "did": "did:example:1234", - "name": "bob.com", - "displayName": "Bob", - "createdAt": "2022-07-11T21:55:36.553Z", - "indexedAt": "2022-07-11T21:55:36.553Z" - } - ] - } - ] - } - } -} \ No newline at end of file diff --git a/packages/microblog/src/schemas/UserFollowersView.json b/packages/microblog/src/schemas/UserFollowersView.json deleted file mode 100644 index ae3d3a2f8e7..00000000000 --- a/packages/microblog/src/schemas/UserFollowersView.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "$type": "adxs-view", - "author": "blueskyweb.xyz", - "name": "UserFollowersView", - "$comment": "Who is following a user?", - "reads": [ - "blueskyweb.xyz:SocialGraph" - ], - "parameters": { - "type": "object", - "required": ["user"], - "properties": { - "user": {"type": "string"}, - "limit": {"type": "number", "maximum": 100}, - "before": {"type": "string", "format": "date-time"} - } - }, - "response": { - "type": "object", - "required": ["subject", "followers"], - "properties": { - "subject": { - "type": "object", - "required": ["did", "name"], - "properties": { - "did": {"type": "string"}, - "name": {"type": "string"}, - "displayName": { - "type": "string", - "maxLength": 64 - } - } - }, - "followers": { - "type": "array", - "items": { - "type": "object", - "required": ["did", "name", "indexedAt"], - "properties": { - "did": {"type": "string"}, - "name": {"type": "string"}, - "displayName": { - "type": "string", - "maxLength": 64 - }, - "createdAt": {"type": "string", "format": "date-time"}, - "indexedAt": {"type": "string", "format": "date-time"} - } - } - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "subject": { - "did": "did:example:1235", - "name": "alice.com", - "displayName": "Alice" - }, - "followers": [ - { - "did": "did:example:1234", - "name": "bob.com", - "displayName": "Bob", - "createdAt": "2022-07-11T21:55:36.553Z", - "indexedAt": "2022-07-11T21:55:36.553Z" - } - ] - } - ] - } - } -} \ No newline at end of file diff --git a/packages/microblog/src/schemas/UserFollowsView.json b/packages/microblog/src/schemas/UserFollowsView.json deleted file mode 100644 index f35382274f2..00000000000 --- a/packages/microblog/src/schemas/UserFollowsView.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "$type": "adxs-view", - "author": "blueskyweb.xyz", - "name": "UserFollowsView", - "$comment": "Who is a user following?", - "reads": [ - "blueskyweb.xyz:SocialGraph" - ], - "parameters": { - "type": "object", - "required": ["user"], - "properties": { - "user": {"type": "string"}, - "limit": {"type": "number", "maximum": 100}, - "before": {"type": "string", "format": "date-time"} - } - }, - "response": { - "type": "object", - "required": ["subject", "follows"], - "properties": { - "subject": { - "type": "object", - "required": ["did", "name"], - "properties": { - "did": {"type": "string"}, - "name": {"type": "string"}, - "displayName": { - "type": "string", - "maxLength": 64 - } - } - }, - "follows": { - "type": "array", - "items": { - "type": "object", - "required": ["did", "name", "indexedAt"], - "properties": { - "did": {"type": "string"}, - "name": {"type": "string"}, - "displayName": { - "type": "string", - "maxLength": 64 - }, - "createdAt": {"type": "string", "format": "date-time"}, - "indexedAt": {"type": "string", "format": "date-time"} - } - } - } - } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "subject": { - "did": "did:example:1235", - "name": "alice.com", - "displayName": "Alice" - }, - "follows": [ - { - "did": "did:example:1234", - "name": "bob.com", - "displayName": "Bob", - "createdAt": "2022-07-11T21:55:36.553Z", - "indexedAt": "2022-07-11T21:55:36.553Z" - } - ] - } - ] - } - } -} \ No newline at end of file diff --git a/packages/microblog/src/types/Badge.ts b/packages/microblog/src/types/Badge.ts deleted file mode 100644 index bd07ae97538..00000000000 --- a/packages/microblog/src/types/Badge.ts +++ /dev/null @@ -1,18 +0,0 @@ -export interface Record { - assertion: InviteAssertion | EmployeeAssertion | TagAssertion | UnknownAssertion; - subject: string; - createdAt: string; -} -export interface InviteAssertion { - type: "invite"; -} -export interface EmployeeAssertion { - type: "employee"; -} -export interface TagAssertion { - type: "tag"; - tag: string; -} -export interface UnknownAssertion { - type: string; -} diff --git a/packages/microblog/src/types/Follow.ts b/packages/microblog/src/types/Follow.ts deleted file mode 100644 index aa72428231b..00000000000 --- a/packages/microblog/src/types/Follow.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface Record { - subject: string; - createdAt: string; -} diff --git a/packages/microblog/src/types/Like.ts b/packages/microblog/src/types/Like.ts deleted file mode 100644 index aa72428231b..00000000000 --- a/packages/microblog/src/types/Like.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface Record { - subject: string; - createdAt: string; -} diff --git a/packages/microblog/src/types/LikedByView.ts b/packages/microblog/src/types/LikedByView.ts deleted file mode 100644 index c2a3ef4a057..00000000000 --- a/packages/microblog/src/types/LikedByView.ts +++ /dev/null @@ -1,16 +0,0 @@ -export interface Params { - uri: string; - limit?: number; - before?: string; -} - -export interface Response { - uri: string; - likedBy: { - did: string; - name: string; - displayName?: string; - createdAt?: string; - indexedAt: string; - }[]; -} diff --git a/packages/microblog/src/types/NotificationsView.ts b/packages/microblog/src/types/NotificationsView.ts deleted file mode 100644 index d0476440463..00000000000 --- a/packages/microblog/src/types/NotificationsView.ts +++ /dev/null @@ -1,19 +0,0 @@ -export interface Params { - limit?: number; - before?: string; -} - -export interface Response { - notifications: Notification[]; -} -export interface Notification { - uri: string; - author: { - did: string; - name: string; - displayName: string; - }; - record: {}; - isRead: boolean; - indexedAt: string; -} diff --git a/packages/microblog/src/types/Post.ts b/packages/microblog/src/types/Post.ts deleted file mode 100644 index 9648e1a73d2..00000000000 --- a/packages/microblog/src/types/Post.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @minItems 2 - * @maxItems 2 - */ -export type TextSlice = [number, number]; -export type Entity = { - index: TextSlice; - type: string; - value: string; -}[]; - -export interface Record { - text: string; - entities?: Entity; - reply?: { - root: string; - parent?: string; - }; - createdAt: string; -} diff --git a/packages/microblog/src/types/ProfileView.ts b/packages/microblog/src/types/ProfileView.ts deleted file mode 100644 index 4928cb14045..00000000000 --- a/packages/microblog/src/types/ProfileView.ts +++ /dev/null @@ -1,30 +0,0 @@ -export interface Params { - user: string; -} - -export interface Response { - did: string; - name: string; - displayName?: string; - description?: string; - followersCount: number; - followsCount: number; - postsCount: number; - badges: Badge[]; - myState?: { - follow?: string; - }; -} -export interface Badge { - uri: string; - error?: string; - issuer?: { - did: string; - name: string; - displayName: string; - }; - assertion?: { - type: string; - }; - createdAt?: string; -} diff --git a/packages/microblog/src/types/Repost.ts b/packages/microblog/src/types/Repost.ts deleted file mode 100644 index aa72428231b..00000000000 --- a/packages/microblog/src/types/Repost.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface Record { - subject: string; - createdAt: string; -} diff --git a/packages/microblog/src/types/RepostedByView.ts b/packages/microblog/src/types/RepostedByView.ts deleted file mode 100644 index 678c4e9f1fa..00000000000 --- a/packages/microblog/src/types/RepostedByView.ts +++ /dev/null @@ -1,16 +0,0 @@ -export interface Params { - uri: string; - limit?: number; - before?: string; -} - -export interface Response { - uri: string; - repostedBy: { - did: string; - name: string; - displayName?: string; - createdAt?: string; - indexedAt: string; - }[]; -} diff --git a/packages/microblog/src/types/UserFollowersView.ts b/packages/microblog/src/types/UserFollowersView.ts deleted file mode 100644 index 9da4809d9e5..00000000000 --- a/packages/microblog/src/types/UserFollowersView.ts +++ /dev/null @@ -1,20 +0,0 @@ -export interface Params { - user: string; - limit?: number; - before?: string; -} - -export interface Response { - subject: { - did: string; - name: string; - displayName?: string; - }; - followers: { - did: string; - name: string; - displayName?: string; - createdAt?: string; - indexedAt: string; - }[]; -} diff --git a/packages/microblog/src/types/UserFollowsView.ts b/packages/microblog/src/types/UserFollowsView.ts deleted file mode 100644 index 9627f07e448..00000000000 --- a/packages/microblog/src/types/UserFollowsView.ts +++ /dev/null @@ -1,20 +0,0 @@ -export interface Params { - user: string; - limit?: number; - before?: string; -} - -export interface Response { - subject: { - did: string; - name: string; - displayName?: string; - }; - follows: { - did: string; - name: string; - displayName?: string; - createdAt?: string; - indexedAt: string; - }[]; -} diff --git a/packages/microblog/src/types/index.ts b/packages/microblog/src/types/index.ts deleted file mode 100644 index 7b32d31c9a3..00000000000 --- a/packages/microblog/src/types/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -export * as Badge from './Badge' -export * as EmbeddedMedia from './EmbeddedMedia' -export * as FeedView from './FeedView' -export * as Follow from './Follow' -export * as Like from './Like' -export * as LikedByView from './LikedByView' -export * as NotificationsView from './NotificationsView' -export * as Post from './Post' -export * as PostThreadView from './PostThreadView' -export * as Profile from './Profile' -export * as ProfileView from './ProfileView' -export * as Repost from './Repost' -export * as RepostedByView from './RepostedByView' -export * as UserFollowersView from './UserFollowersView' -export * as UserFollowsView from './UserFollowsView' \ No newline at end of file diff --git a/packages/microblog/tsconfig.build.json b/packages/microblog/tsconfig.build.json deleted file mode 100644 index 27df65b89e2..00000000000 --- a/packages/microblog/tsconfig.build.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "./tsconfig.json", - "exclude": ["**/*.spec.ts", "**/*.test.ts"] -} \ No newline at end of file diff --git a/packages/microblog/tsconfig.json b/packages/microblog/tsconfig.json deleted file mode 100644 index 624cc32a35f..00000000000 --- a/packages/microblog/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./dist", // Your outDir, - "emitDeclarationOnly": true - }, - "include": ["./src","__tests__/**/**.ts"] -} \ No newline at end of file diff --git a/packages/repo/package.json b/packages/repo/package.json index 765b726b86b..030a9e88281 100644 --- a/packages/repo/package.json +++ b/packages/repo/package.json @@ -19,6 +19,7 @@ "dependencies": { "@adxp/auth": "*", "@adxp/common": "*", + "@adxp/nsid": "*", "@ipld/car": "^3.2.3", "@ipld/dag-cbor": "^7.0.0", "ipld-hashmap": "^2.1.10", diff --git a/packages/repo/src/collection.ts b/packages/repo/src/collection.ts index 5b5f68663d6..f23c3769169 100644 --- a/packages/repo/src/collection.ts +++ b/packages/repo/src/collection.ts @@ -1,23 +1,22 @@ import { TID } from '@adxp/common' +import { NSID } from '@adxp/nsid' import Repo from './repo' export class Collection { repo: Repo - namespace: string - dataset: string + nsid: NSID - constructor(repo: Repo, namespace: string, dataset: string) { + constructor(repo: Repo, nsid: NSID | string) { this.repo = repo - this.namespace = namespace - this.dataset = dataset + this.nsid = typeof nsid === 'string' ? NSID.parse(nsid) : nsid } - dataIdForRecord(tid: TID): string { - return `${this.name()}/${tid.toString()}` + name(): string { + return this.nsid.toString() } - name(): string { - return `${this.namespace}/${this.dataset}` + dataIdForRecord(tid: TID): string { + return `${this.name()}/${tid.toString()}` } async getRecord(tid: TID): Promise { diff --git a/packages/repo/src/mst/diff.ts b/packages/repo/src/mst/diff.ts index 53aeb9376f3..eae53b6de9f 100644 --- a/packages/repo/src/mst/diff.ts +++ b/packages/repo/src/mst/diff.ts @@ -83,10 +83,10 @@ export class DataDiff { neededCapabilities(rootDid: string): auth.ucans.Capability[] { return this.updatedKeys().map((key) => { const parts = key.split('/') - if (parts.length !== 3) { + if (parts.length !== 2) { throw new Error(`Invalid record id: ${key}`) } - return auth.writeCap(rootDid, parts[0], parts[1], parts[2]) + return auth.writeCap(rootDid, parts[0], parts[1]) }) } } diff --git a/packages/repo/src/repo.ts b/packages/repo/src/repo.ts index 4256a933167..d4d367b0b96 100644 --- a/packages/repo/src/repo.ts +++ b/packages/repo/src/repo.ts @@ -125,17 +125,7 @@ export class Repo { `Collection names may not be longer than 256 chars: ${name}`, ) } - const parts = name.split('/') - if (parts.length > 2) { - throw new Error( - `Only one level of namespacing allowed in collection names: ${name}`, - ) - } else if (parts.length < 2) { - throw new Error( - `Expected at least one level of namespacing in collection name: ${name}`, - ) - } - return new Collection(this, parts[0], parts[1]) + return new Collection(this, name) } // The repo is mutable & things can change while you perform an operation diff --git a/packages/repo/tests/_util.ts b/packages/repo/tests/_util.ts index 9d2bd7329d0..6e12d0ff1e0 100644 --- a/packages/repo/tests/_util.ts +++ b/packages/repo/tests/_util.ts @@ -72,7 +72,7 @@ export const generateObject = (): Record => { // Mass repo mutations & checking // ------------------------------- -export const testCollections = ['bsky/posts', 'bsky/likes'] +export const testCollections = ['com.example.posts', 'com.example.likes'] export type CollectionData = Record export type RepoData = Record @@ -157,8 +157,8 @@ export const checkRepoDiff = async ( data: RepoData, ): Promise => { const parts = key.split('/') - const collection = parts.slice(0, 2).join('/') - const obj = (data[collection] || {})[parts[2]] + const collection = parts[0] + const obj = (data[collection] || {})[parts[1]] return obj === undefined ? undefined : fakeStore.put(obj as any) } diff --git a/packages/repo/tests/repo.test.ts b/packages/repo/tests/repo.test.ts index 4891649263f..887dc8a761c 100644 --- a/packages/repo/tests/repo.test.ts +++ b/packages/repo/tests/repo.test.ts @@ -18,7 +18,7 @@ describe('Repo', () => { }) it('does basic operations', async () => { - const collection = repo.getCollection('bsky/posts') + const collection = repo.getCollection('com.example.posts') const obj = util.generateObject() const tid = await collection.createRecord(obj) diff --git a/packages/repo/tests/sync.test.ts b/packages/repo/tests/sync.test.ts index 656e97e8ace..830def7b6d2 100644 --- a/packages/repo/tests/sync.test.ts +++ b/packages/repo/tests/sync.test.ts @@ -66,7 +66,10 @@ describe('Sync', () => { it('throws an error on invalid UCANs', async () => { const obj = util.generateObject() const cid = await aliceBlockstore.put(obj) - const updatedData = await aliceRepo.data.add(`test/coll/${TID.next()}`, cid) + const updatedData = await aliceRepo.data.add( + `com.example.test/${TID.next()}`, + cid, + ) // we create an unrelated token for bob & try to permission alice's repo commit with it const bobAuth = await auth.MemoryStore.load() const badUcan = await bobAuth.claimFull() @@ -93,7 +96,10 @@ describe('Sync', () => { it('throws on a bad signature', async () => { const obj = util.generateObject() const cid = await aliceBlockstore.put(obj) - const updatedData = await aliceRepo.data.add(`test/coll/${TID.next()}`, cid) + const updatedData = await aliceRepo.data.add( + `com.example.test/${TID.next()}`, + cid, + ) const auth_token = await aliceRepo.ucanForOperation(updatedData) const dataCid = await updatedData.save() const root: RepoRoot = { diff --git a/packages/repo/tsconfig.json b/packages/repo/tsconfig.json index ba8bab214a2..ca20365f26f 100644 --- a/packages/repo/tsconfig.json +++ b/packages/repo/tsconfig.json @@ -8,5 +8,6 @@ "references": [ { "path": "../auth/tsconfig.build.json" }, { "path": "../common/tsconfig.build.json" }, + { "path": "../nsid/tsconfig.build.json" }, ] } \ No newline at end of file diff --git a/packages/server/package.json b/packages/server/package.json index 7969d488202..1bf9ad276ad 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -3,6 +3,7 @@ "version": "0.0.2", "main": "src/index.ts", "scripts": { + "codegen": "xrpc-cli gen-server ./src/xrpc ../../schemas/todo.adx/*", "build": "node ./build.js", "start": "node dist/index.js", "test": "jest", @@ -18,7 +19,6 @@ "@adxp/auth": "*", "@adxp/common": "*", "@adxp/crypto": "*", - "@adxp/microblog": "*", "@adxp/repo": "*", "@adxp/schemas": "*", "cors": "^2.8.5", diff --git a/packages/server/src/api/index.ts b/packages/server/src/api/index.ts new file mode 100644 index 00000000000..0a43101cbba --- /dev/null +++ b/packages/server/src/api/index.ts @@ -0,0 +1,10 @@ +import { createServer } from '../xrpc' +import todoAdx from './todo/adx' +import todoSocial from './todo/social' + +export default function () { + const server = createServer() + todoAdx(server) + todoSocial(server) + return server +} diff --git a/packages/server/src/api/todo/adx/account.ts b/packages/server/src/api/todo/adx/account.ts new file mode 100644 index 00000000000..460f49e02a4 --- /dev/null +++ b/packages/server/src/api/todo/adx/account.ts @@ -0,0 +1,58 @@ +import { Server } from '../../../xrpc' +import { InvalidRequestError } from '@adxp/xrpc-server' +import * as util from '../../../util' +import { Repo } from '@adxp/repo' +import * as auth from '@adxp/auth' + +export default function (server: Server) { + server.todo.adx.getAccount(() => { + // TODO + return { encoding: '', body: {} } + }) + + server.todo.adx.createAccount(async (_params, input, _req, res) => { + const { did, username } = input.body + if (username.startsWith('did:')) { + throw new InvalidRequestError( + 'Cannot register a username that starts with `did:`', + ) + } + if (!did.startsWith('did:')) { + throw new InvalidRequestError( + 'Cannot register a did that does not start with `did:`', + ) + } + + const { db, blockstore, keypair } = util.getLocals(res) + await db.registerUser(username, did) + + const authStore = await auth.AuthStore.fromTokens(keypair, []) + const repo = await Repo.create(blockstore, did, authStore) + await db.setRepoRoot(did, repo.cid) + + // const authStore = await serverAuth.checkReq( + // req, + // res, + // auth.maintenanceCap(did), + // ) + // const host = util.getOwnHost(req) + + // if (await db.isNameRegistered(username, host)) { + // throw new ServerError(409, 'Username already taken') + // } else if (await db.isDidRegistered(did)) { + // throw new ServerError(409, 'Did already registered') + // } + + // await db.registerDid(username, did, host) + // // create empty repo + // if (createRepo) { + // const repo = await Repo.create(blockstore, did, authStore) + // await db.createRepoRoot(did, repo.cid) + // } + }) + + server.todo.adx.deleteAccount(() => { + // TODO + return { encoding: '', body: {} } + }) +} diff --git a/packages/server/src/api/todo/adx/index.ts b/packages/server/src/api/todo/adx/index.ts new file mode 100644 index 00000000000..2b44dff3adb --- /dev/null +++ b/packages/server/src/api/todo/adx/index.ts @@ -0,0 +1,14 @@ +import { Server } from '../../../xrpc' +import names from './names' +import session from './session' +import account from './account' +import repo from './repo' +import sync from './sync' + +export default function (server: Server) { + names(server) + session(server) + account(server) + repo(server) + sync(server) +} diff --git a/packages/server/src/api/todo/adx/names.ts b/packages/server/src/api/todo/adx/names.ts new file mode 100644 index 00000000000..b3eb8eee9bb --- /dev/null +++ b/packages/server/src/api/todo/adx/names.ts @@ -0,0 +1,14 @@ +import { Server } from '../../../xrpc' +import * as util from '../../../util' + +export default function (server: Server) { + server.todo.adx.resolveName((_params, _in, _req, res) => { + const keypair = util.getKeypair(res) + // Return the server's did + // TODO check params.name + return { + encoding: 'application/json', + body: { did: keypair.did() }, + } + }) +} diff --git a/packages/server/src/api/todo/adx/repo.ts b/packages/server/src/api/todo/adx/repo.ts new file mode 100644 index 00000000000..793440d249b --- /dev/null +++ b/packages/server/src/api/todo/adx/repo.ts @@ -0,0 +1,226 @@ +import { Server } from '../../../xrpc' +import { InvalidRequestError } from '@adxp/xrpc-server' +import { resolveName, AdxUri, TID } from '@adxp/common' +import * as auth from '@adxp/auth' +import * as didSdk from '@adxp/did-sdk' +import * as repoDiff from '../../../repo-diff' +import * as util from '../../../util' + +async function resolveNameWrapped(name: string) { + try { + return await resolveName(name) + } catch (e) { + throw new InvalidRequestError(`Failed to resolve name: ${name}`) + } +} + +export default function (server: Server) { + server.todo.adx.repoDescribe(async (params, _in, _req, res) => { + const { nameOrDid } = params + + let name: string + let did: string + let didDoc: didSdk.DIDDocument + let nameIsCorrect: boolean | undefined + + // @TODO add back once we have a did network + // if (nameOrDid.startsWith('did:')) { + // did = nameOrDid + // didDoc = await resolveDidWrapped(did) + // name = 'undefined' // TODO: need to decide how username gets published in the did doc + // if (confirmName) { + // const namesDeclaredDid = await resolveNameWrapped(name) + // nameIsCorrect = did === namesDeclaredDid + // } + // } else { + // name = nameOrDid + // did = await resolveNameWrapped(name) + // didDoc = await resolveDidWrapped(did) + // if (confirmName) { + // const didsDeclaredName = 'undefined' // TODO: need to decide how username gets published in the did doc + // nameIsCorrect = name === didsDeclaredName + // } + // } + + const db = util.getDB(res) + const user = await db.getUser(nameOrDid) + if (user === null) { + throw new InvalidRequestError(`Could not find user: ${nameOrDid}`) + } + didDoc = {} as any + nameIsCorrect = true + + const collections = await db.listCollectionsForDid(user.did) + + return { + encoding: 'application/json', + body: { + name: user.username, + did: user.did, + didDoc, + collections, + nameIsCorrect, + }, + } + }) + + server.todo.adx.repoListRecords(async (params, _in, _req, res) => { + const { nameOrDid, type, limit, before, after, reverse } = params + + const db = util.getDB(res) + const did = nameOrDid.startsWith('did:') + ? nameOrDid + : (await db.getUser(nameOrDid))?.did + if (!did) { + throw new InvalidRequestError(`Could not find did for ${nameOrDid}`) + } + + const records = await db.listRecordsForCollection( + did, + type, + limit || 50, + reverse || false, + before, + after, + ) + + return { + encoding: 'application/json', + body: { records: records as { uri: string; value: {} }[] }, + } + }) + + server.todo.adx.repoGetRecord(async (params, _in, _req, res) => { + const { nameOrDid, type, tid } = params + + const did = nameOrDid.startsWith('did:') + ? nameOrDid + : await resolveNameWrapped(nameOrDid) + const uri = new AdxUri(`${did}/${type}/${tid}`) + + const db = util.getDB(res) + const record = await db.getRecord(uri) + if (!record) { + throw new InvalidRequestError(`Could not locate record: ${uri}`) + } + return { + encoding: 'application/json', + body: { uri: uri.toString(), value: record }, + } + }) + + server.todo.adx.repoBatchWrite(async (params, input, _req, res) => { + const { did, validate } = params + const tx = input.body + const db = util.getDB(res) + if (validate) { + for (const write of tx.writes) { + if (write.action === 'create' || write.action === 'update') { + const validation = db.validateRecord(write.collection, write.value) + if (!validation.valid) { + throw new InvalidRequestError( + `Invalid ${write.collection} record: ${validation.error}`, + ) + } + } + } + } + // @TODO add user auth here! + const serverKey = util.getKeypair(res) + const authStore = await auth.AuthStore.fromTokens(serverKey, []) + const repo = await util.loadRepo(res, did, authStore) + const prevCid = repo.cid + await repo.batchWrite(tx.writes) + // @TODO: do something better here instead of rescanning for diff + const diff = await repo.verifySetOfUpdates(prevCid, repo.cid) + try { + await repoDiff.processDiff(db, repo, diff) + } catch (err) { + if (validate) { + throw new InvalidRequestError(`Could not index record: ${err}`) + } + } + await db.setRepoRoot(did, repo.cid) + + return { + encoding: 'application/json', + body: {}, + } + }) + + server.todo.adx.repoCreateRecord(async (params, input, _req, res) => { + const { did, type, validate } = params + const db = util.getDB(res) + if (validate) { + const validation = db.validateRecord(type, input.body) + if (!validation.valid) { + throw new InvalidRequestError( + `Invalid ${type} record: ${validation.error}`, + ) + } + } + const serverKey = util.getKeypair(res) + const authStore = await auth.AuthStore.fromTokens(serverKey, []) + const repo = await util.loadRepo(res, did, authStore) + const tid = await repo.getCollection(type).createRecord(input.body) + const uri = new AdxUri(`${did}/${type}/${tid.toString()}`) + try { + await db.indexRecord(uri, input.body) + } catch (err) { + if (validate) { + throw new InvalidRequestError(`Could not index record: ${err}`) + } + } + await db.setRepoRoot(did, repo.cid) + // @TODO update subscribers + + return { + encoding: 'application/json', + body: { uri: uri.toString() }, + } + }) + + server.todo.adx.repoPutRecord(async (params, input, _req, res) => { + const { did, type, tid, validate } = params + const db = util.getDB(res) + if (validate) { + const validation = db.validateRecord(type, input.body) + if (!validation.valid) { + throw new InvalidRequestError( + `Invalid ${type} record: ${validation.error}`, + ) + } + } + const serverKey = util.getKeypair(res) + const authStore = await auth.AuthStore.fromTokens(serverKey, []) + const repo = await util.loadRepo(res, did, authStore) + await repo.getCollection(type).updateRecord(TID.fromStr(tid), input.body) + const uri = new AdxUri(`${did}/${type}/${tid.toString()}`) + try { + await db.indexRecord(uri, input.body) + } catch (err) { + if (validate) { + throw new InvalidRequestError(`Could not index record: ${err}`) + } + } + await db.setRepoRoot(did, repo.cid) + // @TODO update subscribers + return { + encoding: 'application/json', + body: { uri: uri.toString() }, + } + }) + + server.todo.adx.repoDeleteRecord(async (params, _input, _req, res) => { + const { did, type, tid } = params + const db = util.getDB(res) + const serverKey = util.getKeypair(res) + const authStore = await auth.AuthStore.fromTokens(serverKey, []) + const repo = await util.loadRepo(res, did, authStore) + await repo.getCollection(type).deleteRecord(TID.fromStr(tid)) + const uri = new AdxUri(`${did}/${type}/${tid.toString()}`) + await db.deleteRecord(uri) + await db.setRepoRoot(did, repo.cid) + // @TODO update subscribers + }) +} diff --git a/packages/server/src/api/todo/adx/session.ts b/packages/server/src/api/todo/adx/session.ts new file mode 100644 index 00000000000..b5be1d0c151 --- /dev/null +++ b/packages/server/src/api/todo/adx/session.ts @@ -0,0 +1,16 @@ +import { Server } from '../../../xrpc' + +export default function (server: Server) { + server.todo.adx.getSession(() => { + // TODO + return { encoding: '', body: {} } + }) + server.todo.adx.createSession(() => { + // TODO + return { encoding: '', body: {} } + }) + server.todo.adx.deleteSession(() => { + // TODO + return { encoding: '', body: {} } + }) +} diff --git a/packages/server/src/api/todo/adx/sync.ts b/packages/server/src/api/todo/adx/sync.ts new file mode 100644 index 00000000000..684f690c58a --- /dev/null +++ b/packages/server/src/api/todo/adx/sync.ts @@ -0,0 +1,73 @@ +import { Server } from '../../../xrpc' +import { InvalidRequestError } from '@adxp/xrpc-server' +import { def as common } from '@adxp/common' +import * as util from '../../../util' +import { DataDiff, Repo } from '@adxp/repo' +import * as repoDiff from '../../../repo-diff' + +export default function (server: Server) { + server.todo.adx.syncGetRoot(async (params, _in, _req, res) => { + const { did } = params + const db = util.getDB(res) + const root = await db.getRepoRoot(did) + if (root === null) { + throw new InvalidRequestError(`Could not find root for DID: ${did}`) + } + return { + encoding: 'application/json', + body: { root: root.toString() }, + } + }) + + server.todo.adx.syncGetRepo(async (params, _in, _req, res) => { + const { did, from = null } = params + const fromCid = from ? common.strToCid.parse(from) : null + const repo = await util.loadRepo(res, did) + const diff = await repo.getDiffCar(fromCid) + return { + encoding: 'application/cbor', + body: Buffer.from(diff), + } + }) + + server.todo.adx.syncUpdateRepo(async (params, input, _req, res) => { + // we don't need auth here because the auth is on the data structure 😎 + const { did } = params + const bytes = input.body + const db = util.getDB(res) + + // @TODO add something back here. new route for repos not on server? + + // check to see if we have their username in DB, for indexed queries + // const haveUsername = await db.isDidRegistered(did) + // if (!haveUsername) { + // const username = await service.getUsernameFromDidNetwork(did) + // if (username) { + // const [name, host] = username.split('@') + // await db.registerDid(name, did, host) + // } + // } + + const maybeRepo = await util.maybeLoadRepo(res, did) + const isNewRepo = maybeRepo === null + let repo: Repo + let diff: DataDiff + + // @TODO: we should do these on a temp in-memory blockstore before merging down to our on-disk one + if (!isNewRepo) { + repo = maybeRepo + await repo.loadAndVerifyDiff + diff = await repo.loadAndVerifyDiff(bytes) + } else { + const blockstore = util.getBlockstore(res) + repo = await Repo.fromCarFile(bytes, blockstore) + diff = await repo.verifySetOfUpdates(null, repo.cid) + } + + await repoDiff.processDiff(db, repo, diff) + + // await subscriptions.notifySubscribers(db, repo) + + await db.setRepoRoot(did, repo.cid) + }) +} diff --git a/packages/server/src/api/todo/social/getFeed.ts b/packages/server/src/api/todo/social/getFeed.ts new file mode 100644 index 00000000000..18de5dbfa34 --- /dev/null +++ b/packages/server/src/api/todo/social/getFeed.ts @@ -0,0 +1,152 @@ +import { Server } from '../../../xrpc' +import { AuthRequiredError } from '@adxp/xrpc-server' +import * as GetFeed from '../../../xrpc/types/todo/social/getFeed' +import { FollowIndex } from '../../../db/records/follow' +import { PostIndex } from '../../../db/records/post' +import { ProfileIndex } from '../../../db/records/profile' +import { UserDid } from '../../../db/user-dids' +import * as util from '../../../db/util' +import { LikeIndex } from '../../../db/records/like' +import { RepostIndex } from '../../../db/records/repost' +import { AdxRecord } from '../../../db/record' +import { getLocals } from '../../../util' + +export default function (server: Server) { + server.todo.social.getFeed( + async (params: GetFeed.QueryParams, _input, req, res) => { + const { author, limit, before } = params + + // @TODO switch out for actual auth + const requester = req.headers.authorization + if (!requester) { + throw new AuthRequiredError() + } + + const { db } = getLocals(res) + const builder = db.db.createQueryBuilder() + + if (author === undefined) { + builder + .from(UserDid, 'user') + .innerJoin(FollowIndex, 'follow', 'follow.creator = user.did') + .leftJoin(RepostIndex, 'repost', 'repost.creator = follow.subject') + .innerJoin( + PostIndex, + 'post', + 'post.creator = follow.subject OR post.uri = repost.subject', + ) + .leftJoin(UserDid, 'author', 'author.did = post.creator') + .where('user.did = :did', { did: requester }) + } else { + const authorWhere = author.startsWith('did:') + ? 'author.did' + : 'author.username' + builder + .from(PostIndex, 'post') + .leftJoin(RepostIndex, 'repost', 'repost.subject = post.uri') + .leftJoin( + UserDid, + 'author', + 'author.did = post.creator OR author.did = repost.creator', + ) + .where(`${authorWhere} = :author`, { author }) + } + + builder + .select([ + 'post.uri AS uri', + 'author.did AS authorDid', + 'author.username AS authorName', + 'author_profile.displayName AS authorDisplayName', + 'reposted_by.did AS repostedByDid', + 'reposted_by.username AS repostedByName', + 'reposted_by_profile.displayName AS repostedByDisplayName', + 'record.raw AS rawRecord', + 'like_count.count AS likeCount', + 'repost_count.count AS repostCount', + 'reply_count.count AS replyCount', + 'requester_repost.uri AS requesterRepost', + 'requester_like.uri AS requesterLike', + 'record.indexedAt AS indexedAt', + ]) + .leftJoin( + ProfileIndex, + 'author_profile', + 'author_profile.creator = author.did', + ) + .leftJoin(UserDid, 'reposted_by', 'reposted_by.did = repost.creator') + .leftJoin( + ProfileIndex, + 'reposted_by_profile', + 'reposted_by_profile.creator = reposted_by.did', + ) + .leftJoin(AdxRecord, 'record', 'record.uri = post.uri') + .leftJoin( + util.countSubquery(LikeIndex, 'subject'), + 'like_count', + 'like_count.subject = post.uri', + ) + .leftJoin( + util.countSubquery(RepostIndex, 'subject'), + 'repost_count', + 'repost_count.subject = post.uri', + ) + .leftJoin( + util.countSubquery(PostIndex, 'replyParent'), + 'reply_count', + 'reply_count.subject = post.uri', + ) + .leftJoin( + RepostIndex, + 'requester_repost', + `requester_repost.creator = :requester AND requester_repost.subject = post.uri`, + { requester }, + ) + .leftJoin( + RepostIndex, + 'requester_like', + `requester_like.creator = :requester AND requester_like.subject = post.uri`, + { requester }, + ) + .orderBy('post.createdAt', 'DESC') + .groupBy('post.uri') + + if (before !== undefined) { + builder.andWhere('post.createdAt < :before', { before }) + } + if (limit !== undefined) { + builder.limit(limit) + } + + const queryRes = await builder.getRawMany() + + // @TODO add embeds + const feed: GetFeed.FeedItem[] = queryRes.map((row) => ({ + uri: row.uri, + author: { + did: row.authorDid, + name: row.authorName, + displayName: row.authorDisplayName || undefined, + }, + repostedBy: row.repostedByDid + ? { + did: row.repostedByDid, + name: row.repostedByName, + displayName: row.repostedByDisplayName || undefined, + } + : undefined, + record: JSON.parse(row.rawRecord), + replyCount: row.replyCount || 0, + repostCount: row.repostCount || 0, + likeCount: row.likeCount || 0, + indexedAt: row.indexedAt, + myState: { + repost: row.requesterRepost || undefined, + like: row.requesterLike || undefined, + }, + })) + + return { encoding: 'application/json', body: { feed } } + }, + ) +} diff --git a/packages/server/src/api/todo/social/getLikedBy.ts b/packages/server/src/api/todo/social/getLikedBy.ts new file mode 100644 index 00000000000..a3a45547ed3 --- /dev/null +++ b/packages/server/src/api/todo/social/getLikedBy.ts @@ -0,0 +1,56 @@ +import { Server } from '../../../xrpc' +import * as GetLikedBy from '../../../xrpc/types/todo/social/getLikedBy' +import { AdxRecord } from '../../../db/record' +import { LikeIndex } from '../../../db/records/like' +import { ProfileIndex } from '../../../db/records/profile' +import { UserDid } from '../../../db/user-dids' +import { getLocals } from '../../../util' + +export default function (server: Server) { + server.todo.social.getLikedBy( + async (params: GetLikedBy.QueryParams, _input, _req, res) => { + const { uri, limit, before } = params + const { db } = getLocals(res) + + const builder = db.db + .createQueryBuilder() + .select([ + 'user.did AS did', + 'user.username AS name', + 'profile.displayName AS displayName', + 'like.createdAt AS createdAt', + 'record.indexedAt AS indexedAt', + ]) + .from(LikeIndex, 'like') + .leftJoin(AdxRecord, 'record', 'like.uri = record.uri') + .leftJoin(UserDid, 'user', 'like.creator = user.did') + .leftJoin(ProfileIndex, 'profile', 'profile.creator = user.did') + .where('like.subject = :uri', { uri }) + .orderBy('like.createdAt') + + if (before !== undefined) { + builder.andWhere('like.createdAt < :before', { before }) + } + if (limit !== undefined) { + builder.limit(limit) + } + const likedByRes = await builder.getRawMany() + + const likedBy = likedByRes.map((row) => ({ + did: row.did, + name: row.name, + displayName: row.displayName || undefined, + createdAt: row.createdAt, + indexedAt: row.indexedAt, + })) + + return { + encoding: 'application/json', + body: { + uri, + likedBy, + }, + } + }, + ) +} diff --git a/packages/server/src/db/views/postThread.ts b/packages/server/src/api/todo/social/getPostThread.ts similarity index 62% rename from packages/server/src/db/views/postThread.ts rename to packages/server/src/api/todo/social/getPostThread.ts index 207f9fb3c86..9b6855d39c6 100644 --- a/packages/server/src/db/views/postThread.ts +++ b/packages/server/src/api/todo/social/getPostThread.ts @@ -1,63 +1,60 @@ -import { PostThreadView } from '@adxp/microblog' +import { Server } from '../../../xrpc' +import { AuthRequiredError } from '@adxp/xrpc-server' import { DataSource } from 'typeorm' -import { PostIndex } from '../records/post' -import { ProfileIndex } from '../records/profile' -import { UserDid } from '../user-dids' -import schemas from '../schemas' -import * as util from '../util' -import { DbViewPlugin } from '../types' -import { LikeIndex } from '../records/like' -import { RepostIndex } from '../records/repost' -import { AdxRecord } from '../record' +import * as GetPostThread from '../../../xrpc/types/todo/social/getPostThread' +import { PostIndex } from '../../../db/records/post' +import { ProfileIndex } from '../../../db/records/profile' +import { UserDid } from '../../../db/user-dids' +import * as util from '../../../db/util' +import { LikeIndex } from '../../../db/records/like' +import { RepostIndex } from '../../../db/records/repost' +import { AdxRecord } from '../../../db/record' +import { getLocals } from '../../../util' -const viewId = 'blueskyweb.xyz:PostThreadView' -const validator = schemas.createViewValidator(viewId) -const validParams = (obj: unknown): obj is PostThreadView.Params => { - return validator.isParamsValid(obj) -} - -export const viewFn = - (db: DataSource) => - async ( - params: Record, - requester: string, - ): Promise => { - if (params['depth']) { - params['depth'] = parseInt(params['depth'] as string) - } - if (!validParams(params)) { - throw new Error(`Invalid params for ${viewId}`) - } +export default function (server: Server) { + server.todo.social.getPostThread( + async (params: GetPostThread.QueryParams, _input, req, res) => { + const { uri, depth = 6 } = params + const { db } = getLocals(res) - const { uri, depth = 1 } = params + // @TODO switch out for actual auth + const requester = req.headers.authorization + if (!requester) { + throw new AuthRequiredError() + } - const res = await postInfoBuilder(db, requester) - .where('post.uri = :uri', { - uri, - }) - .getRawOne() + const queryRes = await postInfoBuilder(db.db, requester) + .where('post.uri = :uri', { + uri, + }) + .getRawOne() - let thread = rowToPost(res) - if (depth > 0) { - thread.replies = await getReplies(db, thread, depth - 1, requester) - } - if (res.parent !== null) { - const parentRes = await postInfoBuilder(db, requester).where( - 'post.uri = :uri', - { uri: res.parent }, - ) - thread.parent = rowToPost(parentRes) - } + let thread = rowToPost(queryRes) + if (depth > 0) { + thread.replies = await getReplies(db.db, thread, depth - 1, requester) + } + if (queryRes.parent !== null) { + const parentRes = await postInfoBuilder(db.db, requester).where( + 'post.uri = :uri', + { uri: queryRes.parent }, + ) + thread.parent = rowToPost(parentRes) + } - return { thread } - } + return { + encoding: 'application/json', + body: { thread }, + } + }, + ) +} const getReplies = async ( db: DataSource, - parent: PostThreadView.Post, + parent: GetPostThread.Post, depth: number, requester: string, -): Promise => { +): Promise => { const res = await postInfoBuilder(db, requester) .where('post.replyParent = :uri', { uri: parent.uri }) .orderBy('post.createdAt', 'DESC') @@ -133,8 +130,8 @@ const postInfoBuilder = (db: DataSource, requester: string) => { // unfortunately not type-checked since we're dealing with raw SQL, so change with caution! const rowToPost = ( row: any, - parent?: PostThreadView.Post, -): PostThreadView.Post => { + parent?: GetPostThread.Post, +): GetPostThread.Post => { return { uri: row.uri, author: { @@ -154,10 +151,3 @@ const rowToPost = ( }, } } - -const plugin: DbViewPlugin = { - id: viewId, - fn: viewFn, -} - -export default plugin diff --git a/packages/server/src/api/todo/social/getProfile.ts b/packages/server/src/api/todo/social/getProfile.ts new file mode 100644 index 00000000000..e601deaffa9 --- /dev/null +++ b/packages/server/src/api/todo/social/getProfile.ts @@ -0,0 +1,120 @@ +import { Server } from '../../../xrpc' +import { AuthRequiredError } from '@adxp/xrpc-server' +import * as GetProfile from '../../../xrpc/types/todo/social/getProfile' +import { FollowIndex } from '../../../db/records/follow' +import { PostIndex } from '../../../db/records/post' +import { ProfileBadgeIndex, ProfileIndex } from '../../../db/records/profile' +import { UserDid } from '../../../db/user-dids' +import * as util from '../../../db/util' +import { BadgeIndex } from '../../../db/records/badge' +import { getLocals } from '../../../util' + +export default function (server: Server) { + server.todo.social.getProfile( + async (params: GetProfile.QueryParams, _input, req, res) => { + const { user } = params + const { db } = getLocals(res) + + // @TODO switch out for actual auth + const requester = req.headers.authorization + if (!requester) { + throw new AuthRequiredError() + } + + const queryRes = await db.db + .createQueryBuilder() + .select([ + 'user.did AS did', + 'user.username AS name', + 'profile.displayName AS displayName', + 'profile.description AS description', + 'follows_count.count AS followsCount', + 'followers_count.count AS followersCount', + 'posts_count.count AS postsCount', + 'requester_follow.uri AS requesterFollow', + ]) + .from(UserDid, 'user') + .leftJoin(ProfileIndex, 'profile', 'profile.creator = user.did') + .leftJoin( + util.countSubquery(FollowIndex, 'creator'), + 'follows_count', + 'follows_count.subject = user.did', + ) + .leftJoin( + util.countSubquery(FollowIndex, 'subject'), + 'followers_count', + 'followers_count.subject = user.did', + ) + .leftJoin( + util.countSubquery(PostIndex, 'creator'), + 'posts_count', + 'posts_count.subject = user.did', + ) + .leftJoin( + FollowIndex, + 'requester_follow', + `requester_follow.creator = :requester AND requester_follow.subject = user.did`, + { requester }, + ) + .where(util.userWhereClause(user), { user }) + .getRawOne() + + const badgesRes = await db.db + .createQueryBuilder() + .select([ + 'badge.uri AS uri', + 'badge.assertionType AS assertionType', + 'badge.assertionTag AS assertionTag', + 'issuer.did AS issuerDid', + 'issuer.username AS issuerName', + 'issuer_profile.displayName AS issuerDisplayName', + 'badge.createdAt AS createdAt', + ]) + .from(ProfileIndex, 'profile') + .innerJoin( + ProfileBadgeIndex, + 'profile_badge', + 'profile_badge.profile = profile.uri', + ) + .innerJoin(BadgeIndex, 'badge', 'badge.uri = profile_badge.badge') + .leftJoin(UserDid, 'issuer', 'issuer.did = badge.creator') + .leftJoin( + ProfileIndex, + 'issuer_profile', + 'issuer_profile.creator = issuer.did', + ) + .where('profile.creator = :did', { did: queryRes.did }) + .getRawMany() + + const badges = badgesRes.map((row) => ({ + uri: row.uri, + issuer: { + did: row.issuerDid, + name: row.issuerName, + displayName: row.issuerDisplayName || undefined, + }, + assertion: row.assertionType + ? { type: row.assertionType, tag: row.assertionTag || undefined } + : undefined, + createdAt: row.createdAt, + })) + + return { + encoding: 'application/json', + body: { + did: queryRes.did, + name: queryRes.name, + displayName: queryRes.displayName || undefined, + description: queryRes.description || undefined, + followsCount: queryRes.followsCount || 0, + followersCount: queryRes.followersCount || 0, + postsCount: queryRes.postsCount || 0, + badges: badges, + myState: { + follow: queryRes.requesterFollow || undefined, + }, + }, + } + }, + ) +} diff --git a/packages/server/src/api/todo/social/getRepostedBy.ts b/packages/server/src/api/todo/social/getRepostedBy.ts new file mode 100644 index 00000000000..43c3589a57f --- /dev/null +++ b/packages/server/src/api/todo/social/getRepostedBy.ts @@ -0,0 +1,56 @@ +import { Server } from '../../../xrpc' +import * as GetRepostedBy from '../../../xrpc/types/todo/social/getRepostedBy' +import { AdxRecord } from '../../../db/record' +import { ProfileIndex } from '../../../db/records/profile' +import { UserDid } from '../../../db/user-dids' +import { RepostIndex } from '../../../db/records/repost' +import { getLocals } from '../../../util' + +export default function (server: Server) { + server.todo.social.getRepostedBy( + async (params: GetRepostedBy.QueryParams, _input, _req, res) => { + const { uri, limit, before } = params + const { db } = getLocals(res) + + const builder = db.db + .createQueryBuilder() + .select([ + 'user.did AS did', + 'user.username AS name', + 'profile.displayName AS displayName', + 'repost.createdAt AS createdAt', + 'record.indexedAt AS indexedAt', + ]) + .from(RepostIndex, 'repost') + .leftJoin(AdxRecord, 'record', 'repost.uri = record.uri') + .leftJoin(UserDid, 'user', 'repost.creator = user.did') + .leftJoin(ProfileIndex, 'profile', 'profile.creator = user.did') + .where('repost.subject = :uri', { uri }) + .orderBy('repost.createdAt') + + if (before !== undefined) { + builder.andWhere('repost.createdAt < :before', { before }) + } + if (limit !== undefined) { + builder.limit(limit) + } + const repostedByRes = await builder.getRawMany() + + const repostedBy = repostedByRes.map((row) => ({ + did: row.did, + name: row.name, + displayName: row.displayName || undefined, + createdAt: row.createdAt, + indexedAt: row.indexedAt, + })) + + return { + encoding: 'application/json', + body: { + uri, + repostedBy, + }, + } + }, + ) +} diff --git a/packages/server/src/api/todo/social/getUserFollowers.ts b/packages/server/src/api/todo/social/getUserFollowers.ts new file mode 100644 index 00000000000..f5eb6aa7780 --- /dev/null +++ b/packages/server/src/api/todo/social/getUserFollowers.ts @@ -0,0 +1,59 @@ +import { Server } from '../../../xrpc' +import * as GetUserFollowers from '../../../xrpc/types/todo/social/getUserFollowers' +import { AdxRecord } from '../../../db/record' +import { FollowIndex } from '../../../db/records/follow' +import { ProfileIndex } from '../../../db/records/profile' +import { UserDid } from '../../../db/user-dids' +import * as util from './util' +import { getLocals } from '../../../util' + +export default function (server: Server) { + server.todo.social.getUserFollowers( + async (params: GetUserFollowers.QueryParams, _input, _req, res) => { + const { user, limit, before } = params + const { db } = getLocals(res) + + const subject = await util.getUserInfo(db.db, user) + + const followersReq = db.db + .createQueryBuilder() + .select([ + 'creator.did AS did', + 'creator.username AS name', + 'profile.displayName AS displayName', + 'follow.createdAt AS createdAt', + 'record.indexedAt AS indexedAt', + ]) + .from(FollowIndex, 'follow') + .innerJoin(AdxRecord, 'record', 'record.uri = follow.uri') + .innerJoin(UserDid, 'creator', 'creator.did = record.did') + .leftJoin(ProfileIndex, 'profile', 'profile.creator = record.did') + .where('follow.subject = :subject', { subject: subject.did }) + .orderBy('follow.createdAt') + + if (before !== undefined) { + followersReq.andWhere('follow.createdAt < :before', { before }) + } + if (limit !== undefined) { + followersReq.limit(limit) + } + + const followersRes = await followersReq.getRawMany() + const followers = followersRes.map((row) => ({ + did: row.did, + name: row.name, + displayName: row.displayName || undefined, + createdAt: row.createdAt, + indexedAt: row.indexedAt, + })) + + return { + encoding: 'application/json', + body: { + subject, + followers, + }, + } + }, + ) +} diff --git a/packages/server/src/api/todo/social/getUserFollows.ts b/packages/server/src/api/todo/social/getUserFollows.ts new file mode 100644 index 00000000000..65d618584b5 --- /dev/null +++ b/packages/server/src/api/todo/social/getUserFollows.ts @@ -0,0 +1,59 @@ +import { Server } from '../../../xrpc' +import * as GetUserFollows from '../../../xrpc/types/todo/social/getUserFollows' +import { AdxRecord } from '../../../db/record' +import { FollowIndex } from '../../../db/records/follow' +import { ProfileIndex } from '../../../db/records/profile' +import { UserDid } from '../../../db/user-dids' +import * as util from './util' +import { getLocals } from '../../../util' + +export default function (server: Server) { + server.todo.social.getUserFollows( + async (params: GetUserFollows.QueryParams, _input, _req, res) => { + const { user, limit, before } = params + const { db } = getLocals(res) + + const creator = await util.getUserInfo(db.db, user) + + const followsReq = db.db + .createQueryBuilder() + .select([ + 'subject.did AS did', + 'subject.username AS name', + 'profile.displayName AS displayName', + 'follow.createdAt AS createdAt', + 'record.indexedAt AS indexedAt', + ]) + .from(FollowIndex, 'follow') + .innerJoin(AdxRecord, 'record', 'follow.uri = record.uri') + .innerJoin(UserDid, 'subject', 'follow.subject = subject.did') + .leftJoin(ProfileIndex, 'profile', 'profile.creator = follow.subject') + .where('follow.creator = :creator', { creator: creator.did }) + .orderBy('follow.createdAt') + + if (before !== undefined) { + followsReq.andWhere('follow.createdAt < :before', { before }) + } + if (limit !== undefined) { + followsReq.limit(limit) + } + + const followsRes = await followsReq.getRawMany() + const follows = followsRes.map((row) => ({ + did: row.did, + name: row.name, + displayName: row.displayName || undefined, + createdAt: row.createdAt, + indexedAt: row.indexedAt, + })) + + return { + encoding: 'application/json', + body: { + subject: creator, + follows, + }, + } + }, + ) +} diff --git a/packages/server/src/api/todo/social/index.ts b/packages/server/src/api/todo/social/index.ts new file mode 100644 index 00000000000..63d2992d0c0 --- /dev/null +++ b/packages/server/src/api/todo/social/index.ts @@ -0,0 +1,18 @@ +import { Server } from '../../../xrpc' +import getFeed from './getFeed' +import getLikedBy from './getLikedBy' +import getPostThread from './getPostThread' +import getProfile from './getProfile' +import getRepostedBy from './getRepostedBy' +import getUserFollowers from './getUserFollowers' +import getUserFollows from './getUserFollows' + +export default function (server: Server) { + getFeed(server) + getLikedBy(server) + getPostThread(server) + getProfile(server) + getRepostedBy(server) + getUserFollowers(server) + getUserFollows(server) +} diff --git a/packages/server/src/db/views/util.ts b/packages/server/src/api/todo/social/util.ts similarity index 83% rename from packages/server/src/db/views/util.ts rename to packages/server/src/api/todo/social/util.ts index 66c5f69214d..0ecbd34a2db 100644 --- a/packages/server/src/db/views/util.ts +++ b/packages/server/src/api/todo/social/util.ts @@ -1,7 +1,7 @@ import { DataSource } from 'typeorm' -import { ProfileIndex } from '../records/profile' -import { UserDid } from '../user-dids' -import * as util from '../util' +import { ProfileIndex } from '../../../db/records/profile' +import { UserDid } from '../../../db/user-dids' +import * as util from '../../../db/util' type UserInfo = { did: string diff --git a/packages/server/src/db/index.ts b/packages/server/src/db/index.ts index 97c2b59179b..6e13182c430 100644 --- a/packages/server/src/db/index.ts +++ b/packages/server/src/db/index.ts @@ -1,6 +1,12 @@ -import { Badge, Follow, Like, Post, Profile, Repost } from '@adxp/microblog' +import * as Badge from '../xrpc/types/todo/social/badge' +import * as Follow from '../xrpc/types/todo/social/follow' +import * as Like from '../xrpc/types/todo/social/like' +import * as Post from '../xrpc/types/todo/social/post' +import * as Profile from '../xrpc/types/todo/social/profile' +import * as Repost from '../xrpc/types/todo/social/repost' import { DataSource } from 'typeorm' -import { DbRecordPlugin, ViewFn } from './types' +import { AdxValidationResult, AdxValidationResultCode } from '@adxp/schemas' +import { DbRecordPlugin } from './types' import postPlugin, { PostEntityIndex, PostIndex } from './records/post' import likePlugin, { LikeIndex } from './records/like' import followPlugin, { FollowIndex } from './records/follow' @@ -10,7 +16,6 @@ import profilePlugin, { ProfileIndex, } from './records/profile' import repostPlugin, { RepostIndex } from './records/repost' -import views from './views' import { AdxUri } from '@adxp/common' import { CID } from 'multiformats/cid' import { RepoRoot } from './repo-root' @@ -27,7 +32,6 @@ export class Database { profiles: DbRecordPlugin reposts: DbRecordPlugin } - views: Record constructor(db: DataSource) { this.db = db @@ -39,10 +43,6 @@ export class Database { profiles: profilePlugin(db), reposts: repostPlugin(db), } - this.views = {} - for (const view of views) { - this.views[view.id] = view.fn(db) - } } static async sqlite(location: string): Promise { @@ -111,9 +111,21 @@ export class Database { await table.save(user) } + validateRecord(collection: string, obj: unknown): AdxValidationResult { + let table + try { + table = this.findTableForCollection(collection) + } catch (e) { + const result = new AdxValidationResult() + result._t(AdxValidationResultCode.Incompatible, `Schema not found`) + return result + } + return table.validateSchema(obj) + } + canIndexRecord(collection: string, obj: unknown): boolean { const table = this.findTableForCollection(collection) - return table.isValidSchema(obj) + return table.validateSchema(obj).valid } async indexRecord(uri: AdxUri, obj: unknown) { diff --git a/packages/server/src/db/records/badge.ts b/packages/server/src/db/records/badge.ts index fd826a5e02c..5b46fece420 100644 --- a/packages/server/src/db/records/badge.ts +++ b/packages/server/src/db/records/badge.ts @@ -1,5 +1,5 @@ import { AdxUri } from '@adxp/common' -import { Badge } from '@adxp/microblog' +import * as Badge from '../../xrpc/types/todo/social/badge' import { DataSource, Entity, @@ -14,9 +14,8 @@ import { UserDid } from '../user-dids' import schemas from '../schemas' import { collectionToTableName } from '../util' -const schemaId = 'blueskyweb.xyz:Badge' -const collection = 'bsky/badges' -const tableName = collectionToTableName(collection) +const type = 'todo.social.badge' +const tableName = collectionToTableName(type) @Entity({ name: tableName }) export class BadgeIndex { @@ -50,16 +49,17 @@ const getFn = return found === null ? null : translateDbObj(found) } -const validator = schemas.createRecordValidator(schemaId) +const validator = schemas.createRecordValidator(type) const isValidSchema = (obj: unknown): obj is Badge.Record => { return validator.isValid(obj) } +const validateSchema = (obj: unknown) => validator.validate(obj) const setFn = (repo: Repository) => async (uri: AdxUri, obj: unknown): Promise => { if (!isValidSchema(obj)) { - throw new Error(`Record does not match schema: ${schemaId}`) + throw new Error(`Record does not match schema: ${type}`) } const badge = new BadgeIndex() badge.uri = uri.toString() @@ -97,10 +97,10 @@ export const makePlugin = ( ): DbRecordPlugin => { const repository = db.getRepository(BadgeIndex) return { - collection, + collection: type, tableName, get: getFn(repository), - isValidSchema, + validateSchema, set: setFn(repository), delete: deleteFn(repository), translateDbObj, diff --git a/packages/server/src/db/records/follow.ts b/packages/server/src/db/records/follow.ts index 6f897a1b9f8..b8ecb9ff938 100644 --- a/packages/server/src/db/records/follow.ts +++ b/packages/server/src/db/records/follow.ts @@ -1,5 +1,5 @@ import { AdxUri } from '@adxp/common' -import { Follow } from '@adxp/microblog' +import * as Follow from '../../xrpc/types/todo/social/follow' import { DataSource, Entity, @@ -14,9 +14,8 @@ import { UserDid } from '../user-dids' import schemas from '../schemas' import { collectionToTableName } from '../util' -const schemaId = 'blueskyweb.xyz:Follow' -const collection = 'bsky/follows' -const tableName = collectionToTableName(collection) +const type = 'todo.social.follow' +const tableName = collectionToTableName(type) @Entity({ name: tableName }) export class FollowIndex { @@ -44,16 +43,17 @@ const getFn = return found === null ? null : translateDbObj(found) } -const validator = schemas.createRecordValidator(schemaId) +const validator = schemas.createRecordValidator(type) const isValidSchema = (obj: unknown): obj is Follow.Record => { return validator.isValid(obj) } +const validateSchema = (obj: unknown) => validator.validate(obj) const setFn = (repo: Repository) => async (uri: AdxUri, obj: unknown): Promise => { if (!isValidSchema(obj)) { - throw new Error(`Record does not match schema: ${schemaId}`) + throw new Error(`Record does not match schema: ${type}`) } const follow = new FollowIndex() follow.uri = uri.toString() @@ -81,10 +81,10 @@ export const makePlugin = ( ): DbRecordPlugin => { const repository = db.getRepository(FollowIndex) return { - collection, + collection: type, tableName, get: getFn(repository), - isValidSchema, + validateSchema, set: setFn(repository), delete: deleteFn(repository), translateDbObj, diff --git a/packages/server/src/db/records/like.ts b/packages/server/src/db/records/like.ts index 2888027366c..6a174215d84 100644 --- a/packages/server/src/db/records/like.ts +++ b/packages/server/src/db/records/like.ts @@ -1,6 +1,5 @@ import { AdxUri } from '@adxp/common' -import * as microblog from '@adxp/microblog' -import { Like } from '@adxp/microblog' +import * as Like from '../../xrpc/types/todo/social/like' import { DataSource, Entity, @@ -16,9 +15,8 @@ import { UserDid } from '../user-dids' import schemas from '../schemas' import { collectionToTableName } from '../util' -const schemaId = 'blueskyweb.xyz:Like' -const collection = 'bsky/likes' -const tableName = collectionToTableName(collection) +const type = 'todo.social.like' +const tableName = collectionToTableName(type) @Entity({ name: tableName }) export class LikeIndex { @@ -46,16 +44,17 @@ const getFn = return found === null ? null : translateDbObj(found) } -const validator = schemas.createRecordValidator(schemaId) +const validator = schemas.createRecordValidator(type) const isValidSchema = (obj: unknown): obj is Like.Record => { return validator.isValid(obj) } +const validateSchema = (obj: unknown) => validator.validate(obj) const setFn = (repo: Repository) => async (uri: AdxUri, obj: unknown): Promise => { if (!isValidSchema(obj)) { - throw new Error(`Record does not match schema: ${schemaId}`) + throw new Error(`Record does not match schema: ${type}`) } const like = new LikeIndex() like.uri = uri.toString() @@ -83,10 +82,10 @@ export const makePlugin = ( ): DbRecordPlugin => { const repository = db.getRepository(LikeIndex) return { - collection, + collection: type, tableName, get: getFn(repository), - isValidSchema, + validateSchema, set: setFn(repository), delete: deleteFn(repository), translateDbObj, diff --git a/packages/server/src/db/records/post.ts b/packages/server/src/db/records/post.ts index 4022c1c351f..d58343e06e1 100644 --- a/packages/server/src/db/records/post.ts +++ b/packages/server/src/db/records/post.ts @@ -1,5 +1,5 @@ import { AdxUri } from '@adxp/common' -import { Post } from '@adxp/microblog' +import * as Post from '../../xrpc/types/todo/social/post' import { DataSource, Entity, @@ -13,9 +13,8 @@ import { UserDid } from '../user-dids' import schemas from '../schemas' import { collectionToTableName } from '../util' -const schemaId = 'blueskyweb.xyz:Post' -const collection = 'bsky/posts' -const tableName = collectionToTableName(collection) +const type = 'todo.social.post' +const tableName = collectionToTableName(type) @Entity({ name: tableName }) export class PostIndex { @@ -79,16 +78,17 @@ const getFn = return obj } -const validator = schemas.createRecordValidator(schemaId) +const validator = schemas.createRecordValidator(type) const isValidSchema = (obj: unknown): obj is Post.Record => { return validator.isValid(obj) } +const validateSchema = (obj: unknown) => validator.validate(obj) const setFn = (db: DataSource) => async (uri: AdxUri, obj: unknown): Promise => { if (!isValidSchema(obj)) { - throw new Error(`Record does not match schema: ${schemaId}`) + throw new Error(`Record does not match schema: ${type}`) } const entities = (obj.entities || []).map((entity) => { const entry = new PostEntityIndex() @@ -139,10 +139,10 @@ export const makePlugin = ( ): DbRecordPlugin => { const repository = db.getRepository(PostIndex) return { - collection, + collection: type, tableName, get: getFn(db), - isValidSchema, + validateSchema, set: setFn(db), delete: deleteFn(db), translateDbObj, diff --git a/packages/server/src/db/records/profile.ts b/packages/server/src/db/records/profile.ts index e43bfc9cb1a..c343e8c770f 100644 --- a/packages/server/src/db/records/profile.ts +++ b/packages/server/src/db/records/profile.ts @@ -1,5 +1,5 @@ import { AdxUri } from '@adxp/common' -import { Profile } from '@adxp/microblog' +import * as Profile from '../../xrpc/types/todo/social/profile' import { DataSource, Entity, @@ -11,9 +11,8 @@ import { DbRecordPlugin } from '../types' import schemas from '../schemas' import { collectionToTableName } from '../util' -const schemaId = 'blueskyweb.xyz:Profile' -const collection = 'bsky/profile' -const tableName = collectionToTableName(collection) +const type = 'todo.social.profile' +const tableName = collectionToTableName(type) @Entity({ name: tableName }) export class ProfileIndex { @@ -59,16 +58,17 @@ const getFn = return obj } -const validator = schemas.createRecordValidator(schemaId) +const validator = schemas.createRecordValidator(type) const isValidSchema = (obj: unknown): obj is Profile.Record => { return validator.isValid(obj) } +const validateSchema = (obj: unknown) => validator.validate(obj) const setFn = (db: DataSource) => async (uri: AdxUri, obj: unknown): Promise => { if (!isValidSchema(obj)) { - throw new Error(`Record does not match schema: ${schemaId}`) + throw new Error(`Record does not match schema: ${type}`) } const badges = (obj.badges || []).map((badge) => { @@ -108,10 +108,10 @@ export const makePlugin = ( ): DbRecordPlugin => { const repository = db.getRepository(ProfileIndex) return { - collection, + collection: type, tableName, get: getFn(db), - isValidSchema, + validateSchema, set: setFn(db), delete: deleteFn(db), translateDbObj, diff --git a/packages/server/src/db/records/repost.ts b/packages/server/src/db/records/repost.ts index 467a2371876..a0a164f8f7a 100644 --- a/packages/server/src/db/records/repost.ts +++ b/packages/server/src/db/records/repost.ts @@ -1,6 +1,5 @@ import { AdxUri } from '@adxp/common' -import * as microblog from '@adxp/microblog' -import { Repost } from '@adxp/microblog' +import * as Repost from '../../xrpc/types/todo/social/repost' import { DataSource, Entity, @@ -17,9 +16,8 @@ import schemas from '../schemas' import { collectionToTableName } from '../util' import { PostIndex } from './post' -const schemaId = 'blueskyweb.xyz:Repost' -const collection = 'bsky/reposts' -const tableName = collectionToTableName(collection) +const type = 'todo.social.repost' +const tableName = collectionToTableName(type) @Entity({ name: tableName }) export class RepostIndex { @@ -48,16 +46,17 @@ const getFn = return found === null ? null : translateDbObj(found) } -const validator = schemas.createRecordValidator(schemaId) +const validator = schemas.createRecordValidator(type) const isValidSchema = (obj: unknown): obj is Repost.Record => { return validator.isValid(obj) } +const validateSchema = (obj: unknown) => validator.validate(obj) const setFn = (repo: Repository) => async (uri: AdxUri, obj: unknown): Promise => { if (!isValidSchema(obj)) { - throw new Error(`Record does not match schema: ${schemaId}`) + throw new Error(`Record does not match schema: ${type}`) } const repost = new RepostIndex() repost.uri = uri.toString() @@ -86,10 +85,10 @@ export const makePlugin = ( ): DbRecordPlugin => { const repository = db.getRepository(RepostIndex) return { - collection, + collection: type, tableName, get: getFn(repository), - isValidSchema, + validateSchema, set: setFn(repository), delete: deleteFn(repository), translateDbObj, diff --git a/packages/server/src/db/schemas.ts b/packages/server/src/db/schemas.ts index 3f9ef4399e2..7e49b66b40b 100644 --- a/packages/server/src/db/schemas.ts +++ b/packages/server/src/db/schemas.ts @@ -1,9 +1,9 @@ import { AdxSchemas } from '@adxp/schemas' -import { schemas as schemaDefs } from '@adxp/microblog' +import { recordSchemas } from '../xrpc/schemas' export const schemas = new AdxSchemas() -for (const schema of schemaDefs) { +for (const schema of recordSchemas) { schemas.add(schema) } diff --git a/packages/server/src/db/types.ts b/packages/server/src/db/types.ts index 1ca0680d511..5fe5f311f12 100644 --- a/packages/server/src/db/types.ts +++ b/packages/server/src/db/types.ts @@ -1,22 +1,12 @@ import { AdxUri } from '@adxp/common' -import { DataSource } from 'typeorm' +import { AdxValidationResult } from '@adxp/schemas' export type DbRecordPlugin = { collection: string tableName: string get: (uri: AdxUri) => Promise - isValidSchema: (obj: unknown) => boolean + validateSchema: (obj: unknown) => AdxValidationResult set: (uri: AdxUri, obj: unknown) => Promise delete: (uri: AdxUri) => Promise translateDbObj: (dbObj: S) => T } - -export type DbViewPlugin = { - id: string - fn: (db: DataSource) => ViewFn -} - -export type ViewFn = ( - params: Record, - requesterDid: string, -) => unknown diff --git a/packages/server/src/db/util.ts b/packages/server/src/db/util.ts index 0fe5f7a517e..3f44f47fa86 100644 --- a/packages/server/src/db/util.ts +++ b/packages/server/src/db/util.ts @@ -1,7 +1,7 @@ import { EntityTarget, SelectQueryBuilder } from 'typeorm' export const collectionToTableName = (collection: string): string => { - return `record_${collection.split('/').join('_')}` + return `record_${collection.split('.').join('_')}` } export const userWhereClause = (user: string): string => { diff --git a/packages/server/src/db/views/feed.ts b/packages/server/src/db/views/feed.ts deleted file mode 100644 index 228f2a8f30e..00000000000 --- a/packages/server/src/db/views/feed.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { FeedView } from '@adxp/microblog' -import { DataSource, Like } from 'typeorm' -import { FollowIndex } from '../records/follow' -import { PostIndex } from '../records/post' -import { ProfileIndex } from '../records/profile' -import { UserDid } from '../user-dids' -import schemas from '../schemas' -import * as util from '../util' -import { DbViewPlugin } from '../types' -import { LikeIndex } from '../records/like' -import { RepostIndex } from '../records/repost' -import { AdxRecord } from '../record' -import { FeedItem } from '@adxp/microblog/src/types/FeedView' - -const viewId = 'blueskyweb.xyz:FeedView' -const validator = schemas.createViewValidator(viewId) -const validParams = (obj: unknown): obj is FeedView.Params => { - return validator.isParamsValid(obj) -} - -export const viewFn = - (db: DataSource) => - async (params: unknown, requester: string): Promise => { - if (!validParams(params)) { - throw new Error(`Invalid params for ${viewId}`) - } - - const { author, limit, before } = params - - const builder = db.createQueryBuilder() - - if (author === undefined) { - builder - .from(UserDid, 'user') - .innerJoin(FollowIndex, 'follow', 'follow.creator = user.did') - .leftJoin(RepostIndex, 'repost', 'repost.creator = follow.subject') - .innerJoin( - PostIndex, - 'post', - 'post.creator = follow.subject OR post.uri = repost.subject', - ) - .leftJoin(UserDid, 'author', 'author.did = post.creator') - .where('user.did = :did', { did: requester }) - } else { - const authorWhere = author.startsWith('did:') - ? 'author.did' - : 'author.username' - builder - .from(PostIndex, 'post') - .leftJoin(RepostIndex, 'repost', 'repost.subject = post.uri') - .leftJoin( - UserDid, - 'author', - 'author.did = post.creator OR author.did = repost.creator', - ) - .where(`${authorWhere} = :author`, { author }) - } - - builder - .select([ - 'post.uri AS uri', - 'author.did AS authorDid', - 'author.username AS authorName', - 'author_profile.displayName AS authorDisplayName', - 'reposted_by.did AS repostedByDid', - 'reposted_by.username AS repostedByName', - 'reposted_by_profile.displayName AS repostedByDisplayName', - 'record.raw AS rawRecord', - 'like_count.count AS likeCount', - 'repost_count.count AS repostCount', - 'reply_count.count AS replyCount', - 'requester_repost.uri AS requesterRepost', - 'requester_like.uri AS requesterLike', - 'record.indexedAt AS indexedAt', - ]) - .leftJoin( - ProfileIndex, - 'author_profile', - 'author_profile.creator = author.did', - ) - .leftJoin(UserDid, 'reposted_by', 'reposted_by.did = repost.creator') - .leftJoin( - ProfileIndex, - 'reposted_by_profile', - 'reposted_by_profile.creator = reposted_by.did', - ) - .leftJoin(AdxRecord, 'record', 'record.uri = post.uri') - .leftJoin( - util.countSubquery(LikeIndex, 'subject'), - 'like_count', - 'like_count.subject = post.uri', - ) - .leftJoin( - util.countSubquery(RepostIndex, 'subject'), - 'repost_count', - 'repost_count.subject = post.uri', - ) - .leftJoin( - util.countSubquery(PostIndex, 'replyParent'), - 'reply_count', - 'reply_count.subject = post.uri', - ) - .leftJoin( - RepostIndex, - 'requester_repost', - `requester_repost.creator = :requester AND requester_repost.subject = post.uri`, - { requester }, - ) - .leftJoin( - RepostIndex, - 'requester_like', - `requester_like.creator = :requester AND requester_like.subject = post.uri`, - { requester }, - ) - .orderBy('post.createdAt', 'DESC') - .groupBy('post.uri') - - if (before !== undefined) { - builder.andWhere('post.createdAt < :before', { before }) - } - if (limit !== undefined) { - builder.limit(limit) - } - - const res = await builder.getRawMany() - - // @TODO add embeds - const feed: FeedItem[] = res.map((row) => ({ - uri: row.uri, - author: { - did: row.authorDid, - name: row.authorName, - displayName: row.authorDisplayName || undefined, - }, - repostedBy: row.repostedByDid - ? { - did: row.repostedByDid, - name: row.repostedByName, - displayName: row.repostedByDisplayName || undefined, - } - : undefined, - record: JSON.parse(row.rawRecord), - replyCount: row.replyCount || 0, - repostCount: row.repostCount || 0, - likeCount: row.likeCount || 0, - indexedAt: row.indexedAt, - myState: { - repost: row.requesterRepost || undefined, - like: row.requesterLike || undefined, - }, - })) - - return { feed } - } - -const plugin: DbViewPlugin = { - id: viewId, - fn: viewFn, -} - -export default plugin diff --git a/packages/server/src/db/views/index.ts b/packages/server/src/db/views/index.ts deleted file mode 100644 index e88e08d97a8..00000000000 --- a/packages/server/src/db/views/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import likedBy from './likedBy' -import repostedBy from './repostedBy' -import userFollows from './userFollows' -import userFollowers from './userFollowers' -import profile from './profile' -import feed from './feed' -import postThread from './postThread' - -const views = [ - likedBy, - repostedBy, - userFollows, - userFollowers, - profile, - feed, - postThread, -] - -export default views diff --git a/packages/server/src/db/views/likedBy.ts b/packages/server/src/db/views/likedBy.ts deleted file mode 100644 index 9b3f95174bb..00000000000 --- a/packages/server/src/db/views/likedBy.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { LikedByView } from '@adxp/microblog' -import { DataSource } from 'typeorm' -import { AdxRecord } from '../record' -import { LikeIndex } from '../records/like' -import { ProfileIndex } from '../records/profile' -import { UserDid } from '../user-dids' -import schemas from '../schemas' -import { DbViewPlugin } from '../types' - -const viewId = 'blueskyweb.xyz:LikedByView' -const validator = schemas.createViewValidator(viewId) -const validParams = (obj: unknown): obj is LikedByView.Params => { - return validator.isParamsValid(obj) -} - -const viewFn = - (db: DataSource) => - async (params: unknown): Promise => { - if (!validParams(params)) { - throw new Error(`Invalid params for ${viewId}`) - } - const { uri, limit, before } = params - - const builder = db - .createQueryBuilder() - .select([ - 'user.did AS did', - 'user.username AS name', - 'profile.displayName AS displayName', - 'like.createdAt AS createdAt', - 'record.indexedAt AS indexedAt', - ]) - .from(LikeIndex, 'like') - .leftJoin(AdxRecord, 'record', 'like.uri = record.uri') - .leftJoin(UserDid, 'user', 'like.creator = user.did') - .leftJoin(ProfileIndex, 'profile', 'profile.creator = user.did') - .where('like.subject = :uri', { uri }) - .orderBy('like.createdAt') - - if (before !== undefined) { - builder.andWhere('like.createdAt < :before', { before }) - } - if (limit !== undefined) { - builder.limit(limit) - } - const likedByRes = await builder.getRawMany() - - const likedBy = likedByRes.map((row) => ({ - did: row.did, - name: row.name, - displayName: row.displayName || undefined, - createdAt: row.createdAt, - indexedAt: row.indexedAt, - })) - - return { - uri, - likedBy, - } - } - -const plugin: DbViewPlugin = { - id: viewId, - fn: viewFn, -} - -export default plugin diff --git a/packages/server/src/db/views/profile.ts b/packages/server/src/db/views/profile.ts deleted file mode 100644 index 16b0da0c59b..00000000000 --- a/packages/server/src/db/views/profile.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { ProfileView } from '@adxp/microblog' -import { DataSource } from 'typeorm' -import { FollowIndex } from '../records/follow' -import { PostIndex } from '../records/post' -import { ProfileBadgeIndex, ProfileIndex } from '../records/profile' -import { UserDid } from '../user-dids' -import schemas from '../schemas' -import { DbViewPlugin } from '../types' -import * as util from '../util' -import { BadgeIndex } from '../records/badge' - -const viewId = 'blueskyweb.xyz:ProfileView' -const validator = schemas.createViewValidator(viewId) -const validParams = (obj: unknown): obj is ProfileView.Params => { - return validator.isParamsValid(obj) -} - -export const viewFn = - (db: DataSource) => - async (params: unknown, requester: string): Promise => { - if (!validParams(params)) { - throw new Error(`Invalid params for ${viewId}`) - } - const { user } = params - - const res = await db - .createQueryBuilder() - .select([ - 'user.did AS did', - 'user.username AS name', - 'profile.displayName AS displayName', - 'profile.description AS description', - 'follows_count.count AS followsCount', - 'followers_count.count AS followersCount', - 'posts_count.count AS postsCount', - 'requester_follow.uri AS requesterFollow', - ]) - .from(UserDid, 'user') - .leftJoin(ProfileIndex, 'profile', 'profile.creator = user.did') - .leftJoin( - util.countSubquery(FollowIndex, 'creator'), - 'follows_count', - 'follows_count.subject = user.did', - ) - .leftJoin( - util.countSubquery(FollowIndex, 'subject'), - 'followers_count', - 'followers_count.subject = user.did', - ) - .leftJoin( - util.countSubquery(PostIndex, 'creator'), - 'posts_count', - 'posts_count.subject = user.did', - ) - .leftJoin( - FollowIndex, - 'requester_follow', - `requester_follow.creator = :requester AND requester_follow.subject = user.did`, - { requester }, - ) - .where(util.userWhereClause(user), { user }) - .getRawOne() - - const badgesRes = await db - .createQueryBuilder() - .select([ - 'badge.uri AS uri', - 'badge.assertionType AS assertionType', - 'badge.assertionTag AS assertionTag', - 'issuer.did AS issuerDid', - 'issuer.username AS issuerName', - 'issuer_profile.displayName AS issuerDisplayName', - 'badge.createdAt AS createdAt', - ]) - .from(ProfileIndex, 'profile') - .innerJoin( - ProfileBadgeIndex, - 'profile_badge', - 'profile_badge.profile = profile.uri', - ) - .innerJoin(BadgeIndex, 'badge', 'badge.uri = profile_badge.badge') - .leftJoin(UserDid, 'issuer', 'issuer.did = badge.creator') - .leftJoin( - ProfileIndex, - 'issuer_profile', - 'issuer_profile.creator = issuer.did', - ) - .where('profile.creator = :did', { did: res.did }) - .getRawMany() - - const badges = badgesRes.map((row) => ({ - uri: row.uri, - issuer: { - did: row.issuerDid, - name: row.issuerName, - displayName: row.issuerDisplayName || undefined, - }, - assertion: row.assertionType - ? { type: row.assertionType, tag: row.assertionTag || undefined } - : undefined, - createdAt: row.createdAt, - })) - - return { - did: res.did, - name: res.name, - displayName: res.displayName || undefined, - description: res.description || undefined, - followsCount: res.followsCount || 0, - followersCount: res.followersCount || 0, - postsCount: res.postsCount || 0, - badges: badges, - myState: { - follow: res.requesterFollow || undefined, - }, - } - } - -const plugin: DbViewPlugin = { - id: viewId, - fn: viewFn, -} - -export default plugin diff --git a/packages/server/src/db/views/repostedBy.ts b/packages/server/src/db/views/repostedBy.ts deleted file mode 100644 index 9fea67ea282..00000000000 --- a/packages/server/src/db/views/repostedBy.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { RepostedByView } from '@adxp/microblog' -import { DataSource } from 'typeorm' -import { AdxRecord } from '../record' -import { ProfileIndex } from '../records/profile' -import { UserDid } from '../user-dids' -import schemas from '../schemas' -import { DbViewPlugin } from '../types' -import { RepostIndex } from '../records/repost' - -const viewId = 'blueskyweb.xyz:RepostedByView' -const validator = schemas.createViewValidator(viewId) -const validParams = (obj: unknown): obj is RepostedByView.Params => { - return validator.isParamsValid(obj) -} - -export const viewFn = - (db: DataSource) => - async (params: unknown): Promise => { - if (!validParams(params)) { - throw new Error(`Invalid params for ${viewId}`) - } - const { uri, limit, before } = params - - const builder = db - .createQueryBuilder() - .select([ - 'user.did AS did', - 'user.username AS name', - 'profile.displayName AS displayName', - 'repost.createdAt AS createdAt', - 'record.indexedAt AS indexedAt', - ]) - .from(RepostIndex, 'repost') - .leftJoin(AdxRecord, 'record', 'repost.uri = record.uri') - .leftJoin(UserDid, 'user', 'repost.creator = user.did') - .leftJoin(ProfileIndex, 'profile', 'profile.creator = user.did') - .where('repost.subject = :uri', { uri }) - .orderBy('repost.createdAt') - - if (before !== undefined) { - builder.andWhere('repost.createdAt < :before', { before }) - } - if (limit !== undefined) { - builder.limit(limit) - } - const repostedByRes = await builder.getRawMany() - - const repostedBy = repostedByRes.map((row) => ({ - did: row.did, - name: row.name, - displayName: row.displayName || undefined, - createdAt: row.createdAt, - indexedAt: row.indexedAt, - })) - - return { - uri, - repostedBy, - } - } - -const plugin: DbViewPlugin = { - id: viewId, - fn: viewFn, -} - -export default plugin diff --git a/packages/server/src/db/views/userFollowers.ts b/packages/server/src/db/views/userFollowers.ts deleted file mode 100644 index c64db817df5..00000000000 --- a/packages/server/src/db/views/userFollowers.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { UserFollowersView } from '@adxp/microblog' -import { DataSource } from 'typeorm' -import { AdxRecord } from '../record' -import { FollowIndex } from '../records/follow' -import { ProfileIndex } from '../records/profile' -import { UserDid } from '../user-dids' -import * as util from './util' -import schemas from '../schemas' -import { DbViewPlugin } from '../types' - -const viewId = 'blueskyweb.xyz:UserFollowersView' -const validator = schemas.createViewValidator(viewId) -const validParams = (obj: unknown): obj is UserFollowersView.Params => { - return validator.isParamsValid(obj) -} - -export const viewFn = - (db: DataSource) => - async (params: unknown): Promise => { - if (!validParams(params)) { - throw new Error(`Invalid params for ${viewId}`) - } - const { user, limit, before } = params - - const subject = await util.getUserInfo(db, user) - - const followersReq = db - .createQueryBuilder() - .select([ - 'creator.did AS did', - 'creator.username AS name', - 'profile.displayName AS displayName', - 'follow.createdAt AS createdAt', - 'record.indexedAt AS indexedAt', - ]) - .from(FollowIndex, 'follow') - .innerJoin(AdxRecord, 'record', 'record.uri = follow.uri') - .innerJoin(UserDid, 'creator', 'creator.did = record.did') - .leftJoin(ProfileIndex, 'profile', 'profile.creator = record.did') - .where('follow.subject = :subject', { subject: subject.did }) - .orderBy('follow.createdAt') - - if (before !== undefined) { - followersReq.andWhere('follow.createdAt < :before', { before }) - } - if (limit !== undefined) { - followersReq.limit(limit) - } - - const followersRes = await followersReq.getRawMany() - const followers = followersRes.map((row) => ({ - did: row.did, - name: row.name, - displayName: row.displayName || undefined, - createdAt: row.createdAt, - indexedAt: row.indexedAt, - })) - - return { - subject, - followers, - } - } - -const plugin: DbViewPlugin = { - id: viewId, - fn: viewFn, -} - -export default plugin diff --git a/packages/server/src/db/views/userFollows.ts b/packages/server/src/db/views/userFollows.ts deleted file mode 100644 index 3b965d55c49..00000000000 --- a/packages/server/src/db/views/userFollows.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { UserFollowsView } from '@adxp/microblog' -import { DataSource } from 'typeorm' -import { AdxRecord } from '../record' -import { FollowIndex } from '../records/follow' -import { ProfileIndex } from '../records/profile' -import { UserDid } from '../user-dids' -import * as util from './util' -import schemas from '../schemas' -import { DbViewPlugin } from '../types' - -const viewId = 'blueskyweb.xyz:UserFollowsView' -const validator = schemas.createViewValidator(viewId) -const validParams = (obj: unknown): obj is UserFollowsView.Params => { - return validator.isParamsValid(obj) -} - -export const viewFn = - (db: DataSource) => - async (params: unknown): Promise => { - if (!validParams(params)) { - throw new Error(`Invalid params for ${viewId}`) - } - const { user, limit, before } = params - - const creator = await util.getUserInfo(db, user) - - const followsReq = db - .createQueryBuilder() - .select([ - 'subject.did AS did', - 'subject.username AS name', - 'profile.displayName AS displayName', - 'follow.createdAt AS createdAt', - 'record.indexedAt AS indexedAt', - ]) - .from(FollowIndex, 'follow') - .innerJoin(AdxRecord, 'record', 'follow.uri = record.uri') - .innerJoin(UserDid, 'subject', 'follow.subject = subject.did') - .leftJoin(ProfileIndex, 'profile', 'profile.creator = follow.subject') - .where('follow.creator = :creator', { creator: creator.did }) - .orderBy('follow.createdAt') - - if (before !== undefined) { - followsReq.andWhere('follow.createdAt < :before', { before }) - } - if (limit !== undefined) { - followsReq.limit(limit) - } - - const followsRes = await followsReq.getRawMany() - const follows = followsRes.map((row) => ({ - did: row.did, - name: row.name, - displayName: row.displayName || undefined, - createdAt: row.createdAt, - indexedAt: row.indexedAt, - })) - - return { - subject: creator, - follows, - } - } - -const plugin: DbViewPlugin = { - id: viewId, - fn: viewFn, -} - -export default plugin diff --git a/packages/server/src/routes/index.ts b/packages/server/src/routes/index.ts deleted file mode 100644 index f02165e7619..00000000000 --- a/packages/server/src/routes/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import express from 'express' -import WellKnown from './well-known' -import Account from './v1/account' -import API from './v1/api/index' -import Data from './v1/data/index' -import Session from './v1/session' - -const router = express.Router() - -router.use('/.well-known', WellKnown) -router.use('/.adx/v1/account', Account) -router.use('/.adx/v1/api', API) -router.use('/.adx/v1/data', Data) -router.use('/.adx/v1/session', Session) - -export default router diff --git a/packages/server/src/routes/v1/account.ts b/packages/server/src/routes/v1/account.ts deleted file mode 100644 index 6cedab00ec4..00000000000 --- a/packages/server/src/routes/v1/account.ts +++ /dev/null @@ -1,74 +0,0 @@ -import express from 'express' -import { z } from 'zod' - -import { Repo } from '@adxp/repo' -import * as auth from '@adxp/auth' - -import * as util from '../../util' -import { ServerError } from '../../error' - -const router = express.Router() - -export const registerReq = z.object({ - did: z.string(), - username: z.string(), -}) -export type RegisterReq = z.infer - -router.get('/', async (req, res) => { - // TODO get information about the session account - res.sendStatus(501) -}) - -// @TODO fix this whole route lol -router.post('/', async (req, res) => { - const { did, username } = util.checkReqBody(req.body, registerReq) - if (username.startsWith('did:')) { - throw new ServerError( - 400, - 'Cannot register a username that starts with `did:`', - ) - } - if (!did.startsWith('did:')) { - throw new ServerError( - 400, - 'Cannot register a did that does not start with `did:`', - ) - } - - const { db, blockstore, keypair } = util.getLocals(res) - await db.registerUser(username, did) - - const authStore = await auth.AuthStore.fromTokens(keypair, []) - const repo = await Repo.create(blockstore, did, authStore) - await db.setRepoRoot(did, repo.cid) - - // const authStore = await serverAuth.checkReq( - // req, - // res, - // auth.maintenanceCap(did), - // ) - // const host = util.getOwnHost(req) - - // if (await db.isNameRegistered(username, host)) { - // throw new ServerError(409, 'Username already taken') - // } else if (await db.isDidRegistered(did)) { - // throw new ServerError(409, 'Did already registered') - // } - - // await db.registerDid(username, did, host) - // // create empty repo - // if (createRepo) { - // const repo = await Repo.create(blockstore, did, authStore) - // await db.createRepoRoot(did, repo.cid) - // } - - return res.sendStatus(200) -}) - -router.delete('/', async (req, res) => { - // TODO delete the session account - res.sendStatus(501) -}) - -export default router diff --git a/packages/server/src/routes/v1/api/index.ts b/packages/server/src/routes/v1/api/index.ts deleted file mode 100644 index 95cdf58ce21..00000000000 --- a/packages/server/src/routes/v1/api/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import express from 'express' -import Repo from './repo' -import View from './view' - -const router = express.Router() - -router.use('/repo', Repo) -router.use('/view', View) - -export default router diff --git a/packages/server/src/routes/v1/api/repo.ts b/packages/server/src/routes/v1/api/repo.ts deleted file mode 100644 index a9600e2cb8b..00000000000 --- a/packages/server/src/routes/v1/api/repo.ts +++ /dev/null @@ -1,264 +0,0 @@ -import express from 'express' -import { - NameResolutionFailed, - DidResolutionFailed, - describeRepoParams, - DescribeRepoResponse, - listRecordsParams, - batchWriteParams, - ListRecordsResponse, - GetRecordResponse, -} from '@adxp/api' -import { resolveName, AdxUri, TID } from '@adxp/common' -import * as auth from '@adxp/auth' -import * as didSdk from '@adxp/did-sdk' - -import * as repoDiff from '../../../repo-diff' -import * as util from '../../../util' -import { ServerError } from '../../../error' - -const router = express.Router() - -// EXECUTE TRANSACTIONS -// ------------------- - -router.post('/:did', async (req, res) => { - const validate = util.parseBooleanParam(req.query.validate, true) - const { did } = req.params - const tx = util.checkReqBody(req.body, batchWriteParams) - const db = util.getDB(res) - if (validate) { - for (const write of tx.writes) { - if (write.action === 'create' || write.action === 'update') { - if (!db.canIndexRecord(write.collection, write.value)) { - throw new ServerError( - 400, - `Not a valid record for collection: ${write.collection}`, - ) - } - } - } - } - // @TODO add user auth here! - const serverKey = util.getKeypair(res) - const authStore = await auth.AuthStore.fromTokens(serverKey, []) - const repo = await util.loadRepo(res, did, authStore) - const prevCid = repo.cid - await repo.batchWrite(tx.writes) - // @TODO: do something better here instead of rescanning for diff - const diff = await repo.verifySetOfUpdates(prevCid, repo.cid) - try { - await repoDiff.processDiff(db, repo, diff) - } catch (err) { - if (validate) { - throw new ServerError(400, `Could not index record: ${err}`) - } - } - await db.setRepoRoot(did, repo.cid) - res.sendStatus(200) -}) - -router.post('/:did/c/:namespace/:dataset', async (req, res) => { - const validate = util.parseBooleanParam(req.query.validate, true) - const { did, namespace, dataset } = req.params - const collection = `${namespace}/${dataset}` - if (!req.body) { - throw new ServerError(400, 'Record expected in request body') - } - const db = util.getDB(res) - if (validate) { - if (!db.canIndexRecord(collection, req.body)) { - throw new ServerError( - 400, - `Not a valid record for collection: ${collection}`, - ) - } - } - const serverKey = util.getKeypair(res) - const authStore = await auth.AuthStore.fromTokens(serverKey, []) - const repo = await util.loadRepo(res, did, authStore) - const tid = await repo.getCollection(collection).createRecord(req.body) - const uri = new AdxUri(`${did}/${collection}/${tid.toString()}`) - try { - await db.indexRecord(uri, req.body) - } catch (err) { - if (validate) { - throw new ServerError(400, `Could not index record: ${err}`) - } - } - await db.setRepoRoot(did, repo.cid) - // @TODO update subscribers - res.json({ uri: uri.toString() }) -}) - -router.put('/:did/c/:namespace/:dataset/r/:tid', async (req, res) => { - const validate = util.parseBooleanParam(req.query.validate, true) - const { did, namespace, dataset, tid } = req.params - const collection = `${namespace}/${dataset}` - if (!req.body) { - throw new ServerError(400, 'Record expected in request body') - } - const db = util.getDB(res) - if (validate) { - if (!db.canIndexRecord(collection, req.body)) { - throw new ServerError( - 400, - `Not a valid record for collection: ${collection}`, - ) - } - } - const serverKey = util.getKeypair(res) - const authStore = await auth.AuthStore.fromTokens(serverKey, []) - const repo = await util.loadRepo(res, did, authStore) - await repo.getCollection(collection).updateRecord(TID.fromStr(tid), req.body) - const uri = new AdxUri(`${did}/${collection}/${tid.toString()}`) - try { - await db.indexRecord(uri, req.body) - } catch (err) { - if (validate) { - throw new ServerError(400, `Could not index record: ${err}`) - } - } - await db.setRepoRoot(did, repo.cid) - // @TODO update subscribers - res.json({ uri: uri.toString() }) -}) - -router.delete('/:did/c/:namespace/:dataset/r/:tid', async (req, res) => { - const { did, namespace, dataset, tid } = req.params - const collection = `${namespace}/${dataset}` - const db = util.getDB(res) - const serverKey = util.getKeypair(res) - const authStore = await auth.AuthStore.fromTokens(serverKey, []) - const repo = await util.loadRepo(res, did, authStore) - await repo.getCollection(collection).deleteRecord(TID.fromStr(tid)) - const uri = new AdxUri(`${did}/${collection}/${tid.toString()}`) - await db.deleteRecord(uri) - await db.setRepoRoot(did, repo.cid) - // @TODO update subscribers - res.sendStatus(200) -}) - -// DESCRIBE REPO -// ------------- - -// @TODO move to a utility file -// @TODO: do we want to call out to did sdk here? 🤔 -async function resolveDidWrapped(did: string) { - try { - return (await didSdk.resolve(did)).didDoc - } catch (e) { - throw new DidResolutionFailed(did) - } -} - -async function resolveNameWrapped(name: string) { - try { - return await resolveName(name) - } catch (e) { - throw new NameResolutionFailed(name) - } -} - -router.get('/:nameOrDid', async (req, res) => { - const { nameOrDid } = req.params - const { confirmName } = util.checkReqBody(req.query, describeRepoParams) - - let name: string - let did: string - let didDoc: didSdk.DIDDocument - let nameIsCorrect: boolean | undefined - - // @TODO add back once we have a did network - // if (nameOrDid.startsWith('did:')) { - // did = nameOrDid - // didDoc = await resolveDidWrapped(did) - // name = 'undefined' // TODO: need to decide how username gets published in the did doc - // if (confirmName) { - // const namesDeclaredDid = await resolveNameWrapped(name) - // nameIsCorrect = did === namesDeclaredDid - // } - // } else { - // name = nameOrDid - // did = await resolveNameWrapped(name) - // didDoc = await resolveDidWrapped(did) - // if (confirmName) { - // const didsDeclaredName = 'undefined' // TODO: need to decide how username gets published in the did doc - // nameIsCorrect = name === didsDeclaredName - // } - // } - - const db = util.getDB(res) - const user = await db.getUser(nameOrDid) - if (user === null) { - throw new ServerError(404, `Could not find user: ${nameOrDid}`) - } - didDoc = {} as any - nameIsCorrect = true - - const collections = await db.listCollectionsForDid(user.did) - - const resBody: DescribeRepoResponse = { - name: user.username, - did: user.did, - didDoc, - collections, - nameIsCorrect, - } - res.json(resBody) -}) - -// LIST RECORDS -// ------------ - -router.get('/:nameOrDid/c/:namespace/:dataset', async (req, res) => { - const { nameOrDid, namespace, dataset } = req.params - const coll = namespace + '/' + dataset - const { - limit = 50, - before, - after, - reverse = false, - } = util.checkReqBody(req.query, listRecordsParams) - - const db = util.getDB(res) - const did = nameOrDid.startsWith('did:') - ? nameOrDid - : (await db.getUser(nameOrDid))?.did - if (!did) { - throw new ServerError(404, `Could not find did for ${nameOrDid}`) - } - - const records = await db.listRecordsForCollection( - did, - coll, - limit, - reverse, - before, - after, - ) - - res.json({ records } as ListRecordsResponse) -}) - -// GET RECORD -// ---------- - -router.get('/:nameOrDid/c/:namespace/:dataset/r/:tid', async (req, res) => { - const { nameOrDid, namespace, dataset, tid } = req.params - const coll = namespace + '/' + dataset - - const did = nameOrDid.startsWith('did:') - ? nameOrDid - : await resolveNameWrapped(nameOrDid) - const uri = new AdxUri(`${did}/${coll}/${tid}`) - - const db = util.getDB(res) - const record = await db.getRecord(uri) - if (record === null) { - throw new ServerError(404, `Could not locate record: ${uri}`) - } - res.json({ uri: uri.toString(), value: record } as GetRecordResponse) -}) - -export default router diff --git a/packages/server/src/routes/v1/api/view.ts b/packages/server/src/routes/v1/api/view.ts deleted file mode 100644 index d6b6e14b976..00000000000 --- a/packages/server/src/routes/v1/api/view.ts +++ /dev/null @@ -1,32 +0,0 @@ -import express from 'express' -import * as util from '../../../util' -import { ServerError } from '../../../error' - -const router = express.Router() - -router.get('/:viewId', async (req, res) => { - const { viewId } = req.params - const query = req.query - for (const [key, val] of Object.entries(query)) { - if (typeof val !== 'string') { - throw new ServerError(400, `Could not parse view param: ${key}`) - } - query[key] = decodeURIComponent(val) - } - - // @TODO switch out for actual auth - const requester = req.headers.authorization - if (!requester) { - throw new ServerError(401, 'No user token') - } - - const db = util.getDB(res) - const view = db.views[viewId] - if (!view) { - throw new ServerError(400, `A view does not exist with the id: ${viewId}`) - } - const viewRes = await view(query, requester) - res.status(200).send(viewRes) -}) - -export default router diff --git a/packages/server/src/routes/v1/data/index.ts b/packages/server/src/routes/v1/data/index.ts deleted file mode 100644 index 374b1f67770..00000000000 --- a/packages/server/src/routes/v1/data/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import express from 'express' -import Root from './root' -import Repo from './repo' -import Subscribe from './subscribe' - -const router = express.Router() - -router.use('/root', Root) -router.use('/repo', Repo) -// router.use('/subscribe', Subscribe) - -export default router diff --git a/packages/server/src/routes/v1/data/repo.ts b/packages/server/src/routes/v1/data/repo.ts deleted file mode 100644 index 46f21caf262..00000000000 --- a/packages/server/src/routes/v1/data/repo.ts +++ /dev/null @@ -1,61 +0,0 @@ -import express from 'express' -import * as util from '../../../util' -import { getRepoRequest, postRepoRequest } from '@adxp/api' -import { DataDiff, Repo } from '@adxp/repo' -import * as repoDiff from '../../../repo-diff' - -const router = express.Router() - -router.get('/', async (req, res) => { - const query = util.checkReqBody(req.query, getRepoRequest) - const { did, from = null } = query - const repo = await util.loadRepo(res, did) - const diff = await repo.getDiffCar(from) - res.status(200).send(Buffer.from(diff)) -}) - -router.post('/:did', async (req, res) => { - // we don't need auth here because the auth is on the data structure 😎 - const { did } = util.checkReqBody(req.params, postRepoRequest) - const bytes = await util.readReqBytes(req) - - const db = util.getDB(res) - - // @TODO add something back here. new route for repos not on server? - - // check to see if we have their username in DB, for indexed queries - // const haveUsername = await db.isDidRegistered(did) - // if (!haveUsername) { - // const username = await service.getUsernameFromDidNetwork(did) - // if (username) { - // const [name, host] = username.split('@') - // await db.registerDid(name, did, host) - // } - // } - - const maybeRepo = await util.maybeLoadRepo(res, did) - const isNewRepo = maybeRepo === null - let repo: Repo - let diff: DataDiff - - // @TODO: we should do these on a temp in-memory blockstore before merging down to our on-disk one - if (!isNewRepo) { - repo = maybeRepo - await repo.loadAndVerifyDiff - diff = await repo.loadAndVerifyDiff(bytes) - } else { - const blockstore = util.getBlockstore(res) - repo = await Repo.fromCarFile(bytes, blockstore) - diff = await repo.verifySetOfUpdates(null, repo.cid) - } - - await repoDiff.processDiff(db, repo, diff) - - // await subscriptions.notifySubscribers(db, repo) - - await db.setRepoRoot(did, repo.cid) - - res.status(200).send() -}) - -export default router diff --git a/packages/server/src/routes/v1/data/root.ts b/packages/server/src/routes/v1/data/root.ts deleted file mode 100644 index 17348c9bf9e..00000000000 --- a/packages/server/src/routes/v1/data/root.ts +++ /dev/null @@ -1,23 +0,0 @@ -import express from 'express' -import { z } from 'zod' -import { ServerError } from '../../../error' -import * as util from '../../../util' - -const router = express.Router() - -export const getRootReq = z.object({ - did: z.string(), -}) -export type GetRootReq = z.infer - -router.get('/', async (req, res) => { - const { did } = util.checkReqBody(req.query, getRootReq) - const db = util.getDB(res) - const root = await db.getRepoRoot(did) - if (root === null) { - throw new ServerError(404, `Could not find root for DID: ${did}`) - } - res.status(200).send({ root: root.toString() }) -}) - -export default router diff --git a/packages/server/src/routes/v1/data/subscribe.ts b/packages/server/src/routes/v1/data/subscribe.ts deleted file mode 100644 index 30a9b5b655d..00000000000 --- a/packages/server/src/routes/v1/data/subscribe.ts +++ /dev/null @@ -1,23 +0,0 @@ -import express from 'express' -import { z } from 'zod' -import * as util from '../../../util' - -const router = express.Router() - -export const subscribeReq = z.object({ - did: z.string(), - host: z.string(), -}) -export type subscribeReq = z.infer - -router.post('/', async (req, res) => { - const { did, host } = util.checkReqBody(req.body, subscribeReq) - const db = util.getDB(res) - await db.createSubscription(host, did) - // do an initial push - const repo = await util.loadRepo(res, did) - await repo.push(host) - res.status(200).send() -}) - -export default router diff --git a/packages/server/src/routes/v1/session.ts b/packages/server/src/routes/v1/session.ts deleted file mode 100644 index e12f4ca8e5e..00000000000 --- a/packages/server/src/routes/v1/session.ts +++ /dev/null @@ -1,28 +0,0 @@ -import express from 'express' -import { z } from 'zod' - -const router = express.Router() - -export const registerReq = z.object({ - did: z.string(), - username: z.string(), - createRepo: z.boolean(), -}) -export type RegisterReq = z.infer - -router.get('/', async (req, res) => { - // TODO get current session - return res.sendStatus(501) -}) - -router.post('/', async (req, res) => { - // TODO trade a UCAN for a session token - return res.sendStatus(501) -}) - -router.post('/', async (req, res) => { - // TODO delete the current session - return res.sendStatus(501) -}) - -export default router diff --git a/packages/server/src/routes/well-known.ts b/packages/server/src/routes/well-known.ts deleted file mode 100644 index f5e01d46f15..00000000000 --- a/packages/server/src/routes/well-known.ts +++ /dev/null @@ -1,13 +0,0 @@ -import express from 'express' -import * as util from '../util' - -const router = express.Router() - -router.get('/adx-did', (_req, res) => { - const keypair = util.getKeypair(res) - // Return the server's did - // TODO check host header - return res.send(keypair.did()) -}) - -export default router diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index b41fa316b48..f9a42d8e15b 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -8,7 +8,7 @@ import express from 'express' import cors from 'cors' import http from 'http' import * as auth from '@adxp/auth' -import Routes from './routes' +import API from './api' import { IpldStore } from '@adxp/repo' import Database from './db' import * as error from './error' @@ -20,7 +20,7 @@ const runServer = ( port: number, ): http.Server => { const app = express() - app.use(express.json()) + // app.use(express.json()) app.use(cors()) app.use((_req, res, next) => { @@ -30,7 +30,8 @@ const runServer = ( next() }) - app.use('/', Routes) + const apiServer = API() + app.use(apiServer.xrpc.router) app.use(error.handler) return app.listen(port) diff --git a/packages/server/src/xrpc/index.ts b/packages/server/src/xrpc/index.ts new file mode 100644 index 00000000000..3d898957310 --- /dev/null +++ b/packages/server/src/xrpc/index.ts @@ -0,0 +1,200 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { + createServer as createXrpcServer, + Server as XrpcServer, +} from '@adxp/xrpc-server' +import { methodSchemas } from './schemas' +import * as TodoAdxCreateAccount from './types/todo/adx/createAccount' +import * as TodoAdxCreateSession from './types/todo/adx/createSession' +import * as TodoAdxDeleteAccount from './types/todo/adx/deleteAccount' +import * as TodoAdxDeleteSession from './types/todo/adx/deleteSession' +import * as TodoAdxGetAccount from './types/todo/adx/getAccount' +import * as TodoAdxGetSession from './types/todo/adx/getSession' +import * as TodoAdxRepoBatchWrite from './types/todo/adx/repoBatchWrite' +import * as TodoAdxRepoCreateRecord from './types/todo/adx/repoCreateRecord' +import * as TodoAdxRepoDeleteRecord from './types/todo/adx/repoDeleteRecord' +import * as TodoAdxRepoDescribe from './types/todo/adx/repoDescribe' +import * as TodoAdxRepoGetRecord from './types/todo/adx/repoGetRecord' +import * as TodoAdxRepoListRecords from './types/todo/adx/repoListRecords' +import * as TodoAdxRepoPutRecord from './types/todo/adx/repoPutRecord' +import * as TodoAdxResolveName from './types/todo/adx/resolveName' +import * as TodoAdxSyncGetRepo from './types/todo/adx/syncGetRepo' +import * as TodoAdxSyncGetRoot from './types/todo/adx/syncGetRoot' +import * as TodoAdxSyncUpdateRepo from './types/todo/adx/syncUpdateRepo' +import * as TodoSocialGetFeed from './types/todo/social/getFeed' +import * as TodoSocialGetLikedBy from './types/todo/social/getLikedBy' +import * as TodoSocialGetNotifications from './types/todo/social/getNotifications' +import * as TodoSocialGetPostThread from './types/todo/social/getPostThread' +import * as TodoSocialGetProfile from './types/todo/social/getProfile' +import * as TodoSocialGetRepostedBy from './types/todo/social/getRepostedBy' +import * as TodoSocialGetUserFollowers from './types/todo/social/getUserFollowers' +import * as TodoSocialGetUserFollows from './types/todo/social/getUserFollows' + +export function createServer(): Server { + return new Server() +} + +export class Server { + xrpc: XrpcServer = createXrpcServer(methodSchemas) + todo: TodoNS + + constructor() { + this.todo = new TodoNS(this) + } +} + +export class TodoNS { + server: Server + adx: AdxNS + social: SocialNS + + constructor(server: Server) { + this.server = server + this.adx = new AdxNS(server) + this.social = new SocialNS(server) + } +} + +export class AdxNS { + server: Server + + constructor(server: Server) { + this.server = server + } + + createAccount(handler: TodoAdxCreateAccount.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.adx.createAccount', handler) + } + + createSession(handler: TodoAdxCreateSession.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.adx.createSession', handler) + } + + deleteAccount(handler: TodoAdxDeleteAccount.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.adx.deleteAccount', handler) + } + + deleteSession(handler: TodoAdxDeleteSession.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.adx.deleteSession', handler) + } + + getAccount(handler: TodoAdxGetAccount.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.adx.getAccount', handler) + } + + getSession(handler: TodoAdxGetSession.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.adx.getSession', handler) + } + + repoBatchWrite(handler: TodoAdxRepoBatchWrite.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.adx.repoBatchWrite', handler) + } + + repoCreateRecord(handler: TodoAdxRepoCreateRecord.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.adx.repoCreateRecord', handler) + } + + repoDeleteRecord(handler: TodoAdxRepoDeleteRecord.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.adx.repoDeleteRecord', handler) + } + + repoDescribe(handler: TodoAdxRepoDescribe.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.adx.repoDescribe', handler) + } + + repoGetRecord(handler: TodoAdxRepoGetRecord.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.adx.repoGetRecord', handler) + } + + repoListRecords(handler: TodoAdxRepoListRecords.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.adx.repoListRecords', handler) + } + + repoPutRecord(handler: TodoAdxRepoPutRecord.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.adx.repoPutRecord', handler) + } + + resolveName(handler: TodoAdxResolveName.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.adx.resolveName', handler) + } + + syncGetRepo(handler: TodoAdxSyncGetRepo.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.adx.syncGetRepo', handler) + } + + syncGetRoot(handler: TodoAdxSyncGetRoot.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.adx.syncGetRoot', handler) + } + + syncUpdateRepo(handler: TodoAdxSyncUpdateRepo.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.adx.syncUpdateRepo', handler) + } +} + +export class SocialNS { + server: Server + + constructor(server: Server) { + this.server = server + } + + getFeed(handler: TodoSocialGetFeed.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.social.getFeed', handler) + } + + getLikedBy(handler: TodoSocialGetLikedBy.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.social.getLikedBy', handler) + } + + getNotifications(handler: TodoSocialGetNotifications.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.social.getNotifications', handler) + } + + getPostThread(handler: TodoSocialGetPostThread.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.social.getPostThread', handler) + } + + getProfile(handler: TodoSocialGetProfile.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.social.getProfile', handler) + } + + getRepostedBy(handler: TodoSocialGetRepostedBy.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.social.getRepostedBy', handler) + } + + getUserFollowers(handler: TodoSocialGetUserFollowers.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.social.getUserFollowers', handler) + } + + getUserFollows(handler: TodoSocialGetUserFollows.Handler) { + /** @ts-ignore */ + return this.server.xrpc.method('todo.social.getUserFollows', handler) + } +} diff --git a/packages/server/src/xrpc/schemas.ts b/packages/server/src/xrpc/schemas.ts new file mode 100644 index 00000000000..67b1ff0c6d7 --- /dev/null +++ b/packages/server/src/xrpc/schemas.ts @@ -0,0 +1,1559 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import { MethodSchema } from '@adxp/xrpc' +import { AdxSchemaDefinition } from '@adxp/schemas' + +export const methodSchemas: MethodSchema[] = [ + { + xrpc: 1, + id: 'todo.adx.createAccount', + type: 'procedure', + description: 'Create an account.', + parameters: {}, + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['username', 'did'], + properties: { + username: { + type: 'string', + }, + did: { + type: 'string', + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.adx.createSession', + type: 'procedure', + description: 'Create an authentication session.', + parameters: {}, + input: { + encoding: '', + schema: {}, + }, + output: { + encoding: '', + schema: {}, + }, + }, + { + xrpc: 1, + id: 'todo.adx.deleteAccount', + type: 'procedure', + description: 'Delete an account.', + parameters: {}, + input: { + encoding: '', + schema: {}, + }, + output: { + encoding: '', + schema: {}, + }, + }, + { + xrpc: 1, + id: 'todo.adx.deleteSession', + type: 'procedure', + description: 'Delete the current session.', + parameters: {}, + input: { + encoding: '', + schema: {}, + }, + output: { + encoding: '', + schema: {}, + }, + }, + { + xrpc: 1, + id: 'todo.adx.getAccount', + type: 'query', + description: 'Get information about an account.', + parameters: {}, + input: { + encoding: '', + schema: {}, + }, + output: { + encoding: '', + schema: {}, + }, + }, + { + xrpc: 1, + id: 'todo.adx.getSession', + type: 'query', + description: 'Get information about the current session.', + parameters: {}, + input: { + encoding: '', + schema: {}, + }, + output: { + encoding: '', + schema: {}, + }, + }, + { + xrpc: 1, + id: 'todo.adx.repoBatchWrite', + type: 'procedure', + description: 'Apply a batch transaction of creates, puts, and deletes.', + parameters: { + did: { + type: 'string', + description: 'The DID of the repo.', + required: true, + }, + validate: { + type: 'boolean', + description: 'Validate the records?', + default: true, + }, + }, + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['writes'], + properties: { + writes: { + type: 'array', + items: { + oneOf: [ + { + type: 'object', + required: ['action', 'collection', 'value'], + properties: { + action: { + type: 'string', + const: 'create', + }, + collection: { + type: 'string', + }, + value: {}, + }, + }, + { + type: 'object', + required: ['action', 'collection', 'tid', 'value'], + properties: { + action: { + type: 'string', + const: 'update', + }, + collection: { + type: 'string', + }, + tid: { + type: 'string', + }, + value: {}, + }, + }, + { + type: 'object', + required: ['action', 'collection', 'tid'], + properties: { + action: { + type: 'string', + const: 'delete', + }, + collection: { + type: 'string', + }, + tid: { + type: 'string', + }, + }, + }, + ], + }, + }, + }, + }, + }, + output: { + encoding: 'application/json', + schema: {}, + }, + }, + { + xrpc: 1, + id: 'todo.adx.repoCreateRecord', + type: 'procedure', + description: 'Create a new record.', + parameters: { + did: { + type: 'string', + description: 'The DID of the repo.', + required: true, + }, + type: { + type: 'string', + description: 'The NSID of the record type.', + required: true, + }, + validate: { + type: 'boolean', + description: 'Validate the record?', + default: true, + }, + }, + input: { + encoding: 'application/json', + schema: {}, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['uri'], + properties: { + uri: { + type: 'string', + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.adx.repoDeleteRecord', + type: 'procedure', + description: 'Delete a record.', + parameters: { + did: { + type: 'string', + description: 'The DID of the repo.', + required: true, + }, + type: { + type: 'string', + description: 'The NSID of the record type.', + required: true, + }, + tid: { + type: 'string', + description: 'The TID of the record.', + required: true, + }, + }, + }, + { + xrpc: 1, + id: 'todo.adx.repoDescribe', + type: 'query', + description: + 'Get information about the repo, including the list of collections.', + parameters: { + nameOrDid: { + type: 'string', + description: 'The username or DID of the repo.', + required: true, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['name', 'did', 'didDoc', 'collections', 'nameIsCorrect'], + properties: { + name: { + type: 'string', + }, + did: { + type: 'string', + }, + didDoc: { + type: 'object', + }, + collections: { + type: 'array', + items: { + type: 'string', + }, + }, + nameIsCorrect: { + type: 'boolean', + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.adx.repoGetRecord', + type: 'query', + description: 'Fetch a record.', + parameters: { + nameOrDid: { + type: 'string', + description: 'The name or DID of the repo.', + required: true, + }, + type: { + type: 'string', + description: 'The NSID of the record type.', + required: true, + }, + tid: { + type: 'string', + description: 'The TID of the record.', + required: true, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['uri', 'value'], + properties: { + uri: { + type: 'string', + }, + value: { + type: 'object', + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.adx.repoListRecords', + type: 'query', + description: 'List a range of records in a collection.', + parameters: { + nameOrDid: { + type: 'string', + description: 'The username or DID of the repo.', + required: true, + }, + type: { + type: 'string', + description: 'The NSID of the record type.', + required: true, + }, + limit: { + type: 'number', + description: 'The number of records to return. TODO-max number?', + default: 50, + minimum: 1, + }, + before: { + type: 'string', + description: 'A TID to filter the range of records returned.', + }, + after: { + type: 'string', + description: 'A TID to filter the range of records returned.', + }, + reverse: { + type: 'boolean', + description: 'Reverse the order of the returned records?', + default: false, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['records'], + properties: { + records: { + type: 'array', + items: { + type: 'object', + required: ['uri', 'value'], + properties: { + uri: { + type: 'string', + }, + value: { + type: 'object', + }, + }, + }, + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.adx.repoPutRecord', + type: 'procedure', + description: 'Write a record.', + parameters: { + did: { + type: 'string', + description: 'The DID of the repo.', + required: true, + }, + type: { + type: 'string', + description: 'The NSID of the record type.', + required: true, + }, + tid: { + type: 'string', + description: 'The TID of the record.', + required: true, + }, + validate: { + type: 'boolean', + description: 'Validate the record?', + default: true, + }, + }, + input: { + encoding: 'application/json', + schema: {}, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['uri'], + properties: { + uri: { + type: 'string', + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.adx.resolveName', + type: 'query', + description: + 'Provides the DID of the repo indicated by the Host parameter.', + parameters: { + name: { + type: 'string', + description: 'The name to resolve.', + required: true, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['did'], + properties: { + did: { + type: 'string', + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.adx.syncGetRepo', + type: 'query', + description: 'Gets the repo state.', + parameters: { + did: { + type: 'string', + description: 'The DID of the repo.', + required: true, + }, + from: { + type: 'string', + description: 'A past commit CID', + }, + }, + output: { + encoding: 'application/cbor', + }, + }, + { + xrpc: 1, + id: 'todo.adx.syncGetRoot', + type: 'query', + description: 'Gets the current root CID of a repo.', + parameters: { + did: { + type: 'string', + description: 'The DID of the repo.', + required: true, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['root'], + properties: { + root: { + type: 'string', + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.adx.syncUpdateRepo', + type: 'procedure', + description: 'Writes commits to a repo.', + parameters: { + did: { + type: 'string', + description: 'The DID of the repo.', + required: true, + }, + }, + input: { + encoding: 'application/cbor', + }, + }, + { + xrpc: 1, + id: 'todo.social.getFeed', + type: 'query', + description: "A computed view of the home feed or a user's feed", + parameters: { + author: { + type: 'string', + }, + limit: { + type: 'number', + maximum: 100, + }, + before: { + type: 'string', + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['feed'], + properties: { + feed: { + type: 'array', + items: { + $ref: '#/$defs/feedItem', + }, + }, + }, + $defs: { + feedItem: { + type: 'object', + required: [ + 'uri', + 'author', + 'record', + 'replyCount', + 'repostCount', + 'likeCount', + 'indexedAt', + ], + properties: { + uri: { + type: 'string', + }, + author: { + $ref: '#/$defs/user', + }, + repostedBy: { + $ref: '#/$defs/user', + }, + record: { + type: 'object', + }, + embed: { + oneOf: [ + { + $ref: '#/$defs/recordEmbed', + }, + { + $ref: '#/$defs/externalEmbed', + }, + { + $ref: '#/$defs/unknownEmbed', + }, + ], + }, + replyCount: { + type: 'number', + }, + repostCount: { + type: 'number', + }, + likeCount: { + type: 'number', + }, + indexedAt: { + type: 'string', + format: 'date-time', + }, + myState: { + type: 'object', + properties: { + repost: { + type: 'string', + }, + like: { + type: 'string', + }, + }, + }, + }, + }, + user: { + type: 'object', + required: ['did', 'name'], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + }, + }, + recordEmbed: { + type: 'object', + required: ['type', 'author', 'record'], + properties: { + type: { + const: 'record', + }, + author: { + $ref: '#/$defs/user', + }, + record: { + type: 'object', + }, + }, + }, + externalEmbed: { + type: 'object', + required: ['type', 'uri', 'title', 'description', 'imageUri'], + properties: { + type: { + const: 'external', + }, + uri: { + type: 'string', + }, + title: { + type: 'string', + }, + description: { + type: 'string', + }, + imageUri: { + type: 'string', + }, + }, + }, + unknownEmbed: { + type: 'object', + required: ['type'], + properties: { + type: { + type: 'string', + not: { + enum: ['record', 'external'], + }, + }, + }, + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.social.getLikedBy', + type: 'query', + parameters: { + uri: { + type: 'string', + required: true, + }, + limit: { + type: 'number', + maximum: 100, + }, + before: { + type: 'string', + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['uri', 'likedBy'], + properties: { + uri: { + type: 'string', + }, + likedBy: { + type: 'array', + items: { + type: 'object', + required: ['did', 'name', 'indexedAt'], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + indexedAt: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.social.getNotifications', + type: 'query', + parameters: { + limit: { + type: 'number', + maximum: 100, + }, + before: { + type: 'string', + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['notifications'], + properties: { + notifications: { + type: 'array', + items: { + $ref: '#/$defs/notification', + }, + }, + }, + $defs: { + notification: { + type: 'object', + required: ['uri', 'author', 'record', 'isRead', 'indexedAt'], + properties: { + uri: { + type: 'string', + format: 'uri', + }, + author: { + type: 'object', + required: ['did', 'name', 'displayName'], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + }, + }, + record: { + type: 'object', + }, + isRead: { + type: 'boolean', + }, + indexedAt: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.social.getPostThread', + type: 'query', + parameters: { + uri: { + type: 'string', + required: true, + }, + depth: { + type: 'number', + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['thread'], + properties: { + thread: { + $ref: '#/$defs/post', + }, + }, + $defs: { + post: { + type: 'object', + required: [ + 'uri', + 'author', + 'record', + 'replyCount', + 'likeCount', + 'repostCount', + 'indexedAt', + ], + properties: { + uri: { + type: 'string', + }, + author: { + $ref: '#/$defs/user', + }, + record: { + type: 'object', + }, + embed: { + oneOf: [ + { + $ref: '#/$defs/recordEmbed', + }, + { + $ref: '#/$defs/externalEmbed', + }, + { + $ref: '#/$defs/unknownEmbed', + }, + ], + }, + parent: { + $ref: '#/$defs/post', + }, + replyCount: { + type: 'number', + }, + replies: { + type: 'array', + items: { + $ref: '#/$defs/post', + }, + }, + likeCount: { + type: 'number', + }, + repostCount: { + type: 'number', + }, + indexedAt: { + type: 'string', + format: 'date-time', + }, + myState: { + type: 'object', + properties: { + repost: { + type: 'string', + }, + like: { + type: 'string', + }, + }, + }, + }, + }, + user: { + type: 'object', + required: ['did', 'name'], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + }, + }, + recordEmbed: { + type: 'object', + required: ['type', 'author', 'record'], + properties: { + type: { + const: 'record', + }, + author: { + $ref: '#/$defs/user', + }, + record: { + type: 'object', + }, + }, + }, + externalEmbed: { + type: 'object', + required: ['type', 'uri', 'title', 'description', 'imageUri'], + properties: { + type: { + const: 'external', + }, + uri: { + type: 'string', + }, + title: { + type: 'string', + }, + description: { + type: 'string', + }, + imageUri: { + type: 'string', + }, + }, + }, + unknownEmbed: { + type: 'object', + required: ['type'], + properties: { + type: { + type: 'string', + not: { + enum: ['record', 'external'], + }, + }, + }, + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.social.getProfile', + type: 'query', + parameters: { + user: { + type: 'string', + required: true, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: [ + 'did', + 'name', + 'followersCount', + 'followsCount', + 'postsCount', + 'badges', + ], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + description: { + type: 'string', + maxLength: 256, + }, + followersCount: { + type: 'number', + }, + followsCount: { + type: 'number', + }, + postsCount: { + type: 'number', + }, + badges: { + type: 'array', + items: { + $ref: '#/$defs/badge', + }, + }, + myState: { + type: 'object', + properties: { + follow: { + type: 'string', + }, + }, + }, + }, + $defs: { + badge: { + type: 'object', + required: ['uri'], + properties: { + uri: { + type: 'string', + }, + error: { + type: 'string', + }, + issuer: { + type: 'object', + required: ['did', 'name', 'displayName'], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + }, + }, + assertion: { + type: 'object', + required: ['type'], + properties: { + type: { + type: 'string', + }, + }, + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.social.getRepostedBy', + type: 'query', + parameters: { + uri: { + type: 'string', + required: true, + }, + limit: { + type: 'number', + maximum: 100, + }, + before: { + type: 'string', + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['uri', 'repostedBy'], + properties: { + uri: { + type: 'string', + }, + repostedBy: { + type: 'array', + items: { + type: 'object', + required: ['did', 'name', 'indexedAt'], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + indexedAt: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.social.getUserFollowers', + type: 'query', + description: 'Who is following a user?', + parameters: { + user: { + type: 'string', + required: true, + }, + limit: { + type: 'number', + maximum: 100, + }, + before: { + type: 'string', + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['subject', 'followers'], + properties: { + subject: { + type: 'object', + required: ['did', 'name'], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + }, + }, + followers: { + type: 'array', + items: { + type: 'object', + required: ['did', 'name', 'indexedAt'], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + indexedAt: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, + }, + }, + }, + }, + { + xrpc: 1, + id: 'todo.social.getUserFollows', + type: 'query', + description: 'Who is a user following?', + parameters: { + user: { + type: 'string', + required: true, + }, + limit: { + type: 'number', + maximum: 100, + }, + before: { + type: 'string', + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['subject', 'follows'], + properties: { + subject: { + type: 'object', + required: ['did', 'name'], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + }, + }, + follows: { + type: 'array', + items: { + type: 'object', + required: ['did', 'name', 'indexedAt'], + properties: { + did: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + maxLength: 64, + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + indexedAt: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, + }, + }, + }, + }, +] +export const recordSchemas: AdxSchemaDefinition[] = [ + { + adx: 1, + id: 'todo.social.badge', + description: 'An assertion about the subject by this user.', + record: { + type: 'object', + required: ['assertion', 'subject', 'createdAt'], + properties: { + assertion: { + oneOf: [ + { + $ref: '#/$defs/inviteAssertion', + }, + { + $ref: '#/$defs/employeeAssertion', + }, + { + $ref: '#/$defs/tagAssertion', + }, + { + $ref: '#/$defs/unknownAssertion', + }, + ], + }, + subject: { + type: 'string', + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + }, + $defs: { + inviteAssertion: { + type: 'object', + required: ['type'], + properties: { + type: { + const: 'invite', + }, + }, + }, + employeeAssertion: { + type: 'object', + required: ['type'], + properties: { + type: { + const: 'employee', + }, + }, + }, + tagAssertion: { + type: 'object', + required: ['type', 'tag'], + properties: { + type: { + const: 'tag', + }, + tag: { + type: 'string', + maxLength: 64, + }, + }, + }, + unknownAssertion: { + type: 'object', + required: ['type'], + properties: { + type: { + type: 'string', + not: { + enum: ['invite', 'employee', 'tag'], + }, + }, + }, + }, + }, + }, + }, + { + adx: 1, + id: 'todo.social.follow', + description: 'A social follow', + record: { + type: 'object', + required: ['subject', 'createdAt'], + properties: { + subject: { + type: 'string', + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, + { + adx: 1, + id: 'todo.social.like', + record: { + type: 'object', + required: ['subject', 'createdAt'], + properties: { + subject: { + type: 'string', + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, + { + adx: 1, + id: 'todo.social.mediaEmbed', + description: 'A list of media embedded in a post or document.', + record: { + type: 'object', + required: ['media'], + properties: { + media: { + type: 'array', + items: { + $ref: '#/$defs/mediaEmbed', + }, + }, + }, + $defs: { + mediaEmbed: { + type: 'object', + required: ['original'], + properties: { + alt: { + type: 'string', + }, + thumb: { + $ref: '#/$defs/mediaEmbedBlob', + }, + original: { + $ref: '#/$defs/mediaEmbedBlob', + }, + }, + }, + mediaEmbedBlob: { + type: 'object', + required: ['mimeType', 'blobId'], + properties: { + mimeType: { + type: 'string', + }, + blobId: { + type: 'string', + }, + }, + }, + }, + }, + }, + { + adx: 1, + id: 'todo.social.post', + record: { + type: 'object', + required: ['text', 'createdAt'], + properties: { + text: { + type: 'string', + maxLength: 256, + }, + entities: { + $ref: '#/$defs/entity', + }, + reply: { + type: 'object', + required: ['root'], + properties: { + root: { + type: 'string', + }, + parent: { + type: 'string', + }, + }, + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + }, + $defs: { + entity: { + type: 'array', + items: { + type: 'object', + required: ['index', 'type', 'value'], + properties: { + index: { + $ref: '#/$defs/textSlice', + }, + type: { + type: 'string', + $comment: + "Expected values are 'mention', 'hashtag', and 'link'.", + }, + value: { + type: 'string', + }, + }, + }, + }, + textSlice: { + type: 'array', + items: [ + { + type: 'number', + }, + { + type: 'number', + }, + ], + minItems: 2, + maxItems: 2, + }, + }, + }, + }, + { + adx: 1, + id: 'todo.social.profile', + record: { + type: 'object', + required: ['displayName'], + properties: { + displayName: { + type: 'string', + maxLength: 64, + }, + description: { + type: 'string', + maxLength: 256, + }, + badges: { + type: 'array', + items: { + $ref: '#/$defs/badgeRef', + }, + }, + }, + $defs: { + badgeRef: { + type: 'object', + required: ['uri'], + properties: { + uri: { + type: 'string', + }, + }, + }, + }, + }, + }, + { + adx: 1, + id: 'todo.social.repost', + record: { + type: 'object', + required: ['subject', 'createdAt'], + properties: { + subject: { + type: 'string', + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, +] diff --git a/packages/server/src/xrpc/types/todo/adx/createAccount.ts b/packages/server/src/xrpc/types/todo/adx/createAccount.ts new file mode 100644 index 00000000000..1c0e7128a99 --- /dev/null +++ b/packages/server/src/xrpc/types/todo/adx/createAccount.ts @@ -0,0 +1,25 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams {} + +export interface HandlerInput { + encoding: 'application/json'; + body: InputSchema; +} + +export interface InputSchema { + username: string; + did: string; +} + +export type HandlerOutput = void +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/adx/createSession.ts b/packages/server/src/xrpc/types/todo/adx/createSession.ts new file mode 100644 index 00000000000..92ac29ef02f --- /dev/null +++ b/packages/server/src/xrpc/types/todo/adx/createSession.ts @@ -0,0 +1,29 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams {} + +export type HandlerInput = undefined + +export interface InputSchema { + [k: string]: unknown; +} + +export interface HandlerOutput { + encoding: ''; + body: OutputSchema; +} + +export interface OutputSchema { + [k: string]: unknown; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/adx/deleteAccount.ts b/packages/server/src/xrpc/types/todo/adx/deleteAccount.ts new file mode 100644 index 00000000000..92ac29ef02f --- /dev/null +++ b/packages/server/src/xrpc/types/todo/adx/deleteAccount.ts @@ -0,0 +1,29 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams {} + +export type HandlerInput = undefined + +export interface InputSchema { + [k: string]: unknown; +} + +export interface HandlerOutput { + encoding: ''; + body: OutputSchema; +} + +export interface OutputSchema { + [k: string]: unknown; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/adx/deleteSession.ts b/packages/server/src/xrpc/types/todo/adx/deleteSession.ts new file mode 100644 index 00000000000..92ac29ef02f --- /dev/null +++ b/packages/server/src/xrpc/types/todo/adx/deleteSession.ts @@ -0,0 +1,29 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams {} + +export type HandlerInput = undefined + +export interface InputSchema { + [k: string]: unknown; +} + +export interface HandlerOutput { + encoding: ''; + body: OutputSchema; +} + +export interface OutputSchema { + [k: string]: unknown; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/adx/getAccount.ts b/packages/server/src/xrpc/types/todo/adx/getAccount.ts new file mode 100644 index 00000000000..92ac29ef02f --- /dev/null +++ b/packages/server/src/xrpc/types/todo/adx/getAccount.ts @@ -0,0 +1,29 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams {} + +export type HandlerInput = undefined + +export interface InputSchema { + [k: string]: unknown; +} + +export interface HandlerOutput { + encoding: ''; + body: OutputSchema; +} + +export interface OutputSchema { + [k: string]: unknown; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/adx/getSession.ts b/packages/server/src/xrpc/types/todo/adx/getSession.ts new file mode 100644 index 00000000000..92ac29ef02f --- /dev/null +++ b/packages/server/src/xrpc/types/todo/adx/getSession.ts @@ -0,0 +1,29 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams {} + +export type HandlerInput = undefined + +export interface InputSchema { + [k: string]: unknown; +} + +export interface HandlerOutput { + encoding: ''; + body: OutputSchema; +} + +export interface OutputSchema { + [k: string]: unknown; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/adx/repoBatchWrite.ts b/packages/server/src/xrpc/types/todo/adx/repoBatchWrite.ts new file mode 100644 index 00000000000..38865908907 --- /dev/null +++ b/packages/server/src/xrpc/types/todo/adx/repoBatchWrite.ts @@ -0,0 +1,52 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams { + did: string; + validate?: boolean; +} + +export interface HandlerInput { + encoding: 'application/json'; + body: InputSchema; +} + +export interface InputSchema { + writes: ( + | { + action: 'create', + collection: string, + value: unknown, + } + | { + action: 'update', + collection: string, + tid: string, + value: unknown, + } + | { + action: 'delete', + collection: string, + tid: string, + } + )[]; +} + +export interface HandlerOutput { + encoding: 'application/json'; + body: OutputSchema; +} + +export interface OutputSchema { + [k: string]: unknown; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/adx/repoCreateRecord.ts b/packages/server/src/xrpc/types/todo/adx/repoCreateRecord.ts new file mode 100644 index 00000000000..b187db41f1e --- /dev/null +++ b/packages/server/src/xrpc/types/todo/adx/repoCreateRecord.ts @@ -0,0 +1,36 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams { + did: string; + type: string; + validate?: boolean; +} + +export interface HandlerInput { + encoding: 'application/json'; + body: InputSchema; +} + +export interface InputSchema { + [k: string]: unknown; +} + +export interface HandlerOutput { + encoding: 'application/json'; + body: OutputSchema; +} + +export interface OutputSchema { + uri: string; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/adx/repoDeleteRecord.ts b/packages/server/src/xrpc/types/todo/adx/repoDeleteRecord.ts new file mode 100644 index 00000000000..005131cb64a --- /dev/null +++ b/packages/server/src/xrpc/types/todo/adx/repoDeleteRecord.ts @@ -0,0 +1,20 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams { + did: string; + type: string; + tid: string; +} + +export type HandlerInput = undefined +export type HandlerOutput = void +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/adx/repoDescribe.ts b/packages/server/src/xrpc/types/todo/adx/repoDescribe.ts new file mode 100644 index 00000000000..914f08eacc6 --- /dev/null +++ b/packages/server/src/xrpc/types/todo/adx/repoDescribe.ts @@ -0,0 +1,31 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams { + nameOrDid: string; +} + +export type HandlerInput = undefined + +export interface HandlerOutput { + encoding: 'application/json'; + body: OutputSchema; +} + +export interface OutputSchema { + name: string; + did: string; + didDoc: {}; + collections: string[]; + nameIsCorrect: boolean; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/adx/repoGetRecord.ts b/packages/server/src/xrpc/types/todo/adx/repoGetRecord.ts new file mode 100644 index 00000000000..c133cc825d2 --- /dev/null +++ b/packages/server/src/xrpc/types/todo/adx/repoGetRecord.ts @@ -0,0 +1,30 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams { + nameOrDid: string; + type: string; + tid: string; +} + +export type HandlerInput = undefined + +export interface HandlerOutput { + encoding: 'application/json'; + body: OutputSchema; +} + +export interface OutputSchema { + uri: string; + value: {}; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/adx/repoListRecords.ts b/packages/server/src/xrpc/types/todo/adx/repoListRecords.ts new file mode 100644 index 00000000000..f31f1ed622d --- /dev/null +++ b/packages/server/src/xrpc/types/todo/adx/repoListRecords.ts @@ -0,0 +1,35 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams { + nameOrDid: string; + type: string; + limit?: number; + before?: string; + after?: string; + reverse?: boolean; +} + +export type HandlerInput = undefined + +export interface HandlerOutput { + encoding: 'application/json'; + body: OutputSchema; +} + +export interface OutputSchema { + records: { + uri: string, + value: {}, + }[]; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/adx/repoPutRecord.ts b/packages/server/src/xrpc/types/todo/adx/repoPutRecord.ts new file mode 100644 index 00000000000..9c807a097fc --- /dev/null +++ b/packages/server/src/xrpc/types/todo/adx/repoPutRecord.ts @@ -0,0 +1,37 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams { + did: string; + type: string; + tid: string; + validate?: boolean; +} + +export interface HandlerInput { + encoding: 'application/json'; + body: InputSchema; +} + +export interface InputSchema { + [k: string]: unknown; +} + +export interface HandlerOutput { + encoding: 'application/json'; + body: OutputSchema; +} + +export interface OutputSchema { + uri: string; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/adx/resolveName.ts b/packages/server/src/xrpc/types/todo/adx/resolveName.ts new file mode 100644 index 00000000000..aa2672057fb --- /dev/null +++ b/packages/server/src/xrpc/types/todo/adx/resolveName.ts @@ -0,0 +1,27 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams { + name: string; +} + +export type HandlerInput = undefined + +export interface HandlerOutput { + encoding: 'application/json'; + body: OutputSchema; +} + +export interface OutputSchema { + did: string; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/adx/syncGetRepo.ts b/packages/server/src/xrpc/types/todo/adx/syncGetRepo.ts new file mode 100644 index 00000000000..863dd0f7688 --- /dev/null +++ b/packages/server/src/xrpc/types/todo/adx/syncGetRepo.ts @@ -0,0 +1,24 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams { + did: string; + from?: string; +} + +export type HandlerInput = undefined + +export interface HandlerOutput { + encoding: 'application/cbor'; + body: Uint8Array; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/adx/syncGetRoot.ts b/packages/server/src/xrpc/types/todo/adx/syncGetRoot.ts new file mode 100644 index 00000000000..635331b013c --- /dev/null +++ b/packages/server/src/xrpc/types/todo/adx/syncGetRoot.ts @@ -0,0 +1,27 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams { + did: string; +} + +export type HandlerInput = undefined + +export interface HandlerOutput { + encoding: 'application/json'; + body: OutputSchema; +} + +export interface OutputSchema { + root: string; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/adx/syncUpdateRepo.ts b/packages/server/src/xrpc/types/todo/adx/syncUpdateRepo.ts new file mode 100644 index 00000000000..b5adfcf625d --- /dev/null +++ b/packages/server/src/xrpc/types/todo/adx/syncUpdateRepo.ts @@ -0,0 +1,22 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams { + did: string; +} + +export interface HandlerInput { + encoding: 'application/cbor'; + body: Uint8Array; +} + +export type HandlerOutput = void +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/social/badge.ts b/packages/server/src/xrpc/types/todo/social/badge.ts new file mode 100644 index 00000000000..29ef84969a4 --- /dev/null +++ b/packages/server/src/xrpc/types/todo/social/badge.ts @@ -0,0 +1,31 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +export interface Record { + assertion: + | InviteAssertion + | EmployeeAssertion + | TagAssertion + | UnknownAssertion; + subject: string; + createdAt: string; + [k: string]: unknown; +} +export interface InviteAssertion { + type: 'invite'; + [k: string]: unknown; +} +export interface EmployeeAssertion { + type: 'employee'; + [k: string]: unknown; +} +export interface TagAssertion { + type: 'tag'; + tag: string; + [k: string]: unknown; +} +export interface UnknownAssertion { + type: string; + [k: string]: unknown; +} diff --git a/packages/server/src/xrpc/types/todo/social/follow.ts b/packages/server/src/xrpc/types/todo/social/follow.ts new file mode 100644 index 00000000000..2603e19c186 --- /dev/null +++ b/packages/server/src/xrpc/types/todo/social/follow.ts @@ -0,0 +1,9 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +export interface Record { + subject: string; + createdAt: string; + [k: string]: unknown; +} diff --git a/packages/server/src/xrpc/types/todo/social/getFeed.ts b/packages/server/src/xrpc/types/todo/social/getFeed.ts new file mode 100644 index 00000000000..a47cab54736 --- /dev/null +++ b/packages/server/src/xrpc/types/todo/social/getFeed.ts @@ -0,0 +1,64 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams { + author?: string; + limit?: number; + before?: string; +} + +export type HandlerInput = undefined + +export interface HandlerOutput { + encoding: 'application/json'; + body: OutputSchema; +} + +export interface OutputSchema { + feed: FeedItem[]; +} +export interface FeedItem { + uri: string; + author: User; + repostedBy?: User; + record: {}; + embed?: RecordEmbed | ExternalEmbed | UnknownEmbed; + replyCount: number; + repostCount: number; + likeCount: number; + indexedAt: string; + myState?: { + repost?: string, + like?: string, + }; +} +export interface User { + did: string; + name: string; + displayName?: string; +} +export interface RecordEmbed { + type: 'record'; + author: User; + record: {}; +} +export interface ExternalEmbed { + type: 'external'; + uri: string; + title: string; + description: string; + imageUri: string; +} +export interface UnknownEmbed { + type: string; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/social/getLikedBy.ts b/packages/server/src/xrpc/types/todo/social/getLikedBy.ts new file mode 100644 index 00000000000..0112967c32b --- /dev/null +++ b/packages/server/src/xrpc/types/todo/social/getLikedBy.ts @@ -0,0 +1,36 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams { + uri: string; + limit?: number; + before?: string; +} + +export type HandlerInput = undefined + +export interface HandlerOutput { + encoding: 'application/json'; + body: OutputSchema; +} + +export interface OutputSchema { + uri: string; + likedBy: { + did: string, + name: string, + displayName?: string, + createdAt?: string, + indexedAt: string, + }[]; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/social/getNotifications.ts b/packages/server/src/xrpc/types/todo/social/getNotifications.ts new file mode 100644 index 00000000000..437b0e5eb2c --- /dev/null +++ b/packages/server/src/xrpc/types/todo/social/getNotifications.ts @@ -0,0 +1,39 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams { + limit?: number; + before?: string; +} + +export type HandlerInput = undefined + +export interface HandlerOutput { + encoding: 'application/json'; + body: OutputSchema; +} + +export interface OutputSchema { + notifications: Notification[]; +} +export interface Notification { + uri: string; + author: { + did: string, + name: string, + displayName: string, + }; + record: {}; + isRead: boolean; + indexedAt: string; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/social/getPostThread.ts b/packages/server/src/xrpc/types/todo/social/getPostThread.ts new file mode 100644 index 00000000000..e2d98ed8bc7 --- /dev/null +++ b/packages/server/src/xrpc/types/todo/social/getPostThread.ts @@ -0,0 +1,64 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams { + uri: string; + depth?: number; +} + +export type HandlerInput = undefined + +export interface HandlerOutput { + encoding: 'application/json'; + body: OutputSchema; +} + +export interface OutputSchema { + thread: Post; +} +export interface Post { + uri: string; + author: User; + record: {}; + embed?: RecordEmbed | ExternalEmbed | UnknownEmbed; + parent?: Post; + replyCount: number; + replies?: Post[]; + likeCount: number; + repostCount: number; + indexedAt: string; + myState?: { + repost?: string, + like?: string, + }; +} +export interface User { + did: string; + name: string; + displayName?: string; +} +export interface RecordEmbed { + type: 'record'; + author: User; + record: {}; +} +export interface ExternalEmbed { + type: 'external'; + uri: string; + title: string; + description: string; + imageUri: string; +} +export interface UnknownEmbed { + type: string; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/social/getProfile.ts b/packages/server/src/xrpc/types/todo/social/getProfile.ts new file mode 100644 index 00000000000..3e6b20393a7 --- /dev/null +++ b/packages/server/src/xrpc/types/todo/social/getProfile.ts @@ -0,0 +1,50 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams { + user: string; +} + +export type HandlerInput = undefined + +export interface HandlerOutput { + encoding: 'application/json'; + body: OutputSchema; +} + +export interface OutputSchema { + did: string; + name: string; + displayName?: string; + description?: string; + followersCount: number; + followsCount: number; + postsCount: number; + badges: Badge[]; + myState?: { + follow?: string, + }; +} +export interface Badge { + uri: string; + error?: string; + issuer?: { + did: string, + name: string, + displayName: string, + }; + assertion?: { + type: string, + }; + createdAt?: string; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/social/getRepostedBy.ts b/packages/server/src/xrpc/types/todo/social/getRepostedBy.ts new file mode 100644 index 00000000000..a9bfa5c3ae0 --- /dev/null +++ b/packages/server/src/xrpc/types/todo/social/getRepostedBy.ts @@ -0,0 +1,36 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams { + uri: string; + limit?: number; + before?: string; +} + +export type HandlerInput = undefined + +export interface HandlerOutput { + encoding: 'application/json'; + body: OutputSchema; +} + +export interface OutputSchema { + uri: string; + repostedBy: { + did: string, + name: string, + displayName?: string, + createdAt?: string, + indexedAt: string, + }[]; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/social/getUserFollowers.ts b/packages/server/src/xrpc/types/todo/social/getUserFollowers.ts new file mode 100644 index 00000000000..00b22de365b --- /dev/null +++ b/packages/server/src/xrpc/types/todo/social/getUserFollowers.ts @@ -0,0 +1,40 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams { + user: string; + limit?: number; + before?: string; +} + +export type HandlerInput = undefined + +export interface HandlerOutput { + encoding: 'application/json'; + body: OutputSchema; +} + +export interface OutputSchema { + subject: { + did: string, + name: string, + displayName?: string, + }; + followers: { + did: string, + name: string, + displayName?: string, + createdAt?: string, + indexedAt: string, + }[]; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/social/getUserFollows.ts b/packages/server/src/xrpc/types/todo/social/getUserFollows.ts new file mode 100644 index 00000000000..0e784672bb8 --- /dev/null +++ b/packages/server/src/xrpc/types/todo/social/getUserFollows.ts @@ -0,0 +1,40 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +import express from 'express' + +export interface QueryParams { + user: string; + limit?: number; + before?: string; +} + +export type HandlerInput = undefined + +export interface HandlerOutput { + encoding: 'application/json'; + body: OutputSchema; +} + +export interface OutputSchema { + subject: { + did: string, + name: string, + displayName?: string, + }; + follows: { + did: string, + name: string, + displayName?: string, + createdAt?: string, + indexedAt: string, + }[]; +} + +export type Handler = ( + params: QueryParams, + input: HandlerInput, + req: express.Request, + res: express.Response +) => Promise | HandlerOutput diff --git a/packages/server/src/xrpc/types/todo/social/like.ts b/packages/server/src/xrpc/types/todo/social/like.ts new file mode 100644 index 00000000000..2603e19c186 --- /dev/null +++ b/packages/server/src/xrpc/types/todo/social/like.ts @@ -0,0 +1,9 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +export interface Record { + subject: string; + createdAt: string; + [k: string]: unknown; +} diff --git a/packages/server/src/xrpc/types/todo/social/mediaEmbed.ts b/packages/server/src/xrpc/types/todo/social/mediaEmbed.ts new file mode 100644 index 00000000000..df9e01c8e4d --- /dev/null +++ b/packages/server/src/xrpc/types/todo/social/mediaEmbed.ts @@ -0,0 +1,19 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +export interface Record { + media: MediaEmbed[]; + [k: string]: unknown; +} +export interface MediaEmbed { + alt?: string; + thumb?: MediaEmbedBlob; + original: MediaEmbedBlob; + [k: string]: unknown; +} +export interface MediaEmbedBlob { + mimeType: string; + blobId: string; + [k: string]: unknown; +} diff --git a/packages/server/src/xrpc/types/todo/social/post.ts b/packages/server/src/xrpc/types/todo/social/post.ts new file mode 100644 index 00000000000..27060358056 --- /dev/null +++ b/packages/server/src/xrpc/types/todo/social/post.ts @@ -0,0 +1,27 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +/** + * @minItems 2 + * @maxItems 2 + */ +export type TextSlice = [number, number] +export type Entity = { + index: TextSlice, + type: string, + value: string, + [k: string]: unknown, +}[] + +export interface Record { + text: string; + entities?: Entity; + reply?: { + root: string, + parent?: string, + [k: string]: unknown, + }; + createdAt: string; + [k: string]: unknown; +} diff --git a/packages/server/src/xrpc/types/todo/social/profile.ts b/packages/server/src/xrpc/types/todo/social/profile.ts new file mode 100644 index 00000000000..39d755592c5 --- /dev/null +++ b/packages/server/src/xrpc/types/todo/social/profile.ts @@ -0,0 +1,14 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +export interface Record { + displayName: string; + description?: string; + badges?: BadgeRef[]; + [k: string]: unknown; +} +export interface BadgeRef { + uri: string; + [k: string]: unknown; +} diff --git a/packages/server/src/xrpc/types/todo/social/repost.ts b/packages/server/src/xrpc/types/todo/social/repost.ts new file mode 100644 index 00000000000..2603e19c186 --- /dev/null +++ b/packages/server/src/xrpc/types/todo/social/repost.ts @@ -0,0 +1,9 @@ +/** +* GENERATED CODE - DO NOT MODIFY +* Created Tue Sep 20 2022 +*/ +export interface Record { + subject: string; + createdAt: string; + [k: string]: unknown; +} diff --git a/packages/server/tests/crud.test.ts b/packages/server/tests/crud.test.ts index 55d9d0da77c..535e5c67338 100644 --- a/packages/server/tests/crud.test.ts +++ b/packages/server/tests/crud.test.ts @@ -1,5 +1,6 @@ -import { AdxClient, AdxUri } from '@adxp/api' -import { schemas } from '@adxp/microblog' +import AdxApi, { ServiceClient as AdxServiceClient } from '@adxp/api' +import * as Post from '@adxp/api/src/types/todo/social/post' +import { AdxUri } from '@adxp/common' import * as util from './_util' import getPort from 'get-port' @@ -9,152 +10,223 @@ const alice = { username: 'alice', did: 'did:example:alice' } const bob = { username: 'bob', did: 'did:example:bob' } describe('crud operations', () => { - let adx: AdxClient - let closeFn: util.CloseFn + let client: AdxServiceClient + let closeFn: util.CloseFn | null = null beforeAll(async () => { - const port = USE_TEST_SERVER ? await getPort() : 2583 - closeFn = await util.runTestServer(port) - - const url = `http://localhost:${port}` - adx = new AdxClient({ - pds: url, - schemas: schemas, - }) + let port: number + if (USE_TEST_SERVER) { + port = await getPort() + closeFn = await util.runTestServer(port) + } else { + port = 2583 + } + client = AdxApi.service(`http://localhost:${port}`) }) afterAll(async () => { - await closeFn() + if (closeFn) { + await closeFn() + } }) it('registers users', async () => { - await adx.mainPds.registerRepo(alice) - await adx.mainPds.registerRepo(bob) + await client.todo.adx.createAccount({}, alice) + await client.todo.adx.createAccount({}, bob) }) it('describes repo', async () => { - const description = await adx.mainPds.repo(alice.did).describe() - expect(description.name).toBe(alice.username) - expect(description.did).toBe(alice.did) - const description2 = await adx.mainPds.repo(bob.did).describe() - expect(description2.name).toBe(bob.username) - expect(description2.did).toBe(bob.did) + const description = await client.todo.adx.repoDescribe({ + nameOrDid: alice.did, + }) + expect(description.data.name).toBe(alice.username) + expect(description.data.did).toBe(alice.did) + const description2 = await client.todo.adx.repoDescribe({ + nameOrDid: bob.did, + }) + expect(description2.data.name).toBe(bob.username) + expect(description2.data.did).toBe(bob.did) }) let uri: AdxUri it('creates records', async () => { - uri = await adx.mainPds - .repo(alice.did) - .collection('bsky/posts') - .create('*', { - $type: 'blueskyweb.xyz:Post', + const res = await client.todo.adx.repoCreateRecord( + { did: alice.did, type: 'todo.social.post' }, + { + $type: 'todo.social.post', text: 'Hello, world!', createdAt: new Date().toISOString(), - }) - expect(uri.toString()).toBe( - `adx://${alice.did}/bsky/posts/${uri.recordKey}`, + }, + ) + uri = new AdxUri(res.data.uri) + expect(res.data.uri).toBe( + `adx://${alice.did}/todo.social.post/${uri.recordKey}`, ) }) it('lists records', async () => { - const res = await adx.mainPds - .repo(alice.did) - .collection('bsky/posts') - .list('*') - expect(res.records.length).toBe(1) - expect(res.records[0].uri).toBe(uri.toString()) - expect(res.records[0].value.text).toBe('Hello, world!') + const res1 = await client.todo.adx.repoListRecords({ + nameOrDid: alice.did, + type: 'todo.social.post', + }) + expect(res1.data.records.length).toBe(1) + expect(res1.data.records[0].uri).toBe(uri.toString()) + expect((res1.data.records[0].value as Post.Record).text).toBe( + 'Hello, world!', + ) + + const res2 = await client.todo.social.post.list({ + nameOrDid: alice.did, + }) + expect(res2.records.length).toBe(1) + expect(res2.records[0].uri).toBe(uri.toString()) + expect(res2.records[0].value.text).toBe('Hello, world!') }) it('gets records', async () => { - const res = await adx.mainPds - .repo(alice.did) - .collection('bsky/posts') - .get('*', uri.recordKey) - expect(res.uri).toBe(uri.toString()) - expect(res.value.text).toBe('Hello, world!') + const res1 = await client.todo.adx.repoGetRecord({ + nameOrDid: alice.did, + type: 'todo.social.post', + tid: uri.recordKey, + }) + expect(res1.data.uri).toBe(uri.toString()) + expect((res1.data.value as Post.Record).text).toBe('Hello, world!') + + const res2 = await client.todo.social.post.get({ + nameOrDid: alice.did, + tid: uri.recordKey, + }) + expect(res2.uri).toBe(uri.toString()) + expect(res2.value.text).toBe('Hello, world!') }) it('puts records', async () => { - const res = await adx.mainPds - .repo(alice.did) - .collection('bsky/posts') - .put('*', uri.recordKey, { - $type: 'blueskyweb.xyz:Post', + const res1 = await client.todo.adx.repoPutRecord( + { did: alice.did, type: 'todo.social.post', tid: uri.recordKey }, + { + $type: 'todo.social.post', text: 'Hello, universe!', createdAt: new Date().toISOString(), - }) - expect(res.toString()).toBe(uri.toString()) + }, + ) + expect(res1.data.uri).toBe(uri.toString()) - const res2 = await adx.mainPds - .repo(alice.did) - .collection('bsky/posts') - .get('*', uri.recordKey) - expect(res2.uri).toBe(uri.toString()) - expect(res2.value.text).toBe('Hello, universe!') + const res2 = await client.todo.adx.repoGetRecord({ + nameOrDid: alice.did, + type: 'todo.social.post', + tid: uri.recordKey, + }) + expect(res2.data.uri).toBe(uri.toString()) + expect((res2.data.value as Post.Record).text).toBe('Hello, universe!') + + const res3 = await client.todo.social.post.put( + { did: alice.did, tid: uri.recordKey }, + { + $type: 'todo.social.post', + text: 'Hello, universe!!!', + createdAt: new Date().toISOString(), + }, + ) + expect(res3.uri).toBe(uri.toString()) + + const res4 = await client.todo.social.post.get({ + nameOrDid: alice.did, + tid: uri.recordKey, + }) + expect(res4.uri).toBe(uri.toString()) + expect(res4.value.text).toBe('Hello, universe!!!') }) it('deletes records', async () => { - await adx.mainPds - .repo(alice.did) - .collection('bsky/posts') - .del(uri.recordKey) - const res = await adx.mainPds - .repo(alice.did) - .collection('bsky/posts') - .list('*') - expect(res.records.length).toBe(0) + await client.todo.adx.repoDeleteRecord({ + did: alice.did, + type: 'todo.social.post', + tid: uri.recordKey, + }) + const res1 = await client.todo.adx.repoListRecords({ + nameOrDid: alice.did, + type: 'todo.social.post', + }) + expect(res1.data.records.length).toBe(0) }) - it('lists records with pagination', async () => { - const feed = adx.mainPds.repo(alice.did).collection('bsky/posts') - const uri1 = await feed.create('Post', { - $type: 'blueskyweb.xyz:Post', - text: 'Post 1', - createdAt: new Date().toISOString(), - }) - const uri2 = await feed.create('Post', { - $type: 'blueskyweb.xyz:Post', - text: 'Post 2', - createdAt: new Date().toISOString(), + it('CRUDs records with the semantic sugars', async () => { + const res1 = await client.todo.social.post.create( + { did: alice.did }, + { + $type: 'todo.social.post', + text: 'Hello, world!', + createdAt: new Date().toISOString(), + }, + ) + const uri = new AdxUri(res1.uri) + + const res2 = await client.todo.social.post.list({ + nameOrDid: alice.did, }) - const uri3 = await feed.create('Post', { - $type: 'blueskyweb.xyz:Post', - text: 'Post 3', - createdAt: new Date().toISOString(), + expect(res2.records.length).toBe(1) + + await client.todo.social.post.delete({ + did: alice.did, + tid: uri.recordKey, }) - const uri4 = await feed.create('Post', { - $type: 'blueskyweb.xyz:Post', - text: 'Post 4', - createdAt: new Date().toISOString(), + + const res3 = await client.todo.social.post.list({ + nameOrDid: alice.did, }) + expect(res3.records.length).toBe(0) + }) + + it('lists records with pagination', async () => { + const doCreate = async (text: string) => { + const res = await client.todo.social.post.create( + { did: alice.did }, + { + $type: 'todo.social.post', + text, + createdAt: new Date().toISOString(), + }, + ) + return new AdxUri(res.uri) + } + const doList = async (params: any) => { + const res = await client.todo.social.post.list({ + nameOrDid: alice.did, + ...params, + }) + return res + } + const uri1 = await doCreate('Post 1') + const uri2 = await doCreate('Post 2') + const uri3 = await doCreate('Post 3') + const uri4 = await doCreate('Post 4') { - const list = await feed.list('Post', { limit: 2 }) + const list = await doList({ limit: 2 }) expect(list.records.length).toBe(2) expect(list.records[0].value.text).toBe('Post 1') expect(list.records[1].value.text).toBe('Post 2') } { - const list = await feed.list('Post', { limit: 2, reverse: true }) + const list = await doList({ limit: 2, reverse: true }) expect(list.records.length).toBe(2) expect(list.records[0].value.text).toBe('Post 4') expect(list.records[1].value.text).toBe('Post 3') } { - const list = await feed.list('Post', { after: uri2.recordKey }) + const list = await doList({ after: uri2.recordKey }) expect(list.records.length).toBe(2) expect(list.records[0].value.text).toBe('Post 3') expect(list.records[1].value.text).toBe('Post 4') } { - const list = await feed.list('Post', { before: uri3.recordKey }) + const list = await doList({ before: uri3.recordKey }) expect(list.records.length).toBe(2) expect(list.records[0].value.text).toBe('Post 1') expect(list.records[1].value.text).toBe('Post 2') } { - const list = await feed.list('Post', { + const list = await doList({ before: uri4.recordKey, after: uri1.recordKey, }) @@ -163,112 +235,185 @@ describe('crud operations', () => { expect(list.records[1].value.text).toBe('Post 3') } - await feed.del(uri1.recordKey) - await feed.del(uri2.recordKey) - await feed.del(uri3.recordKey) - await feed.del(uri4.recordKey) + await client.todo.social.post.delete({ + did: alice.did, + tid: uri1.recordKey, + }) + await client.todo.social.post.delete({ + did: alice.did, + tid: uri2.recordKey, + }) + await client.todo.social.post.delete({ + did: alice.did, + tid: uri3.recordKey, + }) + await client.todo.social.post.delete({ + did: alice.did, + tid: uri4.recordKey, + }) }) // Validation // -------------- it('requires a $type on records', async () => { - const feed = adx.mainPds.repo(alice.did).collection('bsky/posts') - await expect(feed.create('Post', {})).rejects.toThrow( + const prom1 = client.todo.adx.repoCreateRecord( + { did: alice.did, type: 'todo.social.post' }, + {}, + ) + await expect(prom1).rejects.toThrow( 'The passed value does not declare a $type', ) - await expect(feed.put('Post', 'foo', {})).rejects.toThrow( + const prom2 = client.todo.adx.repoPutRecord( + { did: alice.did, type: 'todo.social.post', tid: 'foo' }, + {}, + ) + await expect(prom2).rejects.toThrow( 'The passed value does not declare a $type', ) }) - it('requires the schema to be known', async () => { - const feed = adx.mainPds.repo(alice.did).collection('bsky/posts') - await expect(feed.list('Foobar')).rejects.toThrow( - 'Schema not found: Foobar', + it('requires the schema to be known if validating', async () => { + // const prom1 = client.todo.adx.repoListRecords(url, { + // nameOrDid: alice.did, + // type: 'com.example.foobar', + // }) + // await expect(prom1).rejects.toThrow('Schema not found: com.example.foobar') + const prom2 = client.todo.adx.repoCreateRecord( + { did: alice.did, type: 'com.example.foobar' }, + { $type: 'com.example.foobar' }, ) - await expect(feed.create('Foobar', {})).rejects.toThrow( - 'Schema not found: Foobar', - ) - await expect(feed.put('Foobar', 'foo', {})).rejects.toThrow( - 'Schema not found: Foobar', + await expect(prom2).rejects.toThrow('Schema not found') + const prom3 = client.todo.adx.repoPutRecord( + { did: alice.did, type: 'com.example.foobar', tid: 'foo' }, + { $type: 'com.example.foobar' }, ) + await expect(prom3).rejects.toThrow('Schema not found') }) it('requires the $type to match the schema', async () => { - const feed = adx.mainPds.repo(alice.did).collection('bsky/posts') await expect( - feed.create('Post', { $type: 'blueskyweb.xyz:Like' }), - ).rejects.toThrow('Record type blueskyweb.xyz:Like is not supported') + client.todo.adx.repoCreateRecord( + { did: alice.did, type: 'todo.social.post' }, + { $type: 'todo.social.like' }, + ), + ).rejects.toThrow('Record type todo.social.like is not supported') await expect( - feed.put('Post', 'foo', { $type: 'blueskyweb.xyz:Like' }), - ).rejects.toThrow('Record type blueskyweb.xyz:Like is not supported') + client.todo.adx.repoPutRecord( + { did: alice.did, type: 'todo.social.post', tid: 'foo' }, + { $type: 'todo.social.like' }, + ), + ).rejects.toThrow('Record type todo.social.like is not supported') }) it('validates the record on write', async () => { - const feed = adx.mainPds.repo(alice.did).collection('bsky/posts') await expect( - feed.create('Post', { $type: 'blueskyweb.xyz:Post' }), + client.todo.adx.repoCreateRecord( + { did: alice.did, type: 'todo.social.post' }, + { $type: 'todo.social.post' }, + ), ).rejects.toThrow( - "Failed blueskyweb.xyz:Post validation for #/required: must have required property 'text'", + "Failed todo.social.post validation for #/required: must have required property 'text'", ) await expect( - feed.put('Post', 'foo', { $type: 'blueskyweb.xyz:Post' }), + client.todo.adx.repoPutRecord( + { did: alice.did, type: 'todo.social.post', tid: 'foo' }, + { $type: 'todo.social.post' }, + ), ).rejects.toThrow( - "Failed blueskyweb.xyz:Post validation for #/required: must have required property 'text'", + "Failed todo.social.post validation for #/required: must have required property 'text'", ) }) - it('validates the record on read', async () => { - const feed = adx.mainPds.repo(alice.did).collection('bsky/posts') - const uri1 = await feed.create( - '*', - { - $type: 'blueskyweb.xyz:Post', - record: 'is bad', - }, - false, - ) - const uri2 = await feed.create( - '*', - { - $type: 'blueskyweb.xyz:Unknown', - dunno: 'lol', - }, - false, - ) - const res1 = await feed.list('Post') - expect(res1.records[0].value.record).toBe('is bad') - expect(res1.records[0].valid).toBeFalsy() - expect(res1.records[0].fullySupported).toBeFalsy() - expect(res1.records[0].compatible).toBeTruthy() - expect(res1.records[0].error).toBe( - `Failed blueskyweb.xyz:Post validation for #/required: must have required property 'text'`, - ) - expect(res1.records[1].value.dunno).toBe('lol') - expect(res1.records[1].valid).toBeFalsy() - expect(res1.records[1].fullySupported).toBeFalsy() - expect(res1.records[1].compatible).toBeFalsy() - expect(res1.records[1].error).toBe( - `Record type blueskyweb.xyz:Unknown is not supported`, - ) - const res2 = await feed.get('Post', uri1.recordKey) - expect(res2.value.record).toBe('is bad') - expect(res2.valid).toBeFalsy() - expect(res2.fullySupported).toBeFalsy() - expect(res2.compatible).toBeTruthy() - expect(res2.error).toBe( - `Failed blueskyweb.xyz:Post validation for #/required: must have required property 'text'`, - ) - const res3 = await feed.get('Post', uri2.recordKey) - expect(res3.value.dunno).toBe('lol') - expect(res3.valid).toBeFalsy() - expect(res3.fullySupported).toBeFalsy() - expect(res3.compatible).toBeFalsy() - expect(res3.error).toBe( - `Record type blueskyweb.xyz:Unknown is not supported`, - ) - await feed.del(uri1.recordKey) - await feed.del(uri2.recordKey) - }) + // TODO: does it? + // it('validates the record on read', async () => { + // const res1 = await client.todo.adx.repoCreateRecord( + // url, + // { did: alice.did, type: 'todo.social.post', validate: false }, + // { + // encoding: 'application/json', + // data: { $type: 'todo.social.post', record: 'is bad' }, + // }, + // ) + // const res2 = await client.todo.adx.repoCreateRecord( + // url, + // { did: alice.did, type: 'todo.social.post', validate: false }, + // { + // encoding: 'application/json', + // data: { $type: 'com.example.unknown', dunno: 'lol' }, + // }, + // ) + // const res3 = await client.todo.adx.repoListRecords(url, { + // nameOrDid: alice.did, + // type: 'todo.social.post', + // }) + // /** @ts-ignore TODO!!! */ + // expect(res3.data.records[0].value.record).toBe('is bad') + // /** @ts-ignore TODO!!! */ + // expect(res3.data.records[0].valid).toBeFalsy() + // /** @ts-ignore TODO!!! */ + // expect(res3.data.records[0].fullySupported).toBeFalsy() + // /** @ts-ignore TODO!!! */ + // expect(res3.data.records[0].compatible).toBeTruthy() + // /** @ts-ignore TODO!!! */ + // expect(res3.data.records[0].error).toBe( + // `Failed todo.social.post validation for #/required: must have required property 'text'`, + // ) + // /** @ts-ignore TODO!!! */ + // expect(res3.data.records[1].value.dunno).toBe('lol') + // /** @ts-ignore TODO!!! */ + // expect(res3.data.records[1].valid).toBeFalsy() + // /** @ts-ignore TODO!!! */ + // expect(res3.data.records[1].fullySupported).toBeFalsy() + // /** @ts-ignore TODO!!! */ + // expect(res3.data.records[1].compatible).toBeFalsy() + // /** @ts-ignore TODO!!! */ + // expect(res3.data.records[1].error).toBe( + // `Record type com.example.unknown is not supported`, + // ) + // const res4 = await client.todo.adx.repoGetRecord(url, { + // nameOrDid: alice.did, + // type: 'todo.social.post', + // tid: new AdxUri(res1.data.uri).recordKey, + // }) + // /** @ts-ignore TODO!!! */ + // expect(res4.data.value.record).toBe('is bad') + // /** @ts-ignore TODO!!! */ + // expect(res4.data.valid).toBeFalsy() + // /** @ts-ignore TODO!!! */ + // expect(res4.data.fullySupported).toBeFalsy() + // /** @ts-ignore TODO!!! */ + // expect(res4.data.compatible).toBeTruthy() + // /** @ts-ignore TODO!!! */ + // expect(res4.data.error).toBe( + // `Failed todo.social.post validation for #/required: must have required property 'text'`, + // ) + // const res5 = await client.todo.adx.repoGetRecord(url, { + // nameOrDid: alice.did, + // type: 'todo.social.post', + // tid: new AdxUri(res2.data.uri).recordKey, + // }) + // /** @ts-ignore TODO!!! */ + // expect(res5.data.value.dunno).toBe('lol') + // /** @ts-ignore TODO!!! */ + // expect(res5.data.valid).toBeFalsy() + // /** @ts-ignore TODO!!! */ + // expect(res5.data.fullySupported).toBeFalsy() + // /** @ts-ignore TODO!!! */ + // expect(res5.data.compatible).toBeFalsy() + // /** @ts-ignore TODO!!! */ + // expect(res5.data.error).toBe( + // `Record type com.example.unknown is not supported`, + // ) + // await client.todo.adx.repoDeleteRecord(url, { + // did: alice.did, + // type: 'todo.social.post', + // tid: new AdxUri(res1.data.uri).recordKey, + // }) + // await client.todo.adx.repoDeleteRecord(url, { + // did: alice.did, + // type: 'todo.social.post', + // tid: new AdxUri(res2.data.uri).recordKey, + // }) + // }) }) diff --git a/packages/server/tests/views.test.ts b/packages/server/tests/views.test.ts index e64d1645d01..0908f7b0b7e 100644 --- a/packages/server/tests/views.test.ts +++ b/packages/server/tests/views.test.ts @@ -1,5 +1,5 @@ +import AdxApi, { ServiceClient as AdxServiceClient } from '@adxp/api' import { AdxUri } from '@adxp/common' -import { MicroblogClient, Post } from '@adxp/microblog' import { users, posts, replies } from './test-data' import getPort from 'get-port' import * as util from './_util' @@ -15,61 +15,101 @@ let bobLikes: Record = {} let badges: AdxUri[] = [] describe('pds views', () => { - let alice, bob, carol, dan: MicroblogClient - let closeFn: util.CloseFn + let client: AdxServiceClient + let closeFn: util.CloseFn | null = null beforeAll(async () => { - const port = USE_TEST_SERVER ? await getPort() : 2583 - closeFn = await util.runTestServer(port) - - const url = `http://localhost:${port}` - alice = new MicroblogClient(url, users.alice.did) - bob = new MicroblogClient(url, users.bob.did) - carol = new MicroblogClient(url, users.carol.did) - dan = new MicroblogClient(url, users.dan.did) + let port: number + if (USE_TEST_SERVER) { + port = await getPort() + closeFn = await util.runTestServer(port) + } else { + port = 2583 + } + client = AdxApi.service(`http://localhost:${port}`) }) afterAll(async () => { - await closeFn() + if (closeFn) { + await closeFn() + } }) it('register users', async () => { - await alice.register(users.alice.name) - await bob.register(users.bob.name) - await carol.register(users.carol.name) - await dan.register(users.dan.name) + await client.todo.adx.createAccount( + {}, + { username: users.alice.name, did: users.alice.did }, + ) + await client.todo.adx.createAccount( + {}, + { username: users.bob.name, did: users.bob.did }, + ) + await client.todo.adx.createAccount( + {}, + { username: users.carol.name, did: users.carol.did }, + ) + await client.todo.adx.createAccount( + {}, + { username: users.dan.name, did: users.dan.did }, + ) }) it('creates profiles', async () => { - await alice.createProfile(users.alice.displayName, users.alice.description) - await bob.createProfile(users.bob.displayName, users.bob.description) + await client.todo.social.profile.create( + { did: users.alice.did }, + { + displayName: users.alice.displayName, + description: users.alice.description, + }, + ) + await client.todo.social.profile.create( + { did: users.bob.did }, + { + displayName: users.bob.displayName, + description: users.bob.description, + }, + ) }) it('follow people', async () => { - await alice.followUser(bob.did) - await alice.followUser(carol.did) - await alice.followUser(dan.did) - bobFollows[alice.did] = await bob.followUser(alice.did) - bobFollows[carol.did] = await bob.followUser(carol.did) - await carol.followUser(alice.did) - await dan.followUser(bob.did) + const follow = async (from: string, to: string) => { + const res = await client.todo.social.follow.create( + { did: from }, + { subject: to, createdAt: new Date().toISOString() }, + ) + return new AdxUri(res.uri) + } + await follow(users.alice.did, users.bob.did) + await follow(users.alice.did, users.carol.did) + await follow(users.alice.did, users.dan.did) + bobFollows[users.alice.did] = await follow(users.bob.did, users.alice.did) + bobFollows[users.carol.did] = await follow(users.bob.did, users.carol.did) + await follow(users.carol.did, users.alice.did) + await follow(users.dan.did, users.bob.did) }) it('makes some posts', async () => { - const alice0 = await alice.createPost(posts.alice[0]) - const bob0 = await bob.createPost(posts.bob[0]) - const carol0 = await carol.createPost(posts.carol[0]) - const dan0 = await dan.createPost(posts.dan[0]) - const dan1 = await dan.createPost(posts.dan[1], [ + const post = async (by: string, text: string, entities?: any) => { + const res = await client.todo.social.post.create( + { did: by }, + { text: text, entities, createdAt: new Date().toISOString() }, + ) + return new AdxUri(res.uri) + } + const alice0 = await post(users.alice.did, posts.alice[0]) + const bob0 = await post(users.bob.did, posts.bob[0]) + const carol0 = await post(users.carol.did, posts.carol[0]) + const dan0 = await post(users.dan.did, posts.dan[0]) + const dan1 = await post(users.dan.did, posts.dan[1], [ { index: [0, 18], type: 'mention', value: users.carol.did, - } as any, + }, ]) - const alice1 = await alice.createPost(posts.alice[1]) - const bob1 = await bob.createPost(posts.bob[1]) - const alice2 = await alice.createPost(posts.alice[2]) + const alice1 = await post(users.alice.did, posts.alice[1]) + const bob1 = await post(users.bob.did, posts.bob[1]) + const alice2 = await post(users.alice.did, posts.alice[2]) alicePosts = [alice0, alice1, alice2] bobPosts = [bob0, bob1] carolPosts = [carol0] @@ -77,52 +117,96 @@ describe('pds views', () => { }) it('likes a post', async () => { - bobLikes[alicePosts[2].toString()] = await bob.likePost(alicePosts[1]) - bobLikes[alicePosts[2].toString()] = await bob.likePost(alicePosts[2]) - await carol.likePost(alicePosts[1]) - await carol.likePost(alicePosts[2]) - await dan.likePost(alicePosts[1]) + const like = async (by: string, subject: string) => { + const res = await client.todo.social.like.create( + { did: by }, + { subject, createdAt: new Date().toISOString() }, + ) + return new AdxUri(res.uri) + } + bobLikes[alicePosts[2].toString()] = await like( + users.bob.did, + alicePosts[1].toString(), + ) + bobLikes[alicePosts[2].toString()] = await like( + users.bob.did, + alicePosts[2].toString(), + ) + await like(users.carol.did, alicePosts[1].toString()) + await like(users.carol.did, alicePosts[2].toString()) + await like(users.dan.did, alicePosts[1].toString()) }) it('replies to a post', async () => { - const bobReply = await bob.reply( + const reply = async ( + by: string, + root: AdxUri, + parent: AdxUri, + text: string, + ) => { + const res = await client.todo.social.post.create( + { did: by }, + { + text: text, + reply: { + root: root.toString(), + parent: parent.toString(), + }, + createdAt: new Date().toISOString(), + }, + ) + return new AdxUri(res.uri) + } + const bobReply = await reply( + users.bob.did, alicePosts[1], alicePosts[1], replies.bob[0], ) - await carol.reply(alicePosts[1], alicePosts[1], replies.carol[0]) - await alice.reply(alicePosts[1], bobReply, replies.alice[0]) + await reply(users.carol.did, alicePosts[1], alicePosts[1], replies.carol[0]) + await reply(users.alice.did, alicePosts[1], bobReply, replies.alice[0]) }) it('reposts a post', async () => { - await carol.repost(danPosts[1]) + const repost = async (by: string, subject: string) => { + const res = await client.todo.social.repost.create( + { did: by }, + { subject, createdAt: new Date().toISOString() }, + ) + return new AdxUri(res.uri) + } + await repost(users.carol.did, danPosts[1].toString()) }) - it('gives badges', async () => { - const badge0 = await bob.giveBadge(users.alice.did, 'tag', 'tech') - const badge1 = await bob.giveBadge(users.alice.did, 'invite') - badges = [badge0, badge1] - }) + // TODO + // it('gives badges', async () => { + // const badge0 = await bob.giveBadge(users.alice.did, 'tag', 'tech') + // const badge1 = await bob.giveBadge(users.alice.did, 'invite') + // badges = [badge0, badge1] + // }) - it('accepts badges', async () => { - await alice.acceptBadge(badges[0]) - }) + // TODO + // it('accepts badges', async () => { + // await alice.acceptBadge(badges[0]) + // }) it('fetches liked by view', async () => { - const view = await alice.likedByView(alicePosts[1]) - expect(view.uri).toEqual(alicePosts[1].toString()) - expect(view.likedBy.length).toBe(3) - const bobLike = view.likedBy.find((l) => l.name === users.bob.name) + const view = await client.todo.social.getLikedBy({ + uri: alicePosts[1].toString(), + }) + expect(view.data.uri).toEqual(alicePosts[1].toString()) + expect(view.data.likedBy.length).toBe(3) + const bobLike = view.data.likedBy.find((l) => l.name === users.bob.name) expect(bobLike?.did).toEqual(users.bob.did) expect(bobLike?.displayName).toEqual(users.bob.displayName) expect(bobLike?.createdAt).toBeDefined() expect(bobLike?.indexedAt).toBeDefined() - const carolLike = view.likedBy.find((l) => l.name === users.carol.name) + const carolLike = view.data.likedBy.find((l) => l.name === users.carol.name) expect(carolLike?.did).toEqual(users.carol.did) expect(carolLike?.displayName).toEqual(users.carol.displayName) expect(carolLike?.createdAt).toBeDefined() expect(carolLike?.indexedAt).toBeDefined() - const danLike = view.likedBy.find((l) => l.name === users.dan.name) + const danLike = view.data.likedBy.find((l) => l.name === users.dan.name) expect(danLike?.did).toEqual(users.dan.did) expect(danLike?.displayName).toEqual(users.dan.displayName) expect(danLike?.createdAt).toBeDefined() @@ -130,10 +214,12 @@ describe('pds views', () => { }) it('fetches reposted by view', async () => { - const view = await alice.repostedByView(danPosts[1]) - expect(view.uri).toEqual(danPosts[1].toString()) - expect(view.repostedBy.length).toBe(1) - const repost = view.repostedBy[0] + const view = await client.todo.social.getRepostedBy({ + uri: danPosts[1].toString(), + }) + expect(view.data.uri).toEqual(danPosts[1].toString()) + expect(view.data.repostedBy.length).toBe(1) + const repost = view.data.repostedBy[0] expect(repost.did).toEqual(users.carol.did) expect(repost.displayName).toEqual(users.carol.displayName) expect(repost.createdAt).toBeDefined() @@ -141,17 +227,21 @@ describe('pds views', () => { }) it('fetches followers', async () => { - const view = await bob.userFollowersView('alice') - expect(view.subject.did).toEqual(users.alice.did) - expect(view.subject.name).toEqual(users.alice.name) - expect(view.subject.displayName).toEqual(users.alice.displayName) - const bobFollow = view.followers.find((f) => f.name === users.bob.name) + const view = await client.todo.social.getUserFollowers({ + user: 'alice', + }) + expect(view.data.subject.did).toEqual(users.alice.did) + expect(view.data.subject.name).toEqual(users.alice.name) + expect(view.data.subject.displayName).toEqual(users.alice.displayName) + const bobFollow = view.data.followers.find((f) => f.name === users.bob.name) expect(bobFollow?.did).toEqual(users.bob.did) expect(bobFollow?.name).toEqual(users.bob.name) expect(bobFollow?.displayName).toEqual(users.bob.displayName) expect(bobFollow?.createdAt).toBeDefined() expect(bobFollow?.indexedAt).toBeDefined() - const carolFollow = view.followers.find((f) => f.name === users.carol.name) + const carolFollow = view.data.followers.find( + (f) => f.name === users.carol.name, + ) expect(carolFollow?.did).toEqual(users.carol.did) expect(carolFollow?.name).toEqual(users.carol.name) expect(carolFollow?.displayName).toEqual(users.carol.displayName) @@ -160,17 +250,21 @@ describe('pds views', () => { }) it('fetches follows', async () => { - const view = await bob.userFollowsView('bob') - expect(view.subject.did).toEqual(users.bob.did) - expect(view.subject.name).toEqual(users.bob.name) - expect(view.subject.displayName).toEqual(users.bob.displayName) - const alice = view.follows.find((f) => f.name === users.alice.name) + const view = await client.todo.social.getUserFollows({ + user: 'bob', + }) + expect(view.data.subject.did).toEqual(users.bob.did) + expect(view.data.subject.name).toEqual(users.bob.name) + expect(view.data.subject.displayName).toEqual(users.bob.displayName) + const alice = view.data.follows.find((f) => f.name === users.alice.name) expect(alice?.did).toEqual(users.alice.did) expect(alice?.name).toEqual(users.alice.name) expect(alice?.displayName).toEqual(users.alice.displayName) expect(alice?.createdAt).toBeDefined() expect(alice?.indexedAt).toBeDefined() - const carolFollow = view.follows.find((f) => f.name === users.carol.name) + const carolFollow = view.data.follows.find( + (f) => f.name === users.carol.name, + ) expect(carolFollow?.did).toEqual(users.carol.did) expect(carolFollow?.name).toEqual(users.carol.name) expect(carolFollow?.displayName).toEqual(users.carol.displayName) @@ -179,88 +273,178 @@ describe('pds views', () => { }) it('fetches profile', async () => { - const aliceProf = await bob.profileView('alice') - expect(aliceProf.did).toEqual(users.alice.did) - expect(aliceProf.name).toEqual(users.alice.name) - expect(aliceProf.displayName).toEqual(users.alice.displayName) - expect(aliceProf.description).toEqual(users.alice.description) - expect(aliceProf.followersCount).toEqual(2) - expect(aliceProf.followsCount).toEqual(3) - expect(aliceProf.postsCount).toEqual(4) - expect(aliceProf.badges.length).toEqual(1) - expect(aliceProf.badges[0].uri).toEqual(badges[0].toString()) - expect(aliceProf.badges[0].assertion?.type).toEqual('tag') - expect(aliceProf.badges[0].issuer?.did).toEqual(users.bob.did) - expect(aliceProf.badges[0].issuer?.name).toEqual(users.bob.name) - expect(aliceProf.badges[0].issuer?.displayName).toEqual( - users.bob.displayName, + const aliceProf = await client.todo.social.getProfile( + { + user: 'alice', + }, + undefined, + { + headers: { + Authorization: users.bob.did, + }, + }, + ) + expect(aliceProf.data.did).toEqual(users.alice.did) + expect(aliceProf.data.name).toEqual(users.alice.name) + expect(aliceProf.data.displayName).toEqual(users.alice.displayName) + expect(aliceProf.data.description).toEqual(users.alice.description) + expect(aliceProf.data.followersCount).toEqual(2) + expect(aliceProf.data.followsCount).toEqual(3) + expect(aliceProf.data.postsCount).toEqual(4) + // TODO + // expect(aliceProf.data.badges.length).toEqual(1) + // expect(aliceProf.data.badges[0].uri).toEqual(badges[0].toString()) + // expect(aliceProf.data.badges[0].assertion?.type).toEqual('tag') + // expect(aliceProf.data.badges[0].issuer?.did).toEqual(users.bob.did) + // expect(aliceProf.data.badges[0].issuer?.name).toEqual(users.bob.name) + // expect(aliceProf.data.badges[0].issuer?.displayName).toEqual( + // users.bob.displayName, + // ) + expect(aliceProf.data.myState?.follow).toEqual( + bobFollows[users.alice.did]?.toString(), ) - expect(aliceProf.myState?.follow).toEqual(bobFollows[alice.did]?.toString()) - const danProf = await bob.profileView('dan') - expect(danProf.did).toEqual(users.dan.did) - expect(danProf.name).toEqual(users.dan.name) - expect(danProf.displayName).toEqual(users.dan.displayName) - expect(danProf.description).toEqual(users.dan.description) - expect(danProf.followersCount).toEqual(1) - expect(danProf.followsCount).toEqual(1) - expect(danProf.postsCount).toEqual(2) - expect(danProf.badges).toEqual([]) - expect(danProf.myState?.follow).toEqual(bobFollows[dan.did]?.toString()) + const danProf = await client.todo.social.getProfile( + { + user: 'dan', + }, + undefined, + { + headers: { + Authorization: users.bob.did, + }, + }, + ) + expect(danProf.data.did).toEqual(users.dan.did) + expect(danProf.data.name).toEqual(users.dan.name) + expect(danProf.data.displayName).toEqual(users.dan.displayName) + expect(danProf.data.description).toEqual(users.dan.description) + expect(danProf.data.followersCount).toEqual(1) + expect(danProf.data.followsCount).toEqual(1) + expect(danProf.data.postsCount).toEqual(2) + expect(danProf.data.badges).toEqual([]) + expect(danProf.data.myState?.follow).toEqual( + bobFollows[users.dan.did]?.toString(), + ) }) it('fetches timeline', async () => { - const aliceFeed: any = await alice.feedView() - expect(aliceFeed.length).toBe(7) - expect(aliceFeed[0].record.text).toEqual(replies.carol[0]) - expect(aliceFeed[1].record.text).toEqual(replies.bob[0]) - expect(aliceFeed[2].record.text).toEqual(posts.bob[1]) - expect(aliceFeed[3].record.text).toEqual(posts.dan[1]) - expect(aliceFeed[4].record.text).toEqual(posts.dan[0]) - expect(aliceFeed[5].record.text).toEqual(posts.carol[0]) - expect(aliceFeed[6].record.text).toEqual(posts.bob[0]) - expect(aliceFeed[3].repostCount).toEqual(1) + const aliceFeed = await client.todo.social.getFeed({}, undefined, { + headers: { + Authorization: users.alice.did, + }, + }) + expect(aliceFeed.data.feed.length).toBe(7) + /** @ts-ignore TODO */ + expect(aliceFeed.data.feed[0].record.text).toEqual(replies.carol[0]) + /** @ts-ignore TODO */ + expect(aliceFeed.data.feed[1].record.text).toEqual(replies.bob[0]) + /** @ts-ignore TODO */ + expect(aliceFeed.data.feed[2].record.text).toEqual(posts.bob[1]) + /** @ts-ignore TODO */ + expect(aliceFeed.data.feed[3].record.text).toEqual(posts.dan[1]) + /** @ts-ignore TODO */ + expect(aliceFeed.data.feed[4].record.text).toEqual(posts.dan[0]) + /** @ts-ignore TODO */ + expect(aliceFeed.data.feed[5].record.text).toEqual(posts.carol[0]) + /** @ts-ignore TODO */ + expect(aliceFeed.data.feed[6].record.text).toEqual(posts.bob[0]) + expect(aliceFeed.data.feed[3].repostCount).toEqual(1) - const bobFeed: any = await bob.feedView() - expect(bobFeed.length).toBe(7) - expect(bobFeed[0].record.text).toEqual(replies.alice[0]) - expect(bobFeed[1].record.text).toEqual(replies.carol[0]) - expect(bobFeed[2].record.text).toEqual(posts.alice[2]) - expect(bobFeed[3].record.text).toEqual(posts.alice[1]) - expect(bobFeed[4].record.text).toEqual(posts.dan[1]) - expect(bobFeed[5].record.text).toEqual(posts.carol[0]) - expect(bobFeed[6].record.text).toEqual(posts.alice[0]) - expect(bobFeed[3].replyCount).toEqual(2) - expect(bobFeed[3].likeCount).toEqual(3) - expect(bobFeed[2].likeCount).toEqual(2) - expect(bobFeed[3]?.myState?.like).toEqual( + const bobFeed = await client.todo.social.getFeed({}, undefined, { + headers: { + Authorization: users.bob.did, + }, + }) + expect(bobFeed.data.feed.length).toBe(7) + /** @ts-ignore TODO */ + expect(bobFeed.data.feed[0].record.text).toEqual(replies.alice[0]) + /** @ts-ignore TODO */ + expect(bobFeed.data.feed[1].record.text).toEqual(replies.carol[0]) + /** @ts-ignore TODO */ + expect(bobFeed.data.feed[2].record.text).toEqual(posts.alice[2]) + /** @ts-ignore TODO */ + expect(bobFeed.data.feed[3].record.text).toEqual(posts.alice[1]) + /** @ts-ignore TODO */ + expect(bobFeed.data.feed[4].record.text).toEqual(posts.dan[1]) + /** @ts-ignore TODO */ + expect(bobFeed.data.feed[5].record.text).toEqual(posts.carol[0]) + /** @ts-ignore TODO */ + expect(bobFeed.data.feed[6].record.text).toEqual(posts.alice[0]) + expect(bobFeed.data.feed[3].replyCount).toEqual(2) + expect(bobFeed.data.feed[3].likeCount).toEqual(3) + expect(bobFeed.data.feed[2].likeCount).toEqual(2) + expect(bobFeed.data.feed[3]?.myState?.like).toEqual( bobLikes[alicePosts[1].toString()], ) - expect(bobFeed[6]?.myState?.like).toBeUndefined() + expect(bobFeed.data.feed[6]?.myState?.like).toBeUndefined() }) it('fetches user feed', async () => { - const aliceFeed: any = await bob.userFeedView('alice') - expect(aliceFeed[0].record.text).toEqual(replies.alice[0]) - expect(aliceFeed[1].record.text).toEqual(posts.alice[2]) - expect(aliceFeed[2].record.text).toEqual(posts.alice[1]) - expect(aliceFeed[3].record.text).toEqual(posts.alice[0]) + const aliceFeed = await client.todo.social.getFeed( + { author: 'alice' }, + undefined, + { + headers: { + Authorization: users.bob.did, + }, + }, + ) + /** @ts-ignore TODO */ + expect(aliceFeed.data.feed[0].record.text).toEqual(replies.alice[0]) + /** @ts-ignore TODO */ + expect(aliceFeed.data.feed[1].record.text).toEqual(posts.alice[2]) + /** @ts-ignore TODO */ + expect(aliceFeed.data.feed[2].record.text).toEqual(posts.alice[1]) + /** @ts-ignore TODO */ + expect(aliceFeed.data.feed[3].record.text).toEqual(posts.alice[0]) - const carolFeed: any = await bob.userFeedView('carol') - expect(carolFeed[0].record.text).toEqual(replies.carol[0]) - expect(carolFeed[1].record.text).toEqual(posts.dan[1]) - expect(carolFeed[2].record.text).toEqual(posts.carol[0]) + const carolFeed = await client.todo.social.getFeed( + { author: 'carol' }, + undefined, + { + headers: { + Authorization: users.bob.did, + }, + }, + ) + /** @ts-ignore TODO */ + expect(carolFeed.data.feed[0].record.text).toEqual(replies.carol[0]) + /** @ts-ignore TODO */ + expect(carolFeed.data.feed[1].record.text).toEqual(posts.dan[1]) + /** @ts-ignore TODO */ + expect(carolFeed.data.feed[2].record.text).toEqual(posts.carol[0]) }) it('fetches postThread', async () => { - const thread: any = await bob.postThreadView(alicePosts[1], 2) - expect(thread.record.text).toEqual(posts.alice[1]) - expect(thread.replyCount).toEqual(2) - expect(thread.likeCount).toEqual(3) - expect(thread.replies.length).toEqual(2) - expect(thread.replies[0].record.text).toEqual(replies.carol[0]) - expect(thread.replies[1].record.text).toEqual(replies.bob[0]) - expect(thread.replies[1].parent.record.text).toEqual(posts.alice[1]) - expect(thread.replies[1].replies[0].record.text).toEqual(replies.alice[0]) + const thread = await client.todo.social.getPostThread( + { uri: alicePosts[1].toString() }, + undefined, + { + headers: { + Authorization: users.bob.did, + }, + }, + ) + /** @ts-ignore TODO */ + expect(thread.data.thread.record.text).toEqual(posts.alice[1]) + expect(thread.data.thread.replyCount).toEqual(2) + expect(thread.data.thread.likeCount).toEqual(3) + expect(thread.data.thread.replies?.length).toEqual(2) + /** @ts-ignore TODO */ + expect(thread.data.thread.replies?.[0].record.text).toEqual( + replies.carol[0], + ) + /** @ts-ignore TODO */ + expect(thread.data.thread.replies?.[1].record.text).toEqual(replies.bob[0]) + /** @ts-ignore TODO */ + expect(thread.data.thread.replies?.[1].parent?.record.text).toEqual( + posts.alice[1], + ) + /** @ts-ignore TODO */ + // TODO: this is failing -- not clear to me why + expect(thread.data.thread.replies?.[1].replies?.[0].record.text).toEqual( + replies.alice[0], + ) }) }) diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index c64f4da2d54..d062c0b6f2c 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -12,8 +12,9 @@ { "path": "../auth/tsconfig.build.json" }, { "path": "../common/tsconfig.build.json" }, { "path": "../crypto/tsconfig.build.json" }, - { "path": "../microblog/tsconfig.build.json" }, { "path": "../repo/tsconfig.build.json" }, - { "path": "../schemas/tsconfig.build.json" } + { "path": "../schemas/tsconfig.build.json" }, + { "path": "../xrpc-cli/tsconfig.build.json" }, + { "path": "../xrpc-server/tsconfig.build.json" } ] } \ No newline at end of file diff --git a/packages/xrpc-cli/package.json b/packages/xrpc-cli/package.json index 8d645291a8c..784b9f55298 100644 --- a/packages/xrpc-cli/package.json +++ b/packages/xrpc-cli/package.json @@ -4,7 +4,6 @@ "bin": "dist/index.js", "main": "src/index.ts", "scripts": { - "test": "jest", "prettier": "prettier --check src/", "prettier:fix": "prettier --write src/", "lint": "eslint . --ext .ts,.tsx", diff --git a/packages/xrpc/jest.config.js b/packages/xrpc/jest.config.js deleted file mode 100644 index ed6f1fe8647..00000000000 --- a/packages/xrpc/jest.config.js +++ /dev/null @@ -1,6 +0,0 @@ -const base = require('../../jest.config.base.js') - -module.exports = { - ...base, - displayName: 'XRPC', -} diff --git a/packages/xrpc/package.json b/packages/xrpc/package.json index b5b71f95b15..956a971c0fd 100644 --- a/packages/xrpc/package.json +++ b/packages/xrpc/package.json @@ -3,7 +3,6 @@ "version": "0.0.1", "main": "src/index.ts", "scripts": { - "test": "jest", "prettier": "prettier --check src/", "prettier:fix": "prettier --write src/", "lint": "eslint . --ext .ts,.tsx", diff --git a/schemas/todo.adx/createAccount.json b/schemas/todo.adx/createAccount.json new file mode 100644 index 00000000000..2a765f104d3 --- /dev/null +++ b/schemas/todo.adx/createAccount.json @@ -0,0 +1,18 @@ +{ + "xrpc": 1, + "id": "todo.adx.createAccount", + "type": "procedure", + "description": "Create an account.", + "parameters": {}, + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["username", "did"], + "properties": { + "username": {"type": "string"}, + "did": {"type": "string"} + } + } + } +} \ No newline at end of file diff --git a/schemas/todo.adx/createSession.json b/schemas/todo.adx/createSession.json new file mode 100644 index 00000000000..3a26e992fcb --- /dev/null +++ b/schemas/todo.adx/createSession.json @@ -0,0 +1,15 @@ +{ + "xrpc": 1, + "id": "todo.adx.createSession", + "type": "procedure", + "description": "Create an authentication session.", + "parameters": {}, + "input": { + "encoding": "", + "schema": {} + }, + "output": { + "encoding": "", + "schema": {} + } +} \ No newline at end of file diff --git a/schemas/todo.adx/deleteAccount.json b/schemas/todo.adx/deleteAccount.json new file mode 100644 index 00000000000..863d3e9f3cb --- /dev/null +++ b/schemas/todo.adx/deleteAccount.json @@ -0,0 +1,15 @@ +{ + "xrpc": 1, + "id": "todo.adx.deleteAccount", + "type": "procedure", + "description": "Delete an account.", + "parameters": {}, + "input": { + "encoding": "", + "schema": {} + }, + "output": { + "encoding": "", + "schema": {} + } +} \ No newline at end of file diff --git a/schemas/todo.adx/deleteSession.json b/schemas/todo.adx/deleteSession.json new file mode 100644 index 00000000000..c3ce915c1ff --- /dev/null +++ b/schemas/todo.adx/deleteSession.json @@ -0,0 +1,15 @@ +{ + "xrpc": 1, + "id": "todo.adx.deleteSession", + "type": "procedure", + "description": "Delete the current session.", + "parameters": {}, + "input": { + "encoding": "", + "schema": {} + }, + "output": { + "encoding": "", + "schema": {} + } +} \ No newline at end of file diff --git a/schemas/todo.adx/getAccount.json b/schemas/todo.adx/getAccount.json new file mode 100644 index 00000000000..927640786fc --- /dev/null +++ b/schemas/todo.adx/getAccount.json @@ -0,0 +1,15 @@ +{ + "xrpc": 1, + "id": "todo.adx.getAccount", + "type": "query", + "description": "Get information about an account.", + "parameters": {}, + "input": { + "encoding": "", + "schema": {} + }, + "output": { + "encoding": "", + "schema": {} + } +} \ No newline at end of file diff --git a/schemas/todo.adx/getSession.json b/schemas/todo.adx/getSession.json new file mode 100644 index 00000000000..ecd7b2aadd7 --- /dev/null +++ b/schemas/todo.adx/getSession.json @@ -0,0 +1,15 @@ +{ + "xrpc": 1, + "id": "todo.adx.getSession", + "type": "query", + "description": "Get information about the current session.", + "parameters": {}, + "input": { + "encoding": "", + "schema": {} + }, + "output": { + "encoding": "", + "schema": {} + } +} \ No newline at end of file diff --git a/schemas/todo.adx/repoBatchWrite.json b/schemas/todo.adx/repoBatchWrite.json new file mode 100644 index 00000000000..979b837cd11 --- /dev/null +++ b/schemas/todo.adx/repoBatchWrite.json @@ -0,0 +1,58 @@ +{ + "xrpc": 1, + "id": "todo.adx.repoBatchWrite", + "type": "procedure", + "description": "Apply a batch transaction of creates, puts, and deletes.", + "parameters": { + "did": {"type": "string", "required": true, "description": "The DID of the repo."}, + "validate": {"type": "boolean", "default": true, "description": "Validate the records?"} + }, + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["writes"], + "properties": { + "writes": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "object", + "required": ["action", "collection", "value"], + "properties": { + "action": {"type": "string", "const": "create"}, + "collection": {"type": "string"}, + "value": {} + } + }, + { + "type": "object", + "required": ["action", "collection", "tid", "value"], + "properties": { + "action": {"type": "string", "const": "update"}, + "collection": {"type": "string"}, + "tid": {"type": "string"}, + "value": {} + } + }, + { + "type": "object", + "required": ["action", "collection", "tid"], + "properties": { + "action": {"type": "string", "const": "delete"}, + "collection": {"type": "string"}, + "tid": {"type": "string"} + } + } + ] + } + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": {} + } +} \ No newline at end of file diff --git a/schemas/todo.adx/repoCreateRecord.json b/schemas/todo.adx/repoCreateRecord.json new file mode 100644 index 00000000000..c4e95fef593 --- /dev/null +++ b/schemas/todo.adx/repoCreateRecord.json @@ -0,0 +1,26 @@ +{ + "xrpc": 1, + "id": "todo.adx.repoCreateRecord", + "type": "procedure", + "description": "Create a new record.", + "parameters": { + "did": {"type": "string", "required": true, "description": "The DID of the repo."}, + "type": {"type": "string", "required": true, "description": "The NSID of the record type."}, + "validate": {"type": "boolean", "default": true, "description": "Validate the record?"} + }, + "input": { + "encoding": "application/json", + "description": "The record to create", + "schema": {} + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["uri"], + "properties": { + "uri": {"type": "string"} + } + } + } +} \ No newline at end of file diff --git a/schemas/todo.adx/repoDeleteRecord.json b/schemas/todo.adx/repoDeleteRecord.json new file mode 100644 index 00000000000..de7f183a911 --- /dev/null +++ b/schemas/todo.adx/repoDeleteRecord.json @@ -0,0 +1,11 @@ +{ + "xrpc": 1, + "id": "todo.adx.repoDeleteRecord", + "type": "procedure", + "description": "Delete a record.", + "parameters": { + "did": {"type": "string", "required": true, "description": "The DID of the repo."}, + "type": {"type": "string", "required": true, "description": "The NSID of the record type."}, + "tid": {"type": "string", "required": true, "description": "The TID of the record."} + } +} \ No newline at end of file diff --git a/schemas/todo.adx/repoDescribe.json b/schemas/todo.adx/repoDescribe.json new file mode 100644 index 00000000000..fc4303e049e --- /dev/null +++ b/schemas/todo.adx/repoDescribe.json @@ -0,0 +1,23 @@ +{ + "xrpc": 1, + "id": "todo.adx.repoDescribe", + "type": "query", + "description": "Get information about the repo, including the list of collections.", + "parameters": { + "nameOrDid": {"type": "string", "required": true, "description": "The username or DID of the repo."} + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["name", "did", "didDoc", "collections", "nameIsCorrect"], + "properties": { + "name": {"type": "string"}, + "did": {"type": "string"}, + "didDoc": {"type": "object"}, + "collections": {"type": "array", "items": {"type": "string"}}, + "nameIsCorrect": {"type": "boolean"} + } + } + } +} \ No newline at end of file diff --git a/schemas/todo.adx/repoGetRecord.json b/schemas/todo.adx/repoGetRecord.json new file mode 100644 index 00000000000..5f810569e0c --- /dev/null +++ b/schemas/todo.adx/repoGetRecord.json @@ -0,0 +1,22 @@ +{ + "xrpc": 1, + "id": "todo.adx.repoGetRecord", + "type": "query", + "description": "Fetch a record.", + "parameters": { + "nameOrDid": {"type": "string", "required": true, "description": "The name or DID of the repo."}, + "type": {"type": "string", "required": true, "description": "The NSID of the record type."}, + "tid": {"type": "string", "required": true, "description": "The TID of the record."} + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["uri", "value"], + "properties": { + "uri": {"type": "string"}, + "value": {"type": "object"} + } + } + } +} \ No newline at end of file diff --git a/schemas/todo.adx/repoListRecords.json b/schemas/todo.adx/repoListRecords.json new file mode 100644 index 00000000000..a0532e20cb0 --- /dev/null +++ b/schemas/todo.adx/repoListRecords.json @@ -0,0 +1,34 @@ +{ + "xrpc": 1, + "id": "todo.adx.repoListRecords", + "type": "query", + "description": "List a range of records in a collection.", + "parameters": { + "nameOrDid": {"type": "string", "required": true, "description": "The username or DID of the repo."}, + "type": {"type": "string", "required": true, "description": "The NSID of the record type."}, + "limit": {"type": "number", "minimum": 1, "default": 50, "description": "The number of records to return. TODO-max number?"}, + "before": {"type": "string", "description": "A TID to filter the range of records returned."}, + "after": {"type": "string", "description": "A TID to filter the range of records returned."}, + "reverse": {"type": "boolean", "description": "Reverse the order of the returned records?", "default": false} + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["records"], + "properties": { + "records": { + "type": "array", + "items": { + "type": "object", + "required": ["uri", "value"], + "properties": { + "uri": {"type": "string"}, + "value": {"type": "object"} + } + } + } + } + } + } +} \ No newline at end of file diff --git a/schemas/todo.adx/repoPutRecord.json b/schemas/todo.adx/repoPutRecord.json new file mode 100644 index 00000000000..a06242d9d84 --- /dev/null +++ b/schemas/todo.adx/repoPutRecord.json @@ -0,0 +1,26 @@ +{ + "xrpc": 1, + "id": "todo.adx.repoPutRecord", + "type": "procedure", + "description": "Write a record.", + "parameters": { + "did": {"type": "string", "required": true, "description": "The DID of the repo."}, + "type": {"type": "string", "required": true, "description": "The NSID of the record type."}, + "tid": {"type": "string", "required": true, "description": "The TID of the record."}, + "validate": {"type": "boolean", "default": true, "description": "Validate the record?"} + }, + "input": { + "encoding": "application/json", + "schema": {} + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["uri"], + "properties": { + "uri": {"type": "string"} + } + } + } +} \ No newline at end of file diff --git a/schemas/todo.adx/resolveName.json b/schemas/todo.adx/resolveName.json new file mode 100644 index 00000000000..09da4e94170 --- /dev/null +++ b/schemas/todo.adx/resolveName.json @@ -0,0 +1,19 @@ +{ + "xrpc": 1, + "id": "todo.adx.resolveName", + "type": "query", + "description": "Provides the DID of the repo indicated by the Host parameter.", + "parameters": { + "name": {"type": "string", "required": true, "description": "The name to resolve."} + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["did"], + "properties": { + "did": {"type": "string"} + } + } + } +} \ No newline at end of file diff --git a/schemas/todo.adx/syncGetRepo.json b/schemas/todo.adx/syncGetRepo.json new file mode 100644 index 00000000000..751d4c5b20d --- /dev/null +++ b/schemas/todo.adx/syncGetRepo.json @@ -0,0 +1,13 @@ +{ + "xrpc": 1, + "id": "todo.adx.syncGetRepo", + "type": "query", + "description": "Gets the repo state.", + "parameters": { + "did": {"type": "string", "required": true, "description": "The DID of the repo."}, + "from": {"type": "string", "description": "A past commit CID"} + }, + "output": { + "encoding": "application/cbor" + } +} \ No newline at end of file diff --git a/schemas/todo.adx/syncGetRoot.json b/schemas/todo.adx/syncGetRoot.json new file mode 100644 index 00000000000..25ab793f093 --- /dev/null +++ b/schemas/todo.adx/syncGetRoot.json @@ -0,0 +1,19 @@ +{ + "xrpc": 1, + "id": "todo.adx.syncGetRoot", + "type": "query", + "description": "Gets the current root CID of a repo.", + "parameters": { + "did": {"type": "string", "required": true, "description": "The DID of the repo."} + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["root"], + "properties": { + "root": {"type": "string"} + } + } + } +} \ No newline at end of file diff --git a/schemas/todo.adx/updateRepo.json b/schemas/todo.adx/updateRepo.json new file mode 100644 index 00000000000..6ce1c9db1eb --- /dev/null +++ b/schemas/todo.adx/updateRepo.json @@ -0,0 +1,12 @@ +{ + "xrpc": 1, + "id": "todo.adx.syncUpdateRepo", + "type": "procedure", + "description": "Writes commits to a repo.", + "parameters": { + "did": {"type": "string", "required": true, "description": "The DID of the repo."} + }, + "input": { + "encoding": "application/cbor" + } +} \ No newline at end of file diff --git a/packages/microblog/src/schemas/Badge.json b/schemas/todo.social/badge.json similarity index 52% rename from packages/microblog/src/schemas/Badge.json rename to schemas/todo.social/badge.json index b9de7bee909..d40954df26e 100644 --- a/packages/microblog/src/schemas/Badge.json +++ b/schemas/todo.social/badge.json @@ -1,15 +1,8 @@ { - "$type": "adxs-record", - "author": "blueskyweb.xyz", - "name": "Badge", - "$comment": "An assertion about the subject by this user.", - "locale": { - "en-US": { - "nameSingular": "Badge", - "namePlural": "Badges" - } - }, - "schema": { + "adx": 1, + "id": "todo.social.badge", + "description": "An assertion about the subject by this user.", + "record": { "type": "object", "required": ["assertion", "subject", "createdAt"], "properties": { @@ -58,38 +51,5 @@ } } } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "$type": "blueskyweb.xyz:Badge", - "assertion": {"type": "employee"}, - "subject": { - "did": "did:example:1234", - "name": "alice.com" - }, - "createdAt": "2010-01-01T19:23:24Z" - }, - { - "$type": "blueskyweb.xyz:Badge", - "assertion": {"type": "tag", "tag": "tech"}, - "subject": { - "did": "did:example:1234", - "name": "alice.com" - }, - "createdAt": "2010-01-01T19:23:24Z" - }, - { - "$type": "blueskyweb.xyz:Badge", - "assertion": {"type": "something-else", "param": "allowed"}, - "subject": { - "did": "did:example:1234", - "name": "alice.com" - }, - "createdAt": "2010-01-01T19:23:24Z" - } - ] - } } } \ No newline at end of file diff --git a/schemas/todo.social/follow.json b/schemas/todo.social/follow.json new file mode 100644 index 00000000000..c0a9c279bbe --- /dev/null +++ b/schemas/todo.social/follow.json @@ -0,0 +1,13 @@ +{ + "adx": 1, + "id": "todo.social.follow", + "description": "A social follow", + "record": { + "type": "object", + "required": ["subject", "createdAt"], + "properties": { + "subject": { "type": "string" }, + "createdAt": {"type": "string", "format": "date-time"} + } + } +} \ No newline at end of file diff --git a/schemas/todo.social/getFeed.json b/schemas/todo.social/getFeed.json new file mode 100644 index 00000000000..c01929328d4 --- /dev/null +++ b/schemas/todo.social/getFeed.json @@ -0,0 +1,96 @@ +{ + "xrpc": 1, + "id": "todo.social.getFeed", + "type": "query", + "description": "A computed view of the home feed or a user's feed", + "parameters": { + "author": {"type": "string"}, + "limit": {"type": "number", "maximum": 100}, + "before": {"type": "string"} + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["feed"], + "properties": { + "feed": { + "type": "array", + "items": {"$ref": "#/$defs/feedItem"} + } + }, + "$defs": { + "feedItem": { + "type": "object", + "required": ["uri", "author", "record", "replyCount", "repostCount", "likeCount", "indexedAt"], + "properties": { + "uri": {"type": "string"}, + "author": {"$ref": "#/$defs/user"}, + "repostedBy": {"$ref": "#/$defs/user"}, + "record": {"type": "object"}, + "embed": { + "oneOf": [ + {"$ref": "#/$defs/recordEmbed"}, + {"$ref": "#/$defs/externalEmbed"}, + {"$ref": "#/$defs/unknownEmbed"} + ] + }, + "replyCount": {"type": "number"}, + "repostCount": {"type": "number"}, + "likeCount": {"type": "number"}, + "indexedAt": {"type": "string", "format": "date-time"}, + "myState": { + "type": "object", + "properties": { + "repost": {"type": "string"}, + "like": {"type": "string"} + } + } + } + }, + "user": { + "type": "object", + "required": ["did", "name"], + "properties": { + "did": {"type": "string"}, + "name": {"type": "string"}, + "displayName": { + "type": "string", + "maxLength": 64 + } + } + }, + "recordEmbed": { + "type": "object", + "required": ["type", "author", "record"], + "properties": { + "type": {"const": "record"}, + "author": {"$ref": "#/$defs/user"}, + "record": {"type": "object"} + } + }, + "externalEmbed": { + "type": "object", + "required": ["type", "uri", "title", "description", "imageUri"], + "properties": { + "type": {"const": "external"}, + "uri": {"type": "string"}, + "title": {"type": "string"}, + "description": {"type": "string"}, + "imageUri": {"type": "string"} + } + }, + "unknownEmbed": { + "type": "object", + "required": ["type"], + "properties": { + "type": { + "type": "string", + "not": {"enum": ["record", "external"]} + } + } + } + } + } + } +} \ No newline at end of file diff --git a/schemas/todo.social/getLikedBy.json b/schemas/todo.social/getLikedBy.json new file mode 100644 index 00000000000..22987ced441 --- /dev/null +++ b/schemas/todo.social/getLikedBy.json @@ -0,0 +1,37 @@ +{ + "xrpc": 1, + "id": "todo.social.getLikedBy", + "type": "query", + "parameters": { + "uri": {"type": "string", "required": true}, + "limit": {"type": "number", "maximum": 100}, + "before": {"type": "string"} + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["uri", "likedBy"], + "properties": { + "uri": {"type": "string"}, + "likedBy": { + "type": "array", + "items": { + "type": "object", + "required": ["did", "name", "indexedAt"], + "properties": { + "did": {"type": "string"}, + "name": {"type": "string"}, + "displayName": { + "type": "string", + "maxLength": 64 + }, + "createdAt": {"type": "string", "format": "date-time"}, + "indexedAt": {"type": "string", "format": "date-time"} + } + } + } + } + } + } +} \ No newline at end of file diff --git a/schemas/todo.social/getNotifications.json b/schemas/todo.social/getNotifications.json new file mode 100644 index 00000000000..946d0da88fb --- /dev/null +++ b/schemas/todo.social/getNotifications.json @@ -0,0 +1,46 @@ +{ + "xrpc": 1, + "id": "todo.social.getNotifications", + "type": "query", + "parameters": { + "limit": {"type": "number", "maximum": 100}, + "before": {"type": "string"} + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["notifications"], + "properties": { + "notifications": { + "type": "array", + "items": {"$ref": "#/$defs/notification"} + } + }, + "$defs": { + "notification": { + "type": "object", + "required": ["uri", "author", "record", "isRead", "indexedAt"], + "properties": { + "uri": {"type": "string", "format": "uri"}, + "author": { + "type": "object", + "required": ["did", "name", "displayName"], + "properties": { + "did": {"type": "string"}, + "name": {"type": "string"}, + "displayName": { + "type": "string", + "maxLength": 64 + } + } + }, + "record": {"type": "object"}, + "isRead": {"type": "boolean"}, + "indexedAt": {"type": "string", "format": "date-time"} + } + } + } + } + } +} \ No newline at end of file diff --git a/schemas/todo.social/getPostThread.json b/schemas/todo.social/getPostThread.json new file mode 100644 index 00000000000..999f31ebf4b --- /dev/null +++ b/schemas/todo.social/getPostThread.json @@ -0,0 +1,95 @@ +{ + "xrpc": 1, + "id": "todo.social.getPostThread", + "type": "query", + "parameters": { + "uri": {"type": "string", "required": true}, + "depth": {"type": "number"} + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["thread"], + "properties": { + "thread": {"$ref": "#/$defs/post"} + }, + "$defs": { + "post": { + "type": "object", + "required": ["uri", "author", "record", "replyCount", "likeCount", "repostCount", "indexedAt"], + "properties": { + "uri": {"type": "string"}, + "author": {"$ref": "#/$defs/user"}, + "record": {"type": "object"}, + "embed": { + "oneOf": [ + {"$ref": "#/$defs/recordEmbed"}, + {"$ref": "#/$defs/externalEmbed"}, + {"$ref": "#/$defs/unknownEmbed"} + ] + }, + "parent": {"$ref": "#/$defs/post"}, + "replyCount": {"type": "number"}, + "replies": { + "type": "array", + "items": {"$ref": "#/$defs/post"} + }, + "likeCount": {"type": "number"}, + "repostCount": {"type": "number"}, + "indexedAt": {"type": "string", "format": "date-time"}, + "myState": { + "type": "object", + "properties": { + "repost": {"type": "string"}, + "like": {"type": "string"} + } + } + } + }, + "user": { + "type": "object", + "required": ["did", "name"], + "properties": { + "did": {"type": "string"}, + "name": {"type": "string"}, + "displayName": { + "type": "string", + "maxLength": 64 + } + } + }, + "recordEmbed": { + "type": "object", + "required": ["type", "author", "record"], + "properties": { + "type": {"const": "record"}, + "author": {"$ref": "#/$defs/user"}, + "record": {"type": "object"} + } + }, + "externalEmbed": { + "type": "object", + "required": ["type", "uri", "title", "description", "imageUri"], + "properties": { + "type": {"const": "external"}, + "uri": {"type": "string"}, + "title": {"type": "string"}, + "description": {"type": "string"}, + "imageUri": {"type": "string"} + } + }, + "unknownEmbed": { + "type": "object", + "required": ["type"], + "properties": { + "type": { + "type": "string", + "not": {"enum": ["record", "external"]} + } + } + } + } + } + } +} \ No newline at end of file diff --git a/schemas/todo.social/getProfile.json b/schemas/todo.social/getProfile.json new file mode 100644 index 00000000000..e816c1afb30 --- /dev/null +++ b/schemas/todo.social/getProfile.json @@ -0,0 +1,67 @@ +{ + "xrpc": 1, + "id": "todo.social.getProfile", + "type": "query", + "parameters": { + "user": {"type": "string", "required": true} + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["did", "name", "followersCount", "followsCount", "postsCount", "badges"], + "properties": { + "did": {"type": "string"}, + "name": {"type": "string"}, + "displayName": { + "type": "string", + "maxLength": 64 + }, + "description": { + "type": "string", + "maxLength": 256 + }, + "followersCount": {"type": "number"}, + "followsCount": {"type": "number"}, + "postsCount": {"type": "number"}, + "badges": {"type": "array", "items": {"$ref":"#/$defs/badge"}}, + "myState": { + "type": "object", + "properties": { + "follow": {"type": "string"} + } + } + }, + "$defs": { + "badge": { + "type": "object", + "required": ["uri"], + "properties": { + "uri": {"type": "string"}, + "error": {"type": "string"}, + "issuer": { + "type": "object", + "required": ["did", "name", "displayName"], + "properties": { + "did": {"type": "string"}, + "name": {"type": "string"}, + "displayName": { + "type": "string", + "maxLength": 64 + } + } + }, + "assertion": { + "type": "object", + "required": ["type"], + "properties": { + "type": {"type": "string"} + } + }, + "createdAt": {"type": "string", "format": "date-time"} + } + } + } + } + } +} \ No newline at end of file diff --git a/schemas/todo.social/getRepostedBy.json b/schemas/todo.social/getRepostedBy.json new file mode 100644 index 00000000000..a2737e5c3a4 --- /dev/null +++ b/schemas/todo.social/getRepostedBy.json @@ -0,0 +1,37 @@ +{ + "xrpc": 1, + "id": "todo.social.getRepostedBy", + "type": "query", + "parameters": { + "uri": {"type": "string", "required": true}, + "limit": {"type": "number", "maximum": 100}, + "before": {"type": "string"} + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["uri", "repostedBy"], + "properties": { + "uri": {"type": "string"}, + "repostedBy": { + "type": "array", + "items": { + "type": "object", + "required": ["did", "name", "indexedAt"], + "properties": { + "did": {"type": "string"}, + "name": {"type": "string"}, + "displayName": { + "type": "string", + "maxLength": 64 + }, + "createdAt": {"type": "string", "format": "date-time"}, + "indexedAt": {"type": "string", "format": "date-time"} + } + } + } + } + } + } +} \ No newline at end of file diff --git a/schemas/todo.social/getUserFollowers.json b/schemas/todo.social/getUserFollowers.json new file mode 100644 index 00000000000..0ce2c0e038d --- /dev/null +++ b/schemas/todo.social/getUserFollowers.json @@ -0,0 +1,49 @@ +{ + "xrpc": 1, + "id": "todo.social.getUserFollowers", + "type": "query", + "description": "Who is following a user?", + "parameters": { + "user": {"type": "string", "required": true}, + "limit": {"type": "number", "maximum": 100}, + "before": {"type": "string"} + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["subject", "followers"], + "properties": { + "subject": { + "type": "object", + "required": ["did", "name"], + "properties": { + "did": {"type": "string"}, + "name": {"type": "string"}, + "displayName": { + "type": "string", + "maxLength": 64 + } + } + }, + "followers": { + "type": "array", + "items": { + "type": "object", + "required": ["did", "name", "indexedAt"], + "properties": { + "did": {"type": "string"}, + "name": {"type": "string"}, + "displayName": { + "type": "string", + "maxLength": 64 + }, + "createdAt": {"type": "string", "format": "date-time"}, + "indexedAt": {"type": "string", "format": "date-time"} + } + } + } + } + } + } +} \ No newline at end of file diff --git a/schemas/todo.social/getUserFollows.json b/schemas/todo.social/getUserFollows.json new file mode 100644 index 00000000000..dfa5edd89a6 --- /dev/null +++ b/schemas/todo.social/getUserFollows.json @@ -0,0 +1,49 @@ +{ + "xrpc": 1, + "id": "todo.social.getUserFollows", + "type": "query", + "description": "Who is a user following?", + "parameters": { + "user": {"type": "string", "required": true}, + "limit": {"type": "number", "maximum": 100}, + "before": {"type": "string"} + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["subject", "follows"], + "properties": { + "subject": { + "type": "object", + "required": ["did", "name"], + "properties": { + "did": {"type": "string"}, + "name": {"type": "string"}, + "displayName": { + "type": "string", + "maxLength": 64 + } + } + }, + "follows": { + "type": "array", + "items": { + "type": "object", + "required": ["did", "name", "indexedAt"], + "properties": { + "did": {"type": "string"}, + "name": {"type": "string"}, + "displayName": { + "type": "string", + "maxLength": 64 + }, + "createdAt": {"type": "string", "format": "date-time"}, + "indexedAt": {"type": "string", "format": "date-time"} + } + } + } + } + } + } +} \ No newline at end of file diff --git a/schemas/todo.social/like.json b/schemas/todo.social/like.json new file mode 100644 index 00000000000..6d3f2673796 --- /dev/null +++ b/schemas/todo.social/like.json @@ -0,0 +1,12 @@ +{ + "adx": 1, + "id": "todo.social.like", + "record": { + "type": "object", + "required": ["subject", "createdAt"], + "properties": { + "subject": {"type": "string"}, + "createdAt": {"type": "string", "format": "date-time"} + } + } +} \ No newline at end of file diff --git a/packages/microblog/src/schemas/EmbeddedMedia.json b/schemas/todo.social/mediaEmbed.json similarity index 50% rename from packages/microblog/src/schemas/EmbeddedMedia.json rename to schemas/todo.social/mediaEmbed.json index 313e47eb58d..3259216fb9c 100644 --- a/packages/microblog/src/schemas/EmbeddedMedia.json +++ b/schemas/todo.social/mediaEmbed.json @@ -1,15 +1,8 @@ { - "$type": "adxs-record", - "author": "blueskyweb.xyz", - "name": "EmbeddedMedia", - "$comment": "A list of media embedded in a post or document.", - "locale": { - "en-US": { - "nameSingular": "Embedded Media", - "namePlural": "Embedded Media" - } - }, - "schema": { + "adx": 1, + "id": "todo.social.mediaEmbed", + "description": "A list of media embedded in a post or document.", + "record": { "type": "object", "required": ["media"], "properties": { @@ -34,21 +27,5 @@ } } } - }, - "$ext": { - "adxs-doc": { - "examples": [ - { - "$type": "blueskyweb.xyz:EmbeddedMedia", - "media": [ - { - "alt": "Me at the beach", - "thumb": {"mimeType": "image/png", "blobId": "1234"}, - "original": {"mimeType": "image/png", "blobId": "1235"} - } - ] - } - ] - } } } \ No newline at end of file diff --git a/schemas/todo.social/post.json b/schemas/todo.social/post.json new file mode 100644 index 00000000000..fc5b98d04aa --- /dev/null +++ b/schemas/todo.social/post.json @@ -0,0 +1,44 @@ +{ + "adx": 1, + "id": "todo.social.post", + "record": { + "type": "object", + "required": ["text", "createdAt"], + "properties": { + "text": {"type": "string", "maxLength": 256}, + "entities": {"$ref": "#/$defs/entity"}, + "reply": { + "type": "object", + "required": ["root"], + "properties": { + "root": {"type": "string"}, + "parent": {"type": "string"} + } + }, + "createdAt": {"type": "string", "format": "date-time"} + }, + "$defs": { + "entity": { + "type": "array", + "items": { + "type": "object", + "required": ["index", "type", "value"], + "properties": { + "index": {"$ref": "#/$defs/textSlice"}, + "type": { + "type": "string", + "$comment": "Expected values are 'mention', 'hashtag', and 'link'." + }, + "value": {"type": "string"} + } + } + }, + "textSlice": { + "type": "array", + "items": [{"type": "number"}, {"type": "number"}], + "minItems": 2, + "maxItems": 2 + } + } + } +} \ No newline at end of file diff --git a/schemas/todo.social/profile.json b/schemas/todo.social/profile.json new file mode 100644 index 00000000000..fe566cd66f9 --- /dev/null +++ b/schemas/todo.social/profile.json @@ -0,0 +1,28 @@ +{ + "adx": 1, + "id": "todo.social.profile", + "record": { + "type": "object", + "required": ["displayName"], + "properties": { + "displayName": { + "type": "string", + "maxLength": 64 + }, + "description": { + "type": "string", + "maxLength": 256 + }, + "badges": {"type": "array", "items": {"$ref": "#/$defs/badgeRef"}} + }, + "$defs": { + "badgeRef": { + "type": "object", + "required": ["uri"], + "properties": { + "uri": {"type": "string"} + } + } + } + } +} \ No newline at end of file diff --git a/schemas/todo.social/repost.json b/schemas/todo.social/repost.json new file mode 100644 index 00000000000..d2e040fd6c9 --- /dev/null +++ b/schemas/todo.social/repost.json @@ -0,0 +1,12 @@ +{ + "adx": 1, + "id": "todo.social.repost", + "record": { + "type": "object", + "required": ["subject", "createdAt"], + "properties": { + "subject": {"type": "string"}, + "createdAt": {"type": "string", "format": "date-time"} + } + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 86b1d7e8673..d6e46792cf9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,7 +29,6 @@ { "path": "./packages/crypto/tsconfig.build.json" }, { "path": "./packages/dev-env" }, { "path": "./packages/did-sdk/tsconfig.build.json" }, - { "path": "./packages/microblog/tsconfig.build.json" }, { "path": "./packages/nsid/tsconfig.build.json" }, { "path": "./packages/schemas/tsconfig.build.json" }, { "path": "./packages/xrpc/tsconfig.build.json" }, diff --git a/yarn.lock b/yarn.lock index c7aabc41b3e..cfc8ba923e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24,25 +24,25 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.0.tgz#2a592fd89bacb1fcde68de31bee4f2f2dacb0e86" - integrity sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.1.tgz#72d647b4ff6a4f82878d184613353af1dd0290f9" + integrity sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg== "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.18.6": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.0.tgz#d2f5f4f2033c00de8096be3c9f45772563e150c3" - integrity sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ== + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.1.tgz#c8fa615c5e88e272564ace3d42fbc8b17bfeb22b" + integrity sha512-1H8VgqXme4UXCRv7/Wa1bq7RVymKOzC7znjyFM8KiEzwFqcKUKYNoQef4GhdklgNvoBXyW4gYhuBNCM5o1zImw== dependencies: "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.18.6" "@babel/generator" "^7.19.0" - "@babel/helper-compilation-targets" "^7.19.0" + "@babel/helper-compilation-targets" "^7.19.1" "@babel/helper-module-transforms" "^7.19.0" "@babel/helpers" "^7.19.0" - "@babel/parser" "^7.19.0" + "@babel/parser" "^7.19.1" "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" + "@babel/traverse" "^7.19.1" "@babel/types" "^7.19.0" convert-source-map "^1.7.0" debug "^4.1.0" @@ -74,14 +74,14 @@ "@babel/helper-explode-assignable-expression" "^7.18.6" "@babel/types" "^7.18.9" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz#537ec8339d53e806ed422f1e06c8f17d55b96bb0" - integrity sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0", "@babel/helper-compilation-targets@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.1.tgz#7f630911d83b408b76fe584831c98e5395d7a17c" + integrity sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg== dependencies: - "@babel/compat-data" "^7.19.0" + "@babel/compat-data" "^7.19.1" "@babel/helper-validator-option" "^7.18.6" - browserslist "^4.20.2" + browserslist "^4.21.3" semver "^6.3.0" "@babel/helper-create-class-features-plugin@^7.18.6": @@ -105,7 +105,7 @@ "@babel/helper-annotate-as-pure" "^7.18.6" regexpu-core "^5.1.0" -"@babel/helper-define-polyfill-provider@^0.3.2", "@babel/helper-define-polyfill-provider@^0.3.3": +"@babel/helper-define-polyfill-provider@^0.3.3": version "0.3.3" resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz#8612e55be5d51f0cd1f36b4a5a83924e89884b7a" integrity sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww== @@ -195,15 +195,15 @@ "@babel/types" "^7.18.9" "@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.18.9.tgz#1092e002feca980fbbb0bd4d51b74a65c6a500e6" - integrity sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ== + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz#e1592a9b4b368aa6bdb8784a711e0bcbf0612b78" + integrity sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw== dependencies: "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-member-expression-to-functions" "^7.18.9" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/traverse" "^7.18.9" - "@babel/types" "^7.18.9" + "@babel/traverse" "^7.19.1" + "@babel/types" "^7.19.0" "@babel/helper-simple-access@^7.18.6": version "7.18.6" @@ -232,9 +232,9 @@ integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== "@babel/helper-validator-identifier@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" - integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== "@babel/helper-validator-option@^7.18.6": version "7.18.6" @@ -269,10 +269,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.0", "@babel/parser@^7.7.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.0.tgz#497fcafb1d5b61376959c1c338745ef0577aa02c" - integrity sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.1", "@babel/parser@^7.7.0": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.1.tgz#6f6d6c2e621aad19a92544cc217ed13f1aac5b4c" + integrity sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" @@ -290,10 +290,10 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" "@babel/plugin-proposal-optional-chaining" "^7.18.9" -"@babel/plugin-proposal-async-generator-functions@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.0.tgz#cf5740194f170467df20581712400487efc79ff1" - integrity sha512-nhEByMUTx3uZueJ/QkJuSlCfN4FGg+xy+vRsfGQGzSauq5ks2Deid2+05Q3KhfaUjvec1IGhw/Zm3cFm8JigTQ== +"@babel/plugin-proposal-async-generator-functions@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.1.tgz#34f6f5174b688529342288cd264f80c9ea9fb4a7" + integrity sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q== dependencies: "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-plugin-utils" "^7.19.0" @@ -695,10 +695,10 @@ "@babel/helper-module-transforms" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-named-capturing-groups-regex@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.0.tgz#58c52422e4f91a381727faed7d513c89d7f41ada" - integrity sha512-HDSuqOQzkU//kfGdiHBt71/hkDTApw4U/cMVgKgX7PqfB3LOaK+2GtCEsBu1dL9CkswDm0Gwehht1dCr421ULQ== +"@babel/plugin-transform-named-capturing-groups-regex@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz#ec7455bab6cd8fb05c525a94876f435a48128888" + integrity sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.19.0" "@babel/helper-plugin-utils" "^7.19.0" @@ -799,17 +799,17 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/preset-env@^7.18.6": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.19.0.tgz#fd18caf499a67d6411b9ded68dc70d01ed1e5da7" - integrity sha512-1YUju1TAFuzjIQqNM9WsF4U6VbD/8t3wEAlw3LFYuuEr+ywqLRcSXxFKz4DCEj+sN94l/XTDiUXYRrsvMpz9WQ== + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.19.1.tgz#9f04c916f9c0205a48ebe5cc1be7768eb1983f67" + integrity sha512-c8B2c6D16Lp+Nt6HcD+nHl0VbPKVnNPTpszahuxJJnurfMtKeZ80A+qUv48Y7wqvS+dTFuLuaM9oYxyNHbCLWA== dependencies: - "@babel/compat-data" "^7.19.0" - "@babel/helper-compilation-targets" "^7.19.0" + "@babel/compat-data" "^7.19.1" + "@babel/helper-compilation-targets" "^7.19.1" "@babel/helper-plugin-utils" "^7.19.0" "@babel/helper-validator-option" "^7.18.6" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" - "@babel/plugin-proposal-async-generator-functions" "^7.19.0" + "@babel/plugin-proposal-async-generator-functions" "^7.19.1" "@babel/plugin-proposal-class-properties" "^7.18.6" "@babel/plugin-proposal-class-static-block" "^7.18.6" "@babel/plugin-proposal-dynamic-import" "^7.18.6" @@ -857,7 +857,7 @@ "@babel/plugin-transform-modules-commonjs" "^7.18.6" "@babel/plugin-transform-modules-systemjs" "^7.19.0" "@babel/plugin-transform-modules-umd" "^7.18.6" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.1" "@babel/plugin-transform-new-target" "^7.18.6" "@babel/plugin-transform-object-super" "^7.18.6" "@babel/plugin-transform-parameters" "^7.18.8" @@ -873,10 +873,10 @@ "@babel/plugin-transform-unicode-regex" "^7.18.6" "@babel/preset-modules" "^0.1.5" "@babel/types" "^7.19.0" - babel-plugin-polyfill-corejs2 "^0.3.2" - babel-plugin-polyfill-corejs3 "^0.5.3" - babel-plugin-polyfill-regenerator "^0.4.0" - core-js-compat "^3.22.1" + babel-plugin-polyfill-corejs2 "^0.3.3" + babel-plugin-polyfill-corejs3 "^0.6.0" + babel-plugin-polyfill-regenerator "^0.4.1" + core-js-compat "^3.25.1" semver "^6.3.0" "@babel/preset-modules@^0.1.5": @@ -906,10 +906,10 @@ "@babel/parser" "^7.18.10" "@babel/types" "^7.18.10" -"@babel/traverse@^7.18.9", "@babel/traverse@^7.19.0", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.0.tgz#eb9c561c7360005c592cc645abafe0c3c4548eed" - integrity sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA== +"@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.1.tgz#0fafe100a8c2a603b4718b1d9bf2568d1d193347" + integrity sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA== dependencies: "@babel/code-frame" "^7.18.6" "@babel/generator" "^7.19.0" @@ -917,7 +917,7 @@ "@babel/helper-function-name" "^7.19.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.19.0" + "@babel/parser" "^7.19.1" "@babel/types" "^7.19.0" debug "^4.1.0" globals "^11.1.0" @@ -2189,9 +2189,9 @@ integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== "@sinclair/typebox@^0.24.1": - version "0.24.41" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.41.tgz#45470b8bae32a28f1e0501066d0bacbd8b772804" - integrity sha512-TJCgQurls4FipFvHeC+gfAzb+GGstL0TDwYJKQVtTeSvJIznWzP7g3bAd5gEBlr8+bIxqnWS9VGVWREDhmE8jA== + version "0.24.42" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.42.tgz#a74b608d494a1f4cc079738e050142a678813f52" + integrity sha512-d+2AtrHGyWek2u2ITF0lHRIv6Tt7X0dEHW+0rP+5aDCEjC3fiN2RBjrLD0yU0at52BcZbRGxLbAtXiR0hFCjYw== "@sinonjs/commons@^1.7.0": version "1.8.3" @@ -2217,6 +2217,16 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@ts-morph/common@~0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.17.0.tgz#de0d405df10857907469fef8d9363893b4163fd1" + integrity sha512-RMSSvSfs9kb0VzkvQ2NWobwnj7TxCA9vI/IjR9bDHqgAyVbu2T0DN4wiKVqomyDWqO7dPr/tErSfq7urQ1Q37g== + dependencies: + fast-glob "^3.2.11" + minimatch "^5.1.0" + mkdirp "^1.0.4" + path-browserify "^1.0.1" + "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" @@ -2382,9 +2392,9 @@ integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== "@types/node@*": - version "18.7.17" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.17.tgz#52438111ea98f77475470fc62d79b9eb96bb2c92" - integrity sha512-0UyfUnt02zIuqp7yC8RYtDkp/vo8bFaQ13KkSEvUAohPOAlnVNbj5Fi3fgPSuwzakS+EvvnnZ4x9y7i6ASaSPQ== + version "18.7.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.18.tgz#633184f55c322e4fb08612307c274ee6d5ed3154" + integrity sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg== "@types/node@^15.6.2": version "15.14.9" @@ -2926,7 +2936,7 @@ babel-plugin-jest-hoist@^28.1.3: "@types/babel__core" "^7.1.14" "@types/babel__traverse" "^7.0.6" -babel-plugin-polyfill-corejs2@^0.3.2: +babel-plugin-polyfill-corejs2@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz#5d1bd3836d0a19e1b84bbf2d9640ccb6f951c122" integrity sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q== @@ -2935,15 +2945,15 @@ babel-plugin-polyfill-corejs2@^0.3.2: "@babel/helper-define-polyfill-provider" "^0.3.3" semver "^6.1.1" -babel-plugin-polyfill-corejs3@^0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz#d7e09c9a899079d71a8b670c6181af56ec19c5c7" - integrity sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw== +babel-plugin-polyfill-corejs3@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz#56ad88237137eade485a71b52f72dbed57c6230a" + integrity sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA== dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.2" - core-js-compat "^3.21.0" + "@babel/helper-define-polyfill-provider" "^0.3.3" + core-js-compat "^3.25.1" -babel-plugin-polyfill-regenerator@^0.4.0: +babel-plugin-polyfill-regenerator@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz#390f91c38d90473592ed43351e801a9d3e0fd747" integrity sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw== @@ -3039,6 +3049,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" @@ -3056,15 +3073,15 @@ browser-level@^1.0.1: module-error "^1.0.2" run-parallel-limit "^1.1.0" -browserslist@^4.20.2, browserslist@^4.21.3: - version "4.21.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a" - integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ== +browserslist@^4.21.3, browserslist@^4.21.4: + version "4.21.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" + integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== dependencies: - caniuse-lite "^1.0.30001370" - electron-to-chromium "^1.4.202" + caniuse-lite "^1.0.30001400" + electron-to-chromium "^1.4.251" node-releases "^2.0.6" - update-browserslist-db "^1.0.5" + update-browserslist-db "^1.0.9" bs-logger@0.x: version "0.2.6" @@ -3179,10 +3196,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001370: - version "1.0.30001399" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001399.tgz#1bf994ca375d7f33f8d01ce03b7d5139e8587873" - integrity sha512-4vQ90tMKS+FkvuVWS5/QY1+d805ODxZiKFzsU8o/RsVJz49ZSRR8EjykLJbqhzdPgadbX6wB538wOzle3JniRA== +caniuse-lite@^1.0.30001400: + version "1.0.30001407" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001407.tgz#92281a6ee67cb90bfd8a6a1201fcc2dc19b60a15" + integrity sha512-4ydV+t4P7X3zH83fQWNDX/mQEzYomossfpViCOx9zHBSMV+rIe3LFqglHHtVyvNl1FhTNxPxs3jei82iqOW04w== caseless@~0.12.0: version "0.12.0" @@ -3357,6 +3374,11 @@ co@^4.6.0: resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== +code-block-writer@^11.0.3: + version "11.0.3" + resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-11.0.3.tgz#9eec2993edfb79bfae845fbc093758c0a0b73b76" + integrity sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw== + code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -3416,6 +3438,11 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +commander@^9.4.0: + version "9.4.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.0.tgz#bc4a40918fefe52e22450c111ecd6b7acce6f11c" + integrity sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw== + compare-func@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-2.0.0.tgz#fb65e75edbddfd2e568554e8b5b05fff7a51fcb3" @@ -3568,12 +3595,12 @@ cookie@0.5.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== -core-js-compat@^3.21.0, core-js-compat@^3.22.1: - version "3.25.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.25.1.tgz#6f13a90de52f89bbe6267e5620a412c7f7ff7e42" - integrity sha512-pOHS7O0i8Qt4zlPW/eIFjwp+NrTPx+wTL0ctgI2fHn31sZOq89rDsmtc/A2vAX7r6shl+bmVI+678He46jgBlw== +core-js-compat@^3.25.1: + version "3.25.2" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.25.2.tgz#7875573586809909c69e03ef310810c1969ee138" + integrity sha512-TxfyECD4smdn3/CjWxczVtJqVLEEC2up7/82t7vC0AzNogr+4nQ8vyF7abxAuTXWvjTClSbvGhU0RgqA4ToQaQ== dependencies: - browserslist "^4.21.3" + browserslist "^4.21.4" core-js@2.6.5: version "2.6.5" @@ -3867,10 +3894,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.4.202: - version "1.4.248" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.248.tgz#dd2dab68277e91e8452536ee9265f484066f94ad" - integrity sha512-qShjzEYpa57NnhbW2K+g+Fl+eNoDvQ7I+2MRwWnU6Z6F0HhXekzsECCLv+y2OJUsRodjqoSfwHkIX42VUFtUzg== +electron-to-chromium@^1.4.251: + version "1.4.255" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.255.tgz#dc52d1095b876ed8acf25865db10265b02b1d6e1" + integrity sha512-H+mFNKow6gi2P5Gi2d1Fvd3TUEJlB9CF7zYaIV9T83BE3wP1xZ0mRPbNTm0KUjyd1QiVy7iKXuIcjlDtBQMiAQ== emittery@^0.10.2: version "0.10.2" @@ -4407,7 +4434,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.2.9: +fast-glob@^3.2.11, fast-glob@^3.2.9: version "3.2.12" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== @@ -5192,9 +5219,9 @@ is-buffer@^2.0.5: integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== is-callable@^1.1.4, is-callable@^1.2.4: - version "1.2.5" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.5.tgz#6123e0b1fef5d7591514b371bb018204892f1a2b" - integrity sha512-ZIWRujF6MvYGkEuHMYtFRkL2wAtFw89EHfKlXrkPkjQZZRWeh9L1q3SV13NIfHnqxugjLvAOkEHx9mb1zcMnEw== + version "1.2.6" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.6.tgz#fd6170b0b8c7e2cc73de342ef8284a2202023c44" + integrity sha512-krO72EO2NptOGAX2KYyqbP9vYMlNAXdB53rq6f8LXY6RY7JdSR/3BD6wLUlPHSAesmY9vstNrjvqGaCiRK/91Q== is-ci@^2.0.0: version "2.0.0" @@ -6298,13 +6325,20 @@ minimatch@3.0.4: dependencies: brace-expansion "^1.1.7" -minimatch@^3.0.4, minimatch@^3.1.1: +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" +minimatch@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" + integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== + dependencies: + brace-expansion "^2.0.1" + minimist-options@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" @@ -6443,9 +6477,9 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1: integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== multiformats@^9.4.2, multiformats@^9.5.4, multiformats@^9.6.4, multiformats@^9.7.1: - version "9.8.1" - resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.8.1.tgz#0e5f2910cf0c34f55adf0602f920775f9622552a" - integrity sha512-Cu7NfUYtCV+WN7w59WsRRF138S+um4tTo11ScYsWbNgWyCEGOu8wID1e5eMJs91gFZ0I7afodkkdxCF8NGkqZQ== + version "9.9.0" + resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37" + integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== multimatch@^5.0.0: version "5.0.0" @@ -6579,14 +6613,14 @@ node-releases@^2.0.6: integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== nodemon@^2.0.15, nodemon@^2.0.19: - version "2.0.19" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.19.tgz#cac175f74b9cb8b57e770d47841995eebe4488bd" - integrity sha512-4pv1f2bMDj0Eeg/MhGqxrtveeQ5/G/UVe9iO6uTZzjnRluSA4PVWf8CW99LUPwGB3eNIA7zUFoP77YuI7hOc0A== + version "2.0.20" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.20.tgz#e3537de768a492e8d74da5c5813cb0c7486fc701" + integrity sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw== dependencies: chokidar "^3.5.2" debug "^3.2.7" ignore-by-default "^1.0.1" - minimatch "^3.0.4" + minimatch "^3.1.2" pstree.remy "^1.1.8" semver "^5.7.1" simple-update-notifier "^1.0.7" @@ -7073,6 +7107,11 @@ parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +path-browserify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -7515,10 +7554,10 @@ reflect-metadata@^0.1.13: resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== -regenerate-unicode-properties@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56" - integrity sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw== +regenerate-unicode-properties@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" + integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ== dependencies: regenerate "^1.4.2" @@ -7554,26 +7593,26 @@ regexpp@^3.1.0: integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== regexpu-core@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.1.0.tgz#2f8504c3fd0ebe11215783a41541e21c79942c6d" - integrity sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA== + version "5.2.1" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.2.1.tgz#a69c26f324c1e962e9ffd0b88b055caba8089139" + integrity sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ== dependencies: regenerate "^1.4.2" - regenerate-unicode-properties "^10.0.1" - regjsgen "^0.6.0" - regjsparser "^0.8.2" + regenerate-unicode-properties "^10.1.0" + regjsgen "^0.7.1" + regjsparser "^0.9.1" unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.0.0" -regjsgen@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d" - integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA== +regjsgen@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.7.1.tgz#ee5ef30e18d3f09b7c369b76e7c2373ed25546f6" + integrity sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA== -regjsparser@^0.8.2: - version "0.8.4" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.8.4.tgz#8a14285ffcc5de78c5b95d62bbf413b6bc132d5f" - integrity sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA== +regjsparser@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" + integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== dependencies: jsesc "~0.5.0" @@ -8000,9 +8039,9 @@ sprintf-js@~1.0.2: integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== sqlite3@^5.0.11: - version "5.0.11" - resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.0.11.tgz#102c835d70be66da9d95a383fd6ea084a082ef7f" - integrity sha512-4akFOr7u9lJEeAWLJxmwiV43DJcGV7w3ab7SjQFAFaTVyknY3rZjvXTKIVtWqUoY4xwhjwoHKYs2HDW2SoHVsA== + version "5.1.1" + resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.1.1.tgz#c6561220fd875fd88eb2ef717631e0a11af1ec38" + integrity sha512-mMinkrQr/LKJqFiFF+AF7imPSzRCCpTCreusZO3D/ssJHVjZOrbu2Caz+zPH5KTmGGXBxXMGSRDssL+44CLxvg== dependencies: "@mapbox/node-pre-gyp" "^1.0.0" node-addon-api "^4.2.0" @@ -8401,6 +8440,14 @@ ts-jest@^28.0.5: semver "7.x" yargs-parser "^21.0.1" +ts-morph@^16.0.0: + version "16.0.0" + resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-16.0.0.tgz#35caca7c286dd70e09e5f72af47536bf3b6a27af" + integrity sha512-jGNF0GVpFj0orFw55LTsQxVYEUOCWBAbR5Ls7fTYE5pQsbW18ssTb/6UXx/GYAEjS+DQTp8VoTw0vqYMiaaQuw== + dependencies: + "@ts-morph/common" "~0.17.0" + code-block-writer "^11.0.3" + ts-node@^10.8.1, ts-node@^10.8.2: version "10.9.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" @@ -8527,9 +8574,9 @@ typedarray@^0.0.6: integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== typeorm@^0.3.7: - version "0.3.9" - resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.9.tgz#ad0f525d81c081fd11006f97030f47a55978ac81" - integrity sha512-xNcE44D4hn74n7pjuMog9hRgep+BiO3IBpjEaQZ8fb56zsDz7xHT1GAeWwmGuuU+4nDEELp2mIqgSCR+zxR7Jw== + version "0.3.10" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.10.tgz#aa2857fd4b078c912ca693b7eee01b6535704458" + integrity sha512-VMKiM84EpJQ+Mz9xDIPqnfplWhyUy1d8ccaKdMY9obifxJOTFnv8GYVyPsGwG8Lk7Nb8MlttHyHWENGAhBA3WA== dependencies: "@sqltools/formatter" "^1.2.2" app-root-path "^3.0.0" @@ -8555,9 +8602,9 @@ typescript@^4.3.2: integrity sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig== uglify-js@^3.1.4: - version "3.17.0" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.0.tgz#55bd6e9d19ce5eef0d5ad17cd1f587d85b180a85" - integrity sha512-aTeNPVmgIMPpm1cxXr2Q/nEbvkmV8yq66F3om7X3P/cvOXQ0TMQ64Wk63iyT1gPlmdmGzjGpyLh1f3y8MZWXGg== + version "3.17.1" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.1.tgz#1258a2a488147a8266b3034499ce6959978ba7f4" + integrity sha512-+juFBsLLw7AqMaqJ0GFvlsGZwdQfI2ooKQB39PSBgMnMakcFosi9O8jCwE+2/2nMNcc0z63r9mwjoDG8zr+q0Q== uid-number@0.0.6: version "0.0.6" @@ -8610,9 +8657,9 @@ unicode-match-property-value-ecmascript@^2.0.0: integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== unicode-property-aliases-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz#0a36cb9a585c4f6abd51ad1deddb285c165297c8" - integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ== + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" + integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== unique-filename@^1.1.1: version "1.1.1" @@ -8648,7 +8695,7 @@ upath@^2.0.1: resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== -update-browserslist-db@^1.0.5: +update-browserslist-db@^1.0.9: version "1.0.9" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz#2924d3927367a38d5c555413a7ce138fc95fcb18" integrity sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg== @@ -9002,6 +9049,11 @@ yargs@^17.3.1, yargs@^17.5.1: y18n "^5.0.5" yargs-parser "^21.0.0" +yesno@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/yesno/-/yesno-0.4.0.tgz#5d674f14d339f0bd4b0edc47f899612c74fcd895" + integrity sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA== + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"