diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5ecbc49 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,31 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + +jobs: + checks: + name: Test, lint, and format + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Run tests + run: bun run test + + - name: Run lint + run: bun run lint + + - name: Check formatting + run: bun run format:check diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..78c88af --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "printWidth": 120, + "singleQuote": true, + "semi": true, + "trailingComma": "all", + "tabWidth": 2 +} diff --git a/README.md b/README.md index a439d5e..15db85f 100644 --- a/README.md +++ b/README.md @@ -144,11 +144,7 @@ The adapter reads the x402 requirement returned by Atlantic, verifies that `acce Use a custom adapter when signing should happen through another wallet, custody service, browser wallet, or agent wallet. ```ts -import { - AtlanticClient, - type X402PaymentAdapter, - type X402PaymentPayload, -} from '@herodotus_dev/atlantic-sdk'; +import { AtlanticClient, type X402PaymentAdapter, type X402PaymentPayload } from '@herodotus_dev/atlantic-sdk'; const paymentAdapter: X402PaymentAdapter = { async createPayment({ requirement }): Promise { diff --git a/bun.lock b/bun.lock index 957fa50..989033e 100644 --- a/bun.lock +++ b/bun.lock @@ -9,6 +9,7 @@ }, "devDependencies": { "@types/bun": "^1.2.14", + "prettier": "^3.8.3", "typescript": "^5.7.3", }, }, @@ -42,6 +43,8 @@ "ox": ["ox@0.14.20", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.2.3", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-rby38C3nDn8eQkf29Zgw4hkCZJ64Qqi0zRPWL8ENUQ7JVuoITqrVtwWQgM/He19SCMUEc7hS/Sjw0jIOSLJhOw=="], + "prettier": ["prettier@3.8.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], diff --git a/examples/buckets.ts b/examples/buckets.ts index 219e5de..d66d8b2 100644 --- a/examples/buckets.ts +++ b/examples/buckets.ts @@ -1,8 +1,6 @@ import { AtlanticClient, createBucketAndSubmit } from '../src'; -const client = new AtlanticClient( - process.env.ATLANTIC_API_KEY ? { apiKey: process.env.ATLANTIC_API_KEY } : {}, -); +const client = new AtlanticClient(process.env.ATLANTIC_API_KEY ? { apiKey: process.env.ATLANTIC_API_KEY } : {}); const result = await createBucketAndSubmit(client, { bucket: { @@ -20,4 +18,7 @@ const result = await createBucketAndSubmit(client, { ], }); -console.log(result.bucket.atlanticBucket.id, result.submissions.map((submission) => submission.atlanticQueryId)); +console.log( + result.bucket.atlanticBucket.id, + result.submissions.map((submission) => submission.atlanticQueryId), +); diff --git a/examples/submit-and-wait.ts b/examples/submit-and-wait.ts index a12c163..a72cc4e 100644 --- a/examples/submit-and-wait.ts +++ b/examples/submit-and-wait.ts @@ -1,8 +1,6 @@ import { AtlanticClient, submitAndWait } from '../src'; -const client = new AtlanticClient( - process.env.ATLANTIC_API_KEY ? { apiKey: process.env.ATLANTIC_API_KEY } : {}, -); +const client = new AtlanticClient(process.env.ATLANTIC_API_KEY ? { apiKey: process.env.ATLANTIC_API_KEY } : {}); const result = await submitAndWait( client, diff --git a/examples/submit-query.ts b/examples/submit-query.ts index 81f5f95..bc87d24 100644 --- a/examples/submit-query.ts +++ b/examples/submit-query.ts @@ -1,8 +1,6 @@ import { AtlanticClient } from '../src'; -const client = new AtlanticClient( - process.env.ATLANTIC_API_KEY ? { apiKey: process.env.ATLANTIC_API_KEY } : {}, -); +const client = new AtlanticClient(process.env.ATLANTIC_API_KEY ? { apiKey: process.env.ATLANTIC_API_KEY } : {}); const result = await client.submitQuery({ declaredJobSize: 'S', diff --git a/package.json b/package.json index e786fe0..334da7b 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,10 @@ "scripts": { "build": "tsc", "test": "bun test", + "lint": "tsc -p tsconfig.check.json", "typecheck": "tsc -p tsconfig.check.json", + "format": "prettier --write .", + "format:check": "prettier --check .", "check": "bun test && tsc -p tsconfig.check.json", "prepublishOnly": "bun run check && bun run build" }, @@ -45,6 +48,7 @@ }, "devDependencies": { "@types/bun": "^1.2.14", + "prettier": "^3.8.3", "typescript": "^5.7.3" } } diff --git a/src/cli/commands.ts b/src/cli/commands.ts index 7cab9ec..1a86e77 100644 --- a/src/cli/commands.ts +++ b/src/cli/commands.ts @@ -14,26 +14,30 @@ interface CliContext { } const commandHandlers: Record = { - 'health': ({ client }) => client.healthCheck(), + health: ({ client }) => client.healthCheck(), 'submit-query': ({ client, flags }) => client.submitQuery(readSubmitInput(flags)), 'submit-and-wait': ({ client, flags }) => submitAndWait(client, readSubmitInput(flags), readWaitOptions(flags)), 'retry-query': ({ client, flags, positionals }) => client.retryQuery(requiredId(flags, positionals, 'query-id')), - 'retry-if-retriable': ({ client, flags, positionals }) => retryIfRetriable(client, requiredId(flags, positionals, 'query-id')), + 'retry-if-retriable': ({ client, flags, positionals }) => + retryIfRetriable(client, requiredId(flags, positionals, 'query-id')), 'get-query-details': ({ client, flags, positionals }) => client.getQuery(requiredId(flags, positionals, 'query-id')), 'get-query-by-dedup-id': ({ client, flags, positionals }) => client.getQueryByDedupId(requiredId(flags, positionals, 'dedup-id')), 'get-my-queries': ({ client, flags }) => client.listQueries(readPagination(flags)), 'get-query-jobs': ({ client, flags, positionals }) => client.getQueryJobs(requiredId(flags, positionals, 'query-id')), - 'get-query-with-jobs': ({ client, flags, positionals }) => getQueryWithJobs(client, requiredId(flags, positionals, 'query-id')), + 'get-query-with-jobs': ({ client, flags, positionals }) => + getQueryWithJobs(client, requiredId(flags, positionals, 'query-id')), 'get-query-stats': ({ client }) => client.getQueryStats(), 'list-buckets': ({ client, flags }) => client.listBuckets(readPagination(flags)), 'create-bucket': ({ client, flags }) => - client.createBucket(compact({ - aggregatorVersion: requiredString(flags, 'aggregator-version'), - externalId: optionalString(flags, 'external-id') ?? null, - nodeWidth: optionalNumber(flags, 'node-width') ?? null, - mockProof: optionalBoolean(flags, 'mock-proof') ?? null, - })), + client.createBucket( + compact({ + aggregatorVersion: requiredString(flags, 'aggregator-version'), + externalId: optionalString(flags, 'external-id') ?? null, + nodeWidth: optionalNumber(flags, 'node-width') ?? null, + mockProof: optionalBoolean(flags, 'mock-proof') ?? null, + }), + ), 'get-bucket': ({ client, flags, positionals }) => client.getBucket(requiredId(flags, positionals, 'bucket-id')), 'close-bucket': ({ client, flags, positionals }) => client.closeBucket(requiredId(flags, positionals, 'bucket-id')), 'submit-to-bucket': ({ client, flags }) => @@ -44,7 +48,9 @@ const commandHandlers: Record = { }), 'create-bucket-and-submit': ({ client, flags }) => createBucketAndSubmit(client, { - bucket: { aggregatorVersion: requiredString(flags, 'aggregator-version') }, + bucket: { + aggregatorVersion: requiredString(flags, 'aggregator-version'), + }, queries: [readSubmitInput(flags)], }), }; @@ -58,7 +64,11 @@ export async function runCli(argv: string[]): Promise { const handler = commandHandlers[command]; if (!handler) { - printJson({ ok: false, error: { message: `Unknown command: ${command}` }, commands: Object.keys(commandHandlers).sort() }); + printJson({ + ok: false, + error: { message: `Unknown command: ${command}` }, + commands: Object.keys(commandHandlers).sort(), + }); return 1; } @@ -76,7 +86,10 @@ export async function runCli(argv: string[]): Promise { } } -export function parseArgs(argv: string[]): { flags: Record; positionals: string[] } { +export function parseArgs(argv: string[]): { + flags: Record; + positionals: string[]; +} { const flags: Record = {}; const positionals: string[] = []; @@ -138,7 +151,11 @@ function readPagination(flags: Record) { return pagination; } -function requiredId(flags: Record, positionals: string[], flagName: string): string { +function requiredId( + flags: Record, + positionals: string[], + flagName: string, +): string { return optionalString(flags, flagName) ?? positionals[0] ?? fail(`Missing required ${flagName}`); } diff --git a/src/client/multipart.ts b/src/client/multipart.ts index 8f52206..2c4de8a 100644 --- a/src/client/multipart.ts +++ b/src/client/multipart.ts @@ -47,10 +47,5 @@ async function toMultipartValue(value: AtlanticFileValue | AtlanticFileReference } function isFileReference(value: unknown): value is AtlanticFileReference { - return ( - !!value && - typeof value === 'object' && - 'bucketKey' in value && - 'metadataType' in value - ); + return !!value && typeof value === 'object' && 'bucketKey' in value && 'metadataType' in value; } diff --git a/test/client.test.ts b/test/client.test.ts index a3d8d7c..ecca801 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -58,7 +58,10 @@ function responseFor(request: Request): unknown { if (pathname === '/is-alive') return { alive: true }; if (pathname === '/atlantic-query-jobs/query-1') return { jobs: [], steps: [] }; if (pathname === '/atlantic-queries/query-1/retry') { - return { atlanticQuery: query('query-1'), message: 'Query retry initiated' }; + return { + atlanticQuery: query('query-1'), + message: 'Query retry initiated', + }; } if (pathname === '/buckets') { return request.method === 'POST' diff --git a/test/docs.test.ts b/test/docs.test.ts index b605f98..f333f0b 100644 --- a/test/docs.test.ts +++ b/test/docs.test.ts @@ -17,7 +17,7 @@ describe('documentation surface', () => { ]; for (const path of examplePaths) { - expect(await Bun.file(path).text()).toContain("from '../src'"); + expect(await Bun.file(path).text()).toMatch(/from ['"]\.\.\/src['"]/); } }); }); diff --git a/test/http.test.ts b/test/http.test.ts index 43e0960..3ce158c 100644 --- a/test/http.test.ts +++ b/test/http.test.ts @@ -5,9 +5,12 @@ import { AtlanticSdkError } from '../src/errors'; describe('HTTP transport', () => { test('builds URLs with query parameters', () => { - expect(buildUrl('https://example.com', '/atlantic-queries', { limit: 10, offset: 0 })).toBe( - 'https://example.com/atlantic-queries?limit=10&offset=0', - ); + expect( + buildUrl('https://example.com', '/atlantic-queries', { + limit: 10, + offset: 0, + }), + ).toBe('https://example.com/atlantic-queries?limit=10&offset=0'); }); test('adds api-key header when configured', async () => { diff --git a/test/workflows.test.ts b/test/workflows.test.ts index 64af135..0523d4b 100644 --- a/test/workflows.test.ts +++ b/test/workflows.test.ts @@ -10,7 +10,10 @@ describe('workflow helpers', () => { getQuery: async () => queryResult(statuses.shift() ?? 'DONE'), }); - const result = await waitForQuery(client, 'query-1', { intervalMs: 0, timeoutMs: 100 }); + const result = await waitForQuery(client, 'query-1', { + intervalMs: 0, + timeoutMs: 100, + }); expect(result.observedStatuses).toEqual(['IN_PROGRESS', 'DONE']); }); @@ -23,13 +26,18 @@ describe('workflow helpers', () => { }, }); - await expect(retryIfRetriable(client, 'query-1')).rejects.toMatchObject({ code: 'QUERY_NOT_IN_FAILED_STATUS' }); + await expect(retryIfRetriable(client, 'query-1')).rejects.toMatchObject({ + code: 'QUERY_NOT_IN_FAILED_STATUS', + }); }); test('fetches query with jobs', async () => { const client = fakeClient({ getQuery: async () => queryResult('DONE'), - getQueryJobs: async () => ({ jobs: [{ id: 'job-1' }], steps: ['PROOF_GENERATION'] }), + getQueryJobs: async () => ({ + jobs: [{ id: 'job-1' }], + steps: ['PROOF_GENERATION'], + }), }); const result = await getQueryWithJobs(client, 'query-1'); diff --git a/test/x402.test.ts b/test/x402.test.ts index 795ee8b..ec6467b 100644 --- a/test/x402.test.ts +++ b/test/x402.test.ts @@ -7,10 +7,16 @@ import type { X402PaymentRequirement } from '../src/types'; describe('x402 payments', () => { test('parses payment response header', () => { const headers = new Headers({ - 'PAYMENT-RESPONSE': encodeBase64Json({ x402Version: 2, success: true, alreadyProcessed: true }), + 'PAYMENT-RESPONSE': encodeBase64Json({ + x402Version: 2, + success: true, + alreadyProcessed: true, + }), }); - expect(parsePaymentResponseHeader(headers)).toMatchObject({ alreadyProcessed: true }); + expect(parsePaymentResponseHeader(headers)).toMatchObject({ + alreadyProcessed: true, + }); }); test('handles challenge, payment retry, and settlement response', async () => { @@ -32,7 +38,13 @@ describe('x402 payments', () => { if (calls === 1) { return Promise.resolve( Response.json( - { paymentRequired: { x402Version: 2, accepts: [requirement], error: 'payment_required' } }, + { + paymentRequired: { + x402Version: 2, + accepts: [requirement], + error: 'payment_required', + }, + }, { status: 402, headers: { @@ -100,7 +112,11 @@ describe('x402 payments', () => { await expect( client.submitQuery( - { declaredJobSize: 'S', dedupId: 'dedup-1', pieFile: new Blob(['zip']) }, + { + declaredJobSize: 'S', + dedupId: 'dedup-1', + pieFile: new Blob(['zip']), + }, { anonymousPayment: true, paymentAdapter: { @@ -110,6 +126,8 @@ describe('x402 payments', () => { }, }, ), - ).rejects.toMatchObject({ code: 'WALLET_FLOW_DEDUP_ID_NOT_SUPPORTED' } satisfies Partial); + ).rejects.toMatchObject({ + code: 'WALLET_FLOW_DEDUP_ID_NOT_SUPPORTED', + } satisfies Partial); }); });