effect-start 0.25.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 (174) hide show
  1. package/package.json +20 -86
  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/dist/ChildProcess.js +0 -42
  25. package/dist/Commander.js +0 -410
  26. package/dist/ContentNegotiation.js +0 -465
  27. package/dist/Cookies.js +0 -371
  28. package/dist/Development.js +0 -94
  29. package/dist/Effectify.js +0 -27
  30. package/dist/Entity.js +0 -289
  31. package/dist/Fetch.js +0 -192
  32. package/dist/FilePathPattern.js +0 -97
  33. package/dist/FileRouter.js +0 -204
  34. package/dist/FileRouterCodegen.js +0 -298
  35. package/dist/FileSystem.js +0 -132
  36. package/dist/Http.js +0 -107
  37. package/dist/PathPattern.js +0 -451
  38. package/dist/PlatformError.js +0 -40
  39. package/dist/PlatformRuntime.js +0 -71
  40. package/dist/Route.js +0 -143
  41. package/dist/RouteBody.js +0 -92
  42. package/dist/RouteError.js +0 -76
  43. package/dist/RouteHook.js +0 -64
  44. package/dist/RouteHttp.js +0 -367
  45. package/dist/RouteHttpTracer.js +0 -90
  46. package/dist/RouteMount.js +0 -86
  47. package/dist/RouteSchema.js +0 -271
  48. package/dist/RouteSse.js +0 -94
  49. package/dist/RouteTree.js +0 -119
  50. package/dist/RouteTrie.js +0 -179
  51. package/dist/SchemaExtra.js +0 -99
  52. package/dist/Socket.js +0 -40
  53. package/dist/SqlIntrospect.js +0 -515
  54. package/dist/Start.js +0 -79
  55. package/dist/StartApp.js +0 -3
  56. package/dist/StreamExtra.js +0 -135
  57. package/dist/System.js +0 -38
  58. package/dist/TuplePathPattern.js +0 -74
  59. package/dist/Unique.js +0 -226
  60. package/dist/Values.js +0 -52
  61. package/dist/bun/BunBundle.js +0 -186
  62. package/dist/bun/BunChildProcessSpawner.js +0 -142
  63. package/dist/bun/BunImportTrackerPlugin.js +0 -91
  64. package/dist/bun/BunRoute.js +0 -157
  65. package/dist/bun/BunRuntime.js +0 -41
  66. package/dist/bun/BunServer.js +0 -285
  67. package/dist/bun/BunVirtualFilesPlugin.js +0 -54
  68. package/dist/bun/_BunEnhancedResolve.js +0 -127
  69. package/dist/bun/index.js +0 -5
  70. package/dist/bundler/Bundle.js +0 -92
  71. package/dist/bundler/BundleFiles.js +0 -154
  72. package/dist/bundler/BundleRoute.js +0 -62
  73. package/dist/client/Overlay.js +0 -33
  74. package/dist/client/ScrollState.js +0 -106
  75. package/dist/client/index.js +0 -97
  76. package/dist/console/Console.js +0 -42
  77. package/dist/console/ConsoleErrors.js +0 -211
  78. package/dist/console/ConsoleLogger.js +0 -56
  79. package/dist/console/ConsoleMetrics.js +0 -72
  80. package/dist/console/ConsoleProcess.js +0 -59
  81. package/dist/console/ConsoleStore.js +0 -72
  82. package/dist/console/ConsoleTracer.js +0 -107
  83. package/dist/console/Simulation.js +0 -784
  84. package/dist/console/index.js +0 -3
  85. package/dist/console/routes/tree.js +0 -30
  86. package/dist/datastar/actions/fetch.js +0 -536
  87. package/dist/datastar/actions/peek.js +0 -13
  88. package/dist/datastar/actions/setAll.js +0 -19
  89. package/dist/datastar/actions/toggleAll.js +0 -19
  90. package/dist/datastar/attributes/attr.js +0 -49
  91. package/dist/datastar/attributes/bind.js +0 -194
  92. package/dist/datastar/attributes/class.js +0 -54
  93. package/dist/datastar/attributes/computed.js +0 -25
  94. package/dist/datastar/attributes/effect.js +0 -10
  95. package/dist/datastar/attributes/indicator.js +0 -33
  96. package/dist/datastar/attributes/init.js +0 -27
  97. package/dist/datastar/attributes/jsonSignals.js +0 -33
  98. package/dist/datastar/attributes/on.js +0 -81
  99. package/dist/datastar/attributes/onIntersect.js +0 -53
  100. package/dist/datastar/attributes/onInterval.js +0 -31
  101. package/dist/datastar/attributes/onSignalPatch.js +0 -51
  102. package/dist/datastar/attributes/ref.js +0 -11
  103. package/dist/datastar/attributes/show.js +0 -32
  104. package/dist/datastar/attributes/signals.js +0 -18
  105. package/dist/datastar/attributes/style.js +0 -57
  106. package/dist/datastar/attributes/text.js +0 -29
  107. package/dist/datastar/engine.js +0 -1145
  108. package/dist/datastar/index.js +0 -25
  109. package/dist/datastar/utils.js +0 -250
  110. package/dist/datastar/watchers/patchElements.js +0 -486
  111. package/dist/datastar/watchers/patchSignals.js +0 -14
  112. package/dist/experimental/EncryptedCookies.js +0 -328
  113. package/dist/experimental/index.js +0 -1
  114. package/dist/hyper/Hyper.js +0 -28
  115. package/dist/hyper/HyperHtml.js +0 -165
  116. package/dist/hyper/HyperNode.js +0 -13
  117. package/dist/hyper/HyperRoute.js +0 -45
  118. package/dist/hyper/html.js +0 -30
  119. package/dist/hyper/index.js +0 -5
  120. package/dist/hyper/jsx-runtime.js +0 -14
  121. package/dist/index.js +0 -8
  122. package/dist/node/NodeFileSystem.js +0 -675
  123. package/dist/node/NodeUtils.js +0 -23
  124. package/dist/sql/Sql.js +0 -8
  125. package/dist/sql/bun/index.js +0 -142
  126. package/dist/sql/index.js +0 -1
  127. package/dist/sql/libsql/index.js +0 -156
  128. package/dist/sql/mssql/docker.js +0 -110
  129. package/dist/sql/mssql/index.js +0 -194
  130. package/dist/testing/TestLogger.js +0 -42
  131. package/dist/testing/index.js +0 -2
  132. package/dist/testing/utils.js +0 -61
  133. package/dist/x/cloudflare/CloudflareTunnel.js +0 -63
  134. package/dist/x/cloudflare/index.js +0 -1
  135. package/dist/x/tailscale/TailscaleTunnel.js +0 -94
  136. package/dist/x/tailscale/index.js +0 -1
  137. package/dist/x/tailwind/TailwindPlugin.js +0 -294
  138. package/dist/x/tailwind/compile.js +0 -210
  139. package/dist/x/tailwind/plugin.js +0 -17
  140. package/src/console/Console.ts +0 -42
  141. package/src/console/ConsoleErrors.ts +0 -213
  142. package/src/console/ConsoleLogger.ts +0 -56
  143. package/src/console/ConsoleMetrics.ts +0 -72
  144. package/src/console/ConsoleProcess.ts +0 -59
  145. package/src/console/ConsoleStore.ts +0 -187
  146. package/src/console/ConsoleTracer.ts +0 -107
  147. package/src/console/Simulation.ts +0 -814
  148. package/src/console/console.html +0 -340
  149. package/src/console/index.ts +0 -3
  150. package/src/console/routes/errors/route.tsx +0 -97
  151. package/src/console/routes/fiberDetail.tsx +0 -54
  152. package/src/console/routes/fibers/route.tsx +0 -45
  153. package/src/console/routes/git/route.tsx +0 -64
  154. package/src/console/routes/layout.tsx +0 -4
  155. package/src/console/routes/logs/route.tsx +0 -77
  156. package/src/console/routes/metrics/route.tsx +0 -36
  157. package/src/console/routes/route.tsx +0 -8
  158. package/src/console/routes/routes/route.tsx +0 -30
  159. package/src/console/routes/services/route.tsx +0 -21
  160. package/src/console/routes/system/route.tsx +0 -43
  161. package/src/console/routes/traceDetail.tsx +0 -22
  162. package/src/console/routes/traces/route.tsx +0 -81
  163. package/src/console/routes/tree.ts +0 -30
  164. package/src/console/ui/Errors.tsx +0 -76
  165. package/src/console/ui/Fibers.tsx +0 -321
  166. package/src/console/ui/Git.tsx +0 -182
  167. package/src/console/ui/Logs.tsx +0 -46
  168. package/src/console/ui/Metrics.tsx +0 -78
  169. package/src/console/ui/Routes.tsx +0 -125
  170. package/src/console/ui/Services.tsx +0 -273
  171. package/src/console/ui/Shell.tsx +0 -62
  172. package/src/console/ui/System.tsx +0 -131
  173. package/src/console/ui/Traces.tsx +0 -426
  174. package/src/sql/Sql.ts +0 -51
