@canonical/pragma-cli 0.38.0 → 0.40.0
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/README.md +1 -1
- package/dist/package.json +14 -14
- package/dist/pragma.conf.js +273 -176
- package/dist/pragma.conf.js.map +1 -1
- package/dist/src/capabilities/capabilities/capabilities.verb.js +2 -0
- package/dist/src/capabilities/capabilities/capabilities.verb.js.map +1 -1
- package/dist/src/capabilities/capabilities/catalog.js +47 -51
- package/dist/src/capabilities/capabilities/catalog.js.map +1 -1
- package/dist/src/capabilities/capabilities/index.js +2 -0
- package/dist/src/capabilities/capabilities/index.js.map +1 -1
- package/dist/src/capabilities/capabilities/types.js +5 -5
- package/dist/src/capabilities/colophon/colophon.render.js +7 -4
- package/dist/src/capabilities/colophon/colophon.render.js.map +1 -1
- package/dist/src/capabilities/colophon/colophon.verb.js +1 -0
- package/dist/src/capabilities/colophon/colophon.verb.js.map +1 -1
- package/dist/src/capabilities/config/get.verb.js +2 -0
- package/dist/src/capabilities/config/get.verb.js.map +1 -1
- package/dist/src/capabilities/config/runField.js +2 -2
- package/dist/src/capabilities/config/runField.js.map +1 -1
- package/dist/src/capabilities/config/set.verb.js +2 -0
- package/dist/src/capabilities/config/set.verb.js.map +1 -1
- package/dist/src/capabilities/config/show.verb.js +1 -0
- package/dist/src/capabilities/config/show.verb.js.map +1 -1
- package/dist/src/capabilities/config/unset.verb.js +2 -0
- package/dist/src/capabilities/config/unset.verb.js.map +1 -1
- package/dist/src/capabilities/create/constants.js +2 -2
- package/dist/src/capabilities/create/create.verb.js +14 -4
- package/dist/src/capabilities/create/create.verb.js.map +1 -1
- package/dist/src/capabilities/distribution.js +3 -1
- package/dist/src/capabilities/distribution.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/checkPackageRefs.js +10 -2
- package/dist/src/capabilities/doctor/checks/checkPackageRefs.js.map +1 -1
- package/dist/src/capabilities/doctor/doctor.verb.js +2 -0
- package/dist/src/capabilities/doctor/doctor.verb.js.map +1 -1
- package/dist/src/capabilities/graph/connect.verb.js +2 -0
- package/dist/src/capabilities/graph/connect.verb.js.map +1 -1
- package/dist/src/capabilities/graph/inspect.render.js +7 -1
- package/dist/src/capabilities/graph/inspect.render.js.map +1 -1
- package/dist/src/capabilities/graph/inspect.verb.js +2 -0
- package/dist/src/capabilities/graph/inspect.verb.js.map +1 -1
- package/dist/src/capabilities/graph/query.render.js +37 -5
- package/dist/src/capabilities/graph/query.render.js.map +1 -1
- package/dist/src/capabilities/graph/query.verb.js +2 -0
- package/dist/src/capabilities/graph/query.verb.js.map +1 -1
- package/dist/src/capabilities/graph/queryText.js +28 -0
- package/dist/src/capabilities/graph/queryText.js.map +1 -1
- package/dist/src/capabilities/graph/runConnect.js +3 -3
- package/dist/src/capabilities/graph/runConnect.js.map +1 -1
- package/dist/src/capabilities/graph/runQuery.js +8 -6
- package/dist/src/capabilities/graph/runQuery.js.map +1 -1
- package/dist/src/capabilities/info/info.verb.js +2 -0
- package/dist/src/capabilities/info/info.verb.js.map +1 -1
- package/dist/src/capabilities/meta/mcp.verb.js +1 -0
- package/dist/src/capabilities/meta/mcp.verb.js.map +1 -1
- package/dist/src/capabilities/ontology/verbs.js +7 -10
- package/dist/src/capabilities/ontology/verbs.js.map +1 -1
- package/dist/src/capabilities/prompt/verbs.js +5 -4
- package/dist/src/capabilities/prompt/verbs.js.map +1 -1
- package/dist/src/capabilities/resources/provider.js +6 -3
- package/dist/src/capabilities/resources/provider.js.map +1 -1
- package/dist/src/capabilities/setup/setup.verb.js +13 -5
- package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
- package/dist/src/capabilities/shared/calls.js +16 -0
- package/dist/src/capabilities/shared/calls.js.map +1 -0
- package/dist/src/capabilities/skill/render.js +7 -5
- package/dist/src/capabilities/skill/render.js.map +1 -1
- package/dist/src/capabilities/skill/verbs.js +5 -4
- package/dist/src/capabilities/skill/verbs.js.map +1 -1
- package/dist/src/capabilities/sources/collectStatus.js +6 -1
- package/dist/src/capabilities/sources/collectStatus.js.map +1 -1
- package/dist/src/capabilities/sources/index.js +4 -3
- package/dist/src/capabilities/sources/index.js.map +1 -1
- package/dist/src/capabilities/sources/reset.render.js +37 -0
- package/dist/src/capabilities/sources/reset.render.js.map +1 -0
- package/dist/src/capabilities/sources/reset.verb.js +51 -0
- package/dist/src/capabilities/sources/reset.verb.js.map +1 -0
- package/dist/src/capabilities/sources/runReset.js +59 -0
- package/dist/src/capabilities/sources/runReset.js.map +1 -0
- package/dist/src/capabilities/sources/runUpdate.js +7 -6
- package/dist/src/capabilities/sources/runUpdate.js.map +1 -1
- package/dist/src/capabilities/sources/status.render.js +24 -8
- package/dist/src/capabilities/sources/status.render.js.map +1 -1
- package/dist/src/capabilities/sources/status.verb.js +1 -0
- package/dist/src/capabilities/sources/status.verb.js.map +1 -1
- package/dist/src/capabilities/sources/update.verb.js +2 -0
- package/dist/src/capabilities/sources/update.verb.js.map +1 -1
- package/dist/src/capabilities/upgrade/upgrade.verb.js +1 -0
- package/dist/src/capabilities/upgrade/upgrade.verb.js.map +1 -1
- package/dist/src/capabilities/version/version.verb.js +1 -0
- package/dist/src/capabilities/version/version.verb.js.map +1 -1
- package/dist/src/constants.js +4 -3
- package/dist/src/constants.js.map +1 -1
- package/dist/src/kernel/error/index.js +1 -1
- package/dist/src/kernel/error/index.js.map +1 -1
- package/dist/src/kernel/error/recovery.js +24 -42
- package/dist/src/kernel/error/recovery.js.map +1 -1
- package/dist/src/kernel/packs/collect.js +123 -9
- package/dist/src/kernel/packs/collect.js.map +1 -1
- package/dist/src/kernel/packs/compile.js +74 -15
- package/dist/src/kernel/packs/compile.js.map +1 -1
- package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js +3 -3
- package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js.map +1 -1
- package/dist/src/kernel/packs/iri.js +2 -4
- package/dist/src/kernel/packs/iri.js.map +1 -1
- package/dist/src/kernel/packs/renderPack.js +90 -50
- package/dist/src/kernel/packs/renderPack.js.map +1 -1
- package/dist/src/kernel/packs/resolveEntity.js +202 -90
- package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
- package/dist/src/kernel/packs/runBodies.js +89 -30
- package/dist/src/kernel/packs/runBodies.js.map +1 -1
- package/dist/src/kernel/packs/schema.js +66 -12
- package/dist/src/kernel/packs/schema.js.map +1 -1
- package/dist/src/kernel/packs/sparql/buildListQuery.js +80 -13
- package/dist/src/kernel/packs/sparql/buildListQuery.js.map +1 -1
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js +41 -13
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
- package/dist/src/kernel/packs/sparql/filterValues.js +32 -23
- package/dist/src/kernel/packs/sparql/filterValues.js.map +1 -1
- package/dist/src/kernel/packs/sparql/runSelect.js +3 -5
- package/dist/src/kernel/packs/sparql/runSelect.js.map +1 -1
- package/dist/src/kernel/packs/storyRules.js +31 -4
- package/dist/src/kernel/packs/storyRules.js.map +1 -1
- package/dist/src/kernel/packs/tierScope.js +3 -3
- package/dist/src/kernel/packs/tierScope.js.map +1 -1
- package/dist/src/kernel/packs/types.js +4 -0
- package/dist/src/kernel/packs/types.js.map +1 -1
- package/dist/src/kernel/project/cli/buildProgram.js +4 -0
- package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
- package/dist/src/kernel/project/cli/dispatch.js +32 -8
- package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
- package/dist/src/kernel/project/cli/suggestNames.js +44 -1
- package/dist/src/kernel/project/cli/suggestNames.js.map +1 -1
- package/dist/src/kernel/project/cli/verbHelp.js +17 -18
- package/dist/src/kernel/project/cli/verbHelp.js.map +1 -1
- package/dist/src/kernel/project/mcp/buildServer.js +11 -4
- package/dist/src/kernel/project/mcp/buildServer.js.map +1 -1
- package/dist/src/kernel/project/mcp/envelope.js +3 -2
- package/dist/src/kernel/project/mcp/envelope.js.map +1 -1
- package/dist/src/kernel/project/mcp/instructions.js +101 -31
- package/dist/src/kernel/project/mcp/instructions.js.map +1 -1
- package/dist/src/kernel/project/mcp/registerVerb.js +59 -23
- package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
- package/dist/src/kernel/render/renderers.js +3 -3
- package/dist/src/kernel/render/turtle.js +21 -4
- package/dist/src/kernel/render/turtle.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/build.js +54 -1
- package/dist/src/kernel/runtime/graphpack/build.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/manifest.js +14 -0
- package/dist/src/kernel/runtime/graphpack/manifest.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/read.js +3 -5
- package/dist/src/kernel/runtime/graphpack/read.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/schemas.js +1 -0
- package/dist/src/kernel/runtime/graphpack/schemas.js.map +1 -1
- package/dist/src/kernel/runtime/loadSession.js +8 -1
- package/dist/src/kernel/runtime/loadSession.js.map +1 -1
- package/dist/src/kernel/runtime/packVersion.js +75 -0
- package/dist/src/kernel/runtime/packVersion.js.map +1 -0
- package/dist/src/kernel/runtime/readEntity.js +43 -4
- package/dist/src/kernel/runtime/readEntity.js.map +1 -1
- package/dist/src/kernel/runtime/resolveSources.js +61 -9
- package/dist/src/kernel/runtime/resolveSources.js.map +1 -1
- package/dist/src/kernel/runtime/storeReadiness.js +3 -2
- package/dist/src/kernel/runtime/storeReadiness.js.map +1 -1
- package/dist/src/kernel/spec/call.js +167 -0
- package/dist/src/kernel/spec/call.js.map +1 -0
- package/dist/src/kernel/spec/emitReference.js +9 -13
- package/dist/src/kernel/spec/emitReference.js.map +1 -1
- package/dist/src/kernel/spec/emitSurface.js +8 -1
- package/dist/src/kernel/spec/emitSurface.js.map +1 -1
- package/dist/src/kernel/spec/guidance.js +63 -0
- package/dist/src/kernel/spec/guidance.js.map +1 -0
- package/dist/src/kernel/spec/wireType.js +36 -0
- package/dist/src/kernel/spec/wireType.js.map +1 -0
- package/package.json +14 -14
- package/pragma.conf.ts +294 -177
- package/src/capabilities/block.parity.test.ts +1 -1
- package/src/capabilities/callRule.test.ts +147 -0
- package/src/capabilities/capabilities/capabilities.test.ts +47 -19
- package/src/capabilities/capabilities/capabilities.verb.ts +2 -0
- package/src/capabilities/capabilities/catalog.ts +60 -55
- package/src/capabilities/capabilities/index.ts +2 -0
- package/src/capabilities/capabilities/types.ts +16 -19
- package/src/capabilities/colophon/colophon.render.ts +9 -5
- package/src/capabilities/colophon/colophon.verb.ts +1 -0
- package/src/capabilities/config/get.verb.ts +2 -0
- package/src/capabilities/config/runField.ts +3 -3
- package/src/capabilities/config/set.verb.ts +3 -0
- package/src/capabilities/config/show.verb.ts +2 -0
- package/src/capabilities/config/unset.verb.ts +2 -0
- package/src/capabilities/create/constants.ts +2 -2
- package/src/capabilities/create/create.verb.ts +16 -0
- package/src/capabilities/create/createGrammar.test.ts +1 -1
- package/src/capabilities/distribution.test.ts +6 -3
- package/src/capabilities/distribution.ts +6 -0
- package/src/capabilities/doctor/checks/checkPackageRefs.ts +14 -2
- package/src/capabilities/doctor/doctor.test.ts +38 -1
- package/src/capabilities/doctor/doctor.verb.ts +2 -0
- package/src/capabilities/emptyRecovery.test.ts +5 -0
- package/src/capabilities/graph/connect.verb.ts +2 -0
- package/src/capabilities/graph/inspect.render.ts +9 -1
- package/src/capabilities/graph/inspect.test.ts +73 -0
- package/src/capabilities/graph/inspect.verb.ts +2 -0
- package/src/capabilities/graph/query.render.test.ts +35 -0
- package/src/capabilities/graph/query.render.ts +45 -7
- package/src/capabilities/graph/query.shipped.exec.test.ts +44 -0
- package/src/capabilities/graph/query.verb.ts +2 -0
- package/src/capabilities/graph/queryText.test.ts +55 -1
- package/src/capabilities/graph/queryText.ts +47 -0
- package/src/capabilities/graph/runConnect.ts +4 -5
- package/src/capabilities/graph/runQuery.ts +23 -9
- package/src/capabilities/info/info.verb.ts +2 -0
- package/src/capabilities/links.shipped.exec.test.ts +398 -0
- package/src/capabilities/listBudget.shipped.exec.test.ts +2 -2
- package/src/capabilities/listLookup.shipped.exec.test.ts +122 -13
- package/src/capabilities/listQuery.shipped.exec.test.ts +39 -16
- package/src/capabilities/lookupBudget.shipped.exec.test.ts +208 -0
- package/src/capabilities/meta/mcp.verb.ts +2 -0
- package/src/capabilities/ontology/ontology.test.ts +24 -0
- package/src/capabilities/ontology/verbs.ts +16 -12
- package/src/capabilities/prompt/verbs.ts +10 -4
- package/src/capabilities/resources/provider.ts +7 -3
- package/src/capabilities/resources/resources.test.ts +2 -1
- package/src/capabilities/setup/setup.verb.ts +16 -0
- package/src/capabilities/shared/calls.ts +20 -0
- package/src/capabilities/skill/render.ts +9 -6
- package/src/capabilities/skill/verbs.ts +8 -4
- package/src/capabilities/sources/collectStatus.ts +6 -1
- package/src/capabilities/sources/index.ts +4 -3
- package/src/capabilities/sources/reset.render.test.ts +33 -0
- package/src/capabilities/sources/reset.render.ts +43 -0
- package/src/capabilities/sources/reset.verb.ts +59 -0
- package/src/capabilities/sources/runReset.ts +65 -0
- package/src/capabilities/sources/runUpdate.ts +12 -11
- package/src/capabilities/sources/sources.test.ts +158 -3
- package/src/capabilities/sources/status.render.test.ts +63 -1
- package/src/capabilities/sources/status.render.ts +25 -8
- package/src/capabilities/sources/status.verb.ts +2 -0
- package/src/capabilities/sources/types.ts +26 -0
- package/src/capabilities/sources/update.verb.ts +3 -0
- package/src/capabilities/surface.test.ts +2 -2
- package/src/capabilities/token.parity.test.ts +3 -2
- package/src/capabilities/tokenGraph.shipped.exec.test.ts +14 -2
- package/src/capabilities/upgrade/upgrade.verb.ts +1 -0
- package/src/capabilities/variable.parity.test.ts +2 -1
- package/src/capabilities/version/version.verb.ts +1 -0
- package/src/capabilities/wireContract.test.ts +110 -0
- package/src/constants.ts +4 -3
- package/src/kernel/copy.test.ts +1 -1
- package/src/kernel/error/PragmaError.test.ts +7 -3
- package/src/kernel/error/index.ts +1 -1
- package/src/kernel/error/recovery.test.ts +52 -18
- package/src/kernel/error/recovery.ts +25 -46
- package/src/kernel/error/types.ts +1 -1
- package/src/kernel/packs/collect.test.ts +79 -0
- package/src/kernel/packs/collect.ts +151 -11
- package/src/kernel/packs/compile.test.ts +107 -10
- package/src/kernel/packs/compile.ts +100 -11
- package/src/kernel/packs/filteredEmpty.test.ts +22 -5
- package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +5 -6
- package/src/kernel/packs/graphqlSource.test.ts +42 -0
- package/src/kernel/packs/iri.ts +5 -4
- package/src/kernel/packs/listNotice.test.ts +1 -1
- package/src/kernel/packs/nounLink.test.ts +385 -0
- package/src/kernel/packs/renderPack.ts +137 -57
- package/src/kernel/packs/resolveEntity.test.ts +170 -1
- package/src/kernel/packs/resolveEntity.ts +281 -101
- package/src/kernel/packs/runBodies.ts +133 -31
- package/src/kernel/packs/schema.ts +77 -12
- package/src/kernel/packs/sparql/buildListQuery.test.ts +78 -1
- package/src/kernel/packs/sparql/buildListQuery.ts +106 -16
- package/src/kernel/packs/sparql/buildLookupQuery.ts +44 -13
- package/src/kernel/packs/sparql/filterValues.test.ts +36 -4
- package/src/kernel/packs/sparql/filterValues.ts +50 -31
- package/src/kernel/packs/sparql/runSelect.ts +4 -6
- package/src/kernel/packs/storyRules.ts +37 -4
- package/src/kernel/packs/tierScope.test.ts +120 -6
- package/src/kernel/packs/tierScope.ts +5 -7
- package/src/kernel/packs/toolDescription.test.ts +59 -74
- package/src/kernel/packs/types.ts +93 -11
- package/src/kernel/project/cli/buildProgram.ts +4 -0
- package/src/kernel/project/cli/dispatch.ts +34 -8
- package/src/kernel/project/cli/suggest.test.ts +77 -0
- package/src/kernel/project/cli/suggestNames.ts +47 -1
- package/src/kernel/project/cli/verbHelp.ts +16 -19
- package/src/kernel/project/mcp/buildServer.ts +15 -4
- package/src/kernel/project/mcp/envelope.ts +3 -2
- package/src/kernel/project/mcp/instructions.ts +123 -35
- package/src/kernel/project/mcp/prompts/prompts.test.ts +136 -20
- package/src/kernel/project/mcp/registerVerb.test.ts +28 -0
- package/src/kernel/project/mcp/registerVerb.ts +67 -29
- package/src/kernel/render/__snapshots__/renderers.test.ts.snap +1 -1
- package/src/kernel/render/renderers.test.ts +2 -2
- package/src/kernel/render/renderers.ts +3 -3
- package/src/kernel/render/turtle.ts +24 -4
- package/src/kernel/runtime/graphpack/build.ts +58 -1
- package/src/kernel/runtime/graphpack/graphpack.test.ts +20 -0
- package/src/kernel/runtime/graphpack/manifest.ts +15 -0
- package/src/kernel/runtime/graphpack/read.ts +4 -6
- package/src/kernel/runtime/graphpack/schemas.ts +1 -0
- package/src/kernel/runtime/graphpack/types.ts +13 -0
- package/src/kernel/runtime/loadSession.ts +8 -1
- package/src/kernel/runtime/packVersion.test.ts +61 -0
- package/src/kernel/runtime/packVersion.ts +84 -0
- package/src/kernel/runtime/readEntity.ts +61 -6
- package/src/kernel/runtime/resolveSources.test.ts +90 -7
- package/src/kernel/runtime/resolveSources.ts +91 -10
- package/src/kernel/runtime/storeReadiness.ts +4 -4
- package/src/kernel/runtime/types.ts +12 -0
- package/src/kernel/spec/call.test.ts +95 -0
- package/src/kernel/spec/call.ts +186 -0
- package/src/kernel/spec/emitReference.ts +9 -13
- package/src/kernel/spec/emitSurface.ts +9 -1
- package/src/kernel/spec/guidance.test.ts +49 -0
- package/src/kernel/spec/guidance.ts +68 -0
- package/src/kernel/spec/index.ts +4 -0
- package/src/kernel/spec/surfaceConformance.test.ts +2 -2
- package/src/kernel/spec/types.ts +64 -7
- package/src/kernel/spec/wireType.ts +53 -0
- package/src/testing/behavioral/errorMatrix.mcp.test.ts +2 -1
- package/src/testing/behavioral/journeys.firstInstall.test.ts +72 -2
- package/src/testing/behavioral/parity.test.ts +40 -0
- package/src/testing/eval/__snapshots__/eval.test.ts.snap +1 -1
- package/src/testing/eval/cases/assembly.ts +4 -3
- package/src/testing/eval/cases/stable.ts +2 -3
- package/src/testing/helpers/callSchema.ts +40 -0
- package/src/testing/helpers/parity.ts +35 -2
- package/src/testing/setupCallChecking.ts +30 -0
- package/dist/src/capabilities/capabilities/hints.js +0 -223
- package/dist/src/capabilities/capabilities/hints.js.map +0 -1
- package/src/capabilities/capabilities/hints.ts +0 -266
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* (lazy), so these factories carry no heavy static import.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { PragmaError } from "../error/index.js";
|
|
14
14
|
import type { PragmaRuntime } from "../runtime/index.js";
|
|
15
15
|
import { encodeCursor, pageFingerprint, readCursor } from "./cursor.js";
|
|
16
16
|
import { resolvePackDetail } from "./disclosure.js";
|
|
@@ -20,10 +20,17 @@ import {
|
|
|
20
20
|
type LookupOutput,
|
|
21
21
|
type LookupScope,
|
|
22
22
|
listEntityNames,
|
|
23
|
+
listRecovery,
|
|
24
|
+
resolveEntityIris,
|
|
23
25
|
resolveLookup,
|
|
24
26
|
} from "./resolveEntity.js";
|
|
25
27
|
import { parseSampleCount, pickRandom } from "./sample.js";
|
|
26
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
buildListQuery,
|
|
30
|
+
buildTierCountQuery,
|
|
31
|
+
type ListPredicate,
|
|
32
|
+
type ListTierScope,
|
|
33
|
+
} from "./sparql/buildListQuery.js";
|
|
27
34
|
import {
|
|
28
35
|
type FilterVocabularies,
|
|
29
36
|
resolveFilterPredicates,
|
|
@@ -31,6 +38,7 @@ import {
|
|
|
31
38
|
import { runSelect } from "./sparql/runSelect.js";
|
|
32
39
|
import { readSearchTerm } from "./sparql/searchTerm.js";
|
|
33
40
|
import {
|
|
41
|
+
localName,
|
|
34
42
|
resolveReadScope,
|
|
35
43
|
scopeIris,
|
|
36
44
|
scopeLabel,
|
|
@@ -38,34 +46,21 @@ import {
|
|
|
38
46
|
} from "./tierScope.js";
|
|
39
47
|
import {
|
|
40
48
|
ENTITY_VARIABLE,
|
|
49
|
+
type NounLookups,
|
|
41
50
|
type PackAppliedFilter,
|
|
42
51
|
type PackFilter,
|
|
43
52
|
type PackList,
|
|
44
53
|
type PackLookup,
|
|
45
54
|
type PackPage,
|
|
46
55
|
type PackTierScope,
|
|
56
|
+
type PageTierScope,
|
|
47
57
|
type StorySource,
|
|
58
|
+
UNTIERED_KEY,
|
|
48
59
|
} from "./types.js";
|
|
49
60
|
|
|
50
61
|
/** The highest canonical level — sample fetches everything for shape discovery. */
|
|
51
62
|
const HIGHEST_LEVEL = "detailed";
|
|
52
63
|
|
|
53
|
-
/**
|
|
54
|
-
* The recovery every lookup-shaped miss carries: browse the noun's list.
|
|
55
|
-
*
|
|
56
|
-
* `mcp.params` is populated rather than left off. A bare tool NAME is only half
|
|
57
|
-
* an instruction to an agent — it still has to guess an argument bag, and a
|
|
58
|
-
* guess that misses returns `-32602 Invalid arguments`, which reads like the
|
|
59
|
-
* recovery itself was wrong. `{}` is the concrete, valid call: every compiled
|
|
60
|
-
* list verb's params are optional, so the recovery is now copy-pasteable.
|
|
61
|
-
*/
|
|
62
|
-
function listRecovery(noun: string) {
|
|
63
|
-
return cliRecovery(`${noun} list`, `List available ${noun} entries.`, {
|
|
64
|
-
tool: `${noun}_list`,
|
|
65
|
-
params: {},
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
64
|
/** Facts a list-shaped run body needs beyond its `shape`. */
|
|
70
65
|
export interface ListRunMeta {
|
|
71
66
|
readonly source: StorySource;
|
|
@@ -75,6 +70,10 @@ export interface ListRunMeta {
|
|
|
75
70
|
* are. Absent leaves the read exactly as it was.
|
|
76
71
|
*/
|
|
77
72
|
readonly tierScope?: PackTierScope;
|
|
73
|
+
/** The prefix map a prefixed IRI handed to a noun filter expands against. */
|
|
74
|
+
readonly prefixes: Readonly<Record<string, string>>;
|
|
75
|
+
/** The other stories' lookups, for a filter that names a noun. */
|
|
76
|
+
readonly nouns?: NounLookups;
|
|
78
77
|
}
|
|
79
78
|
|
|
80
79
|
/**
|
|
@@ -104,12 +103,20 @@ export function makeListRun(
|
|
|
104
103
|
params,
|
|
105
104
|
meta.source,
|
|
106
105
|
);
|
|
107
|
-
const predicates =
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
106
|
+
const predicates = [
|
|
107
|
+
...resolveFilterPredicates(
|
|
108
|
+
shape.filters?.filter((filter) => filter.noun === undefined),
|
|
109
|
+
params,
|
|
110
|
+
vocabularies,
|
|
111
|
+
meta.source.label,
|
|
112
|
+
),
|
|
113
|
+
...(await resolveNounPredicates(rt, shape.filters, params, meta)),
|
|
114
|
+
];
|
|
115
|
+
// Left out of the rows: over the shipped pack the IRIs a noun filter
|
|
116
|
+
// constrains took the largest list past its payload budget (108 KB).
|
|
117
|
+
const omit = (shape.filters ?? [])
|
|
118
|
+
.flatMap((filter) => (filter.entity ? [filter.entity] : []))
|
|
119
|
+
.filter((name) => !shape.columns.some((column) => column.field === name));
|
|
113
120
|
const search = readSearchTerm(shape.search, params);
|
|
114
121
|
const scope = await resolveReadScope(
|
|
115
122
|
rt,
|
|
@@ -142,18 +149,31 @@ export function makeListRun(
|
|
|
142
149
|
...(listScope ? [JSON.stringify(tiers)] : []),
|
|
143
150
|
]);
|
|
144
151
|
const offset = readCursor(params.after, fingerprint);
|
|
152
|
+
const read = {
|
|
153
|
+
query: shape.query,
|
|
154
|
+
predicates,
|
|
155
|
+
...(search ? { search } : {}),
|
|
156
|
+
label: meta.source.label,
|
|
157
|
+
};
|
|
145
158
|
const rows = await runSelect(
|
|
146
159
|
rt,
|
|
147
160
|
buildListQuery({
|
|
148
|
-
|
|
149
|
-
predicates,
|
|
150
|
-
...(search ? { search } : {}),
|
|
161
|
+
...read,
|
|
151
162
|
...(listScope ? { scope: listScope } : {}),
|
|
163
|
+
omit,
|
|
152
164
|
window: { limit: limit + 1, offset },
|
|
153
|
-
label: meta.source.label,
|
|
154
165
|
}),
|
|
155
166
|
meta.source,
|
|
156
167
|
);
|
|
168
|
+
// First page only, and never the reason a list fails.
|
|
169
|
+
const counts =
|
|
170
|
+
scope?.kind === "tiers" && listScope && params.after === undefined
|
|
171
|
+
? await readTierCounts(
|
|
172
|
+
rt,
|
|
173
|
+
{ ...read, scope: listScope },
|
|
174
|
+
meta.source,
|
|
175
|
+
).catch(() => undefined)
|
|
176
|
+
: undefined;
|
|
157
177
|
const hasMore = rows.length > limit;
|
|
158
178
|
const applied = appliedFilters(shape, params, search?.term, scope);
|
|
159
179
|
return {
|
|
@@ -163,13 +183,45 @@ export function makeListRun(
|
|
|
163
183
|
: {}),
|
|
164
184
|
...(applied.length > 0 ? { filters: applied } : {}),
|
|
165
185
|
...(scope?.kind === "tiers"
|
|
166
|
-
? {
|
|
186
|
+
? {
|
|
187
|
+
scope: {
|
|
188
|
+
tiers: scope.tiers.map((tier) => tier.local),
|
|
189
|
+
...(counts ? { counts } : {}),
|
|
190
|
+
},
|
|
191
|
+
}
|
|
167
192
|
: {}),
|
|
168
193
|
limit,
|
|
169
194
|
};
|
|
170
195
|
};
|
|
171
196
|
}
|
|
172
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Count the whole filtered answer's rows per tier: every in-scope tier (0 when
|
|
200
|
+
* it holds none), then the out-of-scope tiers that hold some, then
|
|
201
|
+
* {@link UNTIERED_KEY} when any row's entity is in no tier.
|
|
202
|
+
*/
|
|
203
|
+
async function readTierCounts(
|
|
204
|
+
rt: PragmaRuntime,
|
|
205
|
+
read: Parameters<typeof buildTierCountQuery>[0] & { scope: ListTierScope },
|
|
206
|
+
source: StorySource,
|
|
207
|
+
): Promise<NonNullable<PageTierScope["counts"]>> {
|
|
208
|
+
const query = buildTierCountQuery(read);
|
|
209
|
+
const byIri = query.read(await runSelect(rt, query.text, source));
|
|
210
|
+
const inScope = read.scope.tiers.map(
|
|
211
|
+
(iri) => [localName(iri), byIri.get(iri) ?? 0] as const,
|
|
212
|
+
);
|
|
213
|
+
const outside = [...byIri]
|
|
214
|
+
.filter(([iri]) => iri !== "" && !read.scope.tiers.includes(iri))
|
|
215
|
+
.map(([iri, count]) => [localName(iri), count] as const)
|
|
216
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
217
|
+
const untiered = byIri.get("");
|
|
218
|
+
return Object.fromEntries([
|
|
219
|
+
...inScope,
|
|
220
|
+
...outside,
|
|
221
|
+
...(untiered ? [[UNTIERED_KEY, untiered] as const] : []),
|
|
222
|
+
]);
|
|
223
|
+
}
|
|
224
|
+
|
|
173
225
|
/**
|
|
174
226
|
* The filters this read was narrowed by, in the spelling the caller used.
|
|
175
227
|
*
|
|
@@ -259,6 +311,52 @@ async function readFilterVocabularies(
|
|
|
259
311
|
return resolved;
|
|
260
312
|
}
|
|
261
313
|
|
|
314
|
+
/**
|
|
315
|
+
* Turn the values a caller supplied for each NOUN filter into a constraint on
|
|
316
|
+
* the entities they name.
|
|
317
|
+
*
|
|
318
|
+
* @returns One `"iri"` predicate per noun filter the caller used, over the
|
|
319
|
+
* IRIs its values reached. Against a store holding none of that noun's
|
|
320
|
+
* entities the predicate names no IRI and the list answers empty.
|
|
321
|
+
* @throws PragmaError INVALID_INPUT when a value reaches no entity of the noun;
|
|
322
|
+
* CONFIG_ERROR when no story known to this one declares a lookup for it.
|
|
323
|
+
*/
|
|
324
|
+
async function resolveNounPredicates(
|
|
325
|
+
rt: PragmaRuntime,
|
|
326
|
+
filters: readonly PackFilter[] | undefined,
|
|
327
|
+
params: Record<string, unknown>,
|
|
328
|
+
meta: ListRunMeta,
|
|
329
|
+
): Promise<ListPredicate[]> {
|
|
330
|
+
const predicates: ListPredicate[] = [];
|
|
331
|
+
for (const filter of filters ?? []) {
|
|
332
|
+
const provided = params[filter.param];
|
|
333
|
+
if (!filter.noun || !filter.entity || provided === undefined) continue;
|
|
334
|
+
const occurrences = Array.isArray(provided) ? provided : [provided];
|
|
335
|
+
if (occurrences.length === 0) continue;
|
|
336
|
+
const lookup = meta.nouns?.(filter.noun);
|
|
337
|
+
if (!lookup) {
|
|
338
|
+
throw PragmaError.configError(
|
|
339
|
+
`Filter "--${filter.param}" in ${meta.source.label} names the noun "${filter.noun}", and no story declares a lookup for it.`,
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
const terms = await resolveEntityIris(
|
|
343
|
+
rt,
|
|
344
|
+
lookup,
|
|
345
|
+
filter.noun,
|
|
346
|
+
occurrences.map((value) => String(value).trim().normalize("NFC")),
|
|
347
|
+
meta.source,
|
|
348
|
+
meta.prefixes,
|
|
349
|
+
);
|
|
350
|
+
predicates.push({
|
|
351
|
+
variable: filter.entity,
|
|
352
|
+
match: "iri",
|
|
353
|
+
terms,
|
|
354
|
+
...(filter.via ? { via: filter.via } : {}),
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
return predicates;
|
|
358
|
+
}
|
|
359
|
+
|
|
262
360
|
/**
|
|
263
361
|
* Build the run body for a lookup verb (variadic names → resolved entities).
|
|
264
362
|
*
|
|
@@ -274,6 +372,7 @@ export function makeLookupRun(
|
|
|
274
372
|
noun: string,
|
|
275
373
|
source: StorySource,
|
|
276
374
|
prefixes: Readonly<Record<string, string>>,
|
|
375
|
+
hasList: boolean,
|
|
277
376
|
tierScope?: PackTierScope,
|
|
278
377
|
): (
|
|
279
378
|
params: Record<string, unknown>,
|
|
@@ -291,6 +390,7 @@ export function makeLookupRun(
|
|
|
291
390
|
source,
|
|
292
391
|
prefixes,
|
|
293
392
|
level,
|
|
393
|
+
hasList,
|
|
294
394
|
lookupScope(tierScope, scope),
|
|
295
395
|
);
|
|
296
396
|
// A total miss (single or all-miss) exits non-zero; a partial batch renders
|
|
@@ -302,7 +402,7 @@ export function makeLookupRun(
|
|
|
302
402
|
code: first.code as PragmaError["code"],
|
|
303
403
|
message: first.message,
|
|
304
404
|
suggestions: first.suggestions ? [...first.suggestions] : undefined,
|
|
305
|
-
recovery: listRecovery(noun),
|
|
405
|
+
recovery: listRecovery(noun, hasList),
|
|
306
406
|
});
|
|
307
407
|
}
|
|
308
408
|
}
|
|
@@ -346,6 +446,7 @@ export function makeSampleRun(
|
|
|
346
446
|
source: StorySource,
|
|
347
447
|
prefixes: Readonly<Record<string, string>>,
|
|
348
448
|
defaultCount: number,
|
|
449
|
+
hasList: boolean,
|
|
349
450
|
): (
|
|
350
451
|
params: Record<string, unknown>,
|
|
351
452
|
rt: PragmaRuntime,
|
|
@@ -358,7 +459,7 @@ export function makeSampleRun(
|
|
|
358
459
|
if (names.length === 0) {
|
|
359
460
|
throw PragmaError.emptyResults(noun, {
|
|
360
461
|
message: `No ${noun} entries to sample.`,
|
|
361
|
-
recovery: listRecovery(noun),
|
|
462
|
+
recovery: listRecovery(noun, hasList),
|
|
362
463
|
});
|
|
363
464
|
}
|
|
364
465
|
const selected = pickRandom(names, count);
|
|
@@ -370,6 +471,7 @@ export function makeSampleRun(
|
|
|
370
471
|
source,
|
|
371
472
|
prefixes,
|
|
372
473
|
HIGHEST_LEVEL,
|
|
474
|
+
hasList,
|
|
373
475
|
);
|
|
374
476
|
return {
|
|
375
477
|
samples: output.results,
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
import { z } from "zod";
|
|
28
|
-
import { DETAIL_LEVELS
|
|
28
|
+
import { DETAIL_LEVELS } from "../../constants.js";
|
|
29
29
|
import { PragmaError } from "../error/index.js";
|
|
30
30
|
import { listShapeIssues, tierScopeIssues } from "./storyRules.js";
|
|
31
31
|
import {
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
type PackList,
|
|
34
34
|
type PackTierScope,
|
|
35
35
|
RESERVED_STORY_PARAMS,
|
|
36
|
+
VERB_PATH_PATTERN,
|
|
36
37
|
} from "./types.js";
|
|
37
38
|
|
|
38
39
|
const NOUN_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
@@ -96,8 +97,14 @@ const term = z.string().regex(TERM_PATTERN, "must be a prefixed name or IRI");
|
|
|
96
97
|
const graphqlName = z.string().regex(GRAPHQL_NAME_PATTERN);
|
|
97
98
|
const fieldName = z.string().regex(FIELD_PATTERN);
|
|
98
99
|
|
|
100
|
+
const nounName = z.string().regex(NOUN_PATTERN, NOUN_MESSAGE);
|
|
101
|
+
|
|
99
102
|
const columnSchema = z
|
|
100
|
-
.object({
|
|
103
|
+
.object({
|
|
104
|
+
field: fieldName,
|
|
105
|
+
label: z.string().optional(),
|
|
106
|
+
noun: nounName.optional(),
|
|
107
|
+
})
|
|
101
108
|
.strict();
|
|
102
109
|
|
|
103
110
|
const vocabularySchema = z
|
|
@@ -114,12 +121,22 @@ const filterSchema = z
|
|
|
114
121
|
values: z.array(z.string()).min(1).optional(),
|
|
115
122
|
match: z.enum(["exact", "set"]).optional(),
|
|
116
123
|
vocabulary: vocabularySchema.optional(),
|
|
124
|
+
noun: nounName.optional(),
|
|
125
|
+
entity: fieldName.optional(),
|
|
126
|
+
via: term.optional(),
|
|
117
127
|
description: z.string().optional(),
|
|
118
128
|
})
|
|
119
129
|
.strict()
|
|
120
130
|
.refine((f) => !RESERVED_PARAMS.has(f.param), {
|
|
121
131
|
message: "filter param is a reserved name",
|
|
122
132
|
})
|
|
133
|
+
.refine((f) => !(f.noun && (f.values || f.vocabulary || f.match)), {
|
|
134
|
+
message:
|
|
135
|
+
'"noun" is mutually exclusive with "values", "vocabulary" and "match" — the named noun admits the values and the rows are matched by IRI',
|
|
136
|
+
})
|
|
137
|
+
.refine((f) => f.noun !== undefined || !(f.entity || f.via), {
|
|
138
|
+
message: '"entity" and "via" belong to a filter that names a "noun"',
|
|
139
|
+
})
|
|
123
140
|
// A declared `values` set IS the vocabulary — the filter projects it as an
|
|
124
141
|
// enum and canonicalizes against it. A second, graph-read one alongside would
|
|
125
142
|
// be a silent no-op at best and a disagreement at worst.
|
|
@@ -144,25 +161,44 @@ const searchSchema = z
|
|
|
144
161
|
})
|
|
145
162
|
.strict();
|
|
146
163
|
|
|
164
|
+
// A call names a VERB PATH (`sources update`) and a param bag — never a command
|
|
165
|
+
// line. A story is portable only if it names no binary and spells no flags: the
|
|
166
|
+
// consuming distribution's renderer does both, for the surface it prints on.
|
|
167
|
+
const callSchema = z
|
|
168
|
+
.object({
|
|
169
|
+
verb: z
|
|
170
|
+
.string()
|
|
171
|
+
.regex(
|
|
172
|
+
VERB_PATH_PATTERN,
|
|
173
|
+
'a call names a verb path — write "sources update", not a command line',
|
|
174
|
+
),
|
|
175
|
+
params: z.record(z.string(), z.unknown()).optional(),
|
|
176
|
+
})
|
|
177
|
+
.strict();
|
|
178
|
+
|
|
147
179
|
const emptyRecoverySchema = z
|
|
148
180
|
.object({
|
|
149
181
|
message: z.string().min(1),
|
|
150
|
-
|
|
151
|
-
//
|
|
152
|
-
// the old
|
|
153
|
-
// loudly, in the `packages` → `packs` tradition, because a third-party pack
|
|
154
|
-
// written against the old grammar is data, not a typo.
|
|
182
|
+
call: callSchema.optional(),
|
|
183
|
+
// The retired spelling, named loudly in the `packages` → `packs` tradition:
|
|
184
|
+
// a third-party pack written against the old grammar is data, not a typo.
|
|
155
185
|
cli: z
|
|
156
|
-
.
|
|
157
|
-
.min(1)
|
|
186
|
+
.unknown()
|
|
158
187
|
.refine(
|
|
159
|
-
(value) =>
|
|
160
|
-
|
|
188
|
+
(value) => value === undefined,
|
|
189
|
+
'emptyRecovery.cli is now emptyRecovery.call — write call: { verb: "sources update" }, not cli: "sources update"',
|
|
161
190
|
)
|
|
162
191
|
.optional(),
|
|
163
192
|
})
|
|
164
193
|
.strict();
|
|
165
194
|
|
|
195
|
+
// What a story half tells a caller choosing between tools. Optional: the schema
|
|
196
|
+
// is strict and third-party packs predate these.
|
|
197
|
+
const guidanceShape = {
|
|
198
|
+
useWhen: z.string().min(1).optional(),
|
|
199
|
+
example: z.record(z.string(), z.unknown()).optional(),
|
|
200
|
+
};
|
|
201
|
+
|
|
166
202
|
const listShape = {
|
|
167
203
|
query: z.string().min(1),
|
|
168
204
|
columns: z.array(columnSchema).min(1),
|
|
@@ -178,6 +214,7 @@ const verbSchema = z
|
|
|
178
214
|
verb: z.string().regex(NOUN_PATTERN, NOUN_MESSAGE),
|
|
179
215
|
description: z.string().optional(),
|
|
180
216
|
toolDescription: z.string().optional(),
|
|
217
|
+
...guidanceShape,
|
|
181
218
|
})
|
|
182
219
|
.strict();
|
|
183
220
|
|
|
@@ -188,6 +225,7 @@ const fieldSchema = z
|
|
|
188
225
|
label: z.string().optional(),
|
|
189
226
|
graphqlField: graphqlName.optional(),
|
|
190
227
|
level: z.string().optional(),
|
|
228
|
+
noun: nounName.optional(),
|
|
191
229
|
})
|
|
192
230
|
.strict();
|
|
193
231
|
|
|
@@ -223,8 +261,14 @@ const expandFieldSchema = z
|
|
|
223
261
|
label: z.string().optional(),
|
|
224
262
|
graphqlField: graphqlName.optional(),
|
|
225
263
|
blankWhenSelf: z.literal(true).optional(),
|
|
264
|
+
many: z.literal(true).optional(),
|
|
265
|
+
noun: nounName.optional(),
|
|
226
266
|
})
|
|
227
|
-
.strict()
|
|
267
|
+
.strict()
|
|
268
|
+
.refine((f) => !(f.many && f.blankWhenSelf), {
|
|
269
|
+
message:
|
|
270
|
+
'"many" and "blankWhenSelf" are mutually exclusive — a set of values has no single one to compare with the entity\'s own',
|
|
271
|
+
});
|
|
228
272
|
|
|
229
273
|
const expandSchema = z
|
|
230
274
|
.object({
|
|
@@ -276,6 +320,7 @@ const sampleSchema = z.union([
|
|
|
276
320
|
fixedCount: z.boolean().optional(),
|
|
277
321
|
description: z.string().optional(),
|
|
278
322
|
toolDescription: z.string().optional(),
|
|
323
|
+
...guidanceShape,
|
|
279
324
|
})
|
|
280
325
|
.strict(),
|
|
281
326
|
]);
|
|
@@ -328,6 +373,7 @@ const lookupSchema = z
|
|
|
328
373
|
type: term.optional(),
|
|
329
374
|
description: z.string().optional(),
|
|
330
375
|
toolDescription: z.string().optional(),
|
|
376
|
+
...guidanceShape,
|
|
331
377
|
types: z.array(term).min(1).optional(),
|
|
332
378
|
weights: z.record(term, z.number().min(0).max(1)).optional(),
|
|
333
379
|
scopeWeight: scopeWeightSchema.optional(),
|
|
@@ -346,6 +392,7 @@ const definitionSchema = z
|
|
|
346
392
|
noun: z.string().regex(NOUN_PATTERN, NOUN_MESSAGE),
|
|
347
393
|
description: z.string().optional(),
|
|
348
394
|
toolDescription: z.string().optional(),
|
|
395
|
+
...guidanceShape,
|
|
349
396
|
list: listSchema.optional(),
|
|
350
397
|
verbs: z.array(verbSchema).min(1).optional(),
|
|
351
398
|
lookup: lookupSchema.optional(),
|
|
@@ -599,6 +646,24 @@ function refineLookup(
|
|
|
599
646
|
path: ["lookup"],
|
|
600
647
|
});
|
|
601
648
|
}
|
|
649
|
+
if ("many" in entry && entry.many && expandSource !== "sparql") {
|
|
650
|
+
ctx.addIssue({
|
|
651
|
+
code: z.ZodIssueCode.custom,
|
|
652
|
+
message: `expand "${expand.name}" field "${entry.name}" sets "many", which only the SPARQL lane can express — set the expand's "source" to "sparql".`,
|
|
653
|
+
path: ["lookup"],
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
if (
|
|
657
|
+
"many" in entry &&
|
|
658
|
+
entry.many &&
|
|
659
|
+
expand.orderBy?.includes(entry.name)
|
|
660
|
+
) {
|
|
661
|
+
ctx.addIssue({
|
|
662
|
+
code: z.ZodIssueCode.custom,
|
|
663
|
+
message: `expand "${expand.name}" orders by "${entry.name}", a "many" field — a set of values has no order to sort on.`,
|
|
664
|
+
path: ["lookup"],
|
|
665
|
+
});
|
|
666
|
+
}
|
|
602
667
|
}
|
|
603
668
|
if (expand.orderBy !== undefined) {
|
|
604
669
|
if (expandSource !== "sparql") {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { describe, expect, it } from "vitest";
|
|
14
14
|
import { PragmaError } from "../../error/index.js";
|
|
15
|
-
import { buildListQuery } from "./buildListQuery.js";
|
|
15
|
+
import { buildListQuery, buildTierCountQuery } from "./buildListQuery.js";
|
|
16
16
|
|
|
17
17
|
/** The author query every case below wraps: aggregate, grouped, ordered. */
|
|
18
18
|
const AUTHORED = [
|
|
@@ -342,3 +342,80 @@ describe("buildListQuery — a story it cannot serve", () => {
|
|
|
342
342
|
);
|
|
343
343
|
});
|
|
344
344
|
});
|
|
345
|
+
|
|
346
|
+
describe("buildTierCountQuery — the whole filtered answer, per tier", () => {
|
|
347
|
+
const scope = {
|
|
348
|
+
entity: "uri",
|
|
349
|
+
via: "ds:tier",
|
|
350
|
+
tiers: ["https://ds.canonical.com/global"],
|
|
351
|
+
};
|
|
352
|
+
const build = (query = AUTHORED) =>
|
|
353
|
+
buildTierCountQuery({
|
|
354
|
+
query,
|
|
355
|
+
predicates: [{ variable: "category", match: "exact", terms: ["react"] }],
|
|
356
|
+
search: { variables: ["name"], term: "props" },
|
|
357
|
+
scope,
|
|
358
|
+
label,
|
|
359
|
+
}).text;
|
|
360
|
+
|
|
361
|
+
it("wraps the author query under the page's own filter and search clauses", () => {
|
|
362
|
+
const page = buildListQuery({
|
|
363
|
+
query: AUTHORED,
|
|
364
|
+
predicates: [{ variable: "category", match: "exact", terms: ["react"] }],
|
|
365
|
+
search: { variables: ["name"], term: "props" },
|
|
366
|
+
window,
|
|
367
|
+
label,
|
|
368
|
+
});
|
|
369
|
+
const clauses = page
|
|
370
|
+
.split("\n")
|
|
371
|
+
.filter((line) => line.startsWith(" FILTER EXISTS"));
|
|
372
|
+
expect(clauses).toHaveLength(2);
|
|
373
|
+
for (const clause of clauses) expect(build()).toContain(clause);
|
|
374
|
+
// The author's text is inside it verbatim, as it is inside the page.
|
|
375
|
+
expect(build()).toContain(AUTHORED);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it("groups by the row's tier, which is OPTIONAL so an untiered row counts", () => {
|
|
379
|
+
expect(build()).toMatch(
|
|
380
|
+
/SELECT \?(\w+) \(COUNT\(\*\) AS \?\w+\)[\s\S]*OPTIONAL \{ \?uri ds:tier \?\1 \}\n\}\nGROUP BY \?\1$/,
|
|
381
|
+
);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it("reads its rows as tier → row count, the unbound tier as the empty key", () => {
|
|
385
|
+
const query = buildTierCountQuery({
|
|
386
|
+
query: AUTHORED,
|
|
387
|
+
predicates: [],
|
|
388
|
+
scope,
|
|
389
|
+
label,
|
|
390
|
+
});
|
|
391
|
+
const [, tier, count] =
|
|
392
|
+
/SELECT \?(\w+) \(COUNT\(\*\) AS \?(\w+)\)/.exec(query.text) ?? [];
|
|
393
|
+
const rows = [
|
|
394
|
+
{
|
|
395
|
+
[tier as string]: "https://ds.canonical.com/apps",
|
|
396
|
+
[count as string]: "3",
|
|
397
|
+
},
|
|
398
|
+
{ [count as string]: "1" },
|
|
399
|
+
];
|
|
400
|
+
expect([...query.read(rows)]).toEqual([
|
|
401
|
+
["https://ds.canonical.com/apps", 3],
|
|
402
|
+
["", 1],
|
|
403
|
+
]);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("carries neither the page nor the scope it is the breakdown OF", () => {
|
|
407
|
+
expect(build()).not.toMatch(/\bLIMIT\b|\bOFFSET\b/);
|
|
408
|
+
expect(build()).not.toContain("https://ds.canonical.com/global");
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it("refuses an author query already using the generated prefix, filtered or not", () => {
|
|
412
|
+
expect(() =>
|
|
413
|
+
buildTierCountQuery({
|
|
414
|
+
query: "SELECT ?uri ?__pragmaX WHERE { ?uri a ds:Thing }",
|
|
415
|
+
predicates: [],
|
|
416
|
+
scope,
|
|
417
|
+
label,
|
|
418
|
+
}),
|
|
419
|
+
).toThrow(PragmaError);
|
|
420
|
+
});
|
|
421
|
+
});
|