@soda-gql/colocation-tools 0.2.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 +213 -0
- package/dist/index.cjs +313 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +272 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +272 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +305 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# @soda-gql/colocation-tools
|
|
2
|
+
|
|
3
|
+
Utilities for colocating GraphQL fragments with components in soda-gql. This package provides tools for fragment composition and data masking patterns.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Fragment colocation** - Keep GraphQL fragments close to components that use them
|
|
8
|
+
- **Data projection** - Create typed projections from fragment data
|
|
9
|
+
- **Type safety** - Full TypeScript support for fragment composition
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @soda-gql/colocation-tools
|
|
15
|
+
# or
|
|
16
|
+
bun add @soda-gql/colocation-tools
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### Fragment Colocation Pattern
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { createProjection, createExecutionResultParser } from "@soda-gql/colocation-tools";
|
|
25
|
+
import { userFragment } from "./graphql-system";
|
|
26
|
+
|
|
27
|
+
// Create a projection with paths and handle function
|
|
28
|
+
const userProjection = createProjection(userFragment, {
|
|
29
|
+
paths: ["$.user.id", "$.user.name"],
|
|
30
|
+
handle: (result) => {
|
|
31
|
+
if (result.isError()) return { error: result.error, user: null };
|
|
32
|
+
if (result.isEmpty()) return { error: null, user: null };
|
|
33
|
+
const data = result.unwrap();
|
|
34
|
+
return { error: null, user: data };
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Use with execution result parser
|
|
39
|
+
const parser = createExecutionResultParser({
|
|
40
|
+
user: userProjection,
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Embedding Fragments
|
|
45
|
+
|
|
46
|
+
Fragments can be embedded in operations:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { gql } from "./graphql-system";
|
|
50
|
+
import { userFragment } from "./UserCard";
|
|
51
|
+
|
|
52
|
+
export const getUserQuery = gql.default(({ query }) =>
|
|
53
|
+
query.operation({ name: "GetUser" }, ({ f }) => [
|
|
54
|
+
f.user({ id: "1" })(userFragment.embed()),
|
|
55
|
+
]),
|
|
56
|
+
);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Using with $colocate
|
|
60
|
+
|
|
61
|
+
When composing multiple fragments in a single operation, use `$colocate` to prefix field selections with labels. The `createExecutionResultParser` will use these same labels to extract the corresponding data.
|
|
62
|
+
|
|
63
|
+
#### Complete Workflow
|
|
64
|
+
|
|
65
|
+
**Step 1: Define component fragments**
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// UserCard.tsx
|
|
69
|
+
export const userCardFragment = gql.default(({ fragment }, { $var }) =>
|
|
70
|
+
fragment.Query({ variables: [$var("userId").scalar("ID:!")] }, ({ f, $ }) => [
|
|
71
|
+
f.user({ id: $.userId })(({ f }) => [f.id(), f.name(), f.email()]),
|
|
72
|
+
]),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
export const userCardProjection = createProjection(userCardFragment, {
|
|
76
|
+
paths: ["$.user"],
|
|
77
|
+
handle: (result) => {
|
|
78
|
+
if (result.isError()) return { error: result.error, user: null };
|
|
79
|
+
if (result.isEmpty()) return { error: null, user: null };
|
|
80
|
+
return { error: null, user: result.unwrap().user };
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Step 2: Compose operation with $colocate**
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// UserPage.tsx
|
|
89
|
+
import { userCardFragment, userCardProjection } from "./UserCard";
|
|
90
|
+
import { postListFragment, postListProjection } from "./PostList";
|
|
91
|
+
|
|
92
|
+
export const userPageQuery = gql.default(({ query }, { $var, $colocate }) =>
|
|
93
|
+
query.operation(
|
|
94
|
+
{ name: "UserPage", variables: [$var("userId").scalar("ID:!")] },
|
|
95
|
+
({ $ }) => [
|
|
96
|
+
$colocate({
|
|
97
|
+
userCard: userCardFragment.embed({ userId: $.userId }),
|
|
98
|
+
postList: postListFragment.embed({ userId: $.userId }),
|
|
99
|
+
}),
|
|
100
|
+
],
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Step 3: Create parser with matching labels**
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
const parseUserPageResult = createExecutionResultParser({
|
|
109
|
+
userCard: userCardProjection,
|
|
110
|
+
postList: postListProjection,
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Step 4: Parse execution result**
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
const result = await executeQuery(userPageQuery);
|
|
118
|
+
const { userCard, postList } = parseUserPageResult(result);
|
|
119
|
+
// userCard and postList contain the projected data
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The labels in `$colocate` (`userCard`, `postList`) must match the labels in `createExecutionResultParser` for proper data routing.
|
|
123
|
+
|
|
124
|
+
## API
|
|
125
|
+
|
|
126
|
+
### createProjection
|
|
127
|
+
|
|
128
|
+
Creates a typed projection from a fragment definition with specified paths and handler.
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { createProjection } from "@soda-gql/colocation-tools";
|
|
132
|
+
|
|
133
|
+
const projection = createProjection(fragment, {
|
|
134
|
+
// Field paths to extract (must start with "$.")
|
|
135
|
+
paths: ["$.user.id", "$.user.name"],
|
|
136
|
+
// Handler to transform the sliced result
|
|
137
|
+
handle: (result) => {
|
|
138
|
+
if (result.isError()) return { error: result.error, data: null };
|
|
139
|
+
if (result.isEmpty()) return { error: null, data: null };
|
|
140
|
+
return { error: null, data: result.unwrap() };
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### createProjectionAttachment
|
|
146
|
+
|
|
147
|
+
Combines fragment definition and projection into a single export using `attach()`. This eliminates the need for separate projection definitions.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { createProjectionAttachment } from "@soda-gql/colocation-tools";
|
|
151
|
+
import { gql } from "./graphql-system";
|
|
152
|
+
|
|
153
|
+
export const postListFragment = gql
|
|
154
|
+
.default(({ fragment }, { $var }) =>
|
|
155
|
+
fragment.Query({ variables: [$var("userId").scalar("ID:!")] }, ({ f, $ }) => [
|
|
156
|
+
f.user({ id: $.userId })(({ f }) => [f.posts({})(({ f }) => [f.id(), f.title()])]),
|
|
157
|
+
]),
|
|
158
|
+
)
|
|
159
|
+
.attach(
|
|
160
|
+
createProjectionAttachment({
|
|
161
|
+
paths: ["$.user.posts"],
|
|
162
|
+
handle: (result) => {
|
|
163
|
+
if (result.isError()) return { error: result.error, posts: null };
|
|
164
|
+
if (result.isEmpty()) return { error: null, posts: null };
|
|
165
|
+
return { error: null, posts: result.unwrap().user?.posts ?? [] };
|
|
166
|
+
},
|
|
167
|
+
}),
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// The fragment now has a .projection property
|
|
171
|
+
postListFragment.projection;
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Benefits**:
|
|
175
|
+
- Single export for both fragment and projection
|
|
176
|
+
- Fragment can be passed directly to `createExecutionResultParser`
|
|
177
|
+
- Reduces boilerplate when projection logic is simple
|
|
178
|
+
|
|
179
|
+
**Using with createExecutionResultParser**:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
const parseResult = createExecutionResultParser({
|
|
183
|
+
userCard: { projection: userCardProjection }, // Explicit projection
|
|
184
|
+
postList: postListFragment, // Fragment with attached projection
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Both patterns work with the parser - it automatically detects fragments with attached projections.
|
|
189
|
+
|
|
190
|
+
### createExecutionResultParser
|
|
191
|
+
|
|
192
|
+
Creates a parser from labeled projections to process GraphQL execution results.
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { createExecutionResultParser } from "@soda-gql/colocation-tools";
|
|
196
|
+
|
|
197
|
+
const parser = createExecutionResultParser({
|
|
198
|
+
userData: userProjection,
|
|
199
|
+
postsData: postsProjection,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const results = parser(executionResult);
|
|
203
|
+
// results.userData, results.postsData
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Related Packages
|
|
207
|
+
|
|
208
|
+
- [@soda-gql/core](../core) - Core types and fragment definitions
|
|
209
|
+
- [@soda-gql/runtime](../runtime) - Runtime operation handling
|
|
210
|
+
|
|
211
|
+
## License
|
|
212
|
+
|
|
213
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
|
|
2
|
+
//#region packages/colocation-tools/src/projection.ts
|
|
3
|
+
/**
|
|
4
|
+
* Nominal type representing any slice selection regardless of schema specifics.
|
|
5
|
+
* Encodes how individual slices map a concrete field path to a projection
|
|
6
|
+
* function. Multiple selections allow slices to expose several derived values.
|
|
7
|
+
*/
|
|
8
|
+
var Projection = class {
|
|
9
|
+
constructor(paths, projector) {
|
|
10
|
+
this.projector = projector;
|
|
11
|
+
this.paths = paths.map((path) => createProjectionPath(path));
|
|
12
|
+
Object.defineProperty(this, "$infer", { get() {
|
|
13
|
+
throw new Error("This property is only for type meta. Do not access this property directly.");
|
|
14
|
+
} });
|
|
15
|
+
}
|
|
16
|
+
paths;
|
|
17
|
+
};
|
|
18
|
+
function createProjectionPath(path) {
|
|
19
|
+
const segments = path.split(".");
|
|
20
|
+
if (path === "$" || segments.length <= 1) throw new Error("Field path must not be only $ or empty");
|
|
21
|
+
return {
|
|
22
|
+
full: path,
|
|
23
|
+
segments: segments.slice(1)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region packages/colocation-tools/src/create-projection.ts
|
|
29
|
+
/**
|
|
30
|
+
* Creates a type-safe projection from a Fragment.
|
|
31
|
+
*
|
|
32
|
+
* The projection extracts and transforms data from GraphQL execution results,
|
|
33
|
+
* with full type inference from the Fragment's output type.
|
|
34
|
+
*
|
|
35
|
+
* Note: The Fragment parameter is used only for type inference.
|
|
36
|
+
* The actual paths must be specified explicitly.
|
|
37
|
+
*
|
|
38
|
+
* @param _fragment - The Fragment to infer types from (used for type inference only)
|
|
39
|
+
* @param options - Projection options including paths and handle function
|
|
40
|
+
* @returns A Projection that can be used with createExecutionResultParser
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* const userFragment = gql(({ fragment }) =>
|
|
45
|
+
* fragment.Query({ variables: [...] }, ({ f, $ }) => [
|
|
46
|
+
* f.user({ id: $.userId })(({ f }) => [f.id(), f.name()]),
|
|
47
|
+
* ])
|
|
48
|
+
* );
|
|
49
|
+
*
|
|
50
|
+
* const userProjection = createProjection(userFragment, {
|
|
51
|
+
* paths: ["$.user"],
|
|
52
|
+
* handle: (result) => {
|
|
53
|
+
* if (result.isError()) return { error: result.error, user: null };
|
|
54
|
+
* if (result.isEmpty()) return { error: null, user: null };
|
|
55
|
+
* const data = result.unwrap();
|
|
56
|
+
* return { error: null, user: data.user };
|
|
57
|
+
* },
|
|
58
|
+
* });
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
const createProjection = (_fragment, options) => {
|
|
62
|
+
return new Projection(options.paths, options.handle);
|
|
63
|
+
};
|
|
64
|
+
const createProjectionAttachment = (options) => {
|
|
65
|
+
return {
|
|
66
|
+
name: "projection",
|
|
67
|
+
createValue: (fragment) => createProjection(fragment, options)
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region packages/colocation-tools/src/utils/map-values.ts
|
|
73
|
+
function mapValues(obj, fn) {
|
|
74
|
+
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, fn(value, key)]));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region packages/colocation-tools/src/projection-path-graph.ts
|
|
79
|
+
function createPathGraph(paths) {
|
|
80
|
+
const intermediate = paths.reduce((acc, { label, raw, segments: [segment, ...segments] }) => {
|
|
81
|
+
if (segment) (acc[segment] || (acc[segment] = [])).push({
|
|
82
|
+
label,
|
|
83
|
+
raw,
|
|
84
|
+
segments
|
|
85
|
+
});
|
|
86
|
+
return acc;
|
|
87
|
+
}, {});
|
|
88
|
+
return {
|
|
89
|
+
matches: paths.map(({ label, raw, segments }) => ({
|
|
90
|
+
label,
|
|
91
|
+
path: raw,
|
|
92
|
+
exact: segments.length === 0
|
|
93
|
+
})),
|
|
94
|
+
children: mapValues(intermediate, (paths$1) => createPathGraph(paths$1))
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Creates a projection path graph from slice entries with field prefixing.
|
|
99
|
+
* Each slice's paths are prefixed with the slice label for disambiguation.
|
|
100
|
+
*/
|
|
101
|
+
function createPathGraphFromSliceEntries(fragments) {
|
|
102
|
+
return createPathGraph(Object.entries(fragments).flatMap(([label, slice]) => Array.from(new Map(slice.projection.paths.map(({ full: raw, segments }) => {
|
|
103
|
+
const [first, ...rest] = segments;
|
|
104
|
+
return [raw, {
|
|
105
|
+
label,
|
|
106
|
+
raw,
|
|
107
|
+
segments: [`${label}_${first}`, ...rest]
|
|
108
|
+
}];
|
|
109
|
+
})).values())));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region packages/colocation-tools/src/sliced-execution-result.ts
|
|
114
|
+
/** Runtime guard interface shared by all slice result variants. */
|
|
115
|
+
var SlicedExecutionResultGuards = class {
|
|
116
|
+
isSuccess() {
|
|
117
|
+
return this.type === "success";
|
|
118
|
+
}
|
|
119
|
+
isError() {
|
|
120
|
+
return this.type === "error";
|
|
121
|
+
}
|
|
122
|
+
isEmpty() {
|
|
123
|
+
return this.type === "empty";
|
|
124
|
+
}
|
|
125
|
+
constructor(type) {
|
|
126
|
+
this.type = type;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
/** Variant representing an empty payload (no data, no error). */
|
|
130
|
+
var SlicedExecutionResultEmpty = class extends SlicedExecutionResultGuards {
|
|
131
|
+
constructor() {
|
|
132
|
+
super("empty");
|
|
133
|
+
}
|
|
134
|
+
unwrap() {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
safeUnwrap() {
|
|
138
|
+
return {
|
|
139
|
+
data: void 0,
|
|
140
|
+
error: void 0
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
/** Variant representing a successful payload. */
|
|
145
|
+
var SlicedExecutionResultSuccess = class extends SlicedExecutionResultGuards {
|
|
146
|
+
constructor(data, extensions) {
|
|
147
|
+
super("success");
|
|
148
|
+
this.data = data;
|
|
149
|
+
this.extensions = extensions;
|
|
150
|
+
}
|
|
151
|
+
unwrap() {
|
|
152
|
+
return this.data;
|
|
153
|
+
}
|
|
154
|
+
safeUnwrap(transform) {
|
|
155
|
+
return {
|
|
156
|
+
data: transform(this.data),
|
|
157
|
+
error: void 0
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
/** Variant representing an error payload. */
|
|
162
|
+
var SlicedExecutionResultError = class extends SlicedExecutionResultGuards {
|
|
163
|
+
constructor(error, extensions) {
|
|
164
|
+
super("error");
|
|
165
|
+
this.error = error;
|
|
166
|
+
this.extensions = extensions;
|
|
167
|
+
}
|
|
168
|
+
unwrap() {
|
|
169
|
+
throw this.error;
|
|
170
|
+
}
|
|
171
|
+
safeUnwrap() {
|
|
172
|
+
return {
|
|
173
|
+
data: void 0,
|
|
174
|
+
error: this.error
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
//#endregion
|
|
180
|
+
//#region packages/colocation-tools/src/parse-execution-result.ts
|
|
181
|
+
const createPathGraphFromSlices = createPathGraphFromSliceEntries;
|
|
182
|
+
function* generateErrorMapEntries(errors, projectionPathGraph) {
|
|
183
|
+
for (const error of errors) {
|
|
184
|
+
const errorPath = error.path ?? [];
|
|
185
|
+
let stack = projectionPathGraph;
|
|
186
|
+
for (let i = 0; i <= errorPath.length; i++) {
|
|
187
|
+
const segment = errorPath[i];
|
|
188
|
+
if (segment == null || typeof segment === "number") {
|
|
189
|
+
yield* stack.matches.map(({ label, path }) => ({
|
|
190
|
+
label,
|
|
191
|
+
path,
|
|
192
|
+
error
|
|
193
|
+
}));
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
yield* stack.matches.filter(({ exact }) => exact).map(({ label, path }) => ({
|
|
197
|
+
label,
|
|
198
|
+
path,
|
|
199
|
+
error
|
|
200
|
+
}));
|
|
201
|
+
const next = stack.children[segment];
|
|
202
|
+
if (!next) break;
|
|
203
|
+
stack = next;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const createErrorMaps = (errors, projectionPathGraph) => {
|
|
208
|
+
const errorMaps = {};
|
|
209
|
+
for (const { label, path, error } of generateErrorMapEntries(errors ?? [], projectionPathGraph)) {
|
|
210
|
+
const mapPerLabel = errorMaps[label] || (errorMaps[label] = {});
|
|
211
|
+
(mapPerLabel[path] || (mapPerLabel[path] = [])).push({ error });
|
|
212
|
+
}
|
|
213
|
+
return errorMaps;
|
|
214
|
+
};
|
|
215
|
+
const accessDataByPathSegments = (data, pathSegments) => {
|
|
216
|
+
let current = data;
|
|
217
|
+
for (const segment of pathSegments) {
|
|
218
|
+
if (current == null) return { error: /* @__PURE__ */ new Error("No data") };
|
|
219
|
+
if (typeof current !== "object") return { error: /* @__PURE__ */ new Error("Incorrect data type") };
|
|
220
|
+
if (Array.isArray(current)) return { error: /* @__PURE__ */ new Error("Incorrect data type") };
|
|
221
|
+
current = current[segment];
|
|
222
|
+
}
|
|
223
|
+
return { data: current };
|
|
224
|
+
};
|
|
225
|
+
/**
|
|
226
|
+
* Creates an execution result parser for composed operations.
|
|
227
|
+
* The parser maps GraphQL errors and data to their corresponding slices
|
|
228
|
+
* based on the projection path graph.
|
|
229
|
+
*
|
|
230
|
+
* @param slices - Object mapping labels to projections
|
|
231
|
+
* @returns A parser function that takes a NormalizedExecutionResult and returns parsed slices
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```typescript
|
|
235
|
+
* const parser = createExecutionResultParser({
|
|
236
|
+
* userCard: userCardProjection,
|
|
237
|
+
* posts: postsProjection,
|
|
238
|
+
* });
|
|
239
|
+
*
|
|
240
|
+
* const results = parser({
|
|
241
|
+
* type: "graphql",
|
|
242
|
+
* body: { data, errors },
|
|
243
|
+
* });
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
const createExecutionResultParser = (slices) => {
|
|
247
|
+
const projectionPathGraph = createPathGraphFromSlices(slices);
|
|
248
|
+
const fragments = slices;
|
|
249
|
+
const prepare = (result) => {
|
|
250
|
+
if (result.type === "graphql") {
|
|
251
|
+
const errorMaps = createErrorMaps(result.body.errors, projectionPathGraph);
|
|
252
|
+
return {
|
|
253
|
+
...result,
|
|
254
|
+
errorMaps
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
if (result.type === "non-graphql-error") return {
|
|
258
|
+
...result,
|
|
259
|
+
error: new SlicedExecutionResultError({
|
|
260
|
+
type: "non-graphql-error",
|
|
261
|
+
error: result.error
|
|
262
|
+
})
|
|
263
|
+
};
|
|
264
|
+
if (result.type === "empty") return {
|
|
265
|
+
...result,
|
|
266
|
+
error: new SlicedExecutionResultEmpty()
|
|
267
|
+
};
|
|
268
|
+
throw new Error("Invalid result type", { cause: result });
|
|
269
|
+
};
|
|
270
|
+
return (result) => {
|
|
271
|
+
const prepared = prepare(result);
|
|
272
|
+
const entries = Object.entries(fragments).map(([label, fragment]) => {
|
|
273
|
+
const { projection } = fragment;
|
|
274
|
+
if (prepared.type === "graphql") {
|
|
275
|
+
const matchedErrors = projection.paths.flatMap(({ full: raw }) => prepared.errorMaps[label]?.[raw] ?? []);
|
|
276
|
+
const uniqueErrors = Array.from(new Set(matchedErrors.map(({ error }) => error)).values());
|
|
277
|
+
if (uniqueErrors.length > 0) return [label, projection.projector(new SlicedExecutionResultError({
|
|
278
|
+
type: "graphql-error",
|
|
279
|
+
errors: uniqueErrors
|
|
280
|
+
}))];
|
|
281
|
+
const dataResults = projection.paths.map(({ segments }) => {
|
|
282
|
+
const [first, ...rest] = segments;
|
|
283
|
+
const prefixedSegments = [`${label}_${first}`, ...rest];
|
|
284
|
+
return prepared.body.data ? accessDataByPathSegments(prepared.body.data, prefixedSegments) : { error: /* @__PURE__ */ new Error("No data") };
|
|
285
|
+
});
|
|
286
|
+
if (dataResults.some(({ error }) => error)) {
|
|
287
|
+
const errors = dataResults.flatMap(({ error }) => error ? [error] : []);
|
|
288
|
+
return [label, projection.projector(new SlicedExecutionResultError({
|
|
289
|
+
type: "parse-error",
|
|
290
|
+
errors
|
|
291
|
+
}))];
|
|
292
|
+
}
|
|
293
|
+
const dataList = dataResults.map(({ data }) => data);
|
|
294
|
+
return [label, projection.projector(new SlicedExecutionResultSuccess(dataList))];
|
|
295
|
+
}
|
|
296
|
+
if (prepared.type === "non-graphql-error") return [label, projection.projector(prepared.error)];
|
|
297
|
+
if (prepared.type === "empty") return [label, projection.projector(prepared.error)];
|
|
298
|
+
throw new Error("Invalid result type", { cause: prepared });
|
|
299
|
+
});
|
|
300
|
+
return Object.fromEntries(entries);
|
|
301
|
+
};
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
//#endregion
|
|
305
|
+
exports.Projection = Projection;
|
|
306
|
+
exports.SlicedExecutionResultEmpty = SlicedExecutionResultEmpty;
|
|
307
|
+
exports.SlicedExecutionResultError = SlicedExecutionResultError;
|
|
308
|
+
exports.SlicedExecutionResultSuccess = SlicedExecutionResultSuccess;
|
|
309
|
+
exports.createExecutionResultParser = createExecutionResultParser;
|
|
310
|
+
exports.createPathGraphFromSliceEntries = createPathGraphFromSliceEntries;
|
|
311
|
+
exports.createProjection = createProjection;
|
|
312
|
+
exports.createProjectionAttachment = createProjectionAttachment;
|
|
313
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["projector: (result: AnySlicedExecutionResult) => TProjected","paths","type: \"success\" | \"error\" | \"empty\"","data: TData","extensions?: unknown","error: NormalizedError","errorMaps: { [label: string]: { [path: string]: { error: GraphQLFormattedError }[] } }","current: unknown"],"sources":["../src/projection.ts","../src/create-projection.ts","../src/utils/map-values.ts","../src/projection-path-graph.ts","../src/sliced-execution-result.ts","../src/parse-execution-result.ts"],"sourcesContent":["import type { AnySlicedExecutionResult } from \"./sliced-execution-result\";\nimport type { Tuple } from \"./utils/type-utils\";\n\n/** Shape of a single selection slice projection. */\n// biome-ignore lint/suspicious/noExplicitAny: Type alias for any Projection regardless of projected type\nexport type AnyProjection = Projection<any>;\n\ndeclare const __PROJECTION_BRAND__: unique symbol;\n/**\n * Nominal type representing any slice selection regardless of schema specifics.\n * Encodes how individual slices map a concrete field path to a projection\n * function. Multiple selections allow slices to expose several derived values.\n */\nexport class Projection<TProjected> {\n declare readonly [__PROJECTION_BRAND__]: void;\n\n declare readonly $infer: { readonly output: TProjected };\n\n constructor(\n paths: Tuple<string>,\n public readonly projector: (result: AnySlicedExecutionResult) => TProjected,\n ) {\n this.paths = paths.map((path) => createProjectionPath(path));\n\n Object.defineProperty(this, \"$infer\", {\n get() {\n throw new Error(\"This property is only for type meta. Do not access this property directly.\");\n },\n });\n }\n\n public readonly paths: ProjectionPath[];\n}\n\nexport type ProjectionPath = {\n full: string;\n segments: Tuple<string>;\n};\n\nfunction createProjectionPath(path: string): ProjectionPath {\n const segments = path.split(\".\");\n if (path === \"$\" || segments.length <= 1) {\n throw new Error(\"Field path must not be only $ or empty\");\n }\n\n return {\n full: path,\n segments: segments.slice(1) as Tuple<string>,\n };\n}\n\nexport type InferExecutionResultProjection<TProjection extends AnyProjection> = ReturnType<TProjection[\"projector\"]>;\n","import type { Fragment, GqlElementAttachment } from \"@soda-gql/core\";\nimport { Projection } from \"./projection\";\nimport type { SlicedExecutionResult } from \"./sliced-execution-result\";\nimport type { Tuple } from \"./utils/type-utils\";\n\n// biome-ignore lint/suspicious/noExplicitAny: Type alias for any Fragment regardless of type parameters\ntype AnyFragment = Fragment<string, any, any, any>;\n\n/**\n * Options for creating a projection from a Fragment.\n */\nexport type CreateProjectionOptions<TOutput extends object, TProjected> = {\n /**\n * Field paths to extract from the execution result.\n * Each path starts with \"$.\" and follows the field selection structure.\n *\n * @example\n * ```typescript\n * paths: [\"$.user.id\", \"$.user.name\"]\n * ```\n */\n paths: Tuple<string>;\n\n /**\n * Handler function to transform the sliced execution result.\n * Receives a SlicedExecutionResult with the Fragment's output type.\n * Handles all cases: success, error, and empty.\n *\n * @example\n * ```typescript\n * handle: (result) => {\n * if (result.isError()) return { error: result.error, data: null };\n * if (result.isEmpty()) return { error: null, data: null };\n * const data = result.unwrap();\n * return { error: null, data: { userId: data.user.id } };\n * }\n * ```\n */\n handle: (result: SlicedExecutionResult<TOutput>) => TProjected;\n};\n\n/**\n * Creates a type-safe projection from a Fragment.\n *\n * The projection extracts and transforms data from GraphQL execution results,\n * with full type inference from the Fragment's output type.\n *\n * Note: The Fragment parameter is used only for type inference.\n * The actual paths must be specified explicitly.\n *\n * @param _fragment - The Fragment to infer types from (used for type inference only)\n * @param options - Projection options including paths and handle function\n * @returns A Projection that can be used with createExecutionResultParser\n *\n * @example\n * ```typescript\n * const userFragment = gql(({ fragment }) =>\n * fragment.Query({ variables: [...] }, ({ f, $ }) => [\n * f.user({ id: $.userId })(({ f }) => [f.id(), f.name()]),\n * ])\n * );\n *\n * const userProjection = createProjection(userFragment, {\n * paths: [\"$.user\"],\n * handle: (result) => {\n * if (result.isError()) return { error: result.error, user: null };\n * if (result.isEmpty()) return { error: null, user: null };\n * const data = result.unwrap();\n * return { error: null, user: data.user };\n * },\n * });\n * ```\n */\nexport const createProjection = <TFragment extends AnyFragment, TProjected>(\n _fragment: TFragment,\n options: CreateProjectionOptions<TFragment[\"$infer\"][\"output\"], TProjected>,\n): Projection<TProjected> => {\n return new Projection(options.paths, options.handle);\n};\n\nexport const createProjectionAttachment = <TFragment extends AnyFragment, TProjected>(\n options: CreateProjectionOptions<NoInfer<TFragment>[\"$infer\"][\"output\"], TProjected>,\n): GqlElementAttachment<TFragment, \"projection\", Projection<TProjected>> => {\n return {\n name: \"projection\",\n createValue: (fragment) => createProjection(fragment, options),\n };\n};\n","type ArgEntries<T extends object> = { [K in keyof T]-?: [value: T[K], key: K] }[keyof T];\ntype Entries<T extends object> = { [K in keyof T]: [key: K, value: T[K]] }[keyof T];\n\nexport function mapValues<TObject extends object, TMappedValue>(\n obj: TObject,\n fn: (...args: ArgEntries<TObject>) => TMappedValue,\n): {\n [K in keyof TObject]: TMappedValue;\n} {\n return Object.fromEntries((Object.entries(obj) as Entries<TObject>[]).map(([key, value]) => [key, fn(value, key)])) as {\n [K in keyof TObject]: TMappedValue;\n };\n}\n","import type { AnyProjection } from \"./projection\";\nimport { mapValues } from \"./utils/map-values\";\n\n/**\n * Node in the projection path graph tree.\n * Used for mapping GraphQL errors and data to their corresponding slices.\n */\nexport type ProjectionPathGraphNode = {\n readonly matches: { label: string; path: string; exact: boolean }[];\n readonly children: { readonly [segment: string]: ProjectionPathGraphNode };\n};\n\n/**\n * Payload from a slice that contains projection.\n */\nexport type AnySlicePayload = {\n readonly projection: AnyProjection;\n};\n\nexport type AnySlicePayloads = Record<string, AnySlicePayload>;\n\ntype ExecutionResultProjectionPathGraphIntermediate = {\n [segment: string]: { label: string; raw: string; segments: string[] }[];\n};\n\nfunction createPathGraph(paths: ExecutionResultProjectionPathGraphIntermediate[string]): ProjectionPathGraphNode {\n const intermediate = paths.reduce(\n (acc: ExecutionResultProjectionPathGraphIntermediate, { label, raw, segments: [segment, ...segments] }) => {\n if (segment) {\n (acc[segment] || (acc[segment] = [])).push({ label, raw, segments });\n }\n return acc;\n },\n {},\n );\n\n return {\n matches: paths.map(({ label, raw, segments }) => ({ label, path: raw, exact: segments.length === 0 })),\n children: mapValues(intermediate, (paths) => createPathGraph(paths)),\n } satisfies ProjectionPathGraphNode;\n}\n\n/**\n * Creates a projection path graph from slice entries with field prefixing.\n * Each slice's paths are prefixed with the slice label for disambiguation.\n */\nexport function createPathGraphFromSliceEntries(fragments: AnySlicePayloads) {\n const paths = Object.entries(fragments).flatMap(([label, slice]) =>\n Array.from(\n new Map(\n slice.projection.paths.map(({ full: raw, segments }) => {\n const [first, ...rest] = segments;\n return [raw, { label, raw, segments: [`${label}_${first}`, ...rest] }];\n }),\n ).values(),\n ),\n );\n\n return createPathGraph(paths);\n}\n","/** Result-like wrapper types returned from slice projections. */\n\nimport type { NormalizedError } from \"./types\";\n\n// biome-ignore lint/suspicious/noExplicitAny: Type alias for any SlicedExecutionResult regardless of data type\nexport type AnySlicedExecutionResult = SlicedExecutionResult<any>;\n\n/**\n * Internal discriminated union describing the Result-like wrapper exposed to\n * slice selection callbacks.\n */\nexport type AnySlicedExecutionResultRecord = {\n [path: string]: AnySlicedExecutionResult;\n};\n\nexport type SafeUnwrapResult<TTransformed, TError> =\n | {\n data?: never;\n error?: never;\n }\n | {\n data: TTransformed;\n error?: never;\n }\n | {\n data?: never;\n error: TError;\n };\n\n/** Utility signature returned by the safe unwrap helper. */\ntype SlicedExecutionResultCommon<TData, TError> = {\n safeUnwrap<TTransformed>(transform: (data: TData) => TTransformed): SafeUnwrapResult<TTransformed, TError>;\n};\n\n/** Public union used by selection callbacks to inspect data, empty, or error states. */\nexport type SlicedExecutionResult<TData> =\n | SlicedExecutionResultEmpty<TData>\n | SlicedExecutionResultSuccess<TData>\n | SlicedExecutionResultError<TData>;\n\n/** Runtime guard interface shared by all slice result variants. */\nclass SlicedExecutionResultGuards<TData> {\n isSuccess(): this is SlicedExecutionResultSuccess<TData> {\n return this.type === \"success\";\n }\n isError(): this is SlicedExecutionResultError<TData> {\n return this.type === \"error\";\n }\n isEmpty(): this is SlicedExecutionResultEmpty<TData> {\n return this.type === \"empty\";\n }\n\n constructor(private readonly type: \"success\" | \"error\" | \"empty\") {}\n}\n\n/** Variant representing an empty payload (no data, no error). */\nexport class SlicedExecutionResultEmpty<TData>\n extends SlicedExecutionResultGuards<TData>\n implements SlicedExecutionResultCommon<TData, NormalizedError>\n{\n constructor() {\n super(\"empty\");\n }\n\n unwrap(): null {\n return null;\n }\n\n safeUnwrap() {\n return {\n data: undefined,\n error: undefined,\n };\n }\n}\n\n/** Variant representing a successful payload. */\nexport class SlicedExecutionResultSuccess<TData>\n extends SlicedExecutionResultGuards<TData>\n implements SlicedExecutionResultCommon<TData, NormalizedError>\n{\n constructor(\n public readonly data: TData,\n public readonly extensions?: unknown,\n ) {\n super(\"success\");\n }\n\n unwrap(): TData {\n return this.data;\n }\n\n safeUnwrap<TTransformed>(transform: (data: TData) => TTransformed) {\n return {\n data: transform(this.data),\n error: undefined,\n };\n }\n}\n\n/** Variant representing an error payload. */\nexport class SlicedExecutionResultError<TData>\n extends SlicedExecutionResultGuards<TData>\n implements SlicedExecutionResultCommon<TData, NormalizedError>\n{\n constructor(\n public readonly error: NormalizedError,\n public readonly extensions?: unknown,\n ) {\n super(\"error\");\n }\n\n unwrap(): never {\n throw this.error;\n }\n\n safeUnwrap() {\n return {\n data: undefined,\n error: this.error,\n };\n }\n}\n","import type { GraphQLFormattedError } from \"graphql\";\nimport { type AnySlicePayloads, createPathGraphFromSliceEntries, type ProjectionPathGraphNode } from \"./projection-path-graph\";\nimport { SlicedExecutionResultEmpty, SlicedExecutionResultError, SlicedExecutionResultSuccess } from \"./sliced-execution-result\";\nimport type { NormalizedExecutionResult } from \"./types\";\n\n// Internal function to build path graph from slices\nconst createPathGraphFromSlices = createPathGraphFromSliceEntries;\n\nfunction* generateErrorMapEntries(errors: readonly GraphQLFormattedError[], projectionPathGraph: ProjectionPathGraphNode) {\n for (const error of errors) {\n const errorPath = error.path ?? [];\n let stack = projectionPathGraph;\n\n for (\n let i = 0;\n // i <= errorPath.length to handle the case where the error path is empty\n i <= errorPath.length;\n i++\n ) {\n const segment = errorPath[i];\n\n if (\n // the end of the path\n segment == null ||\n // FieldPath does not support index access. We treat it as the end of the path.\n typeof segment === \"number\"\n ) {\n yield* stack.matches.map(({ label, path }) => ({ label, path, error }));\n break;\n }\n\n yield* stack.matches.filter(({ exact }) => exact).map(({ label, path }) => ({ label, path, error }));\n\n const next = stack.children[segment];\n if (!next) {\n break;\n }\n\n stack = next;\n }\n }\n}\n\nconst createErrorMaps = (errors: readonly GraphQLFormattedError[] | undefined, projectionPathGraph: ProjectionPathGraphNode) => {\n const errorMaps: { [label: string]: { [path: string]: { error: GraphQLFormattedError }[] } } = {};\n for (const { label, path, error } of generateErrorMapEntries(errors ?? [], projectionPathGraph)) {\n const mapPerLabel = errorMaps[label] || (errorMaps[label] = {});\n const mapPerPath = mapPerLabel[path] || (mapPerLabel[path] = []);\n mapPerPath.push({ error });\n }\n return errorMaps;\n};\n\nconst accessDataByPathSegments = (data: object, pathSegments: string[]) => {\n let current: unknown = data;\n\n for (const segment of pathSegments) {\n if (current == null) {\n return { error: new Error(\"No data\") };\n }\n\n if (typeof current !== \"object\") {\n return { error: new Error(\"Incorrect data type\") };\n }\n\n if (Array.isArray(current)) {\n return { error: new Error(\"Incorrect data type\") };\n }\n\n current = (current as Record<string, unknown>)[segment];\n }\n\n return { data: current };\n};\n\n/**\n * Creates an execution result parser for composed operations.\n * The parser maps GraphQL errors and data to their corresponding slices\n * based on the projection path graph.\n *\n * @param slices - Object mapping labels to projections\n * @returns A parser function that takes a NormalizedExecutionResult and returns parsed slices\n *\n * @example\n * ```typescript\n * const parser = createExecutionResultParser({\n * userCard: userCardProjection,\n * posts: postsProjection,\n * });\n *\n * const results = parser({\n * type: \"graphql\",\n * body: { data, errors },\n * });\n * ```\n */\nexport const createExecutionResultParser = <TSlices extends AnySlicePayloads>(slices: TSlices) => {\n // Build path graph from slices\n const projectionPathGraph = createPathGraphFromSlices(slices);\n const fragments = slices;\n const prepare = (result: NormalizedExecutionResult<object, object>) => {\n if (result.type === \"graphql\") {\n const errorMaps = createErrorMaps(result.body.errors, projectionPathGraph);\n\n return { ...result, errorMaps };\n }\n\n if (result.type === \"non-graphql-error\") {\n return { ...result, error: new SlicedExecutionResultError({ type: \"non-graphql-error\", error: result.error }) };\n }\n\n if (result.type === \"empty\") {\n return { ...result, error: new SlicedExecutionResultEmpty() };\n }\n\n throw new Error(\"Invalid result type\", { cause: result satisfies never });\n };\n\n return (result: NormalizedExecutionResult<object, object>) => {\n const prepared = prepare(result);\n\n const entries = Object.entries(fragments).map(([label, fragment]) => {\n const { projection } = fragment;\n\n if (prepared.type === \"graphql\") {\n const matchedErrors = projection.paths.flatMap(({ full: raw }) => prepared.errorMaps[label]?.[raw] ?? []);\n const uniqueErrors = Array.from(new Set(matchedErrors.map(({ error }) => error)).values());\n\n if (uniqueErrors.length > 0) {\n return [label, projection.projector(new SlicedExecutionResultError({ type: \"graphql-error\", errors: uniqueErrors }))];\n }\n\n // Apply label prefix to first segment for data access (matching $colocate prefix pattern)\n const dataResults = projection.paths.map(({ segments }) => {\n const [first, ...rest] = segments;\n const prefixedSegments = [`${label}_${first}`, ...rest];\n return prepared.body.data\n ? accessDataByPathSegments(prepared.body.data, prefixedSegments)\n : { error: new Error(\"No data\") };\n });\n if (dataResults.some(({ error }) => error)) {\n const errors = dataResults.flatMap(({ error }) => (error ? [error] : []));\n return [label, projection.projector(new SlicedExecutionResultError({ type: \"parse-error\", errors }))];\n }\n\n const dataList = dataResults.map(({ data }) => data);\n return [label, projection.projector(new SlicedExecutionResultSuccess(dataList))];\n }\n\n if (prepared.type === \"non-graphql-error\") {\n return [label, projection.projector(prepared.error)];\n }\n\n if (prepared.type === \"empty\") {\n return [label, projection.projector(prepared.error)];\n }\n\n throw new Error(\"Invalid result type\", { cause: prepared satisfies never });\n });\n\n return Object.fromEntries(entries);\n };\n};\n"],"mappings":"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"}
|