Skip to content

Packaging decisions ​

decisionchoicereasonwhat would reverse it
statement planningin the client library, in the calling process; executors use the native driverusing the ORM needs only the library; no service or daemon to deploy and operatenone
model generationone generator per language: orm-gen gen --lang go (Go), vendor/bin/orm-gen (PHP), the orm-gen npm bin (TypeScript), the polyspec-orm-build crate in build.rs (Rust), the orm-gen script of the Python package (Python); each takes the document set only as its set file dbspec-set.json (document sets)each language builds with its own tool chain, and one set file declares the set for all of themnone
equality across languagestests/conformance vectors on MySQL, PostgreSQL, and SQLiteThe six configured implementations—Go, PHP, PHP extension, TypeScript, Python, and Rust—share one SQL, bind, and result behavior; completed execution coverage is tracked separatelynone
artifactsorm-gen-<version>-<os>-<arch>, SHA256SUMSthe Go orm-gen runs at build time; the schema operations are client library functions— (the version comes from VERSION; file names carry it, never a "latest" symlink)
distributionGo: module path; PHP: composer package polyspec/orm with bin/orm-gen; TypeScript: npm package @polyspec/orm with the orm-gen bin; Rust: crates polyspec-orm and polyspec-orm-build—a registry publish is a separate decision
configurationDSN and secrets are arguments of the connect call; the schema path is a connection option (Go, PHP, TypeScript), the generated schema is an argument of Db.connect_schema (Python), or it is embedded by the build (Rust)——
driversGo go-sql-driver/mysql, pgx, modernc.org/sqlite; Rust sqlx; PHP pdo_mysql, pdo_pgsql, pdo_sqlite; TypeScript mysql2, pg, node:sqliteperf.md §4a measured 2× win from another driver on the hot path
YAML codecGo github.com/goccy/go-yaml (read, which keeps U+0085 as content) and go.yaml.in/yaml/v3 (write), Python rapidyaml (read) and ruamel.yaml (write), PHP purrphp/yaml (read) and symfony/yaml (write), Rust saphyr-parser (read) and serde-saphyr (write), TypeScript yaml; lock files are committed. The PHP extension currently reaches YAML through PHP library calls; native codec replacement remains open under T182-2.The 80 vectors and invalid-input cases define the shared expectation for Go, PHP, PHP extension, TypeScript, Python, and Rust; this states the expected behavior, not completed native codec coverage.replace a library only when the shared vectors and error cases remain unchanged

Installing from release assets ​

In 0.0.x the polyspec packages are distributed through GitHub only, as release assets and tags: the npm, Composer and Python packages are on no registry (npm, Packagist, PyPI). Each GitHub Release carries polyspec-orm-npm-<version>.tgz, polyspec-orm-php-<version>.zip, polyspec-orm-gen-php-<version>.zip, polyspec-orm-php-ext-php-<version>.zip (<package>-<language>-<version>.<ext>) and the Python wheel polyspec_orm-<version>-py3-none-any.whl (see Python client release). Each asset holds the manifest of the repository tree unchanged, and that manifest names every other polyspec package by the exact version of its release, so a project downloads the release assets it needs and installs them together. make release-assets TAG=vX.Y.Z builds them from the tagged commit.

  • npm: list every tarball as a file: dependency, the orm tarball and the tarball of each polyspec package it depends on, from the GitHub Release of the version that the orm package.json names. npm satisfies the exact version of @polyspec/ordered-json with the tarball installed beside it:

    json
    {
      "dependencies": {
        "@polyspec/orm": "file:vendor/polyspec-orm-npm-<version>.tgz",
        "@polyspec/ordered-json": "file:vendor/polyspec-ordered-json-npm-0.0.9.tgz"
      }
    }
  • Composer: an artifact repository is a directory of the downloaded zips, and Composer reads the name, version and requirements of each zip from its composer.json. The artifact repository holds the orm zip and the zip of each polyspec package that it requires (polyspec-ordered-json-php-0.0.9.zip).

    json
    {
      "require": { "polyspec/orm": "<version>" },
      "repositories": [
        { "type": "artifact", "url": "vendor/polyspec" },
        { "type": "vcs", "url": "https://github.com/min-0-max/yaml", "no-api": true }
      ]
    }

    The client requires purrphp/yaml 1.0.0-p1, the release of the fork https://github.com/min-0-max/yaml that reads plain scalars by the core schema of YAML 1.2.2 (section 10.3.2); the upstream 1.0.0 release does not. Composer reads repositories only from the root manifest of a project, never from a dependency's, so the orm package cannot carry this source: a consumer declares the vcs repository itself, as above. no-api makes Composer clone the public repository over HTTPS instead of asking the GitHub API, whose anonymous rate limit (HTTP 403) would otherwise send it to an SSH clone that needs a key.

    polyspec/orm-php-ext has the type php-ext: PIE builds and installs it, and Composer does not.

