Skip to main content
Glama
ssv445

Lorem Ipsum MCP Server

by ssv445
Arbitrary.ts35.1 kB
/** * @since 3.10.0 */ import * as Arr from "./Array.js" import * as FastCheck from "./FastCheck.js" import { globalValue } from "./GlobalValue.js" import * as errors_ from "./internal/schema/errors.js" import * as schemaId_ from "./internal/schema/schemaId.js" import * as util_ from "./internal/schema/util.js" import * as Option from "./Option.js" import * as Predicate from "./Predicate.js" import type * as Schema from "./Schema.js" import * as SchemaAST from "./SchemaAST.js" import type * as Types from "./Types.js" /** * @category model * @since 3.10.0 */ export interface LazyArbitrary<A> { (fc: typeof FastCheck): FastCheck.Arbitrary<A> } /** * @category annotations * @since 3.10.0 */ export interface ArbitraryGenerationContext { readonly maxDepth: number readonly depthIdentifier?: string readonly constraints?: StringConstraints | NumberConstraints | BigIntConstraints | DateConstraints | ArrayConstraints } /** * @category annotations * @since 3.10.0 */ export type ArbitraryAnnotation<A, TypeParameters extends ReadonlyArray<any> = readonly []> = ( ...arbitraries: [ ...{ readonly [K in keyof TypeParameters]: LazyArbitrary<TypeParameters[K]> }, ctx: ArbitraryGenerationContext ] ) => LazyArbitrary<A> /** * Returns a LazyArbitrary for the `A` type of the provided schema. * * @category arbitrary * @since 3.10.0 */ export const makeLazy = <A, I, R>(schema: Schema.Schema<A, I, R>): LazyArbitrary<A> => { const description = getDescription(schema.ast, []) return go(description, { maxDepth: 2 }) } /** * Returns a fast-check Arbitrary for the `A` type of the provided schema. * * @category arbitrary * @since 3.10.0 */ export const make = <A, I, R>(schema: Schema.Schema<A, I, R>): FastCheck.Arbitrary<A> => makeLazy(schema)(FastCheck) interface StringConstraints { readonly _tag: "StringConstraints" readonly constraints: FastCheck.StringSharedConstraints readonly pattern?: string } /** @internal */ export const makeStringConstraints = (options: { readonly minLength?: number | undefined readonly maxLength?: number | undefined readonly pattern?: string | undefined }): StringConstraints => { const out: Types.Mutable<StringConstraints> = { _tag: "StringConstraints", constraints: {} } if (Predicate.isNumber(options.minLength)) { out.constraints.minLength = options.minLength } if (Predicate.isNumber(options.maxLength)) { out.constraints.maxLength = options.maxLength } if (Predicate.isString(options.pattern)) { out.pattern = options.pattern } return out } interface NumberConstraints { readonly _tag: "NumberConstraints" readonly constraints: FastCheck.FloatConstraints readonly isInteger: boolean } /** @internal */ export const makeNumberConstraints = (options: { readonly isInteger?: boolean | undefined readonly min?: unknown readonly minExcluded?: boolean | undefined readonly max?: unknown readonly maxExcluded?: boolean | undefined readonly noNaN?: boolean | undefined readonly noDefaultInfinity?: boolean | undefined }): NumberConstraints => { const out: Types.Mutable<NumberConstraints> = { _tag: "NumberConstraints", constraints: {}, isInteger: options.isInteger ?? false } if (Predicate.isNumber(options.min)) { out.constraints.min = Math.fround(options.min) } if (Predicate.isBoolean(options.minExcluded)) { out.constraints.minExcluded = options.minExcluded } if (Predicate.isNumber(options.max)) { out.constraints.max = Math.fround(options.max) } if (Predicate.isBoolean(options.maxExcluded)) { out.constraints.maxExcluded = options.maxExcluded } if (Predicate.isBoolean(options.noNaN)) { out.constraints.noNaN = options.noNaN } if (Predicate.isBoolean(options.noDefaultInfinity)) { out.constraints.noDefaultInfinity = options.noDefaultInfinity } return out } interface BigIntConstraints { readonly _tag: "BigIntConstraints" readonly constraints: FastCheck.BigIntConstraints } /** @internal */ export const makeBigIntConstraints = (options: { readonly min?: bigint | undefined readonly max?: bigint | undefined }): BigIntConstraints => { const out: Types.Mutable<BigIntConstraints> = { _tag: "BigIntConstraints", constraints: {} } if (Predicate.isBigInt(options.min)) { out.constraints.min = options.min } if (Predicate.isBigInt(options.max)) { out.constraints.max = options.max } return out } interface ArrayConstraints { readonly _tag: "ArrayConstraints" readonly constraints: FastCheck.ArrayConstraints } /** @internal */ export const makeArrayConstraints = (options: { readonly minLength?: unknown readonly maxLength?: unknown }): ArrayConstraints => { const out: Types.Mutable<ArrayConstraints> = { _tag: "ArrayConstraints", constraints: {} } if (Predicate.isNumber(options.minLength)) { out.constraints.minLength = options.minLength } if (Predicate.isNumber(options.maxLength)) { out.constraints.maxLength = options.maxLength } return out } interface DateConstraints { readonly _tag: "DateConstraints" readonly constraints: FastCheck.DateConstraints } /** @internal */ export const makeDateConstraints = (options: { readonly min?: Date | undefined readonly max?: Date | undefined readonly noInvalidDate?: boolean | undefined }): DateConstraints => { const out: Types.Mutable<DateConstraints> = { _tag: "DateConstraints", constraints: {} } if (Predicate.isDate(options.min)) { out.constraints.min = options.min } if (Predicate.isDate(options.max)) { out.constraints.max = options.max } if (Predicate.isBoolean(options.noInvalidDate)) { out.constraints.noInvalidDate = options.noInvalidDate } return out } type Refinements = ReadonlyArray<SchemaAST.Refinement> interface Base { readonly path: ReadonlyArray<PropertyKey> readonly refinements: Refinements readonly annotations: ReadonlyArray<ArbitraryAnnotation<any, any>> } interface StringKeyword extends Base { readonly _tag: "StringKeyword" readonly constraints: ReadonlyArray<StringConstraints> } interface NumberKeyword extends Base { readonly _tag: "NumberKeyword" readonly constraints: ReadonlyArray<NumberConstraints> } interface BigIntKeyword extends Base { readonly _tag: "BigIntKeyword" readonly constraints: ReadonlyArray<BigIntConstraints> } interface DateFromSelf extends Base { readonly _tag: "DateFromSelf" readonly constraints: ReadonlyArray<DateConstraints> } interface Declaration extends Base { readonly _tag: "Declaration" readonly typeParameters: ReadonlyArray<Description> readonly ast: SchemaAST.AST } interface TupleType extends Base { readonly _tag: "TupleType" readonly constraints: ReadonlyArray<ArrayConstraints> readonly elements: ReadonlyArray<{ readonly isOptional: boolean readonly description: Description }> readonly rest: ReadonlyArray<Description> } interface TypeLiteral extends Base { readonly _tag: "TypeLiteral" readonly propertySignatures: ReadonlyArray<{ readonly isOptional: boolean readonly name: PropertyKey readonly value: Description }> readonly indexSignatures: ReadonlyArray<{ readonly parameter: Description readonly value: Description }> } interface Union extends Base { readonly _tag: "Union" readonly members: ReadonlyArray<Description> } interface Suspend extends Base { readonly _tag: "Suspend" readonly id: string readonly ast: SchemaAST.AST readonly description: () => Description } interface Ref extends Base { readonly _tag: "Ref" readonly id: string readonly ast: SchemaAST.AST } interface NeverKeyword extends Base { readonly _tag: "NeverKeyword" readonly ast: SchemaAST.AST } interface Keyword extends Base { readonly _tag: "Keyword" readonly value: | "UndefinedKeyword" | "VoidKeyword" | "UnknownKeyword" | "AnyKeyword" | "BooleanKeyword" | "SymbolKeyword" | "ObjectKeyword" } interface Literal extends Base { readonly _tag: "Literal" readonly literal: SchemaAST.LiteralValue } interface UniqueSymbol extends Base { readonly _tag: "UniqueSymbol" readonly symbol: symbol } interface Enums extends Base { readonly _tag: "Enums" readonly enums: ReadonlyArray<readonly [string, string | number]> readonly ast: SchemaAST.AST } interface TemplateLiteral extends Base { readonly _tag: "TemplateLiteral" readonly head: string readonly spans: ReadonlyArray<{ readonly description: Description readonly literal: string }> } type Description = | Declaration | NeverKeyword | Keyword | Literal | UniqueSymbol | Enums | TemplateLiteral | StringKeyword | NumberKeyword | BigIntKeyword | DateFromSelf | TupleType | TypeLiteral | Union | Suspend | Ref const getArbitraryAnnotation = SchemaAST.getAnnotation<ArbitraryAnnotation<any, any>>(SchemaAST.ArbitraryAnnotationId) const getASTConstraints = (ast: SchemaAST.AST) => { const TypeAnnotationId = ast.annotations[SchemaAST.SchemaIdAnnotationId] if (Predicate.isPropertyKey(TypeAnnotationId)) { const out = ast.annotations[TypeAnnotationId] if (Predicate.isReadonlyRecord(out)) { return out } } } const idMemoMap = globalValue( Symbol.for("effect/Arbitrary/IdMemoMap"), () => new Map<SchemaAST.AST, string>() ) let counter = 0 function wrapGetDescription( f: (ast: SchemaAST.AST, description: Description) => Description, g: (ast: SchemaAST.AST, path: ReadonlyArray<PropertyKey>) => Description ): (ast: SchemaAST.AST, path: ReadonlyArray<PropertyKey>) => Description { return (ast, path) => f(ast, g(ast, path)) } function parseMeta(ast: SchemaAST.AST): [SchemaAST.SchemaIdAnnotation | undefined, Record<string | symbol, unknown>] { const jsonSchema = SchemaAST.getJSONSchemaAnnotation(ast).pipe( Option.filter(Predicate.isReadonlyRecord), Option.getOrUndefined ) const schemaId = Option.getOrElse(SchemaAST.getSchemaIdAnnotation(ast), () => undefined) const schemaParams = Option.fromNullable(schemaId).pipe( Option.map((id) => ast.annotations[id]), Option.filter(Predicate.isReadonlyRecord), Option.getOrUndefined ) return [schemaId, { ...schemaParams, ...jsonSchema }] } /** @internal */ export const getDescription = wrapGetDescription( (ast, description) => { const annotation = getArbitraryAnnotation(ast) if (Option.isSome(annotation)) { return { ...description, annotations: [...description.annotations, annotation.value] } } return description }, (ast, path) => { const [schemaId, meta] = parseMeta(ast) switch (ast._tag) { case "Refinement": { const from = getDescription(ast.from, path) switch (from._tag) { case "StringKeyword": return { ...from, constraints: [...from.constraints, makeStringConstraints(meta)], refinements: [...from.refinements, ast] } case "NumberKeyword": { const c = schemaId === schemaId_.NonNaNSchemaId ? makeNumberConstraints({ noNaN: true }) : makeNumberConstraints({ isInteger: "type" in meta && meta.type === "integer", noNaN: "type" in meta && meta.type === "number" ? true : undefined, noDefaultInfinity: "type" in meta && meta.type === "number" ? true : undefined, min: meta.exclusiveMinimum ?? meta.minimum, minExcluded: "exclusiveMinimum" in meta ? true : undefined, max: meta.exclusiveMaximum ?? meta.maximum, maxExcluded: "exclusiveMaximum" in meta ? true : undefined }) return { ...from, constraints: [...from.constraints, c], refinements: [...from.refinements, ast] } } case "BigIntKeyword": { const c = getASTConstraints(ast) return { ...from, constraints: c !== undefined ? [...from.constraints, makeBigIntConstraints(c)] : from.constraints, refinements: [...from.refinements, ast] } } case "TupleType": return { ...from, constraints: [ ...from.constraints, makeArrayConstraints({ minLength: meta.minItems, maxLength: meta.maxItems }) ], refinements: [...from.refinements, ast] } case "DateFromSelf": return { ...from, constraints: [...from.constraints, makeDateConstraints(meta)], refinements: [...from.refinements, ast] } default: return { ...from, refinements: [...from.refinements, ast] } } } case "Declaration": { if (schemaId === schemaId_.DateFromSelfSchemaId) { return { _tag: "DateFromSelf", constraints: [makeDateConstraints(meta)], path, refinements: [], annotations: [] } } return { _tag: "Declaration", typeParameters: ast.typeParameters.map((ast) => getDescription(ast, path)), path, refinements: [], annotations: [], ast } } case "Literal": { return { _tag: "Literal", literal: ast.literal, path, refinements: [], annotations: [] } } case "UniqueSymbol": { return { _tag: "UniqueSymbol", symbol: ast.symbol, path, refinements: [], annotations: [] } } case "Enums": { return { _tag: "Enums", enums: ast.enums, path, refinements: [], annotations: [], ast } } case "TemplateLiteral": { return { _tag: "TemplateLiteral", head: ast.head, spans: ast.spans.map((span) => ({ description: getDescription(span.type, path), literal: span.literal })), path, refinements: [], annotations: [] } } case "StringKeyword": return { _tag: "StringKeyword", constraints: [], path, refinements: [], annotations: [] } case "NumberKeyword": return { _tag: "NumberKeyword", constraints: [], path, refinements: [], annotations: [] } case "BigIntKeyword": return { _tag: "BigIntKeyword", constraints: [], path, refinements: [], annotations: [] } case "TupleType": return { _tag: "TupleType", constraints: [], elements: ast.elements.map((element, i) => ({ isOptional: element.isOptional, description: getDescription(element.type, [...path, i]) })), rest: ast.rest.map((element, i) => getDescription(element.type, [...path, i])), path, refinements: [], annotations: [] } case "TypeLiteral": return { _tag: "TypeLiteral", propertySignatures: ast.propertySignatures.map((ps) => ({ isOptional: ps.isOptional, name: ps.name, value: getDescription(ps.type, [...path, ps.name]) })), indexSignatures: ast.indexSignatures.map((is) => ({ parameter: getDescription(is.parameter, path), value: getDescription(is.type, path) })), path, refinements: [], annotations: [] } case "Union": return { _tag: "Union", members: ast.types.map((member, i) => getDescription(member, [...path, i])), path, refinements: [], annotations: [] } case "Suspend": { const memoId = idMemoMap.get(ast) if (memoId !== undefined) { return { _tag: "Ref", id: memoId, ast, path, refinements: [], annotations: [] } } counter++ const id = `__id-${counter}__` idMemoMap.set(ast, id) return { _tag: "Suspend", id, ast, description: () => getDescription(ast.f(), path), path, refinements: [], annotations: [] } } case "Transformation": return getDescription(ast.to, path) case "NeverKeyword": return { _tag: "NeverKeyword", path, refinements: [], annotations: [], ast } default: { return { _tag: "Keyword", value: ast._tag, path, refinements: [], annotations: [] } } } } ) function getMax(n1: Date | undefined, n2: Date | undefined): Date | undefined function getMax(n1: bigint | undefined, n2: bigint | undefined): bigint | undefined function getMax(n1: number | undefined, n2: number | undefined): number | undefined function getMax( n1: bigint | number | Date | undefined, n2: bigint | number | Date | undefined ): bigint | number | Date | undefined { return n1 === undefined ? n2 : n2 === undefined ? n1 : n1 <= n2 ? n2 : n1 } function getMin(n1: Date | undefined, n2: Date | undefined): Date | undefined function getMin(n1: bigint | undefined, n2: bigint | undefined): bigint | undefined function getMin(n1: number | undefined, n2: number | undefined): number | undefined function getMin( n1: bigint | number | Date | undefined, n2: bigint | number | Date | undefined ): bigint | number | Date | undefined { return n1 === undefined ? n2 : n2 === undefined ? n1 : n1 <= n2 ? n1 : n2 } const getOr = (a: boolean | undefined, b: boolean | undefined): boolean | undefined => { return a === undefined ? b : b === undefined ? a : a || b } function mergePattern(pattern1: string | undefined, pattern2: string | undefined): string | undefined { if (pattern1 === undefined) { return pattern2 } if (pattern2 === undefined) { return pattern1 } return `(?:${pattern1})|(?:${pattern2})` } function mergeStringConstraints(c1: StringConstraints, c2: StringConstraints): StringConstraints { return makeStringConstraints({ minLength: getMax(c1.constraints.minLength, c2.constraints.minLength), maxLength: getMin(c1.constraints.maxLength, c2.constraints.maxLength), pattern: mergePattern(c1.pattern, c2.pattern) }) } function buildStringConstraints(description: StringKeyword): StringConstraints | undefined { return description.constraints.length === 0 ? undefined : description.constraints.reduce(mergeStringConstraints) } function mergeNumberConstraints(c1: NumberConstraints, c2: NumberConstraints): NumberConstraints { return makeNumberConstraints({ isInteger: c1.isInteger || c2.isInteger, min: getMax(c1.constraints.min, c2.constraints.min), minExcluded: getOr(c1.constraints.minExcluded, c2.constraints.minExcluded), max: getMin(c1.constraints.max, c2.constraints.max), maxExcluded: getOr(c1.constraints.maxExcluded, c2.constraints.maxExcluded), noNaN: getOr(c1.constraints.noNaN, c2.constraints.noNaN), noDefaultInfinity: getOr(c1.constraints.noDefaultInfinity, c2.constraints.noDefaultInfinity) }) } function buildNumberConstraints(description: NumberKeyword): NumberConstraints | undefined { return description.constraints.length === 0 ? undefined : description.constraints.reduce(mergeNumberConstraints) } function mergeBigIntConstraints(c1: BigIntConstraints, c2: BigIntConstraints): BigIntConstraints { return makeBigIntConstraints({ min: getMax(c1.constraints.min, c2.constraints.min), max: getMin(c1.constraints.max, c2.constraints.max) }) } function buildBigIntConstraints(description: BigIntKeyword): BigIntConstraints | undefined { return description.constraints.length === 0 ? undefined : description.constraints.reduce(mergeBigIntConstraints) } function mergeDateConstraints(c1: DateConstraints, c2: DateConstraints): DateConstraints { return makeDateConstraints({ min: getMax(c1.constraints.min, c2.constraints.min), max: getMin(c1.constraints.max, c2.constraints.max), noInvalidDate: getOr(c1.constraints.noInvalidDate, c2.constraints.noInvalidDate) }) } function buildDateConstraints(description: DateFromSelf): DateConstraints | undefined { return description.constraints.length === 0 ? undefined : description.constraints.reduce(mergeDateConstraints) } const constArrayConstraints = makeArrayConstraints({}) function mergeArrayConstraints(c1: ArrayConstraints, c2: ArrayConstraints): ArrayConstraints { return makeArrayConstraints({ minLength: getMax(c1.constraints.minLength, c2.constraints.minLength), maxLength: getMin(c1.constraints.maxLength, c2.constraints.maxLength) }) } function buildArrayConstraints(description: TupleType): ArrayConstraints | undefined { return description.constraints.length === 0 ? undefined : description.constraints.reduce(mergeArrayConstraints) } const arbitraryMemoMap = globalValue( Symbol.for("effect/Arbitrary/arbitraryMemoMap"), () => new WeakMap<SchemaAST.AST, LazyArbitrary<any>>() ) function applyFilters(filters: ReadonlyArray<Predicate.Predicate<any>>, arb: LazyArbitrary<any>): LazyArbitrary<any> { return (fc) => filters.reduce((arb, filter) => arb.filter(filter), arb(fc)) } function absurd(message: string): LazyArbitrary<any> { return () => { throw new Error(message) } } function getContextConstraints(description: Description): ArbitraryGenerationContext["constraints"] { switch (description._tag) { case "StringKeyword": return buildStringConstraints(description) case "NumberKeyword": return buildNumberConstraints(description) case "BigIntKeyword": return buildBigIntConstraints(description) case "DateFromSelf": return buildDateConstraints(description) case "TupleType": return buildArrayConstraints(description) } } function wrapGo( f: (description: Description, ctx: ArbitraryGenerationContext, lazyArb: LazyArbitrary<any>) => LazyArbitrary<any>, g: (description: Description, ctx: ArbitraryGenerationContext) => LazyArbitrary<any> ): (description: Description, ctx: ArbitraryGenerationContext) => LazyArbitrary<any> { return (description, ctx) => f(description, ctx, g(description, ctx)) } const go = wrapGo( (description, ctx, lazyArb) => { const annotation: ArbitraryAnnotation<any, any> | undefined = description.annotations[description.annotations.length - 1] // error handling if (annotation === undefined) { switch (description._tag) { case "Declaration": case "NeverKeyword": throw new Error(errors_.getArbitraryMissingAnnotationErrorMessage(description.path, description.ast)) case "Enums": if (description.enums.length === 0) { throw new Error(errors_.getArbitraryEmptyEnumErrorMessage(description.path)) } } } const filters = description.refinements.map((ast) => (a: any) => Option.isNone(ast.filter(a, SchemaAST.defaultParseOption, ast)) ) if (annotation === undefined) { return applyFilters(filters, lazyArb) } const constraints = getContextConstraints(description) if (constraints !== undefined) { ctx = { ...ctx, constraints } } if (description._tag === "Declaration") { return applyFilters(filters, annotation(...description.typeParameters.map((p) => go(p, ctx)), ctx)) } if (description.refinements.length > 0) { // TODO(4.0): remove the `lazyArb` parameter return applyFilters(filters, annotation(lazyArb, ctx)) } return annotation(ctx) }, (description, ctx) => { switch (description._tag) { case "DateFromSelf": { const constraints = buildDateConstraints(description) return (fc) => fc.date(constraints?.constraints) } case "Declaration": case "NeverKeyword": return absurd(`BUG: cannot generate an arbitrary for ${description._tag}`) case "Literal": return (fc) => fc.constant(description.literal) case "UniqueSymbol": return (fc) => fc.constant(description.symbol) case "Keyword": { switch (description.value) { case "UndefinedKeyword": return (fc) => fc.constant(undefined) case "VoidKeyword": case "UnknownKeyword": case "AnyKeyword": return (fc) => fc.anything() case "BooleanKeyword": return (fc) => fc.boolean() case "SymbolKeyword": return (fc) => fc.string().map((s) => Symbol.for(s)) case "ObjectKeyword": return (fc) => fc.oneof(fc.object(), fc.array(fc.anything())) } } case "Enums": return (fc) => fc.oneof(...description.enums.map(([_, value]) => fc.constant(value))) case "TemplateLiteral": { return (fc) => { const string = fc.string({ maxLength: 5 }) const number = fc.float({ noDefaultInfinity: true, noNaN: true }) const getTemplateLiteralArb = (description: TemplateLiteral) => { const components: Array<FastCheck.Arbitrary<string | number>> = description.head !== "" ? [fc.constant(description.head)] : [] const getTemplateLiteralSpanTypeArb = ( description: Description ): FastCheck.Arbitrary<string | number> => { switch (description._tag) { case "StringKeyword": return string case "NumberKeyword": return number case "Literal": return fc.constant(String(description.literal)) case "Union": return fc.oneof(...description.members.map(getTemplateLiteralSpanTypeArb)) case "TemplateLiteral": return getTemplateLiteralArb(description) default: return fc.constant("") } } description.spans.forEach((span) => { components.push(getTemplateLiteralSpanTypeArb(span.description)) if (span.literal !== "") { components.push(fc.constant(span.literal)) } }) return fc.tuple(...components).map((spans) => spans.join("")) } return getTemplateLiteralArb(description) } } case "StringKeyword": { const constraints = buildStringConstraints(description) const pattern = constraints?.pattern return pattern !== undefined ? (fc) => fc.stringMatching(new RegExp(pattern)) : (fc) => fc.string(constraints?.constraints) } case "NumberKeyword": { const constraints = buildNumberConstraints(description) return constraints?.isInteger ? (fc) => fc.integer(constraints.constraints) : (fc) => fc.float(constraints?.constraints) } case "BigIntKeyword": { const constraints = buildBigIntConstraints(description) return (fc) => fc.bigInt(constraints?.constraints ?? {}) } case "TupleType": { const elements: Array<LazyArbitrary<any>> = [] let hasOptionals = false for (const element of description.elements) { elements.push(go(element.description, ctx)) if (element.isOptional) { hasOptionals = true } } const rest = description.rest.map((d) => go(d, ctx)) return (fc) => { // --------------------------------------------- // handle elements // --------------------------------------------- let output = fc.tuple(...elements.map((arb) => arb(fc))) if (hasOptionals) { const indexes = fc.tuple( ...description.elements.map((element) => element.isOptional ? fc.boolean() : fc.constant(true)) ) output = output.chain((tuple) => indexes.map((booleans) => { for (const [i, b] of booleans.reverse().entries()) { if (!b) { tuple.splice(booleans.length - i, 1) } } return tuple }) ) } // --------------------------------------------- // handle rest element // --------------------------------------------- if (Arr.isNonEmptyReadonlyArray(rest)) { const constraints = buildArrayConstraints(description) ?? constArrayConstraints const [head, ...tail] = rest const item = head(fc) output = output.chain((as) => { const len = as.length // We must adjust the constraints for the rest element // because the elements might have generated some values const restArrayConstraints = subtractElementsLength(constraints.constraints, len) if (restArrayConstraints.maxLength === 0) { return fc.constant(as) } /* `getSuspendedArray` is used to generate less values in the context of a recursive schema. Without it, the following schema would generate an big amount of values possibly leading to a stack overflow: ```ts type A = ReadonlyArray<A | null> const schema = S.Array( S.NullOr(S.suspend((): S.Schema<A> => schema)) ) ``` */ const arr = ctx.depthIdentifier !== undefined ? getSuspendedArray(fc, ctx.depthIdentifier, ctx.maxDepth, item, restArrayConstraints) : fc.array(item, restArrayConstraints) if (len === 0) { return arr } return arr.map((rest) => [...as, ...rest]) }) // --------------------------------------------- // handle post rest elements // --------------------------------------------- for (let j = 0; j < tail.length; j++) { output = output.chain((as) => tail[j](fc).map((a) => [...as, a])) } } return output } } case "TypeLiteral": { const propertySignatures: Array<LazyArbitrary<any>> = [] const requiredKeys: Array<PropertyKey> = [] for (const ps of description.propertySignatures) { if (!ps.isOptional) { requiredKeys.push(ps.name) } propertySignatures.push(go(ps.value, ctx)) } const indexSignatures = description.indexSignatures.map((is) => [go(is.parameter, ctx), go(is.value, ctx)] as const ) return (fc) => { const pps: any = {} for (let i = 0; i < propertySignatures.length; i++) { const ps = description.propertySignatures[i] pps[ps.name] = propertySignatures[i](fc) } let output = fc.record<any, any>(pps, { requiredKeys }) // --------------------------------------------- // handle index signatures // --------------------------------------------- for (let i = 0; i < indexSignatures.length; i++) { const key = indexSignatures[i][0](fc) const value = indexSignatures[i][1](fc) output = output.chain((o) => { const item = fc.tuple(key, value) /* `getSuspendedArray` is used to generate less key/value pairs in the context of a recursive schema. Without it, the following schema would generate an big amount of values possibly leading to a stack overflow: ```ts type A = { [_: string]: A } const schema = S.Record({ key: S.String, value: S.suspend((): S.Schema<A> => schema) }) ``` */ const arr = ctx.depthIdentifier !== undefined ? getSuspendedArray(fc, ctx.depthIdentifier, ctx.maxDepth, item, { maxLength: 2 }) : fc.array(item) return arr.map((tuples) => ({ ...Object.fromEntries(tuples), ...o })) }) } return output } } case "Union": { const members = description.members.map((member) => go(member, ctx)) return (fc) => fc.oneof(...members.map((arb) => arb(fc))) } case "Suspend": { const memo = arbitraryMemoMap.get(description.ast) if (memo) { return memo } if (ctx.depthIdentifier === undefined) { ctx = { ...ctx, depthIdentifier: description.id } } const get = util_.memoizeThunk(() => { return go(description.description(), ctx) }) const out: LazyArbitrary<any> = (fc) => fc.constant(null).chain(() => get()(fc)) arbitraryMemoMap.set(description.ast, out) return out } case "Ref": { const memo = arbitraryMemoMap.get(description.ast) if (memo) { return memo } throw new Error(`BUG: Ref ${JSON.stringify(description.id)} not found`) } } } ) function subtractElementsLength( constraints: FastCheck.ArrayConstraints, len: number ): FastCheck.ArrayConstraints { if (len === 0 || (constraints.minLength === undefined && constraints.maxLength === undefined)) { return constraints } const out = { ...constraints } if (out.minLength !== undefined) { out.minLength = Math.max(out.minLength - len, 0) } if (out.maxLength !== undefined) { out.maxLength = Math.max(out.maxLength - len, 0) } return out } const getSuspendedArray = ( fc: typeof FastCheck, depthIdentifier: string, maxDepth: number, item: FastCheck.Arbitrary<any>, constraints: FastCheck.ArrayConstraints ) => { // In the context of a recursive schema, we don't want a `maxLength` greater than 2. // The only exception is when `minLength` is also set, in which case we set // `maxLength` to the minimum value, which is `minLength`. const maxLengthLimit = Math.max(2, constraints.minLength ?? 0) if (constraints.maxLength !== undefined && constraints.maxLength > maxLengthLimit) { constraints = { ...constraints, maxLength: maxLengthLimit } } return fc.oneof( { maxDepth, depthIdentifier }, fc.constant([]), fc.array(item, constraints) ) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ssv445/lorem-ipsum-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server