@stll/anonymize-wasm 2.8.3 → 2.9.0

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 CHANGED
@@ -18,14 +18,18 @@ and local document-provider tools remain Node-only. The binding is instantiated
18
18
  lazily on first use.
19
19
 
20
20
  ```ts
21
- import { loadDefaultPipeline } from "@stll/anonymize-wasm";
21
+ import { createPipeline } from "@stll/anonymize-wasm";
22
22
 
23
- // Loads the compressed default package bundled in the tarball.
24
- const pipeline = await loadDefaultPipeline();
23
+ const pipeline = await createPipeline({ language: "en" });
25
24
  const { redaction } = pipeline.redactText("A contract signed by Jan Novak.");
26
25
  console.log(redaction.redactedText);
27
26
  ```
28
27
 
28
+ `createPipeline()` accepts one supported language, an exact non-empty
29
+ combination such as `{ language: ["cs", "en"] }`, or `{ language: "all" }`.
30
+ Language-scoped pipelines are assembled and cached from lazy dictionary chunks;
31
+ `"all"` loads the bundled default prepared package.
32
+
29
33
  Bring your own prepared package (bytes, an `ArrayBuffer`, or a URL to fetch):
30
34
 
31
35
  ```ts
@@ -66,11 +70,16 @@ stllAnonymizeWasm({ packages: "none" });
66
70
  stllAnonymizeWasm({ packages: "all" });
67
71
  ```
68
72
 
69
- `"default"` selects the full-dictionary package that `loadDefaultPipeline()`
73
+ `"default"` selects the full-dictionary package that `createPipeline()` with
74
+ `language: "all"` or `loadDefaultPipeline()`
70
75
  (no argument) loads; a language code selects the scoped package that
71
76
  `loadDefaultPipeline("cs")` loads. Requesting a package that is not bundled
72
77
  fails the build.
73
78
 
79
+ The semantic factory supports `cs`, `de`, `en`, `es`, `fr`, `hu`, `it`, `lv`,
80
+ `pl`, `pt-br`, `ro`, `sk`, and `sv`. The lower-level artifact loader has
81
+ bundled scoped packages for `cs`, `de`, and `en` only.
82
+
74
83
  Interplay with the runtime loaders: the plugin only controls which assets ship,
75
84
  not which the code asks for. Calling `loadDefaultPipeline(language)` for a
76
85
  package you did not emit resolves to a missing asset URL and rejects with a
Binary file
package/dist/wasm.d.mts CHANGED
@@ -1076,6 +1076,11 @@ type PreparedSearch = PreparedNativeAnonymizer;
1076
1076
  declare const PreparedAnonymizer: typeof PreparedNativeAnonymizer;
1077
1077
  type PreparedAnonymizer = PreparedNativeAnonymizer;
1078
1078
  //#endregion
1079
+ //#region src/pipeline-language.d.ts
1080
+ type SupportedLanguage = "cs" | "de" | "en" | "es" | "fr" | "hu" | "it" | "lv" | "pl" | "pt-br" | "ro" | "sk" | "sv";
1081
+ declare const SUPPORTED_LANGUAGES: readonly SupportedLanguage[];
1082
+ type PipelineLanguageSelection = SupportedLanguage | readonly [SupportedLanguage, ...SupportedLanguage[]] | "all";
1083
+ //#endregion
1079
1084
  //#region src/context.d.ts
1080
1085
  /**
1081
1086
  * Cached state for a single pipeline run (or a sequence of runs sharing the
@@ -1142,6 +1147,9 @@ type WasmBindingOptions = {
1142
1147
  * the underlying wasm module is instantiated a single time and cached. */
1143
1148
  declare const getBinding: () => Promise<NativeAnonymizeBinding>;
1144
1149
  type LoadPreparedPackageOptions = WasmBindingOptions;
1150
+ type CreatePipelineOptions = WasmBindingOptions & {
1151
+ language?: PipelineLanguageSelection;
1152
+ };
1145
1153
  /** Load a prepared package and return a pipeline ready to redact text. */
1146
1154
  declare const loadPipeline: (source: PreparedPackageSource, options?: LoadPreparedPackageOptions) => Promise<PreparedNativePipeline>;
1147
1155
  /** Load a prepared package and return the lower-level anonymizer. */
