@milaboratories/pf-driver 1.8.4 → 1.9.0

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.
Files changed (48) hide show
  1. package/dist/csv_writer.js +7 -5
  2. package/dist/csv_writer.js.map +1 -1
  3. package/dist/driver_decl.d.ts +3 -2
  4. package/dist/driver_decl.d.ts.map +1 -1
  5. package/dist/driver_impl.d.ts.map +1 -1
  6. package/dist/driver_impl.js +14 -5
  7. package/dist/driver_impl.js.map +1 -1
  8. package/dist/pframe_pool.js +1 -1
  9. package/dist/pframe_pool.js.map +1 -1
  10. package/dist/ptable_def_pool.js.map +1 -1
  11. package/dist/ptable_pool.js.map +1 -1
  12. package/dist/ptable_shared.d.ts +1 -2
  13. package/dist/ptable_shared.js.map +1 -1
  14. package/package.json +11 -12
  15. package/src/__tests__/csv_writer.test.ts +20 -0
  16. package/src/csv_writer.ts +25 -5
  17. package/src/driver_decl.ts +3 -2
  18. package/src/driver_double.test.ts +111 -1
  19. package/src/driver_impl.ts +22 -8
  20. package/src/pframe_pool.ts +2 -2
  21. package/src/ptable_def_pool.ts +2 -1
  22. package/src/ptable_pool.ts +2 -1
  23. package/src/ptable_shared.ts +4 -3
  24. package/dist/_virtual/_@oxc-project_runtime@0.137.0/helpers/esm/usingCtx.cjs +0 -57
  25. package/dist/_virtual/_rolldown/runtime.cjs +0 -23
  26. package/dist/csv_writer.cjs +0 -81
  27. package/dist/csv_writer.cjs.map +0 -1
  28. package/dist/data_info_helpers.cjs +0 -23
  29. package/dist/data_info_helpers.cjs.map +0 -1
  30. package/dist/driver_double.cjs +0 -114
  31. package/dist/driver_double.cjs.map +0 -1
  32. package/dist/driver_impl.cjs +0 -446
  33. package/dist/driver_impl.cjs.map +0 -1
  34. package/dist/index.cjs +0 -9
  35. package/dist/logging.cjs +0 -9
  36. package/dist/logging.cjs.map +0 -1
  37. package/dist/pframe_pool.cjs +0 -215
  38. package/dist/pframe_pool.cjs.map +0 -1
  39. package/dist/ptable_cache_per_frame.cjs +0 -58
  40. package/dist/ptable_cache_per_frame.cjs.map +0 -1
  41. package/dist/ptable_cache_plain.cjs +0 -45
  42. package/dist/ptable_cache_plain.cjs.map +0 -1
  43. package/dist/ptable_def_pool.cjs +0 -66
  44. package/dist/ptable_def_pool.cjs.map +0 -1
  45. package/dist/ptable_pool.cjs +0 -84
  46. package/dist/ptable_pool.cjs.map +0 -1
  47. package/dist/ptable_shared.cjs +0 -63
  48. package/dist/ptable_shared.cjs.map +0 -1
