Skip to content

Commit

Permalink
feat(testcontainers | json): add testcontainers and jellyfish json to…
Browse files Browse the repository at this point in the history
… standard (#482)

* feat(testcontainers): add testcontainers to standard

* fix lint

* update i9n

* include jellyfish json to standard

* remove build info

* add buildinfo to git ignore
  • Loading branch information
veralygit authored Dec 4, 2024
1 parent f1b067b commit 7888474
Show file tree
Hide file tree
Showing 53 changed files with 4,003 additions and 106 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ coverage

# turborepo
.turbo

# typescript
tsconfig.build.tsbuildinfo
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"prettier": "@ordzaar/standard-prettier",
"devDependencies": {
"@ordzaar/standard-prettier": "workspace:*",
"eslint": "^8.57.1",
"@ordzaar/eslint-config": "workspace:*",
"husky": "^9.1.7",
"lint-staged": "^15.2.10",
"prettier": "^3.3.3",
Expand Down
16 changes: 16 additions & 0 deletions packages/eslint-config/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Eslint Config

`@ordzaar/eslint-config`

Within your `package.json`:

```json
{
"eslintConfig": {
"extends": ["@ordzaar"],
"parserOptions": {
"project": "./tsconfig.json"
}
}
}
```
85 changes: 85 additions & 0 deletions packages/eslint-config/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
module.exports = {
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"airbnb-base",
"airbnb-typescript/base",
"prettier",
],
plugins: ["simple-import-sort", "check-file", "unused-imports", "prettier"],
parser: "@typescript-eslint/parser",
rules: {
"@typescript-eslint/no-floating-promises": "error",
"no-console": "error",
curly: "error",
"max-classes-per-file": "off",
"class-methods-use-this": "off",
"no-await-in-loop": "off",
// functions and classes are going to be hoisted in runtime, but don't let var be used before declaration
"@typescript-eslint/no-use-before-define": ["error", { functions: false, classes: false, variables: true }],

// Fix airbnb-typescript/base rule to allow leading underscores for unused vars
"@typescript-eslint/naming-convention": [
"error",
{
selector: "variable",
format: ["camelCase", "PascalCase", "UPPER_CASE"],
leadingUnderscore: "allow",
},
{
selector: "function",
format: ["camelCase", "PascalCase"],
},
{
selector: "typeLike",
format: ["PascalCase"],
},
],
/**
* Separates out the `no-unused-vars` rule depending on it being an import statement in the AST and providing
* an auto-fix rule to remove the nodes if they are imports.
* With this, we can now target test files with `'unused-imports/no-unused-vars': 'off'` for testing DX.
*/
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],

// import sort
"sort-imports": "off",
"import/order": "off",
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",

// To be overridden by each project
"check-file/filename-naming-convention": "off",
},
overrides: [
{
files: ["**/*.{test,spec,unit,i9n,e2e}.{js,ts}"],
rules: {
"@typescript-eslint/no-floating-promises": 0,
/**
* To cater for complex test scenarios, where we need to scope blocks. This allows variables to be reused,
* so we don't have to create `const getObject` and `const updatedObject` for each scenario.
* We can just use `const object`.
*/
"no-lone-blocks": "off",

/**
* Separates out the `no-unused-vars` rule depending on it being an import statement in the AST and providing
* an auto-fix rule to remove the nodes if they are imports.
* With this, we can now target test files with `'unused-imports/no-unused-vars': 'off'` for testing DX.
*/
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": "off",
},
},
],
};
11 changes: 11 additions & 0 deletions packages/eslint-config/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "@ordzaar/eslint-config",
"version": "0.0.0",
"main": "index.js",
"files": [
"index.js"
],
"dependencies": {
"@ordzaar/standard-linter": "workspace:*"
}
}
10 changes: 10 additions & 0 deletions packages/jellyfish-json/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# @ordzaar/jellyfish-json

`JellyfishJSON` allows parsing of JSON with `'lossless'`, `'bignumber'` and `'number'` numeric precision.

