feat: use bigint instead of long (#1403)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Improved support for large integer values in the API, allowing
accurate handling of big numbers in GraphQL queries and responses.
- **Bug Fixes**
- Enhanced reliability when dealing with very large numeric fields,
reducing the risk of data loss or inaccuracies for disk, share, and
memory statistics.
- **Chores**
	- Updated internal dependencies to improve handling of big integers.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Eli Bosley
2025-05-22 10:42:36 -07:00
committed by GitHub
parent 5355115af2
commit 574d572d65
8 changed files with 36 additions and 61 deletions

View File

@@ -115,6 +115,7 @@
"ini": "^5.0.0", "ini": "^5.0.0",
"ip": "^2.0.1", "ip": "^2.0.1",
"jose": "^6.0.0", "jose": "^6.0.0",
"json-bigint-patch": "^0.0.8",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"multi-ini": "^2.3.2", "multi-ini": "^2.3.2",
"mustache": "^4.2.0", "mustache": "^4.2.0",

View File

@@ -1,4 +1,5 @@
import '@app/dotenv.js'; import '@app/dotenv.js';
import 'json-bigint-patch';
import { execa } from 'execa'; import { execa } from 'execa';
import { CommandFactory } from 'nest-commander'; import { CommandFactory } from 'nest-commander';

View File

@@ -1,5 +1,6 @@
import 'reflect-metadata'; import 'reflect-metadata';
import 'global-agent/bootstrap.js'; import 'global-agent/bootstrap.js';
import 'json-bigint-patch';
import '@app/dotenv.js'; import '@app/dotenv.js';
import { type NestFastifyApplication } from '@nestjs/platform-fastify'; import { type NestFastifyApplication } from '@nestjs/platform-fastify';

View File

@@ -4,7 +4,7 @@ import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql'; import { GraphQLModule } from '@nestjs/graphql';
import { NoUnusedVariablesRule } from 'graphql'; import { NoUnusedVariablesRule } from 'graphql';
import { JSONResolver, URLResolver } from 'graphql-scalars'; import { GraphQLBigInt, JSONResolver, URLResolver } from 'graphql-scalars';
import { ENVIRONMENT } from '@app/environment.js'; import { ENVIRONMENT } from '@app/environment.js';
import { getters } from '@app/store/index.js'; import { getters } from '@app/store/index.js';
@@ -14,7 +14,6 @@ import {
} from '@app/unraid-api/graph/directives/use-permissions.directive.js'; } from '@app/unraid-api/graph/directives/use-permissions.directive.js';
import { ResolversModule } from '@app/unraid-api/graph/resolvers/resolvers.module.js'; import { ResolversModule } from '@app/unraid-api/graph/resolvers/resolvers.module.js';
import { sandboxPlugin } from '@app/unraid-api/graph/sandbox-plugin.js'; import { sandboxPlugin } from '@app/unraid-api/graph/sandbox-plugin.js';
import { GraphQLLong } from '@app/unraid-api/graph/scalars/graphql-type-long.js';
import { PrefixedID as PrefixedIDScalar } from '@app/unraid-api/graph/scalars/graphql-type-prefixed-id.js'; import { PrefixedID as PrefixedIDScalar } from '@app/unraid-api/graph/scalars/graphql-type-prefixed-id.js';
import { PluginModule } from '@app/unraid-api/plugin/plugin.module.js'; import { PluginModule } from '@app/unraid-api/plugin/plugin.module.js';
@@ -50,7 +49,7 @@ import { PluginModule } from '@app/unraid-api/plugin/plugin.module.js';
}, },
resolvers: { resolvers: {
JSON: JSONResolver, JSON: JSONResolver,
Long: GraphQLLong, Long: GraphQLBigInt,
URL: URLResolver, URL: URLResolver,
}, },
buildSchemaOptions: { buildSchemaOptions: {

View File

@@ -1,9 +1,9 @@
import { Field, InputType, Int, ObjectType, registerEnumType } from '@nestjs/graphql'; import { Field, InputType, Int, ObjectType, registerEnumType } from '@nestjs/graphql';
import { IsEnum } from 'class-validator'; import { IsEnum } from 'class-validator';
import { GraphQLBigInt } from 'graphql-scalars';
import { Node } from '@app/unraid-api/graph/resolvers/base.model.js'; import { Node } from '@app/unraid-api/graph/resolvers/base.model.js';
import { GraphQLLong } from '@app/unraid-api/graph/scalars/graphql-type-long.js';
import { PrefixedID } from '@app/unraid-api/graph/scalars/graphql-type-prefixed-id.js'; import { PrefixedID } from '@app/unraid-api/graph/scalars/graphql-type-prefixed-id.js';
@ObjectType() @ObjectType()
@@ -43,7 +43,7 @@ export class ArrayDisk extends Node {
@Field(() => String, { nullable: true }) @Field(() => String, { nullable: true })
device?: string; device?: string;
@Field(() => GraphQLLong, { description: '(KB) Disk Size total', nullable: true }) @Field(() => GraphQLBigInt, { description: '(KB) Disk Size total', nullable: true })
size?: number | null; size?: number | null;
@Field(() => ArrayDiskStatus, { nullable: true }) @Field(() => ArrayDiskStatus, { nullable: true })
@@ -58,40 +58,40 @@ export class ArrayDisk extends Node {
}) })
temp?: number | null; temp?: number | null;
@Field(() => GraphQLLong, { @Field(() => GraphQLBigInt, {
nullable: true, nullable: true,
description: description:
'Count of I/O read requests sent to the device I/O drivers. These statistics may be cleared at any time.', 'Count of I/O read requests sent to the device I/O drivers. These statistics may be cleared at any time.',
}) })
numReads?: number | null; numReads?: number | null;
@Field(() => GraphQLLong, { @Field(() => GraphQLBigInt, {
nullable: true, nullable: true,
description: description:
'Count of I/O writes requests sent to the device I/O drivers. These statistics may be cleared at any time.', 'Count of I/O writes requests sent to the device I/O drivers. These statistics may be cleared at any time.',
}) })
numWrites?: number | null; numWrites?: number | null;
@Field(() => GraphQLLong, { @Field(() => GraphQLBigInt, {
nullable: true, nullable: true,
description: description:
'Number of unrecoverable errors reported by the device I/O drivers. Missing data due to unrecoverable array read errors is filled in on-the-fly using parity reconstruct (and we attempt to write this data back to the sector(s) which failed). Any unrecoverable write error results in disabling the disk.', 'Number of unrecoverable errors reported by the device I/O drivers. Missing data due to unrecoverable array read errors is filled in on-the-fly using parity reconstruct (and we attempt to write this data back to the sector(s) which failed). Any unrecoverable write error results in disabling the disk.',
}) })
numErrors?: number | null; numErrors?: number | null;
@Field(() => GraphQLLong, { @Field(() => GraphQLBigInt, {
nullable: true, nullable: true,
description: '(KB) Total Size of the FS (Not present on Parity type drive)', description: '(KB) Total Size of the FS (Not present on Parity type drive)',
}) })
fsSize?: number | null; fsSize?: number | null;
@Field(() => GraphQLLong, { @Field(() => GraphQLBigInt, {
nullable: true, nullable: true,
description: '(KB) Free Size on the FS (Not present on Parity type drive)', description: '(KB) Free Size on the FS (Not present on Parity type drive)',
}) })
fsFree?: number | null; fsFree?: number | null;
@Field(() => GraphQLLong, { @Field(() => GraphQLBigInt, {
nullable: true, nullable: true,
description: '(KB) Used Size on the FS (Not present on Parity type drive)', description: '(KB) Used Size on the FS (Not present on Parity type drive)',
}) })
@@ -243,13 +243,13 @@ export class Share extends Node {
@Field(() => String, { description: 'Display name', nullable: true }) @Field(() => String, { description: 'Display name', nullable: true })
name?: string | null; name?: string | null;
@Field(() => GraphQLLong, { description: '(KB) Free space', nullable: true }) @Field(() => GraphQLBigInt, { description: '(KB) Free space', nullable: true })
free?: number | null; free?: number | null;
@Field(() => GraphQLLong, { description: '(KB) Used Size', nullable: true }) @Field(() => GraphQLBigInt, { description: '(KB) Used Size', nullable: true })
used?: number | null; used?: number | null;
@Field(() => GraphQLLong, { description: '(KB) Total size', nullable: true }) @Field(() => GraphQLBigInt, { description: '(KB) Total size', nullable: true })
size?: number | null; size?: number | null;
@Field(() => [String], { description: 'Disks that are included in this share', nullable: true }) @Field(() => [String], { description: 'Disks that are included in this share', nullable: true })

View File

@@ -8,7 +8,7 @@ import {
registerEnumType, registerEnumType,
} from '@nestjs/graphql'; } from '@nestjs/graphql';
import { GraphQLJSON } from 'graphql-scalars'; import { GraphQLBigInt, GraphQLJSON } from 'graphql-scalars';
import { Node } from '@app/unraid-api/graph/resolvers/base.model.js'; import { Node } from '@app/unraid-api/graph/resolvers/base.model.js';
import { ThemeName } from '@app/unraid-api/graph/resolvers/customization/theme.model.js'; import { ThemeName } from '@app/unraid-api/graph/resolvers/customization/theme.model.js';
@@ -290,7 +290,7 @@ export class Display extends Node {
@ObjectType({ implements: () => Node }) @ObjectType({ implements: () => Node })
export class MemoryLayout extends Node { export class MemoryLayout extends Node {
@Field(() => Int) @Field(() => GraphQLBigInt)
size!: number; size!: number;
@Field(() => String, { nullable: true }) @Field(() => String, { nullable: true })
@@ -326,34 +326,34 @@ export class MemoryLayout extends Node {
@ObjectType({ implements: () => Node }) @ObjectType({ implements: () => Node })
export class InfoMemory extends Node { export class InfoMemory extends Node {
@Field(() => Int) @Field(() => GraphQLBigInt)
max!: number; max!: number;
@Field(() => Int) @Field(() => GraphQLBigInt)
total!: number; total!: number;
@Field(() => Int) @Field(() => GraphQLBigInt)
free!: number; free!: number;
@Field(() => Int) @Field(() => GraphQLBigInt)
used!: number; used!: number;
@Field(() => Int) @Field(() => GraphQLBigInt)
active!: number; active!: number;
@Field(() => Int) @Field(() => GraphQLBigInt)
available!: number; available!: number;
@Field(() => Int) @Field(() => GraphQLBigInt)
buffcache!: number; buffcache!: number;
@Field(() => Int) @Field(() => GraphQLBigInt)
swaptotal!: number; swaptotal!: number;
@Field(() => Int) @Field(() => GraphQLBigInt)
swapused!: number; swapused!: number;
@Field(() => Int) @Field(() => GraphQLBigInt)
swapfree!: number; swapfree!: number;
@Field(() => [MemoryLayout]) @Field(() => [MemoryLayout])

View File

@@ -1,35 +0,0 @@
import type { ASTNode } from 'graphql';
import { GraphQLScalarType } from 'graphql';
import { Kind } from 'graphql/language/index.js';
const MAX_LONG = Number.MAX_SAFE_INTEGER;
const MIN_LONG = Number.MIN_SAFE_INTEGER;
const coerceLong = (value) => {
if (value === '')
throw new TypeError('Long cannot represent non 52-bit signed integer value: (empty string)');
const num = Number(value);
if (num == num && num <= MAX_LONG && num >= MIN_LONG) {
if (num < 0) {
return Math.ceil(num);
}
return Math.floor(num);
}
throw new TypeError('Long cannot represent non 52-bit signed integer value: ' + String(value));
};
const parseLiteral = (ast: ASTNode) => {
if (ast.kind === Kind.INT) {
const num = parseInt(ast.value, 10);
if (num <= MAX_LONG && num >= MIN_LONG) return num;
}
return null;
};
export const GraphQLLong = new GraphQLScalarType({
name: 'Long',
description: 'The `Long` scalar type represents 52-bit integers',
serialize: coerceLong,
parseValue: coerceLong,
parseLiteral: parseLiteral,
});

8
pnpm-lock.yaml generated
View File

@@ -222,6 +222,9 @@ importers:
jose: jose:
specifier: ^6.0.0 specifier: ^6.0.0
version: 6.0.10 version: 6.0.10
json-bigint-patch:
specifier: ^0.0.8
version: 0.0.8
lodash-es: lodash-es:
specifier: ^4.17.21 specifier: ^4.17.21
version: 4.17.21 version: 4.17.21
@@ -8285,6 +8288,9 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
hasBin: true hasBin: true
json-bigint-patch@0.0.8:
resolution: {integrity: sha512-xa0LTQsyaq8awYyZyuUsporWisZFiyqzxGW8CKM3t7oouf0GFAKYJnqAm6e9NLNBQOCtOLvy614DEiRX/rPbnA==}
json-buffer@3.0.1: json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
@@ -20855,6 +20861,8 @@ snapshots:
jsesc@3.1.0: {} jsesc@3.1.0: {}
json-bigint-patch@0.0.8: {}
json-buffer@3.0.1: {} json-buffer@3.0.1: {}
json-parse-better-errors@1.0.2: {} json-parse-better-errors@1.0.2: {}