@leuduan/block 0.0.0-stage → 2.0.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/dist/sql.js ADDED
@@ -0,0 +1,169 @@
1
+ // Port of py/dbox/dbox/block/sql.py
2
+ // The immutable query model (SqlSegment, Pipe, TableExpression, QueryExpression and its queries) and the `sql` tag.
3
+ import * as l from './line.js';
4
+ import { DEFAULT_OPTIONS } from './line.js';
5
+ import * as h from './helpers.js';
6
+ export function mapEntries(m) {
7
+ if (m instanceof Map)
8
+ return [...m];
9
+ return Object.entries(m);
10
+ }
11
+ // ---------------------------------------------------------------------------
12
+ // Segments: one class each for pipes and table expressions, both just a line
13
+ // ---------------------------------------------------------------------------
14
+ export class SqlSegment {
15
+ format(options = DEFAULT_OPTIONS) {
16
+ return l.formatLine(this.toLine(), options);
17
+ }
18
+ toString() {
19
+ return this.format();
20
+ }
21
+ }
22
+ /** One `|>` step. Built by the `p` factories. */
23
+ export class Pipe extends SqlSegment {
24
+ line;
25
+ constructor(line) {
26
+ super();
27
+ this.line = line;
28
+ }
29
+ toLine() {
30
+ return this.line;
31
+ }
32
+ }
33
+ /** Something that goes after FROM or JOIN. Built by the `t` factories. */
34
+ export class TableExpression extends SqlSegment {
35
+ line;
36
+ constructor(line) {
37
+ super();
38
+ this.line = line;
39
+ }
40
+ toLine() {
41
+ return this.line;
42
+ }
43
+ }
44
+ // ---------------------------------------------------------------------------
45
+ // QueryExpression — base query class
46
+ // ---------------------------------------------------------------------------
47
+ export class QueryExpression extends SqlSegment {
48
+ /** Default toTableExpression wraps in a parenthesized subquery. */
49
+ toTableExpression() {
50
+ return new TableExpression(l.paren(this.toLine()));
51
+ }
52
+ }
53
+ // ---------------------------------------------------------------------------
54
+ // Concrete QueryExpression classes
55
+ // ---------------------------------------------------------------------------
56
+ export class RawQuery extends QueryExpression {
57
+ sql;
58
+ constructor(sql) {
59
+ super();
60
+ this.sql = sql;
61
+ }
62
+ toLine() {
63
+ return this.sql;
64
+ }
65
+ }
66
+ export class NamedQueryExpression extends QueryExpression {
67
+ name;
68
+ constructor(name) {
69
+ super();
70
+ this.name = name;
71
+ }
72
+ toLine() {
73
+ return l.lit(this.name);
74
+ }
75
+ /** Named expressions refer to CTEs/tables — no subquery wrapping. */
76
+ toTableExpression() {
77
+ return new TableExpression(this.toLine());
78
+ }
79
+ }
80
+ class FromQuery extends QueryExpression {
81
+ tableExpression;
82
+ constructor(tableExpression) {
83
+ super();
84
+ this.tableExpression = tableExpression;
85
+ }
86
+ toLine() {
87
+ return l.indentNext([l.keyword('FROM'), this.tableExpression.toLine()]);
88
+ }
89
+ }
90
+ /** `FROM table`; a string is emitted as given, so quote identifiers with `t.path(id, true)` or `e.ident`. */
91
+ export function from(tableExpression) {
92
+ return new FromQuery(typeof tableExpression === 'string' ? new TableExpression(l.lit(tableExpression)) : tableExpression);
93
+ }
94
+ export class GoogleSqlQuery extends QueryExpression {
95
+ ctes;
96
+ queryExpression;
97
+ orderByLine;
98
+ limitOffset;
99
+ pipes;
100
+ constructor({ ctes = new Map(), queryExpression, orderByLine, limitOffset, pipes = [] }) {
101
+ super();
102
+ this.ctes = ctes;
103
+ this.queryExpression = queryExpression;
104
+ this.orderByLine = orderByLine;
105
+ this.limitOffset = limitOffset;
106
+ this.pipes = pipes;
107
+ }
108
+ toLine() {
109
+ // A nested GoogleSqlQuery is parenthesized (query.py:126-127)
110
+ const queryLine = this.queryExpression instanceof GoogleSqlQuery
111
+ ? l.paren(this.queryExpression.toLine())
112
+ : this.queryExpression.toLine();
113
+ return l.stack([
114
+ this.ctes.size > 0 ? h.ctes(this.ctes) : null,
115
+ queryLine,
116
+ this.orderByLine,
117
+ this.limitOffset,
118
+ ...this.pipes.map((pipe) => pipe.toLine()),
119
+ ]);
120
+ }
121
+ }
122
+ /**
123
+ * Apply `|>` steps to a query. Pipe syntax is GoogleSQL's, so the result is a GoogleSqlQuery. A GoogleSqlQuery takes
124
+ * the steps on its own pipe list (only its outer wrapper is flattened); anything else is wrapped. The input is not
125
+ * changed.
126
+ */
127
+ export function pipe(query, ...steps) {
128
+ if (query instanceof GoogleSqlQuery) {
129
+ return new GoogleSqlQuery({
130
+ ctes: query.ctes,
131
+ queryExpression: query.queryExpression,
132
+ orderByLine: query.orderByLine,
133
+ limitOffset: query.limitOffset,
134
+ pipes: [...query.pipes, ...steps],
135
+ });
136
+ }
137
+ return new GoogleSqlQuery({ queryExpression: query, pipes: steps });
138
+ }
139
+ /**
140
+ * Tagged template for raw SQL fragments.
141
+ * - string/number interpolations inline as raw SQL text (no quoting).
142
+ * - Line interpolations embed structurally (breaker recurses into them).
143
+ * - SqlSegment (QueryExpression etc.) interpolations use .toLine().
144
+ * For quoted identifiers use ident(name); for string literals use e.literalString(x).
145
+ */
146
+ export function sql(strings, ...values) {
147
+ const parts = [];
148
+ strings.forEach((chunk, i) => {
149
+ if (chunk.length > 0)
150
+ parts.push(l.ensure(chunk));
151
+ if (i < values.length)
152
+ parts.push(interpToLine(values[i]));
153
+ });
154
+ const clean = parts.filter((p) => p != null);
155
+ if (clean.length === 0)
156
+ return l.lit('');
157
+ if (clean.length === 1)
158
+ return clean[0];
159
+ return l.combine(clean, { sep: l.BLANK });
160
+ }
161
+ function interpToLine(v) {
162
+ if (v instanceof l.Line)
163
+ return v;
164
+ if (v instanceof SqlSegment)
165
+ return v.toLine();
166
+ if (typeof v === 'number')
167
+ return l.lit(String(v));
168
+ return l.lit(v); // plain string → raw SQL fragment
169
+ }
package/dist/t.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { type LineOrStr } from './line.js';
2
+ import { type QueryExpression, TableExpression } from './sql.js';
3
+ /** A table path; as given unless `quote`, which quotes the whole path as one identifier (see `e.ident`). */
4
+ export declare function path(id: string, quote?: boolean): TableExpression;
5
+ /** A query in parentheses. */
6
+ export declare function subquery(query: QueryExpression): TableExpression;
7
+ export declare function raw(line: LineOrStr): TableExpression;
8
+ //# sourceMappingURL=t.d.ts.map
package/dist/t.js ADDED
@@ -0,0 +1,17 @@
1
+ // Port of py/dbox/dbox/block/t.py
2
+ // Table expressions: what goes after FROM or JOIN.
3
+ import { ident } from './expr.js';
4
+ import * as l from './line.js';
5
+ import {} from './line.js';
6
+ import { TableExpression } from './sql.js';
7
+ /** A table path; as given unless `quote`, which quotes the whole path as one identifier (see `e.ident`). */
8
+ export function path(id, quote = false) {
9
+ return new TableExpression(quote ? ident(id) : l.lit(id));
10
+ }
11
+ /** A query in parentheses. */
12
+ export function subquery(query) {
13
+ return new TableExpression(l.paren(query.toLine()));
14
+ }
15
+ export function raw(line) {
16
+ return new TableExpression(l.ensure(line));
17
+ }
package/package.json CHANGED
@@ -1,6 +1,39 @@
1
1
  {
2
2
  "name": "@leuduan/block",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "2.0.0",
4
+ "description": "SQL builder and renderer for GoogleSQL",
5
+ "license": "ISC",
6
+ "author": "Duan Leu",
7
+ "type": "module",
8
+ "sideEffects": false,
9
+ "files": [
10
+ "dist",
11
+ "!dist/**/*.map"
12
+ ],
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ }
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "22.19.1",
21
+ "typescript": "^7.0.2",
22
+ "vitest": "4.1.5",
23
+ "@leuduan/tsconfig": "^1.0.0"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/leuduan/luna.git",
31
+ "directory": "js/packages/block"
32
+ },
33
+ "scripts": {
34
+ "clean": "rm -rf dist tsconfig.tsbuildinfo tsconfig.build.tsbuildinfo",
35
+ "build": "tsc --build tsconfig.build.json",
36
+ "typecheck": "tsc --noEmit",
37
+ "test": "vitest run"
38
+ }
6
39
  }