ugly-app 0.1.42 → 0.1.43
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 +83 -28
- package/dist/cli/scaffold.d.ts.map +1 -1
- package/dist/cli/scaffold.js +27 -1
- package/dist/cli/scaffold.js.map +1 -1
- package/package.json +1 -1
- package/templates/client/pages/AITestPage.tsx +1 -1
package/README.md
CHANGED
|
@@ -6,9 +6,9 @@ A full-stack TypeScript framework for building production-ready web applications
|
|
|
6
6
|
|
|
7
7
|
- **Server**: Express + WebSocket with type-safe RPC and Zod validation
|
|
8
8
|
- **Client**: React + Vite with typed routing, lazy pages, and popup management
|
|
9
|
-
- **Database**: MongoDB with typed collections, indexes, migrations, and live document tracking
|
|
9
|
+
- **Database**: MongoDB with typed collections, dot-notation updates, indexes, migrations, and live document tracking
|
|
10
10
|
- **Auth**: JWT + HttpOnly cookies, ugly.bot OAuth out of the box, extensible via `AuthProvider`
|
|
11
|
-
- **AI**: Text generation (Together, Claude, OpenAI, Google, Groq, Fireworks) + image generation (Together, FAL, Google, Wavespeed)
|
|
11
|
+
- **AI**: Text generation (Together, Claude, OpenAI, Google, Groq, Fireworks) + image generation (Together, FAL, Google, Wavespeed) + embeddings + STT/TTS
|
|
12
12
|
- **Storage**: Cloudflare R2 / AWS S3 with presigned uploads
|
|
13
13
|
- **CLI**: `ugly-app` commands for dev, build, deploy, migrations, logs, and auth utilities
|
|
14
14
|
|
|
@@ -143,7 +143,7 @@ export const requests = defineRequests({
|
|
|
143
143
|
|
|
144
144
|
- `fn({ input, output })` — defines a mutation (write operation). `input` and `output` are Zod schemas.
|
|
145
145
|
- `req({ input, output })` — defines a query (read operation).
|
|
146
|
-
- `defineFunctions()` / `defineRequests()` — identity wrappers that preserve types.
|
|
146
|
+
- `defineFunctions()` / `defineRequests()` — identity wrappers that preserve types. `defineCalls` is an alias for `defineFunctions`.
|
|
147
147
|
- `z` is re-exported from Zod for convenience.
|
|
148
148
|
|
|
149
149
|
### Collections (`shared/collections.ts`)
|
|
@@ -164,11 +164,14 @@ export const collections = defineCollections({
|
|
|
164
164
|
});
|
|
165
165
|
```
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
- `
|
|
169
|
-
- `
|
|
170
|
-
- `
|
|
171
|
-
- `
|
|
167
|
+
Each collection definition has:
|
|
168
|
+
- `type` — phantom field for TypeScript type inference (never read at runtime)
|
|
169
|
+
- `meta` — runtime metadata:
|
|
170
|
+
- `cache` — enable in-memory caching
|
|
171
|
+
- `trackable` — allow real-time `trackDoc`/`trackDocs` subscriptions
|
|
172
|
+
- `public` — accessible without auth
|
|
173
|
+
- `parent` — parent collection name for cascade deletes (or `null`)
|
|
174
|
+
- `onDelete?` — optional callback invoked on document deletion
|
|
172
175
|
|
|
173
176
|
All documents extend `DBObject`: `{ id: string, version: number, created: Date, updated: Date }`.
|
|
174
177
|
|
|
@@ -282,6 +285,10 @@ Wraps your app with context for `useApp()`. Provides user info, socket access, p
|
|
|
282
285
|
| `splashDone(step)` | Mark a splash screen step as complete |
|
|
283
286
|
| `localizer(key, params?)` | Localize a string key |
|
|
284
287
|
|
|
288
|
+
`useAppOptional()` returns the same context or `null` if outside `<AppProvider>`.
|
|
289
|
+
|
|
290
|
+
`useLocalizer()` returns the localizer function (falls back to identity if outside provider).
|
|
291
|
+
|
|
285
292
|
### Router
|
|
286
293
|
|
|
287
294
|
#### Setup (`client/router.ts`)
|
|
@@ -315,7 +322,18 @@ export const allPages = {
|
|
|
315
322
|
```
|
|
316
323
|
|
|
317
324
|
- **`lazyPage(factory)`** — lazy-imports a default-exported React component. The component receives route params as props.
|
|
318
|
-
- **`lazyPageLoader(factory)`** — lazy-imports an async loader function `(params) => Promise<ReactElement>` for routes that need data fetching before render. The loader file is the chunk boundary.
|
|
325
|
+
- **`lazyPageLoader(factory)`** — lazy-imports an async loader function `(params) => Promise<ReactElement>` for routes that need data fetching before render. The loader file is the chunk boundary — it can statically import its page component.
|
|
326
|
+
|
|
327
|
+
**`lazyPageLoader` example:**
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
// pages/SlowPageLoader.tsx
|
|
331
|
+
import SlowPage from './SlowPage'; // static import OK — same chunk
|
|
332
|
+
export default async function PageLoader({ id }: { id: string }) {
|
|
333
|
+
const data = await fetchSlowData(id);
|
|
334
|
+
return <SlowPage {...data} />;
|
|
335
|
+
}
|
|
336
|
+
```
|
|
319
337
|
|
|
320
338
|
#### Navigation with `useRouter()`
|
|
321
339
|
|
|
@@ -354,10 +372,12 @@ handle.hide(); // dismiss programmatically
|
|
|
354
372
|
```
|
|
355
373
|
|
|
356
374
|
Popup modes:
|
|
357
|
-
- **`block`** — dark backdrop (40% opacity), clicking backdrop does NOT dismiss
|
|
375
|
+
- **`block`** (default) — dark backdrop (40% opacity), clicking backdrop does NOT dismiss
|
|
358
376
|
- **`transient`** — light backdrop (20% opacity), clicking backdrop dismisses
|
|
359
377
|
- **`contextMenu`** — same as transient, intended for menus and pickers
|
|
360
378
|
|
|
379
|
+
`renderLayer` receives `{ content, spring, hide }` — `spring` is an animated value from 0 to 1, `hide` is a function to close the popup.
|
|
380
|
+
|
|
361
381
|
#### `Link` component
|
|
362
382
|
|
|
363
383
|
```tsx
|
|
@@ -423,7 +443,7 @@ configurator.setAuth({
|
|
|
423
443
|
await ctx.db.setDoc(collections.note, doc);
|
|
424
444
|
await ctx.db.setDoc(collections.note, doc, { skipIfExists: true });
|
|
425
445
|
|
|
426
|
-
// Partial update — only specified fields
|
|
446
|
+
// Partial update — only specified fields (supports dot-notation paths)
|
|
427
447
|
await ctx.db.setDocFields(collections.note, id, { title: 'New title' });
|
|
428
448
|
|
|
429
449
|
// Partial update — returns null if document doesn't exist (no error)
|
|
@@ -447,6 +467,10 @@ const notes = await ctx.db.getDocs(collections.note, { userId }, { sort: { creat
|
|
|
447
467
|
const results = await ctx.db.getQuery<MyResult>('note', pipeline, { skip, limit });
|
|
448
468
|
const count = await ctx.db.getQueryCount('note', pipeline);
|
|
449
469
|
const raw = await ctx.db.getQueryRaw<T>('note', pipeline);
|
|
470
|
+
|
|
471
|
+
// 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);
|
|
450
474
|
```
|
|
451
475
|
|
|
452
476
|
### Deleting
|
|
@@ -474,7 +498,7 @@ import { dbDefaults } from 'ugly-app/shared';
|
|
|
474
498
|
// dbDefaults() returns { version: 1, created: new Date(), updated: new Date() }
|
|
475
499
|
const doc = { id: newId(), ...dbDefaults(), title: 'Hello' };
|
|
476
500
|
|
|
477
|
-
// createUserHelper — typed user CRUD
|
|
501
|
+
// createUserHelper — typed user CRUD with get, set, update methods
|
|
478
502
|
const userHelper = createUserHelper<User>(collections.user);
|
|
479
503
|
const user = await userHelper.get(db, userId);
|
|
480
504
|
await userHelper.set(db, { id: userId, ...dbDefaults(), email });
|
|
@@ -494,6 +518,8 @@ export const dbIndexes = defineDbIndexes({
|
|
|
494
518
|
});
|
|
495
519
|
```
|
|
496
520
|
|
|
521
|
+
Index types supported: standard (`IndexDef`), text search (`SearchIndexDef`), and vector/embedding (`VectorIndexDef` with cosine, euclidean, or dotProduct similarity).
|
|
522
|
+
|
|
497
523
|
Run `npm run db:init` to create/update indexes.
|
|
498
524
|
|
|
499
525
|
---
|
|
@@ -547,13 +573,33 @@ import { createImageGenClient } from 'ugly-app';
|
|
|
547
573
|
const imageGen = createImageGenClient();
|
|
548
574
|
```
|
|
549
575
|
|
|
576
|
+
### Embeddings
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
import { createEmbeddingClient, cosineSimilarity } from 'ugly-app';
|
|
580
|
+
const embeddings = createEmbeddingClient();
|
|
581
|
+
const similarity = cosineSimilarity(vectorA, vectorB);
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### Speech-to-text / Text-to-speech
|
|
585
|
+
|
|
586
|
+
```typescript
|
|
587
|
+
// Server-side providers
|
|
588
|
+
import { registerSTTProvider, groqWhisperSTTProvider, loadVADModel } from 'ugly-app';
|
|
589
|
+
import { registerTTSProvider, azureTTSProvider } from 'ugly-app';
|
|
590
|
+
|
|
591
|
+
// Client-side hooks
|
|
592
|
+
import { useSTT, useTTS, AudioPlayer, AudioRecorder } from 'ugly-app/client';
|
|
593
|
+
```
|
|
594
|
+
|
|
550
595
|
### Custom providers
|
|
551
596
|
|
|
552
597
|
```typescript
|
|
553
|
-
import { registerTextGenProvider, registerImageGenProvider } from 'ugly-app';
|
|
598
|
+
import { registerTextGenProvider, registerImageGenProvider, registerEmbeddingProvider } from 'ugly-app';
|
|
554
599
|
|
|
555
600
|
registerTextGenProvider('myProvider', myTextGenImplementation);
|
|
556
601
|
registerImageGenProvider('myProvider', myImageGenImplementation);
|
|
602
|
+
registerEmbeddingProvider('myProvider', myEmbeddingImplementation);
|
|
557
603
|
```
|
|
558
604
|
|
|
559
605
|
### Client-side AI calls
|
|
@@ -588,7 +634,7 @@ const url = storage.url('public', destKey);
|
|
|
588
634
|
const { uploadUrl, resultUrl } = await storage.presignedPut('temp', key);
|
|
589
635
|
```
|
|
590
636
|
|
|
591
|
-
Buckets: `'public'` and `'temp'`.
|
|
637
|
+
Buckets: `'public'` and `'temp'`. Supports Cloudflare R2 (production) or MinIO (dev).
|
|
592
638
|
|
|
593
639
|
Static build-time assets go in `client/public/`. Never hardcode `/asset/...` paths — use the `buildId` from `shared/Build.ts`.
|
|
594
640
|
|
|
@@ -626,7 +672,7 @@ sub(); // unsubscribe
|
|
|
626
672
|
### Redis
|
|
627
673
|
|
|
628
674
|
```typescript
|
|
629
|
-
import { getRedisClient } from 'ugly-app';
|
|
675
|
+
import { getRedisClient, redisGet, redisSet, redisDel, redisPublish, redisSubscribe } from 'ugly-app';
|
|
630
676
|
const redis = getRedisClient();
|
|
631
677
|
```
|
|
632
678
|
|
|
@@ -641,6 +687,15 @@ configurator.setWorkerQueue(queue);
|
|
|
641
687
|
await enqueueTask('taskName', payload);
|
|
642
688
|
```
|
|
643
689
|
|
|
690
|
+
### Billing
|
|
691
|
+
|
|
692
|
+
```typescript
|
|
693
|
+
import { initBillingGateway, getBillingGateway } from 'ugly-app';
|
|
694
|
+
|
|
695
|
+
await initBillingGateway({ /* config */ });
|
|
696
|
+
const billing = getBillingGateway();
|
|
697
|
+
```
|
|
698
|
+
|
|
644
699
|
### Client error capture
|
|
645
700
|
|
|
646
701
|
```typescript
|
|
@@ -672,21 +727,20 @@ Server-side, `getFeedbackHandlers(maintainBotUserId)` provides the RPC handlers
|
|
|
672
727
|
await ctx.rateLimit.check();
|
|
673
728
|
```
|
|
674
729
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
```typescript
|
|
678
|
-
import { createEmbeddingClient, registerEmbeddingProvider } from 'ugly-app';
|
|
679
|
-
const embeddings = createEmbeddingClient();
|
|
680
|
-
```
|
|
681
|
-
|
|
682
|
-
### Billing
|
|
730
|
+
---
|
|
683
731
|
|
|
684
|
-
|
|
685
|
-
import { initBillingGateway, getBillingGateway } from 'ugly-app';
|
|
732
|
+
## Built-in endpoints
|
|
686
733
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
734
|
+
| Endpoint | Description |
|
|
735
|
+
|----------|-------------|
|
|
736
|
+
| `GET /health` | Health check — returns `{ status: 'ok', timestamp }` |
|
|
737
|
+
| `POST /auth/verify` | Exchange OAuth code for a session cookie |
|
|
738
|
+
| `POST /auth/logout` | Clear the auth cookie |
|
|
739
|
+
| `GET /auth/token` | Refresh and return the current token |
|
|
740
|
+
| `GET /auth/url` | Get the OAuth popup URL |
|
|
741
|
+
| `POST /ai/request` | AI proxy — forwards to ugly.bot (requires auth) |
|
|
742
|
+
| `POST /api/client-error` | Client-side error capture |
|
|
743
|
+
| `GET /my_feedback` | User feedback history (markdown, requires auth) |
|
|
690
744
|
|
|
691
745
|
---
|
|
692
746
|
|
|
@@ -734,6 +788,7 @@ Run with `npm run db:migrate`. Use `npm run db:migrate -- --status` to preview p
|
|
|
734
788
|
| Variable | Description |
|
|
735
789
|
|----------|-------------|
|
|
736
790
|
| `JWT_SECRET` | **Required** — signs auth tokens |
|
|
791
|
+
| `JWT_EXPIRY_SECONDS` | Token lifetime (optional) |
|
|
737
792
|
| `MONGODB_URI` | MongoDB connection string |
|
|
738
793
|
| `PORT` | Server port (default: 3000) |
|
|
739
794
|
| `NODE_ENV` | `development` or `production` |
|
|
@@ -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":"AAyFA,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4DxE"}
|
package/dist/cli/scaffold.js
CHANGED
|
@@ -38,7 +38,33 @@ function generateEnv(examplePath, projectName) {
|
|
|
38
38
|
})
|
|
39
39
|
.join('\n');
|
|
40
40
|
}
|
|
41
|
+
/** Locate the `code` binary — checks PATH then known macOS app bundle locations. */
|
|
42
|
+
function findCodeCli() {
|
|
43
|
+
try {
|
|
44
|
+
execSync('code --version', { stdio: 'ignore' });
|
|
45
|
+
return 'code';
|
|
46
|
+
}
|
|
47
|
+
catch { /* not in PATH */ }
|
|
48
|
+
const candidates = process.platform === 'win32'
|
|
49
|
+
? [
|
|
50
|
+
path.join(process.env['LOCALAPPDATA'] ?? '', 'Programs\\Microsoft VS Code\\bin\\code.cmd'),
|
|
51
|
+
path.join(process.env['ProgramFiles'] ?? '', 'Microsoft VS Code\\bin\\code.cmd'),
|
|
52
|
+
path.join(process.env['ProgramFiles(x86)'] ?? '', 'Microsoft VS Code\\bin\\code.cmd'),
|
|
53
|
+
]
|
|
54
|
+
: [
|
|
55
|
+
'/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code',
|
|
56
|
+
path.join(os.homedir(), 'Applications/Visual Studio Code.app/Contents/Resources/app/bin/code'),
|
|
57
|
+
];
|
|
58
|
+
for (const p of candidates) {
|
|
59
|
+
if (fs.existsSync(p))
|
|
60
|
+
return `"${p}"`;
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
41
64
|
async function installVsixExtension() {
|
|
65
|
+
const codeCli = findCodeCli();
|
|
66
|
+
if (!codeCli)
|
|
67
|
+
return; // VSCode not found — skip silently
|
|
42
68
|
const url = 'https://ugly.bot/asset/ugly-bot.vsix';
|
|
43
69
|
const vsixPath = path.join(os.tmpdir(), 'ugly-bot.vsix');
|
|
44
70
|
try {
|
|
@@ -48,7 +74,7 @@ async function installVsixExtension() {
|
|
|
48
74
|
throw new Error(`HTTP ${res.status}`);
|
|
49
75
|
const buf = await res.arrayBuffer();
|
|
50
76
|
await fs.writeFile(vsixPath, Buffer.from(buf));
|
|
51
|
-
execSync(
|
|
77
|
+
execSync(`${codeCli} --install-extension "${vsixPath}"`, { stdio: 'inherit' });
|
|
52
78
|
console.log('[ugly-app] VSCode extension installed.');
|
|
53
79
|
}
|
|
54
80
|
catch {
|
package/dist/cli/scaffold.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/cli/scaffold.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExE,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC/D,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;AAE9D,gFAAgF;AAChF,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC;IAC5B,YAAY;IACZ,YAAY;IACZ,UAAU;IACV,MAAM;IACN,aAAa;IACb,kBAAkB;IAClB,uBAAuB;CACxB,CAAC,CAAC;AAEH,SAAS,WAAW,CAAC,WAAmB,EAAE,WAAmB;IAC3D,MAAM,KAAK,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAChE,OAAO,KAAK;SACT,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACZ,6BAA6B;QAC7B,IAAI,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;YACnC,OAAO,cAAc,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;QACpC,CAAC;QACD,yEAAyE;QACzE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;QACtD,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,KAAK,CAAC;YAC7B,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;gBACnD,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED,KAAK,UAAU,oBAAoB;IACjC,MAAM,GAAG,GAAG,sCAAsC,CAAC;IACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,eAAe,CAAC,CAAC;IACzD,IAAI,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,oDAAoD,CAAC,CAAC;QAClE,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACnD,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;QACpC,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC/C,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/cli/scaffold.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@ import type { ImageGenModel, TextGenModel } from 'ugly-app/shared';
|
|
|
11
11
|
import { imageGenModels, textGenModels } from 'ugly-app/shared';
|
|
12
12
|
|
|
13
13
|
type Mode = 'text' | 'image';
|
|
14
|
-
|
|
14
|
+
interface LogEntry { ts: number; msg: string; kind: 'info' | 'ok' | 'err' }
|
|
15
15
|
|
|
16
16
|
function fmt(ms: number): string {
|
|
17
17
|
return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`;
|