lowlander 0.3.0 → 0.5.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.
Files changed (70) hide show
  1. package/README.md +125 -38
  2. package/build/client/client.d.ts +5 -1
  3. package/build/client/client.js +15 -4
  4. package/build/client/client.js.map +1 -1
  5. package/build/dashboard/client/main.d.ts +1 -0
  6. package/build/dashboard/client/main.js +623 -0
  7. package/build/dashboard/client/main.js.map +1 -0
  8. package/build/dashboard/client/shim-server.d.ts +3 -0
  9. package/build/dashboard/client/shim-server.js +2 -0
  10. package/build/dashboard/client/shim-server.js.map +1 -0
  11. package/build/dashboard/dashboard.html +20 -0
  12. package/build/dashboard/index.d.ts +18 -0
  13. package/build/dashboard/index.d.ts.map +1 -0
  14. package/build/dashboard/index.js +50 -0
  15. package/build/dashboard/index.js.map +1 -0
  16. package/build/dashboard/serve.d.ts +18 -0
  17. package/build/dashboard/serve.d.ts.map +1 -0
  18. package/build/dashboard/serve.js +53 -0
  19. package/build/dashboard/serve.js.map +1 -0
  20. package/build/dashboard/server.d.ts +82 -0
  21. package/build/dashboard/server.d.ts.map +1 -0
  22. package/build/dashboard/server.js +248 -0
  23. package/build/dashboard/server.js.map +1 -0
  24. package/build/examples/helloworld/.edinburgh/commit_worker.log +3162 -0
  25. package/build/examples/helloworld/.edinburgh/data.mdb +0 -0
  26. package/build/examples/helloworld/.edinburgh/lock.mdb +0 -0
  27. package/build/examples/helloworld/client/index.html +1 -1
  28. package/build/examples/helloworld/client/js/base.css +1 -0
  29. package/build/examples/helloworld/client/js/base.js +217 -71
  30. package/build/examples/helloworld/client/js/base.js.map +1 -1
  31. package/build/examples/helloworld/server/api.d.ts +37 -26
  32. package/build/examples/helloworld/server/api.d.ts.map +1 -1
  33. package/build/examples/helloworld/server/api.js +38 -10
  34. package/build/examples/helloworld/server/api.js.map +1 -1
  35. package/build/examples/helloworld/server/main.d.ts +1 -1
  36. package/build/examples/helloworld/server/main.d.ts.map +1 -1
  37. package/build/examples/helloworld/server/main.js +6 -17
  38. package/build/examples/helloworld/server/main.js.map +1 -1
  39. package/build/server/password.d.ts +10 -0
  40. package/build/server/password.d.ts.map +1 -0
  41. package/build/server/password.js +38 -0
  42. package/build/server/password.js.map +1 -0
  43. package/build/server/protocol.d.ts +1 -0
  44. package/build/server/protocol.d.ts.map +1 -1
  45. package/build/server/protocol.js +1 -0
  46. package/build/server/protocol.js.map +1 -1
  47. package/build/server/server.d.ts +9 -9
  48. package/build/server/server.d.ts.map +1 -1
  49. package/build/server/server.js +21 -2
  50. package/build/server/server.js.map +1 -1
  51. package/build/server/wshandler.d.ts +7 -1
  52. package/build/server/wshandler.d.ts.map +1 -1
  53. package/build/server/wshandler.js +62 -14
  54. package/build/server/wshandler.js.map +1 -1
  55. package/build/tsconfig.client.tsbuildinfo +1 -1
  56. package/build/tsconfig.server.tsbuildinfo +1 -1
  57. package/client/client.ts +20 -6
  58. package/dashboard/build-bundle.ts +38 -0
  59. package/dashboard/client/index.html +12 -0
  60. package/dashboard/client/main.ts +566 -0
  61. package/dashboard/client/shim-server.ts +5 -0
  62. package/dashboard/index.ts +49 -0
  63. package/dashboard/server.ts +255 -0
  64. package/package.json +22 -11
  65. package/server/protocol.ts +1 -0
  66. package/server/server.ts +53 -17
  67. package/server/wshandler.ts +66 -13
  68. package/skill/SKILL.md +85 -4
  69. package/skill/createStreamType.md +1 -1
  70. package/skill/getStreamTypesForModel.md +7 -0
package/skill/SKILL.md CHANGED
@@ -25,17 +25,25 @@ This library is built on top of a number of libraries by the same author:
25
25
 
26
26
  Lowlander glues these together and adds real-time partial data synchronization and type-safe RPCs to provide a framework for rapidly building performant full-stack (database included!) web applications.
27
27
 
28
+ ## Example project
29
+
30
+ An example project is included in `examples/helloworld`. To run it:
31
+
32
+ ```bash
33
+ npm run example
34
+ ```
35
+
36
+ Opens at http://localhost:8080 with the Aberdeen dashboard at http://localhost:8080/_dashboard (password printed to console on start).
37
+
28
38
  ## Tutorial
29
39
 
30
40
  ### Project Setup
31
41
 
32
42
  ```bash
33
- bun init
34
- bun add lowlander aberdeen edinburgh
43
+ npm init
44
+ npm add lowlander aberdeen edinburgh
35
45
  ```
36
46
 
37
- (npm should also work for all of this.)
38
-
39
47
  Create the project structure:
40
48
 
