lowlander 0.2.2 → 0.2.4

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/skill/SKILL.md CHANGED
@@ -134,6 +134,24 @@ export function streamPerson(name: string) {
134
134
 
135
135
  On the client, this returns a reactive Aberdeen proxy that updates live when server data changes.
136
136
 
137
+ ```ts
138
+ // Client-side
139
+ const person = api.streamPerson('Alice');
140
+ // person.value starts as undefined while loading, and
141
+ // then becomes a live-updating reactive proxy object of Alice's data
142
+ A.dump(person);
143
+ ```
144
+
145
+ Lowlander will keep `person.value` up-to-date as long as the Aberdeen scope containing `api.streamPerson` remains active. When the scope is destroyed, the stream subscription is automatically cancelled.
146
+
147
+ It's quite common for the same RPC call to be used to get the same stream multiple times in a short period; when navigating back and forth, or when navigating to a new page that requires some of the same data as the previous page. To optimize for this, `createStreamType` accepts an optional `cache` parameter (in seconds).
148
+
149
+ ```ts
150
+ const PersonStream = createStreamType(Person, fields, { cache: 30 }); // cache for 30s after going out of scope
151
+ ```
152
+
153
+ After a stream with caching goes out of scope, the server keeps it alive for that many seconds, so that if the same stream is requested again with the same parameters, it can be reused instantly without re-sending initial data or re-subscribing to updates. Cached stream rpcs also deduplicate within that time window, so if the same stream is requested multiple times while it's still active or cached, only one stream is created on the server and shared among all requests.
154
+
137
155
  ### ServerProxy for Stateful APIs
138
156
 
139
157
  Wrap a class instance to expose per-connection stateful methods:
@@ -285,90 +303,29 @@ Set `EDINBURGH_LOG_LEVEL` similarly for Edinburgh internals.
285
303
 
286
304
  The following is auto-generated from `server/server.ts`:
287
305
 
288
- ### createStreamType · function
306
+ ### [createStreamType](createStreamType.md) · function
289
307
 
290
308
  Creates a stream type for reactive model streaming to clients with automatic updates.
291
309
 
292
- Specify which fields to include; when they change, updates are pushed to subscribed clients.
293
- Supports nested linked models and type-safe field selection.
294
-
295
- **Signature:** `<T, S extends FieldSelection<T>>(Model: typeof E.Model<unknown> & (new (...args: any[]) => T), selection: S & ValidateSelection<T, S>) => typeof StreamType`
296
-
297
- **Type Parameters:**
298
-
299
- - `T`
300
- - `S extends FieldSelection<T>`
301
-
302
- **Parameters:**
303
-
304
- - `Model: ModelClass & (new (...args: any[]) => T)` - - The Edinburgh model class
305
- - `selection: S & ValidateSelection<T, S>` - - Field selection: `true` for simple fields, nested object for linked models
306
-
307
- **Returns:** Stream type class to instantiate in API functions
308
-
309
- **Examples:**
310
-
311
- ```ts
312
-
313
- ### sendModel · function
310
+ ### [sendModel](sendModel.md) · function
314
311
 
315
312
  Sends (updated) data for `model` to `target`.
316
313
  `target` is a virtual socket with a requestId+'d' user prefix, or a channel that subscribes such virtual sockets.
317
314
 
318
- **Signature:** `(target: number | Uint8Array<ArrayBufferLike> | number[], model: Model<any>, commitId: number, StreamType: typeof StreamTypeBase<any>, changed?: Change) => void`
319
-
320
- **Parameters:**
321
-
322
- - `target: Uint8Array | number | number[]`
323
- - `model: E.Model<any>`
324
- - `commitId: number`
325
- - `StreamType: typeof StreamTypeBase<any>`
326
- - `changed?: E.Change`
327
-
328
- ### pushModel · function
315
+ ### [pushModel](pushModel.md) · function
329
316
 
330
317
  Subscribes `target` to this model, and sends initial data.
331
318
  `target` is a virtual socket with a requestId+'d' user prefix, or a channel that subscribes such virtual sockets.
332
319
 
333
- **Signature:** `(target: number | Uint8Array<ArrayBufferLike> | number[], model: Model<any>, commitId: number, SubStreamType: typeof StreamTypeBase<any>, delta: number) => void`
334
-
335
- **Parameters:**
336
-
337
- - `target: number | Uint8Array | number[]`
338
- - `model: E.Model<any>`
339
- - `commitId: number`
340
- - `SubStreamType: typeof StreamTypeBase<any>`
341
- - `delta: number`
342
-
343
- ### start · function
320
+ ### [start](start.md) · function
344
321
 
345
322
  Starts the Lowlander WebSocket server.
346
323
 
347
- **Signature:** `(mainApiFile: string, opts?: { bind?: string; threads?: number; injectWarpSocket?: typeof import("/var/home/frank/projects/lowlander/node_modules/warpsocket/dist/src/index", { with: { "resolution-mode": "import" } }); }) => Promise<void>`
348
-
349
- **Parameters:**
350
-
351
- - `mainApiFile: string` - - Absolute path to the compiled API file exporting server functions
352
- - `opts: {bind?: string, threads?: number, injectWarpSocket?: typeof realWarpsocket}` (optional)
353
-
354
- **Examples:**
355
-
356
- ```ts
357
- import { start } from 'lowlander/server';
358
- import { fileURLToPath } from 'url';
359
- import { resolve, dirname } from 'path';
360
-
361
- const API_FILE = resolve(dirname(fileURLToPath(import.meta.url)), 'api.js');
362
- start(API_FILE, { bind: '0.0.0.0:8080' });
363
- ```
364
-
365
324
  ### logLevel · constant
366
325
 
367
326
  **Value:** `number`
368
327
 
369
- ### warpsocket · class
370
-
371
- **Type:** `typeof import("/var/home/frank/projects/lowlander/node_modules/warpsocket/dist/src/index", { with: { "resolution-mode": "import" } })`
328
+ ### [warpsocket](warpsocket.md) · class
372
329
 
373
330
  ### StreamTypeBase · abstract class
374
331
 
@@ -386,118 +343,55 @@ start(API_FILE, { bind: '0.0.0.0:8080' });
386
343
 
387
344
  **Type:** `number`
388
345
 
389
- #### streamTypeBase.toString · method
346
+ #### StreamTypeBase.cache · static property
390
347
 
391
- **Signature:** `() => string`
348
+ **Type:** `number`
392
349
 
393
- **Parameters:**
350
+ #### streamTypeBase.toString · method
394
351
 
352
+ **Signature:** `() => string`
395
353
 
396
- ### ServerProxy · class
354
+ ### [ServerProxy](ServerProxy.md) · class
397
355
 
398
356
  Wraps a server-side API object to create a stateful, type-safe proxy accessible from clients.
399
357
  Use for authentication, sessions, or any stateful context that persists across RPC calls.
400
358
 
401
- **Type Parameters:**
402
-
403
- - `API extends object`
404
- - `RETURN`
405
-
406
- **Examples:**
407
-
408
- ```ts
409
- export class UserAPI {
410
- constructor(public user: User) {}
411
- getSecret() { return this.user.secret; }
412
- }
413
-
414
- export async function authenticate(token: string) {
415
- const user = await validateToken(token);
416
- return new ServerProxy(new UserAPI(user), user.name);
417
- }
418
-
419
- // Client: auth.value is user name, auth.serverProxy.getSecret() calls UserAPI method
420
- ```
421
-
422
- **Constructor Parameters:**
423
-
424
- - `api`: - Server-side API object exposed to the client
425
- - `value`: - Value returned immediately to the client
426
-
427
359
  #### serverProxy.toString · method
428
360
 
429
361
  **Signature:** `() => string`
430
362
 
431
- **Parameters:**
432
-
433
-
434
- ### Socket · class
363
+ ### [Socket](Socket.md) · class
435
364
 
436
365
  Server-side socket for pushing data to a client. Server functions with `Socket<T>` parameters
437
366
  receive client callbacks on the client side.
438
367
 
439
- **Type Parameters:**
440
-
441
- - `T`
442
-
443
- **Examples:**
444
-
445
- ```ts
446
- // Server
447
- export function streamNumbers(socket: Socket<number>) {
448
- setInterval(() => {
449
- if (!socket.send(Math.random())) clearInterval(interval);
450
- }, 1000);
451
- }
452
-
453
- // Client
454
- api.streamNumbers(num => console.log(num));
455
- ```
456
-
457
- #### socket.send · method
368
+ #### [socket.send](Socket_send.md) · method
458
369
 
459
370
  Sends data to the client.
460
371
 
461
- **Signature:** `(data: T) => number`
462
-
463
- **Parameters:**
464
-
465
- - `data: T` - - Data to send (automatically serialized)
466
-
467
- **Returns:** `true` if sent, `false` if socket is closed
468
-
469
- #### socket.subscribe · method
470
-
471
- **Signature:** `(channel: Uint8Array<ArrayBufferLike>, delta?: number) => void`
472
-
473
- **Parameters:**
474
-
475
- - `channel: Uint8Array`
476
- - `delta: any` (optional)
372
+ #### [socket.subscribe](Socket_subscribe.md) · method
477
373
 
478
374
  #### socket.toString · method
479
375
 
480
376
  **Signature:** `() => string`
481
377
 
482
- **Parameters:**
483
-
484
-
485
378
  #### socket.[Symbol.for('nodejs.util.inspect.custom')] · method
486
379
 
487
380
  **Signature:** `() => string`
488
381
 
489
- **Parameters:**
490
-
491
-
492
382
  ## Client API Reference
493
383
 
494
384
  The following is auto-generated from `client/client.ts`:
495
385
 
496
- ### logLevel · variable
386
+ ### setLogLevel · function
497
387
 
498
- Set to 1-3 for increasing verbosity.
388
+ Set to 0-3 for increasing verbosity.
499
389
 
500
- **Value:** `number`
390
+ **Signature:** `(level: number) => void`
391
+
392
+ **Parameters:**
393
+
394
+ - `level: number`
501
395
 
502
396
  ### ClientProxyObject · type
503
397
 
@@ -507,42 +401,11 @@ Transforms server-side API objects to client-side proxy objects with type-safe R
507
401
  [K in keyof T]: ClientProxyFunction<T[K]>
508
402
  }`
509
403
 
510
- ### Connection · class
404
+ ### [Connection](Connection.md) · class
511
405
 
512
406
  WebSocket connection to a Lowlander server with type-safe RPC, automatic reconnection,
513
407
  and reactive updates.
514
408
 
515
- **Type Parameters:**
516
-
517
- - `T`
518
-
519
- **Examples:**
520
-
521
- ```ts
522
- import type * as API from './server/api.js';
523
- const conn = new Connection<typeof API>('ws://localhost:8080/');
524
-
525
- // Simple RPC - returns PromiseProxy
526
- const sum = conn.api.add(1, 2);
527
-
528
- // Server proxy for stateful APIs
529
- const auth = conn.api.authenticate('token');
530
- const secret = auth.serverProxy.getSecret();
531
-
532
- // Streaming with callbacks
533
- conn.api.streamData(data => console.log(data));
534
-
535
- // Use within Aberdeen reactive scopes
536
- $(() => {
537
- dump(conn.isOnline());
538
- dump(sum);
539
- });
540
- ```
541
-
542
- **Constructor Parameters:**
543
-
544
- - `url`: - WebSocket URL (e.g., 'ws://localhost:8080/'), or a fake WebSocket object for testing
545
-
546
409
  #### connection.ws · property
547
410
 
548
411
  **Type:** `WebSocket`
@@ -563,6 +426,10 @@ $(() => {
563
426
 
564
427
  **Type:** `ValueRef<boolean>`
565
428
 
429
+ #### connection.streamCache · property
430
+
431
+ **Type:** `Map<string, StreamCacheEntry>`
432
+
566
433
  #### connection.api · property
567
434
 
568
435
  Type-safe proxy to the server-side API. Methods return `PromiseProxy` objects
@@ -577,29 +444,29 @@ Returns the current connection status. Reactive in Aberdeen scopes.
577
444
 
578
445
  **Signature:** `() => boolean`
579
446
 
580
- **Parameters:**
581
-
582
-
583
447
  #### connection.connect · method
584
448
 
585
449
  **Signature:** `() => void`
586
450
 
587
- **Parameters:**
588
-
589
-
590
451
  #### connection.reconnect · method
591
452
 
592
453
  **Signature:** `() => void`
593
454
 
455
+ #### [connection.pruneCommitIds](Connection_pruneCommitIds.md) · method
456
+
457
+ #### connection.cancelRequest · method
458
+
459
+ **Signature:** `(request: ActiveRequest) => void`
460
+
594
461
  **Parameters:**
595
462
 
463
+ - `request: ActiveRequest`
596
464
 
597
- #### connection.pruneCommitIds · method
465
+ #### connection.startLinger · method
598
466
 
599
- **Signature:** `(request: ActiveRequest, maxCommitId: number) => void`
467
+ **Signature:** `(cached: StreamCacheEntry) => void`
600
468
 
601
469
  **Parameters:**
602
470
 
603
- - `request: ActiveRequest`
604
- - `maxCommitId: number`
471
+ - `cached: StreamCacheEntry`
605
472
 
@@ -0,0 +1,30 @@
1
+ ### ServerProxy · class
2
+
3
+ Wraps a server-side API object to create a stateful, type-safe proxy accessible from clients.
4
+ Use for authentication, sessions, or any stateful context that persists across RPC calls.
5
+
6
+ **Type Parameters:**
7
+
8
+ - `API extends object`
9
+ - `RETURN`
10
+
11
+ **Examples:**
12
+
13
+ ```ts
14
+ export class UserAPI {
15
+ constructor(public user: User) {}
16
+ getSecret() { return this.user.secret; }
17
+ }
18
+
19
+ export async function authenticate(token: string) {
20
+ const user = await validateToken(token);
21
+ return new ServerProxy(new UserAPI(user), user.name);
22
+ }
23
+
24
+ // Client: auth.value is user name, auth.serverProxy.getSecret() calls UserAPI method
25
+ ```
26
+
27
+ **Constructor Parameters:**
28
+
29
+ - `api`: - Server-side API object exposed to the client
30
+ - `value`: - Value returned immediately to the client
@@ -0,0 +1,22 @@
1
+ ### Socket · class
2
+
3
+ Server-side socket for pushing data to a client. Server functions with `Socket<T>` parameters
4
+ receive client callbacks on the client side.
5
+
6
+ **Type Parameters:**
7
+
8
+ - `T`
9
+
10
+ **Examples:**
11
+
12
+ ```ts
13
+ // Server
14
+ export function streamNumbers(socket: Socket<number>) {
15
+ setInterval(() => {
16
+ if (!socket.send(Math.random())) clearInterval(interval);
17
+ }, 1000);
18
+ }
19
+
20
+ // Client
21
+ api.streamNumbers(num => console.log(num));
22
+ ```
@@ -0,0 +1,11 @@
1
+ #### socket.send · method
2
+
3
+ Sends data to the client.
4
+
5
+ **Signature:** `(data: T) => number`
6
+
7
+ **Parameters:**
8
+
9
+ - `data: T` - - Data to send (automatically serialized)
10
+
11
+ **Returns:** `true` if sent, `false` if socket is closed
@@ -0,0 +1,8 @@
1
+ #### socket.subscribe · method
2
+
3
+ **Signature:** `(channel: Uint8Array<ArrayBufferLike>, delta?: number) => void`
4
+
5
+ **Parameters:**
6
+
7
+ - `channel: Uint8Array`
8
+ - `delta: any` (optional)
@@ -0,0 +1,45 @@
1
+ ### createStreamType · function
2
+
3
+ Creates a stream type for reactive model streaming to clients with automatic updates.
4
+
5
+ Specify which fields to include; when they change, updates are pushed to subscribed clients.
6
+ Supports nested linked models and type-safe field selection.
7
+
8
+ **Signature:** `<T, S extends FieldSelection<T>>(Model: typeof E.Model<unknown> & (new (...args: any[]) => T), selection: S & ValidateSelection<T, S>, options?: { cache?: number; }) => typeof StreamType`
9
+
10
+ **Type Parameters:**
11
+
12
+ - `T`
13
+ - `S extends FieldSelection<T>`
14
+
15
+ **Parameters:**
16
+
17
+ - `Model: ModelClass & (new (...args: any[]) => T)` - - The Edinburgh model class
18
+ - `selection: S & ValidateSelection<T, S>` - - Field selection: `true` for simple fields, nested object for linked models
19
+ - `options?: { cache?: number }` - - Optional settings
20
+
21
+ **Returns:** Stream type class to instantiate in API functions
22
+
23
+ **Examples:**
24
+
25
+ ```ts
26
+ ⁣@E.registerModel
27
+ class Person extends Model {
28
+ name = field(string);
29
+ age = field(number);
30
+ password = field(string);
31
+ friends = field(array(link(Person)));
32
+ }
33
+
34
+ // Exclude password, include friends' names; cache 30s
35
+ const PersonStream = createStreamType(Person, {
36
+ name: true,
37
+ age: true,
38
+ friends: { name: true }
39
+ }, { cache: 30 });
40
+
41
+ export function streamPerson() {
42
+ const person = Person.byName.get('Alice')!;
43
+ return new PersonStream(person);
44
+ }
45
+ ```
@@ -0,0 +1,14 @@
1
+ ### pushModel · function
2
+
3
+ Subscribes `target` to this model, and sends initial data.
4
+ `target` is a virtual socket with a requestId+'d' user prefix, or a channel that subscribes such virtual sockets.
5
+
6
+ **Signature:** `(target: number | Uint8Array<ArrayBufferLike> | number[], model: Model<any>, commitId: number, SubStreamType: typeof StreamTypeBase<any>, delta: number) => void`
7
+
8
+ **Parameters:**
9
+
10
+ - `target: number | Uint8Array | number[]`
11
+ - `model: E.Model<any>`
12
+ - `commitId: number`
13
+ - `SubStreamType: typeof StreamTypeBase<any>`
14
+ - `delta: number`
@@ -0,0 +1,14 @@
1
+ ### sendModel · function
2
+
3
+ Sends (updated) data for `model` to `target`.
4
+ `target` is a virtual socket with a requestId+'d' user prefix, or a channel that subscribes such virtual sockets.
5
+
6
+ **Signature:** `(target: number | Uint8Array<ArrayBufferLike> | number[], model: Model<any>, commitId: number, StreamType: typeof StreamTypeBase<any>, changed?: Change) => void`
7
+
8
+ **Parameters:**
9
+
10
+ - `target: Uint8Array | number | number[]`
11
+ - `model: E.Model<any>`
12
+ - `commitId: number`
13
+ - `StreamType: typeof StreamTypeBase<any>`
14
+ - `changed?: E.Change`
package/skill/start.md ADDED
@@ -0,0 +1,21 @@
1
+ ### start · function
2
+
3
+ Starts the Lowlander WebSocket server.
4
+
5
+ **Signature:** `(mainApiFile: string, opts?: { bind?: string; threads?: number; injectWarpSocket?: typeof import("/var/home/frank/projects/warpsocket/dist/src/index", { with: { "resolution-mode": "import" } }); }) => Promise<void>`
6
+
7
+ **Parameters:**
8
+
9
+ - `mainApiFile: string` - - Absolute path to the compiled API file exporting server functions
10
+ - `opts: {bind?: string, threads?: number, injectWarpSocket?: typeof realWarpsocket}` (optional)
11
+
12
+ **Examples:**
13
+
14
+ ```ts
15
+ import { start } from 'lowlander/server';
16
+ import { fileURLToPath } from 'url';
17
+ import { resolve, dirname } from 'path';
18
+
19
+ const API_FILE = resolve(dirname(fileURLToPath(import.meta.url)), 'api.js');
20
+ start(API_FILE, { bind: '0.0.0.0:8080' });
21
+ ```
@@ -0,0 +1,3 @@
1
+ ### warpsocket · class
2
+
3
+ **Type:** `typeof import("/var/home/frank/projects/warpsocket/dist/src/index", { with: { "resolution-mode": "import" } })`