@novadxhq/sveltekit-inngest 0.0.1 → 0.0.3

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.
Files changed (3) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -148
  3. package/package.json +12 -11
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 wh1337
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,23 +1,20 @@
1
1
  # @novadxhq/sveltekit-inngest
2
2
 
3
- Svelte 5 helpers for building Inngest realtime subscriptions in SvelteKit.
3
+ Svelte 5 utilities for building typed realtime subscriptions in SvelteKit with Inngest and SSE.
4
4
 
5
- ## Features
5
+ ## What This Library Provides
6
6
 
7
- - `RealtimeManager` wrapper component for app-level realtime context.
8
- - `getRealtimeState()` for Svelte 5 `.current` connection state access.
9
- - `getRealtimeTopicState()` for typed topic `.current` full-envelope payload access.
10
- - Compatibility APIs: `getRealtime()` and `getRealtimeTopicJson()`.
11
- - `createRealtimeEndpoint()` server helper for a single `POST` SSE endpoint using `sveltekit-sse`.
12
- - Full SvelteKit `RequestEvent` access inside `authorize` and `channelArgs`.
7
+ - A client manager component (`RealtimeManager`) that owns the SSE connection.
8
+ - State-first client helpers (`getRealtimeState`, `getRealtimeTopicState`) for Svelte 5 `.current` reads.
9
+ - A server helper (`createRealtimeEndpoint`) that creates a `POST` SSE endpoint using `sveltekit-sse`.
10
+ - Typed topic payloads based on your `@inngest/realtime` channel definitions.
11
+ - Built-in connection health events (`connecting`, `connected`, `degraded`).
13
12
 
14
- ## Install
13
+ ## Requirements
15
14
 
16
- ```sh
17
- npm install @novadxhq/sveltekit-inngest
18
- ```
15
+ This package is for **Svelte 5 + SvelteKit** projects.
19
16
 
20
- Peer dependencies:
17
+ Your app must already include these peer dependencies:
21
18
 
22
19
  - `svelte` (v5)
23
20
  - `@sveltejs/kit`
@@ -25,31 +22,87 @@ Peer dependencies:
25
22
  - `@inngest/realtime`
26
23
  - `inngest`
27
24
 
28
- ## Client usage (Svelte 5)
25
+ ## Install
26
+
27
+ ```sh
28
+ npm install @novadxhq/sveltekit-inngest
29
+ ```
30
+
31
+ ## Quick Start (End-to-End)
32
+
33
+ ### 1. Define your channel and topic
34
+
35
+ ```ts
36
+ // src/lib/realtime/orders-channel.ts
37
+ import { channel, topic } from "@inngest/realtime";
38
+ import { z } from "zod";
39
+
40
+ export const ordersUpdatedTopic = topic("orders.updated").schema(
41
+ z.object({
42
+ orderId: z.string(),
43
+ status: z.string(),
44
+ })
45
+ );
46
+
47
+ export const ordersChannel = channel("orders").addTopic(ordersUpdatedTopic);
48
+ ```
49
+
50
+ ### 2. Create the realtime endpoint
51
+
52
+ ```ts
53
+ // src/routes/api/events/+server.ts
54
+ import { createRealtimeEndpoint } from "@novadxhq/sveltekit-inngest/server";
55
+ import { inngest } from "$lib/server/inngest";
56
+ import { ordersChannel } from "$lib/realtime/orders-channel";
57
+
58
+ export const POST = createRealtimeEndpoint({
59
+ inngest,
60
+ channel: ordersChannel,
61
+ healthCheck: {
62
+ intervalMs: 5_000,
63
+ },
64
+ authorize: ({ event, locals, topics, params }) => {
65
+ // You have full RequestEvent access here.
66
+ // Example: block unauthenticated users.
67
+ if (!locals.user) return false;
68
+
69
+ // Example: optionally filter allowed topics.
70
+ if (params?.scope === "limited") {
71
+ return {
72
+ allowedTopics: topics.filter((topic) => topic === "orders.updated"),
73
+ };
74
+ }
75
+
76
+ return true;
77
+ },
78
+ });
79
+ ```
29
80
 
30
- `RealtimeBody` below is only an example. `RealtimeManager` can wrap any children and render them internally via `{@render children?.()}`.
81
+ ### 3. Wrap your UI with `RealtimeManager`
31
82
 
