@effect/sql-d1 4.0.0-beta.98 → 4.0.0-rc.108

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 (83) hide show
  1. package/AGENTS.md +381 -0
  2. package/CLAUDE.md +381 -0
  3. package/README.md +10 -3
  4. package/ai-docs/README.md +44 -0
  5. package/ai-docs/package.json +36 -0
  6. package/ai-docs/src/01_effect/01_basics/01_effect-gen.ts +30 -0
  7. package/ai-docs/src/01_effect/01_basics/02_effect-fn.ts +39 -0
  8. package/ai-docs/src/01_effect/01_basics/10_creating-effects.ts +74 -0
  9. package/ai-docs/src/01_effect/01_basics/index.md +5 -0
  10. package/ai-docs/src/01_effect/02_schema/10_schema-basics.ts +43 -0
  11. package/ai-docs/src/01_effect/02_schema/index.md +7 -0
  12. package/ai-docs/src/01_effect/03_services/01_service.ts +45 -0
  13. package/ai-docs/src/01_effect/03_services/10_reference.ts +10 -0
  14. package/ai-docs/src/01_effect/03_services/20_layer-composition.ts +70 -0
  15. package/ai-docs/src/01_effect/03_services/20_layer-unwrap.ts +66 -0
  16. package/ai-docs/src/01_effect/03_services/index.md +5 -0
  17. package/ai-docs/src/01_effect/04_errors/01_error-handling.ts +30 -0
  18. package/ai-docs/src/01_effect/04_errors/10_catch-tags.ts +24 -0
  19. package/ai-docs/src/01_effect/04_errors/20_reason-errors.ts +64 -0
  20. package/ai-docs/src/01_effect/04_errors/index.md +1 -0
  21. package/ai-docs/src/01_effect/05_resources/10_acquire-release.ts +105 -0
  22. package/ai-docs/src/01_effect/05_resources/20_layer-side-effects.ts +31 -0
  23. package/ai-docs/src/01_effect/05_resources/30_layer-map.ts +86 -0
  24. package/ai-docs/src/01_effect/05_resources/index.md +3 -0
  25. package/ai-docs/src/01_effect/06_running/10_run-main.ts +30 -0
  26. package/ai-docs/src/01_effect/06_running/20_layer-launch.ts +27 -0
  27. package/ai-docs/src/01_effect/06_running/index.md +1 -0
  28. package/ai-docs/src/01_effect/07_pubsub/10_pubsub.ts +56 -0
  29. package/ai-docs/src/01_effect/07_pubsub/index.md +3 -0
  30. package/ai-docs/src/03_stream/10_creating-streams.ts +103 -0
  31. package/ai-docs/src/03_stream/20_consuming-streams.ts +137 -0
  32. package/ai-docs/src/03_stream/30_encoding.ts +165 -0
  33. package/ai-docs/src/03_stream/index.md +4 -0
  34. package/ai-docs/src/04_integration/10_managed-runtime.ts +129 -0
  35. package/ai-docs/src/04_integration/index.md +5 -0
  36. package/ai-docs/src/05_batching/10_request-resolver.ts +89 -0
  37. package/ai-docs/src/05_batching/index.md +3 -0
  38. package/ai-docs/src/06_schedule/10_schedules.ts +110 -0
  39. package/ai-docs/src/06_schedule/index.md +3 -0
  40. package/ai-docs/src/07_datetime/10_creating-and-formatting.ts +30 -0
  41. package/ai-docs/src/07_datetime/20_time-zones.ts +44 -0
  42. package/ai-docs/src/07_datetime/index.md +5 -0
  43. package/ai-docs/src/08_observability/10_logging.ts +66 -0
  44. package/ai-docs/src/08_observability/20_otlp-tracing.ts +95 -0
  45. package/ai-docs/src/08_observability/index.md +7 -0
  46. package/ai-docs/src/09_testing/10_effect-tests.ts +55 -0
  47. package/ai-docs/src/09_testing/20_layer-tests.ts +138 -0
  48. package/ai-docs/src/09_testing/index.md +1 -0
  49. package/ai-docs/src/10_predicate/01_basics.ts +14 -0
  50. package/ai-docs/src/10_predicate/index.md +9 -0
  51. package/ai-docs/src/50_http-client/10_basics.ts +102 -0
  52. package/ai-docs/src/50_http-client/index.md +3 -0
  53. package/ai-docs/src/51_http-server/10_basics.ts +116 -0
  54. package/ai-docs/src/51_http-server/fixtures/api/Api.ts +14 -0
  55. package/ai-docs/src/51_http-server/fixtures/api/Authorization.ts +36 -0
  56. package/ai-docs/src/51_http-server/fixtures/api/System.ts +10 -0
  57. package/ai-docs/src/51_http-server/fixtures/api/Users.ts +91 -0
  58. package/ai-docs/src/51_http-server/fixtures/domain/User.ts +12 -0
  59. package/ai-docs/src/51_http-server/fixtures/domain/UserErrors.ts +22 -0
  60. package/ai-docs/src/51_http-server/fixtures/server/Authorization.ts +36 -0
  61. package/ai-docs/src/51_http-server/fixtures/server/Users/http.ts +71 -0
  62. package/ai-docs/src/51_http-server/fixtures/server/Users.ts +62 -0
  63. package/ai-docs/src/51_http-server/index.md +3 -0
  64. package/ai-docs/src/60_child-process/10_working-with-child-processes.ts +117 -0
  65. package/ai-docs/src/60_child-process/index.md +3 -0
  66. package/ai-docs/src/70_cli/10_basics.ts +136 -0
  67. package/ai-docs/src/70_cli/index.md +5 -0
  68. package/ai-docs/src/71_ai/10_language-model.ts +156 -0
  69. package/ai-docs/src/71_ai/20_tools.ts +226 -0
  70. package/ai-docs/src/71_ai/30_chat.ts +158 -0
  71. package/ai-docs/src/71_ai/fixtures/domain/LaunchPlan.ts +9 -0
  72. package/ai-docs/src/71_ai/index.md +5 -0
  73. package/ai-docs/src/80_cluster/10_entities.ts +97 -0
  74. package/ai-docs/src/80_cluster/index.md +4 -0
  75. package/ai-docs/src/index.md +10 -0
  76. package/ai-docs/tsconfig.json +24 -0
  77. package/dist/D1Client.d.ts +22 -1
  78. package/dist/D1Client.d.ts.map +1 -1
  79. package/dist/D1Client.js +92 -5
  80. package/dist/D1Client.js.map +1 -1
  81. package/dist/index.js.map +1 -1
  82. package/package.json +11 -9
  83. package/src/D1Client.ts +144 -9
package/src/D1Client.ts CHANGED
@@ -27,6 +27,8 @@ import { SqlError, UnknownError } from "effect/unstable/sql/SqlError"
27
27
  import * as Statement from "effect/unstable/sql/Statement"
28
28
 
29
29
  const ATTR_DB_SYSTEM_NAME = "db.system.name"
30
+ const ATTR_DB_OPERATION_NAME = "db.operation.name"
31
+ const ATTR_DB_QUERY_TEXT = "db.query.text"
30
32
 
31
33
  const classifyError = (cause: unknown, message: string, operation: string) =>
32
34
  new UnknownError({ cause, message, operation })
@@ -50,13 +52,38 @@ export type TypeId = "~@effect/sql-d1/D1Client"
50
52
  /**
51
53
  * Cloudflare D1 SQL client service, extending `SqlClient` with its D1 configuration and no `updateValues` support.
52
54
  *
53
- * @category models
55
+ * @category services
54
56
  * @since 4.0.0
55
57
  */
56
58
  export interface D1Client extends Client.SqlClient {
57
59
  readonly [TypeId]: TypeId
58
60
  readonly config: D1ClientConfig
59
61
 
62
+ /**
63
+ * Executes SQL statements as a single atomic D1 batch and returns their row results in order.
64
+ *
65
+ * **When to use**
66
+ *
67
+ * Use when you have a fixed collection of statements that should run in one
68
+ * request and roll back together if any statement fails.
69
+ *
70
+ * **Gotchas**
71
+ *
72
+ * Each statement uses the query and result name transformations from the
73
+ * client that created it. Mixing clients can produce differently shaped row
74
+ * results within the same batch.
75
+ *
76
+ * @since 4.0.0
77
+ */
78
+ readonly batch: <const Statements extends ReadonlyArray<Statement.Statement<any>>>(
79
+ statements: Statements
80
+ ) => Effect.Effect<
81
+ {
82
+ readonly [K in keyof Statements]: Effect.Success<Statements[K]>
83
+ },
84
+ SqlError
85
+ >
86
+
60
87
  /** Not supported in d1 */
61
88
  readonly updateValues: never
62
89
  }
