@xapi-js/core 1.3.0 → 1.4.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.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2022 Robert Soriano <https://github.com/wobsoriano>
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Robert Soriano <https://github.com/wobsoriano>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -25,6 +25,26 @@ deno add @xapi-ts/core
25
25
 
26
26
  `@xapi-ts/core` provides the fundamental classes and functions for working with X-API data.
27
27
 
28
+ ### Typed schemas
29
+
30
+ ```typescript
31
+ import { InferRoot, xapi } from '@xapi-js/core';
32
+
33
+ const schema = xapi.root({
34
+ parameters: { ErrorCode: xapi.int() },
35
+ datasets: {
36
+ users: xapi.dataset({
37
+ id: xapi.int(),
38
+ balance: xapi.bigdecimal(),
39
+ name: xapi.string({ size: 100 }),
40
+ photo: xapi.blob({ optional: true }),
41
+ }),
42
+ },
43
+ });
44
+
45
+ type Payload = InferRoot<typeof schema>;
46
+ ```
47
+
28
48
  ### Parsing X-API XML
29
49
 
30
50
  You can parse an X-API XML string into an `XapiRoot` object:
@@ -198,4 +218,4 @@ xapi.addDataset(productsDataset);
198
218
 
199
219
  const xmlOutput = write(xapi);
200
220
  console.log('생성된 XML:\n', xmlOutput);
201
- ```
221
+ ```
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
-
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  //#region src/types.ts
3
3
  /**
4
4
  * Defines the possible types of rows in an X-API dataset (e.g., "insert", "update", "delete").
@@ -51,7 +51,6 @@ var InvalidXmlError = class extends Error {
51
51
  this.name = "InvalidXmlError";
52
52
  }
53
53
  };
54
-
55
54
  //#endregion
56
55
  //#region src/xapi-data.ts
57
56
  /**
@@ -317,23 +316,30 @@ var Dataset = class {
317
316
  return this.rows.length;
318
317
  }
319
318
  };
320
-
321
319
  //#endregion
322
320
  //#region src/utils.ts
323
321
  function arrayBufferToString(buffer) {
324
322
  return new TextDecoder().decode(buffer);
325
323
  }
326
324
  /**
327
- * Creates an array of entities for parsing XML, including control characters.
328
- * @returns An array of objects, each with an `entity` (e.g., "&#1;") and its `value` (e.g., "\x01").
325
+ * Pre-defined XML entities for parsing, including control characters.
326
+ * Static array to avoid repeated allocation.
329
327
  */
