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.
- package/package.json +20 -86
- package/src/Entity.ts +6 -6
- package/src/FileRouterCodegen.ts +4 -4
- package/src/FileSystem.ts +4 -8
- package/src/RouteHook.ts +1 -1
- package/src/RouteSse.ts +3 -3
- package/src/SqlIntrospect.ts +2 -2
- package/src/Start.ts +102 -2
- package/src/Values.ts +11 -0
- package/src/bun/BunRoute.ts +1 -1
- package/src/bun/BunRuntime.ts +5 -5
- package/src/hyper/HyperHtml.ts +11 -7
- package/src/hyper/jsx.d.ts +1 -1
- package/src/lint/plugin.js +174 -4
- package/src/sql/SqlClient.ts +355 -0
- package/src/sql/bun/index.ts +117 -50
- package/src/sql/index.ts +1 -1
- package/src/sql/libsql/index.ts +91 -77
- package/src/sql/libsql/libsql.d.ts +4 -1
- package/src/sql/mssql/index.ts +141 -108
- package/src/sql/mssql/mssql.d.ts +1 -0
- package/src/testing/TestLogger.ts +4 -4
- package/src/x/tailwind/compile.ts +6 -14
- package/dist/ChildProcess.js +0 -42
- package/dist/Commander.js +0 -410
- package/dist/ContentNegotiation.js +0 -465
- package/dist/Cookies.js +0 -371
- package/dist/Development.js +0 -94
- package/dist/Effectify.js +0 -27
- package/dist/Entity.js +0 -289
- package/dist/Fetch.js +0 -192
- package/dist/FilePathPattern.js +0 -97
- package/dist/FileRouter.js +0 -204
- package/dist/FileRouterCodegen.js +0 -298
- package/dist/FileSystem.js +0 -132
- package/dist/Http.js +0 -107
- package/dist/PathPattern.js +0 -451
- package/dist/PlatformError.js +0 -40
- package/dist/PlatformRuntime.js +0 -71
- package/dist/Route.js +0 -143
- package/dist/RouteBody.js +0 -92
- package/dist/RouteError.js +0 -76
- package/dist/RouteHook.js +0 -64
- package/dist/RouteHttp.js +0 -367
- package/dist/RouteHttpTracer.js +0 -90
- package/dist/RouteMount.js +0 -86
- package/dist/RouteSchema.js +0 -271
- package/dist/RouteSse.js +0 -94
- package/dist/RouteTree.js +0 -119
- package/dist/RouteTrie.js +0 -179
- package/dist/SchemaExtra.js +0 -99
- package/dist/Socket.js +0 -40
- package/dist/SqlIntrospect.js +0 -515
- package/dist/Start.js +0 -79
- package/dist/StartApp.js +0 -3
- package/dist/StreamExtra.js +0 -135
- package/dist/System.js +0 -38
- package/dist/TuplePathPattern.js +0 -74
- package/dist/Unique.js +0 -226
- package/dist/Values.js +0 -52
- package/dist/bun/BunBundle.js +0 -186
- package/dist/bun/BunChildProcessSpawner.js +0 -142
- package/dist/bun/BunImportTrackerPlugin.js +0 -91
- package/dist/bun/BunRoute.js +0 -157
- package/dist/bun/BunRuntime.js +0 -41
- package/dist/bun/BunServer.js +0 -285
- package/dist/bun/BunVirtualFilesPlugin.js +0 -54
- package/dist/bun/_BunEnhancedResolve.js +0 -127
- package/dist/bun/index.js +0 -5
- package/dist/bundler/Bundle.js +0 -92
- package/dist/bundler/BundleFiles.js +0 -154
- package/dist/bundler/BundleRoute.js +0 -62
- package/dist/client/Overlay.js +0 -33
- package/dist/client/ScrollState.js +0 -106
- package/dist/client/index.js +0 -97
- package/dist/console/Console.js +0 -42
- package/dist/console/ConsoleErrors.js +0 -211
- package/dist/console/ConsoleLogger.js +0 -56
- package/dist/console/ConsoleMetrics.js +0 -72
- package/dist/console/ConsoleProcess.js +0 -59
- package/dist/console/ConsoleStore.js +0 -72
- package/dist/console/ConsoleTracer.js +0 -107
- package/dist/console/Simulation.js +0 -784
- package/dist/console/index.js +0 -3
- package/dist/console/routes/tree.js +0 -30
- package/dist/datastar/actions/fetch.js +0 -536
- package/dist/datastar/actions/peek.js +0 -13
- package/dist/datastar/actions/setAll.js +0 -19
- package/dist/datastar/actions/toggleAll.js +0 -19
- package/dist/datastar/attributes/attr.js +0 -49
- package/dist/datastar/attributes/bind.js +0 -194
- package/dist/datastar/attributes/class.js +0 -54
- package/dist/datastar/attributes/computed.js +0 -25
- package/dist/datastar/attributes/effect.js +0 -10
- package/dist/datastar/attributes/indicator.js +0 -33
- package/dist/datastar/attributes/init.js +0 -27
- package/dist/datastar/attributes/jsonSignals.js +0 -33
- package/dist/datastar/attributes/on.js +0 -81
- package/dist/datastar/attributes/onIntersect.js +0 -53
- package/dist/datastar/attributes/onInterval.js +0 -31
- package/dist/datastar/attributes/onSignalPatch.js +0 -51
- package/dist/datastar/attributes/ref.js +0 -11
- package/dist/datastar/attributes/show.js +0 -32
- package/dist/datastar/attributes/signals.js +0 -18
- package/dist/datastar/attributes/style.js +0 -57
- package/dist/datastar/attributes/text.js +0 -29
- package/dist/datastar/engine.js +0 -1145
- package/dist/datastar/index.js +0 -25
- package/dist/datastar/utils.js +0 -250
- package/dist/datastar/watchers/patchElements.js +0 -486
- package/dist/datastar/watchers/patchSignals.js +0 -14
- package/dist/experimental/EncryptedCookies.js +0 -328
- package/dist/experimental/index.js +0 -1
- package/dist/hyper/Hyper.js +0 -28
- package/dist/hyper/HyperHtml.js +0 -165
- package/dist/hyper/HyperNode.js +0 -13
- package/dist/hyper/HyperRoute.js +0 -45
- package/dist/hyper/html.js +0 -30
- package/dist/hyper/index.js +0 -5
- package/dist/hyper/jsx-runtime.js +0 -14
- package/dist/index.js +0 -8
- package/dist/node/NodeFileSystem.js +0 -675
- package/dist/node/NodeUtils.js +0 -23
- package/dist/sql/Sql.js +0 -8
- package/dist/sql/bun/index.js +0 -142
- package/dist/sql/index.js +0 -1
- package/dist/sql/libsql/index.js +0 -156
- package/dist/sql/mssql/docker.js +0 -110
- package/dist/sql/mssql/index.js +0 -194
- package/dist/testing/TestLogger.js +0 -42
- package/dist/testing/index.js +0 -2
- package/dist/testing/utils.js +0 -61
- package/dist/x/cloudflare/CloudflareTunnel.js +0 -63
- package/dist/x/cloudflare/index.js +0 -1
- package/dist/x/tailscale/TailscaleTunnel.js +0 -94
- package/dist/x/tailscale/index.js +0 -1
- package/dist/x/tailwind/TailwindPlugin.js +0 -294
- package/dist/x/tailwind/compile.js +0 -210
- package/dist/x/tailwind/plugin.js +0 -17
- package/src/console/Console.ts +0 -42
- package/src/console/ConsoleErrors.ts +0 -213
- package/src/console/ConsoleLogger.ts +0 -56
- package/src/console/ConsoleMetrics.ts +0 -72
- package/src/console/ConsoleProcess.ts +0 -59
- package/src/console/ConsoleStore.ts +0 -187
- package/src/console/ConsoleTracer.ts +0 -107
- package/src/console/Simulation.ts +0 -814
- package/src/console/console.html +0 -340
- package/src/console/index.ts +0 -3
- package/src/console/routes/errors/route.tsx +0 -97
- package/src/console/routes/fiberDetail.tsx +0 -54
- package/src/console/routes/fibers/route.tsx +0 -45
- package/src/console/routes/git/route.tsx +0 -64
- package/src/console/routes/layout.tsx +0 -4
- package/src/console/routes/logs/route.tsx +0 -77
- package/src/console/routes/metrics/route.tsx +0 -36
- package/src/console/routes/route.tsx +0 -8
- package/src/console/routes/routes/route.tsx +0 -30
- package/src/console/routes/services/route.tsx +0 -21
- package/src/console/routes/system/route.tsx +0 -43
- package/src/console/routes/traceDetail.tsx +0 -22
- package/src/console/routes/traces/route.tsx +0 -81
- package/src/console/routes/tree.ts +0 -30
- package/src/console/ui/Errors.tsx +0 -76
- package/src/console/ui/Fibers.tsx +0 -321
- package/src/console/ui/Git.tsx +0 -182
- package/src/console/ui/Logs.tsx +0 -46
- package/src/console/ui/Metrics.tsx +0 -78
- package/src/console/ui/Routes.tsx +0 -125
- package/src/console/ui/Services.tsx +0 -273
- package/src/console/ui/Shell.tsx +0 -62
- package/src/console/ui/System.tsx +0 -131
- package/src/console/ui/Traces.tsx +0 -426
- package/src/sql/Sql.ts +0 -51
package/src/lint/plugin.js
CHANGED
|
@@ -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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
+
}
|