@shivaedev/server 0.5.0 → 0.6.1
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/README.md +4 -2
- package/dist/AppHostFactory.d.ts +3 -3
- package/dist/AppHostFactory.d.ts.map +1 -1
- package/dist/AppHostInput.d.ts +2 -2
- package/dist/AppHostInput.d.ts.map +1 -1
- package/dist/MigrationOptions.d.ts +2 -2
- package/dist/MigrationOptions.d.ts.map +1 -1
- package/dist/appHost.d.ts +8 -8
- package/dist/appHost.d.ts.map +1 -1
- package/dist/appHost.js.map +1 -1
- package/dist/appHostHttp.d.ts +28 -28
- package/dist/appHostHttp.d.ts.map +1 -1
- package/dist/appHostHttp.js.map +1 -1
- package/dist/databaseLayer.d.ts +6 -6
- package/dist/databaseLayer.d.ts.map +1 -1
- package/dist/databaseLayer.js +15 -33
- package/dist/databaseLayer.js.map +1 -1
- package/dist/hostChanges.d.ts +1 -1
- package/dist/hostChanges.d.ts.map +1 -1
- package/dist/hostChanges.js +5 -5
- package/dist/hostChanges.js.map +1 -1
- package/dist/socketRpc.d.ts.map +1 -1
- package/dist/socketRpc.js +3 -0
- package/dist/socketRpc.js.map +1 -1
- package/package.json +11 -11
- package/src/AppHostFactory.ts +6 -3
- package/src/AppHostInput.ts +6 -3
- package/src/MigrationOptions.ts +2 -2
- package/src/appHost.ts +10 -6
- package/src/appHostHttp.ts +5 -3
- package/src/databaseLayer.ts +27 -43
- package/src/hostChanges.ts +6 -5
- package/src/socketRpc.ts +3 -0
- package/dist/databaseAddress.d.ts +0 -4
- package/dist/databaseAddress.d.ts.map +0 -1
- package/dist/databaseAddress.js +0 -25
- package/dist/databaseAddress.js.map +0 -1
- package/src/databaseAddress.ts +0 -25
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"databaseLayer.d.ts","sourceRoot":"","sources":["../src/databaseLayer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"databaseLayer.d.ts","sourceRoot":"","sources":["../src/databaseLayer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAoC,KAAK,EAAU,QAAQ,EAAY,MAAM,QAAQ,CAAC;AACrG,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAMtC,OAAO,EAAE,KAAK,sBAAsB,EAAmB,MAAM,mCAAmC,CAAC;AACjG,OAAO,EAAE,KAAK,eAAe,EAAiB,MAAM,iCAAiC,CAAC;AAEtF,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAElE,MAAM,WAAW,cAAe,SAAQ,eAAe;IACtD,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,sBAAsB,EAAE,QAAQ,GAAG,gBAAgB,GAAG,mBAAmB,GAAG,QAAQ,CAAC,CAAC,CAAC;IACvH,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACjD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;CACvD;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,EAAE,OAAO,UAAU,ggBAqCvG"}
|
package/dist/databaseLayer.js
CHANGED
|
@@ -1,44 +1,18 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { createConnection } from "node:net";
|
|
3
2
|
import * as PgClient from "@effect/sql-pg/PgClient";
|
|
4
|
-
import { Config, Duration, Effect, Fiber, Layer, Redacted, Schedule } from "effect";
|
|
3
|
+
import { Config, Context, Duration, Effect, Fiber, Layer, Option, Redacted, Schedule } from "effect";
|
|
5
4
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
5
|
+
import { Migrator } from "effect/sql";
|
|
6
6
|
import { makeLiveServer } from "@shivaedev/contract/makeLiveServer.ts";
|
|
7
7
|
import { assertPostgresMigrations, postgresMigrationError } from "@shivaedev/sql/assertPostgresMigrations.ts";
|
|
8
8
|
import { coordinateWorkerRelease } from "@shivaedev/sql/coordinateWorkerRelease.ts";
|
|
9
9
|
import { migratePostgres } from "@shivaedev/sql/migrations.ts";
|
|
10
10
|
import { pendingPostgresMigrations } from "@shivaedev/sql/pendingPostgresMigrations.ts";
|
|
11
11
|
import { postgresChanges } from "@shivaedev/sql/postgresChanges.ts";
|
|
12
|
+
import { postgresLayer } from "@shivaedev/sql/postgresLayer.ts";
|
|
12
13
|
import { WorkerRelease } from "@shivaedev/sql/WorkerRelease.ts";
|
|
13
|
-
import { databaseAddress } from "./databaseAddress.js";
|
|
14
14
|
export function databaseLayer(options, changesService) {
|
|
15
|
-
const pool = Layer.unwrap(Effect.
|
|
16
|
-
const url = options.config?.databaseUrl ?? (yield* Config.Redacted("DATABASE_URL"));
|
|
17
|
-
const address = databaseAddress(url);
|
|
18
|
-
const disconnected = new Set();
|
|
19
|
-
// Native pool shutdown sends Terminate without waiting for its sockets to close.
|
|
20
|
-
yield* Effect.addFinalizer(() => Effect.promise(() => Promise.all([...disconnected])));
|
|
21
|
-
return PgClient.layer({
|
|
22
|
-
stream: () => {
|
|
23
|
-
const target = new URL(Redacted.value(url));
|
|
24
|
-
const host = address.host ?? target.searchParams.getAll("host").at(-1) ?? (decodeURIComponent(target.hostname) || "localhost");
|
|
25
|
-
const port = address.port ?? 5432;
|
|
26
|
-
const socket = createConnection(host.startsWith("/") ? { path: `${host}/.s.PGSQL.${port}` } : { host, noDelay: true, port });
|
|
27
|
-
const closed = new Promise((resolve) => socket.once("close", () => {
|
|
28
|
-
disconnected.delete(closed);
|
|
29
|
-
resolve();
|
|
30
|
-
}));
|
|
31
|
-
disconnected.add(closed);
|
|
32
|
-
return socket;
|
|
33
|
-
},
|
|
34
|
-
transformQueryNames: options.config?.transformQueryNames,
|
|
35
|
-
transformResultNames: options.config?.transformResultNames,
|
|
36
|
-
url,
|
|
37
|
-
...address,
|
|
38
|
-
...(options.config?.maxConnections === undefined ? {} : { maxConnections: options.config.maxConnections }),
|
|
39
|
-
...(options.config?.applicationName === undefined ? {} : { applicationName: options.config.applicationName }),
|
|
40
|
-
});
|
|
41
|
-
}));
|
|
15
|
+
const pool = Layer.unwrap(Effect.map(options.config?.databaseUrl === undefined ? Config.Redacted("DATABASE_URL") : Effect.succeed(options.config.databaseUrl), (url) => postgresLayer(url, options.config)));
|
|
42
16
|
const prepared = Layer.effectDiscard(prepareMigrations(options.migrations)).pipe(Layer.provideMerge(pool));
|
|
43
17
|
const live = Layer.effect(changesService, makeLiveServer).pipe(Layer.provideMerge(Reactivity.layer));
|
|
44
18
|
const migrations = options.migrations;
|
|
@@ -53,7 +27,15 @@ export function databaseLayer(options, changesService) {
|
|
|
53
27
|
const url = options.config?.databaseUrl ?? (yield* Config.Redacted("DATABASE_URL"));
|
|
54
28
|
const name = yield* Config.String("APP_NAME").pipe(Config.withDefault(new URL(Redacted.value(url)).pathname));
|
|
55
29
|
const channel = changes?.channel ?? `changes_${createHash("sha256").update(name).digest("hex").slice(0, 32)}`;
|
|
56
|
-
|
|
30
|
+
const listenUrl = options.config?.listenDatabaseUrl ?? Option.getOrUndefined(yield* Config.Redacted("LISTEN_DATABASE_URL").pipe(Config.option));
|
|
31
|
+
const listenerClient = listenUrl === undefined
|
|
32
|
+
? client
|
|
33
|
+
: yield* Effect.map(Layer.build(postgresLayer(listenUrl, { applicationName: options.config?.applicationName, maxConnections: 1 })), (context) => Context.get(context, PgClient.PgClient));
|
|
34
|
+
let connectionSetting = options.config?.databaseUrl === undefined ? "DATABASE_URL" : "config.databaseUrl";
|
|
35
|
+
if (listenUrl !== undefined) {
|
|
36
|
+
connectionSetting = options.config?.listenDatabaseUrl === undefined ? "LISTEN_DATABASE_URL" : "config.listenDatabaseUrl";
|
|
37
|
+
}
|
|
38
|
+
return postgresChanges({ ...changes, channel, client, connectionSetting, listenerClient, resync: hints.resync });
|
|
57
39
|
})).pipe(Layer.provideMerge(base));
|
|
58
40
|
}
|
|
59
41
|
function prepareMigrations(migrations) {
|
|
@@ -89,8 +71,8 @@ function waitForMigrations(migrations) {
|
|
|
89
71
|
return yield* Effect.fail(postgresMigrationError(pending));
|
|
90
72
|
}).pipe(Effect.retry({
|
|
91
73
|
schedule: Schedule.max([Schedule.spaced(migrations.pollInterval ?? "1 second"), Schedule.during(migrations.waitTimeout ?? "5 minutes")]),
|
|
92
|
-
while: (error) => error
|
|
93
|
-
}), Effect.tapError((error) => error
|
|
74
|
+
while: (error) => error instanceof Migrator.MigrationError && error.kind === "BadState",
|
|
75
|
+
}), Effect.tapError((error) => error instanceof Migrator.MigrationError && error.kind === "BadState" ? log("Timed out waiting for database migrations") : Effect.void));
|
|
94
76
|
if (progress !== undefined) {
|
|
95
77
|
yield* Fiber.interrupt(progress);
|
|
96
78
|
yield* Effect.logInfo("Database migrations current");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"databaseLayer.js","sourceRoot":"","sources":["../src/databaseLayer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,
|
|
1
|
+
{"version":3,"file":"databaseLayer.js","sourceRoot":"","sources":["../src/databaseLayer.ts"],"names":[],"mappings":"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"}
|
package/dist/hostChanges.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Layer } from "effect";
|
|
2
2
|
import type { Rpc, RpcGroup } from "effect/rpc";
|
|
3
|
-
import {
|
|
3
|
+
import type { SessionPolicy, SessionShape } from "@shivaedev/identity/server/session.ts";
|
|
4
4
|
import type { AppChanges } from "./appHost.ts";
|
|
5
5
|
export declare function hostChanges<R extends Rpc.Any, TSession extends SessionShape>(group: RpcGroup.RpcGroup<R>, changesService: typeof AppChanges, sessions?: SessionPolicy<TSession>): Layer.Layer<Rpc.ToHandler<Extract<R, {
|
|
6
6
|
readonly _tag: `${string}.$changes`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hostChanges.d.ts","sourceRoot":"","sources":["../src/hostChanges.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,EAAkB,MAAM,QAAQ,CAAC;AAEhE,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"hostChanges.d.ts","sourceRoot":"","sources":["../src/hostChanges.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,EAAkB,MAAM,QAAQ,CAAC;AAEhE,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAKhD,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAGzF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,wBAAgB,WAAW,CAAC,CAAC,SAAS,GAAG,CAAC,GAAG,EAAE,QAAQ,SAAS,YAAY,EAC3E,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,EAC3B,cAAc,EAAE,OAAO,UAAU,EACjC,QAAQ,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC;mBAsCoC,GAAG,MAAM,WAAW;mBAG1F"}
|
package/dist/hostChanges.js
CHANGED
|
@@ -2,8 +2,9 @@ import { Context, Effect, Layer, Option, Stream } from "effect";
|
|
|
2
2
|
import { Headers } from "effect/http";
|
|
3
3
|
import { CurrentViewer } from "@shivaedev/contract/readPolicy.ts";
|
|
4
4
|
import { Unauthorized } from "@shivaedev/errors/taxonomy.ts";
|
|
5
|
-
import {
|
|
5
|
+
import { recheckSession } from "@shivaedev/identity/server/recheckSession.ts";
|
|
6
6
|
import { resolveChanges } from "@shivaedev/identity/server/sessionKeys.ts";
|
|
7
|
+
import { revalidateSession } from "@shivaedev/identity/server/socketSession.ts";
|
|
7
8
|
export function hostChanges(group, changesService, sessions) {
|
|
8
9
|
return Layer.effectContext(Effect.gen(function* () {
|
|
9
10
|
const changes = yield* Effect.serviceOption(changesService);
|
|
@@ -45,15 +46,14 @@ function authorizeSession(sessions, tag, viewer, policy) {
|
|
|
45
46
|
if (sessions === undefined) {
|
|
46
47
|
return policy;
|
|
47
48
|
}
|
|
48
|
-
return
|
|
49
|
+
return recheckSession(sessions, Headers.empty, tag).pipe(Effect.map((current) => Option.isNone(viewer) || (Option.isSome(current) && current.value.user.id === viewer.value.id)), Effect.catch(() => Effect.succeed(false)), Effect.flatMap((allowed) => (allowed ? policy : Effect.succeed(false))));
|
|
49
50
|
}
|
|
50
51
|
function sessionRevoked(sessions, tag, id) {
|
|
51
52
|
return Effect.gen(function* () {
|
|
52
|
-
yield*
|
|
53
|
-
const current = yield* resolveSession(sessions, Headers.empty, tag).pipe(Effect.catchTag("AuthUnavailable", () => Effect.succeed(undefined)));
|
|
53
|
+
const current = yield* recheckSession(sessions, Headers.empty, tag).pipe(Effect.catchTag("AuthUnavailable", () => Effect.succeed(undefined)));
|
|
54
54
|
if (current !== undefined && (Option.isNone(current) || current.value.user.id !== id)) {
|
|
55
55
|
return yield* new Unauthorized({ message: "Authentication required" });
|
|
56
56
|
}
|
|
57
|
-
}).pipe(
|
|
57
|
+
}).pipe((check) => revalidateSession(check, sessions.streamRevalidationInterval ?? "30 seconds"));
|
|
58
58
|
}
|
|
59
59
|
//# sourceMappingURL=hostChanges.js.map
|
package/dist/hostChanges.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hostChanges.js","sourceRoot":"","sources":["../src/hostChanges.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC,OAAO,EAAE,aAAa,EAAe,MAAM,mCAAmC,CAAC;AAE/E,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC7D,OAAO,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"hostChanges.js","sourceRoot":"","sources":["../src/hostChanges.ts"],"names":[],"mappings":"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"}
|
package/dist/socketRpc.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"socketRpc.d.ts","sourceRoot":"","sources":["../src/socketRpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAW,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAG7E,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,uCAAuC,CAAC;
|
|
1
|
+
{"version":3,"file":"socketRpc.d.ts","sourceRoot":"","sources":["../src/socketRpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAW,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAG7E,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAE1F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,wBAAgB,SAAS,CAAC,QAAQ,SAAS,YAAY,EACtD,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,kBAAkB,EAAE,KAAK,EAAE,iBAAiB,CAAC,iBAAiB,GAAG,KAAK,CAAC,KAAK,CAAC,EACvH,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,EAC5B,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,aAAa,EAAE,SAAS,MAAM,EAAE,kHAmBhC"}
|
package/dist/socketRpc.js
CHANGED
|
@@ -3,6 +3,7 @@ import { Headers, HttpServerRequest, HttpServerResponse } from "effect/http";
|
|
|
3
3
|
import * as HttpStatus from "effect/http/HttpStatus";
|
|
4
4
|
import { localTransport } from "@shivaedev/identity/server/local.ts";
|
|
5
5
|
import { resolveSession } from "@shivaedev/identity/server/session.ts";
|
|
6
|
+
import { makeSocketSession, SocketSession } from "@shivaedev/identity/server/socketSession.ts";
|
|
6
7
|
export function socketRpc(handler, policy, trustedProxy, nativeOrigins) {
|
|
7
8
|
return Effect.gen(function* () {
|
|
8
9
|
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
@@ -11,6 +12,8 @@ export function socketRpc(handler, policy, trustedProxy, nativeOrigins) {
|
|
|
11
12
|
}
|
|
12
13
|
if (policy.policy !== "loopback") {
|
|
13
14
|
yield* resolveSession(policy.sessions, Headers.empty, "$connect");
|
|
15
|
+
const session = yield* makeSocketSession(policy.sessions.streamRevalidationInterval ?? "30 seconds");
|
|
16
|
+
return yield* Effect.provideService(handler, SocketSession, session);
|
|
14
17
|
}
|
|
15
18
|
return yield* handler;
|
|
16
19
|
}).pipe(Effect.catchTags({
|
package/dist/socketRpc.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"socketRpc.js","sourceRoot":"","sources":["../src/socketRpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAc,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC7E,OAAO,KAAK,UAAU,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,cAAc,EAAqB,MAAM,uCAAuC,CAAC;
|
|
1
|
+
{"version":3,"file":"socketRpc.js","sourceRoot":"","sources":["../src/socketRpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAc,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC7E,OAAO,KAAK,UAAU,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,cAAc,EAAqB,MAAM,uCAAuC,CAAC;AAC1F,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,6CAA6C,CAAC;AAG/F,MAAM,UAAU,SAAS,CACxB,OAAuH,EACvH,MAA4B,EAC5B,YAAgC,EAChC,aAAgC;IAEhC,OAAO,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAC1B,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,iBAAiB,CAAC,iBAAiB,CAAC;QAC3D,IAAI,CAAC,cAAc,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3D,OAAO,kBAAkB,CAAC,IAAI,CAAC,iCAAiC,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QACpH,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YAClC,KAAK,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;YAClE,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,IAAI,YAAY,CAAC,CAAC;YACrG,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC;QACtE,CAAC;QACD,OAAO,KAAK,CAAC,CAAC,OAAO,CAAC;IACvB,CAAC,CAAC,CAAC,IAAI,CACN,MAAM,CAAC,SAAS,CAAC;QAChB,eAAe,EAAE,eAAe;QAChC,SAAS,EAAE,SAAS;KACpB,CAAC,CACF,CAAC;AACH,CAAC;AAED,SAAS,eAAe;IACvB,OAAO,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,oBAAoB,CAAC,EAAE,CAAC,CAAC,CAAC;AAC3G,CAAC;AACD,SAAS,SAAS;IACjB,OAAO,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;AAClG,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "The Effect application host: HTTP and RPC serving, health, OTLP export, database and migrations, and development mode",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"effect"
|
|
@@ -45,22 +45,22 @@
|
|
|
45
45
|
],
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@shivaedev/changes": "0.5.1",
|
|
48
|
-
"@shivaedev/contract": "0.
|
|
49
|
-
"@shivaedev/errors": "0.1.
|
|
50
|
-
"@shivaedev/http": "0.
|
|
51
|
-
"@shivaedev/identity": "0.
|
|
48
|
+
"@shivaedev/contract": "0.22.0",
|
|
49
|
+
"@shivaedev/errors": "0.1.2",
|
|
50
|
+
"@shivaedev/http": "0.3.0",
|
|
51
|
+
"@shivaedev/identity": "0.5.1",
|
|
52
52
|
"@shivaedev/local-postgres": "0.7.0",
|
|
53
|
-
"@shivaedev/sql": "0.
|
|
53
|
+
"@shivaedev/sql": "0.26.1"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@effect/platform-node": "4.0.6",
|
|
57
57
|
"@effect/sql-pg": "4.0.6",
|
|
58
58
|
"@effect/vitest": "4.0.6",
|
|
59
|
-
"@shivaedev/jobs": "0.
|
|
60
|
-
"@shivaedev/quality": "0.
|
|
61
|
-
"@shivaedev/test": "0.
|
|
62
|
-
"@shivaedev/test-sql": "0.
|
|
63
|
-
"@shivaedev/test-story": "0.
|
|
59
|
+
"@shivaedev/jobs": "0.23.1",
|
|
60
|
+
"@shivaedev/quality": "0.47.0",
|
|
61
|
+
"@shivaedev/test": "0.11.0",
|
|
62
|
+
"@shivaedev/test-sql": "0.13.0",
|
|
63
|
+
"@shivaedev/test-story": "0.25.1",
|
|
64
64
|
"@types/node": "24.19.2",
|
|
65
65
|
"@types/pg": "8.20.0",
|
|
66
66
|
"better-auth": "1.7.12",
|
package/src/AppHostFactory.ts
CHANGED
|
@@ -30,6 +30,7 @@ export interface AppHostFactory {
|
|
|
30
30
|
TPreflightRequirements = never,
|
|
31
31
|
TRouteError = never,
|
|
32
32
|
TRouteRequirements = HostRouteRequirements,
|
|
33
|
+
TMigrationError = never,
|
|
33
34
|
>(
|
|
34
35
|
options: AppHostInput<
|
|
35
36
|
TRpcs,
|
|
@@ -47,8 +48,9 @@ export interface AppHostFactory {
|
|
|
47
48
|
TPreflightError,
|
|
48
49
|
TPreflightRequirements,
|
|
49
50
|
TRouteError,
|
|
50
|
-
TRouteRequirements
|
|
51
|
-
|
|
51
|
+
TRouteRequirements,
|
|
52
|
+
TMigrationError
|
|
53
|
+
> & { readonly migrations: AppDatabaseOptions<TMigrations, TMigrationError>["migrations"] },
|
|
52
54
|
): PreflightHost<
|
|
53
55
|
ReturnType<
|
|
54
56
|
typeof deferredHost<
|
|
@@ -64,7 +66,8 @@ export interface AppHostFactory {
|
|
|
64
66
|
TApplicationError,
|
|
65
67
|
TApplicationRequirements,
|
|
66
68
|
TRouteError,
|
|
67
|
-
TRouteRequirements
|
|
69
|
+
TRouteRequirements,
|
|
70
|
+
TMigrationError
|
|
68
71
|
>
|
|
69
72
|
>,
|
|
70
73
|
NoInfer<TPreflight>,
|
package/src/AppHostInput.ts
CHANGED
|
@@ -25,9 +25,10 @@ export type AppHostInput<
|
|
|
25
25
|
TPreflightRequirements = never,
|
|
26
26
|
TRouteError = never,
|
|
27
27
|
TRouteRequirements = HostRouteRequirements,
|
|
28
|
+
TMigrationError = never,
|
|
28
29
|
> = HostPreflight<TPreflight, TPreflightError, TPreflightRequirements> &
|
|
29
30
|
(
|
|
30
|
-
| (AppDatabaseOptions<TMigrations> & {
|
|
31
|
+
| (AppDatabaseOptions<TMigrations, TMigrationError> & {
|
|
31
32
|
readonly application: Effect.Effect<
|
|
32
33
|
AppHttpHostOptions<
|
|
33
34
|
TRpcs,
|
|
@@ -62,7 +63,8 @@ export type AppHostInput<
|
|
|
62
63
|
TPreflightError,
|
|
63
64
|
TPreflightRequirements,
|
|
64
65
|
TRouteError,
|
|
65
|
-
TRouteRequirements
|
|
66
|
+
TRouteRequirements,
|
|
67
|
+
TMigrationError
|
|
66
68
|
>
|
|
67
69
|
| AppLocalHostOptions<
|
|
68
70
|
TRpcs,
|
|
@@ -77,7 +79,8 @@ export type AppHostInput<
|
|
|
77
79
|
TPreflightError,
|
|
78
80
|
TPreflightRequirements,
|
|
79
81
|
TRouteError,
|
|
80
|
-
TRouteRequirements
|
|
82
|
+
TRouteRequirements,
|
|
83
|
+
TMigrationError
|
|
81
84
|
>
|
|
82
85
|
| AppHttpHostOptions<
|
|
83
86
|
TRpcs,
|
package/src/MigrationOptions.ts
CHANGED
|
@@ -2,8 +2,8 @@ import type { Duration } from "effect";
|
|
|
2
2
|
import type { Migrator } from "effect/sql";
|
|
3
3
|
import type { SchemaComponent } from "@shivaedev/sql/SchemaComponent.ts";
|
|
4
4
|
|
|
5
|
-
export interface MigrationOptions<R = never> {
|
|
6
|
-
readonly components?: readonly SchemaComponent<R>[];
|
|
5
|
+
export interface MigrationOptions<R = never, E = never> {
|
|
6
|
+
readonly components?: readonly SchemaComponent<R, E>[];
|
|
7
7
|
readonly loader: Migrator.Loader<R>;
|
|
8
8
|
readonly lockTimeout?: Duration.Input;
|
|
9
9
|
readonly mode?: "migrate" | "check" | "wait";
|
package/src/appHost.ts
CHANGED
|
@@ -11,12 +11,12 @@ import type { HostCoreOptions, HostPolicy, HostRouteRequirements, HostRoutes } f
|
|
|
11
11
|
import type { MigrationOptions } from "#MigrationOptions.ts";
|
|
12
12
|
|
|
13
13
|
export class AppChanges extends Context.Service<AppChanges, Effect.Success<typeof makeLiveServer>>()("@shivaedev/server/AppChanges") {}
|
|
14
|
-
export interface AppDatabaseOptions<R = never> {
|
|
14
|
+
export interface AppDatabaseOptions<R = never, E = never> {
|
|
15
15
|
readonly config?: DatabaseConfig;
|
|
16
|
-
readonly migrations: MigrationOptions<R>;
|
|
16
|
+
readonly migrations: MigrationOptions<R, E>;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export function appDatabase<R>(options: AppDatabaseOptions<R>) {
|
|
19
|
+
export function appDatabase<R, E = never>(options: AppDatabaseOptions<R, E>) {
|
|
20
20
|
return databaseLayer(options, AppChanges);
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -35,10 +35,11 @@ export type AppHostOptions<
|
|
|
35
35
|
TPreflightRequirements = never,
|
|
36
36
|
TRouteError = never,
|
|
37
37
|
TRouteRequirements = HostRouteRequirements,
|
|
38
|
+
TMigrationError = never,
|
|
38
39
|
> = HostPreflight<TPreflight, TPreflightError, TPreflightRequirements> &
|
|
39
40
|
HostCoreOptions<TRpcs, THandlers, THandlerError, THandlerRequirements, TServices, TServiceError, TServiceRequirements> &
|
|
40
41
|
HostRoutes<TRouteError, TRouteRequirements> & {
|
|
41
|
-
readonly migrations: AppDatabaseOptions<TMigrations>["migrations"];
|
|
42
|
+
readonly migrations: AppDatabaseOptions<TMigrations, TMigrationError>["migrations"];
|
|
42
43
|
readonly policy?: undefined;
|
|
43
44
|
readonly sessions: SessionPolicy<TSession>;
|
|
44
45
|
};
|
|
@@ -57,10 +58,11 @@ export type AppLocalHostOptions<
|
|
|
57
58
|
TPreflightRequirements = never,
|
|
58
59
|
TRouteError = never,
|
|
59
60
|
TRouteRequirements = HostRouteRequirements,
|
|
61
|
+
TMigrationError = never,
|
|
60
62
|
> = HostPreflight<TPreflight, TPreflightError, TPreflightRequirements> &
|
|
61
63
|
HostCoreOptions<TRpcs, THandlers, THandlerError, THandlerRequirements, TServices, TServiceError, TServiceRequirements> &
|
|
62
64
|
HostRoutes<TRouteError, TRouteRequirements> & {
|
|
63
|
-
readonly migrations: AppDatabaseOptions<TMigrations>["migrations"];
|
|
65
|
+
readonly migrations: AppDatabaseOptions<TMigrations, TMigrationError>["migrations"];
|
|
64
66
|
readonly policy: "loopback";
|
|
65
67
|
readonly sessions?: never;
|
|
66
68
|
};
|
|
@@ -100,6 +102,7 @@ export const appHost: AppHostFactory = <
|
|
|
100
102
|
TPreflightRequirements = never,
|
|
101
103
|
TRouteError = never,
|
|
102
104
|
TRouteRequirements = HostRouteRequirements,
|
|
105
|
+
TMigrationError = never,
|
|
103
106
|
>(
|
|
104
107
|
options: AppHostInput<
|
|
105
108
|
TRpcs,
|
|
@@ -117,7 +120,8 @@ export const appHost: AppHostFactory = <
|
|
|
117
120
|
TPreflightError,
|
|
118
121
|
TPreflightRequirements,
|
|
119
122
|
TRouteError,
|
|
120
|
-
TRouteRequirements
|
|
123
|
+
TRouteRequirements,
|
|
124
|
+
TMigrationError
|
|
121
125
|
>,
|
|
122
126
|
) => {
|
|
123
127
|
if ("application" in options) {
|
package/src/appHostHttp.ts
CHANGED
|
@@ -82,6 +82,7 @@ export function migratedHost<
|
|
|
82
82
|
TSession extends SessionShape,
|
|
83
83
|
TRouteError = never,
|
|
84
84
|
TRouteRequirements = HostRouteRequirements,
|
|
85
|
+
TMigrationError = never,
|
|
85
86
|
>(
|
|
86
87
|
options: HostOptions<
|
|
87
88
|
TRpcs,
|
|
@@ -95,9 +96,9 @@ export function migratedHost<
|
|
|
95
96
|
TRouteRequirements
|
|
96
97
|
> &
|
|
97
98
|
HostPolicy<TSession> & {
|
|
98
|
-
readonly migrations: MigrationOptions<TMigrations>;
|
|
99
|
+
readonly migrations: MigrationOptions<TMigrations, TMigrationError>;
|
|
99
100
|
},
|
|
100
|
-
database: ReturnType<typeof databaseLayer<TMigrations>>,
|
|
101
|
+
database: ReturnType<typeof databaseLayer<TMigrations, TMigrationError>>,
|
|
101
102
|
changesService: typeof AppChanges,
|
|
102
103
|
) {
|
|
103
104
|
const services = Layer.mergeAll(options.services ?? Layer.empty);
|
|
@@ -118,6 +119,7 @@ export function deferredHost<
|
|
|
118
119
|
TApplicationRequirements,
|
|
119
120
|
TRouteError = never,
|
|
120
121
|
TRouteRequirements = HostRouteRequirements,
|
|
122
|
+
TMigrationError = never,
|
|
121
123
|
>(
|
|
122
124
|
application: Effect.Effect<
|
|
123
125
|
AppHttpHostOptions<
|
|
@@ -138,7 +140,7 @@ export function deferredHost<
|
|
|
138
140
|
TApplicationError,
|
|
139
141
|
TApplicationRequirements
|
|
140
142
|
>,
|
|
141
|
-
database: ReturnType<typeof databaseLayer<TMigrations>>,
|
|
143
|
+
database: ReturnType<typeof databaseLayer<TMigrations, TMigrationError>>,
|
|
142
144
|
changesService: typeof AppChanges,
|
|
143
145
|
) {
|
|
144
146
|
return Layer.unwrap(Effect.map(application, (options) => appHostHttp(options, changesService))).pipe(Layer.provideMerge(database));
|
package/src/databaseLayer.ts
CHANGED
|
@@ -1,57 +1,29 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { createConnection } from "node:net";
|
|
3
2
|
import * as PgClient from "@effect/sql-pg/PgClient";
|
|
4
|
-
import { Config, Duration, Effect, Fiber, Layer, Redacted, Schedule } from "effect";
|
|
3
|
+
import { Config, Context, Duration, Effect, Fiber, Layer, Option, Redacted, Schedule } from "effect";
|
|
5
4
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
5
|
+
import { Migrator } from "effect/sql";
|
|
6
6
|
import { makeLiveServer } from "@shivaedev/contract/makeLiveServer.ts";
|
|
7
7
|
import { assertPostgresMigrations, postgresMigrationError } from "@shivaedev/sql/assertPostgresMigrations.ts";
|
|
8
|
-
import type { SqlNameTransforms } from "@shivaedev/sql/clientOptions.ts";
|
|
9
8
|
import { coordinateWorkerRelease } from "@shivaedev/sql/coordinateWorkerRelease.ts";
|
|
10
9
|
import { migratePostgres } from "@shivaedev/sql/migrations.ts";
|
|
11
10
|
import { type PendingPostgresMigrations, pendingPostgresMigrations } from "@shivaedev/sql/pendingPostgresMigrations.ts";
|
|
12
11
|
import { type PostgresChangesOptions, postgresChanges } from "@shivaedev/sql/postgresChanges.ts";
|
|
12
|
+
import { type PostgresOptions, postgresLayer } from "@shivaedev/sql/postgresLayer.ts";
|
|
13
13
|
import { WorkerRelease } from "@shivaedev/sql/WorkerRelease.ts";
|
|
14
14
|
import type { AppChanges, AppDatabaseOptions } from "#appHost.ts";
|
|
15
|
-
import { databaseAddress } from "./databaseAddress.ts";
|
|
16
15
|
|
|
17
|
-
export interface DatabaseConfig extends
|
|
18
|
-
readonly
|
|
19
|
-
readonly changes?: Partial<Omit<PostgresChangesOptions, "client" | "resync">>;
|
|
16
|
+
export interface DatabaseConfig extends PostgresOptions {
|
|
17
|
+
readonly changes?: Partial<Omit<PostgresChangesOptions, "client" | "listenerClient" | "connectionSetting" | "resync">>;
|
|
20
18
|
readonly databaseUrl?: Redacted.Redacted<string>;
|
|
21
|
-
readonly
|
|
19
|
+
readonly listenDatabaseUrl?: Redacted.Redacted<string>;
|
|
22
20
|
}
|
|
23
21
|
|
|
24
|
-
export function databaseLayer<R>(options: AppDatabaseOptions<R>, changesService: typeof AppChanges) {
|
|
22
|
+
export function databaseLayer<R, E>(options: AppDatabaseOptions<R, E>, changesService: typeof AppChanges) {
|
|
25
23
|
const pool = Layer.unwrap(
|
|
26
|
-
Effect.
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
}),
|
|
24
|
+
Effect.map(options.config?.databaseUrl === undefined ? Config.Redacted("DATABASE_URL") : Effect.succeed(options.config.databaseUrl), (url) =>
|
|
25
|
+
postgresLayer(url, options.config),
|
|
26
|
+
),
|
|
55
27
|
);
|
|
56
28
|
const prepared = Layer.effectDiscard(prepareMigrations(options.migrations)).pipe(Layer.provideMerge(pool));
|
|
57
29
|
const live = Layer.effect(changesService, makeLiveServer).pipe(Layer.provideMerge(Reactivity.layer));
|
|
@@ -69,12 +41,24 @@ export function databaseLayer<R>(options: AppDatabaseOptions<R>, changesService:
|
|
|
69
41
|
const url = options.config?.databaseUrl ?? (yield* Config.Redacted("DATABASE_URL"));
|
|
70
42
|
const name = yield* Config.String("APP_NAME").pipe(Config.withDefault(new URL(Redacted.value(url)).pathname));
|
|
71
43
|
const channel = changes?.channel ?? `changes_${createHash("sha256").update(name).digest("hex").slice(0, 32)}`;
|
|
72
|
-
|
|
44
|
+
const listenUrl = options.config?.listenDatabaseUrl ?? Option.getOrUndefined(yield* Config.Redacted("LISTEN_DATABASE_URL").pipe(Config.option));
|
|
45
|
+
const listenerClient =
|
|
46
|
+
listenUrl === undefined
|
|
47
|
+
? client
|
|
48
|
+
: yield* Effect.map(
|
|
49
|
+
Layer.build(postgresLayer(listenUrl, { applicationName: options.config?.applicationName, maxConnections: 1 })),
|
|
50
|
+
(context) => Context.get(context, PgClient.PgClient),
|
|
51
|
+
);
|
|
52
|
+
let connectionSetting = options.config?.databaseUrl === undefined ? "DATABASE_URL" : "config.databaseUrl";
|
|
53
|
+
if (listenUrl !== undefined) {
|
|
54
|
+
connectionSetting = options.config?.listenDatabaseUrl === undefined ? "LISTEN_DATABASE_URL" : "config.listenDatabaseUrl";
|
|
55
|
+
}
|
|
56
|
+
return postgresChanges({ ...changes, channel, client, connectionSetting, listenerClient, resync: hints.resync });
|
|
73
57
|
}),
|
|
74
58
|
).pipe(Layer.provideMerge(base));
|
|
75
59
|
}
|
|
76
60
|
|
|
77
|
-
function prepareMigrations<R>(migrations: AppDatabaseOptions<R>["migrations"]) {
|
|
61
|
+
function prepareMigrations<R, E>(migrations: AppDatabaseOptions<R, E>["migrations"]) {
|
|
78
62
|
switch (migrations.mode) {
|
|
79
63
|
case "check": {
|
|
80
64
|
const schema = { ...migrations };
|
|
@@ -88,7 +72,7 @@ function prepareMigrations<R>(migrations: AppDatabaseOptions<R>["migrations"]) {
|
|
|
88
72
|
}
|
|
89
73
|
}
|
|
90
74
|
|
|
91
|
-
function waitForMigrations<R>(migrations: AppDatabaseOptions<R>["migrations"]) {
|
|
75
|
+
function waitForMigrations<R, E>(migrations: AppDatabaseOptions<R, E>["migrations"]) {
|
|
92
76
|
return Effect.gen(function* () {
|
|
93
77
|
const progressInterval = Duration.fromInputUnsafe(migrations.progressInterval ?? "30 seconds");
|
|
94
78
|
let pending: PendingPostgresMigrations = { components: [], migrations: [] };
|
|
@@ -113,10 +97,10 @@ function waitForMigrations<R>(migrations: AppDatabaseOptions<R>["migrations"]) {
|
|
|
113
97
|
}).pipe(
|
|
114
98
|
Effect.retry({
|
|
115
99
|
schedule: Schedule.max([Schedule.spaced(migrations.pollInterval ?? "1 second"), Schedule.during(migrations.waitTimeout ?? "5 minutes")]),
|
|
116
|
-
while: (error) => error
|
|
100
|
+
while: (error) => error instanceof Migrator.MigrationError && error.kind === "BadState",
|
|
117
101
|
}),
|
|
118
102
|
Effect.tapError((error) =>
|
|
119
|
-
error
|
|
103
|
+
error instanceof Migrator.MigrationError && error.kind === "BadState" ? log("Timed out waiting for database migrations") : Effect.void,
|
|
120
104
|
),
|
|
121
105
|
);
|
|
122
106
|
if (progress !== undefined) {
|
package/src/hostChanges.ts
CHANGED
|
@@ -4,8 +4,10 @@ import type { Rpc, RpcGroup } from "effect/rpc";
|
|
|
4
4
|
import { CurrentViewer, type Viewer } from "@shivaedev/contract/readPolicy.ts";
|
|
5
5
|
import type { QueryReference } from "@shivaedev/contract/serverMetadata.ts";
|
|
6
6
|
import { Unauthorized } from "@shivaedev/errors/taxonomy.ts";
|
|
7
|
-
import {
|
|
7
|
+
import { recheckSession } from "@shivaedev/identity/server/recheckSession.ts";
|
|
8
|
+
import type { SessionPolicy, SessionShape } from "@shivaedev/identity/server/session.ts";
|
|
8
9
|
import { resolveChanges } from "@shivaedev/identity/server/sessionKeys.ts";
|
|
10
|
+
import { revalidateSession } from "@shivaedev/identity/server/socketSession.ts";
|
|
9
11
|
import type { AppChanges } from "./appHost.ts";
|
|
10
12
|
|
|
11
13
|
export function hostChanges<R extends Rpc.Any, TSession extends SessionShape>(
|
|
@@ -63,7 +65,7 @@ function authorizeSession<TSession extends SessionShape>(
|
|
|
63
65
|
if (sessions === undefined) {
|
|
64
66
|
return policy;
|
|
65
67
|
}
|
|
66
|
-
return
|
|
68
|
+
return recheckSession(sessions, Headers.empty, tag).pipe(
|
|
67
69
|
Effect.map((current) => Option.isNone(viewer) || (Option.isSome(current) && current.value.user.id === viewer.value.id)),
|
|
68
70
|
Effect.catch(() => Effect.succeed(false)),
|
|
69
71
|
Effect.flatMap((allowed) => (allowed ? policy : Effect.succeed(false))),
|
|
@@ -72,10 +74,9 @@ function authorizeSession<TSession extends SessionShape>(
|
|
|
72
74
|
|
|
73
75
|
function sessionRevoked<TSession extends SessionShape>(sessions: SessionPolicy<TSession>, tag: string, id: string) {
|
|
74
76
|
return Effect.gen(function* () {
|
|
75
|
-
yield*
|
|
76
|
-
const current = yield* resolveSession(sessions, Headers.empty, tag).pipe(Effect.catchTag("AuthUnavailable", () => Effect.succeed(undefined)));
|
|
77
|
+
const current = yield* recheckSession(sessions, Headers.empty, tag).pipe(Effect.catchTag("AuthUnavailable", () => Effect.succeed(undefined)));
|
|
77
78
|
if (current !== undefined && (Option.isNone(current) || current.value.user.id !== id)) {
|
|
78
79
|
return yield* new Unauthorized({ message: "Authentication required" });
|
|
79
80
|
}
|
|
80
|
-
}).pipe(
|
|
81
|
+
}).pipe((check) => revalidateSession(check, sessions.streamRevalidationInterval ?? "30 seconds"));
|
|
81
82
|
}
|
package/src/socketRpc.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Headers, HttpServerRequest, HttpServerResponse } from "effect/http";
|
|
|
3
3
|
import * as HttpStatus from "effect/http/HttpStatus";
|
|
4
4
|
import { localTransport } from "@shivaedev/identity/server/local.ts";
|
|
5
5
|
import { resolveSession, type SessionShape } from "@shivaedev/identity/server/session.ts";
|
|
6
|
+
import { makeSocketSession, SocketSession } from "@shivaedev/identity/server/socketSession.ts";
|
|
6
7
|
import type { HostPolicy } from "#hostOptions.ts";
|
|
7
8
|
|
|
8
9
|
export function socketRpc<TSession extends SessionShape>(
|
|
@@ -18,6 +19,8 @@ export function socketRpc<TSession extends SessionShape>(
|
|
|
18
19
|
}
|
|
19
20
|
if (policy.policy !== "loopback") {
|
|
20
21
|
yield* resolveSession(policy.sessions, Headers.empty, "$connect");
|
|
22
|
+
const session = yield* makeSocketSession(policy.sessions.streamRevalidationInterval ?? "30 seconds");
|
|
23
|
+
return yield* Effect.provideService(handler, SocketSession, session);
|
|
21
24
|
}
|
|
22
25
|
return yield* handler;
|
|
23
26
|
}).pipe(
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"databaseAddress.d.ts","sourceRoot":"","sources":["../src/databaseAddress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAElC,wBAAgB,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC,CAqBrG"}
|