@slonik/sql-tag 40.2.2

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.
Files changed (233) hide show
  1. package/README.md +2 -0
  2. package/dist/Logger.d.ts +2 -0
  3. package/dist/Logger.d.ts.map +1 -0
  4. package/dist/Logger.js +8 -0
  5. package/dist/Logger.js.map +1 -0
  6. package/dist/factories/createPrimitiveValueExpressions.d.ts +3 -0
  7. package/dist/factories/createPrimitiveValueExpressions.d.ts.map +1 -0
  8. package/dist/factories/createPrimitiveValueExpressions.js +33 -0
  9. package/dist/factories/createPrimitiveValueExpressions.js.map +1 -0
  10. package/dist/factories/createSqlTag.d.ts +38 -0
  11. package/dist/factories/createSqlTag.d.ts.map +1 -0
  12. package/dist/factories/createSqlTag.js +170 -0
  13. package/dist/factories/createSqlTag.js.map +1 -0
  14. package/dist/factories/createSqlTag.test/array.test.d.ts +2 -0
  15. package/dist/factories/createSqlTag.test/array.test.d.ts.map +1 -0
  16. package/dist/factories/createSqlTag.test/array.test.js +76 -0
  17. package/dist/factories/createSqlTag.test/array.test.js.map +1 -0
  18. package/dist/factories/createSqlTag.test/date.test.d.ts +2 -0
  19. package/dist/factories/createSqlTag.test/date.test.d.ts.map +1 -0
  20. package/dist/factories/createSqlTag.test/date.test.js +25 -0
  21. package/dist/factories/createSqlTag.test/date.test.js.map +1 -0
  22. package/dist/factories/createSqlTag.test/identifier.test.d.ts +2 -0
  23. package/dist/factories/createSqlTag.test/identifier.test.d.ts.map +1 -0
  24. package/dist/factories/createSqlTag.test/identifier.test.js +38 -0
  25. package/dist/factories/createSqlTag.test/identifier.test.js.map +1 -0
  26. package/dist/factories/createSqlTag.test/interval.test.d.ts +2 -0
  27. package/dist/factories/createSqlTag.test/interval.test.d.ts.map +1 -0
  28. package/dist/factories/createSqlTag.test/interval.test.js +43 -0
  29. package/dist/factories/createSqlTag.test/interval.test.js.map +1 -0
  30. package/dist/factories/createSqlTag.test/join.test.d.ts +2 -0
  31. package/dist/factories/createSqlTag.test/join.test.d.ts.map +1 -0
  32. package/dist/factories/createSqlTag.test/join.test.js +87 -0
  33. package/dist/factories/createSqlTag.test/join.test.js.map +1 -0
  34. package/dist/factories/createSqlTag.test/json.test.d.ts +2 -0
  35. package/dist/factories/createSqlTag.test/json.test.d.ts.map +1 -0
  36. package/dist/factories/createSqlTag.test/json.test.js +88 -0
  37. package/dist/factories/createSqlTag.test/json.test.js.map +1 -0
  38. package/dist/factories/createSqlTag.test/jsonb.test.d.ts +2 -0
  39. package/dist/factories/createSqlTag.test/jsonb.test.d.ts.map +1 -0
  40. package/dist/factories/createSqlTag.test/jsonb.test.js +88 -0
  41. package/dist/factories/createSqlTag.test/jsonb.test.js.map +1 -0
  42. package/dist/factories/createSqlTag.test/literalValue.test.d.ts +2 -0
  43. package/dist/factories/createSqlTag.test/literalValue.test.d.ts.map +1 -0
  44. package/dist/factories/createSqlTag.test/literalValue.test.js +18 -0
  45. package/dist/factories/createSqlTag.test/literalValue.test.js.map +1 -0
  46. package/dist/factories/createSqlTag.test/sql.test.d.ts +2 -0
  47. package/dist/factories/createSqlTag.test/sql.test.d.ts.map +1 -0
  48. package/dist/factories/createSqlTag.test/sql.test.js +138 -0
  49. package/dist/factories/createSqlTag.test/sql.test.js.map +1 -0
  50. package/dist/factories/createSqlTag.test/timestamp.test.d.ts +2 -0
  51. package/dist/factories/createSqlTag.test/timestamp.test.d.ts.map +1 -0
  52. package/dist/factories/createSqlTag.test/timestamp.test.js +25 -0
  53. package/dist/factories/createSqlTag.test/timestamp.test.js.map +1 -0
  54. package/dist/factories/createSqlTag.test/type.test.d.ts +2 -0
  55. package/dist/factories/createSqlTag.test/type.test.d.ts.map +1 -0
  56. package/dist/factories/createSqlTag.test/type.test.js +19 -0
  57. package/dist/factories/createSqlTag.test/type.test.js.map +1 -0
  58. package/dist/factories/createSqlTag.test/typeAlias.test.d.ts +2 -0
  59. package/dist/factories/createSqlTag.test/typeAlias.test.d.ts.map +1 -0
  60. package/dist/factories/createSqlTag.test/typeAlias.test.js +47 -0
  61. package/dist/factories/createSqlTag.test/typeAlias.test.js.map +1 -0
  62. package/dist/factories/createSqlTag.test/unnest.test.d.ts +2 -0
  63. package/dist/factories/createSqlTag.test/unnest.test.d.ts.map +1 -0
  64. package/dist/factories/createSqlTag.test/unnest.test.js +127 -0
  65. package/dist/factories/createSqlTag.test/unnest.test.js.map +1 -0
  66. package/dist/factories/createSqlTokenSqlFragment.d.ts +3 -0
  67. package/dist/factories/createSqlTokenSqlFragment.d.ts.map +1 -0
  68. package/dist/factories/createSqlTokenSqlFragment.js +57 -0
  69. package/dist/factories/createSqlTokenSqlFragment.js.map +1 -0
  70. package/dist/index.d.ts +3 -0
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.js +6 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/regexRules/slonikPlaceholderRegexRule.d.ts +2 -0
  75. package/dist/regexRules/slonikPlaceholderRegexRule.d.ts.map +1 -0
  76. package/dist/regexRules/slonikPlaceholderRegexRule.js +5 -0
  77. package/dist/regexRules/slonikPlaceholderRegexRule.js.map +1 -0
  78. package/dist/sqlFragmentFactories/createArraySqlFragment.d.ts +3 -0
  79. package/dist/sqlFragmentFactories/createArraySqlFragment.d.ts.map +1 -0
  80. package/dist/sqlFragmentFactories/createArraySqlFragment.js +48 -0
  81. package/dist/sqlFragmentFactories/createArraySqlFragment.js.map +1 -0
  82. package/dist/sqlFragmentFactories/createBinarySqlFragment.d.ts +3 -0
  83. package/dist/sqlFragmentFactories/createBinarySqlFragment.d.ts.map +1 -0
  84. package/dist/sqlFragmentFactories/createBinarySqlFragment.js +16 -0
  85. package/dist/sqlFragmentFactories/createBinarySqlFragment.js.map +1 -0
  86. package/dist/sqlFragmentFactories/createDateSqlFragment.d.ts +3 -0
  87. package/dist/sqlFragmentFactories/createDateSqlFragment.d.ts.map +1 -0
  88. package/dist/sqlFragmentFactories/createDateSqlFragment.js +16 -0
  89. package/dist/sqlFragmentFactories/createDateSqlFragment.js.map +1 -0
  90. package/dist/sqlFragmentFactories/createFragmentSqlFragment.d.ts +3 -0
  91. package/dist/sqlFragmentFactories/createFragmentSqlFragment.d.ts.map +1 -0
  92. package/dist/sqlFragmentFactories/createFragmentSqlFragment.js +34 -0
  93. package/dist/sqlFragmentFactories/createFragmentSqlFragment.js.map +1 -0
  94. package/dist/sqlFragmentFactories/createIdentifierSqlFragment.d.ts +3 -0
  95. package/dist/sqlFragmentFactories/createIdentifierSqlFragment.d.ts.map +1 -0
  96. package/dist/sqlFragmentFactories/createIdentifierSqlFragment.js +21 -0
  97. package/dist/sqlFragmentFactories/createIdentifierSqlFragment.js.map +1 -0
  98. package/dist/sqlFragmentFactories/createIntervalSqlFragment.d.ts +3 -0
  99. package/dist/sqlFragmentFactories/createIntervalSqlFragment.d.ts.map +1 -0
  100. package/dist/sqlFragmentFactories/createIntervalSqlFragment.js +57 -0
  101. package/dist/sqlFragmentFactories/createIntervalSqlFragment.js.map +1 -0
  102. package/dist/sqlFragmentFactories/createJsonSqlFragment.d.ts +3 -0
  103. package/dist/sqlFragmentFactories/createJsonSqlFragment.d.ts.map +1 -0
  104. package/dist/sqlFragmentFactories/createJsonSqlFragment.js +49 -0
  105. package/dist/sqlFragmentFactories/createJsonSqlFragment.js.map +1 -0
  106. package/dist/sqlFragmentFactories/createListSqlFragment.d.ts +3 -0
  107. package/dist/sqlFragmentFactories/createListSqlFragment.d.ts.map +1 -0
  108. package/dist/sqlFragmentFactories/createListSqlFragment.js +38 -0
  109. package/dist/sqlFragmentFactories/createListSqlFragment.js.map +1 -0
  110. package/dist/sqlFragmentFactories/createQuerySqlFragment.d.ts +3 -0
  111. package/dist/sqlFragmentFactories/createQuerySqlFragment.d.ts.map +1 -0
  112. package/dist/sqlFragmentFactories/createQuerySqlFragment.js +34 -0
  113. package/dist/sqlFragmentFactories/createQuerySqlFragment.js.map +1 -0
  114. package/dist/sqlFragmentFactories/createTimestampSqlFragment.d.ts +3 -0
  115. package/dist/sqlFragmentFactories/createTimestampSqlFragment.d.ts.map +1 -0
  116. package/dist/sqlFragmentFactories/createTimestampSqlFragment.js +18 -0
  117. package/dist/sqlFragmentFactories/createTimestampSqlFragment.js.map +1 -0
  118. package/dist/sqlFragmentFactories/createUnnestSqlFragment.d.ts +3 -0
  119. package/dist/sqlFragmentFactories/createUnnestSqlFragment.d.ts.map +1 -0
  120. package/dist/sqlFragmentFactories/createUnnestSqlFragment.js +81 -0
  121. package/dist/sqlFragmentFactories/createUnnestSqlFragment.js.map +1 -0
  122. package/dist/tokens.d.ts +14 -0
  123. package/dist/tokens.d.ts.map +1 -0
  124. package/dist/tokens.js +17 -0
  125. package/dist/tokens.js.map +1 -0
  126. package/dist/types.d.ts +99 -0
  127. package/dist/types.d.ts.map +1 -0
  128. package/dist/types.js +3 -0
  129. package/dist/types.js.map +1 -0
  130. package/dist/utilities/countArrayDimensions.d.ts +2 -0
  131. package/dist/utilities/countArrayDimensions.d.ts.map +1 -0
  132. package/dist/utilities/countArrayDimensions.js +14 -0
  133. package/dist/utilities/countArrayDimensions.js.map +1 -0
  134. package/dist/utilities/countArrayDimensions.test.d.ts +2 -0
  135. package/dist/utilities/countArrayDimensions.test.d.ts.map +1 -0
  136. package/dist/utilities/countArrayDimensions.test.js +13 -0
  137. package/dist/utilities/countArrayDimensions.test.js.map +1 -0
  138. package/dist/utilities/escapeIdentifier.d.ts +5 -0
  139. package/dist/utilities/escapeIdentifier.d.ts.map +1 -0
  140. package/dist/utilities/escapeIdentifier.js +12 -0
  141. package/dist/utilities/escapeIdentifier.js.map +1 -0
  142. package/dist/utilities/escapeIdentifier.test.d.ts +2 -0
  143. package/dist/utilities/escapeIdentifier.test.d.ts.map +1 -0
  144. package/dist/utilities/escapeIdentifier.test.js +13 -0
  145. package/dist/utilities/escapeIdentifier.test.js.map +1 -0
  146. package/dist/utilities/escapeLiteralValue.d.ts +5 -0
  147. package/dist/utilities/escapeLiteralValue.d.ts.map +1 -0
  148. package/dist/utilities/escapeLiteralValue.js +29 -0
  149. package/dist/utilities/escapeLiteralValue.js.map +1 -0
  150. package/dist/utilities/escapeLiteralValue.test.d.ts +2 -0
  151. package/dist/utilities/escapeLiteralValue.test.d.ts.map +1 -0
  152. package/dist/utilities/escapeLiteralValue.test.js +14 -0
  153. package/dist/utilities/escapeLiteralValue.test.js.map +1 -0
  154. package/dist/utilities/formatSlonikPlaceholder.d.ts +14 -0
  155. package/dist/utilities/formatSlonikPlaceholder.d.ts.map +1 -0
  156. package/dist/utilities/formatSlonikPlaceholder.js +20 -0
  157. package/dist/utilities/formatSlonikPlaceholder.js.map +1 -0
  158. package/dist/utilities/hasOwnProperty.d.ts +6 -0
  159. package/dist/utilities/hasOwnProperty.d.ts.map +1 -0
  160. package/dist/utilities/hasOwnProperty.js +12 -0
  161. package/dist/utilities/hasOwnProperty.js.map +1 -0
  162. package/dist/utilities/isPlainObject.d.ts +2 -0
  163. package/dist/utilities/isPlainObject.d.ts.map +1 -0
  164. package/dist/utilities/isPlainObject.js +16 -0
  165. package/dist/utilities/isPlainObject.js.map +1 -0
  166. package/dist/utilities/isPrimitiveValueExpression.d.ts +2 -0
  167. package/dist/utilities/isPrimitiveValueExpression.d.ts.map +1 -0
  168. package/dist/utilities/isPrimitiveValueExpression.js +12 -0
  169. package/dist/utilities/isPrimitiveValueExpression.js.map +1 -0
  170. package/dist/utilities/isSqlToken.d.ts +3 -0
  171. package/dist/utilities/isSqlToken.d.ts.map +1 -0
  172. package/dist/utilities/isSqlToken.js +35 -0
  173. package/dist/utilities/isSqlToken.js.map +1 -0
  174. package/dist/utilities/safeStringify.d.ts +2 -0
  175. package/dist/utilities/safeStringify.d.ts.map +1 -0
  176. package/dist/utilities/safeStringify.js +18 -0
  177. package/dist/utilities/safeStringify.js.map +1 -0
  178. package/dist/utilities/stripArrayNotation.d.ts +2 -0
  179. package/dist/utilities/stripArrayNotation.d.ts.map +1 -0
  180. package/dist/utilities/stripArrayNotation.js +12 -0
  181. package/dist/utilities/stripArrayNotation.js.map +1 -0
  182. package/dist/utilities/stripArrayNotation.test.d.ts +2 -0
  183. package/dist/utilities/stripArrayNotation.test.d.ts.map +1 -0
  184. package/dist/utilities/stripArrayNotation.test.js +13 -0
  185. package/dist/utilities/stripArrayNotation.test.js.map +1 -0
  186. package/package.json +88 -0
  187. package/src/Logger.ts +5 -0
  188. package/src/declarations.d.ts +8 -0
  189. package/src/factories/createPrimitiveValueExpressions.ts +43 -0
  190. package/src/factories/createSqlTag.test/array.test.ts +107 -0
  191. package/src/factories/createSqlTag.test/date.test.ts +26 -0
  192. package/src/factories/createSqlTag.test/identifier.test.ts +39 -0
  193. package/src/factories/createSqlTag.test/interval.test.ts +44 -0
  194. package/src/factories/createSqlTag.test/join.test.ts +127 -0
  195. package/src/factories/createSqlTag.test/json.test.ts +106 -0
  196. package/src/factories/createSqlTag.test/jsonb.test.ts +106 -0
  197. package/src/factories/createSqlTag.test/literalValue.test.ts +17 -0
  198. package/src/factories/createSqlTag.test/sql.test.ts +165 -0
  199. package/src/factories/createSqlTag.test/timestamp.test.ts +29 -0
  200. package/src/factories/createSqlTag.test/type.test.ts +17 -0
  201. package/src/factories/createSqlTag.test/typeAlias.test.ts +53 -0
  202. package/src/factories/createSqlTag.test/unnest.test.ts +173 -0
  203. package/src/factories/createSqlTag.ts +256 -0
  204. package/src/factories/createSqlTokenSqlFragment.ts +60 -0
  205. package/src/index.ts +2 -0
  206. package/src/regexRules/slonikPlaceholderRegexRule.ts +1 -0
  207. package/src/sqlFragmentFactories/createArraySqlFragment.ts +67 -0
  208. package/src/sqlFragmentFactories/createBinarySqlFragment.ts +17 -0
  209. package/src/sqlFragmentFactories/createDateSqlFragment.ts +19 -0
  210. package/src/sqlFragmentFactories/createFragmentSqlFragment.ts +48 -0
  211. package/src/sqlFragmentFactories/createIdentifierSqlFragment.ts +24 -0
  212. package/src/sqlFragmentFactories/createIntervalSqlFragment.ts +71 -0
  213. package/src/sqlFragmentFactories/createJsonSqlFragment.ts +66 -0
  214. package/src/sqlFragmentFactories/createListSqlFragment.ts +48 -0
  215. package/src/sqlFragmentFactories/createQuerySqlFragment.ts +48 -0
  216. package/src/sqlFragmentFactories/createTimestampSqlFragment.ts +22 -0
  217. package/src/sqlFragmentFactories/createUnnestSqlFragment.ts +118 -0
  218. package/src/tokens.ts +14 -0
  219. package/src/types.ts +189 -0
  220. package/src/utilities/countArrayDimensions.test.ts +8 -0
  221. package/src/utilities/countArrayDimensions.ts +12 -0
  222. package/src/utilities/escapeIdentifier.test.ts +8 -0
  223. package/src/utilities/escapeIdentifier.ts +8 -0
  224. package/src/utilities/escapeLiteralValue.test.ts +9 -0
  225. package/src/utilities/escapeLiteralValue.ts +26 -0
  226. package/src/utilities/formatSlonikPlaceholder.ts +15 -0
  227. package/src/utilities/hasOwnProperty.ts +10 -0
  228. package/src/utilities/isPlainObject.ts +14 -0
  229. package/src/utilities/isPrimitiveValueExpression.ts +11 -0
  230. package/src/utilities/isSqlToken.ts +52 -0
  231. package/src/utilities/safeStringify.ts +25 -0
  232. package/src/utilities/stripArrayNotation.test.ts +8 -0
  233. package/src/utilities/stripArrayNotation.ts +9 -0