32
83
  ```svelte
33
- <!-- +page.svelte -->
84
+ <!-- src/routes/+page.svelte -->
34
85
  <script lang="ts">
35
86
  import { RealtimeManager } from "@novadxhq/sveltekit-inngest";
36
- import { myChannel } from "$lib/channels";
37
- import RealtimeBody from "./RealtimeBody.svelte";
87
+ import { ordersChannel } from "$lib/realtime/orders-channel";
88
+ import OrdersPanel from "./OrdersPanel.svelte";
38
89
  </script>
39
90
 
40
- <RealtimeManager endpoint="/api/events" channel={myChannel}>
41
- <RealtimeBody />
91
+ <RealtimeManager endpoint="/api/events" channel={ordersChannel}>
92
+ <OrdersPanel />
42
93
  </RealtimeManager>
43
94
  ```
44
95
 
96
+ ### 4. Read topic state in a child component
97
+
45
98
  ```svelte
46
- <!-- RealtimeBody.svelte -->
99
+ <!-- src/routes/OrdersPanel.svelte -->
47
100
  <script lang="ts">
48
101
  import { getRealtimeState, getRealtimeTopicState } from "@novadxhq/sveltekit-inngest";
49
- import { myChannel } from "$lib/channels";
102
+ import { ordersChannel } from "$lib/realtime/orders-channel";
50
103
 
51
104
  const { health } = getRealtimeState();
52
- const orderUpdates = getRealtimeTopicState<typeof myChannel, "orders.updated">(
105
+ const orderUpdated = getRealtimeTopicState<typeof ordersChannel, "orders.updated">(
53
106
  "orders.updated"
54
107
  );
55
108
  </script>
@@ -58,169 +111,127 @@ Peer dependencies:
58
111
  {#if health.current}
59
112
  {health.current.ok ? "Connected" : "Degraded"} ({health.current.status})
60
113
  {:else}
61
- Waiting for connection...
114
+ Connecting...
62
115
  {/if}
63
116
  </p>
64
117
 
65
- <pre>{JSON.stringify(orderUpdates.current, null, 2)}</pre>
66
- ```
67
-
68
- By default, topic helpers return the full Inngest envelope, not only `data`, so
69
- metadata like `runId`, `createdAt`, `envId`, `kind`, and `fnId` are available.
70
- `createdAt` is a string on the client because SSE payloads are JSON-parsed.
71
-
72
- ```svelte
73
- <!-- AppRealtime.svelte -->
74
- <script lang="ts">
75
- import type { Snippet } from "svelte";
76
- import { RealtimeManager } from "@novadxhq/sveltekit-inngest";
77
- import { myChannel } from "$lib/channels";
78
-
79
- let { children }: { children?: Snippet } = $props();
80
- </script>
81
-
82
- <RealtimeManager endpoint="/api/events" channel={myChannel}>
83
- {@render children?.()}
84
- </RealtimeManager>
118
+ {#if orderUpdated.current}
119
+ <pre>{JSON.stringify(orderUpdated.current, null, 2)}</pre>
120
+ {/if}
85
121
  ```
86
122
 
87
- ## Server usage (`+server.ts`)
123
+ ## Client API
88
124
 
89
- ```ts
90
- import {createRealtimeEndpoint} from "@novadxhq/sveltekit-inngest/server";
91
- import {inngest} from "$lib/server/inngest";
92
- import {myChannel} from "$lib/channels";
125
+ ### `RealtimeManager`
93
126
 
94
- type LocalsWithPermissions = {
95
- permissions?: {
96
- has(permission: string): boolean;
97
- };
98
- };
127
+ Wraps children and provides realtime context to descendant components.
99
128
 
100
- export const POST = createRealtimeEndpoint({
101
- inngest,
102
- channel: myChannel,
103
- healthCheck: {
104
- // Configure periodic health ticks (for example, every 1000ms)
105
- intervalMs: 1_000,
106
- },
107
- // Optional: derive channel builder args from the full RequestEvent
108
- channelArgs: (event) => [event.params.workspaceId],
109
- authorize: ({event, locals, topics, params}) => {
110
- // You can use all standard +server.ts POST event fields here:
111
- // event.url, event.params, event.cookies, event.fetch, event.getClientAddress, event.setHeaders, etc.
112
- event.setHeaders({"x-realtime-route": "events"});
129
+ Common props:
113
130
 
114
- const canUse =
115
- (locals as LocalsWithPermissions).permissions?.has("can_use") ?? false;
131
+ - `endpoint`: SSE endpoint path (default: `"/api/events"`).
132
+ - `channel`: `Realtime.Channel` or channel definition.
133
+ - `channelArgs`: args for channel definitions that are factories.
134
+ - `topics`: optional subset of topic names.
135
+ - `params`: optional metadata sent to server authorize logic.
116
136
 
117
- if (!canUse) return false;
137
+ ### `getRealtimeState()`
118
138
 
119
- // Optional: filter to a subset of requested topics.
120
- if (params?.scope === "limited") {
121
- return {
122
- allowedTopics: topics.filter((topic) => topic === "orders.updated"),
123
- };
124
- }
139
+ Returns state-first manager context:
125
140
 
126
- return true;
127
- },
128
- });
129
- ```
141
+ - `health.current`: current health payload (`ok`, `status`, `ts`, optional `detail`).
142
+ - `channelId`, `topics`, `select` for lower-level usage.
130
143
 
