// // Copyright 2023 DXOS.org // import { Schema } from 'effect'; export type IntentParams = { readonly input: Schema.Schema.All; readonly output: Schema.Schema.All; }; export type IntentData = Schema.Schema.Type> extends { readonly input: any } ? Schema.Schema.Type>['input'] : any; export type IntentResultData = Schema.Schema.Type> extends { readonly output: any } ? Schema.Schema.Type>['output'] : any; export type IntentSchema = Schema.TaggedClass; /** * An intent is an abstract description of an operation to be performed. * Intents allow actions to be performed across plugins. */ export type Intent = { _schema: IntentSchema; /** * The id of the intent. */ id: Tag; /** * Any data needed to perform the desired action. */ data: IntentData; /** * Whether or not the intent is being undone. */ undo?: boolean; }; export type AnyIntent = Intent; /** * Chain of intents to be executed together. * The result of each intent is merged into the next intent's input data. */ export type IntentChain< FirstTag extends string, LastTag extends string, FirstFields extends IntentParams, LastFields extends IntentParams, > = { first: Intent; last: Intent; all: AnyIntent[]; }; export type AnyIntentChain = IntentChain; /** * Creates a typed intent. * @param schema Schema of the intent. Must be a tagged class with input and output schemas. * @param data Data fulfilling the input schema of the intent. * @param params.plugin Optional plugin ID to send the intent to. * @param params.undo Optional flag to indicate that the intent is being undone. Generally not set manually. */ export const createIntent = ( schema: IntentSchema, data: IntentData = {}, params: Pick = {}, ): IntentChain => { // The output of validateSync breaks proxy objects so this is just used for validation. // TODO(wittjosiah): Is there a better way to make theses types align? const _ = Schema.validateSync(schema.fields.input as Schema.Schema)(data); const intent = { ...params, _schema: schema, id: schema._tag, data, } satisfies Intent; return { first: intent, last: intent, all: [intent], }; }; // TODO(wittjosiah): Add a function for mapping the output of one intent to the input of another. /** * Chain two intents together. * * NOTE: Chaining of intents depends on the data inputs and outputs being structs. */ export const chain = < FirstTag extends string, NextTag extends string, FirstFields extends IntentParams, LastFields extends IntentParams, NextFields extends IntentParams, >( schema: IntentSchema, data: Omit, keyof IntentResultData> = {}, params: Pick = {}, ) => ( intent: IntentChain, ): IntentChain => { const intents = 'all' in intent ? intent.all : [intent]; const first = intents[0]; const last = { ...params, _schema: schema, id: schema._tag, data, } satisfies Intent; return { first, last, all: [...intents, last], }; }; // // Intents // // NOTE: Should maintain compatibility with `i18next` (and @dxos/react-ui). // TODO(wittjosiah): Making this immutable breaks type compatibility. export const Label = Schema.Union( Schema.String, Schema.mutable( Schema.Tuple( Schema.String, Schema.mutable( Schema.Struct({ ns: Schema.String, count: Schema.optional(Schema.Number), defaultValue: Schema.optional(Schema.String), }), ), ), ), ); export type Label = Schema.Schema.Type;