@ai-matrx/alchemy 0.18.0 → 0.19.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.
@@ -1,11 +1,13 @@
1
1
  import { Json } from '@ai-matrx/alchemy/operate';
2
+ import { DelimitedParseOptions, DelimitedParseError } from '@ai-matrx/kit/delimited';
3
+ export { ParsedDelimitedCell as DelimitedCell, DelimitedMeta, DelimitedParseError, DelimitedParseOptions, DelimitedResult, parseDelimited } from '@ai-matrx/kit/delimited';
2
4
 
3
5
  /**
4
6
  * `@ai-matrx/alchemy/operate/read` — the one parse door (ALC-19 T4–T12). Write has `buildFile`; read has `readFile`.
5
7
  *
6
8
  * One engine per format across read and write:
7
- * - CSV / TSV / delimited text: THIS module's parser (no library), the reverse of the `csv`/`tsv`
8
- * serializers in `./core`. `parseDelimited` is Papa Parse–compatible in the options the platform
9
+ * - CSV / TSV / delimited text: kit's `parseDelimited` (no library; it sits in `@ai-matrx/kit/delimited`
10
+ * beside the one writer so packages below alchemy read through it too), re-exported here. `parseDelimited` is Papa Parse–compatible in the options the platform
9
11
  * uses (auto delimiter + line ending, quotes, `skipEmptyLines` true | "greedy", `header`,
10
12
  * `transformHeader`, `dynamicTyping`) and answers the same `{ data, errors, meta }` shape and
11
13
  * error codes, so a repointed importer keeps its behaviour.
@@ -14,52 +16,6 @@ import { Json } from '@ai-matrx/alchemy/operate';
14
16
  * (dates, numbers, formulas as SheetJS reads them) reads it through `readWorkbook` too.
15
17
  */
16
18
 
17
- type DelimitedParseError = {
18
- type: "Quotes" | "Delimiter" | "FieldMismatch";
19
- code: "MissingQuotes" | "InvalidQuotes" | "UndetectableDelimiter" | "TooFewFields" | "TooManyFields";
20
- message: string;
21
- /** 0-based index of the data row the error belongs to (absent for whole-file errors). */
22
- row?: number;
23
- };
24
- type DelimitedParseOptions = {
25
- /** Field delimiter. Absent → guessed from `,` `\t` `|` `;` and the ASCII record/unit separators; `,` when none fits. */
26
- delimiter?: string;
27
- /** true: an exactly empty line is dropped; "greedy": a line of only whitespace is dropped too. */
28
- skipEmptyLines?: boolean | "greedy";
29
- /** Numbers, `true`/`false` and ISO date-times become typed values; an empty field becomes null. */
30
- dynamicTyping?: boolean;
31
- /** Header mode only: rename a header (e.g. trim + lowercase). */
32
- transformHeader?(header: string, index: number): string;
33
- };
34
- type DelimitedCell = string | number | boolean | Date | null;
35
- type DelimitedMeta = {
36
- delimiter: string;
37
- linebreak: string;
38
- fields?: string[];
39
- };
40
- type DelimitedResult<T> = {
41
- data: T[];
42
- errors: DelimitedParseError[];
43
- meta: DelimitedMeta;
44
- };
45
- /** Parse delimited text into rows of fields (header row first). */
46
- declare function parseDelimited(text: string, options?: DelimitedParseOptions & {
47
- header?: false;
48
- dynamicTyping?: false;
49
- }): DelimitedResult<string[]>;
50
- declare function parseDelimited(text: string, options: DelimitedParseOptions & {
51
- header?: false;
52
- dynamicTyping: true;
53
- }): DelimitedResult<DelimitedCell[]>;
54
- /** Parse delimited text into records keyed by the (transformed) header row. Extra fields land in `__parsed_extra`. */
55
- declare function parseDelimited(text: string, options: DelimitedParseOptions & {
56
- header: true;
57
- dynamicTyping?: false;
58
- }): DelimitedResult<Record<string, string>>;
59
- declare function parseDelimited(text: string, options: DelimitedParseOptions & {
60
- header: true;
61
- dynamicTyping: true;
62
- }): DelimitedResult<Record<string, DelimitedCell>>;
63
19
  /**
64
20
  * Delimited text from a header row + body rows, through the SAME `csv`/`tsv` serializer the
65
21
  * Alchemy menu's copy and download use (quoting, nested values as compact JSON, `null` empty).
@@ -172,4 +128,4 @@ declare function fileFormatOf(name: string | undefined): string;
172
128
  */
173
129
  declare function readFile(input: ReadInput, options?: ReadFileOptions): Promise<TabularFile>;
174
130
 
175
- export { type DelimitedCell, type DelimitedMeta, type DelimitedParseError, type DelimitedParseOptions, type DelimitedResult, type ReadFileOptions, type ReadInput, type TabularFile, type TabularFormat, type TabularSheet, UnreadableFormatError, type Workbook, type WorkbookCell, type WorkbookCellAddress, type WorkbookCellJson, type WorkbookCellRange, type WorkbookGridOptions, type WorkbookJson, type WorkbookReadOptions, type WorkbookRecordOptions, type WorkbookSheet, fileFormatOf, parseDelimited, readFile, readWorkbook, toDelimitedText };
131
+ export { type ReadFileOptions, type ReadInput, type TabularFile, type TabularFormat, type TabularSheet, UnreadableFormatError, type Workbook, type WorkbookCell, type WorkbookCellAddress, type WorkbookCellJson, type WorkbookCellRange, type WorkbookGridOptions, type WorkbookJson, type WorkbookReadOptions, type WorkbookRecordOptions, type WorkbookSheet, fileFormatOf, readFile, readWorkbook, toDelimitedText };
@@ -1,11 +1,13 @@
1
1
  import { Json } from '@ai-matrx/alchemy/operate';
2
+ import { DelimitedParseOptions, DelimitedParseError } from '@ai-matrx/kit/delimited';
3
+ export { ParsedDelimitedCell as DelimitedCell, DelimitedMeta, DelimitedParseError, DelimitedParseOptions, DelimitedResult, parseDelimited } from '@ai-matrx/kit/delimited';
2
4
 
3
5
  /**
4
6
  * `@ai-matrx/alchemy/operate/read` — the one parse door (ALC-19 T4–T12). Write has `buildFile`; read has `readFile`.
5
7
  *
6
8
  * One engine per format across read and write:
7
- * - CSV / TSV / delimited text: THIS module's parser (no library), the reverse of the `csv`/`tsv`
8
- * serializers in `./core`. `parseDelimited` is Papa Parse–compatible in the options the platform
9
+ * - CSV / TSV / delimited text: kit's `parseDelimited` (no library; it sits in `@ai-matrx/kit/delimited`
10
+ * beside the one writer so packages below alchemy read through it too), re-exported here. `parseDelimited` is Papa Parse–compatible in the options the platform
9
11
  * uses (auto delimiter + line ending, quotes, `skipEmptyLines` true | "greedy", `header`,
10
12
  * `transformHeader`, `dynamicTyping`) and answers the same `{ data, errors, meta }` shape and
11
13
  * error codes, so a repointed importer keeps its behaviour.
@@ -14,52 +16,6 @@ import { Json } from '@ai-matrx/alchemy/operate';
14
16
  * (dates, numbers, formulas as SheetJS reads them) reads it through `readWorkbook` too.
15
17
  */
16
18
 
17
- type DelimitedParseError = {
18
- type: "Quotes" | "Delimiter" | "FieldMismatch";
19
- code: "MissingQuotes" | "InvalidQuotes" | "UndetectableDelimiter" | "TooFewFields" | "TooManyFields";
20
- message: string;
21
- /** 0-based index of the data row the error belongs to (absent for whole-file errors). */
22
- row?: number;
23
- };
24
- type DelimitedParseOptions = {
25
- /** Field delimiter. Absent → guessed from `,` `\t` `|` `;` and the ASCII record/unit separators; `,` when none fits. */
26
- delimiter?: string;
27
- /** true: an exactly empty line is dropped; "greedy": a line of only whitespace is dropped too. */
28
- skipEmptyLines?: boolean | "greedy";
29
- /** Numbers, `true`/`false` and ISO date-times become typed values; an empty field becomes null. */
30
- dynamicTyping?: boolean;
31
- /** Header mode only: rename a header (e.g. trim + lowercase). */
32
- transformHeader?(header: string, index: number): string;
33
- };
34
- type DelimitedCell = string | number | boolean | Date | null;
35
- type DelimitedMeta = {
36
- delimiter: string;
37
- linebreak: string;
38
- fields?: string[];
39
- };
40
- type DelimitedResult<T> = {
41
- data: T[];
42
- errors: DelimitedParseError[];
43
- meta: DelimitedMeta;
44
- };
45
- /** Parse delimited text into rows of fields (header row first). */
46
- declare function parseDelimited(text: string, options?: DelimitedParseOptions & {
47
- header?: false;
48
- dynamicTyping?: false;
49
- }): DelimitedResult<string[]>;
50
- declare function parseDelimited(text: string, options: DelimitedParseOptions & {
51
- header?: false;
52
- dynamicTyping: true;
53
- }): DelimitedResult<DelimitedCell[]>;
54
- /** Parse delimited text into records keyed by the (transformed) header row. Extra fields land in `__parsed_extra`. */
55
- declare function parseDelimited(text: string, options: DelimitedParseOptions & {
56
- header: true;
57
- dynamicTyping?: false;
58
- }): DelimitedResult<Record<string, string>>;
59
- declare function parseDelimited(text: string, options: DelimitedParseOptions & {
60
- header: true;
61
- dynamicTyping: true;
62
- }): DelimitedResult<Record<string, DelimitedCell>>;
63
19
  /**
64
20
  * Delimited text from a header row + body rows, through the SAME `csv`/`tsv` serializer the
65
21
  * Alchemy menu's copy and download use (quoting, nested values as compact JSON, `null` empty).
@@ -172,4 +128,4 @@ declare function fileFormatOf(name: string | undefined): string;
172
128
  */
173
129
  declare function readFile(input: ReadInput, options?: ReadFileOptions): Promise<TabularFile>;
174
130
 
