@deterministic-code/datasources-mysql 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/MysqlBulkUpdateStatementTextRenderer.d.ts +4 -0
- package/dist/MysqlCreateTableStatementRenderer.d.ts +5 -0
- package/dist/MysqlDriver.d.ts +12 -0
- package/dist/MysqlStatementRenderer.d.ts +7 -0
- package/dist/bind.d.ts +5 -0
- package/dist/columnTypes.d.ts +1 -0
- package/dist/command.d.ts +16 -0
- package/dist/connection-options.d.ts +5 -0
- package/dist/connection.d.ts +19 -0
- package/dist/converters/BinaryConverter.d.ts +5 -0
- package/dist/converters/BoolConverter.d.ts +5 -0
- package/dist/converters/DateTimeConverter.d.ts +5 -0
- package/dist/converters/DecimalConverter.d.ts +5 -0
- package/dist/converters/MysqlRowTypeConverters.d.ts +3 -0
- package/dist/converters/UuidConverter.d.ts +5 -0
- package/dist/i-mysql-engine.d.ts +27 -0
- package/dist/index.d.ts +1 -0
- package/dist/scalar.d.ts +2 -0
- package/dist/transaction.d.ts +9 -0
- package/package.json +10 -6
- package/src/MysqlBulkUpdateStatementTextRenderer.ts +1 -1
- package/src/MysqlCreateTableStatementRenderer.ts +1 -1
- package/src/MysqlDriver.ts +1 -1
- package/src/MysqlStatementRenderer.test.ts +64 -0
- package/src/MysqlStatementRenderer.ts +1 -1
- package/src/bind.ts +1 -1
- package/src/command.ts +1 -1
- package/src/connection.ts +1 -1
- package/src/converters/BinaryConverter.test.ts +12 -0
- package/src/converters/BinaryConverter.ts +1 -1
- package/src/converters/BoolConverter.test.ts +14 -0
- package/src/converters/BoolConverter.ts +1 -1
- package/src/converters/DateTimeConverter.test.ts +14 -0
- package/src/converters/DateTimeConverter.ts +1 -1
- package/src/converters/DecimalConverter.test.ts +11 -0
- package/src/converters/DecimalConverter.ts +1 -1
- package/src/converters/MysqlRowTypeConverters.test.ts +19 -0
- package/src/converters/MysqlRowTypeConverters.ts +1 -1
- package/src/converters/UuidConverter.test.ts +11 -0
- package/src/converters/UuidConverter.ts +1 -1
- package/src/i-mysql-engine.ts +1 -1
- package/src/mysql.test.ts +342 -0
- package/src/transaction.ts +1 -1
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { CreateTableStatementTextRenderer, type Column } from "@datasources/core";
|
|
2
|
+
export declare class MysqlCreateTableStatementRenderer extends CreateTableStatementTextRenderer {
|
|
3
|
+
protected typeName(column: Column): string;
|
|
4
|
+
protected identity(column: Column): string;
|
|
5
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { StatementTextRenderer, type Cell, type DatasourceOptions, type ICommand, type IConnection, type IDriver, type Information, type ITransaction, type IsolationLevel, type TypeConverter } from "@datasources/core";
|
|
2
|
+
import { columnTypes } from "./columnTypes.js";
|
|
3
|
+
export { columnTypes };
|
|
4
|
+
export declare class MysqlDriver implements IDriver {
|
|
5
|
+
readonly statementRenderer: StatementTextRenderer;
|
|
6
|
+
readonly information: Information;
|
|
7
|
+
rowTypeConverters: Record<string, TypeConverter<Cell, Cell>>;
|
|
8
|
+
columnType: (name: string) => string;
|
|
9
|
+
connection: (options?: DatasourceOptions) => IConnection;
|
|
10
|
+
command: (connection: IConnection) => ICommand;
|
|
11
|
+
transaction: (connection: IConnection, isolation?: IsolationLevel) => ITransaction;
|
|
12
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BulkUpdateStatement, CreateTableStatement, SelectInsertedIdStatement, StatementRenderer, type StatementTextRenderer } from "@datasources/core";
|
|
2
|
+
export declare class MysqlStatementRenderer extends StatementRenderer {
|
|
3
|
+
constructor(text: StatementTextRenderer);
|
|
4
|
+
protected visitSelectInsertedId(inserted: SelectInsertedIdStatement): string;
|
|
5
|
+
protected visitCreateTable(create: CreateTableStatement): string;
|
|
6
|
+
protected visitBulkUpdate(update: BulkUpdateStatement): string;
|
|
7
|
+
}
|
package/dist/bind.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type Parameter } from "@datasources/core";
|
|
2
|
+
import type { Scalar } from "./scalar.js";
|
|
3
|
+
export declare const withoutReturning: (sql: string) => string;
|
|
4
|
+
export declare const toMysql: (sql: string) => string;
|
|
5
|
+
export declare const bindParameters: (parameters?: Parameter[] | null) => Record<string, Scalar>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const columnTypes: Record<string, string>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CommandMiddlewareHost, Native, StatementTextRenderer, Response, type Cells, type ICommand, type IReader, type IStatement, type Parameter } from "@datasources/core";
|
|
2
|
+
import type { MysqlConnection } from "./connection.js";
|
|
3
|
+
export declare class MysqlCommand extends CommandMiddlewareHost implements ICommand {
|
|
4
|
+
readonly connection: MysqlConnection;
|
|
5
|
+
private readonly statements;
|
|
6
|
+
constructor(connection: MysqlConnection, statements?: StatementTextRenderer);
|
|
7
|
+
executeReaderNative(text: Native, parameters?: Parameter[]): Promise<IReader<Cells>>;
|
|
8
|
+
executeReaderStatement(statement: IStatement, parameters?: Parameter[]): Promise<IReader<Cells>>;
|
|
9
|
+
executeStatement(statement: IStatement, parameters?: Parameter[]): Promise<Response>;
|
|
10
|
+
private firstCell;
|
|
11
|
+
executeNative(text: Native, parameters?: Parameter[]): Promise<Response>;
|
|
12
|
+
private executeProcedure;
|
|
13
|
+
private runSql;
|
|
14
|
+
private query;
|
|
15
|
+
private callResults;
|
|
16
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type DatasourceOptions, type ICommand, type IConnection, type IsolationLevelName, type ITransaction } from "@datasources/core";
|
|
2
|
+
import type { MysqlConnectionOptions } from "./connection-options.js";
|
|
3
|
+
import { type IMysqlClient } from "./i-mysql-engine.js";
|
|
4
|
+
export declare class MysqlConnection implements IConnection {
|
|
5
|
+
readonly provider = "mysql";
|
|
6
|
+
url: string;
|
|
7
|
+
isOpen: boolean;
|
|
8
|
+
private client?;
|
|
9
|
+
private readonly connect?;
|
|
10
|
+
static from: (options?: DatasourceOptions) => MysqlConnection;
|
|
11
|
+
constructor(options?: MysqlConnectionOptions);
|
|
12
|
+
createCommand: () => ICommand;
|
|
13
|
+
open(): Promise<void>;
|
|
14
|
+
close(): Promise<void>;
|
|
15
|
+
[Symbol.asyncDispose]: () => Promise<void>;
|
|
16
|
+
startTransaction(isolation?: IsolationLevelName): Promise<ITransaction>;
|
|
17
|
+
ensureOpen(): void;
|
|
18
|
+
require(): IMysqlClient;
|
|
19
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Parameter } from "@datasources/core";
|
|
2
|
+
import type { Cells, Scalar } from "./scalar.js";
|
|
3
|
+
export interface MysqlExecuteResult {
|
|
4
|
+
rowsAffected: number;
|
|
5
|
+
result?: Scalar;
|
|
6
|
+
parameters?: Parameter[];
|
|
7
|
+
}
|
|
8
|
+
export interface MysqlQueryHeader {
|
|
9
|
+
affectedRows?: number;
|
|
10
|
+
insertId?: Scalar;
|
|
11
|
+
}
|
|
12
|
+
export interface IMysqlClient {
|
|
13
|
+
query(sql: string, params?: Record<string, Scalar>): Promise<[MysqlQueryHeader | Cells[]]>;
|
|
14
|
+
queryRows(sql: string, params?: Record<string, Scalar>): AsyncIterable<Cells>;
|
|
15
|
+
beginTransaction(): Promise<void>;
|
|
16
|
+
commit(): Promise<void>;
|
|
17
|
+
rollback(): Promise<void>;
|
|
18
|
+
end(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
export type MysqlCreateConnection = (url: string) => Promise<IMysqlClient>;
|
|
21
|
+
export type Mysql2Module = {
|
|
22
|
+
createConnection: (options: {
|
|
23
|
+
uri: string;
|
|
24
|
+
namedPlaceholders: boolean;
|
|
25
|
+
}) => Promise<IMysqlClient>;
|
|
26
|
+
};
|
|
27
|
+
export declare const mysql2Connect: (mysql: Mysql2Module) => MysqlCreateConnection;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MysqlDriver } from "./MysqlDriver.js";
|
package/dist/scalar.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ITransaction, type IsolationLevelName } from "@datasources/core";
|
|
2
|
+
import type { MysqlConnection } from "./connection.js";
|
|
3
|
+
export declare class MysqlTransaction implements ITransaction {
|
|
4
|
+
readonly connection: MysqlConnection;
|
|
5
|
+
readonly isolation: IsolationLevelName;
|
|
6
|
+
constructor(connection: MysqlConnection, isolation?: IsolationLevelName);
|
|
7
|
+
commit(): Promise<void>;
|
|
8
|
+
abort(): Promise<void>;
|
|
9
|
+
}
|
package/package.json
CHANGED
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deterministic-code/datasources-mysql",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
|
-
"types": "./
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
8
|
-
".":
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./src/index.ts",
|
|
11
|
+
"default": "./src/index.ts"
|
|
12
|
+
}
|
|
9
13
|
},
|
|
10
14
|
"files": [
|
|
15
|
+
"dist",
|
|
11
16
|
"src"
|
|
12
17
|
],
|
|
13
18
|
"publishConfig": {
|
|
14
19
|
"access": "public"
|
|
15
20
|
},
|
|
16
|
-
"license": "MIT",
|
|
17
21
|
"dependencies": {
|
|
18
|
-
"
|
|
19
|
-
"
|
|
22
|
+
"mysql2": "^3.14.0",
|
|
23
|
+
"@deterministic-code/datasources": "^0.1.1"
|
|
20
24
|
}
|
|
21
25
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BulkUpdateStatementTextRenderer, type BulkUpdateStatement } from "@
|
|
1
|
+
import { BulkUpdateStatementTextRenderer, type BulkUpdateStatement } from "@datasources/core";
|
|
2
2
|
|
|
3
3
|
export class MysqlBulkUpdateStatementTextRenderer extends BulkUpdateStatementTextRenderer {
|
|
4
4
|
override render = (statement: BulkUpdateStatement): string => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CreateTableStatementTextRenderer, type Column } from "@
|
|
1
|
+
import { CreateTableStatementTextRenderer, type Column } from "@datasources/core";
|
|
2
2
|
import { columnTypes } from "./columnTypes.ts";
|
|
3
3
|
|
|
4
4
|
export class MysqlCreateTableStatementRenderer extends CreateTableStatementTextRenderer {
|
package/src/MysqlDriver.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
type ITransaction,
|
|
11
11
|
type IsolationLevel,
|
|
12
12
|
type TypeConverter,
|
|
13
|
-
} from "@
|
|
13
|
+
} from "@datasources/core";
|
|
14
14
|
import { MysqlConnection } from "./connection.ts";
|
|
15
15
|
import { MysqlCommand } from "./command.ts";
|
|
16
16
|
import { MysqlTransaction } from "./transaction.ts";
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BulkUpdateStatement,
|
|
3
|
+
ColumnDefault,
|
|
4
|
+
CreateTableStatement,
|
|
5
|
+
DefaultStatementTextRenderer,
|
|
6
|
+
ForeignKey,
|
|
7
|
+
SelectInsertedIdStatement,
|
|
8
|
+
NotNull,
|
|
9
|
+
PrimaryKey,
|
|
10
|
+
ReferentialAction,
|
|
11
|
+
SelectStatement,
|
|
12
|
+
StatementTextRenderer,
|
|
13
|
+
} from "@datasources/core";
|
|
14
|
+
import { describe, expect, it } from "vitest";
|
|
15
|
+
import { MysqlCreateTableStatementRenderer } from "./MysqlCreateTableStatementRenderer.ts";
|
|
16
|
+
import { MysqlStatementRenderer } from "./MysqlStatementRenderer.ts";
|
|
17
|
+
|
|
18
|
+
describe("MysqlStatementRenderer", () => {
|
|
19
|
+
const renderer = new StatementTextRenderer(t => new MysqlStatementRenderer(t));
|
|
20
|
+
|
|
21
|
+
it("renders inserted id and create table", () => {
|
|
22
|
+
expect(renderer.render(new SelectInsertedIdStatement("users", "id"))).toBe(
|
|
23
|
+
"SELECT LAST_INSERT_ID() AS id",
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const create = new CreateTableStatement(
|
|
27
|
+
"orders",
|
|
28
|
+
[
|
|
29
|
+
{ name: "id", type: "INTEGER", default: new ColumnDefault("0", "df_id"), notNull: new NotNull("nn_id") },
|
|
30
|
+
{ name: "user_id", type: "INTEGER" },
|
|
31
|
+
],
|
|
32
|
+
[
|
|
33
|
+
new PrimaryKey("pk_orders", ["id"]),
|
|
34
|
+
new ForeignKey("fk_orders_user", ["user_id"], "users", ["id"], ReferentialAction.Cascade),
|
|
35
|
+
],
|
|
36
|
+
);
|
|
37
|
+
expect(renderer.render(create)).toBe(new MysqlCreateTableStatementRenderer().render(create));
|
|
38
|
+
expect(renderer.render(new SelectStatement({ table: "users", fields: ["id"] }))).toBe(
|
|
39
|
+
"SELECT id FROM users",
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("renders type default and identity", () => {
|
|
44
|
+
const create = new CreateTableStatement("users", [
|
|
45
|
+
{ name: "id", type: "int", default: new ColumnDefault("0"), notNull: new NotNull(), identity: true },
|
|
46
|
+
]);
|
|
47
|
+
expect(renderer.render(create)).toBe(
|
|
48
|
+
"CREATE TABLE users (id INT AUTO_INCREMENT DEFAULT 0 NOT NULL)",
|
|
49
|
+
);
|
|
50
|
+
expect(DefaultStatementTextRenderer.instance.render(create)).toBe(
|
|
51
|
+
"CREATE TABLE users (id int DEFAULT 0 NOT NULL)",
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("renders bulk update as join", () => {
|
|
56
|
+
const update = new BulkUpdateStatement("parents", ["label"], ["id"], [
|
|
57
|
+
{ fields: [{ name: "label", value: "a" }], ids: [{ name: "id", value: 1 }] },
|
|
58
|
+
{ fields: [{ name: "label", value: "b" }], ids: [{ name: "id", value: 2 }] },
|
|
59
|
+
]);
|
|
60
|
+
expect(renderer.render(update)).toBe(
|
|
61
|
+
"UPDATE parents JOIN (SELECT $label__0 AS label, $id__0 AS id UNION ALL SELECT $label__1 AS label, $id__1 AS id) AS data ON parents.id = data.id SET parents.label = data.label",
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
SelectInsertedIdStatementTextRenderer,
|
|
6
6
|
StatementRenderer,
|
|
7
7
|
type StatementTextRenderer,
|
|
8
|
-
} from "@
|
|
8
|
+
} from "@datasources/core";
|
|
9
9
|
import { MysqlBulkUpdateStatementTextRenderer } from "./MysqlBulkUpdateStatementTextRenderer.ts";
|
|
10
10
|
import { MysqlCreateTableStatementRenderer } from "./MysqlCreateTableStatementRenderer.ts";
|
|
11
11
|
|
package/src/bind.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parametersToRecord, type Cell, type Parameter } from "@
|
|
1
|
+
import { parametersToRecord, type Cell, type Parameter } from "@datasources/core";
|
|
2
2
|
import { reverse } from "./converters/MysqlRowTypeConverters.ts";
|
|
3
3
|
import type { Scalar } from "./scalar.ts";
|
|
4
4
|
|
package/src/command.ts
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
type IReader,
|
|
20
20
|
type IStatement,
|
|
21
21
|
type Parameter,
|
|
22
|
-
} from "@
|
|
22
|
+
} from "@datasources/core";
|
|
23
23
|
import { bindParameters, toMysql } from "./bind.ts";
|
|
24
24
|
import type { MysqlConnection } from "./connection.ts";
|
|
25
25
|
import type { MysqlExecuteResult } from "./i-mysql-engine.ts";
|
package/src/connection.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type IConnection,
|
|
8
8
|
type IsolationLevelName,
|
|
9
9
|
type ITransaction,
|
|
10
|
-
} from "@
|
|
10
|
+
} from "@datasources/core";
|
|
11
11
|
import { createRequire } from "node:module";
|
|
12
12
|
import { MysqlCommand } from "./command.ts";
|
|
13
13
|
import type { MysqlConnectionOptions } from "./connection-options.ts";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { BinaryConverter } from "./BinaryConverter.ts";
|
|
3
|
+
|
|
4
|
+
describe("BinaryConverter", () => {
|
|
5
|
+
it("maps binary", () => {
|
|
6
|
+
const converter = new BinaryConverter();
|
|
7
|
+
const raw = new Uint8Array([1, 2, 3, 4]);
|
|
8
|
+
expect(converter.convert(raw)).toBe(raw);
|
|
9
|
+
expect(converter.reverseConvert(raw)).toBe(raw);
|
|
10
|
+
expect(converter.reverseConvert("AQIDBA==")).toEqual(raw);
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { BoolConverter } from "./BoolConverter.ts";
|
|
3
|
+
|
|
4
|
+
describe("BoolConverter", () => {
|
|
5
|
+
it("maps bool", () => {
|
|
6
|
+
const converter = new BoolConverter();
|
|
7
|
+
expect(converter.convert(1)).toBe(true);
|
|
8
|
+
expect(converter.convert(0)).toBe(false);
|
|
9
|
+
expect(converter.convert("x")).toBe("x");
|
|
10
|
+
expect(converter.reverseConvert(true)).toBe(1);
|
|
11
|
+
expect(converter.reverseConvert(false)).toBe(0);
|
|
12
|
+
expect(converter.reverseConvert("x")).toBe("x");
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TypeConverter, type Cell } from "@
|
|
1
|
+
import { TypeConverter, type Cell } from "@datasources/core";
|
|
2
2
|
|
|
3
3
|
export class BoolConverter extends TypeConverter<Cell, Cell> {
|
|
4
4
|
convert = (from: Cell): Cell => (typeof from === "number" ? from !== 0 : from);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { DateTimeConverter } from "./DateTimeConverter.ts";
|
|
3
|
+
|
|
4
|
+
describe("DateTimeConverter", () => {
|
|
5
|
+
it("maps datetime", () => {
|
|
6
|
+
const converter = new DateTimeConverter();
|
|
7
|
+
const date = new Date("2020-01-02T03:04:05.000Z");
|
|
8
|
+
expect(converter.convert("2020-01-02T03:04:05.000Z")).toEqual(date);
|
|
9
|
+
expect(converter.convert(date)).toBe(date);
|
|
10
|
+
expect(converter.reverseConvert(date)).toBe("2020-01-02 03:04:05.000");
|
|
11
|
+
expect(converter.reverseConvert("2020-01-02T03:04:05.000Z")).toBe("2020-01-02 03:04:05.000");
|
|
12
|
+
expect(converter.reverseConvert("x")).toBe("x");
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { DecimalConverter } from "./DecimalConverter.ts";
|
|
3
|
+
|
|
4
|
+
describe("DecimalConverter", () => {
|
|
5
|
+
it("maps decimal", () => {
|
|
6
|
+
const converter = new DecimalConverter();
|
|
7
|
+
expect(converter.convert("10.5")).toBe("10.5");
|
|
8
|
+
expect(converter.reverseConvert(10.5)).toBe("10.5");
|
|
9
|
+
expect(converter.reverseConvert("10.5")).toBe("10.5");
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { MysqlDriver } from "../MysqlDriver.ts";
|
|
3
|
+
import { reverse, MysqlRowTypeConverters } from "./MysqlRowTypeConverters.ts";
|
|
4
|
+
|
|
5
|
+
describe("MysqlRowTypeConverters", () => {
|
|
6
|
+
it("registers default types", () => {
|
|
7
|
+
expect(Object.keys(new MysqlDriver().rowTypeConverters)).toEqual(Object.keys(MysqlRowTypeConverters));
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("reverses booleans, dates, and bytes", () => {
|
|
11
|
+
const when = new Date("2024-01-02T00:00:00.000Z");
|
|
12
|
+
expect(reverse(true)).toBe(1);
|
|
13
|
+
expect(reverse(false)).toBe(0);
|
|
14
|
+
expect(reverse(when)).toBe(when.toISOString().replace("T", " ").replace("Z", ""));
|
|
15
|
+
expect(reverse(new Uint8Array([1, 2]))).toEqual(new Uint8Array([1, 2]));
|
|
16
|
+
expect(reverse("a")).toBe("a");
|
|
17
|
+
expect(reverse("2024-01-02T00:00:00Z")).toBe("2024-01-02 00:00:00.000");
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Cell, type TypeConverter } from "@
|
|
1
|
+
import { type Cell, type TypeConverter } from "@datasources/core";
|
|
2
2
|
import { BinaryConverter } from "./BinaryConverter.ts";
|
|
3
3
|
import { BoolConverter } from "./BoolConverter.ts";
|
|
4
4
|
import { DateTimeConverter } from "./DateTimeConverter.ts";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { UuidConverter } from "./UuidConverter.ts";
|
|
3
|
+
|
|
4
|
+
describe("UuidConverter", () => {
|
|
5
|
+
it("maps uuid", () => {
|
|
6
|
+
const converter = new UuidConverter();
|
|
7
|
+
const id = "550e8400-e29b-41d4-a716-446655440000";
|
|
8
|
+
expect(converter.convert(id)).toBe(id);
|
|
9
|
+
expect(converter.reverseConvert(id)).toBe(id);
|
|
10
|
+
});
|
|
11
|
+
});
|
package/src/i-mysql-engine.ts
CHANGED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
vi.mock("mysql2/promise", () => ({
|
|
4
|
+
createConnection: async () => ({
|
|
5
|
+
query: async () => [{}],
|
|
6
|
+
beginTransaction: async () => {},
|
|
7
|
+
commit: async () => {},
|
|
8
|
+
rollback: async () => {},
|
|
9
|
+
end: async () => {},
|
|
10
|
+
}),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
import { Datasource, DatasourceFactory, DatasourceError, IsolationLevel, CRUDRepository, ReadonlyRepository, untyped, Native, ParameterDirection, columnType, RowTypeConverter, CreateTableStatement, ExecuteStoredProcedureStatement, ReturnStatement, SelectStatement, type IDriver } from "@datasources/core";
|
|
14
|
+
import { bindParameters, toMysql } from "./bind.ts";
|
|
15
|
+
import { MysqlCommand } from "./command.ts";
|
|
16
|
+
import { MysqlConnection } from "./connection.ts";
|
|
17
|
+
import { MysqlRowTypeConverters } from "./converters/MysqlRowTypeConverters.ts";
|
|
18
|
+
import { mysql2Connect, type IMysqlClient, type MysqlQueryHeader } from "./i-mysql-engine.ts";
|
|
19
|
+
import { MysqlDriver } from "./MysqlDriver.ts";
|
|
20
|
+
import { MysqlTransaction } from "./transaction.ts";
|
|
21
|
+
import type { Cells, Scalar } from "./scalar.ts";
|
|
22
|
+
|
|
23
|
+
class FakeClient implements IMysqlClient {
|
|
24
|
+
queries: Array<{ sql: string; params?: Record<string, Scalar> }> = [];
|
|
25
|
+
begins = 0;
|
|
26
|
+
commits = 0;
|
|
27
|
+
rollbacks = 0;
|
|
28
|
+
ended = false;
|
|
29
|
+
header: MysqlQueryHeader = { affectedRows: 2, insertId: 5 };
|
|
30
|
+
rows: Cells[] = [{ id: 1 }];
|
|
31
|
+
arrayResult = false;
|
|
32
|
+
/** Multi-result-set payload for CALL (array of row arrays). */
|
|
33
|
+
callSets?: Cells[][];
|
|
34
|
+
|
|
35
|
+
async query(sql: string, params?: Record<string, Scalar>) {
|
|
36
|
+
this.queries.push({ sql, params });
|
|
37
|
+
if (this.callSets !== undefined)
|
|
38
|
+
return [this.callSets] as [MysqlQueryHeader | Cells[]];
|
|
39
|
+
return [this.arrayResult ? this.rows : this.header] as [
|
|
40
|
+
MysqlQueryHeader | Cells[],
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
async *queryRows(sql: string, params?: Record<string, Scalar>): AsyncGenerator<Cells> {
|
|
44
|
+
this.queries.push({ sql, params });
|
|
45
|
+
if (!this.arrayResult) return;
|
|
46
|
+
for (const row of this.rows) yield row;
|
|
47
|
+
}
|
|
48
|
+
async beginTransaction(): Promise<void> {
|
|
49
|
+
this.begins += 1;
|
|
50
|
+
}
|
|
51
|
+
async commit(): Promise<void> {
|
|
52
|
+
this.commits += 1;
|
|
53
|
+
}
|
|
54
|
+
async rollback(): Promise<void> {
|
|
55
|
+
this.rollbacks += 1;
|
|
56
|
+
}
|
|
57
|
+
async end(): Promise<void> {
|
|
58
|
+
this.ended = true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const opened = async (url = "mysql://localhost/app", client = new FakeClient()) => {
|
|
63
|
+
const connection = new MysqlConnection({ url, connect: async () => client });
|
|
64
|
+
await connection.open();
|
|
65
|
+
return { connection, client, command: new MysqlCommand(connection) };
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
afterEach(() => {
|
|
69
|
+
DatasourceFactory.reset();
|
|
70
|
+
DatasourceFactory.register("mysql", new MysqlDriver());
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("bindParameters", () => {
|
|
74
|
+
it("binds and rewrites parameters", () => {
|
|
75
|
+
expect(bindParameters()).toEqual({});
|
|
76
|
+
expect(bindParameters(null)).toEqual({});
|
|
77
|
+
expect(bindParameters([{ name: "id", value: 1 }])).toEqual({ id: 1 });
|
|
78
|
+
expect(toMysql("SELECT * FROM t WHERE id = $id")).toBe(
|
|
79
|
+
"SELECT * FROM t WHERE id = :id",
|
|
80
|
+
);
|
|
81
|
+
expect(toMysql("INSERT INTO t (id) VALUES ($id) RETURNING id")).toBe(
|
|
82
|
+
"INSERT INTO t (id) VALUES (:id)",
|
|
83
|
+
);
|
|
84
|
+
expect(toMysql("INSERT INTO contact_settings (contact_id, key, value) VALUES ($contact_id, $key, $value)"))
|
|
85
|
+
.toBe("INSERT INTO contact_settings (contact_id, `key`, value) VALUES (:contact_id, :key, :value)");
|
|
86
|
+
expect(toMysql("CREATE TABLE t (id INT PRIMARY KEY)")).toBe("CREATE TABLE t (id INT PRIMARY KEY)");
|
|
87
|
+
expect(toMysql("CREATE TABLE t (id INT AUTO_INCREMENT, CONSTRAINT `pk` PRIMARY KEY (`id`))"))
|
|
88
|
+
.toBe("CREATE TABLE t (id INT AUTO_INCREMENT, CONSTRAINT `pk` PRIMARY KEY (`id`))");
|
|
89
|
+
expect(toMysql("INSERT INTO OldContactsTbl (CntID) VALUES ($CntID)"))
|
|
90
|
+
.toBe("INSERT INTO `OldContactsTbl` (`CntID`) VALUES (:CntID)");
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("MysqlConnection", () => {
|
|
95
|
+
it("opens, runs SQL, transacts, and closes", async () => {
|
|
96
|
+
const { connection, client, command } = await opened("mysql://db/app");
|
|
97
|
+
expect(await command.execute("INSERT INTO items (id) VALUES ($id)", [{ name: "id", value: 1 }]))
|
|
98
|
+
.toMatchObject({ rowsAffected: 2, result: null });
|
|
99
|
+
expect(await command.executeStatement(new CreateTableStatement("users", [{ name: "id", type: "INTEGER" }])))
|
|
100
|
+
.toMatchObject({ rowsAffected: 2 });
|
|
101
|
+
expect(client.queries.at(-1)?.sql).toBe("CREATE TABLE users (id INTEGER)");
|
|
102
|
+
client.arrayResult = true;
|
|
103
|
+
expect(await (await command.executeReaderNative(new Native("SELECT 1"))).next()).toBe(true);
|
|
104
|
+
expect(await command.executeNative(new Native(" "))).toEqual({ result: undefined, rowsAffected: 0, parameters: undefined });
|
|
105
|
+
client.arrayResult = true;
|
|
106
|
+
const reader = await command.executeReader("SELECT * FROM items");
|
|
107
|
+
expect(await reader.next()).toBe(true);
|
|
108
|
+
expect(reader.current).toEqual({ id: 1 });
|
|
109
|
+
expect(await (await command.executeReaderNative(new Native(""))).next()).toBe(false);
|
|
110
|
+
client.arrayResult = false;
|
|
111
|
+
await connection.startTransaction();
|
|
112
|
+
await connection.startTransaction(IsolationLevel.ReadUncommitted);
|
|
113
|
+
await connection.startTransaction(IsolationLevel.ReadCommitted);
|
|
114
|
+
await connection.startTransaction(IsolationLevel.RepeatableRead);
|
|
115
|
+
await new MysqlTransaction(connection).commit();
|
|
116
|
+
await new MysqlTransaction(connection).abort();
|
|
117
|
+
expect(client.begins).toBe(4);
|
|
118
|
+
expect(client.commits).toBe(1);
|
|
119
|
+
expect(client.rollbacks).toBe(1);
|
|
120
|
+
expect(client.queries.map((q) => q.sql)).toEqual([
|
|
121
|
+
"SET SESSION sql_mode = CONCAT(@@sql_mode, ',ANSI_QUOTES')",
|
|
122
|
+
"INSERT INTO items (id) VALUES (:id)",
|
|
123
|
+
"CREATE TABLE users (id INTEGER)",
|
|
124
|
+
"SELECT 1",
|
|
125
|
+
"SELECT * FROM items",
|
|
126
|
+
"SET TRANSACTION ISOLATION LEVEL READ COMMITTED",
|
|
127
|
+
"SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED",
|
|
128
|
+
"SET TRANSACTION ISOLATION LEVEL READ COMMITTED",
|
|
129
|
+
"SET TRANSACTION ISOLATION LEVEL REPEATABLE READ",
|
|
130
|
+
]);
|
|
131
|
+
await connection.close();
|
|
132
|
+
expect(client.ended).toBe(true);
|
|
133
|
+
expect(() => connection.require()).toThrow("not open");
|
|
134
|
+
await expect(command.executeNative(new Native("x"))).rejects.toThrow("not open");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("connects through mysql2 and treats array results as row counts", async () => {
|
|
138
|
+
const connect = mysql2Connect({
|
|
139
|
+
createConnection: async () => new FakeClient(),
|
|
140
|
+
});
|
|
141
|
+
const named = new MysqlConnection({ url: "mysql://db/app", connect });
|
|
142
|
+
await named.open();
|
|
143
|
+
await named.close();
|
|
144
|
+
|
|
145
|
+
const { command, client, connection } = await opened("x");
|
|
146
|
+
client.arrayResult = true;
|
|
147
|
+
expect(await command.executeNative(new Native("SELECT 1"))).toMatchObject({ rowsAffected: 1, result: undefined });
|
|
148
|
+
client.arrayResult = false;
|
|
149
|
+
client.header = {};
|
|
150
|
+
expect(await command.executeNative(new Native("UPDATE items"))).toMatchObject({ rowsAffected: 0 });
|
|
151
|
+
await connection.close();
|
|
152
|
+
await connection.close();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("opens, reopens, transacts, and closes", async () => {
|
|
156
|
+
const { connection, client } = await opened("mysql://db/app");
|
|
157
|
+
await expect(connection.open()).rejects.toThrow("Connection is already open");
|
|
158
|
+
expect(connection.createCommand()).toBeInstanceOf(MysqlCommand);
|
|
159
|
+
expect(connection.isOpen).toBe(true);
|
|
160
|
+
const txn = await connection.startTransaction();
|
|
161
|
+
await txn.commit();
|
|
162
|
+
const txn2 = await connection.startTransaction(IsolationLevel.ReadUncommitted);
|
|
163
|
+
expect(txn2.isolation).toBe(IsolationLevel.ReadUncommitted);
|
|
164
|
+
await txn2.abort();
|
|
165
|
+
expect(txn.isolation).toBe("readCommitted");
|
|
166
|
+
await connection.close();
|
|
167
|
+
expect(client.ended).toBe(true);
|
|
168
|
+
expect(() => connection.ensureOpen()).toThrow(DatasourceError);
|
|
169
|
+
await expect(connection.startTransaction()).rejects.toThrow(DatasourceError);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("defaults url and requires a client to open", async () => {
|
|
173
|
+
const connection = new MysqlConnection();
|
|
174
|
+
expect(connection.url).toBe("");
|
|
175
|
+
await expect(connection.open()).rejects.toThrow(DatasourceError);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe("MysqlCommand", () => {
|
|
180
|
+
it("executes SQL through the client", async () => {
|
|
181
|
+
const { command, client } = await opened();
|
|
182
|
+
client.arrayResult = true;
|
|
183
|
+
expect(await command.execute("DELETE FROM items", [{ name: "id", value: 1 }]))
|
|
184
|
+
.toEqual({ result: undefined, rowsAffected: 1, parameters: [{ name: "id", value: 1 }] });
|
|
185
|
+
expect(await command.executeNative(new Native(""))).toEqual({ result: undefined, rowsAffected: 0, parameters: undefined });
|
|
186
|
+
const reader = await command.executeReader("SELECT * FROM items");
|
|
187
|
+
expect(await reader.next()).toBe(true);
|
|
188
|
+
expect(reader.current).toEqual({ id: 1 });
|
|
189
|
+
const empty = await command.executeReaderNative(new Native(""));
|
|
190
|
+
expect(await empty.next()).toBe(false);
|
|
191
|
+
expect(await (await command.executeReaderNative(new Native(";"))).next()).toBe(false);
|
|
192
|
+
client.rows = [{ add_one: 5 }];
|
|
193
|
+
const n = { name: "n", value: 4 };
|
|
194
|
+
expect(await command.executeStatement(new ReturnStatement("add_one", [n]), [n]))
|
|
195
|
+
.toEqual({ result: 5, rowsAffected: 1, parameters: [n] });
|
|
196
|
+
client.rows = [];
|
|
197
|
+
expect(await command.executeNative(new Native("SELECT 1"))).toEqual({
|
|
198
|
+
result: undefined,
|
|
199
|
+
rowsAffected: 0,
|
|
200
|
+
parameters: undefined,
|
|
201
|
+
});
|
|
202
|
+
client.rows = [{ result: 5 }];
|
|
203
|
+
const start = { name: "start_n", value: 4 };
|
|
204
|
+
const out = { name: "result", direction: ParameterDirection.Output };
|
|
205
|
+
expect(
|
|
206
|
+
await command.executeStatement(new ExecuteStoredProcedureStatement("bump", [start, out]), [start, out]),
|
|
207
|
+
).toMatchObject({
|
|
208
|
+
parameters: [start, { name: "result", direction: ParameterDirection.Output, value: 5 }],
|
|
209
|
+
});
|
|
210
|
+
const inout = { name: "n", value: 4, direction: ParameterDirection.InputOutput };
|
|
211
|
+
client.rows = [{ n: 5 }];
|
|
212
|
+
expect(await command.executeStatement(new ExecuteStoredProcedureStatement("bump", [inout]), [inout])).toMatchObject({
|
|
213
|
+
parameters: [{ name: "n", value: 5, direction: ParameterDirection.InputOutput }],
|
|
214
|
+
});
|
|
215
|
+
expect(
|
|
216
|
+
await command.executeStatement(new ExecuteStoredProcedureStatement("bump", [
|
|
217
|
+
{ name: "start_n" },
|
|
218
|
+
{ name: "result", direction: ParameterDirection.Output },
|
|
219
|
+
]), [
|
|
220
|
+
{ name: "start_n" },
|
|
221
|
+
{ name: "result", direction: ParameterDirection.Output },
|
|
222
|
+
]),
|
|
223
|
+
).toMatchObject({ rowsAffected: 0 });
|
|
224
|
+
client.arrayResult = true;
|
|
225
|
+
client.rows = [];
|
|
226
|
+
expect(await command.executeStatement(new SelectStatement({ table: "dual", fields: ["1"] }))).toMatchObject({
|
|
227
|
+
result: null,
|
|
228
|
+
rowsAffected: 0,
|
|
229
|
+
});
|
|
230
|
+
client.rows = [{}];
|
|
231
|
+
expect(await command.executeStatement(new SelectStatement({ table: "dual", fields: ["1"] }))).toMatchObject({ result: null });
|
|
232
|
+
expect(await command.executeStatement(new ExecuteStoredProcedureStatement("bump", []))).toMatchObject({
|
|
233
|
+
result: undefined,
|
|
234
|
+
});
|
|
235
|
+
client.arrayResult = false;
|
|
236
|
+
expect(await (await command.executeReaderNative(new Native("SELECT 1"))).next()).toBe(false);
|
|
237
|
+
expect(
|
|
238
|
+
await command.executeStatement(new ExecuteStoredProcedureStatement("bump", [
|
|
239
|
+
{ name: "result", direction: ParameterDirection.Output },
|
|
240
|
+
]), [
|
|
241
|
+
{ name: "result", direction: ParameterDirection.Output },
|
|
242
|
+
]),
|
|
243
|
+
).toMatchObject({ rowsAffected: 0 });
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("reads CALL result sets through the reader", async () => {
|
|
247
|
+
const { command, client } = await opened();
|
|
248
|
+
client.callSets = [[{ id: 1 }, { id: 2 }], [{ id: 3 }]];
|
|
249
|
+
const reader = await command.executeReaderNative(new Native("CALL two_ids()"));
|
|
250
|
+
expect(await reader.next()).toBe(true);
|
|
251
|
+
expect(reader.current).toEqual({ id: 1 });
|
|
252
|
+
expect(await reader.next()).toBe(true);
|
|
253
|
+
expect(reader.current).toEqual({ id: 2 });
|
|
254
|
+
expect(await reader.next()).toBe(false);
|
|
255
|
+
expect(await reader.nextResult()).toBe(true);
|
|
256
|
+
expect(await reader.next()).toBe(true);
|
|
257
|
+
expect(reader.current).toEqual({ id: 3 });
|
|
258
|
+
expect(await reader.next()).toBe(false);
|
|
259
|
+
expect(await reader.nextResult()).toBe(false);
|
|
260
|
+
|
|
261
|
+
client.callSets = undefined;
|
|
262
|
+
client.arrayResult = false;
|
|
263
|
+
const empty = await command.executeReaderNative(new Native("CALL noop()"));
|
|
264
|
+
expect(await empty.next()).toBe(false);
|
|
265
|
+
expect(await empty.nextResult()).toBe(false);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
describe("Mysql datasource", () => {
|
|
270
|
+
it("registers and constructs children", async () => {
|
|
271
|
+
expect(
|
|
272
|
+
await new Datasource("mysql", new MysqlDriver()).createConnection(),
|
|
273
|
+
).toBeInstanceOf(MysqlConnection);
|
|
274
|
+
const of = (): IDriver => ({
|
|
275
|
+
connection: () => new MysqlConnection({ connect: async () => new FakeClient() }),
|
|
276
|
+
command: (c) => c.createCommand(),
|
|
277
|
+
transaction: (c, isolation = "readCommitted") =>
|
|
278
|
+
new MysqlTransaction(c as MysqlConnection, isolation),
|
|
279
|
+
rowTypeConverters: MysqlRowTypeConverters,
|
|
280
|
+
columnType,
|
|
281
|
+
});
|
|
282
|
+
const ds = new Datasource("mysql", of());
|
|
283
|
+
expect(ds.provider).toBe("mysql");
|
|
284
|
+
const connection = await ds.createConnection();
|
|
285
|
+
await (connection as MysqlConnection).open();
|
|
286
|
+
const command = await ds.createCommand(connection);
|
|
287
|
+
expect(command).toBeInstanceOf(MysqlCommand);
|
|
288
|
+
expect(command.connection).toBe(connection);
|
|
289
|
+
await command.execute("DELETE FROM items");
|
|
290
|
+
await command.executeReaderNative(new Native("SELECT 1"));
|
|
291
|
+
await expect(ds.createCommand()).rejects.toThrow("Connection is already open");
|
|
292
|
+
const repo = ds.createRepository(untyped(), connection);
|
|
293
|
+
expect(ds.createReadonlyRepository(untyped(), connection).constructor).toBe(ReadonlyRepository);
|
|
294
|
+
expect(repo).toBeInstanceOf(CRUDRepository);
|
|
295
|
+
expect(new Datasource("mysql", of()).createRepository(untyped())).toBeInstanceOf(CRUDRepository);
|
|
296
|
+
expect(Datasource.create({ PROVIDER: "mysql" }).provider).toBe("mysql");
|
|
297
|
+
expect(MysqlConnection.from({ DATASOURCE_URL: "mysql://db/app" }).url).toBe("mysql://db/app");
|
|
298
|
+
expect(MysqlConnection.from({ DATASOURCE_URL: "mysql://db/app" }).url).toBe("mysql://db/app");
|
|
299
|
+
expect(MysqlConnection.from().url).toBe("");
|
|
300
|
+
const provider = new MysqlDriver();
|
|
301
|
+
expect(provider.columnType("int")).toBe("INT");
|
|
302
|
+
expect(provider.columnType("uuid")).toBe("CHAR(36)");
|
|
303
|
+
expect(provider.columnType("string")).toBe("TEXT");
|
|
304
|
+
expect(provider.columnType("datetime")).toBe("TEXT");
|
|
305
|
+
expect(provider.columnType("bool")).toBe("TINYINT");
|
|
306
|
+
expect(provider.columnType("decimal")).toBe("DECIMAL(18,4)");
|
|
307
|
+
expect(provider.columnType("float")).toBe("DOUBLE");
|
|
308
|
+
expect(provider.columnType("binary")).toBe("BLOB");
|
|
309
|
+
expect(() => provider.columnType("xml")).toThrow('Unknown mysql type "xml"');
|
|
310
|
+
expect(provider.command(connection)).toBeInstanceOf(MysqlCommand);
|
|
311
|
+
expect(provider.transaction(connection as MysqlConnection).isolation).toBe("readCommitted");
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("maps property targets", () => {
|
|
315
|
+
const provider = new MysqlDriver();
|
|
316
|
+
const created = "2020-01-02T03:04:05.000Z";
|
|
317
|
+
const converter = RowTypeConverter.from(provider.rowTypeConverters, {
|
|
318
|
+
Key: "uuid",
|
|
319
|
+
Active: "bool",
|
|
320
|
+
Login: "string",
|
|
321
|
+
Created: "datetime",
|
|
322
|
+
});
|
|
323
|
+
const row = converter.convert({
|
|
324
|
+
Key: "11111111-1111-1111-1111-111111111111",
|
|
325
|
+
Active: 1,
|
|
326
|
+
Login: "ada",
|
|
327
|
+
Created: created,
|
|
328
|
+
});
|
|
329
|
+
expect(row).toEqual({
|
|
330
|
+
Key: "11111111-1111-1111-1111-111111111111",
|
|
331
|
+
Active: true,
|
|
332
|
+
Login: "ada",
|
|
333
|
+
Created: new Date(created),
|
|
334
|
+
});
|
|
335
|
+
expect(converter.reverseConvert(row)).toEqual({
|
|
336
|
+
Key: "11111111-1111-1111-1111-111111111111",
|
|
337
|
+
Active: 1,
|
|
338
|
+
Login: "ada",
|
|
339
|
+
Created: "2020-01-02 03:04:05.000",
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
});
|
package/src/transaction.ts
CHANGED