ugly-app 0.1.917 → 0.1.920

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 (47) hide show
  1. package/README.md +76 -60
  2. package/dist/cli/publish/orchestrator.d.ts.map +1 -1
  3. package/dist/cli/publish/orchestrator.js +15 -1
  4. package/dist/cli/publish/orchestrator.js.map +1 -1
  5. package/dist/cli/publish/providers/cloudflare.d.ts +15 -4
  6. package/dist/cli/publish/providers/cloudflare.d.ts.map +1 -1
  7. package/dist/cli/publish/providers/cloudflare.js +56 -14
  8. package/dist/cli/publish/providers/cloudflare.js.map +1 -1
  9. package/dist/cli/uglyappConfig.d.ts +28 -0
  10. package/dist/cli/uglyappConfig.d.ts.map +1 -1
  11. package/dist/cli/uglyappConfig.js.map +1 -1
  12. package/dist/cli/version.d.ts +1 -1
  13. package/dist/cli/version.js +1 -1
  14. package/dist/client/Logger.d.ts +4 -0
  15. package/dist/client/Logger.d.ts.map +1 -1
  16. package/dist/client/Logger.js +33 -0
  17. package/dist/client/Logger.js.map +1 -1
  18. package/dist/client/createSocket.d.ts.map +1 -1
  19. package/dist/client/createSocket.js +62 -9
  20. package/dist/client/createSocket.js.map +1 -1
  21. package/dist/inspect/host-bundle.js +8 -8
  22. package/dist/server/Socket.d.ts.map +1 -1
  23. package/dist/server/Socket.js +4 -1
  24. package/dist/server/Socket.js.map +1 -1
  25. package/dist/server/adapter/workers/createWorkersApp.d.ts.map +1 -1
  26. package/dist/server/adapter/workers/createWorkersApp.js +5 -1
  27. package/dist/server/adapter/workers/createWorkersApp.js.map +1 -1
  28. package/dist/server/adapter/workers/wrapWithAssets.d.ts.map +1 -1
  29. package/dist/server/adapter/workers/wrapWithAssets.js +40 -0
  30. package/dist/server/adapter/workers/wrapWithAssets.js.map +1 -1
  31. package/dist/shared/ProxyOps.d.ts +14 -0
  32. package/dist/shared/ProxyOps.d.ts.map +1 -1
  33. package/dist/shared/ProxyOps.js +9 -0
  34. package/dist/shared/ProxyOps.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/cli/publish/orchestrator.ts +15 -0
  37. package/src/cli/publish/providers/cloudflare.ts +80 -22
  38. package/src/cli/publish/providers/queueConsumer.test.ts +115 -0
  39. package/src/cli/uglyappConfig.ts +29 -0
  40. package/src/cli/version.ts +1 -1
  41. package/src/client/Logger.ts +35 -0
  42. package/src/client/createSocket.ts +64 -8
  43. package/src/client/opaqueScriptError.test.ts +35 -0
  44. package/src/server/Socket.ts +4 -1
  45. package/src/server/adapter/workers/createWorkersApp.ts +5 -1
  46. package/src/server/adapter/workers/wrapWithAssets.ts +45 -0
  47. package/src/shared/ProxyOps.ts +9 -0
package/README.md CHANGED
@@ -1,25 +1,26 @@
1
1
  # ugly-app
2
2
 
3
- A full-stack TypeScript framework for shipping production web apps. Scaffold with
4
- `npx ugly-app init my-app` and get an opinionated Express + React + PostgreSQL
5
- stack with type-safe RPC over WebSocket and HTTP, real-time document tracking,
6
- built-in auth, AI generation, storage, workers, and a CLI for every workflow.
3
+ A full-stack TypeScript framework for shipping production web apps. Scaffold
4
+ with `npx ugly-app init my-app` and get an opinionated Node + React + Postgres
5
+ stack with type-safe RPC over WebSocket and HTTP, real-time doc subscriptions,
6
+ built-in auth, AI generation, storage, workers, cron, and a CLI for every
7
+ workflow.
7
8
 
