Implement checked aggregation plans, native SQL, and bounded RPC capture
This commit is contained in:
+28
-31
@@ -14,6 +14,7 @@ import {
|
||||
type GraphQLInputType,
|
||||
type GraphQLFieldConfigMap,
|
||||
type GraphQLInputFieldConfigMap,
|
||||
type DocumentNode,
|
||||
} from "graphql";
|
||||
import { createHash } from "node:crypto";
|
||||
import type {
|
||||
@@ -23,6 +24,7 @@ import type {
|
||||
RelationshipInterfaceMember,
|
||||
} from "../capability-model/types.js";
|
||||
import { QueryCompileError, type QueryDeclaration } from "./types.js";
|
||||
import { relationalSchema, objectRow } from "./relational-schema.js";
|
||||
|
||||
// Validate losslessly; callers carry decimal strings across JSON transports.
|
||||
function integerParser(name: string, min: bigint, max: bigint, value: unknown): string {
|
||||
@@ -67,19 +69,21 @@ export const cursorScalar = new GraphQLScalarType({
|
||||
return value;
|
||||
},
|
||||
});
|
||||
export const referenceScalar = new GraphQLScalarType({ name: "ManagedReference" });
|
||||
|
||||
export function querySchema(declaration: QueryDeclaration, interfaces: readonly InterfaceRevision[]) {
|
||||
export function querySchema(
|
||||
declaration: QueryDeclaration,
|
||||
interfaces: readonly InterfaceRevision[],
|
||||
document?: DocumentNode,
|
||||
) {
|
||||
const contracts = new Map(interfaces.map((entry) => [entry.revisionId, entry]));
|
||||
const objects = new Map<InterfaceRevisionId, GraphQLObjectType>();
|
||||
const filters = new Map<InterfaceRevisionId, GraphQLInputObjectType>();
|
||||
const orders = new Map<InterfaceRevisionId, GraphQLInputObjectType>();
|
||||
const byName = new Map<string, InterfaceRevision>();
|
||||
const comparisons = new Map<string, GraphQLInputObjectType>();
|
||||
const metadata = new GraphQLObjectType({
|
||||
name: "QxMetadata",
|
||||
fields: { ref: { type: new GraphQLNonNull(referenceScalar) } },
|
||||
});
|
||||
const scalarTypes = new Map<string, ValueType>(
|
||||
Object.entries(queryScalars).map(([name, type]) => [type.name, { kind: "scalar", name } as ValueType]),
|
||||
);
|
||||
scalarTypes.set("Cursor", { kind: "scalar", name: "string" });
|
||||
const pageInfo = new GraphQLObjectType({
|
||||
name: "QxPageInfo",
|
||||
fields: {
|
||||
@@ -130,6 +134,8 @@ export function querySchema(declaration: QueryDeclaration, interfaces: readonly
|
||||
let result = comparisons.get(key);
|
||||
if (!result) {
|
||||
const fields: GraphQLInputFieldConfigMap = { eq: { type: value }, isNull: { type: GraphQLBoolean } };
|
||||
if (base.kind === "scalar" && base.name !== "bytes")
|
||||
fields.in = { type: new GraphQLList(new GraphQLNonNull(value)) };
|
||||
if (base.kind === "scalar" && base.name !== "bool" && base.name !== "bytes")
|
||||
for (const op of ["lt", "lte", "gt", "gte"]) fields[op] = { type: value };
|
||||
result = new GraphQLInputObjectType({ name: `QxCompare${key}`, fields });
|
||||
@@ -137,29 +143,12 @@ export function querySchema(declaration: QueryDeclaration, interfaces: readonly
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const filter = (id: InterfaceRevisionId): GraphQLInputObjectType => {
|
||||
const existing = filters.get(id);
|
||||
if (existing) return existing;
|
||||
const result = new GraphQLInputObjectType({
|
||||
name: `${name(id)}Where`,
|
||||
fields: () => {
|
||||
const fields: GraphQLInputFieldConfigMap = {
|
||||
and: { type: new GraphQLList(new GraphQLNonNull(result)) },
|
||||
or: { type: new GraphQLList(new GraphQLNonNull(result)) },
|
||||
not: { type: result },
|
||||
};
|
||||
for (const member of contract(id).members)
|
||||
if (member.kind === "value" && member.queryRead) {
|
||||
if (member.displayName in fields)
|
||||
throw new QueryCompileError("QUERY_SCHEMA_NAME", `Reserved predicate name ${member.displayName}`);
|
||||
fields[member.displayName] = { type: comparison(member.valueType) };
|
||||
}
|
||||
return fields;
|
||||
},
|
||||
});
|
||||
filters.set(id, result);
|
||||
return result;
|
||||
};
|
||||
const relational = relationalSchema(
|
||||
{ contracts, target, name, scalar, comparison, scalarTypes, direction, cursor: cursorScalar, pageInfo },
|
||||
document,
|
||||
);
|
||||
relational.discover(declaration.root);
|
||||
const filter = (id: InterfaceRevisionId) => relational.where(objectRow(id));
|
||||
const order = (id: InterfaceRevisionId) => {
|
||||
let result = orders.get(id);
|
||||
if (!result) {
|
||||
@@ -181,6 +170,14 @@ export function querySchema(declaration: QueryDeclaration, interfaces: readonly
|
||||
const result = new GraphQLObjectType({
|
||||
name: name(id),
|
||||
fields: () => {
|
||||
const helpers = relational.helpers(id);
|
||||
const metadata = new GraphQLObjectType({
|
||||
name: `QxMetadata_${name(id)}`,
|
||||
fields: {
|
||||
ref: { type: new GraphQLNonNull(relational.refs(id)) },
|
||||
...(helpers ? { relations: { type: new GraphQLNonNull(helpers) } } : {}),
|
||||
},
|
||||
});
|
||||
const fields: GraphQLFieldConfigMap<unknown, unknown> = { _qx: { type: new GraphQLNonNull(metadata) } };
|
||||
for (const member of contract(id).members) {
|
||||
if (member.kind === "operation" || !member.queryRead) continue;
|
||||
@@ -249,5 +246,5 @@ export function querySchema(declaration: QueryDeclaration, interfaces: readonly
|
||||
fields: { root: { type: new GraphQLNonNull(object(declaration.root)) } },
|
||||
}),
|
||||
});
|
||||
return { schema, byName, target, contracts };
|
||||
return { schema, byName, target, contracts, relational, scalarTypes };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user