@velarscript/cli 0.18.0 → 0.19.0

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/dist/version.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export declare const VELAR_VERSION = "0.18.0";
1
+ export declare const VELAR_VERSION = "0.19.0";
2
2
  export { VELAR_STANDARD_API_VERSION } from "@velarscript/core";
3
3
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
- export const VELAR_VERSION = "0.18.0";
1
+ export const VELAR_VERSION = "0.19.0";
2
2
  export { VELAR_STANDARD_API_VERSION } from "@velarscript/core";
3
3
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velarscript/cli",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "The VelarScript command-line compiler, dev server, test runner, and language server.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -30,13 +30,13 @@
30
30
  "velar": "./dist/cli.js"
31
31
  },
32
32
  "dependencies": {
33
- "@velarscript/compiler": "0.18.0",
34
- "@velarscript/core": "0.18.0",
35
- "@velarscript/desktop": "0.18.0",
36
- "@velarscript/node": "0.18.0",
37
- "@velarscript/server": "0.18.0",
38
- "@velarscript/web": "0.18.0",
39
- "create-velar": "0.18.0",
33
+ "@velarscript/compiler": "0.19.0",
34
+ "@velarscript/core": "0.19.0",
35
+ "@velarscript/desktop": "0.19.0",
36
+ "@velarscript/node": "0.19.0",
37
+ "@velarscript/server": "0.19.0",
38
+ "@velarscript/web": "0.19.0",
39
+ "create-velar": "0.19.0",
40
40
  "esbuild": "^0.28.1",
41
41
  "playwright": "^1.58.2"
42
42
  }
@@ -337,6 +337,11 @@ canonical HTTP/HTTPS origins. The default rejects any upgrade carrying
337
337
  intentional unrestricted policy. A rejected browser origin receives 403 before
338
338
  it consumes connection-queue capacity. Connections are pull-based. Always
339
339
  consume each connection's `next()`, handle backpressure, and stop the server.
340
+ `closeInfo()` waits for the terminal handshake and returns the actual typed
341
+ `WebSocketClose {code, reason}`; `next()` returning `null` alone does not expose
342
+ why the connection ended. Application sessions should normally use
343
+ server-target `velar/realtime.realtimeSession` instead of rebuilding their own writer and
344
+ cleanup loops over this transport.
340
345
 
341
346
  ## Tests
342
347
 
@@ -13,7 +13,7 @@ browser application activates `@velarscript/web`:
13
13
  ```json
14
14
  {
15
15
  "dependencies": {
16
- "@velarscript/server": "0.18.0"
16
+ "@velarscript/server": "0.19.0"
17
17
  }
18
18
  }
19
19
  ```
@@ -177,6 +177,64 @@ dialect behavior, retry rules, and credentials remain independently installed
177
177
  application dependencies. Do not add SQLite, PostgreSQL, an ORM, or model
178
178
  syntax to `@velarscript/server` or `@velarscript/node`.
179
179
 
