diff --git a/CHANGELOG.md b/CHANGELOG.md index 24ecffc1c..e879632ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- Added support for proprietary license keys that grant a subset of the library ("feature packages and add-ons"). A function your key does not include evaluates to a `#LIC!` error, and the corresponding parts of the API throw a `LicenseCapabilityMissingError`. Keys that grant everything, including `gpl-v3`, are unaffected. [#1728](https://github.com/handsontable/hyperformula/pull/1728) [#1729](https://github.com/handsontable/hyperformula/pull/1729) [#1730](https://github.com/handsontable/hyperformula/pull/1730) + +### Changed + +- Changed `getAvailableFunctions()` and `getFunctionDetails()` to describe only the functions the instance's license key includes, so they no longer advertise a function that would evaluate to a `#LIC!` error. A missing, invalid, or expired key does not shorten the list. [#1731](https://github.com/handsontable/hyperformula/pull/1731) + ## [3.4.0] - 2026-08-10 ### Added diff --git a/docs/guide/license-key.md b/docs/guide/license-key.md index 35ed84712..a6c441b13 100644 --- a/docs/guide/license-key.md +++ b/docs/guide/license-key.md @@ -43,11 +43,40 @@ two dates: This process doesn't require any connection to the server. +## Feature packages and add-ons + +A proprietary license key may grant the whole library, or only part of it. If your key covers +everything you buy nothing new to think about, and neither does the GPLv3 key `gpl-v3`, which +always grants everything. + +If your key grants only part of the library, then: + +* A function your key doesn't include evaluates to a `#LIC!` error, in the same way as any other + [error value](types-of-errors.md). Everything else in the sheet keeps calculating. +* An API method your key doesn't include throws a `LicenseCapabilityMissingError` when you call + it. Methods that only read data never throw. +* [`getAvailableFunctions()`](../api/classes/hyperformula.md#getavailablefunctions) and + [`getFunctionDetails()`](../api/classes/hyperformula.md#getfunctiondetails) describe only the + functions your key includes, so a function picker built from them never offers a function that + then fails. + +Custom functions you register yourself are always available, whatever your key grants. + +::: tip +To find out which package your key includes, check your order confirmation or +[contact our team](contact.md). HyperFormula deliberately reports nothing about the contents of +your key at runtime. +::: + ## License key notifications If your license key is missing, invalid, or expired, you see a corresponding notification in the console. +In that case every function evaluates to a `#LIC!` error — but no API method starts throwing, and +`getAvailableFunctions()` still describes the full set of functions. A key problem never narrows +what the library reports it can do; it stops formulas from calculating until you fix the key. + ## License key support If you have any issues with your license key, [contact our team](contact.md). \ No newline at end of file diff --git a/docs/guide/types-of-errors.md b/docs/guide/types-of-errors.md index 7f8546a90..1ae8c13c8 100644 --- a/docs/guide/types-of-errors.md +++ b/docs/guide/types-of-errors.md @@ -23,4 +23,4 @@ according to the language settings. | #VALUE! | Wrong type of argument | It occurs when a formula tries to improperly use different types of data. For example, you will see this error when you will try to add a string to a number. | | #CYCLE! | Circular reference | It occurs when a formula refers to its own cell, both directly and indirectly. | | #ERROR! | An error occurred | It indicates that there is an unknown error in a formula. | -| #LIC! | Invalid license key | It occurs when the license key is invalid, expired, or missing. | \ No newline at end of file +| #LIC! | License key problem | It occurs when the license key is invalid, expired, or missing, or when the function is not included in the [feature package](license-key.md#feature-packages-and-add-ons) your license key grants. | \ No newline at end of file diff --git a/src/HyperFormula.ts b/src/HyperFormula.ts index 3c4f9fbc2..2935a8746 100644 --- a/src/HyperFormula.ts +++ b/src/HyperFormula.ts @@ -44,7 +44,7 @@ import { import {Evaluator} from './Evaluator' import {ExportedChange, Exporter} from './Exporter' import {LicenseKeyValidityState} from './helpers/licenseKeyValidator' -import {allowsFeature} from './license/CapabilityRegistry' +import {allowsFeature, licenseAllowsFunction} from './license/CapabilityRegistry' import {FeatureId} from './license/LicenseEntitlement' import {buildTranslationPackage, RawTranslationPackage, TranslationPackage} from './i18n' import {FunctionPluginDefinition} from './interpreter' @@ -702,26 +702,57 @@ export class HyperFormula implements TypedEmitter { return {doc, metadata, aliasOf: metadataKey !== functionId ? metadataKey : undefined} } + /** + * Whether an instance's license lets it evaluate the given function id, and therefore whether the + * metadata API may describe it. Mirrors the gate-B branch the interpreter runs per function call + * (`Interpreter.evaluateAstWithoutPostprocessing`, the `FUNCTION_CALL` case), through the same + * [[licenseAllowsFunction]] rule and the same alias canonicalisation, so a listed function is + * always one that actually evaluates. + * + * Gate B only, deliberately — never the license key's validity state. A missing, invalid or expired + * key resolves to an unrestricted entitlement (the invariant `resolveLicense` documents), so it + * reaches this method with `licenseCapabilities.unrestricted` set and every function stays listed. + * That is the intended answer: a key problem is reported on the console and by `#LIC!` in cells, + * and narrowing the catalogue to the two protected built-ins would leave an integrator who has not + * wired up their key yet with an empty function picker and no clue why. The list narrows only for + * a *valid* key that genuinely does not include a function — the case where the answer is useful. + * + * @param {string} functionId - the id as registered, which may be an alias + * @param {FunctionRegistry} functionRegistry - the engine's registry, which resolves the alias map + * @param {Config} config - the instance's config, holding its resolved entitlement + */ + private static licenseListsFunction(functionId: string, functionRegistry: FunctionRegistry, config: Config): boolean { + if (!config.isLicenseGateActive || FunctionRegistry.functionIsProtected(functionId)) { + return true + } + const plugin = functionRegistry.getFunctionPlugin(functionId) + const canonicalId = plugin?.aliases?.[functionId] ?? functionId + return licenseAllowsFunction(config.capabilityRegistry, config.licenseCapabilities, canonicalId) + } + /** * Builds the function list for every id registered in an engine's own registry. Documented functions use their * catalogue entry; custom functions are listed with their name only. Sorted by localized name with * `localeCompare`, so the order follows the host's collation rules, with the language-independent canonical name * as a stable tiebreaker for entries that share a localized name. * - * Takes the [[TranslationPackage]] rather than deriving it from a language code: an instance must describe its - * functions under the package its own evaluator uses (`Config.translationPackage`), which is a snapshot taken + * Takes the instance's whole [[Config]] rather than a language code: an instance must describe its functions + * under the translation package its own evaluator uses (`Config.translationPackage`), which is a snapshot taken * when the instance was built and can differ from whatever is registered globally for the same code today. - * Deriving it here instead would let this method report a localized name the instance refuses to evaluate. + * Deriving it here instead would let this method report a localized name the instance refuses to evaluate. The + * config also carries the resolved entitlement, for the same reason — see [[licenseListsFunction]]. * * @param {FunctionRegistry} functionRegistry - the engine's registry, the source of both the ids and their plugins - * @param {TranslationPackage} language - the translation package to translate the names under + * @param {Config} config - the instance's config: the translation package and the resolved license entitlement */ - private static buildAvailableFunctions(functionRegistry: FunctionRegistry, language: TranslationPackage): FunctionListEntry[] { + private static buildAvailableFunctions(functionRegistry: FunctionRegistry, config: Config): FunctionListEntry[] { + const language = config.translationPackage const translate = (id: string) => language.getMaybeFunctionTranslation(id) return functionRegistry.getListableFunctionIds() // The interpreter refuses to evaluate ids the active language has no translation entry for // (FunctionRegistry.getFunction), so an untranslated function would be advertised but uncallable. .filter(id => language.isFunctionTranslated(id)) + .filter(id => HyperFormula.licenseListsFunction(id, functionRegistry, config)) .map(id => { const resolved = HyperFormula.resolveFunctionMetadata(id, functionRegistry.getFunctionPlugin(id)) if (resolved === undefined) { @@ -745,14 +776,19 @@ export class HyperFormula implements TypedEmitter { * * @param {string} functionId - the language-independent function id (canonical id or alias) * @param {FunctionRegistry} functionRegistry - the engine's registry, which resolves the id to its plugin - * @param {TranslationPackage} language - the translation package to translate the names under + * @param {Config} config - the instance's config: the translation package and the resolved license entitlement */ - private static buildFunctionDetailsFor(functionId: string, functionRegistry: FunctionRegistry, language: TranslationPackage): FunctionDetails | undefined { - // Mirrors the filter in buildAvailableFunctions: an id the active language cannot evaluate - // (no translation entry) gets no details either, so the list and the details always agree. + private static buildFunctionDetailsFor(functionId: string, functionRegistry: FunctionRegistry, config: Config): FunctionDetails | undefined { + const language = config.translationPackage + // Mirrors the filters in buildAvailableFunctions: an id the active language cannot evaluate + // (no translation entry), or one this instance's license does not grant, gets no details + // either, so the list and the details always agree. if (!language.isFunctionTranslated(functionId)) { return undefined } + if (!HyperFormula.licenseListsFunction(functionId, functionRegistry, config)) { + return undefined + } const resolved = HyperFormula.resolveFunctionMetadata(functionId, functionRegistry.getFunctionPlugin(functionId)) if (resolved === undefined) { return undefined @@ -4556,6 +4592,14 @@ export class HyperFormula implements TypedEmitter { * plugin registered without translations for that language. A translation set to an empty string is not a missing * entry: it falls back to the canonical id, so the function stays listed under its canonical name. * + * A function the instance's license key does not include is omitted for the same reason: it would evaluate to a + * `#LIC!` error. The list therefore answers "what can this engine compute", not "what does this package contain". + * Two consequences worth knowing: + * - A missing, invalid or expired license key does **not** shorten the list. Such a key restricts nothing by + * entitlement — it is reported on the console, and every function evaluates to `#LIC!` — so the full catalogue + * is still described. Use it to build a function picker before a key is configured. + * - Custom (user-registered) functions are never omitted; the license covers built-ins only. + * * @example * ```js * const hfInstance = HyperFormula.buildEmpty(); @@ -4569,9 +4613,9 @@ export class HyperFormula implements TypedEmitter { public getAvailableFunctions(): FunctionListEntry[] { return HyperFormula.buildAvailableFunctions( this._functionRegistry, - // The instance's own package, the one its evaluator uses — not a fresh global lookup, which could describe - // the functions under a package this instance never adopted. - this._config.translationPackage, + // The instance's own config: its translation package (not a fresh global lookup, which could describe the + // functions under a package this instance never adopted) and its resolved license entitlement. + this._config, ) } @@ -4582,9 +4626,10 @@ export class HyperFormula implements TypedEmitter { * documentation link (`documentationUrl`) and usage examples (`examples`) — every built-in authors both. * Resolves both built-in and custom (user-registered) functions, as well as aliases. An alias reports its * target's metadata (including examples, which spell the target's name) under the alias id, with the target id - * exposed as `aliasOf`. Returns `undefined` when the function id is unknown, not registered in this instance, or - * has no translation entry for the configured language (an untranslated id cannot be evaluated, so it is not - * described either, which keeps this method consistent with [[getAvailableFunctions]]). + * exposed as `aliasOf`. Returns `undefined` when the function id is unknown, not registered in this instance, has + * no translation entry for the configured language, or is not included in this instance's license key (neither an + * untranslated nor an unlicensed id can be evaluated, so neither is described — which keeps this method consistent + * with [[getAvailableFunctions]], including its behaviour for a missing, invalid or expired key). * For a custom function, `category` is `'Custom'`, there is no `shortDescription`, `documentationUrl` or * `examples`, and parameters are reported positionally (`Arg1`, `Arg2`, ...). A custom plugin registered over a * built-in id is the exception: the catalogue is keyed by function id, so it reports that built-in's authored @@ -4613,8 +4658,8 @@ export class HyperFormula implements TypedEmitter { */ public getFunctionDetails(canonicalName: string): FunctionDetails | undefined { validateArgToType(canonicalName, 'string', 'canonicalName') - // The instance's own package, the one its evaluator uses — see getAvailableFunctions. - return HyperFormula.buildFunctionDetailsFor(canonicalName, this._functionRegistry, this._config.translationPackage) + // The instance's own config, for the same reasons as getAvailableFunctions. + return HyperFormula.buildFunctionDetailsFor(canonicalName, this._functionRegistry, this._config) } /** diff --git a/src/interpreter/Interpreter.ts b/src/interpreter/Interpreter.ts index 2c6e39f2a..39ef01b6b 100644 --- a/src/interpreter/Interpreter.ts +++ b/src/interpreter/Interpreter.ts @@ -13,7 +13,7 @@ import {DependencyGraph} from '../DependencyGraph' import {FormulaVertex} from '../DependencyGraph/FormulaVertex' import {ErrorMessage} from '../error-message' import {LicenseKeyValidityState} from '../helpers/licenseKeyValidator' -import {allowsFunction} from '../license/CapabilityRegistry' +import {licenseAllowsFunction} from '../license/CapabilityRegistry' import {ColumnSearchStrategy} from '../Lookup/SearchStrategy' import {Maybe} from '../Maybe' import {NamedExpressions} from '../NamedExpressions' @@ -196,8 +196,7 @@ export class Interpreter { } const canonicalId = this.canonicalFunctionId(ast.procedureName) - if (this.config.capabilityRegistry.capabilityOf(canonicalId) !== undefined - && !allowsFunction(this.config.licenseCapabilities, canonicalId)) { + if (!licenseAllowsFunction(this.config.capabilityRegistry, this.config.licenseCapabilities, canonicalId)) { return new CellError(ErrorType.LIC, ErrorMessage.LicenseCapability(ast.procedureName)) } } diff --git a/src/license/CapabilityRegistry.ts b/src/license/CapabilityRegistry.ts index 495c51cfb..aa6eb1c60 100644 --- a/src/license/CapabilityRegistry.ts +++ b/src/license/CapabilityRegistry.ts @@ -121,3 +121,34 @@ export function allowsFunction(resolved: ResolvedCapabilities, functionId: strin export function allowsFeature(resolved: ResolvedCapabilities, feature: FeatureId): boolean { return resolved.unrestricted || resolved.features.has(feature) } + +/** + * Whether the license lets an instance evaluate — and therefore describe — the given function. + * + * The rule both gate-B function call sites share: a function the capability table does not cover + * at all is allowed. {@link CapabilityRegistry.capabilityOf} returns `undefined` only for an id no + * token lists, which the completeness invariant in `unit/license/capability-registry.spec.ts` + * guarantees is not an unlisted built-in but a custom, instance-registered function — exempt from + * gate B by decision D1. Everything the table does cover has to be granted by the entitlement. + * + * Extracted so the interpreter and the function metadata API cannot drift apart. The metadata API + * exists to describe the functions an instance can actually evaluate, so a second spelling of this + * rule would eventually let it advertise a function that then returns `#LIC!` — the exact failure + * removing the static metadata methods (HF-349) was meant to prevent. + * + * Note this is gate B only: it says nothing about {@link LicenseKeyValidityState}. Callers that + * also need gate A check it separately, because the two gates have different answers for the same + * key — see the comment on `resolveLicense`. + * + * @param {CapabilityRegistry} registry - the registry the capabilities were resolved against + * @param {ResolvedCapabilities} resolved - the instance's resolved capabilities + * @param {string} canonicalFunctionId - the function id, already resolved through the alias map + */ +export function licenseAllowsFunction( + registry: CapabilityRegistry, + resolved: ResolvedCapabilities, + canonicalFunctionId: string, +): boolean { + return registry.capabilityOf(canonicalFunctionId) === undefined + || allowsFunction(resolved, canonicalFunctionId) +} diff --git a/src/license/capabilities.ts b/src/license/capabilities.ts index 6c640d444..4204d9f6b 100644 --- a/src/license/capabilities.ts +++ b/src/license/capabilities.ts @@ -79,81 +79,87 @@ const OPERATOR_FUNCTIONS = [ /** * Package membership, as the LOWEST package that includes each function. * - * Transcribed from the packaging design's own per-function evidence file. The lists reproduce - * that file's package counts exactly (17 / 51 / 127 / 355 cumulative, plus 15 operators), which - * is how the transcript was checked; `capability-table.spec.ts` pins those counts so a later - * edit cannot drift from them silently. + * Transcribed from Kuba's 12.08 packaging doc (CU doc `8cnjcyf-33175`, page `8cnjcyf-47835`, "HF + * function groups and packages"), which supersedes the earlier evidence file this table was first + * built from. The doc organizes the catalog into 21 group tokens (`fun:.`) and + * states each package as the cumulative union of specific groups: Math engine = the `.A` groups, + * Calculated fields = `.A` + `.B`, Spreadsheet = `.A` + `.B` + `.C`. Reproducing that union gives + * 17 / 64 / 161 cumulative functions before the two protected built-ins below are removed; + * `capability-table.spec.ts` pins the resulting counts so a later edit cannot drift from them + * silently. * - * Two functions of the evidence file are deliberately absent: `OFFSET` and `VERSION` are - * protected built-ins and sit OUTSIDE the token system — the interpreter never gate-checks a - * protected function, so listing them would be dead weight that implies a restriction that does - * not exist. - * - * **This membership is a DRAFT and is expected to change before release.** The packaging design - * it follows is still under review, with the free tier's exact contents and the placement of - * several function families among the points not yet settled. + * `OFFSET` and `VERSION` are named by the doc (as `fun:lookup.A` and `fun:info.A`) but are + * deliberately absent from every list below: both are protected built-ins that sit OUTSIDE the + * token system today — the interpreter never gate-checks a protected function, so listing them + * would be dead weight that implies a restriction that does not exist. The doc calls this a + * "technical limitation" on both; see `hf-306-token-vocabulary-final` for the root cause of each + * (registry protection for VERSION, parse-time resolution for OFFSET) and what closing it would + * take. */ const MATH_ENGINE_FUNCTIONS = [ - 'ABS', 'CEILING', 'EXP', 'FLOOR', 'IF', 'LN', 'LOG', 'LOG10', 'MOD', 'POWER', 'PRODUCT', 'ROUND', - 'ROUNDDOWN', 'ROUNDUP', 'SQRT', 'SUM', + 'ABS', 'AVERAGE', 'COUNT', 'IF', 'LOG', 'MAX', 'MIN', 'MOD', 'POWER', 'PRODUCT', 'ROUND', 'ROUNDDOWN', + 'ROUNDUP', 'SQRT', 'SUM', ] /** Added by the calculated-fields package, on top of {@link MATH_ENGINE_FUNCTIONS}. */ const CALCULATED_FIELDS_FUNCTIONS = [ - 'AND', 'AVERAGE', 'CONCATENATE', 'COUNT', 'DATE', 'DATEDIF', 'DAY', 'DAYS', 'FALSE', 'FIND', 'HOUR', - 'LEFT', 'LEN', 'LOWER', 'MAX', 'MID', 'MIN', 'MINUTE', 'MONTH', 'NOT', 'NOW', 'OR', 'RIGHT', 'SEARCH', - 'SECOND', 'TEXT', 'TEXTJOIN', 'TODAY', 'TRIM', 'TRUE', 'UPPER', 'VALUE', 'XOR', 'YEAR', + 'AND', 'AVERAGEIF', 'CONCATENATE', 'COUNTIF', 'DATE', 'DATEDIF', 'DATEVALUE', 'DAY', 'DAYS', 'EOMONTH', + 'EXACT', 'FALSE', 'HOUR', 'IFS', 'ISOWEEKNUM', 'LEFT', 'LEN', 'LOWER', 'MID', 'MINUTE', 'MONTH', + 'NETWORKDAYS', 'NOT', 'OR', 'RAND', 'RANDBETWEEN', 'REPLACE', 'REPT', 'RIGHT', 'SEARCH', 'SECOND', + 'STDEV.S', 'SUBSTITUTE', 'SUMIF', 'SUMIFS', 'SWITCH', 'TEXT', 'TODAY', 'TRIM', 'TRUE', 'UPPER', 'VALUE', + 'WEEKDAY', 'WEEKNUM', 'WORKDAY', 'XOR', 'YEAR', ] /** Added by the spreadsheet package, on top of {@link CALCULATED_FIELDS_FUNCTIONS}. */ const SPREADSHEET_FUNCTIONS = [ - 'ADDRESS', 'AVERAGEIF', 'CHOOSE', 'COLUMN', 'COLUMNS', 'COUNTIF', 'COUNTIFS', 'DATEVALUE', 'DAYS360', - 'EDATE', 'EOMONTH', 'FILTER', 'FORMULATEXT', 'FV', 'HLOOKUP', 'HYPERLINK', 'IFERROR', 'IFNA', 'INDEX', - 'INTERVAL', 'IPMT', 'IRR', 'ISBINARY', 'ISBLANK', 'ISERR', 'ISERROR', 'ISEVEN', 'ISFORMULA', 'ISLOGICAL', - 'ISNA', 'ISNONTEXT', 'ISNUMBER', 'ISODD', 'ISOWEEKNUM', 'ISREF', 'ISTEXT', 'MATCH', 'MAXIFS', 'MINIFS', - 'NA', 'NETWORKDAYS', 'NETWORKDAYS.INTL', 'NPER', 'NPV', 'PERCENTILE.EXC', 'PERCENTILE.INC', 'PMT', - 'PPMT', 'PV', 'RAND', 'RANDBETWEEN', 'RATE', 'ROW', 'ROWS', 'SORT', 'STDEV.P', 'STDEV.S', 'STDEVA', - 'STDEVPA', 'SUMIF', 'SUMIFS', 'TIME', 'TIMEVALUE', 'UNIQUE', 'VAR.P', 'VAR.S', 'VARA', 'VARPA', - 'VLOOKUP', 'WEEKDAY', 'WEEKNUM', 'WORKDAY', 'WORKDAY.INTL', 'XLOOKUP', 'YEARFRAC', + 'ACOS', 'ADDRESS', 'ARRAYFORMULA', 'ARRAY_CONSTRAIN', 'ASIN', 'ATAN', 'ATAN2', 'AVERAGEA', 'CEILING', + 'CHAR', 'CHOOSE', 'CLEAN', 'CODE', 'COLUMN', 'COLUMNS', 'COS', 'COUNTA', 'COUNTBLANK', 'COUNTIFS', + 'DAYS360', 'DEC2HEX', 'EDATE', 'EVEN', 'EXP', 'FILTER', 'FIND', 'FLOOR', 'FV', 'HEX2DEC', 'HLOOKUP', + 'HSTACK', 'HYPERLINK', 'IFERROR', 'IFNA', 'INDEX', 'INT', 'IPMT', 'IRR', 'ISBLANK', 'ISERR', 'ISERROR', + 'ISEVEN', 'ISLOGICAL', 'ISNA', 'ISNUMBER', 'ISODD', 'ISTEXT', 'LARGE', 'LN', 'MATCH', 'MAXIFS', 'MEDIAN', + 'MINIFS', 'MROUND', 'N', 'NA', 'NOW', 'NPV', 'ODD', 'PERCENTILE.INC', 'PI', 'PMT', 'PPMT', 'PROPER', 'PV', + 'QUOTIENT', 'RATE', 'ROW', 'ROWS', 'SEQUENCE', 'SIGN', 'SIN', 'SLN', 'SMALL', 'SORT', 'STDEV.P', 'STDEVA', + 'STDEVPA', 'SUBTOTAL', 'SUMPRODUCT', 'SUMSQ', 'SUMXMY2', 'T', 'TAN', 'TEXTJOIN', 'TIME', 'TRANSPOSE', + 'UNICHAR', 'UNIQUE', 'VAR.P', 'VAR.S', 'VLOOKUP', 'VSTACK', 'XIRR', 'XLOOKUP', 'XNPV', 'YEARFRAC', ] /** * Added by the excel-simulator package, on top of {@link SPREADSHEET_FUNCTIONS} — the rest of the * implemented catalog. * - * Enumerated rather than taken from the function registry at run time, even though "all - * functions" would be the shorter way to say it. Reading the registry would sweep in functions - * registered through `HyperFormula.registerFunctionPlugin`, putting a user's OWN custom function - * into a paid package and returning `#LIC!` for it on a smaller licence — the opposite of HF-307 - * decision D1, which drops custom-function gating entirely. A function this table does not list - * is not gated at all, which is exactly the treatment a custom function should get. + * Kuba's doc does not itemize this remainder into groups the way it does for the first three + * packages: the excel-simulator package is stated as `fun:all` — the whole catalog, granted as a + * single token rather than assembled from named groups. This list is that remainder, enumerated + * rather than taken from the function registry at run time, even though "all functions" would be + * the shorter way to say it. Reading the registry would sweep in functions registered through + * `HyperFormula.registerFunctionPlugin`, putting a user's OWN custom function into a paid package + * and returning `#LIC!` for it on a smaller licence — the opposite of HF-307 decision D1, which + * drops custom-function gating entirely. A function this table does not list is not gated at all, + * which is exactly the treatment a custom function should get. */ const EXCEL_SIMULATOR_FUNCTIONS = [ - 'ACOS', 'ACOSH', 'ACOT', 'ACOTH', 'ARABIC', 'ARRAYFORMULA', 'ARRAY_CONSTRAIN', 'ASIN', 'ASINH', 'ATAN', - 'ATAN2', 'ATANH', 'AVEDEV', 'AVERAGEA', 'BASE', 'BESSELI', 'BESSELJ', 'BESSELK', 'BESSELY', 'BETA.DIST', - 'BETA.INV', 'BIN2DEC', 'BIN2HEX', 'BIN2OCT', 'BINOM.DIST', 'BINOM.INV', 'BITAND', 'BITLSHIFT', 'BITOR', - 'BITRSHIFT', 'BITXOR', 'CEILING.MATH', 'CEILING.PRECISE', 'CHAR', 'CHISQ.DIST', 'CHISQ.DIST.RT', - 'CHISQ.INV', 'CHISQ.INV.RT', 'CHISQ.TEST', 'CLEAN', 'CODE', 'COMBIN', 'COMBINA', 'COMPLEX', - 'CONFIDENCE.NORM', 'CONFIDENCE.T', 'CORREL', 'COS', 'COSH', 'COT', 'COTH', 'COUNTA', 'COUNTBLANK', - 'COUNTUNIQUE', 'COVARIANCE.P', 'COVARIANCE.S', 'CSC', 'CSCH', 'CUMIPMT', 'CUMPRINC', 'DAVERAGE', 'DB', - 'DCOUNT', 'DCOUNTA', 'DDB', 'DEC2BIN', 'DEC2HEX', 'DEC2OCT', 'DECIMAL', 'DEGREES', 'DELTA', 'DEVSQ', - 'DGET', 'DMAX', 'DMIN', 'DOLLARDE', 'DOLLARFR', 'DPRODUCT', 'DSTDEV', 'DSTDEVP', 'DSUM', 'DVAR', 'DVARP', - 'EFFECT', 'ERF', 'ERFC', 'EVEN', 'EXACT', 'EXPON.DIST', 'F.DIST', 'F.DIST.RT', 'F.INV', 'F.INV.RT', - 'F.TEST', 'FACT', 'FACTDOUBLE', 'FISHER', 'FISHERINV', 'FLOOR.MATH', 'FLOOR.PRECISE', 'FVSCHEDULE', - 'GAMMA', 'GAMMA.DIST', 'GAMMA.INV', 'GAMMALN', 'GAUSS', 'GCD', 'GEOMEAN', 'HARMEAN', 'HEX2BIN', - 'HEX2DEC', 'HEX2OCT', 'HSTACK', 'HYPGEOM.DIST', 'IFS', 'IMABS', 'IMAGINARY', 'IMARGUMENT', 'IMCONJUGATE', - 'IMCOS', 'IMCOSH', 'IMCOT', 'IMCSC', 'IMCSCH', 'IMDIV', 'IMEXP', 'IMLN', 'IMLOG10', 'IMLOG2', 'IMPOWER', - 'IMPRODUCT', 'IMREAL', 'IMSEC', 'IMSECH', 'IMSIN', 'IMSINH', 'IMSQRT', 'IMSUB', 'IMSUM', 'IMTAN', 'INT', - 'ISPMT', 'LARGE', 'LCM', 'LOGNORM.DIST', 'LOGNORM.INV', 'MAXA', 'MAXPOOL', 'MEDIAN', 'MEDIANPOOL', - 'MINA', 'MIRR', 'MMULT', 'MROUND', 'MULTINOMIAL', 'N', 'NEGBINOM.DIST', 'NOMINAL', 'NORM.DIST', - 'NORM.INV', 'NORM.S.DIST', 'NORM.S.INV', 'OCT2BIN', 'OCT2DEC', 'OCT2HEX', 'ODD', 'PDURATION', 'PHI', - 'PI', 'POISSON.DIST', 'PROPER', 'QUARTILE.EXC', 'QUARTILE.INC', 'QUOTIENT', 'RADIANS', 'REPLACE', 'REPT', - 'ROMAN', 'RRI', 'RSQ', 'SEC', 'SECH', 'SEQUENCE', 'SERIESSUM', 'SHEET', 'SHEETS', 'SIGN', 'SIN', 'SINH', - 'SKEW', 'SKEW.P', 'SLN', 'SLOPE', 'SMALL', 'SPLIT', 'SQRTPI', 'STANDARDIZE', 'STEYX', 'SUBSTITUTE', - 'SUBTOTAL', 'SUMPRODUCT', 'SUMSQ', 'SUMX2MY2', 'SUMX2PY2', 'SUMXMY2', 'SWITCH', 'SYD', 'T', 'T.DIST', - 'T.DIST.2T', 'T.DIST.RT', 'T.INV', 'T.INV.2T', 'T.TEST', 'TAN', 'TANH', 'TBILLEQ', 'TBILLPRICE', - 'TBILLYIELD', 'TDIST', 'TRANSPOSE', 'UNICHAR', 'UNICODE', 'VSTACK', 'WEIBULL.DIST', 'XIRR', 'XNPV', - 'Z.TEST', + 'ACOSH', 'ACOT', 'ACOTH', 'ARABIC', 'ASINH', 'ATANH', 'AVEDEV', 'BASE', 'BESSELI', 'BESSELJ', 'BESSELK', + 'BESSELY', 'BETA.DIST', 'BETA.INV', 'BIN2DEC', 'BIN2HEX', 'BIN2OCT', 'BINOM.DIST', 'BINOM.INV', 'BITAND', + 'BITLSHIFT', 'BITOR', 'BITRSHIFT', 'BITXOR', 'CEILING.MATH', 'CEILING.PRECISE', 'CHISQ.DIST', + 'CHISQ.DIST.RT', 'CHISQ.INV', 'CHISQ.INV.RT', 'CHISQ.TEST', 'COMBIN', 'COMBINA', 'COMPLEX', + 'CONFIDENCE.NORM', 'CONFIDENCE.T', 'CORREL', 'COSH', 'COT', 'COTH', 'COUNTUNIQUE', 'COVARIANCE.P', + 'COVARIANCE.S', 'CSC', 'CSCH', 'CUMIPMT', 'CUMPRINC', 'DAVERAGE', 'DB', 'DCOUNT', 'DCOUNTA', 'DDB', + 'DEC2BIN', 'DEC2OCT', 'DECIMAL', 'DEGREES', 'DELTA', 'DEVSQ', 'DGET', 'DMAX', 'DMIN', 'DOLLARDE', + 'DOLLARFR', 'DPRODUCT', 'DSTDEV', 'DSTDEVP', 'DSUM', 'DVAR', 'DVARP', 'EFFECT', 'ERF', 'ERFC', + 'EXPON.DIST', 'F.DIST', 'F.DIST.RT', 'F.INV', 'F.INV.RT', 'F.TEST', 'FACT', 'FACTDOUBLE', 'FISHER', + 'FISHERINV', 'FLOOR.MATH', 'FLOOR.PRECISE', 'FORMULATEXT', 'FVSCHEDULE', 'GAMMA', 'GAMMA.DIST', + 'GAMMA.INV', 'GAMMALN', 'GAUSS', 'GCD', 'GEOMEAN', 'HARMEAN', 'HEX2BIN', 'HEX2OCT', 'HYPGEOM.DIST', + 'IMABS', 'IMAGINARY', 'IMARGUMENT', 'IMCONJUGATE', 'IMCOS', 'IMCOSH', 'IMCOT', 'IMCSC', 'IMCSCH', 'IMDIV', + 'IMEXP', 'IMLN', 'IMLOG10', 'IMLOG2', 'IMPOWER', 'IMPRODUCT', 'IMREAL', 'IMSEC', 'IMSECH', 'IMSIN', + 'IMSINH', 'IMSQRT', 'IMSUB', 'IMSUM', 'IMTAN', 'INTERVAL', 'ISBINARY', 'ISFORMULA', 'ISNONTEXT', 'ISPMT', + 'ISREF', 'LCM', 'LOG10', 'LOGNORM.DIST', 'LOGNORM.INV', 'MAXA', 'MAXPOOL', 'MEDIANPOOL', 'MINA', 'MIRR', + 'MMULT', 'MULTINOMIAL', 'NEGBINOM.DIST', 'NETWORKDAYS.INTL', 'NOMINAL', 'NORM.DIST', 'NORM.INV', + 'NORM.S.DIST', 'NORM.S.INV', 'NPER', 'OCT2BIN', 'OCT2DEC', 'OCT2HEX', 'PDURATION', 'PERCENTILE.EXC', + 'PHI', 'POISSON.DIST', 'QUARTILE.EXC', 'QUARTILE.INC', 'RADIANS', 'ROMAN', 'RRI', 'RSQ', 'SEC', 'SECH', + 'SERIESSUM', 'SHEET', 'SHEETS', 'SINH', 'SKEW', 'SKEW.P', 'SLOPE', 'SPLIT', 'SQRTPI', 'STANDARDIZE', + 'STEYX', 'SUMX2MY2', 'SUMX2PY2', 'SYD', 'T.DIST', 'T.DIST.2T', 'T.DIST.RT', 'T.INV', 'T.INV.2T', 'T.TEST', + 'TANH', 'TBILLEQ', 'TBILLPRICE', 'TBILLYIELD', 'TDIST', 'TIMEVALUE', 'UNICODE', 'VARA', 'VARPA', + 'WEIBULL.DIST', 'WORKDAY.INTL', 'Z.TEST', ] const coreGrant: CapabilityGrant = {functions: [...OPERATOR_FUNCTIONS], features: []}