Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/typed-prepare-variables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@shopify/shopify_function": minor
---

Add an opt-in codegen plugin that types prepare result JSON fields with the matching run query's generated variables type.
21 changes: 21 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Test

on:
pull_request:
push:
branches: [main]

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "24"
cache: npm
- run: npm ci
- run: npm test
114 changes: 114 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,117 @@
This repository contains the source code for the [`@shopify/shopify_function`][package] npm package.

[package]: https://npm.im/@shopify/shopify_function

## Typed prepare variables

A prepare target returns variables for its run target's input query. By default,
that result field is an untyped JSON scalar. The opt-in
`@shopify/shopify_function/prepare-variables.cjs` codegen plugin gives it the
run operation's generated variables type instead.

### Configure codegen

Replace **both** `typescript` and `typescript-operations` in your function's
`package.json` codegen configuration with this plugin:

```json
{
"codegen": {
"schema": "schema.graphql",
"documents": "src/**/*.graphql",
"generates": {
"generated/api.ts": {
"plugins": ["@shopify/shopify_function/prepare-variables.cjs"]
}
},
"config": {
"defaultScalarType": "unknown",
"scalars": { "JSON": "unknown" },
"prepareVariables": {
"CartValidationsGeneratePrepareResult.variables": "CartValidationsGenerateRun"
}
}
}
}
```

Mapping keys are schema `Type.field` paths, not target names or export names.
Values are **named GraphQL query operations**, not document filenames. Include
both prepare and run documents in `documents`. Multiple mappings are supported.
The plugin does not read extension TOML or infer prepare/run pairs.

For example, `src/cart_validations_generate_run.graphql` might contain:

```graphql
query CartValidationsGenerateRun($ruleSetHandle: String! = "rules-default") {
cart {
validations(handles: [$ruleSetHandle]) {
title
}
}
}
```

After running `shopify app function typegen`, the prepare result's `variables`
field accepts `CartValidationsGenerateRunQueryVariables`. Other JSON fields
remain unchanged. The field retains its schema nullability and optionality.

### Check your prepare implementation

Annotate the return type in TypeScript:

```ts
import type { CartValidationsGeneratePrepareResult } from "../generated/api";

export function cartValidationsGeneratePrepare(): CartValidationsGeneratePrepareResult {
return { variables: { ruleSetHandle: "rules-custom" } };
}
```

Or use a JSDoc return annotation in JavaScript:

```js
/** @returns {import('../generated/api').CartValidationsGeneratePrepareResult} */
export function cartValidationsGeneratePrepare() {
return { variables: { ruleSetHandle: "rules-custom" } };
}
```

Run TypeScript checking separately, for example with `tsc --noEmit`. JavaScript
projects also need `allowJs` and `checkJs`; enable `strict` (in particular
`strictNullChecks`) to catch invalid null values.

The generated contract catches missing required variables, incompatible values,
invalid enums and input objects, and unknown keys in object literals. Variables
with defaults can be omitted; a non-null variable with a default still rejects
explicit `null`. GraphQL list input coercion follows the operations generator's
`arrayInputCoercion` option.

**This does not add mandatory build or deploy validation.** The CLI's JavaScript
bundling does not type-check. TypeScript's usual limitations still apply: `any`,
assertions and disabled checking can bypass the contract; structural assignments
can carry extra keys even when fresh object literals would be rejected.

### Compatibility

The plugin composes the package's TypeScript schema and operations generators.
Codegen 6 emits used input objects and enums from the operations generator, so
those declarations take precedence over overlapping schema declarations. Operation
naming options, scalar mappings, readonly types and nullability options are
forwarded to the generators. Do not also list `typescript` or
`typescript-operations` for the same output.

Mapped fields must be `JSON` or `JSON!` on an object type. Anonymous operations
cannot be mapped. `globalNamespace`, `noExport`, `onlyEnums` and
`onlyOperationTypes` are not supported: the plugin needs exported schema and
operation declarations in the same file.

## Development

```sh
npm install
npm test
```

Tests generate types and check valid and invalid prepare results with the
TypeScript compiler, including JavaScript/JSDoc usage.
106 changes: 72 additions & 34 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"version": "2.0.1",
"description": "",
"main": "index.ts",
"scripts": {
"test": "node --test tests/*.test.cjs"
},
"keywords": [],
"author": "Surma <surma@shopify.com>",
"license": "Apache-2.0",
Expand All @@ -17,13 +20,15 @@
"dependencies": {
"@graphql-codegen/cli": "7.0.0",
"@graphql-codegen/typescript": "6.0.1",
"graphql-config": "5.1.3",
"@graphql-codegen/typescript-operations": "6.0.2",
"@graphql-tools/url-loader": "8.0.16",
"@graphql-tools/utils": "10.11.0",
"graphql": "^16.10.0",
"graphql-config": "5.1.3",
"typescript": "5.8.3"
},
"devDependencies": {
"@changesets/cli": "^2.28.1"
"@changesets/cli": "^2.28.1",
"@graphql-codegen/core": "6.0.0"
}
}
Loading
Loading