ugly-app 0.1.78 → 0.1.80

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 (90) hide show
  1. package/README.md +6 -143
  2. package/coverage/AppProvider.tsx.html +817 -0
  3. package/coverage/base.css +224 -0
  4. package/coverage/block-navigation.js +87 -0
  5. package/coverage/clover.xml +62 -0
  6. package/coverage/coverage-final.json +2 -0
  7. package/coverage/favicon.png +0 -0
  8. package/coverage/index.html +116 -0
  9. package/coverage/prettify.css +1 -0
  10. package/coverage/prettify.js +2 -0
  11. package/coverage/sort-arrow-sprite.png +0 -0
  12. package/coverage/sorter.js +210 -0
  13. package/dist/cli/dev.js +1 -1
  14. package/dist/cli/dev.js.map +1 -1
  15. package/dist/cli/version.d.ts +1 -1
  16. package/dist/cli/version.js +1 -1
  17. package/dist/client/components/Button.d.ts +3 -3
  18. package/dist/client/components/Button.d.ts.map +1 -1
  19. package/dist/client/components/Button.js +27 -13
  20. package/dist/client/components/Button.js.map +1 -1
  21. package/dist/client/components/Card.d.ts +3 -3
  22. package/dist/client/components/Card.d.ts.map +1 -1
  23. package/dist/client/components/Card.js +10 -3
  24. package/dist/client/components/Card.js.map +1 -1
  25. package/dist/client/components/EnumInput.d.ts +3 -3
  26. package/dist/client/components/EnumInput.d.ts.map +1 -1
  27. package/dist/client/components/EnumInput.js +17 -2
  28. package/dist/client/components/EnumInput.js.map +1 -1
  29. package/dist/client/components/PageLayout.d.ts +3 -3
  30. package/dist/client/components/PageLayout.d.ts.map +1 -1
  31. package/dist/client/components/PageLayout.js +14 -2
  32. package/dist/client/components/PageLayout.js.map +1 -1
  33. package/dist/client/components/Panel.d.ts +1 -2
  34. package/dist/client/components/Panel.d.ts.map +1 -1
  35. package/dist/client/components/Panel.js +7 -2
  36. package/dist/client/components/Panel.js.map +1 -1
  37. package/dist/client/components/PopupPanel.d.ts +3 -3
  38. package/dist/client/components/PopupPanel.d.ts.map +1 -1
  39. package/dist/client/components/PopupPanel.js +11 -2
  40. package/dist/client/components/PopupPanel.js.map +1 -1
  41. package/dist/client/components/Pressable.d.ts +1 -2
  42. package/dist/client/components/Pressable.d.ts.map +1 -1
  43. package/dist/client/components/Pressable.js +8 -2
  44. package/dist/client/components/Pressable.js.map +1 -1
  45. package/dist/client/components/ScrollView.js +1 -1
  46. package/dist/client/components/ScrollView.js.map +1 -1
  47. package/dist/client/components/SettingGroup.d.ts +3 -3
  48. package/dist/client/components/SettingGroup.d.ts.map +1 -1
  49. package/dist/client/components/SettingGroup.js +15 -2
  50. package/dist/client/components/SettingGroup.js.map +1 -1
  51. package/dist/client/components/ValidatedTextInput.d.ts.map +1 -1
  52. package/dist/client/components/ValidatedTextInput.js +0 -1
  53. package/dist/client/components/ValidatedTextInput.js.map +1 -1
  54. package/dist/client/createSocket.d.ts.map +1 -1
  55. package/dist/client/createSocket.js +3 -3
  56. package/dist/client/createSocket.js.map +1 -1
  57. package/dist/server/App.d.ts +29 -3
  58. package/dist/server/App.d.ts.map +1 -1
  59. package/dist/server/App.js +72 -7
  60. package/dist/server/App.js.map +1 -1
  61. package/dist/server/Router.d.ts +1 -1
  62. package/dist/server/Router.d.ts.map +1 -1
  63. package/dist/server/Router.js +6 -1
  64. package/dist/server/Router.js.map +1 -1
  65. package/dist/server/Socket.d.ts.map +1 -1
  66. package/dist/server/Socket.js +21 -8
  67. package/dist/server/Socket.js.map +1 -1
  68. package/dist/server/StoreHandlers.d.ts +4 -0
  69. package/dist/server/StoreHandlers.d.ts.map +1 -0
  70. package/dist/server/StoreHandlers.js +160 -0
  71. package/dist/server/StoreHandlers.js.map +1 -0
  72. package/dist/server/index.d.ts +1 -1
  73. package/dist/server/index.d.ts.map +1 -1
  74. package/dist/shared/Socket.d.ts +33 -3
  75. package/dist/shared/Socket.d.ts.map +1 -1
  76. package/eslint.config.js +106 -0
  77. package/package.json +2 -1
  78. package/templates/.claude/skills/fix-code/SKILL.md +16 -4
  79. package/templates/client/pages/AITestPage.tsx +17 -56
  80. package/templates/client/pages/AuthDemoPage.tsx +13 -33
  81. package/templates/client/pages/HomePage.tsx +14 -22
  82. package/templates/client/pages/SearchPage.tsx +7 -13
  83. package/templates/client/pages/TodoDemoPage.tsx +222 -0
  84. package/templates/client/pages/UserPage.tsx +7 -13
  85. package/templates/client/styles.css +0 -3
  86. package/templates/eslint.config.js +78 -0
  87. package/templates/package.json +5 -2
  88. package/templates/postcss.config.js +0 -1
  89. package/templates/vitest.config.ts +21 -0
  90. package/templates/tailwind.config.js +0 -6
