dcql
Version:
Digital Credentials Query Language (DCQL)
1,274 lines (1,249 loc) • 58.9 kB
JavaScript
// src/dcql-error/e-base.ts
function isObject(value) {
return !!value && !Array.isArray(value) && typeof value === "object";
}
var UnknownCauseError = class extends Error {
};
function getCauseFromUnknown(cause) {
if (cause instanceof Error) {
return cause;
}
const type = typeof cause;
if (type === "undefined" || type === "function" || cause === null) {
return void 0;
}
if (type !== "object") {
return new Error(String(cause));
}
if (isObject(cause)) {
const err = new UnknownCauseError();
for (const key in cause) {
err[key] = cause[key];
}
return err;
}
return void 0;
}
var isDcqlError = (cause) => {
if (cause instanceof DcqlError) {
return true;
}
if (cause instanceof Error && cause.name === "DcqlError") {
return true;
}
return false;
};
function getDcqlErrorFromUnknown(cause) {
if (isDcqlError(cause)) {
return cause;
}
const dcqlError = new DcqlError({
code: "INTERNAL_SERVER_ERROR",
cause
});
if (cause instanceof Error && cause.stack) {
dcqlError.stack = cause.stack;
}
return dcqlError;
}
var DcqlError = class extends Error {
constructor(opts) {
const cause = getCauseFromUnknown(opts.cause);
const message = opts.message ?? cause?.message ?? opts.code;
super(message, { cause });
this.code = opts.code;
this.name = "DcqlError";
if (!this.cause) {
this.cause = cause;
}
}
};
// src/dcql-error/e-dcql.ts
var DcqlCredentialSetError = class extends DcqlError {
constructor(opts) {
super({ code: "BAD_REQUEST", ...opts });
}
};
var DcqlUndefinedClaimSetIdError = class extends DcqlError {
constructor(opts) {
super({ code: "BAD_REQUEST", ...opts });
}
};
var DcqlNonUniqueCredentialQueryIdsError = class extends DcqlError {
constructor(opts) {
super({ code: "BAD_REQUEST", ...opts });
}
};
var DcqlParseError = class extends DcqlError {
constructor(opts) {
super({ code: "PARSE_ERROR", ...opts });
}
};
var DcqlInvalidClaimsQueryIdError = class extends DcqlError {
constructor(opts) {
super({ code: "BAD_REQUEST", ...opts });
}
};
var DcqlMissingClaimSetParseError = class extends DcqlError {
constructor(opts) {
super({ code: "PARSE_ERROR", ...opts });
}
};
var DcqlInvalidPresentationRecordError = class extends DcqlError {
constructor(opts) {
super({ code: "BAD_REQUEST", ...opts });
}
};
var DcqlPresentationResultError = class extends DcqlError {
constructor(opts) {
super({ code: "BAD_REQUEST", ...opts });
}
};
// src/dcql-presentation/m-dcql-credential-presentation.ts
import * as v5 from "valibot";
// src/u-dcql-credential.ts
import * as v4 from "valibot";
// src/dcql-query/m-dcql-trusted-authorities.ts
import * as v2 from "valibot";
// src/u-dcql.ts
import * as v from "valibot";
var idRegex = /^[a-zA-Z0-9_-]+$/;
function asNonEmptyArrayOrUndefined(array8) {
return array8.length > 0 ? array8 : void 0;
}
function isNonEmptyArray(array8) {
return array8.length > 0;
}
var vNonEmptyArray = (item) => {
return v.pipe(
v.array(item, (i) => `Expected input to be an array, but received '${i.received}'`),
v.custom(
(input) => input.length > 0,
"Array must be non-empty and have length of at least 1"
)
);
};
var vIncludesAll = (subset) => {
return v.custom(
(value) => {
if (!Array.isArray(value)) return false;
return subset.every((item) => value.includes(item));
},
`Value must include all of: ${subset.join(", ")}`
);
};
var vIdString = v.pipe(v.string(), v.regex(idRegex), v.nonEmpty());
var vBase64url = v.regex(/^(?:[\w-]{4})*(?:[\w-]{2}(?:==)?|[\w-]{3}=?)?$/iu, "must be base64url");
function isToJsonable(value) {
if (value === null || typeof value !== "object") return false;
const toJsonFn = value.toJson;
return typeof toJsonFn === "function";
}
var vWithJT = (schema) => v.pipe(
v.custom(() => true),
v.rawTransform(({ dataset, addIssue, NEVER }) => {
const result = v.safeParse(schema, dataset.value);
if (result.success) return dataset.value;
if (!isToJsonable(dataset.value)) {
for (const safeParseIssue of result.issues) {
addIssue({
...safeParseIssue,
expected: safeParseIssue.expected ?? void 0
});
}
return NEVER;
}
let json;
try {
json = dataset.value.toJson();
} catch (error) {
for (const safeParseIssue of result.issues) {
addIssue({
...safeParseIssue,
expected: safeParseIssue.expected ?? void 0
});
}
addIssue({ message: "Json Transformation failed" });
return NEVER;
}
const safeParseResult = v.safeParse(schema, json);
if (safeParseResult.success) return dataset.value;
for (const safeParseIssue of safeParseResult.issues) {
addIssue({
...safeParseIssue,
expected: safeParseIssue.expected ?? void 0
});
}
return NEVER;
})
);
var vJsonLiteral = v.union([v.string(), v.number(), v.boolean(), v.null()]);
var vJson = v.lazy(
() => v.union([vJsonLiteral, v.array(vJson), v.record(v.string(), vJson)])
);
var vJsonWithJT = v.lazy(
() => vWithJT(v.union([vJsonLiteral, v.array(vJson), v.record(v.string(), vJson)]))
);
var vJsonRecord = v.record(v.string(), vJson);
var vStringToJson = v.rawTransform(({ dataset, addIssue, NEVER }) => {
try {
return JSON.parse(dataset.value);
} catch (error) {
addIssue({ message: "Invalid JSON" });
return NEVER;
}
});
// src/dcql-query/m-dcql-trusted-authorities.ts
var getTrustedAuthorityParser = (trustedAuthority) => v2.pipe(
v2.object(
{
type: v2.literal(
trustedAuthority.type,
(i) => `Expected trusted authority type to be '${trustedAuthority.type}' but received ${typeof i.input === "string" ? `'${i.input}'` : i.input}`
),
// Some trusted authorities support an array as input type
values: v2.pipe(
vNonEmptyArray(v2.string()),
v2.someItem(
(item) => trustedAuthority.values.includes(item),
(i) => `Expected one of the trusted authority values to be '${trustedAuthority.values.join("' | '")}' but received '${i.input.join("' , '")}'`
)
)
},
`Expected trusted authority object with type '${trustedAuthority.type}' to be defined, but received undefined`
),
v2.transform(({ values, ...rest }) => ({
...rest,
value: values.find((value) => trustedAuthority.values.includes(value))
}))
);
var vAuthorityKeyIdentifier = v2.object({
type: v2.literal("aki"),
values: vNonEmptyArray(
v2.pipe(
v2.string("aki trusted authority value must be a string"),
vBase64url,
v2.description(
"Contains a list of KeyIdentifier entries of the AuthorityKeyIdentifier as defined in Section 4.2.1.1 of [RFC5280], encoded as base64url. The raw byte representation of one of the elements MUST match with the AuthorityKeyIdentifier element of an X.509 certificate in the certificate chain present in the credential (e.g., in the header of an mdoc or SD-JWT). Note that the chain can consist of a single certificate and the credential can include the entire X.509 chain or parts of it."
)
)
)
});
var vEtsiTrustedList = v2.object({
type: v2.literal("etsi_tl"),
values: vNonEmptyArray(
v2.pipe(
v2.string("etsi_tl trusted authority value must be a string"),
v2.url("etsi_tl trusted authority value must be a valid https url"),
v2.check(
(url2) => url2.startsWith("http://") || url2.startsWith("https://"),
"etsi_tl trusted authority value must be a valid https url"
),
v2.description(
"The identifier of a Trusted List as specified in ETSI TS 119 612 [ETSI.TL]. An ETSI Trusted List contains references to other Trusted Lists, creating a list of trusted lists, or entries for Trust Service Providers with corresponding service description and X.509 Certificates. The trust chain of a matching Credential MUST contain at least one X.509 Certificate that matches one of the entries of the Trusted List or its cascading Trusted Lists."
)
)
)
});
var vOpenidFederation = v2.object({
type: v2.literal("openid_federation"),
values: vNonEmptyArray(
v2.pipe(
v2.string("openid_federation trusted authority value must be a string"),
v2.url("openid_federation trusted authority value must be a valid https url"),
// TODO: should we have a config similar to oid4vc-ts to support http for development?
v2.check(
(url2) => url2.startsWith("http://") || url2.startsWith("https://"),
"openid_federation trusted authority value must be a valid https url"
),
v2.description(
"The Entity Identifier as defined in Section 1 of [OpenID.Federation] that is bound to an entity in a federation. While this Entity Identifier could be any entity in that ecosystem, this entity would usually have the Entity Configuration of a Trust Anchor. A valid trust path, including the given Entity Identifier, must be constructible from a matching credential."
)
)
)
});
var vTrustedAuthorities = [vAuthorityKeyIdentifier, vEtsiTrustedList, vOpenidFederation];
var DcqlTrustedAuthoritiesQuery;
((DcqlTrustedAuthoritiesQuery2) => {
const vTrustedAuthoritiesQuery = vTrustedAuthorities.map(
(authority) => v2.object({
type: v2.pipe(
authority.entries.type,
v2.description(
"REQUIRED. A string uniquely identifying the type of information about the issuer trust framework."
)
),
values: v2.pipe(
vNonEmptyArray(authority.entries.values.item),
v2.description(
"REQUIRED. An array of strings, where each string (value) contains information specific to the used Trusted Authorities Query type that allows to identify an issuer, trust framework, or a federation that an issuer belongs to."
)
)
})
);
DcqlTrustedAuthoritiesQuery2.vModel = v2.variant("type", vTrustedAuthoritiesQuery);
})(DcqlTrustedAuthoritiesQuery || (DcqlTrustedAuthoritiesQuery = {}));
var DcqlCredentialTrustedAuthority;
((DcqlCredentialTrustedAuthority2) => {
DcqlCredentialTrustedAuthority2.vModel = v2.variant("type", vTrustedAuthorities);
})(DcqlCredentialTrustedAuthority || (DcqlCredentialTrustedAuthority = {}));
// src/u-model.ts
import * as v3 from "valibot";
var ModelDefinition = class {
constructor(input) {
this.input = input;
}
get v() {
return this.input.vModel;
}
parse(input) {
const result = this.safeParse(input);
if (result.success) {
return result.output;
}
return new DcqlParseError({
message: JSON.stringify(result.flattened),
cause: result.error
});
}
safeParse(input) {
const res = v3.safeParse(this.input.vModel, input);
if (res.success) {
return { success: true, output: res.output };
}
return {
success: false,
error: new v3.ValiError(res.issues),
flattened: v3.flatten(res.issues)
};
}
is(input) {
return v3.is(this.v, input);
}
};
// src/u-dcql-credential.ts
var vCredentialModelBase = v4.object({
authority: v4.optional(DcqlCredentialTrustedAuthority.vModel),
/**
* Indicates support/inclusion of cryptographic holder binding. This will be checked against
* the `require_cryptographic_holder_binding` property from the query.
*
* In the context of a presentation this value means whether the presentation is created
* with cryptograhpic holder hinding. In the context of a credential query this means whether
* the credential supports cryptographic holder binding.
*/
cryptographic_holder_binding: v4.pipe(
v4.boolean(),
v4.description(
"Indicates support/inclusion of cryptographic holder binding. This will be checked against the `require_cryptographic_holder_binding` property from the query."
)
)
});
var DcqlMdocCredential;
((DcqlMdocCredential2) => {
DcqlMdocCredential2.vNamespaces = v4.record(v4.string(), v4.record(v4.string(), v4.unknown()));
DcqlMdocCredential2.vModel = v4.object({
...vCredentialModelBase.entries,
credential_format: v4.literal("mso_mdoc"),
doctype: v4.string(),
namespaces: DcqlMdocCredential2.vNamespaces
});
DcqlMdocCredential2.model = new ModelDefinition({ vModel: DcqlMdocCredential2.vModel });
})(DcqlMdocCredential || (DcqlMdocCredential = {}));
var DcqlSdJwtVcCredential;
((DcqlSdJwtVcCredential2) => {
DcqlSdJwtVcCredential2.vClaims = vJsonRecord;
DcqlSdJwtVcCredential2.vModel = v4.object({
...vCredentialModelBase.entries,
credential_format: v4.picklist(["vc+sd-jwt", "dc+sd-jwt"]),
vct: v4.string(),
claims: DcqlSdJwtVcCredential2.vClaims
});
DcqlSdJwtVcCredential2.model = new ModelDefinition({ vModel: DcqlSdJwtVcCredential2.vModel });
})(DcqlSdJwtVcCredential || (DcqlSdJwtVcCredential = {}));
var DcqlW3cVcCredential;
((DcqlW3cVcCredential2) => {
DcqlW3cVcCredential2.vClaims = vJsonRecord;
DcqlW3cVcCredential2.vModel = v4.object({
...vCredentialModelBase.entries,
credential_format: v4.picklist(["ldp_vc", "jwt_vc_json"]),
claims: DcqlW3cVcCredential2.vClaims,
type: v4.array(v4.string())
});
DcqlW3cVcCredential2.model = new ModelDefinition({ vModel: DcqlW3cVcCredential2.vModel });
})(DcqlW3cVcCredential || (DcqlW3cVcCredential = {}));
var DcqlCredential;
((DcqlCredential2) => {
DcqlCredential2.vModel = v4.variant("credential_format", [
DcqlMdocCredential.vModel,
DcqlSdJwtVcCredential.vModel,
DcqlW3cVcCredential.vModel
]);
DcqlCredential2.model = new ModelDefinition({ vModel: DcqlCredential2.vModel });
})(DcqlCredential || (DcqlCredential = {}));
// src/dcql-presentation/m-dcql-credential-presentation.ts
var DcqlMdocPresentation;
((DcqlMdocPresentation2) => {
DcqlMdocPresentation2.vModel = DcqlMdocCredential.vModel;
DcqlMdocPresentation2.model = new ModelDefinition({ vModel: DcqlMdocPresentation2.vModel });
})(DcqlMdocPresentation || (DcqlMdocPresentation = {}));
var DcqlSdJwtVcPresentation;
((DcqlSdJwtVcPresentation2) => {
DcqlSdJwtVcPresentation2.vModel = DcqlSdJwtVcCredential.vModel;
DcqlSdJwtVcPresentation2.model = new ModelDefinition({ vModel: DcqlSdJwtVcPresentation2.vModel });
})(DcqlSdJwtVcPresentation || (DcqlSdJwtVcPresentation = {}));
var DcqlW3cVcPresentation;
((DcqlW3cVcPresentation2) => {
DcqlW3cVcPresentation2.vModel = DcqlW3cVcCredential.vModel;
DcqlW3cVcPresentation2.model = new ModelDefinition({ vModel: DcqlW3cVcPresentation2.vModel });
})(DcqlW3cVcPresentation || (DcqlW3cVcPresentation = {}));
var DcqlCredentialPresentation;
((DcqlCredentialPresentation2) => {
DcqlCredentialPresentation2.model = new ModelDefinition({
vModel: v5.variant("credential_format", [
DcqlMdocPresentation.vModel,
DcqlSdJwtVcPresentation.vModel,
DcqlW3cVcPresentation.vModel
])
});
})(DcqlCredentialPresentation || (DcqlCredentialPresentation = {}));
// src/dcql-presentation/m-dcql-presentation-result.ts
import * as v16 from "valibot";
// src/dcql-parser/dcql-claims-query-result.ts
import * as v7 from "valibot";
// src/dcql-query/m-dcql-claims-query.ts
import * as v6 from "valibot";
var DcqlClaimsQuery;
((DcqlClaimsQuery2) => {
DcqlClaimsQuery2.vValue = v6.union([v6.string(), v6.pipe(v6.number(), v6.integer()), v6.boolean()]);
DcqlClaimsQuery2.vPath = v6.union([v6.string(), v6.pipe(v6.number(), v6.integer(), v6.minValue(0)), v6.null()]);
DcqlClaimsQuery2.vW3cSdJwtVc = v6.object({
id: v6.pipe(
v6.optional(vIdString),
v6.description(
"A string identifying the particular claim. The value MUST be a non-empty string consisting of alphanumeric, underscore (_) or hyphen (-) characters. Within the particular claims array, the same id MUST NOT be present more than once."
)
),
path: v6.pipe(
vNonEmptyArray(DcqlClaimsQuery2.vPath),
v6.description(
"A non-empty array representing a claims path pointer that specifies the path to a claim within the Verifiable Credential."
)
),
values: v6.pipe(
v6.optional(v6.array(DcqlClaimsQuery2.vValue)),
v6.description(
"An array of strings, integers or boolean values that specifies the expected values of the claim. If the values property is present, the Wallet SHOULD return the claim only if the type and value of the claim both match for at least one of the elements in the array."
)
)
});
const vMdocBase = v6.object({
id: v6.pipe(
v6.optional(vIdString),
v6.description(
"A string identifying the particular claim. The value MUST be a non-empty string consisting of alphanumeric, underscore (_) or hyphen (-) characters. Within the particular claims array, the same id MUST NOT be present more than once."
)
),
values: v6.pipe(
v6.optional(v6.array(DcqlClaimsQuery2.vValue)),
v6.description(
"An array of strings, integers or boolean values that specifies the expected values of the claim. If the values property is present, the Wallet SHOULD return the claim only if the type and value of the claim both match for at least one of the elements in the array."
)
)
});
DcqlClaimsQuery2.vMdocNamespace = v6.object({
...vMdocBase.entries,
namespace: v6.pipe(
v6.string(),
v6.description(
"A string that specifies the namespace of the data element within the mdoc, e.g., org.iso.18013.5.1."
)
),
claim_name: v6.pipe(
v6.string(),
v6.description(
"A string that specifies the data element identifier of the data element within the provided namespace in the mdoc, e.g., first_name."
)
)
});
DcqlClaimsQuery2.vMdocPath = v6.object({
...vMdocBase.entries,
intent_to_retain: v6.pipe(
v6.optional(v6.boolean()),
v6.description(
"A boolean that is equivalent to `IntentToRetain` variable defined in Section 8.3.2.1.2.1 of [@ISO.18013-5]."
)
),
path: v6.pipe(
v6.tuple([
v6.pipe(
v6.string(),
v6.description(
"A string that specifies the namespace of the data element within the mdoc, e.g., org.iso.18013.5.1."
)
),
v6.pipe(
v6.string(),
v6.description(
"A string that specifies the data element identifier of the data element within the provided namespace in the mdoc, e.g., first_name."
)
)
]),
v6.description(
"An array defining a claims path pointer into an mdoc. It must contain two elements of type string. The first element refers to a namespace and the second element refers to a data element identifier."
)
)
});
DcqlClaimsQuery2.vMdoc = v6.union([DcqlClaimsQuery2.vMdocNamespace, DcqlClaimsQuery2.vMdocPath]);
DcqlClaimsQuery2.vModel = v6.union([DcqlClaimsQuery2.vMdoc, DcqlClaimsQuery2.vW3cSdJwtVc]);
})(DcqlClaimsQuery || (DcqlClaimsQuery = {}));
// src/util/deep-merge.ts
function deepMerge(source, target) {
let newTarget = target;
if (Object.getPrototypeOf(source) !== Object.prototype && !Array.isArray(source)) {
throw new DcqlError({
message: "source value provided to deepMerge is neither an array or object.",
code: "PARSE_ERROR"
});
}
if (Object.getPrototypeOf(target) !== Object.prototype && !Array.isArray(target)) {
throw new DcqlError({
message: "target value provided to deepMerge is neither an array or object.",
code: "PARSE_ERROR"
});
}
for (const [key, val] of Object.entries(source)) {
if (val !== null && typeof val === "object" && (Object.getPrototypeOf(val) === Object.prototype || Array.isArray(val))) {
const newValue = deepMerge(
val,
newTarget[key] ?? new (Object.getPrototypeOf(val)).constructor()
);
newTarget = setValue(newTarget, key, newValue);
} else if (val != null) {
newTarget = setValue(newTarget, key, val);
}
}
return newTarget;
}
function setValue(target, key, value) {
let newTarget = target;
if (Array.isArray(newTarget)) {
newTarget = [...newTarget];
newTarget[key] = value;
} else if (Object.getPrototypeOf(newTarget) === Object.prototype) {
newTarget = { ...newTarget, [key]: value };
} else {
throw new DcqlError({
message: "Unsupported type for deep merge. Only primitive types or Array and Object are supported",
code: "INTERNAL_SERVER_ERROR"
});
}
return newTarget;
}
// src/dcql-parser/dcql-claims-query-result.ts
var pathToString = (path) => path.map((item) => typeof item === "string" ? `'${item}'` : `${item}`).join(".");
var getClaimParser = (path, values) => {
if (values) {
return v7.union(
values.map(
(val) => v7.literal(
val,
(i) => `Expected claim ${pathToString(path)} to be ${typeof val === "string" ? `'${val}'` : val} but received ${typeof i.input === "string" ? `'${i.input}'` : i.input}`
)
),
(i) => `Expected claim ${pathToString(path)} to be ${values.map((v19) => typeof v19 === "string" ? `'${v19}'` : v19).join(" | ")} but received ${typeof i.input === "string" ? `'${i.input}'` : i.input}`
);
}
return v7.pipe(
v7.unknown(),
v7.check((value) => value !== null && value !== void 0, `Expected claim '${path.join("'.'")}' to be defined`)
);
};
var getMdocClaimParser = (claimQuery) => {
const mdocPathQuery = v7.is(DcqlClaimsQuery.vMdocNamespace, claimQuery) ? {
id: claimQuery.id,
path: [claimQuery.namespace, claimQuery.claim_name],
values: claimQuery.values
} : claimQuery;
const namespace = mdocPathQuery.path[0];
const field = mdocPathQuery.path[1];
return v7.object(
{
[namespace]: v7.object(
{
[field]: getClaimParser(mdocPathQuery.path, claimQuery.values)
},
`Expected claim ${pathToString(mdocPathQuery.path)} to be defined`
)
},
`Expected claim ${pathToString(mdocPathQuery.path)} to be defined`
);
};
var getJsonClaimParser = (claimQuery, ctx) => {
const { index, presentation } = ctx;
const pathElement = claimQuery.path[index];
const isLast = index === claimQuery.path.length - 1;
const vClaimParser = getClaimParser(claimQuery.path, claimQuery.values);
if (typeof pathElement === "number") {
const elementParser = isLast ? vClaimParser : getJsonClaimParser(claimQuery, { ...ctx, index: index + 1 });
if (presentation) {
return v7.pipe(
v7.array(v7.any(), `Expected path ${pathToString(claimQuery.path.slice(0, index + 1))} to be an array`),
v7.rawTransform(({ dataset, addIssue }) => {
const issues = [];
for (const item of dataset.value) {
const itemResult = v7.safeParse(elementParser, item);
if (itemResult.success) {
return dataset.value;
}
issues.push(itemResult.issues[0]);
}
addIssue({
...issues[0],
message: isLast ? issues[0].message : `Expected any element in array ${pathToString(claimQuery.path.slice(0, index + 1))} to match sub requirement but none matched: ${issues[0].message}`
});
return dataset.value;
})
);
}
return v7.pipe(
v7.array(v7.any(), `Expected path ${pathToString(claimQuery.path.slice(0, index + 1))} to be an array`),
v7.rawTransform(({ addIssue, dataset, NEVER }) => {
const result = v7.safeParse(elementParser, dataset.value[pathElement]);
if (!result.success) {
addIssue(result.issues[0]);
return NEVER;
}
return [...dataset.value.slice(0, pathElement).map(() => null), result.output];
})
);
}
if (typeof pathElement === "string") {
return v7.object(
{
[pathElement]: isLast ? vClaimParser : getJsonClaimParser(claimQuery, { ...ctx, index: index + 1 })
},
`Expected claim ${pathToString(claimQuery.path)} to be defined`
);
}
return v7.pipe(
v7.array(v7.any(), `Expected path ${pathToString(claimQuery.path.slice(0, index + 1))} to be an array`),
v7.rawTransform(({ addIssue, dataset, NEVER }) => {
const mapped = dataset.value.map((item) => {
const parsed = v7.safeParse(
isLast ? vClaimParser : getJsonClaimParser(claimQuery, { ...ctx, index: index + 1 }),
item
);
return parsed;
});
if (mapped.every((parsed) => !parsed.success)) {
for (const parsed of mapped) {
for (const issue of parsed.issues) {
addIssue(issue);
}
}
return NEVER;
}
return mapped.map((parsed) => parsed.success ? parsed.output : null);
})
);
};
var runClaimsQuery = (credentialQuery, ctx) => {
if (!credentialQuery.claims) {
return {
success: true,
valid_claims: void 0,
failed_claims: void 0,
valid_claim_sets: [
{
claim_set_index: void 0,
output: {},
success: true,
valid_claim_indexes: void 0
}
],
failed_claim_sets: void 0
};
}
const failedClaims = [];
const validClaims = [];
for (const [claimIndex, claimQuery] of credentialQuery.claims.entries()) {
const parser = credentialQuery.format === "mso_mdoc" ? getMdocClaimParser(claimQuery) : getJsonClaimParser(claimQuery, {
index: 0,
presentation: ctx.presentation
});
const parseResult = v7.safeParse(
parser,
ctx.credential.credential_format === "mso_mdoc" ? ctx.credential.namespaces : ctx.credential.claims
);
if (parseResult.success) {
validClaims.push({
success: true,
claim_index: claimIndex,
claim_id: claimQuery.id,
output: parseResult.output,
parser
});
} else {
const flattened = v7.flatten(parseResult.issues);
failedClaims.push({
success: false,
issues: flattened.nested ?? flattened,
claim_index: claimIndex,
claim_id: claimQuery.id,
output: parseResult.output,
parser
});
}
}
const failedClaimSets = [];
const validClaimSets = [];
for (const [claimSetIndex, claimSet] of credentialQuery.claim_sets?.entries() ?? [[void 0, void 0]]) {
const claims = claimSet?.map((id) => {
const claim = validClaims.find((claim2) => claim2.claim_id === id) ?? failedClaims.find((claim2) => claim2.claim_id === id);
if (!claim) {
throw new DcqlParseError({
message: `Claim with id '${id}' in query '${credentialQuery.id}' from claim set with index '${claimSetIndex}' not found in claims of claim`
});
}
return claim;
}) ?? [...validClaims, ...failedClaims];
if (claims.every((claim) => claim.success)) {
const output = claims.reduce((merged, claim) => deepMerge(claim.output, merged), {});
validClaimSets.push({
success: true,
claim_set_index: claimSetIndex,
output,
valid_claim_indexes: asNonEmptyArrayOrUndefined(claims.map((claim) => claim.claim_index))
});
} else {
const issues = failedClaims.reduce((merged, claim) => deepMerge(claim.issues, merged), {});
failedClaimSets.push({
success: false,
issues,
claim_set_index: claimSetIndex,
failed_claim_indexes: claims.filter((claim) => !claim.success).map((claim) => claim.claim_index),
valid_claim_indexes: asNonEmptyArrayOrUndefined(
claims.filter((claim) => claim.success).map((claim) => claim.claim_index)
)
});
}
}
if (isNonEmptyArray(validClaimSets)) {
return {
success: true,
failed_claim_sets: asNonEmptyArrayOrUndefined(failedClaimSets),
valid_claim_sets: validClaimSets,
valid_claims: asNonEmptyArrayOrUndefined(validClaims.map(({ parser, ...rest }) => rest)),
failed_claims: asNonEmptyArrayOrUndefined(failedClaims.map(({ parser, ...rest }) => rest))
};
}
return {
success: false,
failed_claim_sets: failedClaimSets,
failed_claims: failedClaims.map(({ parser, ...rest }) => rest),
valid_claims: asNonEmptyArrayOrUndefined(validClaims.map(({ parser, ...rest }) => rest))
};
};
// src/dcql-parser/dcql-meta-query-result.ts
import * as v8 from "valibot";
var getCryptographicHolderBindingValue = (credentialQuery) => v8.object({
cryptographic_holder_binding: credentialQuery.require_cryptographic_holder_binding ? v8.literal(
true,
(i) => `Expected cryptographic_holder_binding to be true (because credential query '${credentialQuery.id}' requires cryptographic holder binding), but received ${i.input}`
) : v8.boolean()
});
var getMdocMetaParser = (credentialQuery) => {
const vDoctype = credentialQuery.meta?.doctype_value ? v8.literal(
credentialQuery.meta.doctype_value,
(i) => `Expected doctype to be '${credentialQuery.meta?.doctype_value}' but received '${i.input}'`
) : v8.string("Expected doctype to be defined");
const credentialParser = v8.object({
credential_format: v8.literal(
"mso_mdoc",
(i) => `Expected credential format to be 'mso_mdoc' but received '${i.input}'`
),
doctype: vDoctype,
...getCryptographicHolderBindingValue(credentialQuery).entries
});
return credentialParser;
};
var getSdJwtVcMetaParser = (credentialQuery) => {
return v8.object({
credential_format: v8.literal(
credentialQuery.format,
(i) => `Expected credential format to be '${credentialQuery.format}' but received '${i.input}'`
),
vct: credentialQuery.meta?.vct_values ? v8.picklist(
credentialQuery.meta.vct_values,
(i) => `Expected vct to be '${credentialQuery.meta?.vct_values?.join("' | '")}' but received '${i.input}'`
) : v8.string("Expected vct to be defined"),
...getCryptographicHolderBindingValue(credentialQuery).entries
});
};
var getW3cVcMetaParser = (credentialQuery) => {
return v8.object({
credential_format: v8.literal(
credentialQuery.format,
(i) => `Expected credential format to be '${credentialQuery.format}' but received '${i.input}'`
),
type: credentialQuery.meta?.type_values ? v8.union(
credentialQuery.meta.type_values.map((values) => vIncludesAll(values)),
`Expected type to include all values from one of the following subsets: ${credentialQuery.meta.type_values.map((values) => `[${values.join(", ")}]`).join(" | ")}`
) : vNonEmptyArray(v8.string()),
...getCryptographicHolderBindingValue(credentialQuery).entries
});
};
var getMetaParser = (credentialQuery) => {
if (credentialQuery.format === "mso_mdoc") {
return getMdocMetaParser(credentialQuery);
}
if (credentialQuery.format === "dc+sd-jwt" || credentialQuery.format === "vc+sd-jwt") {
return getSdJwtVcMetaParser(credentialQuery);
}
if (credentialQuery.format === "ldp_vc" || credentialQuery.format === "jwt_vc_json") {
return getW3cVcMetaParser(credentialQuery);
}
throw new DcqlError({
code: "NOT_IMPLEMENTED",
message: `Usupported format '${credentialQuery.format}'`
});
};
var runMetaQuery = (credentialQuery, credential) => {
const metaParser = getMetaParser(credentialQuery);
const parseResult = v8.safeParse(metaParser, credential);
if (!parseResult.success) {
const issues = v8.flatten(parseResult.issues);
return {
success: false,
issues: issues.nested ?? issues,
output: parseResult.output
};
}
return {
success: true,
output: parseResult.output
};
};
// src/dcql-parser/dcql-trusted-authorities-result.ts
import * as v9 from "valibot";
var runTrustedAuthoritiesQuery = (credentialQuery, credential) => {
if (!credentialQuery.trusted_authorities) {
return {
success: true
};
}
const failedTrustedAuthorities = [];
for (const [trustedAuthorityIndex, trustedAuthority] of credentialQuery.trusted_authorities.entries()) {
const trustedAuthorityParser = getTrustedAuthorityParser(trustedAuthority);
const parseResult = v9.safeParse(trustedAuthorityParser, credential.authority);
if (parseResult.success) {
return {
success: true,
valid_trusted_authority: {
success: true,
trusted_authority_index: trustedAuthorityIndex,
output: parseResult.output
},
failed_trusted_authorities: asNonEmptyArrayOrUndefined(failedTrustedAuthorities)
};
}
const issues = v9.flatten(parseResult.issues);
failedTrustedAuthorities.push({
success: false,
trusted_authority_index: trustedAuthorityIndex,
issues: issues.nested ?? issues,
output: parseResult.output
});
}
return {
success: false,
failed_trusted_authorities: failedTrustedAuthorities
};
};
// src/dcql-parser/dcql-credential-query-result.ts
var runCredentialQuery = (credentialQuery, ctx) => {
const { credentials, presentation } = ctx;
const validCredentials = [];
const failedCredentials = [];
for (const [credentialIndex, credential] of credentials.entries()) {
const trustedAuthorityResult = runTrustedAuthoritiesQuery(credentialQuery, credential);
const claimsResult = runClaimsQuery(credentialQuery, { credential, presentation });
const metaResult = runMetaQuery(credentialQuery, credential);
if (claimsResult.success && trustedAuthorityResult.success && metaResult.success) {
validCredentials.push({
success: true,
input_credential_index: credentialIndex,
trusted_authorities: trustedAuthorityResult,
meta: metaResult,
claims: claimsResult
});
} else {
failedCredentials.push({
success: false,
input_credential_index: credentialIndex,
trusted_authorities: trustedAuthorityResult,
meta: metaResult,
claims: claimsResult
});
}
}
if (isNonEmptyArray(validCredentials)) {
return {
success: true,
credential_query_id: credentialQuery.id,
failed_credentials: asNonEmptyArrayOrUndefined(failedCredentials),
valid_credentials: validCredentials
};
}
return {
success: false,
credential_query_id: credentialQuery.id,
// Can be undefined if no credentials were provided to the query
failed_credentials: asNonEmptyArrayOrUndefined(failedCredentials),
valid_credentials: void 0
};
};
// src/dcql-query-result/m-dcql-query-result.ts
import * as v15 from "valibot";
// src/dcql-query/m-dcql-credential-query.ts
import * as v10 from "valibot";
var DcqlCredentialQuery;
((DcqlCredentialQuery2) => {
const vBase = v10.object({
id: v10.pipe(
v10.string(),
v10.regex(idRegex),
v10.description(
`REQUIRED. A string identifying the Credential in the response and, if provided, the constraints in 'credential_sets'.`
)
),
require_cryptographic_holder_binding: v10.pipe(
v10.optional(v10.boolean(), true),
v10.description(
"OPTIONAL. A boolean which indicates whether the Verifier requires a Cryptographic Holder Binding proof. The default value is true, i.e., a Verifiable Presentation with Cryptographic Holder Binding is required. If set to false, the Verifier accepts a Credential without Cryptographic Holder Binding proof."
)
),
multiple: v10.pipe(
v10.optional(v10.boolean(), false),
v10.description(
"OPTIONAL. A boolean which indicates whether multiple Credentials can be returned for this Credential Query. If omitted, the default value is false."
)
),
claim_sets: v10.pipe(
v10.optional(vNonEmptyArray(vNonEmptyArray(vIdString))),
v10.description(
`OPTIONAL. A non-empty array containing arrays of identifiers for elements in 'claims' that specifies which combinations of 'claims' for the Credential are requested.`
)
),
trusted_authorities: v10.pipe(
v10.optional(vNonEmptyArray(DcqlTrustedAuthoritiesQuery.vModel)),
v10.description(
"OPTIONAL. A non-empty array of objects as defined in Section 6.1.1 that specifies expected authorities or trust frameworks that certify Issuers, that the Verifier will accept. Every Credential returned by the Wallet SHOULD match at least one of the conditions present in the corresponding trusted_authorities array if present."
)
)
});
DcqlCredentialQuery2.vMdoc = v10.object({
...vBase.entries,
format: v10.pipe(
v10.literal("mso_mdoc"),
v10.description("REQUIRED. A string that specifies the format of the requested Verifiable Credential.")
),
claims: v10.pipe(
v10.optional(vNonEmptyArray(DcqlClaimsQuery.vMdoc)),
v10.description("OPTIONAL. A non-empty array of objects as that specifies claims in the requested Credential.")
),
meta: v10.pipe(
v10.optional(
v10.object({
doctype_value: v10.pipe(
v10.optional(v10.string()),
v10.description(
"OPTIONAL. String that specifies an allowed value for the doctype of the requested Verifiable Credential."
)
)
})
),
v10.description(
"OPTIONAL. An object defining additional properties requested by the Verifier that apply to the metadata and validity data of the Credential."
)
)
});
DcqlCredentialQuery2.vSdJwtVc = v10.object({
...vBase.entries,
format: v10.pipe(
v10.picklist(["vc+sd-jwt", "dc+sd-jwt"]),
v10.description("REQUIRED. A string that specifies the format of the requested Verifiable Credential.")
),
claims: v10.pipe(
v10.optional(vNonEmptyArray(DcqlClaimsQuery.vW3cSdJwtVc)),
v10.description("OPTIONAL. A non-empty array of objects as that specifies claims in the requested Credential.")
),
meta: v10.pipe(
v10.optional(
v10.pipe(
v10.object({
vct_values: v10.optional(v10.array(v10.string()))
}),
v10.description(
"OPTIONAL. An array of strings that specifies allowed values for the type of the requested Verifiable Credential."
)
)
),
v10.description(
"OPTIONAL. An object defining additional properties requested by the Verifier that apply to the metadata and validity data of the Credential."
)
)
});
DcqlCredentialQuery2.vW3cVc = v10.object({
...vBase.entries,
format: v10.picklist(["jwt_vc_json", "ldp_vc"]),
claims: v10.optional(vNonEmptyArray(DcqlClaimsQuery.vW3cSdJwtVc)),
meta: v10.pipe(
v10.pipe(
v10.object({
type_values: v10.pipe(
vNonEmptyArray(vNonEmptyArray(v10.string())),
v10.description(
"REQUIRED. An array of string arrays that specifies the fully expanded types (IRIs) after the @context was applied that the Verifier accepts to be presented in the Presentation. Each of the top-level arrays specifies one alternative to match the type values of the Verifiable Credential against. Each inner array specifies a set of fully expanded types that MUST be present in the type property of the Verifiable Credential, regardless of order or the presence of additional types."
)
)
})
),
v10.description(
"REQUIRED. An object defining additional properties requested by the Verifier that apply to the metadata and validity data of the Credential."
)
)
});
DcqlCredentialQuery2.vModel = v10.variant("format", [DcqlCredentialQuery2.vMdoc, DcqlCredentialQuery2.vSdJwtVc, DcqlCredentialQuery2.vW3cVc]);
DcqlCredentialQuery2.validate = (credentialQuery) => {
claimSetIdsAreDefined(credentialQuery);
};
})(DcqlCredentialQuery || (DcqlCredentialQuery = {}));
var claimSetIdsAreDefined = (credentialQuery) => {
if (!credentialQuery.claim_sets) return;
const claimIds = new Set(credentialQuery.claims?.map((claim) => claim.id));
const undefinedClaims = [];
for (const claim_set of credentialQuery.claim_sets) {
for (const claim_id of claim_set) {
if (!claimIds.has(claim_id)) {
undefinedClaims.push(claim_id);
}
}
}
if (undefinedClaims.length > 0) {
throw new DcqlUndefinedClaimSetIdError({
message: `Credential set contains undefined credential id${undefinedClaims.length === 0 ? "" : "`s"} '${undefinedClaims.join(", ")}'`
});
}
};
// src/dcql-query/m-dcql-credential-set-query.ts
import * as v11 from "valibot";
var CredentialSetQuery;
((CredentialSetQuery2) => {
CredentialSetQuery2.vModel = v11.object({
options: v11.pipe(
vNonEmptyArray(v11.array(vIdString)),
v11.description(
"REQUIRED. A non-empty array, where each value in the array is a list of Credential Query identifiers representing one set of Credentials that satisfies the use case."
)
),
required: v11.pipe(
v11.optional(v11.boolean(), true),
v11.description(
`OPTIONAL. Boolean which indicates whether this set of Credentials is required to satisfy the particular use case at the Verifier. If omitted, the default value is 'true'.`
)
),
purpose: v11.pipe(
v11.optional(v11.union([v11.string(), v11.number(), v11.record(v11.string(), v11.unknown())])),
v11.description("OPTIONAL. A string, number or object specifying the purpose of the query.")
)
});
})(CredentialSetQuery || (CredentialSetQuery = {}));
// src/dcql-query-result/m-claims-result.ts
import * as v12 from "valibot";
var DcqlClaimsResult;
((DcqlClaimsResult2) => {
const vClaimsOutput = v12.union([
DcqlMdocCredential.vModel.entries.namespaces,
DcqlSdJwtVcCredential.vModel.entries.claims,
DcqlW3cVcCredential.vModel.entries.claims
]);
DcqlClaimsResult2.vClaimsEntrySuccessResult = v12.object({
success: v12.literal(true),
claim_index: v12.number(),
claim_id: v12.optional(vIdString),
output: vClaimsOutput
});
DcqlClaimsResult2.vClaimsEntryFailureResult = v12.object({
success: v12.literal(false),
claim_index: v12.number(),
claim_id: v12.optional(vIdString),
issues: v12.record(v12.string(), v12.unknown()),
output: v12.unknown()
});
DcqlClaimsResult2.vClaimSetSuccessResult = v12.object({
success: v12.literal(true),
// Undefined in case of no claim set
claim_set_index: v12.union([v12.number(), v12.undefined()]),
// We use indexes because if there are no claim sets, the ids can be undefined
// Can be empty array in case there are no claims
valid_claim_indexes: v12.optional(vNonEmptyArray(v12.number())),
failed_claim_indexes: v12.optional(v12.undefined()),
output: vClaimsOutput
});
DcqlClaimsResult2.vClaimSetFailureResult = v12.object({
success: v12.literal(false),
// Undefined in case of no claim set
claim_set_index: v12.union([v12.number(), v12.undefined()]),
// We use indexes because if there are no claim sets, the ids can be undefined
valid_claim_indexes: v12.optional(vNonEmptyArray(v12.number())),
failed_claim_indexes: vNonEmptyArray(v12.number()),
issues: v12.record(v12.string(), v12.unknown())
});
DcqlClaimsResult2.vClaimsSuccessResult = v12.object({
success: v12.literal(true),
valid_claims: v12.optional(vNonEmptyArray(DcqlClaimsResult2.vClaimsEntrySuccessResult)),
failed_claims: v12.optional(vNonEmptyArray(DcqlClaimsResult2.vClaimsEntryFailureResult)),
valid_claim_sets: vNonEmptyArray(DcqlClaimsResult2.vClaimSetSuccessResult),
failed_claim_sets: v12.optional(vNonEmptyArray(DcqlClaimsResult2.vClaimSetFailureResult))
});
DcqlClaimsResult2.vClaimsFailureResult = v12.object({
success: v12.literal(false),
valid_claims: v12.optional(vNonEmptyArray(DcqlClaimsResult2.vClaimsEntrySuccessResult)),
failed_claims: vNonEmptyArray(DcqlClaimsResult2.vClaimsEntryFailureResult),
valid_claim_sets: v12.optional(v12.undefined()),
failed_claim_sets: vNonEmptyArray(DcqlClaimsResult2.vClaimSetFailureResult)
});
DcqlClaimsResult2.vModel = v12.union([DcqlClaimsResult2.vClaimsSuccessResult, DcqlClaimsResult2.vClaimsFailureResult]);
})(DcqlClaimsResult || (DcqlClaimsResult = {}));
// src/dcql-query-result/m-meta-result.ts
import * as v13 from "valibot";
var DcqlMetaResult;
((DcqlMetaResult2) => {
DcqlMetaResult2.vMetaSuccessResult = v13.object({
success: v13.literal(true),
output: v13.variant("credential_format", [
v13.pick(DcqlSdJwtVcCredential.vModel, ["credential_format", "cryptographic_holder_binding", "vct"]),
v13.pick(DcqlMdocCredential.vModel, ["credential_format", "cryptographic_holder_binding", "doctype"]),
v13.pick(DcqlW3cVcCredential.vModel, ["credential_format", "cryptographic_holder_binding", "type"])
])
});
DcqlMetaResult2.vMetaFailureResult = v13.object({
success: v13.literal(false),
issues: v13.record(v13.string(), v13.unknown()),
output: v13.unknown()
});
DcqlMetaResult2.vModel = v13.union([DcqlMetaResult2.vMetaSuccessResult, DcqlMetaResult2.vMetaFailureResult]);
})(DcqlMetaResult || (DcqlMetaResult = {}));
// src/dcql-query-result/m-trusted-authorities-result.ts
import * as v14 from "valibot";
var DcqlTrustedAuthoritiesResult;
((DcqlTrustedAuthoritiesResult2) => {
DcqlTrustedAuthoritiesResult2.vTrustedAuthorityEntrySuccessResult = v14.object({
success: v14.literal(true),
trusted_authority_index: v14.number(),
// We map from values (multiple options for a credential/query) to value (the matching option)
output: v14.variant(
"type",
DcqlCredentialTrustedAuthority.vModel.options.map(
(o) => v14.object({
type: o.entries.type,
value: o.entries.values.item
})
)
)
});
DcqlTrustedAuthoritiesResult2.vTrustedAuthorityEntryFailureResult = v14.object({
success: v14.literal(false),
trusted_authority_index: v14.number(),
issues: v14.record(v14.string(), v14.unknown()),
output: v14.unknown()
});
DcqlTrustedAuthoritiesResult2.vTrustedAuthoritySuccessResult = v14.union([
// In this case there is no trusted authority on the query
v14.object({
success: v14.literal(true),
valid_trusted_authority: v14.optional(v14.undefined()),
failed_trusted_authorities: v14.optional(v14.undefined())
}),
v14.object({
success: v14.literal(true),
valid_trusted_authority: DcqlTrustedAuthoritiesResult2.vTrustedAuthorityEntrySuccessResult,
failed_trusted_authorities: v14.optional(vNonEmptyArray(DcqlTrustedAuthoritiesResult2.vTrustedAuthorityEntryFailureResult))
})
]);
DcqlTrustedAuthoritiesResult2.vTrustedAuthorityFailureResult = v14.object({
success: v14.literal(false),
valid_trusted_authority: v14.optional(v14.undefined()),
failed_trusted_authorities: vNonEmptyArray(DcqlTrustedAuthoritiesResult2.vTrustedAuthorityEntryFailureResult)
});
DcqlTrustedAuthoritiesResult2.vModel = v14.union([...DcqlTrustedAuthoritiesResult2.vTrustedAuthoritySuccessResult.options, DcqlTrustedAuthoritiesResult2.vTrustedAuthorityFailureResult]);
})(DcqlTrustedAuthoritiesResult || (DcqlTrustedAuthoritiesResult = {}));
// src/dcql-query-result/m-dcql-query-result.ts
var DcqlQueryResult;
((DcqlQueryResult2) => {
DcqlQueryResult2.vCredentialQueryItemCredentialSuccessResult = v15.object({
success: v15.literal(true),
input_credential_index: v15.number(),
trusted_authorities: DcqlTrustedAuthoritiesResult.vTrustedAuthoritySuccessResult,
// TODO: format specific (we should probably add format to this object, to differentiate?)
claims: DcqlClaimsResult.vClaimsSuccessResult,
meta: DcqlMetaResult.vMetaSuccessResult
});
DcqlQueryResult2.vCredentialQueryItemCredentialFailureResult = v15.object({
success: v15.literal(false),
input_credential_index: v15.number(),
trusted_authorities: DcqlTrustedAuthoritiesResult.vModel,
claims: DcqlClaimsResult.vModel,
meta: DcqlMetaResult.vModel
});
DcqlQueryResult2.vCredentialQueryItemResult = v15.union([
v15.object({
success: v15.literal(true),
credential_query_id: vIdString,
valid_credentials: vNonEmptyArray(DcqlQueryResult2.vCredentialQueryItemCredentialSuccessResult),
failed_credentials: v15.optional(vNonEmptyArray(DcqlQueryResult2.vCredentialQueryItemCredentialFailureResult))
}),
v15.object({
success: v15.literal(false),
credential_query_id: vIdString,
valid_credentials: v15.optional(v15.undefined()),
failed_credentials: v15.optional(vNonEmptyArray(DcqlQueryResult2.vCredentialQueryItemCredentialFailureResult))
})
]);
DcqlQueryResult2.vCredentialQueryResult = v15.record(vIdString, DcqlQueryResult2.vCredentialQueryItemResult);
DcqlQueryResult2.vModel = v15.object({
credentials: v15.pipe(
vNonEmptyArray(DcqlCredentialQuery.vModel),
v15.description(
"REQUIRED. A non-empty array of Credential Queries that specify the requested Verifiable Credentials."
)
),
credential_matches: DcqlQueryResult2.vCredentialQueryResult,
credential_sets: v15.optional(
v15.pipe(
vNonEmptyArray(
v15.object({
...CredentialSetQuery.vModel.entries,
matching_options: v15.union([v15.undefined(), vNonEmptyArray(v15.array(v15.string()))])
})
),
v15.description(
"OPTIONAL. A non-empty array of credential set queries that specifies additional constraints on which of the requested Verifiable Credentials to return."
)
)
),
can_be_satisfied: v15.boolean()
});
})(DcqlQueryResult || (DcqlQueryResult = {}));
// src/dcql-presentation/m-dcql-presentation-result.ts
var DcqlPresentationResult;
((DcqlPresentationResult2) => {
DcqlPresentationResult2.vModel = v16.omit(DcqlQueryResult.vModel, ["credentials"]);
DcqlPresentationResult2.parse = (input) => {
return v16.parse(DcqlPresentationResult2.vModel, input);
};
DcqlPresentationResult2.fromDcqlPresentation = (dcqlPresentation, ctx) => {
const { dcqlQuery } = ctx;
co