@@ -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"
@@ -5,7 +5,8 @@ import * as GlobalValue from "effect/GlobalValue"
5
5
  import * as Layer from "effect/Layer"
6
6
  import * as Option from "effect/Option"
7
7
  import type * as Libsql from "@libsql/client"
8
- import * as Sql from "../Sql.ts"
8
+ import * as Sql from "../SqlClient.ts"
9
+ import * as Values from "../../Values.ts"
9
10
 
10
11
  export interface LibsqlConfig {
11
12
  readonly url: string
@@ -13,6 +14,7 @@ export interface LibsqlConfig {
13
14
  readonly syncUrl?: string
14
15
  readonly syncInterval?: number
15
16
  readonly encryptionKey?: string
17
+ readonly spanAttributes?: Record<string, unknown>
16
18
  }
17
19
 
18
20
  const wrapError = (error: unknown): Sql.SqlError =>
@@ -25,12 +27,28 @@ const wrapError = (error: unknown): Sql.SqlError =>
25
27
  const wrap = <T>(fn: () => PromiseLike<T>): Effect.Effect<T, Sql.SqlError> =>
26
28
  Effect.tryPromise({ try: () => Promise.resolve(fn()), catch: wrapError })
27
29
 
28
- const makeValues: Sql.SqlQuery["values"] = (obj: any, ...columns: Array<string>) => {
29
- const items = Array.isArray(obj) ? obj : [obj]
30
- const cols = columns.length > 0 ? columns : Object.keys(items[0])
31
- return { value: items, columns: cols }
30
+ const dialect = Sql.sqliteDialect
31
+ const makeSpanAttributes = (config: LibsqlConfig): ReadonlyArray<readonly [string, unknown]> => {
32
+ const parsed = (() => {
33
+ try {
34
+ return new URL(config.url)
35
+ } catch {
36
+ return undefined
37
+ }
38
+ })()
39
+ const dbFromPath = parsed?.pathname.replace(/^\/+/, "") || undefined
40
+ const parsedPort = parsed?.port ? Number(parsed.port) : undefined
41
+
42
+ return Object.entries(
43
+ Values.compact({
44
+ ...(config.spanAttributes ?? {}),
45
+ "db.system.name": "sqlite",
46
+ "db.namespace": dbFromPath,
47
+ "server.address": parsed?.hostname,
48
+ "server.port": parsedPort,
49
+ }),
50
+ )
32
51
  }
33
-
34
52
  const resultSetToRows = <T>(result: Libsql.ResultSet): ReadonlyArray<T> => {
35
53
  const { columns, rows } = result
36
54
  return rows.map((row) => {
@@ -40,13 +58,14 @@ const resultSetToRows = <T>(result: Libsql.ResultSet): ReadonlyArray<T> => {
40
58
  })
41
59
  }
42
60
 
43
- const buildQuery = (strings: TemplateStringsArray, values: Array<unknown>) => {
44
- let sql = strings[0]
45
- for (let i = 0; i < values.length; i++) sql += "?" + strings[i + 1]
46
- return { sql, args: values }
47
- }
61
+ const executeQuery = <T>(
62
+ client: Libsql.Client,
63
+ sql: string,
64
+ args: Array<unknown>,
65
+ ): Effect.Effect<ReadonlyArray<T>, Sql.SqlError> =>
66
+ wrap(() => client.execute({ sql, args })).pipe(Effect.map(resultSetToRows<T>))
48
67
 
49
- interface TxState {
68
+ interface TransactionConnection {
50
69
  readonly depth: number
51
70
  }
52
71
 
@@ -58,34 +77,39 @@ const loadLibsql = () => import("@libsql/client") as Promise<LibsqlModule>
58
77
 
59
78
  const currentTransaction = GlobalValue.globalValue(
60
79
  Symbol.for("effect-start/sql/libsql/currentTransaction"),
61
- () => FiberRef.unsafeMake<Option.Option<TxState>>(Option.none()),
80
+ () => FiberRef.unsafeMake<Option.Option<TransactionConnection>>(Option.none()),
62
81
  )
63
82
 
64
- const executeQuery = <T>(
65
- client: Libsql.Client,
66
- sql: string,
67
- args: Array<unknown>,
68
- ): Effect.Effect<ReadonlyArray<T>, Sql.SqlError> =>
69
- wrap(() => client.execute({ sql, args })).pipe(Effect.map(resultSetToRows<T>))
70
-
71
- const runQuery = <T>(
72
- client: Libsql.Client,
73
- strings: TemplateStringsArray,
74
- values: Array<unknown>,
75
- ): Effect.Effect<ReadonlyArray<T>, Sql.SqlError> => {
76
- const { sql, args } = buildQuery(strings, values)
77
- return executeQuery(client, sql, args)
83
+ const exec = (client: Libsql.Client, sql: string) => wrap(() => client.execute(sql))
84
+
85
+ const makeTaggedTemplate = (client: Libsql.Client) => {
86
+ const unsafeFn = <T = any>(query: string, values?: Array<unknown>) =>
87
+ executeQuery<T>(client, query, values ?? [])
88
+
89
+ return <T = any>(
90
+ strings: TemplateStringsArray,
91
+ ...values: Array<unknown>
92
+ ): Effect.Effect<ReadonlyArray<T>, Sql.SqlError> => {
93
+ if (Sql.hasFragments(values)) {
94
+ const compiled = Sql.interpolate(dialect, strings, values)
95
+ return unsafeFn<T>(compiled.sql, compiled.parameters)
96
+ }
97
+
98
+ let sqlText = strings[0]
99
+ for (let i = 0; i < values.length; i++) sqlText += "?" + strings[i + 1]
100
+ return executeQuery<T>(client, sqlText, values)
101
+ }
78
102
  }
79
103
 
80
- const runUnsafe = <T>(
104
+ const makeQuery = (
81
105
  client: Libsql.Client,
82
- query: string,
83
- values?: Array<unknown>,
84
- ): Effect.Effect<ReadonlyArray<T>, Sql.SqlError> =>
85
- executeQuery(client, query, values ?? [])
86
-
87
- const exec = (client: Libsql.Client, sql: string) =>
88
- wrap(() => client.execute(sql))
106
+ spanAttributes: ReadonlyArray<readonly [string, unknown]>,
107
+ ): Sql.Connection => {
108
+ const query = makeTaggedTemplate(client)
109
+ const unsafe: Sql.Connection["unsafe"] = <T = any>(query: string, values?: Array<unknown>) =>
110
+ executeQuery<T>(client, query, values ?? [])
111
+ return Sql.connection(query, unsafe, { spanAttributes, dialect })
112
+ }
89
113
 
90
114
  const makeWithTransaction =
91
115
  (client: Libsql.Client) =>
@@ -98,9 +122,7 @@ const makeWithTransaction =
98
122
  return Effect.gen(function* () {
99
123
  yield* exec(client, `SAVEPOINT ${name}`)
100
124
  const exit = yield* Effect.exit(
101
- restore(
102
- Effect.locally(self, currentTransaction, Option.some({ depth: depth + 1 })),
103
- ),
125
+ restore(Effect.locally(self, currentTransaction, Option.some({ depth: depth + 1 }))),
104
126
  )
105
127
  if (Exit.isSuccess(exit)) {
106
128
  yield* exec(client, `RELEASE SAVEPOINT ${name}`)
@@ -129,45 +151,37 @@ const makeWithTransaction =
129
151
  export const layer = (config: LibsqlConfig): Layer.Layer<Sql.SqlClient, Sql.SqlError> =>
130
152
  Layer.scoped(
131
153
  Sql.SqlClient,
132
- Effect.acquireRelease(
133
- wrap(() => loadLibsql()).pipe(
134
- Effect.map((libsql) => {
135
- const client = libsql.createClient(config)
136
- const use: Sql.SqlClient["use"] = (fn) =>
137
- Effect.tryPromise({ try: () => Promise.resolve(fn(client)), catch: wrapError })
138
- return Object.assign(
139
- <T = any>(strings: TemplateStringsArray, ...values: Array<unknown>) =>
140
- runQuery<T>(client, strings, values),
141
- {
142
- unsafe: <T = any>(query: string, values?: Array<unknown>) =>
143
- runUnsafe<T>(client, query, values),
144
- values: makeValues,
145
- withTransaction: makeWithTransaction(client),
146
- reserve: Effect.acquireRelease(
147
- wrap(() => loadLibsql()).pipe(
148
- Effect.map((m) => m.createClient(config)),
149
- ),
150
- (reserved: Libsql.Client) => Effect.sync(() => reserved.close()),
151
- ).pipe(
152
- Effect.map(
153
- (reserved): Sql.SqlQuery =>
154
- Object.assign(
155
- <T = any>(strings: TemplateStringsArray, ...values: Array<unknown>) =>
156
- runQuery<T>(reserved, strings, values),
157
- {
158
- unsafe: <T = any>(query: string, values?: Array<unknown>) =>
159
- runUnsafe<T>(reserved, query, values),
160
- values: makeValues,
161
- },
162
- ),
163
- ),
164
- ),
165
- close: () => Effect.sync(() => client.close()),
166
- use,
167
- },
168
- ) satisfies Sql.SqlClient
169
- }),
154
+ Effect.map(
155
+ Effect.acquireRelease(
156
+ wrap(() => loadLibsql()).pipe(
157
+ Effect.map((libsql) => {
158
+ const client = libsql.createClient(config)
159
+ const spanAttributes = makeSpanAttributes(config)
160
+ const query = makeTaggedTemplate(client)
161
+ const unsafeFn: Sql.Connection["unsafe"] = <T = any>(
162
+ query: string,
163
+ values?: Array<unknown>,
164
+ ) => executeQuery<T>(client, query, values ?? [])
165
+ const use: Sql.SqlClient["use"] = (fn) =>
166
+ Effect.tryPromise({ try: () => Promise.resolve(fn(client)), catch: wrapError })
167
+
168
+ return {
169
+ client: Sql.make({
170
+ query,
171
+ unsafe: unsafeFn,
172
+ withTransaction: makeWithTransaction(client),
173
+ spanAttributes,
174
+ dialect,
175
+ // libsql has no connection pool — reuse the existing client
176
+ reserve: Effect.succeed(makeQuery(client, spanAttributes)),
177
+ use,
178
+ }),
179
+ close: Effect.sync(() => client.close()),
180
+ }
181
+ }),
182
+ ),
183
+ (handle) => handle.close.pipe(Effect.orDie),
170
184
  ),
171
- (client) => client.close().pipe(Effect.orDie),
185
+ (handle) => handle.client,
172
186
  ),
173
187
  )
@@ -21,7 +21,10 @@ declare module "@libsql/client" {
21
21
 
22
22
  export interface Client {
23
23
  execute(stmt: InStatement | string): Promise<ResultSet>
24
- batch(stmts: ReadonlyArray<InStatement | string>, mode?: TransactionMode): Promise<ReadonlyArray<ResultSet>>
24
+ batch(
25
+ stmts: ReadonlyArray<InStatement | string>,
26
+ mode?: TransactionMode,
27
+ ): Promise<ReadonlyArray<ResultSet>>
25
28
  transaction(mode?: TransactionMode): Promise<Transaction>
26
29
  close(): void
27
30
  }