@torpor/build 1.1.0 → 1.3.0

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 (94) hide show
  1. package/dist/Jsonify-Dg6gChd3.d.mts.map +1 -1
  2. package/dist/MiddlewareFunction-BZilnEei.d.mts +50 -0
  3. package/dist/MiddlewareFunction-BZilnEei.d.mts.map +1 -0
  4. package/dist/{Router-COdQuVUi.d.mts → Router-dAu_5PlG.d.mts} +2 -2
  5. package/dist/{Router-COdQuVUi.d.mts.map → Router-dAu_5PlG.d.mts.map} +1 -1
  6. package/dist/{Server-Dr6yqVa5.mjs → Server-CSti9cvM.mjs} +2 -2
  7. package/dist/Server-CSti9cvM.mjs.map +1 -0
  8. package/dist/{Server-Cler2w5O.d.mts → Server-qn5Ws_d0.d.mts} +3 -23
  9. package/dist/Server-qn5Ws_d0.d.mts.map +1 -0
  10. package/dist/ServerEvent-Bh9cEpro.mjs +335 -0
  11. package/dist/ServerEvent-Bh9cEpro.mjs.map +1 -0
  12. package/dist/ServerHook-BTSGfLiJ.d.mts +21 -0
  13. package/dist/ServerHook-BTSGfLiJ.d.mts.map +1 -0
  14. package/dist/{ServerHook-BlLArueW.d.mts → ServerLoadEvent-DzbxxkF8.d.mts} +13 -21
  15. package/dist/ServerLoadEvent-DzbxxkF8.d.mts.map +1 -0
  16. package/dist/SessionHelper-MaodMaLU.d.mts +148 -0
  17. package/dist/SessionHelper-MaodMaLU.d.mts.map +1 -0
  18. package/dist/{Site-CTADVh7d.d.mts → Site-CWyj0R-l.d.mts} +89 -75
  19. package/dist/Site-CWyj0R-l.d.mts.map +1 -0
  20. package/dist/{_page-4cQKs_Dn.mjs → _page-CW6SLuT4.mjs} +2 -1
  21. package/dist/{_page-4cQKs_Dn.mjs.map → _page-CW6SLuT4.mjs.map} +1 -1
  22. package/dist/basePath-DQnbHc7o.mjs +99 -0
  23. package/dist/basePath-DQnbHc7o.mjs.map +1 -0
  24. package/dist/bin/index.js +1 -1
  25. package/dist/clientEntry.mjs +16 -5
  26. package/dist/clientEntry.mjs.map +1 -1
  27. package/dist/dev.d.mts +2 -2
  28. package/dist/dev.mjs +2 -2
  29. package/dist/discovery.d.mts +43 -0
  30. package/dist/discovery.d.mts.map +1 -0
  31. package/dist/discovery.mjs +72 -0
  32. package/dist/discovery.mjs.map +1 -0
  33. package/dist/env-C2SUAwAq.mjs +49 -0
  34. package/dist/env-C2SUAwAq.mjs.map +1 -0
  35. package/dist/env.d.mts +84 -0
  36. package/dist/env.d.mts.map +1 -0
  37. package/dist/env.mjs +2 -0
  38. package/dist/form.d.mts +14 -5
  39. package/dist/form.d.mts.map +1 -1
  40. package/dist/form.mjs +14 -4
  41. package/dist/form.mjs.map +1 -1
  42. package/dist/index.d.mts +5 -3
  43. package/dist/index.d.mts.map +1 -1
  44. package/dist/index.mjs +54 -0
  45. package/dist/index.mjs.map +1 -1
  46. package/dist/{load-C9RpS_4j.mjs → load-CydkUApM.mjs} +31 -20
  47. package/dist/load-CydkUApM.mjs.map +1 -0
  48. package/dist/nav.d.mts.map +1 -1
  49. package/dist/nav.mjs +4 -2
  50. package/dist/nav.mjs.map +1 -1
  51. package/dist/openapi.d.mts +1 -1
  52. package/dist/run.d.mts +1 -1
  53. package/dist/run.d.mts.map +1 -1
  54. package/dist/run.mjs +1 -1
  55. package/dist/{runOpenApi-yIzYasNN.mjs → runOpenApi-6iKszj_5.mjs} +230 -24
  56. package/dist/runOpenApi-6iKszj_5.mjs.map +1 -0
  57. package/dist/server/Server.d.mts +1 -1
  58. package/dist/server/Server.mjs +1 -1
  59. package/dist/server.d.mts +3 -2
  60. package/dist/server.d.mts.map +1 -1
  61. package/dist/server.mjs +2 -2
  62. package/dist/serverEntry-CnCVNNjp.mjs +13 -0
  63. package/dist/serverEntry-CnCVNNjp.mjs.map +1 -0
  64. package/dist/{serverEntry-DVv9YI3I.d.mts → serverEntry-fj0XfTs4.d.mts} +5 -4
  65. package/dist/serverEntry-fj0XfTs4.d.mts.map +1 -0
  66. package/dist/serverEntry.d.mts +2 -2
  67. package/dist/serverEntry.mjs +2 -2
  68. package/dist/{serverHandlers-DeL1qZJa.mjs → serverHandlers-B6EcNfBP.mjs} +100 -28
  69. package/dist/serverHandlers-B6EcNfBP.mjs.map +1 -0
  70. package/dist/sitemap-4imxJVrG.mjs +30 -0
  71. package/dist/sitemap-4imxJVrG.mjs.map +1 -0
  72. package/dist/sitemap-BjqN4d4W.d.mts +112 -0
  73. package/dist/sitemap-BjqN4d4W.d.mts.map +1 -0
  74. package/dist/state.d.mts +6 -0
  75. package/dist/state.d.mts.map +1 -1
  76. package/dist/state.mjs +1 -1
  77. package/dist/test.d.mts +2 -2
  78. package/dist/test.mjs +2 -2
  79. package/package.json +11 -3
  80. package/src/types/manifest.d.ts +3 -0
  81. package/dist/Server-Cler2w5O.d.mts.map +0 -1
  82. package/dist/Server-Dr6yqVa5.mjs.map +0 -1
  83. package/dist/ServerEvent-CTx3Q-hk.d.mts +0 -48
  84. package/dist/ServerEvent-CTx3Q-hk.d.mts.map +0 -1
  85. package/dist/ServerEvent-gmOvC_Yf.mjs +0 -100
  86. package/dist/ServerEvent-gmOvC_Yf.mjs.map +0 -1
  87. package/dist/ServerHook-BlLArueW.d.mts.map +0 -1
  88. package/dist/Site-CTADVh7d.d.mts.map +0 -1
  89. package/dist/load-C9RpS_4j.mjs.map +0 -1
  90. package/dist/runOpenApi-yIzYasNN.mjs.map +0 -1
  91. package/dist/serverEntry-DVv9YI3I.d.mts.map +0 -1
  92. package/dist/serverEntry-LyB5pl0E.mjs +0 -11
  93. package/dist/serverEntry-LyB5pl0E.mjs.map +0 -1
  94. package/dist/serverHandlers-DeL1qZJa.mjs.map +0 -1
