pgsql-template-tag 0.0.6 → 0.0.7

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.
@@ -1,3 +1,4 @@
1
+ import type { UnionToTuple } from "type-fest";
1
2
  /**
2
3
  * SQL クエリー内で使用される値の型定義です。
3
4
  */
@@ -6,34 +7,122 @@ export type Value = unknown;
6
7
  * SQL クエリーの構築に使用できる生の値、または別の Sql インスタンスを表す型です。
7
8
  */
8
9
  export type RawValue = Value | Slot | Sql;
10
+ /**
11
+ * Slot クラスを一意に識別するためのシンボルです。
12
+ */
13
+ declare const SLOT_SYMBOL: unique symbol;
14
+ /**
15
+ * Slot クラスの基底となる型定義です。
16
+ */
17
+ declare const SlotTypes: {
18
+ new (): {
19
+ /**
20
+ * このプロパティーは、TypeScript の `extends Slot` で `Slot` インスタンスのみに一致させるためにあります。そのため、`Slot` と同じプロパティーを持つオブジェクトに対して一致することはありません。
21
+ */
22
+ readonly ["~kind"]: typeof SLOT_SYMBOL;
23
+ };
24
+ };
9
25
  /**
10
26
  * スロットを表すクラスです。
11
27
  *
12
28
  * スロットは後から値を注入可能なプレースホルダーです。
29
+ *
30
+ * @template TName スロットの名前となる文字列リテラル型です。
31
+ * @template TValue スロットに許容される値の型です。
13
32
  */
