ugly-app 0.1.43 → 0.1.45

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 (103) hide show
  1. package/README.md +218 -111
  2. package/dist/cli/initDb.d.ts.map +1 -1
  3. package/dist/cli/initDb.js +33 -1
  4. package/dist/cli/initDb.js.map +1 -1
  5. package/dist/cli/scaffold.d.ts.map +1 -1
  6. package/dist/cli/scaffold.js +13 -9
  7. package/dist/cli/scaffold.js.map +1 -1
  8. package/dist/cli/uglyBotLogin.d.ts.map +1 -1
  9. package/dist/cli/uglyBotLogin.js.map +1 -1
  10. package/dist/cli/upgrade.d.ts.map +1 -1
  11. package/dist/cli/upgrade.js +5 -5
  12. package/dist/cli/upgrade.js.map +1 -1
  13. package/dist/client/Logger.d.ts +11 -0
  14. package/dist/client/Logger.d.ts.map +1 -1
  15. package/dist/client/Logger.js +82 -22
  16. package/dist/client/Logger.js.map +1 -1
  17. package/dist/client/Router.d.ts +5 -3
  18. package/dist/client/Router.d.ts.map +1 -1
  19. package/dist/client/Router.js +25 -10
  20. package/dist/client/Router.js.map +1 -1
  21. package/dist/client/callAI.d.ts.map +1 -1
  22. package/dist/client/callAI.js +7 -2
  23. package/dist/client/callAI.js.map +1 -1
  24. package/dist/client/createHttpClient.d.ts +13 -0
  25. package/dist/client/createHttpClient.d.ts.map +1 -0
  26. package/dist/client/createHttpClient.js +24 -0
  27. package/dist/client/createHttpClient.js.map +1 -0
  28. package/dist/client/createSocket.d.ts +1 -3
  29. package/dist/client/createSocket.d.ts.map +1 -1
  30. package/dist/client/createSocket.js +0 -11
  31. package/dist/client/createSocket.js.map +1 -1
  32. package/dist/client/index.d.ts +4 -2
  33. package/dist/client/index.d.ts.map +1 -1
  34. package/dist/client/index.js +2 -1
  35. package/dist/client/index.js.map +1 -1
  36. package/dist/server/App.d.ts +4 -4
  37. package/dist/server/App.d.ts.map +1 -1
  38. package/dist/server/App.js +54 -14
  39. package/dist/server/App.js.map +1 -1
  40. package/dist/server/Auth.d.ts.map +1 -1
  41. package/dist/server/Auth.js +14 -11
  42. package/dist/server/Auth.js.map +1 -1
  43. package/dist/server/ErrorLogAdmin.d.ts +50 -0
  44. package/dist/server/ErrorLogAdmin.d.ts.map +1 -0
  45. package/dist/server/ErrorLogAdmin.js +143 -0
  46. package/dist/server/ErrorLogAdmin.js.map +1 -0
  47. package/dist/server/FeedbackReport.d.ts +2 -3
  48. package/dist/server/FeedbackReport.d.ts.map +1 -1
  49. package/dist/server/FeedbackReport.js +4 -5
  50. package/dist/server/FeedbackReport.js.map +1 -1
  51. package/dist/server/Logging.d.ts +6 -0
  52. package/dist/server/Logging.d.ts.map +1 -1
  53. package/dist/server/Logging.js +97 -45
  54. package/dist/server/Logging.js.map +1 -1
  55. package/dist/server/Router.d.ts +12 -25
  56. package/dist/server/Router.d.ts.map +1 -1
  57. package/dist/server/Router.js +66 -48
  58. package/dist/server/Router.js.map +1 -1
  59. package/dist/server/Socket.d.ts.map +1 -1
  60. package/dist/server/Socket.js +3 -1
  61. package/dist/server/Socket.js.map +1 -1
  62. package/dist/server/index.d.ts +6 -4
  63. package/dist/server/index.d.ts.map +1 -1
  64. package/dist/server/index.js +3 -2
  65. package/dist/server/index.js.map +1 -1
  66. package/dist/shared/Api.d.ts +18 -20
  67. package/dist/shared/Api.d.ts.map +1 -1
  68. package/dist/shared/Api.js +9 -9
  69. package/dist/shared/Api.js.map +1 -1
  70. package/dist/shared/DB.d.ts +29 -8
  71. package/dist/shared/DB.d.ts.map +1 -1
  72. package/dist/shared/DB.js.map +1 -1
  73. package/dist/shared/Socket.d.ts +4 -6
  74. package/dist/shared/Socket.d.ts.map +1 -1
  75. package/package.json +1 -1
  76. package/templates/CLAUDE.md +26 -7
  77. package/templates/client/App.tsx +1 -1
  78. package/templates/client/allPages.ts +12 -1
  79. package/templates/client/main.tsx +3 -3
  80. package/templates/client/pages/AITestPage.tsx +29 -12
  81. package/templates/client/pages/AuthDemoPage.tsx +34 -9
  82. package/templates/client/pages/HomePage.tsx +51 -14
  83. package/templates/client/pages/UserPage.tsx +5 -1
  84. package/templates/package.json +1 -1
  85. package/templates/server/index.ts +16 -26
  86. package/templates/shared/api.ts +22 -16
  87. package/templates/shared/collections.ts +43 -2
  88. package/templates/shared/pages.ts +12 -0
  89. package/templates/vite.config.ts +1 -0
  90. package/templates/shared/dbIndexes.ts +0 -11
  91. package/templates/shared/types.ts +0 -6
  92. /package/templates/.claude/skills/{assets.md → assets/SKILL.md} +0 -0
  93. /package/templates/.claude/skills/{check-errors.md → check-errors/SKILL.md} +0 -0
  94. /package/templates/.claude/skills/{check-feedback.md → check-feedback/SKILL.md} +0 -0
  95. /package/templates/.claude/skills/{check-perf.md → check-perf/SKILL.md} +0 -0
  96. /package/templates/.claude/skills/{create-test-users.md → create-test-users/SKILL.md} +0 -0
  97. /package/templates/.claude/skills/{extend-api.md → extend-api/SKILL.md} +0 -0
  98. /package/templates/.claude/skills/{fix-code.md → fix-code/SKILL.md} +0 -0
  99. /package/templates/.claude/skills/{fix-errors.md → fix-errors/SKILL.md} +0 -0
  100. /package/templates/.claude/skills/{fix-feedback.md → fix-feedback/SKILL.md} +0 -0
  101. /package/templates/.claude/skills/{fix-perf.md → fix-perf/SKILL.md} +0 -0
  102. /package/templates/.claude/skills/{uploads.md → uploads/SKILL.md} +0 -0
  103. /package/templates/.claude/skills/{use-ai.md → use-ai/SKILL.md} +0 -0
