0.1.4
Pre-releaseSupport Metafields
Tento now supports Metafields
- Metafields have the same number of field definitions and validations as Metaobjects.
Note
We currently support only the 'PRODUCT' owner type.
import { metafield } from '@drizzle-team/tento';
export const name = metafield({
name: "Name",
ownerType: "PRODUCT",
pin: true,
description: "",
key: "name",
namespace: "custom",
visibleToStorefrontApi: true,
fieldDefinition: (f) =>
f.singleLineTextField({
validations: (v) => [v.min(1), v.max(50)],
}),
});
export const description = metafield({
name: "Description",
ownerType: "PRODUCT",
fieldDefinition: (f) => f.multiLineTextField(),
});
- Additionally, Metafields can now include a Metaobject reference field definition.
import { metafield, metaobject } from '@drizzle-team/tento';
export const designer = metaobject({
name: "Designer",
type: "designer",
fieldDefinitions: (f) => ({
name: f.singleLineTextField({
name: "Title",
required: true,
validations: (v) => [v.min(1), v.max(50)],
}),
description: f.multiLineTextField({
name: "Description",
}),
website: f.url({
name: "Website",
}),
}),
});
export const designer_reference = metafield({
name: "Designer",
ownerType: "PRODUCT",
fieldDefinition: (f) =>
f.metaobjectReference({
validations: (v) => [v.metaobjectDefinitionType(() => designer.type)],
}),
});
Updated Metaobjects
- Previously, we had simple $inferInsert and $inferSelect type definitions based on the Metaobject schema model, which allowed for straightforward types with all required fields. With this release, we are introducing new $inferInsert and $inferSelect type definitions. Now, Tento will specify exactly which fields are required for insertion and which fields can be nullable in the response.
import { metaobject } from '@drizzle-team/tento';
export const designer = metaobject({
name: "Designer",
type: "designer",
fieldDefinitions: (f) => ({
name: f.singleLineTextField({
name: "Title",
required: true,
validations: (v) => [v.min(1), v.max(50)],
}),
description: f.multiLineTextField({
name: "Description",
}),
website: f.url({
name: "Website",
}),
}),
});
type InsertModel = typeof designer.$inferInsert;
type SelectModel = typeof designer.$inferSelect;
Before
type InsertModel = {
name: string;
description: string;
website: string;
}
type SelectModel = {
_id: string;
_handle: string;
_updatedAt: Date;
name: string;
description: string;
website: string;
}
After
type InsertModel = {
name: string;
description?: string | undefined;
website?: string | undefined;
}
type SelectModel = {
_id: string;
_handle: string;
_displayName: string;
_updatedAt: Date;
name: string;
description: string | null;
website: string | null;
}
- Added new API for
upsert
import { tento, metaobject } from "@drizzle-team/tento";
export const designer = metaobject({
name: "Designer",
type: "designer",
fieldDefinitions: (f) => ({
name: f.singleLineTextField({
name: "Title",
required: true,
validations: (v) => [v.min(1), v.max(50)],
}),
description: f.multiLineTextField({
name: "Description",
}),
website: f.url({
name: "Website",
}),
}),
});
const result = await client.metaobjects.designer.upsert("gid://shopify/Metaobject/1", {
fields: {
name: "Alex",
},
});
const result: {
_id: string;
_handle: string;
_displayName: string;
_updatedAt: Date;
name: string;
description: string | null;
website: string | null;
}
- The
bulkDelete
API has been updated to return ajob
instance in the response. This instance includes one field and one asynchronous function:
job.done
: This field contains the actual result and resolves only once the job is complete.job.checkDone()
: This asynchronous function calls Shopify to retrieve an updated result for the job.
import { tento, metaobject } from "@drizzle-team/tento";
export const designer = metaobject({
name: "Designer",
type: "designer",
fieldDefinitions: (f) => ({
name: f.singleLineTextField({
name: "Title",
required: true,
validations: (v) => [v.min(1), v.max(50)],
}),
description: f.multiLineTextField({
name: "Description",
}),
website: f.url({
name: "Website",
}),
}),
});
/**
* job.done -> boolean
* job.chekDone() -> Promise<boolean>
*/
const job: ShopifyJobOperation = await client.metaobjects.designer.bulkDelete(['gid://shopify/Metaobject/1']);
const isDone = await job.checkDone();
const isDone: boolean
Support Products
Starting with this release, we are introducing new Products API's. We will gradually improve and add all shopify Product API's
products.list
is actuallyproducts
shopify query
import { tento } from "@drizzle-team/tento";
const products = await client.products.list();
const products: {
items: {
id: string;
createdAt: Date;
updatedAt: Date;
isGiftCard: boolean;
category?: {
fullName: string;
id: string;
isLeaf: boolean;
isRoot: boolean;
name: string;
ancestorIds: number[];
childrenIds: number[];
isArchived: boolean;
level: number;
parentId?: number | undefined;
} | undefined;
... 30 more ...;
vendor: string;
}[];
pageInfo: {
startCursor: string;
endCursor: string;
hasNextPage: boolean;
hasPreviousPage: boolean;
};
}
By default, all fields are included in the response. However, you can specify which fields to include or exclude as needed. You can query all available fields in the 2024-10 Shopify API version with these filters
Using include in your query will return only the specified fields:
import { tento } from "@drizzle-team/tento";
const products = await client.products.list({
first: 10,
fields: {
id: true,
title: true,
handle: true,
productType: true,
tags: true,
},
query: {
title: "product",
$or: [
{
tag: "tento",
},
{
tag: "custom",
},
],
},
});
const products: {
items: {
id: string;
title: string;
handle: string;
productType: string;
tags: string[];
}[];
pageInfo: {
startCursor: string;
endCursor: string;
hasNextPage: boolean;
hasPreviousPage: boolean;
};
}
Using exclude in your query will return all fields except the specified ones:
const products = await client.products.list({
first: 10,
fields: {
id: false,
title: false,
handle: false,
productType: false,
tags: false,
},
query: {
title: "product",
$or: [
{
tag: "tento",
},
{
tag: "custom",
},
],
},
});
const products: {
items: {
description: string;
createdAt: Date;
updatedAt: Date;
isGiftCard: boolean;
category: {
fullName: string;
id: string;
isLeaf: boolean;
isRoot: boolean;
name: string;
... 4 more ...;
parentId?: number | undefined;
} | undefined;
... 25 more ...;
vendor: string;
}[];
pageInfo: {
startCursor: string;
endCursor: string;
hasNextPage: boolean;
hasPreviousPage: boolean;
};
}
- The
products.update
function corresponds to theproductUpdate
mutation in Shopify.
import { tento, metaobject, metafield } from "@drizzle-team/tento";
export const designer = metaobject({
name: "Designer",
type: "designer",
fieldDefinitions: (f) => ({
name: f.singleLineTextField({
name: "Title",
required: true,
validations: (v) => [v.min(1), v.max(50)],
}),
description: f.multiLineTextField({
name: "Description",
}),
website: f.url({
name: "Website",
}),
}),
});
export const designer_reference = metafield({
name: "Designer",
ownerType: "PRODUCT",
fieldDefinition: (f) =>
f.metaobjectReference({
validations: (v) => [v.metaobjectDefinitionType(() => designer.type)],
}),
});
const gqlClient = new shopify.clients.Graphql({
session,
});
const client = tento({
client: gqlClient,
schema: { designer, designer_reference },
});
/**
* Designer key field is accessed as designer_reference.name, which will be autocompleted by TypeScript
*/
const products = await client.products.update("gid://shopify/Product/1", {
fields: {
title: "product title",
metafields: {
Designer: "metaobject ID",
},
},
});
const products: {
title: string;
metafields: {
key: string;
type: MetafieldFieldType;
value: string;
id: string;
namespace: string;
ownerType: 'PRODUCT';
}[];
}
Common
We’ve added common objects similar to those in Shopify. currently, this includes only the job query
The job query returns the following fields:
id
: A globally unique ID that is generated when running an asynchronous mutation.done
: This field resolves only once the job is complete.
import { tento } from "@drizzle-team/tento";
const job = await client.jobs.get('gid://shopify/Job/1');
const job: {
id: string;
done: boolean;
}
Migrations
Migrations now also support Metafields in both the CLI and client.
CLI
tento pull
tento push
Client
import { tento } from "@drizzle-team/tento";
await client.applySchema();
We have also added a configuration option for the Client: { unknownEntities: 'ignore' | 'delete' }
. This allows you to specify a strategy for handling metaobjects
and metafields
Important
unknownEntities
- helps define the scope of metaobjects
and metafields
to be migrated, preventing unnecessary downloads and avoiding the deletion of other existing items.
-ignore
- This means that only the metaobjects
and metafields
from your schema will be processed.
-delete
- This means that all metaobjects
and metafields
will be processed. If you select this option and run applySchema(), we will create or update your schema in Shopify and remove all metaobjects
and metafields
that do not exist in your schema.
Note
If you don't specify the configuration, the default option will be ignore
- applySchema() with config
await client.applySchema({ unknownEntities: 'ignore' });
- We have also added a new API to remove the current schema from Shopify, which includes the same configuration option.
await client.removeSchema({ unknownEntities: 'ignore' });