pgsql-template-tag 0.0.2 → 0.0.4

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/README.md ADDED
@@ -0,0 +1 @@
1
+ # pgsql-template-tag
@@ -1,17 +1,84 @@
1
+ /**
2
+ * SQL クエリー内で使用される値の型定義です。
3
+ */
1
4
  export type Value = unknown;
5
+ /**
6
+ * SQL クエリーの構築に使用できる生の値、または別の Sql インスタンスを表す型です。
7
+ */
2
8
  export type RawValue = Value | Sql;
9
+ /**
10
+ * 安全な SQL クエリーを構築するためのクラスです。
11
+ * プレースホルダーを使用したパラメーター化クエリーを生成します。
12
+ */
3
13
  export declare class Sql {
14
+ /**
15
+ * 構築された SQL クエリーテキストを取得します。
16
+ *
17
+ * 初回アクセス時に文字列が結合され、結果はキャッシュされます。
18
+ *
19
+ * @returns SQL クエリーテキストを返します。
20
+ */
4
21
  get text(): string;
22
+ /**
23
+ * クエリーに使用されるパラメーター値の配列です。
24
+ */
5
25
  readonly values: readonly Value[];
26
+ /**
27
+ * 内部状態を保持するためのプロパティーです。
28
+ */
6
29
  private readonly _;
30
+ /**
31
+ * 新しい Sql インスタンスを初期化します。
32
+ *
33
+ * @param rawStrings SQL の断片となる文字列の配列です。
34
+ * @param rawBindings 文字列の間に挿入される値の配列です。
35
+ */
7
36
  constructor(rawStrings: readonly string[], rawBindings: readonly RawValue[]);
37
+ /**
38
+ * オブジェクトを JSON 形式に変換可能な形式で返します。
39
+ *
40
+ * @returns クエリーテキストと値の配列を含むオブジェクトを返します。
41
+ */
8
42
  toJSON(): {
9
43
  text: string;
10
44
  values: Value[];
11
45
  };
46
+ /**
47
+ * インスタンスを文字列に変換します。
48
+ *
49
+ * @returns 構築された SQL クエリーテキストを返します。
50
+ */
12
51
  toString(): string;
13
52
  }
53
+ /**
54
+ * 生の文字列を SQL 断片として扱います。
55
+ * この値はパラメーター化の対象にならず、そのままクエリーに含まれます。
56
+ *
57
+ * @param value SQL に含める生の文字列です。
58
+ * @returns 指定された文字列を持つ Sql インスタンスを返します。
59
+ */
14
60
  export declare function raw(value: string): Sql;
61
+ /**
62
+ * 空の SQL インスタンスを表す定数です。
63
+ */
15
64
  export declare const empty: Sql;
65
+ /**
66
+ * 複数の SQL 断片や値を、指定されたセパレーターで結合します。
67
+ *
68
+ * 配列が空の場合は、{@link empty} を返します。
69
+ *
70
+ * @param values 結合対象となる値の配列です。
71
+ * @param separator 結合時に挿入される文字列です。デフォルトはカンマ(,)です。
72
+ * @returns 結合された新しい Sql インスタンスを返します。
73
+ */
16
74
  export declare function join(values: readonly RawValue[], separator?: string | undefined): Sql;