package/src/csv_writer.ts CHANGED
@@ -21,7 +21,9 @@ export interface PTableDataSource {
21
21
 
22
22
  /** Format a CSV/TSV header row from column specs. Line ending is CRLF. */
23
23
  export function formatHeader(specs: PTableColumnSpec[], separator: string): string {
24
- return specs.map((spec) => escapeField(columnLabel(spec), separator)).join(separator) + "\r\n";
24
+ return (
25
+ specs.map((spec) => escapeField(getNativeColumnLabel(spec), separator)).join(separator) + "\r\n"
26
+ );
25
27
  }
26
28
 
27
29
  /** Format a single data row from parallel vectors. Line ending is CRLF. */
@@ -44,6 +46,13 @@ export interface StreamPTableRowsOptions {
44
46
  pTable: PTableDataSource;
45
47
  specs: PTableColumnSpec[];
46
48
  columnIndices: number[];
49
+ /**
50
+ * Header names aligned 1:1 with `columnIndices`. When provided they are
51
+ * written verbatim, letting callers supply disambiguated labels the spec's
52
+ * intrinsic `pl7.app/label` may not carry. When omitted the header is derived
53
+ * from `specs` via {@link getNativeColumnLabel}.
54
+ */
55
+ headerNames?: string[];
47
56
  range?: TableRange;
48
57
  chunkSize: number;
49
58
  separator: string;
@@ -52,16 +61,27 @@ export interface StreamPTableRowsOptions {
52
61
  signal?: AbortSignal;
53
62
  }
54
63
  export async function* streamPTableRows(options: StreamPTableRowsOptions): AsyncIterable<string> {
55
- const { pTable, columnIndices, range, chunkSize, separator, signal, specs, includeHeader, bom } =
64
+ const { pTable, columnIndices, headerNames, range, chunkSize, separator, signal, specs } =
56
65
  options;
66
+ const { includeHeader, bom } = options;
67
+
68
+ if (headerNames !== undefined && headerNames.length !== columnIndices.length) {
69
+ throw new Error(
70
+ `headerNames length (${headerNames.length}) must match columnIndices length (${columnIndices.length})`,
71
+ );
72
+ }
57
73
 
58
74
  if (bom) {
59
75
  yield "\uFEFF";
60
76
  }
61
77
 
62
78
  if (includeHeader) {
63
- const selectedSpecs = columnIndices.map((index) => specs[index]);
64
- yield formatHeader(selectedSpecs, separator);
79
+ yield headerNames !== undefined
80
+ ? headerNames.map((name) => escapeField(name, separator)).join(separator) + "\r\n"
81
+ : formatHeader(
82
+ columnIndices.map((index) => specs[index]),
83
+ separator,
84
+ );
65
85
  }
66
86
 
67
87
  if (isNil(range)) {
@@ -89,7 +109,7 @@ export async function* streamPTableRows(options: StreamPTableRowsOptions): Async
89
109
  // ── Helpers (low-level) ──────────────────────────────────────────────
90
110
 
91
111
  /** Extract a human-readable label from a PTableColumnSpec. */
92
- export function columnLabel(spec: PTableColumnSpec): string {
112
+ export function getNativeColumnLabel(spec: PTableColumnSpec): string {
93
113
  const annotation = readAnnotation(spec.spec, Annotation.Label);
94
114
  return isNil(annotation) ? spec.spec.name : annotation.trim();
95
115
  }
@@ -105,8 +105,9 @@ export interface AbstractInternalPFrameDriver<PColumnData> extends PFrameDriver,
105
105
  /**
106
106
  * Export the table to a file, selecting the format from the extension of
107
107
  * `options.path` (`csv`/`tsv`/`parquet`/`xlsx`). `options.columnIndices`
108
- * selects the columns to export (output order); headers are derived from
109
- * each field's label annotation (falling back to the spec name).
108
+ * selects the columns to export (output order); optional
109
+ * `options.headerNames` (aligned 1:1) names them, with a missing or empty
110
+ * entry falling back to the field's label annotation (then the spec name).
110
111
  */
111
112
  exportPTable(
112
113
  handle: PTableHandle,
@@ -527,7 +527,11 @@ test("exportPTable writes a CSV with derived headers", async ({ expect }) => {
527
527
  try {
528
528
  const filePath = join(tempDir, "table.csv");
529
529
 
530
- await uiDriver.exportPTable(pTable.key, { path: filePath, columnIndices: [0, 1] });
530
+ // Omitted headerNames fall back to labels derived from the column/axis specs.
531
+ await uiDriver.exportPTable(pTable.key, {
532
+ path: filePath,
533
+ columnIndices: [0, 1],
534
+ });
531
535
 
532
536
  const content = fs.readFileSync(filePath, "utf-8");
533
537
  const lines = content.split(/\r?\n/).filter((l) => l.length > 0);
@@ -540,3 +544,109 @@ test("exportPTable writes a CSV with derived headers", async ({ expect }) => {
540
544
  fs.rmSync(tempDir, { recursive: true, force: true });
541
545
  }
542
546
  });
547
+
548
+ test("exportPTable writes caller-supplied header names verbatim", async ({ expect }) => {
549
+ await using driver = await createPFrameDriverDouble({});
550
+
551
+ const columnId = "column1" as PObjectId;
552
+ const column = {
553
+ id: columnId,
554
+ spec: {
555
+ kind: "PColumn" as const,
556
+ axesSpec: [
557
+ {
558
+ name: "axis1",
559
+ type: "String" as const,
560
+ annotations: { "pl7.app/label": "Sample" },
561
+ },
562
+ ],
563
+ name: "column1",
564
+ valueType: "Int" as const,
565
+ annotations: { "pl7.app/label": "Score" },
566
+ },
567
+ data: [
568
+ { key: ["a"], val: 10 },
569
+ { key: ["b"], val: 20 },
570
+ { key: ["c"], val: 30 },
571
+ ],
572
+ };
573
+
574
+ const uiDriver: PFrameDriver = driver;
575
+
576
+ using pTable = driver.createPTableV2({ query: { type: "column", column } });
577
+
578
+ const tempDir = fs.mkdtempSync(join(os.tmpdir(), "pf-export-"));
579
+ try {
580
+ const filePath = join(tempDir, "table.csv");
581
+
582
+ // Caller-supplied names (the disambiguated labels the UI shows) win over the
583
+ // spec's intrinsic `pl7.app/label`.
584
+ await uiDriver.exportPTable(pTable.key, {
585
+ path: filePath,
586
+ columnIndices: [0, 1],
587
+ headerNames: ["Sample id", "Score (disambiguated)"],
588
+ });
589
+
590
+ const content = fs.readFileSync(filePath, "utf-8");
591
+ const lines = content.split(/\r?\n/).filter((l) => l.length > 0);
592
+
593
+ expect(lines[0]).toBe("Sample id,Score (disambiguated)");
594
+ expect(lines.slice(1)).toEqual(["a,10", "b,20", "c,30"]);
595
+ } finally {
596
+ fs.rmSync(tempDir, { recursive: true, force: true });
597
+ }
598
+ });
599
+
600
+ test("writePTableToFs writes caller-supplied header names verbatim", async ({ expect }) => {
601
+ await using driver = await createPFrameDriverDouble({});
602
+
603
+ const columnId = "column1" as PObjectId;
604
+ const column = {
605
+ id: columnId,
606
+ spec: {
607
+ kind: "PColumn" as const,
608
+ axesSpec: [
609
+ {
610
+ name: "axis1",
611
+ type: "String" as const,
612
+ annotations: { "pl7.app/label": "Sample" },
613
+ },
614
+ ],
615
+ name: "column1",
616
+ valueType: "Int" as const,
617
+ annotations: { "pl7.app/label": "Score" },
618
+ },
619
+ data: [
620
+ { key: ["a"], val: 10 },
621
+ { key: ["b"], val: 20 },
622
+ { key: ["c"], val: 30 },
623
+ ],
624
+ };
625
+
626
+ const uiDriver: PFrameDriver = driver;
627
+
628
+ using pTable = driver.createPTableV2({ query: { type: "column", column } });
629
+
630
+ const tempDir = fs.mkdtempSync(join(os.tmpdir(), "pf-export-"));
631
+ try {
632
+ const filePath = join(tempDir, "table.csv");
633
+
634
+ // Fallback path (writePTableToFs): caller-supplied names win over the spec
635
+ // labels, mirroring the native exportPTable path.
636
+ await uiDriver.writePTableToFs(pTable.key, {
637
+ path: filePath,
638
+ format: "csv",
639
+ columnIndices: [0, 1],
640
+ headerNames: ["Sample id", "Score (disambiguated)"],
641
+ });
642
+
643
+ // Strip the leading BOM the CSV writer emits by default.
644
+ const content = fs.readFileSync(filePath, "utf-8").replace(/^/, "");
645
+ const lines = content.split(/\r?\n/).filter((l) => l.length > 0);
646
+
647
+ expect(lines[0]).toBe("Sample id,Score (disambiguated)");
648
+ expect(lines.slice(1)).toEqual(["a,10", "b,20", "c,30"]);
649
+ } finally {
650
+ fs.rmSync(tempDir, { recursive: true, force: true });
651
+ }
652
+ });
@@ -44,13 +44,13 @@ import {
44
44
  } from "@milaboratories/ts-helpers";
45
45
  import { isNil, PoolEntryGuard, type PoolEntry } from "@milaboratories/helpers";
46
46
  import { PFrameFactory } from "@milaboratories/pframes-rs-node";
47
- import { expandAxes, findTableColumn, rewriteLegacyFilters } from "@milaboratories/pframes-rs-wasm";
47
+ import { expandAxes, findTableColumn, rewriteLegacyFilters } from "@milaboratories/pf-spec";
48
48
  import { tmpdir } from "node:os";
49
49
  import * as fs from "node:fs";
50
50
  import { Readable } from "node:stream";
51
51
  import { pipeline } from "node:stream/promises";
52
52
  import * as zlib from "node:zlib";
53
- import { columnLabel, streamPTableRows } from "./csv_writer";
53
+ import { getNativeColumnLabel, streamPTableRows } from "./csv_writer";
54
54
  import type {
55
55
  AbstractInternalPFrameDriver,
56
56
  WritePTableToFsOptions,
@@ -297,6 +297,14 @@ export class AbstractPFrameDriver<
297
297
  const { pTablePromise, disposeSignal } = tableGuard.resource;
298
298
  const pTable = await pTablePromise;
299
299
 
300
+ // Caller-supplied header names win (they carry the disambiguated labels the
301
+ // UI shows); a missing or empty name falls back to the spec-derived label.
302
+ const { columnIndices } = options;
303
+ const headerNames = columnIndices.map((index, i) => {
304
+ const name = options.headerNames?.[i];
305
+ return name !== undefined && name !== "" ? name : getNativeColumnLabel(def.tableSpec[index]);
306
+ });
307
+
300
308
  const combinedSignal = AbortSignal.any(
301
309
  [options.signal, disposeSignal].filter((s): s is AbortSignal => !isNil(s)),
302
310
  );
@@ -316,7 +324,8 @@ export class AbstractPFrameDriver<
316
324
  const iterable = streamPTableRows({
317
325
  pTable,
318
326
  specs,
319
- columnIndices: options.columnIndices,
327
+ columnIndices,
328
+ headerNames,
320
329
  range: clippedRange,
321
330
  chunkSize: options.chunkSize ?? 50_000,
322
331
  separator,
@@ -354,7 +363,7 @@ export class AbstractPFrameDriver<
354
363
  const durationMs = Math.round(performance.now() - startTime);
355
364
  this.logger(
356
365
  "info",
357
- `[WritePTableToFs] complete (handle = ${handle}, columns = ${options.columnIndices.length}, rows = ${rowsWritten}, bytes = ${bytesWritten}, duration = ${durationMs}ms)`,
366
+ `[WritePTableToFs] complete (handle = ${handle}, columns = ${columnIndices.length}, rows = ${rowsWritten}, bytes = ${bytesWritten}, duration = ${durationMs}ms)`,
358
367
  );
359
368
  }
360
369
 
@@ -384,10 +393,15 @@ export class AbstractPFrameDriver<
384
393
  [signal, disposeSignal].filter((s): s is AbortSignal => !isNil(s)),
385
394
  );
386
395
 
387
- const headers: [number, string][] = columnIndices.map((index): [number, string] => [
388
- index,
389
- columnLabel(def.tableSpec[index]),
390
- ]);
396
+ // Caller-supplied header names win (they carry the disambiguated labels the
397
+ // UI shows); a missing or empty name falls back to the spec-derived label.
398
+ const headers: [number, string][] = columnIndices.map((index, i): [number, string] => {
399
+ const name = options.headerNames?.[i];
400
+ return [
401
+ index,
402
+ name !== undefined && name !== "" ? name : getNativeColumnLabel(def.tableSpec[index]),
403
+ ];
404
+ });
391
405
 
392
406
  // keep()/cache live outside the limiter task: on abort `run` rejects and they are
393
407
  // skipped, so an abandoned (detached) operation never caches its result nor touches
@@ -16,7 +16,7 @@ import {
16
16
  import { hashJson, PFrameInternal } from "@milaboratories/pl-model-middle-layer";
17
17
  import { RefCountPoolBase, type PoolEntry } from "@milaboratories/helpers";
18
18
  import { PFrameFactory } from "@milaboratories/pframes-rs-node";
19
- import { createPFrame as createPFrameSpec } from "@milaboratories/pframes-rs-wasm";
19
+ import { createPFrame as createPFrameSpec, type PFrame } from "@milaboratories/pf-spec";
20
20
  import { mapValues } from "es-toolkit";
21
21
  import { logPFrames } from "./logging";
22
22
 
@@ -37,7 +37,7 @@ export class PFrameHolder<TreeEntry extends JsonSerializable> implements Disposa
37
37
  * for spec-side operations: column discovery, selector resolution,
38
38
  * legacy-query lowering.
39
39
  */
40
- public readonly pFrameSpec: PFrameInternal.PFrameWasmV3;
40
+ public readonly pFrameSpec: PFrame;
41
41
  public readonly columnSpecs: Readonly<Record<PObjectId, PColumnSpec>>;
42
42
  private readonly abortController = new AbortController();
43
43
 
@@ -6,6 +6,7 @@ import {
6
6
  type PTableHandle,
7
7
  } from "@milaboratories/pl-model-common";
8
8
  import type { PFrameInternal } from "@milaboratories/pl-model-middle-layer";
9
+ import type { PFrame } from "@milaboratories/pf-spec";
9
10
  import { RefCountPoolBase, type PoolEntry } from "@milaboratories/helpers";
10
11
  import { logPFrames } from "./logging";
11
12
  import type { FullPTableDef } from "./ptable_shared";
@@ -70,7 +71,7 @@ export class PTableDefPool extends RefCountPoolBase<FullPTableDef, PTableHandle,
70
71
  public acquireFromLegacy(opts: {
71
72
  pFrameHandle: PFrameHandle;
72
73
  def: PTableDef<PObjectId>;
73
- pFrameSpec: PFrameInternal.PFrameWasmV3;
74
+ pFrameSpec: PFrame;
74
75
  }): { def: FullPTableDef; entry: PoolEntry<PTableHandle> } {
75
76
  const def = buildFullPTableDefFromLegacy(opts);
76
77
  return { def, entry: this.acquire(def) };
@@ -8,6 +8,7 @@ import {
8
8
  type PObjectId,
9
9
  } from "@milaboratories/pl-model-common";
10
10
  import type { PFrameInternal } from "@milaboratories/pl-model-middle-layer";
11
+ import type { PFrame } from "@milaboratories/pf-spec";
11
12
  import { RefCountPoolBase, type PoolEntry } from "@milaboratories/helpers";
12
13
  import { logPFrames } from "./logging";
13
14
  import type { PFramePool } from "./pframe_pool";
@@ -117,7 +118,7 @@ export class PTablePool<TreeEntry extends JsonSerializable> extends RefCountPool
117
118
  public acquireFromLegacy(opts: {
118
119
  pFrameHandle: PFrameHandle;
119
120
  def: PTableDef<PObjectId>;
120
- pFrameSpec: PFrameInternal.PFrameWasmV3;
121
+ pFrameSpec: PFrame;
121
122
  }): { def: FullPTableDef; entry: PoolEntry<PTableHandle, PTableHolder> } {
122
123
  const def = buildFullPTableDefFromLegacy(opts);
123
124
  return { def, entry: this.acquire(def) };
@@ -10,6 +10,7 @@ import type {
10
10
  } from "@milaboratories/pl-model-common";
11
11
  import { traverseQuerySpec } from "@milaboratories/pl-model-common";
12
12
  import { hashJson, PFrameInternal } from "@milaboratories/pl-model-middle-layer";
13
+ import type { LegacyQuery, PFrame } from "@milaboratories/pf-spec";
13
14
 
14
15
  /**
15
16
  * Make every inline column in a `SpecQuery` self-contained by embedding its
@@ -54,10 +55,10 @@ export function stableKeyFromFullPTableDef(data: FullPTableDef): PTableHandle {
54
55
  * WASM-spec frame.
55
56
  */
56
57
  export function lowerLegacyPTableDef(
57
- pFrameSpec: PFrameInternal.PFrameWasmV3,
58
+ pFrameSpec: PFrame,
58
59
  legacyDef: PTableDef<PObjectId>,
59
60
  ): { tableSpec: PTableColumnSpec[]; dataQuery: DataQuery } {
60
- const legacy: PFrameInternal.LegacyQuery = {
61
+ const legacy: LegacyQuery = {
61
62
  src: legacyDef.src,
62
63
  filters: [...legacyDef.partitionFilters, ...legacyDef.filters],
63
64
  sorting: legacyDef.sorting,
@@ -75,7 +76,7 @@ export function lowerLegacyPTableDef(
75
76
  export function buildFullPTableDefFromLegacy(opts: {
76
77
  pFrameHandle: PFrameHandle;
77
78
  def: PTableDef<PObjectId>;
78
- pFrameSpec: PFrameInternal.PFrameWasmV3;
79
+ pFrameSpec: PFrame;
79
80
  }): FullPTableDef {
80
81
  return {
81
82
  pFrameHandle: opts.pFrameHandle,
@@ -1,57 +0,0 @@
1
- //#region \0@oxc-project+runtime@0.137.0/helpers/esm/usingCtx.js
2
- function _usingCtx() {
3
- var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
4
- var n = Error();
5
- return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
6
- }, e = {}, n = [];
7
- function using(r, e) {
8
- if (null != e) {
9
- if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
10
- if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
11
- if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
12
- if ("function" != typeof o) throw new TypeError("Object is not disposable.");
13
- t && (o = function o() {
14
- try {
15
- t.call(e);
16
- } catch (r) {
17
- return Promise.reject(r);
18
- }
19
- }), n.push({
20
- v: e,
21
- d: o,
22
- a: r
23
- });
24
- } else r && n.push({
25
- d: e,
26
- a: r
27
- });
28
- return e;
29
- }
30
- return {
31
- e,
32
- u: using.bind(null, !1),
33
- a: using.bind(null, !0),
34
- d: function d() {
35
- var o, t = this.e, s = 0;
36
- function next() {
37
- for (; o = n.pop();) try {
38
- if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
39
- if (o.d) {
40
- var r = o.d.call(o.v);
41
- if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
42
- } else s |= 1;
43
- } catch (r) {
44
- return err(r);
45
- }
46
- if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
47
- if (t !== e) throw t;
48
- }
49
- function err(n) {
50
- return t = t !== e ? new r(n, t) : n, next();
51
- }
52
- return next();
53
- }
54
- };
55
- }
56
- //#endregion
57
- exports.default = _usingCtx;
@@ -1,23 +0,0 @@
1
- //#region \0rolldown/runtime.js
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
- key = keys[i];
11
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
- get: ((k) => from[k]).bind(null, key),
13
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
- });
15
- }
16
- return to;
17
- };
18
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
- value: mod,
20
- enumerable: true
21
- }) : target, mod));
22
- //#endregion
23
- exports.__toESM = __toESM;
@@ -1,81 +0,0 @@
1
- let _milaboratories_pl_model_common = require("@milaboratories/pl-model-common");
2
- let _milaboratories_helpers = require("@milaboratories/helpers");
3
- //#region src/csv_writer.ts
4
- /** Format a CSV/TSV header row from column specs. Line ending is CRLF. */
5
- function formatHeader(specs, separator) {
6
- return specs.map((spec) => escapeField(columnLabel(spec), separator)).join(separator) + "\r\n";
7
- }
8
- /** Format a single data row from parallel vectors. Line ending is CRLF. */
9
- function formatRow(vectors, rowIndex, separator) {
10
- return vectors.map((vector) => escapeField(serializeValue(vector, rowIndex), separator)).join(separator) + "\r\n";
11
- }
12
- async function* streamPTableRows(options) {
13
- const { pTable, columnIndices, range, chunkSize, separator, signal, specs, includeHeader, bom } = options;
14
- if (bom) yield "";
15
- if (includeHeader) yield formatHeader(columnIndices.map((index) => specs[index]), separator);
16
- if ((0, _milaboratories_helpers.isNil)(range)) return;
17
- const end = range.offset + range.length;
18
- for (let from = range.offset; from < end; from += chunkSize) {
19
- signal?.throwIfAborted();
20
- const length = Math.min(chunkSize, end - from);
21
- const subRange = {
22
- offset: from,
23
- length
24
- };
25
- const vectors = await pTable.getData(columnIndices, {
26
- range: subRange,
27
- signal
28
- });
29
- const rows = [];
30
- for (let rowIndex = 0; rowIndex < length; rowIndex++) rows.push(formatRow(vectors, rowIndex, separator));
31
- yield rows.join("");
32
- }
33
- }
34
- /** Extract a human-readable label from a PTableColumnSpec. */
35
- function columnLabel(spec) {
36
- const annotation = (0, _milaboratories_pl_model_common.readAnnotation)(spec.spec, _milaboratories_pl_model_common.Annotation.Label);
37
- return (0, _milaboratories_helpers.isNil)(annotation) ? spec.spec.name : annotation.trim();
38
- }
39
- /**
40
- * RFC 4180 field escaping.
41
- * Quote if the field contains the separator, a double-quote, CR, or LF.
42
- * Embedded `"` are doubled.
43
- */
44
- function escapeField(value, separator) {
45
- return needsQuoting(value, separator) ? "\"" + value.replace(/"/g, "\"\"") + "\"" : value;
46
- }
47
- /** Returns true when the value must be wrapped in double-quotes. */
48
- function needsQuoting(value, separator) {
49
- return value.includes(separator) || value.includes("\"") || value.includes("\r") || value.includes("\n");
50
- }
51
- /**
52
- * Serialize one cell value from a typed vector to its string representation.
53
- *
54
- * - `null` / `undefined` -> `""`
55
- * - `bigint` -> `String(x)`
56
- * - `NaN` / `+Inf` / `-Inf` -> `""`
57
- */
58
- function serializeValue(vector, rowIndex) {
59
- const rawValue = vector.data[rowIndex];
60
- if ((0, _milaboratories_helpers.isNil)(rawValue)) return "";
61
- if ((0, _milaboratories_pl_model_common.isValueNA)(vector, rowIndex)) return "";
62
- switch (vector.type) {
63
- case _milaboratories_pl_model_common.ValueType.Long: return String(rawValue);
64
- case _milaboratories_pl_model_common.ValueType.Float:
65
- case _milaboratories_pl_model_common.ValueType.Double: {
66
- const numeric = rawValue;
67
- return Number.isNaN(numeric) || !Number.isFinite(numeric) ? "" : String(numeric);
68
- }
69
- case _milaboratories_pl_model_common.ValueType.Int: return String(rawValue);
70
- case _milaboratories_pl_model_common.ValueType.String: return rawValue;
71
- case _milaboratories_pl_model_common.ValueType.Bytes: return "";
72
- default: return String(rawValue);
73
- }
74
- }
75
- //#endregion
76
- exports.columnLabel = columnLabel;
77
- exports.formatHeader = formatHeader;
78
- exports.formatRow = formatRow;
79
- exports.streamPTableRows = streamPTableRows;
80
-
81
- //# sourceMappingURL=csv_writer.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"csv_writer.cjs","names":["Annotation","ValueType"],"sources":["../src/csv_writer.ts"],"sourcesContent":["import {\n Annotation,\n isValueNA,\n readAnnotation,\n ValueType,\n type PTableColumnSpec,\n type PTableVector,\n type TableRange,\n} from \"@milaboratories/pl-model-common\";\nimport { isNil } from \"@milaboratories/helpers\";\n\n/** Minimal subset of PTable required by streamPTableRows. */\nexport interface PTableDataSource {\n getData(\n columnIndices: number[],\n options?: { range?: TableRange; signal?: AbortSignal },\n ): Promise<PTableVector[]>;\n}\n\n// ── Public API (high-level → low-level) ──────────────────────────────\n\n/** Format a CSV/TSV header row from column specs. Line ending is CRLF. */\nexport function formatHeader(specs: PTableColumnSpec[], separator: string): string {\n return specs.map((spec) => escapeField(columnLabel(spec), separator)).join(separator) + \"\\r\\n\";\n}\n\n/** Format a single data row from parallel vectors. Line ending is CRLF. */\nexport function formatRow(vectors: PTableVector[], rowIndex: number, separator: string): string {\n return (\n vectors\n .map((vector) => escapeField(serializeValue(vector, rowIndex), separator))\n .join(separator) + \"\\r\\n\"\n );\n}\n\n/**\n * Async generator that streams CSV/TSV content chunk by chunk.\n *\n * The caller is responsible for providing a concrete `range` (already clipped\n * to the table shape). When `range` is undefined the generator does nothing\n * beyond emitting an optional BOM and header.\n */\nexport interface StreamPTableRowsOptions {\n pTable: PTableDataSource;\n specs: PTableColumnSpec[];\n columnIndices: number[];\n range?: TableRange;\n chunkSize: number;\n separator: string;\n includeHeader: boolean;\n bom: boolean;\n signal?: AbortSignal;\n}\nexport async function* streamPTableRows(options: StreamPTableRowsOptions): AsyncIterable<string> {\n const { pTable, columnIndices, range, chunkSize, separator, signal, specs, includeHeader, bom } =\n options;\n\n if (bom) {\n yield \"\\uFEFF\";\n }\n\n if (includeHeader) {\n const selectedSpecs = columnIndices.map((index) => specs[index]);\n yield formatHeader(selectedSpecs, separator);\n }\n\n if (isNil(range)) {\n return;\n }\n\n const end = range.offset + range.length;\n\n for (let from = range.offset; from < end; from += chunkSize) {\n signal?.throwIfAborted();\n\n const length = Math.min(chunkSize, end - from);\n const subRange: TableRange = { offset: from, length };\n\n const vectors = await pTable.getData(columnIndices, { range: subRange, signal });\n\n const rows: string[] = [];\n for (let rowIndex = 0; rowIndex < length; rowIndex++) {\n rows.push(formatRow(vectors, rowIndex, separator));\n }\n yield rows.join(\"\");\n }\n}\n\n// ── Helpers (low-level) ──────────────────────────────────────────────\n\n/** Extract a human-readable label from a PTableColumnSpec. */\nexport function columnLabel(spec: PTableColumnSpec): string {\n const annotation = readAnnotation(spec.spec, Annotation.Label);\n return isNil(annotation) ? spec.spec.name : annotation.trim();\n}\n\n/**\n * RFC 4180 field escaping.\n * Quote if the field contains the separator, a double-quote, CR, or LF.\n * Embedded `\"` are doubled.\n */\nfunction escapeField(value: string, separator: string): string {\n return needsQuoting(value, separator) ? '\"' + value.replace(/\"/g, '\"\"') + '\"' : value;\n}\n\n/** Returns true when the value must be wrapped in double-quotes. */\nfunction needsQuoting(value: string, separator: string): boolean {\n return (\n value.includes(separator) || value.includes('\"') || value.includes(\"\\r\") || value.includes(\"\\n\")\n );\n}\n\n/**\n * Serialize one cell value from a typed vector to its string representation.\n *\n * - `null` / `undefined` -> `\"\"`\n * - `bigint` -> `String(x)`\n * - `NaN` / `+Inf` / `-Inf` -> `\"\"`\n */\nfunction serializeValue(vector: PTableVector, rowIndex: number): string {\n const rawValue = vector.data[rowIndex];\n\n if (isNil(rawValue)) {\n return \"\";\n }\n\n if (isValueNA(vector, rowIndex)) {\n return \"\";\n }\n\n switch (vector.type) {\n case ValueType.Long: {\n // BigInt64Array element — may be stored as bigint\n return String(rawValue);\n }\n case ValueType.Float:\n case ValueType.Double: {\n const numeric = rawValue as number;\n return Number.isNaN(numeric) || !Number.isFinite(numeric) ? \"\" : String(numeric);\n }\n case ValueType.Int: {\n return String(rawValue);\n }\n case ValueType.String: {\n return rawValue as string;\n }\n case ValueType.Bytes: {\n return \"\";\n }\n default: {\n return String(rawValue);\n }\n }\n}\n"],"mappings":";;;;AAsBA,SAAgB,aAAa,OAA2B,WAA2B;CACjF,OAAO,MAAM,KAAK,SAAS,YAAY,YAAY,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI;AAC1F;;AAGA,SAAgB,UAAU,SAAyB,UAAkB,WAA2B;CAC9F,OACE,QACG,KAAK,WAAW,YAAY,eAAe,QAAQ,QAAQ,GAAG,SAAS,CAAC,CAAC,CACzE,KAAK,SAAS,IAAI;AAEzB;AAoBA,gBAAuB,iBAAiB,SAAyD;CAC/F,MAAM,EAAE,QAAQ,eAAe,OAAO,WAAW,WAAW,QAAQ,OAAO,eAAe,QACxF;CAEF,IAAI,KACF,MAAM;CAGR,IAAI,eAEF,MAAM,aADgB,cAAc,KAAK,UAAU,MAAM,MAC1B,GAAG,SAAS;CAG7C,KAAA,GAAA,wBAAA,MAAA,CAAU,KAAK,GACb;CAGF,MAAM,MAAM,MAAM,SAAS,MAAM;CAEjC,KAAK,IAAI,OAAO,MAAM,QAAQ,OAAO,KAAK,QAAQ,WAAW;EAC3D,QAAQ,eAAe;EAEvB,MAAM,SAAS,KAAK,IAAI,WAAW,MAAM,IAAI;EAC7C,MAAM,WAAuB;GAAE,QAAQ;GAAM;EAAO;EAEpD,MAAM,UAAU,MAAM,OAAO,QAAQ,eAAe;GAAE,OAAO;GAAU;EAAO,CAAC;EAE/E,MAAM,OAAiB,CAAC;EACxB,KAAK,IAAI,WAAW,GAAG,WAAW,QAAQ,YACxC,KAAK,KAAK,UAAU,SAAS,UAAU,SAAS,CAAC;EAEnD,MAAM,KAAK,KAAK,EAAE;CACpB;AACF;;AAKA,SAAgB,YAAY,MAAgC;CAC1D,MAAM,cAAA,GAAA,gCAAA,eAAA,CAA4B,KAAK,MAAMA,gCAAAA,WAAW,KAAK;CAC7D,QAAA,GAAA,wBAAA,MAAA,CAAa,UAAU,IAAI,KAAK,KAAK,OAAO,WAAW,KAAK;AAC9D;;;;;;AAOA,SAAS,YAAY,OAAe,WAA2B;CAC7D,OAAO,aAAa,OAAO,SAAS,IAAI,OAAM,MAAM,QAAQ,MAAM,MAAI,IAAI,OAAM;AAClF;;AAGA,SAAS,aAAa,OAAe,WAA4B;CAC/D,OACE,MAAM,SAAS,SAAS,KAAK,MAAM,SAAS,IAAG,KAAK,MAAM,SAAS,IAAI,KAAK,MAAM,SAAS,IAAI;AAEnG;;;;;;;;AASA,SAAS,eAAe,QAAsB,UAA0B;CACtE,MAAM,WAAW,OAAO,KAAK;CAE7B,KAAA,GAAA,wBAAA,MAAA,CAAU,QAAQ,GAChB,OAAO;CAGT,KAAA,GAAA,gCAAA,UAAA,CAAc,QAAQ,QAAQ,GAC5B,OAAO;CAGT,QAAQ,OAAO,MAAf;EACE,KAAKC,gCAAAA,UAAU,MAEb,OAAO,OAAO,QAAQ;EAExB,KAAKA,gCAAAA,UAAU;EACf,KAAKA,gCAAAA,UAAU,QAAQ;GACrB,MAAM,UAAU;GAChB,OAAO,OAAO,MAAM,OAAO,KAAK,CAAC,OAAO,SAAS,OAAO,IAAI,KAAK,OAAO,OAAO;EACjF;EACA,KAAKA,gCAAAA,UAAU,KACb,OAAO,OAAO,QAAQ;EAExB,KAAKA,gCAAAA,UAAU,QACb,OAAO;EAET,KAAKA,gCAAAA,UAAU,OACb,OAAO;EAET,SACE,OAAO,OAAO,QAAQ;CAE1B;AACF"}
@@ -1,23 +0,0 @@
1
- let _milaboratories_pl_model_common = require("@milaboratories/pl-model-common");
2
- //#region src/data_info_helpers.ts
3
- function makeJsonDataInfo(spec, data) {
4
- const keyLength = spec.axesSpec.length;
5
- const jsonData = {};
6
- for (const { key, val } of data) {
7
- if (key.length !== keyLength) {
8
- const error = new _milaboratories_pl_model_common.PFrameDriverError(`Inconsistent inline column key length`);
9
- error.cause = /* @__PURE__ */ new Error(`Inline column key length ${key.length} differs from axes count ${keyLength}`);
10
- throw error;
11
- }
12
- jsonData[JSON.stringify(key)] = val;
13
- }
14
- return {
15
- type: "Json",
16
- keyLength,
17
- data: jsonData
18
- };
19
- }
20
- //#endregion
21
- exports.makeJsonDataInfo = makeJsonDataInfo;
22
-
23
- //# sourceMappingURL=data_info_helpers.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"data_info_helpers.cjs","names":["PFrameDriverError"],"sources":["../src/data_info_helpers.ts"],"sourcesContent":["import {\n PFrameDriverError,\n type PColumnSpec,\n type PColumnValues,\n type JsonDataInfo,\n type PColumnValue,\n} from \"@milaboratories/pl-model-common\";\n\nexport function makeJsonDataInfo(spec: PColumnSpec, data: PColumnValues): JsonDataInfo {\n const keyLength = spec.axesSpec.length;\n const jsonData: Record<string, PColumnValue> = {};\n for (const { key, val } of data) {\n if (key.length !== keyLength) {\n const error = new PFrameDriverError(`Inconsistent inline column key length`);\n error.cause = new Error(\n `Inline column key length ${key.length} differs from axes count ${keyLength}`,\n );\n throw error;\n }\n jsonData[JSON.stringify(key)] = val;\n }\n\n return {\n type: \"Json\",\n keyLength,\n data: jsonData,\n };\n}\n"],"mappings":";;AAQA,SAAgB,iBAAiB,MAAmB,MAAmC;CACrF,MAAM,YAAY,KAAK,SAAS;CAChC,MAAM,WAAyC,CAAC;CAChD,KAAK,MAAM,EAAE,KAAK,SAAS,MAAM;EAC/B,IAAI,IAAI,WAAW,WAAW;GAC5B,MAAM,QAAQ,IAAIA,gCAAAA,kBAAkB,uCAAuC;GAC3E,MAAM,wBAAQ,IAAI,MAChB,4BAA4B,IAAI,OAAO,2BAA2B,WACpE;GACA,MAAM;EACR;EACA,SAAS,KAAK,UAAU,GAAG,KAAK;CAClC;CAEA,OAAO;EACL,MAAM;EACN;EACA,MAAM;CACR;AACF"}