@effect-app/infra 1.18.2 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Mechanism for extendning behaviour of all handlers on the server.
3
+ *
4
+ * @since 1.0.0
5
+ */
6
+ import * as crypto from "crypto";
7
+ import { dropUndefined } from "@effect-app/core/utils";
8
+ import { NotLoggedInError } from "@effect-app/infra/errors";
9
+ import * as Middleware from "@effect/platform/HttpMiddleware";
10
+ import * as HttpServerRequest from "@effect/platform/HttpServerRequest";
11
+ import * as ServerResponse from "@effect/platform/HttpServerResponse";
12
+ import { Effect } from "effect-app";
13
+ import { HttpBody, HttpHeaders, HttpServerResponse } from "effect-app/http";
14
+ import * as Either from "effect/Either";
15
+ import * as FiberRef from "effect/FiberRef";
16
+ import { pipe } from "effect/Function";
17
+ import * as HashMap from "effect/HashMap";
18
+ import * as Metric from "effect/Metric";
19
+ export const accessLog = (level = "Info") => Middleware.make((app) => pipe(HttpServerRequest.HttpServerRequest, Effect.flatMap((request) => Effect[`log${level}`](`${request.method} ${request.url}`)), Effect.flatMap(() => app)));
20
+ export const uuidLogAnnotation = (logAnnotationKey = "requestId") => Middleware.make((app) => pipe(Effect.sync(() => crypto.randomUUID()), Effect.flatMap((uuid) => FiberRef.update(FiberRef.currentLogAnnotations, HashMap.set(logAnnotationKey, uuid))), Effect.flatMap(() => app)));
21
+ export const endpointCallsMetric = () => {
22
+ const endpointCalledCounter = Metric.counter("server.endpoint_calls");
23
+ return Middleware.make((app) => Effect.gen(function* (_) {
24
+ const request = yield* _(HttpServerRequest.HttpServerRequest);
25
+ yield* _(Metric.increment(endpointCalledCounter), Effect.tagMetrics("path", request.url));
26
+ return yield* _(app);
27
+ }));
28
+ };
29
+ export const errorLog = Middleware.make((app) => Effect.gen(function* (_) {
30
+ const request = yield* _(HttpServerRequest.HttpServerRequest);
31
+ const response = yield* _(app);
32
+ if (response.status >= 400 && response.status < 500) {
33
+ yield* _(Effect.logWarning(`${request.method.toUpperCase()} ${request.url} client error ${response.status}`));
34
+ }
35
+ else if (response.status >= 500) {
36
+ yield* _(Effect.logError(`${request.method.toUpperCase()} ${request.url} server error ${response.status}`));
37
+ }
38
+ return response;
39
+ }));
40
+ export const toServerResponse = (err) => HttpServerResponse.empty().pipe(HttpServerResponse.setStatus(401), HttpServerResponse.setBody(HttpBody.unsafeJson({ message: err.message })));
41
+ export const basicAuth = (checkCredentials, options) => Middleware.make((app) => Effect.gen(function* (_) {
42
+ const headerName = options?.headerName ?? "Authorization";
43
+ const skippedPaths = options?.skipPaths ?? [];
44
+ const request = yield* _(HttpServerRequest.HttpServerRequest);
45
+ if (skippedPaths.includes(request.url)) {
46
+ return yield* _(app);
47
+ }
48
+ const authHeader = request.headers[headerName.toLowerCase()];
49
+ if (authHeader === undefined) {
50
+ return toServerResponse(new NotLoggedInError(`Expected header ${headerName}`));
51
+ }
52
+ const authorizationParts = authHeader.split(" ");
53
+ if (authorizationParts.length !== 2) {
54
+ return toServerResponse(new NotLoggedInError("Incorrect auhorization scheme. Expected \"Basic <credentials>\""));
55
+ }
56
+ if (authorizationParts[0] !== "Basic") {
57
+ return toServerResponse(new NotLoggedInError(`Incorrect auhorization type. Expected "Basic", got "${authorizationParts[0]}"`));
58
+ }
59
+ const credentialsBuffer = Buffer.from(authorizationParts[1], "base64");
60
+ const credentialsText = credentialsBuffer.toString("utf-8");
61
+ const credentialsParts = credentialsText.split(":");
62
+ if (credentialsParts.length !== 2) {
63
+ return toServerResponse(new NotLoggedInError("Incorrect basic auth credentials format. Expected base64 encoded \"<user>:<pass>\"."));
64
+ }
65
+ const check = yield* _(checkCredentials({
66
+ user: credentialsParts[0],
67
+ password: credentialsParts[1]
68
+ }), Effect.either);
69
+ if (Either.isLeft(check)) {
70
+ return toServerResponse(check.left);
71
+ }
72
+ return yield* _(app);
73
+ }));
74
+ export const cors = (_options) => {
75
+ const DEFAULTS = {
76
+ allowedOrigins: ["*"],
77
+ allowedMethods: ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"],
78
+ allowedHeaders: [],
79
+ exposedHeaders: [],
80
+ credentials: false
81
+ };
82
+ const options = { ...DEFAULTS, ..._options };
83
+ const isAllowedOrigin = (origin) => {
84
+ return options.allowedOrigins.includes(origin);
85
+ };
86
+ const allowOrigin = (originHeader) => {
87
+ if (options.allowedOrigins.length === 0) {
88
+ return { "Access-Control-Allow-Origin": "*" };
89
+ }
90
+ if (options.allowedOrigins.length === 1) {
91
+ return {
92
+ "Access-Control-Allow-Origin": options.allowedOrigins[0],
93
+ Vary: "Origin"
94
+ };
95
+ }
96
+ if (isAllowedOrigin(originHeader)) {
97
+ return {
98
+ "Access-Control-Allow-Origin": originHeader,
99
+ Vary: "Origin"
100
+ };
101
+ }
102
+ return undefined;
103
+ };
104
+ const allowMethods = (() => {
105
+ if (options.allowedMethods.length > 0) {
106
+ return {
107
+ "Access-Control-Allow-Methods": options.allowedMethods.join(", ")
108
+ };
109
+ }
110
+ return undefined;
111
+ })();
112
+ const allowCredentials = (() => {
113
+ if (options.credentials) {
114
+ return { "Access-Control-Allow-Credentials": "true" };
115
+ }
116
+ return undefined;
117
+ })();
118
+ const allowHeaders = (accessControlRequestHeaders) => {
119
+ if (options.allowedHeaders.length === 0 && accessControlRequestHeaders) {
120
+ return {
121
+ Vary: "Access-Control-Request-Headers",
122
+ "Access-Control-Allow-Headers": accessControlRequestHeaders
123
+ };
124
+ }
125
+ if (options.allowedHeaders) {
126
+ return {
127
+ "Access-Control-Allow-Headers": options.allowedHeaders.join(",")
128
+ };
129
+ }
130
+ return undefined;
131
+ };
132
+ const exposeHeaders = (() => {
133
+ if (options.exposedHeaders.length > 0) {
134
+ return {
135
+ "Access-Control-Expose-Headers": options.exposedHeaders.join(",")
136
+ };
137
+ }
138
+ return undefined;
139
+ })();
140
+ const maxAge = (() => {
141
+ if (options.maxAge) {
142
+ return { "Access-Control-Max-Age": options.maxAge.toString() };
143
+ }
144
+ return undefined;
145
+ })();
146
+ return Middleware.make((app) => Effect.gen(function* (_) {
147
+ const request = yield* _(HttpServerRequest.HttpServerRequest);
148
+ const origin = request.headers["origin"];
149
+ const accessControlRequestHeaders = request.headers["access-control-request-headers"];
150
+ let corsHeaders = {
151
+ ...allowOrigin(origin ?? ""),
152
+ ...allowCredentials,
153
+ ...exposeHeaders
154
+ };
155
+ if (request.method === "OPTIONS") {
156
+ corsHeaders = {
157
+ ...corsHeaders,
158
+ ...allowMethods,
159
+ ...allowHeaders(accessControlRequestHeaders),
160
+ ...maxAge
161
+ };
162
+ return ServerResponse.empty({ status: 204, headers: HttpHeaders.fromInput(dropUndefined(corsHeaders)) });
163
+ }
164
+ const response = yield* _(app);
165
+ return response.pipe(ServerResponse.setHeaders(dropUndefined(corsHeaders)));
166
+ }));
167
+ };
168
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWlkZGxld2FyZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvYXBpL2ludGVybmFsL21pZGRsZXdhcmVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7O0dBSUc7QUFDSCxPQUFPLEtBQUssTUFBTSxNQUFNLFFBQVEsQ0FBQTtBQUVoQyxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sd0JBQXdCLENBQUE7QUFDdEQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sMEJBQTBCLENBQUE7QUFDM0QsT0FBTyxLQUFLLFVBQVUsTUFBTSxpQ0FBaUMsQ0FBQTtBQUM3RCxPQUFPLEtBQUssaUJBQWlCLE1BQU0sb0NBQW9DLENBQUE7QUFDdkUsT0FBTyxLQUFLLGNBQWMsTUFBTSxxQ0FBcUMsQ0FBQTtBQUNyRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sWUFBWSxDQUFBO0FBQ25DLE9BQU8sRUFBRSxRQUFRLEVBQUUsV0FBVyxFQUFFLGtCQUFrQixFQUFFLE1BQU0saUJBQWlCLENBQUE7QUFDM0UsT0FBTyxLQUFLLE1BQU0sTUFBTSxlQUFlLENBQUE7QUFDdkMsT0FBTyxLQUFLLFFBQVEsTUFBTSxpQkFBaUIsQ0FBQTtBQUMzQyxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0saUJBQWlCLENBQUE7QUFDdEMsT0FBTyxLQUFLLE9BQU8sTUFBTSxnQkFBZ0IsQ0FBQTtBQUN6QyxPQUFPLEtBQUssTUFBTSxNQUFNLGVBQWUsQ0FBQTtBQUd2QyxNQUFNLENBQUMsTUFBTSxTQUFTLEdBQUcsQ0FBQyxRQUFzQyxNQUFNLEVBQUUsRUFBRSxDQUN4RSxVQUFVLENBQUMsSUFBSSxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FDdEIsSUFBSSxDQUNGLGlCQUFpQixDQUFDLGlCQUFpQixFQUNuQyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQyxNQUFNLENBQUMsTUFBTSxLQUFLLEVBQUUsQ0FBQyxD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
@@ -8,4 +8,4 @@ export declare function generate<T>(arb: FastCheck.Arbitrary<T>): FastCheck.Valu
8
8
  * @tsplus getter effect/schema/Arbitrary generate
