@stll/anonymize-wasm 2.1.0 → 2.3.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/dist/wasm.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DEFAULT_ENTITY_LABELS, DETECTION_SOURCES, DETECTOR_PRIORITY, ENTITY_CAPABILITIES, ENTITY_LABELS, ENTITY_SELECTIONS, OPERATOR_TYPES } from "./constants.mjs";
2
- import { CAPABILITY_MANIFEST, CAPABILITY_MANIFEST_SCHEMA_VERSION, CAPABILITY_RUNTIMES } from "./capabilities.mjs";
2
+ import { CAPABILITY_MANIFEST, CAPABILITY_MANIFEST_SCHEMA_VERSION, CAPABILITY_PARITY_PROFILES, CAPABILITY_RUNTIMES, CAPABILITY_SURFACES } from "./capabilities.mjs";
3
3
  //#region src/native.ts
4
4
  const CALLER_DETECTION_CONTRACT_VERSION = 2;
5
5
  const callerDetectionRequestJson = (detections) => JSON.stringify({
@@ -706,12 +706,31 @@ const pipelineConfigKey = (config, gazetteerEntries) => {
706
706
  const gazFingerprint = config.enableGazetteer && gazetteerEntries.length > 0 ? gazetteerEntries.map((entry) => `${entry.id}:${entry.canonical}:${entry.label}:${[...entry.variants].sort().join(",")}`).toSorted().join(";") : "";
707
707
  return `${config.enableDenyList}:${config.enableTriggerPhrases}:${legalFormsEnabled}:${config.enableNameCorpus}:${contentLanguageFingerprint(config)}:${config.nameCorpusLanguages?.toSorted().join(",") ?? ""}:${config.enableRegex}:${config.threshold}:${config.enableConfidenceBoost}:${config.enableHotwordRules === true}:${config.enableCoreference === true}:${config.enableZoneClassification === true}:${config.labels.toSorted().join(",")}:${config.denyListCountries?.toSorted().join(",") ?? ""}:${config.denyListRegions?.toSorted().join(",") ?? ""}:${config.denyListExcludeCategories?.toSorted().join(",") ?? ""}:${customDenyFingerprint}:${customRegexFingerprint}:${config.enableGazetteer}:${gazFingerprint}:${config.enableCountries !== false}`;
708
708
  };
709
- //#endregion
710
- //#region src/native-pipeline.ts
711
709
  const sharedPackageByDictionaries = /* @__PURE__ */ new WeakMap();
712
710
  const sharedPackageWithoutDictionaries = /* @__PURE__ */ new Map();
713
711
  const dictionaryCacheIds = /* @__PURE__ */ new WeakMap();
714
712
  let nextDictionaryCacheId = 0;
713
+ /** Record `key` as most-recently-used in `cache`, evicting the
714
+ * least-recently-used entry first once the cache is at capacity. A `Map`'s
715
+ * insertion order doubles as recency order here: touching an existing key
716
+ * deletes then re-sets it to move it to the end, and eviction drops the
717
+ * first (oldest) key.
718
+ *
719
+ * Evicting a still-in-flight build only drops the cache's reference to its
720
+ * promise; the caller that started the build (and any concurrent caller that
721
+ * already read the promise before eviction) still resolves it correctly via
722
+ * the guarded `sharedCache.get(key) === promise` checks in
723
+ * `getCachedNativePipelinePackage`. A later caller for the same key just
724
+ * misses the dedupe and starts a fresh build — bounded memory takes priority
725
+ * over perfect dedupe under cache pressure. */
726
+ const touchSharedPackageCacheEntry = (cache, key, value) => {
727
+ cache.delete(key);
728
+ if (cache.size >= 32) {
729
+ const oldestKey = cache.keys().next().value;
730
+ if (oldestKey !== void 0) cache.delete(oldestKey);
731
+ }
732
+ cache.set(key, value);
733
+ };
715
734
  const dictionaryCacheKey = (dictionaries) => {
716
735
  if (dictionaries === void 0) return "none";
717
736
  const existing = dictionaryCacheIds.get(dictionaries);
@@ -730,7 +749,7 @@ const sharedPackageCacheFor = (dictionaries) => {
730
749
  };
731
750
  const getNativePipelineCompatibility = (config) => {
732
751
  const unsupportedFeatures = [];
733
- if (config.enableNer) unsupportedFeatures.push("enableNer");
752
+ if ("enableNer" in config && Boolean(config.enableNer)) unsupportedFeatures.push("enableNer");
734
753
  if (unsupportedFeatures.length === 0) return { status: "supported" };
735
754
  return {
736
755
  status: "unsupported",
@@ -749,11 +768,8 @@ const encoder = new TextEncoder();
749
768
  * the separate bundle preferentially, and keeping the (large) dictionaries out
750
769
  * of the config JSON avoids serializing them twice.
751
770
  */
752
- const toAssembleInputs = ({ dictionaries, enableNer = false, ...config }, gazetteerEntries) => ({
753
- pipelineConfigJson: encoder.encode(JSON.stringify({
754
- ...config,
755
- enableNer
756
- })),
771
+ const toAssembleInputs = ({ dictionaries, ...config }, gazetteerEntries) => ({
772
+ pipelineConfigJson: encoder.encode(JSON.stringify(config)),
757
773
  dictionariesJson: dictionaries === void 0 ? void 0 : encoder.encode(JSON.stringify(dictionaries)),
758
774
  gazetteerJson: gazetteerEntries.length === 0 ? void 0 : encoder.encode(JSON.stringify(gazetteerEntries))
759
775
  });
@@ -807,6 +823,7 @@ const getCachedNativePipelinePackage = async ({ binding, config, gazetteerEntrie
807
823
  const sharedCache = sharedPackageCacheFor(scopedConfig.dictionaries);
808
824
  const shared = sharedCache.get(key);
809
825
  if (shared !== void 0) {
826
+ touchSharedPackageCacheEntry(sharedCache, key, shared);
810
827
  const packageBytes = await shared;
811
828
  ctx.nativePipelinePackage = packageBytes;
812
829
  ctx.nativePipelinePackageKey = key;
@@ -822,7 +839,7 @@ const getCachedNativePipelinePackage = async ({ binding, config, gazetteerEntrie
822
839
  compressed
823
840
  });
824
841
  ctx.nativePipelinePackagePromise = promise;
825
- sharedCache.set(key, promise);
842
+ touchSharedPackageCacheEntry(sharedCache, key, promise);
826
843
  let packageBytes;
827
844
  try {
828
845
  packageBytes = await promise;
@@ -856,6 +873,8 @@ const NODE_FS_MODULE = "node:fs/promises";
856
873
  const NATIVE_ASSET_DIR = "native";
857
874
  const DEFAULT_PACKAGE_FILE = "native-pipeline.stlanonpkg";
858
875
  const LANGUAGE_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
876
+ const DEFAULT_PIPELINE_CACHE_KEY = "<default>";
877
+ const DEFAULT_PIPELINE_CACHE_MAX_ENTRIES = 32;
859
878
  let bindingPromise;
860
879
  const defaultPipelineCache = /* @__PURE__ */ new Map();
861
880
  /** Instantiate (once) and return the wasm binding. Safe to call repeatedly:
@@ -933,6 +952,26 @@ const loadDefaultPipeline = async (language, options) => {
933
952
  return loadPipeline(defaultPackageUrl(baseLanguage), options);
934
953
  }
935
954
  };
955
+ /** Normalized cache key for {@link defaultPipelineCache}: `undefined` maps to
956
+ * the bundled-default sentinel, everything else goes through the same
957
+ * {@link normalizeLanguage} helper `loadDefaultPipeline`/`defaultPackageUrl`
958
+ * already validate against, so aliases that differ only by case or whitespace
959
+ * (e.g. `"EN"` vs `"en"`) share one cached pipeline instead of each minting
960
+ * their own. */
961
+ const defaultPipelineCacheKey = (language) => language === void 0 ? DEFAULT_PIPELINE_CACHE_KEY : normalizeLanguage(language);
962
+ /** Record `key` as most-recently-used in {@link defaultPipelineCache},
963
+ * evicting the least-recently-used entry first once the cache is at
964
+ * capacity. A `Map`'s insertion order doubles as recency order here: touching
965
+ * an existing key deletes then re-sets it to move it to the end, and
966
+ * eviction drops the first (oldest) key. */
967
+ const touchDefaultPipelineCacheEntry = (key, pipeline) => {
968
+ defaultPipelineCache.delete(key);
969
+ if (defaultPipelineCache.size >= DEFAULT_PIPELINE_CACHE_MAX_ENTRIES) {
970
+ const oldestKey = defaultPipelineCache.keys().next().value;
971
+ if (oldestKey !== void 0) defaultPipelineCache.delete(oldestKey);
972
+ }
973
+ defaultPipelineCache.set(key, pipeline);
974
+ };
936
975
  /** Cached variant of {@link loadDefaultPipeline}: the default pipeline for a
937
976
  * given language is fetched and prepared once, then reused.
938
977
  *
@@ -943,14 +982,22 @@ const loadDefaultPipeline = async (language, options) => {
943
982
  * alive. Injected-binding callers get a fresh pipeline each call. */
944
983
  const getDefaultPipeline = (language, options) => {
945
984
  if (options?.binding) return loadDefaultPipeline(language, options);
946
- const key = language ?? "<default>";
985
+ let key;
986
+ try {
987
+ key = defaultPipelineCacheKey(language);
988
+ } catch (error) {
989
+ return Promise.reject(error);
990
+ }
947
991
  const cached = defaultPipelineCache.get(key);
948
- if (cached !== void 0) return cached;
992
+ if (cached !== void 0) {
993
+ touchDefaultPipelineCacheEntry(key, cached);
994
+ return cached;
995
+ }
949
996
  const pipeline = loadDefaultPipeline(language).catch((error) => {
950
997
  defaultPipelineCache.delete(key);
951
998
  throw error;
952
999
  });
953
- defaultPipelineCache.set(key, pipeline);
1000
+ touchDefaultPipelineCacheEntry(key, pipeline);
954
1001
  return pipeline;
955
1002
  };
956
1003
  const redactDefaultText = async (fullText, operators, language) => (await getDefaultPipeline(language)).redactText(fullText, operators);
@@ -1030,6 +1077,6 @@ const normalizeLanguage = (language) => {
1030
1077
  return normalized;
1031
1078
  };
1032
1079
  //#endregion
1033
- export { CALLER_DETECTION_CONTRACT_VERSION, CAPABILITY_MANIFEST, CAPABILITY_MANIFEST_SCHEMA_VERSION, CAPABILITY_RUNTIMES, DEFAULT_ENTITY_LABELS, DETECTION_SOURCES, DETECTOR_PRIORITY, ENTITY_CAPABILITIES, ENTITY_LABELS, ENTITY_SELECTIONS, OPERATOR_TYPES, PreparedAnonymizer, PreparedNativeAnonymizer, PreparedNativePipeline, PreparedNativeRedactionSession, PreparedNativeSessionRedactionPlan, PreparedSearch, assertNativeBindingVersion, assertNativePipelineSupported, createNativeAnonymizerFromConfig, createNativeAnonymizerFromPackage, createNativePipelineFromConfig, createNativePipelineFromPackage, createPipelineContext, deanonymise, defaultPackageUrl, diagnostics_json, diagnostics_stream_json, encodeNativeSearchConfig, encodeNativeSearchConfigInput, exportRedactionKey, getBinding, getDefaultPipeline, getNativeBindingVersion, getNativePipelineCompatibility, 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 };
1080
+ export { CALLER_DETECTION_CONTRACT_VERSION, 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, OPERATOR_TYPES, PreparedAnonymizer, PreparedNativeAnonymizer, PreparedNativePipeline, PreparedNativeRedactionSession, PreparedNativeSessionRedactionPlan, PreparedSearch, assertNativeBindingVersion, assertNativePipelineSupported, createNativeAnonymizerFromConfig, createNativeAnonymizerFromPackage, createNativePipelineFromConfig, createNativePipelineFromPackage, createPipelineContext, deanonymise, defaultPackageUrl, diagnostics_json, diagnostics_stream_json, encodeNativeSearchConfig, encodeNativeSearchConfigInput, exportRedactionKey, getBinding, getDefaultPipeline, getNativeBindingVersion, getNativePipelineCompatibility, 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 };
1034
1081
 
1035
1082
  //# sourceMappingURL=wasm.mjs.map