diff --git a/SUMMARY.md b/SUMMARY.md index 64a4b2d..f6de792 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -236,12 +236,11 @@ * [Getting Started](flipside-api/get-started/README.md) * [Python SDK](flipside-api/get-started/python.md) + * [JS/TS SDK](flipside-api/get-started/js-ts-sdk.md) * [API](flipside-api/get-started/rest-api.md) * [🚦 Rate Limits](flipside-api/get-started/rate-limits.md) - * [🏗 JS/TS SDK](flipside-api/get-started/js-ts-sdk.md) * [🏗 R SDK](flipside-api/get-started/r-sdk.md) * [Archive](flipside-api/get-started/archive/README.md) - * [\[LEGACY\] JavaScript / TypeScript](flipside-api/get-started/archive/javascript-typescript.md) * [\[LEGACY\] R](flipside-api/get-started/archive/r.md) * [Example Code](flipside-api/examples.md) * [Templates & Walkthroughs](flipside-api/templates-and-walkthroughs.md) diff --git a/flipside-api/get-started/README.md b/flipside-api/get-started/README.md index b7a3de0..b3db9bf 100644 --- a/flipside-api/get-started/README.md +++ b/flipside-api/get-started/README.md @@ -14,11 +14,11 @@ Go to the [Flipside Data Studio](https://flipsidecrypto.xyz/account/api-keys) an ## 2. Choose your SDK -| Language | Version | Walkthrough Link | -| ---------------- | ----------- | ---------------------------------------- | -| ✅ Python | 1.0.2 | [Getting Started with Python](python.md) | -| 🏗 JS/TypeScript | Coming Soon | | -| 🏗 R | Coming Soon | | +| Language | Version | Walkthrough Link | +| --------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| ✅ Python | 2.0.4 | [Getting Started with Python](python.md) | +| ✅ JS/TypeScript | 2.0.0 | [Getting Started with JS/TS](https://app.gitbook.com/o/-LdEn7uFmFX9w2zbU4Eu/s/-LdEnDLYh6Su5z7LbnEZ/\~/changes/420/flipside-api/get-started/js) | +| 🏗 R | Coming Soon | | Want an SDK for another language? Or want to create your own SDK? Please reach out in [Discord](https://discord.gg/ZmU3jQuu6W)! @@ -34,7 +34,7 @@ _**For legacy ShroomDK users:** in May of 2023 Flipside released V2 of its API t Explore our data in the [Flipside Data Studio](https://flipsidecrypto.xyz), check out SDK [code examples](../examples.md), and see what people in our community are [creating](../community-showcase.md). -## 4. Connect & create +## 4. Connect & Create Introduce yourself in the SDK [Discord](https://discord.gg/ZmU3jQuu6W) channels, ask questions, and share what you're working on.: diff --git a/flipside-api/get-started/archive/javascript-typescript.md b/flipside-api/get-started/archive/javascript-typescript.md deleted file mode 100644 index a618a5f..0000000 --- a/flipside-api/get-started/archive/javascript-typescript.md +++ /dev/null @@ -1,188 +0,0 @@ -# \[LEGACY] JavaScript / TypeScript - -{% hint style="danger" %} -_The JS/TS SDK is currently undergoing an upgrade to be compatible with V2 of Flipside's API. Until that update rolls out, the JS/TS SDK can only be used by legacy ShroomDK users._ - -_**For Legacy ShroomDK users:** once the upgrade is complete you will be able to seamlessly upgrade to the latest version of the SDK without any changes to your existing code._ -{% endhint %} - -[![](https://github.com/flipsidecrypto/sdk/actions/workflows/ci\_js.yml/badge.svg)](https://github.com/flipsidecrypto/sdk/actions/workflows/ci\_js.yml/badge.svg) - -_**To skip the walkthrough and go straight to dedicated API Documentation,**_ [_**click here**_](https://api-docs.flipsidecrypto.xyz/)_**.**_ - -### 💾 Install the SDK - -```javascript -yarn add @flipsidecrypto/sdk -``` - -or if using npm - -```javascript -npm install @flipsidecrypto/sdk -``` - -### 🦾 Getting Started - -```typescript -import { Flipside, Query, QueryResultSet } from "@flipsidecrypto/sdk"; - -// Initialize `Flipside` with your API key -const flipside = new Flipside( - "", - "https://node-api.flipsidecrypto.com" -); - -// Parameters can be passed into SQL statements via simple & native string interpolation -const myAddress = "0x...."; - -// Create a query object for the `query.run` function to execute -const query: Query = { - sql: `select nft_address, mint_price_eth, mint_price_usd from flipside_prod_db.ethereum_core.ez_nft_mints where nft_to_address = LOWER('${myAddress}')`, - ttlMinutes: 10, -}; - -// Send the `Query` to Flipside's query engine and await the results -const result: QueryResultSet = await flipside.query.run(query); - -// Iterate over the results -result?.records?.forEach((record) => { - const nftAddress = record.nft_address - const mintPriceEth = record.mint_price_eth - const mintPriceUSD = record.mint_price_usd - console.log(`address ${nftAddress} minted at a price of ${mintPriceEth} ETH or $${mintPriceUSD} USD`); -}); -``` - -#### - -### The Details - -**The `Query` Object** - -The `Query` object contains both the sql and configuration you can send to the query engine for execution. - -```typescript -type Query = { - // SQL query to execute - sql: string; - - // The number of minutes to cache the query results - ttlMinutes?: number; - - // An override on the query result cahce. - // A value of false will re-execute the query. - cached?: boolean; - - // The number of minutes until your query run times out - timeoutMinutes?: number; - - // The number of rows to return from the query result set. - // If not specified this defaults to 100k. A max of 1m rows/records - // will be cached and can be paged thru using the pageNumber parameter. - pageSize?: number; - - // The page to retrieve cached query results from. - // The default (if not specified) is 1. - pageNumber?: number; -}; -``` - -Let's create a query to retrieve all NFTs minted by an address: - -```typescript -const yourAddress = ""; - -const query: Query = { - sql: `select nft_address, mint_price_eth, mint_price_usd from flipside_prod_db.ethereum_core.ez_nft_mints where nft_to_address = LOWER('${myAddress}')`, - ttlMinutes: 60, - cached: true, - timeoutMinutes: 15, - pageSize: 1000, - pageNumber: 1 -}; -``` - -Now let's execute the query and retrieve the results. - -``` -const result: QueryResultSet = await flipside.query.run(query); -``` - -The results of this query will be cached for 60 minutes, given the `ttlMinutes` parameter. - - - -**The `QueryResultSet` Object** - -After executing a query the results are stored in a `QueryResultSet` object. - -```typescript -interface QueryResultSet { - // The server id of the query - queryId: string | null; - - // The status of the query (`PENDING`, `FINISHED`, `ERROR`) - status: QueryStatus | null; - - // The names of the columns in the result set - columns: string[] | null; - - // The type of the columns in the result set - columnTypes: string[] | null; - - // The results of the query - rows: Row[] | null; - - // Summary stats on the query run (i.e. the number of rows returned, the elapsed time, etc) - runStats: QueryRunStats | null; - - // The results of the query transformed as an array of objects - records: QueryResultRecord[] | null; - - // If the query failed, this will contain the error - error: - | QueryRunRateLimitError - | QueryRunTimeoutError - | QueryRunExecutionError - | ServerError - | UserError - | UnexpectedSDKError - | null; -} -``` - -Let's iterate over the results from our query above.\ -\ -Our query selected `nft_address`, `mint_price_eth`, and `mint_price_usd`. We can access the returned data via the `records` parameter. The column names in our query are assigned as keys in each record object. - -``` -result?.records?.forEach((record) => { - const nftAddress = record.nft_address - const mintPriceEth = record.mint_price_eth - const mintPriceUSD = record.mint_price_usd - console.log(`address ${nftAddress} minted at a price of ${mintPriceEth} ETH or $${mintPriceUSD} USD`); -}); -``` - - - -**Rate Limits** - -Every API key is subject to a rate limit over a moving 5-minute window, as well as an aggregate daily limit.\ -\ -If the limit is reached in a 5-minute period, the SDK will exponentially backoff and retry the query up to the `timeoutMinutes` parameter set on the `Query` object.\ -\ -This feature is quite useful if leveraging the SDK client-side and your web application sees a large spike in traffic. Rather than using up your daily limit all at once, requests will be smoothed out over the day.y\ -\ -Rate limits can be adjusted per key/use case. - - - -**Client-Side Request Requirements** - -All API Keys correspond to a list of hostnames. Client-side requests that do not originate from the corresponding hostname will fail. - -**Pagination** - -More details on how pagination works can be found here: diff --git a/flipside-api/get-started/js-ts-sdk.md b/flipside-api/get-started/js-ts-sdk.md index 7673558..2cf8e47 100644 --- a/flipside-api/get-started/js-ts-sdk.md +++ b/flipside-api/get-started/js-ts-sdk.md @@ -1,13 +1,360 @@ ---- -description: 🏗 Under Construction ---- +# JS/TS SDK -# 🏗 JS/TS SDK +![tests](https://github.com/flipsidecrypto/sdk/actions/workflows/ci\_js.yml/badge.svg) -{% hint style="info" %} -I_n May of 2023, Flipside released V2 of its API that enables faster, and more reliable querying that scales with your usage. We are currently upgrading the existing SDKs to be compatible with the V2 API. The Javascript/Typescript SDK is currently undergoing an upgrade and will be available soon._ +_**To skip the walkthrough and go straight to dedicated API Documentation,**_ [_**click here**_](https://api-docs.flipsidecrypto.xyz/)_**.**_ +### 💾 Install the SDK +```bash +yarn add @flipsidecrypto/sdk +``` -_**For Legacy ShroomDK users only:** in the meantime, you can still access the existing JS/TS SDK. Once the upgrade is complete you will be able to seamlessly upgrade to the latest version of the SDK without any changes to your existing code._ -{% endhint %} +or if using npm + +```bash +npm install @flipsidecrypto/sdk +``` + +### 🦾 Getting Started + +```typescript +import { Flipside, Query, QueryResultSet } from "@flipsidecrypto/sdk"; + +// Initialize `Flipside` with your API key +const flipside = new Flipside( + "", + "https://api-v2.flipsidecrypto.xyz" +); + +// Parameters can be passed into SQL statements via simple & native string interpolation +const myAddress = "0x...."; + +// Create a query object for the `query.run` function to execute +const query: Query = { + sql: `select nft_address, mint_price_eth, mint_price_usd from flipside_prod_db.ethereum_core.ez_nft_mints where nft_to_address = LOWER('${myAddress}')`, + maxAgeMinutes: 30, +}; + +// Send the `Query` to Flipside's query engine and await the results +const result: QueryResultSet = await flipside.query.run(query); + +// Iterate over the results +result.records.map((record) => { + const nftAddress = record.nft_address + const mintPriceEth = record.mint_price_eth + const mintPriceUSD = = record.mint_price_usd + console.log(`address ${nftAddress} minted at a price of ${mintPrice} ETH or $${mintPriceUSD} USD`); +}); +``` + +### The Details + +#### The `Query` Object + +The `Query` object contains both the sql and configuration you can send to the query engine for execution. + +```typescript +type Query = { + // SQL query to execute + sql: string; + + // The number of minutes you are willing to accept cached + // result up to. If set to 30, if cached results exist within + // the last 30 minutes the api will return them. + maxAgeMinutes?: number; + + // An override on the query result cahce. + // A value of false will re-execute the query and override + // maxAgeMinutes + cached?: boolean; + + // The number of minutes until your query run times out + timeoutMinutes?: number; + + // The number of records to return, defaults to 100000 + pageSize?: number; + + // The page number to return, defaults to 1 + pageNumber?: number; + + // The owner of the data source (defaults to 'flipside') + dataProvider?: string; + + // The data source to execute the query against (defaults to 'snowflake-default') + dataSource?: string; +}; +``` + +Let's create a query to retrieve all NFTs minted by an address: + +```typescript +const yourAddress = ""; + +const query: Query = { + sql: `select nft_address, mint_price_eth, mint_price_usd from flipside_prod_db.ethereum_core.ez_nft_mints where nft_to_address = LOWER('${myAddress}')`, + maxAgeMinutes: 5, + cached: true, + timeoutMinutes: 15, + pageNumber: 1, + pageSize: 10, +}; +``` + +Now let's execute the query and retrieve the results. + +```typescript +const result: QueryResultSet = await flipside.query.run(query); +``` + +The results of this query will be cached for 60 minutes, given the `ttlMinutes` parameter. + +#### The `QueryResultSet` Object + +After executing a query the results are stored in a `QueryResultSet` object. + +```typescript +interface QueryResultSet { + // The server id of the query + queryId: string | null; + + // The status of the query (`PENDING`, `FINISHED`, `ERROR`) + status: QueryStatus | null; + + // The names of the columns in the result set + columns: string[] | null; + + // The type of the columns in the result set + columnTypes: string[] | null; + + // The results of the query + rows: any[] | null; + + // Summary stats on the query run (i.e. the number of rows returned, the elapsed time, etc) + runStats: QueryRunStats | null; + + // The results of the query transformed as an array of objects + records: QueryResultRecord[] | null; + + // The page of results + page: PageStats | null; + + // If the query failed, this will contain the error + error: + | ApiError + | QueryRunRateLimitError + | QueryRunTimeoutError + | QueryRunExecutionError + | ServerError + | UserError + | UnexpectedSDKError + | null; +} +``` + +Let's iterate over the results from our query above.\ +\ +Our query selected `nft_address`, `mint_price_eth`, and `mint_price_usd`. We can access the returned data via the `records` parameter. The column names in our query are assigned as keys in each record object. + +```typescript +result.records.map((record) => { + const nftAddress = record.nft_address; + const mintPriceEth = record.mint_price_eth; + const mintPriceUSD = record.mint_price_usd; + console.log( + `address ${nftAddress} minted at a price of ${mintPriceEth} ETH or $${mintPriceUSD} USD` + ); +}); +``` + +#### Pagination + +To page over the results use the `getQueryResults` method. + +```typescript +// what page are we starting on? +let currentPageNumber = 1 + +// How many records do we want to return in the page? +let pageSize = 1000 + +// set total pages to 1 higher than the `currentPageNumber` until +// we receive the total pages from `getQueryResults` given the +// provided `pageSize` (totalPages is dynamically determined by the API +// based on the `pageSize` you provide) +let totalPages = 2 + +// we'll store all the page results in `allRows` +let allRows = [] + +while (currentPageNumber <= totalPages) { + results = await flipside.query.getQueryResults({ + queryRunId: result.queryId, + pageNumber: currentPageNumber, + pageSize: pageSize + }) + totalPages = results.page.totalPages + allRows = [...allRows, ...results.records] + currentPageNumber += 1 +} + +``` + +#### Sort the Results + +Let's fetch the results sorted in descending order by `mint_price_usd`. + +```typescript +results = await flipside.query.getQueryResults({ + queryRunId: result.queryId, + pageNumber: 1, + pageSize: 1000, + sortBy: [ + { + column: 'mint_price_usd', + direction: 'desc' + } + ] +}) +``` + +Valid directions include `desc` and `asc`. You may also sortBy multiple columns. The order you provide the sortBy objects determine which sortBy object takes precedence. + +The following example will first sort results in descending order by `mint_price_usd` and then in ascending order by `nft_address`. + +```typescript +results = await flipside.query.getQueryResults({ + queryRunId: result.queryId, + pageNumber: 1, + pageSize: 1000, + sortBy: [ + { + column: 'mint_price_usd', + direction: 'desc' + }, + { + column: 'nft_address', + direction: 'asc' + } + ] +}) +``` + +For reference here is the `SortBy` type: + +```typescript +interface SortBy { + column: string; + direction: "desc" | "asc"; +} + +``` + +#### Filter the results + +Now let's filter the results where `mint_price_usd` is greater than $10 + +```typescript +results = await flipside.query.getQueryResults({ + queryRunId: result.queryId, + pageNumber: 1, + pageSize: 1000, + filters: [ + { + gt: 10, + column: 'mint_price_usd' + } + ] +}) +``` + +Filters can be applied for: equals, not equals, greater than, greater than or equals to, less than, less than or equals to, like, in, not in. All filters are executed server side over the entire result set. + +Here is the Filter type: + +```typescript +interface Filter { + column: string; + eq?: string | number | null; + neq?: string | number | null; + gt?: number | null; + gte?: number | null; + lt?: number | null; + lte?: number | null; + like?: string | number | null; + in?: any[] | null; + notIn?: any[] | null; +} +``` + +#### Understanding MaxAgeMinutes (and caching of results) + +The parameter `maxAgeMinutes` can be used to control whether a query will re-execute or return cached results. Let's talk thru an example. + +Set `maxAgeMinutes` to 30: + +```typescript +const query: Query = { + sql: `select nft_address, mint_price_eth, mint_price_usd from flipside_prod_db.ethereum_core.ez_nft_mints where nft_to_address = LOWER('${myAddress}')`, + maxAgeMinutes: 30 +}; +``` + +Behind the scenes the Flipside API will hash the sql text and using that hash determine if results exist that were recorded within the last 30 minutes. If no results exist, or the results that exist are more than 30 minutes old the query will re-execute. + +If you would like to force a cache bust and re-execute the query. You have two options, either set `maxAgeMinutes` to 0 or pass in `cache=false`. Setting `cache` to false effectively sets `maxAgeMinutes` to 0. + +```typescript +const query: Query = { + sql: `select nft_address, mint_price_eth, mint_price_usd from flipside_prod_db.ethereum_core.ez_nft_mints where nft_to_address = LOWER('${myAddress}')`, + maxAgeMinutes: 0 +}; + +// or: +const query: Query = { + sql: `select nft_address, mint_price_eth, mint_price_usd from flipside_prod_db.ethereum_core.ez_nft_mints where nft_to_address = LOWER('${myAddress}')`, + maxAgeMinutes: 30, + cache: false +}; +``` + +#### Understanding Query Seconds + +You can determine how many execution seconds your query took by looking at the `runStats` object on the `QueryResultSet`. + +```typescript +const runStats = result.runStats +``` + +There are a number of stats returned: + +```typescript +type QueryRunStats = { + startedAt: Date; + endedAt: Date; + elapsedSeconds: number; + queryExecStartedAt: Date; + queryExecEndedAt: Date; + streamingStartedAt: Date; + streamingEndedAt: Date; + queuedSeconds: number; + streamingSeconds: number; + queryExecSeconds: number; + bytes: number; // the number of bytes returned by the query + recordCount: number; +}; +``` + +Your account is only debited for `queryExecSeconds`. This is the number of computational seconds your query executes against Flipside's data warehouse. + +```typescript +const execSeconds = runStats.queryExecSeconds +``` + +You are only debited when the query is executed. So if you set `maxAgeMinutes` to a value greater than 0, and the query does not re-execute then you will only be charged for the time it executes. + +Flipside does NOT charge for the number of bytes/records returned. + +#### Client Side Request Requirements + +All API Keys correspond to a list of hostnames. Client-side requests that do not originate from the corresponding hostname will fail. You may configure hostnames [here](https://flipsidecrypto.xyz/account/api-keys). diff --git a/flipside-api/get-started/python.md b/flipside-api/get-started/python.md index 4da9199..1db5781 100644 --- a/flipside-api/get-started/python.md +++ b/flipside-api/get-started/python.md @@ -62,7 +62,6 @@ When executing a query the following parameters can be passed into the `query` m | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | | sql | The sql string to execute | None (required) | | max\_age\_minutes | The the max age of results you are willing to accept before a query is re-executed. For example: if set to 15, you are willing to accept cached results that were generated within the last 15 minutes, if not a query execution is triggered. A value of 0 will always trigger a query execution. | 0 | -| ttl\_minutes | The number of minutes to save the results of your query. | 30 | | timeout\_minutes | The number of minutes until your query run times out | 20 | | retry\_interval\_seconds | The number of seconds to wait between polls to the server | 1 | | page\_size | The number of rows/records to return | 100,000 | @@ -88,7 +87,6 @@ Now let's execute the query and retrieve the first 5 rows of the result set. Not ```python query_result_set = sdk.query( sql, - ttl_minutes=60, max_age_minutes=0, timeout_minutes=20, retry_interval_seconds=1, @@ -97,7 +95,7 @@ query_result_set = sdk.query( ) ``` -**Caching (max\_age\_minutes)** +**Understanding MaxAgeMinutes, aka Caching** The results of this query will be saved for 60 minutes, given the `ttl_minutes` the parameter is set to 60. @@ -167,8 +165,6 @@ record_count = query_result_set.run_stats.record_count print(f"This query took ${elapsed_seconds} seconds to run and returned {record_count} records from the database.") ``` - - ### 🙈 Error Handling The SDK implements the following errors that can be handled when calling the `query` method: @@ -246,8 +242,6 @@ except ApiError as e: print(f"an api error has occurred: {e.message}") ``` - - **SDK Error** `SDKError` - this error is raised when a generic client-side error occurs that cannot be accounted for by the other errors. SDK level errors should be reported [here](https://github.com/FlipsideCrypto/sdk/issues) as a Github Issue with a full stack-trace and detailed steps to reproduce.