@ohtaman/washi 0.5.0 → 0.7.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 (107) hide show
  1. package/README.ja.md +103 -386
  2. package/README.md +104 -387
  3. package/README.zh-CN.md +103 -386
  4. package/THIRD_PARTY_LICENSES.md +5 -4
  5. package/dist/browser/_washi-node-only-BvRk9kiK.js +0 -0
  6. package/dist/browser/browser-runtime-TLCY0hkc.js +1793 -0
  7. package/dist/browser/highs-worker.js +1 -1
  8. package/dist/browser/highs.wasm +0 -0
  9. package/dist/browser/highs.wasm.LICENSE +1 -2
  10. package/dist/browser/loader-DewlSho8.js +643 -0
  11. package/dist/browser/solvers/glpk.js +3 -3
  12. package/dist/browser/solvers/glpk.min.js +1 -1
  13. package/dist/browser/solvers/z3.js +7 -1
  14. package/dist/browser/solvers/z3.min.js +1 -1
  15. package/dist/browser/washi.js +1708 -540
  16. package/dist/browser/washi.min.js +3 -4
  17. package/dist/lib/_washi-node-only-BvRk9kiK.js +0 -0
  18. package/dist/lib/highs-browser-runtime.js +2120 -4124
  19. package/dist/lib/highs-node-runtime.js +24 -1
  20. package/dist/lib/highs-runtime.js +2 -0
  21. package/dist/lib/highs-worker.js +1 -1
  22. package/dist/lib/highs.wasm +0 -0
  23. package/dist/lib/highs.wasm.LICENSE +1 -2
  24. package/dist/lib/loader-BZUQNvh4.js +649 -0
  25. package/dist/lib/{registry-BEfvpZW3.js → registry-D7TZ3Wh_.js} +142 -59
  26. package/dist/lib/solvers/glpk.js +4 -4
  27. package/dist/lib/solvers/z3.js +1 -1
  28. package/dist/lib/washi.js +1576 -465
  29. package/dist/types/decomposition/benders.d.ts +32 -0
  30. package/dist/types/decomposition/benders.d.ts.map +1 -0
  31. package/dist/types/decomposition/column-generation.d.ts +34 -0
  32. package/dist/types/decomposition/column-generation.d.ts.map +1 -0
  33. package/dist/types/decomposition/index.d.ts +4 -0
  34. package/dist/types/decomposition/index.d.ts.map +1 -0
  35. package/dist/types/decomposition/types.d.ts +230 -0
  36. package/dist/types/decomposition/types.d.ts.map +1 -0
  37. package/dist/types/index.d.ts +4 -3
  38. package/dist/types/index.d.ts.map +1 -1
  39. package/dist/types/modeler/boolean-expr.d.ts.map +1 -1
  40. package/dist/types/modeler/constraint.d.ts +11 -0
  41. package/dist/types/modeler/constraint.d.ts.map +1 -1
  42. package/dist/types/modeler/expression.d.ts +57 -26
  43. package/dist/types/modeler/expression.d.ts.map +1 -1
  44. package/dist/types/modeler/index.d.ts +4 -3
  45. package/dist/types/modeler/index.d.ts.map +1 -1
  46. package/dist/types/modeler/linearizer.d.ts +15 -0
  47. package/dist/types/modeler/linearizer.d.ts.map +1 -0
  48. package/dist/types/modeler/lp-writer.d.ts +3 -1
  49. package/dist/types/modeler/lp-writer.d.ts.map +1 -1
  50. package/dist/types/modeler/model.d.ts +159 -8
  51. package/dist/types/modeler/model.d.ts.map +1 -1
  52. package/dist/types/modeler/parser.d.ts +25 -5
  53. package/dist/types/modeler/parser.d.ts.map +1 -1
  54. package/dist/types/modeler/solvers/glpk/solver.d.ts.map +1 -1
  55. package/dist/types/modeler/solvers/highs/browser-runtime.d.ts +2 -1
  56. package/dist/types/modeler/solvers/highs/browser-runtime.d.ts.map +1 -1
  57. package/dist/types/modeler/solvers/highs/driver.d.ts +232 -0
  58. package/dist/types/modeler/solvers/highs/driver.d.ts.map +1 -0
  59. package/dist/types/modeler/solvers/highs/index.d.ts +1 -0
  60. package/dist/types/modeler/solvers/highs/index.d.ts.map +1 -1
  61. package/dist/types/modeler/solvers/highs/loader.d.ts +4 -4
  62. package/dist/types/modeler/solvers/highs/loader.d.ts.map +1 -1
  63. package/dist/types/modeler/solvers/highs/node-runtime.d.ts +2 -1
  64. package/dist/types/modeler/solvers/highs/node-runtime.d.ts.map +1 -1
  65. package/dist/types/modeler/solvers/highs/solver.d.ts +1 -1
  66. package/dist/types/modeler/solvers/highs/solver.d.ts.map +1 -1
  67. package/dist/types/modeler/solvers/highs/worker-client.d.ts.map +1 -1
  68. package/dist/types/modeler/solvers/types.d.ts +4 -0
  69. package/dist/types/modeler/solvers/types.d.ts.map +1 -1
  70. package/dist/types/modeler/types.d.ts +27 -0
  71. package/dist/types/modeler/types.d.ts.map +1 -1
  72. package/docs/examples/diet-sensitivity.md +15 -14
  73. package/docs/examples/ecommerce-coupons.md +15 -22
  74. package/docs/examples/index.md +18 -12
  75. package/docs/examples/knapsack.md +8 -7
  76. package/docs/examples/portfolio-qp.md +11 -4
  77. package/docs/examples/production.md +4 -4
  78. package/docs/examples/shift-scheduling.md +20 -7
  79. package/docs/examples/smt-security.md +16 -4
  80. package/docs/examples/transportation.md +46 -19
  81. package/docs/examples/traveling-salesperson.md +157 -0
  82. package/docs/examples/unsat-core.md +15 -5
  83. package/docs/examples/vehicle-routing.md +208 -0
  84. package/docs/getting-started/ai-agents.md +33 -18
  85. package/docs/getting-started/cross-origin.md +56 -28
  86. package/docs/getting-started/index.md +40 -69
  87. package/docs/getting-started/runtimes.md +66 -61
  88. package/docs/guide/constraints.md +47 -41
  89. package/docs/guide/decomposition.md +267 -0
  90. package/docs/guide/expressions.md +90 -39
  91. package/docs/guide/formulation-tips.md +212 -0
  92. package/docs/guide/objectives.md +7 -4
  93. package/docs/guide/unsat-core.md +28 -9
  94. package/docs/guide/variables.md +33 -38
  95. package/docs/ja/getting-started/ai-agents.md +32 -18
  96. package/docs/solvers/custom-solver.md +1 -1
  97. package/docs/solvers/highs.md +48 -5
  98. package/docs/solvers/index.md +4 -4
  99. package/docs/solvers/z3.md +3 -0
  100. package/docs/zh/getting-started/ai-agents.md +32 -18
  101. package/llms-full.txt +1536 -478
  102. package/llms.txt +28 -22
  103. package/package.json +19 -16
  104. package/skills/washi/SKILL.md +21 -17
  105. package/dist/browser/browser-runtime-D13gHOT5.js +0 -4189
  106. package/dist/browser/loader-CtaDHef_.js +0 -273
  107. package/dist/lib/loader-DmNC_UNk.js +0 -279
