diff --git a/packages/core-realtime/package.json b/packages/core-realtime/package.json index 17194e9..4d01694 100644 --- a/packages/core-realtime/package.json +++ b/packages/core-realtime/package.json @@ -24,6 +24,7 @@ }, "devDependencies": { "@repo/core-eslint": "workspace:*", + "@repo/core-testing": "workspace:*", "@repo/core-typescript": "workspace:*", "@types/node": "^22.0.0", "typescript": "^5.8.0", diff --git a/packages/core-realtime/src/realtime-channel.test.ts b/packages/core-realtime/src/realtime-channel.test.ts new file mode 100644 index 0000000..357e732 --- /dev/null +++ b/packages/core-realtime/src/realtime-channel.test.ts @@ -0,0 +1,25 @@ +import { describe, it, expect } from "vitest"; +import { z } from "zod"; +import { defineRealtimeChannel } from "@/realtime-channel"; + +describe("defineRealtimeChannel", () => { + it("returns a descriptor with name, schema, and scope", () => { + const ch = defineRealtimeChannel( + "test.channel", + z.object({ id: z.string() }).strict(), + { scope: "public" }, + ); + expect(ch.name).toBe("test.channel"); + expect(ch.scope).toBe("public"); + expect(() => ch.schema.parse({ id: "x" })).not.toThrow(); + }); + + it("preserves all four scope shapes", () => { + expect(defineRealtimeChannel("a", z.object({}), { scope: "public" }).scope).toBe("public"); + expect(defineRealtimeChannel("a", z.object({}), { scope: "authenticated" }).scope).toBe("authenticated"); + expect(defineRealtimeChannel("a", z.object({}), { scope: { role: "admin" } }).scope).toEqual({ role: "admin" }); + expect( + defineRealtimeChannel("a", z.object({}), { scope: { userScoped: true, template: "x.{id}" } }).scope, + ).toEqual({ userScoped: true, template: "x.{id}" }); + }); +}); diff --git a/packages/core-realtime/src/realtime-channel.ts b/packages/core-realtime/src/realtime-channel.ts new file mode 100644 index 0000000..3aeeede --- /dev/null +++ b/packages/core-realtime/src/realtime-channel.ts @@ -0,0 +1,21 @@ +import type { z } from "zod"; + +export type ChannelScope = + | "public" + | "authenticated" + | { role: string } + | { userScoped: true; template: string }; + +export type RealtimeChannelDescriptor = { + readonly name: TName; + readonly schema: TSchema; + readonly scope: ChannelScope; +}; + +export function defineRealtimeChannel( + name: TName, + schema: TSchema, + options: { scope: ChannelScope }, +): RealtimeChannelDescriptor { + return { name, schema, scope: options.scope }; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca10382..999c0e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -571,6 +571,9 @@ importers: '@repo/core-eslint': specifier: workspace:* version: link:../core-eslint + '@repo/core-testing': + specifier: workspace:* + version: link:../core-testing '@repo/core-typescript': specifier: workspace:* version: link:../core-typescript