9
9
  */
10
10
  export declare function generateFromArbitrary<T>(arb: A.LazyArbitrary<T>): FastCheck.Value<T>;
11
- //# sourceMappingURL=test.arbs.d.ts.map
11
+ //# sourceMappingURL=arbs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arbs.d.ts","sourceRoot":"","sources":["../../src/test/arbs.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAG1C,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,mBAAmB,CAAA;AAQ1C;;GAEG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,sBAEtD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,sBAE/D"}
@@ -0,0 +1,21 @@
1
+ // Do not import to frontend
2
+ import { FastCheck } from "@effect/schema";
3
+ import { faker } from "@faker-js/faker";
4
+ import { setFaker } from "effect-app/faker";
5
+ import { Random } from "fast-check";
6
+ import * as rand from "pure-rand";
7
+ const rnd = new Random(rand.congruential32(5));
8
+ setFaker(faker);
9
+ /**
10
+ * @tsplus getter FastCheck generateRandom
11
+ */
12
+ export function generate(arb) {
13
+ return arb.generate(rnd, undefined);
14
+ }
15
+ /**
16
+ * @tsplus getter effect/schema/Arbitrary generate
17
+ */
18
+ export function generateFromArbitrary(arb) {
19
+ return generate(arb(FastCheck));
20
+ }
21
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXJicy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90ZXN0L2FyYnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsNEJBQTRCO0FBRTVCLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQTtBQUMxQyxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0saUJBQWlCLENBQUE7QUFDdkMsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLGtCQUFrQixDQUFBO0FBRTNDLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxZQUFZLENBQUE7QUFDbkMsT0FBTyxLQUFLLElBQUksTUFBTSxXQUFXLENBQUE7QUFFakMsTUFBTSxHQUFHLEdBQUcsSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFBO0FBRTlDLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQTtBQUVmOztHQUVHO0FBQ0gsTUFBTSxVQUFVLFFBQVEsQ0FBSSxHQUEyQjtJQUNyRCxPQUFPLEdBQUcsQ0FBQyxRQUFRLENBQUMsR0FBRyxFQUFFLFNBQVMsQ0FBQyxDQUFBO0FBQ3JDLENBQUM7QUFFRDs7R0FFRztBQUNILE1BQU0sVUFBVSxxQkFBcUIsQ0FBSSxHQUF1QjtJQUM5RCxPQUFPLFFBQVEsQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQTtBQUNqQyxDQUFDIn0=
package/dist/test.d.ts CHANGED
@@ -11,5 +11,5 @@ export declare const createRandomInstance: <A extends object, I, R>(s: S.Schema<
11
11
  export declare const createRandomInstanceI: <A extends object, I>(s: S.Schema<A, I, never> & {
12
12
  fields: S.Struct.Fields;
13
13
  }) => (overrides?: Partial<I>) => A;
14
- export * from "./test.arbs.js";
14
+ export * from "./test/arbs.js";
15
15
  //# sourceMappingURL=test.d.ts.map
package/dist/test.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Arbitrary } from "@effect/schema";
2
2
  import { Predicate, S } from "effect-app";
