@baqhub/sdk
Version:
The official JavaScript SDK for the BAQ federated app platform.
400 lines (399 loc) • 13.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.utf8Bytes = exports.base64Bytes = exports.isoDate = void 0;
exports.object = object;
exports.partialObject = partialObject;
exports.dualObject = dualObject;
exports.union = union;
exports.exclusiveUnion = exclusiveUnion;
exports.defaultValue = defaultValue;
exports.arrayIgnore = arrayIgnore;
exports.enumRecord = enumRecord;
exports.listEnumValues = listEnumValues;
exports.enumeration = enumeration;
exports.weakEnumeration = weakEnumeration;
exports.enumerationWithValues = enumerationWithValues;
exports.weakEnumerationWithValues = weakEnumerationWithValues;
exports.optional = optional;
exports.clean = clean;
exports.validate = validate;
exports.tryDecode = tryDecode;
exports.decode = decode;
exports.encode = encode;
const tslib_1 = require("tslib");
const Either_js_1 = require("fp-ts/lib/Either.js");
const function_js_1 = require("fp-ts/lib/function.js");
const t = tslib_1.__importStar(require("io-ts"));
const io_ts_reporters_1 = tslib_1.__importDefault(require("io-ts-reporters"));
const camelCase_js_1 = tslib_1.__importDefault(require("lodash/camelCase.js"));
const isArray_js_1 = tslib_1.__importDefault(require("lodash/isArray.js"));
const isNumber_js_1 = tslib_1.__importDefault(require("lodash/isNumber.js"));
const isString_js_1 = tslib_1.__importDefault(require("lodash/isString.js"));
const map_js_1 = tslib_1.__importDefault(require("lodash/map.js"));
const snakeCase_js_1 = tslib_1.__importDefault(require("lodash/snakeCase.js"));
const fixImport_js_1 = require("./fixImport.js");
const type_js_1 = require("./type.js");
tslib_1.__exportStar(require("io-ts"), exports);
const reporter = (0, fixImport_js_1.fixImport)(io_ts_reporters_1.default);
exports.isoDate = new t.Type("DateFromISOString", (u) => u instanceof Date, (u, c) => (0, function_js_1.pipe)(t.string.validate(u, c), (0, Either_js_1.chain)(s => {
const d = new Date(s);
return isNaN(d.getTime()) ? t.failure(u, c) : t.success(d);
})), a => a.toISOString());
//
// Readonly + Exact + ChangeCase combinator.
//
function getProps(codec) {
switch (codec._tag) {
case "RefinementType":
case "ReadonlyType":
return getProps(codec.type);
case "InterfaceType":
case "StrictType":
case "PartialType":
return codec.props;
case "IntersectionType":
return codec.types.reduce((props, type) => Object.assign(props, getProps(type)), {});
}
}
function getExactWithCaseTypeName(codec) {
return `ExactWithCase<${codec.name}>`;
}
function prefixAwareCase(transform) {
return (value) => {
if (value[0] === "$") {
return "$" + transform(value.substring(1));
}
return transform(value);
};
}
function stripKeysAndChangeCase(o, props, transformCheckKey, transformResultKey) {
const keys = Object.getOwnPropertyNames(o);
let shouldStrip = false;
const r = {};
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const checkKey = transformCheckKey(key);
const resultKey = transformResultKey(key);
const isPropKey = Object.prototype.hasOwnProperty.call(props, checkKey);
if (!isPropKey || key !== resultKey) {
shouldStrip = true;
}
if (isPropKey) {
r[resultKey] = o[key];
}
}
return shouldStrip ? r : o;
}
function exactWithCase(codec, name = getExactWithCaseTypeName(codec)) {
const props = getProps(codec);
return new t.ExactType(name, codec.is, (u, c) => {
const unknownResult = t.UnknownRecord.validate(u, c);
if ((0, Either_js_1.isLeft)(unknownResult)) {
return unknownResult;
}
const strippedObject = stripKeysAndChangeCase(unknownResult.right, props, prefixAwareCase(camelCase_js_1.default), prefixAwareCase(camelCase_js_1.default));
return codec.validate(strippedObject, c);
}, a => {
const encoded = codec.encode(a);
return stripKeysAndChangeCase(encoded, props, t.identity, prefixAwareCase(snakeCase_js_1.default));
}, codec);
}
function object(props, name) {
return t.readonly(exactWithCase(t.type(props, name)));
}
function partialObject(props, name) {
return t.readonly(exactWithCase(t.partial(props, name)));
}
function dualObject(props, partialProps) {
return t.intersection([object(props), partialObject(partialProps)]);
}
//
// Standard union.
//
function pushAll(xs, ys) {
const l = ys.length;
for (let i = 0; i < l; i++) {
xs.push(ys[i]);
}
}
function union(codecs) {
const name = `Union(${codecs.map(type => type.name).join(" | ")})`;
return new t.UnionType(name, (u) => codecs.some(type => type.is(u)), (u, c) => {
const errors = [];
let result;
for (let i = 0; i < codecs.length; i++) {
const codec = codecs[i];
const r = codec.validate(u, t.appendContext(c, String(i), codec, u));
if ((0, Either_js_1.isLeft)(r)) {
pushAll(errors, r.left);
}
else if (t.UnknownRecord.is(u)) {
result = result
? {
...result,
...r.right,
}
: r.right;
}
else {
return t.success(r.right);
}
}
if ((0, type_js_1.isDefined)(result)) {
return t.success(result);
}
return t.failures(errors);
}, codecs.every(c => c.encode === t.identity)
? t.identity
: a => {
let result;
for (const codec of codecs) {
if (!codec.is(a)) {
continue;
}
const value = codec.encode(a);
if (t.UnknownRecord.is(value)) {
const value = codec.encode(a);
result = result
? {
...result,
...value,
}
: value;
}
else {
return value;
}
}
if ((0, type_js_1.isDefined)(result)) {
return result;
}
// https://github.com/gcanti/io-ts/pull/305
throw new Error(`no codec found to encode value in union type ${name}`);
}, codecs);
}
//
// Todo: Optimized union for records.
//
//
// Exclusive union.
//
function exclusiveUnion(codecs) {
const name = `ExclusiveUnion(${codecs.map(type => type.name).join(" | ")})`;
return new t.UnionType(name, (u) => codecs.some(type => type.is(u)), (u, c) => {
const errors = [];
const successes = [];
for (let i = 0; i < codecs.length; i++) {
const codec = codecs[i];
const r = codec.validate(u, t.appendContext(c, String(i), codec, u));
if ((0, Either_js_1.isLeft)(r)) {
errors.push(...r.left);
}
else {
successes.push(r.right);
}
}
if (successes.length === 1) {
return t.success(successes[0]);
}
else if (successes.length > 1) {
return t.failure(u, c, "Multiple matching codecs.");
}
else {
return t.failures(errors);
}
}, codecs.every(c => c.encode === t.identity)
? t.identity
: a => {
for (const codec of codecs) {
if (codec.is(a)) {
return codec.encode(a);
}
}
// https://github.com/gcanti/io-ts/pull/305
throw new Error(`no codec found to encode value in union type ${name}`);
}, codecs);
}
//
// Default value.
//
function defaultValue(type, defaultValue) {
return new t.Type("DefaultOf" + type.name, type.is, value => {
if (!(0, type_js_1.isDefined)(value)) {
return t.success(defaultValue);
}
return type.decode(value);
}, value => {
if (value === defaultValue) {
return undefined;
}
return type.encode(value);
});
}
//
// Array that ignores invalid elements.
//
function arrayIgnore(itemsType) {
return new t.Type("ArrayIgnoreOf" + itemsType.name, (value) => {
if (!(0, isArray_js_1.default)(value)) {
return false;
}
return value.every(value => itemsType.is(value));
}, (value, context) => {
if (!(0, isArray_js_1.default)(value)) {
return t.failure(value, context);
}
const array = value
.map(item => itemsType.decode(item))
.filter(Either_js_1.isRight)
.map(item => item.right);
return t.success(array);
}, value => {
return value.map(item => itemsType.encode(item));
});
}
//
// Partial record combinator for enums.
//
function enumRecord(domain, codomain) {
return t.record(domain, codomain);
}
function listEnumValues(sourceEnum) {
function isStringKey(key) {
const numberKey = Number(key);
return isNaN(numberKey) || sourceEnum[sourceEnum[key] || ""] !== numberKey;
}
function keyToValue(key) {
return sourceEnum[key];
}
return Object.keys(sourceEnum).filter(isStringKey).map(keyToValue);
}
function enumerationBase(name, sourceEnum) {
const enumValues = new Set(listEnumValues(sourceEnum));
function isEnumValue(value) {
return ((0, isString_js_1.default)(value) || (0, isNumber_js_1.default)(value)) && enumValues.has(value);
}
return new t.Type(name, isEnumValue, (value, context) => {
if (isEnumValue(value)) {
return t.success(value);
}
return t.failure(value, context);
}, value => value);
}
function enumeration(sourceEnum) {
return enumerationBase("Enum", sourceEnum);
}
function weakEnumeration(sourceEnum) {
return enumerationBase("WeakEnum", sourceEnum);
}
function toLowerCase(source) {
return source.toLowerCase();
}
function enumerationWithValuesBase(name, sourceEnum, values, { isCaseSensitive } = { isCaseSensitive: true }) {
// Case sensitivity.
const valueTransform = isCaseSensitive ? t.identity : toLowerCase;
// Mapper.
const invertedValues = (0, map_js_1.default)(values, (value, key) => [valueTransform(value), key]);
const valueMap = new Map(invertedValues);
// Type guard.
const enumValues = new Set(listEnumValues(sourceEnum));
function isEnumValue(value) {
return ((0, isString_js_1.default)(value) || (0, isNumber_js_1.default)(value)) && enumValues.has(value);
}
return new t.Type(name, isEnumValue, (value, context) => {
const enumValue = (0, isString_js_1.default)(value) && valueMap.get(valueTransform(value));
if (!enumValue) {
return t.failure(value, context);
}
return t.success(enumValue);
}, value => values[value]);
}
function enumerationWithValues(sourceEnum, values, options) {
return enumerationWithValuesBase("EnumWithValues", sourceEnum, values, options);
}
function weakEnumerationWithValues(sourceEnum, values, options) {
return enumerationWithValuesBase("WeakEnumWithValues", sourceEnum, values, options);
}
//
// Bytes type.
//
class Base64Bytes extends t.Type {
constructor() {
function is(value) {
return value instanceof Uint8Array;
}
function validate(value, context) {
if (is(value)) {
return t.success(value);
}
try {
const valueBytes = atob(String(value));
return t.success(Uint8Array.from(valueBytes, c => c.charCodeAt(0)));
}
catch (_error) {
return t.failure(value, context);
}
}
function encode(value) {
const bytes = String.fromCharCode.apply(null, value);
return btoa(bytes);
}
super("Base64Bytes", is, validate, encode);
}
}
exports.base64Bytes = new Base64Bytes();
class Utf8Bytes extends t.Type {
constructor() {
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
function is(value) {
return value instanceof Uint8Array;
}
function validate(value, context) {
if (is(value)) {
return t.success(value);
}
try {
return t.success(textEncoder.encode(String(value)));
}
catch (_error) {
return t.failure(value, context);
}
}
function encode(value) {
return textDecoder.decode(value);
}
super("Utf8Bytes", is, validate, encode);
}
}
exports.utf8Bytes = new Utf8Bytes();
function optional(model) {
return union([t.undefined, model]);
}
function clean(model) {
return model;
}
function validate(model, value) {
if (!model.is(value)) {
throw new Error("Error while validating.");
}
return value;
}
function tryDecode(model, value) {
const result = model.decode(value);
if ((0, Either_js_1.isLeft)(result)) {
return undefined;
}
return result.right;
}
function decode(model, value) {
const result = model.decode(value);
if ((0, Either_js_1.isLeft)(result)) {
console.log(...reporter.report(result));
throw new Error("Error while decoding.");
}
return result.right;
}
function encode(model, value) {
return model.encode(value);
}