@bobbykim/manguito-cms-api 0.5.0 → 0.6.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/dist/{chunk-D5AO4BTL.mjs → chunk-JVP33FI2.mjs} +16 -4
- package/dist/chunk-JVP33FI2.mjs.map +1 -0
- package/dist/{chunk-ARZXLNZS.mjs → chunk-XNXM4W4K.mjs} +147 -40
- package/dist/chunk-XNXM4W4K.mjs.map +1 -0
- package/dist/graphql/index.d.mts +7 -2
- package/dist/graphql/index.d.ts +7 -2
- package/dist/graphql/index.js +145 -38
- package/dist/graphql/index.js.map +1 -1
- package/dist/graphql/index.mjs +2 -2
- package/dist/handler-XZVDLKZ2.mjs +8 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +258 -70
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +93 -31
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-ARZXLNZS.mjs.map +0 -1
- package/dist/chunk-D5AO4BTL.mjs.map +0 -1
- package/dist/handler-X7JDHJAL.mjs +0 -8
- /package/dist/{handler-X7JDHJAL.mjs.map → handler-XZVDLKZ2.mjs.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/graphql/handler.ts","../src/graphql/schema.ts","../src/graphql/scalars.ts","../src/graphql/type-mapping.ts","../src/graphql/naming.ts","../src/graphql/filters.ts","../src/graphql/resolvers.ts","../src/graphql/version-view.ts","../src/graphql/dataloaders.ts","../src/graphql/security.ts"],"sourcesContent":["import { createYoga, maskError } from 'graphql-yoga'\nimport type { Handler } from 'hono'\nimport type { SchemaRegistry, ContentRepository, VersionProjection } from '@bobbykim/manguito-cms-core'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport type { ProgrammaticResolver } from '../programmatic/resolve.js'\nimport type { FieldKeyMap } from '../field-keys.js'\nimport type { GraphQLContext } from './context.js'\nimport { buildGraphQLSchema } from './schema.js'\nimport { buildVersionView, type VersionView } from './version-view.js'\nimport { createRelationLoaders } from './dataloaders.js'\nimport { buildArmorPlugin, introspectionPlugin } from './security.js'\n\nexport type ResolvedGraphQLOptions = {\n enabled: boolean\n maxDepth: number\n maxComplexity: number\n graphiql: boolean\n introspection: boolean\n}\n\n// Yoga decides whether an error is safe to show a client with\n// `error instanceof GraphQLError`. That check is realm-sensitive: GraphQL Armor\n// is CJS and throws a GraphQLError built from graphql's CJS entry, while Yoga is\n// ESM and compares against the class from graphql's ESM entry. Same version, two\n// classes — so `instanceof` fails and a depth/complexity rejection, which is the\n// client's own query being too large, was masked as\n// `INTERNAL_SERVER_ERROR: Unexpected error.` and logged server-side as a fault.\n//\n// Identify by name instead (what envelop's own masking does), walking the\n// originalError chain the same way. A GraphQLError with no originalError was\n// constructed deliberately by a validation rule or by us and carries no internals;\n// anything else — a resolver throwing, a driver error — still goes to Yoga's\n// masking untouched, dev-mode originalError detail included.\nfunction isClientSafeGraphQLError(error: unknown): boolean {\n let current = error as { name?: string; originalError?: unknown } | null | undefined\n while (current != null && typeof current === 'object' && current.name === 'GraphQLError') {\n if (current.originalError == null) return true\n current = current.originalError as typeof current\n }\n return false\n}\n\nexport function createGraphQLHandler(\n registry: SchemaRegistry,\n repos: Record<string, ContentRepository<unknown>>,\n fieldKeyMaps: Record<string, FieldKeyMap>,\n resolver: ProgrammaticResolver,\n db: DrizzlePostgresInstance,\n options: ResolvedGraphQLOptions,\n // The view is built HERE, not in app.ts: the `.` entry must keep no static\n // import of anything under graphql/ (ADR api/0006), so app.ts passes the\n // plain projection data it already imports as types from core.\n versioning: {\n endpoint?: string\n projection?: VersionProjection\n currentProjection?: VersionProjection\n currentVersion?: string\n } = {}\n): Handler {\n let view: VersionView | undefined\n if (versioning.projection !== undefined) {\n if (versioning.currentVersion === undefined) {\n throw new Error(\n 'createGraphQLHandler: currentVersion is required when a projection is supplied — ' +\n 'deprecation reasons name it, and an empty one ships \"Removed in .\" into published SDL.'\n )\n }\n view = buildVersionView({\n registry,\n projection: versioning.projection,\n currentProjection: versioning.currentProjection,\n currentVersion: versioning.currentVersion,\n })\n }\n const schema = buildGraphQLSchema(registry, fieldKeyMaps, view)\n const { plugins } = buildArmorPlugin({\n maxDepth: options.maxDepth,\n maxComplexity: options.maxComplexity,\n })\n\n const yoga = createYoga<Record<string, never>, GraphQLContext>({\n schema,\n graphqlEndpoint: versioning.endpoint ?? '/graphql',\n graphiql: options.graphiql,\n landingPage: false,\n maskedErrors: {\n maskError: (error, message, isDev) =>\n isClientSafeGraphQLError(error) ? (error as Error) : maskError(error, message, isDev),\n },\n // buildArmorPlugin's return type is `unknown[]` (see security.ts) since Armor's\n // plugin shapes aren't part of its public typings; Yoga's `plugins` option only\n // accepts its own `Plugin` type, so the cast bridges the two.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n plugins: [...(plugins as any[]), introspectionPlugin(options.introspection)],\n context: (): GraphQLContext => ({\n db,\n registry,\n repos,\n resolver,\n loaders: createRelationLoaders(db, registry),\n programmaticMemo: new WeakMap(),\n }),\n })\n\n return (c) => yoga.fetch(c.req.raw, {})\n}\n","import {\n GraphQLSchema,\n GraphQLObjectType,\n GraphQLEnumType,\n GraphQLNonNull,\n GraphQLList,\n GraphQLID,\n GraphQLString,\n GraphQLInt,\n GraphQLBoolean,\n type GraphQLFieldConfig,\n type GraphQLOutputType,\n type GraphQLFieldConfigMap,\n} from 'graphql'\nimport type {\n SchemaRegistry,\n ParsedContentType,\n ParsedTaxonomyType,\n ParsedParagraphType,\n ParsedField,\n ParsedEnumType,\n} from '@bobbykim/manguito-cms-core'\nimport type { GraphQLContext } from './context.js'\nimport type { FieldKeyMap } from '../field-keys.js'\nimport type { VersionView, ViewField } from './version-view.js'\nimport { DateTimeScalar } from './scalars.js'\nimport { scalarOutputType } from './type-mapping.js'\nimport {\n graphqlTypeName,\n singleQueryName,\n collectionQueryName,\n toCamelCase,\n isValidGraphQLName,\n buildFieldNameMap,\n} from './naming.js'\nimport { SortOrderEnum, buildSortFieldEnum, buildFilterInputType } from './filters.js'\nimport {\n scalarFieldResolver,\n relationFieldResolver,\n programmaticFieldResolver,\n collectionResolver,\n singleBySlugResolver,\n singletonResolver,\n taxonomySingleResolver,\n} from './resolvers.js'\nimport type { MediaFieldKey } from './resolvers.js'\n\nconst PAGE_META = new GraphQLObjectType({\n name: 'PageMeta',\n fields: {\n total: { type: new GraphQLNonNull(GraphQLInt), resolve: (m) => m.total },\n page: { type: new GraphQLNonNull(GraphQLInt), resolve: (m) => m.page },\n perPage: { type: new GraphQLNonNull(GraphQLInt), resolve: (m) => m.per_page },\n totalPages: { type: new GraphQLNonNull(GraphQLInt), resolve: (m) => m.total_pages },\n hasNext: { type: new GraphQLNonNull(GraphQLBoolean), resolve: (m) => m.has_next },\n hasPrev: { type: new GraphQLNonNull(GraphQLBoolean), resolve: (m) => m.has_prev },\n },\n})\n\nconst MEDIA = new GraphQLObjectType({\n name: 'Media',\n fields: {\n id: { type: new GraphQLNonNull(GraphQLID), resolve: (m) => m.id },\n type: { type: new GraphQLNonNull(GraphQLString), resolve: (m) => m.type },\n url: { type: new GraphQLNonNull(GraphQLString), resolve: (m) => m.url },\n mimeType: { type: new GraphQLNonNull(GraphQLString), resolve: (m) => m.mime_type },\n alt: { type: GraphQLString, resolve: (m) => m.alt },\n fileSize: { type: GraphQLInt, resolve: (m) => m.file_size },\n width: { type: GraphQLInt, resolve: (m) => m.width },\n height: { type: GraphQLInt, resolve: (m) => m.height },\n duration: { type: GraphQLInt, resolve: (m) => m.duration },\n },\n})\n\nexport function buildGraphQLSchema(\n registry: SchemaRegistry,\n fieldKeyMaps: Record<string, FieldKeyMap> = {},\n view?: VersionView\n): GraphQLSchema {\n const objectTypes = new Map<string, GraphQLObjectType>() // machineName → type\n const enumTypes = new Map<string, GraphQLEnumType>() // enum machineName → type (only when valid)\n\n // Enum types (only when every value is a valid GraphQL identifier).\n for (const [name, enumType] of Object.entries(registry.enum_types) as [string, ParsedEnumType][]) {\n if (enumType.values.every(isValidGraphQLName)) {\n enumTypes.set(\n name,\n new GraphQLEnumType({\n name: graphqlTypeName(name),\n values: Object.fromEntries(enumType.values.map((v) => [v, { value: v }])),\n })\n )\n } else {\n process.stderr.write(\n `⚠ enum '${name}' has values that aren't valid GraphQL identifiers; exposing as String\\n`\n )\n }\n }\n\n // Output type for one field (scalar, enum, relation, media, programmatic).\n function outputTypeForField(field: ParsedField, required: boolean): GraphQLOutputType {\n const scalar = scalarOutputType(field.field_type)\n if (scalar) return required ? new GraphQLNonNull(scalar) : scalar\n\n if (field.field_type === 'enum') {\n const ref = field.ui_component.component === 'select' ? field.ui_component.enum_ref : undefined\n const et = ref ? enumTypes.get(ref) : undefined\n const t = et ?? GraphQLString\n return required ? new GraphQLNonNull(t) : t\n }\n\n if (field.field_type === 'image' || field.field_type === 'video' || field.field_type === 'file') {\n return MEDIA\n }\n\n if (field.field_type === 'paragraph') {\n const ref = field.ui_component.component === 'paragraph-embed' ? field.ui_component.ref : undefined\n const target = ref ? objectTypes.get(ref) : undefined\n if (ref && !target) {\n process.stderr.write(\n `⚠ paragraph field '${field.name}' targets unknown paragraph type '${ref}'; exposing as Media\\n`\n )\n }\n return new GraphQLList(new GraphQLNonNull(target ?? MEDIA))\n }\n\n if (field.field_type === 'reference') {\n const ref = field.ui_component.component === 'typeahead-select' ? field.ui_component.ref : undefined\n const target = ref ? objectTypes.get(ref) : undefined\n if (ref && !target) {\n process.stderr.write(`⚠ reference field '${field.name}' targets unknown type '${ref}'; exposing as Media\\n`)\n }\n const rel = field.ui_component.component === 'typeahead-select' ? field.ui_component.rel : undefined\n const isMany = rel === 'many-to-many' || rel === 'one-to-many'\n const t = (target ?? MEDIA) as GraphQLObjectType\n return isMany ? new GraphQLList(new GraphQLNonNull(t)) : t\n }\n\n return GraphQLString\n }\n\n /**\n * A type's fields minus its tombstones.\n *\n * A tombstone (`removed: true`) is column-backed — the parser keeps its\n * column alive for older live versions — but the version being served must\n * never expose it. REST drops it via the FieldKeyMap's drop-set, which\n * `remap` applies; GraphQL resolves per field by column through\n * `resolveFieldValue` and never touches that map, so the exclusion has to\n * happen where the schema is BUILT. A field absent from the schema is a\n * validation error, so the retained value cannot reach a response at all.\n */\n function exposedFields(type: { fields: ParsedField[] }): ParsedField[] {\n return type.fields.filter((f) => f.removed !== true)\n }\n\n /**\n * The fields one schema exposes for a type, as ViewFields. With a view, the\n * served version's own list; without one, the registry's minus tombstones.\n */\n function viewFieldsFor(machineName: string, type: { fields: ParsedField[] }): ViewField[] {\n return (\n view?.[machineName]?.fields ??\n exposedFields(type).map((f) => ({ field: f, exposedAs: f.name, required: f.required }))\n )\n }\n\n // Build the object type for a content/taxonomy/paragraph type. Fields are a\n // thunk so relations can reference types created later (circular graphs).\n function buildObjectType(\n machineName: string,\n type: ParsedContentType | ParsedTaxonomyType | ParsedParagraphType\n ): GraphQLObjectType {\n const visible = viewFieldsFor(machineName, type)\n\n // Handed to the programmatic resolvers so they can present the same record\n // shape REST does, where media fields are resolved objects (see resolvers.ts).\n //\n // BOTH names, because that record straddles two key spaces. `name` is the\n // field's REAL name — it goes straight to ctx.loaders.load(typeName, name,\n // row), which looks the field up in the registry, and is therefore the key\n // the resolved object lands on. `exposedAs` is the label this version\n // serves, which is where `ctx.get()` looks for it. A version that renames a\n // media field makes them differ, and passing only one of the two loses the\n // resolved object (see relabelMedia in resolvers.ts).\n const mediaFields: MediaFieldKey[] = visible\n .filter(\n (v) =>\n v.field.field_type === 'image' || v.field.field_type === 'video' || v.field.field_type === 'file'\n )\n .map((v) => ({ name: v.field.name, exposedAs: v.exposedAs }))\n\n return new GraphQLObjectType({\n name: graphqlTypeName(machineName),\n fields: () => {\n const fields: GraphQLFieldConfigMap<Record<string, unknown>, GraphQLContext> = {}\n // System fields.\n fields['id'] = { type: new GraphQLNonNull(GraphQLID), resolve: (p) => p['id'] }\n if (type.schema_type !== 'paragraph-type') {\n fields['published'] = { type: new GraphQLNonNull(GraphQLBoolean), resolve: (p) => p['published'] }\n }\n if (type.schema_type === 'content-type') {\n fields['slug'] = { type: new GraphQLNonNull(GraphQLString), resolve: (p) => p['slug'] }\n }\n fields['createdAt'] = { type: new GraphQLNonNull(DateTimeScalar), resolve: (p) => p['created_at'] }\n fields['updatedAt'] = { type: new GraphQLNonNull(DateTimeScalar), resolve: (p) => p['updated_at'] }\n\n for (const vf of visible) {\n const field = vf.field\n const gqlName = toCamelCase(vf.exposedAs)\n const outType = outputTypeForField(field, vf.required)\n let resolve: GraphQLFieldConfig<Record<string, unknown>, GraphQLContext>['resolve']\n if (field.field_type === 'programmatic') {\n resolve = programmaticFieldResolver(machineName, field.name, mediaFields, fieldKeyMaps[machineName])\n } else if (\n field.field_type === 'reference' ||\n field.field_type === 'paragraph' ||\n field.field_type === 'image' ||\n field.field_type === 'video' ||\n field.field_type === 'file'\n ) {\n resolve = relationFieldResolver(machineName, field.name)\n } else {\n resolve = scalarFieldResolver(field, vf.fallback)\n }\n fields[gqlName] = {\n type: outType,\n resolve,\n // Conditional spread, not `deprecationReason: vf.deprecationReason`\n // — exactOptionalPropertyTypes forbids present-and-undefined.\n ...(vf.deprecationReason !== undefined && { deprecationReason: vf.deprecationReason }),\n }\n }\n return fields\n },\n })\n }\n\n // Pass 1: create every object type (empty-safe thunks resolve later).\n // Track GraphQL type name → machine name so two schemas that collapse to the\n // same GraphQL type name (e.g. content--author / taxonomy--author both → \"Author\")\n // fail loud here instead of surfacing as a cryptic GraphQLSchema build error.\n const typeNameOwners = new Map<string, string>() // graphqlTypeName → machineName\n function registerTypeName(machineName: string): void {\n const gqlName = graphqlTypeName(machineName)\n const owner = typeNameOwners.get(gqlName)\n if (owner !== undefined && owner !== machineName) {\n throw new Error(\n `GraphQL type name collision: '${gqlName}' is produced by both '${owner}' and '${machineName}'. ` +\n `Rename one schema's machine name segment so their GraphQL type names differ.`\n )\n }\n typeNameOwners.set(gqlName, machineName)\n }\n\n for (const [name, ct] of Object.entries(registry.content_types)) {\n registerTypeName(name)\n objectTypes.set(name, buildObjectType(name, ct))\n }\n for (const [name, tt] of Object.entries(registry.taxonomy_types)) {\n registerTypeName(name)\n objectTypes.set(name, buildObjectType(name, tt))\n }\n for (const [name, pt] of Object.entries(registry.paragraph_types)) {\n registerTypeName(name)\n objectTypes.set(name, buildObjectType(name, pt))\n }\n\n // Pass 2: build the root Query type.\n const queryFields: GraphQLFieldConfigMap<unknown, GraphQLContext> = {}\n\n for (const [name, ct] of Object.entries(registry.content_types) as [string, ParsedContentType][]) {\n const objType = objectTypes.get(name)!\n const visible = viewFieldsFor(name, ct)\n // The version's own labels: translateFilters maps a GraphQL name back to a\n // label, and columnFor then takes that label to the storage column.\n const nameMap = buildFieldNameMap(visible.map((v) => v.exposedAs))\n\n if (ct.only_one) {\n queryFields[singleQueryName(name)] = { type: objType, resolve: singletonResolver(name) }\n continue\n }\n\n const listType = new GraphQLObjectType({\n name: `${graphqlTypeName(name)}List`,\n fields: {\n data: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(objType))), resolve: (r) => r.data },\n meta: { type: new GraphQLNonNull(PAGE_META), resolve: (r) => r.meta },\n },\n })\n const filterType = buildFilterInputType(ct, visible)\n\n queryFields[collectionQueryName(name)] = {\n type: new GraphQLNonNull(listType),\n args: {\n page: { type: GraphQLInt },\n perPage: { type: GraphQLInt },\n sortBy: { type: buildSortFieldEnum(graphqlTypeName(name), fieldKeyMaps[name]) },\n sortOrder: { type: SortOrderEnum },\n ...(filterType ? { filter: { type: filterType } } : {}),\n },\n resolve: collectionResolver(name, nameMap, fieldKeyMaps[name]),\n }\n queryFields[singleQueryName(name)] = {\n type: objType,\n args: { slug: { type: new GraphQLNonNull(GraphQLString) } },\n resolve: singleBySlugResolver(name),\n }\n }\n\n for (const [name, tt] of Object.entries(registry.taxonomy_types) as [string, ParsedTaxonomyType][]) {\n const objType = objectTypes.get(name)!\n const listType = new GraphQLObjectType({\n name: `${graphqlTypeName(name)}List`,\n fields: {\n data: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(objType))), resolve: (r) => r.data },\n meta: { type: new GraphQLNonNull(PAGE_META), resolve: (r) => r.meta },\n },\n })\n queryFields[collectionQueryName(name)] = {\n type: new GraphQLNonNull(listType),\n args: { page: { type: GraphQLInt }, perPage: { type: GraphQLInt } },\n resolve: collectionResolver(\n name,\n buildFieldNameMap(viewFieldsFor(name, tt).map((v) => v.exposedAs)),\n fieldKeyMaps[name]\n ),\n }\n queryFields[singleQueryName(name)] = {\n type: objType,\n args: { id: { type: new GraphQLNonNull(GraphQLID) } },\n resolve: taxonomySingleResolver(name),\n }\n }\n\n return new GraphQLSchema({\n query: new GraphQLObjectType({ name: 'Query', fields: queryFields }),\n })\n}\n","import { GraphQLScalarType, Kind, valueFromASTUntyped } from 'graphql'\n\nexport const DateTimeScalar = new GraphQLScalarType({\n name: 'DateTime',\n description: 'ISO-8601 date-time string',\n serialize(value) {\n if (value instanceof Date) return value.toISOString()\n if (typeof value === 'string') return value\n if (typeof value === 'number') return new Date(value).toISOString()\n throw new TypeError(`DateTime cannot serialize value: ${String(value)}`)\n },\n parseValue(value) {\n if (typeof value !== 'string') throw new TypeError('DateTime must be a string')\n return value\n },\n parseLiteral(ast) {\n if (ast.kind !== Kind.STRING) throw new TypeError('DateTime must be a string')\n return ast.value\n },\n})\n\nexport const JSONScalar = new GraphQLScalarType({\n name: 'JSON',\n description: 'Arbitrary JSON value',\n serialize: (value) => value,\n parseValue: (value) => value,\n parseLiteral: (ast, variables) => valueFromASTUntyped(ast, variables ?? undefined),\n})\n","import {\n GraphQLString,\n GraphQLInt,\n GraphQLFloat,\n GraphQLBoolean,\n type GraphQLScalarType,\n} from 'graphql'\nimport type { FieldType } from '@bobbykim/manguito-cms-core'\nimport { DateTimeScalar, JSONScalar } from './scalars.js'\n\n// Scalar output type for a field. Relation/enum fields return null — schema.ts\n// resolves those against its type caches.\nexport function scalarOutputType(fieldType: FieldType): GraphQLScalarType | null {\n switch (fieldType) {\n case 'text/plain':\n case 'text/rich':\n return GraphQLString\n case 'integer':\n return GraphQLInt\n case 'float':\n return GraphQLFloat\n case 'boolean':\n return GraphQLBoolean\n case 'date':\n return DateTimeScalar\n case 'programmatic':\n return JSONScalar\n default:\n return null\n }\n}\n","// \"content--blog_post\" → \"blog_post\"; \"category\" → \"category\"\nexport function schemaSegment(machineName: string): string {\n const idx = machineName.indexOf('--')\n return idx === -1 ? machineName : machineName.slice(idx + 2)\n}\n\nfunction words(input: string): string[] {\n return input.split(/[_\\s-]+/).filter(Boolean)\n}\n\nexport function toPascalCase(input: string): string {\n return words(input)\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join('')\n}\n\nexport function toCamelCase(input: string): string {\n const pascal = toPascalCase(input)\n return pascal.charAt(0).toLowerCase() + pascal.slice(1)\n}\n\n// Deterministic English pluralization for the common cases; irregular plurals\n// (person→people) are a known limitation — an optional per-type override can be\n// added later if one is wrong.\nexport function pluralize(word: string): string {\n if (/[^aeiou]y$/.test(word)) return word.slice(0, -1) + 'ies'\n if (/(s|x|z|ch|sh)$/.test(word)) return word + 'es'\n return word + 's'\n}\n\nconst GRAPHQL_NAME = /^[_A-Za-z][_0-9A-Za-z]*$/\nexport function isValidGraphQLName(name: string): boolean {\n return GRAPHQL_NAME.test(name)\n}\n\nexport function graphqlTypeName(machineName: string): string {\n return toPascalCase(schemaSegment(machineName))\n}\n\nexport function singleQueryName(machineName: string): string {\n return toCamelCase(schemaSegment(machineName))\n}\n\nexport function collectionQueryName(machineName: string): string {\n return pluralize(singleQueryName(machineName))\n}\n\nexport function buildFieldNameMap(schemaNames: string[]): {\n toGraphql(schemaName: string): string\n toSchema(graphqlName: string): string\n} {\n const toG = new Map<string, string>()\n const toS = new Map<string, string>()\n for (const name of schemaNames) {\n const g = toCamelCase(name)\n toG.set(name, g)\n toS.set(g, name)\n }\n return {\n toGraphql: (schemaName) => toG.get(schemaName) ?? toCamelCase(schemaName),\n toSchema: (graphqlName) => toS.get(graphqlName) ?? graphqlName,\n }\n}\n","import {\n GraphQLEnumType,\n GraphQLInputObjectType,\n GraphQLList,\n GraphQLNonNull,\n GraphQLString,\n GraphQLBoolean,\n GraphQLInt,\n GraphQLFloat,\n type GraphQLInputType,\n} from 'graphql'\nimport type {\n ParsedContentType,\n ParsedTaxonomyType,\n ParsedField,\n FilterValue,\n FilterOperator,\n FieldType,\n} from '@bobbykim/manguito-cms-core'\nimport { graphqlTypeName, buildFieldNameMap } from './naming.js'\nimport { scalarOutputType } from './type-mapping.js'\nimport { DateTimeScalar } from './scalars.js'\nimport type { FieldKeyMap } from '../field-keys.js'\nimport type { ViewField } from './version-view.js'\n\nexport const SortOrderEnum = new GraphQLEnumType({\n name: 'SortOrder',\n values: { ASC: { value: 'asc' }, DESC: { value: 'desc' } },\n})\n\n// Only these fields are sortable (mirrors the REST SORTABLE_FIELDS).\n//\n// `value` is the enum's internal value, and it is NOT uniformly a storage\n// column: `created_at` / `updated_at` are the snake_case forms of real system\n// columns, but `title` is a schema field's LABEL — the same value REST accepts\n// in `?sort_by=`. `collectionResolver` maps it to a column through the type's\n// FieldKeyMap before the repository builds its ORDER BY.\nconst SORTABLE: Array<{ gql: string; value: string }> = [\n { gql: 'title', value: 'title' },\n { gql: 'createdAt', value: 'created_at' },\n { gql: 'updatedAt', value: 'updated_at' },\n]\n\n/**\n * `fieldKeys` is THIS version's map. `title` in SORTABLE is a schema field's\n * LABEL, not a column, so a version that has no field with that label cannot\n * resolve it — and offering it anyway hands the repository a label that maps\n * to nothing, which surfaces as a 500 from SQL rather than a rejected query.\n * Mirrors `sortableColumnsFor` in versions.ts.\n *\n * `createdAt`/`updatedAt` are real system columns on every content type, so\n * they are always offered and the enum can never end up empty — which\n * GraphQL rejects.\n *\n * With no map, every value is kept: callers that have not threaded a\n * FieldKeyMap through behave exactly as before.\n */\nexport function buildSortFieldEnum(typeName: string, fieldKeys?: FieldKeyMap): GraphQLEnumType {\n const resolvable = SORTABLE.filter(\n (s) =>\n s.gql === 'createdAt' ||\n s.gql === 'updatedAt' ||\n fieldKeys === undefined ||\n fieldKeys.columnFor(s.value) !== undefined\n )\n return new GraphQLEnumType({\n name: `${typeName}SortField`,\n values: Object.fromEntries(resolvable.map((s) => [s.gql, { value: s.value }])),\n })\n}\n\n// A comparable filter input (eq/in/gt/gte/lt/lte) for a given scalar input type.\nfunction comparableFilterInput(name: string, scalar: GraphQLInputType): GraphQLInputObjectType {\n return new GraphQLInputObjectType({\n name,\n fields: {\n eq: { type: scalar },\n in: { type: new GraphQLList(new GraphQLNonNull(scalar)) },\n gt: { type: scalar },\n gte: { type: scalar },\n lt: { type: scalar },\n lte: { type: scalar },\n },\n })\n}\n\nfunction equalityFilterInput(name: string, scalar: GraphQLInputType): GraphQLInputObjectType {\n return new GraphQLInputObjectType({\n name,\n fields: {\n eq: { type: scalar },\n in: { type: new GraphQLList(new GraphQLNonNull(scalar)) },\n },\n })\n}\n\n// Shared per-scalar filter inputs (built once).\nconst StringFilter = equalityFilterInput('StringFilter', GraphQLString)\nconst BooleanFilter = new GraphQLInputObjectType({\n name: 'BooleanFilter',\n fields: { eq: { type: GraphQLBoolean } },\n})\nconst DateTimeFilter = comparableFilterInput('DateTimeFilter', DateTimeScalar)\nconst IDFilter = equalityFilterInput('IDFilter', GraphQLString)\nconst IntFilter = comparableFilterInput('IntFilter', GraphQLInt)\nconst FloatFilter = comparableFilterInput('FloatFilter', GraphQLFloat)\n\n// Choose a filter input for a field based on its scalar output type.\nfunction filterInputForField(fieldType: FieldType): GraphQLInputObjectType | null {\n const scalar = scalarOutputType(fieldType)\n if (fieldType === 'boolean') return BooleanFilter\n if (fieldType === 'date') return DateTimeFilter\n if (fieldType === 'integer') return IntFilter\n if (fieldType === 'float') return FloatFilter\n if (fieldType === 'reference') return IDFilter\n if (scalar === null) return null // only 'enum' reaches here — not filterable via a scalar input\n return StringFilter\n}\n\n// Build the <Type>Filter input. Programmatic fields are excluded (no column).\nexport function buildFilterInputType(\n type: ParsedContentType | ParsedTaxonomyType,\n viewFields?: ViewField[]\n): GraphQLInputObjectType | null {\n const fields: Record<string, { type: GraphQLInputObjectType }> = {}\n\n // System fields that are filterable columns.\n fields['id'] = { type: IDFilter }\n fields['published'] = { type: BooleanFilter }\n fields['createdAt'] = { type: DateTimeFilter }\n fields['updatedAt'] = { type: DateTimeFilter }\n if (type.schema_type === 'content-type') fields['slug'] = { type: StringFilter }\n\n // A view states the label THIS version exposes each field under; without\n // one, fall back to the field's own name minus tombstones (Task 2's rule).\n const entries: Array<{ field: ParsedField; exposedAs: string }> =\n viewFields !== undefined\n ? viewFields.map((v) => ({ field: v.field, exposedAs: v.exposedAs }))\n : type.fields.filter((f) => f.removed !== true).map((f) => ({ field: f, exposedAs: f.name }))\n\n for (const { field: f, exposedAs } of entries) {\n if (f.field_type === 'programmatic' || f.field_type === 'paragraph') continue\n if (f.field_type === 'image' || f.field_type === 'video' || f.field_type === 'file') continue\n const input = filterInputForField(f.field_type)\n if (input) fields[buildFieldNameMap([exposedAs]).toGraphql(exposedAs)] = { type: input }\n }\n\n if (Object.keys(fields).length === 0) return null\n return new GraphQLInputObjectType({ name: `${graphqlTypeName(type.name)}Filter`, fields })\n}\n\n// Convert a GraphQL filter argument into the repository's filters map, keyed by\n// snake_case column. eq → scalar, in → array (OR/IN), gt/gte/lt/lte → operator.\n//\n// `nameMap.toSchema` only ever recovers the schema field's LABEL (`field.name`)\n// from the GraphQL name — that mapping is unaware of storage columns. `columnFor`\n// (typically `FieldKeyMap.columnFor`) takes that label the rest of the way to the\n// storage column the repository actually filters on. It's optional so callers\n// that haven't threaded a FieldKeyMap through yet keep working unchanged: with no\n// mapper, or when it returns nothing for a given label, the label itself is used —\n// correct as long as it equals its column.\nexport function translateFilters(\n input: Record<string, unknown> | undefined,\n nameMap: { toSchema(g: string): string },\n columnFor?: (label: string) => string | undefined\n): Record<string, FilterValue> {\n const out: Record<string, FilterValue> = {}\n if (!input) return out\n\n for (const [gqlField, raw] of Object.entries(input)) {\n if (raw === null || typeof raw !== 'object') continue\n const label = nameMap.toSchema(gqlField)\n const column = columnFor?.(label) ?? label\n const spec = raw as Record<string, unknown>\n\n // eq/in take priority over range operators (gt/gte/lt/lte) when a client sends\n // both in one field filter — the range operators are silently dropped.\n if (spec['eq'] !== undefined) {\n out[column] = spec['eq'] as FilterValue\n } else if (Array.isArray(spec['in'])) {\n out[column] = spec['in'] as FilterValue\n } else {\n const op: FilterOperator = {}\n for (const k of ['gt', 'gte', 'lt', 'lte'] as const) {\n if (spec[k] !== undefined) op[k] = spec[k] as number | string\n }\n if (Object.keys(op).length > 0) out[column] = op\n }\n }\n return out\n}\n","import { GraphQLError } from 'graphql'\nimport type { ParsedField } from '@bobbykim/manguito-cms-core'\nimport type { GraphQLContext } from './context.js'\nimport { translateFilters } from './filters.js'\nimport { isColumnBacked, type FieldKeyMap } from '../field-keys.js'\n\ntype Row = Record<string, unknown>\n\nfunction codeError(code: string, message: string): GraphQLError {\n return new GraphQLError(message, { extensions: { code } })\n}\n\nfunction isPublished(item: Row | null): boolean {\n return !!item && item['published'] === true\n}\n\n/**\n * A GraphQL field's value on a content row. The GraphQL field NAME comes from\n * the field's label (see naming.ts), but the value lives under its storage\n * column, which differs once a field has been renamed.\n *\n * `fallback` is the value a version declares for a column that stopped being\n * written: rows created since the removal hold null there. Substituted on\n * null/undefined ONLY — never on '' or 0 — matching projectRow's rule, so the\n * REST and GraphQL surfaces cannot disagree about the same version.\n */\nexport function resolveFieldValue(\n field: ParsedField,\n row: Record<string, unknown>,\n fallback?: unknown\n): unknown {\n const key = isColumnBacked(field) ? field.db_column.column_name : field.name\n const value = row[key]\n if ((value === null || value === undefined) && fallback !== undefined) return fallback\n return value ?? null\n}\n\nexport function scalarFieldResolver(field: ParsedField, fallback?: unknown) {\n return (parent: Row): unknown => resolveFieldValue(field, parent, fallback)\n}\n\nexport function relationFieldResolver(typeName: string, schemaFieldName: string) {\n return (parent: Row, _args: unknown, ctx: GraphQLContext): Promise<unknown> =>\n ctx.loaders.load(typeName, schemaFieldName, parent)\n}\n\n/**\n * A media field, in both key spaces the programmatic record straddles.\n *\n * `name` is the field's REGISTRY name — the only key `ctx.loaders.load` can\n * look a field up by, and therefore the key the resolved object lands on.\n * `exposedAs` is the label the version being served uses, which is where\n * `ctx.get()` will look for it. They differ exactly when this version renames\n * the field, and that is the whole reason both are needed.\n */\nexport type MediaFieldKey = { name: string; exposedAs: string }\n\nexport function programmaticFieldResolver(\n typeName: string,\n schemaFieldName: string,\n mediaFields: readonly MediaFieldKey[] = [],\n fieldKeys?: FieldKeyMap\n) {\n return async (parent: Row, _args: unknown, ctx: GraphQLContext): Promise<unknown> => {\n let p = ctx.programmaticMemo.get(parent)\n if (!p) {\n p = resolveProgrammaticRow(typeName, parent, ctx, mediaFields, fieldKeys)\n ctx.programmaticMemo.set(parent, p)\n }\n return (await p)[schemaFieldName]\n }\n}\n\n// A programmatic resolver reads its record through `ctx.get()`, so that record\n// must look the same on both public surfaces. REST resolves media fields to full\n// objects before running resolvers; GraphQL reads through repositories that\n// resolve nothing, so without this `ctx.get('hero').url` would work over REST and\n// silently fall back to null over GraphQL.\n//\n// Media is resolved into a COPY, never the row itself: the dataloaders write\n// their results back into the row they are handed, and the real row must keep the\n// raw id so a query that also selects the media field still resolves. Handing an\n// already-resolved object back to the loader is exactly what broke media here in\n// the first place. Copies still batch — one media query per request, not per row.\n//\n// `ctx.get(fieldName)` takes the schema field name (the LABEL), so the record is\n// projected to labels before the resolver runs — the same order REST uses (see\n// \"Response projection order\" in routes/content.ts). GraphQL has no route-level\n// projection to double up with: every other field resolves per field by column\n// (resolveFieldValue), so this copy is the only mapped row, and it is mapped\n// strictly after the media loaders have run.\n//\n// The loaders write onto a field's REGISTRY name, but `toLabels` speaks the\n// served version's labels, and a version that renames a media field agrees with\n// neither: createFieldKeyMapFromProjection un-drops only a projected COLUMN or\n// LABEL, so the registry name of a renamed field stays in the drop-set and\n// `remap` deletes the resolved object outright (and the raw FK column is\n// already gone — resolveRelationField deletes it whenever the field's name\n// differs from its column). Re-attaching each resolved value under this\n// version's own label AFTER the remap is what closes that gap. Done by explicit\n// assignment rather than by letting `remap` carry the key, because the raw\n// column and the resolved object can map onto the same label and key order must\n// not decide which one wins: the resolved object always does.\nasync function resolveProgrammaticRow(\n typeName: string,\n parent: Row,\n ctx: GraphQLContext,\n mediaFields: readonly MediaFieldKey[],\n fieldKeys?: FieldKeyMap\n): Promise<Record<string, unknown>> {\n const toLabels = (row: Row): Row => (fieldKeys ? fieldKeys.toLabels(row) : row)\n\n if (mediaFields.length === 0) return ctx.resolver.resolveItem(typeName, toLabels(parent))\n\n const enriched: Row = { ...parent }\n await Promise.all(mediaFields.map((m) => ctx.loaders.load(typeName, m.name, enriched)))\n return ctx.resolver.resolveItem(typeName, relabelMedia(toLabels(enriched), enriched, mediaFields))\n}\n\n/**\n * `record` (already label-keyed) with each resolved media value restored under\n * the label this version exposes it as. Writes only into `record` — either the\n * fresh object `remap` just returned, or, with no FieldKeyMap, the `enriched`\n * copy itself. Never the caller's row, which must keep its raw FK ids.\n *\n * A field the loaders never wrote is skipped rather than written as undefined —\n * `resolveRelationField` returns early when no row needs FK resolution, and a\n * present-but-undefined key is not the same record shape REST produces.\n */\nfunction relabelMedia(record: Row, enriched: Row, mediaFields: readonly MediaFieldKey[]): Row {\n for (const { name, exposedAs } of mediaFields) {\n if (!(name in enriched)) continue\n record[exposedAs] = enriched[name]\n }\n return record\n}\n\ntype CollectionArgs = {\n page?: number\n perPage?: number\n /**\n * The sort enum's internal value (filters.ts `SORTABLE`), which mixes two key\n * spaces: `created_at` / `updated_at` are system COLUMNS, but `title` is a\n * schema field's LABEL. It must be mapped before it reaches the repository.\n */\n sortBy?: string\n sortOrder?: 'asc' | 'desc'\n filter?: Record<string, unknown>\n}\n\nexport function collectionResolver(\n typeName: string,\n nameMap: { toSchema(g: string): string },\n fieldKeys?: FieldKeyMap\n) {\n return async (_root: unknown, args: CollectionArgs, ctx: GraphQLContext) => {\n const page = args.page ?? 1\n const perPage = args.perPage ?? 10\n if (!Number.isInteger(page) || page < 1) {\n throw codeError('INVALID_PAGINATION', 'page must be ≥ 1')\n }\n if (!Number.isInteger(perPage) || perPage < 1 || perPage > 100) {\n throw codeError('INVALID_PAGINATION', 'perPage must be between 1 and 100')\n }\n const repo = ctx.repos[typeName]!\n // `ORDER BY` takes a storage column, never a label. `columnFor` resolves a\n // schema field's label to its column; a system column (`created_at`,\n // `updated_at`) is not a label at all, so it has no entry and falls\n // through unchanged. Identity today, since every label equals its column.\n const sortBy = args.sortBy ?? 'created_at'\n const sortColumn = fieldKeys?.columnFor(sortBy) ?? sortBy\n const result = await repo.findMany({\n published_only: true,\n page,\n per_page: perPage,\n sort_by: sortColumn as 'title' | 'created_at' | 'updated_at',\n sort_order: args.sortOrder ?? 'asc',\n filters: translateFilters(args.filter, nameMap, fieldKeys?.columnFor),\n })\n return { data: result.data as Row[], meta: result.meta }\n }\n}\n\nexport function singleBySlugResolver(typeName: string) {\n return async (_root: unknown, args: { slug: string }, ctx: GraphQLContext): Promise<Row | null> => {\n const repo = ctx.repos[typeName]!\n const item = (await repo.findBySlug(args.slug)) as Row | null\n return isPublished(item) ? item : null\n }\n}\n\nexport function singletonResolver(typeName: string) {\n return async (_root: unknown, _args: unknown, ctx: GraphQLContext): Promise<Row | null> => {\n const repo = ctx.repos[typeName]!\n const result = await repo.findMany({ published_only: true, page: 1, per_page: 1 })\n return (result.data[0] as Row | undefined) ?? null\n }\n}\n\nexport function taxonomySingleResolver(typeName: string) {\n return async (_root: unknown, args: { id: string }, ctx: GraphQLContext): Promise<Row | null> => {\n const repo = ctx.repos[typeName]!\n const item = (await repo.findOne(args.id)) as Row | null\n return isPublished(item) ? item : null\n }\n}\n","import type { ParsedField, SchemaRegistry, VersionProjection } from '@bobbykim/manguito-cms-core'\nimport { isColumnBacked } from '../field-keys.js'\n\n// ─── One live version's view of the schema ────────────────────────────────────\n//\n// A VersionProjection says WHICH columns a version exposes, under WHICH labels,\n// and whether each is required. It deliberately says nothing about a field's\n// type: `field_type` and `ui_component` are recovered from CURRENT's field for\n// the same column instead.\n//\n// That join is sound, not convenient. `VERSION_COLUMN_MISSING` guarantees every\n// column a live version exposes still exists in current, and\n// `FIELD_TYPE_CHANGED_WHILE_LIVE` guarantees its type has not moved under it —\n// so current's field is a faithful source for everything except the label and\n// the requiredness, which the projection carries precisely because they DO\n// move. See the 2e design, \"Deriving a version's types\".\n\nexport type ViewField = {\n /** Current's field for this column — the source of `field_type` and `ui_component`. */\n field: ParsedField\n /** The name THIS version exposes the column under. */\n exposedAs: string\n /** THIS version's requiredness. Never current's — see VersionProjection. */\n required: boolean\n /**\n * The value to serve when the column holds null, for a column that stopped\n * being written. REST substitutes this in `projectRow`; GraphQL has no\n * route-level projection, so its scalar resolver has to do it or the two\n * surfaces disagree about the same version's contract.\n */\n fallback?: unknown\n /** Present only when this version's contract differs from current's. */\n deprecationReason?: string\n}\n\n/** Keyed by machine name, for content, taxonomy AND paragraph types. */\nexport type VersionView = Record<string, { fields: ViewField[] }>\n\n/** A type's column-backed fields keyed by column. Tombstones included — they hold a real column. */\nfunction byColumn(fields: ParsedField[]): Map<string, ParsedField> {\n const out = new Map<string, ParsedField>()\n for (const f of fields) {\n if (!isColumnBacked(f)) continue\n out.set(f.db_column.column_name, f)\n }\n return out\n}\n\n/**\n * Why this version's contract differs from current's, or undefined when it\n * does not. Keyed by COLUMN, because the label is exactly what may have moved.\n *\n * The reason names CURRENT, not the intermediate version where the change\n * happened: pinpointing that would mean walking every snapshot in between, and\n * \"what it is called now\" is the actionable fact for someone deciding whether\n * to upgrade.\n */\nfunction deprecationFor(\n field: { column_name: string; exposed_as: string },\n currentLabels: Map<string, string>,\n currentVersion: string\n): string | undefined {\n const currentLabel = currentLabels.get(field.column_name)\n if (currentLabel === undefined) {\n return `Removed in ${currentVersion}; column retained while this version is live.`\n }\n if (currentLabel !== field.exposed_as) {\n return `Renamed to '${currentLabel}' in ${currentVersion}.`\n }\n return undefined\n}\n\n/**\n * `projection` undefined means this version has no projection for a type — or\n * none at all. Those types fall back to the registry's own fields minus\n * tombstones, which is the inherited 2d boundary: paragraph types are never\n * projected, so nested paragraph content follows current's shape on every\n * version, by design.\n */\nexport function buildVersionView(input: {\n registry: SchemaRegistry\n projection: VersionProjection | undefined\n currentProjection: VersionProjection | undefined\n currentVersion: string\n}): VersionView {\n const { registry, projection, currentProjection, currentVersion } = input\n const out: VersionView = {}\n\n const sources: Array<Record<string, { fields: ParsedField[] }>> = [\n registry.content_types,\n registry.taxonomy_types,\n registry.paragraph_types,\n ]\n\n for (const source of sources) {\n for (const [typeName, type] of Object.entries(source)) {\n const projectionType = projection?.types[typeName]\n\n // No projection for this type: follow current, minus tombstones.\n if (projectionType === undefined) {\n out[typeName] = {\n fields: type.fields\n .filter((f) => f.removed !== true)\n .map((f) => ({ field: f, exposedAs: f.name, required: f.required })),\n }\n continue\n }\n\n const columns = byColumn(type.fields)\n const currentLabels = new Map<string, string>()\n for (const f of currentProjection?.types[typeName]?.fields ?? []) {\n currentLabels.set(f.column_name, f.exposed_as)\n }\n\n const fields: ViewField[] = []\n\n for (const pf of projectionType.fields) {\n const field = columns.get(pf.column_name)\n // Unreachable on a model that loaded — VERSION_COLUMN_MISSING rejects\n // it. Skipped rather than thrown so a hand-built baked model cannot\n // take the whole schema down over one field.\n if (field === undefined) continue\n const reason = deprecationFor(pf, currentLabels, currentVersion)\n fields.push({\n field,\n exposedAs: pf.exposed_as,\n required: pf.required,\n ...(pf.fallback !== undefined && { fallback: pf.fallback }),\n ...(reason !== undefined && { deprecationReason: reason }),\n })\n }\n\n // Fields with no column of their own — paragraph, many-to-many,\n // programmatic — never appear in a projection, so they must be appended\n // from the registry or an older version's schema would silently lose\n // every relation and computed field. They follow current on every\n // version (the 2d boundary), hence no deprecation reason.\n for (const f of type.fields) {\n if (isColumnBacked(f) || f.removed === true) continue\n fields.push({ field: f, exposedAs: f.name, required: f.required })\n }\n\n out[typeName] = { fields }\n }\n }\n\n return out\n}\n","import DataLoader from 'dataloader'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport type {\n SchemaRegistry,\n ParsedContentType,\n ParsedTaxonomyType,\n ParsedParagraphType,\n} from '@bobbykim/manguito-cms-core'\nimport { buildRelationsMap, resolveRelationField, type RelationDef } from '../relations.js'\n\nexport interface RelationLoaders {\n load(typeName: string, fieldName: string, parent: Record<string, unknown>): Promise<unknown>\n}\n\ntype ParentRow = Record<string, unknown>\n\n// One RelationLoaders instance per request. Loaders batch sibling parents per\n// (type, field); the shared cache dedupes target lookups across all loaders in\n// the request, mirroring the repository's resolveRows cache.\nexport function createRelationLoaders(\n db: DrizzlePostgresInstance,\n registry: SchemaRegistry\n): RelationLoaders {\n const relMaps = new Map<string, Record<string, RelationDef>>()\n const loaders = new Map<string, DataLoader<ParentRow, unknown>>()\n const cache = new Map<string, unknown>()\n\n function relationsFor(typeName: string): Record<string, RelationDef> {\n let m = relMaps.get(typeName)\n if (!m) {\n const type = (registry.content_types[typeName] ??\n registry.taxonomy_types[typeName] ??\n registry.paragraph_types[typeName]) as\n | ParsedContentType\n | ParsedTaxonomyType\n | ParsedParagraphType\n | undefined\n m = type ? buildRelationsMap(type.fields, registry) : {}\n relMaps.set(typeName, m)\n }\n return m\n }\n\n function loaderFor(typeName: string, fieldName: string, rel: RelationDef): DataLoader<ParentRow, unknown> {\n const key = `${typeName}:${fieldName}`\n let dl = loaders.get(key)\n if (!dl) {\n dl = new DataLoader<ParentRow, unknown>(\n async (parents) => {\n const rows = parents as ParentRow[]\n // GraphQL is public-only (see app.ts) — always filter relation targets\n // to published rows, mirroring the REST public repos' publishedRelations.\n await resolveRelationField(db, rows, fieldName, rel, cache, true)\n return rows.map((r) => r[fieldName])\n },\n // Batch within a tick; do not memoize by parent identity (rows are mutated\n // and may recur across nesting levels).\n { cache: false }\n )\n loaders.set(key, dl)\n }\n return dl\n }\n\n return {\n async load(typeName, fieldName, parent) {\n const rel = relationsFor(typeName)[fieldName]\n if (!rel) return null\n return loaderFor(typeName, fieldName, rel).load(parent)\n },\n }\n}\n","import { EnvelopArmor } from '@escape.tech/graphql-armor'\nimport { NoSchemaIntrospectionCustomRule } from 'graphql'\n\n// Depth + cost + alias + directive limits via GraphQL Armor. Returns { plugins } to\n// spread into Yoga's `plugins` array.\n//\n// Note: the installed `@escape.tech/graphql-armor` (3.2.0) exports `EnvelopArmor`, a\n// class whose `.protect()` returns `{ plugins }` — the standalone `EnvelopArmorPlugin`\n// export is a function that returns a single plugin, not a class with `.protect()`.\nexport function buildArmorPlugin(options: { maxDepth: number; maxComplexity: number }): {\n plugins: unknown[]\n} {\n const armor = new EnvelopArmor({\n maxDepth: { n: options.maxDepth },\n costLimit: { maxCost: options.maxComplexity },\n maxAliases: { n: 15 },\n maxDirectives: { n: 50 },\n blockFieldSuggestion: { enabled: true },\n // maxTokens is intentionally left at Armor's secure default (n=1000) to bound\n // GraphQL document token count at parse time, a layer the depth/cost/alias/directive\n // limits do not cover.\n })\n return armor.protect()\n}\n\n// Disables introspection (and the __schema/__type meta-fields) in production by\n// adding graphql's built-in validation rule when `enabled` is false.\nexport function introspectionPlugin(enabled: boolean): {\n onValidate(payload: { addValidationRule: (rule: unknown) => void }): void\n} {\n return {\n onValidate({ addValidationRule }) {\n if (!enabled) addValidationRule(NoSchemaIntrospectionCustomRule)\n },\n }\n}\n"],"mappings":"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","names":["GraphQLEnumType","GraphQLNonNull","GraphQLList","GraphQLString","GraphQLInt","GraphQLBoolean","GraphQLString","GraphQLBoolean","GraphQLInt","GraphQLFloat","GraphQLString","GraphQLBoolean","GraphQLInt","GraphQLFloat","GraphQLNonNull","GraphQLInt","GraphQLBoolean","GraphQLString","GraphQLEnumType","GraphQLList"]}
|
package/dist/graphql/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Handler } from 'hono';
|
|
2
|
-
import { SchemaRegistry, ContentRepository } from '@bobbykim/manguito-cms-core';
|
|
2
|
+
import { SchemaRegistry, ContentRepository, VersionProjection } from '@bobbykim/manguito-cms-core';
|
|
3
3
|
import { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db';
|
|
4
4
|
import { P as ProgrammaticResolver } from '../resolve-Dtsk27aI.mjs';
|
|
5
5
|
|
|
@@ -23,6 +23,11 @@ type ResolvedGraphQLOptions = {
|
|
|
23
23
|
graphiql: boolean;
|
|
24
24
|
introspection: boolean;
|
|
25
25
|
};
|
|
26
|
-
declare function createGraphQLHandler(registry: SchemaRegistry, repos: Record<string, ContentRepository<unknown>>, fieldKeyMaps: Record<string, FieldKeyMap>, resolver: ProgrammaticResolver, db: DrizzlePostgresInstance, options: ResolvedGraphQLOptions
|
|
26
|
+
declare function createGraphQLHandler(registry: SchemaRegistry, repos: Record<string, ContentRepository<unknown>>, fieldKeyMaps: Record<string, FieldKeyMap>, resolver: ProgrammaticResolver, db: DrizzlePostgresInstance, options: ResolvedGraphQLOptions, versioning?: {
|
|
27
|
+
endpoint?: string;
|
|
28
|
+
projection?: VersionProjection;
|
|
29
|
+
currentProjection?: VersionProjection;
|
|
30
|
+
currentVersion?: string;
|
|
31
|
+
}): Handler;
|
|
27
32
|
|
|
28
33
|
export { type ResolvedGraphQLOptions, createGraphQLHandler };
|
package/dist/graphql/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Handler } from 'hono';
|
|
2
|
-
import { SchemaRegistry, ContentRepository } from '@bobbykim/manguito-cms-core';
|
|
2
|
+
import { SchemaRegistry, ContentRepository, VersionProjection } from '@bobbykim/manguito-cms-core';
|
|
3
3
|
import { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db';
|
|
4
4
|
import { P as ProgrammaticResolver } from '../resolve-Dtsk27aI.js';
|
|
5
5
|
|
|
@@ -23,6 +23,11 @@ type ResolvedGraphQLOptions = {
|
|
|
23
23
|
graphiql: boolean;
|
|
24
24
|
introspection: boolean;
|
|
25
25
|
};
|
|
26
|
-
declare function createGraphQLHandler(registry: SchemaRegistry, repos: Record<string, ContentRepository<unknown>>, fieldKeyMaps: Record<string, FieldKeyMap>, resolver: ProgrammaticResolver, db: DrizzlePostgresInstance, options: ResolvedGraphQLOptions
|
|
26
|
+
declare function createGraphQLHandler(registry: SchemaRegistry, repos: Record<string, ContentRepository<unknown>>, fieldKeyMaps: Record<string, FieldKeyMap>, resolver: ProgrammaticResolver, db: DrizzlePostgresInstance, options: ResolvedGraphQLOptions, versioning?: {
|
|
27
|
+
endpoint?: string;
|
|
28
|
+
projection?: VersionProjection;
|
|
29
|
+
currentProjection?: VersionProjection;
|
|
30
|
+
currentVersion?: string;
|
|
31
|
+
}): Handler;
|
|
27
32
|
|
|
28
33
|
export { type ResolvedGraphQLOptions, createGraphQLHandler };
|
package/dist/graphql/index.js
CHANGED
|
@@ -148,10 +148,13 @@ var SORTABLE = [
|
|
|
148
148
|
{ gql: "createdAt", value: "created_at" },
|
|
149
149
|
{ gql: "updatedAt", value: "updated_at" }
|
|
150
150
|
];
|
|
151
|
-
function buildSortFieldEnum(typeName) {
|
|
151
|
+
function buildSortFieldEnum(typeName, fieldKeys) {
|
|
152
|
+
const resolvable = SORTABLE.filter(
|
|
153
|
+
(s) => s.gql === "createdAt" || s.gql === "updatedAt" || fieldKeys === void 0 || fieldKeys.columnFor(s.value) !== void 0
|
|
154
|
+
);
|
|
152
155
|
return new import_graphql3.GraphQLEnumType({
|
|
153
156
|
name: `${typeName}SortField`,
|
|
154
|
-
values: Object.fromEntries(
|
|
157
|
+
values: Object.fromEntries(resolvable.map((s) => [s.gql, { value: s.value }]))
|
|
155
158
|
});
|
|
156
159
|
}
|
|
157
160
|
function comparableFilterInput(name, scalar) {
|
|
@@ -195,18 +198,19 @@ function filterInputForField(fieldType) {
|
|
|
195
198
|
if (scalar === null) return null;
|
|
196
199
|
return StringFilter;
|
|
197
200
|
}
|
|
198
|
-
function buildFilterInputType(type) {
|
|
201
|
+
function buildFilterInputType(type, viewFields) {
|
|
199
202
|
const fields = {};
|
|
200
203
|
fields["id"] = { type: IDFilter };
|
|
201
204
|
fields["published"] = { type: BooleanFilter };
|
|
202
205
|
fields["createdAt"] = { type: DateTimeFilter };
|
|
203
206
|
fields["updatedAt"] = { type: DateTimeFilter };
|
|
204
207
|
if (type.schema_type === "content-type") fields["slug"] = { type: StringFilter };
|
|
205
|
-
|
|
208
|
+
const entries = viewFields !== void 0 ? viewFields.map((v) => ({ field: v.field, exposedAs: v.exposedAs })) : type.fields.filter((f) => f.removed !== true).map((f) => ({ field: f, exposedAs: f.name }));
|
|
209
|
+
for (const { field: f, exposedAs } of entries) {
|
|
206
210
|
if (f.field_type === "programmatic" || f.field_type === "paragraph") continue;
|
|
207
211
|
if (f.field_type === "image" || f.field_type === "video" || f.field_type === "file") continue;
|
|
208
212
|
const input = filterInputForField(f.field_type);
|
|
209
|
-
if (input) fields[buildFieldNameMap([
|
|
213
|
+
if (input) fields[buildFieldNameMap([exposedAs]).toGraphql(exposedAs)] = { type: input };
|
|
210
214
|
}
|
|
211
215
|
if (Object.keys(fields).length === 0) return null;
|
|
212
216
|
return new import_graphql3.GraphQLInputObjectType({ name: `${graphqlTypeName(type.name)}Filter`, fields });
|
|
@@ -253,32 +257,41 @@ function codeError(code, message) {
|
|
|
253
257
|
function isPublished(item) {
|
|
254
258
|
return !!item && item["published"] === true;
|
|
255
259
|
}
|
|
256
|
-
function resolveFieldValue(field, row) {
|
|
260
|
+
function resolveFieldValue(field, row, fallback) {
|
|
257
261
|
const key = isColumnBacked(field) ? field.db_column.column_name : field.name;
|
|
258
|
-
|
|
262
|
+
const value = row[key];
|
|
263
|
+
if ((value === null || value === void 0) && fallback !== void 0) return fallback;
|
|
264
|
+
return value ?? null;
|
|
259
265
|
}
|
|
260
|
-
function scalarFieldResolver(field) {
|
|
261
|
-
return (parent) => resolveFieldValue(field, parent);
|
|
266
|
+
function scalarFieldResolver(field, fallback) {
|
|
267
|
+
return (parent) => resolveFieldValue(field, parent, fallback);
|
|
262
268
|
}
|
|
263
269
|
function relationFieldResolver(typeName, schemaFieldName) {
|
|
264
270
|
return (parent, _args, ctx) => ctx.loaders.load(typeName, schemaFieldName, parent);
|
|
265
271
|
}
|
|
266
|
-
function programmaticFieldResolver(typeName, schemaFieldName,
|
|
272
|
+
function programmaticFieldResolver(typeName, schemaFieldName, mediaFields = [], fieldKeys) {
|
|
267
273
|
return async (parent, _args, ctx) => {
|
|
268
274
|
let p = ctx.programmaticMemo.get(parent);
|
|
269
275
|
if (!p) {
|
|
270
|
-
p = resolveProgrammaticRow(typeName, parent, ctx,
|
|
276
|
+
p = resolveProgrammaticRow(typeName, parent, ctx, mediaFields, fieldKeys);
|
|
271
277
|
ctx.programmaticMemo.set(parent, p);
|
|
272
278
|
}
|
|
273
279
|
return (await p)[schemaFieldName];
|
|
274
280
|
};
|
|
275
281
|
}
|
|
276
|
-
async function resolveProgrammaticRow(typeName, parent, ctx,
|
|
282
|
+
async function resolveProgrammaticRow(typeName, parent, ctx, mediaFields, fieldKeys) {
|
|
277
283
|
const toLabels = (row) => fieldKeys ? fieldKeys.toLabels(row) : row;
|
|
278
|
-
if (
|
|
284
|
+
if (mediaFields.length === 0) return ctx.resolver.resolveItem(typeName, toLabels(parent));
|
|
279
285
|
const enriched = { ...parent };
|
|
280
|
-
await Promise.all(
|
|
281
|
-
return ctx.resolver.resolveItem(typeName, toLabels(enriched));
|
|
286
|
+
await Promise.all(mediaFields.map((m) => ctx.loaders.load(typeName, m.name, enriched)));
|
|
287
|
+
return ctx.resolver.resolveItem(typeName, relabelMedia(toLabels(enriched), enriched, mediaFields));
|
|
288
|
+
}
|
|
289
|
+
function relabelMedia(record, enriched, mediaFields) {
|
|
290
|
+
for (const { name, exposedAs } of mediaFields) {
|
|
291
|
+
if (!(name in enriched)) continue;
|
|
292
|
+
record[exposedAs] = enriched[name];
|
|
293
|
+
}
|
|
294
|
+
return record;
|
|
282
295
|
}
|
|
283
296
|
function collectionResolver(typeName, nameMap, fieldKeys) {
|
|
284
297
|
return async (_root, args, ctx) => {
|
|
@@ -352,7 +365,7 @@ var MEDIA = new import_graphql5.GraphQLObjectType({
|
|
|
352
365
|
duration: { type: import_graphql5.GraphQLInt, resolve: (m) => m.duration }
|
|
353
366
|
}
|
|
354
367
|
});
|
|
355
|
-
function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
|
|
368
|
+
function buildGraphQLSchema(registry, fieldKeyMaps = {}, view) {
|
|
356
369
|
const objectTypes = /* @__PURE__ */ new Map();
|
|
357
370
|
const enumTypes = /* @__PURE__ */ new Map();
|
|
358
371
|
for (const [name, enumType] of Object.entries(registry.enum_types)) {
|
|
@@ -371,14 +384,14 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
|
|
|
371
384
|
);
|
|
372
385
|
}
|
|
373
386
|
}
|
|
374
|
-
function outputTypeForField(field) {
|
|
387
|
+
function outputTypeForField(field, required) {
|
|
375
388
|
const scalar = scalarOutputType(field.field_type);
|
|
376
|
-
if (scalar) return
|
|
389
|
+
if (scalar) return required ? new import_graphql5.GraphQLNonNull(scalar) : scalar;
|
|
377
390
|
if (field.field_type === "enum") {
|
|
378
391
|
const ref = field.ui_component.component === "select" ? field.ui_component.enum_ref : void 0;
|
|
379
392
|
const et = ref ? enumTypes.get(ref) : void 0;
|
|
380
393
|
const t = et ?? import_graphql5.GraphQLString;
|
|
381
|
-
return
|
|
394
|
+
return required ? new import_graphql5.GraphQLNonNull(t) : t;
|
|
382
395
|
}
|
|
383
396
|
if (field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
|
|
384
397
|
return MEDIA;
|
|
@@ -408,8 +421,17 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
|
|
|
408
421
|
}
|
|
409
422
|
return import_graphql5.GraphQLString;
|
|
410
423
|
}
|
|
424
|
+
function exposedFields(type) {
|
|
425
|
+
return type.fields.filter((f) => f.removed !== true);
|
|
426
|
+
}
|
|
427
|
+
function viewFieldsFor(machineName, type) {
|
|
428
|
+
return view?.[machineName]?.fields ?? exposedFields(type).map((f) => ({ field: f, exposedAs: f.name, required: f.required }));
|
|
429
|
+
}
|
|
411
430
|
function buildObjectType(machineName, type) {
|
|
412
|
-
const
|
|
431
|
+
const visible = viewFieldsFor(machineName, type);
|
|
432
|
+
const mediaFields = visible.filter(
|
|
433
|
+
(v) => v.field.field_type === "image" || v.field.field_type === "video" || v.field.field_type === "file"
|
|
434
|
+
).map((v) => ({ name: v.field.name, exposedAs: v.exposedAs }));
|
|
413
435
|
return new import_graphql5.GraphQLObjectType({
|
|
414
436
|
name: graphqlTypeName(machineName),
|
|
415
437
|
fields: () => {
|
|
@@ -423,23 +445,25 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
|
|
|
423
445
|
}
|
|
424
446
|
fields["createdAt"] = { type: new import_graphql5.GraphQLNonNull(DateTimeScalar), resolve: (p) => p["created_at"] };
|
|
425
447
|
fields["updatedAt"] = { type: new import_graphql5.GraphQLNonNull(DateTimeScalar), resolve: (p) => p["updated_at"] };
|
|
426
|
-
for (const
|
|
427
|
-
const
|
|
428
|
-
const
|
|
448
|
+
for (const vf of visible) {
|
|
449
|
+
const field = vf.field;
|
|
450
|
+
const gqlName = toCamelCase(vf.exposedAs);
|
|
451
|
+
const outType = outputTypeForField(field, vf.required);
|
|
429
452
|
let resolve;
|
|
430
453
|
if (field.field_type === "programmatic") {
|
|
431
|
-
resolve = programmaticFieldResolver(
|
|
432
|
-
machineName,
|
|
433
|
-
field.name,
|
|
434
|
-
mediaFieldNames,
|
|
435
|
-
fieldKeyMaps[machineName]
|
|
436
|
-
);
|
|
454
|
+
resolve = programmaticFieldResolver(machineName, field.name, mediaFields, fieldKeyMaps[machineName]);
|
|
437
455
|
} else if (field.field_type === "reference" || field.field_type === "paragraph" || field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
|
|
438
456
|
resolve = relationFieldResolver(machineName, field.name);
|
|
439
457
|
} else {
|
|
440
|
-
resolve = scalarFieldResolver(field);
|
|
458
|
+
resolve = scalarFieldResolver(field, vf.fallback);
|
|
441
459
|
}
|
|
442
|
-
fields[gqlName] = {
|
|
460
|
+
fields[gqlName] = {
|
|
461
|
+
type: outType,
|
|
462
|
+
resolve,
|
|
463
|
+
// Conditional spread, not `deprecationReason: vf.deprecationReason`
|
|
464
|
+
// — exactOptionalPropertyTypes forbids present-and-undefined.
|
|
465
|
+
...vf.deprecationReason !== void 0 && { deprecationReason: vf.deprecationReason }
|
|
466
|
+
};
|
|
443
467
|
}
|
|
444
468
|
return fields;
|
|
445
469
|
}
|
|
@@ -471,7 +495,8 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
|
|
|
471
495
|
const queryFields = {};
|
|
472
496
|
for (const [name, ct] of Object.entries(registry.content_types)) {
|
|
473
497
|
const objType = objectTypes.get(name);
|
|
474
|
-
const
|
|
498
|
+
const visible = viewFieldsFor(name, ct);
|
|
499
|
+
const nameMap = buildFieldNameMap(visible.map((v) => v.exposedAs));
|
|
475
500
|
if (ct.only_one) {
|
|
476
501
|
queryFields[singleQueryName(name)] = { type: objType, resolve: singletonResolver(name) };
|
|
477
502
|
continue;
|
|
@@ -483,13 +508,13 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
|
|
|
483
508
|
meta: { type: new import_graphql5.GraphQLNonNull(PAGE_META), resolve: (r) => r.meta }
|
|
484
509
|
}
|
|
485
510
|
});
|
|
486
|
-
const filterType = buildFilterInputType(ct);
|
|
511
|
+
const filterType = buildFilterInputType(ct, visible);
|
|
487
512
|
queryFields[collectionQueryName(name)] = {
|
|
488
513
|
type: new import_graphql5.GraphQLNonNull(listType),
|
|
489
514
|
args: {
|
|
490
515
|
page: { type: import_graphql5.GraphQLInt },
|
|
491
516
|
perPage: { type: import_graphql5.GraphQLInt },
|
|
492
|
-
sortBy: { type: buildSortFieldEnum(graphqlTypeName(name)) },
|
|
517
|
+
sortBy: { type: buildSortFieldEnum(graphqlTypeName(name), fieldKeyMaps[name]) },
|
|
493
518
|
sortOrder: { type: SortOrderEnum },
|
|
494
519
|
...filterType ? { filter: { type: filterType } } : {}
|
|
495
520
|
},
|
|
@@ -513,7 +538,11 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
|
|
|
513
538
|
queryFields[collectionQueryName(name)] = {
|
|
514
539
|
type: new import_graphql5.GraphQLNonNull(listType),
|
|
515
540
|
args: { page: { type: import_graphql5.GraphQLInt }, perPage: { type: import_graphql5.GraphQLInt } },
|
|
516
|
-
resolve: collectionResolver(
|
|
541
|
+
resolve: collectionResolver(
|
|
542
|
+
name,
|
|
543
|
+
buildFieldNameMap(viewFieldsFor(name, tt).map((v) => v.exposedAs)),
|
|
544
|
+
fieldKeyMaps[name]
|
|
545
|
+
)
|
|
517
546
|
};
|
|
518
547
|
queryFields[singleQueryName(name)] = {
|
|
519
548
|
type: objType,
|
|
@@ -526,6 +555,70 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
|
|
|
526
555
|
});
|
|
527
556
|
}
|
|
528
557
|
|
|
558
|
+
// src/graphql/version-view.ts
|
|
559
|
+
function byColumn(fields) {
|
|
560
|
+
const out = /* @__PURE__ */ new Map();
|
|
561
|
+
for (const f of fields) {
|
|
562
|
+
if (!isColumnBacked(f)) continue;
|
|
563
|
+
out.set(f.db_column.column_name, f);
|
|
564
|
+
}
|
|
565
|
+
return out;
|
|
566
|
+
}
|
|
567
|
+
function deprecationFor(field, currentLabels, currentVersion) {
|
|
568
|
+
const currentLabel = currentLabels.get(field.column_name);
|
|
569
|
+
if (currentLabel === void 0) {
|
|
570
|
+
return `Removed in ${currentVersion}; column retained while this version is live.`;
|
|
571
|
+
}
|
|
572
|
+
if (currentLabel !== field.exposed_as) {
|
|
573
|
+
return `Renamed to '${currentLabel}' in ${currentVersion}.`;
|
|
574
|
+
}
|
|
575
|
+
return void 0;
|
|
576
|
+
}
|
|
577
|
+
function buildVersionView(input) {
|
|
578
|
+
const { registry, projection, currentProjection, currentVersion } = input;
|
|
579
|
+
const out = {};
|
|
580
|
+
const sources = [
|
|
581
|
+
registry.content_types,
|
|
582
|
+
registry.taxonomy_types,
|
|
583
|
+
registry.paragraph_types
|
|
584
|
+
];
|
|
585
|
+
for (const source of sources) {
|
|
586
|
+
for (const [typeName, type] of Object.entries(source)) {
|
|
587
|
+
const projectionType = projection?.types[typeName];
|
|
588
|
+
if (projectionType === void 0) {
|
|
589
|
+
out[typeName] = {
|
|
590
|
+
fields: type.fields.filter((f) => f.removed !== true).map((f) => ({ field: f, exposedAs: f.name, required: f.required }))
|
|
591
|
+
};
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
const columns = byColumn(type.fields);
|
|
595
|
+
const currentLabels = /* @__PURE__ */ new Map();
|
|
596
|
+
for (const f of currentProjection?.types[typeName]?.fields ?? []) {
|
|
597
|
+
currentLabels.set(f.column_name, f.exposed_as);
|
|
598
|
+
}
|
|
599
|
+
const fields = [];
|
|
600
|
+
for (const pf of projectionType.fields) {
|
|
601
|
+
const field = columns.get(pf.column_name);
|
|
602
|
+
if (field === void 0) continue;
|
|
603
|
+
const reason = deprecationFor(pf, currentLabels, currentVersion);
|
|
604
|
+
fields.push({
|
|
605
|
+
field,
|
|
606
|
+
exposedAs: pf.exposed_as,
|
|
607
|
+
required: pf.required,
|
|
608
|
+
...pf.fallback !== void 0 && { fallback: pf.fallback },
|
|
609
|
+
...reason !== void 0 && { deprecationReason: reason }
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
for (const f of type.fields) {
|
|
613
|
+
if (isColumnBacked(f) || f.removed === true) continue;
|
|
614
|
+
fields.push({ field: f, exposedAs: f.name, required: f.required });
|
|
615
|
+
}
|
|
616
|
+
out[typeName] = { fields };
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return out;
|
|
620
|
+
}
|
|
621
|
+
|
|
529
622
|
// src/graphql/dataloaders.ts
|
|
530
623
|
var import_dataloader = __toESM(require("dataloader"));
|
|
531
624
|
|
|
@@ -754,15 +847,29 @@ function isClientSafeGraphQLError(error) {
|
|
|
754
847
|
}
|
|
755
848
|
return false;
|
|
756
849
|
}
|
|
757
|
-
function createGraphQLHandler(registry, repos, fieldKeyMaps, resolver, db, options) {
|
|
758
|
-
|
|
850
|
+
function createGraphQLHandler(registry, repos, fieldKeyMaps, resolver, db, options, versioning = {}) {
|
|
851
|
+
let view;
|
|
852
|
+
if (versioning.projection !== void 0) {
|
|
853
|
+
if (versioning.currentVersion === void 0) {
|
|
854
|
+
throw new Error(
|
|
855
|
+
'createGraphQLHandler: currentVersion is required when a projection is supplied \u2014 deprecation reasons name it, and an empty one ships "Removed in ." into published SDL.'
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
view = buildVersionView({
|
|
859
|
+
registry,
|
|
860
|
+
projection: versioning.projection,
|
|
861
|
+
currentProjection: versioning.currentProjection,
|
|
862
|
+
currentVersion: versioning.currentVersion
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
const schema = buildGraphQLSchema(registry, fieldKeyMaps, view);
|
|
759
866
|
const { plugins } = buildArmorPlugin({
|
|
760
867
|
maxDepth: options.maxDepth,
|
|
761
868
|
maxComplexity: options.maxComplexity
|
|
762
869
|
});
|
|
763
870
|
const yoga = (0, import_graphql_yoga.createYoga)({
|
|
764
871
|
schema,
|
|
765
|
-
graphqlEndpoint: "/graphql",
|
|
872
|
+
graphqlEndpoint: versioning.endpoint ?? "/graphql",
|
|
766
873
|
graphiql: options.graphiql,
|
|
767
874
|
landingPage: false,
|
|
768
875
|
maskedErrors: {
|