effect-qb 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mysql.js +362 -70
- package/dist/postgres/metadata.js +557 -27
- package/dist/postgres.js +5028 -4732
- package/package.json +2 -2
- package/src/internal/column-state.ts +7 -0
- package/src/internal/column.ts +22 -0
- package/src/internal/dialect.ts +12 -1
- package/src/internal/executor.ts +15 -4
- package/src/internal/predicate/analysis.ts +103 -1
- package/src/internal/predicate/atom.ts +7 -0
- package/src/internal/predicate/context.ts +156 -16
- package/src/internal/predicate/key.ts +46 -1
- package/src/internal/predicate/normalize.ts +115 -34
- package/src/internal/predicate/runtime.ts +118 -11
- package/src/internal/query.ts +328 -90
- package/src/internal/renderer.ts +4 -0
- package/src/internal/runtime/driver-value-mapping.ts +186 -0
- package/src/internal/scalar.ts +11 -0
- package/src/mysql/column.ts +1 -0
- package/src/mysql/executor.ts +20 -5
- package/src/mysql/internal/dialect.ts +12 -6
- package/src/mysql/internal/dsl.ts +268 -54
- package/src/mysql/internal/renderer.ts +11 -2
- package/src/mysql/internal/sql-expression-renderer.ts +54 -8
- package/src/mysql/renderer.ts +7 -2
- package/src/postgres/cast.ts +22 -7
- package/src/postgres/column.ts +1 -0
- package/src/postgres/executor.ts +20 -5
- package/src/postgres/internal/dialect.ts +12 -6
- package/src/postgres/internal/dsl.ts +285 -58
- package/src/postgres/internal/renderer.ts +11 -2
- package/src/postgres/internal/sql-expression-renderer.ts +60 -8
- package/src/postgres/renderer.ts +7 -2
- package/src/postgres/type.ts +4 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as Query from "../../internal/query.js"
|
|
2
|
+
import type * as Expression from "../../internal/scalar.js"
|
|
2
3
|
import { type RenderState } from "../../internal/dialect.js"
|
|
3
4
|
import { mysqlDialect } from "./dialect.js"
|
|
4
5
|
import { type Projection } from "../../internal/projections.js"
|
|
@@ -11,16 +12,23 @@ export interface MysqlRenderResult {
|
|
|
11
12
|
readonly sql: string
|
|
12
13
|
readonly params: readonly unknown[]
|
|
13
14
|
readonly projections: readonly Projection[]
|
|
15
|
+
readonly valueMappings?: Expression.DriverValueMappings
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface MysqlRenderOptions {
|
|
19
|
+
readonly valueMappings?: Expression.DriverValueMappings
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
/**
|
|
17
23
|
* Renders the current query AST into MySQL-shaped SQL plus bind parameters.
|
|
18
24
|
*/
|
|
19
25
|
export const renderMysqlPlan = <PlanValue extends Query.Plan.Any>(
|
|
20
|
-
plan: Query.DialectCompatiblePlan<PlanValue, "mysql"
|
|
26
|
+
plan: Query.DialectCompatiblePlan<PlanValue, "mysql">,
|
|
27
|
+
options: MysqlRenderOptions = {}
|
|
21
28
|
): MysqlRenderResult => {
|
|
22
29
|
const state: RenderState = {
|
|
23
30
|
params: [],
|
|
31
|
+
valueMappings: options.valueMappings,
|
|
24
32
|
ctes: [],
|
|
25
33
|
cteNames: new Set<string>()
|
|
26
34
|
}
|
|
@@ -32,6 +40,7 @@ export const renderMysqlPlan = <PlanValue extends Query.Plan.Any>(
|
|
|
32
40
|
return {
|
|
33
41
|
sql: rendered.sql,
|
|
34
42
|
params: state.params,
|
|
35
|
-
projections: rendered.projections
|
|
43
|
+
projections: rendered.projections,
|
|
44
|
+
valueMappings: state.valueMappings
|
|
36
45
|
}
|
|
37
46
|
}
|
|
@@ -5,6 +5,11 @@ import * as QueryAst from "../../internal/query-ast.js"
|
|
|
5
5
|
import type { RenderState, SqlDialect } from "../../internal/dialect.js"
|
|
6
6
|
import * as ExpressionAst from "../../internal/expression-ast.js"
|
|
7
7
|
import * as JsonPath from "../../internal/json/path.js"
|
|
8
|
+
import {
|
|
9
|
+
renderJsonSelectSql,
|
|
10
|
+
renderSelectSql,
|
|
11
|
+
toDriverValue
|
|
12
|
+
} from "../../internal/runtime/driver-value-mapping.js"
|
|
8
13
|
import { flattenSelection, type Projection } from "../../internal/projections.js"
|
|
9
14
|
import { type SelectionValue, validateAggregationSelection } from "../../internal/aggregation-validation.js"
|
|
10
15
|
import * as SchemaExpression from "../../internal/schema-expression.js"
|
|
@@ -313,6 +318,43 @@ const renderPostgresJsonValue = (
|
|
|
313
318
|
: `cast(${rendered} as jsonb)`
|
|
314
319
|
}
|
|
315
320
|
|
|
321
|
+
const expressionDriverContext = (
|
|
322
|
+
expression: Expression.Any,
|
|
323
|
+
state: RenderState,
|
|
324
|
+
dialect: SqlDialect
|
|
325
|
+
) => ({
|
|
326
|
+
dialect: dialect.name,
|
|
327
|
+
valueMappings: state.valueMappings,
|
|
328
|
+
dbType: expression[Expression.TypeId].dbType,
|
|
329
|
+
runtimeSchema: expression[Expression.TypeId].runtimeSchema,
|
|
330
|
+
driverValueMapping: expression[Expression.TypeId].driverValueMapping
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
const renderJsonInputExpression = (
|
|
334
|
+
expression: Expression.Any,
|
|
335
|
+
state: RenderState,
|
|
336
|
+
dialect: SqlDialect
|
|
337
|
+
): string =>
|
|
338
|
+
renderJsonSelectSql(
|
|
339
|
+
renderExpression(expression, state, dialect),
|
|
340
|
+
expressionDriverContext(expression, state, dialect)
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
const encodeArrayValues = (
|
|
344
|
+
values: readonly unknown[],
|
|
345
|
+
column: Table.AnyTable[typeof Table.TypeId]["fields"][string],
|
|
346
|
+
state: RenderState,
|
|
347
|
+
dialect: SqlDialect
|
|
348
|
+
): readonly unknown[] =>
|
|
349
|
+
values.map((value) =>
|
|
350
|
+
toDriverValue(value, {
|
|
351
|
+
dialect: dialect.name,
|
|
352
|
+
valueMappings: state.valueMappings,
|
|
353
|
+
dbType: column.metadata.dbType,
|
|
354
|
+
runtimeSchema: column.schema,
|
|
355
|
+
driverValueMapping: column.metadata.driverValueMapping
|
|
356
|
+
}))
|
|
357
|
+
|
|
316
358
|
const renderPostgresJsonKind = (
|
|
317
359
|
value: Expression.Any
|
|
318
360
|
): "json" | "jsonb" => value[Expression.TypeId].dbType.kind === "jsonb" ? "jsonb" : "json"
|
|
@@ -474,7 +516,7 @@ const renderJsonExpression = (
|
|
|
474
516
|
: []
|
|
475
517
|
const renderedEntries = entries.flatMap((entry) => [
|
|
476
518
|
dialect.renderLiteral(entry.key, state),
|
|
477
|
-
|
|
519
|
+
renderJsonInputExpression(entry.value, state, dialect)
|
|
478
520
|
])
|
|
479
521
|
if (dialect.name === "postgres") {
|
|
480
522
|
return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_object(${renderedEntries.join(", ")})`
|
|
@@ -488,7 +530,7 @@ const renderJsonExpression = (
|
|
|
488
530
|
const values = Array.isArray((ast as { readonly values?: readonly Expression.Any[] }).values)
|
|
489
531
|
? (ast as { readonly values: readonly Expression.Any[] }).values
|
|
490
532
|
: []
|
|
491
|
-
const renderedValues = values.map((value) =>
|
|
533
|
+
const renderedValues = values.map((value) => renderJsonInputExpression(value, state, dialect)).join(", ")
|
|
492
534
|
if (dialect.name === "postgres") {
|
|
493
535
|
return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_array(${renderedValues})`
|
|
494
536
|
}
|
|
@@ -502,7 +544,7 @@ const renderJsonExpression = (
|
|
|
502
544
|
return undefined
|
|
503
545
|
}
|
|
504
546
|
if (dialect.name === "postgres") {
|
|
505
|
-
return `to_json(${
|
|
547
|
+
return `to_json(${renderJsonInputExpression(base, state, dialect)})`
|
|
506
548
|
}
|
|
507
549
|
if (dialect.name === "mysql") {
|
|
508
550
|
return `cast(${renderExpression(base, state, dialect)} as json)`
|
|
@@ -513,7 +555,7 @@ const renderJsonExpression = (
|
|
|
513
555
|
return undefined
|
|
514
556
|
}
|
|
515
557
|
if (dialect.name === "postgres") {
|
|
516
|
-
return `to_jsonb(${
|
|
558
|
+
return `to_jsonb(${renderJsonInputExpression(base, state, dialect)})`
|
|
517
559
|
}
|
|
518
560
|
if (dialect.name === "mysql") {
|
|
519
561
|
return `cast(${renderExpression(base, state, dialect)} as json)`
|
|
@@ -767,7 +809,7 @@ const renderSelectionList = (
|
|
|
767
809
|
const flattened = flattenSelection(selection)
|
|
768
810
|
const projections = selectionProjections(selection)
|
|
769
811
|
const sql = flattened.map(({ expression, alias }) =>
|
|
770
|
-
`${renderExpression(expression, state, dialect)} as ${dialect.quoteIdentifier(alias)}`).join(", ")
|
|
812
|
+
`${renderSelectSql(renderExpression(expression, state, dialect), expressionDriverContext(expression, state, dialect))} as ${dialect.quoteIdentifier(alias)}`).join(", ")
|
|
771
813
|
return {
|
|
772
814
|
sql,
|
|
773
815
|
projections
|
|
@@ -888,14 +930,18 @@ export const renderQueryAst = (
|
|
|
888
930
|
const table = targetSource.source as Table.AnyTable
|
|
889
931
|
const fields = table[Table.TypeId].fields
|
|
890
932
|
const rendered = unnestSource.values.map((entry) =>
|
|
891
|
-
`cast(${dialect.renderLiteral(entry.values, state)} as ${renderCastType(dialect, fields[entry.columnName]!.metadata.dbType)}[])`
|
|
933
|
+
`cast(${dialect.renderLiteral(encodeArrayValues(entry.values, fields[entry.columnName]!, state, dialect), state)} as ${renderCastType(dialect, fields[entry.columnName]!.metadata.dbType)}[])`
|
|
892
934
|
).join(", ")
|
|
893
935
|
sql += ` (${columns}) select * from unnest(${rendered})`
|
|
894
936
|
} else {
|
|
895
937
|
const rowCount = unnestSource.values[0]?.values.length ?? 0
|
|
896
938
|
const rows = Array.from({ length: rowCount }, (_, index) =>
|
|
897
939
|
`(${unnestSource.values.map((entry) =>
|
|
898
|
-
dialect.renderLiteral(
|
|
940
|
+
dialect.renderLiteral(
|
|
941
|
+
entry.values[index],
|
|
942
|
+
state,
|
|
943
|
+
(targetSource.source as Table.AnyTable)[Table.TypeId].fields[entry.columnName]![Expression.TypeId]
|
|
944
|
+
)
|
|
899
945
|
).join(", ")})`
|
|
900
946
|
).join(", ")
|
|
901
947
|
sql += ` (${columns}) values ${rows}`
|
|
@@ -1267,7 +1313,7 @@ export const renderExpression = (
|
|
|
1267
1313
|
? dialect.quoteIdentifier(ast.columnName)
|
|
1268
1314
|
: `${dialect.quoteIdentifier(ast.tableName)}.${dialect.quoteIdentifier(ast.columnName)}`
|
|
1269
1315
|
case "literal":
|
|
1270
|
-
return dialect.renderLiteral(ast.value, state)
|
|
1316
|
+
return dialect.renderLiteral(ast.value, state, expression[Expression.TypeId])
|
|
1271
1317
|
case "excluded":
|
|
1272
1318
|
return dialect.name === "mysql"
|
|
1273
1319
|
? `values(${dialect.quoteIdentifier(ast.columnName)})`
|
package/src/mysql/renderer.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as CoreRenderer from "../internal/renderer.js"
|
|
2
|
+
import type * as Expression from "../internal/scalar.js"
|
|
2
3
|
import { renderMysqlPlan } from "./internal/renderer.js"
|
|
3
4
|
|
|
4
5
|
/** MySQL-specialized rendered query shape. */
|
|
@@ -8,12 +9,16 @@ export type RowOf<Value extends RenderedQuery<any>> = CoreRenderer.RowOf<Value>
|
|
|
8
9
|
/** MySQL-specialized renderer contract. */
|
|
9
10
|
export type Renderer = CoreRenderer.Renderer<"mysql">
|
|
10
11
|
|
|
12
|
+
export interface MakeOptions {
|
|
13
|
+
readonly valueMappings?: Expression.DriverValueMappings
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
export { TypeId } from "../internal/renderer.js"
|
|
12
17
|
export type { Projection } from "../internal/renderer.js"
|
|
13
18
|
|
|
14
19
|
/** Creates the built-in MySQL renderer. */
|
|
15
|
-
export const make = (): Renderer =>
|
|
16
|
-
CoreRenderer.make("mysql", renderMysqlPlan)
|
|
20
|
+
export const make = (options: MakeOptions = {}): Renderer =>
|
|
21
|
+
CoreRenderer.make("mysql", (plan) => renderMysqlPlan(plan, options))
|
|
17
22
|
|
|
18
23
|
/** Shared built-in MySQL renderer instance. */
|
|
19
24
|
export const mysql = make()
|
package/src/postgres/cast.ts
CHANGED
|
@@ -1,26 +1,41 @@
|
|
|
1
1
|
import type * as Expression from "../internal/scalar.js"
|
|
2
|
+
import type * as ExpressionAst from "../internal/expression-ast.js"
|
|
2
3
|
import type { ExpressionInput } from "../internal/query.js"
|
|
3
4
|
import { cast as postgresCast } from "./internal/dsl.js"
|
|
4
5
|
|
|
5
6
|
type CastInput = ExpressionInput
|
|
6
7
|
type CastTarget = Expression.DbType.Any
|
|
7
|
-
type
|
|
8
|
+
type CastNullability<Value extends CastInput> = Value extends Expression.Any
|
|
9
|
+
? Expression.NullabilityOf<Value>
|
|
10
|
+
: Value extends null ? "always" : "never"
|
|
11
|
+
type CastKind<Value extends CastInput> = Value extends Expression.Any
|
|
12
|
+
? Expression.KindOf<Value>
|
|
13
|
+
: "scalar"
|
|
14
|
+
type CastDependencies<Value extends CastInput> = Value extends Expression.Any
|
|
15
|
+
? Expression.DependenciesOf<Value>
|
|
16
|
+
: never
|
|
17
|
+
type CastExpression<
|
|
18
|
+
Value extends CastInput,
|
|
19
|
+
Target extends CastTarget
|
|
20
|
+
> = Expression.Scalar<
|
|
8
21
|
Expression.RuntimeOfDbType<Target>,
|
|
9
22
|
Target,
|
|
10
|
-
|
|
23
|
+
CastNullability<Value>,
|
|
11
24
|
Target["dialect"],
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
>
|
|
25
|
+
CastKind<Value>,
|
|
26
|
+
CastDependencies<Value>
|
|
27
|
+
> & {
|
|
28
|
+
readonly [ExpressionAst.TypeId]: ExpressionAst.CastNode<Value extends Expression.Any ? Value : Expression.Any, Target>
|
|
29
|
+
}
|
|
15
30
|
|
|
16
31
|
const to: {
|
|
17
32
|
<Value extends CastInput, Target extends CastTarget>(
|
|
18
33
|
value: Value,
|
|
19
34
|
target: Target
|
|
20
|
-
): CastExpression<Target>
|
|
35
|
+
): CastExpression<Value, Target>
|
|
21
36
|
<Target extends CastTarget>(
|
|
22
37
|
target: Target
|
|
23
|
-
): <Value extends CastInput>(value: Value) => CastExpression<Target>
|
|
38
|
+
): <Value extends CastInput>(value: Value) => CastExpression<Value, Target>
|
|
24
39
|
} = ((...args: [CastInput, CastTarget] | [CastTarget]) =>
|
|
25
40
|
args.length === 1
|
|
26
41
|
? ((value: CastInput) => postgresCast(value as never, args[0] as never))
|
package/src/postgres/column.ts
CHANGED
|
@@ -165,6 +165,7 @@ export const unique = BaseColumn.unique
|
|
|
165
165
|
const default_ = BaseColumn.default_
|
|
166
166
|
export const generated = BaseColumn.generated
|
|
167
167
|
export const ddlType = BaseColumn.ddlType
|
|
168
|
+
export const driverValueMapping = BaseColumn.driverValueMapping
|
|
168
169
|
export const array = BaseColumn.array
|
|
169
170
|
export const identityAlways = BaseColumn.identityAlways
|
|
170
171
|
export const identityByDefault = BaseColumn.identityByDefault
|
package/src/postgres/executor.ts
CHANGED
|
@@ -5,6 +5,7 @@ import * as Stream from "effect/Stream"
|
|
|
5
5
|
import * as CoreExecutor from "../internal/executor.js"
|
|
6
6
|
import * as CoreQuery from "../internal/query.js"
|
|
7
7
|
import * as CoreRenderer from "../internal/renderer.js"
|
|
8
|
+
import type * as Expression from "../internal/scalar.js"
|
|
8
9
|
import { renderPostgresPlan } from "./internal/renderer.js"
|
|
9
10
|
import {
|
|
10
11
|
narrowPostgresDriverErrorForReadQuery,
|
|
@@ -28,6 +29,7 @@ export interface MakeOptions<Error = never, Context = never> {
|
|
|
28
29
|
readonly renderer?: Renderer
|
|
29
30
|
readonly driver?: Driver<Error, Context>
|
|
30
31
|
readonly driverMode?: CoreExecutor.DriverMode
|
|
32
|
+
readonly valueMappings?: Expression.DriverValueMappings
|
|
31
33
|
}
|
|
32
34
|
/** Standard composed error shape for Postgres executors. */
|
|
33
35
|
export type PostgresExecutorError = PostgresDriverError | RowDecodeError
|
|
@@ -67,7 +69,8 @@ const fromDriver = <
|
|
|
67
69
|
>(
|
|
68
70
|
renderer: Renderer,
|
|
69
71
|
sqlDriver: Driver<Error, Context>,
|
|
70
|
-
driverMode: CoreExecutor.DriverMode = "raw"
|
|
72
|
+
driverMode: CoreExecutor.DriverMode = "raw",
|
|
73
|
+
valueMappings?: Expression.DriverValueMappings
|
|
71
74
|
): QueryExecutor<Context> => ({
|
|
72
75
|
dialect: "postgres",
|
|
73
76
|
execute(plan) {
|
|
@@ -76,7 +79,7 @@ const fromDriver = <
|
|
|
76
79
|
Effect.flatMap(
|
|
77
80
|
sqlDriver.execute(rendered),
|
|
78
81
|
(rows) => Effect.try({
|
|
79
|
-
try: () => CoreExecutor.decodeRows(rendered, plan, rows, { driverMode }),
|
|
82
|
+
try: () => CoreExecutor.decodeRows(rendered, plan, rows, { driverMode, valueMappings }),
|
|
80
83
|
catch: (error) => error as RowDecodeError
|
|
81
84
|
})
|
|
82
85
|
),
|
|
@@ -97,7 +100,7 @@ const fromDriver = <
|
|
|
97
100
|
Stream.mapChunksEffect(
|
|
98
101
|
sqlDriver.stream(rendered),
|
|
99
102
|
(rows) => Effect.try({
|
|
100
|
-
try: () => CoreExecutor.decodeChunk(rendered, plan, rows, { driverMode }),
|
|
103
|
+
try: () => CoreExecutor.decodeChunk(rendered, plan, rows, { driverMode, valueMappings }),
|
|
101
104
|
catch: (error) => error as RowDecodeError
|
|
102
105
|
})
|
|
103
106
|
),
|
|
@@ -135,6 +138,7 @@ export function make(
|
|
|
135
138
|
options: {
|
|
136
139
|
readonly renderer?: Renderer
|
|
137
140
|
readonly driverMode?: CoreExecutor.DriverMode
|
|
141
|
+
readonly valueMappings?: Expression.DriverValueMappings
|
|
138
142
|
}
|
|
139
143
|
): QueryExecutor<SqlClient.SqlClient>
|
|
140
144
|
export function make<Error = never, Context = never>(
|
|
@@ -142,15 +146,26 @@ export function make<Error = never, Context = never>(
|
|
|
142
146
|
readonly renderer?: Renderer
|
|
143
147
|
readonly driver: Driver<Error, Context>
|
|
144
148
|
readonly driverMode?: CoreExecutor.DriverMode
|
|
149
|
+
readonly valueMappings?: Expression.DriverValueMappings
|
|
145
150
|
}
|
|
146
151
|
): QueryExecutor<Context>
|
|
147
152
|
export function make<Error = never, Context = never>(
|
|
148
153
|
options: MakeOptions<Error, Context> = {}
|
|
149
154
|
): QueryExecutor<any> {
|
|
150
155
|
if (options.driver) {
|
|
151
|
-
return fromDriver(
|
|
156
|
+
return fromDriver(
|
|
157
|
+
options.renderer ?? CoreRenderer.make("postgres", (plan) => renderPostgresPlan(plan, { valueMappings: options.valueMappings })),
|
|
158
|
+
options.driver,
|
|
159
|
+
options.driverMode,
|
|
160
|
+
options.valueMappings
|
|
161
|
+
)
|
|
152
162
|
}
|
|
153
|
-
return fromDriver(
|
|
163
|
+
return fromDriver(
|
|
164
|
+
options.renderer ?? CoreRenderer.make("postgres", (plan) => renderPostgresPlan(plan, { valueMappings: options.valueMappings })),
|
|
165
|
+
sqlClientDriver(),
|
|
166
|
+
options.driverMode,
|
|
167
|
+
options.valueMappings
|
|
168
|
+
)
|
|
154
169
|
}
|
|
155
170
|
|
|
156
171
|
/** Creates a Postgres-specialized executor from a typed implementation callback. */
|
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
import type { RenderState, SqlDialect } from "../../internal/dialect.js"
|
|
1
|
+
import type { RenderState, RenderValueContext, SqlDialect } from "../../internal/dialect.js"
|
|
2
|
+
import { toDriverValue } from "../../internal/runtime/driver-value-mapping.js"
|
|
2
3
|
|
|
3
4
|
const quoteIdentifier = (value: string): string => `"${value.replaceAll("\"", "\"\"")}"`
|
|
4
5
|
|
|
5
|
-
const renderLiteral = (value: unknown, state: RenderState): string => {
|
|
6
|
-
|
|
6
|
+
const renderLiteral = (value: unknown, state: RenderState, context: RenderValueContext = {}): string => {
|
|
7
|
+
const driverValue = toDriverValue(value, {
|
|
8
|
+
dialect: "postgres",
|
|
9
|
+
valueMappings: state.valueMappings,
|
|
10
|
+
...context
|
|
11
|
+
})
|
|
12
|
+
if (driverValue === null) {
|
|
7
13
|
return "null"
|
|
8
14
|
}
|
|
9
|
-
if (typeof
|
|
10
|
-
return
|
|
15
|
+
if (typeof driverValue === "boolean") {
|
|
16
|
+
return driverValue ? "true" : "false"
|
|
11
17
|
}
|
|
12
|
-
state.params.push(
|
|
18
|
+
state.params.push(driverValue)
|
|
13
19
|
return `$${state.params.length}`
|
|
14
20
|
}
|
|
15
21
|
|