131
- ## Endpoint contract
144
+ ### `getRealtimeTopicState(topic, options?)`
132
145
 
133
- - Route method: `POST`
134
- - Request body:
146
+ Returns a state wrapper (`.current`) for a topic stream.
135
147
 
136
- ```json
137
- {
138
- "channel": "your-channel-id",
139
- "topics": ["topic.name"],
140
- "params": {
141
- "scope": "limited"
142
- }
143
- }
144
- ```
148
+ By default, `.current` is the **full Inngest message envelope**, including fields like:
145
149
 
146
- - SSE event names:
147
- - `message`: JSON realtime messages
148
- - `health`: `{ ok, status, ts, detail? }`
149
- - Unauthorized requests return `403` JSON and do not start SSE.
150
- - Set `healthCheck.intervalMs` in `createRealtimeEndpoint(...)` to configure how often health ticks are emitted.
150
+ - `topic`
151
+ - `data`
152
+ - `runId`
153
+ - `createdAt`
154
+ - `kind`
155
+ - `envId`
156
+ - `fnId`
151
157
 
152
- ## Health model
158
+ `createdAt` is a string on the client because SSE payloads are JSON-parsed.
153
159
 
154
- `HealthPayload`:
160
+ If you only want `data`, map it:
155
161
 
156
162
  ```ts
157
- type HealthStatus = "connecting" | "connected" | "degraded";
158
- type HealthPayload = {
159
- ok: boolean;
160
- status: HealthStatus;
161
- ts: number;
162
- detail?: string;
163
- };
163
+ const payloadOnly = getRealtimeTopicState<
164
+ typeof ordersChannel,
165
+ "orders.updated",
166
+ { orderId: string; status: string }
167
+ >("orders.updated", {
168
+ map: (message) => message.data,
169
+ });
164
170
  ```
165
171
 
166
- ## Compatibility APIs
172
+ ## Server API
167
173
 
168
- If you prefer store-returning helpers, these are still available:
174
+ ### `createRealtimeEndpoint(options)`
169
175
 
170
- - `getRealtime()` (returns `health` as a `Readable` store)
171
- - `getRealtimeTopicJson()` (returns full topic envelope as a `Readable` store)
176
+ Creates a SvelteKit `RequestHandler` for `POST` SSE.
172
177
 
173
- If you want the previous data-only behavior, project it with `map`:
178
+ Key options:
174
179
 
175
- ```ts
176
- const payloadOnly = getRealtimeTopicState<typeof myChannel, "orders.updated", { id: string }>(
177
- "orders.updated",
178
- {
179
- map: (message) => message.data,
180
- }
181
- );
182
- ```
180
+ - `inngest`: your Inngest client instance.
181
+ - `channel`: channel object or channel definition.
182
+ - `channelArgs`: optional static array or resolver `(event) => unknown[]`.
183
+ - `healthCheck`: heartbeat control (`intervalMs`, `enabled`).
184
+ - `authorize`: access control callback per request.
183
185
 
184
- ## Publishing
186
+ `authorize` receives:
185
187
 
186
- ### 1. Auth setup
188
+ - `event`: full SvelteKit `RequestEvent`.
189
+ - `locals`, `request`, `channelId`, `topics`, `params`.
187
190
 
188
- - npmjs token: create an npm automation token with publish rights for the `@novadxhq` scope.
189
- - GitHub Packages token: create a GitHub token with `write:packages` and `read:packages` for the `novadxhq` org.
191
+ `authorize` return values:
190
192
 
191
- Use local user-level config (do not commit tokens):
193
+ - `true`: allow requested topics.
194
+ - `false`: deny request (`403` JSON).
195
+ - `{ allowedTopics }`: allow only a subset of requested topics.
192
196
 
193
- ```sh
194
- npm config set //registry.npmjs.org/:_authToken <NPM_TOKEN>
195
- npm config set //npm.pkg.github.com/:_authToken <GITHUB_TOKEN>
196
- ```
197
+ ## Behavior and Contracts
197
198
 
