@storecraft/database-sql-base 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +126 -0
  2. package/TODO.md +2 -0
  3. package/db-strategy.md +3 -0
  4. package/driver.js +190 -0
  5. package/index.js +3 -0
  6. package/migrate.js +66 -0
  7. package/migrations.mssql/00000_init_tables.js +268 -0
  8. package/migrations.mysql/00000_init_tables.js +372 -0
  9. package/migrations.mysql/00001_seed_email_templates.js +1 -0
  10. package/migrations.postgres/00000_init_tables.js +358 -0
  11. package/migrations.postgres/00001_seed_email_templates.js +1 -0
  12. package/migrations.shared/00001_seed_email_templates.js +260 -0
  13. package/migrations.sqlite/00000_init_tables.js +357 -0
  14. package/migrations.sqlite/00001_seed_email_templates.js +1 -0
  15. package/package.json +47 -0
  16. package/src/con.auth_users.js +159 -0
  17. package/src/con.collections.js +197 -0
  18. package/src/con.customers.js +202 -0
  19. package/src/con.discounts.js +225 -0
  20. package/src/con.discounts.utils.js +180 -0
  21. package/src/con.helpers.json.js +231 -0
  22. package/src/con.helpers.json.mssql.js +233 -0
  23. package/src/con.helpers.json.mysql.js +239 -0
  24. package/src/con.helpers.json.postgres.js +223 -0
  25. package/src/con.helpers.json.sqlite.js +263 -0
  26. package/src/con.images.js +230 -0
  27. package/src/con.notifications.js +149 -0
  28. package/src/con.orders.js +156 -0
  29. package/src/con.posts.js +147 -0
  30. package/src/con.products.js +497 -0
  31. package/src/con.search.js +148 -0
  32. package/src/con.shared.js +616 -0
  33. package/src/con.shipping.js +147 -0
  34. package/src/con.storefronts.js +301 -0
  35. package/src/con.tags.js +120 -0
  36. package/src/con.templates.js +133 -0
  37. package/src/kysely.sanitize.plugin.js +40 -0
  38. package/src/utils.funcs.js +77 -0
  39. package/src/utils.query.js +195 -0
  40. package/tests/query.cursor.test.js +389 -0
  41. package/tests/query.vql.test.js +71 -0
  42. package/tests/runner.mssql-local.test.js +118 -0
  43. package/tests/runner.mysql-local.test.js +101 -0
  44. package/tests/runner.postgres-local.test.js +99 -0
  45. package/tests/runner.sqlite-local.test.js +99 -0
  46. package/tests/sandbox.test.js +71 -0
  47. package/tsconfig.json +21 -0
  48. package/types.public.d.ts +19 -0
  49. package/types.sql.tables.d.ts +247 -0
