{"id":15607912,"url":"https://github.com/drizzle-team/tento","last_synced_at":"2025-04-06T20:09:49.732Z","repository":{"id":211785549,"uuid":"729912691","full_name":"drizzle-team/tento","owner":"drizzle-team","description":"Shopify data framework  for NodeJS, TypeScript and JavaScript","archived":false,"fork":false,"pushed_at":"2024-10-28T14:17:16.000Z","size":362,"stargazers_count":76,"open_issues_count":2,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-10-30T04:29:56.284Z","etag":null,"topics":["bun","deno","drizzle","javascript","nodejs","shopify","typescript"],"latest_commit_sha":null,"homepage":"https://tento.drizzle.team","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/drizzle-team.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-12-10T18:23:57.000Z","updated_at":"2024-10-29T17:54:37.000Z","dependencies_parsed_at":"2023-12-13T09:38:55.949Z","dependency_job_id":"2d250c40-2a8f-42dd-91a8-27c4256c84bf","html_url":"https://github.com/drizzle-team/tento","commit_stats":{"total_commits":13,"total_committers":2,"mean_commits":6.5,"dds":"0.46153846153846156","last_synced_commit":"632a318b4ffcb9dead06e33b44a5cba1f572165a"},"previous_names":["drizzle-team/tento"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drizzle-team%2Ftento","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drizzle-team%2Ftento/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drizzle-team%2Ftento/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drizzle-team%2Ftento/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/drizzle-team","download_url":"https://codeload.github.com/drizzle-team/tento/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246365644,"owners_count":20765546,"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":["bun","deno","drizzle","javascript","nodejs","shopify","typescript"],"created_at":"2024-10-03T05:05:08.593Z","updated_at":"2025-04-06T20:09:49.714Z","avatar_url":"https://github.com/drizzle-team.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# Tento\n\n### Shopify data framework for NodeJS and TypeScript\n\n\u003ch6\u003eTento [店頭] means \"shop\" 🛍️ in Japanese\u003c/h6\u003e\n\n[Discord](https://driz.link/discord) | [Website](https://drizzle.team) | [Twitter](https://twitter.com/drizzleorm) | [Docs](https://github.com/drizzle-team/tento)\n\u003c/div\u003e\n\n## Overview\n\nTento provides a simple yet powerful API for working with Shopify data, including metaobjects and metafields.\nIt also provides a CLI tool for two-way synchronization between your local schema definition and Shopify.\n\n## Quick Start\n\n### Installation\n\nYou can install Tento with your preferred package manager:\n\n```bash\nnpm install @drizzle-team/tento\nyarn add @drizzle-team/tento\npnpm add @drizzle-team/tento\nbun add @drizzle-team/tento\n```\n\n### Schema\n\nDeclare your Tento metaobjects schema in `schema.ts` file.\nAs of now Tento CLI only supports one schema file:\n\n```ts\nimport { metaobject } from '@drizzle-team/tento';\n\nexport const designers = metaobject({\n  name: 'Designer',\n  type: 'designer',\n  fieldDefinitions: (f) =\u003e ({\n    fullName: f.singleLineTextField({\n      name: 'Full Name',\n      required: true,\n      validations: (v) =\u003e [v.min(5), v.max(100)],\n    }),\n    description: f.singleLineTextField({\n      name: 'Description',\n      required: true,\n      validations: (v) =\u003e [v.min(5), v.max(300)],\n    }),\n    link: f.url(({\n      name: 'Link',\n      validations: (v) =\u003e [v.allowedDomains([\"github.com\"])],\n    }),\n  }),\n});\n```\n\n### Tento queries client\n\n```ts\nimport { tento } from '@drizzle-team/tento';\nimport * as schema from './schema';\n\n// Using @shopify/shopify-api (or its wrappers)\nimport '@shopify/shopify-api/adapters/node';\nimport { shopifyApi, ApiVersion } from '@shopify/shopify-api';\nconst shopifyClient = shopifyApi({ ... });\nconst gqlClient = new shopifyApiClient.clients.Graphql({\n  session: ...,\n});\n\n// Using raw fetch\nimport { createClient } from '@drizzle-team/tento';\nconst gqlClient = createClient({\n  shop: 'your-shop-name',\n  headers: {\n    // any headers you need\n    // Content-Type is added automatically unless you override it\n    'X-Shopify-Access-Token': 'your-admin-api-access-token',\n  },\n  fetch: customFetch, // optionally provide your own fetch implementation\n});\n\n// Create Tento client from any Shopify client above\nconst client = tento({\n  client: gqlClient,\n  schema,\n});\n\n// Apply the local schema to Shopify\nawait client.applySchema();\n\n// Query metaobjects\nconst designers = await tento.metaobjects.designers.list({\n  first: 10,\n});\n/* \n  {\n    _id: string;\n    _handle: string;\n    _updatedAt: Date;\n    fullName: string;\n    description: string;\n    link: string;\n  }[]\n*/\n```\n\n### Schema Migrations | Pull\n\nNow let's pull your existing Shopify Metaobjects schema, first we need to create a `tento.config.ts` file:\n\n```ts\nimport { defineConfig } from '@drizzle-team/tento/cli';\n\nexport default defineConfig({\n  schemaPath: './src/schema.ts',\n  shop: 'd91122',\n  headers: {\n    'X-Shopify-Access-Token': process.env['SHOPIFY_ADMIN_API_TOKEN']!,\n  },\n});\n```\n\nNow let's run Tento CLI `pull` command\n\n```bash\nnpx tento pull\nyarn tento pull\npnpm tento pull\nbun tento pull\n```\n\nTento CLI will consume `tento.config.ts` and fetch your Shopify schema to your project `schema.ts` file:\n\n```ts\nimport { metaobject } from '@drizzle-team/tento';\n\nexport const orm = metaobject({\n  name: 'ORM',\n  type: 'orm',\n  fieldDefinitions: (f) =\u003e ({\n    name: f.singleLineTextField({\n      name: 'Name',\n      required: true,\n      validations: (v) =\u003e [v.min(1), v.max(50)],\n    }),\n    git_hub_repo: f.url({\n      name: 'GitHub repo',\n      required: true,\n      validations: (v) =\u003e [v.allowedDomains([\"github.com\"])],\n    }),\n    stars: f.integer({\n      name: 'Stars',\n      required: true,\n      validations: (v) =\u003e [v.min(0)],\n    }),\n    datetime: f.dateTime({\n      validations: (v) =\u003e [v.min('2023-12-01T13:30:00Z'), v.max('2023-12-02T13:30:00Z')],\n    }),\n    multiline_text: f.multiLineTextField({\n      validations: (v) =\u003e [\n        v.min(1),\n        v.max(2),\n        v.regex(/^[a-zA-Z]+$/),\n      ],\n    }),\n    decimal: f.decimal({\n      validations: (v) =\u003e [\n        v.min(1.0),\n        v.max(2.0),\n        v.maxPrecision(2),\n      ],\n    }),\n    decimal_list: f.decimalList({\n      required: true,\n      validations: (v) =\u003e [\n        v.min(1.0),\n        v.max(2.0),\n        v.maxPrecision(2),\n      ],\n    }),\n    date_list: f.dateList({\n      validations: (v) =\u003e [v.min('2023-12-01'), v.max('2023-12-02')],\n    }),\n    dimension: f.dimension({\n      validations: (v) =\u003e [v.min({ value: 1, unit: \"METERS\" }), v.max({ value: 5, unit: \"FEET\" })],\n    }),\n    dimension_list: f.dimensionList({\n      validations: (v) =\u003e [v.min({ value: 1, unit: \"INCHES\" }), v.max({ value: 5, unit: \"YARDS\" })],\n    }),\n    volume: f.volume({\n      validations: (v) =\u003e [v.min({ value: 1, unit: \"MILLILITERS\" }), v.max({ value: 4, unit: \"PINTS\" })],\n    }),\n    volume_list: f.volumeList({\n      validations: (v) =\u003e [v.min({ value: 1, unit: \"CENTILITERS\" }), v.max({ value: 4, unit: \"IMPERIAL_FLUID_OUNCES\" })],\n    }),\n    date: f.date({\n      validations: (v) =\u003e [v.min('2023-12-01'), v.max('2023-12-02')],\n    }),\n    weight: f.weight({\n      validations: (v) =\u003e [v.min({ value: 1, unit: \"GRAMS\" }), v.max({ value: 5, unit: \"OUNCES\" })],\n    }),\n    weight_list: f.weightList({\n      validations: (v) =\u003e [v.min({ value: 1, unit: \"KILOGRAMS\" }), v.max({ value: 100, unit: \"POUNDS\" })],\n    }),\n  }),\n});\n\nexport const book = metaobject({\n  name: 'Book',\n  type: 'book',\n  fieldDefinitions: (f) =\u003e ({\n    title: f.singleLineTextField({\n      name: 'Title',\n      required: true,\n      validations: (v) =\u003e [v.min(1), v.max(100)],\n    }),\n    author: f.singleLineTextField({\n      name: 'Author',\n      required: true,\n      validations: (v) =\u003e [v.min(1), v.max(50)],\n    }),\n    isbn: f.singleLineTextField({\n      name: 'ISBN',\n      required: true,\n      validations: (v) =\u003e [v.regex(/^(97(8|9))?\\d{9}(\\d|X)$/)],\n    }),\n    genre: f.singleLineTextField({\n      name: 'Genre',\n      validations: (v) =\u003e [v.min(1), v.max(30)],\n    }),\n    language: f.singleLineTextField({\n      name: 'Language',\n      validations: (v) =\u003e [v.min(1), v.max(20)],\n    }),\n    summary: f.multiLineTextField({\n      name: 'Summary',\n      validations: (v) =\u003e [v.min(10), v.max(5000)],\n    }),\n    price: f.decimal({\n      name: 'Price',\n      validations: (v) =\u003e [\n        v.min(0.0),\n        v.max(999.99),\n        v.maxPrecision(2),\n      ],\n    }),\n    publication_date: f.date({\n      name: 'Publication date',\n      validations: (v) =\u003e [v.min('2000-01-01'), v.max('2023-12-31')],\n    }),\n    page_count: f.integer({\n      name: 'Page count',\n      required: true,\n      validations: (v) =\u003e [v.min(1), v.max(2000)],\n    }),\n    cover_type: f.singleLineTextField({\n      name: 'Cover type',\n      validations: (v) =\u003e [v.min(1), v.max(20)],\n    }),\n  }),\n});\n```\n\n### Schema Migrations | Push\n\nWhenever you change your locall schema - you can apply changes to your Shopify by using `tento push` command:\n\n```bash\n~ npx tento push\n\n- Updated metaobject definition \"ORM\"\n✅ All changes applied\n```\n\nIt will consume your `tento.config.ts` file, traverse your schema and apply any diffs to Shopify.\n\n### Queries\n\nTento supports all Shopify Metaobject API methods:\n\n`.list()`\n\n```ts\ntento.metaobjects.designers.list({\n  query: {\n    $raw: 'state:disabled AND (\"sale shopper\" OR VIP)',\n  },\n});\n\ntento.metaobjects.designers.list({\n  query: ['Bob', 'Norman'],\n});\n\ntento.metaobjects.designers.list({\n  query: {\n    displayName: {\n      $raw: 'Bob Norman',\n    },\n  },\n});\n\ntento.metaobjects.designers.list({\n  query: {\n    displayName: 'Bob Norman',\n    updatedAt: new Date('2023-01-01'),\n  },\n});\n\ntento.metaobjects.designers.list({\n  query: {\n    updatedAt: {\n      $gte: new Date('2023-01-01'),\n      $lte: new Date('2024-01-01'),\n    },\n  },\n});\n\ntento.metaobjects.designers.list({\n  query: {\n    displayName: {\n      $not: 'bob',\n    },\n  },\n});\n\ntento.metaobjects.designers.list({\n  query: [{ $or: ['bob', 'norman'] }, 'Shopify'],\n});\n\ntento.metaobjects.designers.list({\n  query: [{ displayName: 'Bob' }, { $or: ['sale shopper', 'VIP'] }],\n});\n\ntento.metaobjects.designers.list({\n  query: {\n    displayName: 'Bob Norman',\n  },\n});\n\ntento.metaobjects.designers.list({\n  query: 'norm*',\n});\n\ntento.metaobjects.designers.list({\n  query: {\n    displayName: 'norm*',\n  },\n});\n```\n\n## Roadmap\n\n- [x] Accept existing Shopify client instance\n- [x] Support OAuth\n- [x] Expose CLI operations as API\n- [x] Allow providing custom `fetch` implementation\n- [ ] Support all field types and validations\n- [ ] Metafields management\n- [ ] Assign metaobjects to resources and metafields\n- [ ] Products management\n- [ ] Support multiple schema files\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrizzle-team%2Ftento","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdrizzle-team%2Ftento","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrizzle-team%2Ftento/lists"}