75
+ /**
76
+ * 文字列を SQL の識別子(テーブル名やカラム名など)として安全にエスケープします。
77
+ *
78
+ * 二重引用符を二重にすることでエスケープを行い、全体を二重引用符で囲みます。
79
+ *
80
+ * @param value エスケープする識別子の文字列です。
81
+ * @returns エスケープ済みの識別子文字列を返します。
82
+ */
83
+ export declare function ident(value: string): string;
17
84
  //# sourceMappingURL=core.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/core.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,KAAK,GAAG,OAAO,CAAC;AAE5B,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,GAAG,CAAC;AAEnC,qBAAa,GAAG;IACd,IAAW,IAAI,IAAI,MAAM,CAiBxB;IAED,SAAgB,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IAEzC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAShB;gBAEiB,UAAU,EAAE,SAAS,MAAM,EAAE,EAAE,WAAW,EAAE,SAAS,QAAQ,EAAE;IA0E3E,MAAM,IAAI;QACf,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,KAAK,EAAE,CAAC;KACjB;IAOM,QAAQ,IAAI,MAAM;CAG1B;AAED,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAEtC;AAED,eAAO,MAAM,KAAK,EAAE,GAAa,CAAC;AAElC,wBAAgB,IAAI,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,EAAE,SAAS,GAAE,MAAM,GAAG,SAAe,GAAG,GAAG,CAM1F"}
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/core.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,KAAK,GAAG,OAAO,CAAC;AAE5B;;GAEG;AAEH,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,GAAG,CAAC;AAsBnC;;;GAGG;AACH,qBAAa,GAAG;IACd;;;;;;OAMG;IACH,IAAW,IAAI,IAAI,MAAM,CAexB;IAED;;OAEG;IACH,SAAgB,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IAEzC;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAgB;IAElC;;;;;OAKG;gBACgB,UAAU,EAAE,SAAS,MAAM,EAAE,EAAE,WAAW,EAAE,SAAS,QAAQ,EAAE;IAsElF;;;;OAIG;IACI,MAAM,IAAI;QACf,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,KAAK,EAAE,CAAC;KACjB;IAOD;;;;OAIG;IACI,QAAQ,IAAI,MAAM;CAG1B;AAED;;;;;;GAMG;AACH,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAEtC;AAED;;GAEG;AACH,eAAO,MAAM,KAAK,EAAE,GAAa,CAAC;AAElC;;;;;;;;GAQG;AACH,wBAAgB,IAAI,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,EAAE,SAAS,GAAE,MAAM,GAAG,SAAe,GAAG,GAAG,CAM1F;AAOD;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3C"}
package/dist/src/core.js CHANGED
@@ -1,19 +1,33 @@
1
+ /**
2
+ * 安全な SQL クエリーを構築するためのクラスです。
3
+ * プレースホルダーを使用したパラメーター化クエリーを生成します。
4
+ */
1
5
  export class Sql {
6
+ /**
7
+ * 構築された SQL クエリーテキストを取得します。
8
+ *
9
+ * 初回アクセス時に文字列が結合され、結果はキャッシュされます。
10
+ *
11
+ * @returns SQL クエリーテキストを返します。
12
+ */
2
13
  get text() {
3
- if (!this._.t.c) {
4
- let text = this._.s[0], placeholderIds = this._.t.v;
5
- if (placeholderIds.length > 0) {
6
- for (let i = 0, len = placeholderIds.length; i < len; i++) {
7
- text += "$" + placeholderIds[i] + this._.s[i + 1];
8
- }
14
+ // キャッシュが存在しない場合にのみ、文字列を構築します。
15
+ if (this._.t === undefined) {
16
+ let i = 0, text = this._.s[0];
17
+ // 文字列の断片とプレースホルダー($1, $2...)を交互に結合します。
18
+ for (; i < this._.p.length; i++) {
19
+ text += "$" + this._.p[i] + this._.s[i + 1];
9
20
  }
10
- this._.t = {
11
- c: true,
12
- v: text,
13
- };
21
+ this._.t = text;
14
22
  }
15
- return this._.t.v;
23
+ return this._.t;
16
24
  }
25
+ /**
26
+ * 新しい Sql インスタンスを初期化します。
27
+ *
28
+ * @param rawStrings SQL の断片となる文字列の配列です。
29
+ * @param rawBindings 文字列の間に挿入される値の配列です。
30
+ */
17
31
  constructor(rawStrings, rawBindings) {
18
32
  if (rawStrings.length === 0) {
19
33
  throw new TypeError("Expected at least 1 string");
@@ -24,66 +38,113 @@ export class Sql {
24
38
  const strings = [rawStrings[0]];
25
39
  const bindings = [];
26
40
  const placeholderIds = [];
27
- if (rawStrings.length > 0) {
28
- const valueToId = new Map();
29
- for (let i = 0, len = rawBindings.length, child, rawString, placeholderId; i < len; i++) {
30
- child = rawBindings[i];
31
- rawString = rawStrings[i + 1];
32
- if (child instanceof Sql) {
33
- strings[strings.length - 1] += child._.s[0];
34
- for (let j = 0, value; j < child.values.length; j++) {
35
- value = child.values[j];
36
- placeholderId = valueToId.get(value);
37
- if (placeholderId === undefined) {
38
- bindings.push(value);
39
- placeholderId = bindings.length;
40
- valueToId.set(value, placeholderId);
41
- }
42
- strings.push(child._.s[j + 1]);
43
- placeholderIds.push(placeholderId);
44
- }
45
- strings[strings.length - 1] += rawString;
46
- }
47
- else {
48
- placeholderId = valueToId.get(child);
41
+ /** 値の重複を排除し、同じ値には同じプレースホルダー ID を割り当てるためのマップです。 */
42
+ const valueToId = new Map();
43
+ // 提供された全てのバインディング値を走査して、SQL 文字列と値を正規化します。
44
+ for (let i = 0; i < rawBindings.length; i++) {
45
+ const child = rawBindings[i];
46
+ const rawString = rawStrings[i + 1];
47
+ // バインディング値が Sql インスタンス(ネストされたクエリー)の場合の処理です。
48
+ if (child instanceof Sql) {
49
+ // 現在の最後の文字列断片に、ネストされた Sql の最初の断片を結合します。
50
+ strings[strings.length - 1] += child._.s[0];
51
+ // ネストされた Sql のプレースホルダーと値を再マッピングします。
52
+ for (let j = 0; j < child._.p.length; j++) {
53
+ const childPlaceholderId = child._.p[j];
54
+ const value = child.values[childPlaceholderId - 1];
55
+ let placeholderId = valueToId.get(value);
49
56
  if (placeholderId === undefined) {
50
- bindings.push(child);
57
+ bindings.push(value);
51
58
  placeholderId = bindings.length;
52
- valueToId.set(child, placeholderId);
59
+ valueToId.set(value, placeholderId);
53
60
  }
54
- strings.push(rawString);
61
+ strings.push(child._.s[j + 1]);
55
62
  placeholderIds.push(placeholderId);
56
63
  }
64
+ // ネストされた Sql の展開が終わった後に、後続の生の文字列を結合します。
65
+ strings[strings.length - 1] += rawString;
66
+ }
67
+ else {
68
+ let placeholderId = valueToId.get(child);
69
+ if (placeholderId === undefined) {
70
+ bindings.push(child);
71
+ placeholderId = bindings.length;
72
+ valueToId.set(child, placeholderId);
73
+ }
74
+ strings.push(rawString);
75
+ placeholderIds.push(placeholderId);
57
76
  }
58
77
  }
59
78
  this.values = bindings;
79
+ // 内部状態を隠蔽し、不必要なプロパティーの露出を防ぎます。
60
80
  Object.defineProperty(this, "_", {
61
- value: this._ = {
62
- t: {
63
- c: false,
64
- v: placeholderIds,
65
- },
81
+ value: {
66
82
  s: strings,
83
+ p: placeholderIds,
67
84
  },
68
85
  });
69
86
  }
87
+ /**
88
+ * オブジェクトを JSON 形式に変換可能な形式で返します。
89
+ *
90
+ * @returns クエリーテキストと値の配列を含むオブジェクトを返します。
91
+ */
70
92
  toJSON() {
71
93
  return {
72
94
  text: this.text,
73
95
  values: this.values.slice(),
74
96
  };
75
97
  }
98
+ /**
99
+ * インスタンスを文字列に変換します。
100
+ *
101
+ * @returns 構築された SQL クエリーテキストを返します。
102
+ */
76
103
  toString() {
77
104
  return this.text;
78
105
  }
79
106
  }
107
+ /**
108
+ * 生の文字列を SQL 断片として扱います。
109
+ * この値はパラメーター化の対象にならず、そのままクエリーに含まれます。
110
+ *
111
+ * @param value SQL に含める生の文字列です。
112
+ * @returns 指定された文字列を持つ Sql インスタンスを返します。
113
+ */
80
114
  export function raw(value) {
81
115
  return new Sql([value], []);
82
116
  }
117
+ /**
118
+ * 空の SQL インスタンスを表す定数です。
119
+ */
83
120
  export const empty = raw("");
121
+ /**
122
+ * 複数の SQL 断片や値を、指定されたセパレーターで結合します。
123
+ *
124
+ * 配列が空の場合は、{@link empty} を返します。
125
+ *
126
+ * @param values 結合対象となる値の配列です。
127
+ * @param separator 結合時に挿入される文字列です。デフォルトはカンマ(,)です。
128
+ * @returns 結合された新しい Sql インスタンスを返します。
129
+ */
84
130
  export function join(values, separator = ",") {
85
131
  if (values.length === 0) {
86
132
  return empty;
87
133
  }
88
134
  return new Sql(["", ...Array(values.length - 1).fill(separator), ""], values);
89
135
  }
136
+ /**
137
+ * 二重引用符をエスケープするための正規表現です。
138
+ */
139
+ const DOUBLE_QUOTE_REGEX = /"/g;
140
+ /**
141
+ * 文字列を SQL の識別子(テーブル名やカラム名など)として安全にエスケープします。
142
+ *
143
+ * 二重引用符を二重にすることでエスケープを行い、全体を二重引用符で囲みます。
144
+ *
145
+ * @param value エスケープする識別子の文字列です。
146
+ * @returns エスケープ済みの識別子文字列を返します。
147
+ */
148
+ export function ident(value) {
149
+ return '"' + value.replace(DOUBLE_QUOTE_REGEX, '""') + '"';
150
+ }
@@ -1,3 +1,3 @@
1
- export { empty, join, raw, type RawValue, Sql, type Value } from "./core.js";
1
+ export { empty, join, raw, ident, type RawValue, Sql, type Value } from "./core.js";
2
2
  export { sql } from "./sql.js";
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,QAAQ,EAAE,GAAG,EAAE,KAAK,KAAK,EAAE,MAAM,WAAW,CAAC;AAE7E,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,QAAQ,EAAE,GAAG,EAAE,KAAK,KAAK,EAAE,MAAM,WAAW,CAAC;AAEpF,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC"}
package/dist/src/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { empty, join, raw, Sql } from "./core.js";
1
+ export { empty, join, raw, ident, Sql } from "./core.js";
2
2
  export { sql } from "./sql.js";
package/dist/src/sql.d.ts CHANGED
@@ -1,14 +1,50 @@
1
- import { join, raw, type RawValue, Sql } from "./core.js";
1
+ import { join, raw, Sql, ident } from "./core.js";
2
2
  declare namespace sql {
3
+ /**
4
+ * SQL クエリーの構築に使用できる生の値、または別の Sql インスタンスを表す型です。
5
+ */
3
6
  type RawValue = import("./core.js").RawValue;
7
+ /**
8
+ * SQL クエリー内で使用される値の型定義です。
9
+ */
4
10
  type Value = import("./core.js").Value;
11
+ /**
12
+ * 安全な SQL クエリーを構築するためのクラス型です。
13
+ */
5
14
  type Sql = import("./core.js").Sql;
6
15
  }
7
- declare const sql: ((strings: TemplateStringsArray, ...bindings: readonly RawValue[]) => Sql) & {
16
+ /**
17
+ * テンプレートリテラルを使用して SQL クエリーを安全に構築するためのタグ関数です。
18
+ *
19
+ * @param strings テンプレートリテラルの静的な文字列部分の配列です。
20
+ * @param bindings テンプレートリテラルに埋め込まれた動的な値の配列です。
21
+ * @returns パラメーター化された SQL 情報を保持する Sql インスタンスを返します。
22
+ * @example
23
+ * ```typescript
24
+ * const query = sql`SELECT * FROM users WHERE id = ${1}`;
25
+ * ```
26
+ */
27
+ declare const sql: ((strings: TemplateStringsArray, ...bindings: readonly sql.RawValue[]) => sql.Sql) & {
28
+ /**
29
+ * Sql クラス自体への参照です。
30
+ */
8
31
  readonly Sql: typeof Sql;
32
+ /**
33
+ * 生の文字列を SQL 断片として扱うための関数です。
34
+ */
9
35
  readonly raw: typeof raw;
36
+ /**
37
+ * 複数の SQL 断片を結合するための関数です。
38
+ */
10
39
  readonly join: typeof join;
40
+ /**
41
+ * 空の SQL クエリーを表す定数です。
42
+ */
11
43
  readonly empty: Sql;
44
+ /**
45
+ * 識別子(テーブル名等)を安全にエスケープするための関数です。
46
+ */
47
+ readonly ident: typeof ident;
12
48
  };
13
49
  export { sql };
14
50
  //# sourceMappingURL=sql.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../../src/sql.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,IAAI,EAAE,GAAG,EAAE,KAAK,QAAQ,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEjE,kBAAU,GAAG,CAAC;IACZ,KAAY,QAAQ,GAAG,OAAO,WAAW,EAAE,QAAQ,CAAC;IAEpD,KAAY,KAAK,GAAG,OAAO,WAAW,EAAE,KAAK,CAAC;IAE9C,KAAY,GAAG,GAAG,OAAO,WAAW,EAAE,GAAG,CAAC;CAC3C;AAED,QAAA,MAAM,GAAG,aACe,oBAAoB,eAAe,SAAS,QAAQ,EAAE,KAAG,GAAG;;;;;CASnF,CAAC;AAEF,OAAO,EAAE,GAAG,EAAE,CAAC"}
1
+ {"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../../src/sql.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAEzD,kBAAU,GAAG,CAAC;IACZ;;OAEG;IACH,KAAY,QAAQ,GAAG,OAAO,WAAW,EAAE,QAAQ,CAAC;IAEpD;;OAEG;IACH,KAAY,KAAK,GAAG,OAAO,WAAW,EAAE,KAAK,CAAC;IAE9C;;OAEG;IACH,KAAY,GAAG,GAAG,OAAO,WAAW,EAAE,GAAG,CAAC;CAC3C;AAED;;;;;;;;;;GAUG;AACH,QAAA,MAAM,GAAG,aACe,oBAAoB,eAAe,SAAS,GAAG,CAAC,QAAQ,EAAE,KAAG,GAAG,CAAC,GAAG;IAIxF;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;CAGN,CAAC;AAEF,OAAO,EAAE,GAAG,EAAE,CAAC"}
package/dist/src/sql.js CHANGED
@@ -1,10 +1,37 @@
1
- import { empty, join, raw, Sql } from "./core.js";
1
+ import { empty, join, raw, Sql, ident } from "./core.js";
2
+ /**
3
+ * テンプレートリテラルを使用して SQL クエリーを安全に構築するためのタグ関数です。
4
+ *
5
+ * @param strings テンプレートリテラルの静的な文字列部分の配列です。
6
+ * @param bindings テンプレートリテラルに埋め込まれた動的な値の配列です。
7
+ * @returns パラメーター化された SQL 情報を保持する Sql インスタンスを返します。
8
+ * @example
9
+ * ```typescript
10
+ * const query = sql`SELECT * FROM users WHERE id = ${1}`;
11
+ * ```
12
+ */
2
13
  const sql = /*#__PURE__*/ Object.assign(function sql(strings, ...bindings) {
3
14
  return new Sql(strings, bindings);
4
15
  }, {
16
+ /**
17
+ * Sql クラス自体への参照です。
18
+ */
5
19
  Sql,
20
+ /**
21
+ * 生の文字列を SQL 断片として扱うための関数です。
22
+ */
6
23
  raw,
24
+ /**
25
+ * 複数の SQL 断片を結合するための関数です。
26
+ */
7
27
  join,
28
+ /**
29
+ * 空の SQL クエリーを表す定数です。
30
+ */
8
31
  empty,
32
+ /**
33
+ * 識別子(テーブル名等)を安全にエスケープするための関数です。
34
+ */
35
+ ident,
9
36
  });
10
37
  export { sql };
package/package.json CHANGED
@@ -1,42 +1,48 @@
1
1
  {
2
2
  "name": "pgsql-template-tag",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "",
5
- "type": "module",
6
- "sideEffects": false,
5
+ "homepage": "https://github.com/tai-kun/pgsql-template-tag",
7
6
  "license": "MIT",
8
7
  "author": "tai-kun",
8
+ "repository": {
9
+ "url": "https://github.com/tai-kun/pgsql-template-tag"
10
+ },
9
11
  "files": [
10
12
  "dist",
11
- "src"
13
+ "src",
14
+ "package.json",
15
+ "LICENSE",
16
+ "README.md"
12
17
  ],
18
+ "type": "module",
19
+ "sideEffects": false,
13
20
  "main": "./dist/src/index.js",
14
- "module": "./dist/src/index.js",
15
21
  "types": "./dist/src/index.d.ts",
16
22
  "exports": {
17
23
  ".": {
18
24
  "types": "./dist/src/index.d.ts",
19
- "import": "./dist/src/index.js",
20
- "require": "./dist/src/index.js"
25
+ "default": "./dist/src/index.js"
21
26
  }
22
27
  },
23
- "homepage": "https://tai-kun.github.io/pgsql-template-tag",
24
- "repository": {
25
- "url": "https://github.com/tai-kun/pgsql-template-tag"
26
- },
27
- "scripts": {
28
- "build": "tsc --project ./.config/tsconfig.build.json"
29
- },
30
28
  "devDependencies": {
31
- "@tsconfig/node22": "^22.0.5",
29
+ "@tsconfig/node24": "^24.0.4",
32
30
  "@tsconfig/strictest": "^2.0.8",
33
- "@types/node": "^22.19.17",
34
- "@vitest/browser": "^4.1.2",
35
- "@vitest/browser-playwright": "^4.1.2",
36
- "dprint": "^0.53.2",
37
- "npm-check-updates": "^20.0.0",
31
+ "@types/node": "^24.12.2",
32
+ "@valibot/i18n": "^1.1.0",
33
+ "@vitest/browser": "^4.1.5",
34
+ "@vitest/browser-playwright": "^4.1.5",
35
+ "npm-check-updates": "^20.0.2",
36
+ "oxfmt": "^0.46.0",
37
+ "oxlint": "^1.61.0",
38
+ "oxlint-tsgolint": "^0.22.0",
38
39
  "playwright": "^1.59.1",
39
- "typescript": "^6.0.2",
40
- "vitest": "^4.1.2"
40
+ "typescript": "^6.0.3",
41
+ "vite": "^8.0.10",
42
+ "vitest": "^4.1.5"
43
+ },
44
+ "readme": "README.md",
45
+ "scripts": {
46
+ "build": "tsc --project ./.config/tsconfig.build.json"
41
47
  }
42
- }
48
+ }
package/src/core.ts CHANGED
@@ -1,40 +1,79 @@
1
+ /**
2
+ * SQL クエリー内で使用される値の型定義です。
3
+ */
1
4
  export type Value = unknown;
2
5
 
6
+ /**
7
+ * SQL クエリーの構築に使用できる生の値、または別の Sql インスタンスを表す型です。
8
+ */
9
+ // oxlint-disable-next-line typescript/no-redundant-type-constituents
3
10
  export type RawValue = Value | Sql;
4
11
 
12
+ /**
13
+ * Sql クラスの内部状態を管理するためのプライベートな型定義です。
14
+ */
15
+ type PrivateState = {
16
+ /**
17
+ * クエリーを構成する静的な文字列の配列です。
18
+ */
19
+ readonly s: readonly [string, ...string[]];
20
+
21
+ /**
22
+ * プレースホルダーに対応するインデックス(1 から始まる数値)の配列です。
23
+ */
24
+ readonly p: readonly number[];
25
+
26
+ /**
27
+ * キャッシュされた最終的なクエリーテキストです。
28
+ */
29
+ t?: string;
30
+ };
31
+
32
+ /**
33
+ * 安全な SQL クエリーを構築するためのクラスです。
34
+ * プレースホルダーを使用したパラメーター化クエリーを生成します。
35
+ */
5
36
  export class Sql {
37
+ /**
38
+ * 構築された SQL クエリーテキストを取得します。
39
+ *
40
+ * 初回アクセス時に文字列が結合され、結果はキャッシュされます。
41
+ *
42
+ * @returns SQL クエリーテキストを返します。
43
+ */
6
44
  public get text(): string {
7
- if (!this._.t.c) {
8
- let text = this._.s[0],
9
- placeholderIds = this._.t.v;
10
- if (placeholderIds.length > 0) {
11
- for (let i = 0, len = placeholderIds.length; i < len; i++) {
12
- text += "$" + placeholderIds[i] + this._.s[i + 1];
13
- }
45
+ // キャッシュが存在しない場合にのみ、文字列を構築します。
46
+ if (this._.t === undefined) {
47
+ let i = 0,
48
+ text = this._.s[0];
49
+
50
+ // 文字列の断片とプレースホルダー($1, $2...)を交互に結合します。
51
+ for (; i < this._.p.length; i++) {
52
+ text += "$" + this._.p[i] + this._.s[i + 1];
14
53
  }
15
54
 
16
- this._.t = {
17
- c: true,
18
- v: text,
19
- };
55
+ this._.t = text;
20
56
  }
21
57
 
22
- return this._.t.v;
58
+ return this._.t;
23
59
  }
24
60
 
61
+ /**
62
+ * クエリーに使用されるパラメーター値の配列です。
63
+ */
25
64
  public readonly values: readonly Value[];
26
65
 
27
- private readonly _: {
28
- t: {
29
- readonly c: false;
30
- readonly v: readonly number[];
31
- } | {
32
- readonly c: true;
33
- readonly v: string;
34
- };
35
- readonly s: readonly [string, ...string[]];
36
- };
37
-
66
+ /**
67
+ * 内部状態を保持するためのプロパティーです。
68
+ */
69
+ private readonly _!: PrivateState;
70
+
71
+ /**
72
+ * 新しい Sql インスタンスを初期化します。
73
+ *
74
+ * @param rawStrings SQL の断片となる文字列の配列です。
75
+ * @param rawBindings 文字列の間に挿入される値の配列です。
76
+ */
38
77
  public constructor(rawStrings: readonly string[], rawBindings: readonly RawValue[]) {
39
78
  if (rawStrings.length === 0) {
40
79
  throw new TypeError("Expected at least 1 string");
@@ -50,65 +89,66 @@ export class Sql {
50
89
  const bindings: Value[] = [];
51
90
  const placeholderIds: number[] = [];
52
91
 
53
- if (rawStrings.length > 0) {
54
- const valueToId = new Map<Value, number>();
55
-
56
- for (
57
- let i = 0,
58
- len = rawBindings.length,
59
- child: unknown,
60
- rawString: string,
61
- placeholderId: number | undefined;
62
- i < len;
63
- i++
64
- ) {
65
- child = rawBindings[i];
66
- rawString = rawStrings[i + 1]!;
67
-
68
- if (child instanceof Sql) {
69
- strings[strings.length - 1] += child._.s[0];
70
-
71
- for (let j = 0, value: unknown; j < child.values.length; j++) {
72
- value = child.values[j];
73
- placeholderId = valueToId.get(value);
74
- if (placeholderId === undefined) {
75
- bindings.push(value);
76
- placeholderId = bindings.length;
77
- valueToId.set(value, placeholderId);
78
- }
79
-
80
- strings.push(child._.s[j + 1]!);
81
- placeholderIds.push(placeholderId);
82
- }
92
+ /** 値の重複を排除し、同じ値には同じプレースホルダー ID を割り当てるためのマップです。 */
93
+ const valueToId = new Map<Value, number>();
94
+
95
+ // 提供された全てのバインディング値を走査して、SQL 文字列と値を正規化します。
96
+ for (let i = 0; i < rawBindings.length; i++) {
97
+ const child = rawBindings[i];
98
+ const rawString = rawStrings[i + 1]!;
99
+
100
+ // バインディング値が Sql インスタンス(ネストされたクエリー)の場合の処理です。
101
+ if (child instanceof Sql) {
102
+ // 現在の最後の文字列断片に、ネストされた Sql の最初の断片を結合します。
103
+ strings[strings.length - 1] += child._.s[0];
83
104
 
84
- strings[strings.length - 1] += rawString;
85
- } else {
86
- placeholderId = valueToId.get(child);
105
+ // ネストされた Sql のプレースホルダーと値を再マッピングします。
106
+ for (let j = 0; j < child._.p.length; j++) {
107
+ const childPlaceholderId = child._.p[j]!;
108
+ const value = child.values[childPlaceholderId - 1]!;
87
109
 
110
+ let placeholderId = valueToId.get(value);
88
111
  if (placeholderId === undefined) {
89
- bindings.push(child);
112
+ bindings.push(value);
90
113
  placeholderId = bindings.length;
91
- valueToId.set(child, placeholderId);
114
+ valueToId.set(value, placeholderId);
92
115
  }
93
116
 
94
- strings.push(rawString);
117
+ strings.push(child._.s[j + 1]!);
95
118
  placeholderIds.push(placeholderId);
96
119
  }
120
+
121
+ // ネストされた Sql の展開が終わった後に、後続の生の文字列を結合します。
122
+ strings[strings.length - 1] += rawString;
123
+ } else {
124
+ let placeholderId = valueToId.get(child);
125
+ if (placeholderId === undefined) {
126
+ bindings.push(child);
127
+ placeholderId = bindings.length;
128
+ valueToId.set(child, placeholderId);
129
+ }
130
+
131
+ strings.push(rawString);
132
+ placeholderIds.push(placeholderId);
97
133
  }
98
134
  }
99
135
 
100
136
  this.values = bindings;
137
+
138
+ // 内部状態を隠蔽し、不必要なプロパティーの露出を防ぎます。
101
139
  Object.defineProperty(this, "_", {
102
- value: this._ = {
103
- t: {
104
- c: false,
105
- v: placeholderIds,
106
- },
140
+ value: {
107
141
  s: strings,
142
+ p: placeholderIds,
108
143
  },
109
144
  });
110
145
  }
111
146
 
147
+ /**
148
+ * オブジェクトを JSON 形式に変換可能な形式で返します。
149
+ *
150
+ * @returns クエリーテキストと値の配列を含むオブジェクトを返します。
151
+ */
112
152
  public toJSON(): {
113
153
  text: string;
114
154
  values: Value[];
@@ -119,17 +159,41 @@ export class Sql {
119
159
  };
120
160
  }
121
161
 
162
+ /**
163
+ * インスタンスを文字列に変換します。
164
+ *
165
+ * @returns 構築された SQL クエリーテキストを返します。
166
+ */
122
167
  public toString(): string {
123
168
  return this.text;
124
169
  }
125
170
  }
126
171
 
172
+ /**
173
+ * 生の文字列を SQL 断片として扱います。
174
+ * この値はパラメーター化の対象にならず、そのままクエリーに含まれます。
175
+ *
176
+ * @param value SQL に含める生の文字列です。
177
+ * @returns 指定された文字列を持つ Sql インスタンスを返します。
178
+ */
127
179
  export function raw(value: string): Sql {
128
180
  return new Sql([value], []);
129
181
  }
130
182
 
183
+ /**
184
+ * 空の SQL インスタンスを表す定数です。
185
+ */
131
186
  export const empty: Sql = raw("");
132
187
 
188
+ /**
189
+ * 複数の SQL 断片や値を、指定されたセパレーターで結合します。
190
+ *
191
+ * 配列が空の場合は、{@link empty} を返します。
192
+ *
193
+ * @param values 結合対象となる値の配列です。
194
+ * @param separator 結合時に挿入される文字列です。デフォルトはカンマ(,)です。
195
+ * @returns 結合された新しい Sql インスタンスを返します。
196
+ */
133
197
  export function join(values: readonly RawValue[], separator: string | undefined = ","): Sql {
134
198
  if (values.length === 0) {
135
199
  return empty;
@@ -137,3 +201,20 @@ export function join(values: readonly RawValue[], separator: string | undefined
137
201
 
138
202
  return new Sql(["", ...Array(values.length - 1).fill(separator), ""], values);
139
203
  }
204
+
205
+ /**
206
+ * 二重引用符をエスケープするための正規表現です。
207
+ */
208
+ const DOUBLE_QUOTE_REGEX = /"/g;
209
+
210
+ /**
211
+ * 文字列を SQL の識別子(テーブル名やカラム名など)として安全にエスケープします。
212
+ *
213
+ * 二重引用符を二重にすることでエスケープを行い、全体を二重引用符で囲みます。
214
+ *
215
+ * @param value エスケープする識別子の文字列です。
216
+ * @returns エスケープ済みの識別子文字列を返します。
217
+ */
218
+ export function ident(value: string): string {
219
+ return '"' + value.replace(DOUBLE_QUOTE_REGEX, '""') + '"';
220
+ }
package/src/index.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { empty, join, raw, type RawValue, Sql, type Value } from "./core.js";
1
+ export { empty, join, raw, ident, type RawValue, Sql, type Value } from "./core.js";
2
2
 
3
3
  export { sql } from "./sql.js";
package/src/sql.ts CHANGED
@@ -1,22 +1,62 @@
1
- import { empty, join, raw, type RawValue, Sql } from "./core.js";
1
+ import { empty, join, raw, Sql, ident } from "./core.js";
2
2
 
3
3
  namespace sql {
4
+ /**
5
+ * SQL クエリーの構築に使用できる生の値、または別の Sql インスタンスを表す型です。
6
+ */
4
7
  export type RawValue = import("./core.js").RawValue;
5
8
 
9
+ /**
10
+ * SQL クエリー内で使用される値の型定義です。
11
+ */
6
12
  export type Value = import("./core.js").Value;
7
13
 
14
+ /**
15
+ * 安全な SQL クエリーを構築するためのクラス型です。
16
+ */
8
17
  export type Sql = import("./core.js").Sql;
9
18
  }
10
19
 
20
+ /**
21
+ * テンプレートリテラルを使用して SQL クエリーを安全に構築するためのタグ関数です。
22
+ *
23
+ * @param strings テンプレートリテラルの静的な文字列部分の配列です。
24
+ * @param bindings テンプレートリテラルに埋め込まれた動的な値の配列です。
25
+ * @returns パラメーター化された SQL 情報を保持する Sql インスタンスを返します。
26
+ * @example
27
+ * ```typescript
28
+ * const query = sql`SELECT * FROM users WHERE id = ${1}`;
29
+ * ```
30
+ */
11
31
  const sql = /*#__PURE__*/ Object.assign(
12
- function sql(strings: TemplateStringsArray, ...bindings: readonly RawValue[]): Sql {
32
+ function sql(strings: TemplateStringsArray, ...bindings: readonly sql.RawValue[]): sql.Sql {
13
33
  return new Sql(strings, bindings);
14
34
  },
15
35
  {
36
+ /**
37
+ * Sql クラス自体への参照です。
38
+ */
16
39
  Sql,
40
+
41
+ /**
42
+ * 生の文字列を SQL 断片として扱うための関数です。
43
+ */
17
44
  raw,
45
+
46
+ /**
47
+ * 複数の SQL 断片を結合するための関数です。
48
+ */
18
49
  join,
50
+
51
+ /**
52
+ * 空の SQL クエリーを表す定数です。
53
+ */
19
54
  empty,
55
+
56
+ /**
57
+ * 識別子(テーブル名等)を安全にエスケープするための関数です。
58
+ */
59
+ ident,
20
60
  } as const,
21
61
  );
22
62