@effect/sql-mssql 4.0.0-beta.7 → 4.0.0-beta.70

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,7 +1,34 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Microsoft SQL Server client implementation for Effect SQL, backed by the
3
+ * `tedious` driver.
4
+ *
5
+ * This module provides the `MssqlClient` service and layers that also satisfy
6
+ * the generic `SqlClient` service. It is intended for server applications,
7
+ * background workers, migrations, and tests that need SQL Server query
8
+ * compilation, Tedious parameter typing, scoped connection management,
9
+ * transactions, and typed stored procedure calls.
10
+ *
11
+ * Clients own a scoped pool of Tedious connections and validate startup with
12
+ * `SELECT 1`. Regular queries borrow a pooled connection per operation, while
13
+ * transactions keep one pooled connection for their lifetime and use SQL Server
14
+ * savepoints for nested transactions. Long-running transactions therefore
15
+ * reduce available pool capacity; size `maxConnections`, `connectionTTL`, and
16
+ * `connectTimeout` accordingly.
17
+ *
18
+ * Tedious permits one active request per connection. This client compiles
19
+ * statements with named `@1`-style parameters, maps Effect SQL primitive values
20
+ * to Tedious `DataType`s unless `param` is used, and does not implement
21
+ * streaming queries. Be deliberate about TLS options: `encrypt` defaults to
22
+ * `false` and `trustServerCertificate` defaults to `true` unless overridden.
23
+ * Stored procedure calls go through `callProcedure`; define input and output
24
+ * parameters with the `Procedure` and `Parameter` helpers so Tedious receives
25
+ * the correct data types and output values can be collected from `returnValue`
26
+ * events.
27
+ *
28
+ * @since 4.0.0
3
29
  */
4
30
  import * as Config from "effect/Config"
31
+ import * as Context from "effect/Context"
5
32
  import * as Duration from "effect/Duration"
6
33
  import * as Effect from "effect/Effect"
7
34
  import { identity } from "effect/Function"
@@ -9,12 +36,23 @@ import * as Layer from "effect/Layer"
9
36
  import * as Pool from "effect/Pool"
10
37
  import * as Redacted from "effect/Redacted"
11
38
  import * as Scope from "effect/Scope"
12
- import * as ServiceMap from "effect/ServiceMap"
13
39
  import * as Stream from "effect/Stream"
14
40
  import * as Reactivity from "effect/unstable/reactivity/Reactivity"
15
41
  import * as Client from "effect/unstable/sql/SqlClient"
16
42
  import type { Connection } from "effect/unstable/sql/SqlConnection"
17
- import { SqlError } from "effect/unstable/sql/SqlError"
43
+ import {
44
+ AuthenticationError,
45
+ AuthorizationError,
46
+ ConnectionError,
47
+ ConstraintError,
48
+ DeadlockError,
49
+ LockTimeoutError,
50
+ SerializationError,
51
+ SqlError,
52
+ SqlSyntaxError,
53
+ UniqueViolation,
54
+ UnknownError
55
+ } from "effect/unstable/sql/SqlError"
18
56
  import * as Statement from "effect/unstable/sql/Statement"
19
57
  import * as Tedious from "tedious"
20
58
  import type { ConnectionOptions } from "tedious/lib/connection.ts"
@@ -28,21 +66,116 @@ const ATTR_DB_NAMESPACE = "db.namespace"
28
66
  const ATTR_SERVER_ADDRESS = "server.address"
29
67
  const ATTR_SERVER_PORT = "server.port"
30
68
 
