@jant/core 0.2.12 → 0.2.13

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 (146) hide show
  1. package/bin/jant.js +3 -1
  2. package/dist/app.d.ts.map +1 -1
  3. package/dist/app.js +112 -85
  4. package/dist/auth.d.ts +1 -0
  5. package/dist/auth.d.ts.map +1 -1
  6. package/dist/auth.js +2 -1
  7. package/dist/client.js +1 -1
  8. package/dist/db/schema.d.ts.map +1 -1
  9. package/dist/i18n/context.d.ts.map +1 -1
  10. package/dist/i18n/context.js +0 -3
  11. package/dist/i18n/detect.d.ts +0 -11
  12. package/dist/i18n/detect.d.ts.map +1 -1
  13. package/dist/i18n/detect.js +1 -52
  14. package/dist/i18n/i18n.d.ts +4 -14
  15. package/dist/i18n/i18n.d.ts.map +1 -1
  16. package/dist/i18n/i18n.js +19 -25
  17. package/dist/i18n/index.d.ts +1 -1
  18. package/dist/i18n/index.d.ts.map +1 -1
  19. package/dist/i18n/index.js +1 -1
  20. package/dist/i18n/middleware.d.ts +2 -5
  21. package/dist/i18n/middleware.d.ts.map +1 -1
  22. package/dist/i18n/middleware.js +12 -23
  23. package/dist/lib/constants.d.ts.map +1 -1
  24. package/dist/lib/image.d.ts.map +1 -1
  25. package/dist/lib/schemas.d.ts.map +1 -1
  26. package/dist/lib/sse.d.ts +45 -17
  27. package/dist/lib/sse.d.ts.map +1 -1
  28. package/dist/lib/sse.js +77 -37
  29. package/dist/middleware/auth.d.ts.map +1 -1
  30. package/dist/routes/api/posts.js +0 -1
  31. package/dist/routes/api/upload.js +3 -1
  32. package/dist/routes/dash/collections.d.ts.map +1 -1
  33. package/dist/routes/dash/collections.js +134 -142
  34. package/dist/routes/dash/index.js +25 -26
  35. package/dist/routes/dash/media.d.ts.map +1 -1
  36. package/dist/routes/dash/media.js +60 -56
  37. package/dist/routes/dash/pages.js +64 -66
  38. package/dist/routes/dash/posts.d.ts.map +1 -1
  39. package/dist/routes/dash/posts.js +50 -59
  40. package/dist/routes/dash/redirects.d.ts.map +1 -1
  41. package/dist/routes/dash/redirects.js +63 -60
  42. package/dist/routes/dash/settings.d.ts.map +1 -1
  43. package/dist/routes/dash/settings.js +249 -93
  44. package/dist/routes/feed/rss.js +6 -4
  45. package/dist/routes/pages/archive.js +60 -62
  46. package/dist/routes/pages/collection.js +8 -8
  47. package/dist/routes/pages/home.js +14 -14
  48. package/dist/routes/pages/page.js +7 -6
  49. package/dist/routes/pages/post.js +8 -8
  50. package/dist/routes/pages/search.js +25 -27
  51. package/dist/services/collection.d.ts.map +1 -1
  52. package/dist/services/index.d.ts.map +1 -1
  53. package/dist/services/media.d.ts.map +1 -1
  54. package/dist/services/post.d.ts.map +1 -1
  55. package/dist/services/redirect.d.ts.map +1 -1
  56. package/dist/services/settings.d.ts.map +1 -1
  57. package/dist/theme/components/ActionButtons.d.ts +1 -1
  58. package/dist/theme/components/ActionButtons.d.ts.map +1 -1
  59. package/dist/theme/components/ActionButtons.js +17 -21
  60. package/dist/theme/components/CrudPageHeader.d.ts.map +1 -1
  61. package/dist/theme/components/DangerZone.d.ts.map +1 -1
  62. package/dist/theme/components/DangerZone.js +12 -15
  63. package/dist/theme/components/EmptyState.d.ts.map +1 -1
  64. package/dist/theme/components/PageForm.d.ts.map +1 -1
  65. package/dist/theme/components/PageForm.js +58 -56
  66. package/dist/theme/components/Pagination.d.ts.map +1 -1
  67. package/dist/theme/components/Pagination.js +22 -25
  68. package/dist/theme/components/PostForm.d.ts +0 -1
  69. package/dist/theme/components/PostForm.d.ts.map +1 -1
  70. package/dist/theme/components/PostForm.js +85 -77
  71. package/dist/theme/components/PostList.d.ts.map +1 -1
  72. package/dist/theme/components/PostList.js +17 -17
  73. package/dist/theme/components/ThreadView.d.ts.map +1 -1
  74. package/dist/theme/components/ThreadView.js +15 -18
  75. package/dist/theme/components/TypeBadge.d.ts.map +1 -1
  76. package/dist/theme/components/TypeBadge.js +20 -20
  77. package/dist/theme/components/VisibilityBadge.d.ts.map +1 -1
  78. package/dist/theme/components/VisibilityBadge.js +14 -14
  79. package/dist/theme/components/index.d.ts +1 -1
  80. package/dist/theme/components/index.d.ts.map +1 -1
  81. package/dist/theme/layouts/BaseLayout.d.ts.map +1 -1
  82. package/dist/theme/layouts/BaseLayout.js +4 -2
  83. package/dist/theme/layouts/DashLayout.d.ts.map +1 -1
  84. package/dist/theme/layouts/DashLayout.js +29 -29
  85. package/dist/types/lingui-react-macro.d.js +9 -0
  86. package/dist/types.d.ts +2 -0
  87. package/dist/types.d.ts.map +1 -1
  88. package/dist/vendor/datastar.js +1606 -0
  89. package/package.json +5 -2
  90. package/src/app.tsx +175 -56
  91. package/src/auth.ts +5 -1
  92. package/src/client.ts +1 -1
  93. package/src/db/schema.ts +22 -7
  94. package/src/i18n/EXAMPLES.md +34 -14
  95. package/src/i18n/README.md +19 -9
  96. package/src/i18n/context.tsx +1 -4
  97. package/src/i18n/detect.ts +1 -67
  98. package/src/i18n/i18n.ts +15 -19
  99. package/src/i18n/index.ts +0 -3
  100. package/src/i18n/middleware.ts +12 -24
  101. package/src/lib/constants.ts +2 -1
  102. package/src/lib/image-processor.ts +23 -7
  103. package/src/lib/image.ts +6 -2
  104. package/src/lib/schemas.ts +6 -2
  105. package/src/lib/sse.ts +138 -50
  106. package/src/middleware/auth.ts +6 -2
  107. package/src/routes/api/posts.ts +14 -5
  108. package/src/routes/api/upload.ts +25 -7
  109. package/src/routes/dash/collections.tsx +162 -70
  110. package/src/routes/dash/index.tsx +22 -7
  111. package/src/routes/dash/media.tsx +59 -16
  112. package/src/routes/dash/pages.tsx +102 -44
  113. package/src/routes/dash/posts.tsx +87 -54
  114. package/src/routes/dash/redirects.tsx +74 -26
  115. package/src/routes/dash/settings.tsx +250 -57
  116. package/src/routes/feed/rss.ts +6 -4
  117. package/src/routes/pages/archive.tsx +71 -21
  118. package/src/routes/pages/collection.tsx +21 -6
  119. package/src/routes/pages/home.tsx +30 -9
  120. package/src/routes/pages/page.tsx +14 -5
  121. package/src/routes/pages/post.tsx +21 -7
  122. package/src/routes/pages/search.tsx +42 -11
  123. package/src/services/collection.ts +34 -9
  124. package/src/services/index.ts +4 -1
  125. package/src/services/media.ts +15 -3
  126. package/src/services/post.ts +39 -10
  127. package/src/services/redirect.ts +4 -1
  128. package/src/services/settings.ts +14 -3
  129. package/src/theme/components/ActionButtons.tsx +26 -14
  130. package/src/theme/components/CrudPageHeader.tsx +6 -1
  131. package/src/theme/components/DangerZone.tsx +19 -13
  132. package/src/theme/components/EmptyState.tsx +6 -1
  133. package/src/theme/components/PageForm.tsx +71 -24
  134. package/src/theme/components/Pagination.tsx +26 -8
  135. package/src/theme/components/PostForm.tsx +72 -25
  136. package/src/theme/components/PostList.tsx +16 -5
  137. package/src/theme/components/ThreadView.tsx +25 -7
  138. package/src/theme/components/TypeBadge.tsx +13 -4
  139. package/src/theme/components/VisibilityBadge.tsx +17 -5
  140. package/src/theme/components/index.ts +4 -1
  141. package/src/theme/layouts/BaseLayout.tsx +5 -2
  142. package/src/theme/layouts/DashLayout.tsx +41 -12
  143. package/src/types/lingui-react-macro.d.ts +34 -0
  144. package/src/types.ts +16 -2
  145. package/src/vendor/datastar.js +9 -0
  146. package/src/vendor/datastar.js.map +7 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jant/core",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "description": "A modern, open-source microblogging platform built on Cloudflare Workers",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,6 +15,10 @@
