├── .gitattributes
├── .github
├── dependabot.yml
└── workflows
│ └── build.yml
├── .gitignore
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── browser.js
├── browser.spec.js
├── node.js
├── node.spec.js
├── test.mzn
├── tests.cjs
└── worker.js
├── tsconfig.json
├── typedoc.json
└── types
└── index.d.ts
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 | - package-ecosystem: "npm"
8 | directory: "/"
9 | schedule:
10 | interval: "weekly"
11 | groups:
12 | production:
13 | dependency-type: "production"
14 | development:
15 | dependency-type: "development"
16 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build, test, release
2 |
3 | on:
4 | push:
5 | pull_request:
6 | workflow_dispatch:
7 | schedule:
8 | - cron: "0 0 * * *"
9 |
10 | env:
11 | STABLE: ${{ (github.ref_name == 'stable' || startsWith(github.ref, 'refs/tags/v')) && 'yes' || 'no' }}
12 |
13 | jobs:
14 | minizinc:
15 | name: Build wasm version of MiniZinc
16 | runs-on: ubuntu-latest
17 | container: emscripten/emsdk
18 | outputs:
19 | cache-key: ${{ steps.get-cache-key.outputs.key }}
20 | cache-hit: ${{ steps.cache.outputs.cache-hit }}
21 | steps:
22 | - name: Workaround for https://github.com/actions/runner/issues/2033
23 | run: |
24 | git config --global --add safe.directory $GITHUB_WORKSPACE
25 | - name: Checkout latest MiniZinc
26 | if: ${{ env.STABLE == 'yes' }}
27 | uses: actions/checkout@v4
28 | with:
29 | repository: minizinc/libminizinc
30 | ref: master
31 | - name: Checkout edge MiniZinc
32 | if: ${{ env.STABLE == 'no' }}
33 | uses: actions/checkout@v4
34 | with:
35 | repository: minizinc/libminizinc
36 | ref: develop
37 | - name: Download vendor
38 | run: ./download_vendor
39 | env:
40 | MZNARCH: wasm
41 | - name: Get MiniZinc cache key
42 | id: get-cache-key
43 | run: echo "key=minizinc-$(git rev-parse HEAD)-${{ hashFiles('vendor/version.json') }}" >> $GITHUB_OUTPUT
44 | - name: Cache MiniZinc Build
45 | id: cache
46 | uses: actions/cache@v4
47 | with:
48 | path: minizinc
49 | key: ${{ steps.get-cache-key.outputs.key }}
50 | - name: Configure MiniZinc
51 | if: steps.cache.outputs.cache-hit != 'true'
52 | run: |
53 | emcmake cmake -S . -B build \
54 | -DCMAKE_FIND_ROOT_PATH="/" \
55 | -DCMAKE_BUILD_TYPE=Release \
56 | -DBUILD_REF=$GITHUB_RUN_ID \
57 | -DGecode_ROOT="$GITHUB_WORKSPACE/vendor/gecode" \
58 | -DOsiCBC_ROOT="$GITHUB_WORKSPACE/vendor/cbc" \
59 | -DCMAKE_PREFIX_PATH="$GITHUB_WORKSPACE/vendor/highs/lib/cmake/highs:$GITHUB_WORKSPACE/vendor/chuffed/lib/cmake/chuffed" \
60 | -DCMAKE_INSTALL_PREFIX="$GITHUB_WORKSPACE/minizinc"
61 | - name: Build MiniZinc
62 | if: steps.cache.outputs.cache-hit != 'true'
63 | run: cmake --build build --config Release --target install -j 2
64 | - name: Upload package artifact
65 | uses: actions/upload-artifact@v4
66 | with:
67 | name: minizinc
68 | path: minizinc/
69 |
70 | build:
71 | name: Build and test minizinc-js
72 | runs-on: ubuntu-latest
73 | needs: [minizinc]
74 | if: github.event_name != 'schedule' || needs.minizinc.outputs.cache-hit != 'true'
75 | env:
76 | MZN_WASM_DIR: ${{ github.workspace }}/minizinc
77 | MZN_NODE_BINARY: ${{ github.workspace }}/native/bin/minizinc
78 | steps:
79 | - uses: actions/checkout@v4
80 | - name: Cache MiniZinc
81 | id: cache
82 | uses: actions/cache@v4
83 | with:
84 | path: minizinc
85 | key: ${{ needs.minizinc.outputs.cache-key }}
86 | - name: Fetch MiniZinc
87 | if: steps.cache.outputs.cache-hit != 'true'
88 | uses: actions/download-artifact@v4
89 | with:
90 | name: minizinc
91 | path: ${{ github.workspace }}/minizinc
92 | - name: Fetch latest MiniZinc image
93 | if: ${{ env.STABLE == 'yes' }}
94 | uses: docker://minizinc/minizinc:latest
95 | with:
96 | args: sh -c "mkdir -p $GITHUB_WORKSPACE/native && cp -v -r /usr/local/* $GITHUB_WORKSPACE/native"
97 | - name: Fetch edge MiniZinc image
98 | if: ${{ env.STABLE == 'no' }}
99 | uses: docker://minizinc/minizinc:edge
100 | with:
101 | args: sh -c "mkdir -p $GITHUB_WORKSPACE/native && cp -v -r /usr/local/* $GITHUB_WORKSPACE/native"
102 | - uses: actions/setup-node@v4
103 | with:
104 | node-version: lts/*
105 | - name: Install dependencies
106 | run: npm ci
107 | - name: Bump edge version
108 | if: ${{ env.STABLE == 'no' }}
109 | run: |
110 | npm version prerelease --preid=edge --no-git-tag-version > /dev/null
111 | PACKAGE=$(jq -r .name package.json)
112 | if PUBLISHED_EDGE=$(npm view "${PACKAGE}@edge" version 2>/dev/null); then
113 | echo "Latest published edge version is ${PUBLISHED_EDGE}"
114 | P1=$(echo "${PUBLISHED_EDGE}" | cut -d - -f 1)
115 | P2=$(jq -r .version package.json | cut -d - -f 1)
116 | if [[ "$P1" == "$P2" ]]; then
117 | jq --arg new_version "${PUBLISHED_EDGE}" '.version|=$new_version' package.json > package.json.tmp
118 | rm package.json
119 | mv package.json.tmp package.json
120 | npm version prerelease --preid=edge --no-git-tag-version > /dev/null
121 | fi
122 | fi
123 | echo "Bumped edge version to $(jq -r .version package.json)"
124 | - name: Build package
125 | run: npm run build
126 | - name: Run tests
127 | run: npm test
128 | - name: Build docs
129 | run: npm run docs
130 | - name: Create package
131 | run: npm pack
132 | - name: Upload docs artifact
133 | uses: actions/upload-artifact@v4
134 | with:
135 | name: docs
136 | path: docs/
137 | - name: Upload package artifact
138 | uses: actions/upload-artifact@v4
139 | with:
140 | name: package
141 | path: minizinc-*.tgz
142 |
143 | publish:
144 | name: Publish minizinc-js
145 | runs-on: ubuntu-latest
146 | needs: [build]
147 | if: ${{ github.event_name != 'pull_request' && ( startsWith(github.ref, 'refs/tags/v') || github.ref_name == 'develop' ) }}
148 | steps:
149 | - uses: actions/setup-node@v4
150 | with:
151 | node-version: lts/*
152 | registry-url: https://registry.npmjs.org
153 | - name: Fetch package
154 | uses: actions/download-artifact@v4
155 | with:
156 | name: package
157 | path: ${{ github.workspace }}
158 | - name: Publish edge version
159 | if: ${{ github.ref_name == 'develop' }}
160 | run: |
161 | if [ -z "$NODE_AUTH_TOKEN" ]; then
162 | npm publish minizinc-*.tgz --tag edge --dry-run
163 | else
164 | npm publish minizinc-*.tgz --tag edge
165 | fi
166 | env:
167 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
168 | - name: Publish latest version
169 | if: ${{ startsWith(github.ref, 'refs/tags/v') }}
170 | run: |
171 | if [ -z "$NODE_AUTH_TOKEN" ]; then
172 | npm publish minizinc-*.tgz --dry-run
173 | else
174 | npm publish minizinc-*.tgz
175 | fi
176 | env:
177 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
178 |
179 | pages:
180 | name: Publish documentation
181 | runs-on: ubuntu-latest
182 | needs: [build]
183 | if: ${{ github.event_name != 'pull_request' && ( github.ref_name == 'develop' || github.ref_name == 'stable' || startsWith(github.ref, 'refs/tags/v') ) }}
184 | steps:
185 | - name: Fetch documentation
186 | uses: actions/download-artifact@v4
187 | with:
188 | name: docs
189 | path: ${{ github.workspace }}/docs
190 | - name: Inject version selector script and favicon into head
191 | run: find ./docs -type f -name '*.html' -exec sh -c 'sed -i "s###" {}' \;
192 | - name: Set package usages to point to edge
193 | if: ${{ github.ref_name == 'develop' }}
194 | run: |
195 | sed -i 's#/minizinc/dist/#/minizinc@edge/dist/#g' docs/index.html
196 | sed -i 's/npm install minizinc/npm install minizinc@edge/g' docs/index.html
197 | sed -i 's#https://js.minizinc.dev/docs/stable/#https://js.minizinc.dev/docs/develop/#g' docs/index.html
198 | - name: Publish documentation
199 | uses: peaceiris/actions-gh-pages@v4
200 | with:
201 | github_token: ${{ secrets.GITHUB_TOKEN }}
202 | publish_dir: ./docs
203 | destination_dir: docs/${{ github.ref_name }}
204 | enable_jekyll: true
205 | cname: ${{ vars.PAGES_CNAME }}
206 |
207 | post_publish:
208 | name: Post-publish minizinc-js
209 | runs-on: ubuntu-latest
210 | needs: [publish]
211 | steps:
212 | - name: Purge jsDelivr edge cache
213 | if: ${{ github.ref_name == 'develop' }}
214 | uses: gacts/purge-jsdelivr-cache@v1
215 | with:
216 | url: |
217 | https://cdn.jsdelivr.net/npm/minizinc@edge/dist/minizinc.js
218 | https://cdn.jsdelivr.net/npm/minizinc@edge/dist/minizinc.mjs
219 | https://cdn.jsdelivr.net/npm/minizinc@edge/dist/minizinc-worker.js
220 | https://cdn.jsdelivr.net/npm/minizinc@edge/dist/minizinc.wasm
221 | https://cdn.jsdelivr.net/npm/minizinc@edge/dist/minizinc.data
222 | - name: Purge jsDelivr latest cache
223 | if: ${{ startsWith(github.ref, 'refs/tags/v') }}
224 | uses: gacts/purge-jsdelivr-cache@v1
225 | with:
226 | url: |
227 | https://cdn.jsdelivr.net/npm/minizinc/dist/minizinc.js
228 | https://cdn.jsdelivr.net/npm/minizinc/dist/minizinc.mjs
229 | https://cdn.jsdelivr.net/npm/minizinc/dist/minizinc-worker.js
230 | https://cdn.jsdelivr.net/npm/minizinc/dist/minizinc.wasm
231 | https://cdn.jsdelivr.net/npm/minizinc/dist/minizinc.data
232 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional stylelint cache
58 | .stylelintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variable files
76 | .env
77 | .env.development.local
78 | .env.test.local
79 | .env.production.local
80 | .env.local
81 |
82 | # parcel-bundler cache (https://parceljs.org/)
83 | .cache
84 | .parcel-cache
85 |
86 | # Next.js build output
87 | .next
88 | out
89 |
90 | # Nuxt.js build / generate output
91 | .nuxt
92 | dist
93 |
94 | # Gatsby files
95 | .cache/
96 | # Comment in the public line in if your project uses Gatsby and not Next.js
97 | # https://nextjs.org/blog/next-9-1#public-directory-support
98 | # public
99 |
100 | # vuepress build output
101 | .vuepress/dist
102 |
103 | # vuepress v2.x temp and cache directory
104 | .temp
105 | .cache
106 |
107 | # Serverless directories
108 | .serverless/
109 |
110 | # FuseBox cache
111 | .fusebox/
112 |
113 | # DynamoDB Local files
114 | .dynamodb/
115 |
116 | # TernJS port file
117 | .tern-port
118 |
119 | # Stores VSCode versions used for testing VSCode extensions
120 | .vscode-test
121 |
122 | # yarn v2
123 | .yarn/cache
124 | .yarn/unplugged
125 | .yarn/build-state.yml
126 | .yarn/install-state.gz
127 | .pnp.*
128 |
129 | bin/
130 | docs/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Mozilla Public License Version 2.0
2 | ==================================
3 |
4 | 1. Definitions
5 | --------------
6 |
7 | 1.1. "Contributor"
8 | means each individual or legal entity that creates, contributes to
9 | the creation of, or owns Covered Software.
10 |
11 | 1.2. "Contributor Version"
12 | means the combination of the Contributions of others (if any) used
13 | by a Contributor and that particular Contributor's Contribution.
14 |
15 | 1.3. "Contribution"
16 | means Covered Software of a particular Contributor.
17 |
18 | 1.4. "Covered Software"
19 | means Source Code Form to which the initial Contributor has attached
20 | the notice in Exhibit A, the Executable Form of such Source Code
21 | Form, and Modifications of such Source Code Form, in each case
22 | including portions thereof.
23 |
24 | 1.5. "Incompatible With Secondary Licenses"
25 | means
26 |
27 | (a) that the initial Contributor has attached the notice described
28 | in Exhibit B to the Covered Software; or
29 |
30 | (b) that the Covered Software was made available under the terms of
31 | version 1.1 or earlier of the License, but not also under the
32 | terms of a Secondary License.
33 |
34 | 1.6. "Executable Form"
35 | means any form of the work other than Source Code Form.
36 |
37 | 1.7. "Larger Work"
38 | means a work that combines Covered Software with other material, in
39 | a separate file or files, that is not Covered Software.
40 |
41 | 1.8. "License"
42 | means this document.
43 |
44 | 1.9. "Licensable"
45 | means having the right to grant, to the maximum extent possible,
46 | whether at the time of the initial grant or subsequently, any and
47 | all of the rights conveyed by this License.
48 |
49 | 1.10. "Modifications"
50 | means any of the following:
51 |
52 | (a) any file in Source Code Form that results from an addition to,
53 | deletion from, or modification of the contents of Covered
54 | Software; or
55 |
56 | (b) any new file in Source Code Form that contains any Covered
57 | Software.
58 |
59 | 1.11. "Patent Claims" of a Contributor
60 | means any patent claim(s), including without limitation, method,
61 | process, and apparatus claims, in any patent Licensable by such
62 | Contributor that would be infringed, but for the grant of the
63 | License, by the making, using, selling, offering for sale, having
64 | made, import, or transfer of either its Contributions or its
65 | Contributor Version.
66 |
67 | 1.12. "Secondary License"
68 | means either the GNU General Public License, Version 2.0, the GNU
69 | Lesser General Public License, Version 2.1, the GNU Affero General
70 | Public License, Version 3.0, or any later versions of those
71 | licenses.
72 |
73 | 1.13. "Source Code Form"
74 | means the form of the work preferred for making modifications.
75 |
76 | 1.14. "You" (or "Your")
77 | means an individual or a legal entity exercising rights under this
78 | License. For legal entities, "You" includes any entity that
79 | controls, is controlled by, or is under common control with You. For
80 | purposes of this definition, "control" means (a) the power, direct
81 | or indirect, to cause the direction or management of such entity,
82 | whether by contract or otherwise, or (b) ownership of more than
83 | fifty percent (50%) of the outstanding shares or beneficial
84 | ownership of such entity.
85 |
86 | 2. License Grants and Conditions
87 | --------------------------------
88 |
89 | 2.1. Grants
90 |
91 | Each Contributor hereby grants You a world-wide, royalty-free,
92 | non-exclusive license:
93 |
94 | (a) under intellectual property rights (other than patent or trademark)
95 | Licensable by such Contributor to use, reproduce, make available,
96 | modify, display, perform, distribute, and otherwise exploit its
97 | Contributions, either on an unmodified basis, with Modifications, or
98 | as part of a Larger Work; and
99 |
100 | (b) under Patent Claims of such Contributor to make, use, sell, offer
101 | for sale, have made, import, and otherwise transfer either its
102 | Contributions or its Contributor Version.
103 |
104 | 2.2. Effective Date
105 |
106 | The licenses granted in Section 2.1 with respect to any Contribution
107 | become effective for each Contribution on the date the Contributor first
108 | distributes such Contribution.
109 |
110 | 2.3. Limitations on Grant Scope
111 |
112 | The licenses granted in this Section 2 are the only rights granted under
113 | this License. No additional rights or licenses will be implied from the
114 | distribution or licensing of Covered Software under this License.
115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
116 | Contributor:
117 |
118 | (a) for any code that a Contributor has removed from Covered Software;
119 | or
120 |
121 | (b) for infringements caused by: (i) Your and any other third party's
122 | modifications of Covered Software, or (ii) the combination of its
123 | Contributions with other software (except as part of its Contributor
124 | Version); or
125 |
126 | (c) under Patent Claims infringed by Covered Software in the absence of
127 | its Contributions.
128 |
129 | This License does not grant any rights in the trademarks, service marks,
130 | or logos of any Contributor (except as may be necessary to comply with
131 | the notice requirements in Section 3.4).
132 |
133 | 2.4. Subsequent Licenses
134 |
135 | No Contributor makes additional grants as a result of Your choice to
136 | distribute the Covered Software under a subsequent version of this
137 | License (see Section 10.2) or under the terms of a Secondary License (if
138 | permitted under the terms of Section 3.3).
139 |
140 | 2.5. Representation
141 |
142 | Each Contributor represents that the Contributor believes its
143 | Contributions are its original creation(s) or it has sufficient rights
144 | to grant the rights to its Contributions conveyed by this License.
145 |
146 | 2.6. Fair Use
147 |
148 | This License is not intended to limit any rights You have under
149 | applicable copyright doctrines of fair use, fair dealing, or other
150 | equivalents.
151 |
152 | 2.7. Conditions
153 |
154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155 | in Section 2.1.
156 |
157 | 3. Responsibilities
158 | -------------------
159 |
160 | 3.1. Distribution of Source Form
161 |
162 | All distribution of Covered Software in Source Code Form, including any
163 | Modifications that You create or to which You contribute, must be under
164 | the terms of this License. You must inform recipients that the Source
165 | Code Form of the Covered Software is governed by the terms of this
166 | License, and how they can obtain a copy of this License. You may not
167 | attempt to alter or restrict the recipients' rights in the Source Code
168 | Form.
169 |
170 | 3.2. Distribution of Executable Form
171 |
172 | If You distribute Covered Software in Executable Form then:
173 |
174 | (a) such Covered Software must also be made available in Source Code
175 | Form, as described in Section 3.1, and You must inform recipients of
176 | the Executable Form how they can obtain a copy of such Source Code
177 | Form by reasonable means in a timely manner, at a charge no more
178 | than the cost of distribution to the recipient; and
179 |
180 | (b) You may distribute such Executable Form under the terms of this
181 | License, or sublicense it under different terms, provided that the
182 | license for the Executable Form does not attempt to limit or alter
183 | the recipients' rights in the Source Code Form under this License.
184 |
185 | 3.3. Distribution of a Larger Work
186 |
187 | You may create and distribute a Larger Work under terms of Your choice,
188 | provided that You also comply with the requirements of this License for
189 | the Covered Software. If the Larger Work is a combination of Covered
190 | Software with a work governed by one or more Secondary Licenses, and the
191 | Covered Software is not Incompatible With Secondary Licenses, this
192 | License permits You to additionally distribute such Covered Software
193 | under the terms of such Secondary License(s), so that the recipient of
194 | the Larger Work may, at their option, further distribute the Covered
195 | Software under the terms of either this License or such Secondary
196 | License(s).
197 |
198 | 3.4. Notices
199 |
200 | You may not remove or alter the substance of any license notices
201 | (including copyright notices, patent notices, disclaimers of warranty,
202 | or limitations of liability) contained within the Source Code Form of
203 | the Covered Software, except that You may alter any license notices to
204 | the extent required to remedy known factual inaccuracies.
205 |
206 | 3.5. Application of Additional Terms
207 |
208 | You may choose to offer, and to charge a fee for, warranty, support,
209 | indemnity or liability obligations to one or more recipients of Covered
210 | Software. However, You may do so only on Your own behalf, and not on
211 | behalf of any Contributor. You must make it absolutely clear that any
212 | such warranty, support, indemnity, or liability obligation is offered by
213 | You alone, and You hereby agree to indemnify every Contributor for any
214 | liability incurred by such Contributor as a result of warranty, support,
215 | indemnity or liability terms You offer. You may include additional
216 | disclaimers of warranty and limitations of liability specific to any
217 | jurisdiction.
218 |
219 | 4. Inability to Comply Due to Statute or Regulation
220 | ---------------------------------------------------
221 |
222 | If it is impossible for You to comply with any of the terms of this
223 | License with respect to some or all of the Covered Software due to
224 | statute, judicial order, or regulation then You must: (a) comply with
225 | the terms of this License to the maximum extent possible; and (b)
226 | describe the limitations and the code they affect. Such description must
227 | be placed in a text file included with all distributions of the Covered
228 | Software under this License. Except to the extent prohibited by statute
229 | or regulation, such description must be sufficiently detailed for a
230 | recipient of ordinary skill to be able to understand it.
231 |
232 | 5. Termination
233 | --------------
234 |
235 | 5.1. The rights granted under this License will terminate automatically
236 | if You fail to comply with any of its terms. However, if You become
237 | compliant, then the rights granted under this License from a particular
238 | Contributor are reinstated (a) provisionally, unless and until such
239 | Contributor explicitly and finally terminates Your grants, and (b) on an
240 | ongoing basis, if such Contributor fails to notify You of the
241 | non-compliance by some reasonable means prior to 60 days after You have
242 | come back into compliance. Moreover, Your grants from a particular
243 | Contributor are reinstated on an ongoing basis if such Contributor
244 | notifies You of the non-compliance by some reasonable means, this is the
245 | first time You have received notice of non-compliance with this License
246 | from such Contributor, and You become compliant prior to 30 days after
247 | Your receipt of the notice.
248 |
249 | 5.2. If You initiate litigation against any entity by asserting a patent
250 | infringement claim (excluding declaratory judgment actions,
251 | counter-claims, and cross-claims) alleging that a Contributor Version
252 | directly or indirectly infringes any patent, then the rights granted to
253 | You by any and all Contributors for the Covered Software under Section
254 | 2.1 of this License shall terminate.
255 |
256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257 | end user license agreements (excluding distributors and resellers) which
258 | have been validly granted by You or Your distributors under this License
259 | prior to termination shall survive termination.
260 |
261 | ************************************************************************
262 | * *
263 | * 6. Disclaimer of Warranty *
264 | * ------------------------- *
265 | * *
266 | * Covered Software is provided under this License on an "as is" *
267 | * basis, without warranty of any kind, either expressed, implied, or *
268 | * statutory, including, without limitation, warranties that the *
269 | * Covered Software is free of defects, merchantable, fit for a *
270 | * particular purpose or non-infringing. The entire risk as to the *
271 | * quality and performance of the Covered Software is with You. *
272 | * Should any Covered Software prove defective in any respect, You *
273 | * (not any Contributor) assume the cost of any necessary servicing, *
274 | * repair, or correction. This disclaimer of warranty constitutes an *
275 | * essential part of this License. No use of any Covered Software is *
276 | * authorized under this License except under this disclaimer. *
277 | * *
278 | ************************************************************************
279 |
280 | ************************************************************************
281 | * *
282 | * 7. Limitation of Liability *
283 | * -------------------------- *
284 | * *
285 | * Under no circumstances and under no legal theory, whether tort *
286 | * (including negligence), contract, or otherwise, shall any *
287 | * Contributor, or anyone who distributes Covered Software as *
288 | * permitted above, be liable to You for any direct, indirect, *
289 | * special, incidental, or consequential damages of any character *
290 | * including, without limitation, damages for lost profits, loss of *
291 | * goodwill, work stoppage, computer failure or malfunction, or any *
292 | * and all other commercial damages or losses, even if such party *
293 | * shall have been informed of the possibility of such damages. This *
294 | * limitation of liability shall not apply to liability for death or *
295 | * personal injury resulting from such party's negligence to the *
296 | * extent applicable law prohibits such limitation. Some *
297 | * jurisdictions do not allow the exclusion or limitation of *
298 | * incidental or consequential damages, so this exclusion and *
299 | * limitation may not apply to You. *
300 | * *
301 | ************************************************************************
302 |
303 | 8. Litigation
304 | -------------
305 |
306 | Any litigation relating to this License may be brought only in the
307 | courts of a jurisdiction where the defendant maintains its principal
308 | place of business and such litigation shall be governed by laws of that
309 | jurisdiction, without reference to its conflict-of-law provisions.
310 | Nothing in this Section shall prevent a party's ability to bring
311 | cross-claims or counter-claims.
312 |
313 | 9. Miscellaneous
314 | ----------------
315 |
316 | This License represents the complete agreement concerning the subject
317 | matter hereof. If any provision of this License is held to be
318 | unenforceable, such provision shall be reformed only to the extent
319 | necessary to make it enforceable. Any law or regulation which provides
320 | that the language of a contract shall be construed against the drafter
321 | shall not be used to construe this License against a Contributor.
322 |
323 | 10. Versions of the License
324 | ---------------------------
325 |
326 | 10.1. New Versions
327 |
328 | Mozilla Foundation is the license steward. Except as provided in Section
329 | 10.3, no one other than the license steward has the right to modify or
330 | publish new versions of this License. Each version will be given a
331 | distinguishing version number.
332 |
333 | 10.2. Effect of New Versions
334 |
335 | You may distribute the Covered Software under the terms of the version
336 | of the License under which You originally received the Covered Software,
337 | or under the terms of any subsequent version published by the license
338 | steward.
339 |
340 | 10.3. Modified Versions
341 |
342 | If you create software not governed by this License, and you want to
343 | create a new license for such software, you may create and use a
344 | modified version of this License if you rename the license and remove
345 | any references to the name of the license steward (except to note that
346 | such modified license differs from this License).
347 |
348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary
349 | Licenses
350 |
351 | If You choose to distribute Source Code Form that is Incompatible With
352 | Secondary Licenses under the terms of this version of the License, the
353 | notice described in Exhibit B of this License must be attached.
354 |
355 | Exhibit A - Source Code Form License Notice
356 | -------------------------------------------
357 |
358 | This Source Code Form is subject to the terms of the Mozilla Public
359 | License, v. 2.0. If a copy of the MPL was not distributed with this
360 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
361 |
362 | If it is not possible or desirable to put the notice in a particular
363 | file, then You may include the notice in a location (such as a LICENSE
364 | file in a relevant directory) where a recipient would be likely to look
365 | for such a notice.
366 |
367 | You may add additional accurate notices of copyright ownership.
368 |
369 | Exhibit B - "Incompatible With Secondary Licenses" Notice
370 | ---------------------------------------------------------
371 |
372 | This Source Code Form is "Incompatible With Secondary Licenses", as
373 | defined by the Mozilla Public License, v. 2.0.
374 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | JavaScript interface for MiniZinc
2 | =================================
3 |
4 | [](https://js.minizinc.dev/docs/stable)
5 | [](https://www.npmjs.com/package/minizinc)
6 | [](https://js.minizinc.dev/docs/develop)
7 | [](https://www.npmjs.com/package/minizinc)
8 |
9 | This package provides a JavaScript API for [MiniZinc](https://minizinc.dev)
10 | for use in web browsers using WebAssembly, or in NodeJS using a native
11 | installation of MiniZinc.
12 |
13 | This library powers the [MiniZinc Playground](https://minizinc.dev/solve).
14 |
15 | ## Getting started
16 |
17 | ### Using a CDN (recommended)
18 |
19 | Using ECMAScript modules:
20 |
21 | ```html
22 |
39 | ```
40 |
41 | Using a traditional script:
42 |
43 | ```html
44 |
45 |
61 | ```
62 |
63 | ### Self-hosting WebAssembly files
64 |
65 | If you're using a bundler, you can add the library to your project:
66 |
67 | ```sh
68 | npm install minizinc
69 | ```
70 |
71 | Then import it with:
72 |
73 | ```js
74 | import * as MiniZinc from 'minizinc';
75 | ```
76 |
77 | These three files need to be served by your webserver (found in `node_modules/minizinc/dist`):
78 |
79 | - `minizinc-worker.js`
80 | - `minizinc.wasm`
81 | - `minizinc.data`
82 |
83 | If you place them alongside your bundled script, they should be found automatically.
84 | Otherwise, their URLs can be specified during [initialisation](#initialisation).
85 |
86 | ### In NodeJS
87 |
88 | This requires an existing [installation of MiniZinc](https://github.com/MiniZinc/MiniZincIDE/releases).
89 |
90 | Add the library with:
91 |
92 | ```sh
93 | npm install minizinc
94 | ```
95 |
96 | Then import it with:
97 |
98 | ```js
99 | // If using ESM
100 | import * as MiniZinc from 'minizinc';
101 | // If using CommonJS
102 | const MiniZinc = require('minizinc');
103 | ```
104 |
105 | If you have added MiniZinc to your `PATH`, it will be found automatically.
106 | Otherwise, you can specify the executable path during [initialisation](#initialisation).
107 |
108 | ## Usage
109 |
110 | ### Initialisation
111 |
112 | Initialisation happens automatically when the library is used, or by calling
113 | [`init(...)`](https://js.minizinc.dev/docs/stable/functions/init.html). This can be used to ensure
114 | that the WebAssembly files start loading immediately, or to specify a different URL for the worker
115 | (or path to the MiniZinc executable if using NodeJS).
116 |
117 | In the browser:
118 |
119 | ```js
120 | MiniZinc.init({
121 | // If omitted, searches for minizinc-worker.js next to the minizinc library script
122 | workerURL: 'http://localhost:3000/path/to/my-own-worker.js',
123 | // If these are omitted, searches next to the worker script
124 | wasmURL: 'http://localhost:3000/path/to/minizinc.wasm',
125 | dataURL: 'http://localhost:3000/path/to/minizinc.data'
126 | }).then(() => {
127 | console.log('Ready');
128 | });
129 | ```
130 |
131 | In NodeJS:
132 |
133 | ```js
134 | MiniZinc.init({
135 | // Executable name
136 | minizinc: 'minizinc',
137 | // Search paths (can omit to use PATH)
138 | minizincPaths: ['/home/me/.local/bin', '/usr/local/bin']
139 | });
140 | ```
141 |
142 | By default, the NodeJS version tries to find MiniZinc on your `PATH`.
143 |
144 | ### Creating Models
145 |
146 | The main entrypoint for using the library is through the
147 | [`Model`](https://js.minizinc.dev/docs/stable/classes/Model.html) class:
148 |
149 | ```js
150 | const model = new MiniZinc.Model();
151 | // Add a file with a given name and string contents
152 | model.addFile('test.mzn', 'var 1..3: x; int: y;');
153 | // If you're using NodeJS, you can add files from the filesystem directly
154 | model.addFile('test.mzn');
155 | // Add model code from a string
156 | model.addString('int: z;');
157 | // Add data in DZN format
158 | model.addDznString('y = 1;');
159 | // Add data from a JSON object
160 | model.addJSON({z: 2});
161 | ```
162 |
163 | ### Solving
164 |
165 | Solving is done using the [`Model.solve(...)`](https://js.minizinc.dev/docs/stable/classes/Model.html#solve) method,
166 | which takes an object with `options` in [`.mpc`](https://minizinc.dev/doc-latest/en/command_line.html#ch-param-files)
167 | format.
168 |
169 | ```js
170 | const solve = model.solve({
171 | options: {
172 | solver: 'gecode',
173 | 'time-limit': 10000,
174 | statistics: true
175 | }
176 | });
177 | // You can listen for events
178 | solve.on('solution', solution => console.log(solution.output.json));
179 | solve.on('statistics', stats => console.log(stats.statistics));
180 | // And/or wait until complete
181 | solve.then(result => {
182 | console.log(result.solution.output.json);
183 | console.log(result.statistics);
184 | });
185 | ```
186 |
187 | During solving, MiniZinc emits events which can be subscribed to/unsubscribed from using the
188 | [`SolveProgress.on`](https://js.minizinc.dev/docs/stable/interfaces/SolveProgress.html#on) /
189 | [`SolveProgress.off`](https://js.minizinc.dev/docs/stable/interfaces/SolveProgress.html#off)
190 | methods. The events are those which appear in
191 | [Machine-readable JSON output format](https://minizinc.dev/doc-latest/en/json-stream.html),
192 | with the addition of the [`exit`](https://js.minizinc.dev/docs/stable/interfaces/ExitMessage.html)
193 | event, which can be used to detect when solving finishes (if you do not wish to await the
194 | [`SolveProgress`](https://js.minizinc.dev/docs/stable/interfaces/SolveProgress.html) object).
195 |
196 | By default, `--output-mode json` is used, allowing you to retrieve the model variable values
197 | directly from the solution objects. Use
198 | [`Model.solve({ jsonOutput: false, ...})`](https://js.minizinc.dev/docs/stable/classes/Model.html#solve)
199 | (and optionally specify a different `output-mode` in the `options`) to disable this behaviour.
200 |
201 | ## Documentation
202 |
203 | For more detailed documentation of all available options and functionality, visit the
204 | [API documentation](https://js.minizinc.dev/docs/stable/).
205 |
206 | ## Building
207 |
208 | ### Compiling MiniZinc for WebAssembly
209 |
210 | The WebAssembly build of MiniZinc requires [Emscripten](https://emscripten.org/).
211 |
212 | ```sh
213 | # Clone MiniZinc
214 | git clone https://github.com/MiniZinc/libminizinc minizinc
215 |
216 | # Download solvers (or you can build them yourself using emscripten)
217 | cd minizinc
218 | MZNARCH=wasm ./download_vendor
219 |
220 | # Configure MiniZinc
221 | emcmake cmake -S . -B build \
222 | -DCMAKE_FIND_ROOT_PATH="/" \
223 | -DCMAKE_BUILD_TYPE=Release \
224 | -DGecode_ROOT="$PWD/vendor/gecode" \
225 | -DOsiCBC_ROOT="$PWD/vendor/cbc" \
226 | -DCMAKE_PREFIX_PATH="$PWD/vendor/highs/lib/cmake/highs:$PWD/vendor/chuffed/lib/cmake/chuffed" \
227 | -DCMAKE_INSTALL_PREFIX="../minizinc-install"
228 |
229 | # Build MiniZinc
230 | cmake --build build --config Release --target install
231 | ```
232 |
233 | The WebAssembly build of MiniZinc can also be obtained from the [build workflow](https://github.com/MiniZinc/minizinc-js/actions/workflows/build.yml) as the `minizinc` artifact.
234 |
235 | ### Building MiniZinc JS
236 |
237 | 1. Run `npm install` to install dependencies.
238 | 2. Place the `bin/` folder of the WebAssembly build of MiniZinc inside this directory.
239 | Alternatively set the `MZN_WASM_DIR` environment variable to the installation directory of the
240 | WebAssembly build of MiniZinc.
241 | 3. Run `npm run build` to build the package. The built files are in the `dist/` directory.
242 | 4. Run `npm run docs` to build the documentation. The output files are in the `docs/` directory.
243 |
244 | ## Testing
245 |
246 | When testing, the [`web-worker`](https://www.npmjs.com/package/web-worker) library is used to emulate Web Worker
247 | support in NodeJS. This allows us to test both the browser version using WebAssembly, as well as the native version.
248 |
249 | Run `npm test` to run tests using [Jest](https://jestjs.io).
250 |
251 | ## License
252 |
253 | This library is distributed under the Mozilla Public License Version 2.0. See LICENSE for more information.
254 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "minizinc",
3 | "version": "4.4.3",
4 | "description": "JavaScript API for MiniZinc",
5 | "homepage": "https://js.minizinc.dev",
6 | "types": "types/index.d.ts",
7 | "main": "dist/minizinc.mjs",
8 | "type": "module",
9 | "exports": {
10 | ".": {
11 | "types": "./types/index.d.ts",
12 | "node": {
13 | "require": "./dist/minizinc-node.cjs",
14 | "default": "./dist/minizinc-node.mjs"
15 | },
16 | "require": "./dist/minizinc.cjs",
17 | "default": "./dist/minizinc.mjs"
18 | },
19 | "./minizinc-worker.js": "./dist/minizinc-worker.js",
20 | "./minizinc.wasm": "./dist/minizinc.wasm",
21 | "./minizinc.data": "./dist/minizinc.data"
22 | },
23 | "scripts": {
24 | "test": "rollup -c --environment TEST && jest",
25 | "dev": "rollup -w -c",
26 | "build": "rollup -c",
27 | "docs": "typedoc"
28 | },
29 | "files": [
30 | "dist/minizinc*",
31 | "types/**/*.d.ts"
32 | ],
33 | "author": "Jason Nguyen ",
34 | "license": "MPL-2.0",
35 | "repository": {
36 | "type": "git",
37 | "url": "https://github.com/MiniZinc/minizinc-js.git"
38 | },
39 | "devDependencies": {
40 | "@el3um4s/rollup-plugin-terser": "^1.0.2",
41 | "@rollup/plugin-alias": "^5.1.1",
42 | "@rollup/plugin-commonjs": "^28.0.3",
43 | "@rollup/plugin-replace": "^6.0.2",
44 | "@rollup/plugin-url": "^8.0.2",
45 | "jest": "^29.7.0",
46 | "rollup": "^4.41.0",
47 | "rollup-plugin-copy": "^3.5.0",
48 | "typedoc": "^0.28.4",
49 | "web-worker": "1.3.0"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import commonjs from "@rollup/plugin-commonjs";
2 | import copy from "rollup-plugin-copy";
3 | import { terser } from "@el3um4s/rollup-plugin-terser";
4 | import replace from "@rollup/plugin-replace";
5 | import alias from "@rollup/plugin-alias";
6 | import path from "path";
7 | import fs from "fs";
8 |
9 | const pkg = JSON.parse(fs.readFileSync("./package.json"));
10 |
11 | const testing = process.env.TEST;
12 | const production = !process.env.ROLLUP_WATCH && !testing;
13 | const minizincInstallDir = path.resolve(process.env.MZN_WASM_DIR || ".");
14 |
15 | const browser = (output, src) => ({
16 | input: "src/browser.js",
17 | output: {
18 | sourcemap: !production,
19 | ...output,
20 | },
21 | plugins: [
22 | production && terser(),
23 | replace({
24 | URL_BASE: src || "document.currentScript.src",
25 | PACKAGE_VERSION: JSON.stringify(pkg.version),
26 | preventAssignment: true,
27 | ...(testing && {
28 | _workerUrl: "settings.workerURL",
29 | }),
30 | }),
31 | ],
32 | });
33 |
34 | const worker = (output) => ({
35 | input: "src/worker.js",
36 | output: {
37 | sourcemap: !production,
38 | ...output,
39 | },
40 | plugins: [
41 | alias({
42 | entries: [
43 | {
44 | find: "minizinc-bin",
45 | replacement: path.join(minizincInstallDir, "/bin/minizinc.js"),
46 | },
47 | ],
48 | }),
49 | copy({
50 | targets: [
51 | {
52 | src: [
53 | path
54 | .join(minizincInstallDir, "/bin/minizinc.data")
55 | .replace(/\\/g, "/"),
56 | path
57 | .join(minizincInstallDir, "/bin/minizinc.wasm")
58 | .replace(/\\/g, "/"),
59 | ],
60 | dest: "dist",
61 | },
62 | ],
63 | verbose: true,
64 | }),
65 | commonjs({ ignore: ["crypto", "fs", "path", "perf_hooks", "ws"] }),
66 | production && terser(),
67 | ],
68 | });
69 |
70 | const node = (output) => ({
71 | input: "src/node.js",
72 | output: {
73 | sourcemap: !production,
74 | ...output,
75 | },
76 | plugins: [production && terser()],
77 | external: [
78 | "node:child_process",
79 | "node:events",
80 | "node:readline",
81 | "node:fs/promises",
82 | "node:path",
83 | "node:os",
84 | ],
85 | });
86 |
87 | const configs = testing
88 | ? [
89 | // Bundles for running tests
90 | browser(
91 | {
92 | file: "dist/test-minizinc.cjs",
93 | format: "cjs",
94 | },
95 | "`file:///${__dirname}`"
96 | ),
97 | worker({
98 | file: "dist/test-minizinc-worker.cjs",
99 | format: "cjs",
100 | }),
101 | node({
102 | file: "dist/test-minizinc-node.cjs",
103 | format: "cjs",
104 | }),
105 | ]
106 | : [
107 | // Bundles for distribution
108 | browser({
109 | name: "MiniZinc",
110 | file: "dist/minizinc.js",
111 | format: "iife",
112 | }),
113 | browser(
114 | {
115 | file: "dist/minizinc.mjs",
116 | format: "es",
117 | },
118 | "import.meta.url"
119 | ),
120 | browser({
121 | file: "dist/minizinc.cjs",
122 | format: "cjs",
123 | }),
124 | worker({
125 | file: "dist/minizinc-worker.js",
126 | format: "iife",
127 | }),
128 | node({
129 | file: "dist/minizinc-node.cjs",
130 | format: "cjs",
131 | }),
132 | node({
133 | file: "dist/minizinc-node.mjs",
134 | format: "es",
135 | }),
136 | ];
137 |
138 | export default configs;
139 |
--------------------------------------------------------------------------------
/src/browser.js:
--------------------------------------------------------------------------------
1 | // Controller in charge of web worker pool in the browser
2 |
3 | const cacheBuster = encodeURIComponent(PACKAGE_VERSION);
4 | let settings = {
5 | workerURL: new URL(`./minizinc-worker.js?version=${cacheBuster}`, URL_BASE),
6 | numWorkers: 2,
7 | };
8 | const workers = [];
9 | let workerObjectURL;
10 |
11 | function newWorker() {
12 | if (!workerObjectURL) {
13 | const importer = `importScripts(${JSON.stringify(settings.workerURL)});`;
14 | workerObjectURL = URL.createObjectURL(
15 | new Blob([importer], { type: "text/javascript" })
16 | );
17 | }
18 | const _workerUrl = workerObjectURL;
19 | const worker = new Worker(_workerUrl);
20 | worker.postMessage({
21 | wasmURL: settings.wasmURL
22 | ? settings.wasmURL.toString()
23 | : new URL(
24 | `./minizinc.wasm?version=${cacheBuster}`,
25 | settings.workerURL
26 | ).toString(),
27 | dataURL: settings.dataURL
28 | ? settings.dataURL.toString()
29 | : new URL(
30 | `./minizinc.data?version=${cacheBuster}`,
31 | settings.workerURL
32 | ).toString(),
33 | });
34 | workers.push({ worker, runCount: 0 });
35 | }
36 |
37 | function fillWorkerPool() {
38 | while (workers.length < settings.numWorkers) {
39 | newWorker();
40 | }
41 | }
42 |
43 | export async function init(cfg) {
44 | if (cfg) {
45 | settings = { ...settings, ...cfg };
46 | }
47 | if (workers.length > 0) {
48 | throw new Error(
49 | "MiniZinc.init() called after library already used/initialised"
50 | );
51 | }
52 | fillWorkerPool();
53 | await Promise.race(
54 | workers.map(
55 | (worker) =>
56 | new Promise((resolve) => {
57 | worker.worker.addEventListener(
58 | "message",
59 | (e) => {
60 | if (e.data.type === "ready") {
61 | resolve();
62 | }
63 | },
64 | { once: true }
65 | );
66 | })
67 | )
68 | );
69 | }
70 |
71 | export function shutdown() {
72 | for (const worker of workers) {
73 | worker.worker.terminate();
74 | }
75 | workers.splice(0);
76 | URL.revokeObjectURL(workerObjectURL);
77 | workerObjectURL = null;
78 | }
79 |
80 | export class Model {
81 | constructor() {
82 | this.vfs = {};
83 | this._toRun = [];
84 | this.unnamedCount = 0;
85 | }
86 | clone() {
87 | const clone = new Model();
88 | clone.vfs = { ...this.vfs };
89 | clone._toRun = [...this.toRun];
90 | clone.unnamedCount = this.unnamedCount;
91 | return clone;
92 | }
93 | addString(model) {
94 | let filename = `_mzn_${this.unnamedCount++}.mzn`;
95 | while (filename in this.vfs) {
96 | filename = `_mzn_${this.unnamedCount++}.mzn`;
97 | }
98 | this.addFile(filename, model);
99 | return filename;
100 | }
101 | addDznString(dzn) {
102 | let filename = `_dzn_${this.unnamedCount++}.dzn`;
103 | while (filename in this.vfs) {
104 | filename = `_dzn_${this.unnamedCount++}.dzn`;
105 | }
106 | this.addFile(filename, dzn);
107 | return filename;
108 | }
109 | addJson(data) {
110 | let filename = `_json_${this.unnamedCount++}.json`;
111 | while (filename in this.vfs) {
112 | filename = `_json_${this.unnamedCount++}.json`;
113 | }
114 | this.addFile(filename, JSON.stringify(data));
115 | return filename;
116 | }
117 | addFile(filename, contents, use = true) {
118 | if (typeof contents !== "string") {
119 | if (filename in this.vfs) {
120 | this._addToRun(filename, use);
121 | return;
122 | }
123 | throw new Error("Missing file contents argument");
124 | }
125 | this.vfs[filename] = contents;
126 | this._addToRun(filename, use);
127 | }
128 | _addToRun(filename, use) {
129 | if (
130 | use &&
131 | (filename.endsWith(".mzn") ||
132 | filename.endsWith(".mzc") ||
133 | filename.endsWith(".dzn") ||
134 | filename.endsWith(".json") ||
135 | filename.endsWith(".mpc") ||
136 | filename.endsWith(".fzn")) &&
137 | this._toRun.indexOf(filename) === -1
138 | ) {
139 | this._toRun.push(filename);
140 | }
141 | }
142 | _run(args, options, outputFiles = null) {
143 | fillWorkerPool();
144 | const preArgs = [];
145 | let files = this.vfs;
146 | if (options) {
147 | let mpcFile = `_mzn_${this.unnamedCount++}.mpc`;
148 | while (mpcFile in this.vfs) {
149 | mpcFile = `_mzn_${this.unnamedCount++}.mpc`;
150 | }
151 | files = { ...this.vfs, [mpcFile]: JSON.stringify(options) };
152 | preArgs.push(mpcFile);
153 | }
154 | let { worker, runCount } = workers.pop();
155 | worker.postMessage({
156 | jsonStream: true,
157 | files,
158 | args: [...preArgs, ...args, ...this._toRun],
159 | outputFiles,
160 | });
161 | return { worker, runCount: runCount + 1 };
162 | }
163 | check(cfg) {
164 | return new Promise((resolve, _reject) => {
165 | const config = { ...cfg };
166 | const { worker, runCount } = this._run(
167 | ["--model-check-only"],
168 | config.options
169 | );
170 | const errors = [];
171 | worker.onmessage = (e) => {
172 | switch (e.data.type) {
173 | case "error":
174 | errors.push(e.data);
175 | break;
176 | case "exit":
177 | if (runCount < 10) {
178 | workers.push({
179 | worker,
180 | runCount,
181 | });
182 | } else {
183 | worker.terminate();
184 | newWorker();
185 | }
186 | resolve(errors);
187 | break;
188 | }
189 | };
190 | });
191 | }
192 | interface(cfg) {
193 | return new Promise((resolve, reject) => {
194 | const config = { ...cfg };
195 | const { worker, runCount } = this._run(
196 | ["--model-interface-only"],
197 | config.options
198 | );
199 | const errors = [];
200 | let iface = null;
201 | worker.onmessage = (e) => {
202 | switch (e.data.type) {
203 | case "error":
204 | errors.push(e.data);
205 | break;
206 | case "interface":
207 | iface = e.data;
208 | break;
209 | case "exit":
210 | if (runCount < 10) {
211 | workers.push({
212 | worker,
213 | runCount,
214 | });
215 | } else {
216 | worker.terminate();
217 | newWorker();
218 | }
219 | if (e.data.code === 0) {
220 | resolve(iface);
221 | } else {
222 | reject(errors);
223 | }
224 | break;
225 | }
226 | };
227 | });
228 | }
229 | compile(cfg) {
230 | const config = { ...cfg };
231 | let i = 0;
232 | let out = `_fzn_${i++}.fzn`;
233 | while (out in this.vfs) {
234 | out = `_fzn_${i++}.fzn`;
235 | }
236 | const args = ["-c", "--fzn", out];
237 | const { worker } = this._run(args, config.options, [out]);
238 | // Don't reuse this worker, always create add a new one to the pool
239 | newWorker();
240 | let callbacks = {};
241 | let exited = false;
242 | let error = null;
243 | worker.onmessage = (e) => {
244 | if (callbacks[e.data.type]) {
245 | for (const f of callbacks[e.data.type]) {
246 | f(e.data);
247 | }
248 | }
249 | switch (e.data.type) {
250 | case "exit":
251 | worker.terminate();
252 | exited = true;
253 | callbacks = {};
254 | break;
255 | case "error":
256 | if (!error) error = e.data;
257 | break;
258 | }
259 | };
260 | return {
261 | isRunning() {
262 | return !exited;
263 | },
264 | cancel() {
265 | if (!exited) {
266 | exited = true;
267 | worker.terminate();
268 | if (callbacks["exit"]) {
269 | for (const f of callbacks["exit"]) {
270 | f({ type: "exit", code: null });
271 | }
272 | }
273 | callbacks = {};
274 | }
275 | },
276 | on(event, callback) {
277 | if (callbacks[event]) {
278 | callbacks[event].add(callback);
279 | } else {
280 | callbacks[event] = new Set([callback]);
281 | }
282 | },
283 | off(event, callback) {
284 | if (callbacks[event]) {
285 | callbacks[event].delete(callback);
286 | }
287 | },
288 | then(resolve, reject) {
289 | const onExit = (e) => {
290 | if (e.code === 0) {
291 | resolve(e.outputFiles[out]);
292 | } else {
293 | const exit = error ? { message: error.message, ...e } : e;
294 | if (reject) {
295 | reject(exit);
296 | } else {
297 | throw exit;
298 | }
299 | }
300 | };
301 | if (callbacks.exit) {
302 | callbacks.exit.add(onExit);
303 | } else {
304 | callbacks.exit = new Set([onExit]);
305 | }
306 | },
307 | };
308 | }
309 | solve(cfg) {
310 | const config = { jsonOutput: true, ...cfg };
311 | const args = ["-i"]; // Always use intermediate solutions
312 | if (config.jsonOutput) {
313 | args.push("--output-mode");
314 | args.push("json");
315 | }
316 | const { worker } = this._run(args, config.options);
317 | // Don't reuse this worker, always create add a new one to the pool
318 | newWorker();
319 | let error = null;
320 | let callbacks = {};
321 | let exited = false;
322 | let solution = null;
323 | let statistics = {};
324 | let status = "UNKNOWN";
325 | worker.onmessage = (e) => {
326 | if (callbacks[e.data.type]) {
327 | for (const f of callbacks[e.data.type]) {
328 | f(e.data);
329 | }
330 | }
331 | switch (e.data.type) {
332 | case "exit":
333 | worker.terminate();
334 | exited = true;
335 | callbacks = {};
336 | break;
337 | case "error":
338 | if (!error) error = e.data;
339 | break;
340 | case "statistics":
341 | statistics = {
342 | ...statistics,
343 | ...e.data.statistics,
344 | };
345 | break;
346 | case "solution":
347 | solution = e.data;
348 | status = "SATISFIED";
349 | break;
350 | case "status":
351 | status = e.data.status;
352 | break;
353 | }
354 | };
355 | return {
356 | isRunning() {
357 | return !exited;
358 | },
359 | cancel() {
360 | if (!exited) {
361 | exited = true;
362 | worker.terminate();
363 | if (callbacks["exit"]) {
364 | for (const f of callbacks["exit"]) {
365 | f({ type: "exit", code: null });
366 | }
367 | }
368 | callbacks = {};
369 | }
370 | },
371 | on(event, callback) {
372 | if (callbacks[event]) {
373 | callbacks[event].add(callback);
374 | } else {
375 | callbacks[event] = new Set([callback]);
376 | }
377 | },
378 | off(event, callback) {
379 | if (callbacks[event]) {
380 | callbacks[event].delete(callback);
381 | }
382 | },
383 | then(resolve, reject) {
384 | const onExit = (e) => {
385 | if (e.code === 0) {
386 | resolve({
387 | status,
388 | solution,
389 | statistics,
390 | });
391 | } else {
392 | const exit = error ? { message: error.message, ...e } : e;
393 | if (reject) {
394 | reject(exit);
395 | } else {
396 | throw exit;
397 | }
398 | }
399 | };
400 | if (callbacks.exit) {
401 | callbacks.exit.add(onExit);
402 | } else {
403 | callbacks.exit = new Set([onExit]);
404 | }
405 | },
406 | };
407 | }
408 | }
409 |
410 | export function version() {
411 | return new Promise((resolve, reject) => {
412 | fillWorkerPool();
413 | let { worker, runCount } = workers.pop();
414 | worker.postMessage({
415 | jsonStream: false,
416 | args: ["--version"],
417 | });
418 | worker.onmessage = (e) => {
419 | if (e.data.type === "exit") {
420 | if (runCount < 10) {
421 | workers.push({
422 | worker,
423 | runCount: runCount + 1,
424 | });
425 | } else {
426 | worker.terminate();
427 | newWorker();
428 | }
429 | if (e.data.code === 0) {
430 | resolve(e.data.stdout);
431 | } else {
432 | reject(e.data);
433 | }
434 | }
435 | };
436 | });
437 | }
438 |
439 | export function solvers() {
440 | return new Promise((resolve, reject) => {
441 | fillWorkerPool();
442 | let { worker, runCount } = workers.pop();
443 | worker.postMessage({
444 | jsonStream: false,
445 | args: ["--solvers-json"],
446 | });
447 | worker.onmessage = (e) => {
448 | if (e.data.type === "exit") {
449 | if (runCount < 10) {
450 | workers.push({
451 | worker,
452 | runCount: runCount + 1,
453 | });
454 | } else {
455 | worker.terminate();
456 | newWorker();
457 | }
458 | if (e.data.code === 0) {
459 | resolve(JSON.parse(e.data.stdout));
460 | } else {
461 | reject(e.data);
462 | }
463 | }
464 | };
465 | });
466 | }
467 |
468 | export function readStdlibFileContents(files) {
469 | const keys = Array.isArray(files) ? files : [files];
470 | return new Promise((resolve, reject) => {
471 | fillWorkerPool();
472 | let { worker, runCount } = workers.pop();
473 | worker.postMessage({
474 | readStdlibFiles: keys,
475 | });
476 | worker.onmessage = (e) => {
477 | if (e.data.type === "readStdlibFiles") {
478 | if (runCount < 10) {
479 | workers.push({
480 | worker,
481 | runCount: runCount + 1,
482 | });
483 | } else {
484 | worker.terminate();
485 | newWorker();
486 | }
487 | if (Array.isArray(files)) {
488 | resolve(e.data.files);
489 | } else {
490 | resolve(e.data.files[files]);
491 | }
492 | } else if (e.data.type === "error") {
493 | worker.terminate();
494 | newWorker();
495 | reject(e.data.message);
496 | }
497 | };
498 | });
499 | }
500 |
--------------------------------------------------------------------------------
/src/browser.spec.js:
--------------------------------------------------------------------------------
1 | const MiniZinc = require("../dist/test-minizinc.cjs");
2 |
3 | // Use web worker library for worker since not available on node
4 | global.Worker = require("web-worker");
5 | global.Blob = require("buffer").Blob;
6 |
7 | const { commonTests } = require("./tests.cjs");
8 |
9 | jest.setTimeout(30000);
10 |
11 | beforeAll(async () => {
12 | await MiniZinc.init({
13 | workerURL: "./dist/test-minizinc-worker.cjs",
14 | wasmURL: "./dist/minizinc.wasm",
15 | dataURL: "./dist/minizinc.data",
16 | });
17 | });
18 |
19 | afterAll(() => {
20 | MiniZinc.shutdown();
21 | });
22 |
23 | test("Virtual filesystem", async () => {
24 | const model = new MiniZinc.Model();
25 | model.addFile("test.mzn", 'include "foo.mzn";');
26 | model.addFile(
27 | "foo.mzn",
28 | `var 1..3: x;
29 | int: y;
30 | constraint x < y;`,
31 | false
32 | );
33 | model.addFile("data.dzn", "y = 2;");
34 | const result = await model.solve();
35 | const x = result.solution.output.json.x;
36 | expect(x).toBe(1);
37 | });
38 |
39 | commonTests(MiniZinc);
40 |
--------------------------------------------------------------------------------
/src/node.js:
--------------------------------------------------------------------------------
1 | // Controller used in node environments which uses child processes instead of wasm
2 |
3 | import child_process from "node:child_process";
4 | import EventEmitter from "node:events";
5 | import rl from "node:readline";
6 | import fs from "node:fs/promises";
7 | import path from "node:path";
8 | import os from "node:os";
9 |
10 | let settings = { minizinc: "minizinc", _executable: "minizinc" };
11 |
12 | export async function init(cfg) {
13 | if (cfg) {
14 | settings = { ...settings, ...cfg };
15 | }
16 | if (!/\\\//.test(settings.minizinc) && settings.minizincPaths) {
17 | for (const p of minizincPaths) {
18 | const executable = path.join(p, settings.minizinc);
19 | try {
20 | await fs.access(executable);
21 | settings._executable = executable;
22 | } catch {}
23 | }
24 | }
25 | settings._executable = settings.minizinc;
26 | }
27 |
28 | const childProcesses = new Set();
29 | export function shutdown() {
30 | for (const proc of childProcesses) {
31 | proc.kill("SIGKILL");
32 | }
33 | childProcesses.clear();
34 | }
35 |
36 | export class Model {
37 | constructor() {
38 | this.vfs = {};
39 | this._toRun = [];
40 | this.unnamedCount = 0;
41 | }
42 | clone() {
43 | const clone = new Model();
44 | clone.vfs = { ...this.vfs };
45 | clone._toRun = [...this.toRun];
46 | clone.unnamedCount = this.unnamedCount;
47 | return clone;
48 | }
49 | addString(model) {
50 | let filename = `_mzn_${this.unnamedCount++}.mzn`;
51 | while (filename in this.vfs) {
52 | filename = `_mzn_${this.unnamedCount++}.mzn`;
53 | }
54 | this._addVirtual(filename, model);
55 | return filename;
56 | }
57 | addDznString(dzn) {
58 | let filename = `_dzn_${this.unnamedCount++}.dzn`;
59 | while (filename in this.vfs) {
60 | filename = `_dzn_${this.unnamedCount++}.dzn`;
61 | }
62 | this._addVirtual(filename, dzn);
63 | return filename;
64 | }
65 | addJson(data) {
66 | let filename = `_json_${this.unnamedCount++}.json`;
67 | while (filename in this.vfs) {
68 | filename = `_json_${this.unnamedCount++}.json`;
69 | }
70 | this._addVirtual(filename, JSON.stringify(data));
71 | return filename;
72 | }
73 | _add_toRun(filename, use) {
74 | if (
75 | use &&
76 | (filename.endsWith(".mzn") ||
77 | filename.endsWith(".mzc") ||
78 | filename.endsWith(".dzn") ||
79 | filename.endsWith(".json") ||
80 | filename.endsWith(".mpc") ||
81 | filename.endsWith(".fzn")) &&
82 | this._toRun.indexOf(filename) === -1
83 | ) {
84 | this._toRun.push(filename);
85 | }
86 | }
87 | _addVirtual(filename, contents, use = true) {
88 | this.vfs[filename] = contents;
89 | this._add_toRun(filename, use);
90 | }
91 | addFile(filename, contents = null, use = true) {
92 | if (typeof contents === "string") {
93 | this._addVirtual(filename, contents, use);
94 | } else {
95 | this._add_toRun(filename, use);
96 | }
97 | }
98 | _run(args, options, outputFiles) {
99 | const emitter = new EventEmitter();
100 | let proc = null;
101 | emitter.on("sigint", () => {
102 | if (proc) {
103 | proc.kill("SIGINT");
104 | } else {
105 | proc = false;
106 | }
107 | });
108 | (async () => {
109 | const preArgs = ["--json-stream"];
110 | const tempdir = await fs.mkdtemp(path.join(os.tmpdir(), "mzn"));
111 | if (options) {
112 | let mpcFile = `_mzn_${this.unnamedCount++}.mpc`;
113 | while (mpcFile in this.vfs) {
114 | mpcFile = `_mzn_${this.unnamedCount++}.mpc`;
115 | }
116 | mpcFile = path.join(tempdir, mpcFile);
117 | await fs.writeFile(mpcFile, JSON.stringify(options));
118 | preArgs.push(mpcFile);
119 | }
120 | for (const key in this.vfs) {
121 | await fs.writeFile(path.join(tempdir, key), this.vfs[key]);
122 | }
123 | if (proc === false) {
124 | emitter.emit("exit", { code: 0 });
125 | return;
126 | }
127 | proc = child_process.spawn(settings._executable, [
128 | ...preArgs,
129 | ...args.map((x) =>
130 | outputFiles && outputFiles.indexOf(x) !== -1
131 | ? path.join(tempdir, x)
132 | : x
133 | ),
134 | ...this._toRun.map((x) => {
135 | if (x in this.vfs) {
136 | return path.join(tempdir, x);
137 | } else {
138 | return x;
139 | }
140 | }),
141 | ]);
142 | childProcesses.add(proc);
143 | const stdout = rl.createInterface(proc.stdout);
144 | stdout.on("line", async (line) => {
145 | try {
146 | const obj = JSON.parse(line);
147 | if (
148 | "location" in obj &&
149 | "filename" in obj.location &&
150 | typeof obj.location.filename === "string" &&
151 | obj.location.filename.indexOf(tempdir) === 0
152 | ) {
153 | // Strip prefix from filename
154 | obj.location.filename = obj.location.filename.substring(
155 | tempdir.length
156 | );
157 | }
158 | if ("stack" in obj && Array.isArray(obj.stack)) {
159 | for (const s of obj.stack) {
160 | if (
161 | "location" in s &&
162 | "filename" in s.location &&
163 | typeof s.location.filename === "string" &&
164 | s.location.filename.indexOf(tempdir) === 0
165 | ) {
166 | // Strip prefix from filename
167 | s.location.filename = s.location.filename.substring(
168 | tempdir.length
169 | );
170 | }
171 | }
172 | }
173 | emitter.emit(obj.type, obj);
174 | } catch (e) {
175 | emitter.emit("stdout", { type: "stdout", value: line });
176 | }
177 | });
178 | const stderr = rl.createInterface(proc.stderr);
179 | stderr.on("line", async (line) => {
180 | emitter.emit("stderr", line);
181 | });
182 | proc.on("exit", async (c, signal) => {
183 | childProcesses.delete(proc);
184 | const exitMessage = {
185 | type: "exit",
186 | code: signal === "SIGINT" ? null : c,
187 | };
188 | if (outputFiles) {
189 | exitMessage.outputFiles = {};
190 | for (const key of outputFiles) {
191 | try {
192 | exitMessage.outputFiles[key] = await fs.readFile(key, {
193 | encoding: "utf8",
194 | });
195 | } catch (e) {
196 | try {
197 | exitMessage.outputFiles[key] = await fs.readFile(
198 | path.join(tempdir, key),
199 | { encoding: "utf8" }
200 | );
201 | } catch (e) {
202 | exitMessage.outputFiles[key] = null;
203 | }
204 | }
205 | }
206 | }
207 | emitter.emit("exit", exitMessage);
208 | fs.rm(tempdir, { recursive: true, force: true });
209 | });
210 | })();
211 | return emitter;
212 | }
213 | check(cfg) {
214 | const config = { ...cfg };
215 | const proc = this._run(["--model-check-only"], config.options);
216 | const errors = [];
217 | proc.on("error", (e) => errors.push(e));
218 | return new Promise((resolve, _reject) => {
219 | proc.on("exit", (e) => resolve(errors));
220 | });
221 | }
222 | interface(cfg) {
223 | const config = { ...cfg };
224 | const proc = this._run(["--model-interface-only"], config.options);
225 | const errors = [];
226 | let iface = null;
227 | proc.on("error", (e) => errors.push(e));
228 | proc.on("interface", (e) => (iface = e));
229 | return new Promise((resolve, reject) => {
230 | proc.on("exit", (e) => {
231 | if (e.code === 0) {
232 | resolve(iface);
233 | } else {
234 | reject(errors);
235 | }
236 | });
237 | });
238 | }
239 | compile(cfg) {
240 | const config = { ...cfg };
241 | let i = 0;
242 | let out = `_fzn_${i++}.fzn`;
243 | while (out in this.vfs) {
244 | out = `_fzn_${i++}.fzn`;
245 | }
246 | const args = ["-c", "--fzn", out];
247 | let running = true;
248 | let error = null;
249 | const proc = this._run(args, config.options, [out]);
250 | proc.on("exit", () => (running = false));
251 | proc.on("error", (e) => {
252 | if (!error) error = e;
253 | });
254 | return {
255 | isRunning() {
256 | return running;
257 | },
258 | cancel() {
259 | proc.emit("sigint");
260 | },
261 | on: (event, listener) => proc.on(event, listener),
262 | off: (event, listener) => proc.off(event, listener),
263 | then(resolve, reject) {
264 | proc.on("exit", (e) => {
265 | if (e.code === 0) {
266 | resolve(e.outputFiles[out]);
267 | } else {
268 | const exit = error ? { message: error.message, ...e } : e;
269 | if (reject) {
270 | reject(exit);
271 | } else {
272 | throw exit;
273 | }
274 | }
275 | });
276 | },
277 | };
278 | }
279 | solve(cfg) {
280 | const config = { jsonOutput: true, ...cfg };
281 | const args = ["-i"]; // Always use intermediate solutions
282 | if (config.jsonOutput) {
283 | args.push("--output-mode");
284 | args.push("json");
285 | }
286 | let running = true;
287 | let error = null;
288 | const proc = this._run(args, config.options);
289 | proc.on("exit", () => (running = false));
290 | let solution = null;
291 | let statistics = {};
292 | let status = "UNKNOWN";
293 | proc.on("statistics", (e) => {
294 | statistics = {
295 | ...statistics,
296 | ...e.statistics,
297 | };
298 | });
299 | proc.on("solution", (e) => {
300 | solution = e;
301 | status = "SATISFIED";
302 | });
303 | proc.on("status", (e) => {
304 | status = e.status;
305 | });
306 | proc.on("error", (e) => {
307 | if (!error) error = e;
308 | });
309 | return {
310 | isRunning() {
311 | return running;
312 | },
313 | cancel() {
314 | proc.emit("sigint");
315 | },
316 | on: (event, listener) => proc.on(event, listener),
317 | off: (event, listener) => proc.off(event, listener),
318 | then(resolve, reject) {
319 | proc.on("exit", (e) => {
320 | if (e.code === 0) {
321 | resolve({
322 | status,
323 | solution,
324 | statistics,
325 | });
326 | } else {
327 | const exit = error ? { message: error.message, ...e } : e;
328 | if (reject) {
329 | reject(exit);
330 | } else {
331 | throw exit;
332 | }
333 | }
334 | });
335 | },
336 | };
337 | }
338 | }
339 |
340 | export function version() {
341 | return new Promise((resolve, reject) => {
342 | let proc = null;
343 | proc = child_process.execFile(
344 | settings._executable,
345 | ["--version"],
346 | (error, stdout, stderr) => {
347 | childProcesses.delete(proc);
348 | if (error) {
349 | reject(error);
350 | }
351 | resolve(stdout);
352 | }
353 | );
354 | childProcesses.add(proc);
355 | });
356 | }
357 |
358 | export function solvers() {
359 | return new Promise((resolve, reject) => {
360 | let proc = null;
361 | proc = child_process.execFile(
362 | settings._executable,
363 | ["--solvers-json"],
364 | (error, stdout, stderr) => {
365 | childProcesses.delete(proc);
366 | if (error) {
367 | reject(error);
368 | }
369 | resolve(JSON.parse(stdout));
370 | }
371 | );
372 | childProcesses.add(proc);
373 | });
374 | }
375 |
376 | export function readStdlibFileContents(files) {
377 | const keys = Array.isArray(files) ? files : [files];
378 | return new Promise((resolve, reject) => {
379 | let proc = null;
380 | proc = child_process.execFile(
381 | settings._executable,
382 | ["--config-dirs"],
383 | async (error, stdout, stderr) => {
384 | childProcesses.delete(proc);
385 | if (error) {
386 | reject(error);
387 | }
388 | const mznStdlibDir = JSON.parse(stdout).mznStdlibDir;
389 | const result = {};
390 | for (const key of keys) {
391 | const p = path.join(mznStdlibDir, key);
392 | const rel = path.relative(mznStdlibDir, p);
393 | if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) {
394 | reject(`Unsupported file path ${key}`);
395 | }
396 | try {
397 | result[key] = await fs.readFile(p, {
398 | encoding: "utf8",
399 | });
400 | } catch (e) {
401 | result[key] = null;
402 | }
403 | }
404 | if (Array.isArray(files)) {
405 | resolve(result);
406 | } else {
407 | resolve(result[files]);
408 | }
409 | }
410 | );
411 | childProcesses.add(proc);
412 | });
413 | }
414 |
--------------------------------------------------------------------------------
/src/node.spec.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const MiniZinc = require("../dist/test-minizinc-node.cjs");
4 |
5 | const { commonTests } = require("./tests.cjs");
6 |
7 | beforeAll(async () => {
8 | await MiniZinc.init({
9 | minizinc: process.env.MZN_NODE_BINARY || "minizinc",
10 | });
11 | });
12 |
13 | afterAll(() => {
14 | MiniZinc.shutdown();
15 | });
16 |
17 | commonTests(MiniZinc);
18 |
19 | test("Load from filesystem", async () => {
20 | const model = new MiniZinc.Model();
21 | model.addFile(path.join(__dirname, "test.mzn"));
22 | const result = await model.solve();
23 | const x = result.solution.output.json.x;
24 | expect(x).toBeGreaterThanOrEqual(1);
25 | expect(x).toBeLessThanOrEqual(3);
26 | });
27 |
--------------------------------------------------------------------------------
/src/test.mzn:
--------------------------------------------------------------------------------
1 | var 1..3: x;
2 |
--------------------------------------------------------------------------------
/src/tests.cjs:
--------------------------------------------------------------------------------
1 | // Tests for common API
2 |
3 | module.exports.commonTests = (MiniZinc) => {
4 | test("Version output", async () => {
5 | const version = await MiniZinc.version();
6 | expect(version).toMatch(/version (\d)+\.(\d)+\.(\d)+/);
7 | });
8 |
9 | test("Solvers output", async () => {
10 | const solvers = await MiniZinc.solvers();
11 | expect(Array.isArray(solvers)).toBe(true);
12 | });
13 |
14 | test("Basic solve", async () => {
15 | const model = new MiniZinc.Model();
16 | model.addString("var 1..3: x;");
17 | const result = await model.solve();
18 | const x = result.solution.output.json.x;
19 | expect(x).toBeGreaterThanOrEqual(1);
20 | expect(x).toBeLessThanOrEqual(3);
21 | });
22 |
23 | test("DZN output", async () => {
24 | const model = new MiniZinc.Model();
25 | model.addString("var 1..3: x;");
26 | const result = await model.solve({
27 | jsonOutput: false,
28 | options: {
29 | "output-mode": "dzn",
30 | },
31 | });
32 | expect(result.solution.output.dzn).toMatch(/x = [1-3];\n/);
33 | });
34 |
35 | test("Solve with DZN data", async () => {
36 | const model = new MiniZinc.Model();
37 | model.addString(`
38 | var 1..3: x;
39 | int: y;
40 | constraint x > y;
41 | `);
42 | model.addDznString("y = 2;");
43 | const result = await model.solve();
44 | const x = result.solution.output.json.x;
45 | expect(x).toBe(3);
46 | });
47 |
48 | test("Solve with JSON data", async () => {
49 | const model = new MiniZinc.Model();
50 | model.addString(`
51 | var 1..3: x;
52 | int: y;
53 | constraint x > y;
54 | `);
55 | model.addJson({
56 | y: 2,
57 | });
58 | const result = await model.solve();
59 | const x = result.solution.output.json.x;
60 | expect(x).toBe(3);
61 | });
62 |
63 | test("Events", async () => {
64 | const model = new MiniZinc.Model();
65 | model.addString("var 1..3: x;");
66 | const solve = model.solve({
67 | options: {
68 | "all-solutions": true,
69 | statistics: true,
70 | },
71 | });
72 | const values = [];
73 | solve.on("solution", (e) => values.push(e.output.json.x));
74 | solve.on("status", (e) => expect(e.status).toBe("ALL_SOLUTIONS"));
75 | const result = await solve;
76 | expect(result.statistics.nSolutions).toBe(3);
77 | expect(values).toContain(1);
78 | expect(values).toContain(2);
79 | expect(values).toContain(3);
80 | expect(values.length).toBe(3);
81 | });
82 |
83 | test("Error events", async () => {
84 | const model = new MiniZinc.Model();
85 | model.addString(`
86 | function int: foo(1..1: x) = x;
87 | int: v = foo(2);
88 | `);
89 | const solve = model.solve();
90 | expect.assertions(3);
91 | solve.on("error", (e) => {
92 | expect(typeof e.message).toBe("string");
93 | });
94 | try {
95 | await solve;
96 | } catch (e) {
97 | expect(e.code).toBe(1);
98 | expect(typeof e.message).toBe("string");
99 | }
100 | });
101 |
102 | test("Basic compile", async () => {
103 | const model = new MiniZinc.Model();
104 | model.addString("var 1..3: x;");
105 | const fzn = await model.compile({ options: { solver: "gecode" } });
106 | expect(fzn).toMatch(
107 | /var\s+1\s*\.\.\s*3\s*:\s*x\s*::\s*output_var\s*;\s*solve\s*satisfy;\s*/
108 | );
109 | });
110 |
111 | test("Model check success", async () => {
112 | const model = new MiniZinc.Model();
113 | model.addString("var 1..3: x;");
114 | const errors = await model.check();
115 | expect(errors.length).toBe(0);
116 | });
117 |
118 | test("Model check error", async () => {
119 | const model = new MiniZinc.Model();
120 | model.addString("var 1..3: x; var 1..3: x;");
121 | const errors = await model.check();
122 | expect(errors.length).toBe(1);
123 | });
124 |
125 | test("Model interface", async () => {
126 | const model = new MiniZinc.Model();
127 | model.addString("var 1..3: x; int: y;");
128 | const iface = await model.interface();
129 | expect(iface.input).toEqual({
130 | y: { type: "int" },
131 | });
132 | expect(iface.output).toEqual({ x: { type: "int" } });
133 | expect(iface.method).toBe("sat");
134 | });
135 |
136 | test("UTF-8 support", async () => {
137 | const model = new MiniZinc.Model();
138 | model.addString("int: 'μ' :: output = 1; string: x :: output = \"μ\";");
139 | const result = await model.solve();
140 | expect(result.solution.output.json).toMatchObject({
141 | μ: 1,
142 | x: "μ",
143 | });
144 | });
145 |
146 | test("Stdlib file retrieval", async () => {
147 | const contents = await MiniZinc.readStdlibFileContents("std/stdlib.mzn");
148 | expect(contents.length).toBeGreaterThan(0);
149 | const multiple = await MiniZinc.readStdlibFileContents([
150 | "std/stdlib.mzn",
151 | "std/redefinitions.mzn",
152 | ]);
153 | expect(multiple["std/stdlib.mzn"].length).toBeGreaterThan(0);
154 | expect(multiple["std/redefinitions.mzn"].length).toBeGreaterThan(0);
155 | await expect(MiniZinc.readStdlibFileContents("../foo")).rejects.toEqual(
156 | "Unsupported file path ../foo"
157 | );
158 | });
159 | };
160 |
--------------------------------------------------------------------------------
/src/worker.js:
--------------------------------------------------------------------------------
1 | // Web worker code for the browser
2 |
3 | import MINIZINC from "minizinc-bin";
4 |
5 | let initMiniZinc = null;
6 |
7 | addEventListener("message", async (e) => {
8 | try {
9 | if (initMiniZinc) {
10 | const Module = await initMiniZinc;
11 | if (e.data.readStdlibFiles) {
12 | const files = {};
13 | const prefix = "file:///usr/share/minizinc/";
14 | for (const key of e.data.readStdlibFiles) {
15 | const resolved = new URL(prefix + key).href;
16 | if (resolved.indexOf(prefix) !== 0) {
17 | // Ensure path is a valid relative path
18 | console.error(`Unsupported file path ${key}`);
19 | postMessage({
20 | type: "error",
21 | message: `Unsupported file path ${key}`,
22 | });
23 | return;
24 | }
25 | const path =
26 | "/usr/share/minizinc/" + resolved.substring(prefix.length);
27 | if (Module.FS.analyzePath(path).exists) {
28 | files[key] = Module.FS.readFile(path, {
29 | encoding: "utf8",
30 | });
31 | } else {
32 | files[key] = null;
33 | }
34 | }
35 | postMessage({
36 | type: "readStdlibFiles",
37 | files,
38 | });
39 | return;
40 | }
41 | Module.stdoutBuffer = [];
42 | Module.stderrBuffer = [];
43 | Module.jsonStream = !!e.data.jsonStream;
44 | Module.FS.mount(Module.FS.filesystems.MEMFS, null, "/minizinc");
45 | if (e.data.files) {
46 | const prefix = "file:///minizinc/";
47 | for (const key in e.data.files) {
48 | const resolved = new URL(prefix + key).href;
49 | if (resolved.indexOf(prefix) !== 0) {
50 | // Ensure path is a valid relative path
51 | throw new Error(`Unsupported file path ${key}`);
52 | }
53 | const dest = "/minizinc/" + resolved.substring(prefix.length);
54 | for (
55 | let i = dest.indexOf("/", 10);
56 | i !== -1;
57 | i = dest.indexOf("/", i + 1)
58 | ) {
59 | // Create parent paths
60 | const path = dest.substring(0, i);
61 | if (!Module.FS.analyzePath(path).exists) {
62 | Module.FS.mkdir(path);
63 | }
64 | }
65 | // Write file
66 | Module.FS.writeFile(dest, e.data.files[key]);
67 | }
68 | }
69 | // Always include --json-stream
70 | const args = Module.jsonStream
71 | ? ["--json-stream", ...e.data.args]
72 | : e.data.args;
73 | const oldCwd = Module.FS.cwd();
74 | Module.FS.chdir("/minizinc");
75 | try {
76 | const code = Module.callMain(args);
77 | // Add exit message so the controller can tell that we're done
78 | const exitMessage = { type: "exit", code };
79 | if (Module.stdoutBuffer.length > 0) {
80 | const decoder = new TextDecoder("utf-8");
81 | exitMessage.stdout = decoder.decode(
82 | new Uint8Array(Module.stdoutBuffer)
83 | );
84 | }
85 | if (Module.stderrBuffer.length > 0) {
86 | const decoder = new TextDecoder("utf-8");
87 | exitMessage.stderr = decoder.decode(
88 | new Uint8Array(Module.stderrBuffer)
89 | );
90 | }
91 | if (e.data.outputFiles) {
92 | exitMessage.outputFiles = {};
93 | const prefix = "file:///minizinc/";
94 | for (const key of e.data.outputFiles) {
95 | const resolved = new URL(prefix + key).href;
96 | if (resolved.indexOf(prefix) !== 0) {
97 | // Ensure path is a valid relative path
98 | throw new Error(`Unsupported file path ${key}`);
99 | }
100 | const path = "/minizinc/" + resolved.substring(prefix.length);
101 | if (Module.FS.analyzePath(path).exists) {
102 | exitMessage.outputFiles[key] = Module.FS.readFile(path, {
103 | encoding: "utf8",
104 | });
105 | } else {
106 | exitMessage.outputFiles[key] = null;
107 | }
108 | }
109 | }
110 | postMessage(exitMessage);
111 | } catch (e) {
112 | console.error(e);
113 | postMessage({
114 | type: "exit",
115 | code: -1,
116 | message: e.message,
117 | });
118 | }
119 | Module.FS.chdir(oldCwd);
120 | Module.FS.unmount("/minizinc");
121 | } else {
122 | initMiniZinc = MINIZINC({
123 | locateFile(path, prefix) {
124 | if (path === "minizinc.wasm") {
125 | return e.data.wasmURL;
126 | }
127 | if (path === "minizinc.data") {
128 | return e.data.dataURL;
129 | }
130 | return prefix + path;
131 | },
132 | preRun: [
133 | (Module) => {
134 | const stdout = (code) => {
135 | if (code === 0x0) {
136 | return;
137 | }
138 | Module.stdoutBuffer.push(code);
139 | if (Module.jsonStream && code === 0x0a) {
140 | const decoder = new TextDecoder("utf-8");
141 | const line = decoder.decode(
142 | new Uint8Array(Module.stdoutBuffer)
143 | );
144 | try {
145 | // Send the JSON stream message
146 | const obj = JSON.parse(line);
147 | if (
148 | "location" in obj &&
149 | "filename" in obj.location &&
150 | typeof obj.location.filename === "string" &&
151 | obj.location.filename.indexOf("/minizinc/") === 0
152 | ) {
153 | // Strip prefix from filename
154 | obj.location.filename = obj.location.filename.substring(10);
155 | }
156 | if ("stack" in obj && Array.isArray(obj.stack)) {
157 | for (const s of obj.stack) {
158 | if (
159 | "location" in s &&
160 | "filename" in s.location &&
161 | typeof s.location.filename === "string" &&
162 | s.location.filename.indexOf("/minizinc/") === 0
163 | ) {
164 | // Strip prefix from filename
165 | s.location.filename = s.location.filename.substring(10);
166 | }
167 | }
168 | }
169 | postMessage(obj);
170 | } catch (e) {
171 | // Fall back to creating a stdout message
172 | postMessage({
173 | type: "stdout",
174 | value: line,
175 | });
176 | }
177 | Module.stdoutBuffer = [];
178 | }
179 | };
180 | const stderr = (code) => {
181 | if (code === 0x0) {
182 | return;
183 | }
184 | Module.stderrBuffer.push(code);
185 | if (Module.jsonStream && code === 0x0a) {
186 | // Send as a stderr message
187 | const decoder = new TextDecoder("utf-8");
188 | const line = decoder.decode(
189 | new Uint8Array(Module.stderrBuffer)
190 | );
191 | postMessage({
192 | type: "stderr",
193 | value: line,
194 | });
195 | Module.stderrBuffer = [];
196 | }
197 | };
198 | Module.FS.init(null, stdout, stderr);
199 | Module.FS.mkdir("/minizinc");
200 |
201 | // Make gecode_presolver available as gecode solver
202 | Module.FS.mkdir("/home/web_user/.minizinc");
203 | Module.FS.writeFile(
204 | "/home/web_user/.minizinc/Preferences.json",
205 | JSON.stringify({
206 | solverDefaults: [
207 | [
208 | "org.minizinc.gecode_presolver",
209 | "--backend-flags",
210 | "--allow-unbounded-vars",
211 | ],
212 | ],
213 | tagDefaults: [["", "org.minizinc.gecode_presolver"]],
214 | })
215 | );
216 | },
217 | ],
218 | noInitialRun: true,
219 | noExitRuntime: true,
220 | });
221 | }
222 |
223 | await initMiniZinc;
224 | postMessage({ type: "ready" });
225 | } catch (e) {
226 | console.error(e);
227 | }
228 | });
229 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "ES2017", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16 | // "jsx": "preserve", /* Specify what JSX code is generated. */
17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
26 |
27 | /* Modules */
28 | "module": "ESNext", /* Specify what module code is generated. */
29 | // "rootDir": "./", /* Specify the root folder within your source files. */
30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
38 | // "resolveJsonModule": true, /* Enable importing .json files. */
39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
40 |
41 | /* JavaScript Support */
42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
45 |
46 | /* Emit */
47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
52 | // "outDir": "./", /* Specify an output folder for all emitted files. */
53 | // "removeComments": true, /* Disable emitting comments. */
54 | // "noEmit": true, /* Disable emitting files from a compilation. */
55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
63 | // "newLine": "crlf", /* Set the newline character for emitting files. */
64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
70 |
71 | /* Interop Constraints */
72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
77 |
78 | /* Type Checking */
79 | "strict": true, /* Enable all strict type-checking options. */
80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
98 |
99 | /* Completeness */
100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://typedoc.org/schema.json",
3 | "entryPoints": ["./types/index.d.ts"],
4 | "out": "docs",
5 | "name": "MiniZinc JavaScript",
6 | "excludePrivate": true,
7 | "excludeProtected": true,
8 | "excludeExternals": true,
9 | "navigationLinks": {
10 | "MiniZinc": "https://minizinc.dev",
11 | "GitHub": "https://github.com/minizinc/minizinc-js"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Main class for solving MiniZinc instances.
3 | *
4 | * This API allows you to add `.mzn`, `.dzn`, `.json` and `.mpc` files using
5 | * the `addFile()` method, and then run MiniZinc on the files using the
6 | * `solve()` method.
7 | *
8 | * Code can also be added programmatically using the `addString()` (and similar)
9 | * methods.
10 | *
11 | * @example
12 | * ```js
13 | * const model = new MiniZinc.Model();
14 | * // Add a file with a given name and string contents
15 | * model.addFile('test.mzn', 'var 1..3: x; int: y;');
16 | * // Add model code from a string
17 | * model.addString('int: z;');
18 | * // Add data in DZN format
19 | * model.addDznString('y = 1;');
20 | * // Add data from a JSON object
21 | * model.addJSON({z: 2});
22 | *
23 | * const solve = model.solve({
24 | * options: {
25 | * solver: 'gecode',
26 | * 'time-limit': 10000,
27 | * statistics: true
28 | * }
29 | * });
30 | *
31 | * // You can listen for events
32 | * solve.on('solution', solution => console.log(solution));
33 | * solve.on('statistics', stats => console.log(stats.statistics));
34 | *
35 | * // And/or wait until complete
36 | * solve.then(result => {
37 | * console.log(result.solution);
38 | * console.log(result.statistics);
39 | * });
40 | * ```
41 | */
42 | export class Model {
43 | /**
44 | * Create a new model.
45 | *
46 | * @example
47 | * ```js
48 | * const model = new MiniZinc.Model();
49 | * ```
50 | */
51 | constructor();
52 |
53 | /**
54 | * Create a clone of this model.
55 | *
56 | * @example
57 | * ```js
58 | * const m1 = new MiniZinc.Model();
59 | * m1.addFile('test.mzn', `
60 | * var 1..3: x;
61 | * int: y;
62 | * `);
63 | * const m2 = m1.clone();
64 | * // Both m1 and m2 have test.mzn
65 | *
66 | * // Add different data to each model
67 | * m1.addJson({
68 | * y: 1
69 | * });
70 | * m2.addJson({
71 | * y: 2
72 | * });
73 | * ```
74 | */
75 | clone(): Model;
76 |
77 | /**
78 | * Add a snippet of code to the model.
79 | *
80 | * Note that each snippet is used as a complete model file.
81 | *
82 | * @example
83 | * ```js
84 | * model.addString("var 1..3: x;");
85 | * model.addString("float: y;");
86 | * ```
87 | *
88 | * @param model MiniZinc code as a string
89 | * @returns The filename of the snippet (may be useful to identify sources of errors)
90 | */
91 | addString(model: string): string;
92 | /**
93 | * Adds a snippet of data to the model.
94 | *
95 | * Note that each snippet is used as a complete data file.
96 | *
97 | * @example
98 | * ```js
99 | * model.addDznString("x = 1;");
100 | * ```
101 | *
102 | * @param dzn DataZinc input as a string
103 | * @returns The filename of the snippet (may be useful to identify sources of errors)
104 | */
105 | addDznString(dzn: string): string;
106 |
107 | /**
108 | * Adds data to the model in JSON format.
109 | *
110 | * Note that each snippet is used as a complete JSON data file.
111 | *
112 | * @example
113 | * ```js
114 | * model.addJson({
115 | * y: 1.5
116 | * });
117 | * ```
118 | *
119 | * @param data The data as an object in MiniZinc JSON data input format
120 | * @returns The filename of the snippet (may be useful to identify sources of errors)
121 | */
122 | addJson(data: object): string;
123 |
124 | /**
125 | * Makes the given string contents available to MiniZinc using the given
126 | * filename.
127 | *
128 | * @example
129 | * ```js
130 | * /// Add this file to the MiniZinc command
131 | * model.addFile("model.mzn", `
132 | * include "foo.mzn";
133 | * var 1..3: x;
134 | * `);
135 | * // Make this file available, but don't add it to the MiniZinc command
136 | * model.addFile("foo.mzn", "var 1..3: y;", false);
137 | * ```
138 | *
139 | * This method is generally only used from the browser.
140 | *
141 | * @param filename The file name to use
142 | * @param contents The contents of the file
143 | * @param use Whether to add this file as an argument to the MiniZinc command
144 | */
145 | addFile(filename: string, contents: string, use?: boolean): void;
146 | /**
147 | * Adds the given file to the model.
148 | *
149 | * @example
150 | * ```js
151 | * model.addFile("./path/to/model.mzn");
152 | * ```
153 | *
154 | * Only available using the native version of MiniZinc in NodeJS.
155 | *
156 | * @param filename The file name to use
157 | */
158 | addFile(filename: string): void;
159 |
160 | /**
161 | * Check for errors in the model using `--model-check-only`.
162 | *
163 | * @example
164 | * ```js
165 | * const errors = model.check({
166 | * options: {
167 | * solver: 'gecode'
168 | * }
169 | * });
170 | * for (const error of errors) {
171 | * console.log(error.what, error.message);
172 | * }
173 | * ```
174 | *
175 | * @param config Configuration options
176 | * @returns The errors in the model
177 | */
178 | check(config: {
179 | /** Options to pass to MiniZinc in parameter configuration file format */
180 | options?: ParamConfig;
181 | }): Promise;
182 |
183 | /**
184 | * Get the model interface using `--model-interface-only`.
185 | *
186 | * @example
187 | * ```js
188 | * model.interface({
189 | * options: {
190 | * solver: 'gecode'
191 | * }
192 | * }).then(console.log);
193 | * ```
194 | *
195 | * @param config Configuration options
196 | * @returns The model interface
197 | */
198 | interface(config: {
199 | /** Options to pass to MiniZinc in parameter configuration file format */
200 | options?: ParamConfig;
201 | }): Promise;
202 |
203 | /**
204 | * Compile this model to FlatZinc.
205 | *
206 | * @example
207 | * ```js
208 | * const compile = model.compile({
209 | * options: {
210 | * solver: 'gecode',
211 | * statistics: true
212 | * }
213 | * });
214 | *
215 | * // Print compilation statistics when received
216 | * compile.on('statistics', e => console.log(e.statistics));
217 | *
218 | * // Wait for completion
219 | * compile.then(console.log);
220 | * ```
221 | *
222 | * @param config Configuration options
223 | */
224 | compile(config: {
225 | /** Options to pass to MiniZinc in parameter configuration file format */
226 | options?: ParamConfig;
227 | }): CompilationProgress;
228 |
229 | /**
230 | * Solve this model and retrieve the result.
231 | *
232 | * @example
233 | * ```js
234 | * // Begin solving
235 | * const solve = model.solve({
236 | * options: {
237 | * solver: 'gecode',
238 | * 'all-solutions': true
239 | * }
240 | * });
241 | *
242 | * // Print each solution as it is produced
243 | * solve.on('solution', e => console.log(e.output));
244 | *
245 | * // Wait for completion
246 | * solve.then(result => {
247 | * console.log(result.status);
248 | * });
249 | * ```
250 | *
251 | * @param config Configuration options
252 | */
253 | solve(config: {
254 | /** Whether to use `--output-mode json` (`true` by default) */
255 | jsonOutput?: boolean;
256 | /** Options to pass to MiniZinc in parameter configuration file format */
257 | options: ParamConfig;
258 | }): SolveProgress;
259 | }
260 |
261 | /**
262 | * Initialises MiniZinc.
263 | *
264 | * Calling this function is generally optional, but may be required if the
265 | * library is unable to automatically find the `minizinc-worker.js` script in
266 | * the browser, or the MiniZinc executable on NodeJS.
267 | *
268 | * @example
269 | * ```js
270 | * // In the browser
271 | * MiniZinc.init({ workerURL: 'https://localhost:3000/minizinc-worker.js'} );
272 | * // In NodeJS
273 | * MiniZinc.init({ minizinc: '/path/to/minizinc' });
274 | * ```
275 | *
276 | * It may also be useful to call in the browser to get a promise which resolves
277 | * when the WebAssembly module has been loaded.
278 | *
279 | * @example
280 | * ```js
281 | * MiniZinc.init().then(() => {
282 | * console.log('Ready to start solving');
283 | * });
284 | * ```
285 | *
286 | * @param config Configuration options for initialising MiniZinc
287 | */
288 | export function init(
289 | config?: BrowserInitConfig | NodeInitConfig
290 | ): Promise;
291 |
292 | /**
293 | * Configuration options for initialising MiniZinc in the browser
294 | */
295 | export interface BrowserInitConfig {
296 | /** URL of the worker script */
297 | workerURL?: string | URL;
298 | /** URL of the minizinc.wasm file */
299 | wasmURL?: string | URL;
300 | /** URL of the minizinc.data file */
301 | dataURL?: string | URL;
302 | /** Size of web worker pool */
303 | numWorkers?: number;
304 | }
305 |
306 | /**
307 | * Configuration options for initialising MiniZinc in NodeJS
308 | */
309 | export interface NodeInitConfig {
310 | /** Name of, or path to the minizinc executable */
311 | minizinc?: string;
312 | /** Paths to search for the MiniZinc executable in */
313 | minizincPaths?: string[];
314 | }
315 |
316 | /**
317 | * Get the version of MiniZinc as returned by `minizinc --version`.
318 | */
319 | export function version(): Promise;
320 |
321 | /**
322 | * Get the list of solver configurations available using `minizinc --solvers-json`.
323 | */
324 | export function solvers(): Promise