UNPKG

@dataql/node

Version:

DataQL core SDK for unified data management with MongoDB and GraphQL - Production Multi-Cloud Ready

265 lines (264 loc) 9.44 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Money = exports.Boolean = exports.String = exports.ID = exports.typeToGraphQL = exports.GraphQLTimestamp = exports.GraphQLLong = exports.GraphQLDecimal128 = void 0; exports.schemaObjectToGraphQLFields = schemaObjectToGraphQLFields; exports.normalizeField = normalizeField; exports.normalizeSchemaObject = normalizeSchemaObject; exports.collection = collection; const graphql_1 = require("graphql"); const index_js_1 = require("./index.js"); /** * Custom GraphQL Scalars for MongoDB BSON types: * - Decimal128: GraphQLDecimal128 * - Long: GraphQLLong * - BinData: GraphQLBinData * - Timestamp: GraphQLTimestamp * - Regex: GraphQLRegex * - DBPointer: GraphQLDBPointer * - JavaScript: GraphQLJavaScript * - MinKey/MaxKey: GraphQLMinKey/GraphQLMaxKey * - Undefined: GraphQLUndefined * - Null: GraphQLNull * * ObjectId is mapped to GraphQLID. */ // --- Keep only supported GraphQL Scalars --- exports.GraphQLDecimal128 = new graphql_1.GraphQLScalarType({ name: "Decimal128", description: "MongoDB Decimal128 type, serialized as string.", serialize: (value) => value?.toString?.() ?? value, parseValue: (value) => value, parseLiteral(ast) { return ast.kind === "StringValue" ? ast.value : null; }, }); exports.GraphQLLong = new graphql_1.GraphQLScalarType({ name: "Long", description: "MongoDB 64-bit integer (Long), serialized as string.", serialize: (value) => value?.toString?.() ?? value, parseValue: (value) => value, parseLiteral(ast) { return ast.kind === "StringValue" ? ast.value : null; }, }); exports.GraphQLTimestamp = new graphql_1.GraphQLScalarType({ name: "Timestamp", description: "MongoDB Timestamp, serialized as string.", serialize: (value) => value?.toString?.() ?? value, parseValue: (value) => value, parseLiteral(ast) { return ast.kind === "StringValue" ? ast.value : null; }, }); // Utility to map Capitalized BSON types to GraphQL types exports.typeToGraphQL = { Double: graphql_1.GraphQLFloat, String: graphql_1.GraphQLString, Object: graphql_1.GraphQLString, // or custom object type Array: (itemType) => new graphql_1.GraphQLList(itemType), Id: graphql_1.GraphQLID, Boolean: graphql_1.GraphQLBoolean, Date: graphql_1.GraphQLString, // ISO string or custom scalar Int: graphql_1.GraphQLInt, Timestamp: exports.GraphQLTimestamp, Long: exports.GraphQLLong, Decimal: exports.GraphQLDecimal128, Number: graphql_1.GraphQLFloat, }; // --- Add GraphQL EnumType cache --- const enumTypeCache = new Map(); function resolveGraphQLType(field, key) { // Enum support if (field && field.enum && Array.isArray(field.enum)) { // Use a cache to avoid duplicate enum types const enumName = key || "Enum_" + field.enum.join("_"); if (!enumTypeCache.has(enumName)) { enumTypeCache.set(enumName, new (require("graphql").GraphQLEnumType)({ name: enumName, values: Object.fromEntries(field.enum.map((v) => [v, { value: v }])), })); } const gqlType = enumTypeCache.get(enumName); return field.required ? new graphql_1.GraphQLNonNull(gqlType) : gqlType; } if (field.type === "array" && field.items) { const itemType = resolveGraphQLType(field.items); return new graphql_1.GraphQLList(itemType); } const gqlType = exports.typeToGraphQL[field.type] || graphql_1.GraphQLString; if (field.required) { return new graphql_1.GraphQLNonNull(gqlType); } return gqlType; } function schemaObjectToGraphQLFields(schema) { const fields = {}; for (const key in schema) { const value = schema[key]; if (typeof value === "object" && value !== null && !Array.isArray(value) && (value.type || value.enum)) { fields[key] = { type: resolveGraphQLType(value, key) }; } else if (typeof value === "object" && value !== null && !Array.isArray(value)) { fields[key] = { type: new graphql_1.GraphQLObjectType({ name: `${key}_Type`, fields: schemaObjectToGraphQLFields(value), }), }; } else { fields[key] = { type: graphql_1.GraphQLString }; } } return fields; } // Helper to detect if a value is a schema object (SchemaDefinition) function isSchemaObject(val) { return (val && typeof val === "object" && "schemaObject" in val && "name" in val && "collection" in val); } function normalizeField(field, isRoot = false, visitedSchemas = new Set()) { // If the field is a schema object reference, wrap as array of subdocuments if (isSchemaObject(field)) { // Prevent infinite loops if (visitedSchemas.has(field)) { return { type: "array", items: { type: "object", properties: {} } }; } visitedSchemas.add(field); return { type: "array", items: normalizeSchemaObject(field.schemaObject, visitedSchemas), }; } if (Array.isArray(field)) { // Check if this is an enum (array of strings) or array of subdocuments if (field.length > 0 && field.every((item) => typeof item === "string")) { // Enum shorthand: ["one", "two", "three"] return { enum: field }; } // Array of objects or primitives return { type: "array", items: normalizeField(field[0], false, visitedSchemas), }; } if (typeof field === "object" && field !== null && !field.type && !field.enum) { // Object shorthand: { ... } const normalized = { type: "object", properties: Object.fromEntries(Object.entries(field).map(([k, v]) => [ k, normalizeField(v, false, visitedSchemas), ])), }; // If not root, wrap in array if (!isRoot) { return { type: "array", items: normalized, }; } return normalized; } if (typeof field === "object" && field !== null && field.enum) { // Enum support return { ...field }; } if (typeof field === "string") { // Primitive shorthand: "string" return { type: field }; } // Already explicit return field; } function normalizeSchemaObject(schemaObject, visitedSchemas = new Set()) { // Automatically add ID, createdAt, and updatedAt fields if not present const withMeta = { ...schemaObject }; if (!("id" in withMeta)) { withMeta.id = exports.ID; } if (!("createdAt" in withMeta)) { withMeta.createdAt = index_js_1.Timestamp; } if (!("updatedAt" in withMeta)) { withMeta.updatedAt = index_js_1.Timestamp; } return Object.fromEntries(Object.entries(withMeta).map(([k, v]) => [ k, normalizeField(v, true, visitedSchemas), ])); } /** * Global schema registry for auto-registration. * All schemas created via collection() are automatically registered with every MongoDBSchemaSDK instance on connect(). */ const __globalSchemaRegistry = []; // --- Utility to convert type values to runtime strings for schema --- function typeToString(val) { // If the value is a function (constructor), map to string name if (typeof val === "function" && val.name) { return val.name; } // If the value is a type alias (ID, String, etc.), map to string if (val === exports.String || val === Number || val === exports.Boolean || val === Date || val === undefined) { return typeof val; } // If the value is a plain object, recursively map if (val && typeof val === "object" && !Array.isArray(val)) { return Object.fromEntries(Object.entries(val).map(([k, v]) => [k, typeToString(v)])); } // If the value is an array, map the first element if (Array.isArray(val)) { return [typeToString(val[0])]; } // If the value is a string, return as is if (typeof val === "string") return val; // Fallback return val; } // Patch collection() to auto-convert type values to strings function collection(name, schemaObject, options) { // Convert type values to strings for runtime const runtimeSchema = typeToString(schemaObject); const def = { name: name.charAt(0).toUpperCase() + name.slice(1), collection: name, schemaObject: runtimeSchema, ...(options?.indexes ? { indexes: options.indexes } : {}), }; // Add to global registry if not already present (by name) if (!__globalSchemaRegistry.find((s) => s.name === def.name)) { __globalSchemaRegistry.push(def); } return def; } function createInputType(name, fields) { const inputFields = {}; Object.entries(fields).forEach(([key, value]) => { inputFields[key] = { type: value.type }; }); return new graphql_1.GraphQLInputObjectType({ name: `${name}Input`, fields: inputFields, }); } exports.ID = "ID"; exports.String = "String"; exports.Boolean = "Boolean"; exports.Money = "Decimal";