effect-start 0.13.1 → 0.15.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 (95) hide show
  1. package/package.json +13 -14
  2. package/src/Commander.test.ts +507 -245
  3. package/src/ContentNegotiation.test.ts +500 -0
  4. package/src/ContentNegotiation.ts +535 -0
  5. package/src/FileRouter.ts +16 -12
  6. package/src/{FileRouterCodegen.test.ts → FileRouterCodegen.todo.ts} +384 -219
  7. package/src/FileRouterCodegen.ts +6 -6
  8. package/src/FileRouterPattern.test.ts +93 -62
  9. package/src/FileRouter_files.test.ts +6 -6
  10. package/src/FileRouter_path.test.ts +121 -69
  11. package/src/FileRouter_tree.test.ts +62 -56
  12. package/src/FileSystemExtra.test.ts +46 -30
  13. package/src/Http.test.ts +24 -0
  14. package/src/Http.ts +25 -0
  15. package/src/HttpAppExtra.test.ts +40 -21
  16. package/src/HttpAppExtra.ts +0 -1
  17. package/src/HttpUtils.test.ts +35 -18
  18. package/src/HttpUtils.ts +2 -0
  19. package/src/PathPattern.test.ts +648 -0
  20. package/src/PathPattern.ts +483 -0
  21. package/src/Route.ts +258 -1073
  22. package/src/RouteBody.test.ts +182 -0
  23. package/src/RouteBody.ts +106 -0
  24. package/src/RouteHook.test.ts +40 -0
  25. package/src/RouteHook.ts +105 -0
  26. package/src/RouteHttp.test.ts +443 -0
  27. package/src/RouteHttp.ts +219 -0
  28. package/src/RouteMount.test.ts +468 -0
  29. package/src/RouteMount.ts +313 -0
  30. package/src/RouteSchema.test.ts +81 -0
  31. package/src/RouteSchema.ts +44 -0
  32. package/src/RouteTree.test.ts +346 -0
  33. package/src/RouteTree.ts +165 -0
  34. package/src/RouteTrie.test.ts +322 -0
  35. package/src/RouteTrie.ts +224 -0
  36. package/src/RouterPattern.test.ts +569 -548
  37. package/src/RouterPattern.ts +7 -7
  38. package/src/Start.ts +3 -37
  39. package/src/StartApp.ts +20 -16
  40. package/src/TuplePathPattern.ts +64 -0
  41. package/src/Values.ts +16 -0
  42. package/src/bun/BunBundle.test.ts +37 -43
  43. package/src/bun/BunBundle.ts +2 -2
  44. package/src/bun/BunBundle_imports.test.ts +6 -8
  45. package/src/bun/BunHttpServer.test.ts +183 -6
  46. package/src/bun/BunHttpServer.ts +56 -32
  47. package/src/bun/BunHttpServer_web.ts +18 -6
  48. package/src/bun/BunImportTrackerPlugin.test.ts +3 -3
  49. package/src/bun/BunRoute.ts +29 -210
  50. package/src/{Bundle.ts → bundler/Bundle.ts} +0 -35
  51. package/src/{BundleHttp.test.ts → bundler/BundleHttp.test.ts} +36 -64
  52. package/src/{BundleHttp.ts → bundler/BundleHttp.ts} +1 -1
  53. package/src/client/index.ts +1 -1
  54. package/src/{Effect_HttpRouter.test.ts → effect/HttpRouter.test.ts} +69 -91
  55. package/src/{EncryptedCookies.test.ts → experimental/EncryptedCookies.test.ts} +125 -64
  56. package/src/{SseHttpResponse.ts → experimental/SseHttpResponse.ts} +1 -1
  57. package/src/experimental/index.ts +2 -0
  58. package/src/hyper/Hyper.ts +89 -0
  59. package/src/{HyperHtml.test.ts → hyper/HyperHtml.test.ts} +13 -13
  60. package/src/{HyperHtml.ts → hyper/HyperHtml.ts} +2 -2
  61. package/src/{jsx.d.ts → hyper/jsx.d.ts} +1 -1
  62. package/src/index.ts +1 -21
  63. package/src/middlewares/BasicAuthMiddleware.test.ts +29 -19
  64. package/src/middlewares/index.ts +1 -0
  65. package/src/{NodeFileSystem.ts → node/FileSystem.ts} +6 -2
  66. package/src/{TestHttpClient.test.ts → testing/TestHttpClient.test.ts} +27 -27
  67. package/src/{TestHttpClient.ts → testing/TestHttpClient.ts} +0 -1
  68. package/src/{TestLogger.test.ts → testing/TestLogger.test.ts} +27 -11
  69. package/src/testing/index.ts +3 -0
  70. package/src/x/datastar/Datastar.test.ts +47 -48
  71. package/src/x/datastar/Datastar.ts +1 -1
  72. package/src/x/tailwind/TailwindPlugin.test.ts +56 -58
  73. package/src/x/tailwind/TailwindPlugin.ts +23 -17
  74. package/src/x/tailwind/plugin.ts +1 -1
  75. package/src/FileHttpRouter.test.ts +0 -239
  76. package/src/FileHttpRouter.ts +0 -194
  77. package/src/Hyper.ts +0 -194
  78. package/src/JsModule.test.ts +0 -14
  79. package/src/JsModule.ts +0 -116
  80. package/src/PublicDirectory.test.ts +0 -280
  81. package/src/PublicDirectory.ts +0 -108
  82. package/src/Route.test.ts +0 -1370
  83. package/src/RouteRender.ts +0 -40
  84. package/src/Router.test.ts +0 -375
  85. package/src/Router.ts +0 -255
  86. package/src/StartHttp.ts +0 -42
  87. package/src/bun/BunRoute.test.ts +0 -480
  88. package/src/bun/BunRoute_bundles.test.ts +0 -219
  89. /package/src/{BundleFiles.ts → bundler/BundleFiles.ts} +0 -0
  90. /package/src/{EncryptedCookies.ts → experimental/EncryptedCookies.ts} +0 -0
  91. /package/src/{HyperNode.ts → hyper/HyperNode.ts} +0 -0
  92. /package/src/{jsx-runtime.ts → hyper/jsx-runtime.ts} +0 -0
  93. /package/src/{NodeUtils.ts → node/Utils.ts} +0 -0
  94. /package/src/{TestLogger.ts → testing/TestLogger.ts} +0 -0
  95. /package/src/{testing.ts → testing/utils.ts} +0 -0
