Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/pbkit-realtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@karnak19/pbkit-realtime": minor
"@karnak19/pbkit": minor
---

Add `@karnak19/pbkit-realtime` plugin for typed realtime subscriptions

Generates a `subscribeTo{Collection}()` helper per non-excluded collection using PocketBase's built-in SSE system. Each helper takes a typed `(event: RealtimeEvent<{Collection}Record>) => void` callback plus optional `filter`/`id`, and returns an unsubscribe function.

Also exports `pascalCase` from `@karnak19/pbkit` so plugins can share it — the realtime, TanStack Query, and Zod plugins now use the core helper instead of duplicating it.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Migrating from pocketbase-typegen
description: A step-by-step guide to moving an existing project from pocketbase-typegen to pbkit.
sidebar:
order: 6
order: 7
---

[pocketbase-typegen](https://github.com/patmood/pocketbase-typegen) generates a
Expand Down
177 changes: 177 additions & 0 deletions apps/docs/src/content/docs/how-to/realtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
---
title: Add realtime subscriptions
description: Generate typed realtime subscription helpers for PocketBase SSE.
sidebar:
order: 5
---

The `@karnak19/pbkit-realtime` package provides a plugin that generates typed realtime subscription helpers using PocketBase's built-in SSE system.

## Install

The plugin runs at generation time, so it is a dev dependency. `pocketbase` is
the runtime peer (already required by the generated client).

```bash
bun add -d @karnak19/pbkit-realtime
```

## Setup

Add the plugin to your `pbkit.config.ts`:

```ts
import { realtimePlugin } from "@karnak19/pbkit-realtime"

export default {
input: "https://my-pb.example.com",
output: "./src/generated",
plugins: [realtimePlugin],
}
```

After running `bunx pbkit generate`, a `realtime.gen.ts` file is created alongside `types.gen.ts`, `client.gen.ts`, and `sdk.gen.ts`.

## Generated output

The plugin generates shared types and one `subscribeTo{Collection}()` function per non-excluded collection:

### Shared types

```ts
export type RealtimeAction = "create" | "update" | "delete"

export interface RealtimeEvent<T> {
action: RealtimeAction
record: T
}
```

### Subscription functions

```ts
export function subscribeToArticles(
callback: (event: RealtimeEvent<ArticlesRecord>) => void,
options?: { filter?: string; id?: string },
): Promise<() => Promise<void>>
```

Each function:
- Accepts a typed callback receiving `RealtimeEvent<{Collection}Record>`
- Accepts optional `filter` (PocketBase filter string) and `id` (subscribe to a specific record)
- Returns `Promise<() => Promise<void>>` — resolves with an **async** unsubscribe callback (PocketBase's `UnsubscribeFunc`); `await` it to surface unsubscribe errors

## Usage example

### Subscribe to all changes

```ts
import { subscribeToArticles } from "./generated/realtime.gen"

const unsub = await subscribeToArticles((event) => {
if (event.action === "create") {
console.log("New article:", event.record.title)
}
if (event.action === "update") {
console.log("Updated article:", event.record.id)
}
if (event.action === "delete") {
console.log("Deleted article:", event.record.id)
}
})

// Later, when you no longer need the subscription
await unsub()
```

### Subscribe with a filter

```ts
const unsub = await subscribeToArticles(
(event) => {
console.log("Published article changed:", event.record.title)
},
{ filter: 'status = "published"' },
)
```

### Subscribe to a specific record

```ts
const unsub = await subscribeToArticles(
(event) => {
console.log("Article updated:", event.record.title)
},
{ id: "RECORD_ID" },
)
```

### React component example

```tsx
import { subscribeToComments } from "./generated/realtime.gen"
import { useEffect, useState } from "react"

function LiveComments({ articleId }: { articleId: string }) {
const [comments, setComments] = useState([])

useEffect(() => {
let unsub: (() => Promise<void>) | undefined

subscribeToComments(
(event) => {
if (event.action === "create") {
setComments((prev) => [...prev, event.record])
}
},
{ filter: `article = "${articleId}"` },
).then((fn) => { unsub = fn })

return () => { unsub?.() }
}, [articleId])

return (
<ul>
{comments.map((c) => (
<li key={c.id}>{c.content}</li>
))}
</ul>
)
}
```

Avoid interpolating untrusted input directly into filter strings. Prefer constructing filters server-side or validating IDs before inserting them.

## Collection filtering

The plugin respects the `collections` config — excluded collections won't generate subscription functions:

```ts
export default {
collections: {
_superusers: { exclude: true },
logs: { exclude: true },
},
plugins: [realtimePlugin],
}
```

## Framework-agnostic

The generated helpers use the PocketBase client directly via `client.collection(name).subscribe()`. No framework-specific imports — works with React, Vue, Svelte, Solid, or any JavaScript environment.

## Combining with other plugins

All plugins can be used together:

```ts
import { tanstackPlugin } from "@karnak19/pbkit-tanstack"
import { zodPlugin } from "@karnak19/pbkit-zod"
import { realtimePlugin } from "@karnak19/pbkit-realtime"

export default {
input: "https://my-pb.example.com",
output: "./src/generated",
plugins: [tanstackPlugin, zodPlugin, realtimePlugin],
}
```
2 changes: 1 addition & 1 deletion apps/docs/src/content/docs/how-to/write-a-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Write a plugin
description: How to write a custom pbkit plugin.
sidebar:
order: 5
order: 6
---

pbkit plugins receive the parsed schema and return generated files. A plugin is an object implementing the `PbkitPlugin` interface.
Expand Down
3 changes: 2 additions & 1 deletion apps/playground/pbkit.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { PbkitConfig } from "@karnak19/pbkit"
import { tanstackPlugin } from "@karnak19/pbkit-tanstack"
import { realtimePlugin } from "@karnak19/pbkit-realtime"

export default {
input: "./pb_schema.json",
output: "./src/generated",
plugins: [tanstackPlugin],
plugins: [tanstackPlugin, realtimePlugin],
} satisfies PbkitConfig
24 changes: 21 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 55 additions & 0 deletions packages/pbkit-realtime/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# @karnak19/pbkit-realtime

Realtime subscription plugin for `@karnak19/pbkit`.

It generates typed SSE subscription helpers from your PocketBase schema using
PocketBase's built-in realtime system.

## Install

```bash
bun add @karnak19/pbkit @karnak19/pbkit-realtime
```

## Setup

```ts
// pbkit.config.ts
import { realtimePlugin } from "@karnak19/pbkit-realtime"

export default {
input: "./pb_schema.json",
output: "./src/generated",
sdk: {
baseUrl: "https://my-pocketbase.example.com",
},
plugins: [realtimePlugin],
}
```

Run pbkit:

```bash
bunx pbkit generate
```

The plugin writes `src/generated/realtime.gen.ts` alongside the core pbkit
generated files.

## Usage

```ts
import { subscribeToArticles } from "./generated/realtime.gen"

const unsub = await subscribeToArticles((event) => {
if (event.action === "create") {
console.log("New article:", event.record.title)
}
})

// Later (unsubscribe is async — await to surface errors)
await unsub()
```

The generated helpers use the PocketBase client directly, so they work with any
framework (React, Solid, Svelte, Vue, etc.).
49 changes: 49 additions & 0 deletions packages/pbkit-realtime/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "@karnak19/pbkit-realtime",
"version": "0.0.0",
"description": "Realtime subscription plugin for pbkit",
"homepage": "https://karnak19.github.io/pbkit/",
"repository": {
"type": "git",
"url": "git+https://github.com/Karnak19/pbkit.git",
"directory": "packages/pbkit-realtime"
},
"bugs": {
"url": "https://github.com/Karnak19/pbkit/issues"
},
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"files": [
"dist",
"README.md"
],
"scripts": {
"build": "bun build src/index.ts --outdir=dist --target=node && tsc --emitDeclarationOnly",
"test": "bun test",
"test:watch": "bun test --watch",
"typecheck": "tsc --noEmit",
"lint": "tsc --noEmit",
"prepublishOnly": "bun run build"
},
"publishConfig": {
"access": "public"
},
"license": "MIT",
"peerDependencies": {
"pocketbase": ">=0.21.0"
},
"dependencies": {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Problem: This package doesn't declare pocketbase as a peerDependency, even though the generated realtime.gen.ts code depends on it at runtime (via client.gen.ts which imports from pocketbase).

Impact: Both sibling plugins follow the peerDependency pattern — pbkit-tanstack declares @tanstack/query-core and pbkit-zod declares zod. Without it, npm install @karnak19/pbkit-realtime won't warn users if pocketbase is missing from their project. While pocketbase is typically already present (required by the core pbkit client), explicit declaration is the correct package hygiene.

Fix: Add a peerDependencies block consistent with the sibling packages:

"peerDependencies": {
  "pocketbase": ">=0.21.0"
}
``` <!-- ai-pr-review:inline -->

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 19adf11. Added "peerDependencies": { "pocketbase": ">=0.21.0" }, matching the pbkit-zod (zod) / pbkit-tanstack (@tanstack/query-core) pattern.

"@karnak19/pbkit": "workspace:*"
},
"devDependencies": {
"@types/bun": "^1.3.13",
"typescript": "^6.0.0"
}
}
Loading
Loading