@@ -1165,6 +1173,8 @@ declare const loadDefaultPipeline: (language?: string, options?: LoadPreparedPac
1165
1173
  * folding the binding into the key would keep unbounded per-binding entries
1166
1174
  * alive. Injected-binding callers get a fresh pipeline each call. */
1167
1175
  declare const getDefaultPipeline: (language?: string, options?: LoadPreparedPackageOptions) => Promise<PreparedNativePipeline>;
1176
+ declare const createPipeline: ({ language, ...bindingOptions }?: CreatePipelineOptions) => Promise<PreparedNativePipeline>;
1177
+ declare const create_pipeline: typeof createPipeline;
1168
1178
  declare const redactDefaultText: (fullText: string, operators?: NativeOperatorConfig, language?: string) => Promise<NativeStaticRedactionResult>;
1169
1179
  declare const redactDefaultTextJson: (fullText: string, operators?: NativeOperatorConfig, language?: string) => Promise<string>;
1170
1180
  declare const native_package_version: (options?: WasmBindingOptions) => Promise<string>;
@@ -1184,5 +1194,5 @@ declare const summary_diagnostics_json: (config: NativeSearchPackageInput, fullT
1184
1194
  * fail-closed core used by Node and Python. This does not redact the PDF. */
1185
1195
  declare const inspect_pdf_json: (document: Uint8Array, observationsJson?: string, options?: WasmBindingOptions) => Promise<string>;
1186
1196
  //#endregion
1187
- export { type AnonymisationOperator, CALLER_DETECTION_CONTRACT_VERSION, CALLER_DETECTION_MAX_COUNT, CALLER_DETECTION_REQUEST_JSON_MAX_BYTES, CALLER_DETECTION_TEXT_MAX_BYTES, CAPABILITY_MANIFEST, CAPABILITY_MANIFEST_SCHEMA_VERSION, CAPABILITY_PARITY_PROFILES, CAPABILITY_RUNTIMES, CAPABILITY_SURFACES, type CapabilityManifest, type CapabilityParityProfile, type CapabilityRuntime, type CapabilitySurface, type CapabilitySurfaceId, ConvertExternalDetectionBatchOptions, DEFAULT_ENTITY_LABELS, DETECTION_SOURCES, DETECTOR_PRIORITY, type DefaultEntityLabel, type DetectionSource, type Dictionaries, ENTITY_CAPABILITIES, ENTITY_LABELS, ENTITY_SELECTIONS, EXTERNAL_DETECTION_BATCH_MAX_BYTES, EXTERNAL_DETECTION_BATCH_VERSION, EXTERNAL_DETECTION_DOCUMENT_MAX_BYTES, EXTERNAL_DETECTION_MAX_DETECTIONS, EXTERNAL_DETECTION_MAX_LABEL_MAPPINGS, EXTERNAL_DETECTION_MAX_METADATA_BYTES, EXTERNAL_DETECTION_OFFSET_UNITS, EXTERNAL_DETECTION_PROVIDER_ID_MAX_BYTES, type Entity, type EntityCapability, type EntityLabel, type EntitySelection, ExternalDetectionBatch, ExternalDetectionOffsetUnit, type GazetteerEntry, LoadPreparedPackageOptions, NATIVE_BINDING_PARITY_MEMBERS, NativeAnonymizeBinding, NativeAnonymizerFromConfigOptions, NativeAnonymizerFromPackageOptions, NativeBindingVersionOptions, NativeCallerDetection, NativeCallerRedactionOptions, NativeCreateSessionWithLifecycleOptions, NativeDiagnosticsBatchCallback, NativeNormalizeOptions, NativeOpenSessionArchiveOptions, NativeOperatorConfig, type NativePipelineBuildOptions, type NativePipelineCompatibility, NativePipelineEntity, NativePipelineFromPackageOptions, type NativePipelinePackageOptions, type NativePipelineUnsupportedFeature, NativePreparedRedactionSessionBinding, NativePreparedSearchBinding, type NativePreparedSearchConfig, NativePreparedSessionRedactionPlanBinding, NativeRedactionResult, NativeResultEventCallback, NativeSearchPackageInput, NativeSearchPackageOptions, NativeSessionBlockRedactionPlan, NativeSessionCallerRedactionInput, NativeSessionCallerRedactionPlanOptions, NativeSessionDeletionSummary, NativeSessionLifecycle, NativeSessionMetadata, NativeSessionRedactionAtOptions, NativeSessionStatus, NativeStaticRedactionResult, NativeTextReplacement, OPERATOR_TYPES, type OperatorConfig, type OperatorType, type PipelineConfig, type PipelineContext, PrepareSearchPackageOptions, PreparedAnonymizer, PreparedNativeAnonymizer, PreparedNativePipeline, PreparedNativeRedactionSession, PreparedNativeSessionRedactionPlan, PreparedPackageSource, PreparedSearch, type RedactionResult, type ReviewDecision, type ReviewedEntity, SESSION_CALLER_INPUTS_JSON_MAX_BYTES, SESSION_CALLER_MAX_INPUTS, SharedNativeDiagnosticsJsonOptions, SharedNativeDiagnosticsStreamJsonOptions, SharedNativePreparedPackageOptions, SharedNativeRedactTextJsonOptions, SharedNativeRedactTextOptions, SharedNativeRedactTextStreamJsonOptions, SharedNativeSearchPackageOptions, WasmBindingOptions, assertNativeBindingVersion, assertNativePipelineSupported, convert_external_detection_batch, createNativeAnonymizerFromConfig, createNativeAnonymizerFromPackage, createNativePipelineFromConfig, createNativePipelineFromPackage, createPipelineContext, deanonymise, defaultPackageUrl, diagnostics_json, diagnostics_stream_json, encodeNativeSearchConfig, encodeNativeSearchConfigInput, exportRedactionKey, getBinding, getDefaultPipeline, getNativeBindingVersion, getNativePipelineCompatibility, inspect_pdf_json, isNativeAnonymizeBinding, loadDefaultPipeline, loadPipeline, load_prepared_package, native_package_version, normalize_for_search, prepareNativePipelineConfig, prepareNativePipelinePackage, prepareNativeSearchPackage, prepare_search_package, redactDefaultText, redactDefaultTextJson, redact_text, redact_text_json, redact_text_stream_json, summary_diagnostics_json };
1197
+ export { type AnonymisationOperator, CALLER_DETECTION_CONTRACT_VERSION, CALLER_DETECTION_MAX_COUNT, CALLER_DETECTION_REQUEST_JSON_MAX_BYTES, CALLER_DETECTION_TEXT_MAX_BYTES, CAPABILITY_MANIFEST, CAPABILITY_MANIFEST_SCHEMA_VERSION, CAPABILITY_PARITY_PROFILES, CAPABILITY_RUNTIMES, CAPABILITY_SURFACES, type CapabilityManifest, type CapabilityParityProfile, type CapabilityRuntime, type CapabilitySurface, type CapabilitySurfaceId, ConvertExternalDetectionBatchOptions, CreatePipelineOptions, DEFAULT_ENTITY_LABELS, DETECTION_SOURCES, DETECTOR_PRIORITY, type DefaultEntityLabel, type DetectionSource, type Dictionaries, ENTITY_CAPABILITIES, ENTITY_LABELS, ENTITY_SELECTIONS, EXTERNAL_DETECTION_BATCH_MAX_BYTES, EXTERNAL_DETECTION_BATCH_VERSION, EXTERNAL_DETECTION_DOCUMENT_MAX_BYTES, EXTERNAL_DETECTION_MAX_DETECTIONS, EXTERNAL_DETECTION_MAX_LABEL_MAPPINGS, EXTERNAL_DETECTION_MAX_METADATA_BYTES, EXTERNAL_DETECTION_OFFSET_UNITS, EXTERNAL_DETECTION_PROVIDER_ID_MAX_BYTES, type Entity, type EntityCapability, type EntityLabel, type EntitySelection, ExternalDetectionBatch, ExternalDetectionOffsetUnit, type GazetteerEntry, LoadPreparedPackageOptions, NATIVE_BINDING_PARITY_MEMBERS, NativeAnonymizeBinding, NativeAnonymizerFromConfigOptions, NativeAnonymizerFromPackageOptions, NativeBindingVersionOptions, NativeCallerDetection, NativeCallerRedactionOptions, NativeCreateSessionWithLifecycleOptions, NativeDiagnosticsBatchCallback, NativeNormalizeOptions, NativeOpenSessionArchiveOptions, NativeOperatorConfig, type NativePipelineBuildOptions, type NativePipelineCompatibility, NativePipelineEntity, NativePipelineFromPackageOptions, type NativePipelinePackageOptions, type NativePipelineUnsupportedFeature, NativePreparedRedactionSessionBinding, NativePreparedSearchBinding, type NativePreparedSearchConfig, NativePreparedSessionRedactionPlanBinding, NativeRedactionResult, NativeResultEventCallback, NativeSearchPackageInput, NativeSearchPackageOptions, NativeSessionBlockRedactionPlan, NativeSessionCallerRedactionInput, NativeSessionCallerRedactionPlanOptions, NativeSessionDeletionSummary, NativeSessionLifecycle, NativeSessionMetadata, NativeSessionRedactionAtOptions, NativeSessionStatus, NativeStaticRedactionResult, NativeTextReplacement, OPERATOR_TYPES, type OperatorConfig, type OperatorType, type PipelineConfig, type PipelineContext, type PipelineLanguageSelection, PrepareSearchPackageOptions, PreparedAnonymizer, PreparedNativeAnonymizer, PreparedNativePipeline, PreparedNativeRedactionSession, PreparedNativeSessionRedactionPlan, PreparedPackageSource, PreparedSearch, type RedactionResult, type ReviewDecision, type ReviewedEntity, SESSION_CALLER_INPUTS_JSON_MAX_BYTES, SESSION_CALLER_MAX_INPUTS, SUPPORTED_LANGUAGES, SharedNativeDiagnosticsJsonOptions, SharedNativeDiagnosticsStreamJsonOptions, SharedNativePreparedPackageOptions, SharedNativeRedactTextJsonOptions, SharedNativeRedactTextOptions, SharedNativeRedactTextStreamJsonOptions, SharedNativeSearchPackageOptions, type SupportedLanguage, WasmBindingOptions, assertNativeBindingVersion, assertNativePipelineSupported, convert_external_detection_batch, createNativeAnonymizerFromConfig, createNativeAnonymizerFromPackage, createNativePipelineFromConfig, createNativePipelineFromPackage, createPipeline, createPipelineContext, create_pipeline, deanonymise, defaultPackageUrl, diagnostics_json, diagnostics_stream_json, encodeNativeSearchConfig, encodeNativeSearchConfigInput, exportRedactionKey, getBinding, getDefaultPipeline, getNativeBindingVersion, getNativePipelineCompatibility, inspect_pdf_json, isNativeAnonymizeBinding, loadDefaultPipeline, loadPipeline, load_prepared_package, native_package_version, normalize_for_search, prepareNativePipelineConfig, prepareNativePipelinePackage, prepareNativeSearchPackage, prepare_search_package, redactDefaultText, redactDefaultTextJson, redact_text, redact_text_json, redact_text_stream_json, summary_diagnostics_json };
1188
1198
  //# sourceMappingURL=wasm.d.mts.map
package/dist/wasm.mjs CHANGED
@@ -838,50 +838,62 @@ const assertPdfPixelPages = (pagePixels) => {
838
838
  for (const [index, page] of pagePixels.entries()) if (!(page instanceof Uint8Array)) throw new TypeError(`PDF pagePixels[${index}] must be a Uint8Array`);
839
839
  };
840
840
  //#endregion
841
- //#region src/context.ts
842
- /** Create a fresh, empty pipeline context. */
843
- const createPipelineContext = () => ({
844
- nativePipelinePackage: null,
845
- nativePipelinePackageKey: "",
846
- nativePipelinePackagePromise: null
847
- });
848
- /**
849
- * Module-level default context. Used when callers
850
- * don't provide an explicit context, preserving full
851
- * backward compatibility with the existing API.
852
- */
853
- const defaultContext = createPipelineContext();
854
- //#endregion
855
- //#region src/redact.ts
856
- /**
857
- * Serialize the redaction key to JSON for export.
858
- * Includes operator metadata so the export is self-describing.
859
- */
860
- const exportRedactionKey = (redactionMap, operatorMap) => {
861
- const entries = {};
862
- for (const [placeholder, value] of redactionMap) entries[placeholder] = {
863
- original: value,
864
- operator: operatorMap.get(placeholder) ?? "replace"
865
- };
866
- return JSON.stringify({ entries }, null, 2);
867
- };
868
- /**
869
- * De-anonymise text using a redaction key.
870
- * Replaces placeholders back with original values.
871
- * Only works for reversible operators (replace).
872
- */
873
- const deanonymise = (redactedText, redactionMap) => {
874
- let result = redactedText;
875
- for (const [placeholder, original] of redactionMap) result = result.replaceAll(placeholder, original);
876
- return result;
877
- };
878
- //#endregion
879
- //#region src/types.ts
880
- const isLegalFormsEnabled = (config) => config.enableLegalForms !== false;
881
- //#endregion
882
- //#region src/language-scope.ts
883
- const scopeData = {
884
- _comment: "Default dictionary scopes for content language hints. Lower-level caller config can still override name corpus languages and deny-list countries independently.",
841
+ //#region src/data/language-scopes.json
842
+ var language_scopes_default = {
843
+ _comment: "Default dictionary scopes for content language hints. The all-language city scope preserves the data package defaults and covers every supported language. Lower-level caller config can still override name corpus languages and deny-list countries independently.",
844
+ allLanguageCityCountries: [
845
+ "AR",
846
+ "AT",
847
+ "AU",
848
+ "BE",
849
+ "BG",
850
+ "BO",
851
+ "BR",
852
+ "CA",
853
+ "CH",
854
+ "CL",
855
+ "CO",
856
+ "CR",
857
+ "CU",
858
+ "CZ",
859
+ "DE",
860
+ "DK",
861
+ "DO",
862
+ "EC",
863
+ "ES",
864
+ "FI",
865
+ "FR",
866
+ "GB",
867
+ "GR",
868
+ "GT",
869
+ "HN",
870
+ "HR",
871
+ "HU",
872
+ "IE",
873
+ "IT",
874
+ "LU",
875
+ "LV",
876
+ "MC",
877
+ "MD",
878
+ "MX",
879
+ "NI",
880
+ "NL",
881
+ "NO",
882
+ "NZ",
883
+ "PA",
884
+ "PE",
885
+ "PL",
886
+ "PT",
887
+ "PY",
888
+ "RO",
889
+ "SE",
890
+ "SI",
891
+ "SK",
892
+ "SV",
893
+ "US",
894
+ "UY",
895
+ "VE"
896
+ ],
885
897
  languages: {
886
898
  "cs": {
887
899
  "nameCorpusLanguages": ["cs", "sk"],
@@ -974,7 +986,10 @@ const scopeData = {
974
986
  }
975
987
  }
976
988
  };
977
- const normalizeLanguage$1 = (language) => language.trim().toLowerCase();
989
+ //#endregion
990
+ //#region src/language-scope.ts
991
+ const scopeData = language_scopes_default;
992
+ const normalizeLanguage$2 = (language) => language.trim().toLowerCase();
978
993
  const fallbackLanguage = (language) => {
979
994
  const index = language.indexOf("-");
980
995
  return index === -1 ? null : language.slice(0, index);
@@ -988,7 +1003,7 @@ const uniquePush = (target, values) => {
988
1003
  }
989
1004
  };
990
1005
  const resolveLanguageScope = (language) => {
991
- const normalized = normalizeLanguage$1(language);
1006
+ const normalized = normalizeLanguage$2(language);
992
1007
  if (normalized.length === 0) return null;
993
1008
  const exact = scopeData.languages[normalized];
994
1009
  if (exact !== void 0) return exact;
@@ -1004,21 +1019,50 @@ const applyPipelineLanguageScope = (config) => {
1004
1019
  if (languages.length === 0) return config;
1005
1020
  const nameCorpusLanguages = [];
1006
1021
  const denyListCountries = [];
1022
+ let hasResolvedScope = false;
1007
1023
  for (const language of languages) {
1008
1024
  const scope = resolveLanguageScope(language);
1009
1025
  if (scope === null) continue;
1026
+ hasResolvedScope = true;
1010
1027
  uniquePush(nameCorpusLanguages, scope.nameCorpusLanguages ?? []);
1011
1028
  uniquePush(denyListCountries, scope.denyListCountries ?? []);
1012
1029
  }
1013
1030
  const next = {};
1014
- if (config.nameCorpusLanguages === void 0 && nameCorpusLanguages.length > 0) next.nameCorpusLanguages = nameCorpusLanguages;
1015
- if (config.denyListCountries === void 0 && denyListCountries.length > 0) next.denyListCountries = denyListCountries;
1031
+ if (config.nameCorpusLanguages === void 0 && hasResolvedScope) next.nameCorpusLanguages = nameCorpusLanguages;
1032
+ if (config.denyListCountries === void 0 && hasResolvedScope) next.denyListCountries = denyListCountries;
1016
1033
  return Object.keys(next).length === 0 ? config : {
1017
1034
  ...config,
1018
1035
  ...next
1019
1036
  };
1020
1037
  };
1021
1038
  //#endregion
1039
+ //#region src/build-native-package.ts
1040
+ const EMPTY_NAME_CORPUS_SCOPE = ["und"];
1041
+ const defaultDictionaryBundleOptions = (config) => ({
1042
+ ...config.denyListCountries === void 0 ? { cityCountries: language_scopes_default.allLanguageCityCountries } : {
1043
+ countries: config.denyListCountries,
1044
+ cityCountries: config.denyListCountries
1045
+ },
1046
+ ...config.nameCorpusLanguages === void 0 ? {} : { nameLanguages: config.nameCorpusLanguages.length === 0 ? EMPTY_NAME_CORPUS_SCOPE : config.nameCorpusLanguages }
1047
+ });
1048
+ //#endregion
1049
+ //#region src/context.ts
1050
+ /** Create a fresh, empty pipeline context. */
1051
+ const createPipelineContext = () => ({
1052
+ nativePipelinePackage: null,
1053
+ nativePipelinePackageKey: "",
1054
+ nativePipelinePackagePromise: null
1055
+ });
1056
+ /**
1057
+ * Module-level default context. Used when callers
1058
+ * don't provide an explicit context, preserving full
1059
+ * backward compatibility with the existing API.
1060
+ */
1061
+ const defaultContext = createPipelineContext();
1062
+ //#endregion
1063
+ //#region src/types.ts
1064
+ const isLegalFormsEnabled = (config) => config.enableLegalForms !== false;
1065
+ //#endregion
1022
1066
  //#region src/util/language-selection.ts
1023
1067
  const normalizeLanguageCode = (language) => language.trim().toLowerCase();
1024
1068
  const normalizeLanguageSelection = (languages) => languages === void 0 ? [] : languages.map(normalizeLanguageCode).filter((language) => language.length > 0);
@@ -1208,6 +1252,141 @@ const nativePackageCacheKey = ({ binding, config, gazetteerEntries, compressed }
1208
1252
  pipelineConfigKey(config, gazetteerEntries)
1209
1253
  ].join(":");
1210
1254
  //#endregion
1255
+ //#region src/native-default-config.ts
1256
+ const DEFAULT_NATIVE_PIPELINE_CONFIG = {
1257
+ threshold: .3,
1258
+ enableTriggerPhrases: true,
1259
+ enableRegex: true,
1260
+ enableLegalForms: true,
1261
+ enableNameCorpus: true,
1262
+ enableDenyList: true,
1263
+ enableGazetteer: false,
1264
+ enableCountries: true,
1265
+ enableConfidenceBoost: true,
1266
+ enableCoreference: true,
1267
+ enableHotwordRules: true,
1268
+ enableZoneClassification: true,
1269
+ standaloneStreetDetection: "off",
1270
+ labels: [...DEFAULT_ENTITY_LABELS],
1271
+ workspaceId: "native-pipeline-default"
1272
+ };
1273
+ //#endregion
1274
+ //#region src/pipeline-language.ts
1275
+ const isSupportedLanguage = (language) => Object.hasOwn(language_scopes_default.languages, language);
1276
+ const SUPPORTED_LANGUAGES = Object.freeze(Object.keys(language_scopes_default.languages).filter(isSupportedLanguage).toSorted());
1277
+ const normalizeLanguage$1 = (language) => {
1278
+ if (typeof language !== "string") throw new TypeError("Pipeline language codes must be strings");
1279
+ const normalized = language.trim().toLowerCase();
1280
+ if (!isSupportedLanguage(normalized)) throw new RangeError(`Unsupported pipeline language ${JSON.stringify(language)}; expected one of: ${SUPPORTED_LANGUAGES.join(", ")}`);
1281
+ return normalized;
1282
+ };
1283
+ const normalizePipelineLanguageSelection = (selection) => {
1284
+ if (selection === void 0 || typeof selection === "string" && selection.trim().toLowerCase() === "all") return { type: "all" };
1285
+ const requested = Array.isArray(selection) ? selection : [selection];
1286
+ if (requested.length === 0) throw new RangeError("Pipeline language selection must not be empty");
1287
+ const normalized = [...new Set(requested.map(normalizeLanguage$1))].toSorted();
1288
+ const first = normalized.at(0);
1289
+ if (first === void 0) throw new RangeError("Pipeline language selection must not be empty");
1290
+ return {
1291
+ type: "languages",
1292
+ languages: [first, ...normalized.slice(1)]
1293
+ };
1294
+ };
1295
+ const pipelineLanguageSelectionKey = (selection) => selection.type === "all" ? "all" : selection.languages.join(",");
1296
+ //#endregion
1297
+ //#region src/create-pipeline.ts
1298
+ const dictionaryCache = /* @__PURE__ */ new Map();
1299
+ const semanticPipelineCache = /* @__PURE__ */ new WeakMap();
1300
+ const MAX_SEMANTIC_PIPELINE_CACHE_ENTRIES = 8;
1301
+ const getCachedEntry = (cache, key) => {
1302
+ const cached = cache.get(key);
1303
+ if (cached === void 0) return;
1304
+ cache.delete(key);
1305
+ cache.set(key, cached);
1306
+ return cached;
1307
+ };
1308
+ const setCachedEntry = (cache, key, value) => {
1309
+ cache.set(key, value);
1310
+ if (cache.size <= MAX_SEMANTIC_PIPELINE_CACHE_ENTRIES) return;
1311
+ const oldestKey = cache.keys().next().value;
1312
+ if (oldestKey !== void 0) cache.delete(oldestKey);
1313
+ };
1314
+ const loadSemanticDictionaries = (key, config) => {
1315
+ const cached = getCachedEntry(dictionaryCache, key);
1316
+ if (cached !== void 0) return cached;
1317
+ let dictionaries;
1318
+ dictionaries = import("@stll/anonymize-data/cities").then(({ loadDictionaryBundle }) => loadDictionaryBundle(defaultDictionaryBundleOptions(config))).catch((error) => {
1319
+ if (dictionaryCache.get(key) === dictionaries) dictionaryCache.delete(key);
1320
+ throw error;
1321
+ });
1322
+ setCachedEntry(dictionaryCache, key, dictionaries);
1323
+ return dictionaries;
1324
+ };
1325
+ const pipelineConfigFor = (selection) => {
1326
+ if (selection.type === "all") return {
1327
+ ...DEFAULT_NATIVE_PIPELINE_CONFIG,
1328
+ labels: [...DEFAULT_NATIVE_PIPELINE_CONFIG.labels]
1329
+ };
1330
+ const [language, ...languages] = selection.languages;
1331
+ return applyPipelineLanguageScope({
1332
+ ...DEFAULT_NATIVE_PIPELINE_CONFIG,
1333
+ labels: [...DEFAULT_NATIVE_PIPELINE_CONFIG.labels],
1334
+ workspaceId: `default-pipeline:${pipelineLanguageSelectionKey(selection)}`,
1335
+ ...languages.length === 0 ? { language } : { languages: [language, ...languages] }
1336
+ });
1337
+ };
1338
+ const semanticPipelineCacheFor = (binding) => {
1339
+ const cached = semanticPipelineCache.get(binding);
1340
+ if (cached !== void 0) return cached;
1341
+ const created = /* @__PURE__ */ new Map();
1342
+ semanticPipelineCache.set(binding, created);
1343
+ return created;
1344
+ };
1345
+ const createSemanticPipeline = ({ binding, selection }) => {
1346
+ const key = pipelineLanguageSelectionKey(selection);
1347
+ const cache = semanticPipelineCacheFor(binding);
1348
+ const cached = getCachedEntry(cache, key);
1349
+ if (cached !== void 0) return cached;
1350
+ const config = pipelineConfigFor(selection);
1351
+ let pipeline;
1352
+ pipeline = loadSemanticDictionaries(key, config).then((dictionaries) => createNativePipelineFromConfig({
1353
+ binding,
1354
+ config: {
1355
+ ...config,
1356
+ dictionaries
1357
+ }
1358
+ })).catch((error) => {
1359
+ if (cache.get(key) === pipeline) cache.delete(key);
1360
+ throw error;
1361
+ });
1362
+ setCachedEntry(cache, key, pipeline);
1363
+ return pipeline;
1364
+ };
1365
+ //#endregion
1366
+ //#region src/redact.ts
1367
+ /**
1368
+ * Serialize the redaction key to JSON for export.
1369
+ * Includes operator metadata so the export is self-describing.
1370
+ */
1371
+ const exportRedactionKey = (redactionMap, operatorMap) => {
1372
+ const entries = {};
1373
+ for (const [placeholder, value] of redactionMap) entries[placeholder] = {
1374
+ original: value,
1375
+ operator: operatorMap.get(placeholder) ?? "replace"
1376
+ };
1377
+ return JSON.stringify({ entries }, null, 2);
1378
+ };
1379
+ /**
1380
+ * De-anonymise text using a redaction key.
1381
+ * Replaces placeholders back with original values.
1382
+ * Only works for reversible operators (replace).
1383
+ */
1384
+ const deanonymise = (redactedText, redactionMap) => {
1385
+ let result = redactedText;
1386
+ for (const [placeholder, original] of redactionMap) result = result.replaceAll(placeholder, original);
1387
+ return result;
1388
+ };
1389
+ //#endregion
1211
1390
  //#region src/wasm.ts
1212
1391
  const GLUE_MODULE = "index.js";
1213
1392
  const WASM_MODULE = "index_bg.wasm";
@@ -1216,10 +1395,18 @@ const NATIVE_ASSET_DIR = "native";
1216
1395
  const ASSET_DIR_ENV = "STLL_ANONYMIZE_ASSET_DIR";
1217
1396
  const DEFAULT_PACKAGE_FILE = "native-pipeline.stlanonpkg";
1218
1397
  const LANGUAGE_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
1398
+ const HTML_MEDIA_TYPES = /* @__PURE__ */ new Set(["application/xhtml+xml", "text/html"]);
1219
1399
  const DEFAULT_PIPELINE_CACHE_KEY = "<default>";
1220
1400
  const DEFAULT_PIPELINE_CACHE_MAX_ENTRIES = 32;
1221
1401
  let bindingPromise;
1222
1402
  const defaultPipelineCache = /* @__PURE__ */ new Map();
1403
+ const unavailablePackageUrls = /* @__PURE__ */ new Set();
1404
+ var PreparedPackageUnavailableError = class extends Error {
1405
+ constructor(href, options) {
1406
+ super(`Prepared package is unavailable: ${href}`, options);
1407
+ this.name = "PreparedPackageUnavailableError";
1408
+ }
1409
+ };
1223
1410
  /** Instantiate (once) and return the wasm binding. Safe to call repeatedly:
1224
1411
  * the underlying wasm module is instantiated a single time and cached. */
1225
1412
  const getBinding = () => {
@@ -1272,11 +1459,27 @@ const toPackageBytes = async (source) => {
1272
1459
  if (source instanceof Uint8Array) return source;
1273
1460
  if (source instanceof ArrayBuffer) return new Uint8Array(source);
1274
1461
  const href = source instanceof URL ? source.href : source;
1275
- if (href.startsWith("file:")) return readFileUrlBytes(href);
1462
+ if (href.startsWith("file:")) try {
1463
+ return await readFileUrlBytes(href);
1464
+ } catch (error) {
1465
+ if (isMissingFileError(error)) throw new PreparedPackageUnavailableError(href, { cause: error });
1466
+ throw error;
1467
+ }
1276
1468
  const response = await fetch(href);
1277
- if (!response.ok) throw new Error(`Failed to fetch prepared package (${response.status} ${response.statusText})`);
1469
+ if (!response.ok) {
1470
+ if (response.status === 404) throw new PreparedPackageUnavailableError(href);
1471
+ throw new Error(`Failed to fetch prepared package (${response.status} ${response.statusText})`);
1472
+ }
1473
+ if (isHtmlResponse(response)) throw new PreparedPackageUnavailableError(href);
1278
1474
  return new Uint8Array(await response.arrayBuffer());
1279
1475
  };
1476
+ const isHtmlResponse = (response) => {
1477
+ const contentType = response.headers.get("content-type");
1478
+ if (contentType === null) return false;
1479
+ const mediaType = contentType.split(";", 1).at(0)?.trim().toLowerCase();
1480
+ return mediaType !== void 0 && HTML_MEDIA_TYPES.has(mediaType);
1481
+ };
1482
+ const isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
1280
1483
  /** Read a `file:` URL through node:fs. The import is dynamic and gated behind
1281
1484
  * the `file:` check (never reached in browsers); the specifier is a runtime
1282
1485
  * value so the bundler leaves it alone, mirroring the runtime glue import in
@@ -1317,6 +1520,7 @@ const loadDefaultPipeline = async (language, options) => {
1317
1520
  try {
1318
1521
  return await loadPipeline(defaultPackageUrl(language), options);
1319
1522
  } catch (error) {
1523
+ if (!(error instanceof PreparedPackageUnavailableError)) throw error;
1320
1524
  const normalized = language === void 0 ? void 0 : normalizeLanguage(language);
1321
1525
  const baseLanguage = normalized?.split("-").at(0);
1322
1526
  if (baseLanguage === void 0 || baseLanguage === normalized) throw error;
@@ -1371,6 +1575,37 @@ const getDefaultPipeline = (language, options) => {
1371
1575
  touchDefaultPipelineCacheEntry(key, pipeline);
1372
1576
  return pipeline;
1373
1577
  };
1578
+ const createPipeline = async ({ language, ...bindingOptions } = {}) => {
1579
+ const selection = normalizePipelineLanguageSelection(language);
1580
+ if (selection.type === "all") {
1581
+ const packageUrl = defaultPackageUrl();
1582
+ if (!unavailablePackageUrls.has(packageUrl.href)) try {
1583
+ return await getDefaultPipeline(void 0, bindingOptions);
1584
+ } catch (error) {
1585
+ if (!(error instanceof PreparedPackageUnavailableError)) throw error;
1586
+ unavailablePackageUrls.add(packageUrl.href);
1587
+ }
1588
+ return createSemanticPipeline({
1589
+ binding: await resolveBinding(bindingOptions),
1590
+ selection
1591
+ });
1592
+ }
1593
+ const [singleLanguage, ...additionalLanguages] = selection.languages;
1594
+ if (additionalLanguages.length === 0) {
1595
+ const packageUrl = defaultPackageUrl(singleLanguage);
1596
+ if (!unavailablePackageUrls.has(packageUrl.href)) try {
1597
+ return await getDefaultPipeline(singleLanguage, bindingOptions);
1598
+ } catch (error) {
1599
+ if (!(error instanceof PreparedPackageUnavailableError)) throw error;
1600
+ unavailablePackageUrls.add(packageUrl.href);
1601
+ }
1602
+ }
1603
+ return createSemanticPipeline({
1604
+ binding: await resolveBinding(bindingOptions),
1605
+ selection
1606
+ });
1607
+ };
1608
+ const create_pipeline = createPipeline;
1374
1609
  const redactDefaultText = async (fullText, operators, language) => (await getDefaultPipeline(language)).redactText(fullText, operators);
1375
1610
  const redactDefaultTextJson = async (fullText, operators, language) => (await getDefaultPipeline(language)).redact_text_json(fullText, operators);
1376
1611
  const native_package_version = async (options) => native_package_version$1(await resolveBinding(options));
@@ -1445,6 +1680,6 @@ const normalizeLanguage = (language) => {
1445
1680
  return normalized;
1446
1681
  };
1447
1682
  //#endregion
1448
- export { CALLER_DETECTION_CONTRACT_VERSION, CALLER_DETECTION_MAX_COUNT, CALLER_DETECTION_REQUEST_JSON_MAX_BYTES, CALLER_DETECTION_TEXT_MAX_BYTES, CAPABILITY_MANIFEST, CAPABILITY_MANIFEST_SCHEMA_VERSION, CAPABILITY_PARITY_PROFILES, CAPABILITY_RUNTIMES, CAPABILITY_SURFACES, DEFAULT_ENTITY_LABELS, DETECTION_SOURCES, DETECTOR_PRIORITY, ENTITY_CAPABILITIES, ENTITY_LABELS, ENTITY_SELECTIONS, EXTERNAL_DETECTION_BATCH_MAX_BYTES, EXTERNAL_DETECTION_BATCH_VERSION, EXTERNAL_DETECTION_DOCUMENT_MAX_BYTES, EXTERNAL_DETECTION_MAX_DETECTIONS, EXTERNAL_DETECTION_MAX_LABEL_MAPPINGS, EXTERNAL_DETECTION_MAX_METADATA_BYTES, EXTERNAL_DETECTION_OFFSET_UNITS, EXTERNAL_DETECTION_PROVIDER_ID_MAX_BYTES, NATIVE_BINDING_PARITY_MEMBERS, OPERATOR_TYPES, PreparedAnonymizer, PreparedNativeAnonymizer, PreparedNativePipeline, PreparedNativeRedactionSession, PreparedNativeSessionRedactionPlan, PreparedSearch, SESSION_CALLER_INPUTS_JSON_MAX_BYTES, SESSION_CALLER_MAX_INPUTS, assertNativeBindingVersion, assertNativePipelineSupported, convert_external_detection_batch, createNativeAnonymizerFromConfig, createNativeAnonymizerFromPackage, createNativePipelineFromConfig, createNativePipelineFromPackage, createPipelineContext, deanonymise, defaultPackageUrl, diagnostics_json, diagnostics_stream_json, encodeNativeSearchConfig, encodeNativeSearchConfigInput, exportRedactionKey, getBinding, getDefaultPipeline, getNativeBindingVersion, getNativePipelineCompatibility, inspect_pdf_json, isNativeAnonymizeBinding, loadDefaultPipeline, loadPipeline, load_prepared_package, native_package_version, normalize_for_search, prepareNativePipelineConfig, prepareNativePipelinePackage, prepareNativeSearchPackage, prepare_search_package, redactDefaultText, redactDefaultTextJson, redact_text, redact_text_json, redact_text_stream_json, summary_diagnostics_json };
1683
+ export { CALLER_DETECTION_CONTRACT_VERSION, CALLER_DETECTION_MAX_COUNT, CALLER_DETECTION_REQUEST_JSON_MAX_BYTES, CALLER_DETECTION_TEXT_MAX_BYTES, CAPABILITY_MANIFEST, CAPABILITY_MANIFEST_SCHEMA_VERSION, CAPABILITY_PARITY_PROFILES, CAPABILITY_RUNTIMES, CAPABILITY_SURFACES, DEFAULT_ENTITY_LABELS, DETECTION_SOURCES, DETECTOR_PRIORITY, ENTITY_CAPABILITIES, ENTITY_LABELS, ENTITY_SELECTIONS, EXTERNAL_DETECTION_BATCH_MAX_BYTES, EXTERNAL_DETECTION_BATCH_VERSION, EXTERNAL_DETECTION_DOCUMENT_MAX_BYTES, EXTERNAL_DETECTION_MAX_DETECTIONS, EXTERNAL_DETECTION_MAX_LABEL_MAPPINGS, EXTERNAL_DETECTION_MAX_METADATA_BYTES, EXTERNAL_DETECTION_OFFSET_UNITS, EXTERNAL_DETECTION_PROVIDER_ID_MAX_BYTES, NATIVE_BINDING_PARITY_MEMBERS, OPERATOR_TYPES, PreparedAnonymizer, PreparedNativeAnonymizer, PreparedNativePipeline, PreparedNativeRedactionSession, PreparedNativeSessionRedactionPlan, PreparedSearch, SESSION_CALLER_INPUTS_JSON_MAX_BYTES, SESSION_CALLER_MAX_INPUTS, SUPPORTED_LANGUAGES, assertNativeBindingVersion, assertNativePipelineSupported, convert_external_detection_batch, createNativeAnonymizerFromConfig, createNativeAnonymizerFromPackage, createNativePipelineFromConfig, createNativePipelineFromPackage, createPipeline, createPipelineContext, create_pipeline, deanonymise, defaultPackageUrl, diagnostics_json, diagnostics_stream_json, encodeNativeSearchConfig, encodeNativeSearchConfigInput, exportRedactionKey, getBinding, getDefaultPipeline, getNativeBindingVersion, getNativePipelineCompatibility, inspect_pdf_json, isNativeAnonymizeBinding, loadDefaultPipeline, loadPipeline, load_prepared_package, native_package_version, normalize_for_search, prepareNativePipelineConfig, prepareNativePipelinePackage, prepareNativeSearchPackage, prepare_search_package, redactDefaultText, redactDefaultTextJson, redact_text, redact_text_json, redact_text_stream_json, summary_diagnostics_json };
1449
1684
 
1450
1685
  //# sourceMappingURL=wasm.mjs.map