@interop/wallet-core 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.md +27 -3
  2. package/dist/display/alignment.d.ts +49 -0
  3. package/dist/display/alignment.d.ts.map +1 -0
  4. package/dist/display/alignment.js +114 -0
  5. package/dist/display/alignment.js.map +1 -0
  6. package/dist/display/credentialName.d.ts +42 -0
  7. package/dist/display/credentialName.d.ts.map +1 -0
  8. package/dist/display/credentialName.js +106 -0
  9. package/dist/display/credentialName.js.map +1 -0
  10. package/dist/display/displayFields.d.ts +55 -0
  11. package/dist/display/displayFields.d.ts.map +1 -0
  12. package/dist/display/displayFields.js +136 -0
  13. package/dist/display/displayFields.js.map +1 -0
  14. package/dist/display/evidence.d.ts +44 -0
  15. package/dist/display/evidence.d.ts.map +1 -0
  16. package/dist/display/evidence.js +65 -0
  17. package/dist/display/evidence.js.map +1 -0
  18. package/dist/display/image.d.ts +18 -0
  19. package/dist/display/image.d.ts.map +1 -0
  20. package/dist/display/image.js +23 -0
  21. package/dist/display/image.js.map +1 -0
  22. package/dist/display/index.d.ts +38 -0
  23. package/dist/display/index.d.ts.map +1 -0
  24. package/dist/display/index.js +32 -0
  25. package/dist/display/index.js.map +1 -0
  26. package/dist/display/issuer.d.ts +97 -0
  27. package/dist/display/issuer.d.ts.map +1 -0
  28. package/dist/display/issuer.js +126 -0
  29. package/dist/display/issuer.js.map +1 -0
  30. package/dist/display/obv3.d.ts +77 -0
  31. package/dist/display/obv3.d.ts.map +1 -0
  32. package/dist/display/obv3.js +127 -0
  33. package/dist/display/obv3.js.map +1 -0
  34. package/dist/display/parse.d.ts +62 -0
  35. package/dist/display/parse.d.ts.map +1 -0
  36. package/dist/display/parse.js +100 -0
  37. package/dist/display/parse.js.map +1 -0
  38. package/dist/display/subject.d.ts +75 -0
  39. package/dist/display/subject.d.ts.map +1 -0
  40. package/dist/display/subject.js +122 -0
  41. package/dist/display/subject.js.map +1 -0
  42. package/dist/display/text.d.ts +42 -0
  43. package/dist/display/text.d.ts.map +1 -0
  44. package/dist/display/text.js +62 -0
  45. package/dist/display/text.js.map +1 -0
  46. package/dist/display/types.d.ts +55 -0
  47. package/dist/display/types.d.ts.map +1 -0
  48. package/dist/display/types.js +41 -0
  49. package/dist/display/types.js.map +1 -0
  50. package/dist/display/validity.d.ts +51 -0
  51. package/dist/display/validity.d.ts.map +1 -0
  52. package/dist/display/validity.js +51 -0
  53. package/dist/display/validity.js.map +1 -0
  54. package/dist/display/verificationView.d.ts +101 -0
  55. package/dist/display/verificationView.d.ts.map +1 -0
  56. package/dist/display/verificationView.js +199 -0
  57. package/dist/display/verificationView.js.map +1 -0
  58. package/dist/identity/agents.d.ts +68 -0
  59. package/dist/identity/agents.d.ts.map +1 -0
  60. package/dist/identity/agents.js +95 -0
  61. package/dist/identity/agents.js.map +1 -0
  62. package/dist/identity/index.d.ts +20 -0
  63. package/dist/identity/index.d.ts.map +1 -0
  64. package/dist/identity/index.js +19 -0
  65. package/dist/identity/index.js.map +1 -0
  66. package/dist/identity/keyResolver.d.ts +30 -0
  67. package/dist/identity/keyResolver.d.ts.map +1 -0
  68. package/dist/identity/keyResolver.js +25 -0
  69. package/dist/identity/keyResolver.js.map +1 -0
  70. package/dist/index.d.ts +14 -2
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +14 -2
  73. package/dist/index.js.map +1 -1
  74. package/dist/request/classify.d.ts +112 -0
  75. package/dist/request/classify.d.ts.map +1 -0
  76. package/dist/request/classify.js +210 -0
  77. package/dist/request/classify.js.map +1 -0
  78. package/dist/request/composeVp.d.ts +52 -0
  79. package/dist/request/composeVp.d.ts.map +1 -0
  80. package/dist/request/composeVp.js +177 -0
  81. package/dist/request/composeVp.js.map +1 -0
  82. package/dist/request/exchangeClient.d.ts +165 -0
  83. package/dist/request/exchangeClient.d.ts.map +1 -0
  84. package/dist/request/exchangeClient.js +212 -0
  85. package/dist/request/exchangeClient.js.map +1 -0
  86. package/dist/request/index.d.ts +35 -0
  87. package/dist/request/index.d.ts.map +1 -0
  88. package/dist/request/index.js +35 -0
  89. package/dist/request/index.js.map +1 -0
  90. package/dist/request/interactionUrl.d.ts +53 -0
  91. package/dist/request/interactionUrl.d.ts.map +1 -0
  92. package/dist/request/interactionUrl.js +83 -0
  93. package/dist/request/interactionUrl.js.map +1 -0
  94. package/dist/request/matching.d.ts +69 -0
  95. package/dist/request/matching.d.ts.map +1 -0
  96. package/dist/request/matching.js +183 -0
  97. package/dist/request/matching.js.map +1 -0
  98. package/dist/request/parse.d.ts +68 -0
  99. package/dist/request/parse.d.ts.map +1 -0
  100. package/dist/request/parse.js +114 -0
  101. package/dist/request/parse.js.map +1 -0
  102. package/dist/request/presentationSuite.d.ts +71 -0
  103. package/dist/request/presentationSuite.d.ts.map +1 -0
  104. package/dist/request/presentationSuite.js +139 -0
  105. package/dist/request/presentationSuite.js.map +1 -0
  106. package/dist/request/processRequest.d.ts +48 -0
  107. package/dist/request/processRequest.d.ts.map +1 -0
  108. package/dist/request/processRequest.js +139 -0
  109. package/dist/request/processRequest.js.map +1 -0
  110. package/dist/request/types.d.ts +150 -0
  111. package/dist/request/types.d.ts.map +1 -0
  112. package/dist/request/types.js +2 -0
  113. package/dist/request/types.js.map +1 -0
  114. package/dist/sync/types.d.ts.map +1 -1
  115. package/package.json +31 -3
