effect-start 0.26.0 → 0.27.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 (58) hide show
  1. package/package.json +4 -2
  2. package/src/Entity.ts +6 -6
  3. package/src/FileRouterCodegen.ts +4 -4
  4. package/src/FileSystem.ts +4 -8
  5. package/src/RouteHook.ts +1 -1
  6. package/src/RouteSse.ts +3 -3
  7. package/src/SqlIntrospect.ts +2 -2
  8. package/src/Start.ts +102 -2
  9. package/src/Values.ts +11 -0
  10. package/src/bun/BunRoute.ts +1 -1
  11. package/src/bun/BunRuntime.ts +5 -5
  12. package/src/hyper/HyperHtml.ts +11 -7
  13. package/src/hyper/jsx.d.ts +1 -1
  14. package/src/lint/plugin.js +174 -4
  15. package/src/sql/SqlClient.ts +355 -0
  16. package/src/sql/bun/index.ts +117 -50
  17. package/src/sql/index.ts +1 -1
  18. package/src/sql/libsql/index.ts +91 -77
  19. package/src/sql/libsql/libsql.d.ts +4 -1
  20. package/src/sql/mssql/index.ts +141 -108
  21. package/src/sql/mssql/mssql.d.ts +1 -0
  22. package/src/testing/TestLogger.ts +4 -4
  23. package/src/x/tailwind/compile.ts +6 -14
  24. package/src/console/Console.ts +0 -42
  25. package/src/console/ConsoleErrors.ts +0 -213
  26. package/src/console/ConsoleLogger.ts +0 -56
  27. package/src/console/ConsoleMetrics.ts +0 -72
  28. package/src/console/ConsoleProcess.ts +0 -59
  29. package/src/console/ConsoleStore.ts +0 -187
  30. package/src/console/ConsoleTracer.ts +0 -107
  31. package/src/console/Simulation.ts +0 -814
  32. package/src/console/console.html +0 -340
  33. package/src/console/index.ts +0 -3
  34. package/src/console/routes/errors/route.tsx +0 -97
  35. package/src/console/routes/fiberDetail.tsx +0 -54
  36. package/src/console/routes/fibers/route.tsx +0 -45
  37. package/src/console/routes/git/route.tsx +0 -64
  38. package/src/console/routes/layout.tsx +0 -4
  39. package/src/console/routes/logs/route.tsx +0 -77
  40. package/src/console/routes/metrics/route.tsx +0 -36
  41. package/src/console/routes/route.tsx +0 -8
  42. package/src/console/routes/routes/route.tsx +0 -30
  43. package/src/console/routes/services/route.tsx +0 -21
  44. package/src/console/routes/system/route.tsx +0 -43
  45. package/src/console/routes/traceDetail.tsx +0 -22
  46. package/src/console/routes/traces/route.tsx +0 -81
  47. package/src/console/routes/tree.ts +0 -30
  48. package/src/console/ui/Errors.tsx +0 -76
  49. package/src/console/ui/Fibers.tsx +0 -321
  50. package/src/console/ui/Git.tsx +0 -182
  51. package/src/console/ui/Logs.tsx +0 -46
  52. package/src/console/ui/Metrics.tsx +0 -78
  53. package/src/console/ui/Routes.tsx +0 -125
  54. package/src/console/ui/Services.tsx +0 -273
  55. package/src/console/ui/Shell.tsx +0 -62
  56. package/src/console/ui/System.tsx +0 -131
  57. package/src/console/ui/Traces.tsx +0 -426
  58. package/src/sql/Sql.ts +0 -51
