@firela/pii-redactor 0.1.0 → 0.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/index-node.cjs CHANGED
@@ -48,16 +48,20 @@ function processFailure(code, message) {
48
48
  // to first use (lazy _exports cache below) so the module is cheap to require.
49
49
  const wasmBytes = fs.readFileSync(path.join(__dirname, 'pii_redactor.opt.wasm'));
50
50
 
51
- let _exports;
51
+ // Cache the in-flight instantiation PROMISE (not the resolved value) so
52
+ // concurrent cold-start callers await the SAME instantiate — no parallel
53
+ // instantiation race.
54
+ let _exportsPromise;
52
55
 
53
56
  /// Lazily instantiate the wasm module ONCE. Cached so the cold-start cost is
54
57
  /// paid only on the first parse/detect call. Returns the exports object.
55
58
  async function getExports() {
56
- if (!_exports) {
57
- const { instance } = await WebAssembly.instantiate(wasmBytes, {}); // {} = zero imports
58
- _exports = instance.exports;
59
+ if (!_exportsPromise) {
60
+ _exportsPromise = WebAssembly.instantiate(wasmBytes, {}).then(({ instance }) => { // {} = zero imports
61
+ return instance.exports;
62
+ });
59
63
  }
60
- return _exports;
64
+ return _exportsPromise;
61
65
  }
62
66
 
63
67
  function makeParse(ex) {
@@ -103,11 +107,16 @@ function makeParse(ex) {
103
107
  }
104
108
 
105
109
  const resultBytes = Buffer.from(new Uint8Array(ex.memory.buffer, ptr, len));
106
- const json = JSON.parse(resultBytes.toString('utf8'));
107
-
110
+ const decoded = resultBytes.toString('utf8'); // decode while buffer is valid
108
111
  // Free BOTH buffers every cycle (input + result) per the FFI contract.
109
112
  ex.free_result(inputPtr, inputLen);
110
113
  ex.free_result(ptr, len);
114
+ let json;
115
+ try {
116
+ json = JSON.parse(decoded);
117
+ } catch (e) {
118
+ return processFailure('INTERNAL', 'parse returned malformed JSON: ' + ((e && e.message) || String(e)));
119
+ }
111
120
  return json;
112
121
  };
113
122
  }
package/index-worker.js CHANGED
@@ -35,14 +35,23 @@ const REQUIRED_EXPORTS = ['alloc', 'parse', 'detect', 'free_result', 'memory'];
35
35
  // instantiation; the result is cached so the cold-start cost is paid once). `mod`
36
36
  // is a pre-compiled WebAssembly.Module, so instantiate() returns an Instance
37
37
  // (unlike the bytes overload, which returns {module, instance}) — handle both.
38
- let _exports;
38
+ // Cache the in-flight instantiation PROMISE (not the resolved value) so
39
+ // concurrent cold-start callers await the SAME instantiate — no parallel
40
+ // instantiation race. Validates the FFI exports once on first use.
41
+ let _exportsPromise;
39
42
  async function getExports() {
40
- if (!_exports) {
41
- const result = await WebAssembly.instantiate(mod, {}); // {} = zero imports
42
- const instance = result.instance || result; // bytes→{instance}, Module→Instance
43
- _exports = instance.exports;
43
+ if (!_exportsPromise) {
44
+ _exportsPromise = WebAssembly.instantiate(mod, {}).then((result) => { // {} = zero imports
45
+ const instance = result.instance || result; // bytes→{instance}, Module→Instance
46
+ const ex = instance.exports;
47
+ const missing = REQUIRED_EXPORTS.filter((name) => !ex[name]);
48
+ if (missing.length) {
49
+ throw new Error('wasm missing required exports: ' + missing.join(', '));
50
+ }
51
+ return ex;
52
+ });
44
53
  }
45
- return _exports;
54
+ return _exportsPromise;
46
55
  }
47
56
 
48
57
  function processFailure(code, message) {
@@ -99,11 +108,16 @@ export async function parse(bytes) {
99
108
  }
100
109
 
101
110
  const resultBytes = new Uint8Array(ex.memory.buffer, ptr, len);
102
- const json = JSON.parse(new TextDecoder().decode(resultBytes));
103
-
111
+ const decoded = new TextDecoder().decode(resultBytes); // decode while buffer is valid
104
112
  // Free BOTH buffers every cycle (input + result) per the FFI contract.
105
113
  ex.free_result(inputPtr, inputLen);
106
114
  ex.free_result(ptr, len);
115
+ let json;
116
+ try {
117
+ json = JSON.parse(decoded);
118
+ } catch (e) {
119
+ return processFailure('INTERNAL', 'parse returned malformed JSON: ' + ((e && e.message) || String(e)));
120
+ }
107
121
  return json;
108
122
  }
109
123
 
@@ -130,5 +144,4 @@ export async function detect(bytes) {
130
144
  return conf;
131
145
  }
132
146
 
133
- export { parse, detect };
134
147
  export default { parse, detect };
package/index.d.ts CHANGED
@@ -77,7 +77,7 @@ export type ParseResult = ProcessSuccess | ProcessFailure;
77
77
  * `errors` lists the categorized error(s). The host never throws on
78
78
  * parser-level failures — they arrive as `ProcessFailure`.
79
79
  */
80
- export declare function parse(bytes: Uint8Array | Buffer): Promise<ParseResult>;
80
+ export declare function parse(bytes: Uint8Array): Promise<ParseResult>;
81
81
 
82
82
  /**
83
83
  * Detection confidence for the given bytes.
@@ -85,4 +85,4 @@ export declare function parse(bytes: Uint8Array | Buffer): Promise<ParseResult>;
85
85
  * @param bytes - Statement bytes to identify.
86
86
  * @returns Confidence in {0, 50, 100}: 0 = no match, 50 = partial, 100 = definite.
87
87
  */
88
- export declare function detect(bytes: Uint8Array | Buffer): Promise<number>;
88
+ export declare function detect(bytes: Uint8Array): Promise<number>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firela/pii-redactor",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "firela parser/sanitizer WASM module — isomorphic Node.js + Cloudflare Worker loader (ADR-0101 compiled-only).",
5
5
  "type": "module",
6
6
  "main": "./index-node.cjs",