effect 4.0.0-beta.36 → 4.0.0-beta.37

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 (65) hide show
  1. package/dist/Cause.d.ts +31 -0
  2. package/dist/Cause.d.ts.map +1 -1
  3. package/dist/Cause.js +19 -0
  4. package/dist/Cause.js.map +1 -1
  5. package/dist/Channel.d.ts +2 -2
  6. package/dist/Channel.d.ts.map +1 -1
  7. package/dist/Effect.d.ts +1 -16
  8. package/dist/Effect.d.ts.map +1 -1
  9. package/dist/Effect.js.map +1 -1
  10. package/dist/Schedule.d.ts.map +1 -1
  11. package/dist/Schedule.js +19 -1
  12. package/dist/Schedule.js.map +1 -1
  13. package/dist/Schema.d.ts +14 -42
  14. package/dist/Schema.d.ts.map +1 -1
  15. package/dist/Schema.js.map +1 -1
  16. package/dist/SchemaParser.d.ts +22 -66
  17. package/dist/SchemaParser.d.ts.map +1 -1
  18. package/dist/SchemaParser.js.map +1 -1
  19. package/dist/Sink.d.ts +5 -2
  20. package/dist/Sink.d.ts.map +1 -1
  21. package/dist/Sink.js.map +1 -1
  22. package/dist/Stream.d.ts +1 -1
  23. package/dist/Stream.d.ts.map +1 -1
  24. package/dist/internal/effect.js +15 -1
  25. package/dist/internal/effect.js.map +1 -1
  26. package/dist/unstable/cli/Prompt.js +16 -6
  27. package/dist/unstable/cli/Prompt.js.map +1 -1
  28. package/dist/unstable/devtools/DevToolsClient.d.ts.map +1 -1
  29. package/dist/unstable/devtools/DevToolsClient.js +4 -3
  30. package/dist/unstable/devtools/DevToolsClient.js.map +1 -1
  31. package/dist/unstable/devtools/DevToolsSchema.d.ts +2 -3
  32. package/dist/unstable/devtools/DevToolsSchema.d.ts.map +1 -1
  33. package/dist/unstable/devtools/DevToolsSchema.js +11 -1
  34. package/dist/unstable/devtools/DevToolsSchema.js.map +1 -1
  35. package/dist/unstable/http/HttpClientRequest.d.ts +24 -2
  36. package/dist/unstable/http/HttpClientRequest.d.ts.map +1 -1
  37. package/dist/unstable/http/HttpClientRequest.js +97 -0
  38. package/dist/unstable/http/HttpClientRequest.js.map +1 -1
  39. package/dist/unstable/http/HttpServerResponse.d.ts.map +1 -1
  40. package/dist/unstable/http/HttpServerResponse.js +2 -1
  41. package/dist/unstable/http/HttpServerResponse.js.map +1 -1
  42. package/dist/unstable/sql/Migrator.d.ts.map +1 -1
  43. package/dist/unstable/sql/Migrator.js +2 -2
  44. package/dist/unstable/sql/Migrator.js.map +1 -1
  45. package/dist/unstable/sql/SqlError.d.ts +229 -9
  46. package/dist/unstable/sql/SqlError.d.ts.map +1 -1
  47. package/dist/unstable/sql/SqlError.js +256 -6
  48. package/dist/unstable/sql/SqlError.js.map +1 -1
  49. package/package.json +1 -1
  50. package/src/Cause.ts +35 -0
  51. package/src/Channel.ts +2 -2
  52. package/src/Effect.ts +1 -16
  53. package/src/Schedule.ts +20 -8
  54. package/src/Schema.ts +6 -10
  55. package/src/SchemaParser.ts +25 -29
  56. package/src/Sink.ts +5 -2
  57. package/src/Stream.ts +1 -1
  58. package/src/internal/effect.ts +27 -2
  59. package/src/unstable/cli/Prompt.ts +15 -6
  60. package/src/unstable/devtools/DevToolsClient.ts +23 -18
  61. package/src/unstable/devtools/DevToolsSchema.ts +13 -1
  62. package/src/unstable/http/HttpClientRequest.ts +104 -2
  63. package/src/unstable/http/HttpServerResponse.ts +8 -4
  64. package/src/unstable/sql/Migrator.ts +7 -5
  65. package/src/unstable/sql/SqlError.ts +360 -8