@@ -0,0 +1,71 @@
1
+ import { type IntervalSqlToken, type SqlFragment } from '../types';
2
+ import { formatSlonikPlaceholder } from '../utilities/formatSlonikPlaceholder';
3
+ import { InvalidInputError } from '@slonik/errors';
4
+ import { z } from 'zod';
5
+
6
+ const IntervalInput = z
7
+ .object({
8
+ days: z.number().optional(),
9
+ hours: z.number().optional(),
10
+ minutes: z.number().optional(),
11
+ months: z.number().optional(),
12
+ seconds: z.number().optional(),
13
+ weeks: z.number().optional(),
14
+ years: z.number().optional(),
15
+ })
16
+ .strict();
17
+
18
+ const intervalFragments = [
19
+ 'years',
20
+ 'months',
21
+ 'weeks',
22
+ 'days',
23
+ 'hours',
24
+ 'minutes',
25
+ 'seconds',
26
+ ];
27
+
28
+ const tokenMap = {
29
+ minutes: 'mins',
30
+ seconds: 'secs',
31
+ };
32
+
33
+ export const createIntervalSqlFragment = (
34
+ token: IntervalSqlToken,
35
+ greatestParameterPosition: number,
36
+ ): SqlFragment => {
37
+ let intervalInput;
38
+
39
+ try {
40
+ intervalInput = IntervalInput.parse(token.interval);
41
+ } catch {
42
+ throw new InvalidInputError(
43
+ 'Interval input must not contain unknown properties.',
44
+ );
45
+ }
46
+
47
+ const values: number[] = [];
48
+
49
+ const intervalTokens: string[] = [];
50
+
51
+ for (const intervalFragment of intervalFragments) {
52
+ const value = intervalInput[intervalFragment];
53
+
54
+ if (value !== undefined) {
55
+ values.push(value);
56
+
57
+ const mappedToken = tokenMap[intervalFragment] ?? intervalFragment;
58
+
59
+ intervalTokens.push(
60
+ mappedToken +
61
+ ' => ' +
62
+ formatSlonikPlaceholder(greatestParameterPosition + values.length),
63
+ );
64
+ }
65
+ }
66
+
67
+ return {
68
+ sql: 'make_interval(' + intervalTokens.join(', ') + ')',
69
+ values,
70
+ };
71
+ };
@@ -0,0 +1,66 @@
1
+ import { Logger } from '../Logger';
2
+ import {
3
+ type JsonBinarySqlToken,
4
+ type JsonSqlToken,
5
+ type SqlFragment,
6
+ } from '../types';
7
+ import { formatSlonikPlaceholder } from '../utilities/formatSlonikPlaceholder';
8
+ import { isPlainObject } from '../utilities/isPlainObject';
9
+ import { safeStringify } from '../utilities/safeStringify';
10
+ import { InvalidInputError } from '@slonik/errors';
11
+ import { serializeError } from 'serialize-error';
12
+
13
+ const log = Logger.child({
14
+ namespace: 'createJsonSqlFragment',
15
+ });
16
+
17
+ export const createJsonSqlFragment = (
18
+ token: JsonBinarySqlToken | JsonSqlToken,
19
+ greatestParameterPosition: number,
20
+ binary: boolean,
21
+ ): SqlFragment => {
22
+ let value;
23
+
24
+ if (token.value === undefined) {
25
+ throw new InvalidInputError('JSON payload must not be undefined.');
26
+ } else if (token.value === null) {
27
+ value = 'null';
28
+
29
+ // @todo Deep check Array.
30
+ } else if (
31
+ !isPlainObject(token.value) &&
32
+ !Array.isArray(token.value) &&
33
+ !['number', 'string', 'boolean'].includes(typeof token.value)
34
+ ) {
35
+ throw new InvalidInputError(
36
+ 'JSON payload must be a primitive value or a plain object.',
37
+ );
38
+ } else {
39
+ try {
40
+ value = safeStringify(token.value);
41
+ } catch (error) {
42
+ log.error(
43
+ {
44
+ error: serializeError(error),
45
+ },
46
+ 'payload cannot be stringified',
47
+ );
48
+
49
+ throw new InvalidInputError('JSON payload cannot be stringified.');
50
+ }
51
+
52
+ if (value === undefined) {
53
+ throw new InvalidInputError(
54
+ 'JSON payload cannot be stringified. The resulting value is undefined.',
55
+ );
56
+ }
57
+ }
58
+
59
+ return {
60
+ sql:
61
+ formatSlonikPlaceholder(greatestParameterPosition + 1) +
62
+ '::' +
63
+ (binary ? 'jsonb' : 'json'),
64
+ values: [value],
65
+ };
66
+ };
@@ -0,0 +1,48 @@
1
+ import { createPrimitiveValueExpressions } from '../factories/createPrimitiveValueExpressions';
2
+ import { createSqlTokenSqlFragment } from '../factories/createSqlTokenSqlFragment';
3
+ import {
4
+ type ListSqlToken,
5
+ type PrimitiveValueExpression,
6
+ type SqlFragment,
7
+ } from '../types';
8
+ import { formatSlonikPlaceholder } from '../utilities/formatSlonikPlaceholder';
9
+ import { isPrimitiveValueExpression } from '../utilities/isPrimitiveValueExpression';
10
+ import { isSqlToken } from '../utilities/isSqlToken';
11
+ import { InvalidInputError } from '@slonik/errors';
12
+
13
+ export const createListSqlFragment = (
14
+ token: ListSqlToken,
15
+ greatestParameterPosition: number,
16
+ ): SqlFragment => {
17
+ const values: PrimitiveValueExpression[] = [];
18
+ const placeholders: Array<PrimitiveValueExpression | null> = [];
19
+
20
+ let placeholderIndex = greatestParameterPosition;
21
+
22
+ if (token.members.length === 0) {
23
+ throw new InvalidInputError('Value list must have at least 1 member.');
24
+ }
25
+
26
+ for (const member of token.members) {
27
+ if (isSqlToken(member)) {
28
+ const sqlFragment = createSqlTokenSqlFragment(member, placeholderIndex);
29
+
30
+ placeholders.push(sqlFragment.sql);
31
+ placeholderIndex += sqlFragment.values.length;
32
+ values.push(...sqlFragment.values);
33
+ } else if (isPrimitiveValueExpression(member)) {
34
+ placeholders.push(formatSlonikPlaceholder(++placeholderIndex));
35
+
36
+ values.push(member);
37
+ } else {
38
+ throw new InvalidInputError(
39
+ 'Invalid list member type. Must be a SQL token or a primitive value expression.',
40
+ );
41
+ }
42
+ }
43
+
44
+ return {
45
+ sql: placeholders.join(token.glue.sql),
46
+ values: createPrimitiveValueExpressions(values),
47
+ };
48
+ };
@@ -0,0 +1,48 @@
1
+ import { slonikPlaceholderRegexRule } from '../regexRules/slonikPlaceholderRegexRule';
2
+ import { type QuerySqlToken, type SqlFragment } from '../types';
3
+ import { formatSlonikPlaceholder } from '../utilities/formatSlonikPlaceholder';
4
+ import { UnexpectedStateError } from '@slonik/errors';
5
+
6
+ export const createQuerySqlFragment = (
7
+ token: QuerySqlToken,
8
+ greatestParameterPosition: number,
9
+ ): SqlFragment => {
10
+ let sql = '';
11
+
12
+ let leastMatchedParameterPosition = Number.POSITIVE_INFINITY;
13
+ let greatestMatchedParameterPosition = 0;
14
+
15
+ sql += token.sql.replaceAll(slonikPlaceholderRegexRule, (match, g1) => {
16
+ const parameterPosition = Number.parseInt(g1, 10);
17
+
18
+ if (parameterPosition > greatestMatchedParameterPosition) {
19
+ greatestMatchedParameterPosition = parameterPosition;
20
+ }
21
+
22
+ if (parameterPosition < leastMatchedParameterPosition) {
23
+ leastMatchedParameterPosition = parameterPosition;
24
+ }
25
+
26
+ return formatSlonikPlaceholder(
27
+ parameterPosition + greatestParameterPosition,
28
+ );
29
+ });
30
+
31
+ if (greatestMatchedParameterPosition > token.values.length) {
32
+ throw new UnexpectedStateError(
33
+ 'The greatest parameter position is greater than the number of parameter values.',
34
+ );
35
+ }
36
+
37
+ if (
38
+ leastMatchedParameterPosition !== Number.POSITIVE_INFINITY &&
39
+ leastMatchedParameterPosition !== 1
40
+ ) {
41
+ throw new UnexpectedStateError('Parameter position must start at 1.');
42
+ }
43
+
44
+ return {
45
+ sql,
46
+ values: token.values,
47
+ };
48
+ };
@@ -0,0 +1,22 @@
1
+ import { type SqlFragment, type TimestampSqlToken } from '../types';
2
+ import { formatSlonikPlaceholder } from '../utilities/formatSlonikPlaceholder';
3
+ import { InvalidInputError } from '@slonik/errors';
4
+
5
+ export const createTimestampSqlFragment = (
6
+ token: TimestampSqlToken,
7
+ greatestParameterPosition: number,
8
+ ): SqlFragment => {
9
+ if (!(token.date instanceof Date)) {
10
+ throw new InvalidInputError(
11
+ 'Timestamp parameter value must be an instance of Date.',
12
+ );
13
+ }
14
+
15
+ return {
16
+ sql:
17
+ 'to_timestamp(' +
18
+ formatSlonikPlaceholder(greatestParameterPosition + 1) +
19
+ ')',
20
+ values: [String(token.date.getTime() / 1_000)],
21
+ };
22
+ };
@@ -0,0 +1,118 @@
1
+ import {
2
+ type PrimitiveValueExpression,
3
+ type SqlFragment,
4
+ type UnnestSqlToken,
5
+ } from '../types';
6
+ import { countArrayDimensions } from '../utilities/countArrayDimensions';
7
+ import { escapeIdentifier } from '../utilities/escapeIdentifier';
8
+ import { formatSlonikPlaceholder } from '../utilities/formatSlonikPlaceholder';
9
+ import { isPrimitiveValueExpression } from '../utilities/isPrimitiveValueExpression';
10
+ import { stripArrayNotation } from '../utilities/stripArrayNotation';
11
+ import { InvalidInputError } from '@slonik/errors';
12
+
13
+ export const createUnnestSqlFragment = (
14
+ token: UnnestSqlToken,
15
+ greatestParameterPosition: number,
16
+ ): SqlFragment => {
17
+ const { columnTypes } = token;
18
+
19
+ const values: PrimitiveValueExpression[] = [];
20
+
21
+ const unnestBindings: PrimitiveValueExpression[][] = [];
22
+ const unnestSqlTokens: string[] = [];
23
+
24
+ let columnIndex = 0;
25
+
26
+ let placeholderIndex = greatestParameterPosition;
27
+
28
+ while (columnIndex < columnTypes.length) {
29
+ const typeMember = columnTypes[columnIndex];
30
+
31
+ let columnType = columnTypes[columnIndex];
32
+ let columnTypeIsIdentifier;
33
+
34
+ if (typeof typeMember === 'string') {
35
+ columnType = typeMember;
36
+ columnTypeIsIdentifier = false;
37
+ } else if (Array.isArray(typeMember)) {
38
+ columnType = typeMember
39
+ .map((identifierName) => {
40
+ if (typeof identifierName !== 'string') {
41
+ throw new InvalidInputError(
42
+ 'sql.unnest column identifier name array member type must be a string (type name identifier) or a SQL token.',
43
+ );
44
+ }
45
+
46
+ return escapeIdentifier(identifierName);
47
+ })
48
+ .join('.');
49
+ columnTypeIsIdentifier = true;
50
+ } else {
51
+ columnType = typeMember.sql;
52
+ columnTypeIsIdentifier = true;
53
+ }
54
+
55
+ unnestSqlTokens.push(
56
+ formatSlonikPlaceholder(++placeholderIndex) +
57
+ '::' +
58
+ (columnTypeIsIdentifier
59
+ ? stripArrayNotation(columnType)
60
+ : escapeIdentifier(stripArrayNotation(columnType))) +
61
+ '[]'.repeat(countArrayDimensions(columnType) + 1),
62
+ );
63
+
64
+ unnestBindings[columnIndex] = [];
65
+
66
+ columnIndex++;
67
+ }
68
+
69
+ let lastTupleSize;
70
+
71
+ for (const tupleValues of token.tuples) {
72
+ if (
73
+ typeof lastTupleSize === 'number' &&
74
+ lastTupleSize !== tupleValues.length
75
+ ) {
76
+ throw new Error(
77
+ 'Each tuple in a list of tuples must have an equal number of members.',
78
+ );
79
+ }
80
+
81
+ if (tupleValues.length !== columnTypes.length) {
82
+ throw new Error('Column types length must match tuple member length.');
83
+ }
84
+
85
+ lastTupleSize = tupleValues.length;
86
+
87
+ let tupleColumnIndex = 0;
88
+
89
+ for (const tupleValue of tupleValues) {
90
+ if (
91
+ !Array.isArray(tupleValue) &&
92
+ !isPrimitiveValueExpression(tupleValue) &&
93
+ !Buffer.isBuffer(tupleValue)
94
+ ) {
95
+ throw new InvalidInputError(
96
+ 'Invalid unnest tuple member type. Must be a primitive value expression.',
97
+ );
98
+ }
99
+
100
+ const tupleBindings = unnestBindings[tupleColumnIndex++];
101
+
102
+ if (!tupleBindings) {
103
+ throw new Error('test');
104
+ }
105
+
106
+ tupleBindings.push(tupleValue);
107
+ }
108
+ }
109
+
110
+ values.push(...unnestBindings);
111
+
112
+ const sql = 'unnest(' + unnestSqlTokens.join(', ') + ')';
113
+
114
+ return {
115
+ sql,
116
+ values,
117
+ };
118
+ };
package/src/tokens.ts ADDED
@@ -0,0 +1,14 @@
1
+ export const ArrayToken = 'SLONIK_TOKEN_ARRAY' as const;
2
+ export const BinaryToken = 'SLONIK_TOKEN_BINARY' as const;
3
+ export const ComparisonPredicateToken =
4
+ 'SLONIK_TOKEN_COMPARISON_PREDICATE' as const;
5
+ export const DateToken = 'SLONIK_TOKEN_DATE' as const;
6
+ export const FragmentToken = 'SLONIK_TOKEN_FRAGMENT' as const;
7
+ export const IdentifierToken = 'SLONIK_TOKEN_IDENTIFIER' as const;
8
+ export const IntervalToken = 'SLONIK_TOKEN_INTERVAL' as const;
9
+ export const JsonBinaryToken = 'SLONIK_TOKEN_JSON_BINARY' as const;
10
+ export const JsonToken = 'SLONIK_TOKEN_JSON' as const;
11
+ export const ListToken = 'SLONIK_TOKEN_LIST' as const;
12
+ export const QueryToken = 'SLONIK_TOKEN_QUERY' as const;
13
+ export const TimestampToken = 'SLONIK_TOKEN_TIMESTAMP' as const;
14
+ export const UnnestToken = 'SLONIK_TOKEN_UNNEST' as const;
package/src/types.ts ADDED
@@ -0,0 +1,189 @@
1
+ import type * as tokens from './tokens';
2
+ import { type ZodTypeAny } from 'zod';
3
+
4
+ export type PrimitiveValueExpression =
5
+ | Buffer
6
+ | bigint
7
+ | boolean
8
+ | number
9
+ | string
10
+ | readonly PrimitiveValueExpression[]
11
+ | null;
12
+
13
+ export type SerializableValue =
14
+ | SerializableValue[]
15
+ | boolean
16
+ | number
17
+ | string
18
+ | readonly SerializableValue[]
19
+ | {
20
+ [key: string]: SerializableValue;
21
+ }
22
+ | null
23
+ | undefined;
24
+
25
+ export type IntervalInput = {
26
+ days?: number;
27
+ hours?: number;
28
+ minutes?: number;
29
+ months?: number;
30
+ seconds?: number;
31
+ weeks?: number;
32
+ years?: number;
33
+ };
34
+
35
+ export type SqlFragment = {
36
+ readonly sql: string;
37
+ readonly values: readonly PrimitiveValueExpression[];
38
+ };
39
+
40
+ export type ValueExpression = PrimitiveValueExpression | SqlFragment | SqlToken;
41
+
42
+ /**
43
+ * "string" type covers all type name identifiers – the literal values are added only to assist developer
44
+ * experience with auto suggestions for commonly used type name identifiers.
45
+ */
46
+ export type TypeNameIdentifier =
47
+ | string
48
+ | 'bool'
49
+ | 'bytea'
50
+ | 'float4'
51
+ | 'float8'
52
+ | 'int2'
53
+ | 'int4'
54
+ | 'int8'
55
+ | 'json'
56
+ | 'text'
57
+ | 'timestamptz'
58
+ | 'uuid';
59
+
60
+ export type ArraySqlToken = {
61
+ readonly memberType: SqlToken | TypeNameIdentifier;
62
+ readonly type: typeof tokens.ArrayToken;
63
+ readonly values: readonly PrimitiveValueExpression[];
64
+ };
65
+
66
+ export type BinarySqlToken = {
67
+ readonly data: Buffer;
68
+ readonly type: typeof tokens.BinaryToken;
69
+ };
70
+
71
+ export type DateSqlToken = {
72
+ readonly date: Date;
73
+ readonly type: typeof tokens.DateToken;
74
+ };
75
+
76
+ export type FragmentSqlToken = {
77
+ readonly sql: string;
78
+ readonly type: typeof tokens.FragmentToken;
79
+ readonly values: readonly PrimitiveValueExpression[];
80
+ };
81
+
82
+ export type IdentifierSqlToken = {
83
+ readonly names: readonly string[];
84
+ readonly type: typeof tokens.IdentifierToken;
85
+ };
86
+
87
+ export type IntervalSqlToken = {
88
+ readonly interval: IntervalInput;
89
+ readonly type: typeof tokens.IntervalToken;
90
+ };
91
+
92
+ export type ListSqlToken = {
93
+ readonly glue: SqlFragment;
94
+ readonly members: readonly ValueExpression[];
95
+ readonly type: typeof tokens.ListToken;
96
+ };
97
+
98
+ export type JsonBinarySqlToken = {
99
+ readonly type: typeof tokens.JsonBinaryToken;
100
+ readonly value: SerializableValue;
101
+ };
102
+
103
+ export type JsonSqlToken = {
104
+ readonly type: typeof tokens.JsonToken;
105
+ readonly value: SerializableValue;
106
+ };
107
+
108
+ export type QuerySqlToken<T extends ZodTypeAny = ZodTypeAny> = {
109
+ readonly parser: T;
110
+ readonly sql: string;
111
+ readonly type: typeof tokens.QueryToken;
112
+ readonly values: readonly PrimitiveValueExpression[];
113
+ };
114
+
115
+ export type TimestampSqlToken = {
116
+ readonly date: Date;
117
+ readonly type: typeof tokens.TimestampToken;
118
+ };
119
+
120
+ export type UnnestSqlToken = {
121
+ readonly columnTypes:
122
+ | Array<[...string[], TypeNameIdentifier]>
123
+ | Array<SqlFragment | TypeNameIdentifier>;
124
+ readonly tuples: ReadonlyArray<readonly ValueExpression[]>;
125
+ readonly type: typeof tokens.UnnestToken;
126
+ };
127
+
128
+ export type SqlToken =
129
+ | ArraySqlToken
130
+ | BinarySqlToken
131
+ | DateSqlToken
132
+ | FragmentSqlToken
133
+ | IdentifierSqlToken
134
+ | IntervalSqlToken
135
+ | JsonBinarySqlToken
136
+ | JsonSqlToken
137
+ | ListSqlToken
138
+ | QuerySqlToken
139
+ | TimestampSqlToken
140
+ | UnnestSqlToken;
141
+
142
+ export type SqlTag<Z extends Record<string, ZodTypeAny>> = {
143
+ array: (
144
+ values: readonly PrimitiveValueExpression[],
145
+ memberType: SqlFragment | TypeNameIdentifier,
146
+ ) => ArraySqlToken;
147
+ binary: (data: Buffer) => BinarySqlToken;
148
+ date: (date: Date) => DateSqlToken;
149
+ fragment: (
150
+ template: TemplateStringsArray,
151
+ ...values: ValueExpression[]
152
+ ) => SqlFragment;
153
+ identifier: (names: readonly string[]) => IdentifierSqlToken;
154
+ interval: (interval: IntervalInput) => IntervalSqlToken;
155
+ join: (
156
+ members: readonly ValueExpression[],
157
+ glue: SqlFragment,
158
+ ) => ListSqlToken;
159
+ json: (value: SerializableValue) => JsonSqlToken;
160
+ jsonb: (value: SerializableValue) => JsonBinarySqlToken;
161
+ literalValue: (value: string) => SqlFragment;
162
+ timestamp: (date: Date) => TimestampSqlToken;
163
+ type: <Y extends ZodTypeAny>(
164
+ parser: Y,
165
+ ) => (
166
+ template: TemplateStringsArray,
167
+ ...values: ValueExpression[]
168
+ ) => QuerySqlToken<Y>;
169
+ typeAlias: <K extends keyof Z>(
170
+ typeAlias: K,
171
+ ) => (
172
+ template: TemplateStringsArray,
173
+ ...values: ValueExpression[]
174
+ ) => QuerySqlToken<Z[K]>;
175
+ unnest: (
176
+ // Value might be ReadonlyArray<ReadonlyArray<PrimitiveValueExpression>>,
177
+ // or it can be infinitely nested array, e.g.
178
+ // https://github.com/gajus/slonik/issues/44
179
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
180
+ tuples: ReadonlyArray<readonly any[]>,
181
+ columnTypes:
182
+ | Array<[...string[], TypeNameIdentifier]>
183
+ | Array<SqlFragment | TypeNameIdentifier>,
184
+ ) => UnnestSqlToken;
185
+ unsafe: (
186
+ template: TemplateStringsArray,
187
+ ...values: ValueExpression[]
188
+ ) => QuerySqlToken;
189
+ };
@@ -0,0 +1,8 @@
1
+ import { countArrayDimensions } from './countArrayDimensions';
2
+ import test from 'ava';
3
+
4
+ test('returns the number of array dimensions', (t) => {
5
+ t.is(countArrayDimensions('foo'), 0);
6
+ t.is(countArrayDimensions('foo[]'), 1);
7
+ t.is(countArrayDimensions('foo[][]'), 2);
8
+ });
@@ -0,0 +1,12 @@
1
+ export const countArrayDimensions = (identifierName: string): number => {
2
+ let tail = identifierName.trim();
3
+ let arrayDimensionCount = 0;
4
+
5
+ while (tail.endsWith('[]')) {
6
+ arrayDimensionCount++;
7
+
8
+ tail = tail.trim().slice(0, -2);
9
+ }
10
+
11
+ return arrayDimensionCount;
12
+ };
@@ -0,0 +1,8 @@
1
+ import { escapeIdentifier } from './escapeIdentifier';
2
+ import test from 'ava';
3
+
4
+ test('escapes SQL identifiers', (t) => {
5
+ t.is(escapeIdentifier('foo'), '"foo"');
6
+ t.is(escapeIdentifier('foo bar'), '"foo bar"');
7
+ t.is(escapeIdentifier('"foo"'), '"""foo"""');
8
+ });
@@ -0,0 +1,8 @@
1
+ const rule = /"/gu;
2
+
3
+ /**
4
+ * @see https://github.com/brianc/node-postgres/blob/6c840aabb09f8a2d640800953f6b884b6841384c/lib/client.js#L306-L322
5
+ */
6
+ export const escapeIdentifier = (identifier: string): string => {
7
+ return '"' + identifier.replaceAll(rule, '""') + '"';
8
+ };
@@ -0,0 +1,9 @@
1
+ import { escapeLiteralValue } from './escapeLiteralValue';
2
+ import test from 'ava';
3
+
4
+ test('escapes SQL literal value', (t) => {
5
+ t.is(escapeLiteralValue('foo'), "'foo'");
6
+ t.is(escapeLiteralValue('foo bar'), "'foo bar'");
7
+ t.is(escapeLiteralValue('"foo"'), '\'"foo"\'');
8
+ t.is(escapeLiteralValue('foo\\bar'), "E'foo\\\\bar'");
9
+ });
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @see https://github.com/brianc/node-postgres/blob/6c840aabb09f8a2d640800953f6b884b6841384c/lib/client.js#L325-L348
3
+ */
4
+ export const escapeLiteralValue = (subject: string): string => {
5
+ let hasBackslash = false;
6
+ let escaped = "'";
7
+
8
+ for (const character of subject) {
9
+ if (character === "'") {
10
+ escaped += character + character;
11
+ } else if (character === '\\') {
12
+ escaped += character + character;
13
+ hasBackslash = true;
14
+ } else {
15
+ escaped += character;
16
+ }
17
+ }
18
+
19
+ escaped += "'";
20
+
21
+ if (hasBackslash === true) {
22
+ escaped = 'E' + escaped;
23
+ }
24
+
25
+ return escaped;
26
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Slonik allows to compose queries using fragments, e.g.
3
+ * ```ts
4
+ * sql.fragment`SELECT ${sql.fragment`${1}`}`;
5
+ * ```
6
+ * Take a look at the logic in `createFragmentSqlFragment` to see how we detect nested fragments.
7
+ * When we detect a nested fragment, we need to offset the index of the placeholder.
8
+ * Historically, we used a placeholder format that was based on the index of the placeholder, e.g. $1, $2, etc.
9
+ * The problem with that approach was that any mention of $\d inside of a nested fragment was wrongly identified as a placeholder.
10
+ * To avoid that, we now use a placeholder that is prefixed with $slonik_.
11
+ * This way, we can safely detect placeholders that are part of a nested fragment.
12
+ */
13
+ export const formatSlonikPlaceholder = (index: number) => {
14
+ return '$slonik_' + String(index);
15
+ };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * A stricter type guard.
3
+ * @see https://tsplay.dev/WK8zGw
4
+ */
5
+ export const hasOwnProperty = <X extends {}, Y extends PropertyKey>(
6
+ object: X,
7
+ property: Y,
8
+ ): object is Record<Y, unknown> & X => {
9
+ return Object.prototype.hasOwnProperty.call(object, property);
10
+ };