@learncard/types 5.22.0 → 5.23.1
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/__tests__/unsupportedSignInOperation.test.d.ts +2 -0
- package/dist/__tests__/unsupportedSignInOperation.test.d.ts.map +1 -0
- package/dist/auth.d.ts +81 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/clr.d.ts +2 -2
- package/dist/clr.d.ts.map +1 -1
- package/dist/clr.test.d.ts +2 -0
- package/dist/clr.test.d.ts.map +1 -0
- package/dist/inAppMessages.d.ts +10 -10
- package/dist/lcn.d.ts +1632 -9
- package/dist/lcn.d.ts.map +1 -1
- package/dist/share-links.d.ts +104 -0
- package/dist/share-links.d.ts.map +1 -1
- package/dist/types.cjs.development.cjs +364 -13
- package/dist/types.cjs.development.cjs.map +2 -2
- package/dist/types.cjs.production.min.cjs +22 -22
- package/dist/types.cjs.production.min.cjs.map +3 -3
- package/dist/types.esm.js +364 -13
- package/dist/types.esm.js.map +2 -2
- package/dist/vc.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/__tests__/unsupportedSignInOperation.test.ts +13 -0
- package/src/auth.ts +89 -4
- package/src/clr.test.ts +163 -0
- package/src/clr.ts +2 -1
- package/src/lcn.ts +342 -5
- package/src/share-links.ts +75 -0
- package/src/vc.test.ts +10 -3
- package/src/vc.ts +13 -1
package/dist/vc.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vc.d.ts","sourceRoot":"","sources":["../src/vc.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"vc.d.ts","sourceRoot":"","sources":["../src/vc.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@learncard/types",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.23.1",
|
|
4
4
|
"description": "Shared types for learn card",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build": "node ./scripts/build.mjs && shx cp ./scripts/mixedEntypoint.js ./dist/index.cjs && tsc --p tsconfig.json && shx cp ./dist/index.d.ts ./dist/index.d.cts",
|
|
27
27
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
28
|
+
"test": "vitest run",
|
|
28
29
|
"start": "aqu watch"
|
|
29
30
|
},
|
|
30
31
|
"author": "Learning Economy Foundation (www.learningeconomy.io)",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { UnsupportedSignInOperationError } from '../auth';
|
|
3
|
+
|
|
4
|
+
describe('UnsupportedSignInOperationError', () => {
|
|
5
|
+
it('preserves the operation and provider for callers', () => {
|
|
6
|
+
const error = new UnsupportedSignInOperationError('sendPhoneOtp', 'keycloak');
|
|
7
|
+
expect(error).toBeInstanceOf(Error);
|
|
8
|
+
expect(error.name).toBe('UnsupportedSignInOperationError');
|
|
9
|
+
expect(error.operation).toBe('sendPhoneOtp');
|
|
10
|
+
expect(error.providerType).toBe('keycloak');
|
|
11
|
+
expect(error.message).toContain('sendPhoneOtp');
|
|
12
|
+
});
|
|
13
|
+
});
|
package/src/auth.ts
CHANGED
|
@@ -32,6 +32,17 @@ export class AuthSessionError extends Error {
|
|
|
32
32
|
// Auth Provider
|
|
33
33
|
// ---------------------------------------------------------------------------
|
|
34
34
|
|
|
35
|
+
/** An action that the selected sign-in provider does not support. */
|
|
36
|
+
export class UnsupportedSignInOperationError extends Error {
|
|
37
|
+
constructor(
|
|
38
|
+
public readonly operation: string,
|
|
39
|
+
public readonly providerType: string
|
|
40
|
+
) {
|
|
41
|
+
super(`${operation} is not supported by ${providerType}`);
|
|
42
|
+
this.name = 'UnsupportedSignInOperationError';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
35
46
|
/**
|
|
36
47
|
* Auth provider identifier. Known values: 'firebase', 'supertokens', 'keycloak', 'oidc'.
|
|
37
48
|
* Use any string to support custom auth providers without modifying this type.
|
|
@@ -260,6 +271,8 @@ export interface RecoveryResult {
|
|
|
260
271
|
export interface IdentityRecoverySession {
|
|
261
272
|
recoverySessionToken: string;
|
|
262
273
|
recoveryMethods: RecoveryMethodInfo[];
|
|
274
|
+
/** The verified account's DID (same source as `ServerKeyStatus.primaryDid`). */
|
|
275
|
+
primaryDid: string | null;
|
|
263
276
|
}
|
|
264
277
|
|
|
265
278
|
// ---------------------------------------------------------------------------
|
|
@@ -269,7 +282,15 @@ export interface IdentityRecoverySession {
|
|
|
269
282
|
export type SssActivationState = 'provisional' | 'active';
|
|
270
283
|
|
|
271
284
|
/** Optional PIN enrollment requires rotating the existing escrow share. */
|
|
272
|
-
export type EscrowEnrollmentOptions = {
|
|
285
|
+
export type EscrowEnrollmentOptions = {
|
|
286
|
+
pin?: string;
|
|
287
|
+
/** P9.1 (attack C): required by the enclave for nitro/authority-mode
|
|
288
|
+
* enroll/replace; bound into the sealed blob + signed intent, never read
|
|
289
|
+
* or changed by lca-api. The authenticated account's email + the app's
|
|
290
|
+
* current locale (one of the compiled template languages). */
|
|
291
|
+
recoveryEmail?: string;
|
|
292
|
+
recoveryLanguage?: string;
|
|
293
|
+
};
|
|
273
294
|
|
|
274
295
|
/** Stable error-message contract shared by PIN recovery clients and servers. */
|
|
275
296
|
export const ESCROW_PIN_LOCKED_MESSAGE =
|
|
@@ -288,10 +309,21 @@ export const EscrowPinStatusValidator = z.object({
|
|
|
288
309
|
});
|
|
289
310
|
export type EscrowPinStatus = z.infer<typeof EscrowPinStatusValidator>;
|
|
290
311
|
|
|
312
|
+
/**
|
|
313
|
+
* Reason a stored escrow blob no longer matches the server's active enclave:
|
|
314
|
+
* `mode-mismatch` — blob was sealed for `software`/`nitro` but the server now
|
|
315
|
+
* runs the other backend (e.g. the P6 software→Nitro migration, or a rollback);
|
|
316
|
+
* `key-rotated` — blob's `enclaveKeyId` no longer matches the active
|
|
317
|
+
* attestation's key (e.g. a PCR/measurement rotation).
|
|
318
|
+
*/
|
|
319
|
+
export type EscrowBlobStaleReason = 'mode-mismatch' | 'key-rotated';
|
|
320
|
+
|
|
291
321
|
/** Enrollment details for PIN-aware strategies. Legacy strategies may still return a string. */
|
|
292
322
|
export interface EscrowEnrollmentState {
|
|
293
|
-
state: 'enrolled' | 'not-enrolled' | 'opted-out' | 'disabled';
|
|
323
|
+
state: 'enrolled' | 'not-enrolled' | 'opted-out' | 'disabled' | 'stale';
|
|
294
324
|
escrowPin?: EscrowPinStatus;
|
|
325
|
+
/** Present only when `state === 'stale'`. */
|
|
326
|
+
staleReason?: EscrowBlobStaleReason;
|
|
295
327
|
}
|
|
296
328
|
|
|
297
329
|
/**
|
|
@@ -309,6 +341,7 @@ export interface ServerKeyStatus {
|
|
|
309
341
|
maskedRecoveryEmail?: string | null;
|
|
310
342
|
escrowOptedOut?: boolean;
|
|
311
343
|
escrowPin?: EscrowPinStatus;
|
|
344
|
+
escrowStale?: EscrowBlobStaleReason;
|
|
312
345
|
sssActivationState?: SssActivationState | null;
|
|
313
346
|
}
|
|
314
347
|
|
|
@@ -362,6 +395,20 @@ export interface KeyDerivationCapabilities {
|
|
|
362
395
|
// Key Derivation Strategy (generic)
|
|
363
396
|
// ---------------------------------------------------------------------------
|
|
364
397
|
|
|
398
|
+
/** Initiating-client bindings. Persist with the recovery secrets and never replace
|
|
399
|
+
* these with fields from a status response. Missing bindings require a new start. */
|
|
400
|
+
export interface EscrowRecoveryExpectations {
|
|
401
|
+
tenant: string;
|
|
402
|
+
did: string;
|
|
403
|
+
holdId: string;
|
|
404
|
+
clientEphemeralPublicKey: string;
|
|
405
|
+
blobHash: string;
|
|
406
|
+
authorityGeneration: string;
|
|
407
|
+
holdDigest: string;
|
|
408
|
+
ledgerPublicKey: string;
|
|
409
|
+
keyId: string;
|
|
410
|
+
}
|
|
411
|
+
|
|
365
412
|
/**
|
|
366
413
|
* Key Derivation Strategy
|
|
367
414
|
*
|
|
@@ -527,18 +574,27 @@ export interface KeyDerivationStrategy<
|
|
|
527
574
|
privateKey: string;
|
|
528
575
|
signDidAuthVp: DidAuthVpSigner;
|
|
529
576
|
pin: string;
|
|
577
|
+
/** P9.1: only `recoveryEmail`/`recoveryLanguage` apply here; `options.pin`
|
|
578
|
+
* is ignored in favor of the required top-level `pin`. */
|
|
579
|
+
options?: EscrowEnrollmentOptions;
|
|
530
580
|
}): Promise<void>;
|
|
531
581
|
|
|
532
|
-
/** Remove a PIN by rotating escrow material with an owner proof.
|
|
582
|
+
/** Remove a PIN by rotating escrow material with an owner proof. In
|
|
583
|
+
* nitro/authority mode this is a `replace` enrollment action, which
|
|
584
|
+
* requires `options.recoveryEmail`/`recoveryLanguage`, same as
|
|
585
|
+
* `setEscrowPin`. */
|
|
533
586
|
clearEscrowPin?(params: {
|
|
534
587
|
token: string;
|
|
535
588
|
providerType: AuthProviderType;
|
|
536
589
|
privateKey: string;
|
|
537
590
|
signDidAuthVp: DidAuthVpSigner;
|
|
591
|
+
options?: EscrowEnrollmentOptions;
|
|
538
592
|
}): Promise<void>;
|
|
539
593
|
|
|
540
594
|
/** Start an escrow hold. Securely persist the returned secrets; null means an existing hold. */
|
|
541
595
|
startEscrowRecovery?(params: {
|
|
596
|
+
/** Account selected by the initiating client, required for Nitro recovery. */
|
|
597
|
+
expectedDid?: string;
|
|
542
598
|
token?: string;
|
|
543
599
|
providerType?: AuthProviderType;
|
|
544
600
|
recoverySessionToken?: string;
|
|
@@ -553,15 +609,19 @@ export interface KeyDerivationStrategy<
|
|
|
553
609
|
completedAt?: string;
|
|
554
610
|
resumeToken: string | null;
|
|
555
611
|
clientEphemeralPrivateKey: string;
|
|
612
|
+
/** Persist alongside the private key; pass unchanged on status, confirm and restore. */
|
|
613
|
+
expectations?: EscrowRecoveryExpectations;
|
|
556
614
|
pinSalt?: string;
|
|
557
615
|
/** Absent on legacy hold-only strategies. */
|
|
558
616
|
releasePolicy?: 'hold' | 'pin';
|
|
617
|
+
/** Present only for an authority-mode hold-policy hold; see confirmEscrowRecovery. */
|
|
618
|
+
requiresConfirmation?: boolean;
|
|
559
619
|
}>;
|
|
560
620
|
|
|
561
621
|
/** Read a hold using its resume proof or the active device's provider session. */
|
|
562
622
|
getEscrowRecoveryStatus?(
|
|
563
623
|
params:
|
|
564
|
-
| { holdId: string; resumeToken: string }
|
|
624
|
+
| { holdId: string; resumeToken: string; expectations?: EscrowRecoveryExpectations }
|
|
565
625
|
| { token: string; providerType: AuthProviderType }
|
|
566
626
|
): Promise<{
|
|
567
627
|
holdId: string;
|
|
@@ -572,6 +632,9 @@ export interface KeyDerivationStrategy<
|
|
|
572
632
|
releasePolicy?: 'hold' | 'pin';
|
|
573
633
|
cancelledAt?: string;
|
|
574
634
|
completedAt?: string;
|
|
635
|
+
/** Only present for an authority-mode hold-policy hold; `confirmed` only appears alongside it. */
|
|
636
|
+
requiresConfirmation?: boolean;
|
|
637
|
+
confirmed?: boolean;
|
|
575
638
|
} | null>;
|
|
576
639
|
|
|
577
640
|
/** Cancel a pending hold with a fresh, owner-signed DID challenge. */
|
|
@@ -582,6 +645,28 @@ export interface KeyDerivationStrategy<
|
|
|
582
645
|
signDidAuthVp: DidAuthVpSigner;
|
|
583
646
|
}): Promise<{ cancelled: boolean }>;
|
|
584
647
|
|
|
648
|
+
/**
|
|
649
|
+
* Submit the confirmation code the enclave emailed to the account's
|
|
650
|
+
* recovery address. Only meaningful when startEscrowRecovery returned
|
|
651
|
+
* `requiresConfirmation: true`; the resume proof (no DID session exists
|
|
652
|
+
* yet at this point in the flow) is the only credential.
|
|
653
|
+
*/
|
|
654
|
+
confirmEscrowRecovery?(params: {
|
|
655
|
+
/** Retained initiating-client bindings, required in Nitro mode, including after reload. */
|
|
656
|
+
expectations?: EscrowRecoveryExpectations;
|
|
657
|
+
holdId: string;
|
|
658
|
+
resumeToken: string;
|
|
659
|
+
code: string;
|
|
660
|
+
tenantId?: string;
|
|
661
|
+
}): Promise<{ success: true }>;
|
|
662
|
+
|
|
663
|
+
/** Request a fresh confirmation code for a hold awaiting confirmation. */
|
|
664
|
+
resendEscrowRecoveryCode?(params: {
|
|
665
|
+
holdId: string;
|
|
666
|
+
resumeToken: string;
|
|
667
|
+
tenantId?: string;
|
|
668
|
+
}): Promise<{ success: true }>;
|
|
669
|
+
|
|
585
670
|
/** Execute a recovery flow and return the recovered private key + DID */
|
|
586
671
|
executeRecovery(params: {
|
|
587
672
|
token: string;
|
package/src/clr.test.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { describe, expect, expectTypeOf, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AssociationValidator,
|
|
5
|
+
ClrCredentialValidator,
|
|
6
|
+
ClrSubjectValidator,
|
|
7
|
+
UnsignedClrCredentialValidator,
|
|
8
|
+
type Association,
|
|
9
|
+
} from './clr';
|
|
10
|
+
|
|
11
|
+
const association = {
|
|
12
|
+
type: 'Association' as const,
|
|
13
|
+
associationType: 'isPartOf',
|
|
14
|
+
sourceId: 'https://example.org/achievements/course',
|
|
15
|
+
targetId: 'https://example.org/achievements/program',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const supportedTypeNames = [
|
|
19
|
+
'Association',
|
|
20
|
+
'https://purl.imsglobal.org/spec/vc/clr/vocab.html#Association',
|
|
21
|
+
'https://example.org/ExtendedAssociation',
|
|
22
|
+
'CustomAssociation',
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const subject = {
|
|
26
|
+
id: 'did:example:learner',
|
|
27
|
+
type: ['ClrSubject'],
|
|
28
|
+
association: [association],
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const unsignedClr = {
|
|
32
|
+
'@context': [
|
|
33
|
+
'https://www.w3.org/ns/credentials/v2',
|
|
34
|
+
'https://purl.imsglobal.org/spec/clr/v2p0/context.json',
|
|
35
|
+
'https://purl.imsglobal.org/spec/ob/v3p0/context.json',
|
|
36
|
+
],
|
|
37
|
+
id: 'https://example.org/credentials/clr',
|
|
38
|
+
type: ['VerifiableCredential', 'ClrCredential'],
|
|
39
|
+
issuer: { id: 'did:example:issuer', type: ['Profile'], name: 'Example University' },
|
|
40
|
+
validFrom: '2026-01-01T00:00:00Z',
|
|
41
|
+
credentialSubject: subject,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const proof = {
|
|
45
|
+
type: 'DataIntegrityProof',
|
|
46
|
+
cryptosuite: 'eddsa-rdfc-2022',
|
|
47
|
+
created: '2026-01-01T00:00:00Z',
|
|
48
|
+
proofPurpose: 'assertionMethod',
|
|
49
|
+
verificationMethod: 'did:example:issuer#key',
|
|
50
|
+
proofValue: 'zExampleSignature',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
describe('CLR Association.type', () => {
|
|
54
|
+
it('accepts the canonical scalar without changing it or extension fields', () => {
|
|
55
|
+
// CLR 2.0 $defs.Association.properties.type requires the scalar "Association".
|
|
56
|
+
// https://purl.imsglobal.org/spec/clr/v2p0/schema/json/clr_v2p0_clrcredential_schema.json
|
|
57
|
+
const input = { ...association, 'ext:description': 'Course belongs to program' };
|
|
58
|
+
|
|
59
|
+
expect(AssociationValidator.parse(input)).toEqual(input);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it.each(supportedTypeNames)(
|
|
63
|
+
'preserves scalar and singleton-array type values through full CLR parsing: %s',
|
|
64
|
+
typeName => {
|
|
65
|
+
for (const type of [typeName, [typeName]]) {
|
|
66
|
+
const input = { ...association, type };
|
|
67
|
+
const credential = {
|
|
68
|
+
...unsignedClr,
|
|
69
|
+
credentialSubject: { ...subject, association: [input] },
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// This SDK validator checks structure; the official CLR schema is stricter.
|
|
73
|
+
expect(AssociationValidator.parse(input)).toEqual(input);
|
|
74
|
+
expect(UnsignedClrCredentialValidator.parse(credential)).toEqual(credential);
|
|
75
|
+
expect(ClrCredentialValidator.parse({ ...credential, proof })).toEqual({
|
|
76
|
+
...credential,
|
|
77
|
+
proof,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
it.each(
|
|
84
|
+
[
|
|
85
|
+
['Association'],
|
|
86
|
+
['Association', 'https://example.org/ExtendedAssociation'],
|
|
87
|
+
['https://example.org/LegacyAssociation'],
|
|
88
|
+
[''], // Existing arrays accept every string; preserve that contract.
|
|
89
|
+
].map(type => ({ type }))
|
|
90
|
+
)('preserves supported legacy arrays: $type', ({ type }) => {
|
|
91
|
+
const input = { ...association, type };
|
|
92
|
+
|
|
93
|
+
expect(AssociationValidator.parse(input)).toEqual(input);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it.each(
|
|
97
|
+
[undefined, null, false, 42, '', {}, [], ['Association', 42], [['Association']]].map(
|
|
98
|
+
type => ({ type })
|
|
99
|
+
)
|
|
100
|
+
)('rejects invalid values: $type', ({ type }) => {
|
|
101
|
+
expect(AssociationValidator.safeParse({ ...association, type }).success).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('exposes scalar and legacy array types to callers', () => {
|
|
105
|
+
expectTypeOf<Association['type']>().toEqualTypeOf<string | string[]>();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('CLR credentials with associations', () => {
|
|
110
|
+
it('parses a subject containing both canonical and legacy associations', () => {
|
|
111
|
+
const input = {
|
|
112
|
+
...subject,
|
|
113
|
+
association: [association, { ...association, type: ['Association'] }],
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
expect(ClrSubjectValidator.parse(input)).toEqual(input);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it.each([false, true])('parses unsigned and signed CLR subject arrays: %s', subjectArray => {
|
|
120
|
+
const input = {
|
|
121
|
+
...unsignedClr,
|
|
122
|
+
credentialSubject: subjectArray ? [subject] : subject,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
expect(UnsignedClrCredentialValidator.parse(input)).toEqual(input);
|
|
126
|
+
expect(ClrCredentialValidator.parse({ ...input, proof })).toEqual({ ...input, proof });
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('preserves nested CLR content and validates its associations when parsed directly', () => {
|
|
130
|
+
const nested = {
|
|
131
|
+
...unsignedClr,
|
|
132
|
+
id: 'https://example.org/credentials/nested-clr',
|
|
133
|
+
credentialSubject: {
|
|
134
|
+
...subject,
|
|
135
|
+
association: [{ ...association, type: supportedTypeNames[1] }],
|
|
136
|
+
},
|
|
137
|
+
proof,
|
|
138
|
+
};
|
|
139
|
+
const input = {
|
|
140
|
+
...unsignedClr,
|
|
141
|
+
credentialSubject: { ...subject, verifiableCredential: [nested] },
|
|
142
|
+
proof,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
expect(ClrCredentialValidator.parse(input)).toEqual(input);
|
|
146
|
+
// Embedded verifiableCredential values are intentionally opaque in ClrSubjectValidator.
|
|
147
|
+
expect(ClrCredentialValidator.parse(nested)).toEqual(nested);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it.each([[], '', ['Association', null]].map(type => ({ type })))(
|
|
151
|
+
'rejects invalid associations through full CLR parsing: $type',
|
|
152
|
+
({ type }) => {
|
|
153
|
+
const invalidSubject = { ...subject, association: [{ ...association, type }] };
|
|
154
|
+
|
|
155
|
+
for (const credentialSubject of [invalidSubject, [invalidSubject]]) {
|
|
156
|
+
const input = { ...unsignedClr, credentialSubject };
|
|
157
|
+
|
|
158
|
+
expect(UnsignedClrCredentialValidator.safeParse(input).success).toBe(false);
|
|
159
|
+
expect(ClrCredentialValidator.safeParse({ ...input, proof }).success).toBe(false);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
});
|
package/src/clr.ts
CHANGED
|
@@ -27,7 +27,8 @@ export type AssociationType = z.infer<typeof AssociationTypeValidator>;
|
|
|
27
27
|
|
|
28
28
|
export const AssociationValidator = z
|
|
29
29
|
.object({
|
|
30
|
-
|
|
30
|
+
// Accept compacted scalars while retaining the existing permissive array contract.
|
|
31
|
+
type: z.string().min(1).or(z.string().array().nonempty()),
|
|
31
32
|
associationType: AssociationTypeValidator,
|
|
32
33
|
sourceId: z.string().optional(),
|
|
33
34
|
targetId: z.string(),
|