effect-start 0.9.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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +109 -0
  3. package/package.json +57 -0
  4. package/src/Bundle.ts +167 -0
  5. package/src/BundleFiles.ts +174 -0
  6. package/src/BundleHttp.test.ts +160 -0
  7. package/src/BundleHttp.ts +259 -0
  8. package/src/Commander.test.ts +1378 -0
  9. package/src/Commander.ts +672 -0
  10. package/src/Datastar.test.ts +267 -0
  11. package/src/Datastar.ts +68 -0
  12. package/src/Effect_HttpRouter.test.ts +570 -0
  13. package/src/EncryptedCookies.test.ts +427 -0
  14. package/src/EncryptedCookies.ts +451 -0
  15. package/src/FileHttpRouter.test.ts +207 -0
  16. package/src/FileHttpRouter.ts +122 -0
  17. package/src/FileRouter.ts +405 -0
  18. package/src/FileRouterCodegen.test.ts +598 -0
  19. package/src/FileRouterCodegen.ts +251 -0
  20. package/src/FileRouter_files.test.ts +64 -0
  21. package/src/FileRouter_path.test.ts +132 -0
  22. package/src/FileRouter_tree.test.ts +126 -0
  23. package/src/FileSystemExtra.ts +102 -0
  24. package/src/HttpAppExtra.ts +127 -0
  25. package/src/Hyper.ts +194 -0
  26. package/src/HyperHtml.test.ts +90 -0
  27. package/src/HyperHtml.ts +139 -0
  28. package/src/HyperNode.ts +37 -0
  29. package/src/JsModule.test.ts +14 -0
  30. package/src/JsModule.ts +116 -0
  31. package/src/PublicDirectory.test.ts +280 -0
  32. package/src/PublicDirectory.ts +108 -0
  33. package/src/Route.test.ts +873 -0
  34. package/src/Route.ts +992 -0
  35. package/src/Router.ts +80 -0
  36. package/src/SseHttpResponse.ts +55 -0
  37. package/src/Start.ts +133 -0
  38. package/src/StartApp.ts +43 -0
  39. package/src/StartHttp.ts +42 -0
  40. package/src/StreamExtra.ts +146 -0
  41. package/src/TestHttpClient.test.ts +54 -0
  42. package/src/TestHttpClient.ts +100 -0
  43. package/src/bun/BunBundle.test.ts +277 -0
  44. package/src/bun/BunBundle.ts +309 -0
  45. package/src/bun/BunBundle_imports.test.ts +50 -0
  46. package/src/bun/BunFullstackServer.ts +45 -0
  47. package/src/bun/BunFullstackServer_httpServer.ts +541 -0
  48. package/src/bun/BunImportTrackerPlugin.test.ts +77 -0
  49. package/src/bun/BunImportTrackerPlugin.ts +97 -0
  50. package/src/bun/BunTailwindPlugin.test.ts +335 -0
  51. package/src/bun/BunTailwindPlugin.ts +322 -0
  52. package/src/bun/BunVirtualFilesPlugin.ts +59 -0
  53. package/src/bun/index.ts +4 -0
  54. package/src/client/Overlay.ts +34 -0
  55. package/src/client/ScrollState.ts +120 -0
  56. package/src/client/index.ts +101 -0
  57. package/src/index.ts +24 -0
  58. package/src/jsx-datastar.d.ts +63 -0
  59. package/src/jsx-runtime.ts +23 -0
  60. package/src/jsx.d.ts +4402 -0
  61. package/src/testing.ts +55 -0
  62. package/src/x/cloudflare/CloudflareTunnel.ts +110 -0
  63. package/src/x/cloudflare/index.ts +1 -0
  64. package/src/x/datastar/Datastar.test.ts +267 -0
  65. package/src/x/datastar/Datastar.ts +68 -0
  66. package/src/x/datastar/index.ts +4 -0
  67. package/src/x/datastar/jsx-datastar.d.ts +63 -0
