@contismo/sdk 0.1.0
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/.github/workflows/ci.yml +41 -0
- package/.github/workflows/release.yml +38 -0
- package/CODE_OF_CONDUCT.md +28 -0
- package/CONTRIBUTING.md +34 -0
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/clients/typescript/docs/codegen.md +107 -0
- package/clients/typescript/eslint.config.js +18 -0
- package/clients/typescript/package.json +87 -0
- package/clients/typescript/src/api-keys.ts +13 -0
- package/clients/typescript/src/cli/codegen-typescript-config.ts +36 -0
- package/clients/typescript/src/cli/generate-models.ts +94 -0
- package/clients/typescript/src/cli/generate.ts +44 -0
- package/clients/typescript/src/cli/import-from-sdk.ts +12 -0
- package/clients/typescript/src/cli/load-config.ts +1 -0
- package/clients/typescript/src/cli/main.ts +39 -0
- package/clients/typescript/src/cli/prettify-generated-types.ts +129 -0
- package/clients/typescript/src/cli/run-codegen.ts +58 -0
- package/clients/typescript/src/client.ts +152 -0
- package/clients/typescript/src/config.ts +41 -0
- package/clients/typescript/src/errors.ts +82 -0
- package/clients/typescript/src/execute.ts +104 -0
- package/clients/typescript/src/fetch-query.ts +123 -0
- package/clients/typescript/src/index.ts +41 -0
- package/clients/typescript/src/introspection.ts +165 -0
- package/clients/typescript/src/load-client.ts +47 -0
- package/clients/typescript/src/load-config.ts +64 -0
- package/clients/typescript/src/load-models.ts +35 -0
- package/clients/typescript/src/models.ts +16 -0
- package/clients/typescript/src/request-payload.ts +16 -0
- package/clients/typescript/src/resolve-codegen-paths.ts +31 -0
- package/clients/typescript/src/select.ts +22 -0
- package/clients/typescript/src/types.ts +55 -0
- package/clients/typescript/src/write-introspection.ts +13 -0
- package/clients/typescript/tests/api-keys.test.ts +14 -0
- package/clients/typescript/tests/client.test.ts +221 -0
- package/clients/typescript/tests/load-client.test.ts +126 -0
- package/clients/typescript/tests/load-config.test.ts +32 -0
- package/clients/typescript/tests/prettify-generated-types.test.ts +43 -0
- package/clients/typescript/tests/resolve-codegen-paths.test.ts +57 -0
- package/clients/typescript/tests/select.test.ts +53 -0
- package/clients/typescript/tests/write-introspection.test.ts +30 -0
- package/clients/typescript/tsconfig.json +16 -0
- package/clients/typescript/tsup.config.ts +32 -0
- package/clients/typescript/vitest.config.ts +8 -0
- package/package.json +17 -0
- package/pnpm-workspace.yaml +2 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
build:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- uses: pnpm/action-setup@v4
|
|
20
|
+
with:
|
|
21
|
+
version: 9
|
|
22
|
+
|
|
23
|
+
- uses: actions/setup-node@v4
|
|
24
|
+
with:
|
|
25
|
+
node-version: 22
|
|
26
|
+
cache: pnpm
|
|
27
|
+
|
|
28
|
+
- run: pnpm install --frozen-lockfile
|
|
29
|
+
working-directory: .
|
|
30
|
+
|
|
31
|
+
- run: pnpm typecheck
|
|
32
|
+
working-directory: .
|
|
33
|
+
|
|
34
|
+
- run: pnpm lint
|
|
35
|
+
working-directory: .
|
|
36
|
+
|
|
37
|
+
- run: pnpm test
|
|
38
|
+
working-directory: .
|
|
39
|
+
|
|
40
|
+
- run: pnpm build
|
|
41
|
+
working-directory: .
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: ${{ github.workflow }}-release
|
|
10
|
+
cancel-in-progress: false
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
publish:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
id-token: write
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- uses: pnpm/action-setup@v4
|
|
22
|
+
with:
|
|
23
|
+
version: 9
|
|
24
|
+
|
|
25
|
+
- uses: actions/setup-node@v4
|
|
26
|
+
with:
|
|
27
|
+
node-version: 22
|
|
28
|
+
cache: pnpm
|
|
29
|
+
registry-url: https://registry.npmjs.org
|
|
30
|
+
|
|
31
|
+
- run: pnpm install --frozen-lockfile
|
|
32
|
+
|
|
33
|
+
- run: pnpm build
|
|
34
|
+
|
|
35
|
+
- name: Publish to npm
|
|
36
|
+
run: pnpm --filter @contismo/sdk publish --no-git-checks --access public
|
|
37
|
+
env:
|
|
38
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our pledge
|
|
4
|
+
|
|
5
|
+
We pledge to make participation in our community a harassment-free experience for everyone.
|
|
6
|
+
|
|
7
|
+
## Our standards
|
|
8
|
+
|
|
9
|
+
Examples of behavior that contributes to a positive environment:
|
|
10
|
+
|
|
11
|
+
- Using welcoming and inclusive language
|
|
12
|
+
- Being respectful of differing viewpoints and experiences
|
|
13
|
+
- Gracefully accepting constructive criticism
|
|
14
|
+
- Focusing on what is best for the community
|
|
15
|
+
|
|
16
|
+
Examples of unacceptable behavior:
|
|
17
|
+
|
|
18
|
+
- Trolling, insulting or derogatory comments, and personal or political attacks
|
|
19
|
+
- Public or private harassment
|
|
20
|
+
- Publishing others' private information without explicit permission
|
|
21
|
+
|
|
22
|
+
## Enforcement
|
|
23
|
+
|
|
24
|
+
Project maintainers may remove, edit, or reject comments, commits, code, and other contributions that are not aligned to this Code of Conduct.
|
|
25
|
+
|
|
26
|
+
## Attribution
|
|
27
|
+
|
|
28
|
+
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1.
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thank you for contributing to the Contismo SDK.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
Requirements: Node.js 20+, pnpm 9.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
| Command | Description |
|
|
16
|
+
| ------- | ----------- |
|
|
17
|
+
| `pnpm test` | Run unit tests |
|
|
18
|
+
| `pnpm build` | Build `@contismo/sdk` |
|
|
19
|
+
| `pnpm typecheck` | Typecheck the TypeScript client |
|
|
20
|
+
| `pnpm lint` | Lint the TypeScript client |
|
|
21
|
+
|
|
22
|
+
## Releases
|
|
23
|
+
|
|
24
|
+
Bump the version in `clients/typescript/package.json`, tag the commit (e.g. `v0.1.0`), and push the tag. The release workflow publishes `@contismo/sdk` to npm.
|
|
25
|
+
|
|
26
|
+
## Pull requests
|
|
27
|
+
|
|
28
|
+
- Keep changes focused and well-tested.
|
|
29
|
+
- Match existing code style (`pnpm lint`).
|
|
30
|
+
- Update documentation when changing public API behavior.
|
|
31
|
+
|
|
32
|
+
## Live API tests
|
|
33
|
+
|
|
34
|
+
Optional integration tests against a real Contismo project are not run in CI. To test locally, construct a `ContismoClient` with your own endpoint, environment, and API key in a scratch script or test — do not commit secrets.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Contismo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Contismo SDK
|
|
2
|
+
|
|
3
|
+
Official client libraries for the [Contismo](https://contismo.com) GraphQL Content API.
|
|
4
|
+
|
|
5
|
+
## TypeScript
|
|
6
|
+
|
|
7
|
+
Install:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @contismo/sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Quick start (with `contismo.config.ts` in your project root):
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { ContismoClient } from "@contismo/sdk";
|
|
17
|
+
|
|
18
|
+
const client = new ContismoClient();
|
|
19
|
+
|
|
20
|
+
const posts = await client.fetch("BlogPost", {
|
|
21
|
+
select: { _id: true, title: true },
|
|
22
|
+
limit: 10,
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Use `new ContismoClient({ config: "./path/to/contismo.config.ts" })` when the config file is elsewhere, or `{ cwd: "…" }` when the process cwd differs. Credentials and codegen paths come from the config file; the generated model registry is loaded automatically.
|
|
27
|
+
|
|
28
|
+
For explicit credentials without a config file:
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
const client = new ContismoClient({
|
|
32
|
+
apiKey: "gql_…",
|
|
33
|
+
endpoint: "https://graphql.contismo.com",
|
|
34
|
+
project: "your-project-id",
|
|
35
|
+
environment: "production",
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### API keys
|
|
40
|
+
|
|
41
|
+
| Key prefix | Access |
|
|
42
|
+
| ---------- | ------------------------------------- |
|
|
43
|
+
| `gql_` | Queries and introspection only |
|
|
44
|
+
| `gqlw_` | Queries, mutations, and introspection |
|
|
45
|
+
|
|
46
|
+
Pass the key as `apiKey` in the client constructor with `Authorization: Bearer` applied automatically.
|
|
47
|
+
|
|
48
|
+
### Required configuration
|
|
49
|
+
|
|
50
|
+
| Option | Description |
|
|
51
|
+
| ------------- | ----------------------------------------------------- |
|
|
52
|
+
| `apiKey` | GraphQL API key from Studio (**Settings → API keys**) |
|
|
53
|
+
| `endpoint` | GraphQL URL (`https://graphql.contismo.com`) |
|
|
54
|
+
| `project` | Project ID from Studio (sent as `X-Project-Id`) |
|
|
55
|
+
| `environment` | Environment apiId (sent as `X-Environment`) |
|
|
56
|
+
|
|
57
|
+
### Mutations
|
|
58
|
+
|
|
59
|
+
Use a read/write key (`gqlw_…`) and `client.mutate()`:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
const client = new ContismoClient({
|
|
63
|
+
apiKey: "gqlw_…",
|
|
64
|
+
endpoint: "https://graphql.contismo.com",
|
|
65
|
+
project: "your-project-id",
|
|
66
|
+
environment: "production",
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
await client.mutate(
|
|
70
|
+
`
|
|
71
|
+
mutation CreatePost($input: BlogPostCreateInput!) {
|
|
72
|
+
createBlogPost(input: $input) { _id }
|
|
73
|
+
}
|
|
74
|
+
`,
|
|
75
|
+
{ input: { title: "Hello" } },
|
|
76
|
+
);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Generate types
|
|
80
|
+
|
|
81
|
+
Add `contismo.config.ts`:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { defineConfig } from "@contismo/sdk/config";
|
|
85
|
+
|
|
86
|
+
export default defineConfig({
|
|
87
|
+
client: {
|
|
88
|
+
apiKey: "gql_…",
|
|
89
|
+
endpoint: "https://graphql.contismo.com",
|
|
90
|
+
project: "your-project-id",
|
|
91
|
+
environment: "production",
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Run:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pnpm contismo generate
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Writes `contismo/schema.json`, `src/generated/contismo.ts`, and `contismo-models.ts` by default (`schemaDir`, `outputDir`, and file names are configurable). See [clients/typescript/docs/codegen.md](clients/typescript/docs/codegen.md) for options.
|
|
103
|
+
|
|
104
|
+
### Select-based reads
|
|
105
|
+
|
|
106
|
+
After `contismo generate`, use `fetch` / `fetchOne` with a Prisma-style `select` — no hand-written query:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import type { Entry_BlogPost } from "./generated/contismo";
|
|
110
|
+
|
|
111
|
+
const client = new ContismoClient();
|
|
112
|
+
|
|
113
|
+
const posts = await client.fetch<Entry_BlogPost>("BlogPost", {
|
|
114
|
+
select: {
|
|
115
|
+
_id: true,
|
|
116
|
+
title: true,
|
|
117
|
+
author: { name: true },
|
|
118
|
+
},
|
|
119
|
+
limit: 10,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const post = await client.fetchOne<Entry_BlogPost>("BlogPost", {
|
|
123
|
+
id: "…",
|
|
124
|
+
select: { _id: true, title: true },
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The first argument is the content model API id (`"BlogPost"`, `"Test"`, …). Nested objects in `select` become nested GraphQL selections; `true` marks a scalar leaf.
|
|
129
|
+
|
|
130
|
+
### Introspection
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
const schema = await client.introspect();
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Errors
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import { ContismoClient, isContismoError, ContismoGraphQLError } from "@contismo/sdk";
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
await client.query(`query { blogPost(id: "missing") { _id } }`);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (isContismoError(error) && error instanceof ContismoGraphQLError) {
|
|
145
|
+
console.error(error.code, error.message);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Other languages
|
|
151
|
+
|
|
152
|
+
Additional clients will live under `clients/<language>/` when available.
|
|
153
|
+
|
|
154
|
+
## Development
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
pnpm install
|
|
158
|
+
pnpm test
|
|
159
|
+
pnpm build
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# TypeScript types from your schema
|
|
2
|
+
|
|
3
|
+
## Quick path: `contismo generate`
|
|
4
|
+
|
|
5
|
+
1. Create `contismo.config.ts` in your project root:
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { defineConfig } from "@contismo/sdk/config";
|
|
9
|
+
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
client: {
|
|
12
|
+
apiKey: "gql_…",
|
|
13
|
+
endpoint: "https://graphql.contismo.com",
|
|
14
|
+
project: "your-project-id",
|
|
15
|
+
environment: "production",
|
|
16
|
+
},
|
|
17
|
+
codegen: {
|
|
18
|
+
outputDir: "./src/generated",
|
|
19
|
+
outputFile: "contismo.ts",
|
|
20
|
+
schemaDir: "./contismo",
|
|
21
|
+
// Or full paths: schema: "./contismo/schema.json", output: "./src/generated/contismo.ts"
|
|
22
|
+
// documents: ["src/**/*.graphql"], // optional: typed operations
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
2. Add a script (optional):
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"scripts": {
|
|
32
|
+
"generate": "contismo generate"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
3. Run:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pnpm generate
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This pulls your live GraphQL schema and generates TypeScript types from it, similar to `prisma generate`.
|
|
44
|
+
|
|
45
|
+
Generated entry types use readable fields (`string | null`, `EntryStatus | null`) instead of `Maybe<Scalars['DateTime']['output']>`. Set `codegen.prettify: false` to keep raw graphql-codegen output.
|
|
46
|
+
|
|
47
|
+
## Manual workflow
|
|
48
|
+
|
|
49
|
+
### Fetch the schema
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { ContismoClient, writeIntrospectionResult } from "@contismo/sdk";
|
|
53
|
+
|
|
54
|
+
const client = new ContismoClient({
|
|
55
|
+
apiKey: "gql_…",
|
|
56
|
+
endpoint: "https://graphql.contismo.com",
|
|
57
|
+
project: "your-project-id",
|
|
58
|
+
environment: "production",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const schema = await client.introspect();
|
|
62
|
+
await writeIntrospectionResult(schema, "./contismo/schema.json");
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Configure codegen yourself
|
|
66
|
+
|
|
67
|
+
See [GraphQL Code Generator](https://the-guild.dev/graphql/codegen) docs if you prefer a custom `codegen.ts`.
|
|
68
|
+
|
|
69
|
+
### Select-based reads (no hand-written query)
|
|
70
|
+
|
|
71
|
+
`contismo generate` writes `contismo-models.ts`. `new ContismoClient()` reads `contismo.config.*` and loads that registry automatically (paths follow your `codegen` settings).
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { ContismoClient } from "@contismo/sdk";
|
|
75
|
+
import type { Entry_BlogPost } from "./generated/contismo";
|
|
76
|
+
|
|
77
|
+
const client = new ContismoClient();
|
|
78
|
+
// Custom config path: new ContismoClient({ config: "./config/contismo.config.ts" });
|
|
79
|
+
|
|
80
|
+
const posts = await client.fetch<Entry_BlogPost>("BlogPost", {
|
|
81
|
+
select: {
|
|
82
|
+
_id: true,
|
|
83
|
+
title: true,
|
|
84
|
+
author: { name: true, avatar: { url: true } },
|
|
85
|
+
},
|
|
86
|
+
limit: 10,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const post = await client.fetchOne<Entry_BlogPost>("BlogPost", {
|
|
90
|
+
id: "…",
|
|
91
|
+
select: { _id: true, title: true },
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Use the content model **API id** (e.g. `"BlogPost"`, `"Test"`) as the first argument. Nested `select` objects mirror GraphQL field selection (`true` = scalar leaf, nested object = sub-selection).
|
|
96
|
+
|
|
97
|
+
### Raw GraphQL (optional)
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import type { Query } from "./generated/contismo";
|
|
101
|
+
|
|
102
|
+
const data = await client.query<Pick<Query, "blogPosts">>(`
|
|
103
|
+
query { blogPosts { _id title } }
|
|
104
|
+
`);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
When using `documents` in config, you can use `TypedDocumentNode` outputs with `client.query` / `client.mutate` as usual.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import eslint from "@eslint/js";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
import tseslint from "typescript-eslint";
|
|
4
|
+
|
|
5
|
+
export default tseslint.config(
|
|
6
|
+
eslint.configs.recommended,
|
|
7
|
+
...tseslint.configs.recommended,
|
|
8
|
+
{
|
|
9
|
+
languageOptions: {
|
|
10
|
+
globals: {
|
|
11
|
+
...globals.node,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
ignores: ["dist/**"],
|
|
17
|
+
},
|
|
18
|
+
);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@contismo/ts-client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official TypeScript client for the Contismo GraphQL Content Delivery API",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Contismo",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/contismo/cms-sdk.git",
|
|
10
|
+
"directory": "clients/typescript"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/contismo/cms-sdk#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/contismo/cms-sdk/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"contismo",
|
|
18
|
+
"cms",
|
|
19
|
+
"graphql",
|
|
20
|
+
"headless-cms",
|
|
21
|
+
"content-delivery"
|
|
22
|
+
],
|
|
23
|
+
"type": "module",
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20"
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"contismo": "./dist/cli/main.js"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist"
|
|
33
|
+
],
|
|
34
|
+
"main": "./dist/index.cjs",
|
|
35
|
+
"module": "./dist/index.js",
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"import": {
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"default": "./dist/index.js"
|
|
42
|
+
},
|
|
43
|
+
"require": {
|
|
44
|
+
"types": "./dist/index.d.cts",
|
|
45
|
+
"default": "./dist/index.cjs"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"./config": {
|
|
49
|
+
"import": {
|
|
50
|
+
"types": "./dist/config.d.ts",
|
|
51
|
+
"default": "./dist/config.js"
|
|
52
|
+
},
|
|
53
|
+
"require": {
|
|
54
|
+
"types": "./dist/config.d.cts",
|
|
55
|
+
"default": "./dist/config.cjs"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "tsup",
|
|
64
|
+
"dev": "tsup --watch",
|
|
65
|
+
"typecheck": "tsc --noEmit",
|
|
66
|
+
"lint": "eslint src tests",
|
|
67
|
+
"test": "vitest run",
|
|
68
|
+
"test:watch": "vitest"
|
|
69
|
+
},
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"@graphql-codegen/cli": "^5.0.3",
|
|
72
|
+
"@graphql-codegen/typescript": "^4.1.2",
|
|
73
|
+
"@graphql-codegen/typescript-operations": "^4.4.0",
|
|
74
|
+
"@graphql-codegen/typed-document-node": "^5.0.4",
|
|
75
|
+
"jiti": "^2.4.2"
|
|
76
|
+
},
|
|
77
|
+
"devDependencies": {
|
|
78
|
+
"@eslint/js": "^9.16.0",
|
|
79
|
+
"@types/node": "^22.10.1",
|
|
80
|
+
"eslint": "^9.16.0",
|
|
81
|
+
"globals": "^15.13.0",
|
|
82
|
+
"tsup": "^8.3.5",
|
|
83
|
+
"typescript": "^5.7.2",
|
|
84
|
+
"typescript-eslint": "^8.18.0",
|
|
85
|
+
"vitest": "^2.1.8"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Read-only GraphQL API key prefix (`gql_`). */
|
|
2
|
+
export const READ_ONLY_API_KEY_PREFIX = "gql_" as const;
|
|
3
|
+
|
|
4
|
+
/** Read/write GraphQL API key prefix (`gqlw_`). */
|
|
5
|
+
export const READ_WRITE_API_KEY_PREFIX = "gqlw_" as const;
|
|
6
|
+
|
|
7
|
+
export function isReadOnlyApiKey(apiKey: string): boolean {
|
|
8
|
+
return apiKey.startsWith(READ_ONLY_API_KEY_PREFIX) && !apiKey.startsWith(READ_WRITE_API_KEY_PREFIX);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function isReadWriteApiKey(apiKey: string): boolean {
|
|
12
|
+
return apiKey.startsWith(READ_WRITE_API_KEY_PREFIX);
|
|
13
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ContismoCodegenConfig } from "../config";
|
|
2
|
+
|
|
3
|
+
/** Default `@graphql-codegen/typescript` options for readable Contismo types. */
|
|
4
|
+
export const DEFAULT_TYPESCRIPT_CODEGEN_CONFIG = {
|
|
5
|
+
preResolveTypes: true,
|
|
6
|
+
skipTypename: true,
|
|
7
|
+
enumsAsTypes: true,
|
|
8
|
+
useTypeImports: true,
|
|
9
|
+
strictScalars: true,
|
|
10
|
+
defaultScalarType: "string",
|
|
11
|
+
maybeValue: "T | null",
|
|
12
|
+
scalars: {
|
|
13
|
+
ID: "string",
|
|
14
|
+
String: "string",
|
|
15
|
+
Boolean: "boolean",
|
|
16
|
+
Int: "number",
|
|
17
|
+
Float: "number",
|
|
18
|
+
DateTime: "string",
|
|
19
|
+
Date: "string",
|
|
20
|
+
JSON: "unknown",
|
|
21
|
+
Time: "string",
|
|
22
|
+
},
|
|
23
|
+
} as const satisfies Record<string, unknown>;
|
|
24
|
+
|
|
25
|
+
export function buildTypescriptCodegenConfig(
|
|
26
|
+
codegen?: ContismoCodegenConfig,
|
|
27
|
+
): Record<string, unknown> {
|
|
28
|
+
return {
|
|
29
|
+
...DEFAULT_TYPESCRIPT_CODEGEN_CONFIG,
|
|
30
|
+
...codegen?.typescript,
|
|
31
|
+
scalars: {
|
|
32
|
+
...DEFAULT_TYPESCRIPT_CODEGEN_CONFIG.scalars,
|
|
33
|
+
...codegen?.typescript?.scalars,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import type { IntrospectionQueryResult, IntrospectionType, IntrospectionTypeRef } from "../introspection";
|
|
3
|
+
import type { ModelRegistry } from "../models";
|
|
4
|
+
|
|
5
|
+
function unwrapNamedType(ref: IntrospectionTypeRef): { name: string; isList: boolean } | null {
|
|
6
|
+
let current: IntrospectionTypeRef | null | undefined = ref;
|
|
7
|
+
let isList = false;
|
|
8
|
+
|
|
9
|
+
while (current) {
|
|
10
|
+
if (current.kind === "LIST") {
|
|
11
|
+
isList = true;
|
|
12
|
+
current = current.ofType;
|
|
13
|
+
} else if (current.kind === "NON_NULL") {
|
|
14
|
+
current = current.ofType;
|
|
15
|
+
} else if (current.name) {
|
|
16
|
+
return { name: current.name, isList };
|
|
17
|
+
} else {
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function buildModelRegistry(introspection: IntrospectionQueryResult): ModelRegistry {
|
|
26
|
+
const queryType = introspection.__schema.types.find((t) => t.name === "Query");
|
|
27
|
+
if (!queryType?.fields) {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const byGraphqlType = new Map<
|
|
32
|
+
string,
|
|
33
|
+
{ singular?: string; plural?: string; whereInput?: string }
|
|
34
|
+
>();
|
|
35
|
+
|
|
36
|
+
for (const field of queryType.fields) {
|
|
37
|
+
const unwrapped = unwrapNamedType(field.type);
|
|
38
|
+
if (!unwrapped?.name.startsWith("Entry_")) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const graphqlType = unwrapped.name;
|
|
43
|
+
const existing = byGraphqlType.get(graphqlType) ?? {};
|
|
44
|
+
|
|
45
|
+
if (unwrapped.isList) {
|
|
46
|
+
existing.plural = field.name;
|
|
47
|
+
const whereArg = field.args?.find((arg) => arg.name === "where");
|
|
48
|
+
const whereType = whereArg ? unwrapNamedType(whereArg.type)?.name : undefined;
|
|
49
|
+
if (whereType) {
|
|
50
|
+
existing.whereInput = whereType;
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
existing.singular = field.name;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
byGraphqlType.set(graphqlType, existing);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const registry: ModelRegistry = {};
|
|
60
|
+
|
|
61
|
+
for (const [graphqlType, fields] of byGraphqlType) {
|
|
62
|
+
const singular = fields.singular ?? fields.plural;
|
|
63
|
+
const plural = fields.plural ?? fields.singular;
|
|
64
|
+
if (!singular || !plural) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const isSingleton = fields.plural === undefined;
|
|
69
|
+
|
|
70
|
+
registry[graphqlType.slice("Entry_".length)] = {
|
|
71
|
+
apiId: graphqlType.slice("Entry_".length),
|
|
72
|
+
singular,
|
|
73
|
+
plural,
|
|
74
|
+
graphqlType,
|
|
75
|
+
...(isSingleton ? { isSingleton: true } : {}),
|
|
76
|
+
...(fields.whereInput ? { whereInput: fields.whereInput } : {}),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return registry;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function writeModelsModule(registry: ModelRegistry, filePath: string): Promise<void> {
|
|
84
|
+
const content = `/* eslint-disable */
|
|
85
|
+
/** Generated by \`contismo generate\` — do not edit manually. */
|
|
86
|
+
import type { ModelRegistry } from "@contismo/sdk";
|
|
87
|
+
|
|
88
|
+
export const contismoModels = ${JSON.stringify(registry, null, 2)} as const satisfies ModelRegistry;
|
|
89
|
+
|
|
90
|
+
export type ContismoModelName = keyof typeof contismoModels;
|
|
91
|
+
`;
|
|
92
|
+
|
|
93
|
+
await writeFile(filePath, content, "utf8");
|
|
94
|
+
}
|