Skip to content

feat: Standard Schema wrapper, JSR, pkg.pr.new, docs previews and a playground - #556

Merged
hyanmandian merged 70 commits into
mainfrom
claude/standard-schema
Sep 22, 2026
Merged

hyanmandian merged 70 commits into
mainfrom
claude/standard-schema

Conversation

@hyanmandian

@hyanmandian hyanmandian commented Sep 19, 2026 •

Copy link
Copy Markdown
Member

Merge order

This one goes last. It now carries the rewrite of the documentation from #572 (closed): the getting started and migration pages in plain language, and the utilities reference restructured into short per-utility sections and trimmed to what a caller needs. Every open feature PR (#558 through #570) adds a section to docs/utilities.md and docs/pt-br/utilities.md, so merging this first would make each of them rewrite its docs; merging it last means one rebase here, carrying the new sections into the new format.

What does this PR do?

Items 2 to 7 of the distribution plan, one commit each, plus the plan for item 8. No domain utility, validator or table is added and nothing breaks; the package root still has zero runtime dependencies.

# Commit What
2 feat: add toStandardSchema The validators in Standard Schema form
3 test: draw valid documents from fast-check arbitraries Internal arbitraries replace generate* inside 35 properties; nothing published
7 ci(datasets) The dataset refresh pull request describes what changed
5 ci(preview) pkg.pr.new preview per pull request
4 ci(jsr) jsr.json, dry run in CI, OIDC publication on release
6 docs An examples page: one CPF field in React, Vue, Angular and plain JavaScript, plus a live demo
+ ci(docs) A preview deployment of the docs site per pull request (Vercel)

2. Standard Schema

const cpf = toStandardSchema(isValidCpf, { message: "CPF inválido" });
const cnpj = toStandardSchema(isValidCnpj, { options: { version: 2 } });

One generic wrapper instead of a prebuilt schema per validator: a module with ~40 schemas imports every validator, the dataset-backed ones included (isValidNcm 114 KB, isValidCbo 119 KB), and relies on each consumer's bundler proving every top-level call pure. The wrapper costs the validator handed to it (1.1 KB alone), covers options and future validators, and only wraps; no validation logic is added. The spec's types are copied (@standard-schema/spec, MIT) as type aliases and exported. It never throws. Docs (en, pt-BR) show TanStack Form, Zod (refine), Valibot (check) and react-hook-form's standardSchemaResolver.

3. fast-check: kept internal, not published

The first revision of this PR published 13 arbitraries as subpaths with fast-check as an optional peer dependency. They are gone: a second dependency story to explain, for something nobody asked for. What they were good for stays inside the test suite, where it fixes a real weakness.

35 properties in 22 test files called generateCpf(), generatePhone(type) and the like inside fc.property. Those utilities draw from Math.random(), which the seed fast-check reports on a failure does not control, so a failing document could be neither replayed nor shrunk; several were properties in name only (fc.constant("landline") as the single arbitrary). They now draw the document through fc.gen():