8
- ugly-app is designed to run against [ugly.bot](https://ugly.bot), which provides
9
- auth, infra (PostgreSQL, Qdrant, NATS, S3-compatible object storage), AI provider
10
- keys, and deployment. Your app talks to all of it through the project's dev
11
- tunnel and its `UGLY_BOT_TOKEN`.
9
+ ugly-app is designed to run against [ugly.bot](https://ugly.bot), which
10
+ provides auth, infra (Postgres, Qdrant, NATS, S3-compatible object storage),
11
+ AI provider keys, push, email, and deployment. Your app talks to all of it
12
+ through the project's dev tunnel and its `UGLY_BOT_TOKEN`.
12
13
 
13
14
  ## What's included
14
15
 
15
- - **Server** — Express + WebSocket with type-safe RPC and Zod validation.
16
+ - **Server** — Express + WebSocket with typed RPC and Zod validation.
16
17
  - **Client** — React 19 + Vite with typed routing, lazy pages, animated transitions, and a router-owned popup layer.
17
- - **Database** — `TypedDB` over PostgreSQL (JSONB) or Cloudflare D1, with full-text and vector search.
18
+ - **Database** — `TypedDB` over Postgres (JSONB) or Cloudflare D1, with full-text and vector search.
18
19
  - **Auth** — HttpOnly cookies + JWT. ugly.bot OAuth by default; magic-link / Google in `mode: 'self'`.
19
20
  - **AI** — Text, JSON, image, embeddings, web search — proxied through ugly.bot.
20
21
  - **Realtime** — NATS pub/sub and doc change subscriptions (`trackDoc` / `trackDocs`).
21
22
  - **Storage** — S3-compatible buckets with presigned uploads.
22
- - **Workers & cron** — `setWorkers()` registers named async tasks with optional Zod input schemas and cron schedules.
23
+ - **Workers & cron** — `setWorkers()` registers named async tasks with optional Zod schemas and cron schedules.
23
24
  - **Two-adapter deploy** — Same source runs on Node (Express) or Cloudflare Workers (Hono + Durable Objects).
24
25
  - **CLI** — `ugly-app` commands for dev, build, deploy, migrations, logs, AI, and auth.
25
26
 
@@ -37,12 +38,13 @@ wired up.
37
38
 
38
39
  ---
39
40
 
40
- ## Server — `createApp()` / `AppConfigurator`
41
+ ## Server — `createApp()`
41
42
 
42
- The single server entry point. Returns an `App` that owns Express, the
43
- WebSocket server, the typed DB, and the RPC dispatcher.
43
+ Single server entry point. Returns an `App` that owns Express, the WebSocket
44
+ server, the typed DB, and the RPC dispatcher.
44
45
 
45
46
  ```ts
47
+ // server/index.ts
46
48
  import {
47
49
  createApp,
48
50
  type AppConfigurator,
@@ -53,17 +55,29 @@ import type { WorkerHandlers } from 'ugly-app/shared';
53
55
  import { messages, requests } from '../shared/api';
54
56
  import { collections } from '../shared/collections';
55
57
  import { cronTasks } from '../shared/cron';
58
+ import { experiments } from '../shared/experiments';
56
59
  import { pages } from '../shared/pages';
60
+ import { stringsDef } from '../shared/strings';
61
+ import en from '../shared/lang/en';
62
+ import es from '../shared/lang/es';
57
63
 
58
64
  const cronHandlers: WorkerHandlers<typeof cronTasks> = { /* … */ };
59
65
 
60
66
  const app = createApp(
61
- { requests, messages }, // AppRegistryBase (add `pages` for typed pushSend)
67
+ { requests, messages },
62
68
  { /* handlers */ } satisfies RequestHandlers<typeof requests>,
63
69
  collections,
64
70
  (configurator: AppConfigurator) => {
65
71
  configurator.setPages({ pages });
72
+ configurator.setExperiments(experiments);
73
+ configurator.setStrings({
74
+ defaultLang: stringsDef.defaultLang,
75
+ langs: stringsDef.langs,
76
+ criticalKeys: stringsDef.criticalKeys,
77
+ getTable: (lang) => ({ en, es } as Record<string, unknown>)[lang] ?? en,
78
+ });
66
79
  configurator.setWorkers(cronTasks, cronHandlers);
80
+ configurator.setOnEmail(async (inbound: InboundEmail) => { /* … */ });
67
81
  },
68
82
  );
69
83
 
@@ -97,16 +111,17 @@ call `configurator.setPages()` still work — they just get the loose
97
111
  | `start(port?)` | Start the server (default 3000; templates use 4321). |
98
112
  | `db` | The `TypedDB<Map>` (map inferred from your collections + framework built-ins). |
99
113
  | `httpServer` | The underlying Node `http.Server`. |
100
- | `wss` | The main `WebSocketServer` (path set by `setWsPath`, default `/rpc`). |
101
- | `dispatch(name, input, userId)` | Invoke an RPC handler programmatically (inherited from `AppRouter`). |
114
+ | `wss` | The main `WebSocketServer` (path set via `setWsPath`, default `/rpc`). |
115
+ | `dispatch(name, input, userId)` | Invoke a registered RPC handler programmatically (inherited from `AppRouter`). |
102
116
  | `registerRoutes(fn)` | Mount additional Express routes after creation. |
103
- | `pushSend(input)` | Send a push whose click-through target is a route from this app's `pages` table. `page` and `query` are type-checked when the registry carries `pages`; the framework mints a `https://ugly.bot/l/<code>` short link so the click-through is always absolute and dock-app-routable. |
117
+ | `pushSend(input)` | Send a push whose click-through target is a route from this app's `pages`. `page` and `query` are type-checked when the registry carries `pages`; the framework mints a `https://ugly.bot/l/<code>` short link so the click-through is always absolute and dock-app-routable. |
104
118
 
105
- Framework services start automatically inside `app.start()`: schema drift check,
106
- NATS connection + KV buckets, data-proxy connection, event-counter flush, TTL
107
- cleanup for log tables, console/error capture, and ugly.bot log forwarding.
108
- Postgres, NATS, storage, and AI clients are loaded **lazily** — a host without
109
- `DATABASE_URL` / `NATS_URL` / `R2_BUCKET` doesn't pay their startup cost.
119
+ Framework services start automatically inside `app.start()`: schema drift
120
+ check, NATS connection + KV buckets, data-proxy connection, event-counter
121
+ flush, TTL cleanup for log tables, console/error capture, and ugly.bot log
122
+ forwarding. Postgres, NATS, storage, and AI clients are loaded **lazily** — a
123
+ host without `DATABASE_URL` / `NATS_URL` / `R2_BUCKET` doesn't pay their
124
+ startup cost.
110
125
 
111
126
  ### `AppConfigurator`
112
127
 
@@ -115,7 +130,7 @@ Every method is optional; `setPages` is what mounts the SPA.
115
130
  | Method | Description |
116
131
  |--------|-------------|
117
132
  | `setPages({ pages, renderPage?, clientDistPath? })` | Mount the SPA. Dev runs Vite in middleware mode; prod serves `clientDistPath` (default `dist/client`). Provide `renderPage(routeName, params) => Promise<string>` to SSR any `ssr: true` pages. |
118
- | `setUserHelper(helper)` | Provide a `UserHelper<UserBase>` — required for WebSocket auth (framework reads/writes the `user` collection through it). |
133
+ | `setUserHelper(helper)` | A `UserHelper<UserBase>` the framework uses to look up / create users on WebSocket auth. Falls back to a minimal shim if unset. |
119
134
  | `setOnUserCreate(handler)` | `(userId, { email?, phone? }, db) => Promise<void>` — called on first login; create the user record. |
120
135
  | `setAuth(provider)` | Replace the default auth provider (`verify(code)`, `authUrl(origin)`, optional `registerRoutes`). |
121
136
  | `setOnSocketMessage(handler)` | Single raw-WebSocket handler. `(ws, userId, msg) => boolean` — return `true` to consume, `false` to fall through. **Replaces** any previously registered handler. |
@@ -123,7 +138,7 @@ Every method is optional; `setPages` is what mounts the SPA.
123
138
  | `setWsPath(path)` | Override the WebSocket path (default `/rpc`). |
124
139
  | `setOnWsAuth(handler)` | `(ws, userId, req) => void` — fires after a socket session authenticates. |
125
140
  | `setOnAfterStart(handler)` | `(db) => Promise<void>` — called once after data-proxy + NATS are ready. |
126
- | `setOnMinuteTick(fn)` / `setOnHourlyTick(fn)` | Framework-managed periodic callbacks. Only fire when `CLOCK_ENABLED=true`. |
141
+ | `setOnMinuteTick(fn)` / `setOnHourlyTick(fn)` | Framework-managed periodic callbacks. Fire only when `CLOCK_ENABLED=true`. |
127
142
  | `setHealthHandler(fn)` | Override the default `GET /health`. |
128
143
  | `setExperiments(experiments)` | Register `Experiment` definitions for `initSession` / `captureEvent` bucketing. |
129
144
  | `setIsAdmin(fn)` | `(userId, db) => boolean \| Promise<boolean>` — gate for admin-only framework requests. Defaults to matching `MAINTAIN_BOT_USER_ID`. |
@@ -131,7 +146,7 @@ Every method is optional; `setPages` is what mounts the SPA.
131
146
  | `setOnEmail(handler)` | Handle inbound emails routed to `{domain}@ugly.bot` (delivered as internal HTTP). |
132
147
  | `setCronTasks(tasks, handlers)` | **Deprecated.** Legacy cron-only registry — prefer `setWorkers()`. |
133
148
  | `setWorkers(workers, handlers)` | Register named async tasks with optional Zod input schema and cron schedule. Powers `GET /_workers/manifest`, `POST /_workers/run`, and the cron orchestrator. Scheduled workers are mirrored into the cron registry automatically. |
134
- | `setStrings(config)` | Localization config — framework injects language + critical strings into SSR HTML. |
149
+ | `setStrings(config)` | Localization config — the framework injects language + critical strings into SSR HTML. |
135
150
  | `registerRoutes(fn)` | Mount custom Express routes. |
136
151
  | `setWorkerQueue(queue)` | Register a `WorkerQueue` with `start()` / `stop()` for lifecycle management. |
137
152
 
@@ -142,10 +157,10 @@ captured imports (`app.db`, `storage`, `pgQuery`, `uglyBotRequest`, etc.).
142
157
 
143
158
  ```ts
144
159
  // req() — public, userId may be null
145
- getPublicData: async (userId: string | null, input) => { … }
160
+ getPublicData: async (userId: string | null, input) => { /* … */ }
146
161
 
147
162
  // authReq() — authenticated, framework returns 401 if no/invalid token
148
- getMe: async (userId: string, input) => { … }
163
+ getMe: async (userId: string, input) => { /* … */ }
149
164
  ```
150
165
 
151
166
  ### Built-in framework requests
@@ -193,7 +208,7 @@ export const requests = defineRequests({
193
208
  output: z.object({ userId: z.string(), email: z.string().optional() }),
194
209
  }),
195
210
 
196
- // With per-endpoint rate limiting (enforced before the handler runs)
211
+ // Per-endpoint rate limiting — enforced before the handler runs
197
212
  submitFeedback: authReq({
198
213
  input: z.object({ type: z.enum(['bug', 'design', 'feature']), message: z.string() }),
199
214
  output: z.object({ id: z.string() }),
@@ -202,9 +217,9 @@ export const requests = defineRequests({
202
217
  });
203
218
  ```
204
219
 
205
- Every request is reachable as **both** `socket.request(name, input)` (WebSocket)
206
- and `POST /api/:name { input }` (HTTP). `z` is re-exported from Zod for
207
- convenience.
220
+ Every request is reachable as **both** `socket.request(name, input)`
221
+ (WebSocket) and `POST /api/:name { input }` (HTTP). `z` is re-exported from
222
+ Zod for convenience.
208
223
 
209
224
  ### Collections (`shared/collections.ts`)
210
225
 
@@ -233,26 +248,26 @@ export const collections = defineCollections({
233
248
  });
234
249
  ```
235
250
 
236
- `CollectionMeta`:
237
- - **`db`** — **required.** `neon` (Postgres) or `d1` (Cloudflare D1 / SQLite). Both handles are exported from `ugly-app/shared`. Omitting it is a compile error and a runtime throw — there is no implicit default. A `d1` collection may also declare `search` (SQLite FTS5) and/or `vector` (Cloudflare Vectorize).
238
- - `cache` — `true` / `false` or `{ ttlMs }`. When enabled, `getDoc` / `getByIds` read from an LRU cache; writes invalidate it.
251
+ **`CollectionMeta`:**
252
+ - `db` — **required.** `neon` (Postgres) or `d1` (Cloudflare D1 / SQLite). Both handles are exported from `ugly-app/shared`. Omitting it is a compile error and runtime throw. A `d1` collection may also declare `search` (SQLite FTS5) and/or `vector` (Cloudflare Vectorize).
253
+ - `cache` — `true` / `false` / `{ ttlMs }`. When enabled, `getDoc` / `getByIds` read from an LRU cache; writes invalidate it.
239
254
  - `trackable` — enables real-time `trackDoc` / `trackDocs` via change-stream NATS notifications.
240
255
  - `public` — allow unauthenticated client reads.
241
256
  - `cascadeFrom` — parent collection: when the parent doc is deleted, docs in this collection are cascade-deleted.
242
257
  - `trackKeys?` — fields usable as NATS routing keys for scoped `trackDocs` subscriptions.
243
- - `getter?(ids)` — batch resolver for collections that have no local table (framework-only pattern; e.g. `userPublic` resolves via ugly.bot).
258
+ - `getter?(ids)` — batch resolver for collections with no local table (framework pattern; e.g. `userPublic` resolves via ugly.bot).
244
259
  - `skipReadValidation?` — opt out of per-row zod validation on reads for very hot collections. Writes are always validated. Global kill-switch: `UGLY_DB_VALIDATE_READS=0`.
245
260
  - `search?: { fields, language? }` — full-text index over the named JSONB paths (Neon: Postgres FTS; D1: SQLite FTS5, ranked by bm25).
246
- - `vector?: { dimensions, metric?, filterable? }` — ANN index. The vector is supplied out-of-band at write time (`setDoc(c, doc, { vec })`) — never stored in the doc JSON. Query with `getDocs(c, filter, { near })`.
261
+ - `vector?: { dimensions, metric?, filterable? }` — ANN index. Vector supplied out-of-band at write time (`setDoc(c, doc, { vec })`) — never stored in the doc JSON. Query with `getDocs(c, filter, { near })`.
247
262
 
248
263
  All documents extend `DBObject`: `{ _id, version, created, updated }`. Use
249
- `dbDefaults()` to stamp `version` / `created` / `updated` on inserts. **Always
250
- generate `_id` with `nanoid()`** — never `crypto.randomUUID()`, `Date.now()`, or
251
- `Math.random()` (see `CLAUDE.md`).
264
+ `dbDefaults()` to stamp `version` / `created` / `updated` on inserts.
265
+ **Always generate `_id` with `nanoid()`** — never `crypto.randomUUID()`,
266
+ `Date.now()`, or `Math.random()` (see `CLAUDE.md`).
252
267
 
253
- After schema changes, run `npm run db:schema-gen` then `npm run db:migrate`. The
254
- app refuses to start when drift is detected (set `SCHEMA_CHECK_SKIP=true` only
255
- as a last resort).
268
+ After schema changes, run `npm run db:schema-gen` then `npm run db:migrate`.
269
+ The app refuses to start when drift is detected (set `SCHEMA_CHECK_SKIP=true`
270
+ only as a last resort).
256
271
 
257
272
  ---
258
273
 
@@ -276,9 +291,10 @@ export type AppPages = typeof pages;
276
291
  ```
277
292
 
278
293
  `definePage<Params>(options?)` returns a `PageDef<Params>` — a runtime object
279
- carrying `PageMeta` plus a phantom `_params` used only for TypeScript inference.
294
+ carrying `PageMeta` plus a phantom `_params` used only for TypeScript
295
+ inference.
280
296
 
281
- Options (all optional):
297
+ **Options** (all optional):
282
298
 
283
299
  - `auth` (default `true`) — protected route. When `isAuthenticated()` returns false on this route, the router synchronously renders the framework's `<AuthRoot>` in place of the page (dispatches on `window.__UGLY_APP_AUTH_MODE__`: Mode A → `<LoginPopup>`, Mode B → `<MagicLinkForm>` plus optional Google button). Apps cannot override this fallback — the framework owns the login UX so no route can accidentally ship as auth-required with no way to log in.
284
300
  - `ssr` (default `false`) — server-render the page for SEO. `{ auth: true, ssr: true }` is silently dropped to `ssr: false` with a warning: the SSR document is served from a shared edge cache and must not depend on the viewer.
@@ -310,7 +326,7 @@ export const {
310
326
 
311
327
  **Config:**
312
328
  - `pages` (required) — the `PageRegistry` from `shared/pages.ts`.
313
- - `allPages?` — the lazy route → element map (`PageMap<Pages>`). Prefer registering it via `setAllPages()` from the browser entry when the app also server-renders — the Worker bundle has no code splitting, so a static import inlines every page (and its deps) into `worker.js`.
329
+ - `allPages?` — the lazy route → element map (`PageMap<Pages>`). Prefer registering via `setAllPages()` from the browser entry when the app also server-renders — the Worker bundle has no code splitting, so a static import inlines every page (and its deps) into `worker.js`.
314
330
  - `ssrPages?` — `Partial<{ [K]: ComponentType<Params> }>` of statically-imported components for `ssr: true` routes. Used by both the Worker's `renderToString` and the client's first hydration render so the initial paint resolves synchronously and never mismatches the server output.
315
331
 
316
332
  **`createRouter` returns:**
@@ -347,8 +363,8 @@ export const allPages = {
347
363
  - **`lazyPage(factory)`** — lazy-imports a default-exported `React.ComponentType<Params>`. The page receives route params as props.
348
364
  - **`lazyPageLoader(factory)`** — lazy-imports an async loader `(params) => Promise<ReactElement>`. Use when a route needs data fetching before render. The loader file is the chunk boundary, so it can statically import its page component.
349
365
 
350
- Both wrappers recover from stale-deploy chunk 404s ("Failed to fetch dynamically
351
- imported module"): on the first failure they trigger a single
366
+ Both wrappers recover from stale-deploy chunk 404s ("Failed to fetch
367
+ dynamically imported module"): on the first failure they trigger a single
352
368
  `window.location.reload()` (guarded by `sessionStorage` so a genuinely broken
353
369
  chunk can't loop) to fetch the fresh `index.html` with new chunk hashes.
354
370
 
@@ -387,8 +403,8 @@ match a registered route.
387
403
  ### Popups — `openPopup()`
388
404
 
389
405
  `useRouter().openPopup()` is the canonical modal / sheet / menu API. The
390
- router owns the popup layer, drives a spring animation (via
391
- `createAnimatedValue`), and stacks popups z-index-correctly above every page.
406
+ router owns the popup layer, drives a spring animation, and stacks popups
407
+ z-index-correctly above every page.
392
408
 
393
409
  ```tsx
394
410
  const { openPopup } = useRouter();
@@ -449,7 +465,7 @@ bootstrapApp({
449
465
  });
450
466
  ```
451
467
 
452
- `BootstrapAppOptions`:
468
+ **`BootstrapAppOptions`:**
453
469
 
454
470
  | Field | Description |
455
471
  |-------|-------------|
@@ -504,12 +520,11 @@ const {
504
520
  ```
505
521
 
506
522
  `useApp<TAsyncOptions>()` is generic in the async-options type so apps can
507
- pass a custom option through to a custom `loadingOverlay` element (accepted as
508
- an `AppProvider` prop — the framework `cloneElement`s the overlay with
509
- `{ label, asyncOptions }` while `runAsync` is pending). `useAppOptional()`
510
- returns `null` outside the provider; `useLocalizer()` returns a localizer that
511
- prefers `<StringsProvider>` data, falls back to the `AppProvider` `localizer`
512
- prop, and finally to identity.
523
+ pass a custom option through to a custom `loadingOverlay` element. `AppProvider`
524
+ `cloneElement`s the overlay with `{ label, asyncOptions }` while `runAsync` is
525
+ pending. `useAppOptional()` returns `null` outside the provider; `useLocalizer()`
526
+ returns a localizer that prefers `<StringsProvider>` data, falls back to the
527
+ `AppProvider` `localizer` prop, and finally to identity.
513
528
 
514
529
  Two popup APIs coexist: `useRouter().openPopup()` (spring-animated,
515
530
  dismissible, mode-aware — the default choice) and `useApp().showPopup()` (a
@@ -575,7 +590,7 @@ Any GET request with `?token=<JWT>` will, if the token verifies, set the
575
590
  cookie and 302-redirect to the same URL without the token parameter — useful
576
591
  for embedding any page in an iframe.
577
592
 
578
- ### Built-in routes
593
+ ### Built-in auth routes
579
594
 
580
595
  | Endpoint | Description |
581
596
  |----------|-------------|
@@ -597,7 +612,7 @@ configurator.setAuth({
597
612
  });
598
613
  ```
599
614
 
600
- ### Server-side helpers
615
+ ### Server-side auth helpers
601
616
 
602
617
  - `verifyToken(token)` — verifies a token against ugly.bot and returns the `userId`.
603
618
  - `getRequestUser(req)` — synchronous decode of the per-project `UGLY_PROJECT_TOKEN` cookie set by ugly.bot's wake-on-traffic gate. Returns `{ userId } | null` without a network round-trip; safe as the primary auth check in deployed-app handlers.
@@ -927,7 +942,7 @@ requests automatically tag events with the user's branch assignments.
927
942
 
928
943
  ---
929
944
 
930
- ## Built-in endpoints
945
+ ## Built-in HTTP endpoints
931
946
 
932
947
  | Endpoint | Description |
933
948
  |----------|-------------|
@@ -955,6 +970,7 @@ requests automatically tag events with the user's branch assignments.
955
970
  | `ugly-app/shared` | Cross-tier: `defineRequests`, `defineCollections`, `definePage`, `defineWorkers`, Zod, experiments, time constants. |
956
971
  | `ugly-app/client` | React: `bootstrapApp`, `createRouter`, `lazyPage`, `AppProvider`, components, animations, audio, AI helpers. |
957
972
  | `ugly-app/server/adapter/workers` | Cloudflare Workers entry (Hono router + Durable Objects + Neon HTTP driver + R2). |
973
+ | `ugly-app/server/ssr` | SSR renderer used by the Workers adapter. |
958
974
  | `ugly-app/conversation/{shared,server,client,engine}` | AI chat sessions with persisted history. |
959
975
  | `ugly-app/collab/{server,client}` | Yjs-based collaborative editing. |
960
976
  | `ugly-app/markdown/{shared,client}` | Markdown rendering + editor. |
@@ -1 +1 @@
1
- {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../../src/cli/publish/orchestrator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../../src/cli/publish/orchestrator.ts"],"names":[],"mappings":"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"}
@@ -2248,7 +2248,21 @@ export async function runPublish(input) {
2248
2248
  // Register Cron Triggers + attach the queue consumer now that the
2249
2249
  // script exists (both reference the deployed worker by name).
2250
2250
  if (queueId) {
2251
- await cloudflare.ensureQueueConsumer(cloudflareToken, state.cloudflare.accountId, queueId, input.appName);
2251
+ const queueCfg = readUglyAppConfig(input.projectPath)?.queue;
2252
+ await cloudflare.ensureQueueConsumer(cloudflareToken, state.cloudflare.accountId, queueId, input.appName, {
2253
+ ...(queueCfg?.maxConcurrency !== undefined
2254
+ ? { maxConcurrency: queueCfg.maxConcurrency }
2255
+ : {}),
2256
+ ...(queueCfg?.batchSize !== undefined
2257
+ ? { batchSize: queueCfg.batchSize }
2258
+ : {}),
2259
+ ...(queueCfg?.maxRetries !== undefined
2260
+ ? { maxRetries: queueCfg.maxRetries }
2261
+ : {}),
2262
+ ...(queueCfg?.maxWaitTimeMs !== undefined
2263
+ ? { maxWaitTimeMs: queueCfg.maxWaitTimeMs }
2264
+ : {}),
2265
+ });
2252
2266
  }
2253
2267
  // Always PUT schedules (empty array clears stale triggers).
2254
2268
  await cloudflare.putCronTriggers(cloudflareToken, state.cloudflare.accountId, input.appName, cronSchedules);