@contractspec/lib.contracts-runtime-server-graphql 2.0.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 +3 -0
- package/dist/browser/graphql-pothos.js +146 -0
- package/dist/browser/index.js +146 -0
- package/dist/graphql-pothos.d.ts +9 -0
- package/dist/graphql-pothos.js +147 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +147 -0
- package/dist/node/graphql-pothos.js +146 -0
- package/dist/node/index.js +146 -0
- package/package.json +80 -0
package/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// src/graphql-pothos.ts
|
|
2
|
+
import { defaultGqlField } from "@contractspec/lib.contracts-spec/jsonschema";
|
|
3
|
+
import"@pothos/plugin-prisma";
|
|
4
|
+
import"@pothos/plugin-complexity";
|
|
5
|
+
import"@pothos/plugin-relay";
|
|
6
|
+
import"@pothos/plugin-dataloader";
|
|
7
|
+
import"@pothos/plugin-tracing";
|
|
8
|
+
import { isSchemaModel } from "@contractspec/lib.schema";
|
|
9
|
+
import { createInputTypeBuilder } from "@contractspec/lib.contracts-runtime-server-rest/contracts-adapter-input";
|
|
10
|
+
import {
|
|
11
|
+
hydrateResourceIfNeeded,
|
|
12
|
+
parseReturns
|
|
13
|
+
} from "@contractspec/lib.contracts-runtime-server-rest/contracts-adapter-hydration";
|
|
14
|
+
function registerContractsOnBuilder(builder, reg, resources) {
|
|
15
|
+
const { buildInputFieldArgs } = createInputTypeBuilder(builder);
|
|
16
|
+
const outputTypeCache = new Map;
|
|
17
|
+
for (const spec of reg.list()) {
|
|
18
|
+
const out = spec.io.output;
|
|
19
|
+
if (out && "getZod" in out && typeof out.getZod === "function") {
|
|
20
|
+
const model = out;
|
|
21
|
+
const typeName = isSchemaModel(model) ? model.config?.name : "UnknownOutput";
|
|
22
|
+
if (typeName && !outputTypeCache.has(typeName)) {
|
|
23
|
+
outputTypeCache.set(typeName, model);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
for (const [typeName, model] of outputTypeCache.entries()) {
|
|
28
|
+
if (!isSchemaModel(model))
|
|
29
|
+
continue;
|
|
30
|
+
builder.objectType(typeName, {
|
|
31
|
+
fields: (t) => {
|
|
32
|
+
const entries = Object.entries(model.config.fields);
|
|
33
|
+
const acc = {};
|
|
34
|
+
for (const [key, field] of entries) {
|
|
35
|
+
const fieldType = field.type;
|
|
36
|
+
let gqlType = "JSON";
|
|
37
|
+
if (fieldType && typeof fieldType.getPothos === "function") {
|
|
38
|
+
const pothosInfo = fieldType.getPothos();
|
|
39
|
+
gqlType = pothosInfo.name || "JSON";
|
|
40
|
+
if (gqlType === "String_unsecure")
|
|
41
|
+
gqlType = "String";
|
|
42
|
+
if (gqlType === "Int_unsecure")
|
|
43
|
+
gqlType = "Int";
|
|
44
|
+
if (gqlType === "Float_unsecure")
|
|
45
|
+
gqlType = "Float";
|
|
46
|
+
if (gqlType === "Boolean_unsecure")
|
|
47
|
+
gqlType = "Boolean";
|
|
48
|
+
if (gqlType === "ID_unsecure")
|
|
49
|
+
gqlType = "ID";
|
|
50
|
+
}
|
|
51
|
+
const typeRef = field.isArray ? [gqlType] : gqlType;
|
|
52
|
+
acc[key] = t.field({
|
|
53
|
+
type: typeRef,
|
|
54
|
+
nullable: field.isOptional,
|
|
55
|
+
resolve: (parent) => parent[key]
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return acc;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function resolveGraphQLTypeName(contractSpec) {
|
|
63
|
+
const returnsName = contractSpec.transport?.gql?.returns;
|
|
64
|
+
if (returnsName)
|
|
65
|
+
return returnsName;
|
|
66
|
+
const out = contractSpec.io.output ?? {};
|
|
67
|
+
if (out && "kind" in out && out.kind === "resource_ref" && "graphQLType" in out && out.graphQLType) {
|
|
68
|
+
return String(out.graphQLType);
|
|
69
|
+
}
|
|
70
|
+
if (out && "getZod" in out && typeof out.getZod === "function") {
|
|
71
|
+
const model = out;
|
|
72
|
+
if (isSchemaModel(model)) {
|
|
73
|
+
const typeName = model.config?.name;
|
|
74
|
+
if (typeName && outputTypeCache.has(typeName)) {
|
|
75
|
+
return typeName;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return "JSON";
|
|
80
|
+
}
|
|
81
|
+
for (const spec of reg.list()) {
|
|
82
|
+
const fieldName = spec.transport?.gql?.field ?? defaultGqlField(spec.meta.key, spec.meta.version);
|
|
83
|
+
const byIdField = spec.transport?.gql?.byIdField ?? "id";
|
|
84
|
+
const returnsResource = spec.transport?.gql?.resource;
|
|
85
|
+
const isQuery = spec.meta.kind === "query";
|
|
86
|
+
const graphQLTypeName = resolveGraphQLTypeName(spec);
|
|
87
|
+
const returnsDecl = spec.transport?.gql?.returns;
|
|
88
|
+
const parsed = parseReturns(returnsDecl ?? graphQLTypeName);
|
|
89
|
+
const resolveFieldFn = async (_root, args, ctx) => {
|
|
90
|
+
if (spec.policy.auth !== "anonymous" && !ctx.user)
|
|
91
|
+
throw new Error("Unauthorized");
|
|
92
|
+
const handlerCtx = {
|
|
93
|
+
traceId: ctx.logger?.getTraceId?.() ?? undefined,
|
|
94
|
+
userId: ctx.user?.id ?? null,
|
|
95
|
+
organizationId: ctx.session?.activeOrganizationId ?? null,
|
|
96
|
+
actor: ctx.user ? "user" : "anonymous",
|
|
97
|
+
channel: "web",
|
|
98
|
+
eventPublisher: ctx.eventPublisher
|
|
99
|
+
};
|
|
100
|
+
const parsedInput = spec.io.input?.getZod().parse(args.input ?? {});
|
|
101
|
+
const result = await reg.execute(spec.meta.key, spec.meta.version, parsedInput, handlerCtx);
|
|
102
|
+
const out = spec.io.output;
|
|
103
|
+
if (resources && (returnsResource || out?.kind === "resource_ref")) {
|
|
104
|
+
const varName = byIdField ?? out?.varName ?? "id";
|
|
105
|
+
const template = returnsResource ?? out?.uriTemplate;
|
|
106
|
+
const hydrated = await hydrateResourceIfNeeded(resources, result, {
|
|
107
|
+
template,
|
|
108
|
+
varName,
|
|
109
|
+
returns: parsed
|
|
110
|
+
});
|
|
111
|
+
if (hydrated !== result)
|
|
112
|
+
return hydrated;
|
|
113
|
+
}
|
|
114
|
+
if (graphQLTypeName) {
|
|
115
|
+
if (parsed.inner === "Boolean" && !parsed.isList) {
|
|
116
|
+
return Boolean(result?.ok ?? result);
|
|
117
|
+
}
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
const parsedOut = spec.io.output.getZod().parse(result);
|
|
121
|
+
return parsedOut;
|
|
122
|
+
};
|
|
123
|
+
const fieldSettingsFn = (t) => {
|
|
124
|
+
const inputType = buildInputFieldArgs(spec.io.input);
|
|
125
|
+
return t.field({
|
|
126
|
+
type: parsed.isList ? [parsed.inner] : parsed.inner,
|
|
127
|
+
complexity: () => 10,
|
|
128
|
+
resolve: resolveFieldFn,
|
|
129
|
+
args: inputType ? {
|
|
130
|
+
input: t.arg({
|
|
131
|
+
type: inputType,
|
|
132
|
+
required: true
|
|
133
|
+
})
|
|
134
|
+
} : undefined
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
if (isQuery) {
|
|
138
|
+
builder.queryField(fieldName, fieldSettingsFn);
|
|
139
|
+
} else {
|
|
140
|
+
builder.mutationField(fieldName, fieldSettingsFn);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export {
|
|
145
|
+
registerContractsOnBuilder
|
|
146
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// src/graphql-pothos.ts
|
|
2
|
+
import { defaultGqlField } from "@contractspec/lib.contracts-spec/jsonschema";
|
|
3
|
+
import"@pothos/plugin-prisma";
|
|
4
|
+
import"@pothos/plugin-complexity";
|
|
5
|
+
import"@pothos/plugin-relay";
|
|
6
|
+
import"@pothos/plugin-dataloader";
|
|
7
|
+
import"@pothos/plugin-tracing";
|
|
8
|
+
import { isSchemaModel } from "@contractspec/lib.schema";
|
|
9
|
+
import { createInputTypeBuilder } from "@contractspec/lib.contracts-runtime-server-rest/contracts-adapter-input";
|
|
10
|
+
import {
|
|
11
|
+
hydrateResourceIfNeeded,
|
|
12
|
+
parseReturns
|
|
13
|
+
} from "@contractspec/lib.contracts-runtime-server-rest/contracts-adapter-hydration";
|
|
14
|
+
function registerContractsOnBuilder(builder, reg, resources) {
|
|
15
|
+
const { buildInputFieldArgs } = createInputTypeBuilder(builder);
|
|
16
|
+
const outputTypeCache = new Map;
|
|
17
|
+
for (const spec of reg.list()) {
|
|
18
|
+
const out = spec.io.output;
|
|
19
|
+
if (out && "getZod" in out && typeof out.getZod === "function") {
|
|
20
|
+
const model = out;
|
|
21
|
+
const typeName = isSchemaModel(model) ? model.config?.name : "UnknownOutput";
|
|
22
|
+
if (typeName && !outputTypeCache.has(typeName)) {
|
|
23
|
+
outputTypeCache.set(typeName, model);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
for (const [typeName, model] of outputTypeCache.entries()) {
|
|
28
|
+
if (!isSchemaModel(model))
|
|
29
|
+
continue;
|
|
30
|
+
builder.objectType(typeName, {
|
|
31
|
+
fields: (t) => {
|
|
32
|
+
const entries = Object.entries(model.config.fields);
|
|
33
|
+
const acc = {};
|
|
34
|
+
for (const [key, field] of entries) {
|
|
35
|
+
const fieldType = field.type;
|
|
36
|
+
let gqlType = "JSON";
|
|
37
|
+
if (fieldType && typeof fieldType.getPothos === "function") {
|
|
38
|
+
const pothosInfo = fieldType.getPothos();
|
|
39
|
+
gqlType = pothosInfo.name || "JSON";
|
|
40
|
+
if (gqlType === "String_unsecure")
|
|
41
|
+
gqlType = "String";
|
|
42
|
+
if (gqlType === "Int_unsecure")
|
|
43
|
+
gqlType = "Int";
|
|
44
|
+
if (gqlType === "Float_unsecure")
|
|
45
|
+
gqlType = "Float";
|
|
46
|
+
if (gqlType === "Boolean_unsecure")
|
|
47
|
+
gqlType = "Boolean";
|
|
48
|
+
if (gqlType === "ID_unsecure")
|
|
49
|
+
gqlType = "ID";
|
|
50
|
+
}
|
|
51
|
+
const typeRef = field.isArray ? [gqlType] : gqlType;
|
|
52
|
+
acc[key] = t.field({
|
|
53
|
+
type: typeRef,
|
|
54
|
+
nullable: field.isOptional,
|
|
55
|
+
resolve: (parent) => parent[key]
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return acc;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function resolveGraphQLTypeName(contractSpec) {
|
|
63
|
+
const returnsName = contractSpec.transport?.gql?.returns;
|
|
64
|
+
if (returnsName)
|
|
65
|
+
return returnsName;
|
|
66
|
+
const out = contractSpec.io.output ?? {};
|
|
67
|
+
if (out && "kind" in out && out.kind === "resource_ref" && "graphQLType" in out && out.graphQLType) {
|
|
68
|
+
return String(out.graphQLType);
|
|
69
|
+
}
|
|
70
|
+
if (out && "getZod" in out && typeof out.getZod === "function") {
|
|
71
|
+
const model = out;
|
|
72
|
+
if (isSchemaModel(model)) {
|
|
73
|
+
const typeName = model.config?.name;
|
|
74
|
+
if (typeName && outputTypeCache.has(typeName)) {
|
|
75
|
+
return typeName;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return "JSON";
|
|
80
|
+
}
|
|
81
|
+
for (const spec of reg.list()) {
|
|
82
|
+
const fieldName = spec.transport?.gql?.field ?? defaultGqlField(spec.meta.key, spec.meta.version);
|
|
83
|
+
const byIdField = spec.transport?.gql?.byIdField ?? "id";
|
|
84
|
+
const returnsResource = spec.transport?.gql?.resource;
|
|
85
|
+
const isQuery = spec.meta.kind === "query";
|
|
86
|
+
const graphQLTypeName = resolveGraphQLTypeName(spec);
|
|
87
|
+
const returnsDecl = spec.transport?.gql?.returns;
|
|
88
|
+
const parsed = parseReturns(returnsDecl ?? graphQLTypeName);
|
|
89
|
+
const resolveFieldFn = async (_root, args, ctx) => {
|
|
90
|
+
if (spec.policy.auth !== "anonymous" && !ctx.user)
|
|
91
|
+
throw new Error("Unauthorized");
|
|
92
|
+
const handlerCtx = {
|
|
93
|
+
traceId: ctx.logger?.getTraceId?.() ?? undefined,
|
|
94
|
+
userId: ctx.user?.id ?? null,
|
|
95
|
+
organizationId: ctx.session?.activeOrganizationId ?? null,
|
|
96
|
+
actor: ctx.user ? "user" : "anonymous",
|
|
97
|
+
channel: "web",
|
|
98
|
+
eventPublisher: ctx.eventPublisher
|
|
99
|
+
};
|
|
100
|
+
const parsedInput = spec.io.input?.getZod().parse(args.input ?? {});
|
|
101
|
+
const result = await reg.execute(spec.meta.key, spec.meta.version, parsedInput, handlerCtx);
|
|
102
|
+
const out = spec.io.output;
|
|
103
|
+
if (resources && (returnsResource || out?.kind === "resource_ref")) {
|
|
104
|
+
const varName = byIdField ?? out?.varName ?? "id";
|
|
105
|
+
const template = returnsResource ?? out?.uriTemplate;
|
|
106
|
+
const hydrated = await hydrateResourceIfNeeded(resources, result, {
|
|
107
|
+
template,
|
|
108
|
+
varName,
|
|
109
|
+
returns: parsed
|
|
110
|
+
});
|
|
111
|
+
if (hydrated !== result)
|
|
112
|
+
return hydrated;
|
|
113
|
+
}
|
|
114
|
+
if (graphQLTypeName) {
|
|
115
|
+
if (parsed.inner === "Boolean" && !parsed.isList) {
|
|
116
|
+
return Boolean(result?.ok ?? result);
|
|
117
|
+
}
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
const parsedOut = spec.io.output.getZod().parse(result);
|
|
121
|
+
return parsedOut;
|
|
122
|
+
};
|
|
123
|
+
const fieldSettingsFn = (t) => {
|
|
124
|
+
const inputType = buildInputFieldArgs(spec.io.input);
|
|
125
|
+
return t.field({
|
|
126
|
+
type: parsed.isList ? [parsed.inner] : parsed.inner,
|
|
127
|
+
complexity: () => 10,
|
|
128
|
+
resolve: resolveFieldFn,
|
|
129
|
+
args: inputType ? {
|
|
130
|
+
input: t.arg({
|
|
131
|
+
type: inputType,
|
|
132
|
+
required: true
|
|
133
|
+
})
|
|
134
|
+
} : undefined
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
if (isQuery) {
|
|
138
|
+
builder.queryField(fieldName, fieldSettingsFn);
|
|
139
|
+
} else {
|
|
140
|
+
builder.mutationField(fieldName, fieldSettingsFn);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export {
|
|
145
|
+
registerContractsOnBuilder
|
|
146
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SchemaTypes } from '@pothos/core';
|
|
2
|
+
import type { OperationSpecRegistry } from '@contractspec/lib.contracts-spec/operations/registry';
|
|
3
|
+
import '@pothos/plugin-prisma';
|
|
4
|
+
import '@pothos/plugin-complexity';
|
|
5
|
+
import '@pothos/plugin-relay';
|
|
6
|
+
import '@pothos/plugin-dataloader';
|
|
7
|
+
import '@pothos/plugin-tracing';
|
|
8
|
+
import type { ResourceRegistry } from '@contractspec/lib.contracts-spec/resources';
|
|
9
|
+
export declare function registerContractsOnBuilder<T extends SchemaTypes>(builder: PothosSchemaTypes.SchemaBuilder<T>, reg: OperationSpecRegistry, resources?: ResourceRegistry): void;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/graphql-pothos.ts
|
|
3
|
+
import { defaultGqlField } from "@contractspec/lib.contracts-spec/jsonschema";
|
|
4
|
+
import"@pothos/plugin-prisma";
|
|
5
|
+
import"@pothos/plugin-complexity";
|
|
6
|
+
import"@pothos/plugin-relay";
|
|
7
|
+
import"@pothos/plugin-dataloader";
|
|
8
|
+
import"@pothos/plugin-tracing";
|
|
9
|
+
import { isSchemaModel } from "@contractspec/lib.schema";
|
|
10
|
+
import { createInputTypeBuilder } from "@contractspec/lib.contracts-runtime-server-rest/contracts-adapter-input";
|
|
11
|
+
import {
|
|
12
|
+
hydrateResourceIfNeeded,
|
|
13
|
+
parseReturns
|
|
14
|
+
} from "@contractspec/lib.contracts-runtime-server-rest/contracts-adapter-hydration";
|
|
15
|
+
function registerContractsOnBuilder(builder, reg, resources) {
|
|
16
|
+
const { buildInputFieldArgs } = createInputTypeBuilder(builder);
|
|
17
|
+
const outputTypeCache = new Map;
|
|
18
|
+
for (const spec of reg.list()) {
|
|
19
|
+
const out = spec.io.output;
|
|
20
|
+
if (out && "getZod" in out && typeof out.getZod === "function") {
|
|
21
|
+
const model = out;
|
|
22
|
+
const typeName = isSchemaModel(model) ? model.config?.name : "UnknownOutput";
|
|
23
|
+
if (typeName && !outputTypeCache.has(typeName)) {
|
|
24
|
+
outputTypeCache.set(typeName, model);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
for (const [typeName, model] of outputTypeCache.entries()) {
|
|
29
|
+
if (!isSchemaModel(model))
|
|
30
|
+
continue;
|
|
31
|
+
builder.objectType(typeName, {
|
|
32
|
+
fields: (t) => {
|
|
33
|
+
const entries = Object.entries(model.config.fields);
|
|
34
|
+
const acc = {};
|
|
35
|
+
for (const [key, field] of entries) {
|
|
36
|
+
const fieldType = field.type;
|
|
37
|
+
let gqlType = "JSON";
|
|
38
|
+
if (fieldType && typeof fieldType.getPothos === "function") {
|
|
39
|
+
const pothosInfo = fieldType.getPothos();
|
|
40
|
+
gqlType = pothosInfo.name || "JSON";
|
|
41
|
+
if (gqlType === "String_unsecure")
|
|
42
|
+
gqlType = "String";
|
|
43
|
+
if (gqlType === "Int_unsecure")
|
|
44
|
+
gqlType = "Int";
|
|
45
|
+
if (gqlType === "Float_unsecure")
|
|
46
|
+
gqlType = "Float";
|
|
47
|
+
if (gqlType === "Boolean_unsecure")
|
|
48
|
+
gqlType = "Boolean";
|
|
49
|
+
if (gqlType === "ID_unsecure")
|
|
50
|
+
gqlType = "ID";
|
|
51
|
+
}
|
|
52
|
+
const typeRef = field.isArray ? [gqlType] : gqlType;
|
|
53
|
+
acc[key] = t.field({
|
|
54
|
+
type: typeRef,
|
|
55
|
+
nullable: field.isOptional,
|
|
56
|
+
resolve: (parent) => parent[key]
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return acc;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function resolveGraphQLTypeName(contractSpec) {
|
|
64
|
+
const returnsName = contractSpec.transport?.gql?.returns;
|
|
65
|
+
if (returnsName)
|
|
66
|
+
return returnsName;
|
|
67
|
+
const out = contractSpec.io.output ?? {};
|
|
68
|
+
if (out && "kind" in out && out.kind === "resource_ref" && "graphQLType" in out && out.graphQLType) {
|
|
69
|
+
return String(out.graphQLType);
|
|
70
|
+
}
|
|
71
|
+
if (out && "getZod" in out && typeof out.getZod === "function") {
|
|
72
|
+
const model = out;
|
|
73
|
+
if (isSchemaModel(model)) {
|
|
74
|
+
const typeName = model.config?.name;
|
|
75
|
+
if (typeName && outputTypeCache.has(typeName)) {
|
|
76
|
+
return typeName;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return "JSON";
|
|
81
|
+
}
|
|
82
|
+
for (const spec of reg.list()) {
|
|
83
|
+
const fieldName = spec.transport?.gql?.field ?? defaultGqlField(spec.meta.key, spec.meta.version);
|
|
84
|
+
const byIdField = spec.transport?.gql?.byIdField ?? "id";
|
|
85
|
+
const returnsResource = spec.transport?.gql?.resource;
|
|
86
|
+
const isQuery = spec.meta.kind === "query";
|
|
87
|
+
const graphQLTypeName = resolveGraphQLTypeName(spec);
|
|
88
|
+
const returnsDecl = spec.transport?.gql?.returns;
|
|
89
|
+
const parsed = parseReturns(returnsDecl ?? graphQLTypeName);
|
|
90
|
+
const resolveFieldFn = async (_root, args, ctx) => {
|
|
91
|
+
if (spec.policy.auth !== "anonymous" && !ctx.user)
|
|
92
|
+
throw new Error("Unauthorized");
|
|
93
|
+
const handlerCtx = {
|
|
94
|
+
traceId: ctx.logger?.getTraceId?.() ?? undefined,
|
|
95
|
+
userId: ctx.user?.id ?? null,
|
|
96
|
+
organizationId: ctx.session?.activeOrganizationId ?? null,
|
|
97
|
+
actor: ctx.user ? "user" : "anonymous",
|
|
98
|
+
channel: "web",
|
|
99
|
+
eventPublisher: ctx.eventPublisher
|
|
100
|
+
};
|
|
101
|
+
const parsedInput = spec.io.input?.getZod().parse(args.input ?? {});
|
|
102
|
+
const result = await reg.execute(spec.meta.key, spec.meta.version, parsedInput, handlerCtx);
|
|
103
|
+
const out = spec.io.output;
|
|
104
|
+
if (resources && (returnsResource || out?.kind === "resource_ref")) {
|
|
105
|
+
const varName = byIdField ?? out?.varName ?? "id";
|
|
106
|
+
const template = returnsResource ?? out?.uriTemplate;
|
|
107
|
+
const hydrated = await hydrateResourceIfNeeded(resources, result, {
|
|
108
|
+
template,
|
|
109
|
+
varName,
|
|
110
|
+
returns: parsed
|
|
111
|
+
});
|
|
112
|
+
if (hydrated !== result)
|
|
113
|
+
return hydrated;
|
|
114
|
+
}
|
|
115
|
+
if (graphQLTypeName) {
|
|
116
|
+
if (parsed.inner === "Boolean" && !parsed.isList) {
|
|
117
|
+
return Boolean(result?.ok ?? result);
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
const parsedOut = spec.io.output.getZod().parse(result);
|
|
122
|
+
return parsedOut;
|
|
123
|
+
};
|
|
124
|
+
const fieldSettingsFn = (t) => {
|
|
125
|
+
const inputType = buildInputFieldArgs(spec.io.input);
|
|
126
|
+
return t.field({
|
|
127
|
+
type: parsed.isList ? [parsed.inner] : parsed.inner,
|
|
128
|
+
complexity: () => 10,
|
|
129
|
+
resolve: resolveFieldFn,
|
|
130
|
+
args: inputType ? {
|
|
131
|
+
input: t.arg({
|
|
132
|
+
type: inputType,
|
|
133
|
+
required: true
|
|
134
|
+
})
|
|
135
|
+
} : undefined
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
if (isQuery) {
|
|
139
|
+
builder.queryField(fieldName, fieldSettingsFn);
|
|
140
|
+
} else {
|
|
141
|
+
builder.mutationField(fieldName, fieldSettingsFn);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
export {
|
|
146
|
+
registerContractsOnBuilder
|
|
147
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './graphql-pothos';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/graphql-pothos.ts
|
|
3
|
+
import { defaultGqlField } from "@contractspec/lib.contracts-spec/jsonschema";
|
|
4
|
+
import"@pothos/plugin-prisma";
|
|
5
|
+
import"@pothos/plugin-complexity";
|
|
6
|
+
import"@pothos/plugin-relay";
|
|
7
|
+
import"@pothos/plugin-dataloader";
|
|
8
|
+
import"@pothos/plugin-tracing";
|
|
9
|
+
import { isSchemaModel } from "@contractspec/lib.schema";
|
|
10
|
+
import { createInputTypeBuilder } from "@contractspec/lib.contracts-runtime-server-rest/contracts-adapter-input";
|
|
11
|
+
import {
|
|
12
|
+
hydrateResourceIfNeeded,
|
|
13
|
+
parseReturns
|
|
14
|
+
} from "@contractspec/lib.contracts-runtime-server-rest/contracts-adapter-hydration";
|
|
15
|
+
function registerContractsOnBuilder(builder, reg, resources) {
|
|
16
|
+
const { buildInputFieldArgs } = createInputTypeBuilder(builder);
|
|
17
|
+
const outputTypeCache = new Map;
|
|
18
|
+
for (const spec of reg.list()) {
|
|
19
|
+
const out = spec.io.output;
|
|
20
|
+
if (out && "getZod" in out && typeof out.getZod === "function") {
|
|
21
|
+
const model = out;
|
|
22
|
+
const typeName = isSchemaModel(model) ? model.config?.name : "UnknownOutput";
|
|
23
|
+
if (typeName && !outputTypeCache.has(typeName)) {
|
|
24
|
+
outputTypeCache.set(typeName, model);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
for (const [typeName, model] of outputTypeCache.entries()) {
|
|
29
|
+
if (!isSchemaModel(model))
|
|
30
|
+
continue;
|
|
31
|
+
builder.objectType(typeName, {
|
|
32
|
+
fields: (t) => {
|
|
33
|
+
const entries = Object.entries(model.config.fields);
|
|
34
|
+
const acc = {};
|
|
35
|
+
for (const [key, field] of entries) {
|
|
36
|
+
const fieldType = field.type;
|
|
37
|
+
let gqlType = "JSON";
|
|
38
|
+
if (fieldType && typeof fieldType.getPothos === "function") {
|
|
39
|
+
const pothosInfo = fieldType.getPothos();
|
|
40
|
+
gqlType = pothosInfo.name || "JSON";
|
|
41
|
+
if (gqlType === "String_unsecure")
|
|
42
|
+
gqlType = "String";
|
|
43
|
+
if (gqlType === "Int_unsecure")
|
|
44
|
+
gqlType = "Int";
|
|
45
|
+
if (gqlType === "Float_unsecure")
|
|
46
|
+
gqlType = "Float";
|
|
47
|
+
if (gqlType === "Boolean_unsecure")
|
|
48
|
+
gqlType = "Boolean";
|
|
49
|
+
if (gqlType === "ID_unsecure")
|
|
50
|
+
gqlType = "ID";
|
|
51
|
+
}
|
|
52
|
+
const typeRef = field.isArray ? [gqlType] : gqlType;
|
|
53
|
+
acc[key] = t.field({
|
|
54
|
+
type: typeRef,
|
|
55
|
+
nullable: field.isOptional,
|
|
56
|
+
resolve: (parent) => parent[key]
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return acc;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function resolveGraphQLTypeName(contractSpec) {
|
|
64
|
+
const returnsName = contractSpec.transport?.gql?.returns;
|
|
65
|
+
if (returnsName)
|
|
66
|
+
return returnsName;
|
|
67
|
+
const out = contractSpec.io.output ?? {};
|
|
68
|
+
if (out && "kind" in out && out.kind === "resource_ref" && "graphQLType" in out && out.graphQLType) {
|
|
69
|
+
return String(out.graphQLType);
|
|
70
|
+
}
|
|
71
|
+
if (out && "getZod" in out && typeof out.getZod === "function") {
|
|
72
|
+
const model = out;
|
|
73
|
+
if (isSchemaModel(model)) {
|
|
74
|
+
const typeName = model.config?.name;
|
|
75
|
+
if (typeName && outputTypeCache.has(typeName)) {
|
|
76
|
+
return typeName;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return "JSON";
|
|
81
|
+
}
|
|
82
|
+
for (const spec of reg.list()) {
|
|
83
|
+
const fieldName = spec.transport?.gql?.field ?? defaultGqlField(spec.meta.key, spec.meta.version);
|
|
84
|
+
const byIdField = spec.transport?.gql?.byIdField ?? "id";
|
|
85
|
+
const returnsResource = spec.transport?.gql?.resource;
|
|
86
|
+
const isQuery = spec.meta.kind === "query";
|
|
87
|
+
const graphQLTypeName = resolveGraphQLTypeName(spec);
|
|
88
|
+
const returnsDecl = spec.transport?.gql?.returns;
|
|
89
|
+
const parsed = parseReturns(returnsDecl ?? graphQLTypeName);
|
|
90
|
+
const resolveFieldFn = async (_root, args, ctx) => {
|
|
91
|
+
if (spec.policy.auth !== "anonymous" && !ctx.user)
|
|
92
|
+
throw new Error("Unauthorized");
|
|
93
|
+
const handlerCtx = {
|
|
94
|
+
traceId: ctx.logger?.getTraceId?.() ?? undefined,
|
|
95
|
+
userId: ctx.user?.id ?? null,
|
|
96
|
+
organizationId: ctx.session?.activeOrganizationId ?? null,
|
|
97
|
+
actor: ctx.user ? "user" : "anonymous",
|
|
98
|
+
channel: "web",
|
|
99
|
+
eventPublisher: ctx.eventPublisher
|
|
100
|
+
};
|
|
101
|
+
const parsedInput = spec.io.input?.getZod().parse(args.input ?? {});
|
|
102
|
+
const result = await reg.execute(spec.meta.key, spec.meta.version, parsedInput, handlerCtx);
|
|
103
|
+
const out = spec.io.output;
|
|
104
|
+
if (resources && (returnsResource || out?.kind === "resource_ref")) {
|
|
105
|
+
const varName = byIdField ?? out?.varName ?? "id";
|
|
106
|
+
const template = returnsResource ?? out?.uriTemplate;
|
|
107
|
+
const hydrated = await hydrateResourceIfNeeded(resources, result, {
|
|
108
|
+
template,
|
|
109
|
+
varName,
|
|
110
|
+
returns: parsed
|
|
111
|
+
});
|
|
112
|
+
if (hydrated !== result)
|
|
113
|
+
return hydrated;
|
|
114
|
+
}
|
|
115
|
+
if (graphQLTypeName) {
|
|
116
|
+
if (parsed.inner === "Boolean" && !parsed.isList) {
|
|
117
|
+
return Boolean(result?.ok ?? result);
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
const parsedOut = spec.io.output.getZod().parse(result);
|
|
122
|
+
return parsedOut;
|
|
123
|
+
};
|
|
124
|
+
const fieldSettingsFn = (t) => {
|
|
125
|
+
const inputType = buildInputFieldArgs(spec.io.input);
|
|
126
|
+
return t.field({
|
|
127
|
+
type: parsed.isList ? [parsed.inner] : parsed.inner,
|
|
128
|
+
complexity: () => 10,
|
|
129
|
+
resolve: resolveFieldFn,
|
|
130
|
+
args: inputType ? {
|
|
131
|
+
input: t.arg({
|
|
132
|
+
type: inputType,
|
|
133
|
+
required: true
|
|
134
|
+
})
|
|
135
|
+
} : undefined
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
if (isQuery) {
|
|
139
|
+
builder.queryField(fieldName, fieldSettingsFn);
|
|
140
|
+
} else {
|
|
141
|
+
builder.mutationField(fieldName, fieldSettingsFn);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
export {
|
|
146
|
+
registerContractsOnBuilder
|
|
147
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// src/graphql-pothos.ts
|
|
2
|
+
import { defaultGqlField } from "@contractspec/lib.contracts-spec/jsonschema";
|
|
3
|
+
import"@pothos/plugin-prisma";
|
|
4
|
+
import"@pothos/plugin-complexity";
|
|
5
|
+
import"@pothos/plugin-relay";
|
|
6
|
+
import"@pothos/plugin-dataloader";
|
|
7
|
+
import"@pothos/plugin-tracing";
|
|
8
|
+
import { isSchemaModel } from "@contractspec/lib.schema";
|
|
9
|
+
import { createInputTypeBuilder } from "@contractspec/lib.contracts-runtime-server-rest/contracts-adapter-input";
|
|
10
|
+
import {
|
|
11
|
+
hydrateResourceIfNeeded,
|
|
12
|
+
parseReturns
|
|
13
|
+
} from "@contractspec/lib.contracts-runtime-server-rest/contracts-adapter-hydration";
|
|
14
|
+
function registerContractsOnBuilder(builder, reg, resources) {
|
|
15
|
+
const { buildInputFieldArgs } = createInputTypeBuilder(builder);
|
|
16
|
+
const outputTypeCache = new Map;
|
|
17
|
+
for (const spec of reg.list()) {
|
|
18
|
+
const out = spec.io.output;
|
|
19
|
+
if (out && "getZod" in out && typeof out.getZod === "function") {
|
|
20
|
+
const model = out;
|
|
21
|
+
const typeName = isSchemaModel(model) ? model.config?.name : "UnknownOutput";
|
|
22
|
+
if (typeName && !outputTypeCache.has(typeName)) {
|
|
23
|
+
outputTypeCache.set(typeName, model);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
for (const [typeName, model] of outputTypeCache.entries()) {
|
|
28
|
+
if (!isSchemaModel(model))
|
|
29
|
+
continue;
|
|
30
|
+
builder.objectType(typeName, {
|
|
31
|
+
fields: (t) => {
|
|
32
|
+
const entries = Object.entries(model.config.fields);
|
|
33
|
+
const acc = {};
|
|
34
|
+
for (const [key, field] of entries) {
|
|
35
|
+
const fieldType = field.type;
|
|
36
|
+
let gqlType = "JSON";
|
|
37
|
+
if (fieldType && typeof fieldType.getPothos === "function") {
|
|
38
|
+
const pothosInfo = fieldType.getPothos();
|
|
39
|
+
gqlType = pothosInfo.name || "JSON";
|
|
40
|
+
if (gqlType === "String_unsecure")
|
|
41
|
+
gqlType = "String";
|
|
42
|
+
if (gqlType === "Int_unsecure")
|
|
43
|
+
gqlType = "Int";
|
|
44
|
+
if (gqlType === "Float_unsecure")
|
|
45
|
+
gqlType = "Float";
|
|
46
|
+
if (gqlType === "Boolean_unsecure")
|
|
47
|
+
gqlType = "Boolean";
|
|
48
|
+
if (gqlType === "ID_unsecure")
|
|
49
|
+
gqlType = "ID";
|
|
50
|
+
}
|
|
51
|
+
const typeRef = field.isArray ? [gqlType] : gqlType;
|
|
52
|
+
acc[key] = t.field({
|
|
53
|
+
type: typeRef,
|
|
54
|
+
nullable: field.isOptional,
|
|
55
|
+
resolve: (parent) => parent[key]
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return acc;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function resolveGraphQLTypeName(contractSpec) {
|
|
63
|
+
const returnsName = contractSpec.transport?.gql?.returns;
|
|
64
|
+
if (returnsName)
|
|
65
|
+
return returnsName;
|
|
66
|
+
const out = contractSpec.io.output ?? {};
|
|
67
|
+
if (out && "kind" in out && out.kind === "resource_ref" && "graphQLType" in out && out.graphQLType) {
|
|
68
|
+
return String(out.graphQLType);
|
|
69
|
+
}
|
|
70
|
+
if (out && "getZod" in out && typeof out.getZod === "function") {
|
|
71
|
+
const model = out;
|
|
72
|
+
if (isSchemaModel(model)) {
|
|
73
|
+
const typeName = model.config?.name;
|
|
74
|
+
if (typeName && outputTypeCache.has(typeName)) {
|
|
75
|
+
return typeName;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return "JSON";
|
|
80
|
+
}
|
|
81
|
+
for (const spec of reg.list()) {
|
|
82
|
+
const fieldName = spec.transport?.gql?.field ?? defaultGqlField(spec.meta.key, spec.meta.version);
|
|
83
|
+
const byIdField = spec.transport?.gql?.byIdField ?? "id";
|
|
84
|
+
const returnsResource = spec.transport?.gql?.resource;
|
|
85
|
+
const isQuery = spec.meta.kind === "query";
|
|
86
|
+
const graphQLTypeName = resolveGraphQLTypeName(spec);
|
|
87
|
+
const returnsDecl = spec.transport?.gql?.returns;
|
|
88
|
+
const parsed = parseReturns(returnsDecl ?? graphQLTypeName);
|
|
89
|
+
const resolveFieldFn = async (_root, args, ctx) => {
|
|
90
|
+
if (spec.policy.auth !== "anonymous" && !ctx.user)
|
|
91
|
+
throw new Error("Unauthorized");
|
|
92
|
+
const handlerCtx = {
|
|
93
|
+
traceId: ctx.logger?.getTraceId?.() ?? undefined,
|
|
94
|
+
userId: ctx.user?.id ?? null,
|
|
95
|
+
organizationId: ctx.session?.activeOrganizationId ?? null,
|
|
96
|
+
actor: ctx.user ? "user" : "anonymous",
|
|
97
|
+
channel: "web",
|
|
98
|
+
eventPublisher: ctx.eventPublisher
|
|
99
|
+
};
|
|
100
|
+
const parsedInput = spec.io.input?.getZod().parse(args.input ?? {});
|
|
101
|
+
const result = await reg.execute(spec.meta.key, spec.meta.version, parsedInput, handlerCtx);
|
|
102
|
+
const out = spec.io.output;
|
|
103
|
+
if (resources && (returnsResource || out?.kind === "resource_ref")) {
|
|
104
|
+
const varName = byIdField ?? out?.varName ?? "id";
|
|
105
|
+
const template = returnsResource ?? out?.uriTemplate;
|
|
106
|
+
const hydrated = await hydrateResourceIfNeeded(resources, result, {
|
|
107
|
+
template,
|
|
108
|
+
varName,
|
|
109
|
+
returns: parsed
|
|
110
|
+
});
|
|
111
|
+
if (hydrated !== result)
|
|
112
|
+
return hydrated;
|
|
113
|
+
}
|
|
114
|
+
if (graphQLTypeName) {
|
|
115
|
+
if (parsed.inner === "Boolean" && !parsed.isList) {
|
|
116
|
+
return Boolean(result?.ok ?? result);
|
|
117
|
+
}
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
const parsedOut = spec.io.output.getZod().parse(result);
|
|
121
|
+
return parsedOut;
|
|
122
|
+
};
|
|
123
|
+
const fieldSettingsFn = (t) => {
|
|
124
|
+
const inputType = buildInputFieldArgs(spec.io.input);
|
|
125
|
+
return t.field({
|
|
126
|
+
type: parsed.isList ? [parsed.inner] : parsed.inner,
|
|
127
|
+
complexity: () => 10,
|
|
128
|
+
resolve: resolveFieldFn,
|
|
129
|
+
args: inputType ? {
|
|
130
|
+
input: t.arg({
|
|
131
|
+
type: inputType,
|
|
132
|
+
required: true
|
|
133
|
+
})
|
|
134
|
+
} : undefined
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
if (isQuery) {
|
|
138
|
+
builder.queryField(fieldName, fieldSettingsFn);
|
|
139
|
+
} else {
|
|
140
|
+
builder.mutationField(fieldName, fieldSettingsFn);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export {
|
|
145
|
+
registerContractsOnBuilder
|
|
146
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// src/graphql-pothos.ts
|
|
2
|
+
import { defaultGqlField } from "@contractspec/lib.contracts-spec/jsonschema";
|
|
3
|
+
import"@pothos/plugin-prisma";
|
|
4
|
+
import"@pothos/plugin-complexity";
|
|
5
|
+
import"@pothos/plugin-relay";
|
|
6
|
+
import"@pothos/plugin-dataloader";
|
|
7
|
+
import"@pothos/plugin-tracing";
|
|
8
|
+
import { isSchemaModel } from "@contractspec/lib.schema";
|
|
9
|
+
import { createInputTypeBuilder } from "@contractspec/lib.contracts-runtime-server-rest/contracts-adapter-input";
|
|
10
|
+
import {
|
|
11
|
+
hydrateResourceIfNeeded,
|
|
12
|
+
parseReturns
|
|
13
|
+
} from "@contractspec/lib.contracts-runtime-server-rest/contracts-adapter-hydration";
|
|
14
|
+
function registerContractsOnBuilder(builder, reg, resources) {
|
|
15
|
+
const { buildInputFieldArgs } = createInputTypeBuilder(builder);
|
|
16
|
+
const outputTypeCache = new Map;
|
|
17
|
+
for (const spec of reg.list()) {
|
|
18
|
+
const out = spec.io.output;
|
|
19
|
+
if (out && "getZod" in out && typeof out.getZod === "function") {
|
|
20
|
+
const model = out;
|
|
21
|
+
const typeName = isSchemaModel(model) ? model.config?.name : "UnknownOutput";
|
|
22
|
+
if (typeName && !outputTypeCache.has(typeName)) {
|
|
23
|
+
outputTypeCache.set(typeName, model);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
for (const [typeName, model] of outputTypeCache.entries()) {
|
|
28
|
+
if (!isSchemaModel(model))
|
|
29
|
+
continue;
|
|
30
|
+
builder.objectType(typeName, {
|
|
31
|
+
fields: (t) => {
|
|
32
|
+
const entries = Object.entries(model.config.fields);
|
|
33
|
+
const acc = {};
|
|
34
|
+
for (const [key, field] of entries) {
|
|
35
|
+
const fieldType = field.type;
|
|
36
|
+
let gqlType = "JSON";
|
|
37
|
+
if (fieldType && typeof fieldType.getPothos === "function") {
|
|
38
|
+
const pothosInfo = fieldType.getPothos();
|
|
39
|
+
gqlType = pothosInfo.name || "JSON";
|
|
40
|
+
if (gqlType === "String_unsecure")
|
|
41
|
+
gqlType = "String";
|
|
42
|
+
if (gqlType === "Int_unsecure")
|
|
43
|
+
gqlType = "Int";
|
|
44
|
+
if (gqlType === "Float_unsecure")
|
|
45
|
+
gqlType = "Float";
|
|
46
|
+
if (gqlType === "Boolean_unsecure")
|
|
47
|
+
gqlType = "Boolean";
|
|
48
|
+
if (gqlType === "ID_unsecure")
|
|
49
|
+
gqlType = "ID";
|
|
50
|
+
}
|
|
51
|
+
const typeRef = field.isArray ? [gqlType] : gqlType;
|
|
52
|
+
acc[key] = t.field({
|
|
53
|
+
type: typeRef,
|
|
54
|
+
nullable: field.isOptional,
|
|
55
|
+
resolve: (parent) => parent[key]
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return acc;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function resolveGraphQLTypeName(contractSpec) {
|
|
63
|
+
const returnsName = contractSpec.transport?.gql?.returns;
|
|
64
|
+
if (returnsName)
|
|
65
|
+
return returnsName;
|
|
66
|
+
const out = contractSpec.io.output ?? {};
|
|
67
|
+
if (out && "kind" in out && out.kind === "resource_ref" && "graphQLType" in out && out.graphQLType) {
|
|
68
|
+
return String(out.graphQLType);
|
|
69
|
+
}
|
|
70
|
+
if (out && "getZod" in out && typeof out.getZod === "function") {
|
|
71
|
+
const model = out;
|
|
72
|
+
if (isSchemaModel(model)) {
|
|
73
|
+
const typeName = model.config?.name;
|
|
74
|
+
if (typeName && outputTypeCache.has(typeName)) {
|
|
75
|
+
return typeName;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return "JSON";
|
|
80
|
+
}
|
|
81
|
+
for (const spec of reg.list()) {
|
|
82
|
+
const fieldName = spec.transport?.gql?.field ?? defaultGqlField(spec.meta.key, spec.meta.version);
|
|
83
|
+
const byIdField = spec.transport?.gql?.byIdField ?? "id";
|
|
84
|
+
const returnsResource = spec.transport?.gql?.resource;
|
|
85
|
+
const isQuery = spec.meta.kind === "query";
|
|
86
|
+
const graphQLTypeName = resolveGraphQLTypeName(spec);
|
|
87
|
+
const returnsDecl = spec.transport?.gql?.returns;
|
|
88
|
+
const parsed = parseReturns(returnsDecl ?? graphQLTypeName);
|
|
89
|
+
const resolveFieldFn = async (_root, args, ctx) => {
|
|
90
|
+
if (spec.policy.auth !== "anonymous" && !ctx.user)
|
|
91
|
+
throw new Error("Unauthorized");
|
|
92
|
+
const handlerCtx = {
|
|
93
|
+
traceId: ctx.logger?.getTraceId?.() ?? undefined,
|
|
94
|
+
userId: ctx.user?.id ?? null,
|
|
95
|
+
organizationId: ctx.session?.activeOrganizationId ?? null,
|
|
96
|
+
actor: ctx.user ? "user" : "anonymous",
|
|
97
|
+
channel: "web",
|
|
98
|
+
eventPublisher: ctx.eventPublisher
|
|
99
|
+
};
|
|
100
|
+
const parsedInput = spec.io.input?.getZod().parse(args.input ?? {});
|
|
101
|
+
const result = await reg.execute(spec.meta.key, spec.meta.version, parsedInput, handlerCtx);
|
|
102
|
+
const out = spec.io.output;
|
|
103
|
+
if (resources && (returnsResource || out?.kind === "resource_ref")) {
|
|
104
|
+
const varName = byIdField ?? out?.varName ?? "id";
|
|
105
|
+
const template = returnsResource ?? out?.uriTemplate;
|
|
106
|
+
const hydrated = await hydrateResourceIfNeeded(resources, result, {
|
|
107
|
+
template,
|
|
108
|
+
varName,
|
|
109
|
+
returns: parsed
|
|
110
|
+
});
|
|
111
|
+
if (hydrated !== result)
|
|
112
|
+
return hydrated;
|
|
113
|
+
}
|
|
114
|
+
if (graphQLTypeName) {
|
|
115
|
+
if (parsed.inner === "Boolean" && !parsed.isList) {
|
|
116
|
+
return Boolean(result?.ok ?? result);
|
|
117
|
+
}
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
const parsedOut = spec.io.output.getZod().parse(result);
|
|
121
|
+
return parsedOut;
|
|
122
|
+
};
|
|
123
|
+
const fieldSettingsFn = (t) => {
|
|
124
|
+
const inputType = buildInputFieldArgs(spec.io.input);
|
|
125
|
+
return t.field({
|
|
126
|
+
type: parsed.isList ? [parsed.inner] : parsed.inner,
|
|
127
|
+
complexity: () => 10,
|
|
128
|
+
resolve: resolveFieldFn,
|
|
129
|
+
args: inputType ? {
|
|
130
|
+
input: t.arg({
|
|
131
|
+
type: inputType,
|
|
132
|
+
required: true
|
|
133
|
+
})
|
|
134
|
+
} : undefined
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
if (isQuery) {
|
|
138
|
+
builder.queryField(fieldName, fieldSettingsFn);
|
|
139
|
+
} else {
|
|
140
|
+
builder.mutationField(fieldName, fieldSettingsFn);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export {
|
|
145
|
+
registerContractsOnBuilder
|
|
146
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@contractspec/lib.contracts-runtime-server-graphql",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "GraphQL server runtime adapters for ContractSpec contracts",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"publish:pkg": "bun publish --tolerate-republish --ignore-scripts --verbose",
|
|
9
|
+
"publish:pkg:canary": "bun publish:pkg --tag canary",
|
|
10
|
+
"clean": "rm -rf dist",
|
|
11
|
+
"lint": "bun run lint:fix",
|
|
12
|
+
"lint:fix": "eslint src --fix",
|
|
13
|
+
"lint:check": "eslint src",
|
|
14
|
+
"build": "bun run prebuild && bun run build:bundle && bun run build:types",
|
|
15
|
+
"build:bundle": "contractspec-bun-build transpile",
|
|
16
|
+
"build:types": "contractspec-bun-build types",
|
|
17
|
+
"prebuild": "contractspec-bun-build prebuild",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"dev": "contractspec-bun-build dev"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@contractspec/lib.contracts-runtime-server-rest": "2.0.0",
|
|
23
|
+
"@contractspec/lib.contracts-spec": "2.0.0",
|
|
24
|
+
"@contractspec/lib.schema": "2.0.0",
|
|
25
|
+
"@pothos/plugin-complexity": "^4.1.2",
|
|
26
|
+
"@pothos/plugin-dataloader": "^4.4.2",
|
|
27
|
+
"@pothos/plugin-prisma": "^4.14.0",
|
|
28
|
+
"@pothos/plugin-relay": "^4.7.0",
|
|
29
|
+
"@pothos/plugin-tracing": "^1.1.2"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@pothos/core": "^4.9.1",
|
|
33
|
+
"@pothos/plugin-relay": "^4.7.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@contractspec/tool.typescript": "2.0.0",
|
|
37
|
+
"typescript": "^5.9.3",
|
|
38
|
+
"@contractspec/tool.bun": "2.0.0"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"README.md"
|
|
43
|
+
],
|
|
44
|
+
"exports": {
|
|
45
|
+
".": {
|
|
46
|
+
"types": "./dist/index.d.ts",
|
|
47
|
+
"bun": "./dist/index.js",
|
|
48
|
+
"node": "./dist/node/index.js",
|
|
49
|
+
"browser": "./dist/browser/index.js",
|
|
50
|
+
"default": "./dist/index.js"
|
|
51
|
+
},
|
|
52
|
+
"./graphql-pothos": {
|
|
53
|
+
"types": "./dist/graphql-pothos.d.ts",
|
|
54
|
+
"bun": "./dist/graphql-pothos.js",
|
|
55
|
+
"node": "./dist/node/graphql-pothos.js",
|
|
56
|
+
"browser": "./dist/browser/graphql-pothos.js",
|
|
57
|
+
"default": "./dist/graphql-pothos.js"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"publishConfig": {
|
|
61
|
+
"access": "public",
|
|
62
|
+
"registry": "https://registry.npmjs.org/",
|
|
63
|
+
"exports": {
|
|
64
|
+
".": {
|
|
65
|
+
"types": "./dist/index.d.ts",
|
|
66
|
+
"bun": "./dist/index.js",
|
|
67
|
+
"node": "./dist/node/index.js",
|
|
68
|
+
"browser": "./dist/browser/index.js",
|
|
69
|
+
"default": "./dist/index.js"
|
|
70
|
+
},
|
|
71
|
+
"./graphql-pothos": {
|
|
72
|
+
"types": "./dist/graphql-pothos.d.ts",
|
|
73
|
+
"bun": "./dist/graphql-pothos.js",
|
|
74
|
+
"node": "./dist/node/graphql-pothos.js",
|
|
75
|
+
"browser": "./dist/browser/graphql-pothos.js",
|
|
76
|
+
"default": "./dist/graphql-pothos.js"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|