diff --git a/src/backend/common/infrastructure/Atomic.ts b/src/backend/common/infrastructure/Atomic.ts index 7f6264ea..938d3adf 100644 --- a/src/backend/common/infrastructure/Atomic.ts +++ b/src/backend/common/infrastructure/Atomic.ts @@ -12,6 +12,7 @@ import type { ClientType } from "../../../core/Atomic.ts"; import assert from 'assert'; import * as path from 'path'; import * as z from 'zod'; +import { stripIndents } from 'common-tags'; export const __filename = import.meta.filename; export const projectRootDir = path.resolve(__filename, '../../../../../'); @@ -416,7 +417,12 @@ export interface ScrobbleRangeResult { export const REFRESH_STALE_DEFAULT = 60; -export const durationValueSchema = z.union([z.number(), z.string()]); +export const durationValueSchema = z.union([z.number(), z.string()]).meta({ + description: stripIndents`A duration of time as either + + * a number of seconds + * or a string containing a number and a unit of time compatible with dayjs`, + example: [60, 3600, "1 hour", "4 days"]}); /** * A duration of time * diff --git a/src/backend/common/infrastructure/config/client/index.ts b/src/backend/common/infrastructure/config/client/index.ts index 2060db26..85ee1a5d 100644 --- a/src/backend/common/infrastructure/config/client/index.ts +++ b/src/backend/common/infrastructure/config/client/index.ts @@ -159,9 +159,11 @@ export const commonClientOptionsSchema = z.object({ examples: [3] }), - playTransform: playTransformOptionsSchema.optional(), + /** Enhance/correct Play data by applying a transform pipeline */ + playTransform: playTransformOptionsSchema.optional().meta({description: 'Enhance/correct Play data by applying a transform pipeline'}), - retention: retentionConfigDurationValueSchema.optional(), + /** Retention policy for Plays stored in the database */ + retention: retentionConfigDurationValueSchema.optional().meta({description: 'Retention policy for Plays stored in the database'}), }); export type CommonClientOptions = z.infer; diff --git a/src/backend/common/infrastructure/config/database.ts b/src/backend/common/infrastructure/config/database.ts index d38787d6..6ceb7d48 100644 --- a/src/backend/common/infrastructure/config/database.ts +++ b/src/backend/common/infrastructure/config/database.ts @@ -17,7 +17,7 @@ const durationSchema = z.custom( {message: 'Expected a dayjs Duration instance'} ); -export const retentionValueUnparsedSchema = z.union([durationValueSchema, durationSchema, z.literal(false)]); +export const retentionValueUnparsedSchema = z.union([durationValueSchema, z.literal(false)]); export type RetentionValueUnparsed = z.infer; diff --git a/src/backend/utils/SchemaDocsStaticUtils.ts b/src/backend/utils/SchemaDocsStaticUtils.ts index 237dfc55..8edbce98 100644 --- a/src/backend/utils/SchemaDocsStaticUtils.ts +++ b/src/backend/utils/SchemaDocsStaticUtils.ts @@ -22,6 +22,7 @@ const jsonSchema = z.toJSONSchema(koitoClientConfigSchema, { delete a.const; } } + //return; } if(ctx.jsonSchema.oneOf !== undefined) { for(const a of ctx.jsonSchema.oneOf) { @@ -30,11 +31,27 @@ const jsonSchema = z.toJSONSchema(koitoClientConfigSchema, { delete a.const; } } + //return; } if(ctx.jsonSchema.const !== undefined) { ctx.jsonSchema.enum = [ctx.jsonSchema.const]; delete ctx.jsonSchema.const; + return } + // if(ctx.zodSchema.type === 'object') { + // const ks = Object.keys(ctx.jsonSchema.properties); + // for(const k of ks) { + // if(ctx.jsonSchema.properties[k]?.description !== undefined) { + // let isUnion = ctx.zodSchema.shape[k].type === 'union'; + // if(!isUnion && ctx.zodSchema.shape[k].type === 'optional' && ctx.zodSchema.shape[k].unwrap().type === 'union') { + // isUnion = true; + // } + // if(ctx.zodSchema.shape[k].type === 'union') + // // ctx.zodSchema.shape[k]. + // } + // } + // } + // if (ctx.jsonSchema.anyOf) { // ctx.jsonSchema.oneOf = ctx.jsonSchema.anyOf; // delete ctx.jsonSchema.anyOf; diff --git a/src/core/Transform.ts b/src/core/Transform.ts index 26a49b2b..5f6f7793 100644 --- a/src/core/Transform.ts +++ b/src/core/Transform.ts @@ -1,6 +1,7 @@ import * as z from "zod"; import type {SearchAndReplaceRegExp} from "@foxxmd/regex-buddy-core"; import type { MarkRequired } from "ts-essentials"; +import { stripIndents } from "common-tags"; // The following generic types are used elsewhere in the codebase with several different concrete type // parameters (e.g. `PlayTransformHooks`, `PlayTransformStage`, @@ -28,7 +29,7 @@ export interface PlayTransformPartsAtomic { } export const STAGE_TYPES_USER: StageTypeUser[] = ['user']; -export const STAGE_TYPES_METADATA: StageTypeMetadata[] = ['spotify','musicbrainz','native']; +export const STAGE_TYPES_METADATA: StageTypeMetadata[] = ['musicbrainz','native']; export const STAGE_TYPES: StageType[] = [...STAGE_TYPES_METADATA, ...STAGE_TYPES_USER]; export interface StageTyped { @@ -193,9 +194,9 @@ export const flowControlSchema = z.object({ export type FlowControl = z.infer; -export const stageTypeMetadataSchema = z.enum(['spotify', 'musicbrainz', 'native']).meta({title: 'Stage Type Metadata'}); +export const stageTypeMetadataSchema = z.enum(['musicbrainz', 'native']).meta({title: 'Stage Type Metadata'}); -export const typedStageSchema = z.enum(['spotify', 'musicbrainz', 'native','user']).meta({title: 'Stage Type'}); +export const typedStageSchema = z.enum(['musicbrainz', 'native','user']).meta({title: 'Stage Type'}); export type StageTypeMetadata = z.infer; @@ -291,8 +292,8 @@ const playTransformUserStageRulesSchema = z.object({ // metadata branch to `'spotify' | 'musicbrainz'` only for the purposes of this union - the overall set of // `type` values covered across the whole union is unchanged. const metadataStageForUnionSchema = playTransformMetadataStageSchema.extend({ - type: z.enum(['spotify', 'musicbrainz']), -}).meta({title: 'Metadata Types'}); + type: z.enum(['musicbrainz']), +}).meta({title: 'Transform External Stage'}); // `PlayTransformParts = Extract, Y> & Whennable` - the `& Whennable` intersection // is redundant here since every stage schema already includes `when` via `stageConfigSchema`/`untypedStageConfigSchema`. @@ -328,11 +329,20 @@ const playTransformPartsConfigOptionsSchema = z.union([ ]); export const playTransformConfigSchema = z.object({ - preCompare: playTransformPartsConfigOptionsSchema.optional(), + /** Stages to be applied when a Play is first seen by this component */ + preCompare: playTransformPartsConfigOptionsSchema.optional().meta({description: 'Stages to be applied when a Play is first seen by this component'}), + /** Stages to be applied when comparing a candidate Play to any existing Plays such as when checking for duplicates or discovered Plays + * + * **Note:** Transforms applies are not persistent. They are used only during comparison operations. + */ compare: z.object({ - candidate: playTransformPartsConfigOptionsSchema.optional(), - existing: playTransformPartsConfigOptionsSchema.optional(), - }).optional(), + /** Stages to apply to the Play being considered for discovery or scrobbling */ + candidate: playTransformPartsConfigOptionsSchema.optional().meta({description: `Stages to apply to the Play being considered for discovery or scrobbling`}), + /** Stages to apply to the existing Plays (already discovered, existing scrobbles) */ + existing: playTransformPartsConfigOptionsSchema.optional().meta({description: `Stages to apply to the existing Plays (already discovered, existing scrobbles)`}), + }).optional().meta({description: stripIndents`Stages to be applied when comparing a candidate Play to any existing Plays such as when checking for duplicates or discovered Plays + + **Note:** Transforms applies are not persistent. They are used only during comparison operations.`}), postCompare: playTransformPartsConfigOptionsSchema.optional(), }).meta({title: 'Transform Config'}); @@ -350,7 +360,7 @@ const playTransformPartsArrayRulesSchema = z.array(playTransformTypedStageRulesS /** Represents the final, strongly-typed transform configuration used during runtime. */ export const playTransformRulesSchema = z.object({ - preCompare: playTransformPartsArrayRulesSchema.optional(), + preCompare: playTransformPartsArrayRulesSchema.optional().meta({description: 'Stages to be applied when a Play is first seen by this component'}), compare: z.object({ candidate: playTransformPartsArrayRulesSchema.optional(), existing: playTransformPartsArrayRulesSchema.optional(),