tsx-sql 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GuXianWN
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,169 @@
1
+ # tsx-sql
2
+
3
+ Compile TSX SQL fragments into parameterized SQL.
4
+
5
+ `tsx-sql` is a small SQL generation library. It lets you write dynamic SQL with TSX tags, then compile it into SQL text and parameter values.
6
+
7
+ It does not execute SQL, manage database connections, model schemas, or replace your ORM. Use the generated SQL with your database client or ORM of choice.
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ npm install tsx-sql
13
+ ```
14
+
15
+ ## TypeScript Setup
16
+
17
+ Configure TypeScript to use the `tsx-sql` JSX runtime:
18
+
19
+ ```json
20
+ {
21
+ "compilerOptions": {
22
+ "jsx": "react-jsx",
23
+ "jsxImportSource": "tsx-sql"
24
+ }
25
+ }
26
+ ```
27
+
28
+ Use `.tsx` files for SQL fragments written with JSX syntax.
29
+
30
+ ## Basic Usage
31
+
32
+ ```tsx
33
+ import { $, compile, If, Where } from "tsx-sql";
34
+
35
+ const name: string | undefined = "Tom";
36
+ const age: number | undefined = undefined;
37
+
38
+ const query = compile(
39
+ <>
40
+ SELECT * FROM users
41
+ <Where>
42
+ <If test={name !== undefined}> AND name = { $(name) }</If>
43
+ <If test={age !== undefined}> AND age = { $(age) }</If>
44
+ </Where>
45
+ </>
46
+ );
47
+
48
+ console.log(query);
49
+ ```
50
+
51
+ Output:
52
+
53
+ ```ts
54
+ {
55
+ sql: "SELECT * FROM users WHERE name = ?",
56
+ values: ["Tom"],
57
+ text: "SELECT * FROM users WHERE name = 'Tom'"
58
+ }
59
+ ```
60
+
61
+ ## Dynamic Conditions
62
+
63
+ Use `If` to include SQL only when a condition is true.
64
+
65
+ ```tsx
66
+ import { $, compile, If } from "tsx-sql";
67
+
68
+ const name: string | undefined = "Tom";
69
+
70
+ const query = compile(
71
+ <>
72
+ SELECT * FROM users
73
+ <If test={name !== undefined}> WHERE name = { $(name) }</If>
74
+ </>
75
+ );
76
+ ```
77
+
78
+ ## Where
79
+
80
+ `Where` follows the MyBatis-style behavior:
81
+
82
+ - emits `WHERE` only when its children produce SQL
83
+ - removes a leading `AND` or `OR`
84
+
85
+ ```tsx
86
+ import { $, compile, If, Where } from "tsx-sql";
87
+
88
+ const name: string | undefined = "Tom";
89
+ const age: number | undefined = undefined;
90
+
91
+ const query = compile(
92
+ <>
93
+ SELECT * FROM users
94
+ <Where>
95
+ <If test={name !== undefined}> AND name = { $(name) }</If>
96
+ <If test={age !== undefined}> AND age = { $(age) }</If>
97
+ </Where>
98
+ </>
99
+ );
100
+ ```
101
+
102
+ Output:
103
+
104
+ ```ts
105
+ {
106
+ sql: "SELECT * FROM users WHERE name = ?",
107
+ values: ["Tom"],
108
+ text: "SELECT * FROM users WHERE name = 'Tom'"
109
+ }
110
+ ```
111
+
112
+ ## Execute With Your Database Library
113
+
114
+ `compile` returns a plain object:
115
+
116
+ ```ts
117
+ interface CompileResult {
118
+ sql: string;
119
+ values: unknown[];
120
+ text: string;
121
+ }
122
+ ```
123
+
124
+ Use `sql` and `values` with a mature database client that accepts `?` placeholders.
125
+
126
+ ```ts
127
+ await connection.query(query.sql, query.values);
128
+ ```
129
+
130
+ `text` is intended for debugging and logging. Do not use it as executable SQL.
131
+
132
+ ## API
133
+
134
+ ### `compile(node)`
135
+
136
+ Compiles a TSX SQL node into:
137
+
138
+ - `sql`: SQL with `?` placeholders
139
+ - `values`: parameter values in order
140
+ - `text`: debug SQL with escaped values
141
+
142
+ ### `$(value)`
143
+
144
+ Marks a value as a SQL parameter. Raw `{value}` expressions are not treated as SQL parameters; wrap values with `$()`.
145
+
146
+ ```tsx
147
+ WHERE id = { $(id) }
148
+ ```
149
+
150
+ ### `<If test={boolean}>`
151
+
152
+ Includes its children when `test` is true.
153
+
154
+ ### `<Where>`
155
+
156
+ Adds `WHERE` when children are not empty, and removes a leading `AND` or `OR`.
157
+
158
+ ## Scope
159
+
160
+ This project only generates SQL.
161
+
162
+ It intentionally does not provide:
163
+
164
+ - database drivers
165
+ - connection pooling
166
+ - query execution
167
+ - schema modeling
168
+ - migrations
169
+ - ORM behavior
package/dist/index.cjs ADDED
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ $: () => $,
24
+ If: () => If,
25
+ Where: () => Where,
26
+ compile: () => compile
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/internal/escape.ts
31
+ function escapeSqlTextValue(value) {
32
+ if (value === null || value === void 0) {
33
+ return "NULL";
34
+ }
35
+ if (typeof value === "number") {
36
+ return Number.isFinite(value) ? String(value) : "NULL";
37
+ }
38
+ if (typeof value === "bigint") {
39
+ return value.toString();
40
+ }
41
+ if (typeof value === "boolean") {
42
+ return value ? "TRUE" : "FALSE";
43
+ }
44
+ if (value instanceof Date) {
45
+ return quoteString(value.toISOString());
46
+ }
47
+ return quoteString(String(value));
48
+ }
49
+ function quoteString(value) {
50
+ return `'${value.replaceAll("'", "''")}'`;
51
+ }
52
+
53
+ // src/node.ts
54
+ var FRAGMENT_SYMBOL = "tsx-sql.fragment";
55
+ var SqlFragment = Symbol.for(FRAGMENT_SYMBOL);
56
+
57
+ // src/internal/resolve.ts
58
+ function resolveNode(input) {
59
+ if (Array.isArray(input)) {
60
+ return input.flatMap(resolveNode);
61
+ }
62
+ if (typeof input === "string") {
63
+ return [input];
64
+ }
65
+ if (input.kind === "param") {
66
+ return [input];
67
+ }
68
+ if (input.kind === "element") {
69
+ if (input.type === SqlFragment) {
70
+ return resolveNode(input.children ?? "");
71
+ }
72
+ return resolveNode(input.type(componentProps(input)));
73
+ }
74
+ return assertNever(input);
75
+ }
76
+ function componentProps(element) {
77
+ return {
78
+ ...element.props,
79
+ children: element.children
80
+ };
81
+ }
82
+ function assertNever(value) {
83
+ throw new TypeError(`Unsupported SQL node: ${String(value)}`);
84
+ }
85
+
86
+ // src/compile.ts
87
+ function compile(input) {
88
+ const sqlParts = [];
89
+ const values = [];
90
+ const textParts = [];
91
+ for (const node of resolveNode(input)) {
92
+ if (typeof node === "string") {
93
+ sqlParts.push(node);
94
+ textParts.push(node);
95
+ continue;
96
+ }
97
+ sqlParts.push("?");
98
+ values.push(node.value);
99
+ textParts.push(escapeSqlTextValue(node.value));
100
+ }
101
+ return {
102
+ sql: compactWhitespace(sqlParts.join("")),
103
+ values,
104
+ text: textParts.join("")
105
+ };
106
+ }
107
+ function compactWhitespace(source) {
108
+ return source.replace(/\s+/g, " ").trim();
109
+ }
110
+
111
+ // src/param.ts
112
+ function $(value) {
113
+ return {
114
+ kind: "param",
115
+ value
116
+ };
117
+ }
118
+
119
+ // src/tags/if.ts
120
+ function If(props) {
121
+ return props.test ? props.children ?? "" : "";
122
+ }
123
+
124
+ // src/tags/where.ts
125
+ function Where(props) {
126
+ const children = removeLeadingAndOr(resolveNode(props.children ?? ""));
127
+ if (isBlank(children)) {
128
+ return "";
129
+ }
130
+ return [" WHERE ", children];
131
+ }
132
+ function removeLeadingAndOr(nodes) {
133
+ const firstConditionIndex = nodes.findIndex((node) => typeof node !== "string" || node.trim().length > 0);
134
+ if (firstConditionIndex === -1) {
135
+ return [];
136
+ }
137
+ const firstCondition = nodes[firstConditionIndex];
138
+ if (typeof firstCondition !== "string") {
139
+ return nodes;
140
+ }
141
+ return [
142
+ ...nodes.slice(0, firstConditionIndex),
143
+ removeLeadingAndOrText(firstCondition),
144
+ ...nodes.slice(firstConditionIndex + 1)
145
+ ];
146
+ }
147
+ function removeLeadingAndOrText(text) {
148
+ return text.replace(/^\s*(AND|OR)\b\s*/i, "");
149
+ }
150
+ function isBlank(nodes) {
151
+ return nodes.every((node) => typeof node === "string" && node.trim().length === 0);
152
+ }
153
+ // Annotate the CommonJS export names for ESM import in node:
154
+ 0 && (module.exports = {
155
+ $,
156
+ If,
157
+ Where,
158
+ compile
159
+ });
160
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/internal/escape.ts","../src/node.ts","../src/internal/resolve.ts","../src/compile.ts","../src/param.ts","../src/tags/if.ts","../src/tags/where.ts"],"sourcesContent":["export { compile } from \"./compile\";\nexport { $ } from \"./param\";\nexport { If } from \"./tags/if\";\nexport { Where } from \"./tags/where\";\nexport type { CompileResult } from \"./compile\";\nexport type { IfProps } from \"./tags/if\";\nexport type { SqlParam } from \"./param\";\n","export function escapeSqlTextValue(value: unknown): string {\r\n if (value === null || value === undefined) {\r\n return \"NULL\";\r\n }\r\n\r\n if (typeof value === \"number\") {\r\n return Number.isFinite(value) ? String(value) : \"NULL\";\r\n }\r\n\r\n if (typeof value === \"bigint\") {\r\n return value.toString();\r\n }\r\n\r\n if (typeof value === \"boolean\") {\r\n return value ? \"TRUE\" : \"FALSE\";\r\n }\r\n\r\n if (value instanceof Date) {\r\n return quoteString(value.toISOString());\r\n }\r\n\r\n return quoteString(String(value));\r\n}\r\n\r\nfunction quoteString(value: string): string {\r\n return `'${value.replaceAll(\"'\", \"''\")}'`;\r\n}\r\n","import type { SqlParam } from \"./param\";\n\nconst FRAGMENT_SYMBOL = \"tsx-sql.fragment\";\n\n// SQL fragments are transparent wrappers for <>...</>.\nexport const SqlFragment = Symbol.for(FRAGMENT_SYMBOL);\n\r\n// Any piece of SQL content that compile(...) knows how to read.\n// You can write:\n// <>WHERE name = { $(\"Tom\") }</>\n// TSX stores those children as:\r\n// [\"WHERE name = \", SqlParam]\r\n// Nested tags are also SqlNode values, so children can contain more elements.\nexport type SqlNode = string | SqlParam | SqlElement | SqlNode[];\n\ntype SqlProps = Record<string, unknown>;\n\nexport type SqlComponentProps = SqlProps & { children?: SqlNode };\n\n// A function that can be used as a TSX SQL tag.\n// You can write:\n// <If test={name}>...</If>\n// The element stores the If function in its type field, and compile(...)\n// calls that function to decide which SqlNode should be compiled next.\nexport type SqlComponent = (props: SqlComponentProps) => SqlNode;\n\r\n// The object created by createNode(...) after TypeScript compiles TSX.\r\n// You can write:\r\n// <If test={name}>AND name = { $(\"Tom\") }</If>\r\n// Runtime stores:\r\n// { type: If, props: { test: name, children: [\"AND name = \", SqlParam] } }\r\n// Fragment elements use SqlFragment as their type and only wrap children.\r\nexport interface SqlElement {\n kind: \"element\";\n type: typeof SqlFragment | SqlComponent;\n props: SqlProps;\n children?: SqlNode;\n}\n\n// TypeScript calls this through jsx/jsxs/jsxDEV after compiling TSX.\n// We keep the node shape tiny: just the tag/component type and its props.\nexport function createNode(type: SqlElement[\"type\"], props: (SqlProps & { children?: SqlNode }) | null): SqlElement {\n const { children, ...restProps } = props ?? {};\n\n return {\n kind: \"element\",\n type,\n props: restProps,\n children\n };\n}\n","import { SqlFragment, type SqlComponentProps, type SqlElement, type SqlNode } from \"../node\";\nimport type { SqlParam } from \"tsx-sql\";\n\nexport type ResolvedSqlNode = string | SqlParam;\n\nexport function resolveNode(input: SqlNode): ResolvedSqlNode[] {\n if (Array.isArray(input)) {\n return input.flatMap(resolveNode);\n }\n\n if (typeof input === \"string\") {\n return [input];\n }\n\n if (input.kind === \"param\") {\n return [input];\n }\n\n if (input.kind === \"element\") {\n if (input.type === SqlFragment) {\n return resolveNode(input.children ?? \"\");\n }\n\n return resolveNode(input.type(componentProps(input)));\n }\n\n return assertNever(input);\n}\n\nfunction componentProps(element: SqlElement): SqlComponentProps {\n return {\n ...element.props,\n children: element.children\n };\n}\n\nfunction assertNever(value: never): never {\n throw new TypeError(`Unsupported SQL node: ${String(value)}`);\n}\n","import { escapeSqlTextValue } from \"./internal/escape\";\nimport { resolveNode } from \"./internal/resolve\";\nimport type { SqlNode } from \"./node\";\n\nexport interface CompileResult {\n sql: string;\n values: unknown[];\n text: string;\n}\n\nexport function compile(input: SqlNode): CompileResult {\n const sqlParts: string[] = [];\n const values: unknown[] = [];\n const textParts: string[] = [];\n\n for (const node of resolveNode(input)) {\n // Raw strings are SQL text, not parameters.\n if (typeof node === \"string\") {\n sqlParts.push(node);\n textParts.push(node);\n continue;\n }\n\n // Parameters become placeholders in executable SQL and values in order.\n sqlParts.push(\"?\");\n values.push(node.value);\n textParts.push(escapeSqlTextValue(node.value));\n }\n\n return {\n sql: compactWhitespace(sqlParts.join(\"\")),\n values,\n text: textParts.join(\"\")\n };\n}\n\nfunction compactWhitespace(source: string): string {\n return source.replace(/\\s+/g, \" \").trim();\n}\n","export interface SqlParam {\n kind: \"param\";\n value: unknown;\n}\n\nexport function $(value: unknown): SqlParam {\n return {\n kind: \"param\",\n value\n };\n}\n","import type { SqlComponentProps, SqlNode } from \"../node\";\n\nexport interface IfProps extends SqlComponentProps {\n test: boolean;\n}\n\r\nexport function If(props: IfProps): SqlNode {\r\n return props.test ? props.children ?? \"\" : \"\";\r\n}\r\n","import { resolveNode, type ResolvedSqlNode } from \"../internal/resolve\";\nimport type { SqlComponentProps, SqlNode } from \"../node\";\n\nexport function Where(props: SqlComponentProps): SqlNode {\n const children = removeLeadingAndOr(resolveNode(props.children ?? \"\"));\n\n if (isBlank(children)) {\n return \"\";\n }\n\n return [\" WHERE \", children];\n}\n\nfunction removeLeadingAndOr(nodes: ResolvedSqlNode[]): ResolvedSqlNode[] {\n // MyBatis where ignores leading whitespace, then removes a leading AND/OR.\n const firstConditionIndex = nodes.findIndex((node) => typeof node !== \"string\" || node.trim().length > 0);\n if (firstConditionIndex === -1) {\n return [];\n }\n\n const firstCondition = nodes[firstConditionIndex];\n if (typeof firstCondition !== \"string\") {\n return nodes;\n }\n\n return [\n ...nodes.slice(0, firstConditionIndex),\n removeLeadingAndOrText(firstCondition),\n ...nodes.slice(firstConditionIndex + 1)\n ];\n}\n\nfunction removeLeadingAndOrText(text: string): string {\n return text.replace(/^\\s*(AND|OR)\\b\\s*/i, \"\");\n}\n\nfunction isBlank(nodes: ResolvedSqlNode[]): boolean {\n return nodes.every((node) => typeof node === \"string\" && node.trim().length === 0);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,18 @@
1
+ import { S as SqlNode, a as SqlComponentProps } from './node-DV0niL4A.cjs';
2
+ export { $, b as SqlParam } from './node-DV0niL4A.cjs';
3
+
4
+ interface CompileResult {
5
+ sql: string;
6
+ values: unknown[];
7
+ text: string;
8
+ }
9
+ declare function compile(input: SqlNode): CompileResult;
10
+
11
+ interface IfProps extends SqlComponentProps {
12
+ test: boolean;
13
+ }
14
+ declare function If(props: IfProps): SqlNode;
15
+
16
+ declare function Where(props: SqlComponentProps): SqlNode;
17
+
18
+ export { type CompileResult, If, type IfProps, Where, compile };
@@ -0,0 +1,18 @@
1
+ import { S as SqlNode, a as SqlComponentProps } from './node-DV0niL4A.js';
2
+ export { $, b as SqlParam } from './node-DV0niL4A.js';
3
+
4
+ interface CompileResult {
5
+ sql: string;
6
+ values: unknown[];
7
+ text: string;
8
+ }
9
+ declare function compile(input: SqlNode): CompileResult;
10
+
11
+ interface IfProps extends SqlComponentProps {
12
+ test: boolean;
13
+ }
14
+ declare function If(props: IfProps): SqlNode;
15
+
16
+ declare function Where(props: SqlComponentProps): SqlNode;
17
+
18
+ export { type CompileResult, If, type IfProps, Where, compile };
package/dist/index.js ADDED
@@ -0,0 +1,130 @@
1
+ // src/internal/escape.ts
2
+ function escapeSqlTextValue(value) {
3
+ if (value === null || value === void 0) {
4
+ return "NULL";
5
+ }
6
+ if (typeof value === "number") {
7
+ return Number.isFinite(value) ? String(value) : "NULL";
8
+ }
9
+ if (typeof value === "bigint") {
10
+ return value.toString();
11
+ }
12
+ if (typeof value === "boolean") {
13
+ return value ? "TRUE" : "FALSE";
14
+ }
15
+ if (value instanceof Date) {
16
+ return quoteString(value.toISOString());
17
+ }
18
+ return quoteString(String(value));
19
+ }
20
+ function quoteString(value) {
21
+ return `'${value.replaceAll("'", "''")}'`;
22
+ }
23
+
24
+ // src/node.ts
25
+ var FRAGMENT_SYMBOL = "tsx-sql.fragment";
26
+ var SqlFragment = Symbol.for(FRAGMENT_SYMBOL);
27
+
28
+ // src/internal/resolve.ts
29
+ function resolveNode(input) {
30
+ if (Array.isArray(input)) {
31
+ return input.flatMap(resolveNode);
32
+ }
33
+ if (typeof input === "string") {
34
+ return [input];
35
+ }
36
+ if (input.kind === "param") {
37
+ return [input];
38
+ }
39
+ if (input.kind === "element") {
40
+ if (input.type === SqlFragment) {
41
+ return resolveNode(input.children ?? "");
42
+ }
43
+ return resolveNode(input.type(componentProps(input)));
44
+ }
45
+ return assertNever(input);
46
+ }
47
+ function componentProps(element) {
48
+ return {
49
+ ...element.props,
50
+ children: element.children
51
+ };
52
+ }
53
+ function assertNever(value) {
54
+ throw new TypeError(`Unsupported SQL node: ${String(value)}`);
55
+ }
56
+
57
+ // src/compile.ts
58
+ function compile(input) {
59
+ const sqlParts = [];
60
+ const values = [];
61
+ const textParts = [];
62
+ for (const node of resolveNode(input)) {
63
+ if (typeof node === "string") {
64
+ sqlParts.push(node);
65
+ textParts.push(node);
66
+ continue;
67
+ }
68
+ sqlParts.push("?");
69
+ values.push(node.value);
70
+ textParts.push(escapeSqlTextValue(node.value));
71
+ }
72
+ return {
73
+ sql: compactWhitespace(sqlParts.join("")),
74
+ values,
75
+ text: textParts.join("")
76
+ };
77
+ }
78
+ function compactWhitespace(source) {
79
+ return source.replace(/\s+/g, " ").trim();
80
+ }
81
+
82
+ // src/param.ts
83
+ function $(value) {
84
+ return {
85
+ kind: "param",
86
+ value
87
+ };
88
+ }
89
+
90
+ // src/tags/if.ts
91
+ function If(props) {
92
+ return props.test ? props.children ?? "" : "";
93
+ }
94
+
95
+ // src/tags/where.ts
96
+ function Where(props) {
97
+ const children = removeLeadingAndOr(resolveNode(props.children ?? ""));
98
+ if (isBlank(children)) {
99
+ return "";
100
+ }
101
+ return [" WHERE ", children];
102
+ }
103
+ function removeLeadingAndOr(nodes) {
104
+ const firstConditionIndex = nodes.findIndex((node) => typeof node !== "string" || node.trim().length > 0);
105
+ if (firstConditionIndex === -1) {
106
+ return [];
107
+ }
108
+ const firstCondition = nodes[firstConditionIndex];
109
+ if (typeof firstCondition !== "string") {
110
+ return nodes;
111
+ }
112
+ return [
113
+ ...nodes.slice(0, firstConditionIndex),
114
+ removeLeadingAndOrText(firstCondition),
115
+ ...nodes.slice(firstConditionIndex + 1)
116
+ ];
117
+ }
118
+ function removeLeadingAndOrText(text) {
119
+ return text.replace(/^\s*(AND|OR)\b\s*/i, "");
120
+ }
121
+ function isBlank(nodes) {
122
+ return nodes.every((node) => typeof node === "string" && node.trim().length === 0);
123
+ }
124
+ export {
125
+ $,
126
+ If,
127
+ Where,
128
+ compile
129
+ };
130
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/escape.ts","../src/node.ts","../src/internal/resolve.ts","../src/compile.ts","../src/param.ts","../src/tags/if.ts","../src/tags/where.ts"],"sourcesContent":["export function escapeSqlTextValue(value: unknown): string {\r\n if (value === null || value === undefined) {\r\n return \"NULL\";\r\n }\r\n\r\n if (typeof value === \"number\") {\r\n return Number.isFinite(value) ? String(value) : \"NULL\";\r\n }\r\n\r\n if (typeof value === \"bigint\") {\r\n return value.toString();\r\n }\r\n\r\n if (typeof value === \"boolean\") {\r\n return value ? \"TRUE\" : \"FALSE\";\r\n }\r\n\r\n if (value instanceof Date) {\r\n return quoteString(value.toISOString());\r\n }\r\n\r\n return quoteString(String(value));\r\n}\r\n\r\nfunction quoteString(value: string): string {\r\n return `'${value.replaceAll(\"'\", \"''\")}'`;\r\n}\r\n","import type { SqlParam } from \"./param\";\n\nconst FRAGMENT_SYMBOL = \"tsx-sql.fragment\";\n\n// SQL fragments are transparent wrappers for <>...</>.\nexport const SqlFragment = Symbol.for(FRAGMENT_SYMBOL);\n\r\n// Any piece of SQL content that compile(...) knows how to read.\n// You can write:\n// <>WHERE name = { $(\"Tom\") }</>\n// TSX stores those children as:\r\n// [\"WHERE name = \", SqlParam]\r\n// Nested tags are also SqlNode values, so children can contain more elements.\nexport type SqlNode = string | SqlParam | SqlElement | SqlNode[];\n\ntype SqlProps = Record<string, unknown>;\n\nexport type SqlComponentProps = SqlProps & { children?: SqlNode };\n\n// A function that can be used as a TSX SQL tag.\n// You can write:\n// <If test={name}>...</If>\n// The element stores the If function in its type field, and compile(...)\n// calls that function to decide which SqlNode should be compiled next.\nexport type SqlComponent = (props: SqlComponentProps) => SqlNode;\n\r\n// The object created by createNode(...) after TypeScript compiles TSX.\r\n// You can write:\r\n// <If test={name}>AND name = { $(\"Tom\") }</If>\r\n// Runtime stores:\r\n// { type: If, props: { test: name, children: [\"AND name = \", SqlParam] } }\r\n// Fragment elements use SqlFragment as their type and only wrap children.\r\nexport interface SqlElement {\n kind: \"element\";\n type: typeof SqlFragment | SqlComponent;\n props: SqlProps;\n children?: SqlNode;\n}\n\n// TypeScript calls this through jsx/jsxs/jsxDEV after compiling TSX.\n// We keep the node shape tiny: just the tag/component type and its props.\nexport function createNode(type: SqlElement[\"type\"], props: (SqlProps & { children?: SqlNode }) | null): SqlElement {\n const { children, ...restProps } = props ?? {};\n\n return {\n kind: \"element\",\n type,\n props: restProps,\n children\n };\n}\n","import { SqlFragment, type SqlComponentProps, type SqlElement, type SqlNode } from \"../node\";\nimport type { SqlParam } from \"tsx-sql\";\n\nexport type ResolvedSqlNode = string | SqlParam;\n\nexport function resolveNode(input: SqlNode): ResolvedSqlNode[] {\n if (Array.isArray(input)) {\n return input.flatMap(resolveNode);\n }\n\n if (typeof input === \"string\") {\n return [input];\n }\n\n if (input.kind === \"param\") {\n return [input];\n }\n\n if (input.kind === \"element\") {\n if (input.type === SqlFragment) {\n return resolveNode(input.children ?? \"\");\n }\n\n return resolveNode(input.type(componentProps(input)));\n }\n\n return assertNever(input);\n}\n\nfunction componentProps(element: SqlElement): SqlComponentProps {\n return {\n ...element.props,\n children: element.children\n };\n}\n\nfunction assertNever(value: never): never {\n throw new TypeError(`Unsupported SQL node: ${String(value)}`);\n}\n","import { escapeSqlTextValue } from \"./internal/escape\";\nimport { resolveNode } from \"./internal/resolve\";\nimport type { SqlNode } from \"./node\";\n\nexport interface CompileResult {\n sql: string;\n values: unknown[];\n text: string;\n}\n\nexport function compile(input: SqlNode): CompileResult {\n const sqlParts: string[] = [];\n const values: unknown[] = [];\n const textParts: string[] = [];\n\n for (const node of resolveNode(input)) {\n // Raw strings are SQL text, not parameters.\n if (typeof node === \"string\") {\n sqlParts.push(node);\n textParts.push(node);\n continue;\n }\n\n // Parameters become placeholders in executable SQL and values in order.\n sqlParts.push(\"?\");\n values.push(node.value);\n textParts.push(escapeSqlTextValue(node.value));\n }\n\n return {\n sql: compactWhitespace(sqlParts.join(\"\")),\n values,\n text: textParts.join(\"\")\n };\n}\n\nfunction compactWhitespace(source: string): string {\n return source.replace(/\\s+/g, \" \").trim();\n}\n","export interface SqlParam {\n kind: \"param\";\n value: unknown;\n}\n\nexport function $(value: unknown): SqlParam {\n return {\n kind: \"param\",\n value\n };\n}\n","import type { SqlComponentProps, SqlNode } from \"../node\";\n\nexport interface IfProps extends SqlComponentProps {\n test: boolean;\n}\n\r\nexport function If(props: IfProps): SqlNode {\r\n return props.test ? props.children ?? \"\" : \"\";\r\n}\r\n","import { resolveNode, type ResolvedSqlNode } from \"../internal/resolve\";\nimport type { SqlComponentProps, SqlNode } from \"../node\";\n\nexport function Where(props: SqlComponentProps): SqlNode {\n const children = removeLeadingAndOr(resolveNode(props.children ?? \"\"));\n\n if (isBlank(children)) {\n return \"\";\n }\n\n return [\" WHERE \", children];\n}\n\nfunction removeLeadingAndOr(nodes: ResolvedSqlNode[]): ResolvedSqlNode[] {\n // MyBatis where ignores leading whitespace, then removes a leading AND/OR.\n const firstConditionIndex = nodes.findIndex((node) => typeof node !== \"string\" || node.trim().length > 0);\n if (firstConditionIndex === -1) {\n return [];\n }\n\n const firstCondition = nodes[firstConditionIndex];\n if (typeof firstCondition !== \"string\") {\n return nodes;\n }\n\n return [\n ...nodes.slice(0, firstConditionIndex),\n removeLeadingAndOrText(firstCondition),\n ...nodes.slice(firstConditionIndex + 1)\n ];\n}\n\nfunction removeLeadingAndOrText(text: string): string {\n return text.replace(/^\\s*(AND|OR)\\b\\s*/i, \"\");\n}\n\nfunction isBlank(nodes: ResolvedSqlNode[]): boolean {\n return nodes.every((node) => typeof node === \"string\" && node.trim().length === 0);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/jsx-dev-runtime.ts
21
+ var jsx_dev_runtime_exports = {};
22
+ __export(jsx_dev_runtime_exports, {
23
+ Fragment: () => SqlFragment,
24
+ jsx: () => jsx,
25
+ jsxDEV: () => createNode,
26
+ jsxs: () => jsxs
27
+ });
28
+ module.exports = __toCommonJS(jsx_dev_runtime_exports);
29
+
30
+ // src/node.ts
31
+ var FRAGMENT_SYMBOL = "tsx-sql.fragment";
32
+ var SqlFragment = Symbol.for(FRAGMENT_SYMBOL);
33
+ function createNode(type, props) {
34
+ const { children, ...restProps } = props ?? {};
35
+ return {
36
+ kind: "element",
37
+ type,
38
+ props: restProps,
39
+ children
40
+ };
41
+ }
42
+
43
+ // src/jsx-runtime.ts
44
+ var jsx = createNode;
45
+ var jsxs = createNode;
46
+ // Annotate the CommonJS export names for ESM import in node:
47
+ 0 && (module.exports = {
48
+ Fragment,
49
+ jsx,
50
+ jsxDEV,
51
+ jsxs
52
+ });
53
+ //# sourceMappingURL=jsx-dev-runtime.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/jsx-dev-runtime.ts","../src/node.ts","../src/jsx-runtime.ts"],"sourcesContent":["export { Fragment, jsx, jsxs } from \"./jsx-runtime\";\nexport { createNode as jsxDEV } from \"./node\";\n","import type { SqlParam } from \"./param\";\n\nconst FRAGMENT_SYMBOL = \"tsx-sql.fragment\";\n\n// SQL fragments are transparent wrappers for <>...</>.\nexport const SqlFragment = Symbol.for(FRAGMENT_SYMBOL);\n\r\n// Any piece of SQL content that compile(...) knows how to read.\n// You can write:\n// <>WHERE name = { $(\"Tom\") }</>\n// TSX stores those children as:\r\n// [\"WHERE name = \", SqlParam]\r\n// Nested tags are also SqlNode values, so children can contain more elements.\nexport type SqlNode = string | SqlParam | SqlElement | SqlNode[];\n\ntype SqlProps = Record<string, unknown>;\n\nexport type SqlComponentProps = SqlProps & { children?: SqlNode };\n\n// A function that can be used as a TSX SQL tag.\n// You can write:\n// <If test={name}>...</If>\n// The element stores the If function in its type field, and compile(...)\n// calls that function to decide which SqlNode should be compiled next.\nexport type SqlComponent = (props: SqlComponentProps) => SqlNode;\n\r\n// The object created by createNode(...) after TypeScript compiles TSX.\r\n// You can write:\r\n// <If test={name}>AND name = { $(\"Tom\") }</If>\r\n// Runtime stores:\r\n// { type: If, props: { test: name, children: [\"AND name = \", SqlParam] } }\r\n// Fragment elements use SqlFragment as their type and only wrap children.\r\nexport interface SqlElement {\n kind: \"element\";\n type: typeof SqlFragment | SqlComponent;\n props: SqlProps;\n children?: SqlNode;\n}\n\n// TypeScript calls this through jsx/jsxs/jsxDEV after compiling TSX.\n// We keep the node shape tiny: just the tag/component type and its props.\nexport function createNode(type: SqlElement[\"type\"], props: (SqlProps & { children?: SqlNode }) | null): SqlElement {\n const { children, ...restProps } = props ?? {};\n\n return {\n kind: \"element\",\n type,\n props: restProps,\n children\n };\n}\n","import { createNode, SqlFragment } from \"./node\";\nimport type { SqlNode } from \"./node\";\n\ndeclare global {\n namespace JSX {\n type Element = SqlNode;\n\n interface ElementChildrenAttribute {\n children: {};\n }\n }\n}\n\nexport { SqlFragment as Fragment };\n\nexport const jsx = createNode;\nexport const jsxs = createNode;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEA,IAAM,kBAAkB;AAGjB,IAAM,cAAc,OAAO,IAAI,eAAe;AAoC9C,SAAS,WAAW,MAA0B,OAA+D;AAClH,QAAM,EAAE,UAAU,GAAG,UAAU,IAAI,SAAS,CAAC;AAE7C,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,OAAO;AAAA,IACP;AAAA,EACF;AACF;;;ACnCO,IAAM,MAAM;AACZ,IAAM,OAAO;","names":[]}
@@ -0,0 +1,2 @@
1
+ export { jsx, jsxs } from './jsx-runtime.cjs';
2
+ export { d as Fragment, c as jsxDEV } from './node-DV0niL4A.cjs';
@@ -0,0 +1,2 @@
1
+ export { jsx, jsxs } from './jsx-runtime.js';
2
+ export { d as Fragment, c as jsxDEV } from './node-DV0niL4A.js';
@@ -0,0 +1,23 @@
1
+ // src/node.ts
2
+ var FRAGMENT_SYMBOL = "tsx-sql.fragment";
3
+ var SqlFragment = Symbol.for(FRAGMENT_SYMBOL);
4
+ function createNode(type, props) {
5
+ const { children, ...restProps } = props ?? {};
6
+ return {
7
+ kind: "element",
8
+ type,
9
+ props: restProps,
10
+ children
11
+ };
12
+ }
13
+
14
+ // src/jsx-runtime.ts
15
+ var jsx = createNode;
16
+ var jsxs = createNode;
17
+ export {
18
+ SqlFragment as Fragment,
19
+ jsx,
20
+ createNode as jsxDEV,
21
+ jsxs
22
+ };
23
+ //# sourceMappingURL=jsx-dev-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/node.ts","../src/jsx-runtime.ts"],"sourcesContent":["import type { SqlParam } from \"./param\";\n\nconst FRAGMENT_SYMBOL = \"tsx-sql.fragment\";\n\n// SQL fragments are transparent wrappers for <>...</>.\nexport const SqlFragment = Symbol.for(FRAGMENT_SYMBOL);\n\r\n// Any piece of SQL content that compile(...) knows how to read.\n// You can write:\n// <>WHERE name = { $(\"Tom\") }</>\n// TSX stores those children as:\r\n// [\"WHERE name = \", SqlParam]\r\n// Nested tags are also SqlNode values, so children can contain more elements.\nexport type SqlNode = string | SqlParam | SqlElement | SqlNode[];\n\ntype SqlProps = Record<string, unknown>;\n\nexport type SqlComponentProps = SqlProps & { children?: SqlNode };\n\n// A function that can be used as a TSX SQL tag.\n// You can write:\n// <If test={name}>...</If>\n// The element stores the If function in its type field, and compile(...)\n// calls that function to decide which SqlNode should be compiled next.\nexport type SqlComponent = (props: SqlComponentProps) => SqlNode;\n\r\n// The object created by createNode(...) after TypeScript compiles TSX.\r\n// You can write:\r\n// <If test={name}>AND name = { $(\"Tom\") }</If>\r\n// Runtime stores:\r\n// { type: If, props: { test: name, children: [\"AND name = \", SqlParam] } }\r\n// Fragment elements use SqlFragment as their type and only wrap children.\r\nexport interface SqlElement {\n kind: \"element\";\n type: typeof SqlFragment | SqlComponent;\n props: SqlProps;\n children?: SqlNode;\n}\n\n// TypeScript calls this through jsx/jsxs/jsxDEV after compiling TSX.\n// We keep the node shape tiny: just the tag/component type and its props.\nexport function createNode(type: SqlElement[\"type\"], props: (SqlProps & { children?: SqlNode }) | null): SqlElement {\n const { children, ...restProps } = props ?? {};\n\n return {\n kind: \"element\",\n type,\n props: restProps,\n children\n };\n}\n","import { createNode, SqlFragment } from \"./node\";\nimport type { SqlNode } from \"./node\";\n\ndeclare global {\n namespace JSX {\n type Element = SqlNode;\n\n interface ElementChildrenAttribute {\n children: {};\n }\n }\n}\n\nexport { SqlFragment as Fragment };\n\nexport const jsx = createNode;\nexport const jsxs = createNode;\n"],"mappings":";AAEA,IAAM,kBAAkB;AAGjB,IAAM,cAAc,OAAO,IAAI,eAAe;AAoC9C,SAAS,WAAW,MAA0B,OAA+D;AAClH,QAAM,EAAE,UAAU,GAAG,UAAU,IAAI,SAAS,CAAC;AAE7C,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,OAAO;AAAA,IACP;AAAA,EACF;AACF;;;ACnCO,IAAM,MAAM;AACZ,IAAM,OAAO;","names":[]}
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/jsx-runtime.ts
21
+ var jsx_runtime_exports = {};
22
+ __export(jsx_runtime_exports, {
23
+ Fragment: () => SqlFragment,
24
+ jsx: () => jsx,
25
+ jsxs: () => jsxs
26
+ });
27
+ module.exports = __toCommonJS(jsx_runtime_exports);
28
+
29
+ // src/node.ts
30
+ var FRAGMENT_SYMBOL = "tsx-sql.fragment";
31
+ var SqlFragment = Symbol.for(FRAGMENT_SYMBOL);
32
+ function createNode(type, props) {
33
+ const { children, ...restProps } = props ?? {};
34
+ return {
35
+ kind: "element",
36
+ type,
37
+ props: restProps,
38
+ children
39
+ };
40
+ }
41
+
42
+ // src/jsx-runtime.ts
43
+ var jsx = createNode;
44
+ var jsxs = createNode;
45
+ // Annotate the CommonJS export names for ESM import in node:
46
+ 0 && (module.exports = {
47
+ Fragment,
48
+ jsx,
49
+ jsxs
50
+ });
51
+ //# sourceMappingURL=jsx-runtime.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/jsx-runtime.ts","../src/node.ts"],"sourcesContent":["import { createNode, SqlFragment } from \"./node\";\nimport type { SqlNode } from \"./node\";\n\ndeclare global {\n namespace JSX {\n type Element = SqlNode;\n\n interface ElementChildrenAttribute {\n children: {};\n }\n }\n}\n\nexport { SqlFragment as Fragment };\n\nexport const jsx = createNode;\nexport const jsxs = createNode;\n","import type { SqlParam } from \"./param\";\n\nconst FRAGMENT_SYMBOL = \"tsx-sql.fragment\";\n\n// SQL fragments are transparent wrappers for <>...</>.\nexport const SqlFragment = Symbol.for(FRAGMENT_SYMBOL);\n\r\n// Any piece of SQL content that compile(...) knows how to read.\n// You can write:\n// <>WHERE name = { $(\"Tom\") }</>\n// TSX stores those children as:\r\n// [\"WHERE name = \", SqlParam]\r\n// Nested tags are also SqlNode values, so children can contain more elements.\nexport type SqlNode = string | SqlParam | SqlElement | SqlNode[];\n\ntype SqlProps = Record<string, unknown>;\n\nexport type SqlComponentProps = SqlProps & { children?: SqlNode };\n\n// A function that can be used as a TSX SQL tag.\n// You can write:\n// <If test={name}>...</If>\n// The element stores the If function in its type field, and compile(...)\n// calls that function to decide which SqlNode should be compiled next.\nexport type SqlComponent = (props: SqlComponentProps) => SqlNode;\n\r\n// The object created by createNode(...) after TypeScript compiles TSX.\r\n// You can write:\r\n// <If test={name}>AND name = { $(\"Tom\") }</If>\r\n// Runtime stores:\r\n// { type: If, props: { test: name, children: [\"AND name = \", SqlParam] } }\r\n// Fragment elements use SqlFragment as their type and only wrap children.\r\nexport interface SqlElement {\n kind: \"element\";\n type: typeof SqlFragment | SqlComponent;\n props: SqlProps;\n children?: SqlNode;\n}\n\n// TypeScript calls this through jsx/jsxs/jsxDEV after compiling TSX.\n// We keep the node shape tiny: just the tag/component type and its props.\nexport function createNode(type: SqlElement[\"type\"], props: (SqlProps & { children?: SqlNode }) | null): SqlElement {\n const { children, ...restProps } = props ?? {};\n\n return {\n kind: \"element\",\n type,\n props: restProps,\n children\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEA,IAAM,kBAAkB;AAGjB,IAAM,cAAc,OAAO,IAAI,eAAe;AAoC9C,SAAS,WAAW,MAA0B,OAA+D;AAClH,QAAM,EAAE,UAAU,GAAG,UAAU,IAAI,SAAS,CAAC;AAE7C,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,OAAO;AAAA,IACP;AAAA,EACF;AACF;;;ADnCO,IAAM,MAAM;AACZ,IAAM,OAAO;","names":[]}
@@ -0,0 +1,16 @@
1
+ import { S as SqlNode, c as createNode } from './node-DV0niL4A.cjs';
2
+ export { d as Fragment } from './node-DV0niL4A.cjs';
3
+
4
+ declare global {
5
+ namespace JSX {
6
+ type Element = SqlNode;
7
+ interface ElementChildrenAttribute {
8
+ children: {};
9
+ }
10
+ }
11
+ }
12
+
13
+ declare const jsx: typeof createNode;
14
+ declare const jsxs: typeof createNode;
15
+
16
+ export { jsx, jsxs };
@@ -0,0 +1,16 @@
1
+ import { S as SqlNode, c as createNode } from './node-DV0niL4A.js';
2
+ export { d as Fragment } from './node-DV0niL4A.js';
3
+
4
+ declare global {
5
+ namespace JSX {
6
+ type Element = SqlNode;
7
+ interface ElementChildrenAttribute {
8
+ children: {};
9
+ }
10
+ }
11
+ }
12
+
13
+ declare const jsx: typeof createNode;
14
+ declare const jsxs: typeof createNode;
15
+
16
+ export { jsx, jsxs };
@@ -0,0 +1,22 @@
1
+ // src/node.ts
2
+ var FRAGMENT_SYMBOL = "tsx-sql.fragment";
3
+ var SqlFragment = Symbol.for(FRAGMENT_SYMBOL);
4
+ function createNode(type, props) {
5
+ const { children, ...restProps } = props ?? {};
6
+ return {
7
+ kind: "element",
8
+ type,
9
+ props: restProps,
10
+ children
11
+ };
12
+ }
13
+
14
+ // src/jsx-runtime.ts
15
+ var jsx = createNode;
16
+ var jsxs = createNode;
17
+ export {
18
+ SqlFragment as Fragment,
19
+ jsx,
20
+ jsxs
21
+ };
22
+ //# sourceMappingURL=jsx-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/node.ts","../src/jsx-runtime.ts"],"sourcesContent":["import type { SqlParam } from \"./param\";\n\nconst FRAGMENT_SYMBOL = \"tsx-sql.fragment\";\n\n// SQL fragments are transparent wrappers for <>...</>.\nexport const SqlFragment = Symbol.for(FRAGMENT_SYMBOL);\n\r\n// Any piece of SQL content that compile(...) knows how to read.\n// You can write:\n// <>WHERE name = { $(\"Tom\") }</>\n// TSX stores those children as:\r\n// [\"WHERE name = \", SqlParam]\r\n// Nested tags are also SqlNode values, so children can contain more elements.\nexport type SqlNode = string | SqlParam | SqlElement | SqlNode[];\n\ntype SqlProps = Record<string, unknown>;\n\nexport type SqlComponentProps = SqlProps & { children?: SqlNode };\n\n// A function that can be used as a TSX SQL tag.\n// You can write:\n// <If test={name}>...</If>\n// The element stores the If function in its type field, and compile(...)\n// calls that function to decide which SqlNode should be compiled next.\nexport type SqlComponent = (props: SqlComponentProps) => SqlNode;\n\r\n// The object created by createNode(...) after TypeScript compiles TSX.\r\n// You can write:\r\n// <If test={name}>AND name = { $(\"Tom\") }</If>\r\n// Runtime stores:\r\n// { type: If, props: { test: name, children: [\"AND name = \", SqlParam] } }\r\n// Fragment elements use SqlFragment as their type and only wrap children.\r\nexport interface SqlElement {\n kind: \"element\";\n type: typeof SqlFragment | SqlComponent;\n props: SqlProps;\n children?: SqlNode;\n}\n\n// TypeScript calls this through jsx/jsxs/jsxDEV after compiling TSX.\n// We keep the node shape tiny: just the tag/component type and its props.\nexport function createNode(type: SqlElement[\"type\"], props: (SqlProps & { children?: SqlNode }) | null): SqlElement {\n const { children, ...restProps } = props ?? {};\n\n return {\n kind: \"element\",\n type,\n props: restProps,\n children\n };\n}\n","import { createNode, SqlFragment } from \"./node\";\nimport type { SqlNode } from \"./node\";\n\ndeclare global {\n namespace JSX {\n type Element = SqlNode;\n\n interface ElementChildrenAttribute {\n children: {};\n }\n }\n}\n\nexport { SqlFragment as Fragment };\n\nexport const jsx = createNode;\nexport const jsxs = createNode;\n"],"mappings":";AAEA,IAAM,kBAAkB;AAGjB,IAAM,cAAc,OAAO,IAAI,eAAe;AAoC9C,SAAS,WAAW,MAA0B,OAA+D;AAClH,QAAM,EAAE,UAAU,GAAG,UAAU,IAAI,SAAS,CAAC;AAE7C,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,OAAO;AAAA,IACP;AAAA,EACF;AACF;;;ACnCO,IAAM,MAAM;AACZ,IAAM,OAAO;","names":[]}
@@ -0,0 +1,24 @@
1
+ interface SqlParam {
2
+ kind: "param";
3
+ value: unknown;
4
+ }
5
+ declare function $(value: unknown): SqlParam;
6
+
7
+ declare const SqlFragment: unique symbol;
8
+ type SqlNode = string | SqlParam | SqlElement | SqlNode[];
9
+ type SqlProps = Record<string, unknown>;
10
+ type SqlComponentProps = SqlProps & {
11
+ children?: SqlNode;
12
+ };
13
+ type SqlComponent = (props: SqlComponentProps) => SqlNode;
14
+ interface SqlElement {
15
+ kind: "element";
16
+ type: typeof SqlFragment | SqlComponent;
17
+ props: SqlProps;
18
+ children?: SqlNode;
19
+ }
20
+ declare function createNode(type: SqlElement["type"], props: (SqlProps & {
21
+ children?: SqlNode;
22
+ }) | null): SqlElement;
23
+
24
+ export { $, type SqlNode as S, type SqlComponentProps as a, type SqlParam as b, createNode as c, SqlFragment as d };
@@ -0,0 +1,24 @@
1
+ interface SqlParam {
2
+ kind: "param";
3
+ value: unknown;
4
+ }
5
+ declare function $(value: unknown): SqlParam;
6
+
7
+ declare const SqlFragment: unique symbol;
8
+ type SqlNode = string | SqlParam | SqlElement | SqlNode[];
9
+ type SqlProps = Record<string, unknown>;
10
+ type SqlComponentProps = SqlProps & {
11
+ children?: SqlNode;
12
+ };
13
+ type SqlComponent = (props: SqlComponentProps) => SqlNode;
14
+ interface SqlElement {
15
+ kind: "element";
16
+ type: typeof SqlFragment | SqlComponent;
17
+ props: SqlProps;
18
+ children?: SqlNode;
19
+ }
20
+ declare function createNode(type: SqlElement["type"], props: (SqlProps & {
21
+ children?: SqlNode;
22
+ }) | null): SqlElement;
23
+
24
+ export { $, type SqlNode as S, type SqlComponentProps as a, type SqlParam as b, createNode as c, SqlFragment as d };
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "tsx-sql",
3
+ "version": "0.1.0",
4
+ "description": "Compile TSX SQL fragments into parameterized SQL.",
5
+ "keywords": [
6
+ "sql",
7
+ "tsx",
8
+ "jsx",
9
+ "typescript",
10
+ "mybatis",
11
+ "query-builder"
12
+ ],
13
+ "author": "GuXianWN",
14
+ "license": "MIT",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/GuXianWN/tsx-sql.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/GuXianWN/tsx-sql/issues"
21
+ },
22
+ "homepage": "https://github.com/GuXianWN/tsx-sql#readme",
23
+ "type": "module",
24
+ "main": "./dist/index.cjs",
25
+ "module": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "packageManager": "pnpm@9.15.0",
28
+ "sideEffects": false,
29
+ "files": [
30
+ "dist",
31
+ "README.md",
32
+ "LICENSE"
33
+ ],
34
+ "exports": {
35
+ ".": {
36
+ "types": "./dist/index.d.ts",
37
+ "import": "./dist/index.js",
38
+ "require": "./dist/index.cjs"
39
+ },
40
+ "./jsx-runtime": {
41
+ "types": "./dist/jsx-runtime.d.ts",
42
+ "import": "./dist/jsx-runtime.js",
43
+ "require": "./dist/jsx-runtime.cjs"
44
+ },
45
+ "./jsx-dev-runtime": {
46
+ "types": "./dist/jsx-dev-runtime.d.ts",
47
+ "import": "./dist/jsx-dev-runtime.js",
48
+ "require": "./dist/jsx-dev-runtime.cjs"
49
+ }
50
+ },
51
+ "scripts": {
52
+ "build": "tsup",
53
+ "prepublishOnly": "pnpm test && pnpm typecheck && pnpm build",
54
+ "test": "vitest run",
55
+ "typecheck": "tsc --noEmit"
56
+ },
57
+ "devDependencies": {
58
+ "tsup": "^8.3.5",
59
+ "typescript": "^5.6.3",
60
+ "vitest": "^2.1.4"
61
+ }
62
+ }