@stless/modify-js 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -107
- package/dist/index.min.js +3 -4
- package/dist/index.min.js.map +1 -1
- package/package.json +6 -4
- package/src/index.d.ts +75 -85
- package/src/index.js +100 -286
package/README.md
CHANGED
|
@@ -7,17 +7,37 @@
|
|
|
7
7
|
# @stless/modify-js
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/@stless/modify-js)
|
|
10
|
-

|
|
11
11
|
[](https://github.com/harnuma9/modify-js/blob/main/LICENSE)
|
|
12
12
|

|
|
13
|
-
[](https://badge.socket.dev/npm/package/@stless/modify-js/1.1.1)
|
|
14
14
|
[](https://harnuma9.github.io/donate/)
|
|
15
15
|
|
|
16
16
|
<br />
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
`@stless/modify-js` is a lightweight, zero-dependency utility that brings functional piping to JavaScript. It provides a secure, high-performance alternative to the proposed **[TC39 Pipeline Operator](https://github.com/tc39/proposal-pipeline-operator)**.
|
|
19
|
+
|
|
20
|
+
Values wrapped in a **Pipe container** can transform data through readable, linear pipelines. It is designed for **defense-in-depth**, utilizing modern JS features to reduce data exposure and ensure state integrity throughout the transformation lifecycle.
|
|
21
|
+
|
|
22
|
+
<br />
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
<br />
|
|
27
|
+
|
|
28
|
+
## ✨ Why `[modify](JS)`?
|
|
29
|
+
|
|
30
|
+
While standard JavaScript requires nested function calls or intermediate variables, [modify-js](https://www.npmjs.com/package/@stless/modify-js) enables a clean, top-down data flow with enhanced lifecycle management.
|
|
31
|
+
|
|
32
|
+
<br />
|
|
33
|
+
|
|
34
|
+
* **🛡️ Data Encapsulation**: Leverages **[ES2022 Private Class Fields](https://dev.to/smitterhane/private-class-fields-in-javascript-es2022-3b8)** (`#value`) to ensure internal state is inaccessible to external scripts, loggers, or enumeration.
|
|
35
|
+
|
|
36
|
+
* **🧼 Explicit Sanitization**: Prevents memory lingering by optionally wiping the internal state (`null`) and freezing the instance immediately upon extraction.
|
|
37
|
+
|
|
38
|
+
* **🎯 Environment Safety**: Operates as a standalone wrapper with **zero prototype pollution**, making it safe for sensitive production environments and legacy codebases.
|
|
39
|
+
|
|
40
|
+
* **⚡ GC-Friendly**: Optimized for the V8 engine with a single-allocation design that minimizes overhead during high-frequency transformations.
|
|
21
41
|
|
|
22
42
|
<br />
|
|
23
43
|
|
|
@@ -50,6 +70,14 @@ yarn add @stless/modify-js
|
|
|
50
70
|
pnpm add @stless/modify-js
|
|
51
71
|
```
|
|
52
72
|
|
|
73
|
+
<br />
|
|
74
|
+
|
|
75
|
+
### Requirements
|
|
76
|
+
|
|
77
|
+
* **Node.js**: `>=16.11.0` (For stable [ES2022 Private Field](https://dev.to/smitterhane/private-class-fields-in-javascript-es2022-3b8) support)
|
|
78
|
+
|
|
79
|
+
* **Browser**: Modern evergreen browsers (Chrome 91+, Firefox 90+, Safari 15+)
|
|
80
|
+
|
|
53
81
|
<br />
|
|
54
82
|
<br />
|
|
55
83
|
|
|
@@ -59,20 +87,19 @@ pnpm add @stless/modify-js
|
|
|
59
87
|
|
|
60
88
|
### Initialize the Environment
|
|
61
89
|
|
|
62
|
-
`
|
|
90
|
+
Use `chain_` (or the shorthand `chain$`) to start a pipeline. The data is immediately encapsulated in a private class field.
|
|
63
91
|
|
|
64
92
|
```javascript
|
|
65
93
|
// ESM
|
|
66
|
-
import
|
|
94
|
+
import chain_ from '@stless/modify-js';
|
|
67
95
|
// or CJS
|
|
68
|
-
const { default:
|
|
96
|
+
const { default: chain_, chain$ } = require('@stless/modify-js');
|
|
69
97
|
|
|
70
98
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
//
|
|
75
|
-
applyModify(discover(), __F0V0);
|
|
99
|
+
const result = chain_(" hello world ")
|
|
100
|
+
._p(s => s.trim())
|
|
101
|
+
.$p(s => s.toUpperCase())
|
|
102
|
+
.out(); // "HELLO WORLD"
|
|
76
103
|
```
|
|
77
104
|
|
|
78
105
|
<br />
|
|
@@ -82,56 +109,56 @@ applyModify(discover(), __F0V0);
|
|
|
82
109
|
Transform data from left-to-right. Use `._p()`, `.$p()`, or `.modify()` to chain functions.
|
|
83
110
|
|
|
84
111
|
```javascript
|
|
85
|
-
|
|
86
|
-
._p(s => s.trim())
|
|
87
|
-
._p(s => s.toUpperCase())
|
|
88
|
-
._p(s => s + "!!!")
|
|
89
|
-
.out();
|
|
112
|
+
import { Pipe } from '@stless/modify-js';
|
|
90
113
|
|
|
91
|
-
|
|
114
|
+
const user = new Pipe(apiResponse)
|
|
115
|
+
._p(res => res.data)
|
|
116
|
+
._p(data => ({ ...data, timestamp: Date.now() }))
|
|
117
|
+
.out({ error: "No data found" }); // Safe fallback
|
|
92
118
|
```
|
|
93
119
|
|
|
94
120
|
<br />
|
|
95
121
|
|
|
96
|
-
### Tapping
|
|
122
|
+
### Tapping & Side Effects
|
|
97
123
|
|
|
98
|
-
You can
|
|
124
|
+
You can “tap” into the pipe mid-chain for logging or cleanup without breaking the flow.
|
|
99
125
|
|
|
100
126
|
```javascript
|
|
101
|
-
const
|
|
102
|
-
._p(
|
|
103
|
-
|
|
104
|
-
._p(
|
|
105
|
-
._p(v => ({ value: v }))
|
|
106
|
-
.out();
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
<br />
|
|
127
|
+
const a = chain$(100)
|
|
128
|
+
._p(n => n * 2)
|
|
129
|
+
// log then return value
|
|
130
|
+
._p(v => (console.log(`Value is: ${v}`), v));
|
|
110
131
|
|
|
111
|
-
|
|
132
|
+
const val1 = a.out(null, false); // set to false
|
|
112
133
|
|
|
113
|
-
|
|
134
|
+
// Reuse the same chain
|
|
135
|
+
const val2 = a
|
|
136
|
+
.modify(n => n - 20)
|
|
137
|
+
.out(null, true); // lock the pipe
|
|
114
138
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const val = chain$("data")
|
|
119
|
-
.$p(d => d.split(""))
|
|
120
|
-
.out();
|
|
139
|
+
console.log(val1); // Output: 200
|
|
140
|
+
console.log(val2); // Output: 180
|
|
121
141
|
```
|
|
122
142
|
|
|
123
143
|
<br />
|
|
124
144
|
|
|
125
|
-
###
|
|
145
|
+
### The “Security Exit”
|
|
126
146
|
|
|
127
|
-
|
|
147
|
+
The `.out()` method is the termination point of the pipe. It is designed to handle extraction and cleanup as a single atomic-like operation.
|
|
128
148
|
|
|
129
149
|
```javascript
|
|
130
|
-
|
|
150
|
+
// Signature: .out(fallbackValue?, shouldLock=true)
|
|
131
151
|
|
|
132
|
-
|
|
152
|
+
const data = pipe.out("Unknown", true);
|
|
153
|
+
console.log(pipe.isLocked()); // true
|
|
133
154
|
```
|
|
134
155
|
|
|
156
|
+
* **Safe Fallback**: If the internal pipeline results in `null` or `undefined`, it returns your provided default value.
|
|
157
|
+
|
|
158
|
+
* **Wiping**: It clears the internal private state.
|
|
159
|
+
|
|
160
|
+
* **Locking**: It freezes the pipe object, making it unable to reuse.
|
|
161
|
+
|
|
135
162
|
<br />
|
|
136
163
|
|
|
137
164
|
### Shortcut one-liner
|
|
@@ -140,15 +167,13 @@ It is possible to achieve a functional code with almost no `const`, `let`, or `v
|
|
|
140
167
|
|
|
141
168
|
<br />
|
|
142
169
|
|
|
143
|
-
**Before:**
|
|
170
|
+
**Before (Imperative):**
|
|
144
171
|
|
|
145
172
|
```javascript
|
|
146
173
|
function encryptMsg(algo, data, key, iv) {
|
|
147
174
|
const cipher = crypto.createCipheriv(algo, key, iv);
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
const encrypted = Buffer.concat([part1, part2]);
|
|
151
|
-
key.fill(0);
|
|
175
|
+
const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
176
|
+
key.fill(0); // Sensitive cleanup
|
|
152
177
|
iv.fill(0);
|
|
153
178
|
return encrypted;
|
|
154
179
|
}
|
|
@@ -156,11 +181,11 @@ function encryptMsg(algo, data, key, iv) {
|
|
|
156
181
|
|
|
157
182
|
<br />
|
|
158
183
|
|
|
159
|
-
**After (
|
|
184
|
+
**After (With Hardened Pipe):**
|
|
160
185
|
|
|
161
186
|
```javascript
|
|
162
187
|
function encryptMsg(algo, data, key, iv) {
|
|
163
|
-
return [algo, key, iv]
|
|
188
|
+
return chain_([algo, key, iv])
|
|
164
189
|
._p(a => crypto.createCipheriv(...a)) // Create cipher
|
|
165
190
|
._p(c => Buffer.concat([c.update(data), c.final()])) // Process data
|
|
166
191
|
._p(enc => (key.fill(0), iv.fill(0), enc)) // Cleanup & return
|
|
@@ -184,64 +209,6 @@ function encryptMsg(algo, data, key, iv) {
|
|
|
184
209
|
<br />
|
|
185
210
|
<br />
|
|
186
211
|
|
|
187
|
-
## Advanced Injection Options
|
|
188
|
-
|
|
189
|
-
Control how `modify-js` handles collisions with existing methods using pre-defined shorthand constants:
|
|
190
|
-
|
|
191
|
-
| Constant | Force | Verbose | Description |
|
|
192
|
-
|--------------|--------------|--------------|--------------|
|
|
193
|
-
|`__F0V1` | `false` | `true` | **Standard:** Only patch new methods, log collisions. |
|
|
194
|
-
|`__F1V1` | `true` | `true` | **Debug:** Overwrite everything and log actions. |
|
|
195
|
-
|`__F0V0` | `false` | `false` | **Silent:** Patch if possible, no logs. |
|
|
196
|
-
|`__F1V0` | `true` | `false` | **Override:** Overwrite everything silently. |
|
|
197
|
-
|
|
198
|
-
<br />
|
|
199
|
-
<br />
|
|
200
|
-
|
|
201
|
-
## 🔩 API Reference
|
|
202
|
-
|
|
203
|
-
<br />
|
|
204
|
-
|
|
205
|
-
### `applyModify(targets, options)`
|
|
206
|
-
|
|
207
|
-
Injects pipeline methods into prototype chains.
|
|
208
|
-
|
|
209
|
-
* `targets`: `Array<Function>` - List of constructors (e.g. `[String, Array]`).
|
|
210
|
-
* `options`: `ModifyOptions` - `{ force: boolean, verbose: boolean }`.
|
|
211
|
-
|
|
212
|
-
<br />
|
|
213
|
-
|
|
214
|
-
### `discover()`
|
|
215
|
-
|
|
216
|
-
Reflectively scans `globalThis` for all [PascalCase](https://convertcase.help/blog/what-is-pascalcase/) functions with prototypes. Includes `Buffer` in Node environments.
|
|
217
|
-
|
|
218
|
-
<br />
|
|
219
|
-
|
|
220
|
-
### `hasBeenApplied()`
|
|
221
|
-
|
|
222
|
-
Returns a `boolean` indicating if the library has been initialized. Useful for conditional logic in environments with [hot-module reloading (HMR)](https://webpack.js.org/guides/hot-module-replacement/) or complex entry points.
|
|
223
|
-
|
|
224
|
-
<br />
|
|
225
|
-
|
|
226
|
-
### `getAppliedTargets()`
|
|
227
|
-
|
|
228
|
-
Returns an `Array` of all currently patched constructors. This provides full transparency into what native objects have been modified by the library.
|
|
229
|
-
|
|
230
|
-
<br />
|
|
231
|
-
|
|
232
|
-
### `chain_(val)` / `chain$(val)`
|
|
233
|
-
|
|
234
|
-
Wraps a value in a `ChainBox` for processing without prototype extensions.
|
|
235
|
-
|
|
236
|
-
<br />
|
|
237
|
-
|
|
238
|
-
### `ejectModify(options)`
|
|
239
|
-
|
|
240
|
-
Reverses all changes made by `applyModify` and resets internal library state.
|
|
241
|
-
|
|
242
|
-
<br />
|
|
243
|
-
<br />
|
|
244
|
-
|
|
245
212
|
---
|
|
246
213
|
|
|
247
214
|
<br />
|
package/dist/index.min.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @file
|
|
3
|
-
* @summary
|
|
2
|
+
* @file Optimized utility for functional chaining and data transformation.
|
|
3
|
+
* @summary Lightweight, high-performance, zero-dependency, hardened polyfill for the TC39 Pipeline Operator.
|
|
4
4
|
* @author Aries Harbinger
|
|
5
5
|
* @license Apache-2.0
|
|
6
|
-
* @module modify-js
|
|
7
6
|
*/
|
|
8
|
-
|
|
7
|
+
export class Pipe{#t;constructor(t){this.#t=t}modify(t){if(Object.isFrozen(this))return this;if("function"!=typeof t)throw new TypeError("[modify-js] Expected a function, but received: "+typeof t);return this.#t=t(this.#t),this}_p(t){return this.modify(t)}$p(t){return this.modify(t)}out(t,e=!0){try{return null==this.#t?t:this.#t}finally{e&&(this.#t=null,Object.freeze(this))}}_o(t,e){return this.out(t,e)}$o(t,e){return this.out(t,e)}isLocked(){return Object.isFrozen(this)}_l(){return this.isLocked()}$l(){return this.isLocked()}}export const chain_=t=>new Pipe(t);export const chain$=t=>new Pipe(t);export default chain_;
|
|
9
8
|
//# sourceMappingURL=index.min.js.map
|
package/dist/index.min.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dist/index.min.js.map","names":["
|
|
1
|
+
{"version":3,"file":"dist/index.min.js.map","names":["Pipe","value","constructor","val","this","modify","fn","Object","isFrozen","TypeError","_p","$p","out","def","lock","freeze","_o","$o","isLocked","_l","$l","chain_","chain$"],"sources":["src/index.js"],"mappings":";;;;;;OAkCO,MAAMA,KAMTC,GAMA,WAAAC,CAAYC,GAAOC,MAAKH,EAASE,CAAK,CAUtC,MAAAE,CAAOC,GACH,GAAIC,OAAOC,SAASJ,MAAO,OAAOA,KAClC,GAAkB,mBAAPE,EACP,MAAM,IAAIG,UAAU,yDAAyDH,GAGjF,OADAF,MAAKH,EAASK,EAAGF,MAAKH,GACfG,IACX,CAOA,EAAAM,CAAGJ,GAAM,OAAOF,KAAKC,OAAOC,EAAK,CAOjC,EAAAK,CAAGL,GAAM,OAAOF,KAAKC,OAAOC,EAAK,CAWjC,GAAAM,CAAIC,EAAKC,GAAO,GACZ,IACI,OAAsB,MAAfV,MAAKH,EACNY,EACAT,MAAKH,CAEf,CAAE,QACMa,IACAV,MAAKH,EAAS,KACdM,OAAOQ,OAAOX,MAEtB,CACJ,CAQA,EAAAY,CAAGH,EAAKC,GAAQ,OAAOV,KAAKQ,IAAIC,EAAKC,EAAO,CAQ5C,EAAAG,CAAGJ,EAAKC,GAAQ,OAAOV,KAAKQ,IAAIC,EAAKC,EAAO,CAS5C,QAAAI,GAAa,OAAOX,OAAOC,SAASJ,KAAO,CAG3C,EAAAe,GAAO,OAAOf,KAAKc,UAAY,CAG/B,EAAAE,GAAO,OAAOhB,KAAKc,UAAY,SAc5B,MAAMG,OAAUlB,GAAQ,IAAIH,KAAKG,UAQjC,MAAMmB,OAAUnB,GAAQ,IAAIH,KAAKG,kBAEzBkB","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stless/modify-js",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "Lightweight, high-performance, zero-dependency, hardened polyfill for the TC39 Pipeline Operator.",
|
|
5
5
|
"author": "Aries Harbinger",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"homepage": "https://github.com/harnuma9/modify-js#readme",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
"engines": {
|
|
31
|
-
"node": ">=
|
|
31
|
+
"node": ">=16.11.0"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"build": "bash update.sh",
|
|
@@ -39,10 +39,12 @@
|
|
|
39
39
|
},
|
|
40
40
|
"keywords": [
|
|
41
41
|
"pipeline",
|
|
42
|
+
"lightweight",
|
|
42
43
|
"functional",
|
|
43
44
|
"chaining",
|
|
44
45
|
"utility",
|
|
45
|
-
"
|
|
46
|
+
"polyfill",
|
|
47
|
+
"hardened",
|
|
46
48
|
"tc39",
|
|
47
49
|
"fluent-interface"
|
|
48
50
|
],
|
package/src/index.d.ts
CHANGED
|
@@ -1,110 +1,100 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* This identifies native objects (Array, String, Map, etc.) available for patching.
|
|
4
|
-
* @function discover
|
|
5
|
-
* @returns {Array<Function>} A list of discoverable global constructors.
|
|
6
|
-
*/
|
|
7
|
-
export function discover(): Array<Function>;
|
|
8
|
-
/**
|
|
9
|
-
* Removes .modify() and shorthand pipeline methods from all patched constructors.
|
|
10
|
-
* This restores the environment to its original state by deleting the injected
|
|
11
|
-
* prototype properties.
|
|
12
|
-
* @function ejectModify
|
|
13
|
-
* @memberof module:modify-js
|
|
14
|
-
* @param {ModifyOptions} [options={}] - Configuration for the ejection process.
|
|
15
|
-
* @param {boolean} [options.verbose=true] - If true, logs restoration status and warnings.
|
|
16
|
-
* @returns {void}
|
|
17
|
-
* @example
|
|
18
|
-
* ejectModify({ verbose: false }); // Clean up the prototypes
|
|
19
|
-
*/
|
|
20
|
-
export function ejectModify({ verbose }?: ModifyOptions): void;
|
|
21
|
-
/**
|
|
22
|
-
* Injects .modify() and shorthands into specified prototype chains.
|
|
23
|
-
* Follows the Principle of Least Power; it does not patch globally
|
|
24
|
-
* unless explicitly passed the results of {@link module:modify-js.discover}.
|
|
25
|
-
* These methods return a {@link ChainBox} to ensure subsequent calls are null-safe.
|
|
2
|
+
* Copyright 2026 Aries Harbinger
|
|
26
3
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* applyModify([Array, String, Boolean]);
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
31
7
|
*
|
|
32
|
-
*
|
|
33
|
-
* // Intentional global injection
|
|
34
|
-
* applyModify(discover(), __F0V0);
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
35
9
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* @param {ModifyOptions} [options={}] - Configuration for injection behavior.
|
|
42
|
-
* @returns {void}
|
|
43
|
-
*/
|
|
44
|
-
export function applyModify(targets?: Array<Function>, { force, verbose }?: ModifyOptions): void;
|
|
45
|
-
/**
|
|
46
|
-
* Configuration options for injection behavior.
|
|
47
|
-
* @typedef {Object} ModifyOptions
|
|
48
|
-
* @property {boolean} [force=false] - If true, overwrites existing .modify() or shorthand methods.
|
|
49
|
-
* @property {boolean} [verbose=true] - If true, logs warnings when collisions occur.
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
50
15
|
*/
|
|
51
16
|
/**
|
|
52
|
-
*
|
|
53
|
-
* @
|
|
17
|
+
* @file Optimized utility for functional chaining and data transformation.
|
|
18
|
+
* @summary Lightweight, high-performance, zero-dependency, hardened polyfill for the TC39 Pipeline Operator.
|
|
19
|
+
* @author Aries Harbinger
|
|
20
|
+
* @license Apache-2.0
|
|
54
21
|
*/
|
|
55
|
-
export const __F0V0: ModifyOptions;
|
|
56
22
|
/**
|
|
57
|
-
*
|
|
58
|
-
* @
|
|
23
|
+
* A transformation function used within the pipeline.
|
|
24
|
+
* @callback ModifyCallback
|
|
25
|
+
* @param {any} value - The current value in the pipeline.
|
|
26
|
+
* @returns {any} The transformed value.
|
|
59
27
|
*/
|
|
60
|
-
export const __F1V1: ModifyOptions;
|
|
61
28
|
/**
|
|
62
|
-
*
|
|
63
|
-
* @
|
|
29
|
+
* Represents a secure, stateful container for functional transformations.
|
|
30
|
+
* @class Pipe
|
|
64
31
|
*/
|
|
65
|
-
export
|
|
66
|
-
/**
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
export type ModifyOptions = {
|
|
32
|
+
export class Pipe {
|
|
33
|
+
/**
|
|
34
|
+
* Creates an instance of Pipe.
|
|
35
|
+
* @param {any} val - The initial value to wrap.
|
|
36
|
+
*/
|
|
37
|
+
constructor(val: any);
|
|
38
|
+
/**
|
|
39
|
+
* Passes the internal value through a transformation function.
|
|
40
|
+
* Updates the internal state and returns the same Pipe instance.
|
|
41
|
+
* @method modify
|
|
42
|
+
* @param {ModifyCallback} fn - The transformation function to execute.
|
|
43
|
+
* @returns {this} The current Pipe instance for further chaining.
|
|
44
|
+
*/
|
|
45
|
+
modify(fn: ModifyCallback): this;
|
|
80
46
|
/**
|
|
81
|
-
*
|
|
47
|
+
* Alias for {@link modify}.
|
|
48
|
+
* @param {ModifyCallback} fn
|
|
49
|
+
* @returns {this}
|
|
82
50
|
*/
|
|
83
|
-
|
|
51
|
+
_p(fn: ModifyCallback): this;
|
|
84
52
|
/**
|
|
85
|
-
*
|
|
53
|
+
* Alias for {@link modify}.
|
|
54
|
+
* @param {ModifyCallback} fn
|
|
55
|
+
* @returns {this}
|
|
86
56
|
*/
|
|
87
|
-
|
|
88
|
-
};
|
|
89
|
-
export type ChainBox = {
|
|
57
|
+
$p(fn: ModifyCallback): this;
|
|
90
58
|
/**
|
|
91
|
-
*
|
|
59
|
+
* Extracts the value from the pipe and terminates the chain.
|
|
60
|
+
* If the internal value is null or undefined, the provided default is returned.
|
|
61
|
+
* @method out
|
|
62
|
+
* @param {any} [def] - Optional fallback value if the pipe's value is null/undefined.
|
|
63
|
+
* @param {boolean} [lock=true] - If true, wipes the internal state and freezes the object.
|
|
64
|
+
* @returns {any} The final transformed value or the default fallback.
|
|
92
65
|
*/
|
|
93
|
-
|
|
66
|
+
out(def?: any, lock?: boolean): any;
|
|
94
67
|
/**
|
|
95
|
-
*
|
|
68
|
+
* Alias for {@link out}.
|
|
69
|
+
* @param {any} [def]
|
|
70
|
+
* @param {boolean} [lock=true]
|
|
71
|
+
* @returns {any}
|
|
96
72
|
*/
|
|
97
|
-
|
|
73
|
+
_o(def?: any, lock?: boolean): any;
|
|
98
74
|
/**
|
|
99
|
-
*
|
|
75
|
+
* Alias for {@link out}.
|
|
76
|
+
* @param {any} [def]
|
|
77
|
+
* @param {boolean} [lock=true]
|
|
78
|
+
* @returns {any}
|
|
100
79
|
*/
|
|
101
|
-
|
|
80
|
+
$o(def?: any, lock?: boolean): any;
|
|
102
81
|
/**
|
|
103
|
-
*
|
|
82
|
+
* Checks if the pipe instance has been locked and frozen.
|
|
83
|
+
* A locked pipe can no longer be modified or used to extract values.
|
|
84
|
+
* @method isLocked
|
|
85
|
+
* @returns {boolean} True if the instance is frozen, false otherwise.
|
|
104
86
|
*/
|
|
105
|
-
|
|
106
|
-
}
|
|
87
|
+
isLocked(): boolean;
|
|
88
|
+
/** Alias for {@link isLocked} */
|
|
89
|
+
_l(): boolean;
|
|
90
|
+
/** Alias for {@link isLocked} */
|
|
91
|
+
$l(): boolean;
|
|
92
|
+
#private;
|
|
93
|
+
}
|
|
94
|
+
export function chain_(val: any): Pipe;
|
|
95
|
+
export function chain$(val: any): Pipe;
|
|
96
|
+
export default chain_;
|
|
107
97
|
/**
|
|
108
|
-
*
|
|
98
|
+
* A transformation function used within the pipeline.
|
|
109
99
|
*/
|
|
110
|
-
export type ModifyCallback = (
|
|
100
|
+
export type ModifyCallback = (value: any) => any;
|
package/src/index.js
CHANGED
|
@@ -15,328 +15,142 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* @file
|
|
19
|
-
* @summary
|
|
18
|
+
* @file Optimized utility for functional chaining and data transformation.
|
|
19
|
+
* @summary Lightweight, high-performance, zero-dependency, hardened polyfill for the TC39 Pipeline Operator.
|
|
20
20
|
* @author Aries Harbinger
|
|
21
21
|
* @license Apache-2.0
|
|
22
|
-
* @module modify-js
|
|
23
22
|
*/
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Internal state to track patched constructors.
|
|
28
|
-
* @type {Set<Function>}
|
|
29
|
-
* @private
|
|
30
|
-
*/
|
|
31
|
-
const appliedTargets = new Set();
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Internal state to track if the utility has been initialized at least once.
|
|
35
|
-
* @type {boolean}
|
|
36
|
-
* @private
|
|
37
|
-
*/
|
|
38
|
-
let isApplied = false;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Configuration options for injection behavior.
|
|
43
|
-
* @typedef {Object} ModifyOptions
|
|
44
|
-
* @property {boolean} [force=false] - If true, overwrites existing .modify() or shorthand methods.
|
|
45
|
-
* @property {boolean} [verbose=true] - If true, logs warnings when collisions occur.
|
|
46
|
-
*/
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Shorthand: Force=False, Verbose=False (Silent Mode)
|
|
50
|
-
* @type {ModifyOptions}
|
|
51
|
-
*/
|
|
52
|
-
export const __F0V0 = { force: false, verbose: false };
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Shorthand: Force=True, Verbose=True (Debug/Aggressive Mode)
|
|
56
|
-
* @type {ModifyOptions}
|
|
57
|
-
*/
|
|
58
|
-
export const __F1V1 = { force: true, verbose: true };
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Shorthand: Force=False, Verbose=True (Standard Mode)
|
|
62
|
-
* @type {ModifyOptions}
|
|
63
|
-
*/
|
|
64
|
-
export const __F0V1 = { force: false, verbose: true };
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Shorthand: Force=True, Verbose=False (Override Mode)
|
|
68
|
-
* @type {ModifyOptions}
|
|
69
|
-
*/
|
|
70
|
-
export const __F1V0 = { force: true, verbose: false };
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Utility: Checks if the library has been executed at least once.
|
|
75
|
-
* @function hasBeenApplied
|
|
76
|
-
* @returns {boolean} True if applyModify has been called successfully.
|
|
77
|
-
*/
|
|
78
|
-
export const hasBeenApplied = () => isApplied;
|
|
79
|
-
|
|
80
|
-
|
|
81
24
|
/**
|
|
82
|
-
*
|
|
83
|
-
* @
|
|
84
|
-
* @
|
|
25
|
+
* A transformation function used within the pipeline.
|
|
26
|
+
* @callback ModifyCallback
|
|
27
|
+
* @param {any} value - The current value in the pipeline.
|
|
28
|
+
* @returns {any} The transformed value.
|
|
85
29
|
*/
|
|
86
|
-
export const getAppliedTargets = () => Array.from(appliedTargets);
|
|
87
|
-
|
|
88
30
|
|
|
89
31
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* @function discover
|
|
93
|
-
* @returns {Array<Function>} A list of discoverable global constructors.
|
|
32
|
+
* Represents a secure, stateful container for functional transformations.
|
|
33
|
+
* @class Pipe
|
|
94
34
|
*/
|
|
95
|
-
export
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
&& typeof item === 'function'
|
|
103
|
-
&& item.prototype;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
catch (_) { return false; }
|
|
107
|
-
})
|
|
108
|
-
.map(name => globalThis[name]);
|
|
109
|
-
|
|
110
|
-
// Include Buffer if in a Node-like environment
|
|
111
|
-
if (typeof Buffer !== 'undefined' && !selection.includes(Buffer))
|
|
112
|
-
selection.push(Buffer);
|
|
113
|
-
|
|
114
|
-
return selection;
|
|
115
|
-
}
|
|
116
|
-
|
|
35
|
+
export class Pipe {
|
|
36
|
+
/**
|
|
37
|
+
* The internal value held by the pipe.
|
|
38
|
+
* @type {any}
|
|
39
|
+
* @private
|
|
40
|
+
*/
|
|
41
|
+
#value;
|
|
117
42
|
|
|
118
|
-
/**
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
* @property {function(any=): any} out - Unwraps the value and exits the chain.
|
|
124
|
-
*/
|
|
43
|
+
/**
|
|
44
|
+
* Creates an instance of Pipe.
|
|
45
|
+
* @param {any} val - The initial value to wrap.
|
|
46
|
+
*/
|
|
47
|
+
constructor(val) { this.#value = val; }
|
|
125
48
|
|
|
126
|
-
/**
|
|
127
|
-
* A safe wrapper for functional chaining that handles null/undefined values.
|
|
128
|
-
* Once inside a chain_, all subsequent calls are null-safe.
|
|
129
|
-
* @function chain_
|
|
130
|
-
* @param {any} val - The initial value to wrap.
|
|
131
|
-
* @returns {ChainBox} A chainable object containing transformation methods.
|
|
132
|
-
* @example
|
|
133
|
-
* const result = chain_(" hello ")
|
|
134
|
-
* .$p(s => s.trim())
|
|
135
|
-
* .$p(s => null) // Pipeline continues safely
|
|
136
|
-
* .$p(s => console.log(s)) // Output mid-chain
|
|
137
|
-
* .out("DEFAULT"); // Returns "DEFAULT"
|
|
138
|
-
*/
|
|
139
|
-
export const chain_ = (val) => {
|
|
140
|
-
/** @private */
|
|
141
|
-
const inner = (v) => chain_(v);
|
|
142
49
|
|
|
143
|
-
return Object.freeze({
|
|
144
50
|
/**
|
|
51
|
+
* Passes the internal value through a transformation function.
|
|
52
|
+
* Updates the internal state and returns the same Pipe instance.
|
|
145
53
|
* @method modify
|
|
146
|
-
* @
|
|
147
|
-
* @
|
|
148
|
-
* @returns {ChainBox}
|
|
54
|
+
* @param {ModifyCallback} fn - The transformation function to execute.
|
|
55
|
+
* @returns {this} The current Pipe instance for further chaining.
|
|
149
56
|
*/
|
|
150
|
-
modify
|
|
57
|
+
modify(fn) {
|
|
58
|
+
if (Object.isFrozen(this)) return this; // Silent guard for locked pipes
|
|
59
|
+
if (typeof fn !== 'function')
|
|
60
|
+
throw new TypeError(`[modify-js] Expected a function, but received: ${typeof fn}`);
|
|
61
|
+
|
|
62
|
+
this.#value = fn(this.#value);
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
151
65
|
|
|
152
66
|
/**
|
|
153
|
-
* @
|
|
154
|
-
* @
|
|
155
|
-
* @
|
|
156
|
-
* @returns {ChainBox}
|
|
67
|
+
* Alias for {@link modify}.
|
|
68
|
+
* @param {ModifyCallback} fn
|
|
69
|
+
* @returns {this}
|
|
157
70
|
*/
|
|
158
|
-
_p
|
|
71
|
+
_p(fn) { return this.modify(fn); }
|
|
159
72
|
|
|
160
73
|
/**
|
|
161
|
-
* @
|
|
162
|
-
* @
|
|
163
|
-
* @
|
|
164
|
-
* @returns {ChainBox}
|
|
74
|
+
* Alias for {@link modify}.
|
|
75
|
+
* @param {ModifyCallback} fn
|
|
76
|
+
* @returns {this}
|
|
165
77
|
*/
|
|
166
|
-
$p
|
|
78
|
+
$p(fn) { return this.modify(fn); }
|
|
79
|
+
|
|
167
80
|
|
|
168
81
|
/**
|
|
82
|
+
* Extracts the value from the pipe and terminates the chain.
|
|
83
|
+
* If the internal value is null or undefined, the provided default is returned.
|
|
169
84
|
* @method out
|
|
170
|
-
* @
|
|
171
|
-
* @param {
|
|
172
|
-
* @returns {any}
|
|
85
|
+
* @param {any} [def] - Optional fallback value if the pipe's value is null/undefined.
|
|
86
|
+
* @param {boolean} [lock=true] - If true, wipes the internal state and freezes the object.
|
|
87
|
+
* @returns {any} The final transformed value or the default fallback.
|
|
173
88
|
*/
|
|
174
|
-
out
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* The transformation callback passed to .modify() or shorthands.
|
|
188
|
-
* @callback ModifyCallback
|
|
189
|
-
* @param {any} instance - The object instance the method was called upon.
|
|
190
|
-
* @returns {any} - The result of the transformation.
|
|
191
|
-
*/
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Property descriptor for the utility methods.
|
|
195
|
-
* @type {PropertyDescriptor}
|
|
196
|
-
* @private
|
|
197
|
-
*/
|
|
198
|
-
const modifyDefinition = Object.freeze({
|
|
199
|
-
// This prevents the new methods from breaking other libraries that loop over objects.
|
|
200
|
-
enumerable: false,
|
|
201
|
-
|
|
202
|
-
// Allows the property to be deleted or changed later.
|
|
203
|
-
configurable: true,
|
|
204
|
-
|
|
205
|
-
// Allows the value (the function itself) to be changed using an assignment operator (=).
|
|
206
|
-
writable: true,
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* @param {ModifyCallback} fn - The transformation function.
|
|
210
|
-
* @throws {TypeError} If fn is not a function.
|
|
211
|
-
* @returns {any} The result of fn(this).
|
|
212
|
-
*/
|
|
213
|
-
value: (function () {
|
|
214
|
-
return function(fn) {
|
|
215
|
-
if (typeof fn !== 'function') throw new TypeError('Expected a function');
|
|
216
|
-
// Once the user uses .modify() or shorthands, they get a chain_ back.
|
|
217
|
-
// This may "solve" the friction of mid-chain null values.
|
|
218
|
-
return chain_(fn(this));
|
|
89
|
+
out(def, lock = true) {
|
|
90
|
+
try {
|
|
91
|
+
return this.#value == null
|
|
92
|
+
? def
|
|
93
|
+
: this.#value;
|
|
94
|
+
|
|
95
|
+
} finally {
|
|
96
|
+
if (lock) {
|
|
97
|
+
this.#value = null;
|
|
98
|
+
Object.freeze(this);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
219
101
|
}
|
|
220
|
-
})()
|
|
221
|
-
});
|
|
222
|
-
|
|
223
102
|
|
|
224
|
-
/**
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
* @param {boolean} [options.verbose=true] - If true, logs restoration status and warnings.
|
|
232
|
-
* @returns {void}
|
|
233
|
-
* @example
|
|
234
|
-
* ejectModify({ verbose: false }); // Clean up the prototypes
|
|
235
|
-
*/
|
|
236
|
-
export function ejectModify({ verbose = true } = {}) {
|
|
237
|
-
/** @type {Array<Function>} */
|
|
238
|
-
const targets = getAppliedTargets();
|
|
103
|
+
/**
|
|
104
|
+
* Alias for {@link out}.
|
|
105
|
+
* @param {any} [def]
|
|
106
|
+
* @param {boolean} [lock=true]
|
|
107
|
+
* @returns {any}
|
|
108
|
+
*/
|
|
109
|
+
_o(def, lock) { return this.out(def, lock); }
|
|
239
110
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Alias for {@link out}.
|
|
113
|
+
* @param {any} [def]
|
|
114
|
+
* @param {boolean} [lock=true]
|
|
115
|
+
* @returns {any}
|
|
116
|
+
*/
|
|
117
|
+
$o(def, lock) { return this.out(def, lock); }
|
|
243
118
|
|
|
244
|
-
// Remove the methods from the prototype chain
|
|
245
|
-
// These correspond to the methods defined in applyModify
|
|
246
|
-
delete proto.modify;
|
|
247
|
-
delete proto._p;
|
|
248
|
-
delete proto.$p;
|
|
249
119
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Checks if the pipe instance has been locked and frozen.
|
|
122
|
+
* A locked pipe can no longer be modified or used to extract values.
|
|
123
|
+
* @method isLocked
|
|
124
|
+
* @returns {boolean} True if the instance is frozen, false otherwise.
|
|
125
|
+
*/
|
|
126
|
+
isLocked() { return Object.isFrozen(this); }
|
|
253
127
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
console.warn(
|
|
257
|
-
`[modify-js] Skipping ${target.name || 'Anonymous'}: ` +
|
|
258
|
-
`Unable to eject. Reason: ${e.message}`
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
});
|
|
128
|
+
/** Alias for {@link isLocked} */
|
|
129
|
+
_l() { return this.isLocked(); }
|
|
262
130
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if (verbose)
|
|
266
|
-
console.log("[modify-js] Environment fully restored.");
|
|
267
|
-
isApplied = false;
|
|
268
|
-
}
|
|
131
|
+
/** Alias for {@link isLocked} */
|
|
132
|
+
$l() { return this.isLocked(); }
|
|
269
133
|
}
|
|
270
134
|
|
|
271
|
-
|
|
272
135
|
/**
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
* @function applyModify
|
|
279
|
-
* @example
|
|
280
|
-
* // Manual explicit injection
|
|
281
|
-
* applyModify([Array, String, Boolean]);
|
|
282
|
-
*
|
|
283
|
-
* @example
|
|
284
|
-
* // Intentional global injection
|
|
285
|
-
* applyModify(discover(), __F0V0);
|
|
286
|
-
*
|
|
136
|
+
* Entry point to create a new Pipe instance.
|
|
137
|
+
* @function chain_
|
|
138
|
+
* @param {any} val - The value to start the pipeline with.
|
|
139
|
+
* @returns {Pipe} A new Pipe container.
|
|
287
140
|
* @example
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
* @param {ModifyOptions} [options={}] - Configuration for injection behavior.
|
|
293
|
-
* @returns {void}
|
|
141
|
+
* const result = chain_(" hello ")
|
|
142
|
+
* ._p(s => s.trim())
|
|
143
|
+
* ._p(s => s.toUpperCase())
|
|
144
|
+
* .out(); // "HELLO"
|
|
294
145
|
*/
|
|
295
|
-
export
|
|
296
|
-
// Exit early if no work is requested and library was already used
|
|
297
|
-
if (isApplied && !force && targets.length <= 0) return;
|
|
298
|
-
|
|
299
|
-
if (!Array.isArray(targets))
|
|
300
|
-
throw new Error('applyModify requires an array of constructors. Use discover() to get all available.');
|
|
301
|
-
|
|
302
|
-
targets.forEach(target => {
|
|
303
|
-
// Idempotency check
|
|
304
|
-
if (appliedTargets.has(target) && !force) return;
|
|
305
|
-
|
|
306
|
-
const proto = target?.prototype;
|
|
307
|
-
|
|
308
|
-
// Validate target has a prototype to extend
|
|
309
|
-
if (!proto) {
|
|
310
|
-
if (verbose) console.warn(`[modify-js] Skipping ${target}: No prototype available.`);
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
146
|
+
export const chain_ = (val) => new Pipe(val);
|
|
313
147
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
Object.defineProperty(proto, methodName, modifyDefinition);
|
|
322
|
-
}
|
|
323
|
-
catch (e) {
|
|
324
|
-
if (verbose)
|
|
325
|
-
console.warn(
|
|
326
|
-
`[modify-js] Skipping ${target.name || 'Anonymous'}: ` +
|
|
327
|
-
`Unable to inject .${methodName}(). Reason: ${e.message}`
|
|
328
|
-
);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
} else if (verbose) {
|
|
332
|
-
console.warn(`[modify-js] Skipping ${target.name || 'Anonymous'}: .${methodName}() already exists.`);
|
|
333
|
-
}
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
appliedTargets.add(target);
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
isApplied = true;
|
|
340
|
-
}
|
|
148
|
+
/**
|
|
149
|
+
* Shorthand alias for {@link chain_}.
|
|
150
|
+
* @function chain$
|
|
151
|
+
* @param {any} val
|
|
152
|
+
* @returns {Pipe}
|
|
153
|
+
*/
|
|
154
|
+
export const chain$ = (val) => new Pipe(val);
|
|
341
155
|
|
|
342
|
-
export default
|
|
156
|
+
export default chain_;
|