# `gadget-server`  The `gadget-server` package is an auto-generated package of utilities for implementing your Gadget app's backend. Exports from `gadget-server` are only available in the **backend** of your application, and not on the frontend. For reading and writing data from your frontend, see the [`@gadgetinc/react`](https://docs.gadget.dev/reference/react) package. ## `logger`  An instance of the for your Gadget app. ```typescript import { logger } from "gadget-server"; // log at the info level logger.info("hello world"); // log an error logger.error({ error: new Error("Something went wrong") }); ``` Logs are viewable in the [Log Viewer](https://docs.gadget.dev/guides/development-tools/logger) within Gadget. ## `api`  An instance of your Gadget app's [API client](https://docs.gadget.dev/api/example-app/development/). ```typescript import { api } from "gadget-server"; await api.widget.create({ title: "new widget " }); ``` This `api` client has the `system-admin` role and can perform any action in your Gadget app. See the [API Reference](https://docs.gadget.dev/api/example-app/development/) for more information on your app's API and the `api` client object. ## `trigger`  An instance of the triggers associated with your Gadget app's action. When an action is executed, it receives a trigger object containing information about the event that initiated the action. This object varies depending on the trigger type and includes a type property that specifies the kind of trigger. ```typescript export const run: ActionRun = async ({ logger, trigger }) => { // This logs an object providing details about the trigger // Output example: { "type": "api", "rootModel": "someModel", "rootAction": "someAction", ... } logger.info(trigger); }; ``` For more information on triggers [check out our guide here](https://docs.gadget.dev/guides/actions/triggers). ## `connections`  An object with utility functions for each of your app's connections to external systems. This object contains pre-configured clients for supported Gadget connections, such as Shopify, OpenAI, and Sentry. For example, if you wanted to access your OpenAI connection in a Fastify boot plugin, you could do so like this: ```typescript import { connections, Server } from "gadget-server"; export default async function (server: Server) { const openAIKey = connections.openai.apiKey; // Do things with the key } ``` For more information on the available connections for Gadget app [check out the guide here](https://docs.gadget.dev/guides/plugins#what-are-connections-and-what-do-they-provide). ### `connections.shopify`  If you have a Shopify connection configured, the `connections` object offers helpers for getting a Shopify API client to make calls to Shopify. All functions that create Shopify API clients create an instance of the client from the [`shopify-api-node`](https://www.npmjs.com/package/shopify-api-node) package, pre-configured with the right credentials retrieved from the database. For example, you can get a Shopify client instance for a given shop ID using `connections.shopify.forShopId` ```typescript import { RouteHandler } from "gadget-server"; const route: RouteHandler = async ({ request, reply, connections, logger }) => { const shopify = await connections.shopify.forShopId("12345"); logger.info( await shopify.graphql(` query { shop { name } } `) ); }; export default route; ``` #### `connections.shopify.forShopId(shopId: bigint | string): Promise`  Returns an instantiated Shopify API client from [`shopify-api-node`](https://www.npmjs.com/package/shopify-api-node) ready for calling the given shop with the given `shopId`. Retrieves the credentials for this shop by by querying the `shopifyShop` model under the hood, and using the `apiKey` and `accessToken` fields to instantiate the client. ```typescript import { RouteHandler } from "gadget-server"; const route: RouteHandler = async ({ request, reply, connections, logger }) => { const shopify = await connections.shopify.forShopId("12345"); logger.info( await shopify.graphql(` query { shop { name } } `) ); }; export default route; ``` #### `connections.shopify.forShopDomain(myshopifyDomain: string): Promise`  Returns an instantiated Shopify API client from [`shopify-api-node`](https://www.npmjs.com/package/shopify-api-node) ready for calling the shop with given `myshopifyDomain`. Retrieves the credentials for this shop by by querying the `shopifyShop` model under the hood, and using the `apiKey` and `accessToken` fields to instantiate the client. ```typescript import { RouteHandler } from "gadget-server"; const route: RouteHandler = async ({ request, reply, connections, logger }) => { const shopify = await connections.shopify.forShopDomain("snowdevil.myshopify.com"); logger.info( await shopify.graphql(` query { shop { name } } `) ); }; export default route; ``` #### `connections.shopify.current: ShopifyClient | undefined`  Returns an instantiated Shopify API client from [`shopify-api-node`](https://www.npmjs.com/package/shopify-api-node) ready for calling the shop calling the current action or route, if there is one. The current shop is derived from the trigger running the current action or route. If the current shop is unknown, `connections.shopify.current` will be undefined. For more information on calling your backend from your frontend and preserving authentication information, see the [Shopify plugin guides](https://docs.gadget.dev/guides/plugins/shopify/frontends#calling-the-shopify-api). ```typescript import { RouteHandler } from "gadget-server"; const route: RouteHandler = async ({ request, reply, connections, logger }) => { const shopify = connections.shopify.current; if (!shopify) { throw new Error("don't know the current shop for creating a shopify client"); } logger.info( await shopify.graphql(` query { shop { name } } `) ); }; export default route; ``` #### `connections.shopify.setCurrentShop(shopId: bigint): Promise`  Allows setting the currently authenticated shop for manually overriding Gadget's Shopify authentication. **Warning**: This function is generally not used in the course of normal development with Gadget, as Gadget includes robust for automatically authenticating requests from Shopify as belonging to a particular shop. For frontends using the `@gadgetinc/react-shopify-app-bridge` library, or building Shopify extensions, Gadget passes headers from the frontend to the backend that securely authenticate shops and populate `connections.shopify.current` if possible. This function should only be used when you are building your own secure authentication mechanism, and you'd like your code to be able to use `connections.shopify.current` as you might otherwise. For more information on calling your backend from your frontend and preserving authentication information, see the [Shopify plugin guides](https://docs.gadget.dev/guides/plugins/shopify/frontends#calling-the-shopify-api). ```typescript import { RouteHandler } from "gadget-server"; const route: RouteHandler = async ({ request, reply, connections, logger }) => { // undefined for some reason where Gadget can't authenticate the request as coming from a particular shop logger.info(connections.shopify.current); // imperatively set the current shop ID await connections.shopify.setCurrentShop("12345"); // undefined for some reason where Gadget can't authenticate the request as coming from a particular shop const shopify = connections.shopify.current; if (!shopify) { throw new Error("don't know the current shop for creating a shopify client"); } logger.info( await shopify.graphql(` query { shop { name } } `) ); }; export default route; ``` #### `connections.shopify.currentShopId: string | undefined`  Returns the id of the currently authenticated shop, if it is set. Returns `undefined` otherwise. #### `connections.shopify.currentShopDomain: string | undefined`  Returns the myshopify domain (like `snowdevil.myshopify.com`) of the currently authenticated shop, if it is set. Returns `undefined` otherwise. #### `connections.shopify.currentClientId: string | undefined`  Returns the **Client ID** of the Shopify App that has authenticated the current request, if it is known. Returns `undefined` otherwise. #### `connections.shopify.currentClientSecret: string | undefined`  Returns the **Client Secret** of the Shopify App that has authenticated the current request, if it is known. Returns `undefined` otherwise. #### `connections.shopify.currentSession: { token: string, userId?: string } | undefined`  Returns the contents of the Shopify Session Token passed from the frontend if one was passed. Useful for manually inspecting the session token contents, or accessing the `userId` for Customer Account extensions that correctly pass a session token. See the [`@gadgetinc/shopify-extensions`](https://docs.gadget.dev/reference/shopify-extensions) docs for more info on authenticating extensions to your Gadget backend. #### `connections.shopify.currentAppProxy: { pathPrefix: string | null, loggedInCustomerId: string | null } | undefined`  Returns the proxy parameters if the current request was made through a Shopify App Proxy. See the [authenticated requests with Shopify App Proxy](https://docs.gadget.dev/guides/plugins/shopify/advanced-topics/extensions#authenticated-requests-with-shopify-app-proxies) docs for more information. ### `connections.bigcommerce`  If you have a BigCommerce connection configured, the `connections` object offers helpers for getting an API client to make calls to BigCommerce. All functions that create BigCommerce API clients create an instance of the client from the [`@space48/bigcommerce-api` package](https://github.com/Space48/bigcommerce-api-js), pre-configured with the right credentials retrieved from the database. #### `connections.bigcommerce.current: BigCommerceClient | undefined`  Returns an instantiated BigCommerce API client from [`@space48/bigcommerce-api` package](https://github.com/Space48/bigcommerce-api-js) ready for calling the store calling the current action or route, if there is one. The current store is derived from the trigger running the current action or route. If the current store is unknown, `connections.bigcommerce.current` will be `undefined`. For more information on calling your backend from your frontend and preserving authentication information, see the [BigCommerce frontend guide](https://docs.gadget.dev/guides/plugins/bigcommerce/frontends#reading-data-from-your-backend). ```typescript export const run: ActionRun = async ({ logger, connections }) => { const bigcommerce = connections.bigcommerce.current; if (!bigcommerce) { throw new Error( "don't know the current store for creating a bigcommerce client" ); } logger.info( await bigcommerce.v3.get("/catalog/products", { query: { limit: 1 } }), "read 1 bigcommerce product" ); }; ``` #### `connections.bigcommerce.forStoreHash(storeHash: string) : BigCommerceClient | undefined`  Returns an instantiated BigCommerce API client from [`@space48/bigcommerce-api` package](https://github.com/Space48/bigcommerce-api-js) ready for calling the given store with the given `storeHash`. Retrieves the credentials for this store by by querying the `bigcommerce/store` model under the hood, and using the `apiKey` and `accessToken` fields to instantiate the client. ```typescript // init BigCommerce API client using forStoreHash export const run: ActionRun = async ({ logger, connections }) => { const bigcommerce = await connections.bigcommerce.forStoreHash( "" ); logger.info( await bigcommerce.v3.get("/catalog/products", { query: { limit: 1 } }), "read 1 bigcommerce product" ); }; ``` #### `connections.bigcommerce.forStore(store: GadgetRecord<{storeHash: string, accessToken: string}>) : BigCommerceClient | undefined`  Returns an instantiated BigCommerce API client from [`@space48/bigcommerce-api` package](https://github.com/Space48/bigcommerce-api-js) ready for calling the given store with the given `bigcommerce/store` record. Retrieves the credentials for this store by by querying the `bigcommerce/store model` under the hood, and using the `apiKey` and `accessToken` fields to instantiate the client. `forStore` is an optimized way to initialize a BigCommerce API client, used when you already have a store record loaded in context and don't want to make another round trip to the database using other client initialization methods. ```typescript // in init BigCommerce API client using forStore export const run: ActionRun = async ({ logger, connections }) => { // read a store record from Gadget const store = await api.internal.bigcommerce.store.findFirst({ filter: { storeHash: { equals: "" } }, }); const bigcommerce = await connections.bigcommerce.forStore(store); logger.info( await bigcommerce.v3.get("/catalog/products", { query: { limit: 1 } }), "read 1 bigcommerce product" ); }; ``` #### `connections.bigcommerce.setCurrentStore(storeOrStoreHash: string | GadgetRecord<{storeHash: string, accessToken: string}>): Promise`  Allows setting the currently authenticated store for manually overriding Gadget's BigCommerce authentication. **Warning**: This function is generally not used in the course of normal development with Gadget, as Gadget includes robust for automatically authenticating requests from BigCommerce as belonging to a particular store. For frontends using the `@gadgetinc/react-bigcommerce` library, Gadget passes headers from the frontend to the backend that securely authenticate stores and populate `connections.bigcommerce.current` if possible. This function should only be used when you are building your own secure authentication mechanism, and you'd like your code to be able to use `connections.bigcommerce.current` as you might otherwise. For more information on calling your backend from your frontend and preserving authentication information, see the [BigCommerce plugin guides](https://docs.gadget.dev/guides/plugins/bigcommerce/frontends). ```typescript // in example setting custom authentication using setCurrentStore export const run: ActionRun = async ({ logger, connections }) => { // undefined for some reason where Gadget can't authenticate the request as coming from a particular store logger.info(connections.bigcommerce.current); // imperatively set the current store ID await connections.bigcommerce.setCurrentStore("store_hash_here"); // defined now const bigcommerce = connections.bigcommerce.current; if (!bigcommerce) { throw new Error( "don't know the current store for creating a BigCommerce client" ); } logger.info( await bigcommerce.v3.get("/catalog/products", { query: { limit: 1 } }), "read 1 bigcommerce product" ); }; ``` #### `connections.bigcommerce.currentClientId : string | undefined`  Returns the **Client ID** of the BigCommerce app that has authenticated the current request, if it is known. Returns `undefined` otherwise. #### `connections.bigcommerce.currentSecret: string | undefined`  Returns the **Client Secret** of the BigCommerce app that has authenticated the current request, if it is known. Returns `undefined` otherwise. #### `connections.bigcommerce.currentStoreId: string | undefined`  Returns the unique Gadget-assigned `id` of the currently authenticated `bigcommerce/store` record, if it is set. Returns `undefined` otherwise. #### `connections.bigcommerce.currentStoreHash: string | undefined`  Returns the store hash of the currently authenticated BigCommerce store, if it is set. Returns `undefined` otherwise. #### `connections.bigcommerce.currentUserId: string | undefined`  Returns the user ID of the currently authenticated BigCommerce user stored in the `session` model, if it is set. Returns `undefined` otherwise. ### `connections.openai`  If you have configured an OpenAI connection with Gadget, `connections.openai` is an instance of the official OpenAI JavaScript client for making API calls to all of OpenAI's services. When using Gadget's built-in credentials for OpenAI, only some calls are available, but when using your own credentials, all API functionality is available. See [OpenAI's node documentation](https://platform.openai.com/docs/quickstart?context=node) for more details on the OpenAI JavaScript client. ```typescript export const run: ActionRun = async ({ connections }) => { const chatCompletion = await connections.openai.chat.completions.create({ model: "gpt-5.1", messages: [{ role: "user", content: "What are some synonyms for Gadget?" }], }); return { message: chatCompletion.choices[0].message }; }; ``` ## Model actions  ### `save(record)`  Saves the given record to the database. ```typescript import { save } from "gadget-server"; export const run: ActionRun = async ({ record }) => { await save(record); }; ``` If the record is a new record, this will persist the record and assign it an ID. If the record is an existing record, this will update the database with any changed values from the record. `save` will validate the record before persisting it. `save` persists the record to the database using your app's [Internal API](https://docs.gadget.dev/api/example-app/development/internal-api). ##### Returns  Async function, which returns a Promise. Resolves to void. Any encountered validation errors will be thrown. ### `deleteRecord(record)`  Deletes the given record from the database. ```typescript import { deleteRecord } from "gadget-server"; export const run: ActionRun = async ({ record }) => { await deleteRecord(record); }; ``` `delete` removes the record from your database using your app's [Internal API](https://docs.gadget.dev/api/example-app/development/internal-api). ##### Returns  Async function, which returns a Promise. Resolves to void. ### `applyParams(params, record)`  Sets incoming parameters onto a `record` object. ```typescript import { applyParams } from "gadget-server"; export const run: ActionRun = async ({ params, record }) => { applyParams(params, record); await save(record); }; ``` `applyParams` will set any incoming parameters onto the record. This is useful for updating a record with the parameters from an action or similar call. `applyParams` does _not_ persist the record -- it just mutates the in-memory `record` object. ##### Parameters  * `params` - The data passed to an action from API calls, webhook events, or direct user inputs, which include the fields of a record. * `record` - Record to apply parameters to. ##### Returns  Returns void. ## Background actions  ### `enqueue(action, input, options)`  Enqueues a model or global action to run in the background. ```typescript export const run: ActionRun = async ({ api }) => { await api.enqueue( api.someModelOrGlobalAction, {}, { id: "background-action-1", priority: "high" } ); }; ``` ##### Parameters  * `action` - Any model or global action. * `input` - Parameters or data to pass to an action. * `shopifyShop` (optional) - The ID of the Shopify shop this action is associated with, used for [Shopify API rate limits](https://docs.gadget.dev/guides/plugins/shopify/building-shopify-apps#managing-shopify-api-rate-limits). * `options` (optional) - Options for governing how a background action is enqueued: * `id` - A unique identifier to use for the background action. Must be unique among all other background actions within its environment. If not set, a unique ID will be auto-generated and returned. * `priority` - How high to place this background action in its queue, `high` priority actions will be executed before `default` priority actions, and those before `low` priority actions. If not set, the `default` priority will be used. * `queue` - A queue to put this background action in that limits the maximum concurrency of all actions in the queue. If not set, the action will go into the global queue, and won't be concurrency limited. For more info on . * `retries` - Configure how many times to retry the action if it fails, and how fast. For more info on the available retry options, see the . * `onDuplicateID` - What to do if an action with the same `id` is already enqueued. Can be set to `"ignore"` to prevent enqueuing, or `"error"` to throw an error. Defaults to `"error"`. * `startAt` - The time to start running a background action. The time defined must be formatted as an ISO string. * `shopifyShop` (optional) - The ID of the Shopify shop this action is associated with, used for [Shopify API rate limits](https://docs.gadget.dev/guides/plugins/shopify/building-shopify-apps#managing-shopify-api-rate-limits). * `id` - A unique identifier to use for the background action. Must be unique among all other background actions within its environment. If not set, a unique ID will be auto-generated and returned. * `priority` - How high to place this background action in its queue, `high` priority actions will be executed before `default` priority actions, and those before `low` priority actions. If not set, the `default` priority will be used. * `queue` - A queue to put this background action in that limits the maximum concurrency of all actions in the queue. If not set, the action will go into the global queue, and won't be concurrency limited. For more info on . * `retries` - Configure how many times to retry the action if it fails, and how fast. For more info on the available retry options, see the . * `onDuplicateID` - What to do if an action with the same `id` is already enqueued. Can be set to `"ignore"` to prevent enqueuing, or `"error"` to throw an error. Defaults to `"error"`. * `startAt` - The time to start running a background action. The time defined must be formatted as an ISO string. ##### Returns  Returns `void`. ### `queue`  `queue` allows you to place the background action in a dedicated queue and also enables you to control the maximum concurrency of all actions in the queue. ```typescript export const run: ActionRun = async ({ api }) => { await api.enqueue( api.someModelOrGlobalAction, {}, { queue: { name: "my-queue", maxConcurrency: 4, }, } ); }; ``` * `queue`: The name of the queue. * `maxConcurrency`: The maximum concurrency of all actions in the queue. If not set, the default will be set to `1` ### `retries`  `retries` allows you to configure how fast and how many times to retry the background action if it fails. `retries` can be set to an integer to make your background action retry that many times with the default retry schedule, or set to an object to configure exactly how many times and at what rate retries occur. ```typescript export const run: ActionRun = async ({ api }) => { await api.enqueue( api.someModelOrGlobalAction, {}, { retries: { retryCount: 5, maxInterval: 60000, backoffFactor: 2, initialInterval: 1000, randomizeInterval: true, }, // OR simply retries: 5 } ); }; ``` * `retryCount`: The maximum number of times to retry the operation if it keeps failing. The default is 6. * `maxInterval`: The maximum amount of time to delay a retry while exponentially backing off. The default is not set, so the retry can backoff indefinitely. * `backoffFactor`: The exponential backoff factor to use for calculating the retry delay for successive retries. Set this higher to delay longer. The default is 2. * `initialInterval`: How long to initially delay the first retry, in milliseconds. The default is 1000 ms. * `randomizeInterval`: Randomizes the delays between attempts by multiplying with a factor between 1 to 2. The default is false. ## Shopify  ### `preventCrossShopDataAccess(params, record, options)`  Enforce that the given record is only accessible by the current shop. For multi-tenant Shopify applications, this is key for enforcing data can only be accessed by the shop that owns it. ```typescript import { applyParams, save } from "gadget-server"; import { preventCrossShopDataAccess } from "gadget-server/shopify"; export const run: ActionRun = async ({ params, record, logger, api }) => { applyParams(params, record); await preventCrossShopDataAccess(params, record); await save(record); }; ``` * For existing records, this function verifies the record object has the same `shopId` as the shop in the current session, and throws if not. * For new records, this function sets the record's `shopId` to the current session's `shopId`. ##### Parameters  * `params` - Incoming parameters, validated against the current `shopId` * `record` - Record to validate or set the `shopId` on * `options`: * `shopBelongsToField`: Picks which belongs to relationship on a model is used for cross-shop validation. Must be the API identifier of a belongs to relationship to the `shopifyShop` model. If there are multiple relationships to the `shopifyShop` model on the passed-in `record`, this is a required parameter * `customerBelongsToField`: Picks which belongs to relationship on a model is used for cross-customer validation. Must be the API identifier of a belongs to relationship to the `shopifyCustomer` model. If there are multiple relationships to the `shopifyCustomer` model on the passed-in `record`, this is a required parameter * `enforceCustomerTenancy`: A `boolean` field that disables cross-customer validation. Defaults to `true` * `shopBelongsToField`: Picks which belongs to relationship on a model is used for cross-shop validation. Must be the API identifier of a belongs to relationship to the `shopifyShop` model. If there are multiple relationships to the `shopifyShop` model on the passed-in `record`, this is a required parameter * `customerBelongsToField`: Picks which belongs to relationship on a model is used for cross-customer validation. Must be the API identifier of a belongs to relationship to the `shopifyCustomer` model. If there are multiple relationships to the `shopifyCustomer` model on the passed-in `record`, this is a required parameter * `enforceCustomerTenancy`: A `boolean` field that disables cross-customer validation. Defaults to `true` ##### Returns  Returns void. Throws if the record is not accessible by the current shop. ### `preventCrossUserDataAccess(params, record, options)`  Enforce that the given record is only accessible by the current shop. For multi-tenant user applications, this is key for enforcing data can only be accessed by the user that owns it. ```typescript import { applyParams, save } from "gadget-server"; import { preventCrossUserDataAccess } from "gadget-server/auth"; export const run: ActionRun = async ({ params, record, logger, api }) => { applyParams(params, record); await preventCrossUserDataAccess(params, record); await save(record); }; ``` * For existing records, this function verifies the record object has the same `userId` as the user in the current session, and throws if not. * For new records, this function sets the record's `userId` to the current session's `userId`. ##### Parameters  * `params` - Incoming parameters, validated against the current `userId` * `record` - Record to validate or set the `userId` on * `options?`: * `userBelongsToField?`: Picks which belongs to relationship on a model is used for cross-user validation. Must be the API identifier of a belongs to relationship to the `user` model. If there are multiple relationships to the `user` model on the passed-in `record`, this is a required parameter * `userBelongsToField?`: Picks which belongs to relationship on a model is used for cross-user validation. Must be the API identifier of a belongs to relationship to the `user` model. If there are multiple relationships to the `user` model on the passed-in `record`, this is a required parameter ### `finishBulkOperation(record)`  Updates the state of a `bulkOperation` record from Shopify when the operation completes. ```typescript import { applyParams, save } from "gadget-server"; import { preventCrossShopDataAccess, finishBulkOperation, } from "gadget-server/shopify"; export const run: ActionRun = async ({ params, record, logger, api }) => { applyParams(params, record); await preventCrossShopDataAccess(params, record); await finishBulkOperation(record); await save(record); }; ``` ##### Parameters  * `record` - The `bulkOperation` record to update ### `globalShopifySync(params)`  Start running a global Shopify sync for all shops matching the `params`. This will create a `shopifySync` record and trigger a sync for each shop. ##### Parameters  * `params`: The params object for the sync * `apiKeys`: the list of Shopify app API keys to trigger a sync for, default: all enabled apps * `syncSince`: the start date to start syncing data from, default: all time * `syncSinceBy`: configure the timestamp field used to sort records when `syncSince` is used, options: `updated_at` (default), `created_at` * `syncLast`: the number of records to be synced * `syncLastBy`: configure the timestamp field used to sort records when `syncLast` is used, options: `updated_at` (default), `created_at` * `models`: the list of model api identifiers to trigger syncs for, default: all enabled models * `force`: should Gadget still run actions for records where the `updated_at` timestamps match * `priority`: the priority level for the sync, options: `low`, `default`, `high` (optional, defaults to normal priority) * `startReason`: a string reason to store on the created `shopifySync` records * `apiKeys`: the list of Shopify app API keys to trigger a sync for, default: all enabled apps * `syncSince`: the start date to start syncing data from, default: all time * `syncSinceBy`: configure the timestamp field used to sort records when `syncSince` is used, options: `updated_at` (default), `created_at` * `syncLast`: the number of records to be synced * `syncLastBy`: configure the timestamp field used to sort records when `syncLast` is used, options: `updated_at` (default), `created_at` * `models`: the list of model api identifiers to trigger syncs for, default: all enabled models * `force`: should Gadget still run actions for records where the `updated_at` timestamps match * `priority`: the priority level for the sync, options: `low`, `default`, `high` (optional, defaults to normal priority) * `startReason`: a string reason to store on the created `shopifySync` records ##### returns  Async function. Returns void. ## BigCommerce  BigCommerce functions can be imported from `gadget-server/bigcommerce`. ### `preventCrossStoreDataAccess(params, record, options)`  Enforce that the given record is only accessible by the current store. For multi-tenant BigCommerce applications, this is key for enforcing data can only be accessed by the store that owns it. ```typescript import { applyParams, save } from "gadget-server"; import { preventCrossStoreDataAccess } from "gadget-server/bigcommerce"; export const run: ActionRun = async ({ params, record, logger, api }) => { applyParams(params, record); await preventCrossStoreDataAccess(params, record); await save(record); }; ``` * For existing records, this function verifies the record object has the same `storeId` as the shop in the current session, otherwise an `Error` is thrown. * For new records, this function sets the record's `storeId` to the current session's `bigcommerceStore`. ##### Parameters  * `params` - Incoming parameters, validated against the current `storeId` * `record` - Record to validate or set the `storeId` on * `options`: * `storeBelongsToField`: Picks which belongs to relationship on a model is used for cross-shop validation. Must be the API identifier of a belongs to relationship to the `bigcommerce/store` model. If there are multiple relationships to the `bigcommerce/store` model on the passed-in `record`, this is a required parameter * `storeBelongsToField`: Picks which belongs to relationship on a model is used for cross-shop validation. Must be the API identifier of a belongs to relationship to the `bigcommerce/store` model. If there are multiple relationships to the `bigcommerce/store` model on the passed-in `record`, this is a required parameter ##### Returns  Returns void. Throws if the record is not accessible by the current shop. ## OpenAI  ### `openAIResponseStream`  Converts the result returned when making a request using the OpenAI connection with `stream: true` into a readable stream that Fastify can respond with. `openAIResponseStream` must be imported from `gadget-server/ai` a sub module of `gadget-server` ```typescript import { openAIResponseStream } from "gadget-server/ai"; // Using the openAIResponseStream function to convert an AsyncIterable into a Readable stream export const run: ActionRun = async ({ params, connections, logger, api }) => { const stream = await connections.openai.chat.completions.create({ model: "gpt-3.5-turbo", messages: [{ role: "user", content: "Hello!" }], stream: true, }); return openAIResponseStream(stream, { onComplete: (content) => { console.log(content); }, }); }; ``` ##### Parameters  * `stream` - An AsyncIterable containing OpenAI response parts. ##### returns  A [Readable](https://nodejs.org/api/stream.html#readable-streams) stream with the transformed content from the input stream. ## Vite  ### `gadget` Vite plugin [Framework v1.2.0+](https://docs.gadget.dev/guides/gadget-framework)  `gadget()` is a Vite plugin that automatically detects the frontend framework type used in the Vite config. The plugin modifies Vite configuration parameters so your app frontend can be run and deployed properly in Gadget. It is imported from `gadget-server/vite`. ```typescript import react from "@vitejs/plugin-react-swc"; import { defineConfig } from "vite"; import { gadget } from "gadget-server/vite"; export default defineConfig({ plugins: [gadget(), react()], clearScreen: false, }); ``` Currently, the plugin detects if your app frontend is using Remix or Vite + React Router. This plugin is required in your Vite config as of framework version 1.2.0. #### Injected configuration  The `gadget()` plugin injects the following configuration into your Vite config, depending on the frontend framework type detected: ##### Vite + React Router config  The following configuration is injected into your Vite config while developing: ```json // in gadget() output - Vite + React Router - developing { "build": { "manifest": true, "outDir": "./.gadget/vite-dist", "emptyOutDir": true } } ``` When building for production, the following configuration is outputted: ```json // in gadget() output - Vite + React Router - building production bundle { "build": { "manifest": true, "outDir": ".gadget/vite-dist", "emptyOutDir": true }, "base": "https://app-assets.gadget.dev/a/{applicationId}/{productionEnvironmentId}" } ``` Additional HTML tags are also outputted to help with setting up your app frontend. While developing, a dev harness is injected into your app frontend so Gadget can show you errors and popups: ```html // in dev harness setup ``` When connecting to Shopify (AppBridge v4) or BigCommerce, additional scripts are added to your frontend while developing and bundling: ```html // in injected HTML scripts for connections ``` ##### Remix config  The following configuration is injected into your Vite config while developing: ```json // in gadget() output - Remix - developing { "build": { "manifest": true } } ``` When building for production, the following configuration is outputted: ```json // in gadget() output - Remix - building production bundle { "build": { "manifest": true }, "base": "https://app-assets.gadget.dev/a/{applicationId}/{productionEnvironmentId}/" } ``` ## Remix  ### `remixViteOptions`  Gadget provides pre-defined Remix configuration in `remixViteOptions` to help set up your Vite app to work with Remix. It is imported from `gadget-server/remix`. ```typescript import { defineConfig } from "vite"; import { gadget } from "gadget-server/vite"; // import the pre-defined config import { remixViteOptions } from "gadget-server/remix"; import { vitePlugin as remix } from "@remix-run/dev"; export default defineConfig({ plugins: [ gadget(), remix({ // pass the config to the remix plugin ...remixViteOptions, }), ], }); ``` `remixViteOptions` contains the following configuration: ```json // in remixViteOptions { "buildDirectory": ".gadget/remix-dist/build", "appDirectory": "web", "future": { "unstable_optimizeDeps": true } } ``` We upload and serve the bundled Remix files inside the `.gadget/remix-dist/build` directory, so we need to specify the `buildDirectory` value from Remix. `appDirectory` references the directory containing your frontend files, which is `web` by default. Check out the [Remix documentation](https://remix.run/docs/en/main/file-conventions/vite-config) for more information about these parameters. ## Action contexts  Each Action and Global Action is passed a context object as its first and only argument. This context object contains all the inputs necessary for running an action, as well as some utilities. The context object is usually destructured in the `run` or `onSuccess` function arguments with curly braces: ```typescript export const run: ActionRun = async ({ api, record, params, ...rest }) => { // ... }; ``` ##### Properties  | Property | Type | Description | | --- | --- | --- | | `api` | instance of your app's client | A connected, authorized instance of the generated API client for the current Gadget application. See the [API Reference](https://docs.gadget.dev/api/example-app) for more details on this object's interface | | `params` | `Record` | The incoming data from the API call invoking this action | | `record` | `GadgetRecord` | The record this action is operating on. Only available in model actions, and not available in global actions. | | `session` | `Session` | A record representing the current user's session, if there is one | | `config` | `Record` | An object of all the environment variables created in Gadget's Environment Variables editor | | `connections` | `Connections` | An object containing client objects for all connections. Read the [connections guide](https://docs.gadget.dev/guides/plugins) to see what each connection provides | | `logger` | | A [logger](https://docs.gadget.dev/guides/development-tools/logger#logger-api) object suitable for emitting log entries viewable in Gadget's Log Viewer | | `emails` | `GadgetMailer` | A instance of the Gadget wrapper for NodeMailer, used to facilitate the sending of [emails](https://docs.gadget.dev/guides/plugins/authentication). | | `model` | `NotYetTyped` | An object describing the metadata for the model currently being operated on, like the fields and validations this model applies | | `currentAppUrl` | `string` | The current URL for your app and environment. e.g. `https://my-app--development.gadget.app` | | `trigger` | | An object containing what event invoked the model's Action to run and trigger the Run Effect. | | `request` | | An object describing the incoming HTTP request, if this action was triggered by an HTTP request | ## Exported types  The `gadget-server` package contains TypeScript types specific to your application, generated from your app's models and fields. This gives great type safety where your actions and routes can import a specific type that describes the required inputs and output. ### Actions  `gadget-server` exports a type for each Action's context argument, which is the data passed to the `run` or `onSuccess` function. The context type is named `ActionContext`, where `` is the capitalized name of your action, and `` is the capitalized name of your model. For example, if you have an action named `create` on a model named \`user, you can import the type for that action like this: ```typescript import { CreateUserActionContext } from "gadget-server"; ``` ### Global actions  `gadget-server` exports a type for each Global Action's context argument, which is the data passed to the `run` or `onSuccess` function. The context type is named `GlobalActionContext`, where `` is the capitalized name of your Global Action. For example, if you have a Global Action named `flipWidgets`, you can import the type for that action like this: ```typescript import { FlipWidgetsGlobalActionContext } from "gadget-server"; ``` ### Trigger descriptors  #### `api` trigger  `api` triggers describe calls to your Gadget app's GraphQL API, like those made by the JS client or in the GraphQL playground. ```json // in An example API trigger { "type": "api", "mutationName": "updateWidget", "rootModel": "widget", "rootAction": "update", "rawParams": { "id": "123", "widget": { "title": "New Widget Title", "inventoryCount": 10 } } } ``` ##### Properties  * `type`: will always be set to `"api"` * `mutationName`: the string name of the mutation called in the API * `rootModel`: the API identifier of the Model the mutation was called on. Can be different than the root-level model when invoking Nested Actions. Is not set for Global Actions. * `rootAction`: the API identifier of the Action triggered by the mutation. Can be different than the root-level action when invoking Nested Actions. * `rawParams`: the params passed to this API call, including any data for nested actions if passed #### `background-action` trigger  `background-action` triggers describe actions invoked by when [background actions](https://docs.gadget.dev/guides/actions/background) are dispatched and run. No other data is currently passed with this type of trigger. ```json // in an example background action trigger object { "attemptNumber": "3", "finalAttempt": false, "id": "job-1234567890ABCDEF", "mutationName": "createPost", "priority": "default", "rawParams": { "backgroundOptions": { "priority": "DEFAULT" } }, "rootAction": "create", "rootModel": "post", "type": "background-action" } ``` ##### Properties  * `type`: will always be set to `"background-action"`. * `attemptNumber`: if retries are enabled, this will be the number of the attempt. * `finalAttempt`: whether this is the final retry attempt. * `id`: the ID of the background action record. * `mutationName`: the string name of the GraphQL mutation called in the API. * `priority`: the priority of the background action. * `rawParams`: the params passed to this background action. * `rootAction`: the API identifier of the action triggered by the mutation. * `rootModel`: the model of the record being created. #### `scheduler` trigger  `scheduler` triggers describe actions invoked by the built-in [Scheduler](https://docs.gadget.dev/guides/actions#scheduler-trigger) within Gadget. No other data is currently passed with this type of trigger. ```json { "type": "scheduler" } ``` #### `shopify_sync` trigger  `shopify_sync` triggers describe actions run by Gadget's Shopify Sync, including daily syncs and manual syncs. ```json // in An example Shopify Sync trigger { "type": "shopify_sync", "shopId": "123456", "apiVersion": "2023-01", "shopifyScopes": ["read_products", "write_products"], "syncId": "1", "syncSince": null, "models": ["shopifyShop", "shopifyProduct"], "force": false, "priority": "high", "startReason": undefined // will be "scheduled" if Action ran via daily sync } ``` ##### Properties  * `type`: will always be set to `"shopify_sync"` * `shopId`: the identifier of the Shopify shop being synced * `apiVersion`: the version of the Shopify API being used for the sync * `shopifyScopes`: the available OAuth scopes of the Shopify shop being synced * `syncId`: the identifier of the sync record tracking the state of this sync (optional, only available if set) * `syncSince`: the specified date range of this sync (optional, only set if specified when the sync was started) * `models`: the list of model API identifiers that this sync will work on * `force`: indicates if this sync is being run in 'force' mode, which will always run actions even if the 'updated\_at' timestamps match between Gadget and Shopify * `priority`: the execution priority for this sync (optional, values: `low`, `default`, `high`) * `startReason`: the string describing the reason why this sync was started (optional, only set if specified when the sync began) #### `shopify_webhook` trigger  `shopify_webhook` triggers describe actions that occur in response to Shopify webhooks, such as 'products/update' or 'orders/create'. ```json // An example Shopify Webhook trigger { "type": "shopify_webhook", "topic": "products/update", "payload": { "id": 788032119674292900, "title": "Example T-Shirt", "body_html": "An example T-Shirt", "vendor": "Acme", "product_type": "Shirts", "created_at": null, "handle": "example-t-shirt" // ... etc matching Shopify's format exactly }, "shopId": "shop123", "retries": 0 } ``` ##### Properties  * `type`: will always be set to `"shopify_webhook"` * `topic`: the string representing the topic of the incoming webhook from Shopify, like `products/update` or `orders/create` * `payload`: the raw incoming payload from Shopify, which includes all the data sent by the webhook unchanged * `shopId`: the identifier for the Shopify store that received the webhook * `retries`: the number of times this webhook has been retried #### `shopify_oauth` trigger  `shopify_oauth` triggers describe actions invoked during the installation of an app through the Shopify Dev Dashboard connection process. No other data is currently passed with this type of trigger. ```json { "type": "shopify_oauth" } ``` #### `shopify_admin` trigger  `shopify_admin` triggers describe actions invoked during the installation of a Shopify app provisioned in the Shopify Admin. No other data is currently passed with this type of trigger. ```json { "type": "shopify_admin" } ``` ### `RequestData`  The `RequestData` type describes an incoming HTTP request being processed by an Action. **Note**: The `RequestData` object is passed to Actions and Global Actions, and is a read-only view of the incoming request. This is different than the object passed to HTTP Route handlers which has more properties only available in HTTP routes. ##### Properties  * `ip`: the requesting client's IP (also known as the `x-forwarded-for` header in other systems) * `url`: the requested URL (usually `/api/graphql`) * `method`: the HTTP request method, like `GET`, `POST`, etc * `userAgent`: the passed user agent string for this request * `headers`: a map of strings to strings or string arrays describing each incoming request header * `id`: a unique identifier assigned to this request by Gadget, used for logging and available in the `x-request-id` response header ```typescript export const run: ActionRun = async ({ api, request, logger, ...rest }) => { // log the IP of the client making this request logger.info(request.ip); }; ``` ### `RouteHandler`  The `RouteHandler` type describes the handler exported from a route file. ```typescript import type { RouteHandler } from "gadget-server"; const route: RouteHandler<{ Body: { name: string } }> = async ({ request, reply }) => { const { name } = request.body; await reply.send({ message: `Hello, ${name}!` }); }; route.options = { schema: { body: { type: "object", properties: { name: { type: "string" }, }, }, }, }; export default route; ``` This type is based on the `RouteHandlerMethod` type from Fastify, with the addition of the `options` property which can be used to define the request and response schemas for the route. Check out the [route configuration](https://docs.gadget.dev/guides/http-routes/route-configuration#route-options) guide for more details on the `options` property. Inside the route handler generic type, you can specify the expected request body, query parameters, and more. The generic type accepts a generic object `RouteGenericInterface` from Fastify containing five named properties: `Body`, `Querystring`, `Params`, `Headers` and `Reply`. The interfaces `Body`, `Querystring`, `Params` and `Headers` will be passed down through the route method into the route method handler request instance and the `Reply` interface to the reply instance. Examples: ```typescript import type { RouteHandler } from "gadget-server"; const route: RouteHandler<{ Body: { name: string } }> = async ({ request, reply }) => { // name is typed as a string const { name } = request.body; await reply.send({ message: `Hello, ${name}!` }); }; export default route; ``` ```typescript import type { RouteHandler } from "gadget-server"; const route: RouteHandler<{ Body: { age: number }, Querystring: { name: string } }> = async ({ request, reply }) => { const { name } = request.query; const { age } = request.body; // ... }; export default route; ``` ```typescript // api/routes/GET-[id].ts file import type { RouteHandler } from "gadget-server"; const route: RouteHandler<{ Params: { id: string } }> = async ({ request, reply }) => { const { id } = request.params; // ... }; export default route; ``` Check out the [HTTP Routes](https://docs.gadget.dev/guides/http-routes/route-structure) guide for more details on how to define routes with parameters. You can also add a type to a response payload to ensure that the response body is typed correctly. ```typescript import type { RouteHandler } from "gadget-server"; const route: RouteHandler<{ Reply: { message: string } }> = async ({ request, reply }) => { // reply.send has to be called with an object that matches the Reply type, // or it will have a type error await reply.send({ message: "Hello, world!" }); }; export default route; ``` Check out the [Fastify documentation](https://fastify.dev/docs/latest/Reference/TypeScript/#using-generics) for more details on the generic types available. ## Route handlers  ### Route context  All route handlers registered in [HTTP Routes](https://docs.gadget.dev/guides/http-routes) are passed a context object as their first and only argument: | Context key | Description | | --- | --- | | `request` | the object describing the incoming HTTP request | | `reply` | the object for sending an HTTP response | | `api` | a connected, authorized instance of the generated API client for the current Gadget application. See the [API Reference](https://docs.gadget.dev/api/example-app) for more details on this object's interface. | | `applicationSession` | a record representing the current user's session, if there is one. | | `applicationSessionID` | the ID of the record representing the current user's session, if there is one. | | `connections` | an object containing client objects for all connections. Read the [connections guide](https://docs.gadget.dev/guides/plugins) to see what each connection provides. | | `logger` | a [logger](https://docs.gadget.dev/guides/development-tools/logger) object suitable for emitting log entries viewable in Gadget's Log Viewer. | | `config` | an object of all the environment variables created in Gadget's Environment Variables editor. | | `currentAppUrl` | the current url for the environment. e.g. `https://my-app.gadget.app` | ### `request`  The `request` object passed in the route context describes the incoming HTTP request, with properties for accessing the HTTP request headers, the request body, the matched route, and more. `request` is powered by Fastify, a high-performance HTTP framework for nodejs. `Request` objects have the following fields: | `FastifyRequest` field | Description | | --- | --- | | `query` | the parsed query string from the incoming request, its format is specified by the route's `querystringParser` | | `body` | the request payload, see [Content-Type Parser](https://www.fastify.dev/docs/latest/Reference/ContentTypeParser/) for details on what request payloads Fastify natively parses and how to support other content types | | `params` | the params matching the URL | | `headers` | the headers getter and setter | | `method` | the HTTP method for the route, like `GET`, `POST`, or `DELETE` | | `raw` | the incoming HTTP request from Node core | | `id` | the request ID | | `log` | a logger instance for the incoming request | | `ip` | the IP address of the incoming request | | `hostname` | the host of the incoming request (derived from `X-Forwarded-Host` header when the trustProxy option is enabled). For HTTP/2 compatibility it returns :authority if no host header exists. | | `protocol` | the protocol of the incoming request (will always be `https` on Gadget) | | `method` | the method of the incoming request | | `url` | the URL of the incoming request | | `routerMethod` | the method defined for the router that is handling the request | | `routerPath` | the path pattern defined for the router that is handling the request | | `is404` | true if the request is being handled by a 404 error handler, false if it is not | | `socket` | the underlying connection of the incoming request | | `routeSchema` | the scheme definition set for the router that is handling the request | | `routeConfig` | the route config object | | `routeOptions` | the route option object passed when defining the route | | `bodyLimit` | either the server-wide limit or route-specific limit on the size of the request body | | `url` | the path of the URL to match this route | | `logLevel` | log level defined for this route | | `version` | a semver-compatible string that defines the version of the endpoint | | `exposeHeadRoute` | creates a sibling HEAD route for any GET routes | | `prefixTrailingSlash` | string used to determine how to handle passing / as a route with a prefix. | For more details on the `Request` object, see the [Fastify documentation](https://www.fastify.dev/docs/v3.29.x/Reference/Request/). ### `reply`  The `reply` object passed to each route in the context has functions for setting up and sending an HTTP response from your server for your route. The object is a `FastifyReply` object from [Fastify](https://www.fastify.dev/docs/v3.29.x/Reference/Reply/), a high-performance HTTP framework for nodejs. `Reply` objects have these functions and properties: | `Reply` property | Description | | --- | --- | | `code(statusCode)` | sets the status code | | `status(statusCode)` | an alias for `.code(statusCode)` | | `statusCode` | read and set the HTTP status code | | `header(name, value)` | sets a response header | | `headers(object)` | sets all the keys of the object as response headers | | `getHeader(name)` | retrieve the value of an already set header | | `getHeaders()` | gets a shallow copy of all current response headers | | `removeHeader(key)` | remove the value of a previously set header | | `hasHeader(name)` | determine if a header has been set | | `trailer(key, function)` | sets a response trailer | | `hasTrailer(key)` | determine if a trailer has been set | | `removeTrailer(key)` | remove the value of a previously set trailer | | `type(value)` | sets the header `Content-Type` | | `redirect([code,] dest)` | redirect to the specified URL with an optional status code. If not provided, the status code defaults to `302` | | `callNotFound()` | invokes the custom not found handler | | `serialize(payload)` | serializes the specified payload using the default JSON serializer or using the custom serializer (if one is set) and returns the serialized payload | | `serializer(function)` | sets a custom serializer for the payload | | `send(payload)` | sends the payload to the user, could be a plain text, a buffer, JSON, stream, or an Error object | | `sent` | a boolean value that you can use if you need to know if `send` has already been called | | `raw` | the [`http.ServerResponse`](https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_class_http_serverresponse) from Node core | | `log` | the logger instance of the incoming request | | `request` | the incoming request | | `context` | access the request's context property | For more details on the `Reply` object, see the [Fastify documentation](https://www.fastify.dev/docs/v3.29.x/Reference/Reply/). ### `CORSRouteOptions`  The `CORSRouteOptions` type describes the options for the `cors` route option or the argument passed to `server.setScopeCORS()`. | `CORSRouteOptions` property | Description | | --- | --- | | `origin` | the origin or origins to allow requests from. Can be a string, an array of strings, a boolean, or a function that returns a string or array of strings. Defaults to `"*"`, which allows requests from any origin. | | `credentials` | whether to allow requests with credentials. Can be a boolean, or a function that returns a boolean. Defaults to `false`. | | `methods` | the HTTP methods to allow requests for. Can be a string, an array of strings, or a function that returns a string or array of strings. Defaults to `["GET", "HEAD", "POST"]`. | | `allowedHeaders` | the headers to allow requests for. Can be a string, an array of strings, or a function that returns a string or array of strings. Defaults to `undefined`, which will send all headers allowed by the request's `Access-Control-Request-Headers` header. | | `exposedHeaders` | the headers to expose to the client. Can be a string, an array of strings, or a function that returns a string or array of strings. Defaults to `undefined`, which won't send any exposed headers for preflight requests. | | `maxAge` | the maximum age of the CORS preflight request in seconds. Can be a number, or a function that returns a number. Defaults to `undefined`, which won't send any max age headers for preflight requests. | | `cacheControl` | the cache control directive to use for the CORS preflight request. Can be a number, or a function that returns a number. Defaults to `undefined`, which won't send any cache control headers for preflight requests. | | `optionsSuccessStatus` | the status code to use for successful OPTIONS requests. Can be a number, or a function that returns a number. Defaults to `204`, which is the default status code for successful OPTIONS requests. | | `strictPreflight` | whether to enforce strict requirement of the CORS preflight request headers (Access-Control-Request-Method and Origin). Can be a boolean, or a function that returns a boolean. Defaults to `true`. | See more about the `CORSRouteOptions` type in the [HTTP Routes](https://docs.gadget.dev/guides/http-routes/route-configuration#cors-configuration) guide. ## `LoggerInstance`  The `LoggerInstance` object is a [Pino](https://getpino.io/#/) logger instance that can be used to log messages from your Gadget app. ```typescript import { logger } from "gadget-server"; // log a plain old string message at the info level logger.info("Hello world"); // log a structured object and a message at the info level logger.info({ key: "value", foo: 42, record: someRecord }, "making progress"); // log an error at the error level try { "foo" in null; } catch (error) { logger.error({ error }); } ``` `LoggerInstance` is a high performance alternative to `console.log` which is optimized for use in production. `console.log` logs will still appear in your [Log Viewer](https://docs.gadget.dev/guides/development-tools/logger) but are not recommended for use in production. The default log levels (and associated log functions) are `trace`, `debug`, `info`, `warn`, `error`, and `fatal`. View the [Pino logger docs](https://getpino.io/#/docs/api) for full API documentation. ### Passing structured data  An object can optionally be supplied as the first parameter to log messages. Each key and value of the object is stringified and written to your application's logs as JSON. ```typescript logger.info({ value: { foo: "bar" } }); ``` Log calls at any level can also pass `Error` objects to get an easy-to-digest log entry in the Log Viewer for the error, including its stack trace and any other associated details. To log `Error` objects, pass the object at the `error` key of the structured data: ```typescript const error = new Error("something went wrong"); logger.warn({ error }, "error encountered, continuing"); ```