41
49
  ```
@@ -179,6 +187,20 @@ export async function authenticate(token: string) {
179
187
 
180
188
  The client receives `'secret-value'` as `.value` and can call `UserAPI` methods via `.serverProxy`.
181
189
 
190
+ You can also pass a stream type instance as the value — the client's `.value` will then be reactive and update live whenever the model changes:
191
+
192
+ ```ts
193
+ const PersonStream = createStreamType(Person, { name: true, age: true });
194
+
195
+ export async function authenticate(token: string) {
196
+ const user = Person.getBy('name', token);
197
+ if (!user) throw new Error('User not found');
198
+ return new ServerProxy(new UserAPI(token), new PersonStream(user));
199
+ }
200
+ ```
201
+
202
+ The client gets both `.serverProxy` (for calling `UserAPI` methods) and a live-updating `.value`.
203
+
182
204
  When a proxy is dropped, because the request's Aberdeen scope was destroyed or the WebSocket disconnected, Lowlander calls `onDrop()` on the API object if it exists, letting you clean up server-side state.
183
205
 
184
206
  ```ts
@@ -295,6 +317,25 @@ Reconnection is automatic with exponential backoff.
295
317
 
296
318
  Aberdeen's `clean()` handles RPC lifecycle. When a reactive scope is destroyed, active requests and subscriptions are cancelled automatically.
297
319
 
320
+
321
+ #### Named Client-Side Types
322
+
323
+ Use `ClientProxyObject<T>` to get the fully-typed client API shape, which is useful for deriving types from stream methods without duplicating field selections:
324
+
325
+ ```ts
326
+ import type { ClientProxyObject } from 'lowlander/client';
327
+ import type * as API from './server/api.js';
328
+
329
+ type APIClient = ClientProxyObject<typeof API>;
330
+ const api: APIClient = new Connection<typeof API>('ws://localhost:8080/').api;
331
+
332
+ type SomethingType = ReturnType<APIClient['streamSomething']>;
333
+ const something: SomethingType = api.streamSomething();
334
+ ```
335
+
336
+ `ClientProxyObject` maps server return types to their client-side equivalents. Stream methods return `PromiseProxy<ProjectedData>`, plain values return `PromiseProxy<T>`, and `ServerProxy<API, R>` methods return a proxy with a `.serverProxy` of type `ClientProxyObject<SubAPI>`.
337
+
338
+
298
339
  ### Logging
299
340
 
300
341
  Set the `LOWLANDER_LOG_LEVEL` environment variable to a number from 0 to 3:
@@ -306,10 +347,50 @@ Set the `LOWLANDER_LOG_LEVEL` environment variable to a number from 0 to 3:
306
347
 
307
348
  Set `EDINBURGH_LOG_LEVEL` similarly for Edinburgh internals.
308
349
 
350
+ ### Dashboard
351
+
352
+ Lowlander ships with an optional admin/developer dashboard for inspecting
353
+ Edinburgh models, browsing index rows, listing RPC methods, viewing source
354
+ code, and peeking at warpsocket debug state (channels, sockets, workers,
355
+ KV). It's a single self-contained HTML bundle.
356
+
357
+ To enable it:
358
+
359
+ 1. Re-export `_dashboard` from your top-level API module:
360
+
361
+ ```ts
362
+ // server/api.ts
363
+ export { _dashboard } from "lowlander/dashboard";
364
+ ```
365
+
366
+ 2. Serve the bundled HTML by calling `serveDashboard(res)` from a
367
+ `warpsocket` `handleHttpRequest` export:
368
+
369
+ ```ts
370
+ import type { HttpRequest, HttpResponse } from "warpsocket";
371
+ import { serveDashboard } from "lowlander/dashboard";
372
+
373
+ export function handleHttpRequest(req: HttpRequest, res: HttpResponse) {
374
+ if (req.url === '/_dashboard' || req.url.startsWith('/_dashboard?')) {
375
+ return serveDashboard(res);
376
+ }
377
+ // … serve your own static files …
378
+ }
379
+ ```
380
+
381
+ 3. On first server start (per warpsocket KV namespace), a random password
382
+ is generated and printed to the console. Override with the
383
+ `LOWLANDER_DASHBOARD_PASSWORD` env var.
384
+
385
+ The dashboard prompts for the websocket URL (defaults to the current host)
386
+ and password on first load, then stores them in localStorage.
387
+
309
388
  ## Server API Reference
310
389
 
311
390
  The following is auto-generated from `server/server.ts`:
312
391
 
392
+ ### [getStreamTypesForModel](getStreamTypesForModel.md) · function
393
+
313
394
  ### [createStreamType](createStreamType.md) · function
314
395
 
315
396
  Creates a stream type for reactive model streaming to clients with automatic updates.
@@ -5,7 +5,7 @@ Creates a stream type for reactive model streaming to clients with automatic upd
5
5
  Specify which fields to include; when they change, updates are pushed to subscribed clients.
6
6
  Supports nested linked models and type-safe field selection.
7
7
 
8
- **Signature:** `<T, S extends FieldSelection<T>>(Model: (new () => any) & ModelClassRuntime<any, readonly any[], any, any> & (new (initial?: Partial<any>, txn?: Transaction) => any) & (new (...args: any[]) => T), selection: S & ValidateSelection<...>, options?: { ...; }) => typeof StreamType`
8
+ **Signature:** `<T, S extends FieldSelection<T>>(Model: object & ModelClassRuntime<any, readonly any[], any, any> & (new (initial?: Partial<any>, txn?: Transaction) => any) & (new (...args: any[]) => T), selection: S & ValidateSelection<...>, options?: { ...; }) => { ...; }`
9
9
 
10
10
  **Type Parameters:**
11
11
 
@@ -0,0 +1,7 @@
1
+ ### getStreamTypesForModel · function
2
+
3
+ **Signature:** `(Model: AnyModelClass) => readonly (typeof StreamTypeBase<unknown>)[]`
4
+
5
+ **Parameters:**
6
+
7
+ - `Model: E.AnyModelClass`