15
15
  "types": "./dist/theme/index.d.ts",
16
16
  "default": "./dist/theme/index.js"
17
17
  },
18
+ "./i18n": {
19
+ "types": "./dist/i18n/index.d.ts",
20
+ "default": "./dist/i18n/index.js"
21
+ },
18
22
  "./preset.css": "./src/preset.css",
19
23
  "./client": "./dist/client.js"
20
24
  },
@@ -29,7 +33,6 @@
29
33
  },
30
34
  "dependencies": {
31
35
  "@lingui/core": "^5.9.0",
32
- "@sudodevnull/datastar": "^0.19.9",
33
36
  "better-auth": "^1.4.18",
34
37
  "drizzle-orm": "^0.45.1",
35
38
  "hono": "^4.11.7",
package/src/app.tsx CHANGED
@@ -7,7 +7,8 @@ import type { FC } from "hono/jsx";
7
7
  import { createDatabase } from "./db/index.js";
8
8
  import { createServices, type Services } from "./services/index.js";
9
9
  import { createAuth, type Auth } from "./auth.js";
10
- import { i18nMiddleware, useLingui } from "./i18n/index.js";
10
+ import { i18nMiddleware } from "./i18n/index.js";
11
+ import { useLingui } from "@lingui/react/macro";
11
12
  import type { Bindings, JantConfig } from "./types.js";
12
13
 
13
14
  // Routes - Pages
@@ -41,6 +42,7 @@ import { requireAuth } from "./middleware/auth.js";
41
42
 
42
43
  // Layouts for auth pages
43
44
  import { BaseLayout } from "./theme/layouts/index.js";
45
+ import { sse } from "./lib/sse.js";
44
46
 
45
47
  // Extend Hono's context variables
46
48
  export interface AppVariables {
@@ -123,7 +125,7 @@ export function createApp(config: JantConfig = {}): App {
123
125
  status: "ok",
124
126
  auth: c.env.AUTH_SECRET ? "configured" : "missing",
125
127
  authSecretLength: c.env.AUTH_SECRET?.length ?? 0,
126
- })
128
+ }),
127
129
  );
