@wemush/wols 0.1.0 → 1.2.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.
- package/dist/cjs/compact-url.js +64 -0
- package/dist/cjs/compact-url.js.map +1 -1
- package/dist/cjs/crypto/decrypt.js +16 -5
- package/dist/cjs/crypto/decrypt.js.map +1 -1
- package/dist/cjs/crypto/encrypt.js +111 -9
- package/dist/cjs/crypto/encrypt.js.map +1 -1
- package/dist/cjs/index.js +53 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/parser.js +3 -0
- package/dist/cjs/parser.js.map +1 -1
- package/dist/cjs/specimen.js +14 -1
- package/dist/cjs/specimen.js.map +1 -1
- package/dist/cjs/types.js +267 -3
- package/dist/cjs/types.js.map +1 -1
- package/dist/cjs/utils/environment.js +206 -0
- package/dist/cjs/utils/environment.js.map +1 -0
- package/dist/cjs/utils/migration.js +248 -0
- package/dist/cjs/utils/migration.js.map +1 -0
- package/dist/cjs/validator.js +62 -9
- package/dist/cjs/validator.js.map +1 -1
- package/dist/esm/compact-url.js +62 -0
- package/dist/esm/compact-url.js.map +1 -1
- package/dist/esm/crypto/decrypt.js +16 -5
- package/dist/esm/crypto/decrypt.js.map +1 -1
- package/dist/esm/crypto/encrypt.js +111 -9
- package/dist/esm/crypto/encrypt.js.map +1 -1
- package/dist/esm/index.js +21 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/parser.js +3 -0
- package/dist/esm/parser.js.map +1 -1
- package/dist/esm/specimen.js +15 -2
- package/dist/esm/specimen.js.map +1 -1
- package/dist/esm/types.js +258 -2
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/utils/environment.js +193 -0
- package/dist/esm/utils/environment.js.map +1 -0
- package/dist/esm/utils/migration.js +237 -0
- package/dist/esm/utils/migration.js.map +1 -0
- package/dist/esm/validator.js +62 -9
- package/dist/esm/validator.js.map +1 -1
- package/dist/types/compact-url.d.ts +46 -0
- package/dist/types/compact-url.d.ts.map +1 -1
- package/dist/types/crypto/decrypt.d.ts +6 -0
- package/dist/types/crypto/decrypt.d.ts.map +1 -1
- package/dist/types/crypto/encrypt.d.ts +56 -1
- package/dist/types/crypto/encrypt.d.ts.map +1 -1
- package/dist/types/index.d.ts +10 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/parser.d.ts.map +1 -1
- package/dist/types/specimen.d.ts.map +1 -1
- package/dist/types/types.d.ts +246 -5
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/utils/environment.d.ts +137 -0
- package/dist/types/utils/environment.d.ts.map +1 -0
- package/dist/types/utils/migration.d.ts +162 -0
- package/dist/types/utils/migration.d.ts.map +1 -0
- package/dist/types/validator.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -4,16 +4,90 @@
|
|
|
4
4
|
* Provides AES-256-GCM encryption for specimens using Web Crypto API.
|
|
5
5
|
* Supports both password-based (PBKDF2) and raw CryptoKey encryption.
|
|
6
6
|
*
|
|
7
|
+
* ## Security Model
|
|
8
|
+
*
|
|
9
|
+
* This module implements a **deterministic key derivation** model optimized for:
|
|
10
|
+
* - Cross-environment compatibility (browser + Node.js)
|
|
11
|
+
* - Offline decryption (no external key storage needed)
|
|
12
|
+
* - Self-describing payloads (iterations stored in output)
|
|
13
|
+
*
|
|
14
|
+
* ### Password vs CryptoKey
|
|
15
|
+
*
|
|
16
|
+
* | Input Type | Use Case | Security Level |
|
|
17
|
+
* |------------|----------|----------------|
|
|
18
|
+
* | `string` (password) | Simple sharing, QR codes | Good (depends on password strength) |
|
|
19
|
+
* | `CryptoKey` | Server-side, key management systems | Best (full control over key material) |
|
|
20
|
+
*
|
|
21
|
+
* ### Security Considerations
|
|
22
|
+
*
|
|
23
|
+
* 1. **Fixed Salt**: The PBKDF2 salt is fixed per crypto version. This is intentional
|
|
24
|
+
* for deterministic cross-environment key derivation. Password strength is critical.
|
|
25
|
+
*
|
|
26
|
+
* 2. **Iteration Count**: Default 100,000 iterations. Can be increased via `iterations`
|
|
27
|
+
* option for higher security (at cost of performance).
|
|
28
|
+
*
|
|
29
|
+
* 3. **Random IV**: Each encryption uses a cryptographically random 96-bit IV,
|
|
30
|
+
* ensuring identical plaintexts produce different ciphertexts.
|
|
31
|
+
*
|
|
32
|
+
* 4. **Authenticated Encryption**: AES-256-GCM provides both confidentiality and
|
|
33
|
+
* integrity - tampered ciphertext will fail decryption.
|
|
34
|
+
*
|
|
35
|
+
* ## Future Specification Updates (v1.2.1+)
|
|
36
|
+
*
|
|
37
|
+
* TODO(wols-spec): The following enhancements should be documented in the upstream
|
|
38
|
+
* WOLS specification (wemush/open-standard/SPECIFICATION.md) in a future release:
|
|
39
|
+
*
|
|
40
|
+
* 1. **EncryptedSpecimen schema**: Define the `cryptoVersion` and `iterations` fields
|
|
41
|
+
* in the encrypted payload format section.
|
|
42
|
+
*
|
|
43
|
+
* 2. **Configurable iterations**: Document the `iterations` option in EncryptionOptions
|
|
44
|
+
* with recommended ranges (100k-600k) and minimum floor (10k).
|
|
45
|
+
*
|
|
46
|
+
* 3. **Self-describing payloads**: Explain that v1.2.0+ payloads include derivation
|
|
47
|
+
* parameters for forward-compatible decryption.
|
|
48
|
+
*
|
|
49
|
+
* 4. **Security guidance**: Add a security considerations section covering:
|
|
50
|
+
* - Fixed salt tradeoffs (determinism vs uniqueness)
|
|
51
|
+
* - Password strength requirements
|
|
52
|
+
* - When to use CryptoKey vs password
|
|
53
|
+
* - Optional random salt for high-security scenarios (future v2)
|
|
54
|
+
*
|
|
7
55
|
* @module crypto/encrypt
|
|
8
56
|
*/
|
|
9
57
|
import { serializeSpecimen } from '../specimen.js';
|
|
10
58
|
// =============================================================================
|
|
11
59
|
// CONSTANTS
|
|
12
60
|
// =============================================================================
|
|
13
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* Salt for PBKDF2 key derivation.
|
|
63
|
+
*
|
|
64
|
+
* DESIGN DECISION: This salt is intentionally fixed (not random) for deterministic
|
|
65
|
+
* key derivation. This allows:
|
|
66
|
+
*
|
|
67
|
+
* 1. **Cross-environment compatibility**: Same password produces same key in
|
|
68
|
+
* browser (Web Crypto API) and server (Node.js crypto), enabling encrypted
|
|
69
|
+
* specimens to be decrypted in either environment.
|
|
70
|
+
*
|
|
71
|
+
* 2. **Offline decryption**: No need to store/transmit the salt alongside the
|
|
72
|
+
* encrypted payload - the salt is known from the library version.
|
|
73
|
+
*
|
|
74
|
+
* 3. **Version-aware migration**: The salt includes a version identifier
|
|
75
|
+
* (@wemush/wols/crypto/v1) allowing future versions to use different salts
|
|
76
|
+
* while maintaining backward compatibility for decryption.
|
|
77
|
+
*
|
|
78
|
+
* SECURITY NOTE: The fixed salt means password security depends entirely on
|
|
79
|
+
* password strength. Users should use strong, unique passwords. For maximum
|
|
80
|
+
* security, use a CryptoKey directly instead of a password string.
|
|
81
|
+
*
|
|
82
|
+
* @internal
|
|
83
|
+
*/
|
|
14
84
|
const PBKDF2_SALT = new TextEncoder().encode('@wemush/wols/crypto/v1');
|
|
15
|
-
/**
|
|
16
|
-
const
|
|
85
|
+
/** Default number of PBKDF2 iterations */
|
|
86
|
+
const DEFAULT_PBKDF2_ITERATIONS = 100_000;
|
|
87
|
+
/** Minimum allowed PBKDF2 iterations (security floor) */
|
|
88
|
+
const MIN_PBKDF2_ITERATIONS = 10_000;
|
|
89
|
+
/** Current crypto schema version */
|
|
90
|
+
const CRYPTO_VERSION = 'v1';
|
|
17
91
|
/** IV length in bytes (96 bits = 12 bytes recommended for GCM) */
|
|
18
92
|
const IV_LENGTH = 12;
|
|
19
93
|
// =============================================================================
|
|
@@ -30,10 +104,21 @@ function toBase64Url(buffer) {
|
|
|
30
104
|
}
|
|
31
105
|
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
32
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Validate and normalize iteration count
|
|
109
|
+
* @throws Error if iterations below minimum
|
|
110
|
+
*/
|
|
111
|
+
function validateIterations(iterations) {
|
|
112
|
+
const count = iterations ?? DEFAULT_PBKDF2_ITERATIONS;
|
|
113
|
+
if (count < MIN_PBKDF2_ITERATIONS) {
|
|
114
|
+
throw new Error(`PBKDF2 iterations must be at least ${MIN_PBKDF2_ITERATIONS}, got ${count}`);
|
|
115
|
+
}
|
|
116
|
+
return count;
|
|
117
|
+
}
|
|
33
118
|
/**
|
|
34
119
|
* Derive a CryptoKey from a password using PBKDF2
|
|
35
120
|
*/
|
|
36
|
-
async function deriveKey(password) {
|
|
121
|
+
async function deriveKey(password, iterations) {
|
|
37
122
|
const encoder = new TextEncoder();
|
|
38
123
|
const passwordBytes = encoder.encode(password);
|
|
39
124
|
// Import password as key material
|
|
@@ -44,7 +129,7 @@ async function deriveKey(password) {
|
|
|
44
129
|
return crypto.subtle.deriveKey({
|
|
45
130
|
name: 'PBKDF2',
|
|
46
131
|
salt: PBKDF2_SALT,
|
|
47
|
-
iterations
|
|
132
|
+
iterations,
|
|
48
133
|
hash: 'SHA-256',
|
|
49
134
|
}, keyMaterial, { name: 'AES-GCM', length: 256 }, false, ['encrypt']);
|
|
50
135
|
}
|
|
@@ -71,10 +156,17 @@ function generateIV() {
|
|
|
71
156
|
* ```typescript
|
|
72
157
|
* import { encryptSpecimen } from '@wemush/wols/crypto';
|
|
73
158
|
*
|
|
159
|
+
* // Basic encryption with default iterations (100k)
|
|
74
160
|
* const encrypted = await encryptSpecimen(specimen, {
|
|
75
161
|
* key: 'my-secret-password',
|
|
76
162
|
* });
|
|
77
|
-
*
|
|
163
|
+
*
|
|
164
|
+
* // Higher security with custom iterations
|
|
165
|
+
* const secureEncrypted = await encryptSpecimen(specimen, {
|
|
166
|
+
* key: 'my-secret-password',
|
|
167
|
+
* iterations: 300_000,
|
|
168
|
+
* });
|
|
169
|
+
* // Returns: { encrypted: true, payload: "...", iv: "...", algorithm: "AES-256-GCM", cryptoVersion: "v1", iterations: 300000 }
|
|
78
170
|
* ```
|
|
79
171
|
*/
|
|
80
172
|
export async function encryptSpecimen(specimen, options) {
|
|
@@ -83,8 +175,12 @@ export async function encryptSpecimen(specimen, options) {
|
|
|
83
175
|
if (fields !== undefined && fields.length > 0) {
|
|
84
176
|
return encryptPartial(specimen, options);
|
|
85
177
|
}
|
|
178
|
+
// Validate and get iteration count
|
|
179
|
+
const iterations = validateIterations(options.iterations);
|
|
86
180
|
// Get or derive the encryption key
|
|
87
|
-
|
|
181
|
+
// When key is a CryptoKey, iterations is still recorded for documentation
|
|
182
|
+
// but not used (key is already derived)
|
|
183
|
+
const cryptoKey = typeof key === 'string' ? await deriveKey(key, iterations) : key;
|
|
88
184
|
// Serialize the specimen to JSON
|
|
89
185
|
const plaintext = serializeSpecimen(specimen);
|
|
90
186
|
const plaintextBytes = new TextEncoder().encode(plaintext);
|
|
@@ -92,12 +188,14 @@ export async function encryptSpecimen(specimen, options) {
|
|
|
92
188
|
const { iv, ivBuffer } = generateIV();
|
|
93
189
|
// Encrypt
|
|
94
190
|
const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, cryptoKey, plaintextBytes);
|
|
95
|
-
// Return encrypted specimen
|
|
191
|
+
// Return encrypted specimen with version and iterations for self-describing decryption
|
|
96
192
|
return {
|
|
97
193
|
encrypted: true,
|
|
98
194
|
payload: toBase64Url(ciphertext),
|
|
99
195
|
iv: toBase64Url(ivBuffer),
|
|
100
196
|
algorithm: 'AES-256-GCM',
|
|
197
|
+
cryptoVersion: CRYPTO_VERSION,
|
|
198
|
+
iterations,
|
|
101
199
|
};
|
|
102
200
|
}
|
|
103
201
|
/**
|
|
@@ -107,8 +205,10 @@ export async function encryptSpecimen(specimen, options) {
|
|
|
107
205
|
*/
|
|
108
206
|
async function encryptPartial(specimen, options) {
|
|
109
207
|
const { key, fields = [] } = options;
|
|
208
|
+
// Validate and get iteration count
|
|
209
|
+
const iterations = validateIterations(options.iterations);
|
|
110
210
|
// Get or derive the encryption key
|
|
111
|
-
const cryptoKey = typeof key === 'string' ? await deriveKey(key) : key;
|
|
211
|
+
const cryptoKey = typeof key === 'string' ? await deriveKey(key, iterations) : key;
|
|
112
212
|
// Clone the specimen
|
|
113
213
|
const result = { ...specimen };
|
|
114
214
|
// Encrypt each specified field
|
|
@@ -124,6 +224,8 @@ async function encryptPartial(specimen, options) {
|
|
|
124
224
|
payload: toBase64Url(ciphertext),
|
|
125
225
|
iv: toBase64Url(ivBuffer),
|
|
126
226
|
algorithm: 'AES-256-GCM',
|
|
227
|
+
cryptoVersion: CRYPTO_VERSION,
|
|
228
|
+
iterations,
|
|
127
229
|
};
|
|
128
230
|
}
|
|
129
231
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encrypt.js","sourceRoot":"","sources":["../../../src/crypto/encrypt.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"encrypt.js","sourceRoot":"","sources":["../../../src/crypto/encrypt.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/index.js
CHANGED
|
@@ -17,6 +17,18 @@ export { WOLS_VERSION, WOLS_CONTEXT } from './types.js';
|
|
|
17
17
|
// =============================================================================
|
|
18
18
|
export { asSpecimenId, isSpecimenType, isGrowthStage, isStrain, SPECIMEN_TYPES, GROWTH_STAGES, } from './types.js';
|
|
19
19
|
// =============================================================================
|
|
20
|
+
// Type Alias System (v1.2.0)
|
|
21
|
+
// =============================================================================
|
|
22
|
+
export { registerTypeAlias, resolveTypeAlias, getTypeAliases } from './types.js';
|
|
23
|
+
// =============================================================================
|
|
24
|
+
// Type Mapping System (v1.2.0)
|
|
25
|
+
// =============================================================================
|
|
26
|
+
export { WOLS_TO_PLATFORM_MAP, mapToWolsType, mapFromWolsType, registerPlatformType, } from './types.js';
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Generation Format System (v1.2.0)
|
|
29
|
+
// =============================================================================
|
|
30
|
+
export { normalizeGeneration, isValidGeneration } from './types.js';
|
|
31
|
+
// =============================================================================
|
|
20
32
|
// Errors
|
|
21
33
|
// =============================================================================
|
|
22
34
|
export { WolsErrorCode, WolsError, WolsParseError, WolsValidationError, WolsEncryptionError, } from './errors.js';
|
|
@@ -35,7 +47,7 @@ export { parseSpecimen } from './parser.js';
|
|
|
35
47
|
// =============================================================================
|
|
36
48
|
// Core Functions - Compact URL
|
|
37
49
|
// =============================================================================
|
|
38
|
-
export { toCompactUrl, parseCompactUrl, SPECIES_CODES, getSpeciesFromCode, getCodeFromSpecies, registerSpeciesCode, } from './compact-url.js';
|
|
50
|
+
export { toCompactUrl, parseCompactUrl, parseCompactUrlOrThrow, parseCompactUrlOrNull, SPECIES_CODES, getSpeciesFromCode, getCodeFromSpecies, registerSpeciesCode, } from './compact-url.js';
|
|
39
51
|
// =============================================================================
|
|
40
52
|
// Core Functions - Encryption
|
|
41
53
|
// =============================================================================
|
|
@@ -44,4 +56,12 @@ export { encryptSpecimen, isEncrypted, decryptSpecimen } from './crypto/index.js
|
|
|
44
56
|
// Core Functions - QR Code Generation
|
|
45
57
|
// =============================================================================
|
|
46
58
|
export { toQRCode, toQRCodeDataURL, toQRCodeSVG, isQRAvailable, isQRAvailableSync, } from './qr/index.js';
|
|
59
|
+
// =============================================================================
|
|
60
|
+
// Environment Detection (v1.2.0)
|
|
61
|
+
// =============================================================================
|
|
62
|
+
export { isServer, isBrowser, isWebWorker, isDeno, isBun, getRuntimeEnvironment, supportsWebCrypto, supportsNodeCrypto, isCryptoSupported, supportsTextEncoding, supportsURLAPIs, } from './utils/environment.js';
|
|
63
|
+
// =============================================================================
|
|
64
|
+
// Migration Utilities (v1.2.0)
|
|
65
|
+
// =============================================================================
|
|
66
|
+
export { compareVersions, isOutdated, isNewer, getCurrentVersion, registerMigration, canMigrate, migrate, getMigrations, clearMigrations, } from './utils/migration.js';
|
|
47
67
|
//# sourceMappingURL=index.js.map
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,gFAAgF;AAChF,YAAY;AACZ,gFAAgF;AAChF,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,gFAAgF;AAChF,YAAY;AACZ,gFAAgF;AAChF,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAyCxD,gFAAgF;AAChF,iBAAiB;AACjB,gFAAgF;AAChF,OAAO,EACL,YAAY,EACZ,cAAc,EACd,aAAa,EACb,QAAQ,EACR,cAAc,EACd,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,gFAAgF;AAChF,6BAA6B;AAC7B,gFAAgF;AAChF,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjF,gFAAgF;AAChF,+BAA+B;AAC/B,gFAAgF;AAChF,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,eAAe,EACf,oBAAoB,GACrB,MAAM,YAAY,CAAC;AAEpB,gFAAgF;AAChF,oCAAoC;AACpC,gFAAgF;AAChF,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAGpE,gFAAgF;AAChF,SAAS;AACT,gFAAgF;AAChF,OAAO,EACL,aAAa,EACb,SAAS,EACT,cAAc,EACd,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,aAAa,CAAC;AAErB,gFAAgF;AAChF,4BAA4B;AAC5B,gFAAgF;AAChF,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElE,gFAAgF;AAChF,8BAA8B;AAC9B,gFAAgF;AAChF,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,gFAAgF;AAChF,0BAA0B;AAC1B,gFAAgF;AAChF,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,gFAAgF;AAChF,+BAA+B;AAC/B,gFAAgF;AAChF,OAAO,EACL,YAAY,EACZ,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAE1B,gFAAgF;AAChF,8BAA8B;AAC9B,gFAAgF;AAChF,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAElF,gFAAgF;AAChF,sCAAsC;AACtC,gFAAgF;AAChF,OAAO,EACL,QAAQ,EACR,eAAe,EACf,WAAW,EACX,aAAa,EACb,iBAAiB,GAClB,MAAM,eAAe,CAAC;AAEvB,gFAAgF;AAChF,iCAAiC;AACjC,gFAAgF;AAChF,OAAO,EACL,QAAQ,EACR,SAAS,EACT,WAAW,EACX,MAAM,EACN,KAAK,EACL,qBAAqB,EACrB,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,GAChB,MAAM,wBAAwB,CAAC;AAGhC,gFAAgF;AAChF,+BAA+B;AAC/B,gFAAgF;AAChF,OAAO,EACL,eAAe,EACf,UAAU,EACV,OAAO,EACP,iBAAiB,EACjB,iBAAiB,EACjB,UAAU,EACV,OAAO,EACP,aAAa,EACb,eAAe,GAChB,MAAM,sBAAsB,CAAC"}
|
package/dist/esm/parser.js
CHANGED
|
@@ -137,6 +137,9 @@ export function parseSpecimen(json) {
|
|
|
137
137
|
if (data['signature'] !== undefined) {
|
|
138
138
|
specimen.signature = data['signature'];
|
|
139
139
|
}
|
|
140
|
+
if (data['_meta'] !== undefined) {
|
|
141
|
+
specimen._meta = data['_meta'];
|
|
142
|
+
}
|
|
140
143
|
return {
|
|
141
144
|
success: true,
|
|
142
145
|
data: specimen,
|
package/dist/esm/parser.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/parser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,uBAAuB,EACvB,aAAa,EACb,cAAc,EACd,mBAAmB,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/parser.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/specimen.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module specimen
|
|
8
8
|
*/
|
|
9
|
-
import { WOLS_CONTEXT, WOLS_VERSION, asSpecimenId, isStrain } from './types.js';
|
|
9
|
+
import { WOLS_CONTEXT, WOLS_VERSION, asSpecimenId, isSpecimenType, isStrain, resolveTypeAlias, } from './types.js';
|
|
10
10
|
import { createSpecimenId } from './utils/cuid.js';
|
|
11
11
|
/**
|
|
12
12
|
* Expand a strain input to a full Strain object.
|
|
@@ -69,13 +69,20 @@ function expandStrain(strain) {
|
|
|
69
69
|
export function createSpecimen(input) {
|
|
70
70
|
const id = asSpecimenId(createSpecimenId());
|
|
71
71
|
const strain = expandStrain(input.strain);
|
|
72
|
+
// Resolve type alias to canonical WOLS type (e.g., 'LIQUID_CULTURE' -> 'CULTURE')
|
|
73
|
+
const resolvedType = resolveTypeAlias(input.type);
|
|
74
|
+
if (!isSpecimenType(resolvedType)) {
|
|
75
|
+
throw new Error(`Invalid specimen type: '${input.type}'. ` +
|
|
76
|
+
`Must be one of: CULTURE, SPAWN, SUBSTRATE, FRUITING, HARVEST ` +
|
|
77
|
+
`(or a registered alias).`);
|
|
78
|
+
}
|
|
72
79
|
// Build specimen with required fields
|
|
73
80
|
const specimen = {
|
|
74
81
|
'@context': WOLS_CONTEXT,
|
|
75
82
|
'@type': 'Specimen',
|
|
76
83
|
id,
|
|
77
84
|
version: WOLS_VERSION,
|
|
78
|
-
type:
|
|
85
|
+
type: resolvedType,
|
|
79
86
|
species: input.species,
|
|
80
87
|
};
|
|
81
88
|
// Add optional fields only if defined
|
|
@@ -97,6 +104,9 @@ export function createSpecimen(input) {
|
|
|
97
104
|
if (input.custom !== undefined) {
|
|
98
105
|
specimen.custom = input.custom;
|
|
99
106
|
}
|
|
107
|
+
if (input._meta !== undefined) {
|
|
108
|
+
specimen._meta = input._meta;
|
|
109
|
+
}
|
|
100
110
|
return specimen;
|
|
101
111
|
}
|
|
102
112
|
/**
|
|
@@ -154,6 +164,9 @@ export function serializeSpecimen(specimen) {
|
|
|
154
164
|
if (specimen.signature !== undefined) {
|
|
155
165
|
ordered['signature'] = specimen.signature;
|
|
156
166
|
}
|
|
167
|
+
if (specimen._meta !== undefined) {
|
|
168
|
+
ordered['_meta'] = specimen._meta;
|
|
169
|
+
}
|
|
157
170
|
return JSON.stringify(ordered);
|
|
158
171
|
}
|
|
159
172
|
//# sourceMappingURL=specimen.js.map
|
package/dist/esm/specimen.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"specimen.js","sourceRoot":"","sources":["../../src/specimen.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,
|
|
1
|
+
{"version":3,"file":"specimen.js","sourceRoot":"","sources":["../../src/specimen.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/types.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* WOLS TypeScript Library - Core Types
|
|
3
3
|
*
|
|
4
4
|
* This module defines all the core types, interfaces, and enums
|
|
5
|
-
* for the WeMush Open Labeling Standard (WOLS) v1.
|
|
5
|
+
* for the WeMush Open Labeling Standard (WOLS) v1.2.0.
|
|
6
6
|
*
|
|
7
7
|
* @packageDocumentation
|
|
8
8
|
* @module @wemush/wols
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
/**
|
|
14
14
|
* WOLS specification version implemented by this library.
|
|
15
15
|
*/
|
|
16
|
-
export const WOLS_VERSION = '1.
|
|
16
|
+
export const WOLS_VERSION = '1.2.0';
|
|
17
17
|
/**
|
|
18
18
|
* JSON-LD context URL for WOLS specimens.
|
|
19
19
|
*/
|
|
@@ -35,15 +35,271 @@ export const SPECIMEN_TYPES = [
|
|
|
35
35
|
'FRUITING',
|
|
36
36
|
'HARVEST',
|
|
37
37
|
];
|
|
38
|
+
// =============================================================================
|
|
39
|
+
// TYPE ALIAS SYSTEM (v1.2.0)
|
|
40
|
+
// =============================================================================
|
|
41
|
+
/**
|
|
42
|
+
* Type alias registry for mapping platform-specific types to WOLS types.
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
const TYPE_ALIAS_REGISTRY = new Map([
|
|
46
|
+
// Culture aliases
|
|
47
|
+
['LIQUID_CULTURE', 'CULTURE'],
|
|
48
|
+
['LC', 'CULTURE'],
|
|
49
|
+
['AGAR', 'CULTURE'],
|
|
50
|
+
['PETRI', 'CULTURE'],
|
|
51
|
+
['SLANT', 'CULTURE'],
|
|
52
|
+
['CULTURE_PLATE', 'CULTURE'],
|
|
53
|
+
// Spawn aliases
|
|
54
|
+
['GRAIN_SPAWN', 'SPAWN'],
|
|
55
|
+
['SAWDUST_SPAWN', 'SPAWN'],
|
|
56
|
+
['PLUG_SPAWN', 'SPAWN'],
|
|
57
|
+
['DOWEL', 'SPAWN'],
|
|
58
|
+
// Substrate aliases
|
|
59
|
+
['BLOCK', 'SUBSTRATE'],
|
|
60
|
+
['BAG', 'SUBSTRATE'],
|
|
61
|
+
['BED', 'SUBSTRATE'],
|
|
62
|
+
['LOG', 'SUBSTRATE'],
|
|
63
|
+
['BUCKET', 'SUBSTRATE'],
|
|
64
|
+
// Fruiting aliases
|
|
65
|
+
['FLUSH', 'FRUITING'],
|
|
66
|
+
['PINNING', 'FRUITING'],
|
|
67
|
+
['PRIMORDIA', 'FRUITING'], // Scientifically precise term for pin initiation stage
|
|
68
|
+
['FRUIT', 'FRUITING'],
|
|
69
|
+
['FIRST_FLUSH', 'FRUITING'],
|
|
70
|
+
// Harvest aliases
|
|
71
|
+
['FRESH', 'HARVEST'],
|
|
72
|
+
['DRIED', 'HARVEST'],
|
|
73
|
+
['PROCESSED', 'HARVEST'],
|
|
74
|
+
['EXTRACT', 'HARVEST'],
|
|
75
|
+
]);
|
|
76
|
+
/**
|
|
77
|
+
* Register a custom type alias.
|
|
78
|
+
*
|
|
79
|
+
* @param alias - The alias string (case-insensitive, stored uppercase)
|
|
80
|
+
* @param wolsType - The canonical WOLS SpecimenType
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* registerTypeAlias('MYCELIUM_BLOCK', 'SUBSTRATE');
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export function registerTypeAlias(alias, wolsType) {
|
|
88
|
+
TYPE_ALIAS_REGISTRY.set(alias.toUpperCase(), wolsType);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Resolve a type alias to its canonical WOLS type.
|
|
92
|
+
* Returns the input if it's already a valid SpecimenType or no alias exists.
|
|
93
|
+
*
|
|
94
|
+
* @param typeOrAlias - Type string or alias to resolve
|
|
95
|
+
* @returns Canonical SpecimenType or original string if not found
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* resolveTypeAlias('LIQUID_CULTURE') // Returns 'CULTURE'
|
|
100
|
+
* resolveTypeAlias('CULTURE') // Returns 'CULTURE'
|
|
101
|
+
* resolveTypeAlias('unknown') // Returns 'unknown'
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export function resolveTypeAlias(typeOrAlias) {
|
|
105
|
+
const upper = typeOrAlias.toUpperCase();
|
|
106
|
+
// Already a valid type?
|
|
107
|
+
if (SPECIMEN_TYPES.includes(upper)) {
|
|
108
|
+
return upper;
|
|
109
|
+
}
|
|
110
|
+
// Check alias registry
|
|
111
|
+
const resolved = TYPE_ALIAS_REGISTRY.get(upper);
|
|
112
|
+
return resolved ?? typeOrAlias;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Get all registered type aliases.
|
|
116
|
+
*
|
|
117
|
+
* @returns ReadonlyMap of alias -> SpecimenType mappings
|
|
118
|
+
*/
|
|
119
|
+
export function getTypeAliases() {
|
|
120
|
+
return TYPE_ALIAS_REGISTRY;
|
|
121
|
+
}
|
|
122
|
+
// =============================================================================
|
|
123
|
+
// TYPE MAPPING EXPORTS (v1.2.0)
|
|
124
|
+
// =============================================================================
|
|
125
|
+
/**
|
|
126
|
+
* Mapping from WOLS types to common platform type names.
|
|
127
|
+
*
|
|
128
|
+
* This provides a centralized lookup for integrations that need to
|
|
129
|
+
* display user-friendly type names or map to platform-specific values.
|
|
130
|
+
*
|
|
131
|
+
* @since 1.2.0
|
|
132
|
+
*/
|
|
133
|
+
export const WOLS_TO_PLATFORM_MAP = {
|
|
134
|
+
CULTURE: ['Liquid Culture', 'LC', 'Agar', 'Petri', 'Slant', 'Culture Plate'],
|
|
135
|
+
SPAWN: ['Grain Spawn', 'Sawdust Spawn', 'Plug Spawn', 'Dowel'],
|
|
136
|
+
SUBSTRATE: ['Block', 'Bag', 'Bed', 'Log', 'Bucket'],
|
|
137
|
+
FRUITING: ['Pinning', 'Primordia', 'Fruiting', 'Flush', 'First Flush'],
|
|
138
|
+
HARVEST: ['Fresh', 'Dried', 'Processed', 'Extract'],
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Map a platform-specific type to a WOLS SpecimenType.
|
|
142
|
+
*
|
|
143
|
+
* First checks registered aliases, then falls back to direct lookup.
|
|
144
|
+
*
|
|
145
|
+
* @param platformType - Platform-specific type name
|
|
146
|
+
* @returns Matching SpecimenType or null if not found
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```typescript
|
|
150
|
+
* mapToWolsType('Liquid Culture') // Returns 'CULTURE'
|
|
151
|
+
* mapToWolsType('LC') // Returns 'CULTURE'
|
|
152
|
+
* mapToWolsType('Unknown Type') // Returns null
|
|
153
|
+
* ```
|
|
154
|
+
*
|
|
155
|
+
* @since 1.2.0
|
|
156
|
+
*/
|
|
157
|
+
export function mapToWolsType(platformType) {
|
|
158
|
+
// First try the alias registry
|
|
159
|
+
const resolved = resolveTypeAlias(platformType);
|
|
160
|
+
if (isSpecimenType(resolved)) {
|
|
161
|
+
return resolved;
|
|
162
|
+
}
|
|
163
|
+
// Fall back to searching WOLS_TO_PLATFORM_MAP
|
|
164
|
+
const normalized = platformType.toUpperCase().replace(/\s+/g, '_');
|
|
165
|
+
for (const [wolsType, platformTypes] of Object.entries(WOLS_TO_PLATFORM_MAP)) {
|
|
166
|
+
for (const pt of platformTypes) {
|
|
167
|
+
if (pt.toUpperCase().replace(/\s+/g, '_') === normalized) {
|
|
168
|
+
return wolsType;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get all platform type names for a WOLS SpecimenType.
|
|
176
|
+
*
|
|
177
|
+
* @param wolsType - WOLS SpecimenType
|
|
178
|
+
* @returns Array of platform type names
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```typescript
|
|
182
|
+
* mapFromWolsType('CULTURE')
|
|
183
|
+
* // Returns ['Liquid Culture', 'LC', 'Agar', 'Petri', 'Slant', 'Culture Plate']
|
|
184
|
+
* ```
|
|
185
|
+
*
|
|
186
|
+
* @since 1.2.0
|
|
187
|
+
*/
|
|
188
|
+
export function mapFromWolsType(wolsType) {
|
|
189
|
+
return WOLS_TO_PLATFORM_MAP[wolsType] ?? [];
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Register a custom platform type mapping.
|
|
193
|
+
*
|
|
194
|
+
* This adds to both the alias registry (for resolveTypeAlias) and
|
|
195
|
+
* can be used for custom platform integrations.
|
|
196
|
+
*
|
|
197
|
+
* @param platformType - Platform-specific type name
|
|
198
|
+
* @param wolsType - WOLS SpecimenType to map to
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```typescript
|
|
202
|
+
* registerPlatformType('Mycelium Block', 'SUBSTRATE');
|
|
203
|
+
* mapToWolsType('Mycelium Block') // Returns 'SUBSTRATE'
|
|
204
|
+
* ```
|
|
205
|
+
*
|
|
206
|
+
* @since 1.2.0
|
|
207
|
+
*/
|
|
208
|
+
export function registerPlatformType(platformType, wolsType) {
|
|
209
|
+
// Add to alias registry for resolveTypeAlias support
|
|
210
|
+
registerTypeAlias(platformType, wolsType);
|
|
211
|
+
}
|
|
38
212
|
/**
|
|
39
213
|
* Array of valid growth stages for validation.
|
|
214
|
+
* Ordered by typical lifecycle progression.
|
|
40
215
|
*/
|
|
41
216
|
export const GROWTH_STAGES = [
|
|
42
217
|
'INOCULATION',
|
|
218
|
+
'INCUBATION',
|
|
43
219
|
'COLONIZATION',
|
|
220
|
+
'PRIMORDIA',
|
|
44
221
|
'FRUITING',
|
|
222
|
+
'SENESCENCE',
|
|
45
223
|
'HARVEST',
|
|
46
224
|
];
|
|
225
|
+
/**
|
|
226
|
+
* Pattern for parsing generation strings.
|
|
227
|
+
* Matches: P, P1, F{n}, G{n}, or plain numbers.
|
|
228
|
+
*/
|
|
229
|
+
const GENERATION_PARSE_PATTERN = /^(P|P1|F(\d+)|G(\d+)|(\d+))$/i;
|
|
230
|
+
/**
|
|
231
|
+
* Normalize a generation string to the specified format.
|
|
232
|
+
*
|
|
233
|
+
* Accepts multiple input formats:
|
|
234
|
+
* - Filial: F1, F2, F3, etc.
|
|
235
|
+
* - Parental: P or P1
|
|
236
|
+
* - Generational: G1, G2, G3, etc.
|
|
237
|
+
* - Numeric: 1, 2, 3, etc.
|
|
238
|
+
*
|
|
239
|
+
* @param generation - The generation string to normalize
|
|
240
|
+
* @param format - Target format (default: 'preserve')
|
|
241
|
+
* @returns Normalized generation string, or original if not parseable
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ```typescript
|
|
245
|
+
* normalizeGeneration('G2', 'filial') // Returns 'F2'
|
|
246
|
+
* normalizeGeneration('3', 'filial') // Returns 'F3'
|
|
247
|
+
* normalizeGeneration('F1', 'numeric') // Returns '1'
|
|
248
|
+
* normalizeGeneration('P', 'filial') // Returns 'P'
|
|
249
|
+
* ```
|
|
250
|
+
*
|
|
251
|
+
* @since 1.2.0
|
|
252
|
+
*/
|
|
253
|
+
export function normalizeGeneration(generation, format = 'preserve') {
|
|
254
|
+
const trimmed = generation.trim().toUpperCase();
|
|
255
|
+
// Handle parental generation specially
|
|
256
|
+
if (trimmed === 'P' || trimmed === 'P1') {
|
|
257
|
+
return format === 'numeric' ? '0' : 'P';
|
|
258
|
+
}
|
|
259
|
+
const match = GENERATION_PARSE_PATTERN.exec(trimmed);
|
|
260
|
+
if (!match) {
|
|
261
|
+
return generation; // Return original if not parseable
|
|
262
|
+
}
|
|
263
|
+
// Extract the numeric value
|
|
264
|
+
let numericValue;
|
|
265
|
+
if (match[2] !== undefined) {
|
|
266
|
+
// F{n} format
|
|
267
|
+
numericValue = parseInt(match[2], 10);
|
|
268
|
+
}
|
|
269
|
+
else if (match[3] !== undefined) {
|
|
270
|
+
// G{n} format
|
|
271
|
+
numericValue = parseInt(match[3], 10);
|
|
272
|
+
}
|
|
273
|
+
else if (match[4] !== undefined) {
|
|
274
|
+
// Plain number
|
|
275
|
+
numericValue = parseInt(match[4], 10);
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
return generation; // Shouldn't reach here
|
|
279
|
+
}
|
|
280
|
+
switch (format) {
|
|
281
|
+
case 'preserve':
|
|
282
|
+
return trimmed;
|
|
283
|
+
case 'filial':
|
|
284
|
+
return `F${numericValue}`;
|
|
285
|
+
case 'numeric':
|
|
286
|
+
return String(numericValue);
|
|
287
|
+
default:
|
|
288
|
+
return generation;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Check if a generation string is valid.
|
|
293
|
+
*
|
|
294
|
+
* @param generation - The generation string to validate
|
|
295
|
+
* @returns True if valid, false otherwise
|
|
296
|
+
*
|
|
297
|
+
* @since 1.2.0
|
|
298
|
+
*/
|
|
299
|
+
export function isValidGeneration(generation) {
|
|
300
|
+
const trimmed = generation.trim().toUpperCase();
|
|
301
|
+
return GENERATION_PARSE_PATTERN.test(trimmed);
|
|
302
|
+
}
|
|
47
303
|
// =============================================================================
|
|
48
304
|
// TYPE GUARDS
|
|
49
305
|
// =============================================================================
|