- `'lossless'` uses LosslessJSON that parses numeric values as LosslessNumber. With LosslessNumber, one can perform
regular numeric operations, and it will throw an error when this would result in losing information.
- `'bignumber'` parse all numeric values as 'BigNumber' using bignumber.js library.
- `'number'` parse all numeric values as 'Number' and precision will be loss if it exceeds IEEE-754 standard.
- `'PrecisionPath'` provides path based precision mapping, specifying 'bignumber' will automatically map all Number in
that path as 'bignumber'. Otherwise, it will default to number, This applies deeply.
28 changes: 28 additions & 0 deletions packages/jellyfish-json/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"private": false,
"name": "@ordzaar/jellyfish-json",
"version": "0.0.0",
"license": "MIT",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "tsc -b ./tsconfig.build.json",
"test": "jest"
},
"dependencies": {
"@types/lossless-json": "^1.0.1",
"bignumber.js": "^9.0.2",
"lossless-json": "^1.0.5"
},
"jest": {
"displayName": "test",
"preset": "@ordzaar/turbo-jest"
},
"devDependencies": {
"@ordzaar/turbo-jest": "workspace:*",
"@ordzaar/standard-typescript": "workspace:*"
}
}
89 changes: 89 additions & 0 deletions packages/jellyfish-json/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import BigNumber from "bignumber.js";
import { LosslessNumber, parse, stringify } from "lossless-json";
import { PrecisionPath, remap } from "./remap";

export { BigNumber, LosslessNumber };
export type { PrecisionPath };

/**
* Numeric precision to parse RPC payload as.
*
* 'lossless' uses LosslessJSON that parses numeric values as LosslessNumber. With LosslessNumber, one can perform
* regular numeric operations, and it will throw an error when this would result in losing information.
*
* 'bignumber' parse all numeric values as 'BigNumber' using bignumber.js library.
*
* 'number' parse all numeric values as 'Number' and precision will be loss if it exceeds IEEE-754 standard.
*/
export type Precision = "lossless" | "bignumber" | "number";

/**
* Revive lossless as a type
*/
function reviveLosslessAs(transformer: (string: string) => any) {
return (key: string, value: any) => {
if (value instanceof LosslessNumber) {
return transformer(value.toString());
}

return value;
};
}

/**
* JellyfishJSON allows parsing of JSON with 'lossless', 'bignumber' and 'number' numeric precision.
*/
export const JellyfishJSON = {
/**
* Precision parses all numeric value as the given Precision.
*
* PrecisionPath selectively remap each numeric value based on the mapping provided,
* defaults to number if precision is not provided for the key. This works deeply.
*
* @param {string} text JSON string to parse into object.
* @param {Precision | PrecisionPath} precision Numeric precision to parse payload as.
*/
parse(text: string, precision: Precision | PrecisionPath): any {
if (typeof precision === "string") {
switch (precision) {
case "lossless":
return parse(text);

case "bignumber":
return parse(
text,
reviveLosslessAs((string) => new BigNumber(string)),
);

case "number":
return parse(
text,
reviveLosslessAs((string) => Number(string)),
);

default:
throw new Error(`JellyfishJSON.parse ${precision as string} precision is not supported`);
}
}

const losslessObj = parse(text);
return remap(losslessObj, precision);
},

/**
* @param {any} value object to stringify, with no risk of losing precision.
*/
stringify(value: any): string {
function replacer(key: string, value: any): any {
if (value instanceof BigNumber) {
return new LosslessNumber(value.toString());
}
if (typeof value === "bigint") {
return new LosslessNumber(value.toString());
}
return value;
}

return stringify(value, replacer);
},
};
88 changes: 88 additions & 0 deletions packages/jellyfish-json/src/index.unit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { JellyfishJSON, LosslessNumber } from "../src/";
import BigNumber from "bignumber.js";

