create-seiro 0.1.6 → 0.1.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-seiro",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Scaffold a new Seiro project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -7,9 +7,9 @@ CQRS over WebSocket with Bun + Preact Signals + Web Components.
7
7
  ```
8
8
  ← { profile } sent on connect (User or null)
9
9
 
10
- → { cmd, cid, data } command request
11
- ← { cid, result } command success
12
- ← { cid, err } command error
10
+ → { cmd, cid, data, ack? } command request (ack=true requests response)
11
+ ← { cid, result } command success (only if ack was true)
12
+ ← { cid, err } command error (always sent on failure)
13
13
 
14
14
  → { q, id, params } query request
15
15
  ← { id, row } query row (repeated)
@@ -22,6 +22,8 @@ CQRS over WebSocket with Bun + Preact Signals + Web Components.
22
22
  → { unsub: "pattern" } unsubscribe
23
23
  ```
24
24
 
25
+ The `ack` flag controls whether the server sends a success response. When `ack: true`, the server sends `{ cid, result }` on completion. When `ack` is absent or false, no success response is sent (fire-and-forget). Error responses are always sent regardless of `ack`.
26
+
25
27
  ## Type Definitions
26
28
 
27
29
  Use `Command<D, R>` and `Query<P, R>` helpers:
@@ -93,31 +95,44 @@ export async function register<
93
95
  E extends EntityEvents,
94
96
  >(server: Server<C, Q, E>, sql: Sql, listener?: Sql) {
95
97
  // Listen to postgres notifications (if listener provided)
98
+ // The 3rd callback is called on connect AND reconnect - postgres.js handles reconnection internally
96
99
  if (listener) {
97
- await listener.listen("entity_created", (payload: string) => {
98
- try {
99
- server.emit("entity_created", JSON.parse(payload) as Entity);
100
- } catch (e) {
101
- console.error("Failed to parse entity_created payload:", payload, e);
102
- }
103
- });
104
-
105
- await listener.listen("entity_updated", (payload: string) => {
106
- try {
107
- server.emit("entity_updated", JSON.parse(payload) as Entity);
108
- } catch (e) {
109
- console.error("Failed to parse entity_updated payload:", payload, e);
110
- }
111
- });
100
+ await listener.listen(
101
+ "entity_created",
102
+ (payload: string) => {
103
+ try {
104
+ server.emit("entity_created", JSON.parse(payload) as Entity);
105
+ } catch (e) {
106
+ console.error("Failed to parse entity_created payload:", payload, e);
107
+ }
108
+ },
109
+ () => console.log("Listening on entity_created"),
110
+ );
111
+
112
+ await listener.listen(
113
+ "entity_updated",
114
+ (payload: string) => {
115
+ try {
116
+ server.emit("entity_updated", JSON.parse(payload) as Entity);
117
+ } catch (e) {
118
+ console.error("Failed to parse entity_updated payload:", payload, e);
119
+ }
120
+ },
121
+ () => console.log("Listening on entity_updated"),
122
+ );
112
123
 
113
124
  // Different payload type for delete - just the id
114
- await listener.listen("entity_deleted", (payload: string) => {
115
- try {
116
- server.emit("entity_deleted", JSON.parse(payload) as { id: number });
117
- } catch (e) {
118
- console.error("Failed to parse entity_deleted payload:", payload, e);
119
- }
120
- });
125
+ await listener.listen(
126
+ "entity_deleted",
127
+ (payload: string) => {
128
+ try {
129
+ server.emit("entity_deleted", JSON.parse(payload) as { id: number });
130
+ } catch (e) {
131
+ console.error("Failed to parse entity_deleted payload:", payload, e);
132
+ }
133
+ },
134
+ () => console.log("Listening on entity_deleted"),
135
+ );
121
136
  }
122
137
 
123
138
  // Command with typed result
@@ -163,12 +178,15 @@ client.subscribe();
163
178
  ## Client API
164
179
 
165
180
  ```typescript
166
- // Command with callbacks
181
+ // Command with callbacks (sends ack: true, server responds on success)
167
182
  client.cmd("entity.save", { id: 1, name: "Updated" }, {
168
183
  onSuccess: (result) => navigate(`#/entities/${result.id}`),
169
184
  onError: (err) => showError(err),
170
185
  });
171
186
 
187
+ // Fire-and-forget command (no callbacks = no ack, no success response)
188
+ client.cmd("analytics.track", { event: "page_view" });
189
+
172
190
  // Query (async iterator)
173
191
  for await (const row of client.query("entities.all")) {
174
192
  items.push(row);
@@ -357,6 +375,29 @@ server.query("logs.stream", async function* (params, ctx) {
357
375
  });
358
376
  ```
359
377
 
378
+ ## PostgreSQL Listener Reconnection
379
+
380
+ LISTEN blocks the connection - if it drops, you need to re-establish subscriptions. The postgres.js library handles this automatically via the `onlisten` callback (third parameter to `sql.listen()`):
381
+
382
+ ```typescript
383
+ await listener.listen(
384
+ "entity_created",
385
+ (payload: string) => {
386
+ // Handle notification
387
+ server.emit("entity_created", JSON.parse(payload) as Entity);
388
+ },
389
+ () => {
390
+ // Called on initial connect AND on reconnect
391
+ console.log("Listening on entity_created");
392
+ },
393
+ );
394
+ ```
395
+
396
+ The `onlisten` callback is useful for:
397
+ - Logging when listeners are active (helpful for debugging)
398
+ - Fetching any missed data after reconnection
399
+ - Re-syncing state with the database
400
+
360
401
  ## Conventions
361
402
 
362
403
  - Commands return `{ id }` for create/save operations
@@ -10,7 +10,7 @@
10
10
  "test": "bun test server.test.ts"
11
11
  },
12
12
  "dependencies": {
13
- "seiro": "^0.1.6",
13
+ "seiro": "^0.1.8",
14
14
  "@preact/signals-core": "^1.12.2",
15
15
  "postgres": "^3.4.8"
16
16
  },