@anuma/sdk 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.
Files changed (87) hide show
  1. package/README.md +54 -53
  2. package/dist/api/spec.cjs +30 -0
  3. package/dist/api/spec.d.mts +3 -0
  4. package/dist/api/spec.d.ts +3 -0
  5. package/dist/api/spec.mjs +12 -0
  6. package/dist/chunk-4ETHWSXJ.mjs +25 -0
  7. package/dist/client/index.cjs +2344 -0
  8. package/dist/client/index.d.mts +10368 -0
  9. package/dist/client/index.d.ts +10368 -0
  10. package/dist/client/index.mjs +2168 -0
  11. package/dist/clientConfig-WITO4T6L.mjs +12 -0
  12. package/dist/constants/index.cjs +30 -0
  13. package/dist/constants/index.d.mts +20 -0
  14. package/dist/constants/index.d.ts +20 -0
  15. package/dist/constants/index.mjs +5 -0
  16. package/dist/design/index.cjs +1523 -0
  17. package/dist/design/index.d.mts +561 -0
  18. package/dist/design/index.d.ts +561 -0
  19. package/dist/design/index.mjs +1468 -0
  20. package/dist/expo/chunk-3HZFU3PV.mjs +5283 -0
  21. package/dist/expo/chunk-DFVCS3QQ.mjs +246 -0
  22. package/dist/{react/chunk-MJJIYFAX.mjs → expo/chunk-GYKJOWEO.mjs} +13 -1
  23. package/dist/expo/chunk-Y7XT2VNQ.mjs +646 -0
  24. package/dist/expo/clientConfig-VPOGQRNP.mjs +12 -0
  25. package/dist/expo/consolidate-HEMQGOUD.mjs +131 -0
  26. package/dist/expo/index.cjs +16904 -6750
  27. package/dist/expo/index.d.mts +5755 -1474
  28. package/dist/expo/index.d.ts +5755 -1474
  29. package/dist/expo/index.mjs +9718 -6182
  30. package/dist/expo/recall-MCB3TVBW.mjs +8 -0
  31. package/dist/index.cjs +2992 -42
  32. package/dist/index.d.mts +7719 -1835
  33. package/dist/index.d.ts +7719 -1835
  34. package/dist/index.mjs +2859 -39
  35. package/dist/next/index.cjs +6 -2
  36. package/dist/next/index.d.mts +24 -1
  37. package/dist/next/index.d.ts +24 -1
  38. package/dist/next/index.mjs +6 -2
  39. package/dist/pii/transformers.cjs +126 -0
  40. package/dist/pii/transformers.d.mts +103 -0
  41. package/dist/pii/transformers.d.ts +103 -0
  42. package/dist/pii/transformers.mjs +90 -0
  43. package/dist/react/chunk-DFVCS3QQ.mjs +246 -0
  44. package/dist/react/chunk-ELDXIXRE.mjs +5656 -0
  45. package/dist/{expo/chunk-MJJIYFAX.mjs → react/chunk-GYKJOWEO.mjs} +13 -1
  46. package/dist/react/chunk-TP6CJZ25.mjs +645 -0
  47. package/dist/react/clientConfig-VPOGQRNP.mjs +12 -0
  48. package/dist/react/consolidate-IJH7LD2T.mjs +131 -0
  49. package/dist/react/index.cjs +25683 -11143
  50. package/dist/react/index.d.mts +7399 -1393
  51. package/dist/react/index.d.ts +7399 -1393
  52. package/dist/react/index.mjs +16709 -9196
  53. package/dist/react/recall-CSOMIT4H.mjs +8 -0
  54. package/dist/server/chunk-FMGZTVXQ.mjs +62 -0
  55. package/dist/server/chunk-G3DC7QBH.mjs +19457 -0
  56. package/dist/server/chunk-PXN5Q3MD.mjs +643 -0
  57. package/dist/server/chunk-RCCKRTBI.mjs +230 -0
  58. package/dist/server/clientConfig-GMADOBPK.mjs +12 -0
  59. package/dist/server/consolidate-A2ZIX5WF.mjs +131 -0
  60. package/dist/server/index.cjs +32161 -0
  61. package/dist/server/index.d.mts +7319 -0
  62. package/dist/server/index.d.ts +7319 -0
  63. package/dist/server/index.mjs +11217 -0
  64. package/dist/server/recall-LFYTTIIY.mjs +8 -0
  65. package/dist/tools/index.cjs +12682 -34
  66. package/dist/tools/index.d.mts +2733 -18
  67. package/dist/tools/index.d.ts +2733 -18
  68. package/dist/tools/index.mjs +12565 -33
  69. package/dist/utils/index.cjs +165 -0
  70. package/dist/utils/index.d.mts +14 -0
  71. package/dist/utils/index.d.ts +14 -0
  72. package/dist/utils/index.mjs +145 -0
  73. package/dist/vercel/index.d.mts +0 -3
  74. package/dist/vercel/index.d.ts +0 -3
  75. package/package.json +127 -16
  76. package/dist/expo/chunk-LJYAMK62.mjs +0 -25
  77. package/dist/expo/chunk-PJCZO4BQ.mjs +0 -25
  78. package/dist/expo/clientConfig-2MI4KULF.mjs +0 -10
  79. package/dist/expo/clientConfig-QDAXFL3W.mjs +0 -10
  80. package/dist/expo/clientConfig-R4IOW7I2.mjs +0 -10
  81. package/dist/react/chunk-LJYAMK62.mjs +0 -25
  82. package/dist/react/chunk-PJCZO4BQ.mjs +0 -25
  83. package/dist/react/clientConfig-2MI4KULF.mjs +0 -10
  84. package/dist/react/clientConfig-QDAXFL3W.mjs +0 -10
  85. package/dist/react/clientConfig-R4IOW7I2.mjs +0 -10
  86. package/dist/tools/chunk-KDFGY4SK.mjs +0 -13
  87. package/dist/tools/clientConfig-RMDOT5YM.mjs +0 -10