describe("parse", () => {
describe("lossless", () => {
/* eslint-disable @typescript-eslint/restrict-plus-operands */
it("18 digit significant should parse as lossless without precision lost", () => {
const obj = JellyfishJSON.parse('{"lossless":1200000000.00000001}', "lossless");
expect(obj.lossless.toString()).toStrictEqual("1200000000.00000001");
});

it("10 digit significant should parse as lossless with math operators should not have an error", () => {
const obj = JellyfishJSON.parse('{"lossless":1200000000}', "lossless");
expect((obj.lossless + 1).toString()).toStrictEqual("1200000001");
});

it("18 digit significant should parse as lossless with math operators should throw an error", () => {
const obj = JellyfishJSON.parse('{"lossless":1200000000.00000001}', "lossless");
expect(() => {
console.log(obj.lossless + 1);
}).toThrow(/Cannot convert to number: number would be truncated \(value: 1200000000\.00000001\)/);
});
/* eslint-enable @typescript-eslint/restrict-plus-operands */
});

describe("bignumber", () => {
it("18 digit significant should parse as bignumber without precision lost", () => {
const obj = JellyfishJSON.parse('{"bignumber":1200000000.00000001}', "bignumber");
expect(obj.bignumber.toString()).toStrictEqual("1200000000.00000001");
});
});

describe("number", () => {
it("18 digit significant should parse as number with precision lost", () => {
const obj = JellyfishJSON.parse('{"number":1200000000.00000001}', "number");
expect(obj.number.toString()).not.toStrictEqual("1200000000.00000001");
});

it("10 digit significant should parse as number without precision lost", () => {
const obj = JellyfishJSON.parse('{"number":1200000000}', "number");
expect(obj.number.toString()).toStrictEqual("1200000000");
});
});
});

describe("stringify", () => {
it("should stringify number as json numeric", () => {
const string = JellyfishJSON.stringify({
number: Number("1200000000"),
});
expect(string).toStrictEqual('{"number":1200000000}');
});

it("should stringify lossless as json numeric", () => {
const string = JellyfishJSON.stringify({
lossless: new LosslessNumber("1200000000.00000001"),
});
expect(string).toStrictEqual('{"lossless":1200000000.00000001}');
});

it("should stringify bignumber as json number", () => {
const string = JellyfishJSON.stringify({
bignumber: new BigNumber("1200000000.00000001"),
});
expect(string).toStrictEqual('{"bignumber":1200000000.00000001}');
});
it("should stringify bigint as json number", () => {
const string = JellyfishJSON.stringify({
bigint: BigInt("120000000000000001"),
});
expect(string).toStrictEqual('{"bigint":120000000000000001}');
});
});

it("should remap object at root with precision", () => {
const parsed = JellyfishJSON.parse(
`{
"big": 10.4,
"num": 1234
}`,
{
big: "bignumber",
},
);

expect(parsed.big instanceof BigNumber).toStrictEqual(true);
expect(parsed.num).toStrictEqual(1234);
});
42 changes: 42 additions & 0 deletions packages/jellyfish-json/src/precision.unit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import BigNumber from "bignumber.js";

/**
* Why JavaScript default number should not be used as it lose precision
*/
describe("number will lose precision", () => {
it("1200000000.00000003 should not lose precision as a number but it does", () => {
/* eslint-disable no-loss-of-precision */
const dfi = 1200000000.00000003;

expect(() => {
expect(dfi.toString()).toStrictEqual("1200000000.00000003");
}).toThrow();
});

it("12.00000003 * 1000000 should be 12000000.03 but its not", () => {
const obj = JSON.parse('{"dfi": 12.00000003}');
const dfi = obj.dfi * 1000000;

expect(() => {
expect(dfi).toStrictEqual(12000000.03);
}).toThrow();
});
});

/**
* BigNumber from 'bignumber.js' implementations will not lose precision
*/
describe("BigNumber should not lose precision", () => {
it("1200000000.00000003 should not lose precision", () => {
const dfi = new BigNumber("1200000000.00000003");

expect(dfi.toString()).toStrictEqual("1200000000.00000003");
});

it("12.00000003 * 1000000 should be 12000000.03", () => {
const obj = { dfi: new BigNumber("12.00000003") };
const dfi = obj.dfi.multipliedBy(1000000);

expect(dfi.toString()).toStrictEqual("12000000.03");
});
});
Loading

0 comments on commit 7888474

Please sign in to comment.