@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 ADDED
@@ -0,0 +1,3 @@
1
+ # @contractspec/lib.contracts-runtime-server-graphql
2
+
3
+ Split package for GraphQL server adapters.
@@ -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
+ };
@@ -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
+ }