14
- export declare class Slot {
33
+ export declare class Slot<const TName extends string = string, TValue extends RawValue = RawValue> extends SlotTypes {
15
34
  /**
16
35
  * スロット名です。
17
36
  */
18
- readonly name: string;
37
+ readonly name: TName;
19
38
  /**
20
39
  * デフォルト値です。
21
40
  */
22
- readonly defaultValue: Value;
41
+ readonly defaultValue: TValue;
23
42
  /**
24
43
  * 新しい Slot インスタンスを初期化します。
25
44
  *
26
45
  * @param name スロット名です。
27
46
  * @param defaultValue デフォルト値です。
28
47
  */
29
- constructor(name: string, defaultValue?: Value);
48
+ constructor(...args: null extends TValue ? [name: TName, defaultValue?: TValue] : [name: TName, defaultValue: TValue]);
30
49
  }
50
+ /**
51
+ * オブジェクトのバリューの型を抽出するヘルパー型です。
52
+ *
53
+ * @template T 対象となるオブジェクト型です。
54
+ */
55
+ type $ValueOf<T> = T[keyof T];
56
+ /**
57
+ * スロットの配列から、再帰的に値をマージして型を決定します。
58
+ *
59
+ * @template TSlots スロットのタプル型です。
60
+ */
61
+ type $MergeSlotValue<TSlots> = TSlots extends [
62
+ Slot<string, infer TValue>,
63
+ infer TSlot,
64
+ ...infer TOtherSlots
65
+ ] ? TValue & $MergeSlotValue<[TSlot, ...TOtherSlots]> : TSlots extends [Slot<string, infer TValue>] ? TValue : never;
66
+ /**
67
+ * RawValue の配列からスロット情報を抽出し、名前ごとのマップ型に変換します。
68
+ *
69
+ * @template TValues RawValue の読み取り専用配列型です。
70
+ */
71
+ type $MapSlotValue<TValues extends readonly RawValue[]> = TValues extends readonly (infer TSlot extends Slot)[] ? {
72
+ [TName in TSlot["name"]]: $MergeSlotValue<UnionToTuple<Extract<TSlot, Slot<TName>>>>;
73
+ } : {};
74
+ /**
75
+ * 指定された値のリストに対して、スロットを実際の内容で置き換えた型を生成します。
76
+ *
77
+ * @template TValues 置き換え対象の配列型です。
78
+ * @template TSlots スロット名と値のマップ型です。
79
+ */
80
+ type $FillSlots<TValues, TSlots> = TValues extends [infer TValue, ...infer TOtherValues] ? [
81
+ TValue extends Slot<infer TName extends Extract<keyof TSlots, string>, infer TSlotValue> ? TSlotValue extends TSlots[TName] ? TSlotValue : TValue : TValue,
82
+ ...$FillSlots<TOtherValues, TSlots>
83
+ ] : [];
84
+ /**
85
+ * スロットを埋めるための部分的な引数型を定義します。
86
+ *
87
+ * @template TSlots スロット名と値のマップ型です。
88
+ */
89
+ type _FillSlots<TSlots> = {
90
+ readonly [TName in Extract<keyof TSlots, string>]?: TSlots[TName];
91
+ } | Iterable<readonly [
92
+ name: $ValueOf<{
93
+ [TName in Extract<keyof TSlots, string>]: TName | Slot<TName, TSlots[TName]>;
94
+ }>,
95
+ value: $ValueOf<TSlots>
96
+ ]>;
97
+ /**
98
+ * すべてのスロットを埋めるために必要な引数型を定義します。
99
+ *
100
+ * @template TSlots スロット名と値のマップ型です。
101
+ */
102
+ type _FillAllSlots<TSlots> = {
103
+ readonly [TName in Extract<keyof TSlots, string>]: TSlots[TName];
104
+ };
105
+ /**
106
+ * スロットの部分的な補完に使用する外部向けの型定義です。
107
+ *
108
+ * @template TValues RawValue の配列です。
109
+ */
110
+ export type FillSlots<TValues extends readonly RawValue[] = readonly RawValue[]> = _FillSlots<$MapSlotValue<TValues>>;
111
+ /**
112
+ * すべてのスロットの強制的な補完に使用する外部向けの型定義です。
113
+ *
114
+ * @template TValues RawValue の配列です。
115
+ */
116
+ export type FillAllSlots<TValues extends readonly RawValue[] = readonly RawValue[]> = _FillAllSlots<$MapSlotValue<TValues>>;
31
117
  /**
32
118
  * 安全な SQL クエリーを構築するためのクラスです。
33
119
  *
34
120
  * プレースホルダーを使用したパラメーター化クエリーを生成します。
121
+ *
122
+ * @template TRawBindings クエリーに渡される生の値のタプル型です。
35
123
  */
36
- export declare class Sql {
124
+ export declare class Sql<const TRawBindings extends readonly RawValue[] = readonly RawValue[]> {
125
+ #private;
37
126
  /**
38
127
  * 構築された SQL クエリーテキストを取得します。
39
128
  *
@@ -46,26 +135,29 @@ export declare class Sql {
46
135
  * クエリーに使用されるパラメーター値の配列です。
47
136
  */
48
137
  readonly values: readonly Value[];
49
- /**
50
- * 内部状態を保持するためのプロパティーです。
51
- */
52
- private readonly _;
53
138
  /**
54
139
  * 新しい Sql インスタンスを初期化します。
55
140
  *
56
141
  * @param rawStrings SQL の断片となる文字列の配列です。
57
142
  * @param rawBindings 文字列の間に挿入される値の配列です。
58
143
  */
59
- constructor(rawStrings: readonly string[], rawBindings: readonly RawValue[]);
144
+ constructor(rawStrings: readonly string[], rawBindings: TRawBindings);
60
145
  /**
61
146
  * スロットを値で埋めます。
62
147
  *
148
+ * @template TSlots 指定されたスロットのマップ型です。
149
+ * @param slots スロットの値です。
150
+ * @returns スロットが埋められた新しい Sql インスタンスです。
151
+ */
152
+ fill<TSlots extends FillSlots<TRawBindings>>(slots: TSlots): Sql<$FillSlots<TRawBindings, TSlots>>;
153
+ /**
154
+ * すべてのスロットを値で埋めます。
155
+ *
156
+ * @template TSlots 全てのスロットをカバーするマップ型です。
63
157
  * @param slots スロットの値です。
64
158
  * @returns スロットが埋められた新しい Sql インスタンスです。
65
159
  */
66
- fill(slots: {
67
- readonly [name: string]: Value;
68
- } | Iterable<readonly [string | Slot, Value]>): Sql;
160
+ fillAll<TSlots extends FillAllSlots<TRawBindings>>(slots: TSlots): Sql<$FillSlots<TRawBindings, TSlots>>;
69
161
  /**
70
162
  * オブジェクトを JSON 形式に変換可能な形式で返します。
71
163
  *
@@ -123,4 +215,5 @@ export declare function ident(value: string): string;
123
215
  * @returns エスケープ済みのリテラル文字列を返します。
124
216
  */
125
217
  export declare function literal(value: string): string;
218
+ export {};
126
219
  //# sourceMappingURL=core.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/core.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,MAAM,KAAK,GAAG,OAAO,CAAC;AAE5B;;GAEG;AAEH,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,IAAI,GAAG,GAAG,CAAC;AAE1C;;;;GAIG;AACH,qBAAa,IAAI;IACf;;OAEG;IACH,SAAgB,IAAI,EAAE,MAAM,CAAC;IAE7B;;OAEG;IACH,SAAgB,YAAY,EAAE,KAAK,CAAC;IAEpC;;;;;OAKG;gBACgB,IAAI,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,KAAK;CAQtD;AAgCD;;;;GAIG;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;IA2GlF;;;;;OAKG;IACI,IAAI,CACT,KAAK,EAAE;QAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,CAAA;KAAE,GAAG,QAAQ,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,EAAE,KAAK,CAAC,CAAC,GACpF,GAAG;IAmCN;;;;OAIG;IACI,MAAM,IAAI;QACf,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,KAAK,EAAE,CAAC;KACjB;IAOD;;;;OAIG;IACI,QAAQ,IAAI,MAAM;CAG1B;AAED;;;;;;;GAOG;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;AAOD;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7C"}
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/core.ts"],"names":[],"mappings":"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"}
package/dist/src/core.js CHANGED
@@ -1,17 +1,32 @@
1
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
+ };
6
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
7
+ if (kind === "m") throw new TypeError("Private method is not writable");
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
+ };
12
+ var _Sql_instances, _a, _Sql_state, _Sql_fill;
1
13
  import { isPlainObject } from "es-toolkit/predicate";
14
+ /**
15
+ * Slot クラスの基底となる型定義です。
16
+ */
17
+ const SlotTypes = class {
18
+ };
2
19
  /**
3
20
  * スロットを表すクラスです。
4
21
  *
5
22
  * スロットは後から値を注入可能なプレースホルダーです。
23
+ *
24
+ * @template TName スロットの名前となる文字列リテラル型です。
25
+ * @template TValue スロットに許容される値の型です。
6
26
  */
7
- export class Slot {
8
- /**
9
- * 新しい Slot インスタンスを初期化します。
10
- *
11
- * @param name スロット名です。
12
- * @param defaultValue デフォルト値です。
13
- */
27
+ export class Slot extends SlotTypes {
14
28
  constructor(name, defaultValue) {
29
+ super();
15
30
  if (arguments.length < 2) {
16
31
  defaultValue = null;
17
32
  }
@@ -23,6 +38,8 @@ export class Slot {
23
38
  * 安全な SQL クエリーを構築するためのクラスです。
24
39
  *
25
40
  * プレースホルダーを使用したパラメーター化クエリーを生成します。
41
+ *
42
+ * @template TRawBindings クエリーに渡される生の値のタプル型です。
26
43
  */
27
44
  export class Sql {
28
45
  /**
@@ -34,15 +51,15 @@ export class Sql {
34
51
  */
35
52
  get text() {
36
53
  // キャッシュが存在しない場合にのみ、文字列を構築します。
37
- if (this._.text === undefined) {
38
- let i = 0, text = this._.parts[0];
54
+ if (__classPrivateFieldGet(this, _Sql_state, "f").text === undefined) {
55
+ let i = 0, text = __classPrivateFieldGet(this, _Sql_state, "f").parts[0];
39
56
  // 文字列の断片とプレースホルダー($1, $2...)を交互に結合します。
40
- for (; i < this._.phIds.length; i++) {
41
- text += "$" + this._.phIds[i] + this._.parts[i + 1];
57
+ for (; i < __classPrivateFieldGet(this, _Sql_state, "f").phIds.length; i++) {
58
+ text += "$" + __classPrivateFieldGet(this, _Sql_state, "f").phIds[i] + __classPrivateFieldGet(this, _Sql_state, "f").parts[i + 1];
42
59
  }
43
- this._.text = text;
60
+ __classPrivateFieldGet(this, _Sql_state, "f").text = text;
44
61
  }
45
- return this._.text;
62
+ return __classPrivateFieldGet(this, _Sql_state, "f").text;
46
63
  }
47
64
  /**
48
65
  * 新しい Sql インスタンスを初期化します。
@@ -51,6 +68,11 @@ export class Sql {
51
68
  * @param rawBindings 文字列の間に挿入される値の配列です。
52
69
  */
53
70
  constructor(rawStrings, rawBindings) {
71
+ _Sql_instances.add(this);
72
+ /**
73
+ * 内部状態を保持するためのプロパティーです。
74
+ */
75
+ _Sql_state.set(this, void 0);
54
76
  if (rawStrings.length === 0) {
55
77
  throw new TypeError("Expected at least 1 string");
56
78
  }
@@ -103,17 +125,17 @@ export class Sql {
103
125
  const child = rawBindings[i];
104
126
  const rawString = rawStrings[i + 1];
105
127
  // バインディング値が Sql インスタンス(ネストされたクエリー)の場合の処理です。
106
- if (child instanceof Sql) {
128
+ if (child instanceof _a) {
107
129
  // 現在の最後の文字列断片に、ネストされた Sql の最初の断片を結合します。
108
- strings[strings.length - 1] += child._.parts[0];
130
+ strings[strings.length - 1] += __classPrivateFieldGet(child, _Sql_state, "f").parts[0];
109
131
  // ネストされた Sql のプレースホルダーと値を再マッピングします。
110
- for (let j = 0; j < child._.phIds.length; j++) {
111
- const childPlaceholderId = child._.phIds[j];
132
+ for (let j = 0; j < __classPrivateFieldGet(child, _Sql_state, "f").phIds.length; j++) {
133
+ const childPlaceholderId = __classPrivateFieldGet(child, _Sql_state, "f").phIds[j];
112
134
  const valueIndex = childPlaceholderId - 1;
113
135
  const value = child.values[valueIndex];
114
- const slot = child._.idx2slot.get(valueIndex);
136
+ const slot = __classPrivateFieldGet(child, _Sql_state, "f").idx2slot.get(valueIndex);
115
137
  const placeholderId = slot !== undefined ? registerSlot(slot) : registerValue(value);
116
- strings.push(child._.parts[j + 1]);
138
+ strings.push(__classPrivateFieldGet(child, _Sql_state, "f").parts[j + 1]);
117
139
  placeholderIds.push(placeholderId);
118
140
  }
119
141
  // ネストされた Sql の展開が終わった後に、後続の生の文字列を結合します。
@@ -126,53 +148,32 @@ export class Sql {
126
148
  }
127
149
  }
128
150
  this.values = bindings;
129
- // 内部状態を隠蔽し、不必要なプロパティーの露出を防ぎます。
130
- Object.defineProperty(this, "_", {
131
- value: {
132
- parts: strings,
133
- phIds: placeholderIds,
134
- idx2slot,
135
- slot2idx,
136
- },
137
- });
151
+ __classPrivateFieldSet(this, _Sql_state, {
152
+ parts: strings,
153
+ phIds: placeholderIds,
154
+ idx2slot,
155
+ slot2idx,
156
+ }, "f");
138
157
  }
139
158
  /**
140
159
  * スロットを値で埋めます。
141
160
  *
161
+ * @template TSlots 指定されたスロットのマップ型です。
142
162
  * @param slots スロットの値です。
143
163
  * @returns スロットが埋められた新しい Sql インスタンスです。
144
164
  */
145
165
  fill(slots) {
146
- if (isPlainObject(slots)) {
147
- slots = Object.entries(slots);
148
- }
149
- else {
150
- // 一旦 Map のインスタンスにすることで、重複する名前またはスロットを 1 つに絞ります。
151
- slots = new Map(slots);
152
- }
153
- const values = [...this.values];
154
- for (const [target, value] of new Map(slots)) {
155
- let idxes;
156
- if (typeof target === "string") {
157
- idxes = this._.slot2idx.get(target);
158
- }
159
- else {
160
- for (const [idx, slot] of this._.idx2slot) {
161
- if (slot === target) {
162
- idxes || (idxes = new Set());
163
- idxes.add(idx);
164
- }
165
- }
166
- }
167
- if (idxes === undefined) {
168
- // スロットが見つからない場合は無視します。
169
- continue;
170
- }
171
- for (const idx of idxes) {
172
- values[idx] = value;
173
- }
174
- }
175
- return new Sql(this._.parts, values);
166
+ return __classPrivateFieldGet(this, _Sql_instances, "m", _Sql_fill).call(this, slots, false);
167
+ }
168
+ /**
169
+ * すべてのスロットを値で埋めます。
170
+ *
171
+ * @template TSlots 全てのスロットをカバーするマップ型です。
172
+ * @param slots スロットの値です。
173
+ * @returns スロットが埋められた新しい Sql インスタンスです。
174
+ */
175
+ fillAll(slots) {
176
+ return __classPrivateFieldGet(this, _Sql_instances, "m", _Sql_fill).call(this, slots, true);
176
177
  }
177
178
  /**
178
179
  * オブジェクトを JSON 形式に変換可能な形式で返します。
@@ -194,6 +195,58 @@ export class Sql {
194
195
  return this.text;
195
196
  }
196
197
  }
198
+ _a = Sql, _Sql_state = new WeakMap(), _Sql_instances = new WeakSet(), _Sql_fill = function _Sql_fill(slots, all) {
199
+ if (isPlainObject(slots)) {
200
+ slots = Object.entries(slots);
201
+ }
202
+ else {
203
+ // 一旦 Map のインスタンスにすることで、重複する名前またはスロットを 1 つに絞ります。
204
+ slots = new Map(slots);
205
+ }
206
+ const { parts, idx2slot, slot2idx } = __classPrivateFieldGet(this, _Sql_state, "f");
207
+ const filled = new Set();
208
+ const values = this.values.slice();
209
+ for (const [target, value] of new Map(slots)) {
210
+ let idxes;
211
+ if (typeof target === "string") {
212
+ idxes = slot2idx.get(target);
213
+ }
214
+ else {
215
+ // インスタンスが直接指定された場合、全インデックスから一致するものを探します。
216
+ for (const [idx, slot] of idx2slot) {
217
+ if (slot === target) {
218
+ idxes || (idxes = new Set());
219
+ idxes.add(idx);
220
+ }
221
+ }
222
+ }
223
+ if (idxes === undefined) {
224
+ // スロットが見つからない場合は無視します。
225
+ continue;
226
+ }
227
+ // 該当するすべてのプレースホルダーインデックスを新しい値で更新します。
228
+ for (const idx of idxes) {
229
+ values[idx] = value;
230
+ filled.add(idx);
231
+ }
232
+ }
233
+ // 全て埋める必要がある場合、未解決のスロットが残っていないか検証します。
234
+ if (all) {
235
+ for (let idx = 0; idx < values.length; idx++) {
236
+ if (idx2slot.has(idx) && !filled.has(idx)) {
237
+ const missingSlots = new Set();
238
+ missingSlots.add(idx2slot.get(idx).name);
239
+ for (; idx < values.length; idx++) {
240
+ if (idx2slot.has(idx)) {
241
+ missingSlots.add(idx2slot.get(idx).name);
242
+ }
243
+ }
244
+ throw new Error(`Not all slots are filled. Missing: ${[...missingSlots].join(", ")}`);
245
+ }
246
+ }
247
+ }
248
+ return new _a(parts, values);
249
+ };
197
250
  /**
198
251
  * 生の文字列を SQL 断片として扱います。
199
252
  *
package/dist/src/sql.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { join, raw, Sql, ident, literal, Slot } from "./core.js";
1
+ import { join, raw, Sql, ident, literal, Slot, RawValue } from "./core.js";
2
2
  declare namespace sql {
3
3
  /**
4
4
  * SQL クエリーの構築に使用できる生の値、または別の Sql インスタンスを表す型です。
@@ -12,24 +12,32 @@ declare namespace sql {
12
12
  * スロットを表すクラスです。
13
13
  *
14
14
  * スロットは後から値を注入可能なプレースホルダーです。
15
+ *
16
+ * @template TName スロットの名前となる文字列リテラル型です。
17
+ * @template TValue スロットに許容される値の型です。
15
18
  */
16
- type Slot = import("./core.js").Slot;
19
+ type Slot<TName extends string = string, TValue extends RawValue = RawValue> = import("./core.js").Slot<TName, TValue>;
17
20
  /**
18
21
  * 安全な SQL クエリーを構築するためのクラス型です。
22
+ *
23
+ * @template TRawBindings クエリーに渡される生の値のタプル型です。
19
24
  */
20
- type Sql = import("./core.js").Sql;
25
+ type Sql<TRawBindings extends readonly RawValue[] = readonly RawValue[]> = import("./core.js").Sql<TRawBindings>;
21
26
  }
22
27
  /**
23
28
  * 新しい Slot インスタンスを作成します。
24
29
  *
30
+ * @template TName スロットの名前となる文字列リテラル型です。
31
+ * @template TValue スロットに許容される値の型です。
25
32
  * @param name スロット名です。
26
33
  * @param defaultValue デフォルト値です。
27
34
  * @returns 作成された新しい Slot インスタンスです。
28
35
  */
29
- declare function slot(name: string, defaultValue?: sql.Value): sql.Slot;
36
+ declare function slot<const TName extends string, TValue extends RawValue = RawValue>(name: TName, defaultValue?: TValue): sql.Slot<TName, TValue>;
30
37
  /**
31
38
  * テンプレートリテラルを使用して SQL クエリーを安全に構築するためのタグ関数です。
32
39
  *
40
+ * @template TRawBindings クエリーに渡される生の値のタプル型です。
33
41
  * @param strings テンプレートリテラルの静的な文字列部分の配列です。
34
42
  * @param bindings テンプレートリテラルに埋め込まれた動的な値の配列です。
35
43
  * @returns パラメーター化された SQL 情報を保持する Sql インスタンスを返します。
@@ -38,7 +46,7 @@ declare function slot(name: string, defaultValue?: sql.Value): sql.Slot;
38
46
  * const query = sql`SELECT * FROM users WHERE id = ${1}`;
39
47
  * ```
40
48
  */
41
- declare const sql: ((strings: TemplateStringsArray, ...bindings: readonly sql.RawValue[]) => sql.Sql) & {
49
+ declare const sql: (<const TRawBindings extends readonly RawValue[]>(strings: TemplateStringsArray, ...bindings: TRawBindings) => sql.Sql<TRawBindings>) & {
42
50
  /**
43
51
  * Sql クラスです。
44
52
  */
@@ -64,7 +72,7 @@ declare const sql: ((strings: TemplateStringsArray, ...bindings: readonly sql.Ra
64
72
  /**
65
73
  * 空の SQL クエリーを表す定数です。
66
74
  */
67
- readonly empty: Sql;
75
+ readonly empty: Sql<readonly unknown[]>;
68
76
  /**
69
77
  * 識別子(テーブル名等)を安全にエスケープするための関数です。
70
78
  */
@@ -1 +1 @@
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,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAExE,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;;;;OAIG;IACH,KAAY,IAAI,GAAG,OAAO,WAAW,EAAE,IAAI,CAAC;IAE5C;;OAEG;IACH,KAAY,GAAG,GAAG,OAAO,WAAW,EAAE,GAAG,CAAC;CAC3C;AAED;;;;;;GAMG;AACH,iBAAS,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,GAAG,CAAC,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC;AAMhE;;;;;;;;;;GAUG;AACH,QAAA,MAAM,GAAG,aACe,oBAAoB,eAAe,SAAS,GAAG,CAAC,QAAQ,EAAE,KAAG,GAAG,CAAC,GAAG;IAIxF;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;;;OAIG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;CAGN,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,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAElF,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;;;;;;;OAOG;IACH,KAAY,IAAI,CACd,KAAK,SAAS,MAAM,GAAG,MAAM,EAC7B,MAAM,SAAS,QAAQ,GAAG,QAAQ,IAChC,OAAO,WAAW,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAE5C;;;;OAIG;IACH,KAAY,GAAG,CAAC,YAAY,SAAS,SAAS,QAAQ,EAAE,GAAG,SAAS,QAAQ,EAAE,IAC5E,OAAO,WAAW,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC;CACzC;AAED;;;;;;;;GAQG;AACH,iBAAS,IAAI,CAAC,KAAK,CAAC,KAAK,SAAS,MAAM,EAAE,MAAM,SAAS,QAAQ,GAAG,QAAQ,EAC1E,IAAI,EAAE,KAAK,EACX,YAAY,CAAC,EAAE,MAAM,GACpB,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AAM3B;;;;;;;;;;;GAWG;AACH,QAAA,MAAM,GAAG,UACY,YAAY,SAAS,SAAS,QAAQ,EAAE,WAChD,oBAAoB,eAChB,YAAY,KACxB,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC;IAItB;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;;;OAIG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;CAGN,CAAC;AAEF,OAAO,EAAE,GAAG,EAAE,CAAC"}
package/dist/src/sql.js CHANGED
@@ -5,6 +5,7 @@ function slot(...args) {
5
5
  /**
6
6
  * テンプレートリテラルを使用して SQL クエリーを安全に構築するためのタグ関数です。
7
7
  *
8
+ * @template TRawBindings クエリーに渡される生の値のタプル型です。
8
9
  * @param strings テンプレートリテラルの静的な文字列部分の配列です。
9
10
  * @param bindings テンプレートリテラルに埋め込まれた動的な値の配列です。
10
11
  * @returns パラメーター化された SQL 情報を保持する Sql インスタンスを返します。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgsql-template-tag",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "",
5
5
  "homepage": "https://github.com/tai-kun/pgsql-template-tag",
6
6
  "license": "MIT",
@@ -26,7 +26,8 @@
26
26
  }
27
27
  },
28
28
  "dependencies": {
29
- "es-toolkit": "^1.46.1"
29
+ "es-toolkit": "^1.46.1",
30
+ "type-fest": "^5.6.0"
30
31
  },
31
32
  "devDependencies": {
32
33
  "@tsconfig/node24": "^24.0.4",
package/src/core.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { isPlainObject } from "es-toolkit/predicate";
2
+ import type { UnionToTuple } from "type-fest";
2
3
 
3
4
  /**
4
5
  * SQL クエリー内で使用される値の型定義です。
@@ -11,21 +12,44 @@ export type Value = unknown;
11
12
  // oxlint-disable-next-line typescript/no-redundant-type-constituents
12
13
  export type RawValue = Value | Slot | Sql;
13
14
 
15
+ /**
16
+ * Slot クラスを一意に識別するためのシンボルです。
17
+ */
18
+ declare const SLOT_SYMBOL: unique symbol;
19
+
20
+ /**
21
+ * Slot クラスの基底となる型定義です。
22
+ */
23
+ const SlotTypes = class {} as {
24
+ new (): {
25
+ /**
26
+ * このプロパティーは、TypeScript の `extends Slot` で `Slot` インスタンスのみに一致させるためにあります。そのため、`Slot` と同じプロパティーを持つオブジェクトに対して一致することはありません。
27
+ */
28
+ readonly ["~kind"]: typeof SLOT_SYMBOL;
29
+ };
30
+ };
31
+
14
32
  /**
15
33
  * スロットを表すクラスです。
16
34
  *
17
35
  * スロットは後から値を注入可能なプレースホルダーです。
36
+ *
37
+ * @template TName スロットの名前となる文字列リテラル型です。
38
+ * @template TValue スロットに許容される値の型です。
18
39
  */
19
- export class Slot {
40
+ export class Slot<
41
+ const TName extends string = string,
42
+ TValue extends RawValue = RawValue,
43
+ > extends SlotTypes {
20
44
  /**
21
45
  * スロット名です。
22
46
  */
23
- public readonly name: string;
47
+ public readonly name: TName;
24
48
 
25
49
  /**
26
50
  * デフォルト値です。
27
51
  */
28
- public readonly defaultValue: Value;
52
+ public readonly defaultValue: TValue;
29
53
 
30
54
  /**
31
55
  * 新しい Slot インスタンスを初期化します。
@@ -33,16 +57,121 @@ export class Slot {
33
57
  * @param name スロット名です。
34
58
  * @param defaultValue デフォルト値です。
35
59
  */
36
- public constructor(name: string, defaultValue?: Value) {
60
+ public constructor(
61
+ ...args: null extends TValue
62
+ ? [name: TName, defaultValue?: TValue]
63
+ : [name: TName, defaultValue: TValue]
64
+ );
65
+
66
+ public constructor(name: TName, defaultValue?: RawValue) {
67
+ super();
68
+
37
69
  if (arguments.length < 2) {
38
70
  defaultValue = null;
39
71
  }
40
72
 
41
73
  this.name = name;
42
- this.defaultValue = defaultValue;
74
+ this.defaultValue = defaultValue as TValue;
43
75
  }
44
76
  }
45
77
 
78
+ /**
79
+ * オブジェクトのバリューの型を抽出するヘルパー型です。
80
+ *
81
+ * @template T 対象となるオブジェクト型です。
82
+ */
83
+ type $ValueOf<T> = T[keyof T];
84
+
85
+ /**
86
+ * スロットの配列から、再帰的に値をマージして型を決定します。
87
+ *
88
+ * @template TSlots スロットのタプル型です。
89
+ */
90
+ type $MergeSlotValue<TSlots> = TSlots extends [
91
+ Slot<string, infer TValue>,
92
+ infer TSlot,
93
+ ...infer TOtherSlots,
94
+ ]
95
+ ? TValue & $MergeSlotValue<[TSlot, ...TOtherSlots]>
96
+ : TSlots extends [Slot<string, infer TValue>]
97
+ ? TValue
98
+ : never;
99
+
100
+ /**
101
+ * RawValue の配列からスロット情報を抽出し、名前ごとのマップ型に変換します。
102
+ *
103
+ * @template TValues RawValue の読み取り専用配列型です。
104
+ */
105
+ type $MapSlotValue<TValues extends readonly RawValue[]> =
106
+ TValues extends readonly (infer TSlot extends Slot)[]
107
+ ? {
108
+ // `Slot<"id", string | number> | Slot<"id", string>` の場合、`(string | number) & (string)` となるように、各スロットの積集合をとります。
109
+ [TName in TSlot["name"]]: $MergeSlotValue<UnionToTuple<Extract<TSlot, Slot<TName>>>>;
110
+ }
111
+ : {};
112
+
113
+ /**
114
+ * 指定された値のリストに対して、スロットを実際の内容で置き換えた型を生成します。
115
+ *
116
+ * @template TValues 置き換え対象の配列型です。
117
+ * @template TSlots スロット名と値のマップ型です。
118
+ */
119
+ type $FillSlots<TValues, TSlots> = TValues extends [infer TValue, ...infer TOtherValues]
120
+ ? [
121
+ TValue extends Slot<infer TName extends Extract<keyof TSlots, string>, infer TSlotValue>
122
+ ? TSlotValue extends TSlots[TName]
123
+ ? TSlotValue
124
+ : TValue
125
+ : TValue,
126
+ ...$FillSlots<TOtherValues, TSlots>,
127
+ ]
128
+ : [];
129
+
130
+ /**
131
+ * スロットを埋めるための部分的な引数型を定義します。
132
+ *
133
+ * @template TSlots スロット名と値のマップ型です。
134
+ */
135
+ type _FillSlots<TSlots> =
136
+ | {
137
+ readonly [TName in Extract<keyof TSlots, string>]?: TSlots[TName];
138
+ }
139
+ | Iterable<
140
+ readonly [
141
+ name: $ValueOf<{
142
+ [TName in Extract<keyof TSlots, string>]: TName | Slot<TName, TSlots[TName]>;
143
+ }>,
144
+ value: $ValueOf<TSlots>,
145
+ ]
146
+ >;
147
+
148
+ /**
149
+ * すべてのスロットを埋めるために必要な引数型を定義します。
150
+ *
151
+ * @template TSlots スロット名と値のマップ型です。
152
+ */
153
+ type _FillAllSlots<TSlots> = {
154
+ readonly [TName in Extract<keyof TSlots, string>]: TSlots[TName];
155
+ };
156
+
157
+ /**
158
+ * スロットの部分的な補完に使用する外部向けの型定義です。
159
+ *
160
+ * @template TValues RawValue の配列です。
161
+ */
162
+ export type FillSlots<TValues extends readonly RawValue[] = readonly RawValue[]> = _FillSlots<
163
+ $MapSlotValue<TValues>
164
+ >;
165
+
166
+ /**
167
+ * すべてのスロットの強制的な補完に使用する外部向けの型定義です。
168
+ *
169
+ * @template TValues RawValue の配列です。
170
+ */
171
+ export type FillAllSlots<TValues extends readonly RawValue[] = readonly RawValue[]> = _FillAllSlots<
172
+ $MapSlotValue<TValues>
173
+ >;
174
+
46
175
  /**
47
176
  * Sql クラスの内部状態を管理するためのプライベートな型定義です。
48
177
  */
@@ -77,8 +206,10 @@ type PrivateState = {
77
206
  * 安全な SQL クエリーを構築するためのクラスです。
78
207
  *
79
208
  * プレースホルダーを使用したパラメーター化クエリーを生成します。
209
+ *
210
+ * @template TRawBindings クエリーに渡される生の値のタプル型です。
80
211
  */
81
- export class Sql {
212
+ export class Sql<const TRawBindings extends readonly RawValue[] = readonly RawValue[]> {
82
213
  /**
83
214
  * 構築された SQL クエリーテキストを取得します。
84
215
  *
@@ -88,19 +219,19 @@ export class Sql {
88
219
  */
89
220
  public get text(): string {
90
221
  // キャッシュが存在しない場合にのみ、文字列を構築します。
91
- if (this._.text === undefined) {
222
+ if (this.#state.text === undefined) {
92
223
  let i = 0,
93
- text = this._.parts[0];
224
+ text = this.#state.parts[0];
94
225
 
95
226
  // 文字列の断片とプレースホルダー($1, $2...)を交互に結合します。
96
- for (; i < this._.phIds.length; i++) {
97
- text += "$" + this._.phIds[i] + this._.parts[i + 1];
227
+ for (; i < this.#state.phIds.length; i++) {
228
+ text += "$" + this.#state.phIds[i] + this.#state.parts[i + 1];
98
229
  }
99
230
 
100
- this._.text = text;
231
+ this.#state.text = text;
101
232
  }
102
233
 
103
- return this._.text;
234
+ return this.#state.text;
104
235
  }
105
236
 
106
237
  /**
@@ -111,7 +242,7 @@ export class Sql {
111
242
  /**
112
243
  * 内部状態を保持するためのプロパティーです。
113
244
  */
114
- private readonly _!: PrivateState;
245
+ readonly #state: PrivateState;
115
246
 
116
247
  /**
117
248
  * 新しい Sql インスタンスを初期化します。
@@ -119,7 +250,7 @@ export class Sql {
119
250
  * @param rawStrings SQL の断片となる文字列の配列です。
120
251
  * @param rawBindings 文字列の間に挿入される値の配列です。
121
252
  */
122
- public constructor(rawStrings: readonly string[], rawBindings: readonly RawValue[]) {
253
+ public constructor(rawStrings: readonly string[], rawBindings: TRawBindings) {
123
254
  if (rawStrings.length === 0) {
124
255
  throw new TypeError("Expected at least 1 string");
125
256
  }
@@ -187,19 +318,19 @@ export class Sql {
187
318
  // バインディング値が Sql インスタンス(ネストされたクエリー)の場合の処理です。
188
319
  if (child instanceof Sql) {
189
320
  // 現在の最後の文字列断片に、ネストされた Sql の最初の断片を結合します。
190
- strings[strings.length - 1] += child._.parts[0];
321
+ strings[strings.length - 1] += child.#state.parts[0];
191
322
 
192
323
  // ネストされた Sql のプレースホルダーと値を再マッピングします。
193
- for (let j = 0; j < child._.phIds.length; j++) {
194
- const childPlaceholderId = child._.phIds[j]!;
324
+ for (let j = 0; j < child.#state.phIds.length; j++) {
325
+ const childPlaceholderId = child.#state.phIds[j]!;
195
326
  const valueIndex = childPlaceholderId - 1;
196
327
  const value = child.values[valueIndex]!;
197
328
 
198
- const slot = child._.idx2slot.get(valueIndex);
329
+ const slot = child.#state.idx2slot.get(valueIndex);
199
330
 
200
331
  const placeholderId = slot !== undefined ? registerSlot(slot) : registerValue(value);
201
332
 
202
- strings.push(child._.parts[j + 1]!);
333
+ strings.push(child.#state.parts[j + 1]!);
203
334
  placeholderIds.push(placeholderId);
204
335
  }
205
336
 
@@ -215,26 +346,22 @@ export class Sql {
215
346
 
216
347
  this.values = bindings;
217
348
 
218
- // 内部状態を隠蔽し、不必要なプロパティーの露出を防ぎます。
219
- Object.defineProperty(this, "_", {
220
- value: {
221
- parts: strings,
222
- phIds: placeholderIds,
223
- idx2slot,
224
- slot2idx,
225
- } satisfies PrivateState,
226
- });
349
+ this.#state = {
350
+ parts: strings,
351
+ phIds: placeholderIds,
352
+ idx2slot,
353
+ slot2idx,
354
+ };
227
355
  }
228
356
 
229
357
  /**
230
- * スロットを値で埋めます。
358
+ * スロットを値で埋める内部メソッドです。
231
359
  *
232
- * @param slots スロットの値です。
233
- * @returns スロットが埋められた新しい Sql インスタンスです。
360
+ * @param slots スロットのマップまたはエントリーの配列です。
361
+ * @param all 全てのスロットが埋まっているかチェックするかどうかです。
362
+ * @returns 新しい Sql インスタンスを返します。
234
363
  */
235
- public fill(
236
- slots: { readonly [name: string]: Value } | Iterable<readonly [string | Slot, Value]>,
237
- ): Sql {
364
+ #fill(slots: FillSlots<Slot[]>, all: boolean): Sql {
238
365
  if (isPlainObject(slots)) {
239
366
  slots = Object.entries(slots);
240
367
  } else {
@@ -242,13 +369,16 @@ export class Sql {
242
369
  slots = new Map(slots);
243
370
  }
244
371
 
245
- const values: Value[] = [...this.values];
372
+ const { parts, idx2slot, slot2idx } = this.#state;
373
+ const filled = new Set<number>();
374
+ const values = this.values.slice();
246
375
  for (const [target, value] of new Map(slots)) {
247
376
  let idxes: ReadonlySet<number> | undefined;
248
377
  if (typeof target === "string") {
249
- idxes = this._.slot2idx.get(target);
378
+ idxes = slot2idx.get(target);
250
379
  } else {
251
- for (const [idx, slot] of this._.idx2slot) {
380
+ // インスタンスが直接指定された場合、全インデックスから一致するものを探します。
381
+ for (const [idx, slot] of idx2slot) {
252
382
  if (slot === target) {
253
383
  idxes ||= new Set();
254
384
  (idxes as Set<number>).add(idx);
@@ -261,12 +391,57 @@ export class Sql {
261
391
  continue;
262
392
  }
263
393
 
394
+ // 該当するすべてのプレースホルダーインデックスを新しい値で更新します。
264
395
  for (const idx of idxes) {
265
396
  values[idx] = value;
397
+ filled.add(idx);
398
+ }
399
+ }
400
+
401
+ // 全て埋める必要がある場合、未解決のスロットが残っていないか検証します。
402
+ if (all) {
403
+ for (let idx = 0; idx < values.length; idx++) {
404
+ if (idx2slot.has(idx) && !filled.has(idx)) {
405
+ const missingSlots = new Set<string>();
406
+ missingSlots.add(idx2slot.get(idx)!.name);
407
+ for (; idx < values.length; idx++) {
408
+ if (idx2slot.has(idx)) {
409
+ missingSlots.add(idx2slot.get(idx)!.name);
410
+ }
411
+ }
412
+
413
+ throw new Error(`Not all slots are filled. Missing: ${[...missingSlots].join(", ")}`);
414
+ }
266
415
  }
267
416
  }
268
417
 
269
- return new Sql(this._.parts, values);
418
+ return new Sql(parts, values);
419
+ }
420
+
421
+ /**
422
+ * スロットを値で埋めます。
423
+ *
424
+ * @template TSlots 指定されたスロットのマップ型です。
425
+ * @param slots スロットの値です。
426
+ * @returns スロットが埋められた新しい Sql インスタンスです。
427
+ */
428
+ public fill<TSlots extends FillSlots<TRawBindings>>(
429
+ slots: TSlots,
430
+ ): Sql<$FillSlots<TRawBindings, TSlots>> {
431
+ return this.#fill(slots, false);
432
+ }
433
+
434
+ /**
435
+ * すべてのスロットを値で埋めます。
436
+ *
437
+ * @template TSlots 全てのスロットをカバーするマップ型です。
438
+ * @param slots スロットの値です。
439
+ * @returns スロットが埋められた新しい Sql インスタンスです。
440
+ */
441
+ public fillAll<TSlots extends FillAllSlots<TRawBindings>>(
442
+ slots: TSlots,
443
+ ): Sql<$FillSlots<TRawBindings, TSlots>> {
444
+ return this.#fill(slots, true);
270
445
  }
271
446
 
272
447
  /**
package/src/sql.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { empty, join, raw, Sql, ident, literal, Slot } from "./core.js";
1
+ import { empty, join, raw, Sql, ident, literal, Slot, RawValue } from "./core.js";
2
2
 
3
3
  namespace sql {
4
4
  /**
@@ -15,23 +15,37 @@ namespace sql {
15
15
  * スロットを表すクラスです。
16
16
  *
17
17
  * スロットは後から値を注入可能なプレースホルダーです。
18
+ *
19
+ * @template TName スロットの名前となる文字列リテラル型です。
20
+ * @template TValue スロットに許容される値の型です。
18
21
  */
19
- export type Slot = import("./core.js").Slot;
22
+ export type Slot<
23
+ TName extends string = string,
24
+ TValue extends RawValue = RawValue,
25
+ > = import("./core.js").Slot<TName, TValue>;
20
26
 
21
27
  /**
22
28
  * 安全な SQL クエリーを構築するためのクラス型です。
29
+ *
30
+ * @template TRawBindings クエリーに渡される生の値のタプル型です。
23
31
  */
24
- export type Sql = import("./core.js").Sql;
32
+ export type Sql<TRawBindings extends readonly RawValue[] = readonly RawValue[]> =
33
+ import("./core.js").Sql<TRawBindings>;
25
34
  }
26
35
 
27
36
  /**
28
37
  * 新しい Slot インスタンスを作成します。
29
38
  *
39
+ * @template TName スロットの名前となる文字列リテラル型です。
40
+ * @template TValue スロットに許容される値の型です。
30
41
  * @param name スロット名です。
31
42
  * @param defaultValue デフォルト値です。
32
43
  * @returns 作成された新しい Slot インスタンスです。
33
44
  */
34
- function slot(name: string, defaultValue?: sql.Value): sql.Slot;
45
+ function slot<const TName extends string, TValue extends RawValue = RawValue>(
46
+ name: TName,
47
+ defaultValue?: TValue,
48
+ ): sql.Slot<TName, TValue>;
35
49
 
36
50
  function slot(...args: [any]): sql.Slot {
37
51
  return new sql.Slot(...args);
@@ -40,6 +54,7 @@ function slot(...args: [any]): sql.Slot {
40
54
  /**
41
55
  * テンプレートリテラルを使用して SQL クエリーを安全に構築するためのタグ関数です。
42
56
  *
57
+ * @template TRawBindings クエリーに渡される生の値のタプル型です。
43
58
  * @param strings テンプレートリテラルの静的な文字列部分の配列です。
44
59
  * @param bindings テンプレートリテラルに埋め込まれた動的な値の配列です。
45
60
  * @returns パラメーター化された SQL 情報を保持する Sql インスタンスを返します。
@@ -49,7 +64,10 @@ function slot(...args: [any]): sql.Slot {
49
64
  * ```
50
65
  */
51
66
  const sql = /*#__PURE__*/ Object.assign(
52
- function sql(strings: TemplateStringsArray, ...bindings: readonly sql.RawValue[]): sql.Sql {
67
+ function sql<const TRawBindings extends readonly RawValue[]>(
68
+ strings: TemplateStringsArray,
69
+ ...bindings: TRawBindings
70
+ ): sql.Sql<TRawBindings> {
53
71
  return new Sql(strings, bindings);
54
72
  },
55
73
  {