package/README.md CHANGED
@@ -33,41 +33,39 @@ import {
33
33
  createApp,
34
34
  createUserHelper,
35
35
  getFeedbackHandlers,
36
- type CallHandlers,
36
+ type AppConfigurator,
37
37
  type RequestHandlers,
38
- type HandlerContext,
39
38
  } from 'ugly-app';
40
39
  import { dbDefaults } from 'ugly-app/shared';
41
- import { functions, requests } from '../shared/api';
40
+ import { requests } from '../shared/api';
42
41
  import { collections } from '../shared/collections';
43
42
  import { pages } from '../shared/pages';
44
- import type { User } from '../shared/types';
43
+ import type { User } from '../shared/collections';
45
44
 
46
45
  const userHelper = createUserHelper<User>(collections.user);
47
-
48
- const calls = {
49
- ...getFeedbackHandlers(maintainBotUserId),
50
- myFunction: async (ctx: HandlerContext, input) => {
51
- return { greeting: `Hello, ${input.name}` };
52
- },
53
- } satisfies CallHandlers<typeof functions>;
54
-
55
- const reqs = {
56
- getMe: async (ctx: HandlerContext) => {
57
- const user = await userHelper.get(ctx.db, ctx.userId);
58
- return { userId: ctx.userId, email: user?.email };
46
+ const maintainBotUserId = process.env.MAINTAIN_BOT_USER_ID ?? '';
47
+
48
+ const app = createApp(
49
+ { requests },
50
+ {
51
+ ...getFeedbackHandlers(maintainBotUserId),
52
+ getMe: async (userId: string) => {
53
+ const user = await userHelper.get(app.db, userId);
54
+ return { userId, email: user?.email, phone: user?.phone };
55
+ },
56
+ } satisfies RequestHandlers<typeof requests>,
57
+ collections,
58
+ (configurator: AppConfigurator) => {
59
+ configurator.setPages({ pages });
60
+ configurator.setUserHelper(userHelper);
61
+ configurator.setOnUserCreate(async (userId, info, db) => {
62
+ await userHelper.set(db, { id: userId, ...dbDefaults(), ...info });
63
+ });
59
64
  },
60
- } satisfies RequestHandlers<typeof requests>;
61
-
62
- const app = createApp({ functions, requests }, calls, reqs, collections, (configurator) => {
63
- configurator.setPages({ pages });
64
- configurator.setUserHelper(userHelper);
65
- configurator.setOnUserCreate(async (userId, info, db) => {
66
- await userHelper.set(db, { id: userId, ...dbDefaults(), ...info });
67
- });
68
- });
65
+ );
69
66
 
70
- await app.start(3000);
67
+ const port = parseInt(process.env['PORT'] ?? '3000');
68
+ await app.start(port);
71
69
  ```
72
70
 
73
71
  **Signature:**
@@ -75,7 +73,6 @@ await app.start(3000);
75
73
  ```typescript
76
74
  function createApp<R extends AppRegistryBase, Defs extends CollectionDefRegistry>(
77
75
  registry: R,
78
- calls: Partial<CallHandlers<R['functions']>>,
79
76
  requests: Partial<RequestHandlers<R['requests']>>,
80
77
  appDefs: Defs,
81
78
  configure?: (configurator: AppConfigurator) => void,
@@ -86,7 +83,8 @@ The returned `App` object has:
86
83
  - `start(port?)` — starts the server (default port 3000)
87
84
  - `registerRoutes(fn)` — mount additional Express routes after creation
88
85
  - `httpServer` — the underlying Node.js HTTP server
89
- - `dispatch(type, name, input, userId)` invoke an RPC handler programmatically
86
+ - `db` the `TypedDB` instance for direct database access
87
+ - `dispatch(name, input, userId)` — invoke an RPC handler programmatically
90
88
 
91
89
  ### `AppConfigurator`
92
90
 
@@ -96,24 +94,23 @@ The returned `App` object has:
96
94
  | `setUserHelper(helper)` | User lookup for WebSocket auth handshake |
97
95
  | `setOnUserCreate(handler)` | Called on first login — must create the user record. `(userId, { email?, phone? }, db) => Promise<void>` |
98
96
  | `setAuth(provider)` | Custom `AuthProvider` (default: ugly.bot OAuth). Provider must implement `verify(code)` and `authUrl(origin)` |
99
- | `setContextExtensions(fn)` | Add fields to `HandlerContext` — `(base: HandlerContext) => Promise<E>` |
100
97
  | `setOnSocketMessage(handler)` | Handle raw WebSocket messages. Return `true` to consume, `false` to let the framework handle it |
101
98
  | `registerRoutes(fn)` | Mount custom Express routes — `(router: express.Router) => void` |
102
99
  | `setWorkerQueue(queue)` | Register a background worker queue with `start()` and `stop()` |
103
100
 
104
- ### Handler context (`ctx`)
101
+ ### Handler signatures
102
+
103
+ Handlers are plain async functions — no context object, just `userId` and `input`:
105
104
 
106
- Every RPC handler receives a `HandlerContext`:
105
+ ```typescript
106
+ // req() — public, userId may be null
107
+ getPublicData: async (userId: string | null, input) => { ... }
108
+
109
+ // authReq() — authenticated, 401 auto-enforced, userId always a string
110
+ getMe: async (userId: string, input) => { ... }
111
+ ```
107
112
 
108
- | Field | Type | Description |
109
- |-------|------|-------------|
110
- | `ctx.userId` | `string` | Authenticated user ID (always set) |
111
- | `ctx.db` | `TypedDB` | MongoDB client with typed collection methods |
112
- | `ctx.storage` | `StorageClient` | R2/S3 presigned uploads and public URLs |
113
- | `ctx.textGen` | `TextGenClient` | Text generation (see AI section) |
114
- | `ctx.imageGen` | `ImageGenClient` | Image generation |
115
- | `ctx.log` | `Logger` | Structured logging to MongoDB |
116
- | `ctx.rateLimit` | `RateLimiter` | Token-bucket rate limiting |
113
+ Access `app.db`, storage, and AI clients directly via imports or the `app` object — they are not injected into handlers.
117
114
 
118
115
  ---
119
116
 
@@ -121,31 +118,40 @@ Every RPC handler receives a `HandlerContext`:
121
118
 
122
119
  All type definitions live in `shared/` and are used by both server and client.
123
120
 
124
- ### Functions and requests (`shared/api.ts`)
121
+ ### Requests (`shared/api.ts`)
125
122
 
126
123
  ```typescript
127
- import { defineFunctions, defineRequests, fn, req, z } from 'ugly-app/shared';
124
+ import { authReq, defineRequests, req, z } from 'ugly-app/shared';
128
125
 
129
- export const functions = defineFunctions({
130
- createNote: fn({
131
- input: z.object({ title: z.string(), body: z.string() }),
132
- output: z.object({ id: z.string() }),
126
+ export const requests = defineRequests({
127
+ // Public request — handler receives (userId: string | null, input)
128
+ getPublicData: req({
129
+ input: z.object({ id: z.string() }),
130
+ output: z.object({ data: z.string() }),
133
131
  }),
134
- });
135
132
 
136
- export const requests = defineRequests({
137
- getNotes: req({
133
+ // Authenticated request handler receives (userId: string, input)
134
+ getMe: authReq({
138
135
  input: z.object({}),
139
- output: z.array(z.object({ id: z.string(), title: z.string() })),
136
+ output: z.object({ userId: z.string(), email: z.string().optional() }),
137
+ }),
138
+
139
+ // With rate limiting
140
+ submitFeedback: authReq({
141
+ input: z.object({ type: z.enum(['bug', 'design', 'feature']), message: z.string() }),
142
+ output: z.object({ id: z.string() }),
143
+ rateLimit: { max: 20, window: 60 },
140
144
  }),
141
145
  });
142
146
  ```
143
147
 
144
- - `fn({ input, output })` — defines a mutation (write operation). `input` and `output` are Zod schemas.
145
- - `req({ input, output })` — defines a query (read operation).
146
- - `defineFunctions()` / `defineRequests()` — identity wrappers that preserve types. `defineCalls` is an alias for `defineFunctions`.
148
+ - `req({ input, output })` — defines a public request. Handler signature: `(userId: string | null, input: I) => Promise<O>`.
149
+ - `authReq({ input, output })` — defines an authenticated request. Handler signature: `(userId: string, input: I) => Promise<O>`. Returns 401 automatically if no token.
150
+ - `defineRequests()` — identity wrapper that preserves types.
147
151
  - `z` is re-exported from Zod for convenience.
148
152
 
153
+ Every endpoint is accessible via both WebSocket (`socket.request(name, input)`) and HTTP (`POST /api/:name { input }`).
154
+
149
155
  ### Collections (`shared/collections.ts`)
150
156
 
151
157
  ```typescript
@@ -203,11 +209,11 @@ export type AppPages = typeof pages;
203
209
  ```tsx
204
210
  import { createRoot } from 'react-dom/client';
205
211
  import { AppProvider, createSocket, LoginPopup } from 'ugly-app/client';
206
- import { functions, requests } from '../shared/api';
212
+ import { requests } from '../shared/api';
207
213
  import { RouterProvider } from './router';
208
214
  import App from './App';
209
215
 
210
- const token = (window as any).__AUTH_TOKEN__;
216
+ const token = (window as unknown as { __AUTH_TOKEN__?: string }).__AUTH_TOKEN__;
211
217
  const root = createRoot(document.getElementById('root')!);
212
218
  const loginPopup = <LoginPopup onSuccess={() => window.location.reload()} />;
213
219
 
@@ -219,7 +225,7 @@ if (!token) {
219
225
  );
220
226
  } else {
221
227
  const userId = JSON.parse(atob(token.split('.')[1]!)).sub as string;
222
- const socket = createSocket({ functions, requests, url: '/rpc' });
228
+ const socket = createSocket({ requests, url: '/rpc' });
223
229
  socket.connect(token).then((user) => {
224
230
  root.render(
225
231
  <RouterProvider fallback={<div>404</div>} loginFallback={loginPopup} isAuthenticated={() => true}>
@@ -237,7 +243,7 @@ if (!token) {
237
243
  Creates a typed WebSocket client for RPC communication.
238
244
 
239
245
  ```typescript
240
- const socket = createSocket({ functions, requests, url: '/rpc' });
246
+ const socket = createSocket({ requests, url: '/rpc' });
241
247
  await socket.connect(token); // returns UserBase
242
248
  ```
243
249
 
@@ -246,14 +252,30 @@ await socket.connect(token); // returns UserBase
246
252
  | Method | Description |
247
253
  |--------|-------------|
248
254
  | `connect(token)` | Authenticate and connect. Returns the user object |
249
- | `call(name, input)` | Invoke a function (mutation) |
250
- | `request(name, input)` | Invoke a request (query) |
255
+ | `request(name, input)` | Invoke a request (query or mutation) |
251
256
  | `getDoc(collection, id)` | Fetch a single document |
252
257
  | `trackDoc(collection, id, cb)` | Subscribe to real-time doc changes. Returns unsubscribe fn |
253
258
  | `trackDocs(collection, filter, cb, opts?)` | Subscribe to query results. Returns unsubscribe fn |
254
259
  | `uploadFile(file, key)` | Upload a file via presigned URL |
255
260
  | `disconnect()` | Close the connection |
256
261
 
262
+ ### `createHttpClient()`
263
+
264
+ Creates a typed HTTP client for RPC communication (no WebSocket needed).
265
+
266
+ ```typescript
267
+ import { createHttpClient } from 'ugly-app/client';
268
+
269
+ const client = createHttpClient({ requests, token: 'eyJ...', baseUrl: '' });
270
+ const result = await client.request('getMe', {});
271
+ ```
272
+
273
+ | Option | Description |
274
+ |--------|-------------|
275
+ | `requests` | The requests registry from `shared/api.ts` |
276
+ | `token?` | Bearer token for authenticated requests |
277
+ | `baseUrl?` | URL prefix (default: `''` — relative paths: `POST /api/:name`) |
278
+
257
279
  ### `AppProvider`
258
280
 
259
281
  Wraps your app with context for `useApp()`. Provides user info, socket access, popup management, async loading overlay, splash screen, and localization.
@@ -303,7 +325,7 @@ export const { RouterProvider, RouterView, useRouter } = createRouter({ pages, a
303
325
 
304
326
  `createRouter()` returns three things:
305
327
  - **`RouterProvider`** — wrap your app. Props: `children`, `fallback?` (shown before first route resolves), `loginFallback?` (shown for auth-guarded pages when unauthenticated), `isAuthenticated?` (function returning boolean)
306
- - **`RouterView`** — renders the active page with animated transitions. Props: `durationMs?`, `easing?`, `transitionComponent?`
328
+ - **`RouterView`** — renders the active page with animated transitions. Props: `durationMs?`, `easing?`, `transitionComponent?`, `renderPage?`
307
329
  - **`useRouter()`** — hook returning the router context
308
330
 
309
331
  #### Page map (`client/allPages.ts`)
@@ -390,6 +412,49 @@ import { Link } from 'ugly-app/client';
390
412
 
391
413
  Renders an `<a>` tag with the correct `href`. Intercepts clicks for client-side navigation (ctrl/cmd+click opens in new tab).
392
414
 
415
+ #### Animation system
416
+
417
+ Built-in spring-like animation primitives for transitions and popups.
418
+
419
+ ```typescript
420
+ import {
421
+ Animated,
422
+ createAnimatedValue,
423
+ useAnimatedValue,
424
+ easingFunctions,
425
+ FadeIn,
426
+ SlideFromBottom,
427
+ SlideFromRight,
428
+ } from 'ugly-app/client';
429
+
430
+ // Create an animated value (0→1 spring)
431
+ const spring = createAnimatedValue(0);
432
+ spring.start(1, { duration: 300, easing: easingFunctions.easeOut });
433
+
434
+ // Use in components
435
+ <Animated.div style={{ opacity: spring.to((v) => String(v)) }}>
436
+ Content
437
+ </Animated.div>
438
+
439
+ // Hook version — creates and manages an animated value in a component
440
+ const anim = useAnimatedValue(0);
441
+
442
+ // Pre-built entrance animations (wrap any children)
443
+ <FadeIn>{children}</FadeIn>
444
+ <SlideFromBottom>{children}</SlideFromBottom>
445
+ <SlideFromRight>{children}</SlideFromRight>
446
+ ```
447
+
448
+ Available easings: `easingFunctions.linear`, `easeIn`, `easeOut`, `easeInOut`.
449
+
450
+ #### Screenshot capture
451
+
452
+ ```typescript
453
+ import { captureScreenshot } from 'ugly-app/client';
454
+
455
+ const dataUrl = await captureScreenshot(); // captures the current viewport
456
+ ```
457
+
393
458
  ---
394
459
 
395
460
  ## Auth
@@ -436,57 +501,59 @@ configurator.setAuth({
436
501
 
437
502
  ## Database (`TypedDB`)
438
503
 
504
+ Access via `app.db` or by importing `createTypedDB` / `getMongoClient` from `'ugly-app'`.
505
+
439
506
  ### Writing
440
507
 
441
508
  ```typescript
442
509
  // Insert or replace a document
443
- await ctx.db.setDoc(collections.note, doc);
444
- await ctx.db.setDoc(collections.note, doc, { skipIfExists: true });
510
+ await db.setDoc(collections.note, doc);
511
+ await db.setDoc(collections.note, doc, { skipIfExists: true });
445
512
 
446
513
  // Partial update — only specified fields (supports dot-notation paths)
447
- await ctx.db.setDocFields(collections.note, id, { title: 'New title' });
514
+ await db.setDocFields(collections.note, id, { title: 'New title' });
448
515
 
449
516
  // Partial update — returns null if document doesn't exist (no error)
450
- const doc = await ctx.db.setDocFieldsOrIgnore(collections.note, id, { title });
517
+ const doc = await db.setDocFieldsOrIgnore(collections.note, id, { title });
451
518
 
452
519
  // Partial update — creates the document if it doesn't exist (obj = default doc for insert)
453
- await ctx.db.setDocFieldsOrCreate(collections.note, id, { title }, defaultDoc);
520
+ await db.setDocFieldsOrCreate(collections.note, id, { title }, defaultDoc);
454
521
 
455
522
  // MongoDB update operators ($inc, $addToSet, $pull, $unset, $set)
456
- await ctx.db.setDocOp(collections.note, id, { $inc: { views: 1 } });
457
- await ctx.db.setDocOpOrIgnore(collections.note, id, { $inc: { views: 1 } }); // no error if missing
523
+ await db.setDocOp(collections.note, id, { $inc: { views: 1 } });
524
+ await db.setDocOpOrIgnore(collections.note, id, { $inc: { views: 1 } }); // no error if missing
458
525
  ```
459
526
 
460
527
  ### Reading
461
528
 
462
529
  ```typescript
463
- const note = await ctx.db.getDoc(collections.note, id);
464
- const notes = await ctx.db.getDocs(collections.note, { userId }, { sort: { created: -1 }, limit: 20 });
530
+ const note = await db.getDoc(collections.note, id);
531
+ const notes = await db.getDocs(collections.note, { userId }, { sort: { created: -1 }, limit: 20 });
465
532
 
466
533
  // Aggregation pipeline
467
- const results = await ctx.db.getQuery<MyResult>('note', pipeline, { skip, limit });
468
- const count = await ctx.db.getQueryCount('note', pipeline);
469
- const raw = await ctx.db.getQueryRaw<T>('note', pipeline);
534
+ const results = await db.getQuery<MyResult>('note', pipeline, { skip, limit });
535
+ const count = await db.getQueryCount('note', pipeline);
536
+ const raw = await db.getQueryRaw<T>('note', pipeline);
470
537
 
471
538
  // Dynamic/untyped access (when collection name is a runtime string)
472
- const doc = await ctx.db.rawGetDoc(collectionName, id);
473
- const docs = await ctx.db.rawGetDocs(collectionName, filter);
539
+ const doc = await db.rawGetDoc(collectionName, id);
540
+ const docs = await db.rawGetDocs(collectionName, filter);
474
541
  ```
475
542
 
476
543
  ### Deleting
477
544
 
478
545
  ```typescript
479
- await ctx.db.deleteDoc(collections.note, id); // single doc (cascades via parent)
480
- await ctx.db.deleteQuery(collections.note, { userId }); // bulk delete by filter
546
+ await db.deleteDoc(collections.note, id); // single doc (cascades via parent)
547
+ await db.deleteQuery(collections.note, { userId }); // bulk delete by filter
481
548
  ```
482
549
 
483
550
  ### Caching
484
551
 
485
552
  ```typescript
486
- const cached = await ctx.db.cacheGet<MyType>(key);
487
- await ctx.db.cacheSet(key, value, ttlMs);
488
- await ctx.db.cacheDelete(key);
489
- const key = ctx.db.cacheKey('prefix', id); // generate a cache key
553
+ const cached = db.cacheGet<MyType>(key);
554
+ db.cacheSet(key, value, ttlMs);
555
+ db.cacheDelete(key);
556
+ const key = db.cacheKey('prefix', id); // generate a cache key
490
557
  ```
491
558
 
492
559
  ### Helpers
@@ -511,15 +578,21 @@ await userHelper.set(db, { id: userId, ...dbDefaults(), email });
511
578
  import { defineDbIndexes } from 'ugly-app/shared';
512
579
 
513
580
  export const dbIndexes = defineDbIndexes({
514
- note: [
515
- { key: { userId: 1, created: -1 } },
516
- { key: { title: 'text' }, type: 'search' },
517
- ],
581
+ note: {
582
+ indexes: [
583
+ { fields: { userId: 1, created: -1 } },
584
+ { fields: { title: 1 }, unique: true },
585
+ ],
586
+ searchIndexes: [
587
+ { name: 'note_search', fields: { title: 'string', body: 'string' } },
588
+ ],
589
+ vectorIndexes: [
590
+ { name: 'note_embedding', field: 'embedding', dimensions: 1536, similarity: 'cosine' },
591
+ ],
592
+ },
518
593
  });
519
594
  ```
520
595
 
521
- Index types supported: standard (`IndexDef`), text search (`SearchIndexDef`), and vector/embedding (`VectorIndexDef` with cosine, euclidean, or dotProduct similarity).
522
-
523
596
  Run `npm run db:init` to create/update indexes.
524
597
 
525
598
  ---
@@ -529,10 +602,12 @@ Run `npm run db:init` to create/update indexes.
529
602
  ### Text generation
530
603
 
531
604
  ```typescript
532
- // In a handler via ctx.textGen:
533
- const text = await ctx.textGen.generate(messages);
534
- const json = await ctx.textGen.generateJson(schema, messages); // Zod schema, retries on parse failure
535
- const result = await ctx.textGen.generateWithTools(messages, tools); // automatic tool-call loop
605
+ import { createTextGenClient } from 'ugly-app';
606
+ const textGen = createTextGenClient();
607
+
608
+ const text = await textGen.generate(messages);
609
+ const json = await textGen.generateJson(schema, messages); // Zod schema, retries on parse failure
610
+ const result = await textGen.generateWithTools(messages, tools); // automatic tool-call loop
536
611
  ```
537
612
 
538
613
  | Provider | `provider` value | JSON | Tools | Vision |
@@ -546,17 +621,13 @@ const result = await ctx.textGen.generateWithTools(messages, tools); // automati
546
621
 
547
622
  Use `provider: 'auto'` (default) to let the system pick based on requirements.
548
623
 
549
- **Standalone client (outside handlers):**
550
-
551
- ```typescript
552
- import { createTextGenClient } from 'ugly-app';
553
- const textGen = createTextGenClient();
554
- ```
555
-
556
624
  ### Image generation
557
625
 
558
626
  ```typescript
559
- const url = await ctx.imageGen.generate(prompt, { width: 1024, height: 1024 });
627
+ import { createImageGenClient } from 'ugly-app';
628
+ const imageGen = createImageGenClient();
629
+
630
+ const url = await imageGen.generate(prompt, { width: 1024, height: 1024 });
560
631
  ```
561
632
 
562
633
  | Provider | `provider` value |
@@ -566,13 +637,6 @@ const url = await ctx.imageGen.generate(prompt, { width: 1024, height: 1024 });
566
637
  | Google (Imagen 3) | `'google'` |
567
638
  | Wavespeed | `'wavespeed'` |
568
639
 
569
- **Standalone client:**
570
-
571
- ```typescript
572
- import { createImageGenClient } from 'ugly-app';
573
- const imageGen = createImageGenClient();
574
- ```
575
-
576
640
  ### Embeddings
577
641
 
578
642
  ```typescript
@@ -609,9 +673,9 @@ The client can call AI through the server proxy without managing tokens directly
609
673
  ```typescript
610
674
  import { callTextGen, callJsonGen, callImageGen } from 'ugly-app/client';
611
675
 
612
- const text = await callTextGen(messages, options);
613
- const json = await callJsonGen(schema, messages);
614
- const image = await callImageGen(prompt, options);
676
+ const text = await callTextGen(input);
677
+ const json = await callJsonGen(input);
678
+ const image = await callImageGen(input);
615
679
  ```
616
680
 
617
681
  These call `POST /ai/request` (same-origin, avoids CORS). The server verifies the user JWT and forwards to `https://ugly.bot` using the `UGLY_BOT_TOKEN` env var.
@@ -722,11 +786,18 @@ Server-side, `getFeedbackHandlers(maintainBotUserId)` provides the RPC handlers
722
786
 
723
787
  ### Rate limiting
724
788
 
789
+ Rate limiting is configured per-endpoint in the request definition:
790
+
725
791
  ```typescript
726
- // Inside a handler — always call before expensive operations
727
- await ctx.rateLimit.check();
792
+ submitFeedback: authReq({
793
+ input: z.object({ ... }),
794
+ output: z.object({ ... }),
795
+ rateLimit: { max: 20, window: 60 }, // 20 requests per 60 seconds
796
+ })
728
797
  ```
729
798
 
799
+ The framework enforces rate limits automatically before calling the handler.
800
+
730
801
  ---
731
802
 
732
803
  ## Built-in endpoints
@@ -738,8 +809,10 @@ await ctx.rateLimit.check();
738
809
  | `POST /auth/logout` | Clear the auth cookie |
739
810
  | `GET /auth/token` | Refresh and return the current token |
740
811
  | `GET /auth/url` | Get the OAuth popup URL |
812
+ | `POST /api/:name` | RPC endpoint — dispatches any registered request handler |
741
813
  | `POST /ai/request` | AI proxy — forwards to ugly.bot (requires auth) |
742
814
  | `POST /api/client-error` | Client-side error capture |
815
+ | `POST /logs/client` | Client-side log ingestion |
743
816
  | `GET /my_feedback` | User feedback history (markdown, requires auth) |
744
817
 
745
818
  ---
@@ -788,7 +861,7 @@ Run with `npm run db:migrate`. Use `npm run db:migrate -- --status` to preview p
788
861
  | Variable | Description |
789
862
  |----------|-------------|
790
863
  | `JWT_SECRET` | **Required** — signs auth tokens |
791
- | `JWT_EXPIRY_SECONDS` | Token lifetime (optional) |
864
+ | `JWT_EXPIRY_SECONDS` | Token lifetime (optional, default: 2592000 = 30 days) |
792
865
  | `MONGODB_URI` | MongoDB connection string |
793
866
  | `PORT` | Server port (default: 3000) |
794
867
  | `NODE_ENV` | `development` or `production` |
@@ -816,3 +889,37 @@ Client-side variables must be prefixed with `VITE_`.
816
889
  ## Tech stack
817
890
 
818
891
  Node.js · TypeScript · Express · React 19 · Vite · Tailwind CSS · MongoDB · NATS · Redis · Cloudflare R2 · Zod · JWT (jose) · ugly.bot OAuth
892
+
893
+ ---
894
+
895
+ ## Shared utilities
896
+
897
+ `ugly-app/shared` exports common helpers used by both server and client:
898
+
899
+ ```typescript
900
+ import {
901
+ isDefined,
902
+ compact,
903
+ debounce,
904
+ formatDate,
905
+ formatRelativeTime,
906
+ oneSecond,
907
+ oneMinute,
908
+ oneHour,
909
+ oneDay,
910
+ oneWeek,
911
+ } from 'ugly-app/shared';
912
+
913
+ isDefined(value); // type guard — true if not null/undefined
914
+ compact([1, null, 2, undefined]); // [1, 2] — filters out null/undefined
915
+ const debouncedFn = debounce(fn, 300);
916
+ formatDate(new Date()); // locale-formatted date string
917
+ formatRelativeTime(new Date()); // "2 hours ago", "just now", etc.
918
+
919
+ // Time constants (milliseconds)
920
+ oneSecond; // 1000
921
+ oneMinute; // 60_000
922
+ oneHour; // 3_600_000
923
+ oneDay; // 86_400_000
924
+ oneWeek; // 604_800_000
925
+ ```
@@ -1 +1 @@
1
- {"version":3,"file":"initDb.d.ts","sourceRoot":"","sources":["../../src/cli/initDb.ts"],"names":[],"mappings":"AAgCA,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAiG/C"}
1
+ {"version":3,"file":"initDb.d.ts","sourceRoot":"","sources":["../../src/cli/initDb.ts"],"names":[],"mappings":"AAuCA,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CA6H/C"}
@@ -16,7 +16,14 @@ async function loadDbIndexes() {
16
16
  // Built-in indexes always applied
17
17
  const BUILTIN_INDEXES = {
18
18
  user: { indexes: [{ fields: { email: 1 }, unique: true }] },
19
- errorLog: { indexes: [{ fields: { created: 1 }, ttl: 7 * 86400 }] },
19
+ errorLog: {
20
+ timeseries: {
21
+ timeField: 'created',
22
+ metaField: 'meta',
23
+ granularity: 'seconds',
24
+ expireAfterSeconds: 7 * 24 * 60 * 60, // 7 days
25
+ },
26
+ },
20
27
  consoleLog: { indexes: [{ fields: { created: 1 }, ttl: 7 * 86400 }] },
21
28
  perfLog: { indexes: [{ fields: { created: 1 }, ttl: 7 * 86400 }] },
22
29
  feedbackLog: { indexes: [{ fields: { created: 1 }, ttl: 7 * 86400 }] },
@@ -38,6 +45,31 @@ export async function runInitDb() {
38
45
  }
39
46
  for (const [collection, config] of Object.entries(allIndexes)) {
40
47
  const col = db.collection(collection);
48
+ if (config.timeseries) {
49
+ try {
50
+ await db.createCollection(collection, {
51
+ timeseries: {
52
+ timeField: config.timeseries.timeField,
53
+ ...(config.timeseries.metaField
54
+ ? { metaField: config.timeseries.metaField }
55
+ : {}),
56
+ ...(config.timeseries.granularity
57
+ ? { granularity: config.timeseries.granularity }
58
+ : {}),
59
+ },
60
+ ...(config.timeseries.expireAfterSeconds !== undefined
61
+ ? { expireAfterSeconds: config.timeseries.expireAfterSeconds }
62
+ : {}),
63
+ });
64
+ console.log(`[init-db] Time-series collection created: ${collection}`);
65
+ }
66
+ catch (e) {
67
+ const err = e;
68
+ if (err.code !== 48)
69
+ throw e; // 48 = NamespaceExists (already exists)
70
+ console.log(`[init-db] Time-series collection already exists: ${collection}`);
71
+ }
72
+ }
41
73
  for (const idx of config.indexes ?? []) {
42
74
  const opts = {};
43
75
  if (idx.unique)
@@ -1 +1 @@
1
- {"version":3,"file":"initDb.js","sourceRoot":"","sources":["../../src/cli/initDb.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"initDb.js","sourceRoot":"","sources":["../../src/cli/initDb.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/cli/scaffold.ts"],"names":[],"mappings":"AAyFA,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4DxE"}
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/cli/scaffold.ts"],"names":[],"mappings":"AA6GA,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4DxE"}