@@ -0,0 +1,468 @@
1
+ import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
2
+ import * as test from "bun:test"
3
+ import * as Effect from "effect/Effect"
4
+ import * as ParseResult from "effect/ParseResult"
5
+ import * as Schema from "effect/Schema"
6
+ import * as Route from "./Route.ts"
7
+ import * as RouteMount from "./RouteMount.ts"
8
+
9
+ test.it("uses GET method", async () => {
10
+ const route = Route.get(
11
+ Route.text((context) =>
12
+ Effect.gen(function*() {
13
+ test
14
+ .expectTypeOf(context)
15
+ .toMatchObjectType<{
16
+ method: "GET"
17
+ format: "text"
18
+ }>()
19
+ test
20
+ .expect(context)
21
+ .toEqual({
22
+ method: "GET",
23
+ format: "text",
24
+ })
25
+
26
+ return "Hello, World!"
27
+ })
28
+ ),
29
+ )
30
+
31
+ test
32
+ .expectTypeOf(route)
33
+ .toExtend<
34
+ Route.RouteSet.RouteSet<
35
+ {},
36
+ {},
37
+ [
38
+ Route.Route.Route<
39
+ {
40
+ method: "GET"
41
+ format: "text"
42
+ },
43
+ {},
44
+ string
45
+ >,
46
+ ]
47
+ >
48
+ >()
49
+
50
+ test
51
+ .expect(Route.items(route))
52
+ .toHaveLength(1)
53
+
54
+ test
55
+ .expectTypeOf<Route.Route.Context<typeof route>>()
56
+ .toMatchTypeOf<{
57
+ method: "GET"
58
+ format: "text"
59
+ }>()
60
+ })
61
+
62
+ test.it("uses GET & POST method", async () => {
63
+ const route = Route
64
+ .get(
65
+ Route.text((r) => {
66
+ test
67
+ .expectTypeOf(r.method)
68
+ .toEqualTypeOf<"GET">()
69
+ test
70
+ .expectTypeOf(r.format)
71
+ .toEqualTypeOf<"text">()
72
+
73
+ return Effect.succeed("get")
74
+ }),
75
+ )
76
+ .post(
77
+ Route.text((r) => {
78
+ test
79
+ .expectTypeOf(r.method)
80
+ .toEqualTypeOf<"POST">()
81
+ test
82
+ .expectTypeOf(r.format)
83
+ .toEqualTypeOf<"text">()
84
+
85
+ return Effect.succeed("post")
86
+ }),
87
+ )
88
+
89
+ test
90
+ .expect(Route.items(route))
91
+ .toHaveLength(2)
92
+
93
+ type Items = Route.RouteSet.Items<typeof route>
94
+
95
+ test
96
+ .expectTypeOf<Items[0]>()
97
+ .toExtend<
98
+ Route.Route.Route<
99
+ {
100
+ method: "GET"
101
+ format: "text"
102
+ },
103
+ {},
104
+ string
105
+ >
106
+ >()
107
+
108
+ test
109
+ .expectTypeOf<Items[1]>()
110
+ .toExtend<
111
+ Route.Route.Route<
112
+ {
113
+ method: "POST"
114
+ format: "text"
115
+ },
116
+ {},
117
+ string
118
+ >
119
+ >()
120
+ })
121
+
122
+ test.describe("use", () => {
123
+ test.it(`infers context`, () => {
124
+ const routes = Route
125
+ .use(
126
+ Route.filter({
127
+ context: {
128
+ answer: 42,
129
+ },
130
+ }),
131
+ )
132
+ .use(
133
+ Route.filter((ctx) => {
134
+ test
135
+ .expectTypeOf(ctx)
136
+ .toMatchObjectType<{
137
+ answer: number
138
+ }>()
139
+
140
+ return {
141
+ context: {
142
+ doubledAnswer: ctx.answer * 2,
143
+ },
144
+ }
145
+ }),
146
+ )
147
+ .get(
148
+ Route.filter({
149
+ context: {
150
+ getter: true,
151
+ },
152
+ }),
153
+ Route.text(function*(ctx) {
154
+ test
155
+ .expectTypeOf(ctx)
156
+ .toMatchObjectType<{
157
+ method: "GET"
158
+ format: "text"
159
+ answer: number
160
+ doubledAnswer: number
161
+ getter: boolean
162
+ }>()
163
+
164
+ return `The answer is ${ctx.answer}`
165
+ }),
166
+ )
167
+ .post(
168
+ Route.json(function*(ctx) {
169
+ test
170
+ .expectTypeOf(ctx)
171
+ .not
172
+ .toHaveProperty("getter")
173
+
174
+ test
175
+ .expectTypeOf(ctx)
176
+ .toMatchObjectType<{
177
+ method: "POST"
178
+ answer: number
179
+ doubledAnswer: number
180
+ format: "json"
181
+ }>()
182
+
183
+ return {
184
+ ok: true,
185
+ answer: ctx.answer,
186
+ }
187
+ }),
188
+ )
189
+
190
+ type Items = Route.RouteSet.Items<typeof routes>
191
+
192
+ test
193
+ .expect(Route.items(routes))
194
+ .toHaveLength(5)
195
+
196
+ // First use() - adds answer context (method flattened into Route descriptor)
197
+ test
198
+ .expectTypeOf<Route.RouteSet.Descriptor<Items[0]>>()
199
+ .toMatchObjectType<{ method: "*" }>()
200
+
201
+ test
202
+ .expectTypeOf<Route.Route.Bindings<Items[0]>>()
203
+ .toMatchTypeOf<{ answer: number }>()
204
+
205
+ test
206
+ .expectTypeOf<Route.Route.Context<Items[0]>>()
207
+ .toMatchTypeOf<{
208
+ method: "*"
209
+ answer: number
210
+ }>()
211
+
212
+ // Second use() - adds doubledAnswer context, inherits answer binding (method flattened into Route descriptor)
213
+ test
214
+ .expectTypeOf<Route.RouteSet.Descriptor<Items[1]>>()
215
+ .toMatchObjectType<{ method: "*" }>()
216
+
217
+ test
218
+ .expectTypeOf<Route.Route.Bindings<Items[1]>>()
219
+ .toMatchTypeOf<{
220
+ answer: number
221
+ doubledAnswer: number
222
+ }>()
223
+
224
+ test
225
+ .expectTypeOf<Route.Route.Context<Items[1]>>()
226
+ .toMatchTypeOf<{
227
+ method: "*"
228
+ answer: number
229
+ doubledAnswer: number
230
+ }>()
231
+
232
+ // GET filter route
233
+ test
234
+ .expectTypeOf<Route.RouteSet.Descriptor<Items[2]>>()
235
+ .toMatchObjectType<{ method: "GET" }>()
236
+
237
+ test
238
+ .expectTypeOf<Route.Route.Bindings<Items[2]>>()
239
+ .toMatchTypeOf<{
240
+ answer: number
241
+ doubledAnswer: number
242
+ getter: boolean
243
+ }>()
244
+
245
+ test
246
+ .expectTypeOf<Route.Route.Context<Items[2]>>()
247
+ .toMatchTypeOf<{
248
+ method: "GET"
249
+ answer: number
250
+ doubledAnswer: number
251
+ getter: boolean
252
+ }>()
253
+
254
+ // GET text route
255
+ test
256
+ .expectTypeOf<Route.RouteSet.Descriptor<Items[3]>>()
257
+ .toMatchObjectType<{
258
+ method: "GET"
259
+ format: "text"
260
+ }>()
261
+
262
+ test
263
+ .expectTypeOf<Route.Route.Bindings<Items[3]>>()
264
+ .toMatchTypeOf<{
265
+ answer: number
266
+ doubledAnswer: number
267
+ getter: boolean
268
+ }>()
269
+
270
+ test
271
+ .expectTypeOf<Route.Route.Context<Items[3]>>()
272
+ .toMatchTypeOf<{
273
+ method: "GET"
274
+ format: "text"
275
+ answer: number
276
+ doubledAnswer: number
277
+ getter: boolean
278
+ }>()
279
+
280
+ // POST route - inherits answer/doubledAnswer only (no getter since that was in GET branch)
281
+ test
282
+ .expectTypeOf<Route.RouteSet.Descriptor<Items[4]>>()
283
+ .toMatchObjectType<{
284
+ method: "POST"
285
+ format: "json"
286
+ }>()
287
+
288
+ test
289
+ .expectTypeOf<Route.Route.Bindings<Items[4]>>()
290
+ .toMatchTypeOf<{
291
+ answer: number
292
+ doubledAnswer: number
293
+ }>()
294
+
295
+ test
296
+ .expectTypeOf<Route.Route.Context<Items[4]>>()
297
+ .toMatchTypeOf<{
298
+ method: "POST"
299
+ format: "json"
300
+ answer: number
301
+ doubledAnswer: number
302
+ }>()
303
+ })
304
+ })
305
+
306
+ test.it("Builder extends RouteSet", () => {
307
+ const builder = Route
308
+ .use(
309
+ Route.filter({
310
+ context: { answer: 42 },
311
+ }),
312
+ )
313
+ .get(Route.text("Hello"))
314
+
315
+ test
316
+ .expectTypeOf(builder)
317
+ .toExtend<Route.RouteSet.Any>()
318
+
319
+ test
320
+ .expectTypeOf(builder)
321
+ .toExtend<Route.RouteSet.RouteSet<any, any, any>>()
322
+
323
+ // Verify it has the TypeId
324
+ test
325
+ .expect(builder[Route.TypeId])
326
+ .toBe(Route.TypeId)
327
+
328
+ // Verify it's iterable (from RouteSet.Proto)
329
+ test
330
+ .expect(Symbol.iterator in builder)
331
+ .toBe(true)
332
+ })
333
+
334
+ test.it("schemaHeaders flattens method into route descriptor", () => {
335
+ const routes = Route
336
+ .use(
337
+ Route.schemaHeaders(
338
+ Schema.Struct({
339
+ "hello": Schema.String,
340
+ }),
341
+ ),
342
+ )
343
+ .get(
344
+ Route.schemaHeaders(
345
+ Schema.Struct({
346
+ "x-custom-header": Schema.String,
347
+ }),
348
+ ),
349
+ Route.html(function*(_ctx) {
350
+ return `<h1>Hello, world!</h1>`
351
+ }),
352
+ )
353
+ .post(
354
+ Route.filter({
355
+ context: {
356
+ postOnly: "yo",
357
+ },
358
+ }),
359
+ Route.text(function*(ctx) {
360
+ return "hello"
361
+ }),
362
+ )
363
+
364
+ type Items = Route.RouteSet.Items<typeof routes>
365
+
366
+ // Assert routes is a Builder with specific descriptor
367
+ test
368
+ .expectTypeOf(routes)
369
+ .toExtend<
370
+ RouteMount.RouteMount.Builder<
371
+ {},
372
+ Items
373
+ >
374
+ >()
375
+
376
+ test
377
+ .expect(Route.items(routes))
378
+ .toHaveLength(5)
379
+
380
+ // First use() - schemaHeaders with method "*"
381
+ test
382
+ .expectTypeOf<Items[0]>()
383
+ .toExtend<
384
+ Route.Route.Route<
385
+ { method: "*" },
386
+ {
387
+ headers: {
388
+ readonly hello: string
389
+ }
390
+ },
391
+ void,
392
+ ParseResult.ParseError,
393
+ HttpServerRequest.HttpServerRequest
394
+ >
395
+ >()
396
+
397
+ // GET schemaHeaders
398
+ test
399
+ .expectTypeOf<Items[1]>()
400
+ .toExtend<
401
+ Route.Route.Route<
402
+ { method: "GET" },
403
+ {
404
+ headers: {
405
+ readonly hello: string
406
+ readonly "x-custom-header": string
407
+ }
408
+ },
409
+ void,
410
+ ParseResult.ParseError,
411
+ HttpServerRequest.HttpServerRequest
412
+ >
413
+ >()
414
+
415
+ // GET html route
416
+ test
417
+ .expectTypeOf<Items[2]>()
418
+ .toExtend<
419
+ Route.Route.Route<
420
+ {
421
+ method: "GET"
422
+ format: "html"
423
+ },
424
+ {
425
+ headers: {
426
+ readonly hello: string
427
+ readonly "x-custom-header": string
428
+ }
429
+ },
430
+ any
431
+ >
432
+ >()
433
+
434
+ // POST filter route
435
+ test
436
+ .expectTypeOf<Items[3]>()
437
+ .toExtend<
438
+ Route.Route.Route<
439
+ { method: "POST" },
440
+ {
441
+ headers: {
442
+ hello: string
443
+ }
444
+ postOnly: string
445
+ },
446
+ void
447
+ >
448
+ >()
449
+
450
+ // POST text route - verify descriptor and bindings separately
451
+ test
452
+ .expectTypeOf<Route.RouteSet.Descriptor<Items[4]>>()
453
+ .toMatchObjectType<{
454
+ method: "POST"
455
+ format: "text"
456
+ }>()
457
+
458
+ test
459
+ .expectTypeOf<Route.Route.Context<Items[4]>>()
460
+ .toMatchTypeOf<{
461
+ method: "POST"
462
+ format: "text"
463
+ headers: {
464
+ readonly hello: string
465
+ }
466
+ postOnly: string
467
+ }>()
468
+ })