@spectragraph/core
v0.7.0
Published
A JavaScript library for working with schema-driven graph data. SpectraGraph provides powerful tools for querying, validating, and manipulating relational data structures with a focus on type safety and developer experience. It is intended to be a toolkit
Readme
SpectraGraph Core
A JavaScript library for working with schema-driven graph data. SpectraGraph provides powerful tools for querying, validating, and manipulating relational data structures with a focus on type safety and developer experience. It is intended to be a toolkit for building SpectraGraph stores.
Overview
SpectraGraph Core is built around several key principles:
- Schema-driven: Everything flows from user-defined schemas that describe your data structure
- Query-focused: Powerful query language for retrieving exactly the data you need
- Practical: Optimized for real-world messy data scenarios with schema powered validation
Installation
npm install @spectragraph/coreCore Concepts
Schemas
Schemas define the structure of your data, including resource types, attributes, and relationships. They serve as the foundation for all SpectraGraph operations.
const schema = {
resources: {
teams: {
attributes: {
id: { type: "string" },
name: { type: "string" },
city: { type: "string" },
},
relationships: {
homeMatches: {
type: "matches",
cardinality: "many",
inverse: "homeTeam",
},
awayMatches: {
type: "matches",
cardinality: "many",
inverse: "awayTeam",
},
},
},
matches: {
attributes: {
id: { type: "string" },
field: { type: "string" },
ageGroup: { type: "integer" },
},
relationships: {
homeTeam: { type: "teams", cardinality: "one", inverse: "homeMatches" },
awayTeam: { type: "teams", cardinality: "one", inverse: "awayMatches" },
},
},
},
};Graphs
Graphs represent your actual data in a normalized structure, organized by resource type and ID:
const graph = {
teams: {
"team-1": {
type: "teams",
id: "team-1",
attributes: { name: "Arizona Bay FC", city: "Phoenix" },
relationships: {
homeMatches: [{ type: "matches", id: "match-1" }],
awayMatches: [{ type: "matches", id: "match-2" }],
},
},
"team-2": {
type: "teams",
id: "team-2",
attributes: { name: "Scottsdale Surf", city: "Scottsdale" },
relationships: {
homeMatches: [{ type: "matches", id: "match-2" }],
awayMatches: [{ type: "matches", id: "match-1" }],
},
},
},
matches: {
"match-1": {
type: "matches",
id: "match-1",
attributes: { field: "Phoenix Park 1", ageGroup: 11 },
relationships: {
homeTeam: { type: "teams", id: "team-1" },
awayTeam: { type: "teams", id: "team-2" },
},
},
"match-2": {
type: "matches",
id: "match-2",
attributes: { field: "Scottsdale Community Center", ageGroup: 11 },
relationships: {
homeTeam: { type: "teams", id: "team-2" },
awayTeam: { type: "teams", id: "team-1" },
},
},
},
};Queries
Queries describe what data you want to retrieve, closely matching your desired output structure:
const query = {
type: "teams",
select: ["name", { homeMatches: ["field", "ageGroup"] }],
};Grouping and Aggregation
SpectraGraph supports grouping and aggregation operations through the group clause, enabling analytics queries similar to SQL's GROUP BY functionality.
Grand Totals
Aggregate all resources into a single group using an empty by array:
// Compute totals across all matches
const query = {
type: "matches",
group: {
by: [],
aggregates: {
totalMatches: { $count: null },
totalGoals: { $sum: { $pluck: "goals" } },
avgGoals: { $mean: { $pluck: "goals" } },
},
},
};
// Returns: [{ totalMatches: 150, totalGoals: 423, avgGoals: 2.82 }]This is equivalent to SQL aggregates without a GROUP BY clause.
Basic Grouping
Group resources by one or more attributes:
// Group matches by age group
const query = {
type: "matches",
group: {
by: "ageGroup",
},
};
// Returns: [{ ageGroup: 11 }, { ageGroup: 12 }, { ageGroup: 13 }]
// Group by multiple fields
const query = {
type: "matches",
group: {
by: ["ageGroup", "field"],
},
};Aggregates
Compute aggregate values for each group:
// Count matches per age group
const query = {
type: "matches",
group: {
by: "ageGroup",
aggregates: {
total: { $count: null },
},
},
};
// Returns: [
// { ageGroup: 11, total: 2 },
// { ageGroup: 12, total: 1 },
// { ageGroup: 13, total: 1 }
// ]
// Sum goals per age group
const query = {
type: "matches",
group: {
by: "ageGroup",
aggregates: {
totalGoals: { $sum: { $pluck: "goals" } },
},
},
};Select in Groups
Control which fields to include in results:
// Rename fields
const query = {
type: "matches",
group: {
by: "ageGroup",
select: { age: "ageGroup" }, // Rename ageGroup to age
},
};
// Compute derived fields
const query = {
type: "matches",
group: {
by: "ageGroup",
select: [
"ageGroup",
{
tier: {
$if: {
if: { $gte: [{ $get: "ageGroup" }, 12] },
then: "senior",
else: "junior",
},
},
},
],
},
};Filtering Within Groups
Use where on a group clause to filter groups after aggregation (similar to SQL's HAVING):
// Only show age groups with more than 2 total goals
const query = {
type: "matches",
group: {
by: "ageGroup",
aggregates: { total: { $sum: { $pluck: "goals" } } },
where: { $gt: [{ $get: "total" }, 2] },
},
};Ordering, Limiting Groups
Groups support the same query modifiers as regular queries:
const query = {
type: "matches",
group: {
by: "ageGroup",
aggregates: { total: { $sum: { $pluck: "goals" } } },
order: { total: "desc" }, // Order by aggregate
limit: 5,
offset: 0,
},
};Nested Grouping
Groups can be nested to create multi-level aggregations:
// Group by age, compute tier, then regroup by tier
const query = {
type: "matches",
group: {
by: "ageGroup",
select: [
"ageGroup",
{
tier: {
$if: {
if: { $gte: [{ $get: "ageGroup" }, 12] },
then: "senior",
else: "junior",
},
},
},
],
group: {
by: "tier",
aggregates: { count: { $count: null } },
},
},
};
// Returns: [
// { tier: "junior", count: 1 },
// { tier: "senior", count: 2 }
// ]Expressions
SpectraGraph queries support expressions for computed fields and conditional logic. These expressions are provided by the json-expressions library, which offers a comprehensive set of operators for data transformation and filtering.
Expression Engines
SpectraGraph uses focused expression engines to provide different capabilities for different query contexts:
- SELECT Engine (
defaultSelectEngine): Full expression capabilities including filtering, aggregations, transformations, and computed fields - WHERE Engine (
defaultWhereEngine): Filtering-only operations for performance and security - excludes expensive aggregation operations
This architecture ensures that resource-intensive operations are only available where appropriate, preventing performance issues from expensive aggregations in WHERE clauses.
What are Expressions?
Expressions are JSON objects that describe computations to be performed on data. Each expression has a single key that identifies the operation (prefixed with $) and a value that provides the parameters:
Simple comparison:
{ "$gt": 11 }Logical expressions:
{ "$and": [{ "$gte": 2020 }, { "$eq": "active" }] }Conditional logic:
{
"$if": {
"if": { "$eq": "Phoenix" },
"then": { "$get": "name" },
"else": "Away Team"
}
}Basic Expression Examples
Check if player is eligible for age group:
{ "$gte": 11 }Get team's home field:
{ "$get": "homeField.name" }Calculate total match cost:
{ "$add": [150, 25] }Sum all team scores:
{ "$sum": [2, 1, 4, 0] }Filter teams by city:
{ "$filter": { "$eq": "Phoenix" } }Get all team names:
{ "$map": { "$get": "name" } }Join team cities:
{ "$join": " vs " }Available Operations
The json-expressions library provides comprehensive operations including:
- Core:
$compose,$debug,$ensurePath,$get,$prop,$isPresent,$literal,$pipe - Conditionals:
$switch,$case,$if - Logic:
$and,$not,$or - Comparisons:
$eq,$gt,$gte,$lt,$lte,$in,$nin,$ne - Pattern matching:
$matchesRegex,$matchesLike,$matchesGlob - Aggregation:
$count,$max,$min,$mean,$median,$mode,$sum - Array operations:
$all,$any,$append,$filter,$find,$flatMap,$join,$map,$prepend,$reverse - Math:
$add,$subtract,$multiply,$divide,$modulo - Generative:
$random,$uuid - Temporal:
$nowLocal,$nowUTC,$timestamp
For complete documentation of all available operations, see the json-expressions documentation.
Custom Expressions
Available expressions can be extended. Most stores accept selectEngine and whereEngine parameters. See the json-expressions documentation for how to create and use custom expressions.
Using Expressions in Queries
Expressions can be used in both where clauses for filtering and select clauses for computed fields:
// Query teams with expressions in where clause
const teamQuery = {
type: "teams",
where: {
$and: [
{ founded: { $gte: 2000 } }, // founded >= 2000
{ founded: { $lte: 2020 } }, // founded <= 2020
{ active: { $eq: true } }, // active === true
],
},
select: {
name: "name",
city: "city",
seasonsActive: { $subtract: [2024, { $get: "founded" }] }, // Computed field
division: "division",
},
};
// Complex conditional logic for match analysis
const matchQuery = {
type: "matches",
select: {
field: "field",
ageGroup: "ageGroup",
status: {
$case: {
value: { $get: "ageGroup" },
cases: [
{ when: { $lt: 12 }, then: "Youth League" },
{ when: { $lt: 16 }, then: "Junior League" },
{ when: { $gte: 16 }, then: "Senior League" },
],
default: "Unassigned",
},
},
isPlayoff: {
$and: [
{ $gte: 10 }, // week >= 10
{ $eq: "important" }, // importance === "important"
],
},
},
};API Reference
Schema Functions
ensureValidSchema(schema, options?)
Validates that a schema conforms to the SpectraGraph schema specification.
Parameters:
schema(unknown) - The schema to validateoptions.validator(Ajv, optional) - Custom AJV validator instance
Throws: Error if schema is invalid
import { ensureValidSchema } from "@spectragraph/core";
ensureValidSchema(mySchema); // Throws if invalidGraph Functions
createEmptyGraph(schema, options?)
Creates an empty graph structure based on a schema.
Parameters:
schema(Schema) - The schema to base the graph onoptions.skipValidation(boolean, optional) - Skip schema validation for performance
Returns: Empty graph with resource type placeholders
import { createEmptyGraph } from "@spectragraph/core";
const emptyGraph = createEmptyGraph(schema);
// Returns: { teams: {}, matches: {} }
// Skip validation for performance
const emptyGraph = createEmptyGraph(schema, { skipValidation: true });linkInverses(schema, graph)
Links inverse relationships in a graph, filling in missing relationship data where possible. Queries will not work without relationships going in the direction of the query, so it's important to use this if you're constructing a graph from semi-connected pieces. This is commonly used with mergeGraphsDeep.
Parameters:
schema(Schema) - The schema defining relationshipsgraph(Graph) - The graph to link inverses in
Returns: New graph with inverse relationships linked
import {
linkInverses,
buildNormalResource,
createEmptyGraph,
} from "@spectragraph/core";
// Example: Multi-API store pattern
const graph = createEmptyGraph(schema);
// Resources built with includeRelationships: false have undefined relationships
const team = buildNormalResource(schema, "teams", apiData, {
includeRelationships: false,
});
graph.teams["1"] = team;
// Link relationships from their inverses
const linkedGraph = linkInverses(schema, graph);mergeGraphs(left, right)
Merges two graphs together by combining resource collections. Right graph takes precedence for resources with conflicting IDs.
Parameters:
left(Graph) - The left graphright(Graph) - The right graph (takes precedence for conflicts)
Returns: Merged graph with combined resource collections
import { mergeGraphs } from "@spectragraph/core";
const combined = mergeGraphs(graph1, graph2);mergeGraphsDeep(left, right)
Merges two graphs together, merging individual resources with matching IDs using mergeNormalResources().
Parameters:
left(Graph) - The left graphright(Graph) - The right graph
Returns: Merged graph with resources intelligently merged
import { mergeGraphsDeep } from "@spectragraph/core";
const combined = mergeGraphsDeep(graph1, graph2);Query Functions
ensureValidQuery(schema, query, options?)
Validates that a query is valid against a schema.
Parameters:
schema(Schema) - The schema to validate againstquery(RootQuery) - The query to validateoptions.selectEngine(SelectExpressionEngine, optional) - Expression engine for SELECT clausesoptions.whereEngine(WhereExpressionEngine, optional) - Expression engine for WHERE clauses
Throws: Error if query is invalid
import { ensureValidQuery } from "@spectragraph/core";
ensureValidQuery(schema, query); // Uses default enginesnormalizeQuery(schema, rootQuery, options?)
Normalizes a query by expanding shorthand syntax and ensuring consistent structure.
Parameters:
schema(Schema) - The schema objectrootQuery(RootQuery) - The query to normalizeoptions.selectEngine(SelectExpressionEngine, optional) - Expression engine for SELECT clausesoptions.whereEngine(WhereExpressionEngine, optional) - Expression engine for WHERE clauses
Returns: Normalized query with expanded selections and consistent structure
import { normalizeQuery } from "@spectragraph/core";
const normalized = normalizeQuery(schema, {
type: "users",
select: "*", // Expands to all attributes
});getQueryExtentByClause(schema, query)
Analyzes a query to determine which schema attributes and relationships are referenced by each query clause (select, where, order, group) separately.
This enables fine-grained optimization by store implementations where different clauses have different performance or security characteristics.
Parameters:
schema(Schema) - The schema defining resource types and relationshipsquery(RootQuery) - The query object to analyze
Returns: Object with separate extents for each clause:
{
select: QueryExtent,
where: QueryExtent,
order: QueryExtent,
group: QueryExtent
}Where QueryExtent has the structure:
{
attributes: string[],
relationships: { [relationshipName: string]: QueryExtent }
}Use Cases:
- SQL query building: WHERE clause paths → JOIN/WHERE, SELECT paths → SELECT columns, ORDER paths → ORDER BY
- Access control: Apply different permissions to filter fields vs display fields
- Incremental fetching: Fetch filtering data first, then display data for filtered results
- Query optimization: Identify which fields need indexes (ORDER/WHERE) vs which are just displayed
import { getQueryExtentByClause } from "@spectragraph/core";
const extents = getQueryExtentByClause(schema, {
type: "bears",
select: ["name", { home: ["caringMeter"] }],
where: { furColor: "brown" },
order: { yearIntroduced: "asc" },
});
// Returns:
// {
// select: {
// attributes: ["name"],
// relationships: { home: { attributes: ["caringMeter"], relationships: {} } }
// },
// where: {
// attributes: ["furColor"],
// relationships: {}
// },
// order: {
// attributes: ["yearIntroduced"],
// relationships: {}
// },
// group: {
// attributes: [],
// relationships: {}
// }
// }
// Use in SQL store:
const whereColumns = extents.where.attributes; // ["furColor"]
const selectColumns = extents.select.attributes; // ["name"]
const orderColumns = extents.order.attributes; // ["yearIntroduced"]
const joins = Object.keys(extents.select.relationships); // ["home"]getFullQueryExtent(schema, query)
Analyzes a query to determine the complete set of schema attributes and relationships referenced across all query clauses (select, where, order, group), merged into a single extent.
This is useful for store implementations that need to know all data requirements upfront.
Parameters:
schema(Schema) - The schema defining resource types and relationshipsquery(RootQuery) - The query object to analyze
Returns: Single QueryExtent object with all referenced attributes and relationships merged
Use Cases:
- Prefetching: Fetch all needed data in one round trip
- Simple permission checks: Does user have access to ANY of these fields?
- Caching: Cache key based on all accessed data
- Multi-API coordination: Coordinate fetches across multiple backend stores
import { getFullQueryExtent } from "@spectragraph/core";
const extent = getFullQueryExtent(schema, {
type: "bears",
select: ["name", { home: ["caringMeter"] }],
where: { furColor: "brown" },
order: { yearIntroduced: "asc" },
});
// Returns:
// {
// attributes: ["name", "furColor", "yearIntroduced"],
// relationships: {
// home: { attributes: ["caringMeter"], relationships: {} }
// }
// }
// Use in multi-API store:
const allAttributes = extent.attributes; // ["name", "furColor", "yearIntroduced"]
const allRelationships = Object.keys(extent.relationships); // ["home"]
prefetchData(allAttributes, allRelationships);queryGraph(schema, query, graph, options?)
Executes a query against a graph directly (convenience function).
Parameters:
schema(Schema) - The schema defining relationshipsquery(RootQuery) - The query to executegraph(Graph) - The graph to queryoptions.selectEngine(SelectExpressionEngine, optional) - Expression engine for SELECT clausesoptions.whereEngine(WhereExpressionEngine, optional) - Expression engine for WHERE clauses
Returns: Query results matching the query structure
Query Options:
id- Fetch a single resource by ID (returns single object or null)ids- Fetch multiple specific resources by IDs (returns array, mutually exclusive withid)where- Filter resources by conditionsorder- Sort resultslimit/offset- Paginate results
import { queryGraph } from "@spectragraph/core";
const results = queryGraph(
schema,
{
type: "teams",
id: "team-1",
select: {
name: "name",
homeMatches: { select: ["field"] },
},
},
graph,
);Resource Functions
normalizeResource(schema, resourceType, resource)
Converts a flat resource object into SpectraGraph's normalized format.
Parameters:
schema(Schema) - The schema defining the resource structureresourceType(string) - The type of resource being normalizedresource(object) - The flat resource data
Returns: Normalized resource with separated attributes and relationships
import { normalizeResource } from "@spectragraph/core";
const flatTeam = {
id: "team-1",
name: "Tempe Tidal Wave",
city: "Tempe",
homeMatches: ["match-1", "match-3"], // IDs or objects
awayMatches: ["match-2", "match-4"],
};
const normalized = normalizeResource(schema, "teams", flatTeam);
// Returns normalized resource with attributes and relationships separatedbuildResource(schema, resourceType, partialResource, options?)
Creates a flat resource with schema defaults applied to attributes. Returns a flat object with attributes and relationship IDs at the root level.
Parameters:
schema(Schema) - The schema defining the resource structureresourceType(string) - The type of resource to buildpartialResource(object) - The partial resource dataoptions(object, optional) - Configuration optionsincludeRelationships(boolean) - Whether to include default values for relationships not present in partialResource. Whenfalse, only relationships explicitly provided in partialResource will be included. Defaults totrue.
Returns: Flat resource with attribute defaults applied
import { buildResource } from "@spectragraph/core";
// Default behavior - includes relationship defaults
const team = buildResource(schema, "teams", {
name: "Phoenix Fire",
city: "Phoenix",
});
// Returns: { name: "Phoenix Fire", city: "Phoenix", homeMatches: [], awayMatches: [], ...other defaults }
// For Multi-API stores - omit relationship defaults
const teamFromAPI = buildResource(
schema,
"teams",
{
name: "Phoenix Fire",
city: "Phoenix",
},
{ includeRelationships: false },
);
// Returns: { name: "Phoenix Fire", city: "Phoenix", ...attribute defaults only }buildNormalResource(schema, resourceType, partialResource, options?)
Creates a normalized resource with schema defaults applied to attributes. Returns a resource with the type/id/attributes/relationships structure.
Parameters:
schema(Schema) - The schema defining the resource structureresourceType(string) - The type of resource to buildpartialResource(object) - The partial resource dataoptions(object, optional) - Configuration optionsincludeRelationships(boolean) - Whether to include default values for relationships not present in partialResource. Whenfalse, only relationships explicitly provided in partialResource will be included. This is useful when relationships will be linked later vialinkInverses(). Defaults totrue.
Returns: Normalized resource with attribute defaults applied
import { buildNormalResource } from "@spectragraph/core";
// Default behavior - includes relationship defaults
const team = buildNormalResource(schema, "teams", {
name: "Phoenix Fire",
city: "Phoenix",
});
// Returns:
// {
// type: "teams",
// id: undefined,
// attributes: { name: "Phoenix Fire", city: "Phoenix", ...defaults },
// relationships: { homeMatches: [], awayMatches: [] }
// }
// For Multi-API stores - omit relationship defaults
const teamFromAPI = buildNormalResource(
schema,
"teams",
{
name: "Phoenix Fire",
city: "Phoenix",
},
{ includeRelationships: false },
);
// teamFromAPI.relationships = {}
// Explicit relationships are preserved even with includeRelationships: false
const teamWithExplicitRel = buildNormalResource(
schema,
"teams",
{
name: "Phoenix Fire",
homeMatches: [{ type: "matches", id: "match-1" }],
},
{ includeRelationships: false },
);
// teamWithExplicitRel.relationships = { homeMatches: [{ type: "matches", id: "match-1" }] }mergeNormalResources(left, right)
Merges two partial resources of the same type, combining their attributes and relationships. The right resource takes precedence for conflicting attribute keys.
Parameters:
left(PartialNormalResource) - The first resource to mergeright(PartialNormalResource) - The second resource to merge
Returns: Merged PartialNormalResource with combined attributes and relationships
Throws: Error if resources are of different types or have conflicting IDs
import { mergeNormalResources } from "@spectragraph/core";
const existing = {
type: "teams",
id: "team-1",
attributes: { name: "Old Name", founded: 2000 },
relationships: { homeField: { type: "fields", id: "field-1" } },
};
const update = {
type: "teams",
id: "team-1",
attributes: { name: "New Name", active: true }, // name will override
};
const merged = mergeNormalResources(existing, update);
// Returns:
// {
// type: "teams",
// id: "team-1",
// attributes: { name: "New Name", founded: 2000, active: true },
// relationships: { homeField: { type: "fields", id: "field-1" } }
// }createGraphFromResources(schema, rootResourceType, rootResources)
Creates a complete graph from an array of resource objects, recursively processing nested relationships. This function is particularly useful for handling nested resources.
Parameters:
schema(Schema) - The schema defining the resource structurerootResourceType(string) - The root resource type to processrootResources(Array) - Array of flat resource objects to convert
Returns: Complete graph with all discoverable resources from the input data
import { createGraphFromResources } from "@spectragraph/core";
const flatTeam = {
id: "team-1",
name: "Arizona Bay FC",
city: "Phoenix",
homeMatches: [
{
id: "match-1",
field: "Phoenix Park 1",
ageGroup: 11,
awayTeam: "team-2",
},
],
};
const graph = createGraphFromResources(schema, "teams", [flatTeam]);
// Returns a graph containing teams and matches resources
// with proper normalization and relationship structureError Classes
SpectraGraph Core provides specialized error classes to help stores and applications handle unsupported functionality gracefully.
ExpressionNotSupportedError
Error thrown when a store does not support a particular expression. This allows stores to explicitly declare unsupported functionality and enables tests to handle these cases gracefully.
Constructor Parameters:
expression(string) - The expression that is not supported (e.g., "$matchesRegex")storeName(string) - The name of the store that doesn't support the expressionreason(string, optional) - Optional reason why the expression is not supported
Properties:
name- "ExpressionNotSupportedError"expression- The unsupported expression namestoreName- The store namereason- Optional reason (if provided)
import { ExpressionNotSupportedError } from "@spectragraph/core";
// In a store implementation
if (!this.supportsRegex) {
throw new ExpressionNotSupportedError(
"$matchesRegex",
"simple-store",
"Regex operations require additional dependencies",
);
}
// In application code
try {
const results = await store.query(queryWithRegex);
} catch (error) {
if (error instanceof ExpressionNotSupportedError) {
console.log(
`Expression ${error.expression} not supported by ${error.storeName}`,
);
// Handle gracefully, perhaps with a fallback query
}
}StoreOperationNotSupportedError
Error thrown when a store does not support a particular operation. This allows stores to explicitly declare unsupported functionality and enables tests to handle these cases gracefully. This is particularly useful for readonly stores or stores with limited write capabilities.
Constructor Parameters:
operation(string) - The store operation that is not supported (e.g., "create", "update", "delete")storeName(string) - The name of the store that doesn't support the operationreason(string, optional) - Optional reason why the operation is not supported
Properties:
name- "StoreOperationNotSupportedError"operation- The unsupported operation namestoreName- The store namereason- Optional reason (if provided)
import { StoreOperationNotSupportedError } from "@spectragraph/core";
// In a readonly store implementation
class ReadOnlyStore {
create(resource) {
throw new StoreOperationNotSupportedError(
"create",
"readonly-store",
"This store only supports read operations",
);
}
update(resource) {
throw new StoreOperationNotSupportedError("update", "readonly-store");
}
delete(resource) {
throw new StoreOperationNotSupportedError("delete", "readonly-store");
}
}
// In application code
try {
await store.create(newResource);
} catch (error) {
if (error instanceof StoreOperationNotSupportedError) {
console.log(
`Operation ${error.operation} not supported by ${error.storeName}`,
);
// Handle gracefully, perhaps by using a different store or informing the user
}
}Validation Functions
validateSchema(schema, options?)
Validates that a schema conforms to the SpectraGraph schema specification.
Parameters:
schema(unknown) - The schema to validateoptions.validator(Ajv, optional) - Custom AJV validator instance
Returns: Array of validation errors (empty if valid)
import { validateSchema } from "@spectragraph/core";
const errors = validateSchema(mySchema);
if (errors.length > 0) {
console.error("Schema validation failed:", errors);
}validateQuery(schema, query, options?)
Validates that a query is valid against a schema.
Parameters:
schema(Schema) - The schema to validate againstquery(RootQuery) - The query to validateoptions.selectEngine(SelectExpressionEngine, optional) - Expression engine for SELECT clausesoptions.whereEngine(WhereExpressionEngine, optional) - Expression engine for WHERE clauses
Returns: Array of validation errors (empty if valid)
import { validateQuery } from "@spectragraph/core";
const errors = validateQuery(schema, query);
if (errors.length > 0) {
console.error("Query validation failed:", errors);
}createValidator(options?)
Creates a new AJV validator instance configured for SpectraGraph.
Parameters:
options.ajvSchemas(Array, optional) - Additional schemas to add
Returns: Configured AJV validator
import { createValidator } from "@spectragraph/core";
const validator = createValidator({
ajvSchemas: [myCustomSchema],
});defaultValidator
The default AJV validator instance used by SpectraGraph.
import { defaultValidator } from "@spectragraph/core";
// Use the default validator directly
const isValid = defaultValidator.validate(schema, data);defaultSelectEngine
The default expression engine for SELECT clauses, providing full expression capabilities including filtering, aggregations, transformations, and computed fields.
import { defaultSelectEngine } from "@spectragraph/core";
// Use in custom query normalization
const normalizedQuery = normalizeQuery(schema, query, {
selectEngine: defaultSelectEngine,
});defaultWhereEngine
The default expression engine for WHERE clauses, providing filtering-only operations for performance and security. Excludes expensive aggregation operations.
import { defaultWhereEngine } from "@spectragraph/core";
// Use in custom query validation
const errors = validateQuery(schema, query, {
whereEngine: defaultWhereEngine,
});validateCreateResource(schema, resource, options?)
Validates a resource for creation operations.
Parameters:
schema(Schema) - The schema to validate againstresource(CreateResource) - The resource to validateoptions.validator(Ajv, optional) - Custom validator instance
Returns: Array of validation errors (empty if valid)
import { validateCreateResource } from "@spectragraph/core";
const errors = validateCreateResource(schema, {
type: "teams",
attributes: { name: "Scottsdale Surf", city: "Scottsdale" },
});validateUpdateResource(schema, resource, options?)
Validates a resource for update operations.
Parameters:
schema(Schema) - The schema to validate againstresource(UpdateResource) - The resource to validateoptions.validator(Ajv, optional) - Custom validator instance
Returns: Array of validation errors (empty if valid)
validateDeleteResource(schema, resource)
Validates a resource for delete operations.
Parameters:
schema(Schema) - The schema to validate againstresource(DeleteResource) - The resource reference to validate
Returns: Array of validation errors (empty if valid)
validateMergeResource(schema, resource, options?)
Validates a resource tree that will be merged into a graph.
Parameters:
schema(Schema) - The schema to validate againstresource(unknown) - The resource tree to validateoptions.validator(Ajv, optional) - Custom validator instance
Returns: Array of validation errors (empty if valid)
validateQueryResult(schema, rootQuery, result, options?)
Validates that query results match the expected structure.
Parameters:
schema(Schema) - The schema to validate againstrootQuery(RootQuery) - The original queryresult(unknown) - The query results to validateoptions.selectEngine(SelectExpressionEngine, optional) - Expression engine for SELECT clausesoptions.validator(Ajv, optional) - Custom validator instance
Returns: Array of validation errors (empty if valid)
import { validateQueryResult } from "@spectragraph/core";
const errors = validateQueryResult(schema, query, results);
if (errors.length > 0) {
console.error("Query result validation failed:", errors);
}Ensure Functions
The following functions provide throwing variants of the validation functions above, useful for fail-fast scenarios:
ensureValidCreateResource(schema, resource, options?)
Validates a resource for creation operations. Throws on validation failure.
Parameters:
schema(Schema) - The schema to validate againstresource(CreateResource) - The resource to validateoptions.validator(Ajv, optional) - Custom validator instance
Throws: Error if resource is invalid
import { ensureValidCreateResource } from "@spectragraph/core";
ensureValidCreateResource(schema, newResource); // Throws if invalidensureValidUpdateResource(schema, resource, options?)
Validates a resource for update operations. Throws on validation failure.
Parameters:
schema(Schema) - The schema to validate againstresource(UpdateResource) - The resource to validateoptions.validator(Ajv, optional) - Custom validator instance
Throws: Error if resource is invalid
ensureValidDeleteResource(schema, resource)
Validates a resource for delete operations. Throws on validation failure.
Parameters:
schema(Schema) - The schema to validate againstresource(DeleteResource) - The resource reference to validate
Throws: Error if resource is invalid
ensureValidMergeResource(schema, resource, options?)
Validates a resource tree that will be merged into a graph. Throws on validation failure.
Parameters:
schema(Schema) - The schema to validate againstresource(unknown) - The resource tree to validateoptions.validator(Ajv, optional) - Custom validator instance
Throws: Error if resource is invalid
ensureValidQueryResult(schema, rootQuery, result, options?)
Validates that query results match the expected structure. Throws on validation failure.
Parameters:
schema(Schema) - The schema to validate againstrootQuery(RootQuery) - The original queryresult(unknown) - The query results to validateoptions.selectEngine(SelectExpressionEngine, optional) - Expression engine for SELECT clausesoptions.validator(Ajv, optional) - Custom validator instance
Throws: Error if results don't match expected structure
Examples
Basic Usage
import {
ensureValidSchema,
createEmptyGraph,
queryGraph,
normalizeResource,
} from "@spectragraph/core";
// 1. Define your schema
const schema = {
resources: {
teams: {
attributes: {
id: { type: "string" },
name: { type: "string" },
},
relationships: {
homeMatches: {
type: "matches",
cardinality: "many",
inverse: "homeTeam",
},
awayMatches: {
type: "matches",
cardinality: "many",
inverse: "awayTeam",
},
},
},
},
};
// 2. Validate the schema
ensureValidSchema(schema);
// 3. Create and populate a graph
const graph = createEmptyGraph(schema);
const teamData = { id: "1", name: "Arizona Bay FC" };
const normalizedTeam = normalizeResource(schema, "teams", teamData);
graph.teams["1"] = normalizedTeam;
// 4. Query the data
const results = queryGraph(
schema,
{
type: "teams",
select: ["name"],
},
graph,
);
console.log(results); // [{ name: "Arizona Bay FC" }]Working with Relationships
const schema = {
resources: {
teams: {
attributes: {
id: { type: "string" },
name: { type: "string" },
},
relationships: {
homeMatches: {
type: "matches",
cardinality: "many",
inverse: "homeTeam",
},
awayMatches: {
type: "matches",
cardinality: "many",
inverse: "awayTeam",
},
},
},
matches: {
attributes: {
id: { type: "string" },
field: { type: "string" },
},
relationships: {
homeTeam: { type: "teams", cardinality: "one", inverse: "homeMatches" },
awayTeam: { type: "teams", cardinality: "one", inverse: "awayMatches" },
},
},
},
};
// Query with relationship traversal
const results = queryGraph(
schema,
{
type: "teams",
select: {
name: "name",
homeMatches: {
select: ["field", "awayTeam"],
},
},
},
graph,
);Data Validation
import {
validateCreateResource,
ensureValidCreateResource,
} from "@spectragraph/core";
const newTeam = {
type: "teams",
attributes: {
name: "Mesa Mariners",
city: "Mesa",
},
};
// Option 1: Check errors manually
const errors = validateCreateResource(schema, newTeam);
if (errors.length > 0) {
console.error("Validation failed:", errors);
}
// Option 2: Throw on validation failure
try {
ensureValidCreateResource(schema, newTeam);
console.log("Resource is valid!");
} catch (error) {
console.error("Validation failed:", error.message);
}TypeScript Support
SpectraGraph Core includes comprehensive TypeScript definitions. Import types as needed:
import type {
Schema,
Graph,
Query,
RootQuery,
NormalResource,
} from "@spectragraph/core";
const schema: Schema = {
resources: {
teams: {
attributes: {
id: { type: "string" },
name: { type: "string" },
},
relationships: {
homeMatches: {
type: "matches",
cardinality: "many",
inverse: "homeTeam",
},
awayMatches: {
type: "matches",
cardinality: "many",
inverse: "awayTeam",
},
},
},
},
};Advanced Usage
Custom Validation
import {
createValidator,
validateSchema,
ensureValidSchema,
} from "@spectragraph/core";
const customValidator = createValidator({
ajvSchemas: [myCustomSchema],
});
// Option 1: Check errors manually
const errors = validateSchema(schema, { validator: customValidator });
if (errors.length > 0) {
console.error("Schema validation failed:", errors);
}
// Option 2: Throw on validation failure
ensureValidSchema(schema, { validator: customValidator });Performance Optimization
For better performance with large datasets:
- Link inverses once with
linkInversesrather than on each query - Validate schemas once during application startup
Related Packages
@spectragraph/memory-store- In-memory data store implementation@spectragraph/postgres-store- PostgreSQL backend@spectragraph/jsonapi-store- JSON:API client store
