diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 53f2f08..3109165 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,7 +18,7 @@ concurrency:
env:
PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.playwright
-# DOMQL builds against the Nexo release nexo.release pins, the tree of its tag, taken from a checkout beside its own, formaui-net.Nexo beside formaui-net.DOMQL, until it consumes Nexo's published package, so the job checks out both, Nexo at that tag.
+# DOMQL builds against Nexo's npm package, and its link test reads Nexo's documents at the release's tag from a checkout beside its own, formaui-net.Nexo beside formaui-net.DOMQL, so the job checks out both, Nexo at that tag.
defaults:
run:
working-directory: formaui-net.DOMQL
@@ -36,12 +36,12 @@ jobs:
with:
path: formaui-net.DOMQL
- # The tag of the Nexo release DOMQL builds against, which nexo.release pins.
- - name: Read the pinned Nexo release
+ # The tag of the Nexo release DOMQL builds against, the version of Nexo's package the lock installs.
+ - name: Read the Nexo release
id: nexo
- run: echo "tag=v$(tr -d '[:space:]' < nexo.release)" >> "$GITHUB_OUTPUT"
+ run: echo "tag=v$(jq -r '.packages["node_modules/@formaui-net/nexo"].version' package-lock.json)" >> "$GITHUB_OUTPUT"
- # Nexo's whole history, since DOMQL's documents link to commits a record of the past names, which the link test reads.
+ # Nexo's whole history, since DOMQL's documents link to the release's tag and to commits a record of the past names, which the link test reads.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: FormaUI/Nexo
@@ -90,6 +90,7 @@ jobs:
with:
name: packages
path: |
+ formaui-net.DOMQL/artifacts/*.tgz
formaui-net.DOMQL/artifacts/*.nupkg
formaui-net.DOMQL/artifacts/*.snupkg
if-no-files-found: error
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 82478ee..f7d6459 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,6 +1,6 @@
name: Release
-# A release tag's push: the release check of the FormaUI Versioning release DOMQL follows, then the packages candidate's full gate built, attached to the tag's GitHub release with their digests, and published from there to nuget.org through trusted publishing. A run that fails runs again for the same tag, publishing what the feed lacks. DOMQL publishes no npm package yet.
+# A release tag's push: the release check of the FormaUI Versioning release DOMQL follows, then the packages candidate's full gate built, attached to the tag's GitHub release with their digests, and published from there to nuget.org and npm through trusted publishing. A run that fails runs again for the same tag, publishing what each feed lacks.
on:
push:
tags: ['v*']
@@ -42,6 +42,12 @@ jobs:
packages-artifact: packages
name: DOMQL
+ # Node's npm, 11.5.1 or later, which trusted publishing needs.
+ - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
+ with:
+ node-version: 24
+ registry-url: https://registry.npmjs.org
+
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: nuget/global.json
@@ -64,3 +70,22 @@ jobs:
if ($LASTEXITCODE -ne 0) {
throw 'Publishing to nuget.org failed.'
}
+
+ # The npm package, under the dist-tag of its stage, past a version npm holds already; trusted publishing records its provenance. It depends on Nexo's npm package, which Nexo's own release published.
+ - name: Publish to npm
+ shell: pwsh
+ env:
+ PACKAGES: ${{ steps.packages.outputs.directory }}
+ VERSION: ${{ steps.check.outputs.version }}
+ run: |
+ $held = npm view "@formaui-net/domql@$env:VERSION" version 2>$null
+ if ($held -eq $env:VERSION) {
+ Write-Host "npm holds @formaui-net/domql $env:VERSION already."
+ exit 0
+ }
+
+ $tag = if ($env:VERSION -match '-(preview|rc)\.') { $Matches[1] } else { 'latest' }
+ npm publish (Join-Path $env:PACKAGES "formaui-net-domql-$env:VERSION.tgz") --tag $tag --access public
+ if ($LASTEXITCODE -ne 0) {
+ throw 'Publishing to npm failed.'
+ }
diff --git a/.gitignore b/.gitignore
index b8687e7..4b90e7b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,6 @@
# Dependencies, installed by npm in the repository root and in tests/.
node_modules/
-# Nexo at the release nexo.release pins, or a link to the checkout NEXO_SOURCE names, which build.ps1 prepares; without a trailing slash, so a link is ignored as a folder is.
-/.nexo
-
# The package project's build output and the packed package.
nuget/bin/
nuget/obj/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 66cb772..46d3502 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,10 +1,18 @@
# Changelog
-Each release of DOMQL's packages, newest first. The NuGet package `formaui-net.DOMQL` carries the runtime version, and the npm package `@formaui-net/domql` will carry the same once it is made. An entry names the version of DOMQL's specification the release implements, the Nexo language and definition format it runs on, and what changed in the implementation. What changed in DOMQL's contract is in the [version history](VERSION-HISTORY.md).
+Each release of DOMQL's packages, newest first. The npm package `@formaui-net/domql` and the NuGet package `formaui-net.DOMQL` carry the runtime version. An entry names the version of DOMQL's specification the release implements, the Nexo language and definition format it runs on, and what changed in the implementation. What changed in DOMQL's contract is in the [version history](VERSION-HISTORY.md).
- **An entry is dated when its release is cut,** and reads *Unreleased* until then. [Cutting a release](CONTRIBUTING.md#cut-a-release) tags its commit, and its packages are published from that tag.
- **A change a caller can notice** is listed in the newest entry while it is unreleased. Once it is released, the change opens the next version's entry, and the sources declare that version in the same commit.
+
+
+## `1.0.0-preview.2`
+
+*November 5, 2026 · specification `1.0.0-preview.1` · Nexo language `1.0.0-preview.1` · definition format 38*
+
+- DOMQL is published to npm, as `@formaui-net/domql`: its ES module sources and their declarations, which import Nexo from its own package, `@formaui-net/nexo`, from `1.0.0-preview.1` on. A host that installs both loads one Nexo, and catches Nexo's own `NexoError`. Install it with `npm install @formaui-net/domql@preview`.
+
## `1.0.0-preview.1`
diff --git a/CLAUDE.md b/CLAUDE.md
index f0ebdfa..4e735c3 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -47,7 +47,7 @@ Add a folder when a new responsibility needs one. Keep dependencies in this dire
- `dom/` may depend on Nexo and `snapshots/`.
- `snapshots/` may depend on Nexo.
-The language is Nexo, the package `@formaui-net/nexo`, which DOMQL imports by its name and depends on through `file:.nexo`, until it consumes a published package. `build.ps1` fills `.nexo` with the release `nexo.release` pins, the tree of its tag, taken from a checkout of [FormaUI/Nexo](https://github.com/FormaUI/Nexo) beside this one, named `formaui-net.Nexo`, so a change reaches DOMQL only when `nexo.release` moves to a release that holds it; with `NEXO_SOURCE` naming a checkout, `.nexo` links to that checkout's working tree instead. A change to the language is made there, and a change DOMQL needs of it in both repositories together, with `NEXO_SOURCE` set, moving `nexo.release` once Nexo releases it. Link Nexo's documents at the pinned release's tag, as `https://github.com/FormaUI/Nexo/blob/v1.0.0-preview.1/spec/README.md`, so they move with the pin.
+The language is Nexo, the package `@formaui-net/nexo`, which DOMQL imports by its name and installs from npm, at the release `package.json` declares as the lowest it works with and its lock installs, so a change reaches DOMQL only when that version moves to a release that holds it; with `NEXO_SOURCE` naming a checkout, `build.ps1` links that checkout's working tree in the package's place instead. A change to the language is made there, and a change DOMQL needs of it in both repositories together, with `NEXO_SOURCE` set, moving the version once Nexo releases it. Link Nexo's documents at the release's tag, as `https://github.com/FormaUI/Nexo/blob/v1.0.0-preview.1/spec/README.md`, so they move with it; the link test reads them from a checkout of [FormaUI/Nexo](https://github.com/FormaUI/Nexo) beside this one, named `formaui-net.Nexo`.
Pass the window and document into DOM code; do not read globals there. Importing the language layer and resolving a query must work without a DOM.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 31c0a05..29c45f7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -14,19 +14,19 @@ You'll need:
- PowerShell to run the build scripts.
- The .NET SDK pinned in `nuget/global.json`.
-DOMQL is written in [Nexo](https://github.com/FormaUI/Nexo/blob/v1.0.0-preview.1/spec/README.md), which it builds against at the release `nexo.release` pins, taken from its tag in a checkout of [FormaUI/Nexo](https://github.com/FormaUI/Nexo) beside this one, until it consumes a published package. Clone it beside this repository under the name the build looks for:
+DOMQL is written in [Nexo](https://github.com/FormaUI/Nexo/blob/v1.0.0-preview.1/spec/README.md), which it installs from npm, `@formaui-net/nexo`, at the release `package.json` declares and its lock installs. Its link test reads Nexo's documents at that release's tag from a checkout of [FormaUI/Nexo](https://github.com/FormaUI/Nexo) beside this one, so clone it beside this repository under the name the build looks for:
```sh
git clone https://github.com/FormaUI/Nexo.git formaui-net.Nexo
```
-After cloning the repositories, run the build, which extracts the pinned Nexo into `.nexo`, installs the dependencies and runs the unit tests:
+After cloning the repositories, run the build, which installs the dependencies, Nexo's package among them, and runs the unit tests:
```sh
pwsh ./build.ps1
```
-To work on Nexo and DOMQL together, set `NEXO_SOURCE` to a Nexo checkout, and the build links `.nexo` to its working tree; unset it to build against the pinned release again.
+To work on Nexo and DOMQL together, set `NEXO_SOURCE` to a Nexo checkout, and the build links its working tree in the package's place; unset it to build against the release again. To move to another release, run `npm install @formaui-net/nexo@^`, which declares it as the lowest version DOMQL works with, and move every link into Nexo to its tag in the same commit.
Run the full build from the repository root:
@@ -104,11 +104,11 @@ The language a query is written in is Nexo's, and its contract is Nexo's.
| Version | Declared as | What carries it | Its history |
| --- | --- | --- | --- |
-| The package version | `runtime` | The package, the bundle, the release's tag and `domql.info.versions.runtime` | The [changelog](CHANGELOG.md) |
+| The package version | `runtime` | The packages, the bundle, the release's tag and `domql.info.versions.runtime` | The [changelog](CHANGELOG.md) |
| The specification version | `specification` | DOMQL's contract, the [specification](spec/README.md) | The [version history](VERSION-HISTORY.md) |
-| The language version | `language`, read from the Nexo runtime DOMQL runs on | Nothing of DOMQL's: it names the Nexo language DOMQL runs on, the release `nexo.release` pins | Nexo's |
+| The language version | `language`, read from the Nexo runtime DOMQL runs on | Nothing of DOMQL's: it names the Nexo language DOMQL runs on, the release `package.json` declares | Nexo's |
-**The gates.** The fast gate, `./build.ps1 -Browser`, runs the tests, the tests in a real browser, the bundle, the package project's build with no warnings and the format check. The full gate, `./build.ps1 -Browser -Full`, runs it, then packs the package and its symbols into `artifacts/`, checks each holds exactly the files its list in `scripts/packages/` names, and installs the package outside the repository from a local feed into a web application whose published `domql.js` resolves a query.
+**The gates.** The fast gate, `./build.ps1 -Browser`, runs the tests, the tests in a real browser, the bundle, the package project's build with no warnings and the format check. The full gate, `./build.ps1 -Browser -Full`, runs it, then packs the npm package, the NuGet package and its symbols into `artifacts/`, checks each holds exactly the files its list in `scripts/packages/` names, and installs each outside the repository from a local feed: the npm package into a Node project, with Nexo's package from npm, which resolves a query and type-checks against its declarations, and the NuGet package into a web application whose published `domql.js` resolves a query.
**The workflows:**
@@ -116,7 +116,7 @@ The language a query is written in is Nexo's, and its contract is Nexo's.
| --- | --- | --- |
| `ci.yml` | Every push but `main`'s, and every pull request | The fast gate, and on a push to `candidate` the full gate, which keeps the packages it verified as its `packages` artifact |
| `promotion.yml` | A pull request into `main` | The [promotion check](https://github.com/FormaUI/Versioning/blob/v1.0.0-preview.1/docs/releases.md#promotion) |
-| `release.yml` | A `v*` tag's push, in the `release` environment, which only `v*` tags deploy to | The [release check](https://github.com/FormaUI/Versioning/blob/v1.0.0-preview.1/docs/releases.md#the-release-check), then the full gate's packages attached to the tag's GitHub release with their digests, and the NuGet package published through trusted publishing |
+| `release.yml` | A `v*` tag's push, in the `release` environment, which only `v*` tags deploy to | The [release check](https://github.com/FormaUI/Versioning/blob/v1.0.0-preview.1/docs/releases.md#the-release-check), then the full gate's packages attached to the tag's GitHub release with their digests, and the packages published to nuget.org and npm through trusted publishing |
`dev` and `main` require `ci.yml`'s `Gate`, and `main` the `Promotion` check too.
@@ -124,9 +124,10 @@ The language a query is written in is Nexo's, and its contract is Nexo's.
| Package | What it holds | Its targets | Its feed | Depends on |
| --- | --- | --- | --- | --- |
-| NuGet, `formaui-net.DOMQL` | The bundle, `domql.js`, with Nexo inside it, and its declarations, served at `_content/domql/`, with the README, license and notice, as `scripts/packages/nuget.files` lists them, and its symbols beside it, as `scripts/packages/nuget-symbols.files` lists them | `net11.0` applications | nuget.org | Nothing: it carries the Nexo release `nexo.release` pins inside its bundle |
+| npm, `@formaui-net/domql` | The ES module sources and their declarations, `src/domql.d.ts`, with the README, license and notice, as `scripts/packages/npm.files` lists them | ES2022 JavaScript: browsers, as modules a bundler or an import map resolves | npm, under the dist-tag of its stage, `preview` or `rc`, and `latest` for a stable release | `@formaui-net/nexo`, from the release `package.json` declares |
+| NuGet, `formaui-net.DOMQL` | The bundle, `domql.js`, with Nexo inside it, and its declarations, served at `_content/domql/`, with the README, license and notice, as `scripts/packages/nuget.files` lists them, and its symbols beside it, as `scripts/packages/nuget-symbols.files` lists them | `net11.0` applications | nuget.org | Nothing: it carries the Nexo release `package.json` declares inside its bundle |
-DOMQL publishes no npm package yet: `package.json` is private, and the npm package waits for DOMQL to consume Nexo's published packages, as the status's [packages](docs/features-status.md#packages) set out. A package of DOMQL's that depends on Nexo's names the lowest Nexo version DOMQL works with.
+A package of DOMQL's that depends on Nexo's names the lowest Nexo version DOMQL works with, as `package.json` does.
The [changelog](CHANGELOG.md) records each release, and the [version history](VERSION-HISTORY.md) each specification version, by their own rules.
@@ -136,7 +137,7 @@ The [changelog](CHANGELOG.md) records each release, and the [version history](VE
A release is a candidate that passed its full gate, promoted to `main` and tagged there, as [FormaUI Versioning's releases](https://github.com/FormaUI/Versioning/blob/v1.0.0-preview.1/docs/releases.md) set out. Publishing its package is the step after it.
-1. **Set the release on `dev`.** Pause merges unrelated to it, as [stabilizing a candidate](https://github.com/FormaUI/Versioning/blob/v1.0.0-preview.1/docs/branches.md#stabilizing-a-candidate) sets out. Check that `src/Versions.mjs` declares the release's version and `nexo.release` the Nexo release it builds against, date the changelog's newest entry by the day of the release, and date the version history's newest entry where it reads *Unreleased*.
+1. **Set the release on `dev`.** Pause merges unrelated to it, as [stabilizing a candidate](https://github.com/FormaUI/Versioning/blob/v1.0.0-preview.1/docs/branches.md#stabilizing-a-candidate) sets out. Check that `src/Versions.mjs` declares the release's version and `package.json` the Nexo release it builds against, date the changelog's newest entry by the day of the release, and date the version history's newest entry where it reads *Unreleased*.
2. **Select the candidate.** Fast-forward `candidate` to `dev`'s tip, `git push origin dev:candidate`. `ci.yml` runs the full gate on it. A fix lands on `dev`, and `candidate` moves to the corrected tip.
3. **Promote it.** Open a pull request from `candidate` into `main`, which `promotion.yml` checks. Once the check passes, a release signer fast-forwards `main` to it, `git push origin candidate:main`.
4. **Tag it.** A release signer tags `main`'s commit with an annotated, signed tag, `git tag -s v1.0.0-preview.1 -m "DOMQL 1.0.0-preview.1"`, and pushes it, `git push origin v1.0.0-preview.1`. A pushed tag is never moved or deleted.
diff --git a/README.md b/README.md
index 4c11b18..77e28eb 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
DOMQL is a JavaScript library for querying the DOM, allowing you to read information from a document once or observe it as it changes. Its queries are written in [Nexo](https://github.com/FormaUI/Nexo/blob/v1.0.0-preview.1/spec/README.md).
-> **Preview.** No breaking redesign is planned, and a version that breaks what the one before it allowed says how to migrate. The runtime, `1.0.0-preview.1`, implements DOMQL's specification `1.0.0-preview.1` on Nexo's language `1.0.0-preview.1`, definition version 38. DOMQL declares the DOM as types of its own. It is published to NuGet as `formaui-net.DOMQL`, and not yet to npm; you can also [build and try it locally](#build-and-try-it). See [the status overview](docs/features-status.md), and [what 1.0 requires](docs/features-status.md#what-1-0-requires).
+> **Preview.** No breaking redesign is planned, and a version that breaks what the one before it allowed says how to migrate. The runtime, `1.0.0-preview.2`, implements DOMQL's specification `1.0.0-preview.1` on Nexo's language `1.0.0-preview.1`, definition version 38. DOMQL declares the DOM as types of its own. It is published to npm and NuGet, as [installing it](#install) sets out, and you can also [build and try it locally](#build-and-try-it). See [the status overview](docs/features-status.md), and [what 1.0 requires](docs/features-status.md#what-1-0-requires).
## Features
@@ -20,7 +20,21 @@ DOMQL is a JavaScript library for querying the DOM, allowing you to read informa
- Typed extension modules: members, observations, event sources and executions
- Validation before anything is evaluated
- Definitions as JSON
-- One ES module with TypeScript declarations
+- ES modules with TypeScript declarations, from npm or NuGet
+
+## Install
+
+From npm, as ES modules that import Nexo from its own package, `@formaui-net/nexo`, and that a module imports as `import { domql } from '@formaui-net/domql'`:
+
+```sh
+npm install @formaui-net/domql@preview
+```
+
+From NuGet, as one bundle with Nexo inside it, which a .NET web application serves at `_content/domql/domql.js`, as the [library guide](nuget/README.md#install-and-load) sets out:
+
+```sh
+dotnet add package formaui-net.DOMQL --prerelease
+```
## Your first query
@@ -113,10 +127,10 @@ Clone the repository. You'll need Node.js with npm, PowerShell and the .NET SDK
```powershell
./build.ps1 # The build and the checks
./build.ps1 -Browser # With the real-browser tests
-./build.ps1 -Full # A local NuGet package, in artifacts/, verified as installed
+./build.ps1 -Full # The npm and NuGet packages, in artifacts/, verified as installed
```
-The package supplies the bundle at `_content/domql/domql.js`, with its TypeScript declarations beside it.
+The NuGet package supplies the bundle at `_content/domql/domql.js`, and the npm package the sources, each with its TypeScript declarations.
## License
diff --git a/build.ps1 b/build.ps1
index 6ef9f8a..8a9afc5 100644
--- a/build.ps1
+++ b/build.ps1
@@ -9,10 +9,10 @@ Removes every build output first, so the build starts from nothing.
Also runs the tests that need a real browser's layout, in headless Chromium, and those of what browser engines decide differently in Chromium, Firefox and WebKit, each installed first if it is not.
.PARAMETER Full
-Runs the full gate: the gate, then the NuGet package and its symbols packed into artifacts/ as a release ships them, each checked against the files its list names and installed into a consumer outside the repository, by scripts/Test-Packages.ps1. A release's full gate runs with -Browser too.
+Runs the full gate: the gate, then the npm package, the NuGet package and its symbols packed into artifacts/ as a release ships them, each checked against the files its list names and installed into a consumer outside the repository, by scripts/Test-Packages.ps1. A release's full gate runs with -Browser too.
.NOTES
-DOMQL builds against the Nexo release nexo.release pins, the tree of its tag, which it extracts into .nexo from the checkout beside this one, formaui-net.Nexo, fetching the tag where the checkout lacks it. Set NEXO_SOURCE to a Nexo checkout to build against that checkout's working tree instead, for a change made in both repositories.
+DOMQL builds against Nexo's npm package, at the release package.json declares and the lock installs. Set NEXO_SOURCE to a Nexo checkout to build against that checkout's working tree instead, linked in the package's place, for a change made in both repositories. The link test reads Nexo's documents at the release's tag from Nexo's checkout: the one NEXO_SOURCE names, or formaui-net.Nexo beside this one, fetching the tag where the checkout lacks it.
.EXAMPLE
./build.ps1 -Clean
@@ -38,81 +38,22 @@ $nuget = Join-Path $root 'nuget'
$solution = 'Forma.DOMQL.slnx'
$tests = Join-Path $root 'tests'
$artifacts = Join-Path $root 'artifacts'
-$nexo = Join-Path $root '.nexo'
-$nexoCheckout = Join-Path (Split-Path $root -Parent) 'formaui-net.Nexo'
-$nexoRelease = (Get-Content (Join-Path $root 'nexo.release') -Raw).Trim()
-$nexoTag = "v$nexoRelease"
-
-# Removes .nexo, a link alone where it is one, so the checkout it points to is never touched.
-function Remove-Nexo {
- $item = Get-Item -LiteralPath $nexo -Force -ErrorAction SilentlyContinue
- if ($null -eq $item) {
- return
- }
-
- if ($item.LinkType) {
- $item.Delete()
- }
- else {
- Remove-Item -LiteralPath $nexo -Recurse -Force
- }
-}
-
-# Makes .nexo the Nexo DOMQL builds against: a link to NEXO_SOURCE where it is set, and otherwise the tree of the pinned release's tag.
-function Initialize-Nexo {
- $item = Get-Item -LiteralPath $nexo -Force -ErrorAction SilentlyContinue
-
- if ($env:NEXO_SOURCE) {
- $source = (Resolve-Path -LiteralPath $env:NEXO_SOURCE).Path
- if ($item -and $item.LinkType -and ($item.Target -eq $source)) {
- return
- }
-
- Remove-Nexo
- $linkType = if ($IsWindows) { 'Junction' } else { 'SymbolicLink' }
- New-Item -ItemType $linkType -Path $nexo -Target $source | Out-Null
- Write-Host "Linking Nexo at $source"
- return
- }
-
- # A release's tag never moves, so a tree stamped with it is that release's.
- $stamp = Join-Path $nexo '.release'
- if ($item -and -not $item.LinkType -and (Test-Path $stamp) -and ((Get-Content $stamp -Raw).Trim() -eq $nexoTag)) {
- return
- }
+$nodeModules = Join-Path $root 'node_modules'
+$nexoInstalled = Join-Path $nodeModules '@formaui-net' 'nexo'
+$nexoCheckout = if ($env:NEXO_SOURCE) { (Resolve-Path -LiteralPath $env:NEXO_SOURCE).Path } else { Join-Path (Split-Path $root -Parent) 'formaui-net.Nexo' }
- if (-not (Test-Path (Join-Path $nexoCheckout '.git'))) {
- throw "DOMQL builds against Nexo's release $nexoRelease, the tag $nexoTag of a checkout at $nexoCheckout; clone Nexo there, or set NEXO_SOURCE to a checkout."
- }
-
- git -C $nexoCheckout cat-file -e "refs/tags/$nexoTag^{commit}" 2>$null
- if ($LASTEXITCODE -ne 0) {
- git -C $nexoCheckout fetch origin "refs/tags/${nexoTag}:refs/tags/${nexoTag}"
- if ($LASTEXITCODE -ne 0) {
- throw "Nexo's checkout does not have the tag $nexoTag, and fetching it failed."
- }
- }
-
- Write-Host "Extracting Nexo's release $nexoRelease"
- Remove-Nexo
- New-Item -ItemType Directory -Path $nexo | Out-Null
- $archive = Join-Path ([System.IO.Path]::GetTempPath()) "nexo-$nexoTag.tar"
- try {
- git -C $nexoCheckout archive --format=tar -o $archive "refs/tags/$nexoTag"
- if ($LASTEXITCODE -ne 0) {
- throw "Archiving Nexo's release $nexoRelease failed."
- }
+# The Nexo release DOMQL builds against: the version the lock installs, which package.json declares as the lowest it works with.
+$nexoRelease = (Get-Content (Join-Path $root 'package-lock.json') -Raw | ConvertFrom-Json -AsHashtable).packages['node_modules/@formaui-net/nexo'].version
+$nexoTag = "v$nexoRelease"
- tar -xf $archive -C $nexo
- if ($LASTEXITCODE -ne 0) {
- throw "Extracting Nexo's release $nexoRelease failed."
- }
- }
- finally {
- Remove-Item -LiteralPath $archive -Force -ErrorAction SilentlyContinue
+# Removes node_modules, a link inside it first, so removing it never reaches into the checkout the link leads to.
+function Remove-NodeModules {
+ $link = Get-Item -LiteralPath $nexoInstalled -Force -ErrorAction SilentlyContinue
+ if ($link -and $link.LinkType) {
+ $link.Delete()
}
- Set-Content -LiteralPath $stamp -Value $nexoTag -NoNewline
+ Remove-Item -LiteralPath $nodeModules -Recurse -Force
}
if ($Clean) {
@@ -124,19 +65,22 @@ if ($Clean) {
}
}
-Initialize-Nexo
-
-# The root's link to Nexo must lead to .nexo; one made before the pin leads to the checkout itself.
-$linked = Get-Item -LiteralPath (Join-Path $root 'node_modules/@formaui-net/nexo') -Force -ErrorAction SilentlyContinue
-# npm links with a relative target on Linux and macOS and an absolute one on Windows, so a target resolves against the link's own folder.
-$linkedToNexo = $linked -and $linked.LinkType -and ([System.IO.Path]::GetFullPath($linked.Target, (Split-Path $linked.FullName -Parent)) -eq [System.IO.Path]::GetFullPath($nexo))
-if (-not $linkedToNexo -and (Test-Path (Join-Path $root 'node_modules'))) {
- # The link alone first, so removing node_modules never reaches into the checkout it leads to.
- if ($linked -and $linked.LinkType) {
- $linked.Delete()
- }
+# Nexo as DOMQL builds against it: the npm package the lock installs, or, with NEXO_SOURCE set, a link in its place to that checkout's working tree. An installation that is neither is made again.
+$installed = Get-Item -LiteralPath $nexoInstalled -Force -ErrorAction SilentlyContinue
+# A link's target is relative on Linux and macOS and absolute on Windows, so it resolves against the link's own folder.
+$linkedToSource = $env:NEXO_SOURCE -and $installed -and $installed.LinkType -and ([System.IO.Path]::GetFullPath($installed.Target, (Split-Path $installed.FullName -Parent)) -eq [System.IO.Path]::GetFullPath($nexoCheckout))
+$installedRelease = $installed -and -not $installed.LinkType -and ((Get-Content (Join-Path $nexoInstalled 'package.json') -Raw | ConvertFrom-Json).version -eq $nexoRelease)
+if ((Test-Path $nodeModules) -and -not $linkedToSource -and -not $installedRelease) {
+ Remove-NodeModules
+}
- Remove-Item -LiteralPath (Join-Path $root 'node_modules') -Recurse -Force
+# The tree, or the link to a checkout, a build kept Nexo in before it installed Nexo's package, which a checkout made then may still hold; a link alone is removed.
+$extracted = Get-Item -LiteralPath (Join-Path $root '.nexo') -Force -ErrorAction SilentlyContinue
+if ($extracted -and $extracted.LinkType) {
+ $extracted.Delete()
+}
+elseif ($extracted) {
+ Remove-Item -LiteralPath $extracted.FullName -Recurse -Force
}
foreach ($folder in $root, $tests) {
@@ -149,6 +93,26 @@ foreach ($folder in $root, $tests) {
}
}
+if ($env:NEXO_SOURCE -and -not $linkedToSource) {
+ Remove-Item -LiteralPath $nexoInstalled -Recurse -Force
+ $linkType = if ($IsWindows) { 'Junction' } else { 'SymbolicLink' }
+ New-Item -ItemType $linkType -Path $nexoInstalled -Target $nexoCheckout | Out-Null
+ Write-Host "Linking Nexo at $nexoCheckout"
+}
+
+# DOMQL's documents link to Nexo's at the release's tag, which the link test reads from Nexo's checkout.
+if (-not (Test-Path (Join-Path $nexoCheckout '.git'))) {
+ throw "DOMQL's documents link to Nexo's at $nexoTag, which the link test reads from a checkout of Nexo at $nexoCheckout; clone Nexo there, or set NEXO_SOURCE to a checkout."
+}
+
+git -C $nexoCheckout cat-file -e "refs/tags/$nexoTag^{commit}" 2>$null
+if ($LASTEXITCODE -ne 0) {
+ git -C $nexoCheckout fetch origin "refs/tags/${nexoTag}:refs/tags/${nexoTag}"
+ if ($LASTEXITCODE -ne 0) {
+ throw "Nexo's checkout does not have the tag $nexoTag, and fetching it failed."
+ }
+}
+
Write-Host 'Testing the scripts'
npm test --prefix $tests
if ($LASTEXITCODE -ne 0) {
@@ -209,7 +173,7 @@ if (-not $Full) {
return
}
-# The packages a release publishes: packed afresh, by a build that records its source as a release's does.
+# The packages a release publishes: packed afresh, the NuGet package by a build that records its source as a release's does.
if (Test-Path $artifacts) {
Remove-Item $artifacts -Recurse -Force
}
@@ -226,5 +190,11 @@ finally {
Pop-Location
}
+Write-Host 'Packing the npm package'
+npm pack $root --pack-destination $artifacts --loglevel=error
+if ($LASTEXITCODE -ne 0) {
+ throw 'npm pack failed.'
+}
+
$version = [regex]::Match((Get-Content (Join-Path $root 'src' 'Versions.mjs') -Raw), "runtime = '([^']+)'").Groups[1].Value
& (Join-Path $root 'scripts' 'Test-Packages.ps1') -Directory $artifacts -Version $version
diff --git a/docs/decisions.md b/docs/decisions.md
index c30067e..aca5136 100644
--- a/docs/decisions.md
+++ b/docs/decisions.md
@@ -1660,3 +1660,39 @@ Each entry is one of three kinds:
- DOMQL's first release runs on a released Nexo, and Nexo's first preview is its newest release.
- The language changed nothing a query relies on: a body's canonical text changes, and reads back as the same definition.
- **Declined:** Releasing DOMQL on Nexo's `1.0.0-beta.2`, a stage FormaUI Versioning no longer names, beside a DOMQL in its preview.
+
+
+
+## D109 — DOMQL builds against Nexo's npm package
+
+*November 5, 2026*
+
+- **Kind:** Process, approved.
+- **Decision:** DOMQL depends on `@formaui-net/nexo` from npm at `^1.0.0-preview.1`, the lowest version it works with, as [D107](#d107) sets out, and its lock installs that release; a test holds the two to one version. `nexo.release`, and the tree the build extracted into `.nexo` ([D77](#d77)), are gone. CI checks Nexo out at the release's tag, and the link test reads Nexo's documents there, from the checkout's history ([D78](#d78)). `NEXO_SOURCE` links a Nexo checkout's working tree in the package's place, as it linked `.nexo`.
+- **Why:**
+ - Nexo's packages are published from its `1.0.0-preview.1`, so the step D77 left for them can be taken.
+ - DOMQL's npm package declares Nexo's as a dependency, which `file:.nexo` can't be: a consumer has no `.nexo`.
+ - The tests run against the package a consumer installs, as [consuming the language](features-status.md#consuming-nexo) plans, whose integrity the lock records as Nexo's release published it.
+ - The release is named where npm reads it, `package.json` and its lock. `nexo.release` named it a third time, and the lock's entry for `.nexo` had stayed at `1.0.0-beta.1`, unseen.
+- **Declined:**
+ - Keeping `file:.nexo` and rewriting the dependency as the package is packed, so the repository would never state the dependency its package declares.
+ - Keeping `nexo.release` beside the lock, a third file naming one release.
+
+
+
+## D110 — DOMQL publishes its sources to npm
+
+*November 5, 2026*
+
+- **Kind:** Process, approved.
+- **Decision:** `@formaui-net/domql` holds DOMQL's ES module sources and their declarations, `src/domql.d.ts`, which import Nexo from `@formaui-net/nexo`, the dependency it declares at the lowest version DOMQL works with ([D109](#d109)). It carries the runtime version from `1.0.0-preview.2` on, and `release.yml` publishes it under its stage's dist-tag through trusted publishing, as Nexo's is. The bundle, with Nexo inside it, stays the NuGet package's. It replaces what [D107](#d107) declined.
+ - **Verification.** The full gate packs it, checks its files, and installs it into a Node project with Nexo's package from npm, which resolves a query, finds DOMQL's `NexoError` to be Nexo's own, and type-checks against the declarations.
+- **Why:**
+ - The owner's call: DOMQL is on npm as Nexo is, and being on both feeds, tested as consumers install them, is what 1.0 requires.
+ - Nexo's npm package is published, so DOMQL's depends on it rather than carrying the language: a host that installs both loads one Nexo, and catches one `NexoError`.
+ - Sources are what Nexo's npm package ships, and what a bundler or an import map resolves. The bundle is the NuGet package's build output.
+ - `1.0.0-preview.1` is released, and a release's packages are fixed once it's published, as FormaUI Versioning's [publication](https://github.com/FormaUI/Versioning/blob/v1.0.0-preview.1/docs/releases.md#publication) sets out, so the npm package is the next release's.
+- **Declined:**
+ - Shipping the bundle on npm: it carries Nexo inside it, so a host that installs Nexo too would load two, each with its own `NexoError`.
+ - Publishing an npm package of `1.0.0-preview.1` from another commit, which would give a fixed release a package its full gate never built.
+ - Waiting for the NuGet package to depend on Nexo's as well, which changes how a .NET application serves the language, and which [consuming the language](features-status.md#consuming-nexo) plans on its own.
diff --git a/docs/features-status.md b/docs/features-status.md
index 8fc1f52..ba28834 100644
--- a/docs/features-status.md
+++ b/docs/features-status.md
@@ -10,8 +10,8 @@
| Nexo language | `1.0.0-preview.1` | `1.0.0-preview.1` |
| Definition format | 38 | 38 |
-- **The language DOMQL runs on.** DOMQL follows Nexo's language `1.0.0-preview.1`, which Nexo's release `1.0.0-preview.1` carries and `nexo.release` pins, as [D108](decisions.md#d108) records, and its documents link to [Nexo's documents at that tag](https://github.com/FormaUI/Nexo/blob/v1.0.0-preview.1/spec/README.md), moving with the pin. The language was called Lexica before the [migration](#migration-to-nexo).
-- **Packages.** The bundle and type declarations are built, with Nexo bundled inside `domql.js`. The NuGet package carries the runtime version `1.0.0-preview.1`, and `release.yml` publishes it to nuget.org from the release's tag. The npm package is not made yet: `package.json` is private and carries no version.
+- **The language DOMQL runs on.** DOMQL follows Nexo's language `1.0.0-preview.1`, which Nexo's release `1.0.0-preview.1` carries, the version of Nexo's npm package DOMQL installs, as [D108](decisions.md#d108) and [D109](decisions.md#d109) record, and its documents link to [Nexo's documents at that tag](https://github.com/FormaUI/Nexo/blob/v1.0.0-preview.1/spec/README.md), moving with the pin. The language was called Lexica before the [migration](#migration-to-nexo).
+- **Packages.** The bundle and type declarations are built, with Nexo bundled inside `domql.js`. The npm package holds DOMQL's sources, which import Nexo's package, and the NuGet package the bundle. Both carry the runtime version, and `release.yml` publishes each from a release's tag: the NuGet package from `1.0.0-preview.1`, and the npm package from `1.0.0-preview.2`.
- **The C# half** waits for its design, as [D46](decisions.md#d46) records.
The [specification](../spec/README.md) defines the contract, and the [design](domql-design.md) explains the architecture. The [version history](../VERSION-HISTORY.md) records what each version changed, and the [changelog](../CHANGELOG.md) each release of the packages. This file tracks DOMQL's implementation and its remaining work. "Implemented" means built and tested in the gate for the current implementation version; it does not imply that later specification changes are implemented.
@@ -24,8 +24,8 @@ The [specification](../spec/README.md) defines the contract, and the [design](do
| --- | --- |
| [The C# half](#the-c-half) | Waits for its design |
| [Planned capabilities](#planned-capabilities) | The C# half's alone remain, waiting for its design |
-| [Release](#release) | Done: `1.0.0-preview.1`, DOMQL's first release, is released, on Nexo's first preview |
-| [What 1.0 requires](#what-1-0-requires) | Six requirements, one met in part |
+| [Release](#release) | Done: `1.0.0-preview.2`, which adds the npm package, is released |
+| [What 1.0 requires](#what-1-0-requires) | Six requirements, two met |
- **A specification version collects the changes between two releases.** A change to DOMQL's contract joins the [version history](../VERSION-HISTORY.md)'s newest entry while it reads *Unreleased*, and opens the next version's entry once it is released, as [D79](decisions.md#d79) records.
- **A breaking change says how to migrate.** Since the beta, a version's entry in the [version history](../VERSION-HISTORY.md) says how to migrate beside each thing it breaks.
@@ -271,36 +271,34 @@ None remains: T1 to T3 are implemented, as [the features](#the-host) show: an im
## Release
-DOMQL releases as [cutting a release](../CONTRIBUTING.md#cut-a-release) sets out, by FormaUI Versioning's path, as [D107](decisions.md#d107) records. Its first release, `1.0.0-preview.1`, is released, tagged `v1.0.0-preview.1`, and `release.yml` publishes its NuGet package, with Nexo inside its bundle, to nuget.org through trusted publishing. nuget.org's policy names `FormaUI/DOMQL`, `release.yml` and the `release` environment. Consuming Nexo's published packages follows it.
+DOMQL releases as [cutting a release](../CONTRIBUTING.md#cut-a-release) sets out, by FormaUI Versioning's path, as [D107](decisions.md#d107) records. Its first release, `1.0.0-preview.1`, is released, tagged `v1.0.0-preview.1`, and `release.yml` publishes its NuGet package, with Nexo inside its bundle, to nuget.org through trusted publishing. nuget.org's policy names `FormaUI/DOMQL`, `release.yml` and the `release` environment.
+
+Its second, `1.0.0-preview.2`, is released, tagged `v1.0.0-preview.2`, and `release.yml` publishes its npm package beside the NuGet package, as [D110](decisions.md#d110) records. npm's trusted publisher names the same repository, workflow and environment, and allows `npm publish`. A deprecated placeholder, `@formaui-net/domql@0.0.0`, holds npm's `latest` dist-tag until 1.0, as Nexo's does, since npm takes a trusted publisher only for a package it holds.
### Consuming the language
-**Current:** DOMQL depends on `@formaui-net/nexo` through `file:.nexo`, which the build fills with Nexo's release `nexo.release` pins, `1.0.0-preview.1`, the tree of its tag, taken from a checkout beside this one named `formaui-net.Nexo`, or links to the checkout `NEXO_SOURCE` names; CI checks Nexo out at the same tag. It imports the language's entry point alone, and bundles it into `domql.js`. The language's tests that run through DOMQL's entry point are DOMQL's integration tests.
+**Current:** DOMQL depends on Nexo's npm package, `@formaui-net/nexo`, from `1.0.0-preview.1`, the release its lock installs and its tests run against, as [D109](decisions.md#d109) records; with `NEXO_SOURCE` naming a Nexo checkout, the build links that checkout's working tree in the package's place. CI checks Nexo out at the release's tag, whose documents the link test reads. It imports the language's entry point alone, and bundles it into `domql.js`. The language's tests that run through DOMQL's entry point are DOMQL's integration tests.
**Remaining:**
-1. Depend on Nexo's released packages, `@formaui-net/nexo` and `formaui-net.Nexo`, each naming the lowest Nexo version DOMQL works with.
-2. Write the NuGet bundle with `../nexo/nexo.js` as an import, and the npm bundle with `@formaui-net/nexo`, rather than carrying the language inside.
-3. Have the gate check that DOMQL's packages depend on Nexo's and Nexo's on nothing of DOMQL's, and run DOMQL's tests against the packaged Nexo.
+1. Depend on Nexo's NuGet package, `formaui-net.Nexo`, naming the lowest Nexo version DOMQL works with, as the npm dependency does.
+2. Write the NuGet bundle with `../nexo/nexo.js` as an import, rather than carrying the language inside, as the npm package's sources import `@formaui-net/nexo`.
+3. Have the gate check that DOMQL's packages depend on Nexo's and Nexo's on nothing of DOMQL's.
-Nexo's packages are published from its `1.0.0-preview.1`, as its [release](https://github.com/FormaUI/Nexo/blob/v1.0.0-preview.1/docs/features-status.md#release) records, so this work can start.
+Nexo's packages are published from its `1.0.0-preview.1`, as its [release](https://github.com/FormaUI/Nexo/blob/v1.0.0-preview.1/docs/features-status.md#release) records.
### Packages
**Current:**
-- **NuGet:** the full gate, `./build.ps1 -Full`, packs `formaui-net.DOMQL` locally, checks it holds exactly the files `scripts/packages` lists, and installs it into a web application outside the repository, whose published `domql.js` resolves a query. It carries the runtime version `src/Versions.mjs` declares, `1.0.0-preview.1`, and `release.yml` publishes it to nuget.org from each release's tag.
-- **npm:** not made yet. `package.json` is private and carries no version.
-- **The gate** holds the NuGet package, both READMEs, the [changelog](../CHANGELOG.md) and `domql.info.versions` to the versions the sources declare. The sources declare the version due at the next release, so nothing advances before a release is cut, as [D75](decisions.md#d75) and [D79](decisions.md#d79) record.
-
-**Remaining:**
-
-1. Make the npm package, `@formaui-net/domql`, carrying the runtime version as the NuGet package does, its bundle importing `@formaui-net/nexo`, as [consuming the language](#consuming-nexo) sets out, and have `release.yml` publish it beside the NuGet package.
+- **NuGet:** the full gate, `./build.ps1 -Full`, packs `formaui-net.DOMQL` locally, checks it holds exactly the files `scripts/packages` lists, and installs it into a web application outside the repository, whose published `domql.js` resolves a query. It carries the runtime version `src/Versions.mjs` declares, and `release.yml` publishes it to nuget.org from each release's tag, from `1.0.0-preview.1` on.
+- **npm:** the full gate packs `@formaui-net/domql`, checks it holds exactly the files `scripts/packages/npm.files` lists, and installs it into a Node project outside the repository, with Nexo's package from npm. The project resolves a query, finds DOMQL's `NexoError` to be Nexo's own, and type-checks against the declarations. The package holds DOMQL's ES module sources and declarations, which import `@formaui-net/nexo`, so a host that installs both loads one Nexo, as [D110](decisions.md#d110) records. It carries the runtime version, and `release.yml` publishes it to npm from `1.0.0-preview.2` on.
+- **The gate** holds both packages, both READMEs, the [changelog](../CHANGELOG.md) and `domql.info.versions` to the versions the sources declare. The sources declare the version due at the next release, so nothing advances before a release is cut, as [D75](decisions.md#d75) and [D79](decisions.md#d79) record.
-It follows [consuming the language](#consuming-nexo). `release.yml` publishes the NuGet package from each release's tag, as [contributing](../CONTRIBUTING.md#cut-a-release) sets out.
+`release.yml` publishes both packages from each release's tag, as [contributing](../CONTRIBUTING.md#cut-a-release) sets out.
### Preview
@@ -322,7 +320,7 @@ It follows [consuming the language](#consuming-nexo). `release.yml` publishes th
| **Nexo's 1.0.** DOMQL runs on Nexo's 1.0, from its published packages. | DOMQL's contract holds the language its queries are written in, so it promises no more than Nexo does. | Nexo is in its preview, at `1.0.0-preview.1`, as [what Nexo's 1.0 requires](https://github.com/FormaUI/Nexo/blob/v1.0.0-preview.1/docs/features-status.md#what-1-0-requires) sets out. |
| **Contracts settled.** No decision is open, and each [deferred](#deferred) item and [decision not taken](domql-design.md#decisions-not-taken) is shown to land without a break. | What may still change after 1.0 must be able to change compatibly. | Holds: no decision is open, and each [deferred](#deferred) item and [decision not taken](domql-design.md#decisions-not-taken) lands as an addition or within the implementation. A watch's promise to evaluate its whole query each time was the one that would have made evaluating less a break; [what an evaluation gives](../spec/watches.md#watching-what-an-evaluation-gives) now leaves it to DOMQL. |
| **The C# half decided.** It is designed, or decided to follow 1.0 as an addition. | It waits for the owner's design, as [D46](decisions.md#d46) records, and a .NET application may need it to drive DOMQL. | Waits for its design. |
-| **Published.** DOMQL's packages are on npm and NuGet, tested as consumers install them. | A caller pins a release it can install. | In part: the NuGet package is published from `1.0.0-preview.1`, and the npm package is not made yet. |
+| **Published.** DOMQL's packages are on npm and NuGet, tested as consumers install them. | A caller pins a release it can install. | Holds: the NuGet package is published from `1.0.0-preview.1` and the npm package from `1.0.0-preview.2`, each verified as consumers install it before its release. |
| **Used for real.** Forma's own scripts run as DOMQL queries, watches, subscriptions and executions on a published release. | The [planned capabilities](#planned-capabilities) came from reading those scripts, and only running them settles the contract. | Planned. |
| **A release candidate that breaks nothing.** `1.0.0-rc.1` is released once the rest hold, and Forma takes it with no change but its version. `1.0.0` is its contract, unchanged. | It is the last check that nothing still needs to break. | Waits for the rest. |
diff --git a/nexo.release b/nexo.release
deleted file mode 100644
index 5fd7619..0000000
--- a/nexo.release
+++ /dev/null
@@ -1 +0,0 @@
-1.0.0-preview.1
diff --git a/nuget/README.md b/nuget/README.md
index af3f11c..a20c56c 100644
--- a/nuget/README.md
+++ b/nuget/README.md
@@ -4,7 +4,7 @@
DOMQL is a JavaScript library for querying the DOM, allowing you to read information from a document once or observe it as it changes. This guide takes you from your first query to watching results, handling events and extending the library. Its queries are written in [Nexo](https://github.com/FormaUI/Nexo/blob/v1.0.0-preview.1/spec/README.md).
-> **Preview:** no breaking redesign is planned, and a version that breaks what the one before it allowed says how to migrate. `formaui-net.DOMQL` is published to NuGet as a pre-release, which you [install by name](#install-and-load). The package distributes a JavaScript ES module; the API in this guide runs in JavaScript. Every example here runs on this preview. The runtime, `1.0.0-preview.1`, implements DOMQL's specification `1.0.0-preview.1` on Nexo's language `1.0.0-preview.1`, definition version 38. [The status overview](https://github.com/FormaUI/DOMQL/blob/main/docs/features-status.md) says what is implemented and what is being built.
+> **Preview:** no breaking redesign is planned, and a version that breaks what the one before it allowed says how to migrate. `formaui-net.DOMQL` is published to NuGet as a pre-release, which you [install by name](#install-and-load). The package distributes a JavaScript ES module; the API in this guide runs in JavaScript. Every example here runs on this preview. The runtime, `1.0.0-preview.2`, implements DOMQL's specification `1.0.0-preview.1` on Nexo's language `1.0.0-preview.1`, definition version 38. [The status overview](https://github.com/FormaUI/DOMQL/blob/main/docs/features-status.md) says what is implemented and what is being built.
## In this guide
diff --git a/package-lock.json b/package-lock.json
index 4846552..1455068 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,25 +1,20 @@
{
"name": "@formaui-net/domql",
+ "version": "1.0.0-preview.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@formaui-net/domql",
+ "version": "1.0.0-preview.2",
+ "license": "Apache-2.0",
"dependencies": {
- "@formaui-net/nexo": "file:.nexo"
+ "@formaui-net/nexo": "^1.0.0-preview.1"
},
"devDependencies": {
"esbuild": "^0.25.0"
}
},
- ".nexo": {
- "name": "@formaui-net/nexo",
- "version": "1.0.0-beta.1",
- "license": "Apache-2.0",
- "devDependencies": {
- "esbuild": "^0.25.0"
- }
- },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
@@ -463,8 +458,10 @@
}
},
"node_modules/@formaui-net/nexo": {
- "resolved": ".nexo",
- "link": true
+ "version": "1.0.0-preview.1",
+ "resolved": "https://registry.npmjs.org/@formaui-net/nexo/-/nexo-1.0.0-preview.1.tgz",
+ "integrity": "sha512-713+idtSy2efEFqAguoJX6hE4G5OJ0vxSmETqngu9571ruQcuVGZTW6YXAsRkHT7homK1zqB2JPSBD1jth0PbQ==",
+ "license": "Apache-2.0"
},
"node_modules/esbuild": {
"version": "0.25.12",
diff --git a/package.json b/package.json
index e56ccf3..66c68c2 100644
--- a/package.json
+++ b/package.json
@@ -1,14 +1,37 @@
{
"name": "@formaui-net/domql",
- "private": true,
+ "version": "1.0.0-preview.2",
+ "description": "DOMQL is a JavaScript library for querying the DOM, allowing you to read information from a document once or observe it as it changes.",
+ "license": "Apache-2.0",
+ "author": "Eyal Alon",
+ "homepage": "https://github.com/FormaUI/DOMQL#readme",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/FormaUI/DOMQL.git"
+ },
"type": "module",
+ "exports": {
+ ".": {
+ "types": "./src/domql.d.ts",
+ "default": "./src/domql.js"
+ }
+ },
+ "files": [
+ "src",
+ "LICENSE",
+ "NOTICE",
+ "README.md"
+ ],
+ "publishConfig": {
+ "access": "public"
+ },
"scripts": {
"bundle": "node scripts/bundle.mjs"
},
+ "dependencies": {
+ "@formaui-net/nexo": "^1.0.0-preview.1"
+ },
"devDependencies": {
"esbuild": "^0.25.0"
- },
- "dependencies": {
- "@formaui-net/nexo": "file:.nexo"
}
}
diff --git a/scripts/Test-Packages.ps1 b/scripts/Test-Packages.ps1
index d688ae1..0a8825a 100644
--- a/scripts/Test-Packages.ps1
+++ b/scripts/Test-Packages.ps1
@@ -1,9 +1,12 @@
<#
.SYNOPSIS
-Verifies DOMQL's package as consumers receive it.
+Verifies DOMQL's packages as consumers receive them.
.DESCRIPTION
-Checks that the NuGet package and its symbols hold exactly the files their lists in scripts/packages name. Then it installs the package into a web application outside the repository, from a local feed holding the package and nuget.org alone, publishes the application, and has its published domql.js resolve a query.
+Checks that each package in a directory holds exactly the files its list in scripts/packages names. Then it installs each into a consumer outside the repository, from a local feed holding the packages and the public registries alone:
+
+- the npm package into a Node project, with Nexo's package from npm, which imports it by its name, resolves a query, and type-checks against its declarations;
+- the NuGet package into a web application, which publishes it, and whose published domql.js resolves a query.
.PARAMETER Directory
The directory holding the packages, and nothing else.
@@ -29,14 +32,36 @@ $Directory = (Resolve-Path $Directory).Path
# Each package the release holds, by its file name, with the list of the files it holds.
$expected = [ordered]@{
+ "formaui-net-domql-$Version.tgz" = 'npm.files'
"formaui-net.DOMQL.$Version.nupkg" = 'nuget.files'
"formaui-net.DOMQL.$Version.snupkg" = 'nuget-symbols.files'
}
-# The paths a NuGet package holds, as its archive holds them.
+# The paths a package holds: an npm tarball's under its package folder, a NuGet package's as its archive holds them.
function Get-PackageFile {
param([Parameter(Mandatory)] [string] $Path)
+ if ($Path.EndsWith('.tgz')) {
+ $stream = [System.IO.File]::OpenRead($Path)
+ $gzip = [System.IO.Compression.GZipStream]::new($stream, [System.IO.Compression.CompressionMode]::Decompress)
+ $tar = [System.Formats.Tar.TarReader]::new($gzip)
+
+ try {
+ while ($entry = $tar.GetNextEntry()) {
+ if ($entry.EntryType -ne [System.Formats.Tar.TarEntryType]::Directory) {
+ $entry.Name -replace '^package/', ''
+ }
+ }
+ }
+ finally {
+ $tar.Dispose()
+ $gzip.Dispose()
+ $stream.Dispose()
+ }
+
+ return
+ }
+
$archive = [System.IO.Compression.ZipFile]::OpenRead($Path)
try {
@@ -82,7 +107,24 @@ $work = Join-Path ([System.IO.Path]::GetTempPath()) "domql-packages-$([guid]::Ne
$previousPackages = $env:NUGET_PACKAGES
try {
- # The NuGet package, restored into a web application from the local feed and nuget.org into packages of its own, so no cached copy of the same version stands in for it. The consumer takes its own files alone, whatever an IDE built beside them.
+ # The npm package, installed into a Node project beside the TypeScript the tests compile with, its dependency on Nexo's package from the public registry. Each consumer takes its own files alone, whatever an IDE built beside them.
+ Write-Host 'Installing the npm package into a consumer'
+ $npmConsumer = New-Item -ItemType Directory -Path (Join-Path $work 'npm')
+ Get-ChildItem -LiteralPath (Join-Path $packages 'npm-consumer') -File | Copy-Item -Destination $npmConsumer
+ $typescript = (Get-Content -LiteralPath (Join-Path $root 'tests' 'package-lock.json') -Raw | ConvertFrom-Json -AsHashtable).packages['node_modules/typescript'].version
+
+ Push-Location $npmConsumer
+ try {
+ Invoke-Checked 'Installing the npm package' { npm install (Join-Path $Directory "formaui-net-domql-$Version.tgz") "typescript@$typescript" --no-audit --no-fund --loglevel=error }
+ Invoke-Checked 'Resolving with the npm package' { node consumer.mjs $Version }
+ Invoke-Checked 'Type-checking against the npm package' { npx --no-install tsc -p . }
+ Write-Host " @formaui-net/domql $Version type-checks against its declarations"
+ }
+ finally {
+ Pop-Location
+ }
+
+ # The NuGet package, restored into a web application from the local feed and nuget.org into packages of its own, so no cached copy of the same version stands in for it.
Write-Host 'Installing the NuGet package into a consumer'
$consumer = New-Item -ItemType Directory -Path (Join-Path $work 'nuget')
Get-ChildItem -LiteralPath (Join-Path $packages 'nuget-consumer') -File | Copy-Item -Destination $consumer
diff --git a/scripts/packages/npm-consumer/consumer.mjs b/scripts/packages/npm-consumer/consumer.mjs
new file mode 100644
index 0000000..6215e94
--- /dev/null
+++ b/scripts/packages/npm-consumer/consumer.mjs
@@ -0,0 +1,18 @@
+// An application that installed @formaui-net/domql from its feed, and Nexo's package from npm with it: it imports DOMQL by its name and resolves a query that reads the DOM, as the package's version; resolving needs no DOM. A failure is Nexo's own NexoError, since DOMQL imports Nexo's package rather than carrying a copy.
+import { domql, NexoError } from '@formaui-net/domql';
+import { NexoError as NexoPackageError } from '@formaui-net/nexo';
+
+const [version] = process.argv.slice(2);
+const { type } = domql.query('let inputs = Domql.document.all("input")\n\nreturn { total = inputs.count, checked = inputs.where(it.checked).count }').resolve();
+
+if (domql.info.versions.runtime !== version || type !== '{ total: Number, checked: Number }') {
+ console.error(`The installed package answered ${domql.info.versions.runtime} and the type ${type}, where ${version} and { total: Number, checked: Number } were expected.`);
+ process.exit(1);
+}
+
+if (NexoError !== NexoPackageError) {
+ console.error('The installed package exports a NexoError other than the one Nexo\'s package exports.');
+ process.exit(1);
+}
+
+console.log(`@formaui-net/domql ${version} resolved a query, installed from its feed with Nexo's package.`);
diff --git a/scripts/packages/npm-consumer/consumer.ts b/scripts/packages/npm-consumer/consumer.ts
new file mode 100644
index 0000000..a421594
--- /dev/null
+++ b/scripts/packages/npm-consumer/consumer.ts
@@ -0,0 +1,25 @@
+// A TypeScript application that installed @formaui-net/domql from its feed: it type-checks against the declarations the package holds.
+import { domql, NexoError } from '@formaui-net/domql';
+import type { DomqlQuery, DomqlResolvedDefinition } from '@formaui-net/domql';
+
+export const countChildren = (panel: Element): number => {
+ const query: DomqlQuery = domql.query('panel.children.count', { panel });
+
+ return query.evaluate();
+};
+
+export const typeOf = (text: string): string | null => {
+ try {
+ const resolved: DomqlResolvedDefinition = domql.query(text).resolve();
+
+ return resolved.type;
+ } catch (error) {
+ if (error instanceof NexoError) {
+ return `${error.code}: ${error.message}`;
+ }
+
+ throw error;
+ }
+};
+
+export const runtime: string = domql.info.versions.runtime;
diff --git a/scripts/packages/npm-consumer/package.json b/scripts/packages/npm-consumer/package.json
new file mode 100644
index 0000000..7bab6bc
--- /dev/null
+++ b/scripts/packages/npm-consumer/package.json
@@ -0,0 +1,5 @@
+{
+ "name": "domql-npm-consumer",
+ "private": true,
+ "type": "module"
+}
diff --git a/scripts/packages/npm-consumer/tsconfig.json b/scripts/packages/npm-consumer/tsconfig.json
new file mode 100644
index 0000000..5f163aa
--- /dev/null
+++ b/scripts/packages/npm-consumer/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "target": "es2022",
+ "module": "nodenext",
+ "moduleResolution": "nodenext",
+ "strict": true,
+ "noEmit": true
+ },
+ "files": ["consumer.ts"]
+}
diff --git a/scripts/packages/npm.files b/scripts/packages/npm.files
new file mode 100644
index 0000000..2561e0f
--- /dev/null
+++ b/scripts/packages/npm.files
@@ -0,0 +1,35 @@
+# The files the npm package @formaui-net/domql holds, by their path in the package; build.ps1 -Full checks the package holds exactly these.
+LICENSE
+NOTICE
+README.md
+package.json
+src/Versions.mjs
+src/common/ObjectUtils.mjs
+src/dom/Behaviors.mjs
+src/dom/BrowserExecutionImplementations.mjs
+src/dom/BrowserModule.mjs
+src/dom/BrowserObservationImplementations.mjs
+src/dom/BrowserVocabularyDeclarations.mjs
+src/dom/BrowserVocabularyImplementations.mjs
+src/dom/DispatchTable.mjs
+src/dom/DomqlQuery.mjs
+src/dom/EventDispatcher.mjs
+src/dom/EventListener.mjs
+src/dom/ExecutionHandle.mjs
+src/dom/FocusChain.mjs
+src/dom/FrameSchedule.mjs
+src/dom/MutationObservers.mjs
+src/dom/ObservationSession.mjs
+src/dom/Observations.mjs
+src/dom/OwnedWrites.mjs
+src/dom/QueryEvaluation.mjs
+src/dom/QueryEvaluator.mjs
+src/dom/Selectors.mjs
+src/dom/SessionCache.mjs
+src/dom/Watch.mjs
+src/domql.d.ts
+src/domql.js
+src/snapshots/LiveState.mjs
+src/snapshots/Provenance.mjs
+src/snapshots/SnapshotComparer.mjs
+src/snapshots/SnapshotPatcher.mjs
diff --git a/src/Versions.mjs b/src/Versions.mjs
index 03a624b..532953c 100644
--- a/src/Versions.mjs
+++ b/src/Versions.mjs
@@ -6,7 +6,7 @@ import { nexo } from '@formaui-net/nexo';
export class Versions {
/** The runtime release version, which the package and the bundle carry. */
- static runtime = '1.0.0-preview.1';
+ static runtime = '1.0.0-preview.2';
/** The DOMQL specification version the implementation follows. */
static specification = '1.0.0-preview.1';
diff --git a/tests/Alignment.test.js b/tests/Alignment.test.js
index 340df8d..3e91521 100644
--- a/tests/Alignment.test.js
+++ b/tests/Alignment.test.js
@@ -43,8 +43,17 @@ describe('Alignment', () => {
expect(format).toBe(describedFormat);
});
- it('has a package that carries the runtime version the sources declare, which they report', () => {
+ it('declares Nexo\'s package at the lowest version it works with, the release the lock installs from npm', () => {
+ const declared = JSON.parse(read('package.json')).dependencies['@formaui-net/nexo'];
+ const installed = JSON.parse(read('package-lock.json')).packages['node_modules/@formaui-net/nexo'];
+
+ expect(declared).toBe(`^${installed.version}`);
+ expect(installed.resolved).toBe(`https://registry.npmjs.org/@formaui-net/nexo/-/nexo-${installed.version}.tgz`);
+ });
+
+ it('has packages that carry the runtime version the sources declare, which they report', () => {
expect(project).toContain('src\\Versions.mjs');
+ expect(JSON.parse(read('package.json')).version).toBe(Versions.runtime);
expect(domql.info.versions.runtime).toBe(Versions.runtime);
expect(domql.info.versions.specification).toBe(Versions.specification);
});
diff --git a/tests/DocumentLinks.test.js b/tests/DocumentLinks.test.js
index 01ad849..d43f9c9 100644
--- a/tests/DocumentLinks.test.js
+++ b/tests/DocumentLinks.test.js
@@ -6,14 +6,14 @@ import { describe, it, expect } from 'vitest';
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
-/** The folders that hold no document of DOMQL's: Git's, dependencies, Nexo's copy, build outputs, the browser CI installs, and the notes Git ignores. */
-const SKIPPED = new Set(['.git', '.local', '.nexo', '.playwright', '.vs', '.idea', 'artifacts', 'bin', 'node_modules', 'obj', 'wwwroot']);
+/** The folders that hold no document of DOMQL's: Git's, dependencies, build outputs, the browser CI installs, and the notes Git ignores. */
+const SKIPPED = new Set(['.git', '.local', '.playwright', '.vs', '.idea', 'artifacts', 'bin', 'node_modules', 'obj', 'wwwroot']);
/** The kinds of file a link into Nexo may stand in; a document's own links are read in Markdown alone. */
const LINKING = new Set(['.md', '.js', '.mjs', '.ts', '.ps1', '.yml', '.json']);
-/** The release of Nexo DOMQL builds against, as `nexo.release` pins it, and the tag that names it. */
-const release = readFileSync(resolve(root, 'nexo.release'), 'utf8').trim();
+/** The release of Nexo DOMQL builds against, the version of Nexo's package the lock installs, and the tag that names it. */
+const release = JSON.parse(readFileSync(resolve(root, 'package-lock.json'), 'utf8')).packages['node_modules/@formaui-net/nexo'].version;
const tag = `v${release}`;
/** A link into Nexo's repository at a ref: the ref, the file's path in it, and the anchor in that file, if one is given. */
@@ -76,22 +76,23 @@ const anchorsOf = text => {
return anchors;
};
-/** The Nexo checkout whose history holds the commits a record of the past names: the one `NEXO_SOURCE` names, or the one beside this repository. */
+/** The Nexo checkout whose history holds the release's tag and the commits a record of the past names: the one `NEXO_SOURCE` names, or the one beside this repository. */
const nexoCheckout = process.env.NEXO_SOURCE ? resolve(process.env.NEXO_SOURCE) : resolve(root, '..', 'formaui-net.Nexo');
-/** A file of Nexo's as a ref holds it, or undefined where the ref holds no such file: the pinned release's from `.nexo`, which holds its tree, and a commit's from the checkout's history. */
+/** A file of Nexo's as a ref holds it, from the checkout's history, or undefined where the ref holds no such file; each is read once for the run, since a tag and a commit never change. */
+const nexoFiles = new Map();
const nexoFileAt = (ref, path) => {
- if (ref === tag) {
- const file = resolve(root, '.nexo', path);
+ const key = `${ref}:${path}`;
- return existsSync(file) ? readFileSync(file, 'utf8') : undefined;
+ if (!nexoFiles.has(key)) {
+ try {
+ nexoFiles.set(key, execFileSync('git', ['-C', nexoCheckout, 'show', key], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }));
+ } catch {
+ nexoFiles.set(key, undefined);
+ }
}
- try {
- return execFileSync('git', ['-C', nexoCheckout, 'show', `${ref}:${path}`], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
- } catch {
- return undefined;
- }
+ return nexoFiles.get(key);
};
/** The commits of Nexo's that a record of DOMQL's past links to, each with what it holds; every other link names the pinned release, so it moves when the pin does. */