@thisismissem/adonisjs-atproto-tap 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,9 @@
1
+ # The MIT License
2
+
3
+ Copyright (c) 2023
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # AdonisJS AT Protocol Tap
2
+
3
+ This package provides a small [Adonis.js](https://adonisjs.com) provider and services for `@atproto/tap`, which allows interacting with [Tap](https://docs.bsky.app/blog/introducing-tap) for AT Protocol development.
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ node ace add @thisismissem/adonisjs-atproto-tap
9
+ ```
10
+
11
+ ### Configuring
12
+
13
+ If you didn't use `node ace add` you can later run the configuration using:
14
+
15
+ ```sh
16
+ node ace configure @thisismissem/adonisjs-atproto-tap
17
+ ```
18
+
19
+ ## Indexer
20
+
21
+ The Tap Indexer (`SimpleIndexer`) can be accessed using:
22
+
23
+ ```ts
24
+ import indexer from '@thisismissem/adonisjs-atproto-tap/services/indexer'
25
+ ```
26
+
27
+ If you've installed using the instructions above, you will have the file `start/indexer.ts` created, which is where you can add the logic to handle the events from Tap. The provider automatically connects the `indexer.error()` handler to the [Adonis.js logger](https://docs.adonisjs.com/guides/digging-deeper/logger).
28
+
29
+ You can find out more in the [`@atproto/tap` documentation](https://github.com/bluesky-social/atproto/blob/main/packages/tap/README.md)
30
+
31
+ ## Tap API
32
+
33
+ The Tap Client API for adding and removing repositories, resolving DIDs and such is accessible via:
34
+
35
+ ```ts
36
+ import tap from '@thisismissem/adonisjs-atproto-tap/services/tap'
37
+ ```
38
+
39
+ Which provides the following methods for interacting with the Tap server:
40
+
41
+ - `addRepos(dids: string[]): Promise<void>` - Add repos to track (triggers backfill)
42
+ - `removeRepos(dids: string[]): Promise<void>` - Stop tracking repos
43
+ - `resolveDid(did: string): Promise<DidDocument | null>` - Resolve a DID to its DID document
44
+ - `getRepoInfo(did: string): Promise<RepoInfo>` - Get info about a tracked repo
45
+
46
+ ## Docker Setup for Tap
47
+
48
+ To run Tap locally, you'll likely want a `docker-compose.yaml` file with the following contents:
49
+
50
+ ```yaml
51
+ services:
52
+ tap:
53
+ image: ghcr.io/bluesky-social/indigo/tap:latest
54
+ platform: linux/amd64
55
+ restart: unless-stopped
56
+ healthcheck:
57
+ test: ["CMD", "curl", "-f", "http://localhost:2480/health"]
58
+ interval: 2s
59
+ retries: 5
60
+ start_period: 10s
61
+ timeout: 10s
62
+ volumes:
63
+ - ./data/tap:/data
64
+ env_file: tap.env
65
+ environment:
66
+ TAP_BIND: :2480
67
+ ports:
68
+ - "127.0.0.1:2480:2480"
69
+ ```
70
+
71
+ The `tap.env` file looks like:
72
+
73
+ ```sh
74
+ TAP_SIGNAL_COLLECTION=fyi.questionable.actor.profile
75
+ TAP_COLLECTION_FILTERS=fyi.questionable.*
76
+ TAP_ADMIN_PASSWORD=admin-password
77
+ ```
78
+
79
+ For the full configuration see the [Tap documentation](https://github.com/bluesky-social/indigo/blob/main/cmd/tap/README.md). You cannot use `TAP_WEBHOOK_URL` with this package, since it depends on the WebSocket interface.
@@ -0,0 +1,2 @@
1
+ import type ConfigureCommand from '@adonisjs/core/commands/configure';
2
+ export declare function configure(command: ConfigureCommand): Promise<void>;
@@ -0,0 +1,51 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | Configure hook
4
+ |--------------------------------------------------------------------------
5
+ |
6
+ | The configure hook is called when someone runs "node ace configure <package>"
7
+ | command. You are free to perform any operations inside this function to
8
+ | configure the package.
9
+ |
10
+ | To make things easier, you have access to the underlying "ConfigureCommand"
11
+ | instance and you can use codemods to modify the source files.
12
+ |
13
+ */
14
+ import { stubsRoot } from './stubs/main.js';
15
+ export async function configure(command) {
16
+ /**
17
+ * Prompt when `install` or `--no-install` flags are
18
+ * not used
19
+ */
20
+ let shouldInstallPackages = command.parsedFlags.install;
21
+ if (shouldInstallPackages === undefined) {
22
+ shouldInstallPackages = await command.prompt.confirm('Do you want to install additional packages required by "@thisismissem/adonisjs-atproto-tap"?');
23
+ }
24
+ const codemods = await command.createCodemods();
25
+ const packagesToInstall = [{ name: '@atproto/tap', isDevDependency: false }];
26
+ if (shouldInstallPackages) {
27
+ await codemods.installPackages(packagesToInstall);
28
+ }
29
+ else {
30
+ await codemods.listPackagesToInstall(packagesToInstall);
31
+ }
32
+ // Publish config file
33
+ await codemods.makeUsingStub(stubsRoot, 'config/tap.stub', {});
34
+ await codemods.makeUsingStub(stubsRoot, 'start/indexer.stub', {});
35
+ // Add provider to rc file
36
+ await codemods.updateRcFile((rcFile) => {
37
+ rcFile.addProvider('@thisismissem/adonisjs-atproto-tap/provider');
38
+ rcFile.addPreloadFile('#start/indexer');
39
+ });
40
+ await codemods.defineEnvVariables({
41
+ TAP_URL: 'http://localhost:2480/',
42
+ TAP_ADMIN_PASSWORD: 'admin-password',
43
+ });
44
+ await codemods.defineEnvValidations({
45
+ variables: {
46
+ TAP_URL: `Env.schema.string({ format: 'url', tld: false, protocol: true })`,
47
+ TAP_ADMIN_PASSWORD: `Env.schema.string.optional()`,
48
+ },
49
+ leadingComment: 'Variables for configuring the AT Protocol Tap client',
50
+ });
51
+ }
@@ -0,0 +1,2 @@
1
+ export { configure } from './configure.js';
2
+ export { defineConfig } from './src/define_config.js';
package/build/index.js ADDED
@@ -0,0 +1,10 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | Package entrypoint
4
+ |--------------------------------------------------------------------------
5
+ |
6
+ | Export values from the package entrypoint as you see fit.
7
+ |
8
+ */
9
+ export { configure } from './configure.js';
10
+ export { defineConfig } from './src/define_config.js';
@@ -0,0 +1,19 @@
1
+ import { Tap, SimpleIndexer } from '@atproto/tap';
2
+ import type { ApplicationService } from '@adonisjs/core/types';
3
+ import TapApi from '../src/api.js';
4
+ declare module '@adonisjs/core/types' {
5
+ interface ContainerBindings {
6
+ 'tap.client': Tap;
7
+ 'tap.indexer': SimpleIndexer;
8
+ 'tap.api': TapApi;
9
+ }
10
+ }
11
+ export default class TapProvider {
12
+ protected app: ApplicationService;
13
+ private channel?;
14
+ constructor(app: ApplicationService);
15
+ register(): void;
16
+ boot(): Promise<void>;
17
+ start(): Promise<void>;
18
+ shutdown(): Promise<void>;
19
+ }
@@ -0,0 +1,48 @@
1
+ import { RuntimeException } from '@adonisjs/core/exceptions';
2
+ import { Tap, SimpleIndexer } from '@atproto/tap';
3
+ import TapApi from '../src/api.js';
4
+ export default class TapProvider {
5
+ app;
6
+ channel;
7
+ constructor(app) {
8
+ this.app = app;
9
+ }
10
+ register() {
11
+ this.app.container.singleton('tap.client', async () => {
12
+ const config = this.app.config.get('tap', {});
13
+ if (!config || !config.url) {
14
+ throw new RuntimeException('Invalid config exported from "config/tap.ts" file. Make sure to return an object with the url property defined');
15
+ }
16
+ // FIXME: Workaround for https://github.com/bluesky-social/atproto/issues/4476
17
+ if (config.url.endsWith('/')) {
18
+ config.url = config.url.slice(0, -1);
19
+ }
20
+ return new Tap(config.url, config.config ?? {});
21
+ });
22
+ this.app.container.singleton('tap.indexer', () => {
23
+ return new SimpleIndexer();
24
+ });
25
+ this.app.container.singleton('tap.api', async (resolver) => {
26
+ const client = await resolver.make('tap.client');
27
+ return new TapApi(client);
28
+ });
29
+ }
30
+ async boot() {
31
+ const logger = await this.app.container.make('logger');
32
+ const indexer = await this.app.container.make('tap.indexer');
33
+ indexer.error((err) => logger.error(err, 'Tap indexer error'));
34
+ }
35
+ async start() {
36
+ if (this.app.getEnvironment() === 'web') {
37
+ const tap = await this.app.container.make('tap.client');
38
+ const indexer = await this.app.container.make('tap.indexer');
39
+ this.channel = tap.channel(indexer);
40
+ this.channel.start();
41
+ }
42
+ }
43
+ async shutdown() {
44
+ if (this.channel) {
45
+ await this.channel.destroy();
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,3 @@
1
+ import { SimpleIndexer } from '@atproto/tap';
2
+ declare let indexer: SimpleIndexer;
3
+ export { indexer as default };
@@ -0,0 +1,15 @@
1
+ /*
2
+ * @adonisjs/transmit
3
+ *
4
+ * (c) AdonisJS
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ import app from '@adonisjs/core/services/app';
10
+ let indexer;
11
+ await app.booted(async () => {
12
+ console.log('Tap booted');
13
+ indexer = await app.container.make('tap.indexer');
14
+ });
15
+ export { indexer as default };
@@ -0,0 +1,3 @@
1
+ import type TapApi from '../src/api.js';
2
+ declare let tap: TapApi;
3
+ export { tap as default };
@@ -0,0 +1,6 @@
1
+ import app from '@adonisjs/core/services/app';
2
+ let tap;
3
+ await app.booted(async () => {
4
+ tap = await app.container.make('tap.api');
5
+ });
6
+ export { tap as default };
@@ -0,0 +1,32 @@
1
+ import { Tap as TapClient } from '@atproto/tap';
2
+ import { Tap } from './types.js';
3
+ export default class TapApi implements Tap {
4
+ protected client: TapClient;
5
+ constructor(client: TapClient);
6
+ addRepos(dids: string[]): Promise<void>;
7
+ removeRepos(dids: string[]): Promise<void>;
8
+ resolveDid(did: string): Promise<{
9
+ id: string;
10
+ alsoKnownAs?: string[] | undefined;
11
+ verificationMethod?: {
12
+ id: string;
13
+ type: string;
14
+ controller: string;
15
+ publicKeyMultibase?: string | undefined;
16
+ }[] | undefined;
17
+ service?: {
18
+ id: string;
19
+ type: string;
20
+ serviceEndpoint: string | Record<string, unknown>;
21
+ }[] | undefined;
22
+ } | null>;
23
+ getRepoInfo(did: string): Promise<{
24
+ did: string;
25
+ rev: string;
26
+ handle: string;
27
+ state: string;
28
+ records: number;
29
+ error?: string | undefined;
30
+ retries?: number | undefined;
31
+ }>;
32
+ }
@@ -0,0 +1,18 @@
1
+ export default class TapApi {
2
+ client;
3
+ constructor(client) {
4
+ this.client = client;
5
+ }
6
+ async addRepos(dids) {
7
+ return this.client.addRepos(dids);
8
+ }
9
+ async removeRepos(dids) {
10
+ return this.client.removeRepos(dids);
11
+ }
12
+ async resolveDid(did) {
13
+ return this.client.resolveDid(did);
14
+ }
15
+ async getRepoInfo(did) {
16
+ return this.client.getRepoInfo(did);
17
+ }
18
+ }
@@ -0,0 +1,2 @@
1
+ import type { Config, TapProviderConfig } from './types.js';
2
+ export declare function defineConfig<T extends Config>(config: T): TapProviderConfig;
@@ -0,0 +1,15 @@
1
+ export function defineConfig(config) {
2
+ const tapConfig = {
3
+ url: config.url,
4
+ config: {},
5
+ };
6
+ // Sensible default:
7
+ if (typeof tapConfig.url === 'undefined') {
8
+ config.url = 'http://localhost:2480/';
9
+ }
10
+ // Copy across the admin password if any:
11
+ if (config && typeof config.adminPassword === 'string') {
12
+ tapConfig.config.adminPassword = config.adminPassword;
13
+ }
14
+ return tapConfig;
15
+ }
@@ -0,0 +1,10 @@
1
+ import { TapConfig, Tap as TapClient } from '@atproto/tap';
2
+ export type Config = {
3
+ url: string;
4
+ adminPassword?: string;
5
+ };
6
+ export type TapProviderConfig = {
7
+ url: Config['url'];
8
+ config: TapConfig;
9
+ };
10
+ export type Tap = Pick<TapClient, 'addRepos' | 'removeRepos' | 'getRepoInfo' | 'resolveDid'>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ {{{
2
+ exports({ to: app.configPath('tap.ts') })
3
+ }}}
4
+ import { defineConfig } from '@thisismissem/adonisjs-atproto-tap'
5
+ import env from "#start/env"
6
+
7
+ export default defineConfig({
8
+ url: env.get('TAP_URL'),
9
+ config: {
10
+ adminPassword: env.get('TAP_ADMIN_PASSWORD'),
11
+ },
12
+ })
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Path to the root directory where the stubs are stored. We use
3
+ * this path within commands and the configure hook
4
+ */
5
+ export declare const stubsRoot: string;
@@ -0,0 +1,7 @@
1
+ import { dirname } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ /**
4
+ * Path to the root directory where the stubs are stored. We use
5
+ * this path within commands and the configure hook
6
+ */
7
+ export const stubsRoot = dirname(fileURLToPath(import.meta.url));
@@ -0,0 +1,21 @@
1
+ {{{
2
+ exports({ to: app.startPath('indexer.ts') })
3
+ }}}
4
+ {{#var identityEvt = '`${evt.did} updated identity: ${evt.handle} (${evt.status})`' }}
5
+ {{#var atUri = '`at://${evt.did}/${evt.collection}/${evt.rkey}`' }}
6
+ {{#var upsertEvt = '`${evt.action}: ${uri}`' }}
7
+ {{#var deleteEvt = '`deleted: ${uri}`' }}
8
+ import indexer from '@thisismissem/adonisjs-atproto-tap/services/indexer'
9
+
10
+ indexer.identity(async (evt) => {
11
+ console.log({{ identityEvt }})
12
+ })
13
+
14
+ indexer.record(async (evt) => {
15
+ const uri = {{ atUri }}
16
+ if (evt.action === 'create' || evt.action === 'update') {
17
+ console.log({{ upsertEvt }})
18
+ } else {
19
+ console.log({{ deleteEvt }})
20
+ }
21
+ })
package/package.json ADDED
@@ -0,0 +1,103 @@
1
+ {
2
+ "name": "@thisismissem/adonisjs-atproto-tap",
3
+ "description": "Adonis.js provider for AT Protocol Tap Client",
4
+ "version": "0.0.0",
5
+ "engines": {
6
+ "node": ">=20.6.0"
7
+ },
8
+ "type": "module",
9
+ "files": [
10
+ "build/src",
11
+ "build/providers",
12
+ "build/services",
13
+ "build/stubs",
14
+ "build/index.d.ts",
15
+ "build/index.js",
16
+ "build/configure.d.ts",
17
+ "build/configure.js"
18
+ ],
19
+ "exports": {
20
+ ".": "./build/index.js",
21
+ "./provider": "./build/providers/tap_provider.js",
22
+ "./services/tap": "./build/services/tap.js",
23
+ "./services/indexer": "./build/services/indexer.js",
24
+ "./types": "./build/src/types.js"
25
+ },
26
+ "keywords": [
27
+ "adonisjs",
28
+ "adonis",
29
+ "atproto",
30
+ "at protocol",
31
+ "tap"
32
+ ],
33
+ "author": "Emelia Smith",
34
+ "license": "MIT",
35
+ "contributors": [
36
+ {
37
+ "name": "Emelia Smith",
38
+ "url": "https://github.com/thisismissem"
39
+ }
40
+ ],
41
+ "repository": {
42
+ "url": "https://github.com/ThisIsMissEm/adonisjs-atproto-tap"
43
+ },
44
+ "devDependencies": {
45
+ "@adonisjs/assembler": "^7.8.2",
46
+ "@adonisjs/core": "^6.12.0",
47
+ "@adonisjs/eslint-config": "2.1.2",
48
+ "@adonisjs/prettier-config": "^1.4.0",
49
+ "@adonisjs/tsconfig": "^1.3.0",
50
+ "@arethetypeswrong/cli": "^0.18.2",
51
+ "@changesets/changelog-github": "^0.5.2",
52
+ "@changesets/cli": "^2.29.8",
53
+ "@japa/assert": "^4.2.0",
54
+ "@japa/runner": "^5.0.0",
55
+ "@swc/core": "^1.6.3",
56
+ "@types/node": "^22.19.3",
57
+ "c8": "^10.1.2",
58
+ "copyfiles": "^2.4.1",
59
+ "del-cli": "^7.0.0",
60
+ "eslint": "^9.15.0",
61
+ "np": "^10.0.6",
62
+ "prettier": "^3.3.2",
63
+ "ts-node-maintained": "^10.9.4",
64
+ "typescript": "^5.4.5",
65
+ "yalc": "1.0.0-pre.53"
66
+ },
67
+ "peerDependencies": {
68
+ "@adonisjs/core": "^6.2.0",
69
+ "@atproto/tap": "^0.0.2"
70
+ },
71
+ "publishConfig": {
72
+ "access": "public",
73
+ "tag": "latest"
74
+ },
75
+ "c8": {
76
+ "reporter": [
77
+ "text",
78
+ "html"
79
+ ],
80
+ "exclude": [
81
+ "tests/**"
82
+ ]
83
+ },
84
+ "prettier": "@adonisjs/prettier-config",
85
+ "scripts": {
86
+ "changeset": "changeset",
87
+ "clean": "del-cli build",
88
+ "copy:templates": "copyfiles \"stubs/**/*.stub\" build",
89
+ "typecheck": "tsc --noEmit",
90
+ "lint": "pnpm run typecheck && eslint .",
91
+ "format": "prettier --write .",
92
+ "format:write": "prettier --ignore-path .gitignore --write",
93
+ "quick:test": "node --import=./tsnode.esm.js --enable-source-maps bin/test.ts",
94
+ "pretest": "pnpm run lint",
95
+ "test": "c8 pnpm run quick:test",
96
+ "types:check": "attw --pack . --profile esm-only --profile node16 --ignore-rules=cjs-resolves-to-esm",
97
+ "prebuild": "pnpm run lint && pnpm run clean",
98
+ "build": "tsc",
99
+ "postbuild": "pnpm run copy:templates",
100
+ "version-packages": "changeset version && pnpm run format:write CHANGELOG.md && git add package.json CHANGELOG.md",
101
+ "release": "pnpm run build && changeset publish"
102
+ }
103
+ }