@@ -0,0 +1,180 @@
1
+ import { enums } from "@storecraft/core/v-api";
2
+
3
+ /** @param {import("@storecraft/core/v-api").DiscountType} d */
4
+ const is_order_discount = d => {
5
+ return (d.info.details.meta.id===enums.DiscountMetaEnum.order.id);
6
+ }
7
+
8
+ /** @param {import("@storecraft/core/v-api").DiscountType} d */
9
+ const is_automatic_discount = d => {
10
+ return (d.application.id===enums.DiscountApplicationEnum.Auto.id);
11
+ }
12
+
13
+ const extract_abs_number = v => {
14
+ return v && !isNaN(v) && v!==Infinity && Math.abs(v);
15
+ }
16
+ /**
17
+ * @typedef {import("../index.js").Database} Database
18
+ * @param {import("kysely").ExpressionBuilder<Database, 'products'>} eb
19
+ * @param {keyof Pick<Database, 'entity_to_tags_projections' | 'products_to_collections'>} table
20
+ * @param {import("kysely").BinaryOperator} op
21
+ * @param {string[]} value
22
+ */
23
+ const eb_in = (eb, table, op, value) => {
24
+ return eb.exists(
25
+ eb => eb
26
+ .selectFrom(table)
27
+ .select('id')
28
+ .where(
29
+ eb => eb.and([
30
+ eb.or(
31
+ [
32
+ eb(`${table}.entity_id`, '=', eb.ref('products.id')),
33
+ eb(`${table}.entity_handle`, '=', eb.ref('products.handle')),
34
+ ]
35
+ ),
36
+ eb(`${table}.value`, op, value)
37
+ ])
38
+ )
39
+ )
40
+ }
41
+
42
+ /**
43
+ * create a mongodb conjunctions clauses from discount, intended
44
+ * for filtering.
45
+ * @param {import("kysely").ExpressionBuilder<Database, 'products'>} eb
46
+ * @param {import("@storecraft/core/v-api").DiscountType} d
47
+ */
48
+ export const discount_to_conjunctions = (eb, d) => {
49
+ // discount has to be product discount + automatic + active + has filters
50
+ const is_good = (
51
+ !is_order_discount(d) && is_automatic_discount(d) &&
52
+ d.active && d?.info?.filters?.length
53
+ );
54
+
55
+ if(!is_good) return [];
56
+
57
+ const conjunctions = [];
58
+ const filters = d.info.filters;
59
+
60
+ for(const filter of filters) {
61
+ const op = filter.meta.op;
62
+
63
+ switch (op) {
64
+ case enums.FilterMetaEnum.p_all.op:
65
+ // do nothing
66
+ break;
67
+ case enums.FilterMetaEnum.p_in_products.op:
68
+ {
69
+ /** @type {import("@storecraft/core/v-api").FilterValue_p_in_products} */
70
+ const cast = Array.isArray(filter?.value) ? filter.value : [];
71
+
72
+ conjunctions.push(
73
+ eb(
74
+ 'products.handle', 'in',
75
+ cast.map(item => item.handle).filter(Boolean)
76
+ )
77
+ );
78
+ }
79
+ break;
80
+ case enums.FilterMetaEnum.p_not_in_products.op:
81
+ {
82
+ /** @type {import("@storecraft/core/v-api").FilterValue_p_not_in_products} */
83
+ const cast = Array.isArray(filter?.value) ? filter.value : [];
84
+
85
+ conjunctions.push(
86
+ eb(
87
+ 'products.handle', 'not in',
88
+ cast.map(item => item.handle).filter(Boolean)
89
+ )
90
+ );
91
+ }
92
+ break;
93
+ case enums.FilterMetaEnum.p_in_tags.op:
94
+ {
95
+ /** @type {import("@storecraft/core/v-api").FilterValue_p_in_tags} */
96
+ const cast = Array.isArray(filter?.value) ? filter.value : [];
97
+
98
+ conjunctions.push(
99
+ eb_in(
100
+ eb, 'entity_to_tags_projections', 'in',
101
+ cast
102
+ )
103
+ );
104
+ }
105
+ break;
106
+ case enums.FilterMetaEnum.p_not_in_tags.op:
107
+ {
108
+ /** @type {import("@storecraft/core/v-api").FilterValue_p_not_in_tags} */
109
+ const cast = Array.isArray(filter?.value) ? filter.value : [];
110
+
111
+ conjunctions.push(
112
+ eb.not(
113
+ eb_in(
114
+ eb, 'entity_to_tags_projections', 'in',
115
+ cast
116
+ )
117
+ )
118
+ );
119
+ }
120
+ break;
121
+ case enums.FilterMetaEnum.p_in_collections.op:
122
+ {
123
+ /** @type {import("@storecraft/core/v-api").FilterValue_p_in_collections} */
124
+ const cast = Array.isArray(filter?.value) ? filter.value : [];
125
+
126
+ // PROBLEM: we only have ids, but use handles in the filters
127
+ conjunctions.push(
128
+ eb_in(
129
+ eb, 'products_to_collections', 'in',
130
+ cast.map(c => c.id)
131
+ )
132
+ );
133
+ }
134
+ break;
135
+ case enums.FilterMetaEnum.p_not_in_collections.op:
136
+ {
137
+ /** @type {import("@storecraft/core/v-api").FilterValue_p_not_in_collections} */
138
+ const cast = Array.isArray(filter?.value) ? filter.value : [];
139
+
140
+ conjunctions.push(
141
+ eb.not(
142
+ eb_in(
143
+ eb, 'products_to_collections', 'in',
144
+ cast.map(c => c.id)
145
+ )
146
+ )
147
+ );
148
+ }
149
+ break;
150
+ case enums.FilterMetaEnum.p_in_price_range.op:
151
+ {
152
+ /** @type {import("@storecraft/core/v-api").FilterValue_p_in_price_range} */
153
+ const cast = {
154
+ from: 0,
155
+ to: Number.POSITIVE_INFINITY,
156
+ ...(filter?.value ?? {}),
157
+ };
158
+
159
+ const from = extract_abs_number(cast.from);
160
+ const to = extract_abs_number(cast.to);
161
+
162
+ const conj = { price: { $and: [] } };
163
+
164
+ if(from)
165
+ conjunctions.push(eb('price', '>=', from));
166
+
167
+ if(to)
168
+ conjunctions.push(eb('price', '<', to));
169
+
170
+ }
171
+ break;
172
+
173
+ default:
174
+ break;
175
+ }
176
+ }
177
+
178
+ return conjunctions;
179
+ }
180
+
@@ -0,0 +1,231 @@
1
+ import {
2
+ AliasNode, ColumnNode, ExpressionWrapper, IdentifierNode,
3
+ ReferenceNode, SelectQueryNode, TableNode, ValueNode } from 'kysely'
4
+ import { sqlite_jsonArrayFrom, sqlite_jsonObjectFrom,
5
+ sqlite_stringArrayFrom } from './con.helpers.json.sqlite.js'
6
+ import { pg_jsonArrayFrom, pg_jsonObjectFrom,
7
+ pg_stringArrayFrom } from './con.helpers.json.postgres.js'
8
+ import { mysql_jsonArrayFrom, mysql_jsonObjectFrom,
9
+ mysql_stringArrayFrom } from './con.helpers.json.mysql.js'
10
+ import { mssql_jsonArrayFrom, mssql_jsonObjectFrom,
11
+ mssql_stringArrayFrom } from './con.helpers.json.mssql.js'
12
+
13
+
14
+ /**
15
+ * @template O
16
+ * @typedef {Object} _SelectQueryBuilderExpression
17
+ * @property {boolean} isSelectQueryBuilder
18
+ * @property {() => SelectQueryNode} toOperationNode
19
+ */
20
+
21
+ /**
22
+ * @template O
23
+ * @typedef {import('kysely').AliasableExpression<O> & _SelectQueryBuilderExpression<O>} SelectQueryBuilderExpression
24
+ * @property {boolean} isSelectQueryBuilder
25
+ * @property {(): SelectQueryNode} toOperationNode
26
+ */
27
+
28
+
29
+ /**
30
+ *
31
+ * @param {SelectQueryNode} node
32
+ * @param {string} table
33
+ * @returns {import('kysely').Expression<unknown>[] }
34
+ */
35
+ export function getJsonObjectArgs(node, table) {
36
+ /** @type {import('kysely').Expression<unknown>[] } */
37
+ const args = []
38
+
39
+ for (const { selection: s } of node.selections ?? []) {
40
+
41
+ if (ReferenceNode.is(s) && ColumnNode.is(s.column)) {
42
+ args.push(
43
+ colName(s.column.column.name),
44
+ colRef(table, s.column.column.name),
45
+ )
46
+ } else if (ColumnNode.is(s)) {
47
+ args.push(colName(s.column.name), colRef(table, s.column.name))
48
+ } else if (AliasNode.is(s) && IdentifierNode.is(s.alias)) {
49
+ args.push(colName(s.alias.name), colRef(table, s.alias.name))
50
+ } else {
51
+ throw new Error(`can't extract column names from the select query node`)
52
+ }
53
+ }
54
+
55
+ return args
56
+ }
57
+
58
+ /**
59
+ *
60
+ * @param {string} col
61
+ * @returns {import('kysely').Expression<unknown> }
62
+ */
63
+ function colName(col) {
64
+ return new ExpressionWrapper(ValueNode.createImmediate(col))
65
+ }
66
+
67
+ /**
68
+ *
69
+ * @param {string} table
70
+ * @param {string} col
71
+ * @returns {import('kysely').Expression<unknown> }
72
+ */
73
+ function colRef(table, col) {
74
+ return new ExpressionWrapper(
75
+ ReferenceNode.create(ColumnNode.create(col), TableNode.create(table)),
76
+ )
77
+ }
78
+
79
+ /**
80
+ * @template O
81
+ * @param {SelectQueryBuilderExpression<O>} expr
82
+ * @param {string} table
83
+ * @returns {import('kysely').Expression<unknown>}
84
+ */
85
+ export const extract_first_selection = (expr, table) => {
86
+ /** @type {any} */
87
+ let s_ = expr.toOperationNode();
88
+ /** @type {SelectQueryNode} */
89
+ const s__ = s_;
90
+ const s = s__.selections[0].selection;
91
+ /** @type {import('kysely').Expression<unknown>} */
92
+ let arg;
93
+ if (ReferenceNode.is(s) && ColumnNode.is(s.column)) {
94
+ // console.log('arg ', s)
95
+ arg = colRef(table, s.column.column.name);
96
+ } else if (ColumnNode.is(s)) {
97
+ arg = colRef(table, s.column.name);
98
+ // console.log('arg ', 2)
99
+ } else if (AliasNode.is(s) && IdentifierNode.is(s.alias)) {
100
+ arg = colRef(table, s.alias.name);
101
+ // console.log('arg ', 3)
102
+ } else {
103
+ throw new Error(`can't extract column names from the select query node`)
104
+ }
105
+ return arg;
106
+ }
107
+
108
+ /**
109
+ * ### Examples
110
+ *
111
+ * ```ts
112
+ * const result = await db
113
+ * .selectFrom('person')
114
+ * .select((eb) => [
115
+ * 'id',
116
+ * jsonArrayFrom(
117
+ * eb.selectFrom('pet')
118
+ * .select(['pet.id as pet_id', 'pet.name'])
119
+ * .whereRef('pet.owner_id', '=', 'person.id')
120
+ * .orderBy('pet.name')
121
+ * ).as('pets')
122
+ * ])
123
+ * .execute()
124
+ *
125
+ * result[0].id
126
+ * result[0].pets[0].pet_id
127
+ * result[0].pets[0].name
128
+ * ```
129
+ *
130
+ * @template O
131
+ * @param {import('./con.helpers.json.js').SelectQueryBuilderExpression<O>} expr
132
+ * @param {import('../types.public.js').SqlDialectType} sql_type
133
+ * @returns {import('kysely').RawBuilder<import('kysely').Simplify<O>[]>}
134
+ */
135
+ export function jsonArrayFrom(expr, sql_type) {
136
+ switch(sql_type) {
137
+ case 'SQLITE':
138
+ return sqlite_jsonArrayFrom(expr);
139
+ case 'POSTGRES':
140
+ return pg_jsonArrayFrom(expr);
141
+ case 'MYSQL':
142
+ return mysql_jsonArrayFrom(expr);
143
+ case 'MSSQL':
144
+ return mssql_jsonArrayFrom(expr);
145
+ default:
146
+ throw new Error(`sql_type=${sql_type} NOT SUPPORTED !`);
147
+ }
148
+ }
149
+
150
+
151
+ /**
152
+ * A SQLite helper for aggregating a subquery into a JSON array.
153
+ * ### Examples
154
+ *
155
+ * ```ts
156
+ * const result = await db
157
+ * .selectFrom('person')
158
+ * .select((eb) => [
159
+ * 'id',
160
+ * stringArrayFrom(
161
+ * eb.selectFrom('pet')
162
+ * .select('pet.name')
163
+ * .whereRef('pet.owner_id', '=', 'person.id')
164
+ * .orderBy('pet.name')
165
+ * ).as('pets')
166
+ * ])
167
+ * .execute()
168
+ *
169
+ * result[0].pets = ['name1', 'name2', ....]
170
+ * ```
171
+ * @template O
172
+ * @param {import('./con.helpers.json.js').SelectQueryBuilderExpression<O>} expr
173
+ * @param {import('../types.public.js').SqlDialectType} sql_type
174
+ * @returns {import('kysely').RawBuilder<import('kysely').Simplify<O>[]>}
175
+ */
176
+ export function stringArrayFrom(expr, sql_type) {
177
+ switch(sql_type) {
178
+ case 'SQLITE':
179
+ return sqlite_stringArrayFrom(expr);
180
+ case 'POSTGRES':
181
+ return pg_stringArrayFrom(expr);
182
+ case 'MYSQL':
183
+ return mysql_stringArrayFrom(expr);
184
+ case 'MSSQL':
185
+ return mssql_stringArrayFrom(expr);
186
+ default:
187
+ throw new Error(`sql_type=${sql_type} NOT SUPPORTED !`);
188
+ }
189
+ }
190
+
191
+ /**
192
+ * ### Examples
193
+ *
194
+ * ```ts
195
+ * const result = await db
196
+ * .selectFrom('person')
197
+ * .select((eb) => [
198
+ * 'id',
199
+ * jsonObjectFrom(
200
+ * eb.selectFrom('pet')
201
+ * .select(['pet.id as pet_id', 'pet.name'])
202
+ * .whereRef('pet.owner_id', '=', 'person.id')
203
+ * .where('pet.is_favorite', '=', true)
204
+ * ).as('favorite_pet')
205
+ * ])
206
+ * .execute()
207
+ *
208
+ * result[0].id
209
+ * result[0].favorite_pet.pet_id
210
+ * result[0].favorite_pet.name
211
+ * ```
212
+ *
213
+ * @template O
214
+ * @param {import('./con.helpers.json.js').SelectQueryBuilderExpression<O>} expr
215
+ * @param {import('../types.public.js').SqlDialectType} sql_type
216
+ * @returns {import('kysely').RawBuilder<import('kysely').Simplify<O> | null>}
217
+ */
218
+ export function jsonObjectFrom(expr, sql_type) {
219
+ switch(sql_type) {
220
+ case 'SQLITE':
221
+ return sqlite_jsonObjectFrom(expr);
222
+ case 'POSTGRES':
223
+ return pg_jsonObjectFrom(expr);
224
+ case 'MYSQL':
225
+ return mysql_jsonObjectFrom(expr);
226
+ case 'MSSQL':
227
+ return mssql_jsonObjectFrom(expr);
228
+ default:
229
+ throw new Error(`sql_type=${sql_type} NOT SUPPORTED !`);
230
+ }
231
+ }
@@ -0,0 +1,233 @@
1
+ import { sql } from "kysely"
2
+
3
+ /**
4
+ * An MS SQL Server helper for aggregating a subquery into a JSON array.
5
+ *
6
+ * NOTE: This helper only works correctly if you've installed the `ParseJSONResultsPlugin`.
7
+ * Otherwise the nested selections will be returned as JSON strings.
8
+ *
9
+ * The plugin can be installed like this:
10
+ *
11
+ * ```ts
12
+ * const db = new Kysely({
13
+ * dialect: new MssqlDialect(config),
14
+ * plugins: [new ParseJSONResultsPlugin()]
15
+ * })
16
+ * ```
17
+ *
18
+ * ### Examples
19
+ *
20
+ * ```ts
21
+ * const result = await db
22
+ * .selectFrom('person')
23
+ * .select((eb) => [
24
+ * 'id',
25
+ * jsonArrayFrom(
26
+ * eb.selectFrom('pet')
27
+ * .select(['pet.id as pet_id', 'pet.name'])
28
+ * .whereRef('pet.owner_id', '=', 'person.id')
29
+ * .orderBy('pet.name')
30
+ * .modifyEnd(sql`offset 0 rows`)
31
+ * ).as('pets')
32
+ * ])
33
+ * .execute()
34
+ *
35
+ * result[0].id
36
+ * result[0].pets[0].pet_id
37
+ * result[0].pets[0].name
38
+ * ```
39
+ *
40
+ * The generated SQL (MS SQL Server):
41
+ *
42
+ * ```sql
43
+ * select "id", (
44
+ * select coalesce((select * from (
45
+ * select "pet"."id" as "pet_id", "pet"."name"
46
+ * from "pet"
47
+ * where "pet"."owner_id" = "person"."id"
48
+ * order by "pet"."name"
49
+ * offset 0 rows
50
+ * ) as "agg" for json path, include_null_values), '[]')
51
+ * ) as "pets"
52
+ * from "person"
53
+ * ```
54
+ *
55
+ * @template O
56
+ * @param {import("kysely").Expression<O>} expr
57
+ * @returns {import("kysely").RawBuilder<import("kysely").Simplify<O>[]>}
58
+ */
59
+ export function mssql_jsonArrayFrom(expr) {
60
+ return sql`coalesce((select * from ${expr} as agg for json path, include_null_values), '[]')`
61
+ }
62
+
63
+ /**
64
+ * An MS SQL Server helper for aggregating a subquery into a JSON array.
65
+ *
66
+ * NOTE: This helper only works correctly if you've installed the `ParseJSONResultsPlugin`.
67
+ * Otherwise the nested selections will be returned as JSON strings.
68
+ *
69
+ * The plugin can be installed like this:
70
+ *
71
+ * ```ts
72
+ * const db = new Kysely({
73
+ * dialect: new MssqlDialect(config),
74
+ * plugins: [new ParseJSONResultsPlugin()]
75
+ * })
76
+ * ```
77
+ *
78
+ * ### Examples
79
+ *
80
+ * ```ts
81
+ * const result = await db
82
+ * .selectFrom('person')
83
+ * .select((eb) => [
84
+ * 'id',
85
+ * jsonArrayFrom(
86
+ * eb.selectFrom('pet')
87
+ * .select(['pet.id as pet_id', 'pet.name'])
88
+ * .whereRef('pet.owner_id', '=', 'person.id')
89
+ * .orderBy('pet.name')
90
+ * .modifyEnd(sql`offset 0 rows`)
91
+ * ).as('pets')
92
+ * ])
93
+ * .execute()
94
+ *
95
+ * result[0].id
96
+ * result[0].pets[0].pet_id
97
+ * result[0].pets[0].name
98
+ * ```
99
+ *
100
+ * The generated SQL (MS SQL Server):
101
+ *
102
+ * ```sql
103
+ * select "id", (
104
+ * select coalesce((select * from (
105
+ * select "pet"."id" as "pet_id", "pet"."name"
106
+ * from "pet"
107
+ * where "pet"."owner_id" = "person"."id"
108
+ * order by "pet"."name"
109
+ * offset 0 rows
110
+ * ) as "agg" for json path, include_null_values), '[]')
111
+ * ) as "pets"
112
+ * from "person"
113
+ * ```
114
+ *
115
+ * @template O
116
+ * @param {import("kysely").Expression<O>} expr
117
+ * @returns {import("kysely").RawBuilder<import("kysely").Simplify<O>[]>}
118
+ */
119
+ export function mssql_stringArrayFrom(expr) {
120
+ return sql`coalesce((select * from ${expr} as agg for json path, include_null_values), '[]')`
121
+ }
122
+
123
+
124
+ /**
125
+ * An MS SQL Server helper for turning a subquery into a JSON object.
126
+ *
127
+ * The subquery must only return one row.
128
+ *
129
+ * NOTE: This helper only works correctly if you've installed the `ParseJSONResultsPlugin`.
130
+ * Otherwise the nested selections will be returned as JSON strings.
131
+ *
132
+ * The plugin can be installed like this:
133
+ *
134
+ * ```ts
135
+ * const db = new Kysely({
136
+ * dialect: new MssqlDialect(config),
137
+ * plugins: [new ParseJSONResultsPlugin()]
138
+ * })
139
+ * ```
140
+ *
141
+ * ### Examples
142
+ *
143
+ * ```ts
144
+ * const result = await db
145
+ * .selectFrom('person')
146
+ * .select((eb) => [
147
+ * 'id',
148
+ * jsonObjectFrom(
149
+ * eb.selectFrom('pet')
150
+ * .select(['pet.id as pet_id', 'pet.name'])
151
+ * .whereRef('pet.owner_id', '=', 'person.id')
152
+ * .where('pet.is_favorite', '=', 1)
153
+ * ).as('favorite_pet')
154
+ * ])
155
+ * .execute()
156
+ *
157
+ * result[0].id
158
+ * result[0].favorite_pet.pet_id
159
+ * result[0].favorite_pet.name
160
+ * ```
161
+ *
162
+ * The generated SQL (MS SQL Server):
163
+ *
164
+ * ```sql
165
+ * select "id", (
166
+ * select * from (
167
+ * select "pet"."id" as "pet_id", "pet"."name"
168
+ * from "pet"
169
+ * where "pet"."owner_id" = "person"."id"
170
+ * and "pet"."is_favorite" = @1
171
+ * ) as "agg" for json path, include_null_values, without_array_wrapper
172
+ * ) as "favorite_pet"
173
+ * from "person"
174
+ * ```
175
+ *
176
+ * @template O
177
+ * @param {import("kysely").Expression<O>} expr
178
+ * @returns {import("kysely").RawBuilder<import("kysely").Simplify<O> | null>}
179
+ */
180
+ export function mssql_jsonObjectFrom(expr) {
181
+ return sql`(select * from ${expr} as agg for json path, include_null_values, without_array_wrapper)`
182
+ }
183
+
184
+ /**
185
+ * The MS SQL Server `json_query` function, single argument variant.
186
+ *
187
+ * NOTE: This helper only works correctly if you've installed the `ParseJSONResultsPlugin`.
188
+ * Otherwise the nested selections will be returned as JSON strings.
189
+ *
190
+ * The plugin can be installed like this:
191
+ *
192
+ * ```ts
193
+ * const db = new Kysely({
194
+ * dialect: new MssqlDialect(config),
195
+ * plugins: [new ParseJSONResultsPlugin()]
196
+ * })
197
+ * ```
198
+ *
199
+ * ### Examples
200
+ *
201
+ * ```ts
202
+ * const result = await db
203
+ * .selectFrom('person')
204
+ * .select((eb) => [
205
+ * 'id',
206
+ * jsonBuildObject({
207
+ * first: eb.ref('first_name'),
208
+ * last: eb.ref('last_name'),
209
+ * full: eb.fn('concat', ['first_name', eb.val(' '), 'last_name'])
210
+ * }).as('name')
211
+ * ])
212
+ * .execute()
213
+ * ```
214
+ *
215
+ * The generated SQL (MS SQL Server):
216
+ *
217
+ * ```sql
218
+ * select "id", json_query(
219
+ * '{"first":"'+"first_name"+',"last":"'+"last_name"+',"full":"'+concat("first_name", ' ', "last_name")+'"}'
220
+ * ) as "name"
221
+ * from "person"
222
+ * ```
223
+ *
224
+ * @template {Record<string, import("kysely").Expression<unknown>>} O
225
+ * @param {O} obj
226
+ * @returns {import("kysely").RawBuilder<import("kysely").Simplify<{[K in keyof O]: O[K] extends Expression<infer V> ? V : never}>>}
227
+ */
228
+ export function mssql_jsonBuildObject(obj) {
229
+ return sql`json_query('{${sql.join(
230
+ Object.keys(obj).map((k) => sql`"${sql.raw(k)}":"'+${obj[k]}+'"`),
231
+ sql`,`,
232
+ )}}')`
233
+ }