@graphql-suite/client 0.8.3 → 0.9.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 +193 -125
- package/client.d.ts +19 -0
- package/entity.d.ts +59 -0
- package/errors.d.ts +19 -0
- package/index.d.ts +11 -1
- package/index.js +25 -1
- package/infer.d.ts +107 -0
- package/package.json +13 -6
- package/query-builder.d.ts +12 -0
- package/schema-builder.d.ts +3 -0
- package/types.d.ts +50 -0
package/README.md
CHANGED
|
@@ -1,191 +1,259 @@
|
|
|
1
|
-
|
|
2
|
-
[](https://www.npmjs.com/package/drizzle-graphql-suite)
|
|
3
|
-
[](https://github.com/annexare/drizzle-graphql-suite/actions/workflows/ci.yml)
|
|
1
|
+
# @graphql-suite/client
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
> Part of [`graphql-suite`](https://github.com/annexare/graphql-suite).
|
|
4
|
+
> See also: [`schema`](https://github.com/annexare/graphql-suite/tree/main/packages/schema) | [`query`](https://github.com/annexare/graphql-suite/tree/main/packages/query)
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## Overview
|
|
10
|
-
|
|
11
|
-
`drizzle-graphql-suite` is a three-layer toolkit that turns your Drizzle ORM schema into a fully working GraphQL API with end-to-end type safety:
|
|
12
|
-
|
|
13
|
-
1. **Schema builder** — generates a complete GraphQL schema with CRUD operations, relation-level filtering, per-operation hooks, and runtime permissions from Drizzle table definitions.
|
|
14
|
-
2. **Client** — provides a type-safe GraphQL client that infers query/mutation types directly from your Drizzle schema, with full TypeScript support for filters, relations, and results.
|
|
15
|
-
3. **React Query hooks** — wraps the client in TanStack React Query hooks for caching, pagination, and mutations with automatic cache invalidation.
|
|
16
|
-
|
|
17
|
-
Inspired by [`drizzle-graphql`](https://github.com/drizzle-team/drizzle-graphql), rewritten with significant improvements including relation-level filtering, hooks, count queries, configurable schema generation, and code generation.
|
|
18
|
-
|
|
19
|
-
## Packages
|
|
20
|
-
|
|
21
|
-
| Subpath | Package | Description |
|
|
22
|
-
|---------|---------|-------------|
|
|
23
|
-
| `drizzle-graphql-suite/schema` | [`@drizzle-graphql-suite/schema`](packages/schema/README.md) | GraphQL schema builder with CRUD, filtering, hooks, permissions, and codegen |
|
|
24
|
-
| `drizzle-graphql-suite/client` | [`@drizzle-graphql-suite/client`](packages/client/README.md) | Type-safe GraphQL client with full Drizzle type inference |
|
|
25
|
-
| `drizzle-graphql-suite/query` | [`@drizzle-graphql-suite/query`](packages/query/README.md) | TanStack React Query hooks for the client |
|
|
6
|
+
Type-safe GraphQL client auto-generated from Drizzle schemas, with full TypeScript inference for queries, mutations, filters, and relations.
|
|
26
7
|
|
|
27
8
|
## Installation
|
|
28
9
|
|
|
29
10
|
```bash
|
|
30
|
-
bun add
|
|
11
|
+
bun add @graphql-suite/client
|
|
31
12
|
```
|
|
32
13
|
|
|
33
14
|
```bash
|
|
34
|
-
npm install
|
|
15
|
+
npm install @graphql-suite/client
|
|
35
16
|
```
|
|
36
17
|
|
|
37
|
-
|
|
18
|
+
Or install the full suite:
|
|
38
19
|
|
|
39
|
-
|
|
20
|
+
```bash
|
|
21
|
+
bun add graphql-suite
|
|
22
|
+
```
|
|
40
23
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
| `./client` | `drizzle-orm` >=0.44.0 |
|
|
45
|
-
| `./query` | `react` >=18.0.0, `@tanstack/react-query` >=5.0.0 |
|
|
24
|
+
```bash
|
|
25
|
+
npm install graphql-suite
|
|
26
|
+
```
|
|
46
27
|
|
|
47
28
|
## Quick Start
|
|
48
29
|
|
|
49
|
-
###
|
|
30
|
+
### From Drizzle Schema (recommended)
|
|
31
|
+
|
|
32
|
+
Use `createDrizzleClient` to create a client that infers all types directly from your Drizzle schema module — no code generation needed.
|
|
50
33
|
|
|
51
34
|
```ts
|
|
52
|
-
import {
|
|
53
|
-
import
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
user: {
|
|
61
|
-
query: {
|
|
62
|
-
before: async ({ context }) => {
|
|
63
|
-
if (!context.user) throw new Error('Unauthorized')
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
},
|
|
35
|
+
import { createDrizzleClient } from '@graphql-suite/client'
|
|
36
|
+
import * as schema from './db/schema'
|
|
37
|
+
|
|
38
|
+
const client = createDrizzleClient({
|
|
39
|
+
schema,
|
|
40
|
+
config: {
|
|
41
|
+
suffixes: { list: 's' },
|
|
42
|
+
tables: { exclude: ['session', 'verification'] },
|
|
67
43
|
},
|
|
44
|
+
url: '/api/graphql',
|
|
45
|
+
headers: { Authorization: 'Bearer ...' },
|
|
68
46
|
})
|
|
69
|
-
|
|
70
|
-
const yoga = createYoga({ schema })
|
|
71
|
-
const server = createServer(yoga)
|
|
72
|
-
server.listen(4000)
|
|
73
47
|
```
|
|
74
48
|
|
|
75
|
-
|
|
49
|
+
### From Schema Descriptor (separate-repo setups)
|
|
50
|
+
|
|
51
|
+
Use `createClient` with a codegen-generated schema descriptor when the client is in a separate repository that can't import the Drizzle schema directly.
|
|
76
52
|
|
|
77
53
|
```ts
|
|
78
|
-
import {
|
|
54
|
+
import { createClient } from '@graphql-suite/client'
|
|
55
|
+
import { schema, type EntityDefs } from './generated/entity-defs'
|
|
79
56
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
viewer: withPermissions(restricted('viewer', { post: { query: true } })),
|
|
85
|
-
}
|
|
57
|
+
const client = createClient<typeof schema, EntityDefs>({
|
|
58
|
+
schema,
|
|
59
|
+
url: '/api/graphql',
|
|
60
|
+
})
|
|
86
61
|
```
|
|
87
62
|
|
|
88
|
-
|
|
63
|
+
## EntityClient API
|
|
64
|
+
|
|
65
|
+
Access a typed entity client via `client.entity('name')`:
|
|
89
66
|
|
|
90
67
|
```ts
|
|
91
|
-
|
|
92
|
-
|
|
68
|
+
const user = client.entity('user')
|
|
69
|
+
```
|
|
93
70
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
71
|
+
Each entity client provides the following methods:
|
|
72
|
+
|
|
73
|
+
### `query(params)`
|
|
74
|
+
|
|
75
|
+
Fetch a list of records with optional filtering, pagination, and ordering. Returns `T[]`.
|
|
99
76
|
|
|
100
|
-
|
|
77
|
+
```ts
|
|
78
|
+
const users = await user.query({
|
|
101
79
|
select: {
|
|
102
80
|
id: true,
|
|
103
81
|
name: true,
|
|
104
|
-
|
|
82
|
+
email: true,
|
|
83
|
+
posts: {
|
|
84
|
+
id: true,
|
|
85
|
+
title: true,
|
|
86
|
+
comments: { id: true, body: true },
|
|
87
|
+
},
|
|
105
88
|
},
|
|
106
|
-
where: {
|
|
107
|
-
|
|
89
|
+
where: { email: { ilike: '%@example.com' } },
|
|
90
|
+
orderBy: { name: { direction: 'asc', priority: 1 } },
|
|
91
|
+
limit: 20,
|
|
92
|
+
offset: 0,
|
|
108
93
|
})
|
|
109
94
|
```
|
|
110
95
|
|
|
111
|
-
###
|
|
96
|
+
### `querySingle(params)`
|
|
112
97
|
|
|
113
|
-
|
|
114
|
-
import { GraphQLProvider, useEntity, useEntityList } from 'drizzle-graphql-suite/query'
|
|
115
|
-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
98
|
+
Fetch a single record. Returns `T | null`.
|
|
116
99
|
|
|
117
|
-
|
|
100
|
+
```ts
|
|
101
|
+
const found = await user.querySingle({
|
|
102
|
+
select: { id: true, name: true, email: true },
|
|
103
|
+
where: { id: { eq: 'some-uuid' } },
|
|
104
|
+
})
|
|
105
|
+
```
|
|
118
106
|
|
|
119
|
-
|
|
120
|
-
return (
|
|
121
|
-
<QueryClientProvider client={queryClient}>
|
|
122
|
-
<GraphQLProvider client={graphqlClient}>
|
|
123
|
-
<UserList />
|
|
124
|
-
</GraphQLProvider>
|
|
125
|
-
</QueryClientProvider>
|
|
126
|
-
)
|
|
127
|
-
}
|
|
107
|
+
### `count(params?)`
|
|
128
108
|
|
|
129
|
-
|
|
130
|
-
const user = useEntity('user')
|
|
131
|
-
const { data, isLoading } = useEntityList(user, {
|
|
132
|
-
select: { id: true, name: true, email: true },
|
|
133
|
-
limit: 20,
|
|
134
|
-
})
|
|
109
|
+
Count matching records. Returns `number`.
|
|
135
110
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
111
|
+
```ts
|
|
112
|
+
const total = await user.count({
|
|
113
|
+
where: { role: { eq: 'admin' } },
|
|
114
|
+
})
|
|
139
115
|
```
|
|
140
116
|
|
|
141
|
-
|
|
117
|
+
### `insert(params)`
|
|
118
|
+
|
|
119
|
+
Insert multiple records. Returns `T[]` of inserted rows.
|
|
142
120
|
|
|
143
|
-
|
|
121
|
+
```ts
|
|
122
|
+
const created = await user.insert({
|
|
123
|
+
values: [
|
|
124
|
+
{ name: 'Alice', email: 'alice@example.com' },
|
|
125
|
+
{ name: 'Bob', email: 'bob@example.com' },
|
|
126
|
+
],
|
|
127
|
+
returning: { id: true, name: true },
|
|
128
|
+
})
|
|
129
|
+
```
|
|
144
130
|
|
|
145
|
-
###
|
|
131
|
+
### `insertSingle(params)`
|
|
132
|
+
|
|
133
|
+
Insert a single record. Returns `T | null`.
|
|
146
134
|
|
|
147
135
|
```ts
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
136
|
+
const created = await user.insertSingle({
|
|
137
|
+
values: { name: 'Alice', email: 'alice@example.com' },
|
|
138
|
+
returning: { id: true, name: true },
|
|
139
|
+
})
|
|
140
|
+
```
|
|
152
141
|
|
|
153
|
-
|
|
142
|
+
### `update(params)`
|
|
154
143
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
144
|
+
Update records matching a filter. Returns `T[]` of updated rows.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
const updated = await user.update({
|
|
148
|
+
set: { role: 'admin' },
|
|
149
|
+
where: { id: { eq: 'some-uuid' } },
|
|
150
|
+
returning: { id: true, role: true },
|
|
159
151
|
})
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### `delete(params)`
|
|
155
|
+
|
|
156
|
+
Delete records matching a filter. Returns `T[]` of deleted rows.
|
|
160
157
|
|
|
161
|
-
|
|
158
|
+
```ts
|
|
159
|
+
const deleted = await user.delete({
|
|
160
|
+
where: { deletedAt: { isNotNull: true } },
|
|
161
|
+
returning: { id: true },
|
|
162
|
+
})
|
|
162
163
|
```
|
|
163
164
|
|
|
164
|
-
|
|
165
|
+
## Schema Descriptor
|
|
166
|
+
|
|
167
|
+
A `SchemaDescriptor` is a runtime object mapping entity names to their operation names, fields, and relations. It tells the client how to build GraphQL queries.
|
|
168
|
+
|
|
169
|
+
### `buildSchemaDescriptor(schema, config?)`
|
|
170
|
+
|
|
171
|
+
Builds a `SchemaDescriptor` from a Drizzle schema module. This is called internally by `createDrizzleClient`, but can be used directly if you need the descriptor.
|
|
165
172
|
|
|
166
173
|
```ts
|
|
167
|
-
|
|
168
|
-
import
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
174
|
+
import { buildSchemaDescriptor } from '@graphql-suite/client'
|
|
175
|
+
import * as schema from './db/schema'
|
|
176
|
+
|
|
177
|
+
const descriptor = buildSchemaDescriptor(schema, {
|
|
178
|
+
suffixes: { list: 's' },
|
|
179
|
+
tables: { exclude: ['session'] },
|
|
180
|
+
pruneRelations: { 'user.sessions': false },
|
|
181
|
+
})
|
|
182
|
+
```
|
|
172
183
|
|
|
173
|
-
|
|
184
|
+
Config options mirror the schema package: `mutations`, `suffixes`, `tables.exclude`, and `pruneRelations`.
|
|
174
185
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
186
|
+
## Type Inference
|
|
187
|
+
|
|
188
|
+
The client provides end-to-end type inference from Drizzle schema to query results:
|
|
189
|
+
|
|
190
|
+
### `InferEntityDefs<TSchema, TConfig>`
|
|
191
|
+
|
|
192
|
+
Infers the complete entity type definitions from a Drizzle schema module, including fields (with Date → string wire conversion), relations, filters, insert inputs, update inputs, and orderBy types.
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
import type { InferEntityDefs } from '@graphql-suite/client'
|
|
196
|
+
import type * as schema from './db/schema'
|
|
197
|
+
|
|
198
|
+
type MyEntityDefs = InferEntityDefs<typeof schema, { tables: { exclude: ['session'] } }>
|
|
178
199
|
```
|
|
179
200
|
|
|
180
|
-
|
|
201
|
+
### `InferResult<TDefs, TEntity, TSelect>`
|
|
181
202
|
|
|
182
|
-
|
|
203
|
+
Infers the return type of a query from the `select` object. Only selected scalar fields and relations are included in the result type. Relations resolve to arrays or `T | null` based on their cardinality.
|
|
183
204
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
205
|
+
### `SelectInput<TDefs, TEntity>`
|
|
206
|
+
|
|
207
|
+
Describes the valid shape of a `select` parameter — `true` for scalar fields, nested objects for relations.
|
|
208
|
+
|
|
209
|
+
## Dynamic URL and Headers
|
|
210
|
+
|
|
211
|
+
Both `url` and `headers` support static values or functions (sync or async) that are called per-request:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
const client = createDrizzleClient({
|
|
215
|
+
schema,
|
|
216
|
+
config: {},
|
|
217
|
+
// Dynamic URL
|
|
218
|
+
url: () => `${getApiBase()}/graphql`,
|
|
219
|
+
// Async headers (e.g., refresh token)
|
|
220
|
+
headers: async () => ({
|
|
221
|
+
Authorization: `Bearer ${await getAccessToken()}`,
|
|
222
|
+
}),
|
|
223
|
+
})
|
|
187
224
|
```
|
|
188
225
|
|
|
189
|
-
##
|
|
226
|
+
## Error Handling
|
|
227
|
+
|
|
228
|
+
The client throws two error types:
|
|
190
229
|
|
|
191
|
-
|
|
230
|
+
### `GraphQLClientError`
|
|
231
|
+
|
|
232
|
+
Thrown when the server returns GraphQL errors in the response body.
|
|
233
|
+
|
|
234
|
+
- **`errors`** — `Array<{ message, locations?, path?, extensions? }>` — individual GraphQL errors
|
|
235
|
+
- **`status`** — HTTP status code (usually `200`)
|
|
236
|
+
- **`message`** — concatenated error messages
|
|
237
|
+
|
|
238
|
+
### `NetworkError`
|
|
239
|
+
|
|
240
|
+
Thrown when the HTTP request fails (network error or non-2xx status).
|
|
241
|
+
|
|
242
|
+
- **`status`** — HTTP status code (`0` for network failures)
|
|
243
|
+
- **`message`** — error description
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
import { GraphQLClientError, NetworkError } from '@graphql-suite/client'
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const users = await client.entity('user').query({
|
|
250
|
+
select: { id: true, name: true },
|
|
251
|
+
})
|
|
252
|
+
} catch (e) {
|
|
253
|
+
if (e instanceof GraphQLClientError) {
|
|
254
|
+
console.error('GraphQL errors:', e.errors)
|
|
255
|
+
} else if (e instanceof NetworkError) {
|
|
256
|
+
console.error('Network error:', e.status, e.message)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
package/client.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { BuildSchemaConfig } from '@graphql-suite/schema';
|
|
2
|
+
import { type EntityClient } from './entity';
|
|
3
|
+
import type { InferEntityDefs } from './infer';
|
|
4
|
+
import type { AnyEntityDefs, ClientConfig, EntityDefsRef, SchemaDescriptor } from './types';
|
|
5
|
+
export declare class GraphQLClient<TSchema extends SchemaDescriptor, TDefs extends AnyEntityDefs = AnyEntityDefs> {
|
|
6
|
+
private url;
|
|
7
|
+
private schema;
|
|
8
|
+
private headers?;
|
|
9
|
+
constructor(config: ClientConfig<TSchema>);
|
|
10
|
+
entity<TEntityName extends string & keyof TSchema & keyof TDefs>(entityName: TEntityName): EntityClient<EntityDefsRef<TDefs>, TEntityName>;
|
|
11
|
+
execute(query: string, variables?: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
12
|
+
}
|
|
13
|
+
export type DrizzleClientConfig<TSchema extends Record<string, unknown>, TConfig extends BuildSchemaConfig> = {
|
|
14
|
+
schema: TSchema;
|
|
15
|
+
config: TConfig;
|
|
16
|
+
url: string | (() => string);
|
|
17
|
+
headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
18
|
+
};
|
|
19
|
+
export declare function createDrizzleClient<TSchema extends Record<string, unknown>, const TConfig extends BuildSchemaConfig>(options: DrizzleClientConfig<TSchema, TConfig>): GraphQLClient<SchemaDescriptor, InferEntityDefs<TSchema, TConfig>>;
|
package/entity.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { AnyEntityDefs, EntityDef, EntityDefsRef, EntityDescriptor, InferResult, SchemaDescriptor } from './types';
|
|
2
|
+
type ResolveEntity<TRef extends EntityDefsRef<AnyEntityDefs>, TEntityName extends string> = TRef['__defs'][TEntityName] & EntityDef;
|
|
3
|
+
export interface EntityClient<TRef extends EntityDefsRef<AnyEntityDefs>, TEntityName extends string> {
|
|
4
|
+
query<S extends Record<string, unknown>>(params: {
|
|
5
|
+
select: S;
|
|
6
|
+
where?: ResolveEntity<TRef, TEntityName> extends {
|
|
7
|
+
filters: infer F;
|
|
8
|
+
} ? F : never;
|
|
9
|
+
limit?: number;
|
|
10
|
+
offset?: number;
|
|
11
|
+
orderBy?: ResolveEntity<TRef, TEntityName> extends {
|
|
12
|
+
orderBy: infer O;
|
|
13
|
+
} ? O : never;
|
|
14
|
+
}): Promise<InferResult<TRef['__defs'], ResolveEntity<TRef, TEntityName>, S>[]>;
|
|
15
|
+
querySingle<S extends Record<string, unknown>>(params: {
|
|
16
|
+
select: S;
|
|
17
|
+
where?: ResolveEntity<TRef, TEntityName> extends {
|
|
18
|
+
filters: infer F;
|
|
19
|
+
} ? F : never;
|
|
20
|
+
offset?: number;
|
|
21
|
+
orderBy?: ResolveEntity<TRef, TEntityName> extends {
|
|
22
|
+
orderBy: infer O;
|
|
23
|
+
} ? O : never;
|
|
24
|
+
}): Promise<InferResult<TRef['__defs'], ResolveEntity<TRef, TEntityName>, S> | null>;
|
|
25
|
+
count(params?: {
|
|
26
|
+
where?: ResolveEntity<TRef, TEntityName> extends {
|
|
27
|
+
filters: infer F;
|
|
28
|
+
} ? F : never;
|
|
29
|
+
}): Promise<number>;
|
|
30
|
+
insert<S extends Record<string, unknown>>(params: {
|
|
31
|
+
values: ResolveEntity<TRef, TEntityName> extends {
|
|
32
|
+
insertInput: infer I;
|
|
33
|
+
} ? I[] : never;
|
|
34
|
+
returning?: S;
|
|
35
|
+
}): Promise<InferResult<TRef['__defs'], ResolveEntity<TRef, TEntityName>, S>[]>;
|
|
36
|
+
insertSingle<S extends Record<string, unknown>>(params: {
|
|
37
|
+
values: ResolveEntity<TRef, TEntityName> extends {
|
|
38
|
+
insertInput: infer I;
|
|
39
|
+
} ? I : never;
|
|
40
|
+
returning?: S;
|
|
41
|
+
}): Promise<InferResult<TRef['__defs'], ResolveEntity<TRef, TEntityName>, S> | null>;
|
|
42
|
+
update<S extends Record<string, unknown>>(params: {
|
|
43
|
+
set: ResolveEntity<TRef, TEntityName> extends {
|
|
44
|
+
updateInput: infer U;
|
|
45
|
+
} ? U : never;
|
|
46
|
+
where?: ResolveEntity<TRef, TEntityName> extends {
|
|
47
|
+
filters: infer F;
|
|
48
|
+
} ? F : never;
|
|
49
|
+
returning?: S;
|
|
50
|
+
}): Promise<InferResult<TRef['__defs'], ResolveEntity<TRef, TEntityName>, S>[]>;
|
|
51
|
+
delete<S extends Record<string, unknown>>(params: {
|
|
52
|
+
where?: ResolveEntity<TRef, TEntityName> extends {
|
|
53
|
+
filters: infer F;
|
|
54
|
+
} ? F : never;
|
|
55
|
+
returning?: S;
|
|
56
|
+
}): Promise<InferResult<TRef['__defs'], ResolveEntity<TRef, TEntityName>, S>[]>;
|
|
57
|
+
}
|
|
58
|
+
export declare function createEntityClient<TRef extends EntityDefsRef<AnyEntityDefs>, TEntityName extends string>(entityName: string, entityDef: EntityDescriptor, schema: SchemaDescriptor, executeGraphQL: (query: string, variables: Record<string, unknown>) => Promise<Record<string, unknown>>): EntityClient<TRef, TEntityName>;
|
|
59
|
+
export {};
|
package/errors.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type GraphQLErrorLocation = {
|
|
2
|
+
line: number;
|
|
3
|
+
column: number;
|
|
4
|
+
};
|
|
5
|
+
export type GraphQLErrorEntry = {
|
|
6
|
+
message: string;
|
|
7
|
+
locations?: GraphQLErrorLocation[];
|
|
8
|
+
path?: (string | number)[];
|
|
9
|
+
extensions?: Record<string, unknown>;
|
|
10
|
+
};
|
|
11
|
+
export declare class GraphQLClientError extends Error {
|
|
12
|
+
readonly errors: GraphQLErrorEntry[];
|
|
13
|
+
readonly status: number;
|
|
14
|
+
constructor(errors: GraphQLErrorEntry[], status?: number);
|
|
15
|
+
}
|
|
16
|
+
export declare class NetworkError extends Error {
|
|
17
|
+
readonly status: number;
|
|
18
|
+
constructor(message: string, status: number);
|
|
19
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -1 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import { GraphQLClient } from './client';
|
|
2
|
+
import type { AnyEntityDefs, ClientConfig, SchemaDescriptor } from './types';
|
|
3
|
+
export declare function createClient<TSchema extends SchemaDescriptor, TDefs extends AnyEntityDefs = AnyEntityDefs>(config: ClientConfig<TSchema>): GraphQLClient<TSchema, TDefs>;
|
|
4
|
+
export type { BuildSchemaConfig } from '@graphql-suite/schema';
|
|
5
|
+
export type { DrizzleClientConfig } from './client';
|
|
6
|
+
export { createDrizzleClient, GraphQLClient } from './client';
|
|
7
|
+
export type { EntityClient } from './entity';
|
|
8
|
+
export { GraphQLClientError, NetworkError } from './errors';
|
|
9
|
+
export type { InferEntityDefs } from './infer';
|
|
10
|
+
export { buildSchemaDescriptor } from './schema-builder';
|
|
11
|
+
export type { AnyEntityDefs, ClientConfig, EntityDef, EntityDefsRef, EntityDescriptor, InferResult, SchemaDescriptor, SelectInput, } from './types';
|
package/index.js
CHANGED
|
@@ -1 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
/** @graphql-suite/client v0.9.1 | MIT */
|
|
2
|
+
var j=(E)=>E.charAt(0).toUpperCase()+E.slice(1);function G(E,x,$,Y=4){let H=" ".repeat(Y),C=[];for(let[R,J]of Object.entries(E)){if(J===!0){C.push(`${H}${R}`);continue}if(typeof J==="object"&&J!==null){let X=$.relations[R];if(!X)continue;let A=x[X.entity];if(!A)continue;let Z=G(J,x,A,Y+2);C.push(`${H}${R} {`),C.push(Z),C.push(`${H}}`)}}return C.join(`
|
|
3
|
+
`)}function L(E){return`${j(E)}Filters`}function S(E){return`${j(E)}OrderBy`}function p(E){return`${j(E)}InsertInput`}function c(E){return`${j(E)}UpdateInput`}function T(E,x,$,Y,H,C,R,J){let X=x.queryListName;if(!X)throw Error(`Entity '${E}' has no list query`);let A=L(E),Z=S(E),_=`${j(X)}Query`,V=[],I=[];if(H)V.push(`$where: ${A}`),I.push("where: $where");if(C)V.push(`$orderBy: ${Z}`),I.push("orderBy: $orderBy");if(R)V.push("$limit: Int"),I.push("limit: $limit");if(J)V.push("$offset: Int"),I.push("offset: $offset");let q=V.length?`(${V.join(", ")})`:"",U=I.length?`(${I.join(", ")})`:"",F=G(Y,$,x);return{query:`query ${_}${q} {
|
|
4
|
+
${X}${U} {
|
|
5
|
+
${F}
|
|
6
|
+
}
|
|
7
|
+
}`,variables:{},operationName:_}}function d(E,x,$,Y,H,C,R){let J=x.queryName;if(!J)throw Error(`Entity '${E}' has no single query`);let X=L(E),A=S(E),Z=`${j(J)}SingleQuery`,_=[],V=[];if(H)_.push(`$where: ${X}`),V.push("where: $where");if(C)_.push(`$orderBy: ${A}`),V.push("orderBy: $orderBy");if(R)_.push("$offset: Int"),V.push("offset: $offset");let I=_.length?`(${_.join(", ")})`:"",q=V.length?`(${V.join(", ")})`:"",U=G(Y,$,x);return{query:`query ${Z}${I} {
|
|
8
|
+
${J}${q} {
|
|
9
|
+
${U}
|
|
10
|
+
}
|
|
11
|
+
}`,variables:{},operationName:Z}}function b(E,x,$){let Y=x.countName;if(!Y)throw Error(`Entity '${E}' has no count query`);let H=L(E),C=`${j(Y)}Query`,R=[],J=[];if($)R.push(`$where: ${H}`),J.push("where: $where");let X=R.length?`(${R.join(", ")})`:"",A=J.length?`(${J.join(", ")})`:"";return{query:`query ${C}${X} {
|
|
12
|
+
${Y}${A}
|
|
13
|
+
}`,variables:{},operationName:C}}function w(E,x,$,Y,H){let C=H?x.insertSingleName:x.insertName;if(!C)throw Error(`Entity '${E}' has no ${H?"insertSingle":"insert"} mutation`);let R=p(E),J=`${j(C)}Mutation`,A=`($values: ${H?`${R}!`:`[${R}!]!`})`,Z="(values: $values)",_="";if(Y)_=` {
|
|
14
|
+
${G(Y,$,x)}
|
|
15
|
+
}`;return{query:`mutation ${J}${A} {
|
|
16
|
+
${C}${Z}${_}
|
|
17
|
+
}`,variables:{},operationName:J}}function v(E,x,$,Y,H){let C=x.updateName;if(!C)throw Error(`Entity '${E}' has no update mutation`);let R=c(E),J=L(E),X=`${j(C)}Mutation`,A=[`$set: ${R}!`],Z=["set: $set"];if(H)A.push(`$where: ${J}`),Z.push("where: $where");let _=`(${A.join(", ")})`,V=`(${Z.join(", ")})`,I="";if(Y)I=` {
|
|
18
|
+
${G(Y,$,x)}
|
|
19
|
+
}`;return{query:`mutation ${X}${_} {
|
|
20
|
+
${C}${V}${I}
|
|
21
|
+
}`,variables:{},operationName:X}}function g(E,x,$,Y,H){let C=x.deleteName;if(!C)throw Error(`Entity '${E}' has no delete mutation`);let R=L(E),J=`${j(C)}Mutation`,X=[],A=[];if(H)X.push(`$where: ${R}`),A.push("where: $where");let Z=X.length?`(${X.join(", ")})`:"",_=A.length?`(${A.join(", ")})`:"",V="";if(Y)V=` {
|
|
22
|
+
${G(Y,$,x)}
|
|
23
|
+
}`;return{query:`mutation ${J}${Z} {
|
|
24
|
+
${C}${_}${V}
|
|
25
|
+
}`,variables:{},operationName:J}}function D(E,x,$,Y){return{async query(H){let{select:C,where:R,limit:J,offset:X,orderBy:A}=H,Z=T(E,x,$,C,R!=null,A!=null,J!=null,X!=null),_={};if(R!=null)_.where=R;if(A!=null)_.orderBy=A;if(J!=null)_.limit=J;if(X!=null)_.offset=X;return(await Y(Z.query,_))[x.queryListName]},async querySingle(H){let{select:C,where:R,offset:J,orderBy:X}=H,A=d(E,x,$,C,R!=null,X!=null,J!=null),Z={};if(R!=null)Z.where=R;if(X!=null)Z.orderBy=X;if(J!=null)Z.offset=J;return(await Y(A.query,Z))[x.queryName]??null},async count(H){let C=H?.where,R=b(E,x,C!=null),J={};if(C!=null)J.where=C;return(await Y(R.query,J))[x.countName]},async insert(H){let{values:C,returning:R}=H,J=w(E,x,$,R,!1),X={values:C};return(await Y(J.query,X))[x.insertName]},async insertSingle(H){let{values:C,returning:R}=H,J=w(E,x,$,R,!0),X={values:C};return(await Y(J.query,X))[x.insertSingleName]??null},async update(H){let{set:C,where:R,returning:J}=H,X=v(E,x,$,J,R!=null),A={set:C};if(R!=null)A.where=R;return(await Y(X.query,A))[x.updateName]},async delete(H){let{where:C,returning:R}=H,J=g(E,x,$,R,C!=null),X={};if(C!=null)X.where=C;return(await Y(J.query,X))[x.deleteName]}}}class K extends Error{errors;status;constructor(E,x=200){let $=E.map((Y)=>Y.message).join("; ");super($);this.name="GraphQLClientError",this.errors=E,this.status=x}}class W extends Error{status;constructor(E,x){super(E);this.name="NetworkError",this.status=x}}import{getTableColumns as f,getTableName as h,is as z,Many as N,One as u,Relations as m,Table as l}from"drizzle-orm";var O=(E)=>E.charAt(0).toUpperCase()+E.slice(1);function k(E,x={}){let $=new Set(x.tables?.exclude??[]),Y=x.suffixes?.list??"s",H=new Map,C=new Map;for(let[A,Z]of Object.entries(E))if(z(Z,l)){if($.has(A))continue;let _=h(Z),V=Object.keys(f(Z));H.set(A,{table:Z,dbName:_,columns:V}),C.set(_,A)}let R=new Map;for(let A of Object.values(E)){if(!z(A,m))continue;let Z=h(A.table),_=C.get(Z);if(!_||!H.has(_))continue;let V={one:(U,F)=>{return new u(A.table,U,F,!1)},many:(U,F)=>{return new N(A.table,U,F)}},I=A.config(V),q={};for(let[U,F]of Object.entries(I)){let Q=F,P=Q.referencedTableName;if(!P)continue;let B=C.get(P);if(!B)continue;let o=z(Q,u)?"one":"many";q[U]={entity:B,type:o}}R.set(_,q)}let J=x.pruneRelations??{};for(let[A,Z]of R)for(let _ of Object.keys(Z)){let V=`${A}.${_}`;if(J[V]===!1)delete Z[_]}let X={};for(let[A,{columns:Z}]of H){let _=R.get(A)??{};X[A]={queryName:A,queryListName:`${A}${Y}`,countName:`${A}Count`,insertName:`insertInto${O(A)}`,insertSingleName:`insertInto${O(A)}Single`,updateName:`update${O(A)}`,deleteName:`deleteFrom${O(A)}`,fields:Z,relations:_}}return X}class M{url;schema;headers;constructor(E){this.url=E.url,this.schema=E.schema,this.headers=E.headers}entity(E){let x=this.schema[E];if(!x)throw Error(`Entity '${E}' not found in schema`);return D(E,x,this.schema,($,Y)=>this.execute($,Y))}async execute(E,x={}){let $=typeof this.url==="function"?this.url():this.url,Y={"Content-Type":"application/json",...typeof this.headers==="function"?await this.headers():this.headers??{}},H;try{H=await fetch($,{method:"POST",headers:Y,body:JSON.stringify({query:E,variables:x})})}catch(R){throw new W(R instanceof Error?R.message:"Network request failed",0)}if(!H.ok)throw new W(`HTTP ${H.status}: ${H.statusText}`,H.status);let C=await H.json();if(C.errors?.length)throw new K(C.errors,H.status);if(!C.data)throw new K([{message:"No data in response"}],H.status);return C.data}}function y(E){let x=k(E.schema,E.config);return new M({url:E.url,schema:x,headers:E.headers})}function RE(E){return new M(E)}export{y as createDrizzleClient,RE as createClient,k as buildSchemaDescriptor,W as NetworkError,K as GraphQLClientError,M as GraphQLClient};
|
package/infer.d.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { Many, One, Relations, Table } from 'drizzle-orm';
|
|
2
|
+
type ToWire<T> = T extends Date ? string : T;
|
|
3
|
+
type WireFormat<T> = {
|
|
4
|
+
[K in keyof T]: ToWire<T[K]>;
|
|
5
|
+
};
|
|
6
|
+
type ExtractTables<TSchema> = {
|
|
7
|
+
[K in keyof TSchema as TSchema[K] extends Table ? K : never]: TSchema[K];
|
|
8
|
+
};
|
|
9
|
+
type FindRelationConfig<TSchema, TTableName extends string> = {
|
|
10
|
+
[K in keyof TSchema]: TSchema[K] extends Relations<TTableName, infer TConfig> ? TConfig : never;
|
|
11
|
+
}[keyof TSchema];
|
|
12
|
+
type MapRelation<T> = T extends One<infer N, infer TIsNullable> ? {
|
|
13
|
+
entity: N;
|
|
14
|
+
type: 'one';
|
|
15
|
+
required: TIsNullable;
|
|
16
|
+
} : T extends Many<infer N> ? {
|
|
17
|
+
entity: N;
|
|
18
|
+
type: 'many';
|
|
19
|
+
} : never;
|
|
20
|
+
type InferRelationDefs<TSchema, TTableName extends string> = {
|
|
21
|
+
[K in keyof FindRelationConfig<TSchema, TTableName>]: MapRelation<FindRelationConfig<TSchema, TTableName>[K]>;
|
|
22
|
+
};
|
|
23
|
+
type ScalarFilterOps<T> = {
|
|
24
|
+
eq?: T | null;
|
|
25
|
+
ne?: T | null;
|
|
26
|
+
lt?: T | null;
|
|
27
|
+
lte?: T | null;
|
|
28
|
+
gt?: T | null;
|
|
29
|
+
gte?: T | null;
|
|
30
|
+
like?: string | null;
|
|
31
|
+
notLike?: string | null;
|
|
32
|
+
ilike?: string | null;
|
|
33
|
+
notIlike?: string | null;
|
|
34
|
+
inArray?: T[] | null;
|
|
35
|
+
notInArray?: T[] | null;
|
|
36
|
+
isNull?: boolean | null;
|
|
37
|
+
isNotNull?: boolean | null;
|
|
38
|
+
};
|
|
39
|
+
type ScalarColumnFilters<TFields> = {
|
|
40
|
+
[K in keyof TFields]?: ScalarFilterOps<NonNullable<TFields[K]>>;
|
|
41
|
+
};
|
|
42
|
+
type ManyRelationFilter<TFilter> = {
|
|
43
|
+
some?: TFilter;
|
|
44
|
+
every?: TFilter;
|
|
45
|
+
none?: TFilter;
|
|
46
|
+
};
|
|
47
|
+
type IsResolvableRelation<TSchema, TRel> = TRel extends {
|
|
48
|
+
entity: infer E;
|
|
49
|
+
} ? E extends string ? KeyForDbName<TSchema, E> extends keyof ExtractTables<TSchema> ? true : false : false : false;
|
|
50
|
+
type Prev<T extends unknown[]> = T extends [unknown, ...infer Rest] ? Rest : [];
|
|
51
|
+
type InferRelationFilterFields<TSchema, TRels, TDepth extends unknown[]> = {
|
|
52
|
+
[K in keyof TRels as IsResolvableRelation<TSchema, TRels[K]> extends true ? K : never]?: TRels[K] extends {
|
|
53
|
+
entity: infer E;
|
|
54
|
+
type: infer TRelType;
|
|
55
|
+
} ? E extends string ? KeyForDbName<TSchema, E> extends infer RK ? RK extends keyof ExtractTables<TSchema> ? ExtractTables<TSchema>[RK] extends infer TTarget ? TTarget extends Table ? TRelType extends 'many' ? ManyRelationFilter<InferEntityFilters<TSchema, TTarget, Prev<TDepth>>> : InferEntityFilters<TSchema, TTarget, Prev<TDepth>> : never : never : never : never : never : never;
|
|
56
|
+
};
|
|
57
|
+
type InferEntityFilters<TSchema, T extends Table, TDepth extends unknown[] = [0]> = ScalarColumnFilters<WireFormat<T['$inferSelect']>> & (TDepth extends [] ? {} : InferRelationFilterFields<TSchema, InferRelationDefs<TSchema, TableDbName<T>>, TDepth>) & {
|
|
58
|
+
OR?: InferEntityFilters<TSchema, T, TDepth>[];
|
|
59
|
+
};
|
|
60
|
+
type InferInsertInput<T> = T extends Table ? WireFormat<T['$inferInsert']> : never;
|
|
61
|
+
type InferUpdateInput<T> = T extends Table ? {
|
|
62
|
+
[K in keyof T['$inferInsert']]?: ToWire<T['$inferInsert'][K]> | null;
|
|
63
|
+
} : never;
|
|
64
|
+
type InferOrderBy<T> = T extends Table ? {
|
|
65
|
+
[K in keyof T['$inferSelect']]?: {
|
|
66
|
+
direction: 'asc' | 'desc';
|
|
67
|
+
priority: number;
|
|
68
|
+
};
|
|
69
|
+
} : never;
|
|
70
|
+
type ExcludedNames<TConfig> = TConfig extends {
|
|
71
|
+
tables: {
|
|
72
|
+
exclude: readonly (infer T)[];
|
|
73
|
+
};
|
|
74
|
+
} ? T : never;
|
|
75
|
+
type NumberToTuple<N extends number, T extends unknown[] = []> = T['length'] extends N ? T : T['length'] extends 5 ? T : NumberToTuple<N, [...T, 0]>;
|
|
76
|
+
type ExtractFilterDepth<TConfig> = TConfig extends {
|
|
77
|
+
limitRelationDepth: infer D;
|
|
78
|
+
} ? D extends number ? number extends D ? [0] : NumberToTuple<D> : [0] : [0];
|
|
79
|
+
type TableDbName<T> = T extends Table<infer TConfig> ? TConfig['name'] extends string ? TConfig['name'] : string : string;
|
|
80
|
+
type DbNameToKey<TSchema> = {
|
|
81
|
+
[K in keyof ExtractTables<TSchema>]: TableDbName<ExtractTables<TSchema>[K]>;
|
|
82
|
+
};
|
|
83
|
+
type KeyForDbName<TSchema, TDbName extends string> = {
|
|
84
|
+
[K in keyof DbNameToKey<TSchema>]: DbNameToKey<TSchema>[K] extends TDbName ? K : never;
|
|
85
|
+
}[keyof DbNameToKey<TSchema>];
|
|
86
|
+
type ResolveRelationEntity<TSchema, TDbName extends string> = KeyForDbName<TSchema, TDbName> extends infer K ? (K extends string ? K : TDbName) : TDbName;
|
|
87
|
+
type ResolveRelationDefs<TSchema, TRels> = {
|
|
88
|
+
[K in keyof TRels]: TRels[K] extends {
|
|
89
|
+
entity: infer E;
|
|
90
|
+
type: infer T;
|
|
91
|
+
} ? E extends string ? Omit<TRels[K], 'entity'> & {
|
|
92
|
+
entity: ResolveRelationEntity<TSchema, E>;
|
|
93
|
+
type: T;
|
|
94
|
+
} : TRels[K] : TRels[K];
|
|
95
|
+
};
|
|
96
|
+
type BuildEntityDef<TSchema, T, TDepth extends unknown[]> = T extends Table ? {
|
|
97
|
+
fields: WireFormat<T['$inferSelect']>;
|
|
98
|
+
relations: ResolveRelationDefs<TSchema, InferRelationDefs<TSchema, TableDbName<T>>>;
|
|
99
|
+
filters: InferEntityFilters<TSchema, T, TDepth>;
|
|
100
|
+
insertInput: InferInsertInput<T>;
|
|
101
|
+
updateInput: InferUpdateInput<T>;
|
|
102
|
+
orderBy: InferOrderBy<T>;
|
|
103
|
+
} : never;
|
|
104
|
+
export type InferEntityDefs<TSchema, TConfig = Record<string, never>> = {
|
|
105
|
+
[K in keyof ExtractTables<TSchema> as K extends ExcludedNames<TConfig> ? never : K extends string ? K : never]: BuildEntityDef<TSchema, ExtractTables<TSchema>[K], ExtractFilterDepth<TConfig>>;
|
|
106
|
+
};
|
|
107
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graphql-suite/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Type-safe GraphQL client with entity-based API and full Drizzle type inference",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "https://github.com/dmythro",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/annexare/
|
|
9
|
+
"url": "git+https://github.com/annexare/graphql-suite.git",
|
|
10
10
|
"directory": "packages/client"
|
|
11
11
|
},
|
|
12
|
+
"homepage": "https://graphql-suite.annexare.com/client/overview/",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"drizzle",
|
|
15
|
+
"graphql",
|
|
16
|
+
"client",
|
|
17
|
+
"type-safe",
|
|
18
|
+
"typescript",
|
|
19
|
+
"orm",
|
|
20
|
+
"query-builder"
|
|
21
|
+
],
|
|
12
22
|
"type": "module",
|
|
13
23
|
"publishConfig": {
|
|
14
24
|
"access": "public"
|
|
@@ -21,11 +31,8 @@
|
|
|
21
31
|
"import": "./index.js"
|
|
22
32
|
}
|
|
23
33
|
},
|
|
24
|
-
"dependencies": {
|
|
25
|
-
"@drizzle-graphql-suite/client": "0.8.3"
|
|
26
|
-
},
|
|
27
34
|
"peerDependencies": {
|
|
28
|
-
"@
|
|
35
|
+
"@graphql-suite/schema": ">=0.9.0",
|
|
29
36
|
"drizzle-orm": ">=0.44.0"
|
|
30
37
|
}
|
|
31
38
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { EntityDescriptor, SchemaDescriptor } from './types';
|
|
2
|
+
export type BuiltQuery = {
|
|
3
|
+
query: string;
|
|
4
|
+
variables: Record<string, unknown>;
|
|
5
|
+
operationName: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function buildListQuery(entityName: string, entityDef: EntityDescriptor, schema: SchemaDescriptor, select: Record<string, unknown>, hasWhere: boolean, hasOrderBy: boolean, hasLimit: boolean, hasOffset: boolean): BuiltQuery;
|
|
8
|
+
export declare function buildSingleQuery(entityName: string, entityDef: EntityDescriptor, schema: SchemaDescriptor, select: Record<string, unknown>, hasWhere: boolean, hasOrderBy: boolean, hasOffset: boolean): BuiltQuery;
|
|
9
|
+
export declare function buildCountQuery(entityName: string, entityDef: EntityDescriptor, hasWhere: boolean): BuiltQuery;
|
|
10
|
+
export declare function buildInsertMutation(entityName: string, entityDef: EntityDescriptor, schema: SchemaDescriptor, returning: Record<string, unknown> | undefined, isSingle: boolean): BuiltQuery;
|
|
11
|
+
export declare function buildUpdateMutation(entityName: string, entityDef: EntityDescriptor, schema: SchemaDescriptor, returning: Record<string, unknown> | undefined, hasWhere: boolean): BuiltQuery;
|
|
12
|
+
export declare function buildDeleteMutation(entityName: string, entityDef: EntityDescriptor, schema: SchemaDescriptor, returning: Record<string, unknown> | undefined, hasWhere: boolean): BuiltQuery;
|
package/types.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type RelationDef = {
|
|
2
|
+
entity: string;
|
|
3
|
+
type: 'one' | 'many';
|
|
4
|
+
required?: boolean;
|
|
5
|
+
};
|
|
6
|
+
export type EntityDef = {
|
|
7
|
+
fields: Record<string, unknown>;
|
|
8
|
+
relations: Record<string, RelationDef>;
|
|
9
|
+
filters?: Record<string, unknown>;
|
|
10
|
+
insertInput?: Record<string, unknown>;
|
|
11
|
+
updateInput?: Record<string, unknown>;
|
|
12
|
+
orderBy?: Record<string, unknown>;
|
|
13
|
+
};
|
|
14
|
+
export type AnyEntityDefs = Record<string, EntityDef>;
|
|
15
|
+
/** Opaque wrapper that prevents TS from expanding entity defs during serialization */
|
|
16
|
+
export interface EntityDefsRef<TDefs extends AnyEntityDefs> {
|
|
17
|
+
readonly __defs: TDefs;
|
|
18
|
+
}
|
|
19
|
+
export type EntityDescriptor = {
|
|
20
|
+
queryName: string;
|
|
21
|
+
queryListName: string;
|
|
22
|
+
countName: string;
|
|
23
|
+
insertName: string;
|
|
24
|
+
insertSingleName: string;
|
|
25
|
+
updateName: string;
|
|
26
|
+
deleteName: string;
|
|
27
|
+
fields: readonly string[];
|
|
28
|
+
relations: Record<string, {
|
|
29
|
+
entity: string;
|
|
30
|
+
type: 'one' | 'many';
|
|
31
|
+
}>;
|
|
32
|
+
};
|
|
33
|
+
export type SchemaDescriptor = Record<string, EntityDescriptor>;
|
|
34
|
+
export type SelectInput<TDefs extends AnyEntityDefs, TEntity extends EntityDef> = {
|
|
35
|
+
[K in keyof TEntity['fields'] | keyof TEntity['relations']]?: K extends keyof TEntity['relations'] ? TEntity['relations'][K] extends RelationDef ? TEntity['relations'][K]['entity'] extends keyof TDefs ? SelectInput<TDefs, TDefs[TEntity['relations'][K]['entity']]> : never : never : K extends keyof TEntity['fields'] ? true : never;
|
|
36
|
+
};
|
|
37
|
+
type Simplify<T> = {
|
|
38
|
+
[K in keyof T]: T[K];
|
|
39
|
+
} & {};
|
|
40
|
+
export type InferResult<TDefs extends AnyEntityDefs, TEntity extends EntityDef, TSelect> = Simplify<InferScalars<TEntity, TSelect> & InferRelations<TDefs, TEntity, TSelect>>;
|
|
41
|
+
type InferScalars<TEntity extends EntityDef, TSelect> = Pick<TEntity['fields'], keyof TSelect & keyof TEntity['fields']>;
|
|
42
|
+
type InferRelations<TDefs extends AnyEntityDefs, TEntity extends EntityDef, TSelect> = {
|
|
43
|
+
[K in keyof TSelect & keyof TEntity['relations'] as TSelect[K] extends Record<string, unknown> ? K : never]: TEntity['relations'][K] extends RelationDef ? TEntity['relations'][K]['entity'] extends keyof TDefs ? TEntity['relations'][K]['type'] extends 'many' ? InferResult<TDefs, TDefs[TEntity['relations'][K]['entity']], TSelect[K]>[] : InferResult<TDefs, TDefs[TEntity['relations'][K]['entity']], TSelect[K]> | null : never : never;
|
|
44
|
+
};
|
|
45
|
+
export type ClientConfig<TSchema extends SchemaDescriptor = SchemaDescriptor> = {
|
|
46
|
+
url: string | (() => string);
|
|
47
|
+
schema: TSchema;
|
|
48
|
+
headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
49
|
+
};
|
|
50
|
+
export {};
|