@@ -27,8 +27,12 @@ var withAnuma = (nextConfig = {}) => {
27
27
  return {
28
28
  ...nextConfig,
29
29
  serverExternalPackages: [
30
- ...nextConfig.serverExternalPackages || [],
31
- "sharp"
30
+ ...nextConfig.serverExternalPackages ?? [],
31
+ "sharp",
32
+ // exceljs pulls in unzipper → fstream which calls process.umask() at
33
+ // module init time, crashing Cloudflare Workers and other edge runtimes.
34
+ // Externalizing prevents the server bundler from including it in SSR.
35
+ "exceljs"
32
36
  ],
33
37
  webpack: (config, options) => {
34
38
  const { isServer } = options;
@@ -18,6 +18,29 @@
18
18
  *
19
19
  * @module
20
20
  */
21
- declare const withAnuma: (nextConfig?: any) => any;
21
+ /** Minimal Webpack configuration shape used by the SDK plugin. */
22
+ interface WebpackConfig {
23
+ resolve: {
24
+ alias: Record<string, unknown>;
25
+ fallback: Record<string, unknown>;
26
+ };
27
+ module: {
28
+ rules: Array<Record<string, unknown>>;
29
+ };
30
+ }
31
+ /** Webpack compiler options passed by Next.js. */
32
+ interface WebpackOptions {
33
+ isServer: boolean;
34
+ }
35
+ /** Next.js configuration object shape used by the SDK plugin. */
36
+ interface NextConfig {
37
+ serverExternalPackages?: string[];
38
+ webpack?: (config: WebpackConfig, options: WebpackOptions) => WebpackConfig;
39
+ [key: string]: unknown;
40
+ }
41
+ declare const withAnuma: (nextConfig?: NextConfig) => {
42
+ serverExternalPackages: string[];
43
+ webpack: (config: WebpackConfig, options: WebpackOptions) => WebpackConfig;
44
+ };
22
45
 
23
46
  export { withAnuma };
@@ -18,6 +18,29 @@
18
18
  *
19
19
  * @module
20
20
  */
21
- declare const withAnuma: (nextConfig?: any) => any;
21
+ /** Minimal Webpack configuration shape used by the SDK plugin. */
22
+ interface WebpackConfig {
23
+ resolve: {
24
+ alias: Record<string, unknown>;
25
+ fallback: Record<string, unknown>;
26
+ };
27
+ module: {
28
+ rules: Array<Record<string, unknown>>;
29
+ };
30
+ }
31
+ /** Webpack compiler options passed by Next.js. */
32
+ interface WebpackOptions {
33
+ isServer: boolean;
34
+ }
35
+ /** Next.js configuration object shape used by the SDK plugin. */
36
+ interface NextConfig {
37
+ serverExternalPackages?: string[];
38
+ webpack?: (config: WebpackConfig, options: WebpackOptions) => WebpackConfig;
39
+ [key: string]: unknown;
40
+ }
41
+ declare const withAnuma: (nextConfig?: NextConfig) => {
42
+ serverExternalPackages: string[];
43
+ webpack: (config: WebpackConfig, options: WebpackOptions) => WebpackConfig;
44
+ };
22
45
 
23
46
  export { withAnuma };
@@ -3,8 +3,12 @@ var withAnuma = (nextConfig = {}) => {
3
3
  return {
4
4
  ...nextConfig,
5
5
  serverExternalPackages: [
6
- ...nextConfig.serverExternalPackages || [],
7
- "sharp"
6
+ ...nextConfig.serverExternalPackages ?? [],
7
+ "sharp",
8
+ // exceljs pulls in unzipper → fstream which calls process.umask() at
9
+ // module init time, crashing Cloudflare Workers and other edge runtimes.
10
+ // Externalizing prevents the server bundler from including it in SSR.
11
+ "exceljs"
8
12
  ],
9
13
  webpack: (config, options) => {
10
14
  const { isServer } = options;
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/lib/pii/detectors/transformers.ts
31
+ var transformers_exports = {};
32
+ __export(transformers_exports, {
33
+ aggregateTokens: () => aggregateTokens,
34
+ createTransformersNerDetector: () => createTransformersNerDetector
35
+ });
36
+ module.exports = __toCommonJS(transformers_exports);
37
+ var DEFAULT_MODEL = "Xenova/bert-base-NER";
38
+ var DEFAULT_TAG_MAP = {
39
+ PER: "PERSON",
40
+ LOC: "LOCATION",
41
+ ORG: "ORG",
42
+ MISC: void 0
43
+ };
44
+ function placeTokens(tokens, text) {
45
+ const placed = [];
46
+ let cursor = 0;
47
+ for (const t of tokens) {
48
+ const surface = t.word.startsWith("##") ? t.word.slice(2) : t.word;
49
+ const start = text.indexOf(surface, cursor);
50
+ if (start < 0) {
51
+ placed.push({ ...t, start: -1, end: -1 });
52
+ continue;
53
+ }
54
+ const end = start + surface.length;
55
+ placed.push({ ...t, start, end });
56
+ cursor = end;
57
+ }
58
+ return placed;
59
+ }
60
+ function aggregateTokens(tokens, text, tagMap, minScore) {
61
+ const placed = placeTokens(tokens, text);
62
+ const spans = [];
63
+ let cur = null;
64
+ const flush = () => {
65
+ if (!cur) return;
66
+ const category = cur.valid ? tagMap[cur.base] : void 0;
67
+ if (category) {
68
+ const score = cur.scores.reduce((a, b) => a + b, 0) / cur.scores.length;
69
+ if (score >= minScore) spans.push({ start: cur.start, end: cur.end, category, score });
70
+ }
71
+ cur = null;
72
+ };
73
+ for (const t of placed) {
74
+ const dash = t.entity.indexOf("-");
75
+ const prefix = dash >= 0 ? t.entity.slice(0, dash) : t.entity;
76
+ const base = dash >= 0 ? t.entity.slice(dash + 1) : t.entity;
77
+ const isContinuation = t.word.startsWith("##");
78
+ const unplaceable = t.start < 0;
79
+ const continues = cur !== null && base === cur.base && (prefix === "I" || isContinuation) && (unplaceable || t.start - cur.end <= 1);
80
+ if (continues) {
81
+ if (unplaceable) cur.valid = false;
82
+ else {
83
+ cur.end = t.end;
84
+ cur.scores.push(t.score);
85
+ }
86
+ } else {
87
+ flush();
88
+ cur = unplaceable ? null : { base, start: t.start, end: t.end, scores: [t.score], valid: true };
89
+ }
90
+ }
91
+ flush();
92
+ return spans;
93
+ }
94
+ function createTransformersNerDetector(options = {}) {
95
+ const model = options.model ?? DEFAULT_MODEL;
96
+ const dtype = options.dtype ?? "q8";
97
+ const minScore = options.minScore ?? 0.5;
98
+ const tagMap = options.tagMap ?? DEFAULT_TAG_MAP;
99
+ let pipePromise = null;
100
+ const load = () => {
101
+ if (!pipePromise) {
102
+ const loading = (async () => {
103
+ const transformers = await import("@huggingface/transformers");
104
+ return transformers.pipeline("token-classification", model, { dtype });
105
+ })();
106
+ pipePromise = loading;
107
+ loading.catch(() => {
108
+ if (pipePromise === loading) pipePromise = null;
109
+ });
110
+ }
111
+ return pipePromise;
112
+ };
113
+ return {
114
+ async detect(text) {
115
+ if (!text) return [];
116
+ const classify = await load();
117
+ const tokens = await classify(text);
118
+ return aggregateTokens(tokens, text, tagMap, minScore);
119
+ }
120
+ };
121
+ }
122
+ // Annotate the CommonJS export names for ESM import in node:
123
+ 0 && (module.exports = {
124
+ aggregateTokens,
125
+ createTransformersNerDetector
126
+ });
@@ -0,0 +1,103 @@
1
+ /**
2
+ * PII detection patterns for structured data types.
3
+ *
4
+ * Each pattern has a category (used for placeholder tags like [EMAIL_1]),
5
+ * a regex, and an optional validator for reducing false positives.
6
+ */
7
+ type PiiCategory = "EMAIL" | "PHONE" | "SSN" | "CREDIT_CARD" | "IP_ADDRESS" | "API_KEY" | "US_ADDRESS" | "DATE_OF_BIRTH";
8
+
9
+ /**
10
+ * NER detector contract for PII redaction.
11
+ *
12
+ * The regex layer in {@link ./patterns} handles *structured* PII (email, phone,
13
+ * SSN, credit card, …). It cannot detect *unstructured* PII — person names,
14
+ * locations, organizations — which need a named-entity-recognition model.
15
+ *
16
+ * Rather than bundle a model (and a runtime) into the SDK, we define a small
17
+ * runtime-agnostic contract: a {@link NerDetector} takes text and returns the
18
+ * spans it found. Consumers supply an implementation appropriate to their
19
+ * platform — a Transformers.js model in the browser/Node (see
20
+ * `./detectors/transformers`), an `onnxruntime-react-native` model on mobile,
21
+ * a remote service, or a fake in tests. The SDK keeps detection *on-device* by
22
+ * never calling out itself; it only consumes the spans a detector returns and
23
+ * merges them into the existing placeholder map.
24
+ */
25
+
26
+ /**
27
+ * A character-offset span of detected PII. `start`/`end` are indices into the
28
+ * text passed to {@link NerDetector.detect} (`end` exclusive). `category`
29
+ * becomes the placeholder tag prefix (e.g. "PERSON" → `[PERSON_1]`); it may be
30
+ * any string, conventionally `PERSON` / `LOCATION` / `ORG` for NER entities.
31
+ */
32
+ interface PiiSpan {
33
+ start: number;
34
+ end: number;
35
+ category: PiiCategory | (string & {});
36
+ /** Optional model confidence in [0,1]; informational (filter in the detector). */
37
+ score?: number;
38
+ }
39
+ /**
40
+ * Detects unstructured PII in text. Implementations run a model (or service)
41
+ * and return spans. The SDK defensively clamps spans to the text bounds and
42
+ * snaps them to whole-word boundaries before use, so a detector returning a
43
+ * sub-word span cannot corrupt the output — but a detector should still return
44
+ * its cleanest spans.
45
+ *
46
+ * `detect` should be deterministic (same text → same spans): the redactor uses
47
+ * the returned order as a placeholder-numbering tiebreak, so a nondeterministic
48
+ * detector can produce unstable placeholder numbers across runs.
49
+ */
50
+ interface NerDetector {
51
+ detect(text: string): Promise<PiiSpan[]>;
52
+ }
53
+
54
+ /**
55
+ * Reference {@link NerDetector} backed by Transformers.js (`@huggingface/transformers`).
56
+ *
57
+ * Runs a quantized BERT NER model fully on-device (browser via the
58
+ * onnxruntime-web/WASM backend, or Node) — no network call, so PII never leaves
59
+ * the device. The model is loaded lazily on first `detect()` and cached, so
60
+ * importing this module is cheap and apps that never enable NER pay nothing.
61
+ *
62
+ * `@huggingface/transformers` is an OPTIONAL peer dependency: it is `import()`-ed
63
+ * dynamically (matching `lib/memory/reranker.ts`) so it is only required when a
64
+ * caller actually constructs this detector. React Native cannot use this
65
+ * detector (its WASM backend doesn't run under Hermes) — supply an
66
+ * `onnxruntime-react-native`-based {@link NerDetector} there instead.
67
+ */
68
+
69
+ interface TransformersNerDetectorOptions {
70
+ /** Hugging Face model id. Default: `Xenova/bert-base-NER`. */
71
+ model?: string;
72
+ /** Weight precision passed to the pipeline. Default: `q8` (smallest). */
73
+ dtype?: "fp32" | "fp16" | "q8" | "int8" | "uint8" | "q4" | (string & {});
74
+ /** Drop entities below this confidence in [0,1]. Default: `0.5`. */
75
+ minScore?: number;
76
+ /**
77
+ * Map raw NER tags (without the `B-`/`I-` prefix) to PII categories. A tag
78
+ * mapped to `undefined` is dropped. Default maps PER→PERSON, LOC→LOCATION,
79
+ * ORG→ORG and drops MISC (noisy and not reliably PII).
80
+ */
81
+ tagMap?: Record<string, string | undefined>;
82
+ }
83
+ /** Raw per-token output of a Transformers.js `token-classification` pipeline. */
84
+ interface RawToken {
85
+ entity: string;
86
+ score: number;
87
+ index: number;
88
+ word: string;
89
+ }
90
+ /**
91
+ * Aggregate B-/I- subword tokens into whole-entity spans. A new entity starts
92
+ * on a `B-` tag or a tag change; contiguous `I-` tokens (and WordPiece "##"
93
+ * continuations) of the same base tag extend it. (The SDK additionally snaps
94
+ * spans to word boundaries, so dropped trailing subwords are recovered there.)
95
+ */
96
+ declare function aggregateTokens(tokens: RawToken[], text: string, tagMap: Record<string, string | undefined>, minScore: number): PiiSpan[];
97
+ /**
98
+ * Create a Transformers.js-backed {@link NerDetector}. The returned detector
99
+ * loads the model lazily on first use and caches it for the process/page.
100
+ */
101
+ declare function createTransformersNerDetector(options?: TransformersNerDetectorOptions): NerDetector;
102
+
103
+ export { type TransformersNerDetectorOptions, aggregateTokens, createTransformersNerDetector };
@@ -0,0 +1,103 @@
1
+ /**
2
+ * PII detection patterns for structured data types.
3
+ *
4
+ * Each pattern has a category (used for placeholder tags like [EMAIL_1]),
5
+ * a regex, and an optional validator for reducing false positives.
6
+ */
7
+ type PiiCategory = "EMAIL" | "PHONE" | "SSN" | "CREDIT_CARD" | "IP_ADDRESS" | "API_KEY" | "US_ADDRESS" | "DATE_OF_BIRTH";
8
+
9
+ /**
10
+ * NER detector contract for PII redaction.
11
+ *
12
+ * The regex layer in {@link ./patterns} handles *structured* PII (email, phone,
13
+ * SSN, credit card, …). It cannot detect *unstructured* PII — person names,
14
+ * locations, organizations — which need a named-entity-recognition model.
15
+ *
16
+ * Rather than bundle a model (and a runtime) into the SDK, we define a small
17
+ * runtime-agnostic contract: a {@link NerDetector} takes text and returns the
18
+ * spans it found. Consumers supply an implementation appropriate to their
19
+ * platform — a Transformers.js model in the browser/Node (see
20
+ * `./detectors/transformers`), an `onnxruntime-react-native` model on mobile,
21
+ * a remote service, or a fake in tests. The SDK keeps detection *on-device* by
22
+ * never calling out itself; it only consumes the spans a detector returns and
23
+ * merges them into the existing placeholder map.
24
+ */
25
+
26
+ /**
27
+ * A character-offset span of detected PII. `start`/`end` are indices into the
28
+ * text passed to {@link NerDetector.detect} (`end` exclusive). `category`
29
+ * becomes the placeholder tag prefix (e.g. "PERSON" → `[PERSON_1]`); it may be
30
+ * any string, conventionally `PERSON` / `LOCATION` / `ORG` for NER entities.
31
+ */
32
+ interface PiiSpan {
33
+ start: number;
34
+ end: number;
35
+ category: PiiCategory | (string & {});
36
+ /** Optional model confidence in [0,1]; informational (filter in the detector). */
37
+ score?: number;
38
+ }
39
+ /**
40
+ * Detects unstructured PII in text. Implementations run a model (or service)
41
+ * and return spans. The SDK defensively clamps spans to the text bounds and
42
+ * snaps them to whole-word boundaries before use, so a detector returning a
43
+ * sub-word span cannot corrupt the output — but a detector should still return
44
+ * its cleanest spans.
45
+ *
46
+ * `detect` should be deterministic (same text → same spans): the redactor uses
47
+ * the returned order as a placeholder-numbering tiebreak, so a nondeterministic
48
+ * detector can produce unstable placeholder numbers across runs.
49
+ */
50
+ interface NerDetector {
51
+ detect(text: string): Promise<PiiSpan[]>;
52
+ }
53
+
54
+ /**
55
+ * Reference {@link NerDetector} backed by Transformers.js (`@huggingface/transformers`).
56
+ *
57
+ * Runs a quantized BERT NER model fully on-device (browser via the
58
+ * onnxruntime-web/WASM backend, or Node) — no network call, so PII never leaves
59
+ * the device. The model is loaded lazily on first `detect()` and cached, so
60
+ * importing this module is cheap and apps that never enable NER pay nothing.
61
+ *
62
+ * `@huggingface/transformers` is an OPTIONAL peer dependency: it is `import()`-ed
63
+ * dynamically (matching `lib/memory/reranker.ts`) so it is only required when a
64
+ * caller actually constructs this detector. React Native cannot use this
65
+ * detector (its WASM backend doesn't run under Hermes) — supply an
66
+ * `onnxruntime-react-native`-based {@link NerDetector} there instead.
67
+ */
68
+
69
+ interface TransformersNerDetectorOptions {
70
+ /** Hugging Face model id. Default: `Xenova/bert-base-NER`. */
71
+ model?: string;
72
+ /** Weight precision passed to the pipeline. Default: `q8` (smallest). */
73
+ dtype?: "fp32" | "fp16" | "q8" | "int8" | "uint8" | "q4" | (string & {});
74
+ /** Drop entities below this confidence in [0,1]. Default: `0.5`. */
75
+ minScore?: number;
76
+ /**
77
+ * Map raw NER tags (without the `B-`/`I-` prefix) to PII categories. A tag
78
+ * mapped to `undefined` is dropped. Default maps PER→PERSON, LOC→LOCATION,
79
+ * ORG→ORG and drops MISC (noisy and not reliably PII).
80
+ */
81
+ tagMap?: Record<string, string | undefined>;
82
+ }
83
+ /** Raw per-token output of a Transformers.js `token-classification` pipeline. */
84
+ interface RawToken {
85
+ entity: string;
86
+ score: number;
87
+ index: number;
88
+ word: string;
89
+ }
90
+ /**
91
+ * Aggregate B-/I- subword tokens into whole-entity spans. A new entity starts
92
+ * on a `B-` tag or a tag change; contiguous `I-` tokens (and WordPiece "##"
93
+ * continuations) of the same base tag extend it. (The SDK additionally snaps
94
+ * spans to word boundaries, so dropped trailing subwords are recovered there.)
95
+ */
96
+ declare function aggregateTokens(tokens: RawToken[], text: string, tagMap: Record<string, string | undefined>, minScore: number): PiiSpan[];
97
+ /**
98
+ * Create a Transformers.js-backed {@link NerDetector}. The returned detector
99
+ * loads the model lazily on first use and caches it for the process/page.
100
+ */
101
+ declare function createTransformersNerDetector(options?: TransformersNerDetectorOptions): NerDetector;
102
+
103
+ export { type TransformersNerDetectorOptions, aggregateTokens, createTransformersNerDetector };
@@ -0,0 +1,90 @@
1
+ // src/lib/pii/detectors/transformers.ts
2
+ var DEFAULT_MODEL = "Xenova/bert-base-NER";
3
+ var DEFAULT_TAG_MAP = {
4
+ PER: "PERSON",
5
+ LOC: "LOCATION",
6
+ ORG: "ORG",
7
+ MISC: void 0
8
+ };
9
+ function placeTokens(tokens, text) {
10
+ const placed = [];
11
+ let cursor = 0;
12
+ for (const t of tokens) {
13
+ const surface = t.word.startsWith("##") ? t.word.slice(2) : t.word;
14
+ const start = text.indexOf(surface, cursor);
15
+ if (start < 0) {
16
+ placed.push({ ...t, start: -1, end: -1 });
17
+ continue;
18
+ }
19
+ const end = start + surface.length;
20
+ placed.push({ ...t, start, end });
21
+ cursor = end;
22
+ }
23
+ return placed;
24
+ }
25
+ function aggregateTokens(tokens, text, tagMap, minScore) {
26
+ const placed = placeTokens(tokens, text);
27
+ const spans = [];
28
+ let cur = null;
29
+ const flush = () => {
30
+ if (!cur) return;
31
+ const category = cur.valid ? tagMap[cur.base] : void 0;
32
+ if (category) {
33
+ const score = cur.scores.reduce((a, b) => a + b, 0) / cur.scores.length;
34
+ if (score >= minScore) spans.push({ start: cur.start, end: cur.end, category, score });
35
+ }
36
+ cur = null;
37
+ };
38
+ for (const t of placed) {
39
+ const dash = t.entity.indexOf("-");
40
+ const prefix = dash >= 0 ? t.entity.slice(0, dash) : t.entity;
41
+ const base = dash >= 0 ? t.entity.slice(dash + 1) : t.entity;
42
+ const isContinuation = t.word.startsWith("##");
43
+ const unplaceable = t.start < 0;
44
+ const continues = cur !== null && base === cur.base && (prefix === "I" || isContinuation) && (unplaceable || t.start - cur.end <= 1);
45
+ if (continues) {
46
+ if (unplaceable) cur.valid = false;
47
+ else {
48
+ cur.end = t.end;
49
+ cur.scores.push(t.score);
50
+ }
51
+ } else {
52
+ flush();
53
+ cur = unplaceable ? null : { base, start: t.start, end: t.end, scores: [t.score], valid: true };
54
+ }
55
+ }
56
+ flush();
57
+ return spans;
58
+ }
59
+ function createTransformersNerDetector(options = {}) {
60
+ const model = options.model ?? DEFAULT_MODEL;
61
+ const dtype = options.dtype ?? "q8";
62
+ const minScore = options.minScore ?? 0.5;
63
+ const tagMap = options.tagMap ?? DEFAULT_TAG_MAP;
64
+ let pipePromise = null;
65
+ const load = () => {
66
+ if (!pipePromise) {
67
+ const loading = (async () => {
68
+ const transformers = await import("@huggingface/transformers");
69
+ return transformers.pipeline("token-classification", model, { dtype });
70
+ })();
71
+ pipePromise = loading;
72
+ loading.catch(() => {
73
+ if (pipePromise === loading) pipePromise = null;
74
+ });
75
+ }
76
+ return pipePromise;
77
+ };
78
+ return {
79
+ async detect(text) {
80
+ if (!text) return [];
81
+ const classify = await load();
82
+ const tokens = await classify(text);
83
+ return aggregateTokens(tokens, text, tagMap, minScore);
84
+ }
85
+ };
86
+ }
87
+ export {
88
+ aggregateTokens,
89
+ createTransformersNerDetector
90
+ };