remove base type, keep primitive type and make hints as unknown optional
This commit is contained in:
@@ -16,10 +16,10 @@ import { CASHASSEMBLY_VARIABLE_METHOD_REFERENCE_PATTERN } from './defaults.ts';
|
||||
import { convertValueToBytes } from './bytes.ts';
|
||||
|
||||
/**
|
||||
* Template hint values mapped to a primitive class for resolving primitive method evaluations.
|
||||
* Keys are values from XOTemplatePrimitiveTypes.
|
||||
* Maps method resolvable template types to `@xo-cash/primitives` classes for `base.method` resolution.
|
||||
* Keys are a subset of {@link XOTemplatePrimitiveTypes}.
|
||||
*/
|
||||
const PRIMITIVE_BY_TEMPLATE_HINT = {
|
||||
const RESOLVABLE_PRIMITIVE_CLASS_BY_TYPE = {
|
||||
[XOTemplatePrimitiveTypes.FUNGIBLE_TOKEN_AMOUNT]: FungibleTokenAmount,
|
||||
[XOTemplatePrimitiveTypes.NFT_COMMITMENT]: NFTCommitment,
|
||||
[XOTemplatePrimitiveTypes.PUBLIC_KEY]: PublicKey,
|
||||
@@ -31,7 +31,11 @@ const PRIMITIVE_BY_TEMPLATE_HINT = {
|
||||
[XOTemplatePrimitiveTypes.TRANSACTION_HASH]: TransactionHash,
|
||||
} as const;
|
||||
|
||||
type SupportedPrimitiveHint = keyof typeof PRIMITIVE_BY_TEMPLATE_HINT;
|
||||
/**
|
||||
* Template primitive types that map to an `@xo-cash/primitives` class for `base.method` resolution.
|
||||
* This is a subset of {@link XOTemplatePrimitiveType}, not every declared template type.
|
||||
*/
|
||||
type ResolvablePrimitiveType = keyof typeof RESOLVABLE_PRIMITIVE_CLASS_BY_TYPE;
|
||||
|
||||
/**
|
||||
* Inputs needed to call a primitive method for a `name.method` push.
|
||||
@@ -54,9 +58,9 @@ type CallPrimitiveMethodParameters = {
|
||||
value: unknown;
|
||||
|
||||
/**
|
||||
* Supported template hint that selects the primitive class.
|
||||
* Method resolvable template type that selects the primitive class.
|
||||
*/
|
||||
hint: SupportedPrimitiveHint;
|
||||
type: ResolvablePrimitiveType;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -77,30 +81,30 @@ export type ResolvePrimitiveMethodBytesParameters = {
|
||||
|
||||
/**
|
||||
* Template variable definitions. When omitted, no primitive methods are resolved.
|
||||
* The `hint` on each entry selects the primitive class.
|
||||
* The `type` on each entry selects the primitive class.
|
||||
*/
|
||||
templateVariables?: XOTemplate['variables'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true when `hint` maps to a supported primitive class in `PRIMITIVE_BY_TEMPLATE_HINT`.
|
||||
* Returns true when `type` maps to a primitive class in `RESOLVABLE_PRIMITIVE_CLASS_BY_TYPE` for method resolution.
|
||||
*
|
||||
* @param {XOTemplatePrimitiveType | undefined} hint - Template variable hint.
|
||||
* @returns {boolean} True when the hint selects a supported primitive class.
|
||||
* @param {XOTemplatePrimitiveType | undefined} type - Template variable type.
|
||||
* @returns {boolean} True when the type can resolve `base.method` through a primitive class.
|
||||
*/
|
||||
const isSupportedPrimitiveHint = (hint: XOTemplatePrimitiveType | undefined): hint is SupportedPrimitiveHint => {
|
||||
return hint !== undefined && Object.hasOwn(PRIMITIVE_BY_TEMPLATE_HINT, hint) === true;
|
||||
const isResolvablePrimitiveType = (type: XOTemplatePrimitiveType | undefined): type is ResolvablePrimitiveType => {
|
||||
return type !== undefined && Object.hasOwn(RESOLVABLE_PRIMITIVE_CLASS_BY_TYPE, type) === true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true when `methodName` is an own function on the primitive class for `hint`.
|
||||
* Returns true when `methodName` is an own function on the primitive class for `type`.
|
||||
*
|
||||
* @param {SupportedPrimitiveHint} hint - Supported template hint.
|
||||
* @param {ResolvablePrimitiveType} type - Method resolvable template type.
|
||||
* @param {string} methodName - Method name from the evaluation text, for example `toSatoshis`.
|
||||
* @returns {boolean} True when that class exposes the named method.
|
||||
*/
|
||||
const canResolvePrimitiveMethod = (hint: SupportedPrimitiveHint, methodName: string): boolean => {
|
||||
const PrimitiveClass = PRIMITIVE_BY_TEMPLATE_HINT[hint];
|
||||
const canResolvePrimitiveMethod = (type: ResolvablePrimitiveType, methodName: string): boolean => {
|
||||
const PrimitiveClass = RESOLVABLE_PRIMITIVE_CLASS_BY_TYPE[type];
|
||||
|
||||
// Check own properties only so inherited Object.prototype names are rejected without needing a value.
|
||||
if (Object.hasOwn(PrimitiveClass.prototype, methodName) === false) {
|
||||
@@ -113,17 +117,17 @@ const canResolvePrimitiveMethod = (hint: SupportedPrimitiveHint, methodName: str
|
||||
/**
|
||||
* Constructs a primitive from a raw value and calls one instance method on it.
|
||||
*
|
||||
* Call only after `canResolvePrimitiveMethod` is true for the same hint and method.
|
||||
* Call only after `canResolvePrimitiveMethod` is true for the same type and method.
|
||||
*
|
||||
* @param {CallPrimitiveMethodParameters} parameters - Identifier, method, value, and supported hint.
|
||||
* @param {CallPrimitiveMethodParameters} parameters - Identifier, method, value, and resolvable type.
|
||||
* @returns {unknown} Method return value, later encoded as CashAssembly push bytes.
|
||||
* @throws {@link CashAssemblyPrimitiveMethodMissingError} When the prototype member is not a function.
|
||||
* @throws When the primitive constructor rejects the raw value (validation errors from `@xo-cash/primitives`).
|
||||
*/
|
||||
const callPrimitiveMethod = (parameters: CallPrimitiveMethodParameters): unknown => {
|
||||
const { identifier, methodName, value, hint } = parameters;
|
||||
const { identifier, methodName, value, type } = parameters;
|
||||
|
||||
const PrimitiveClass = PRIMITIVE_BY_TEMPLATE_HINT[hint];
|
||||
const PrimitiveClass = RESOLVABLE_PRIMITIVE_CLASS_BY_TYPE[type];
|
||||
|
||||
// Constructing runs each primitive's own input validation (range checks, hex length, etc).
|
||||
// `as never` satisfies TypeScript across constructors that accept different input shapes.
|
||||
@@ -133,23 +137,23 @@ const callPrimitiveMethod = (parameters: CallPrimitiveMethodParameters): unknown
|
||||
const primitiveMethod = Reflect.get(PrimitiveClass.prototype, methodName);
|
||||
|
||||
if (typeof primitiveMethod !== 'function') {
|
||||
throw new CashAssemblyPrimitiveMethodMissingError(identifier, methodName, hint);
|
||||
throw new CashAssemblyPrimitiveMethodMissingError(identifier, methodName, type);
|
||||
}
|
||||
|
||||
return primitiveMethod.call(primitiveInstance);
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolves supported `base.method` identifiers to CashAssembly variable bytes.
|
||||
* Resolves method resolvable `base.method` identifiers to CashAssembly variable bytes.
|
||||
*
|
||||
* Each single dot identifier whose `hint` maps to a supported primitive is resolved and stored under
|
||||
* the full identifier (`base.method`). Unsupported or multi dot identifiers are left for CashAssembly.
|
||||
* Each single dot identifier whose `type` is in `RESOLVABLE_PRIMITIVE_CLASS_BY_TYPE` is resolved and stored under
|
||||
* the full identifier (`base.method`). Other template types or multi dot identifiers are left for CashAssembly.
|
||||
*
|
||||
* When `templateVariables` is omitted, returns an empty map.
|
||||
*
|
||||
* @param {ResolvePrimitiveMethodBytesParameters} parameters - Identifiers, values, and optional template metadata.
|
||||
* @returns {Record<string, Uint8Array>} Resolved method identifiers mapped to bytes for CashAssembly pushes.
|
||||
* @throws {@link CashAssemblyPrimitiveMethodMissingError} When the hint is a supported primitive but the method is missing.
|
||||
* @throws {@link CashAssemblyPrimitiveMethodMissingError} When the type is method resolvable but the method is missing.
|
||||
* @throws {@link CashAssemblyPrimitiveVariableMissingError} When the runtime value is missing from the variables map.
|
||||
* @throws {@link CashAssemblyUnsupportedValueTypeError} When the method return type cannot be embedded.
|
||||
* @throws {@link CashAssemblyNumberNotSafeIntegerError} When a method return value is a number that is not a safe integer.
|
||||
@@ -157,7 +161,7 @@ const callPrimitiveMethod = (parameters: CallPrimitiveMethodParameters): unknown
|
||||
export const resolvePrimitiveMethodBytes = (parameters: ResolvePrimitiveMethodBytesParameters): Record<string, Uint8Array> => {
|
||||
const { identifiers, templateVariables, variables } = parameters;
|
||||
|
||||
// Without template metadata there is no hint to select a primitive class.
|
||||
// Without template metadata there is no type to select a primitive class.
|
||||
if (templateVariables === undefined) {
|
||||
return {};
|
||||
}
|
||||
@@ -185,15 +189,16 @@ export const resolvePrimitiveMethodBytes = (parameters: ResolvePrimitiveMethodBy
|
||||
continue;
|
||||
}
|
||||
|
||||
const hint = templateVariables[baseName].hint;
|
||||
const type = templateVariables[baseName].type;
|
||||
|
||||
if (isSupportedPrimitiveHint(hint) === false) {
|
||||
// Template types without a mapped primitive class are left for CashAssembly.
|
||||
if (isResolvablePrimitiveType(type) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Supported hint with an unknown method should be thrown as an error.
|
||||
if (canResolvePrimitiveMethod(hint, methodName) === false) {
|
||||
throw new CashAssemblyPrimitiveMethodMissingError(identifier, methodName, hint);
|
||||
// Method resolvable type with an unknown method should throw.
|
||||
if (canResolvePrimitiveMethod(type, methodName) === false) {
|
||||
throw new CashAssemblyPrimitiveMethodMissingError(identifier, methodName, type);
|
||||
}
|
||||
|
||||
// If the method is known but the runtime value is missing, throw an error.
|
||||
@@ -205,7 +210,7 @@ export const resolvePrimitiveMethodBytes = (parameters: ResolvePrimitiveMethodBy
|
||||
identifier,
|
||||
methodName,
|
||||
value: variables[baseName],
|
||||
hint,
|
||||
type,
|
||||
});
|
||||
|
||||
// Store the result under identifier.
|
||||
|
||||
Reference in New Issue
Block a user