Skip to content

Commit

Permalink
wip: prototype typings
Browse files Browse the repository at this point in the history
  • Loading branch information
Julusian committed Apr 8, 2024
1 parent 3402925 commit c67c3b6
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 3 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"nanoid": "^3.3.4",
"p-queue": "^6.6.2",
"p-timeout": "^4.1.0",
"tslib": "^2.6.2"
"tslib": "^2.6.2",
"type-fest": "^4.15.0"
},
"devDependencies": {
"@companion-module/tools": "^1.5.0",
Expand Down
72 changes: 72 additions & 0 deletions src/internal/strict-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { ConditionalKeys } from 'type-fest'
import type { CompanionOptionValues, CompanionActionContext } from '../module-api/index.js'
import type { CompanionCommonCallbackContext, StrictOptions, StrictOptionsObject } from '../module-api/common.js'

export class StrictOptionsImpl<TOptions> implements StrictOptions<TOptions> {
readonly #options: any
readonly #context: CompanionCommonCallbackContext
readonly #fields: StrictOptionsObject<TOptions, any>

constructor(
options: CompanionOptionValues,
context: CompanionActionContext,
fields: StrictOptionsObject<TOptions, any>
) {
this.#options = options
this.#context = context
this.#fields = fields
}

getRawJson(): any {
return { ...this.#options }
}
getRaw<Key extends keyof TOptions>(fieldName: Key): any {
// TODO - should this populate defaults?
return this.#options[fieldName]
}

getPlainString<Key extends ConditionalKeys<TOptions, string>>(fieldName: Key): TOptions[Key] {
const fieldSpec = this.#fields[fieldName]
const defaultValue = fieldSpec && 'default' in fieldSpec ? fieldSpec.default : undefined

const rawValue = this.#options[fieldName]
if (defaultValue !== undefined && rawValue === undefined) return String(defaultValue) as any

return String(rawValue) as any
}

getPlainNumber<Key extends ConditionalKeys<TOptions, number>>(fieldName: Key): TOptions[Key] {
const fieldSpec = this.#fields[fieldName]
const defaultValue = fieldSpec && 'default' in fieldSpec ? fieldSpec.default : undefined

const rawValue = this.#options[fieldName]
if (defaultValue !== undefined && rawValue === undefined) return Number(defaultValue) as any

const value = Number(rawValue)
if (isNaN(value)) {
throw new Error(`Invalid option '${String(fieldName)}'`)
}
return value as any
}

getPlainBoolean<Key extends ConditionalKeys<TOptions, boolean>>(fieldName: Key): boolean {
const fieldSpec = this.#fields[fieldName]
const defaultValue = fieldSpec && 'default' in fieldSpec ? fieldSpec.default : undefined

const rawValue = this.#options[fieldName]
if (defaultValue !== undefined && rawValue === undefined) return Boolean(defaultValue)

return Boolean(rawValue)
}

async getParsedString<Key extends ConditionalKeys<TOptions, string | undefined>>(fieldName: Key): Promise<string> {
const rawValue = this.#options[fieldName]

return this.#context.parseVariablesInString(rawValue)
}
async getParsedNumber<Key extends ConditionalKeys<TOptions, string | undefined>>(fieldName: Key): Promise<number> {
const str = await this.getParsedString(fieldName)

return Number(str)
}
}
60 changes: 59 additions & 1 deletion src/module-api/action.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CompanionCommonCallbackContext } from './common.js'
import type { CompanionCommonCallbackContext, StrictOptions, StrictOptionsObject } from './common.js'
import type {
CompanionOptionValues,
CompanionInputFieldCheckbox,
Expand Down Expand Up @@ -30,6 +30,8 @@ export type CompanionActionContext = CompanionCommonCallbackContext
* The definition of an action
*/
export interface CompanionActionDefinition {
type?: 'loose'

/** Name to show in the actions list */
name: string
/** Additional description of the action */
Expand Down Expand Up @@ -93,3 +95,59 @@ export interface CompanionActionEvent extends CompanionActionInfo {
/** Identifier of the surface which triggered this action */
readonly surfaceId: string | undefined
}

/**
* Basic information about an instance of an action
*/
export interface StrictActionInfo<TOptions> {
/** The unique id for this action */
readonly id: string
/** The unique id for the location of this action */
readonly controlId: string
/** The id of the action definition */
readonly actionId: string
/** The user selected options for the action */
readonly options: StrictOptions<TOptions>
}
/**
* Extended information for execution of an action
*/
export interface StrictActionEvent<TOptions> extends StrictActionInfo<TOptions> {
/** Identifier of the surface which triggered this action */
readonly surfaceId: string | undefined
}

export interface StrictActionDefinition<TOptions> {
type: 'strict'

/** Name to show in the actions list */
name: string
/** Additional description of the action */
description?: string
/** The input fields for the action */
options: StrictOptionsObject<TOptions, SomeCompanionActionInputField>

/** Called to execute the action */
callback: (action: StrictActionEvent<TOptions>, context: CompanionActionContext) => Promise<void> | void
/**
* Called to report the existence of an action
* Useful to ensure necessary data is loaded
*/
subscribe?: (action: StrictActionInfo<TOptions>, context: CompanionActionContext) => Promise<void> | void
/**
* Called to report an action has been edited/removed
* Useful to cleanup subscriptions setup in subscribe
*/
unsubscribe?: (action: StrictActionInfo<TOptions>, context: CompanionActionContext) => Promise<void> | void
/**
* The user requested to 'learn' the values for this action.
*/
learn?: (
action: StrictActionEvent<TOptions>,
context: CompanionActionContext
) => TOptions | undefined | Promise<TOptions | undefined>
}

export type StrictActionDefinitions<TTypes> = {
[Key in keyof TTypes]: StrictActionDefinition<TTypes[Key]> | undefined
}
21 changes: 21 additions & 0 deletions src/module-api/common.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import type { ConditionalKeys } from 'type-fest'
import type { CompanionInputFieldBase } from './input.js'

/**
* Utility functions available in the context of an action/feedback
*/
Expand All @@ -10,3 +13,21 @@ export interface CompanionCommonCallbackContext {
*/
parseVariablesInString(text: string): Promise<string>
}

export type StrictOptionsObject<TOptions, TFields extends CompanionInputFieldBase> = {
[K in keyof TOptions]: undefined extends TOptions[K] ? TFields | undefined : TFields
}

/**
*
*/
export interface StrictOptions<TOptions> {
getRawJson(): any
getRaw<Key extends keyof TOptions>(fieldName: Key): TOptions[Key] | undefined
getPlainString<Key extends ConditionalKeys<TOptions, string>>(fieldName: Key): TOptions[Key]
getPlainNumber<Key extends ConditionalKeys<TOptions, number>>(fieldName: Key): TOptions[Key]
getPlainBoolean<Key extends ConditionalKeys<TOptions, boolean>>(fieldName: Key): boolean

getParsedString<Key extends ConditionalKeys<TOptions, string | undefined>>(fieldName: Key): Promise<string>
getParsedNumber<Key extends ConditionalKeys<TOptions, string | undefined>>(fieldName: Key): Promise<number>
}
96 changes: 95 additions & 1 deletion src/module-api/feedback.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CompanionCommonCallbackContext } from './common.js'
import type { CompanionCommonCallbackContext, StrictOptions, StrictOptionsObject } from './common.js'
import type {
CompanionOptionValues,
CompanionInputFieldStaticText,
Expand Down Expand Up @@ -161,3 +161,97 @@ export type CompanionFeedbackDefinition = CompanionBooleanFeedbackDefinition | C
export interface CompanionFeedbackDefinitions {
[id: string]: CompanionFeedbackDefinition | undefined
}

/**
* Basic information about an instance of an Feedback
*/
export interface StrictFeedbackInfo<TOptions> {
/** The type of the feedback */
readonly type: 'boolean-strict' | 'advanced-strict'
/** The unique id for this feedback */
readonly id: string
/** The unique id for the location of this feedback */
readonly controlId: string
/** The id of the feedback definition */
readonly feedbackId: string
/** The user selected options for the feedback */
readonly options: StrictOptions<TOptions>
}
/**
* Extended information for execution of an Feedback
*/
export type StrictBooleanFeedbackEvent<TOptions> = StrictFeedbackInfo<TOptions>

/**
* Extended information for execution of an advanced feedback
*/
export interface StrictAdvancedFeedbackEvent<TOptions> extends StrictFeedbackInfo<TOptions> {
/** If control supports an imageBuffer, the dimensions the buffer should be */
readonly image?: {
readonly width: number
readonly height: number
}
}

export interface StrictFeedbackDefinitionBase<TOptions> {
/** Name to show in the Feedbacks list */
name: string
/** Additional description of the Feedback */
description?: string
/** The input fields for the Feedback */
options: StrictOptionsObject<TOptions, SomeCompanionFeedbackInputField>

/**
* Called to report the existence of an Feedback
* Useful to ensure necessary data is loaded
*/
subscribe?: (Feedback: StrictFeedbackInfo<TOptions>, context: CompanionFeedbackContext) => Promise<void> | void
/**
* Called to report an Feedback has been edited/removed
* Useful to cleanup subscriptions setup in subscribe
*/
unsubscribe?: (Feedback: StrictFeedbackInfo<TOptions>, context: CompanionFeedbackContext) => Promise<void> | void
/**
* The user requested to 'learn' the values for this Feedback.
*/
learn?: (
Feedback: StrictFeedbackInfo<TOptions>,
context: CompanionFeedbackContext
) => TOptions | undefined | Promise<TOptions | undefined>
}

export interface StrictBooleanFeedbackDefinition<TOptions> extends StrictFeedbackDefinitionBase<TOptions> {
type: 'boolean-strict'

/** The default style properties for this feedback */
defaultStyle: Partial<CompanionFeedbackButtonStyleResult>

/**
* If `undefined` or true, Companion will add an 'Inverted' checkbox for your feedback, and handle the logic for you.
* By setting this to false, you can disable this for your feedback. You should do this if it does not make sense for your feedback.
*/
showInvert?: boolean

/** Called to execute the Feedback */
callback: (
Feedback: StrictBooleanFeedbackEvent<TOptions>,
context: CompanionFeedbackContext
) => Promise<boolean> | boolean
}
export interface StrictAdvancedFeedbackDefinition<TOptions> extends StrictFeedbackDefinitionBase<TOptions> {
type: 'advanced-strict'

/** Called to execute the Feedback */
callback: (
Feedback: StrictAdvancedFeedbackEvent<TOptions>,
context: CompanionFeedbackContext
) => Promise<CompanionAdvancedFeedbackResult> | CompanionAdvancedFeedbackResult
}

export declare type StrictFeedbackDefinition<TOption> =
| StrictBooleanFeedbackDefinition<TOption>
| StrictAdvancedFeedbackDefinition<TOption>

export type StrictFeedbackDefinitions<TTypes> = {
[Key in keyof TTypes]: StrictFeedbackDefinition<TTypes[Key]> | undefined
}
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3932,6 +3932,11 @@ type-fest@^0.21.3:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==

type-fest@^4.15.0:
version "4.15.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.15.0.tgz#21da206b89c15774cc718c4f2d693e13a1a14a43"
integrity sha512-tB9lu0pQpX5KJq54g+oHOLumOx+pMep4RaM6liXh2PKmVRFF+/vAtUP0ZaJ0kOySfVNjF6doBWPHhBhISKdlIA==

type@^2.7.2:
version "2.7.2"
resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0"
Expand Down

0 comments on commit c67c3b6

Please sign in to comment.