@@ -0,0 +1,148 @@
1
+ import { SerializeOptions } from "cookie";
2
+ //#region src/server/CookieHelper.d.ts
3
+ declare class CookieHelper {
4
+ request: Request;
5
+ cookies: Map<string, string>;
6
+ constructor(request: Request);
7
+ get(name: string): string | undefined;
8
+ set(name: string, value: string, options?: SerializeOptions): void;
9
+ delete(name: string, options?: SerializeOptions): void;
10
+ }
11
+ //#endregion
12
+ //#region src/server/FlashHelper.d.ts
13
+ /**
14
+ * A one-time values store set in a form action and shown on the next page
15
+ * render, which survives the redirect. Available as `event.flash`; the
16
+ * framework reads it into `$page.flash` when a page is rendered, so
17
+ * components display it reactively:
18
+ *
19
+ * ```ts
20
+ * actions: {
21
+ * save: async (event) => {
22
+ * // ...save...
23
+ * event.flash.set("Project saved"); // { message: "Project saved" }
24
+ * event.flash.set({ error: "Import failed", retryable: true });
25
+ * return seeOther(`/projects/${id}`);
26
+ * },
27
+ * }
28
+ * ```
29
+ *
30
+ * ```torp
31
+ * @if ($page.flash?.message) {
32
+ * <p class="flash">{$page.flash.message}</p>
33
+ * }
34
+ * ```
35
+ *
36
+ * The values ride in a (session-lifetime) cookie that is deleted when it is
37
+ * read, so a page refresh shows them once. No signing: the only person who
38
+ * can fake a flash is the one seeing it, and values are rendered as text.
39
+ * They must be JSON-safe and small enough to fit in the cookie (~4KB). A
40
+ * common shape for a styled banner is `{ type, message }`, for use e.g.
41
+ * with the Notification component -- but the framework only requires the
42
+ * values to be an object. Without cookies the message is simply lost --
43
+ * the action itself still completes.
44
+ */
45
+ declare class FlashHelper {
46
+ #private;
47
+ constructor(cookies: CookieHelper);
48
+ /**
49
+ * Reads the flash values, if any, and clears them: the same response
50
+ * deletes the cookie, so the message shows exactly once. (When the
51
+ * message was set during this same request, e.g. a form action that
52
+ * re-renders instead of redirecting, the cookie is consumed without it
53
+ * ever reaching the browser.)
54
+ */
55
+ get(): Record<string, unknown> | undefined;
56
+ /**
57
+ * Sets the flash values. A string is stored as `{ message: value }`, an
58
+ * object as-is, e.g. `flash.set({ error: "Import failed" })`.
59
+ */
60
+ set(value: string | Record<string, unknown>): void;
61
+ /**
62
+ * Clears an unread flash message, e.g. so it isn't shown on a later
63
+ * re-render of the same page.
64
+ */
65
+ clear(): void;
66
+ }
67
+ //#endregion
68
+ //#region src/server/HeaderHelper.d.ts
69
+ declare class HeaderHelper {
70
+ request: Request;
71
+ headers: Map<string, string>;
72
+ constructor(request: Request);
73
+ get(name: string): string | undefined;
74
+ set(name: string, value: string): void;
75
+ delete(name: string): void;
76
+ }
77
+ //#endregion
78
+ //#region src/server/SessionHelper.d.ts
79
+ /**
80
+ * The data stored in the session. Must be JSON-safe, and small enough to
81
+ * fit in a cookie (4KB) along with the id, expiry and signature.
82
+ */
83
+ type SessionData = Record<string, unknown>;
84
+ /**
85
+ * A helper for reading and writing the signed session cookie, available as
86
+ * `event.session` in load functions, actions, +server endpoints and hooks.
87
+ *
88
+ * The value is signed with HMAC-SHA256 using `TORPOR_SESSION_SECRET` from
89
+ * the environment, so it can't be forged or tampered with -- but it is
90
+ * still client-side storage: everything rides in the cookie, it can't be
91
+ * revoked individually, and it is limited to ~4KB. When you need
92
+ * revocation, store `session.id` in your database, check it on each
93
+ * authed request (e.g. in a `_hook.server.ts`), and delete the row to
94
+ * revoke.
95
+ *
96
+ * ```ts
97
+ * // Login action
98
+ * const user = await checkPassword(event.form());
99
+ * await event.session.regenerate({ userId: user.id });
100
+ *
101
+ * // Root hook
102
+ * const session = await event.session.get();
103
+ * if (session) {
104
+ * event.appData.user = await getUser(session.userId);
105
+ * }
106
+ * ```
107
+ */
108
+ declare class SessionHelper {
109
+ #private;
110
+ constructor(cookies: CookieHelper);
111
+ /**
112
+ * The id embedded in the current session cookie, or undefined when
113
+ * there is no valid session. Stable across `set` calls, so it can be
114
+ * used as a database key; `regenerate` mints a new one.
115
+ */
116
+ get id(): string | undefined;
117
+ /**
118
+ * Reads and verifies the session, returning its data, or undefined when
119
+ * there is no cookie, the signature doesn't match, or the session has
120
+ * expired.
121
+ */
122
+ get(): Promise<SessionData | undefined>;
123
+ /**
124
+ * Writes the session, signing the data and setting the cookie. Keeps
125
+ * the current session id, minting one when there isn't a valid session
126
+ * yet. Call `regenerate` instead when the session's identity should
127
+ * change (e.g. at login, to prevent session fixation).
128
+ */
129
+ set(data: SessionData, options?: {
130
+ maxAge?: number;
131
+ }): Promise<void>;
132
+ /**
133
+ * Writes the session with a fresh id, replacing any existing one. Use
134
+ * it when a session's privilege level changes (login, permission
135
+ * upgrade), so a cookie that was set before the change can't be reused.
136
+ */
137
+ regenerate(data?: SessionData, options?: {
138
+ maxAge?: number;
139
+ }): Promise<void>;
140
+ /**
141
+ * Deletes the session cookie. Note that a server-side record (if you
142
+ * keep one) must be deleted or revoked separately.
143
+ */
144
+ destroy(): Promise<void>;
145
+ }
146
+ //#endregion
147
+ export { CookieHelper as i, HeaderHelper as n, FlashHelper as r, SessionHelper as t };
148
+ //# sourceMappingURL=SessionHelper-MaodMaLU.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionHelper-MaodMaLU.d.mts","names":[],"sources":["../src/server/CookieHelper.ts","../src/server/FlashHelper.ts","../src/server/HeaderHelper.ts","../src/server/SessionHelper.ts"],"mappings":";;cAEqB;EACpB,SAAS;EACT,SAAS;EAET,YAAY,SAAS;EAIrB,IAAI;EAaJ,IAAI,cAAc,eAAe,UAAU;EAU3C,OAAO,cAAc,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cCGX;;EAGpB,YAAY,SAAS;;;;;;;;EAWrB,OAAO;;;;;EAmBP,IAAI,gBAAgB;;;;;EASpB;;;;cC9EoB;EACpB,SAAS;EACT,SAAS;EAET,YAAY,SAAS;EAIrB,IAAI;EASJ,IAAI,cAAc;EAIlB,OAAO;;;;;;;;KCXH,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;cAqCE;;EAIpB,YAAY,SAAS;;;;;;MASjB;;;;;;EASJ,OAAa,QAAQ;;;;;;;EAerB,IAAU,MAAM,aAAa;IAAY;MAAoB;;;;;;EAW7D,WAAiB,OAAM,aAAkB;IAAY;MAAoB;;;;;EAUzE,WAAiB"}
@@ -1,9 +1,10 @@
1
- import { t as Server } from "./Server-Cler2w5O.mjs";
2
- import { a as RouteParamsOf, n as ParseRouteParams, t as ExactRouteParams } from "./ParseRouteParams-DBmqlmLh.mjs";
3
- import { i as ServerLoadEvent, n as FormDataRecord, r as QueryRecord, t as ServerHook } from "./ServerHook-BlLArueW.mjs";
4
- import { n as UntypedResponse, t as TypedResponse } from "./TypedResponse-6lznqN4U.mjs";
1
+ import { t as MiddlewareFunction } from "./MiddlewareFunction-BZilnEei.mjs";
2
+ import { t as Server } from "./Server-qn5Ws_d0.mjs";
3
+ import { a as RouteParamsOf } from "./ParseRouteParams-DBmqlmLh.mjs";
4
+ import { n as QueryRecord, r as ServerLoadEvent, t as FormDataRecord } from "./ServerLoadEvent-DzbxxkF8.mjs";
5
+ import { t as ServerHook } from "./ServerHook-BTSGfLiJ.mjs";
6
+ import { a as PageLoadReturn, i as PageEndPoint, t as SitemapOptions } from "./sitemap-BjqN4d4W.mjs";
5
7
  import { t as StandardSchemaV1 } from "./StandardSchema-D28L8_zZ.mjs";
6
- import { Component } from "@torpor/view";
7
8
  import { Plugin, UserConfig } from "vite";
8
9
  //#region src/types/Adapter.d.ts