Python client release ​

The Python client, packages/python/pyproject.toml (distribution polyspec-orm), is released as a PEP 427 wheel, polyspec_orm-<version>-py3-none-any.whl, one of the assets of its GitHub Release beside the npm and Composer archives. make release-assets TAG=vX.Y.Z builds the wheel from the tagged commit with uv build, which takes the build backend from build-system.requires of the pyproject.toml (setuptools==84.0.0). The wheel keeps the PEP 427 name because it is not an archive of the <package>-<language>-<version>.<ext> form.

A Requires-Dist of the wheel is an exact name==version for a registry package, and a direct reference with the sha256 of the asset for a polyspec package (kit K49): polyspec-ordered-json @ https://github.com/polyspec/ordered-json/releases/download/v0.0.9/polyspec_ordered_json-0.0.9-py3-none-any.whl#sha256=bc4d…f01d. ordered-json is on no package index, and the reference names its release asset, so a pip install without a lock fetches it from GitHub and never asks a package index for that name. The lock of the Python client, packages/python/uv.lock, holds every dependency, transitive ones included, with the hash of each file, and ordered-json by the URL of its release asset with its sha256; make orm-python-lock writes it with uv lock from packages/python/pyproject.toml. make orm-install-python installs the client non-editable from it with uv sync --locked --no-editable --group test, and uv checks each file by the hash of the lock. The wheel supports Python 3.14 and CPython only: requires-python is >=3.14, the minor release of the Python of mise.toml that CI tests, and no older release, since the dependency polyspec-ordered-json==0.0.9 requires 3.14; packages/python/tests/test_requires_python.py compares the two. The toolchain of orm is the CPython of python-build-standalone, and the package declares the classifier Programming Language :: Python :: Implementation :: CPython. Its dependency on psycopg-binary==3.3.6 therefore names the binary build of psycopg directly, with no marker for other implementations.

The binary extra of psycopg is written as the two plain requirements that it expands to, psycopg==3.3.6 and psycopg-binary==3.3.6. The extra excludes PyPy, which a plain requirement cannot express, so psycopg-binary is also required there.

Development layout ​

The released manifests are packages/ts/package.json, packages/php/composer.json and packages/php-ext/composer.json. They take the polyspec packages of other repositories by exact version and declare no repository. Two private root manifests, which are never released, resolve those versions in this repository:

  • package.json at the repository root lists packages/* under workspaces, and its overrides take @polyspec/ordered-json from the tarball URL of its GitHub Release, the URL naming the release tag, and take vite 7 for vitepress, whose latest release depends on vite 5, a release with advisories. npm ci at the root installs the workspace. package-lock.json at the root is the one npm lockfile.
  • composer.json at the repository root installs packages/php from a path repository, polyspec/ordered-json from a package repository whose dist is the zip URL of its GitHub Release with its shasum (the URL names the release tag), purrphp/yaml from the vcs repository of the fork min-0-max/yaml, and the development dependency opis/json-schema 2.6.0-p1 from the vcs repository (no-api) of the fork min-0-max/json-schema, which reports every refusal in its collect-all-errors mode for the PHP vector runners (tests/dbspec/vectors.php); no released manifest requires it, so a consumer declares nothing for it. Its vendor-dir is vendor-php, because vendor at the root is the vendor directory of the Go module, and its config.platform.php is 8.4.0, the lowest PHP of the clients. composer.lock at the root is the one Composer lockfile, and the PHP tests load vendor-php/autoload.php.

Tracked generated sources ​

The error-code sources of the clients are generated by orm-gen errors --lang <lang> from docs/spec/errors.yaml, and they are tracked, not build output: packages/go/orm/codes.go, packages/php/src/Code.php, packages/rust/orm/src/codes.rs, packages/ts/src/codes.ts and packages/python/src/polyspec/orm/codes.py. The reason is that each of them is part of the source that its consumer installs (a Go module, a Composer archive, a cargo package, a TypeScript package whose build ships dist, and the Python sdist and wheel), and none of those installs runs a generator; orm-gen errors is one Go generator for every language, so the TypeScript build does not need the Go toolchain and does not regenerate codes.ts. The five paths are declared exceptions in config/generated.json. Each client's code-catalog test (packages/go/orm/codes_test.go, packages/php/tests/CodeCatalogTest.php, packages/rust/orm/tests/code_catalog.rs, packages/ts/tests/code-catalog.mjs, packages/python/tests/test_code_catalog.py) keeps its file equal to docs/spec/errors.yaml, and the error-codes feature regenerates them to check them.

MIT License · Go / PHP / Rust / TypeScript