package/README.md CHANGED
@@ -8,8 +8,7 @@ A full-stack TypeScript framework for building production-ready web applications
8
8
  - **Client**: React + Vite with typed routing, lazy pages, and popup management
9
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, Kie) + image generation (Together, FAL, Google, Wavespeed, Kie) + embeddings + STT/TTS
12
- - **Analytics**: EventCounter (high-throughput counters), EventLog (event capture), A/B experiments
11
+ - **AI**: Text generation (Together, Claude, OpenAI, Google, Groq, Fireworks) + image generation (Together, FAL, Google, Wavespeed) + embeddings + STT/TTS
13
12
  - **Storage**: Cloudflare R2 / AWS S3 with presigned uploads
14
13
  - **CLI**: `ugly-app` commands for dev, build, deploy, migrations, logs, and auth utilities
15
14
 
@@ -32,22 +31,16 @@ Entry point for the server. Creates an Express + WebSocket server with typed RPC
32
31
  ```typescript
33
32
  import {
34
33
  createApp,
35
- createImageGen,
36
- createTextGen,
37
34
  createUserHelper,
38
- eventLogCapture,
39
- eventLogServerCapture,
40
35
  getFeedbackHandlers,
41
- getExperimentAssignments,
42
36
  type AppConfigurator,
43
37
  type RequestHandlers,
44
38
  } from 'ugly-app';
45
39
  import { dbDefaults } from 'ugly-app/shared';
46
40
  import { requests } from '../shared/api';
47
- import type { User } from '../shared/collections';
48
41
  import { collections } from '../shared/collections';
49
- import { experiments } from '../shared/experiments';
50
42
  import { pages } from '../shared/pages';
43
+ import type { User } from '../shared/collections';
51
44
 
52
45
  const userHelper = createUserHelper<User>(collections.user);
53
46
  const maintainBotUserId = process.env.MAINTAIN_BOT_USER_ID ?? '';
@@ -56,38 +49,10 @@ const app = createApp(
56
49
  { requests },
57
50
  {
58
51
  ...getFeedbackHandlers(maintainBotUserId),
59
-
60
52
  getMe: async (userId: string) => {
61
53
  const user = await userHelper.get(app.db, userId);
62
54
  return { userId, email: user?.email, phone: user?.phone };
63
55
  },
64
-
65
- // Experiment bucketing + session start event
66
- initSession: async (userId, { sessionId }) => {
67
- const branches = getExperimentAssignments(userId, sessionId, experiments);
68
- await eventLogServerCapture('SESSION_START', {}, sessionId, userId, branches);
69
- return { branches };
70
- },
71
-
72
- // Generic event capture with experiment branch tagging
73
- captureEvent: async (userId, { eventName, sessionId, properties }) => {
74
- const branches = getExperimentAssignments(userId, sessionId, experiments);
75
- const { eventId } = await eventLogCapture(
76
- { eventName, sessionId, userId, properties: properties ?? {}, experimentBranches: branches },
77
- userId,
78
- );
79
- return { eventId };
80
- },
81
-
82
- textGen: async (userId, { model, messages }) => {
83
- const text = await createTextGen(userId).generate(messages, { model });
84
- return { text };
85
- },
86
-
87
- imageGen: async (userId, { model, prompt }) => {
88
- const url = await createImageGen(userId).generate(prompt, { model });
89
- return { url };
90
- },
91
56
  } satisfies RequestHandlers<typeof requests>,
92
57
  collections,
93
58
  (configurator: AppConfigurator) => {
@@ -672,16 +637,14 @@ Run `npm run db:init` to create/update indexes.
672
637
  ### Text generation
673
638
 
674
639
  ```typescript
675
- import { createTextGen } from 'ugly-app';
676
- const textGen = createTextGen(userId);
640
+ import { createTextGenClient } from 'ugly-app';
641
+ const textGen = createTextGenClient();
677
642
 
678
643
  const text = await textGen.generate(messages);
679
644
  const json = await textGen.generateJson(schema, messages); // Zod schema, retries on parse failure
680
645
  const result = await textGen.generateWithTools(messages, tools); // automatic tool-call loop
681
646
  ```
682
647
 
683
- `createTextGen(userId, defaults?)` creates a client scoped to a user (for rate limiting and billing). Optional `defaults` set provider, model, temperature, etc.
684
-
685
648
  | Provider | `provider` value | Default model | JSON | Tools | Vision |
686
649
  |----------|-----------------|---------------|------|-------|--------|
687
650
  | Together AI | `'together'` | Llama-4-Maverick-17B-128E | yes | yes | yes |
@@ -697,14 +660,12 @@ Use `provider: 'auto'` (default) to let the system pick based on requirements.
697
660
  ### Image generation
698
661
 
699
662
  ```typescript
