dcql 0.2.2

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.
Files changed (81) hide show
  1. package/.cache/tsbuildinfo.json +1 -0
  2. package/.turbo/turbo-build.log +4 -0
  3. package/CHANGELOG.md +7 -0
  4. package/dist/esm/src/claims-query/claims-query-result.d.ts +24 -0
  5. package/dist/esm/src/claims-query/claims-query-result.d.ts.map +1 -0
  6. package/dist/esm/src/claims-query/claims-query-result.js +67 -0
  7. package/dist/esm/src/claims-query/claims-query-result.js.map +1 -0
  8. package/dist/esm/src/claims-query/v-claims-query-result.d.ts +82 -0
  9. package/dist/esm/src/claims-query/v-claims-query-result.d.ts.map +1 -0
  10. package/dist/esm/src/claims-query/v-claims-query-result.js +30 -0
  11. package/dist/esm/src/claims-query/v-claims-query-result.js.map +1 -0
  12. package/dist/esm/src/claims-query/v-claims-query.d.ts +34 -0
  13. package/dist/esm/src/claims-query/v-claims-query.d.ts.map +1 -0
  14. package/dist/esm/src/claims-query/v-claims-query.js +24 -0
  15. package/dist/esm/src/claims-query/v-claims-query.js.map +1 -0
  16. package/dist/esm/src/credential-query/credential-query-result.d.ts +101 -0
  17. package/dist/esm/src/credential-query/credential-query-result.d.ts.map +1 -0
  18. package/dist/esm/src/credential-query/credential-query-result.js +91 -0
  19. package/dist/esm/src/credential-query/credential-query-result.js.map +1 -0
  20. package/dist/esm/src/credential-query/v-credential-query-result.d.ts +696 -0
  21. package/dist/esm/src/credential-query/v-credential-query-result.d.ts.map +1 -0
  22. package/dist/esm/src/credential-query/v-credential-query-result.js +44 -0
  23. package/dist/esm/src/credential-query/v-credential-query-result.js.map +1 -0
  24. package/dist/esm/src/credential-query/v-credential-query.d.ts +109 -0
  25. package/dist/esm/src/credential-query/v-credential-query.d.ts.map +1 -0
  26. package/dist/esm/src/credential-query/v-credential-query.js +57 -0
  27. package/dist/esm/src/credential-query/v-credential-query.js.map +1 -0
  28. package/dist/esm/src/e-base.d.ts +25 -0
  29. package/dist/esm/src/e-base.d.ts.map +1 -0
  30. package/dist/esm/src/e-base.js +83 -0
  31. package/dist/esm/src/e-base.js.map +1 -0
  32. package/dist/esm/src/e-vp-query.d.ts +32 -0
  33. package/dist/esm/src/e-vp-query.d.ts.map +1 -0
  34. package/dist/esm/src/e-vp-query.js +27 -0
  35. package/dist/esm/src/e-vp-query.js.map +1 -0
  36. package/dist/esm/src/index.d.ts +7 -0
  37. package/dist/esm/src/index.d.ts.map +1 -0
  38. package/dist/esm/src/index.js +7 -0
  39. package/dist/esm/src/index.js.map +1 -0
  40. package/dist/esm/src/u-query.d.ts +20 -0
  41. package/dist/esm/src/u-query.d.ts.map +1 -0
  42. package/dist/esm/src/u-query.js +25 -0
  43. package/dist/esm/src/u-query.js.map +1 -0
  44. package/dist/esm/src/vp-query/v-vp-query-result.d.ts +767 -0
  45. package/dist/esm/src/vp-query/v-vp-query-result.d.ts.map +1 -0
  46. package/dist/esm/src/vp-query/v-vp-query-result.js +18 -0
  47. package/dist/esm/src/vp-query/v-vp-query-result.js.map +1 -0
  48. package/dist/esm/src/vp-query/v-vp-query.d.ts +94 -0
  49. package/dist/esm/src/vp-query/v-vp-query.d.ts.map +1 -0
  50. package/dist/esm/src/vp-query/v-vp-query.js +52 -0
  51. package/dist/esm/src/vp-query/v-vp-query.js.map +1 -0
  52. package/dist/esm/src/vp-query/vp-query.d.ts +5 -0
  53. package/dist/esm/src/vp-query/vp-query.d.ts.map +1 -0
  54. package/dist/esm/src/vp-query/vp-query.fixtures.d.ts +7 -0
  55. package/dist/esm/src/vp-query/vp-query.fixtures.d.ts.map +1 -0
  56. package/dist/esm/src/vp-query/vp-query.fixtures.js +172 -0
  57. package/dist/esm/src/vp-query/vp-query.fixtures.js.map +1 -0
  58. package/dist/esm/src/vp-query/vp-query.js +21 -0
  59. package/dist/esm/src/vp-query/vp-query.js.map +1 -0
  60. package/dist/esm/src/vp-query/vp-query.test.d.ts +31 -0
  61. package/dist/esm/src/vp-query/vp-query.test.d.ts.map +1 -0
  62. package/dist/esm/src/vp-query/vp-query.test.js +130 -0
  63. package/dist/esm/src/vp-query/vp-query.test.js.map +1 -0
  64. package/eslint.config.js +10 -0
  65. package/package.json +60 -0
  66. package/src/claims-query/claims-query-result.ts +104 -0
  67. package/src/claims-query/v-claims-query-result.ts +42 -0
  68. package/src/claims-query/v-claims-query.ts +34 -0
  69. package/src/credential-query/credential-query-result.ts +130 -0
  70. package/src/credential-query/v-credential-query-result.ts +71 -0
  71. package/src/credential-query/v-credential-query.ts +85 -0
  72. package/src/e-base.ts +116 -0
  73. package/src/e-vp-query.ts +31 -0
  74. package/src/index.ts +6 -0
  75. package/src/u-query.ts +39 -0
  76. package/src/vp-query/v-vp-query-result.ts +23 -0
  77. package/src/vp-query/v-vp-query.ts +71 -0
  78. package/src/vp-query/vp-query.fixtures.ts +178 -0
  79. package/src/vp-query/vp-query.test.ts +156 -0
  80. package/src/vp-query/vp-query.ts +44 -0
  81. package/tsconfig.json +5 -0
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "dcql",
3
+ "description": "Digital Credentials Query Language (DCQL)",
4
+ "author": "Martin Auer",
5
+ "version": "0.2.2",
6
+ "private": false,
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./src/index.ts",
11
+ "default": "./dist/src/index.js"
12
+ }
13
+ },
14
+ "license": "MIT",
15
+ "dependencies": {
16
+ "valibot": "0.37.0"
17
+ },
18
+ "devDependencies": {
19
+ "@ausweis/eslint": "^0.2.34",
20
+ "@ausweis/prettier": "^0.2.34",
21
+ "@ausweis/typescript": "^0.2.34",
22
+ "@types/node": "^20.16.10",
23
+ "eslint": "^9.9.0",
24
+ "prettier": "^3.3.3",
25
+ "tsx": "^4.11.2",
26
+ "typescript": "^5.6.2"
27
+ },
28
+ "prettier": "@ausweis/prettier",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/auer-martin/vp-query"
32
+ },
33
+ "keywords": [
34
+ "Digital Credentials Query Language (DCQL)",
35
+ "Credentials Query Language",
36
+ "Verifiable Presentation Query Language",
37
+ "Verifiable Presentations",
38
+ "Presentation Definition",
39
+ "Credentials",
40
+ "SSI",
41
+ "OpenID",
42
+ "SIOP",
43
+ "Self Issued OpenId Provider",
44
+ "OpenId for Verifiable Presentations",
45
+ "SIOPv2",
46
+ "OID4VC",
47
+ "OID4VP",
48
+ "OpenID4VP",
49
+ "OpenID4VC",
50
+ "OIDC4VP"
51
+ ],
52
+ "scripts": {
53
+ "clean": "git clean -xdf .cache .turbo node_modules dist",
54
+ "build": "tsc",
55
+ "format": "prettier --check . --ignore-path ./../.gitignore",
56
+ "lint": "eslint .",
57
+ "typecheck": "tsc --noEmit",
58
+ "test": "tsx --test"
59
+ }
60
+ }
@@ -0,0 +1,104 @@
1
+ import * as v from 'valibot';
2
+ import { InvalidClaimsQueryIdError } from '../e-vp-query.js';
3
+ import type { Mdoc } from '../u-query.js';
4
+ import { getIdMetadata } from '../u-query.js';
5
+ import type { ClaimsQueryResult } from './v-claims-query-result.js';
6
+ import { ClaimsQuery } from './v-claims-query.js';
7
+
8
+ const getClaimParser = (input: {
9
+ value?: string | number | boolean;
10
+ values?: (string | number | boolean)[];
11
+ }) => {
12
+ const { value, values } = input;
13
+ if (value) {
14
+ return v.literal(value);
15
+ }
16
+
17
+ if (values) {
18
+ return v.union(values.map(val => v.literal(val)));
19
+ }
20
+
21
+ return ClaimsQuery.vValue;
22
+ };
23
+
24
+ export const getNamespacesParser = (
25
+ claimsQueries: ReturnType<typeof getClaimsQueriesForClaimSet>
26
+ ) => {
27
+ const claimsForNamespace: Record<
28
+ string,
29
+ ReturnType<typeof getClaimsQueriesForClaimSet>
30
+ > = {};
31
+
32
+ for (const claimQuery of claimsQueries) {
33
+ if (claimsForNamespace[claimQuery.namespace]) {
34
+ claimsForNamespace[claimQuery.namespace]?.push({ ...claimQuery });
35
+ } else {
36
+ claimsForNamespace[claimQuery.namespace] = [{ ...claimQuery }];
37
+ }
38
+ }
39
+
40
+ const parsersForNamespaces = Object.entries(claimsForNamespace).map(
41
+ ([namespace, claims]) => {
42
+ const claimParsers = Object.fromEntries(
43
+ claims.map(claim => [
44
+ claim.claim_name,
45
+ claim.isOptional || claim.isRequiredIfPresent
46
+ ? v.optional(getClaimParser(claim))
47
+ : getClaimParser(claim),
48
+ ])
49
+ );
50
+ return [namespace, v.object(claimParsers)];
51
+ }
52
+ );
53
+
54
+ return v.object(Object.fromEntries(parsersForNamespaces));
55
+ };
56
+
57
+ export const queryClaimFromMdoc = (
58
+ claimsQuery: ClaimsQuery.Mdoc,
59
+ credential: Mdoc
60
+ ) => {
61
+ const claimParser = getClaimParser(claimsQuery);
62
+ const claimParsers =
63
+ claimParser.type === 'union' ? claimParser.options : [claimParser];
64
+
65
+ const namespace = credential.namespaces[claimsQuery.namespace];
66
+ const claimParseResult: ClaimsQueryResult.ParseResult[] = [];
67
+ for (const claimParser of claimParsers) {
68
+ const parseResult = v.safeParse(
69
+ claimParser,
70
+ namespace?.[claimsQuery.claim_name]
71
+ );
72
+
73
+ const { typed, ...result } = parseResult;
74
+ claimParseResult.push(result);
75
+ }
76
+
77
+ return claimParseResult;
78
+ };
79
+
80
+ export const getClaimsQueriesForClaimSet = (
81
+ claimsQueries: ClaimsQuery.Mdoc[],
82
+ claimSet?: string[]
83
+ ) => {
84
+ if (!claimSet) {
85
+ return claimsQueries.map(query => ({
86
+ ...query,
87
+ isOptional: false,
88
+ isRequiredIfPresent: false,
89
+ }));
90
+ }
91
+
92
+ return claimSet.map(credential_id => {
93
+ const { isOptional, isRequiredIfPresent, baseId } =
94
+ getIdMetadata(credential_id);
95
+
96
+ const query = claimsQueries.find(query => query.id === baseId);
97
+ if (!query) {
98
+ throw new InvalidClaimsQueryIdError({
99
+ message: `Claims-query with id '${baseId}' not found.`,
100
+ });
101
+ }
102
+ return { ...query, isOptional, isRequiredIfPresent };
103
+ });
104
+ };
@@ -0,0 +1,42 @@
1
+ import * as v from 'valibot';
2
+ import { ClaimsQuery } from './v-claims-query.js';
3
+
4
+ export namespace ClaimsQueryResult {
5
+ export const vParseResult = v.union([
6
+ v.looseObject({
7
+ success: v.literal(true),
8
+ output: ClaimsQuery.vValue,
9
+ }),
10
+ v.looseObject({
11
+ success: v.literal(false),
12
+ output: v.unknown(),
13
+ }),
14
+ ]);
15
+ export type ParseResult = v.InferOutput<typeof vParseResult>;
16
+
17
+ export const vQueryResult = v.array(
18
+ v.union([v.literal('all'), v.array(vParseResult)])
19
+ );
20
+ export type QueryResult = v.InferOutput<typeof vQueryResult>;
21
+
22
+ export const vModelMdoc = v.object({
23
+ ...ClaimsQuery.vMdoc.entries,
24
+ claim_query_results: v.array(vQueryResult),
25
+ isOptional: v.boolean(),
26
+ isRequiredIfPresent: v.boolean(),
27
+ });
28
+ export type Mdoc = v.InferOutput<typeof vModelMdoc>;
29
+
30
+ export const vW3cSdJwtVc = v.object({
31
+ ...ClaimsQuery.vW3cSdJwtVc.entries,
32
+ claim_query_results: v.array(vQueryResult),
33
+ isOptional: v.boolean(),
34
+ isRequiredIfPresent: v.boolean(),
35
+ });
36
+ export type W3cSdJwtVc = v.InferOutput<typeof vModelMdoc>;
37
+
38
+ export const vModel = v.union([vModelMdoc, vW3cSdJwtVc]);
39
+ export type Input = v.InferInput<typeof vModel>;
40
+ export type Out = v.InferOutput<typeof vModel>;
41
+ }
42
+ export type ClaimsQueryResult = ClaimsQueryResult.Out;
@@ -0,0 +1,34 @@
1
+ import * as v from 'valibot';
2
+ import { idRegex } from '../u-query.js';
3
+
4
+ export namespace ClaimsQuery {
5
+ export const vValue = v.union([v.string(), v.number(), v.boolean()]);
6
+ export type ClaimValue = v.InferOutput<typeof vValue>;
7
+
8
+ export const vPath = v.union([
9
+ v.string(),
10
+ v.pipe(v.number(), v.integer(), v.minValue(0)),
11
+ v.literal('*'),
12
+ ]);
13
+ export type ClaimPath = v.InferOutput<typeof vPath>;
14
+
15
+ export const vW3cSdJwtVc = v.object({
16
+ id: v.optional(v.pipe(v.string(), v.regex(idRegex))),
17
+ path: v.optional(v.array(vPath)),
18
+ values: v.optional(v.array(vValue)),
19
+ });
20
+ export type W3cAndSdJwtVcInput = v.InferOutput<typeof vW3cSdJwtVc>;
21
+
22
+ export const vMdoc = v.object({
23
+ id: v.optional(v.pipe(v.string(), v.regex(idRegex))),
24
+ namespace: v.string(),
25
+ claim_name: v.string(),
26
+ values: v.optional(v.array(vValue)),
27
+ });
28
+ export type Mdoc = v.InferOutput<typeof vMdoc>;
29
+
30
+ export const vModel = v.union([vMdoc, vW3cSdJwtVc]);
31
+ export type Input = v.InferInput<typeof vModel>;
32
+ export type Out = v.InferOutput<typeof vModel>;
33
+ }
34
+ export type ClaimsQuery = ClaimsQuery.Out;
@@ -0,0 +1,130 @@
1
+ import * as v from 'valibot';
2
+ import {
3
+ getClaimsQueriesForClaimSet,
4
+ getNamespacesParser,
5
+ queryClaimFromMdoc,
6
+ } from '../claims-query/claims-query-result.js';
7
+ import type { ClaimsQueryResult } from '../claims-query/v-claims-query-result.js';
8
+ import { InvalidCredentialQueryIdError } from '../e-vp-query.js';
9
+ import type { Mdoc } from '../u-query.js';
10
+ import { getIdMetadata } from '../u-query.js';
11
+ import type { CredentialQueryResult } from './v-credential-query-result.js';
12
+ import type { CredentialQuery } from './v-credential-query.js';
13
+
14
+ const queryClaimsFromCredentials = (
15
+ credentialQuery: CredentialQuery,
16
+ credentials: Mdoc[]
17
+ ): ClaimsQueryResult.QueryResult[] => {
18
+ const claimQueryResults: ClaimsQueryResult.QueryResult[] = [];
19
+
20
+ if (!credentialQuery.claims) {
21
+ claimQueryResults.push(['all']);
22
+ return claimQueryResults;
23
+ }
24
+
25
+ if (credentialQuery.format !== 'mso_mdoc') {
26
+ throw new Error('Hello World');
27
+ }
28
+
29
+ for (const claimQuery of credentialQuery.claims) {
30
+ const claimQueryResultsForClaimSet: ClaimsQueryResult.QueryResult = [];
31
+ for (const credential of credentials) {
32
+ const res = queryClaimFromMdoc(claimQuery, credential);
33
+ claimQueryResultsForClaimSet.push(res);
34
+ }
35
+ claimQueryResults.push(claimQueryResultsForClaimSet);
36
+ }
37
+
38
+ return claimQueryResults;
39
+ };
40
+
41
+ const getCredentialQueriesForCredentialSet = (
42
+ credentialQueries: CredentialQuery[],
43
+ credentialSet?: string[]
44
+ ) => {
45
+ if (!credentialSet) {
46
+ return credentialQueries.map(query => ({ ...query, isOptional: false }));
47
+ }
48
+ return credentialSet.map(credential_id => {
49
+ const { isOptional, baseId } = getIdMetadata(credential_id);
50
+
51
+ const query = credentialQueries.find(
52
+ credentialQuery => (credentialQuery.id = baseId)
53
+ );
54
+ if (!query) {
55
+ throw new InvalidCredentialQueryIdError({
56
+ message: `Credential-query with id '${baseId}' not found.`,
57
+ });
58
+ }
59
+ return { ...query, isOptional };
60
+ });
61
+ };
62
+
63
+ export const queryCredentialSet = (
64
+ credentialQueries: CredentialQuery[],
65
+ credentials: Mdoc[],
66
+ credential_set?: [string, ...string[]]
67
+ ) => {
68
+ const credentialQueriesForCredentialSet =
69
+ getCredentialQueriesForCredentialSet(credentialQueries, credential_set);
70
+
71
+ const credentialQueryResults: CredentialQueryResult[] = [];
72
+
73
+ for (const credentialQuery of credentialQueriesForCredentialSet) {
74
+ if (credentialQuery.format !== 'mso_mdoc') {
75
+ throw new Error('asdfasdf');
76
+ }
77
+ const vDocType = credentialQuery.meta?.doctype_values
78
+ ? v.union(credentialQuery.meta.doctype_values.map(v.literal))
79
+ : v.unknown();
80
+
81
+ const claim_sets_results: CredentialQueryResult.QueryResult[] = [];
82
+
83
+ for (const claim_set of credentialQuery.claim_sets ?? [undefined]) {
84
+ const x2 = credentialQuery.claims
85
+ ? getClaimsQueriesForClaimSet(credentialQuery.claims, claim_set)
86
+ : undefined;
87
+ const credentialParser = v.object({
88
+ docType: vDocType,
89
+ namespaces: x2
90
+ ? getNamespacesParser(x2)
91
+ : v.record(v.string(), v.record(v.string(), v.unknown())),
92
+ });
93
+
94
+ const credentialQueryResult: CredentialQueryResult.QueryResult = [];
95
+ for (const credential of credentials) {
96
+ const parseResult = v.safeParse(credentialParser, credential);
97
+
98
+ const { typed, ...result } = parseResult;
99
+ if (result.success) {
100
+ credentialQueryResult.push({
101
+ areRequiredClaimsPresent: result.success,
102
+ ...result,
103
+ });
104
+ } else {
105
+ credentialQueryResult.push({
106
+ areRequiredClaimsPresent: result.success,
107
+ ...result,
108
+ });
109
+ }
110
+ }
111
+ claim_sets_results.push(credentialQueryResult);
112
+ }
113
+
114
+ const claimQueryResults = queryClaimsFromCredentials(
115
+ credentialQuery,
116
+ credentials
117
+ );
118
+
119
+ credentialQueryResults.push({
120
+ ...credentialQuery,
121
+ claim_sets_results,
122
+ claims: credentialQuery.claims?.map(claim => ({
123
+ ...claim,
124
+ claim_query_results: claimQueryResults,
125
+ })),
126
+ });
127
+ }
128
+
129
+ return credentialQueryResults;
130
+ };
@@ -0,0 +1,71 @@
1
+ import * as v from 'valibot';
2
+ import { ClaimsQueryResult } from '../claims-query/v-claims-query-result.js';
3
+ import { CredentialQuery } from './v-credential-query.js';
4
+
5
+ export namespace CredentialQueryResult {
6
+ export const vParseResult = v.union([
7
+ v.object({
8
+ areRequiredClaimsPresent: v.literal(true),
9
+ output: v.record(v.string(), v.unknown()),
10
+ }),
11
+ v.object({
12
+ areRequiredClaimsPresent: v.literal(false),
13
+ output: v.unknown(),
14
+ }),
15
+ ]);
16
+ export type ParseResult = v.InferOutput<typeof vParseResult>;
17
+
18
+ export const vQueryResult = v.array(vParseResult);
19
+ export type QueryResult = v.InferOutput<typeof vQueryResult>;
20
+
21
+ export const vMdoc = v.object({
22
+ ...CredentialQuery.vMdoc.entries,
23
+ claims: v.optional(
24
+ v.pipe(
25
+ v.array(
26
+ v.omit(ClaimsQueryResult.vModelMdoc, [
27
+ 'isOptional',
28
+ 'isRequiredIfPresent',
29
+ ])
30
+ ),
31
+ v.nonEmpty()
32
+ )
33
+ ),
34
+ claim_sets_results: v.array(vQueryResult),
35
+ isOptional: v.boolean(),
36
+ });
37
+ export type Mdoc = v.InferInput<typeof vMdoc>;
38
+
39
+ const vW3cSdJwtVcBase = v.object({
40
+ claims: v.optional(
41
+ v.pipe(
42
+ v.array(
43
+ v.omit(ClaimsQueryResult.vW3cSdJwtVc, [
44
+ 'isOptional',
45
+ 'isRequiredIfPresent',
46
+ ])
47
+ ),
48
+ v.nonEmpty()
49
+ )
50
+ ),
51
+ claim_sets_results: v.array(vQueryResult),
52
+ isOptional: v.boolean(),
53
+ });
54
+
55
+ export const vSdJwtVc = v.object({
56
+ ...CredentialQuery.vSdJwtVc.entries,
57
+ ...vW3cSdJwtVcBase.entries,
58
+ });
59
+ export type SdJwtVc = v.InferOutput<typeof vSdJwtVc>;
60
+
61
+ export const vW3c = v.object({
62
+ ...CredentialQuery.vW3c.entries,
63
+ ...vW3cSdJwtVcBase.entries,
64
+ });
65
+ export type W3c = v.InferOutput<typeof vW3c>;
66
+
67
+ export const vModel = v.variant('format', [vMdoc, vSdJwtVc, vW3c]);
68
+ export type Input = v.InferInput<typeof vModel>;
69
+ export type Out = v.InferOutput<typeof vModel>;
70
+ }
71
+ export type CredentialQueryResult = CredentialQueryResult.Out;
@@ -0,0 +1,85 @@
1
+ import * as v from 'valibot';
2
+ import { ClaimsQuery } from '../claims-query/v-claims-query.js';
3
+ import { VpQueryUndefinedClaimSetIdError } from '../e-vp-query.js';
4
+ import { claimSetIdRegex, getIdMetadata, idRegex } from '../u-query.js';
5
+
6
+ export namespace CredentialQuery {
7
+ export const vMdoc = v.object({
8
+ id: v.pipe(v.string(), v.regex(idRegex)),
9
+ format: v.literal('mso_mdoc'),
10
+ meta: v.optional(
11
+ v.object({
12
+ doctype_values: v.optional(v.array(v.string())),
13
+ })
14
+ ),
15
+ claims: v.optional(v.pipe(v.array(ClaimsQuery.vMdoc), v.nonEmpty())),
16
+ claim_sets: v.optional(
17
+ v.pipe(
18
+ v.array(v.array(v.pipe(v.string(), v.regex(claimSetIdRegex)))),
19
+ v.nonEmpty()
20
+ )
21
+ ),
22
+ });
23
+ export type Mdoc = v.InferOutput<typeof vMdoc>;
24
+
25
+ const vW3cSdJwtVcBase = v.object({
26
+ id: v.pipe(v.string(), v.regex(idRegex)),
27
+ claims: v.optional(v.pipe(v.array(ClaimsQuery.vW3cSdJwtVc), v.nonEmpty())),
28
+ claim_sets: v.optional(
29
+ v.pipe(
30
+ v.array(v.array(v.pipe(v.string(), v.regex(claimSetIdRegex)))),
31
+ v.nonEmpty()
32
+ )
33
+ ),
34
+ });
35
+
36
+ export const vSdJwtVc = v.object({
37
+ ...vW3cSdJwtVcBase.entries,
38
+ format: v.literal('vc+sd-jwt'),
39
+ meta: v.optional(
40
+ v.object({
41
+ vct_values: v.optional(v.array(v.string())),
42
+ })
43
+ ),
44
+ });
45
+ export type SdJwtVc = v.InferOutput<typeof vSdJwtVc>;
46
+
47
+ export const vW3c = v.object({
48
+ ...vW3cSdJwtVcBase.entries,
49
+ format: v.picklist(['jwt_vc_json', 'jwt_vc_json-ld']),
50
+ });
51
+ export type W3c = v.InferOutput<typeof vW3c>;
52
+
53
+ export const vModel = v.variant('format', [vMdoc, vSdJwtVc, vW3c]);
54
+ export type Input = v.InferInput<typeof vModel>;
55
+ export type Out = v.InferInput<typeof vModel>;
56
+
57
+ export const validate = (credentialQuery: Out) => {
58
+ claimSetIdsAreDefined(credentialQuery);
59
+ };
60
+ }
61
+ export type CredentialQuery = CredentialQuery.Out;
62
+
63
+ // --- validations --- //
64
+
65
+ const claimSetIdsAreDefined = (credentialQuery: CredentialQuery) => {
66
+ if (!credentialQuery.claim_sets) return;
67
+ const claimIds = new Set(credentialQuery.claims?.map(c => c.id));
68
+
69
+ const undefinedClaims: string[] = [];
70
+ for (const claim_set of credentialQuery.claim_sets) {
71
+ for (const claim_id of claim_set) {
72
+ const { baseId } = getIdMetadata(claim_id);
73
+
74
+ if (!claimIds.has(baseId)) {
75
+ undefinedClaims.push(baseId);
76
+ }
77
+ }
78
+ }
79
+
80
+ if (undefinedClaims.length > 1) {
81
+ throw new VpQueryUndefinedClaimSetIdError({
82
+ message: `Credential set contains undefined credential id${undefinedClaims.length === 0 ? '' : '`s'} '${undefinedClaims.join(', ')}'`,
83
+ });
84
+ }
85
+ };
package/src/e-base.ts ADDED
@@ -0,0 +1,116 @@
1
+ function isObject(value: unknown): value is Record<string, unknown> {
2
+ return !!value && !Array.isArray(value) && typeof value === 'object';
3
+ }
4
+
5
+ class UnknownCauseError extends Error {
6
+ [key: string]: unknown;
7
+ }
8
+
9
+ export function getCauseFromUnknown(cause: unknown): Error | undefined {
10
+ if (cause instanceof Error) {
11
+ return cause;
12
+ }
13
+
14
+ const type = typeof cause;
15
+ if (type === 'undefined' || type === 'function' || cause === null) {
16
+ return undefined;
17
+ }
18
+
19
+ // Primitive types just get wrapped in an error
20
+ if (type !== 'object') {
21
+ return new Error(String(cause));
22
+ }
23
+
24
+ // If it's an object, we'll create a synthetic error
25
+ if (isObject(cause)) {
26
+ const err = new UnknownCauseError();
27
+ for (const key in cause) {
28
+ err[key] = cause[key];
29
+ }
30
+ return err;
31
+ }
32
+
33
+ return undefined;
34
+ }
35
+
36
+ export const isAusweisError = (cause: unknown): cause is VpQueryError => {
37
+ if (cause instanceof VpQueryError) {
38
+ return true;
39
+ }
40
+ if (cause instanceof Error && cause.name === 'AusweisError') {
41
+ // https://github.com/trpc/trpc/pull/4848
42
+ return true;
43
+ }
44
+
45
+ return false;
46
+ };
47
+
48
+ export function getAusweisErrorFromUnknown(cause: unknown): VpQueryError {
49
+ if (isAusweisError(cause)) {
50
+ return cause;
51
+ }
52
+
53
+ const ausweisError = new VpQueryError({
54
+ code: 'INTERNAL_SERVER_ERROR',
55
+ cause,
56
+ });
57
+
58
+ // Inherit stack from error
59
+ if (cause instanceof Error && cause.stack) {
60
+ ausweisError.stack = cause.stack;
61
+ }
62
+
63
+ return ausweisError;
64
+ }
65
+
66
+ type AUSWEIS_ERROR_CODE =
67
+ | 'PARSE_ERROR'
68
+ | 'INTERNAL_SERVER_ERROR'
69
+ | 'NOT_IMPLEMENTED'
70
+ | 'BAD_REQUEST';
71
+
72
+ export class VpQueryError extends Error {
73
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
74
+ // @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'"
75
+ public override readonly cause?: Error;
76
+ public readonly code;
77
+
78
+ constructor(opts: {
79
+ message?: string;
80
+ code: AUSWEIS_ERROR_CODE;
81
+ cause?: unknown;
82
+ }) {
83
+ const cause = getCauseFromUnknown(opts.cause);
84
+ const message = opts.message ?? cause?.message ?? opts.code;
85
+
86
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
87
+ // @ts-ignore https://github.com/tc39/proposal-error-cause
88
+ super(message, { cause });
89
+
90
+ this.code = opts.code;
91
+ this.name = 'AusweisError';
92
+
93
+ if (!this.cause) {
94
+ // < ES2022 / < Node 16.9.0 compatability
95
+ this.cause = cause;
96
+ }
97
+ }
98
+ }
99
+
100
+ export const NOT_IMPLEMENTED = <Error extends typeof VpQueryError>(input: {
101
+ message?: string;
102
+ error?: Error;
103
+ }) => {
104
+ const { message, error } = input;
105
+
106
+ throw new (error ?? VpQueryError)({
107
+ code: 'NOT_IMPLEMENTED',
108
+ message: `NOT IMPLEMENTED. ${message}`,
109
+ });
110
+ };
111
+
112
+ export class Base64Error extends VpQueryError {
113
+ constructor(opts: { message: string; cause?: unknown }) {
114
+ super({ code: 'BAD_REQUEST', ...opts });
115
+ }
116
+ }
@@ -0,0 +1,31 @@
1
+ import { VpQueryError } from './e-base.js';
2
+
3
+ export class VpQueryCredentialSetError extends VpQueryError {
4
+ constructor(opts: { message: string; cause?: unknown }) {
5
+ super({ code: 'BAD_REQUEST', ...opts });
6
+ }
7
+ }
8
+
9
+ export class VpQueryUndefinedClaimSetIdError extends VpQueryError {
10
+ constructor(opts: { message: string; cause?: unknown }) {
11
+ super({ code: 'BAD_REQUEST', ...opts });
12
+ }
13
+ }
14
+
15
+ export class VpQueryNonUniqueCredentialQueryIdsError extends VpQueryError {
16
+ constructor(opts: { message: string; cause?: unknown }) {
17
+ super({ code: 'BAD_REQUEST', ...opts });
18
+ }
19
+ }
20
+
21
+ export class InvalidClaimsQueryIdError extends VpQueryError {
22
+ constructor(opts: { message: string; cause?: unknown }) {
23
+ super({ code: 'BAD_REQUEST', ...opts });
24
+ }
25
+ }
26
+
27
+ export class InvalidCredentialQueryIdError extends VpQueryError {
28
+ constructor(opts: { message: string; cause?: unknown }) {
29
+ super({ code: 'BAD_REQUEST', ...opts });
30
+ }
31
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export { ClaimsQueryResult } from './claims-query/v-claims-query-result.js';
2
+ export { ClaimsQuery } from './claims-query/v-claims-query.js';
3
+ export { CredentialQueryResult } from './credential-query/v-credential-query-result.js';
4
+ export { CredentialQuery } from './credential-query/v-credential-query.js';
5
+ export { VpQueryResult } from './vp-query/v-vp-query-result.js';
6
+ export { VpQuery } from './vp-query/v-vp-query.js';