@@ -0,0 +1,355 @@
1
+ import * as Clock from "effect/Clock"
2
+ import * as Context from "effect/Context"
3
+ import * as Data from "effect/Data"
4
+ import * as Effect from "effect/Effect"
5
+ import * as Exit from "effect/Exit"
6
+ import * as FiberRef from "effect/FiberRef"
7
+ import * as GlobalValue from "effect/GlobalValue"
8
+ import type * as Scope from "effect/Scope"
9
+ import * as Values from "../Values.ts"
10
+
11
+ export class SqlError extends Data.TaggedError("SqlError")<{
12
+ readonly code: string
13
+ readonly message: string
14
+ readonly cause?: unknown
15
+ }> {}
16
+
17
+ export interface DialectConfig {
18
+ readonly placeholder: (index: number) => string
19
+ readonly identifier: (name: string) => string
20
+ }
21
+
22
+ export type SqlRow = Record<string, unknown>
23
+
24
+ export interface Connection {
25
+ /**
26
+ * Execute a parameterized query via tagged template literal
27
+ */
28
+ <A extends object = SqlRow>(
29
+ strings: TemplateStringsArray,
30
+ ...values: Array<unknown>
31
+ ): Effect.Effect<ReadonlyArray<A>, SqlError>
32
+ /**
33
+ * Create a safely-escaped SQL identifier from a string
34
+ */
35
+ (name: string): unknown
36
+ /**
37
+ * Build a VALUES clause from an object or array of objects,
38
+ * optionally picking specific columns
39
+ */
40
+ <T extends Record<string, unknown>, K extends keyof T>(
41
+ obj: T | ReadonlyArray<T>,
42
+ columns?: [K, ...Array<K>],
43
+ ): unknown
44
+ /**
45
+ * Build an IN-list from an array of primitive values
46
+ */
47
+ (values: ReadonlyArray<string | number | boolean | null>): unknown
48
+
49
+ /**
50
+ * Execute a raw SQL string without tagged template escaping
51
+ */
52
+ readonly unsafe: <A extends object = SqlRow>(
53
+ query: string,
54
+ values?: Array<unknown>,
55
+ ) => Effect.Effect<ReadonlyArray<A>, SqlError>
56
+ }
57
+
58
+ export interface SqlClient extends Connection {
59
+ /**
60
+ * Run an effect inside a transaction
61
+ */
62
+ readonly withTransaction: <A, E, R>(
63
+ self: Effect.Effect<A, E, R>,
64
+ ) => Effect.Effect<A, SqlError | E, R>
65
+
66
+ /**
67
+ * Reserve a dedicated connection from the pool
68
+ */
69
+ readonly reserve: Effect.Effect<Connection, SqlError, Scope.Scope>
70
+
71
+ /**
72
+ * Access the underlying database driver directly
73
+ */
74
+ readonly use: <T>(fn: (driver: any) => Promise<T> | T) => Effect.Effect<T, SqlError>
75
+ }
76
+
77
+ export const SqlClient: Context.Tag<SqlClient, SqlClient> = Context.GenericTag<SqlClient>(
78
+ "effect-start/Sql/SqlClient",
79
+ )
80
+
81
+ export type TaggedQuery = <A extends object = SqlRow>(
82
+ strings: TemplateStringsArray,
83
+ ...values: Array<unknown>
84
+ ) => Effect.Effect<ReadonlyArray<A>, SqlError>
85
+
86
+ export interface MakeOptions {
87
+ readonly withTransaction: SqlClient["withTransaction"]
88
+ readonly reserve: SqlClient["reserve"]
89
+ readonly use: SqlClient["use"]
90
+ readonly unsafe: Connection["unsafe"]
91
+ readonly query: TaggedQuery
92
+ readonly spanAttributes: ReadonlyArray<readonly [string, unknown]>
93
+ readonly dialect: DialectConfig
94
+ }
95
+
96
+ export function make(options: MakeOptions): SqlClient {
97
+ const trace = makeTraceOptions(options.spanAttributes, options.dialect)
98
+ const query = withExecuteSpan(options.query, trace)
99
+ const unsafe = withUnsafeExecuteSpan(options.unsafe, trace)
100
+ const withTransaction = withTransactionSpan(options.withTransaction, trace.spanAttributes)
101
+
102
+ return Object.assign(dispatchCallable(query), {
103
+ ...options,
104
+ query,
105
+ unsafe,
106
+ withTransaction,
107
+ }) as unknown as SqlClient
108
+ }
109
+
110
+ export function connection(
111
+ query: TaggedQuery,
112
+ unsafe: Connection["unsafe"],
113
+ options?: {
114
+ readonly spanAttributes?: ReadonlyArray<readonly [string, unknown]>
115
+ readonly dialect?: DialectConfig
116
+ },
117
+ ): Connection {
118
+ const trace = makeTraceOptions(options?.spanAttributes, options?.dialect)
119
+ const tracedQuery = withExecuteSpan(query, trace)
120
+ const tracedUnsafe = withUnsafeExecuteSpan(unsafe, trace)
121
+ return Object.assign(dispatchCallable(tracedQuery), { unsafe: tracedUnsafe }) as unknown as Connection
122
+ }
123
+
124
+ function dispatchCallable(query: TaggedQuery) {
125
+ return (first: any, ...rest: Array<any>) => {
126
+ if (Values.isTemplateStringsArray(first)) return query(first, ...rest)
127
+ if (typeof first === "string") return makeIdentifier(first)
128
+ if (
129
+ Array.isArray(first) &&
130
+ (first.length === 0 || typeof first[0] !== "object" || first[0] === null)
131
+ )
132
+ return makeList(first)
133
+ return makeValues(first, rest[0])
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Interpolate fragments into a SQL string and parameter array.
139
+ **/
140
+ export function interpolate(
141
+ dialect: DialectConfig,
142
+ strings: TemplateStringsArray,
143
+ interpolations: Array<unknown>,
144
+ ): { readonly sql: string; readonly parameters: Array<unknown> } {
145
+ const parts: Array<string> = []
146
+ const parameters: Array<unknown> = []
147
+ let pi = 1
148
+
149
+ const pushItems = (items: ReadonlyArray<unknown>) => {
150
+ const ph: Array<string> = []
151
+ for (const item of items) {
152
+ ph.push(dialect.placeholder(pi++))
153
+ parameters.push(item)
154
+ }
155
+ return ph.join(", ")
156
+ }
157
+
158
+ parts.push(strings[0])
159
+ for (let i = 0; i < interpolations.length; i++) {
160
+ const frag = isSqlFragment(interpolations[i])
161
+ if (frag) {
162
+ const tag = frag[SqlFragmentTag]
163
+ if (tag === "Identifier") parts.push(dialect.identifier(frag.name))
164
+ else if (tag === "List") parts.push(`(${pushItems(frag.items)})`)
165
+ else if (tag === "Values") {
166
+ const cols = frag.columns.map((c) => dialect.identifier(c as string)).join(", ")
167
+ const rows = frag.value
168
+ .map((row) => `(${pushItems(frag.columns.map((c) => row[c as string]))})`)
169
+ .join(", ")
170
+ parts.push(`(${cols}) VALUES ${rows}`)
171
+ }
172
+ } else {
173
+ parts.push(dialect.placeholder(pi++))
174
+ parameters.push(interpolations[i])
175
+ }
176
+ parts.push(strings[i + 1])
177
+ }
178
+ return { sql: parts.join(""), parameters }
179
+ }
180
+
181
+ export function hasFragments(values: Array<unknown>): boolean {
182
+ return values.some((v) => isSqlFragment(v) !== undefined)
183
+ }
184
+
185
+ export const postgresDialect: DialectConfig = {
186
+ placeholder: (i) => `$${i}`,
187
+ identifier: (name) => `"${name.replace(/"/g, '""')}"`,
188
+ }
189
+
190
+ export const sqliteDialect: DialectConfig = {
191
+ placeholder: () => "?",
192
+ identifier: (name) => `"${name.replace(/"/g, '""')}"`,
193
+ }
194
+
195
+ export const mssqlDialect: DialectConfig = {
196
+ placeholder: (i) => `@p${i}`,
197
+ identifier: (name) => `[${name.replace(/\]/g, "]]")}]`,
198
+ }
199
+
200
+ const SqlFragmentTag = Symbol.for("effect-start/Sql/SqlFragment")
201
+
202
+ type SqlFragment =
203
+ | { readonly [SqlFragmentTag]: "Identifier"; readonly name: string }
204
+ | {
205
+ readonly [SqlFragmentTag]: "Values"
206
+ readonly value: ReadonlyArray<Record<string, unknown>>
207
+ readonly columns: ReadonlyArray<string>
208
+ }
209
+ | { readonly [SqlFragmentTag]: "List"; readonly items: ReadonlyArray<unknown> }
210
+
211
+ const makeIdentifier = (name: string): SqlFragment => ({ [SqlFragmentTag]: "Identifier", name })
212
+
213
+ const makeValues = <T extends Record<string, unknown>>(
214
+ obj: T | ReadonlyArray<T>,
215
+ columns?: Array<keyof T & string>,
216
+ ): SqlFragment => {
217
+ const items = Array.isArray(obj) ? obj : [obj]
218
+ const cols = columns && columns.length > 0 ? columns : (Object.keys(items[0]) as Array<string>)
219
+ return { [SqlFragmentTag]: "Values", value: items, columns: cols }
220
+ }
221
+
222
+ const makeList = (items: ReadonlyArray<unknown>): SqlFragment => ({
223
+ [SqlFragmentTag]: "List",
224
+ items,
225
+ })
226
+
227
+ const isSqlFragment = (value: unknown): SqlFragment | undefined =>
228
+ value !== null && typeof value === "object" && SqlFragmentTag in value
229
+ ? (value as SqlFragment)
230
+ : undefined
231
+
232
+ const currentTransactionDepth = GlobalValue.globalValue(
233
+ Symbol.for("effect-start/sql/currentTransactionDepth"),
234
+ () => FiberRef.unsafeMake(0),
235
+ )
236
+
237
+ const withExecuteSpan =
238
+ (
239
+ query: TaggedQuery,
240
+ options: {
241
+ readonly spanAttributes: Record<string, unknown>
242
+ readonly dialect: DialectConfig
243
+ },
244
+ ): TaggedQuery =>
245
+ <A extends object = SqlRow>(
246
+ strings: TemplateStringsArray,
247
+ ...values: Array<unknown>
248
+ ): Effect.Effect<ReadonlyArray<A>, SqlError> =>
249
+ query<A>(strings, ...values).pipe(
250
+ Effect.withSpan("sql.execute", {
251
+ kind: "client",
252
+ attributes: {
253
+ ...options.spanAttributes,
254
+ "db.operation.name": "execute",
255
+ "db.query.text": renderTemplateSql(options.dialect, strings, values),
256
+ },
257
+ captureStackTrace: false,
258
+ }),
259
+ )
260
+
261
+ const withUnsafeExecuteSpan =
262
+ (
263
+ unsafe: Connection["unsafe"],
264
+ options: {
265
+ readonly spanAttributes: Record<string, unknown>
266
+ },
267
+ ): Connection["unsafe"] =>
268
+ <A extends object = SqlRow>(
269
+ query: string,
270
+ values?: Array<unknown>,
271
+ ): Effect.Effect<ReadonlyArray<A>, SqlError> =>
272
+ unsafe<A>(query, values).pipe(
273
+ Effect.withSpan("sql.execute", {
274
+ kind: "client",
275
+ attributes: {
276
+ ...options.spanAttributes,
277
+ "db.operation.name": "executeRaw",
278
+ "db.query.text": query,
279
+ },
280
+ captureStackTrace: false,
281
+ }),
282
+ )
283
+
284
+ const withTransactionSpan =
285
+ (
286
+ withTransaction: SqlClient["withTransaction"],
287
+ spanAttributes: Record<string, unknown>,
288
+ ): SqlClient["withTransaction"] =>
289
+ <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, SqlError | E, R> =>
290
+ Effect.flatMap(FiberRef.get(currentTransactionDepth), (depth) =>
291
+ Effect.useSpan(
292
+ "sql.transaction",
293
+ {
294
+ kind: "client",
295
+ attributes: spanAttributes,
296
+ captureStackTrace: false,
297
+ },
298
+ (span) =>
299
+ Effect.flatMap(
300
+ Effect.exit(
301
+ Effect.withParentSpan(
302
+ Effect.locally(
303
+ withTransaction(self),
304
+ currentTransactionDepth,
305
+ depth + 1,
306
+ ),
307
+ span,
308
+ ),
309
+ ),
310
+ (exit) =>
311
+ Effect.flatMap(Clock.currentTimeNanos, (timestamp) =>
312
+ Effect.zipRight(
313
+ Effect.sync(() =>
314
+ span.event(
315
+ Exit.isSuccess(exit)
316
+ ? depth === 0
317
+ ? "db.transaction.commit"
318
+ : "db.transaction.savepoint"
319
+ : "db.transaction.rollback",
320
+ timestamp,
321
+ ),
322
+ ),
323
+ exit,
324
+ ),
325
+ ),
326
+ ),
327
+ ),
328
+ )
329
+
330
+ const makeTraceOptions = (
331
+ spanAttributes: ReadonlyArray<readonly [string, unknown]> | undefined,
332
+ dialect: DialectConfig | undefined,
333
+ ): {
334
+ readonly spanAttributes: Record<string, unknown>
335
+ readonly dialect: DialectConfig
336
+ } => ({
337
+ spanAttributes: spanAttributes ? Object.fromEntries(spanAttributes) : {},
338
+ dialect: dialect ?? sqliteDialect,
339
+ })
340
+
341
+ const renderTemplateSql = (
342
+ dialect: DialectConfig,
343
+ strings: TemplateStringsArray,
344
+ values: Array<unknown>,
345
+ ): string => {
346
+ if (hasFragments(values)) {
347
+ return interpolate(dialect, strings, values).sql
348
+ }
349
+
350
+ let sql = strings[0]
351
+ for (let i = 0; i < values.length; i++) {
352
+ sql += dialect.placeholder(i + 1) + strings[i + 1]
353
+ }
354
+ return sql
355
+ }
@@ -4,7 +4,8 @@ import * as FiberRef from "effect/FiberRef"
4
4
  import * as GlobalValue from "effect/GlobalValue"
5
5
  import * as Layer from "effect/Layer"
6
6
  import * as Option from "effect/Option"
7
- import * as Sql from "../Sql.ts"
7
+ import * as Sql from "../SqlClient.ts"
8
+ import * as Values from "../../Values.ts"
8
9
 
9
10
  const errorCode = (error: unknown): string => {
10
11
  const e = error as any
@@ -22,20 +23,14 @@ const wrapError = (error: unknown): Sql.SqlError =>
22
23
  const wrap = <T>(fn: () => PromiseLike<T>): Effect.Effect<T, Sql.SqlError> =>
23
24
  Effect.tryPromise({ try: () => Promise.resolve(fn()), catch: wrapError })
24
25
 
25
- const makeValues: Sql.SqlQuery["values"] = (obj: any, ...columns: Array<string>) => {
26
- const items = Array.isArray(obj) ? obj : [obj]
27
- const cols = columns.length > 0 ? columns : Object.keys(items[0])
28
- return { value: items, columns: cols }
29
- }
30
-
31
- interface TxState {
26
+ interface TransactionConnection {
32
27
  readonly conn: any
33
28
  readonly depth: number
34
29
  }
35
30
 
36
31
  const currentTransaction = GlobalValue.globalValue(
37
32
  Symbol.for("effect-start/sql/bun/currentTransaction"),
38
- () => FiberRef.unsafeMake<Option.Option<TxState>>(Option.none()),
33
+ () => FiberRef.unsafeMake<Option.Option<TransactionConnection>>(Option.none()),
39
34
  )
40
35
 
41
36
  const makeRun =
@@ -45,6 +40,72 @@ const makeRun =
45
40
  wrap(() => fn(Option.isSome(txOpt) ? txOpt.value.conn : bunSql)),
46
41
  )
47
42
 
43
+ const detectDialect = (bunSql: any): Sql.DialectConfig => {
44
+ const adapter = bunSql?.options?.adapter ?? bunSql?.adapter
45
+ if (adapter === "sqlite") return Sql.sqliteDialect
46
+ return Sql.postgresDialect
47
+ }
48
+
49
+ const makeSpanAttributes = (config: ConstructorParameters<typeof Bun.SQL>[0]): Record<string, unknown> => {
50
+ const c = config as Record<string, unknown>
51
+ const adapter = c.adapter
52
+ if (adapter === "sqlite") {
53
+ return Values.compact({
54
+ ...(c.spanAttributes as Record<string, unknown> | undefined),
55
+ "db.system.name": "sqlite",
56
+ })
57
+ }
58
+
59
+ const parsed = (() => {
60
+ if (typeof c.url !== "string") return undefined
61
+ try {
62
+ return new URL(c.url)
63
+ } catch {
64
+ return undefined
65
+ }
66
+ })()
67
+ const dbFromPath = parsed?.pathname.replace(/^\/+/, "") || undefined
68
+ const parsedPort = parsed?.port ? Number(parsed.port) : undefined
69
+
70
+ return Values.compact({
71
+ ...(c.spanAttributes as Record<string, unknown> | undefined),
72
+ "db.system.name": "postgresql",
73
+ "db.namespace": (c.database as string | undefined) ?? dbFromPath,
74
+ "server.address": ((c.hostname ?? c.host) as string | undefined) ?? parsed?.hostname,
75
+ "server.port": (c.port as number | undefined) ?? parsedPort,
76
+ })
77
+ }
78
+
79
+ const makeTaggedTemplate = (
80
+ run: <T>(fn: (conn: any) => PromiseLike<T>) => Effect.Effect<T, Sql.SqlError>,
81
+ dialect: Sql.DialectConfig,
82
+ ) => {
83
+ const unsafeFn = <T = any>(query: string, values?: Array<unknown>) =>
84
+ run<ReadonlyArray<T>>((conn) => conn.unsafe(query, values))
85
+
86
+ return <T = any>(
87
+ strings: TemplateStringsArray,
88
+ ...values: Array<unknown>
89
+ ): Effect.Effect<ReadonlyArray<T>, Sql.SqlError> => {
90
+ if (Sql.hasFragments(values)) {
91
+ const compiled = Sql.interpolate(dialect, strings, values)
92
+ return unsafeFn<T>(compiled.sql, compiled.parameters)
93
+ }
94
+ return run<ReadonlyArray<T>>((conn) => conn(strings, ...values))
95
+ }
96
+ }
97
+
98
+ const makeQuery = (
99
+ run: <T>(fn: (conn: any) => PromiseLike<T>) => Effect.Effect<T, Sql.SqlError>,
100
+ dialect: Sql.DialectConfig,
101
+ spanAttributes: ReadonlyArray<readonly [string, unknown]>,
102
+ ): Sql.Connection => {
103
+ const query = makeTaggedTemplate(run, dialect)
104
+ const unsafe: Sql.Connection["unsafe"] = <T = any>(query: string, values?: Array<unknown>) =>
105
+ run<ReadonlyArray<T>>((conn) => conn.unsafe(query, values))
106
+ return Sql.connection(query, unsafe, { spanAttributes, dialect })
107
+ }
108
+
48
109
  const makeWithTransaction =
49
110
  (bunSql: any) =>
50
111
  <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, Sql.SqlError | E, R> =>
@@ -98,50 +159,56 @@ const makeWithTransaction =
98
159
  )
99
160
 
100
161
  export const layer = (
101
- config: ConstructorParameters<typeof Bun.SQL>[0],
162
+ config: ConstructorParameters<typeof Bun.SQL>[0] & {
163
+ readonly spanAttributes?: Record<string, unknown>
164
+ },
102
165
  ): Layer.Layer<Sql.SqlClient, Sql.SqlError> =>
103
166
  Layer.scoped(
104
167
  Sql.SqlClient,
105
- Effect.acquireRelease(
106
- Effect.try({
107
- try: () => {
108
- const bunSql = new Bun.SQL(config as any)
109
- const run = makeRun(bunSql)
110
- const use: Sql.SqlClient["use"] = (fn) =>
111
- Effect.tryPromise({ try: () => Promise.resolve(fn(bunSql)), catch: wrapError })
112
- return Object.assign(
113
- <T = any>(strings: TemplateStringsArray, ...values: Array<unknown>) =>
114
- run<ReadonlyArray<T>>((conn) => conn(strings, ...values)),
115
- {
116
- unsafe: <T = any>(query: string, values?: Array<unknown>) =>
117
- run<ReadonlyArray<T>>((conn) => conn.unsafe(query, values)),
118
- values: makeValues,
119
- withTransaction: makeWithTransaction(bunSql),
120
- reserve: Effect.acquireRelease(
121
- wrap(() => bunSql.reserve()),
122
- (reserved: any) => Effect.sync(() => reserved.release()),
123
- ).pipe(
124
- Effect.map(
125
- (reserved: any): Sql.SqlQuery =>
126
- Object.assign(
127
- <T = any>(strings: TemplateStringsArray, ...values: Array<unknown>) =>
128
- wrap<ReadonlyArray<T>>(() => reserved(strings, ...values)),
129
- {
130
- unsafe: <T = any>(query: string, values?: Array<unknown>) =>
131
- wrap<ReadonlyArray<T>>(() => reserved.unsafe(query, values)),
132
- values: makeValues,
133
- },
134
- ),
168
+ Effect.map(
169
+ Effect.acquireRelease(
170
+ Effect.try({
171
+ try: () => {
172
+ const driverConfig = { ...config } as Record<string, unknown>
173
+ delete driverConfig.spanAttributes
174
+ const bunSql = new Bun.SQL(driverConfig as any)
175
+ const run = makeRun(bunSql)
176
+ const dialect = detectDialect(bunSql)
177
+ const spanAttributes = Object.entries(makeSpanAttributes(config))
178
+ const unsafeFn: Sql.Connection["unsafe"] = <T = any>(
179
+ query: string,
180
+ values?: Array<unknown>,
181
+ ) => run<ReadonlyArray<T>>((conn) => conn.unsafe(query, values))
182
+ const query = makeTaggedTemplate(run, dialect)
183
+ const use: Sql.SqlClient["use"] = (fn) =>
184
+ Effect.tryPromise({ try: () => Promise.resolve(fn(bunSql)), catch: wrapError })
185
+
186
+ return {
187
+ client: Sql.make({
188
+ query,
189
+ unsafe: unsafeFn,
190
+ withTransaction: makeWithTransaction(bunSql),
191
+ spanAttributes,
192
+ dialect,
193
+ reserve: Effect.acquireRelease(
194
+ wrap(() => bunSql.reserve()),
195
+ (reserved: any) => Effect.sync(() => reserved.release()),
196
+ ).pipe(
197
+ Effect.map((reserved: any): Sql.Connection => {
198
+ const reservedRun = <T>(fn: (conn: any) => PromiseLike<T>) =>
199
+ wrap<T>(() => fn(reserved))
200
+ return makeQuery(reservedRun, dialect, spanAttributes)
201
+ }),
135
202
  ),
136
- ),
137
- close: (options?: { readonly timeout?: number }) =>
138
- use((bunSql) => bunSql.close(options)),
139
- use,
140
- },
141
- ) satisfies Sql.SqlClient
142
- },
143
- catch: wrapError,
144
- }),
145
- (client) => client.close().pipe(Effect.orDie),
203
+ use,
204
+ }),
205
+ close: use((bunSql) => bunSql.close()),
206
+ }
207
+ },
208
+ catch: wrapError,
209
+ }),
210
+ (handle) => handle.close.pipe(Effect.orDie),
211
+ ),
212
+ (handle) => handle.client,
146
213
  ),
147
214
  )
package/src/sql/index.ts CHANGED
@@ -1 +1 @@
1
- export * as Sql from "./Sql.ts"
1
+ export * as SqlClient from "./SqlClient.ts"