180
+ ## Typed realtime sessions
181
+
182
+ Keep the physical socket and the application session separate. A declarative
183
+ `@websocket` route receives `WebSocketConnection`; pass it to
184
+ `realtimeSession` with the codec from the application's shared protocol
185
+ package:
186
+
187
+ ```velar fragment
188
+ import {Bytes} from "velar/binary"
189
+ import {RealtimeFailure, RealtimeFailureAction, RealtimePeer, realtimeSession} from "velar/realtime"
190
+ import {WebSocketConnection} from "velar/websocket"
191
+
192
+ type Command:
193
+ operation: string
194
+
195
+ type ServerEvent:
196
+ event: string
197
+
198
+ def decode(message: string | Bytes) -> Command:
199
+ if message is string: return Json.parse(message, Command)
200
+ throw Error("Binary commands are not supported")
201
+
202
+ def encode(event: ServerEvent) -> string | Bytes:
203
+ return Json.stringify(event)
204
+
205
+ async def receive(command: Command, peer: RealtimePeer<ServerEvent>):
206
+ await peer.send({event: command.operation})
207
+
208
+ async def failed(failure: RealtimeFailure, _peer: RealtimePeer<ServerEvent>):
209
+ print(failure.error)
210
+ return RealtimeFailureAction.close
211
+
212
+ async def serveSession(connection: WebSocketConnection):
213
+ await realtimeSession(
214
+ connection,
215
+ {decode, encode},
216
+ receive,
217
+ failed=failed,
218
+ options={maxQueuedMessages: 64, maxQueuedBytes: 1_048_576, drainTimeout: 5s},
219
+ )
220
+ ```
221
+
222
+ The session runs one sequential inbound handler and one writer over a bounded
223
+ outbound mailbox. `peer.send(event)` waits for its own transport send;
224
+ `peer.trySend(event)` returns `false` instead of waiting when the mailbox is
225
+ full. `opened` may install a subscription and return an async cleanup function;
226
+ cleanup runs exactly once before the writer finishes draining. `closed`
227
+ receives the actual `WebSocketClose` code and reason.
228
+
229
+ `RealtimeFailureAction.continue` skips one failed decode or command and keeps
230
+ the session alive. Unrecoverable setup, transport, encode, and send failures
231
+ close the session. Route authentication, authorization, room membership,
232
+ subscription ownership, and application delivery semantics remain application
233
+ policy. Do not report a command as durable merely because `send` completed:
234
+ WebSocket is ordered but application delivery is still at-most-once unless the
235
+ shared protocol adds message IDs, acknowledgements, resume cursors, and
236
+ idempotent handling.
237
+
180
238
  ## Custom shared HTTP/WebSocket startup
181
239
 
182
240
  A service whose route table contains `@websocket` declarations may load the
@@ -153,6 +153,51 @@ property unconditionally to override the component's outright, and declare it
153
153
  under a condition to refine that condition alone and leave the component's
154
154
  other values standing.
155
155
 
156
+ ## Realtime client
157
+
158
+ `velar/websocket.connect` is the sole raw socket transport.
159
+ `velar/realtime.realtimeClient` is the application layer: a typed codec,
160
+ explicit lifecycle, finite reconnect policy, connection generations, and one
161
+ callback where subscriptions can be rebuilt after reconnect.
162
+
163
+ ```velar fragment
164
+ import {Bytes} from "velar/binary"
165
+ import {RealtimeClient, RealtimeClientFailureAction, RealtimeOpen, realtimeClient} from "velar/realtime"
166
+
167
+ type ServerEvent:
168
+ event: string
169
+
170
+ type Command:
171
+ operation: string
172
+
173
+ def decode(message: string | Bytes) -> ServerEvent:
174
+ if message is string: return Json.parse(message, ServerEvent)
175
+ throw Error("Binary events are not supported")
176
+
177
+ def encode(command: Command) -> string | Bytes: return Json.stringify(command)
178
+
179
+ async def opened(client: RealtimeClient<Command>, open: RealtimeOpen):
180
+ if open.reconnected: await client.send({operation: "resync"})
181
+
182
+ export def liveClient(url: string) -> RealtimeClient<Command>:
183
+ return realtimeClient(
184
+ url,
185
+ {decode, encode},
186
+ async (event, _client) => print(event.event),
187
+ opened=opened,
188
+ failed=async (_failure, _client) => RealtimeClientFailureAction.reconnect,
189
+ options={reconnectDelays: [0ms, 1s, 2s, 5s], reconnectJitter: 0.2},
190
+ )
191
+ ```
192
+
193
+ Call `await client.start()` from owned startup and `await client.close()` from
194
+ cleanup. `whenOpen()` waits for the current or next generation;
195
+ `whenClosed()` waits for terminal shutdown. The client does not queue or replay
196
+ commands across a disconnect. Put message IDs, acknowledgements, resume
197
+ cursors, and idempotency in the shared application protocol when stronger
198
+ delivery is required. A URL function may refresh a signed URL on every attempt.
199
+ Initial retry is off unless `retryInitial: true` is explicit.
200
+
156
201
  ## Storage and tests
157
202
 
158
203
  `velar/storage` stores JSON and validates on read. A generic type spelling is