@sanity-labs/groq-content-mapper 0.1.0 → 0.1.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/README.md +40 -41
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
# @sanity-labs/groq-content-mapper
|
|
2
2
|
|
|
3
|
-
Typed `.groq` file imports for TypeScript 7.1 and later. A query kept in its own file is imported as a string that carries its result and params types, inferred from your Sanity schema
|
|
3
|
+
Typed `.groq` file imports for TypeScript 7.1 and later. A query kept in its own file is imported as a string that carries its result and params types, inferred from your Sanity schema.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- **TypeScript 7.1 or later.** Content mappers do not exist in earlier versions. 7.1 is currently a beta: `npm install -D typescript@beta`.
|
|
8
|
+
- **Node.js 22.12 or later.** On Node 26.5 and later, run `.groq` imports with `--experimental-import-text`, or with the preload below. On earlier Node, run `node --import @sanity-labs/groq-content-mapper/register`.
|
|
9
|
+
- **Type-check with `tsc --runExternalCode`.** Without that flag the mapper does not run, and `.groq` imports are not typed.
|
|
10
|
+
|
|
11
|
+
```jsonc
|
|
12
|
+
// package.json
|
|
13
|
+
{
|
|
14
|
+
"scripts": {
|
|
15
|
+
"typecheck": "tsc --noEmit --runExternalCode",
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
4
21
|
|
|
5
22
|
```groq
|
|
6
23
|
// postsBySearch.groq
|
|
@@ -18,18 +35,17 @@ const posts = await client.fetch(postsBySearch, {searchQuery: 'foo', start: 0, e
|
|
|
18
35
|
// ^? QueryResponse, with the params checked
|
|
19
36
|
```
|
|
20
37
|
|
|
21
|
-
|
|
38
|
+
Types come from the file as it is. Nothing is written to disk, and nothing is added to the global scope.
|
|
22
39
|
|
|
23
40
|
## What an import gives you
|
|
24
41
|
|
|
25
|
-
Each `.groq` file
|
|
42
|
+
Each `.groq` file has three exports:
|
|
26
43
|
|
|
27
|
-
- The default export is the query string, typed as its own
|
|
28
|
-
- `QueryResponse` is the result type
|
|
44
|
+
- The default export is the query string, typed as its own text, and still assignable to `string`. The type includes the file's trailing newline, because that is what the import evaluates to.
|
|
45
|
+
- `QueryResponse` is the result type. `[0]` is `T | null`.
|
|
29
46
|
- `QueryParams` is the object the `$` placeholders expect. A placeholder compared with a field takes the field's type, `match` takes a string, slice bounds are numbers, and anything the schema says nothing about is `unknown`. A query without placeholders has `QueryParams = Record<string, never>`.
|
|
30
47
|
|
|
31
48
|
```ts
|
|
32
|
-
// What './postsBySearch.groq' resolves to
|
|
33
49
|
export type QueryResponse = Array<{_id: string; title: string | null; slug: Slug | null}>
|
|
34
50
|
export type QueryParams = {searchQuery: string; start: number; end: number}
|
|
35
51
|
declare const query: '*[_type == "post" && title match $searchQuery] …\n' & {
|
|
@@ -39,13 +55,13 @@ declare const query: '*[_type == "post" && title match $searchQuery] …\n' & {
|
|
|
39
55
|
export default query
|
|
40
56
|
```
|
|
41
57
|
|
|
42
|
-
|
|
58
|
+
`client.fetch(query, params)` infers both types once `@sanity/client` includes [sanity-io/client#1373](https://github.com/sanity-io/client/pull/1373). With an older client, pass the type yourself: `client.fetch<QueryResponse>(query, params)`. `GroqQuery<Query, Result, Params>`, `GroqResult<Q>` and `GroqParams<Q>` are exported for the same shape.
|
|
43
59
|
|
|
44
60
|
### Imports
|
|
45
61
|
|
|
46
|
-
A value import needs
|
|
62
|
+
A value import needs `with {type: 'text'}`, the [standard text import](https://github.com/tc39/proposal-import-text). At run time the default export is the file's content.
|
|
47
63
|
|
|
48
|
-
A type-only import needs no attribute
|
|
64
|
+
A type-only import needs no attribute. It is erased, so the query string stays out of the bundle:
|
|
49
65
|
|
|
50
66
|
```ts
|
|
51
67
|
import type {QueryResponse} from './postsBySearch.groq'
|
|
@@ -53,31 +69,27 @@ import type {QueryResponse} from './postsBySearch.groq'
|
|
|
53
69
|
export function PostList(props: {posts: QueryResponse}) {}
|
|
54
70
|
```
|
|
55
71
|
|
|
56
|
-
When a file needs both the query and its types, write one statement
|
|
72
|
+
When a file needs both the query and its types, write one statement:
|
|
57
73
|
|
|
58
74
|
```ts
|
|
59
75
|
import postsBySearch, {type QueryResponse} from './postsBySearch.groq' with {type: 'text'}
|
|
60
76
|
```
|
|
61
77
|
|
|
62
|
-
|
|
78
|
+
A separate `import type` from the same file gets merged by ESLint's `import-x/no-duplicates` autofix, which drops or mangles the attribute.
|
|
63
79
|
|
|
64
80
|
## Setup
|
|
65
81
|
|
|
66
|
-
You need TypeScript 7.1 or later and Node 22.12 or later.
|
|
67
|
-
|
|
68
82
|
```sh
|
|
69
83
|
npm install -D @sanity-labs/groq-content-mapper typescript@beta
|
|
70
84
|
```
|
|
71
85
|
|
|
72
|
-
If
|
|
73
|
-
|
|
74
|
-
Write the schema file with the Sanity CLI:
|
|
86
|
+
If the app runs `.groq` imports on Node through the preload (rather than as a bundle), install the package as a regular dependency instead.
|
|
75
87
|
|
|
76
88
|
```sh
|
|
77
89
|
npx sanity schema extract
|
|
78
90
|
```
|
|
79
91
|
|
|
80
|
-
|
|
92
|
+
`schema` is required, and the tsconfig is the only configuration. Paths are relative to it:
|
|
81
93
|
|
|
82
94
|
```jsonc
|
|
83
95
|
// tsconfig.json
|
|
@@ -93,22 +105,11 @@ Register the mapper in `tsconfig.json`, next to `compilerOptions`. The tsconfig
|
|
|
93
105
|
}
|
|
94
106
|
```
|
|
95
107
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
```jsonc
|
|
99
|
-
// package.json
|
|
100
|
-
{
|
|
101
|
-
"scripts": {
|
|
102
|
-
"typecheck": "tsc --noEmit --runExternalCode",
|
|
103
|
-
},
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
The schema file is watched, so extracting it again retypes the imports. A file that cannot be typed, because the query does not parse or the schema is missing, still resolves, with `unknown` for what could not be inferred and a diagnostic saying why. GROQ syntax errors are reported at the `.groq` file as `error groq2`.
|
|
108
|
+
Type-check with the `tsc --noEmit --runExternalCode` script above. The schema file is watched, so extracting it again retypes the imports. A query that does not parse, or a missing schema, still resolves: the unknown parts are `unknown`, and the diagnostic says why. A GROQ syntax error is reported on the `.groq` file as `error groq2`.
|
|
108
109
|
|
|
109
110
|
### Editor
|
|
110
111
|
|
|
111
|
-
In VS Code or Cursor, install the TypeScript 7 extension (`TypeScriptTeam.native-preview`) and point it at the project's TypeScript
|
|
112
|
+
In VS Code or Cursor, install the TypeScript 7 extension (`TypeScriptTeam.native-preview`) and point it at the project's TypeScript. The extension bundles 7.0:
|
|
112
113
|
|
|
113
114
|
```jsonc
|
|
114
115
|
// .vscode/settings.json
|
|
@@ -122,7 +123,7 @@ Trust the workspace, and allow the extension to use the workspace's TypeScript w
|
|
|
122
123
|
|
|
123
124
|
## One schema per folder
|
|
124
125
|
|
|
125
|
-
A codebase that queries more than one project or dataset has one schema per folder. TypeScript
|
|
126
|
+
A codebase that queries more than one project or dataset has one schema per folder. TypeScript allows one mapper entry per extension, so list the folders inside that entry with `schemas`:
|
|
126
127
|
|
|
127
128
|
```jsonc
|
|
128
129
|
{
|
|
@@ -141,9 +142,9 @@ A codebase that queries more than one project or dataset has one schema per fold
|
|
|
141
142
|
}
|
|
142
143
|
```
|
|
143
144
|
|
|
144
|
-
A file
|
|
145
|
+
A file uses the schema of the deepest folder that contains it, and `schema` when no folder does. Without a `schema`, a file outside every folder gets a diagnostic.
|
|
145
146
|
|
|
146
|
-
|
|
147
|
+
A `package.json` `imports` alias resolves to the file, so the folder's schema still applies. Node, Bun, Deno and the bundlers resolve `imports` themselves:
|
|
147
148
|
|
|
148
149
|
```jsonc
|
|
149
150
|
// package.json
|
|
@@ -161,7 +162,7 @@ import products from '#queries/shop/products.groq' with {type: 'text'}
|
|
|
161
162
|
|
|
162
163
|
## Running the imports
|
|
163
164
|
|
|
164
|
-
|
|
165
|
+
`with {type: 'text'}` makes the runtime or bundler deliver the file as a string. Where one does not, one line fills in.
|
|
165
166
|
|
|
166
167
|
| Runtime or bundler | Setup |
|
|
167
168
|
| --------------------------------------------- | ----------------------------------------------------------------------- |
|
|
@@ -182,8 +183,6 @@ At run time `with {type: 'text'}` makes the runtime or bundler deliver the file
|
|
|
182
183
|
node --import @sanity-labs/groq-content-mapper/register main.ts
|
|
183
184
|
```
|
|
184
185
|
|
|
185
|
-
The preload uses `module.registerHooks` where Node has it (22.15 and later) and `module.register` before that.
|
|
186
|
-
|
|
187
186
|
### Vite, Vitest, Rollup
|
|
188
187
|
|
|
189
188
|
```ts
|
|
@@ -194,7 +193,7 @@ import {defineConfig} from 'vite'
|
|
|
194
193
|
export default defineConfig({plugins: [groqFiles()]})
|
|
195
194
|
```
|
|
196
195
|
|
|
197
|
-
Vite's dev server does not handle the attribute
|
|
196
|
+
Vite's dev server does not handle the attribute, so the plugin is needed there even when a production build would work without it.
|
|
198
197
|
|
|
199
198
|
### Next.js
|
|
200
199
|
|
|
@@ -205,7 +204,7 @@ import {withGroqFiles} from '@sanity-labs/groq-content-mapper/next'
|
|
|
205
204
|
export default withGroqFiles(nextConfig)
|
|
206
205
|
```
|
|
207
206
|
|
|
208
|
-
|
|
207
|
+
`withGroqFiles` covers both Turbopack and the webpack Next bundles. It also replaces the type check `next build` runs, which does not pass `--runExternalCode` and so fails on every `.groq` import. The same check runs with the flag after the compile. If the config already sets `typescript.ignoreBuildErrors`, no check is run.
|
|
209
208
|
|
|
210
209
|
### Jest
|
|
211
210
|
|
|
@@ -216,12 +215,12 @@ export default {
|
|
|
216
215
|
}
|
|
217
216
|
```
|
|
218
217
|
|
|
219
|
-
Jest
|
|
218
|
+
Jest rejects `type: 'text'` in its ES module loader, so this is for tests compiled to CommonJS, where the attribute is dropped.
|
|
220
219
|
|
|
221
220
|
## Limitations
|
|
222
221
|
|
|
223
|
-
- A comparison against `^`
|
|
224
|
-
-
|
|
222
|
+
- A comparison against `^` types its placeholder as `unknown`.
|
|
223
|
+
- `score()` is not typed yet, so `_score` is `null`, and a `[_score > 0]` filter empties the result type.
|
|
225
224
|
- `include` takes folders, not globs.
|
|
226
225
|
|
|
227
226
|
## License
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity-labs/groq-content-mapper",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Typed .groq file imports for TypeScript 7.1, from your Sanity schema",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"content-mapper",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@babel/generator": "^7.29.7",
|
|
44
44
|
"@babel/types": "^7.29.7",
|
|
45
|
-
"@sanity/codegen": "
|
|
45
|
+
"@sanity/codegen": "8.2.0",
|
|
46
46
|
"groq-js": "^2.0.0",
|
|
47
47
|
"valibot": "^1.4.2"
|
|
48
48
|
},
|