@fgv/ts-extras 5.0.2 → 5.1.0-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.
Files changed (92) hide show
  1. package/dist/index.browser.js +6 -2
  2. package/dist/index.js +5 -1
  3. package/dist/packlets/ai-assist/apiClient.js +484 -0
  4. package/dist/packlets/ai-assist/converters.js +121 -0
  5. package/dist/packlets/ai-assist/index.js +10 -0
  6. package/dist/packlets/ai-assist/model.js +90 -0
  7. package/dist/packlets/ai-assist/registry.js +145 -0
  8. package/dist/packlets/ai-assist/toolFormats.js +160 -0
  9. package/dist/packlets/crypto-utils/constants.js +48 -0
  10. package/dist/packlets/crypto-utils/converters.js +155 -0
  11. package/dist/packlets/crypto-utils/directEncryptionProvider.js +86 -0
  12. package/dist/packlets/crypto-utils/encryptedFile.js +161 -0
  13. package/dist/packlets/crypto-utils/index.browser.js +41 -0
  14. package/dist/packlets/crypto-utils/index.js +41 -0
  15. package/dist/packlets/crypto-utils/keystore/converters.js +84 -0
  16. package/dist/packlets/crypto-utils/keystore/index.js +31 -0
  17. package/dist/packlets/crypto-utils/keystore/keyStore.js +758 -0
  18. package/dist/packlets/crypto-utils/keystore/model.js +64 -0
  19. package/dist/packlets/crypto-utils/model.js +39 -0
  20. package/dist/packlets/crypto-utils/nodeCryptoProvider.js +159 -0
  21. package/dist/packlets/experimental/formatter.js +1 -1
  22. package/dist/packlets/mustache/index.js +23 -0
  23. package/dist/packlets/mustache/interfaces.js +25 -0
  24. package/dist/packlets/mustache/mustacheTemplate.js +242 -0
  25. package/dist/packlets/record-jar/recordJarHelpers.js +1 -1
  26. package/dist/packlets/yaml/converters.js +46 -0
  27. package/dist/packlets/yaml/index.js +23 -0
  28. package/dist/packlets/zip-file-tree/index.js +1 -0
  29. package/dist/packlets/zip-file-tree/zipFileTreeAccessors.js +6 -2
  30. package/dist/packlets/zip-file-tree/zipFileTreeWriter.js +40 -0
  31. package/dist/ts-extras.d.ts +1964 -112
  32. package/dist/tsdoc-metadata.json +1 -1
  33. package/lib/index.browser.d.ts +3 -1
  34. package/lib/index.browser.js +6 -1
  35. package/lib/index.d.ts +5 -1
  36. package/lib/index.js +9 -1
  37. package/lib/packlets/ai-assist/apiClient.d.ts +60 -0
  38. package/lib/packlets/ai-assist/apiClient.js +488 -0
  39. package/lib/packlets/ai-assist/converters.d.ts +55 -0
  40. package/lib/packlets/ai-assist/converters.js +124 -0
  41. package/lib/packlets/ai-assist/index.d.ts +10 -0
  42. package/lib/packlets/ai-assist/index.js +33 -0
  43. package/lib/packlets/ai-assist/model.d.ts +222 -0
  44. package/lib/packlets/ai-assist/model.js +95 -0
  45. package/lib/packlets/ai-assist/registry.d.ts +25 -0
  46. package/lib/packlets/ai-assist/registry.js +150 -0
  47. package/lib/packlets/ai-assist/toolFormats.d.ts +44 -0
  48. package/lib/packlets/ai-assist/toolFormats.js +166 -0
  49. package/lib/packlets/crypto-utils/constants.d.ts +26 -0
  50. package/lib/packlets/crypto-utils/constants.js +51 -0
  51. package/lib/packlets/crypto-utils/converters.d.ts +58 -0
  52. package/lib/packlets/crypto-utils/converters.js +192 -0
  53. package/lib/packlets/crypto-utils/directEncryptionProvider.d.ts +69 -0
  54. package/lib/packlets/crypto-utils/directEncryptionProvider.js +90 -0
  55. package/lib/packlets/crypto-utils/encryptedFile.d.ts +88 -0
  56. package/lib/packlets/crypto-utils/encryptedFile.js +201 -0
  57. package/lib/packlets/crypto-utils/index.browser.d.ts +14 -0
  58. package/lib/packlets/crypto-utils/index.browser.js +91 -0
  59. package/lib/packlets/crypto-utils/index.d.ts +15 -0
  60. package/lib/packlets/crypto-utils/index.js +88 -0
  61. package/lib/packlets/crypto-utils/keystore/converters.d.ts +29 -0
  62. package/lib/packlets/crypto-utils/keystore/converters.js +87 -0
  63. package/lib/packlets/crypto-utils/keystore/index.d.ts +9 -0
  64. package/lib/packlets/crypto-utils/keystore/index.js +71 -0
  65. package/lib/packlets/crypto-utils/keystore/keyStore.d.ts +239 -0
  66. package/lib/packlets/crypto-utils/keystore/keyStore.js +795 -0
  67. package/lib/packlets/crypto-utils/keystore/model.d.ts +245 -0
  68. package/lib/packlets/crypto-utils/keystore/model.js +68 -0
  69. package/lib/packlets/crypto-utils/model.d.ts +236 -0
  70. package/lib/packlets/crypto-utils/model.js +76 -0
  71. package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts +62 -0
  72. package/lib/packlets/crypto-utils/nodeCryptoProvider.js +196 -0
  73. package/lib/packlets/experimental/formatter.d.ts +1 -1
  74. package/lib/packlets/experimental/formatter.js +1 -1
  75. package/lib/packlets/mustache/index.d.ts +3 -0
  76. package/lib/packlets/mustache/index.js +27 -0
  77. package/lib/packlets/mustache/interfaces.d.ts +97 -0
  78. package/lib/packlets/mustache/interfaces.js +26 -0
  79. package/lib/packlets/mustache/mustacheTemplate.d.ts +76 -0
  80. package/lib/packlets/mustache/mustacheTemplate.js +249 -0
  81. package/lib/packlets/record-jar/recordJarHelpers.js +1 -1
  82. package/lib/packlets/yaml/converters.d.ts +9 -0
  83. package/lib/packlets/yaml/converters.js +82 -0
  84. package/lib/packlets/yaml/index.d.ts +2 -0
  85. package/lib/packlets/yaml/index.js +39 -0
  86. package/lib/packlets/zip-file-tree/index.d.ts +1 -0
  87. package/lib/packlets/zip-file-tree/index.js +15 -0
  88. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.d.ts +5 -1
  89. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.js +6 -2
  90. package/lib/packlets/zip-file-tree/zipFileTreeWriter.d.ts +27 -0
  91. package/lib/packlets/zip-file-tree/zipFileTreeWriter.js +43 -0
  92. package/package.json +50 -31
@@ -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
@@ -41,7 +41,7 @@ export class FormattableBase {
41
41
  }
42
42
  }
43
43
  /**
44
- * {@inheritdoc Experimental.Formattable.format}
44
+ * {@inheritDoc Experimental.Formattable.format}
45
45
  */
46
46
  format(template) {
47
47
  return captureResult(() => Mustache.render(template, this));
@@ -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
@@ -29,4 +29,5 @@
29
29
  * @packageDocumentation
30
30
  */
31
31
  export { ZipFileTreeAccessors, ZipFileItem, ZipDirectoryItem } from './zipFileTreeAccessors';
32
+ export * from './zipFileTreeWriter';
32
33
  //# sourceMappingURL=index.js.map