175
- export { type DelimitedCell, type DelimitedMeta, type DelimitedParseError, type DelimitedParseOptions, type DelimitedResult, type ReadFileOptions, type ReadInput, type TabularFile, type TabularFormat, type TabularSheet, UnreadableFormatError, type Workbook, type WorkbookCell, type WorkbookCellAddress, type WorkbookCellJson, type WorkbookCellRange, type WorkbookGridOptions, type WorkbookJson, type WorkbookReadOptions, type WorkbookRecordOptions, type WorkbookSheet, fileFormatOf, parseDelimited, readFile, readWorkbook, toDelimitedText };
131
+ export { type ReadFileOptions, type ReadInput, type TabularFile, type TabularFormat, type TabularSheet, UnreadableFormatError, type Workbook, type WorkbookCell, type WorkbookCellAddress, type WorkbookCellJson, type WorkbookCellRange, type WorkbookGridOptions, type WorkbookJson, type WorkbookReadOptions, type WorkbookRecordOptions, type WorkbookSheet, fileFormatOf, readFile, readWorkbook, toDelimitedText };
@@ -1,6 +1,2 @@
1
- import{createDraft,directSource,projectSnapshot,serialize}from"@ai-matrx/alchemy/operate";var RECORD_SEP="",UNIT_SEP="",GUESS_DELIMITERS=[","," ","|",";",RECORD_SEP,UNIT_SEP],FLOAT=/^\s*-?(\d+\.?|\.\d+|\d+\.\d+)([eE][-+]?\d+)?\s*$/,ISO_DATE=/^((\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)))$/;function guessLinebreak(rawText){let text=rawText.includes('"')?rawText.replace(/"[^"]*"/g,'""'):rawText,crlf=text.indexOf(`\r
2
- `),lf=text.indexOf(`
3
- `),cr=text.indexOf("\r");return crlf!==-1&&crlf===cr?`\r
4
- `:cr!==-1&&(lf===-1||cr<lf)?"\r":`
5
- `}function tokenize(text,delimiter,linebreak,preview=1/0){let rows=[],errors=[];if(text==="")return{rows,errors};let row=[],i=0,n=text.length,dl=delimiter.length,ll=linebreak.length;for(;i<=n&&rows.length<preview;){if(text.startsWith('"',i)){let value="",j=i+1,closed=!1;for(;j<n;){let q=text.indexOf('"',j);if(q===-1)break;if(value+=text.slice(j,q),text[q+1]==='"'){value+='"',j=q+2;continue}let after=q+1;if(after>=n||text.startsWith(delimiter,after)||text.startsWith(linebreak,after)){closed=!0,j=after;break}errors.push({type:"Quotes",code:"InvalidQuotes",message:"Trailing quote on quoted field is malformed",row:rows.length}),value+='"',j=after}if(!closed)return errors.push({type:"Quotes",code:"MissingQuotes",message:"Quoted field unterminated",row:rows.length}),value+=text.slice(j),row.push(value),rows.push(row),{rows,errors};if(row.push(value),i=j,i>=n){rows.push(row);break}if(text.startsWith(delimiter,i)){if(i+=dl,i>=n){row.push(""),rows.push(row);break}continue}if(rows.push(row),row=[],i+=ll,i>=n){rows.push([""]);break}continue}let nextDelim=text.indexOf(delimiter,i),nextBreak=text.indexOf(linebreak,i);if(nextDelim!==-1&&(nextBreak===-1||nextDelim<nextBreak)){if(row.push(text.slice(i,nextDelim)),i=nextDelim+dl,i>=n){row.push(""),rows.push(row);break}continue}if(nextBreak!==-1){if(row.push(text.slice(i,nextBreak)),rows.push(row),row=[],i=nextBreak+ll,i>=n){rows.push([""]);break}continue}row.push(text.slice(i)),rows.push(row);break}return{rows,errors}}var isEmptyRow=(row,mode)=>mode==="greedy"?row.join("").trim()==="":row.length===1&&row[0].length===0;function guessDelimiter(text,linebreak,skip){let best,bestDelta,bestAvg;for(let delim of GUESS_DELIMITERS){let{rows}=tokenize(text,delim,linebreak,10),delta=0,avg=0,empty=0,prev;for(let row of rows){if(skip&&isEmptyRow(row,skip)){empty++;continue}let count=row.length;if(avg+=count,prev===void 0){prev=count;continue}count>0&&(delta+=Math.abs(count-prev),prev=count)}rows.length>0&&(avg/=rows.length-empty),(bestDelta===void 0||delta<=bestDelta)&&(bestAvg===void 0||avg>bestAvg)&&avg>1.99&&(bestDelta=delta,bestAvg=avg,best=delim)}return{delimiter:best??",",successful:best!==void 0}}function typed(value){if(value==="true"||value==="TRUE")return!0;if(value==="false"||value==="FALSE")return!1;if(FLOAT.test(value)){let num=parseFloat(value);return num>-(2**53)&&num<2**53?num:value}return ISO_DATE.test(value)?new Date(value):value===""?null:value}function parseDelimited(text,options={}){let input=text.charCodeAt(0)===65279?text.slice(1):text,linebreak=guessLinebreak(input),errors=[],delimiter=options.delimiter;if(!delimiter){let guess=guessDelimiter(input,linebreak,options.skipEmptyLines);delimiter=guess.delimiter,guess.successful||errors.push({type:"Delimiter",code:"UndetectableDelimiter",message:"Unable to auto-detect delimiting character; defaulted to ','"})}let tokens=tokenize(input,delimiter,linebreak);errors.unshift(...tokens.errors);let rows=tokens.rows;options.skipEmptyLines&&(rows=rows.filter(row=>!isEmptyRow(row,options.skipEmptyLines)));let meta={delimiter,linebreak};if(!options.header)return{data:options.dynamicTyping?rows.map(row=>row.map(typed)):rows,errors,meta};let[head=[],...body]=rows,seen=new Map,fields=head.map((raw,index)=>{let name=options.transformHeader?options.transformHeader(raw,index):raw,count=seen.get(name)??0;return seen.set(name,count+1),count===0?name:`${name}_${count}`});return meta.fields=fields,{data:body.map((row,index)=>{let record={};return row.forEach((value,j)=>{let cell=options.dynamicTyping?typed(value):value;j>=fields.length?(record.__parsed_extra??=[]).push(cell):record[fields[j]]=cell}),row.length>fields.length?errors.push({type:"FieldMismatch",code:"TooManyFields",message:`Too many fields: expected ${fields.length} fields but parsed ${row.length}`,row:index}):row.length<fields.length&&errors.push({type:"FieldMismatch",code:"TooFewFields",message:`Too few fields: expected ${fields.length} fields but parsed ${row.length}`,row:index}),record}),errors,meta}}function toDelimitedText(header,rows,options={}){let columns=header.map((label,i)=>({id:`c${i}`,label,path:`/c${i}`,visible:!0})),record=row=>{let out={};return header.forEach((label,i)=>{let v=Array.isArray(row)?row[i]:row[label];v!==void 0&&(out[`c${i}`]=v)}),out},captured=directSource({kind:"rows",columns,rows:rows.map(record)}),draft=createDraft(projectSnapshot(captured.snapshot,captured.projection));return serialize(draft,options.format??"csv",options.spreadsheetSafe===void 0?{}:{spreadsheetSafe:options.spreadsheetSafe}).plainText}async function bytesOf(input){return typeof input=="string"?new TextEncoder().encode(input):input instanceof Uint8Array?input:input instanceof ArrayBuffer?new Uint8Array(input):new Uint8Array(await input.arrayBuffer())}async function readWorkbook(input,options={}){let bytes=await bytesOf(input),{readWorkbookBytes}=await import("@ai-matrx/alchemy/operate/formats/xlsx");return readWorkbookBytes(bytes,options)}var WORKBOOK_FORMATS=new Set(["xlsx","xls","ods","xlsm","xlsb"]),DELIMITED_FORMATS=new Set(["csv","tsv","txt","tab"]),UnreadableFormatError=class extends Error{format;constructor(format){super(`A "${format}" file is not a table we can read. Choose a CSV, TSV, or Excel file.`),this.format=format,this.name="UnreadableFormatError"}};function fileFormatOf(name){let m=/\.([a-z0-9]+)$/i.exec(name??"");return m?m[1].toLowerCase():""}async function readFile(input,options={}){let name=options.filename??(typeof Blob<"u"&&input instanceof Blob&&"name"in input?String(input.name):void 0),format=(options.format??fileFormatOf(name))||(typeof input=="string"?"csv":"");if(WORKBOOK_FORMATS.has(format)){let book=await readWorkbook(input,options.workbook);return{format:format==="xlsm"||format==="xlsb"?"xlsx":format,sheets:book.sheets.map(s=>({name:s.name,grid:s.grid(options.grid),merges:s.merges})),errors:[]}}if(!DELIMITED_FORMATS.has(format))throw new UnreadableFormatError(format||"unknown");let text=typeof input=="string"?input:new TextDecoder("utf-8").decode(await bytesOf(input)),parsed=parseDelimited(text,{...options,header:!1,...options.delimiter?{}:format==="tsv"||format==="tab"?{delimiter:" "}:{}});return{format:format==="tab"?"tsv":format,sheets:[{name:name?.replace(/\.[^.]+$/,"")||"Sheet1",grid:parsed.data,merges:[]}],errors:parsed.errors,delimiter:parsed.meta.delimiter}}export{UnreadableFormatError,fileFormatOf,parseDelimited,readFile,readWorkbook,toDelimitedText};
1
+ import{createDraft,directSource,projectSnapshot,serialize}from"@ai-matrx/alchemy/operate";import{parseDelimited}from"@ai-matrx/kit/delimited";import{parseDelimited as parseDelimited2}from"@ai-matrx/kit/delimited";function toDelimitedText(header,rows,options={}){let columns=header.map((label,i)=>({id:`c${i}`,label,path:`/c${i}`,visible:!0})),record=row=>{let out={};return header.forEach((label,i)=>{let v=Array.isArray(row)?row[i]:row[label];v!==void 0&&(out[`c${i}`]=v)}),out},captured=directSource({kind:"rows",columns,rows:rows.map(record)}),draft=createDraft(projectSnapshot(captured.snapshot,captured.projection));return serialize(draft,options.format??"csv",options.spreadsheetSafe===void 0?{}:{spreadsheetSafe:options.spreadsheetSafe}).plainText}async function bytesOf(input){return typeof input=="string"?new TextEncoder().encode(input):input instanceof Uint8Array?input:input instanceof ArrayBuffer?new Uint8Array(input):new Uint8Array(await input.arrayBuffer())}async function readWorkbook(input,options={}){let bytes=await bytesOf(input),{readWorkbookBytes}=await import("@ai-matrx/alchemy/operate/formats/xlsx");return readWorkbookBytes(bytes,options)}var WORKBOOK_FORMATS=new Set(["xlsx","xls","ods","xlsm","xlsb"]),DELIMITED_FORMATS=new Set(["csv","tsv","txt","tab"]),UnreadableFormatError=class extends Error{format;constructor(format){super(`A "${format}" file is not a table we can read. Choose a CSV, TSV, or Excel file.`),this.format=format,this.name="UnreadableFormatError"}};function fileFormatOf(name){let m=/\.([a-z0-9]+)$/i.exec(name??"");return m?m[1].toLowerCase():""}async function readFile(input,options={}){let name=options.filename??(typeof Blob<"u"&&input instanceof Blob&&"name"in input?String(input.name):void 0),format=(options.format??fileFormatOf(name))||(typeof input=="string"?"csv":"");if(WORKBOOK_FORMATS.has(format)){let book=await readWorkbook(input,options.workbook);return{format:format==="xlsm"||format==="xlsb"?"xlsx":format,sheets:book.sheets.map(s=>({name:s.name,grid:s.grid(options.grid),merges:s.merges})),errors:[]}}if(!DELIMITED_FORMATS.has(format))throw new UnreadableFormatError(format||"unknown");let text=typeof input=="string"?input:new TextDecoder("utf-8").decode(await bytesOf(input)),parsed=parseDelimited2(text,{...options,header:!1,...options.delimiter?{}:format==="tsv"||format==="tab"?{delimiter:" "}:{}});return{format:format==="tab"?"tsv":format,sheets:[{name:name?.replace(/\.[^.]+$/,"")||"Sheet1",grid:parsed.data,merges:[]}],errors:parsed.errors,delimiter:parsed.meta.delimiter}}export{UnreadableFormatError,fileFormatOf,parseDelimited,readFile,readWorkbook,toDelimitedText};
6
2
  //# sourceMappingURL=operate-read.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/operate/read.ts"],"sourcesContent":["/**\n * `@ai-matrx/alchemy/operate/read` — the one parse door (ALC-19 T4–T12). Write has `buildFile`; read has `readFile`.\n *\n * One engine per format across read and write:\n * - CSV / TSV / delimited text: THIS module's parser (no library), the reverse of the `csv`/`tsv`\n * serializers in `./core`. `parseDelimited` is Papa Parse–compatible in the options the platform\n * uses (auto delimiter + line ending, quotes, `skipEmptyLines` true | \"greedy\", `header`,\n * `transformHeader`, `dynamicTyping`) and answers the same `{ data, errors, meta }` shape and\n * error codes, so a repointed importer keeps its behaviour.\n * - XLSX / XLS / ODS: SheetJS, the library the `xlsx` writer uses, loaded on first use from the\n * `/operate/formats/xlsx` leaf (`readWorkbook`). A caller that needs spreadsheet typing of a CSV\n * (dates, numbers, formulas as SheetJS reads them) reads it through `readWorkbook` too.\n */\n\n// Through the PUBLIC `/operate` subpath, so this entry shares the transfer core at runtime instead of bundling a copy.\nimport { createDraft, directSource, projectSnapshot, serialize, type Column, type Json } from \"@ai-matrx/alchemy/operate\";\n\n// ─── Delimited text ───────────────────────────────────────────────────────────────────────────────\n\nexport type DelimitedParseError = {\n type: \"Quotes\" | \"Delimiter\" | \"FieldMismatch\";\n code: \"MissingQuotes\" | \"InvalidQuotes\" | \"UndetectableDelimiter\" | \"TooFewFields\" | \"TooManyFields\";\n message: string;\n /** 0-based index of the data row the error belongs to (absent for whole-file errors). */\n row?: number;\n};\n\nexport type DelimitedParseOptions = {\n /** Field delimiter. Absent → guessed from `,` `\\t` `|` `;` and the ASCII record/unit separators; `,` when none fits. */\n delimiter?: string;\n /** true: an exactly empty line is dropped; \"greedy\": a line of only whitespace is dropped too. */\n skipEmptyLines?: boolean | \"greedy\";\n /** Numbers, `true`/`false` and ISO date-times become typed values; an empty field becomes null. */\n dynamicTyping?: boolean;\n /** Header mode only: rename a header (e.g. trim + lowercase). */\n transformHeader?(header: string, index: number): string;\n};\n\nexport type DelimitedCell = string | number | boolean | Date | null;\nexport type DelimitedMeta = { delimiter: string; linebreak: string; fields?: string[] };\nexport type DelimitedResult<T> = { data: T[]; errors: DelimitedParseError[]; meta: DelimitedMeta };\n\nconst RECORD_SEP = String.fromCharCode(30);\nconst UNIT_SEP = String.fromCharCode(31);\nconst GUESS_DELIMITERS = [\",\", \"\\t\", \"|\", \";\", RECORD_SEP, UNIT_SEP];\nconst FLOAT = /^\\s*-?(\\d+\\.?|\\.\\d+|\\d+\\.\\d+)([eE][-+]?\\d+)?\\s*$/;\nconst ISO_DATE =\n /^((\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z)))$/;\n\nfunction guessLinebreak(rawText: string): string {\n // Quoted cells may hold any line break; only the ones between records say what the file uses.\n const text = rawText.includes('\"') ? rawText.replace(/\"[^\"]*\"/g, '\"\"') : rawText;\n const crlf = text.indexOf(\"\\r\\n\");\n const lf = text.indexOf(\"\\n\");\n const cr = text.indexOf(\"\\r\");\n if (crlf !== -1 && crlf === cr) return \"\\r\\n\";\n if (cr !== -1 && (lf === -1 || cr < lf)) return \"\\r\";\n return \"\\n\";\n}\n\n/** Tokenize into rows of raw string fields; quote errors are reported against their data row. */\nfunction tokenize(text: string, delimiter: string, linebreak: string, preview = Infinity): { rows: string[][]; errors: DelimitedParseError[] } {\n const rows: string[][] = [];\n const errors: DelimitedParseError[] = [];\n if (text === \"\") return { rows, errors };\n let row: string[] = [];\n let i = 0;\n const n = text.length;\n const dl = delimiter.length;\n const ll = linebreak.length;\n while (i <= n && rows.length < preview) {\n if (text.startsWith('\"', i)) {\n // Quoted field: \"\" is a literal quote; the closing quote must precede a delimiter, a line break or the end.\n let value = \"\";\n let j = i + 1;\n let closed = false;\n while (j < n) {\n const q = text.indexOf('\"', j);\n if (q === -1) break;\n value += text.slice(j, q);\n if (text[q + 1] === '\"') {\n value += '\"';\n j = q + 2;\n continue;\n }\n const after = q + 1;\n if (after >= n || text.startsWith(delimiter, after) || text.startsWith(linebreak, after)) {\n closed = true;\n j = after;\n break;\n }\n errors.push({ type: \"Quotes\", code: \"InvalidQuotes\", message: \"Trailing quote on quoted field is malformed\", row: rows.length });\n value += '\"';\n j = after;\n }\n if (!closed) {\n errors.push({ type: \"Quotes\", code: \"MissingQuotes\", message: \"Quoted field unterminated\", row: rows.length });\n value += text.slice(j);\n row.push(value);\n rows.push(row);\n return { rows, errors };\n }\n row.push(value);\n i = j;\n if (i >= n) {\n rows.push(row);\n break;\n }\n if (text.startsWith(delimiter, i)) {\n i += dl;\n if (i >= n) {\n row.push(\"\");\n rows.push(row);\n break;\n }\n continue;\n }\n rows.push(row);\n row = [];\n i += ll;\n if (i >= n) {\n rows.push([\"\"]);\n break;\n }\n continue;\n }\n const nextDelim = text.indexOf(delimiter, i);\n const nextBreak = text.indexOf(linebreak, i);\n if (nextDelim !== -1 && (nextBreak === -1 || nextDelim < nextBreak)) {\n row.push(text.slice(i, nextDelim));\n i = nextDelim + dl;\n if (i >= n) {\n row.push(\"\");\n rows.push(row);\n break;\n }\n continue;\n }\n if (nextBreak !== -1) {\n row.push(text.slice(i, nextBreak));\n rows.push(row);\n row = [];\n i = nextBreak + ll;\n if (i >= n) {\n rows.push([\"\"]);\n break;\n }\n continue;\n }\n row.push(text.slice(i));\n rows.push(row);\n break;\n }\n return { rows, errors };\n}\n\nconst isEmptyRow = (row: readonly string[], mode: boolean | \"greedy\") =>\n mode === \"greedy\" ? row.join(\"\").trim() === \"\" : row.length === 1 && row[0]!.length === 0;\n\nfunction guessDelimiter(text: string, linebreak: string, skip: boolean | \"greedy\" | undefined): { delimiter: string; successful: boolean } {\n let best: string | undefined;\n let bestDelta: number | undefined;\n let bestAvg: number | undefined;\n for (const delim of GUESS_DELIMITERS) {\n const { rows } = tokenize(text, delim, linebreak, 10);\n let delta = 0;\n let avg = 0;\n let empty = 0;\n let prev: number | undefined;\n for (const row of rows) {\n if (skip && isEmptyRow(row, skip)) {\n empty++;\n continue;\n }\n const count = row.length;\n avg += count;\n if (prev === undefined) {\n prev = count;\n continue;\n }\n if (count > 0) {\n delta += Math.abs(count - prev);\n prev = count;\n }\n }\n if (rows.length > 0) avg /= rows.length - empty;\n if ((bestDelta === undefined || delta <= bestDelta) && (bestAvg === undefined || avg > bestAvg) && avg > 1.99) {\n bestDelta = delta;\n bestAvg = avg;\n best = delim;\n }\n }\n return { delimiter: best ?? \",\", successful: best !== undefined };\n}\n\nfunction typed(value: string): DelimitedCell {\n if (value === \"true\" || value === \"TRUE\") return true;\n if (value === \"false\" || value === \"FALSE\") return false;\n if (FLOAT.test(value)) {\n const num = parseFloat(value);\n if (num > -(2 ** 53) && num < 2 ** 53) return num;\n return value;\n }\n if (ISO_DATE.test(value)) return new Date(value);\n return value === \"\" ? null : value;\n}\n\n/** Parse delimited text into rows of fields (header row first). */\nexport function parseDelimited(text: string, options?: DelimitedParseOptions & { header?: false; dynamicTyping?: false }): DelimitedResult<string[]>;\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header?: false; dynamicTyping: true }): DelimitedResult<DelimitedCell[]>;\n/** Parse delimited text into records keyed by the (transformed) header row. Extra fields land in `__parsed_extra`. */\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header: true; dynamicTyping?: false }): DelimitedResult<Record<string, string>>;\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header: true; dynamicTyping: true }): DelimitedResult<Record<string, DelimitedCell>>;\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header?: boolean } = {}): DelimitedResult<unknown> {\n const input = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n const linebreak = guessLinebreak(input);\n const errors: DelimitedParseError[] = [];\n let delimiter = options.delimiter;\n if (!delimiter) {\n const guess = guessDelimiter(input, linebreak, options.skipEmptyLines);\n delimiter = guess.delimiter;\n if (!guess.successful)\n errors.push({ type: \"Delimiter\", code: \"UndetectableDelimiter\", message: \"Unable to auto-detect delimiting character; defaulted to ','\" });\n }\n const tokens = tokenize(input, delimiter, linebreak);\n errors.unshift(...tokens.errors);\n let rows = tokens.rows;\n if (options.skipEmptyLines) rows = rows.filter((row) => !isEmptyRow(row, options.skipEmptyLines!));\n const meta: DelimitedMeta = { delimiter, linebreak };\n if (!options.header) {\n return { data: options.dynamicTyping ? rows.map((row) => row.map(typed)) : rows, errors, meta };\n }\n const [head = [], ...body] = rows;\n const seen = new Map<string, number>();\n const fields = head.map((raw, index) => {\n const name = options.transformHeader ? options.transformHeader(raw, index) : raw;\n const count = seen.get(name) ?? 0;\n seen.set(name, count + 1);\n return count === 0 ? name : `${name}_${count}`;\n });\n meta.fields = fields;\n const data = body.map((row, index) => {\n const record: Record<string, unknown> = {};\n row.forEach((value, j) => {\n const cell = options.dynamicTyping ? typed(value) : value;\n if (j >= fields.length) {\n ((record.__parsed_extra as unknown[] | undefined) ??= []).push(cell);\n } else record[fields[j]!] = cell;\n });\n if (row.length > fields.length)\n errors.push({ type: \"FieldMismatch\", code: \"TooManyFields\", message: `Too many fields: expected ${fields.length} fields but parsed ${row.length}`, row: index });\n else if (row.length < fields.length)\n errors.push({ type: \"FieldMismatch\", code: \"TooFewFields\", message: `Too few fields: expected ${fields.length} fields but parsed ${row.length}`, row: index });\n return record;\n });\n return { data, errors, meta };\n}\n\n/**\n * Delimited text from a header row + body rows, through the SAME `csv`/`tsv` serializer the\n * Alchemy menu's copy and download use (quoting, nested values as compact JSON, `null` empty).\n * `spreadsheetSafe` defaults ON (kit `spreadsheetSafeByDefault`: csv/tsv are for spreadsheets) and\n * prefixes formula-looking text with `'`; pass `false` only for a lossless round-trip or AI-read text.\n */\nexport function toDelimitedText(\n header: readonly string[],\n rows: readonly (readonly (Json | undefined)[] | Readonly<Record<string, Json | undefined>>)[],\n options: { format?: \"csv\" | \"tsv\"; spreadsheetSafe?: boolean } = {},\n): string {\n const columns: Column[] = header.map((label, i) => ({ id: `c${i}`, label, path: `/c${i}`, visible: true }));\n const record = (row: (typeof rows)[number]): Record<string, Json> => {\n const out: Record<string, Json> = {};\n header.forEach((label, i) => {\n const v = Array.isArray(row) ? (row as readonly (Json | undefined)[])[i] : (row as Readonly<Record<string, Json | undefined>>)[label];\n if (v !== undefined) out[`c${i}`] = v;\n });\n return out;\n };\n const captured = directSource({ kind: \"rows\", columns, rows: rows.map(record) });\n const draft = createDraft(projectSnapshot(captured.snapshot, captured.projection));\n return serialize(draft, options.format ?? \"csv\", options.spreadsheetSafe === undefined ? {} : { spreadsheetSafe: options.spreadsheetSafe }).plainText;\n}\n\n// ─── Workbooks (the SheetJS leaf, loaded on first use) ───────────────────────────────────────────\n\nexport type WorkbookCellAddress = { r: number; c: number };\nexport type WorkbookCellRange = { s: WorkbookCellAddress; e: WorkbookCellAddress };\n/** A cell as the workbook engine reads it: type, raw value, formatted text, formula source. */\nexport type WorkbookCell = {\n t: \"s\" | \"n\" | \"b\" | \"d\" | \"e\" | \"z\";\n v?: string | number | boolean | Date;\n w?: string;\n f?: string;\n styled: boolean;\n};\n/** Grid shape options (header row included): `raw: false` → formatted text; `defval` fills gaps. */\nexport type WorkbookGridOptions = { raw?: boolean; defval?: unknown; blankrows?: boolean; dateNF?: string; rawNumbers?: boolean };\nexport type WorkbookRecordOptions = { raw?: boolean; defval?: unknown; blankrows?: boolean; dateNF?: string; rawNumbers?: boolean };\nexport interface WorkbookSheet {\n name: string;\n /** The used range, null for a sheet without one. */\n range: WorkbookCellRange | null;\n merges: WorkbookCellRange[];\n cell(r: number, c: number): WorkbookCell | null;\n cells(): Iterable<readonly [WorkbookCellAddress, WorkbookCell]>;\n grid(options?: WorkbookGridOptions): unknown[][];\n records(options?: WorkbookRecordOptions): Record<string, unknown>[];\n}\nexport interface Workbook {\n sheetNames: string[];\n sheets: WorkbookSheet[];\n sheet(name: string): WorkbookSheet | undefined;\n}\n/** `dates`: date cells as Date (`t: \"d\"`); `formulas`: keep formula source; `styles`: read cell styles. */\nexport type WorkbookReadOptions = { dates?: boolean; formulas?: boolean; styles?: boolean };\n\n/** A typed cell for the `xlsx-workbook` format; a bare string/number/boolean is a value cell. */\nexport type WorkbookCellJson = { t?: \"s\" | \"n\" | \"b\"; v?: string | number | boolean | null; f?: string };\n/** The `xlsx-workbook` payload value (`{ kind: \"registered\", format: \"workbook\", value }`). */\nexport type WorkbookJson = {\n sheets: { name: string; rows: (WorkbookCellJson | string | number | boolean | null)[][]; merges?: WorkbookCellRange[] }[];\n};\n\nexport type ReadInput = Blob | ArrayBuffer | Uint8Array | string;\n\nasync function bytesOf(input: ReadInput): Promise<Uint8Array> {\n if (typeof input === \"string\") return new TextEncoder().encode(input);\n if (input instanceof Uint8Array) return input;\n if (input instanceof ArrayBuffer) return new Uint8Array(input);\n return new Uint8Array(await input.arrayBuffer());\n}\n\n/** Read a spreadsheet (XLSX, XLS, ODS; CSV/TSV with spreadsheet typing) through the one workbook engine. */\nexport async function readWorkbook(input: ReadInput, options: WorkbookReadOptions = {}): Promise<Workbook> {\n const bytes = await bytesOf(input);\n const { readWorkbookBytes } = await import(\"@ai-matrx/alchemy/operate/formats/xlsx\");\n return readWorkbookBytes(bytes, options);\n}\n\n// ─── readFile: the door ──────────────────────────────────────────────────────────────────────────\n\nexport type TabularFormat = \"csv\" | \"tsv\" | \"txt\" | \"xlsx\" | \"xls\" | \"ods\";\nconst WORKBOOK_FORMATS: ReadonlySet<string> = new Set([\"xlsx\", \"xls\", \"ods\", \"xlsm\", \"xlsb\"]);\nconst DELIMITED_FORMATS: ReadonlySet<string> = new Set([\"csv\", \"tsv\", \"txt\", \"tab\"]);\n\nexport type ReadFileOptions = DelimitedParseOptions & {\n /** The file's format; absent → from `filename` (or a File's name) extension. */\n format?: TabularFormat | (string & {});\n filename?: string;\n /** Workbooks: the grid shape (`sheet_to_json` header-row semantics). */\n grid?: WorkbookGridOptions;\n /** Workbooks: read options. */\n workbook?: WorkbookReadOptions;\n};\nexport type TabularSheet = { name: string; grid: unknown[][]; merges: WorkbookCellRange[] };\nexport type TabularFile = { format: TabularFormat; sheets: TabularSheet[]; errors: DelimitedParseError[]; delimiter?: string };\n\nexport class UnreadableFormatError extends Error {\n readonly format: string;\n constructor(format: string) {\n super(`A \"${format}\" file is not a table we can read. Choose a CSV, TSV, or Excel file.`);\n this.format = format;\n this.name = \"UnreadableFormatError\";\n }\n}\n\n/** The format a file name declares (lower-case extension), or \"\" when it has none. */\nexport function fileFormatOf(name: string | undefined): string {\n const m = /\\.([a-z0-9]+)$/i.exec(name ?? \"\");\n return m ? m[1]!.toLowerCase() : \"\";\n}\n\n/**\n * Read a table file — CSV/TSV/plain text through the delimited parser, XLSX/XLS/ODS through the\n * workbook engine — into sheets of grids (header row first). Delimited text is one sheet.\n */\nexport async function readFile(input: ReadInput, options: ReadFileOptions = {}): Promise<TabularFile> {\n const name = options.filename ?? (typeof Blob !== \"undefined\" && input instanceof Blob && \"name\" in input ? String((input as File).name) : undefined);\n const format = (options.format ?? fileFormatOf(name)) || (typeof input === \"string\" ? \"csv\" : \"\");\n if (WORKBOOK_FORMATS.has(format)) {\n const book = await readWorkbook(input, options.workbook);\n return {\n format: (format === \"xlsm\" || format === \"xlsb\" ? \"xlsx\" : format) as TabularFormat,\n sheets: book.sheets.map((s) => ({ name: s.name, grid: s.grid(options.grid), merges: s.merges })),\n errors: [],\n };\n }\n if (!DELIMITED_FORMATS.has(format)) throw new UnreadableFormatError(format || \"unknown\");\n const text = typeof input === \"string\" ? input : new TextDecoder(\"utf-8\").decode(await bytesOf(input));\n const parsed = parseDelimited(text, {\n ...options,\n header: false,\n ...(options.delimiter ? {} : format === \"tsv\" || format === \"tab\" ? { delimiter: \"\\t\" } : {}),\n } as DelimitedParseOptions & { header: false; dynamicTyping?: false });\n return {\n format: (format === \"tab\" ? \"tsv\" : format) as TabularFormat,\n sheets: [{ name: name?.replace(/\\.[^.]+$/, \"\") || \"Sheet1\", grid: parsed.data, merges: [] }],\n errors: parsed.errors,\n delimiter: parsed.meta.delimiter,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/operate/read.ts"],"sourcesContent":["/**\n * `@ai-matrx/alchemy/operate/read` — the one parse door (ALC-19 T4–T12). Write has `buildFile`; read has `readFile`.\n *\n * One engine per format across read and write:\n * - CSV / TSV / delimited text: kit's `parseDelimited` (no library; it sits in `@ai-matrx/kit/delimited`\n * beside the one writer so packages below alchemy read through it too), re-exported here. `parseDelimited` is Papa Parse–compatible in the options the platform\n * uses (auto delimiter + line ending, quotes, `skipEmptyLines` true | \"greedy\", `header`,\n * `transformHeader`, `dynamicTyping`) and answers the same `{ data, errors, meta }` shape and\n * error codes, so a repointed importer keeps its behaviour.\n * - XLSX / XLS / ODS: SheetJS, the library the `xlsx` writer uses, loaded on first use from the\n * `/operate/formats/xlsx` leaf (`readWorkbook`). A caller that needs spreadsheet typing of a CSV\n * (dates, numbers, formulas as SheetJS reads them) reads it through `readWorkbook` too.\n */\n\n// Through the PUBLIC `/operate` subpath, so this entry shares the transfer core at runtime instead of bundling a copy.\nimport { createDraft, directSource, projectSnapshot, serialize, type Column, type Json } from \"@ai-matrx/alchemy/operate\";\n\n// ─── Delimited text ───────────────────────────────────────────────────────────────────────────────\n// THE one parser lives beside THE one writer in @ai-matrx/kit/delimited (AP-2: packages below\n// alchemy read CSV too); this door re-exports it unchanged, so `/operate/read` keeps its API.\nexport { parseDelimited } from \"@ai-matrx/kit/delimited\";\nexport type {\n DelimitedParseError,\n DelimitedParseOptions,\n ParsedDelimitedCell as DelimitedCell,\n DelimitedMeta,\n DelimitedResult,\n} from \"@ai-matrx/kit/delimited\";\nimport { parseDelimited, type DelimitedParseError, type DelimitedParseOptions } from \"@ai-matrx/kit/delimited\";\n\n/**\n * Delimited text from a header row + body rows, through the SAME `csv`/`tsv` serializer the\n * Alchemy menu's copy and download use (quoting, nested values as compact JSON, `null` empty).\n * `spreadsheetSafe` defaults ON (kit `spreadsheetSafeByDefault`: csv/tsv are for spreadsheets) and\n * prefixes formula-looking text with `'`; pass `false` only for a lossless round-trip or AI-read text.\n */\nexport function toDelimitedText(\n header: readonly string[],\n rows: readonly (readonly (Json | undefined)[] | Readonly<Record<string, Json | undefined>>)[],\n options: { format?: \"csv\" | \"tsv\"; spreadsheetSafe?: boolean } = {},\n): string {\n const columns: Column[] = header.map((label, i) => ({ id: `c${i}`, label, path: `/c${i}`, visible: true }));\n const record = (row: (typeof rows)[number]): Record<string, Json> => {\n const out: Record<string, Json> = {};\n header.forEach((label, i) => {\n const v = Array.isArray(row) ? (row as readonly (Json | undefined)[])[i] : (row as Readonly<Record<string, Json | undefined>>)[label];\n if (v !== undefined) out[`c${i}`] = v;\n });\n return out;\n };\n const captured = directSource({ kind: \"rows\", columns, rows: rows.map(record) });\n const draft = createDraft(projectSnapshot(captured.snapshot, captured.projection));\n return serialize(draft, options.format ?? \"csv\", options.spreadsheetSafe === undefined ? {} : { spreadsheetSafe: options.spreadsheetSafe }).plainText;\n}\n\n// ─── Workbooks (the SheetJS leaf, loaded on first use) ───────────────────────────────────────────\n\nexport type WorkbookCellAddress = { r: number; c: number };\nexport type WorkbookCellRange = { s: WorkbookCellAddress; e: WorkbookCellAddress };\n/** A cell as the workbook engine reads it: type, raw value, formatted text, formula source. */\nexport type WorkbookCell = {\n t: \"s\" | \"n\" | \"b\" | \"d\" | \"e\" | \"z\";\n v?: string | number | boolean | Date;\n w?: string;\n f?: string;\n styled: boolean;\n};\n/** Grid shape options (header row included): `raw: false` → formatted text; `defval` fills gaps. */\nexport type WorkbookGridOptions = { raw?: boolean; defval?: unknown; blankrows?: boolean; dateNF?: string; rawNumbers?: boolean };\nexport type WorkbookRecordOptions = { raw?: boolean; defval?: unknown; blankrows?: boolean; dateNF?: string; rawNumbers?: boolean };\nexport interface WorkbookSheet {\n name: string;\n /** The used range, null for a sheet without one. */\n range: WorkbookCellRange | null;\n merges: WorkbookCellRange[];\n cell(r: number, c: number): WorkbookCell | null;\n cells(): Iterable<readonly [WorkbookCellAddress, WorkbookCell]>;\n grid(options?: WorkbookGridOptions): unknown[][];\n records(options?: WorkbookRecordOptions): Record<string, unknown>[];\n}\nexport interface Workbook {\n sheetNames: string[];\n sheets: WorkbookSheet[];\n sheet(name: string): WorkbookSheet | undefined;\n}\n/** `dates`: date cells as Date (`t: \"d\"`); `formulas`: keep formula source; `styles`: read cell styles. */\nexport type WorkbookReadOptions = { dates?: boolean; formulas?: boolean; styles?: boolean };\n\n/** A typed cell for the `xlsx-workbook` format; a bare string/number/boolean is a value cell. */\nexport type WorkbookCellJson = { t?: \"s\" | \"n\" | \"b\"; v?: string | number | boolean | null; f?: string };\n/** The `xlsx-workbook` payload value (`{ kind: \"registered\", format: \"workbook\", value }`). */\nexport type WorkbookJson = {\n sheets: { name: string; rows: (WorkbookCellJson | string | number | boolean | null)[][]; merges?: WorkbookCellRange[] }[];\n};\n\nexport type ReadInput = Blob | ArrayBuffer | Uint8Array | string;\n\nasync function bytesOf(input: ReadInput): Promise<Uint8Array> {\n if (typeof input === \"string\") return new TextEncoder().encode(input);\n if (input instanceof Uint8Array) return input;\n if (input instanceof ArrayBuffer) return new Uint8Array(input);\n return new Uint8Array(await input.arrayBuffer());\n}\n\n/** Read a spreadsheet (XLSX, XLS, ODS; CSV/TSV with spreadsheet typing) through the one workbook engine. */\nexport async function readWorkbook(input: ReadInput, options: WorkbookReadOptions = {}): Promise<Workbook> {\n const bytes = await bytesOf(input);\n const { readWorkbookBytes } = await import(\"@ai-matrx/alchemy/operate/formats/xlsx\");\n return readWorkbookBytes(bytes, options);\n}\n\n// ─── readFile: the door ──────────────────────────────────────────────────────────────────────────\n\nexport type TabularFormat = \"csv\" | \"tsv\" | \"txt\" | \"xlsx\" | \"xls\" | \"ods\";\nconst WORKBOOK_FORMATS: ReadonlySet<string> = new Set([\"xlsx\", \"xls\", \"ods\", \"xlsm\", \"xlsb\"]);\nconst DELIMITED_FORMATS: ReadonlySet<string> = new Set([\"csv\", \"tsv\", \"txt\", \"tab\"]);\n\nexport type ReadFileOptions = DelimitedParseOptions & {\n /** The file's format; absent → from `filename` (or a File's name) extension. */\n format?: TabularFormat | (string & {});\n filename?: string;\n /** Workbooks: the grid shape (`sheet_to_json` header-row semantics). */\n grid?: WorkbookGridOptions;\n /** Workbooks: read options. */\n workbook?: WorkbookReadOptions;\n};\nexport type TabularSheet = { name: string; grid: unknown[][]; merges: WorkbookCellRange[] };\nexport type TabularFile = { format: TabularFormat; sheets: TabularSheet[]; errors: DelimitedParseError[]; delimiter?: string };\n\nexport class UnreadableFormatError extends Error {\n readonly format: string;\n constructor(format: string) {\n super(`A \"${format}\" file is not a table we can read. Choose a CSV, TSV, or Excel file.`);\n this.format = format;\n this.name = \"UnreadableFormatError\";\n }\n}\n\n/** The format a file name declares (lower-case extension), or \"\" when it has none. */\nexport function fileFormatOf(name: string | undefined): string {\n const m = /\\.([a-z0-9]+)$/i.exec(name ?? \"\");\n return m ? m[1]!.toLowerCase() : \"\";\n}\n\n/**\n * Read a table file — CSV/TSV/plain text through the delimited parser, XLSX/XLS/ODS through the\n * workbook engine — into sheets of grids (header row first). Delimited text is one sheet.\n */\nexport async function readFile(input: ReadInput, options: ReadFileOptions = {}): Promise<TabularFile> {\n const name = options.filename ?? (typeof Blob !== \"undefined\" && input instanceof Blob && \"name\" in input ? String((input as File).name) : undefined);\n const format = (options.format ?? fileFormatOf(name)) || (typeof input === \"string\" ? \"csv\" : \"\");\n if (WORKBOOK_FORMATS.has(format)) {\n const book = await readWorkbook(input, options.workbook);\n return {\n format: (format === \"xlsm\" || format === \"xlsb\" ? \"xlsx\" : format) as TabularFormat,\n sheets: book.sheets.map((s) => ({ name: s.name, grid: s.grid(options.grid), merges: s.merges })),\n errors: [],\n };\n }\n if (!DELIMITED_FORMATS.has(format)) throw new UnreadableFormatError(format || \"unknown\");\n const text = typeof input === \"string\" ? input : new TextDecoder(\"utf-8\").decode(await bytesOf(input));\n const parsed = parseDelimited(text, {\n ...options,\n header: false,\n ...(options.delimiter ? {} : format === \"tsv\" || format === \"tab\" ? { delimiter: \"\\t\" } : {}),\n } as DelimitedParseOptions & { header: false; dynamicTyping?: false });\n return {\n format: (format === \"tab\" ? \"tsv\" : format) as TabularFormat,\n sheets: [{ name: name?.replace(/\\.[^.]+$/, \"\") || \"Sheet1\", grid: parsed.data, merges: [] }],\n errors: parsed.errors,\n delimiter: parsed.meta.delimiter,\n };\n}\n"],"mappings":"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","names":["parseDelimited"]}
@@ -65,7 +65,7 @@ type Notice = {
65
65
  };
66
66
  interface FormatOptions {
67
67
  filename?: string;
68
- /** Formula guard for delimited text; unset = kit `spreadsheetSafeByDefault(format)` (on for csv/tsv/xlsx). */
68
+ /** Formula guard for delimited text; unset = kit `spreadsheetSafeByDefault(format)` (on for csv/tsv; an .xlsx's typed text cells are never evaluated, its plain text is the guarded TSV). */
69
69
  spreadsheetSafe?: boolean;
70
70
  /** Markdown→HTML skin: the two page looks plus "inline" for the rich clipboard (styles on elements). */
71
71
  skin?: "document" | "article" | "inline";
@@ -65,7 +65,7 @@ type Notice = {
65
65
  };
66
66
  interface FormatOptions {
67
67
  filename?: string;
68
- /** Formula guard for delimited text; unset = kit `spreadsheetSafeByDefault(format)` (on for csv/tsv/xlsx). */
68
+ /** Formula guard for delimited text; unset = kit `spreadsheetSafeByDefault(format)` (on for csv/tsv; an .xlsx's typed text cells are never evaluated, its plain text is the guarded TSV). */
69
69
  spreadsheetSafe?: boolean;
70
70
  /** Markdown→HTML skin: the two page looks plus "inline" for the rich clipboard (styles on elements). */
71
71
  skin?: "document" | "article" | "inline";
@@ -138,10 +138,9 @@ EOD`}return{to_workbook:dif_to_workbook,to_sheet:dif_to_sheet,from_sheet:sheet_t
138
138
  </table:calculation-settings>
139
139
  `);for(var i=0;i!=wb.SheetNames.length;++i)o.push(write_ws(wb.Sheets[wb.SheetNames[i]],wb,i,opts,nfs,((wb.Workbook||{}).WBProps||{}).date1904));return(wb.Workbook||{}).Names&&o.push(write_names_ods(wb.Workbook.Names,wb.SheetNames,-1)),o.push(` </office:spreadsheet>
140
140
  `),o.push(` </office:body>
141
- `),opts.bookType=="fods"?o.push("</office:document>"):o.push("</office:document-content>"),o.join("")}})();subarray=(function(){try{return typeof Uint8Array>"u"||typeof Uint8Array.prototype.subarray>"u"?"slice":typeof Buffer<"u"?typeof Buffer.prototype.subarray>"u"?"slice":(typeof Buffer.from=="function"?Buffer.from([72,62]):new Buffer([72,62]))instanceof Uint8Array?"subarray":"slice":"subarray"}catch{return"slice"}})();numbers_lut_new=function(){return{sst:[],rsst:[],ofmt:[],nfmt:[],fmla:[],ferr:[],cmnt:[]}};USE_WIDE_ROWS=!0;qreg=/"/g;utils={encode_col,encode_row,encode_cell,encode_range,decode_col,decode_row,split_cell,decode_cell,decode_range,format_cell,sheet_new,sheet_add_aoa,sheet_add_json,sheet_add_dom,aoa_to_sheet,json_to_sheet,table_to_sheet:parse_dom_table,table_to_book,sheet_to_csv,sheet_to_txt,sheet_to_json,sheet_to_html,sheet_to_formulae,sheet_to_row_object_array:sheet_to_json,sheet_get_cell:ws_get_cell_stub,book_new,book_append_sheet,book_set_sheet_visibility,cell_set_number_format,cell_set_hyperlink,cell_set_internal_link,cell_add_comment,sheet_set_array_formula,consts:{SHEET_VISIBLE:0,SHEET_HIDDEN:1,SHEET_VERY_HIDDEN:2}};__stream={to_json:write_json_stream,to_html:write_html_stream,to_csv:write_csv_stream,to_xlml:write_xlml_stream,set_readable},version=XLSX.version,xlsx_default={parse_xlscfb,parse_zip,read:readSync,readFile:readFileSync,readFileSync,write:writeSync,writeFile:writeFileSync,writeFileSync,writeFileAsync,writeXLSX:writeSyncXLSX,writeFileXLSX:writeFileSyncXLSX,utils,set_fs,set_cptable,stream:__stream,SSF,CFB}}});var operate_xlsx_exports={};__export(operate_xlsx_exports,{loadSheetJs:()=>loadSheetJs,readWorkbookBytes:()=>readWorkbookBytes,workbookEngine:()=>workbookEngine,writeWorkbookBytes:()=>writeWorkbookBytes,xlsxEngine:()=>xlsxEngine});module.exports=__toCommonJS(operate_xlsx_exports);var import_download=require("@ai-matrx/kit/download"),import_json_format=require("@ai-matrx/kit/json-format"),import_html_escape=require("@ai-matrx/kit/html-escape"),import_delimited=require("@ai-matrx/kit/delimited"),import_transfer_json=require("@ai-matrx/kit/transfer-json"),import_transfer_json2=require("@ai-matrx/kit/transfer-json");var enc=x=>x.replace(/~/g,"~0").replace(/\//g,"~1"),dec=x=>x.replace(/~1/g,"/").replace(/~0/g,"~");function deepFreeze(v){if(ArrayBuffer.isView(v))return v;if(v&&typeof v=="object"&&!Object.isFrozen(v)){Object.freeze(v);for(let x of Object.values(v))deepFreeze(x)}return v}function at(v,path){if(validatePointer(path),path==="")return v;if(!path.startsWith("/"))throw new import_transfer_json.ContentTransferError("invalid-path","Expected RFC 6901 pointer",path);let cur=v;for(let part of path.slice(1).split("/").map(dec))if(Array.isArray(cur))cur=cur[Number(part)];else if(cur&&typeof cur=="object")cur=Object.hasOwn(cur,part)?cur[part]:void 0;else return;return cur}function validatePointer(path){if(path!==""&&!path.startsWith("/")||/~(?![01])/u.test(path))throw new import_transfer_json.ContentTransferError("invalid-path","Expected an RFC 6901 JSON pointer",path)}function readTransferCell(row,column){return at(row,column.path)}var textValue=value=>value==null?"":typeof value=="string"?value:(0,import_json_format.stringifyJson)(value,{style:"compact"});function sealTransferArtifact(artifact){if(!artifact.file)return deepFreeze({...artifact,omissions:artifact.omissions.map(o=>({...o}))});let bytes=artifact.file.bytes.slice(),file={filename:artifact.file.filename,mime:artifact.file.mime,get bytes(){return bytes.slice()}};return deepFreeze({...artifact,file,omissions:artifact.omissions.map(o=>({...o}))})}function exportedColumns(columns){return columns.filter(c=>c.visible&&c.exportable!==!1&&c.classification!=="secret"&&c.classification!=="credential")}function serialize(draft,format,options={}){let payload=draft.payload,omissions=draft.omissions.map(o=>({...o})),plainText="",html,mime="text/plain;charset=utf-8",extension="txt",columnCell=(row,column)=>textValue(readTransferCell(row,column));if((format==="csv"||format==="tsv")&&payload.kind!=="rows")throw new import_transfer_json.ContentTransferError("unsupported-format",`${format.toUpperCase()} requires tabular data`);if(format==="json"||format==="compact-json"){let value=payload.kind==="rows"?payload.rows:payload.kind==="text"||payload.kind==="markdown"?payload.text:payload.value;plainText=(0,import_json_format.stringifyJson)(value,{style:format==="json"?"pretty":"compact"}),mime="application/json;charset=utf-8",extension="json"}else if(payload.kind==="rows"){let columns=exportedColumns(payload.columns);if(format==="markdown"){let escapeCell=value=>(0,import_html_escape.escapeHtml)(value).replace(/\\/g,"\\\\").replace(/\|/g,"\\|").replace(/\r\n|\r|\n/g,"<br>");plainText=[`| ${columns.map(c=>escapeCell(c.label)).join(" | ")} |`,`| ${columns.map(()=>"---").join(" | ")} |`,...payload.rows.map(row=>`| ${columns.map(c=>escapeCell(columnCell(row,c))).join(" | ")} |`)].join(`
141
+ `),opts.bookType=="fods"?o.push("</office:document>"):o.push("</office:document-content>"),o.join("")}})();subarray=(function(){try{return typeof Uint8Array>"u"||typeof Uint8Array.prototype.subarray>"u"?"slice":typeof Buffer<"u"?typeof Buffer.prototype.subarray>"u"?"slice":(typeof Buffer.from=="function"?Buffer.from([72,62]):new Buffer([72,62]))instanceof Uint8Array?"subarray":"slice":"subarray"}catch{return"slice"}})();numbers_lut_new=function(){return{sst:[],rsst:[],ofmt:[],nfmt:[],fmla:[],ferr:[],cmnt:[]}};USE_WIDE_ROWS=!0;qreg=/"/g;utils={encode_col,encode_row,encode_cell,encode_range,decode_col,decode_row,split_cell,decode_cell,decode_range,format_cell,sheet_new,sheet_add_aoa,sheet_add_json,sheet_add_dom,aoa_to_sheet,json_to_sheet,table_to_sheet:parse_dom_table,table_to_book,sheet_to_csv,sheet_to_txt,sheet_to_json,sheet_to_html,sheet_to_formulae,sheet_to_row_object_array:sheet_to_json,sheet_get_cell:ws_get_cell_stub,book_new,book_append_sheet,book_set_sheet_visibility,cell_set_number_format,cell_set_hyperlink,cell_set_internal_link,cell_add_comment,sheet_set_array_formula,consts:{SHEET_VISIBLE:0,SHEET_HIDDEN:1,SHEET_VERY_HIDDEN:2}};__stream={to_json:write_json_stream,to_html:write_html_stream,to_csv:write_csv_stream,to_xlml:write_xlml_stream,set_readable},version=XLSX.version,xlsx_default={parse_xlscfb,parse_zip,read:readSync,readFile:readFileSync,readFileSync,write:writeSync,writeFile:writeFileSync,writeFileSync,writeFileAsync,writeXLSX:writeSyncXLSX,writeFileXLSX:writeFileSyncXLSX,utils,set_fs,set_cptable,stream:__stream,SSF,CFB}}});var operate_xlsx_exports={};__export(operate_xlsx_exports,{loadSheetJs:()=>loadSheetJs,readWorkbookBytes:()=>readWorkbookBytes,workbookEngine:()=>workbookEngine,writeWorkbookBytes:()=>writeWorkbookBytes,xlsxEngine:()=>xlsxEngine});module.exports=__toCommonJS(operate_xlsx_exports);var import_delimited2=require("@ai-matrx/kit/delimited");var import_download=require("@ai-matrx/kit/download"),import_json_format=require("@ai-matrx/kit/json-format"),import_html_escape=require("@ai-matrx/kit/html-escape"),import_delimited=require("@ai-matrx/kit/delimited"),import_transfer_json=require("@ai-matrx/kit/transfer-json"),import_transfer_json2=require("@ai-matrx/kit/transfer-json");var enc=x=>x.replace(/~/g,"~0").replace(/\//g,"~1"),dec=x=>x.replace(/~1/g,"/").replace(/~0/g,"~");function deepFreeze(v){if(ArrayBuffer.isView(v))return v;if(v&&typeof v=="object"&&!Object.isFrozen(v)){Object.freeze(v);for(let x of Object.values(v))deepFreeze(x)}return v}function at(v,path){if(validatePointer(path),path==="")return v;if(!path.startsWith("/"))throw new import_transfer_json.ContentTransferError("invalid-path","Expected RFC 6901 pointer",path);let cur=v;for(let part of path.slice(1).split("/").map(dec))if(Array.isArray(cur))cur=cur[Number(part)];else if(cur&&typeof cur=="object")cur=Object.hasOwn(cur,part)?cur[part]:void 0;else return;return cur}function validatePointer(path){if(path!==""&&!path.startsWith("/")||/~(?![01])/u.test(path))throw new import_transfer_json.ContentTransferError("invalid-path","Expected an RFC 6901 JSON pointer",path)}function readTransferCell(row,column){return at(row,column.path)}var textValue=value=>value==null?"":typeof value=="string"?value:(0,import_json_format.stringifyJson)(value,{style:"compact"});function sealTransferArtifact(artifact){if(!artifact.file)return deepFreeze({...artifact,omissions:artifact.omissions.map(o=>({...o}))});let bytes=artifact.file.bytes.slice(),file={filename:artifact.file.filename,mime:artifact.file.mime,get bytes(){return bytes.slice()}};return deepFreeze({...artifact,file,omissions:artifact.omissions.map(o=>({...o}))})}function exportedColumns(columns){return columns.filter(c=>c.visible&&c.exportable!==!1&&c.classification!=="secret"&&c.classification!=="credential")}function serialize(draft,format,options={}){let payload=draft.payload,omissions=draft.omissions.map(o=>({...o})),plainText="",html,mime="text/plain;charset=utf-8",extension="txt",columnCell=(row,column)=>textValue(readTransferCell(row,column));if((format==="csv"||format==="tsv")&&payload.kind!=="rows")throw new import_transfer_json.ContentTransferError("unsupported-format",`${format.toUpperCase()} requires tabular data`);if(format==="json"||format==="compact-json"){let value=payload.kind==="rows"?payload.rows:payload.kind==="text"||payload.kind==="markdown"?payload.text:payload.value;plainText=(0,import_json_format.stringifyJson)(value,{style:format==="json"?"pretty":"compact"}),mime="application/json;charset=utf-8",extension="json"}else if(payload.kind==="rows"){let columns=exportedColumns(payload.columns);if(format==="markdown"){let escapeCell=value=>(0,import_html_escape.escapeHtml)(value).replace(/\\/g,"\\\\").replace(/\|/g,"\\|").replace(/\r\n|\r|\n/g,"<br>");plainText=[`| ${columns.map(c=>escapeCell(c.label)).join(" | ")} |`,`| ${columns.map(()=>"---").join(" | ")} |`,...payload.rows.map(row=>`| ${columns.map(c=>escapeCell(columnCell(row,c))).join(" | ")} |`)].join(`
142
142
  `),mime="text/markdown;charset=utf-8",extension="md"}else{let delimiter=format==="csv"?",":" ",safe=options.spreadsheetSafe??(0,import_delimited.spreadsheetSafeByDefault)(format),encode=(value,path,isString=!0)=>{let out=(0,import_delimited.encodeDelimitedCell)(value,{delimiter,spreadsheetSafe:safe&&isString});return out.escaped&&omissions.push({path,reason:"escaped",count:1}),out.text};plainText=[columns.map(c=>encode(c.label,`/columns/${enc(c.id)}`)).join(delimiter),...payload.rows.map((row,index)=>columns.map(c=>encode(columnCell(row,c),`/rows/${index}${c.path}`,typeof readTransferCell(row,c)=="string")).join(delimiter))].join(`
143
- `),format==="csv"&&(mime="text/csv;charset=utf-8",extension="csv"),format==="tsv"&&(mime="text/tab-separated-values;charset=utf-8",extension="tsv")}format==="html"&&(html=`<table><thead><tr>${columns.map(c=>`<th>${(0,import_html_escape.escapeHtml)(c.label)}</th>`).join("")}</tr></thead><tbody>${payload.rows.map(row=>`<tr>${columns.map(c=>`<td>${(0,import_html_escape.escapeHtml)(columnCell(row,c)).replace(/\r\n|\r|\n/g,"<br>")}</td>`).join("")}</tr>`).join("")}</tbody></table>`)}else plainText=payload.kind==="text"||payload.kind==="markdown"?payload.text:(0,import_json_format.stringifyJson)(payload.value,{style:"pretty"}),format==="markdown"&&(mime="text/markdown;charset=utf-8",extension="md"),format==="html"&&(html=`<pre>${(0,import_html_escape.escapeHtml)(plainText)}</pre>`);return format==="html"&&(mime="text/html;charset=utf-8",extension="html"),sealTransferArtifact({snapshotId:draft.snapshotId,draftRevision:draft.revision,format,plainText,...html===void 0?{}:{html},file:{filename:options.filename??`export.${extension}`,mime,bytes:new TextEncoder().encode(format==="html"?html??plainText:plainText)},omissions})}var cell=value=>value==null?null:typeof value=="string"||typeof value=="number"||typeof value=="boolean"?value:JSON.stringify(value),xlsxEngine={async build(draft,options,signal){if(signal.aborted)throw new DOMException("Cancelled","AbortError");let p=draft.payload;if(p.kind!=="rows")throw new import_transfer_json2.ContentTransferError("unsupported-format","An Excel workbook needs table rows. Download it as CSV or JSON instead.");let columns=p.columns.filter(c=>c.visible&&c.exportable!==!1&&c.classification!=="secret"&&c.classification!=="credential"),XLSX2=await Promise.resolve().then(()=>(init_xlsx(),xlsx_exports));if(signal.aborted)throw new DOMException("Cancelled","AbortError");let sheet=XLSX2.utils.aoa_to_sheet([columns.map(c=>c.label),...p.rows.map(row=>columns.map(c=>cell(readTransferCell(row,c))))]),book=XLSX2.utils.book_new(),filename=options.filename??"export.xlsx",sheetName=filename.replace(/\.xlsx$/i,"").replace(/[\\/?*[\]:]/g," ").trim().slice(0,31)||"Data";XLSX2.utils.book_append_sheet(book,sheet,sheetName);let written=XLSX2.write(book,{type:"array",bookType:"xlsx",compression:!0});return sealTransferArtifact({snapshotId:draft.snapshotId,draftRevision:draft.revision,format:"xlsx",plainText:serialize(draft,"tsv").plainText,file:{filename,mime:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",bytes:written instanceof Uint8Array?written:new Uint8Array(written)},omissions:draft.omissions.map(o=>({...o}))})}},sheetJs=null;async function loadSheetJs(){return sheetJs??=await Promise.resolve().then(()=>(init_xlsx(),xlsx_exports)),sheetJs}function asCell(cell2){return{t:cell2.t,...cell2.v===void 0?{}:{v:cell2.v},...typeof cell2.w=="string"?{w:cell2.w}:{},...typeof cell2.f=="string"&&cell2.f.length>0?{f:cell2.f}:{},styled:!!cell2.s}}function wrapSheet(X,name,ws){let ref=ws["!ref"],range=ref?X.utils.decode_range(ref):null,merges=(ws["!merges"]??[]).map(m=>({s:{...m.s},e:{...m.e}}));return{name,range,merges,cell(r,c){let found=ws[X.utils.encode_cell({r,c})];return found?asCell(found):null},*cells(){for(let key in ws){if(key.startsWith("!"))continue;let found=ws[key];found&&(yield[X.utils.decode_cell(key),asCell(found)])}},grid(options={}){return X.utils.sheet_to_json(ws,{header:1,...options})},records(options={}){return X.utils.sheet_to_json(ws,options)}}}async function readWorkbookBytes(bytes,options={}){let X=await loadSheetJs(),book=X.read(bytes,{type:"array",...options.dates?{cellDates:!0}:{},...options.formulas?{cellFormula:!0}:{},...options.styles?{cellStyles:!0}:{}}),sheets=book.SheetNames.flatMap(name=>{let ws=book.Sheets[name];return ws?[wrapSheet(X,name,ws)]:[]});return{sheetNames:[...book.SheetNames],sheets,sheet:name=>sheets.find(s=>s.name===name)}}var XLSX_MIME="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";function dedupedSheetName(taken,raw){let name=raw.replace(/[\\/?*[\]:]/g," ").slice(0,31)||"Sheet1";if(!taken.includes(name))return name;let i=2;for(;taken.includes(`${name} (${i})`);)i++;return`${name} (${i})`}async function writeWorkbookBytes(value){let X=await loadSheetJs(),book=X.utils.book_new();for(let sheet of value.sheets){let ws={},maxR=-1,maxC=-1;sheet.rows.forEach((row,r)=>{row.forEach((raw,c)=>{if(raw==null)return;let typed=typeof raw=="object"?raw:null,v=typeof raw=="object"?raw.v:raw,out={t:typed?.t??(typeof v=="number"?"n":typeof v=="boolean"?"b":"s"),v:v??""};typed?.f&&(out.f=typed.f.replace(/^=/,"")),ws[X.utils.encode_cell({r,c})]=out,r>maxR&&(maxR=r),c>maxC&&(maxC=c)})}),ws["!ref"]=maxR<0?"A1:A1":X.utils.encode_range({s:{r:0,c:0},e:{r:maxR,c:maxC}}),sheet.merges?.length&&(ws["!merges"]=sheet.merges.map(m=>({s:{...m.s},e:{...m.e}}))),X.utils.book_append_sheet(book,ws,dedupedSheetName(book.SheetNames,sheet.name))}book.SheetNames.length===0&&X.utils.book_append_sheet(book,X.utils.aoa_to_sheet([[""]]),"Sheet1");let written=X.write(book,{type:"array",bookType:"xlsx",compression:!0});return written instanceof Uint8Array?written:new Uint8Array(written)}var workbookEngine={async build(draft,options,signal){if(signal.aborted)throw new DOMException("Cancelled","AbortError");let p=draft.payload;if(p.kind!=="registered"||p.format!=="workbook")throw new import_transfer_json2.ContentTransferError("unsupported-format","A formatted workbook needs workbook content. Download the table as Excel instead.");let value=p.value,bytes=await writeWorkbookBytes(value);if(signal.aborted)throw new DOMException("Cancelled","AbortError");let plainText=(value.sheets[0]?.rows??[]).map(row=>row.map(c=>c==null?"":String(typeof c=="object"?c.v??"":c)).join(" ")).join(`
144
- `);return sealTransferArtifact({snapshotId:draft.snapshotId,draftRevision:draft.revision,format:"xlsx-workbook",plainText,file:{filename:options.filename??"workbook.xlsx",mime:XLSX_MIME,bytes},omissions:draft.omissions.map(o=>({...o}))})}};
143
+ `),format==="csv"&&(mime="text/csv;charset=utf-8",extension="csv"),format==="tsv"&&(mime="text/tab-separated-values;charset=utf-8",extension="tsv")}format==="html"&&(html=`<table><thead><tr>${columns.map(c=>`<th>${(0,import_html_escape.escapeHtml)(c.label)}</th>`).join("")}</tr></thead><tbody>${payload.rows.map(row=>`<tr>${columns.map(c=>`<td>${(0,import_html_escape.escapeHtml)(columnCell(row,c)).replace(/\r\n|\r|\n/g,"<br>")}</td>`).join("")}</tr>`).join("")}</tbody></table>`)}else plainText=payload.kind==="text"||payload.kind==="markdown"?payload.text:(0,import_json_format.stringifyJson)(payload.value,{style:"pretty"}),format==="markdown"&&(mime="text/markdown;charset=utf-8",extension="md"),format==="html"&&(html=`<pre>${(0,import_html_escape.escapeHtml)(plainText)}</pre>`);return format==="html"&&(mime="text/html;charset=utf-8",extension="html"),sealTransferArtifact({snapshotId:draft.snapshotId,draftRevision:draft.revision,format,plainText,...html===void 0?{}:{html},file:{filename:options.filename??`export.${extension}`,mime,bytes:new TextEncoder().encode(format==="html"?html??plainText:plainText)},omissions})}var cell=value=>value==null?null:typeof value=="string"||typeof value=="number"||typeof value=="boolean"?value:JSON.stringify(value),xlsxEngine={async build(draft,options,signal){if(signal.aborted)throw new DOMException("Cancelled","AbortError");let p=draft.payload;if(p.kind!=="rows")throw new import_transfer_json2.ContentTransferError("unsupported-format","An Excel workbook needs table rows. Download it as CSV or JSON instead.");let columns=p.columns.filter(c=>c.visible&&c.exportable!==!1&&c.classification!=="secret"&&c.classification!=="credential"),XLSX2=await Promise.resolve().then(()=>(init_xlsx(),xlsx_exports));if(signal.aborted)throw new DOMException("Cancelled","AbortError");let sheet=XLSX2.utils.aoa_to_sheet([columns.map(c=>c.label),...p.rows.map(row=>columns.map(c=>cell(readTransferCell(row,c))))]),book=XLSX2.utils.book_new(),filename=options.filename??"export.xlsx",sheetName=filename.replace(/\.xlsx$/i,"").replace(/[\\/?*[\]:]/g," ").trim().slice(0,31)||"Data";XLSX2.utils.book_append_sheet(book,sheet,sheetName);let written=XLSX2.write(book,{type:"array",bookType:"xlsx",compression:!0});return sealTransferArtifact({snapshotId:draft.snapshotId,draftRevision:draft.revision,format:"xlsx",plainText:serialize(draft,"tsv").plainText,file:{filename,mime:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",bytes:written instanceof Uint8Array?written:new Uint8Array(written)},omissions:draft.omissions.map(o=>({...o}))})}},sheetJs=null;async function loadSheetJs(){return sheetJs??=await Promise.resolve().then(()=>(init_xlsx(),xlsx_exports)),sheetJs}function asCell(cell2){return{t:cell2.t,...cell2.v===void 0?{}:{v:cell2.v},...typeof cell2.w=="string"?{w:cell2.w}:{},...typeof cell2.f=="string"&&cell2.f.length>0?{f:cell2.f}:{},styled:!!cell2.s}}function wrapSheet(X,name,ws){let ref=ws["!ref"],range=ref?X.utils.decode_range(ref):null,merges=(ws["!merges"]??[]).map(m=>({s:{...m.s},e:{...m.e}}));return{name,range,merges,cell(r,c){let found=ws[X.utils.encode_cell({r,c})];return found?asCell(found):null},*cells(){for(let key in ws){if(key.startsWith("!"))continue;let found=ws[key];found&&(yield[X.utils.decode_cell(key),asCell(found)])}},grid(options={}){return X.utils.sheet_to_json(ws,{header:1,...options})},records(options={}){return X.utils.sheet_to_json(ws,options)}}}async function readWorkbookBytes(bytes,options={}){let X=await loadSheetJs(),book=X.read(bytes,{type:"array",...options.dates?{cellDates:!0}:{},...options.formulas?{cellFormula:!0}:{},...options.styles?{cellStyles:!0}:{}}),sheets=book.SheetNames.flatMap(name=>{let ws=book.Sheets[name];return ws?[wrapSheet(X,name,ws)]:[]});return{sheetNames:[...book.SheetNames],sheets,sheet:name=>sheets.find(s=>s.name===name)}}var XLSX_MIME="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";function dedupedSheetName(taken,raw){let name=raw.replace(/[\\/?*[\]:]/g," ").slice(0,31)||"Sheet1";if(!taken.includes(name))return name;let i=2;for(;taken.includes(`${name} (${i})`);)i++;return`${name} (${i})`}async function writeWorkbookBytes(value){let X=await loadSheetJs(),book=X.utils.book_new();for(let sheet of value.sheets){let ws={},maxR=-1,maxC=-1;sheet.rows.forEach((row,r)=>{row.forEach((raw,c)=>{if(raw==null)return;let typed=typeof raw=="object"?raw:null,v=typeof raw=="object"?raw.v:raw,out={t:typed?.t??(typeof v=="number"?"n":typeof v=="boolean"?"b":"s"),v:v??""};typed?.f&&(out.f=typed.f.replace(/^=/,"")),ws[X.utils.encode_cell({r,c})]=out,r>maxR&&(maxR=r),c>maxC&&(maxC=c)})}),ws["!ref"]=maxR<0?"A1:A1":X.utils.encode_range({s:{r:0,c:0},e:{r:maxR,c:maxC}}),sheet.merges?.length&&(ws["!merges"]=sheet.merges.map(m=>({s:{...m.s},e:{...m.e}}))),X.utils.book_append_sheet(book,ws,dedupedSheetName(book.SheetNames,sheet.name))}book.SheetNames.length===0&&X.utils.book_append_sheet(book,X.utils.aoa_to_sheet([[""]]),"Sheet1");let written=X.write(book,{type:"array",bookType:"xlsx",compression:!0});return written instanceof Uint8Array?written:new Uint8Array(written)}var workbookEngine={async build(draft,options,signal){if(signal.aborted)throw new DOMException("Cancelled","AbortError");let p=draft.payload;if(p.kind!=="registered"||p.format!=="workbook")throw new import_transfer_json2.ContentTransferError("unsupported-format","A formatted workbook needs workbook content. Download the table as Excel instead.");let value=p.value,bytes=await writeWorkbookBytes(value);if(signal.aborted)throw new DOMException("Cancelled","AbortError");let plainText=(0,import_delimited2.toDelimited)((value.sheets[0]?.rows??[]).map(row=>row.map(c=>c==null?null:typeof c=="object"?c.v??null:c)),{format:"tsv"});return sealTransferArtifact({snapshotId:draft.snapshotId,draftRevision:draft.revision,format:"xlsx-workbook",plainText,file:{filename:options.filename??"workbook.xlsx",mime:XLSX_MIME,bytes},omissions:draft.omissions.map(o=>({...o}))})}};
145
144
  /*! Bundled license information:
146
145
 
147
146
  xlsx/xlsx.mjs: