{"id":14970704,"url":"https://github.com/tablelandnetwork/jeti","last_synced_at":"2025-10-26T13:31:13.527Z","repository":{"id":65495159,"uuid":"485942843","full_name":"tablelandnetwork/jeti","owner":"tablelandnetwork","description":"An IPFS extension for the Tableland SDK","archived":false,"fork":false,"pushed_at":"2024-09-23T14:24:02.000Z","size":2179,"stargazers_count":6,"open_issues_count":10,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-09-28T13:23:21.756Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tablelandnetwork.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-26T20:47:33.000Z","updated_at":"2024-07-26T17:17:33.000Z","dependencies_parsed_at":"2023-12-07T06:25:40.532Z","dependency_job_id":"0a84f05c-75ac-414d-8150-8a768e1bc425","html_url":"https://github.com/tablelandnetwork/jeti","commit_stats":{"total_commits":128,"total_committers":4,"mean_commits":32.0,"dds":0.3828125,"last_synced_commit":"fb67e9d84e04fb24501bfbdf0d6a09c156b1665d"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tablelandnetwork%2Fjeti","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tablelandnetwork%2Fjeti/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tablelandnetwork%2Fjeti/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tablelandnetwork%2Fjeti/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tablelandnetwork","download_url":"https://codeload.github.com/tablelandnetwork/jeti/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":219862887,"owners_count":16555951,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-09-24T13:44:00.846Z","updated_at":"2025-10-26T13:31:13.149Z","avatar_url":"https://github.com/tablelandnetwork.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @tableland/jeti\n\n[![Review](https://github.com/tablelandnetwork/jeti/actions/workflows/review.yml/badge.svg)](https://github.com/tablelandnetwork/jeti/actions/workflows/review.yml)\n[![Test](https://github.com/tablelandnetwork/jeti/actions/workflows/test.yml/badge.svg)](https://github.com/tablelandnetwork/jeti/actions/workflows/test.yml)\n[![License: MIT AND Apache-2.0](https://img.shields.io/badge/License-MIT%20AND%20Apache--2.0-blue.svg)](./LICENSE)\n[![Version](https://img.shields.io/github/v/release/tablelandnetwork/jeti)](./package.json)\n[![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg)](https://github.com/RichardLitt/standard-readme)\n\n\u003e An IPFS and generic plugin framework for inserting \u0026 retrieving data with the @tableland/sdk\n\n## Table of Contents\n\n- [Background](#background)\n- [Install](#install)\n- [Usage](#usage)\n  - [Set up Tableland database](#set-up-tableland-database)\n  - [Pin to IPFS](#pin-to-ipfs)\n  - [Truncate](#truncate)\n  - [Create your own](#create-your-own)\n- [Development](#development)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Background\n\nThe [`@tableland/jeti`](https://github.com/tablelandnetwork/jeti) library builds on top of the [`@tableland/sdk`](https://github.com/tablelandnetwork/tableland-js/packages/sdk), allowing you to easily add IPFS data to Tableland and read the underlying data from Tableland. Simply import the library, connect to the Tableland network, and you are ready to start creating, updating, and reading table data.\n\nJETI (JavaScript Extension for Tableland Integrations) is also designed to be extensible. You can create your own custom plugins/processors that transform data inserted into tables or retrieved from tables. For example, the IPFS plugin processes data and inserts a CID into a cell, and when data is read, it will \"see\" the CID and fetch/transform the underlying content within the query response. You can do whatever you'd like with an implementation using the `creatorProcessor` method.\n\n## Install\n\nInstallation is easy using npm or yarn. An ES bundle is also available for those operating purely in a browser environment.\n\n```bash\nnpm i @tableland/jeti\n```\n\n## Usage\n\nBefore getting started, you'll need to set up and have an IPFS node running. For example, IPFS Desktop exposes the API on `http://127.0.0.1:5001` by default; you'll then need to pass host/port information to connect to the IPFS node, which are endpoints defined in the [IPFS HTTP API](https://docs.ipfs.tech/reference/http/api/).\n\nFor the full documentation, see the docs page: [here](https://docs.tableland.xyz/sdk/plugins/).\n\n### Set up Tableland database\n\nFirst, connect and create a table. You'll want to make sure `@tableland/sdk` is installed as well, and this example shows ethers for setting up a signer with a private key on a local-only [Local Tableland](https://github.com/tablelandnetwork/tableland-js/tree/main/packages/local) network running.\n\n```js\nimport { Database } from \"@tableland/sdk\";\nimport { Wallet, getDefaultProvider } from \"ethers\";\n\n// Set up signer\nconst privateKey =\n  \"59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d\";\nconst wallet = new Wallet(privateKey);\n// To avoid connecting to the browser wallet (locally, port 8545).\n// For example: \"https://polygon-mumbai.g.alchemy.com/v2/YOUR_ALCHEMY_KEY\"\nconst provider = getDefaultProvider(\"http://127.0.0.1:8545\");\nconst signer = wallet.connect(provider);\n\n// Connect to database \u0026 create table\nconst db = new Database({ signer });\nconst { meta: create } = await db\n  .prepare(`CREATE TABLE my_table (id integer primary key, val text);`)\n  .run();\nawait create.txn?.wait();\nconst [tableName] = create.txn?.names ?? [];\nconsole.log(tableName); // my_table_31337_2\n```\n\n### Pin to IPFS\n\nIn this example, using `pinToLocal` as a tagged template will insert the proper values in your statement, while simultaneously uploading files to your remote pinning services.\n\nUsing the `resolve` function of `pinToLocal` fetches the data from IPFS into the result set.\n\n```JavaScript\n// Existing imports\n// For pinning to a local IPFS node\nimport { pinToLocal, skip } from \"@tableland/jeti\";\n\n// Database/table setup steps from above\n\n// Set up pinner for string templating\nconst localPinner = pinToLocal({\n  host: \"127.0.0.1\",\n  port: 5001,\n  protocol: \"http\",\n});\n\n// Define content and process SQL string\nconst contentToPin = \"Hello world\"; // A string, or a file buffer (Uint8Array)\nconst sql = await localPinner`insert into ${skip(\n  tableName\n)} (val) values ('${contentToPin}');`; // Converts non-skipped variable to CID\nconsole.log(sql);\n\n// Insert the transformed data with a CID\nconst { meta: insert } = await db.prepare(sql).all();\nawait insert.txn?.wait();\n```\n\nWhen you read the raw results, it'll contain the CID that was inserted, unless to `resolve` the results with the pinner:\n\n```js\nconst { results } = await db.prepare(`SELECT * FROM ${tableName}`).all();\nconsole.log(results);\n// [\n//   {\n//     id: 1,\n//     val: 'bafybeiabfiu2uipule2sro2maoufk2waokktnsbqp5gvaaod3y44ouft54'\n//   }\n// ]\n\nconst resultsWithCIDsResolved = await localPinner.resolve(results, [\"val\"]);\nconsole.log(resultsWithCIDsResolved);\n// [\n//   {\n//     id: 1,\n//     val: 'Hello world'\n//   }\n// ]\n```\n\nThe `pinToProvider` follows nearly the same steps, except is assumes you've configured a remote pinning provider on your IPFS node. For example, if you've configured Pinata, you can use the `pinToProvider` template to pin to Pinata.\n\n### Truncate\n\nTable cells have a max limit of 1 KB (1024 bytes), so the IPFS plugin is especially useful for data larger than that. However, if you're okay with lossy data that gets cut at 1024 bytes, the `truncate` plugin can be used:\n\n```js\nimport { truncate } from \"@tableland/jeti\";\n\n// 1025 letter 'a's, i.e., 1025 bytes is one over the limit\nconst longString = new Array(1026).join(\"a\"); // First value is `undefined`, so it will be skipped\n\nconst sql =\n  await truncate`INSERT INTO my_table_31337_2 (val) values ('${longString}')`;\n```\n\nSince the input string was one byte over the limit, it will be truncated to 1024 bytes. When you resolve it, the original string will not be returned—an ellipses is appended to represent the truncation occurred:\n\n```js\nconst detruncated = await truncate.resolve([{ val: `${longString}` }], [\"val\"]);\nconsole.log(detruncated); // Truncated input at 1024 bytes plus `...`\n```\n\n### Create your own\n\nCreating your own processor requires two function to be created: one for transforming inputs _before_ data is created, and one for transforming the data _after_ it is read. For example, if you wanted to add a value to the end of a string before it is inserted, and remove it when it is read, you could do the following:\n\n```js\nimport { createProcessor } from \"@tableland/jeti\";\n\nfunction addValue(value: string): PrepareResult {\n  const add = (input: string) =\u003e {\n    return input + value;\n  };\n\n  const remove = (input: string) =\u003e {\n    return input.replace(value, \"\");\n  };\n\n  return createProcessor(add, remove);\n}\n```\n\nThen, set up the processor and insert data:\n\n```js\nconst processor = addValue(\" world\");\nconst originalValueOne = \"Hello\";\nconst originalValueTwo = \"Hello again\";\n\nconst sql =\n  await processor`INSERT INTO my_table_31337_2 (val) VALUES ('${originalValueOne}'), ('${originalValueTwo}');`;\nconsole.log(sql);\n// INSERT INTO my_table_31337_2 VALUES ('Hello world'), ('Hello again world');\n```\n\nThe processor is designed to work with the parameter and response types from the Tableland SDK. But, to emulate it's functionality, we can do the following and resolve the results:\n\n```js\nconst rawResults = [\n  {\n    id: 1,\n    val: \"Hello world\",\n  },\n  {\n    id: 2,\n    val: \"Hello again world\",\n  },\n];\n\nconst unprocessedResults = await processor.resolve(rawResults, [\"val\"]);\nconsole.log(unprocessedResults);\n// [\n//   {\n//     id: 1,\n//     val: \"Hello\",\n//   },\n//   {\n//     id: 2,\n//     val: \"Hello again\",\n//   },\n// ];\n```\n\n## Development\n\nGet started by cloning, installing, building, and testing the project:\n\n```shell\ngit clone https://github.com/tablelandnetwork/jeti.git\ncd jeti\nnpm install\nnpm run build\nnpm test\n```\n\n## Contributing\n\nPRs accepted.\n\nSmall note: If editing the README, please conform to the\n[standard-readme](https://github.com/RichardLitt/standard-readme) specification.\n\n## License\n\nMIT AND Apache-2.0, © 2021-2023 Tableland Network Contributors\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftablelandnetwork%2Fjeti","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftablelandnetwork%2Fjeti","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftablelandnetwork%2Fjeti/lists"}