9
10
  interface Adapter {
@@ -45,74 +46,6 @@ interface Adapter {
45
46
  */
46
47
  type SitePlugin = (site: Site) => void | Promise<void>;
47
48
  //#endregion
48
- //#region src/types/PageLoadEvent.d.ts
49
- /**
50
- * The event passed to client load functions. Annotate with a route path to
51
- * get typed params, e.g. `PageLoadEvent<"/posts/[id]">`, and with a data type
52
- * to type the data accumulated from the layouts above, e.g.
53
- * `PageLoadEvent<"/posts/[id]", PageData<typeof layoutServer>>`.
54
- */
55
- interface PageLoadEvent<Route extends string | undefined = undefined, Data = Record<string, any>> {
56
- /**
57
- * The URL for the server function.
58
- */
59
- url: URL;
60
- /**
61
- * Route params from the URL and route path.
62
- */
63
- params: RouteParamsOf<Route>;
64
- /**
65
- * Data loaded by the layouts above, accumulated top down. Loads may add to
66
- * it, and the merged result is passed into the page as `$props.data`.
67
- */
68
- data: Data;
69
- }
70
- //#endregion
71
- //#region src/types/PageLoadReturn.d.ts
72
- /**
73
- * What a load function may return. When `Data` is provided, a typed response
74
- * (e.g. `ok({ ... })`) must only contain keys the page expects; untyped
75
- * responses (redirects, errors, plain text) are always allowed.
76
- */
77
- type PageLoadReturn<Data> = TypedResponse<Partial<Data>> | UntypedResponse | undefined | void;
78
- //#endregion
79
- //#region src/types/PageEndPoint.d.ts
80
- /**
81
- * For +page. Annotate with a route path to get typed params, and with a data
82
- * shape to flag loads that return keys the page doesn't expect, e.g.
83
- * `PageEndPoint<"/posts/[id]", PageData<typeof server>>`.
84
- */
85
- interface PageEndPoint<Route extends string | undefined = undefined, Data = Record<string, any>> {
86
- /**
87
- * Builds the route path for the page in a type-safe manner. The params
88
- * object is checked for exact keys when `Route` is annotated.
89
- *
90
- * NOTE: The rest args conditional must stay INLINE — routing it through a
91
- * type alias defeats `Params` inference, and excess keys stop being checked
92
- */
93
- route?: <Params extends RouteParamsOf<Route>>(...args: Route extends string ? string extends Route ? [params?: Record<string, string>] : keyof ParseRouteParams<Route> extends never ? [] : [params: Params & ExactRouteParams<Params, ParseRouteParams<Route>>] : [params?: Record<string, string>]) => string;
94
- /**
95
- * Loads data for the page.
96
- */
97
- load?: (event: PageLoadEvent<Route>) => PageLoadReturn<Data> | Promise<PageLoadReturn<Data>>;
98
- /**
99
- * The component that is displayed for the page.
100
- */
101
- component?: Component;
102
- /**
103
- * The head element data for the page, which may include a <title> and <meta> elements.
104
- */
105
- head?: HeadElement[] | ((event: PageLoadEvent<Route>) => HeadElement[]);
106
- }
107
- type HeadElement = TitleElement | MetaElement;
108
- interface TitleElement {
109
- title: string;
110
- }
111
- interface MetaElement {
112
- name: string;
113
- content: string;
114
- }
115
- //#endregion
116
49
  //#region src/types/PageServerAction.d.ts
117
50
  type PageServerAction<Route extends string | undefined = undefined, FormBody = FormDataRecord, Params = RouteParamsOf<Route>> = (event: ServerLoadEvent<Route, unknown, FormBody, QueryRecord, Params>) => Response | undefined | void | Promise<Response | undefined | void>;
118
51
  //#endregion
@@ -198,6 +131,26 @@ interface PageServerEndPoint<Route extends string | undefined = undefined, Data
198
131
  * ```
199
132
  */
200
133
  schema?: Schemas | undefined;
134
+ /**
135
+ * Marks the page for prerendering. Set `true` for a static route, or
136
+ * `params` entries letting `tb build` render it once per entry for a
137
+ * dynamic route, e.g. `prerender: { params: [{ id: "1" }] }` under
138
+ * `/posts/[id]`. Params are typed by the route annotation (or the
139
+ * `params` schema), so the entries are checked.
140
+ *
141
+ * The load function runs at build time with a synthetic request, exactly
142
+ * as for a real GET.
143
+ */
144
+ prerender?: boolean | {
145
+ params: LoadParams<Schemas, Route>[];
146
+ };
147
+ /**
148
+ * Route middleware, run for this route's loads and actions after the
149
+ * global middleware (see `site.middleware`) and before folder hooks.
150
+ * `enter` may return a Response to short-circuit; `exit` hooks run in
151
+ * reverse. Middleware see the raw request url.
152
+ */
153
+ middleware?: MiddlewareFunction[];
201
154
  }
202
155
  //#endregion
203
156
  //#region src/types/ServerRequest.d.ts
@@ -303,6 +256,13 @@ type ServerEndPoint<Route extends string | undefined = undefined, Schemas extend
303
256
  * Performs a HEAD request. Its schema validates the query string.
304
257
  */
305
258
  head?: ServerRequest<Route, unknown, FormDataRecord, SchemaQuery<Schemas, "head">, ParamsOf<Schemas, Route>>;
259
+ /**
260
+ * Route middleware, run for this endpoint's handlers after the global
261
+ * middleware (see `site.middleware`). `enter` may return a Response to
262
+ * short-circuit; `exit` hooks run in reverse. Middleware see the raw
263
+ * request url.
264
+ */
265
+ middleware?: MiddlewareFunction[];
306
266
  };
307
267
  //#endregion
308
268
  //#region src/types/RouteType.d.ts
@@ -404,6 +364,60 @@ declare class Site {
404
364
  * instances, `Symbol()` ones are not.
405
365
  */
406
366
  pluginState: Map<PropertyKey, unknown>;
367
+ /**
368
+ * The path the site is mounted under, e.g. "/app". Incoming server
369
+ * requests that don't carry it are not found; everything the framework
370
+ * generates (HTML attributes, redirect locations, paths from `route()`)
371
+ * gets it added. Server code and markup stay base-free.
372
+ */
373
+ basePath: string;
374
+ /**
375
+ * Prerendering, applied to every page route that doesn't have (or
376
+ * inherit) a `prerender` flag of its own. `true` prerenders everything;
377
+ * a `Record<string, boolean>` maps route paths -- exact (`/about`) or
378
+ * prefix (`/blog/**` for a path and everything below it) -- to flags,
379
+ * with the most specific matching key winning. Route endpoint and
380
+ * layout flags always take precedence over this.
381
+ */
382
+ prerender?: boolean | Record<string, boolean>;
383
+ /**
384
+ * The site's absolute origin, e.g. "https://example.com". Required for
385
+ * a build-time sitemap (see `sitemap`), since there is no request to
386
+ * take the host from.
387
+ */
388
+ origin?: string;
389
+ /**
390
+ * Global middleware, run for every request before routing -- including
391
+ * requests that won't match a route (maintenance mode, legacy url
392
+ * redirects, etc). Each middleware's `enter` runs in order and may
393
+ * return a Response to short-circuit the request; `exit` hooks run in
394
+ * reverse after the handler. Middleware see the raw request url.
395
+ *
396
+ * Route middleware (scoped to a single route) is set on the route's
397
+ * server endpoint instead, e.g. `middleware: [...]` in
398
+ * `+page.server.ts` or `+server.ts`.
399
+ */
400
+ middleware: MiddlewareFunction[];
401
+ /**
402
+ * Writes a sitemap.xml listing the prerendered pages at build time
403
+ * (see `prerender`). Pass a custom path (e.g. "/blog/sitemap.xml") or
404
+ * `true` for "/sitemap.xml"; requires `origin` to be set.
405
+ *
406
+ * For a runtime sitemap -- one that reads the database and keeps up with
407
+ * content that isn't prerendered -- pass a config instead, e.g.
408
+ * `sitemap = { get: async () => ["/posts/a", "/posts/b"] }`, which serves
409
+ * the XML live at that path (customizable with `path`).
410
+ */
411
+ sitemap?: SitemapOptions | boolean | string;
412
+ /**
413
+ * A Standard Schema (zod, valibot, arktype, etc) used to validate (and
414
+ * parse) the server environment. When set, `env()` from
415
+ * `@torpor/build/env` checks the environment on first use per request and
416
+ * throws with the schema's issues -- so a missing or invalid key fails
417
+ * immediately instead of passing `undefined` along. The schema must
418
+ * validate synchronously.
419
+ */
420
+ env?: StandardSchemaV1<unknown, TorporEnv>;
407
421
  /**
408
422
  * Inline endpoints keyed by `"path:type"`. Populated by `addRoute` when
409
423
  * the user passes an inline endpoint object instead of a file path.
@@ -483,5 +497,5 @@ declare class Site {
483
497
  }, subFolder?: string): void;
484
498
  }
485
499
  //#endregion
486
- export { PageLoadEvent as _, ServerEndPoint as a, ActionBody as c, PageServerActionSchemas as d, PageServerEndPoint as f, PageLoadReturn as g, PageEndPoint as h, SchemaQuery as i, LoadParams as l, PageServerAction as m, ParamsOf as n, ServerEndPointSchemas as o, PageServerLoad as p, SchemaBody as r, ServerRequest as s, Site as t, LoadQuery as u, SitePlugin as v, Adapter as y };
487
- //# sourceMappingURL=Site-CTADVh7d.d.mts.map
500
+ export { ServerEndPoint as a, ActionBody as c, PageServerActionSchemas as d, PageServerEndPoint as f, Adapter as g, SitePlugin as h, SchemaQuery as i, LoadParams as l, PageServerAction as m, ParamsOf as n, ServerEndPointSchemas as o, PageServerLoad as p, SchemaBody as r, ServerRequest as s, Site as t, LoadQuery as u };
501
+ //# sourceMappingURL=Site-CWyj0R-l.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Site-CWyj0R-l.d.mts","names":[],"sources":["../src/types/Adapter.ts","../src/types/SitePlugin.ts","../src/types/PageServerAction.ts","../src/types/PageServerLoad.ts","../src/types/PageServerEndPoint.ts","../src/types/ServerRequest.ts","../src/types/ServerEndPoint.ts","../src/types/RouteType.ts","../src/types/Route.ts","../src/site/Site.ts"],"mappings":";;;;;;;;;UAIyB;EACxB,YAAY,MAAM,SAAS;EAC3B,aAAa,MAAM,SAAS;;;;;;;EAO5B,OAAO,MAAM,SAAS,SAAS;;;;;EAK/B,QAAQ,QAAQ,QAAQ,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;KCG1C,cAAc,MAAM,gBAAgB;;;KCjBpC,iBACJ,8CACA,WAAW,gBACX,SAAS,cAAc,WAEvB,OAAO,gBAAgB,gBAAgB,UAAU,aAAa,YAC1D,8BAA8B,QAAQ;;;KCLtC,eACJ,8CACA,OAAO,qBACP,YAAY,aACZ,SAAS,cAAc,WAEvB,OAAO,gBAAgB,gBAAgB,gBAAgB,WAAW,YAC9D,eAAe,QAAQ,QAAQ,eAAe;;;;;;;;;;;;;;;;;;;;;KCavC;EACX,SAAS;EACT,OAAO;GACW,iBAAA;;;;;;KAOP,WAAW,gBAAgB,yBAAyB,wBAC/D,qBACgB,oBACd,QAAQ,cAAc,mBACrB,iBAAiB,YAAY,QAAQ,SACrC,iBACD;;;;KAKS,UAAU,gBAAgB,2BAA2B;EAChE,MAAM;IAEJ,iBAAiB,YAAY,mBAC7B;;;;KAKS,WACX,gBAAgB,yBAChB,oCACG;EAAkB,QAAQ;IAC3B,iBAAiB,YAAY,qBAC7B,cAAc;;;;;;;;UASQ,mBACxB,8CACA,OAAO,qBACP,gBAAgB,0BAA0B;;;;;EAM1C,OAAO,eAAe,OAAO,MAAM,UAAU,UAAU,WAAW,SAAS;;;;EAI3E,aACE,iBAAiB,iBACjB,OACA,WAAW,SAAS,OACpB,WAAW,SAAS;;;;;;;;;;;;;;;;;;;;;EAuBtB,SAAS;;;;;;;;;;;EAWT;IAAwB,QAAQ,WAAW,SAAS;;;;;;;;EAOpD,aAAa;;;;KC3HT,cACJ,8CACA,gBACA,WAAW,gBACX,YAAY,aACZ,SAAS,cAAc,WAEvB,OAAO,gBAAgB,OAAO,MAAM,UAAU,WAAW,YACrD,8BAA8B,QAAQ;;;;;;;;;;;;;;;;;;;;KCW/B;EACX,SAAS;EACT,MAAM;EACN,OAAO;EACP,QAAQ;EACR,MAAM;EACN,MAAM;EACN,UAAU;EACV,OAAO;;;;;;KAOI,WAAW,gBAAgB,uBAAuB,0BAC7D,uBACgB,oBACd,QAAQ,gBAAgB,mBACvB,iBAAiB,YAAY,QAAQ;;;;;KAQ7B,YAAY,gBAAgB,uBAAuB,0BAC9D,uBACgB,oBACd,QAAQ,gBAAgB,mBACvB,iBAAiB,YAAY,QAAQ,WACrC,cACD;;;;;KAMS,SACX;EAAkB,SAAS;GAC3B,oCACG;EAAkB,QAAQ;IAC3B,iBAAiB,YAAY,qBAC7B,cAAc;;;;;;;KAQZ,eACJ,8CACA,gBAAgB,wBAAwB;;;;;;;;;;;;;;;;;;EAmBxC,SAAS;;;;EAIT,MAAM,cACL,gBAEA,gBACA,YAAY,iBACZ,SAAS,SAAS;;;;EAKnB,OAAO,cACN,OACA,WAAW,kBACX,gBACA,aACA,SAAS,SAAS;;;;EAKnB,QAAQ,cACP,OACA,WAAW,mBACX,gBACA,aACA,SAAS,SAAS;;;;EAKnB,MAAM,cACL,OACA,WAAW,iBACX,gBACA,aACA,SAAS,SAAS;;;;EAKnB,MAAM,cACL,OACA,WAAW,iBACX,gBACA,aACA,SAAS,SAAS;;;;EAKnB,UAAU,cACT,OACA,WAAW,qBACX,gBACA,aACA,SAAS,SAAS;;;;EAKnB,OAAO,cACN,gBAEA,gBACA,YAAY,kBACZ,SAAS,SAAS;;;;;;;EAQnB,aAAa;;;;cC7KD;cACA;cACA;cACA;cACA;cACA;cACA;cACA;KAED,mBACF,oBACA,2BACA,sBACA,sBACA,6BACA,oBACA,2BACA;;;;;;KCRE,iBAAiB,qBAAqB,iBAAiB,aAAa;;;;UAKvD;EACxB;;;;;EAKA;;;;;;EAMA,WAAW;EACX,MAAM;EACN;;;;;;;;cCOoB;;EACpB;EACA,QAAQ;;;;;;;EAOR,SAAS;;;;EAIT,aAAa;;;;;;;;;EASb,SAAS;;;;;;EAMT;;;;;;;EAOA,aAAa;;;;;EAKb;;;;;;;;;;;;;;;;EAgBA,aAAa,IAAI;;;;;;;EAOjB;;;;;;;;;EASA,sBAAsB;;;;;;EAMtB;;;;;;;;;;;;EAYA,YAAY;;;;;;;;;;;EAWZ,UAAU;;;;;;;;;EASV,MAAM,0BAA0B;;;;;EAKhC,iBAAiB,eAAe;EAEhC;;;;;;;;;EAYA,eAAqB,gBAAgB,qBAAqB;;;;;;;;EAiC1D,aACC,cACA,cACA,kHASA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+HD,SACC,cACA;IACC;IACA,sBAAsB;IACtB,kBAAkB;IAClB;IACA,wBAAwB;IACxB,sBAAsB;IACtB;KAED"}
@@ -5,9 +5,10 @@ const $page = globalThis[PAGE_SYMBOL] ??= $watch({
5
5
  status: 404,
6
6
  url: new URL("http://localhost"),
7
7
  form: void 0,
8
+ flash: void 0,
8
9
  error: { message: "" }
9
10
  }, { shallow: true });
10
11
  //#endregion
11
12
  export { $page as t };
12
13
 
13
- //# sourceMappingURL=_page-4cQKs_Dn.mjs.map
14
+ //# sourceMappingURL=_page-CW6SLuT4.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"_page-4cQKs_Dn.mjs","names":[],"sources":["../src/state/$page.ts"],"sourcesContent":["import { $watch } from \"@torpor/view\";\nimport type PageState from \"../types/PageState\";\n\nconst PAGE_SYMBOL: unique symbol = Symbol.for(\"torp.Page\");\n\nconst $page: PageState =\n\t// @ts-ignore\n\t(globalThis[PAGE_SYMBOL] ??=\n\t\t// The server request path uses `state/$serverPage`, which stores the\n\t\t// same state (same `globalThis` slot) without a reactive proxy\n\t\t$watch(\n\t\t\t{\n\t\t\t\tstatus: 404,\n\t\t\t\turl: new URL(\"http://localhost\"),\n\t\t\t\tform: undefined,\n\t\t\t\terror: {\n\t\t\t\t\tmessage: \"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tshallow: true,\n\t\t\t},\n\t\t));\n\nexport default $page;\n"],"mappings":";;AAGA,MAAM,cAA6B,OAAO,IAAI,WAAW;AAEzD,MAAM,QAEJ,WAAW,iBAGX,OACC;CACC,QAAQ;CACR,KAAK,IAAI,IAAI,kBAAkB;CAC/B,MAAM,KAAA;CACN,OAAO,EACN,SAAS,GACV;AACD,GACA,EACC,SAAS,KACV,CACD"}
1
+ {"version":3,"file":"_page-CW6SLuT4.mjs","names":[],"sources":["../src/state/$page.ts"],"sourcesContent":["import { $watch } from \"@torpor/view\";\nimport type PageState from \"../types/PageState\";\n\nconst PAGE_SYMBOL: unique symbol = Symbol.for(\"torp.Page\");\n\nconst $page: PageState =\n\t// @ts-ignore\n\t(globalThis[PAGE_SYMBOL] ??=\n\t\t// The server request path uses `state/$serverPage`, which stores the\n\t\t// same state (same `globalThis` slot) without a reactive proxy\n\t\t$watch(\n\t\t\t{\n\t\t\t\tstatus: 404,\n\t\t\t\turl: new URL(\"http://localhost\"),\n\t\t\t\tform: undefined,\n\t\t\t\tflash: undefined,\n\t\t\t\terror: {\n\t\t\t\t\tmessage: \"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tshallow: true,\n\t\t\t},\n\t\t));\n\nexport default $page;\n"],"mappings":";;AAGA,MAAM,cAA6B,OAAO,IAAI,WAAW;AAEzD,MAAM,QAEJ,WAAW,iBAGX,OACC;CACC,QAAQ;CACR,KAAK,IAAI,IAAI,kBAAkB;CAC/B,MAAM,KAAA;CACN,OAAO,KAAA;CACP,OAAO,EACN,SAAS,GACV;AACD,GACA,EACC,SAAS,KACV,CACD"}
@@ -0,0 +1,99 @@
1
+ //#region src/site/basePath.ts
2
+ /**
3
+ * The site's base path, e.g. "/app" when the site is mounted under a
4
+ * subpath. Inherited from `site.basePath` through the manifest at startup
5
+ * (see serverEntry and clientEntry); default "" when the site is served
6
+ * from the root.
7
+ *
8
+ * Server code and user markup are written base-free: the server strips the
9
+ * base from incoming URLs before routing (requests that don't carry it get
10
+ * a 404), and adds it back to generated HTML attributes, redirect
11
+ * locations and paths built with `route()`. The client router does the
12
+ * mirror image: strips for matching, adds for data fetches.
13
+ */
14
+ let basePath = "";
15
+ /**
16
+ * Validates and sets the site's base path: must be "", start with "/" and
17
+ * not end with "/" (a trailing slash is stripped, "/" is normalized to "")
18
+ */
19
+ function setBasePath(value) {
20
+ basePath = normalizeBasePath(value);
21
+ }
22
+ function getBasePath() {
23
+ return basePath;
24
+ }
25
+ /**
26
+ * Normalizes a base path value, throwing a descriptive error when it
27
+ * can't be one. Trailing slashes are stripped and "/" is normalized to "".
28
+ */
29
+ function normalizeBasePath(value) {
30
+ const trimmed = (value ?? "").trim();
31
+ if (trimmed === "" || trimmed === "/") return "";
32
+ if (!trimmed.startsWith("/") || /\s/.test(trimmed)) throw new Error(`The base path must be an absolute path without spaces (e.g. "/app"), got "${value}"`);
33
+ return trimmed.replace(/\/+$/, "");
34
+ }
35
+ /**
36
+ * Normalizes a site origin value, throwing a descriptive error when it
37
+ * isn't an absolute http(s) origin without a path or spaces.
38
+ */
39
+ function normalizeOrigin(value) {
40
+ const trimmed = (value ?? "").trim();
41
+ if (trimmed === "") throw new Error(`The site origin is empty (e.g. "https://example.com")`);
42
+ let url;
43
+ try {
44
+ url = new URL(trimmed);
45
+ } catch {
46
+ throw new Error(`The site origin must be an absolute url (e.g. "https://example.com"), got "${value}"`);
47
+ }
48
+ if (url.protocol !== "https:" && url.protocol !== "http:" || url.pathname !== "/" || /\s/.test(trimmed)) throw new Error(`The site origin must be an http(s) url without spaces or a path (e.g. "https://example.com"), got "${value}"`);
49
+ return url.origin;
50
+ }
51
+ /**
52
+ * Strips the base from a URL's pathname, copying the URL so it can be used
53
+ * for routing without disturbing the request. Returns undefined when there
54
+ * is a base and the URL doesn't carry it -- the caller should then return
55
+ * a not-found response, since the site only serves requests under its
56
+ * mount point.
57
+ */
58
+ function stripBaseFromUrl(url, base) {
59
+ const stripped = new URL(url);
60
+ if (!base) return stripped;
61
+ if (stripped.pathname === base) {
62
+ stripped.pathname = "/";
63
+ return stripped;
64
+ }
65
+ if (stripped.pathname.startsWith(base + "/")) {
66
+ stripped.pathname = stripped.pathname.slice(base.length) || "/";
67
+ return stripped;
68
+ }
69
+ }
70
+ /**
71
+ * Adds the base to an absolute path, idempotent for paths that already
72
+ * carry it. Relative paths ("other/page"), protocol-relative ("//...") and
73
+ * full URLs are left alone.
74
+ */
75
+ function addBaseToPath(path, base) {
76
+ if (!base || !path.startsWith("/") || path.startsWith("//")) return path;
77
+ if (path === "/") return base + "/";
78
+ if (path === base || path.startsWith(base + "/")) return path;
79
+ return base + path;
80
+ }
81
+ /**
82
+ * Rewrites absolute-path `href`, `action` and `src` attribute values in
83
+ * rendered HTML, so that markup can be written base-free. Values that
84
+ * aren't absolute paths (relative, "//...", "http://...", "mailto:...")
85
+ * are left alone, as are ones that already carry the base.
86
+ */
87
+ function rewriteBaseInHtml(html, base) {
88
+ if (!base) return html;
89
+ return html.replace(/\s(href|action|src)\s*=\s*("([^"]*)"|'([^']*)')/g, (match, name, _quoted, double, single) => {
90
+ const value = double ?? single ?? "";
91
+ const rewritten = addBaseToPath(value, base);
92
+ if (rewritten === value) return match;
93
+ return ` ${name}="${rewritten}"`;
94
+ });
95
+ }
96
+ //#endregion
97
+ export { rewriteBaseInHtml as a, normalizeOrigin as i, getBasePath as n, setBasePath as o, normalizeBasePath as r, stripBaseFromUrl as s, addBaseToPath as t };
98
+
99
+ //# sourceMappingURL=basePath-DQnbHc7o.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"basePath-DQnbHc7o.mjs","names":[],"sources":["../src/site/basePath.ts"],"sourcesContent":["/**\n * The site's base path, e.g. \"/app\" when the site is mounted under a\n * subpath. Inherited from `site.basePath` through the manifest at startup\n * (see serverEntry and clientEntry); default \"\" when the site is served\n * from the root.\n *\n * Server code and user markup are written base-free: the server strips the\n * base from incoming URLs before routing (requests that don't carry it get\n * a 404), and adds it back to generated HTML attributes, redirect\n * locations and paths built with `route()`. The client router does the\n * mirror image: strips for matching, adds for data fetches.\n */\nlet basePath = \"\";\n\n/**\n * Validates and sets the site's base path: must be \"\", start with \"/\" and\n * not end with \"/\" (a trailing slash is stripped, \"/\" is normalized to \"\")\n */\nexport function setBasePath(value: string | undefined): void {\n\tbasePath = normalizeBasePath(value);\n}\n\nexport function getBasePath(): string {\n\treturn basePath;\n}\n\n/**\n * Normalizes a base path value, throwing a descriptive error when it\n * can't be one. Trailing slashes are stripped and \"/\" is normalized to \"\".\n */\nexport function normalizeBasePath(value: string | undefined): string {\n\tconst trimmed = (value ?? \"\").trim();\n\tif (trimmed === \"\" || trimmed === \"/\") return \"\";\n\tif (!trimmed.startsWith(\"/\") || /\\s/.test(trimmed)) {\n\t\tthrow new Error(\n\t\t\t`The base path must be an absolute path without spaces (e.g. \"/app\"), ` + `got \"${value}\"`,\n\t\t);\n\t}\n\treturn trimmed.replace(/\\/+$/, \"\");\n}\n\n/**\n * Normalizes a site origin value, throwing a descriptive error when it\n * isn't an absolute http(s) origin without a path or spaces.\n */\nexport function normalizeOrigin(value: string | undefined): string {\n\tconst trimmed = (value ?? \"\").trim();\n\tif (trimmed === \"\") {\n\t\tthrow new Error(`The site origin is empty (e.g. \"https://example.com\")`);\n\t}\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(trimmed);\n\t} catch {\n\t\tthrow new Error(\n\t\t\t`The site origin must be an absolute url (e.g. \"https://example.com\"), got \"${value}\"`,\n\t\t);\n\t}\n\tif (\n\t\t(url.protocol !== \"https:\" && url.protocol !== \"http:\") ||\n\t\turl.pathname !== \"/\" ||\n\t\t/\\s/.test(trimmed)\n\t) {\n\t\tthrow new Error(\n\t\t\t`The site origin must be an http(s) url without spaces or a path ` +\n\t\t\t\t`(e.g. \"https://example.com\"), got \"${value}\"`,\n\t\t);\n\t}\n\t// A trailing slash is allowed\n\treturn url.origin;\n}\n\n/**\n * Strips the base from a URL's pathname, copying the URL so it can be used\n * for routing without disturbing the request. Returns undefined when there\n * is a base and the URL doesn't carry it -- the caller should then return\n * a not-found response, since the site only serves requests under its\n * mount point.\n */\nexport function stripBaseFromUrl(url: URL, base: string): URL | undefined {\n\tconst stripped = new URL(url);\n\tif (!base) return stripped;\n\tif (stripped.pathname === base) {\n\t\tstripped.pathname = \"/\";\n\t\treturn stripped;\n\t}\n\tif (stripped.pathname.startsWith(base + \"/\")) {\n\t\tstripped.pathname = stripped.pathname.slice(base.length) || \"/\";\n\t\treturn stripped;\n\t}\n\treturn undefined;\n}\n\n/**\n * Adds the base to an absolute path, idempotent for paths that already\n * carry it. Relative paths (\"other/page\"), protocol-relative (\"//...\") and\n * full URLs are left alone.\n */\nexport function addBaseToPath(path: string, base: string): string {\n\tif (!base || !path.startsWith(\"/\") || path.startsWith(\"//\")) return path;\n\tif (path === \"/\") return base + \"/\";\n\tif (path === base || path.startsWith(base + \"/\")) return path;\n\treturn base + path;\n}\n\n/**\n * Rewrites absolute-path `href`, `action` and `src` attribute values in\n * rendered HTML, so that markup can be written base-free. Values that\n * aren't absolute paths (relative, \"//...\", \"http://...\", \"mailto:...\")\n * are left alone, as are ones that already carry the base.\n */\nexport function rewriteBaseInHtml(html: string, base: string): string {\n\tif (!base) return html;\n\treturn html.replace(\n\t\t/\\s(href|action|src)\\s*=\\s*(\"([^\"]*)\"|'([^']*)')/g,\n\t\t(match, name: string, _quoted: string, double: string, single: string) => {\n\t\t\tconst value = double ?? single ?? \"\";\n\t\t\tconst rewritten = addBaseToPath(value, base);\n\t\t\tif (rewritten === value) return match;\n\t\t\treturn ` ${name}=\"${rewritten}\"`;\n\t\t},\n\t);\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,IAAI,WAAW;;;;;AAMf,SAAgB,YAAY,OAAiC;CAC5D,WAAW,kBAAkB,KAAK;AACnC;AAEA,SAAgB,cAAsB;CACrC,OAAO;AACR;;;;;AAMA,SAAgB,kBAAkB,OAAmC;CACpE,MAAM,WAAW,SAAS,GAAA,CAAI,KAAK;CACnC,IAAI,YAAY,MAAM,YAAY,KAAK,OAAO;CAC9C,IAAI,CAAC,QAAQ,WAAW,GAAG,KAAK,KAAK,KAAK,OAAO,GAChD,MAAM,IAAI,MACT,6EAAkF,MAAM,EACzF;CAED,OAAO,QAAQ,QAAQ,QAAQ,EAAE;AAClC;;;;;AAMA,SAAgB,gBAAgB,OAAmC;CAClE,MAAM,WAAW,SAAS,GAAA,CAAI,KAAK;CACnC,IAAI,YAAY,IACf,MAAM,IAAI,MAAM,uDAAuD;CAExE,IAAI;CACJ,IAAI;EACH,MAAM,IAAI,IAAI,OAAO;CACtB,QAAQ;EACP,MAAM,IAAI,MACT,8EAA8E,MAAM,EACrF;CACD;CACA,IACE,IAAI,aAAa,YAAY,IAAI,aAAa,WAC/C,IAAI,aAAa,OACjB,KAAK,KAAK,OAAO,GAEjB,MAAM,IAAI,MACT,sGACuC,MAAM,EAC9C;CAGD,OAAO,IAAI;AACZ;;;;;;;;AASA,SAAgB,iBAAiB,KAAU,MAA+B;CACzE,MAAM,WAAW,IAAI,IAAI,GAAG;CAC5B,IAAI,CAAC,MAAM,OAAO;CAClB,IAAI,SAAS,aAAa,MAAM;EAC/B,SAAS,WAAW;EACpB,OAAO;CACR;CACA,IAAI,SAAS,SAAS,WAAW,OAAO,GAAG,GAAG;EAC7C,SAAS,WAAW,SAAS,SAAS,MAAM,KAAK,MAAM,KAAK;EAC5D,OAAO;CACR;AAED;;;;;;AAOA,SAAgB,cAAc,MAAc,MAAsB;CACjE,IAAI,CAAC,QAAQ,CAAC,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,IAAI,GAAG,OAAO;CACpE,IAAI,SAAS,KAAK,OAAO,OAAO;CAChC,IAAI,SAAS,QAAQ,KAAK,WAAW,OAAO,GAAG,GAAG,OAAO;CACzD,OAAO,OAAO;AACf;;;;;;;AAQA,SAAgB,kBAAkB,MAAc,MAAsB;CACrE,IAAI,CAAC,MAAM,OAAO;CAClB,OAAO,KAAK,QACX,qDACC,OAAO,MAAc,SAAiB,QAAgB,WAAmB;EACzE,MAAM,QAAQ,UAAU,UAAU;EAClC,MAAM,YAAY,cAAc,OAAO,IAAI;EAC3C,IAAI,cAAc,OAAO,OAAO;EAChC,OAAO,IAAI,KAAK,IAAI,UAAU;CAC/B,CACD;AACD"}
package/dist/bin/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #! /usr/bin/env node
2
- import { n as loadSite, r as run, t as runOpenApi } from "../runOpenApi-yIzYasNN.mjs";
2
+ import { n as loadSite, r as run, t as runOpenApi } from "../runOpenApi-6iKszj_5.mjs";
3
3
  //#region src/bin/index.ts