3
3
  import { copy } from "effect-app/utils";
4
- import { generate } from "./test.arbs.js";
4
+ import { generate } from "./test/arbs.js";
5
5
  const isPropertySignature = (u) => Predicate.hasProperty(u, S.PropertySignatureTypeId);
6
6
  const defaults = (fields) => {
7
7
  const keys = Object.keys(fields);
@@ -43,5 +43,5 @@ export const createRandomInstanceI = (s) => {
43
43
  return decode({ ...encode(v), ...overrides });
44
44
  };
45
45
  };
46
- export * from "./test.arbs.js";
46
+ export * from "./test/arbs.js";
47
47
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy90ZXN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQTtBQUUxQyxPQUFPLEVBQUUsU0FBUyxFQUFFLENBQUMsRUFBRSxNQUFNLFlBQVksQ0FBQTtBQUN6QyxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sa0JBQWtCLENBQUE7QUFDdkMsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLGdCQUFnQixDQUFBO0FBRXpDLE1BQU0sbUJBQW1CLEdBQUcsQ0FBQyxDQUFVLEVBQThCLEVBQUUsQ0FDckUsU0FBUyxDQUFDLFdBQVcsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLHVCQUF1QixDQUFDLENBQUE7QUFFckQsTUFBTSxRQUFRLEdBQUcsQ0FBQyxNQUF1QixFQUFFLEVBQUU7SUFDM0MsTUFBTSxJQUFJLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQTtJQUNoQyw4REFBOEQ7SUFDOUQsTUFBTSxHQUFHLEdBQXdCLEVBQUUsQ0FBQTtJQUNuQyxLQUFLLE1BQU0sR0FBRyxJQUFJLElBQUksRUFBRSxDQUFDO1FBQ3ZCLE1BQU0sS0FBSyxHQUFHLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQTtRQUN6QixJQUFJLG1CQUFtQixDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDL0IsTUFBTSxHQUFHLEdBQUcsS0FBSyxDQUFDLEdBQUcsQ0FBQTtZQUNyQixNQUFNLFlBQVksR0FBRyxHQUFHLENBQUMsSUFBSSxLQUFLLDhCQUE4QixDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLFlBQVksQ0FBQTtZQUN6RyxJQUFJLFlBQVksS0FBSyxTQUFTLEVBQUUsQ0FBQztnQkFDL0IsR0FBRyxDQUFDLEdBQUcsQ0FBQyxHQUFHLFlBQVksRUFBRSxDQUFBO1lBQzNCLENBQUM7UUFDSCxDQUFDO0lBQ0gsQ0FBQztJQUNELE9BQU8sR0FBRyxDQUFBO0FBQ1osQ0FBQyxDQUFBO0FBRUQ7O0dBRUc7QUFDSCxNQUFNLENBQUMsTUFBTSxvQkFBb0IsR0FBRyxDQUF5QixDQUFrRCxFQUFFLEVBQUU7SUFDakgsTUFBTSxHQUFHLEdBQUcsUUFBUSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQTtJQUN2QyxPQUFPLENBQUMsU0FBc0IsRUFBRSxFQUFFO1FBQ2hDLE1BQU0sQ0FBQyxHQUFHLEVBQUUsR0FBRyxHQUFHLENBQUMsS0FBSyxFQUFFLEdBQUcsUUFBUSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFBO1FBQ2pELE9BQU8sU0FBUyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxFQUFFLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUE7SUFDM0MsQ0FBQyxDQUFBO0FBQ0gsQ0FBQyxDQUFBO0FBRUQ7O0dBRUc7QUFDSCxNQUFNLENBQUMsTUFBTSxxQkFBcUIsR0FBRyxDQUFzQixDQUFzRCxFQUFFLEVBQUU7SUFDbkgsTUFBTSxHQUFHLEdBQUcsUUFBUSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQTtJQUN2QyxNQUFNLE1BQU0sR0FBRyxDQUFDLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxDQUFBO0lBQzlCLE1BQU0sTUFBTSxHQUFHLENBQUMsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUE7SUFDOUIsT0FBTyxDQUFDLFNBQXNCLEVBQUUsRUFBRTtRQUNoQyxNQUFNLENBQUMsR0FBRyxFQUFFLEdBQUcsR0FBRyxDQUFDLEtBQUssRUFBRSxHQUFHLFFBQVEsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQTtRQUNqRCxJQUFJLENBQUMsU0FBUztZQUFFLE9BQU8sQ0FBQyxDQUFBO1FBQ3hCLE9BQU8sTUFBTSxDQUFDLEVBQUUsR0FBRyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsR0FBRyxTQUFTLEVBQUUsQ0FBQyxDQUFBO0lBQy9DLENBQUMsQ0FBQTtBQUNILENBQUMsQ0FBQTtBQUVELGNBQWMsZ0JBQWdCLENBQUEifQ==
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-app/infra",
3
- "version": "1.18.2",
3
+ "version": "1.19.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -19,9 +19,9 @@
19
19
  "pure-rand": "6.1.0",
