@soda-gql/core 0.0.8 → 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/README.md +312 -0
- package/dist/{index-CFNJ_Aa6.d.ts → index-DYwkqPzd.d.ts} +124 -294
- package/dist/index-DYwkqPzd.d.ts.map +1 -0
- package/dist/index-Db9ogofS.d.ts +365 -0
- package/dist/index-Db9ogofS.d.ts.map +1 -0
- package/dist/{index-DHh8XRal.d.cts → index-Dth0NSJt.d.cts} +124 -294
- package/dist/index-Dth0NSJt.d.cts.map +1 -0
- package/dist/index-zGZ61WLt.d.cts +365 -0
- package/dist/index-zGZ61WLt.d.cts.map +1 -0
- package/dist/index.cjs +132 -44
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +129 -45
- package/dist/index.js.map +1 -1
- package/dist/merge-CeMx09is.js +74 -0
- package/dist/merge-CeMx09is.js.map +1 -0
- package/dist/merge-ZxKV1syS.cjs +85 -0
- package/dist/metadata/index.cjs +62 -0
- package/dist/metadata/index.d.cts +71 -0
- package/dist/metadata/index.d.cts.map +1 -0
- package/dist/metadata/index.d.ts +71 -0
- package/dist/metadata/index.d.ts.map +1 -0
- package/dist/metadata/index.js +59 -0
- package/dist/metadata/index.js.map +1 -0
- package/dist/runtime/index.cjs +5 -3
- package/dist/runtime/index.d.cts +2 -1
- package/dist/runtime/index.d.cts.map +1 -1
- package/dist/runtime/index.d.ts +2 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +5 -3
- package/dist/runtime/index.js.map +1 -1
- package/dist/{slice-ua5mSfhV.js → slice-BuSNc8vw.js} +27 -5
- package/dist/slice-BuSNc8vw.js.map +1 -0
- package/dist/{slice-DlVY4UJG.cjs → slice-C-FIQK-f.cjs} +43 -3
- package/package.json +10 -3
- package/dist/index-CFNJ_Aa6.d.ts.map +0 -1
- package/dist/index-DHh8XRal.d.cts.map +0 -1
- package/dist/slice-ua5mSfhV.js.map +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# @soda-gql/core
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@soda-gql/core)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
Core GraphQL types and utilities for the soda-gql ecosystem. This package provides the foundational building blocks for writing type-safe GraphQL operations.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
bun add @soda-gql/core
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Core Concepts
|
|
15
|
+
|
|
16
|
+
soda-gql uses three main building blocks for constructing GraphQL operations:
|
|
17
|
+
|
|
18
|
+
### Models
|
|
19
|
+
|
|
20
|
+
Reusable type-safe fragments with data normalization. Models define how to select fields from a GraphQL type and optionally transform the result.
|
|
21
|
+
|
|
22
|
+
### Slices
|
|
23
|
+
|
|
24
|
+
Domain-specific query/mutation/subscription pieces. Slices are reusable operation fragments that can be composed into complete operations.
|
|
25
|
+
|
|
26
|
+
### Operations
|
|
27
|
+
|
|
28
|
+
Complete GraphQL operations that can be executed. There are two types:
|
|
29
|
+
- **Composed Operations**: Built by combining multiple slices
|
|
30
|
+
- **Inline Operations**: Self-contained operations with field selections defined directly
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
All soda-gql definitions use the `gql.default()` pattern, which is provided by the generated GraphQL system:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { gql } from "@/graphql-system";
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Writing Models
|
|
41
|
+
|
|
42
|
+
Models define reusable fragments for a specific GraphQL type:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
export const userModel = gql.default(({ model }, { $var }) =>
|
|
46
|
+
model.User(
|
|
47
|
+
{ variables: [$var("includeEmail").scalar("Boolean:?")] },
|
|
48
|
+
({ f, $ }) => [
|
|
49
|
+
f.id(),
|
|
50
|
+
f.name(),
|
|
51
|
+
f.email({ if: $.includeEmail }),
|
|
52
|
+
],
|
|
53
|
+
(selection) => ({
|
|
54
|
+
id: selection.id,
|
|
55
|
+
name: selection.name,
|
|
56
|
+
email: selection.email,
|
|
57
|
+
}),
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Writing Slices
|
|
63
|
+
|
|
64
|
+
Slices define reusable operation fragments:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
export const userSlice = gql.default(({ query }, { $var }) =>
|
|
68
|
+
query.slice(
|
|
69
|
+
{ variables: [$var("userId").scalar("ID:!")] },
|
|
70
|
+
({ f, $ }) => [
|
|
71
|
+
f.user({ id: $.userId })(() => [
|
|
72
|
+
userModel.fragment({}),
|
|
73
|
+
]),
|
|
74
|
+
],
|
|
75
|
+
({ select }) =>
|
|
76
|
+
select(["$.user"], (result) =>
|
|
77
|
+
result.safeUnwrap(([user]) => userModel.normalize(user)),
|
|
78
|
+
),
|
|
79
|
+
),
|
|
80
|
+
);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Writing Operations (Composed)
|
|
84
|
+
|
|
85
|
+
Composed operations combine multiple slices:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
export const getUserQuery = gql.default(({ query }, { $var }) =>
|
|
89
|
+
query.composed(
|
|
90
|
+
{
|
|
91
|
+
operationName: "GetUser",
|
|
92
|
+
variables: [$var("id").scalar("ID:!")],
|
|
93
|
+
},
|
|
94
|
+
({ $ }) => ({
|
|
95
|
+
user: userSlice.embed({ userId: $.id }),
|
|
96
|
+
}),
|
|
97
|
+
),
|
|
98
|
+
);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Writing Operations (Inline)
|
|
102
|
+
|
|
103
|
+
Inline operations define field selections directly:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
export const getUserInline = gql.default(({ query }, { $var }) =>
|
|
107
|
+
query.inline(
|
|
108
|
+
{
|
|
109
|
+
operationName: "GetUserInline",
|
|
110
|
+
variables: [$var("id").scalar("ID:!")],
|
|
111
|
+
},
|
|
112
|
+
({ f, $ }) => [
|
|
113
|
+
f.user({ id: $.id })(({ f }) => [
|
|
114
|
+
f.id(),
|
|
115
|
+
f.name(),
|
|
116
|
+
]),
|
|
117
|
+
],
|
|
118
|
+
),
|
|
119
|
+
);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Variable Type Syntax
|
|
123
|
+
|
|
124
|
+
Variables are declared using a string-based type syntax:
|
|
125
|
+
|
|
126
|
+
| Syntax | Meaning | GraphQL Equivalent |
|
|
127
|
+
|--------|---------|-------------------|
|
|
128
|
+
| `"ID:!"` | Required ID | `ID!` |
|
|
129
|
+
| `"String:?"` | Optional String | `String` |
|
|
130
|
+
| `"Int:![]!"` | Required list of required Int | `[Int!]!` |
|
|
131
|
+
| `"String:![]?"` | Optional list of required Strings | `[String!]` |
|
|
132
|
+
| `"MyInput:!"` | Required custom input type | `MyInput!` |
|
|
133
|
+
|
|
134
|
+
## Field Selection Patterns
|
|
135
|
+
|
|
136
|
+
| Pattern | Description |
|
|
137
|
+
|---------|-------------|
|
|
138
|
+
| `f.id()` | Basic field selection |
|
|
139
|
+
| `f.posts({ limit: 10 })` | Field with arguments |
|
|
140
|
+
| `f.posts()(({ f }) => [...])` | Nested selection (curried) |
|
|
141
|
+
| `f.id(null, { alias: "uuid" })` | Field with alias |
|
|
142
|
+
| `f.email({ if: $.includeEmail })` | Conditional field |
|
|
143
|
+
| `userModel.fragment({})` | Use model fragment |
|
|
144
|
+
|
|
145
|
+
## Defining Custom Scalars
|
|
146
|
+
|
|
147
|
+
Scalars define the TypeScript types for GraphQL scalar values:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { defineScalar } from "@soda-gql/core";
|
|
151
|
+
|
|
152
|
+
export const scalar = {
|
|
153
|
+
// Built-in scalars
|
|
154
|
+
...defineScalar<"ID", string, string>("ID"),
|
|
155
|
+
...defineScalar<"String", string, string>("String"),
|
|
156
|
+
...defineScalar<"Int", number, number>("Int"),
|
|
157
|
+
...defineScalar<"Float", number, number>("Float"),
|
|
158
|
+
...defineScalar<"Boolean", boolean, boolean>("Boolean"),
|
|
159
|
+
|
|
160
|
+
// Custom scalars - defineScalar<Name, InputType, OutputType>(name)
|
|
161
|
+
...defineScalar<"DateTime", string, Date>("DateTime"),
|
|
162
|
+
...defineScalar<"JSON", Record<string, unknown>, Record<string, unknown>>("JSON"),
|
|
163
|
+
} as const;
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Alternative callback syntax:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
export const scalar = {
|
|
170
|
+
...defineScalar("DateTime", ({ type }) => ({
|
|
171
|
+
input: type<string>(),
|
|
172
|
+
output: type<Date>(),
|
|
173
|
+
directives: {},
|
|
174
|
+
})),
|
|
175
|
+
} as const;
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Type Inference
|
|
179
|
+
|
|
180
|
+
Extract TypeScript types from soda-gql elements using `$infer`:
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
// Model types
|
|
184
|
+
type UserInput = typeof userModel.$infer.input;
|
|
185
|
+
type UserOutputRaw = typeof userModel.$infer.output.raw;
|
|
186
|
+
type UserOutputNormalized = typeof userModel.$infer.output.normalized;
|
|
187
|
+
|
|
188
|
+
// Operation types
|
|
189
|
+
type QueryVariables = typeof getUserQuery.$infer.input;
|
|
190
|
+
type QueryResult = typeof getUserQuery.$infer.output.projected;
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Metadata
|
|
194
|
+
|
|
195
|
+
Metadata allows you to attach runtime information to operations and slices. This is useful for HTTP headers, GraphQL extensions, and application-specific values.
|
|
196
|
+
|
|
197
|
+
### Metadata Structure
|
|
198
|
+
|
|
199
|
+
All metadata types share three base properties:
|
|
200
|
+
|
|
201
|
+
| Property | Type | Purpose |
|
|
202
|
+
|----------|------|---------|
|
|
203
|
+
| `headers` | `Record<string, string>` | HTTP headers to include with the GraphQL request |
|
|
204
|
+
| `extensions` | `Record<string, unknown>` | GraphQL extensions in the request payload |
|
|
205
|
+
| `custom` | `Record<string, unknown>` | Application-specific values (auth requirements, cache settings, etc.) |
|
|
206
|
+
|
|
207
|
+
### Defining Metadata
|
|
208
|
+
|
|
209
|
+
Metadata can be defined on both slices and operations:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
// Slice with metadata
|
|
213
|
+
export const userSlice = gql.default(({ query }, { $var }) =>
|
|
214
|
+
query.slice(
|
|
215
|
+
{
|
|
216
|
+
variables: [$var("userId").scalar("ID:!")],
|
|
217
|
+
metadata: ({ $ }) => ({
|
|
218
|
+
headers: { "X-Request-ID": "user-query" },
|
|
219
|
+
custom: { requiresAuth: true, cacheTtl: 300 },
|
|
220
|
+
}),
|
|
221
|
+
},
|
|
222
|
+
({ f, $ }) => [f.user({ id: $.userId })(({ f }) => [f.id()])],
|
|
223
|
+
({ select }) => select(["$.user"], (user) => user),
|
|
224
|
+
),
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
// Operation with metadata (can reference variables and document)
|
|
228
|
+
export const getUserQuery = gql.default(({ query }, { $var }) =>
|
|
229
|
+
query.composed(
|
|
230
|
+
{
|
|
231
|
+
operationName: "GetUser",
|
|
232
|
+
variables: [$var("id").scalar("ID:!")],
|
|
233
|
+
metadata: ({ $, document }) => ({
|
|
234
|
+
extensions: {
|
|
235
|
+
trackedVariables: [$var.getInner($.id)],
|
|
236
|
+
},
|
|
237
|
+
}),
|
|
238
|
+
},
|
|
239
|
+
({ $ }) => ({
|
|
240
|
+
user: userSlice.embed({ userId: $.id }),
|
|
241
|
+
}),
|
|
242
|
+
),
|
|
243
|
+
);
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### MetadataAdapter
|
|
247
|
+
|
|
248
|
+
Use `createMetadataAdapter` to customize metadata behavior at the schema level:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import { createMetadataAdapter } from "@soda-gql/core/metadata";
|
|
252
|
+
import { createHash } from "crypto";
|
|
253
|
+
|
|
254
|
+
export const metadataAdapter = createMetadataAdapter({
|
|
255
|
+
// Default metadata applied to all operations
|
|
256
|
+
defaults: {
|
|
257
|
+
headers: { "X-GraphQL-Client": "soda-gql" },
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
// Transform metadata at build time (e.g., add persisted query hash)
|
|
261
|
+
transform: ({ document, metadata }) => ({
|
|
262
|
+
...metadata,
|
|
263
|
+
extensions: {
|
|
264
|
+
...metadata.extensions,
|
|
265
|
+
persistedQuery: {
|
|
266
|
+
version: 1,
|
|
267
|
+
sha256Hash: createHash("sha256").update(document).digest("hex"),
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
}),
|
|
271
|
+
|
|
272
|
+
// Custom merge strategy for slice metadata (optional)
|
|
273
|
+
mergeSliceMetadata: (operationMetadata, sliceMetadataList) => {
|
|
274
|
+
// Default: shallow merge where operation takes precedence
|
|
275
|
+
return { ...sliceMetadataList.reduce((acc, s) => ({ ...acc, ...s }), {}), ...operationMetadata };
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Metadata Merging
|
|
281
|
+
|
|
282
|
+
When an operation includes multiple slices, metadata is merged in this order:
|
|
283
|
+
|
|
284
|
+
1. **Slice metadata** - Merged together (later slices override earlier ones)
|
|
285
|
+
2. **Operation metadata** - Takes precedence over slice metadata
|
|
286
|
+
3. **Schema defaults** - Applied first, overridden by operation/slice values
|
|
287
|
+
|
|
288
|
+
## Runtime Exports
|
|
289
|
+
|
|
290
|
+
The `/runtime` subpath provides utilities for operation registration and retrieval:
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
import { gqlRuntime } from "@soda-gql/core/runtime";
|
|
294
|
+
|
|
295
|
+
// Retrieve registered operations (typically handled by build plugins)
|
|
296
|
+
const operation = gqlRuntime.getComposedOperation("canonicalId");
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## TypeScript Support
|
|
300
|
+
|
|
301
|
+
This package requires TypeScript 5.x or later for full type inference support.
|
|
302
|
+
|
|
303
|
+
## Related Packages
|
|
304
|
+
|
|
305
|
+
- [@soda-gql/cli](../cli) - Command-line interface for code generation
|
|
306
|
+
- [@soda-gql/config](../config) - Configuration management
|
|
307
|
+
- [@soda-gql/runtime](../runtime) - Runtime utilities for operation execution
|
|
308
|
+
- [@soda-gql/tsc-plugin](../tsc-plugin) - TypeScript compiler plugin
|
|
309
|
+
|
|
310
|
+
## License
|
|
311
|
+
|
|
312
|
+
MIT
|