batis-xml 0.1.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-APACHE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/LICENSE-MIT ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ESPINS <dlwlalsggg@gmail.com>
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 ADDED
@@ -0,0 +1,152 @@
1
+ # batis-xml
2
+
3
+ WebAssembly bindings for [`batis-xml`](https://github.com/espins-labs/batis-xml)
4
+ — a parser and dynamic-SQL flattener for MyBatis and iBatis mapper XML.
5
+
6
+ ```js
7
+ const batisXml = require("batis-xml");
8
+
9
+ const bytes = fs.readFileSync("OrderMapper.xml"); // Buffer, NOT a decoded string
10
+ const result = JSON.parse(batisXml.parse(bytes)); // schema v1, see schema.d.ts
11
+ const dialect = batisXml.detect(bytes); // "mybatis" | "ibatis" | "unknown" -- cheap pre-check
12
+ ```
13
+
14
+ TypeScript consumers: `schema.d.ts` ships in this package (generated from
15
+ `schema/batis-xml.v1.json`, drift-checked in CI) —
16
+ `import type { ParseResult } from "batis-xml/schema"`.
17
+
18
+ **Node.js target only — no browser/bundler build yet.** This package is
19
+ built with `wasm-pack --target nodejs` (CommonJS, loads the `.wasm` via
20
+ `fs.readFileSync` at require time). It will not work as-is in a browser
21
+ or with a bundler expecting `--target web`/`--target bundler` output
22
+ (`fetch`-based instantiation, ESM). That's a separate build target to
23
+ add later, not a difference in the Rust source.
24
+
25
+ ## Four things that will bite you
26
+
27
+ **(a) Feed raw bytes — never a host-pre-decoded string.** Always pass the
28
+ file's original `Buffer`/`Uint8Array` to `parse`/`detect`, not a string
29
+ you already decoded (e.g. `fs.readFileSync(path, "utf-8")` then
30
+ re-encoded). `batis-xml` detects the encoding itself: a BOM sniff first
31
+ (UTF-16 LE/BE select directly; a UTF-8 BOM is stripped), then a UTF-8
32
+ attempt, then the XML declaration's own `encoding=` label
33
+ (BOM/declared-label-driven, covering every WHATWG encoding via
34
+ `encoding_rs` — Shift_JIS, GB18030, Big5, UTF-16, …), then an EUC-KR
35
+ heuristic for declaration-less legacy files; anything else decodes
36
+ lossily with a diagnostic. `result.encoding` reports which of these
37
+ actually won (see (b)). Feeding it bytes that already went through a host
38
+ UTF-8 decoder defeats all of that, since a genuinely non-UTF-8 file would
39
+ already have been mangled (replacement characters) before `batis-xml`
40
+ ever sees it. Read files as bytes and stay in bytes until you call in.
41
+
42
+ **(b) Spans are byte offsets into the UTF-8 text `batis-xml` itself
43
+ decoded — never JS string indices, and never the *original* file's raw
44
+ bytes for anything but a UTF-8 source.** Every `ByteSpan { start, end }`
45
+ in the JSON indexes into the UTF-8 bytes of the *decoded* text (see
46
+ `ByteSpan`'s own doc in `schema.d.ts`), while a JS string is indexed by
47
+ UTF-16 code units — these diverge the moment a multi-byte character
48
+ appears before the offset you care about. Worse, for anything decoded
49
+ from a non-UTF-8 encoding, the original file's raw bytes aren't even the
50
+ same *length* as the UTF-8 re-encoding the spans are offsets into — do
51
+ not slice the original `Buffer`/`Uint8Array` directly with these offsets.
52
+ `result.encoding` (the WHATWG name `TextDecoder` accepts directly) is
53
+ what makes this reproducible:
54
+
55
+ ```js
56
+ // bytes is the same Buffer/Uint8Array you fed to parse()
57
+ const decodedText = new TextDecoder(result.encoding).decode(bytes);
58
+ const utf8Bytes = new TextEncoder().encode(decodedText); // byte-identical to batis-xml's own internal String
59
+ const text = new TextDecoder("utf-8").decode(
60
+ utf8Bytes.subarray(span.start, span.end)
61
+ );
62
+ ```
63
+
64
+ If the input was plain UTF-8, `bytes` and `utf8Bytes` are already
65
+ byte-identical (decoding then re-encoding UTF-8 is a no-op), so slicing
66
+ `bytes` directly happens to work in that one case — but relying on that
67
+ silently breaks the moment a file turns out to be Shift_JIS/EUC-KR/
68
+ UTF-16/etc., which is exactly the failure mode `result.encoding` exists
69
+ to prevent. Always go through the `TextDecoder`/`TextEncoder` round trip
70
+ above regardless of what encoding you expect.
71
+
72
+ **(c) Build qualified names as `ns.id`, suffixed `@databaseId` when
73
+ present.** `Statement.database_id` is deliberately *not* folded into
74
+ `id` — that's the consumer's call. If you build a "qualified name" key
75
+ (e.g. `namespace.statementId`) and drop `database_id`, two dual-dialect
76
+ variants of the same statement (`databaseId="oracle"` / `"mysql"`)
77
+ collide onto one key. Recommended recipe:
78
+
79
+ ```ts
80
+ // Both mapper.namespace (Option<Spanned<String>> — a namespace-less iBatis
81
+ // sqlMap has none) and statement.id (Option<..>, absent + a
82
+ // MissingStatementId diagnostic when the tag is missing an id attribute)
83
+ // are nullable in the schema -- `statement.id` in particular is easy to
84
+ // miss, since template literals silently accept `undefined` (stringified
85
+ // as "undefined") instead of a type error, so `tsc --strict` alone won't
86
+ // catch skipping this guard.
87
+ if (statement.id == null) {
88
+ // No stable identifier for this statement at all -- MissingStatementId
89
+ // is already in result.diagnostics; skip it rather than fabricate a key.
90
+ } else {
91
+ const namespace = mapper.namespace?.value ?? "";
92
+ const qualifiedName = statement.database_id
93
+ ? `${namespace}.${statement.id.value}@${statement.database_id.value}`
94
+ : `${namespace}.${statement.id.value}`;
95
+ }
96
+ ```
97
+
98
+ **(d) `<include>` expands *before* `<where>`/`<set>`/`<trim>` in MyBatis/
99
+ iBatis — this crate doesn't expand it at all.** `IncludeRef` gives you the
100
+ raw `refid` plus a best-effort `IncludeTarget`; substituting the
101
+ referenced `<sql>` fragment's text in is on you.
102
+
103
+ The marker's textual form is a **stable v1 contract**: it renders in the
104
+ flattened SQL as the literal, fixed-prefix comment token
105
+ `` /* batis:include(<raw>) */ ``, where `<raw>` is `IncludeRef.raw`
106
+ verbatim (any literal `*/` inside it is replaced with `*_/` so it can't
107
+ terminate the comment early) — the same whether the target is `Local`,
108
+ `Qualified` (rendered with its original dot, e.g. `otherNs.frag`), or
109
+ `Dynamic` (the unresolved `${...}` text rendered as-is). Since the prefix
110
+ is fixed, `sqlText.indexOf("/* batis:include(")` (or a global regex) finds
111
+ every token directly; match each one to its `Statement.includes`/
112
+ `SqlFragment.includes` entry by reconstructing the exact token string
113
+ from that entry's `raw` field:
114
+
115
+ ```js
116
+ for (const inc of statement.includes) {
117
+ const token = `/* batis:include(${inc.value.raw.replaceAll("*/", "*_/")}) */`;
118
+ sqlText = sqlText.replace(token, resolveFragmentText(inc.value)); // your own lookup
119
+ }
120
+ ```
121
+
122
+ If the fragment you're substituting is itself multi-variant
123
+ (`sql.variants` on the fragment's own flattened output has more than one
124
+ entry), there's no single deterministic substitution — pick the
125
+ fragment's variant whose `conditions` match the same parameter state as
126
+ the *enclosing* statement's variant you're substituting into, not
127
+ `variants[0]` arbitrarily.
128
+
129
+ If you splice fragment text in *after* flattening (rather than before,
130
+ like the real engines do), redo the wrapper's own cleanup against that
131
+ substituted text: re-apply the leading-AND/OR strip or trailing-comma
132
+ strip when the include token was first/last inside the wrapper, and
133
+ treat a wrapper whose only content is an include token as conditional
134
+ (the fragment might expand to nothing). `result.diagnostics` carries
135
+ `include_at_wrapper_boundary` for every spot this applies to — each
136
+ diagnostic's `span` is the same original-XML span as the matching
137
+ `includes[]` entry's `span` (not a position in the flattened text) — see
138
+ the core crate's README ("Include expansion order") for the full
139
+ contract. As with any diagnostic, match on `code`
140
+ (`"include_at_wrapper_boundary"`), never on `message` text — messages
141
+ may be reworded between versions without that being a breaking change.
142
+
143
+ One more ordering guarantee worth relying on: `result.mapper.statements`
144
+ (and `fragments`/`result_maps`) preserve the source document's order.
145
+
146
+ ## One more thing
147
+
148
+ `IncludeRef.raw` (the raw, unparsed `refid` text) is kept even when
149
+ `IncludeTarget` is `Dynamic` (a `${}`-driven refid `batis-xml` can't
150
+ resolve statically) — inspect it for a static prefix or pattern to
151
+ attempt your own best-effort match, rather than treating every dynamic
152
+ include as a dead end.
@@ -0,0 +1,36 @@
1
+ This package's compiled WebAssembly binary statically links the Rust crate
2
+ `encoding_rs` (https://docs.rs/encoding_rs), used for encoding detection and
3
+ decoding. `encoding_rs` is itself dual-licensed under Apache-2.0 OR MIT (see
4
+ LICENSE-APACHE / LICENSE-MIT in this package), but it also embeds encoding
5
+ index/conversion data owned by WHATWG (Apple, Google, Mozilla, Microsoft)
6
+ that is licensed separately under BSD-3-Clause. That notice is reproduced
7
+ below, unmodified, per its own terms.
8
+
9
+ -------------------------------------------------------------------------
10
+
11
+ Copyright © WHATWG (Apple, Google, Mozilla, Microsoft).
12
+
13
+ Redistribution and use in source and binary forms, with or without
14
+ modification, are permitted provided that the following conditions are met:
15
+
16
+ 1. Redistributions of source code must retain the above copyright notice, this
17
+ list of conditions and the following disclaimer.
18
+
19
+ 2. Redistributions in binary form must reproduce the above copyright notice,
20
+ this list of conditions and the following disclaimer in the documentation
21
+ and/or other materials provided with the distribution.
22
+
23
+ 3. Neither the name of the copyright holder nor the names of its
24
+ contributors may be used to endorse or promote products derived from
25
+ this software without specific prior written permission.
26
+
27
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
28
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
30
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
31
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
33
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
34
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
35
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,37 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ /**
5
+ * Cheap dialect pre-check (MM-01 logic only, no statement/fragment/
6
+ * resultMap capture or flattening) -- returns the plain string
7
+ * (`"mybatis"` / `"ibatis"` / `"unknown"`, matching the schema's enum
8
+ * spelling), not a JSON-quoted one: unlike `parse`, this returns a single
9
+ * scalar with no nested model to keep schema-faithful, so there's no
10
+ * reason to make callers `JSON.parse` it. Guaranteed to agree with
11
+ * `parse(bytes)`'s `dialect` field (see the core crate's contract test).
12
+ * Same `Uint8Array`/`Buffer` input validation as `parse` (A16).
13
+ *
14
+ * B32 (cold code review, minor): typed as the `Dialect` union
15
+ * (`unchecked_return_type`) rather than the generic `string` wasm-bindgen
16
+ * would otherwise infer from `Result<String, _>` -- callers get real
17
+ * autocomplete/type-narrowing on the three actual values instead of an
18
+ * unconstrained string.
19
+ */
20
+ export function detect(input: Uint8Array): "mybatis" | "ibatis" | "unknown";
21
+
22
+ /**
23
+ * Parses mapper XML bytes and returns the `ParseResult` (schema v1) as a
24
+ * JSON string. Never panics: encoding/parse failures already surface as
25
+ * diagnostics inside the JSON per the core crate's contract, and the
26
+ * (practically unreachable, since `ParseResult` has no non-string map
27
+ * keys) serialization failure case falls back to the JSON literal `null`
28
+ * rather than trapping the wasm instance. Throws a `TypeError` (rejecting
29
+ * the call, not silently coercing) if `input` isn't a `Uint8Array`/`Buffer`
30
+ * -- see [`require_bytes`].
31
+ */
32
+ export function parse(input: Uint8Array): string;
33
+
34
+ /**
35
+ * This crate's version, from `Cargo.toml`.
36
+ */
37
+ export function version(): string;
@@ -0,0 +1,325 @@
1
+ /* @ts-self-types="./batis_xml_wasm.d.ts" */
2
+
3
+ /**
4
+ * Cheap dialect pre-check (MM-01 logic only, no statement/fragment/
5
+ * resultMap capture or flattening) -- returns the plain string
6
+ * (`"mybatis"` / `"ibatis"` / `"unknown"`, matching the schema's enum
7
+ * spelling), not a JSON-quoted one: unlike `parse`, this returns a single
8
+ * scalar with no nested model to keep schema-faithful, so there's no
9
+ * reason to make callers `JSON.parse` it. Guaranteed to agree with
10
+ * `parse(bytes)`'s `dialect` field (see the core crate's contract test).
11
+ * Same `Uint8Array`/`Buffer` input validation as `parse` (A16).
12
+ *
13
+ * B32 (cold code review, minor): typed as the `Dialect` union
14
+ * (`unchecked_return_type`) rather than the generic `string` wasm-bindgen
15
+ * would otherwise infer from `Result<String, _>` -- callers get real
16
+ * autocomplete/type-narrowing on the three actual values instead of an
17
+ * unconstrained string.
18
+ * @param {Uint8Array} input
19
+ * @returns {"mybatis" | "ibatis" | "unknown"}
20
+ */
21
+ function detect(input) {
22
+ let deferred2_0;
23
+ let deferred2_1;
24
+ try {
25
+ const ret = wasm.detect(input);
26
+ var ptr1 = ret[0];
27
+ var len1 = ret[1];
28
+ if (ret[3]) {
29
+ ptr1 = 0; len1 = 0;
30
+ throw takeFromExternrefTable0(ret[2]);
31
+ }
32
+ deferred2_0 = ptr1;
33
+ deferred2_1 = len1;
34
+ return getStringFromWasm0(ptr1, len1);
35
+ } finally {
36
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
37
+ }
38
+ }
39
+ exports.detect = detect;
40
+
41
+ /**
42
+ * Parses mapper XML bytes and returns the `ParseResult` (schema v1) as a
43
+ * JSON string. Never panics: encoding/parse failures already surface as
44
+ * diagnostics inside the JSON per the core crate's contract, and the
45
+ * (practically unreachable, since `ParseResult` has no non-string map
46
+ * keys) serialization failure case falls back to the JSON literal `null`
47
+ * rather than trapping the wasm instance. Throws a `TypeError` (rejecting
48
+ * the call, not silently coercing) if `input` isn't a `Uint8Array`/`Buffer`
49
+ * -- see [`require_bytes`].
50
+ * @param {Uint8Array} input
51
+ * @returns {string}
52
+ */
53
+ function parse(input) {
54
+ let deferred2_0;
55
+ let deferred2_1;
56
+ try {
57
+ const ret = wasm.parse(input);
58
+ var ptr1 = ret[0];
59
+ var len1 = ret[1];
60
+ if (ret[3]) {
61
+ ptr1 = 0; len1 = 0;
62
+ throw takeFromExternrefTable0(ret[2]);
63
+ }
64
+ deferred2_0 = ptr1;
65
+ deferred2_1 = len1;
66
+ return getStringFromWasm0(ptr1, len1);
67
+ } finally {
68
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
69
+ }
70
+ }
71
+ exports.parse = parse;
72
+
73
+ /**
74
+ * This crate's version, from `Cargo.toml`.
75
+ * @returns {string}
76
+ */
77
+ function version() {
78
+ let deferred1_0;
79
+ let deferred1_1;
80
+ try {
81
+ const ret = wasm.version();
82
+ deferred1_0 = ret[0];
83
+ deferred1_1 = ret[1];
84
+ return getStringFromWasm0(ret[0], ret[1]);
85
+ } finally {
86
+ wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
87
+ }
88
+ }
89
+ exports.version = version;
90
+ function __wbg_get_imports() {
91
+ const import0 = {
92
+ __proto__: null,
93
+ __wbg___wbindgen_boolean_get_fa956cfa2d1bd751: function(arg0) {
94
+ const v = arg0;
95
+ const ret = typeof(v) === 'boolean' ? v : undefined;
96
+ return isLikeNone(ret) ? 0xFFFFFF : ret ? 1 : 0;
97
+ },
98
+ __wbg___wbindgen_is_null_ea9085d691f535d3: function(arg0) {
99
+ const ret = arg0 === null;
100
+ return ret;
101
+ },
102
+ __wbg___wbindgen_is_object_a27215656b807791: function(arg0) {
103
+ const val = arg0;
104
+ const ret = typeof(val) === 'object' && val !== null;
105
+ return ret;
106
+ },
107
+ __wbg___wbindgen_is_undefined_c05833b95a3cf397: function(arg0) {
108
+ const ret = arg0 === undefined;
109
+ return ret;
110
+ },
111
+ __wbg___wbindgen_number_get_394265ed1e1b84ee: function(arg0, arg1) {
112
+ const obj = arg1;
113
+ const ret = typeof(obj) === 'number' ? obj : undefined;
114
+ getDataViewMemory0().setFloat64(arg0 + 8 * 1, isLikeNone(ret) ? 0 : ret, true);
115
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true);
116
+ },
117
+ __wbg___wbindgen_string_get_b0ca35b86a603356: function(arg0, arg1) {
118
+ const obj = arg1;
119
+ const ret = typeof(obj) === 'string' ? obj : undefined;
120
+ var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
121
+ var len1 = WASM_VECTOR_LEN;
122
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
123
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
124
+ },
125
+ __wbg___wbindgen_throw_344f42d3211c4765: function(arg0, arg1) {
126
+ throw new Error(getStringFromWasm0(arg0, arg1));
127
+ },
128
+ __wbg_byteLength_41862ca4020b9c43: function(arg0) {
129
+ const ret = arg0.byteLength;
130
+ return ret;
131
+ },
132
+ __wbg_construct_4e1a16de27aea5b9: function() { return handleError(function (arg0, arg1) {
133
+ const ret = Reflect.construct(arg0, arg1);
134
+ return ret;
135
+ }, arguments); },
136
+ __wbg_get_78f252d074a84d0b: function() { return handleError(function (arg0, arg1) {
137
+ const ret = Reflect.get(arg0, arg1);
138
+ return ret;
139
+ }, arguments); },
140
+ __wbg_instanceof_Array_de0fa65266a8c7c0: function(arg0) {
141
+ let result;
142
+ try {
143
+ result = arg0 instanceof Array;
144
+ } catch (_) {
145
+ result = false;
146
+ }
147
+ const ret = result;
148
+ return ret;
149
+ },
150
+ __wbg_instanceof_Uint8Array_309b927aaf7a3fc7: function(arg0) {
151
+ let result;
152
+ try {
153
+ result = arg0 instanceof Uint8Array;
154
+ } catch (_) {
155
+ result = false;
156
+ }
157
+ const ret = result;
158
+ return ret;
159
+ },
160
+ __wbg_length_1f0964f4a5e2c6d8: function(arg0) {
161
+ const ret = arg0.length;
162
+ return ret;
163
+ },
164
+ __wbg_new_5e245ef5857d7f33: function(arg0, arg1) {
165
+ const ret = new TypeError(getStringFromWasm0(arg0, arg1));
166
+ return ret;
167
+ },
168
+ __wbg_of_85f52f8b6491a7ca: function(arg0) {
169
+ const ret = Array.of(arg0);
170
+ return ret;
171
+ },
172
+ __wbg_prototypesetcall_4770620bbe4688a0: function(arg0, arg1, arg2) {
173
+ Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
174
+ },
175
+ __wbg_static_accessor_GLOBAL_4ef717fb391d88b7: function() {
176
+ const ret = typeof global === 'undefined' ? null : global;
177
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
178
+ },
179
+ __wbg_static_accessor_GLOBAL_THIS_8d1badc68b5a74f4: function() {
180
+ const ret = typeof globalThis === 'undefined' ? null : globalThis;
181
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
182
+ },
183
+ __wbg_static_accessor_SELF_146583524fe1469b: function() {
184
+ const ret = typeof self === 'undefined' ? null : self;
185
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
186
+ },
187
+ __wbg_static_accessor_WINDOW_f2829a2234d7819e: function() {
188
+ const ret = typeof window === 'undefined' ? null : window;
189
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
190
+ },
191
+ __wbindgen_cast_0000000000000001: function(arg0, arg1) {
192
+ // Cast intrinsic for `Ref(String) -> Externref`.
193
+ const ret = getStringFromWasm0(arg0, arg1);
194
+ return ret;
195
+ },
196
+ __wbindgen_init_externref_table: function() {
197
+ const table = wasm.__wbindgen_externrefs;
198
+ const offset = table.grow(4);
199
+ table.set(0, undefined);
200
+ table.set(offset + 0, undefined);
201
+ table.set(offset + 1, null);
202
+ table.set(offset + 2, true);
203
+ table.set(offset + 3, false);
204
+ },
205
+ };
206
+ return {
207
+ __proto__: null,
208
+ "./batis_xml_wasm_bg.js": import0,
209
+ };
210
+ }
211
+
212
+ function addToExternrefTable0(obj) {
213
+ const idx = wasm.__externref_table_alloc();
214
+ wasm.__wbindgen_externrefs.set(idx, obj);
215
+ return idx;
216
+ }
217
+
218
+ function getArrayU8FromWasm0(ptr, len) {
219
+ ptr = ptr >>> 0;
220
+ return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
221
+ }
222
+
223
+ let cachedDataViewMemory0 = null;
224
+ function getDataViewMemory0() {
225
+ if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
226
+ cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
227
+ }
228
+ return cachedDataViewMemory0;
229
+ }
230
+
231
+ function getStringFromWasm0(ptr, len) {
232
+ return decodeText(ptr >>> 0, len);
233
+ }
234
+
235
+ let cachedUint8ArrayMemory0 = null;
236
+ function getUint8ArrayMemory0() {
237
+ if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
238
+ cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
239
+ }
240
+ return cachedUint8ArrayMemory0;
241
+ }
242
+
243
+ function handleError(f, args) {
244
+ try {
245
+ return f.apply(this, args);
246
+ } catch (e) {
247
+ const idx = addToExternrefTable0(e);
248
+ wasm.__wbindgen_exn_store(idx);
249
+ }
250
+ }
251
+
252
+ function isLikeNone(x) {
253
+ return x === undefined || x === null;
254
+ }
255
+
256
+ function passStringToWasm0(arg, malloc, realloc) {
257
+ if (realloc === undefined) {
258
+ const buf = cachedTextEncoder.encode(arg);
259
+ const ptr = malloc(buf.length, 1) >>> 0;
260
+ getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
261
+ WASM_VECTOR_LEN = buf.length;
262
+ return ptr;
263
+ }
264
+
265
+ let len = arg.length;
266
+ let ptr = malloc(len, 1) >>> 0;
267
+
268
+ const mem = getUint8ArrayMemory0();
269
+
270
+ let offset = 0;
271
+
272
+ for (; offset < len; offset++) {
273
+ const code = arg.charCodeAt(offset);
274
+ if (code > 0x7F) break;
275
+ mem[ptr + offset] = code;
276
+ }
277
+ if (offset !== len) {
278
+ if (offset !== 0) {
279
+ arg = arg.slice(offset);
280
+ }
281
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
282
+ const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
283
+ const ret = cachedTextEncoder.encodeInto(arg, view);
284
+
285
+ offset += ret.written;
286
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
287
+ }
288
+
289
+ WASM_VECTOR_LEN = offset;
290
+ return ptr;
291
+ }
292
+
293
+ function takeFromExternrefTable0(idx) {
294
+ const value = wasm.__wbindgen_externrefs.get(idx);
295
+ wasm.__externref_table_dealloc(idx);
296
+ return value;
297
+ }
298
+
299
+ let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
300
+ cachedTextDecoder.decode();
301
+ function decodeText(ptr, len) {
302
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
303
+ }
304
+
305
+ const cachedTextEncoder = new TextEncoder();
306
+
307
+ if (!('encodeInto' in cachedTextEncoder)) {
308
+ cachedTextEncoder.encodeInto = function (arg, view) {
309
+ const buf = cachedTextEncoder.encode(arg);
310
+ view.set(buf);
311
+ return {
312
+ read: arg.length,
313
+ written: buf.length
314
+ };
315
+ };
316
+ }
317
+
318
+ let WASM_VECTOR_LEN = 0;
319
+
320
+ const wasmPath = `${__dirname}/batis_xml_wasm_bg.wasm`;
321
+ const wasmBytes = require('fs').readFileSync(wasmPath);
322
+ const wasmModule = new WebAssembly.Module(wasmBytes);
323
+ let wasmInstance = new WebAssembly.Instance(wasmModule, __wbg_get_imports());
324
+ let wasm = wasmInstance.exports;
325
+ wasm.__wbindgen_start();
Binary file
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "batis-xml",
3
+ "collaborators": [
4
+ "ESPINS <dlwlalsggg@gmail.com>"
5
+ ],
6
+ "description": "WebAssembly bindings for batis-xml (MyBatis/iBatis mapper XML parser)",
7
+ "version": "0.1.0",
8
+ "license": "MIT OR Apache-2.0",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/espins-labs/batis-xml.git"
12
+ },
13
+ "files": [
14
+ "batis_xml_wasm_bg.wasm",
15
+ "batis_xml_wasm.js",
16
+ "batis_xml_wasm.d.ts",
17
+ "schema.d.ts",
18
+ "LICENSE-MIT",
19
+ "LICENSE-APACHE",
20
+ "THIRD_PARTY_NOTICES"
21
+ ],
22
+ "main": "batis_xml_wasm.js",
23
+ "types": "batis_xml_wasm.d.ts",
24
+ "keywords": [
25
+ "mybatis",
26
+ "ibatis",
27
+ "wasm",
28
+ "parser"
29
+ ],
30
+ "engines": {
31
+ "node": ">=18"
32
+ },
33
+ "exports": {
34
+ ".": {
35
+ "types": "./batis_xml_wasm.d.ts",
36
+ "default": "./batis_xml_wasm.js"
37
+ },
38
+ "./schema": {
39
+ "types": "./schema.d.ts"
40
+ }
41
+ }
42
+ }
package/schema.d.ts ADDED
@@ -0,0 +1,320 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * This file was automatically generated by json-schema-to-typescript.
4
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
5
+ * and run json-schema-to-typescript to regenerate this file.
6
+ */
7
+
8
+ /**
9
+ * Additions only; removal/renaming is breaking. `#[non_exhaustive]` because new codes may appear within v1 itself (see `schema/README.md`) -- an exhaustive `match` outside this crate must add a wildcard arm, which is exactly the forward-compat behavior consumers need (an unrecognized code is not an error). `Other` covers the equivalent case for *deserialization* (this build reading JSON produced by a newer version).
10
+ *
11
+ * This interface was referenced by `ParseResult`'s JSON-Schema
12
+ * via the `definition` "DiagCode".
13
+ */
14
+ export type DiagCode =
15
+ | (
16
+ | "encoding_undetectable"
17
+ | "encoding_mismatch"
18
+ | "unclosed_tag"
19
+ | "duplicate_statement_id"
20
+ | "missing_statement_id"
21
+ | "branch_limit_exceeded"
22
+ | "unknown_element"
23
+ )
24
+ | "dangling_refid"
25
+ | "oversize_input"
26
+ | "duplicate_attribute"
27
+ | "invalid_entity"
28
+ | "unterminated_placeholder"
29
+ | "nesting_limit_exceeded"
30
+ | "include_at_wrapper_boundary"
31
+ | "other";
32
+ /**
33
+ * Closed set: an exhaustive `match` is a consumer feature (there's no forward-compat concern the way there is for `DiagCode`/`SqlText`, since `Unknown` already covers "neither of the two known dialects"). Adding a third dialect would be a v2.
34
+ *
35
+ * This interface was referenced by `ParseResult`'s JSON-Schema
36
+ * via the `definition` "Dialect".
37
+ */
38
+ export type Dialect = "mybatis" | "ibatis" | "unknown";
39
+ /**
40
+ * Closed set: an exhaustive `match` is a consumer feature. `Dynamic` already covers "can't be resolved statically" -- there's no third kind of refid target. Adding a variant here would be a v2.
41
+ *
42
+ * ## Expansion-order contract
43
+ *
44
+ * This crate never substitutes the referenced `<sql>` fragment's text in place of the `<include>` token -- resolving `IncludeTarget` to actual SQL and splicing it in is entirely the consumer's job. MyBatis/iBatis themselves expand `<include>` *before* evaluating `<where>`/`<set>`/ `<trim>` dynamic semantics, so a wrapper's leading-AND/OR strip or trailing-comma strip sees the fragment's real, substituted text. Flattening here with the token still in place means a consumer substituting fragment text in afterward must, at minimum:
45
+ *
46
+ * - re-apply the wrapper's leading-AND/OR / trailing-comma cleanup to the substituted text when the include token was first/last inside a `<where>`/`<set>`/`<trim>`, and - treat a wrapper whose only content is an include token as conditional (the fragment may expand to nothing).
47
+ *
48
+ * `DiagCode::IncludeAtWrapperBoundary` flags exactly the spots this applies to -- see the README's "Include expansion order" section for the full write-up.
49
+ *
50
+ * This interface was referenced by `ParseResult`'s JSON-Schema
51
+ * via the `definition` "IncludeTarget".
52
+ */
53
+ export type IncludeTarget =
54
+ | {
55
+ local: string;
56
+ }
57
+ | {
58
+ qualified: {
59
+ id: string;
60
+ ns: string;
61
+ [k: string]: unknown;
62
+ };
63
+ }
64
+ | "dynamic";
65
+ /**
66
+ * Result of dynamic-tag flattening (MM-06). Branch combination cap N=32 — total candidates per statement, computed as the cartesian product of tag branches.
67
+ *
68
+ * `#[non_exhaustive]`: a third fallback representation is plausible future work, and consumers matching exhaustively today must not break at compile time if one's added.
69
+ *
70
+ * This interface was referenced by `ParseResult`'s JSON-Schema
71
+ * via the `definition` "SqlText".
72
+ */
73
+ export type SqlText =
74
+ | {
75
+ variants: SqlVariant[];
76
+ }
77
+ | {
78
+ union: {
79
+ /**
80
+ * A **lower bound**, not necessarily the exact branch count: flattening bails out of cartesian expansion as soon as it's certain the total exceeds the cap, without finishing the (possibly much larger) exact count. Treat this as "at least this many, over the cap" rather than a precise total.
81
+ */
82
+ branch_count: number;
83
+ text: SqlString;
84
+ [k: string]: unknown;
85
+ };
86
+ };
87
+ /**
88
+ * Closed set: an exhaustive `match` is a consumer feature. `Generic` already covers "some other statement-like tag with no CRUD-verb equivalent" -- a new MyBatis/iBatis statement-like tag would be recognized by adding to `Generic`'s callers, not by growing this enum. Adding a variant here would be a v2.
89
+ *
90
+ * This interface was referenced by `ParseResult`'s JSON-Schema
91
+ * via the `definition` "StatementKind".
92
+ */
93
+ export type StatementKind = ("select" | "insert" | "update" | "delete" | "procedure") | "generic";
94
+
95
+ export interface ParseResult {
96
+ /**
97
+ * Parsing never fails — every anomaly accumulates here.
98
+ */
99
+ diagnostics: Diagnostic[];
100
+ dialect: Dialect;
101
+ /**
102
+ * The WHATWG name of the encoding the detection chain actually decoded the input with (`"UTF-8"`, `"EUC-KR"`, `"Shift_JIS"`, `"UTF-16LE"`, ...; `encoding_rs::Encoding::name()`'s own values, which are WHATWG-standard labels). `None` only when no decode was attempted at all (the raw-byte oversize cap rejected the input before `encoding.rs` ever ran) -- `parse` (already-decoded `&str` input) always reports `"UTF-8"`, since that's the one encoding a Rust `&str` can ever be.
103
+ *
104
+ * This is what makes the [`ByteSpan`] re-encoding caveat actionable: every span in this result is a byte offset into the UTF-8 text *after* decoding, so a consumer working with the **original** input bytes must decode them the same way first -- `new TextDecoder(result.encoding)` (Node.js/browsers both accept WHATWG labels directly), re-encode that decoded text to UTF-8, and slice spans against *that* buffer, not the original input bytes directly (see `wasm/README.md` for a worked recipe).
105
+ *
106
+ * BOM handling: for a document that opened with a byte-order mark, the mark is consumed during decoding and never appears in the decoded text -- spans are relative to the BOM-stripped content, same as every other span in this crate (offsets into what this crate's own decoding produced, not the original file's raw byte layout). A consumer re-decoding the original file with `TextDecoder` gets BOM-stripping for free (that's standard `TextDecoder` behavior for UTF-8/UTF-16 with a matching BOM), so no extra adjustment is needed on the consumer's side either.
107
+ */
108
+ encoding?: string | null;
109
+ /**
110
+ * `None` when the root element is not a mapper/sqlMap (reason is reported as a diagnostic).
111
+ */
112
+ mapper?: Mapper | null;
113
+ [k: string]: unknown;
114
+ }
115
+ /**
116
+ * This interface was referenced by `ParseResult`'s JSON-Schema
117
+ * via the `definition` "Diagnostic".
118
+ */
119
+ export interface Diagnostic {
120
+ code: DiagCode;
121
+ message: string;
122
+ span?: ByteSpan | null;
123
+ [k: string]: unknown;
124
+ }
125
+ /**
126
+ * Half-open range `[start, end)`: byte offsets into the UTF-8 text as decoded by this crate (identical to raw input bytes for UTF-8 sources; see the caveat below for re-encoded documents).
127
+ *
128
+ * Caveat: this holds exactly for UTF-8 input, which decoding leaves byte-for-byte unchanged. For documents decoded from any other encoding (EUC-KR, Shift_JIS, GB18030, UTF-16, ... -- see `encoding.rs`, which supports every WHATWG encoding via a BOM/declared-label-driven chain, not just EUC-KR), decoding to UTF-8 changes byte *widths* per character, so spans on such documents are offsets into the re-encoded UTF-8 string, not the original raw bytes. This applies uniformly to every re-encoded document, not just Korean legacy files. Consumers reading spans back against a source file must decode that source the same way this crate did before slicing.
129
+ *
130
+ * A leading byte-order mark is never part of this text either way: a BOM is consumed during decoding for [`crate::parse_bytes`] (see [`ParseResult::encoding`]'s doc comment) and stripped from the input string itself for [`crate::parse`] (a caller can hand it an already-decoded string that still carries a BOM, e.g. read from a file without stripping it first) -- both entry points agree that every span is relative to the BOM-stripped content.
131
+ *
132
+ * This interface was referenced by `ParseResult`'s JSON-Schema
133
+ * via the `definition` "ByteSpan".
134
+ */
135
+ export interface ByteSpan {
136
+ end: number;
137
+ start: number;
138
+ [k: string]: unknown;
139
+ }
140
+ /**
141
+ * This interface was referenced by `ParseResult`'s JSON-Schema
142
+ * via the `definition` "Mapper".
143
+ */
144
+ export interface Mapper {
145
+ fragments: SqlFragment[];
146
+ /**
147
+ * Usually `None` for iBatis sqlMaps (observed in the wild: the prefix lives inside the statement id itself, e.g. `WidgetDAO.getWidget`).
148
+ */
149
+ namespace?: SpannedFor_String | null;
150
+ result_maps: ResultMap[];
151
+ statements: Statement[];
152
+ [k: string]: unknown;
153
+ }
154
+ /**
155
+ * This interface was referenced by `ParseResult`'s JSON-Schema
156
+ * via the `definition` "SqlFragment".
157
+ */
158
+ export interface SqlFragment {
159
+ id: SpannedFor_String;
160
+ /**
161
+ * Nested includes inside the fragment (MM-04).
162
+ */
163
+ includes: SpannedFor_IncludeRef[];
164
+ /**
165
+ * Full original extent: opening-tag start → subtree end.
166
+ */
167
+ span: ByteSpan;
168
+ sql: SqlText;
169
+ [k: string]: unknown;
170
+ }
171
+ /**
172
+ * This interface was referenced by `ParseResult`'s JSON-Schema
173
+ * via the `definition` "Spanned_for_String".
174
+ */
175
+ export interface SpannedFor_String {
176
+ span: ByteSpan;
177
+ value: string;
178
+ [k: string]: unknown;
179
+ }
180
+ /**
181
+ * This interface was referenced by `ParseResult`'s JSON-Schema
182
+ * via the `definition` "Spanned_for_IncludeRef".
183
+ */
184
+ export interface SpannedFor_IncludeRef {
185
+ span: ByteSpan;
186
+ value: IncludeRef;
187
+ [k: string]: unknown;
188
+ }
189
+ /**
190
+ * The include-token textual contract -- a stable part of the v1 output.
191
+ *
192
+ * Every `<include refid="...">` marker renders into the flattened [`SqlText`] as a SQL block comment: an opening `/`+`*`, the literal text `batis:include(`, this struct's own `raw` field, a closing `)`, then the closing `*`+`/`. `raw` is rendered verbatim -- the unparsed `refid` attribute value -- **except** any literal `*` immediately followed by `/` inside it is rewritten to `*` + `_` + `/`, so the token can never terminate its own enclosing comment early (a `refid` is untrusted XML attribute content, not something this crate controls the shape of). This holds regardless of `target`'s classification: `Local("frag")` renders the comment around `frag` verbatim; `Qualified { ns: "otherNs", id: "frag" }` renders the *original, still-dotted* text `otherNs.frag` (`raw` is the whole unparsed attribute value; `ns`/`id` are just it split on the last dot for convenience, not a separate rendering); `Dynamic` renders the literal, unresolved `${...}` text as-is.
193
+ *
194
+ * **Locating tokens**: since the token's opening (`/`+`*` followed by the literal text `batis:include(`) is a fixed prefix, a plain substring search over the flattened SQL text finds every token directly -- no need to reconstruct it from `raw` first. Each token's position correlates 1:1 with one entry in the owning [`Statement::includes`]/[`SqlFragment::includes`] list: match by `Spanned::span`, which is the *original XML* span of the `<include>` element (not a position in the flattened text) -- the same span a [`DiagCode::IncludeAtWrapperBoundary`] diagnostic reports when this token sits at a `<where>`/`<set>`/`<trim>` boundary (see that variant's own doc comment, and the README's "Include expansion order" section, for the substitution contract itself).
195
+ *
196
+ * **Substituting a fragment with multiple variants**: a referenced `<sql>` fragment is itself flattened to a [`SqlText`], which may be `Variants` (several condition-gated alternatives) rather than one fixed string. There is no single deterministic substitution in that case -- the fragment's *own* active variant depends on the same runtime parameter state as the enclosing statement's variant does, so a consumer substituting fragment text into one variant of the parent statement must pick the matching variant of the fragment (by `conditions`), not an arbitrary one (e.g. `variants[0]`).
197
+ *
198
+ * **Document order**: [`Mapper::statements`] (and `fragments`/ `result_maps`) preserve source document order -- safe to assume when resolving forward/backward references across statements in one file.
199
+ *
200
+ * **Diagnostic messages are not a stable matching surface.** `message` strings may be reworded between versions without that being a breaking change; match on [`Diagnostic::code`] instead.
201
+ *
202
+ * This interface was referenced by `ParseResult`'s JSON-Schema
203
+ * via the `definition` "IncludeRef".
204
+ */
205
+ export interface IncludeRef {
206
+ raw: string;
207
+ target: IncludeTarget;
208
+ [k: string]: unknown;
209
+ }
210
+ /**
211
+ * This interface was referenced by `ParseResult`'s JSON-Schema
212
+ * via the `definition` "SqlVariant".
213
+ */
214
+ export interface SqlVariant {
215
+ /**
216
+ * The `test` expressions (verbatim) that activate this variant -- positive-only, and not a full boolean formula.
217
+ *
218
+ * Only the `test` conditions of `<if>`/dynamic tags whose branch is actually *taken* in this variant's path through the tag tree are recorded here, in document order. An `<if>`'s *not-taken* path contributes an alternative with `conditions: []` (empty) -- which is indistinguishable, at the type level, from a statement that had no `<if>` at all. This is by design (recording "this condition was false" would require inventing a negated-expression representation this crate doesn't have a use for elsewhere), but it means an empty `conditions` list is not itself proof that a variant is unconditional -- a consumer that needs that distinction has to correlate against the source XML's own dynamic-tag structure.
219
+ *
220
+ * `SqlText::Variants` as a whole lists *candidate* SQL shapes, not a runtime guarantee: this crate has no visibility into actual parameter values, so it cannot say which variant (if any single one) will execute for a given call -- only that these are the shapes the dynamic-tag tree can produce, gated by the conditions listed here.
221
+ */
222
+ conditions: string[];
223
+ text: SqlString;
224
+ [k: string]: unknown;
225
+ }
226
+ /**
227
+ * Flattened SQL text plus a mapping back to the source.
228
+ *
229
+ * This interface was referenced by `ParseResult`'s JSON-Schema
230
+ * via the `definition` "SqlString".
231
+ */
232
+ export interface SqlString {
233
+ /**
234
+ * (synthetic-text offset, original byte offset) segment-start pairs.
235
+ *
236
+ * Only the first column -- the synthetic-text offset (position in `text` above) -- is strictly increasing (B45, cold code review): each entry marks where a new mapped segment starts in the flattened output, so later entries always point further into `text` than earlier ones. The second column -- the original byte offset into the source document -- has no such guarantee, and legitimately repeats or goes backwards at synthetic tokens. Wrapper-added text (a `<trim>`/`<where>`/`<set>`/`<foreach>`'s `prefix`/`open`/`close`/`separator` etc.) has no original bytes of its own, so its span_map entry reuses the wrapper tag's own span start -- for *every* synthetic token the wrapper contributes, including a trailing `close`. E.g. `<foreach open="(" close=")">` around `#{id}` flattens to `"(?)"` with `span_map == [(0, wrapper_start), (1, <raw offset of #{id}>), (2, wrapper_start)]` -- the raw column rises across the body then drops back to `wrapper_start` for the closing `)`, which sits earlier in the source than the body it follows in `text`. Consumers must sort/dedupe on the first column only; the second is not monotonic and must not be assumed so.
237
+ */
238
+ span_map: [number, number][];
239
+ /**
240
+ * Placeholders already normalized: `#{..}` → `?`, `${..}` → `__BATIS_DYN__`.
241
+ */
242
+ text: string;
243
+ [k: string]: unknown;
244
+ }
245
+ /**
246
+ * This interface was referenced by `ParseResult`'s JSON-Schema
247
+ * via the `definition` "ResultMap".
248
+ */
249
+ export interface ResultMap {
250
+ extends?: SpannedFor_String | null;
251
+ id: SpannedFor_String;
252
+ mappings: ColumnMapping[];
253
+ /**
254
+ * Full original extent: opening-tag start → subtree end.
255
+ */
256
+ span: ByteSpan;
257
+ type_ref?: SpannedFor_ClassRef | null;
258
+ [k: string]: unknown;
259
+ }
260
+ /**
261
+ * This interface was referenced by `ParseResult`'s JSON-Schema
262
+ * via the `definition` "ColumnMapping".
263
+ */
264
+ export interface ColumnMapping {
265
+ column?: string | null;
266
+ property?: string | null;
267
+ [k: string]: unknown;
268
+ }
269
+ /**
270
+ * This interface was referenced by `ParseResult`'s JSON-Schema
271
+ * via the `definition` "Spanned_for_ClassRef".
272
+ */
273
+ export interface SpannedFor_ClassRef {
274
+ span: ByteSpan;
275
+ value: ClassRef;
276
+ [k: string]: unknown;
277
+ }
278
+ /**
279
+ * Alias resolution is the consumer's job — only the raw text is kept.
280
+ *
281
+ * This interface was referenced by `ParseResult`'s JSON-Schema
282
+ * via the `definition` "ClassRef".
283
+ */
284
+ export interface ClassRef {
285
+ raw: string;
286
+ [k: string]: unknown;
287
+ }
288
+ /**
289
+ * This interface was referenced by `ParseResult`'s JSON-Schema
290
+ * via the `definition` "Statement".
291
+ */
292
+ export interface Statement {
293
+ /**
294
+ * MyBatis per-vendor branching (`databaseId="oracle"` etc.) — `None` when neither this statement nor (for a `<selectKey>`-synthesized statement) its parent declares one. Distinguishes otherwise duplicate ids (MM-03).
295
+ *
296
+ * Inheritance rule (A20/A22, cold code review): a `<selectKey>` child statement (synthesized as `"{parent_id}!selectKey"`, see `id`) reads its *own* `databaseId` attribute first; only when it has none does it inherit the parent statement's `databaseId`. An explicit `databaseId` on the `<selectKey>` itself always wins.
297
+ *
298
+ * When inherited, `span` points at the **parent's** `databaseId` attribute, not anywhere inside this child statement's own `span` -- the parent's attribute is textually outside the child's subtree (a `<selectKey>` never contains its enclosing statement's opening tag). This is the one case where a `Spanned` value's `span` is legitimately outside the `Statement` it's attached to; every other `Spanned`/`ByteSpan` field on `Statement` points within `span`.
299
+ */
300
+ database_id?: SpannedFor_String | null;
301
+ /**
302
+ * `None` when missing, plus a `MissingStatementId` diagnostic. Synthesized ids are never invented.
303
+ */
304
+ id?: SpannedFor_String | null;
305
+ includes: SpannedFor_IncludeRef[];
306
+ kind: StatementKind;
307
+ param_class?: SpannedFor_ClassRef | null;
308
+ /**
309
+ * Expression paths collected from `#{a.b}` / `${c}` (MM-07).
310
+ */
311
+ property_paths: SpannedFor_String[];
312
+ result_class?: SpannedFor_ClassRef | null;
313
+ result_map_ref?: SpannedFor_String | null;
314
+ /**
315
+ * Full original extent of the statement: opening-tag start → subtree end (i.e. past its closing tag, or its own end for a self-closed element).
316
+ */
317
+ span: ByteSpan;
318
+ sql: SqlText;
319
+ [k: string]: unknown;
320
+ }