@@ -3,7 +3,10 @@
3
3
  > **Scope Note**: This document is the **consumer modeling quick-reference** for AI coding agents and developers building optimization or satisfiability models with `@ohtaman/washi`.
4
4
  > - **Portable Agent Skill (`SKILL.md`)**: For an actionable end-to-end modeling, `toLpString()` inspection, `validateSolution()` verification, and Z3 `unsatCore` conflict-diagnosis workflow skill (compatible with Claude Code, Antigravity, Cursor, Codex, and other `SKILL.md`-aware agents), use [`skills/washi/SKILL.md`](https://github.com/ohtaman/washi/blob/main/skills/washi/SKILL.md) (also shipped in the npm package at `node_modules/@ohtaman/washi/skills/washi/SKILL.md`):
5
5
  > ```bash
6
- > # Link or copy the packaged skill into your project's agent skills directory
6
+ > # Install directly via the skills CLI (skills.sh / vercel-labs/skills)
7
+ > npx skills add ohtaman/washi
8
+ >
9
+ > # Or link/copy the packaged skill into your project's agent skills directory
7
10
  > mkdir -p .agents/skills/washi
8
11
  > cp node_modules/@ohtaman/washi/skills/washi/SKILL.md .agents/skills/washi/SKILL.md
9
12
  > ```
@@ -54,7 +57,7 @@ import "@ohtaman/washi/solvers/glpk";
54
57
  1. **Bundled WASM by default (no implicit CDN fallback)**:
55
58
  - In Node.js and modern bundlers, WebAssembly assets resolve relative to the module automatically.
56
59
  - In browser deployments, deploy the full `dist/browser/` directory (JS, Worker, and `.wasm` files) together. If a local WASM URL is missing or returns an HTML SPA fallback, initialization fails immediately and reports the attempted URL—it **never** silently switches to a public CDN.
57
- - In browser runtimes, to load `highs.wasm` from a custom location, call `setHighsWasmUrl("https://.../highs.wasm")` or pass `await m.solve({ wasmUrl: "https://.../highs.wasm" })` (ignored in Node.js, which always loads the local Node `highs` package runtime).
60
+ - In browser runtimes, to load `highs.wasm` from a custom location, call `setHighsWasmUrl("https://.../highs.wasm")` or pass `await m.solve({ wasmUrl: "https://.../highs.wasm" })` (ignored in Node.js, which always loads WaSHI's bundled local `highs.wasm` runtime).
58
61
  2. **Z3 requires a Secure Context, Cross-Origin Isolation, and CORS in browsers**:
59
62
  - Z3 WebAssembly uses `SharedArrayBuffer` multithreading. Browser pages running Z3 must run in a secure context (`window.isSecureContext === true`, i.e., HTTPS or `localhost`) and be cross-origin isolated (`window.crossOriginIsolated === true`) by serving:
60
63
  - `Cross-Origin-Opener-Policy: same-origin` (on the document)
@@ -63,7 +66,7 @@ import "@ohtaman/washi/solvers/glpk";
63
66
  - Importing `@ohtaman/washi` never auto-injects a Service Worker. When a secure context or cross-origin isolation is absent in a browser, Z3 rejects immediately with a descriptive error.
64
67
  3. **Web Workers & Cancellation (`AbortSignal`)**:
65
68
  - Pre-solve cancellation (`signal.aborted` before execution starts) is supported across all solvers.
66
- - In browsers where `Worker` is available, HiGHS runs in a dedicated Web Worker by default (`useWorker: true`). Passing `{ signal }` to `m.solve({ signal })` preemptively terminates an in-flight Worker solve (or cancels queued Worker requests).
69
+ - In browsers where `Worker` is available on a non-opaque effective origin (`window.origin !== "null"`), HiGHS runs in a dedicated Web Worker by default (`useWorker: true`). On opaque effective origins (`window.origin === "null"`, such as `file:` URLs or `sandbox="allow-scripts"` `<iframe>` embeds without `allow-same-origin`), HiGHS defaults to main-thread execution (`useWorker: false`). Passing `{ signal }` to `m.solve({ signal })` preemptively terminates an in-flight Worker solve (or cancels queued Worker requests).
67
70
  - On main-thread HiGHS (`useWorker: false` or in Node.js), cooperative mid-solve cancellation is supported when `abort()` is triggered synchronously inside an `onProgress` callback at a solver interrupt checkpoint; arbitrary asynchronous aborts from outside cannot preempt a blocked synchronous WASM thread until a callback checkpoint or completion. Single-threaded WASM solvers without interrupt hooks (GLPK, Z3) check `signal` before execution starts.
68
71
 
69
72
  ---
@@ -79,6 +82,7 @@ const m = new Model("ExampleModel");
79
82
 
80
83
  // Scalar variables
81
84
  const x = m.continuous("x", { lb: 0, ub: 100 });
85
+ const y = m.continuous("y", { lb: 0, ub: 100 });
82
86
  const n = m.integer("n", { lb: 0, ub: 10 });
83
87
  const b = m.binary("b");
84
88
  const flag = m.bool("flag"); // Propositional boolean (for Z3 / logical constraints)
@@ -100,12 +104,12 @@ const mixed = m.binary(["A", "B"] as const, 3); // Record<"A" | "B", Var[]>
100
104
  ### Step 2: Build Expressions (`expr`, `sum`, `dot`)
101
105
 
102
106
  - **Unified `expr` template tag**:
103
- - Linear expression (`LinExpr`): `` expr`30 * ${x} + 50 * ${n} - 10` ``
104
- - Quadratic expression (`QuadExpr`): `` expr`${x}^2 + 2 * ${x} * ${n} + ${n}^2` `` (variable-by-variable products and squares are supported for convex QP objectives).
105
- - Linear constraint (`Constraint`): `` expr`${x} + 2 * ${n} <= 40` `` (supports `<=`, `>=`, `==`, and `=`).
106
- - **Aggregations**:
107
- - `sum(iterable, mapper?)` sums an `Iterable` of `Var` / `LinExpr` / `number` (e.g., `Var[]`, `grid.flat()`, or `Object.values(record)`).
108
- - `dot(a, b)` computes the inner product of matching 1D arrays, 2D arrays (`number[][]` / `Var[][]`), plain records (`Record<string, ...>`), or mixed Array/Record containers in either operand order.
107
+ - Linear expression (`LinExpr`): `` expr`30 * ${x} + 50 * ${n} - 10` ``, `` expr`dot(${profit}, ${byItem}) - 20 * ${b}` ``, `` expr`sum((${price} - ${cost}) * ${byItem})` ``
108
+ - Quadratic expression (`QuadExpr`): `` expr`${x}^2 + 2 * ${x} * ${y} + ${y}^2` ``, `` expr`sum((${vec} - ${target})^2)` `` (supported for convex QP objectives on purely continuous models using `m.continuous`; built-in HiGHS does not support MIQP models containing integer, binary, or boolean variables).
109
+ - Linear constraint (`Constraint`): `` expr`${x} + 2 * ${n} <= 40` ``, `` expr`sum(${byItem}) <= 100` `` (supports `<=`, `>=`, `==`, and `=`).
110
+ - **Aggregations & Element-Wise Container Arithmetic**:
111
+ - `sum(containerOrItems, mapper?)` (usable inside `` expr`sum(...)` `` or standalone `sum(...)`) sums a 1D/2D `Array`, `Iterable`, or plain `Record` of `Var` / `LinExpr` / `number` (without needing `Object.values(record)`), or maps over items/counts via `sum(items, (item, i) => ...)`. Inside `` expr`sum(...)` ``, element-wise container arithmetic (`+`, `-`, `*`, `/`, `^2`) is also supported.
112
+ - `dot(a, b)` (usable inside `` expr`dot(${a}, ${b})` `` or standalone `dot(a, b)`) computes the inner product of matching 1D arrays, 2D arrays (`number[][]` / `Var[][]`, computing the Frobenius inner product $\sum_{i,j} A_{i,j} B_{i,j}$), plain records (`Record<string, ...>`), or mixed Array/Record containers in either operand order.
109
113
  - **Fluent methods**:
110
114
  - Arithmetic (on `LinExpr` and `QuadExpr`; `Var` supports `.add()`, `.sub()`, `.mul()`, `.neg()`): `.add(other)`, `.sub(other)`, `.mul(other)`, `.div(scalar)`, `.neg()`
111
115
  - Comparisons (on `Var` and `LinExpr`): `.le(rhs, name?)`, `.ge(rhs, name?)`, `.eq(rhs, name?)`
@@ -119,8 +123,8 @@ Constructing an `expr` or `.le()` object does **not** attach it to the model unt
119
123
  // Set objective using maximize() or minimize()
120
124
  m.objective(maximize(expr`30 * ${x} + 50 * ${n}`));
121
125
 
122
- // Register constraints on the model
123
- m.constraint(expr`${x} + 2 * ${n} <= 40`, "resource_limit");
126
+ // Register constraints on the model (returns the registered Constraint / BoolExpr or array)
127
+ const limit = m.constraint(expr`${x} + 2 * ${n} <= 40`, "resource_limit");
124
128
 
125
129
  // Indicator constraint (MIP Big-M reformulation: b === 1 => x <= 50)
126
130
  m.indicator(b, expr`${x} <= 50`, { activeValue: 1, name: "cap_when_b" });
@@ -132,9 +136,10 @@ m.indicator(b, expr`${x} <= 50`, { activeValue: 1, name: "cap_when_b" });
132
136
  - Returns `SolverStatus` (`"Optimal"`, `"Feasible"`, `"Infeasible"`, `"Unbounded"`, `"TimeLimit"`, `"IterationLimit"`, `"Error"`, `"Unknown"`, `"NotSolved"`, `"Sat"`, or `"Unsat"`). Built-in optimization solvers return the optimization statuses (`"Optimal"`, `"Feasible"`, `"Infeasible"`, `"Unbounded"`, `"TimeLimit"`, `"IterationLimit"`, `"Error"`, `"Unknown"`, `"NotSolved"`), where a time-, iteration-, or solution-limited run (or an interrupted solve) that found a valid primal incumbent returns `"Feasible"`; `"Sat"` and `"Unsat"` are included in `SolverStatus` for satisfiability/custom solver adapters (such as `MockSolver`).
133
137
  - Read values via `x.value`, `expr`...`.value`, `m.getValues(x)`, `m.getValues(container)`, `m.objectiveValue`, `constr.dual`, `constr.slack`, and `x.reducedCost`.
134
138
  - Call `m.validateSolution()` after solving to independently verify variable finiteness, variable bounds (`lb`/`ub`), integer/binary/boolean domains, linear constraint residuals, and logical constraints (`SolutionValidationReport`: `{ valid, complete, maxViolation, violations, ... }`). Note that `validateSolution()` checks primal feasibility of variables and constraints; it does not recompute or compare `m.objectiveValue`.
135
- - **Satisfiability & Unsat Core (`await m.check(options?)`)**:
136
- - Returns `CheckResult` (`{ status: "Sat" | "Unsat" | "Unknown", values: Record<string, number | boolean>, conflicts?: readonly string[], unsatCore?: readonly string[], runTime?: number }`), where `conflicts` and `unsatCore` are optional and populated only when an unsat-core-capable solver (such as Z3) returns `"Unsat"`.
137
- - For models containing only linear arithmetic constraints, `m.check()` works with any solver (including the default HiGHS and GLPK) by falling back to a feasibility `solve()`. Logical constraints (`BoolVar`, `and`, `or`, `implies`, etc.) and unsatisfiable-core extraction (`result.conflicts` / `result.unsatCore`) require an SMT-capable solver (`{ solver: "z3" }`). Because Z3 asserts variable bounds (`lb`/`ub`) and binary `{0, 1}` domains directly while tracking registered constraints, `conflicts` / `unsatCore` reports a subset of registered constraint names that conflicts *together with the model's fixed variable bounds/domains* (and is not guaranteed minimal).
139
+ - **Satisfiability & Conflict Diagnosis (`await m.check(options?)`)**:
140
+ - Returns `CheckResult` (`{ status: "Sat" | "Unsat" | "Unknown", values: Record<string, number | boolean>, conflicts?: readonly string[], unsatCore?: readonly string[], runTime?: number }`), where `conflicts` is populated when the model is proven `"Unsat"`.
141
+ - For models containing linear arithmetic constraints or logical constraints (`BoolVar`, `and`, `or`, `implies`, `xor`), `m.check()` works with any solver (including the default HiGHS and GLPK via single-solve elastic slack relaxation, automatically linearizing logical constraints into 0-1 MIP formulation) and diagnoses conflicting constraints as `conflicts` (leaving `unsatCore: undefined` for semantic rigor).
142
+ - Exact SMT unsatisfiable-core extraction (`result.unsatCore` and `result.conflicts`) is provided when using an SMT-capable solver (`{ solver: "z3" }`). Because Z3 asserts variable bounds (`lb`/`ub`) and binary `{0, 1}` domains directly while tracking registered constraints, `unsatCore` reports a subset of registered constraint names that conflicts *together with the model's fixed variable bounds/domains* (and is not guaranteed minimal).
138
143
  - **No concurrent mutation during async solve/check**:
139
144
  - Mutating variables, constraints, or the objective while `await m.solve()` or `await m.check()` is pending invalidates the in-flight execution and rejects the stale promise.
140
145
 
@@ -157,10 +162,13 @@ export async function runMipExample() {
157
162
  const qty = m.integer(products, { lb: 0, ub: 100 });
158
163
  const setup = m.binary("setup");
159
164
 
160
- m.objective(maximize(expr`${dot(profit, qty)} - 20 * ${setup}`));
161
- m.constraint(expr`${dot(woodPerUnit, qty)} <= 40`, "wood_cap");
162
- m.constraint(expr`${dot(laborPerUnit, qty)} <= 50`, "labor_cap");
163
- m.constraint(expr`${sum(Object.values(qty))} <= 30 * ${setup}`, "require_setup");
165
+ m.objective(maximize(expr`dot(${profit}, ${qty}) - 20 * ${setup}`));
166
+ m.constraint(expr`dot(${woodPerUnit}, ${qty}) <= 40`, "wood_cap");
167
+ m.constraint(expr`dot(${laborPerUnit}, ${qty}) <= 50`, "labor_cap");
168
+ m.indicator(setup, expr`sum(${qty}) <= 0`, {
169
+ activeValue: 0,
170
+ name: "require_setup",
171
+ });
164
172
 
165
173
  const status = await m.solve();
166
174
  const report = m.validateSolution();
@@ -265,3 +273,10 @@ export function verifyNegativePatterns() {
265
273
  }
266
274
  ```
267
275
 
276
+ ### Practical OR Modeling Guardrails
277
+
278
+ - **Prefer inequalities (`>=` / `<=`) over exact equalities (`==`)** for demand coverage and resource limits when objective costs are positive; reserve `==` for physical conservation laws (e.g., flow balance, portfolio weights summing to `1`).
279
+ - **Always provide finite `lb` and `ub` bounds** on variables referenced inside `m.indicator(...)` and omit `M` so WaSHI automatically derives the tightest valid Big-M instead of falling back to `1e6`.
280
+ - **Use soft constraints (elastic slack variables)** for non-rigid business targets (e.g., preferred staffing, safety stock, day-off requests) by adding a non-negative slack variable (`lb: 0`) with a tiered objective penalty, reserving hard constraints for physical or legal invariants.
281
+
282
+
@@ -1,47 +1,70 @@
1
1
  # Cross-Origin Isolation (Z3 & Browsers)
2
2
 
3
- When running the **Z3** solver engine inside a web browser, special HTTP headers are required due to browser security models.
3
+ When running the **Z3** solver inside a web browser, Cross-Origin Isolation HTTP headers are required so the browser enables `SharedArrayBuffer`.
4
+
5
+ > [!TIP]
6
+ > **Not needed for HiGHS, GLPK, or Node.js**: The built-in **HiGHS** solver and **GLPK** plugin run in standard browser contexts without Cross-Origin Isolation or `SharedArrayBuffer`. Running Z3 in **Node.js** also requires no HTTP headers. You only need this page when using `@ohtaman/washi/solvers/z3` in the browser.
4
7
 
5
8
  ---
6
9
 
7
10
  ## Why is Cross-Origin Isolation Required?
8
11
 
9
- The Z3 WebAssembly engine utilizes multithreading backed by `SharedArrayBuffer` and Web Workers.
10
-
11
- In modern browsers (Chrome, Firefox, Safari, Edge), `SharedArrayBuffer` is disabled by default to mitigate Spectre-style timing side-channel attacks. To re-enable it, the hosting page must be **Cross-Origin Isolated**.
12
+ Z3's WebAssembly build uses multithreading (pthreads) backed by `SharedArrayBuffer` and Web Workers. Modern browsers (Chrome, Firefox, Safari, Edge) disable `SharedArrayBuffer` by default to mitigate Spectre-style side-channel attacks unless the top-level document opts into **Cross-Origin Isolation** (`window.crossOriginIsolated === true`).
12
13
 
13
14
  ---
14
15
 
15
16
  ## Required HTTP Response Headers
16
17
 
17
- To establish cross-origin isolation, your web server must serve the top-level HTML document with the following HTTP headers:
18
+ Serve the top-level HTML document with:
18
19
 
19
20
  ```http
20
21
  Cross-Origin-Opener-Policy: same-origin
21
22
  Cross-Origin-Embedder-Policy: require-corp
22
23
  ```
23
24
 
24
- In addition, any cross-origin assets (e.g. scripts or WASM files loaded from external domains) must send:
25
+ If your page loads cross-origin subresources (such as scripts, images, or WASM files from another domain), those responses must also include `Cross-Origin-Resource-Policy: cross-origin` (or CORS headers).
25
26
 
26
- ```http
27
- Cross-Origin-Resource-Policy: cross-origin
28
- ```
27
+ > [!NOTE]
28
+ > Chromium and Firefox also support `Cross-Origin-Embedder-Policy: credentialless`, which relaxes subresource CORP requirements, **but Safari (WebKit) does not support `credentialless`**. Use `require-corp` if you support Safari / iOS.
29
29
 
30
30
  ---
31
31
 
32
- ## Solutions by Hosting Environment
32
+ ## Setup by Environment
33
33
 
34
- ### 1. Custom Node.js / Express Server
34
+ ### 1. Vite (`vite.config.ts`)
35
35
 
36
- ```javascript
37
- app.use((req, res, next) => {
38
- res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
39
- res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
40
- next();
36
+ Configure both the local dev server (`vite`) and production preview server (`vite preview`):
37
+
38
+ ```typescript
39
+ import { defineConfig } from "vite";
40
+
41
+ export default defineConfig({
42
+ server: {
43
+ headers: {
44
+ "Cross-Origin-Opener-Policy": "same-origin",
45
+ "Cross-Origin-Embedder-Policy": "require-corp",
46
+ },
47
+ },
48
+ preview: {
49
+ headers: {
50
+ "Cross-Origin-Opener-Policy": "same-origin",
51
+ "Cross-Origin-Embedder-Policy": "require-corp",
52
+ },
53
+ },
41
54
  });
42
55
  ```
43
56
 
44
- ### 2. Next.js (`next.config.js`)
57
+ ### 2. Cloudflare Pages / Netlify (`public/_headers`)
58
+
59
+ Place a `_headers` file in `public/` so it is copied to the root of your build output:
60
+
61
+ ```text
62
+ /*
63
+ Cross-Origin-Opener-Policy: same-origin
64
+ Cross-Origin-Embedder-Policy: require-corp
65
+ ```
66
+
67
+ ### 3. Next.js (`next.config.js`)
45
68
 
46
69
  ```javascript
47
70
  /** @type {import('next').NextConfig} */
@@ -62,26 +85,31 @@ const nextConfig = {
62
85
  module.exports = nextConfig;
63
86
  ```
64
87
 
65
- ### 3. Static Hosting without Header Control (e.g., GitHub Pages)
88
+ ### 4. Custom Node.js / Express Server
89
+
90
+ ```javascript
91
+ app.use((req, res, next) => {
92
+ res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
93
+ res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
94
+ next();
95
+ });
96
+ ```
66
97
 
67
- Platforms like GitHub Pages or preview environments do not allow customizing response headers directly.
98
+ ### 5. Static Hosting without Header Control (e.g., GitHub Pages)
68
99
 
69
- For these environments, you can use **`coi-serviceworker`** (a lightweight Service Worker that injects isolation headers on the client side):
100
+ Platforms like GitHub Pages do not allow customizing HTTP response headers directly. You can use **`coi-serviceworker`** to inject the isolation headers via a client-side Service Worker:
70
101
 
71
- 1. Place `coi-serviceworker.js` in your website root.
72
- 2. In your HTML `<head>`, include:
102
+ 1. Place `coi-serviceworker.js` at your site root.
103
+ 2. Load it in your HTML `<head>`:
73
104
 
74
105
  ```html
75
106
  <script src="/coi-serviceworker.js"></script>
76
107
  ```
77
108
 
78
- When a user visits for the first time, the service worker registers and reloads the page once to establish `window.crossOriginIsolated === true`.
109
+ On the first visit, the Service Worker registers and reloads the page once to establish `window.crossOriginIsolated === true`.
79
110
 
80
111
  ---
81
112
 
82
- ## Graceful Degradation in WaSHI
83
-
84
- WaSHI's Z3 solver adapter automatically inspects the environment at runtime. If Z3 is invoked in a browser lacking cross-origin isolation, it immediately rejects with a clear, informative error message explaining that COOP/COEP headers or a service worker are required, rather than hanging silently.
113
+ ## Runtime Check in WaSHI
85
114
 
86
- > [!TIP]
87
- > The default **HiGHS** solver engine does not require cross-origin isolation or `SharedArrayBuffer`. It works out of the box in standard browser contexts.
115
+ WaSHI's Z3 adapter checks `crossOriginIsolated` before initializing Z3 in the browser. If Cross-Origin Isolation is missing, `solve()` / `check()` immediately rejects with an actionable error explaining that COOP/COEP headers or a Service Worker are required, rather than hanging silently.
@@ -1,19 +1,35 @@
1
1
  # Introduction & Installation
2
2
 
3
- Welcome to **WaSHI** (WebAssembly-backed, Solver-independent, High-level modeling Interface).
3
+ **WaSHI** (WebAssembly-backed, Solver-independent, High-level modeling Interface) brings the algebraic modeling style of Python's **PuLP** to the TypeScript and JavaScript ecosystem. Write expressions naturally with tagged templates (`expr`) and solve mathematical optimization problems directly in **Node.js and the browser** using WebAssembly solvers (**HiGHS**, **Z3**, and **GLPK**).
4
4
 
5
- WaSHI brings the algebraic modeling style of Python's **PuLP** and Julia's **JuMP** to the TypeScript and JavaScript ecosystem. You can formulate mathematical optimization problems using algebraic expressions and solve them locally using WebAssembly solvers (**HiGHS**, **Z3**, and **GLPK**)—without a separate Python service or native C/C++ build steps.
5
+ ```typescript
6
+ import { Model, expr, maximize } from "@ohtaman/washi";
7
+
8
+ const m = new Model("production");
6
9
 
7
- ### Why TypeScript & WebAssembly?
10
+ // 1. Define decision variables with realistic finite bounds (lb / ub)
11
+ const desks = m.integer("desks", { lb: 0, ub: 100 });
12
+ const chairs = m.integer("chairs", { lb: 0, ub: 100 });
8
13
 
9
- - **Removing the deployment bottleneck**: In traditional optimization workflows (e.g., Python with PuLP or Pyomo), turning a model into an interactive application requires provisioning a backend service and managing native C/C++ solver binaries. As AI-assisted development accelerates UI and application prototyping, backend infrastructure often becomes the primary bottleneck to shipping. Running both the modeler and WebAssembly solvers on standard Web technologies allows an optimization tool to be deployed as static assets (e.g., on GitHub Pages or Cloudflare Pages, serving the generated JS, Worker, and WASM files; with COOP/COEP headers when using Z3)—and makes it straightforward to pair mathematical optimization with in-browser LLMs (e.g., WebGPU / WebLLM) entirely on the client.
10
- - **Algebraic syntax without operator overloading (`expr`)**: Because JavaScript lacks operator overloading, libraries usually trade off between verbose method chaining (`x.mul(30).add(y.mul(50))`) and untyped strings (`"30 * x + 50 * y"`). WaSHI uses tagged template literals (`` expr`30 * ${x} + 50 * ${y} <= 40` ``) similar to parameterized SQL queries—parsing infix operators while binding `${x}` directly as typed TypeScript object references for static type checking, IDE symbol renaming, and multi-dimensional indexing (`${flow[i][j]}`).
14
+ // 2. Set the objective function and constraints
15
+ m.objective(maximize(expr`30 * ${desks} + 50 * ${chairs}`));
16
+ m.constraint(expr`${desks} + 2 * ${chairs} <= 40`, "wood_capacity");
17
+ m.constraint(expr`2 * ${desks} + ${chairs} <= 50`, "labor_capacity");
18
+
19
+ // 3. Solve (built-in HiGHS WebAssembly) and validate the solution
20
+ const status = await m.solve();
21
+ if (m.hasPrimalSolution) {
22
+ const report = m.validateSolution();
23
+ console.log(status, report.valid); // "Optimal", true
24
+ console.log(`Profit: ${m.objectiveValue} (desks=${desks.value}, chairs=${chairs.value})`); // 1100 (desks=20, chairs=10)
25
+ }
26
+ ```
11
27
 
12
28
  ---
13
29
 
14
30
  ## 📦 Installation
15
31
 
16
- Install WaSHI using your preferred package manager:
32
+ The default **HiGHS** solver (LP / MIP / Convex QP) is bundled and works out of the box:
17
33
 
18
34
  ::: code-group
19
35
 
@@ -35,82 +51,37 @@ bun add @ohtaman/washi
35
51
 
36
52
  :::
37
53
 
38
- ### Optional Solver Dependencies
39
-
40
- WaSHI ships with **HiGHS** built-in with zero configuration. If you need SMT logic / Unsat Core diagnosis via **Z3**, or compatibility with **GLPK**, install their respective peer dependencies:
54
+ Install optional peer dependencies only if you need **Z3** (SMT logic & Unsat Core diagnosis) or **GLPK**:
41
55
 
42
56
  ```bash
43
- # For SMT logic and Unsat Core conflict diagnosis
44
- npm install z3-solver
45
-
46
- # For GLPK solver (GPL-3.0)
47
- npm install glpk.js
57
+ npm install z3-solver # For SMT logic and Unsat Core conflict diagnosis
58
+ npm install glpk.js # For GLPK solver (GPL-3.0)
48
59
  ```
49
60
 
50
61
  ---
51
62
 
52
- ## 🚀 Quickstart Walkthrough
53
-
54
- Here is a step-by-step example of solving a production allocation problem.
55
-
56
- ### 1. Import the Core Classes
57
-
58
- ```typescript
59
- import { Model, expr, maximize } from "@ohtaman/washi";
60
- ```
61
-
62
- ### 2. Instantiate a Model
63
-
64
- ```typescript
65
- const model = new Model("WoodworkingProfit");
66
- ```
67
-
68
- ### 3. Add Decision Variables
63
+ ## 💡 Why TypeScript & WebAssembly?
69
64
 
70
- Variables represent the unknowns you want to determine. Factory methods (`m.continuous`, `m.integer`, `m.binary`) clearly define their domain:
65
+ - **Zero native setup across Node.js and the browser**: No platform-specific C/C++ builds or separate Python runtimes to manage. A single `npm install` runs the same modeling code across **Node.js APIs, CLIs, and scripts** as well as in the **browser**.
66
+ - **Lightweight embedding with standard Web APIs & UIs**: Go beyond local analysis scripts by shipping **interactive Web tools, mini-games, or Chrome extensions** that anyone can open via a URL with zero backend hosting cost. You can wire mathematical optimization directly into drag-and-drop UIs or **standard browser capabilities**—such as screen/camera capture and speech recognition (Web Speech API)—with no network round-trips or external data transfer.
67
+ - **End-to-end type safety from app data to model**: Prevent common indexing and dictionary-key mistakes with TypeScript's type system. Typed variable dictionaries (`Record<K, Var>`) and variable-reference interpolation (`` expr`30 * ${qty.desk} <= 40` ``) connect directly to your application schemas, preserving compile-time variable/key checks, IDE autocompletion, and symbol renaming.
68
+ - **Strong synergy with AI development & LLM workflows**: Keeping your application and optimization model in a **single TypeScript codebase** lets you validate everything in one test runner—without crossing a Python service boundary—and deploy easily as static assets or a single service. That tight test-and-deploy loop fits AI-assisted coding naturally, while at runtime you can pair flexible LLM extraction with exact solver feasibility and conflict diagnosis (`unsatCore`) in the same runtime.
71
69
 
72
- ```typescript
73
- const x = model.integer("x", { lb: 0 });
74
- const y = model.integer("y", { lb: 0 });
75
- ```
76
-
77
- ### 4. Set the Objective Function
78
-
79
- Use the `expr` tagged template literal along with `maximize(...)` or `minimize(...)` to define the goal:
80
-
81
- ```typescript
82
- // Maximize 30*x + 50*y
83
- model.objective(maximize(expr`30 * ${x} + 50 * ${y}`));
84
- ```
85
-
86
- ### 5. Add Constraints
87
-
88
- Use `model.constraint()` with the `expr` tagged template to formulate equality and inequality constraints:
89
-
90
- ```typescript
91
- model.constraint(expr`${x} + 2 * ${y} <= 40`, "wood_capacity");
92
- model.constraint(expr`2 * ${x} + ${y} <= 50`, "labor_capacity");
93
- ```
94
-
95
- ### 6. Solve and Extract Results
70
+ ---
96
71
 
97
- Solve the model asynchronously via WebAssembly (HiGHS by default):
72
+ ## 🔍 Core Workflow Essentials
98
73
 
99
- ```typescript
100
- const status = await model.solve();
74
+ Three practical habits cover the essentials shown in the snippet above:
101
75
 
102
- console.log("Status:", status); // "Optimal"
103
- console.log("Optimal Objective Value:", model.objectiveValue); // 1100
104
- console.log(`Solution: x = ${x.value}, y = ${y.value}`); // x = 20, y = 10
105
- ```
76
+ 1. **Set realistic finite bounds (`lb`, `ub`)**: When defining variables with `m.continuous`, `m.integer`, or `m.binary`, finite bounds strengthen MIP presolve and enable automatic tight Big-M derivation for logical constraints.
77
+ 2. **Formulate objectives and constraints with `expr`**: Use `` m.objective(maximize(expr`...`)) `` and `` m.constraint(expr`... <= ...`, "name") ``. Naming constraints lets you pinpoint conflicting rules immediately if a model becomes infeasible.
78
+ 3. **Check `hasPrimalSolution` and call `validateSolution()`**: After `await m.solve()`, check `m.hasPrimalSolution` before reading `.value` on variables, and call `m.validateSolution()` to independently verify that bounds, integrality, and constraints hold within tolerance.
106
79
 
107
80
  ---
108
81
 
109
82
  ## 🧭 Next Steps
110
83
 
111
- - Give AI coding agents a concise API & deployment reference with the [AI Coding Agent Quick-Reference Guide](./ai-agents.md).
112
- - Learn how to run WaSHI across different environments in [Runtime Environments](./runtimes.md).
113
- - Understand how to formulate complex expressions in the [Modeling Guide](../guide/expressions.md).
114
- - Explore built-in and pluggable solvers in the [Solvers Matrix](../solvers/).
115
- - Browse 9 runnable optimization and SMT recipes in [Examples & API Finder](../examples/).
116
-
84
+ - **[Modeling Guide](../guide/variables.md)**: Array/dictionary variables, aggregations (`sum`, `dot`), logical constraints, and Unsat Core diagnosis
85
+ - **[Examples & API Finder](../examples/)**: 11 runnable optimization and SMT recipes—from shift scheduling and portfolio QP to vehicle routing
86
+ - **[Solvers Overview](../solvers/)**: Compare built-in HiGHS with optional Z3 and GLPK adapters
87
+ - **[Runtime Environments](./runtimes.md)**: Setup details for Node.js, Vite / Next.js bundlers, CDNs, and static hosting
@@ -1,12 +1,14 @@
1
1
  # Runtime Environments & Deployment
2
2
 
3
- WaSHI runs across JavaScript and TypeScript runtimes—from Node.js, Bun, and Deno to modern bundlers, vanilla browser `<script>` tags, and air-gapped intranets—without native C/C++ compilation.
3
+ WaSHI runs across JavaScript and TypeScript runtimes—from Node.js, Bun, and Deno to modern bundlers, static hosting, and vanilla browser `<script>` tags—without native C/C++ compilation.
4
4
 
5
5
  ---
6
6
 
7
- ## 🟢 Node.js
7
+ ## 🟢 Node.js, Bun & Deno
8
8
 
9
- WaSHI supports Node.js (v18+) out of the box with standard ECMAScript Modules (ESM). The built-in HiGHS WebAssembly binary is loaded directly from the installed package.
9
+ ### Node.js
10
+
11
+ WaSHI supports Node.js (v18+) out of the box with standard ECMAScript Modules (ESM). The built-in HiGHS WebAssembly binary is loaded directly from the installed package:
10
12
 
11
13
  ```typescript
12
14
  import { Model, expr, maximize } from "@ohtaman/washi";
@@ -19,8 +21,10 @@ m.objective(maximize(expr`2 * ${x} + 3 * ${y}`));
19
21
  m.constraint(expr`${x} + ${y} <= 15`, "capacity");
20
22
 
21
23
  await m.solve();
22
- console.log(`Status: ${m.status}, Objective: ${m.objectiveValue}`);
23
- console.log(`x = ${x.value}, y = ${y.value}`);
24
+ if (m.hasPrimalSolution) {
25
+ console.log(`Status: ${m.status}, Objective: ${m.objectiveValue}`);
26
+ console.log(`x = ${x.value}, y = ${y.value}`);
27
+ }
24
28
  ```
25
29
 
26
30
  Run directly with `tsx` or `node`:
@@ -29,11 +33,33 @@ Run directly with `tsx` or `node`:
29
33
  npx tsx app.ts
30
34
  ```
31
35
 
36
+ ### Bun & Deno
37
+
38
+ ```bash
39
+ # Bun
40
+ bun add @ohtaman/washi
41
+ bun run app.ts
42
+ ```
43
+
44
+ In Deno (v1.30+ with `npm:` specifiers):
45
+
46
+ ```typescript
47
+ import { Model, maximize } from "npm:@ohtaman/washi";
48
+
49
+ const m = new Model();
50
+ const x = m.continuous("x", { lb: 0, ub: 10 });
51
+ m.objective(maximize(x));
52
+ await m.solve();
53
+ if (m.hasPrimalSolution) {
54
+ console.log("x =", x.value);
55
+ }
56
+ ```
57
+
32
58
  ---
33
59
 
34
- ## 🌐 Bundlers (Vite, Webpack, Rollup, Next.js)
60
+ ## 🌐 Bundlers & Static Hosting (Vite, Next.js, Webpack, Rollup)
35
61
 
36
- ### Modern Bundlers (Vite / Rollup / Webpack)
62
+ ### Modern Bundlers & Static Deployment
37
63
 
38
64
  When importing `@ohtaman/washi` in a browser bundler, the `browser` export condition resolves automatically:
39
65
 
@@ -45,14 +71,16 @@ export async function runOptimization() {
45
71
  const x = m.continuous("x", { lb: 0, ub: 100 });
46
72
  m.objective(maximize(x));
47
73
  await m.solve();
48
- return x.value;
74
+ return m.hasPrimalSolution ? x.value : null;
49
75
  }
50
76
  ```
51
77
 
78
+ Because both the modeler and solver run on WebAssembly, the static output of `vite build` (or your bundler's production build) can be deployed directly to **GitHub Pages, Cloudflare Pages, Vercel, or Netlify** with no backend server required. (The default HiGHS and GLPK solvers require no special HTTP headers; if you use Z3 in the browser, see [Cross-Origin Isolation](./cross-origin.md).)
79
+
52
80
  #### Background Web Worker Execution (Default)
53
- In browsers, HiGHS automatically runs inside a dedicated **Web Worker** so long-running LP/MIP/QP solves never freeze the main UI thread, animations, or user input, while enabling real-time progress events (`onProgress`) and cancellation (`AbortSignal`).
81
+ In browsers, HiGHS automatically runs inside a dedicated **Web Worker** so LP/MIP/QP solves never freeze the main UI thread, animations, or user input, while enabling real-time progress events (`onProgress`) and cancellation (`AbortSignal`).
54
82
 
55
- If your environment prohibits Web Workers or you explicitly want synchronous main-thread execution, pass `useWorker: false`:
83
+ If your environment prohibits Web Workers or you explicitly want main-thread execution, pass `useWorker: false`:
56
84
 
57
85
  ```typescript
58
86
  await m.solve({ useWorker: false });
@@ -91,7 +119,9 @@ export default function OptimizerComponent() {
91
119
  const x = m.continuous("x", { lb: 0, ub: 50 });
92
120
  m.objective(maximize(x));
93
121
  await m.solve();
94
- setVal(x.value);
122
+ if (m.hasPrimalSolution) {
123
+ setVal(x.value);
124
+ }
95
125
  };
96
126
 
97
127
  return (
@@ -107,36 +137,36 @@ export default function OptimizerComponent() {
107
137
 
108
138
  ## 📦 Browser ESM (`<script type="module">`)
109
139
 
110
- You can import the pre-bundled browser ESM modules directly from a local static server (or from an npm CDN once a compatible release is published) without a build step:
140
+ Without a bundler, you can import the pre-built browser ESM module directly from your own static server or an npm CDN:
111
141
 
112
142
  ```html
113
143
  <script type="module">
114
- // Serve from your local static server / dist:
115
- import { Model, expr, maximize } from "./dist/browser/washi.js";
116
- // Or from an npm CDN once a compatible release is published:
117
- // import { Model, expr, maximize } from "https://cdn.jsdelivr.net/npm/@ohtaman/washi@<version>/dist/browser/washi.js";
144
+ // From an npm CDN (or "./dist/browser/washi.js" when self-hosted):
145
+ import { Model, expr, maximize } from "https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.6/dist/browser/washi.js";
118
146
  // Optional solver plugins:
119
- // import "./dist/browser/solvers/glpk.js";
120
- // import "./dist/browser/solvers/z3.js";
147
+ // import "https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.6/dist/browser/solvers/glpk.js";
148
+ // import "https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.6/dist/browser/solvers/z3.js";
121
149
 
122
150
  const m = new Model("BrowserESM");
123
- const x = m.integer("x", { lb: 0 });
124
- const y = m.integer("y", { lb: 0 });
151
+ const x = m.integer("x", { lb: 0, ub: 100 });
152
+ const y = m.integer("y", { lb: 0, ub: 100 });
125
153
 
126
154
  m.objective(maximize(expr`30 * ${x} + 50 * ${y}`));
127
155
  m.constraint(expr`${x} + 2 * ${y} <= 40`, "wood");
128
156
  m.constraint(expr`2 * ${x} + ${y} <= 50`, "labor");
129
157
 
130
158
  await m.solve();
131
- console.log("Optimal Objective:", m.objectiveValue);
159
+ if (m.hasPrimalSolution) {
160
+ console.log("Optimal Objective:", m.objectiveValue);
161
+ }
132
162
  </script>
133
163
  ```
134
164
 
135
165
  ---
136
166
 
137
- ## 🟨 Classic `<script>` Tags (IIFE / Standalone Build / CDN)
167
+ ## 🟨 Classic `<script>` Tags (IIFE / Standalone HTML / CDN)
138
168
 
139
- For static HTML pages, CodePen, or JSFiddle, load `washi.min.js` via a standard `<script>` tag. All exports are attached to the global `window.Washi` object:
169
+ For single-file HTML pages, CodePen, or JSFiddle, load `washi.min.js` via a standard `<script>` tag. All exports are attached to the global `window.Washi` object:
140
170
 
141
171
  ```html
142
172
  <!DOCTYPE html>
@@ -149,13 +179,11 @@ For static HTML pages, CodePen, or JSFiddle, load `washi.min.js` via a standard
149
179
  <h1>WaSHI Browser Optimization</h1>
150
180
  <pre id="output">Solving...</pre>
151
181
 
152
- <!-- Core WaSHI + HiGHS (MIT) from local static build / assets -->
153
- <script src="./dist/browser/washi.min.js"></script>
154
- <!-- Or via CDN once a compatible release is published: -->
155
- <!-- <script src="https://cdn.jsdelivr.net/npm/@ohtaman/washi@<version>/dist/browser/washi.min.js"></script> -->
182
+ <!-- Core WaSHI + HiGHS (MIT) via CDN (or "./dist/browser/washi.min.js" when self-hosted) -->
183
+ <script src="https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.6/dist/browser/washi.min.js"></script>
156
184
  <!-- Optional plugins (auto-register on window.Washi): -->
157
- <!-- <script src="./dist/browser/solvers/glpk.min.js"></script> -->
158
- <!-- <script src="./dist/browser/solvers/z3.min.js"></script> -->
185
+ <!-- <script src="https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.6/dist/browser/solvers/glpk.min.js"></script> -->
186
+ <!-- <script src="https://cdn.jsdelivr.net/npm/@ohtaman/washi@0.6/dist/browser/solvers/z3.min.js"></script> -->
159
187
 
160
188
  <script>
161
189
  (async () => {
@@ -170,10 +198,12 @@ For static HTML pages, CodePen, or JSFiddle, load `washi.min.js` via a standard
170
198
 
171
199
  await m.solve();
172
200
 
173
- document.getElementById("output").textContent =
174
- `Status: ${m.status}\n` +
175
- `Objective: ${m.objectiveValue}\n` +
176
- `x = ${x.value}, y = ${y.value}`;
201
+ if (m.hasPrimalSolution) {
202
+ document.getElementById("output").textContent =
203
+ `Status: ${m.status}\n` +
204
+ `Objective: ${m.objectiveValue}\n` +
205
+ `x = ${x.value}, y = ${y.value}`;
206
+ }
177
207
  })();
178
208
  </script>
179
209
  </body>
@@ -181,7 +211,7 @@ For static HTML pages, CodePen, or JSFiddle, load `washi.min.js` via a standard
181
211
  ```
182
212
 
183
213
  > [!NOTE]
184
- > When loading `washi.min.js` from a cross-origin CDN (such as jsDelivr or unpkg), browsers block direct `new Worker("https://cdn...")` construction due to the same-origin policy. WaSHI automatically bridges cross-origin workers via a lightweight `Blob` wrapper using `importScripts()`, so Web Worker execution works out of the box from CDNs.
214
+ > When loading from a cross-origin CDN (such as jsDelivr or unpkg), browsers block direct `new Worker("https://cdn...")` construction under the same-origin policy. On standard web origins (`https://` / `http://`), WaSHI automatically bridges cross-origin workers via a lightweight ES module `Blob` bootstrap (`import "https://..."`). On opaque effective origins (`window.origin === "null"`, such as local `file:` URLs or `sandbox="allow-scripts"` `<iframe>` embeds without `allow-same-origin`), WaSHI automatically runs HiGHS on the main thread when `useWorker` is omitted.
185
215
 
186
216
  ---
187
217
 
@@ -195,33 +225,8 @@ WaSHI never phones home or falls back to public CDNs. To self-host in an air-gap
195
225
  - Optional Z3: `solvers/z3.js` / `solvers/z3.min.js`, `solvers/z3-built.js`, `solvers/z3-built.wasm`
196
226
  2. **Content Security Policy (CSP) Headers**:
197
227
  - Allow WebAssembly compilation (`'wasm-unsafe-eval'` in `script-src`).
198
- - If using the default Web Worker or cross-origin Blob worker fallback, allow `worker-src 'self' blob:;`.
228
+ - If using the default Web Worker or cross-origin Blob worker bootstrap, allow `worker-src 'self' blob:;`.
199
229
  - If `blob:` workers are prohibited by policy, either serve `highs-worker.js` from `'self'` or run on the main thread with `await m.solve({ useWorker: false })`.
200
230
  3. **Cross-Origin Isolation (Z3 Only)**:
201
231
  - HiGHS and GLPK run in standard browser contexts without special headers.
202
- - Z3 WASM uses pthreads + `SharedArrayBuffer` and requires `Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp` (or `credentialless`). See the [Cross-Origin Isolation Guide](./cross-origin.md) for setup details.
203
-
204
- ---
205
-
206
- ## ⚡ Bun & Deno
207
-
208
- ### Bun
209
-
210
- ```bash
211
- bun add @ohtaman/washi
212
- bun run app.ts
213
- ```
214
-
215
- ### Deno
216
-
217
- In Deno (v1.30+ with `npm:` specifiers):
218
-
219
- ```typescript
220
- import { Model, maximize } from "npm:@ohtaman/washi";
221
-
222
- const m = new Model();
223
- const x = m.continuous("x", { lb: 0, ub: 10 });
224
- m.objective(maximize(x));
225
- await m.solve();
226
- console.log("x =", x.value);
227
- ```
232
+ - Z3 WASM uses pthreads + `SharedArrayBuffer` and requires `Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp` (`credentialless` works in Chromium/Firefox, whereas Safari requires `require-corp`). See the [Cross-Origin Isolation Guide](./cross-origin.md) for setup details.