@_linked/core 2.18.0-next.20260904105013 → 2.18.0-next.20260904105507
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -5
- package/lib/esm/index.d.ts +3 -0
- package/lib/esm/index.js +1 -0
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/interfaces/IDataset.d.ts +22 -0
- package/lib/esm/queries/AskBuilder.d.ts +62 -0
- package/lib/esm/queries/AskBuilder.js +178 -0
- package/lib/esm/queries/AskBuilder.js.map +1 -0
- package/lib/esm/queries/AskQuery.d.ts +80 -0
- package/lib/esm/queries/AskQuery.js +7 -0
- package/lib/esm/queries/AskQuery.js.map +1 -0
- package/lib/esm/queries/IntermediateRepresentation.d.ts +22 -1
- package/lib/esm/queries/QueryBuilder.d.ts +20 -10
- package/lib/esm/queries/QueryBuilder.js +56 -24
- package/lib/esm/queries/QueryBuilder.js.map +1 -1
- package/lib/esm/queries/fromJSON.d.ts +5 -3
- package/lib/esm/queries/fromJSON.js +4 -1
- package/lib/esm/queries/fromJSON.js.map +1 -1
- package/lib/esm/queries/lower.d.ts +9 -2
- package/lib/esm/queries/lower.js +44 -0
- package/lib/esm/queries/lower.js.map +1 -1
- package/lib/esm/queries/queryDispatch.d.ts +25 -0
- package/lib/esm/queries/queryDispatch.js +41 -0
- package/lib/esm/queries/queryDispatch.js.map +1 -1
- package/lib/esm/queries/wireVersion.d.ts +1 -1
- package/lib/esm/queries/wireVersion.js +1 -1
- package/lib/esm/shapes/Shape.d.ts +16 -3
- package/lib/esm/shapes/Shape.js +23 -4
- package/lib/esm/shapes/Shape.js.map +1 -1
- package/lib/esm/sparql/SparqlAlgebra.d.ts +12 -1
- package/lib/esm/sparql/SparqlDataset.d.ts +17 -3
- package/lib/esm/sparql/SparqlDataset.js +21 -2
- package/lib/esm/sparql/SparqlDataset.js.map +1 -1
- package/lib/esm/sparql/algebraToString.d.ts +8 -1
- package/lib/esm/sparql/algebraToString.js +19 -0
- package/lib/esm/sparql/algebraToString.js.map +1 -1
- package/lib/esm/sparql/index.d.ts +5 -5
- package/lib/esm/sparql/index.js +4 -4
- package/lib/esm/sparql/index.js.map +1 -1
- package/lib/esm/sparql/irToAlgebra.d.ts +22 -2
- package/lib/esm/sparql/irToAlgebra.js +91 -11
- package/lib/esm/sparql/irToAlgebra.js.map +1 -1
- package/lib/esm/sparql/resultMapping.d.ts +27 -0
- package/lib/esm/sparql/resultMapping.js +25 -0
- package/lib/esm/sparql/resultMapping.js.map +1 -1
- package/lib/esm/utils/LinkedStorage.d.ts +31 -0
- package/lib/esm/utils/LinkedStorage.js +64 -1
- package/lib/esm/utils/LinkedStorage.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,90 @@
|
|
|
4
4
|
|
|
5
5
|
### Minor Changes
|
|
6
6
|
|
|
7
|
+
- [#208](https://github.com/linked-cm/core/pull/208) [`085cfad`](https://github.com/linked-cm/core/commit/085cfadd4ebf0e584da9476a234b6ec47b261b5f) Thanks [@flyon](https://github.com/flyon)! - Ask queries — a first-class query kind whose answer is a boolean — plus two `rdf:type` / `sh:path`
|
|
8
|
+
resolution fixes it surfaced.
|
|
9
|
+
|
|
10
|
+
## Ask queries
|
|
11
|
+
|
|
12
|
+
`.exists()` is a shortcut for an ask, not a special case of select. An ask carries a **pattern and
|
|
13
|
+
nothing else** — no projection, sorting or pagination — at every layer: `AskBuilder`, `IRAskQuery`,
|
|
14
|
+
an `op: 'ask'` wire envelope, and `ASK WHERE { … }` in SPARQL.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
await Person.exists({ id }); // ASK { ?a0 a <PersonClass> . FILTER(?a0 = <id>) }
|
|
18
|
+
await Person.select()
|
|
19
|
+
.where((p) => p.name.equals("Semmy"))
|
|
20
|
+
.exists(); // ASK with the filter
|
|
21
|
+
await Shape.exists(uri); // ASK { <uri> ?p ?o }
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**`Shape.exists(uri)` on the base class asks whether a node exists at all** — no `rdf:type`
|
|
25
|
+
constraint, under any shape or none. (`Shape` is free to mean "anything": the shapes themselves are
|
|
26
|
+
described by `NodeShape` and `PropertyShape`.) A shapeless ask has no shape to route on, so
|
|
27
|
+
`LinkedStorage` asks **every** dataset it knows and ORs the answers, short-circuiting on the first
|
|
28
|
+
`true` — cheap precisely because the answers are booleans. Any router implementing `IDataset`
|
|
29
|
+
inherits that obligation: a shapeless ask means "anywhere I can reach", not "in my default store".
|
|
30
|
+
|
|
31
|
+
### Breaking: `IDataset.askQuery(query: AskQuery): Promise<boolean>` is required
|
|
32
|
+
|
|
33
|
+
Every store must implement it; `query.shape` is optional. **No code path in this package rewrites an
|
|
34
|
+
ask as a select** — a store with no boolean primitive decides for itself how to answer, and
|
|
35
|
+
defaulting that here would hide the choice. `askQuery` must resolve to a real boolean (a non-boolean
|
|
36
|
+
is rejected, not coerced, since a truthy value would read as "exists") and must reject on failure.
|
|
37
|
+
|
|
38
|
+
### Wire format `1.1`
|
|
39
|
+
|
|
40
|
+
An ask travels as its own envelope, discriminated by `op: 'ask'`:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{"v": "1.1", "op": "ask", "shape": "…/Person", "subject": "…/p1"}
|
|
44
|
+
{"v": "1.1", "op": "ask", "subject": "https://example.org/thing"}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Omitting `shape` **is** the shapeless form. There is no `fields`, `limit`, `offset`, `sortBy` or
|
|
48
|
+
`one`, so a receiver has nothing to validate or ignore. `fromJSON` routes `op: 'ask'` to an
|
|
49
|
+
`AskBuilder` and still throws `Unknown query op` on anything unrecognised, so an older peer fails
|
|
50
|
+
loud rather than reinterpreting the envelope as a select. Deploy receivers first.
|
|
51
|
+
|
|
52
|
+
New exports: `AskBuilder`, `isAskQuery`, and the `AskQuery` / `AskQueryJSON` / `RawAskInput` /
|
|
53
|
+
`AskSpec` types. `lower()` gains an ask overload returning `IRAskQuery`; `askToAlgebra` /
|
|
54
|
+
`askToSparql` / `askPlanToSparql` / `SparqlAskPlan` / `mapSparqlAskResult` are the SPARQL arm.
|
|
55
|
+
|
|
56
|
+
## Breaking: a shape must declare a `targetClass`
|
|
57
|
+
|
|
58
|
+
A query or mutation on a shape with none — on it or on any shape it extends — now throws instead of
|
|
59
|
+
silently typing instances with the shape's own IRI.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
@linkedShape
|
|
63
|
+
class Person extends Shape {
|
|
64
|
+
static targetClass = { id: "https://example.org/Person" }; // required
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`rdf:type` names the class a node **is**; the shape IRI identifies the SHACL description _of_ that
|
|
69
|
+
class — a different node. Substituting one for the other conflated them, and did so invisibly: read
|
|
70
|
+
and write used the same substitution, so data round-tripped and nothing surfaced the mistake.
|
|
71
|
+
`targetClass` is read off the shape class, so JavaScript static inheritance already walks the
|
|
72
|
+
superclass chain.
|
|
73
|
+
|
|
74
|
+
## A declared `sh:path` is always the predicate
|
|
75
|
+
|
|
76
|
+
The resolver skipped any shape or property IRI beginning `linked://tmp/`, substituting the property
|
|
77
|
+
shape's own "shadow" IRI. That skip existed only so this repo's fixtures could assert shape-derived
|
|
78
|
+
predicates; it is gone, along with the `linked://tmp/` special case. Two mutation paths that built
|
|
79
|
+
traversal predicates by hand — bypassing the resolver — now go through it, fixing an expression
|
|
80
|
+
update (`p.bestFriend.name`) that emitted the shadow IRI as a predicate and therefore matched
|
|
81
|
+
nothing.
|
|
82
|
+
|
|
83
|
+
## Migration
|
|
84
|
+
|
|
85
|
+
- Declare a `targetClass` on any shape lacking one. Data written under the old behaviour is typed
|
|
86
|
+
with the shape IRI: either set that IRI as the `targetClass`, or retype the nodes.
|
|
87
|
+
- Implement `askQuery` on any `IDataset`.
|
|
88
|
+
- A shape declaring a `linked://tmp/` path gets its declared path as the predicate instead of the
|
|
89
|
+
shadow IRI. No released code minted such IRIs, so this is expected to affect nobody.
|
|
90
|
+
|
|
7
91
|
- [#205](https://github.com/linked-cm/core/pull/205) [`48ecb4d`](https://github.com/linked-cm/core/commit/48ecb4dbdc20471bf19a4c4cc9d58ef90f5cd1b1) Thanks [@flyon](https://github.com/flyon)! - Expose the meta-model's SHACL constraint table via `getPropertyShapeTerms()` /
|
|
8
92
|
`getPropertyShapeTerm(label)`, so an alternative serializer can look up each constraint's
|
|
9
93
|
predicate, datatype and node kind rather than hard-coding its own copy.
|
|
@@ -42,12 +126,13 @@
|
|
|
42
126
|
preloads, sorting and pagination — none of them can change whether a _match_ exists, and
|
|
43
127
|
honouring `offset` while dropping the projection could actively flip the answer, since
|
|
44
128
|
`OFFSET` skips rows of a solution sequence whose cardinality depends on the projection.
|
|
45
|
-
Kept: filters, `minus` entries and the subject.
|
|
46
|
-
`Person.select(p => p.name).orderBy(…).offset(10).exists()`
|
|
47
|
-
|
|
129
|
+
Kept: filters, `minus` entries and the subject. So
|
|
130
|
+
`Person.select(p => p.name).orderBy(…).offset(10).exists()` costs and answers exactly the same
|
|
131
|
+
as a bare `Person.exists({id})`.
|
|
48
132
|
|
|
49
|
-
|
|
50
|
-
|
|
133
|
+
See the ask-query entry in this release for what that normalised query becomes on the wire and in
|
|
134
|
+
SPARQL: `.exists()` is a shortcut for an ask query, which is its own query kind with its own
|
|
135
|
+
`IDataset.askQuery` method.
|
|
51
136
|
|
|
52
137
|
### Patch Changes
|
|
53
138
|
|
package/lib/esm/index.d.ts
CHANGED
|
@@ -17,6 +17,9 @@ export { PropertyPath, walkPropertyPath } from './queries/PropertyPath.js';
|
|
|
17
17
|
export { FieldSet } from './queries/FieldSet.js';
|
|
18
18
|
export type { FieldSetEntry, FieldSetInput, FieldSetJSON, FieldSetFieldJSON } from './queries/FieldSet.js';
|
|
19
19
|
export type { QueryBuilderJSON } from './queries/QueryBuilder.js';
|
|
20
|
+
export { AskBuilder, isAskQuery } from './queries/AskBuilder.js';
|
|
21
|
+
export type { AskSpec } from './queries/AskBuilder.js';
|
|
22
|
+
export type { AskQuery, AskQueryJSON, RawAskInput } from './queries/AskQuery.js';
|
|
20
23
|
export { CreateBuilder } from './queries/CreateBuilder.js';
|
|
21
24
|
export { UpdateBuilder } from './queries/UpdateBuilder.js';
|
|
22
25
|
export { DeleteBuilder } from './queries/DeleteBuilder.js';
|
package/lib/esm/index.js
CHANGED
|
@@ -21,6 +21,7 @@ export { PropertyPath, walkPropertyPath } from './queries/PropertyPath.js';
|
|
|
21
21
|
// Phase 3a — FieldSet
|
|
22
22
|
export { FieldSet } from './queries/FieldSet.js';
|
|
23
23
|
// Phase 3b — Mutation builders
|
|
24
|
+
export { AskBuilder, isAskQuery } from './queries/AskBuilder.js';
|
|
24
25
|
export { CreateBuilder } from './queries/CreateBuilder.js';
|
|
25
26
|
export { UpdateBuilder } from './queries/UpdateBuilder.js';
|
|
26
27
|
export { DeleteBuilder } from './queries/DeleteBuilder.js';
|
package/lib/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAC,UAAU,EAAE,SAAS,EAAC,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAC,OAAO,EAAC,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAC,uBAAuB,EAAC,MAAM,qCAAqC,CAAC;AAC5E,sEAAsE;AACtE,OAAO,EAAC,KAAK,EAAC,MAAM,mBAAmB,CAAC;AACxC,kEAAkE;AAClE,OAAO,EAAC,QAAQ,EAAE,WAAW,EAAE,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AAQnF,OAAO,EAAC,aAAa,EAAC,MAAM,0BAA0B,CAAC;AACvD,2CAA2C;AAC3C,OAAO,EAAC,aAAa,EAAE,YAAY,EAAC,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAC,KAAK,EAAC,MAAM,oBAAoB,CAAC;AAEzC,OAAO,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAC;AAE/C,OAAO,EACL,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAC,YAAY,EAAE,gBAAgB,EAAC,MAAM,2BAA2B,CAAC;AAEzE,sBAAsB;AACtB,OAAO,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAC;AAM/C,+BAA+B;AAC/B,OAAO,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AAGzD,8CAA8C;AAC9C,OAAO,EAAC,cAAc,EAAC,MAAM,iCAAiC,CAAC;AAE/D,OAAO,EAAC,IAAI,EAAC,MAAM,uBAAuB,CAAC;AAe3C,sEAAsE;AACtE,OAAO,EACL,cAAc,EACd,WAAW,GACZ,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,cAAc,EACd,iBAAiB,GAClB,MAAM,gCAAgC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAC,UAAU,EAAE,SAAS,EAAC,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAC,OAAO,EAAC,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAC,uBAAuB,EAAC,MAAM,qCAAqC,CAAC;AAC5E,sEAAsE;AACtE,OAAO,EAAC,KAAK,EAAC,MAAM,mBAAmB,CAAC;AACxC,kEAAkE;AAClE,OAAO,EAAC,QAAQ,EAAE,WAAW,EAAE,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AAQnF,OAAO,EAAC,aAAa,EAAC,MAAM,0BAA0B,CAAC;AACvD,2CAA2C;AAC3C,OAAO,EAAC,aAAa,EAAE,YAAY,EAAC,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAC,KAAK,EAAC,MAAM,oBAAoB,CAAC;AAEzC,OAAO,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAC;AAE/C,OAAO,EACL,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAC,YAAY,EAAE,gBAAgB,EAAC,MAAM,2BAA2B,CAAC;AAEzE,sBAAsB;AACtB,OAAO,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAC;AAM/C,+BAA+B;AAC/B,OAAO,EAAC,UAAU,EAAE,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAG/D,OAAO,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AAGzD,8CAA8C;AAC9C,OAAO,EAAC,cAAc,EAAC,MAAM,iCAAiC,CAAC;AAE/D,OAAO,EAAC,IAAI,EAAC,MAAM,uBAAuB,CAAC;AAe3C,sEAAsE;AACtE,OAAO,EACL,cAAc,EACd,WAAW,GACZ,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,cAAc,EACd,iBAAiB,GAClB,MAAM,gCAAgC,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SelectQuery } from '../queries/SelectQuery.js';
|
|
2
|
+
import type { AskQuery } from '../queries/AskQuery.js';
|
|
2
3
|
import type { CreateQuery } from '../queries/CreateQuery.js';
|
|
3
4
|
import type { UpdateQuery } from '../queries/UpdateQuery.js';
|
|
4
5
|
import type { DeleteQuery, DeleteResponse } from '../queries/DeleteQuery.js';
|
|
@@ -20,6 +21,27 @@ export interface IDataset {
|
|
|
20
21
|
*/
|
|
21
22
|
init?(): Promise<any>;
|
|
22
23
|
selectQuery(query: SelectQuery): Promise<SelectResult>;
|
|
24
|
+
/**
|
|
25
|
+
* Answer an ask query — a boolean, not a result set.
|
|
26
|
+
*
|
|
27
|
+
* **Required.** An {@link AskQuery} carries a pattern and nothing else: no
|
|
28
|
+
* projection, no sorting, no pagination. A SPARQL-backed store emits
|
|
29
|
+
* `ASK WHERE { … }`; another backend answers it however it can.
|
|
30
|
+
*
|
|
31
|
+
* It is required rather than optional, and this package contains **no path that
|
|
32
|
+
* rewrites an ask as a select**. A store that has no boolean primitive decides
|
|
33
|
+
* for itself how to answer — that decision belongs to the store, and defaulting
|
|
34
|
+
* it here would hide it.
|
|
35
|
+
*
|
|
36
|
+
* `query.shape` is optional: absent means no `rdf:type` constraint at all
|
|
37
|
+
* ("does a node with this IRI exist"), which lowers to `ASK { <iri> ?p ?o }`.
|
|
38
|
+
*
|
|
39
|
+
* Must resolve to a real boolean — a non-boolean is rejected, not coerced,
|
|
40
|
+
* since a truthy value would silently read as "exists". Must reject on
|
|
41
|
+
* failure: reporting an unreachable store as `false` is the failure mode this
|
|
42
|
+
* API was built to remove.
|
|
43
|
+
*/
|
|
44
|
+
askQuery(query: AskQuery): Promise<boolean>;
|
|
23
45
|
updateQuery?(query: UpdateQuery): Promise<UpdateResult>;
|
|
24
46
|
createQuery?(query: CreateQuery): Promise<CreateResult>;
|
|
25
47
|
deleteQuery?(query: DeleteQuery): Promise<DeleteResponse>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `AskBuilder` — a query whose answer is a boolean.
|
|
3
|
+
*
|
|
4
|
+
* A small sibling of `SelectBuilder` rather than a mode of it. It can only hold a
|
|
5
|
+
* pattern (shape, subject(s), where, minus), so the normalisation an existence
|
|
6
|
+
* check needs is a property of the *type*: there is no projection, sorting or
|
|
7
|
+
* pagination to drop, because none can be set.
|
|
8
|
+
*/
|
|
9
|
+
import { type ShapeConstructor } from '../shapes/Shape.js';
|
|
10
|
+
import type { WherePath } from './SelectQuery.js';
|
|
11
|
+
import type { RawMinusEntry } from './IRDesugar.js';
|
|
12
|
+
import type { NodeShapeData } from '../shapes/SHACL.js';
|
|
13
|
+
import type { NodeReferenceValue } from './QueryFactory.js';
|
|
14
|
+
import type { IDataset } from '../interfaces/IDataset.js';
|
|
15
|
+
import { PendingQueryContext } from './QueryContext.js';
|
|
16
|
+
import type { AskQuery, AskQueryJSON, RawAskInput } from './AskQuery.js';
|
|
17
|
+
/** Everything an ask can carry. Assembled by `.exists()` or by `Shape.exists()`. */
|
|
18
|
+
export type AskSpec = {
|
|
19
|
+
/** The shape class whose instances are asked about; omitted = shapeless. */
|
|
20
|
+
shapeClass?: ShapeConstructor<any>;
|
|
21
|
+
subject?: NodeReferenceValue | PendingQueryContext;
|
|
22
|
+
subjects?: NodeReferenceValue[];
|
|
23
|
+
where?: WherePath;
|
|
24
|
+
minusEntries?: RawMinusEntry[];
|
|
25
|
+
/** `.for(null)` was called — there is no subject to ask about. */
|
|
26
|
+
nullSubject?: boolean;
|
|
27
|
+
};
|
|
28
|
+
export declare class AskBuilder implements PromiseLike<boolean>, Promise<boolean> {
|
|
29
|
+
private readonly _spec;
|
|
30
|
+
private constructor();
|
|
31
|
+
/** Build from an assembled spec — used by `.exists()` and `Shape.exists()`. */
|
|
32
|
+
static of(spec: AskSpec): AskBuilder;
|
|
33
|
+
/**
|
|
34
|
+
* A shapeless ask: does a node with this IRI exist at all, under any type or
|
|
35
|
+
* none? Lowers to `ASK { <iri> ?p ?o }`.
|
|
36
|
+
*/
|
|
37
|
+
static forNode(id: string | NodeReferenceValue | PendingQueryContext | null | undefined): AskBuilder;
|
|
38
|
+
/** Discriminator for the free `lower()` function and dataset routing. */
|
|
39
|
+
readonly __queryKind: "ask";
|
|
40
|
+
/** The shape asked about — `undefined` for a shapeless ask. */
|
|
41
|
+
get shape(): NodeShapeData | undefined;
|
|
42
|
+
toRawInput(): RawAskInput;
|
|
43
|
+
toJSON(): AskQueryJSON;
|
|
44
|
+
static fromJSON(json: AskQueryJSON): AskBuilder;
|
|
45
|
+
/**
|
|
46
|
+
* Execute and resolve to a real boolean.
|
|
47
|
+
*
|
|
48
|
+
* Never `null`, never a row. A store, transport or lowering failure **rejects**;
|
|
49
|
+
* it is never reported as `false`. The one case that resolves `false` without
|
|
50
|
+
* querying is a query with no subject to ask about — `.for(null)`, or a
|
|
51
|
+
* `PendingQueryContext` as the subject that has not landed: "does the node with
|
|
52
|
+
* no id exist?" has a correct total answer, and it is `false`.
|
|
53
|
+
*/
|
|
54
|
+
exec(target?: IDataset): Promise<boolean>;
|
|
55
|
+
/** `await` triggers execution. */
|
|
56
|
+
then<TResult1 = boolean, TResult2 = never>(onfulfilled?: ((value: boolean) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
57
|
+
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<boolean | TResult>;
|
|
58
|
+
finally(onfinally?: (() => void) | null): Promise<boolean>;
|
|
59
|
+
get [Symbol.toStringTag](): string;
|
|
60
|
+
}
|
|
61
|
+
/** Narrow an unknown query object to an ask. */
|
|
62
|
+
export declare function isAskQuery(query: unknown): query is AskQuery;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
|
+
*/
|
|
6
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
7
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
8
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
9
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
10
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
11
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
12
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
import { resolveShape } from './resolveShape.js';
|
|
16
|
+
import { PendingQueryContext } from './QueryContext.js';
|
|
17
|
+
import { encodeContextRef, isContextRefJSON } from './ContextRef.js';
|
|
18
|
+
import { WIRE_VERSION, assertWireVersion } from './wireVersion.js';
|
|
19
|
+
import { getQueryDispatch, resolveExistence } from './queryDispatch.js';
|
|
20
|
+
import { resolveUriOrThrow } from '../utils/NodeReference.js';
|
|
21
|
+
import { serializeWherePath, serializeRawMinusEntry, deserializeWherePath, deserializeRawMinusEntry, } from './QueryBuilderSerialization.js';
|
|
22
|
+
export class AskBuilder {
|
|
23
|
+
constructor(spec) {
|
|
24
|
+
/** Discriminator for the free `lower()` function and dataset routing. */
|
|
25
|
+
this.__queryKind = 'ask';
|
|
26
|
+
this._spec = spec;
|
|
27
|
+
}
|
|
28
|
+
/** Build from an assembled spec — used by `.exists()` and `Shape.exists()`. */
|
|
29
|
+
static of(spec) {
|
|
30
|
+
return new AskBuilder(spec);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A shapeless ask: does a node with this IRI exist at all, under any type or
|
|
34
|
+
* none? Lowers to `ASK { <iri> ?p ?o }`.
|
|
35
|
+
*/
|
|
36
|
+
static forNode(id) {
|
|
37
|
+
if (id === null || id === undefined) {
|
|
38
|
+
return new AskBuilder({ nullSubject: true });
|
|
39
|
+
}
|
|
40
|
+
if (id instanceof PendingQueryContext) {
|
|
41
|
+
return new AskBuilder({ subject: id });
|
|
42
|
+
}
|
|
43
|
+
const ref = typeof id === 'string' ? { id: resolveUriOrThrow(id) } : id;
|
|
44
|
+
return new AskBuilder({ subject: ref });
|
|
45
|
+
}
|
|
46
|
+
/** The shape asked about — `undefined` for a shapeless ask. */
|
|
47
|
+
get shape() {
|
|
48
|
+
var _a;
|
|
49
|
+
return (_a = this._spec.shapeClass) === null || _a === void 0 ? void 0 : _a.shape;
|
|
50
|
+
}
|
|
51
|
+
toRawInput() {
|
|
52
|
+
const { shapeClass, subject, subjects, where, minusEntries, nullSubject } = this._spec;
|
|
53
|
+
const input = {};
|
|
54
|
+
// Carried so lowering can reject it. `exec()` answers `false` before
|
|
55
|
+
// dispatching, but a store handed the builder directly (e.g. after
|
|
56
|
+
// `fromJSON`) would otherwise lower a subject-less query that matches every
|
|
57
|
+
// instance of the shape and answer `true`.
|
|
58
|
+
if (nullSubject)
|
|
59
|
+
input.nullSubject = true;
|
|
60
|
+
if (shapeClass)
|
|
61
|
+
input.shape = shapeClass;
|
|
62
|
+
if (subject)
|
|
63
|
+
input.subject = subject;
|
|
64
|
+
if (subjects && subjects.length > 0)
|
|
65
|
+
input.subjects = subjects;
|
|
66
|
+
if (where)
|
|
67
|
+
input.where = where;
|
|
68
|
+
if (minusEntries && minusEntries.length > 0)
|
|
69
|
+
input.minusEntries = minusEntries;
|
|
70
|
+
return input;
|
|
71
|
+
}
|
|
72
|
+
toJSON() {
|
|
73
|
+
var _a;
|
|
74
|
+
const { shapeClass, subject, subjects, where, minusEntries, nullSubject } = this._spec;
|
|
75
|
+
const json = { v: WIRE_VERSION, op: 'ask' };
|
|
76
|
+
if (shapeClass) {
|
|
77
|
+
json.shape = ((_a = shapeClass.shape) === null || _a === void 0 ? void 0 : _a.id) || '';
|
|
78
|
+
}
|
|
79
|
+
if (subject instanceof PendingQueryContext) {
|
|
80
|
+
// Carry the reference, not its (possibly unresolved) id, so the receiver
|
|
81
|
+
// resolves it against its own context map at lowering time.
|
|
82
|
+
json.subject = encodeContextRef(subject.contextName);
|
|
83
|
+
}
|
|
84
|
+
else if (subject && typeof subject === 'object' && 'id' in subject) {
|
|
85
|
+
json.subject = subject.id;
|
|
86
|
+
}
|
|
87
|
+
if (subjects && subjects.length > 0) {
|
|
88
|
+
json.subjects = subjects.map((s) => s.id);
|
|
89
|
+
}
|
|
90
|
+
// A shapeless ask carries neither — both name properties, which only a shape
|
|
91
|
+
// can resolve — so `shapeClass` is present whenever these are.
|
|
92
|
+
if (where && shapeClass) {
|
|
93
|
+
json.where = serializeWherePath(where, shapeClass.shape);
|
|
94
|
+
}
|
|
95
|
+
if (minusEntries && minusEntries.length > 0 && shapeClass) {
|
|
96
|
+
json.minusEntries = minusEntries.map((e) => serializeRawMinusEntry(e, shapeClass.shape));
|
|
97
|
+
}
|
|
98
|
+
if (nullSubject) {
|
|
99
|
+
json.nullSubject = true;
|
|
100
|
+
}
|
|
101
|
+
return json;
|
|
102
|
+
}
|
|
103
|
+
static fromJSON(json) {
|
|
104
|
+
var _a, _b;
|
|
105
|
+
assertWireVersion(json.v);
|
|
106
|
+
const shapeClass = json.shape
|
|
107
|
+
? resolveShape(json.shape)
|
|
108
|
+
: undefined;
|
|
109
|
+
const spec = {};
|
|
110
|
+
if (shapeClass)
|
|
111
|
+
spec.shapeClass = shapeClass;
|
|
112
|
+
if (json.subject !== undefined) {
|
|
113
|
+
spec.subject = isContextRefJSON(json.subject)
|
|
114
|
+
? new PendingQueryContext(json.subject['@ctx'])
|
|
115
|
+
: { id: json.subject };
|
|
116
|
+
}
|
|
117
|
+
if (json.subjects && json.subjects.length > 0) {
|
|
118
|
+
spec.subjects = json.subjects.map((id) => ({ id }));
|
|
119
|
+
}
|
|
120
|
+
if (json.where || ((_a = json.minusEntries) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
121
|
+
// A where clause or minus entry names properties, and a property is only
|
|
122
|
+
// resolvable through a shape. A shapeless ask cannot carry either.
|
|
123
|
+
if (!shapeClass) {
|
|
124
|
+
throw new Error('An ask envelope with a `where` or `minusEntries` must name a `shape`: ' +
|
|
125
|
+
'the clause references properties, which are resolved through the shape.');
|
|
126
|
+
}
|
|
127
|
+
if (json.where) {
|
|
128
|
+
spec.where = deserializeWherePath(shapeClass.shape, json.where);
|
|
129
|
+
}
|
|
130
|
+
if ((_b = json.minusEntries) === null || _b === void 0 ? void 0 : _b.length) {
|
|
131
|
+
spec.minusEntries = json.minusEntries.map((e) => deserializeRawMinusEntry(shapeClass.shape, e));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (json.nullSubject)
|
|
135
|
+
spec.nullSubject = true;
|
|
136
|
+
return new AskBuilder(spec);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Execute and resolve to a real boolean.
|
|
140
|
+
*
|
|
141
|
+
* Never `null`, never a row. A store, transport or lowering failure **rejects**;
|
|
142
|
+
* it is never reported as `false`. The one case that resolves `false` without
|
|
143
|
+
* querying is a query with no subject to ask about — `.for(null)`, or a
|
|
144
|
+
* `PendingQueryContext` as the subject that has not landed: "does the node with
|
|
145
|
+
* no id exist?" has a correct total answer, and it is `false`.
|
|
146
|
+
*/
|
|
147
|
+
exec(target) {
|
|
148
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
149
|
+
const { nullSubject, subject } = this._spec;
|
|
150
|
+
if (nullSubject)
|
|
151
|
+
return false;
|
|
152
|
+
if (subject instanceof PendingQueryContext && !subject.id)
|
|
153
|
+
return false;
|
|
154
|
+
// `async` ensures a missing global dispatch rejects rather than throwing
|
|
155
|
+
// synchronously past the caller's `.catch()`.
|
|
156
|
+
const dispatch = target !== null && target !== void 0 ? target : getQueryDispatch();
|
|
157
|
+
return resolveExistence(dispatch, this);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/** `await` triggers execution. */
|
|
161
|
+
then(onfulfilled, onrejected) {
|
|
162
|
+
return this.exec().then(onfulfilled, onrejected);
|
|
163
|
+
}
|
|
164
|
+
catch(onrejected) {
|
|
165
|
+
return this.then().catch(onrejected);
|
|
166
|
+
}
|
|
167
|
+
finally(onfinally) {
|
|
168
|
+
return this.then().finally(onfinally);
|
|
169
|
+
}
|
|
170
|
+
get [Symbol.toStringTag]() {
|
|
171
|
+
return 'AskBuilder';
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** Narrow an unknown query object to an ask. */
|
|
175
|
+
export function isAskQuery(query) {
|
|
176
|
+
return (!!query && query.__queryKind === 'ask');
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=AskBuilder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AskBuilder.js","sourceRoot":"","sources":["../../../src/queries/AskBuilder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ask query — "does any solution exist?", answered with a boolean.
|
|
3
|
+
*
|
|
4
|
+
* An ask carries a **pattern and nothing else**: no projection, no sorting, no
|
|
5
|
+
* pagination. Those shape or window a *solution sequence*, and an ask has no
|
|
6
|
+
* solution sequence to shape — so rather than being ignored or guarded against,
|
|
7
|
+
* they are simply not expressible here.
|
|
8
|
+
*
|
|
9
|
+
* `.exists()` is the first question asked this way; it is a shortcut for the
|
|
10
|
+
* general form, not a special case of select.
|
|
11
|
+
*/
|
|
12
|
+
import type { NodeShapeData } from '../shapes/SHACL.js';
|
|
13
|
+
import type { WherePath } from './SelectQuery.js';
|
|
14
|
+
import type { RawMinusEntry } from './IRDesugar.js';
|
|
15
|
+
import type { NodeReferenceValue } from './QueryFactory.js';
|
|
16
|
+
import type { PendingQueryContext } from './QueryContext.js';
|
|
17
|
+
import type { ContextRefJSON } from './ContextRef.js';
|
|
18
|
+
import type { WherePathJSON, RawMinusEntryJSON } from './QueryBuilderSerialization.js';
|
|
19
|
+
/**
|
|
20
|
+
* The live, closed (read-only) ask query — the object an `IDataset` receives.
|
|
21
|
+
*
|
|
22
|
+
* `shape` is **optional**. Absent means no `rdf:type` constraint at all: "does a
|
|
23
|
+
* node with this IRI exist", independent of any shape. A dataset reads
|
|
24
|
+
* `toJSON()` to forward the query, or `lower()` to get canonical IR.
|
|
25
|
+
*/
|
|
26
|
+
export interface AskQuery {
|
|
27
|
+
readonly __queryKind: 'ask';
|
|
28
|
+
/** The shape whose instances are asked about; absent for a shapeless ask. */
|
|
29
|
+
readonly shape?: NodeShapeData;
|
|
30
|
+
toJSON(): AskQueryJSON;
|
|
31
|
+
toRawInput(): RawAskInput;
|
|
32
|
+
}
|
|
33
|
+
/** Pre-lowering input, as the builder hands it to `lower()`. */
|
|
34
|
+
export type RawAskInput = {
|
|
35
|
+
/** `.for(null)` — no subject to ask about. Lowering rejects it; see `lowerAsk`. */
|
|
36
|
+
nullSubject?: boolean;
|
|
37
|
+
shape?: {
|
|
38
|
+
shape?: {
|
|
39
|
+
id?: string;
|
|
40
|
+
};
|
|
41
|
+
id?: string;
|
|
42
|
+
};
|
|
43
|
+
subject?: NodeReferenceValue | PendingQueryContext;
|
|
44
|
+
subjects?: NodeReferenceValue[];
|
|
45
|
+
where?: WherePath;
|
|
46
|
+
minusEntries?: RawMinusEntry[];
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* The DSL-JSON wire form of an ask query.
|
|
50
|
+
*
|
|
51
|
+
* Carries `op: 'ask'` — the same discriminator mutation envelopes use, so
|
|
52
|
+
* `fromJSON` routes on it and an older peer rejects an unknown op loudly rather
|
|
53
|
+
* than reinterpreting the envelope as a select.
|
|
54
|
+
*
|
|
55
|
+
* Every field is pattern-bearing. There is deliberately no `fields`, `limit`,
|
|
56
|
+
* `offset`, `sortBy` or `one`: a receiver has nothing to validate or ignore.
|
|
57
|
+
*
|
|
58
|
+
* ```json
|
|
59
|
+
* {"v": "1.1", "op": "ask",
|
|
60
|
+
* "shape": "https://linked.cm/shape/core/Person",
|
|
61
|
+
* "subject": "linked://tmp/entities/p1"}
|
|
62
|
+
*
|
|
63
|
+
* {"v": "1.1", "op": "ask", "subject": "https://example.org/thing"}
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* The second has no `shape` — that absence *is* the shapeless discriminator.
|
|
67
|
+
*/
|
|
68
|
+
export type AskQueryJSON = {
|
|
69
|
+
v?: string;
|
|
70
|
+
op: 'ask';
|
|
71
|
+
/** Omitted for a shapeless ask (no `rdf:type` constraint). */
|
|
72
|
+
shape?: string;
|
|
73
|
+
/** A node id, or a `{@ctx: name}` reference resolved at lowering. */
|
|
74
|
+
subject?: string | ContextRefJSON;
|
|
75
|
+
subjects?: string[];
|
|
76
|
+
where?: WherePathJSON;
|
|
77
|
+
minusEntries?: RawMinusEntryJSON[];
|
|
78
|
+
/** `.for(null)` — no subject to ask about; the answer is `false` without querying. */
|
|
79
|
+
nullSubject?: boolean;
|
|
80
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AskQuery.js","sourceRoot":"","sources":["../../../src/queries/AskQuery.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -3,7 +3,7 @@ import type { PathExpr } from '../paths/PropertyPathExpr.js';
|
|
|
3
3
|
export type IRDirection = 'ASC' | 'DESC';
|
|
4
4
|
export type IRAlias = string;
|
|
5
5
|
export type IRValue = string | number | boolean | null;
|
|
6
|
-
export type IRQuery = IRSelectQuery | IRCreateMutation | IRUpdateMutation | IRDeleteMutation | IRDeleteAllMutation | IRDeleteWhereMutation | IRUpdateWhereMutation;
|
|
6
|
+
export type IRQuery = IRSelectQuery | IRAskQuery | IRCreateMutation | IRUpdateMutation | IRDeleteMutation | IRDeleteAllMutation | IRDeleteWhereMutation | IRUpdateWhereMutation;
|
|
7
7
|
export type IRSelectQuery = {
|
|
8
8
|
kind: 'select';
|
|
9
9
|
root: IRShapeScanPattern;
|
|
@@ -18,6 +18,27 @@ export type IRSelectQuery = {
|
|
|
18
18
|
singleResult?: boolean;
|
|
19
19
|
resultMap?: IRResultMapEntry[];
|
|
20
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* A query whose answer is a boolean.
|
|
23
|
+
*
|
|
24
|
+
* Carries a pattern and nothing else. There is no `projection`, `orderBy`,
|
|
25
|
+
* `limit` or `offset`: each of those shapes or windows a *solution sequence*, and
|
|
26
|
+
* an ask has none — so rather than being ignored during conversion, or guarded
|
|
27
|
+
* against, they are unrepresentable.
|
|
28
|
+
*
|
|
29
|
+
* `root` is **optional**. Absent means no `rdf:type` constraint at all — "does a
|
|
30
|
+
* node with this IRI exist", under any shape or none — which lowers to
|
|
31
|
+
* `ASK { <iri> ?p ?o }`. A rootless ask carries no `patterns` or `where`, since
|
|
32
|
+
* both reference properties and a property is only resolvable through a shape.
|
|
33
|
+
*/
|
|
34
|
+
export type IRAskQuery = {
|
|
35
|
+
kind: 'ask';
|
|
36
|
+
root?: IRShapeScanPattern;
|
|
37
|
+
patterns: IRGraphPattern[];
|
|
38
|
+
where?: IRExpression;
|
|
39
|
+
subjectId?: string;
|
|
40
|
+
subjectIds?: string[];
|
|
41
|
+
};
|
|
21
42
|
export type IRProjectionItem = {
|
|
22
43
|
alias: IRAlias;
|
|
23
44
|
expression: IRExpression;
|
|
@@ -127,26 +127,26 @@ export declare class SelectBuilder<S extends Shape = Shape, R = any, Result = an
|
|
|
127
127
|
* **Kept** — filters, `minus` entries and the subject: those decide whether a match
|
|
128
128
|
* exists. `LIMIT 1` is then applied.
|
|
129
129
|
*
|
|
130
|
-
* So `.select(…).orderBy(…).offset(10).exists()` costs, and answers, exactly the
|
|
131
|
-
* as a bare `.exists()
|
|
130
|
+
* So `.select(…).orderBy(…).offset(10).exists()` costs, and answers, exactly the
|
|
131
|
+
* same as a bare `.exists()`. Against a SPARQL store that goes out as:
|
|
132
132
|
*
|
|
133
133
|
* ```sparql
|
|
134
|
-
*
|
|
134
|
+
* ASK WHERE { ?a0 rdf:type <…> . FILTER(?a0 = <…>) }
|
|
135
135
|
* ```
|
|
136
136
|
*
|
|
137
|
-
* Pagination
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* the
|
|
141
|
-
* `false` after. `exists()` therefore answers a question about the **match set**,
|
|
142
|
-
* not about a page of it.
|
|
137
|
+
* Pagination does not survive, and cannot: `OFFSET` skips rows of a *solution
|
|
138
|
+
* sequence*, and an ask has none. (Nor is it merely dropped — an
|
|
139
|
+
* {@link AskBuilder} has nowhere to hold it.) `exists()` answers a question about
|
|
140
|
+
* the **match set**, not about a page of it.
|
|
143
141
|
*
|
|
144
142
|
* ### Errors are not swallowed
|
|
145
143
|
*
|
|
146
144
|
* A store, transport or lowering failure rejects the returned promise; it is never
|
|
147
145
|
* reported as `false`. That includes an unresolved query-context reference in a
|
|
148
146
|
* where clause, which `exec()` deliberately reports as `null` ("not ready") but
|
|
149
|
-
* which `exists()` must not flatten into a boolean
|
|
147
|
+
* which `exists()` must not flatten into a boolean — and a store whose `askQuery`
|
|
148
|
+
* resolves to something that is not a boolean, which rejects rather than being
|
|
149
|
+
* coerced into one.
|
|
150
150
|
*
|
|
151
151
|
* The one case that does resolve `false` without querying is a query with **no
|
|
152
152
|
* subject to ask about** — `.for(null)`, `.for(undefined)`, or an unresolved
|
|
@@ -156,6 +156,16 @@ export declare class SelectBuilder<S extends Shape = Shape, R = any, Result = an
|
|
|
156
156
|
* @param target Optional explicit dataset, as for {@link exec}.
|
|
157
157
|
*/
|
|
158
158
|
exists(target?: IDataset): Promise<boolean>;
|
|
159
|
+
/**
|
|
160
|
+
* Reduce this select to the ask query that answers the same existence question.
|
|
161
|
+
*
|
|
162
|
+
* Only the pattern survives — shape, subject(s), filters, `minus`. The
|
|
163
|
+
* projection, preloads, sorting and pagination are not "dropped" so much as
|
|
164
|
+
* unrepresentable: {@link AskBuilder} has nowhere to put them. That is the point
|
|
165
|
+
* of it being a separate builder rather than a mode of this one — the
|
|
166
|
+
* normalisation cannot be forgotten or half-applied.
|
|
167
|
+
*/
|
|
168
|
+
private _toAsk;
|
|
159
169
|
/**
|
|
160
170
|
* Preload a component's query fields at the given property path.
|
|
161
171
|
*
|