effect 4.0.0-beta.35 → 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 (99) 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/Channel.js +9 -7
  8. package/dist/Channel.js.map +1 -1
  9. package/dist/Cron.d.ts +7 -0
  10. package/dist/Cron.d.ts.map +1 -1
  11. package/dist/Cron.js +109 -45
  12. package/dist/Cron.js.map +1 -1
  13. package/dist/Effect.d.ts +1 -16
  14. package/dist/Effect.d.ts.map +1 -1
  15. package/dist/Effect.js.map +1 -1
  16. package/dist/Equivalence.d.ts +52 -0
  17. package/dist/Equivalence.d.ts.map +1 -1
  18. package/dist/Equivalence.js +52 -0
  19. package/dist/Equivalence.js.map +1 -1
  20. package/dist/LayerMap.d.ts +5 -4
  21. package/dist/LayerMap.d.ts.map +1 -1
  22. package/dist/LayerMap.js.map +1 -1
  23. package/dist/PubSub.d.ts.map +1 -1
  24. package/dist/PubSub.js +9 -3
  25. package/dist/PubSub.js.map +1 -1
  26. package/dist/Schedule.d.ts.map +1 -1
  27. package/dist/Schedule.js +19 -1
  28. package/dist/Schedule.js.map +1 -1
  29. package/dist/Schema.d.ts +37 -41
  30. package/dist/Schema.d.ts.map +1 -1
  31. package/dist/Schema.js +20 -0
  32. package/dist/Schema.js.map +1 -1
  33. package/dist/SchemaParser.d.ts +39 -54
  34. package/dist/SchemaParser.d.ts.map +1 -1
  35. package/dist/SchemaParser.js +38 -0
  36. package/dist/SchemaParser.js.map +1 -1
  37. package/dist/Sink.d.ts +5 -2
  38. package/dist/Sink.d.ts.map +1 -1
  39. package/dist/Sink.js.map +1 -1
  40. package/dist/Stream.d.ts +1 -1
  41. package/dist/Stream.d.ts.map +1 -1
  42. package/dist/internal/effect.js +18 -3
  43. package/dist/internal/effect.js.map +1 -1
  44. package/dist/unstable/ai/LanguageModel.d.ts.map +1 -1
  45. package/dist/unstable/ai/LanguageModel.js +15 -3
  46. package/dist/unstable/ai/LanguageModel.js.map +1 -1
  47. package/dist/unstable/cli/Prompt.js +158 -15
  48. package/dist/unstable/cli/Prompt.js.map +1 -1
  49. package/dist/unstable/devtools/DevToolsClient.d.ts.map +1 -1
  50. package/dist/unstable/devtools/DevToolsClient.js +4 -3
  51. package/dist/unstable/devtools/DevToolsClient.js.map +1 -1
  52. package/dist/unstable/devtools/DevToolsSchema.d.ts +2 -3
  53. package/dist/unstable/devtools/DevToolsSchema.d.ts.map +1 -1
  54. package/dist/unstable/devtools/DevToolsSchema.js +11 -1
  55. package/dist/unstable/devtools/DevToolsSchema.js.map +1 -1
  56. package/dist/unstable/http/HttpClientRequest.d.ts +24 -2
  57. package/dist/unstable/http/HttpClientRequest.d.ts.map +1 -1
  58. package/dist/unstable/http/HttpClientRequest.js +97 -0
  59. package/dist/unstable/http/HttpClientRequest.js.map +1 -1
  60. package/dist/unstable/http/HttpServerResponse.d.ts.map +1 -1
  61. package/dist/unstable/http/HttpServerResponse.js +2 -1
  62. package/dist/unstable/http/HttpServerResponse.js.map +1 -1
  63. package/dist/unstable/httpapi/HttpApiEndpoint.js +2 -2
  64. package/dist/unstable/httpapi/HttpApiEndpoint.js.map +1 -1
  65. package/dist/unstable/httpapi/HttpApiMiddleware.d.ts +15 -13
  66. package/dist/unstable/httpapi/HttpApiMiddleware.d.ts.map +1 -1
  67. package/dist/unstable/httpapi/HttpApiMiddleware.js +5 -3
  68. package/dist/unstable/httpapi/HttpApiMiddleware.js.map +1 -1
  69. package/dist/unstable/sql/Migrator.d.ts.map +1 -1
  70. package/dist/unstable/sql/Migrator.js +2 -2
  71. package/dist/unstable/sql/Migrator.js.map +1 -1
  72. package/dist/unstable/sql/SqlError.d.ts +229 -9
  73. package/dist/unstable/sql/SqlError.d.ts.map +1 -1
  74. package/dist/unstable/sql/SqlError.js +256 -6
  75. package/dist/unstable/sql/SqlError.js.map +1 -1
  76. package/package.json +1 -1
  77. package/src/Cause.ts +35 -0
  78. package/src/Channel.ts +14 -12
  79. package/src/Cron.ts +142 -45
  80. package/src/Effect.ts +1 -16
  81. package/src/Equivalence.ts +56 -0
  82. package/src/LayerMap.ts +7 -5
  83. package/src/PubSub.ts +13 -5
  84. package/src/Schedule.ts +20 -8
  85. package/src/Schema.ts +38 -18
  86. package/src/SchemaParser.ts +74 -25
  87. package/src/Sink.ts +5 -2
  88. package/src/Stream.ts +1 -1
  89. package/src/internal/effect.ts +30 -4
  90. package/src/unstable/ai/LanguageModel.ts +17 -2
  91. package/src/unstable/cli/Prompt.ts +158 -16
  92. package/src/unstable/devtools/DevToolsClient.ts +23 -18
  93. package/src/unstable/devtools/DevToolsSchema.ts +13 -1
  94. package/src/unstable/http/HttpClientRequest.ts +104 -2
  95. package/src/unstable/http/HttpServerResponse.ts +8 -4
  96. package/src/unstable/httpapi/HttpApiEndpoint.ts +2 -2
  97. package/src/unstable/httpapi/HttpApiMiddleware.ts +36 -20
  98. package/src/unstable/sql/Migrator.ts +7 -5
  99. package/src/unstable/sql/SqlError.ts +360 -8
