@firela/hk-hsbc-credit 0.1.0 → 0.1.2

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, 'hk_hsbc_credit.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-node.mjs ADDED
@@ -0,0 +1,19 @@
1
+ // ESM entry for Node.js consumers (Phase 74 UAT gap-closure, test 8).
2
+ //
3
+ // Why this exists: the isomorphic loaders branch ONLY at module-load. The CJS
4
+ // loader (index-node.cjs) reads the wasm via fs + WebAssembly.instantiate(bytes);
5
+ // the Worker loader (index-worker.js) does a static `import mod from '*.wasm'`,
6
+ // which is valid only under a Cloudflare Worker bundler — Node ESM `import` of
7
+ // the Worker entry throws ERR_UNKNOWN_FILE_EXTENSION. package.json routes Node
8
+ // ESM resolution here via exports.node.import.
9
+ //
10
+ // We bridge to the PROVEN CJS loader with createRequire instead of duplicating
11
+ // the FFI cycle — index-node.cjs locates the wasm via path.join(__dirname, ...),
12
+ // and __dirname inside that .cjs is its own directory, so the wasm resolves
13
+ // correctly regardless of how the .cjs was required. The parse/detect contract
14
+ // is therefore byte-identical to the CommonJS path (no second implementation).
15
+ import { createRequire } from 'node:module';
16
+
17
+ const require = createRequire(import.meta.url);
18
+
19
+ export const { parse, detect } = require('./index-node.cjs');
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/hk-hsbc-credit",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
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",
@@ -8,7 +8,8 @@
8
8
  "exports": {
9
9
  ".": {
10
10
  "node": {
11
- "require": "./index-node.cjs"
11
+ "require": "./index-node.cjs",
12
+ "import": "./index-node.mjs"
12
13
  },
13
14
  "worker": "./index-worker.js",
14
15
  "default": "./index-worker.js"
@@ -17,6 +18,7 @@
17
18
  },
18
19
  "files": [
19
20
  "index-node.cjs",
21
+ "index-node.mjs",
20
22
  "index-worker.js",
21
23
  "index.d.ts",
22
24
  "hk_hsbc_credit.opt.wasm"