69
+ const mssqlNumberFromCause = (cause: unknown): number | undefined => {
70
+ if (typeof cause !== "object" || cause === null || !("number" in cause)) {
71
+ return undefined
72
+ }
73
+ const number = cause.number
74
+ return typeof number === "number" ? number : undefined
75
+ }
76
+
77
+ const mssqlConnectionErrorCodes = new Set([233, 10054])
78
+ const mssqlAuthenticationErrorCodes = new Set([4060, 18452, 18456])
79
+ const mssqlAuthorizationErrorCodes = new Set([229, 230, 262, 297, 300])
80
+ const mssqlSyntaxErrorCodes = new Set([102, 207, 208, 2714])
81
+ const mssqlConstraintErrorCodes = new Set([515, 547])
82
+
83
+ const UNKNOWN_CONSTRAINT = "unknown"
84
+
85
+ const normalizeConstraintIdentifier = (identifier: unknown): string => {
86
+ if (typeof identifier !== "string") {
87
+ return UNKNOWN_CONSTRAINT
88
+ }
89
+ const trimmed = identifier.trim()
90
+ return trimmed.length === 0 ? UNKNOWN_CONSTRAINT : trimmed
91
+ }
92
+
93
+ const mssqlCauseProperty = (cause: unknown, property: "constraint" | "message"): unknown => {
94
+ if (typeof cause !== "object" || cause === null || !(property in cause)) {
95
+ return undefined
96
+ }
97
+ return (cause as Record<string, unknown>)[property]
98
+ }
99
+
100
+ const mssqlUniqueViolationConstraintFromMessage = (number: 2601 | 2627, message: unknown): string => {
101
+ if (typeof message !== "string") {
102
+ return UNKNOWN_CONSTRAINT
103
+ }
104
+ const match = number === 2627 ?
105
+ /\bconstraint\s+'([^']*)'/i.exec(message) :
106
+ /\bunique index\s+'([^']*)'/i.exec(message)
107
+ return match === null ? UNKNOWN_CONSTRAINT : normalizeConstraintIdentifier(match[1])
108
+ }
109
+
110
+ const mssqlUniqueViolationConstraintFromCause = (number: 2601 | 2627, cause: unknown): string => {
111
+ const constraint = normalizeConstraintIdentifier(mssqlCauseProperty(cause, "constraint"))
112
+ if (constraint !== UNKNOWN_CONSTRAINT) {
113
+ return constraint
114
+ }
115
+ return mssqlUniqueViolationConstraintFromMessage(number, mssqlCauseProperty(cause, "message"))
116
+ }
117
+
118
+ const classifyError = (
119
+ cause: unknown,
120
+ message: string,
121
+ operation: string,
122
+ fallback: "connection" | "unknown" = "unknown"
123
+ ) => {
124
+ const props = { cause, message, operation }
125
+ const number = mssqlNumberFromCause(cause)
126
+ if (number !== undefined) {
127
+ if (mssqlConnectionErrorCodes.has(number)) {
128
+ return new ConnectionError(props)
129
+ }
130
+ if (mssqlAuthenticationErrorCodes.has(number)) {
131
+ return new AuthenticationError(props)
132
+ }
133
+ if (mssqlAuthorizationErrorCodes.has(number)) {
134
+ return new AuthorizationError(props)
135
+ }
136
+ if (mssqlSyntaxErrorCodes.has(number)) {
137
+ return new SqlSyntaxError(props)
138
+ }
139
+ if (number === 2601 || number === 2627) {
140
+ return new UniqueViolation({ ...props, constraint: mssqlUniqueViolationConstraintFromCause(number, cause) })
141
+ }
142
+ if (mssqlConstraintErrorCodes.has(number)) {
143
+ return new ConstraintError(props)
144
+ }
145
+ if (number === 1205) {
146
+ return new DeadlockError(props)
147
+ }
148
+ if (number === 3960) {
149
+ return new SerializationError(props)
150
+ }
151
+ if (number === 1222) {
152
+ return new LockTimeoutError(props)
153
+ }
154
+ }
155
+ return fallback === "connection" ? new ConnectionError(props) : new UnknownError(props)
156
+ }
157
+
31
158
  /**
32
- * @category type ids
33
- * @since 1.0.0
159
+ * Runtime type identifier used to mark `MssqlClient` values.
160
+ *
161
+ * @category type IDs
162
+ * @since 4.0.0
34
163
  */
35
164
  export const TypeId: unique symbol = Symbol.for("@effect/sql-mssql/MssqlClient")
36
165
 
37
166
  /**
38
- * @category type ids
39
- * @since 1.0.0
167
+ * Type-level identifier used to mark `MssqlClient` values.
168
+ *
169
+ * @category type IDs
170
+ * @since 4.0.0
40
171
  */
41
172
  export type TypeId = typeof TypeId
42
173
 
43
174
  /**
175
+ * Microsoft SQL Server client service, extending `SqlClient` with typed parameter fragments and stored procedure calls.
176
+ *
44
177
  * @category models
45
- * @since 1.0.0
178
+ * @since 4.0.0
46
179
  */