128
130
 
129
131
  // better-auth handler
@@ -138,7 +140,7 @@ export function createApp(config: JantConfig = {}): App {
138
140
  app.route("/api/posts", postsApiRoutes);
139
141
 
140
142
  // Setup page component
141
- const SetupContent: FC<{ error?: string }> = ({ error }) => {
143
+ const SetupContent: FC = () => {
142
144
  const { t } = useLingui();
143
145
 
144
146
  return (
@@ -146,7 +148,10 @@ export function createApp(config: JantConfig = {}): App {
146
148
  <div class="card max-w-md w-full">
147
149
  <header>
148
150
  <h2>
149
- {t({ message: "Welcome to Jant", comment: "@context: Setup page welcome heading" })}
151
+ {t({
152
+ message: "Welcome to Jant",
153
+ comment: "@context: Setup page welcome heading",
154
+ })}
150
155
  </h2>
151
156
  <p>
152
157
  {t({
@@ -156,15 +161,22 @@ export function createApp(config: JantConfig = {}): App {
156
161
  </p>
157
162
  </header>
158
163
  <section>
159
- {error && <p class="text-destructive text-sm mb-4">{error}</p>}
160
- <form method="post" action="/setup" class="flex flex-col gap-4">
164
+ <div id="setup-message"></div>
165
+ <form
166
+ data-signals="{siteName: '', name: '', email: '', password: ''}"
167
+ data-on:submit__prevent="@post('/setup')"
168
+ class="flex flex-col gap-4"
169
+ >
161
170
  <div class="field">
162
171
  <label class="label">
163
- {t({ message: "Site Name", comment: "@context: Setup form field - site name" })}
172
+ {t({
173
+ message: "Site Name",
174
+ comment: "@context: Setup form field - site name",
175
+ })}
164
176
  </label>
165
177
  <input
166
178
  type="text"
167
- name="siteName"
179
+ data-bind="siteName"
168
180
  class="input"
169
181
  required
170
182
  placeholder={t({
@@ -175,17 +187,29 @@ export function createApp(config: JantConfig = {}): App {
175
187
  </div>
176
188
  <div class="field">
177
189
  <label class="label">
178
- {t({ message: "Your Name", comment: "@context: Setup form field - user name" })}
190
+ {t({
191
+ message: "Your Name",
192
+ comment: "@context: Setup form field - user name",
193
+ })}
179
194
  </label>
180
- <input type="text" name="name" class="input" required placeholder="John Doe" />
195
+ <input
196
+ type="text"
197
+ data-bind="name"
198
+ class="input"
199
+ required
200
+ placeholder="John Doe"
201
+ />
181
202
  </div>
182
203
  <div class="field">
183
204
  <label class="label">
184
- {t({ message: "Email", comment: "@context: Setup/signin form field - email" })}
205
+ {t({
206
+ message: "Email",
207
+ comment: "@context: Setup/signin form field - email",
208
+ })}
185
209
  </label>
186
210
  <input
187
211
  type="email"
188
- name="email"
212
+ data-bind="email"
189
213
  class="input"
190
214
  required
191
215
  placeholder="you@example.com"
@@ -198,10 +222,19 @@ export function createApp(config: JantConfig = {}): App {
198
222
  comment: "@context: Setup/signin form field - password",
199
223
  })}
200
224
  </label>
201
- <input type="password" name="password" class="input" required minLength={8} />
225
+ <input
226
+ type="password"
227
+ data-bind="password"
228
+ class="input"
229
+ required
230
+ minLength={8}
231
+ />
202
232
  </div>
203
233
  <button type="submit" class="btn">
204
- {t({ message: "Complete Setup", comment: "@context: Setup form submit button" })}
234
+ {t({
235
+ message: "Complete Setup",
236
+ comment: "@context: Setup form submit button",
237
+ })}
205
238
  </button>
206
239
  </form>
207
240
  </section>
@@ -215,12 +248,10 @@ export function createApp(config: JantConfig = {}): App {
215
248
  const isComplete = await c.var.services.settings.isOnboardingComplete();
216
249
  if (isComplete) return c.redirect("/");
217
250
 
218
- const error = c.req.query("error");
219
-
220
251
  return c.html(
221
252
  <BaseLayout title="Setup - Jant" c={c}>
222
- <SetupContent error={error} />
223
- </BaseLayout>
253
+ <SetupContent />
254
+ </BaseLayout>,
224
255
  );
225
256
  });
226
257
 
@@ -228,22 +259,36 @@ export function createApp(config: JantConfig = {}): App {
228
259
  const isComplete = await c.var.services.settings.isOnboardingComplete();
229
260
  if (isComplete) return c.redirect("/");
230
261
 
231
- const formData = await c.req.formData();
232
- const siteName = formData.get("siteName") as string;
233
- const name = formData.get("name") as string;
234
- const email = formData.get("email") as string;
235
- const password = formData.get("password") as string;
262
+ const body = await c.req.json<{
263
+ siteName: string;
264
+ name: string;
265
+ email: string;
266
+ password: string;
267
+ }>();
268
+ const { siteName, name, email, password } = body;
236
269
 
237
270
  if (!siteName || !name || !email || !password) {
238
- return c.redirect("/setup?error=All fields are required");
271
+ return sse(c, async (stream) => {
272
+ await stream.patchElements(
273
+ '<div id="setup-message"><p class="text-destructive text-sm mb-4">All fields are required</p></div>',
274
+ );
275
+ });
239
276
  }
240
277
 
241
278
  if (password.length < 8) {
242
- return c.redirect("/setup?error=Password must be at least 8 characters");
279
+ return sse(c, async (stream) => {
280
+ await stream.patchElements(
281
+ '<div id="setup-message"><p class="text-destructive text-sm mb-4">Password must be at least 8 characters</p></div>',
282
+ );
283
+ });
243
284
  }
244
285
 
245
286
  if (!c.var.auth) {
246
- return c.redirect("/setup?error=AUTH_SECRET not configured");
287
+ return sse(c, async (stream) => {
288
+ await stream.patchElements(
289
+ '<div id="setup-message"><p class="text-destructive text-sm mb-4">AUTH_SECRET not configured</p></div>',
290
+ );
291
+ });
247
292
  }
248
293
 
249
294
  try {
@@ -252,7 +297,11 @@ export function createApp(config: JantConfig = {}): App {
252
297
  });
253
298
 
254
299
  if (!signUpResponse || "error" in signUpResponse) {
255
- return c.redirect("/setup?error=Failed to create account");
300
+ return sse(c, async (stream) => {
301
+ await stream.patchElements(
302
+ '<div id="setup-message"><p class="text-destructive text-sm mb-4">Failed to create account</p></div>',
303
+ );
304
+ });
256
305
  }
257
306
 
258
307
  await c.var.services.settings.setMany({
@@ -261,32 +310,66 @@ export function createApp(config: JantConfig = {}): App {
261
310
  });
262
311
  await c.var.services.settings.completeOnboarding();
263
312
 
264
- return c.redirect("/signin");
313
+ return sse(c, async (stream) => {
314
+ await stream.redirect("/signin");
315
+ });
265
316
  } catch (err) {
266
317
  // eslint-disable-next-line no-console -- Error logging is intentional
267
318
  console.error("Setup error:", err);
268
- return c.redirect("/setup?error=Failed to create account");
319
+ return sse(c, async (stream) => {
320
+ await stream.patchElements(
321
+ '<div id="setup-message"><p class="text-destructive text-sm mb-4">Failed to create account</p></div>',
322
+ );
323
+ });
269
324
  }
270
325
  });
271
326
 
272
327
  // Signin page component
273
- const SigninContent: FC<{ error?: string }> = ({ error }) => {
328
+ const SigninContent: FC<{
329
+ demoEmail?: string;
330
+ demoPassword?: string;
331
+ }> = ({ demoEmail, demoPassword }) => {
274
332
  const { t } = useLingui();
333
+ const signals = JSON.stringify({
334
+ email: demoEmail || "",
335
+ password: demoPassword || "",
336
+ }).replace(/</g, "\\u003c");
275
337
 
276
338
  return (
277
339
  <div class="min-h-screen flex items-center justify-center">
278
340
  <div class="card max-w-md w-full">
279
341
  <header>
280
- <h2>{t({ message: "Sign In", comment: "@context: Sign in page heading" })}</h2>
342
+ <h2>
343
+ {t({
344
+ message: "Sign In",
345
+ comment: "@context: Sign in page heading",
346
+ })}
347
+ </h2>
281
348
  </header>
282
349
  <section>
283
- {error && <p class="text-destructive text-sm mb-4">{error}</p>}
284
- <form method="post" action="/signin" class="flex flex-col gap-4">
350
+ <div id="signin-message"></div>
351
+ {demoEmail && demoPassword && (
352
+ <p class="text-muted-foreground text-sm mb-4">
353
+ {t({
354
+ message: "Demo account pre-filled. Just click Sign In.",
355
+ comment:
356
+ "@context: Hint shown on signin page when demo credentials are pre-filled",
357
+ })}
358
+ </p>
359
+ )}
360
+ <form
361
+ data-signals={signals}
362
+ data-on:submit__prevent="@post('/signin')"
363
+ class="flex flex-col gap-4"
364
+ >
285
365
  <div class="field">
286
366
  <label class="label">
287
- {t({ message: "Email", comment: "@context: Setup/signin form field - email" })}
367
+ {t({
368
+ message: "Email",
369
+ comment: "@context: Setup/signin form field - email",
370
+ })}
288
371
  </label>
289
- <input type="email" name="email" class="input" required />
372
+ <input type="email" data-bind="email" class="input" required />
290
373
  </div>
291
374
  <div class="field">
292
375
  <label class="label">
@@ -295,10 +378,18 @@ export function createApp(config: JantConfig = {}): App {
295
378
  comment: "@context: Setup/signin form field - password",
296
379
  })}
297
380
  </label>
298
- <input type="password" name="password" class="input" required />
381
+ <input
382
+ type="password"
383
+ data-bind="password"
384
+ class="input"
385
+ required
386
+ />
299
387
  </div>
300
388
  <button type="submit" class="btn">
301
- {t({ message: "Sign In", comment: "@context: Sign in form submit button" })}
389
+ {t({
390
+ message: "Sign In",
391
+ comment: "@context: Sign in form submit button",
392
+ })}
302
393
  </button>
303
394
  </form>
304
395
  </section>
@@ -309,45 +400,70 @@ export function createApp(config: JantConfig = {}): App {
309
400
 
310
401
  // Signin page
311
402
  app.get("/signin", async (c) => {
312
- const error = c.req.query("error");
313
-
314
403
  return c.html(
315
404
  <BaseLayout title="Sign In - Jant" c={c}>
316
- <SigninContent error={error} />
317
- </BaseLayout>
405
+ <SigninContent
406
+ demoEmail={c.env.DEMO_EMAIL}
407
+ demoPassword={c.env.DEMO_PASSWORD}
408
+ />
409
+ </BaseLayout>,
318
410
  );
319
411
  });
320
412
 
321
413
  app.post("/signin", async (c) => {
322
414
  if (!c.var.auth) {
323
- return c.redirect("/signin?error=Auth not configured");
415
+ return sse(c, async (stream) => {
416
+ await stream.patchElements(
417
+ '<div id="signin-message"><p class="text-destructive text-sm mb-4">Auth not configured</p></div>',
418
+ );
419
+ });
324
420
  }
325
421
 
326
- const formData = await c.req.formData();
327
- const email = formData.get("email") as string;
328
- const password = formData.get("password") as string;
422
+ const body = await c.req.json<{ email: string; password: string }>();
423
+ const { email, password } = body;
329
424
 
330
425
  try {
331
- const signInRequest = new Request(`${c.env.SITE_URL}/api/auth/sign-in/email`, {
332
- method: "POST",
333
- headers: { "Content-Type": "application/json" },
334
- body: JSON.stringify({ email, password }),
335
- });
426
+ const signInRequest = new Request(
427
+ `${c.env.SITE_URL}/api/auth/sign-in/email`,
428
+ {
429
+ method: "POST",
430
+ headers: { "Content-Type": "application/json" },
431
+ body: JSON.stringify({ email, password }),
432
+ },
433
+ );
336
434
 
337
435
  const response = await c.var.auth.handler(signInRequest);
338
436
 
339
437
  if (!response.ok) {
340
- return c.redirect("/signin?error=Invalid email or password");
438
+ return sse(c, async (stream) => {
439
+ await stream.patchElements(
440
+ '<div id="signin-message"><p class="text-destructive text-sm mb-4">Invalid email or password</p></div>',
441
+ );
442
+ });
341
443
  }
342
444
 
343
- const headers = new Headers(response.headers);
344
- headers.set("Location", "/dash");
445
+ // Forward Set-Cookie headers from auth response
446
+ const cookieHeaders: Record<string, string> = {};
447
+ const setCookie = response.headers.get("set-cookie");
448
+ if (setCookie) {
449
+ cookieHeaders["Set-Cookie"] = setCookie;
450
+ }
345
451
 
346
- return new Response(null, { status: 302, headers });
452
+ return sse(
453
+ c,
454
+ async (stream) => {
455
+ await stream.redirect("/dash");
456
+ },
457
+ { headers: cookieHeaders },
458
+ );
347
459
  } catch (err) {
348
460
  // eslint-disable-next-line no-console -- Error logging is intentional
349
461
  console.error("Signin error:", err);
350
- return c.redirect("/signin?error=Invalid email or password");
462
+ return sse(c, async (stream) => {
463
+ await stream.patchElements(
464
+ '<div id="signin-message"><p class="text-destructive text-sm mb-4">Invalid email or password</p></div>',
465
+ );
466
+ });
351
467
  }
352
468
  });
353
469
 
@@ -397,7 +513,10 @@ export function createApp(config: JantConfig = {}): App {
397
513
  }
398
514
 
399
515
  const headers = new Headers();
400
- headers.set("Content-Type", object.httpMetadata?.contentType || media.mimeType);
516
+ headers.set(
517
+ "Content-Type",
518
+ object.httpMetadata?.contentType || media.mimeType,
519
+ );
401
520
  headers.set("Cache-Control", "public, max-age=31536000, immutable");
402
521
 
403
522
  return new Response(object.body, { headers });
package/src/auth.ts CHANGED
@@ -7,7 +7,10 @@ import { drizzleAdapter } from "better-auth/adapters/drizzle";
7
7
  import { drizzle } from "drizzle-orm/d1";
8
8
  import * as schema from "./db/schema.js";
9
9
 
10
- export function createAuth(d1: D1Database, options: { secret: string; baseURL: string }) {
10
+ export function createAuth(
11
+ d1: D1Database,
12
+ options: { secret: string; baseURL: string },
13
+ ) {
11
14
  const db = drizzle(d1, { schema });
12
15
 
13
16
  return betterAuth({
@@ -25,6 +28,7 @@ export function createAuth(d1: D1Database, options: { secret: string; baseURL: s
25
28
  emailAndPassword: {
26
29
  enabled: true,
27
30
  autoSignIn: true,
31
+ minPasswordLength: 8,
28
32
  },
29
33
  session: {
30
34
  cookieCache: {
package/src/client.ts CHANGED
@@ -7,6 +7,6 @@
7
7
  * - ImageProcessor (media uploads)
8
8
  */
9
9
 
10
- import "@sudodevnull/datastar";
10
+ import "./vendor/datastar.js";
11
11
  import "basecoat-css/all";
12
12
  import "./lib/image-processor.js";
package/src/db/schema.ts CHANGED
@@ -4,7 +4,12 @@
4
4
  * Database schema for Jant
5
5
  */
6
6
 
7
- import { sqliteTable, text, integer, primaryKey } from "drizzle-orm/sqlite-core";
7
+ import {
8
+ sqliteTable,
9
+ text,
10
+ integer,
11
+ primaryKey,
12
+ } from "drizzle-orm/sqlite-core";
8
13
 
9
14
  // =============================================================================
10
15
  // Posts
@@ -12,8 +17,12 @@ import { sqliteTable, text, integer, primaryKey } from "drizzle-orm/sqlite-core"
12
17
 
13
18
  export const posts = sqliteTable("posts", {
14
19
  id: integer("id").primaryKey({ autoIncrement: true }),
15
- type: text("type", { enum: ["note", "article", "link", "quote", "image", "page"] }).notNull(),
16
- visibility: text("visibility", { enum: ["featured", "quiet", "unlisted", "draft"] })
20
+ type: text("type", {
21
+ enum: ["note", "article", "link", "quote", "image", "page"],
22
+ }).notNull(),
23
+ visibility: text("visibility", {
24
+ enum: ["featured", "quiet", "unlisted", "draft"],
25
+ })
17
26
  .notNull()
18
27
  .default("quiet"),
19
28
  title: text("title"),
@@ -77,7 +86,7 @@ export const postCollections = sqliteTable(
77
86
  .references(() => collections.id),
78
87
  addedAt: integer("added_at").notNull(),
79
88
  },
80
- (table) => [primaryKey({ columns: [table.postId, table.collectionId] })]
89
+ (table) => [primaryKey({ columns: [table.postId, table.collectionId] })],
81
90
  );
82
91
 
83
92
  // =============================================================================
@@ -111,7 +120,9 @@ export const user = sqliteTable("user", {
111
120
  id: text("id").primaryKey(),
112
121
  name: text("name").notNull(),
113
122
  email: text("email").notNull().unique(),
114
- emailVerified: integer("email_verified", { mode: "boolean" }).notNull().default(false),
123
+ emailVerified: integer("email_verified", { mode: "boolean" })
124
+ .notNull()
125
+ .default(false),
115
126
  image: text("image"),
116
127
  role: text("role").default("admin"),
117
128
  createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
@@ -141,8 +152,12 @@ export const account = sqliteTable("account", {
141
152
  accessToken: text("access_token"),
142
153
  refreshToken: text("refresh_token"),
143
154
  idToken: text("id_token"),
144
- accessTokenExpiresAt: integer("access_token_expires_at", { mode: "timestamp" }),
145
- refreshTokenExpiresAt: integer("refresh_token_expires_at", { mode: "timestamp" }),
155
+ accessTokenExpiresAt: integer("access_token_expires_at", {
156
+ mode: "timestamp",
157
+ }),
158
+ refreshTokenExpiresAt: integer("refresh_token_expires_at", {
159
+ mode: "timestamp",
160
+ }),
146
161
  scope: text("scope"),
147
162
  password: text("password"),
148
163
  createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
@@ -14,7 +14,7 @@ dashRoute.get("/", async (c) => {
14
14
  return c.html(
15
15
  <I18nProvider c={c}>
16
16
  <MyApp />
17
- </I18nProvider>
17
+ </I18nProvider>,
18
18
  );
19
19
  });
20
20
 
@@ -22,7 +22,9 @@ dashRoute.get("/", async (c) => {
22
22
  function MyApp() {
23
23
  const { t } = useLingui();
24
24
 
25
- return <h1>{t({ message: "Dashboard", comment: "@context: Page title" })}</h1>;
25
+ return (
26
+ <h1>{t({ message: "Dashboard", comment: "@context: Page title" })}</h1>
27
+ );
26
28
  }
27
29
  ```
28
30
 
@@ -53,12 +55,18 @@ dashRoute.get("/", async (c) => {
53
55
  return c.html(
54
56
  <I18nProvider c={c}>
55
57
  <Dashboard postCount={posts.length} username="Alice" />
56
- </I18nProvider>
58
+ </I18nProvider>,
57
59
  );
58
60
  });
59
61
 
60
62
  // Component: use useLingui() hook
61
- function Dashboard({ postCount, username }: { postCount: number; username: string }) {
63
+ function Dashboard({
64
+ postCount,
65
+ username,
66
+ }: {
67
+ postCount: number;
68
+ username: string;
69
+ }) {
62
70
  const { t } = useLingui();
63
71
 
64
72
  return (
@@ -69,16 +77,22 @@ function Dashboard({ postCount, username }: { postCount: number; username: strin
69
77
  {/* 2. With variables */}
70
78
  <p>
71
79
  {t(
72
- { message: "Welcome back, {name}!", comment: "@context: Welcome message" },
73
- { name: username }
80
+ {
81
+ message: "Welcome back, {name}!",
82
+ comment: "@context: Welcome message",
83
+ },
84
+ { name: username },
74
85
  )}
75
86
  </p>
76
87
 
77
88
  {/* 3. With dynamic values */}
78
89
  <p>
79
90
  {t(
80
- { message: "You have {count} posts", comment: "@context: Post count" },
81
- { count: postCount }
91
+ {
92
+ message: "You have {count} posts",
93
+ comment: "@context: Post count",
94
+ },
95
+ { count: postCount },
82
96
  )}
83
97
  </p>
84
98
 
@@ -130,11 +144,14 @@ const { t } = useLingui();
130
144
  // For dynamic content, use t() with placeholders
131
145
  <p>
132
146
  {t(
133
- { message: "Visit {linkStart}our website{linkEnd}", comment: "@context: Website link" },
147
+ {
148
+ message: "Visit {linkStart}our website{linkEnd}",
149
+ comment: "@context: Website link",
150
+ },
134
151
  {
135
152
  linkStart: '<a href="/" class="text-primary">',
136
153
  linkEnd: "</a>",
137
- }
154
+ },
138
155
  )}
139
156
  </p>;
140
157
  ```
@@ -154,7 +171,7 @@ dashRoute.get("/", async (c) => {
154
171
  return c.html(
155
172
  <Layout>
156
173
  <MyComponent c={c} /> {/* Must pass c prop */}
157
- </Layout>
174
+ </Layout>,
158
175
  );
159
176
  });
160
177
 
@@ -163,7 +180,7 @@ function MyComponent({ c }: { c: Context }) {
163
180
  const title = i18n._({ message: "Dashboard", comment: "@context: Title" });
164
181
  const greeting = i18n._(
165
182
  { message: "Hello {name}", comment: "@context: Greeting" },
166
- { name: "Alice" }
183
+ { name: "Alice" },
167
184
  );
168
185
 
169
186
  return <h1>{title}</h1>;
@@ -181,14 +198,17 @@ dashRoute.get("/", async (c) => {
181
198
  <Layout>
182
199
  <MyComponent /> {/* No props needed */}
183
200
  </Layout>
184
- </I18nProvider>
201
+ </I18nProvider>,
185
202
  );
186
203
  });
187
204
 
188
205
  function MyComponent() {
189
206
  const { t } = useLingui();
190
207
  const title = t({ message: "Dashboard", comment: "@context: Title" });
191
- const greeting = t({ message: "Hello {name}", comment: "@context: Greeting" }, { name: "Alice" });
208
+ const greeting = t(
209
+ { message: "Hello {name}", comment: "@context: Greeting" },
210
+ { name: "Alice" },
211
+ );
192
212
 
193
213
  return <h1>{title}</h1>;
194
214
  }