Remove the static function metadata API (HF-349) - #1724
Merged
sequba merged 3 commits intoAug 10, 2026
Merged
Conversation
Removes the static `getAvailableFunctions` and `getFunctionDetails`. The function metadata API is instance-scoped only. With license-based entitlement landing (HF-307), the two forms answer different questions: an instance knows its license key and can report what the engine the caller actually holds may run, while a static method has no key to read and can only report what the package contains. Only the instance answer is the one callers need, and the static one invites a function picker to advertise functions that then fail on evaluation. Both methods are unreleased, so the 3.4.0 changelog and release-notes entries are amended rather than accompanied by a removal note. Also removed as dead after the change: - `FunctionRegistry.getListableFunctionIds` (static), whose only caller was the static `getAvailableFunctions` - the `getPlugin` callback threaded through `buildAvailableFunctions`, which existed only to abstract over the two registries; both private builders now take the engine's own `FunctionRegistry` The built-in functions guide page is generated from this API, so `script/generate-builtin-functions-doc.ts` now builds an engine with the `gpl-v3` license key — the fully-entitled key — and reads the instance methods. Naming the key in the script keeps the published reference documenting the complete function set instead of narrowing to whatever key the build environment supplies. The generated page is byte-identical to the one the static path produced (423 rows). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Gft7sLWVr5Hz3iDSBiEPz
✅ Deploy Preview for hyperformula-dev-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Contributor
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
hyperformula-docs | 6fe2aaf | Commit Preview URL Branch Preview URL |
Aug 10 2026, 02:39 PM |
Performance comparison of head (6fe2aaf) vs base (814c3b3) |
Review caught that the LICENSE_KEY comment and the docs/README.md paragraph described entitlement gating as if it already existed: "the engine's entitlement therefore decides which functions reach the page". It does not. Verified by building engines with `gpl-v3`, with a bogus key, and with no key at all: all three report the same 423 functions, and the pre-change generator, which passed no key, produced a byte-identical page. The key's only current effect is the opposite of what was written: building an engine without one logs "The license key for HyperFormula is missing.", noise the static path never produced because it built no engine. Both texts now say that, and keep the forward-looking reason (HF-307 gating will make the key decide, so the fully-entitled one is named now) clearly marked as not yet in effect. Also documented two invariants the change leaves implicit: - the generator must keep using a default-config engine, since the function total on the page is computed separately from the global registry in docs/.vuepress/config.js - an engine's registry is a snapshot taken at build time, so a plugin registered globally afterwards reaches only later engines. The removed static method was the one documented as answering the live global registry; the surviving instance method now carries the caveat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Gft7sLWVr5Hz3iDSBiEPz
sequba
commented
Aug 10, 2026
Comment on lines
+84
to
+90
| That API is instance-scoped, so the generator builds an engine to read it — a default-config one, because the | ||
| function total printed on the page is computed separately, from the global registry, in `docs/.vuepress/config.js`. | ||
| The engine is built with the `gpl-v3` license key. Today that only keeps the build quiet (an engine built without a | ||
| key logs a missing-license warning); function availability is not license-gated. Once entitlement lands, the key will | ||
| decide which functions the metadata API reports, and naming the fully-entitled one here is what keeps the published | ||
| reference documenting every built-in instead of narrowing to a tier. | ||
|
|
Contributor
Author
There was a problem hiding this comment.
remove thir paragraph
Contributor
Author
There was a problem hiding this comment.
Removed in 6fe2aaf — docs/README.md is now identical to release/3.4.0, so this PR no longer touches the file.
The rationale for the generator building an engine with the gpl-v3 key stays in the JSDoc of script/generate-builtin-functions-doc.ts, next to the code it explains.
Generated by Claude Code
sequba
marked this pull request as ready for review
August 10, 2026 14:34
Review request from @sequba. The file is now identical to the base branch again; the rationale for the docs generator building an engine with the gpl-v3 key stays where it belongs, in the JSDoc of script/generate-builtin-functions-doc.ts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Gft7sLWVr5Hz3iDSBiEPz
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## release/3.4.0 #1724 +/- ##
=================================================
- Coverage 97.31% 97.31% -0.01%
=================================================
Files 195 195
Lines 15734 15719 -15
Branches 3456 3455 -1
=================================================
- Hits 15312 15297 -15
Misses 414 414
Partials 8 8
🚀 New features to boost your workflow:
|
12 tasks
This was referenced Aug 20, 2026
marcin-kordas-hoc
added a commit
that referenced
this pull request
Aug 20, 2026
… gate The instance method now lists exactly what the instance can evaluate, through the same listable ids and the same licenseListsFunction rule the metadata API and the interpreter share: protected built-ins included (OFFSET was missing before), the instance's own translation snapshot instead of a fresh global language lookup, and no function the license key does not include. A missing/invalid/expired key does not shorten the list. The static form is REMOVED, finishing what HF-349 started: a static method has no key or config in scope, so it can only ever answer for the package as a whole - Kuba's own rationale on #1724, applied to the one API it missed. The docs build-time function count moves to an unlicensed instance (measured: both spellings count 423 for enGB). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019pxNP45obT2LZfjitaCv9o
marcin-kordas-hoc
added a commit
that referenced
this pull request
Aug 21, 2026
… gate The instance method now lists exactly what the instance can evaluate, through the same listable ids and the same licenseListsFunction rule the metadata API and the interpreter share: protected built-ins included (OFFSET was missing before), the instance's own translation snapshot instead of a fresh global language lookup, and no function the license key does not include. A missing/invalid/expired key does not shorten the list. The static form is REMOVED, finishing what HF-349 started: a static method has no key or config in scope, so it can only ever answer for the package as a whole - Kuba's own rationale on #1724, applied to the one API it missed. The docs build-time function count moves to an unlicensed instance (measured: both spellings count 423 for enGB). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019pxNP45obT2LZfjitaCv9o
marcin-kordas-hoc
added a commit
that referenced
this pull request
Aug 21, 2026
… gate The instance method now lists exactly what the instance can evaluate, through the same listable ids and the same licenseListsFunction rule the metadata API and the interpreter share: protected built-ins included (OFFSET was missing before), the instance's own translation snapshot instead of a fresh global language lookup, and no function the license key does not include. A missing/invalid/expired key does not shorten the list. The static form is REMOVED, finishing what HF-349 started: a static method has no key or config in scope, so it can only ever answer for the package as a whole - Kuba's own rationale on #1724, applied to the one API it missed. The docs build-time function count moves to an unlicensed instance (measured: both spellings count 423 for enGB). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019pxNP45obT2LZfjitaCv9o
marcin-kordas-hoc
added a commit
that referenced
this pull request
Aug 26, 2026
… gate The instance method now lists exactly what the instance can evaluate, through the same listable ids and the same licenseListsFunction rule the metadata API and the interpreter share: protected built-ins included (OFFSET was missing before), the instance's own translation snapshot instead of a fresh global language lookup, and no function the license key does not include. A missing/invalid/expired key does not shorten the list. The static form is DEPRECATED, not removed: a static method has no key or config in scope, so it can only ever answer for the package as a whole - the rationale recorded on #1724, applied to the one API it missed. Whether it is eventually removed is a separate decision and is not taken here; the JSDoc and the changelog say deprecated, and this commit message previously claimed removal, which was never true of the diff. The docs build-time function count moves to an unlicensed instance (measured: both spellings count 423 for enGB). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
marcin-kordas-hoc
added a commit
that referenced
this pull request
Sep 24, 2026
…er shipped The retroactive 3.4.0 entry announced, as a breaking change, the removal of the static getAvailableFunctions() and getFunctionDetails(). Neither was ever released: both arrived in #1692 and left again in #1724, inside the 3.4.0 development cycle, and no tag carries them. The entry told every upgrader about a break that cannot affect them, so it is withdrawn from the changelog, and the copy of it in the guide's release notes goes too. Release notes are written at release time, not during development, so that file is back to develop's. The deprecation note on the static getRegisteredFunctionNames() leaned on the same false history; it now says the metadata methods were instance-only from their first release. The guide's sentence on getRegisteredFunctionNames() was a continuation line of the bullet before it, so it rendered inside the claim it contradicts. It is its own bullet now. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
sequba
added a commit
that referenced
this pull request
Sep 24, 2026
…cate its static form (#1743) > **Update 2026-09-22 — review round with @sequba.** The sections below are the PR as written > before that round, kept for the reasoning they record. Where a sentence no longer matches the > code it is corrected inline and marked, rather than rewritten away. ## Update 2026-09-18 — rebased onto the collapsed engine/tests PRs `hyperformula#1730` and `hyperformula-tests#32` were folded from five PRs each into one, so this PR's base moved to `#1730`. Nothing in this PR's own diff changed; the "9/9" numbering and the `#1741` base reference below are stale — the stack is now `#1728 → #1729 → #1730 → this PR`. --- 9/9 of the HF-307 stack, stacked on #1741. Pairs with `hyperformula-tests#43` — **merge the tests PR first**. Finishes decision **D2** on the one API it missed. ## The problem `getRegisteredFunctionNames()` returned the whole catalogue under a restricted key (measured: 422 names on a calculated-fields key, including functions that evaluate to `#LIC!`). A function picker built on it has exactly the problem #1724 (HF-349) and #1731's metadata filter exist to prevent. Flagged in #1731's "found, not fixed here" section; this closes it. ## What changed **Instance method** — now lists exactly what the instance can evaluate, sharing the one `licenseListsFunction` rule with the interpreter and the metadata API so the three surfaces cannot drift: - reads `getListableFunctionIds()` instead of `getRegisteredFunctionIds()` — the protected built-ins are included uniformly (`OFFSET` was missing before; `getAvailableFunctions` already listed it); - reads `config.translationPackage` — the instance's own snapshot — instead of a fresh global `getLanguage` lookup, which can report a localized name the instance refuses to evaluate, and which throws once the host unregisters that language code; - filters by the license, with the invariant intact: a missing, invalid, or expired key does **not** shorten the list. **Static method — deprecated, not removed.** See below; this is a change from the first version of this PR. ## The static: deprecated rather than removed (changed after review) The first version of this PR deleted `HyperFormula.getRegisteredFunctionNames(code)`, citing HF-349 as precedent. That was wrong, and the review caught it: ``` $ git show 3.4.0:src/HyperFormula.ts | grep -c "public static getRegisteredFunctionNames" # 1 ``` The method **is in the released 3.4.0 tag**, and HF-349's own commit message says its removal was free precisely because *"Both methods are unreleased, which is the only free moment to remove them."* So the precedent does not extend here: deleting it would be a breaking change in a minor release, against the Semantic Versioning this project states it follows, and DEV_DOCS's Definition of Done would require a migration-guide section that `docs/guide/` has no 3.x home for. So it is now `@deprecated` with the wording this repo already uses for that situation (`arraySizeMethod` / `arrayFunction` in 3.1.0: "deprecated and will be removed in one of the next major releases"), plus a `Deprecated` changelog entry. **Overturnable in one comment** if you would rather take the break now — the direction is Kuba's D2 either way; only the timing changed. The deprecation notice is explicit that the two are not interchangeable: the static translates into any registered language without an engine, so migrating means building one (`HyperFormula.buildEmpty({ language: 'plPL' }).getRegisteredFunctionNames()`). ## D2's precondition, discharged Kuba's D2 answer made the removal conditional: *"perhaps we can remove the static methods, I'll check which methods does formula-builder use"*. That check is done — searched the org, and **both consumers call the instance form, not the static one**: | Consumer | Call site | Form | |---|---|---| | `formula-builder` | `packages/core/src/engine/functionCatalog.ts:81` — `this.engine?.getRegisteredFunctionNames?.()`, in try/catch, declared optional in `engine/types.ts:66` | instance | | `aurasheet` | `src/core/FormulaEngine.ts:97` — `this.hf.getRegisteredFunctionNames()` | instance | Both use it to build a **function picker** (`functionCatalog.ts`; `FormulaAutocompletePlugin.ts`) — the surface #1724's rationale was about — so the license filter added here improves both rather than disturbing them. Under `gpl-v3` or any unrestricted key their lists are unchanged. ## Spec-to-ship review (2026-08-20): also fixed here - **The translation-snapshot change was unpinned.** Mutation-verified: reverting it to the global lookup left 309 tests green, even though the commit message and the new JSDoc both name it. Now pinned by a test that unregisters the language after the engine is built — the scenario that distinguishes the two implementations, since `getLanguage` throws for an unregistered code while the snapshot keeps working. - **The licence guide listed only two of the three narrowing methods.** It now names this one too, in both places. - **The new JSDoc over-claimed parity** with `getAvailableFunctions`: for a function whose translation is the empty string this method returns `''` while that one falls back to the canonical id. The claim is now scoped to the ids and the licence rule, with the naming difference stated. - Reverting the static removal also removed the docs-build change it required, so `docs/.vuepress/config.js` is untouched by this PR again (it no longer builds one engine per documentation page). ## Testing New suite pins the alignment in both directions (agrees with `getAvailableFunctions` name for name; narrows on a restricted key; never narrows on a bad key; aliases gate with their canonical; answers from the instance's own snapshot). Full private suite: **517 suites / 6462 passing, 3 pre-existing skips**; `tsc --noEmit` and ESLint clean. ## Note for review `functions-metadata.spec.ts` now skips listed ids with no plugin: `OFFSET` is listed (it is callable) but parse-time resolved, so it legitimately has no registry metadata. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_019pxNP45obT2LZfjitaCv9o ## The codecov/project dip, traced `codecov/project` was red at −0.02% while `codecov/patch` reported 100% of the diff hit. Rather than write that off as a threshold artifact, I measured coverage on this commit and on its base and diffed the per-file numbers: | file | base (8/9) | this PR, before the fix | |---|---|---| | `src/HyperFormula.ts` | 674 / 675 | 675 / 676 | | `src/interpreter/FunctionRegistry.ts` | 130 / 130 | **129 / 130** | | total | 12 933 / 13 268 | 12 933 / 13 269 | So the covered count did not move and one previously covered line stopped executing — a real consequence of this change, not a rounding artifact. The line was the **instance** `FunctionRegistry.prototype.getRegisteredFunctionIds()`, whose only caller in the whole repository was the method this PR rewrites. Nothing in `src/`, nothing in the private suite, and nothing outside (the class is not exported from `src/index.ts`) calls it any more. Removed, since this change is what orphaned it. The static `FunctionRegistry.getRegisteredFunctionIds()` is untouched and still used — by the deprecated static method above and by three specs. For the record, the single uncovered line left in `HyperFormula.ts` is pre-existing and not mine: `removeNamedExpression`'s unreachable `return []`, which already carries a `codecov note` comment explaining why it cannot be hit. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Documentation and deprecation only; no runtime behavior changes in this diff. > > **Overview** > **Deprecates** the static `HyperFormula.getRegisteredFunctionNames(code)` API (removal planned in a future major) and documents how it differs from the instance method. > > The static form is documented as reflecting the **global** function registry and any registered language without an engine; callers should migrate via `HyperFormula.buildEmpty({ language: '…' }).getRegisteredFunctionNames()`. The **instance** `getRegisteredFunctionNames()` JSDoc now states it lists everything in the instance registry (registration, not license entitlement) and points license-aware UIs to `getAvailableFunctions()`. > > `CHANGELOG.md` adds a **Deprecated** entry for the static method. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit a8ffef3. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --- ### Open in the 2026-09-22 review round - @sequba: "`getRegisteredFunctionNames` should still list ALL functions". Agreed on the principle — this method answers for the REGISTRY, and registration is not entitlement, while `getAvailableFunctions()` and `getFunctionDetails()` answer about availability and stay filtered. That removes most of this PR's substance, leaving the deprecation note and a docs correction. **Awaiting a decision on whether to reduce this PR to that or close it with the deprecation folded into #1730.** - Fixed here meanwhile: `docs/guide/release-notes.md` now carries the same retroactive `### Removed` section for 3.4.0 that `CHANGELOG.md` gained in this PR's history. The two are mirrors and only one had it. --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: Kuba Sekowski <jakub.sekowski@handsontable.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
HF-349 — a blocker for the 3.4.0 release, under HF-307 (feature packages and add-ons), decision D2.
getAvailableFunctionsandgetFunctionDetailsshipped in both a static and an instance form. License-based entitlement splits the two apart: an instance knows its license key, so it can answer for the engine the caller actually holds; a static method has no key to read, so it can only ever answer for the package as a whole. Only the instance answer is the one integrators need — the static one invites a function picker to advertise functions that then fail on evaluation, and nothing errors at integration time.Both methods are unreleased, which is the only free moment to remove them.
This PR removes the static variants. The function metadata API is instance-scoped only.
Code removed as dead after that change:
FunctionRegistry.getListableFunctionIds(static) — its only caller was the staticgetAvailableFunctionsgetPlugincallback threaded throughbuildAvailableFunctions— it existed only to abstract over the static and the instance registry. Both private builders now take the engine's ownFunctionRegistry, which also removes the possibility of ids and plugins being resolved from two different sources.Docs generator. The built-in functions guide page is generated from this API, so
script/generate-builtin-functions-doc.tsnow builds an engine and reads the instance methods. It builds that engine with thegpl-v3license key — the fully-entitled one — so the published reference keeps documenting every built-in instead of narrowing to the tier of whichever key the build environment happens to supply. The ADR calls this out as a real risk introduced by the decision, so the key is named in the script rather than left to the environment, with the reasoning recorded there and indocs/README.md.Changelog. The 3.4.0 entry that introduced these methods is amended (
(both static and instance)→instance methods) rather than accompanied by a "Removed" note: nothing was ever released to remove, and announcing the removal of a method 3.4.0 never shipped would only confuse readers. Same edit indocs/guide/release-notes.md.Tests live in the private repo — companion PR: handsontable/hyperformula-tests#29. Both target
release/3.4.0and must land together.The two JSDoc nuances the removed static docs carried (a custom plugin registered over a built-in id inherits that id's catalogue entry, in both the list and the details) were folded into the instance methods' JSDoc, so the generated API reference does not lose behaviour that is still tested.
How did you test your changes?
npm run test:ciwith the private suite attached: 502/502 suites, 6180 tests passingnpm run lint: clean (exit 0, no errors)npx tsc --noEmit: cleannpm run bundle:typings: succeeds; the emittedHyperFormula.d.tsexposes only the instancegetAvailableFunctions()/getFunctionDetails(canonicalName)npm run docs:generate-function-docs: the generatedbuilt-in-functions.mdis byte-identical to the one the static path produced (diffed before/after), 423 rows — matching the countdocs/.vuepress/config.jsprints on the pageNot run in this environment:
npm run test:browser. Karma is configured forChromeHeadlessandFirefoxHeadless, and no Firefox is available here. The change is not environment-specific and the same specs pass under Jest.Types of changes
Marked breaking because the methods disappear from the public surface. In practice nothing released ever exposed them, so no published version is affected and no migration guide entry is needed.
Related issues:
Checklist:
The three OpenDocument/Excel/Google Sheets boxes are left unticked as not applicable: this change touches no formula semantics.
Note
Medium Risk
Breaking public API removal (static metadata methods), though unreleased in 3.4.0; integrators must use an engine instance, which is intentional for upcoming license-gated function lists.
Overview
Removes the static
getAvailableFunctionsandgetFunctionDetailsAPI so function metadata is only available on aHyperFormulainstance (with language taken from that engine’s config). That aligns metadata with license key and per-instancefunctionPlugins/ registry snapshots instead of the global registry.Private list/detail builders now take the engine’s
FunctionRegistrydirectly; staticFunctionRegistry.getListableFunctionIdsis dropped. Instance JSDoc picks up behaviour notes that lived on the removed static methods (registry snapshotting, custom plugins shadowing built-in ids).The built-in functions doc generator builds an empty engine (
gpl-v3license key, default registry) and calls the instance metadata methods. CHANGELOG and release notes for 3.4.0 are edited to describe instance methods only (no separate “Removed” entry for unreleased static APIs).Reviewed by Cursor Bugbot for commit 6fe2aaf. Bugbot is set up for automated code reviews on this repo. Configure here.