Releases: Semantu/linked
v2.4.1
Patch Changes
-
#60
ec239d3Thanks @flyon! - Fix QueryBuilder.toJSON() to serialize where, orderBy, minus, and preload clauses that were previously silently dropped during JSON round-trips -
#64
a8d9ad9Thanks @flyon! - Refine SPARQL select lowering so top-level null-rejecting filters emit required triples instead of redundantOPTIONALbindings. Queries likePerson.select().where((p) => p.name.equals('Semmy'))now lower to a required?a0 <name> ?a0_nametriple, while cases that still need nullable behavior such asp.name.equals('Jinx').or(p.hobby.equals('Jogging'))remain optional.This change does not add new DSL APIs, but it does change the generated SPARQL shape for some outer
where()clauses to better match hand-written intent. Inline traversal.where(...),EXISTSfilters, and aggregateHAVINGpaths keep their previous behavior.See
documentation/sparql-algebra.mdfor the updated lowering rules and examples.
v2.4.0
Minor Changes
-
#53
44da872Thanks @flyon! - ### New:.none()collection quantifierAdded
.none()onQueryShapeSetfor filtering where no elements match a condition:// "People who have NO friends that play chess" Person.select((p) => p.name).where((p) => p.friends.none((f) => f.hobby.equals("Chess")) );
Generates
FILTER(NOT EXISTS { ... })in SPARQL. Equivalent to.some(fn).not().Changed:
.equals()now returnsExpressionNode(wasEvaluation).equals()on query proxies now returnsExpressionNodeinstead ofEvaluation, enabling.not()chaining:// Now works — .equals() chains with .not() .where(p => p.name.equals('Alice').not()) .where(p => Expr.not(p.name.equals('Alice')))
Changed:
.some()/.every()now returnExistsCondition(wasSetEvaluation).some()and.every()on collections now returnExistsConditionwhich supports.not():.where(p => p.friends.some(f => f.name.equals('Alice')).not()) // same as .none()
Breaking:
Evaluationclass removedThe
Evaluationclass and related types (SetEvaluation,WhereMethods,WhereEvaluationPath) have been removed. Code that imported or depended on these types must migrate toExpressionNode/ExistsCondition. TheWhereClausetype now acceptsExpressionNode | ExistsCondition | callback.New exports
ExistsCondition— from@_linked/core/expressions/ExpressionNodeisExistsCondition()— type guard for ExistsCondition
v2.2.3
Patch Changes
- #42
1b4d114Thanks @flyon! - AddPendingQueryContextfor lazy query context resolution.getQueryContext()now returns a live reference with a lazy.idgetter instead ofnullwhen the context hasn't been set yet.QueryBuilder.for()acceptsPendingQueryContextandnull. NewhasPendingContext()method.setQueryContext(name, null)now properly clears the entry. Test Fuseki port changed to 3939;globalSetup/globalTeardownadded for reliable Fuseki auto-start.
v2.2.1
v2.2.0
Patch Changes
-
#34
e2ae4a2Thanks @flyon! - ### SHACL property path supportProperty decorators now accept full SPARQL property path syntax:
@literalProperty({path: 'foaf:knows/foaf:name'}) // sequence @literalProperty({path: '<http://ex.org/a>|<http://ex.org/b>'}) // alternative @literalProperty({path: '^foaf:knows'}) // inverse @literalProperty({path: 'foaf:knows*'}) // zeroOrMore
New exports from
src/paths/:PathExpr,PathRef— AST types for property pathsparsePropertyPath(input): PathExpr— parser for SPARQL property path stringsnormalizePropertyPath(input): PathExpr— normalizes any input form to canonical ASTpathExprToSparql(expr): string— renders PathExpr to SPARQL syntaxserializePathToSHACL(expr): SHACLPathResult— serializes to SHACL RDF triples
PropertyShape.pathis now typed asPathExpr(was opaque). Complex paths flow through the full IR pipeline and emit correct SPARQL property path syntax in generated queries.Strict prefix resolution in query API
QueryBuilder.for()and.forAll()now throw on unregistered prefixes instead of silently passing through. New export:resolveUriOrThrow(str): string— strict prefix resolution (throws on unknown prefix)
SHACL constraint field fixes
hasValueandinconfig fields now correctly handle literal values (string,number,boolean) — previously all values were wrapped as IRI nodeslessThanandlessThanOrEqualsconfig fields are now wired intocreatePropertyShapeand exposed viagetResult()- New
PropertyShapeResultinterface provides typed access togetResult()output
v2.0.1
Patch Changes
-
#27
d3c1e91Thanks @flyon! - Add MINUS support on QueryBuilder with multiple call styles:.minus(Shape)— exclude by shape type.minus(p => p.prop.equals(val))— exclude by condition.minus(p => p.prop)— exclude by property existence.minus(p => [p.prop1, p.nested.prop2])— exclude by multi-property existence with nested path support
Add bulk delete operations:
Shape.deleteAll()/DeleteBuilder.from(Shape).all()— delete all instances with schema-aware blank node cleanupShape.deleteWhere(fn)/DeleteBuilder.from(Shape).where(fn)— conditional delete
Add conditional update operations:
.update(data).where(fn)— update matching instances.update(data).forAll()— update all instances
API cleanup:
- Deprecate
sortBy()in favor oforderBy() - Remove
DeleteBuilder.for()— useDeleteBuilder.from(shape, ids)instead - Require
dataparameter inShape.update(data)
v2.0.0
Major Changes
-
#23
d2d1ecaThanks @flyon! - ## Breaking ChangesShape.select()andShape.update()no longer accept an ID as the first argumentUse
.for(id)to target a specific entity instead.Select:
// Before const result = await Person.select({ id: "..." }, (p) => p.name); // After const result = await Person.select((p) => p.name).for({ id: "..." });
.for(id)unwraps the result type from array to single object, matching the old single-subject overload behavior.Update:
// Before const result = await Person.update({ id: "..." }, { name: "Alice" }); // After const result = await Person.update({ name: "Alice" }).for({ id: "..." });
Shape.selectAll(id)also no longer accepts an id — usePerson.selectAll().for(id).ShapeTyperenamed toShapeConstructorThe type alias for concrete Shape subclass constructors has been renamed. Update any imports or references:
// Before import type { ShapeType } from "@_linked/core/shapes/Shape"; // After import type { ShapeConstructor } from "@_linked/core/shapes/Shape";
QueryString,QueryNumber,QueryBoolean,QueryDateclasses removedThese have been consolidated into a single generic
QueryPrimitive<T>class. If you were usinginstanceofchecks against these classes, useinstanceof QueryPrimitiveinstead and check the value's type.Internal IR types removed
The following types and functions have been removed from
SelectQuery. These were internal pipeline types — if you were using them for custom store integrations, the replacement isFieldSetEntry[](available fromFieldSet):- Types:
SelectPath,QueryPath,CustomQueryObject,SubQueryPaths,ComponentQueryPath - Functions:
fieldSetToSelectPath(),entryToQueryPath() - Methods:
QueryBuilder.getQueryPaths(),BoundComponent.getComponentQueryPaths() RawSelectInput.selectfield renamed toRawSelectInput.entries(type changed fromSelectPathtoFieldSetEntry[])
getPackageShape()return type is now nullableReturns
ShapeConstructor | undefinedinstead oftypeof Shape. Code that didn't null-check the return value will now get TypeScript errors.New Features
.for(id)and.forAll(ids)chainingConsistent API for targeting entities across select and update operations:
// Single entity (result is unwrapped, not an array) await Person.select((p) => p.name).for({ id: "..." }); await Person.select((p) => p.name).for("https://..."); // Multiple specific entities await QueryBuilder.from(Person) .select((p) => p.name) .forAll([{ id: "..." }, { id: "..." }]); // All instances (default — no .for() needed) await Person.select((p) => p.name);
Dynamic Query Building with
QueryBuilderandFieldSetBuild queries programmatically at runtime — for CMS dashboards, API endpoints, configurable reports. See the Dynamic Query Building section in the README for full documentation and examples.
Key capabilities:
QueryBuilder.from(Person)orQueryBuilder.from('https://schema.org/Person')— fluent, chainable, immutable query constructionFieldSet.for(Person, ['name', 'knows'])— composable field selections with.add(),.remove(),.pick(),FieldSet.merge()FieldSet.all(Person, {depth: 2})— select all decorated properties with optional depth- JSON serialization:
query.toJSON()/QueryBuilder.fromJSON(json)andfieldSet.toJSON()/FieldSet.fromJSON(json) - All builders are
PromiseLike—awaitthem directly or call.build()to inspect the IR
Mutation Builders
CreateBuilder,UpdateBuilder, andDeleteBuilderprovide the programmatic equivalent ofPerson.create(),Person.update(), andPerson.delete(), accepting Shape classes or shape IRI strings. See the Mutation Builders section in the README.PropertyPathexportedThe
PropertyPathvalue object is now a public export — a type-safe representation of a sequence of property traversals through a shape graph.import { PropertyPath, walkPropertyPath } from "@_linked/core";
ShapeConstructor<S>typeNew concrete constructor type for Shape subclasses. Eliminates ~30
as anycasts across the codebase and provides better type safety at runtime boundaries (builder.from()methods, Shape static methods). - Types:
v1.3.0
Minor Changes
-
#20
33e9fb0Thanks @flyon! - Breaking:QueryParserhas been removed. If you importedQueryParserdirectly, replace withgetQueryDispatch()from@_linked/core/queries/queryDispatch. The Shape DSL (Shape.select(),.create(),.update(),.delete()) andSelectQuery.exec()are unchanged.New:
getQueryDispatch()andsetQueryDispatch()are now exported, allowing custom query dispatch implementations (e.g. for testing or alternative storage backends) without subclassingLinkedStorage.
v1.2.1
Patch Changes
-
#17
0654780Thanks @flyon! - Preserve nested array sub-select branches in canonical IR sobuild()emits complete traversals, projection fields, andresultMapentries for nested selections.This fixes cases where nested branches present in
toRawInput().selectwere dropped during desugar/lowering (for example nestedfriends.select([name, hobby])branches under another sub-select).Also adds regression coverage for desugar preservation, IR lowering completeness, and updated SPARQL golden output for nested query fixtures.
v1.2.0
Minor Changes
-
#9
381067bThanks @flyon! - Replaced internal query representation with a canonical backend-agnostic IR AST.SelectQuery,CreateQuery,UpdateQuery, andDeleteQueryare now typed IR objects withkinddiscriminators, compact shape/property ID references, and expression trees — replacing the previous ad-hoc nested arrays. The public Shape DSL is unchanged; what changed is whatIQuadStoreimplementations receive. Store result types (ResultRow,SelectResult,CreateResult,UpdateResult) are now exported. All factories exposebuild()as the primary method. Seedocumentation/intermediate-representation.mdfor the full IR reference and migration guidance. -
#14
b65e156Thanks @flyon! - Add SPARQL conversion layer — compiles Linked IR queries into executable SPARQL and maps results back to typed DSL objects.New exports from
@_linked/core/sparql:-
SparqlStore— abstract base class for SPARQL-backed stores. Extend it and implement two methods to connect any SPARQL 1.1 endpoint:import { SparqlStore } from "@_linked/core/sparql"; class MyStore extends SparqlStore { protected async executeSparqlSelect( sparql: string ): Promise<SparqlJsonResults> { /* ... */ } protected async executeSparqlUpdate(sparql: string): Promise<void> { /* ... */ } }
-
IR → SPARQL string convenience functions (full pipeline in one call):
selectToSparql(query, options?)— SelectQuery → SPARQL stringcreateToSparql(query, options?)— CreateQuery → SPARQL stringupdateToSparql(query, options?)— UpdateQuery → SPARQL stringdeleteToSparql(query, options?)— DeleteQuery → SPARQL string
-
IR → SPARQL algebra (for stores that want to inspect/optimize the algebra before serialization):
selectToAlgebra(query, options?)— returnsSparqlSelectPlancreateToAlgebra(query, options?)— returnsSparqlInsertDataPlanupdateToAlgebra(query, options?)— returnsSparqlDeleteInsertPlandeleteToAlgebra(query, options?)— returnsSparqlDeleteInsertPlan
-
Algebra → SPARQL string serialization:
selectPlanToSparql(plan, options?),insertDataPlanToSparql(plan, options?),deleteInsertPlanToSparql(plan, options?),deleteWherePlanToSparql(plan, options?)serializeAlgebraNode(node),serializeExpression(expr),serializeTerm(term)
-
Result mapping (SPARQL JSON results → typed DSL objects):
mapSparqlSelectResult(json, query)— handles flat/nested/aggregated results with XSD type coercionmapSparqlCreateResult(uri, query)— echoes created fields with generated URImapSparqlUpdateResult(query)— echoes updated fields
-
All algebra types re-exported:
SparqlTerm,SparqlTriple,SparqlAlgebraNode,SparqlExpression,SparqlSelectPlan,SparqlInsertDataPlan,SparqlDeleteInsertPlan,SparqlDeleteWherePlan,SparqlPlan,SparqlOptions, etc.
Bug fixes included:
- Fixed
isNodeReference()in MutationQuery.ts — nested creates with predefined IDs (e.g.,{id: '...', name: 'Bestie'}) now correctly insert entity data instead of only creating the link.
See SPARQL Algebra Layer docs for the full type reference, conversion rules, and store implementation guide.
-