4
4
  const workingDir = process.cwd();
5
5
  if (process.argv.includes("--openapi")) {
@@ -1,8 +1,10 @@
1
1
  import { t as Router } from "./Router-C4ADUfhv.mjs";
2
- import { t as $page } from "./_page-4cQKs_Dn.mjs";
3
- import { a as client, n as navigate, r as loadData } from "./load-C9RpS_4j.mjs";
2
+ import { o as setBasePath } from "./basePath-DQnbHc7o.mjs";
3
+ import { t as $page } from "./_page-CW6SLuT4.mjs";
4
+ import { a as client, n as navigate, r as loadData } from "./load-CydkUApM.mjs";
4
5
  import manifest from "@torpor/build/manifest";
5
6
  //#region src/site/clientEntry.ts
7
+ setBasePath(manifest.base);
6
8
  const router = new Router();
7
9
  router.addPages(manifest.routes);
8
10
  client.router = router;
@@ -13,8 +15,10 @@ window.addEventListener("click", async (e) => {
13
15
  e.preventDefault();
14
16
  const href = link.href;
15
17
  const url = new URL(href);
16
- if (await navigate(url)) window.history.pushState(null, "", url);
17
- else window.location.href = href;
18
+ if (await navigate(url)) {
19
+ window.history.replaceState({ scroll: window.scrollY }, "", document.location.href);
20
+ window.history.pushState({ scroll: 0 }, "", url);
21
+ } else window.location.href = href;
18
22
  }
19
23
  });
