ProjectNeutrino Help

Bindings

Achieving parity with Type-Script in type-safety is certainly a goal.

Electron generates their .d.ts from their docs directly.

  1. The docs are parsed using @electron/docs-parser

  2. A json api is generated.

  3. The json api is then utilised by a separate package to generate the type definitions

Our parser uses the same source of truth to generate our bindings.

  1. We first parse the json into strong types based off the source

  2. We then map this to a more idiomatic FSharp representation.

  3. We can then either choose to further map this to an intermediary AST such as Glutinum to generate the source, or generate our own source using Fantomas

Parser Types

The types are crafted from the @electron/docs-parser/ParsedDocumentation.d.ts file which you can download ParsedDocumentation.d.ts to view.

My methodology to craft the types is to use Discriminated Unions to distinguish between types delineated by a tagged field such as type.

export declare type DetailedStringType = { type: 'String'; possibleValues: PossibleStringValue[] | null; }; export declare type DetailedObjectType = { type: 'Object'; properties: PropertyDocumentationBlock[]; }; export declare type DetailedEventType = { type: 'Event'; eventProperties: PropertyDocumentationBlock[]; }; export declare type DetailedEventReferenceType = { type: 'Event'; eventPropertiesReference: TypeInformation; }; export declare type DetailedFunctionType = { type: 'Function'; parameters: MethodParameterDocumentation[]; returns: TypeInformation | null; }; export declare type DetailedType = ({ type: TypeInformation[]; } | DetailedFunctionType | DetailedObjectType | DetailedEventType | DetailedEventReferenceType | DetailedStringType | { type: string; }) & { innerTypes?: TypeInformation[]; };
[<RequireQualifiedAccess>] module TypeInformationKind = type InfoArray = { Collection: bool Type: TypeInformation[] } type InfoString = { Collection: bool Type: string } type String = { Collection: bool PossibleValues: PossibleStringValue[] option } type Object = { Collection: bool Properties: PropertyDocumentationBlock[] } type Event = { Collection: bool EventProperties: PropertyDocumentationBlock[] } type EventRef = { Collection: bool EventPropertiesReference: TypeInformation } type Function = { Collection: bool Parameters: MethodParameterDocumentation[] Returns: TypeInformation option } type TypeInformationKind = | InfoArray of TypeInformationKind.InfoArray | InfoString of TypeInformationKind.InfoString | String of TypeInformationKind.String | Object of TypeInformationKind.Object | Event of TypeInformationKind.Event | EventRef of TypeInformationKind.EventRef | Function of TypeInformationKind.Function type TypeInformation = | WithInnerTypes of kind: TypeInformationKind * innerTypes: TypeInformation[] | TypeInformation of kind: TypeInformationKind

Where relevant, I would 'embed' inherited fields using a named field.

export declare type MethodParameterDocumentation = { name: string; description: string; required: boolean; } & TypeInformation;
type MethodParameterDocumentation = { Name: string Description: string Required: bool // Embedded TypeInformation: TypeInformation }

Immediately following the type definition, I would write the Thoth.Json.Net decoder.

type PropertyDocumentationBlock = { // Embedded DocumentationBlock: DocumentationBlock Required: bool // Embedded TypeInformation: TypeInformation } module PropertyDocumentationBlock = let decode: Decoder<PropertyDocumentationBlock> = Decode.object (fun get -> { DocumentationBlock = get.Required.Raw DocumentationBlock.decode Required = get.Required.Field "required" Decode.bool TypeInformation = get.Required.Raw TypeInformation.decode })
16 October 2025