@effect-app/infra 4.0.0-beta.13 → 4.0.0-beta.131
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 +864 -0
- package/dist/CUPS.d.ts +13 -5
- package/dist/CUPS.d.ts.map +1 -1
- package/dist/CUPS.js +10 -12
- package/dist/Emailer/service.d.ts +2 -2
- package/dist/Emailer/service.d.ts.map +1 -1
- package/dist/Emailer/service.js +3 -3
- package/dist/MainFiberSet.d.ts +2 -2
- package/dist/MainFiberSet.d.ts.map +1 -1
- package/dist/MainFiberSet.js +3 -3
- package/dist/Model/Repository/Registry.d.ts +20 -0
- package/dist/Model/Repository/Registry.d.ts.map +1 -0
- package/dist/Model/Repository/Registry.js +17 -0
- package/dist/Model/Repository/ext.d.ts +21 -3
- package/dist/Model/Repository/ext.d.ts.map +1 -1
- package/dist/Model/Repository/ext.js +54 -2
- package/dist/Model/Repository/internal/internal.d.ts +4 -4
- package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
- package/dist/Model/Repository/internal/internal.js +31 -21
- package/dist/Model/Repository/makeRepo.d.ts +6 -5
- package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
- package/dist/Model/Repository/makeRepo.js +4 -1
- package/dist/Model/Repository/service.d.ts +27 -22
- package/dist/Model/Repository/service.d.ts.map +1 -1
- package/dist/Model/Repository/validation.d.ts +59 -9
- package/dist/Model/Repository/validation.d.ts.map +1 -1
- package/dist/Model/Repository.d.ts +1 -0
- package/dist/Model/Repository.d.ts.map +1 -1
- package/dist/Model/Repository.js +2 -1
- package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -1
- package/dist/Model/query/new-kid-interpreter.js +3 -3
- package/dist/Model.d.ts +1 -0
- package/dist/Model.d.ts.map +1 -1
- package/dist/Model.js +2 -1
- package/dist/Operations.d.ts +4 -4
- package/dist/Operations.d.ts.map +1 -1
- package/dist/Operations.js +56 -59
- package/dist/OperationsRepo.d.ts +4 -4
- package/dist/OperationsRepo.d.ts.map +1 -1
- package/dist/OperationsRepo.js +3 -3
- package/dist/QueueMaker/SQLQueue.d.ts +3 -6
- package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
- package/dist/QueueMaker/SQLQueue.js +105 -114
- package/dist/QueueMaker/errors.d.ts +1 -1
- package/dist/QueueMaker/errors.d.ts.map +1 -1
- package/dist/QueueMaker/memQueue.d.ts +6 -3
- package/dist/QueueMaker/memQueue.d.ts.map +1 -1
- package/dist/QueueMaker/memQueue.js +51 -62
- package/dist/QueueMaker/sbqueue.d.ts +5 -2
- package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
- package/dist/QueueMaker/sbqueue.js +36 -52
- package/dist/RequestContext.d.ts +51 -21
- package/dist/RequestContext.d.ts.map +1 -1
- package/dist/RequestContext.js +5 -5
- package/dist/RequestFiberSet.d.ts +2 -2
- package/dist/RequestFiberSet.d.ts.map +1 -1
- package/dist/RequestFiberSet.js +5 -5
- package/dist/Store/ContextMapContainer.d.ts +18 -2
- package/dist/Store/ContextMapContainer.d.ts.map +1 -1
- package/dist/Store/ContextMapContainer.js +13 -3
- package/dist/Store/Cosmos.d.ts.map +1 -1
- package/dist/Store/Cosmos.js +308 -242
- package/dist/Store/Disk.d.ts +1 -1
- package/dist/Store/Disk.d.ts.map +1 -1
- package/dist/Store/Disk.js +25 -22
- package/dist/Store/Memory.d.ts +3 -3
- package/dist/Store/Memory.d.ts.map +1 -1
- package/dist/Store/Memory.js +27 -22
- 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 +189 -0
- package/dist/Store/SQL/query.d.ts +38 -0
- package/dist/Store/SQL/query.d.ts.map +1 -0
- package/dist/Store/SQL/query.js +367 -0
- package/dist/Store/SQL.d.ts +20 -0
- package/dist/Store/SQL.d.ts.map +1 -0
- package/dist/Store/SQL.js +381 -0
- package/dist/Store/index.d.ts +4 -1
- package/dist/Store/index.d.ts.map +1 -1
- package/dist/Store/index.js +15 -3
- package/dist/Store/service.d.ts +16 -5
- package/dist/Store/service.d.ts.map +1 -1
- package/dist/Store/service.js +24 -6
- package/dist/Store/utils.d.ts.map +1 -1
- package/dist/Store/utils.js +3 -4
- package/dist/adapters/ServiceBus.d.ts +6 -6
- package/dist/adapters/ServiceBus.d.ts.map +1 -1
- package/dist/adapters/ServiceBus.js +13 -15
- package/dist/adapters/cosmos-client.d.ts +2 -2
- package/dist/adapters/cosmos-client.d.ts.map +1 -1
- package/dist/adapters/cosmos-client.js +3 -3
- package/dist/adapters/logger.d.ts.map +1 -1
- package/dist/adapters/memQueue.d.ts +2 -2
- package/dist/adapters/memQueue.d.ts.map +1 -1
- package/dist/adapters/memQueue.js +3 -3
- package/dist/adapters/mongo-client.d.ts +2 -2
- package/dist/adapters/mongo-client.d.ts.map +1 -1
- package/dist/adapters/mongo-client.js +3 -3
- package/dist/adapters/redis-client.d.ts +2 -2
- package/dist/adapters/redis-client.d.ts.map +1 -1
- package/dist/adapters/redis-client.js +3 -3
- package/dist/api/ContextProvider.d.ts +6 -6
- package/dist/api/ContextProvider.d.ts.map +1 -1
- package/dist/api/ContextProvider.js +6 -6
- package/dist/api/internal/auth.d.ts +42 -4
- package/dist/api/internal/auth.d.ts.map +1 -1
- package/dist/api/internal/auth.js +160 -29
- package/dist/api/internal/events.d.ts +2 -2
- package/dist/api/internal/events.d.ts.map +1 -1
- package/dist/api/internal/events.js +11 -7
- package/dist/api/layerUtils.d.ts +5 -5
- package/dist/api/layerUtils.d.ts.map +1 -1
- package/dist/api/layerUtils.js +5 -5
- package/dist/api/routing/middleware/RouterMiddleware.d.ts +3 -3
- package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware.d.ts +37 -1
- package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware.js +45 -14
- package/dist/api/routing.d.ts +4 -6
- package/dist/api/routing.d.ts.map +1 -1
- package/dist/api/routing.js +13 -6
- package/dist/api/setupRequest.d.ts +6 -3
- package/dist/api/setupRequest.d.ts.map +1 -1
- package/dist/api/setupRequest.js +11 -6
- package/dist/errorReporter.d.ts +3 -3
- package/dist/errorReporter.d.ts.map +1 -1
- package/dist/errorReporter.js +16 -23
- package/dist/logger.d.ts.map +1 -1
- package/dist/rateLimit.d.ts +8 -2
- package/dist/rateLimit.d.ts.map +1 -1
- package/dist/rateLimit.js +5 -11
- package/examples/query.ts +30 -26
- package/package.json +36 -22
- package/src/CUPS.ts +9 -11
- package/src/Emailer/service.ts +2 -2
- package/src/MainFiberSet.ts +2 -2
- package/src/Model/Repository/Registry.ts +33 -0
- package/src/Model/Repository/ext.ts +93 -6
- package/src/Model/Repository/internal/internal.ts +84 -76
- package/src/Model/Repository/makeRepo.ts +11 -8
- package/src/Model/Repository/service.ts +31 -22
- package/src/Model/Repository.ts +1 -0
- package/src/Model/query/new-kid-interpreter.ts +2 -2
- package/src/Model.ts +1 -0
- package/src/Operations.ts +78 -113
- package/src/OperationsRepo.ts +2 -2
- package/src/QueueMaker/SQLQueue.ts +121 -151
- package/src/QueueMaker/memQueue.ts +82 -103
- package/src/QueueMaker/sbqueue.ts +55 -85
- package/src/RequestContext.ts +4 -4
- package/src/RequestFiberSet.ts +4 -4
- package/src/Store/ContextMapContainer.ts +41 -2
- package/src/Store/Cosmos.ts +437 -343
- package/src/Store/Disk.ts +52 -49
- package/src/Store/Memory.ts +54 -48
- package/src/Store/SQL/Pg.ts +318 -0
- package/src/Store/SQL/query.ts +409 -0
- package/src/Store/SQL.ts +668 -0
- package/src/Store/index.ts +17 -2
- package/src/Store/service.ts +31 -7
- package/src/Store/utils.ts +23 -22
- package/src/adapters/ServiceBus.ts +111 -115
- package/src/adapters/cosmos-client.ts +2 -2
- package/src/adapters/memQueue.ts +2 -2
- package/src/adapters/mongo-client.ts +2 -2
- package/src/adapters/redis-client.ts +2 -2
- package/src/api/ContextProvider.ts +11 -11
- package/src/api/internal/auth.ts +246 -44
- package/src/api/internal/events.ts +14 -9
- package/src/api/layerUtils.ts +8 -8
- package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
- package/src/api/routing/middleware/middleware.ts +52 -12
- package/src/api/routing.ts +17 -7
- package/src/api/setupRequest.ts +27 -7
- package/src/errorReporter.ts +58 -72
- package/src/rateLimit.ts +30 -22
- package/test/auth.test.ts +101 -0
- package/test/contextProvider.test.ts +11 -11
- package/test/controller.test.ts +18 -13
- 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 +19 -9
- package/test/dist/fixtures.d.ts.map +1 -1
- package/test/dist/fixtures.js +11 -9
- 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/routing-interruptibility.test.d.ts.map +1 -0
- package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
- package/test/dist/sql-store.test.d.ts.map +1 -0
- package/test/fixtures.ts +10 -8
- package/test/query.test.ts +209 -31
- package/test/rawQuery.test.ts +23 -19
- package/test/repository-ext.test.ts +58 -0
- package/test/requires.test.ts +6 -5
- package/test/routing-interruptibility.test.ts +63 -0
- package/test/rpc-multi-middleware.test.ts +72 -3
- package/test/sql-store.test.ts +1064 -0
- package/test/validateSample.test.ts +12 -9
- package/tsconfig.json +0 -1
package/test/rawQuery.test.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { describe, expect, it } from "@effect/vitest"
|
|
2
|
-
import { Array, Config, Effect, flow, Layer, ManagedRuntime, Redacted, References, Result, S
|
|
2
|
+
import { Array, Config, Context, Effect, flow, Layer, ManagedRuntime, Redacted, References, Result, S } from "effect-app"
|
|
3
3
|
import { LogLevels } from "effect-app/utils"
|
|
4
4
|
import { setupRequestContextFromCurrent } from "../src/api/setupRequest.js"
|
|
5
5
|
import { and, or, project, where, whereEvery, whereSome } from "../src/Model/query.js"
|
|
6
6
|
import { makeRepo } from "../src/Model/Repository/makeRepo.js"
|
|
7
|
+
import { RepositoryRegistryLive } from "../src/Model/Repository/Registry.js"
|
|
7
8
|
import { CosmosStoreLayer } from "../src/Store/Cosmos.js"
|
|
8
9
|
import { MemoryStoreLive } from "../src/Store/Memory.js"
|
|
9
10
|
|
|
@@ -24,7 +25,7 @@ class Something extends S.Class<Something>("Something")({
|
|
|
24
25
|
id: S.String,
|
|
25
26
|
name: S.String,
|
|
26
27
|
description: S.String,
|
|
27
|
-
items: S.Array(S.Struct({ id: S.String, value: S.
|
|
28
|
+
items: S.Array(S.Struct({ id: S.String, value: S.Finite, description: S.String }))
|
|
28
29
|
}) {}
|
|
29
30
|
|
|
30
31
|
const items = [
|
|
@@ -49,7 +50,7 @@ const items = [
|
|
|
49
50
|
]
|
|
50
51
|
|
|
51
52
|
// @effect-diagnostics-next-line missingEffectServiceDependency:off
|
|
52
|
-
class SomethingRepo extends
|
|
53
|
+
class SomethingRepo extends Context.Service<SomethingRepo>()(
|
|
53
54
|
"SomethingRepo",
|
|
54
55
|
{
|
|
55
56
|
make: Effect.gen(function*() {
|
|
@@ -76,28 +77,31 @@ class SomethingRepo extends ServiceMap.Service<SomethingRepo>()(
|
|
|
76
77
|
static readonly Test = this
|
|
77
78
|
.layer
|
|
78
79
|
.pipe(
|
|
79
|
-
Layer.provide(MemoryStoreLive)
|
|
80
|
+
Layer.provide(Layer.merge(MemoryStoreLive, RepositoryRegistryLive))
|
|
80
81
|
)
|
|
81
82
|
|
|
82
83
|
static readonly TestCosmos = this
|
|
83
84
|
.layer
|
|
84
85
|
.pipe(
|
|
85
86
|
Layer.provide(
|
|
86
|
-
Effect
|
|
87
|
-
|
|
88
|
-
Config.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
87
|
+
Effect
|
|
88
|
+
.gen(function*() {
|
|
89
|
+
const url = yield* Config.redacted("STORAGE_URL").pipe(
|
|
90
|
+
Config.withDefault(
|
|
91
|
+
Redacted.make(
|
|
92
|
+
// the emulator doesn't implement array projections :/ so you need an actual cloud instance!
|
|
93
|
+
"AccountEndpoint=http://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
|
|
94
|
+
)
|
|
92
95
|
)
|
|
93
96
|
)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
return CosmosStoreLayer({
|
|
98
|
+
dbName: "test",
|
|
99
|
+
prefix: "",
|
|
100
|
+
url
|
|
101
|
+
})
|
|
102
|
+
.pipe(Layer.merge(RepositoryRegistryLive))
|
|
99
103
|
})
|
|
100
|
-
|
|
104
|
+
.pipe(Layer.unwrap)
|
|
101
105
|
)
|
|
102
106
|
)
|
|
103
107
|
}
|
|
@@ -107,7 +111,7 @@ describe("select first-level array fields", () => {
|
|
|
107
111
|
.gen(function*() {
|
|
108
112
|
const repo = yield* SomethingRepo
|
|
109
113
|
|
|
110
|
-
const projected = S.Struct({ name: S.String, items: S.Array(S.Struct({ id: S.String, value: S.
|
|
114
|
+
const projected = S.Struct({ name: S.String, items: S.Array(S.Struct({ id: S.String, value: S.Finite })) })
|
|
111
115
|
|
|
112
116
|
// ok crazy lol, "value" is a reserved word in CosmosDB, so we have to use t["value"] as a field name instead of t.value
|
|
113
117
|
const items = yield* repo.queryRaw(projected, {
|
|
@@ -159,7 +163,7 @@ describe("select first-level array fields", () => {
|
|
|
159
163
|
.pipe(Effect.provide(SomethingRepo.Test), rt.runPromise))
|
|
160
164
|
})
|
|
161
165
|
|
|
162
|
-
const projected = S.Struct({ name: S.String, items: S.Array(S.Struct({ id: S.String, value: S.
|
|
166
|
+
const projected = S.Struct({ name: S.String, items: S.Array(S.Struct({ id: S.String, value: S.Finite })) })
|
|
163
167
|
|
|
164
168
|
const expected = [
|
|
165
169
|
{
|
|
@@ -405,7 +409,7 @@ describe("removeByIds", () => {
|
|
|
405
409
|
|
|
406
410
|
yield* repo.saveAndPublish(items)
|
|
407
411
|
const itemsAfterSave = yield* repo.all
|
|
408
|
-
yield* repo.removeById(
|
|
412
|
+
yield* repo.removeById([items[0]!.id, items[1]!.id])
|
|
409
413
|
|
|
410
414
|
const items2 = yield* repo.all
|
|
411
415
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, expect, it } from "@effect/vitest"
|
|
2
|
+
import { Effect, Layer, S } from "effect-app"
|
|
3
|
+
import { setupRequestContextFromCurrent } from "../src/api/setupRequest.js"
|
|
4
|
+
import { makeRepo } from "../src/Model/Repository.js"
|
|
5
|
+
import { RepositoryRegistryLive } from "../src/Model/Repository/Registry.js"
|
|
6
|
+
import { MemoryStoreLive } from "../src/Store/Memory.js"
|
|
7
|
+
|
|
8
|
+
class BatchItem extends S.Class<BatchItem>("BatchItem")({
|
|
9
|
+
id: S.String,
|
|
10
|
+
label: S.String
|
|
11
|
+
}) {}
|
|
12
|
+
|
|
13
|
+
const TestStoreLive = Layer.merge(MemoryStoreLive, RepositoryRegistryLive)
|
|
14
|
+
|
|
15
|
+
describe("repository ext save/remove batching", () => {
|
|
16
|
+
it.effect("supports save batching overload", () =>
|
|
17
|
+
Effect.gen(function*() {
|
|
18
|
+
const repo = yield* makeRepo("BatchItem", BatchItem, {})
|
|
19
|
+
const items = [
|
|
20
|
+
new BatchItem({ id: "1", label: "one" }),
|
|
21
|
+
new BatchItem({ id: "2", label: "two" }),
|
|
22
|
+
new BatchItem({ id: "3", label: "three" }),
|
|
23
|
+
new BatchItem({ id: "4", label: "four" })
|
|
24
|
+
] as const
|
|
25
|
+
|
|
26
|
+
yield* repo.save(items, { batch: 2 })
|
|
27
|
+
|
|
28
|
+
const all = yield* repo.all
|
|
29
|
+
expect(all).toHaveLength(4)
|
|
30
|
+
expect(all.map((_) => _.id).toSorted()).toEqual(["1", "2", "3", "4"])
|
|
31
|
+
}).pipe(
|
|
32
|
+
setupRequestContextFromCurrent(),
|
|
33
|
+
Effect.provide(TestStoreLive)
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
it.effect("supports remove batching overload", () =>
|
|
38
|
+
Effect.gen(function*() {
|
|
39
|
+
const repo = yield* makeRepo("BatchItem", BatchItem, {})
|
|
40
|
+
const items = [
|
|
41
|
+
new BatchItem({ id: "1", label: "one" }),
|
|
42
|
+
new BatchItem({ id: "2", label: "two" }),
|
|
43
|
+
new BatchItem({ id: "3", label: "three" }),
|
|
44
|
+
new BatchItem({ id: "4", label: "four" })
|
|
45
|
+
] as const
|
|
46
|
+
|
|
47
|
+
yield* repo.save(items)
|
|
48
|
+
yield* repo.remove([items[0], items[1], items[2]], { batch: true })
|
|
49
|
+
|
|
50
|
+
const all = yield* repo.all
|
|
51
|
+
expect(all).toHaveLength(1)
|
|
52
|
+
expect(all[0]?.id).toBe("4")
|
|
53
|
+
}).pipe(
|
|
54
|
+
setupRequestContextFromCurrent(),
|
|
55
|
+
Effect.provide(TestStoreLive)
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
})
|
package/test/requires.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, expectTypeOf, it } from "@effect/vitest"
|
|
2
|
-
import { Effect, Layer, Result, S
|
|
2
|
+
import { Context, Effect, Layer, Result, S } from "effect-app"
|
|
3
3
|
import { NotLoggedInError, UnauthorizedError } from "effect-app/client"
|
|
4
4
|
import { HttpHeaders } from "effect-app/http"
|
|
5
5
|
import * as RpcX from "effect-app/rpc"
|
|
@@ -63,11 +63,12 @@ const testSuite = (_mw: typeof middleware3) =>
|
|
|
63
63
|
"works",
|
|
64
64
|
Effect.fn(function*() {
|
|
65
65
|
const defaultOpts = {
|
|
66
|
+
client: null as any, // TODO?
|
|
66
67
|
headers: HttpHeaders.fromRecordUnsafe({}),
|
|
67
68
|
payload: { _tag: "Test" },
|
|
68
69
|
clientId: 0,
|
|
69
70
|
requestId: "test-id" as any,
|
|
70
|
-
rpc: { ...TestRpc, annotations:
|
|
71
|
+
rpc: { ...TestRpc, annotations: Context.make(_mw.requestContext, {}) }
|
|
71
72
|
}
|
|
72
73
|
const next = Effect.void as unknown as Effect.Effect<SuccessValue, unhandled, never>
|
|
73
74
|
const layer = _mw.layer.pipe(
|
|
@@ -89,7 +90,7 @@ const testSuite = (_mw: typeof middleware3) =>
|
|
|
89
90
|
headers: HttpHeaders.fromRecordUnsafe({ "x-user": "test-user", "x-is-manager": "true" }),
|
|
90
91
|
rpc: {
|
|
91
92
|
...defaultOpts.rpc,
|
|
92
|
-
annotations:
|
|
93
|
+
annotations: Context.make(_mw.requestContext, { requireRoles: ["manager"] })
|
|
93
94
|
}
|
|
94
95
|
})
|
|
95
96
|
)
|
|
@@ -127,7 +128,7 @@ const testSuite = (_mw: typeof middleware3) =>
|
|
|
127
128
|
Object.assign({ ...defaultOpts }, {
|
|
128
129
|
rpc: {
|
|
129
130
|
...defaultOpts.rpc,
|
|
130
|
-
annotations:
|
|
131
|
+
annotations: Context.make(_mw.requestContext, { requireRoles: ["manager"] })
|
|
131
132
|
}
|
|
132
133
|
})
|
|
133
134
|
)
|
|
@@ -153,7 +154,7 @@ const testSuite = (_mw: typeof middleware3) =>
|
|
|
153
154
|
{
|
|
154
155
|
rpc: {
|
|
155
156
|
...defaultOpts.rpc,
|
|
156
|
-
annotations:
|
|
157
|
+
annotations: Context.make(_mw.requestContext, { requireRoles: ["manager"] })
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
160
|
)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, it } from "@effect/vitest"
|
|
2
|
+
import { Effect, Fiber, Layer, Ref } from "effect"
|
|
3
|
+
import { S } from "effect-app"
|
|
4
|
+
import { ConfigureInterruptibilityMiddleware } from "effect-app/middleware"
|
|
5
|
+
import { Rpc, RpcGroup, RpcTest } from "effect/unstable/rpc"
|
|
6
|
+
import { applyRequestTypeInterruptibility } from "../src/api/routing.js"
|
|
7
|
+
import { ConfigureInterruptibilityMiddlewareLive, RequestType } from "../src/api/routing/middleware.js"
|
|
8
|
+
|
|
9
|
+
const InterruptibilityRpcs = RpcGroup.make(
|
|
10
|
+
Rpc
|
|
11
|
+
.make("doCommand", { success: S.Void })
|
|
12
|
+
.annotate(RequestType, "command")
|
|
13
|
+
.middleware(ConfigureInterruptibilityMiddleware),
|
|
14
|
+
Rpc
|
|
15
|
+
.make("doQuery", { success: S.Void })
|
|
16
|
+
.annotate(RequestType, "query")
|
|
17
|
+
.middleware(ConfigureInterruptibilityMiddleware)
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
const makeImplLayer = (commandDone: Ref.Ref<boolean>, queryDone: Ref.Ref<boolean>) =>
|
|
21
|
+
InterruptibilityRpcs.toLayer({
|
|
22
|
+
doCommand: () =>
|
|
23
|
+
applyRequestTypeInterruptibility(
|
|
24
|
+
"command",
|
|
25
|
+
Effect.sleep("120 millis").pipe(Effect.andThen(Ref.set(commandDone, true)))
|
|
26
|
+
),
|
|
27
|
+
doQuery: () =>
|
|
28
|
+
applyRequestTypeInterruptibility(
|
|
29
|
+
"query",
|
|
30
|
+
Effect.sleep("120 millis").pipe(Effect.andThen(Ref.set(queryDone, true)))
|
|
31
|
+
)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe("routing interruptibility", () => {
|
|
35
|
+
it.live(
|
|
36
|
+
"e2e: command continues after client interrupt, query does not",
|
|
37
|
+
() =>
|
|
38
|
+
Effect.gen(function*() {
|
|
39
|
+
const commandDone = yield* Ref.make(false)
|
|
40
|
+
const queryDone = yield* Ref.make(false)
|
|
41
|
+
|
|
42
|
+
const client = yield* RpcTest
|
|
43
|
+
.makeClient(InterruptibilityRpcs)
|
|
44
|
+
.pipe(
|
|
45
|
+
Effect.provide(
|
|
46
|
+
Layer.mergeAll(makeImplLayer(commandDone, queryDone), ConfigureInterruptibilityMiddlewareLive)
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
const commandFiber = yield* Effect.forkDetach(client.doCommand())
|
|
51
|
+
yield* Effect.sleep("20 millis")
|
|
52
|
+
yield* Fiber.interrupt(commandFiber)
|
|
53
|
+
yield* Effect.sleep("180 millis")
|
|
54
|
+
expect(yield* Ref.get(commandDone)).toBe(true)
|
|
55
|
+
|
|
56
|
+
const queryFiber = yield* Effect.forkDetach(client.doQuery())
|
|
57
|
+
yield* Effect.sleep("20 millis")
|
|
58
|
+
yield* Fiber.interrupt(queryFiber)
|
|
59
|
+
yield* Effect.sleep("180 millis")
|
|
60
|
+
expect(yield* Ref.get(queryDone)).toBe(false)
|
|
61
|
+
})
|
|
62
|
+
)
|
|
63
|
+
})
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { NodeHttpServer } from "@effect/platform-node"
|
|
2
2
|
import { expect, expectTypeOf, it } from "@effect/vitest"
|
|
3
|
-
import { Console, Effect, Layer, Result } from "effect"
|
|
4
|
-
import { S } from "effect-app"
|
|
3
|
+
import { Console, Effect, Layer, Ref, Result } from "effect"
|
|
4
|
+
import { Context, S } from "effect-app"
|
|
5
5
|
import { NotLoggedInError } from "effect-app/client"
|
|
6
6
|
import { HttpRouter } from "effect-app/http"
|
|
7
7
|
import { DefaultGenericMiddlewares } from "effect-app/middleware"
|
|
8
8
|
import { MiddlewareMaker } from "effect-app/rpc"
|
|
9
9
|
import { middlewareGroup } from "effect-app/rpc/MiddlewareMaker"
|
|
10
10
|
import { FetchHttpClient } from "effect/unstable/http"
|
|
11
|
-
import { RpcClient, RpcGroup, RpcSerialization, RpcServer, RpcTest } from "effect/unstable/rpc"
|
|
11
|
+
import { Rpc, RpcClient, RpcGroup, RpcSerialization, RpcServer, RpcTest } from "effect/unstable/rpc"
|
|
12
12
|
import { createServer } from "http"
|
|
13
13
|
import { DefaultGenericMiddlewaresLive } from "../src/api/routing.js"
|
|
14
14
|
import { AllowAnonymous, AllowAnonymousLive, RequestContextMap, RequireRoles, RequireRolesLive, Some, SomeElseMiddleware, SomeElseMiddlewareLive, SomeMiddleware, SomeMiddlewareLive, SomeService, Test, TestLive, UserProfile } from "./fixtures.js"
|
|
@@ -136,3 +136,72 @@ it.live(
|
|
|
136
136
|
Effect.provide(RpcTestLayer)
|
|
137
137
|
)
|
|
138
138
|
)
|
|
139
|
+
|
|
140
|
+
// Per-request service isolation test
|
|
141
|
+
|
|
142
|
+
class PerRequestCounter extends Context.Service<PerRequestCounter>()(
|
|
143
|
+
"PerRequestCounter",
|
|
144
|
+
{ make: Effect.sync(() => ({ a: 0 })) }
|
|
145
|
+
) {
|
|
146
|
+
static Default = Layer.effect(this, this.make)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
class GlobalCounter extends Context.Service<GlobalCounter, {
|
|
150
|
+
readonly ref: Ref.Ref<number>
|
|
151
|
+
}>()("GlobalCounter") {}
|
|
152
|
+
|
|
153
|
+
const CounterRpcs = RpcGroup.make(
|
|
154
|
+
Rpc.make("incrementA", {
|
|
155
|
+
success: S.Number
|
|
156
|
+
}),
|
|
157
|
+
Rpc.make("incrementB", {
|
|
158
|
+
success: S.Number
|
|
159
|
+
})
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
const counterImpl = CounterRpcs
|
|
163
|
+
.toLayer({
|
|
164
|
+
incrementA: Effect.fn(function*() {
|
|
165
|
+
const counter = yield* PerRequestCounter
|
|
166
|
+
counter.a++
|
|
167
|
+
const global = yield* GlobalCounter
|
|
168
|
+
yield* Ref.update(global.ref, (n) => n + 1)
|
|
169
|
+
return counter.a
|
|
170
|
+
}, Effect.provide(PerRequestCounter.Default)),
|
|
171
|
+
incrementB: Effect.fn(function*() {
|
|
172
|
+
const counter = yield* PerRequestCounter
|
|
173
|
+
counter.a++
|
|
174
|
+
const global = yield* GlobalCounter
|
|
175
|
+
yield* Ref.update(global.ref, (n) => n + 1)
|
|
176
|
+
return counter.a
|
|
177
|
+
}, Effect.provide(PerRequestCounter.Default))
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
const GlobalCounterLive = Layer.effect(
|
|
181
|
+
GlobalCounter,
|
|
182
|
+
Ref.make(0).pipe(Effect.map((ref) => ({ ref })))
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
const CounterTestLayer = counterImpl.pipe(Layer.provideMerge(GlobalCounterLive))
|
|
186
|
+
|
|
187
|
+
it.live(
|
|
188
|
+
"per-request service isolation with shared global counter",
|
|
189
|
+
Effect.fnUntraced(
|
|
190
|
+
function*() {
|
|
191
|
+
const client = yield* RpcTest.makeClient(CounterRpcs)
|
|
192
|
+
const global = yield* GlobalCounter
|
|
193
|
+
|
|
194
|
+
const r1 = yield* client.incrementA()
|
|
195
|
+
const r2 = yield* client.incrementB()
|
|
196
|
+
|
|
197
|
+
// per-request counter is fresh each time → both return 1
|
|
198
|
+
expect(r1).toBe(1)
|
|
199
|
+
expect(r2).toBe(1)
|
|
200
|
+
|
|
201
|
+
// global counter is shared across requests → accumulates to 2
|
|
202
|
+
const globalCount = yield* Ref.get(global.ref)
|
|
203
|
+
expect(globalCount).toBe(2)
|
|
204
|
+
},
|
|
205
|
+
Effect.provide(CounterTestLayer)
|
|
206
|
+
)
|
|
207
|
+
)
|