20
24
  window.addEventListener("mouseover", maybePrefetch);
@@ -47,7 +51,14 @@ function ignoreAnchorMouseEvent(link, e) {
47
51
  return e.defaultPrevented || e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || link.target && link.target !== "_self" || link.rel === "external" || link.hasAttribute("download") || link.isContentEditable;
48
52
  }
49
53
  window.addEventListener("popstate", async () => {
50
- await navigateToLocation(document.location);
54
+ if (!await navigateToLocation(document.location)) {
55
+ window.location.reload();
56
+ return;
57
+ }
58
+ requestAnimationFrame(() => {
59
+ const state = window.history.state;
60
+ window.scrollTo(0, state?.scroll ?? 0);
61
+ });
51
62
  });
52
63
  await navigateToLocation(document.location, true);
53
64
  async function navigateToLocation(location, firstTime = false) {
@@ -1 +1 @@
1
- {"version":3,"file":"clientEntry.mjs","names":[],"sources":["../src/site/clientEntry.ts"],"sourcesContent":["import manifest from \"@torpor/build/manifest\";\nimport loadData from \"../nav/loadData.ts\";\nimport navigate from \"../nav/navigate.ts\";\nimport $page from \"../state/$page.ts\";\nimport client from \"../state/client\";\nimport type LayoutPath from \"../types/LayoutPath\";\nimport type PageEndPoint from \"../types/PageEndPoint\";\nimport type PageServerEndPoint from \"../types/PageServerEndPoint\";\nimport Router from \"./Router.ts\";\n\n// Build the router from the Site object created by the user\nconst router = new Router();\nrouter.addPages(manifest.routes);\n\n// Put it in client state so that it can be accessed from navigate. We can't\n// just import it to navigate because that gets built by tsdown, without Vite\n// and the manifest\nclient.router = router;\n\n// Intercept clicks on links\nwindow.addEventListener(\"click\", async (e) => {\n\tconst link = getLink(e.target);\n\tif (link) {\n\t\tif (ignoreAnchorMouseEvent(link, e)) {\n\t\t\treturn;\n\t\t}\n\n\t\te.preventDefault();\n\n\t\tconst href = link.href;\n\t\tconst url = new URL(href);\n\n\t\tif (await navigate(url)) {\n\t\t\twindow.history.pushState(null, \"\", url);\n\t\t} else {\n\t\t\twindow.location.href = href;\n\t\t}\n\t}\n});\n\n// Intercept mouseover/focus/touchstart on links for prefetching\n// TODO: settings e.g. <a href=\"\" data-tb-prefetch=\"eager\"/\"visible\"/\"hover\"/\"touch\">\nwindow.addEventListener(\"mouseover\", maybePrefetch);\nwindow.addEventListener(\"touchstart\", maybePrefetch);\n\nasync function maybePrefetch(e: MouseEvent | TouchEvent) {\n\tconst link = getLink(e.target);\n\tif (link) {\n\t\tif (ignoreAnchorMouseEvent(link, e)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst href = link.href;\n\t\tconst url = new URL(href);\n\n\t\tconst path = url.pathname;\n\t\tconst query = url.searchParams;\n\t\tconst route = router.match(path, query);\n\t\tif (route) {\n\t\t\tconst handler = route.handler;\n\t\t\tconst params = route.params || {};\n\n\t\t\t// There should be a client endpoint\n\t\t\tconst clientEndPoint: PageEndPoint | undefined = (await handler.endPoint()).default;\n\n\t\t\t// There may be a server endpoint\n\t\t\tconst serverEndPoint: PageServerEndPoint | undefined =\n\t\t\t\thandler.serverEndPoint && (await handler.serverEndPoint())?.default;\n\n\t\t\t// Just dummy this up\n\t\t\tlet newStack: LayoutPath[] = [];\n\n\t\t\tawait loadData(handler, params, path, query, newStack, clientEndPoint, serverEndPoint, true);\n\t\t}\n\t}\n}\n\nfunction getLink(target: EventTarget | null): HTMLAnchorElement | undefined {\n\t// The target may be a child element of the link (e.g. an icon inside it),\n\t// so walk up to the nearest actual link. Fragment-only links (href=\"#...\")\n\t// are left to the browser's native jump-to-anchor behavior\n\tif (!(target instanceof Element)) return;\n\tconst link = target.closest(\"a[href]\");\n\tif (link?.getAttribute(\"href\")?.startsWith(\"#\")) return;\n\treturn (link as HTMLAnchorElement) ?? undefined;\n}\n\nfunction ignoreAnchorMouseEvent(link: HTMLAnchorElement, e: MouseEvent | TouchEvent) {\n\t// Ignore the event if it was already handled (e.g. a user handler calling\n\t// preventDefault), a modifier key is pressed, the target is not \"_self\"\n\t// (e.g. \"_blank\"), rel=\"external\", it's a download link, or it's in a\n\t// contenteditable region\n\treturn (\n\t\te.defaultPrevented ||\n\t\te.altKey ||\n\t\te.ctrlKey ||\n\t\te.metaKey ||\n\t\te.shiftKey ||\n\t\t(link.target && link.target !== \"_self\") ||\n\t\tlink.rel === \"external\" ||\n\t\tlink.hasAttribute(\"download\") ||\n\t\tlink.isContentEditable\n\t);\n}\n\n// Listen for changes to the URL that occur when the user navigates using the\n// back or forward buttons\nwindow.addEventListener(\"popstate\", async () => {\n\tawait navigateToLocation(document.location);\n});\n\n// Do the initial navigation and hydration\nawait navigateToLocation(document.location, true);\n\nasync function navigateToLocation(location: Location, firstTime = false) {\n\t// Reset $page on submit and link navigation\n\t// TODO: Should we put data in $page.data??\n\t$page.form = undefined;\n\n\treturn await navigate(new URL(location.toString()), firstTime);\n}\n"],"mappings":";;;;;AAWA,MAAM,SAAS,IAAI,OAAO;AAC1B,OAAO,SAAS,SAAS,MAAM;AAK/B,OAAO,SAAS;AAGhB,OAAO,iBAAiB,SAAS,OAAO,MAAM;CAC7C,MAAM,OAAO,QAAQ,EAAE,MAAM;CAC7B,IAAI,MAAM;EACT,IAAI,uBAAuB,MAAM,CAAC,GACjC;EAGD,EAAE,eAAe;EAEjB,MAAM,OAAO,KAAK;EAClB,MAAM,MAAM,IAAI,IAAI,IAAI;EAExB,IAAI,MAAM,SAAS,GAAG,GACrB,OAAO,QAAQ,UAAU,MAAM,IAAI,GAAG;OAEtC,OAAO,SAAS,OAAO;CAEzB;AACD,CAAC;AAID,OAAO,iBAAiB,aAAa,aAAa;AAClD,OAAO,iBAAiB,cAAc,aAAa;AAEnD,eAAe,cAAc,GAA4B;CACxD,MAAM,OAAO,QAAQ,EAAE,MAAM;CAC7B,IAAI,MAAM;EACT,IAAI,uBAAuB,MAAM,CAAC,GACjC;EAGD,MAAM,OAAO,KAAK;EAClB,MAAM,MAAM,IAAI,IAAI,IAAI;EAExB,MAAM,OAAO,IAAI;EACjB,MAAM,QAAQ,IAAI;EAClB,MAAM,QAAQ,OAAO,MAAM,MAAM,KAAK;EACtC,IAAI,OAAO;GACV,MAAM,UAAU,MAAM;GACtB,MAAM,SAAS,MAAM,UAAU,CAAC;GAGhC,MAAM,kBAA4C,MAAM,QAAQ,SAAS,EAAA,CAAG;GAG5E,MAAM,iBACL,QAAQ,mBAAmB,MAAM,QAAQ,eAAe,EAAA,EAAI;GAK7D,MAAM,SAAS,SAAS,QAAQ,MAAM,OAAO,CAAO,GAAG,gBAAgB,gBAAgB,IAAI;EAC5F;CACD;AACD;AAEA,SAAS,QAAQ,QAA2D;CAI3E,IAAI,EAAE,kBAAkB,UAAU;CAClC,MAAM,OAAO,OAAO,QAAQ,SAAS;CACrC,IAAI,MAAM,aAAa,MAAM,CAAC,EAAE,WAAW,GAAG,GAAG;CACjD,OAAQ,QAA8B,KAAA;AACvC;AAEA,SAAS,uBAAuB,MAAyB,GAA4B;CAKpF,OACC,EAAE,oBACF,EAAE,UACF,EAAE,WACF,EAAE,WACF,EAAE,YACD,KAAK,UAAU,KAAK,WAAW,WAChC,KAAK,QAAQ,cACb,KAAK,aAAa,UAAU,KAC5B,KAAK;AAEP;AAIA,OAAO,iBAAiB,YAAY,YAAY;CAC/C,MAAM,mBAAmB,SAAS,QAAQ;AAC3C,CAAC;AAGD,MAAM,mBAAmB,SAAS,UAAU,IAAI;AAEhD,eAAe,mBAAmB,UAAoB,YAAY,OAAO;CAGxE,MAAM,OAAO,KAAA;CAEb,OAAO,MAAM,SAAS,IAAI,IAAI,SAAS,SAAS,CAAC,GAAG,SAAS;AAC9D"}
1
+ {"version":3,"file":"clientEntry.mjs","names":[],"sources":["../src/site/clientEntry.ts"],"sourcesContent":["import manifest from \"@torpor/build/manifest\";\nimport loadData from \"../nav/loadData.ts\";\nimport navigate from \"../nav/navigate.ts\";\nimport $page from \"../state/$page.ts\";\nimport client from \"../state/client\";\nimport type LayoutPath from \"../types/LayoutPath\";\nimport type PageEndPoint from \"../types/PageEndPoint\";\nimport type PageServerEndPoint from \"../types/PageServerEndPoint\";\nimport Router from \"./Router.ts\";\nimport { setBasePath } from \"./basePath.ts\";\n\nsetBasePath(manifest.base);\n\n// Build the router from the Site object created by the user\nconst router = new Router();\nrouter.addPages(manifest.routes);\n\n// Put it in client state so that it can be accessed from navigate. We can't\n// just import it to navigate because that gets built by tsdown, without Vite\n// and the manifest\nclient.router = router;\n\n// Intercept clicks on links\nwindow.addEventListener(\"click\", async (e) => {\n\tconst link = getLink(e.target);\n\tif (link) {\n\t\tif (ignoreAnchorMouseEvent(link, e)) {\n\t\t\treturn;\n\t\t}\n\n\t\te.preventDefault();\n\n\t\tconst href = link.href;\n\t\tconst url = new URL(href);\n\n\t\tif (await navigate(url)) {\n\t\t\t// Save the scroll position of the outgoing page on the current\n\t\t\t// history entry, so that going (or coming) back can restore it\n\t\t\twindow.history.replaceState({ scroll: window.scrollY }, \"\", document.location.href);\n\t\t\twindow.history.pushState({ scroll: 0 }, \"\", url);\n\t\t} else {\n\t\t\twindow.location.href = href;\n\t\t}\n\t}\n});\n\n// Intercept mouseover/focus/touchstart on links for prefetching\n// TODO: settings e.g. <a href=\"\" data-tb-prefetch=\"eager\"/\"visible\"/\"hover\"/\"touch\">\nwindow.addEventListener(\"mouseover\", maybePrefetch);\nwindow.addEventListener(\"touchstart\", maybePrefetch);\n\nasync function maybePrefetch(e: MouseEvent | TouchEvent) {\n\tconst link = getLink(e.target);\n\tif (link) {\n\t\tif (ignoreAnchorMouseEvent(link, e)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst href = link.href;\n\t\tconst url = new URL(href);\n\n\t\tconst path = url.pathname;\n\t\tconst query = url.searchParams;\n\t\tconst route = router.match(path, query);\n\t\tif (route) {\n\t\t\tconst handler = route.handler;\n\t\t\tconst params = route.params || {};\n\n\t\t\t// There should be a client endpoint\n\t\t\tconst clientEndPoint: PageEndPoint | undefined = (await handler.endPoint()).default;\n\n\t\t\t// There may be a server endpoint\n\t\t\tconst serverEndPoint: PageServerEndPoint | undefined =\n\t\t\t\thandler.serverEndPoint && (await handler.serverEndPoint())?.default;\n\n\t\t\t// Just dummy this up\n\t\t\tlet newStack: LayoutPath[] = [];\n\n\t\t\tawait loadData(handler, params, path, query, newStack, clientEndPoint, serverEndPoint, true);\n\t\t}\n\t}\n}\n\nfunction getLink(target: EventTarget | null): HTMLAnchorElement | undefined {\n\t// The target may be a child element of the link (e.g. an icon inside it),\n\t// so walk up to the nearest actual link. Fragment-only links (href=\"#...\")\n\t// are left to the browser's native jump-to-anchor behavior\n\tif (!(target instanceof Element)) return;\n\tconst link = target.closest(\"a[href]\");\n\tif (link?.getAttribute(\"href\")?.startsWith(\"#\")) return;\n\treturn (link as HTMLAnchorElement) ?? undefined;\n}\n\nfunction ignoreAnchorMouseEvent(link: HTMLAnchorElement, e: MouseEvent | TouchEvent) {\n\t// Ignore the event if it was already handled (e.g. a user handler calling\n\t// preventDefault), a modifier key is pressed, the target is not \"_self\"\n\t// (e.g. \"_blank\"), rel=\"external\", it's a download link, or it's in a\n\t// contenteditable region\n\treturn (\n\t\te.defaultPrevented ||\n\t\te.altKey ||\n\t\te.ctrlKey ||\n\t\te.metaKey ||\n\t\te.shiftKey ||\n\t\t(link.target && link.target !== \"_self\") ||\n\t\tlink.rel === \"external\" ||\n\t\tlink.hasAttribute(\"download\") ||\n\t\tlink.isContentEditable\n\t);\n}\n\n// Listen for changes to the URL that occur when the user navigates using the\n// back or forward buttons\nwindow.addEventListener(\"popstate\", async () => {\n\tconst navigated = await navigateToLocation(document.location);\n\tif (!navigated) {\n\t\twindow.location.reload();\n\t\treturn;\n\t}\n\t// Restore the scroll position that was saved on the history entry\n\trequestAnimationFrame(() => {\n\t\tconst state = window.history.state as { scroll?: number } | null;\n\t\twindow.scrollTo(0, state?.scroll ?? 0);\n\t});\n});\n\n// Do the initial navigation and hydration\nawait navigateToLocation(document.location, true);\n\nasync function navigateToLocation(location: Location, firstTime = false) {\n\t// Reset $page on submit and link navigation\n\t// TODO: Should we put data in $page.data??\n\t$page.form = undefined;\n\n\treturn await navigate(new URL(location.toString()), firstTime);\n}\n"],"mappings":";;;;;;AAWA,YAAY,SAAS,IAAI;AAGzB,MAAM,SAAS,IAAI,OAAO;AAC1B,OAAO,SAAS,SAAS,MAAM;AAK/B,OAAO,SAAS;AAGhB,OAAO,iBAAiB,SAAS,OAAO,MAAM;CAC7C,MAAM,OAAO,QAAQ,EAAE,MAAM;CAC7B,IAAI,MAAM;EACT,IAAI,uBAAuB,MAAM,CAAC,GACjC;EAGD,EAAE,eAAe;EAEjB,MAAM,OAAO,KAAK;EAClB,MAAM,MAAM,IAAI,IAAI,IAAI;EAExB,IAAI,MAAM,SAAS,GAAG,GAAG;GAGxB,OAAO,QAAQ,aAAa,EAAE,QAAQ,OAAO,QAAQ,GAAG,IAAI,SAAS,SAAS,IAAI;GAClF,OAAO,QAAQ,UAAU,EAAE,QAAQ,EAAE,GAAG,IAAI,GAAG;EAChD,OACC,OAAO,SAAS,OAAO;CAEzB;AACD,CAAC;AAID,OAAO,iBAAiB,aAAa,aAAa;AAClD,OAAO,iBAAiB,cAAc,aAAa;AAEnD,eAAe,cAAc,GAA4B;CACxD,MAAM,OAAO,QAAQ,EAAE,MAAM;CAC7B,IAAI,MAAM;EACT,IAAI,uBAAuB,MAAM,CAAC,GACjC;EAGD,MAAM,OAAO,KAAK;EAClB,MAAM,MAAM,IAAI,IAAI,IAAI;EAExB,MAAM,OAAO,IAAI;EACjB,MAAM,QAAQ,IAAI;EAClB,MAAM,QAAQ,OAAO,MAAM,MAAM,KAAK;EACtC,IAAI,OAAO;GACV,MAAM,UAAU,MAAM;GACtB,MAAM,SAAS,MAAM,UAAU,CAAC;GAGhC,MAAM,kBAA4C,MAAM,QAAQ,SAAS,EAAA,CAAG;GAG5E,MAAM,iBACL,QAAQ,mBAAmB,MAAM,QAAQ,eAAe,EAAA,EAAI;GAK7D,MAAM,SAAS,SAAS,QAAQ,MAAM,OAAO,CAAO,GAAG,gBAAgB,gBAAgB,IAAI;EAC5F;CACD;AACD;AAEA,SAAS,QAAQ,QAA2D;CAI3E,IAAI,EAAE,kBAAkB,UAAU;CAClC,MAAM,OAAO,OAAO,QAAQ,SAAS;CACrC,IAAI,MAAM,aAAa,MAAM,CAAC,EAAE,WAAW,GAAG,GAAG;CACjD,OAAQ,QAA8B,KAAA;AACvC;AAEA,SAAS,uBAAuB,MAAyB,GAA4B;CAKpF,OACC,EAAE,oBACF,EAAE,UACF,EAAE,WACF,EAAE,WACF,EAAE,YACD,KAAK,UAAU,KAAK,WAAW,WAChC,KAAK,QAAQ,cACb,KAAK,aAAa,UAAU,KAC5B,KAAK;AAEP;AAIA,OAAO,iBAAiB,YAAY,YAAY;CAE/C,IAAI,CAAC,MADmB,mBAAmB,SAAS,QAAQ,GAC5C;EACf,OAAO,SAAS,OAAO;EACvB;CACD;CAEA,4BAA4B;EAC3B,MAAM,QAAQ,OAAO,QAAQ;EAC7B,OAAO,SAAS,GAAG,OAAO,UAAU,CAAC;CACtC,CAAC;AACF,CAAC;AAGD,MAAM,mBAAmB,SAAS,UAAU,IAAI;AAEhD,eAAe,mBAAmB,UAAoB,YAAY,OAAO;CAGxE,MAAM,OAAO,KAAA;CAEb,OAAO,MAAM,SAAS,IAAI,IAAI,SAAS,SAAS,CAAC,GAAG,SAAS;AAC9D"}
package/dist/dev.d.mts CHANGED
@@ -1,3 +1,3 @@
1
- import { t as Server } from "./Server-Cler2w5O.mjs";
2
- import { t as load } from "./serverEntry-DVv9YI3I.mjs";
1
+ import { t as Server } from "./Server-qn5Ws_d0.mjs";
2
+ import { t as load } from "./serverEntry-fj0XfTs4.mjs";
3
3
  export { Server, load };
package/dist/dev.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { t as Server } from "./Server-Dr6yqVa5.mjs";
2
- import { t as load } from "./serverEntry-LyB5pl0E.mjs";
1
+ import { t as Server } from "./Server-CSti9cvM.mjs";
2
+ import { t as load } from "./serverEntry-CnCVNNjp.mjs";
3
3
  export { Server, load };
@@ -0,0 +1,43 @@
1
+ import { n as sitemapResponse, r as HeadElement, t as SitemapOptions } from "./sitemap-BjqN4d4W.mjs";
2
+ //#region src/discovery/seo.d.ts
3
+ /**
4
+ * The elements to build head markup from: a page title, a description and
5
+ * (for social media) an image and a canonical URL.
6
+ */
7
+ type SeoOptions = {
8
+ title?: string;
9
+ description?: string;
10
+ image?: string;
11
+ url?: string;
12
+ };
13
+ /**
14
+ * Builds head element data for a page: a title, a description, and Open
15
+ * Graph / Twitter (property metas) when an image or url is included, e.g.
16
+ *
17
+ * ```ts
18
+ * import { seo } from "@torpor/build/discovery";
19
+ *
20
+ * export default {
21
+ * component,
22
+ * head: (event) => seo({
23
+ * title: post.title,
24
+ * description: post.excerpt,
25
+ * image: post.coverImage,
26
+ * url: event.url.href,
27
+ * }),
28
+ * } satisfies PageEndPoint;
29
+ * ```
30
+ *
31
+ * The values are rendered into the document head at render time (with
32
+ * `@head` markup from the component around them), overriding any static
33
+ * `<title>` in the site template. A twitter:card is included only when an
34
+ * image is set, with `og:title`/`og:description`/`og:url` set from the
35
+ * other values.
36
+ *
37
+ * @param options The values to include
38
+ * @returns The head element data
39
+ */
40
+ declare function seo(options: SeoOptions): HeadElement[];
41
+ //#endregion
42
+ export { type SeoOptions, type SitemapOptions, seo, sitemapResponse };
43
+ //# sourceMappingURL=discovery.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.d.mts","names":[],"sources":["../src/discovery/seo.ts"],"mappings":";;;;;;KAMY;EACX;EACA;EACA;EACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8BuB,IAAI,SAAS,aAAa"}