no-pii 1.2.0 → 1.2.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.
Files changed (2) hide show
  1. package/nopii.js +16 -103
  2. package/package.json +11 -6
package/nopii.js CHANGED
@@ -1,14 +1,8 @@
1
- import fs from 'node:fs/promises';
2
- import crypto from 'node:crypto';
3
- import { Buffer } from 'node:buffer';
4
- import { getPassword, setPassword } from 'cross-keychain';
5
-
6
- export class NoPii {
1
+ export default class NoPii {
7
2
  #strategies = [];
8
3
  #combinedRegex = null;
9
- #storage = null;
10
4
  #config = {};
11
- #currentRules = {};
5
+ #currentRules = {};
12
6
 
13
7
  static PRESETS = {
14
8
  COMMON: {
@@ -24,7 +18,6 @@ export class NoPii {
24
18
 
25
19
  constructor(options = {}) {
26
20
  this.#config = { verbose: false, ...options };
27
- this.#storage = this.#initStorage(options.storage);
28
21
 
29
22
  if (options.rules) {
30
23
  for (const [key, val] of Object.entries(options.rules)) {
@@ -33,13 +26,12 @@ export class NoPii {
33
26
  }
34
27
  }
35
28
 
36
- list() {
29
+ listRules() {
37
30
  return { ...this.#currentRules };
38
31
  }
39
32
 
40
- async addRule(key, value) {
33
+ addRule(key, value) {
41
34
  this.#applyRuleLogic(key, value);
42
- await this.#persist();
43
35
  return this;
44
36
  }
45
37
 
@@ -58,8 +50,10 @@ export class NoPii {
58
50
  this.#register(upperKey, allPresets[upperKey]);
59
51
  }
60
52
  else if (Array.isArray(value)) {
61
- const escaped = value.map(v => v.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
62
- this.#register(key, new RegExp(`\\b(${escaped})\\b`, 'gu'));
53
+ const escaped = value
54
+ .map(v => v.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
55
+ .join('|');
56
+ this.#register(key, new RegExp(escaped, 'g'));
63
57
  }
64
58
  else {
65
59
  this.#register(key, value);
@@ -68,7 +62,6 @@ export class NoPii {
68
62
 
69
63
  #register(name, regex) {
70
64
  let pattern;
71
- // FIX: Handle string-serialized regex like "/pattern/flags"
72
65
  if (typeof regex === 'string' && regex.startsWith('/') && regex.lastIndexOf('/') > 0) {
73
66
  const lastSlash = regex.lastIndexOf('/');
74
67
  const source = regex.slice(1, lastSlash);
@@ -98,38 +91,16 @@ export class NoPii {
98
91
  this.#combinedRegex = new RegExp(source, 'gu');
99
92
  }
100
93
 
101
- async #persist() {
102
- if (!this.#storage) return;
103
-
104
- // FIX: Serialize RegExp objects to strings so they don't become {} in JSON
105
- const serializable = {};
106
- for (const [k, v] of Object.entries(this.#currentRules)) {
107
- serializable[k] = (v instanceof RegExp) ? `/${v.source}/${v.flags}` : v;
108
- }
109
- await this.#storage.set('rules', serializable);
110
- }
111
-
112
- async load() {
113
- if (!this.#storage) return this;
114
- const savedRules = await this.#storage.get('rules');
115
- if (savedRules) {
116
- for (const [key, val] of Object.entries(savedRules)) {
117
- this.#applyRuleLogic(key, val);
118
- }
119
- }
120
- return this;
121
- }
122
-
123
- redact(text) {
94
+ redact(text, startCount) {
124
95
  if (!this.#combinedRegex || !text) return { safeText: text, vault: new Map() };
125
- const vault = new Map();
126
- let count = 0;
96
+ const vault = {};
97
+ let count = startCount ? startCount : 0;
127
98
  const safeText = text.replace(this.#combinedRegex, (...args) => {
128
99
  const groups = args.at(-1);
129
100
  const label = Object.keys(groups).find(key => groups[key] !== undefined);
130
101
  const value = groups[label];
131
102
  const placeholder = `[${label}_${++count}]`;
132
- vault.set(placeholder, value);
103
+ vault[placeholder] = value;
133
104
  return placeholder;
134
105
  });
135
106
  return { safeText, vault };
@@ -149,68 +120,10 @@ export class NoPii {
149
120
 
150
121
  return redactedText.replace(restoreRegex, (m) => lookup(m));
151
122
  }
123
+
124
+
125
+
152
126
 
153
- #initStorage(storage) {
154
- if (!storage) {
155
- const _mem = new Map();
156
- return { get: async (k) => _mem.get(k), set: async (k, v) => _mem.set(k, v) };
157
- }
158
-
159
- if (storage.method === 'os') {
160
- const { service = 'nopii' } = storage;
161
- const account = service;
162
- return {
163
- get: async (key) => {
164
- try {
165
- const val = await getPassword(service, `${account}_${key}`);
166
- return val ? JSON.parse(val) : null;
167
- } catch { return null; }
168
- },
169
- set: async (key, val) => {
170
- await setPassword(service, `${account}_${key}`, JSON.stringify(val));
171
- }
172
- };
173
- }
174
-
175
- if (typeof storage.method === 'string' && storage.method.endsWith('.json')) {
176
- const aesKey = Buffer.alloc(32, storage.aes || 'default-secret-key');
177
- const filePath = storage.method;
178
-
179
- const encrypt = (text) => {
180
- const iv = crypto.randomBytes(12);
181
- const cipher = crypto.createCipheriv('aes-256-gcm', aesKey, iv);
182
- const enc = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
183
- return Buffer.concat([iv, cipher.getAuthTag(), enc]).toString('base64');
184
- };
185
-
186
- const decrypt = (data) => {
187
- const buf = Buffer.from(data, 'base64');
188
- const iv = buf.subarray(0, 12), tag = buf.subarray(12, 28), enc = buf.subarray(28);
189
- const decipher = crypto.createDecipheriv('aes-256-gcm', aesKey, iv);
190
- decipher.setAuthTag(tag);
191
- return decipher.update(enc, 'utf8') + decipher.final('utf8');
192
- };
193
-
194
- return {
195
- async get(lookupKey) {
196
- try {
197
- const raw = await fs.readFile(filePath, 'utf8');
198
- const data = JSON.parse(decrypt(raw));
199
- return lookupKey ? data[lookupKey] : data;
200
- } catch { return null; }
201
- },
202
- async set(lookupKey, val) {
203
- let data = {};
204
- try {
205
- const raw = await fs.readFile(filePath, 'utf8');
206
- data = JSON.parse(decrypt(raw));
207
- } catch (e) {}
208
- data[lookupKey] = val;
209
- await fs.writeFile(filePath, encrypt(JSON.stringify(data)));
210
- }
211
- };
212
- }
213
- }
127
+
214
128
  }
215
129
 
216
- export const nopii = (options) => new NoPii(options);
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "no-pii",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Production-grade PII redaction library with CLI support",
5
5
  "main": "nopii.js",
6
6
  "type": "module",
7
7
  "bin": {
8
- "nopii": "./nopii-cli.js"
8
+ "nopii": "nopii-cli.js"
9
9
  },
10
10
  "scripts": {
11
11
  "test": "echo \"Error: no test specified\" && exit 1"
12
12
  },
13
13
  "keywords": [
14
14
  "pii",
15
- "nopii",
16
- "no-pii",
15
+ "nopii",
16
+ "no-pii",
17
17
  "redaction",
18
18
  "privacy",
19
19
  "gdpr",
@@ -30,7 +30,12 @@
30
30
  "node": ">=18.0.0"
31
31
  },
32
32
  "dependencies": {
33
- "cross-keychain": "^1.0.0"
33
+ "@huggingface/transformers": "^3.8.1",
34
+ "@nlpjs/lang-zh": "^5.0.0-alpha.5",
35
+ "@nlpjs/ner": "^5.0.0-alpha.5",
36
+ "cross-keychain": "^1.0.0",
37
+ "nodejieba": "^3.5.7",
38
+ "onnxruntime-node": "^1.24.3"
34
39
  },
35
40
  "files": [
36
41
  "nopii.js",
@@ -41,4 +46,4 @@
41
46
  ".": "./nopii.js",
42
47
  "./cli": "./nopii-cli.js"
43
48
  }
44
- }
49
+ }