diff --git a/jest.config.ts b/jest.config.ts index 1d691a2..851a11c 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -9,9 +9,12 @@ const createJestConfig = nextJest({ // Add any custom config to be passed to Jest const config: Config = { coveragePathIgnorePatterns: ["/.*.tsx$"], + globals: { + window: {}, + }, testEnvironment: "node", // Add more setup options before each test is run - // setupFilesAfterEnv: ['/jest.setup.ts'], + setupFilesAfterEnv: ["/jest.setup.ts"], }; // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async diff --git a/jest.setup.ts b/jest.setup.ts new file mode 100644 index 0000000..7bd9186 --- /dev/null +++ b/jest.setup.ts @@ -0,0 +1,7 @@ +import crypto from "crypto"; + +Object.defineProperty(window, "crypto", { + value: { + randomUUID: crypto.randomUUID, + }, +}); diff --git a/package-lock.json b/package-lock.json index acaf602..34c6887 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,7 +66,9 @@ "drizzle-orm": "^0.30.1", "eslint-plugin-jest": "^27.6.3", "file-saver": "^2.0.5", + "immer": "^10.1.1", "libsql-stateless-easy": "^1.6.11", + "lodash": "^4.17.21", "lucia": "^3.2.0", "lucide-react": "^0.309.0", "magic-bytes.js": "^1.10.0", @@ -87,6 +89,7 @@ "@types/deep-equal": "^1.0.4", "@types/file-saver": "^2.0.7", "@types/jest": "^29.5.11", + "@types/lodash": "^4.17.9", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", @@ -8337,6 +8340,13 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.17.9", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.9.tgz", + "integrity": "sha512-w9iWudx1XWOHW5lQRS9iKpK/XuRhnN+0T7HvdCCd802FYkT1AMTnxndJHGrNJwRoRHkslGr4S29tjm1cT7x/7w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", @@ -13464,6 +13474,16 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/immutable": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", @@ -15680,7 +15700,7 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", diff --git a/package.json b/package.json index 12f6dfa..252d1b1 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,9 @@ "drizzle-orm": "^0.30.1", "eslint-plugin-jest": "^27.6.3", "file-saver": "^2.0.5", + "immer": "^10.1.1", "libsql-stateless-easy": "^1.6.11", + "lodash": "^4.17.21", "lucia": "^3.2.0", "lucide-react": "^0.309.0", "magic-bytes.js": "^1.10.0", @@ -106,6 +108,7 @@ "@types/deep-equal": "^1.0.4", "@types/file-saver": "^2.0.7", "@types/jest": "^29.5.11", + "@types/lodash": "^4.17.9", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", diff --git a/src/app/(theme)/connect/saved-connection-card.tsx b/src/app/(theme)/connect/saved-connection-card.tsx index 66ea332..1e581ff 100644 --- a/src/app/(theme)/connect/saved-connection-card.tsx +++ b/src/app/(theme)/connect/saved-connection-card.tsx @@ -51,14 +51,16 @@ export default function ConnectionItemCard({ : `/client/r?p=${conn.id}` } > -
-
-
+
+
+
-
-
{conn.name}
+
+
+ {conn.name} +
{DRIVER_DETAIL[conn.driver ?? "turso"].displayName}
diff --git a/src/components/gui/schema-editor/index.tsx b/src/components/gui/schema-editor/index.tsx index 0fdb85b..5dbd447 100644 --- a/src/components/gui/schema-editor/index.tsx +++ b/src/components/gui/schema-editor/index.tsx @@ -62,7 +62,7 @@ export default function SchemaEditor({ const hasChange = checkSchemaChange(value); const previewScript = useMemo(() => { - return databaseDriver.createUpdateTableSchema(value).join("\n"); + return databaseDriver.createUpdateTableSchema(value).join(";\n"); }, [value, databaseDriver]); return ( @@ -188,6 +188,9 @@ export default function SchemaEditor({ onChange={onChange} onAddColumn={onAddColumn} schemaName={value.schemaName} + disabledEditExistingColumn={ + !databaseDriver.getFlags().supportModifyColumn + } /> >; + disabledEditExistingColumn?: boolean; }) { const { setNodeRef, @@ -109,7 +111,7 @@ function ColumnItem({ transition, setActivatorNodeRef, } = useSortable({ id: value.key, disabled: !!value.old }); - const disabled = !!value.old; + const disabled = !!disabledEditExistingColumn && !!value.old; const style = { transform: CSS.Transform.toString(transform), @@ -338,11 +340,13 @@ export default function SchemaEditorColumnList({ onChange, schemaName, onAddColumn, + disabledEditExistingColumn, }: Readonly<{ columns: DatabaseTableColumnChange[]; onChange: Dispatch>; schemaName?: string; onAddColumn: () => void; + disabledEditExistingColumn?: boolean; }>) { const headerStyle = "text-xs p-2 text-left bg-secondary border"; @@ -397,6 +401,7 @@ export default function SchemaEditorColumnList({ key={col.key} onChange={onChange} schemaName={schemaName} + disabledEditExistingColumn={disabledEditExistingColumn} /> ))} diff --git a/src/components/gui/tabs/schema-editor-tab.tsx b/src/components/gui/tabs/schema-editor-tab.tsx index 0779e19..f22a6db 100644 --- a/src/components/gui/tabs/schema-editor-tab.tsx +++ b/src/components/gui/tabs/schema-editor-tab.tsx @@ -4,6 +4,8 @@ import { useDatabaseDriver } from "@/context/driver-provider"; import SchemaSaveDialog from "../schema-editor/schema-save-dialog"; import { DatabaseTableSchemaChange } from "@/drivers/base-driver"; import SchemaEditor from "../schema-editor"; +import { createTableSchemaDraft } from "@/components/lib/sql-generate.schema"; +import { cloneDeep } from "lodash"; interface SchemaEditorTabProps { tableName?: string; @@ -37,24 +39,7 @@ export default function SchemaEditorTab({ databaseDriver .tableSchema(schemaName, name) .then((schema) => { - setSchema({ - schemaName, - name: { - old: schema.tableName, - new: schema.tableName, - }, - columns: schema.columns.map((col) => ({ - key: window.crypto.randomUUID(), - old: col, - new: structuredClone(col), - })), - constraints: (schema.constraints ?? []).map((con) => ({ - id: window.crypto.randomUUID(), - old: con, - new: structuredClone(con), - })), - createScript: schema.createScript, - }); + setSchema(createTableSchemaDraft(schemaName, schema)); }) .catch(console.error) .finally(() => setLoading(false)); @@ -85,13 +70,13 @@ export default function SchemaEditorTab({ .map((col) => ({ key: col.key, old: col.old, - new: structuredClone(col.old), + new: cloneDeep(col.old), })) .filter((col) => col.old), constraints: prev.constraints.map((con) => ({ id: window.crypto.randomUUID(), old: con.old, - new: structuredClone(con.old), + new: cloneDeep(con.old), })), }; }); diff --git a/src/components/lib/sql-generate.schema.ts b/src/components/lib/sql-generate.schema.ts index 0434ea9..3a8d9d4 100644 --- a/src/components/lib/sql-generate.schema.ts +++ b/src/components/lib/sql-generate.schema.ts @@ -1,11 +1,10 @@ -import { escapeIdentity, escapeSqlValue } from "@/drivers/sqlite/sql-helper"; import deepEqual from "deep-equal"; import { - DatabaseTableColumn, DatabaseTableColumnChange, - DatabaseTableColumnConstraint, + DatabaseTableSchema, DatabaseTableSchemaChange, } from "@/drivers/base-driver"; +import { cloneDeep } from "lodash"; export function checkSchemaColumnChange(change: DatabaseTableColumnChange) { return !deepEqual(change.old, change.new); @@ -23,165 +22,26 @@ export function checkSchemaChange(change: DatabaseTableSchemaChange) { return false; } -function wrapParen(str: string) { - if (str.length >= 2 && str.startsWith("(") && str.endsWith(")")) return str; - return "(" + str + ")"; -} - -function geneateCreateColumn(col: DatabaseTableColumn): string { - const tokens: string[] = [escapeIdentity(col.name), col.type]; - - if (col.constraint?.primaryKey) { - tokens.push( - [ - "PRIMARY KEY", - col.constraint.primaryKeyOrder, - col.constraint.primaryKeyConflict - ? `ON CONFLICT ${col.constraint.primaryKeyConflict}` - : undefined, - col.constraint.autoIncrement ? "AUTOINCREMENT" : undefined, - ] - .filter(Boolean) - .join(" ") - ); - } - - if (col.constraint?.unique) { - tokens.push( - [ - "UNIQUE", - col.constraint.uniqueConflict - ? `ON CONFLICT ${col.constraint.uniqueConflict}` - : undefined, - ] - .filter(Boolean) - .join(" ") - ); - } - - if (col.constraint?.notNull) { - tokens.push( - [ - "NOT NULL", - col.constraint.notNullConflict - ? `ON CONFLICT ${col.constraint.notNullConflict}` - : undefined, - ] - .filter(Boolean) - .join(" ") - ); - } - - if (col.constraint?.defaultValue) { - tokens.push( - ["DEFAULT", escapeSqlValue(col.constraint.defaultValue)].join(" ") - ); - } - - if (col.constraint?.defaultExpression) { - tokens.push( - ["DEFAULT", wrapParen(col.constraint.defaultExpression)].join(" ") - ); - } - - if (col.constraint?.generatedExpression) { - tokens.push( - [ - "GENERATED ALWAYS AS", - wrapParen(col.constraint.generatedExpression), - col.constraint.generatedType, - ].join(" ") - ); - } - - if (col.constraint?.checkExpression) { - tokens.push("CHECK " + wrapParen(col.constraint.checkExpression)); - } - - const foreignTableName = col.constraint?.foreignKey?.foreignTableName; - const foreignColumnName = (col.constraint?.foreignKey?.foreignColumns ?? [ - undefined, - ])[0]; - - if (foreignTableName && foreignColumnName) { - tokens.push( - [ - "REFERENCES", - escapeIdentity(foreignTableName) + - `(${escapeIdentity(foreignColumnName)})`, - ].join(" ") - ); - } - - return tokens.join(" "); -} - -function generateConstraintScript(con: DatabaseTableColumnConstraint) { - if (con.primaryKey) { - return `PRIMARY KEY (${con.primaryColumns?.map(escapeIdentity).join(", ")})`; - } else if (con.unique) { - return `UNIQUE (${con.uniqueColumns?.map(escapeIdentity).join(", ")})`; - } else if (con.checkExpression !== undefined) { - return `CHECK (${con.checkExpression})`; - } else if (con.foreignKey) { - return ( - `FOREIGN KEY (${con.foreignKey.columns?.map(escapeIdentity).join(", ")}) ` + - `REFERENCES ${escapeIdentity(con.foreignKey.foreignTableName ?? "")} ` + - `(${con.foreignKey.foreignColumns?.map(escapeIdentity).join(", ")})` - ); - } -} - -export default function generateSqlSchemaChange( - change: DatabaseTableSchemaChange -): string[] { - const isCreateScript = !change.name.old; - - const lines = []; - - for (const col of change.columns) { - if (col.new === null) lines.push(`DROP COLUMN ${col.old?.name}`); - else if (col.old === null) { - if (isCreateScript) { - lines.push(geneateCreateColumn(col.new)); - } else { - lines.push("ADD " + geneateCreateColumn(col.new)); - } - } else { - if (col.new.name !== col.old.name) { - lines.push( - `RENAME COLUMN ${escapeIdentity(col.old.name)} TO ${escapeIdentity( - col.new.name - )}` - ); - } - } - } - - for (const con of change.constraints) { - if (con.new) { - if (isCreateScript) { - lines.push(generateConstraintScript(con.new)); - } - } - } - - if (!isCreateScript) { - if (change.name.new !== change.name.old) { - lines.push( - `RENAME TO ${escapeIdentity(change.schemaName ?? "main")}.${escapeIdentity(change.name.new ?? "")}` - ); - } - } - - if (isCreateScript) { - return [ - `CREATE TABLE ${escapeIdentity(change.schemaName ?? "main")}.${escapeIdentity( - change.name.new || "no_table_name" - )}(\n${lines.map((line) => " " + line).join(",\n")}\n)`, - ]; - } else { - const alter = `ALTER TABLE ${escapeIdentity(change.schemaName ?? "main")}.${escapeIdentity(change.name.old ?? "")} `; - return lines.map((line) => alter + line); - } +export function createTableSchemaDraft( + schemaName: string, + schema: DatabaseTableSchema +): DatabaseTableSchemaChange { + return { + schemaName, + name: { + old: schema.tableName, + new: schema.tableName, + }, + columns: schema.columns.map((col) => ({ + key: window.crypto.randomUUID(), + old: col, + new: cloneDeep(col), + })), + constraints: (schema.constraints ?? []).map((con) => ({ + id: window.crypto.randomUUID(), + old: con, + new: cloneDeep(con), + })), + createScript: schema.createScript, + }; } diff --git a/src/drivers/base-driver.ts b/src/drivers/base-driver.ts index 791cfb3..f099088 100644 --- a/src/drivers/base-driver.ts +++ b/src/drivers/base-driver.ts @@ -204,6 +204,7 @@ export interface DriverFlags { optionalSchema: boolean; supportBigInt: boolean; supportCreateUpdateTable: boolean; + supportModifyColumn: boolean; mismatchDetection: boolean; dialect: SupportedDialect; } diff --git a/src/drivers/mysql/mysql-driver.ts b/src/drivers/mysql/mysql-driver.ts index fadad57..2ab51ef 100644 --- a/src/drivers/mysql/mysql-driver.ts +++ b/src/drivers/mysql/mysql-driver.ts @@ -49,6 +49,7 @@ export default abstract class MySQLLikeDriver extends CommonSQLImplement { defaultSchema: "", optionalSchema: false, supportBigInt: false, + supportModifyColumn: false, mismatchDetection: false, supportCreateUpdateTable: false, dialect: "mysql", diff --git a/src/drivers/sqlite-base-driver.ts b/src/drivers/sqlite-base-driver.ts index 4faf6ea..84d62c0 100644 --- a/src/drivers/sqlite-base-driver.ts +++ b/src/drivers/sqlite-base-driver.ts @@ -15,7 +15,7 @@ import { escapeSqlValue } from "@/drivers/sqlite/sql-helper"; import { parseCreateTableScript } from "@/drivers/sqlite/sql-parse-table"; import { parseCreateTriggerScript } from "@/drivers/sqlite/sql-parse-trigger"; import CommonSQLImplement from "./common-sql-imp"; -import generateSqlSchemaChange from "@/components/lib/sql-generate.schema"; +import generateSqlSchemaChange from "./sqlite/sqlite-generate-schema"; export abstract class SqliteLikeBaseDriver extends CommonSQLImplement { supportPragmaList = true; @@ -31,6 +31,7 @@ export abstract class SqliteLikeBaseDriver extends CommonSQLImplement { getFlags(): DriverFlags { return { supportBigInt: false, + supportModifyColumn: false, defaultSchema: "main", optionalSchema: true, mismatchDetection: false, diff --git a/src/drivers/sqlite/sqlite-generate-schema.test.ts b/src/drivers/sqlite/sqlite-generate-schema.test.ts new file mode 100644 index 0000000..ca4b1c5 --- /dev/null +++ b/src/drivers/sqlite/sqlite-generate-schema.test.ts @@ -0,0 +1,45 @@ +import { createTableSchemaDraft } from "@/components/lib/sql-generate.schema"; +import { parseCreateTableScript } from "./sql-parse-table"; +import { produce } from "immer"; +import generateSqlSchemaChange from "./sqlite-generate-schema"; + +function c(sql: string) { + return createTableSchemaDraft("main", parseCreateTableScript("main", sql)); +} + +describe("sqlite - generate table schema", () => { + test("rename column name", () => { + let t = c(`create table testing(id integer, qty integer, amount real)`); + t = produce(t, (draft) => { + if (draft.columns[1]?.new) { + draft.columns[1].new.name = "quantity"; + } + }); + + const code = generateSqlSchemaChange(t); + expect(code).toEqual([ + `ALTER TABLE "main"."testing" RENAME COLUMN "qty" TO "quantity"`, + ]); + }); + + test("rename column name and change some data type", () => { + let t = c(`create table testing(id integer, qty integer, amount real)`); + t = produce(t, (draft) => { + if (draft.columns[1]?.new) { + draft.columns[1].new.name = "quantity"; + draft.columns[1].new.type = "REAL"; + } + + if (draft.columns[2]?.new) { + draft.columns[2].new.name = "amt"; + } + }); + + const code = generateSqlSchemaChange(t); + expect(code).toEqual([ + `ALTER TABLE "main"."testing" RENAME COLUMN "qty" TO "quantity"`, + `ALTER TABLE "main"."testing" ALTER COLUMN "quantity" TO "quantity" REAL`, + `ALTER TABLE "main"."testing" RENAME COLUMN "amount" TO "amt"`, + ]); + }); +}); diff --git a/src/drivers/sqlite/sqlite-generate-schema.ts b/src/drivers/sqlite/sqlite-generate-schema.ts new file mode 100644 index 0000000..00ebe14 --- /dev/null +++ b/src/drivers/sqlite/sqlite-generate-schema.ts @@ -0,0 +1,178 @@ +import { escapeIdentity, escapeSqlValue } from "@/drivers/sqlite/sql-helper"; +import { + DatabaseTableColumn, + DatabaseTableColumnConstraint, + DatabaseTableSchemaChange, +} from "@/drivers/base-driver"; +import { omit, isEqual } from "lodash"; + +function wrapParen(str: string) { + if (str.length >= 2 && str.startsWith("(") && str.endsWith(")")) return str; + return "(" + str + ")"; +} + +function generateCreateColumn(col: DatabaseTableColumn): string { + const tokens: string[] = [escapeIdentity(col.name), col.type]; + + if (col.constraint?.primaryKey) { + tokens.push( + [ + "PRIMARY KEY", + col.constraint.primaryKeyOrder, + col.constraint.primaryKeyConflict + ? `ON CONFLICT ${col.constraint.primaryKeyConflict}` + : undefined, + col.constraint.autoIncrement ? "AUTOINCREMENT" : undefined, + ] + .filter(Boolean) + .join(" ") + ); + } + + if (col.constraint?.unique) { + tokens.push( + [ + "UNIQUE", + col.constraint.uniqueConflict + ? `ON CONFLICT ${col.constraint.uniqueConflict}` + : undefined, + ] + .filter(Boolean) + .join(" ") + ); + } + + if (col.constraint?.notNull) { + tokens.push( + [ + "NOT NULL", + col.constraint.notNullConflict + ? `ON CONFLICT ${col.constraint.notNullConflict}` + : undefined, + ] + .filter(Boolean) + .join(" ") + ); + } + + if (col.constraint?.defaultValue) { + tokens.push( + ["DEFAULT", escapeSqlValue(col.constraint.defaultValue)].join(" ") + ); + } + + if (col.constraint?.defaultExpression) { + tokens.push( + ["DEFAULT", wrapParen(col.constraint.defaultExpression)].join(" ") + ); + } + + if (col.constraint?.generatedExpression) { + tokens.push( + [ + "GENERATED ALWAYS AS", + wrapParen(col.constraint.generatedExpression), + col.constraint.generatedType, + ].join(" ") + ); + } + + if (col.constraint?.checkExpression) { + tokens.push("CHECK " + wrapParen(col.constraint.checkExpression)); + } + + const foreignTableName = col.constraint?.foreignKey?.foreignTableName; + const foreignColumnName = (col.constraint?.foreignKey?.foreignColumns ?? [ + undefined, + ])[0]; + + if (foreignTableName && foreignColumnName) { + tokens.push( + [ + "REFERENCES", + escapeIdentity(foreignTableName) + + `(${escapeIdentity(foreignColumnName)})`, + ].join(" ") + ); + } + + return tokens.join(" "); +} + +function generateConstraintScript(con: DatabaseTableColumnConstraint) { + if (con.primaryKey) { + return `PRIMARY KEY (${con.primaryColumns?.map(escapeIdentity).join(", ")})`; + } else if (con.unique) { + return `UNIQUE (${con.uniqueColumns?.map(escapeIdentity).join(", ")})`; + } else if (con.checkExpression !== undefined) { + return `CHECK (${con.checkExpression})`; + } else if (con.foreignKey) { + return ( + `FOREIGN KEY (${con.foreignKey.columns?.map(escapeIdentity).join(", ")}) ` + + `REFERENCES ${escapeIdentity(con.foreignKey.foreignTableName ?? "")} ` + + `(${con.foreignKey.foreignColumns?.map(escapeIdentity).join(", ")})` + ); + } +} + +export default function generateSqlSchemaChange( + change: DatabaseTableSchemaChange +): string[] { + const isCreateScript = !change.name.old; + + const lines = []; + + for (const col of change.columns) { + if (col.new === null) lines.push(`DROP COLUMN ${col.old?.name}`); + else if (col.old === null) { + if (isCreateScript) { + lines.push(generateCreateColumn(col.new)); + } else { + lines.push("ADD " + generateCreateColumn(col.new)); + } + } else { + // check if there is rename + if (col.new.name !== col.old.name) { + lines.push( + `RENAME COLUMN ${escapeIdentity(col.old.name)} TO ${escapeIdentity( + col.new.name + )}` + ); + } + + // check if there is any changed except name + if (!isEqual(omit(col.old, ["name"]), omit(col.new, ["name"]))) { + lines.push( + `ALTER COLUMN ${escapeIdentity(col.new.name)} TO ${generateCreateColumn(col.new)}` + ); + } + } + } + + for (const con of change.constraints) { + if (con.new) { + if (isCreateScript) { + lines.push(generateConstraintScript(con.new)); + } + } + } + + if (!isCreateScript) { + if (change.name.new !== change.name.old) { + lines.push( + `RENAME TO ${escapeIdentity(change.schemaName ?? "main")}.${escapeIdentity(change.name.new ?? "")}` + ); + } + } + + if (isCreateScript) { + return [ + `CREATE TABLE ${escapeIdentity(change.schemaName ?? "main")}.${escapeIdentity( + change.name.new || "no_table_name" + )}(\n${lines.map((line) => " " + line).join(",\n")}\n)`, + ]; + } else { + const alter = `ALTER TABLE ${escapeIdentity(change.schemaName ?? "main")}.${escapeIdentity(change.name.old ?? "")} `; + return lines.map((line) => alter + line); + } +} diff --git a/src/drivers/turso-driver.tsx b/src/drivers/turso-driver.tsx index 0ab9efd..1b546bb 100644 --- a/src/drivers/turso-driver.tsx +++ b/src/drivers/turso-driver.tsx @@ -102,6 +102,7 @@ export default class TursoDriver extends SqliteLikeBaseDriver { return { ...super.getFlags(), supportBigInt: this.bigInt, + supportModifyColumn: true, mismatchDetection: this.bigInt, }; }