@shivaedev/server 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +323 -2
- package/dist/AppHostFactory.d.ts +15 -0
- package/dist/AppHostFactory.d.ts.map +1 -0
- package/dist/AppHostFactory.js +2 -0
- package/dist/AppHostFactory.js.map +1 -0
- package/dist/AppHostInput.d.ts +13 -0
- package/dist/AppHostInput.d.ts.map +1 -0
- package/dist/AppHostInput.js +2 -0
- package/dist/AppHostInput.js.map +1 -0
- package/dist/MigrationOptions.d.ts +15 -0
- package/dist/MigrationOptions.d.ts.map +1 -0
- package/dist/MigrationOptions.js +2 -0
- package/dist/MigrationOptions.js.map +1 -0
- package/dist/RedactionPolicy.d.ts +4 -0
- package/dist/RedactionPolicy.d.ts.map +1 -0
- package/dist/RedactionPolicy.js +4 -0
- package/dist/RedactionPolicy.js.map +1 -0
- package/dist/appHost.d.ts +47 -0
- package/dist/appHost.d.ts.map +1 -0
- package/dist/appHost.js +17 -0
- package/dist/appHost.js.map +1 -0
- package/dist/appHostHttp.d.ts +88 -0
- package/dist/appHostHttp.d.ts.map +1 -0
- package/dist/appHostHttp.js +67 -0
- package/dist/appHostHttp.js.map +1 -0
- package/dist/boundedRpcRequest.d.ts +3 -0
- package/dist/boundedRpcRequest.d.ts.map +1 -0
- package/dist/boundedRpcRequest.js +22 -0
- package/dist/boundedRpcRequest.js.map +1 -0
- package/dist/databaseAddress.d.ts +4 -0
- package/dist/databaseAddress.d.ts.map +1 -0
- package/dist/databaseAddress.js +25 -0
- package/dist/databaseAddress.js.map +1 -0
- package/dist/databaseLayer.d.ts +14 -0
- package/dist/databaseLayer.d.ts.map +1 -0
- package/dist/databaseLayer.js +100 -0
- package/dist/databaseLayer.js.map +1 -0
- package/dist/development.d.ts +10 -0
- package/dist/development.d.ts.map +1 -0
- package/dist/development.js +20 -0
- package/dist/development.js.map +1 -0
- package/dist/healthRoute.d.ts +3 -0
- package/dist/healthRoute.d.ts.map +1 -0
- package/dist/healthRoute.js +18 -0
- package/dist/healthRoute.js.map +1 -0
- package/dist/hostChanges.d.ts +7 -0
- package/dist/hostChanges.d.ts.map +1 -0
- package/dist/hostChanges.js +25 -0
- package/dist/hostChanges.js.map +1 -0
- package/dist/hostOptions.d.ts +34 -0
- package/dist/hostOptions.d.ts.map +1 -0
- package/dist/hostOptions.js +2 -0
- package/dist/hostOptions.js.map +1 -0
- package/dist/hostTransport.d.ts +16 -0
- package/dist/hostTransport.d.ts.map +1 -0
- package/dist/hostTransport.js +16 -0
- package/dist/hostTransport.js.map +1 -0
- package/dist/nativeClientOrigins.d.ts +4 -0
- package/dist/nativeClientOrigins.d.ts.map +1 -0
- package/dist/nativeClientOrigins.js +38 -0
- package/dist/nativeClientOrigins.js.map +1 -0
- package/dist/node-http/subscriptionSignal.d.ts +12 -0
- package/dist/node-http/subscriptionSignal.d.ts.map +1 -0
- package/dist/node-http/subscriptionSignal.js +55 -0
- package/dist/node-http/subscriptionSignal.js.map +1 -0
- package/dist/otlpHttpClient.d.ts +4 -0
- package/dist/otlpHttpClient.d.ts.map +1 -0
- package/dist/otlpHttpClient.js +36 -0
- package/dist/otlpHttpClient.js.map +1 -0
- package/dist/otlpTracing.d.ts +3 -0
- package/dist/otlpTracing.d.ts.map +1 -0
- package/dist/otlpTracing.js +35 -0
- package/dist/otlpTracing.js.map +1 -0
- package/dist/redactOtlpUrl.d.ts +2 -0
- package/dist/redactOtlpUrl.d.ts.map +1 -0
- package/dist/redactOtlpUrl.js +41 -0
- package/dist/redactOtlpUrl.js.map +1 -0
- package/dist/redactingDiagnostics.d.ts +5 -0
- package/dist/redactingDiagnostics.d.ts.map +1 -0
- package/dist/redactingDiagnostics.js +25 -0
- package/dist/redactingDiagnostics.js.map +1 -0
- package/dist/redactingRpcSerialization.d.ts +4 -0
- package/dist/redactingRpcSerialization.d.ts.map +1 -0
- package/dist/redactingRpcSerialization.js +74 -0
- package/dist/redactingRpcSerialization.js.map +1 -0
- package/dist/redactingTracer.d.ts +4 -0
- package/dist/redactingTracer.d.ts.map +1 -0
- package/dist/redactingTracer.js +32 -0
- package/dist/redactingTracer.js.map +1 -0
- package/dist/rpc-server/publicConfigLayer.d.ts +4 -0
- package/dist/rpc-server/publicConfigLayer.d.ts.map +1 -0
- package/dist/rpc-server/publicConfigLayer.js +9 -0
- package/dist/rpc-server/publicConfigLayer.js.map +1 -0
- package/dist/runtime/makePlatform.d.ts +4 -0
- package/dist/runtime/makePlatform.d.ts.map +1 -0
- package/dist/runtime/makePlatform.js +65 -0
- package/dist/runtime/makePlatform.js.map +1 -0
- package/dist/runtime/types.d.ts +19 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +2 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/safeOtlpSerialization.d.ts +6 -0
- package/dist/safeOtlpSerialization.d.ts.map +1 -0
- package/dist/safeOtlpSerialization.js +67 -0
- package/dist/safeOtlpSerialization.js.map +1 -0
- package/dist/schemaSensitiveKeys.d.ts +5 -0
- package/dist/schemaSensitiveKeys.d.ts.map +1 -0
- package/dist/schemaSensitiveKeys.js +143 -0
- package/dist/schemaSensitiveKeys.js.map +1 -0
- package/dist/setupDatabases.d.ts +3 -0
- package/dist/setupDatabases.d.ts.map +1 -0
- package/dist/setupDatabases.js +12 -0
- package/dist/setupDatabases.js.map +1 -0
- package/dist/tracedGroup.d.ts +4 -0
- package/dist/tracedGroup.d.ts.map +1 -0
- package/dist/tracedGroup.js +13 -0
- package/dist/tracedGroup.js.map +1 -0
- package/package.json +110 -3
- package/src/AppHostFactory.ts +121 -0
- package/src/AppHostInput.ts +74 -0
- package/src/MigrationOptions.ts +15 -0
- package/src/RedactionPolicy.ts +4 -0
- package/src/appHost.ts +117 -0
- package/src/appHostHttp.ts +147 -0
- package/src/boundedRpcRequest.ts +28 -0
- package/src/databaseAddress.ts +25 -0
- package/src/databaseLayer.ts +127 -0
- package/src/development.ts +34 -0
- package/src/healthRoute.ts +27 -0
- package/src/hostChanges.ts +34 -0
- package/src/hostOptions.ts +30 -0
- package/src/hostTransport.ts +26 -0
- package/src/nativeClientOrigins.ts +44 -0
- package/src/node-http/subscriptionSignal.ts +64 -0
- package/src/otlpHttpClient.ts +54 -0
- package/src/otlpTracing.ts +61 -0
- package/src/redactOtlpUrl.ts +48 -0
- package/src/redactingDiagnostics.ts +33 -0
- package/src/redactingRpcSerialization.ts +85 -0
- package/src/redactingTracer.ts +37 -0
- package/src/rpc-server/publicConfigLayer.ts +12 -0
- package/src/runtime/makePlatform.ts +75 -0
- package/src/runtime/types.ts +24 -0
- package/src/safeOtlpSerialization.ts +73 -0
- package/src/schemaSensitiveKeys.ts +148 -0
- package/src/setupDatabases.ts +12 -0
- package/src/tracedGroup.ts +14 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { Effect, Layer } from "effect";
|
|
2
|
+
import type { Rpc } from "effect/rpc";
|
|
3
|
+
import type { SessionShape } from "@shivaedev/identity/server/session.ts";
|
|
4
|
+
import type { HostPreflight } from "#AppHostInput.ts";
|
|
5
|
+
import type { AppDatabaseOptions, AppHostOptions, AppHttpHostOptions, AppLocalHostOptions } from "#appHost.ts";
|
|
6
|
+
import type { appHostHttp, deferredHost } from "#appHostHttp.ts";
|
|
7
|
+
|
|
8
|
+
type PreflightHost<THost extends Layer.Any, TServices, TError, TRequirements> = Layer.Layer<
|
|
9
|
+
Layer.Success<THost> | TServices,
|
|
10
|
+
Layer.Error<THost> | TError,
|
|
11
|
+
Exclude<Layer.Services<THost>, TServices> | TRequirements
|
|
12
|
+
>;
|
|
13
|
+
|
|
14
|
+
export interface AppHostFactory {
|
|
15
|
+
<
|
|
16
|
+
TRpcs extends Rpc.Any,
|
|
17
|
+
THandlers,
|
|
18
|
+
THandlerError,
|
|
19
|
+
THandlerRequirements,
|
|
20
|
+
TServices = never,
|
|
21
|
+
TServiceError = never,
|
|
22
|
+
TServiceRequirements = never,
|
|
23
|
+
TMigrations = never,
|
|
24
|
+
TSession extends SessionShape = SessionShape,
|
|
25
|
+
TApplicationError = never,
|
|
26
|
+
TApplicationRequirements = never,
|
|
27
|
+
TPreflight = never,
|
|
28
|
+
TPreflightError = never,
|
|
29
|
+
TPreflightRequirements = never,
|
|
30
|
+
>(
|
|
31
|
+
options: HostPreflight<TPreflight, TPreflightError, TPreflightRequirements> &
|
|
32
|
+
(
|
|
33
|
+
| (AppDatabaseOptions<TMigrations> & {
|
|
34
|
+
readonly application: Effect.Effect<
|
|
35
|
+
AppHttpHostOptions<TRpcs, THandlers, THandlerError, THandlerRequirements, TServices, TServiceError, TServiceRequirements, TSession>,
|
|
36
|
+
TApplicationError,
|
|
37
|
+
TApplicationRequirements
|
|
38
|
+
>;
|
|
39
|
+
})
|
|
40
|
+
| AppHostOptions<
|
|
41
|
+
TRpcs,
|
|
42
|
+
THandlers,
|
|
43
|
+
THandlerError,
|
|
44
|
+
THandlerRequirements,
|
|
45
|
+
TServices,
|
|
46
|
+
TServiceError,
|
|
47
|
+
TServiceRequirements,
|
|
48
|
+
TMigrations,
|
|
49
|
+
TSession,
|
|
50
|
+
TPreflight,
|
|
51
|
+
TPreflightError,
|
|
52
|
+
TPreflightRequirements
|
|
53
|
+
>
|
|
54
|
+
| AppLocalHostOptions<
|
|
55
|
+
TRpcs,
|
|
56
|
+
THandlers,
|
|
57
|
+
THandlerError,
|
|
58
|
+
THandlerRequirements,
|
|
59
|
+
TServices,
|
|
60
|
+
TServiceError,
|
|
61
|
+
TServiceRequirements,
|
|
62
|
+
TMigrations,
|
|
63
|
+
TPreflight,
|
|
64
|
+
TPreflightError,
|
|
65
|
+
TPreflightRequirements
|
|
66
|
+
>
|
|
67
|
+
),
|
|
68
|
+
): PreflightHost<
|
|
69
|
+
ReturnType<
|
|
70
|
+
typeof deferredHost<
|
|
71
|
+
TRpcs,
|
|
72
|
+
THandlers,
|
|
73
|
+
THandlerError,
|
|
74
|
+
THandlerRequirements,
|
|
75
|
+
TServices,
|
|
76
|
+
TServiceError,
|
|
77
|
+
TServiceRequirements,
|
|
78
|
+
TMigrations,
|
|
79
|
+
TSession,
|
|
80
|
+
TApplicationError,
|
|
81
|
+
TApplicationRequirements
|
|
82
|
+
>
|
|
83
|
+
>,
|
|
84
|
+
NoInfer<TPreflight>,
|
|
85
|
+
NoInfer<TPreflightError>,
|
|
86
|
+
NoInfer<TPreflightRequirements>
|
|
87
|
+
>;
|
|
88
|
+
|
|
89
|
+
<
|
|
90
|
+
TRpcs extends Rpc.Any,
|
|
91
|
+
THandlers,
|
|
92
|
+
THandlerError,
|
|
93
|
+
THandlerRequirements,
|
|
94
|
+
TServices = never,
|
|
95
|
+
TServiceError = never,
|
|
96
|
+
TServiceRequirements = never,
|
|
97
|
+
TSession extends SessionShape = SessionShape,
|
|
98
|
+
TPreflight = never,
|
|
99
|
+
TPreflightError = never,
|
|
100
|
+
TPreflightRequirements = never,
|
|
101
|
+
>(
|
|
102
|
+
options: AppHttpHostOptions<
|
|
103
|
+
TRpcs,
|
|
104
|
+
THandlers,
|
|
105
|
+
THandlerError,
|
|
106
|
+
THandlerRequirements,
|
|
107
|
+
TServices,
|
|
108
|
+
TServiceError,
|
|
109
|
+
TServiceRequirements,
|
|
110
|
+
TSession,
|
|
111
|
+
TPreflight,
|
|
112
|
+
TPreflightError,
|
|
113
|
+
TPreflightRequirements
|
|
114
|
+
>,
|
|
115
|
+
): PreflightHost<
|
|
116
|
+
ReturnType<typeof appHostHttp<TRpcs, THandlers, THandlerError, THandlerRequirements, TServices, TServiceError, TServiceRequirements, TSession>>,
|
|
117
|
+
NoInfer<TPreflight>,
|
|
118
|
+
NoInfer<TPreflightError>,
|
|
119
|
+
NoInfer<TPreflightRequirements>
|
|
120
|
+
>;
|
|
121
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Effect, Layer } from "effect";
|
|
2
|
+
import type { Rpc } from "effect/rpc";
|
|
3
|
+
import type { SessionShape } from "@shivaedev/identity/server/session.ts";
|
|
4
|
+
import type { AppDatabaseOptions, AppHostOptions, AppHttpHostOptions, AppLocalHostOptions } from "#appHost.ts";
|
|
5
|
+
|
|
6
|
+
export type HostPreflight<TServices = never, TError = never, TRequirements = never> =
|
|
7
|
+
| { readonly preflight: Layer.Layer<TServices, TError, TRequirements> }
|
|
8
|
+
| ([TServices | TError | TRequirements] extends [never] ? { readonly preflight?: never } : never);
|
|
9
|
+
|
|
10
|
+
export type AppHostInput<
|
|
11
|
+
TRpcs extends Rpc.Any,
|
|
12
|
+
THandlers,
|
|
13
|
+
THandlerError,
|
|
14
|
+
THandlerRequirements,
|
|
15
|
+
TServices = never,
|
|
16
|
+
TServiceError = never,
|
|
17
|
+
TServiceRequirements = never,
|
|
18
|
+
TMigrations = never,
|
|
19
|
+
TSession extends SessionShape = SessionShape,
|
|
20
|
+
TApplicationError = never,
|
|
21
|
+
TApplicationRequirements = never,
|
|
22
|
+
TPreflight = never,
|
|
23
|
+
TPreflightError = never,
|
|
24
|
+
TPreflightRequirements = never,
|
|
25
|
+
> = HostPreflight<TPreflight, TPreflightError, TPreflightRequirements> &
|
|
26
|
+
(
|
|
27
|
+
| (AppDatabaseOptions<TMigrations> & {
|
|
28
|
+
readonly application: Effect.Effect<
|
|
29
|
+
AppHttpHostOptions<TRpcs, THandlers, THandlerError, THandlerRequirements, TServices, TServiceError, TServiceRequirements, TSession>,
|
|
30
|
+
TApplicationError,
|
|
31
|
+
TApplicationRequirements
|
|
32
|
+
>;
|
|
33
|
+
})
|
|
34
|
+
| AppHostOptions<
|
|
35
|
+
TRpcs,
|
|
36
|
+
THandlers,
|
|
37
|
+
THandlerError,
|
|
38
|
+
THandlerRequirements,
|
|
39
|
+
TServices,
|
|
40
|
+
TServiceError,
|
|
41
|
+
TServiceRequirements,
|
|
42
|
+
TMigrations,
|
|
43
|
+
TSession,
|
|
44
|
+
TPreflight,
|
|
45
|
+
TPreflightError,
|
|
46
|
+
TPreflightRequirements
|
|
47
|
+
>
|
|
48
|
+
| AppLocalHostOptions<
|
|
49
|
+
TRpcs,
|
|
50
|
+
THandlers,
|
|
51
|
+
THandlerError,
|
|
52
|
+
THandlerRequirements,
|
|
53
|
+
TServices,
|
|
54
|
+
TServiceError,
|
|
55
|
+
TServiceRequirements,
|
|
56
|
+
TMigrations,
|
|
57
|
+
TPreflight,
|
|
58
|
+
TPreflightError,
|
|
59
|
+
TPreflightRequirements
|
|
60
|
+
>
|
|
61
|
+
| AppHttpHostOptions<
|
|
62
|
+
TRpcs,
|
|
63
|
+
THandlers,
|
|
64
|
+
THandlerError,
|
|
65
|
+
THandlerRequirements,
|
|
66
|
+
TServices,
|
|
67
|
+
TServiceError,
|
|
68
|
+
TServiceRequirements,
|
|
69
|
+
TSession,
|
|
70
|
+
TPreflight,
|
|
71
|
+
TPreflightError,
|
|
72
|
+
TPreflightRequirements
|
|
73
|
+
>
|
|
74
|
+
);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Duration } from "effect";
|
|
2
|
+
import type { Migrator } from "effect/sql";
|
|
3
|
+
import type { SchemaComponent } from "@shivaedev/sql/SchemaComponent.ts";
|
|
4
|
+
|
|
5
|
+
export interface MigrationOptions<R = never> {
|
|
6
|
+
readonly components?: readonly SchemaComponent<R>[];
|
|
7
|
+
readonly loader: Migrator.Loader<R>;
|
|
8
|
+
readonly lockTimeout?: Duration.Input;
|
|
9
|
+
readonly mode?: "migrate" | "check" | "wait";
|
|
10
|
+
readonly pollInterval?: Duration.Input;
|
|
11
|
+
readonly progressInterval?: Duration.Input;
|
|
12
|
+
readonly release?: string;
|
|
13
|
+
readonly table?: string;
|
|
14
|
+
readonly waitTimeout?: Duration.Input;
|
|
15
|
+
}
|
package/src/appHost.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Context, type Effect, Layer } from "effect";
|
|
2
|
+
import type { Rpc } from "effect/rpc";
|
|
3
|
+
import type { makeLiveServer } from "@shivaedev/contract/makeLiveServer.ts";
|
|
4
|
+
import type { SessionPolicy, SessionShape } from "@shivaedev/identity/server/session.ts";
|
|
5
|
+
import type { AppHostFactory } from "#AppHostFactory.ts";
|
|
6
|
+
import type { AppHostInput, HostPreflight } from "#AppHostInput.ts";
|
|
7
|
+
import { appHostHttp, deferredHost, migratedHost } from "#appHostHttp.ts";
|
|
8
|
+
import { type DatabaseConfig, databaseLayer } from "#databaseLayer.ts";
|
|
9
|
+
import type { HostOptions, HostPolicy } from "#hostOptions.ts";
|
|
10
|
+
import type { MigrationOptions } from "#MigrationOptions.ts";
|
|
11
|
+
|
|
12
|
+
export class AppChanges extends Context.Service<AppChanges, Effect.Success<typeof makeLiveServer>>()("@shivaedev/server/AppChanges") {}
|
|
13
|
+
export interface AppDatabaseOptions<R = never> {
|
|
14
|
+
readonly config?: DatabaseConfig;
|
|
15
|
+
readonly migrations: MigrationOptions<R>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function appDatabase<R>(options: AppDatabaseOptions<R>) {
|
|
19
|
+
return databaseLayer(options, AppChanges);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type AppHostOptions<
|
|
23
|
+
TRpcs extends Rpc.Any,
|
|
24
|
+
THandlers,
|
|
25
|
+
THandlerError,
|
|
26
|
+
THandlerRequirements,
|
|
27
|
+
TServices = never,
|
|
28
|
+
TServiceError = never,
|
|
29
|
+
TServiceRequirements = never,
|
|
30
|
+
TMigrations = never,
|
|
31
|
+
TSession extends SessionShape = SessionShape,
|
|
32
|
+
TPreflight = never,
|
|
33
|
+
TPreflightError = never,
|
|
34
|
+
TPreflightRequirements = never,
|
|
35
|
+
> = HostPreflight<TPreflight, TPreflightError, TPreflightRequirements> &
|
|
36
|
+
HostOptions<TRpcs, THandlers, THandlerError, THandlerRequirements, TServices, TServiceError, TServiceRequirements> & {
|
|
37
|
+
readonly migrations: AppDatabaseOptions<TMigrations>["migrations"];
|
|
38
|
+
readonly policy?: undefined;
|
|
39
|
+
readonly sessions: SessionPolicy<TSession>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type AppLocalHostOptions<
|
|
43
|
+
TRpcs extends Rpc.Any,
|
|
44
|
+
THandlers,
|
|
45
|
+
THandlerError,
|
|
46
|
+
THandlerRequirements,
|
|
47
|
+
TServices = never,
|
|
48
|
+
TServiceError = never,
|
|
49
|
+
TServiceRequirements = never,
|
|
50
|
+
TMigrations = never,
|
|
51
|
+
TPreflight = never,
|
|
52
|
+
TPreflightError = never,
|
|
53
|
+
TPreflightRequirements = never,
|
|
54
|
+
> = HostPreflight<TPreflight, TPreflightError, TPreflightRequirements> &
|
|
55
|
+
HostOptions<TRpcs, THandlers, THandlerError, THandlerRequirements, TServices, TServiceError, TServiceRequirements> & {
|
|
56
|
+
readonly migrations: AppDatabaseOptions<TMigrations>["migrations"];
|
|
57
|
+
readonly policy: "loopback";
|
|
58
|
+
readonly sessions?: never;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type AppHttpHostOptions<
|
|
62
|
+
TRpcs extends Rpc.Any,
|
|
63
|
+
THandlers,
|
|
64
|
+
THandlerError,
|
|
65
|
+
THandlerRequirements,
|
|
66
|
+
TServices = never,
|
|
67
|
+
TServiceError = never,
|
|
68
|
+
TServiceRequirements = never,
|
|
69
|
+
TSession extends SessionShape = SessionShape,
|
|
70
|
+
TPreflight = never,
|
|
71
|
+
TPreflightError = never,
|
|
72
|
+
TPreflightRequirements = never,
|
|
73
|
+
> = HostPreflight<TPreflight, TPreflightError, TPreflightRequirements> &
|
|
74
|
+
HostOptions<TRpcs, THandlers, THandlerError, THandlerRequirements, TServices, TServiceError, TServiceRequirements> &
|
|
75
|
+
HostPolicy<TSession> & { readonly migrations?: undefined };
|
|
76
|
+
export const appHost: AppHostFactory = <
|
|
77
|
+
TRpcs extends Rpc.Any,
|
|
78
|
+
THandlers,
|
|
79
|
+
THandlerError,
|
|
80
|
+
THandlerRequirements,
|
|
81
|
+
TServices = never,
|
|
82
|
+
TServiceError = never,
|
|
83
|
+
TServiceRequirements = never,
|
|
84
|
+
TMigrations = never,
|
|
85
|
+
TSession extends SessionShape = SessionShape,
|
|
86
|
+
TApplicationError = never,
|
|
87
|
+
TApplicationRequirements = never,
|
|
88
|
+
TPreflight = never,
|
|
89
|
+
TPreflightError = never,
|
|
90
|
+
TPreflightRequirements = never,
|
|
91
|
+
>(
|
|
92
|
+
options: AppHostInput<
|
|
93
|
+
TRpcs,
|
|
94
|
+
THandlers,
|
|
95
|
+
THandlerError,
|
|
96
|
+
THandlerRequirements,
|
|
97
|
+
TServices,
|
|
98
|
+
TServiceError,
|
|
99
|
+
TServiceRequirements,
|
|
100
|
+
TMigrations,
|
|
101
|
+
TSession,
|
|
102
|
+
TApplicationError,
|
|
103
|
+
TApplicationRequirements,
|
|
104
|
+
TPreflight,
|
|
105
|
+
TPreflightError,
|
|
106
|
+
TPreflightRequirements
|
|
107
|
+
>,
|
|
108
|
+
) => {
|
|
109
|
+
if ("application" in options) {
|
|
110
|
+
return deferredHost(options.application, appDatabase(options), AppChanges).pipe(Layer.provideMerge(options.preflight ?? Layer.empty));
|
|
111
|
+
}
|
|
112
|
+
return options.migrations === undefined
|
|
113
|
+
? appHostHttp(options, AppChanges).pipe(Layer.provideMerge(options.preflight ?? Layer.empty))
|
|
114
|
+
: migratedHost({ ...options, migrations: options.migrations }, appDatabase({ ...options, migrations: options.migrations }), AppChanges).pipe(
|
|
115
|
+
Layer.provideMerge(options.preflight ?? Layer.empty),
|
|
116
|
+
);
|
|
117
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { NodeHttpServer, NodeServices } from "@effect/platform-node";
|
|
3
|
+
import { ByteSize, Config, Effect, Layer } from "effect";
|
|
4
|
+
import { HttpEffect, HttpIncomingMessage, HttpRouter, HttpServerRequest, HttpServerResponse, HttpStaticServer } from "effect/http";
|
|
5
|
+
import { type Rpc, RpcSerialization, RpcServer } from "effect/rpc";
|
|
6
|
+
import type { Authenticated, MaybeAuthenticated, RequestTracing } from "@shivaedev/identity/middleware.ts";
|
|
7
|
+
import { localRpcGuard } from "@shivaedev/identity/server/local.ts";
|
|
8
|
+
import { authenticatedLayer, maybeAuthenticatedLayer, type SessionShape } from "@shivaedev/identity/server/session.ts";
|
|
9
|
+
import { requestTracingLayer } from "@shivaedev/identity/server/tracing.ts";
|
|
10
|
+
import type { AppChanges, AppHttpHostOptions } from "#appHost.ts";
|
|
11
|
+
import { boundedRpcRequest } from "#boundedRpcRequest.ts";
|
|
12
|
+
import type { databaseLayer } from "#databaseLayer.ts";
|
|
13
|
+
import { healthRoute } from "#healthRoute.ts";
|
|
14
|
+
import { hostChanges } from "#hostChanges.ts";
|
|
15
|
+
import type { HostOptions, HostPolicy } from "#hostOptions.ts";
|
|
16
|
+
import { hostTransport } from "#hostTransport.ts";
|
|
17
|
+
import type { MigrationOptions } from "#MigrationOptions.ts";
|
|
18
|
+
import { nativeClientOrigins } from "#nativeClientOrigins.ts";
|
|
19
|
+
import { otlpTracing } from "#otlpTracing.ts";
|
|
20
|
+
import { RedactionPolicy } from "#RedactionPolicy.ts";
|
|
21
|
+
import { redactingDiagnostics } from "#redactingDiagnostics.ts";
|
|
22
|
+
import { redactingRpcSerialization } from "#redactingRpcSerialization.ts";
|
|
23
|
+
import { schemaSensitiveKeys } from "#schemaSensitiveKeys.ts";
|
|
24
|
+
import { tracedGroup } from "#tracedGroup.ts";
|
|
25
|
+
|
|
26
|
+
const rpcCacheControl: HttpEffect.PreResponseHandler = (_, response) =>
|
|
27
|
+
Effect.succeed(HttpServerResponse.setHeader(response, "cache-control", "private, no-store"));
|
|
28
|
+
|
|
29
|
+
export function appHostHttp<
|
|
30
|
+
TRpcs extends Rpc.Any,
|
|
31
|
+
THandlers,
|
|
32
|
+
THandlerError,
|
|
33
|
+
THandlerRequirements,
|
|
34
|
+
TServices,
|
|
35
|
+
TServiceError,
|
|
36
|
+
TServiceRequirements,
|
|
37
|
+
TSession extends SessionShape,
|
|
38
|
+
>(
|
|
39
|
+
options: HostOptions<TRpcs, THandlers, THandlerError, THandlerRequirements, TServices, TServiceError, TServiceRequirements> & HostPolicy<TSession>,
|
|
40
|
+
changesService: typeof AppChanges,
|
|
41
|
+
) {
|
|
42
|
+
const sensitive = schemaSensitiveKeys(options.group, options.config?.sensitiveKeys);
|
|
43
|
+
return Layer.unwrap(
|
|
44
|
+
Effect.gen(function* () {
|
|
45
|
+
const { bodySize, trustedProxy, nativeOrigins } = yield* hostTransport(options);
|
|
46
|
+
const middleware: Layer.Layer<RequestTracing> | Layer.Layer<Authenticated | MaybeAuthenticated | RequestTracing> =
|
|
47
|
+
options.policy === "loopback"
|
|
48
|
+
? requestTracingLayer({ sensitive })
|
|
49
|
+
: Layer.mergeAll(authenticatedLayer(options.sessions), maybeAuthenticatedLayer(options.sessions), requestTracingLayer({ sensitive }));
|
|
50
|
+
const routes = HttpRouter.use((router) =>
|
|
51
|
+
Effect.gen(function* () {
|
|
52
|
+
const wire = redactingRpcSerialization(yield* RpcSerialization.RpcSerialization, sensitive, sensitive.maskFailure);
|
|
53
|
+
const handler = yield* RpcServer.toHttpEffect(tracedGroup(options.group)).pipe(
|
|
54
|
+
Effect.provideService(RpcSerialization.RpcSerialization, wire),
|
|
55
|
+
);
|
|
56
|
+
const guarded = localRpcGuard(handler, trustedProxy, nativeOrigins);
|
|
57
|
+
if (nativeOrigins.length > 0) {
|
|
58
|
+
yield* router.add("OPTIONS", options.rpcPath ?? "/rpc", HttpServerResponse.text("Local same-origin requests only", { status: 403 }));
|
|
59
|
+
}
|
|
60
|
+
yield* router.add("POST", options.rpcPath ?? "/rpc", (request) => {
|
|
61
|
+
HttpEffect.appendPreResponseHandlerUnsafe(request, rpcCacheControl);
|
|
62
|
+
return guarded.pipe(
|
|
63
|
+
Effect.provideService(HttpIncomingMessage.MaxBodySize, ByteSize.bytes(bodySize)),
|
|
64
|
+
Effect.provideService(HttpServerRequest.HttpServerRequest, boundedRpcRequest(request)),
|
|
65
|
+
Effect.provideService(RpcSerialization.RpcSerialization, wire),
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
}),
|
|
69
|
+
).pipe(
|
|
70
|
+
Layer.provide(Layer.merge(middleware, hostChanges(options.group, changesService).pipe(Layer.provideMerge(options.handlers)))),
|
|
71
|
+
Layer.provide(RpcSerialization.layerNdjson),
|
|
72
|
+
Layer.provide(nativeClientOrigins(nativeOrigins, trustedProxy)),
|
|
73
|
+
);
|
|
74
|
+
const assets =
|
|
75
|
+
options.assets === undefined
|
|
76
|
+
? Layer.empty
|
|
77
|
+
: HttpStaticServer.layer(typeof options.assets === "string" ? { root: options.assets } : options.assets);
|
|
78
|
+
const application = Layer.mergeAll(options.services ?? Layer.empty);
|
|
79
|
+
const server = Layer.unwrap(
|
|
80
|
+
Effect.gen(function* () {
|
|
81
|
+
const port = options.config?.port ?? (yield* Config.Int("PORT").pipe(Config.withDefault(3000)));
|
|
82
|
+
const host = options.config?.host ?? (yield* Config.String("HOST").pipe(Config.withDefault("127.0.0.1")));
|
|
83
|
+
return NodeHttpServer.layer(createServer, {
|
|
84
|
+
gracefulShutdownTimeout: options.config?.gracefulShutdownTimeout,
|
|
85
|
+
host,
|
|
86
|
+
port,
|
|
87
|
+
...(options.config?.disablePreemptiveShutdown === undefined
|
|
88
|
+
? {}
|
|
89
|
+
: { disablePreemptiveShutdown: options.config.disablePreemptiveShutdown }),
|
|
90
|
+
});
|
|
91
|
+
}),
|
|
92
|
+
);
|
|
93
|
+
const applicationRoutes = (options.routes ?? Layer.empty).pipe(
|
|
94
|
+
Layer.provide(options.nativeOrigins === undefined ? Layer.empty : nativeClientOrigins(nativeOrigins, trustedProxy, true)),
|
|
95
|
+
);
|
|
96
|
+
return HttpRouter.serve(Layer.mergeAll(routes, healthRoute, assets, applicationRoutes), { disableLogger: true }).pipe(
|
|
97
|
+
Layer.provideMerge(application),
|
|
98
|
+
Layer.provideMerge(server),
|
|
99
|
+
Layer.provide(NodeServices.layer),
|
|
100
|
+
);
|
|
101
|
+
}),
|
|
102
|
+
).pipe(Layer.provide(redactingDiagnostics(sensitive)), Layer.provide(otlpTracing), Layer.provide(Layer.succeed(RedactionPolicy, sensitive)));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function migratedHost<
|
|
106
|
+
TRpcs extends Rpc.Any,
|
|
107
|
+
THandlers,
|
|
108
|
+
THandlerError,
|
|
109
|
+
THandlerRequirements,
|
|
110
|
+
TServices,
|
|
111
|
+
TServiceError,
|
|
112
|
+
TServiceRequirements,
|
|
113
|
+
TMigrations,
|
|
114
|
+
TSession extends SessionShape,
|
|
115
|
+
>(
|
|
116
|
+
options: HostOptions<TRpcs, THandlers, THandlerError, THandlerRequirements, TServices, TServiceError, TServiceRequirements> &
|
|
117
|
+
HostPolicy<TSession> & { readonly migrations: MigrationOptions<TMigrations> },
|
|
118
|
+
database: ReturnType<typeof databaseLayer<TMigrations>>,
|
|
119
|
+
changesService: typeof AppChanges,
|
|
120
|
+
) {
|
|
121
|
+
const services = Layer.mergeAll(options.services ?? Layer.empty);
|
|
122
|
+
return appHostHttp({ ...options, services: services.pipe(Layer.provideMerge(database)) }, changesService);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function deferredHost<
|
|
126
|
+
TRpcs extends Rpc.Any,
|
|
127
|
+
THandlers,
|
|
128
|
+
THandlerError,
|
|
129
|
+
THandlerRequirements,
|
|
130
|
+
TServices,
|
|
131
|
+
TServiceError,
|
|
132
|
+
TServiceRequirements,
|
|
133
|
+
TMigrations,
|
|
134
|
+
TSession extends SessionShape,
|
|
135
|
+
TApplicationError,
|
|
136
|
+
TApplicationRequirements,
|
|
137
|
+
>(
|
|
138
|
+
application: Effect.Effect<
|
|
139
|
+
AppHttpHostOptions<TRpcs, THandlers, THandlerError, THandlerRequirements, TServices, TServiceError, TServiceRequirements, TSession>,
|
|
140
|
+
TApplicationError,
|
|
141
|
+
TApplicationRequirements
|
|
142
|
+
>,
|
|
143
|
+
database: ReturnType<typeof databaseLayer<TMigrations>>,
|
|
144
|
+
changesService: typeof AppChanges,
|
|
145
|
+
) {
|
|
146
|
+
return Layer.unwrap(Effect.map(application, (options) => appHostHttp(options, changesService))).pipe(Layer.provideMerge(database));
|
|
147
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { HttpServerError, type HttpServerRequest } from "effect/http";
|
|
3
|
+
import { readBoundedBody } from "@shivaedev/http/server/boundedBody.ts";
|
|
4
|
+
|
|
5
|
+
export function boundedRpcRequest(request: HttpServerRequest.HttpServerRequest): HttpServerRequest.HttpServerRequest {
|
|
6
|
+
const arrayBuffer = readBoundedBody(request).pipe(
|
|
7
|
+
Effect.mapError((cause) =>
|
|
8
|
+
cause._tag === "RequestBodyTooLarge"
|
|
9
|
+
? new HttpServerError.HttpServerError({ reason: new HttpServerError.RequestParseError({ cause, request }) })
|
|
10
|
+
: cause,
|
|
11
|
+
),
|
|
12
|
+
Effect.cached,
|
|
13
|
+
Effect.runSync,
|
|
14
|
+
);
|
|
15
|
+
const text = Effect.map(arrayBuffer, (body) => new TextDecoder().decode(body));
|
|
16
|
+
// The native text reader destroys the HTTP socket on overflow, preventing a 4xx response.
|
|
17
|
+
return new Proxy(request, {
|
|
18
|
+
get: (target, key, receiver) => {
|
|
19
|
+
if (key === "text") {
|
|
20
|
+
return text;
|
|
21
|
+
}
|
|
22
|
+
if (key === "arrayBuffer") {
|
|
23
|
+
return arrayBuffer;
|
|
24
|
+
}
|
|
25
|
+
return Reflect.get(target, key, receiver);
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { PgPoolConfig } from "@effect/sql-pg/PgClient";
|
|
2
|
+
import { Redacted } from "effect";
|
|
3
|
+
|
|
4
|
+
export function databaseAddress(value: Redacted.Redacted<string>): Pick<PgPoolConfig, "host" | "port"> {
|
|
5
|
+
try {
|
|
6
|
+
const url = new URL(Redacted.value(value));
|
|
7
|
+
if (url.protocol !== "postgres:" && url.protocol !== "postgresql:") {
|
|
8
|
+
return {};
|
|
9
|
+
}
|
|
10
|
+
const hostname = url.hostname.startsWith("[") ? url.hostname.slice(1, -1) : decodeURIComponent(url.hostname);
|
|
11
|
+
// PgConnection applies query parameters in order, after the authority.
|
|
12
|
+
const host = url.searchParams.getAll("host").at(-1) ?? hostname;
|
|
13
|
+
const rawPort = url.searchParams.getAll("port").at(-1) ?? url.port;
|
|
14
|
+
const port = rawPort === "" ? undefined : Number(rawPort);
|
|
15
|
+
if (host.includes(",") || (port !== undefined && (!Number.isInteger(port) || port < 1 || port > 65_535))) {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
...(host === "" && !url.searchParams.has("host") ? {} : { host }),
|
|
20
|
+
...(port === undefined ? {} : { port }),
|
|
21
|
+
};
|
|
22
|
+
} catch {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { createConnection } from "node:net";
|
|
3
|
+
import * as PgClient from "@effect/sql-pg/PgClient";
|
|
4
|
+
import { Config, Duration, Effect, Fiber, Layer, Redacted, Schedule } from "effect";
|
|
5
|
+
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
6
|
+
import { makeLiveServer } from "@shivaedev/contract/makeLiveServer.ts";
|
|
7
|
+
import { assertPostgresMigrations, postgresMigrationError } from "@shivaedev/sql/assertPostgresMigrations.ts";
|
|
8
|
+
import type { SqlNameTransforms } from "@shivaedev/sql/clientOptions.ts";
|
|
9
|
+
import { coordinateWorkerRelease } from "@shivaedev/sql/coordinateWorkerRelease.ts";
|
|
10
|
+
import { migratePostgres } from "@shivaedev/sql/migrations.ts";
|
|
11
|
+
import { type PendingPostgresMigrations, pendingPostgresMigrations } from "@shivaedev/sql/pendingPostgresMigrations.ts";
|
|
12
|
+
import { type PostgresChangesOptions, postgresChanges } from "@shivaedev/sql/postgresChanges.ts";
|
|
13
|
+
import { WorkerRelease } from "@shivaedev/sql/WorkerRelease.ts";
|
|
14
|
+
import type { AppChanges, AppDatabaseOptions } from "#appHost.ts";
|
|
15
|
+
import { databaseAddress } from "./databaseAddress.ts";
|
|
16
|
+
|
|
17
|
+
export interface DatabaseConfig extends SqlNameTransforms {
|
|
18
|
+
readonly applicationName?: string;
|
|
19
|
+
readonly changes?: Partial<Omit<PostgresChangesOptions, "client" | "resync">>;
|
|
20
|
+
readonly databaseUrl?: Redacted.Redacted<string>;
|
|
21
|
+
readonly maxConnections?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function databaseLayer<R>(options: AppDatabaseOptions<R>, changesService: typeof AppChanges) {
|
|
25
|
+
const pool = Layer.unwrap(
|
|
26
|
+
Effect.gen(function* () {
|
|
27
|
+
const url = options.config?.databaseUrl ?? (yield* Config.Redacted("DATABASE_URL"));
|
|
28
|
+
const address = databaseAddress(url);
|
|
29
|
+
const disconnected = new Set<Promise<void>>();
|
|
30
|
+
// Native pool shutdown sends Terminate without waiting for its sockets to close.
|
|
31
|
+
yield* Effect.addFinalizer(() => Effect.promise(() => Promise.all([...disconnected])));
|
|
32
|
+
return PgClient.layer({
|
|
33
|
+
stream: () => {
|
|
34
|
+
const target = new URL(Redacted.value(url));
|
|
35
|
+
const host = address.host ?? target.searchParams.getAll("host").at(-1) ?? (decodeURIComponent(target.hostname) || "localhost");
|
|
36
|
+
const port = address.port ?? 5432;
|
|
37
|
+
const socket = createConnection(host.startsWith("/") ? { path: `${host}/.s.PGSQL.${port}` } : { host, noDelay: true, port });
|
|
38
|
+
const closed = new Promise<void>((resolve) =>
|
|
39
|
+
socket.once("close", () => {
|
|
40
|
+
disconnected.delete(closed);
|
|
41
|
+
resolve();
|
|
42
|
+
}),
|
|
43
|
+
);
|
|
44
|
+
disconnected.add(closed);
|
|
45
|
+
return socket;
|
|
46
|
+
},
|
|
47
|
+
transformQueryNames: options.config?.transformQueryNames,
|
|
48
|
+
transformResultNames: options.config?.transformResultNames,
|
|
49
|
+
url,
|
|
50
|
+
...address,
|
|
51
|
+
...(options.config?.maxConnections === undefined ? {} : { maxConnections: options.config.maxConnections }),
|
|
52
|
+
...(options.config?.applicationName === undefined ? {} : { applicationName: options.config.applicationName }),
|
|
53
|
+
});
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
const prepared = Layer.effectDiscard(prepareMigrations(options.migrations)).pipe(Layer.provideMerge(pool));
|
|
57
|
+
const live = Layer.effect(changesService, makeLiveServer).pipe(Layer.provideMerge(Reactivity.layer));
|
|
58
|
+
const migrations = options.migrations;
|
|
59
|
+
const worker =
|
|
60
|
+
migrations.mode === "wait" && migrations.release !== undefined
|
|
61
|
+
? Layer.effect(WorkerRelease, coordinateWorkerRelease(migrations.release, migrations.pollInterval, migrations.table))
|
|
62
|
+
: Layer.empty;
|
|
63
|
+
const base = Layer.merge(worker.pipe(Layer.provideMerge(prepared)), live);
|
|
64
|
+
const changes = options.config?.changes;
|
|
65
|
+
return Layer.unwrap(
|
|
66
|
+
Effect.gen(function* () {
|
|
67
|
+
const client = yield* PgClient.PgClient;
|
|
68
|
+
const hints = yield* changesService;
|
|
69
|
+
const url = options.config?.databaseUrl ?? (yield* Config.Redacted("DATABASE_URL"));
|
|
70
|
+
const name = yield* Config.String("APP_NAME").pipe(Config.withDefault(new URL(Redacted.value(url)).pathname));
|
|
71
|
+
const channel = changes?.channel ?? `changes_${createHash("sha256").update(name).digest("hex").slice(0, 32)}`;
|
|
72
|
+
return postgresChanges({ ...changes, channel, client, resync: hints.resync });
|
|
73
|
+
}),
|
|
74
|
+
).pipe(Layer.provideMerge(base));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function prepareMigrations<R>(migrations: AppDatabaseOptions<R>["migrations"]) {
|
|
78
|
+
switch (migrations.mode) {
|
|
79
|
+
case "check": {
|
|
80
|
+
const schema = { ...migrations };
|
|
81
|
+
delete schema.release;
|
|
82
|
+
return assertPostgresMigrations(schema);
|
|
83
|
+
}
|
|
84
|
+
case "wait":
|
|
85
|
+
return waitForMigrations(migrations);
|
|
86
|
+
default:
|
|
87
|
+
return migratePostgres({ ...migrations, lockTimeout: migrations.lockTimeout ?? "5 seconds" }).pipe(Effect.asVoid);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function waitForMigrations<R>(migrations: AppDatabaseOptions<R>["migrations"]) {
|
|
92
|
+
return Effect.gen(function* () {
|
|
93
|
+
const progressInterval = Duration.fromInputUnsafe(migrations.progressInterval ?? "30 seconds");
|
|
94
|
+
let pending: PendingPostgresMigrations = { components: [], migrations: [] };
|
|
95
|
+
let progress: Fiber.Fiber<never> | undefined;
|
|
96
|
+
function log(message: string) {
|
|
97
|
+
return Effect.suspend(() =>
|
|
98
|
+
Effect.logInfo(message).pipe(Effect.annotateLogs({ pendingComponents: pending.components, pendingMigrations: pending.migrations })),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
yield* Effect.gen(function* () {
|
|
102
|
+
pending = yield* pendingPostgresMigrations(migrations);
|
|
103
|
+
if (pending.migrations.length === 0 && pending.components.length === 0) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (progress === undefined) {
|
|
107
|
+
yield* log("Waiting for database migrations");
|
|
108
|
+
progress = yield* Effect.forever(Effect.sleep(progressInterval).pipe(Effect.andThen(log("Still waiting for database migrations")))).pipe(
|
|
109
|
+
Effect.forkScoped,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
return yield* Effect.fail(postgresMigrationError(pending));
|
|
113
|
+
}).pipe(
|
|
114
|
+
Effect.retry({
|
|
115
|
+
schedule: Schedule.max([Schedule.spaced(migrations.pollInterval ?? "1 second"), Schedule.during(migrations.waitTimeout ?? "5 minutes")]),
|
|
116
|
+
while: (error) => error._tag === "MigrationError" && error.kind === "BadState",
|
|
117
|
+
}),
|
|
118
|
+
Effect.tapError((error) =>
|
|
119
|
+
error._tag === "MigrationError" && error.kind === "BadState" ? log("Timed out waiting for database migrations") : Effect.void,
|
|
120
|
+
),
|
|
121
|
+
);
|
|
122
|
+
if (progress !== undefined) {
|
|
123
|
+
yield* Fiber.interrupt(progress);
|
|
124
|
+
yield* Effect.logInfo("Database migrations current");
|
|
125
|
+
}
|
|
126
|
+
}).pipe(Effect.scoped);
|
|
127
|
+
}
|