UNPKG

dcql

Version:
1 lines 105 kB
{"version":3,"sources":["../src/dcql-error/e-base.ts","../src/dcql-error/e-dcql.ts","../src/dcql-presentation/m-dcql-credential-presentation.ts","../src/u-dcql-credential.ts","../src/dcql-query/m-dcql-trusted-authorities.ts","../src/u-dcql.ts","../src/u-model.ts","../src/dcql-presentation/m-dcql-presentation-result.ts","../src/dcql-parser/dcql-claims-query-result.ts","../src/dcql-query/m-dcql-claims-query.ts","../src/util/deep-merge.ts","../src/dcql-parser/dcql-meta-query-result.ts","../src/dcql-parser/dcql-trusted-authorities-result.ts","../src/dcql-parser/dcql-credential-query-result.ts","../src/dcql-query-result/m-dcql-query-result.ts","../src/dcql-query/m-dcql-credential-query.ts","../src/dcql-query/m-dcql-credential-set-query.ts","../src/dcql-query-result/m-claims-result.ts","../src/dcql-query-result/m-meta-result.ts","../src/dcql-query-result/m-trusted-authorities-result.ts","../src/dcql-presentation/m-dcql-presentation.ts","../src/dcql-query-result/run-dcql-query.ts","../src/dcql-query/m-dcql-query.ts"],"sourcesContent":["function isObject(value: unknown): value is Record<string, unknown> {\n return !!value && !Array.isArray(value) && typeof value === 'object'\n}\n\nclass UnknownCauseError extends Error {\n [key: string]: unknown\n}\n\nexport function getCauseFromUnknown(cause: unknown): Error | undefined {\n if (cause instanceof Error) {\n return cause\n }\n\n const type = typeof cause\n if (type === 'undefined' || type === 'function' || cause === null) {\n return undefined\n }\n\n // Primitive types just get wrapped in an error\n if (type !== 'object') {\n return new Error(String(cause))\n }\n\n // If it's an object, we'll create a synthetic error\n if (isObject(cause)) {\n const err = new UnknownCauseError()\n for (const key in cause) {\n err[key] = cause[key]\n }\n return err\n }\n\n return undefined\n}\n\nexport const isDcqlError = (cause: unknown): cause is DcqlError => {\n if (cause instanceof DcqlError) {\n return true\n }\n if (cause instanceof Error && cause.name === 'DcqlError') {\n // https://github.com/trpc/trpc/pull/4848\n return true\n }\n\n return false\n}\n\nexport function getDcqlErrorFromUnknown(cause: unknown): DcqlError {\n if (isDcqlError(cause)) {\n return cause\n }\n\n const dcqlError = new DcqlError({\n code: 'INTERNAL_SERVER_ERROR',\n cause,\n })\n\n // Inherit stack from error\n if (cause instanceof Error && cause.stack) {\n dcqlError.stack = cause.stack\n }\n\n return dcqlError\n}\n\ntype DCQL_ERROR_CODE = 'PARSE_ERROR' | 'INTERNAL_SERVER_ERROR' | 'NOT_IMPLEMENTED' | 'BAD_REQUEST'\n\nexport class DcqlError extends Error {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore override doesn't work in all environments due to \"This member cannot have an 'override' modifier because it is not declared in the base class 'Error'\"\n public override readonly cause?: Error\n public readonly code\n\n constructor(opts: {\n message?: string\n code: DCQL_ERROR_CODE\n cause?: unknown\n }) {\n const cause = getCauseFromUnknown(opts.cause)\n const message = opts.message ?? cause?.message ?? opts.code\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore https://github.com/tc39/proposal-error-cause\n super(message, { cause })\n\n this.code = opts.code\n this.name = 'DcqlError'\n\n if (!this.cause) {\n // < ES2022 / < Node 16.9.0 compatability\n this.cause = cause\n }\n }\n}\n","import { DcqlError } from './e-base.js'\n\nexport class DcqlCredentialSetError extends DcqlError {\n constructor(opts: { message: string; cause?: unknown }) {\n super({ code: 'BAD_REQUEST', ...opts })\n }\n}\n\nexport class DcqlUndefinedClaimSetIdError extends DcqlError {\n constructor(opts: { message: string; cause?: unknown }) {\n super({ code: 'BAD_REQUEST', ...opts })\n }\n}\n\nexport class DcqlNonUniqueCredentialQueryIdsError extends DcqlError {\n constructor(opts: { message: string; cause?: unknown }) {\n super({ code: 'BAD_REQUEST', ...opts })\n }\n}\n\nexport class DcqlParseError extends DcqlError {\n constructor(opts: { message: string; cause?: unknown }) {\n super({ code: 'PARSE_ERROR', ...opts })\n }\n}\n\nexport class DcqlInvalidClaimsQueryIdError extends DcqlError {\n constructor(opts: { message: string; cause?: unknown }) {\n super({ code: 'BAD_REQUEST', ...opts })\n }\n}\n\nexport class DcqlMissingClaimSetParseError extends DcqlError {\n constructor(opts: { message: string; cause?: unknown }) {\n super({ code: 'PARSE_ERROR', ...opts })\n }\n}\n\nexport class DcqlInvalidPresentationRecordError extends DcqlError {\n constructor(opts: { message: string; cause?: unknown }) {\n super({ code: 'BAD_REQUEST', ...opts })\n }\n}\n\nexport class DcqlPresentationResultError extends DcqlError {\n constructor(opts: { message: string; cause?: unknown }) {\n super({ code: 'BAD_REQUEST', ...opts })\n }\n}\n","import * as v from 'valibot'\nimport { DcqlMdocCredential, DcqlSdJwtVcCredential, DcqlW3cVcCredential } from '../u-dcql-credential'\nimport type { InferModelTypes } from '../u-model'\nimport { ModelDefinition } from '../u-model'\n\nexport namespace DcqlMdocPresentation {\n export const vModel = DcqlMdocCredential.vModel\n export const model = new ModelDefinition({ vModel })\n export type Model = InferModelTypes<typeof model>\n}\nexport type DcqlMdocPresentation = DcqlMdocPresentation.Model['Output']\n\nexport namespace DcqlSdJwtVcPresentation {\n export const vModel = DcqlSdJwtVcCredential.vModel\n export const model = new ModelDefinition({ vModel })\n export type Model = InferModelTypes<typeof model>\n}\nexport type DcqlSdJwtVcPresentation = DcqlSdJwtVcPresentation.Model['Output']\n\nexport namespace DcqlW3cVcPresentation {\n export const vModel = DcqlW3cVcCredential.vModel\n export const model = new ModelDefinition({ vModel })\n export type Model = InferModelTypes<typeof model>\n}\nexport type DcqlW3cVcPresentation = DcqlW3cVcPresentation.Model['Output']\n\nexport namespace DcqlCredentialPresentation {\n export const model = new ModelDefinition({\n vModel: v.variant('credential_format', [\n DcqlMdocPresentation.vModel,\n DcqlSdJwtVcPresentation.vModel,\n DcqlW3cVcPresentation.vModel,\n ]),\n })\n export type Model = InferModelTypes<typeof model>\n}\nexport type DcqlCredentialPresentation = DcqlCredentialPresentation.Model['Output']\n","import * as v from 'valibot'\nimport { DcqlCredentialTrustedAuthority } from './dcql-query/m-dcql-trusted-authorities.js'\nimport { vJsonRecord } from './u-dcql.js'\nimport type { InferModelTypes } from './u-model.js'\nimport { ModelDefinition } from './u-model.js'\n\nconst vCredentialModelBase = v.object({\n authority: v.optional(DcqlCredentialTrustedAuthority.vModel),\n\n /**\n * Indicates support/inclusion of cryptographic holder binding. This will be checked against\n * the `require_cryptographic_holder_binding` property from the query.\n *\n * In the context of a presentation this value means whether the presentation is created\n * with cryptograhpic holder hinding. In the context of a credential query this means whether\n * the credential supports cryptographic holder binding.\n */\n cryptographic_holder_binding: v.pipe(\n v.boolean(),\n v.description(\n 'Indicates support/inclusion of cryptographic holder binding. This will be checked against the `require_cryptographic_holder_binding` property from the query.'\n )\n ),\n})\n\nexport namespace DcqlMdocCredential {\n export const vNamespaces = v.record(v.string(), v.record(v.string(), v.unknown()))\n export const vModel = v.object({\n ...vCredentialModelBase.entries,\n credential_format: v.literal('mso_mdoc'),\n doctype: v.string(),\n namespaces: vNamespaces,\n })\n\n export const model = new ModelDefinition({ vModel })\n export type Model = InferModelTypes<typeof model>\n export type NameSpaces = v.InferOutput<typeof vNamespaces>\n}\nexport type DcqlMdocCredential = DcqlMdocCredential.Model['Output']\n\nexport namespace DcqlSdJwtVcCredential {\n export const vClaims = vJsonRecord\n export const vModel = v.object({\n ...vCredentialModelBase.entries,\n credential_format: v.picklist(['vc+sd-jwt', 'dc+sd-jwt']),\n vct: v.string(),\n claims: vClaims,\n })\n export const model = new ModelDefinition({ vModel })\n export type Model = InferModelTypes<typeof model>\n export type Claims = Model['Output']['claims']\n}\nexport type DcqlSdJwtVcCredential = DcqlSdJwtVcCredential.Model['Output']\n\nexport namespace DcqlW3cVcCredential {\n export const vClaims = vJsonRecord\n export const vModel = v.object({\n ...vCredentialModelBase.entries,\n credential_format: v.picklist(['ldp_vc', 'jwt_vc_json']),\n claims: vClaims,\n type: v.array(v.string()),\n })\n\n export const model = new ModelDefinition({ vModel })\n export type Model = InferModelTypes<typeof model>\n export type Claims = Model['Output']['claims']\n}\nexport type DcqlW3cVcCredential = DcqlW3cVcCredential.Model['Output']\n\nexport namespace DcqlCredential {\n export const vModel = v.variant('credential_format', [\n DcqlMdocCredential.vModel,\n DcqlSdJwtVcCredential.vModel,\n DcqlW3cVcCredential.vModel,\n ])\n\n export const model = new ModelDefinition({ vModel })\n export type Model = InferModelTypes<typeof model>\n}\nexport type DcqlCredential = DcqlCredential.Model['Output']\n","import * as v from 'valibot'\nimport { vBase64url, vNonEmptyArray } from '../u-dcql.js'\n\nexport const getTrustedAuthorityParser = (trustedAuthority: DcqlTrustedAuthoritiesQuery) =>\n v.pipe(\n v.object(\n {\n type: v.literal(\n trustedAuthority.type,\n (i) =>\n `Expected trusted authority type to be '${trustedAuthority.type}' but received ${typeof i.input === 'string' ? `'${i.input}'` : i.input}`\n ),\n\n // Some trusted authorities support an array as input type\n values: v.pipe(\n vNonEmptyArray(v.string()),\n v.someItem(\n (item) => trustedAuthority.values.includes(item),\n (i) =>\n `Expected one of the trusted authority values to be '${trustedAuthority.values.join(\"' | '\")}' but received '${i.input.join(\"' , '\")}'`\n )\n ),\n },\n `Expected trusted authority object with type '${trustedAuthority.type}' to be defined, but received undefined`\n ),\n v.transform(({ values, ...rest }) => ({\n ...rest,\n value: values.find((value) => trustedAuthority.values.includes(value)) as string,\n }))\n )\n\nconst vAuthorityKeyIdentifier = v.object({\n type: v.literal('aki'),\n values: vNonEmptyArray(\n v.pipe(\n v.string('aki trusted authority value must be a string'),\n vBase64url,\n v.description(\n '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.'\n )\n )\n ),\n})\n\nconst vEtsiTrustedList = v.object({\n type: v.literal('etsi_tl'),\n values: vNonEmptyArray(\n v.pipe(\n v.string('etsi_tl trusted authority value must be a string'),\n v.url('etsi_tl trusted authority value must be a valid https url'),\n v.check(\n (url) => url.startsWith('http://') || url.startsWith('https://'),\n 'etsi_tl trusted authority value must be a valid https url'\n ),\n v.description(\n '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.'\n )\n )\n ),\n})\n\nconst vOpenidFederation = v.object({\n type: v.literal('openid_federation'),\n values: vNonEmptyArray(\n v.pipe(\n v.string('openid_federation trusted authority value must be a string'),\n v.url('openid_federation trusted authority value must be a valid https url'),\n // TODO: should we have a config similar to oid4vc-ts to support http for development?\n v.check(\n (url) => url.startsWith('http://') || url.startsWith('https://'),\n 'openid_federation trusted authority value must be a valid https url'\n ),\n v.description(\n '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.'\n )\n )\n ),\n})\n\nconst vTrustedAuthorities = [vAuthorityKeyIdentifier, vEtsiTrustedList, vOpenidFederation] as const\n\n/**\n * Specifies trusted authorities within a requested Credential.\n */\nexport namespace DcqlTrustedAuthoritiesQuery {\n const vTrustedAuthoritiesQuery = vTrustedAuthorities.map((authority) =>\n v.object({\n type: v.pipe(\n authority.entries.type,\n v.description(\n 'REQUIRED. A string uniquely identifying the type of information about the issuer trust framework.'\n )\n ),\n values: v.pipe(\n vNonEmptyArray(authority.entries.values.item),\n v.description(\n '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.'\n )\n ),\n })\n )\n\n export const vModel = v.variant('type', vTrustedAuthoritiesQuery)\n export type Input = v.InferInput<typeof vModel>\n export type Out = v.InferOutput<typeof vModel>\n}\nexport type DcqlTrustedAuthoritiesQuery = DcqlTrustedAuthoritiesQuery.Out\n\n/**\n * Specifies trusted authorities within a requested Credential.\n */\nexport namespace DcqlCredentialTrustedAuthority {\n export const vModel = v.variant('type', vTrustedAuthorities)\n export type Input = v.InferInput<typeof vModel>\n export type Out = v.InferOutput<typeof vModel>\n}\nexport type DcqlCredentialTrustedAuthority = DcqlCredentialTrustedAuthority.Out\n","import * as v from 'valibot'\nimport type { UnknownBaseSchema } from './u-model'\nexport const idRegex = /^[a-zA-Z0-9_-]+$/\n\n// biome-ignore lint/suspicious/noExplicitAny: we want to allow any schema here\nexport type vBaseSchemaAny = v.BaseSchema<any, any, any>\n\nexport function asNonEmptyArrayOrUndefined<U>(array: U[]): NonEmptyArray<U> | undefined {\n return array.length > 0 ? (array as NonEmptyArray<U>) : undefined\n}\n\nexport function isNonEmptyArray<U>(array: U[]): array is NonEmptyArray<U> {\n return array.length > 0\n}\n\nexport type NonEmptyArray<T> = [T, ...T[]]\nexport type ToNonEmptyArray<T extends Array<unknown>> = [T[number], ...T]\nexport const vNonEmptyArray = <const TItem extends v.BaseSchema<unknown, unknown, v.BaseIssue<unknown>>>(\n item: TItem\n) => {\n return v.pipe(\n v.array(item, (i) => `Expected input to be an array, but received '${i.received}'`),\n v.custom<NonEmptyArray<v.InferOutput<TItem>>>(\n (input) => (input as TItem[]).length > 0,\n 'Array must be non-empty and have length of at least 1'\n )\n )\n}\n\nexport const vIncludesAll = <T extends unknown[]>(subset: T) => {\n return v.custom<T>(\n (value) => {\n if (!Array.isArray(value)) return false\n\n // Check if all elements from the subset are in the value array\n return subset.every((item) => value.includes(item))\n },\n `Value must include all of: ${subset.join(', ')}`\n )\n}\n\nexport const vIdString = v.pipe(v.string(), v.regex(idRegex), v.nonEmpty())\nexport const vBase64url = v.regex(/^(?:[\\w-]{4})*(?:[\\w-]{2}(?:==)?|[\\w-]{3}=?)?$/iu, 'must be base64url')\n\nexport type Json = string | number | boolean | null | { [key: string]: Json } | Json[]\n\ninterface HasToJson {\n toJson(): Json\n}\n\nfunction isToJsonable(value: unknown): value is HasToJson {\n if (value === null || typeof value !== 'object') return false\n\n // biome-ignore lint/suspicious/noExplicitAny: <explanation>\n const toJsonFn = (value as any).toJson\n return typeof toJsonFn === 'function'\n}\n\nexport const vWithJT = <Schema extends UnknownBaseSchema>(schema: Schema) =>\n v.pipe(\n v.custom<v.InferInput<Schema>>(() => true),\n v.rawTransform<v.InferInput<Schema>, v.InferOutput<Schema>>(({ dataset, addIssue, NEVER }) => {\n const result = v.safeParse(schema, dataset.value)\n if (result.success) return dataset.value\n\n if (!isToJsonable(dataset.value)) {\n for (const safeParseIssue of result.issues) {\n addIssue({\n ...safeParseIssue,\n expected: safeParseIssue.expected ?? undefined,\n })\n }\n\n return NEVER\n }\n\n let json: Json\n\n try {\n json = dataset.value.toJson()\n } catch (error) {\n for (const safeParseIssue of result.issues) {\n addIssue({\n ...safeParseIssue,\n expected: safeParseIssue.expected ?? undefined,\n })\n }\n addIssue({ message: 'Json Transformation failed' })\n return NEVER\n }\n\n const safeParseResult: v.SafeParseResult<Schema> = v.safeParse(schema, json)\n if (safeParseResult.success) return dataset.value\n\n for (const safeParseIssue of safeParseResult.issues) {\n addIssue({\n ...safeParseIssue,\n expected: safeParseIssue.expected ?? undefined,\n })\n }\n\n return NEVER\n })\n )\n\nexport const vJsonLiteral = v.union([v.string(), v.number(), v.boolean(), v.null()])\n\nexport type JsonLiteral = v.InferOutput<typeof vJsonLiteral>\n\nexport const vJson: v.GenericSchema<Json> = v.lazy(() =>\n v.union([vJsonLiteral, v.array(vJson), v.record(v.string(), vJson)])\n)\n\nexport const vJsonWithJT: v.GenericSchema<Json> = v.lazy(() =>\n vWithJT(v.union([vJsonLiteral, v.array(vJson), v.record(v.string(), vJson)]))\n)\n\nexport const vJsonRecord = v.record(v.string(), vJson)\nexport type JsonRecord = v.InferOutput<typeof vJsonRecord>\n\nexport const vStringToJson = v.rawTransform<string, Json>(({ dataset, addIssue, NEVER }) => {\n try {\n return JSON.parse(dataset.value) as Json\n } catch (error) {\n addIssue({ message: 'Invalid JSON' })\n return NEVER\n }\n})\n","import * as v from 'valibot'\nimport { DcqlParseError } from './dcql-error/e-dcql'\n\nexport type UnknownBaseSchema = v.BaseSchema<unknown, unknown, v.BaseIssue<unknown>>\n\ntype EnsureOutputAssignableToInput<T extends UnknownBaseSchema> = v.InferOutput<T> extends v.InferInput<T> ? T : never\n\nexport class ModelDefinition<T extends UnknownBaseSchema> {\n constructor(private input: { vModel: EnsureOutputAssignableToInput<T> }) {}\n\n public get v() {\n return this.input.vModel\n }\n\n public parse(input: T) {\n const result = this.safeParse(input)\n\n if (result.success) {\n return result.output\n }\n\n return new DcqlParseError({\n message: JSON.stringify(result.flattened),\n cause: result.error,\n })\n }\n\n public safeParse(\n input: unknown\n ):\n | { success: true; output: v.InferOutput<T> }\n | { success: false; flattened: v.FlatErrors<T>; error: v.ValiError<T> } {\n const res = v.safeParse(this.input.vModel, input)\n if (res.success) {\n return { success: true, output: res.output }\n }\n return {\n success: false,\n error: new v.ValiError(res.issues),\n flattened: v.flatten<T>(res.issues),\n }\n }\n\n public is(input: unknown): input is v.InferOutput<T> {\n return v.is(this.v, input)\n }\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: <explanation>\nexport type InferModelTypes<T extends ModelDefinition<any>> = T extends ModelDefinition<infer U>\n ? {\n Input: v.InferInput<U>\n Output: v.InferOutput<U>\n }\n : never\n","import * as v from 'valibot'\n\nimport { DcqlInvalidPresentationRecordError, DcqlPresentationResultError } from '../dcql-error/e-dcql.js'\nimport { runCredentialQuery } from '../dcql-parser/dcql-credential-query-result.js'\nimport { DcqlQueryResult } from '../dcql-query-result/m-dcql-query-result.js'\nimport type { DcqlQuery } from '../dcql-query/m-dcql-query.js'\nimport type { DcqlCredentialPresentation } from './m-dcql-credential-presentation.js'\n\nexport namespace DcqlPresentationResult {\n export const vModel = v.omit(DcqlQueryResult.vModel, ['credentials'])\n\n export type Input = v.InferInput<typeof vModel>\n export type Output = v.InferOutput<typeof vModel>\n\n export const parse = (input: Input | DcqlQueryResult) => {\n return v.parse(vModel, input)\n }\n\n /**\n * Query if the presentation record can be satisfied by the provided presentations\n * considering the dcql query\n *\n * @param dcqlQuery\n * @param dcqlPresentation\n */\n export const fromDcqlPresentation = (\n dcqlPresentation: Record<string, DcqlCredentialPresentation | DcqlCredentialPresentation[]>,\n ctx: { dcqlQuery: DcqlQuery }\n ): Output => {\n const { dcqlQuery } = ctx\n\n const queriesResults = Object.entries(dcqlPresentation).map(([credentialQueryId, presentations]) => {\n const credentialQuery = dcqlQuery.credentials.find((c) => c.id === credentialQueryId)\n if (!credentialQuery) {\n throw new DcqlPresentationResultError({\n message: `Query ${credentialQueryId} not found in the dcql query. Cannot validate presentation.`,\n })\n }\n\n if (Array.isArray(presentations)) {\n if (presentations.length === 0) {\n throw new DcqlPresentationResultError({\n message: `Query credential '${credentialQueryId}' is present in the presentations but the value is an empty array. Each entry must at least provide one presentation.`,\n })\n }\n\n if (!credentialQuery.multiple && presentations.length > 1) {\n throw new DcqlPresentationResultError({\n message: `Query credential '${credentialQueryId}' has not enabled 'multiple', but multiple presentations were provided. Only a single presentation is allowed for each query credential when 'multiple' is not enabled on the query.`,\n })\n }\n }\n\n return runCredentialQuery(credentialQuery, {\n presentation: true,\n credentials: presentations ? (Array.isArray(presentations) ? presentations : [presentations]) : [],\n })\n })\n\n const credentialSetResults = dcqlQuery.credential_sets?.map((set) => {\n const matchingOptions = set.options.filter((option) =>\n option.every(\n (credentialQueryId) =>\n queriesResults.find((result) => result.credential_query_id === credentialQueryId)?.success\n )\n )\n\n return {\n ...set,\n matching_options: matchingOptions.length > 0 ? (matchingOptions as [string[], ...string[][]]) : undefined,\n }\n }) as DcqlQueryResult.Output['credential_sets']\n\n const dqclQueryMatched =\n // We require that all the submitted presentations match with the queries\n // So we must have success for all queries, and we don't allow failed_credentials\n queriesResults.every((result) => result.success && !result.failed_credentials) &&\n (credentialSetResults\n ? credentialSetResults.every((set) => !set.required || set.matching_options)\n : // If not credential_sets are used, we require that at least every credential has a match\n dcqlQuery.credentials.every(\n (credentialQuery) =>\n queriesResults.find((result) => result.credential_query_id === credentialQuery.id)?.success\n ))\n\n return {\n // NOTE: can_be_satisfied is maybe not the best term, because we return false if it can be\n // satisfied, but one of the provided presentations did not match\n can_be_satisfied: dqclQueryMatched,\n credential_sets: credentialSetResults,\n credential_matches: Object.fromEntries(queriesResults.map((result) => [result.credential_query_id, result])),\n }\n }\n\n export const validate = (dcqlQueryResult: DcqlPresentationResult) => {\n if (!dcqlQueryResult.can_be_satisfied) {\n throw new DcqlInvalidPresentationRecordError({\n message: 'Invalid Presentation record',\n cause: dcqlQueryResult,\n })\n }\n\n return dcqlQueryResult satisfies Output\n }\n}\nexport type DcqlPresentationResult = DcqlPresentationResult.Output\n","import * as v from 'valibot'\nimport { DcqlParseError } from '../dcql-error/e-dcql.js'\nimport type { DcqlClaimsResult } from '../dcql-query-result/m-claims-result.js'\nimport { DcqlClaimsQuery } from '../dcql-query/m-dcql-claims-query.js'\nimport type { DcqlCredentialQuery } from '../dcql-query/m-dcql-credential-query.js'\nimport type { DcqlCredential } from '../u-dcql-credential.js'\nimport { type ToNonEmptyArray, asNonEmptyArrayOrUndefined, isNonEmptyArray, type vBaseSchemaAny } from '../u-dcql.js'\nimport { deepMerge } from '../util/deep-merge.js'\n\nconst pathToString = (path: Array<string | null | number>) =>\n path.map((item) => (typeof item === 'string' ? `'${item}'` : `${item}`)).join('.')\n\nconst getClaimParser = (path: Array<string | number | null>, values?: DcqlClaimsQuery.ClaimValue[]) => {\n if (values) {\n return v.union(\n values.map((val) =>\n v.literal(\n val,\n (i) =>\n `Expected claim ${pathToString(path)} to be ${typeof val === 'string' ? `'${val}'` : val} but received ${typeof i.input === 'string' ? `'${i.input}'` : i.input}`\n )\n ),\n (i) =>\n `Expected claim ${pathToString(path)} to be ${values.map((v) => (typeof v === 'string' ? `'${v}'` : v)).join(' | ')} but received ${typeof i.input === 'string' ? `'${i.input}'` : i.input}`\n )\n }\n\n return v.pipe(\n v.unknown(),\n v.check((value) => value !== null && value !== undefined, `Expected claim '${path.join(\"'.'\")}' to be defined`)\n )\n}\n\nexport const getMdocClaimParser = (claimQuery: DcqlClaimsQuery.Mdoc) => {\n // Normalize the query to the latest path syntax\n const mdocPathQuery: DcqlClaimsQuery.MdocPath = v.is(DcqlClaimsQuery.vMdocNamespace, claimQuery)\n ? {\n id: claimQuery.id,\n path: [claimQuery.namespace, claimQuery.claim_name],\n values: claimQuery.values,\n }\n : claimQuery\n\n const namespace = mdocPathQuery.path[0]\n const field = mdocPathQuery.path[1]\n\n return v.object(\n {\n [namespace]: v.object(\n {\n [field]: getClaimParser(mdocPathQuery.path, claimQuery.values),\n },\n `Expected claim ${pathToString(mdocPathQuery.path)} to be defined`\n ),\n },\n `Expected claim ${pathToString(mdocPathQuery.path)} to be defined`\n )\n}\n\nexport const getJsonClaimParser = (\n claimQuery: DcqlClaimsQuery.W3cAndSdJwtVc,\n ctx: { index: number; presentation: boolean }\n): vBaseSchemaAny => {\n const { index, presentation } = ctx\n const pathElement = claimQuery.path[index]\n const isLast = index === claimQuery.path.length - 1\n\n const vClaimParser = getClaimParser(claimQuery.path, claimQuery.values)\n\n if (typeof pathElement === 'number') {\n const elementParser = isLast ? vClaimParser : getJsonClaimParser(claimQuery, { ...ctx, index: index + 1 })\n\n if (presentation) {\n return v.pipe(\n v.array(v.any(), `Expected path ${pathToString(claimQuery.path.slice(0, index + 1))} to be an array`),\n v.rawTransform(({ dataset, addIssue }) => {\n const issues = []\n for (const item of dataset.value) {\n const itemResult = v.safeParse(elementParser, item)\n\n if (itemResult.success) {\n return dataset.value\n }\n\n issues.push(itemResult.issues[0])\n }\n\n addIssue({\n ...issues[0],\n message: isLast\n ? issues[0].message\n : `Expected any element in array ${pathToString(claimQuery.path.slice(0, index + 1))} to match sub requirement but none matched: ${issues[0].message}`,\n })\n\n return dataset.value\n })\n )\n }\n\n return v.pipe(\n v.array(v.any(), `Expected path ${pathToString(claimQuery.path.slice(0, index + 1))} to be an array`),\n v.rawTransform(({ addIssue, dataset, NEVER }) => {\n // Validate the specific element\n const result = v.safeParse(elementParser, dataset.value[pathElement])\n if (!result.success) {\n addIssue(result.issues[0])\n return NEVER\n }\n\n // We need to preserve array ordering, so we add null elements for all items\n // before the current pathElement number\n return [...dataset.value.slice(0, pathElement).map(() => null), result.output]\n })\n )\n }\n\n if (typeof pathElement === 'string') {\n return v.object(\n {\n [pathElement]: isLast ? vClaimParser : getJsonClaimParser(claimQuery, { ...ctx, index: index + 1 }),\n },\n `Expected claim ${pathToString(claimQuery.path)} to be defined`\n )\n }\n\n return v.pipe(\n v.array(v.any(), `Expected path ${pathToString(claimQuery.path.slice(0, index + 1))} to be an array`),\n v.rawTransform(({ addIssue, dataset, NEVER }) => {\n const mapped = dataset.value.map((item) => {\n const parsed = v.safeParse(\n isLast ? vClaimParser : getJsonClaimParser(claimQuery, { ...ctx, index: index + 1 }),\n item\n )\n\n return parsed\n })\n\n if (mapped.every((parsed) => !parsed.success)) {\n for (const parsed of mapped) {\n for (const issue of parsed.issues) {\n addIssue(issue)\n }\n }\n\n return NEVER\n }\n\n return mapped.map((parsed) => (parsed.success ? parsed.output : null))\n })\n )\n}\n\nexport const runClaimsQuery = (\n credentialQuery: DcqlCredentialQuery,\n ctx: {\n credential: DcqlCredential\n presentation: boolean\n }\n): DcqlClaimsResult => {\n // No claims, always matches\n if (!credentialQuery.claims) {\n return {\n success: true,\n valid_claims: undefined,\n failed_claims: undefined,\n valid_claim_sets: [\n {\n claim_set_index: undefined,\n output: {},\n success: true,\n valid_claim_indexes: undefined,\n },\n ],\n failed_claim_sets: undefined,\n }\n }\n\n const failedClaims: Array<\n v.InferOutput<typeof DcqlClaimsResult.vClaimsEntryFailureResult> & {\n parser: vBaseSchemaAny\n }\n > = []\n const validClaims: Array<\n v.InferOutput<typeof DcqlClaimsResult.vClaimsEntrySuccessResult> & {\n parser: vBaseSchemaAny\n }\n > = []\n\n for (const [claimIndex, claimQuery] of credentialQuery.claims.entries()) {\n const parser =\n credentialQuery.format === 'mso_mdoc'\n ? getMdocClaimParser(claimQuery as DcqlClaimsQuery.Mdoc)\n : getJsonClaimParser(claimQuery as DcqlClaimsQuery.W3cAndSdJwtVc, {\n index: 0,\n presentation: ctx.presentation,\n })\n\n const parseResult = v.safeParse(\n parser,\n ctx.credential.credential_format === 'mso_mdoc' ? ctx.credential.namespaces : ctx.credential.claims\n )\n\n if (parseResult.success) {\n validClaims.push({\n success: true,\n claim_index: claimIndex,\n claim_id: claimQuery.id,\n output: parseResult.output,\n parser,\n })\n } else {\n const flattened = v.flatten(parseResult.issues)\n failedClaims.push({\n success: false,\n issues: flattened.nested ?? flattened,\n claim_index: claimIndex,\n claim_id: claimQuery.id,\n output: parseResult.output,\n parser,\n })\n }\n }\n\n const failedClaimSets: v.InferOutput<typeof DcqlClaimsResult.vClaimSetFailureResult>[] = []\n const validClaimSets: v.InferOutput<typeof DcqlClaimsResult.vClaimSetSuccessResult>[] = []\n\n for (const [claimSetIndex, claimSet] of credentialQuery.claim_sets?.entries() ?? [[undefined, undefined]]) {\n const claims = claimSet?.map((id) => {\n const claim =\n validClaims.find((claim) => claim.claim_id === id) ?? failedClaims.find((claim) => claim.claim_id === id)\n if (!claim) {\n throw new DcqlParseError({\n message: `Claim with id '${id}' in query '${credentialQuery.id}' from claim set with index '${claimSetIndex}' not found in claims of claim`,\n })\n }\n\n return claim\n }) ?? [...validClaims, ...failedClaims] // This handles the case where there is no claim sets (so we use all claims)\n\n if (claims.every((claim) => claim.success)) {\n const output = claims.reduce((merged, claim) => deepMerge(claim.output, merged), {})\n validClaimSets.push({\n success: true,\n claim_set_index: claimSetIndex,\n output,\n valid_claim_indexes: asNonEmptyArrayOrUndefined(claims.map((claim) => claim.claim_index)),\n })\n } else {\n const issues = failedClaims.reduce((merged, claim) => deepMerge(claim.issues, merged), {})\n failedClaimSets.push({\n success: false,\n issues,\n claim_set_index: claimSetIndex,\n failed_claim_indexes: claims.filter((claim) => !claim.success).map((claim) => claim.claim_index) as [\n number,\n ...number[],\n ],\n valid_claim_indexes: asNonEmptyArrayOrUndefined(\n claims.filter((claim) => claim.success).map((claim) => claim.claim_index)\n ),\n })\n }\n }\n\n if (isNonEmptyArray(validClaimSets)) {\n return {\n success: true,\n failed_claim_sets: asNonEmptyArrayOrUndefined(failedClaimSets),\n valid_claim_sets: validClaimSets,\n valid_claims: asNonEmptyArrayOrUndefined(validClaims.map(({ parser, ...rest }) => rest)),\n failed_claims: asNonEmptyArrayOrUndefined(failedClaims.map(({ parser, ...rest }) => rest)),\n }\n }\n\n // If valid claim sets is empty we know for sure there\n // is at least one failed claim and one failed claim set\n return {\n success: false,\n failed_claim_sets: failedClaimSets as ToNonEmptyArray<typeof failedClaimSets>,\n failed_claims: failedClaims.map(({ parser, ...rest }) => rest) as ToNonEmptyArray<typeof failedClaims>,\n valid_claims: asNonEmptyArrayOrUndefined(validClaims.map(({ parser, ...rest }) => rest)),\n }\n}\n","import * as v from 'valibot'\nimport { vIdString, vNonEmptyArray } from '../u-dcql.js'\n\n/**\n * Specifies claims within a requested Credential.\n */\nexport namespace DcqlClaimsQuery {\n export const vValue = v.union([v.string(), v.pipe(v.number(), v.integer()), v.boolean()])\n\n export const vPath = v.union([v.string(), v.pipe(v.number(), v.integer(), v.minValue(0)), v.null()])\n\n export const vW3cSdJwtVc = v.object({\n id: v.pipe(\n v.optional(vIdString),\n v.description(\n '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.'\n )\n ),\n path: v.pipe(\n vNonEmptyArray(vPath),\n v.description(\n 'A non-empty array representing a claims path pointer that specifies the path to a claim within the Verifiable Credential.'\n )\n ),\n values: v.pipe(\n v.optional(v.array(vValue)),\n v.description(\n '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.'\n )\n ),\n })\n export type W3cAndSdJwtVc = v.InferOutput<typeof vW3cSdJwtVc>\n\n const vMdocBase = v.object({\n id: v.pipe(\n v.optional(vIdString),\n v.description(\n '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.'\n )\n ),\n values: v.pipe(\n v.optional(v.array(vValue)),\n v.description(\n '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.'\n )\n ),\n })\n\n // Syntax up until Draft 23 of OID4VP. Keeping due to Draft 23 having\n // reach ID-3 status and thus being targeted by several implementations\n export const vMdocNamespace = v.object({\n ...vMdocBase.entries,\n\n namespace: v.pipe(\n v.string(),\n v.description(\n 'A string that specifies the namespace of the data element within the mdoc, e.g., org.iso.18013.5.1.'\n )\n ),\n claim_name: v.pipe(\n v.string(),\n v.description(\n 'A string that specifies the data element identifier of the data element within the provided namespace in the mdoc, e.g., first_name.'\n )\n ),\n })\n\n // Syntax starting from Draft 24 of OID4VP\n export const vMdocPath = v.object({\n ...vMdocBase.entries,\n\n intent_to_retain: v.pipe(\n v.optional(v.boolean()),\n v.description(\n 'A boolean that is equivalent to `IntentToRetain` variable defined in Section 8.3.2.1.2.1 of [@ISO.18013-5].'\n )\n ),\n\n path: v.pipe(\n v.tuple([\n v.pipe(\n v.string(),\n v.description(\n 'A string that specifies the namespace of the data element within the mdoc, e.g., org.iso.18013.5.1.'\n )\n ),\n v.pipe(\n v.string(),\n v.description(\n 'A string that specifies the data element identifier of the data element within the provided namespace in the mdoc, e.g., first_name.'\n )\n ),\n ]),\n v.description(\n '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.'\n )\n ),\n })\n export const vMdoc = v.union([vMdocNamespace, vMdocPath])\n export type MdocNamespace = v.InferOutput<typeof vMdocNamespace>\n export type MdocPath = v.InferOutput<typeof vMdocPath>\n export type Mdoc = v.InferOutput<typeof vMdoc>\n\n export const vModel = v.union([vMdoc, vW3cSdJwtVc])\n export type Input = v.InferInput<typeof vModel>\n export type Out = v.InferOutput<typeof vModel>\n\n export type ClaimValue = v.InferOutput<typeof vValue>\n}\nexport type DcqlClaimsQuery = DcqlClaimsQuery.Out\n","import { DcqlError } from '../dcql-error'\n\n/**\n * Deep merge two objects. Null values will be overriden if there is a value in one\n * of the two objects. Objects can also be arrays, but otherwise only primitive types\n * are allowed\n */\nexport function deepMerge(source: Array<unknown> | object, target: Array<unknown> | object): Array<unknown> | object {\n let newTarget = target\n\n if (Object.getPrototypeOf(source) !== Object.prototype && !Array.isArray(source)) {\n throw new DcqlError({\n message: 'source value provided to deepMerge is neither an array or object.',\n code: 'PARSE_ERROR',\n })\n }\n if (Object.getPrototypeOf(target) !== Object.prototype && !Array.isArray(target)) {\n throw new DcqlError({\n message: 'target value provided to deepMerge is neither an array or object.',\n code: 'PARSE_ERROR',\n })\n }\n\n for (const [key, val] of Object.entries(source)) {\n if (\n val !== null &&\n typeof val === 'object' &&\n (Object.getPrototypeOf(val) === Object.prototype || Array.isArray(val))\n ) {\n const newValue = deepMerge(\n val,\n newTarget[key as keyof typeof newTarget] ?? new (Object.getPrototypeOf(val).constructor)()\n )\n newTarget = setValue(newTarget, key, newValue)\n } else if (val != null) {\n newTarget = setValue(newTarget, key, val)\n }\n }\n return newTarget\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: value can be anything\nfunction setValue(target: any, key: string, value: any) {\n let newTarget = target\n\n if (Array.isArray(newTarget)) {\n newTarget = [...newTarget]\n newTarget[key as keyof typeof newTarget] = value\n } else if (Object.getPrototypeOf(newTarget) === Object.prototype) {\n newTarget = { ...newTarget, [key]: value }\n } else {\n throw new DcqlError({\n message: 'Unsupported type for deep merge. Only primitive types or Array and Object are supported',\n code: 'INTERNAL_SERVER_ERROR',\n })\n }\n\n return newTarget\n}\n","import * as v from 'valibot'\nimport { DcqlError } from '../dcql-error/e-base.js'\nimport type { DcqlMetaResult } from '../dcql-query-result/m-meta-result.js'\nimport type { DcqlCredentialQuery } from '../dcql-query/m-dcql-credential-query.js'\nimport type { DcqlCredential } from '../u-dcql-credential.js'\nimport { vIncludesAll, vNonEmptyArray } from '../u-dcql.js'\n\nconst getCryptographicHolderBindingValue = (credentialQuery: DcqlCredentialQuery) =>\n v.object({\n cryptographic_holder_binding: credentialQuery.require_cryptographic_holder_binding\n ? v.literal(\n true,\n (i) =>\n `Expected cryptographic_holder_binding to be true (because credential query '${credentialQuery.id}' requires cryptographic holder binding), but received ${i.input}`\n )\n : v.boolean(),\n })\n\nconst getMdocMetaParser = (credentialQuery: DcqlCredentialQuery.Mdoc) => {\n const vDoctype = credentialQuery.meta?.doctype_value\n ? v.literal(\n credentialQuery.meta.doctype_value,\n (i) => `Expected doctype to be '${credentialQuery.meta?.doctype_value}' but received '${i.input}'`\n )\n : v.string('Expected doctype to be defined')\n\n const credentialParser = v.object({\n credential_format: v.literal(\n 'mso_mdoc',\n (i) => `Expected credential format to be 'mso_mdoc' but received '${i.input}'`\n ),\n doctype: vDoctype,\n ...getCryptographicHolderBindingValue(credentialQuery).entries,\n })\n\n return credentialParser\n}\n\nconst getSdJwtVcMetaParser = (credentialQuery: DcqlCredentialQuery.SdJwtVc) => {\n return v.object({\n credential_format: v.literal(\n credentialQuery.format,\n (i) => `Expected credential format to be '${credentialQuery.format}' but received '${i.input}'`\n ),\n vct: credentialQuery.meta?.vct_values\n ? v.picklist(\n credentialQuery.meta.vct_values,\n (i) => `Expected vct to be '${credentialQuery.meta?.vct_values?.join(\"' | '\")}' but received '${i.input}'`\n )\n : v.string('Expected vct to be defined'),\n ...getCryptographicHolderBindingValue(credentialQuery).entries,\n })\n}\n\nconst getW3cVcMetaParser = (credentialQuery: DcqlCredentialQuery.W3cVc) => {\n return v.object({\n credential_format: v.literal(\n credentialQuery.format,\n (i) => `Expected credential format to be '${credentialQuery.format}' but received '${i.input}'`\n ),\n type: credentialQuery.meta?.type_values\n ? v.union(\n credentialQuery.meta.type_values.map((values) => vIncludesAll(values)),\n `Expected type to include all values from one of the following subsets: ${credentialQuery.meta.type_values\n .map((values) => `[${values.join(', ')}]`)\n .join(' | ')}`\n )\n : vNonEmptyArray(v.string()),\n ...getCryptographicHolderBindingValue(credentialQuery).entries,\n })\n}\n\nexport const getMetaParser = (credentialQuery: DcqlCredentialQuery) => {\n if (credentialQuery.format === 'mso_mdoc') {\n return getMdocMetaParser(credentialQuery)\n }\n\n if (credentialQuery.format === 'dc+sd-jwt' || credentialQuery.format === 'vc+sd-jwt') {\n return getSdJwtVcMetaParser(credentialQuery)\n }\n\n if (credentialQuery.format === 'ldp_vc' || credentialQuery.format === 'jwt_vc_json') {\n return getW3cVcMetaParser(credentialQuery)\n }\n\n throw new DcqlError({\n code: 'NOT_IMPLEMENTED',\n message: `Usupported format '${credentialQuery.format}'`,\n })\n}\n\nexport const runMetaQuery = (credentialQuery: DcqlCredentialQuery, credential: DcqlCredential): DcqlMetaResult => {\n const metaParser = getMetaParser(credentialQuery)\n const parseResult = v.safeParse(metaParser, credential)\n\n if (!parseResult.success) {\n const issues = v.flatten(parseResult.issues)\n return {\n success: false,\n issues: issues.nested ?? issues,\n output: parseResult.output,\n }\n }\n\n return {\n success: true,\n output: parseResult.output,\n }\n}\n","import * as v from 'valibot'\nimport type { DcqlCredentialQuery } from '../dcql-query/m-dcql-credential-query.js'\nimport type { DcqlCredential } from '../u-dcql-credential.js'\n\nimport type { DcqlTrustedAuthoritiesResult } from '../dcql-query-result/m-trusted-authorities-result.js'\nimport { getTrustedAuthorityParser } from '../dcql-query/m-dcql-trusted-authorities.js'\nimport { type ToNonEmptyArray, asNonEmptyArrayOrUndefined } from '../u-dcql.js'\n\nexport const runTrustedAuthoritiesQuery = (\n credentialQuery: DcqlCredentialQuery,\n credential: DcqlCredential\n): DcqlTrustedAuthoritiesResult => {\n if (!credentialQuery.trusted_authorities) {\n return {\n success: true,\n }\n }\n\n const failedTrustedAuthorities: v.InferOutput<\n typeof DcqlTrustedAuthoritiesResult.vTrustedAuthorityEntryFailureResult\n >[] = []\n\n for (const [trustedAuthorityIndex, trustedAuthority] of credentialQuery.trusted_authorities.entries()) {\n const trustedAuthorityParser = getTrustedAuthorityParser(trustedAuthority)\n const parseResult = v.safeParse(trustedAuthorityParser, credential.authority)\n\n if (parseResult.success) {\n return {\n success: true,\n valid_trusted_authority: {\n success: true,\n trusted_authority_index: trustedAuthorityIndex,\n output: parseResult.output,\n },\n failed_trusted_authorities: asNonEmptyArrayOrUndefined(failedTrustedAuthorities),\n }\n }\n\n const issues = v.flatten(parseResult.issues)\n failedTrustedAuthorities.push({\n success: false,\n trusted_authority_index: trustedAuthorityIndex,\n issues: issues.nested ?? issues,\n output: parseResult.output,\n })\n }\n\n return {\n success: false,\n failed_trusted_authorities: failedTrustedAuthorities as ToNonEmptyArray<typeof failedTrustedAuthorities>,\n }\n}\n","import type { DcqlQueryResult } from '../dcql-query-result/m-dcql-query-result.js'\nimport type { DcqlCredentialQuery } from '../dcql-query/m-dcql-credential-query.js'\nimport type { DcqlCredential } from '../u-dcql-credential.js'\nimport { asNonEmptyArrayOrUndefined, isNonEmptyArray } from '../u-dcql.js'\nimport { runClaimsQuery } from './dcql-claims-query-result.js'\nimport { runMetaQuery } from './dcql-meta-query-result.js'\nimport { runTrustedAuthoritiesQuery } from './dcql-trusted-authorities-result.js'\n\nexport const runCredentialQuery = (\n credentialQuery: DcqlCredentialQuery,\n ctx: {\n credentials: DcqlCredential[]\n presentation: boolean\n }\n): DcqlQueryResult.CredentialQueryItemResult => {\n const { credentials, presentation } = ctx\n\n const validCredentials: DcqlQueryResult.CredentialQueryItemCredentialSuccessResult[] = []\n const failedCredentials: DcqlQueryResult.CredentialQueryItemCredentialFailureResult[] = []\n\n for (const [credentialIndex, credential] of credentials.entries()) {\n const trustedAuthorityResult = runTrustedAuthoritiesQuery(credentialQuery, credential)\n const claimsResult = runClaimsQuery(credentialQuery, { credential, presentation })\n const metaResult = runMetaQuery(credentialQuery, credential)\n\n // if we found a valid claim set and trusted authority, the credential succeeded processing\n if (claimsResult.success && trustedAuthorityResult.success && metaResult.success) {\n validCredentials.push({\n success: true,\n\n input_credential_index: credentialIndex,\n trusted_authorities: trustedAuthorityResult,\n meta: metaResult,\n claims: claimsResult,\n })\n } else {\n failedCredentials.push({\n success: false,\n input_credential_index: credentialIndex,\n trusted_authorities: trustedAuthorityResult,\n meta: metaResult,\n claims: claimsResult,\n })\n }\n }\n\n if (isNonEmptyArray(validCredentials)) {\n return {\n success: true,\n credential_query_id: credentialQuery.id,\n failed_credentials: asNonEmptyArrayOrUndefined(failedCredentials),\n valid_credentials: validCredentials,\n }\n }\n\n return {\n success: false,\n credential_query_id: credentialQuery.id,\n // Can be undefined i