@@ -739,7 +739,7 @@ export const file = (options: FileOptions = {}): Prompt<string> => {
739
739
  const currentPath = yield* resolveCurrentPath(Option.none(), opts)
740
740
  const files = yield* getFileList(currentPath, opts)
741
741
  const confirm = Confirm.Hide()
742
- return { cursor: 0, files, path: Option.none(), confirm }
742
+ return { cursor: 0, files, allFiles: files, query: "", path: Option.none(), confirm }
743
743
  })
744
744
  return custom(initialState, {
745
745
  render: handleFileRender(opts),
@@ -1855,11 +1855,16 @@ interface FileOptionsReq extends Required<Omit<FileOptions, "startingPath">> {
1855
1855
  interface FileState {
1856
1856
  readonly cursor: number
1857
1857
  readonly files: ReadonlyArray<string>
1858
+ readonly allFiles: ReadonlyArray<string>
1859
+ readonly query: string
1858
1860
  readonly path: Option.Option<string>
1859
1861
  readonly confirm: Confirm
1860
1862
  }
1861
1863
 
1862
1864
  const CONFIRM_MESSAGE = "The selected directory contains files. Would you like to traverse the selected directory?"
1865
+ const FILE_FILTER_LABEL = "filter"
1866
+ const FILE_FILTER_PLACEHOLDER = "type to filter"
1867
+ const FILE_EMPTY_MESSAGE = "No matches"
1863
1868
  type Confirm = Data.TaggedEnum<{
1864
1869
  readonly Show: {}
1865
1870
  readonly Hide: {}
@@ -1914,6 +1919,40 @@ const getFileList = Effect.fnUntraced(function*(directory: string, options: File
1914
1919
  }, { concurrency: files.length })
1915
1920
  })
1916
1921
 
1922
+ const filterFiles = (files: ReadonlyArray<string>, query: string) => {
1923
+ if (query.length === 0) {
1924
+ return files
1925
+ }
1926
+ const normalizedQuery = query.toLowerCase()
1927
+ const filtered: Array<string> = []
1928
+ for (let index = 0; index < files.length; index++) {
1929
+ if (files[index].toLowerCase().includes(normalizedQuery)) {
1930
+ filtered.push(files[index])
1931
+ }
1932
+ }
1933
+ return filtered
1934
+ }
1935
+
1936
+ const updateFileState = (
1937
+ state: FileState,
1938
+ query: string,
1939
+ allFiles: ReadonlyArray<string> = state.allFiles
1940
+ ): FileState => {
1941
+ const files = filterFiles(allFiles, query)
1942
+ if (files.length === 0) {
1943
+ return { ...state, query, allFiles, files, cursor: 0 }
1944
+ }
1945
+ const selected = state.files[state.cursor]
1946
+ const cursor = selected === undefined ? 0 : files.indexOf(selected)
1947
+ return {
1948
+ ...state,
1949
+ query,
1950
+ allFiles,
1951
+ files,
1952
+ cursor: cursor === -1 ? 0 : cursor
1953
+ }
1954
+ }
1955
+
1917
1956
  const handleFileClear = (options: FileOptionsReq) => {
1918
1957
  return Effect.fnUntraced(function*(state: FileState, _: Action<FileState, string>) {
1919
1958
  const terminal = yield* Terminal.Terminal
@@ -1922,12 +1961,14 @@ const handleFileClear = (options: FileOptionsReq) => {
1922
1961
  const figures = yield* platformFigures
1923
1962
  const currentPath = yield* resolveCurrentPath(state.path, options)
1924
1963
  const selectedPath = state.files[state.cursor]
1925
- const resolvedPath = path.resolve(currentPath, selectedPath)
1964
+ const resolvedPath = selectedPath === undefined ? currentPath : path.resolve(currentPath, selectedPath)
1926
1965
  const resolvedPathText = `${figures.pointerSmall} ${resolvedPath}`
1927
1966
  const isConfirming = showConfirmation(state.confirm)
1928
1967
  const promptText = isConfirming
1929
1968
  ? renderPrompt("(Y/n)", CONFIRM_MESSAGE, "?", figures.pointerSmall, { plain: true })
1930
- : renderPrompt("", options.message, figures.tick, figures.ellipsis, { plain: true })
1969
+ : renderPrompt(renderFileFilter(state, { plain: true }), options.message, figures.tick, figures.ellipsis, {
1970
+ plain: true
1971
+ })
1931
1972
  const filesText = isConfirming
1932
1973
  ? ""
1933
1974
  : renderFiles(state, state.files, figures, options, { plain: true })
@@ -1995,6 +2036,17 @@ const renderFileName = (file: string, isSelected: boolean, renderOptions?: Rende
1995
2036
  : file
1996
2037
  }
1997
2038
 
2039
+ const renderFileFilter = (state: FileState, renderOptions?: RenderOptions | undefined) => {
2040
+ const filterValue = state.query.length === 0
2041
+ ? renderOptions?.plain === true
2042
+ ? FILE_FILTER_PLACEHOLDER
2043
+ : Ansi.annotate(FILE_FILTER_PLACEHOLDER, Ansi.blackBright)
2044
+ : renderOptions?.plain === true
2045
+ ? state.query
2046
+ : Ansi.annotate(state.query, Ansi.combine(Ansi.underlined, Ansi.cyanBright))
2047
+ return `[${FILE_FILTER_LABEL}: ${filterValue}]`
2048
+ }
2049
+
1998
2050
  const renderFiles = (
1999
2051
  state: FileState,
2000
2052
  files: ReadonlyArray<string>,
@@ -2003,6 +2055,11 @@ const renderFiles = (
2003
2055
  renderOptions?: RenderOptions | undefined
2004
2056
  ) => {
2005
2057
  const length = files.length
2058
+ if (length === 0) {
2059
+ return renderOptions?.plain === true
2060
+ ? FILE_EMPTY_MESSAGE
2061
+ : Ansi.annotate(FILE_EMPTY_MESSAGE, Ansi.blackBright)
2062
+ }
2006
2063
  const toDisplay = entriesToDisplay(state.cursor, length, options.maxPerPage)
2007
2064
  const documents: Array<string> = []
2008
2065
  for (let index = toDisplay.startIndex; index < toDisplay.endIndex; index++) {
@@ -2019,7 +2076,7 @@ const renderFileNextFrame = Effect.fnUntraced(function*(state: FileState, option
2019
2076
  const figures = yield* platformFigures
2020
2077
  const currentPath = yield* resolveCurrentPath(state.path, options)
2021
2078
  const selectedPath = state.files[state.cursor]
2022
- const resolvedPath = path.resolve(currentPath, selectedPath)
2079
+ const resolvedPath = selectedPath === undefined ? currentPath : path.resolve(currentPath, selectedPath)
2023
2080
  const resolvedPathMsg = Ansi.annotate(figures.pointerSmall + " " + resolvedPath, Ansi.blackBright)
2024
2081
 
2025
2082
  if (showConfirmation(state.confirm)) {
@@ -2031,33 +2088,36 @@ const renderFileNextFrame = Effect.fnUntraced(function*(state: FileState, option
2031
2088
  }
2032
2089
  const leadingSymbol = Ansi.annotate(figures.tick, Ansi.green)
2033
2090
  const trailingSymbol = Ansi.annotate(figures.ellipsis, Ansi.blackBright)
2034
- const promptMsg = renderPrompt("", options.message, leadingSymbol, trailingSymbol)
2091
+ const promptMsg = renderPrompt(renderFileFilter(state), options.message, leadingSymbol, trailingSymbol)
2035
2092
  const files = renderFiles(state, state.files, figures, options)
2036
2093
  return Ansi.cursorHide + promptMsg + "\n" + resolvedPathMsg + "\n" + files
2037
2094
  })
2038
2095
 
2039
- const renderFileSubmission = Effect.fnUntraced(function*(value: string, options: FileOptionsReq) {
2096
+ const renderFileSubmission = Effect.fnUntraced(function*(state: FileState, value: string, options: FileOptionsReq) {
2040
2097
  const figures = yield* platformFigures
2041
2098
  const leadingSymbol = Ansi.annotate(figures.tick, Ansi.green)
2042
2099
  const trailingSymbol = Ansi.annotate(figures.ellipsis, Ansi.blackBright)
2043
- const promptMsg = renderPrompt("", options.message, leadingSymbol, trailingSymbol)
2100
+ const promptMsg = renderPrompt(renderFileFilter(state), options.message, leadingSymbol, trailingSymbol)
2044
2101
  return promptMsg + " " + Ansi.annotate(value, Ansi.white) + "\n"
2045
2102
  })
2046
2103
 
2047
2104
  const handleFileRender = (options: FileOptionsReq) => {
2048
2105
  return (
2049
- _: FileState,
2106
+ state: FileState,
2050
2107
  action: Action<FileState, string>
2051
2108
  ): Effect.Effect<string, never, Path.Path | FileSystem.FileSystem> => {
2052
2109
  return Action.$match(action, {
2053
2110
  Beep: () => Effect.succeed(renderBeep),
2054
2111
  NextFrame: ({ state }) => renderFileNextFrame(state, options),
2055
- Submit: ({ value }) => renderFileSubmission(value, options)
2112
+ Submit: ({ value }) => renderFileSubmission(state, value, options)
2056
2113
  })
2057
2114
  }
2058
2115
  }
2059
2116
 
2060
2117
  const processFileCursorUp = (state: FileState) => {
2118
+ if (state.files.length === 0) {
2119
+ return Effect.succeed(Action.Beep())
2120
+ }
2061
2121
  const cursor = state.cursor - 1
2062
2122
  return Effect.succeed(Action.NextFrame({
2063
2123
  state: { ...state, cursor: cursor < 0 ? state.files.length - 1 : cursor }
@@ -2065,12 +2125,36 @@ const processFileCursorUp = (state: FileState) => {
2065
2125
  }
2066
2126
 
2067
2127
  const processFileCursorDown = (state: FileState) => {
2128
+ if (state.files.length === 0) {
2129
+ return Effect.succeed(Action.Beep())
2130
+ }
2068
2131
  return Effect.succeed(Action.NextFrame({
2069
2132
  state: { ...state, cursor: (state.cursor + 1) % state.files.length }
2070
2133
  }))
2071
2134
  }
2072
2135
 
2136
+ const processFileBackspace = (state: FileState) => {
2137
+ if (state.query.length === 0) {
2138
+ return Effect.succeed(Action.Beep())
2139
+ }
2140
+ const query = state.query.slice(0, state.query.length - 1)
2141
+ return Effect.succeed(Action.NextFrame({ state: updateFileState(state, query) }))
2142
+ }
2143
+
2144
+ const processFileClear = (state: FileState) => Effect.succeed(Action.NextFrame({ state: updateFileState(state, "") }))
2145
+
2146
+ const processFileInput = (input: string, state: FileState) => {
2147
+ if (input.length === 0) {
2148
+ return Effect.succeed(Action.Beep())
2149
+ }
2150
+ const query = state.query + input
2151
+ return Effect.succeed(Action.NextFrame({ state: updateFileState(state, query) }))
2152
+ }
2153
+
2073
2154
  const processSelection = Effect.fnUntraced(function*(state: FileState, options: FileOptionsReq) {
2155
+ if (state.files.length === 0) {
2156
+ return Action.Beep()
2157
+ }
2074
2158
  const fs = yield* FileSystem.FileSystem
2075
2159
  const path = yield* Path.Path
2076
2160
  const currentPath = yield* resolveCurrentPath(state.path, options)
@@ -2097,6 +2181,8 @@ const processSelection = Effect.fnUntraced(function*(state: FileState, options:
2097
2181
  state: {
2098
2182
  cursor: 0,
2099
2183
  files,
2184
+ allFiles: files,
2185
+ query: "",
2100
2186
  path: Option.some(resolvedPath),
2101
2187
  confirm: Confirm.Hide()
2102
2188
  }
@@ -2107,6 +2193,15 @@ const processSelection = Effect.fnUntraced(function*(state: FileState, options:
2107
2193
 
2108
2194
  const handleFileProcess = (options: FileOptionsReq) => {
2109
2195
  return Effect.fnUntraced(function*(input: Terminal.UserInput, state: FileState) {
2196
+ if (input.key.ctrl) {
2197
+ if (input.key.name === "u") {
2198
+ if (showConfirmation(state.confirm)) {
2199
+ return Action.Beep()
2200
+ }
2201
+ return yield* processFileClear(state)
2202
+ }
2203
+ return Action.Beep()
2204
+ }
2110
2205
  switch (input.key.name) {
2111
2206
  case "k":
2112
2207
  case "up": {
@@ -2117,6 +2212,12 @@ const handleFileProcess = (options: FileOptionsReq) => {
2117
2212
  case "tab": {
2118
2213
  return yield* processFileCursorDown(state)
2119
2214
  }
2215
+ case "backspace": {
2216
+ if (showConfirmation(state.confirm)) {
2217
+ return Action.Beep()
2218
+ }
2219
+ return yield* processFileBackspace(state)
2220
+ }
2120
2221
  case "enter":
2121
2222
  case "return": {
2122
2223
  return yield* processSelection(state, options)
@@ -2133,12 +2234,14 @@ const handleFileProcess = (options: FileOptionsReq) => {
2133
2234
  state: {
2134
2235
  cursor: 0,
2135
2236
  files,
2237
+ allFiles: files,
2238
+ query: "",
2136
2239
  path: Option.some(resolvedPath),
2137
2240
  confirm: Confirm.Hide()
2138
2241
  }
2139
2242
  })
2140
2243
  }
2141
- return Action.Beep()
2244
+ return yield* processFileInput(Option.getOrElse(input.input, () => ""), state)
2142
2245
  }
2143
2246
  case "n":
2144
2247
  case "f": {
@@ -2149,10 +2252,13 @@ const handleFileProcess = (options: FileOptionsReq) => {
2149
2252
  const resolvedPath = path.resolve(currentPath, selectedPath)
2150
2253
  return Action.Submit({ value: resolvedPath })
2151
2254
  }
2152
- return Action.Beep()
2255
+ return yield* processFileInput(Option.getOrElse(input.input, () => ""), state)
2153
2256
  }
2154
2257
  default: {
2155
- return Action.Beep()
2258
+ if (showConfirmation(state.confirm)) {
2259
+ return Action.Beep()
2260
+ }
2261
+ return yield* processFileInput(Option.getOrElse(input.input, () => ""), state)
2156
2262
  }
2157
2263
  }
2158
2264
  })
@@ -3057,8 +3163,11 @@ const handleSelectProcess = <A>(options: SelectOptionsReq<A>) => {
3057
3163
 
3058
3164
  const handleAutoCompleteProcess = <A>(options: AutoCompleteOptionsReq<A>) => {
3059
3165
  return (input: Terminal.UserInput, state: AutoCompleteState) => {
3060
- if (input.key.ctrl && input.key.name === "u") {
3061
- return processAutoCompleteClear(state, options)
3166
+ if (input.key.ctrl) {
3167
+ if (input.key.name === "u") {
3168
+ return processAutoCompleteClear(state, options)
3169
+ }
3170
+ return Effect.succeed(Action.Beep())
3062
3171
  }
3063
3172
  switch (input.key.name) {
3064
3173
  case "k":
@@ -3259,6 +3368,20 @@ const processTextCursorRight = (state: TextState) => {
3259
3368
  )
3260
3369
  }
3261
3370
 
3371
+ const processTextCursorStart = (state: TextState) =>
3372
+ Effect.succeed(
3373
+ Action.NextFrame({
3374
+ state: { ...state, cursor: 0, error: Option.none() }
3375
+ })
3376
+ )
3377
+
3378
+ const processTextCursorEnd = (state: TextState) =>
3379
+ Effect.succeed(
3380
+ Action.NextFrame({
3381
+ state: { ...state, cursor: state.value.length, error: Option.none() }
3382
+ })
3383
+ )
3384
+
3262
3385
  const processTab = (state: TextState, options: TextOptionsReq) => {
3263
3386
  if (state.value === options.default) {
3264
3387
  return Effect.succeed(Action.Beep())
@@ -3295,8 +3418,21 @@ const handleTextRender = (options: TextOptionsReq) => {
3295
3418
 
3296
3419
  const handleTextProcess = (options: TextOptionsReq) => {
3297
3420
  return (input: Terminal.UserInput, state: TextState) => {
3298
- if (input.key.ctrl && input.key.name === "u") {
3299
- return processTextClear(state)
3421
+ if (input.key.ctrl) {
3422
+ switch (input.key.name) {
3423
+ case "u": {
3424
+ return processTextClear(state)
3425
+ }
3426
+ case "a": {
3427
+ return processTextCursorStart(state)
3428
+ }
3429
+ case "e": {
3430
+ return processTextCursorEnd(state)
3431
+ }
3432
+ default: {
3433
+ return Effect.succeed(Action.Beep())
3434
+ }
3435
+ }
3300
3436
  }
3301
3437
  switch (input.key.name) {
3302
3438
  case "backspace": {
@@ -3308,6 +3444,12 @@ const handleTextProcess = (options: TextOptionsReq) => {
3308
3444
  case "right": {
3309
3445
  return processTextCursorRight(state)
3310
3446
  }
3447
+ case "home": {
3448
+ return processTextCursorStart(state)
3449
+ }
3450
+ case "end": {
3451
+ return processTextCursorEnd(state)
3452
+ }
3311
3453
  case "enter":
3312
3454
  case "return": {
3313
3455
  const value = state.value
@@ -4,6 +4,7 @@
4
4
  import * as Cause from "../../Cause.ts"
5
5
  import * as Deferred from "../../Deferred.ts"
6
6
  import * as Effect from "../../Effect.ts"
7
+ import * as Fiber from "../../Fiber.ts"
7
8
  import * as Layer from "../../Layer.ts"
8
9
  import * as Metric from "../../Metric.ts"
9
10
  import * as Queue from "../../Queue.ts"
@@ -23,9 +24,14 @@ const ResponseSchema = Schema.toCodecJson(DevToolsSchema.Response)
23
24
  * @since 4.0.0
24
25
  * @category tags
25
26
  */
26
- export class DevToolsClient extends ServiceMap.Service<DevToolsClient, {
27
- readonly sendUnsafe: (_: DevToolsSchema.Span | DevToolsSchema.SpanEvent) => void
28
- }>()("effect/devtools/DevToolsClient") {}
27
+ export class DevToolsClient extends ServiceMap.Service<
28
+ DevToolsClient,
29
+ {
30
+ readonly sendUnsafe: (
31
+ _: DevToolsSchema.Span | DevToolsSchema.SpanEvent
32
+ ) => void
33
+ }
34
+ >()("effect/devtools/DevToolsClient") {}
29
35
 
30
36
  const makeEffect = Effect.gen(function*() {
31
37
  const socket = yield* Socket.Socket
@@ -37,7 +43,9 @@ const makeEffect = Effect.gen(function*() {
37
43
  Queue.offerUnsafe(requests, toMetricsSnapshot(services))
38
44
  })
39
45
 
40
- const handleResponse = (response: DevToolsSchema.Response): Effect.Effect<void> => {
46
+ const handleResponse = (
47
+ response: DevToolsSchema.Response
48
+ ): Effect.Effect<void> => {
41
49
  switch (response._tag) {
42
50
  case "MetricsRequest": {
43
51
  return offerMetricsSnapshot
@@ -48,7 +56,7 @@ const makeEffect = Effect.gen(function*() {
48
56
  }
49
57
  }
50
58
 
51
- yield* Stream.fromQueue(requests).pipe(
59
+ const fiber = yield* Stream.fromQueue(requests).pipe(
52
60
  Stream.pipeThroughChannel(
53
61
  Ndjson.duplexSchemaString(Socket.toChannelString(socket), {
54
62
  inputSchema: RequestSchema,
@@ -57,12 +65,15 @@ const makeEffect = Effect.gen(function*() {
57
65
  ),
58
66
  Stream.onFirst(() => Deferred.completeWith(connected, Effect.void)),
59
67
  Stream.runForEach(handleResponse),
60
- Effect.forkScoped
68
+ Effect.forkDetach
61
69
  )
62
70
 
63
71
  yield* Effect.addFinalizer(() =>
64
72
  offerMetricsSnapshot.pipe(
65
- Effect.andThen(Effect.flatMap(Effect.fiberId, (id) => Queue.failCause(requests, Cause.interrupt(id))))
73
+ Effect.andThen(
74
+ Effect.flatMap(Effect.fiberId, (id) => Queue.failCause(requests, Cause.interrupt(id)))
75
+ ),
76
+ Effect.andThen(Fiber.await(fiber))
66
77
  )
67
78
  )
68
79
 
@@ -84,7 +95,9 @@ const makeEffect = Effect.gen(function*() {
84
95
  })
85
96
  })
86
97
 
87
- const toMetricsSnapshot = (services: ServiceMap.ServiceMap<never>): DevToolsSchema.MetricsSnapshot => ({
98
+ const toMetricsSnapshot = (
99
+ services: ServiceMap.ServiceMap<never>
100
+ ): DevToolsSchema.MetricsSnapshot => ({
88
101
  _tag: "MetricsSnapshot",
89
102
  metrics: Metric.snapshotUnsafe(services)
90
103
  })
@@ -108,11 +121,7 @@ export const make: Effect.Effect<
108
121
  * @since 4.0.0
109
122
  * @category layers
110
123
  */
111
- export const layer: Layer.Layer<
112
- DevToolsClient,
113
- never,
114
- Socket.Socket
115
- > = Layer.effect(DevToolsClient, make)
124
+ export const layer: Layer.Layer<DevToolsClient, never, Socket.Socket> = Layer.effect(DevToolsClient, make)
116
125
 
117
126
  const makeTracerEffect = Effect.gen(function*() {
118
127
  const client = yield* DevToolsClient
@@ -162,10 +171,6 @@ export const makeTracer: Effect.Effect<Tracer.Tracer, never, DevToolsClient> = m
162
171
  * @since 4.0.0
163
172
  * @category layers
164
173
  */
165
- export const layerTracer: Layer.Layer<
166
- never,
167
- never,
168
- Socket.Socket
169
- > = Layer.effect(Tracer.Tracer, makeTracer).pipe(
174
+ export const layerTracer: Layer.Layer<never, never, Socket.Socket> = Layer.effect(Tracer.Tracer, makeTracer).pipe(
170
175
  Layer.provide(layer)
171
176
  )
@@ -1,8 +1,11 @@
1
1
  /**
2
2
  * @since 4.0.0
3
3
  */
4
+ import * as Exit from "../../Exit.ts"
5
+ import { identity } from "../../Function.ts"
4
6
  import type * as Option from "../../Option.ts"
5
7
  import * as Schema from "../../Schema.ts"
8
+ import * as SchemaTransformation from "../../SchemaTransformation.ts"
6
9
 
7
10
  /**
8
11
  * @since 4.0.0
@@ -26,7 +29,16 @@ export type SpanStatusStarted = Schema.Schema.Type<typeof SpanStatusStarted>
26
29
  export const SpanStatusEnded = Schema.Struct({
27
30
  _tag: Schema.tag("Ended"),
28
31
  startTime: Schema.BigInt,
29
- endTime: Schema.BigInt
32
+ endTime: Schema.BigInt,
33
+ exit: Schema.Exit(Schema.Void, Schema.DefectWithStack, Schema.DefectWithStack).pipe(
34
+ Schema.decodeTo(
35
+ Schema.Exit(Schema.Unknown, Schema.Unknown, Schema.Unknown),
36
+ SchemaTransformation.transform({
37
+ decode: identity,
38
+ encode: Exit.asVoid
39
+ })
40
+ )
41
+ )
30
42
  })
31
43
 
32
44
  /**
@@ -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
@@ -175,8 +175,8 @@ export function getErrorSchemas(endpoint: AnyWithProps): [Schema.Top, ...Array<S
175
175
  const schemas = new Set<Schema.Top>(endpoint.error)
176
176
  for (const middleware of endpoint.middlewares) {
177
177
  const key = middleware as any as HttpApiMiddleware.AnyService
178
- if (key.error !== undefined) {
179
- schemas.add(key.error)
178
+ for (const schema of key.error) {
179
+ schemas.add(schema)
180
180
  }
181
181
  }
182
182
  return Arr.append(Array.from(schemas), BadRequestFromSchemaError)