From c7b25252e0638ce21c20694c85f65e7f0047a9b0 Mon Sep 17 00:00:00 2001 From: glasstiger Date: Sat, 26 Jul 2025 01:38:37 +0100 Subject: [PATCH 1/5] feat(nodejs): array support --- src/buffer/base.ts | 76 ++++++++++++++++++------ src/buffer/bufferv1.ts | 4 ++ src/buffer/bufferv2.ts | 24 ++++++++ src/buffer/index.ts | 2 + src/sender.ts | 5 ++ src/utils.ts | 13 +++++ test/sender.buffer.test.ts | 117 +++++++++++++++++++++++++++++++++++++ 7 files changed, 224 insertions(+), 17 deletions(-) diff --git a/src/buffer/base.ts b/src/buffer/base.ts index 442d7c5..e8c0de3 100644 --- a/src/buffer/base.ts +++ b/src/buffer/base.ts @@ -11,6 +11,7 @@ import { } from "./index"; import { isInteger, + getDimensions, timestampToMicros, timestampToNanos, TimestampUnit, @@ -235,6 +236,8 @@ abstract class SenderBufferBase implements SenderBuffer { */ abstract floatColumn(name: string, value: number): SenderBuffer; + abstract arrayColumn(name: string, value: unknown[]): SenderBuffer; + /** * Write an integer column with its value into the buffer of the sender. * @@ -367,36 +370,66 @@ abstract class SenderBufferBase implements SenderBuffer { this.writeEscaped(name); this.write("="); writeValue(); + this.assertBufferOverflow(); this.hasColumns = true; } protected write(data: string) { this.position += this.buffer.write(data, this.position); - if (this.position > this.bufferSize) { - // should never happen, if checkCapacity() is correctly used - throw new Error( - `Buffer overflow [position=${this.position}, bufferSize=${this.bufferSize}]`, - ); - } } protected writeByte(data: number) { this.position = this.buffer.writeInt8(data, this.position); - if (this.position > this.bufferSize) { - // should never happen, if checkCapacity() is correctly used - throw new Error( - `Buffer overflow [position=${this.position}, bufferSize=${this.bufferSize}]`, - ); - } + } + + protected writeInt(data: number) { + this.position = this.buffer.writeInt32LE(data, this.position); } protected writeDouble(data: number) { this.position = this.buffer.writeDoubleLE(data, this.position); - if (this.position > this.bufferSize) { - // should never happen, if checkCapacity() is correctly used - throw new Error( - `Buffer overflow [position=${this.position}, bufferSize=${this.bufferSize}]`, - ); + } + + protected writeArray(arr: unknown[]) { + const dimensions = getDimensions(arr); + this.checkCapacity([], 1 + dimensions.length * 4); + this.writeByte(dimensions.length); + let numOfElements = 1; + for (let i = 0; i < dimensions.length; i++) { + numOfElements *= dimensions[i]; + this.writeInt(dimensions[i]); + } + + this.checkCapacity([], numOfElements * 8); + this.writeArrayValues(arr, dimensions); + } + + private writeArrayValues(arr: unknown[], dimensions: number[]) { + if (Array.isArray(arr[0])) { + const length = arr[0].length; + for (let i = 0; i < arr.length; i++) { + const subArray = arr[i] as unknown[]; + if (subArray.length !== length) { + throw new Error( + `length does not match array dimensions [dimensions=[${dimensions}], length=${subArray.length}]`, + ); + } + this.writeArrayValues(subArray, dimensions); + } + } else { + const dataType = typeof arr[0]; + switch (dataType) { + case "number": + for (let i = 0; i < arr.length; i++) { + this.position = this.buffer.writeDoubleLE( + arr[i] as number, + this.position, + ); + } + break; + default: + throw new Error(`unsupported array type [type=${dataType}]`); + } } } @@ -436,6 +469,15 @@ abstract class SenderBufferBase implements SenderBuffer { } } } + + private assertBufferOverflow() { + if (this.position > this.bufferSize) { + // should never happen, if checkCapacity() is correctly used + throw new Error( + `Buffer overflow [position=${this.position}, bufferSize=${this.bufferSize}]`, + ); + } + } } export { SenderBufferBase }; diff --git a/src/buffer/bufferv1.ts b/src/buffer/bufferv1.ts index aa2a37e..0efe7c5 100644 --- a/src/buffer/bufferv1.ts +++ b/src/buffer/bufferv1.ts @@ -21,6 +21,10 @@ class SenderBufferV1 extends SenderBufferBase { ); return this; } + + arrayColumn(): SenderBuffer { + throw new Error("Arrays are not supported in protocol v1"); + } } export { SenderBufferV1 }; diff --git a/src/buffer/bufferv2.ts b/src/buffer/bufferv2.ts index e21fdb1..168d5b5 100644 --- a/src/buffer/bufferv2.ts +++ b/src/buffer/bufferv2.ts @@ -3,7 +3,12 @@ import { SenderOptions } from "../options"; import { SenderBuffer } from "./index"; import { SenderBufferBase } from "./base"; +const COLUMN_TYPE_DOUBLE: number = 10; +const COLUMN_TYPE_NULL: number = 33; + +const ENTITY_TYPE_ARRAY: number = 14; const ENTITY_TYPE_DOUBLE: number = 16; + const EQUALS_SIGN: number = "=".charCodeAt(0); class SenderBufferV2 extends SenderBufferBase { @@ -25,6 +30,25 @@ class SenderBufferV2 extends SenderBufferBase { ); return this; } + + arrayColumn(name: string, value: unknown[]): SenderBuffer { + if (value && !Array.isArray(value)) { + throw new Error(`Value must be an array, received ${value}`); + } + this.writeColumn(name, value, () => { + this.checkCapacity([], 3); + this.writeByte(EQUALS_SIGN); + this.writeByte(ENTITY_TYPE_ARRAY); + + if (!value) { + this.writeByte(COLUMN_TYPE_NULL); + } else { + this.writeByte(COLUMN_TYPE_DOUBLE); + this.writeArray(value); + } + }); + return this; + } } export { SenderBufferV2 }; diff --git a/src/buffer/index.ts b/src/buffer/index.ts index fdac848..cd11d09 100644 --- a/src/buffer/index.ts +++ b/src/buffer/index.ts @@ -109,6 +109,8 @@ interface SenderBuffer { */ floatColumn(name: string, value: number): SenderBuffer; + arrayColumn(name: string, value: unknown[]): SenderBuffer; + /** * Write an integer column with its value into the buffer of the sender. * diff --git a/src/sender.ts b/src/sender.ts index e06df2e..a5767d9 100644 --- a/src/sender.ts +++ b/src/sender.ts @@ -234,6 +234,11 @@ class Sender { return this; } + arrayColumn(name: string, value: unknown[]): Sender { + this.buffer.arrayColumn(name, value); + return this; + } + /** * Write an integer column with its value into the buffer of the sender. * diff --git a/src/utils.ts b/src/utils.ts index 734740d..9a1b8a5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -36,6 +36,18 @@ function timestampToNanos(timestamp: bigint, unit: TimestampUnit) { } } +function getDimensions(arr: unknown) { + const dimensions: number[] = []; + while (Array.isArray(arr)) { + if (arr.length === 0) { + throw new Error("zero length array not supported"); + } + dimensions.push(arr.length); + arr = arr[0]; + } + return dimensions; +} + async function fetchJson(url: string): Promise { let response: globalThis.Response; try { @@ -59,4 +71,5 @@ export { timestampToNanos, TimestampUnit, fetchJson, + getDimensions, }; diff --git a/test/sender.buffer.test.ts b/test/sender.buffer.test.ts index 4f43623..3f6e2ee 100644 --- a/test/sender.buffer.test.ts +++ b/test/sender.buffer.test.ts @@ -167,6 +167,108 @@ describe("Sender message builder test suite (anything not covered in client inte await sender.close(); }); + it("does not support arrays with protocol v1", async function () { + const sender = new Sender({ + protocol: "tcp", + protocol_version: "1", + host: "host", + init_buf_size: 1024, + }); + expect(() => + sender.table("tableName").arrayColumn("arrayCol", [12.3, 23.4]), + ).toThrow("Arrays are not supported in protocol v1"); + await sender.close(); + }); + + it("supports arrays with protocol v2", async function () { + const sender = new Sender({ + protocol: "tcp", + protocol_version: "2", + host: "host", + init_buf_size: 1024, + }); + await sender + .table("tableName") + .arrayColumn("arrayCol", [12.3, 23.4]) + .atNow(); + expect(bufferContentHex(sender)).toBe( + toHex("tableName arrayCol==") + + " 0e 0a 01 02 00 00 00 9a 99 99 99 99 99 28 40 66 66 66 66 66 66 37 40 " + + toHex("\n"), + ); + await sender.close(); + }); + + it("supports multidimensional arrays with protocol v2", async function () { + const sender = new Sender({ + protocol: "tcp", + protocol_version: "2", + host: "host", + init_buf_size: 1024, + }); + await sender + .table("tableName") + .arrayColumn("arrayCol", [[12.3], [23.4]]) + .atNow(); + expect(bufferContentHex(sender)).toBe( + toHex("tableName arrayCol==") + + " 0e 0a 02 02 00 00 00 01 00 00 00 9a 99 99 99 99 99 28 40 66 66 66 66 66 66 37 40 " + + toHex("\n"), + ); + await sender.close(); + }); + + it("does not accept empty array", async function () { + const sender = new Sender({ + protocol: "tcp", + protocol_version: "2", + host: "host", + init_buf_size: 1024, + }); + sender.table("tableName"); + expect(() => sender.arrayColumn("arrayCol", [])).toThrow( + "zero length array not supported", + ); + expect(() => sender.arrayColumn("arrayCol", [[], []])).toThrow( + "zero length array not supported", + ); + await sender.close(); + }); + + it("does not accept irregular array", async function () { + const sender = new Sender({ + protocol: "tcp", + protocol_version: "2", + host: "host", + init_buf_size: 1024, + }); + expect(() => + sender.table("tableName").arrayColumn("arrayCol", [[1.1, 2.2], [3.3]]), + ).toThrow( + "length does not match array dimensions [dimensions=[2,2], length=1]", + ); + await sender.close(); + }); + + it("supports arrays with NULL value", async function () { + const sender = new Sender({ + protocol: "http", + protocol_version: "2", + host: "host", + init_buf_size: 1024, + }); + await sender.table("tableName").arrayColumn("arrayCol", undefined).atNow(); + await sender.table("tableName").arrayColumn("arrayCol", null).atNow(); + expect(bufferContentHex(sender)).toBe( + toHex("tableName arrayCol==") + + " 0e 21 " + + toHex("\ntableName arrayCol==") + + " 0e 21 " + + toHex("\n"), + ); + await sender.close(); + }); + it("supports timestamp field as number", async function () { const sender = new Sender({ protocol: "tcp", @@ -838,6 +940,21 @@ function bufferContent(sender: Sender) { return sender.buffer.toBufferView().toString(); } +function bufferContentHex(sender: Sender) { + // @ts-expect-error - Accessing private field + return toHexString(sender.buffer.toBufferView()); +} + +function toHex(str: string) { + return toHexString(Buffer.from(str)); +} + +function toHexString(buffer: Buffer) { + return Array.from(buffer) + .map((b) => b.toString(16).padStart(2, "0")) + .join(" "); +} + function bufferSize(sender: Sender) { // @ts-expect-error - Accessing private field return sender.buffer.bufferSize; From 46664d007195fde96a41e9dc5b04a1460b51ad2e Mon Sep 17 00:00:00 2001 From: glasstiger Date: Tue, 29 Jul 2025 01:26:03 +0100 Subject: [PATCH 2/5] test unsupported array types --- test/sender.buffer.test.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/sender.buffer.test.ts b/test/sender.buffer.test.ts index 3f6e2ee..1fd55f8 100644 --- a/test/sender.buffer.test.ts +++ b/test/sender.buffer.test.ts @@ -250,6 +250,22 @@ describe("Sender message builder test suite (anything not covered in client inte await sender.close(); }); + it("does not accept unsupported types", async function () { + const sender = new Sender({ + protocol: "http", + protocol_version: "2", + host: "host", + init_buf_size: 1024, + }); + sender.table("tableName"); + expect(() => sender.arrayColumn("col", ['str'])).toThrow("unsupported array type [type=string]"); + expect(() => sender.arrayColumn("col", [true])).toThrow("unsupported array type [type=boolean]"); + expect(() => sender.arrayColumn("col", [{}])).toThrow("unsupported array type [type=object]"); + expect(() => sender.arrayColumn("col", [null])).toThrow("unsupported array type [type=object]"); + expect(() => sender.arrayColumn("col", [undefined])).toThrow("unsupported array type [type=undefined]"); + await sender.close(); + }); + it("supports arrays with NULL value", async function () { const sender = new Sender({ protocol: "http", From 2cdf4e5ac29846516370ea849c27a739e1cc88b3 Mon Sep 17 00:00:00 2001 From: glasstiger Date: Wed, 30 Jul 2025 11:36:22 +0100 Subject: [PATCH 3/5] more tests, better error message --- src/buffer/bufferv2.ts | 4 ++-- test/sender.buffer.test.ts | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/buffer/bufferv2.ts b/src/buffer/bufferv2.ts index 168d5b5..c1de5af 100644 --- a/src/buffer/bufferv2.ts +++ b/src/buffer/bufferv2.ts @@ -32,8 +32,8 @@ class SenderBufferV2 extends SenderBufferBase { } arrayColumn(name: string, value: unknown[]): SenderBuffer { - if (value && !Array.isArray(value)) { - throw new Error(`Value must be an array, received ${value}`); + if (value !== null && value !== undefined && !Array.isArray(value)) { + throw new Error(`The value must be an array [value=${JSON.stringify(value)}, type=${typeof value}]`); } this.writeColumn(name, value, () => { this.checkCapacity([], 3); diff --git a/test/sender.buffer.test.ts b/test/sender.buffer.test.ts index 1fd55f8..42c8886 100644 --- a/test/sender.buffer.test.ts +++ b/test/sender.buffer.test.ts @@ -266,6 +266,29 @@ describe("Sender message builder test suite (anything not covered in client inte await sender.close(); }); + it("does not accept non-array types", async function () { + const sender = new Sender({ + protocol: "http", + protocol_version: "2", + host: "host", + init_buf_size: 1024, + }); + sender.table("tableName"); + // @ts-expect-error - Testing invalid input + expect(() => sender.arrayColumn("col", 12.345)).toThrow("The value must be an array [value=12.345, type=number]"); + // @ts-expect-error - Testing invalid input + expect(() => sender.arrayColumn("col", 42)).toThrow("The value must be an array [value=42, type=number]"); + // @ts-expect-error - Testing invalid input + expect(() => sender.arrayColumn("col", "str")).toThrow("The value must be an array [value=\"str\", type=string]"); + // @ts-expect-error - Testing invalid input + expect(() => sender.arrayColumn("col", "")).toThrow("The value must be an array [value=\"\", type=string]"); + // @ts-expect-error - Testing invalid input + expect(() => sender.arrayColumn("col", true)).toThrow("The value must be an array [value=true, type=boolean]"); + // @ts-expect-error - Testing invalid input + expect(() => sender.arrayColumn("col", {})).toThrow("The value must be an array [value={}, type=object]"); + await sender.close(); + }); + it("supports arrays with NULL value", async function () { const sender = new Sender({ protocol: "http", From ab6f96818fccbf2af5119ae2a1922c3eebb1cead Mon Sep 17 00:00:00 2001 From: glasstiger Date: Thu, 31 Jul 2025 01:14:55 +0100 Subject: [PATCH 4/5] array validation --- src/buffer/base.ts | 47 +++++++++----- src/buffer/bufferv2.ts | 11 +++- src/utils.ts | 71 ++++++++++++++++++-- test/sender.buffer.test.ts | 130 ++++++++++++++++++++++++++++++++----- 4 files changed, 217 insertions(+), 42 deletions(-) diff --git a/src/buffer/base.ts b/src/buffer/base.ts index e8c0de3..2d0ab23 100644 --- a/src/buffer/base.ts +++ b/src/buffer/base.ts @@ -10,8 +10,8 @@ import { DEFAULT_MAX_BUFFER_SIZE, } from "./index"; import { + ArrayPrimitive, isInteger, - getDimensions, timestampToMicros, timestampToNanos, TimestampUnit, @@ -390,35 +390,29 @@ abstract class SenderBufferBase implements SenderBuffer { this.position = this.buffer.writeDoubleLE(data, this.position); } - protected writeArray(arr: unknown[]) { - const dimensions = getDimensions(arr); + protected writeArray( + arr: unknown[], + dimensions: number[], + type: ArrayPrimitive, + ) { this.checkCapacity([], 1 + dimensions.length * 4); this.writeByte(dimensions.length); - let numOfElements = 1; for (let i = 0; i < dimensions.length; i++) { - numOfElements *= dimensions[i]; this.writeInt(dimensions[i]); } - this.checkCapacity([], numOfElements * 8); + this.checkCapacity([], SenderBufferBase.arraySize(dimensions, type)); this.writeArrayValues(arr, dimensions); } private writeArrayValues(arr: unknown[], dimensions: number[]) { if (Array.isArray(arr[0])) { - const length = arr[0].length; for (let i = 0; i < arr.length; i++) { - const subArray = arr[i] as unknown[]; - if (subArray.length !== length) { - throw new Error( - `length does not match array dimensions [dimensions=[${dimensions}], length=${subArray.length}]`, - ); - } - this.writeArrayValues(subArray, dimensions); + this.writeArrayValues(arr[i] as unknown[], dimensions); } } else { - const dataType = typeof arr[0]; - switch (dataType) { + const type = typeof arr[0]; + switch (type) { case "number": for (let i = 0; i < arr.length; i++) { this.position = this.buffer.writeDoubleLE( @@ -428,7 +422,7 @@ abstract class SenderBufferBase implements SenderBuffer { } break; default: - throw new Error(`unsupported array type [type=${dataType}]`); + throw new Error(`Unsupported array type [type=${type}]`); } } } @@ -470,6 +464,25 @@ abstract class SenderBufferBase implements SenderBuffer { } } + private static arraySize(dimensions: number[], type: ArrayPrimitive): number { + let numOfElements = 1; + for (let i = 0; i < dimensions.length; i++) { + numOfElements *= dimensions[i]; + } + + switch (type) { + case "number": + return numOfElements * 8; + case "boolean": + return numOfElements; + case "string": + // in case of string[] capacity check is done separately for each array element + return 0; + default: + throw new Error(`Unsupported array type [type=${type}]`); + } + } + private assertBufferOverflow() { if (this.position > this.bufferSize) { // should never happen, if checkCapacity() is correctly used diff --git a/src/buffer/bufferv2.ts b/src/buffer/bufferv2.ts index c1de5af..f9fd602 100644 --- a/src/buffer/bufferv2.ts +++ b/src/buffer/bufferv2.ts @@ -2,6 +2,7 @@ import { SenderOptions } from "../options"; import { SenderBuffer } from "./index"; import { SenderBufferBase } from "./base"; +import { getDimensions, validateArray } from "../utils"; const COLUMN_TYPE_DOUBLE: number = 10; const COLUMN_TYPE_NULL: number = 33; @@ -32,9 +33,13 @@ class SenderBufferV2 extends SenderBufferBase { } arrayColumn(name: string, value: unknown[]): SenderBuffer { - if (value !== null && value !== undefined && !Array.isArray(value)) { - throw new Error(`The value must be an array [value=${JSON.stringify(value)}, type=${typeof value}]`); + const dimensions = getDimensions(value); + const type = validateArray(value, dimensions); + // only number arrays and NULL supported for now + if (type !== "number" && type !== null) { + throw new Error(`Unsupported array type [type=${type}]`); } + this.writeColumn(name, value, () => { this.checkCapacity([], 3); this.writeByte(EQUALS_SIGN); @@ -44,7 +49,7 @@ class SenderBufferV2 extends SenderBufferBase { this.writeByte(COLUMN_TYPE_NULL); } else { this.writeByte(COLUMN_TYPE_DOUBLE); - this.writeArray(value); + this.writeArray(value, dimensions, type); } }); return this; diff --git a/src/utils.ts b/src/utils.ts index 9a1b8a5..095bcf6 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,5 @@ +type ArrayPrimitive = "number" | "boolean" | "string" | null; + type TimestampUnit = "ns" | "us" | "ms"; function isBoolean(value: unknown): value is boolean { @@ -36,18 +38,73 @@ function timestampToNanos(timestamp: bigint, unit: TimestampUnit) { } } -function getDimensions(arr: unknown) { +function getDimensions(data: unknown) { const dimensions: number[] = []; - while (Array.isArray(arr)) { - if (arr.length === 0) { - throw new Error("zero length array not supported"); + while (Array.isArray(data)) { + if (data.length === 0) { + throw new Error("Zero length array not supported"); } - dimensions.push(arr.length); - arr = arr[0]; + dimensions.push(data.length); + data = data[0]; } return dimensions; } +function validateArray(data: unknown[], dimensions: number[]): ArrayPrimitive { + if (data === null || data === undefined) { + return null; + } + if (!Array.isArray(data)) { + throw new Error( + `The value must be an array [value=${JSON.stringify(data)}, type=${typeof data}]`, + ); + } + + let expectedType: ArrayPrimitive = null; + + function checkArray( + array: unknown[], + depth: number = 0, + path: string = "", + ): void { + const expectedLength = dimensions[depth]; + if (array.length !== expectedLength) { + throw new Error( + `Length of arrays do not match [expected=${expectedLength}, actual=${array.length}, dimensions=[${dimensions}], path=${path}]`, + ); + } + + if (depth < dimensions.length - 1) { + // intermediate level, expecting arrays + for (let i = 0; i < array.length; i++) { + if (!Array.isArray(array[i])) { + throw new Error( + `Mixed types found [expected=array, current=${typeof array[i]}, path=${path}[${i}]]`, + ); + } + checkArray(array[i] as unknown[], depth + 1, `${path}[${i}]`); + } + } else { + // leaf level, expecting primitives + if (expectedType === null) { + expectedType = typeof array[0] as ArrayPrimitive; + } + + for (let i = 0; i < array.length; i++) { + const currentType = typeof array[i] as ArrayPrimitive; + if (currentType !== expectedType) { + throw new Error( + `Mixed types found [expected=${expectedType}, current=${currentType}, path=${path}[${i}]]`, + ); + } + } + } + } + + checkArray(data); + return expectedType; +} + async function fetchJson(url: string): Promise { let response: globalThis.Response; try { @@ -72,4 +129,6 @@ export { TimestampUnit, fetchJson, getDimensions, + validateArray, + ArrayPrimitive, }; diff --git a/test/sender.buffer.test.ts b/test/sender.buffer.test.ts index 42c8886..89dfeac 100644 --- a/test/sender.buffer.test.ts +++ b/test/sender.buffer.test.ts @@ -227,15 +227,15 @@ describe("Sender message builder test suite (anything not covered in client inte }); sender.table("tableName"); expect(() => sender.arrayColumn("arrayCol", [])).toThrow( - "zero length array not supported", + "Zero length array not supported", ); expect(() => sender.arrayColumn("arrayCol", [[], []])).toThrow( - "zero length array not supported", + "Zero length array not supported", ); await sender.close(); }); - it("does not accept irregular array", async function () { + it("does not accept irregularly sized array", async function () { const sender = new Sender({ protocol: "tcp", protocol_version: "2", @@ -243,9 +243,85 @@ describe("Sender message builder test suite (anything not covered in client inte init_buf_size: 1024, }); expect(() => - sender.table("tableName").arrayColumn("arrayCol", [[1.1, 2.2], [3.3]]), + sender.table("tableName").arrayColumn("arrayCol", [ + [ + [1.1, 2.2], + [3.3, 4.4], + [5.5, 6.6], + ], + [ + [1.1, 2.2], + [3.3, 4.4], + [5.5, 6.6], + ], + [ + [1.1, 2.2], + [3.3, 4.4], + [5.5, 6.6], + ], + [[1.1, 2.2], [3.3], [5.5, 6.6]], + ]), ).toThrow( - "length does not match array dimensions [dimensions=[2,2], length=1]", + "Length of arrays do not match [expected=2, actual=1, dimensions=[4,3,2], path=[3][1]]", + ); + await sender.close(); + }); + + it("does not accept non-homogenous array", async function () { + const sender = new Sender({ + protocol: "tcp", + protocol_version: "2", + host: "host", + init_buf_size: 1024, + }); + sender.table("tableName"); + expect(() => + sender.arrayColumn("arrayCol", [ + [ + [1.1, 2.2], + [3.3, 4.4], + [5.5, 6.6], + ], + [ + [1.1, 2.2], + [3.3, 4.4], + [5.5, 6.6], + ], + [ + [1.1, 2.2], + [3.3, 4.4], + [5.5, 6.6], + ], + [ + [1.1, 2.2], + [3.3, "4.4"], + [5.5, 6.6], + ], + ]), + ).toThrow( + "Mixed types found [expected=number, current=string, path=[3][1][1]]", + ); + expect(() => + sender.arrayColumn("arrayCol", [ + [ + [1.1, 2.2], + [3.3, 4.4], + [5.5, 6.6], + ], + [ + [1.1, 2.2], + [3.3, 4.4], + [5.5, 6.6], + ], + [ + [1.1, 2.2], + [3.3, 4.4], + [5.5, 6.6], + ], + [[1.1, 2.2], 3.3, [5.5, 6.6]], + ]), + ).toThrow( + "Mixed types found [expected=array, current=number, path=[3][1]]", ); await sender.close(); }); @@ -258,11 +334,21 @@ describe("Sender message builder test suite (anything not covered in client inte init_buf_size: 1024, }); sender.table("tableName"); - expect(() => sender.arrayColumn("col", ['str'])).toThrow("unsupported array type [type=string]"); - expect(() => sender.arrayColumn("col", [true])).toThrow("unsupported array type [type=boolean]"); - expect(() => sender.arrayColumn("col", [{}])).toThrow("unsupported array type [type=object]"); - expect(() => sender.arrayColumn("col", [null])).toThrow("unsupported array type [type=object]"); - expect(() => sender.arrayColumn("col", [undefined])).toThrow("unsupported array type [type=undefined]"); + expect(() => sender.arrayColumn("col", ["str"])).toThrow( + "Unsupported array type [type=string]", + ); + expect(() => sender.arrayColumn("col", [true])).toThrow( + "Unsupported array type [type=boolean]", + ); + expect(() => sender.arrayColumn("col", [{}])).toThrow( + "Unsupported array type [type=object]", + ); + expect(() => sender.arrayColumn("col", [null])).toThrow( + "Unsupported array type [type=object]", + ); + expect(() => sender.arrayColumn("col", [undefined])).toThrow( + "Unsupported array type [type=undefined]", + ); await sender.close(); }); @@ -275,17 +361,29 @@ describe("Sender message builder test suite (anything not covered in client inte }); sender.table("tableName"); // @ts-expect-error - Testing invalid input - expect(() => sender.arrayColumn("col", 12.345)).toThrow("The value must be an array [value=12.345, type=number]"); + expect(() => sender.arrayColumn("col", 12.345)).toThrow( + "The value must be an array [value=12.345, type=number]", + ); // @ts-expect-error - Testing invalid input - expect(() => sender.arrayColumn("col", 42)).toThrow("The value must be an array [value=42, type=number]"); + expect(() => sender.arrayColumn("col", 42)).toThrow( + "The value must be an array [value=42, type=number]", + ); // @ts-expect-error - Testing invalid input - expect(() => sender.arrayColumn("col", "str")).toThrow("The value must be an array [value=\"str\", type=string]"); + expect(() => sender.arrayColumn("col", "str")).toThrow( + 'The value must be an array [value="str", type=string]', + ); // @ts-expect-error - Testing invalid input - expect(() => sender.arrayColumn("col", "")).toThrow("The value must be an array [value=\"\", type=string]"); + expect(() => sender.arrayColumn("col", "")).toThrow( + 'The value must be an array [value="", type=string]', + ); // @ts-expect-error - Testing invalid input - expect(() => sender.arrayColumn("col", true)).toThrow("The value must be an array [value=true, type=boolean]"); + expect(() => sender.arrayColumn("col", true)).toThrow( + "The value must be an array [value=true, type=boolean]", + ); // @ts-expect-error - Testing invalid input - expect(() => sender.arrayColumn("col", {})).toThrow("The value must be an array [value={}, type=object]"); + expect(() => sender.arrayColumn("col", {})).toThrow( + "The value must be an array [value={}, type=object]", + ); await sender.close(); }); From 505a9bb918406fa4cd0599e924876e8f26b98e99 Mon Sep 17 00:00:00 2001 From: glasstiger Date: Fri, 1 Aug 2025 00:52:01 +0100 Subject: [PATCH 5/5] add back scheduled run of build --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3dead93..7c87581 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,6 +5,8 @@ on: branches: - main pull_request: + schedule: + - cron: '15 2,10,18 * * *' jobs: test: