From 8cac267adda6aeb2e8a7169fe885b25c2e86f11e Mon Sep 17 00:00:00 2001 From: gmemez Date: Mon, 10 Feb 2025 21:34:22 +0700 Subject: [PATCH 1/3] amend --- docs/generateHtml.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/generateHtml.ts b/docs/generateHtml.ts index a9121df..3285b76 100644 --- a/docs/generateHtml.ts +++ b/docs/generateHtml.ts @@ -2,6 +2,7 @@ import fs from 'fs'; import path from 'path'; function generateHtml() { + const config = { repoAddress: 'https://github.com/raphjaph/ordapi', version: 'v0.0.5' From f852f132557ce7ff33338681d1342580a159161b Mon Sep 17 00:00:00 2001 From: gmemez Date: Mon, 10 Feb 2025 21:36:11 +0700 Subject: [PATCH 2/3] Add back POST endpoints --- src/api.ts | 2 + src/client.ts | 50 +++++++++++ src/test/integration/api.test.ts | 71 ++++++++++++++++ src/test/unit/schemas.test.ts | 140 +++++++++++++------------------ 4 files changed, 181 insertions(+), 82 deletions(-) diff --git a/src/api.ts b/src/api.ts index 7a45ed7..b5a53ca 100644 --- a/src/api.ts +++ b/src/api.ts @@ -12,9 +12,11 @@ const api = { getInscriptionInfo: (id: string) => `/inscription/${id}`, getChild: (id: string, child: number) => `/inscription/${id}/${child}`, getInscriptions: '/inscriptions', + getInscriptionsByIds: '/inscriptions', getInscriptionsByPage: (page: number) => `/inscriptions/${page}`, getInscriptionsByBlock: (height: number) => `/inscriptions/block/${height}`, getOutput: (outpoint: string) => `/output/${outpoint}`, + getOutputs: '/outputs', getOutputsByAddress: (address: string, type?: OutputType) => { const base = `/outputs/${address}`; return type ? `${base}?type=${type}` : base; diff --git a/src/client.ts b/src/client.ts index de0872e..d898761 100644 --- a/src/client.ts +++ b/src/client.ts @@ -91,6 +91,34 @@ export class OrdClient { return result.data; } + private async fetchPost( + endpoint: string, + payload: P, + schema: T, + ): Promise> { + const response = await fetch(`${this.baseUrl}${endpoint}`, { + method: 'POST', + headers: { + ...this.headers, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + + if (!response.ok) { + throw new Error(`API request failed: ${response.statusText}`); + } + + const data = await response.json(); + const result = schema.safeParse(data); + + if (!result.success) { + throw new Error(`Validation error: ${result.error.message}`); + } + + return result.data; + } + /** * Retrieves information about a specific address including its outputs, * inscriptions, and rune balances. @@ -234,6 +262,19 @@ export class OrdClient { return this.fetch(api.getInscriptions, InscriptionsResponseSchema); } + /** + * Retrieves information about multiple inscriptions by their IDs. + * + * @param {string[]} ids - Array of inscription IDs to fetch + */ + async getInscriptionsByIds(ids: string[]): Promise { + return this.fetchPost( + api.getInscriptionsByIds, + ids, + z.array(InscriptionInfoSchema), + ); + } + /** * Gets inscriptions for a specific page number in paginated results. * @@ -405,6 +446,15 @@ export class OrdClient { return this.fetch(api.getOutputAssets(outpoint), OutputAssetsSchema); } + /** + * Gets information about multiple UTXOs. + * + * @param {string[]} outpoints - Array of outpoints to fetch + */ + async getOutputs(outpoints: string[]): Promise { + return this.fetchPost(api.getOutputs, outpoints, z.array(OutputInfoSchema)); + } + /** * Gets all UTXOs for a specific address, optionally filtered by type. * diff --git a/src/test/integration/api.test.ts b/src/test/integration/api.test.ts index fcdb3a8..c2970a6 100644 --- a/src/test/integration/api.test.ts +++ b/src/test/integration/api.test.ts @@ -325,6 +325,43 @@ describe('API Integration Tests', () => { ); }); + describe('getInscriptionsByIds', () => { + test( + 'fetches multiple inscriptions successfully', + async () => { + const inscriptions = await client.getInscriptionsByIds([ + SAMPLE_INSCRIPTION_ID, + SAMPLE_CHILD_ID, + ]); + expect(Array.isArray(inscriptions)).toBe(true); + expect(inscriptions.length).toBe(2); + expect(inscriptions[0].id).toBe(SAMPLE_INSCRIPTION_ID); + expect(inscriptions[1].id).toBe(SAMPLE_CHILD_ID); + }, + TIMEOUT, + ); + + test( + 'handles server error', + async () => { + expect( + invalidClient.getInscriptionsByIds([SAMPLE_INSCRIPTION_ID]), + ).rejects.toThrow(); + }, + TIMEOUT, + ); + + test( + 'handles empty array', + async () => { + const inscriptions = await client.getInscriptionsByIds([]); + expect(Array.isArray(inscriptions)).toBe(true); + expect(inscriptions.length).toBe(0); + }, + TIMEOUT, + ); + }); + describe('getOutput', () => { test( 'fetches output successfully', @@ -354,6 +391,40 @@ describe('API Integration Tests', () => { ); }); + describe('getOutputs', () => { + test( + 'fetches multiple outputs successfully', + async () => { + const outpoints = [SAMPLE_OUTPOINT_A, SAMPLE_OUTPOINT_B]; + const outputs = await client.getOutputs(outpoints); + expect(Array.isArray(outputs)).toBe(true); + expect(outputs[0].outpoint).toBe(SAMPLE_OUTPOINT_A); + expect(outputs[1].outpoint).toBe(SAMPLE_OUTPOINT_B); + }, + TIMEOUT, + ); + + test( + 'handles empty array', + async () => { + const outputs = await client.getOutputs([]); + expect(Array.isArray(outputs)).toBe(true); + expect(outputs.length).toBe(0); + }, + TIMEOUT, + ); + + test( + 'handles server error', + async () => { + expect( + invalidClient.getOutputs([SAMPLE_OUTPOINT_A, SAMPLE_OUTPOINT_B]), + ).rejects.toThrow(); + }, + TIMEOUT, + ); + }); + describe('getOutputsByAddress', () => { test( 'fetches outputs by address successfully', diff --git a/src/test/unit/schemas.test.ts b/src/test/unit/schemas.test.ts index c350720..3bdd3a0 100644 --- a/src/test/unit/schemas.test.ts +++ b/src/test/unit/schemas.test.ts @@ -19,16 +19,8 @@ import { InscriptionRecursiveSchema, InscriptionsResponseSchema, } from '../../schemas/inscription'; -import { - OutputAssetsSchema, - OutputInfoSchema, - SatRangeSchema, -} from '../../schemas/output'; -import { - RuneBalanceSchema, - RuneInfoSchema, - RunesResponseSchema, -} from '../../schemas/rune'; +import { OutputAssetsSchema, OutputInfoSchema, SatRangeSchema } from '../../schemas/output'; +import { RuneBalanceSchema, RuneInfoSchema, RunesResponseSchema } from '../../schemas/rune'; import { SatInfoSchema } from '../../schemas/sat'; import { ServerStatusSchema } from '../../schemas/status'; import { @@ -112,21 +104,15 @@ describe('Schema Validation', () => { describe('TransactionHexSchema', () => { test('validates valid hex string', () => { - expect(TransactionHexSchema.safeParse('0123456789abcdef').success).toBe( - true, - ); + expect(TransactionHexSchema.safeParse("0123456789abcdef").success).toBe(true); }); - + test('rejects non-hex characters', () => { - expect( - TransactionHexSchema.safeParse('0123456789abcdefg').success, - ).toBe(false); + expect(TransactionHexSchema.safeParse("0123456789abcdefg").success).toBe(false); }); - + test('rejects uppercase hex', () => { - expect(TransactionHexSchema.safeParse('0123456789ABCDEF').success).toBe( - false, - ); + expect(TransactionHexSchema.safeParse("0123456789ABCDEF").success).toBe(false); }); }); @@ -136,42 +122,42 @@ describe('Schema Validation', () => { inscriptions: [], runes: {}, sat_ranges: [], - value: 1000, + value: 1000 }; expect(OutputAssetsSchema.safeParse(validAssets).success).toBe(true); }); - + test('validates null fields', () => { const validAssets = { inscriptions: null, runes: null, sat_ranges: null, - value: 1000, + value: 1000 }; expect(OutputAssetsSchema.safeParse(validAssets).success).toBe(true); }); - + test('rejects negative value', () => { const invalidAssets = { inscriptions: [], runes: {}, sat_ranges: [], - value: -1000, + value: -1000 }; expect(OutputAssetsSchema.safeParse(invalidAssets).success).toBe(false); }); }); - + describe('SatRangeSchema', () => { test('validates valid sat range', () => { expect(SatRangeSchema.safeParse([0, 1000]).success).toBe(true); }); - + test('rejects negative numbers', () => { expect(SatRangeSchema.safeParse([-1, 1000]).success).toBe(false); expect(SatRangeSchema.safeParse([0, -1000]).success).toBe(false); }); - + test('rejects wrong tuple length', () => { expect(SatRangeSchema.safeParse([0]).success).toBe(false); expect(SatRangeSchema.safeParse([0, 1000, 2000]).success).toBe(false); @@ -376,26 +362,24 @@ describe('Schema Validation', () => { describe('InscriptionRecursiveSchema', () => { test('validates valid recursive inscription', () => { const validInscription = { - charms: ['rare', 'uncommon'], - content_type: 'text/plain', + charms: ["rare", "uncommon"], + content_type: "text/plain", content_length: 100, delegate: null, fee: 1000, height: 1000, - id: 'abc123', + id: "abc123", number: 1, - output: 'txid:0', + output: "txid:0", sat: 1000, - satpoint: 'txid:0:0', + satpoint: "txid:0:0", timestamp: 1234567890, value: 1000, - address: 'bc1...', + address: "bc1..." }; - expect( - InscriptionRecursiveSchema.safeParse(validInscription).success, - ).toBe(true); + expect(InscriptionRecursiveSchema.safeParse(validInscription).success).toBe(true); }); - + test('validates null fields', () => { const validInscription = { charms: [], @@ -404,88 +388,80 @@ describe('Schema Validation', () => { delegate: null, fee: 1000, height: 1000, - id: 'abc123', + id: "abc123", number: 1, - output: 'txid:0', + output: "txid:0", sat: null, - satpoint: 'txid:0:0', + satpoint: "txid:0:0", timestamp: 1234567890, value: null, - address: null, + address: null }; - expect( - InscriptionRecursiveSchema.safeParse(validInscription).success, - ).toBe(true); + expect(InscriptionRecursiveSchema.safeParse(validInscription).success).toBe(true); }); }); - + describe('ChildInfoSchema', () => { test('validates valid child info', () => { const validChild = { - charms: ['rare'], + charms: ["rare"], fee: 1000, height: 1000, - id: 'abc123', + id: "abc123", number: 1, - output: 'txid:0', + output: "txid:0", sat: 1000, - satpoint: 'txid:0:0', - timestamp: 1234567890, + satpoint: "txid:0:0", + timestamp: 1234567890 }; expect(ChildInfoSchema.safeParse(validChild).success).toBe(true); }); - + test('rejects negative numbers', () => { const invalidChild = { - charms: ['rare'], + charms: ["rare"], fee: -1000, height: 1000, - id: 'abc123', + id: "abc123", number: 1, - output: 'txid:0', + output: "txid:0", sat: 1000, - satpoint: 'txid:0:0', - timestamp: 1234567890, + satpoint: "txid:0:0", + timestamp: 1234567890 }; expect(ChildInfoSchema.safeParse(invalidChild).success).toBe(false); }); }); - + describe('ChildrenInfoResponseSchema', () => { test('validates valid children info response', () => { const validResponse = { children: [], more: false, - page: 0, + page: 0 }; - expect( - ChildrenInfoResponseSchema.safeParse(validResponse).success, - ).toBe(true); + expect(ChildrenInfoResponseSchema.safeParse(validResponse).success).toBe(true); }); - + test('rejects negative page number', () => { const invalidResponse = { children: [], more: false, - page: -1, + page: -1 }; - expect( - ChildrenInfoResponseSchema.safeParse(invalidResponse).success, - ).toBe(false); + expect(ChildrenInfoResponseSchema.safeParse(invalidResponse).success).toBe(false); }); }); - + describe('InscriptionIDSchema', () => { test('validates valid inscription ID', () => { - expect(InscriptionIDSchema.safeParse({ id: 'abc123' }).success).toBe( - true, - ); + expect(InscriptionIDSchema.safeParse({ id: "abc123" }).success).toBe(true); }); - + test('validates null ID', () => { expect(InscriptionIDSchema.safeParse({ id: null }).success).toBe(true); }); - + test('rejects missing ID field', () => { expect(InscriptionIDSchema.safeParse({}).success).toBe(false); }); @@ -547,38 +523,38 @@ describe('Schema Validation', () => { const validBalance = { amount: 1000, divisibility: 8, - symbol: 'TEST•RUNE', + symbol: "TEST•RUNE" }; expect(RuneBalanceSchema.safeParse(validBalance).success).toBe(true); }); - + test('rejects negative amount', () => { const invalidBalance = { amount: -1000, divisibility: 8, - symbol: 'TEST•RUNE', + symbol: "TEST•RUNE" }; expect(RuneBalanceSchema.safeParse(invalidBalance).success).toBe(false); }); - + test('rejects negative divisibility', () => { const invalidBalance = { amount: 1000, divisibility: -8, - symbol: 'TEST•RUNE', + symbol: "TEST•RUNE" }; expect(RuneBalanceSchema.safeParse(invalidBalance).success).toBe(false); }); - + test('rejects missing required fields', () => { const invalidBalance = { amount: 1000, - symbol: 'TEST•RUNE', + symbol: "TEST•RUNE" }; expect(RuneBalanceSchema.safeParse(invalidBalance).success).toBe(false); }); }); - + describe('RuneInfoSchema', () => { test('validates valid rune', () => { const result = RuneInfoSchema.safeParse(SAMPLE_RUNE); From 98bb1c99b64269c4461f5595e7fda65f5ea1e63a Mon Sep 17 00:00:00 2001 From: gmemez Date: Mon, 10 Feb 2025 21:58:11 +0700 Subject: [PATCH 3/3] remove double quotes --- src/test/unit/schemas.test.ts | 140 ++++++++++++++++++++-------------- 1 file changed, 82 insertions(+), 58 deletions(-) diff --git a/src/test/unit/schemas.test.ts b/src/test/unit/schemas.test.ts index 3bdd3a0..c350720 100644 --- a/src/test/unit/schemas.test.ts +++ b/src/test/unit/schemas.test.ts @@ -19,8 +19,16 @@ import { InscriptionRecursiveSchema, InscriptionsResponseSchema, } from '../../schemas/inscription'; -import { OutputAssetsSchema, OutputInfoSchema, SatRangeSchema } from '../../schemas/output'; -import { RuneBalanceSchema, RuneInfoSchema, RunesResponseSchema } from '../../schemas/rune'; +import { + OutputAssetsSchema, + OutputInfoSchema, + SatRangeSchema, +} from '../../schemas/output'; +import { + RuneBalanceSchema, + RuneInfoSchema, + RunesResponseSchema, +} from '../../schemas/rune'; import { SatInfoSchema } from '../../schemas/sat'; import { ServerStatusSchema } from '../../schemas/status'; import { @@ -104,15 +112,21 @@ describe('Schema Validation', () => { describe('TransactionHexSchema', () => { test('validates valid hex string', () => { - expect(TransactionHexSchema.safeParse("0123456789abcdef").success).toBe(true); + expect(TransactionHexSchema.safeParse('0123456789abcdef').success).toBe( + true, + ); }); - + test('rejects non-hex characters', () => { - expect(TransactionHexSchema.safeParse("0123456789abcdefg").success).toBe(false); + expect( + TransactionHexSchema.safeParse('0123456789abcdefg').success, + ).toBe(false); }); - + test('rejects uppercase hex', () => { - expect(TransactionHexSchema.safeParse("0123456789ABCDEF").success).toBe(false); + expect(TransactionHexSchema.safeParse('0123456789ABCDEF').success).toBe( + false, + ); }); }); @@ -122,42 +136,42 @@ describe('Schema Validation', () => { inscriptions: [], runes: {}, sat_ranges: [], - value: 1000 + value: 1000, }; expect(OutputAssetsSchema.safeParse(validAssets).success).toBe(true); }); - + test('validates null fields', () => { const validAssets = { inscriptions: null, runes: null, sat_ranges: null, - value: 1000 + value: 1000, }; expect(OutputAssetsSchema.safeParse(validAssets).success).toBe(true); }); - + test('rejects negative value', () => { const invalidAssets = { inscriptions: [], runes: {}, sat_ranges: [], - value: -1000 + value: -1000, }; expect(OutputAssetsSchema.safeParse(invalidAssets).success).toBe(false); }); }); - + describe('SatRangeSchema', () => { test('validates valid sat range', () => { expect(SatRangeSchema.safeParse([0, 1000]).success).toBe(true); }); - + test('rejects negative numbers', () => { expect(SatRangeSchema.safeParse([-1, 1000]).success).toBe(false); expect(SatRangeSchema.safeParse([0, -1000]).success).toBe(false); }); - + test('rejects wrong tuple length', () => { expect(SatRangeSchema.safeParse([0]).success).toBe(false); expect(SatRangeSchema.safeParse([0, 1000, 2000]).success).toBe(false); @@ -362,24 +376,26 @@ describe('Schema Validation', () => { describe('InscriptionRecursiveSchema', () => { test('validates valid recursive inscription', () => { const validInscription = { - charms: ["rare", "uncommon"], - content_type: "text/plain", + charms: ['rare', 'uncommon'], + content_type: 'text/plain', content_length: 100, delegate: null, fee: 1000, height: 1000, - id: "abc123", + id: 'abc123', number: 1, - output: "txid:0", + output: 'txid:0', sat: 1000, - satpoint: "txid:0:0", + satpoint: 'txid:0:0', timestamp: 1234567890, value: 1000, - address: "bc1..." + address: 'bc1...', }; - expect(InscriptionRecursiveSchema.safeParse(validInscription).success).toBe(true); + expect( + InscriptionRecursiveSchema.safeParse(validInscription).success, + ).toBe(true); }); - + test('validates null fields', () => { const validInscription = { charms: [], @@ -388,80 +404,88 @@ describe('Schema Validation', () => { delegate: null, fee: 1000, height: 1000, - id: "abc123", + id: 'abc123', number: 1, - output: "txid:0", + output: 'txid:0', sat: null, - satpoint: "txid:0:0", + satpoint: 'txid:0:0', timestamp: 1234567890, value: null, - address: null + address: null, }; - expect(InscriptionRecursiveSchema.safeParse(validInscription).success).toBe(true); + expect( + InscriptionRecursiveSchema.safeParse(validInscription).success, + ).toBe(true); }); }); - + describe('ChildInfoSchema', () => { test('validates valid child info', () => { const validChild = { - charms: ["rare"], + charms: ['rare'], fee: 1000, height: 1000, - id: "abc123", + id: 'abc123', number: 1, - output: "txid:0", + output: 'txid:0', sat: 1000, - satpoint: "txid:0:0", - timestamp: 1234567890 + satpoint: 'txid:0:0', + timestamp: 1234567890, }; expect(ChildInfoSchema.safeParse(validChild).success).toBe(true); }); - + test('rejects negative numbers', () => { const invalidChild = { - charms: ["rare"], + charms: ['rare'], fee: -1000, height: 1000, - id: "abc123", + id: 'abc123', number: 1, - output: "txid:0", + output: 'txid:0', sat: 1000, - satpoint: "txid:0:0", - timestamp: 1234567890 + satpoint: 'txid:0:0', + timestamp: 1234567890, }; expect(ChildInfoSchema.safeParse(invalidChild).success).toBe(false); }); }); - + describe('ChildrenInfoResponseSchema', () => { test('validates valid children info response', () => { const validResponse = { children: [], more: false, - page: 0 + page: 0, }; - expect(ChildrenInfoResponseSchema.safeParse(validResponse).success).toBe(true); + expect( + ChildrenInfoResponseSchema.safeParse(validResponse).success, + ).toBe(true); }); - + test('rejects negative page number', () => { const invalidResponse = { children: [], more: false, - page: -1 + page: -1, }; - expect(ChildrenInfoResponseSchema.safeParse(invalidResponse).success).toBe(false); + expect( + ChildrenInfoResponseSchema.safeParse(invalidResponse).success, + ).toBe(false); }); }); - + describe('InscriptionIDSchema', () => { test('validates valid inscription ID', () => { - expect(InscriptionIDSchema.safeParse({ id: "abc123" }).success).toBe(true); + expect(InscriptionIDSchema.safeParse({ id: 'abc123' }).success).toBe( + true, + ); }); - + test('validates null ID', () => { expect(InscriptionIDSchema.safeParse({ id: null }).success).toBe(true); }); - + test('rejects missing ID field', () => { expect(InscriptionIDSchema.safeParse({}).success).toBe(false); }); @@ -523,38 +547,38 @@ describe('Schema Validation', () => { const validBalance = { amount: 1000, divisibility: 8, - symbol: "TEST•RUNE" + symbol: 'TEST•RUNE', }; expect(RuneBalanceSchema.safeParse(validBalance).success).toBe(true); }); - + test('rejects negative amount', () => { const invalidBalance = { amount: -1000, divisibility: 8, - symbol: "TEST•RUNE" + symbol: 'TEST•RUNE', }; expect(RuneBalanceSchema.safeParse(invalidBalance).success).toBe(false); }); - + test('rejects negative divisibility', () => { const invalidBalance = { amount: 1000, divisibility: -8, - symbol: "TEST•RUNE" + symbol: 'TEST•RUNE', }; expect(RuneBalanceSchema.safeParse(invalidBalance).success).toBe(false); }); - + test('rejects missing required fields', () => { const invalidBalance = { amount: 1000, - symbol: "TEST•RUNE" + symbol: 'TEST•RUNE', }; expect(RuneBalanceSchema.safeParse(invalidBalance).success).toBe(false); }); }); - + describe('RuneInfoSchema', () => { test('validates valid rune', () => { const result = RuneInfoSchema.safeParse(SAMPLE_RUNE);