47
180
  export interface MssqlClient extends Client.SqlClient {
48
181
  readonly [TypeId]: TypeId
@@ -65,14 +198,18 @@ export interface MssqlClient extends Client.SqlClient {
65
198
  }
66
199
 
67
200
  /**
201
+ * Context tag used to access the `MssqlClient` service.
202
+ *
68
203
  * @category tags
69
- * @since 1.0.0
204
+ * @since 4.0.0
70
205
  */
71
- export const MssqlClient = ServiceMap.Service<MssqlClient>("@effect/sql-mssql/MssqlClient")
206
+ export const MssqlClient = Context.Service<MssqlClient>("@effect/sql-mssql/MssqlClient")
72
207
 
73
208
  /**
209
+ * Configuration for a Microsoft SQL Server client, including connection, authentication, pool, parameter type, span attribute, and query/result name transform options.
210
+ *
74
211
  * @category models
75
- * @since 1.0.0
212
+ * @since 4.0.0
76
213
  */
77
214
  export interface MssqlClientConfig {
78
215
  readonly domain?: string | undefined
@@ -111,14 +248,18 @@ interface MssqlConnection extends Connection {
111
248
  readonly rollback: (name?: string) => Effect.Effect<void, SqlError>
112
249
  }
113
250
 
114
- const TransactionConnection = Client.TransactionConnection as unknown as ServiceMap.Service<
251
+ const TransactionConnection = Client.TransactionConnection as unknown as (clientId: number) => Context.Service<
115
252
  readonly [conn: MssqlConnection, counter: number],
116
253
  readonly [conn: MssqlConnection, counter: number]
117
254
  >
118
255
 
256
+ let clientIdCounter = 0
257
+
119
258
  /**
259
+ * Creates a scoped Microsoft SQL Server client backed by a connection pool, with transaction and stored procedure support. Streaming queries are not implemented.
260
+ *
120
261
  * @category constructors
121
- * @since 1.0.0
262
+ * @since 4.0.0
122
263
  */
123
264
  export const make = (
124
265
  options: MssqlClientConfig
@@ -174,7 +315,9 @@ export const make = (
174
315
  yield* Effect.callback<void, SqlError>((resume) => {
175
316
  conn.connect((cause) => {
176
317
  if (cause) {
177
- resume(Effect.fail(new SqlError({ cause, message: "Failed to connect" })))
318
+ resume(
319
+ Effect.fail(new SqlError({ reason: classifyError(cause, "Failed to connect", "connect", "connection") }))
320
+ )
178
321
  } else {
179
322
  resume(Effect.void)
180
323
  }
@@ -189,7 +332,9 @@ export const make = (
189
332
  Effect.callback<any, SqlError>((resume) => {
190
333
  const req = new Tedious.Request(sql, (cause, _rowCount, result) => {
191
334
  if (cause) {
192
- resume(Effect.fail(new SqlError({ cause, message: "Failed to execute statement" })))
335
+ resume(
336
+ Effect.fail(new SqlError({ reason: classifyError(cause, "Failed to execute statement", "execute") }))
337
+ )
193
338
  return
194
339
  }
195
340
 
@@ -232,7 +377,9 @@ export const make = (
232
377
  escape(procedure.name),
233
378
  (cause, _, rows) => {
234
379
  if (cause) {
235
- resume(Effect.fail(new SqlError({ cause, message: "Failed to execute statement" })))
380
+ resume(
381
+ Effect.fail(new SqlError({ reason: classifyError(cause, "Failed to execute statement", "execute") }))
382
+ )
236
383
  } else {
237
384
  rows = rowsToObjects(rows)
238
385
  if (transformRows) {
@@ -291,7 +438,13 @@ export const make = (
291
438
  begin: Effect.callback<void, SqlError>((resume) => {
292
439
  conn.beginTransaction((cause) => {
293
440
  if (cause) {
294
- resume(Effect.fail(new SqlError({ cause, message: "Failed to begin transaction" })))
441
+ resume(
442
+ Effect.fail(
443
+ new SqlError({
444
+ reason: classifyError(cause, "Failed to begin transaction", "beginTransaction")
445
+ })
446
+ )
447
+ )
295
448
  } else {
296
449
  resume(Effect.void)
297
450
  }
@@ -300,7 +453,13 @@ export const make = (
300
453
  commit: Effect.callback<void, SqlError>((resume) => {
301
454
  conn.commitTransaction((cause) => {
302
455
  if (cause) {
303
- resume(Effect.fail(new SqlError({ cause, message: "Failed to commit transaction" })))
456
+ resume(
457
+ Effect.fail(
458
+ new SqlError({
459
+ reason: classifyError(cause, "Failed to commit transaction", "commitTransaction")
460
+ })
461
+ )
462
+ )
304
463
  } else {
305
464
  resume(Effect.void)
306
465
  }
@@ -310,7 +469,11 @@ export const make = (
310
469
  Effect.callback<void, SqlError>((resume) => {
311
470
  conn.saveTransaction((cause) => {
312
471
  if (cause) {
313
- resume(Effect.fail(new SqlError({ cause, message: "Failed to create savepoint" })))
472
+ resume(
473
+ Effect.fail(
474
+ new SqlError({ reason: classifyError(cause, "Failed to create savepoint", "createSavepoint") })
475
+ )
476
+ )
314
477
  } else {
315
478
  resume(Effect.void)
316
479
  }
@@ -320,7 +483,13 @@ export const make = (
320
483
  Effect.callback<void, SqlError>((resume) => {
321
484
  conn.rollbackTransaction((cause) => {
322
485
  if (cause) {
323
- resume(Effect.fail(new SqlError({ cause, message: "Failed to rollback transaction" })))
486
+ resume(
487
+ Effect.fail(
488
+ new SqlError({
489
+ reason: classifyError(cause, "Failed to rollback transaction", "rollbackTransaction")
490
+ })
491
+ )
492
+ )
324
493
  } else {
325
494
  resume(Effect.void)
326
495
  }
@@ -349,22 +518,29 @@ export const make = (
349
518
 
350
519
  yield* Pool.get(pool).pipe(
351
520
  Effect.tap((connection) => connection.executeUnprepared("SELECT 1", [], undefined)),
352
- Effect.mapError(({ cause }) => new SqlError({ cause, message: "MssqlClient: Failed to connect" })),
521
+ Effect.mapError((cause) =>
522
+ new SqlError({ reason: classifyError(cause, "MssqlClient: Failed to connect", "connect", "connection") })
523
+ ),
353
524
  Effect.scoped,
354
525
  Effect.timeoutOrElse({
355
526
  duration: options.connectTimeout ?? Duration.seconds(5),
356
- onTimeout: () =>
527
+ orElse: () =>
357
528
  Effect.fail(
358
529
  new SqlError({
359
- message: "MssqlClient: Connection timeout",
360
- cause: new Error("connection timeout")
530
+ reason: new ConnectionError({
531
+ message: "MssqlClient: Connection timeout",
532
+ cause: new Error("connection timeout"),
533
+ operation: "connect"
534
+ })
361
535
  })
362
536
  )
363
537
  })
364
538
  )
365
539
 
540
+ const transactionService = TransactionConnection(clientIdCounter++)
541
+
366
542
  const withTransaction = Client.makeWithTransaction({
367
- transactionService: TransactionConnection,
543
+ transactionService,
368
544
  spanAttributes,
369
545
  acquireConnection: Effect.gen(function*() {
370
546
  const scope = Scope.makeUnsafe()
@@ -382,6 +558,7 @@ export const make = (
382
558
  yield* Client.make({
383
559
  acquirer: Pool.get(pool),
384
560
  compiler,
561
+ transactionService: transactionService as any,
385
562
  spanAttributes,
386
563
  transformRows
387
564
  }),
@@ -426,42 +603,48 @@ export const make = (
426
603
  })
427
604
 
428
605
  /**
606
+ * Creates a layer from a `Config`-wrapped SQL Server client configuration, providing both `MssqlClient` and `SqlClient`.
607
+ *
429
608
  * @category layers
430
- * @since 1.0.0
609
+ * @since 4.0.0
431
610
  */
432
611
  export const layerConfig: (
433
612
  config: Config.Wrap<MssqlClientConfig>
434
613
  ) => Layer.Layer<Client.SqlClient | MssqlClient, Config.ConfigError | SqlError> = (
435
614
  config: Config.Wrap<MssqlClientConfig>
436
615
  ): Layer.Layer<Client.SqlClient | MssqlClient, Config.ConfigError | SqlError> =>
437
- Layer.effectServices(
438
- Config.unwrap(config).asEffect().pipe(
616
+ Layer.effectContext(
617
+ Config.unwrap(config).pipe(
439
618
  Effect.flatMap(make),
440
619
  Effect.map((client) =>
441
- ServiceMap.make(MssqlClient, client).pipe(
442
- ServiceMap.add(Client.SqlClient, client)
620
+ Context.make(MssqlClient, client).pipe(
621
+ Context.add(Client.SqlClient, client)
443
622
  )
444
623
  )
445
624
  )
446
625
  ).pipe(Layer.provide(Reactivity.layer))
447
626
 
448
627
  /**
628
+ * Creates a layer from a concrete SQL Server client configuration, providing both `MssqlClient` and `SqlClient`.
629
+ *
449
630
  * @category layers
450
- * @since 1.0.0
631
+ * @since 4.0.0
451
632
  */
452
633
  export const layer = (
453
634
  config: MssqlClientConfig
454
635
  ): Layer.Layer<Client.SqlClient | MssqlClient, never | SqlError> =>
455
- Layer.effectServices(
636
+ Layer.effectContext(
456
637
  Effect.map(make(config), (client) =>
457
- ServiceMap.make(MssqlClient, client).pipe(
458
- ServiceMap.add(Client.SqlClient, client)
638
+ Context.make(MssqlClient, client).pipe(
639
+ Context.add(Client.SqlClient, client)
459
640
  ))
460
641
  ).pipe(Layer.provide(Reactivity.layer))
461
642
 
462
643
  /**
644
+ * Creates the SQL Server statement compiler, using `@1`-style placeholders, bracket-escaped identifiers, and SQL Server `OUTPUT INSERTED` returning clauses.
645
+ *
463
646
  * @category compiler
464
- * @since 1.0.0
647
+ * @since 4.0.0
465
648
  */
466
649
  export const makeCompiler = (transform?: (_: string) => string) =>
467
650
  Statement.makeCompiler<MssqlCustom>({
@@ -510,7 +693,10 @@ function numberToParamName(n: number) {
510
693
  }
511
694
 
512
695
  /**
513
- * @since 1.0.0
696
+ * Default mapping from Effect SQL primitive value kinds to Tedious SQL Server parameter data types.
697
+ *
698
+ * @category configuration
699
+ * @since 4.0.0
514
700
  */
515
701
  export const defaultParameterTypes: Record<Statement.PrimitiveKind, DataType> = {
516
702
  string: Tedious.TYPES.VarChar,
@@ -1,5 +1,26 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Utilities for applying Effect SQL migrations to Microsoft SQL Server.
3
+ *
4
+ * This module re-exports the shared `Migrator` loaders and error types, then
5
+ * provides `run` and `layer` helpers for applying ordered migrations through
6
+ * the current SQL Server `SqlClient`. It is typically used at application
7
+ * startup, during deployment, in integration tests that provision temporary SQL
8
+ * Server databases, or in layer graphs that need the database schema to be
9
+ * current before dependent services are acquired.
10
+ *
11
+ * Applied migrations are stored in `effect_sql_migrations` by default and use
12
+ * the shared `<id>_<name>` loader convention. Only migrations with ids greater
13
+ * than the latest recorded id are run, so avoid inserting older migration ids
14
+ * after a later migration has reached production. SQL Server migrations run
15
+ * inside the shared migrator transaction and this adapter does not add a
16
+ * dialect-specific table lock, so coordinate concurrent startup runners and
17
+ * write T-SQL that is valid inside an explicit transaction. Remember that `GO`
18
+ * is a client-side batch separator rather than a T-SQL statement, and split
19
+ * migrations when SQL Server requires an object definition such as `CREATE
20
+ * VIEW`, `CREATE PROCEDURE`, or `CREATE TRIGGER` to start its own batch. This
21
+ * adapter also does not emit SQL Server schema dumps for `schemaDirectory`.
22
+ *
23
+ * @since 4.0.0
3
24
  */
4
25
  import type * as Effect from "effect/Effect"
5
26
  import * as Layer from "effect/Layer"
@@ -8,13 +29,15 @@ import type * as Client from "effect/unstable/sql/SqlClient"
8
29
  import type { SqlError } from "effect/unstable/sql/SqlError"
9
30
 
10
31
  /**
11
- * @since 1.0.0
32
+ * @since 4.0.0
12
33
  */
13
34
  export * from "effect/unstable/sql/Migrator"
14
35
 
15
36
  /**
16
- * @category constructor
17
- * @since 1.0.0
37
+ * Runs SQL migrations using the configured `SqlClient`, returning the migrations that were applied.
38
+ *
39
+ * @category constructors
40
+ * @since 4.0.0
18
41
  */
19
42
  export const run: <R>(
20
43
  options: Migrator.MigratorOptions<R>
@@ -25,8 +48,10 @@ export const run: <R>(
25
48
  > = Migrator.make({})
26
49
 
27
50
  /**
51
+ * Creates a layer that runs the configured SQL migrations during layer construction.
52
+ *
28
53
  * @category layers
29
- * @since 1.0.0
54
+ * @since 4.0.0
30
55
  */
31
56
  export const layer = <R>(
32
57
  options: Migrator.MigratorOptions<R>
package/src/Parameter.ts CHANGED
@@ -1,25 +1,51 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Typed metadata for SQL Server stored procedure parameters.
3
+ *
4
+ * This module records the bare parameter name, Tedious `DataType`, Tedious
5
+ * `ParameterOptions`, and phantom TypeScript value type used by
6
+ * `Procedure.param` and `Procedure.outputParam`. `MssqlClient.call` later
7
+ * forwards input parameters to Tedious with `Request.addParameter` and output
8
+ * parameters with `Request.addOutputParameter`, so names should match the
9
+ * stored procedure parameter name without a leading `@`.
10
+ *
11
+ * Use these values when defining stored procedures that need explicit SQL
12
+ * Server parameter metadata, such as sized strings or binary values, decimal
13
+ * precision/scale, table-valued parameters, and output parameters. The generic
14
+ * type parameter is only a compile-time guide for the value record accepted by
15
+ * `Procedure.compile`; Tedious still validates and encodes the runtime value.
16
+ * In particular, TVP values must use Tedious' table shape with `name`,
17
+ * optional `schema`, `columns`, and `rows`, and output parameters are registered
18
+ * with no initial value, so SQL Server input-output parameters need separate
19
+ * care rather than assuming an output parameter is populated from compiled
20
+ * input values.
21
+ *
22
+ * @since 4.0.0
3
23
  */
4
24
  import { identity } from "effect/Function"
5
25
  import type { DataType } from "tedious/lib/data-type.ts"
6
26
  import type { ParameterOptions } from "tedious/lib/request.ts"
7
27
 
8
28
  /**
29
+ * Runtime type identifier used to mark SQL Server stored procedure parameter metadata.
30
+ *
9
31
  * @category type id
10
- * @since 1.0.0
32
+ * @since 4.0.0
11
33
  */
12
34
  export const TypeId: TypeId = "~@effect/sql-mssql/Parameter"
13
35
 
14
36
  /**
37
+ * Type-level identifier used to mark SQL Server stored procedure parameter metadata.
38
+ *
15
39
  * @category type id
16
- * @since 1.0.0
40
+ * @since 4.0.0
17
41
  */
18
42
  export type TypeId = "~@effect/sql-mssql/Parameter"
19
43
 
20
44
  /**
21
- * @category model
22
- * @since 1.0.0
45
+ * Metadata for a SQL Server stored procedure parameter, including its name, Tedious data type, options, and phantom value type.
46
+ *
47
+ * @category models
48
+ * @since 4.0.0
23
49
  */
24
50
  export interface Parameter<out A> {
25
51
  readonly [TypeId]: (_: never) => A
@@ -30,8 +56,10 @@ export interface Parameter<out A> {
30
56
  }
31
57
 
32
58
  /**
33
- * @category constructor
34
- * @since 1.0.0
59
+ * Creates typed metadata for a SQL Server stored procedure parameter.
60
+ *
61
+ * @category constructors
62
+ * @since 4.0.0
35
63
  */
36
64
  export const make = <A>(
37
65
  name: string,