@effect-app/infra 4.0.0-beta.22 → 4.0.0-beta.221
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.
- package/CHANGELOG.md +1648 -0
- package/_check.sh +1 -1
- package/dist/CUPS.d.ts +12 -7
- package/dist/CUPS.d.ts.map +1 -1
- package/dist/CUPS.js +16 -12
- package/dist/Emailer/Sendgrid.d.ts +15 -15
- package/dist/Emailer/Sendgrid.d.ts.map +1 -1
- package/dist/Emailer/Sendgrid.js +20 -16
- package/dist/Emailer/fake.d.ts +1 -1
- package/dist/Emailer/fake.js +2 -2
- package/dist/Emailer/service.d.ts +13 -4
- package/dist/Emailer/service.d.ts.map +1 -1
- package/dist/Emailer/service.js +4 -3
- package/dist/Emailer.d.ts +1 -1
- package/dist/MainFiberSet.d.ts +12 -9
- package/dist/MainFiberSet.d.ts.map +1 -1
- package/dist/MainFiberSet.js +7 -3
- package/dist/Model/Repository/Registry.d.ts +21 -0
- package/dist/Model/Repository/Registry.d.ts.map +1 -0
- package/dist/Model/Repository/Registry.js +18 -0
- package/dist/Model/Repository/ext.d.ts +35 -16
- package/dist/Model/Repository/ext.d.ts.map +1 -1
- package/dist/Model/Repository/ext.js +60 -3
- package/dist/Model/Repository/internal/internal.d.ts +9 -6
- package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
- package/dist/Model/Repository/internal/internal.js +115 -51
- package/dist/Model/Repository/legacy.d.ts +4 -2
- package/dist/Model/Repository/legacy.d.ts.map +1 -1
- package/dist/Model/Repository/makeRepo.d.ts +10 -6
- package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
- package/dist/Model/Repository/makeRepo.js +5 -2
- package/dist/Model/Repository/service.d.ts +32 -24
- package/dist/Model/Repository/service.d.ts.map +1 -1
- package/dist/Model/Repository/validation.d.ts +47 -18
- package/dist/Model/Repository/validation.d.ts.map +1 -1
- package/dist/Model/Repository/validation.js +6 -6
- package/dist/Model/Repository.d.ts +2 -1
- package/dist/Model/Repository.d.ts.map +1 -1
- package/dist/Model/Repository.js +2 -1
- package/dist/Model/dsl.d.ts +6 -5
- package/dist/Model/dsl.d.ts.map +1 -1
- package/dist/Model/dsl.js +2 -3
- package/dist/Model/filter/filterApi.d.ts +5 -5
- package/dist/Model/filter/filterApi.d.ts.map +1 -1
- package/dist/Model/filter/types/errors.d.ts +1 -1
- package/dist/Model/filter/types/fields.d.ts +1 -1
- package/dist/Model/filter/types/path/common.d.ts +1 -1
- package/dist/Model/filter/types/path/eager.d.ts +1 -1
- package/dist/Model/filter/types/path/eager.d.ts.map +1 -1
- package/dist/Model/filter/types/path/eager.js +1 -1
- package/dist/Model/filter/types/path/index.d.ts +1 -1
- package/dist/Model/filter/types/utils.d.ts +1 -1
- package/dist/Model/filter/types/validator.d.ts +1 -1
- package/dist/Model/filter/types.d.ts +1 -1
- package/dist/Model/query/dsl.d.ts +142 -17
- package/dist/Model/query/dsl.d.ts.map +1 -1
- package/dist/Model/query/dsl.js +190 -5
- package/dist/Model/query/new-kid-interpreter.d.ts +77 -8
- package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -1
- package/dist/Model/query/new-kid-interpreter.js +127 -6
- package/dist/Model/query.d.ts +1 -1
- package/dist/Model.d.ts +2 -1
- package/dist/Model.d.ts.map +1 -1
- package/dist/Model.js +2 -1
- package/dist/QueueMaker/SQLQueue.d.ts +7 -8
- package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
- package/dist/QueueMaker/SQLQueue.js +135 -117
- package/dist/QueueMaker/errors.d.ts +5 -3
- package/dist/QueueMaker/errors.d.ts.map +1 -1
- package/dist/QueueMaker/errors.js +4 -2
- package/dist/QueueMaker/memQueue.d.ts +9 -5
- package/dist/QueueMaker/memQueue.d.ts.map +1 -1
- package/dist/QueueMaker/memQueue.js +81 -65
- package/dist/QueueMaker/sbqueue.d.ts +8 -4
- package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
- package/dist/QueueMaker/sbqueue.js +57 -55
- package/dist/QueueMaker/service.d.ts +4 -2
- package/dist/QueueMaker/service.d.ts.map +1 -1
- package/dist/QueueMaker/service.js +1 -1
- package/dist/RequestContext.d.ts +75 -35
- package/dist/RequestContext.d.ts.map +1 -1
- package/dist/RequestContext.js +14 -14
- package/dist/RequestFiberSet.d.ts +10 -7
- package/dist/RequestFiberSet.d.ts.map +1 -1
- package/dist/RequestFiberSet.js +8 -3
- package/dist/Store/ContextMapContainer.d.ts +22 -3
- package/dist/Store/ContextMapContainer.d.ts.map +1 -1
- package/dist/Store/ContextMapContainer.js +17 -3
- package/dist/Store/Cosmos/query.d.ts +7 -2
- package/dist/Store/Cosmos/query.d.ts.map +1 -1
- package/dist/Store/Cosmos/query.js +115 -35
- package/dist/Store/Cosmos.d.ts +2 -2
- package/dist/Store/Cosmos.d.ts.map +1 -1
- package/dist/Store/Cosmos.js +343 -244
- package/dist/Store/Disk.d.ts +3 -3
- package/dist/Store/Disk.d.ts.map +1 -1
- package/dist/Store/Disk.js +76 -36
- package/dist/Store/Memory.d.ts +7 -4
- package/dist/Store/Memory.d.ts.map +1 -1
- package/dist/Store/Memory.js +251 -58
- package/dist/Store/SQL/Pg.d.ts +4 -0
- package/dist/Store/SQL/Pg.d.ts.map +1 -0
- package/dist/Store/SQL/Pg.js +233 -0
- package/dist/Store/SQL/query.d.ts +43 -0
- package/dist/Store/SQL/query.d.ts.map +1 -0
- package/dist/Store/SQL/query.js +478 -0
- package/dist/Store/SQL.d.ts +21 -0
- package/dist/Store/SQL.d.ts.map +1 -0
- package/dist/Store/SQL.js +450 -0
- package/dist/Store/codeFilter.d.ts +2 -2
- package/dist/Store/codeFilter.d.ts.map +1 -1
- package/dist/Store/codeFilter.js +6 -3
- package/dist/Store/index.d.ts +6 -3
- package/dist/Store/index.d.ts.map +1 -1
- package/dist/Store/index.js +18 -4
- package/dist/Store/service.d.ts +26 -8
- package/dist/Store/service.d.ts.map +1 -1
- package/dist/Store/service.js +25 -6
- package/dist/Store/utils.d.ts +3 -2
- package/dist/Store/utils.d.ts.map +1 -1
- package/dist/Store/utils.js +5 -5
- package/dist/Store.d.ts +1 -1
- package/dist/adapters/SQL/Model.d.ts +32 -43
- package/dist/adapters/SQL/Model.d.ts.map +1 -1
- package/dist/adapters/SQL/Model.js +30 -39
- package/dist/adapters/SQL.d.ts +1 -1
- package/dist/adapters/ServiceBus.d.ts +14 -11
- package/dist/adapters/ServiceBus.d.ts.map +1 -1
- package/dist/adapters/ServiceBus.js +30 -21
- package/dist/adapters/cosmos-client.d.ts +5 -3
- package/dist/adapters/cosmos-client.d.ts.map +1 -1
- package/dist/adapters/cosmos-client.js +5 -3
- package/dist/adapters/index.d.ts +8 -2
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +8 -2
- package/dist/adapters/logger.d.ts +2 -2
- package/dist/adapters/logger.d.ts.map +1 -1
- package/dist/adapters/memQueue.d.ts +5 -3
- package/dist/adapters/memQueue.d.ts.map +1 -1
- package/dist/adapters/memQueue.js +6 -5
- package/dist/adapters/mongo-client.d.ts +4 -3
- package/dist/adapters/mongo-client.d.ts.map +1 -1
- package/dist/adapters/mongo-client.js +5 -3
- package/dist/adapters/redis-client.d.ts +6 -3
- package/dist/adapters/redis-client.d.ts.map +1 -1
- package/dist/adapters/redis-client.js +7 -3
- package/dist/api/ContextProvider.d.ts +12 -8
- package/dist/api/ContextProvider.d.ts.map +1 -1
- package/dist/api/ContextProvider.js +9 -7
- package/dist/api/codec.d.ts +1 -1
- package/dist/api/internal/RequestContextMiddleware.d.ts +3 -3
- package/dist/api/internal/RequestContextMiddleware.d.ts.map +1 -1
- package/dist/api/internal/RequestContextMiddleware.js +10 -6
- package/dist/api/internal/auth.d.ts +45 -7
- package/dist/api/internal/auth.d.ts.map +1 -1
- package/dist/api/internal/auth.js +162 -29
- package/dist/api/internal/events.d.ts +6 -4
- package/dist/api/internal/events.d.ts.map +1 -1
- package/dist/api/internal/events.js +16 -9
- package/dist/api/internal/health.d.ts +1 -1
- package/dist/api/layerUtils.d.ts +10 -6
- package/dist/api/layerUtils.d.ts.map +1 -1
- package/dist/api/layerUtils.js +7 -6
- package/dist/api/middlewares.d.ts +1 -1
- package/dist/api/reportError.d.ts +2 -2
- package/dist/api/reportError.d.ts.map +1 -1
- package/dist/api/reportError.js +3 -2
- package/dist/api/routing/middleware/RouterMiddleware.d.ts +5 -4
- package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware.d.ts +42 -3
- package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware.js +53 -17
- package/dist/api/routing/middleware.d.ts +1 -2
- package/dist/api/routing/middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware.js +1 -2
- package/dist/api/routing/schema/jwt.d.ts +1 -1
- package/dist/api/routing/schema/jwt.d.ts.map +1 -1
- package/dist/api/routing/schema/jwt.js +3 -2
- package/dist/api/routing/tsort.d.ts +1 -1
- package/dist/api/routing/tsort.d.ts.map +1 -1
- package/dist/api/routing/utils.d.ts +4 -4
- package/dist/api/routing/utils.d.ts.map +1 -1
- package/dist/api/routing/utils.js +3 -2
- package/dist/api/routing.d.ts +84 -37
- package/dist/api/routing.d.ts.map +1 -1
- package/dist/api/routing.js +115 -45
- package/dist/api/setupRequest.d.ts +10 -6
- package/dist/api/setupRequest.d.ts.map +1 -1
- package/dist/api/setupRequest.js +15 -7
- package/dist/api/util.d.ts +1 -1
- package/dist/arbs.d.ts +2 -2
- package/dist/arbs.d.ts.map +1 -1
- package/dist/arbs.js +5 -3
- package/dist/errorReporter.d.ts +7 -5
- package/dist/errorReporter.d.ts.map +1 -1
- package/dist/errorReporter.js +22 -26
- package/dist/errors.d.ts +1 -1
- package/dist/fileUtil.d.ts +2 -2
- package/dist/fileUtil.d.ts.map +1 -1
- package/dist/fileUtil.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/logger/jsonLogger.d.ts +2 -2
- package/dist/logger/jsonLogger.d.ts.map +1 -1
- package/dist/logger/jsonLogger.js +4 -2
- package/dist/logger/logFmtLogger.d.ts +2 -2
- package/dist/logger/logFmtLogger.d.ts.map +1 -1
- package/dist/logger/logFmtLogger.js +2 -2
- package/dist/logger/shared.d.ts +2 -2
- package/dist/logger/shared.d.ts.map +1 -1
- package/dist/logger/shared.js +3 -3
- package/dist/logger.d.ts +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/otel.d.ts +75 -0
- package/dist/otel.d.ts.map +1 -0
- package/dist/otel.js +65 -0
- package/dist/rateLimit.d.ts +12 -4
- package/dist/rateLimit.d.ts.map +1 -1
- package/dist/rateLimit.js +7 -12
- package/dist/test.d.ts +3 -3
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +2 -2
- package/dist/vitest.d.ts +1 -1
- package/examples/query.ts +46 -38
- package/package.json +46 -37
- package/src/CUPS.ts +15 -11
- package/src/Emailer/Sendgrid.ts +21 -15
- package/src/Emailer/fake.ts +1 -1
- package/src/Emailer/service.ts +13 -3
- package/src/MainFiberSet.ts +9 -6
- package/src/Model/Repository/Registry.ts +34 -0
- package/src/Model/Repository/ext.ts +103 -11
- package/src/Model/Repository/internal/internal.ts +231 -149
- package/src/Model/Repository/legacy.ts +3 -1
- package/src/Model/Repository/makeRepo.ts +15 -10
- package/src/Model/Repository/service.ts +35 -23
- package/src/Model/Repository/validation.ts +5 -5
- package/src/Model/Repository.ts +1 -0
- package/src/Model/dsl.ts +5 -4
- package/src/Model/filter/types/path/eager.ts +1 -2
- package/src/Model/query/dsl.ts +353 -19
- package/src/Model/query/new-kid-interpreter.ts +211 -6
- package/src/Model.ts +1 -0
- package/src/QueueMaker/SQLQueue.ts +150 -153
- package/src/QueueMaker/errors.ts +3 -1
- package/src/QueueMaker/memQueue.ts +111 -105
- package/src/QueueMaker/sbqueue.ts +76 -88
- package/src/QueueMaker/service.ts +3 -1
- package/src/RequestContext.ts +15 -16
- package/src/RequestFiberSet.ts +8 -2
- package/src/Store/ContextMapContainer.ts +45 -2
- package/src/Store/Cosmos/query.ts +143 -44
- package/src/Store/Cosmos.ts +491 -350
- package/src/Store/Disk.ts +106 -66
- package/src/Store/Memory.ts +285 -87
- package/src/Store/SQL/Pg.ts +364 -0
- package/src/Store/SQL/query.ts +540 -0
- package/src/Store/SQL.ts +736 -0
- package/src/Store/codeFilter.ts +5 -2
- package/src/Store/index.ts +20 -3
- package/src/Store/service.ts +45 -10
- package/src/Store/utils.ts +25 -23
- package/src/adapters/SQL/Model.ts +42 -41
- package/src/adapters/ServiceBus.ts +131 -121
- package/src/adapters/cosmos-client.ts +4 -2
- package/src/adapters/index.ts +7 -0
- package/src/adapters/memQueue.ts +5 -4
- package/src/adapters/mongo-client.ts +4 -2
- package/src/adapters/redis-client.ts +6 -2
- package/src/api/ContextProvider.ts +17 -13
- package/src/api/internal/RequestContextMiddleware.ts +16 -5
- package/src/api/internal/auth.ts +248 -44
- package/src/api/internal/events.ts +19 -10
- package/src/api/layerUtils.ts +12 -8
- package/src/api/reportError.ts +2 -1
- package/src/api/routing/middleware/RouterMiddleware.ts +5 -4
- package/src/api/routing/middleware/middleware.ts +60 -15
- package/src/api/routing/middleware.ts +0 -2
- package/src/api/routing/schema/jwt.ts +2 -1
- package/src/api/routing/utils.ts +2 -1
- package/src/api/routing.ts +304 -131
- package/src/api/setupRequest.ts +31 -8
- package/src/arbs.ts +5 -3
- package/src/errorReporter.ts +65 -75
- package/src/fileUtil.ts +1 -1
- package/src/logger/jsonLogger.ts +3 -1
- package/src/logger/logFmtLogger.ts +1 -1
- package/src/logger/shared.ts +3 -2
- package/src/otel.ts +152 -0
- package/src/rateLimit.ts +34 -23
- package/src/test.ts +2 -2
- package/test/auth.test.ts +101 -0
- package/test/contextProvider.test.ts +14 -11
- package/test/controller.test.ts +25 -29
- package/test/dist/auth.test.d.ts.map +1 -0
- package/test/dist/contextProvider.test.d.ts.map +1 -1
- package/test/dist/controller.test.d.ts.map +1 -1
- package/test/dist/date-query.test.d.ts.map +1 -0
- package/test/dist/fixtures.d.ts +30 -12
- package/test/dist/fixtures.d.ts.map +1 -1
- package/test/dist/fixtures.js +17 -10
- package/test/dist/query.test.d.ts.map +1 -1
- package/test/dist/rawQuery.test.d.ts.map +1 -1
- package/test/dist/repository-ext.test.d.ts.map +1 -0
- package/test/dist/requires.test.d.ts.map +1 -1
- package/test/dist/router-generator.test.d.ts.map +1 -0
- package/test/dist/routing-interruptibility.test.d.ts.map +1 -0
- package/test/dist/rpc-e2e-invalidation.test.d.ts.map +1 -0
- package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
- package/test/dist/rpc-stream-fullstack.test.d.ts.map +1 -0
- package/test/dist/sql-store.test.d.ts.map +1 -0
- package/test/fixtures.ts +16 -9
- package/test/layerUtils.test.ts +1 -1
- package/test/query.test.ts +819 -38
- package/test/rawQuery.test.ts +312 -20
- package/test/repository-ext.test.ts +62 -0
- package/test/requires.test.ts +10 -5
- package/test/router-generator.test.ts +187 -0
- package/test/routing-interruptibility.test.ts +66 -0
- package/test/rpc-e2e-invalidation.test.ts +256 -0
- package/test/rpc-multi-middleware.test.ts +84 -9
- package/test/rpc-stream-fullstack.test.ts +304 -0
- package/test/sql-store.test.ts +1592 -0
- package/test/validateSample.test.ts +17 -12
- package/tsconfig.examples.json +1 -1
- package/tsconfig.json +0 -1
- package/tsconfig.json.bak +2 -2
- package/tsconfig.src.json +35 -35
- package/tsconfig.test.json +2 -2
- package/dist/Operations.d.ts +0 -55
- package/dist/Operations.d.ts.map +0 -1
- package/dist/Operations.js +0 -102
- package/dist/OperationsRepo.d.ts +0 -41
- package/dist/OperationsRepo.d.ts.map +0 -1
- package/dist/OperationsRepo.js +0 -14
- package/eslint.config.mjs +0 -24
- package/src/Operations.ts +0 -235
- package/src/OperationsRepo.ts +0 -16
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full-stack stream test exercising the entire wrapper:
|
|
3
|
+
* resources (TaggedRequestFor)
|
|
4
|
+
* → controllers (Router(...)({ effect }))
|
|
5
|
+
* → router (makeRouter / matchAll)
|
|
6
|
+
* → api ClientFactory (ApiClientFactory.makeFor)
|
|
7
|
+
*
|
|
8
|
+
* Server runs over real HTTP (NodeHttpServer on a loopback port). Client uses
|
|
9
|
+
* FetchHttpClient through ApiClientFactory. This covers the wrapper-level
|
|
10
|
+
* `Stream` request constructor end-to-end.
|
|
11
|
+
*/
|
|
12
|
+
import { NodeHttpServer } from "@effect/platform-node"
|
|
13
|
+
import { expect, it } from "@effect/vitest"
|
|
14
|
+
import { ApiClientFactory, makeRpcClient } from "effect-app/client"
|
|
15
|
+
import { HttpRouter, HttpServer } from "effect-app/http"
|
|
16
|
+
import { DefaultGenericMiddlewares } from "effect-app/middleware"
|
|
17
|
+
import { MiddlewareMaker } from "effect-app/rpc"
|
|
18
|
+
import * as S from "effect-app/Schema"
|
|
19
|
+
import { TaggedErrorClass } from "effect-app/Schema"
|
|
20
|
+
import * as Effect from "effect/Effect"
|
|
21
|
+
import * as Exit from "effect/Exit"
|
|
22
|
+
import * as Layer from "effect/Layer"
|
|
23
|
+
import * as Option from "effect/Option"
|
|
24
|
+
import * as Stream from "effect/Stream"
|
|
25
|
+
import { FetchHttpClient } from "effect/unstable/http"
|
|
26
|
+
import { RpcSerialization } from "effect/unstable/rpc"
|
|
27
|
+
import { createServer } from "http"
|
|
28
|
+
import { makeRouter } from "../src/api/routing.js"
|
|
29
|
+
import { DefaultGenericMiddlewaresLive } from "../src/api/routing/middleware.js"
|
|
30
|
+
import { AllowAnonymous, AllowAnonymousLive, RequestContextMap, RequireRoles, RequireRolesLive, SomeElseMiddleware, SomeElseMiddlewareLive, SomeService, Test, TestLive } from "./fixtures.js"
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Middleware (mirrors the boilerplate AppMiddleware shape).
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
class AppMiddleware extends MiddlewareMaker
|
|
37
|
+
.Tag<AppMiddleware>()("AppMiddleware", RequestContextMap)
|
|
38
|
+
.middleware(RequireRoles, Test)
|
|
39
|
+
.middleware(AllowAnonymous)
|
|
40
|
+
.middleware(SomeElseMiddleware)
|
|
41
|
+
.middleware(...DefaultGenericMiddlewares)
|
|
42
|
+
{
|
|
43
|
+
static Default = this.layer.pipe(
|
|
44
|
+
Layer.provide(
|
|
45
|
+
[
|
|
46
|
+
RequireRolesLive.pipe(Layer.provide(SomeService.Default)),
|
|
47
|
+
AllowAnonymousLive,
|
|
48
|
+
TestLive,
|
|
49
|
+
SomeElseMiddlewareLive,
|
|
50
|
+
DefaultGenericMiddlewaresLive
|
|
51
|
+
] as const
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const { Router, matchAll } = makeRouter(AppMiddleware.Default)
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Resources — Stream with and without payload.
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
const { TaggedRequestFor } = makeRpcClient(AppMiddleware)
|
|
63
|
+
const Req = TaggedRequestFor("Streamy")
|
|
64
|
+
|
|
65
|
+
class StreamTicks extends Req.Command<StreamTicks>()("StreamTicks", {}, {
|
|
66
|
+
stream: true,
|
|
67
|
+
allowAnonymous: true,
|
|
68
|
+
success: S.Number
|
|
69
|
+
}) {}
|
|
70
|
+
|
|
71
|
+
class StreamCountTo extends Req.Command<StreamCountTo>()("StreamCountTo", {
|
|
72
|
+
to: S.Number
|
|
73
|
+
}, {
|
|
74
|
+
stream: true,
|
|
75
|
+
allowAnonymous: true,
|
|
76
|
+
success: S.Number
|
|
77
|
+
}) {}
|
|
78
|
+
|
|
79
|
+
class StreamRealtime extends Req.Command<StreamRealtime>()("StreamRealtime", {}, {
|
|
80
|
+
stream: true,
|
|
81
|
+
allowAnonymous: true,
|
|
82
|
+
success: S.Number
|
|
83
|
+
}) {}
|
|
84
|
+
|
|
85
|
+
class StreamBoom extends TaggedErrorClass<StreamBoom>()("StreamBoom", { reason: S.String }) {}
|
|
86
|
+
|
|
87
|
+
class StreamFailStream extends Req.Command<StreamFailStream>()("StreamFailStream", {}, {
|
|
88
|
+
stream: true,
|
|
89
|
+
allowAnonymous: true,
|
|
90
|
+
success: S.Number,
|
|
91
|
+
error: StreamBoom
|
|
92
|
+
}) {}
|
|
93
|
+
|
|
94
|
+
class StreamNoSuccess extends Req.Command<StreamNoSuccess>()("StreamNoSuccess", {}, {
|
|
95
|
+
stream: true,
|
|
96
|
+
allowAnonymous: true
|
|
97
|
+
}) {}
|
|
98
|
+
|
|
99
|
+
// Defaults to allowAnonymous: false → AllowAnonymous middleware fails with NotLoggedInError
|
|
100
|
+
// when the request lacks the `x-user` header.
|
|
101
|
+
class StreamRequiresAuth extends Req.Command<StreamRequiresAuth>()("StreamRequiresAuth", {}, {
|
|
102
|
+
stream: true,
|
|
103
|
+
success: S.Number
|
|
104
|
+
}) {}
|
|
105
|
+
|
|
106
|
+
class CommandRequiresAuth extends Req.Command<CommandRequiresAuth>()("CommandRequiresAuth", {}, {
|
|
107
|
+
success: S.Number
|
|
108
|
+
}) {}
|
|
109
|
+
|
|
110
|
+
class QueryRequiresAuth extends Req.Query<QueryRequiresAuth>()("QueryRequiresAuth", {}, {
|
|
111
|
+
success: S.Number
|
|
112
|
+
}) {}
|
|
113
|
+
|
|
114
|
+
const StreamyRsc = {
|
|
115
|
+
StreamTicks,
|
|
116
|
+
StreamCountTo,
|
|
117
|
+
StreamRealtime,
|
|
118
|
+
StreamFailStream,
|
|
119
|
+
StreamNoSuccess,
|
|
120
|
+
StreamRequiresAuth,
|
|
121
|
+
CommandRequiresAuth,
|
|
122
|
+
QueryRequiresAuth
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// Controllers / router — Stream impls returned from the match callback.
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
const router = Router(StreamyRsc)({
|
|
130
|
+
*effect(match) {
|
|
131
|
+
return match({
|
|
132
|
+
StreamTicks: () => Stream.fromIterable([10, 20, 30]),
|
|
133
|
+
StreamCountTo: ({ to }: { readonly to: number }) =>
|
|
134
|
+
Effect
|
|
135
|
+
.gen(function*() {
|
|
136
|
+
return Stream.range(1, to)
|
|
137
|
+
})
|
|
138
|
+
.pipe(Stream.unwrap),
|
|
139
|
+
// emits 3 values 100ms apart so the test can prove element-by-element
|
|
140
|
+
// delivery rather than a single batched response
|
|
141
|
+
StreamRealtime: () =>
|
|
142
|
+
Stream.fromIterable([1, 2, 3]).pipe(
|
|
143
|
+
Stream.mapEffect((n) => Effect.sleep("100 millis").pipe(Effect.as(n)))
|
|
144
|
+
),
|
|
145
|
+
StreamFailStream: () => Stream.fail(new StreamBoom({ reason: "from-stream" })),
|
|
146
|
+
StreamNoSuccess: () => Stream.empty,
|
|
147
|
+
// handlers below are unreachable when middleware-auth fails; bodies exist
|
|
148
|
+
// only so the resource type-checks
|
|
149
|
+
StreamRequiresAuth: () => Stream.fromIterable([1, 2, 3]),
|
|
150
|
+
CommandRequiresAuth: () => Effect.succeed(1),
|
|
151
|
+
QueryRequiresAuth: () => Effect.succeed(1)
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
const RpcRouterLayer = matchAll({ router })
|
|
157
|
+
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// HTTP wiring — real server on a loopback port + FetchHttpClient on the client.
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
// Server binds an ephemeral port (port: 0). The actual URL is read from the
|
|
163
|
+
// `HttpServer` service after binding, then fed into `ApiClientFactory.layer` so
|
|
164
|
+
// each `it.live` scope gets a fresh server without colliding on a fixed port.
|
|
165
|
+
const NodeServerLayer = NodeHttpServer.layer(() => createServer(), { port: 0 })
|
|
166
|
+
|
|
167
|
+
const ServerLayer = HttpRouter
|
|
168
|
+
.serve(RpcRouterLayer)
|
|
169
|
+
.pipe(
|
|
170
|
+
Layer.provide(NodeServerLayer),
|
|
171
|
+
Layer.provide(RpcSerialization.layerNdjson)
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
const ClientLayer = Layer
|
|
175
|
+
.unwrap(
|
|
176
|
+
Effect.gen(function*() {
|
|
177
|
+
const server = yield* HttpServer.HttpServer
|
|
178
|
+
const addr = server.address
|
|
179
|
+
if (addr._tag !== "TcpAddress") return yield* Effect.die(new Error("expected TcpAddress"))
|
|
180
|
+
const host = addr.hostname === "0.0.0.0" ? "127.0.0.1" : addr.hostname
|
|
181
|
+
const url = `http://${host}:${addr.port}`
|
|
182
|
+
return ApiClientFactory
|
|
183
|
+
.layer({ url, headers: Option.none() })
|
|
184
|
+
.pipe(Layer.provide(FetchHttpClient.layer))
|
|
185
|
+
})
|
|
186
|
+
)
|
|
187
|
+
.pipe(Layer.provide(NodeServerLayer))
|
|
188
|
+
|
|
189
|
+
const TestLayer = Layer.mergeAll(ServerLayer, ClientLayer)
|
|
190
|
+
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// Tests
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
it.live(
|
|
196
|
+
"stream resource without input: ApiClientFactory client emits all values",
|
|
197
|
+
Effect.fnUntraced(function*() {
|
|
198
|
+
const client = yield* ApiClientFactory.makeFor(Layer.empty)(StreamyRsc)
|
|
199
|
+
const values = yield* Stream.runCollect(client.StreamTicks.handler())
|
|
200
|
+
expect(values).toStrictEqual([10, 20, 30])
|
|
201
|
+
}, Effect.provide(TestLayer)),
|
|
202
|
+
{ timeout: 10_000 }
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
it.live(
|
|
206
|
+
"stream resource with input: payload drives the emitted values",
|
|
207
|
+
Effect.fnUntraced(function*() {
|
|
208
|
+
const client = yield* ApiClientFactory.makeFor(Layer.empty)(StreamyRsc)
|
|
209
|
+
const values = yield* Stream.runCollect(client.StreamCountTo.handler({ to: 4 }))
|
|
210
|
+
expect(values).toStrictEqual([1, 2, 3, 4])
|
|
211
|
+
}, Effect.provide(TestLayer)),
|
|
212
|
+
{ timeout: 10_000 }
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
it.live(
|
|
216
|
+
"stream resource is delivered element-by-element in real time (not batched)",
|
|
217
|
+
Effect.fnUntraced(function*() {
|
|
218
|
+
const client = yield* ApiClientFactory.makeFor(Layer.empty)(StreamyRsc)
|
|
219
|
+
const start = Date.now()
|
|
220
|
+
const arrivals = yield* Stream.runCollect(
|
|
221
|
+
client.StreamRealtime.handler().pipe(
|
|
222
|
+
Stream.map((n) => ({ n, at: Date.now() - start }))
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
expect(arrivals.map((_) => _.n)).toStrictEqual([1, 2, 3])
|
|
226
|
+
// server emits each value 100ms after the previous one. If the response
|
|
227
|
+
// were batched, deltas would be ~0ms. Allow generous slack for CI jitter
|
|
228
|
+
// but require clear separation between consecutive arrivals.
|
|
229
|
+
const delta1 = arrivals[1]!.at - arrivals[0]!.at
|
|
230
|
+
const delta2 = arrivals[2]!.at - arrivals[1]!.at
|
|
231
|
+
expect(delta1).toBeGreaterThan(50)
|
|
232
|
+
expect(delta2).toBeGreaterThan(50)
|
|
233
|
+
// first element should not be withheld until the whole stream completes
|
|
234
|
+
expect(arrivals[0]!.at).toBeLessThan(arrivals[2]!.at - 50)
|
|
235
|
+
}, Effect.provide(TestLayer)),
|
|
236
|
+
{ timeout: 10_000 }
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
it.live(
|
|
240
|
+
"stream handler returning Stream.fail surfaces as failing stream on client",
|
|
241
|
+
Effect.fnUntraced(function*() {
|
|
242
|
+
const client = yield* ApiClientFactory.makeFor(Layer.empty)(StreamyRsc)
|
|
243
|
+
const exit = yield* Stream.runCollect(client.StreamFailStream.handler()).pipe(Effect.exit)
|
|
244
|
+
expect(Exit.isFailure(exit)).toBe(true)
|
|
245
|
+
if (Exit.isFailure(exit)) {
|
|
246
|
+
const failures = (exit.cause as any).reasons as ReadonlyArray<{ _tag: "Fail"; error: StreamBoom }>
|
|
247
|
+
expect(failures.length).toBeGreaterThan(0)
|
|
248
|
+
expect(failures[0]!.error._tag).toBe("StreamBoom")
|
|
249
|
+
expect(failures[0]!.error.reason).toBe("from-stream")
|
|
250
|
+
}
|
|
251
|
+
}, Effect.provide(TestLayer)),
|
|
252
|
+
{ timeout: 10_000 }
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
it.live(
|
|
256
|
+
"stream resource without `success` exposes handler as a Stream on the client",
|
|
257
|
+
Effect.fnUntraced(function*() {
|
|
258
|
+
const client = yield* ApiClientFactory.makeFor(Layer.empty)(StreamyRsc)
|
|
259
|
+
const exit = yield* Stream.runCollect(client.StreamNoSuccess.handler()).pipe(Effect.exit)
|
|
260
|
+
expect(Exit.isSuccess(exit)).toBe(true)
|
|
261
|
+
}, Effect.provide(TestLayer)),
|
|
262
|
+
{ timeout: 10_000 }
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
const expectNotLoggedIn = (exit: Exit.Exit<unknown, unknown>) => {
|
|
266
|
+
expect(Exit.isFailure(exit)).toBe(true)
|
|
267
|
+
if (!Exit.isFailure(exit)) return
|
|
268
|
+
const failures = (exit.cause as any).reasons as ReadonlyArray<{ _tag: "Fail"; error: any }>
|
|
269
|
+
expect(failures.length).toBeGreaterThan(0)
|
|
270
|
+
// The bug surfaces here as a SchemaError ("Expected never | { _tag: 'error', ... }")
|
|
271
|
+
// because the middleware-thrown NotLoggedInError doesn't match the
|
|
272
|
+
// wire StreamFailureChunk / CommandFailureWithMetaData wrapping.
|
|
273
|
+
expect(failures[0]!.error?._tag).toBe("NotLoggedInError")
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
it.live(
|
|
277
|
+
"stream resource: middleware-emitted NotLoggedInError surfaces cleanly on the client",
|
|
278
|
+
Effect.fnUntraced(function*() {
|
|
279
|
+
const client = yield* ApiClientFactory.makeFor(Layer.empty)(StreamyRsc)
|
|
280
|
+
const exit = yield* Stream.runCollect(client.StreamRequiresAuth.handler()).pipe(Effect.exit)
|
|
281
|
+
expectNotLoggedIn(exit)
|
|
282
|
+
}, Effect.provide(TestLayer)),
|
|
283
|
+
{ timeout: 10_000 }
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
it.live(
|
|
287
|
+
"command resource: middleware-emitted NotLoggedInError surfaces cleanly on the client",
|
|
288
|
+
Effect.fnUntraced(function*() {
|
|
289
|
+
const client = yield* ApiClientFactory.makeFor(Layer.empty)(StreamyRsc)
|
|
290
|
+
const exit = yield* client.CommandRequiresAuth.handler().pipe(Effect.exit)
|
|
291
|
+
expectNotLoggedIn(exit)
|
|
292
|
+
}, Effect.provide(TestLayer)),
|
|
293
|
+
{ timeout: 10_000 }
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
it.live(
|
|
297
|
+
"query resource: middleware-emitted NotLoggedInError surfaces cleanly on the client",
|
|
298
|
+
Effect.fnUntraced(function*() {
|
|
299
|
+
const client = yield* ApiClientFactory.makeFor(Layer.empty)(StreamyRsc)
|
|
300
|
+
const exit = yield* client.QueryRequiresAuth.handler().pipe(Effect.exit)
|
|
301
|
+
expectNotLoggedIn(exit)
|
|
302
|
+
}, Effect.provide(TestLayer)),
|
|
303
|
+
{ timeout: 10_000 }
|
|
304
|
+
)
|