package/src/Route.ts ADDED
@@ -0,0 +1,992 @@
1
+ import * as HttpMethod from "@effect/platform/HttpMethod"
2
+ import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
3
+ import * as HttpServerRespondable from "@effect/platform/HttpServerRespondable"
4
+ import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
5
+ import * as Effect from "effect/Effect"
6
+ import * as Pipeable from "effect/Pipeable"
7
+ import * as Predicate from "effect/Predicate"
8
+ import * as Schema from "effect/Schema"
9
+ import type { YieldWrap } from "effect/Utils"
10
+ import * as HyperHtml from "./HyperHtml.ts"
11
+
12
+ export {
13
+ pipe,
14
+ } from "effect/Function"
15
+
16
+ type RouteModule = typeof import("./Route.ts")
17
+
18
+ /**
19
+ * 'this' argument type for {@link RouteBuilder} functions.
20
+ * Its value depend on how the function is called as described below.
21
+ */
22
+ type Self =
23
+ /**
24
+ * Called as {@link RouteSet} method:
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * let route: Route
29
+ *
30
+ * route.text("Hello")
31
+ *
32
+ * ```
33
+ */
34
+ | RouteSet.Default
35
+ | RouteSet<Route.Empty, RouteSchemas>
36
+ /**
37
+ * Called from namespaced import.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * import * as Route from "./Route.ts"
42
+ *
43
+ * let route: Route
44
+ *
45
+ * Route.text("Hello")
46
+ *
47
+ * ```
48
+ */
49
+ | RouteModule
50
+ /**
51
+ * Called directly from exported function.
52
+ * Disencouraged but possible.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * import { text } from "./Route.ts"
57
+ *
58
+ * text("Hello")
59
+ *
60
+ * ```
61
+ */
62
+ | undefined
63
+
64
+ const TypeId: unique symbol = Symbol.for("effect-start/Route")
65
+ const RouteSetTypeId: unique symbol = Symbol.for("effect-start/RouteSet")
66
+
67
+ export type RouteMethod =
68
+ | "*"
69
+ | HttpMethod.HttpMethod
70
+
71
+ export type RoutePath = `/${string}`
72
+
73
+ /**
74
+ * Route media type used for content negotiation.
75
+ * This allows to create routes that serve different media types
76
+ * for the same path & method, depending on the `Accept` header
77
+ * of the request.
78
+ */
79
+ type RouteMedia =
80
+ | "*"
81
+ | "text/plain"
82
+ | "text/html"
83
+ | "application/json"
84
+
85
+ export type RouteHandler<
86
+ A = unknown,
87
+ E = any,
88
+ R = any,
89
+ > =
90
+ /**
91
+ * A handler that contains raw value.
92
+ * Can be consumed from other handlers to build more complex responses.
93
+ * For example, a Route can render markdown for API/AI consumption
94
+ * and another Route can wrap it in HTML for browsers.
95
+ */
96
+ | RouteHandler.Value<A, E, R>
97
+ /**
98
+ * A handler returns `HttpServerResponse`.
99
+ * Should not be consumed with caution: if body is a stream,
100
+ * consuming it in another handler may break the stream.
101
+ */
102
+ | RouteHandler.Encoded<E, R>
103
+
104
+ export namespace RouteHandler {
105
+ export type Value<
106
+ A = unknown,
107
+ E = any,
108
+ R = any,
109
+ > = Effect.Effect<
110
+ {
111
+ [HttpServerRespondable.symbol]: () => Effect.Effect<
112
+ HttpServerResponse.HttpServerResponse,
113
+ E,
114
+ R
115
+ >
116
+ raw: A
117
+ },
118
+ E,
119
+ R
120
+ >
121
+
122
+ export type Encoded<E = any, R = any> = Effect.Effect<
123
+ HttpServerResponse.HttpServerResponse,
124
+ E,
125
+ R
126
+ >
127
+ }
128
+
129
+ /**
130
+ * Helper type for a value that can be a single item or an array.
131
+ */
132
+ export type OneOrMany<T> = T | T[] | readonly T[]
133
+
134
+ export type RouteSchemas = {
135
+ readonly PathParams?: Schema.Struct<any>
136
+ readonly UrlParams?: Schema.Struct<any>
137
+ readonly Payload?: Schema.Schema.Any
138
+ readonly Success?: Schema.Schema.Any
139
+ readonly Error?: Schema.Schema.Any
140
+ readonly Headers?: Schema.Struct<any>
141
+ }
142
+
143
+ export namespace RouteSchemas {
144
+ export type Empty = {
145
+ readonly PathParams?: never
146
+ readonly UrlParams?: never
147
+ readonly Payload?: never
148
+ readonly Success?: never
149
+ readonly Error?: never
150
+ readonly Headers?: never
151
+ }
152
+ }
153
+
154
+ export interface Route<
155
+ out Method extends RouteMethod = "*",
156
+ out Media extends RouteMedia = "*",
157
+ out Handler extends RouteHandler = RouteHandler,
158
+ out Schemas extends RouteSchemas = RouteSchemas.Empty,
159
+ > extends RouteSet<[Route.Default], Schemas> {
160
+ [TypeId]: typeof TypeId
161
+ readonly method: Method
162
+ readonly media: Media
163
+ readonly handler: Handler
164
+ readonly schemas: Schemas
165
+ }
166
+
167
+ /**
168
+ * Describes a single route that varies by method & media type.
169
+ *
170
+ * Implements {@link RouteSet} interface that contains itself.
171
+ */
172
+ export namespace Route {
173
+ export type Data<
174
+ Method extends RouteMethod = RouteMethod,
175
+ Media extends RouteMedia = RouteMedia,
176
+ Handler extends RouteHandler = RouteHandler,
177
+ Schemas extends RouteSchemas = RouteSchemas.Empty,
178
+ > = {
179
+ readonly method: Method
180
+ readonly media: Media
181
+ readonly handler: Handler
182
+ readonly schemas: Schemas
183
+ }
184
+
185
+ export type Default = Route<
186
+ RouteMethod,
187
+ RouteMedia,
188
+ RouteHandler,
189
+ RouteSchemas
190
+ >
191
+
192
+ export type Tuple = readonly [Default, ...Default[]]
193
+
194
+ export type Empty = readonly []
195
+
196
+ export type Proto =
197
+ & Pipeable.Pipeable
198
+ & {
199
+ [TypeId]: typeof TypeId
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Consists of function to build {@link RouteSet}.
205
+ * This should include all exported functions in this module ({@link RouteModule})
206
+ * that have `this` as {@link Self}.
207
+ *
208
+ * Method functions, like {@link post}, modify the method of existing routes.
209
+ * Media functions, like {@link json}, create new routes with specific media type.
210
+ */
211
+ type RouteBuilder = {
212
+ post: typeof post
213
+ get: typeof get
214
+ put: typeof put
215
+ patch: typeof patch
216
+ del: typeof del
217
+ options: typeof options
218
+ head: typeof head
219
+
220
+ text: typeof text
221
+ html: typeof html
222
+ json: typeof json
223
+
224
+ schemaPathParams: typeof schemaPathParams
225
+ schemaUrlParams: typeof schemaUrlParams
226
+ schemaPayload: typeof schemaPayload
227
+ schemaSuccess: typeof schemaSuccess
228
+ schemaError: typeof schemaError
229
+ schemaHeaders: typeof schemaHeaders
230
+ }
231
+
232
+ /**
233
+ * Set of one or many {@link Route} with chainable builder functions
234
+ * to modify the set or add new routes.
235
+ */
236
+ export type RouteSet<
237
+ M extends ReadonlyArray<Route.Default>,
238
+ Schemas extends RouteSchemas = RouteSchemas.Empty,
239
+ > =
240
+ & Pipeable.Pipeable
241
+ & RouteSet.Instance<M, Schemas>
242
+ & {
243
+ [RouteSetTypeId]: typeof RouteSetTypeId
244
+ }
245
+ & RouteBuilder
246
+
247
+ export namespace RouteSet {
248
+ export type Instance<
249
+ M extends ReadonlyArray<Route.Default> = Route.Tuple,
250
+ Schemas extends RouteSchemas = RouteSchemas.Empty,
251
+ > = {
252
+ set: M
253
+ schema: Schemas
254
+ }
255
+
256
+ export type Default = RouteSet<Route.Tuple, RouteSchemas>
257
+
258
+ export type Proto =
259
+ & {
260
+ [RouteSetTypeId]: typeof RouteSetTypeId
261
+ }
262
+ & RouteBuilder
263
+ }
264
+
265
+ export const post = makeMethodModifier("POST")
266
+ export const get = makeMethodModifier("GET")
267
+ export const put = makeMethodModifier("PUT")
268
+ export const patch = makeMethodModifier("PATCH")
269
+ export const del = makeMethodModifier("DELETE")
270
+ export const options = makeMethodModifier("OPTIONS")
271
+ export const head = makeMethodModifier("HEAD")
272
+
273
+ export const text = makeMediaFunction(
274
+ "GET",
275
+ "text/plain",
276
+ makeValueHandler<string>(HttpServerResponse.text),
277
+ )
278
+
279
+ export const html = makeMediaFunction(
280
+ "GET",
281
+ "text/html",
282
+ makeValueHandler<string | JsxObject>((raw) => {
283
+ // Check if it's a JSX element (has type and props properties)
284
+ if (isJsxObject(raw)) {
285
+ return HttpServerResponse.html(HyperHtml.renderToString(raw))
286
+ }
287
+ return HttpServerResponse.html(raw as string)
288
+ }),
289
+ )
290
+
291
+ export const json = makeMediaFunction(
292
+ "GET",
293
+ "application/json",
294
+ makeValueHandler<JsonValue>((raw) => HttpServerResponse.unsafeJson(raw)),
295
+ )
296
+
297
+ /**
298
+ * Schema type that accepts string-encoded input.
299
+ * Used for path parameters which are always strings.
300
+ */
301
+ type StringEncodedSchema =
302
+ | Schema.Schema<any, string, any>
303
+ // TODO: we accept PropertySignature to support Schema.optional
304
+ // not great but Effect 4 should be better about it
305
+ | Schema.PropertySignature.All
306
+
307
+ /**
308
+ * Schema type that accepts string or string array encoded input.
309
+ * Used for URL params and headers which can have multiple values.
310
+ */
311
+ type StringOrArrayEncodedSchema =
312
+ | Schema.Schema<any, OneOrMany<string>, any>
313
+ | Schema.PropertySignature.All
314
+
315
+ /**
316
+ * Helper type to extract the Encoded type from a Schema.
317
+ */
318
+ type GetEncoded<S> = S extends { Encoded: infer E } ? E : never
319
+
320
+ /**
321
+ * Check if a schema's encoded type is string.
322
+ */
323
+ type IsStringEncoded<S> = S extends Schema.PropertySignature.All ? true
324
+ : GetEncoded<S> extends string ? true
325
+ : false
326
+
327
+ /**
328
+ * Check if a schema's encoded type is string or string array.
329
+ */
330
+ type IsStringOrArrayEncoded<S> = S extends Schema.PropertySignature.All ? true
331
+ : GetEncoded<S> extends OneOrMany<string> ? true
332
+ : false
333
+
334
+ /**
335
+ * Validate that all fields have string-encoded schemas.
336
+ */
337
+ type ValidateStringEncodedFields<T extends Record<PropertyKey, any>> = {
338
+ [K in keyof T]: IsStringEncoded<T[K]> extends true ? T[K]
339
+ : StringEncodedSchema
340
+ }
341
+
342
+ /**
343
+ * Validate that all fields have string or array-encoded schemas.
344
+ */
345
+ type ValidateStringOrArrayEncodedFields<T extends Record<PropertyKey, any>> = {
346
+ [K in keyof T]: IsStringOrArrayEncoded<T[K]> extends true ? T[K]
347
+ : StringOrArrayEncodedSchema
348
+ }
349
+
350
+ function makeSingleStringSchemaModifier<
351
+ K extends string,
352
+ >(key: K) {
353
+ return function<
354
+ S extends Self,
355
+ const Fields extends Record<PropertyKey, any>,
356
+ >(
357
+ this: S,
358
+ fieldsOrSchema: Fields extends Schema.Struct<any> ? Fields
359
+ : ValidateStringEncodedFields<Fields>,
360
+ ): S extends RouteSet<infer Routes, infer Schemas> ? RouteSet<
361
+ Routes,
362
+ & Schemas
363
+ & {
364
+ [P in K]: Fields extends Schema.Struct<infer F> ? Schema.Struct<F>
365
+ : Schema.Struct<
366
+ Fields extends Record<PropertyKey, infer _> ? Fields : never
367
+ >
368
+ }
369
+ >
370
+ : RouteSet<
371
+ [],
372
+ {
373
+ [P in K]: Fields extends Schema.Struct<infer F> ? Schema.Struct<F>
374
+ : Schema.Struct<
375
+ Fields extends Record<PropertyKey, infer _> ? Fields : never
376
+ >
377
+ }
378
+ >
379
+ {
380
+ const baseRoutes = isRouteSet(this)
381
+ ? this.set
382
+ : []
383
+ const baseSchema = isRouteSet(this)
384
+ ? this.schema
385
+ : {} as RouteSchemas.Empty
386
+
387
+ const schema = Schema.isSchema(fieldsOrSchema)
388
+ ? fieldsOrSchema
389
+ : Schema.Struct(fieldsOrSchema as Schema.Struct.Fields)
390
+
391
+ return makeSet(
392
+ baseRoutes as any,
393
+ {
394
+ ...baseSchema,
395
+ [key]: schema,
396
+ } as any,
397
+ ) as any
398
+ }
399
+ }
400
+
401
+ function makeMultiStringSchemaModifier<
402
+ K extends string,
403
+ >(key: K) {
404
+ return function<
405
+ S extends Self,
406
+ const Fields extends Record<PropertyKey, any>,
407
+ >(
408
+ this: S,
409
+ fieldsOrSchema: Fields extends Schema.Struct<any> ? Fields
410
+ : ValidateStringOrArrayEncodedFields<Fields>,
411
+ ): S extends RouteSet<infer Routes, infer Schemas> ? RouteSet<
412
+ Routes,
413
+ & Schemas
414
+ & {
415
+ [P in K]: Fields extends Schema.Struct<infer F> ? Schema.Struct<F>
416
+ : Schema.Struct<
417
+ Fields extends Record<PropertyKey, infer _> ? Fields : never
418
+ >
419
+ }
420
+ >
421
+ : RouteSet<
422
+ [],
423
+ {
424
+ [P in K]: Fields extends Schema.Struct<infer F> ? Schema.Struct<F>
425
+ : Schema.Struct<
426
+ Fields extends Record<PropertyKey, infer _> ? Fields : never
427
+ >
428
+ }
429
+ >
430
+ {
431
+ const baseRoutes = isRouteSet(this)
432
+ ? this.set
433
+ : []
434
+ const baseSchema = isRouteSet(this)
435
+ ? this.schema
436
+ : {} as RouteSchemas.Empty
437
+
438
+ const schema = Schema.isSchema(fieldsOrSchema)
439
+ ? fieldsOrSchema
440
+ : Schema.Struct(fieldsOrSchema as Schema.Struct.Fields)
441
+
442
+ return makeSet(
443
+ baseRoutes as any,
444
+ {
445
+ ...baseSchema,
446
+ [key]: schema,
447
+ } as any,
448
+ ) as any
449
+ }
450
+ }
451
+
452
+ function makeUnionSchemaModifier<
453
+ K extends "Payload" | "Success" | "Error",
454
+ >(key: K) {
455
+ return function<
456
+ S extends Self,
457
+ Fields extends Schema.Struct.Fields | Schema.Schema.Any,
458
+ >(
459
+ this: S,
460
+ fieldsOrSchema: Fields,
461
+ ): S extends RouteSet<infer Routes, infer Schemas> ? RouteSet<
462
+ Routes,
463
+ & Schemas
464
+ & {
465
+ [P in K]: Fields extends Schema.Schema.Any ? Fields
466
+ : Fields extends Schema.Struct.Fields ? Schema.Struct<Fields>
467
+ : never
468
+ }
469
+ >
470
+ : RouteSet<
471
+ [],
472
+ {
473
+ [P in K]: Fields extends Schema.Schema.Any ? Fields
474
+ : Fields extends Schema.Struct.Fields ? Schema.Struct<Fields>
475
+ : never
476
+ }
477
+ >
478
+ {
479
+ const baseRoutes = isRouteSet(this)
480
+ ? this.set
481
+ : []
482
+ const baseSchema = isRouteSet(this)
483
+ ? this.schema
484
+ : {} as RouteSchemas.Empty
485
+
486
+ const schema = Schema.isSchema(fieldsOrSchema)
487
+ ? fieldsOrSchema
488
+ : Schema.Struct(fieldsOrSchema as Schema.Struct.Fields)
489
+
490
+ return makeSet(
491
+ baseRoutes as any,
492
+ {
493
+ ...baseSchema,
494
+ [key]: schema,
495
+ } as any,
496
+ ) as any
497
+ }
498
+ }
499
+
500
+ export const schemaPathParams = makeSingleStringSchemaModifier("PathParams")
501
+ export const schemaUrlParams = makeMultiStringSchemaModifier("UrlParams")
502
+ export const schemaHeaders = makeMultiStringSchemaModifier("Headers")
503
+ export const schemaPayload = makeUnionSchemaModifier("Payload")
504
+ export const schemaSuccess = makeUnionSchemaModifier("Success")
505
+ export const schemaError = makeUnionSchemaModifier("Error")
506
+
507
+ const SetProto = {
508
+ [RouteSetTypeId]: RouteSetTypeId,
509
+
510
+ post,
511
+ get,
512
+ put,
513
+ patch,
514
+ del,
515
+ options,
516
+ head,
517
+
518
+ text,
519
+ html,
520
+ json,
521
+
522
+ schemaPathParams,
523
+ schemaUrlParams,
524
+ schemaPayload,
525
+ schemaSuccess,
526
+ schemaError,
527
+ schemaHeaders,
528
+ } satisfies RouteSet.Proto
529
+
530
+ const RouteProto = Object.assign(
531
+ Object.create(SetProto),
532
+ {
533
+ [TypeId]: TypeId,
534
+
535
+ pipe() {
536
+ return Pipeable.pipeArguments(this, arguments)
537
+ },
538
+ } satisfies Route.Proto,
539
+ )
540
+
541
+ export function isRoute(input: unknown): input is Route {
542
+ return Predicate.hasProperty(input, TypeId)
543
+ }
544
+
545
+ export function isRouteSet(
546
+ input: unknown,
547
+ ): input is RouteSet.Default {
548
+ return Predicate.hasProperty(input, RouteSetTypeId)
549
+ }
550
+
551
+ export type JsonValue =
552
+ | string
553
+ | number
554
+ | boolean
555
+ | null
556
+ | JsonValue[]
557
+ | {
558
+ [key: string]: JsonValue
559
+ }
560
+
561
+ /**
562
+ * Constructs a URL from HttpServerRequest.
563
+ * Handles relative URLs by using headers to determine the base URL.
564
+ */
565
+ function makeUrlFromRequest(
566
+ request: HttpServerRequest.HttpServerRequest,
567
+ ): URL {
568
+ const origin = request.headers.origin
569
+ ?? request.headers.host
570
+ ?? "http://localhost"
571
+ const protocol = request.headers["x-forwarded-proto"] ?? "http"
572
+ const host = request.headers.host ?? "localhost"
573
+ const base = origin.startsWith("http")
574
+ ? origin
575
+ : `${protocol}://${host}`
576
+ return new URL(request.url, base)
577
+ }
578
+
579
+ type RouteContextDecoded = {
580
+ readonly pathParams?: Record<string, any>
581
+ readonly urlParams?: Record<string, any>
582
+ readonly payload?: any
583
+ readonly headers?: Record<string, any>
584
+ }
585
+
586
+ /**
587
+ * Decode RouteSchemas to make context in media handlers easier to read:
588
+ * - Converts keys from PascalCase to camelCase
589
+ * - Decodes schema types to their Type representation
590
+ */
591
+ export type DecodeRouteSchemas<Schemas extends RouteSchemas> =
592
+ & (Schemas["PathParams"] extends Schema.Struct<any> ? {
593
+ pathParams: Schema.Schema.Type<Schemas["PathParams"]>
594
+ }
595
+ : {})
596
+ & (Schemas["UrlParams"] extends Schema.Struct<any> ? {
597
+ urlParams: Schema.Schema.Type<Schemas["UrlParams"]>
598
+ }
599
+ : {})
600
+ & (Schemas["Payload"] extends Schema.Schema.Any ? {
601
+ payload: Schema.Schema.Type<Schemas["Payload"]>
602
+ }
603
+ : {})
604
+ & (Schemas["Headers"] extends Schema.Struct<any> ? {
605
+ headers: Schema.Schema.Type<Schemas["Headers"]>
606
+ }
607
+ : {})
608
+
609
+ /**
610
+ * Context passed to route handler generator functions.
611
+ */
612
+ export type RouteContext<
613
+ Input extends RouteContextDecoded = {},
614
+ > = {
615
+ request: HttpServerRequest.HttpServerRequest
616
+ get url(): URL
617
+ } & Input
618
+
619
+ /**
620
+ * Extracts fields from a Schema.Struct or returns never if not a struct.
621
+ */
622
+ type ExtractStructFields<S> = S extends Schema.Struct<infer Fields> ? Fields
623
+ : never
624
+
625
+ /**
626
+ * Merges two RouteSchemas types.
627
+ * For PathParams, UrlParams, and Headers: merges struct fields.
628
+ * For Payload, Success, and Error: creates Schema.Union.
629
+ */
630
+ type MergeSchemas<
631
+ A extends RouteSchemas,
632
+ B extends RouteSchemas,
633
+ > = {
634
+ readonly PathParams: [A["PathParams"], B["PathParams"]] extends [
635
+ Schema.Struct<infer AFields>,
636
+ Schema.Struct<infer BFields>,
637
+ ] ? Schema.Struct<AFields & BFields>
638
+ : A["PathParams"] extends Schema.Struct<any> ? A["PathParams"]
639
+ : B["PathParams"] extends Schema.Struct<any> ? B["PathParams"]
640
+ : never
641
+ readonly UrlParams: [A["UrlParams"], B["UrlParams"]] extends [
642
+ Schema.Struct<infer AFields>,
643
+ Schema.Struct<infer BFields>,
644
+ ] ? Schema.Struct<AFields & BFields>
645
+ : A["UrlParams"] extends Schema.Struct<any> ? A["UrlParams"]
646
+ : B["UrlParams"] extends Schema.Struct<any> ? B["UrlParams"]
647
+ : never
648
+ readonly Payload: [A["Payload"], B["Payload"]] extends [
649
+ Schema.Schema.Any,
650
+ Schema.Schema.Any,
651
+ ] ? Schema.Union<[A["Payload"], B["Payload"]]>
652
+ : A["Payload"] extends Schema.Schema.Any ? A["Payload"]
653
+ : B["Payload"] extends Schema.Schema.Any ? B["Payload"]
654
+ : never
655
+ readonly Success: [A["Success"], B["Success"]] extends [
656
+ Schema.Schema.Any,
657
+ Schema.Schema.Any,
658
+ ] ? Schema.Union<[A["Success"], B["Success"]]>
659
+ : A["Success"] extends Schema.Schema.Any ? A["Success"]
660
+ : B["Success"] extends Schema.Schema.Any ? B["Success"]
661
+ : never
662
+ readonly Error: [A["Error"], B["Error"]] extends [
663
+ Schema.Schema.Any,
664
+ Schema.Schema.Any,
665
+ ] ? Schema.Union<[A["Error"], B["Error"]]>
666
+ : A["Error"] extends Schema.Schema.Any ? A["Error"]
667
+ : B["Error"] extends Schema.Schema.Any ? B["Error"]
668
+ : never
669
+ readonly Headers: [A["Headers"], B["Headers"]] extends [
670
+ Schema.Struct<infer AFields>,
671
+ Schema.Struct<infer BFields>,
672
+ ] ? Schema.Struct<AFields & BFields>
673
+ : A["Headers"] extends Schema.Struct<any> ? A["Headers"]
674
+ : B["Headers"] extends Schema.Struct<any> ? B["Headers"]
675
+ : never
676
+ }
677
+
678
+ /**
679
+ * Runtime function to merge two RouteSchemas.
680
+ * For PathParams, UrlParams, and Headers: merges struct fields.
681
+ * For Payload, Success, and Error: creates Schema.Union.
682
+ */
683
+ function mergeSchemas<
684
+ A extends RouteSchemas,
685
+ B extends RouteSchemas,
686
+ >(
687
+ a: A,
688
+ b: B,
689
+ ): MergeSchemas<A, B> {
690
+ const result: any = {}
691
+
692
+ const structKeys: Array<keyof RouteSchemas> = [
693
+ "PathParams",
694
+ "UrlParams",
695
+ "Headers",
696
+ ]
697
+
698
+ const unionKeys: Array<keyof RouteSchemas> = [
699
+ "Payload",
700
+ "Success",
701
+ "Error",
702
+ ]
703
+
704
+ for (const key of structKeys) {
705
+ if (a[key] && b[key]) {
706
+ const aSchema = a[key]! as Schema.Struct<any>
707
+ const bSchema = b[key]! as Schema.Struct<any>
708
+ const mergedFields = {
709
+ ...aSchema.fields,
710
+ ...bSchema.fields,
711
+ }
712
+ result[key] = Schema.Struct(mergedFields)
713
+ } else if (a[key]) {
714
+ result[key] = a[key]
715
+ } else if (b[key]) {
716
+ result[key] = b[key]
717
+ }
718
+ }
719
+
720
+ for (const key of unionKeys) {
721
+ if (a[key] && b[key]) {
722
+ result[key] = Schema.Union(a[key]!, b[key]!)
723
+ } else if (a[key]) {
724
+ result[key] = a[key]
725
+ } else if (b[key]) {
726
+ result[key] = b[key]
727
+ }
728
+ }
729
+
730
+ return result
731
+ }
732
+
733
+ function make<
734
+ Method extends RouteMethod = "*",
735
+ Media extends RouteMedia = "*",
736
+ Handler extends RouteHandler = never,
737
+ Schemas extends RouteSchemas = RouteSchemas.Empty,
738
+ >(
739
+ input: Route.Data<
740
+ Method,
741
+ Media,
742
+ Handler,
743
+ Schemas
744
+ >,
745
+ ): Route<
746
+ Method,
747
+ Media,
748
+ Handler,
749
+ Schemas
750
+ > {
751
+ const route = Object.assign(
752
+ Object.create(RouteProto),
753
+ {
754
+ set: [],
755
+ // @ts-expect-error: assigned below
756
+ schemas: input.schemas,
757
+ method: input.method,
758
+ media: input.media,
759
+ handler: input.handler,
760
+ } satisfies Route.Data,
761
+ )
762
+
763
+ route.set = [
764
+ route,
765
+ ]
766
+ route.schemas = input.schemas
767
+
768
+ return route
769
+ }
770
+
771
+ function makeSet<
772
+ M extends ReadonlyArray<Route.Default>,
773
+ Schemas extends RouteSchemas = RouteSchemas.Empty,
774
+ >(
775
+ routes: M,
776
+ schema: Schemas = {} as Schemas,
777
+ ): RouteSet<M, Schemas> {
778
+ return Object.assign(
779
+ Object.create(SetProto),
780
+ {
781
+ set: routes,
782
+ schema,
783
+ },
784
+ ) as RouteSet<M, Schemas>
785
+ }
786
+
787
+ /**
788
+ * Factory function that creates Route for a specific method & media.
789
+ * Supports both Effect values and generator functions that receive context.
790
+ */
791
+ function makeMediaFunction<
792
+ Method extends HttpMethod.HttpMethod,
793
+ Media extends RouteMedia,
794
+ HandlerFn extends (
795
+ handler: any,
796
+ ) => any,
797
+ >(
798
+ method: Method,
799
+ media: Media,
800
+ handlerFn: HandlerFn,
801
+ ) {
802
+ return function<
803
+ S extends Self,
804
+ A,
805
+ E = never,
806
+ R = never,
807
+ >(
808
+ this: S,
809
+ handler: S extends RouteSet<infer _Routes, infer Schemas> ?
810
+ | Effect.Effect<A, E, R>
811
+ | ((
812
+ context: RouteContext<DecodeRouteSchemas<Schemas>>,
813
+ ) =>
814
+ | Effect.Effect<A, E, R>
815
+ | Generator<YieldWrap<Effect.Effect<A, E, R>>, A, never>)
816
+ :
817
+ | Effect.Effect<A, E, R>
818
+ | ((
819
+ context: RouteContext<{}>,
820
+ ) =>
821
+ | Effect.Effect<A, E, R>
822
+ | Generator<YieldWrap<Effect.Effect<A, E, R>>, A, never>),
823
+ ): S extends RouteSet<infer Routes, infer Schemas> ? RouteSet<[
824
+ ...Routes,
825
+ Route<
826
+ Method,
827
+ Media,
828
+ ReturnType<HandlerFn>,
829
+ Schemas
830
+ >,
831
+ ], Schemas>
832
+ : RouteSet<[
833
+ Route<
834
+ Method,
835
+ Media,
836
+ ReturnType<HandlerFn>,
837
+ RouteSchemas.Empty
838
+ >,
839
+ ], RouteSchemas.Empty>
840
+ {
841
+ const effect = typeof handler === "function"
842
+ ? Effect.gen(function*() {
843
+ const request = yield* HttpServerRequest.HttpServerRequest
844
+ const context: RouteContext<any> = {
845
+ request,
846
+ get url() {
847
+ return makeUrlFromRequest(request)
848
+ },
849
+ }
850
+ const result = handler(context)
851
+ return yield* (typeof result === "object"
852
+ && result !== null
853
+ && Symbol.iterator in result
854
+ ? Effect.gen(() => result as any)
855
+ : result as Effect.Effect<A, E, R>)
856
+ })
857
+ : handler
858
+
859
+ const baseRoutes = isRouteSet(this)
860
+ ? this.set
861
+ : []
862
+ const baseSchema = isRouteSet(this)
863
+ ? this.schema
864
+ : {} as RouteSchemas.Empty
865
+
866
+ return makeSet(
867
+ [
868
+ ...baseRoutes,
869
+ make({
870
+ method,
871
+ media,
872
+ handler: handlerFn(effect as any) as any,
873
+ schemas: baseSchema as any,
874
+ }),
875
+ ] as any,
876
+ baseSchema as any,
877
+ ) as any
878
+ }
879
+ }
880
+
881
+ /**
882
+ * Factory to create RouteHandler.Value.
883
+ * Useful for structural handlers like JSON
884
+ * or content that can be embedded in other formats,
885
+ * like text or HTML.
886
+ */
887
+ function makeValueHandler<ExpectedRaw = string>(
888
+ responseFn: (raw: ExpectedRaw) => HttpServerResponse.HttpServerResponse,
889
+ ) {
890
+ return <A extends ExpectedRaw, E = never, R = never>(
891
+ handler: Effect.Effect<A, E, R>,
892
+ ): RouteHandler.Value<A, E, R> => {
893
+ return Effect.gen(function*() {
894
+ const raw = yield* handler
895
+
896
+ return {
897
+ [HttpServerRespondable.symbol]: () =>
898
+ Effect.succeed(responseFn(raw as ExpectedRaw)),
899
+ raw,
900
+ }
901
+ }) as RouteHandler.Value<A, E, R>
902
+ }
903
+ }
904
+
905
+ /**
906
+ * Factory function that changes method in RouteSet.
907
+ */
908
+ function makeMethodModifier<
909
+ M extends HttpMethod.HttpMethod,
910
+ >(method: M) {
911
+ return function<
912
+ S extends Self,
913
+ T extends Route.Tuple,
914
+ InSchemas extends RouteSchemas,
915
+ >(
916
+ this: S,
917
+ routes: RouteSet<T, InSchemas>,
918
+ ): S extends RouteSet<infer B, infer BaseSchemas>
919
+ // append to existing RouteSet
920
+ ? RouteSet<
921
+ [
922
+ ...B,
923
+ ...{
924
+ [K in keyof T]: T[K] extends Route<
925
+ infer _,
926
+ infer Media,
927
+ infer H,
928
+ infer RouteSchemas
929
+ > ? Route<
930
+ M,
931
+ Media,
932
+ H,
933
+ MergeSchemas<BaseSchemas, RouteSchemas>
934
+ >
935
+ : T[K]
936
+ },
937
+ ],
938
+ BaseSchemas
939
+ >
940
+ // otherwise create new RouteSet
941
+ : RouteSet<
942
+ {
943
+ [K in keyof T]: T[K] extends Route<
944
+ infer _,
945
+ infer Media,
946
+ infer H,
947
+ infer RouteSchemas
948
+ > ? Route<
949
+ M,
950
+ Media,
951
+ H,
952
+ RouteSchemas
953
+ >
954
+ : T[K]
955
+ },
956
+ InSchemas
957
+ >
958
+ {
959
+ const baseRoutes = isRouteSet(this)
960
+ ? this.set
961
+ : [] as const
962
+ const baseSchema = isRouteSet(this)
963
+ ? this.schema
964
+ : {} as RouteSchemas.Empty
965
+
966
+ return makeSet(
967
+ [
968
+ ...baseRoutes,
969
+ ...routes.set.map(route => {
970
+ return make({
971
+ ...route,
972
+ method,
973
+ schemas: mergeSchemas(baseSchema, route.schemas) as any,
974
+ })
975
+ }),
976
+ ] as any,
977
+ baseSchema as any,
978
+ ) as any
979
+ }
980
+ }
981
+
982
+ type JsxObject = {
983
+ type: any
984
+ props: any
985
+ }
986
+
987
+ function isJsxObject(value: any) {
988
+ return typeof value === "object"
989
+ && value !== null
990
+ && "type" in value
991
+ && "props" in value
992
+ }