@fourtwelvelabs/fetch-contentful 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +71 -0
- package/README.md +95 -2
- package/dist/cli/index.mjs +949 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/index.cjs +20 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +91 -10
- package/dist/index.d.ts +91 -10
- package/dist/index.mjs +20 -26
- package/dist/index.mjs.map +1 -1
- package/dist/tada/index.cjs +4 -0
- package/dist/tada/index.cjs.map +1 -0
- package/dist/tada/index.d.cts +106 -0
- package/dist/tada/index.d.ts +106 -0
- package/dist/tada/index.mjs +3 -0
- package/dist/tada/index.mjs.map +1 -0
- package/docs/tada.md +301 -0
- package/package.json +28 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.mjs"}
|
package/docs/tada.md
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# Typed queries with gql.tada
|
|
2
|
+
|
|
3
|
+
[gql.tada](https://gql-tada.0no.co) infers TypeScript types from your GraphQL
|
|
4
|
+
documents *as you write them* — no codegen step in your dev loop, no
|
|
5
|
+
hand-written result interfaces that drift from the query. This package ships
|
|
6
|
+
a companion that points it at a Contentful space: one command writes the
|
|
7
|
+
schema, the editor plugin config and the `graphql` helper, and
|
|
8
|
+
`fetchContentful` infers both the result and the variables from whatever
|
|
9
|
+
document you hand it.
|
|
10
|
+
|
|
11
|
+
- [Five-minute quickstart](#five-minute-quickstart)
|
|
12
|
+
- [What you get](#what-you-get)
|
|
13
|
+
- [Manual setup](#manual-setup)
|
|
14
|
+
- [Keeping the schema fresh](#keeping-the-schema-fresh)
|
|
15
|
+
- [CI recipe](#ci-recipe)
|
|
16
|
+
- [CLI reference](#cli-reference)
|
|
17
|
+
|
|
18
|
+
## Five-minute quickstart
|
|
19
|
+
|
|
20
|
+
**1. Install the peers.** gql.tada and its TypeScript plugin are optional
|
|
21
|
+
peer dependencies — they only matter at author time, so they belong in
|
|
22
|
+
`devDependencies`:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
yarn add -D gql.tada @0no-co/graphqlsp
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**2. Run `tada-init`.** With `CONTENTFUL_SPACE_ID` and
|
|
29
|
+
`CONTENTFUL_ACCESS_TOKEN` already in your environment (the same variables the
|
|
30
|
+
library reads at runtime — see the README's Configuration section), this
|
|
31
|
+
needs no arguments:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx @fourtwelvelabs/fetch-contentful tada-init
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
Configuring gql.tada for space abc123, environment master.
|
|
39
|
+
created contentful-schema.graphql (412.7 kB)
|
|
40
|
+
updated tsconfig.json
|
|
41
|
+
created src/graphql.ts
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
It downloads the schema as SDL, adds `@0no-co/graphqlsp` to
|
|
45
|
+
`compilerOptions.plugins` in your `tsconfig.json`, and writes `src/graphql.ts`.
|
|
46
|
+
Add `--dry-run` first if you'd rather see the changes before they happen.
|
|
47
|
+
|
|
48
|
+
**3. Restart the TypeScript server** so the editor picks up the plugin. In
|
|
49
|
+
VS Code: <kbd>⌘⇧P</kbd> → *TypeScript: Restart TS Server*.
|
|
50
|
+
|
|
51
|
+
**4. Write a query.** Autocomplete, field validation and types all come from
|
|
52
|
+
your space's real content model:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { fetchContentful } from '@fourtwelvelabs/fetch-contentful';
|
|
56
|
+
import { graphql } from './graphql';
|
|
57
|
+
|
|
58
|
+
const PageQuery = graphql(`
|
|
59
|
+
query Page($slug: String!) {
|
|
60
|
+
pageCollection(where: { slug: $slug }, limit: 1) {
|
|
61
|
+
items {
|
|
62
|
+
title
|
|
63
|
+
publishedAt
|
|
64
|
+
sectionsCollection {
|
|
65
|
+
items { heading }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
`);
|
|
71
|
+
|
|
72
|
+
const pages = await fetchContentful(PageQuery, { variables: { slug: 'home' } });
|
|
73
|
+
// ^? Array<{ title: string | null; publishedAt: string | null;
|
|
74
|
+
// sections: Array<{ heading: string | null }> }> | null
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
No type arguments, no result interface, no cast.
|
|
78
|
+
|
|
79
|
+
## What you get
|
|
80
|
+
|
|
81
|
+
**The result type follows the response you actually receive.** gql.tada types
|
|
82
|
+
the raw wire shape; `fetchContentful` then applies its own shaping and
|
|
83
|
+
unwrapping to *that* type, so `pageCollection.items` arrives as `pages` — an
|
|
84
|
+
array — in both the value and the type. `shapeResponseData: false` and
|
|
85
|
+
`unwrapRootField: false` are reflected too:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
const raw = await fetchContentful(PageQuery, {
|
|
89
|
+
variables: { slug: 'home' },
|
|
90
|
+
shapeResponseData: false,
|
|
91
|
+
unwrapRootField: false,
|
|
92
|
+
});
|
|
93
|
+
// ^? { pageCollection: { items: [...] } | null }
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Variables are required when — and only when — you have to supply them.**
|
|
97
|
+
`$preview` and `$locale` are injected by the library, so a document that
|
|
98
|
+
declares only those needs no `variables` at all. Anything else is enforced,
|
|
99
|
+
and a typo is a compile error rather than a silently dropped argument:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
await fetchContentful(PageQuery);
|
|
103
|
+
// ~~~~~~~~~ `variables` is required: $slug
|
|
104
|
+
|
|
105
|
+
await fetchContentful(PageQuery, { variables: { slgu: 'home' } });
|
|
106
|
+
// ~~~~ not a variable of this document
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Contentful's custom scalars are typed**, via the `ContentfulScalars` map
|
|
110
|
+
that `tada-init` wires up. Without it every one of them would be `unknown`:
|
|
111
|
+
|
|
112
|
+
| Scalar | TypeScript |
|
|
113
|
+
| --- | --- |
|
|
114
|
+
| `DateTime` | `string` (ISO 8601 at UTC) |
|
|
115
|
+
| `Dimension` | `number` (1–4000, image transforms) |
|
|
116
|
+
| `HexColor` | `string` (`"rgb:ffffff"`) |
|
|
117
|
+
| `JSON` | `JsonValue` (recursive JSON) |
|
|
118
|
+
| `Quality` | `number` (1–100) |
|
|
119
|
+
| `Circle` | `{ lat, lon, radius }` — a `_within_circle` search area |
|
|
120
|
+
| `Rectangle` | `{ topLeftLat, topLeftLon, bottomRightLat, bottomRightLon }` |
|
|
121
|
+
|
|
122
|
+
`Circle` and `Rectangle` are geographic search areas used by location
|
|
123
|
+
filters, not numbers — Contentful rejects anything else.
|
|
124
|
+
|
|
125
|
+
**Fragments work as usual.** `tada-init` re-exports the helpers you need
|
|
126
|
+
from the generated module:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import { readFragment, type FragmentOf } from './graphql';
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Manual setup
|
|
133
|
+
|
|
134
|
+
The CLI is a convenience, not a requirement. `ContentfulScalars` is exported
|
|
135
|
+
as a plain type, so you can hand it to `initGraphQLTada` yourself — useful if
|
|
136
|
+
you already generate the schema another way, or keep several spaces side by
|
|
137
|
+
side:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
// src/graphql.ts
|
|
141
|
+
import { initGraphQLTada } from 'gql.tada';
|
|
142
|
+
import type { ContentfulScalars } from '@fourtwelvelabs/fetch-contentful/tada';
|
|
143
|
+
import type { introspection } from './contentful-env.d.ts';
|
|
144
|
+
|
|
145
|
+
export const graphql = initGraphQLTada<{
|
|
146
|
+
introspection: introspection;
|
|
147
|
+
scalars: ContentfulScalars;
|
|
148
|
+
}>();
|
|
149
|
+
|
|
150
|
+
export { readFragment } from 'gql.tada';
|
|
151
|
+
export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada';
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
with, in `tsconfig.json`:
|
|
155
|
+
|
|
156
|
+
```jsonc
|
|
157
|
+
{
|
|
158
|
+
"compilerOptions": {
|
|
159
|
+
"plugins": [
|
|
160
|
+
{
|
|
161
|
+
"name": "@0no-co/graphqlsp",
|
|
162
|
+
"schema": "./contentful-schema.graphql",
|
|
163
|
+
"tadaOutputLocation": "./src/contentful-env.d.ts"
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The `/tada` entry point is types only — nothing in it imports gql.tada, and
|
|
171
|
+
the built module is empty — so importing it adds nothing to your bundle.
|
|
172
|
+
|
|
173
|
+
`fetchContentful` targets `TypedDocumentNode`, which gql.tada's documents
|
|
174
|
+
satisfy structurally. Documents from graphql-codegen's client preset satisfy
|
|
175
|
+
it too, so everything above applies equally if you use codegen instead.
|
|
176
|
+
|
|
177
|
+
## Keeping the schema fresh
|
|
178
|
+
|
|
179
|
+
The SDL file is a snapshot of your content model. After you publish a
|
|
180
|
+
content-type change, re-download it:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
npx @fourtwelvelabs/fetch-contentful tada-refresh
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
If nothing changed, the file is left untouched — same bytes, same mtime, no
|
|
187
|
+
entry in `git status`. Commit `contentful-schema.graphql`: it's what makes a
|
|
188
|
+
fresh checkout type-check without network access.
|
|
189
|
+
|
|
190
|
+
The gql.tada output file (`src/contentful-env.d.ts`) is regenerated from the
|
|
191
|
+
SDL by gql.tada itself, either by the editor plugin or with
|
|
192
|
+
`npx gql.tada generate-output`.
|
|
193
|
+
|
|
194
|
+
## CI recipe
|
|
195
|
+
|
|
196
|
+
Contentful can tell you when the content model changes; the schema file can
|
|
197
|
+
then update itself through a pull request rather than by someone remembering
|
|
198
|
+
to run a command.
|
|
199
|
+
|
|
200
|
+
**1. Add a Contentful webhook.** In *Settings → Webhooks*, create one that
|
|
201
|
+
fires on **ContentType → publish** and **unpublish**, pointing at GitHub's
|
|
202
|
+
`repository_dispatch` endpoint:
|
|
203
|
+
|
|
204
|
+
- URL: `https://api.github.com/repos/<owner>/<repo>/dispatches`
|
|
205
|
+
- Method: `POST`
|
|
206
|
+
- Headers: `Accept: application/vnd.github+json`,
|
|
207
|
+
`Authorization: Bearer <a GitHub token with contents:write>`
|
|
208
|
+
- Payload (custom): `{ "event_type": "contentful-schema-changed" }`
|
|
209
|
+
|
|
210
|
+
**2. Add the workflow:**
|
|
211
|
+
|
|
212
|
+
```yaml
|
|
213
|
+
# .github/workflows/contentful-schema.yml
|
|
214
|
+
name: Refresh Contentful schema
|
|
215
|
+
|
|
216
|
+
on:
|
|
217
|
+
repository_dispatch:
|
|
218
|
+
types: [contentful-schema-changed]
|
|
219
|
+
schedule:
|
|
220
|
+
- cron: '0 6 * * 1' # a Monday safety net, in case a webhook is missed
|
|
221
|
+
workflow_dispatch:
|
|
222
|
+
|
|
223
|
+
jobs:
|
|
224
|
+
refresh:
|
|
225
|
+
runs-on: ubuntu-latest
|
|
226
|
+
permissions:
|
|
227
|
+
contents: write
|
|
228
|
+
pull-requests: write
|
|
229
|
+
steps:
|
|
230
|
+
- uses: actions/checkout@v4
|
|
231
|
+
- uses: actions/setup-node@v4
|
|
232
|
+
with:
|
|
233
|
+
node-version: 22
|
|
234
|
+
cache: yarn
|
|
235
|
+
- run: yarn install --immutable
|
|
236
|
+
|
|
237
|
+
- run: yarn fetch-contentful tada-refresh
|
|
238
|
+
env:
|
|
239
|
+
CONTENTFUL_SPACE_ID: ${{ secrets.CONTENTFUL_SPACE_ID }}
|
|
240
|
+
CONTENTFUL_ACCESS_TOKEN: ${{ secrets.CONTENTFUL_ACCESS_TOKEN }}
|
|
241
|
+
|
|
242
|
+
# gql.tada's generated types follow the SDL.
|
|
243
|
+
- run: yarn gql.tada generate-output
|
|
244
|
+
|
|
245
|
+
# Fails the PR if the new schema broke an existing query.
|
|
246
|
+
- run: yarn tsc --noEmit
|
|
247
|
+
continue-on-error: true
|
|
248
|
+
|
|
249
|
+
- uses: peter-evans/create-pull-request@v7
|
|
250
|
+
with:
|
|
251
|
+
title: 'chore: refresh Contentful schema'
|
|
252
|
+
body: |
|
|
253
|
+
The Contentful content model changed. Review the schema diff and
|
|
254
|
+
check that every query still type-checks.
|
|
255
|
+
branch: chore/contentful-schema
|
|
256
|
+
commit-message: 'chore: refresh Contentful schema'
|
|
257
|
+
add-paths: |
|
|
258
|
+
contentful-schema.graphql
|
|
259
|
+
src/contentful-env.d.ts
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
`create-pull-request` opens a PR only when something actually changed, and
|
|
263
|
+
`tada-refresh` writes nothing when the schema is identical — so a no-op
|
|
264
|
+
webhook costs one CI run and produces no noise.
|
|
265
|
+
|
|
266
|
+
Use a **Content Delivery API** token here, not a Content Management one: it
|
|
267
|
+
is read-only, and introspection is all this needs.
|
|
268
|
+
|
|
269
|
+
## CLI reference
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
fetch-contentful tada-init [options] Set up gql.tada in this project
|
|
273
|
+
fetch-contentful tada-refresh [options] Re-download the schema only
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Both commands accept the same options. Flags win over environment variables.
|
|
277
|
+
|
|
278
|
+
| Option | Default | Notes |
|
|
279
|
+
| --- | --- | --- |
|
|
280
|
+
| `--space <id>` | `CONTENTFUL_SPACE_ID` | Required |
|
|
281
|
+
| `--environment <id>` | `CONTENTFUL_ENVIRONMENT`, then `master` | |
|
|
282
|
+
| `--token <token>` | `CONTENTFUL_ACCESS_TOKEN` | Content Delivery API token |
|
|
283
|
+
| `--schema-path <path>` | `./contentful-schema.graphql` | Where the SDL is written |
|
|
284
|
+
| `--tada-output <path>` | `./src/contentful-env.d.ts` | Where gql.tada writes its types |
|
|
285
|
+
| `--graphql-file <path>` | `./src/graphql.ts` | `tada-init` only |
|
|
286
|
+
| `--tsconfig <path>` | `./tsconfig.json` | `tada-init` only |
|
|
287
|
+
| `--dry-run` | | Print the changes; write nothing |
|
|
288
|
+
| `--force` | | Overwrite an existing graphql file |
|
|
289
|
+
| `--cwd <path>` | | Run against another directory |
|
|
290
|
+
| `-h`, `--help` | | |
|
|
291
|
+
|
|
292
|
+
The `NEXT_PUBLIC_`-prefixed variable names are read as fallbacks, exactly as
|
|
293
|
+
the library reads them at runtime.
|
|
294
|
+
|
|
295
|
+
`tada-init` is safe to run repeatedly. It updates the graphqlsp entry in your
|
|
296
|
+
tsconfig in place rather than adding a second one, preserves your comments
|
|
297
|
+
and formatting, and never overwrites `graphql.ts` unless you pass `--force`.
|
|
298
|
+
If it cannot patch a tsconfig safely, it changes nothing and prints the block
|
|
299
|
+
for you to paste in.
|
|
300
|
+
|
|
301
|
+
The access token is never printed — not in output, not in error messages.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fourtwelvelabs/fetch-contentful",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Foolproof, type-safe GraphQL fetching utility for Contentful with automatic query splitting, retries, and response shaping.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"contentful",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"type": "module",
|
|
24
24
|
"sideEffects": false,
|
|
25
|
+
"bin": "./dist/cli/index.mjs",
|
|
25
26
|
"main": "./dist/index.cjs",
|
|
26
27
|
"module": "./dist/index.mjs",
|
|
27
28
|
"types": "./dist/index.d.ts",
|
|
@@ -36,10 +37,21 @@
|
|
|
36
37
|
"default": "./dist/index.cjs"
|
|
37
38
|
}
|
|
38
39
|
},
|
|
40
|
+
"./tada": {
|
|
41
|
+
"import": {
|
|
42
|
+
"types": "./dist/tada/index.d.ts",
|
|
43
|
+
"default": "./dist/tada/index.mjs"
|
|
44
|
+
},
|
|
45
|
+
"require": {
|
|
46
|
+
"types": "./dist/tada/index.d.cts",
|
|
47
|
+
"default": "./dist/tada/index.cjs"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
39
50
|
"./package.json": "./package.json"
|
|
40
51
|
},
|
|
41
52
|
"files": [
|
|
42
53
|
"dist",
|
|
54
|
+
"docs",
|
|
43
55
|
"README.md",
|
|
44
56
|
"LICENSE"
|
|
45
57
|
],
|
|
@@ -59,18 +71,32 @@
|
|
|
59
71
|
"test:coverage": "vitest run --coverage",
|
|
60
72
|
"clean": "rm -rf dist .turbo"
|
|
61
73
|
},
|
|
74
|
+
"dependencies": {
|
|
75
|
+
"@graphql-typed-document-node/core": "^3.2.0"
|
|
76
|
+
},
|
|
62
77
|
"peerDependencies": {
|
|
78
|
+
"@0no-co/graphqlsp": "^1.12.0",
|
|
79
|
+
"gql.tada": "^1.8.0",
|
|
63
80
|
"graphql": "^16.14.2 || ^17.0.0"
|
|
64
81
|
},
|
|
82
|
+
"peerDependenciesMeta": {
|
|
83
|
+
"@0no-co/graphqlsp": {
|
|
84
|
+
"optional": true
|
|
85
|
+
},
|
|
86
|
+
"gql.tada": {
|
|
87
|
+
"optional": true
|
|
88
|
+
}
|
|
89
|
+
},
|
|
65
90
|
"devDependencies": {
|
|
66
91
|
"@repo/eslint-config": "*",
|
|
67
92
|
"@repo/typescript-config": "*",
|
|
68
93
|
"@types/node": "^22.15.3",
|
|
69
94
|
"@vitest/coverage-v8": "^2.1.9",
|
|
70
95
|
"eslint": "^9.39.1",
|
|
96
|
+
"gql.tada": "^1.11.3",
|
|
71
97
|
"graphql": "^16.14.2",
|
|
72
98
|
"tsup": "^8.5.1",
|
|
73
99
|
"typescript": "5.9.2",
|
|
74
100
|
"vitest": "^2.1.9"
|
|
75
101
|
}
|
|
76
|
-
}
|
|
102
|
+
}
|