package/README.md CHANGED
@@ -20,9 +20,10 @@
20
20
  wallet apps (a React Native mobile wallet and a browser wallet) hold in common:
21
21
  the cross-replica byte-compatibility surface both must agree on to converge on
22
22
  identical bytes. It is isomorphic (browser, Node.js, React Native) and has no
23
- UI, storage, or crypto-key dependencies -- every side effect is injected.
23
+ UI or storage dependencies -- side effects are injected, and the dep-heavier
24
+ protocol subpaths are import-directly-only.
24
25
 
25
- Two subpaths:
26
+ Five subpaths:
26
27
 
27
28
  - **`@interop/wallet-core/sync`** -- the Wallet Attached Storage (WAS)
28
29
  replication engine core: the `SyncEngine` orchestration (single-flight,
@@ -39,6 +40,27 @@ Two subpaths:
39
40
  derivation, and the `was-link` QR hand-off contract. Contacts collection specs
40
41
  live in [`@interop/social-core`](https://npm.im/@interop/social-core).
41
42
 
43
+ - **`@interop/wallet-core/identity`** -- the WAS identity derivation both
44
+ wallet apps must perform byte-for-byte identically: `agentsFromSecret` /
45
+ `agentsFromSeed` (controller secret or 32-byte seed to the did:key
46
+ `CapabilityAgent`, `ZcapClient`, X25519 key agreement key, and single-key
47
+ resolver, under the fixed bootstrap handle / key name) and
48
+ `singleKeyResolver`.
49
+
50
+ - **`@interop/wallet-core/request`** -- wallet-request / exchange protocol
51
+ handling: request classification and parsing (CHAPI get/store events,
52
+ wallet-api messages and URLs), QueryByExample matching, cryptosuite
53
+ negotiation, `composeVp` (signer and holder injected), the pure
54
+ `processRequest` (consent runs in the caller; zcap / App Connect processing
55
+ injected), the VC-API exchange client, and VCALM `interaction:` URL handling.
56
+ The VPR type vocabulary lives in
57
+ [`@interop/data-integrity-core`](https://npm.im/@interop/data-integrity-core)
58
+ and is re-exported here.
59
+
60
+ - **`@interop/wallet-core/display`** -- pure verifiable-credential derivation /
61
+ display helpers and credential input parsing. Raw values out (ISO strings,
62
+ `Date`, booleans); date formatting, i18n, and UI concerns stay in the app.
63
+
42
64
  ## Install
43
65
 
44
66
  - Node.js 24+ is recommended.
@@ -68,7 +90,9 @@ import {
68
90
  } from '@interop/wallet-core/space'
69
91
  ```
70
92
 
71
- Both subpaths are re-exported from the package root as well.
93
+ The `sync` and `space` subpaths are re-exported from the package root as well.
94
+ `identity`, `request`, and `display` are import-directly-only, so consumers of
95
+ the root never pull the signing / KMS / document-loader dependency graph.
72
96
 
73
97
  ## Contribute
74
98
 
@@ -0,0 +1,49 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * Achievement alignment normalization. Two functions are kept on purpose
6
+ * because they answer different questions:
7
+ *
8
+ * - `normalizeAlignments` (from Freewallet) trims each alignment and drops the
9
+ * ones with no `targetName`, returning a plain `{ targetName, targetUrl,
10
+ * targetDescription }` view. It does NOT validate URLs.
11
+ * - `getValidAlignments` (from DCW) additionally runs a strict http(s) URL
12
+ * safety check and reports it via an `isValidUrl` flag, so the UI can decide
13
+ * whether to render a target as a clickable link. It omits `targetUrl` when
14
+ * absent (rather than emitting `''`).
15
+ *
16
+ * Both had passing tests in their respective apps and both are exported.
17
+ */
18
+ import type { IAlignment } from '@interop/data-integrity-core';
19
+ /** The trimmed, always-present-string alignment view `normalizeAlignments` emits. */
20
+ export interface IAlignmentView {
21
+ targetName: string;
22
+ targetUrl: string;
23
+ targetDescription: string;
24
+ }
25
+ /** A display-safe alignment with an optional validated URL (`getValidAlignments`). */
26
+ export interface ValidAlignment {
27
+ targetName: string;
28
+ targetUrl?: string;
29
+ targetDescription?: string;
30
+ isValidUrl?: boolean;
31
+ }
32
+ /**
33
+ * Normalizes a raw alignment field (a single alignment or an array) to trimmed
34
+ * views, dropping entries with no `targetName`. Does not validate URLs.
35
+ *
36
+ * @param rawAlignments {unknown}
37
+ * @returns {IAlignmentView[]}
38
+ */
39
+ export declare function normalizeAlignments(rawAlignments: unknown): IAlignmentView[];
40
+ /**
41
+ * Filters alignments to those with a `targetName` and, for each, reports
42
+ * whether its `targetUrl` (when present) is a safe http(s) URL via `isValidUrl`.
43
+ * `targetUrl` is omitted from the result when the input had none.
44
+ *
45
+ * @param alignments {IAlignment[] | undefined}
46
+ * @returns {ValidAlignment[]}
47
+ */
48
+ export declare function getValidAlignments(alignments?: IAlignment[]): ValidAlignment[];
49
+ //# sourceMappingURL=alignment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alignment.d.ts","sourceRoot":"","sources":["../../src/display/alignment.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAA;AAG9D,qFAAqF;AACrF,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,iBAAiB,EAAE,MAAM,CAAA;CAC1B;AAED,sFAAsF;AACtF,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,OAAO,GAAG,cAAc,EAAE,CAqB5E;AAoED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,CAAC,EAAE,UAAU,EAAE,GACxB,cAAc,EAAE,CAwBlB"}
@@ -0,0 +1,114 @@
1
+ import { asRecord } from './text.js';
2
+ /**
3
+ * Normalizes a raw alignment field (a single alignment or an array) to trimmed
4
+ * views, dropping entries with no `targetName`. Does not validate URLs.
5
+ *
6
+ * @param rawAlignments {unknown}
7
+ * @returns {IAlignmentView[]}
8
+ */
9
+ export function normalizeAlignments(rawAlignments) {
10
+ if (!rawAlignments) {
11
+ return [];
12
+ }
13
+ const alignmentArray = Array.isArray(rawAlignments)
14
+ ? rawAlignments
15
+ : [rawAlignments];
16
+ return alignmentArray
17
+ .map((alignmentField) => {
18
+ const normalizedField = asRecord(alignmentField) ?? {};
19
+ return {
20
+ targetName: String(normalizedField.targetName ?? '').trim(),
21
+ targetUrl: String(normalizedField.targetUrl ?? '').trim(),
22
+ targetDescription: String(normalizedField.targetDescription ?? '').trim()
23
+ };
24
+ })
25
+ .filter(alignmentField => Boolean(alignmentField.targetName));
26
+ }
27
+ function normalizeUrl(raw) {
28
+ const trimmed = String(raw).trim();
29
+ // If it already has a scheme (e.g., http:, https:)
30
+ if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed)) {
31
+ return trimmed;
32
+ }
33
+ // Otherwise, assume https
34
+ return `https://${trimmed}`;
35
+ }
36
+ function isStrictHttpUrl(raw) {
37
+ const candidate = String(raw).trim();
38
+ if (candidate.length === 0) {
39
+ return null;
40
+ }
41
+ if (/\s/.test(candidate)) {
42
+ return null;
43
+ }
44
+ const normalized = normalizeUrl(candidate);
45
+ // Reject strings that embed multiple schemes
46
+ const firstSchemeIdx = normalized.indexOf('://');
47
+ if (firstSchemeIdx === -1) {
48
+ return null;
49
+ }
50
+ if (normalized.indexOf('://', firstSchemeIdx + 3) !== -1) {
51
+ return null;
52
+ }
53
+ try {
54
+ const u = new URL(normalized);
55
+ // Only allow http(s)
56
+ if (u.protocol !== 'http:' && u.protocol !== 'https:') {
57
+ return null;
58
+ }
59
+ // Must have a hostname
60
+ if (!u.hostname) {
61
+ return null;
62
+ }
63
+ // Forbid credentials/userinfo
64
+ if (u.username || u.password) {
65
+ return null;
66
+ }
67
+ // Hostname must be reasonable, or be 'localhost' or an IPv4
68
+ const isHostname = /^[A-Za-z0-9.-]+$/.test(u.hostname);
69
+ const isLocalhost = u.hostname.toLowerCase() === 'localhost';
70
+ const isIPv4 = /^(25[0-5]|2[0-4]\d|1?\d?\d)(\.(25[0-5]|2[0-4]\d|1?\d?\d)){3}$/.test(u.hostname);
71
+ if (!(isHostname || isLocalhost || isIPv4)) {
72
+ return null;
73
+ }
74
+ // If it's a standard hostname (not localhost/IP), require at least one dot
75
+ if (isHostname && !isLocalhost && !isIPv4 && !u.hostname.includes('.')) {
76
+ return null;
77
+ }
78
+ return normalized;
79
+ }
80
+ catch {
81
+ return null;
82
+ }
83
+ }
84
+ /**
85
+ * Filters alignments to those with a `targetName` and, for each, reports
86
+ * whether its `targetUrl` (when present) is a safe http(s) URL via `isValidUrl`.
87
+ * `targetUrl` is omitted from the result when the input had none.
88
+ *
89
+ * @param alignments {IAlignment[] | undefined}
90
+ * @returns {ValidAlignment[]}
91
+ */
92
+ export function getValidAlignments(alignments) {
93
+ if (!alignments || !Array.isArray(alignments)) {
94
+ return [];
95
+ }
96
+ return alignments
97
+ .filter(alignment => {
98
+ // targetName is required for display
99
+ return !!alignment.targetName;
100
+ })
101
+ .map(alignment => {
102
+ const result = {
103
+ targetName: alignment.targetName,
104
+ targetDescription: alignment.targetDescription
105
+ };
106
+ if (alignment.targetUrl) {
107
+ const normalizedUrl = isStrictHttpUrl(alignment.targetUrl);
108
+ result.targetUrl = alignment.targetUrl;
109
+ result.isValidUrl = !!normalizedUrl;
110
+ }
111
+ return result;
112
+ });
113
+ }
114
+ //# sourceMappingURL=alignment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alignment.js","sourceRoot":"","sources":["../../src/display/alignment.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * Credential title derivation.
6
+ *
7
+ * Drift resolution (merged chain): DCW and Freewallet produced different titles
8
+ * for the same credential. The merged `credentialName` keeps DCW's
9
+ * type-specific human prefixes first (product copy its screens rely on:
10
+ * `Recommendation From ...`, `Performance Review: ...`, the SkillClaim skill
11
+ * name, `Employment: ... @ ...`, `Volunteer: ... @ ...`), then falls through to
12
+ * Freewallet's more complete generic chain: top-level `vc.name`, achievement
13
+ * name(s) JOINED with `' · '` (DCW returned only the first), skill name(s),
14
+ * resolved person full name, `hasCredential.name`, a `'Skill Claim'` label for
15
+ * a SkillClaimCredential, and finally `'Verifiable Credential'`.
16
+ *
17
+ * Deliberate behavior changes vs DCW: the final fallback is now
18
+ * `'Verifiable Credential'` (was `'Unknown Credential'`); a multi-achievement
19
+ * VC joins all names with `' · '`; and an `achievement` name now wins over a
20
+ * sibling `hasCredential` name (Freewallet's chain order), where DCW preferred
21
+ * `hasCredential`.
22
+ */
23
+ import type { IVerifiableCredential } from '@interop/data-integrity-core';
24
+ /**
25
+ * Freewallet's generic title chain over a resolved subject: top-level name,
26
+ * achievement name(s), skill name(s), person full name, `hasCredential.name`,
27
+ * a `'Skill Claim'` label, else `'Verifiable Credential'`.
28
+ *
29
+ * @param verifiableCredential {IVerifiableCredential}
30
+ * @param subject {Record<string, unknown>}
31
+ * @returns {string}
32
+ */
33
+ export declare function credentialNameFrom(verifiableCredential: IVerifiableCredential, subject: Record<string, unknown>): string;
34
+ /**
35
+ * The display title for a credential: DCW's type-specific prefixes first, then
36
+ * the generic {@link credentialNameFrom} chain.
37
+ *
38
+ * @param credential {IVerifiableCredential}
39
+ * @returns {string}
40
+ */
41
+ export declare function credentialName(credential: IVerifiableCredential): string;
42
+ //# sourceMappingURL=credentialName.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentialName.d.ts","sourceRoot":"","sources":["../../src/display/credentialName.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAA;AAOzE;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,oBAAoB,EAAE,qBAAqB,EAC3C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,MAAM,CA8CR;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,qBAAqB,GAAG,MAAM,CAkDxE"}
@@ -0,0 +1,106 @@
1
+ import { typeArray } from '@interop/data-integrity-core/guards';
2
+ import { getSubject, resolvePersonFullName } from './subject.js';
3
+ import { achievementsList, skillsList } from './obv3.js';
4
+ import { isEmploymentCredential, isVolunteerCredential } from './types.js';
5
+ import { asNonEmptyString, asRecord, getTrimmedString } from './text.js';
6
+ /**
7
+ * Freewallet's generic title chain over a resolved subject: top-level name,
8
+ * achievement name(s), skill name(s), person full name, `hasCredential.name`,
9
+ * a `'Skill Claim'` label, else `'Verifiable Credential'`.
10
+ *
11
+ * @param verifiableCredential {IVerifiableCredential}
12
+ * @param subject {Record<string, unknown>}
13
+ * @returns {string}
14
+ */
15
+ export function credentialNameFrom(verifiableCredential, subject) {
16
+ const topLevelName = getTrimmedString(verifiableCredential.name);
17
+ if (topLevelName) {
18
+ return topLevelName;
19
+ }
20
+ const achievementNames = achievementsList(subject)
21
+ .map(achievement => getTrimmedString(achievement.name))
22
+ .filter((name) => Boolean(name));
23
+ if (achievementNames.length > 1) {
24
+ return achievementNames.join(' · ');
25
+ }
26
+ const [firstAchievementName] = achievementNames;
27
+ if (firstAchievementName) {
28
+ return firstAchievementName;
29
+ }
30
+ const skillNames = skillsList(subject)
31
+ .map(skill => getTrimmedString(skill.name))
32
+ .filter((name) => Boolean(name));
33
+ if (skillNames.length > 1) {
34
+ return skillNames.join(' · ');
35
+ }
36
+ const [firstSkillName] = skillNames;
37
+ if (firstSkillName) {
38
+ return firstSkillName;
39
+ }
40
+ const resumeFullName = resolvePersonFullName(subject);
41
+ if (resumeFullName) {
42
+ return resumeFullName;
43
+ }
44
+ const hasCredential = asRecord(subject.hasCredential);
45
+ const hasCredentialName = getTrimmedString(hasCredential?.name);
46
+ if (hasCredentialName) {
47
+ return hasCredentialName;
48
+ }
49
+ if (typeArray(verifiableCredential.type).includes('SkillClaimCredential')) {
50
+ return 'Skill Claim';
51
+ }
52
+ return 'Verifiable Credential';
53
+ }
54
+ /**
55
+ * The display title for a credential: DCW's type-specific prefixes first, then
56
+ * the generic {@link credentialNameFrom} chain.
57
+ *
58
+ * @param credential {IVerifiableCredential}
59
+ * @returns {string}
60
+ */
61
+ export function credentialName(credential) {
62
+ const types = typeArray(credential.type);
63
+ const subject = asRecord(getSubject(credential)) ?? {};
64
+ if (types.includes('https://schema.org/RecommendationCredential')) {
65
+ const name = asNonEmptyString(subject.name);
66
+ return name ? `Recommendation From ${name}` : 'Recommendation Credential';
67
+ }
68
+ if (types.includes('PerformanceReviewCredential')) {
69
+ const employeeName = asNonEmptyString(subject.employeeName);
70
+ return employeeName
71
+ ? `Performance Review: ${employeeName}`
72
+ : 'Performance Review Credential';
73
+ }
74
+ if (types.includes('SkillClaimCredential')) {
75
+ const [firstSkill] = skillsList(subject);
76
+ const skillName = asNonEmptyString(firstSkill?.name);
77
+ if (skillName) {
78
+ return skillName;
79
+ }
80
+ // fall through to the generic chain (lands on 'Skill Claim')
81
+ }
82
+ if (isEmploymentCredential(credential)) {
83
+ const fullName = asNonEmptyString(subject.fullName);
84
+ const company = asNonEmptyString(subject.company);
85
+ if (fullName && company) {
86
+ return `Employment: ${fullName} @ ${company}`;
87
+ }
88
+ if (fullName) {
89
+ return `Employment: ${fullName}`;
90
+ }
91
+ return 'Employment Credential';
92
+ }
93
+ if (isVolunteerCredential(credential)) {
94
+ const fullName = asNonEmptyString(subject.fullName);
95
+ const volunteerOrg = asNonEmptyString(subject.volunteerOrg);
96
+ if (fullName && volunteerOrg) {
97
+ return `Volunteer: ${fullName} @ ${volunteerOrg}`;
98
+ }
99
+ if (fullName) {
100
+ return `Volunteer: ${fullName}`;
101
+ }
102
+ return 'Volunteer Credential';
103
+ }
104
+ return credentialNameFrom(credential, subject);
105
+ }
106
+ //# sourceMappingURL=credentialName.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentialName.js","sourceRoot":"","sources":["../../src/display/credentialName.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * Freewallet's aggregate display projection: one pass over a VC producing the
6
+ * flat field set the credential card and detail view render. Dates stay RAW
7
+ * (the `expirationDate` field is an ISO string, not a formatted one -- the
8
+ * raw-values seam); the title comes from the merged {@link credentialName}.
9
+ *
10
+ * `credentialNameFrom` lives in `credentialName.ts` (co-located with the merged
11
+ * title fn that calls it); `buildCredentialDescription` / `buildCriteria` are
12
+ * Freewallet's, kept here as they are used only by `getDisplayFields` (and
13
+ * covered by the ported helper tests).
14
+ */
15
+ import type { IAlignment, IVerifiableCredential } from '@interop/data-integrity-core';
16
+ /** The flat display projection of a VC for the card / detail view. */
17
+ export interface CredentialDisplayFields {
18
+ credentialName: string;
19
+ issuedTo: string;
20
+ /** Raw ISO expiration string (`''` when none). */
21
+ expirationDate: string;
22
+ credentialDescription: string;
23
+ criteria: string;
24
+ achievementImage: string;
25
+ achievementType: string;
26
+ alignments: IAlignment[];
27
+ }
28
+ /**
29
+ * Joins achievement descriptions, skill narratives / durations, and various
30
+ * subject description fields into a single paragraph-separated string.
31
+ *
32
+ * @param subject {Record<string, unknown>}
33
+ * @param achievements {Record<string, unknown>[]}
34
+ * @returns {string}
35
+ */
36
+ export declare function buildCredentialDescription(subject: Record<string, unknown>, achievements: Record<string, unknown>[]): string;
37
+ /**
38
+ * Builds the criteria text from the achievements' criteria narratives (labeled
39
+ * with the achievement name when there is more than one), falling back to the
40
+ * subject's `hasCredential.competencyRequired`.
41
+ *
42
+ * @param subject {Record<string, unknown>}
43
+ * @param achievements {Record<string, unknown>[]}
44
+ * @returns {string}
45
+ */
46
+ export declare function buildCriteria(subject: Record<string, unknown>, achievements: Record<string, unknown>[]): string;
47
+ /**
48
+ * Projects a VC to its flat display field set. The title uses the merged
49
+ * {@link credentialName}; `expirationDate` is a raw ISO string.
50
+ *
51
+ * @param verifiableCredential {IVerifiableCredential}
52
+ * @returns {CredentialDisplayFields}
53
+ */
54
+ export declare function getDisplayFields(verifiableCredential: IVerifiableCredential): CredentialDisplayFields;
55
+ //# sourceMappingURL=displayFields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"displayFields.d.ts","sourceRoot":"","sources":["../../src/display/displayFields.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAEV,UAAU,EACV,qBAAqB,EACtB,MAAM,8BAA8B,CAAA;AAerC,sEAAsE;AACtE,MAAM,WAAW,uBAAuB;IACtC,cAAc,EAAE,MAAM,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,kDAAkD;IAClD,cAAc,EAAE,MAAM,CAAA;IACtB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,QAAQ,EAAE,MAAM,CAAA;IAChB,gBAAgB,EAAE,MAAM,CAAA;IACxB,eAAe,EAAE,MAAM,CAAA;IACvB,UAAU,EAAE,UAAU,EAAE,CAAA;CACzB;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GACtC,MAAM,CAqDR;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GACtC,MAAM,CAuBR;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,oBAAoB,EAAE,qBAAqB,GAC1C,uBAAuB,CA2CzB"}
@@ -0,0 +1,136 @@
1
+ import { credentialName } from './credentialName.js';
2
+ import { getSubject, extractIssuedTo } from './subject.js';
3
+ import { getExpirationDate } from './validity.js';
4
+ import { achievementsList, skillsList, getSkillImage, getEvidenceImage, getAchievementImage, getAchievementType } from './obv3.js';
5
+ import { normalizeAlignments } from './alignment.js';
6
+ import { asRecord, getTrimmedString } from './text.js';
7
+ /**
8
+ * Joins achievement descriptions, skill narratives / durations, and various
9
+ * subject description fields into a single paragraph-separated string.
10
+ *
11
+ * @param subject {Record<string, unknown>}
12
+ * @param achievements {Record<string, unknown>[]}
13
+ * @returns {string}
14
+ */
15
+ export function buildCredentialDescription(subject, achievements) {
16
+ const descriptionParts = [];
17
+ for (const achievement of achievements) {
18
+ const achievementDescription = getTrimmedString(achievement.description);
19
+ if (achievementDescription) {
20
+ descriptionParts.push(achievementDescription);
21
+ }
22
+ }
23
+ for (const skill of skillsList(subject)) {
24
+ const narrative = getTrimmedString(skill.narrative);
25
+ if (narrative) {
26
+ descriptionParts.push(narrative);
27
+ }
28
+ const skillDuration = getTrimmedString(skill.durationPerformed);
29
+ if (skillDuration) {
30
+ descriptionParts.push(`Duration: ${skillDuration}`);
31
+ }
32
+ }
33
+ const subjectNarrative = getTrimmedString(subject.narrative);
34
+ if (subjectNarrative) {
35
+ descriptionParts.push(subjectNarrative);
36
+ }
37
+ const evidenceDescription = getTrimmedString(subject.evidenceDescription);
38
+ if (evidenceDescription) {
39
+ descriptionParts.push(evidenceDescription);
40
+ }
41
+ const duration = getTrimmedString(subject.duration);
42
+ if (duration) {
43
+ descriptionParts.push(`Duration: ${duration}`);
44
+ }
45
+ const evidenceLink = getTrimmedString(subject.evidenceLink);
46
+ if (evidenceLink) {
47
+ descriptionParts.push(`Evidence: ${evidenceLink}`);
48
+ }
49
+ const subjectDescription = getTrimmedString(subject.description);
50
+ if (subjectDescription) {
51
+ descriptionParts.push(subjectDescription);
52
+ }
53
+ const hasCredential = asRecord(subject.hasCredential);
54
+ const hasCredentialDescription = getTrimmedString(hasCredential?.description);
55
+ if (hasCredentialDescription) {
56
+ descriptionParts.push(hasCredentialDescription);
57
+ }
58
+ return descriptionParts.join('\n\n');
59
+ }
60
+ /**
61
+ * Builds the criteria text from the achievements' criteria narratives (labeled
62
+ * with the achievement name when there is more than one), falling back to the
63
+ * subject's `hasCredential.competencyRequired`.
64
+ *
65
+ * @param subject {Record<string, unknown>}
66
+ * @param achievements {Record<string, unknown>[]}
67
+ * @returns {string}
68
+ */
69
+ export function buildCriteria(subject, achievements) {
70
+ const criteriaBlocks = achievements
71
+ .map(achievement => {
72
+ const criteria = asRecord(achievement.criteria);
73
+ const narrative = getTrimmedString(criteria?.narrative);
74
+ if (!narrative) {
75
+ return '';
76
+ }
77
+ const achievementName = getTrimmedString(achievement.name);
78
+ if (achievements.length > 1 && achievementName) {
79
+ return `**${achievementName}**\n\n${narrative}`;
80
+ }
81
+ return narrative;
82
+ })
83
+ .filter((criteriaBlock) => Boolean(criteriaBlock));
84
+ const criteriaText = criteriaBlocks.join('\n\n');
85
+ if (criteriaText) {
86
+ return criteriaText;
87
+ }
88
+ const hasCredential = asRecord(subject.hasCredential);
89
+ return getTrimmedString(hasCredential?.competencyRequired);
90
+ }
91
+ /**
92
+ * Projects a VC to its flat display field set. The title uses the merged
93
+ * {@link credentialName}; `expirationDate` is a raw ISO string.
94
+ *
95
+ * @param verifiableCredential {IVerifiableCredential}
96
+ * @returns {CredentialDisplayFields}
97
+ */
98
+ export function getDisplayFields(verifiableCredential) {
99
+ const commonFields = {
100
+ credentialName: credentialName(verifiableCredential),
101
+ issuedTo: extractIssuedTo(verifiableCredential),
102
+ expirationDate: getExpirationDate(verifiableCredential) ?? ''
103
+ };
104
+ const subject = asRecord(getSubject(verifiableCredential));
105
+ if (!subject) {
106
+ return {
107
+ ...commonFields,
108
+ credentialDescription: '',
109
+ criteria: '',
110
+ achievementImage: '',
111
+ achievementType: '',
112
+ alignments: []
113
+ };
114
+ }
115
+ const achievements = achievementsList(subject);
116
+ const primaryAchievement = achievements[0];
117
+ const skills = skillsList(subject);
118
+ const alignments = achievements.flatMap(achievement => normalizeAlignments(achievement.alignment));
119
+ const evidenceRaw = verifiableCredential.evidence;
120
+ const evidence = Array.isArray(evidenceRaw)
121
+ ? evidenceRaw
122
+ : evidenceRaw
123
+ ? [evidenceRaw]
124
+ : [];
125
+ return {
126
+ ...commonFields,
127
+ credentialDescription: buildCredentialDescription(subject, achievements),
128
+ criteria: buildCriteria(subject, achievements),
129
+ achievementImage: getAchievementImage(primaryAchievement) ||
130
+ getSkillImage(skills) ||
131
+ getEvidenceImage(evidence),
132
+ achievementType: getAchievementType(primaryAchievement),
133
+ alignments
134
+ };
135
+ }
136
+ //# sourceMappingURL=displayFields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"displayFields.js","sourceRoot":"","sources":["../../src/display/displayFields.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * Portfolio-evidence extraction (from DCW; no Freewallet equivalent beyond the
6
+ * image helper in `obv3.ts`). Turns a credential's `evidence` array (or a
7
+ * subject `portfolio`) into a flat list of `{ name, url }` links, coercing the
8
+ * many shapes evidence appears in (string URL, `{ url }`, `{ id }`) and dropping
9
+ * anything without a usable URL.
10
+ */
11
+ /** A resolved evidence link. */
12
+ export type PortfolioEvidenceItem = {
13
+ name: string;
14
+ url: string;
15
+ };
16
+ /** Evidence item from a W3C VC evidence array: `{ id, type, name }`. */
17
+ export type VCEvidenceItem = {
18
+ id: string;
19
+ type?: string;
20
+ name?: string;
21
+ };
22
+ /** A VC carrying a top-level `evidence` array (W3C / IMS OB spec). */
23
+ export type VCWithEvidence = {
24
+ evidence?: unknown;
25
+ credentialSubject?: unknown;
26
+ };
27
+ /**
28
+ * Extracts evidence links from a raw array/object/string, dropping items with
29
+ * no usable URL.
30
+ *
31
+ * @param raw {unknown}
32
+ * @returns {PortfolioEvidenceItem[]}
33
+ */
34
+ export declare function portfolioEvidenceFrom(raw: unknown): PortfolioEvidenceItem[];
35
+ /**
36
+ * Extracts evidence from a credential: prefers the credential's top-level
37
+ * `evidence` array, else falls back to the subject's `portfolio`.
38
+ *
39
+ * @param credential {VCWithEvidence}
40
+ * @param subject {unknown} optional pre-resolved subject
41
+ * @returns {PortfolioEvidenceItem[]}
42
+ */
43
+ export declare function evidenceFromCredential(credential: VCWithEvidence, subject?: unknown): PortfolioEvidenceItem[];
44
+ //# sourceMappingURL=evidence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidence.d.ts","sourceRoot":"","sources":["../../src/display/evidence.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;GAMG;AAEH,gCAAgC;AAChC,MAAM,MAAM,qBAAqB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AAEjE,wEAAwE;AACxE,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,sEAAsE;AACtE,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B,CAAA;AA8BD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,qBAAqB,EAAE,CAG3E;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,OAAO,GAChB,qBAAqB,EAAE,CAazB"}