diff --git a/src/commands/AddEntry.ts b/src/commands/AddEntry.ts index 5804da4..46d4646 100644 --- a/src/commands/AddEntry.ts +++ b/src/commands/AddEntry.ts @@ -33,30 +33,41 @@ export function addEntryCommand(): CommandModule { const empathyList = guidedEmpathyListPrompt({inquire, promptForEmpathy: empathy}) const mainMenuItems = [ + // TODO: add ability to amend previous entry condition.mainMenu, summary.mainMenu, new Separator(), journal.mainMenu, empathyList.mainMenu, + // TODO: Modified HierarchicalCheckboxInput to allow for putting more things in the main menu than + // there are letters + // TODO: gratitude journaling + // TODO: thinking about the future - where do you want to be in x years type thing + // TODO: breathing regulation exercises + // TODO: Goals for the day and checking in on how yesterday's goals went /* { name: typeof entry.needs === "object" ? "Check back in on needs" : "Check in on needs", + // TODO: physical needs: food, water, sleep, exercise tracking, possibly with autocompletes for each saved in a file like the empathy guide value: NEEDS, key: "n" }, { name: typeof entry.personas === "object" ? "Check back in on personas" : "Check in on personas", + // TODO: Personas' individual journal entries value: PERSONA, key: "p" }, {name: typeof entry.rpg === "object" ? "Change RPG stats" : "Add RPG stats", value: RPG, key: "r"}, { name: typeof entry.activities === "object" ? "Change record of recent activities" : "Add record of recent activities", + // TODO: Add general activity set with historical activities saved in a file like the empathy guide value: ACTIVITIES, key: "a" }, { name: typeof entry.music === "object" ? "Change record of recently played music" : "Add record of recently played music", + // TODO: Add music value: MUSIC, key: "m" }, @@ -65,6 +76,7 @@ export function addEntryCommand(): CommandModule { /* { name: typeof entry.recoveries === "object" ? "Try more recovery methods" : "Try some recovery methods", + // TODO: Add list of recovery methods value: RECOVERIES, key: "y" }, diff --git a/src/prompts/Inquire.ts b/src/prompts/Inquire.ts index c4293e6..9a17d09 100644 --- a/src/prompts/Inquire.ts +++ b/src/prompts/Inquire.ts @@ -1,9 +1,9 @@ -import {prompt, QuestionCollection} from "inquirer"; +import {prompt, QuestionMap} from "inquirer"; -export type InquireFunction = (question: Omit) => Promise +export type InquireFunction = (question: QuestionT) => Promise export type ShowFunction = (text: string) => Promise -export async function inquire(question: Omit): Promise { +export async function inquire(question: QuestionT): Promise { const result = await prompt([{...question, name: "answer"}]) return result.answer } \ No newline at end of file diff --git a/src/prompts/implementations/ConditionPrompt.ts b/src/prompts/implementations/ConditionPrompt.ts index 08902e7..096cb79 100644 --- a/src/prompts/implementations/ConditionPrompt.ts +++ b/src/prompts/implementations/ConditionPrompt.ts @@ -10,7 +10,7 @@ export interface ConditionPromptOptions extends Partial } export function conditionPrompt(deps: ConditionPromptDependencies): { diff --git a/src/prompts/implementations/EmpathyGroupListPrompt.ts b/src/prompts/implementations/EmpathyGroupListPrompt.ts index 01a7e64..fa3f41d 100644 --- a/src/prompts/implementations/EmpathyGroupListPrompt.ts +++ b/src/prompts/implementations/EmpathyGroupListPrompt.ts @@ -4,6 +4,7 @@ import chalk from "chalk"; import pluralize from "pluralize"; import {InquireFunction} from "../Inquire"; import {EmpathyGroupPromptOptions} from "./EmpathyGroupPrompt"; +import {ListQuestion} from "inquirer"; export interface EmpathyGroupListPromptOptions { readonly default?: readonly EmpathyGroup[] @@ -11,7 +12,7 @@ export interface EmpathyGroupListPromptOptions { } export interface EmpathyGroupListPromptDependencies { - readonly inquire: InquireFunction + readonly inquire: InquireFunction readonly promptForEmpathyGroup: (opts: EmpathyGroupPromptOptions) => Promise } diff --git a/src/prompts/implementations/EmpathyGroupPrompt.ts b/src/prompts/implementations/EmpathyGroupPrompt.ts index 19c9d35..6b4a3a4 100644 --- a/src/prompts/implementations/EmpathyGroupPrompt.ts +++ b/src/prompts/implementations/EmpathyGroupPrompt.ts @@ -1,8 +1,9 @@ import capitalize from "capitalize"; -import {editYaml} from "../../schemata/YAMLPrompt"; +import {editYaml, EditYamlInquire} from "../../schemata/YAMLPrompt"; import chalk from "chalk"; import {InquireFunction, ShowFunction} from "../Inquire"; import {EmpathyGroup, EmpathyGroupJTD} from "../../datatypes/EmpathyGroup"; +import {ExpandQuestion, InputQuestion, MultiTextInputQuestion} from "inquirer"; export interface EmpathyGroupPromptOptions { default?: EmpathyGroup @@ -10,7 +11,7 @@ export interface EmpathyGroupPromptOptions { } export interface EmpathyGroupPromptDependencies { - inquire: InquireFunction + inquire: InquireFunction & EditYamlInquire & InquireFunction & InquireFunction showError: ShowFunction } diff --git a/src/prompts/implementations/EmpathyGuidePrompt.ts b/src/prompts/implementations/EmpathyGuidePrompt.ts index 427f417..10a0e1e 100644 --- a/src/prompts/implementations/EmpathyGuidePrompt.ts +++ b/src/prompts/implementations/EmpathyGuidePrompt.ts @@ -2,8 +2,8 @@ import {isPopulatedArray} from "../../utils/Arrays"; import chalk from "chalk"; import pluralize from "pluralize"; import {totalItems} from "../../datatypes/EmpathyGroupList"; -import {Separator} from "inquirer"; -import {editYaml} from "../../schemata/YAMLPrompt"; +import {ExpandQuestion, Separator} from "inquirer"; +import {editYaml, EditYamlInquire} from "../../schemata/YAMLPrompt"; import {InquireFunction, ShowFunction} from "../Inquire"; import {EmpathyGroupListPromptOptions} from "./EmpathyGroupListPrompt"; import {EmpathyGroup} from "../../datatypes/EmpathyGroup"; @@ -14,7 +14,7 @@ export interface EmpathyGuidePromptOptions { } export interface EmpathyGuidePromptDependencies { - readonly inquire: InquireFunction + readonly inquire: InquireFunction & EditYamlInquire readonly showError: ShowFunction readonly promptForEmpathyGroupList: (opts: EmpathyGroupListPromptOptions) => Promise } diff --git a/src/prompts/implementations/EntryMainMenuPrompt.ts b/src/prompts/implementations/EntryMainMenuPrompt.ts index 879542e..e6d5b4f 100644 --- a/src/prompts/implementations/EntryMainMenuPrompt.ts +++ b/src/prompts/implementations/EntryMainMenuPrompt.ts @@ -1,13 +1,15 @@ import {InquireFunction, ShowFunction} from "../Inquire"; -import {ExpandChoiceOptions, Separator, SeparatorOptions} from "inquirer"; +import {ExpandChoiceOptions, ExpandQuestion, Separator, SeparatorOptions} from "inquirer"; import {merge} from "../../utils/Merge"; import {Entry, EntryJTD} from "../../datatypes/Entry"; -import {editYaml} from "../../schemata/YAMLPrompt"; +import {editYaml, EditYamlInquire} from "../../schemata/YAMLPrompt"; const VIEW = ".View" const DONE = ".Done" -export interface EntryMainMenuChoice { +type EntryMainMenuChoiceKey = string & keyof EntryMainMenuOptions + +export interface EntryMainMenuChoice { readonly type: "mainMenu" readonly property: PropertyT choice(input: EntryMainMenuOptions[PropertyT]): ExpandChoiceOptions & { value: PropertyT } @@ -57,8 +59,8 @@ export function makeEntryMainMenuChoice {} export interface EntryMainMenuDependencies { - readonly inquire: InquireFunction - readonly choices: readonly (EntryMainMenuChoice|SeparatorOptions)[] + readonly inquire: InquireFunction & EditYamlInquire + readonly choices: readonly (EntryMainMenuChoice|SeparatorOptions)[] readonly showError: ShowFunction } @@ -106,7 +108,7 @@ export async function promptForEntryMainMenu(entry: EntryMainMenuOptions, deps: const withoutFinishedAt = {...updated, finishedAt: undefined} return continueWith(withoutFinishedAt) } else { - const selectedChoice = choices.find((choice) => choice.type === "mainMenu" && choice.property === result) as EntryMainMenuChoice + const selectedChoice = choices.find((choice) => choice.type === "mainMenu" && choice.property === result) as EntryMainMenuChoice return continueWith(await onSelected(selectedChoice, entry)) } } \ No newline at end of file diff --git a/src/prompts/implementations/GuidedEmpathyListPrompt.ts b/src/prompts/implementations/GuidedEmpathyListPrompt.ts index a85694a..d64f500 100644 --- a/src/prompts/implementations/GuidedEmpathyListPrompt.ts +++ b/src/prompts/implementations/GuidedEmpathyListPrompt.ts @@ -5,7 +5,7 @@ import {EntryMainMenuChoice, makeEntryMainMenuChoice} from "./EntryMainMenuPromp import chalk from "chalk"; import pluralize from "pluralize"; import {isPopulatedArray} from "../../utils/Arrays"; -import {ListChoiceOptions} from "inquirer"; +import {ListChoiceOptions, ListQuestion} from "inquirer"; import {asDefault} from "../../utils/Objects"; import {GuidedEmpathyPromptOptions} from "./GuidedEmpathyPrompt"; @@ -14,7 +14,7 @@ export interface GuidedEmpathyListPromptOptions extends PersonaPrompt { } export interface GuidedEmpathyListPromptDependencies { - readonly inquire: InquireFunction + readonly inquire: InquireFunction readonly promptForEmpathy: (input: GuidedEmpathyPromptOptions) => Promise } diff --git a/src/prompts/implementations/GuidedEmpathyPrompt.ts b/src/prompts/implementations/GuidedEmpathyPrompt.ts index 532a2b5..26311e1 100644 --- a/src/prompts/implementations/GuidedEmpathyPrompt.ts +++ b/src/prompts/implementations/GuidedEmpathyPrompt.ts @@ -11,14 +11,14 @@ import {isPopulatedArray} from "../../utils/Arrays"; import Separator from "inquirer/lib/objects/separator"; import {EmpathyGroup} from "../../datatypes/EmpathyGroup"; import {GuidedEmpathy, guidedEmpathyToString, isPopulatedGuidedEmpathy} from "../../datatypes/GuidedEmpathy"; -import {HierarchicalCheckboxChildChoice, HierarchicalCheckboxParentChoice } from "inquirer"; +import {HierarchicalCheckboxChildChoice, HierarchicalCheckboxParentChoice, HierarchicalCheckboxQuestion, MultiTextInputQuestion } from "inquirer"; export interface GuidedEmpathyPromptOptions extends PersonaPrompt { readonly default?: GuidedEmpathy } export interface GuidedEmpathyPromptDependencies { - readonly inquire: InquireFunction, + readonly inquire: InquireFunction & InquireFunction readonly guideFactory: () => EmpathyGuide, readonly show: ShowFunction, } diff --git a/src/prompts/implementations/JournalEntryPrompt.ts b/src/prompts/implementations/JournalEntryPrompt.ts index 62f2d58..1074350 100644 --- a/src/prompts/implementations/JournalEntryPrompt.ts +++ b/src/prompts/implementations/JournalEntryPrompt.ts @@ -10,7 +10,7 @@ import {asDefault} from "../../utils/Objects"; export interface JournalEntryPromptOptions extends Partial>, PersonaPrompt {} export interface JournalEntryPromptDependencies { - readonly inquire: InquireFunction + readonly inquire: InquireFunction } export function journalEntryPrompt(deps: JournalEntryPromptDependencies): { diff --git a/src/prompts/implementations/SuicidalityPrompt.ts b/src/prompts/implementations/SuicidalityPrompt.ts index 1902d04..9188c4c 100644 --- a/src/prompts/implementations/SuicidalityPrompt.ts +++ b/src/prompts/implementations/SuicidalityPrompt.ts @@ -9,7 +9,7 @@ export interface SuicidalityPromptOptions extends Partial } export function suicidalityPrompt(deps: SuicidalityPromptDependencies): { diff --git a/src/prompts/implementations/SummaryPrompt.ts b/src/prompts/implementations/SummaryPrompt.ts index fd12c24..f27e6ca 100644 --- a/src/prompts/implementations/SummaryPrompt.ts +++ b/src/prompts/implementations/SummaryPrompt.ts @@ -10,7 +10,7 @@ import {asDefault} from "../../utils/Objects"; export interface SummaryPromptOptions extends Partial>, PersonaPrompt {} export interface SummaryPromptDependencies { - readonly inquire: InquireFunction + readonly inquire: InquireFunction } export function summaryPrompt(deps: SummaryPromptDependencies): { diff --git a/src/prompts/types/HierarchicalCheckboxInput.ts b/src/prompts/types/HierarchicalCheckboxInput.ts index 0a831ca..73ac803 100644 --- a/src/prompts/types/HierarchicalCheckboxInput.ts +++ b/src/prompts/types/HierarchicalCheckboxInput.ts @@ -17,6 +17,7 @@ import figures from "figures"; import Paginator from "inquirer/lib/utils/paginator"; import {isPopulatedArray} from "../../utils/Arrays"; import {filter as fuzzyFilter, FilterOptions} from "fuzzy"; +import ScreenManager from "inquirer/lib/utils/screen-manager"; interface ExtendedReadLine extends ReadLineInterface { line: string @@ -51,7 +52,7 @@ declare module "inquirer" { export interface HierarchicalCheckboxQuestion extends Question, HierarchicalCheckboxQuestionOptions { /** @inheritDoc */ type: "hierarchical-checkbox" - default?: string[] + default?: readonly string[] } export interface QuestionMap { @@ -59,6 +60,14 @@ declare module "inquirer" { } } +interface ExtendedPaginatorConstructor { + new(manager: ScreenManager, options: {isInfinite:boolean}): ExtendedPaginator +} + +interface ExtendedPaginator extends Paginator { + paginate(content: string, selectedIndex: number, pageSize?: number): string; +} + interface NormalizedChoiceBase { readonly name: string readonly value: string|null @@ -260,14 +269,17 @@ export class HierarchicalCheckboxInput void) { + _run(callback: Exclude, error?: (err: Error) => void) { const invalidChoices = Object.keys(this.invalidValues); const invalidValues = this.value.filter((value) => !this.valueMap.hasOwnProperty(value)); if (isPopulatedArray(invalidChoices)) { - error(Error(`Duplicate values: ${invalidChoices.join()}`)) + if (error) { + error(Error(`Duplicate values: ${invalidChoices.join()}`)) + } } else if (isPopulatedArray(invalidValues)) { - error(Error(`Unknown values: ${invalidValues.join()}`)) + if (error) { + error(Error(`Unknown values: ${invalidValues.join()}`)) + } } this.rl.on("history", (history) => { history.splice(0, history.length) @@ -294,13 +306,12 @@ export class HierarchicalCheckboxInput void|null + private done: ((state: unknown) => void)|null private location: Breadcrumb private activeIndex: number private unlistedValues: string[] private submitter: Subject = new Subject() - // @ts-ignore - private paginator: Paginator = new Paginator(this.screen, {isInfinite: true}); + private paginator: ExtendedPaginator = new (Paginator as ExtendedPaginatorConstructor)(this.screen, {isInfinite: true}); private scheduledRender: NodeJS.Immediate|null = null private get readline(): ExtendedReadLine { @@ -563,7 +574,7 @@ export class HierarchicalCheckboxInput).map((result) => ({ @@ -644,7 +655,9 @@ export class HierarchicalCheckboxInput extends Question, MultiTextInputQuestionOptions { /** @inheritDoc */ type: "multitext" - default?: string[] + default?: readonly string[] } export interface QuestionMap { - ["multitext"]: HierarchicalCheckboxQuestion + ["multitext"]: MultiTextInputQuestion } } @@ -60,7 +60,7 @@ export class MultiTextInput void|null + private done: (state: unknown) => void|null private activeIndex: number private readonly value: string[] private submitter: Subject = new Subject() diff --git a/src/repository/LocalRepository.ts b/src/repository/LocalRepository.ts index 566e4ae..fc444d7 100644 --- a/src/repository/LocalRepository.ts +++ b/src/repository/LocalRepository.ts @@ -5,7 +5,7 @@ import { ReferencedTypes, schema, SchemaData, - UntypedReferenceList + AnyReferenceList, Value } from "../schemata/SchemaData"; import {rm, open, FileHandle} from "fs/promises"; import {join, dirname} from "path"; @@ -25,6 +25,12 @@ export const AutosaveFileJTD = schema({ references: [] }) +export interface LocalRepositoryDependencies { + rm: typeof rm + open: typeof open + makeDir: typeof makeDir +} + export class LocalRepository { private readonly paths: envPaths.Paths @@ -120,10 +126,11 @@ export class LocalRepository { } } - async loadAutosaveObjects( + // TODO: Enable autosave for all commands + async loadAutosaveObjects( {autosaveNamespace, schemas}: { autosaveNamespace: string, schemas: TypesT }): Promise<{ validated: Partial>, - unvalidated: Partial, unknown>> + unvalidated: Partial, string>> }> { const path = this.getAutosaveFilePath(autosaveNamespace) let handle: FileHandle @@ -143,14 +150,14 @@ export class LocalRepository { throw Error(`Autosave file ${autosaveNamespace} was too corrupted to make sense of`) } const validated: Partial> = {} - const unvalidated: Partial, unknown>> = {} + const unvalidated: Partial, string>> = {} schemas.forEach((schema: ReferencedSchema) => { if (autosaved.hasOwnProperty(schema.key)) { const value = autosaved[schema.key] if (!schema.validate(value)) { unvalidated[schema.key as keyof ReferencedTypes] = value } else { - validated[schema.key as keyof ReferencedTypes] = value + validated[schema.key as keyof ReferencedTypes] = value as Value> } } }) @@ -160,7 +167,7 @@ export class LocalRepository { } } - async saveAutosaveObject({schema, value, autosaveNamespace}: {autosaveNamespace: string, schema: SchemaData, value: T}): Promise { + async saveAutosaveObject({schema, value, autosaveNamespace}: {autosaveNamespace: string, schema: SchemaData>, value: T}): Promise { const path = this.getAutosaveFilePath(autosaveNamespace) await makeDir(dirname(path)) const handle = await open(path, "a+") @@ -178,7 +185,7 @@ export class LocalRepository { } } - async clearAutosaveObject({schema, autosaveNamespace}: {autosaveNamespace: string, schema: SchemaData}): Promise { + async clearAutosaveObject({schema, autosaveNamespace}: {autosaveNamespace: string, schema: SchemaData>}): Promise { const path = this.getAutosaveFilePath(autosaveNamespace) await makeDir(dirname(path)) const handle = await open(path, "a+") diff --git a/src/schemata/SchemaData.ts b/src/schemata/SchemaData.ts index 82f7f4c..fdd45b4 100644 --- a/src/schemata/SchemaData.ts +++ b/src/schemata/SchemaData.ts @@ -3,7 +3,18 @@ import AJV, {ValidateFunction, JTDSchemaType} from "ajv/dist/jtd"; const ajv = new AJV(); -export type SchemaData> = { +interface BaseSchemaData { + value?: RepresentedT, + schema: unknown, + key: ReferenceT, + definition: { [key in ReferenceT]: unknown } + referenced?: { [key in ReferenceT]: RepresentedT } + reference: { "ref": ReferenceT }, + validate: ValidateFunction + requiredReferences: AnyReferenceList +} + +export interface SchemaData> extends BaseSchemaData { value?: RepresentedT, schema: JTDSchemaType, key: ReferenceT, @@ -14,35 +25,38 @@ export type SchemaData[] +export type AnySchemaData = BaseSchemaData +export type AnySchemaDataFor = BaseSchemaData +export type AnyReferenceList = AnySchemaData[] +export type AnyDefinitions = Record -export type Schema> = Exclude -export type Value> = Exclude -export type Definition> = Exclude -export type Reference> = Exclude -export type ReferenceKey> = Exclude -export type Referenced> = Exclude +export type Schema = Exclude +export type Value = Exclude +export type Definition = Exclude +export type Reference = Exclude +export type ReferenceKey = Exclude +export type Referenced = Exclude -export type ReferencedSchemaMap = { - [Property in keyof ReferencesT as ReferencesT[Property] extends SchemaData ? ReferenceKey : never]: ReferencesT[Property] extends SchemaData ? ReferencesT[Property] : never +export type ReferencedSchemaMap = { + [Property in keyof ReferencesT as ReferencesT[Property] extends AnySchemaData ? ReferenceKey : never]: ReferencesT[Property] extends AnySchemaData ? ReferencesT[Property] : never } -export type ReferencedSchema = ReferencedSchemaMap[keyof ReferencedSchemaMap] -export type ReferencedDefinitions = { +export type ReferencedSchema = ReferencedSchemaMap[keyof ReferencedSchemaMap] +export type ReferencedDefinitions = { [Property in keyof ReferencedSchemaMap]?: Definition[Property]> } -export type ReferencedTypes = { +export type ReferencedTypes = { [Property in keyof ReferencedSchemaMap]?: Value[Property]> } export function schema< RepresentedT, KeyT extends string, - ReferencesT extends SchemaData>[], + ReferencesT extends AnySchemaData[], >({schema, key, references}: { schema: JTDSchemaType>, key: KeyT, references: ReferencesT, typeHint?: RepresentedT|null }): SchemaData> { const definition = {[key]: schema} as Definition>> const reference = {ref: key} - const definitions = references.reduce((definitions, reference) => ({...reference.definition, ...definitions}), {} as ReferencedDefinitions) - const validate = ajv.compile({ ...schema, definitions }) + const definitions = references.reduce((definitions, reference) => ({...reference.definition, ...definitions}), definition) + const validate = ajv.compile({ ...reference, definitions }) return { schema, key, diff --git a/src/schemata/YAMLPrompt.ts b/src/schemata/YAMLPrompt.ts index 4561384..bb8e651 100644 --- a/src/schemata/YAMLPrompt.ts +++ b/src/schemata/YAMLPrompt.ts @@ -1,14 +1,17 @@ -import {SchemaData} from "./SchemaData"; +import {AnySchemaDataFor} from "./SchemaData"; import {dump, load} from "js-yaml"; -import {EditorQuestionOptions} from "inquirer"; import {InquireFunction, ShowFunction} from "../prompts/Inquire"; +import {EditorQuestion, ExpandQuestion} from "inquirer"; const RETRY = "Retry" const ABORT = "Abort" -export async function editYaml({schema, currentValue, name, inquire, showError}: {schema: SchemaData, currentValue: ObjectT, name: string, inquire: InquireFunction, showError: ShowFunction}): Promise -export async function editYaml({schema, currentValue, name, inquire, showError}: {schema: SchemaData, currentValue: ObjectT|undefined, name: string, inquire: InquireFunction, showError: ShowFunction}): Promise -export async function editYaml({schema, currentValue, name, inquire, showError}: {schema: SchemaData, currentValue: ObjectT|undefined, name: string, inquire: InquireFunction, showError: ShowFunction}): Promise { +export type EditYamlInquire = InquireFunction & InquireFunction + +// TODO: Inject this one's dependencies (inquire, showError) as well, instead of making callers do it, and give it to said callers. +export async function editYaml({schema, currentValue, name, inquire, showError}: {schema: AnySchemaDataFor, currentValue: ObjectT, name: string, inquire: EditYamlInquire, showError: ShowFunction}): Promise +export async function editYaml({schema, currentValue, name, inquire, showError}: {schema: AnySchemaDataFor, currentValue: ObjectT|undefined, name: string, inquire: EditYamlInquire, showError: ShowFunction}): Promise +export async function editYaml({schema, currentValue, name, inquire, showError}: {schema: AnySchemaDataFor, currentValue: ObjectT|undefined, name: string, inquire: EditYamlInquire, showError: ShowFunction}): Promise { const original = dump(currentValue) let text = original while (true) { @@ -16,7 +19,7 @@ export async function editYaml({schema, currentValue, name, inquire, sh type: "editor", message: `View and edit ${name}:`, default: text, - } as EditorQuestionOptions) + }) if (original !== modified) { try { const result = load(modified) diff --git a/src/utils/Merge.ts b/src/utils/Merge.ts index 30c92a1..97964ff 100644 --- a/src/utils/Merge.ts +++ b/src/utils/Merge.ts @@ -1,4 +1,4 @@ -export function merge(update: Update, base: Data): Data { +export function merge(update: Update, base: Data): Data { const data = { ...update, ...base,