-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Extract serializer modules and use in core umi library (#68)
* Create new packages for sub-libraries * Extract umi-options package * Extract umi-serializers-core * Extract umi-serializers-encodings * Extract umi-public-keys * Extract umi-serializers-numbers * Refactor toDataView helper * Create numberFactory helper * Refactor integer tests * Refactor other tests * Extract each number serializer into its own file * Cleanup unused test helpers * Extract umi-serializers * Refactor tests * Remove option and pubkey code from main umi lib * Extract serializers and export as sub-path * Import serializer options from lib * Re-exports subset of serializers for backwards compatibility And mark them as deprecated * Remove unused export * Fix tests * Delegate dataViewSerializer to umi-serializers * Add deprecation notices for serializer interfaces * Fix test and linting * Add serializers submodule to externals of consumers * Add changeset * Add OptionOrNullable type
- Loading branch information
1 parent
abb3011
commit 4accd34
Showing
201 changed files
with
4,461 additions
and
2,755 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
--- | ||
'@metaplex-foundation/umi-transaction-factory-web3js': patch | ||
'@metaplex-foundation/umi-serializers-encodings': patch | ||
'@metaplex-foundation/umi-serializer-data-view': patch | ||
'@metaplex-foundation/umi-serializers-numbers': patch | ||
'@metaplex-foundation/umi-serializers-core': patch | ||
'@metaplex-foundation/umi-serializer-beet': patch | ||
'@metaplex-foundation/umi-public-keys': patch | ||
'@metaplex-foundation/umi-serializers': patch | ||
'@metaplex-foundation/umi-options': patch | ||
'@metaplex-foundation/umi': patch | ||
--- | ||
|
||
Extract serializer modules and use in core umi library |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# umi-options | ||
|
||
A TypeScript implementation of Rust Options | ||
|
||
## Installation | ||
|
||
```sh | ||
npm install @metaplex-foundation/umi-options | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": "../../babel.config.json" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
{ | ||
"name": "@metaplex-foundation/umi-options", | ||
"version": "0.0.1", | ||
"description": "A TypeScript implementation of Rust Options", | ||
"license": "MIT", | ||
"sideEffects": false, | ||
"module": "dist/esm/index.mjs", | ||
"main": "dist/cjs/index.cjs", | ||
"types": "dist/types/index.d.ts", | ||
"exports": { | ||
".": { | ||
"import": "./dist/esm/index.mjs", | ||
"require": "./dist/cjs/index.cjs" | ||
} | ||
}, | ||
"files": [ | ||
"/dist/cjs", | ||
"/dist/esm", | ||
"/dist/types", | ||
"/src" | ||
], | ||
"scripts": { | ||
"lint": "eslint --ext js,ts,tsx src", | ||
"lint:fix": "eslint --fix --ext js,ts,tsx src", | ||
"clean": "rimraf dist", | ||
"build": "pnpm clean && tsc && tsc -p test/tsconfig.json && rollup -c", | ||
"test": "ava" | ||
}, | ||
"devDependencies": { | ||
"@ava/typescript": "^3.0.1", | ||
"ava": "^5.1.0" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"author": "Metaplex Maintainers <[email protected]>", | ||
"homepage": "https://metaplex.com", | ||
"repository": { | ||
"url": "https://github.com/metaplex-foundation/umi.git" | ||
}, | ||
"typedoc": { | ||
"entryPoint": "./src/index.ts", | ||
"readmeFile": "./README.md", | ||
"displayName": "umi-options" | ||
}, | ||
"ava": { | ||
"typescript": { | ||
"compile": false, | ||
"rewritePaths": { | ||
"src/": "dist/test/src/", | ||
"test/": "dist/test/test/" | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { createConfigs } from '../../rollup.config'; | ||
import pkg from './package.json'; | ||
|
||
export default createConfigs({ | ||
pkg, | ||
builds: [ | ||
{ | ||
dir: 'dist/esm', | ||
format: 'es', | ||
}, | ||
{ | ||
dir: 'dist/cjs', | ||
format: 'cjs', | ||
}, | ||
], | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/** | ||
* Defines a type `T` that can also be `null`. | ||
* @category Utils — Options | ||
*/ | ||
export type Nullable<T> = T | null; | ||
|
||
/** | ||
* An implementation of the Rust Option type in JavaScript. | ||
* It can be one of the following: | ||
* - <code>{@link Some}<T></code>: Meaning there is a value of type T. | ||
* - <code>{@link None}</code>: Meaning there is no value. | ||
* | ||
* @category Utils — Options | ||
*/ | ||
export type Option<T> = Some<T> | None; | ||
|
||
/** | ||
* Defines a looser type that can be used when serializing an {@link Option}. | ||
* This allows us to pass null or the Option value directly whilst still | ||
* supporting the Option type for use-cases that need more type safety. | ||
* | ||
* @category Utils — Options | ||
*/ | ||
export type OptionOrNullable<T> = Option<T> | Nullable<T>; | ||
|
||
/** | ||
* Represents an option of type `T` that has a value. | ||
* | ||
* @see {@link Option} | ||
* @category Utils — Options | ||
*/ | ||
export type Some<T> = { __option: 'Some'; value: T }; | ||
|
||
/** | ||
* Represents an option of type `T` that has no value. | ||
* | ||
* @see {@link Option} | ||
* @category Utils — Options | ||
*/ | ||
export type None = { __option: 'None' }; | ||
|
||
/** | ||
* Creates a new {@link Option} of type `T` that has a value. | ||
* | ||
* @see {@link Option} | ||
* @category Utils — Options | ||
*/ | ||
export const some = <T>(value: T): Option<T> => ({ __option: 'Some', value }); | ||
|
||
/** | ||
* Creates a new {@link Option} of type `T` that has no value. | ||
* | ||
* @see {@link Option} | ||
* @category Utils — Options | ||
*/ | ||
export const none = <T>(): Option<T> => ({ __option: 'None' }); | ||
|
||
/** | ||
* Whether the given data is an {@link Option}. | ||
* @category Utils — Options | ||
*/ | ||
export const isOption = <T = unknown>(input: any): input is Option<T> => | ||
input && | ||
typeof input === 'object' && | ||
'__option' in input && | ||
((input.__option === 'Some' && 'value' in input) || | ||
input.__option === 'None'); | ||
|
||
/** | ||
* Whether the given {@link Option} is a {@link Some}. | ||
* @category Utils — Options | ||
*/ | ||
export const isSome = <T>(option: Option<T>): option is Some<T> => | ||
option.__option === 'Some'; | ||
|
||
/** | ||
* Whether the given {@link Option} is a {@link None}. | ||
* @category Utils — Options | ||
*/ | ||
export const isNone = <T>(option: Option<T>): option is None => | ||
option.__option === 'None'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './common'; | ||
export * from './unwrapOption'; | ||
export * from './unwrapOptionRecursively'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { Nullable, Option, isSome, none, some } from './common'; | ||
|
||
/** | ||
* Unwraps the value of an {@link Option} of type `T` | ||
* or returns a fallback value that defaults to `null`. | ||
* | ||
* @category Utils — Options | ||
*/ | ||
export function unwrapOption<T>(option: Option<T>): Nullable<T>; | ||
export function unwrapOption<T, U>(option: Option<T>, fallback: () => U): T | U; | ||
export function unwrapOption<T, U = null>( | ||
option: Option<T>, | ||
fallback?: () => U | ||
): T | U { | ||
if (isSome(option)) return option.value; | ||
return fallback ? fallback() : (null as U); | ||
} | ||
|
||
/** | ||
* Wraps a nullable value into an {@link Option}. | ||
* | ||
* @category Utils — Options | ||
*/ | ||
export const wrapNullable = <T>(nullable: Nullable<T>): Option<T> => | ||
nullable !== null ? some(nullable) : none<T>(); | ||
|
||
/** | ||
* Unwraps the value of an {@link Option} of type `T`. | ||
* If the option is a {@link Some}, it returns its value, | ||
* Otherwise, it returns `null`. | ||
* | ||
* @category Utils — Options | ||
* @deprecated Use {@link unwrapOption} instead. | ||
*/ | ||
export const unwrapSome = <T>(option: Option<T>): Nullable<T> => | ||
isSome(option) ? option.value : null; | ||
|
||
/** | ||
* Unwraps the value of an {@link Option} of type `T` | ||
* or returns a custom fallback value. | ||
* If the option is a {@link Some}, it returns its value, | ||
* Otherwise, it returns the return value of the provided fallback callback. | ||
* | ||
* @category Utils — Options | ||
* @deprecated Use {@link unwrapOption} instead. | ||
*/ | ||
export const unwrapSomeOrElse = <T, U>( | ||
option: Option<T>, | ||
fallback: () => U | ||
): T | U => (isSome(option) ? option.value : fallback()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { None, Some, isOption, isSome } from './common'; | ||
|
||
/** | ||
* A type that defines the recursive unwrapping of a type `T` | ||
* such that all nested {@link Option} types are unwrapped. | ||
* | ||
* For each nested {@link Option} type, if the option is a {@link Some}, | ||
* it returns the type of its value, otherwise, it returns the provided | ||
* fallback type `U` which defaults to `null`. | ||
* | ||
* @category Utils — Options | ||
*/ | ||
type UnwrappedOption<T, U = null> = T extends Some<infer TValue> | ||
? UnwrappedOption<TValue, U> | ||
: T extends None | ||
? U | ||
: T extends object | ||
? { [key in keyof T]: UnwrappedOption<T[key], U> } | ||
: T extends Array<infer TItem> | ||
? Array<UnwrappedOption<TItem, U>> | ||
: T; | ||
|
||
/** | ||
* Recursively go through a type `T`such that all | ||
* nested {@link Option} types are unwrapped. | ||
* | ||
* For each nested {@link Option} type, if the option is a {@link Some}, | ||
* it returns its value, otherwise, it returns the provided fallback value | ||
* which defaults to `null`. | ||
* | ||
* @category Utils — Options | ||
*/ | ||
export function unwrapOptionRecursively<T>(input: T): UnwrappedOption<T>; | ||
export function unwrapOptionRecursively<T, U>( | ||
input: T, | ||
fallback: () => U | ||
): UnwrappedOption<T, U>; | ||
export function unwrapOptionRecursively<T, U = null>( | ||
input: T, | ||
fallback?: () => U | ||
): UnwrappedOption<T, U> { | ||
// Because null passes `typeof input === 'object'`. | ||
if (!input) return input as UnwrappedOption<T, U>; | ||
const next = <X>(x: X) => | ||
(fallback | ||
? unwrapOptionRecursively(x, fallback) | ||
: unwrapOptionRecursively(x)) as UnwrappedOption<X, U>; | ||
|
||
// Handle Option. | ||
if (isOption(input)) { | ||
if (isSome(input)) return next(input.value) as UnwrappedOption<T, U>; | ||
return (fallback ? fallback() : null) as UnwrappedOption<T, U>; | ||
} | ||
|
||
// Walk. | ||
if (Array.isArray(input)) { | ||
return input.map(next) as UnwrappedOption<T, U>; | ||
} | ||
if (typeof input === 'object') { | ||
return Object.fromEntries( | ||
Object.entries(input).map(([k, v]) => [k, next(v)]) | ||
) as UnwrappedOption<T, U>; | ||
} | ||
return input as UnwrappedOption<T, U>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import test from 'ava'; | ||
import { isNone, isSome, none, Option, some } from '../src'; | ||
|
||
test('it can create Some and None options', (t) => { | ||
const optionA: Option<number> = some(42); | ||
t.deepEqual(optionA, { __option: 'Some', value: 42 }); | ||
|
||
const optionB: Option<null> = some(null); | ||
t.deepEqual(optionB, { __option: 'Some', value: null }); | ||
|
||
const optionC: Option<unknown> = none(); | ||
t.deepEqual(optionC, { __option: 'None' }); | ||
|
||
const optionD: Option<string> = none<string>(); | ||
t.deepEqual(optionD, { __option: 'None' }); | ||
}); | ||
|
||
test('it can check if an option is Some or None', (t) => { | ||
const optionA = some(42); | ||
t.true(isSome(optionA)); | ||
t.false(isNone(optionA)); | ||
|
||
const optionB = none<number>(); | ||
t.false(isSome(optionB)); | ||
t.true(isNone(optionB)); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"extends": "../../../tsconfig.json", | ||
"include": ["./**/*"], | ||
"compilerOptions": { | ||
"module": "commonjs", | ||
"outDir": "../dist/test", | ||
"declarationDir": null, | ||
"declaration": false, | ||
"emitDeclarationOnly": false | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import test from 'ava'; | ||
import { none, some, unwrapOption, wrapNullable } from '../src'; | ||
|
||
test('it can unwrap an Option as a Nullable', (t) => { | ||
t.is(unwrapOption(some(42)), 42); | ||
t.is(unwrapOption(some(null)), null); | ||
t.is(unwrapOption(some('hello')), 'hello'); | ||
t.is(unwrapOption(none()), null); | ||
t.is(unwrapOption(none<number>()), null); | ||
t.is(unwrapOption(none<string>()), null); | ||
}); | ||
|
||
test('it can unwrap an Option using a fallback callback', (t) => { | ||
const fallbackA = () => 42 as const; | ||
t.is(unwrapOption(some(1), fallbackA), <number | 42>1); | ||
t.is(unwrapOption(some('A'), fallbackA), <string | 42>'A'); | ||
t.is(unwrapOption(none(), fallbackA), <unknown | 42>42); | ||
|
||
const fallbackB = () => { | ||
throw new Error('Fallback Error'); | ||
}; | ||
t.is(unwrapOption(some(1), fallbackB), 1); | ||
t.is(unwrapOption(some('A'), fallbackB), 'A'); | ||
t.throws(() => unwrapOption(none(), fallbackB), { | ||
message: 'Fallback Error', | ||
}); | ||
}); | ||
|
||
test('it can wrap a Nullable as an Option', (t) => { | ||
t.deepEqual(wrapNullable(42), some(42)); | ||
t.deepEqual(wrapNullable('hello'), some('hello')); | ||
t.deepEqual(wrapNullable(false), some(false)); | ||
t.deepEqual(wrapNullable(undefined), some(undefined)); | ||
t.deepEqual(wrapNullable<string>(null), none<string>()); | ||
t.deepEqual(wrapNullable<number>(null), none<number>()); | ||
}); |
Oops, something went wrong.