330
- function makeParseEntities() {
331
- const entities$1 = [];
332
- for (let i = 1; i <= 32; i++) entities$1.push({
328
+ const PARSE_ENTITIES = (() => {
329
+ const entities = [];
330
+ for (let i = 1; i <= 32; i++) entities.push({
333
331
  entity: `&#${i};`,
334
332
  value: String.fromCharCode(i)
335
333
  });
336
- return entities$1;
334
+ return entities;
335
+ })();
336
+ /**
337
+ * Creates an array of entities for parsing XML, including control characters.
338
+ * @deprecated Use PARSE_ENTITIES directly for better performance
339
+ * @returns An array of objects, each with an `entity` (e.g., "&#1;") and its `value` (e.g., "\x01").
340
+ */
341
+ function makeParseEntities() {
342
+ return PARSE_ENTITIES.slice();
337
343
  }
338
344
  /**
339
345
  * Converts a Base64 string to a Uint8Array.
@@ -462,10 +468,10 @@ function stringToReadableStream(str) {
462
468
  function convertToColumnType(value, type) {
463
469
  switch (type) {
464
470
  case "INT":
465
- case "BIGDECIMAL":
466
471
  const intValue = parseInt(value, 10);
467
472
  return isNaN(intValue) ? value : intValue;
468
473
  case "FLOAT":
474
+ case "BIGDECIMAL":
469
475
  const floatValue = parseFloat(value);
470
476
  return isNaN(floatValue) ? value : floatValue;
471
477
  case "DECIMAL":
@@ -503,14 +509,21 @@ function convertToString(value, type) {
503
509
  default: return String(value);
504
510
  }
505
511
  }
506
- const entities = makeParseEntities();
512
+ /**
513
+ * Pre-compiled regex for control character entities.
514
+ * Created once to avoid repeated compilation.
515
+ */
516
+ const CONTROL_CHAR_ENTITY_REGEX = new RegExp(PARSE_ENTITIES.map((e) => e.entity.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"), "g");
517
+ /**
518
+ * Map for fast entity lookup during decoding.
519
+ */
520
+ const ENTITY_MAP = new Map(PARSE_ENTITIES.map((e) => [e.entity, e.value]));
507
521
  function _unescapeXml(str) {
508
522
  if (!str) return str;
523
+ if (str.indexOf("&") === -1) return str;
509
524
  let result = str.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, "\"").replace(/&apos;/g, "'").replace(/&amp;/g, "&");
510
- const regex = new RegExp(entities.map((e) => e.entity).join("|"), "g");
511
- result = result.replace(regex, (match) => {
512
- const entity = entities.find((e) => e.entity === match);
513
- return entity ? entity.value : match;
525
+ result = result.replace(CONTROL_CHAR_ENTITY_REGEX, (match) => {
526
+ return ENTITY_MAP.get(match) || match;
514
527
  });
515
528
  return result;
516
529
  }
@@ -621,6 +634,18 @@ function parseXml(xml) {
621
634
  return new XapiXmlParser(xml).parse();
622
635
  }
623
636
  /**
637
+ * Character codes for fast comparison
638
+ */
639
+ const CHAR_SPACE = 32;
640
+ const CHAR_TAB = 9;
641
+ const CHAR_LF = 10;
642
+ const CHAR_CR = 13;
643
+ const CHAR_GT = 62;
644
+ const CHAR_SLASH = 47;
645
+ const CHAR_EQUALS = 61;
646
+ const CHAR_QUOTE = 34;
647
+ const CHAR_APOS = 39;
648
+ /**
624
649
  * A lightweight XML parser optimized for X-API structure.
625
650
  */
626
651
  var XapiXmlParser = class {
@@ -636,12 +661,12 @@ var XapiXmlParser = class {
636
661
  while (this.pos < this.length) {
637
662
  this.skipWhitespace();
638
663
  if (this.pos >= this.length) break;
639
- if (this.peek(5) === "<?xml") {
664
+ if (this.matches("<?xml")) {
640
665
  this.skipUntil("?>");
641
666
  this.pos += 2;
642
667
  continue;
643
668
  }
644
- if (this.peek(4) === "<!--") {
669
+ if (this.matches("<!--")) {
645
670
  this.skipUntil("-->");
646
671
  this.pos += 3;
647
672
  continue;
@@ -659,11 +684,15 @@ var XapiXmlParser = class {
659
684
  if (this.current() === "/") return null;
660
685
  const tagName = this.parseTagName();
661
686
  if (!tagName) return null;
662
- const node = { tagName };
687
+ const node = {
688
+ tagName,
689
+ attributes: void 0,
690
+ children: void 0
691
+ };
663
692
  const attributes = this.parseAttributes();
664
693
  if (attributes && Object.keys(attributes).length > 0) node.attributes = attributes;
665
694
  this.skipWhitespace();
666
- if (this.peek(2) === "/>") {
695
+ if (this.matches("/>")) {
667
696
  this.pos += 2;
668
697
  return node;
669
698
  }
@@ -671,12 +700,12 @@ var XapiXmlParser = class {
671
700
  const children = [];
672
701
  let textContent = "";
673
702
  while (this.pos < this.length) {
674
- if (this.peek(9) === "<![CDATA[") {
703
+ if (this.matches("<![CDATA[")) {
675
704
  const cdataText = this.parseCDATA();
676
705
  if (cdataText !== null) textContent += cdataText;
677
706
  continue;
678
707
  }
679
- if (this.peek(2) === "</") {
708
+ if (this.matches("</")) {
680
709
  if (textContent) children.push(textContent);
681
710
  this.pos += 2;
682
711
  this.skipUntil(">");
@@ -699,21 +728,20 @@ var XapiXmlParser = class {
699
728
  return node;
700
729
  }
701
730
  parseTagName() {
702
- let name = "";
731
+ const start = this.pos;
703
732
  while (this.pos < this.length) {
704
- const ch = this.current();
705
- if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === ">" || ch === "/") break;
706
- name += ch;
733
+ const code = this.currentCharCode();
734
+ if (this.isWhitespace(code) || code === CHAR_GT || code === CHAR_SLASH) break;
707
735
  this.pos++;
708
736
  }
709
- return name;
737
+ return this.xml.substring(start, this.pos);
710
738
  }
711
739
  parseAttributes() {
712
740
  const attrs = {};
713
741
  while (this.pos < this.length) {
714
742
  this.skipWhitespace();
715
743
  const ch = this.current();
716
- if (ch === ">" || ch === "/" || this.peek(2) === "/>") break;
744
+ if (ch === ">" || ch === "/" || this.matches("/>")) break;
717
745
  const attrName = this.parseAttributeName();
718
746
  if (!attrName) break;
719
747
  this.skipWhitespace();
@@ -724,74 +752,95 @@ var XapiXmlParser = class {
724
752
  return Object.keys(attrs).length > 0 ? attrs : void 0;
725
753
  }
726
754
  parseAttributeName() {
727
- let name = "";
755
+ const start = this.pos;
728
756
  while (this.pos < this.length) {
729
- const ch = this.current();
730
- if (ch === "=" || ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === ">" || ch === "/") break;
731
- name += ch;
757
+ const code = this.currentCharCode();
758
+ if (code === CHAR_EQUALS || this.isWhitespace(code) || code === CHAR_GT || code === CHAR_SLASH) break;
732
759
  this.pos++;
733
760
  }
734
- return name;
761
+ return this.xml.substring(start, this.pos);
735
762
  }
736
763
  parseAttributeValue() {
737
- let value = "";
738
- const quote = this.current();
739
- if (quote !== "\"" && quote !== "'") {
764
+ const quoteCode = this.currentCharCode();
765
+ if (quoteCode !== CHAR_QUOTE && quoteCode !== CHAR_APOS) {
766
+ const start = this.pos;
740
767
  while (this.pos < this.length) {
741
- const ch = this.current();
742
- if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === ">" || ch === "/") break;
743
- value += ch;
768
+ const code = this.currentCharCode();
769
+ if (this.isWhitespace(code) || code === CHAR_GT || code === CHAR_SLASH) break;
744
770
  this.pos++;
745
771
  }
746
- return value;
772
+ return this.xml.substring(start, this.pos);
747
773
  }
748
774
  this.pos++;
775
+ const start = this.pos;
749
776
  while (this.pos < this.length) {
750
- const ch = this.current();
751
- if (ch === quote) {
777
+ if (this.currentCharCode() === quoteCode) {
778
+ const value = this.xml.substring(start, this.pos);
752
779
  this.pos++;
753
- break;
780
+ return value;
754
781
  }
755
- value += ch;
756
782
  this.pos++;
757
783
  }
758
- return value;
784
+ return this.xml.substring(start, this.pos);
759
785
  }
760
786
  parseCDATA() {
761
- if (this.peek(9) !== "<![CDATA[") return null;
787
+ if (!this.matches("<![CDATA[")) return null;
762
788
  this.pos += 9;
763
- let content = "";
764
- while (this.pos < this.length) {
765
- if (this.peek(3) === "]]>") {
766
- this.pos += 3;
767
- break;
768
- }
769
- content += this.current();
770
- this.pos++;
789
+ const endPos = this.findString("]]>");
790
+ if (endPos === -1) {
791
+ const content = this.xml.substring(this.pos);
792
+ this.pos = this.length;
793
+ return content;
771
794
  }
795
+ const content = this.xml.substring(this.pos, endPos);
796
+ this.pos = endPos + 3;
772
797
  return content;
773
798
  }
774
799
  skipWhitespace() {
775
800
  while (this.pos < this.length) {
776
- const ch = this.current();
777
- if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r") break;
801
+ const code = this.currentCharCode();
802
+ if (code !== CHAR_SPACE && code !== CHAR_TAB && code !== CHAR_LF && code !== CHAR_CR) break;
778
803
  this.pos++;
779
804
  }
780
805
  }
806
+ isWhitespace(code) {
807
+ return code === CHAR_SPACE || code === CHAR_TAB || code === CHAR_LF || code === CHAR_CR;
808
+ }
781
809
  skipUntil(target) {
782
- while (this.pos < this.length) {
783
- if (this.peek(target.length) === target) break;
784
- this.pos++;
785
- }
810
+ const pos = this.findString(target);
811
+ if (pos !== -1) this.pos = pos;
812
+ else this.pos = this.length;
786
813
  }
787
814
  current() {
788
815
  return this.xml[this.pos];
789
816
  }
790
- peek(length) {
791
- return this.xml.substring(this.pos, this.pos + length);
817
+ currentCharCode() {
818
+ return this.xml.charCodeAt(this.pos);
819
+ }
820
+ /**
821
+ * Checks if the string at the given position matches the target string.
822
+ * This avoids creating substring instances for comparison.
823
+ * @param str - The target string to match
824
+ * @param pos - The position to check from (default: current position)
825
+ */
826
+ matches(str, pos = this.pos) {
827
+ const len = str.length;
828
+ if (pos + len > this.length) return false;
829
+ for (let i = 0; i < len; i++) if (this.xml.charCodeAt(pos + i) !== str.charCodeAt(i)) return false;
830
+ return true;
831
+ }
832
+ /**
833
+ * Finds the position of a target string starting from the given position.
834
+ * @param str - The string to find
835
+ * @param startPos - Starting position (default: current position)
836
+ * @returns The position where the string is found, or -1 if not found
837
+ */
838
+ findString(str, startPos = this.pos) {
839
+ const maxPos = this.length - str.length;
840
+ for (let pos = startPos; pos <= maxPos; pos++) if (this.matches(str, pos)) return pos;
841
+ return -1;
792
842
  }
793
843
  };
794
-
795
844
  //#endregion
796
845
  //#region src/handler.ts
797
846
  let _options = {
@@ -826,28 +875,31 @@ function parse(xml) {
826
875
  if (typeof child === "string") continue;
827
876
  const tagName = child.tagName;
828
877
  if (tagName === "Parameters") parseParameters(child, xapiRoot);
829
- else if (tagName === "Dataset") {
830
- if (!child.attributes || !child.attributes.id) throw new InvalidXmlError("Dataset element must have an 'id' attribute");
831
- const datasetId = child.attributes.id;
832
- const dataset = new Dataset(datasetId);
833
- xapiRoot.addDataset(dataset);
834
- const datasetChildren = child.children;
835
- let columnInfoElement;
836
- let rowsElement;
837
- if (datasetChildren) for (let j = 0; j < datasetChildren.length; j++) {
838
- const datasetChild = datasetChildren[j];
839
- if (typeof datasetChild === "string") continue;
840
- const childTagName = datasetChild.tagName;
841
- if (childTagName === "ColumnInfo") columnInfoElement = datasetChild;
842
- else if (childTagName === "Rows") rowsElement = datasetChild;
843
- if (columnInfoElement && rowsElement) break;
844
- }
845
- parseColumnInfo(columnInfoElement, dataset);
846
- parseRows(rowsElement, dataset);
847
- }
878
+ else if (tagName === "Datasets") {
879
+ for (const datasetElement of child.children ?? []) if (typeof datasetElement !== "string" && datasetElement.tagName === "Dataset") parseDataset(datasetElement, xapiRoot);
880
+ } else if (tagName === "Dataset") parseDataset(child, xapiRoot);
848
881
  }
849
882
  return xapiRoot;
850
883
  }
884
+ function parseDataset(child, xapiRoot) {
885
+ if (!child.attributes || !child.attributes.id) throw new InvalidXmlError("Dataset element must have an 'id' attribute");
886
+ const datasetId = child.attributes.id;
887
+ const dataset = new Dataset(datasetId);
888
+ xapiRoot.addDataset(dataset);
889
+ const datasetChildren = child.children;
890
+ let columnInfoElement;
891
+ let rowsElement;
892
+ if (datasetChildren) for (let j = 0; j < datasetChildren.length; j++) {
893
+ const datasetChild = datasetChildren[j];
894
+ if (typeof datasetChild === "string") continue;
895
+ const childTagName = datasetChild.tagName;
896
+ if (childTagName === "ColumnInfo") columnInfoElement = datasetChild;
897
+ else if (childTagName === "Rows") rowsElement = datasetChild;
898
+ if (columnInfoElement && rowsElement) break;
899
+ }
900
+ parseColumnInfo(columnInfoElement, dataset);
901
+ parseRows(rowsElement, dataset);
902
+ }
851
903
  function parseValue(value, type = "STRING") {
852
904
  value = _unescapeXml(value);
853
905
  return _options.parseToTypes ? convertToColumnType(value, type) : value;
@@ -943,7 +995,7 @@ function parseParameters(parametersElement, xapiRoot) {
943
995
  const attrs = p.attributes;
944
996
  const id = attrs?.id;
945
997
  const type = attrs?.type || "STRING";
946
- const value = p.children?.[0];
998
+ const value = p.children?.[0] ?? attrs?.value;
947
999
  xapiRoot.addParameter({
948
1000
  id,
949
1001
  type,
@@ -1021,7 +1073,111 @@ function writeColumn(builder, dataset, col) {
1021
1073
  builder.writeElementWithText("Col", { id: col.id }, convertToString(col.value, colInfo.type));
1022
1074
  } else builder.writeStartElement("Col", { id: col.id }, true);
1023
1075
  }
1024
-
1076
+ //#endregion
1077
+ //#region src/schema.ts
1078
+ const defaultSizes = {
1079
+ STRING: 256,
1080
+ INT: 10,
1081
+ FLOAT: 20,
1082
+ DECIMAL: 20,
1083
+ BIGDECIMAL: 30,
1084
+ DATE: 8,
1085
+ DATETIME: 17,
1086
+ TIME: 6,
1087
+ BLOB: 1e3
1088
+ };
1089
+ function column(type, options = {}) {
1090
+ return {
1091
+ kind: "xapi-column",
1092
+ type,
1093
+ size: options.size ?? defaultSizes[type],
1094
+ optional: options.optional ?? false
1095
+ };
1096
+ }
1097
+ function defineDataset(columns) {
1098
+ return {
1099
+ kind: "xapi-dataset",
1100
+ columns
1101
+ };
1102
+ }
1103
+ function defineRoot(definition) {
1104
+ return {
1105
+ kind: "xapi-root",
1106
+ datasets: definition.datasets,
1107
+ parameters: definition.parameters ?? {}
1108
+ };
1109
+ }
1110
+ function defineOperation(definition) {
1111
+ return {
1112
+ kind: "xapi-operation",
1113
+ ...definition
1114
+ };
1115
+ }
1116
+ const xapi = {
1117
+ string: (options) => column("STRING", options),
1118
+ int: (options) => column("INT", options),
1119
+ float: (options) => column("FLOAT", options),
1120
+ decimal: (options) => column("DECIMAL", options),
1121
+ bigdecimal: (options) => column("BIGDECIMAL", options),
1122
+ date: (options) => column("DATE", options),
1123
+ datetime: (options) => column("DATETIME", options),
1124
+ time: (options) => column("TIME", options),
1125
+ blob: (options) => column("BLOB", options),
1126
+ dataset: defineDataset,
1127
+ root: defineRoot,
1128
+ operation: defineOperation
1129
+ };
1130
+ function encodeRoot(schema, value) {
1131
+ const root = new XapiRoot();
1132
+ for (const [id, parameterSchema] of Object.entries(schema.parameters)) {
1133
+ const parameterValue = value.parameters[id];
1134
+ if (parameterValue !== void 0 || !parameterSchema.optional) root.addParameter({
1135
+ id,
1136
+ type: parameterSchema.type,
1137
+ value: parameterValue
1138
+ });
1139
+ }
1140
+ for (const [datasetId, datasetSchema] of Object.entries(schema.datasets)) {
1141
+ const dataset = new Dataset(datasetId);
1142
+ for (const [id, columnSchema] of Object.entries(datasetSchema.columns)) dataset.addColumn({
1143
+ id,
1144
+ type: columnSchema.type,
1145
+ size: columnSchema.size
1146
+ });
1147
+ const rows = value.datasets[datasetId];
1148
+ for (const row of rows) {
1149
+ const rowIndex = dataset.newRow();
1150
+ for (const id of Object.keys(datasetSchema.columns)) dataset.setColumn(rowIndex, id, row[id]);
1151
+ }
1152
+ root.addDataset(dataset);
1153
+ }
1154
+ return root;
1155
+ }
1156
+ function decodeRoot(schema, root) {
1157
+ const parameters = {};
1158
+ for (const id of Object.keys(schema.parameters)) {
1159
+ const value = root.getParameter(id)?.value;
1160
+ if (value !== void 0) parameters[id] = value;
1161
+ }
1162
+ const datasets = {};
1163
+ for (const [datasetId, datasetSchema] of Object.entries(schema.datasets)) {
1164
+ const dataset = root.getDataset(datasetId);
1165
+ if (!dataset) {
1166
+ datasets[datasetId] = [];
1167
+ continue;
1168
+ }
1169
+ const constants = Object.fromEntries(dataset.getConstColumns().map(({ id, value }) => [id, value]));
1170
+ datasets[datasetId] = dataset.getRows().map((row) => {
1171
+ const value = { ...constants };
1172
+ for (const column of row.cols) if (column.id in datasetSchema.columns) value[column.id] = column.value;
1173
+ return value;
1174
+ });
1175
+ }
1176
+ return {
1177
+ parameters,
1178
+ datasets
1179
+ };
1180
+ }
1025
1181
  //#endregion
1026
1182
  exports.ColumnTypeError = ColumnTypeError;
1027
1183
  exports.Dataset = Dataset;
@@ -1038,7 +1194,12 @@ exports.columnType = columnType;
1038
1194
  exports.convertToColumnType = convertToColumnType;
1039
1195
  exports.convertToString = convertToString;
1040
1196
  exports.dateToString = dateToString;
1197
+ exports.decodeRoot = decodeRoot;
1198
+ exports.defineDataset = defineDataset;
1199
+ exports.defineOperation = defineOperation;
1200
+ exports.defineRoot = defineRoot;
1041
1201
  exports.encodeControlChars = encodeControlChars;
1202
+ exports.encodeRoot = encodeRoot;
1042
1203
  exports.escapeXml = escapeXml;
1043
1204
  exports.initXapi = initXapi;
1044
1205
  exports.isXapiRoot = isXapiRoot;
@@ -1049,4 +1210,5 @@ exports.rowType = rowType;
1049
1210
  exports.stringToDate = stringToDate;
1050
1211
  exports.stringToReadableStream = stringToReadableStream;
1051
1212
  exports.uint8ArrayToBase64 = uint8ArrayToBase64;
1052
- exports.write = write;
1213
+ exports.write = write;
1214
+ exports.xapi = xapi;
package/dist/index.d.cts CHANGED
@@ -341,6 +341,7 @@ type XmlNode = {
341
341
  declare function arrayBufferToString(buffer: ArrayBuffer): string;
342
342
  /**
343
343
  * Creates an array of entities for parsing XML, including control characters.
344
+ * @deprecated Use PARSE_ENTITIES directly for better performance
344
345
  * @returns An array of objects, each with an `entity` (e.g., "&#1;") and its `value` (e.g., "\x01").
345
346
  */
346
347
  declare function makeParseEntities(): {
@@ -468,4 +469,78 @@ declare class XmlStringBuilder {
468
469
  */
469
470
  declare function parseXml(xml: string): XmlNode[];
470
471
  //#endregion
471
- export { Col, Column, ColumnInfo, ColumnType, ColumnTypeError, ConstColumn, Dataset, InvalidXmlError, NexaVersion, Parameter, Row, RowType, Rows, StringWritableStream, XapiOptions, XapiParameters, XapiRoot, XapiValueType, XapiVersion, XmlNode, XmlStringBuilder, XplatformVersion, _unescapeXml, arrayBufferToString, base64ToUint8Array, columnType, convertToColumnType, convertToString, dateToString, encodeControlChars, escapeXml, initXapi, isXapiRoot, makeParseEntities, parse, parseXml, rowType, stringToDate, stringToReadableStream, uint8ArrayToBase64, write };
472
+ //#region src/schema.d.ts
473
+ type XapiTypeMap = {
474
+ STRING: string;
475
+ INT: number;
476
+ FLOAT: number;
477
+ DECIMAL: number;
478
+ BIGDECIMAL: number;
479
+ DATE: Date;
480
+ DATETIME: Date;
481
+ TIME: Date;
482
+ BLOB: Uint8Array;
483
+ };
484
+ interface XapiColumnSchema<T extends ColumnType = ColumnType, Optional extends boolean = boolean> {
485
+ readonly kind: "xapi-column";
486
+ readonly type: T;
487
+ readonly size: number;
488
+ readonly optional: Optional;
489
+ }
490
+ type XapiColumns = Record<string, XapiColumnSchema>;
491
+ interface XapiDatasetSchema<Columns extends XapiColumns = XapiColumns> {
492
+ readonly kind: "xapi-dataset";
493
+ readonly columns: Columns;
494
+ }
495
+ type XapiDatasets = Record<string, XapiDatasetSchema>;
496
+ interface XapiRootSchema<Datasets extends XapiDatasets = XapiDatasets, Parameters extends XapiColumns = XapiColumns> {
497
+ readonly kind: "xapi-root";
498
+ readonly datasets: Datasets;
499
+ readonly parameters: Parameters;
500
+ }
501
+ interface XapiOperation<Request extends XapiRootSchema = XapiRootSchema, Response extends XapiRootSchema = XapiRootSchema> {
502
+ readonly kind: "xapi-operation";
503
+ readonly request: Request;
504
+ readonly response: Response;
505
+ }
506
+ type RequiredColumnKeys<Columns extends XapiColumns> = { [Key in keyof Columns]-?: Columns[Key]["optional"] extends true ? never : Key; }[keyof Columns];
507
+ type OptionalColumnKeys<Columns extends XapiColumns> = { [Key in keyof Columns]-?: Columns[Key]["optional"] extends true ? Key : never; }[keyof Columns];
508
+ type InferColumns<Columns extends XapiColumns> = { [Key in RequiredColumnKeys<Columns>]: XapiTypeMap[Columns[Key]["type"]]; } & { [Key in OptionalColumnKeys<Columns>]?: XapiTypeMap[Columns[Key]["type"]]; };
509
+ type InferDataset<Schema extends XapiDatasetSchema> = InferColumns<Schema["columns"]>[];
510
+ type InferRoot<Schema extends XapiRootSchema> = {
511
+ parameters: InferColumns<Schema["parameters"]>;
512
+ datasets: { [Key in keyof Schema["datasets"]]: InferDataset<Schema["datasets"][Key]>; };
513
+ };
514
+ type RequestOf<Operation extends XapiOperation> = InferRoot<Operation["request"]>;
515
+ type ResponseOf<Operation extends XapiOperation> = InferRoot<Operation["response"]>;
516
+ type ColumnOptions<Optional extends boolean> = {
517
+ size?: number;
518
+ optional?: Optional;
519
+ };
520
+ declare function defineDataset<const Columns extends XapiColumns>(columns: Columns): XapiDatasetSchema<Columns>;
521
+ declare function defineRoot<const Datasets extends XapiDatasets, const Parameters extends XapiColumns = Record<never, never>>(definition: {
522
+ datasets: Datasets;
523
+ parameters?: Parameters;
524
+ }): XapiRootSchema<Datasets, Parameters>;
525
+ declare function defineOperation<const Request extends XapiRootSchema, const Response extends XapiRootSchema>(definition: {
526
+ request: Request;
527
+ response: Response;
528
+ }): XapiOperation<Request, Response>;
529
+ declare const xapi: {
530
+ readonly string: <const Optional extends boolean = false>(options?: ColumnOptions<Optional>) => XapiColumnSchema<"STRING", Optional>;
531
+ readonly int: <const Optional extends boolean = false>(options?: ColumnOptions<Optional>) => XapiColumnSchema<"INT", Optional>;
532
+ readonly float: <const Optional extends boolean = false>(options?: ColumnOptions<Optional>) => XapiColumnSchema<"FLOAT", Optional>;
533
+ readonly decimal: <const Optional extends boolean = false>(options?: ColumnOptions<Optional>) => XapiColumnSchema<"DECIMAL", Optional>;
534
+ readonly bigdecimal: <const Optional extends boolean = false>(options?: ColumnOptions<Optional>) => XapiColumnSchema<"BIGDECIMAL", Optional>;
535
+ readonly date: <const Optional extends boolean = false>(options?: ColumnOptions<Optional>) => XapiColumnSchema<"DATE", Optional>;
536
+ readonly datetime: <const Optional extends boolean = false>(options?: ColumnOptions<Optional>) => XapiColumnSchema<"DATETIME", Optional>;
537
+ readonly time: <const Optional extends boolean = false>(options?: ColumnOptions<Optional>) => XapiColumnSchema<"TIME", Optional>;
538
+ readonly blob: <const Optional extends boolean = false>(options?: ColumnOptions<Optional>) => XapiColumnSchema<"BLOB", Optional>;
539
+ readonly dataset: typeof defineDataset;
540
+ readonly root: typeof defineRoot;
541
+ readonly operation: typeof defineOperation;
542
+ };
543
+ declare function encodeRoot<Schema extends XapiRootSchema>(schema: Schema, value: InferRoot<Schema>): XapiRoot;
544
+ declare function decodeRoot<Schema extends XapiRootSchema>(schema: Schema, root: XapiRoot): InferRoot<Schema>;
545
+ //#endregion
546
+ export { Col, Column, ColumnInfo, ColumnType, ColumnTypeError, ConstColumn, Dataset, InferColumns, InferDataset, InferRoot, InvalidXmlError, NexaVersion, Parameter, RequestOf, ResponseOf, Row, RowType, Rows, StringWritableStream, XapiColumnSchema, XapiColumns, XapiDatasetSchema, XapiDatasets, XapiOperation, XapiOptions, XapiParameters, XapiRoot, XapiRootSchema, XapiTypeMap, XapiValueType, XapiVersion, XmlNode, XmlStringBuilder, XplatformVersion, _unescapeXml, arrayBufferToString, base64ToUint8Array, columnType, convertToColumnType, convertToString, dateToString, decodeRoot, defineDataset, defineOperation, defineRoot, encodeControlChars, encodeRoot, escapeXml, initXapi, isXapiRoot, makeParseEntities, parse, parseXml, rowType, stringToDate, stringToReadableStream, uint8ArrayToBase64, write, xapi };
package/dist/index.d.ts CHANGED
@@ -341,6 +341,7 @@ type XmlNode = {
341
341
  declare function arrayBufferToString(buffer: ArrayBuffer): string;
342
342
  /**
343
343
  * Creates an array of entities for parsing XML, including control characters.
344
+ * @deprecated Use PARSE_ENTITIES directly for better performance
344
345
  * @returns An array of objects, each with an `entity` (e.g., "&#1;") and its `value` (e.g., "\x01").
345
346
  */
346
347
  declare function makeParseEntities(): {
@@ -468,4 +469,78 @@ declare class XmlStringBuilder {
468
469
  */
469
470
  declare function parseXml(xml: string): XmlNode[];
470
471
  //#endregion
471
- export { Col, Column, ColumnInfo, ColumnType, ColumnTypeError, ConstColumn, Dataset, InvalidXmlError, NexaVersion, Parameter, Row, RowType, Rows, StringWritableStream, XapiOptions, XapiParameters, XapiRoot, XapiValueType, XapiVersion, XmlNode, XmlStringBuilder, XplatformVersion, _unescapeXml, arrayBufferToString, base64ToUint8Array, columnType, convertToColumnType, convertToString, dateToString, encodeControlChars, escapeXml, initXapi, isXapiRoot, makeParseEntities, parse, parseXml, rowType, stringToDate, stringToReadableStream, uint8ArrayToBase64, write };
472
+ //#region src/schema.d.ts
473
+ type XapiTypeMap = {
474
+ STRING: string;
475
+ INT: number;
476
+ FLOAT: number;
477
+ DECIMAL: number;
478
+ BIGDECIMAL: number;
479
+ DATE: Date;
480
+ DATETIME: Date;
481
+ TIME: Date;
482
+ BLOB: Uint8Array;
483
+ };
484
+ interface XapiColumnSchema<T extends ColumnType = ColumnType, Optional extends boolean = boolean> {
485
+ readonly kind: "xapi-column";
486
+ readonly type: T;
487
+ readonly size: number;
488
+ readonly optional: Optional;
489
+ }
490
+ type XapiColumns = Record<string, XapiColumnSchema>;
491
+ interface XapiDatasetSchema<Columns extends XapiColumns = XapiColumns> {
492
+ readonly kind: "xapi-dataset";
493
+ readonly columns: Columns;
494
+ }
495
+ type XapiDatasets = Record<string, XapiDatasetSchema>;
496
+ interface XapiRootSchema<Datasets extends XapiDatasets = XapiDatasets, Parameters extends XapiColumns = XapiColumns> {
497
+ readonly kind: "xapi-root";
498
+ readonly datasets: Datasets;
499
+ readonly parameters: Parameters;
500
+ }
501
+ interface XapiOperation<Request extends XapiRootSchema = XapiRootSchema, Response extends XapiRootSchema = XapiRootSchema> {
502
+ readonly kind: "xapi-operation";
503
+ readonly request: Request;
504
+ readonly response: Response;
505
+ }
506
+ type RequiredColumnKeys<Columns extends XapiColumns> = { [Key in keyof Columns]-?: Columns[Key]["optional"] extends true ? never : Key; }[keyof Columns];
507
+ type OptionalColumnKeys<Columns extends XapiColumns> = { [Key in keyof Columns]-?: Columns[Key]["optional"] extends true ? Key : never; }[keyof Columns];
508
+ type InferColumns<Columns extends XapiColumns> = { [Key in RequiredColumnKeys<Columns>]: XapiTypeMap[Columns[Key]["type"]]; } & { [Key in OptionalColumnKeys<Columns>]?: XapiTypeMap[Columns[Key]["type"]]; };
509
+ type InferDataset<Schema extends XapiDatasetSchema> = InferColumns<Schema["columns"]>[];
510
+ type InferRoot<Schema extends XapiRootSchema> = {
511
+ parameters: InferColumns<Schema["parameters"]>;
512
+ datasets: { [Key in keyof Schema["datasets"]]: InferDataset<Schema["datasets"][Key]>; };
513
+ };
514
+ type RequestOf<Operation extends XapiOperation> = InferRoot<Operation["request"]>;
515
+ type ResponseOf<Operation extends XapiOperation> = InferRoot<Operation["response"]>;
516
+ type ColumnOptions<Optional extends boolean> = {
517
+ size?: number;
518
+ optional?: Optional;
519
+ };
520
+ declare function defineDataset<const Columns extends XapiColumns>(columns: Columns): XapiDatasetSchema<Columns>;
521
+ declare function defineRoot<const Datasets extends XapiDatasets, const Parameters extends XapiColumns = Record<never, never>>(definition: {
522
+ datasets: Datasets;
523
+ parameters?: Parameters;
524
+ }): XapiRootSchema<Datasets, Parameters>;
525
+ declare function defineOperation<const Request extends XapiRootSchema, const Response extends XapiRootSchema>(definition: {
526
+ request: Request;
527
+ response: Response;
528
+ }): XapiOperation<Request, Response>;
529
+ declare const xapi: {
530
+ readonly string: <const Optional extends boolean = false>(options?: ColumnOptions<Optional>) => XapiColumnSchema<"STRING", Optional>;
531
+ readonly int: <const Optional extends boolean = false>(options?: ColumnOptions<Optional>) => XapiColumnSchema<"INT", Optional>;
532
+ readonly float: <const Optional extends boolean = false>(options?: ColumnOptions<Optional>) => XapiColumnSchema<"FLOAT", Optional>;
533
+ readonly decimal: <const Optional extends boolean = false>(options?: ColumnOptions<Optional>) => XapiColumnSchema<"DECIMAL", Optional>;
534
+ readonly bigdecimal: <const Optional extends boolean = false>(options?: ColumnOptions<Optional>) => XapiColumnSchema<"BIGDECIMAL", Optional>;
535
+ readonly date: <const Optional extends boolean = false>(options?: ColumnOptions<Optional>) => XapiColumnSchema<"DATE", Optional>;
536
+ readonly datetime: <const Optional extends boolean = false>(options?: ColumnOptions<Optional>) => XapiColumnSchema<"DATETIME", Optional>;
537
+ readonly time: <const Optional extends boolean = false>(options?: ColumnOptions<Optional>) => XapiColumnSchema<"TIME", Optional>;
538
+ readonly blob: <const Optional extends boolean = false>(options?: ColumnOptions<Optional>) => XapiColumnSchema<"BLOB", Optional>;
539
+ readonly dataset: typeof defineDataset;
540
+ readonly root: typeof defineRoot;
541
+ readonly operation: typeof defineOperation;
542
+ };
543
+ declare function encodeRoot<Schema extends XapiRootSchema>(schema: Schema, value: InferRoot<Schema>): XapiRoot;
544
+ declare function decodeRoot<Schema extends XapiRootSchema>(schema: Schema, root: XapiRoot): InferRoot<Schema>;
545
+ //#endregion
546
+ export { Col, Column, ColumnInfo, ColumnType, ColumnTypeError, ConstColumn, Dataset, InferColumns, InferDataset, InferRoot, InvalidXmlError, NexaVersion, Parameter, RequestOf, ResponseOf, Row, RowType, Rows, StringWritableStream, XapiColumnSchema, XapiColumns, XapiDatasetSchema, XapiDatasets, XapiOperation, XapiOptions, XapiParameters, XapiRoot, XapiRootSchema, XapiTypeMap, XapiValueType, XapiVersion, XmlNode, XmlStringBuilder, XplatformVersion, _unescapeXml, arrayBufferToString, base64ToUint8Array, columnType, convertToColumnType, convertToString, dateToString, decodeRoot, defineDataset, defineOperation, defineRoot, encodeControlChars, encodeRoot, escapeXml, initXapi, isXapiRoot, makeParseEntities, parse, parseXml, rowType, stringToDate, stringToReadableStream, uint8ArrayToBase64, write, xapi };
package/dist/index.js CHANGED
@@ -50,7 +50,6 @@ var InvalidXmlError = class extends Error {
50
50
  this.name = "InvalidXmlError";
51
51
  }
52
52
  };
53
-
54
53
  //#endregion
55
54
  //#region src/xapi-data.ts
56
55
  /**
@@ -316,23 +315,30 @@ var Dataset = class {
316
315
  return this.rows.length;
317
316
  }
318
317
  };
319
-
320
318
  //#endregion
321
319
  //#region src/utils.ts
322
320
  function arrayBufferToString(buffer) {
323
321
  return new TextDecoder().decode(buffer);
324
322
  }
325
323
  /**
326
- * Creates an array of entities for parsing XML, including control characters.
327
- * @returns An array of objects, each with an `entity` (e.g., "&#1;") and its `value` (e.g., "\x01").
324
+ * Pre-defined XML entities for parsing, including control characters.
325
+ * Static array to avoid repeated allocation.
328
326
  */
329
- function makeParseEntities() {
330
- const entities$1 = [];
331
- for (let i = 1; i <= 32; i++) entities$1.push({
327
+ const PARSE_ENTITIES = (() => {
328
+ const entities = [];
329
+ for (let i = 1; i <= 32; i++) entities.push({
332
330
  entity: `&#${i};`,
333
331
  value: String.fromCharCode(i)
334
332
  });
335
- return entities$1;
333
+ return entities;
334
+ })();
335
+ /**
336
+ * Creates an array of entities for parsing XML, including control characters.
337
+ * @deprecated Use PARSE_ENTITIES directly for better performance
338
+ * @returns An array of objects, each with an `entity` (e.g., "&#1;") and its `value` (e.g., "\x01").
339
+ */
340
+ function makeParseEntities() {
341
+ return PARSE_ENTITIES.slice();
336
342
  }
337
343
  /**
338
344
  * Converts a Base64 string to a Uint8Array.
@@ -461,10 +467,10 @@ function stringToReadableStream(str) {
461
467
  function convertToColumnType(value, type) {
462
468
  switch (type) {
463
469
  case "INT":
464
- case "BIGDECIMAL":
465
470
  const intValue = parseInt(value, 10);
466
471
  return isNaN(intValue) ? value : intValue;
467
472
  case "FLOAT":
473
+ case "BIGDECIMAL":
468
474
  const floatValue = parseFloat(value);
469
475
  return isNaN(floatValue) ? value : floatValue;
470
476
  case "DECIMAL":
@@ -502,14 +508,21 @@ function convertToString(value, type) {
502
508
  default: return String(value);
503
509
  }
504
510
  }
505
- const entities = makeParseEntities();
511
+ /**
512
+ * Pre-compiled regex for control character entities.
513
+ * Created once to avoid repeated compilation.
514
+ */
515
+ const CONTROL_CHAR_ENTITY_REGEX = new RegExp(PARSE_ENTITIES.map((e) => e.entity.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"), "g");
516
+ /**
517
+ * Map for fast entity lookup during decoding.
518
+ */
519
+ const ENTITY_MAP = new Map(PARSE_ENTITIES.map((e) => [e.entity, e.value]));
506
520
  function _unescapeXml(str) {
507
521
  if (!str) return str;
522
+ if (str.indexOf("&") === -1) return str;
508
523
  let result = str.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, "\"").replace(/&apos;/g, "'").replace(/&amp;/g, "&");
509
- const regex = new RegExp(entities.map((e) => e.entity).join("|"), "g");
510
- result = result.replace(regex, (match) => {
511
- const entity = entities.find((e) => e.entity === match);
512
- return entity ? entity.value : match;
524
+ result = result.replace(CONTROL_CHAR_ENTITY_REGEX, (match) => {
525
+ return ENTITY_MAP.get(match) || match;
513
526
  });
514
527
  return result;
515
528
  }
@@ -620,6 +633,18 @@ function parseXml(xml) {
620
633
  return new XapiXmlParser(xml).parse();
621
634
  }
622
635
  /**
636
+ * Character codes for fast comparison
637
+ */
638
+ const CHAR_SPACE = 32;
639
+ const CHAR_TAB = 9;
640
+ const CHAR_LF = 10;
641
+ const CHAR_CR = 13;
642
+ const CHAR_GT = 62;
643
+ const CHAR_SLASH = 47;
644
+ const CHAR_EQUALS = 61;
645
+ const CHAR_QUOTE = 34;
646
+ const CHAR_APOS = 39;
647
+ /**
623
648
  * A lightweight XML parser optimized for X-API structure.
624
649
  */
625
650
  var XapiXmlParser = class {
@@ -635,12 +660,12 @@ var XapiXmlParser = class {
635
660
  while (this.pos < this.length) {
636
661
  this.skipWhitespace();
637
662
  if (this.pos >= this.length) break;
638
- if (this.peek(5) === "<?xml") {
663
+ if (this.matches("<?xml")) {
639
664
  this.skipUntil("?>");
640
665
  this.pos += 2;
641
666
  continue;
642
667
  }
643
- if (this.peek(4) === "<!--") {
668
+ if (this.matches("<!--")) {
644
669
  this.skipUntil("-->");
645
670
  this.pos += 3;
646
671
  continue;
@@ -658,11 +683,15 @@ var XapiXmlParser = class {
658
683
  if (this.current() === "/") return null;
659
684
  const tagName = this.parseTagName();
660
685
  if (!tagName) return null;
661
- const node = { tagName };
686
+ const node = {
687
+ tagName,
688
+ attributes: void 0,
689
+ children: void 0
690
+ };
662
691
  const attributes = this.parseAttributes();
663
692
  if (attributes && Object.keys(attributes).length > 0) node.attributes = attributes;
664
693
  this.skipWhitespace();
665
- if (this.peek(2) === "/>") {
694
+ if (this.matches("/>")) {
666
695
  this.pos += 2;
667
696
  return node;
668
697
  }
@@ -670,12 +699,12 @@ var XapiXmlParser = class {
670
699
  const children = [];
671
700
  let textContent = "";
672
701
  while (this.pos < this.length) {
673
- if (this.peek(9) === "<![CDATA[") {
702
+ if (this.matches("<![CDATA[")) {
674
703
  const cdataText = this.parseCDATA();
675
704
  if (cdataText !== null) textContent += cdataText;
676
705
  continue;
677
706
  }
678
- if (this.peek(2) === "</") {
707
+ if (this.matches("</")) {
679
708
  if (textContent) children.push(textContent);
680
709
  this.pos += 2;
681
710
  this.skipUntil(">");
@@ -698,21 +727,20 @@ var XapiXmlParser = class {
698
727
  return node;
699
728
  }
700
729
  parseTagName() {
701
- let name = "";
730
+ const start = this.pos;
702
731
  while (this.pos < this.length) {
703
- const ch = this.current();
704
- if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === ">" || ch === "/") break;
705
- name += ch;
732
+ const code = this.currentCharCode();
733
+ if (this.isWhitespace(code) || code === CHAR_GT || code === CHAR_SLASH) break;
706
734
  this.pos++;
707
735
  }
708
- return name;
736
+ return this.xml.substring(start, this.pos);
709
737
  }
710
738
  parseAttributes() {
711
739
  const attrs = {};
712
740
  while (this.pos < this.length) {
713
741
  this.skipWhitespace();
714
742
  const ch = this.current();
715
- if (ch === ">" || ch === "/" || this.peek(2) === "/>") break;
743
+ if (ch === ">" || ch === "/" || this.matches("/>")) break;
716
744
  const attrName = this.parseAttributeName();
717
745
  if (!attrName) break;
718
746
  this.skipWhitespace();
@@ -723,74 +751,95 @@ var XapiXmlParser = class {
723
751
  return Object.keys(attrs).length > 0 ? attrs : void 0;
724
752
  }
725
753
  parseAttributeName() {
726
- let name = "";
754
+ const start = this.pos;
727
755
  while (this.pos < this.length) {
728
- const ch = this.current();
729
- if (ch === "=" || ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === ">" || ch === "/") break;
730
- name += ch;
756
+ const code = this.currentCharCode();
757
+ if (code === CHAR_EQUALS || this.isWhitespace(code) || code === CHAR_GT || code === CHAR_SLASH) break;
731
758
  this.pos++;
732
759
  }
733
- return name;
760
+ return this.xml.substring(start, this.pos);
734
761
  }
735
762
  parseAttributeValue() {
736
- let value = "";
737
- const quote = this.current();
738
- if (quote !== "\"" && quote !== "'") {
763
+ const quoteCode = this.currentCharCode();
764
+ if (quoteCode !== CHAR_QUOTE && quoteCode !== CHAR_APOS) {
765
+ const start = this.pos;
739
766
  while (this.pos < this.length) {
740
- const ch = this.current();
741
- if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === ">" || ch === "/") break;
742
- value += ch;
767
+ const code = this.currentCharCode();
768
+ if (this.isWhitespace(code) || code === CHAR_GT || code === CHAR_SLASH) break;
743
769
  this.pos++;
744
770
  }
745
- return value;
771
+ return this.xml.substring(start, this.pos);
746
772
  }
747
773
  this.pos++;
774
+ const start = this.pos;
748
775
  while (this.pos < this.length) {
749
- const ch = this.current();
750
- if (ch === quote) {
776
+ if (this.currentCharCode() === quoteCode) {
777
+ const value = this.xml.substring(start, this.pos);
751
778
  this.pos++;
752
- break;
779
+ return value;
753
780
  }
754
- value += ch;
755
781
  this.pos++;
756
782
  }
757
- return value;
783
+ return this.xml.substring(start, this.pos);
758
784
  }
759
785
  parseCDATA() {
760
- if (this.peek(9) !== "<![CDATA[") return null;
786
+ if (!this.matches("<![CDATA[")) return null;
761
787
  this.pos += 9;
762
- let content = "";
763
- while (this.pos < this.length) {
764
- if (this.peek(3) === "]]>") {
765
- this.pos += 3;
766
- break;
767
- }
768
- content += this.current();
769
- this.pos++;
788
+ const endPos = this.findString("]]>");
789
+ if (endPos === -1) {
790
+ const content = this.xml.substring(this.pos);
791
+ this.pos = this.length;
792
+ return content;
770
793
  }
794
+ const content = this.xml.substring(this.pos, endPos);
795
+ this.pos = endPos + 3;
771
796
  return content;
772
797
  }
773
798
  skipWhitespace() {
774
799
  while (this.pos < this.length) {
775
- const ch = this.current();
776
- if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r") break;
800
+ const code = this.currentCharCode();
801
+ if (code !== CHAR_SPACE && code !== CHAR_TAB && code !== CHAR_LF && code !== CHAR_CR) break;
777
802
  this.pos++;
778
803
  }
779
804
  }
805
+ isWhitespace(code) {
806
+ return code === CHAR_SPACE || code === CHAR_TAB || code === CHAR_LF || code === CHAR_CR;
807
+ }
780
808
  skipUntil(target) {
781
- while (this.pos < this.length) {
782
- if (this.peek(target.length) === target) break;
783
- this.pos++;
784
- }
809
+ const pos = this.findString(target);
810
+ if (pos !== -1) this.pos = pos;
811
+ else this.pos = this.length;
785
812
  }
786
813
  current() {
787
814
  return this.xml[this.pos];
788
815
  }
789
- peek(length) {
790
- return this.xml.substring(this.pos, this.pos + length);
816
+ currentCharCode() {
817
+ return this.xml.charCodeAt(this.pos);
818
+ }
819
+ /**
820
+ * Checks if the string at the given position matches the target string.
821
+ * This avoids creating substring instances for comparison.
822
+ * @param str - The target string to match
823
+ * @param pos - The position to check from (default: current position)
824
+ */
825
+ matches(str, pos = this.pos) {
826
+ const len = str.length;
827
+ if (pos + len > this.length) return false;
828
+ for (let i = 0; i < len; i++) if (this.xml.charCodeAt(pos + i) !== str.charCodeAt(i)) return false;
829
+ return true;
830
+ }
831
+ /**
832
+ * Finds the position of a target string starting from the given position.
833
+ * @param str - The string to find
834
+ * @param startPos - Starting position (default: current position)
835
+ * @returns The position where the string is found, or -1 if not found
836
+ */
837
+ findString(str, startPos = this.pos) {
838
+ const maxPos = this.length - str.length;
839
+ for (let pos = startPos; pos <= maxPos; pos++) if (this.matches(str, pos)) return pos;
840
+ return -1;
791
841
  }
792
842
  };
793
-
794
843
  //#endregion
795
844
  //#region src/handler.ts
796
845
  let _options = {
@@ -825,28 +874,31 @@ function parse(xml) {
825
874
  if (typeof child === "string") continue;
826
875
  const tagName = child.tagName;
827
876
  if (tagName === "Parameters") parseParameters(child, xapiRoot);
828
- else if (tagName === "Dataset") {
829
- if (!child.attributes || !child.attributes.id) throw new InvalidXmlError("Dataset element must have an 'id' attribute");
830
- const datasetId = child.attributes.id;
831
- const dataset = new Dataset(datasetId);
832
- xapiRoot.addDataset(dataset);
833
- const datasetChildren = child.children;
834
- let columnInfoElement;
835
- let rowsElement;
836
- if (datasetChildren) for (let j = 0; j < datasetChildren.length; j++) {
837
- const datasetChild = datasetChildren[j];
838
- if (typeof datasetChild === "string") continue;
839
- const childTagName = datasetChild.tagName;
840
- if (childTagName === "ColumnInfo") columnInfoElement = datasetChild;
841
- else if (childTagName === "Rows") rowsElement = datasetChild;
842
- if (columnInfoElement && rowsElement) break;
843
- }
844
- parseColumnInfo(columnInfoElement, dataset);
845
- parseRows(rowsElement, dataset);
846
- }
877
+ else if (tagName === "Datasets") {
878
+ for (const datasetElement of child.children ?? []) if (typeof datasetElement !== "string" && datasetElement.tagName === "Dataset") parseDataset(datasetElement, xapiRoot);
879
+ } else if (tagName === "Dataset") parseDataset(child, xapiRoot);
847
880
  }
848
881
  return xapiRoot;
849
882
  }
883
+ function parseDataset(child, xapiRoot) {
884
+ if (!child.attributes || !child.attributes.id) throw new InvalidXmlError("Dataset element must have an 'id' attribute");
885
+ const datasetId = child.attributes.id;
886
+ const dataset = new Dataset(datasetId);
887
+ xapiRoot.addDataset(dataset);
888
+ const datasetChildren = child.children;
889
+ let columnInfoElement;
890
+ let rowsElement;
891
+ if (datasetChildren) for (let j = 0; j < datasetChildren.length; j++) {
892
+ const datasetChild = datasetChildren[j];
893
+ if (typeof datasetChild === "string") continue;
894
+ const childTagName = datasetChild.tagName;
895
+ if (childTagName === "ColumnInfo") columnInfoElement = datasetChild;
896
+ else if (childTagName === "Rows") rowsElement = datasetChild;
897
+ if (columnInfoElement && rowsElement) break;
898
+ }
899
+ parseColumnInfo(columnInfoElement, dataset);
900
+ parseRows(rowsElement, dataset);
901
+ }
850
902
  function parseValue(value, type = "STRING") {
851
903
  value = _unescapeXml(value);
852
904
  return _options.parseToTypes ? convertToColumnType(value, type) : value;
@@ -942,7 +994,7 @@ function parseParameters(parametersElement, xapiRoot) {
942
994
  const attrs = p.attributes;
943
995
  const id = attrs?.id;
944
996
  const type = attrs?.type || "STRING";
945
- const value = p.children?.[0];
997
+ const value = p.children?.[0] ?? attrs?.value;
946
998
  xapiRoot.addParameter({
947
999
  id,
948
1000
  type,
@@ -1020,6 +1072,110 @@ function writeColumn(builder, dataset, col) {
1020
1072
  builder.writeElementWithText("Col", { id: col.id }, convertToString(col.value, colInfo.type));
1021
1073
  } else builder.writeStartElement("Col", { id: col.id }, true);
1022
1074
  }
1023
-
1024
1075
  //#endregion
1025
- export { ColumnTypeError, Dataset, InvalidXmlError, NexaVersion, StringWritableStream, XapiRoot, XmlStringBuilder, XplatformVersion, _unescapeXml, arrayBufferToString, base64ToUint8Array, columnType, convertToColumnType, convertToString, dateToString, encodeControlChars, escapeXml, initXapi, isXapiRoot, makeParseEntities, parse, parseXml, rowType, stringToDate, stringToReadableStream, uint8ArrayToBase64, write };
1076
+ //#region src/schema.ts
1077
+ const defaultSizes = {
1078
+ STRING: 256,
1079
+ INT: 10,
1080
+ FLOAT: 20,
1081
+ DECIMAL: 20,
1082
+ BIGDECIMAL: 30,
1083
+ DATE: 8,
1084
+ DATETIME: 17,
1085
+ TIME: 6,
1086
+ BLOB: 1e3
1087
+ };
1088
+ function column(type, options = {}) {
1089
+ return {
1090
+ kind: "xapi-column",
1091
+ type,
1092
+ size: options.size ?? defaultSizes[type],
1093
+ optional: options.optional ?? false
1094
+ };
1095
+ }
1096
+ function defineDataset(columns) {
1097
+ return {
1098
+ kind: "xapi-dataset",
1099
+ columns
1100
+ };
1101
+ }
1102
+ function defineRoot(definition) {
1103
+ return {
1104
+ kind: "xapi-root",
1105
+ datasets: definition.datasets,
1106
+ parameters: definition.parameters ?? {}
1107
+ };
1108
+ }
1109
+ function defineOperation(definition) {
1110
+ return {
1111
+ kind: "xapi-operation",
1112
+ ...definition
1113
+ };
1114
+ }
1115
+ const xapi = {
1116
+ string: (options) => column("STRING", options),
1117
+ int: (options) => column("INT", options),
1118
+ float: (options) => column("FLOAT", options),
1119
+ decimal: (options) => column("DECIMAL", options),
1120
+ bigdecimal: (options) => column("BIGDECIMAL", options),
1121
+ date: (options) => column("DATE", options),
1122
+ datetime: (options) => column("DATETIME", options),
1123
+ time: (options) => column("TIME", options),
1124
+ blob: (options) => column("BLOB", options),
1125
+ dataset: defineDataset,
1126
+ root: defineRoot,
1127
+ operation: defineOperation
1128
+ };
1129
+ function encodeRoot(schema, value) {
1130
+ const root = new XapiRoot();
1131
+ for (const [id, parameterSchema] of Object.entries(schema.parameters)) {
1132
+ const parameterValue = value.parameters[id];
1133
+ if (parameterValue !== void 0 || !parameterSchema.optional) root.addParameter({
1134
+ id,
1135
+ type: parameterSchema.type,
1136
+ value: parameterValue
1137
+ });
1138
+ }
1139
+ for (const [datasetId, datasetSchema] of Object.entries(schema.datasets)) {
1140
+ const dataset = new Dataset(datasetId);
1141
+ for (const [id, columnSchema] of Object.entries(datasetSchema.columns)) dataset.addColumn({
1142
+ id,
1143
+ type: columnSchema.type,
1144
+ size: columnSchema.size
1145
+ });
1146
+ const rows = value.datasets[datasetId];
1147
+ for (const row of rows) {
1148
+ const rowIndex = dataset.newRow();
1149
+ for (const id of Object.keys(datasetSchema.columns)) dataset.setColumn(rowIndex, id, row[id]);
1150
+ }
1151
+ root.addDataset(dataset);
1152
+ }
1153
+ return root;
1154
+ }
1155
+ function decodeRoot(schema, root) {
1156
+ const parameters = {};
1157
+ for (const id of Object.keys(schema.parameters)) {
1158
+ const value = root.getParameter(id)?.value;
1159
+ if (value !== void 0) parameters[id] = value;
1160
+ }
1161
+ const datasets = {};
1162
+ for (const [datasetId, datasetSchema] of Object.entries(schema.datasets)) {
1163
+ const dataset = root.getDataset(datasetId);
1164
+ if (!dataset) {
1165
+ datasets[datasetId] = [];
1166
+ continue;
1167
+ }
1168
+ const constants = Object.fromEntries(dataset.getConstColumns().map(({ id, value }) => [id, value]));
1169
+ datasets[datasetId] = dataset.getRows().map((row) => {
1170
+ const value = { ...constants };
1171
+ for (const column of row.cols) if (column.id in datasetSchema.columns) value[column.id] = column.value;
1172
+ return value;
1173
+ });
1174
+ }
1175
+ return {
1176
+ parameters,
1177
+ datasets
1178
+ };
1179
+ }
1180
+ //#endregion
1181
+ export { ColumnTypeError, Dataset, InvalidXmlError, NexaVersion, StringWritableStream, XapiRoot, XmlStringBuilder, XplatformVersion, _unescapeXml, arrayBufferToString, base64ToUint8Array, columnType, convertToColumnType, convertToString, dateToString, decodeRoot, defineDataset, defineOperation, defineRoot, encodeControlChars, encodeRoot, escapeXml, initXapi, isXapiRoot, makeParseEntities, parse, parseXml, rowType, stringToDate, stringToReadableStream, uint8ArrayToBase64, write, xapi };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xapi-js/core",
3
3
  "type": "module",
4
- "version": "1.3.0",
4
+ "version": "1.4.0",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -29,7 +29,7 @@
29
29
  "author": "Clickin <josh87786@gmail.com>",
30
30
  "devDependencies": {
31
31
  "@types/node": "^20.11.24",
32
- "typescript": "^5.3.3"
32
+ "typescript": "^7.0.2"
33
33
  },
34
34
  "scripts": {
35
35
  "build": "tsdown",