@@ -15,10 +15,11 @@ import type * as Redacted from "../../Redacted.ts"
15
15
  import * as Result from "../../Result.ts"
16
16
  import type * as Schema from "../../Schema.ts"
17
17
  import type { ParseOptions } from "../../SchemaAST.ts"
18
- import type * as Stream from "../../Stream.ts"
18
+ import * as ServiceMap from "../../ServiceMap.ts"
19
+ import * as Stream from "../../Stream.ts"
19
20
  import * as Headers from "./Headers.ts"
20
21
  import * as HttpBody from "./HttpBody.ts"
21
- import type { HttpMethod } from "./HttpMethod.ts"
22
+ import { hasBody, type HttpMethod } from "./HttpMethod.ts"
22
23
  import * as UrlParams from "./UrlParams.ts"
23
24
 
24
25
  const TypeId = "~effect/http/HttpClientRequest"
@@ -954,3 +955,104 @@ export function toUrl(self: HttpClientRequest): Option.Option<URL> {
954
955
  }
955
956
  return Option.none()
956
957
  }
958
+
959
+ /**
960
+ * @since 4.0.0
961
+ * @category conversions
962
+ */
963
+ export const fromWeb = (request: globalThis.Request): HttpClientRequest => {
964
+ const method = request.method.toUpperCase() as HttpMethod
965
+ return modify(empty, {
966
+ method,
967
+ url: new URL(request.url),
968
+ headers: request.headers,
969
+ body: fromWebBody(request, method)
970
+ })
971
+ }
972
+
973
+ const fromWebBody = (request: globalThis.Request, method: HttpMethod): HttpBody.HttpBody => {
974
+ if (!hasBody(method) || request.body === null) {
975
+ return HttpBody.empty
976
+ }
977
+ return HttpBody.raw(request.body, {
978
+ contentType: request.headers.get("content-type") ?? undefined,
979
+ contentLength: parseContentLength(request.headers.get("content-length"))
980
+ })
981
+ }
982
+
983
+ const parseContentLength = (contentLength: string | null): number | undefined => {
984
+ if (contentLength === null) {
985
+ return undefined
986
+ }
987
+ const parsed = Number.parseInt(contentLength, 10)
988
+ return Number.isNaN(parsed) ? undefined : parsed
989
+ }
990
+
991
+ /**
992
+ * @since 4.0.0
993
+ * @category conversions
994
+ */
995
+ export const toWebResult = (self: HttpClientRequest, options?: {
996
+ readonly signal?: AbortSignal | undefined
997
+ readonly services?: ServiceMap.ServiceMap<never> | undefined
998
+ }): Result.Result<Request, UrlParams.UrlParamsError> => {
999
+ const url = UrlParams.makeUrl(self.url, self.urlParams, Option.getOrUndefined(self.hash))
1000
+ if (Result.isFailure(url)) {
1001
+ return Result.fail(url.failure)
1002
+ }
1003
+ const requestInit: RequestInit = {
1004
+ method: self.method,
1005
+ headers: self.headers
1006
+ }
1007
+ if (options?.signal) {
1008
+ requestInit.signal = options.signal
1009
+ }
1010
+ if (hasBody(self.method)) {
1011
+ switch (self.body._tag) {
1012
+ case "Empty": {
1013
+ break
1014
+ }
1015
+ case "Raw": {
1016
+ requestInit.body = self.body.body as any
1017
+ if (isReadableStream(self.body.body)) {
1018
+ ;(requestInit as any).duplex = "half"
1019
+ }
1020
+ break
1021
+ }
1022
+ case "Uint8Array": {
1023
+ requestInit.body = self.body.body as any
1024
+ break
1025
+ }
1026
+ case "FormData": {
1027
+ requestInit.body = self.body.formData
1028
+ break
1029
+ }
1030
+ case "Stream": {
1031
+ requestInit.body = Stream.toReadableStreamWith(self.body.stream, options?.services ?? ServiceMap.empty())
1032
+ ;(requestInit as any).duplex = "half"
1033
+ break
1034
+ }
1035
+ }
1036
+ }
1037
+ return Result.try({
1038
+ try: () => new Request(url.success, requestInit),
1039
+ catch: (cause) => new UrlParams.UrlParamsError({ cause })
1040
+ })
1041
+ }
1042
+
1043
+ const isReadableStream = (u: unknown): u is ReadableStream<Uint8Array> =>
1044
+ typeof ReadableStream !== "undefined" && u instanceof ReadableStream
1045
+
1046
+ /**
1047
+ * @since 4.0.0
1048
+ * @category conversions
1049
+ */
1050
+ export const toWeb = (self: HttpClientRequest, options?: {
1051
+ readonly signal?: AbortSignal | undefined
1052
+ }): Effect.Effect<Request, UrlParams.UrlParamsError> =>
1053
+ Effect.servicesWith((services) =>
1054
+ toWebResult(self, {
1055
+ services,
1056
+ signal: options?.signal
1057
+ }).asEffect()
1058
+ )
@@ -1225,12 +1225,16 @@ export const fromWeb = (response: Response): HttpServerResponse => {
1225
1225
  cookies: Cookies.fromSetCookie(setCookieHeaders)
1226
1226
  })
