toiljs 0.0.32 → 0.0.34

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.
@@ -0,0 +1,40 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { box, tagline } from '../src/cli/ui';
4
+
5
+ describe('tagline', () => {
6
+ it('always yields a non-empty line, whichever variant is drawn', () => {
7
+ for (let i = 0; i < 100; i++) {
8
+ const t = tagline();
9
+ expect(t.length).toBeGreaterThan(0);
10
+ expect(t).not.toContain('undefined');
11
+ }
12
+ });
13
+ });
14
+
15
+ describe('box', () => {
16
+ it('frames lines in a rounded box sized to the widest line', () => {
17
+ expect(box(['hello', 'hi'])).toBe(
18
+ [
19
+ ' ╭─────────╮',
20
+ ' │ hello │',
21
+ ' │ hi │',
22
+ ' ╰─────────╯',
23
+ ].join('\n'),
24
+ );
25
+ });
26
+
27
+ it('pads on visible width, ignoring ANSI color codes', () => {
28
+ const colored = '\x1b[1mhello\x1b[22m';
29
+ const lines = box([colored, 'hi']).split('\n');
30
+ // Both content rows must end at the same column once colors are stripped.
31
+ const stripped = lines.map((l) => l.replace(/\x1b\[[0-9;]*m/g, ''));
32
+ expect(new Set(stripped.map((l) => l.length)).size).toBe(1);
33
+ });
34
+
35
+ it('paints only the border', () => {
36
+ const out = box(['hi'], (s) => `<${s}>`);
37
+ expect(out).toContain('<│> hi <│>');
38
+ expect(out).toContain('<╭──────╮>');
39
+ });
40
+ });
@@ -1,137 +0,0 @@
1
- // A small REST demo: a players list + a leaderboard.
2
- //
3
- // IMPORTANT - the server runs with a FRESH WebAssembly instance per request, so linear memory
4
- // (and any module-level state, like the `store` below) is reset on every request. It does NOT
5
- // persist across requests. We seed a few players at module init so the read routes always have
6
- // data; the write routes (create / addScore) take effect only for the current request's response.
7
- // For real persistence, call out to a database or KV store from your handler.
8
- //
9
- // `@data` types are the wire model (shared by HTTP and RPC). `@rest` controllers expose HTTP
10
- // routes; `@service`/`@remote` expose typed RPC. Building the server (toilscript --rpcModule)
11
- // regenerates the typed client into shared/server.ts:
12
- // @rest -> Server.REST.<controller>.<route>(args) (a working fetch client)
13
- // @service -> Server.<service>.<method>() (RPC, transport TODO)
14
-
15
- import { Response, RouteContext } from 'toiljs/server/runtime';
16
-
17
- /** A leaderboard player. */
18
- @data
19
- class Player {
20
- id: u64 = 0;
21
- name: string = '';
22
- score: i64 = 0;
23
- }
24
-
25
- /** Request body for `POST /players` - the fields a client supplies to create a player. */
26
- @data
27
- class NewPlayer {
28
- name: string = '';
29
- }
30
-
31
- /** Request body for `POST /players/:id/score` - points to add to a player's score. */
32
- @data
33
- class ScoreDelta {
34
- points: i64 = 0;
35
- }
36
-
37
- /** A leaderboard page. A `@data` wrapper so the `Player[]` round-trips through the codec. */
38
- @data
39
- class Standings {
40
- players: Player[] = [];
41
- }
42
-
43
- // Re-seeded on EVERY request - module memory does not persist across requests (see the note at
44
- // the top of the file). Swap this for a database/KV to keep data between requests.
45
- const store = new Map<u64, Player>();
46
- let nextId: u64 = 1;
47
-
48
- function seed(name: string, score: i64): void {
49
- const p = new Player();
50
- p.id = nextId++;
51
- p.name = name;
52
- p.score = score;
53
- store.set(p.id, p);
54
- }
55
- seed('Ada', 120);
56
- seed('Linus', 95);
57
- seed('Grace', 140);
58
-
59
- /**
60
- * Players, mounted at `/players`. On the client:
61
- * await Server.REST.players.get({ params: { id } })
62
- * await Server.REST.players.create({ body: new NewPlayer('Bob') })
63
- * await Server.REST.players.addScore({ params: { id }, body: new ScoreDelta(10n) })
64
- */
65
- @rest('players')
66
- class Players {
67
- /** `GET /players/:id` - returns a `Response` for full control: a real 404 for a missing id,
68
- * a custom header, and the `@data` body serialized with `toJSON()`. (The toilscript editor
69
- * plugin types the compiler-injected `toJSON()`, so this is clean; return the `@data` type
70
- * directly, like the other routes, when you do not need that control.) */
71
- @get('/:id')
72
- public get(ctx: RouteContext): Response {
73
- const id = u64.parse(ctx.param('id'));
74
- if (!store.has(id)) return Response.notFound();
75
- const p = store.get(id);
76
-
77
- return Response.json(p.toJSON().toString()).setHeader('cache-control', 'no-store');
78
- }
79
-
80
- /** `POST /players` - build a player from the request body and return it with a fresh id.
81
- * Note: it is NOT saved (memory resets next request); persist to a real store to keep it. */
82
- @post('/')
83
- public create(input: NewPlayer): Player {
84
- const p = new Player();
85
- p.id = nextId++;
86
- p.name = input.name;
87
- p.score = 0;
88
- store.set(p.id, p);
89
-
90
- return p;
91
- }
92
-
93
- /** `POST /players/:id/score` - add `points` (from the body) to the seeded player named by
94
- * `:id` and return it. The change applies to this response only (memory resets next request). */
95
- @post('/:id/score')
96
- public addScore(input: ScoreDelta, ctx: RouteContext): Player {
97
- const id = u64.parse(ctx.param('id'));
98
- if (!store.has(id)) return new Player();
99
- const p = store.get(id);
100
- p.score += input.points;
101
-
102
- return p;
103
- }
104
- }
105
-
106
- /**
107
- * The leaderboard, mounted at `/leaderboard`. On the client:
108
- * const board = await Server.REST.leaderboard.top(); // typed Standings { players: Player[] }
109
- */
110
- @rest('leaderboard')
111
- class Leaderboard {
112
- /** `GET /leaderboard` - the seeded players, highest score first. */
113
- @get('/')
114
- public top(): Standings {
115
- const board = new Standings();
116
- const all = store.values();
117
- for (let i = 0; i < all.length; i++) board.players.push(all[i]);
118
- board.players.sort((a: Player, b: Player): i32 => (a.score < b.score ? 1 : a.score > b.score ? -1 : 0));
119
- return board;
120
- }
121
- }
122
-
123
- /** Typed RPC service (transport still a TODO): reached as `Server.stats.playerCount()` on the client. */
124
- @service
125
- class Stats {
126
- /** Number of seeded players (the RPC transport is a TODO, so this throws on the client for now). */
127
- @remote
128
- public playerCount(): i32 {
129
- return store.size;
130
- }
131
- }
132
-
133
- /** A free `@remote` function: `Server.ping(n)` on the client. */
134
- @remote
135
- function ping(n: i32): i32 {
136
- return n + 1;
137
- }