@@ -90,6 +117,78 @@ export interface D1ClientConfig {
90
117
  readonly transformQueryNames?: ((str: string) => string) | undefined
91
118
  }
92
119
 
120
+ type TransformRows = <A extends object>(rows: ReadonlyArray<A>) => ReadonlyArray<A>
121
+
122
+ type BatchResults<Statements extends ReadonlyArray<Statement.Statement<any>>> = {
123
+ readonly [K in keyof Statements]: Effect.Success<Statements[K]>
124
+ }
125
+
126
+ interface StatementWithTransformRows extends Statement.Statement<any> {
127
+ readonly transformRows: TransformRows | undefined
128
+ }
129
+
130
+ const makeBatch = (options: {
131
+ readonly db: D1Database
132
+ readonly prepareCache: Cache.Cache<string, D1PreparedStatement, SqlError>
133
+ readonly spanAttributes: ReadonlyArray<readonly [string, unknown]>
134
+ readonly getClient: () => D1Client
135
+ }): D1Client["batch"] =>
136
+ <const Statements extends ReadonlyArray<Statement.Statement<any>>>(
137
+ statements: Statements
138
+ ) => {
139
+ if (statements.length === 0) {
140
+ return Effect.succeed([] as unknown as BatchResults<Statements>)
141
+ }
142
+ return Effect.useSpan(
143
+ "sql.execute",
144
+ { kind: "client" },
145
+ (span) =>
146
+ Effect.withFiber(Effect.fnUntraced(function*(fiber) {
147
+ const transformer = fiber.getRef(Statement.CurrentTransformer)
148
+ const prepared: Array<D1PreparedStatement> = []
149
+ const transforms: Array<TransformRows | undefined> = []
150
+ const queryTexts: Array<string> = []
151
+
152
+ for (const original of statements) {
153
+ const statement = transformer === undefined
154
+ ? original
155
+ : yield* transformer(original, options.getClient(), fiber, span)
156
+ const [sql, params] = statement.compile()
157
+ queryTexts.push(sql)
158
+ transforms.push((statement as StatementWithTransformRows).transformRows)
159
+ prepared.push((yield* Cache.get(options.prepareCache, sql)).bind(...params))
160
+ }
161
+
162
+ for (const [key, value] of options.spanAttributes) {
163
+ span.attribute(key, value)
164
+ }
165
+ span.attribute(ATTR_DB_OPERATION_NAME, "batch")
166
+ span.attribute(ATTR_DB_QUERY_TEXT, queryTexts.join("; "))
167
+
168
+ // D1 batches execute on the binding directly and intentionally cannot participate in SqlClient transactions.
169
+ const responses = yield* Effect.tryPromise({
170
+ try: () =>
171
+ options.db.batch<Record<string, unknown>>(prepared).then((responses) => {
172
+ for (const response of responses) {
173
+ if (response.error) {
174
+ throw response.error
175
+ }
176
+ }
177
+ return responses
178
+ }),
179
+ catch: (cause) => new SqlError({ reason: classifyError(cause, "Failed to execute batch", "execute") })
180
+ })
181
+
182
+ const results = responses.map((response, index) => {
183
+ const rows = response.results || []
184
+ const transformRows = transforms[index]
185
+ return transformRows ? transformRows(rows) : rows
186
+ })
187
+ return results as BatchResults<Statements>
188
+ }))
189
+ )
190
+ }
191
+
93
192
  /**
94
193
  * Creates a scoped Cloudflare D1 SQL client. Prepared statements are cached, while transactions and streaming queries are not supported by this driver.
95
194
  *
@@ -104,6 +203,10 @@ export const make = (
104
203
  const transformRows = options.transformResultNames ?
105
204
  Statement.defaultTransforms(options.transformResultNames).array :
106
205
  undefined
206
+ const spanAttributes: Array<readonly [string, unknown]> = [
207
+ ...(options.spanAttributes ? Object.entries(options.spanAttributes) : []),
208
+ [ATTR_DB_SYSTEM_NAME, "sqlite"]
209
+ ]
107
210
 
108
211
  const makeConnection = Effect.gen(function*() {
109
212
  const db = options.db
@@ -182,7 +285,7 @@ export const make = (
182
285
  catch: (cause) => new SqlError({ reason: classifyError(cause, "Failed to execute statement", "execute") })
183
286
  })
184
287
 
185
- return identity<Connection>({
288
+ const connection = identity<Connection>({
186
289
  execute(sql, params, transformRows) {
187
290
  return transformRows
188
291
  ? Effect.map(runCached(sql, params), transformRows)
@@ -206,28 +309,60 @@ export const make = (
206
309
  return Stream.die("executeStream not implemented")
207
310
  }
208
311
  })
312
+ return { connection, prepareCache } as const
209
313
  })
210
314
 
211
- const connection = yield* makeConnection
315
+ const { connection, prepareCache } = yield* makeConnection
212
316
  const acquirer = Effect.succeed(connection)
213
317
  const transactionAcquirer = Effect.die("transactions are not supported in D1")
214
318
 
215
- return Object.assign(
319
+ let client!: D1Client
320
+ client = Object.assign(
216
321
  (yield* Client.make({
217
322
  acquirer,
218
323
  compiler,
219
324
  transactionAcquirer,
220
- spanAttributes: [
221
- ...(options.spanAttributes ? Object.entries(options.spanAttributes) : []),
222
- [ATTR_DB_SYSTEM_NAME, "sqlite"]
223
- ],
325
+ spanAttributes,
224
326
  transformRows
225
327
  })) as D1Client,
226
328
  {
227
329
  [TypeId]: TypeId as TypeId,
228
- config: options
330
+ config: options,
331
+ batch: makeBatch({
332
+ db: options.db,
333
+ prepareCache,
334
+ spanAttributes,
335
+ getClient: () => client
336
+ })
229
337
  }
230
338
  )
339
+
340
+ if (options.transformQueryNames !== undefined || transformRows !== undefined) {
341
+ const clientWithoutTransformsBase = yield* Client.make({
342
+ acquirer: Effect.succeed(connection),
343
+ compiler: compiler.withoutTransform,
344
+ transactionAcquirer,
345
+ spanAttributes,
346
+ transformRows: undefined
347
+ })
348
+ let clientWithoutTransforms!: D1Client
349
+ clientWithoutTransforms = Object.assign(clientWithoutTransformsBase as D1Client, {
350
+ [TypeId]: TypeId as TypeId,
351
+ config: options,
352
+ batch: makeBatch({
353
+ db: options.db,
354
+ prepareCache,
355
+ spanAttributes,
356
+ getClient: () => clientWithoutTransforms
357
+ }),
358
+ withoutTransforms: () => clientWithoutTransforms
359
+ })
360
+ Object.assign(client, {
361
+ withoutTransforms: () => clientWithoutTransforms
362
+ })
363
+ }
364
+
365
+ return client
231
366
  })
232
367
 
233
368
  /**