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.
- package/README.md +218 -111
- package/dist/cli/initDb.d.ts.map +1 -1
- package/dist/cli/initDb.js +33 -1
- package/dist/cli/initDb.js.map +1 -1
- package/dist/cli/scaffold.d.ts.map +1 -1
- package/dist/cli/scaffold.js +13 -9
- package/dist/cli/scaffold.js.map +1 -1
- package/dist/cli/uglyBotLogin.d.ts.map +1 -1
- package/dist/cli/uglyBotLogin.js.map +1 -1
- package/dist/cli/upgrade.d.ts.map +1 -1
- package/dist/cli/upgrade.js +5 -5
- package/dist/cli/upgrade.js.map +1 -1
- package/dist/client/Logger.d.ts +11 -0
- package/dist/client/Logger.d.ts.map +1 -1
- package/dist/client/Logger.js +82 -22
- package/dist/client/Logger.js.map +1 -1
- package/dist/client/Router.d.ts +5 -3
- package/dist/client/Router.d.ts.map +1 -1
- package/dist/client/Router.js +25 -10
- package/dist/client/Router.js.map +1 -1
- package/dist/client/callAI.d.ts.map +1 -1
- package/dist/client/callAI.js +7 -2
- package/dist/client/callAI.js.map +1 -1
- package/dist/client/createHttpClient.d.ts +13 -0
- package/dist/client/createHttpClient.d.ts.map +1 -0
- package/dist/client/createHttpClient.js +24 -0
- package/dist/client/createHttpClient.js.map +1 -0
- package/dist/client/createSocket.d.ts +1 -3
- package/dist/client/createSocket.d.ts.map +1 -1
- package/dist/client/createSocket.js +0 -11
- package/dist/client/createSocket.js.map +1 -1
- package/dist/client/index.d.ts +4 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -1
- package/dist/client/index.js.map +1 -1
- package/dist/server/App.d.ts +4 -4
- package/dist/server/App.d.ts.map +1 -1
- package/dist/server/App.js +54 -14
- package/dist/server/App.js.map +1 -1
- package/dist/server/Auth.d.ts.map +1 -1
- package/dist/server/Auth.js +14 -11
- package/dist/server/Auth.js.map +1 -1
- package/dist/server/ErrorLogAdmin.d.ts +50 -0
- package/dist/server/ErrorLogAdmin.d.ts.map +1 -0
- package/dist/server/ErrorLogAdmin.js +143 -0
- package/dist/server/ErrorLogAdmin.js.map +1 -0
- package/dist/server/FeedbackReport.d.ts +2 -3
- package/dist/server/FeedbackReport.d.ts.map +1 -1
- package/dist/server/FeedbackReport.js +4 -5
- package/dist/server/FeedbackReport.js.map +1 -1
- package/dist/server/Logging.d.ts +6 -0
- package/dist/server/Logging.d.ts.map +1 -1
- package/dist/server/Logging.js +97 -45
- package/dist/server/Logging.js.map +1 -1
- package/dist/server/Router.d.ts +12 -25
- package/dist/server/Router.d.ts.map +1 -1
- package/dist/server/Router.js +66 -48
- package/dist/server/Router.js.map +1 -1
- package/dist/server/Socket.d.ts.map +1 -1
- package/dist/server/Socket.js +3 -1
- package/dist/server/Socket.js.map +1 -1
- package/dist/server/index.d.ts +6 -4
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -2
- package/dist/server/index.js.map +1 -1
- package/dist/shared/Api.d.ts +18 -20
- package/dist/shared/Api.d.ts.map +1 -1
- package/dist/shared/Api.js +9 -9
- package/dist/shared/Api.js.map +1 -1
- package/dist/shared/DB.d.ts +29 -8
- package/dist/shared/DB.d.ts.map +1 -1
- package/dist/shared/DB.js.map +1 -1
- package/dist/shared/Socket.d.ts +4 -6
- package/dist/shared/Socket.d.ts.map +1 -1
- package/package.json +1 -1
- package/templates/CLAUDE.md +26 -7
- package/templates/client/App.tsx +1 -1
- package/templates/client/allPages.ts +12 -1
- package/templates/client/main.tsx +3 -3
- package/templates/client/pages/AITestPage.tsx +29 -12
- package/templates/client/pages/AuthDemoPage.tsx +34 -9
- package/templates/client/pages/HomePage.tsx +51 -14
- package/templates/client/pages/UserPage.tsx +5 -1
- package/templates/package.json +1 -1
- package/templates/server/index.ts +16 -26
- package/templates/shared/api.ts +22 -16
- package/templates/shared/collections.ts +43 -2
- package/templates/shared/pages.ts +12 -0
- package/templates/vite.config.ts +1 -0
- package/templates/shared/dbIndexes.ts +0 -11
- package/templates/shared/types.ts +0 -6
- /package/templates/.claude/skills/{assets.md → assets/SKILL.md} +0 -0
- /package/templates/.claude/skills/{check-errors.md → check-errors/SKILL.md} +0 -0
- /package/templates/.claude/skills/{check-feedback.md → check-feedback/SKILL.md} +0 -0
- /package/templates/.claude/skills/{check-perf.md → check-perf/SKILL.md} +0 -0
- /package/templates/.claude/skills/{create-test-users.md → create-test-users/SKILL.md} +0 -0
- /package/templates/.claude/skills/{extend-api.md → extend-api/SKILL.md} +0 -0
- /package/templates/.claude/skills/{fix-code.md → fix-code/SKILL.md} +0 -0
- /package/templates/.claude/skills/{fix-errors.md → fix-errors/SKILL.md} +0 -0
- /package/templates/.claude/skills/{fix-feedback.md → fix-feedback/SKILL.md} +0 -0
- /package/templates/.claude/skills/{fix-perf.md → fix-perf/SKILL.md} +0 -0
- /package/templates/.claude/skills/{uploads.md → uploads/SKILL.md} +0 -0
- /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
|
|
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 {
|
|
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/
|
|
43
|
+
import type { User } from '../shared/collections';
|
|
45
44
|
|
|
46
45
|
const userHelper = createUserHelper<User>(collections.user);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
- `
|
|
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
|
|
101
|
+
### Handler signatures
|
|
102
|
+
|
|
103
|
+
Handlers are plain async functions — no context object, just `userId` and `input`:
|
|
105
104
|
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
121
|
+
### Requests (`shared/api.ts`)
|
|
125
122
|
|
|
126
123
|
```typescript
|
|
127
|
-
import {
|
|
124
|
+
import { authReq, defineRequests, req, z } from 'ugly-app/shared';
|
|
128
125
|
|
|
129
|
-
export const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
137
|
-
|
|
133
|
+
// Authenticated request — handler receives (userId: string, input)
|
|
134
|
+
getMe: authReq({
|
|
138
135
|
input: z.object({}),
|
|
139
|
-
output: z.
|
|
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
|
-
- `
|
|
145
|
-
- `
|
|
146
|
-
- `
|
|
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 {
|
|
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
|
|
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({
|
|
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({
|
|
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
|
-
| `
|
|
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
|
|
444
|
-
await
|
|
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
|
|
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
|
|
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
|
|
520
|
+
await db.setDocFieldsOrCreate(collections.note, id, { title }, defaultDoc);
|
|
454
521
|
|
|
455
522
|
// MongoDB update operators ($inc, $addToSet, $pull, $unset, $set)
|
|
456
|
-
await
|
|
457
|
-
await
|
|
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
|
|
464
|
-
const notes = await
|
|
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
|
|
468
|
-
const count = await
|
|
469
|
-
const raw = await
|
|
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
|
|
473
|
-
const docs = await
|
|
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
|
|
480
|
-
await
|
|
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 =
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
const 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
|
-
|
|
516
|
-
|
|
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
|
-
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
const
|
|
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
|
-
|
|
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(
|
|
613
|
-
const json = await callJsonGen(
|
|
614
|
-
const image = await callImageGen(
|
|
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
|
-
|
|
727
|
-
|
|
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
|
+
```
|
package/dist/cli/initDb.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initDb.d.ts","sourceRoot":"","sources":["../../src/cli/initDb.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/cli/initDb.js
CHANGED
|
@@ -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: {
|
|
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)
|
package/dist/cli/initDb.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initDb.js","sourceRoot":"","sources":["../../src/cli/initDb.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAGpC,KAAK,UAAU,aAAa;IAC1B,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,qBAAqB,CAAC,CAAC;IACrE,sEAAsE;IACtE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAEvD,CAAC;QACF,OAAO,GAAG,CAAC,SAAS,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,IAAI,CACV,yEAAyE,CAC1E,CAAC;QACF,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,kCAAkC;AAClC,MAAM,eAAe,GAAoB;IACvC,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE;IAC3D,QAAQ,EAAE,EAAE,
|
|
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":"
|
|
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"}
|