@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
JavaScript
;
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";