Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"printWidth": 120,
"singleQuote": true,
"semi": true,
"trailingComma": "all",
"tabWidth": 2
}
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<X402PaymentPayload> {
Expand Down
3 changes: 3 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions examples/buckets.ts
Original file line number Diff line number Diff line change
@@ -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: {
Expand All @@ -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),
);
4 changes: 1 addition & 3 deletions examples/submit-and-wait.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
4 changes: 1 addition & 3 deletions examples/submit-query.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand All @@ -45,6 +48,7 @@
},
"devDependencies": {
"@types/bun": "^1.2.14",
"prettier": "^3.8.3",
"typescript": "^5.7.3"
}
}
43 changes: 30 additions & 13 deletions src/cli/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,30 @@ interface CliContext {
}

const commandHandlers: Record<string, CommandHandler> = {
'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 }) =>
Expand All @@ -44,7 +48,9 @@ const commandHandlers: Record<string, CommandHandler> = {
}),
'create-bucket-and-submit': ({ client, flags }) =>
createBucketAndSubmit(client, {
bucket: { aggregatorVersion: requiredString(flags, 'aggregator-version') },
bucket: {
aggregatorVersion: requiredString(flags, 'aggregator-version'),
},
queries: [readSubmitInput(flags)],
}),
};
Expand All @@ -58,7 +64,11 @@ export async function runCli(argv: string[]): Promise<number> {

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;
}

Expand All @@ -76,7 +86,10 @@ export async function runCli(argv: string[]): Promise<number> {
}
}

export function parseArgs(argv: string[]): { flags: Record<string, string | boolean>; positionals: string[] } {
export function parseArgs(argv: string[]): {
flags: Record<string, string | boolean>;
positionals: string[];
} {
const flags: Record<string, string | boolean> = {};
const positionals: string[] = [];

Expand Down Expand Up @@ -138,7 +151,11 @@ function readPagination(flags: Record<string, string | boolean | undefined>) {
return pagination;
}

function requiredId(flags: Record<string, string | boolean | undefined>, positionals: string[], flagName: string): string {
function requiredId(
flags: Record<string, string | boolean | undefined>,
positionals: string[],
flagName: string,
): string {
return optionalString(flags, flagName) ?? positionals[0] ?? fail(`Missing required ${flagName}`);
}

Expand Down
7 changes: 1 addition & 6 deletions src/client/multipart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
5 changes: 4 additions & 1 deletion test/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion test/docs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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['"]/);
}
});
});
9 changes: 6 additions & 3 deletions test/http.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
14 changes: 11 additions & 3 deletions test/workflows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
});
Expand All @@ -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');
Expand Down
28 changes: 23 additions & 5 deletions test/x402.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand All @@ -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: {
Expand Down Expand Up @@ -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: {
Expand All @@ -110,6 +126,8 @@ describe('x402 payments', () => {
},
},
),
).rejects.toMatchObject({ code: 'WALLET_FLOW_DEDUP_ID_NOT_SUPPORTED' } satisfies Partial<AtlanticSdkError>);
).rejects.toMatchObject({
code: 'WALLET_FLOW_DEDUP_ID_NOT_SUPPORTED',
} satisfies Partial<AtlanticSdkError>);
});
});
Loading