1227
1227
  if (response.body) {
1228
+ const contentType = response.headers.get("content-type")
1228
1229
  self = setBody(
1229
1230
  self,
1230
- Body.stream(Stream.fromReadableStream({
1231
- evaluate: () => response.body!,
1232
- onError: (e) => e
1233
- }))
1231
+ Body.stream(
1232
+ Stream.fromReadableStream({
1233
+ evaluate: () => response.body!,
1234
+ onError: (e) => e
1235
+ }),
1236
+ contentType ?? undefined
1237
+ )
1234
1238
  )
1235
1239
  }
1236
1240
  return self
@@ -236,11 +236,13 @@ export const make = <RD = never>({
236
236
  if (required.length > 0) {
237
237
  yield* pipe(
238
238
  insertMigrations(required.map(([id, name]) => [id, name])),
239
- Effect.mapError((_) =>
240
- new MigrationError({
241
- kind: "Locked",
242
- message: "Migrations already running"
243
- })
239
+ Effect.mapError((error): MigrationError | SqlError =>
240
+ error.reason._tag === "ConstraintError"
241
+ ? new MigrationError({
242
+ kind: "Locked",
243
+ message: "Migrations already running"
244
+ })
245
+ : error
244
246
  )
245
247
  )
246
248
  }
@@ -1,21 +1,378 @@
1
1
  /**
2
2
  * @since 4.0.0
3
3
  */
4
+ import * as Predicate from "../../Predicate.ts"
4
5
  import * as Schema from "../../Schema.ts"
5
6
 
6
- const TypeId = "~effect/sql/SqlError"
7
+ const TypeId = "~effect/sql/SqlError" as const
8
+ const ReasonTypeId = "~effect/sql/SqlError/Reason" as const
9
+
10
+ const ReasonFields = {
11
+ cause: Schema.Defect,
12
+ message: Schema.optional(Schema.String),
13
+ operation: Schema.optional(Schema.String)
14
+ }
15
+
16
+ /**
17
+ * @since 4.0.0
18
+ */
19
+ export class ConnectionError extends Schema.TaggedErrorClass<ConnectionError>("effect/sql/SqlError/ConnectionError")(
20
+ "ConnectionError",
21
+ ReasonFields
22
+ ) {
23
+ /**
24
+ * @since 4.0.0
25
+ */
26
+ readonly [ReasonTypeId] = ReasonTypeId
27
+
28
+ /**
29
+ * @since 4.0.0
30
+ */
31
+ get isRetryable(): boolean {
32
+ return true
33
+ }
34
+ }
35
+
36
+ /**
37
+ * @since 4.0.0
38
+ */
39
+ export class AuthenticationError extends Schema.TaggedErrorClass<AuthenticationError>(
40
+ "effect/sql/SqlError/AuthenticationError"
41
+ )("AuthenticationError", ReasonFields) {
42
+ /**
43
+ * @since 4.0.0
44
+ */
45
+ readonly [ReasonTypeId] = ReasonTypeId
46
+
47
+ /**
48
+ * @since 4.0.0
49
+ */
50
+ get isRetryable(): boolean {
51
+ return false
52
+ }
53
+ }
54
+
55
+ /**
56
+ * @since 4.0.0
57
+ */
58
+ export class AuthorizationError extends Schema.TaggedErrorClass<AuthorizationError>(
59
+ "effect/sql/SqlError/AuthorizationError"
60
+ )("AuthorizationError", ReasonFields) {
61
+ /**
62
+ * @since 4.0.0
63
+ */
64
+ readonly [ReasonTypeId] = ReasonTypeId
65
+
66
+ /**
67
+ * @since 4.0.0
68
+ */
69
+ get isRetryable(): boolean {
70
+ return false
71
+ }
72
+ }
73
+
74
+ /**
75
+ * @since 4.0.0
76
+ */
77
+ export class SqlSyntaxError extends Schema.TaggedErrorClass<SqlSyntaxError>("effect/sql/SqlError/SqlSyntaxError")(
78
+ "SqlSyntaxError",
79
+ ReasonFields
80
+ ) {
81
+ /**
82
+ * @since 4.0.0
83
+ */
84
+ readonly [ReasonTypeId] = ReasonTypeId
85
+
86
+ /**
87
+ * @since 4.0.0
88
+ */
89
+ get isRetryable(): boolean {
90
+ return false
91
+ }
92
+ }
93
+
94
+ /**
95
+ * @since 4.0.0
96
+ */
97
+ export class ConstraintError extends Schema.TaggedErrorClass<ConstraintError>("effect/sql/SqlError/ConstraintError")(
98
+ "ConstraintError",
99
+ ReasonFields
100
+ ) {
101
+ /**
102
+ * @since 4.0.0
103
+ */
104
+ readonly [ReasonTypeId] = ReasonTypeId
105
+
106
+ /**
107
+ * @since 4.0.0
108
+ */
109
+ get isRetryable(): boolean {
110
+ return false
111
+ }
112
+ }
113
+
114
+ /**
115
+ * @since 4.0.0
116
+ */
117
+ export class DeadlockError extends Schema.TaggedErrorClass<DeadlockError>("effect/sql/SqlError/DeadlockError")(
118
+ "DeadlockError",
119
+ ReasonFields
120
+ ) {
121
+ /**
122
+ * @since 4.0.0
123
+ */
124
+ readonly [ReasonTypeId] = ReasonTypeId
125
+
126
+ /**
127
+ * @since 4.0.0
128
+ */
129
+ get isRetryable(): boolean {
130
+ return true
131
+ }
132
+ }
133
+
134
+ /**
135
+ * @since 4.0.0
136
+ */
137
+ export class SerializationError extends Schema.TaggedErrorClass<SerializationError>(
138
+ "effect/sql/SqlError/SerializationError"
139
+ )("SerializationError", ReasonFields) {
140
+ /**
141
+ * @since 4.0.0
142
+ */
143
+ readonly [ReasonTypeId] = ReasonTypeId
144
+
145
+ /**
146
+ * @since 4.0.0
147
+ */
148
+ get isRetryable(): boolean {
149
+ return true
150
+ }
151
+ }
152
+
153
+ /**
154
+ * @since 4.0.0
155
+ */
156
+ export class LockTimeoutError extends Schema.TaggedErrorClass<LockTimeoutError>("effect/sql/SqlError/LockTimeoutError")(
157
+ "LockTimeoutError",
158
+ ReasonFields
159
+ ) {
160
+ /**
161
+ * @since 4.0.0
162
+ */
163
+ readonly [ReasonTypeId] = ReasonTypeId
164
+
165
+ /**
166
+ * @since 4.0.0
167
+ */
168
+ get isRetryable(): boolean {
169
+ return true
170
+ }
171
+ }
172
+
173
+ /**
174
+ * @since 4.0.0
175
+ */
176
+ export class StatementTimeoutError extends Schema.TaggedErrorClass<StatementTimeoutError>(
177
+ "effect/sql/SqlError/StatementTimeoutError"
178
+ )("StatementTimeoutError", ReasonFields) {
179
+ /**
180
+ * @since 4.0.0
181
+ */
182
+ readonly [ReasonTypeId] = ReasonTypeId
183
+
184
+ /**
185
+ * @since 4.0.0
186
+ */
187
+ get isRetryable(): boolean {
188
+ return true
189
+ }
190
+ }
191
+
192
+ /**
193
+ * @since 4.0.0
194
+ */
195
+ export class UnknownError extends Schema.TaggedErrorClass<UnknownError>("effect/sql/SqlError/UnknownError")(
196
+ "UnknownError",
197
+ ReasonFields
198
+ ) {
199
+ /**
200
+ * @since 4.0.0
201
+ */
202
+ readonly [ReasonTypeId] = ReasonTypeId
203
+
204
+ /**
205
+ * @since 4.0.0
206
+ */
207
+ get isRetryable(): boolean {
208
+ return false
209
+ }
210
+ }
211
+
212
+ /**
213
+ * @since 4.0.0
214
+ */
215
+ export type SqlErrorReason =
216
+ | ConnectionError
217
+ | AuthenticationError
218
+ | AuthorizationError
219
+ | SqlSyntaxError
220
+ | ConstraintError
221
+ | DeadlockError
222
+ | SerializationError
223
+ | LockTimeoutError
224
+ | StatementTimeoutError
225
+ | UnknownError
226
+
227
+ /**
228
+ * @since 4.0.0
229
+ */
230
+ export const SqlErrorReason: Schema.Union<[
231
+ typeof ConnectionError,
232
+ typeof AuthenticationError,
233
+ typeof AuthorizationError,
234
+ typeof SqlSyntaxError,
235
+ typeof ConstraintError,
236
+ typeof DeadlockError,
237
+ typeof SerializationError,
238
+ typeof LockTimeoutError,
239
+ typeof StatementTimeoutError,
240
+ typeof UnknownError
241
+ ]> = Schema.Union([
242
+ ConnectionError,
243
+ AuthenticationError,
244
+ AuthorizationError,
245
+ SqlSyntaxError,
246
+ ConstraintError,
247
+ DeadlockError,
248
+ SerializationError,
249
+ LockTimeoutError,
250
+ StatementTimeoutError,
251
+ UnknownError
252
+ ])
7
253
 
8
254
  /**
9
255
  * @since 4.0.0
10
256
  */
11
257
  export class SqlError extends Schema.TaggedErrorClass<SqlError>("effect/sql/SqlError")("SqlError", {
12
- cause: Schema.Defect,
13
- message: Schema.optional(Schema.String)
258
+ reason: SqlErrorReason
14
259
  }) {
15
260
  /**
16
261
  * @since 4.0.0
17
262
  */
18
263
  readonly [TypeId] = TypeId
264
+
265
+ /**
266
+ * @since 4.0.0
267
+ */
268
+ override readonly cause = this.reason
269
+
270
+ /**
271
+ * @since 4.0.0
272
+ */
273
+ override get message(): string {
274
+ return this.reason.message || this.reason._tag
275
+ }
276
+
277
+ /**
278
+ * @since 4.0.0
279
+ */
280
+ get isRetryable(): boolean {
281
+ return this.reason.isRetryable
282
+ }
283
+ }
284
+
285
+ /**
286
+ * @since 4.0.0
287
+ */
288
+ export const isSqlError = (u: unknown): u is SqlError => Predicate.hasProperty(u, TypeId)
289
+
290
+ /**
291
+ * @since 4.0.0
292
+ */
293
+ export const isSqlErrorReason = (u: unknown): u is SqlErrorReason => Predicate.hasProperty(u, ReasonTypeId)
294
+
295
+ type SqliteClassifyOptions = {
296
+ readonly message?: string | undefined
297
+ readonly operation?: string | undefined
298
+ }
299
+
300
+ const sqliteCodeFromCause = (cause: unknown): string | number | undefined => {
301
+ if (!Predicate.hasProperty(cause, "code")) {
302
+ return undefined
303
+ }
304
+ const code = cause.code
305
+ return typeof code === "string" || typeof code === "number" ? code : undefined
306
+ }
307
+
308
+ const sqliteNumericCodeFromCause = (cause: unknown): number | undefined => {
309
+ const code = sqliteCodeFromCause(cause)
310
+ if (typeof code === "number") {
311
+ return code
312
+ }
313
+ if (!Predicate.hasProperty(cause, "errno")) {
314
+ return undefined
315
+ }
316
+ const errno = cause.errno
317
+ return typeof errno === "number" ? errno : undefined
318
+ }
319
+
320
+ const matchesSqliteCode = (code: string, expected: string): boolean =>
321
+ code === expected || code.startsWith(expected + "_")
322
+
323
+ /**
324
+ * @since 4.0.0
325
+ */
326
+ export const classifySqliteError = (
327
+ cause: unknown,
328
+ { message, operation }: SqliteClassifyOptions = {}
329
+ ): SqlErrorReason => {
330
+ const props = {
331
+ cause,
332
+ message,
333
+ operation
334
+ }
335
+ const code = sqliteCodeFromCause(cause)
336
+
337
+ if (typeof code === "string") {
338
+ if (matchesSqliteCode(code, "SQLITE_AUTH")) {
339
+ return new AuthenticationError(props)
340
+ }
341
+ if (matchesSqliteCode(code, "SQLITE_PERM")) {
342
+ return new AuthorizationError(props)
343
+ }
344
+ if (matchesSqliteCode(code, "SQLITE_CONSTRAINT")) {
345
+ return new ConstraintError(props)
346
+ }
347
+ if (matchesSqliteCode(code, "SQLITE_BUSY") || matchesSqliteCode(code, "SQLITE_LOCKED")) {
348
+ return new LockTimeoutError(props)
349
+ }
350
+ if (matchesSqliteCode(code, "SQLITE_CANTOPEN")) {
351
+ return new ConnectionError(props)
352
+ }
353
+ }
354
+
355
+ const numericCode = sqliteNumericCodeFromCause(cause)
356
+ if (typeof numericCode === "number") {
357
+ const code = numericCode & 0xff
358
+ switch (code) {
359
+ case 23:
360
+ return new AuthenticationError(props)
361
+ case 3:
362
+ return new AuthorizationError(props)
363
+ case 19:
364
+ return new ConstraintError(props)
365
+ case 5:
366
+ case 6:
367
+ return new LockTimeoutError(props)
368
+ case 14:
369
+ return new ConnectionError(props)
370
+ default:
371
+ return new UnknownError(props)
372
+ }
373
+ }
374
+
375
+ return new UnknownError(props)
19
376
  }
20
377
 
21
378
  /**
@@ -27,11 +384,6 @@ export class ResultLengthMismatch
27
384
  actual: Schema.Number
28
385
  })
29
386
  {
30
- /**
31
- * @since 4.0.0
32
- */
33
- readonly [TypeId] = TypeId
34
-
35
387
  /**
36
388
  * @since 4.0.0
37
389
  */