@shivaedev/server 0.3.0 → 0.4.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.
@@ -1,10 +1,28 @@
1
1
  import { Context, Effect, Layer, Option, Stream } from "effect";
2
+ import { Headers } from "effect/http";
3
+ import { CurrentViewer } from "@shivaedev/contract/readPolicy.ts";
4
+ import { Unauthorized } from "@shivaedev/errors/taxonomy.ts";
5
+ import { resolveSession } from "@shivaedev/identity/server/session.ts";
2
6
  import { resolveChanges } from "@shivaedev/identity/server/sessionKeys.ts";
3
- export function hostChanges(group, changesService) {
7
+ export function hostChanges(group, changesService, sessions) {
4
8
  return Layer.effectContext(Effect.gen(function* () {
5
9
  const changes = yield* Effect.serviceOption(changesService);
6
10
  const services = yield* Effect.context();
7
11
  const handlers = new Map();
12
+ function subscribe(rpc, queries) {
13
+ return Stream.unwrap(Effect.gen(function* () {
14
+ const { subscription, context } = yield* resolveChanges(rpc, queries);
15
+ const viewer = yield* CurrentViewer;
16
+ const authorized = authorizeSession(sessions, rpc._tag, viewer, subscription.authorized);
17
+ const stream = Option.isSome(changes)
18
+ ? changes.value.subscribe({ ...subscription, authorized }, context)
19
+ : Stream.concat(Stream.succeed({ _tag: "Resync" }), Stream.never);
20
+ if (sessions === undefined || Option.isNone(viewer)) {
21
+ return stream;
22
+ }
23
+ return Stream.interruptWhen(stream, sessionRevoked(sessions, rpc._tag, viewer.value.id));
24
+ }));
25
+ }
8
26
  for (const rpc of group.requests.values()) {
9
27
  if (!rpc._tag.endsWith(".$changes")) {
10
28
  continue;
@@ -14,12 +32,28 @@ export function hostChanges(group, changesService) {
14
32
  handlers.set(rpc.key, provided);
15
33
  continue;
16
34
  }
17
- function handler({ queries }) {
18
- return Stream.unwrap(Effect.map(resolveChanges(rpc, queries), ({ subscription, context }) => Option.isSome(changes) ? changes.value.subscribe(subscription, context) : Stream.never));
19
- }
20
- handlers.set(rpc.key, { context: services, handler, tag: rpc._tag });
35
+ handlers.set(rpc.key, {
36
+ context: services,
37
+ handler: ({ queries }) => subscribe(rpc, queries),
38
+ tag: rpc._tag,
39
+ });
21
40
  }
22
41
  return Context.makeUnsafe(handlers);
23
42
  }));
24
43
  }
44
+ function authorizeSession(sessions, tag, viewer, policy) {
45
+ if (sessions === undefined) {
46
+ return policy;
47
+ }
48
+ return resolveSession(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
+ function sessionRevoked(sessions, tag, id) {
51
+ return Effect.gen(function* () {
52
+ yield* Effect.sleep(sessions.streamRevalidationInterval ?? "30 seconds");
53
+ const current = yield* resolveSession(sessions, Headers.empty, tag).pipe(Effect.catchTag("AuthUnavailable", () => Effect.succeed(undefined)));
54
+ if (current !== undefined && (Option.isNone(current) || current.value.user.id !== id)) {
55
+ return yield* new Unauthorized({ message: "Authentication required" });
56
+ }
57
+ }).pipe(Effect.forever);
58
+ }
25
59
  //# sourceMappingURL=hostChanges.js.map
@@ -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;AAGhE,OAAO,EAAE,cAAc,EAAE,MAAM,2CAA2C,CAAC;AAG3E,MAAM,UAAU,WAAW,CAAoB,KAA2B,EAAE,cAAiC;IAC5G,OAAO,KAAK,CAAC,aAAa,CACzB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC;QAC5D,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,EAAS,CAAC;QAChD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAmB,CAAC;QAC5C,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YAC3C,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;gBACrC,SAAS;YACV,CAAC;YACD,MAAM,QAAQ,GAAG,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACjD,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBACtD,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;gBAChC,SAAS;YACV,CAAC;YACD,SAAS,OAAO,CAAC,EAAE,OAAO,EAAmD;gBAC5E,OAAO,MAAM,CAAC,MAAM,CACnB,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,YAAY,EAAE,OAAO,EAAE,EAAE,EAAE,CACtE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACtF,CACD,CAAC;YACH,CAAC;YACD,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QACtE,CAAC;QACD,OAAO,OAAO,CAAC,UAAU,CAAqE,QAAQ,CAAC,CAAC;IACzG,CAAC,CAAC,CACF,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"hostChanges.js","sourceRoot":"","sources":["../src/hostChanges.ts"],"names":[],"mappings":"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"}
package/dist/hostRpc.d.ts CHANGED
@@ -7,5 +7,5 @@ declare class HostedReadPolicy extends HostedReadPolicy_base {
7
7
  }
8
8
  export type { HostedReadPolicy };
9
9
  export declare const rpcCacheControl: HttpEffect.PreResponseHandler;
10
- export declare const hostRpc: <R extends Rpc.Any>(group: RpcGroup.RpcGroup<R>) => Effect.Effect<Effect.Effect<HttpServerResponse.HttpServerResponse, never, import("effect/http/HttpServerRequest").HttpServerRequest | import("effect/Scope").Scope>, never, import("effect/rpc/RpcSerialization").RpcSerialization | import("effect/Scope").Scope | Exclude<Rpc.Middleware<Rpc.AddMiddleware<Rpc.AddMiddleware<R, typeof HostedReadPolicy>, typeof import("@shivaedev/identity/middleware.ts").RequestTracing>>, HostedReadPolicy> | Exclude<Rpc.ServicesServer<Rpc.AddMiddleware<Rpc.AddMiddleware<R, typeof HostedReadPolicy>, typeof import("@shivaedev/identity/middleware.ts").RequestTracing>>, HostedReadPolicy> | Exclude<Rpc.ToHandler<Rpc.AddMiddleware<Rpc.AddMiddleware<R, typeof HostedReadPolicy>, typeof import("@shivaedev/identity/middleware.ts").RequestTracing>>, HostedReadPolicy>>;
10
+ export declare const hostRpc: <R extends Rpc.Any>(group: RpcGroup.RpcGroup<R>, websocket?: boolean | undefined) => Effect.Effect<Effect.Effect<HttpServerResponse.HttpServerResponse, never, import("effect/http/HttpServerRequest").HttpServerRequest | import("effect/Scope").Scope>, never, import("effect/rpc/RpcSerialization").RpcSerialization | import("effect/Scope").Scope | Exclude<Rpc.Middleware<Rpc.AddMiddleware<Rpc.AddMiddleware<R, typeof HostedReadPolicy>, typeof import("@shivaedev/identity/middleware.ts").RequestTracing>>, HostedReadPolicy> | Exclude<Rpc.ServicesServer<Rpc.AddMiddleware<Rpc.AddMiddleware<R, typeof HostedReadPolicy>, typeof import("@shivaedev/identity/middleware.ts").RequestTracing>>, HostedReadPolicy> | Exclude<Rpc.ToHandler<Rpc.AddMiddleware<Rpc.AddMiddleware<R, typeof HostedReadPolicy>, typeof import("@shivaedev/identity/middleware.ts").RequestTracing>>, HostedReadPolicy>>;
11
11
  //# sourceMappingURL=hostRpc.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hostRpc.d.ts","sourceRoot":"","sources":["../src/hostRpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,KAAK,UAAU,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,QAAQ,EAAE,aAAa,EAAa,MAAM,YAAY,CAAC;AAE/E,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;;AAG7F,cAAM,gBAAiB,SAAQ,qBAE7B;CAAG;AAEL,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAEjC,eAAO,MAAM,eAAe,EAAE,UAAU,CAAC,kBACoD,CAAC;AAE9F,eAAO,MAAM,OAAO,GAAgC,CAAC,SAAS,GAAG,CAAC,GAAG,0zBASnE,CAAC"}
1
+ {"version":3,"file":"hostRpc.d.ts","sourceRoot":"","sources":["../src/hostRpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,KAAK,UAAU,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,QAAQ,EAAE,aAAa,EAAa,MAAM,YAAY,CAAC;AAE/E,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;;AAG7F,cAAM,gBAAiB,SAAQ,qBAE7B;CAAG;AAEL,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAEjC,eAAO,MAAM,eAAe,EAAE,UAAU,CAAC,kBACoD,CAAC;AAE9F,eAAO,MAAM,OAAO,GAAgC,CAAC,SAAS,GAAG,CAAC,GAAG,21BAWnE,CAAC"}
package/dist/hostRpc.js CHANGED
@@ -9,12 +9,14 @@ class HostedReadPolicy extends RpcMiddleware.Service()("@shivaedev/server/Hosted
9
9
  }) {
10
10
  }
11
11
  export const rpcCacheControl = (_, response) => Effect.succeed(HttpServerResponse.setHeader(response, "cache-control", "private, no-store"));
12
- export const hostRpc = Effect.fnUntraced(function* (group) {
12
+ export const hostRpc = Effect.fnUntraced(function* (group, websocket = false) {
13
13
  const guarded = group.middleware(HostedReadPolicy);
14
14
  // Keep the policy inside identity middleware so it sees the request's viewer.
15
15
  for (const rpc of guarded.requests.values()) {
16
16
  Object.assign(rpc, { middlewares: new Set([HostedReadPolicy, ...rpc.middlewares]) });
17
17
  }
18
- return yield* RpcServer.toHttpEffect(tracedGroup(guarded)).pipe(Effect.provideService(HostedReadPolicy, (effect, request) => Effect.flatMap(CurrentViewer, (viewer) => authorizeReads(effect, request, viewer))));
18
+ const traced = tracedGroup(guarded);
19
+ const handler = websocket ? RpcServer.toHttpEffectWebsocket(traced) : RpcServer.toHttpEffect(traced);
20
+ return yield* handler.pipe(Effect.provideService(HostedReadPolicy, (effect, request) => Effect.flatMap(CurrentViewer, (viewer) => authorizeReads(effect, request, viewer))));
19
21
  });
20
22
  //# sourceMappingURL=hostRpc.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"hostRpc.js","sourceRoot":"","sources":["../src/hostRpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAmB,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAA2B,aAAa,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAClF,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC7F,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,MAAM,gBAAiB,SAAQ,aAAa,CAAC,OAAO,EAAoB,CAAC,oCAAoC,EAAE;IAC9G,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,iBAAiB,EAAE,eAAe,CAAC,CAAC;CACzD,CAAC;CAAG;AAIL,MAAM,CAAC,MAAM,eAAe,GAAkC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,CAC7E,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,SAAS,CAAC,QAAQ,EAAE,eAAe,EAAE,mBAAmB,CAAC,CAAC,CAAC;AAE9F,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAqB,KAA2B;IACjG,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;IACnD,8EAA8E;IAC9E,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;QAC7C,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,IAAI,GAAG,CAAC,CAAC,gBAAgB,EAAE,GAAG,GAAG,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC;IACtF,CAAC;IACD,OAAO,KAAK,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAC9D,MAAM,CAAC,cAAc,CAAC,gBAAgB,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAChJ,CAAC;AACH,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"hostRpc.js","sourceRoot":"","sources":["../src/hostRpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAmB,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAA2B,aAAa,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAClF,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC7F,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,MAAM,gBAAiB,SAAQ,aAAa,CAAC,OAAO,EAAoB,CAAC,oCAAoC,EAAE;IAC9G,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,iBAAiB,EAAE,eAAe,CAAC,CAAC;CACzD,CAAC;CAAG;AAIL,MAAM,CAAC,MAAM,eAAe,GAAkC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,CAC7E,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,SAAS,CAAC,QAAQ,EAAE,eAAe,EAAE,mBAAmB,CAAC,CAAC,CAAC;AAE9F,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAqB,KAA2B,EAAE,SAAS,GAAG,KAAK;IACpH,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;IACnD,8EAA8E;IAC9E,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;QAC7C,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,IAAI,GAAG,CAAC,CAAC,gBAAgB,EAAE,GAAG,GAAG,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC;IACtF,CAAC;IACD,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IACrG,OAAO,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CACzB,MAAM,CAAC,cAAc,CAAC,gBAAgB,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAChJ,CAAC;AACH,CAAC,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"hostRpcRoutes.d.ts","sourceRoot":"","sources":["../src/hostRpcRoutes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACjD,OAAO,EAAmC,UAAU,EAAyC,MAAM,aAAa,CAAC;AAEjH,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,QAAQ,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,KAAK,EAAqC,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAE3G,OAAO,EAA+C,KAAK,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAEvH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAEnE,wBAAgB,aAAa,CAAC,KAAK,SAAS,GAAG,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,QAAQ,SAAS,YAAY,EACnH,OAAO,EAAE;IACR,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACzC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;IACjE,QAAQ,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC;CACxC,GAAG,UAAU,CAAC,QAAQ,CAAC,EACxB,cAAc,EAAE,OAAO,UAAU,EACjC,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,EAC3F,SAAS,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC;;;;;;;;0CA+BjD"}
1
+ {"version":3,"file":"hostRpcRoutes.d.ts","sourceRoot":"","sources":["../src/hostRpcRoutes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACjD,OAAO,EAAmC,UAAU,EAAyC,MAAM,aAAa,CAAC;AAEjH,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,QAAQ,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,KAAK,EAAqC,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAE3G,OAAO,EAA+C,KAAK,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAEvH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAGnE,wBAAgB,aAAa,CAAC,KAAK,SAAS,GAAG,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,QAAQ,SAAS,YAAY,EACnH,OAAO,EAAE;IACR,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACzC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;IACjE,QAAQ,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC;CACxC,GAAG,UAAU,CAAC,QAAQ,CAAC,EACxB,cAAc,EAAE,OAAO,UAAU,EACjC,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,EAC3F,SAAS,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC;;;;;;;;0CA6CjD"}
@@ -9,21 +9,25 @@ import { boundedRpcRequest } from "#boundedRpcRequest.ts";
9
9
  import { hostChanges } from "#hostChanges.ts";
10
10
  import { hostRpc, rpcCacheControl } from "#hostRpc.ts";
11
11
  import { redactingRpcSerialization } from "#redactingRpcSerialization.ts";
12
+ import { socketRpc } from "#socketRpc.ts";
12
13
  export function hostRpcRoutes(options, changesService, { bodySize, trustedProxy, nativeOrigins }, sensitive) {
13
14
  const middleware = options.policy === "loopback"
14
15
  ? requestTracingLayer({ sensitive })
15
16
  : Layer.mergeAll(authenticatedLayer(options.sessions), maybeAuthenticatedLayer(options.sessions), requestTracingLayer({ sensitive }));
16
17
  return HttpRouter.use((router) => Effect.gen(function* () {
17
18
  const wire = redactingRpcSerialization(yield* RpcSerialization.RpcSerialization, sensitive, sensitive.maskFailure);
18
- const handler = yield* hostRpc(options.group).pipe(Effect.provideService(RpcSerialization.RpcSerialization, wire));
19
+ const httpGroup = options.group.omit(...[...options.group.requests.keys()].filter((tag) => tag.endsWith(".$changes")));
20
+ const handler = yield* hostRpc(httpGroup).pipe(Effect.provideService(RpcSerialization.RpcSerialization, wire));
19
21
  const guarded = localRpcGuard(handler, trustedProxy, nativeOrigins);
22
+ const websocket = yield* hostRpc(options.group, true).pipe(Effect.provideService(RpcSerialization.RpcSerialization, wire));
20
23
  yield* router.add("OPTIONS", options.rpcPath ?? "/rpc", HttpServerResponse.text("Local same-origin requests only", {
21
24
  status: HttpStatus.fromLiteral("Forbidden"),
22
25
  }));
26
+ yield* router.add("GET", options.rpcPath ?? "/rpc", socketRpc(websocket, options, trustedProxy, nativeOrigins).pipe(Effect.provideService(RpcSerialization.RpcSerialization, wire)));
23
27
  yield* router.add("POST", options.rpcPath ?? "/rpc", (request) => {
24
28
  HttpEffect.appendPreResponseHandlerUnsafe(request, rpcCacheControl);
25
29
  return guarded.pipe(Effect.provideService(HttpIncomingMessage.MaxBodySize, ByteSize.bytes(bodySize)), Effect.provideService(HttpServerRequest.HttpServerRequest, boundedRpcRequest(request)), Effect.provideService(RpcSerialization.RpcSerialization, wire));
26
30
  });
27
- })).pipe(Layer.provide(Layer.merge(middleware, hostChanges(options.group, changesService).pipe(Layer.provideMerge(options.handlers)))), Layer.provide(RpcSerialization.layerNdjson));
31
+ })).pipe(Layer.provide(Layer.merge(middleware, hostChanges(options.group, changesService, options.policy === "loopback" ? undefined : options.sessions).pipe(Layer.provideMerge(options.handlers)))), Layer.provide(RpcSerialization.layerNdjson));
28
32
  }
29
33
  //# sourceMappingURL=hostRpcRoutes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"hostRpcRoutes.js","sourceRoot":"","sources":["../src/hostRpcRoutes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hostRpcRoutes.js","sourceRoot":"","sources":["../src/hostRpcRoutes.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { createServer } from "node:http";
2
- import { NodeHttpServer } from "@effect/platform-node";
2
+ import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
3
3
  import { Config, Effect, Layer } from "effect";
4
4
  export function nodeHttpServer(host, config) {
5
5
  return Layer.unwrap(Effect.gen(function* () {
@@ -1 +1 @@
1
- {"version":3,"file":"nodeHttpServer.js","sourceRoot":"","sources":["../src/nodeHttpServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,MAAM,EAAiB,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAE9D,MAAM,UAAU,cAAc,CAC7B,IAAY,EACZ,MAIC;IAED,OAAO,KAAK,CAAC,MAAM,CAClB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,MAAM,IAAI,GAAG,MAAM,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACxF,OAAO,cAAc,CAAC,KAAK,CAAC,YAAY,EAAE;YACzC,uBAAuB,EAAE,MAAM,EAAE,uBAAuB;YACxD,IAAI;YACJ,IAAI;YACJ,GAAG,CAAC,MAAM,EAAE,yBAAyB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,yBAAyB,EAAE,MAAM,CAAC,yBAAyB,EAAE,CAAC;SAC3H,CAAC,CAAC;IACJ,CAAC,CAAC,CACF,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"nodeHttpServer.js","sourceRoot":"","sources":["../src/nodeHttpServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,cAAc,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,MAAM,EAAiB,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAE9D,MAAM,UAAU,cAAc,CAC7B,IAAY,EACZ,MAIC;IAED,OAAO,KAAK,CAAC,MAAM,CAClB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,MAAM,IAAI,GAAG,MAAM,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACxF,OAAO,cAAc,CAAC,KAAK,CAAC,YAAY,EAAE;YACzC,uBAAuB,EAAE,MAAM,EAAE,uBAAuB;YACxD,IAAI;YACJ,IAAI;YACJ,GAAG,CAAC,MAAM,EAAE,yBAAyB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,yBAAyB,EAAE,MAAM,CAAC,yBAAyB,EAAE,CAAC;SAC3H,CAAC,CAAC;IACJ,CAAC,CAAC,CACF,CAAC;AACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { Effect, type Scope } from "effect";
2
+ import { HttpServerRequest, HttpServerResponse } from "effect/http";
3
+ import { type SessionShape } from "@shivaedev/identity/server/session.ts";
4
+ import type { HostPolicy } from "#hostOptions.ts";
5
+ export declare function socketRpc<TSession extends SessionShape>(handler: Effect.Effect<HttpServerResponse.HttpServerResponse, never, HttpServerRequest.HttpServerRequest | Scope.Scope>, policy: HostPolicy<TSession>, trustedProxy: string | undefined, nativeOrigins: readonly string[]): Effect.Effect<HttpServerResponse.HttpServerResponse, never, HttpServerRequest.HttpServerRequest | Scope.Scope>;
6
+ //# sourceMappingURL=socketRpc.d.ts.map
@@ -0,0 +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;AAC1F,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,kHAiBhC"}
@@ -0,0 +1,27 @@
1
+ import { Effect } from "effect";
2
+ import { Headers, HttpServerRequest, HttpServerResponse } from "effect/http";
3
+ import * as HttpStatus from "effect/http/HttpStatus";
4
+ import { localTransport } from "@shivaedev/identity/server/local.ts";
5
+ import { resolveSession } from "@shivaedev/identity/server/session.ts";
6
+ export function socketRpc(handler, policy, trustedProxy, nativeOrigins) {
7
+ return Effect.gen(function* () {
8
+ const request = yield* HttpServerRequest.HttpServerRequest;
9
+ if (!localTransport(trustedProxy, nativeOrigins)(request)) {
10
+ return HttpServerResponse.text("Local same-origin requests only", { status: HttpStatus.fromLiteral("Forbidden") });
11
+ }
12
+ if (policy.policy !== "loopback") {
13
+ yield* resolveSession(policy.sessions, Headers.empty, "$connect");
14
+ }
15
+ return yield* handler;
16
+ }).pipe(Effect.catchTags({
17
+ AuthUnavailable: authUnavailable,
18
+ Forbidden: forbidden,
19
+ }));
20
+ }
21
+ function authUnavailable() {
22
+ return Effect.succeed(HttpServerResponse.empty({ status: HttpStatus.fromLiteral("ServiceUnavailable") }));
23
+ }
24
+ function forbidden() {
25
+ return Effect.succeed(HttpServerResponse.empty({ status: HttpStatus.fromLiteral("Forbidden") }));
26
+ }
27
+ //# sourceMappingURL=socketRpc.js.map
@@ -0,0 +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;AAG1F,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;QACnE,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.0",
3
+ "version": "0.4.0",
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,10 +45,10 @@
45
45
  ],
46
46
  "dependencies": {
47
47
  "@shivaedev/changes": "0.5.0",
48
- "@shivaedev/contract": "0.19.0",
48
+ "@shivaedev/contract": "0.20.0",
49
49
  "@shivaedev/errors": "0.1.0",
50
- "@shivaedev/http": "0.1.0",
51
- "@shivaedev/identity": "0.3.0",
50
+ "@shivaedev/http": "0.1.1",
51
+ "@shivaedev/identity": "0.3.2",
52
52
  "@shivaedev/local-postgres": "0.7.0",
53
53
  "@shivaedev/sql": "0.24.0"
54
54
  },
@@ -57,10 +57,10 @@
57
57
  "@effect/sql-pg": "4.0.5",
58
58
  "@effect/vitest": "4.0.5",
59
59
  "@shivaedev/jobs": "0.22.0",
60
- "@shivaedev/quality": "0.43.0",
60
+ "@shivaedev/quality": "0.44.1",
61
61
  "@shivaedev/test": "0.10.0",
62
62
  "@shivaedev/test-sql": "0.10.2",
63
- "@shivaedev/test-story": "0.22.0",
63
+ "@shivaedev/test-story": "0.23.0",
64
64
  "@types/node": "24.19.2",
65
65
  "@types/pg": "8.20.0",
66
66
  "better-auth": "1.7.12",
package/src/appHost.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { NodeCrypto } from "@effect/platform-node";
1
+ import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
2
2
  import { Context, type Effect, Layer } from "effect";
3
3
  import type { Rpc } from "effect/rpc";
4
4
  import type { makeLiveServer } from "@shivaedev/contract/makeLiveServer.ts";
@@ -1,6 +1,6 @@
1
- import { NodeServices } from "@effect/platform-node";
1
+ import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
2
2
  import { Effect, Layer } from "effect";
3
- import { HttpRouter, HttpStaticServer } from "effect/http";
3
+ import { HttpRouter, HttpServer, HttpStaticServer } from "effect/http";
4
4
  import type { Rpc } from "effect/rpc";
5
5
  import type { SessionShape } from "@shivaedev/identity/server/session.ts";
6
6
  import type { AppChanges, AppHttpHostOptions } from "#appHost.ts";
@@ -61,11 +61,11 @@ export function appHostHttp<
61
61
  Layer.provide(nativeClientOrigins(nativeOrigins, trustedProxy, false, options.rpcPath)),
62
62
  Layer.provide(nativeClientOrigins(nativeOrigins, trustedProxy, options.nativeOrigins === undefined ? "preflight" : true)),
63
63
  );
64
- return HttpRouter.serve(httpRoutes, { disableLogger: true }).pipe(
65
- Layer.provideMerge(application),
66
- Layer.provideMerge(server),
67
- Layer.provide(NodeServices.layer),
68
- );
64
+ return Layer.unwrap(
65
+ HttpRouter.toHttpEffect(httpRoutes).pipe(
66
+ Effect.map((handler) => HttpServer.serve(handler).pipe(HttpServer.withLogAddress, Layer.provideMerge(server))),
67
+ ),
68
+ ).pipe(Layer.provideMerge(application), Layer.provideMerge(NodeHttpServer.layerHttpServices));
69
69
  }),
70
70
  ).pipe(Layer.provide(redactingDiagnostics(sensitive)), Layer.provide(otlpTracing), Layer.provide(Layer.succeed(RedactionPolicy, sensitive)));
71
71
  }
@@ -1,5 +1,6 @@
1
1
  import process from "node:process";
2
- import { NodeRuntime, NodeServices } from "@effect/platform-node";
2
+ import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
3
+ import * as NodeServices from "@effect/platform-node/NodeServices";
3
4
  import { Effect } from "effect";
4
5
  import { ChildProcess, ChildProcessSpawner } from "effect/process";
5
6
 
@@ -1,15 +1,39 @@
1
1
  import { Context, Effect, Layer, Option, Stream } from "effect";
2
+ import { Headers } from "effect/http";
2
3
  import type { Rpc, RpcGroup } from "effect/rpc";
4
+ import { CurrentViewer, type Viewer } from "@shivaedev/contract/readPolicy.ts";
3
5
  import type { QueryReference } from "@shivaedev/contract/serverMetadata.ts";
6
+ import { Unauthorized } from "@shivaedev/errors/taxonomy.ts";
7
+ import { resolveSession, type SessionPolicy, type SessionShape } from "@shivaedev/identity/server/session.ts";
4
8
  import { resolveChanges } from "@shivaedev/identity/server/sessionKeys.ts";
5
9
  import type { AppChanges } from "./appHost.ts";
6
10
 
7
- export function hostChanges<R extends Rpc.Any>(group: RpcGroup.RpcGroup<R>, changesService: typeof AppChanges) {
11
+ export function hostChanges<R extends Rpc.Any, TSession extends SessionShape>(
12
+ group: RpcGroup.RpcGroup<R>,
13
+ changesService: typeof AppChanges,
14
+ sessions?: SessionPolicy<TSession>,
15
+ ) {
8
16
  return Layer.effectContext(
9
17
  Effect.gen(function* () {
10
18
  const changes = yield* Effect.serviceOption(changesService);
11
19
  const services = yield* Effect.context<never>();
12
20
  const handlers = new Map<string, unknown>();
21
+ function subscribe(rpc: Rpc.Any, queries: readonly QueryReference[]) {
22
+ return Stream.unwrap(
23
+ Effect.gen(function* () {
24
+ const { subscription, context } = yield* resolveChanges(rpc, queries);
25
+ const viewer = yield* CurrentViewer;
26
+ const authorized = authorizeSession(sessions, rpc._tag, viewer, subscription.authorized);
27
+ const stream = Option.isSome(changes)
28
+ ? changes.value.subscribe({ ...subscription, authorized }, context)
29
+ : Stream.concat(Stream.succeed({ _tag: "Resync" } as const), Stream.never);
30
+ if (sessions === undefined || Option.isNone(viewer)) {
31
+ return stream;
32
+ }
33
+ return Stream.interruptWhen(stream, sessionRevoked(sessions, rpc._tag, viewer.value.id));
34
+ }),
35
+ );
36
+ }
13
37
  for (const rpc of group.requests.values()) {
14
38
  if (!rpc._tag.endsWith(".$changes")) {
15
39
  continue;
@@ -19,16 +43,39 @@ export function hostChanges<R extends Rpc.Any>(group: RpcGroup.RpcGroup<R>, chan
19
43
  handlers.set(rpc.key, provided);
20
44
  continue;
21
45
  }
22
- function handler({ queries }: { readonly queries: readonly QueryReference[] }) {
23
- return Stream.unwrap(
24
- Effect.map(resolveChanges(rpc, queries), ({ subscription, context }) =>
25
- Option.isSome(changes) ? changes.value.subscribe(subscription, context) : Stream.never,
26
- ),
27
- );
28
- }
29
- handlers.set(rpc.key, { context: services, handler, tag: rpc._tag });
46
+ handlers.set(rpc.key, {
47
+ context: services,
48
+ handler: ({ queries }: { readonly queries: readonly QueryReference[] }) => subscribe(rpc, queries),
49
+ tag: rpc._tag,
50
+ });
30
51
  }
31
52
  return Context.makeUnsafe<Rpc.ToHandler<Extract<R, { readonly _tag: `${string}.$changes` }>>>(handlers);
32
53
  }),
33
54
  );
34
55
  }
56
+
57
+ function authorizeSession<TSession extends SessionShape>(
58
+ sessions: SessionPolicy<TSession> | undefined,
59
+ tag: string,
60
+ viewer: Option.Option<Viewer>,
61
+ policy: Effect.Effect<boolean>,
62
+ ) {
63
+ if (sessions === undefined) {
64
+ return policy;
65
+ }
66
+ return resolveSession(sessions, Headers.empty, tag).pipe(
67
+ Effect.map((current) => Option.isNone(viewer) || (Option.isSome(current) && current.value.user.id === viewer.value.id)),
68
+ Effect.catch(() => Effect.succeed(false)),
69
+ Effect.flatMap((allowed) => (allowed ? policy : Effect.succeed(false))),
70
+ );
71
+ }
72
+
73
+ function sessionRevoked<TSession extends SessionShape>(sessions: SessionPolicy<TSession>, tag: string, id: string) {
74
+ return Effect.gen(function* () {
75
+ yield* Effect.sleep(sessions.streamRevalidationInterval ?? "30 seconds");
76
+ const current = yield* resolveSession(sessions, Headers.empty, tag).pipe(Effect.catchTag("AuthUnavailable", () => Effect.succeed(undefined)));
77
+ if (current !== undefined && (Option.isNone(current) || current.value.user.id !== id)) {
78
+ return yield* new Unauthorized({ message: "Authentication required" });
79
+ }
80
+ }).pipe(Effect.forever);
81
+ }
package/src/hostRpc.ts CHANGED
@@ -14,13 +14,15 @@ export type { HostedReadPolicy };
14
14
  export const rpcCacheControl: HttpEffect.PreResponseHandler = (_, response) =>
15
15
  Effect.succeed(HttpServerResponse.setHeader(response, "cache-control", "private, no-store"));
16
16
 
17
- export const hostRpc = Effect.fnUntraced(function* <R extends Rpc.Any>(group: RpcGroup.RpcGroup<R>) {
17
+ export const hostRpc = Effect.fnUntraced(function* <R extends Rpc.Any>(group: RpcGroup.RpcGroup<R>, websocket = false) {
18
18
  const guarded = group.middleware(HostedReadPolicy);
19
19
  // Keep the policy inside identity middleware so it sees the request's viewer.
20
20
  for (const rpc of guarded.requests.values()) {
21
21
  Object.assign(rpc, { middlewares: new Set([HostedReadPolicy, ...rpc.middlewares]) });
22
22
  }
23
- return yield* RpcServer.toHttpEffect(tracedGroup(guarded)).pipe(
23
+ const traced = tracedGroup(guarded);
24
+ const handler = websocket ? RpcServer.toHttpEffectWebsocket(traced) : RpcServer.toHttpEffect(traced);
25
+ return yield* handler.pipe(
24
26
  Effect.provideService(HostedReadPolicy, (effect, request) => Effect.flatMap(CurrentViewer, (viewer) => authorizeReads(effect, request, viewer))),
25
27
  );
26
28
  });
@@ -14,6 +14,7 @@ import { hostRpc, rpcCacheControl } from "#hostRpc.ts";
14
14
  import type { hostTransport } from "#hostTransport.ts";
15
15
  import { redactingRpcSerialization } from "#redactingRpcSerialization.ts";
16
16
  import type { schemaSensitiveKeys } from "#schemaSensitiveKeys.ts";
17
+ import { socketRpc } from "#socketRpc.ts";
17
18
 
18
19
  export function hostRpcRoutes<TRpcs extends Rpc.Any, THandlers, TError, TRequirements, TSession extends SessionShape>(
19
20
  options: {
@@ -32,8 +33,10 @@ export function hostRpcRoutes<TRpcs extends Rpc.Any, THandlers, TError, TRequire
32
33
  return HttpRouter.use((router) =>
33
34
  Effect.gen(function* () {
34
35
  const wire = redactingRpcSerialization(yield* RpcSerialization.RpcSerialization, sensitive, sensitive.maskFailure);
35
- const handler = yield* hostRpc(options.group).pipe(Effect.provideService(RpcSerialization.RpcSerialization, wire));
36
+ const httpGroup = options.group.omit(...[...options.group.requests.keys()].filter((tag) => tag.endsWith(".$changes")));
37
+ const handler = yield* hostRpc(httpGroup).pipe(Effect.provideService(RpcSerialization.RpcSerialization, wire));
36
38
  const guarded = localRpcGuard(handler, trustedProxy, nativeOrigins);
39
+ const websocket = yield* hostRpc(options.group, true).pipe(Effect.provideService(RpcSerialization.RpcSerialization, wire));
37
40
  yield* router.add(
38
41
  "OPTIONS",
39
42
  options.rpcPath ?? "/rpc",
@@ -41,6 +44,11 @@ export function hostRpcRoutes<TRpcs extends Rpc.Any, THandlers, TError, TRequire
41
44
  status: HttpStatus.fromLiteral("Forbidden"),
42
45
  }),
43
46
  );
47
+ yield* router.add(
48
+ "GET",
49
+ options.rpcPath ?? "/rpc",
50
+ socketRpc(websocket, options, trustedProxy, nativeOrigins).pipe(Effect.provideService(RpcSerialization.RpcSerialization, wire)),
51
+ );
44
52
  yield* router.add("POST", options.rpcPath ?? "/rpc", (request) => {
45
53
  HttpEffect.appendPreResponseHandlerUnsafe(request, rpcCacheControl);
46
54
  return guarded.pipe(
@@ -51,7 +59,14 @@ export function hostRpcRoutes<TRpcs extends Rpc.Any, THandlers, TError, TRequire
51
59
  });
52
60
  }),
53
61
  ).pipe(
54
- Layer.provide(Layer.merge(middleware, hostChanges(options.group, changesService).pipe(Layer.provideMerge(options.handlers)))),
62
+ Layer.provide(
63
+ Layer.merge(
64
+ middleware,
65
+ hostChanges(options.group, changesService, options.policy === "loopback" ? undefined : options.sessions).pipe(
66
+ Layer.provideMerge(options.handlers),
67
+ ),
68
+ ),
69
+ ),
55
70
  Layer.provide(RpcSerialization.layerNdjson),
56
71
  );
57
72
  }
@@ -1,5 +1,5 @@
1
1
  import { createServer } from "node:http";
2
- import { NodeHttpServer } from "@effect/platform-node";
2
+ import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
3
3
  import { Config, type Duration, Effect, Layer } from "effect";
4
4
 
5
5
  export function nodeHttpServer(
@@ -0,0 +1,36 @@
1
+ import { Effect, type Scope } from "effect";
2
+ import { Headers, HttpServerRequest, HttpServerResponse } from "effect/http";
3
+ import * as HttpStatus from "effect/http/HttpStatus";
4
+ import { localTransport } from "@shivaedev/identity/server/local.ts";
5
+ import { resolveSession, type SessionShape } from "@shivaedev/identity/server/session.ts";
6
+ import type { HostPolicy } from "#hostOptions.ts";
7
+
8
+ export function socketRpc<TSession extends SessionShape>(
9
+ handler: Effect.Effect<HttpServerResponse.HttpServerResponse, never, HttpServerRequest.HttpServerRequest | Scope.Scope>,
10
+ policy: HostPolicy<TSession>,
11
+ trustedProxy: string | undefined,
12
+ nativeOrigins: readonly string[],
13
+ ) {
14
+ return Effect.gen(function* () {
15
+ const request = yield* HttpServerRequest.HttpServerRequest;
16
+ if (!localTransport(trustedProxy, nativeOrigins)(request)) {
17
+ return HttpServerResponse.text("Local same-origin requests only", { status: HttpStatus.fromLiteral("Forbidden") });
18
+ }
19
+ if (policy.policy !== "loopback") {
20
+ yield* resolveSession(policy.sessions, Headers.empty, "$connect");
21
+ }
22
+ return yield* handler;
23
+ }).pipe(
24
+ Effect.catchTags({
25
+ AuthUnavailable: authUnavailable,
26
+ Forbidden: forbidden,
27
+ }),
28
+ );
29
+ }
30
+
31
+ function authUnavailable() {
32
+ return Effect.succeed(HttpServerResponse.empty({ status: HttpStatus.fromLiteral("ServiceUnavailable") }));
33
+ }
34
+ function forbidden() {
35
+ return Effect.succeed(HttpServerResponse.empty({ status: HttpStatus.fromLiteral("Forbidden") }));
36
+ }