UNPKG

effect

Version:

The missing standard library for TypeScript, for writing production-grade software.

1,546 lines (1,545 loc) 190 kB
/** * @since 3.10.0 */ import * as array_ from "./Array.js"; import * as bigDecimal_ from "./BigDecimal.js"; import * as bigInt_ from "./BigInt.js"; import * as boolean_ from "./Boolean.js"; import * as cause_ from "./Cause.js"; import * as chunk_ from "./Chunk.js"; import * as config_ from "./Config.js"; import * as configError_ from "./ConfigError.js"; import * as data_ from "./Data.js"; import * as dateTime from "./DateTime.js"; import * as duration_ from "./Duration.js"; import * as Effect from "./Effect.js"; import * as either_ from "./Either.js"; import * as Encoding from "./Encoding.js"; import * as Equal from "./Equal.js"; import * as Equivalence from "./Equivalence.js"; import * as exit_ from "./Exit.js"; import * as fastCheck_ from "./FastCheck.js"; import * as fiberId_ from "./FiberId.js"; import { dual, identity } from "./Function.js"; import { globalValue } from "./GlobalValue.js"; import * as hashMap_ from "./HashMap.js"; import * as hashSet_ from "./HashSet.js"; import * as internalCause_ from "./internal/cause.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 list_ from "./List.js"; import * as number_ from "./Number.js"; import * as option_ from "./Option.js"; import * as ParseResult from "./ParseResult.js"; import { pipeArguments } from "./Pipeable.js"; import * as Predicate from "./Predicate.js"; import * as redacted_ from "./Redacted.js"; import * as Request from "./Request.js"; import * as scheduler_ from "./Scheduler.js"; import * as AST from "./SchemaAST.js"; import * as sortedSet_ from "./SortedSet.js"; import * as string_ from "./String.js"; import * as struct_ from "./Struct.js"; /** * @since 3.10.0 * @category symbol */ export const TypeId = /*#__PURE__*/Symbol.for("effect/Schema"); /** * @category constructors * @since 3.10.0 */ export function make(ast) { return class SchemaClass { [TypeId] = variance; static ast = ast; static annotations(annotations) { return make(mergeSchemaAnnotations(this.ast, annotations)); } static pipe() { return pipeArguments(this, arguments); } static toString() { return String(ast); } static Type; static Encoded; static Context; static [TypeId] = variance; }; } const variance = { /* c8 ignore next */ _A: _ => _, /* c8 ignore next */ _I: _ => _, /* c8 ignore next */ _R: _ => _ }; const makeStandardResult = exit => exit_.isSuccess(exit) ? exit.value : makeStandardFailureResult(cause_.pretty(exit.cause)); const makeStandardFailureResult = message => ({ issues: [{ message }] }); const makeStandardFailureFromParseIssue = issue => Effect.map(ParseResult.ArrayFormatter.formatIssue(issue), issues => ({ issues: issues.map(issue => ({ path: issue.path, message: issue.message })) })); /** * Returns a "Standard Schema" object conforming to the [Standard Schema * v1](https://standardschema.dev/) specification. * * This function creates a schema whose `validate` method attempts to decode and * validate the provided input synchronously. If the underlying `Schema` * includes any asynchronous components (e.g., asynchronous message resolutions * or checks), then validation will necessarily return a `Promise` instead. * * Any detected defects will be reported via a single issue containing no * `path`. * * @example * ```ts * import { Schema } from "effect" * * const schema = Schema.Struct({ * name: Schema.String * }) * * // ┌─── StandardSchemaV1<{ readonly name: string; }> * // ▼ * const standardSchema = Schema.standardSchemaV1(schema) * ``` * * @category Standard Schema * @since 3.13.0 */ export const standardSchemaV1 = schema => { const decodeUnknown = ParseResult.decodeUnknown(schema); return { "~standard": { version: 1, vendor: "effect", validate(value) { const scheduler = new scheduler_.SyncScheduler(); const fiber = Effect.runFork(Effect.matchEffect(decodeUnknown(value), { onFailure: makeStandardFailureFromParseIssue, onSuccess: value => Effect.succeed({ value }) }), { scheduler }); scheduler.flush(); const exit = fiber.unsafePoll(); if (exit) { return makeStandardResult(exit); } return new Promise(resolve => { fiber.addObserver(exit => { resolve(makeStandardResult(exit)); }); }); } } }; }; const builtInAnnotations = { schemaId: AST.SchemaIdAnnotationId, message: AST.MessageAnnotationId, missingMessage: AST.MissingMessageAnnotationId, identifier: AST.IdentifierAnnotationId, title: AST.TitleAnnotationId, description: AST.DescriptionAnnotationId, examples: AST.ExamplesAnnotationId, default: AST.DefaultAnnotationId, documentation: AST.DocumentationAnnotationId, jsonSchema: AST.JSONSchemaAnnotationId, arbitrary: AST.ArbitraryAnnotationId, pretty: AST.PrettyAnnotationId, equivalence: AST.EquivalenceAnnotationId, concurrency: AST.ConcurrencyAnnotationId, batching: AST.BatchingAnnotationId, parseIssueTitle: AST.ParseIssueTitleAnnotationId, parseOptions: AST.ParseOptionsAnnotationId, decodingFallback: AST.DecodingFallbackAnnotationId }; const toASTAnnotations = annotations => { if (!annotations) { return {}; } const out = { ...annotations }; for (const key in builtInAnnotations) { if (key in annotations) { const id = builtInAnnotations[key]; out[id] = annotations[key]; delete out[key]; } } return out; }; const mergeSchemaAnnotations = (ast, annotations) => AST.annotations(ast, toASTAnnotations(annotations)); /** * @since 3.10.0 */ export function asSchema(schema) { return schema; } /** * @category formatting * @since 3.10.0 */ export const format = schema => String(schema.ast); /** * The `encodedSchema` function allows you to extract the `Encoded` portion of a * schema, creating a new schema that conforms to the properties defined in the * original schema without retaining any refinements or transformations that * were applied previously. * * @since 3.10.0 */ export const encodedSchema = schema => make(AST.encodedAST(schema.ast)); /** * The `encodedBoundSchema` function is similar to `encodedSchema` but preserves * the refinements up to the first transformation point in the original schema. * * @since 3.10.0 */ export const encodedBoundSchema = schema => make(AST.encodedBoundAST(schema.ast)); /** * The `typeSchema` function allows you to extract the `Type` portion of a * schema, creating a new schema that conforms to the properties defined in the * original schema without considering the initial encoding or transformation * processes. * * @since 3.10.0 */ export const typeSchema = schema => make(AST.typeAST(schema.ast)); /* c8 ignore start */ export { /** * By default the option `exact` is set to `true`. * * @throws `ParseError` * @category validation * @since 3.10.0 */ asserts, /** * @category decoding * @since 3.10.0 */ decodeOption, /** * @throws `ParseError` * @category decoding * @since 3.10.0 */ decodeSync, /** * @category decoding * @since 3.10.0 */ decodeUnknownOption, /** * @throws `ParseError` * @category decoding * @since 3.10.0 */ decodeUnknownSync, /** * @category encoding * @since 3.10.0 */ encodeOption, /** * @throws `ParseError` * @category encoding * @since 3.10.0 */ encodeSync, /** * @category encoding * @since 3.10.0 */ encodeUnknownOption, /** * @throws `ParseError` * @category encoding * @since 3.10.0 */ encodeUnknownSync, /** * By default the option `exact` is set to `true`. * * @category validation * @since 3.10.0 */ is, /** * @category validation * @since 3.10.0 */ validateOption, /** * @throws `ParseError` * @category validation * @since 3.10.0 */ validateSync } from "./ParseResult.js"; /* c8 ignore end */ /** * @category encoding * @since 3.10.0 */ export const encodeUnknown = (schema, options) => { const encodeUnknown = ParseResult.encodeUnknown(schema, options); return (u, overrideOptions) => ParseResult.mapError(encodeUnknown(u, overrideOptions), ParseResult.parseError); }; /** * @category encoding * @since 3.10.0 */ export const encodeUnknownEither = (schema, options) => { const encodeUnknownEither = ParseResult.encodeUnknownEither(schema, options); return (u, overrideOptions) => either_.mapLeft(encodeUnknownEither(u, overrideOptions), ParseResult.parseError); }; /** * @category encoding * @since 3.10.0 */ export const encodeUnknownPromise = (schema, options) => { const parser = encodeUnknown(schema, options); return (u, overrideOptions) => Effect.runPromise(parser(u, overrideOptions)); }; /** * @category encoding * @since 3.10.0 */ export const encode = encodeUnknown; /** * @category encoding * @since 3.10.0 */ export const encodeEither = encodeUnknownEither; /** * @category encoding * @since 3.10.0 */ export const encodePromise = encodeUnknownPromise; /** * @category decoding * @since 3.10.0 */ export const decodeUnknown = (schema, options) => { const decodeUnknown = ParseResult.decodeUnknown(schema, options); return (u, overrideOptions) => ParseResult.mapError(decodeUnknown(u, overrideOptions), ParseResult.parseError); }; /** * @category decoding * @since 3.10.0 */ export const decodeUnknownEither = (schema, options) => { const decodeUnknownEither = ParseResult.decodeUnknownEither(schema, options); return (u, overrideOptions) => either_.mapLeft(decodeUnknownEither(u, overrideOptions), ParseResult.parseError); }; /** * @category decoding * @since 3.10.0 */ export const decodeUnknownPromise = (schema, options) => { const parser = decodeUnknown(schema, options); return (u, overrideOptions) => Effect.runPromise(parser(u, overrideOptions)); }; /** * @category decoding * @since 3.10.0 */ export const decode = decodeUnknown; /** * @category decoding * @since 3.10.0 */ export const decodeEither = decodeUnknownEither; /** * @category decoding * @since 3.10.0 */ export const decodePromise = decodeUnknownPromise; /** * @category validation * @since 3.10.0 */ export const validate = (schema, options) => { const validate = ParseResult.validate(schema, options); return (u, overrideOptions) => ParseResult.mapError(validate(u, overrideOptions), ParseResult.parseError); }; /** * @category validation * @since 3.10.0 */ export const validateEither = (schema, options) => { const validateEither = ParseResult.validateEither(schema, options); return (u, overrideOptions) => either_.mapLeft(validateEither(u, overrideOptions), ParseResult.parseError); }; /** * @category validation * @since 3.10.0 */ export const validatePromise = (schema, options) => { const parser = validate(schema, options); return (u, overrideOptions) => Effect.runPromise(parser(u, overrideOptions)); }; /** * Tests if a value is a `Schema`. * * @category guards * @since 3.10.0 */ export const isSchema = u => Predicate.hasProperty(u, TypeId) && Predicate.isObject(u[TypeId]); function getDefaultLiteralAST(literals) { return AST.isMembers(literals) ? AST.Union.make(AST.mapMembers(literals, literal => new AST.Literal(literal))) : new AST.Literal(literals[0]); } function makeLiteralClass(literals, ast = getDefaultLiteralAST(literals)) { return class LiteralClass extends make(ast) { static annotations(annotations) { return makeLiteralClass(this.literals, mergeSchemaAnnotations(this.ast, annotations)); } static literals = [...literals]; }; } export function Literal(...literals) { return array_.isNonEmptyReadonlyArray(literals) ? makeLiteralClass(literals) : Never; } /** * Creates a new `Schema` from a literal schema. * * @example * ```ts * import * as Schema from "effect/Schema" * import { Either } from "effect" * * const schema = Schema.Literal("a", "b", "c").pipe(Schema.pickLiteral("a", "b")) * * assert.deepStrictEqual(Schema.decodeSync(schema)("a"), "a") * assert.deepStrictEqual(Schema.decodeSync(schema)("b"), "b") * assert.strictEqual(Either.isLeft(Schema.decodeUnknownEither(schema)("c")), true) * ``` * * @category constructors * @since 3.10.0 */ export const pickLiteral = (...literals) => _schema => Literal(...literals); /** * @category constructors * @since 3.10.0 */ export const UniqueSymbolFromSelf = symbol => make(new AST.UniqueSymbol(symbol)); const getDefaultEnumsAST = enums => new AST.Enums(Object.keys(enums).filter(key => typeof enums[enums[key]] !== "number").map(key => [key, enums[key]])); const makeEnumsClass = (enums, ast = getDefaultEnumsAST(enums)) => class EnumsClass extends make(ast) { static annotations(annotations) { return makeEnumsClass(this.enums, mergeSchemaAnnotations(this.ast, annotations)); } static enums = { ...enums }; }; /** * @category constructors * @since 3.10.0 */ export const Enums = enums => makeEnumsClass(enums); /** * @category template literal * @since 3.10.0 */ export const TemplateLiteral = (...[head, ...tail]) => { const spans = []; let h = ""; let ts = tail; if (isSchema(head)) { if (AST.isLiteral(head.ast)) { h = String(head.ast.literal); } else { ts = [head, ...ts]; } } else { h = String(head); } for (let i = 0; i < ts.length; i++) { const item = ts[i]; if (isSchema(item)) { if (i < ts.length - 1) { const next = ts[i + 1]; if (isSchema(next)) { if (AST.isLiteral(next.ast)) { spans.push(new AST.TemplateLiteralSpan(item.ast, String(next.ast.literal))); i++; continue; } } else { spans.push(new AST.TemplateLiteralSpan(item.ast, String(next))); i++; continue; } } spans.push(new AST.TemplateLiteralSpan(item.ast, "")); } else { spans.push(new AST.TemplateLiteralSpan(new AST.Literal(item), "")); } } if (array_.isNonEmptyArray(spans)) { return make(new AST.TemplateLiteral(h, spans)); } else { return make(new AST.TemplateLiteral("", [new AST.TemplateLiteralSpan(new AST.Literal(h), "")])); } }; function getTemplateLiteralParserCoercedElement(encoded, schema) { const ast = encoded.ast; switch (ast._tag) { case "Literal": { const literal = ast.literal; if (!Predicate.isString(literal)) { const s = String(literal); return transform(Literal(s), schema, { strict: true, decode: () => literal, encode: () => s }); } break; } case "NumberKeyword": return compose(NumberFromString, schema); case "Union": { const members = []; let hasCoercions = false; for (const member of ast.types) { const schema = make(member); const encoded = encodedSchema(schema); const coerced = getTemplateLiteralParserCoercedElement(encoded, schema); if (coerced) { hasCoercions = true; } members.push(coerced ?? schema); } return hasCoercions ? compose(Union(...members), schema) : schema; } } } /** * @category template literal * @since 3.10.0 */ export const TemplateLiteralParser = (...params) => { const encodedSchemas = []; const elements = []; const schemas = []; let coerced = false; for (let i = 0; i < params.length; i++) { const param = params[i]; const schema = isSchema(param) ? param : Literal(param); schemas.push(schema); const encoded = encodedSchema(schema); encodedSchemas.push(encoded); const element = getTemplateLiteralParserCoercedElement(encoded, schema); if (element) { elements.push(element); coerced = true; } else { elements.push(schema); } } const from = TemplateLiteral(...encodedSchemas); const re = AST.getTemplateLiteralCapturingRegExp(from.ast); let to = Tuple(...elements); if (coerced) { to = to.annotations({ [AST.AutoTitleAnnotationId]: format(Tuple(...schemas)) }); } return class TemplateLiteralParserClass extends transformOrFail(from, to, { strict: false, decode: (i, _, ast) => { const match = re.exec(i); return match ? ParseResult.succeed(match.slice(1, params.length + 1)) : ParseResult.fail(new ParseResult.Type(ast, i, `${re.source}: no match for ${JSON.stringify(i)}`)); }, encode: tuple => ParseResult.succeed(tuple.join("")) }) { static params = params.slice(); }; }; const declareConstructor = (typeParameters, options, annotations) => makeDeclareClass(typeParameters, new AST.Declaration(typeParameters.map(tp => tp.ast), (...typeParameters) => options.decode(...typeParameters.map(make)), (...typeParameters) => options.encode(...typeParameters.map(make)), toASTAnnotations(annotations))); const declarePrimitive = (is, annotations) => { const decodeUnknown = () => (input, _, ast) => is(input) ? ParseResult.succeed(input) : ParseResult.fail(new ParseResult.Type(ast, input)); const encodeUnknown = decodeUnknown; return makeDeclareClass([], new AST.Declaration([], decodeUnknown, encodeUnknown, toASTAnnotations(annotations))); }; function makeDeclareClass(typeParameters, ast) { return class DeclareClass extends make(ast) { static annotations(annotations) { return makeDeclareClass(this.typeParameters, mergeSchemaAnnotations(this.ast, annotations)); } static typeParameters = [...typeParameters]; }; } /** * The constraint `R extends Schema.Context<P[number]>` enforces dependencies solely from `typeParameters`. * This ensures that when you call `Schema.to` or `Schema.from`, you receive a schema with a `never` context. * * @category constructors * @since 3.10.0 */ export const declare = function () { if (Array.isArray(arguments[0])) { const typeParameters = arguments[0]; const options = arguments[1]; const annotations = arguments[2]; return declareConstructor(typeParameters, options, annotations); } const is = arguments[0]; const annotations = arguments[1]; return declarePrimitive(is, annotations); }; /** * @category schema id * @since 3.10.0 */ export const BrandSchemaId = /*#__PURE__*/Symbol.for("effect/SchemaId/Brand"); /** * @category constructors * @since 3.10.0 */ export const fromBrand = (constructor, annotations) => self => { const out = makeBrandClass(self, new AST.Refinement(self.ast, function predicate(a, _, ast) { const either = constructor.either(a); return either_.isLeft(either) ? option_.some(new ParseResult.Type(ast, a, either.left.map(v => v.message).join(", "))) : option_.none(); }, toASTAnnotations({ schemaId: BrandSchemaId, [BrandSchemaId]: { constructor }, ...annotations }))); return out; }; /** * @category schema id * @since 3.10.0 */ export const InstanceOfSchemaId = /*#__PURE__*/Symbol.for("effect/SchemaId/InstanceOf"); /** * @category constructors * @since 3.10.0 */ export const instanceOf = (constructor, annotations) => declare(u => u instanceof constructor, { title: constructor.name, description: `an instance of ${constructor.name}`, pretty: () => String, schemaId: InstanceOfSchemaId, [InstanceOfSchemaId]: { constructor }, ...annotations }); /** * @category primitives * @since 3.10.0 */ export class Undefined extends /*#__PURE__*/make(AST.undefinedKeyword) {} /** * @category primitives * @since 3.10.0 */ export class Void extends /*#__PURE__*/make(AST.voidKeyword) {} /** * @category primitives * @since 3.10.0 */ export class Null extends /*#__PURE__*/make(AST.null) {} /** * @category primitives * @since 3.10.0 */ export class Never extends /*#__PURE__*/make(AST.neverKeyword) {} /** * @category primitives * @since 3.10.0 */ export class Unknown extends /*#__PURE__*/make(AST.unknownKeyword) {} /** * @category primitives * @since 3.10.0 */ export class Any extends /*#__PURE__*/make(AST.anyKeyword) {} /** * @category primitives * @since 3.10.0 */ export class BigIntFromSelf extends /*#__PURE__*/make(AST.bigIntKeyword) {} /** * @category primitives * @since 3.10.0 */ export class SymbolFromSelf extends /*#__PURE__*/make(AST.symbolKeyword) {} /** @ignore */ class String$ extends /*#__PURE__*/make(AST.stringKeyword) {} /** @ignore */ class Number$ extends /*#__PURE__*/make(AST.numberKeyword) {} /** @ignore */ class Boolean$ extends /*#__PURE__*/make(AST.booleanKeyword) {} /** @ignore */ class Object$ extends /*#__PURE__*/make(AST.objectKeyword) {} export { /** * @category primitives * @since 3.10.0 */ Boolean$ as Boolean, /** * @category primitives * @since 3.10.0 */ Number$ as Number, /** * @category primitives * @since 3.10.0 */ Object$ as Object, /** * @category primitives * @since 3.10.0 */ String$ as String }; const getDefaultUnionAST = members => AST.Union.make(members.map(m => m.ast)); function makeUnionClass(members, ast = getDefaultUnionAST(members)) { return class UnionClass extends make(ast) { static annotations(annotations) { return makeUnionClass(this.members, mergeSchemaAnnotations(this.ast, annotations)); } static members = [...members]; }; } export function Union(...members) { return AST.isMembers(members) ? makeUnionClass(members) : array_.isNonEmptyReadonlyArray(members) ? members[0] : Never; } /** * @category combinators * @since 3.10.0 */ export const NullOr = self => Union(self, Null); /** * @category combinators * @since 3.10.0 */ export const UndefinedOr = self => Union(self, Undefined); /** * @category combinators * @since 3.10.0 */ export const NullishOr = self => Union(self, Null, Undefined); /** * @category combinators * @since 3.10.0 */ export const keyof = self => make(AST.keyof(self.ast)); /** * @since 3.10.0 */ export const element = self => new ElementImpl(new AST.OptionalType(self.ast, false), self); /** * @since 3.10.0 */ export const optionalElement = self => new ElementImpl(new AST.OptionalType(self.ast, true), self); class ElementImpl { ast; from; [TypeId]; _Token; constructor(ast, from) { this.ast = ast; this.from = from; } annotations(annotations) { return new ElementImpl(new AST.OptionalType(this.ast.type, this.ast.isOptional, { ...this.ast.annotations, ...toASTAnnotations(annotations) }), this.from); } toString() { return `${this.ast.type}${this.ast.isOptional ? "?" : ""}`; } } const getDefaultTupleTypeAST = (elements, rest) => new AST.TupleType(elements.map(el => isSchema(el) ? new AST.OptionalType(el.ast, false) : el.ast), rest.map(el => isSchema(el) ? new AST.Type(el.ast) : el.ast), true); function makeTupleTypeClass(elements, rest, ast = getDefaultTupleTypeAST(elements, rest)) { return class TupleTypeClass extends make(ast) { static annotations(annotations) { return makeTupleTypeClass(this.elements, this.rest, mergeSchemaAnnotations(this.ast, annotations)); } static elements = [...elements]; static rest = [...rest]; }; } export function Tuple(...args) { return Array.isArray(args[0]) ? makeTupleTypeClass(args[0], args.slice(1)) : makeTupleTypeClass(args, []); } function makeArrayClass(value, ast) { return class ArrayClass extends makeTupleTypeClass([], [value], ast) { static annotations(annotations) { return makeArrayClass(this.value, mergeSchemaAnnotations(this.ast, annotations)); } static value = value; }; } const Array$ = value => makeArrayClass(value); export { /** * @category constructors * @since 3.10.0 */ Array$ as Array }; function makeNonEmptyArrayClass(value, ast) { return class NonEmptyArrayClass extends makeTupleTypeClass([value], [value], ast) { static annotations(annotations) { return makeNonEmptyArrayClass(this.value, mergeSchemaAnnotations(this.ast, annotations)); } static value = value; }; } /** * @category constructors * @since 3.10.0 */ export const NonEmptyArray = value => makeNonEmptyArrayClass(value); /** * @category constructors * @since 3.10.0 */ export function ArrayEnsure(value) { return transform(Union(value, Array$(value)), Array$(typeSchema(asSchema(value))), { strict: true, decode: i => array_.ensure(i), encode: a => a.length === 1 ? a[0] : a }); } /** * @category constructors * @since 3.10.0 */ export function NonEmptyArrayEnsure(value) { return transform(Union(value, NonEmptyArray(value)), NonEmptyArray(typeSchema(asSchema(value))), { strict: true, decode: i => array_.isNonEmptyReadonlyArray(i) ? i : array_.of(i), encode: a => a.length === 1 ? a[0] : a }); } const formatPropertySignatureToken = isOptional => isOptional ? "\"?:\"" : "\":\""; /** * @category PropertySignature * @since 3.10.0 */ export class PropertySignatureDeclaration extends AST.OptionalType { isReadonly; defaultValue; /** * @since 3.10.0 */ _tag = "PropertySignatureDeclaration"; constructor(type, isOptional, isReadonly, annotations, defaultValue) { super(type, isOptional, annotations); this.isReadonly = isReadonly; this.defaultValue = defaultValue; } /** * @since 3.10.0 */ toString() { const token = formatPropertySignatureToken(this.isOptional); const type = String(this.type); return `PropertySignature<${token}, ${type}, never, ${token}, ${type}>`; } } /** * @category PropertySignature * @since 3.10.0 */ export class FromPropertySignature extends AST.OptionalType { isReadonly; fromKey; constructor(type, isOptional, isReadonly, annotations, fromKey) { super(type, isOptional, annotations); this.isReadonly = isReadonly; this.fromKey = fromKey; } } /** * @category PropertySignature * @since 3.10.0 */ export class ToPropertySignature extends AST.OptionalType { isReadonly; defaultValue; constructor(type, isOptional, isReadonly, annotations, defaultValue) { super(type, isOptional, annotations); this.isReadonly = isReadonly; this.defaultValue = defaultValue; } } const formatPropertyKey = p => { if (p === undefined) { return "never"; } if (Predicate.isString(p)) { return JSON.stringify(p); } return String(p); }; /** * @category PropertySignature * @since 3.10.0 */ export class PropertySignatureTransformation { from; to; decode; encode; /** * @since 3.10.0 */ _tag = "PropertySignatureTransformation"; constructor(from, to, decode, encode) { this.from = from; this.to = to; this.decode = decode; this.encode = encode; } /** * @since 3.10.0 */ toString() { return `PropertySignature<${formatPropertySignatureToken(this.to.isOptional)}, ${this.to.type}, ${formatPropertyKey(this.from.fromKey)}, ${formatPropertySignatureToken(this.from.isOptional)}, ${this.from.type}>`; } } const mergeSignatureAnnotations = (ast, annotations) => { switch (ast._tag) { case "PropertySignatureDeclaration": { return new PropertySignatureDeclaration(ast.type, ast.isOptional, ast.isReadonly, { ...ast.annotations, ...annotations }, ast.defaultValue); } case "PropertySignatureTransformation": { return new PropertySignatureTransformation(new FromPropertySignature(ast.from.type, ast.from.isOptional, ast.from.isReadonly, ast.from.annotations), new ToPropertySignature(ast.to.type, ast.to.isOptional, ast.to.isReadonly, { ...ast.to.annotations, ...annotations }, ast.to.defaultValue), ast.decode, ast.encode); } } }; /** * @since 3.10.0 * @category symbol */ export const PropertySignatureTypeId = /*#__PURE__*/Symbol.for("effect/PropertySignature"); /** * @since 3.10.0 * @category guards */ export const isPropertySignature = u => Predicate.hasProperty(u, PropertySignatureTypeId); class PropertySignatureImpl { ast; [TypeId]; [PropertySignatureTypeId] = null; _TypeToken; _Key; _EncodedToken; _HasDefault; constructor(ast) { this.ast = ast; } pipe() { return pipeArguments(this, arguments); } annotations(annotations) { return new PropertySignatureImpl(mergeSignatureAnnotations(this.ast, toASTAnnotations(annotations))); } toString() { return String(this.ast); } } /** * @category PropertySignature * @since 3.10.0 */ export const makePropertySignature = ast => new PropertySignatureImpl(ast); class PropertySignatureWithFromImpl extends PropertySignatureImpl { from; constructor(ast, from) { super(ast); this.from = from; } annotations(annotations) { return new PropertySignatureWithFromImpl(mergeSignatureAnnotations(this.ast, toASTAnnotations(annotations)), this.from); } } /** * Lifts a `Schema` into a `PropertySignature`. * * @category PropertySignature * @since 3.10.0 */ export const propertySignature = self => new PropertySignatureWithFromImpl(new PropertySignatureDeclaration(self.ast, false, true, {}, undefined), self); /** * Enhances a property signature with a default constructor value. * * @category PropertySignature * @since 3.10.0 */ export const withConstructorDefault = /*#__PURE__*/dual(2, (self, defaultValue) => { const ast = self.ast; switch (ast._tag) { case "PropertySignatureDeclaration": return makePropertySignature(new PropertySignatureDeclaration(ast.type, ast.isOptional, ast.isReadonly, ast.annotations, defaultValue)); case "PropertySignatureTransformation": return makePropertySignature(new PropertySignatureTransformation(ast.from, new ToPropertySignature(ast.to.type, ast.to.isOptional, ast.to.isReadonly, ast.to.annotations, defaultValue), ast.decode, ast.encode)); } }); const applyDefaultValue = (o, defaultValue) => option_.match(o, { onNone: () => option_.some(defaultValue()), onSome: value => option_.some(value === undefined ? defaultValue() : value) }); const pruneUndefined = ast => AST.pruneUndefined(ast, pruneUndefined, ast => { const pruned = pruneUndefined(ast.to); if (pruned) { return new AST.Transformation(ast.from, pruned, ast.transformation); } }); /** * Enhances a property signature with a default decoding value. * * @category PropertySignature * @since 3.10.0 */ export const withDecodingDefault = /*#__PURE__*/dual(2, (self, defaultValue) => { const ast = self.ast; switch (ast._tag) { case "PropertySignatureDeclaration": { const to = AST.typeAST(ast.type); return makePropertySignature(new PropertySignatureTransformation(new FromPropertySignature(ast.type, ast.isOptional, ast.isReadonly, ast.annotations), new ToPropertySignature(pruneUndefined(to) ?? to, false, true, {}, ast.defaultValue), o => applyDefaultValue(o, defaultValue), identity)); } case "PropertySignatureTransformation": { const to = ast.to.type; return makePropertySignature(new PropertySignatureTransformation(ast.from, new ToPropertySignature(pruneUndefined(to) ?? to, false, ast.to.isReadonly, ast.to.annotations, ast.to.defaultValue), o => applyDefaultValue(ast.decode(o), defaultValue), ast.encode)); } } }); /** * Enhances a property signature with a default decoding value and a default constructor value. * * @category PropertySignature * @since 3.10.0 */ export const withDefaults = /*#__PURE__*/dual(2, (self, defaults) => self.pipe(withDecodingDefault(defaults.decoding), withConstructorDefault(defaults.constructor))); /** * Enhances a property signature by specifying a different key for it in the Encoded type. * * @category PropertySignature * @since 3.10.0 */ export const fromKey = /*#__PURE__*/dual(2, (self, key) => { const ast = self.ast; switch (ast._tag) { case "PropertySignatureDeclaration": { return makePropertySignature(new PropertySignatureTransformation(new FromPropertySignature(ast.type, ast.isOptional, ast.isReadonly, ast.annotations, key), new ToPropertySignature(AST.typeAST(ast.type), ast.isOptional, ast.isReadonly, {}, ast.defaultValue), identity, identity)); } case "PropertySignatureTransformation": return makePropertySignature(new PropertySignatureTransformation(new FromPropertySignature(ast.from.type, ast.from.isOptional, ast.from.isReadonly, ast.from.annotations, key), ast.to, ast.decode, ast.encode)); } }); /** * Converts an optional property to a required one through a transformation `Option -> Type`. * * - `decode`: `none` as argument means the value is missing in the input. * - `encode`: `none` as return value means the value will be missing in the output. * * @category PropertySignature * @since 3.10.0 */ export const optionalToRequired = (from, to, options) => makePropertySignature(new PropertySignatureTransformation(new FromPropertySignature(from.ast, true, true, {}, undefined), new ToPropertySignature(to.ast, false, true, {}, undefined), o => option_.some(options.decode(o)), option_.flatMap(options.encode))); /** * Converts an optional property to a required one through a transformation `Type -> Option`. * * - `decode`: `none` as return value means the value will be missing in the output. * - `encode`: `none` as argument means the value is missing in the input. * * @category PropertySignature * @since 3.10.0 */ export const requiredToOptional = (from, to, options) => makePropertySignature(new PropertySignatureTransformation(new FromPropertySignature(from.ast, false, true, {}, undefined), new ToPropertySignature(to.ast, true, true, {}, undefined), option_.flatMap(options.decode), o => option_.some(options.encode(o)))); /** * Converts an optional property to another optional property through a transformation `Option -> Option`. * * - `decode`: * - `none` as argument means the value is missing in the input. * - `none` as return value means the value will be missing in the output. * - `encode`: * - `none` as argument means the value is missing in the input. * - `none` as return value means the value will be missing in the output. * * @category PropertySignature * @since 3.10.0 */ export const optionalToOptional = (from, to, options) => makePropertySignature(new PropertySignatureTransformation(new FromPropertySignature(from.ast, true, true, {}, undefined), new ToPropertySignature(to.ast, true, true, {}, undefined), options.decode, options.encode)); const optionalPropertySignatureAST = (self, options) => { const isExact = options?.exact; const defaultValue = options?.default; const isNullable = options?.nullable; const asOption = options?.as == "Option"; const asOptionEncode = options?.onNoneEncoding ? option_.orElse(options.onNoneEncoding) : identity; if (isExact) { if (defaultValue) { if (isNullable) { return withConstructorDefault(optionalToRequired(NullOr(self), typeSchema(self), { decode: option_.match({ onNone: defaultValue, onSome: a => a === null ? defaultValue() : a }), encode: option_.some }), defaultValue).ast; } else { return withConstructorDefault(optionalToRequired(self, typeSchema(self), { decode: option_.match({ onNone: defaultValue, onSome: identity }), encode: option_.some }), defaultValue).ast; } } else if (asOption) { if (isNullable) { return optionalToRequired(NullOr(self), OptionFromSelf(typeSchema(self)), { decode: option_.filter(Predicate.isNotNull), encode: asOptionEncode }).ast; } else { return optionalToRequired(self, OptionFromSelf(typeSchema(self)), { decode: identity, encode: identity }).ast; } } else { if (isNullable) { return optionalToOptional(NullOr(self), typeSchema(self), { decode: option_.filter(Predicate.isNotNull), encode: identity }).ast; } else { return new PropertySignatureDeclaration(self.ast, true, true, {}, undefined); } } } else { if (defaultValue) { if (isNullable) { return withConstructorDefault(optionalToRequired(NullishOr(self), typeSchema(self), { decode: option_.match({ onNone: defaultValue, onSome: a => a == null ? defaultValue() : a }), encode: option_.some }), defaultValue).ast; } else { return withConstructorDefault(optionalToRequired(UndefinedOr(self), typeSchema(self), { decode: option_.match({ onNone: defaultValue, onSome: a => a === undefined ? defaultValue() : a }), encode: option_.some }), defaultValue).ast; } } else if (asOption) { if (isNullable) { return optionalToRequired(NullishOr(self), OptionFromSelf(typeSchema(self)), { decode: option_.filter(a => a != null), encode: asOptionEncode }).ast; } else { return optionalToRequired(UndefinedOr(self), OptionFromSelf(typeSchema(self)), { decode: option_.filter(Predicate.isNotUndefined), encode: asOptionEncode }).ast; } } else { if (isNullable) { return optionalToOptional(NullishOr(self), UndefinedOr(typeSchema(self)), { decode: option_.filter(Predicate.isNotNull), encode: identity }).ast; } else { return new PropertySignatureDeclaration(UndefinedOr(self).ast, true, true, {}, undefined); } } } }; /** * @category PropertySignature * @since 3.10.0 */ export const optional = self => { const ast = self.ast === AST.undefinedKeyword || self.ast === AST.neverKeyword ? AST.undefinedKeyword : UndefinedOr(self).ast; return new PropertySignatureWithFromImpl(new PropertySignatureDeclaration(ast, true, true, {}, undefined), self); }; /** * @category PropertySignature * @since 3.10.0 */ export const optionalWith = /*#__PURE__*/dual(args => isSchema(args[0]), (self, options) => { return new PropertySignatureWithFromImpl(optionalPropertySignatureAST(self, options), self); }); const preserveMissingMessageAnnotation = /*#__PURE__*/AST.whiteListAnnotations([AST.MissingMessageAnnotationId]); const getDefaultTypeLiteralAST = (fields, records) => { const ownKeys = util_.ownKeys(fields); const pss = []; if (ownKeys.length > 0) { const from = []; const to = []; const transformations = []; for (let i = 0; i < ownKeys.length; i++) { const key = ownKeys[i]; const field = fields[key]; if (isPropertySignature(field)) { const ast = field.ast; switch (ast._tag) { case "PropertySignatureDeclaration": { const type = ast.type; const isOptional = ast.isOptional; const toAnnotations = ast.annotations; from.push(new AST.PropertySignature(key, type, isOptional, true, preserveMissingMessageAnnotation(ast))); to.push(new AST.PropertySignature(key, AST.typeAST(type), isOptional, true, toAnnotations)); pss.push(new AST.PropertySignature(key, type, isOptional, true, toAnnotations)); break; } case "PropertySignatureTransformation": { const fromKey = ast.from.fromKey ?? key; from.push(new AST.PropertySignature(fromKey, ast.from.type, ast.from.isOptional, true, ast.from.annotations)); to.push(new AST.PropertySignature(key, ast.to.type, ast.to.isOptional, true, ast.to.annotations)); transformations.push(new AST.PropertySignatureTransformation(fromKey, key, ast.decode, ast.encode)); break; } } } else { from.push(new AST.PropertySignature(key, field.ast, false, true)); to.push(new AST.PropertySignature(key, AST.typeAST(field.ast), false, true)); pss.push(new AST.PropertySignature(key, field.ast, false, true)); } } if (array_.isNonEmptyReadonlyArray(transformations)) { const issFrom = []; const issTo = []; for (const r of records) { const { indexSignatures, propertySignatures } = AST.record(r.key.ast, r.value.ast); propertySignatures.forEach(ps => { from.push(ps); to.push(new AST.PropertySignature(ps.name, AST.typeAST(ps.type), ps.isOptional, ps.isReadonly, ps.annotations)); }); indexSignatures.forEach(is => { issFrom.push(is); issTo.push(new AST.IndexSignature(is.parameter, AST.typeAST(is.type), is.isReadonly)); }); } return new AST.Transformation(new AST.TypeLiteral(from, issFrom, { [AST.AutoTitleAnnotationId]: "Struct (Encoded side)" }), new AST.TypeLiteral(to, issTo, { [AST.AutoTitleAnnotationId]: "Struct (Type side)" }), new AST.TypeLiteralTransformation(transformations)); } } const iss = []; for (const r of records) { const { indexSignatures, propertySignatures } = AST.record(r.key.ast, r.value.ast); propertySignatures.forEach(ps => pss.push(ps)); indexSignatures.forEach(is => iss.push(is)); } return new AST.TypeLiteral(pss, iss); }; const lazilyMergeDefaults = (fields, out) => { const ownKeys = util_.ownKeys(fields); for (const key of ownKeys) { const field = fields[key]; if (out[key] === undefined && isPropertySignature(field)) { const ast = field.ast; const defaultValue = ast._tag === "PropertySignatureDeclaration" ? ast.defaultValue : ast.to.defaultValue; if (defaultValue !== undefined) { out[key] = defaultValue(); } } } return out; }; const makeTypeLiteralClass = (fields, records, ast = getDefaultTypeLiteralAST(fields, records)) => { return class TypeLiteralClass extends make(ast) { static annotations(annotations) { return makeTypeLiteralClass(this.fields, this.records, mergeSchemaAnnotations(this.ast, annotations)); } static fields = { ...fields }; static records = [...records]; static make = (props, options) => { const propsWithDefaults = lazilyMergeDefaults(fields, { ...props }); return getDisableValidationMakeOption(options) ? propsWithDefaults : ParseResult.validateSync(this)(propsWithDefaults); }; static pick(...keys) { return Struct(struct_.pick(fields, ...keys)); } static omit(...keys) { return Struct(struct_.omit(fields, ...keys)); } }; }; export function Struct(fields, ...records) { return makeTypeLiteralClass(fields, records); } /** * Returns a property signature that represents a tag. * A tag is a literal value that is used to distinguish between different types of objects. * The tag is optional when using the `make` method. * * @see {@link TaggedStruct} * * @example * ```ts * import { Schema } from "effect" * * const User = Schema.Struct({ * _tag: Schema.tag("User"), * name: Schema.String, * age: Schema.Number * }) * * assert.deepStrictEqual(User.make({ name: "John", age: 44 }), { _tag: "User", name: "John", age: 44 }) * ``` * * @since 3.10.0 */ export const tag = tag => Literal(tag).pipe(propertySignature, withConstructorDefault(() => tag)); /** * A tagged struct is a struct that has a tag property that is used to distinguish between different types of objects. * * The tag is optional when using the `make` method. * * @example * ```ts * import { Schema } from "effect" * * const User = Schema.TaggedStruct("User", { * name: Schema.String, * age: Schema.Number * }) * * assert.deepStrictEqual(User.make({ name: "John", age: 44 }), { _tag: "User", name: "John", age: 44 }) * ``` * * @category constructors * @since 3.10.0 */ export const TaggedStruct = (value, fields) => Struct({ _tag: tag(value), ...fields }); const makeRecordClass = (key, value, ast) => class RecordClass extends makeTypeLiteralClass({}, [{ key, value }], ast) { static annotations(annotations) { return makeRecordClass(key, value, mergeSchemaAnnotations(this.ast, annotations)); } static key = key; static value = value; }; /** * @category constructors * @since 3.10.0 */ export const Record = options => makeRecordClass(options.key, options.value); /** * @category struct transformations * @since 3.10.0 */ export const pick = (...keys) => self => make(AST.pick(self.ast, keys)); /** * @category struct transformations * @since 3.10.0 */ export const omit = (...keys) => self => make(AST.omit(self.ast, keys)); /** * Given a schema `Schema<A, I, R>` and a key `key: K`, this function extracts a specific field from the `A` type, * producing a new schema that represents a transformation from the `{ readonly [key]: I[K] }` type to `A[K]`. * * @example * ```ts * import * as Schema from "effect/Schema" * * // --------------------------------------------- * // use case: pull out a single field from a * // struct through a transformation * // --------------------------------------------- * * const mytable = Schema.Struct({ * column1: Schema.NumberFromString, * column2: Schema.Number * }) * * // const pullOutColumn: S.Schema<number, { * // readonly column1: string; * // }, never> * const pullOutColumn = mytable.pipe(Schema.pluck("column1")) * * console.log(Schema.decodeUnknownEither(Schema.Array(pullOutColumn))([{ column1: "1", column2: 100 }, { column1: "2", column2: 300 }])) * // Output: { _id: 'Either', _tag: 'Right', right: [ 1, 2 ] } * ``` * * @category struct transformations * @since 3.10.0 */ export const pluck = /*#__PURE__*/dual(2, (schema, key) => { const ps = AST.getPropertyKeyIndexedAccess(AST.typeAST(schema.ast), key); const value = make(ps.isOptional ? AST.orUndefined(ps.type) : ps.type); const out = transform(schema.pipe(pick(key)), value, { strict: true, decode: i => i[key], encode: a => ps.isOptional && a === undefined ? {} : { [key]: a } }); return out; }); function makeBrandClass(from, ast) { return class BrandClass extends make(ast) { static annotations(annotations) { return makeBrandClass(this.from, mergeSchemaAnnotations(this.ast, annotations)); } static make = (a, options) => { return getDisableValidationMakeOption(options) ? a : ParseResult.validateSync(this)(a); }; static from = from; }; } /** * Returns a nominal branded schema by applying a brand to a given schema. * * ``` * Schema<A> + B -> Schema<A & Brand<B>> * ``` * * @param self - The input schema to be combined with the brand. * @param brand - The brand to apply. * * @example * ```ts * import * as Schema from "effect/Schema" * * const Int = Schema.Number.pipe(Schema.int(), Schema.brand("Int")) * type Int = Schema.Schema.Type<typeof Int> // number & Brand<"Int"> * ``` * * @category branding * @since 3.10.0 */ export const brand = (brand, annotations) => self => { const annotation = option_.match(AST.getBrandAnnotation(self.ast), { onNone: () => [brand], onSome: brands => [...brands, brand] }); const ast = AST.annotations(self.ast, toASTAnnotations({ [AST.BrandAnnotationId]: annotation, ...annotations })); return makeBrandClass(self, ast); }; /** * @category combinators * @since 3.10.0 */ export const partial = self => make(AST.partial(self.ast)); /** * @category combinators * @since 3.10.0 */ export const partialWith = /*#__PURE__*/dual(args => isSchema(args[0]), (self, options) => make(AST.partial(self.ast, options))); /** * @category combinators * @since 3.10.0 */ export const required = self => make(AST.required(self.ast)); /** * Creates a new schema with shallow mutability applied to its properties. * * @param schema - The original schema to make properties mutable (shallowly). * * @category combinators * @since 3.10.0 */ export const mutable = schema => make(AST.mutable(schema.ast)); const intersectTypeLiterals = (x, y, path) => { if (AST.isTypeLiteral(x) && AST.isTypeLiteral(y)) { const propertySignatures = [...x.propertySignatures]; for (const ps of y.propertySignatures) { const name = ps.name; const i = propertySignatures.findIndex(ps => ps.name === name); if (i === -1) { propertySignatures.push(ps); } else { const { isOptional, type } = propertySignatures[i]; propertySignatures[i] = new AST.PropertySignature(name, extendAST(type, ps.type, path.concat(name)), isOptional, true); } } return new AST.TypeLiteral(propertySignatures, x.indexSignatures.concat(y.indexSignatures)); } throw new Error(errors_.getSchemaExtendErrorMessage(x, y, path)); }; const preserveRefinementAnnotations = /*#__PURE__*/AST.blackListAnnotations([AST.IdentifierAnnotationId]); const addRefinementToMembers = (refinement, asts) => asts.map(ast => new AST.Refinement(ast, refinement.filter, preserveRefinementAnnotations(refinement))); const extendAST = (x, y, path) => AST.Union.make(intersectUnionMembers([x], [y], path)); const getTypes = ast => AST.isUnion(ast) ? ast.types : [ast]; const intersectUnionMembers = (xs, ys, path) => array_.flatMap(xs, x => array_.flatMap(ys, y => { switch (y._tag) { case "Literal": { if (Predicate.isString(y.literal) && AST.isStringKeyword(x) || Predicate.isNumber(y.literal) && AST.isNumberKeyword(x) || Predicate.isBoolean(y.literal) && AST.isBooleanKeyword(x)) { return [y]; } break; } case "StringKeyword": { if (y === AST.stringKeyword) { if (AST.isStringKeyword(x) || AST.isLiteral(x) && Predicate.isString(x.literal)) { return [x]; } else if (AST.isRefinement(x)) { return addRefinementToMembers(x, intersectUnionMembers(getTypes(x.from), [y], path)); }