20
20
  "redlock": "^4.2.0",
21
21
  "@effect-app/core": "1.10.1",
22
- "@effect-app/infra-adapters": "1.11.2",
23
- "effect-app": "1.17.0",
24
- "@effect-app/schema": "1.12.1"
22
+ "@effect-app/infra-adapters": "1.11.3",
23
+ "@effect-app/schema": "1.12.1",
24
+ "effect-app": "1.17.1"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@babel/cli": "^7.25.6",
@@ -95,6 +95,16 @@
95
95
  "default": "./_cjs/api/codec.cjs"
96
96
  }
97
97
  },
98
+ "./api/middlewares": {
99
+ "import": {
100
+ "types": "./dist/api/middlewares.d.ts",
101
+ "default": "./dist/api/middlewares.js"
102
+ },
103
+ "require": {
104
+ "types": "./dist/api/middlewares.d.ts",
105
+ "default": "./_cjs/api/middlewares.cjs"
106
+ }
107
+ },
98
108
  "./api/reportError": {
99
109
  "import": {
100
110
  "types": "./dist/api/reportError.d.ts",
@@ -645,14 +655,34 @@
645
655
  "default": "./_cjs/services/query/new-kid-interpreter.cjs"
646
656
  }
647
657
  },
648
- "./test.arbs": {
658
+ "./test": {
659
+ "import": {
660
+ "types": "./dist/test.d.ts",
661
+ "default": "./dist/test.js"
662
+ },
663
+ "require": {
664
+ "types": "./dist/test.d.ts",
665
+ "default": "./_cjs/test.cjs"
666
+ }
667
+ },
668
+ "./test/arbs": {
669
+ "import": {
670
+ "types": "./dist/test/arbs.d.ts",
671
+ "default": "./dist/test/arbs.js"
672
+ },
673
+ "require": {
674
+ "types": "./dist/test/arbs.d.ts",
675
+ "default": "./_cjs/test/arbs.cjs"
676
+ }
677
+ },
678
+ "./vitest": {
649
679
  "import": {
650
- "types": "./dist/test.arbs.d.ts",
651
- "default": "./dist/test.arbs.js"
680
+ "types": "./dist/vitest.d.ts",
681
+ "default": "./dist/vitest.js"
652
682
  },
653
683
  "require": {
654
- "types": "./dist/test.arbs.d.ts",
655
- "default": "./_cjs/test.arbs.cjs"
684
+ "types": "./dist/vitest.d.ts",
685
+ "default": "./_cjs/vitest.cjs"
656
686
  }
657
687
  }
658
688
  },
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Mechanism for extendning behaviour of all handlers on the server.
3
+ *
4
+ * @since 1.0.0
5
+ */
6
+ import * as crypto from "crypto"
7
+
8
+ import { dropUndefined } from "@effect-app/core/utils"
9
+ import { NotLoggedInError } from "@effect-app/infra/errors"
10
+ import * as Middleware from "@effect/platform/HttpMiddleware"
11
+ import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
12
+ import * as ServerResponse from "@effect/platform/HttpServerResponse"
13
+ import { Effect } from "effect-app"
14
+ import { HttpBody, HttpHeaders, HttpServerResponse } from "effect-app/http"
15
+ import * as Either from "effect/Either"
16
+ import * as FiberRef from "effect/FiberRef"
17
+ import { pipe } from "effect/Function"
18
+ import * as HashMap from "effect/HashMap"
19
+ import * as Metric from "effect/Metric"
20
+ import type * as Middlewares from "../middlewares.js"
21
+
22
+ export const accessLog = (level: "Info" | "Warning" | "Debug" = "Info") =>
23
+ Middleware.make((app) =>
24
+ pipe(
25
+ HttpServerRequest.HttpServerRequest,
26
+ Effect.flatMap((request) => Effect[`log${level}`](`${request.method} ${request.url}`)),
27
+ Effect.flatMap(() => app)
28
+ )
29
+ )
30
+
31
+ export const uuidLogAnnotation = (logAnnotationKey = "requestId") =>
32
+ Middleware.make((app) =>
33
+ pipe(
34
+ Effect.sync(() => crypto.randomUUID()),
35
+ Effect.flatMap((uuid) =>
36
+ FiberRef.update(
37
+ FiberRef.currentLogAnnotations,
38
+ HashMap.set<string, unknown>(logAnnotationKey, uuid)
39
+ )
40
+ ),
41
+ Effect.flatMap(() => app)
42
+ )
43
+ )
44
+
45
+ export const endpointCallsMetric = () => {
46
+ const endpointCalledCounter = Metric.counter("server.endpoint_calls")
47
+
48
+ return Middleware.make((app) =>
49
+ Effect.gen(function*(_) {
50
+ const request = yield* _(HttpServerRequest.HttpServerRequest)
51
+
52
+ yield* _(
53
+ Metric.increment(endpointCalledCounter),
54
+ Effect.tagMetrics("path", request.url)
55
+ )
56
+
57
+ return yield* _(app)
58
+ })
59
+ )
60
+ }
61
+
62
+ export const errorLog = Middleware.make((app) =>
63
+ Effect.gen(function*(_) {
64
+ const request = yield* _(HttpServerRequest.HttpServerRequest)
65
+
66
+ const response = yield* _(app)
67
+
68
+ if (response.status >= 400 && response.status < 500) {
69
+ yield* _(
70
+ Effect.logWarning(
71
+ `${request.method.toUpperCase()} ${request.url} client error ${response.status}`
72
+ )
73
+ )
74
+ } else if (response.status >= 500) {
75
+ yield* _(
76
+ Effect.logError(
77
+ `${request.method.toUpperCase()} ${request.url} server error ${response.status}`
78
+ )
79
+ )
80
+ }
81
+
82
+ return response
83
+ })
84
+ )
85
+
86
+ export const toServerResponse = (err: NotLoggedInError) =>
87
+ HttpServerResponse.empty().pipe(
88
+ HttpServerResponse.setStatus(401),
89
+ HttpServerResponse.setBody(HttpBody.unsafeJson({ message: err.message }))
90
+ )
91
+
92
+ export const basicAuth = <_, R>(
93
+ checkCredentials: (
94
+ credentials: Middlewares.BasicAuthCredentials
95
+ ) => Effect<_, NotLoggedInError, R>,
96
+ options?: Partial<{
97
+ headerName: string
98
+ skipPaths: readonly string[]
99
+ }>
100
+ ) =>
101
+ Middleware.make((app) =>
102
+ Effect.gen(function*(_) {
103
+ const headerName = options?.headerName ?? "Authorization"
104
+ const skippedPaths = options?.skipPaths ?? []
105
+ const request = yield* _(HttpServerRequest.HttpServerRequest)
106
+
107
+ if (skippedPaths.includes(request.url)) {
108
+ return yield* _(app)
109
+ }
110
+
111
+ const authHeader = request.headers[headerName.toLowerCase()]
112
+
113
+ if (authHeader === undefined) {
114
+ return toServerResponse(
115
+ new NotLoggedInError(
116
+ `Expected header ${headerName}`
117
+ )
118
+ )
119
+ }
120
+
121
+ const authorizationParts = authHeader.split(" ")
122
+
123
+ if (authorizationParts.length !== 2) {
124
+ return toServerResponse(
125
+ new NotLoggedInError(
126
+ "Incorrect auhorization scheme. Expected \"Basic <credentials>\""
127
+ )
128
+ )
129
+ }
130
+
131
+ if (authorizationParts[0] !== "Basic") {
132
+ return toServerResponse(
133
+ new NotLoggedInError(
134
+ `Incorrect auhorization type. Expected "Basic", got "${authorizationParts[0]}"`
135
+ )
136
+ )
137
+ }
138
+
139
+ const credentialsBuffer = Buffer.from(authorizationParts[1]!, "base64")
140
+ const credentialsText = credentialsBuffer.toString("utf-8")
141
+ const credentialsParts = credentialsText.split(":")
142
+
143
+ if (credentialsParts.length !== 2) {
144
+ return toServerResponse(
145
+ new NotLoggedInError(
146
+ "Incorrect basic auth credentials format. Expected base64 encoded \"<user>:<pass>\"."
147
+ )
148
+ )
149
+ }
150
+
151
+ const check = yield* _(
152
+ checkCredentials({
153
+ user: credentialsParts[0],
154
+ password: credentialsParts[1]!
155
+ }),
156
+ Effect.either
157
+ )
158
+
159
+ if (Either.isLeft(check)) {
160
+ return toServerResponse(check.left)
161
+ }
162
+
163
+ return yield* _(app)
164
+ })
165
+ )
166
+
167
+ export const cors = (_options?: Partial<Middlewares.CorsOptions>) => {
168
+ const DEFAULTS = {
169
+ allowedOrigins: ["*"],
170
+ allowedMethods: ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"],
171
+ allowedHeaders: [],
172
+ exposedHeaders: [],
173
+ credentials: false
174
+ } as const
175
+
176
+ const options = { ...DEFAULTS, ..._options }
177
+
178
+ const isAllowedOrigin = (origin: string) => {
179
+ return options.allowedOrigins.includes(origin)
180
+ }
181
+
182
+ const allowOrigin = (originHeader: string) => {
183
+ if (options.allowedOrigins.length === 0) {
184
+ return { "Access-Control-Allow-Origin": "*" }
185
+ }
186
+
187
+ if (options.allowedOrigins.length === 1) {
188
+ return {
189
+ "Access-Control-Allow-Origin": options.allowedOrigins[0],
190
+ Vary: "Origin"
191
+ }
192
+ }
193
+
194
+ if (isAllowedOrigin(originHeader)) {
195
+ return {
196
+ "Access-Control-Allow-Origin": originHeader,
197
+ Vary: "Origin"
198
+ }
199
+ }
200
+
201
+ return undefined
202
+ }
203
+
204
+ const allowMethods = (() => {
205
+ if (options.allowedMethods.length > 0) {
206
+ return {
207
+ "Access-Control-Allow-Methods": options.allowedMethods.join(", ")
208
+ }
209
+ }
210
+
211
+ return undefined
212
+ })()
213
+
214
+ const allowCredentials = (() => {
215
+ if (options.credentials) {
216
+ return { "Access-Control-Allow-Credentials": "true" }
217
+ }
218
+
219
+ return undefined
220
+ })()
221
+
222
+ const allowHeaders = (accessControlRequestHeaders: string | undefined) => {
223
+ if (options.allowedHeaders.length === 0 && accessControlRequestHeaders) {
224
+ return {
225
+ Vary: "Access-Control-Request-Headers",
226
+ "Access-Control-Allow-Headers": accessControlRequestHeaders
227
+ }
228
+ }
229
+
230
+ if (options.allowedHeaders) {
231
+ return {
232
+ "Access-Control-Allow-Headers": options.allowedHeaders.join(",")
233
+ }
234
+ }
235
+
236
+ return undefined
237
+ }
238
+
239
+ const exposeHeaders = (() => {
240
+ if (options.exposedHeaders.length > 0) {
241
+ return {
242
+ "Access-Control-Expose-Headers": options.exposedHeaders.join(",")
243
+ }
244
+ }
245
+
246
+ return undefined
247
+ })()
248
+
249
+ const maxAge = (() => {
250
+ if (options.maxAge) {
251
+ return { "Access-Control-Max-Age": options.maxAge.toString() }
252
+ }
253
+
254
+ return undefined
255
+ })()
256
+
257
+ return Middleware.make((app) =>
258
+ Effect.gen(function*(_) {
259
+ const request = yield* _(HttpServerRequest.HttpServerRequest)
260
+
261
+ const origin = request.headers["origin"]
262
+ const accessControlRequestHeaders = request.headers["access-control-request-headers"]
263
+
264
+ let corsHeaders = {
265
+ ...allowOrigin(origin ?? ""),
266
+ ...allowCredentials,
267
+ ...exposeHeaders
268
+ }
269
+
270
+ if (request.method === "OPTIONS") {
271
+ corsHeaders = {
272
+ ...corsHeaders,
273
+ ...allowMethods,
274
+ ...allowHeaders(accessControlRequestHeaders),
275
+ ...maxAge
276
+ }
277
+
278
+ return ServerResponse.empty({ status: 204, headers: HttpHeaders.fromInput(dropUndefined(corsHeaders)) })
279
+ }
280
+
281
+ const response = yield* _(app)
282
+
283
+ return response.pipe(ServerResponse.setHeaders(dropUndefined(corsHeaders)))
284
+ })
285
+ )
286
+ }