chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:25:07 +08:00
commit a26e856398
1681 changed files with 296950 additions and 0 deletions
@@ -0,0 +1,191 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import * as arrow from "apache-arrow";
import { test, expect } from "@perspective-dev/test";
import perspective from "../perspective_client";
import * as fs from "node:fs";
import * as url from "node:url";
const __dirname = url.fileURLToPath(new URL(".", import.meta.url)).slice(0, -1);
test.describe("Arrow", function () {
test.describe("Date columns", function () {
// https://github.com/perspective-dev/perspective/issues/2894
// https://github.com/jdangerx/repro-perspective-float-filter/tree/dates
test("Date columns are preserved through Arrow in and out", async function () {
const tableData = arrow.tableFromArrays({
date: arrow.vectorFromArray([20089], new arrow.Date_()),
});
const table = await perspective.table(arrow.tableToIPC(tableData));
const view = await table.view();
const json = await view.to_json();
const d = new Date(json[0].date);
expect(json[0].date).toEqual(1735689600000);
// This doesn't test anything except my math
expect(d.getUTCFullYear()).toEqual(2025);
expect(d.getUTCDate()).toEqual(1);
expect(d.getUTCMonth()).toEqual(0);
expect(d.getUTCHours()).toEqual(0);
expect(d.getUTCMinutes()).toEqual(0);
expect(d.getUTCSeconds()).toEqual(0);
expect(d.getTimezoneOffset()).toEqual(0);
await view.delete();
await table.delete();
});
test("Date columns are preserved through Arrow in and out, in a negative timezone", async function () {
process.env.TZ = `America/New_York`;
const tableData = arrow.tableFromArrays({
date: arrow.vectorFromArray([20089], new arrow.Date_()),
});
const table = await perspective.table(arrow.tableToIPC(tableData));
const view = await table.view();
const json = await view.to_json();
const d = new Date(json[0].date);
expect(json[0].date).toEqual(1735689600000);
expect(d.getUTCFullYear()).toEqual(2025);
expect(d.getUTCDate()).toEqual(1);
expect(d.getUTCMonth()).toEqual(0);
expect(d.getUTCHours()).toEqual(0);
expect(d.getUTCMinutes()).toEqual(0);
expect(d.getUTCSeconds()).toEqual(0);
// NY now ...
expect(d.getTimezoneOffset()).toEqual(300);
await view.delete();
await table.delete();
process.env.TZ = `UTC`;
});
});
test.describe("regressions", () => {
// https://github.com/perspective-dev/perspective/issues/3169
test("null values are preserved across multi-batch Arrow IPC streams", async function () {
function row(
identifier: string,
value: number | null,
date: Date | null,
) {
return arrow.tableFromArrays({
Identifier: arrow.vectorFromArray(
[identifier],
new arrow.Utf8(),
),
Value: arrow.vectorFromArray([value], new arrow.Float64()),
Date: arrow.vectorFromArray([date], new arrow.DateDay()),
});
}
const t1 = row("A", null, null);
const t2 = row("B", 5, null);
const t3 = row("C", null, new Date(Date.UTC(2025, 5, 15)));
const multiBatchTable = new arrow.Table([
...t1.batches,
...t2.batches,
...t3.batches,
]);
expect(multiBatchTable.batches.length).toEqual(3);
const ipc = arrow.tableToIPC(multiBatchTable, "stream");
const table = await perspective.table(ipc.buffer as ArrayBuffer);
const view = await table.view();
const json = await view.to_json();
await view.delete();
await table.delete();
expect(json).toStrictEqual([
{ Identifier: "A", Value: null, Date: null },
{ Identifier: "B", Value: 5, Date: null },
{ Identifier: "C", Value: null, Date: 1749945600000 },
]);
});
test("null equality works correctly in updates", async function () {
async function write_to_json(
buffer: ArrayBuffer,
filename: string,
) {
const table = await perspective.table(buffer);
const view = await table.view({
columns: ["ENTITY_TYPE"],
});
const json = await view.to_columns_string();
fs.writeFileSync(filename, json);
await view.delete();
await table.delete();
}
const file = JSON.parse(
fs.readFileSync(
`${__dirname}/../../arrow/untitled.json`,
"utf8",
),
);
const table = await perspective.table(file, {
name: "arrow_null_test",
});
const view = await table.view({ group_by: ["ENTITY_TYPE"] });
for (let i = 2; i < 6; i++) {
const file = JSON.parse(
fs.readFileSync(
`${__dirname}/../../arrow/untitled${i}.json`,
"utf8",
),
);
await table.update(file);
}
const cols = await view.to_columns({ end_row: 4 });
expect(cols).toStrictEqual({
ENTITY_TYPE: [2158, 985, 168, 311],
__ROW_PATH__: [[], [null], [""], ["AAAA"]],
});
});
test("Loads a time32 (millisecond) column without overflow", async function () {
const expected = Array.from({ length: 64 }, (_, i) => i);
const tableData = arrow.tableFromArrays({
t: arrow.vectorFromArray(expected, new arrow.TimeMillisecond()),
});
const table = await perspective.table(arrow.tableToIPC(tableData));
const view = await table.view();
expect(await table.size()).toEqual(64);
expect(await view.to_columns()).toEqual({ t: expected });
await view.delete();
await table.delete();
});
});
test.describe("Malformed input", function () {
test("Rejects an Arrow with a row count that exceeds 32 bits", async function () {
const bytes = new Uint8Array(
fs.readFileSync(`${__dirname}/../../arrow/bad_row_count.arrow`),
);
await expect(perspective.table(bytes)).rejects.toThrow(
/row count exceeds maximum supported size/,
);
});
});
});
@@ -0,0 +1,66 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { test, expect } from "@perspective-dev/test";
import perspective from "../perspective_client";
const CSV = "x,y,z\n1,2,3\n4,5,6";
test.describe("CSV Constructors", function () {
test("Handles String format", async function () {
const table = await perspective.table(CSV);
const v = await table.view();
const json = await v.to_json();
expect(json).toEqual([
{ x: 1, y: 2, z: 3 },
{ x: 4, y: 5, z: 6 },
]);
});
test("Handles String format with explicit format option", async function () {
const table = await perspective.table(CSV, {
format: "csv",
});
const v = await table.view();
const json = await v.to_json();
expect(json).toEqual([
{ x: 1, y: 2, z: 3 },
{ x: 4, y: 5, z: 6 },
]);
});
test("Handles ArrayBuffer format", async function () {
var enc = new TextEncoder();
const table = await perspective.table(enc.encode(CSV).buffer, {
format: "csv",
});
const v = await table.view();
const json = await v.to_json();
expect(json).toEqual([
{ x: 1, y: 2, z: 3 },
{ x: 4, y: 5, z: 6 },
]);
});
test("Handles UInt8Arry format", async function () {
var enc = new TextEncoder();
const table = await perspective.table(enc.encode(CSV), {
format: "csv",
});
const v = await table.view();
const json = await v.to_json();
expect(json).toEqual([
{ x: 1, y: 2, z: 3 },
{ x: 4, y: 5, z: 6 },
]);
});
});
@@ -0,0 +1,111 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { test, expect } from "@perspective-dev/test";
import perspective from "../perspective_client";
const JAN_1_2024_UTC = 1704067200000;
test.describe("Date timezone invariance", function () {
let SAVED_TZ: string | undefined;
test.beforeEach(() => {
SAVED_TZ = process.env.TZ;
process.env.TZ = "Europe/Amsterdam";
});
test.afterEach(() => {
process.env.TZ = SAVED_TZ;
});
test("date strings serialize to UTC midnight epoch ms", async function () {
const table = await perspective.table({ d: "date" });
await table.update({ d: ["2024-01-01"] });
const view = await table.view();
const cols = (await view.to_columns()) as { d: number[] };
expect(cols.d).toEqual([JAN_1_2024_UTC]);
await view.delete();
await table.delete();
});
test("date epoch ms input round-trips exactly", async function () {
const table = await perspective.table({ d: "date" });
await table.update({ d: [JAN_1_2024_UTC] });
const view = await table.view();
const cols = (await view.to_columns()) as { d: number[] };
expect(cols.d).toEqual([JAN_1_2024_UTC]);
await view.delete();
await table.delete();
});
test("formatted output prints the stored calendar day", async function () {
const table = await perspective.table({ d: "date" });
await table.update({ d: ["2024-01-01"] });
const view = await table.view();
expect(await view.to_columns_string({ formatted: true })).toEqual(
'{"d":["2024-01-01"]}',
);
expect(await view.to_csv()).toEqual('"d"\n2024-01-01\n');
await view.delete();
await table.delete();
});
test("day_bucket stays on the UTC calendar day at the day boundary", async function () {
const table = await perspective.table({ t: "datetime" });
// 23:59 UTC is already the next day in Europe/Amsterdam
await table.update({ t: ["2020-01-31 23:59:00"] });
const view = await table.view({
expressions: { bucket: `bucket("t", 'D')` },
});
const cols = (await view.to_columns()) as { bucket: number[] };
// 2020-01-31T00:00:00Z
expect(cols.bucket).toEqual([1580428800000]);
await view.delete();
await table.delete();
});
test("day_of_week and hour_of_day compute in UTC", async function () {
const table = await perspective.table({ t: "datetime" });
await table.update({ t: ["2020-01-31 23:59:00"] });
const view = await table.view({
expressions: {
dow: `day_of_week("t")`,
hod: `hour_of_day("t")`,
},
});
const cols = (await view.to_columns()) as {
dow: string[];
hod: number[];
};
// Friday 23:00 UTC, not Saturday 00:59 Amsterdam
expect(cols.dow).toEqual(["6 Friday"]);
expect(cols.hod).toEqual([23]);
await view.delete();
await table.delete();
});
test("JSON date output agrees with Arrow date32 day arithmetic", async function () {
const table = await perspective.table({ d: "date" });
await table.update({ d: ["2024-01-01"] });
const view = await table.view();
const cols = (await view.to_columns()) as { d: number[] };
expect(cols.d[0] % 86400000).toEqual(0);
expect(cols.d[0] / 86400000).toEqual(19723); // days since epoch
await view.delete();
await table.delete();
});
});
@@ -0,0 +1,90 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { test, expect } from "@perspective-dev/test";
import perspective from "../perspective_client";
const TEST_JSON = { x: [1, 4], y: [2, 5], z: [3, 6] };
test.describe("JSON", function () {
test.describe("Integer columns", function () {
test("Integer columns can be updated with all JSON numeric types and cousins", async function () {
const table = await perspective.table({ x: "integer" });
await table.update([{ x: 0 }]);
await table.update([{ x: 1 }]);
await table.update([{ x: undefined }]);
await table.update([{ x: null }]);
await table.update([{ x: 7.23 }]);
await table.update([{ x: -6 }]);
await table.update([{ x: Infinity }]);
await table.update([{ x: "100.1" }]);
await table.update([{ x: "11" }]);
const v = await table.view();
const json = await v.to_json();
expect(json).toEqual([
{ x: 0 },
{ x: 1 },
{ x: null },
{ x: null },
{ x: 7 },
{ x: -6 },
{ x: null },
{ x: 100 },
{ x: 11 },
]);
});
});
test("Handles String format", async function () {
const table = await perspective.table(JSON.stringify(TEST_JSON), {
format: "columns",
});
const v = await table.view();
const json = await v.to_json();
expect(json).toEqual([
{ x: 1, y: 2, z: 3 },
{ x: 4, y: 5, z: 6 },
]);
});
test("Handles UInt8Array format", async function () {
const enc = new TextEncoder();
const table = await perspective.table(
enc.encode(JSON.stringify(TEST_JSON)),
{
format: "columns",
},
);
const v = await table.view();
const json = await v.to_json();
expect(json).toEqual([
{ x: 1, y: 2, z: 3 },
{ x: 4, y: 5, z: 6 },
]);
});
test("Handles ArrayBuffer format", async function () {
const enc = new TextEncoder();
const table = await perspective.table(
enc.encode(JSON.stringify(TEST_JSON)).buffer,
{
format: "columns",
},
);
const v = await table.view();
const json = await v.to_json();
expect(json).toEqual([
{ x: 1, y: 2, z: 3 },
{ x: 4, y: 5, z: 6 },
]);
});
});