tempest-react-sdk 0.31.1 → 0.32.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.
Files changed (78) hide show
  1. package/README.md +2 -0
  2. package/bin/lib/design/collect.mjs +103 -0
  3. package/bin/lib/design/findings.mjs +70 -0
  4. package/bin/lib/design/functions.mjs +188 -0
  5. package/bin/lib/design/functions.test.mjs +127 -0
  6. package/bin/lib/design/index.mjs +80 -0
  7. package/bin/lib/design/index.test.mjs +129 -0
  8. package/bin/lib/design/markers.mjs +76 -0
  9. package/bin/lib/design/markers.test.mjs +60 -0
  10. package/bin/lib/design/mask.mjs +209 -0
  11. package/bin/lib/design/mask.test.mjs +69 -0
  12. package/bin/lib/design/scan.mjs +229 -0
  13. package/bin/lib/design/scan.test.mjs +218 -0
  14. package/bin/lib/doctor/doctor.e2e.test.mjs +79 -2
  15. package/bin/tempest.mjs +83 -3
  16. package/dist/imaging/canvas.cjs +2 -0
  17. package/dist/imaging/canvas.cjs.map +1 -0
  18. package/dist/imaging/canvas.js +23 -0
  19. package/dist/imaging/canvas.js.map +1 -0
  20. package/dist/imaging/compress.cjs +2 -0
  21. package/dist/imaging/compress.cjs.map +1 -0
  22. package/dist/imaging/compress.js +43 -0
  23. package/dist/imaging/compress.js.map +1 -0
  24. package/dist/imaging/decode.cjs +2 -0
  25. package/dist/imaging/decode.cjs.map +1 -0
  26. package/dist/imaging/decode.js +42 -0
  27. package/dist/imaging/decode.js.map +1 -0
  28. package/dist/imaging/encode.cjs +2 -0
  29. package/dist/imaging/encode.cjs.map +1 -0
  30. package/dist/imaging/encode.js +48 -0
  31. package/dist/imaging/encode.js.map +1 -0
  32. package/dist/imaging/exceptions.cjs +2 -0
  33. package/dist/imaging/exceptions.cjs.map +1 -0
  34. package/dist/imaging/exceptions.js +26 -0
  35. package/dist/imaging/exceptions.js.map +1 -0
  36. package/dist/imaging/thumbnails.cjs +2 -0
  37. package/dist/imaging/thumbnails.cjs.map +1 -0
  38. package/dist/imaging/thumbnails.js +34 -0
  39. package/dist/imaging/thumbnails.js.map +1 -0
  40. package/dist/imaging/transform.cjs +2 -0
  41. package/dist/imaging/transform.cjs.map +1 -0
  42. package/dist/imaging/transform.js +97 -0
  43. package/dist/imaging/transform.js.map +1 -0
  44. package/dist/imaging/use-image-processing.cjs +2 -0
  45. package/dist/imaging/use-image-processing.cjs.map +1 -0
  46. package/dist/imaging/use-image-processing.js +45 -0
  47. package/dist/imaging/use-image-processing.js.map +1 -0
  48. package/dist/imaging.cjs +1 -0
  49. package/dist/imaging.d.ts +541 -0
  50. package/dist/imaging.js +9 -0
  51. package/dist/tabular/assets.cjs +2 -0
  52. package/dist/tabular/assets.cjs.map +1 -0
  53. package/dist/tabular/assets.js +19 -0
  54. package/dist/tabular/assets.js.map +1 -0
  55. package/dist/tabular/cache.cjs +2 -0
  56. package/dist/tabular/cache.cjs.map +1 -0
  57. package/dist/tabular/cache.js +48 -0
  58. package/dist/tabular/cache.js.map +1 -0
  59. package/dist/tabular/exceptions.cjs +2 -0
  60. package/dist/tabular/exceptions.cjs.map +1 -0
  61. package/dist/tabular/exceptions.js +30 -0
  62. package/dist/tabular/exceptions.js.map +1 -0
  63. package/dist/tabular/manifest.cjs +2 -0
  64. package/dist/tabular/manifest.cjs.map +1 -0
  65. package/dist/tabular/manifest.js +42 -0
  66. package/dist/tabular/manifest.js.map +1 -0
  67. package/dist/tabular/predictor.cjs +2 -0
  68. package/dist/tabular/predictor.cjs.map +1 -0
  69. package/dist/tabular/predictor.js +111 -0
  70. package/dist/tabular/predictor.js.map +1 -0
  71. package/dist/tabular/use-tabular-predictor.cjs +2 -0
  72. package/dist/tabular/use-tabular-predictor.cjs.map +1 -0
  73. package/dist/tabular/use-tabular-predictor.js +51 -0
  74. package/dist/tabular/use-tabular-predictor.js.map +1 -0
  75. package/dist/tabular.cjs +1 -0
  76. package/dist/tabular.d.ts +489 -0
  77. package/dist/tabular.js +7 -0
  78. package/package.json +11 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.cjs","names":[],"sources":["../../src/tabular/cache.ts"],"sourcesContent":["/**\n * Keeping model bytes on the device, so the second visit works offline.\n *\n * A prediction that needs the network is not offline inference. The model\n * file is the one asset the app cannot re-derive, so it goes into Cache\n * Storage on first load and is read from there afterwards.\n *\n * Cache-first, not network-first, and deliberately: a model file is\n * immutable for a given version, so revalidating it on every load spends a\n * round trip to learn nothing. Publish a new version under a new URL (or\n * pass `revalidate`) when the model changes.\n */\n\nimport { ModelFetchError } from \"./exceptions\";\n\n/** Cache Storage bucket used when the caller does not name one. */\nexport const DEFAULT_MODEL_CACHE = \"tempest-tabular-models\";\n\n/** Options for {@link fetchModelBytes}. */\nexport interface ModelCacheOptions {\n /** Cache Storage bucket name. */\n readonly cacheName?: string;\n /**\n * Go to the network first and fall back to the cache.\n *\n * For a URL that serves \"whatever is current\" rather than a pinned\n * version. Costs a round trip on every load when online.\n */\n readonly revalidate?: boolean;\n /** `fetch` options, e.g. credentials for a private model endpoint. */\n readonly requestInit?: RequestInit;\n}\n\n/**\n * Whether Cache Storage is usable here.\n *\n * Absent in a non-secure context, in Node, and in some private-mode\n * browsers. The module degrades to a plain fetch rather than failing —\n * losing offline support is better than losing inference.\n *\n * @returns `true` when `caches` can be used.\n */\nfunction hasCacheStorage(): boolean {\n return typeof globalThis.caches !== \"undefined\";\n}\n\n/**\n * Fetch the model bytes, preferring the on-device copy.\n *\n * @example\n * ```ts\n * const bytes = await fetchModelBytes(\"/models/classifier-v3.onnx\");\n * const predictor = await TabularPredictor.create(bytes);\n * ```\n *\n * @param url Where the model lives.\n * @param options Cache bucket, revalidation and `fetch` options.\n * @returns The model bytes.\n * @throws {@link ModelFetchError} when the model is neither cached nor\n * reachable — which is the \"offline and never warmed\" case, and the\n * message says so.\n */\nexport async function fetchModelBytes(\n url: string,\n options: ModelCacheOptions = {},\n): Promise<Uint8Array> {\n const cacheName = options.cacheName ?? DEFAULT_MODEL_CACHE;\n\n if (!hasCacheStorage()) {\n return await downloadBytes(url, options.requestInit);\n }\n\n const cache = await globalThis.caches.open(cacheName);\n\n if (options.revalidate !== true) {\n const cached = await cache.match(url);\n if (cached !== undefined) return new Uint8Array(await cached.arrayBuffer());\n }\n\n try {\n const response = await fetch(url, options.requestInit);\n if (!response.ok) {\n throw new ModelFetchError(\n `Failed to download the model: ${response.status} ${response.statusText}`,\n );\n }\n await cache.put(url, response.clone());\n return new Uint8Array(await response.arrayBuffer());\n } catch (error) {\n const cached = await cache.match(url);\n if (cached !== undefined) return new Uint8Array(await cached.arrayBuffer());\n if (error instanceof ModelFetchError) throw error;\n throw new ModelFetchError(\n `The model is not cached and could not be downloaded: ${url}. ` +\n \"Warm the cache while online — prefetch it at install time, or \" +\n \"precache it in the service worker.\",\n { cause: error },\n );\n }\n}\n\n/**\n * Download without touching the cache.\n *\n * @param url Where the model lives.\n * @param requestInit `fetch` options.\n * @returns The model bytes.\n * @throws {@link ModelFetchError} when the request fails.\n */\nasync function downloadBytes(url: string, requestInit?: RequestInit): Promise<Uint8Array> {\n try {\n const response = await fetch(url, requestInit);\n if (!response.ok) {\n throw new ModelFetchError(\n `Failed to download the model: ${response.status} ${response.statusText}`,\n );\n }\n return new Uint8Array(await response.arrayBuffer());\n } catch (error) {\n if (error instanceof ModelFetchError) throw error;\n throw new ModelFetchError(`Failed to download the model: ${url}`, { cause: error });\n }\n}\n\n/**\n * Whether a model is already on the device.\n *\n * Useful for showing \"available offline\" in the UI, and for deciding\n * whether to prefetch on a metered connection.\n *\n * @param url The model URL.\n * @param cacheName Cache Storage bucket name.\n * @returns `true` when the bytes are cached.\n */\nexport async function isModelCached(\n url: string,\n cacheName: string = DEFAULT_MODEL_CACHE,\n): Promise<boolean> {\n if (!hasCacheStorage()) return false;\n const cache = await globalThis.caches.open(cacheName);\n return (await cache.match(url)) !== undefined;\n}\n\n/**\n * Store model bytes without downloading them.\n *\n * For an app that already has the bytes — from a file input, or from a\n * bundle it unpacked — and wants the next load to find them cached.\n *\n * @param url The URL to key the entry under.\n * @param bytes The model bytes.\n * @param cacheName Cache Storage bucket name.\n * @returns `true` when the entry was stored, `false` without Cache Storage.\n */\nexport async function cacheModelBytes(\n url: string,\n bytes: Uint8Array,\n cacheName: string = DEFAULT_MODEL_CACHE,\n): Promise<boolean> {\n if (!hasCacheStorage()) return false;\n const cache = await globalThis.caches.open(cacheName);\n const body = new Uint8Array(bytes).buffer as ArrayBuffer;\n await cache.put(\n url,\n new Response(body, {\n headers: { \"content-type\": \"application/octet-stream\" },\n }),\n );\n return true;\n}\n\n/**\n * Drop cached models.\n *\n * @param url A specific model to evict; omit to delete the whole bucket.\n * @param cacheName Cache Storage bucket name.\n * @returns `true` when something was deleted.\n */\nexport async function clearModelCache(\n url?: string,\n cacheName: string = DEFAULT_MODEL_CACHE,\n): Promise<boolean> {\n if (!hasCacheStorage()) return false;\n if (url === undefined) return await globalThis.caches.delete(cacheName);\n const cache = await globalThis.caches.open(cacheName);\n return await cache.delete(url);\n}\n"],"mappings":"oCAgBA,IAAa,EAAsB,yBA0BnC,SAAS,GAA2B,CAChC,OAAc,WAAW,SAAW,MACxC,CAkBA,eAAsB,EAClB,EACA,EAA6B,CAAC,EACX,CACnB,IAAM,EAAY,EAAQ,WAAA,yBAE1B,GAAI,CAAC,EAAgB,EACjB,OAAO,MAAM,EAAc,EAAK,EAAQ,WAAW,EAGvD,IAAM,EAAQ,MAAM,WAAW,OAAO,KAAK,CAAS,EAEpD,GAAI,EAAQ,aAAe,GAAM,CAC7B,IAAM,EAAS,MAAM,EAAM,MAAM,CAAG,EACpC,GAAI,IAAW,IAAA,GAAW,OAAO,IAAI,WAAW,MAAM,EAAO,YAAY,CAAC,CAC9E,CAEA,GAAI,CACA,IAAM,EAAW,MAAM,MAAM,EAAK,EAAQ,WAAW,EACrD,GAAI,CAAC,EAAS,GACV,MAAM,IAAI,EAAA,gBACN,iCAAiC,EAAS,OAAO,GAAG,EAAS,YACjE,EAGJ,OADA,MAAM,EAAM,IAAI,EAAK,EAAS,MAAM,CAAC,EAC9B,IAAI,WAAW,MAAM,EAAS,YAAY,CAAC,CACtD,OAAS,EAAO,CACZ,IAAM,EAAS,MAAM,EAAM,MAAM,CAAG,EACpC,GAAI,IAAW,IAAA,GAAW,OAAO,IAAI,WAAW,MAAM,EAAO,YAAY,CAAC,EAE1E,MADI,aAAiB,EAAA,gBAAuB,EACtC,IAAI,EAAA,gBACN,wDAAwD,EAAI,oGAG5D,CAAE,MAAO,CAAM,CACnB,CACJ,CACJ,CAUA,eAAe,EAAc,EAAa,EAAgD,CACtF,GAAI,CACA,IAAM,EAAW,MAAM,MAAM,EAAK,CAAW,EAC7C,GAAI,CAAC,EAAS,GACV,MAAM,IAAI,EAAA,gBACN,iCAAiC,EAAS,OAAO,GAAG,EAAS,YACjE,EAEJ,OAAO,IAAI,WAAW,MAAM,EAAS,YAAY,CAAC,CACtD,OAAS,EAAO,CAEZ,MADI,aAAiB,EAAA,gBAAuB,EACtC,IAAI,EAAA,gBAAgB,iCAAiC,IAAO,CAAE,MAAO,CAAM,CAAC,CACtF,CACJ,CAYA,eAAsB,EAClB,EACA,EAAoB,EACJ,CAGhB,OAFK,EAAgB,EAEb,MAAM,MADM,WAAW,OAAO,KAAK,CAAS,EAAA,CAChC,MAAM,CAAG,IAAO,IAAA,GAFL,EAGnC,CAaA,eAAsB,EAClB,EACA,EACA,EAAoB,EACJ,CAChB,GAAI,CAAC,EAAgB,EAAG,MAAO,GAC/B,IAAM,EAAQ,MAAM,WAAW,OAAO,KAAK,CAAS,EAC9C,EAAO,IAAI,WAAW,CAAK,CAAC,CAAC,OAOnC,OANA,MAAM,EAAM,IACR,EACA,IAAI,SAAS,EAAM,CACf,QAAS,CAAE,eAAgB,0BAA2B,CAC1D,CAAC,CACL,EACO,EACX,CASA,eAAsB,EAClB,EACA,EAAoB,EACJ,CAIhB,OAHK,EAAgB,EACjB,IAAQ,IAAA,GAAkB,MAAM,WAAW,OAAO,OAAO,CAAS,EAE/D,MAAM,MADO,WAAW,OAAO,KAAK,CAAS,EAAA,CACjC,OAAO,CAAG,EAHE,EAInC"}
@@ -0,0 +1,48 @@
1
+ import { ModelFetchError as e } from "./exceptions.js";
2
+ //#region src/tabular/cache.ts
3
+ var t = "tempest-tabular-models";
4
+ function n() {
5
+ return globalThis.caches !== void 0;
6
+ }
7
+ async function r(t, r = {}) {
8
+ let a = r.cacheName ?? "tempest-tabular-models";
9
+ if (!n()) return await i(t, r.requestInit);
10
+ let o = await globalThis.caches.open(a);
11
+ if (r.revalidate !== !0) {
12
+ let e = await o.match(t);
13
+ if (e !== void 0) return new Uint8Array(await e.arrayBuffer());
14
+ }
15
+ try {
16
+ let n = await fetch(t, r.requestInit);
17
+ if (!n.ok) throw new e(`Failed to download the model: ${n.status} ${n.statusText}`);
18
+ return await o.put(t, n.clone()), new Uint8Array(await n.arrayBuffer());
19
+ } catch (n) {
20
+ let r = await o.match(t);
21
+ if (r !== void 0) return new Uint8Array(await r.arrayBuffer());
22
+ throw n instanceof e ? n : new e(`The model is not cached and could not be downloaded: ${t}. Warm the cache while online — prefetch it at install time, or precache it in the service worker.`, { cause: n });
23
+ }
24
+ }
25
+ async function i(t, n) {
26
+ try {
27
+ let r = await fetch(t, n);
28
+ if (!r.ok) throw new e(`Failed to download the model: ${r.status} ${r.statusText}`);
29
+ return new Uint8Array(await r.arrayBuffer());
30
+ } catch (n) {
31
+ throw n instanceof e ? n : new e(`Failed to download the model: ${t}`, { cause: n });
32
+ }
33
+ }
34
+ async function a(e, r = t) {
35
+ return n() ? await (await globalThis.caches.open(r)).match(e) !== void 0 : !1;
36
+ }
37
+ async function o(e, r, i = t) {
38
+ if (!n()) return !1;
39
+ let a = await globalThis.caches.open(i), o = new Uint8Array(r).buffer;
40
+ return await a.put(e, new Response(o, { headers: { "content-type": "application/octet-stream" } })), !0;
41
+ }
42
+ async function s(e, r = t) {
43
+ return n() ? e === void 0 ? await globalThis.caches.delete(r) : await (await globalThis.caches.open(r)).delete(e) : !1;
44
+ }
45
+ //#endregion
46
+ export { t as DEFAULT_MODEL_CACHE, o as cacheModelBytes, s as clearModelCache, r as fetchModelBytes, a as isModelCached };
47
+
48
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","names":[],"sources":["../../src/tabular/cache.ts"],"sourcesContent":["/**\n * Keeping model bytes on the device, so the second visit works offline.\n *\n * A prediction that needs the network is not offline inference. The model\n * file is the one asset the app cannot re-derive, so it goes into Cache\n * Storage on first load and is read from there afterwards.\n *\n * Cache-first, not network-first, and deliberately: a model file is\n * immutable for a given version, so revalidating it on every load spends a\n * round trip to learn nothing. Publish a new version under a new URL (or\n * pass `revalidate`) when the model changes.\n */\n\nimport { ModelFetchError } from \"./exceptions\";\n\n/** Cache Storage bucket used when the caller does not name one. */\nexport const DEFAULT_MODEL_CACHE = \"tempest-tabular-models\";\n\n/** Options for {@link fetchModelBytes}. */\nexport interface ModelCacheOptions {\n /** Cache Storage bucket name. */\n readonly cacheName?: string;\n /**\n * Go to the network first and fall back to the cache.\n *\n * For a URL that serves \"whatever is current\" rather than a pinned\n * version. Costs a round trip on every load when online.\n */\n readonly revalidate?: boolean;\n /** `fetch` options, e.g. credentials for a private model endpoint. */\n readonly requestInit?: RequestInit;\n}\n\n/**\n * Whether Cache Storage is usable here.\n *\n * Absent in a non-secure context, in Node, and in some private-mode\n * browsers. The module degrades to a plain fetch rather than failing —\n * losing offline support is better than losing inference.\n *\n * @returns `true` when `caches` can be used.\n */\nfunction hasCacheStorage(): boolean {\n return typeof globalThis.caches !== \"undefined\";\n}\n\n/**\n * Fetch the model bytes, preferring the on-device copy.\n *\n * @example\n * ```ts\n * const bytes = await fetchModelBytes(\"/models/classifier-v3.onnx\");\n * const predictor = await TabularPredictor.create(bytes);\n * ```\n *\n * @param url Where the model lives.\n * @param options Cache bucket, revalidation and `fetch` options.\n * @returns The model bytes.\n * @throws {@link ModelFetchError} when the model is neither cached nor\n * reachable — which is the \"offline and never warmed\" case, and the\n * message says so.\n */\nexport async function fetchModelBytes(\n url: string,\n options: ModelCacheOptions = {},\n): Promise<Uint8Array> {\n const cacheName = options.cacheName ?? DEFAULT_MODEL_CACHE;\n\n if (!hasCacheStorage()) {\n return await downloadBytes(url, options.requestInit);\n }\n\n const cache = await globalThis.caches.open(cacheName);\n\n if (options.revalidate !== true) {\n const cached = await cache.match(url);\n if (cached !== undefined) return new Uint8Array(await cached.arrayBuffer());\n }\n\n try {\n const response = await fetch(url, options.requestInit);\n if (!response.ok) {\n throw new ModelFetchError(\n `Failed to download the model: ${response.status} ${response.statusText}`,\n );\n }\n await cache.put(url, response.clone());\n return new Uint8Array(await response.arrayBuffer());\n } catch (error) {\n const cached = await cache.match(url);\n if (cached !== undefined) return new Uint8Array(await cached.arrayBuffer());\n if (error instanceof ModelFetchError) throw error;\n throw new ModelFetchError(\n `The model is not cached and could not be downloaded: ${url}. ` +\n \"Warm the cache while online — prefetch it at install time, or \" +\n \"precache it in the service worker.\",\n { cause: error },\n );\n }\n}\n\n/**\n * Download without touching the cache.\n *\n * @param url Where the model lives.\n * @param requestInit `fetch` options.\n * @returns The model bytes.\n * @throws {@link ModelFetchError} when the request fails.\n */\nasync function downloadBytes(url: string, requestInit?: RequestInit): Promise<Uint8Array> {\n try {\n const response = await fetch(url, requestInit);\n if (!response.ok) {\n throw new ModelFetchError(\n `Failed to download the model: ${response.status} ${response.statusText}`,\n );\n }\n return new Uint8Array(await response.arrayBuffer());\n } catch (error) {\n if (error instanceof ModelFetchError) throw error;\n throw new ModelFetchError(`Failed to download the model: ${url}`, { cause: error });\n }\n}\n\n/**\n * Whether a model is already on the device.\n *\n * Useful for showing \"available offline\" in the UI, and for deciding\n * whether to prefetch on a metered connection.\n *\n * @param url The model URL.\n * @param cacheName Cache Storage bucket name.\n * @returns `true` when the bytes are cached.\n */\nexport async function isModelCached(\n url: string,\n cacheName: string = DEFAULT_MODEL_CACHE,\n): Promise<boolean> {\n if (!hasCacheStorage()) return false;\n const cache = await globalThis.caches.open(cacheName);\n return (await cache.match(url)) !== undefined;\n}\n\n/**\n * Store model bytes without downloading them.\n *\n * For an app that already has the bytes — from a file input, or from a\n * bundle it unpacked — and wants the next load to find them cached.\n *\n * @param url The URL to key the entry under.\n * @param bytes The model bytes.\n * @param cacheName Cache Storage bucket name.\n * @returns `true` when the entry was stored, `false` without Cache Storage.\n */\nexport async function cacheModelBytes(\n url: string,\n bytes: Uint8Array,\n cacheName: string = DEFAULT_MODEL_CACHE,\n): Promise<boolean> {\n if (!hasCacheStorage()) return false;\n const cache = await globalThis.caches.open(cacheName);\n const body = new Uint8Array(bytes).buffer as ArrayBuffer;\n await cache.put(\n url,\n new Response(body, {\n headers: { \"content-type\": \"application/octet-stream\" },\n }),\n );\n return true;\n}\n\n/**\n * Drop cached models.\n *\n * @param url A specific model to evict; omit to delete the whole bucket.\n * @param cacheName Cache Storage bucket name.\n * @returns `true` when something was deleted.\n */\nexport async function clearModelCache(\n url?: string,\n cacheName: string = DEFAULT_MODEL_CACHE,\n): Promise<boolean> {\n if (!hasCacheStorage()) return false;\n if (url === undefined) return await globalThis.caches.delete(cacheName);\n const cache = await globalThis.caches.open(cacheName);\n return await cache.delete(url);\n}\n"],"mappings":";;AAgBA,IAAa,IAAsB;AA0BnC,SAAS,IAA2B;CAChC,OAAc,WAAW,WAAW;AACxC;AAkBA,eAAsB,EAClB,GACA,IAA6B,CAAC,GACX;CACnB,IAAM,IAAY,EAAQ,aAAA;CAE1B,IAAI,CAAC,EAAgB,GACjB,OAAO,MAAM,EAAc,GAAK,EAAQ,WAAW;CAGvD,IAAM,IAAQ,MAAM,WAAW,OAAO,KAAK,CAAS;CAEpD,IAAI,EAAQ,eAAe,IAAM;EAC7B,IAAM,IAAS,MAAM,EAAM,MAAM,CAAG;EACpC,IAAI,MAAW,KAAA,GAAW,OAAO,IAAI,WAAW,MAAM,EAAO,YAAY,CAAC;CAC9E;CAEA,IAAI;EACA,IAAM,IAAW,MAAM,MAAM,GAAK,EAAQ,WAAW;EACrD,IAAI,CAAC,EAAS,IACV,MAAM,IAAI,EACN,iCAAiC,EAAS,OAAO,GAAG,EAAS,YACjE;EAGJ,OADA,MAAM,EAAM,IAAI,GAAK,EAAS,MAAM,CAAC,GAC9B,IAAI,WAAW,MAAM,EAAS,YAAY,CAAC;CACtD,SAAS,GAAO;EACZ,IAAM,IAAS,MAAM,EAAM,MAAM,CAAG;EACpC,IAAI,MAAW,KAAA,GAAW,OAAO,IAAI,WAAW,MAAM,EAAO,YAAY,CAAC;EAE1E,MADI,aAAiB,IAAuB,IACtC,IAAI,EACN,wDAAwD,EAAI,qGAG5D,EAAE,OAAO,EAAM,CACnB;CACJ;AACJ;AAUA,eAAe,EAAc,GAAa,GAAgD;CACtF,IAAI;EACA,IAAM,IAAW,MAAM,MAAM,GAAK,CAAW;EAC7C,IAAI,CAAC,EAAS,IACV,MAAM,IAAI,EACN,iCAAiC,EAAS,OAAO,GAAG,EAAS,YACjE;EAEJ,OAAO,IAAI,WAAW,MAAM,EAAS,YAAY,CAAC;CACtD,SAAS,GAAO;EAEZ,MADI,aAAiB,IAAuB,IACtC,IAAI,EAAgB,iCAAiC,KAAO,EAAE,OAAO,EAAM,CAAC;CACtF;AACJ;AAYA,eAAsB,EAClB,GACA,IAAoB,GACJ;CAGhB,OAFK,EAAgB,IAEb,OAAM,MADM,WAAW,OAAO,KAAK,CAAS,EAAA,CAChC,MAAM,CAAG,MAAO,KAAA,IAFL;AAGnC;AAaA,eAAsB,EAClB,GACA,GACA,IAAoB,GACJ;CAChB,IAAI,CAAC,EAAgB,GAAG,OAAO;CAC/B,IAAM,IAAQ,MAAM,WAAW,OAAO,KAAK,CAAS,GAC9C,IAAO,IAAI,WAAW,CAAK,CAAC,CAAC;CAOnC,OANA,MAAM,EAAM,IACR,GACA,IAAI,SAAS,GAAM,EACf,SAAS,EAAE,gBAAgB,2BAA2B,EAC1D,CAAC,CACL,GACO;AACX;AASA,eAAsB,EAClB,GACA,IAAoB,GACJ;CAIhB,OAHK,EAAgB,IACjB,MAAQ,KAAA,IAAkB,MAAM,WAAW,OAAO,OAAO,CAAS,IAE/D,OAAM,MADO,WAAW,OAAO,KAAK,CAAS,EAAA,CACjC,OAAO,CAAG,IAHE;AAInC"}
@@ -0,0 +1,2 @@
1
+ var e=class extends Error{constructor(e,t){super(e,t),this.name=`TabularError`}},t=class extends e{constructor(e,t){super(e,t),this.name=`ModelLoadError`}},n=class extends e{constructor(e,t){super(e,t),this.name=`UnsupportedGraphError`}},r=class extends e{constructor(e,t){super(e,t),this.name=`FeatureShapeError`}},i=class extends e{constructor(e,t){super(e,t),this.name=`InferenceError`}},a=class extends e{constructor(e,t){super(e,t),this.name=`ModelFetchError`}};exports.FeatureShapeError=r,exports.InferenceError=i,exports.ModelFetchError=a,exports.ModelLoadError=t,exports.TabularError=e,exports.UnsupportedGraphError=n;
2
+ //# sourceMappingURL=exceptions.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exceptions.cjs","names":[],"sources":["../../src/tabular/exceptions.ts"],"sourcesContent":["/**\n * Errors thrown by the tabular inference module.\n *\n * Each one exists because the underlying failure is unreadable on its own:\n * ONNX Runtime reports a missing operator registration, and the actual cause\n * is an import path chosen three files away.\n *\n * Every subclass sets `name` to a **literal** string rather than to\n * `new.target.name`. Measured in a real build: the minifier renames the\n * class, so the derived form ships as `error.name === \"t\"` — useless in a\n * log and in any consumer that branches on the name.\n */\n\n/** Base class for every error this module throws. */\nexport class TabularError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"TabularError\";\n }\n}\n\n/** The model bytes could not be loaded into a session. */\nexport class ModelLoadError extends TabularError {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"ModelLoadError\";\n }\n}\n\n/**\n * The runtime has no kernels for this graph's operators.\n *\n * Measured, and the reason this class exists: importing\n * `onnxruntime-web/webgpu` loads a WebAssembly build without the\n * `ai.onnx.ml` domain, so creating a session over any scikit-learn export\n * fails with `No Op registered for TreeEnsembleClassifier`. The message\n * names the fix, because the raw error points at the model instead of at\n * the import.\n */\nexport class UnsupportedGraphError extends TabularError {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"UnsupportedGraphError\";\n }\n}\n\n/** The rows do not match what the model expects. */\nexport class FeatureShapeError extends TabularError {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"FeatureShapeError\";\n }\n}\n\n/** The session ran but its outputs could not be read. */\nexport class InferenceError extends TabularError {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"InferenceError\";\n }\n}\n\n/**\n * The model bytes could not be fetched or read from the cache.\n *\n * Distinct from {@link ModelLoadError}: this one means the app is offline\n * and nothing was cached, which is a deployment problem, not a model\n * problem.\n */\nexport class ModelFetchError extends TabularError {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"ModelFetchError\";\n }\n}\n"],"mappings":"AAcA,IAAa,EAAb,cAAkC,KAAM,CACpC,YAAY,EAAiB,EAAwB,CACjD,MAAM,EAAS,CAAO,EACtB,KAAK,KAAO,cAChB,CACJ,EAGa,EAAb,cAAoC,CAAa,CAC7C,YAAY,EAAiB,EAAwB,CACjD,MAAM,EAAS,CAAO,EACtB,KAAK,KAAO,gBAChB,CACJ,EAYa,EAAb,cAA2C,CAAa,CACpD,YAAY,EAAiB,EAAwB,CACjD,MAAM,EAAS,CAAO,EACtB,KAAK,KAAO,uBAChB,CACJ,EAGa,EAAb,cAAuC,CAAa,CAChD,YAAY,EAAiB,EAAwB,CACjD,MAAM,EAAS,CAAO,EACtB,KAAK,KAAO,mBAChB,CACJ,EAGa,EAAb,cAAoC,CAAa,CAC7C,YAAY,EAAiB,EAAwB,CACjD,MAAM,EAAS,CAAO,EACtB,KAAK,KAAO,gBAChB,CACJ,EASa,EAAb,cAAqC,CAAa,CAC9C,YAAY,EAAiB,EAAwB,CACjD,MAAM,EAAS,CAAO,EACtB,KAAK,KAAO,iBAChB,CACJ"}
@@ -0,0 +1,30 @@
1
+ //#region src/tabular/exceptions.ts
2
+ var e = class extends Error {
3
+ constructor(e, t) {
4
+ super(e, t), this.name = "TabularError";
5
+ }
6
+ }, t = class extends e {
7
+ constructor(e, t) {
8
+ super(e, t), this.name = "ModelLoadError";
9
+ }
10
+ }, n = class extends e {
11
+ constructor(e, t) {
12
+ super(e, t), this.name = "UnsupportedGraphError";
13
+ }
14
+ }, r = class extends e {
15
+ constructor(e, t) {
16
+ super(e, t), this.name = "FeatureShapeError";
17
+ }
18
+ }, i = class extends e {
19
+ constructor(e, t) {
20
+ super(e, t), this.name = "InferenceError";
21
+ }
22
+ }, a = class extends e {
23
+ constructor(e, t) {
24
+ super(e, t), this.name = "ModelFetchError";
25
+ }
26
+ };
27
+ //#endregion
28
+ export { r as FeatureShapeError, i as InferenceError, a as ModelFetchError, t as ModelLoadError, e as TabularError, n as UnsupportedGraphError };
29
+
30
+ //# sourceMappingURL=exceptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exceptions.js","names":[],"sources":["../../src/tabular/exceptions.ts"],"sourcesContent":["/**\n * Errors thrown by the tabular inference module.\n *\n * Each one exists because the underlying failure is unreadable on its own:\n * ONNX Runtime reports a missing operator registration, and the actual cause\n * is an import path chosen three files away.\n *\n * Every subclass sets `name` to a **literal** string rather than to\n * `new.target.name`. Measured in a real build: the minifier renames the\n * class, so the derived form ships as `error.name === \"t\"` — useless in a\n * log and in any consumer that branches on the name.\n */\n\n/** Base class for every error this module throws. */\nexport class TabularError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"TabularError\";\n }\n}\n\n/** The model bytes could not be loaded into a session. */\nexport class ModelLoadError extends TabularError {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"ModelLoadError\";\n }\n}\n\n/**\n * The runtime has no kernels for this graph's operators.\n *\n * Measured, and the reason this class exists: importing\n * `onnxruntime-web/webgpu` loads a WebAssembly build without the\n * `ai.onnx.ml` domain, so creating a session over any scikit-learn export\n * fails with `No Op registered for TreeEnsembleClassifier`. The message\n * names the fix, because the raw error points at the model instead of at\n * the import.\n */\nexport class UnsupportedGraphError extends TabularError {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"UnsupportedGraphError\";\n }\n}\n\n/** The rows do not match what the model expects. */\nexport class FeatureShapeError extends TabularError {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"FeatureShapeError\";\n }\n}\n\n/** The session ran but its outputs could not be read. */\nexport class InferenceError extends TabularError {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"InferenceError\";\n }\n}\n\n/**\n * The model bytes could not be fetched or read from the cache.\n *\n * Distinct from {@link ModelLoadError}: this one means the app is offline\n * and nothing was cached, which is a deployment problem, not a model\n * problem.\n */\nexport class ModelFetchError extends TabularError {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"ModelFetchError\";\n }\n}\n"],"mappings":";AAcA,IAAa,IAAb,cAAkC,MAAM;CACpC,YAAY,GAAiB,GAAwB;EAEjD,AADA,MAAM,GAAS,CAAO,GACtB,KAAK,OAAO;CAChB;AACJ,GAGa,IAAb,cAAoC,EAAa;CAC7C,YAAY,GAAiB,GAAwB;EAEjD,AADA,MAAM,GAAS,CAAO,GACtB,KAAK,OAAO;CAChB;AACJ,GAYa,IAAb,cAA2C,EAAa;CACpD,YAAY,GAAiB,GAAwB;EAEjD,AADA,MAAM,GAAS,CAAO,GACtB,KAAK,OAAO;CAChB;AACJ,GAGa,IAAb,cAAuC,EAAa;CAChD,YAAY,GAAiB,GAAwB;EAEjD,AADA,MAAM,GAAS,CAAO,GACtB,KAAK,OAAO;CAChB;AACJ,GAGa,IAAb,cAAoC,EAAa;CAC7C,YAAY,GAAiB,GAAwB;EAEjD,AADA,MAAM,GAAS,CAAO,GACtB,KAAK,OAAO;CAChB;AACJ,GASa,IAAb,cAAqC,EAAa;CAC9C,YAAY,GAAiB,GAAwB;EAEjD,AADA,MAAM,GAAS,CAAO,GACtB,KAAK,OAAO;CAChB;AACJ"}
@@ -0,0 +1,2 @@
1
+ const e=require("./exceptions.cjs"),t=require("./cache.cjs"),n=require("./predictor.cjs");var r=1,i=`manifest.json`;async function a(t,n){let r=s(t),i;try{i=await fetch(r,n)}catch(t){throw new e.ModelFetchError(`Could not read the manifest at ${r}`,{cause:t})}if(!i.ok)throw new e.ModelFetchError(`Could not read the manifest at ${r}: ${i.status} ${i.statusText}`);let a=await i.json();if(typeof a?.schema_version!=`number`)throw new e.ModelFetchError(`${r} is not an edge package manifest (no schema_version).`);if(a.schema_version>1)throw new e.ModelFetchError(`The manifest at ${r} uses schema_version ${a.schema_version}, and this SDK understands 1. Upgrade tempest-react-sdk before serving this package.`);return a}async function o(e,r={}){let i=await a(e),o=`${e.endsWith(`/`)?e:`${e}/`}${i.model.file}`,s=r.cache??!0,c=s===!1?o:await t.fetchModelBytes(o,typeof s==`object`?s:{}),l=await n.TabularPredictor.create(c,{providers:r.providers,warmup:r.warmup,sessionOptions:r.sessionOptions}),u=i.output.classes;return{manifest:i,predictor:l,featureNames:i.input.feature_names,classes:u,explain:e=>e.map((e,t)=>({name:u[t]??String(t),score:e})).sort((e,t)=>t.score-e.score)}}function s(e){return e.endsWith(`.json`)?e:e.endsWith(`/`)?`${e}${i}`:`${e}/${i}`}exports.MANIFEST_FILENAME=i,exports.SUPPORTED_MANIFEST_SCHEMA=r,exports.fetchEdgeManifest=a,exports.loadEdgePackage=o;
2
+ //# sourceMappingURL=manifest.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.cjs","names":[],"sources":["../../src/tabular/manifest.ts"],"sourcesContent":["/**\n * Reading an edge package published by `tempest-fastapi-sdk`.\n *\n * `edge_pipeline` writes a directory — the graph, a gzipped copy, a drift\n * baseline and a `manifest.json` — and the browser is one of its two\n * intended consumers. The manifest is what turns a `.onnx` URL into\n * something a UI can use: the **column order** the model was trained on,\n * the class names behind `probabilities[2]`, and a version to compare\n * against what is already cached.\n *\n * That column order is the field worth loading a manifest for. A model fed\n * the right features in the wrong order answers confidently and wrongly,\n * and no runtime check catches it.\n *\n * The contract is pinned by `schema_version`. Unknown fields are ignored\n * rather than rejected, so a package written by a newer SDK still loads.\n */\n\nimport { fetchModelBytes, type ModelCacheOptions } from \"./cache\";\nimport { ModelFetchError } from \"./exceptions\";\nimport { TabularPredictor } from \"./predictor\";\nimport type { TabularPredictorOptions } from \"./types\";\n\n/** Manifest schema version this reader was written against. */\nexport const SUPPORTED_MANIFEST_SCHEMA = 1;\n\n/** Fixed filename inside a package directory. */\nexport const MANIFEST_FILENAME = \"manifest.json\";\n\n/** The graph file and how to check you got it whole. */\nexport interface ManifestModelFile {\n readonly file: string;\n readonly sha256: string;\n readonly bytes: number;\n readonly gzip_file: string | null;\n readonly gzip_bytes: number | null;\n readonly opset: number;\n readonly dtype: string;\n}\n\n/** What the graph expects per row. */\nexport interface ManifestInput {\n readonly name: string;\n readonly features: number;\n /** Column order used at training time. */\n readonly feature_names: readonly string[];\n}\n\n/** What the graph answers. */\nexport interface ManifestOutput {\n readonly is_classifier: boolean;\n readonly label_output: string;\n readonly probability_output: string | null;\n /** Class labels in score-column order. */\n readonly classes: readonly string[];\n}\n\n/** The package manifest, as written by `edge_pipeline`. */\nexport interface EdgeManifest {\n readonly schema_version: number;\n readonly name: string;\n readonly version: string;\n readonly created_at: string;\n readonly sdk_version: string;\n readonly estimator: string;\n readonly model: ManifestModelFile;\n readonly input: ManifestInput;\n readonly output: ManifestOutput;\n readonly verified: boolean | null;\n readonly baseline_file: string | null;\n readonly baseline_samples: number;\n}\n\n/** A package loaded and ready to answer. */\nexport interface LoadedEdgePackage {\n /** What was published. */\n readonly manifest: EdgeManifest;\n /** The running model. */\n readonly predictor: TabularPredictor;\n /** Column order the rows must follow. */\n readonly featureNames: readonly string[];\n /** Class names behind each probability column. */\n readonly classes: readonly string[];\n /**\n * Map a prediction's scores onto class names.\n *\n * @param probabilities One row of scores.\n * @returns Name/score pairs, highest first.\n */\n readonly explain: (probabilities: readonly number[]) => { name: string; score: number }[];\n}\n\n/** Options for {@link loadEdgePackage}. */\nexport interface LoadEdgePackageOptions extends TabularPredictorOptions {\n /** Cache the model bytes for offline use. `true` by default. */\n readonly cache?: boolean | ModelCacheOptions;\n}\n\n/**\n * Read a package's manifest.\n *\n * Cheap: it is a few hundred bytes, so an app can check for a new version\n * without downloading a model it may already have.\n *\n * @example\n * ```ts\n * const manifest = await fetchEdgeManifest(\"/models/risk/\");\n * if (manifest.version !== localStorage.getItem(\"risk-version\")) {\n * // a new model was published\n * }\n * ```\n *\n * @param directoryUrl URL of the package directory, with or without a\n * trailing slash. A full URL to the manifest file also works.\n * @param requestInit `fetch` options.\n * @returns The parsed manifest.\n * @throws {@link ModelFetchError} when the manifest cannot be read, or when\n * its `schema_version` is newer than this reader understands — loading it\n * anyway would risk misreading the field that defines column order.\n */\nexport async function fetchEdgeManifest(\n directoryUrl: string,\n requestInit?: RequestInit,\n): Promise<EdgeManifest> {\n const url = manifestUrl(directoryUrl);\n let response: Response;\n try {\n response = await fetch(url, requestInit);\n } catch (error) {\n throw new ModelFetchError(`Could not read the manifest at ${url}`, {\n cause: error,\n });\n }\n if (!response.ok) {\n throw new ModelFetchError(\n `Could not read the manifest at ${url}: ${response.status} ${response.statusText}`,\n );\n }\n\n const manifest = (await response.json()) as EdgeManifest;\n if (typeof manifest?.schema_version !== \"number\") {\n throw new ModelFetchError(`${url} is not an edge package manifest (no schema_version).`);\n }\n if (manifest.schema_version > SUPPORTED_MANIFEST_SCHEMA) {\n throw new ModelFetchError(\n `The manifest at ${url} uses schema_version ${manifest.schema_version}, ` +\n `and this SDK understands ${SUPPORTED_MANIFEST_SCHEMA}. Upgrade ` +\n \"tempest-react-sdk before serving this package.\",\n );\n }\n return manifest;\n}\n\n/**\n * Load a whole edge package: manifest, model, and the names to read it by.\n *\n * @example\n * ```ts\n * const pkg = await loadEdgePackage(\"/models/risk/\");\n *\n * console.log(pkg.featureNames); // [\"age\", \"income\", \"tenure\", \"score\", \"visits\"]\n *\n * const { probabilities } = await pkg.predictor.predict([[41, 5200, 3, 0.82, 12]]);\n * console.log(pkg.explain(probabilities[0]!)); // [{ name: \"approved\", score: 0.91 }, ...]\n * ```\n *\n * @param directoryUrl URL of the package directory.\n * @param options Predictor options plus caching.\n * @returns The loaded package.\n * @throws {@link ModelFetchError} when the manifest or model cannot be read.\n */\nexport async function loadEdgePackage(\n directoryUrl: string,\n options: LoadEdgePackageOptions = {},\n): Promise<LoadedEdgePackage> {\n const manifest = await fetchEdgeManifest(directoryUrl);\n const base = directoryUrl.endsWith(\"/\") ? directoryUrl : `${directoryUrl}/`;\n const modelUrl = `${base}${manifest.model.file}`;\n\n const cache = options.cache ?? true;\n const source =\n cache === false\n ? modelUrl\n : await fetchModelBytes(modelUrl, typeof cache === \"object\" ? cache : {});\n\n const predictor = await TabularPredictor.create(source, {\n providers: options.providers,\n warmup: options.warmup,\n sessionOptions: options.sessionOptions,\n });\n\n const classes = manifest.output.classes;\n return {\n manifest,\n predictor,\n featureNames: manifest.input.feature_names,\n classes,\n explain: (probabilities: readonly number[]) =>\n probabilities\n .map((score, index) => ({\n name: classes[index] ?? String(index),\n score,\n }))\n .sort((a, b) => b.score - a.score),\n };\n}\n\n/**\n * Resolve a directory URL to its manifest file.\n *\n * @param directoryUrl The package directory, or the manifest itself.\n * @returns The manifest URL.\n */\nfunction manifestUrl(directoryUrl: string): string {\n if (directoryUrl.endsWith(\".json\")) return directoryUrl;\n return directoryUrl.endsWith(\"/\")\n ? `${directoryUrl}${MANIFEST_FILENAME}`\n : `${directoryUrl}/${MANIFEST_FILENAME}`;\n}\n"],"mappings":"0FAwBA,IAAa,EAA4B,EAG5B,EAAoB,gBA6FjC,eAAsB,EAClB,EACA,EACqB,CACrB,IAAM,EAAM,EAAY,CAAY,EAChC,EACJ,GAAI,CACA,EAAW,MAAM,MAAM,EAAK,CAAW,CAC3C,OAAS,EAAO,CACZ,MAAM,IAAI,EAAA,gBAAgB,kCAAkC,IAAO,CAC/D,MAAO,CACX,CAAC,CACL,CACA,GAAI,CAAC,EAAS,GACV,MAAM,IAAI,EAAA,gBACN,kCAAkC,EAAI,IAAI,EAAS,OAAO,GAAG,EAAS,YAC1E,EAGJ,IAAM,EAAY,MAAM,EAAS,KAAK,EACtC,GAAI,OAAO,GAAU,gBAAmB,SACpC,MAAM,IAAI,EAAA,gBAAgB,GAAG,EAAI,sDAAsD,EAE3F,GAAI,EAAS,eAAA,EACT,MAAM,IAAI,EAAA,gBACN,mBAAmB,EAAI,uBAAuB,EAAS,eAAe,qFAG1E,EAEJ,OAAO,CACX,CAoBA,eAAsB,EAClB,EACA,EAAkC,CAAC,EACT,CAC1B,IAAM,EAAW,MAAM,EAAkB,CAAY,EAE/C,EAAW,GADJ,EAAa,SAAS,GAAG,EAAI,EAAe,GAAG,EAAa,KAC9C,EAAS,MAAM,OAEpC,EAAQ,EAAQ,OAAS,GACzB,EACF,IAAU,GACJ,EACA,MAAM,EAAA,gBAAgB,EAAU,OAAO,GAAU,SAAW,EAAQ,CAAC,CAAC,EAE1E,EAAY,MAAM,EAAA,iBAAiB,OAAO,EAAQ,CACpD,UAAW,EAAQ,UACnB,OAAQ,EAAQ,OAChB,eAAgB,EAAQ,cAC5B,CAAC,EAEK,EAAU,EAAS,OAAO,QAChC,MAAO,CACH,WACA,YACA,aAAc,EAAS,MAAM,cAC7B,UACA,QAAU,GACN,EACK,KAAK,EAAO,KAAW,CACpB,KAAM,EAAQ,IAAU,OAAO,CAAK,EACpC,OACJ,EAAE,CAAC,CACF,MAAM,EAAG,IAAM,EAAE,MAAQ,EAAE,KAAK,CAC7C,CACJ,CAQA,SAAS,EAAY,EAA8B,CAE/C,OADI,EAAa,SAAS,OAAO,EAAU,EACpC,EAAa,SAAS,GAAG,EAC1B,GAAG,IAAe,IAClB,GAAG,EAAa,GAAG,GAC7B"}
@@ -0,0 +1,42 @@
1
+ import { ModelFetchError as e } from "./exceptions.js";
2
+ import { fetchModelBytes as t } from "./cache.js";
3
+ import { TabularPredictor as n } from "./predictor.js";
4
+ //#region src/tabular/manifest.ts
5
+ var r = 1, i = "manifest.json";
6
+ async function a(t, n) {
7
+ let r = s(t), i;
8
+ try {
9
+ i = await fetch(r, n);
10
+ } catch (t) {
11
+ throw new e(`Could not read the manifest at ${r}`, { cause: t });
12
+ }
13
+ if (!i.ok) throw new e(`Could not read the manifest at ${r}: ${i.status} ${i.statusText}`);
14
+ let a = await i.json();
15
+ if (typeof a?.schema_version != "number") throw new e(`${r} is not an edge package manifest (no schema_version).`);
16
+ if (a.schema_version > 1) throw new e(`The manifest at ${r} uses schema_version ${a.schema_version}, and this SDK understands 1. Upgrade tempest-react-sdk before serving this package.`);
17
+ return a;
18
+ }
19
+ async function o(e, r = {}) {
20
+ let i = await a(e), o = `${e.endsWith("/") ? e : `${e}/`}${i.model.file}`, s = r.cache ?? !0, c = s === !1 ? o : await t(o, typeof s == "object" ? s : {}), l = await n.create(c, {
21
+ providers: r.providers,
22
+ warmup: r.warmup,
23
+ sessionOptions: r.sessionOptions
24
+ }), u = i.output.classes;
25
+ return {
26
+ manifest: i,
27
+ predictor: l,
28
+ featureNames: i.input.feature_names,
29
+ classes: u,
30
+ explain: (e) => e.map((e, t) => ({
31
+ name: u[t] ?? String(t),
32
+ score: e
33
+ })).sort((e, t) => t.score - e.score)
34
+ };
35
+ }
36
+ function s(e) {
37
+ return e.endsWith(".json") ? e : e.endsWith("/") ? `${e}${i}` : `${e}/${i}`;
38
+ }
39
+ //#endregion
40
+ export { i as MANIFEST_FILENAME, r as SUPPORTED_MANIFEST_SCHEMA, a as fetchEdgeManifest, o as loadEdgePackage };
41
+
42
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","names":[],"sources":["../../src/tabular/manifest.ts"],"sourcesContent":["/**\n * Reading an edge package published by `tempest-fastapi-sdk`.\n *\n * `edge_pipeline` writes a directory — the graph, a gzipped copy, a drift\n * baseline and a `manifest.json` — and the browser is one of its two\n * intended consumers. The manifest is what turns a `.onnx` URL into\n * something a UI can use: the **column order** the model was trained on,\n * the class names behind `probabilities[2]`, and a version to compare\n * against what is already cached.\n *\n * That column order is the field worth loading a manifest for. A model fed\n * the right features in the wrong order answers confidently and wrongly,\n * and no runtime check catches it.\n *\n * The contract is pinned by `schema_version`. Unknown fields are ignored\n * rather than rejected, so a package written by a newer SDK still loads.\n */\n\nimport { fetchModelBytes, type ModelCacheOptions } from \"./cache\";\nimport { ModelFetchError } from \"./exceptions\";\nimport { TabularPredictor } from \"./predictor\";\nimport type { TabularPredictorOptions } from \"./types\";\n\n/** Manifest schema version this reader was written against. */\nexport const SUPPORTED_MANIFEST_SCHEMA = 1;\n\n/** Fixed filename inside a package directory. */\nexport const MANIFEST_FILENAME = \"manifest.json\";\n\n/** The graph file and how to check you got it whole. */\nexport interface ManifestModelFile {\n readonly file: string;\n readonly sha256: string;\n readonly bytes: number;\n readonly gzip_file: string | null;\n readonly gzip_bytes: number | null;\n readonly opset: number;\n readonly dtype: string;\n}\n\n/** What the graph expects per row. */\nexport interface ManifestInput {\n readonly name: string;\n readonly features: number;\n /** Column order used at training time. */\n readonly feature_names: readonly string[];\n}\n\n/** What the graph answers. */\nexport interface ManifestOutput {\n readonly is_classifier: boolean;\n readonly label_output: string;\n readonly probability_output: string | null;\n /** Class labels in score-column order. */\n readonly classes: readonly string[];\n}\n\n/** The package manifest, as written by `edge_pipeline`. */\nexport interface EdgeManifest {\n readonly schema_version: number;\n readonly name: string;\n readonly version: string;\n readonly created_at: string;\n readonly sdk_version: string;\n readonly estimator: string;\n readonly model: ManifestModelFile;\n readonly input: ManifestInput;\n readonly output: ManifestOutput;\n readonly verified: boolean | null;\n readonly baseline_file: string | null;\n readonly baseline_samples: number;\n}\n\n/** A package loaded and ready to answer. */\nexport interface LoadedEdgePackage {\n /** What was published. */\n readonly manifest: EdgeManifest;\n /** The running model. */\n readonly predictor: TabularPredictor;\n /** Column order the rows must follow. */\n readonly featureNames: readonly string[];\n /** Class names behind each probability column. */\n readonly classes: readonly string[];\n /**\n * Map a prediction's scores onto class names.\n *\n * @param probabilities One row of scores.\n * @returns Name/score pairs, highest first.\n */\n readonly explain: (probabilities: readonly number[]) => { name: string; score: number }[];\n}\n\n/** Options for {@link loadEdgePackage}. */\nexport interface LoadEdgePackageOptions extends TabularPredictorOptions {\n /** Cache the model bytes for offline use. `true` by default. */\n readonly cache?: boolean | ModelCacheOptions;\n}\n\n/**\n * Read a package's manifest.\n *\n * Cheap: it is a few hundred bytes, so an app can check for a new version\n * without downloading a model it may already have.\n *\n * @example\n * ```ts\n * const manifest = await fetchEdgeManifest(\"/models/risk/\");\n * if (manifest.version !== localStorage.getItem(\"risk-version\")) {\n * // a new model was published\n * }\n * ```\n *\n * @param directoryUrl URL of the package directory, with or without a\n * trailing slash. A full URL to the manifest file also works.\n * @param requestInit `fetch` options.\n * @returns The parsed manifest.\n * @throws {@link ModelFetchError} when the manifest cannot be read, or when\n * its `schema_version` is newer than this reader understands — loading it\n * anyway would risk misreading the field that defines column order.\n */\nexport async function fetchEdgeManifest(\n directoryUrl: string,\n requestInit?: RequestInit,\n): Promise<EdgeManifest> {\n const url = manifestUrl(directoryUrl);\n let response: Response;\n try {\n response = await fetch(url, requestInit);\n } catch (error) {\n throw new ModelFetchError(`Could not read the manifest at ${url}`, {\n cause: error,\n });\n }\n if (!response.ok) {\n throw new ModelFetchError(\n `Could not read the manifest at ${url}: ${response.status} ${response.statusText}`,\n );\n }\n\n const manifest = (await response.json()) as EdgeManifest;\n if (typeof manifest?.schema_version !== \"number\") {\n throw new ModelFetchError(`${url} is not an edge package manifest (no schema_version).`);\n }\n if (manifest.schema_version > SUPPORTED_MANIFEST_SCHEMA) {\n throw new ModelFetchError(\n `The manifest at ${url} uses schema_version ${manifest.schema_version}, ` +\n `and this SDK understands ${SUPPORTED_MANIFEST_SCHEMA}. Upgrade ` +\n \"tempest-react-sdk before serving this package.\",\n );\n }\n return manifest;\n}\n\n/**\n * Load a whole edge package: manifest, model, and the names to read it by.\n *\n * @example\n * ```ts\n * const pkg = await loadEdgePackage(\"/models/risk/\");\n *\n * console.log(pkg.featureNames); // [\"age\", \"income\", \"tenure\", \"score\", \"visits\"]\n *\n * const { probabilities } = await pkg.predictor.predict([[41, 5200, 3, 0.82, 12]]);\n * console.log(pkg.explain(probabilities[0]!)); // [{ name: \"approved\", score: 0.91 }, ...]\n * ```\n *\n * @param directoryUrl URL of the package directory.\n * @param options Predictor options plus caching.\n * @returns The loaded package.\n * @throws {@link ModelFetchError} when the manifest or model cannot be read.\n */\nexport async function loadEdgePackage(\n directoryUrl: string,\n options: LoadEdgePackageOptions = {},\n): Promise<LoadedEdgePackage> {\n const manifest = await fetchEdgeManifest(directoryUrl);\n const base = directoryUrl.endsWith(\"/\") ? directoryUrl : `${directoryUrl}/`;\n const modelUrl = `${base}${manifest.model.file}`;\n\n const cache = options.cache ?? true;\n const source =\n cache === false\n ? modelUrl\n : await fetchModelBytes(modelUrl, typeof cache === \"object\" ? cache : {});\n\n const predictor = await TabularPredictor.create(source, {\n providers: options.providers,\n warmup: options.warmup,\n sessionOptions: options.sessionOptions,\n });\n\n const classes = manifest.output.classes;\n return {\n manifest,\n predictor,\n featureNames: manifest.input.feature_names,\n classes,\n explain: (probabilities: readonly number[]) =>\n probabilities\n .map((score, index) => ({\n name: classes[index] ?? String(index),\n score,\n }))\n .sort((a, b) => b.score - a.score),\n };\n}\n\n/**\n * Resolve a directory URL to its manifest file.\n *\n * @param directoryUrl The package directory, or the manifest itself.\n * @returns The manifest URL.\n */\nfunction manifestUrl(directoryUrl: string): string {\n if (directoryUrl.endsWith(\".json\")) return directoryUrl;\n return directoryUrl.endsWith(\"/\")\n ? `${directoryUrl}${MANIFEST_FILENAME}`\n : `${directoryUrl}/${MANIFEST_FILENAME}`;\n}\n"],"mappings":";;;;AAwBA,IAAa,IAA4B,GAG5B,IAAoB;AA6FjC,eAAsB,EAClB,GACA,GACqB;CACrB,IAAM,IAAM,EAAY,CAAY,GAChC;CACJ,IAAI;EACA,IAAW,MAAM,MAAM,GAAK,CAAW;CAC3C,SAAS,GAAO;EACZ,MAAM,IAAI,EAAgB,kCAAkC,KAAO,EAC/D,OAAO,EACX,CAAC;CACL;CACA,IAAI,CAAC,EAAS,IACV,MAAM,IAAI,EACN,kCAAkC,EAAI,IAAI,EAAS,OAAO,GAAG,EAAS,YAC1E;CAGJ,IAAM,IAAY,MAAM,EAAS,KAAK;CACtC,IAAI,OAAO,GAAU,kBAAmB,UACpC,MAAM,IAAI,EAAgB,GAAG,EAAI,sDAAsD;CAE3F,IAAI,EAAS,iBAAA,GACT,MAAM,IAAI,EACN,mBAAmB,EAAI,uBAAuB,EAAS,eAAe,qFAG1E;CAEJ,OAAO;AACX;AAoBA,eAAsB,EAClB,GACA,IAAkC,CAAC,GACT;CAC1B,IAAM,IAAW,MAAM,EAAkB,CAAY,GAE/C,IAAW,GADJ,EAAa,SAAS,GAAG,IAAI,IAAe,GAAG,EAAa,KAC9C,EAAS,MAAM,QAEpC,IAAQ,EAAQ,SAAS,IACzB,IACF,MAAU,KACJ,IACA,MAAM,EAAgB,GAAU,OAAO,KAAU,WAAW,IAAQ,CAAC,CAAC,GAE1E,IAAY,MAAM,EAAiB,OAAO,GAAQ;EACpD,WAAW,EAAQ;EACnB,QAAQ,EAAQ;EAChB,gBAAgB,EAAQ;CAC5B,CAAC,GAEK,IAAU,EAAS,OAAO;CAChC,OAAO;EACH;EACA;EACA,cAAc,EAAS,MAAM;EAC7B;EACA,UAAU,MACN,EACK,KAAK,GAAO,OAAW;GACpB,MAAM,EAAQ,MAAU,OAAO,CAAK;GACpC;EACJ,EAAE,CAAC,CACF,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;CAC7C;AACJ;AAQA,SAAS,EAAY,GAA8B;CAE/C,OADI,EAAa,SAAS,OAAO,IAAU,IACpC,EAAa,SAAS,GAAG,IAC1B,GAAG,IAAe,MAClB,GAAG,EAAa,GAAG;AAC7B"}
@@ -0,0 +1,2 @@
1
+ const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./exceptions.cjs");let n=require("onnxruntime-web");n=e.__toESM(n,1);var r=[`wasm`],i=[`label`,`class`,`variable`,`output`],a=[`probabilit`,`score`],o=1e6;function s(e,t){for(let n of t){let t=e.find(e=>e.toLowerCase().includes(n));if(t!==void 0)return t}return null}function c(e){let t=e.inputMetadata?.[0];if(t===void 0||t.isTensor!==!0)return null;let n=t.shape[1];return typeof n!=`number`||!Number.isInteger(n)||n<=0||n>o?null:n}function l(e){return typeof e==`bigint`?Number(e):typeof e==`number`?e:String(e)}function u(e){let n=e instanceof Error?e.message:String(e);return n.includes(`No Op registered`)?new t.UnsupportedGraphError(`This runtime build has no kernels for the model's operators. scikit-learn exports use the ai.onnx.ml domain, which is missing from the WebGPU build: import "onnxruntime-web", not "onnxruntime-web/webgpu". Original error: `+n,{cause:e}):new t.ModelLoadError(`Failed to load the model: ${n}`,{cause:e})}function d(e){let n=e instanceof Error?e.message:String(e);return n.includes(`non-tensor typed value`)||n.includes(`Can't access output tensor data`)?new t.InferenceError(`The model has a non-tensor output, which ONNX Runtime Web cannot read. A scikit-learn export made with ZipMap enabled returns a sequence of maps per row — re-export with export_sklearn_to_onnx, which disables it. Original error: ${n}`,{cause:e}):new t.InferenceError(`Inference failed: ${n}`,{cause:e})}var f=class e{session;info;constructor(e,t){this.session=e,this.info=t}static async create(t,i={}){let o=i.providers??r,l;try{l=await n.InferenceSession.create(t,{...i.sessionOptions??{},executionProviders:o})}catch(e){throw u(e)}let d=[...l.outputNames],f=s(d,a),m=d.find(e=>e!==f&&p(e))??d.find(e=>e!==f)??d[0],h=new e(l,{inputName:l.inputNames[0],numFeatures:c(l),outputNames:d,labelOutput:m,probabilityOutput:f,isClassifier:f!==null,providers:o});return i.warmup!==!1&&await h.warmUp(),h}async warmUp(){let e=this.info.numFeatures;if(e!==null)try{await this.predict([Array(e).fill(0)])}catch{}}async predict(e){if(!Array.isArray(e)||e.length===0)throw new t.FeatureShapeError(`predict() needs at least one row, shaped [[f1, f2, ...]].`);let r=e[0]?.length??0;if(r===0)throw new t.FeatureShapeError(`The first row has no feature values.`);let i=e.findIndex(e=>e.length!==r);if(i!==-1)throw new t.FeatureShapeError(`All rows must have the same width; row ${i} has ${e[i]?.length} values, expected ${r}.`);let a=this.info.numFeatures;if(a!==null&&r!==a)throw new t.FeatureShapeError(`The model expects ${a} features per row, got ${r}.`);let o=new Float32Array(e.length*r);for(let t=0;t<e.length;t+=1)o.set(e[t],t*r);let s=new n.Tensor(`float32`,o,[e.length,r]),c=performance.now(),u;try{u=await this.session.run({[this.info.inputName]:s})}catch(e){throw d(e)}let f=performance.now()-c,p=u[this.info.labelOutput];if(p?.data===void 0)throw new t.InferenceError(`The model produced no readable "${this.info.labelOutput}" output.`);let m=Array.from(p.data,l),h=[];if(this.info.probabilityOutput!==null){let t=u[this.info.probabilityOutput];if(t?.data!==void 0){let n=Array.from(t.data,Number),r=n.length/e.length;for(let t=0;t<e.length;t+=1)h.push(n.slice(t*r,(t+1)*r))}}return{labels:m,probabilities:h,numRows:e.length,ms:f}}async dispose(){await this.session.release?.()}};function p(e){let t=e.toLowerCase();return i.some(e=>t.includes(e))}exports.DEFAULT_TABULAR_PROVIDERS=r,exports.TabularPredictor=f;
2
+ //# sourceMappingURL=predictor.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"predictor.cjs","names":[],"sources":["../../src/tabular/predictor.ts"],"sourcesContent":["/**\n * Running a scikit-learn model in the browser, offline.\n *\n * The model file is produced by `tempest-fastapi-sdk`'s\n * `export_sklearn_to_onnx`. This is everything between that file and an\n * answer — the same glue the Python `OnnxPredictor` provides on a device,\n * with the browser's own traps handled:\n *\n * - **int64 labels arrive as `bigint`.** ONNX Runtime Web surfaces the\n * label tensor as a `BigInt64Array`, so a caller comparing `label === 1`\n * silently gets `false` and `JSON.stringify` throws. Labels are converted.\n * - **`ai.onnx.ml` needs the right build.** Measured: importing\n * `onnxruntime-web/webgpu` loads a WebAssembly binary without those\n * operators, and session creation fails with `No Op registered for\n * TreeEnsembleClassifier`. That failure is translated into an error that\n * names the import.\n * - **Which output is which.** A classifier returns `label` and\n * `probabilities`; a regressor returns a single `variable`. Indexing by\n * position works until the day you deploy the other kind.\n */\n\nimport * as ort from \"onnxruntime-web\";\n\nimport {\n FeatureShapeError,\n InferenceError,\n ModelLoadError,\n UnsupportedGraphError,\n} from \"./exceptions\";\nimport type {\n FeatureRow,\n PredictedLabel,\n TabularModelSource,\n TabularPrediction,\n TabularPredictorInfo,\n TabularPredictorOptions,\n} from \"./types\";\n\n/**\n * Execution providers used when the caller does not choose.\n *\n * WebAssembly only, and deliberately: scikit-learn graphs are `ai.onnx.ml`\n * operators, which the WebGPU backend does not implement. There is no\n * speed left on the table here — a 10-tree forest predicts a row in about\n * 0.05 ms in Chromium.\n */\nexport const DEFAULT_TABULAR_PROVIDERS: readonly string[] = [\"wasm\"];\n\n/** Output names that indicate predicted classes rather than scores. */\nconst LABEL_HINTS = [\"label\", \"class\", \"variable\", \"output\"] as const;\n\n/** Output names that indicate class scores. */\nconst PROBABILITY_HINTS = [\"probabilit\", \"score\"] as const;\n\n/** Largest plausible feature count; anything above is a dynamic-dim sentinel. */\nconst MAX_DECLARED_FEATURES = 1_000_000;\n\n/**\n * Pick the first output whose name contains one of `hints`.\n *\n * @param names Graph output names.\n * @param hints Lowercase substrings to look for.\n * @returns The matching name, or `null`.\n */\nfunction matchOutput(names: readonly string[], hints: readonly string[]): string | null {\n for (const hint of hints) {\n const found = names.find((name) => name.toLowerCase().includes(hint));\n if (found !== undefined) return found;\n }\n return null;\n}\n\n/**\n * Read the declared feature count from the input metadata.\n *\n * A dynamic batch dimension is reported as a symbolic string or as an\n * out-of-range number (`4294967295` — an unsigned `-1`), so only a sane\n * positive integer in the second position is trusted.\n *\n * @param session The loaded session.\n * @returns The feature count, or `null` when the graph does not declare one.\n */\nfunction declaredFeatures(session: ort.InferenceSession): number | null {\n const metadata = session.inputMetadata?.[0];\n if (metadata === undefined || metadata.isTensor !== true) return null;\n const dimension = metadata.shape[1];\n if (typeof dimension !== \"number\") return null;\n if (!Number.isInteger(dimension) || dimension <= 0) return null;\n return dimension > MAX_DECLARED_FEATURES ? null : dimension;\n}\n\n/**\n * Convert one raw label value into a JS-friendly label.\n *\n * @param value A tensor element: `bigint` for int64, `number` for float,\n * `string` for a string-labelled classifier.\n * @returns The label as a number or string.\n */\nfunction toLabel(value: unknown): PredictedLabel {\n if (typeof value === \"bigint\") return Number(value);\n if (typeof value === \"number\") return value;\n return String(value);\n}\n\n/**\n * Translate a session-creation failure into an error naming its cause.\n *\n * @param error Whatever ONNX Runtime threw.\n * @returns The error to surface.\n */\nfunction asLoadError(error: unknown): Error {\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes(\"No Op registered\")) {\n return new UnsupportedGraphError(\n \"This runtime build has no kernels for the model's operators. \" +\n \"scikit-learn exports use the ai.onnx.ml domain, which is missing \" +\n 'from the WebGPU build: import \"onnxruntime-web\", not ' +\n '\"onnxruntime-web/webgpu\". Original error: ' +\n message,\n { cause: error },\n );\n }\n return new ModelLoadError(`Failed to load the model: ${message}`, { cause: error });\n}\n\n/**\n * Translate a run failure into an error naming its cause.\n *\n * Measured: an export made with skl2onnx's default (ZipMap enabled) has a\n * probability output that is a sequence of maps, and ONNX Runtime Web\n * refuses to read non-tensor values — `Reading data from non-tensor typed\n * value is not supported`. That message describes the runtime's limitation,\n * not the fix, so it is replaced by one that names the export flag.\n *\n * @param error Whatever ONNX Runtime threw.\n * @returns The error to surface.\n */\nfunction asRunError(error: unknown): Error {\n const message = error instanceof Error ? error.message : String(error);\n if (\n message.includes(\"non-tensor typed value\") ||\n message.includes(\"Can't access output tensor data\")\n ) {\n return new InferenceError(\n \"The model has a non-tensor output, which ONNX Runtime Web cannot \" +\n \"read. A scikit-learn export made with ZipMap enabled returns a \" +\n \"sequence of maps per row — re-export with export_sklearn_to_onnx, \" +\n `which disables it. Original error: ${message}`,\n { cause: error },\n );\n }\n return new InferenceError(`Inference failed: ${message}`, { cause: error });\n}\n\n/**\n * A loaded tabular model, ready to answer.\n *\n * @example\n * ```ts\n * const predictor = await TabularPredictor.create(\"/models/classifier.onnx\");\n * const { labels, probabilities } = await predictor.predict([[5.1, 3.5, 1.4, 0.2]]);\n * ```\n */\nexport class TabularPredictor {\n private constructor(\n private readonly session: ort.InferenceSession,\n /** What is loaded and how it is configured. */\n public readonly info: TabularPredictorInfo,\n ) {}\n\n /**\n * Load a model and describe its graph.\n *\n * @param source A URL string, or the model bytes (which is what an\n * offline app passes, having read them from the cache).\n * @param options Providers, warm-up and pass-through session options.\n * @throws {@link UnsupportedGraphError} when the runtime build lacks the\n * `ai.onnx.ml` operators — the WebGPU entry point does.\n * @throws {@link ModelLoadError} for any other load failure.\n */\n static async create(\n source: TabularModelSource,\n options: TabularPredictorOptions = {},\n ): Promise<TabularPredictor> {\n const providers = options.providers ?? DEFAULT_TABULAR_PROVIDERS;\n let session: ort.InferenceSession;\n try {\n session = await ort.InferenceSession.create(source as never, {\n ...(options.sessionOptions ?? {}),\n executionProviders:\n providers as ort.InferenceSession.SessionOptions[\"executionProviders\"],\n });\n } catch (error) {\n throw asLoadError(error);\n }\n\n const outputNames = [...session.outputNames];\n const probabilityOutput = matchOutput(outputNames, PROBABILITY_HINTS);\n const labelOutput =\n outputNames.find((name) => name !== probabilityOutput && isLabelName(name)) ??\n outputNames.find((name) => name !== probabilityOutput) ??\n (outputNames[0] as string);\n\n const predictor = new TabularPredictor(session, {\n inputName: session.inputNames[0] as string,\n numFeatures: declaredFeatures(session),\n outputNames,\n labelOutput,\n probabilityOutput,\n isClassifier: probabilityOutput !== null,\n providers,\n });\n\n if (options.warmup !== false) await predictor.warmUp();\n return predictor;\n }\n\n /**\n * Run one throwaway inference so the first real call is not the slow one.\n *\n * Skipped when the graph does not declare a feature count, since there\n * is no shape to synthesise. Failures are swallowed: a warm-up that\n * cannot run is not a reason to refuse to serve.\n */\n async warmUp(): Promise<void> {\n const features = this.info.numFeatures;\n if (features === null) return;\n try {\n await this.predict([new Array<number>(features).fill(0)]);\n } catch {\n /* a failed warm-up must not prevent serving */\n }\n }\n\n /**\n * Predict for a batch of rows.\n *\n * @param rows One array of feature values per row, in training column\n * order. A single row is still wrapped: `[[...]]`.\n * @returns Labels, class scores when the model produces them, and the\n * call's duration.\n * @throws {@link FeatureShapeError} when the batch is empty, ragged, or\n * the wrong width — checked here so the failure names the mismatch\n * instead of surfacing as an opaque runtime error.\n * @throws {@link InferenceError} when the session runs but its outputs\n * cannot be read.\n */\n async predict(rows: readonly FeatureRow[]): Promise<TabularPrediction> {\n if (!Array.isArray(rows) || rows.length === 0) {\n throw new FeatureShapeError(\n \"predict() needs at least one row, shaped [[f1, f2, ...]].\",\n );\n }\n const width = rows[0]?.length ?? 0;\n if (width === 0) {\n throw new FeatureShapeError(\"The first row has no feature values.\");\n }\n const ragged = rows.findIndex((row) => row.length !== width);\n if (ragged !== -1) {\n throw new FeatureShapeError(\n `All rows must have the same width; row ${ragged} has ` +\n `${rows[ragged]?.length} values, expected ${width}.`,\n );\n }\n const expected = this.info.numFeatures;\n if (expected !== null && width !== expected) {\n throw new FeatureShapeError(\n `The model expects ${expected} features per row, got ${width}.`,\n );\n }\n\n const flat = new Float32Array(rows.length * width);\n for (let index = 0; index < rows.length; index += 1) {\n flat.set(rows[index] as number[], index * width);\n }\n const tensor = new ort.Tensor(\"float32\", flat, [rows.length, width]);\n\n const started = performance.now();\n let outputs: ort.InferenceSession.OnnxValueMapType;\n try {\n outputs = await this.session.run({ [this.info.inputName]: tensor });\n } catch (error) {\n throw asRunError(error);\n }\n const ms = performance.now() - started;\n\n const labelTensor = outputs[this.info.labelOutput];\n if (labelTensor?.data === undefined) {\n throw new InferenceError(\n `The model produced no readable \"${this.info.labelOutput}\" output.`,\n );\n }\n\n const labels: PredictedLabel[] = Array.from(\n labelTensor.data as ArrayLike<unknown>,\n toLabel,\n );\n\n const probabilities: number[][] = [];\n if (this.info.probabilityOutput !== null) {\n const scores = outputs[this.info.probabilityOutput];\n if (scores?.data !== undefined) {\n const values = Array.from(scores.data as ArrayLike<number>, Number);\n const classes = values.length / rows.length;\n for (let index = 0; index < rows.length; index += 1) {\n probabilities.push(values.slice(index * classes, (index + 1) * classes));\n }\n }\n }\n\n return { labels, probabilities, numRows: rows.length, ms };\n }\n\n /**\n * Release the session's memory.\n *\n * Worth calling on a route that swaps models: the WebAssembly heap does\n * not shrink on garbage collection alone.\n */\n async dispose(): Promise<void> {\n await this.session.release?.();\n }\n}\n\n/**\n * Whether an output name looks like a label rather than a score.\n *\n * @param name The graph output name.\n * @returns `true` when the name matches a known label convention.\n */\nfunction isLabelName(name: string): boolean {\n const lowered = name.toLowerCase();\n return LABEL_HINTS.some((hint) => lowered.includes(hint));\n}\n"],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ import { FeatureShapeError as e, InferenceError as t, ModelLoadError as n, UnsupportedGraphError as r } from "./exceptions.js";
2
+ import * as i from "onnxruntime-web";
3
+ //#region src/tabular/predictor.ts
4
+ var a = ["wasm"], o = [
5
+ "label",
6
+ "class",
7
+ "variable",
8
+ "output"
9
+ ], s = ["probabilit", "score"], c = 1e6;
10
+ function l(e, t) {
11
+ for (let n of t) {
12
+ let t = e.find((e) => e.toLowerCase().includes(n));
13
+ if (t !== void 0) return t;
14
+ }
15
+ return null;
16
+ }
17
+ function u(e) {
18
+ let t = e.inputMetadata?.[0];
19
+ if (t === void 0 || t.isTensor !== !0) return null;
20
+ let n = t.shape[1];
21
+ return typeof n != "number" || !Number.isInteger(n) || n <= 0 || n > c ? null : n;
22
+ }
23
+ function d(e) {
24
+ return typeof e == "bigint" ? Number(e) : typeof e == "number" ? e : String(e);
25
+ }
26
+ function f(e) {
27
+ let t = e instanceof Error ? e.message : String(e);
28
+ return t.includes("No Op registered") ? new r("This runtime build has no kernels for the model's operators. scikit-learn exports use the ai.onnx.ml domain, which is missing from the WebGPU build: import \"onnxruntime-web\", not \"onnxruntime-web/webgpu\". Original error: " + t, { cause: e }) : new n(`Failed to load the model: ${t}`, { cause: e });
29
+ }
30
+ function p(e) {
31
+ let n = e instanceof Error ? e.message : String(e);
32
+ return n.includes("non-tensor typed value") || n.includes("Can't access output tensor data") ? new t(`The model has a non-tensor output, which ONNX Runtime Web cannot read. A scikit-learn export made with ZipMap enabled returns a sequence of maps per row — re-export with export_sklearn_to_onnx, which disables it. Original error: ${n}`, { cause: e }) : new t(`Inference failed: ${n}`, { cause: e });
33
+ }
34
+ var m = class n {
35
+ session;
36
+ info;
37
+ constructor(e, t) {
38
+ this.session = e, this.info = t;
39
+ }
40
+ static async create(e, t = {}) {
41
+ let r = t.providers ?? a, o;
42
+ try {
43
+ o = await i.InferenceSession.create(e, {
44
+ ...t.sessionOptions ?? {},
45
+ executionProviders: r
46
+ });
47
+ } catch (e) {
48
+ throw f(e);
49
+ }
50
+ let c = [...o.outputNames], d = l(c, s), p = c.find((e) => e !== d && h(e)) ?? c.find((e) => e !== d) ?? c[0], m = new n(o, {
51
+ inputName: o.inputNames[0],
52
+ numFeatures: u(o),
53
+ outputNames: c,
54
+ labelOutput: p,
55
+ probabilityOutput: d,
56
+ isClassifier: d !== null,
57
+ providers: r
58
+ });
59
+ return t.warmup !== !1 && await m.warmUp(), m;
60
+ }
61
+ async warmUp() {
62
+ let e = this.info.numFeatures;
63
+ if (e !== null) try {
64
+ await this.predict([Array(e).fill(0)]);
65
+ } catch {}
66
+ }
67
+ async predict(n) {
68
+ if (!Array.isArray(n) || n.length === 0) throw new e("predict() needs at least one row, shaped [[f1, f2, ...]].");
69
+ let r = n[0]?.length ?? 0;
70
+ if (r === 0) throw new e("The first row has no feature values.");
71
+ let a = n.findIndex((e) => e.length !== r);
72
+ if (a !== -1) throw new e(`All rows must have the same width; row ${a} has ${n[a]?.length} values, expected ${r}.`);
73
+ let o = this.info.numFeatures;
74
+ if (o !== null && r !== o) throw new e(`The model expects ${o} features per row, got ${r}.`);
75
+ let s = new Float32Array(n.length * r);
76
+ for (let e = 0; e < n.length; e += 1) s.set(n[e], e * r);
77
+ let c = new i.Tensor("float32", s, [n.length, r]), l = performance.now(), u;
78
+ try {
79
+ u = await this.session.run({ [this.info.inputName]: c });
80
+ } catch (e) {
81
+ throw p(e);
82
+ }
83
+ let f = performance.now() - l, m = u[this.info.labelOutput];
84
+ if (m?.data === void 0) throw new t(`The model produced no readable "${this.info.labelOutput}" output.`);
85
+ let h = Array.from(m.data, d), g = [];
86
+ if (this.info.probabilityOutput !== null) {
87
+ let e = u[this.info.probabilityOutput];
88
+ if (e?.data !== void 0) {
89
+ let t = Array.from(e.data, Number), r = t.length / n.length;
90
+ for (let e = 0; e < n.length; e += 1) g.push(t.slice(e * r, (e + 1) * r));
91
+ }
92
+ }
93
+ return {
94
+ labels: h,
95
+ probabilities: g,
96
+ numRows: n.length,
97
+ ms: f
98
+ };
99
+ }
100
+ async dispose() {
101
+ await this.session.release?.();
102
+ }
103
+ };
104
+ function h(e) {
105
+ let t = e.toLowerCase();
106
+ return o.some((e) => t.includes(e));
107
+ }
108
+ //#endregion
109
+ export { a as DEFAULT_TABULAR_PROVIDERS, m as TabularPredictor };
110
+
111
+ //# sourceMappingURL=predictor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"predictor.js","names":[],"sources":["../../src/tabular/predictor.ts"],"sourcesContent":["/**\n * Running a scikit-learn model in the browser, offline.\n *\n * The model file is produced by `tempest-fastapi-sdk`'s\n * `export_sklearn_to_onnx`. This is everything between that file and an\n * answer — the same glue the Python `OnnxPredictor` provides on a device,\n * with the browser's own traps handled:\n *\n * - **int64 labels arrive as `bigint`.** ONNX Runtime Web surfaces the\n * label tensor as a `BigInt64Array`, so a caller comparing `label === 1`\n * silently gets `false` and `JSON.stringify` throws. Labels are converted.\n * - **`ai.onnx.ml` needs the right build.** Measured: importing\n * `onnxruntime-web/webgpu` loads a WebAssembly binary without those\n * operators, and session creation fails with `No Op registered for\n * TreeEnsembleClassifier`. That failure is translated into an error that\n * names the import.\n * - **Which output is which.** A classifier returns `label` and\n * `probabilities`; a regressor returns a single `variable`. Indexing by\n * position works until the day you deploy the other kind.\n */\n\nimport * as ort from \"onnxruntime-web\";\n\nimport {\n FeatureShapeError,\n InferenceError,\n ModelLoadError,\n UnsupportedGraphError,\n} from \"./exceptions\";\nimport type {\n FeatureRow,\n PredictedLabel,\n TabularModelSource,\n TabularPrediction,\n TabularPredictorInfo,\n TabularPredictorOptions,\n} from \"./types\";\n\n/**\n * Execution providers used when the caller does not choose.\n *\n * WebAssembly only, and deliberately: scikit-learn graphs are `ai.onnx.ml`\n * operators, which the WebGPU backend does not implement. There is no\n * speed left on the table here — a 10-tree forest predicts a row in about\n * 0.05 ms in Chromium.\n */\nexport const DEFAULT_TABULAR_PROVIDERS: readonly string[] = [\"wasm\"];\n\n/** Output names that indicate predicted classes rather than scores. */\nconst LABEL_HINTS = [\"label\", \"class\", \"variable\", \"output\"] as const;\n\n/** Output names that indicate class scores. */\nconst PROBABILITY_HINTS = [\"probabilit\", \"score\"] as const;\n\n/** Largest plausible feature count; anything above is a dynamic-dim sentinel. */\nconst MAX_DECLARED_FEATURES = 1_000_000;\n\n/**\n * Pick the first output whose name contains one of `hints`.\n *\n * @param names Graph output names.\n * @param hints Lowercase substrings to look for.\n * @returns The matching name, or `null`.\n */\nfunction matchOutput(names: readonly string[], hints: readonly string[]): string | null {\n for (const hint of hints) {\n const found = names.find((name) => name.toLowerCase().includes(hint));\n if (found !== undefined) return found;\n }\n return null;\n}\n\n/**\n * Read the declared feature count from the input metadata.\n *\n * A dynamic batch dimension is reported as a symbolic string or as an\n * out-of-range number (`4294967295` — an unsigned `-1`), so only a sane\n * positive integer in the second position is trusted.\n *\n * @param session The loaded session.\n * @returns The feature count, or `null` when the graph does not declare one.\n */\nfunction declaredFeatures(session: ort.InferenceSession): number | null {\n const metadata = session.inputMetadata?.[0];\n if (metadata === undefined || metadata.isTensor !== true) return null;\n const dimension = metadata.shape[1];\n if (typeof dimension !== \"number\") return null;\n if (!Number.isInteger(dimension) || dimension <= 0) return null;\n return dimension > MAX_DECLARED_FEATURES ? null : dimension;\n}\n\n/**\n * Convert one raw label value into a JS-friendly label.\n *\n * @param value A tensor element: `bigint` for int64, `number` for float,\n * `string` for a string-labelled classifier.\n * @returns The label as a number or string.\n */\nfunction toLabel(value: unknown): PredictedLabel {\n if (typeof value === \"bigint\") return Number(value);\n if (typeof value === \"number\") return value;\n return String(value);\n}\n\n/**\n * Translate a session-creation failure into an error naming its cause.\n *\n * @param error Whatever ONNX Runtime threw.\n * @returns The error to surface.\n */\nfunction asLoadError(error: unknown): Error {\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes(\"No Op registered\")) {\n return new UnsupportedGraphError(\n \"This runtime build has no kernels for the model's operators. \" +\n \"scikit-learn exports use the ai.onnx.ml domain, which is missing \" +\n 'from the WebGPU build: import \"onnxruntime-web\", not ' +\n '\"onnxruntime-web/webgpu\". Original error: ' +\n message,\n { cause: error },\n );\n }\n return new ModelLoadError(`Failed to load the model: ${message}`, { cause: error });\n}\n\n/**\n * Translate a run failure into an error naming its cause.\n *\n * Measured: an export made with skl2onnx's default (ZipMap enabled) has a\n * probability output that is a sequence of maps, and ONNX Runtime Web\n * refuses to read non-tensor values — `Reading data from non-tensor typed\n * value is not supported`. That message describes the runtime's limitation,\n * not the fix, so it is replaced by one that names the export flag.\n *\n * @param error Whatever ONNX Runtime threw.\n * @returns The error to surface.\n */\nfunction asRunError(error: unknown): Error {\n const message = error instanceof Error ? error.message : String(error);\n if (\n message.includes(\"non-tensor typed value\") ||\n message.includes(\"Can't access output tensor data\")\n ) {\n return new InferenceError(\n \"The model has a non-tensor output, which ONNX Runtime Web cannot \" +\n \"read. A scikit-learn export made with ZipMap enabled returns a \" +\n \"sequence of maps per row — re-export with export_sklearn_to_onnx, \" +\n `which disables it. Original error: ${message}`,\n { cause: error },\n );\n }\n return new InferenceError(`Inference failed: ${message}`, { cause: error });\n}\n\n/**\n * A loaded tabular model, ready to answer.\n *\n * @example\n * ```ts\n * const predictor = await TabularPredictor.create(\"/models/classifier.onnx\");\n * const { labels, probabilities } = await predictor.predict([[5.1, 3.5, 1.4, 0.2]]);\n * ```\n */\nexport class TabularPredictor {\n private constructor(\n private readonly session: ort.InferenceSession,\n /** What is loaded and how it is configured. */\n public readonly info: TabularPredictorInfo,\n ) {}\n\n /**\n * Load a model and describe its graph.\n *\n * @param source A URL string, or the model bytes (which is what an\n * offline app passes, having read them from the cache).\n * @param options Providers, warm-up and pass-through session options.\n * @throws {@link UnsupportedGraphError} when the runtime build lacks the\n * `ai.onnx.ml` operators — the WebGPU entry point does.\n * @throws {@link ModelLoadError} for any other load failure.\n */\n static async create(\n source: TabularModelSource,\n options: TabularPredictorOptions = {},\n ): Promise<TabularPredictor> {\n const providers = options.providers ?? DEFAULT_TABULAR_PROVIDERS;\n let session: ort.InferenceSession;\n try {\n session = await ort.InferenceSession.create(source as never, {\n ...(options.sessionOptions ?? {}),\n executionProviders:\n providers as ort.InferenceSession.SessionOptions[\"executionProviders\"],\n });\n } catch (error) {\n throw asLoadError(error);\n }\n\n const outputNames = [...session.outputNames];\n const probabilityOutput = matchOutput(outputNames, PROBABILITY_HINTS);\n const labelOutput =\n outputNames.find((name) => name !== probabilityOutput && isLabelName(name)) ??\n outputNames.find((name) => name !== probabilityOutput) ??\n (outputNames[0] as string);\n\n const predictor = new TabularPredictor(session, {\n inputName: session.inputNames[0] as string,\n numFeatures: declaredFeatures(session),\n outputNames,\n labelOutput,\n probabilityOutput,\n isClassifier: probabilityOutput !== null,\n providers,\n });\n\n if (options.warmup !== false) await predictor.warmUp();\n return predictor;\n }\n\n /**\n * Run one throwaway inference so the first real call is not the slow one.\n *\n * Skipped when the graph does not declare a feature count, since there\n * is no shape to synthesise. Failures are swallowed: a warm-up that\n * cannot run is not a reason to refuse to serve.\n */\n async warmUp(): Promise<void> {\n const features = this.info.numFeatures;\n if (features === null) return;\n try {\n await this.predict([new Array<number>(features).fill(0)]);\n } catch {\n /* a failed warm-up must not prevent serving */\n }\n }\n\n /**\n * Predict for a batch of rows.\n *\n * @param rows One array of feature values per row, in training column\n * order. A single row is still wrapped: `[[...]]`.\n * @returns Labels, class scores when the model produces them, and the\n * call's duration.\n * @throws {@link FeatureShapeError} when the batch is empty, ragged, or\n * the wrong width — checked here so the failure names the mismatch\n * instead of surfacing as an opaque runtime error.\n * @throws {@link InferenceError} when the session runs but its outputs\n * cannot be read.\n */\n async predict(rows: readonly FeatureRow[]): Promise<TabularPrediction> {\n if (!Array.isArray(rows) || rows.length === 0) {\n throw new FeatureShapeError(\n \"predict() needs at least one row, shaped [[f1, f2, ...]].\",\n );\n }\n const width = rows[0]?.length ?? 0;\n if (width === 0) {\n throw new FeatureShapeError(\"The first row has no feature values.\");\n }\n const ragged = rows.findIndex((row) => row.length !== width);\n if (ragged !== -1) {\n throw new FeatureShapeError(\n `All rows must have the same width; row ${ragged} has ` +\n `${rows[ragged]?.length} values, expected ${width}.`,\n );\n }\n const expected = this.info.numFeatures;\n if (expected !== null && width !== expected) {\n throw new FeatureShapeError(\n `The model expects ${expected} features per row, got ${width}.`,\n );\n }\n\n const flat = new Float32Array(rows.length * width);\n for (let index = 0; index < rows.length; index += 1) {\n flat.set(rows[index] as number[], index * width);\n }\n const tensor = new ort.Tensor(\"float32\", flat, [rows.length, width]);\n\n const started = performance.now();\n let outputs: ort.InferenceSession.OnnxValueMapType;\n try {\n outputs = await this.session.run({ [this.info.inputName]: tensor });\n } catch (error) {\n throw asRunError(error);\n }\n const ms = performance.now() - started;\n\n const labelTensor = outputs[this.info.labelOutput];\n if (labelTensor?.data === undefined) {\n throw new InferenceError(\n `The model produced no readable \"${this.info.labelOutput}\" output.`,\n );\n }\n\n const labels: PredictedLabel[] = Array.from(\n labelTensor.data as ArrayLike<unknown>,\n toLabel,\n );\n\n const probabilities: number[][] = [];\n if (this.info.probabilityOutput !== null) {\n const scores = outputs[this.info.probabilityOutput];\n if (scores?.data !== undefined) {\n const values = Array.from(scores.data as ArrayLike<number>, Number);\n const classes = values.length / rows.length;\n for (let index = 0; index < rows.length; index += 1) {\n probabilities.push(values.slice(index * classes, (index + 1) * classes));\n }\n }\n }\n\n return { labels, probabilities, numRows: rows.length, ms };\n }\n\n /**\n * Release the session's memory.\n *\n * Worth calling on a route that swaps models: the WebAssembly heap does\n * not shrink on garbage collection alone.\n */\n async dispose(): Promise<void> {\n await this.session.release?.();\n }\n}\n\n/**\n * Whether an output name looks like a label rather than a score.\n *\n * @param name The graph output name.\n * @returns `true` when the name matches a known label convention.\n */\nfunction isLabelName(name: string): boolean {\n const lowered = name.toLowerCase();\n return LABEL_HINTS.some((hint) => lowered.includes(hint));\n}\n"],"mappings":";;;AA8CA,IAAa,IAA+C,CAAC,MAAM,GAG7D,IAAc;CAAC;CAAS;CAAS;CAAY;AAAQ,GAGrD,IAAoB,CAAC,cAAc,OAAO,GAG1C,IAAwB;AAS9B,SAAS,EAAY,GAA0B,GAAyC;CACpF,KAAK,IAAM,KAAQ,GAAO;EACtB,IAAM,IAAQ,EAAM,MAAM,MAAS,EAAK,YAAY,CAAC,CAAC,SAAS,CAAI,CAAC;EACpE,IAAI,MAAU,KAAA,GAAW,OAAO;CACpC;CACA,OAAO;AACX;AAYA,SAAS,EAAiB,GAA8C;CACpE,IAAM,IAAW,EAAQ,gBAAgB;CACzC,IAAI,MAAa,KAAA,KAAa,EAAS,aAAa,IAAM,OAAO;CACjE,IAAM,IAAY,EAAS,MAAM;CAGjC,OAFI,OAAO,KAAc,YACrB,CAAC,OAAO,UAAU,CAAS,KAAK,KAAa,KAC1C,IAAY,IADwC,OACT;AACtD;AASA,SAAS,EAAQ,GAAgC;CAG7C,OAFI,OAAO,KAAU,WAAiB,OAAO,CAAK,IAC9C,OAAO,KAAU,WAAiB,IAC/B,OAAO,CAAK;AACvB;AAQA,SAAS,EAAY,GAAuB;CACxC,IAAM,IAAU,aAAiB,QAAQ,EAAM,UAAU,OAAO,CAAK;CAWrE,OAVI,EAAQ,SAAS,kBAAkB,IAC5B,IAAI,EACP,sOAII,GACJ,EAAE,OAAO,EAAM,CACnB,IAEG,IAAI,EAAe,6BAA6B,KAAW,EAAE,OAAO,EAAM,CAAC;AACtF;AAcA,SAAS,EAAW,GAAuB;CACvC,IAAM,IAAU,aAAiB,QAAQ,EAAM,UAAU,OAAO,CAAK;CAarE,OAXI,EAAQ,SAAS,wBAAwB,KACzC,EAAQ,SAAS,iCAAiC,IAE3C,IAAI,EACP,wOAG0C,KAC1C,EAAE,OAAO,EAAM,CACnB,IAEG,IAAI,EAAe,qBAAqB,KAAW,EAAE,OAAO,EAAM,CAAC;AAC9E;AAWA,IAAa,IAAb,MAAa,EAAiB;CAEL;CAED;CAHpB,YACI,GAEA,GACF;EADkB,AAFC,KAAA,UAAA,GAED,KAAA,OAAA;CACjB;CAYH,aAAa,OACT,GACA,IAAmC,CAAC,GACX;EACzB,IAAM,IAAY,EAAQ,aAAa,GACnC;EACJ,IAAI;GACA,IAAU,MAAM,EAAI,iBAAiB,OAAO,GAAiB;IACzD,GAAI,EAAQ,kBAAkB,CAAC;IAC/B,oBACI;GACR,CAAC;EACL,SAAS,GAAO;GACZ,MAAM,EAAY,CAAK;EAC3B;EAEA,IAAM,IAAc,CAAC,GAAG,EAAQ,WAAW,GACrC,IAAoB,EAAY,GAAa,CAAiB,GAC9D,IACF,EAAY,MAAM,MAAS,MAAS,KAAqB,EAAY,CAAI,CAAC,KAC1E,EAAY,MAAM,MAAS,MAAS,CAAiB,KACpD,EAAY,IAEX,IAAY,IAAI,EAAiB,GAAS;GAC5C,WAAW,EAAQ,WAAW;GAC9B,aAAa,EAAiB,CAAO;GACrC;GACA;GACA;GACA,cAAc,MAAsB;GACpC;EACJ,CAAC;EAGD,OADI,EAAQ,WAAW,MAAO,MAAM,EAAU,OAAO,GAC9C;CACX;CASA,MAAM,SAAwB;EAC1B,IAAM,IAAW,KAAK,KAAK;EACvB,UAAa,MACjB,IAAI;GACA,MAAM,KAAK,QAAQ,CAAK,MAAc,CAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;EAC5D,QAAQ,CAER;CACJ;CAeA,MAAM,QAAQ,GAAyD;EACnE,IAAI,CAAC,MAAM,QAAQ,CAAI,KAAK,EAAK,WAAW,GACxC,MAAM,IAAI,EACN,2DACJ;EAEJ,IAAM,IAAQ,EAAK,EAAE,EAAE,UAAU;EACjC,IAAI,MAAU,GACV,MAAM,IAAI,EAAkB,sCAAsC;EAEtE,IAAM,IAAS,EAAK,WAAW,MAAQ,EAAI,WAAW,CAAK;EAC3D,IAAI,MAAW,IACX,MAAM,IAAI,EACN,0CAA0C,EAAO,OAC1C,EAAK,EAAO,EAAE,OAAO,oBAAoB,EAAM,EAC1D;EAEJ,IAAM,IAAW,KAAK,KAAK;EAC3B,IAAI,MAAa,QAAQ,MAAU,GAC/B,MAAM,IAAI,EACN,qBAAqB,EAAS,yBAAyB,EAAM,EACjE;EAGJ,IAAM,IAAO,IAAI,aAAa,EAAK,SAAS,CAAK;EACjD,KAAK,IAAI,IAAQ,GAAG,IAAQ,EAAK,QAAQ,KAAS,GAC9C,EAAK,IAAI,EAAK,IAAoB,IAAQ,CAAK;EAEnD,IAAM,IAAS,IAAI,EAAI,OAAO,WAAW,GAAM,CAAC,EAAK,QAAQ,CAAK,CAAC,GAE7D,IAAU,YAAY,IAAI,GAC5B;EACJ,IAAI;GACA,IAAU,MAAM,KAAK,QAAQ,IAAI,GAAG,KAAK,KAAK,YAAY,EAAO,CAAC;EACtE,SAAS,GAAO;GACZ,MAAM,EAAW,CAAK;EAC1B;EACA,IAAM,IAAK,YAAY,IAAI,IAAI,GAEzB,IAAc,EAAQ,KAAK,KAAK;EACtC,IAAI,GAAa,SAAS,KAAA,GACtB,MAAM,IAAI,EACN,mCAAmC,KAAK,KAAK,YAAY,UAC7D;EAGJ,IAAM,IAA2B,MAAM,KACnC,EAAY,MACZ,CACJ,GAEM,IAA4B,CAAC;EACnC,IAAI,KAAK,KAAK,sBAAsB,MAAM;GACtC,IAAM,IAAS,EAAQ,KAAK,KAAK;GACjC,IAAI,GAAQ,SAAS,KAAA,GAAW;IAC5B,IAAM,IAAS,MAAM,KAAK,EAAO,MAA2B,MAAM,GAC5D,IAAU,EAAO,SAAS,EAAK;IACrC,KAAK,IAAI,IAAQ,GAAG,IAAQ,EAAK,QAAQ,KAAS,GAC9C,EAAc,KAAK,EAAO,MAAM,IAAQ,IAAU,IAAQ,KAAK,CAAO,CAAC;GAE/E;EACJ;EAEA,OAAO;GAAE;GAAQ;GAAe,SAAS,EAAK;GAAQ;EAAG;CAC7D;CAQA,MAAM,UAAyB;EAC3B,MAAM,KAAK,QAAQ,UAAU;CACjC;AACJ;AAQA,SAAS,EAAY,GAAuB;CACxC,IAAM,IAAU,EAAK,YAAY;CACjC,OAAO,EAAY,MAAM,MAAS,EAAQ,SAAS,CAAI,CAAC;AAC5D"}
@@ -0,0 +1,2 @@
1
+ const e=require("./cache.cjs"),t=require("./predictor.cjs");let n=require("react");function r(r,i={}){let[a,o]=(0,n.useState)(null),[s,c]=(0,n.useState)(`idle`),[l,u]=(0,n.useState)(null),[d,f]=(0,n.useState)(0),p=(0,n.useRef)(i);(0,n.useEffect)(()=>{p.current=i}),(0,n.useEffect)(()=>{if(r===null){c(`idle`);return}let n=!1,i=null;return c(`loading`),u(null),(async()=>{try{let a=p.current,s=a.cache??!0,l=typeof r==`string`&&s!==!1?await e.fetchModelBytes(r,typeof s==`object`?s:{}):r,u=await t.TabularPredictor.create(l,{providers:a.providers,warmup:a.warmup,sessionOptions:a.sessionOptions});if(i=u,n){await u.dispose();return}o(u),c(`ready`)}catch(e){if(n)return;o(null),u(e instanceof Error?e:Error(String(e))),c(`error`)}})(),()=>{n=!0,o(null),i?.dispose()}},[r,d]);let m=(0,n.useCallback)(async e=>{if(a===null)throw Error("predict() was called before the model finished loading. Gate on `isReady`.");return await a.predict(e)},[a]),h=(0,n.useCallback)(()=>f(e=>e+1),[]);return{predictor:a,status:s,error:l,isReady:s===`ready`&&a!==null,predict:m,reload:h}}exports.useTabularPredictor=r;
2
+ //# sourceMappingURL=use-tabular-predictor.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-tabular-predictor.cjs","names":[],"sources":["../../src/tabular/use-tabular-predictor.ts"],"sourcesContent":["/**\n * React binding for {@link TabularPredictor}.\n *\n * Loading a model is async, cancellable, and has to be undone on unmount —\n * three things every component that touches inference gets wrong the same\n * way. The hook owns that lifecycle so a component only deals with\n * `status` and `predict`.\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { fetchModelBytes, type ModelCacheOptions } from \"./cache\";\nimport { TabularPredictor } from \"./predictor\";\nimport type {\n FeatureRow,\n TabularModelSource,\n TabularPrediction,\n TabularPredictorOptions,\n} from \"./types\";\n\n/** Lifecycle of the model behind the hook. */\nexport type TabularPredictorStatus = \"idle\" | \"loading\" | \"ready\" | \"error\";\n\n/** Options for {@link useTabularPredictor}. */\nexport interface UseTabularPredictorOptions extends TabularPredictorOptions {\n /**\n * Cache the model bytes on the device, so later loads work offline.\n *\n * On by default when the source is a URL: an app that runs inference in\n * the browser almost always wants it to keep working without a network,\n * and the failure mode of not caching only shows up in a tunnel.\n */\n readonly cache?: boolean | ModelCacheOptions;\n}\n\n/** What {@link useTabularPredictor} returns. */\nexport interface UseTabularPredictorResult {\n /** The loaded predictor, or `null` while loading or on error. */\n readonly predictor: TabularPredictor | null;\n /** Where the load is. */\n readonly status: TabularPredictorStatus;\n /** Why the load failed. */\n readonly error: Error | null;\n /** Whether the model is loaded and can answer. */\n readonly isReady: boolean;\n /**\n * Predict for a batch of rows.\n *\n * @throws When called before the model is ready — awaiting `isReady`\n * is the caller's job, and a silent empty result would hide the bug.\n */\n readonly predict: (rows: readonly FeatureRow[]) => Promise<TabularPrediction>;\n /** Load the model again, e.g. after a failure or a new version. */\n readonly reload: () => void;\n}\n\n/**\n * Load a tabular model and keep it for the component's lifetime.\n *\n * @example\n * ```tsx\n * function RiskWidget() {\n * const { predict, isReady } = useTabularPredictor(\"/models/risk-v3.onnx\");\n * const [score, setScore] = useState<number | null>(null);\n *\n * async function onSubmit(features: number[]) {\n * const { probabilities } = await predict([features]);\n * setScore(probabilities[0]?.[1] ?? null);\n * }\n *\n * return <button disabled={!isReady} onClick={() => onSubmit([1, 2, 3, 4])}>Score</button>;\n * }\n * ```\n *\n * @param source Model URL, or the bytes when the app already has them.\n * Pass `null` to hold off loading (a gate, a lazy tab).\n * @param options Predictor options plus caching.\n * @returns The predictor, its status, and a `predict` bound to it.\n */\nexport function useTabularPredictor(\n source: TabularModelSource | null,\n options: UseTabularPredictorOptions = {},\n): UseTabularPredictorResult {\n const [predictor, setPredictor] = useState<TabularPredictor | null>(null);\n const [status, setStatus] = useState<TabularPredictorStatus>(\"idle\");\n const [error, setError] = useState<Error | null>(null);\n const [attempt, setAttempt] = useState(0);\n\n const optionsRef = useRef(options);\n\n useEffect(() => {\n optionsRef.current = options;\n });\n\n useEffect(() => {\n if (source === null) {\n setStatus(\"idle\");\n return;\n }\n\n let cancelled = false;\n let loaded: TabularPredictor | null = null;\n\n setStatus(\"loading\");\n setError(null);\n\n void (async () => {\n try {\n const current = optionsRef.current;\n const cache = current.cache ?? true;\n const bytes =\n typeof source === \"string\" && cache !== false\n ? await fetchModelBytes(source, typeof cache === \"object\" ? cache : {})\n : source;\n const created = await TabularPredictor.create(bytes, {\n providers: current.providers,\n warmup: current.warmup,\n sessionOptions: current.sessionOptions,\n });\n loaded = created;\n if (cancelled) {\n await created.dispose();\n return;\n }\n setPredictor(created);\n setStatus(\"ready\");\n } catch (caught) {\n if (cancelled) return;\n setPredictor(null);\n setError(caught instanceof Error ? caught : new Error(String(caught)));\n setStatus(\"error\");\n }\n })();\n\n return () => {\n cancelled = true;\n setPredictor(null);\n void loaded?.dispose();\n };\n }, [source, attempt]);\n\n const predict = useCallback(\n async (rows: readonly FeatureRow[]): Promise<TabularPrediction> => {\n if (predictor === null) {\n throw new Error(\n \"predict() was called before the model finished loading. \" +\n \"Gate on `isReady`.\",\n );\n }\n return await predictor.predict(rows);\n },\n [predictor],\n );\n\n const reload = useCallback(() => setAttempt((value) => value + 1), []);\n\n return {\n predictor,\n status,\n error,\n isReady: status === \"ready\" && predictor !== null,\n predict,\n reload,\n };\n}\n"],"mappings":"mFA+EA,SAAgB,EACZ,EACA,EAAsC,CAAC,EACd,CACzB,GAAM,CAAC,EAAW,IAAA,EAAA,EAAA,SAAA,CAAkD,IAAI,EAClE,CAAC,EAAQ,IAAA,EAAA,EAAA,SAAA,CAA8C,MAAM,EAC7D,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,CAAmC,IAAI,EAC/C,CAAC,EAAS,IAAA,EAAA,EAAA,SAAA,CAAuB,CAAC,EAElC,GAAA,EAAA,EAAA,OAAA,CAAoB,CAAO,GAEjC,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAW,QAAU,CACzB,CAAC,GAED,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,IAAW,KAAM,CACjB,EAAU,MAAM,EAChB,MACJ,CAEA,IAAI,EAAY,GACZ,EAAkC,KAiCtC,OA/BA,EAAU,SAAS,EACnB,EAAS,IAAI,GAEP,SAAY,CACd,GAAI,CACA,IAAM,EAAU,EAAW,QACrB,EAAQ,EAAQ,OAAS,GACzB,EACF,OAAO,GAAW,UAAY,IAAU,GAClC,MAAM,EAAA,gBAAgB,EAAQ,OAAO,GAAU,SAAW,EAAQ,CAAC,CAAC,EACpE,EACJ,EAAU,MAAM,EAAA,iBAAiB,OAAO,EAAO,CACjD,UAAW,EAAQ,UACnB,OAAQ,EAAQ,OAChB,eAAgB,EAAQ,cAC5B,CAAC,EAED,GADA,EAAS,EACL,EAAW,CACX,MAAM,EAAQ,QAAQ,EACtB,MACJ,CACA,EAAa,CAAO,EACpB,EAAU,OAAO,CACrB,OAAS,EAAQ,CACb,GAAI,EAAW,OACf,EAAa,IAAI,EACjB,EAAS,aAAkB,MAAQ,EAAa,MAAM,OAAO,CAAM,CAAC,CAAC,EACrE,EAAU,OAAO,CACrB,CACJ,EAAA,CAAG,MAEU,CACT,EAAY,GACZ,EAAa,IAAI,EACjB,GAAa,QAAQ,CACzB,CACJ,EAAG,CAAC,EAAQ,CAAO,CAAC,EAEpB,IAAM,GAAA,EAAA,EAAA,YAAA,CACF,KAAO,IAA4D,CAC/D,GAAI,IAAc,KACd,MAAU,MACN,4EAEJ,EAEJ,OAAO,MAAM,EAAU,QAAQ,CAAI,CACvC,EACA,CAAC,CAAS,CACd,EAEM,GAAA,EAAA,EAAA,YAAA,KAA2B,EAAY,GAAU,EAAQ,CAAC,EAAG,CAAC,CAAC,EAErE,MAAO,CACH,YACA,SACA,QACA,QAAS,IAAW,SAAW,IAAc,KAC7C,UACA,QACJ,CACJ"}
@@ -0,0 +1,51 @@
1
+ import { fetchModelBytes as e } from "./cache.js";
2
+ import { TabularPredictor as t } from "./predictor.js";
3
+ import { useCallback as n, useEffect as r, useRef as i, useState as a } from "react";
4
+ //#region src/tabular/use-tabular-predictor.ts
5
+ function o(o, s = {}) {
6
+ let [c, l] = a(null), [u, d] = a("idle"), [f, p] = a(null), [m, h] = a(0), g = i(s);
7
+ r(() => {
8
+ g.current = s;
9
+ }), r(() => {
10
+ if (o === null) {
11
+ d("idle");
12
+ return;
13
+ }
14
+ let n = !1, r = null;
15
+ return d("loading"), p(null), (async () => {
16
+ try {
17
+ let i = g.current, a = i.cache ?? !0, s = typeof o == "string" && a !== !1 ? await e(o, typeof a == "object" ? a : {}) : o, c = await t.create(s, {
18
+ providers: i.providers,
19
+ warmup: i.warmup,
20
+ sessionOptions: i.sessionOptions
21
+ });
22
+ if (r = c, n) {
23
+ await c.dispose();
24
+ return;
25
+ }
26
+ l(c), d("ready");
27
+ } catch (e) {
28
+ if (n) return;
29
+ l(null), p(e instanceof Error ? e : Error(String(e))), d("error");
30
+ }
31
+ })(), () => {
32
+ n = !0, l(null), r?.dispose();
33
+ };
34
+ }, [o, m]);
35
+ let _ = n(async (e) => {
36
+ if (c === null) throw Error("predict() was called before the model finished loading. Gate on `isReady`.");
37
+ return await c.predict(e);
38
+ }, [c]), v = n(() => h((e) => e + 1), []);
39
+ return {
40
+ predictor: c,
41
+ status: u,
42
+ error: f,
43
+ isReady: u === "ready" && c !== null,
44
+ predict: _,
45
+ reload: v
46
+ };
47
+ }
48
+ //#endregion
49
+ export { o as useTabularPredictor };
50
+
51
+ //# sourceMappingURL=use-tabular-predictor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-tabular-predictor.js","names":[],"sources":["../../src/tabular/use-tabular-predictor.ts"],"sourcesContent":["/**\n * React binding for {@link TabularPredictor}.\n *\n * Loading a model is async, cancellable, and has to be undone on unmount —\n * three things every component that touches inference gets wrong the same\n * way. The hook owns that lifecycle so a component only deals with\n * `status` and `predict`.\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { fetchModelBytes, type ModelCacheOptions } from \"./cache\";\nimport { TabularPredictor } from \"./predictor\";\nimport type {\n FeatureRow,\n TabularModelSource,\n TabularPrediction,\n TabularPredictorOptions,\n} from \"./types\";\n\n/** Lifecycle of the model behind the hook. */\nexport type TabularPredictorStatus = \"idle\" | \"loading\" | \"ready\" | \"error\";\n\n/** Options for {@link useTabularPredictor}. */\nexport interface UseTabularPredictorOptions extends TabularPredictorOptions {\n /**\n * Cache the model bytes on the device, so later loads work offline.\n *\n * On by default when the source is a URL: an app that runs inference in\n * the browser almost always wants it to keep working without a network,\n * and the failure mode of not caching only shows up in a tunnel.\n */\n readonly cache?: boolean | ModelCacheOptions;\n}\n\n/** What {@link useTabularPredictor} returns. */\nexport interface UseTabularPredictorResult {\n /** The loaded predictor, or `null` while loading or on error. */\n readonly predictor: TabularPredictor | null;\n /** Where the load is. */\n readonly status: TabularPredictorStatus;\n /** Why the load failed. */\n readonly error: Error | null;\n /** Whether the model is loaded and can answer. */\n readonly isReady: boolean;\n /**\n * Predict for a batch of rows.\n *\n * @throws When called before the model is ready — awaiting `isReady`\n * is the caller's job, and a silent empty result would hide the bug.\n */\n readonly predict: (rows: readonly FeatureRow[]) => Promise<TabularPrediction>;\n /** Load the model again, e.g. after a failure or a new version. */\n readonly reload: () => void;\n}\n\n/**\n * Load a tabular model and keep it for the component's lifetime.\n *\n * @example\n * ```tsx\n * function RiskWidget() {\n * const { predict, isReady } = useTabularPredictor(\"/models/risk-v3.onnx\");\n * const [score, setScore] = useState<number | null>(null);\n *\n * async function onSubmit(features: number[]) {\n * const { probabilities } = await predict([features]);\n * setScore(probabilities[0]?.[1] ?? null);\n * }\n *\n * return <button disabled={!isReady} onClick={() => onSubmit([1, 2, 3, 4])}>Score</button>;\n * }\n * ```\n *\n * @param source Model URL, or the bytes when the app already has them.\n * Pass `null` to hold off loading (a gate, a lazy tab).\n * @param options Predictor options plus caching.\n * @returns The predictor, its status, and a `predict` bound to it.\n */\nexport function useTabularPredictor(\n source: TabularModelSource | null,\n options: UseTabularPredictorOptions = {},\n): UseTabularPredictorResult {\n const [predictor, setPredictor] = useState<TabularPredictor | null>(null);\n const [status, setStatus] = useState<TabularPredictorStatus>(\"idle\");\n const [error, setError] = useState<Error | null>(null);\n const [attempt, setAttempt] = useState(0);\n\n const optionsRef = useRef(options);\n\n useEffect(() => {\n optionsRef.current = options;\n });\n\n useEffect(() => {\n if (source === null) {\n setStatus(\"idle\");\n return;\n }\n\n let cancelled = false;\n let loaded: TabularPredictor | null = null;\n\n setStatus(\"loading\");\n setError(null);\n\n void (async () => {\n try {\n const current = optionsRef.current;\n const cache = current.cache ?? true;\n const bytes =\n typeof source === \"string\" && cache !== false\n ? await fetchModelBytes(source, typeof cache === \"object\" ? cache : {})\n : source;\n const created = await TabularPredictor.create(bytes, {\n providers: current.providers,\n warmup: current.warmup,\n sessionOptions: current.sessionOptions,\n });\n loaded = created;\n if (cancelled) {\n await created.dispose();\n return;\n }\n setPredictor(created);\n setStatus(\"ready\");\n } catch (caught) {\n if (cancelled) return;\n setPredictor(null);\n setError(caught instanceof Error ? caught : new Error(String(caught)));\n setStatus(\"error\");\n }\n })();\n\n return () => {\n cancelled = true;\n setPredictor(null);\n void loaded?.dispose();\n };\n }, [source, attempt]);\n\n const predict = useCallback(\n async (rows: readonly FeatureRow[]): Promise<TabularPrediction> => {\n if (predictor === null) {\n throw new Error(\n \"predict() was called before the model finished loading. \" +\n \"Gate on `isReady`.\",\n );\n }\n return await predictor.predict(rows);\n },\n [predictor],\n );\n\n const reload = useCallback(() => setAttempt((value) => value + 1), []);\n\n return {\n predictor,\n status,\n error,\n isReady: status === \"ready\" && predictor !== null,\n predict,\n reload,\n };\n}\n"],"mappings":";;;;AA+EA,SAAgB,EACZ,GACA,IAAsC,CAAC,GACd;CACzB,IAAM,CAAC,GAAW,KAAgB,EAAkC,IAAI,GAClE,CAAC,GAAQ,KAAa,EAAiC,MAAM,GAC7D,CAAC,GAAO,KAAY,EAAuB,IAAI,GAC/C,CAAC,GAAS,KAAc,EAAS,CAAC,GAElC,IAAa,EAAO,CAAO;CAMjC,AAJA,QAAgB;EACZ,EAAW,UAAU;CACzB,CAAC,GAED,QAAgB;EACZ,IAAI,MAAW,MAAM;GACjB,EAAU,MAAM;GAChB;EACJ;EAEA,IAAI,IAAY,IACZ,IAAkC;EAiCtC,OA/BA,EAAU,SAAS,GACnB,EAAS,IAAI,IAEP,YAAY;GACd,IAAI;IACA,IAAM,IAAU,EAAW,SACrB,IAAQ,EAAQ,SAAS,IACzB,IACF,OAAO,KAAW,YAAY,MAAU,KAClC,MAAM,EAAgB,GAAQ,OAAO,KAAU,WAAW,IAAQ,CAAC,CAAC,IACpE,GACJ,IAAU,MAAM,EAAiB,OAAO,GAAO;KACjD,WAAW,EAAQ;KACnB,QAAQ,EAAQ;KAChB,gBAAgB,EAAQ;IAC5B,CAAC;IAED,IADA,IAAS,GACL,GAAW;KACX,MAAM,EAAQ,QAAQ;KACtB;IACJ;IAEA,AADA,EAAa,CAAO,GACpB,EAAU,OAAO;GACrB,SAAS,GAAQ;IACb,IAAI,GAAW;IAGf,AAFA,EAAa,IAAI,GACjB,EAAS,aAAkB,QAAQ,IAAa,MAAM,OAAO,CAAM,CAAC,CAAC,GACrE,EAAU,OAAO;GACrB;EACJ,EAAA,CAAG,SAEU;GAGT,AAFA,IAAY,IACZ,EAAa,IAAI,GACjB,GAAa,QAAQ;EACzB;CACJ,GAAG,CAAC,GAAQ,CAAO,CAAC;CAEpB,IAAM,IAAU,EACZ,OAAO,MAA4D;EAC/D,IAAI,MAAc,MACd,MAAU,MACN,4EAEJ;EAEJ,OAAO,MAAM,EAAU,QAAQ,CAAI;CACvC,GACA,CAAC,CAAS,CACd,GAEM,IAAS,QAAkB,GAAY,MAAU,IAAQ,CAAC,GAAG,CAAC,CAAC;CAErE,OAAO;EACH;EACA;EACA;EACA,SAAS,MAAW,WAAW,MAAc;EAC7C;EACA;CACJ;AACJ"}
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./tabular/assets.cjs"),t=require("./tabular/exceptions.cjs"),n=require("./tabular/cache.cjs"),r=require("./tabular/predictor.cjs"),i=require("./tabular/manifest.cjs"),a=require("./tabular/use-tabular-predictor.cjs");exports.DEFAULT_MODEL_CACHE=n.DEFAULT_MODEL_CACHE,exports.DEFAULT_TABULAR_PROVIDERS=r.DEFAULT_TABULAR_PROVIDERS,exports.FeatureShapeError=t.FeatureShapeError,exports.InferenceError=t.InferenceError,exports.MANIFEST_FILENAME=i.MANIFEST_FILENAME,exports.ModelFetchError=t.ModelFetchError,exports.ModelLoadError=t.ModelLoadError,exports.ORT_WASM_ASSETS=e.ORT_WASM_ASSETS,exports.SUPPORTED_MANIFEST_SCHEMA=i.SUPPORTED_MANIFEST_SCHEMA,exports.TabularError=t.TabularError,exports.TabularPredictor=r.TabularPredictor,exports.UnsupportedGraphError=t.UnsupportedGraphError,exports.cacheModelBytes=n.cacheModelBytes,exports.clearModelCache=n.clearModelCache,exports.configureOrtAssets=e.configureOrtAssets,exports.fetchEdgeManifest=i.fetchEdgeManifest,exports.fetchModelBytes=n.fetchModelBytes,exports.isModelCached=n.isModelCached,exports.loadEdgePackage=i.loadEdgePackage,exports.ortAssetUrls=e.ortAssetUrls,exports.useTabularPredictor=a.useTabularPredictor;