fc.property(fc.gen(), masks, (g, separators) => {
	const cpf = g(cpfs);

The arbitraries (cpfs, cnpjs, cnhs, pisNumbers, voterIds, processosJuridicos, boletos, phones) live in src/_internals/test/, next to the existing arbitraries.ts, in four files because the lint rule caps a file at ten imports. They are built from fast-check primitives plus the check-digit internals; a base that must not be one digit repeated gets its second character offset from the first, so nothing is filtered. The generate* utilities keep their own tests, and CONTRIBUTING states the rule. package.json, the lockfile and the docs have no mention of fast-check outside the test suite.

Refactor, no behavior change: the boleto assembly moves from generateBoleto into two internals shared with the boletos arbitrary, tested with literals checked against isValidBoleto.

7. Dataset refresh

The weekly Update datasets workflow already rebuilds every generated table and opens a pull request, so no monthly one is added. New: scripts/data-summary.ts writes the description (per table, entries added and removed with a sample, and a reminder to check the source when a table shrinks). CONTRIBUTING gains a Datasets section listing the tables with no generator, because their source is a law or regulation: area codes and service prefixes (Anatel), holidays, CNJ órgãos and tribunals, IBAN lengths, IBGE state codes, legal nature categories, CST/CSOSN, professional councils, the CPF região fiscal digits and the voter ID state codes.

5. pkg.pr.new

Preview workflow: builds and runs pkg-pr-new@0.0.88 publish --compact. contents: read only and no token, so it works for forks. The pkg.pr.new GitHub App is installed on the repository, so the job runs on every pull request, with no opt-in variable.

4. JSR

  • jsr.json: name, license, publish list and exports (the root plus one subpath per utility folder, generated by scripts/jsr.ts / npm run build:jsr, stale file fails the Check workflow).
  • No slow types to fix: deno publish --dry-run passes as is, since the lint rules already require explicit return types on the public API.
  • Tests workflow, Deno job: deno publish --dry-run on every pull request.
  • Release workflow: publish-jsr job next to the npm one (renamed from publish to publish-npm so the two read as a pair), OIDC (id-token: write), no stored token, reusing the pinned setup-deno action. It runs on every release, with no opt-in variable.
  • release-please bumps jsr.json's version with package.json (extra-files), so no version is edited by hand.
  • Required before the next release: on jsr.io create the scope brazilian-utils and the package brazilian-utils, and link this repository under the package's Settings -> GitHub Actions. Until that exists the publish-jsr job fails (the npm publish is a separate job and is not affected).

6. Examples

/examples (en and pt-BR): a CPF field that formats as you type and validates once complete, in one tab per framework (React, Angular, Vue, vanilla). Each tab shows one file of docs/snippets/, complete and copyable, and loads a live demo of that same file in a lazy iframe the first time the tab opens: docs/snippets/live/run.js compiles the React, Vue and Angular files in the browser (Babel, the Vue SFC compiler and Angular's JIT compiler from a CDN), the vanilla one runs as is. The mask keeps the caret next to the digit being edited and turns a deleted separator into a deleted digit. Each field fits the form library most projects use, shown under its tab and run in a browser once: react-hook-form (a controlled component that takes the input's props), Angular Reactive Forms (a ControlValueAccessor), VeeValidate (v-model). The demos use the site's own theme: docs/styles.css now holds the docsify theme and the site's styles, linked by every page shell instead of an inline <style> copied into each one. Checked in headless Chromium: eight editing scenarios on each demo, the four tabs in both languages; the React, Angular and Vue files type-check in strict mode.

9. Documentation rewrite (from #572)

The getting started and migration pages rewritten in plain language, the utilities reference restructured into short sections (one sentence, a few bullets, an example, a source line) and trimmed to what a caller needs, CONTRIBUTING.md describing that shape. The toStandardSchema section follows it and sits at the end of the reference.

Docs preview per pull request

vercel.json publishes docs/ as it is (no install, no build) as a Vercel preview deployment for every pull request that touches it, forks included, with the URL posted as a comment. cleanUrls serves /getting-started from getting-started.html, as GitHub Pages does, so the history-mode URLs behave the same. Production stays on GitHub Pages: deployments of main are turned off (git.deploymentEnabled) and every response carries X-Robots-Tag: noindex, so previews never compete with brazilian-utils.com.br. The file validates against Vercel's published schema. The home page still loads the README from main, so a README change is the one thing a preview does not show. To enable: vercel.com -> Add New -> Project -> import brazilian-utils/javascript; Vercel reads the file, nothing else to configure.

8. MCP server and Agent Skill (plan only, nothing implemented)

  • Where: a separate package, @brazilian-utils/mcp, in its own repository (brazilian-utils/mcp). Putting it here means a monorepo (workspaces, two release-please packages, a second publish job) for a package with runtime dependencies (@modelcontextprotocol/sdk, zod), against this repository's zero-dependency rule and single-package release flow. It depends on the published @brazilian-utils/brazilian-utils, so it needs nothing from this repo's internals.
  • Tools (all read-only, thin wrappers, never throwing): validate_document (type: cpf, cnpj, cep, boleto, pix_key, pix_payload, nfe_key, phone, ie, pis, cnh, renavam, voter_id, license_plate, ... plus options), format_document, parse_document, generate_document (flagged as test data), get_document_info (boleto, Pix key and payload, NF-e key, IBAN, certidão), lookup_code (table: cfop, cnae, cbo, ncm, bank, legal_nature, municipality, state, area_code), get_holidays / is_business_day / add_business_days, get_address_by_cep (the only networked one, marked as such), number_to_words / currency_to_words.
  • Agent Skill: a SKILL.md in the same repo teaching when to call which tool and the library's conventions (alphanumeric CNPJ, never-throw, deprecated names), reusing the Context7 rules.

Verification

  • 6131 tests pass with the 100% coverage thresholds; the rewritten properties also run on Bun and Deno.
  • Stryker on every new or refactored source file: 100% (the full run is the Mutation workflow).
  • The final tree is byte-identical to the branch before its history was rewritten into one coherent commit per item (no commit announces a feature the PR does not ship).
  • npm run check, check:duplication, check:unused, check:api (report gains toStandardSchema and its types, 58 lines, additions only), check:lockfile, check:dependencies, check:vex, build (publint, attw), build:llms / build:site / build:jsr without drift, deno publish --dry-run, commitlint, actionlint and zizmor (no findings).
  • Full single-import bundle total: about +150 B (the boleto refactor); toStandardSchema is the only new root export.

One feat commit (toStandardSchema), so release-please will propose 2.5.0.

Checklist

  • My commit/PR title follows Conventional Commits.
  • I added or updated tests covering this change (npm test).
  • I updated the documentation in both docs/utilities.md and docs/pt-br/utilities.md.
  • npm run check passes locally (format, lint, types).
  • I ran npm run build:llms.
  • This change does not introduce a breaking change.
  • This change does not add any runtime or peer dependency.

@coderabbitai

coderabbitai Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: c33c5b73-f227-4c92-8e79-364a3ac48c3c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (2b2c735) to head (d9f8e92).
⚠️ Report is 74 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #556   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          183       186    +3     
  Lines         2069      2069           
  Branches       612       613    +1     
=========================================
  Hits          2069      2069           
Flag Coverage Δ
node 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Tree-shaking report

✅ No size regression. 1 grew, 1 new out of 156 exports.

Base Head Δ
Pre-existing exports, all imported 648.9 KB 649.1 KB (gzip 166.3 KB) +152 B (+0.0%)
Full import 648.9 KB 649.3 KB (gzip 166.4 KB) +417 B (+0.1%)
Exports 155 156 +1

What changed (2)

Export Base Head Δ gzip
🆕 toStandardSchema — 1.1 KB new 716 B
🟡 generateBoleto 2.0 KB 2.1 KB +148 B (+7.4%) 1.2 KB
All exports (156)
Export Base Head Δ gzip
⚪ GetAddressInfoByCepError 966 B 966 B 0 B 600 B
⚪ GetAddressInfoByCepNotFoundError 1.0 KB 1.0 KB 0 B 618 B
⚪ GetAddressInfoByCepServiceError 1.0 KB 1.0 KB 0 B 617 B
⚪ GetAddressInfoByCepValidationError 1.0 KB 1.0 KB 0 B 620 B
⚪ GetCepInfoByAddressError 966 B 966 B 0 B 600 B
⚪ GetCepInfoByAddressNotFoundError 1.0 KB 1.0 KB 0 B 618 B
⚪ GetCepInfoByAddressValidationError 1.0 KB 1.0 KB 0 B 620 B
⚪ addBusinessDays 6.8 KB 6.8 KB 0 B 2.8 KB
⚪ capitalize 2.5 KB 2.5 KB 0 B 1.3 KB
⚪ convertCurrencyToWords 2.8 KB 2.8 KB 0 B 1.5 KB
⚪ convertDateToWords 3.2 KB 3.2 KB 0 B 1.7 KB
⚪ convertLicensePlateToMercosul 1.3 KB 1.3 KB 0 B 807 B
⚪ convertNumberToWords 2.4 KB 2.4 KB 0 B 1.3 KB
⚪ differenceInBusinessDays 6.9 KB 6.9 KB 0 B 2.9 KB
⚪ formatBoleto 1.4 KB 1.4 KB 0 B 837 B
⚪ formatCEP 1.2 KB 1.2 KB 0 B 778 B
⚪ formatCNPJ 1.4 KB 1.4 KB 0 B 854 B
⚪ formatCPF 1.3 KB 1.3 KB 0 B 806 B
⚪ formatCaepf 1.3 KB 1.3 KB 0 B 787 B
⚪ formatCei 1.3 KB 1.3 KB 0 B 785 B
⚪ formatCep 1.2 KB 1.2 KB 0 B 778 B
⚪ formatCertidao 1.3 KB 1.3 KB 0 B 789 B
⚪ formatCnae 1.2 KB 1.2 KB 0 B 782 B
⚪ formatCnh 1.3 KB 1.3 KB 0 B 780 B
⚪ formatCno 1.3 KB 1.3 KB 0 B 786 B
⚪ formatCnpj 1.4 KB 1.4 KB 0 B 854 B
⚪ formatCns 1.3 KB 1.3 KB 0 B 780 B
⚪ formatCpf 1.3 KB 1.3 KB 0 B 806 B
⚪ formatCurrency 1.8 KB 1.8 KB 0 B 1.0 KB
⚪ formatIban 1.1 KB 1.1 KB 0 B 696 B
⚪ formatLegalNature 1.2 KB 1.2 KB 0 B 777 B
⚪ formatLicensePlate 1.2 KB 1.2 KB 0 B 738 B
⚪ formatNcm 1.2 KB 1.2 KB 0 B 780 B
⚪ formatNfeKey 1.3 KB 1.3 KB 0 B 784 B
⚪ formatPassport 1.0 KB 1.0 KB 0 B 643 B
⚪ formatPhone 2.8 KB 2.8 KB 0 B 1.5 KB
⚪ formatPis 1.3 KB 1.3 KB 0 B 781 B
⚪ formatProcessoJuridico 1.3 KB 1.3 KB 0 B 785 B
⚪ formatVoterId 1.3 KB 1.3 KB 0 B 821 B
🟡 generateBoleto 2.0 KB 2.1 KB +148 B (+7.4%) 1.2 KB
⚪ generateCNPJ 1.6 KB 1.6 KB 0 B 965 B
⚪ generateCPF 1.4 KB 1.4 KB 0 B 878 B
⚪ generateCep 984 B 984 B 0 B 610 B
⚪ generateCnh 1.4 KB 1.4 KB 0 B 828 B
⚪ generateCnpj 1.6 KB 1.6 KB 0 B 965 B
⚪ generateCpf 1.4 KB 1.4 KB 0 B 878 B
⚪ generateLegalNature 5.9 KB 5.9 KB 0 B 2.1 KB
⚪ generateLicensePlate 1.1 KB 1.1 KB 0 B 692 B
⚪ generatePassport 1.1 KB 1.1 KB 0 B 656 B
⚪ generatePhone 1.5 KB 1.5 KB 0 B 900 B
⚪ generatePis 1.2 KB 1.2 KB 0 B 744 B
⚪ generatePixPayload 6.3 KB 6.3 KB 0 B 2.8 KB
⚪ generateProcessoJuridico 1.4 KB 1.4 KB 0 B 870 B
⚪ generateRenavam 1.2 KB 1.2 KB 0 B 760 B
⚪ generateVoterId 1.7 KB 1.7 KB 0 B 1021 B
⚪ getAddressInfoByCep 4.1 KB 4.1 KB 0 B 1.9 KB
⚪ getAreaCodeInfo 3.9 KB 3.9 KB 0 B 1.4 KB
⚪ getAreaCodesByState 1.6 KB 1.6 KB 0 B 917 B
⚪ getBankByCode 38.6 KB 38.6 KB 0 B 9.8 KB
⚪ getBankByIspb 38.6 KB 38.6 KB 0 B 9.8 KB
⚪ getBanks 38.4 KB 38.4 KB 0 B 9.6 KB
⚪ getBoletoInfo 3.1 KB 3.1 KB 0 B 1.6 KB
⚪ getCbo 119.1 KB 119.1 KB 0 B 30.7 KB
⚪ getCepInfoByAddress 2.7 KB 2.7 KB 0 B 1.4 KB
⚪ getCertidaoInfo 1.8 KB 1.8 KB 0 B 1.0 KB
⚪ getCfop 68.9 KB 68.9 KB 0 B 6.9 KB
⚪ getCities 154.3 KB 154.3 KB 0 B 49.9 KB
⚪ getCnae 93.9 KB 93.9 KB 0 B 21.2 KB
⚪ getFormatLicensePlate 1.1 KB 1.1 KB 0 B 692 B
⚪ getHolidays 6.1 KB 6.1 KB 0 B 2.6 KB
⚪ getIbanInfo 1.6 KB 1.6 KB 0 B 955 B
⚪ getLegalNature 6.3 KB 6.3 KB 0 B 2.3 KB
⚪ getLegalNatures 5.9 KB 5.9 KB 0 B 2.1 KB
⚪ getLegalNaturesByCategory 6.5 KB 6.5 KB 0 B 2.4 KB
⚪ getMunicipalities 156.4 KB 156.4 KB 0 B 50.3 KB
⚪ getMunicipality 154.9 KB 154.9 KB 0 B 50.3 KB
⚪ getMunicipalityByCode 156.5 KB 156.5 KB 0 B 50.4 KB
⚪ getNfeKeyInfo 2.7 KB 2.7 KB 0 B 1.5 KB
⚪ getPixKeyInfo 4.5 KB 4.5 KB 0 B 2.0 KB
⚪ getPixPayloadInfo 2.9 KB 2.9 KB 0 B 1.4 KB
⚪ getStateByIbgeCode 3.2 KB 3.2 KB 0 B 1.1 KB
⚪ getStateCodeByName 3.2 KB 3.2 KB 0 B 1.1 KB
⚪ getStateNameByCode 3.1 KB 3.1 KB 0 B 1.0 KB
⚪ getStates 3.0 KB 3.0 KB 0 B 1017 B
⚪ getTimezoneByState 1.6 KB 1.6 KB 0 B 809 B
⚪ isBusinessDay 6.5 KB 6.5 KB 0 B 2.7 KB
⚪ isHoliday 6.4 KB 6.4 KB 0 B 2.7 KB
⚪ isValidBankAccount 7.4 KB 7.4 KB 0 B 2.8 KB
⚪ isValidBoleto 2.4 KB 2.4 KB 0 B 1.3 KB
⚪ isValidCEP 984 B 984 B 0 B 610 B
⚪ isValidCNPJ 1.6 KB 1.6 KB 0 B 914 B
⚪ isValidCPF 1.3 KB 1.3 KB 0 B 805 B
⚪ isValidCaepf 1.5 KB 1.5 KB 0 B 913 B
⚪ isValidCbo 119.2 KB 119.2 KB 0 B 30.7 KB
⚪ isValidCei 1.5 KB 1.5 KB 0 B 899 B
⚪ isValidCep 984 B 984 B 0 B 610 B
⚪ isValidCertidao 1.6 KB 1.6 KB 0 B 938 B
⚪ isValidCfop 68.9 KB 68.9 KB 0 B 6.9 KB
⚪ isValidCnae 94.0 KB 94.0 KB 0 B 21.2 KB
⚪ isValidCnh 1.4 KB 1.4 KB 0 B 856 B
⚪ isValidCno 1.5 KB 1.5 KB 0 B 901 B
⚪ isValidCnpj 1.6 KB 1.6 KB 0 B 914 B
⚪ isValidCns 1.5 KB 1.5 KB 0 B 925 B
⚪ isValidCpf 1.3 KB 1.3 KB 0 B 805 B
⚪ isValidCreditCard 1.4 KB 1.4 KB 0 B 868 B
⚪ isValidCsosn 1.2 KB 1.2 KB 0 B 737 B
⚪ isValidCst 1.8 KB 1.8 KB 0 B 1.0 KB
⚪ isValidEmail 1.0 KB 1.0 KB 0 B 622 B
⚪ isValidIE 5.7 KB 5.7 KB 0 B 2.1 KB
⚪ isValidIban 1.3 KB 1.3 KB 0 B 836 B
⚪ isValidIe 5.7 KB 5.7 KB 0 B 2.1 KB
⚪ isValidLandlinePhone 1.5 KB 1.5 KB 0 B 933 B
⚪ isValidLegalNature 5.8 KB 5.8 KB 0 B 2.1 KB
⚪ isValidLicensePlate 1.1 KB 1.1 KB 0 B 702 B
⚪ isValidMobilePhone 1.6 KB 1.6 KB 0 B 971 B
⚪ isValidNcm 114.2 KB 114.2 KB 0 B 24.6 KB
⚪ isValidNfeKey 2.7 KB 2.7 KB 0 B 1.5 KB
⚪ isValidPIS 1.2 KB 1.2 KB 0 B 785 B
⚪ isValidPassport 1.0 KB 1.0 KB 0 B 654 B
⚪ isValidPhone 2.6 KB 2.6 KB 0 B 1.3 KB
⚪ isValidPis 1.2 KB 1.2 KB 0 B 785 B
⚪ isValidPixKey 4.6 KB 4.6 KB 0 B 2.1 KB
⚪ isValidPixPayload 2.9 KB 2.9 KB 0 B 1.5 KB
⚪ isValidProcessoJuridico 1.3 KB 1.3 KB 0 B 787 B
⚪ isValidRegistroProfissional 1.6 KB 1.6 KB 0 B 964 B
⚪ isValidRenavam 1.3 KB 1.3 KB 0 B 815 B
⚪ isValidServicePhone 1.5 KB 1.5 KB 0 B 846 B
⚪ isValidVin 1.6 KB 1.6 KB 0 B 995 B
⚪ isValidVoterId 1.6 KB 1.6 KB 0 B 900 B
⚪ parseBoleto 1020 B 1020 B 0 B 634 B
⚪ parseCaepf 1003 B 1003 B 0 B 621 B
⚪ parseCbo 1002 B 1002 B 0 B 620 B
⚪ parseCei 1003 B 1003 B 0 B 619 B
⚪ parseCep 1002 B 1002 B 0 B 620 B
⚪ parseCertidao 1003 B 1003 B 0 B 621 B
⚪ parseCfop 1002 B 1002 B 0 B 620 B
⚪ parseCnae 1002 B 1002 B 0 B 620 B
⚪ parseCnh 1003 B 1003 B 0 B 621 B
⚪ parseCno 1003 B 1003 B 0 B 619 B
⚪ parseCnpj 1.1 KB 1.1 KB 0 B 669 B
⚪ parseCns 1003 B 1003 B 0 B 621 B
⚪ parseCpf 1003 B 1003 B 0 B 621 B
⚪ parseCurrency 1.4 KB 1.4 KB 0 B 881 B
⚪ parseIban 1.0 KB 1.0 KB 0 B 638 B
⚪ parseLegalNature 1002 B 1002 B 0 B 620 B
⚪ parseLicensePlate 1.0 KB 1.0 KB 0 B 638 B
⚪ parseNcm 1002 B 1002 B 0 B 620 B
⚪ parseNfeKey 1.0 KB 1.0 KB 0 B 659 B
⚪ parsePassport 1.0 KB 1.0 KB 0 B 637 B
⚪ parsePhone 1.1 KB 1.1 KB 0 B 707 B
⚪ parsePis 1003 B 1003 B 0 B 621 B
⚪ parseProcessoJuridico 1003 B 1003 B 0 B 621 B
⚪ parseVoterId 1.0 KB 1.0 KB 0 B 650 B
⚪ removeAccents 953 B 953 B 0 B 593 B
⚪ subBusinessDays 6.9 KB 6.9 KB 0 B 2.9 KB
🆕 toStandardSchema — 1.1 KB new 716 B
How this is measured

Every export is imported alone into an esbuild consumer bundle (minified, tree-shaken) built from the head and from the base of this pull request; the sizes are the resulting bundles, gzip is their gzipped size. 🔴 marks a regression: a pre-existing export that grew more than 20% and more than 256 B, or the bundle importing every pre-existing export growing more than 5%. 🟡 is growth under the threshold, 🟢 a decrease, ⚪ no change, 🆕 an export that does not exist on the base (never a regression), 🗑️ an export that was removed. An intentional increase is accepted with the tree-shaking: accepted label.

@hyanmandian hyanmandian changed the title feat: add toStandardSchema, a Standard Schema wrapper for the validators feat: Standard Schema wrapper, fast-check arbitraries, JSR, pkg.pr.new and a playground Sep 19, 2026
`toStandardSchema(isValidCpf, { message, options })` wraps any `isValid*` utility
in a Standard Schema (the `~standard` interface), so the validators plug into
TanStack Form, tRPC, Hono, react-hook-form's standardSchemaResolver and whatever
else accepts the interface, next to Zod, Valibot and ArkType schemas.

The types of the specification are copied into the package as the specification
intends, so there is still no runtime dependency, and they are exported. The
wrapper is generic rather than one prebuilt schema per validator: a module
importing every validator would pull the dataset-backed ones into any bundle
that failed to tree-shake it, while this costs the validator handed to it and
nothing else, and covers options and future validators for free. Like the
validators it never throws. Available from the root and as the
`to-standard-schema` subpath.
…erate*

35 properties in 22 test files called generateCpf(), generatePhone(type) and the
like inside fc.property. Those utilities draw from Math.random(), which the seed
fast-check reports on a failure does not control, so a failing document could be
neither replayed nor shrunk; several were properties in name only
(fc.constant("landline") as the single arbitrary). They now draw the document
with fc.gen(): `const cpf = g(cpfs)`.

The arbitraries live where the project keeps its test arbitraries,
src/_internals/test/: cpfs, cnpjs, cnhs, pisNumbers, voterIds,
processosJuridicos, boletos and phones, built from fast-check primitives plus the
check-digit internals, in four files (the lint rule caps a file at ten imports).
A base that must not be one digit repeated gets its second character offset from
the first, so nothing is filtered. The generate* utilities keep their own tests.

The boleto assembly moves from generateBoleto into two internals shared with the
boletos arbitrary; no behavior change.
The weekly Update datasets workflow already rebuilds every generated table
(Banco Central, CBO, CFOP, IBGE, CNAE, CONCLA, NCM) and opens a pull request;
its description now comes from scripts/data-summary.ts: per table, how many
entries were added and removed, with a sample of each, and a reminder to check
the source when a table shrinks. The title stops naming IBGE/CONCLA only.
A Preview workflow builds the package and publishes it with pkg-pr-new (pinned
to 0.0.88), so a change can be installed from its pull request before it is
merged; the pkg.pr.new GitHub App posts the install command as a comment. The
job needs no token and only contents: read, so it also runs for forks. It stays
off until the app is installed and the PKG_PR_NEW repository variable is true.
jsr.json names the package, its files and its exports: the root plus one subpath
per utility folder, mirrored from the npm subpaths by scripts/jsr.ts (npm run
build:jsr; the Check workflow fails when it is stale). The public API has no slow
types, so nothing had to change in src/.

- Tests workflow, Deno job: `deno publish --dry-run` on every pull request.
- Release workflow: a publish-jsr job publishes through OIDC (id-token: write, no
  token stored) when a release is created, off until the JSR package exists, the
  repository is linked to it and the JSR_PUBLISH variable is true.
- release-please bumps the version in jsr.json together with package.json.
…rowser

A Playground page (en and pt-BR): pick any exported function, edit its arguments
as a JSON array and see the result. playground.js, loaded only on that page,
imports the ESM build of the latest release from jsDelivr (unpkg as fallback), so
there is no build step and the page runs what npm install gives. Arguments go
through JSON.parse, never eval; a Date is written as {"$date": "..."}; async
utilities are awaited and a rejection is shown, not thrown. `?fn=<name>` opens
on a given function.

The UMD build is not used: jsDelivr serves .cjs as application/node with nosniff,
which a browser refuses as a script. The pages join the sidebars, so build:site
gives them their shells and sitemap entries.
@hyanmandian
hyanmandian force-pushed the claude/standard-schema branch from 143df95 to 1de31c7 Compare September 19, 2026 12:47
@hyanmandian hyanmandian changed the title feat: Standard Schema wrapper, fast-check arbitraries, JSR, pkg.pr.new and a playground feat: Standard Schema wrapper, JSR, pkg.pr.new, docs previews and a playground Sep 19, 2026
@hyanmandian
hyanmandian force-pushed the claude/standard-schema branch from 1de31c7 to ab6c6ad Compare September 19, 2026 13:08
…equest

vercel.json publishes docs/ as it is (no install, no build) as a Vercel preview
deployment per pull request, with the URL posted as a comment by the Vercel app,
forks included. cleanUrls serves /getting-started from getting-started.html, as
GitHub Pages does, so the history-mode URLs behave the same. Production stays on
GitHub Pages: deployments of main are turned off, every response carries
X-Robots-Tag: noindex, and a deployment is skipped when nothing under docs/
changed.
With two registries in the workflow, `publish` says too little: the jobs are now
publish-npm and publish-jsr. The JSR job loses its JSR_PUBLISH gate and runs on
every release; it needs the one-time JSR setup documented at the top of the
workflow, and the npm job does not depend on it. The npm trusted publisher is
bound to the workflow file and the environment, not to the job name, so the
rename does not affect it.
@vercel

vercel Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
brazilian-utils Ready Ready Preview Sep 22, 2026 3:58am UTC

@pkg-pr-new

pkg-pr-new Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@brazilian-utils/brazilian-utils@556

commit: d9f8e92

The templates of the document field guide were nineteen files in one folder, named by framework
and part. They are now one folder per framework, so a framework's field, mask and form sit
together. The base field the address guide reuses is generated there too, and is no longer kept in
the repository, like the rest of what the generator writes.
It sat above the tabs on a negative margin, off to one side of them. The picker and the tabs are
the two choices a guide offers, so they now share one strip: the picker first, then the tabs.
An Angular component is an element of its own, so a field's label, input and message were laid out
inside it rather than by the form, and every Angular demo stood out from the React, Vue and vanilla
ones next to it. Each of those components now steps aside with `display: contents`, and the element
the runner mounts React and Vue into does the same, so the four demos of a guide are the same
demo.
In front of the tabs it read as one of them, and a select on a tab bar has nowhere to sit. The
tabs now start the strip and the document closes it, quieter than they are and on a line of its
own when the screen is narrow. Two copies of these rules had also been left behind by a merge.
The demo went in as the first thing of its panel, so on the address and the state and city guides
it landed above the line that says what is about to happen. It now goes between that line and the
code, which is where the document field guide already had it.
The forms logged them, which is out of sight of the demo. They now show what the form holds, the
values without their masks, formatted as JSON.
A select on its own says nothing about what it changes. It now comes with the question it answers,
above the framework tabs, and the line that opens the page no longer repeats it.
A list of field names and a loop over it, to set four values. It now sets them, which is shorter
than the machinery was.
…oking

The effect that fills the form asked the resource for its value without asking whether it had
one. In an error state that throws, which left change detection with an exception to report and
the form saying it was still looking something up. It now checks first.
Its old name said what one schema is for rather than what the guide covers, which is the four
ways the validators reach a schema.
Picking a state fetched 154 KB of cities whether or not anyone was going to pick one, and the
states came with the page. Each list is now fetched when its own select is focused, which is the
first moment it is about to be read, and the cities are tied to the state they were asked for, so
picking another state puts them back to being fetched on demand.
Rewriting these to load on focus left them without the abort the other guides show. A controller
now marks a load as no longer wanted when another state is picked or the component goes, and
whether the cities are loading is read from the state they were asked for, so a load that is
dropped cannot leave its message behind. Angular says where a resource does this by itself.

This branch was successfully deployed

1 active deployment
Preview — d9f8e92c Deployed Sep 22, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant