effect-start 0.25.0 → 0.26.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 (117) hide show
  1. package/package.json +18 -86
  2. package/dist/ChildProcess.js +0 -42
  3. package/dist/Commander.js +0 -410
  4. package/dist/ContentNegotiation.js +0 -465
  5. package/dist/Cookies.js +0 -371
  6. package/dist/Development.js +0 -94
  7. package/dist/Effectify.js +0 -27
  8. package/dist/Entity.js +0 -289
  9. package/dist/Fetch.js +0 -192
  10. package/dist/FilePathPattern.js +0 -97
  11. package/dist/FileRouter.js +0 -204
  12. package/dist/FileRouterCodegen.js +0 -298
  13. package/dist/FileSystem.js +0 -132
  14. package/dist/Http.js +0 -107
  15. package/dist/PathPattern.js +0 -451
  16. package/dist/PlatformError.js +0 -40
  17. package/dist/PlatformRuntime.js +0 -71
  18. package/dist/Route.js +0 -143
  19. package/dist/RouteBody.js +0 -92
  20. package/dist/RouteError.js +0 -76
  21. package/dist/RouteHook.js +0 -64
  22. package/dist/RouteHttp.js +0 -367
  23. package/dist/RouteHttpTracer.js +0 -90
  24. package/dist/RouteMount.js +0 -86
  25. package/dist/RouteSchema.js +0 -271
  26. package/dist/RouteSse.js +0 -94
  27. package/dist/RouteTree.js +0 -119
  28. package/dist/RouteTrie.js +0 -179
  29. package/dist/SchemaExtra.js +0 -99
  30. package/dist/Socket.js +0 -40
  31. package/dist/SqlIntrospect.js +0 -515
  32. package/dist/Start.js +0 -79
  33. package/dist/StartApp.js +0 -3
  34. package/dist/StreamExtra.js +0 -135
  35. package/dist/System.js +0 -38
  36. package/dist/TuplePathPattern.js +0 -74
  37. package/dist/Unique.js +0 -226
  38. package/dist/Values.js +0 -52
  39. package/dist/bun/BunBundle.js +0 -186
  40. package/dist/bun/BunChildProcessSpawner.js +0 -142
  41. package/dist/bun/BunImportTrackerPlugin.js +0 -91
  42. package/dist/bun/BunRoute.js +0 -157
  43. package/dist/bun/BunRuntime.js +0 -41
  44. package/dist/bun/BunServer.js +0 -285
  45. package/dist/bun/BunVirtualFilesPlugin.js +0 -54
  46. package/dist/bun/_BunEnhancedResolve.js +0 -127
  47. package/dist/bun/index.js +0 -5
  48. package/dist/bundler/Bundle.js +0 -92
  49. package/dist/bundler/BundleFiles.js +0 -154
  50. package/dist/bundler/BundleRoute.js +0 -62
  51. package/dist/client/Overlay.js +0 -33
  52. package/dist/client/ScrollState.js +0 -106
  53. package/dist/client/index.js +0 -97
  54. package/dist/console/Console.js +0 -42
  55. package/dist/console/ConsoleErrors.js +0 -211
  56. package/dist/console/ConsoleLogger.js +0 -56
  57. package/dist/console/ConsoleMetrics.js +0 -72
  58. package/dist/console/ConsoleProcess.js +0 -59
  59. package/dist/console/ConsoleStore.js +0 -72
  60. package/dist/console/ConsoleTracer.js +0 -107
  61. package/dist/console/Simulation.js +0 -784
  62. package/dist/console/index.js +0 -3
  63. package/dist/console/routes/tree.js +0 -30
  64. package/dist/datastar/actions/fetch.js +0 -536
  65. package/dist/datastar/actions/peek.js +0 -13
  66. package/dist/datastar/actions/setAll.js +0 -19
  67. package/dist/datastar/actions/toggleAll.js +0 -19
  68. package/dist/datastar/attributes/attr.js +0 -49
  69. package/dist/datastar/attributes/bind.js +0 -194
  70. package/dist/datastar/attributes/class.js +0 -54
  71. package/dist/datastar/attributes/computed.js +0 -25
  72. package/dist/datastar/attributes/effect.js +0 -10
  73. package/dist/datastar/attributes/indicator.js +0 -33
  74. package/dist/datastar/attributes/init.js +0 -27
  75. package/dist/datastar/attributes/jsonSignals.js +0 -33
  76. package/dist/datastar/attributes/on.js +0 -81
  77. package/dist/datastar/attributes/onIntersect.js +0 -53
  78. package/dist/datastar/attributes/onInterval.js +0 -31
  79. package/dist/datastar/attributes/onSignalPatch.js +0 -51
  80. package/dist/datastar/attributes/ref.js +0 -11
  81. package/dist/datastar/attributes/show.js +0 -32
  82. package/dist/datastar/attributes/signals.js +0 -18
  83. package/dist/datastar/attributes/style.js +0 -57
  84. package/dist/datastar/attributes/text.js +0 -29
  85. package/dist/datastar/engine.js +0 -1145
  86. package/dist/datastar/index.js +0 -25
  87. package/dist/datastar/utils.js +0 -250
  88. package/dist/datastar/watchers/patchElements.js +0 -486
  89. package/dist/datastar/watchers/patchSignals.js +0 -14
  90. package/dist/experimental/EncryptedCookies.js +0 -328
  91. package/dist/experimental/index.js +0 -1
  92. package/dist/hyper/Hyper.js +0 -28
  93. package/dist/hyper/HyperHtml.js +0 -165
  94. package/dist/hyper/HyperNode.js +0 -13
  95. package/dist/hyper/HyperRoute.js +0 -45
  96. package/dist/hyper/html.js +0 -30
  97. package/dist/hyper/index.js +0 -5
  98. package/dist/hyper/jsx-runtime.js +0 -14
  99. package/dist/index.js +0 -8
  100. package/dist/node/NodeFileSystem.js +0 -675
  101. package/dist/node/NodeUtils.js +0 -23
  102. package/dist/sql/Sql.js +0 -8
  103. package/dist/sql/bun/index.js +0 -142
  104. package/dist/sql/index.js +0 -1
  105. package/dist/sql/libsql/index.js +0 -156
  106. package/dist/sql/mssql/docker.js +0 -110
  107. package/dist/sql/mssql/index.js +0 -194
  108. package/dist/testing/TestLogger.js +0 -42
  109. package/dist/testing/index.js +0 -2
  110. package/dist/testing/utils.js +0 -61
  111. package/dist/x/cloudflare/CloudflareTunnel.js +0 -63
  112. package/dist/x/cloudflare/index.js +0 -1
  113. package/dist/x/tailscale/TailscaleTunnel.js +0 -94
  114. package/dist/x/tailscale/index.js +0 -1
  115. package/dist/x/tailwind/TailwindPlugin.js +0 -294
  116. package/dist/x/tailwind/compile.js +0 -210
  117. package/dist/x/tailwind/plugin.js +0 -17
package/dist/RouteHttp.js DELETED
@@ -1,367 +0,0 @@
1
- import * as Cause from "effect/Cause"
2
- import * as Effect from "effect/Effect"
3
- import * as FiberId from "effect/FiberId"
4
- import * as FiberRef from "effect/FiberRef"
5
- import * as HashSet from "effect/HashSet"
6
- import * as Option from "effect/Option"
7
- import * as Runtime from "effect/Runtime"
8
- import * as Stream from "effect/Stream"
9
- import * as ContentNegotiation from "./ContentNegotiation.js"
10
- import * as Entity from "./Entity.js"
11
- import * as Route from "./Route.js"
12
- import * as RouteHttpTracer from "./RouteHttpTracer.js"
13
- import * as RouteTree from "./RouteTree.js"
14
- import * as StreamExtra from "./StreamExtra.js"
15
-
16
- // Used to match Accept headers against available route formats.
17
- // text/* matches any text type (text/plain, text/event-stream, text/markdown, etc.)
18
- const formatToMediaType = /** @type {const} */ {
19
- text: "text/*",
20
- html: "text/html",
21
- json: "application/json",
22
- bytes: "application/octet-stream",
23
- }
24
-
25
- // Used after content negotiation to determine which format was selected.
26
- const mediaTypeToFormat = /** @type {const} */ {
27
- "text/*": "text",
28
- "text/html": "html",
29
- "application/json": "json",
30
- "application/octet-stream": "bytes",
31
- }
32
-
33
- /**
34
- * A synthetic fiber used to tag interruptions caused by client disconnects.
35
- * Number stands for HTTP status 499 "Client Closed Request".
36
- * This is what @effect/platform does to signal request cancelation.
37
- */
38
- export const clientAbortFiberId = FiberId.runtime(-499, 0)
39
-
40
- const isClientAbort = (cause) =>
41
- Cause.isInterruptedOnly(cause) &&
42
- HashSet.some(Cause.interruptors(cause), (id) => id === clientAbortFiberId)
43
-
44
- const getStatusFromCause = (cause) => {
45
- const failure = Cause.failureOption(cause)
46
-
47
- if (failure._tag === "Some") {
48
- const error = failure.value
49
- if (error._tag === "ParseError" || error._tag === "RequestBodyError") {
50
- return 400
51
- }
52
- }
53
-
54
- return 500
55
- }
56
-
57
- const respondError = (options) =>
58
- new Response(JSON.stringify(options, null, 2), {
59
- status: options.status,
60
- headers: { "content-type": "application/json" },
61
- })
62
-
63
- function streamResponse(
64
- stream,
65
- headers,
66
- status,
67
- runtime,
68
- ) {
69
- const encoder = new TextEncoder()
70
- const byteStream = (stream).pipe(
71
- Stream.map(
72
- (chunk) =>
73
- typeof chunk === "string" ? encoder.encode(chunk) : (chunk),
74
- ),
75
- Stream.catchAll((error) =>
76
- Stream.fail(error instanceof Error ? error : new Error(String(error))),
77
- ),
78
- )
79
- return new Response(Stream.toReadableStreamRuntime(byteStream, runtime), {
80
- status,
81
- headers: headers,
82
- })
83
- }
84
-
85
- function toResponse(
86
- entity,
87
- format,
88
- runtime,
89
- ) {
90
- const contentType = Entity.type(entity)
91
- const status = entity.status ?? 200
92
- const headers = { ...entity.headers, "content-type": contentType }
93
-
94
- if (StreamExtra.isStream(entity.body)) {
95
- return Effect.succeed(streamResponse(entity.body, headers, status, runtime))
96
- }
97
-
98
- if (format === "json") {
99
- return Effect.map(
100
- entity.json,
101
- (data) => new Response(JSON.stringify(data), { status, headers }),
102
- )
103
- }
104
-
105
- if (format === "text" || format === "html") {
106
- return Effect.map(
107
- entity.text,
108
- (text) => new Response(text, { status, headers }),
109
- )
110
- }
111
-
112
- if (format === "bytes") {
113
- return Effect.map(
114
- entity.bytes,
115
- (bytes) => new Response(bytes, { status, headers }),
116
- )
117
- }
118
-
119
- return Effect.succeed(streamResponse(entity.stream, headers, status, runtime))
120
- }
121
-
122
- function determineSelectedFormat(
123
- accept,
124
- routes,
125
- ) {
126
- const formats = routes
127
- .filter((r) => Route.descriptor(r).method !== "*")
128
- .map((r) => Route.descriptor(r).format)
129
- .filter((f) => Boolean(f) && f !== "*")
130
-
131
- const uniqueFormats = [...new Set(formats)]
132
- const mediaTypes = uniqueFormats.map((f) => formatToMediaType[f]).filter(Boolean)
133
-
134
- if (mediaTypes.length === 0) {
135
- return undefined
136
- }
137
-
138
- if (!accept) {
139
- return uniqueFormats[0]
140
- }
141
-
142
- const negotiated = ContentNegotiation.media(accept, mediaTypes)
143
- if (negotiated.length > 0) {
144
- return mediaTypeToFormat[negotiated[0]]
145
- }
146
-
147
- return undefined
148
- }
149
-
150
- export const toWebHandlerRuntime = (runtime) => {
151
- const runFork = Runtime.runFork(runtime)
152
-
153
- return (routes) => {
154
- const grouped = Object.groupBy(
155
- routes,
156
- (route) => Route.descriptor(route).method?.toUpperCase() ?? "*",
157
- )
158
- const wildcards = grouped["*"] ?? []
159
- const methodGroups = {
160
- GET: undefined,
161
- POST: undefined,
162
- PUT: undefined,
163
- PATCH: undefined,
164
- DELETE: undefined,
165
- HEAD: undefined,
166
- OPTIONS: undefined,
167
- }
168
-
169
- for (const method in grouped) {
170
- if (method !== "*") {
171
- methodGroups[method] = grouped[method]
172
- }
173
- }
174
-
175
- return (request) => {
176
- const method = request.method.toUpperCase()
177
- const accept = request.headers.get("accept")
178
- const methodRoutes = methodGroups[method] ?? []
179
-
180
- if (methodRoutes.length === 0 && wildcards.length === 0) {
181
- return Promise.resolve(respondError({ status: 405, message: "method not allowed" }))
182
- }
183
-
184
- const allRoutes = [...wildcards, ...methodRoutes]
185
- const selectedFormat = determineSelectedFormat(accept, allRoutes)
186
-
187
- const specificFormats = new Set()
188
- let hasWildcardFormatRoutes = false
189
- for (const r of allRoutes) {
190
- const format = Route.descriptor(r).format
191
- if (format === "*") hasWildcardFormatRoutes = true
192
- else if (format) specificFormats.add(format)
193
- }
194
- const hasSpecificFormatRoutes = specificFormats.size > 0
195
- const varyAccept = specificFormats.size > 1
196
-
197
- if (selectedFormat === undefined && hasSpecificFormatRoutes && !hasWildcardFormatRoutes) {
198
- return Promise.resolve(respondError({ status: 406, message: "not acceptable" }))
199
- }
200
-
201
- const createChain = (initialContext) => {
202
- let index = 0
203
- let currentContext = initialContext
204
- let routePathSet = false
205
-
206
- const runNext = (passedContext) => {
207
- if (passedContext !== undefined) {
208
- currentContext = passedContext
209
- }
210
-
211
- if (index >= allRoutes.length) {
212
- return Effect.succeed(
213
- Entity.make({ status: 404, message: "route not found" }, { status: 404 }),
214
- )
215
- }
216
-
217
- const route = allRoutes[index++]
218
- const descriptor = Route.descriptor(route)
219
- const format = descriptor.format
220
- const handler = route.handler
221
-
222
- if (format && format !== "*" && format !== selectedFormat) {
223
- return runNext()
224
- }
225
-
226
- currentContext = { ...currentContext, ...descriptor }
227
-
228
- const nextArg = (ctx) => Entity.effect(Effect.suspend(() => runNext(ctx)))
229
-
230
- const routePath = descriptor["path"]
231
- if (!routePathSet && routePath !== undefined) {
232
- routePathSet = true
233
- return Effect.flatMap(Effect.currentSpan.pipe(Effect.option), (spanOption) => {
234
- if (Option.isSome(spanOption)) {
235
- spanOption.value.attribute("http.route", routePath)
236
- }
237
- return handler(currentContext, nextArg)
238
- })
239
- }
240
-
241
- return handler(currentContext, nextArg)
242
- }
243
-
244
- return runNext()
245
- }
246
-
247
- const effect = Effect.withFiberRuntime((fiber) => {
248
- const tracerDisabled =
249
- !fiber.getFiberRef(FiberRef.currentTracerEnabled) ||
250
- fiber.getFiberRef(RouteHttpTracer.currentTracerDisabledWhen)(request)
251
-
252
- const url = new URL(request.url)
253
-
254
- const innerEffect = Effect.gen(function* () {
255
- const result = yield* createChain({ request, selectedFormat })
256
-
257
- const entity = Entity.isEntity(result) ? result : Entity.make(result, { status: 200 })
258
-
259
- if (entity.status === 404 && entity.body === undefined) {
260
- return respondError({ status: 406, message: "not acceptable" })
261
- }
262
-
263
- const response = yield* toResponse(entity, selectedFormat, runtime)
264
- if (varyAccept) {
265
- response.headers.set("vary", "Accept")
266
- }
267
- return response
268
- })
269
-
270
- if (tracerDisabled) {
271
- return innerEffect
272
- }
273
-
274
- const spanNameGenerator = fiber.getFiberRef(RouteHttpTracer.currentSpanNameGenerator)
275
-
276
- return Effect.useSpan(
277
- spanNameGenerator(request),
278
- {
279
- parent: Option.getOrUndefined(RouteHttpTracer.parentSpanFromHeaders(request.headers)),
280
- kind: "server",
281
- captureStackTrace: false,
282
- },
283
- (span) => {
284
- span.attribute("http.request.method", request.method)
285
- span.attribute("url.full", url.toString())
286
- span.attribute("url.path", url.pathname)
287
- const query = url.search.slice(1)
288
- if (query !== "") {
289
- span.attribute("url.query", query)
290
- }
291
- span.attribute("url.scheme", url.protocol.slice(0, -1))
292
-
293
- const userAgent = request.headers.get("user-agent")
294
- if (userAgent !== null) {
295
- span.attribute("user_agent.original", userAgent)
296
- }
297
-
298
- return Effect.flatMap(Effect.exit(Effect.withParentSpan(innerEffect, span)), (exit) => {
299
- if (exit._tag === "Success") {
300
- span.attribute("http.response.status_code", exit.value.status)
301
- }
302
- return exit
303
- })
304
- },
305
- )
306
- })
307
-
308
- return new Promise((resolve) => {
309
- const fiber = runFork(
310
- effect.pipe(
311
- Effect.scoped,
312
- Effect.catchAllCause((cause) =>
313
- Effect.gen(function* () {
314
- yield* Effect.logError(cause)
315
- const status = getStatusFromCause(cause)
316
- const message = Cause.pretty(cause, { renderErrorCause: true })
317
- return respondError({ status, message })
318
- }),
319
- ),
320
- ),
321
- )
322
-
323
- request.signal?.addEventListener(
324
- "abort",
325
- () => {
326
- fiber.unsafeInterruptAsFork(clientAbortFiberId)
327
- },
328
- { once: true },
329
- )
330
-
331
- fiber.addObserver((exit) => {
332
- if (exit._tag === "Success") {
333
- resolve(exit.value)
334
- } else if (isClientAbort(exit.cause)) {
335
- resolve(respondError({ status: 499, message: "client closed request" }))
336
- } else {
337
- const status = getStatusFromCause(exit.cause)
338
- const message = Cause.pretty(exit.cause, { renderErrorCause: true })
339
- resolve(respondError({ status, message }))
340
- }
341
- })
342
- })
343
- }
344
- }
345
- }
346
-
347
- export const toWebHandler =
348
- toWebHandlerRuntime(Runtime.defaultRuntime)
349
-
350
- export function* walkHandles(
351
- tree,
352
- runtime = Runtime.defaultRuntime,
353
- ) {
354
- const pathGroups = new Map()
355
-
356
- for (const route of RouteTree.walk(tree)) {
357
- const path = Route.descriptor(route).path
358
- const group = pathGroups.get(path) ?? []
359
- group.push(route)
360
- pathGroups.set(path, group)
361
- }
362
-
363
- const toHandler = toWebHandlerRuntime(runtime)
364
- for (const [path, routes] of pathGroups) {
365
- yield [path, toHandler(routes)]
366
- }
367
- }
@@ -1,90 +0,0 @@
1
- import * as Effect from "effect/Effect"
2
- import * as FiberRef from "effect/FiberRef"
3
- import * as GlobalValue from "effect/GlobalValue"
4
- import * as Option from "effect/Option"
5
- import * as Tracer from "effect/Tracer"
6
-
7
- export const currentTracerDisabledWhen = GlobalValue.globalValue(
8
- Symbol.for("effect-start/RouteHttp/tracerDisabledWhen"),
9
- () => FiberRef.unsafeMake(() => false),
10
- )
11
-
12
- export const withTracerDisabledWhen = (
13
- effect,
14
- predicate,
15
- ) => Effect.locally(effect, currentTracerDisabledWhen, predicate)
16
-
17
- export const currentSpanNameGenerator = GlobalValue.globalValue(
18
- Symbol.for("effect-start/RouteHttp/spanNameGenerator"),
19
- () =>
20
- FiberRef.unsafeMake((request) => `http.server ${request.method}`),
21
- )
22
-
23
- export const withSpanNameGenerator = (
24
- effect,
25
- f,
26
- ) => Effect.locally(effect, currentSpanNameGenerator, f)
27
-
28
- const w3cTraceparent = (headers) => {
29
- const header = headers.get("traceparent")
30
- if (header === null) return Option.none()
31
-
32
- const parts = header.split("-")
33
- if (parts.length < 4) return Option.none()
34
-
35
- const [_version, traceId, spanId, flags] = parts
36
- if (!traceId || !spanId) return Option.none()
37
-
38
- return Option.some(
39
- Tracer.externalSpan({
40
- spanId,
41
- traceId,
42
- sampled: flags === "01",
43
- }),
44
- )
45
- }
46
-
47
- const b3Single = (headers) => {
48
- const header = headers.get("b3")
49
- if (header === null) return Option.none()
50
-
51
- const parts = header.split("-")
52
- if (parts.length < 2) return Option.none()
53
-
54
- const [traceId, spanId, sampledStr] = parts
55
- if (!traceId || !spanId) return Option.none()
56
-
57
- return Option.some(
58
- Tracer.externalSpan({
59
- spanId,
60
- traceId,
61
- sampled: sampledStr === "1",
62
- }),
63
- )
64
- }
65
-
66
- const xb3 = (headers) => {
67
- const traceId = headers.get("x-b3-traceid")
68
- const spanId = headers.get("x-b3-spanid")
69
- if (traceId === null || spanId === null) return Option.none()
70
-
71
- const sampled = headers.get("x-b3-sampled")
72
-
73
- return Option.some(
74
- Tracer.externalSpan({
75
- spanId,
76
- traceId,
77
- sampled: sampled === "1",
78
- }),
79
- )
80
- }
81
-
82
- export const parentSpanFromHeaders = (headers) => {
83
- let span = w3cTraceparent(headers)
84
- if (span._tag === "Some") return span
85
-
86
- span = b3Single(headers)
87
- if (span._tag === "Some") return span
88
-
89
- return xb3(headers)
90
- }
@@ -1,86 +0,0 @@
1
- import * as Function from "effect/Function"
2
- import * as Route from "./Route.js"
3
-
4
- const RouteSetTypeId = Symbol.for("effect-start/RouteSet")
5
-
6
- // oxlint-disable-next-line import/first, typescript/consistent-type-imports -- typeof import() is not an import statement
7
-
8
- export const use = makeMethodDescriber("*")
9
- export const get = makeMethodDescriber("GET")
10
- export const post = makeMethodDescriber("POST")
11
- export const put = makeMethodDescriber("PUT")
12
- export const del = makeMethodDescriber("DELETE")
13
- export const patch = makeMethodDescriber("PATCH")
14
- export const head = makeMethodDescriber("HEAD")
15
- export const options = makeMethodDescriber("OPTIONS")
16
-
17
- export const add = function (
18
- path,
19
- routes,
20
- ) {
21
- const baseItems = Route.isRouteSet(this) ? Route.items(this) : (/** @type {const} */ [])
22
-
23
- const routeSet = typeof routes === "function" ? routes(make([])) : routes
24
- const routeItems = Route.items(routeSet)
25
- const newItems = routeItems.map((item) => {
26
- const itemDescriptor = Route.descriptor(item)
27
- const concatenatedPath =
28
- typeof itemDescriptor?.path === "string" ? path + itemDescriptor.path : path
29
- const newDescriptor = { ...itemDescriptor, path: concatenatedPath }
30
- return Route.isRoute(item)
31
- ? Route.make(item.handler, newDescriptor)
32
- : Route.set(Route.items(item), newDescriptor)
33
- })
34
-
35
- return make([...baseItems, ...newItems])
36
- }
37
-
38
- const Proto = Object.assign(Object.create(null), {
39
- [RouteSetTypeId]: RouteSetTypeId,
40
- *[Symbol.iterator]() {
41
- yield* Route.items(this)
42
- },
43
- use,
44
- get,
45
- post,
46
- put,
47
- del,
48
- patch,
49
- head,
50
- options,
51
- add,
52
- })
53
-
54
- function make(items) {
55
- return Object.assign(Object.create(Proto), {
56
- [Route.RouteItems]: items,
57
- [Route.RouteDescriptor]: {},
58
- })
59
- }
60
-
61
- function makeMethodDescriber(method) {
62
- function describeMethod(
63
- ...fs
64
- ) {
65
- const baseItems = Route.isRouteSet(this) ? Route.items(this) : (/** @type {const} */ [])
66
-
67
- const methodSet = Route.set([], { method })
68
- const f = Function.flow(...(fs))
69
- const result = f(methodSet)
70
- const resultItems = Route.items(result)
71
-
72
- // Items are already flat (only Routes), just merge method into each descriptor
73
- const flattenedItems = resultItems.map((item) => {
74
- const itemDescriptor = Route.descriptor(item)
75
- const newDescriptor = { method, ...itemDescriptor }
76
- return Route.make(
77
- (item).handler,
78
- newDescriptor,
79
- )
80
- })
81
-
82
- return make([...baseItems, ...flattenedItems])
83
- }
84
- return describeMethod
85
- }
86
-