700
- import { createImageGen } from 'ugly-app';
701
- const imageGen = createImageGen(userId);
663
+ import { createImageGenClient } from 'ugly-app';
664
+ const imageGen = createImageGenClient();
702
665
 
703
666
  const url = await imageGen.generate(prompt, { width: 1024, height: 1024 });
704
667
  ```
705
668
 
706
- `createImageGen(userId, defaults?)` works the same way — user-scoped with optional defaults.
707
-
708
669
  | Provider | `provider` value |
709
670
  |----------|-----------------|
710
671
  | Together AI (FLUX schnell) | `'together'` |
@@ -885,104 +846,6 @@ submitFeedback: authReq({
885
846
 
886
847
  The framework enforces rate limits automatically before calling the handler.
887
848
 
888
- ### EventCounter
889
-
890
- High-throughput counters aggregated into hourly buckets in MongoDB. Increments are instant (in-memory) and flushed every 60 seconds.
891
-
892
- ```typescript
893
- import {
894
- eventCounterIncrement,
895
- startEventCounterFlush,
896
- flushCountersToMongo,
897
- eventCounterGetCounts,
898
- eventCounterGetTopTypes,
899
- } from 'ugly-app';
900
-
901
- // Increment a counter (instant, no DB write)
902
- eventCounterIncrement('api_getMe');
903
-
904
- // Start periodic flush — call once at server startup
905
- startEventCounterFlush();
906
-
907
- // Manually flush (e.g. on graceful shutdown)
908
- await flushCountersToMongo();
909
-
910
- // Query counts (admin only)
911
- const { counts } = await eventCounterGetCounts(
912
- { granularity: 'hours', intervals: 24, category: 'api' },
913
- isAdmin,
914
- );
915
-
916
- // Query top counter types (admin only)
917
- const { types } = await eventCounterGetTopTypes(
918
- { limit: 10, category: 'api' },
919
- isAdmin,
920
- );
921
- ```
922
-
923
- Counter type names use a `category_name` convention (e.g. `api_getMe`, `ai_textGen`). Data is auto-expired after 30 days via MongoDB TTL.
924
-
925
- ### EventLog
926
-
927
- Structured event capture for analytics. Events are stored in MongoDB with session, user, and experiment branch associations.
928
-
929
- ```typescript
930
- import { eventLogCapture, eventLogServerCapture } from 'ugly-app';
931
-
932
- // Capture an event (returns eventId)
933
- const { eventId } = await eventLogCapture(
934
- {
935
- eventName: 'BUTTON_CLICK',
936
- sessionId,
937
- userId,
938
- properties: { page: 'home', target: 'cta' },
939
- experimentBranches: branches,
940
- },
941
- userId,
942
- );
943
-
944
- // Server-side shorthand (fire-and-forget, no eventId returned)
945
- await eventLogServerCapture('SESSION_START', { source: 'web' }, sessionId, userId, branches);
946
- ```
947
-
948
- Admin query functions: `eventLogGetList`, `eventLogGetCounts`, `eventLogGetTopEvents`, `eventLogGetTopUsers`, `eventLogGetTopSessions`, `eventLogGetUniqueUsersCounts`, `eventLogGetUniqueSessionsCounts`.
949
-
950
- ### Experiments (A/B testing)
951
-
952
- Define experiments in `shared/experiments.ts` and use deterministic bucketing to assign users to branches.
953
-
954
- ```typescript
955
- // shared/experiments.ts
956
- import type { Experiment } from 'ugly-app/shared';
957
-
958
- export const experiments: Experiment[] = [
959
- {
960
- id: 'onboarding-v2',
961
- name: 'Onboarding V2',
962
- description: 'Test new onboarding flow',
963
- branches: [
964
- { id: 'control', name: 'Control', weight: 1 },
965
- { id: 'variant', name: 'Variant', weight: 1 },
966
- ],
967
- events: ['ONBOARDING_COMPLETE', 'SIGNUP'],
968
- active: true,
969
- },
970
- ];
971
- ```
972
-
973
- ```typescript
974
- // Server-side assignment
975
- import { getExperimentAssignments, getExperimentBranch } from 'ugly-app';
976
-
977
- // Get all branch assignments for a user/session (deterministic hash)
978
- const branches = getExperimentAssignments(userId, sessionId, experiments);
979
-
980
- // Get a single experiment branch
981
- const branch = getExperimentBranch(userId, sessionId, experiment);
982
- ```
983
-
984
- Shared helpers: `getActiveExperiments`, `getExperimentById`, `getExperimentBranch`, `getExperimentAssignments`. Server admin: `experimentGetMetrics` for per-branch analytics.
985
-
986
849
  ---
987
850
 
988
851
  ## Built-in endpoints