@learncard/helpers 1.3.1 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/helpers.d.ts CHANGED
@@ -435,6 +435,81 @@ declare const VCValidator: z.ZodObject<{
435
435
  ]>;
436
436
  }, z.core.$catchall<z.ZodAny>>;
437
437
  export type VC = z.infer<typeof VCValidator>;
438
+ declare const CredentialFormatValidator: z.ZodEnum<{
439
+ "w3c-vc-2.0": "w3c-vc-2.0";
440
+ "w3c-vc-1.1": "w3c-vc-1.1";
441
+ "jwt-vc-json": "jwt-vc-json";
442
+ "dc+sd-jwt": "dc+sd-jwt";
443
+ "vc+sd-jwt": "vc+sd-jwt";
444
+ mso_mdoc: "mso_mdoc";
445
+ }>;
446
+ export type CredentialFormat = z.infer<typeof CredentialFormatValidator>;
447
+ /**
448
+ * Format-discriminated read view over a stored credential. Returned
449
+ * by `toStoredCredential(record)` in `@learncard/helpers`.
450
+ *
451
+ * The `data` field carries the correct wire-form representation for
452
+ * the credential's format:
453
+ * - W3C VCs: the JSON-LD VC object (also what `record.vc` holds)
454
+ * - JWT-VC: the compact JWS string (extracted from `record.vc.proof.jwt`
455
+ * if the record uses the legacy LDP-around-JWT envelope, otherwise
456
+ * the raw string from `record.rawWireForm`)
457
+ * - SD-JWT-VC: the compact `<JWT>~<disclosures>~` string
458
+ * - mDoc: base64url-encoded CBOR bytes (stored as a string for
459
+ * LearnCloud's JSON-only encrypted store; consumers base64-decode
460
+ * when they need raw bytes)
461
+ *
462
+ * Format-aware consumers pattern-match on `format` and use `data`.
463
+ * Legacy consumers continue to read `record.vc` directly — the
464
+ * projector is opt-in, never required.
465
+ */
466
+ export type StoredCredential = {
467
+ format: "w3c-vc-2.0";
468
+ data: VC;
469
+ } | {
470
+ format: "w3c-vc-1.1";
471
+ data: VC;
472
+ } | {
473
+ format: "jwt-vc-json";
474
+ data: string;
475
+ } | {
476
+ format: "dc+sd-jwt";
477
+ data: string;
478
+ } | {
479
+ format: "vc+sd-jwt";
480
+ data: string;
481
+ } | {
482
+ format: "mso_mdoc";
483
+ data: string;
484
+ };
485
+ export type CredentialRecord<Metadata extends Record<string, any> = Record<never, never>> = {
486
+ id: string;
487
+ uri: string;
488
+ /**
489
+ * Wire-format discriminator for the credential. Optional — when
490
+ * absent, legacy readers infer it from `vc` shape (see
491
+ * `toStoredCredential` in `@learncard/helpers`). Populated on new
492
+ * writes once the consumer is format-aware.
493
+ *
494
+ * Added in ADR-0001 Phase 1 as additive metadata; existing
495
+ * records without this field continue to work unchanged.
496
+ */
497
+ format?: CredentialFormat;
498
+ /**
499
+ * Optional semantic type hint for fast filtering without parsing
500
+ * the full credential — `vct` for SD-JWT-VC, last-non-VC type for
501
+ * W3C VCs, doctype for mDoc. Added in ADR-0001 Phase 1.
502
+ */
503
+ semanticType?: string;
504
+ /**
505
+ * On-the-wire representation for formats whose `vc` field cannot
506
+ * hold the canonical form (SD-JWT compact, JWT-VC compact, base64
507
+ * mDoc CBOR). Undefined for W3C VCs (`vc` IS the wire form). Added
508
+ * in ADR-0001 Phase 1.
509
+ */
510
+ rawWireForm?: string;
511
+ [key: string]: any;
512
+ } & Metadata;
438
513
  declare const JWEValidator: z.ZodObject<{
439
514
  protected: z.ZodString;
440
515
  iv: z.ZodString;
@@ -769,6 +844,32 @@ export declare const AGE_RATING_TO_MIN_AGE: Record<string, number>;
769
844
  * @returns Age in years, or null if the date is invalid
770
845
  */
771
846
  export declare const calculateAgeFromDob: (dob?: string | null) => number | null;
847
+ /**
848
+ * Project a `CredentialRecord` into a format-discriminated read view.
849
+ *
850
+ * Two paths into the same output:
851
+ *
852
+ * 1. **Explicit format**: if the record carries an explicit `format`
853
+ * discriminator (and `rawWireForm` where required), the projector
854
+ * trusts it. This is the path new format-aware writers take.
855
+ *
856
+ * 2. **Inferred from shape** (legacy fallback): for records that
857
+ * pre-date ADR-0001 Phase 1, the projector inspects `record.vc` and
858
+ * infers the format. W3C VCs → `w3c-vc-2.0` / `w3c-vc-1.1` based
859
+ * on `@context`; transitional SD-JWT wrappers (which never shipped
860
+ * but exist on branch `lc-1796-3`) → `dc+sd-jwt` with the compact
861
+ * extracted from `proof.jwt`; legacy JWT-VC envelopes → `jwt-vc-json`.
862
+ *
863
+ * Returns a `StoredCredential` for every conceivable record. Never
864
+ * throws — credentials with unrecognizable shape fall back to
865
+ * `w3c-vc-1.1` with `data = record.vc` so legacy consumers keep
866
+ * something they can read.
867
+ *
868
+ * This projector is the SAFETY NET that makes the ADR-0001 migration
869
+ * non-breaking: writers can adopt format-tagging at their own pace;
870
+ * readers using this projector see the right discriminator either way.
871
+ */
872
+ export declare const toStoredCredential: (record: CredentialRecord) => StoredCredential;
772
873
  /**
773
874
  * Determines whether or not a string is a valid hexadecimal string
774
875
  *
@@ -29,9 +29,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
29
  mod
30
30
  ));
31
31
 
32
- // ../learn-card-types/dist/types.cjs.development.js
32
+ // ../learn-card-types/dist/types.cjs.development.cjs
33
33
  var require_types_cjs_development = __commonJS({
34
- "../learn-card-types/dist/types.cjs.development.js"(exports, module) {
34
+ "../learn-card-types/dist/types.cjs.development.cjs"(exports, module) {
35
35
  "use strict";
36
36
  var __defProp2 = Object.defineProperty;
37
37
  var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
@@ -141,6 +141,7 @@ var require_types_cjs_development = __commonJS({
141
141
  CredentialActivityStatsValidator: /* @__PURE__ */ __name(() => CredentialActivityStatsValidator, "CredentialActivityStatsValidator"),
142
142
  CredentialActivityValidator: /* @__PURE__ */ __name(() => CredentialActivityValidator, "CredentialActivityValidator"),
143
143
  CredentialActivityWithDetailsValidator: /* @__PURE__ */ __name(() => CredentialActivityWithDetailsValidator, "CredentialActivityWithDetailsValidator"),
144
+ CredentialFormatValidator: /* @__PURE__ */ __name(() => CredentialFormatValidator, "CredentialFormatValidator"),
144
145
  CredentialInfoValidator: /* @__PURE__ */ __name(() => CredentialInfoValidator, "CredentialInfoValidator"),
145
146
  CredentialNameRefValidator: /* @__PURE__ */ __name(() => CredentialNameRefValidator, "CredentialNameRefValidator"),
146
147
  CredentialRecordValidator: /* @__PURE__ */ __name(() => CredentialRecordValidator, "CredentialRecordValidator"),
@@ -169,6 +170,8 @@ var require_types_cjs_development = __commonJS({
169
170
  GetSkillPathResultValidator: /* @__PURE__ */ __name(() => GetSkillPathResultValidator, "GetSkillPathResultValidator"),
170
171
  GetTemplateRecipientsEventValidator: /* @__PURE__ */ __name(() => GetTemplateRecipientsEventValidator, "GetTemplateRecipientsEventValidator"),
171
172
  GuardianStatusValidator: /* @__PURE__ */ __name(() => GuardianStatusValidator, "GuardianStatusValidator"),
173
+ HolderExportConsentRecordValidator: /* @__PURE__ */ __name(() => HolderExportConsentRecordValidator, "HolderExportConsentRecordValidator"),
174
+ HolderExportMetadataValidator: /* @__PURE__ */ __name(() => HolderExportMetadataValidator, "HolderExportMetadataValidator"),
172
175
  IdentifierEntryValidator: /* @__PURE__ */ __name(() => IdentifierEntryValidator, "IdentifierEntryValidator"),
173
176
  IdentifierTypeValidator: /* @__PURE__ */ __name(() => IdentifierTypeValidator, "IdentifierTypeValidator"),
174
177
  IdentityObjectValidator: /* @__PURE__ */ __name(() => IdentityObjectValidator, "IdentityObjectValidator"),
@@ -14116,6 +14119,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
14116
14119
  var ClrCredentialValidator = UnsignedClrCredentialValidator.extend({
14117
14120
  proof: ProofValidator.or(ProofValidator.array())
14118
14121
  });
14122
+ var CredentialFormatValidator = external_exports.enum([
14123
+ "w3c-vc-2.0",
14124
+ "w3c-vc-1.1",
14125
+ "jwt-vc-json",
14126
+ "dc+sd-jwt",
14127
+ "vc+sd-jwt",
14128
+ "mso_mdoc"
14129
+ ]);
14119
14130
  var StatusCheckEntryValidator = external_exports.object({
14120
14131
  /**
14121
14132
  * The `credentialStatus.type` as it appeared on the credential.
@@ -14171,7 +14182,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
14171
14182
  issuee: ProfileValidator.optional(),
14172
14183
  credentialSubject: CredentialSubjectValidator.optional()
14173
14184
  });
14174
- var CredentialRecordValidator = external_exports.object({ id: external_exports.string(), uri: external_exports.string() }).catchall(external_exports.any());
14185
+ var CredentialRecordValidator = external_exports.object({
14186
+ id: external_exports.string(),
14187
+ uri: external_exports.string(),
14188
+ format: CredentialFormatValidator.optional(),
14189
+ semanticType: external_exports.string().optional(),
14190
+ rawWireForm: external_exports.string().optional()
14191
+ }).catchall(external_exports.any());
14175
14192
  var PaginationOptionsValidator = external_exports.object({
14176
14193
  limit: external_exports.number(),
14177
14194
  cursor: external_exports.string().optional(),
@@ -14739,6 +14756,22 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
14739
14756
  date: external_exports.string(),
14740
14757
  uris: external_exports.string().array().optional()
14741
14758
  });
14759
+ var HolderExportConsentRecordValidator = external_exports.object({
14760
+ termsUri: external_exports.string(),
14761
+ status: ConsentFlowTermsStatusValidator,
14762
+ contract: ConsentFlowContractDetailsValidator,
14763
+ terms: ConsentFlowTermsValidator,
14764
+ transactions: ConsentFlowTransactionValidator.array()
14765
+ });
14766
+ var HolderExportMetadataValidator = external_exports.object({
14767
+ consentRecords: HolderExportConsentRecordValidator.array(),
14768
+ truncated: external_exports.boolean().optional(),
14769
+ warnings: external_exports.string().array().optional(),
14770
+ limits: external_exports.object({
14771
+ maxConsentRecords: external_exports.number(),
14772
+ maxTransactionsPerConsentRecord: external_exports.number()
14773
+ }).optional()
14774
+ });
14742
14775
  var PaginatedConsentFlowTransactionsValidator = PaginationResponseValidator.extend({
14743
14776
  records: ConsentFlowTransactionValidator.array()
14744
14777
  });
@@ -14825,10 +14858,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
14825
14858
  var LCNNotificationValidator = external_exports.object({
14826
14859
  type: LCNNotificationTypeEnumValidator,
14827
14860
  to: LCNProfileValidator.partial().and(external_exports.object({ did: external_exports.string() })),
14828
- from: external_exports.union([
14829
- external_exports.string(),
14830
- LCNProfileValidator.partial().and(external_exports.object({ did: external_exports.string() }))
14831
- ]),
14861
+ from: external_exports.union([external_exports.string(), LCNProfileValidator.partial().and(external_exports.object({ did: external_exports.string() }))]),
14832
14862
  message: LCNNotificationMessageValidator.optional(),
14833
14863
  data: LCNNotificationDataValidator.optional(),
14834
14864
  sent: external_exports.iso.datetime().optional(),
@@ -15588,9 +15618,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15588
15618
  }
15589
15619
  });
15590
15620
 
15591
- // ../learn-card-types/dist/index.js
15621
+ // ../learn-card-types/dist/index.cjs
15592
15622
  var require_dist = __commonJS({
15593
- "../learn-card-types/dist/index.js"(exports, module) {
15623
+ "../learn-card-types/dist/index.cjs"(exports, module) {
15594
15624
  "use strict";
15595
15625
  if (false) {
15596
15626
  module.exports = null;
@@ -16538,6 +16568,78 @@ var calculateAgeFromDob = /* @__PURE__ */ __name((dob) => {
16538
16568
  return age;
16539
16569
  }, "calculateAgeFromDob");
16540
16570
 
16571
+ // src/credential-format.ts
16572
+ var toStoredCredential = /* @__PURE__ */ __name((record) => {
16573
+ if (record.format) {
16574
+ if (record.format === "dc+sd-jwt" || record.format === "vc+sd-jwt" || record.format === "jwt-vc-json" || record.format === "mso_mdoc") {
16575
+ const wireForm = record.rawWireForm ?? extractWireFormFromVc(record.vc);
16576
+ if (typeof wireForm === "string" && wireForm.length > 0) {
16577
+ return { format: record.format, data: wireForm };
16578
+ }
16579
+ }
16580
+ if (record.format === "w3c-vc-2.0" || record.format === "w3c-vc-1.1") {
16581
+ return { format: record.format, data: record.vc };
16582
+ }
16583
+ }
16584
+ const vc = record.vc;
16585
+ if (typeof vc === "string") {
16586
+ if (looksLikeSdJwtCompact(vc)) {
16587
+ return { format: "dc+sd-jwt", data: vc };
16588
+ }
16589
+ if (looksLikeJwsCompact(vc)) {
16590
+ return { format: "jwt-vc-json", data: vc };
16591
+ }
16592
+ }
16593
+ if (vc && typeof vc === "object") {
16594
+ const proof = getWireFormProof(vc, true);
16595
+ const wireFromProof = getWireFormFromProof(proof);
16596
+ if (wireFromProof) {
16597
+ const proofType = proof?.type;
16598
+ if (proofType === "SdJwtCompactProof") {
16599
+ return { format: "dc+sd-jwt", data: wireFromProof };
16600
+ }
16601
+ if (proofType === "JwtProof2020") {
16602
+ return { format: "jwt-vc-json", data: wireFromProof };
16603
+ }
16604
+ }
16605
+ const inferred = inferW3cVersionFromContext(vc);
16606
+ return { format: inferred, data: vc };
16607
+ }
16608
+ return { format: "w3c-vc-1.1", data: vc };
16609
+ }, "toStoredCredential");
16610
+ var SD_JWT_COMPACT_RE = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+~/;
16611
+ var JWS_COMPACT_RE = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
16612
+ var looksLikeSdJwtCompact = /* @__PURE__ */ __name((value) => SD_JWT_COMPACT_RE.test(value), "looksLikeSdJwtCompact");
16613
+ var looksLikeJwsCompact = /* @__PURE__ */ __name((value) => JWS_COMPACT_RE.test(value), "looksLikeJwsCompact");
16614
+ var getWireFormProof = /* @__PURE__ */ __name((vc, requireSupportedType = false) => {
16615
+ if (!vc || typeof vc !== "object") return void 0;
16616
+ const proof = vc.proof;
16617
+ if (Array.isArray(proof)) {
16618
+ for (const entry of proof) {
16619
+ const proofObject2 = asWireFormProof(entry);
16620
+ if (proofObject2 && isUsableWireFormProof(proofObject2, requireSupportedType)) {
16621
+ return proofObject2;
16622
+ }
16623
+ }
16624
+ return void 0;
16625
+ }
16626
+ const proofObject = asWireFormProof(proof);
16627
+ return proofObject && isUsableWireFormProof(proofObject, requireSupportedType) ? proofObject : void 0;
16628
+ }, "getWireFormProof");
16629
+ var asWireFormProof = /* @__PURE__ */ __name((proof) => proof && typeof proof === "object" ? proof : void 0, "asWireFormProof");
16630
+ var isUsableWireFormProof = /* @__PURE__ */ __name((proof, requireSupportedType) => typeof proof.jwt === "string" && proof.jwt.length > 0 && (!requireSupportedType || proof.type === "SdJwtCompactProof" || proof.type === "JwtProof2020"), "isUsableWireFormProof");
16631
+ var getWireFormFromProof = /* @__PURE__ */ __name((proof) => typeof proof?.jwt === "string" && proof.jwt.length > 0 ? proof.jwt : void 0, "getWireFormFromProof");
16632
+ var extractWireFormFromVc = /* @__PURE__ */ __name((vc) => getWireFormFromProof(getWireFormProof(vc)), "extractWireFormFromVc");
16633
+ var inferW3cVersionFromContext = /* @__PURE__ */ __name((vc) => {
16634
+ if (!vc || typeof vc !== "object") return "w3c-vc-1.1";
16635
+ const contextRaw = vc["@context"];
16636
+ const contexts = Array.isArray(contextRaw) ? contextRaw : contextRaw !== void 0 ? [contextRaw] : [];
16637
+ const isV2 = contexts.some(
16638
+ (c) => typeof c === "string" && c.includes("w3.org/ns/credentials/v2")
16639
+ );
16640
+ return isV2 ? "w3c-vc-2.0" : "w3c-vc-1.1";
16641
+ }, "inferW3cVersionFromContext");
16642
+
16541
16643
  // src/index.ts
16542
16644
  var isHex = /* @__PURE__ */ __name((str) => /^[0-9a-f]+$/i.test(str), "isHex");
16543
16645
  var isEncrypted = /* @__PURE__ */ __name((item) => {
@@ -16629,5 +16731,6 @@ export {
16629
16731
  quantizeValue,
16630
16732
  resizeAndChangeQuality4 as resizeAndChangeQuality,
16631
16733
  setBitstringStatusListBit,
16734
+ toStoredCredential,
16632
16735
  unwrapBoostCredential
16633
16736
  };