@effect-app/infra 4.0.0-beta.22 → 4.0.0-beta.220
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 +1640 -0
- package/_check.sh +1 -1
- package/dist/CUPS.d.ts +7 -7
- package/dist/CUPS.d.ts.map +1 -1
- package/dist/CUPS.js +10 -12
- package/dist/Emailer/Sendgrid.d.ts +14 -14
- package/dist/Emailer/Sendgrid.d.ts.map +1 -1
- package/dist/Emailer/Sendgrid.js +16 -15
- package/dist/Emailer/fake.d.ts +1 -1
- package/dist/Emailer/service.d.ts +10 -4
- package/dist/Emailer/service.d.ts.map +1 -1
- package/dist/Emailer/service.js +3 -3
- package/dist/Emailer.d.ts +1 -1
- package/dist/MainFiberSet.d.ts +9 -9
- 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 +33 -15
- 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 +6 -6
- package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
- package/dist/Model/Repository/internal/internal.js +103 -51
- package/dist/Model/Repository/legacy.d.ts +1 -1
- package/dist/Model/Repository/makeRepo.d.ts +7 -6
- package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
- package/dist/Model/Repository/makeRepo.js +5 -1
- package/dist/Model/Repository/service.d.ts +28 -23
- package/dist/Model/Repository/service.d.ts.map +1 -1
- package/dist/Model/Repository/validation.d.ts +46 -17
- package/dist/Model/Repository/validation.d.ts.map +1 -1
- package/dist/Model/Repository/validation.js +5 -5
- 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 +4 -4
- package/dist/Model/dsl.d.ts.map +1 -1
- 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 +139 -16
- package/dist/Model/query/dsl.d.ts.map +1 -1
- package/dist/Model/query/dsl.js +187 -1
- package/dist/Model/query/new-kid-interpreter.d.ts +76 -7
- package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -1
- package/dist/Model/query/new-kid-interpreter.js +122 -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 +5 -7
- package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
- package/dist/QueueMaker/SQLQueue.js +130 -116
- package/dist/QueueMaker/errors.d.ts +2 -2
- package/dist/QueueMaker/errors.d.ts.map +1 -1
- package/dist/QueueMaker/memQueue.d.ts +7 -4
- package/dist/QueueMaker/memQueue.d.ts.map +1 -1
- package/dist/QueueMaker/memQueue.js +75 -63
- package/dist/QueueMaker/sbqueue.d.ts +6 -3
- package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
- package/dist/QueueMaker/sbqueue.js +52 -53
- package/dist/QueueMaker/service.d.ts +1 -1
- package/dist/RequestContext.d.ts +74 -35
- package/dist/RequestContext.d.ts.map +1 -1
- package/dist/RequestContext.js +13 -14
- package/dist/RequestFiberSet.d.ts +7 -7
- package/dist/RequestFiberSet.d.ts.map +1 -1
- package/dist/RequestFiberSet.js +3 -3
- package/dist/Store/ContextMapContainer.d.ts +19 -3
- package/dist/Store/ContextMapContainer.d.ts.map +1 -1
- package/dist/Store/ContextMapContainer.js +13 -3
- package/dist/Store/Cosmos/query.d.ts +5 -1
- package/dist/Store/Cosmos/query.d.ts.map +1 -1
- package/dist/Store/Cosmos/query.js +113 -34
- package/dist/Store/Cosmos.d.ts +1 -1
- package/dist/Store/Cosmos.d.ts.map +1 -1
- package/dist/Store/Cosmos.js +335 -243
- package/dist/Store/Disk.d.ts +2 -2
- package/dist/Store/Disk.d.ts.map +1 -1
- package/dist/Store/Disk.js +72 -35
- package/dist/Store/Memory.d.ts +6 -4
- package/dist/Store/Memory.d.ts.map +1 -1
- package/dist/Store/Memory.js +242 -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 +231 -0
- package/dist/Store/SQL/query.d.ts +42 -0
- package/dist/Store/SQL/query.d.ts.map +1 -0
- package/dist/Store/SQL/query.js +479 -0
- package/dist/Store/SQL.d.ts +20 -0
- package/dist/Store/SQL.d.ts.map +1 -0
- package/dist/Store/SQL.js +446 -0
- package/dist/Store/codeFilter.d.ts +1 -1
- package/dist/Store/codeFilter.d.ts.map +1 -1
- package/dist/Store/codeFilter.js +4 -2
- package/dist/Store/index.d.ts +5 -2
- package/dist/Store/index.d.ts.map +1 -1
- package/dist/Store/index.js +15 -3
- package/dist/Store/service.d.ts +22 -8
- package/dist/Store/service.d.ts.map +1 -1
- package/dist/Store/service.js +24 -6
- package/dist/Store/utils.d.ts +1 -1
- package/dist/Store/utils.d.ts.map +1 -1
- package/dist/Store/utils.js +3 -4
- package/dist/Store.d.ts +1 -1
- package/dist/adapters/SQL/Model.d.ts +31 -42
- package/dist/adapters/SQL/Model.d.ts.map +1 -1
- package/dist/adapters/SQL/Model.js +29 -38
- package/dist/adapters/SQL.d.ts +1 -1
- package/dist/adapters/ServiceBus.d.ts +11 -11
- package/dist/adapters/ServiceBus.d.ts.map +1 -1
- package/dist/adapters/ServiceBus.js +25 -21
- package/dist/adapters/cosmos-client.d.ts +3 -3
- package/dist/adapters/cosmos-client.d.ts.map +1 -1
- package/dist/adapters/cosmos-client.js +3 -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 +1 -1
- package/dist/adapters/logger.d.ts.map +1 -1
- package/dist/adapters/memQueue.d.ts +3 -3
- package/dist/adapters/memQueue.d.ts.map +1 -1
- package/dist/adapters/memQueue.js +3 -3
- package/dist/adapters/mongo-client.d.ts +3 -3
- 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 +3 -3
- package/dist/adapters/redis-client.d.ts.map +1 -1
- package/dist/adapters/redis-client.js +3 -3
- package/dist/api/ContextProvider.d.ts +8 -8
- package/dist/api/ContextProvider.d.ts.map +1 -1
- package/dist/api/ContextProvider.js +6 -6
- package/dist/api/codec.d.ts +1 -1
- package/dist/api/internal/RequestContextMiddleware.d.ts +2 -2
- package/dist/api/internal/RequestContextMiddleware.d.ts.map +1 -1
- package/dist/api/internal/RequestContextMiddleware.js +9 -6
- package/dist/api/internal/auth.d.ts +44 -6
- 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 +3 -3
- package/dist/api/internal/events.d.ts.map +1 -1
- package/dist/api/internal/events.js +10 -8
- package/dist/api/internal/health.d.ts +1 -1
- package/dist/api/layerUtils.d.ts +6 -6
- package/dist/api/layerUtils.d.ts.map +1 -1
- package/dist/api/layerUtils.js +5 -5
- package/dist/api/middlewares.d.ts +1 -1
- package/dist/api/reportError.d.ts +1 -1
- package/dist/api/routing/middleware/RouterMiddleware.d.ts +4 -4
- package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware.d.ts +39 -3
- package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware.js +48 -16
- 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/tsort.d.ts +1 -1
- package/dist/api/routing/tsort.d.ts.map +1 -1
- package/dist/api/routing/utils.d.ts +3 -3
- package/dist/api/routing/utils.d.ts.map +1 -1
- package/dist/api/routing.d.ts +80 -37
- package/dist/api/routing.d.ts.map +1 -1
- package/dist/api/routing.js +109 -41
- package/dist/api/setupRequest.d.ts +8 -5
- package/dist/api/setupRequest.d.ts.map +1 -1
- package/dist/api/setupRequest.js +12 -7
- package/dist/api/util.d.ts +1 -1
- package/dist/arbs.d.ts +1 -1
- package/dist/arbs.d.ts.map +1 -1
- package/dist/arbs.js +5 -3
- package/dist/errorReporter.d.ts +4 -4
- package/dist/errorReporter.d.ts.map +1 -1
- package/dist/errorReporter.js +20 -25
- package/dist/errors.d.ts +1 -1
- package/dist/fileUtil.d.ts +1 -1
- package/dist/fileUtil.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/logger/jsonLogger.d.ts +1 -1
- package/dist/logger/logFmtLogger.d.ts +1 -1
- package/dist/logger/shared.d.ts +1 -1
- package/dist/logger/shared.js +2 -2
- 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 +9 -3
- package/dist/rateLimit.d.ts.map +1 -1
- package/dist/rateLimit.js +5 -11
- package/dist/test.d.ts +2 -2
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +1 -1
- package/dist/vitest.d.ts +1 -1
- package/examples/query.ts +42 -38
- package/package.json +46 -37
- package/src/CUPS.ts +9 -11
- package/src/Emailer/Sendgrid.ts +17 -14
- package/src/Emailer/service.ts +9 -3
- package/src/MainFiberSet.ts +5 -6
- package/src/Model/Repository/Registry.ts +33 -0
- package/src/Model/Repository/ext.ts +96 -10
- package/src/Model/Repository/internal/internal.ts +218 -149
- package/src/Model/Repository/makeRepo.ts +12 -10
- package/src/Model/Repository/service.ts +31 -22
- package/src/Model/Repository/validation.ts +4 -4
- package/src/Model/Repository.ts +1 -0
- package/src/Model/dsl.ts +3 -3
- package/src/Model/filter/types/path/eager.ts +1 -2
- package/src/Model/query/dsl.ts +348 -18
- package/src/Model/query/new-kid-interpreter.ts +206 -6
- package/src/Model.ts +1 -0
- package/src/QueueMaker/SQLQueue.ts +144 -152
- package/src/QueueMaker/memQueue.ts +104 -103
- package/src/QueueMaker/sbqueue.ts +70 -86
- package/src/RequestContext.ts +14 -16
- package/src/RequestFiberSet.ts +2 -2
- package/src/Store/ContextMapContainer.ts +41 -2
- package/src/Store/Cosmos/query.ts +140 -43
- package/src/Store/Cosmos.ts +482 -349
- package/src/Store/Disk.ts +102 -65
- package/src/Store/Memory.ts +275 -87
- package/src/Store/SQL/Pg.ts +361 -0
- package/src/Store/SQL/query.ts +539 -0
- package/src/Store/SQL.ts +731 -0
- package/src/Store/codeFilter.ts +3 -1
- package/src/Store/index.ts +17 -2
- package/src/Store/service.ts +41 -10
- package/src/Store/utils.ts +23 -22
- package/src/adapters/SQL/Model.ts +41 -40
- package/src/adapters/ServiceBus.ts +125 -121
- package/src/adapters/cosmos-client.ts +2 -2
- package/src/adapters/index.ts +7 -0
- 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 +12 -13
- package/src/api/internal/RequestContextMiddleware.ts +15 -5
- package/src/api/internal/auth.ts +246 -44
- package/src/api/internal/events.ts +13 -9
- package/src/api/layerUtils.ts +8 -8
- package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
- package/src/api/routing/middleware/middleware.ts +55 -14
- package/src/api/routing/middleware.ts +0 -2
- package/src/api/routing.ts +296 -131
- package/src/api/setupRequest.ts +28 -8
- package/src/arbs.ts +4 -2
- package/src/errorReporter.ts +62 -74
- package/src/logger/shared.ts +1 -1
- package/src/otel.ts +152 -0
- package/src/rateLimit.ts +30 -22
- package/src/test.ts +1 -1
- package/test/auth.test.ts +101 -0
- package/test/contextProvider.test.ts +11 -11
- package/test/controller.test.ts +21 -30
- 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 +26 -12
- package/test/dist/fixtures.d.ts.map +1 -1
- package/test/dist/fixtures.js +12 -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 +11 -9
- package/test/query.test.ts +813 -38
- package/test/rawQuery.test.ts +301 -20
- package/test/repository-ext.test.ts +60 -0
- package/test/requires.test.ts +6 -6
- package/test/router-generator.test.ts +183 -0
- package/test/routing-interruptibility.test.ts +63 -0
- package/test/rpc-e2e-invalidation.test.ts +251 -0
- package/test/rpc-multi-middleware.test.ts +78 -9
- package/test/rpc-stream-fullstack.test.ts +300 -0
- package/test/sql-store.test.ts +1592 -0
- package/test/validateSample.test.ts +15 -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
package/src/api/setupRequest.ts
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
import { Effect, Layer, Tracer } from "effect-app"
|
|
1
|
+
import { Effect, Layer, Option, Tracer } from "effect-app"
|
|
2
2
|
import { NonEmptyString255 } from "effect-app/Schema"
|
|
3
|
+
import { SqlClient } from "effect/unstable/sql"
|
|
3
4
|
import { LocaleRef, RequestContext, spanAttributes } from "../RequestContext.js"
|
|
4
5
|
import { ContextMapContainer } from "../Store/ContextMapContainer.js"
|
|
5
6
|
import { storeId } from "../Store/Memory.js"
|
|
6
7
|
|
|
8
|
+
const withSqlTransaction = <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
|
|
9
|
+
Effect.serviceOption(SqlClient.SqlClient).pipe(
|
|
10
|
+
Effect.flatMap(Option.match({
|
|
11
|
+
onNone: () => self,
|
|
12
|
+
onSome: (sql) => sql.withTransaction(self).pipe(Effect.orDie)
|
|
13
|
+
}))
|
|
14
|
+
)
|
|
15
|
+
|
|
7
16
|
export const getRequestContext = Effect
|
|
8
17
|
.all({
|
|
9
18
|
span: Effect.currentSpan.pipe(Effect.orDie),
|
|
@@ -12,7 +21,7 @@ export const getRequestContext = Effect
|
|
|
12
21
|
})
|
|
13
22
|
.pipe(
|
|
14
23
|
Effect.map(({ locale, namespace, span }) =>
|
|
15
|
-
|
|
24
|
+
RequestContext.make({
|
|
16
25
|
span: Tracer.externalSpan(span),
|
|
17
26
|
locale,
|
|
18
27
|
namespace,
|
|
@@ -43,16 +52,25 @@ const withRequestSpan = (name = "request", options?: Tracer.SpanOptions) => <R,
|
|
|
43
52
|
)
|
|
44
53
|
)
|
|
45
54
|
|
|
55
|
+
export interface SetupRequestOptions {
|
|
56
|
+
readonly withTransaction?: boolean
|
|
57
|
+
}
|
|
58
|
+
|
|
46
59
|
export const setupRequestContextFromCurrent =
|
|
47
|
-
(name = "request", options?: Tracer.SpanOptions) => <R, E, A>(self: Effect.Effect<A, E, R>) =>
|
|
60
|
+
(name = "request", options?: Tracer.SpanOptions & SetupRequestOptions) => <R, E, A>(self: Effect.Effect<A, E, R>) =>
|
|
48
61
|
self
|
|
49
62
|
.pipe(
|
|
63
|
+
options?.withTransaction === true ? withSqlTransaction : (_) => _,
|
|
50
64
|
withRequestSpan(name, options),
|
|
51
|
-
Effect.provide(ContextMapContainer.layer)
|
|
65
|
+
Effect.provide(ContextMapContainer.layer, { local: true })
|
|
52
66
|
)
|
|
53
67
|
|
|
54
68
|
// TODO: consider integrating Effect.withParentSpan
|
|
55
|
-
export function setupRequestContext<R, E, A>(
|
|
69
|
+
export function setupRequestContext<R, E, A>(
|
|
70
|
+
self: Effect.Effect<A, E, R>,
|
|
71
|
+
requestContext: RequestContext,
|
|
72
|
+
options?: SetupRequestOptions
|
|
73
|
+
) {
|
|
56
74
|
const layer = Layer.mergeAll(
|
|
57
75
|
ContextMapContainer.layer,
|
|
58
76
|
Layer.succeed(LocaleRef, requestContext.locale),
|
|
@@ -60,8 +78,9 @@ export function setupRequestContext<R, E, A>(self: Effect.Effect<A, E, R>, reque
|
|
|
60
78
|
)
|
|
61
79
|
return self
|
|
62
80
|
.pipe(
|
|
81
|
+
options?.withTransaction === true ? withSqlTransaction : (_) => _,
|
|
63
82
|
withRequestSpan(requestContext.name),
|
|
64
|
-
Effect.provide(layer)
|
|
83
|
+
Effect.provide(layer, { local: true })
|
|
65
84
|
)
|
|
66
85
|
}
|
|
67
86
|
|
|
@@ -69,7 +88,7 @@ export function setupRequestContextWithCustomSpan<R, E, A>(
|
|
|
69
88
|
self: Effect.Effect<A, E, R>,
|
|
70
89
|
requestContext: RequestContext,
|
|
71
90
|
name: string,
|
|
72
|
-
options?: Tracer.SpanOptions
|
|
91
|
+
options?: Tracer.SpanOptions & SetupRequestOptions
|
|
73
92
|
) {
|
|
74
93
|
const layer = Layer.mergeAll(
|
|
75
94
|
ContextMapContainer.layer,
|
|
@@ -78,7 +97,8 @@ export function setupRequestContextWithCustomSpan<R, E, A>(
|
|
|
78
97
|
)
|
|
79
98
|
return self
|
|
80
99
|
.pipe(
|
|
100
|
+
options?.withTransaction === true ? withSqlTransaction : (_) => _,
|
|
81
101
|
withRequestSpan(name, options),
|
|
82
|
-
Effect.provide(layer)
|
|
102
|
+
Effect.provide(layer, { local: true })
|
|
83
103
|
)
|
|
84
104
|
}
|
package/src/arbs.ts
CHANGED
|
@@ -5,9 +5,11 @@ import { type S } from "effect-app"
|
|
|
5
5
|
import { setFaker } from "effect-app/faker"
|
|
6
6
|
import * as FastCheck from "effect/testing/FastCheck"
|
|
7
7
|
import { Random } from "fast-check"
|
|
8
|
-
import
|
|
8
|
+
import { congruential32 } from "pure-rand/generator/congruential32"
|
|
9
9
|
|
|
10
|
-
const
|
|
10
|
+
const seed = 5
|
|
11
|
+
const rng = congruential32(seed)
|
|
12
|
+
const rnd = new Random(rng)
|
|
11
13
|
|
|
12
14
|
setFaker(faker)
|
|
13
15
|
|
package/src/errorReporter.ts
CHANGED
|
@@ -13,47 +13,41 @@ const tryCauseException = <E>(cause: Cause.Cause<E>, name: string): CauseExcepti
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export function reportError(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return
|
|
29
|
-
}
|
|
30
|
-
const error = tryCauseException(cause, name)
|
|
31
|
-
|
|
32
|
-
yield* reportSentry(error, extras, LogLevelToSentry(level))
|
|
33
|
-
yield* InfraLogger
|
|
34
|
-
.logWithLevel(level, "Reporting error", cause)
|
|
35
|
-
.pipe(
|
|
36
|
-
Effect.annotateLogs(dropUndefined({
|
|
37
|
-
extras,
|
|
38
|
-
error: tryToReport(error),
|
|
39
|
-
cause: tryToJson(cause),
|
|
40
|
-
__error_name__: name
|
|
41
|
-
}))
|
|
42
|
-
)
|
|
43
|
-
.pipe(
|
|
44
|
-
Effect.catchCause((cause) => InfraLogger.logWarning("Failed to log error", cause)),
|
|
45
|
-
Effect.catchCause(() => InfraLogger.logFatal("Failed to log error cause"))
|
|
46
|
-
)
|
|
16
|
+
export function reportError(name: string) {
|
|
17
|
+
return Effect.fnUntraced(
|
|
18
|
+
function*(
|
|
19
|
+
cause: Cause.Cause<unknown>,
|
|
20
|
+
extras?: Record<string, unknown>,
|
|
21
|
+
level: LogLevel.Severity = "Error"
|
|
22
|
+
) {
|
|
23
|
+
if (Cause.hasInterruptsOnly(cause)) {
|
|
24
|
+
yield* InfraLogger.logDebug("Interrupted").pipe(Effect.annotateLogs("extras", JSON.stringify(extras ?? {})))
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
const error = tryCauseException(cause, name)
|
|
47
28
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
29
|
+
yield* reportSentry(error, extras, LogLevelToSentry(level))
|
|
30
|
+
yield* InfraLogger
|
|
31
|
+
.logWithLevel(level, "Reporting error", cause)
|
|
32
|
+
.pipe(
|
|
33
|
+
Effect.annotateLogs(dropUndefined({
|
|
34
|
+
extras,
|
|
35
|
+
error: tryToReport(error),
|
|
36
|
+
cause: tryToJson(cause),
|
|
37
|
+
__error_name__: name
|
|
38
|
+
})),
|
|
39
|
+
Effect.catchCause((cause) => InfraLogger.logWarning("Failed to log error", cause)),
|
|
40
|
+
Effect.catchCause(() => InfraLogger.logFatal("Failed to log error cause"))
|
|
55
41
|
)
|
|
56
|
-
|
|
42
|
+
|
|
43
|
+
return error
|
|
44
|
+
},
|
|
45
|
+
(effect) =>
|
|
46
|
+
Effect.tapCause(effect, (cause) =>
|
|
47
|
+
InfraLogger.logError("Failed to report error", cause).pipe(
|
|
48
|
+
Effect.tapCause(() => InfraLogger.logFatal("Failed to log error cause"))
|
|
49
|
+
))
|
|
50
|
+
)
|
|
57
51
|
}
|
|
58
52
|
|
|
59
53
|
function reportSentry(
|
|
@@ -66,45 +60,39 @@ function reportSentry(
|
|
|
66
60
|
scope.setLevel(level)
|
|
67
61
|
if (context) scope.setContext("context", { ...context })
|
|
68
62
|
if (extras) scope.setContext("extras", extras)
|
|
69
|
-
|
|
70
|
-
scope.setContext("
|
|
63
|
+
const squashed = Cause.squash(error.originalCause)
|
|
64
|
+
scope.setContext("mainError", tryToJson(squashed))
|
|
65
|
+
scope.setContext("error", tryToReport(error))
|
|
66
|
+
scope.setContext("cause", tryToJson(error.originalCause))
|
|
71
67
|
Sentry.captureException(error, scope)
|
|
72
68
|
}))
|
|
73
69
|
}
|
|
74
70
|
|
|
75
|
-
export function logError<E>(
|
|
76
|
-
|
|
77
|
-
) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}))
|
|
93
|
-
)
|
|
94
|
-
})
|
|
95
|
-
.pipe(
|
|
96
|
-
Effect.tapCause(() => InfraLogger.logFatal("Failed to log error cause"))
|
|
97
|
-
)
|
|
71
|
+
export function logError<E>(name: string) {
|
|
72
|
+
return Effect.fnUntraced(
|
|
73
|
+
function*(cause: Cause.Cause<E>, extras?: Record<string, unknown>) {
|
|
74
|
+
if (Cause.hasInterruptsOnly(cause)) {
|
|
75
|
+
yield* InfraLogger.logDebug("Interrupted").pipe(Effect.annotateLogs(dropUndefined({ extras })))
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
yield* InfraLogger
|
|
79
|
+
.logWarning("Logging error", cause)
|
|
80
|
+
.pipe(Effect.annotateLogs(dropUndefined({
|
|
81
|
+
extras,
|
|
82
|
+
cause: tryToJson(cause),
|
|
83
|
+
__error_name__: name
|
|
84
|
+
})))
|
|
85
|
+
},
|
|
86
|
+
(effect) => Effect.tapCause(effect, () => InfraLogger.logFatal("Failed to log error cause"))
|
|
87
|
+
)
|
|
98
88
|
}
|
|
99
89
|
|
|
100
|
-
export
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
Sentry.captureMessage(message, scope)
|
|
90
|
+
export const reportMessage = Effect.fnUntraced(function*(message: string, extras?: Record<string, unknown>) {
|
|
91
|
+
const context = yield* getRC
|
|
92
|
+
const scope = new Sentry.Scope()
|
|
93
|
+
if (context) scope.setContext("context", { ...context })
|
|
94
|
+
if (extras) scope.setContext("extras", extras)
|
|
95
|
+
Sentry.captureMessage(message, scope)
|
|
107
96
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
97
|
+
console.warn(message, extras)
|
|
98
|
+
})
|
package/src/logger/shared.ts
CHANGED
|
@@ -7,7 +7,7 @@ export function getRequestContextFromFiber(fiber: Fiber.Fiber<unknown, unknown>)
|
|
|
7
7
|
const span = Option.fromNullishOr(fiber.currentSpan)
|
|
8
8
|
const locale = fiber.getRef(LocaleRef)
|
|
9
9
|
const namespace = fiber.getRef(storeId)
|
|
10
|
-
return
|
|
10
|
+
return RequestContext.make({
|
|
11
11
|
span: Option.map(span, (s) => ({ spanId: s.spanId, traceId: s.traceId, sampled: s.sampled })).pipe(
|
|
12
12
|
Option.getOrElse(() => ({ spanId: "bogus", sampled: true, traceId: "bogus" }))
|
|
13
13
|
),
|
package/src/otel.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry semantic-convention helpers for span attributes.
|
|
3
|
+
*
|
|
4
|
+
* Aligns repository / queue / cache adapters with stable OTel semconv keys so
|
|
5
|
+
* downstream collectors and dashboards work without per-adapter mappings.
|
|
6
|
+
*
|
|
7
|
+
* - Database: https://opentelemetry.io/docs/specs/semconv/database/
|
|
8
|
+
* - Messaging: https://opentelemetry.io/docs/specs/semconv/messaging/
|
|
9
|
+
* - Cosmos DB: https://opentelemetry.io/docs/specs/semconv/database/cosmosdb/
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Effect } from "effect-app"
|
|
13
|
+
|
|
14
|
+
export type DbSystem =
|
|
15
|
+
| "postgresql"
|
|
16
|
+
| "sqlite"
|
|
17
|
+
| "cosmosdb"
|
|
18
|
+
| "mongodb"
|
|
19
|
+
| "redis"
|
|
20
|
+
| "other_sql"
|
|
21
|
+
| "memory"
|
|
22
|
+
| "disk"
|
|
23
|
+
|
|
24
|
+
export interface DbSpanOptions {
|
|
25
|
+
/** OTel `db.operation.name` (e.g. `find`, `all`, `filter`, `set`). */
|
|
26
|
+
readonly operation: string
|
|
27
|
+
readonly system: DbSystem
|
|
28
|
+
/** Logical collection / table / container name. */
|
|
29
|
+
readonly collection: string
|
|
30
|
+
/** Tenant / namespace / database name. */
|
|
31
|
+
readonly namespace?: string | undefined
|
|
32
|
+
/** Application-level entity / model name (custom: `app.entity`). */
|
|
33
|
+
readonly entity?: string | undefined
|
|
34
|
+
/** Sanitized / parameterized query text. Never include bound values. */
|
|
35
|
+
readonly query?: string | undefined
|
|
36
|
+
/** Optional fragments merged into final attributes (e.g. id, partition). */
|
|
37
|
+
readonly extra?: Record<string, unknown> | undefined
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const dbAttributes = (a: DbSpanOptions): Record<string, unknown> => ({
|
|
41
|
+
"db.system.name": a.system,
|
|
42
|
+
"db.operation.name": a.operation,
|
|
43
|
+
"db.collection.name": a.collection,
|
|
44
|
+
...(a.namespace !== undefined && { "db.namespace": a.namespace }),
|
|
45
|
+
...(a.query !== undefined && { "db.query.text": a.query }),
|
|
46
|
+
...(a.entity !== undefined && { "app.entity": a.entity }),
|
|
47
|
+
...a.extra
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Wrap an effect with an OTel-semconv database span.
|
|
52
|
+
*
|
|
53
|
+
* Span name follows the low-cardinality convention: `<operation> <collection>`.
|
|
54
|
+
*/
|
|
55
|
+
export const withDbSpan = (a: DbSpanOptions) =>
|
|
56
|
+
Effect.withSpan(
|
|
57
|
+
`${a.operation} ${a.collection}`,
|
|
58
|
+
{ attributes: dbAttributes(a), kind: "client" as const },
|
|
59
|
+
{ captureStackTrace: false }
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Annotate the current span with OTel-semconv database attributes.
|
|
64
|
+
*
|
|
65
|
+
* Use when the caller already owns the span (e.g. a repository) and the
|
|
66
|
+
* adapter should only contribute db.* semconv attrs without opening a child.
|
|
67
|
+
* Annotates before running so attrs persist even on failure.
|
|
68
|
+
* No-op if there is no current span.
|
|
69
|
+
*/
|
|
70
|
+
export const annotateDb = (a: DbSpanOptions) => <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
|
|
71
|
+
Effect.flatMap(Effect.annotateCurrentSpan(dbAttributes(a)), () => self)
|
|
72
|
+
|
|
73
|
+
/** Annotate the current span with response metrics from a DB call. */
|
|
74
|
+
export const annotateDbResponse = (m: {
|
|
75
|
+
readonly returnedRows?: number | undefined
|
|
76
|
+
readonly responseBytes?: number | undefined
|
|
77
|
+
}) =>
|
|
78
|
+
Effect.annotateCurrentSpan({
|
|
79
|
+
...(m.returnedRows !== undefined && { "db.response.returned_rows": m.returnedRows }),
|
|
80
|
+
...(m.responseBytes !== undefined && { "db.response.body.size": m.responseBytes })
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
/** Cosmos-specific response annotations. */
|
|
84
|
+
export const annotateCosmosResponse = (m: {
|
|
85
|
+
readonly requestCharge?: number | undefined
|
|
86
|
+
readonly returnedRows?: number | undefined
|
|
87
|
+
readonly responseBytes?: number | undefined
|
|
88
|
+
readonly statusCode?: number | undefined
|
|
89
|
+
}) =>
|
|
90
|
+
Effect.annotateCurrentSpan({
|
|
91
|
+
...(m.requestCharge !== undefined && { "azure.cosmosdb.operation.request_charge": m.requestCharge }),
|
|
92
|
+
...(m.statusCode !== undefined && { "db.response.status_code": String(m.statusCode) }),
|
|
93
|
+
...(m.returnedRows !== undefined && { "db.response.returned_rows": m.returnedRows }),
|
|
94
|
+
...(m.responseBytes !== undefined && { "db.response.body.size": m.responseBytes })
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
export type MessagingSystem =
|
|
98
|
+
| "servicebus"
|
|
99
|
+
| "rabbitmq"
|
|
100
|
+
| "kafka"
|
|
101
|
+
| "memory"
|
|
102
|
+
| "sql"
|
|
103
|
+
|
|
104
|
+
export type MessagingOperation =
|
|
105
|
+
| "publish"
|
|
106
|
+
| "create"
|
|
107
|
+
| "receive"
|
|
108
|
+
| "process"
|
|
109
|
+
| "settle"
|
|
110
|
+
|
|
111
|
+
export interface MessagingSpanOptions {
|
|
112
|
+
readonly operation: MessagingOperation
|
|
113
|
+
readonly system: MessagingSystem
|
|
114
|
+
/** Queue / topic name. */
|
|
115
|
+
readonly destination: string
|
|
116
|
+
readonly messageId?: string | undefined
|
|
117
|
+
readonly conversationId?: string | undefined
|
|
118
|
+
readonly bodySize?: number | undefined
|
|
119
|
+
readonly extra?: Record<string, unknown> | undefined
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const messagingAttributes = (a: MessagingSpanOptions): Record<string, unknown> => ({
|
|
123
|
+
"messaging.system": a.system,
|
|
124
|
+
"messaging.operation.name": a.operation,
|
|
125
|
+
"messaging.destination.name": a.destination,
|
|
126
|
+
...(a.messageId !== undefined && { "messaging.message.id": a.messageId }),
|
|
127
|
+
...(a.conversationId !== undefined && { "messaging.message.conversation_id": a.conversationId }),
|
|
128
|
+
...(a.bodySize !== undefined && { "messaging.message.body.size": a.bodySize }),
|
|
129
|
+
...a.extra
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
/** Wrap an effect with an OTel-semconv messaging span. */
|
|
133
|
+
export const withMessagingSpan = (
|
|
134
|
+
a: MessagingSpanOptions,
|
|
135
|
+
kind: "producer" | "consumer"
|
|
136
|
+
) =>
|
|
137
|
+
Effect.withSpan(
|
|
138
|
+
`${a.operation} ${a.destination}`,
|
|
139
|
+
{ kind, attributes: messagingAttributes(a) },
|
|
140
|
+
{ captureStackTrace: false }
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
/** Build messaging span options without wrapping (for Effect.fn / setupRequestContextWithCustomSpan). */
|
|
144
|
+
export const messagingSpanArgs = (
|
|
145
|
+
a: MessagingSpanOptions,
|
|
146
|
+
kind: "producer" | "consumer"
|
|
147
|
+
) =>
|
|
148
|
+
({
|
|
149
|
+
name: `${a.operation} ${a.destination}`,
|
|
150
|
+
kind,
|
|
151
|
+
attributes: messagingAttributes(a)
|
|
152
|
+
}) as const
|
package/src/rateLimit.ts
CHANGED
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
// }
|
|
22
22
|
|
|
23
23
|
import { Array, type Duration, Effect, type NonEmptyArray } from "effect-app"
|
|
24
|
+
import { dual } from "effect-app/Function"
|
|
24
25
|
import type { Semaphore } from "effect/Semaphore"
|
|
26
|
+
import type { Concurrency } from "effect/Types"
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* Executes the specified effect, acquiring the specified number of permits
|
|
@@ -45,36 +47,42 @@ export function SEM_withPermitsDuration(permits: number, duration: Duration.Dura
|
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
export
|
|
49
|
-
|
|
50
|
-
forEachItem: (item: T, iWithinBatch: number, batchI: number) => Effect.Effect<A, E, R>,
|
|
51
|
-
forEachBatch: (a: NonEmptyArray<A>, i: number) => Effect.Effect<A2, E2, R2>
|
|
52
|
-
) {
|
|
53
|
-
return (items: Iterable<T>) =>
|
|
54
|
-
Effect.forEach(
|
|
55
|
-
Array.chunksOf(items, n),
|
|
56
|
-
(_, i) =>
|
|
57
|
-
Effect
|
|
58
|
-
.forEach(_, (_, j) => forEachItem(_, j, i), { concurrency: "inherit" })
|
|
59
|
-
.pipe(Effect.flatMap((_) => forEachBatch(_, i))),
|
|
60
|
-
{ concurrency: "inherit" }
|
|
61
|
-
)
|
|
50
|
+
export interface BatchOptions {
|
|
51
|
+
readonly concurrency?: Concurrency | undefined
|
|
62
52
|
}
|
|
63
53
|
|
|
64
|
-
export
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
)
|
|
69
|
-
|
|
54
|
+
export const batch: {
|
|
55
|
+
<T, A, E, R, A2, E2, R2>(
|
|
56
|
+
n: number,
|
|
57
|
+
forEachItem: (item: T, iWithinBatch: number, batchI: number) => Effect.Effect<A, E, R>,
|
|
58
|
+
forEachBatch: (a: NonEmptyArray<A>, i: number) => Effect.Effect<A2, E2, R2>,
|
|
59
|
+
options?: BatchOptions
|
|
60
|
+
): (items: Iterable<T>) => Effect.Effect<Array<A2>, E | E2, R | R2>
|
|
61
|
+
<T, A, E, R, A2, E2, R2>(
|
|
62
|
+
items: Iterable<T>,
|
|
63
|
+
n: number,
|
|
64
|
+
forEachItem: (item: T, iWithinBatch: number, batchI: number) => Effect.Effect<A, E, R>,
|
|
65
|
+
forEachBatch: (a: NonEmptyArray<A>, i: number) => Effect.Effect<A2, E2, R2>,
|
|
66
|
+
options?: BatchOptions
|
|
67
|
+
): Effect.Effect<Array<A2>, E | E2, R | R2>
|
|
68
|
+
} = dual(
|
|
69
|
+
(args) => typeof args[0] !== "number",
|
|
70
|
+
<T, A, E, R, A2, E2, R2>(
|
|
71
|
+
items: Iterable<T>,
|
|
72
|
+
n: number,
|
|
73
|
+
forEachItem: (item: T, iWithinBatch: number, batchI: number) => Effect.Effect<A, E, R>,
|
|
74
|
+
forEachBatch: (a: NonEmptyArray<A>, i: number) => Effect.Effect<A2, E2, R2>,
|
|
75
|
+
options?: BatchOptions
|
|
76
|
+
) =>
|
|
70
77
|
Effect.forEach(
|
|
71
78
|
Array.chunksOf(items, n),
|
|
72
79
|
(_, i) =>
|
|
73
80
|
Effect
|
|
74
81
|
.forEach(_, (_, j) => forEachItem(_, j, i), { concurrency: "inherit" })
|
|
75
|
-
.pipe(Effect.flatMap((_) => forEachBatch(_, i)))
|
|
82
|
+
.pipe(Effect.flatMap((_) => forEachBatch(_, i))),
|
|
83
|
+
{ concurrency: options?.concurrency }
|
|
76
84
|
)
|
|
77
|
-
|
|
85
|
+
)
|
|
78
86
|
|
|
79
87
|
// export function rateLimit(
|
|
80
88
|
// n: number,
|
package/src/test.ts
CHANGED
|
@@ -16,7 +16,7 @@ export const createRandomInstance = <A extends object, I, R>(s: S.Codec<A, I, R>
|
|
|
16
16
|
/**
|
|
17
17
|
* Like `createRandomInstance`, but takes encoded values rather than decoded ones.
|
|
18
18
|
*/
|
|
19
|
-
export const createRandomInstanceI = <A extends object, I>(s: S.Codec<A, I
|
|
19
|
+
export const createRandomInstanceI = <A extends object, I>(s: S.Codec<A, I> & { fields: S.Struct.Fields }) => {
|
|
20
20
|
const gen = generate(S.toArbitrary(s))
|
|
21
21
|
const encode = S.encodeSync(s)
|
|
22
22
|
const decode = S.decodeSync(s)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, expect, it } from "@effect/vitest"
|
|
2
|
+
import { Effect } from "effect-app"
|
|
3
|
+
import { HttpHeaders } from "effect-app/http"
|
|
4
|
+
import { SignJWT } from "jose"
|
|
5
|
+
import { checkJWTI, InvalidRequestError, InvalidTokenError, UnauthorizedError } from "../src/api/internal/auth.js"
|
|
6
|
+
|
|
7
|
+
const issuer = "https://issuer.example.com/"
|
|
8
|
+
const audience = "effect-app"
|
|
9
|
+
const secret = "test-secret-test-secret-test-secret"
|
|
10
|
+
|
|
11
|
+
const makeToken = () =>
|
|
12
|
+
new SignJWT({ scope: "read:all" })
|
|
13
|
+
.setProtectedHeader({ alg: "HS256", typ: "at+jwt" })
|
|
14
|
+
.setIssuer(issuer)
|
|
15
|
+
.setAudience(audience)
|
|
16
|
+
.setIssuedAt()
|
|
17
|
+
.setExpirationTime("10m")
|
|
18
|
+
.sign(new TextEncoder().encode(secret))
|
|
19
|
+
|
|
20
|
+
describe("checkJWTI", () => {
|
|
21
|
+
it.effect(
|
|
22
|
+
"validates a bearer token from headers",
|
|
23
|
+
Effect.fnUntraced(function*() {
|
|
24
|
+
const token = yield* Effect.promise(() => makeToken())
|
|
25
|
+
|
|
26
|
+
yield* checkJWTI({
|
|
27
|
+
audience,
|
|
28
|
+
issuer,
|
|
29
|
+
secret,
|
|
30
|
+
strict: true,
|
|
31
|
+
tokenSigningAlg: "HS256"
|
|
32
|
+
})(HttpHeaders.fromRecordUnsafe({ authorization: `Bearer ${token}` }))
|
|
33
|
+
})
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
it.effect(
|
|
37
|
+
"fails on malformed authorization headers",
|
|
38
|
+
Effect.fnUntraced(function*() {
|
|
39
|
+
const error = yield* Effect.flip(
|
|
40
|
+
checkJWTI({
|
|
41
|
+
audience,
|
|
42
|
+
issuer,
|
|
43
|
+
secret,
|
|
44
|
+
tokenSigningAlg: "HS256"
|
|
45
|
+
})(HttpHeaders.fromRecordUnsafe({ authorization: "Basic abc" }))
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
expect(error).toBeInstanceOf(InvalidRequestError)
|
|
49
|
+
expect(error.status).toBe(400)
|
|
50
|
+
})
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
it.effect(
|
|
54
|
+
"fails when the token is missing",
|
|
55
|
+
Effect.fnUntraced(function*() {
|
|
56
|
+
const error = yield* Effect.flip(
|
|
57
|
+
checkJWTI({
|
|
58
|
+
audience,
|
|
59
|
+
issuer,
|
|
60
|
+
secret,
|
|
61
|
+
tokenSigningAlg: "HS256"
|
|
62
|
+
})(HttpHeaders.empty)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
expect(error).toBeInstanceOf(UnauthorizedError)
|
|
66
|
+
expect(error.status).toBe(401)
|
|
67
|
+
})
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
it.effect(
|
|
71
|
+
"allows missing tokens when auth is optional",
|
|
72
|
+
Effect.fnUntraced(function*() {
|
|
73
|
+
yield* checkJWTI({
|
|
74
|
+
audience,
|
|
75
|
+
authRequired: false,
|
|
76
|
+
issuer,
|
|
77
|
+
secret,
|
|
78
|
+
tokenSigningAlg: "HS256"
|
|
79
|
+
})(HttpHeaders.empty)
|
|
80
|
+
})
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
it.effect(
|
|
84
|
+
"fails when the token signature is invalid",
|
|
85
|
+
Effect.fnUntraced(function*() {
|
|
86
|
+
const token = yield* Effect.promise(() => makeToken())
|
|
87
|
+
|
|
88
|
+
const error = yield* Effect.flip(
|
|
89
|
+
checkJWTI({
|
|
90
|
+
audience,
|
|
91
|
+
issuer,
|
|
92
|
+
secret: "wrong-secret-wrong-secret-wrong-secret",
|
|
93
|
+
tokenSigningAlg: "HS256"
|
|
94
|
+
})(HttpHeaders.fromRecordUnsafe({ authorization: `Bearer ${token}` }))
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
expect(error).toBeInstanceOf(InvalidTokenError)
|
|
98
|
+
expect(error.status).toBe(401)
|
|
99
|
+
})
|
|
100
|
+
)
|
|
101
|
+
})
|