toiljs 0.0.114 → 0.0.116
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/CHANGELOG.md +18 -0
- package/TYPESCRIPT_LAW.md +1946 -2073
- package/build/backend/.tsbuildinfo +1 -1
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +53 -14
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/devserver/.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/cli/create.ts +170 -25
- package/src/cli/features.ts +7 -3
- package/src/cli/index.ts +2 -2
package/src/cli/create.ts
CHANGED
|
@@ -38,7 +38,7 @@ import { run } from './proc.js';
|
|
|
38
38
|
import { accent, dim, version } from './ui.js';
|
|
39
39
|
import { isPackageManager, isValidName, resolveProjectDir } from './validate.js';
|
|
40
40
|
|
|
41
|
-
export type Template = 'app' | 'minimal';
|
|
41
|
+
export type Template = 'app' | 'minimal' | 'agent';
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* The `server/migrations/` README, scaffolded by `create` and ensured by `doctor`/`update`. ToilDB
|
|
@@ -298,11 +298,16 @@ function scaffold(
|
|
|
298
298
|
'server/migrations/README.md': MIGRATIONS_README,
|
|
299
299
|
};
|
|
300
300
|
|
|
301
|
-
// The `app` template's client UI + server are copied from examples/basic at runtime;
|
|
302
|
-
//
|
|
301
|
+
// The `app` template's client UI + server are copied from examples/basic at runtime; the
|
|
302
|
+
// `minimal` and `agent` templates ship an inline client + working server here. `agent` adds
|
|
303
|
+
// one correctly-structured @rest endpoint (a @data model + controller) and a home page that
|
|
304
|
+
// calls it, the smallest complete client-to-backend loop for an agent to build on.
|
|
303
305
|
if (template === 'minimal') {
|
|
304
306
|
Object.assign(files, minimalClient(name, features));
|
|
305
307
|
Object.assign(files, minimalServer());
|
|
308
|
+
} else if (template === 'agent') {
|
|
309
|
+
Object.assign(files, agentClient(name, features));
|
|
310
|
+
Object.assign(files, agentServer());
|
|
306
311
|
}
|
|
307
312
|
|
|
308
313
|
// Selected AI-assistant pointer files at the root (committed). The real docs are always seeded
|
|
@@ -363,6 +368,22 @@ declare namespace AuthService { const SESSION_COOKIE: string; const USER_COOKIE:
|
|
|
363
368
|
interface __ToilAuthUser {}
|
|
364
369
|
`;
|
|
365
370
|
|
|
371
|
+
/** The `server/README.md` shipped by the inline templates: the folder-conventions map. */
|
|
372
|
+
const SERVER_README =
|
|
373
|
+
'# server/\n\n' +
|
|
374
|
+
'Your ToilScript backend, compiled to a single WebAssembly module. One folder per concern:\n\n' +
|
|
375
|
+
'| Folder | What lives here |\n' +
|
|
376
|
+
'| --- | --- |\n' +
|
|
377
|
+
'| `main.ts` | The entry point: wires the handler and imports the surface modules. |\n' +
|
|
378
|
+
'| `core/` | The request handler and shared app logic (state, helpers). |\n' +
|
|
379
|
+
'| `models/` | `@data` classes, the typed wire model shared by HTTP and RPC. One type per file. |\n' +
|
|
380
|
+
'| `migrations/` | ToilDB schema migrations: a `<Type>.migration.ts` per evolving `@data` value type, holding the old shapes + the `@migrate` transform. |\n' +
|
|
381
|
+
'| `routes/` | `@rest` controllers (HTTP). One controller per file, named after its class. |\n' +
|
|
382
|
+
'| `services/` | `@service` classes and free `@remote` functions (typed RPC). |\n' +
|
|
383
|
+
'| `scheduled/` | Reserved for scheduled tasks (not shipped yet). |\n\n' +
|
|
384
|
+
'New decorated files are picked up automatically by `toiljs build`/`dev`; also add an import\n' +
|
|
385
|
+
'in `main.ts` so a direct `toilscript` run builds the same server.\n';
|
|
386
|
+
|
|
366
387
|
/**
|
|
367
388
|
* A minimal but working server for the `minimal` template (the `app` template copies
|
|
368
389
|
* examples/basic/server). Same folder conventions as the full starter, just fewer files:
|
|
@@ -406,25 +427,86 @@ function minimalServer(): Record<string, string> {
|
|
|
406
427
|
'export function abort(message: string, fileName: string, line: u32, column: u32): void {\n' +
|
|
407
428
|
' revertOnError(message, fileName, line, column);\n' +
|
|
408
429
|
'}\n',
|
|
409
|
-
'server/README.md':
|
|
410
|
-
'# server/\n\n' +
|
|
411
|
-
'Your ToilScript backend, compiled to a single WebAssembly module. One folder per concern:\n\n' +
|
|
412
|
-
'| Folder | What lives here |\n' +
|
|
413
|
-
'| --- | --- |\n' +
|
|
414
|
-
'| `main.ts` | The entry point: wires the handler and imports the surface modules. |\n' +
|
|
415
|
-
'| `core/` | The request handler and shared app logic (state, helpers). |\n' +
|
|
416
|
-
'| `models/` | `@data` classes, the typed wire model shared by HTTP and RPC. One type per file. |\n' +
|
|
417
|
-
'| `migrations/` | ToilDB schema migrations: a `<Type>.migration.ts` per evolving `@data` value type, holding the old shapes + the `@migrate` transform. |\n' +
|
|
418
|
-
'| `routes/` | `@rest` controllers (HTTP). One controller per file, named after its class. |\n' +
|
|
419
|
-
'| `services/` | `@service` classes and free `@remote` functions (typed RPC). |\n' +
|
|
420
|
-
'| `scheduled/` | Reserved for scheduled tasks (not shipped yet). |\n\n' +
|
|
421
|
-
'New decorated files are picked up automatically by `toiljs build`/`dev`; also add an import\n' +
|
|
422
|
-
'in `main.ts` so a direct `toilscript` run builds the same server.\n',
|
|
430
|
+
'server/README.md': SERVER_README,
|
|
423
431
|
};
|
|
424
432
|
}
|
|
425
433
|
|
|
426
|
-
/**
|
|
427
|
-
|
|
434
|
+
/**
|
|
435
|
+
* A working server for the `agent` template: one correctly-structured `@rest` endpoint. A `@data`
|
|
436
|
+
* model under models/, a controller under routes/, an AppHandler that tries the REST controllers
|
|
437
|
+
* via `Rest.dispatch` (then yields to the client), and main.ts importing the route. The server
|
|
438
|
+
* build turns this into the typed `Server.REST.greeting.hello()` fetch the home page calls.
|
|
439
|
+
*/
|
|
440
|
+
function agentServer(): Record<string, string> {
|
|
441
|
+
return {
|
|
442
|
+
'server/toil-server-env.d.ts': TOIL_SERVER_ENV_DTS,
|
|
443
|
+
'server/models/Greeting.ts':
|
|
444
|
+
'/** The response body for `GET /greeting`. `@data` classes cross the wire as JSON and\n' +
|
|
445
|
+
' * land on the client as this exact typed class. One `@data` type per file. */\n' +
|
|
446
|
+
'@data\n' +
|
|
447
|
+
'export class Greeting {\n' +
|
|
448
|
+
" message: string = '';\n" +
|
|
449
|
+
'}\n',
|
|
450
|
+
'server/routes/Greet.ts':
|
|
451
|
+
"import { Greeting } from '../models/Greeting';\n\n" +
|
|
452
|
+
'/**\n' +
|
|
453
|
+
' * The greeting endpoint, mounted at `/greeting`. `@rest` / `@get` are ambient compiler\n' +
|
|
454
|
+
' * decorators (no import). On the client this is one typed fetch:\n' +
|
|
455
|
+
' * await Server.REST.greeting.hello()\n' +
|
|
456
|
+
' */\n' +
|
|
457
|
+
"@rest('greeting')\n" +
|
|
458
|
+
'class Greet {\n' +
|
|
459
|
+
' /** `GET /greeting` - returns a `Greeting`, serialized to JSON for the client. */\n' +
|
|
460
|
+
" @get('/')\n" +
|
|
461
|
+
' public hello(): Greeting {\n' +
|
|
462
|
+
' const g = new Greeting();\n' +
|
|
463
|
+
" g.message = 'Hello from the toiljs backend';\n" +
|
|
464
|
+
' return g;\n' +
|
|
465
|
+
' }\n' +
|
|
466
|
+
'}\n',
|
|
467
|
+
'server/core/AppHandler.ts':
|
|
468
|
+
"import { Request, Response, Rest, ToilHandler } from 'toiljs/server/runtime';\n\n" +
|
|
469
|
+
'/**\n' +
|
|
470
|
+
' * Every request enters here. The `@rest` controllers under routes/ are tried first via\n' +
|
|
471
|
+
' * `Rest.dispatch`; anything they do not claim yields to the client (page routes, assets).\n' +
|
|
472
|
+
' */\n' +
|
|
473
|
+
'export class AppHandler extends ToilHandler {\n' +
|
|
474
|
+
' public handle(req: Request): Response {\n' +
|
|
475
|
+
' // Rest.dispatch returns the first matching route\'s Response, or null when nothing\n' +
|
|
476
|
+
' // matched. REST composes; it never takes over handle().\n' +
|
|
477
|
+
' const hit = Rest.dispatch(req);\n' +
|
|
478
|
+
' if (hit != null) {\n' +
|
|
479
|
+
' return hit;\n' +
|
|
480
|
+
' }\n' +
|
|
481
|
+
' // Nothing matched: yield to the client. Under `toiljs dev` this falls through to\n' +
|
|
482
|
+
' // Vite so the app renders at /.\n' +
|
|
483
|
+
' return Response.unhandled();\n' +
|
|
484
|
+
' }\n' +
|
|
485
|
+
'}\n',
|
|
486
|
+
'server/main.ts':
|
|
487
|
+
"import { Server } from 'toiljs/server/runtime';\n" +
|
|
488
|
+
"import { revertOnError } from 'toiljs/server/runtime/abort/abort';\n\n" +
|
|
489
|
+
"import { AppHandler } from './core/AppHandler';\n\n" +
|
|
490
|
+
'// Surface modules: import every @rest route (and @service/@remote RPC) here so a direct\n' +
|
|
491
|
+
'// `toilscript` run builds the same server `toiljs build` does.\n' +
|
|
492
|
+
"import './routes/Greet';\n\n" +
|
|
493
|
+
'// Wire your handler here.\n' +
|
|
494
|
+
'Server.handler = () => new AppHandler();\n\n' +
|
|
495
|
+
'// Required: re-export the WASM entry points and the abort hook.\n' +
|
|
496
|
+
"export * from 'toiljs/server/runtime/exports';\n" +
|
|
497
|
+
'export function abort(message: string, fileName: string, line: u32, column: u32): void {\n' +
|
|
498
|
+
' revertOnError(message, fileName, line, column);\n' +
|
|
499
|
+
'}\n',
|
|
500
|
+
'server/README.md': SERVER_README,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* The shared client shell for the inline templates (`minimal`, `agent`): the public assets,
|
|
506
|
+
* `toil.tsx` mount, global stylesheet, and the (optional) Tailwind entry. Each template layers
|
|
507
|
+
* its own `layout.tsx` + `routes/` on top; the `app` template copies examples/basic/client instead.
|
|
508
|
+
*/
|
|
509
|
+
function clientShell(name: string, features: StyleFeatures): Record<string, string> {
|
|
428
510
|
const files: Record<string, string> = {
|
|
429
511
|
'client/public/index.html':
|
|
430
512
|
'<!doctype html>\n<html lang="en">\n <head>\n' +
|
|
@@ -472,7 +554,17 @@ function minimalClient(name: string, features: StyleFeatures): Record<string, st
|
|
|
472
554
|
'Toil.mount(routes, layout, notFound, globalError, slots);\n',
|
|
473
555
|
[`client/${styleEntry(features.preprocessor)}`]: DEFAULT_STYLE_CONTENT,
|
|
474
556
|
'client/components/.gitkeep': '# Place shared React components here.\n',
|
|
475
|
-
|
|
557
|
+
};
|
|
558
|
+
if (features.tailwind) files[`client/${TAILWIND_ENTRY}`] = TAILWIND_CSS;
|
|
559
|
+
return files;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/** The root `layout.tsx`: the app frame + a nav bar of `Toil.Link`s (one per `nav` entry). */
|
|
563
|
+
function layoutTsx(name: string, nav: readonly { href: string; label: string }[]): string {
|
|
564
|
+
const links = nav
|
|
565
|
+
.map((l) => ` <Toil.Link href="${l.href}">${l.label}</Toil.Link>`)
|
|
566
|
+
.join('\n');
|
|
567
|
+
return `import { type ReactNode } from 'react';
|
|
476
568
|
|
|
477
569
|
export default function Layout({ children }: { children?: ReactNode }) {
|
|
478
570
|
return (
|
|
@@ -488,14 +580,21 @@ export default function Layout({ children }: { children?: ReactNode }) {
|
|
|
488
580
|
}}>
|
|
489
581
|
<strong style={{ color: '#2563FF', fontSize: '1.1rem' }}>${path.basename(name)}</strong>
|
|
490
582
|
<nav style={{ display: 'flex', gap: '1rem' }}>
|
|
491
|
-
|
|
583
|
+
${links}
|
|
492
584
|
</nav>
|
|
493
585
|
</header>
|
|
494
586
|
{children}
|
|
495
587
|
</div>
|
|
496
588
|
);
|
|
497
589
|
}
|
|
498
|
-
|
|
590
|
+
`;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/** The inline client UI for the `minimal` template (the `app` template copies examples/basic/client). */
|
|
594
|
+
function minimalClient(name: string, features: StyleFeatures): Record<string, string> {
|
|
595
|
+
return {
|
|
596
|
+
...clientShell(name, features),
|
|
597
|
+
'client/layout.tsx': layoutTsx(name, [{ href: '/', label: 'home' }]),
|
|
499
598
|
'client/routes/index.tsx':
|
|
500
599
|
'export default function Home() {\n' +
|
|
501
600
|
' return (\n <main>\n' +
|
|
@@ -503,8 +602,49 @@ export default function Layout({ children }: { children?: ReactNode }) {
|
|
|
503
602
|
' <p>File-based routing, bundled by Vite, zero config.</p>\n' +
|
|
504
603
|
' </main>\n );\n}\n',
|
|
505
604
|
};
|
|
506
|
-
|
|
507
|
-
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* The inline client UI for the `agent` template: two file-routed pages (basic routing) and a home
|
|
609
|
+
* page that calls the backend's one @rest endpoint through the generated, typed `Server.REST`
|
|
610
|
+
* fetch client. `Server` and `parseError` are client globals (no import), like `Toil`.
|
|
611
|
+
*/
|
|
612
|
+
function agentClient(name: string, features: StyleFeatures): Record<string, string> {
|
|
613
|
+
return {
|
|
614
|
+
...clientShell(name, features),
|
|
615
|
+
'client/layout.tsx': layoutTsx(name, [
|
|
616
|
+
{ href: '/', label: 'home' },
|
|
617
|
+
{ href: '/about', label: 'about' },
|
|
618
|
+
]),
|
|
619
|
+
'client/routes/index.tsx':
|
|
620
|
+
"import { useEffect, useState } from 'react';\n\n" +
|
|
621
|
+
'// The home page calls the backend. `Server.REST.greeting.hello()` is a real, typed\n' +
|
|
622
|
+
'// fetch generated from the @rest controller in server/routes/Greet.ts (see the\n' +
|
|
623
|
+
'// gitignored shared/server.ts, emitted by the server build). It needs the server running.\n' +
|
|
624
|
+
'export default function Home() {\n' +
|
|
625
|
+
" const [message, setMessage] = useState('loading…');\n\n" +
|
|
626
|
+
' useEffect(() => {\n' +
|
|
627
|
+
' Server.REST.greeting\n' +
|
|
628
|
+
' .hello()\n' +
|
|
629
|
+
' .then((g) => setMessage(g.message))\n' +
|
|
630
|
+
' .catch((err) => setMessage(parseError(err)));\n' +
|
|
631
|
+
' }, []);\n\n' +
|
|
632
|
+
' return (\n <main>\n' +
|
|
633
|
+
' <h1>Welcome to toiljs</h1>\n' +
|
|
634
|
+
' <p>File-based routing on the client, a ToilScript backend behind one typed fetch.</p>\n' +
|
|
635
|
+
' <p>\n' +
|
|
636
|
+
' <code>GET /greeting</code> says: <strong>{message}</strong>\n' +
|
|
637
|
+
' </p>\n' +
|
|
638
|
+
' </main>\n );\n}\n',
|
|
639
|
+
'client/routes/about.tsx':
|
|
640
|
+
'export default function About() {\n' +
|
|
641
|
+
' return (\n <main>\n' +
|
|
642
|
+
' <h1>About</h1>\n' +
|
|
643
|
+
' <p>\n' +
|
|
644
|
+
' A second page, routed by its file name. Add more under <code>client/routes/</code>.\n' +
|
|
645
|
+
' </p>\n' +
|
|
646
|
+
' </main>\n );\n}\n',
|
|
647
|
+
};
|
|
508
648
|
}
|
|
509
649
|
|
|
510
650
|
/**
|
|
@@ -625,6 +765,11 @@ export async function runCreate(opts: CreateOptions): Promise<void> {
|
|
|
625
765
|
hint: 'the full ToilJS starter, landing page, layout, styles, demo routes',
|
|
626
766
|
},
|
|
627
767
|
{ value: 'minimal', label: 'Minimal', hint: 'just a layout and a home route' },
|
|
768
|
+
{
|
|
769
|
+
value: 'agent',
|
|
770
|
+
label: 'Agent',
|
|
771
|
+
hint: 'minimal, plus one @rest endpoint the home page calls, a base to build on',
|
|
772
|
+
},
|
|
628
773
|
];
|
|
629
774
|
const choice = await select({
|
|
630
775
|
message: 'Which template?',
|
|
@@ -632,7 +777,7 @@ export async function runCreate(opts: CreateOptions): Promise<void> {
|
|
|
632
777
|
initialValue: 'app',
|
|
633
778
|
});
|
|
634
779
|
bail(choice);
|
|
635
|
-
template = choice === 'minimal'
|
|
780
|
+
template = choice === 'minimal' || choice === 'agent' ? choice : 'app';
|
|
636
781
|
}
|
|
637
782
|
|
|
638
783
|
let preprocessor: Preprocessor = opts.preprocessor ?? 'css';
|
package/src/cli/features.ts
CHANGED
|
@@ -36,13 +36,17 @@ export const PREPROCESSOR_PKG: Record<Preprocessor, string | null> = {
|
|
|
36
36
|
/** Tailwind v4 packages. The framework auto-wires the Vite plugin when `@tailwindcss/vite` resolves. */
|
|
37
37
|
export const TAILWIND_PKGS: readonly string[] = ['tailwindcss', '@tailwindcss/vite'];
|
|
38
38
|
|
|
39
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Pinned versions for every package these features may install. `@tailwindcss/vite` must be
|
|
41
|
+
* `>= 4.3.0`: earlier 4.x releases cap their `vite` peer at `^7`, so they ERESOLVE against the
|
|
42
|
+
* Vite 8 toiljs ships (the "Tailwind broken on install" error). Keep both Tailwind pins in lockstep.
|
|
43
|
+
*/
|
|
40
44
|
export const PKG_VERSION: Record<string, string> = {
|
|
41
45
|
sass: '^1.83.0',
|
|
42
46
|
less: '^4.2.1',
|
|
43
47
|
stylus: '^0.64.0',
|
|
44
|
-
tailwindcss: '^4.
|
|
45
|
-
'@tailwindcss/vite': '^4.
|
|
48
|
+
tailwindcss: '^4.3.0',
|
|
49
|
+
'@tailwindcss/vite': '^4.3.0',
|
|
46
50
|
};
|
|
47
51
|
|
|
48
52
|
/** Dedicated Tailwind entry (kept `.css` so no preprocessor touches its `@import`). */
|
package/src/cli/index.ts
CHANGED
|
@@ -74,7 +74,7 @@ function parseArgs(argv: string[]): Flags {
|
|
|
74
74
|
case '--template':
|
|
75
75
|
case '-t': {
|
|
76
76
|
const t = argv[++i];
|
|
77
|
-
if (t === 'app' || t === 'minimal') flags.template = t;
|
|
77
|
+
if (t === 'app' || t === 'minimal' || t === 'agent') flags.template = t;
|
|
78
78
|
break;
|
|
79
79
|
}
|
|
80
80
|
case '--pm':
|
|
@@ -160,7 +160,7 @@ function printHelp(): void {
|
|
|
160
160
|
cmd('--port <n>', 'dev/start: listen port (or PORT env / client.port; default 3000)'),
|
|
161
161
|
cmd('--host <h>', 'dev/start: bind host, e.g. 0.0.0.0 (or HOST env / client.host; default 127.0.0.1)'),
|
|
162
162
|
cmd('--threads <n>', 'start: production HTTP worker count'),
|
|
163
|
-
cmd('-t, --template', 'create: app | minimal'),
|
|
163
|
+
cmd('-t, --template', 'create: app | minimal | agent'),
|
|
164
164
|
cmd('--style <name>', 'create/configure: css | sass | less | stylus'),
|
|
165
165
|
cmd('--tailwind', 'create/configure: enable Tailwind (--no-tailwind to remove)'),
|
|
166
166
|
cmd('--no-ai', 'create: skip AI assistant files (CLAUDE.md, etc.)'),
|