@fgv/ts-extras 5.0.2 → 5.1.0-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/index.browser.js +6 -2
- package/dist/index.js +5 -1
- package/dist/packlets/ai-assist/apiClient.js +484 -0
- package/dist/packlets/ai-assist/converters.js +121 -0
- package/dist/packlets/ai-assist/index.js +10 -0
- package/dist/packlets/ai-assist/model.js +90 -0
- package/dist/packlets/ai-assist/registry.js +145 -0
- package/dist/packlets/ai-assist/toolFormats.js +160 -0
- package/dist/packlets/crypto-utils/constants.js +48 -0
- package/dist/packlets/crypto-utils/converters.js +155 -0
- package/dist/packlets/crypto-utils/directEncryptionProvider.js +86 -0
- package/dist/packlets/crypto-utils/encryptedFile.js +161 -0
- package/dist/packlets/crypto-utils/index.browser.js +41 -0
- package/dist/packlets/crypto-utils/index.js +41 -0
- package/dist/packlets/crypto-utils/keystore/converters.js +84 -0
- package/dist/packlets/crypto-utils/keystore/index.js +31 -0
- package/dist/packlets/crypto-utils/keystore/keyStore.js +758 -0
- package/dist/packlets/crypto-utils/keystore/model.js +64 -0
- package/dist/packlets/crypto-utils/model.js +39 -0
- package/dist/packlets/crypto-utils/nodeCryptoProvider.js +159 -0
- package/dist/packlets/experimental/formatter.js +1 -1
- package/dist/packlets/mustache/index.js +23 -0
- package/dist/packlets/mustache/interfaces.js +25 -0
- package/dist/packlets/mustache/mustacheTemplate.js +242 -0
- package/dist/packlets/record-jar/recordJarHelpers.js +1 -1
- package/dist/packlets/yaml/converters.js +46 -0
- package/dist/packlets/yaml/index.js +23 -0
- package/dist/packlets/zip-file-tree/index.js +1 -0
- package/dist/packlets/zip-file-tree/zipFileTreeAccessors.js +43 -2
- package/dist/packlets/zip-file-tree/zipFileTreeWriter.js +40 -0
- package/dist/ts-extras.d.ts +1990 -112
- package/dist/tsdoc-metadata.json +1 -1
- package/lib/index.browser.d.ts +3 -1
- package/lib/index.browser.js +6 -1
- package/lib/index.d.ts +5 -1
- package/lib/index.js +9 -1
- package/lib/packlets/ai-assist/apiClient.d.ts +60 -0
- package/lib/packlets/ai-assist/apiClient.js +488 -0
- package/lib/packlets/ai-assist/converters.d.ts +55 -0
- package/lib/packlets/ai-assist/converters.js +124 -0
- package/lib/packlets/ai-assist/index.d.ts +10 -0
- package/lib/packlets/ai-assist/index.js +33 -0
- package/lib/packlets/ai-assist/model.d.ts +222 -0
- package/lib/packlets/ai-assist/model.js +95 -0
- package/lib/packlets/ai-assist/registry.d.ts +25 -0
- package/lib/packlets/ai-assist/registry.js +150 -0
- package/lib/packlets/ai-assist/toolFormats.d.ts +44 -0
- package/lib/packlets/ai-assist/toolFormats.js +166 -0
- package/lib/packlets/crypto-utils/constants.d.ts +26 -0
- package/lib/packlets/crypto-utils/constants.js +51 -0
- package/lib/packlets/crypto-utils/converters.d.ts +58 -0
- package/lib/packlets/crypto-utils/converters.js +192 -0
- package/lib/packlets/crypto-utils/directEncryptionProvider.d.ts +69 -0
- package/lib/packlets/crypto-utils/directEncryptionProvider.js +90 -0
- package/lib/packlets/crypto-utils/encryptedFile.d.ts +88 -0
- package/lib/packlets/crypto-utils/encryptedFile.js +201 -0
- package/lib/packlets/crypto-utils/index.browser.d.ts +14 -0
- package/lib/packlets/crypto-utils/index.browser.js +91 -0
- package/lib/packlets/crypto-utils/index.d.ts +15 -0
- package/lib/packlets/crypto-utils/index.js +88 -0
- package/lib/packlets/crypto-utils/keystore/converters.d.ts +29 -0
- package/lib/packlets/crypto-utils/keystore/converters.js +87 -0
- package/lib/packlets/crypto-utils/keystore/index.d.ts +9 -0
- package/lib/packlets/crypto-utils/keystore/index.js +71 -0
- package/lib/packlets/crypto-utils/keystore/keyStore.d.ts +239 -0
- package/lib/packlets/crypto-utils/keystore/keyStore.js +795 -0
- package/lib/packlets/crypto-utils/keystore/model.d.ts +245 -0
- package/lib/packlets/crypto-utils/keystore/model.js +68 -0
- package/lib/packlets/crypto-utils/model.d.ts +236 -0
- package/lib/packlets/crypto-utils/model.js +76 -0
- package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts +62 -0
- package/lib/packlets/crypto-utils/nodeCryptoProvider.js +196 -0
- package/lib/packlets/experimental/formatter.d.ts +1 -1
- package/lib/packlets/experimental/formatter.js +1 -1
- package/lib/packlets/mustache/index.d.ts +3 -0
- package/lib/packlets/mustache/index.js +27 -0
- package/lib/packlets/mustache/interfaces.d.ts +97 -0
- package/lib/packlets/mustache/interfaces.js +26 -0
- package/lib/packlets/mustache/mustacheTemplate.d.ts +76 -0
- package/lib/packlets/mustache/mustacheTemplate.js +249 -0
- package/lib/packlets/record-jar/recordJarHelpers.js +1 -1
- package/lib/packlets/yaml/converters.d.ts +9 -0
- package/lib/packlets/yaml/converters.js +82 -0
- package/lib/packlets/yaml/index.d.ts +2 -0
- package/lib/packlets/yaml/index.js +39 -0
- package/lib/packlets/zip-file-tree/index.d.ts +1 -0
- package/lib/packlets/zip-file-tree/index.js +15 -0
- package/lib/packlets/zip-file-tree/zipFileTreeAccessors.d.ts +31 -2
- package/lib/packlets/zip-file-tree/zipFileTreeAccessors.js +42 -1
- package/lib/packlets/zip-file-tree/zipFileTreeWriter.d.ts +27 -0
- package/lib/packlets/zip-file-tree/zipFileTreeWriter.js +43 -0
- package/package.json +37 -18
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Copyright (c) 2026 Erik Fortune
|
|
2
|
+
//
|
|
3
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
// in the Software without restriction, including without limitation the rights
|
|
6
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
// furnished to do so, subject to the following conditions:
|
|
9
|
+
//
|
|
10
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
// copies or substantial portions of the Software.
|
|
12
|
+
//
|
|
13
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
// SOFTWARE.
|
|
20
|
+
/**
|
|
21
|
+
* Current format version constant.
|
|
22
|
+
* @public
|
|
23
|
+
*/
|
|
24
|
+
export const KEYSTORE_FORMAT = 'keystore-v1';
|
|
25
|
+
/**
|
|
26
|
+
* Default PBKDF2 iterations for key store encryption.
|
|
27
|
+
* Higher than regular files since this protects the master key vault.
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
30
|
+
export const DEFAULT_KEYSTORE_ITERATIONS = 600000;
|
|
31
|
+
/**
|
|
32
|
+
* Minimum salt length for key derivation.
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
35
|
+
export const MIN_SALT_LENGTH = 16;
|
|
36
|
+
/**
|
|
37
|
+
* All valid key store secret types.
|
|
38
|
+
* @public
|
|
39
|
+
*/
|
|
40
|
+
export const allKeyStoreSecretTypes = ['encryption-key', 'api-key'];
|
|
41
|
+
/**
|
|
42
|
+
* Default PBKDF2 iterations for secret-level key derivation.
|
|
43
|
+
* Lower than keystore encryption since these are used more frequently.
|
|
44
|
+
* @public
|
|
45
|
+
*/
|
|
46
|
+
export const DEFAULT_SECRET_ITERATIONS = 350000;
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// Detection Helper
|
|
49
|
+
// ============================================================================
|
|
50
|
+
/**
|
|
51
|
+
* Checks if a JSON object appears to be a key store file.
|
|
52
|
+
* Uses the format field as a discriminator.
|
|
53
|
+
* @param json - JSON object to check
|
|
54
|
+
* @returns true if the object has the key store format field
|
|
55
|
+
* @public
|
|
56
|
+
*/
|
|
57
|
+
export function isKeyStoreFile(json) {
|
|
58
|
+
if (typeof json !== 'object' || json === null) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
const obj = json;
|
|
62
|
+
return obj.format === KEYSTORE_FORMAT;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Copyright (c) 2024 Erik Fortune
|
|
2
|
+
//
|
|
3
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
// in the Software without restriction, including without limitation the rights
|
|
6
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
// furnished to do so, subject to the following conditions:
|
|
9
|
+
//
|
|
10
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
// copies or substantial portions of the Software.
|
|
12
|
+
//
|
|
13
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
// SOFTWARE.
|
|
20
|
+
import * as Constants from './constants';
|
|
21
|
+
export { Constants };
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Detection Helper
|
|
24
|
+
// ============================================================================
|
|
25
|
+
/**
|
|
26
|
+
* Checks if a JSON object appears to be an encrypted file.
|
|
27
|
+
* Uses the format field as a discriminator.
|
|
28
|
+
* @param json - JSON object to check
|
|
29
|
+
* @returns true if the object has the encrypted file format field
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
export function isEncryptedFile(json) {
|
|
33
|
+
if (typeof json !== 'object' || json === null) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
const obj = json;
|
|
37
|
+
return obj.format === Constants.ENCRYPTED_FILE_FORMAT;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// Copyright (c) 2024 Erik Fortune
|
|
2
|
+
//
|
|
3
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
// in the Software without restriction, including without limitation the rights
|
|
6
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
// furnished to do so, subject to the following conditions:
|
|
9
|
+
//
|
|
10
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
// copies or substantial portions of the Software.
|
|
12
|
+
//
|
|
13
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
// SOFTWARE.
|
|
20
|
+
import * as crypto from 'crypto';
|
|
21
|
+
import { captureResult, fail, Failure, succeed, Success } from '@fgv/ts-utils';
|
|
22
|
+
import * as Constants from './constants';
|
|
23
|
+
/**
|
|
24
|
+
* Node.js implementation of {@link CryptoUtils.ICryptoProvider} using the built-in crypto module.
|
|
25
|
+
* Uses AES-256-GCM for authenticated encryption.
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export class NodeCryptoProvider {
|
|
29
|
+
/**
|
|
30
|
+
* Encrypts plaintext using AES-256-GCM.
|
|
31
|
+
* @param plaintext - UTF-8 string to encrypt
|
|
32
|
+
* @param key - 32-byte encryption key
|
|
33
|
+
* @returns `Success` with encryption result, or `Failure` with an error.
|
|
34
|
+
*/
|
|
35
|
+
async encrypt(plaintext, key) {
|
|
36
|
+
return captureResult(() => {
|
|
37
|
+
if (key.length !== Constants.AES_256_KEY_SIZE) {
|
|
38
|
+
throw new Error(`Key must be ${Constants.AES_256_KEY_SIZE} bytes, got ${key.length}`);
|
|
39
|
+
}
|
|
40
|
+
// Generate random IV
|
|
41
|
+
const iv = crypto.randomBytes(Constants.GCM_IV_SIZE);
|
|
42
|
+
// Create cipher
|
|
43
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
|
44
|
+
// Encrypt
|
|
45
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
46
|
+
// Get auth tag
|
|
47
|
+
const authTag = cipher.getAuthTag();
|
|
48
|
+
return {
|
|
49
|
+
iv: new Uint8Array(iv),
|
|
50
|
+
authTag: new Uint8Array(authTag),
|
|
51
|
+
encryptedData: new Uint8Array(encrypted)
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Decrypts ciphertext using AES-256-GCM.
|
|
57
|
+
* @param encryptedData - Encrypted bytes
|
|
58
|
+
* @param key - 32-byte decryption key
|
|
59
|
+
* @param iv - Initialization vector (12 bytes)
|
|
60
|
+
* @param authTag - GCM authentication tag (16 bytes)
|
|
61
|
+
* @returns `Success` with decrypted UTF-8 string, or `Failure` with an error.
|
|
62
|
+
*/
|
|
63
|
+
async decrypt(encryptedData, key, iv, authTag) {
|
|
64
|
+
if (key.length !== Constants.AES_256_KEY_SIZE) {
|
|
65
|
+
return fail(`Key must be ${Constants.AES_256_KEY_SIZE} bytes, got ${key.length}`);
|
|
66
|
+
}
|
|
67
|
+
if (iv.length !== Constants.GCM_IV_SIZE) {
|
|
68
|
+
return fail(`IV must be ${Constants.GCM_IV_SIZE} bytes, got ${iv.length}`);
|
|
69
|
+
}
|
|
70
|
+
if (authTag.length !== Constants.GCM_AUTH_TAG_SIZE) {
|
|
71
|
+
return fail(`Auth tag must be ${Constants.GCM_AUTH_TAG_SIZE} bytes, got ${authTag.length}`);
|
|
72
|
+
}
|
|
73
|
+
return captureResult(() => {
|
|
74
|
+
// Create decipher
|
|
75
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', Buffer.from(key), Buffer.from(iv));
|
|
76
|
+
// Set auth tag
|
|
77
|
+
decipher.setAuthTag(Buffer.from(authTag));
|
|
78
|
+
// Decrypt
|
|
79
|
+
const decrypted = Buffer.concat([decipher.update(Buffer.from(encryptedData)), decipher.final()]);
|
|
80
|
+
return decrypted.toString('utf8');
|
|
81
|
+
}).withErrorFormat((e) => `Decryption failed: ${e}`);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Generates a random 32-byte key suitable for AES-256.
|
|
85
|
+
* @returns `Success` with generated key, or `Failure` with an error.
|
|
86
|
+
*/
|
|
87
|
+
async generateKey() {
|
|
88
|
+
return captureResult(() => {
|
|
89
|
+
const key = crypto.randomBytes(Constants.AES_256_KEY_SIZE);
|
|
90
|
+
return new Uint8Array(key);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Derives a key from a password using PBKDF2.
|
|
95
|
+
* @param password - Password string
|
|
96
|
+
* @param salt - Salt bytes (should be at least 16 bytes)
|
|
97
|
+
* @param iterations - Number of iterations (recommend 100000+)
|
|
98
|
+
* @returns `Success` with derived 32-byte key, or `Failure` with an error.
|
|
99
|
+
*/
|
|
100
|
+
async deriveKey(password, salt, iterations) {
|
|
101
|
+
if (iterations < 1) {
|
|
102
|
+
return fail('Iterations must be at least 1');
|
|
103
|
+
}
|
|
104
|
+
if (salt.length < 8) {
|
|
105
|
+
return fail('Salt should be at least 8 bytes');
|
|
106
|
+
}
|
|
107
|
+
return new Promise((resolve) => {
|
|
108
|
+
crypto.pbkdf2(password, Buffer.from(salt), iterations, Constants.AES_256_KEY_SIZE, 'sha256', (err, derivedKey) => {
|
|
109
|
+
/* c8 ignore next 3 - PBKDF2 internal errors are hard to trigger with valid parameters */
|
|
110
|
+
if (err) {
|
|
111
|
+
resolve(fail(`Key derivation failed: ${err.message}`));
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
resolve(succeed(new Uint8Array(derivedKey)));
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
// ============================================================================
|
|
120
|
+
// Platform Utility Methods
|
|
121
|
+
// ============================================================================
|
|
122
|
+
/**
|
|
123
|
+
* Generates cryptographically secure random bytes.
|
|
124
|
+
* @param length - Number of bytes to generate
|
|
125
|
+
* @returns Success with random bytes, or Failure with error
|
|
126
|
+
*/
|
|
127
|
+
generateRandomBytes(length) {
|
|
128
|
+
if (length < 1) {
|
|
129
|
+
return Failure.with('Length must be at least 1');
|
|
130
|
+
}
|
|
131
|
+
return captureResult(() => new Uint8Array(crypto.randomBytes(length)));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Encodes binary data to base64 string.
|
|
135
|
+
* @param data - Binary data to encode
|
|
136
|
+
* @returns Base64-encoded string
|
|
137
|
+
*/
|
|
138
|
+
toBase64(data) {
|
|
139
|
+
return Buffer.from(data).toString('base64');
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Decodes base64 string to binary data.
|
|
143
|
+
* @param base64 - Base64-encoded string
|
|
144
|
+
* @returns Success with decoded bytes, or Failure if invalid base64
|
|
145
|
+
*/
|
|
146
|
+
fromBase64(base64) {
|
|
147
|
+
// Check for obviously invalid characters
|
|
148
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(base64)) {
|
|
149
|
+
return Failure.with('Invalid base64 string');
|
|
150
|
+
}
|
|
151
|
+
return Success.with(new Uint8Array(Buffer.from(base64, 'base64')));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Singleton instance of {@link CryptoUtils.NodeCryptoProvider}.
|
|
156
|
+
* @public
|
|
157
|
+
*/
|
|
158
|
+
export const nodeCryptoProvider = new NodeCryptoProvider();
|
|
159
|
+
//# sourceMappingURL=nodeCryptoProvider.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2020 Erik Fortune
|
|
3
|
+
*
|
|
4
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
* in the Software without restriction, including without limitation the rights
|
|
7
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
* furnished to do so, subject to the following conditions:
|
|
10
|
+
*
|
|
11
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
* copies or substantial portions of the Software.
|
|
13
|
+
*
|
|
14
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
* SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
export { MustacheTemplate } from './mustacheTemplate';
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/* c8 ignore start - Type definitions only, no runtime code */
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2020 Erik Fortune
|
|
4
|
+
*
|
|
5
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
* in the Software without restriction, including without limitation the rights
|
|
8
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
* furnished to do so, subject to the following conditions:
|
|
11
|
+
*
|
|
12
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
* copies or substantial portions of the Software.
|
|
14
|
+
*
|
|
15
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
* SOFTWARE.
|
|
22
|
+
*/
|
|
23
|
+
export {};
|
|
24
|
+
/* c8 ignore stop */
|
|
25
|
+
//# sourceMappingURL=interfaces.js.map
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2020 Erik Fortune
|
|
3
|
+
*
|
|
4
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
* in the Software without restriction, including without limitation the rights
|
|
7
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
* furnished to do so, subject to the following conditions:
|
|
10
|
+
*
|
|
11
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
* copies or substantial portions of the Software.
|
|
13
|
+
*
|
|
14
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
* SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
import { captureResult, fail, succeed } from '@fgv/ts-utils';
|
|
23
|
+
import Mustache from 'mustache';
|
|
24
|
+
/**
|
|
25
|
+
* Default options for MustacheTemplate
|
|
26
|
+
*/
|
|
27
|
+
const DEFAULT_OPTIONS = {
|
|
28
|
+
tags: ['{{', '}}'],
|
|
29
|
+
includeComments: false,
|
|
30
|
+
includePartials: false
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* A helper class for working with Mustache templates that provides
|
|
34
|
+
* validation, variable extraction, and context validation utilities.
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
export class MustacheTemplate {
|
|
38
|
+
constructor(template, tokens, options) {
|
|
39
|
+
this.template = template;
|
|
40
|
+
this._tokens = tokens;
|
|
41
|
+
this.options = options;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Creates a new MustacheTemplate instance.
|
|
45
|
+
* @param template - The Mustache template string to parse
|
|
46
|
+
* @param options - Optional parsing options
|
|
47
|
+
* @returns Success with the template instance, or Failure if parsing fails
|
|
48
|
+
*/
|
|
49
|
+
static create(template, options) {
|
|
50
|
+
const resolvedOptions = MustacheTemplate._resolveOptions(options);
|
|
51
|
+
return MustacheTemplate._parseTokens(template, resolvedOptions).onSuccess((tokens) => {
|
|
52
|
+
return succeed(new MustacheTemplate(template, tokens, resolvedOptions));
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Validates that a template string has valid Mustache syntax.
|
|
57
|
+
* @param template - The template string to validate
|
|
58
|
+
* @param options - Optional parsing options
|
|
59
|
+
* @returns Success with true if valid, or Failure with a descriptive error message
|
|
60
|
+
*/
|
|
61
|
+
static validate(template, options) {
|
|
62
|
+
const resolvedOptions = MustacheTemplate._resolveOptions(options);
|
|
63
|
+
return MustacheTemplate._parseTokens(template, resolvedOptions).onSuccess(() => succeed(true));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Checks if this template instance has valid syntax.
|
|
67
|
+
* Always returns Success(true) since parsing succeeded in create().
|
|
68
|
+
* @returns Success with true
|
|
69
|
+
*/
|
|
70
|
+
validate() {
|
|
71
|
+
return succeed(true);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Extracts all variable references from the template.
|
|
75
|
+
* @returns An array of variable references found in the template
|
|
76
|
+
*/
|
|
77
|
+
extractVariables() {
|
|
78
|
+
if (this._variables === undefined) {
|
|
79
|
+
this._variables = this._extractVariablesFromTokens(this._tokens);
|
|
80
|
+
}
|
|
81
|
+
return this._variables;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Extracts unique variable names from the template.
|
|
85
|
+
* @returns An array of unique variable name strings (e.g., ['user.name', 'items'])
|
|
86
|
+
*/
|
|
87
|
+
extractVariableNames() {
|
|
88
|
+
const variables = this.extractVariables();
|
|
89
|
+
const seen = new Set();
|
|
90
|
+
const names = [];
|
|
91
|
+
for (const variable of variables) {
|
|
92
|
+
if (!seen.has(variable.name)) {
|
|
93
|
+
seen.add(variable.name);
|
|
94
|
+
names.push(variable.name);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return names;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Validates that a context object has all required variables.
|
|
101
|
+
* @param context - The context object to validate
|
|
102
|
+
* @returns Success with validation result containing details about present/missing variables
|
|
103
|
+
*/
|
|
104
|
+
validateContext(context) {
|
|
105
|
+
const variables = this.extractVariables();
|
|
106
|
+
const presentVariables = [];
|
|
107
|
+
const missingVariables = [];
|
|
108
|
+
const missingDetails = [];
|
|
109
|
+
const checked = new Set();
|
|
110
|
+
for (const variable of variables) {
|
|
111
|
+
if (checked.has(variable.name)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
checked.add(variable.name);
|
|
115
|
+
const lookup = this._lookupPath(context, variable.path);
|
|
116
|
+
if (lookup.found) {
|
|
117
|
+
presentVariables.push(variable.name);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
missingVariables.push(variable.name);
|
|
121
|
+
missingDetails.push({
|
|
122
|
+
variable,
|
|
123
|
+
failedAtSegment: lookup.failedAt,
|
|
124
|
+
existingPath: lookup.existingPath
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return succeed({
|
|
129
|
+
isValid: missingVariables.length === 0,
|
|
130
|
+
presentVariables,
|
|
131
|
+
missingVariables,
|
|
132
|
+
missingDetails
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Renders the template with the given context.
|
|
137
|
+
* Use this for pre-validated contexts where you've already checked
|
|
138
|
+
* that all required variables are present.
|
|
139
|
+
* @param context - The context object for template rendering
|
|
140
|
+
* @returns Success with the rendered string, or Failure if rendering fails
|
|
141
|
+
*/
|
|
142
|
+
render(context) {
|
|
143
|
+
return captureResult(() => Mustache.render(this.template, context));
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Validates the context and renders the template if validation passes.
|
|
147
|
+
* @param context - The context object to validate and render with
|
|
148
|
+
* @returns Success with the rendered string, or Failure with validation or render errors
|
|
149
|
+
*/
|
|
150
|
+
validateAndRender(context) {
|
|
151
|
+
return this.validateContext(context).onSuccess((validation) => {
|
|
152
|
+
if (!validation.isValid) {
|
|
153
|
+
const missing = validation.missingVariables.join(', ');
|
|
154
|
+
return fail(`Missing required variables: ${missing}`);
|
|
155
|
+
}
|
|
156
|
+
return this.render(context);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
static _resolveOptions(options) {
|
|
160
|
+
var _a, _b;
|
|
161
|
+
if (options === undefined) {
|
|
162
|
+
return DEFAULT_OPTIONS;
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
tags: options.tags ? [options.tags[0], options.tags[1]] : DEFAULT_OPTIONS.tags,
|
|
166
|
+
includeComments: (_a = options.includeComments) !== null && _a !== void 0 ? _a : DEFAULT_OPTIONS.includeComments,
|
|
167
|
+
includePartials: (_b = options.includePartials) !== null && _b !== void 0 ? _b : DEFAULT_OPTIONS.includePartials
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
static _parseTokens(template, options) {
|
|
171
|
+
return captureResult(() => Mustache.parse(template, options.tags));
|
|
172
|
+
}
|
|
173
|
+
_extractVariablesFromTokens(tokens) {
|
|
174
|
+
const variables = [];
|
|
175
|
+
for (const token of tokens) {
|
|
176
|
+
const type = token[0];
|
|
177
|
+
const value = token[1];
|
|
178
|
+
// Skip text tokens
|
|
179
|
+
if (type === 'text') {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
// Handle comments based on options
|
|
183
|
+
if (type === '!' && !this.options.includeComments) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
// Handle partials based on options
|
|
187
|
+
if (type === '>' && !this.options.includePartials) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
// Handle variable tokens: 'name', '&', '#', '^', and optionally '!', '>'
|
|
191
|
+
if (type === 'name' || type === '&' || type === '#' || type === '^' || type === '!' || type === '>') {
|
|
192
|
+
const isSection = type === '#' || type === '^';
|
|
193
|
+
variables.push({
|
|
194
|
+
name: value,
|
|
195
|
+
path: this._parsePath(value),
|
|
196
|
+
tokenType: type,
|
|
197
|
+
isSection
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
// Recursively extract from nested tokens in sections
|
|
201
|
+
if ((type === '#' || type === '^') && token.length > 4) {
|
|
202
|
+
const nestedTokens = token[4];
|
|
203
|
+
if (nestedTokens) {
|
|
204
|
+
variables.push(...this._extractVariablesFromTokens(nestedTokens));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return variables;
|
|
209
|
+
}
|
|
210
|
+
_parsePath(name) {
|
|
211
|
+
// Handle special case of '.' which means current context
|
|
212
|
+
if (name === '.') {
|
|
213
|
+
return ['.'];
|
|
214
|
+
}
|
|
215
|
+
// Split on dots to get path segments
|
|
216
|
+
return name.split('.').filter((segment) => segment.length > 0);
|
|
217
|
+
}
|
|
218
|
+
_lookupPath(context, path) {
|
|
219
|
+
// Handle '.' which always exists if context is not null/undefined
|
|
220
|
+
if (path.length === 1 && path[0] === '.') {
|
|
221
|
+
return { found: context !== undefined && context !== null, existingPath: [] };
|
|
222
|
+
}
|
|
223
|
+
let current = context;
|
|
224
|
+
const existingPath = [];
|
|
225
|
+
for (const segment of path) {
|
|
226
|
+
if (current === undefined || current === null) {
|
|
227
|
+
return { found: false, existingPath, failedAt: segment };
|
|
228
|
+
}
|
|
229
|
+
if (typeof current !== 'object') {
|
|
230
|
+
return { found: false, existingPath, failedAt: segment };
|
|
231
|
+
}
|
|
232
|
+
const obj = current;
|
|
233
|
+
if (!(segment in obj)) {
|
|
234
|
+
return { found: false, existingPath, failedAt: segment };
|
|
235
|
+
}
|
|
236
|
+
current = obj[segment];
|
|
237
|
+
existingPath.push(segment);
|
|
238
|
+
}
|
|
239
|
+
return { found: true, existingPath };
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=mustacheTemplate.js.map
|
|
@@ -150,7 +150,7 @@ class RecordParser {
|
|
|
150
150
|
var _a, _b;
|
|
151
151
|
let trimmed = line.trim();
|
|
152
152
|
if (!this._body.isContinuation) {
|
|
153
|
-
/* c8 ignore next */
|
|
153
|
+
/* c8 ignore next - functional code tested but coverage intermittently missed */
|
|
154
154
|
const fixedSize = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.fixedContinuationSize) !== null && _b !== void 0 ? _b : 0;
|
|
155
155
|
if (fixedSize > 0) {
|
|
156
156
|
if (trimmed.length < line.length - fixedSize) {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2024 Erik Fortune
|
|
3
|
+
*
|
|
4
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
* in the Software without restriction, including without limitation the rights
|
|
7
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
* furnished to do so, subject to the following conditions:
|
|
10
|
+
*
|
|
11
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
* copies or substantial portions of the Software.
|
|
13
|
+
*
|
|
14
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
* SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
import { Conversion, captureResult, fail } from '@fgv/ts-utils';
|
|
23
|
+
import * as yaml from 'js-yaml';
|
|
24
|
+
/**
|
|
25
|
+
* Creates a converter that parses YAML string content and then applies the supplied converter.
|
|
26
|
+
* @param converter - Converter to apply to the parsed YAML
|
|
27
|
+
* @returns Converter that parses YAML then validates
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
30
|
+
export function yamlConverter(converter) {
|
|
31
|
+
return new Conversion.BaseConverter((from) => {
|
|
32
|
+
if (typeof from !== 'string') {
|
|
33
|
+
return fail('Input must be a string');
|
|
34
|
+
}
|
|
35
|
+
const parseResult = captureResult(() => yaml.load(from));
|
|
36
|
+
if (parseResult.isFailure()) {
|
|
37
|
+
return fail(`Failed to parse YAML: ${parseResult.message}`);
|
|
38
|
+
}
|
|
39
|
+
const parsed = parseResult.value;
|
|
40
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
41
|
+
return fail('Failed to parse YAML: YAML content must be an object');
|
|
42
|
+
}
|
|
43
|
+
return converter.convert(parsed);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=converters.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2024 Erik Fortune
|
|
3
|
+
*
|
|
4
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
* in the Software without restriction, including without limitation the rights
|
|
7
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
* furnished to do so, subject to the following conditions:
|
|
10
|
+
*
|
|
11
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
* copies or substantial portions of the Software.
|
|
13
|
+
*
|
|
14
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
* SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
export * from './converters';
|
|
23
|
+
//# sourceMappingURL=index.js.map
|