Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rc final rundown #2422

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/lib/stable/did_file/to_did_string.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
import { CandidTypesDefs, VisitorResult } from './visitor/index.js';

/**
* Converts a Candid type visitor result into a formatted Candid interface definition string.
* Used to generate .did files from TypeScript canister definitions.
*
* @param result - The visitor result containing Candid type definitions and service interface
* @returns A formatted string containing the complete Candid interface definition
*
* @remarks
* - Combines named type definitions with the service interface
* - Handles recursive type definitions (currently using numeric suffixes)
* - Preserves type relationships and structure
* - Adds proper newlines for formatting
*
* @example
* const visitorResult = visitCanister(canisterClass);
* const didString = toDidString(visitorResult);
* // Result:
* // type MyRecord = record { field: text };
* // service : {
* // method : (MyRecord) -> (bool);
* // }
*/
export function toDidString(result: VisitorResult): string {
// TODO it would be nice to have names for the rec types instead of rec_1, rec_2 etc
// TODO Once types have names we should deduplicate the init and post_upgrade param types
Expand Down
46 changes: 46 additions & 0 deletions src/lib/stable/did_file/visitor/did_visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,51 @@ import { visitTuple } from './visit/tuple';
import { visitVariant } from './visit/variant';
import { visitVec } from './visit/vec';

/**
* Configuration data passed through the Candid type visitor system.
* Controls visitor behavior and tracks state during traversal.
*/
export type VisitorData = {
/** Tracks recursive types to prevent infinite loops */
usedRecClasses: IDL.RecClass[];
/** Indicates if currently visiting a service definition */
isOnService: boolean;
/** Indicates if this is the first/primary service being processed */
isFirstService: boolean;
/** Collection of system functions (init, postUpgrade) to process */
systemFuncs: IDL.FuncClass[];
};

/**
* Result tuple returned by visitor operations.
* Combines Candid definitions with their associated type definitions.
*/
export type VisitorResult = [CandidDef, CandidTypesDefs];

/**
* Name of a Candid type definition.
* Used as keys in the type definition map.
*/
export type TypeName = string;

/**
* String representation of a Candid type or definition.
* The actual Candid syntax for a type, method, or service.
*/
export type CandidDef = string;

/**
* Map of named type definitions in a Candid interface.
* Keys are type names, values are their Candid definitions.
*/
export type CandidTypesDefs = { [key: TypeName]: CandidDef };

/**
* Creates default visitor configuration data.
* Used to initialize the visitor system for processing a canister's types.
*
* @returns Fresh VisitorData with empty tracking collections
*/
export function getDefaultVisitorData(): VisitorData {
return {
usedRecClasses: [],
Expand All @@ -31,6 +64,19 @@ export function getDefaultVisitorData(): VisitorData {
};
}

/**
* Visitor implementation for converting TypeScript/IDL types to Candid definitions.
* Extends the IDL.Visitor to handle all Candid type constructs.
*
* Used to generate .did interface files from canister class definitions.
* Processes types recursively while maintaining proper scoping and type relationships.
*
* @example
* const visitor = new DidVisitor();
* const myType = new IDL.Service({...implementation});
* const result = myType.accept(visitor, getDefaultVisitorData());
* const candidString = toDidString(result);
*/
export class DidVisitor extends IDL.Visitor<VisitorData, VisitorResult> {
visitService(t: IDL.ServiceClass, data: VisitorData): VisitorResult {
return visitService(t, this, data);
Expand Down
17 changes: 17 additions & 0 deletions src/lib/stable/did_file/visitor/escape_candid_keywords.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
/**
* Internal list of Candid language keywords that need to be escaped in identifiers.
*
* @internal
* These keywords cannot be used as raw identifiers in Candid and must be quoted when used as field names.
*/
const CANDID_KEYWORDS = [
'blob',
'bool',
Expand Down Expand Up @@ -25,6 +31,17 @@ const CANDID_KEYWORDS = [
'vec'
];

/**
* Internal helper that quotes Candid keywords when they appear as identifiers.
*
* @internal
* @param key - The identifier to potentially escape
* @returns The identifier, quoted if it's a Candid keyword
*
* @example
* escapeCandidKeywords('text') // returns '"text"'
* escapeCandidKeywords('myField') // returns 'myField'
*/
export function escapeCandidKeywords(key: string): string {
if (CANDID_KEYWORDS.includes(key)) {
return `"${key}"`;
Expand Down
14 changes: 14 additions & 0 deletions src/lib/stable/did_file/visitor/extract_candid.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
import { CandidDef, CandidTypesDefs } from './did_visitor';

/**
* Internal helper for the Candid visitor system that combines multiple visitor results.
* Separates Candid definitions from their associated type definitions.
*
* @internal
* @param paramInfo - Array of visitor results, each containing a Candid definition and its type definitions
* @returns Tuple of [Array of Candid definitions, Combined type definitions map]
*
* @remarks
* Used by visitor components to:
* - Extract Candid definitions for method parameters, records, variants etc.
* - Merge type definitions from multiple visited nodes
* - Maintain type relationships in the final Candid interface
*/
export function extractCandid(
paramInfo: [CandidDef, CandidTypesDefs][]
): [CandidDef[], CandidTypesDefs] {
Expand Down
4 changes: 4 additions & 0 deletions src/lib/stable/did_file/visitor/visit/func.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { IDL } from '@dfinity/candid';
import { DidVisitor, VisitorData, VisitorResult } from '../did_visitor';
import { extractCandid } from '../extract_candid';

/**
* @internal
* Visitor for function types in Candid generation.
*/
export function visitFunc(
t: IDL.FuncClass,
didVisitor: DidVisitor,
Expand Down
4 changes: 4 additions & 0 deletions src/lib/stable/did_file/visitor/visit/opt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { IDL } from '@dfinity/candid';

import { DidVisitor, VisitorData, VisitorResult } from '../did_visitor';

/**
* @internal
* Visitor for optional types in Candid generation.
*/
export function visitOpt<T>(
ty: IDL.Type<T>,
didVisitor: DidVisitor,
Expand Down
4 changes: 4 additions & 0 deletions src/lib/stable/did_file/visitor/visit/primitive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { IDL } from '@dfinity/candid';

import { VisitorResult } from '../did_visitor';

/**
* @internal
* Visitor for primitive types in Candid generation.
*/
export function visitPrimitive<T>(t: IDL.PrimitiveType<T>): VisitorResult {
return [t.display(), {}];
}
4 changes: 4 additions & 0 deletions src/lib/stable/did_file/visitor/visit/record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { DidVisitor, VisitorData, VisitorResult } from '../did_visitor';
import { escapeCandidKeywords } from '../escape_candid_keywords';
import { extractCandid } from '../extract_candid';

/**
* @internal
* Visitor for record types in Candid generation.
*/
export function visitRecord(
fields: [string, IDL.Type<any>][],
didVisitor: DidVisitor,
Expand Down
4 changes: 4 additions & 0 deletions src/lib/stable/did_file/visitor/visit/recursive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { IDL } from '@dfinity/candid';

import { DidVisitor, VisitorData, VisitorResult } from '../did_visitor';

/**
* @internal
* Visitor for recursive types in Candid generation.
*/
export function visitRecursive<T>(
t: IDL.RecClass<T>,
ty: IDL.ConstructType<T>,
Expand Down
4 changes: 4 additions & 0 deletions src/lib/stable/did_file/visitor/visit/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import {
import { escapeCandidKeywords } from '../escape_candid_keywords';
import { extractCandid } from '../extract_candid';

/**
* @internal
* Visitor for service definitions in Candid generation.
*/
export function visitService(
t: IDL.ServiceClass,
didVisitor: DidVisitor,
Expand Down
4 changes: 4 additions & 0 deletions src/lib/stable/did_file/visitor/visit/tuple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { IDL } from '@dfinity/candid';
import { DidVisitor, VisitorData, VisitorResult } from '../did_visitor';
import { extractCandid } from '../extract_candid';

/**
* @internal
* Visitor for tuple types in Candid generation.
*/
export function visitTuple(
components: IDL.Type<any>[],
didVisitor: DidVisitor,
Expand Down
4 changes: 4 additions & 0 deletions src/lib/stable/did_file/visitor/visit/variant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { DidVisitor, VisitorData, VisitorResult } from '../did_visitor';
import { escapeCandidKeywords } from '../escape_candid_keywords';
import { extractCandid } from '../extract_candid';

/**
* @internal
* Visitor for variant types in Candid generation.
*/
export function visitVariant(
fields: [string, IDL.Type<any>][],
didVisitor: DidVisitor,
Expand Down
4 changes: 4 additions & 0 deletions src/lib/stable/did_file/visitor/visit/vec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { IDL } from '@dfinity/candid';

import { DidVisitor, VisitorData, VisitorResult } from '../did_visitor';

/**
* @internal
* Visitor for vector types in Candid generation.
*/
export function visitVec<T>(
ty: IDL.Type<T>,
didVisitor: DidVisitor,
Expand Down
9 changes: 9 additions & 0 deletions src/lib/stable/error.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import { trap } from './ic_apis/trap';

/**
* Handles uncaught errors in the canister execution environment by converting them
* to a formatted error message and calling IC trap. This function ensures that all
* uncaught errors are properly reported with stack traces before halting execution.
*
* @param rawError - The raw error value to handle. Can be an Error object or any other value
* @returns never - This function always traps and never returns
* @throws Calls IC trap with the formatted error message
*/
export function handleUncaughtError(rawError: any): never {
if (rawError instanceof Error) {
const error = rawError;
Expand Down
72 changes: 65 additions & 7 deletions src/lib/stable/execute_with_candid_serde.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,32 @@ import { IDL, JsonValue } from '@dfinity/candid';

import { reply } from './ic_apis';

/**
* Represents the different types of canister method execution modes.
*/
export type CanisterMethodMode =
| 'query'
| 'update'
| 'init'
| 'postUpgrade'
| 'preUpgrade'
| 'inspectMessage'
| 'heartbeat';
| 'query' // Read-only operations
| 'update' // State-modifying operations
| 'init' // Canister initialization
| 'postUpgrade' // After canister upgrade
| 'preUpgrade' // Before canister upgrade
| 'inspectMessage' // Message inspection
| 'heartbeat'; // Periodic heartbeat

/**
* Executes a canister method with Candid serialization/deserialization handling.
* This function manages the full lifecycle of a canister method call:
* 1. Decodes the input arguments from Candid format
* 2. Executes the callback with decoded arguments
* 3. Encodes and replies with the result
*
* @param mode - The execution mode of the canister method
* @param args - Raw Candid-encoded input arguments as bytes
* @param callback - The actual method implementation to execute
* @param paramIdlTypes - Candid type definitions for the input parameters
* @param returnIdlType - Candid type definition for the return value
* @param manual - If true, skips automatic reply handling
*/
export async function executeAndReplyWithCandidSerde(
mode: CanisterMethodMode,
args: Uint8Array,
Expand All @@ -24,6 +41,15 @@ export async function executeAndReplyWithCandidSerde(
encodeResultAndReply(mode, manual, unencodedResult, returnIdlType);
}

/**
* Decodes Candid-encoded arguments based on the method mode.
* Only decodes arguments for init, postUpgrade, query, and update methods.
*
* @param mode - The execution mode of the canister method
* @param args - Raw Candid-encoded input arguments
* @param paramIdlTypes - Candid type definitions for the parameters
* @returns Decoded argument values as a JSON-compatible array
*/
function decodeArgs(
mode: CanisterMethodMode,
args: Uint8Array,
Expand All @@ -41,13 +67,29 @@ function decodeArgs(
}
}

/**
* Executes the callback function with the decoded arguments.
*
* @param args - Decoded arguments to pass to the callback
* @param callback - The method implementation to execute
* @returns The result of the callback execution
*/
async function getUnencodedResult(
args: JsonValue[],
callback: (...args: any) => any
): Promise<any> {
return await callback(...args);
}

/**
* Handles the encoding and reply of the method result.
* Only sends replies for query and update methods when manual mode is not enabled.
*
* @param mode - The execution mode of the canister method
* @param manual - If true, skips automatic reply handling
* @param unencodedResult - The raw result from the callback
* @param returnIdlType - Candid type definition for the return value
*/
function encodeResultAndReply(
mode: CanisterMethodMode,
manual: boolean,
Expand All @@ -61,6 +103,14 @@ function encodeResultAndReply(
reply({ data: unencodedResult, idlType: returnIdlType });
}

/**
* Encodes JavaScript values into Candid format.
*
* @param argTypes - Candid type definitions for the values to encode
* @param args - Values to encode into Candid format
* @returns Candid-encoded data as bytes
* @throws {Error} If encoding fails
*/
export function idlEncode(
argTypes: Array<IDL.Type<any>>,
args: any[]
Expand All @@ -76,6 +126,14 @@ export function idlEncode(
}
}

/**
* Decodes Candid-encoded data into JavaScript values.
*
* @param retTypes - Candid type definitions for the values to decode
* @param bytes - Candid-encoded data to decode
* @returns Decoded JavaScript values
* @throws {Error} If decoding fails
*/
export function idlDecode(
retTypes: IDL.Type[],
bytes: Uint8Array
Expand Down
Loading
Loading