@novadxhq/sveltekit-inngest 0.0.1 → 0.0.2
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/LICENSE +21 -0
- package/README.md +159 -148
- 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
|
|
3
|
+
Svelte 5 utilities for building typed realtime subscriptions in SvelteKit with Inngest and SSE.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## What This Library Provides
|
|
6
6
|
|
|
7
|
-
- `RealtimeManager`
|
|
8
|
-
- `getRealtimeState
|
|
9
|
-
- `
|
|
10
|
-
-
|
|
11
|
-
-
|
|
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
|
-
##
|
|
13
|
+
## Requirements
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
npm install @novadxhq/sveltekit-inngest
|
|
18
|
-
```
|
|
15
|
+
This package is for **Svelte 5 + SvelteKit** projects.
|
|
19
16
|
|
|
20
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
81
|
+
### 3. Wrap your UI with `RealtimeManager`
|
|
31
82
|
|
|
32
83
|
```svelte
|
|
33
|
-
<!--
|
|
84
|
+
<!-- src/routes/+page.svelte -->
|
|
34
85
|
<script lang="ts">
|
|
35
86
|
import { RealtimeManager } from "@novadxhq/sveltekit-inngest";
|
|
36
|
-
import {
|
|
37
|
-
import
|
|
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={
|
|
41
|
-
<
|
|
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
|
-
<!--
|
|
99
|
+
<!-- src/routes/OrdersPanel.svelte -->
|
|
47
100
|
<script lang="ts">
|
|
48
101
|
import { getRealtimeState, getRealtimeTopicState } from "@novadxhq/sveltekit-inngest";
|
|
49
|
-
import {
|
|
102
|
+
import { ordersChannel } from "$lib/realtime/orders-channel";
|
|
50
103
|
|
|
51
104
|
const { health } = getRealtimeState();
|
|
52
|
-
const
|
|
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
|
-
|
|
114
|
+
Connecting...
|
|
62
115
|
{/if}
|
|
63
116
|
</p>
|
|
64
117
|
|
|
65
|
-
|
|
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
|
-
##
|
|
123
|
+
## Client API
|
|
88
124
|
|
|
89
|
-
|
|
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
|
-
|
|
95
|
-
permissions?: {
|
|
96
|
-
has(permission: string): boolean;
|
|
97
|
-
};
|
|
98
|
-
};
|
|
127
|
+
Wraps children and provides realtime context to descendant components.
|
|
99
128
|
|
|
100
|
-
|
|
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
|
-
|
|
115
|
-
|
|
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
|
-
|
|
137
|
+
### `getRealtimeState()`
|
|
118
138
|
|
|
119
|
-
|
|
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
|
-
|
|
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
|
-
|
|
144
|
+
### `getRealtimeTopicState(topic, options?)`
|
|
132
145
|
|
|
133
|
-
|
|
134
|
-
- Request body:
|
|
146
|
+
Returns a state wrapper (`.current`) for a topic stream.
|
|
135
147
|
|
|
136
|
-
|
|
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
|
-
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
-
|
|
150
|
-
-
|
|
150
|
+
- `topic`
|
|
151
|
+
- `data`
|
|
152
|
+
- `runId`
|
|
153
|
+
- `createdAt`
|
|
154
|
+
- `kind`
|
|
155
|
+
- `envId`
|
|
156
|
+
- `fnId`
|
|
151
157
|
|
|
152
|
-
|
|
158
|
+
`createdAt` is a string on the client because SSE payloads are JSON-parsed.
|
|
153
159
|
|
|
154
|
-
`
|
|
160
|
+
If you only want `data`, map it:
|
|
155
161
|
|
|
156
162
|
```ts
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
status:
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
##
|
|
172
|
+
## Server API
|
|
167
173
|
|
|
168
|
-
|
|
174
|
+
### `createRealtimeEndpoint(options)`
|
|
169
175
|
|
|
170
|
-
|
|
171
|
-
- `getRealtimeTopicJson()` (returns full topic envelope as a `Readable` store)
|
|
176
|
+
Creates a SvelteKit `RequestHandler` for `POST` SSE.
|
|
172
177
|
|
|
173
|
-
|
|
178
|
+
Key options:
|
|
174
179
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
186
|
+
`authorize` receives:
|
|
185
187
|
|
|
186
|
-
|
|
188
|
+
- `event`: full SvelteKit `RequestEvent`.
|
|
189
|
+
- `locals`, `request`, `channelId`, `topics`, `params`.
|
|
187
190
|
|
|
188
|
-
|
|
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
|
-
|
|
193
|
+
- `true`: allow requested topics.
|
|
194
|
+
- `false`: deny request (`403` JSON).
|
|
195
|
+
- `{ allowedTopics }`: allow only a subset of requested topics.
|
|
192
196
|
|
|
193
|
-
|
|
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
|
-
|
|
199
|
+
- Endpoint method: `POST`.
|
|
200
|
+
- Request body:
|
|
199
201
|
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"channel": "orders",
|
|
205
|
+
"topics": ["orders.updated"],
|
|
206
|
+
"params": {
|
|
207
|
+
"scope": "limited"
|
|
208
|
+
}
|
|
209
|
+
}
|
|
202
210
|
```
|
|
203
211
|
|
|
204
|
-
|
|
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
|
-
|
|
207
|
-
# npmjs
|
|
208
|
-
npm run publish:npm
|
|
209
|
-
|
|
210
|
-
# GitHub Packages
|
|
211
|
-
npm run publish:gpr
|
|
219
|
+
## Compatibility APIs (Concise)
|
|
212
220
|
|
|
213
|
-
|
|
214
|
-
npm run publish:both
|
|
215
|
-
```
|
|
221
|
+
These alternatives are still available if you prefer store-returning helpers:
|
|
216
222
|
|
|
217
|
-
|
|
223
|
+
- `getRealtime()` -> returns context with `health` as a `Readable`.
|
|
224
|
+
- `getRealtimeTopicJson()` -> returns topic stream as a `Readable`.
|
|
218
225
|
|
|
219
|
-
|
|
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
|
-
##
|
|
228
|
+
## Troubleshooting
|
|
225
229
|
|
|
226
|
-
|
|
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.
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"license": "MIT",
|
|
4
5
|
"scripts": {
|
|
5
|
-
"dev": "vite dev",
|
|
6
|
-
"build": "vite build &&
|
|
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": "
|
|
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",
|
|
13
14
|
"publish:npm": "npm publish --access public --registry https://registry.npmjs.org/ --cache /tmp/novadx-npm-cache",
|
|
14
15
|
"publish:gpr": "npm publish --registry https://npm.pkg.github.com/ --cache /tmp/novadx-npm-cache",
|
|
15
|
-
"publish:both": "
|
|
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",
|
|
@@ -79,4 +80,4 @@
|
|
|
79
80
|
"dependencies": {
|
|
80
81
|
"zod": "^4.3.6"
|
|
81
82
|
}
|
|
82
|
-
}
|
|
83
|
+
}
|