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
@@ -55,15 +55,41 @@ export default {
55
55
 
56
56
  const typePrefix = node.importKind === "type" ? "type " : ""
57
57
 
58
+ const sourceCode = context.sourceCode || context.getSourceCode()
59
+
58
60
  context.report({
59
61
  node,
60
62
  messageId: "preferNamespace",
61
63
  data: { source, baseName, typePrefix },
62
64
  fix(fixer) {
63
- return fixer.replaceText(
64
- node,
65
- `import ${typePrefix}* as ${baseName} from "${source}"`,
66
- )
65
+ const fixes = [
66
+ fixer.replaceText(
67
+ node,
68
+ `import ${typePrefix}* as ${baseName} from "${source}"`,
69
+ ),
70
+ ]
71
+
72
+ for (const specifier of node.specifiers) {
73
+ if (specifier.type !== "ImportSpecifier") continue
74
+ const localName = specifier.local.name
75
+ const importedName = specifier.imported.name
76
+
77
+ for (const variable of sourceCode.getDeclaredVariables(specifier)) {
78
+ for (const ref of variable.references) {
79
+ if (ref.identifier.range[0] === specifier.local.range[0]) continue
80
+ fixes.push(
81
+ fixer.replaceTextRange(
82
+ ref.identifier.range,
83
+ localName !== importedName
84
+ ? `${baseName}.${importedName}`
85
+ : `${baseName}.${localName}`,
86
+ ),
87
+ )
88
+ }
89
+ }
90
+ }
91
+
92
+ return fixes
67
93
  },
68
94
  })
69
95
  },
@@ -182,6 +208,150 @@ export default {
182
208
  },
183
209
  },
184
210
 
211
+ "no-destructured-params": {
212
+ meta: {
213
+ type: "suggestion",
214
+ docs: {
215
+ description: "Disallow destructuring objects in function parameters",
216
+ },
217
+ fixable: "code",
218
+ schema: [],
219
+ messages: {
220
+ noDestructuredParam:
221
+ "Avoid destructuring objects in function parameters. Use a single parameter and access properties on it instead.",
222
+ },
223
+ },
224
+ create(context) {
225
+ const sourceCode = context.sourceCode || context.getSourceCode()
226
+
227
+ function findUnusedName(scope, base) {
228
+ const names = new Set()
229
+ let current = scope
230
+ while (current) {
231
+ for (const v of current.variables) names.add(v.name)
232
+ current = current.upper
233
+ }
234
+ if (!names.has(base)) return base
235
+ for (let i = 2; ; i++) {
236
+ const candidate = base + i
237
+ if (!names.has(candidate)) return candidate
238
+ }
239
+ }
240
+
241
+ function isDefinition(node) {
242
+ if (node.type === "FunctionDeclaration") return true
243
+ const ancestors = sourceCode.getAncestors(node)
244
+ const parent = ancestors[ancestors.length - 1]
245
+ if (!parent) return false
246
+ if (parent.type === "MethodDefinition" || parent.type === "Property" && parent.method) return true
247
+ if (
248
+ parent.type === "VariableDeclarator" &&
249
+ parent.init === node
250
+ ) return true
251
+ return false
252
+ }
253
+
254
+ function checkParams(node) {
255
+ for (const param of node.params) {
256
+ const pattern =
257
+ param.type === "AssignmentPattern" ? param.left : param
258
+ if (pattern.type !== "ObjectPattern") continue
259
+
260
+ const baseName = isDefinition(node) ? "options" : "v"
261
+ const outerScope = sourceCode.getScope(node).upper || sourceCode.getScope(node)
262
+ const name = findUnusedName(outerScope, baseName)
263
+
264
+ const hasPropertyDefaults = pattern.properties.some(
265
+ (prop) =>
266
+ prop.type !== "RestElement" &&
267
+ prop.value.type === "AssignmentPattern",
268
+ )
269
+
270
+ context.report({
271
+ node: pattern,
272
+ messageId: "noDestructuredParam",
273
+ fix: hasPropertyDefaults ? undefined : (fixer) => {
274
+ const fixes = []
275
+
276
+ const typeAnnotation = pattern.typeAnnotation
277
+ ? sourceCode.getText(pattern.typeAnnotation)
278
+ : ""
279
+ const hasDefault = param.type === "AssignmentPattern"
280
+ const defaultValue = hasDefault
281
+ ? " = " + sourceCode.getText(param.right)
282
+ : ""
283
+
284
+ fixes.push(
285
+ fixer.replaceTextRange(
286
+ param.range,
287
+ name + typeAnnotation + defaultValue,
288
+ ),
289
+ )
290
+
291
+ const fnScope = sourceCode.getScope(node)
292
+ const localToKey = new Map()
293
+ for (const prop of pattern.properties) {
294
+ if (prop.type === "RestElement") continue
295
+ const keyName =
296
+ prop.key.type === "Identifier"
297
+ ? prop.key.name
298
+ : sourceCode.getText(prop.key)
299
+ const localNode =
300
+ prop.value.type === "AssignmentPattern"
301
+ ? prop.value.left
302
+ : prop.value
303
+ if (localNode.type === "Identifier") {
304
+ localToKey.set(localNode.name, keyName)
305
+ }
306
+ }
307
+
308
+ for (const variable of fnScope.variables) {
309
+ const keyName = localToKey.get(variable.name)
310
+ if (keyName === undefined) continue
311
+
312
+ for (const ref of variable.references) {
313
+ if (ref.identifier.range[0] >= pattern.range[0] &&
314
+ ref.identifier.range[1] <= pattern.range[1]) continue
315
+
316
+ const ancestors = sourceCode.getAncestors(ref.identifier)
317
+ const parent = ancestors[ancestors.length - 1]
318
+ if (
319
+ parent &&
320
+ parent.type === "Property" &&
321
+ parent.shorthand &&
322
+ parent.value === ref.identifier
323
+ ) {
324
+ fixes.push(
325
+ fixer.replaceTextRange(
326
+ parent.range,
327
+ keyName + ": " + name + "." + keyName,
328
+ ),
329
+ )
330
+ } else {
331
+ fixes.push(
332
+ fixer.replaceTextRange(
333
+ ref.identifier.range,
334
+ name + "." + keyName,
335
+ ),
336
+ )
337
+ }
338
+ }
339
+ }
340
+
341
+ return fixes
342
+ },
343
+ })
344
+ }
345
+ }
346
+
347
+ return {
348
+ FunctionDeclaration: checkParams,
349
+ FunctionExpression: checkParams,
350
+ ArrowFunctionExpression: checkParams,
351
+ }
352
+ },
353
+ },
354
+
185
355
  "test-assertion-newline": {
186
356
  meta: {
187
357
  type: "layout",
@@ -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
+ }