198
- ### 2. Verify package output locally
199
+ - Endpoint method: `POST`.
200
+ - Request body:
199
201
 
200
- ```sh
201
- npm run release:verify
202
+ ```json
203
+ {
204
+ "channel": "orders",
205
+ "topics": ["orders.updated"],
206
+ "params": {
207
+ "scope": "limited"
208
+ }
209
+ }
202
210
  ```
203
211
 
204
- ### 3. Publish
212
+ - SSE events emitted by the endpoint:
213
+ - `message`: realtime message payloads (JSON stringified).
214
+ - `health`: connection health payloads (JSON stringified).
215
+ - Unauthorized requests return `403` JSON and no SSE stream.
216
+ - Health emits `connecting`, then `connected`, and `degraded` on failures.
217
+ - Heartbeat cadence is configurable via `healthCheck.intervalMs`.
205
218
 
206
- ```sh
207
- # npmjs
208
- npm run publish:npm
209
-
210
- # GitHub Packages
211
- npm run publish:gpr
219
+ ## Compatibility APIs (Concise)
212
220
 
213
- # or both (sequential)
214
- npm run publish:both
215
- ```
221
+ These alternatives are still available if you prefer store-returning helpers:
216
222
 
217
- ### 4. Dry-run checks
223
+ - `getRealtime()` -> returns context with `health` as a `Readable`.
224
+ - `getRealtimeTopicJson()` -> returns topic stream as a `Readable`.
218
225
 
219
- ```sh
220
- npm publish --dry-run --registry https://registry.npmjs.org/ --cache /tmp/novadx-npm-cache
221
- npm publish --dry-run --registry https://npm.pkg.github.com/ --cache /tmp/novadx-npm-cache
222
- ```
226
+ The recommended Svelte 5 path is `getRealtimeState()` and `getRealtimeTopicState()` with `.current`.
223
227
 
224
- ## Breaking rename
228
+ ## Troubleshooting
225
229
 
226
- This package scope changed from `@novadx/sveltekit-inngest` to `@novadxhq/sveltekit-inngest`.
230
+ - `getRealtimeState() requires <RealtimeManager>...`
231
+ - Ensure the component calling it is rendered under `<RealtimeManager>`.
232
+ - Endpoint returns `403`
233
+ - Check your `authorize` callback and `locals` auth state.
234
+ - No topic messages
235
+ - Confirm `channel` and topic names match your `@inngest/realtime` definitions.
236
+ - `createdAt` is not a `Date`
237
+ - It is a string after JSON parsing; convert it in UI when needed.
package/package.json CHANGED
@@ -1,18 +1,19 @@
1
1
  {
2
2
  "name": "@novadxhq/sveltekit-inngest",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
+ "license": "MIT",
4
5
  "scripts": {
5
- "dev": "vite dev",
6
- "build": "vite build && npm run prepack",
7
- "preview": "vite preview",
8
- "prepare": "svelte-kit sync || echo ''",
9
- "prepack": "svelte-kit sync && svelte-package && publint",
10
- "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
11
- "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
12
- "release:verify": "npm run check && npm run build && npm pack --dry-run --cache /tmp/novadx-npm-cache",
13
- "publish:npm": "npm publish --access public --registry https://registry.npmjs.org/ --cache /tmp/novadx-npm-cache",
6
+ "dev": "bunx vite dev",
7
+ "build": "bunx vite build && bun run prepack",
8
+ "preview": "bunx vite preview",
9
+ "prepare": "bunx svelte-kit sync || echo ''",
10
+ "prepack": "bunx svelte-kit sync && bunx svelte-package && bunx publint",
11
+ "check": "bunx svelte-kit sync && bunx svelte-check --tsconfig ./tsconfig.json",
12
+ "check:watch": "bunx svelte-kit sync && bunx svelte-check --tsconfig ./tsconfig.json --watch",
13
+ "release:verify": "bun run check && bun run build && npm pack --dry-run --cache /tmp/novadx-npm-cache",
14
+ "publish:npm": "npm publish --access public",
14
15
  "publish:gpr": "npm publish --registry https://npm.pkg.github.com/ --cache /tmp/novadx-npm-cache",
15
- "publish:both": "npm run release:verify && npm run publish:npm && npm run publish:gpr"
16
+ "publish:both": "bun run release:verify && bun run publish:npm && bun run publish:gpr"
16
17
  },
17
18
  "files": [
18
19
  "dist/index.js",