Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

x-value

Package Overview
Dependencies
Maintainers
1
Versions
61
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

x-value

[![NPM version](https://img.shields.io/npm/v/x-value?color=%23cb3837&style=flat-square)](https://www.npmjs.com/package/x-value) [![Repository package.json version](https://img.shields.io/github/package-json/v/vilic/x-value?color=%230969da&label=repo&style

  • 0.1.3
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
28
decreased by-56.25%
Maintainers
1
Weekly downloads
 
Created
Source

NPM version Repository package.json version Coveralls MIT license

X-Value

X-Value (X stands for "cross") is a medium-somewhat-neutral runtime type validation library.

Comparing to alternatives like io-ts and Zod, X-Value uses medium/value concept and allows values to be decoded from and encoded to different mediums.

Installation

yarn add x-value
# or
npm install x-value

Usages

Defining types with X-Value is similar to io-ts/Zod.

import * as x from 'x-value';

const Oops = x.object({
  foo: x.string,
  bar: x.number.optional(),
});

const Rock = x.record(x.string, x.number);

const Aha = x.array(Oops);

const Tick = x.tuple([x.string, x.number]);

const Um = x.union([Oops, x.boolean]);

const I = x.intersection([
  Oops,
  x.object({
    yoha: x.boolean,
  }),
]);

interface R {
  type: 'recursive';
  child: R;
}

const R = x.recursive<R>(R =>
  x.object({
    type: x.literal('recursive'),
    child: R,
  }),
);

Get static type of type object:

declare global {
  namespace XValue {
    interface Using extends x.UsingJSONMedium {}
  }
}

type Oops = x.TypeOf<typeof Oops>;
type JSONOops = x.MediumTypeOf<'json', typeof Oops>;

Refine type:

const Email = x.string.refined(value =>
  x.refinement(value.includes('@'), value),
);

// Or with refined or nominal type:
const Email = x.string.refined<never, `${string}@${string}`>(value =>
  x.refinement(value.includes('@'), value),
);
const Email = x.string.refined<'email'>(value =>
  x.refinement(value.includes('@'), value),
);

// Or just nominal type without extra constraints:
const Email = x.string.nominal<'email'>();

Decode from medium:

declare global {
  namespace XValue {
    interface Using extends x.UsingJSONMedium {}
  }
}

let value = Oops.decode(x.json, '{"foo":"abc","bar":123}');

Encode to medium:

declare global {
  namespace XValue {
    interface Using extends x.UsingJSONMedium {}
  }
}

let json = Oops.encode(x.json, {foo: 'abc', bar: 123});

Transform from medium to medium:

declare global {
  namespace XValue {
    interface Using extends x.UsingJSONMedium, x.UsingQueryStringMedium {}
  }
}

let json = Oops.transform(x.queryString, x.json, 'foo=abc&bar=123');

Type is guard:

if (Oops.is(value)) {
  // ...
}

Type satisfies assertion (will throw if does not satisfy):

let oops = Oops.satisfies(value);

Diagnose for type issues:

let issues = Oops.diagnose(value);

Mediums and Values

Mediums are what's used to store values: JSON strings, query strings, buffers etc.

For example, a string "2022-03-31T16:00:00.000Z" in JSON medium with type Date represents value new Date('2022-03-31T16:00:00.000Z').

Assuming we have 3 mediums: browser, server, rpc; and 2 types: ObjectId, Date. Their types in mediums and value are listed below.

Type\MediumBrowserRPCServerValue
ObjectIdstringpacked as stringObjectIdstring
DateDatepacked as stringDateDate

We can encode values to mediums:

let id = '6246056b1be8cbf6ca18401f';

ObjectId.encode(browser, id); // string '6246056b1be8cbf6ca18401f'
ObjectId.encode(rpc, id);     // packed string '"6246056b1be8cbf6ca18401f"'
ObjectId.encode(server, id);  // new ObjectId('6246056b1be8cbf6ca18401f')

let date = new Date('2022-03-31T16:00:00.000Z');

Date.encode(browser, date); // new Date('2022-03-31T16:00:00.000Z')
Date.encode(rpc, date);     // packed string '"2022-03-31T16:00:00.000Z"'
Date.encode(server, date);  // new Date('2022-03-31T16:00:00.000Z')

Or decode packed data of mediums to values:

// All result in '6246056b1be8cbf6ca18401f'
ObjectId.decode(browser, '6246056b1be8cbf6ca18401f');
ObjectId.decode(rpc, '"6246056b1be8cbf6ca18401f"');
ObjectId.decode(server, new ObjectId('6246056b1be8cbf6ca18401f'));

// All result in new Date('2022-03-31T16:00:00.000Z')
Date.decode(browser, new Date('2022-03-31T16:00:00.000Z'));
Date.decode(rpc, '"2022-03-31T16:00:00.000Z"');
Date.decode(server, new Date('2022-03-31T16:00:00.000Z'));

Ideally there's no need to have "value" as a separate concept because it's essentially "ECMAScript runtime medium". But to make decode/encode easier among different mediums, "value" is promoted as an interchangeable medium.

New Atomic Type

Before we can add medium support for a new type of atomic value, we need to add new atomic value. It is quite easy to do so:

import * as x from 'x-value';

// 1. Create a symbol for the new atomic type.
const newAtomicTypeSymbol = Symbol();

// 3. Create the new atomic type with constraint.
const NewAtomic = x.atomic(newAtomicTypeSymbol, value =>
  x.constraint(Buffer.isBuffer(value)),
);

declare global {
  namespace XValue {
    interface Types {
      // 2. Define the symbol to value type mapping.
      [newAtomicTypeSymbol]: Buffer;
    }
  }
}

New Medium

After creating the new atomic type, we need to create/extend a new medium that supports this type:

interface SuperJSONTypes extends x.JSONTypes {
  [newAtomicTypeSymbol]: string;
}

interface UsingSuperJSONMedium {
  'super-json': SuperJSONTypes;
}

const superJSON = x.json.extend<UsingSuperJSONMedium>('super-json', {
  codecs: {
    [newAtomicTypeSymbol]: {
      decode(value) {
        if (typeof value !== 'string') {
          throw new TypeError(
            `Expected hex string, getting ${Object.prototype.toString.call(
              value,
            )}`,
          );
        }

        return Buffer.from(value, 'hex');
      },
      encode(value) {
        return value.toString('hex');
      },
    },
  },
});

To use this medium:

declare global {
  namespace XValue {
    interface Using extends x.UsingSuperJSONMedium {}
  }
}

Medium Packing

When decode() from a medium, X-Value unpacks data for a structured input (e.g., JSON.parse()). It packs the data again on encode() (e.g., JSON.stringify()).

For medium that requires packing:

interface PackedTypes {
  // 1. Define the packed type.
  packed: string;
}

const packed = x.medium<PackedTypes>('Packed ', {
  // 2. Define packing methods.
  packing: {
    pack(data) {
      return JSON.stringify(data);
    },
    unpack(json) {
      return JSON.parse(json);
    },
  },
});

The superJSON medium is actually a packed medium. However, the related definitions are inherited from x.JSONTypes.

License

MIT License.

FAQs

Package last updated on 14 Feb 2023

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc