@shaferllc/keel 0.59.0 → 0.68.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 (166) hide show
  1. package/AGENTS.md +167 -0
  2. package/README.md +30 -1
  3. package/bin/keel-mcp.mjs +9 -0
  4. package/dist/core/application.d.ts +5 -5
  5. package/dist/core/application.js +14 -2
  6. package/dist/core/auth.d.ts +47 -0
  7. package/dist/core/auth.js +77 -0
  8. package/dist/core/authorization.d.ts +9 -1
  9. package/dist/core/authorization.js +22 -2
  10. package/dist/core/cache.d.ts +82 -5
  11. package/dist/core/cache.js +181 -23
  12. package/dist/core/cli/stubs.d.ts +12 -0
  13. package/dist/core/cli/stubs.js +120 -0
  14. package/dist/core/container.d.ts +20 -0
  15. package/dist/core/container.js +52 -0
  16. package/dist/core/cors.d.ts +29 -0
  17. package/dist/core/cors.js +72 -0
  18. package/dist/core/crypto.d.ts +40 -4
  19. package/dist/core/crypto.js +66 -6
  20. package/dist/core/csrf.d.ts +25 -0
  21. package/dist/core/csrf.js +78 -0
  22. package/dist/core/database.d.ts +49 -4
  23. package/dist/core/database.js +89 -21
  24. package/dist/core/events.d.ts +129 -5
  25. package/dist/core/events.js +165 -7
  26. package/dist/core/health.d.ts +141 -0
  27. package/dist/core/health.js +226 -0
  28. package/dist/core/helpers.d.ts +15 -3
  29. package/dist/core/helpers.js +23 -3
  30. package/dist/core/index.d.ts +33 -18
  31. package/dist/core/index.js +16 -8
  32. package/dist/core/lock.d.ts +139 -0
  33. package/dist/core/lock.js +215 -0
  34. package/dist/core/logger.d.ts +82 -4
  35. package/dist/core/logger.js +141 -23
  36. package/dist/core/mail.d.ts +128 -7
  37. package/dist/core/mail.js +264 -16
  38. package/dist/core/model.d.ts +2 -0
  39. package/dist/core/model.js +16 -14
  40. package/dist/core/provider.d.ts +7 -0
  41. package/dist/core/provider.js +7 -0
  42. package/dist/core/queue.d.ts +134 -9
  43. package/dist/core/queue.js +304 -14
  44. package/dist/core/rate-limit.js +3 -0
  45. package/dist/core/relations.js +13 -13
  46. package/dist/core/request.d.ts +26 -0
  47. package/dist/core/request.js +77 -0
  48. package/dist/core/shield.d.ts +39 -0
  49. package/dist/core/shield.js +60 -0
  50. package/dist/core/social.d.ts +173 -0
  51. package/dist/core/social.js +337 -0
  52. package/dist/core/storage.d.ts +159 -6
  53. package/dist/core/storage.js +287 -7
  54. package/dist/core/tokens.d.ts +74 -0
  55. package/dist/core/tokens.js +155 -0
  56. package/dist/db/d1.d.ts +32 -0
  57. package/dist/db/d1.js +26 -0
  58. package/dist/db/libsql.d.ts +29 -0
  59. package/dist/db/libsql.js +32 -0
  60. package/dist/db/pg.d.ts +29 -0
  61. package/dist/db/pg.js +33 -0
  62. package/dist/mcp/server.d.ts +19 -0
  63. package/dist/mcp/server.js +355 -0
  64. package/docs/ai-manifest.json +2472 -0
  65. package/docs/ai.md +128 -0
  66. package/docs/architecture.md +331 -0
  67. package/docs/authentication.md +453 -0
  68. package/docs/authorization.md +167 -0
  69. package/docs/broadcasting.md +137 -0
  70. package/docs/broker.md +500 -0
  71. package/docs/cache.md +558 -0
  72. package/docs/configuration.md +311 -0
  73. package/docs/console.md +356 -0
  74. package/docs/container.md +467 -0
  75. package/docs/controllers.md +265 -0
  76. package/docs/cors.md +51 -0
  77. package/docs/database.md +530 -0
  78. package/docs/debugging.md +129 -0
  79. package/docs/decorators.md +127 -0
  80. package/docs/errors.md +395 -0
  81. package/docs/events.md +496 -0
  82. package/docs/examples/architecture-app.ts +27 -0
  83. package/docs/examples/authentication.ts +61 -0
  84. package/docs/examples/authorization.ts +79 -0
  85. package/docs/examples/broadcasting.ts +60 -0
  86. package/docs/examples/broker-cache-validate.ts +34 -0
  87. package/docs/examples/broker-fault-tolerance.ts +29 -0
  88. package/docs/examples/broker-middleware.ts +27 -0
  89. package/docs/examples/broker.ts +203 -0
  90. package/docs/examples/cache.ts +222 -0
  91. package/docs/examples/configuration.ts +81 -0
  92. package/docs/examples/container.ts +134 -0
  93. package/docs/examples/controllers.ts +86 -0
  94. package/docs/examples/database.ts +118 -0
  95. package/docs/examples/debugging.ts +41 -0
  96. package/docs/examples/decorators.ts +40 -0
  97. package/docs/examples/errors.ts +121 -0
  98. package/docs/examples/events.ts +204 -0
  99. package/docs/examples/factories.ts +84 -0
  100. package/docs/examples/hashing.ts +71 -0
  101. package/docs/examples/health.ts +94 -0
  102. package/docs/examples/helpers.ts +171 -0
  103. package/docs/examples/hooks.ts +54 -0
  104. package/docs/examples/inertia.ts +81 -0
  105. package/docs/examples/locks.ts +120 -0
  106. package/docs/examples/logger.ts +92 -0
  107. package/docs/examples/mail.ts +160 -0
  108. package/docs/examples/middleware.ts +119 -0
  109. package/docs/examples/migrations.ts +126 -0
  110. package/docs/examples/models.ts +239 -0
  111. package/docs/examples/notification.ts +124 -0
  112. package/docs/examples/providers.ts +123 -0
  113. package/docs/examples/queues.ts +254 -0
  114. package/docs/examples/rate-limiting.ts +42 -0
  115. package/docs/examples/redis.ts +99 -0
  116. package/docs/examples/request-response.ts +197 -0
  117. package/docs/examples/routing.ts +186 -0
  118. package/docs/examples/scheduling.ts +62 -0
  119. package/docs/examples/sessions.ts +102 -0
  120. package/docs/examples/static-files.ts +63 -0
  121. package/docs/examples/storage.ts +132 -0
  122. package/docs/examples/templates.ts +58 -0
  123. package/docs/examples/testing.ts +66 -0
  124. package/docs/examples/transformer.ts +141 -0
  125. package/docs/examples/transformers.ts +49 -0
  126. package/docs/examples/url-builder.ts +86 -0
  127. package/docs/examples/validation.ts +102 -0
  128. package/docs/examples/views.tsx +62 -0
  129. package/docs/examples/vite.ts +106 -0
  130. package/docs/factories.md +166 -0
  131. package/docs/getting-started.md +290 -0
  132. package/docs/hashing.md +259 -0
  133. package/docs/health.md +225 -0
  134. package/docs/helpers.md +347 -0
  135. package/docs/hono.md +186 -0
  136. package/docs/hooks.md +118 -0
  137. package/docs/inertia.md +241 -0
  138. package/docs/locks.md +323 -0
  139. package/docs/logger.md +290 -0
  140. package/docs/mail.md +678 -0
  141. package/docs/middleware.md +425 -0
  142. package/docs/migrations.md +476 -0
  143. package/docs/models.md +810 -0
  144. package/docs/notifications.md +474 -0
  145. package/docs/providers.md +363 -0
  146. package/docs/queues.md +679 -0
  147. package/docs/rate-limiting.md +155 -0
  148. package/docs/redis.md +178 -0
  149. package/docs/request-response.md +953 -0
  150. package/docs/routing.md +804 -0
  151. package/docs/scheduling.md +110 -0
  152. package/docs/security.md +85 -0
  153. package/docs/sessions.md +354 -0
  154. package/docs/social-auth.md +174 -0
  155. package/docs/static-files.md +211 -0
  156. package/docs/storage.md +450 -0
  157. package/docs/templates.md +315 -0
  158. package/docs/testing.md +125 -0
  159. package/docs/transformers.md +381 -0
  160. package/docs/url-builder.md +295 -0
  161. package/docs/validation.md +288 -0
  162. package/docs/views.md +267 -0
  163. package/docs/vite.md +434 -0
  164. package/llms-full.txt +17694 -0
  165. package/llms.txt +116 -0
  166. package/package.json +38 -7
@@ -0,0 +1,141 @@
1
+ // Type-check harness for docs/transformers.md. Every type-checkable snippet in
2
+ // the guide is exercised here against the real exports, so a renamed method or
3
+ // wrong argument type fails `npm run typecheck:docs`. Compile-only — never run.
4
+ import {
5
+ Transformer,
6
+ Model,
7
+ auth,
8
+ json,
9
+ type Attributes,
10
+ type DocumentOptions,
11
+ type Ctx,
12
+ } from "@shaferllc/keel/core";
13
+
14
+ class Post extends Model {
15
+ static table = "posts";
16
+ declare id: number;
17
+ declare title: string;
18
+ declare author: User;
19
+ }
20
+
21
+ class User extends Model {
22
+ static table = "users";
23
+ declare id: number;
24
+ declare name: string;
25
+ declare email: string;
26
+ declare created_at: string;
27
+ declare admin: boolean;
28
+ declare role: string;
29
+ declare permissions: string[];
30
+ }
31
+
32
+ class PostTransformer extends Transformer<Post> {
33
+ transform(post: Post): Attributes {
34
+ return { id: post.id, title: post.title };
35
+ }
36
+ }
37
+
38
+ // Defining a transformer
39
+ export class BasicUserTransformer extends Transformer<User> {
40
+ transform(user: User): Attributes {
41
+ return {
42
+ id: user.id,
43
+ name: user.name,
44
+ joined: user.created_at,
45
+ };
46
+ }
47
+ }
48
+
49
+ // Constructor context + conditional fields + nesting + relations
50
+ export class UserTransformer extends Transformer<User> {
51
+ constructor(private viewerId: string | null) {
52
+ super();
53
+ }
54
+
55
+ transform(user: User): Attributes {
56
+ return {
57
+ id: user.id,
58
+ name: user.name,
59
+ email: this.when(String(user.id) === this.viewerId, user.email),
60
+ token: this.when(fresh, () => mintToken(user), null),
61
+ ...this.mergeWhen(user.admin, { role: user.role, permissions: user.permissions }),
62
+ author: new UserTransformer(this.viewerId).item(user),
63
+ posts: this.whenLoaded(user, "posts", new PostTransformer()),
64
+ roles: this.whenLoaded(user, "roles", (roles: { name: string }[]) =>
65
+ roles.map((r) => r.name),
66
+ ),
67
+ };
68
+ }
69
+ }
70
+
71
+ class WrappedUserTransformer extends Transformer<User> {
72
+ wrapKey = "user";
73
+ transform(user: User): Attributes {
74
+ return { id: user.id, name: user.name };
75
+ }
76
+ }
77
+
78
+ declare const user: User;
79
+ declare const list: User[];
80
+ declare const fresh: boolean;
81
+ declare const fetchedAt: string;
82
+ declare function mintToken(user: User): string;
83
+
84
+ // Transforming
85
+ export function transforming() {
86
+ const users = new UserTransformer(null);
87
+ json(users.item(user));
88
+ json(users.collection(list));
89
+ json(users.document(list, { meta: { total: list.length } }));
90
+ }
91
+
92
+ // item null passthrough
93
+ export function nullable() {
94
+ const out: Attributes | null = new BasicUserTransformer().item(null);
95
+ return out;
96
+ }
97
+
98
+ // Passing viewer context through the constructor
99
+ export function withViewer() {
100
+ return json(new UserTransformer(auth().id()).collection(list));
101
+ }
102
+
103
+ // Nesting / relations — load first, then transform
104
+ export async function relations() {
105
+ const users = await User.all<User>();
106
+ await User.load(users, "posts");
107
+ return json(new UserTransformer(null).collection(users));
108
+ }
109
+
110
+ // Response documents
111
+ export async function documents() {
112
+ const page = await User.all<User>();
113
+ json(
114
+ new UserTransformer(null).document(page, {
115
+ meta: { total: page.length, page: 1 },
116
+ }),
117
+ );
118
+
119
+ new WrappedUserTransformer().document(user); // { user: { … } }
120
+ new UserTransformer(null).document(user, { key: null, meta: { fetchedAt } });
121
+ new UserTransformer(null).document(user, { key: "records", meta: { total: 42 } });
122
+ }
123
+
124
+ // In a controller
125
+ export class UserController {
126
+ async show(c: Ctx) {
127
+ const found = await User.findOrFail<User>(c.req.param("id"));
128
+ return c.json(new UserTransformer(auth().id()).item(found));
129
+ }
130
+
131
+ async index(c: Ctx) {
132
+ const users = await User.all<User>();
133
+ await User.load(users, "posts");
134
+ return c.json(new UserTransformer(auth().id()).document(users));
135
+ }
136
+ }
137
+
138
+ // Types
139
+ const options: DocumentOptions = { key: "records", meta: { total: 42 } };
140
+ const shape: Attributes = { id: 1, name: "Ada" };
141
+ export { options, shape };
@@ -0,0 +1,49 @@
1
+ // Type-check harness for docs/transformers.md. Exercises every type-checkable
2
+ // snippet against the real exports so a renamed method or wrong signature fails
3
+ // `npm run typecheck:docs`. Compile-only — never executed.
4
+ import {
5
+ Transformer,
6
+ json,
7
+ type Attributes,
8
+ type DocumentOptions,
9
+ } from "@shaferllc/keel/core";
10
+
11
+ type User = { id: number; name: string; email: string; role: string; admin: boolean };
12
+ type Post = { id: number; title: string };
13
+
14
+ declare const isSelf: boolean;
15
+ declare const isAdmin: boolean;
16
+ declare const fresh: boolean;
17
+ declare function mint(): string;
18
+
19
+ class PostTransformer extends Transformer<Post> {
20
+ transform(post: Post): Attributes {
21
+ return { id: post.id, title: post.title };
22
+ }
23
+ }
24
+
25
+ class UserTransformer extends Transformer<User> {
26
+ transform(user: User): Attributes {
27
+ return {
28
+ id: user.id,
29
+ name: user.name,
30
+ email: this.when(isSelf, user.email), // key vanishes for others
31
+ token: this.when(fresh, () => mint(), null), // explicit fallback
32
+ ...this.mergeWhen(isAdmin, { role: user.role }),
33
+ posts: this.whenLoaded(user, "posts", new PostTransformer()),
34
+ roles: this.whenLoaded(user, "roles", (rs: { name: string }[]) => rs.map((r) => r.name)),
35
+ };
36
+ }
37
+ }
38
+
39
+ export function usage(user: User, users: User[]) {
40
+ const one: Attributes | null = new UserTransformer().item(user);
41
+ const many: Attributes[] = new UserTransformer().collection(users);
42
+ const wrapped: Attributes = new UserTransformer().document(users, {
43
+ meta: { total: users.length },
44
+ });
45
+ const opts: DocumentOptions = { key: "data", meta: { page: 1 } };
46
+ const custom: Attributes = new UserTransformer().document(user, opts);
47
+ const unwrapped: Attributes = new UserTransformer().document(user, { key: null });
48
+ return [json(one), json(many), json(wrapped), json(custom), json(unwrapped)];
49
+ }
@@ -0,0 +1,86 @@
1
+ // Type-check harness for docs/url-builder.md. Every type-checkable snippet in
2
+ // the reference is exercised here against the real exports, so a renamed method
3
+ // or wrong argument type fails `npm run typecheck:docs`. Compile-only — never
4
+ // executed.
5
+ import {
6
+ Router,
7
+ Container,
8
+ matchers,
9
+ type UrlOptions,
10
+ type SignedUrlOptions,
11
+ type Matcher,
12
+ } from "@shaferllc/keel/core";
13
+
14
+ // The router is resolved from the container in a real app; construct it
15
+ // directly here (as the test suite does).
16
+ const router = new Router(new Container());
17
+
18
+ // Controllers referenced by the doc's route registrations.
19
+ class UserController {}
20
+ class FileController {}
21
+ class PostController {}
22
+ class TeamController {}
23
+ class CodeController {}
24
+ class XController {}
25
+
26
+ export function building() {
27
+ router.get("/users/:id", [UserController, "show"]).name("users.show");
28
+
29
+ const a: string = router.url("users.show", { id: 42 });
30
+ const b: string = router.url("users.show", { id: 42 }, { qs: { tab: "posts", page: 2 } });
31
+
32
+ router.get("/files/:name", [FileController]).name("files.show");
33
+ const c: string = router.url("files.show", { name: "a/b c.txt" });
34
+
35
+ return { a, b, c };
36
+ }
37
+
38
+ export function optionalParams() {
39
+ router.get("/posts/:id?", [PostController, "show"]).name("posts.show");
40
+ const withId: string = router.url("posts.show", { id: 7 });
41
+ const without: string = router.url("posts.show", {});
42
+ return { withId, without };
43
+ }
44
+
45
+ export function errors() {
46
+ // Throws at runtime; type-checks fine.
47
+ return router.url("nope");
48
+ }
49
+
50
+ export async function signing() {
51
+ const url: string = await router.signedUrl("download", { id: 7 });
52
+ const expiring: string = await router.signedUrl(
53
+ "download",
54
+ { id: 7 },
55
+ { expiresIn: 3600 },
56
+ );
57
+ const valid: boolean = await router.hasValidSignature();
58
+ return { url, expiring, valid };
59
+ }
60
+
61
+ export function constraints() {
62
+ router.get("/users/:id", [UserController]).where("id", /\d+/);
63
+ router.get("/p/:slug", [PostController]).where("slug", { match: /[a-z0-9-]+/ });
64
+
65
+ router.get("/n/:id", [UserController]).where("id", matchers.number());
66
+ router.get("/t/:id", [TeamController]).where("id", matchers.uuid());
67
+ router.get("/s/:slug", [PostController]).where("slug", matchers.slug());
68
+ router.get("/c/:code", [CodeController]).where("code", matchers.alpha());
69
+
70
+ // Also reachable off the instance.
71
+ router.get("/i/:id", [XController]).where("id", router.matchers.number());
72
+ }
73
+
74
+ export function typeSeams() {
75
+ const opts: UrlOptions = { qs: { page: 2, tab: "posts" } };
76
+ router.url("users.show", { id: 1 }, opts);
77
+
78
+ const signed: SignedUrlOptions = { qs: { plan: "pro" }, expiresIn: 3600 };
79
+
80
+ const a: Matcher = /\d+/;
81
+ const b: Matcher = "[0-9]+";
82
+ const c: Matcher = { match: /[a-z-]+/ };
83
+ router.get("/x/:id", [XController]).where("id", a);
84
+
85
+ return { opts, signed, a, b, c };
86
+ }
@@ -0,0 +1,102 @@
1
+ // Type-check harness for docs/validation.md. Every type-checkable snippet in the
2
+ // guide is exercised here against the real exports, so a renamed method or wrong
3
+ // argument type fails `npm run typecheck:docs`. Compile-only — never executed.
4
+ import {
5
+ json,
6
+ validate,
7
+ validateRequest,
8
+ validated,
9
+ request,
10
+ response,
11
+ ValidationException,
12
+ Router,
13
+ type Schema,
14
+ } from "@shaferllc/keel/core";
15
+ import { z } from "zod";
16
+
17
+ declare const Users: new (...a: unknown[]) => object;
18
+ declare const Posts: new (...a: unknown[]) => object;
19
+
20
+ export function declarative(router: Router) {
21
+ const NewUser = z.object({ email: z.string().email(), name: z.string().min(1) });
22
+ router.post("/users", [Users, "store"]).middleware([validateRequest({ body: NewUser })]);
23
+ router.get("/posts/:id", [Posts, "show"]).middleware([
24
+ validateRequest({
25
+ params: z.object({ id: z.coerce.number() }),
26
+ query: z.object({ page: z.coerce.number().min(1).default(1) }),
27
+ }),
28
+ ]);
29
+ }
30
+
31
+ export function readValidated() {
32
+ const user = validated<{ email: string; name: string }>("body");
33
+ const id = validated<{ id: number }>("params");
34
+ const page = validated<{ page: number }>("query");
35
+ return { user, id, page };
36
+ }
37
+
38
+ declare const email: string;
39
+ declare const age: number;
40
+ declare const err: unknown;
41
+
42
+ const NewUser = z.object({
43
+ email: z.string().email(),
44
+ age: z.number().min(18),
45
+ });
46
+
47
+ export class UserController {
48
+ async store() {
49
+ const data = await validate(NewUser); // { email: string; age: number }
50
+ return json({ created: data.email }, 201);
51
+ }
52
+ }
53
+
54
+ const Search = z.object({ q: z.string().min(1), page: z.coerce.number().default(1) });
55
+
56
+ export async function search() {
57
+ const { q, page } = await validate(Search, request.query());
58
+ return { q, page };
59
+ }
60
+
61
+ export async function mergedInput() {
62
+ const data = await validate(NewUser, await request.all());
63
+ return data;
64
+ }
65
+
66
+ export function handleError() {
67
+ if (err instanceof ValidationException) {
68
+ return response.json({ fields: err.errors }, 422);
69
+ }
70
+ }
71
+
72
+ export async function reference() {
73
+ const fromBody = await validate(NewUser);
74
+ const fromData = await validate(NewUser, { email, age });
75
+ return { fromBody, fromData };
76
+ }
77
+
78
+ // Schema<T> — hand-rolled implementation
79
+ const Positive: Schema<number> = {
80
+ safeParse: (data) =>
81
+ typeof data === "number" && data > 0
82
+ ? { success: true, data }
83
+ : { success: false, error: { issues: [{ path: [], message: "must be > 0" }] } },
84
+ };
85
+
86
+ export async function customSchema() {
87
+ const n = await validate(Positive, 42); // number
88
+ return n;
89
+ }
90
+
91
+ // ValidationException seam
92
+ export async function exceptionShape() {
93
+ try {
94
+ await validate(NewUser);
95
+ } catch (e) {
96
+ if (e instanceof ValidationException) {
97
+ const status: number = e.status;
98
+ const errors: Record<string, string[]> = e.errors;
99
+ return { status, errors };
100
+ }
101
+ }
102
+ }
@@ -0,0 +1,62 @@
1
+ // Type-check harness for docs/views.md. Every type-checkable snippet in the
2
+ // reference is exercised here against the real exports, so a renamed method or
3
+ // wrong argument type fails `npm run typecheck:docs`. Compile-only — never
4
+ // executed. This file is .tsx so the JSX in the examples compiles (tsconfig
5
+ // already routes JSX through hono/jsx).
6
+ import { view, View, type Renderable, type ViewConfig } from "@shaferllc/keel/core";
7
+ import type { FC, PropsWithChildren } from "hono/jsx";
8
+
9
+ // A layout is just a component that wraps its children.
10
+ const Layout: FC<PropsWithChildren<{ title: string }>> = ({ title, children }) => (
11
+ <html lang="en">
12
+ <head>
13
+ <meta charset="utf-8" />
14
+ <title>{title}</title>
15
+ </head>
16
+ <body>{children}</body>
17
+ </html>
18
+ );
19
+
20
+ const WelcomePage: FC<{ appName: string }> = ({ appName }) => (
21
+ <Layout title={appName}>
22
+ <h1>⚓ {appName}</h1>
23
+ <p>Your view is rendering.</p>
24
+ </Layout>
25
+ );
26
+
27
+ const HomePage: FC = () => <p>home</p>;
28
+
29
+ // An async component: data-fetching happens inside, render() awaits it.
30
+ declare function loadStats(userId: number): Promise<unknown>;
31
+ const Dashboard: FC<{ userId: number }> = async ({ userId }) => {
32
+ const stats = await loadStats(userId);
33
+ return <pre>{JSON.stringify(stats)}</pre>;
34
+ };
35
+
36
+ const Fragment: FC = () => <span>fragment</span>;
37
+
38
+ export async function helper() {
39
+ const withProps = await view(WelcomePage, { appName: "Keel" });
40
+ const noProps = await view(HomePage);
41
+ const dash = await view(Dashboard, { userId: 1 });
42
+ return { withProps, noProps, dash };
43
+ }
44
+
45
+ export async function service() {
46
+ const html = await new View().render(WelcomePage({ appName: "Keel" }));
47
+ const raw = await new View().render("<p>plain html</p>");
48
+ const frag = await new View({ doctype: false }).render(Fragment({}));
49
+
50
+ // null / undefined render just the doctype (or empty when doctype is off).
51
+ const shell = await new View().render(null);
52
+ const empty = await new View({ doctype: false }).render(undefined);
53
+
54
+ return { html, raw, frag, shell, empty };
55
+ }
56
+
57
+ // Interface / type seams
58
+ const a: Renderable = "<h1>hi</h1>";
59
+ const b: Renderable = Promise.resolve("<h1>hi</h1>");
60
+ const c: Renderable = null;
61
+ const config: ViewConfig = { doctype: false };
62
+ export { a, b, c, config };
@@ -0,0 +1,106 @@
1
+ // Type-check harness for docs/vite.md. Every type-checkable snippet in the guide
2
+ // is exercised here against the real exports, so a renamed method or wrong
3
+ // argument type fails `npm run typecheck:docs`. Compile-only — never run.
4
+ //
5
+ // The `vite.config.ts` / `keelVite` snippets aren't covered here (they live in
6
+ // the `@shaferllc/keel/vite` entry and need Vite's own types); tests/vite.test.ts
7
+ // type-checks that plugin API instead.
8
+ import {
9
+ ServiceProvider,
10
+ singleton,
11
+ serveStatic,
12
+ Vite,
13
+ viteTags,
14
+ viteAsset,
15
+ viteReactRefresh,
16
+ type ViteOptions,
17
+ type ViteAttributes,
18
+ type Manifest,
19
+ type ManifestChunk,
20
+ } from "@shaferllc/keel/core";
21
+
22
+ // Constructing + options
23
+ export function constructing() {
24
+ const basic = new Vite({ entrypoints: ["resources/js/app.ts"], assetsUrl: "/assets" });
25
+
26
+ const cdn = new Vite({
27
+ entrypoints: ["resources/js/app.ts", "resources/js/admin.ts"],
28
+ assetsUrl: "https://cdn.example.com",
29
+ });
30
+
31
+ const withAttrs = new Vite({
32
+ entrypoints: ["resources/js/app.ts"],
33
+ scriptAttributes: { defer: true, crossorigin: "anonymous" },
34
+ styleAttributes: ({ src }) =>
35
+ src.includes("admin") ? { "data-turbo-track": "reload" } : undefined,
36
+ });
37
+
38
+ return { basic, cdn, withAttrs };
39
+ }
40
+
41
+ // Loading + injection
42
+ export async function loading() {
43
+ const disk = await new Vite({ entrypoints: ["resources/js/app.ts"] }).loadFromDisk();
44
+
45
+ const manifest: Manifest = {
46
+ "resources/js/app.ts": { file: "app-abc123.js", isEntry: true, css: ["app-ghi.css"] },
47
+ };
48
+ const edge = new Vite({ entrypoints: ["resources/js/app.ts"] }).useManifest(manifest);
49
+ const forced = new Vite({}).useHotUrl("http://localhost:5173");
50
+
51
+ return { disk, edge, forced };
52
+ }
53
+
54
+ // Tag generation + resolution
55
+ export function rendering() {
56
+ const vite = new Vite({ entrypoints: ["resources/js/app.ts"] }).useHotUrl("http://localhost:5173");
57
+ const one = vite.generateEntryPointsTags("resources/js/app.ts");
58
+ const many = vite.generateEntryPointsTags(["resources/js/app.ts", "resources/js/admin.ts"]);
59
+ const preamble = vite.reactHMR();
60
+ const asset = vite.assetPath("resources/images/logo.png");
61
+ const hot: string | null = vite.hot();
62
+ return { one, many, preamble, asset, hot };
63
+ }
64
+
65
+ export function manifestAccess() {
66
+ const vite = new Vite({}).useManifest({ "a.ts": { file: "a.hash.js" } });
67
+ const manifest: Manifest = vite.manifest();
68
+ const chunk: ManifestChunk | undefined = manifest["a.ts"];
69
+ return chunk;
70
+ }
71
+
72
+ // Binding in a provider
73
+ export class AppServiceProvider extends ServiceProvider {
74
+ register(): void {
75
+ singleton(Vite, () => new Vite({ entrypoints: ["resources/js/app.ts"] }));
76
+ }
77
+ async boot(): Promise<void> {
78
+ await this.app.make(Vite).loadFromDisk();
79
+ }
80
+ }
81
+
82
+ // Serving the build in production
83
+ export function serving() {
84
+ return serveStatic({ root: "./public" });
85
+ }
86
+
87
+ // Free helpers used in views
88
+ export function helpers() {
89
+ const refresh = viteReactRefresh();
90
+ const tags = viteTags("resources/js/app.ts");
91
+ const many = viteTags(["resources/js/app.ts", "resources/js/admin.ts"]);
92
+ const logo = viteAsset("resources/images/logo.png");
93
+ return { refresh, tags, many, logo };
94
+ }
95
+
96
+ // Types
97
+ const options: ViteOptions = {
98
+ entrypoints: ["resources/js/app.ts"],
99
+ hotFile: "public/hot",
100
+ buildDirectory: "public/assets",
101
+ assetsUrl: "/assets",
102
+ };
103
+
104
+ const attributes: ViteAttributes = ({ src, url }) => ({ "data-src": src, "data-url": url });
105
+
106
+ export { options, attributes };