acrylblends 0.7.0 → 0.7.2

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 (50) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +75 -4
  3. package/dist/index.js.map +1 -1
  4. package/docs/framework/README.md +2 -1
  5. package/docs/framework/UPSTREAM.md +1 -1
  6. package/docs/framework/api/summary.md +7 -4
  7. package/docs/framework/cordis/node-server.md +3 -0
  8. package/docs/framework/getting-started/deploy.md +133 -0
  9. package/docs/framework/getting-started/desktop.md +1 -1
  10. package/docs/framework/getting-started/nodejs.md +37 -16
  11. package/docs/framework/guides/client-modules.md +175 -0
  12. package/docs/framework/guides/forms-and-sessions.md +231 -0
  13. package/docs/framework/guides/jsx-dom.md +12 -7
  14. package/docs/framework/guides/validation.md +88 -1
  15. package/docs/framework/helpers/cookie.md +1 -1
  16. package/docs/framework/helpers/testing.md +17 -0
  17. package/docs/framework/upstream.json +3 -3
  18. package/lib/browser/client.js +307 -0
  19. package/lib/browser/jsx-dom.js +1432 -0
  20. package/lib/index.js +1 -25
  21. package/lib/node-server/register.js +7 -49
  22. package/lib/node-server.d.ts +74 -7
  23. package/lib/node-server.js +264 -7
  24. package/lib/schema-validator.d.ts +40 -14
  25. package/lib/schema-validator.js +2 -2
  26. package/lib/src-0hI7M7cN.js +27 -0
  27. package/lib/transpile-CTnWof4z.js +90 -0
  28. package/package.json +1 -1
  29. package/plugins/acryl-inspect/upstream.json +1 -1
  30. package/plugins/acryl-node-server/src/adapter/server.ts +28 -5
  31. package/plugins/acryl-node-server/src/adapter/types.ts +14 -2
  32. package/plugins/acryl-node-server/src/client-modules.ts +293 -0
  33. package/plugins/acryl-node-server/src/register.ts +5 -62
  34. package/plugins/acryl-node-server/src/transpile.ts +120 -0
  35. package/plugins/acryl-node-server/upstream.json +4 -3
  36. package/plugins/acryl-router/src/browser/client.ts +3 -0
  37. package/plugins/acryl-router/src/browser/jsx-dom.ts +9 -0
  38. package/plugins/acryl-router/src/helpers/index.ts +1 -1
  39. package/plugins/acryl-router/src/helpers/schema-validator.ts +39 -12
  40. package/plugins/acryl-router/upstream.json +2 -2
  41. package/plugins/acryl-router-http/upstream.json +1 -1
  42. package/plugins/acryl-state/upstream.json +1 -1
  43. package/plugins/desktop/plugin.json +1 -1
  44. package/plugins/desktop/upstream.json +1 -1
  45. package/plugins/desktop-web/plugin.json +1 -1
  46. package/plugins/desktop-web/upstream.json +1 -1
  47. package/templates/agent/package.json +4 -3
  48. package/templates/blank/package.json +4 -3
  49. package/templates/todo/package.json +4 -3
  50. package/templates/todo/upstream.json +1 -1
package/lib/index.js CHANGED
@@ -1,28 +1,4 @@
1
1
  import { t as Context } from "./context-DDO0cfuP.js";
2
2
  import { t as Acryl } from "./app-BM0rTCp7.js";
3
- //#region ../../plugins/acryl-router/src/core/index.ts
4
- /**
5
- * @module
6
- *
7
- * Acryl - Web Framework built on Web Standards
8
- *
9
- * @example
10
- * ```ts
11
- * import { Acryl } from 'acrylblends'
12
- * const app = new Acryl()
13
- *
14
- * app.get('/', (c) => c.text('Acryl!'))
15
- *
16
- * export default app
17
- * ```
18
- */
19
- //#endregion
20
- //#region ../../plugins/acryl-router/src/deprecated.ts
21
- /**
22
- * The 0.4 names, kept for one release so code written against them keeps working: `App` is now `Acryl` and `AppRequest` is now `AcrylRequest`,
23
- * the upstream names with the project renamed. Not part of the ported core; both the plugin's root and the public package's root export these.
24
- */
25
- /** @deprecated Use `Acryl` (`new Acryl()`); `App` is the 0.4 name and goes away in the next release. */
26
- const App = Acryl;
27
- //#endregion
3
+ import { t as App } from "./src-0hI7M7cN.js";
28
4
  export { Acryl, App, Context };
@@ -1,6 +1,6 @@
1
- import { createRequire, registerHooks } from "node:module";
2
- import { existsSync, readFileSync } from "node:fs";
3
- import { dirname } from "node:path";
1
+ import { n as compileModule } from "../transpile-CTnWof4z.js";
2
+ import { registerHooks } from "node:module";
3
+ import { readFileSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  //#region ../../plugins/acryl-node-server/src/register.ts
6
6
  /**
@@ -14,58 +14,16 @@ import { fileURLToPath } from "node:url";
14
14
  /** The JSX runtime the docs set up; an app's tsconfig.json (or a file's own `@jsxImportSource` pragma) chooses another. */
15
15
  const DEFAULT_IMPORT_SOURCE = "acrylblends/jsx";
16
16
  const JSX_FILE = /\.[jt]sx$/u;
17
- const compilers = /* @__PURE__ */ new Map();
18
- const importSources = /* @__PURE__ */ new Map();
19
- /** The app's own `typescript`, resolved from the JSX file, so each app compiles with the version it pinned. */
20
- function compilerFor(file) {
21
- let path;
22
- try {
23
- path = createRequire(file).resolve("typescript");
24
- } catch {
25
- throw new Error(`acrylblends: ${file} contains JSX, and compiling it needs the typescript package in this app: npm install --save-dev typescript`);
26
- }
27
- let compiler = compilers.get(path);
28
- if (compiler === void 0) {
29
- compiler = createRequire(path)(path);
30
- compilers.set(path, compiler);
31
- }
32
- return compiler;
33
- }
34
- /** `jsxImportSource` of the tsconfig.json nearest to the file (following `extends`), read once per config. */
35
- function importSourceFor(file, ts) {
36
- const config = ts.findConfigFile(dirname(file), (path) => existsSync(path));
37
- if (config === void 0) return DEFAULT_IMPORT_SOURCE;
38
- let source = importSources.get(config);
39
- if (source === void 0) {
40
- const { config: json } = ts.readConfigFile(config, (path) => ts.sys.readFile(path));
41
- source = ts.parseJsonConfigFileContent(json ?? {}, ts.sys, dirname(config)).options.jsxImportSource ?? DEFAULT_IMPORT_SOURCE;
42
- importSources.set(config, source);
43
- }
44
- return source;
45
- }
46
- /** A JSX module compiled to JavaScript, with its source map inline. */
47
- function compileJsx(file, text) {
48
- const ts = compilerFor(file);
49
- return ts.transpileModule(text, {
50
- fileName: file,
51
- compilerOptions: {
52
- module: "esnext",
53
- target: "es2022",
54
- jsx: "react-jsx",
55
- jsxImportSource: importSourceFor(file, ts),
56
- allowImportingTsExtensions: true,
57
- inlineSourceMap: true,
58
- inlineSources: true
59
- }
60
- }).outputText;
61
- }
62
17
  process.setSourceMapsEnabled(true);
63
18
  registerHooks({ load(url, context, nextLoad) {
64
19
  if (!url.startsWith("file:") || !JSX_FILE.test(new URL(url).pathname)) return nextLoad(url, context);
65
20
  const file = fileURLToPath(url);
66
21
  return {
67
22
  format: "module",
68
- source: compileJsx(file, readFileSync(file, "utf8")),
23
+ source: compileModule(file, readFileSync(file, "utf8"), {
24
+ defaultImportSource: DEFAULT_IMPORT_SOURCE,
25
+ what: "contains JSX"
26
+ }),
69
27
  shortCircuit: true
70
28
  };
71
29
  } });
@@ -1,3 +1,4 @@
1
+ import { t as Acryl } from "./app-D8riatvv.js";
1
2
  import { n as UpgradeWebSocket } from "./index-BvFh1Gfx.js";
2
3
  import { ServeStaticOptions, serveStatic } from "./node-server/serve-static.js";
3
4
  import { getConnInfo } from "./node-server/conninfo.js";
@@ -5,7 +6,7 @@ import { AddressInfo } from "node:net";
5
6
  import { IncomingMessage, Server, ServerOptions, ServerResponse, createServer } from "node:http";
6
7
  import { Http2SecureServer, Http2Server, Http2ServerRequest, Http2ServerResponse, SecureServerOptions, ServerOptions as ServerOptions$1, createSecureServer, createServer as createServer$1 } from "node:http2";
7
8
  import { Duplex } from "node:stream";
8
- import { ServerOptions as ServerOptions$2, createServer as createServer$2 } from "node:https";
9
+ import { Server as Server$1, ServerOptions as ServerOptions$2, createServer as createServer$2 } from "node:https";
9
10
 
10
11
  //#region ../../plugins/acryl-node-server/src/adapter/websocket-types.d.ts
11
12
  type WSReadyState = 0 | 1 | 2 | 3;
@@ -63,21 +64,47 @@ type createSecureHttp2Options = {
63
64
  createServer?: typeof createSecureServer;
64
65
  };
65
66
  type ServerOptions$3 = createHttpOptions | createHttpsOptions | createHttp2Options | createSecureHttp2Options;
66
- type Options = {
67
+ type BaseOptions = {
67
68
  fetch: FetchCallback;
68
69
  overrideGlobalObjects?: boolean;
69
70
  autoCleanupIncoming?: boolean;
70
- port?: number;
71
+ port?: number; /** The interface to listen on; `serve` uses `127.0.0.1` when it is not given. */
71
72
  hostname?: string;
72
73
  websocket?: {
73
74
  server: WebSocketServerLike;
74
75
  };
75
- } & ServerOptions$3;
76
+ };
77
+ type Options = BaseOptions & ServerOptions$3;
78
+ /** Options for the default HTTP/1 server: `serve` and `createAdaptorServer` return an `http.Server`. */
79
+ type HttpOptions = BaseOptions & createHttpOptions;
80
+ /** Options naming `https.createServer`: an `https.Server`. */
81
+ type HttpsOptions = BaseOptions & {
82
+ serverOptions?: ServerOptions$2;
83
+ createServer: typeof createServer$2;
84
+ };
85
+ /** Options naming `http2.createServer`: an `Http2Server`. */
86
+ type Http2Options = BaseOptions & {
87
+ serverOptions?: ServerOptions$1;
88
+ createServer: typeof createServer$1;
89
+ };
90
+ /** Options naming `http2.createSecureServer`: an `Http2SecureServer`. */
91
+ type SecureHttp2Options = BaseOptions & {
92
+ serverOptions?: SecureServerOptions;
93
+ createServer: typeof createSecureServer;
94
+ };
76
95
  type CustomErrorHandler = (err: unknown) => void | Response | Promise<void | Response>;
77
96
  //#endregion
78
97
  //#region ../../plugins/acryl-node-server/src/adapter/server.d.ts
79
- declare const createAdaptorServer: (options: Options) => ServerType;
80
- declare const serve: (options: Options, listeningListener?: (info: AddressInfo) => void) => ServerType;
98
+ declare function createAdaptorServer(options: SecureHttp2Options): Http2SecureServer;
99
+ declare function createAdaptorServer(options: Http2Options): Http2Server;
100
+ declare function createAdaptorServer(options: HttpsOptions): Server$1;
101
+ declare function createAdaptorServer(options: HttpOptions): Server;
102
+ declare function createAdaptorServer(options: Options): ServerType;
103
+ declare function serve(options: SecureHttp2Options, listeningListener?: (info: AddressInfo) => void): Http2SecureServer;
104
+ declare function serve(options: Http2Options, listeningListener?: (info: AddressInfo) => void): Http2Server;
105
+ declare function serve(options: HttpsOptions, listeningListener?: (info: AddressInfo) => void): Server$1;
106
+ declare function serve(options: HttpOptions, listeningListener?: (info: AddressInfo) => void): Server;
107
+ declare function serve(options: Options, listeningListener?: (info: AddressInfo) => void): ServerType;
81
108
  //#endregion
82
109
  //#region ../../plugins/acryl-node-server/src/adapter/websocket.d.ts
83
110
  type UpgradeWebSocketOptions = {
@@ -100,4 +127,44 @@ declare class RequestError extends Error {
100
127
  });
101
128
  }
102
129
  //#endregion
103
- export { type Http2Bindings, type HttpBindings, RequestError, type ServeStaticOptions, type ServerType, type WebSocketData, type WebSocketLike, type WebSocketServerLike, createAdaptorServer, getConnInfo, getRequestListener, serve, serveStatic, upgradeWebSocket };
130
+ //#region ../../plugins/acryl-node-server/src/client-modules.d.ts
131
+ /** Options of {@link clientModules}. */
132
+ interface ClientModulesOptions {
133
+ /** The folder of client modules, absolute or relative to the working directory. */
134
+ readonly root: string;
135
+ /** The folder {@link buildClientModules} wrote; with `NODE_ENV=production` it is served instead of compiling, which needs no `typescript`. */
136
+ readonly compiled?: string | undefined;
137
+ /** At most this many bytes of compiled modules are kept in memory (least recently served first out); 16 MiB when not given. */
138
+ readonly cacheBytes?: number | undefined;
139
+ /** The folder holding `jsx-dom.js` and `client.js`; the framework's own when not given. */
140
+ readonly runtimeDir?: string | undefined;
141
+ }
142
+ /** Options of {@link buildClientModules}. */
143
+ interface BuildClientModulesOptions {
144
+ /** The folder of client modules, absolute or relative to the working directory. */
145
+ readonly root: string;
146
+ /** Where the compiled modules go, at the same relative paths and file names; it must not lie inside `root`. */
147
+ readonly outDir: string;
148
+ /** The folder holding `jsx-dom.js` and `client.js`; the framework's own when not given. */
149
+ readonly runtimeDir?: string | undefined;
150
+ }
151
+ /**
152
+ * An app serving a folder of client modules to the browser. Route it where the page's script tags point: `app.route('/client',
153
+ * clientModules({ root: 'src/client' }))` serves `src/client/counter.tsx` at `/client/counter.tsx`. Files are compiled on request and
154
+ * cached until they change (bounded by `cacheBytes`); a response carries an ETag and `Cache-Control: no-cache`, so an edit shows on the
155
+ * next reload. With `NODE_ENV=production` and `compiled` set, only the `compiled` folder is served and nothing is compiled.
156
+ * @param options - the folder, the precompiled folder, and the cache bound.
157
+ * @returns the app.
158
+ */
159
+ declare function clientModules(options: ClientModulesOptions): Acryl;
160
+ /**
161
+ * Compile a folder of client modules once, for a production that has no `typescript`: each module is written to `outDir` at the same
162
+ * relative path and file name (so the page's URLs stay the same), compiled exactly as {@link clientModules} compiles it, and the two
163
+ * browser files are copied to `outDir/@acrylblends/`. Files already in `outDir` that no source produces are left there.
164
+ * @param options - the source folder and the output folder.
165
+ * @returns the paths written, relative to `outDir`.
166
+ * @throws MissingTypeScriptError when the app has no `typescript`; an error when `outDir` lies inside `root`.
167
+ */
168
+ declare function buildClientModules(options: BuildClientModulesOptions): string[];
169
+ //#endregion
170
+ export { type BuildClientModulesOptions, type ClientModulesOptions, type Http2Bindings, type HttpBindings, RequestError, type ServeStaticOptions, type ServerType, type WebSocketData, type WebSocketLike, type WebSocketServerLike, buildClientModules, clientModules, createAdaptorServer, getConnInfo, getRequestListener, serve, serveStatic, upgradeWebSocket };
@@ -1,9 +1,15 @@
1
+ import { t as Acryl } from "./app-BM0rTCp7.js";
2
+ import { basePath } from "./route.js";
1
3
  import { defineWebSocketHelper } from "./ws.js";
2
4
  import { t as serveStatic } from "./serve-static-B14MF1Va.js";
3
5
  import { getConnInfo } from "./node-server/conninfo.js";
6
+ import { n as compileModule, t as MissingTypeScriptError } from "./transpile-CTnWof4z.js";
4
7
  import { STATUS_CODES, ServerResponse, createServer } from "node:http";
5
8
  import { Http2ServerRequest, constants } from "node:http2";
6
9
  import { Readable } from "node:stream";
10
+ import { copyFileSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, writeFileSync } from "node:fs";
11
+ import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
12
+ import { fileURLToPath } from "node:url";
7
13
  //#region ../../plugins/acryl-node-server/src/adapter/error.ts
8
14
  var RequestError = class extends Error {
9
15
  constructor(message, options) {
@@ -1293,9 +1299,7 @@ const upgradeWebSocket = defineWebSocketHelper(async (c, events, options) => {
1293
1299
  })();
1294
1300
  return new Response();
1295
1301
  });
1296
- //#endregion
1297
- //#region ../../plugins/acryl-node-server/src/adapter/server.ts
1298
- const createAdaptorServer = (options) => {
1302
+ function createAdaptorServer(options) {
1299
1303
  const fetchCallback = options.fetch;
1300
1304
  const requestListener = getRequestListener(fetchCallback, {
1301
1305
  hostname: options.hostname,
@@ -1312,14 +1316,267 @@ const createAdaptorServer = (options) => {
1312
1316
  });
1313
1317
  }
1314
1318
  return server;
1315
- };
1316
- const serve = (options, listeningListener) => {
1319
+ }
1320
+ function serve(options, listeningListener) {
1317
1321
  const server = createAdaptorServer(options);
1318
- server.listen(options?.port ?? 3e3, options.hostname, () => {
1322
+ server.listen(options?.port ?? 3e3, options.hostname ?? "127.0.0.1", () => {
1319
1323
  const serverInfo = server.address();
1320
1324
  listeningListener && listeningListener(serverInfo);
1321
1325
  });
1322
1326
  return server;
1327
+ }
1328
+ //#endregion
1329
+ //#region ../../plugins/acryl-node-server/src/client-modules.ts
1330
+ /**
1331
+ * Client components without a bundler. `clientModules({ root: 'src/client' })` is an app that serves the TypeScript and JSX modules of a
1332
+ * folder to the browser as ES modules: a `.tsx`/`.ts`/`.jsx` file compiled with the app's own `typescript` (JSX for `acrylblends/jsx/dom`),
1333
+ * a `.js`/`.mjs` file as it is. The framework's own browser imports (`acrylblends/jsx/dom` with its runtimes and `client`, and
1334
+ * `acrylblends/client`) are rewritten to two self-contained files the same app serves, at a URL relative to each module, so a page needs
1335
+ * one `<script type="module" src="/client/counter.tsx">` and no import map, under whatever path the app is routed at.
1336
+ *
1337
+ * Production runs without `typescript` (a deploy that leaves out devDependencies): `buildClientModules` writes the same compiled modules
1338
+ * and the two browser files into a folder, and with `NODE_ENV=production` and `compiled` set, only that folder is served.
1339
+ */
1340
+ /** The folder the two browser files are served from, below the app's mount. A source folder of this name is not served. */
1341
+ const RUNTIME_DIR = "@acrylblends";
1342
+ /** The framework's browser imports and the browser file that provides each. */
1343
+ const BROWSER_IMPORTS = {
1344
+ "acrylblends/jsx/dom": "jsx-dom.js",
1345
+ "acrylblends/jsx/dom/jsx-runtime": "jsx-dom.js",
1346
+ "acrylblends/jsx/dom/jsx-dev-runtime": "jsx-dom.js",
1347
+ "acrylblends/jsx/dom/client": "jsx-dom.js",
1348
+ "acrylblends/client": "client.js"
1323
1349
  };
1350
+ const RUNTIME_FILES = ["jsx-dom.js", "client.js"];
1351
+ const COMPILED = /* @__PURE__ */ new Set([
1352
+ ".ts",
1353
+ ".tsx",
1354
+ ".jsx",
1355
+ ".mts"
1356
+ ]);
1357
+ const SERVED = /* @__PURE__ */ new Set([
1358
+ ...COMPILED,
1359
+ ".js",
1360
+ ".mjs"
1361
+ ]);
1362
+ const JS_TYPE = "text/javascript; charset=utf-8";
1363
+ /** The cache bound when `cacheBytes` is not given. */
1364
+ const DEFAULT_CACHE_BYTES = 16 * 1024 * 1024;
1365
+ /** The framework's two browser files, next to the built module that contains this code. */
1366
+ const defaultRuntimeDir = () => fileURLToPath(new URL("./browser/", import.meta.url));
1367
+ /** A module path below the root (`widgets/counter.tsx`) with the URL prefix that reaches the browser files from it. */
1368
+ const runtimePrefix = (relativePath) => {
1369
+ const depth = relativePath.split("/").length - 1;
1370
+ return depth === 0 ? "./" : "../".repeat(depth);
1371
+ };
1372
+ /** One client module compiled for the browser: TypeScript and JSX compiled, the framework's imports pointed at its browser files. */
1373
+ function compileForBrowser(file, relativePath) {
1374
+ const prefix = runtimePrefix(relativePath);
1375
+ return compileModule(file, readFileSync(file, "utf8"), {
1376
+ importSource: "acrylblends/jsx/dom",
1377
+ defaultImportSource: "acrylblends/jsx/dom",
1378
+ what: "is a client module",
1379
+ rewrite: (specifier) => {
1380
+ const target = BROWSER_IMPORTS[specifier];
1381
+ return target === void 0 ? void 0 : `${prefix}${RUNTIME_DIR}/${target}`;
1382
+ }
1383
+ });
1384
+ }
1385
+ /**
1386
+ * The path below the root a request names, or `undefined` when it must not be served: a `..` or dot segment, a backslash or NUL, an
1387
+ * extension outside the served set, or a `.d.ts` file.
1388
+ */
1389
+ function servedPath(requestPath) {
1390
+ let decoded;
1391
+ try {
1392
+ decoded = decodeURIComponent(requestPath);
1393
+ } catch {
1394
+ return;
1395
+ }
1396
+ const parts = decoded.split("/").filter((part) => part !== "");
1397
+ if (parts.length === 0 || parts.some((part) => part.startsWith(".") || part.includes("\\") || part.includes("\0"))) return void 0;
1398
+ const path = parts.join("/");
1399
+ if (!SERVED.has(extname(path)) || path.endsWith(".d.ts")) return void 0;
1400
+ return path;
1401
+ }
1402
+ /** A real file inside `root` (symbolic links followed), or `undefined`. */
1403
+ function fileInside(root, path) {
1404
+ try {
1405
+ const file = realpathSync(join(root, ...path.split("/")));
1406
+ return file.startsWith(root + sep) && statSync(file).isFile() ? file : void 0;
1407
+ } catch {
1408
+ return;
1409
+ }
1410
+ }
1411
+ /** A file's validator: its size and modification time. */
1412
+ const etagOf = (stat) => `"${stat.size.toString(36)}-${Math.trunc(stat.mtimeMs).toString(36)}"`;
1413
+ /** Compiled modules, least recently served first, bounded by the UTF-8 bytes of their code. Exported for its tests. */
1414
+ var CompileCache = class {
1415
+ entries = /* @__PURE__ */ new Map();
1416
+ bytes = 0;
1417
+ limit;
1418
+ constructor(limit) {
1419
+ this.limit = limit;
1420
+ }
1421
+ get(file, mtimeMs, size) {
1422
+ const entry = this.entries.get(file);
1423
+ if (entry === void 0) return void 0;
1424
+ this.entries.delete(file);
1425
+ if (entry.mtimeMs !== mtimeMs || entry.size !== size) {
1426
+ this.bytes -= entry.bytes;
1427
+ return;
1428
+ }
1429
+ this.entries.set(file, entry);
1430
+ return entry;
1431
+ }
1432
+ set(file, stat, code) {
1433
+ const entry = {
1434
+ mtimeMs: stat.mtimeMs,
1435
+ size: stat.size,
1436
+ etag: etagOf(stat),
1437
+ code,
1438
+ bytes: Buffer.byteLength(code)
1439
+ };
1440
+ const previous = this.entries.get(file);
1441
+ if (previous !== void 0) {
1442
+ this.entries.delete(file);
1443
+ this.bytes -= previous.bytes;
1444
+ }
1445
+ if (entry.bytes > this.limit) return entry;
1446
+ this.entries.set(file, entry);
1447
+ this.bytes += entry.bytes;
1448
+ for (const [key, old] of this.entries) {
1449
+ if (this.bytes <= this.limit) break;
1450
+ this.entries.delete(key);
1451
+ this.bytes -= old.bytes;
1452
+ }
1453
+ return entry;
1454
+ }
1455
+ /** The UTF-8 bytes of code held now. */
1456
+ get heldBytes() {
1457
+ return this.bytes;
1458
+ }
1459
+ /** The files held now, least recently served first. */
1460
+ get files() {
1461
+ return [...this.entries.keys()];
1462
+ }
1463
+ };
1464
+ const absolute = (path) => isAbsolute(path) ? path : resolve(path);
1465
+ /** A folder's real path; one that does not exist (a production image that ships only the compiled folder) keeps its absolute path. */
1466
+ const realFolder = (path) => {
1467
+ try {
1468
+ return realpathSync(absolute(path));
1469
+ } catch {
1470
+ return absolute(path);
1471
+ }
1472
+ };
1473
+ /**
1474
+ * An app serving a folder of client modules to the browser. Route it where the page's script tags point: `app.route('/client',
1475
+ * clientModules({ root: 'src/client' }))` serves `src/client/counter.tsx` at `/client/counter.tsx`. Files are compiled on request and
1476
+ * cached until they change (bounded by `cacheBytes`); a response carries an ETag and `Cache-Control: no-cache`, so an edit shows on the
1477
+ * next reload. With `NODE_ENV=production` and `compiled` set, only the `compiled` folder is served and nothing is compiled.
1478
+ * @param options - the folder, the precompiled folder, and the cache bound.
1479
+ * @returns the app.
1480
+ */
1481
+ function clientModules(options) {
1482
+ const root = realFolder(options.root);
1483
+ const runtimeDir = absolute(options.runtimeDir ?? defaultRuntimeDir());
1484
+ let compiled;
1485
+ if (process.env.NODE_ENV === "production" && options.compiled !== void 0) try {
1486
+ compiled = realpathSync(absolute(options.compiled));
1487
+ } catch {
1488
+ throw new Error(`acrylblends: the compiled client folder ${absolute(options.compiled)} does not exist; build it with buildClientModules before starting in production`);
1489
+ }
1490
+ const cache = new CompileCache(options.cacheBytes ?? DEFAULT_CACHE_BYTES);
1491
+ const app = new Acryl();
1492
+ app.get("/*", (c) => {
1493
+ const requestPath = c.req.path.slice(basePath(c).length);
1494
+ const runtime = /^\/@acrylblends\/([a-z-]+\.js)$/u.exec(requestPath)?.[1];
1495
+ const send = (code, etag) => {
1496
+ if (c.req.header("if-none-match") === etag) return c.body(null, 304, {
1497
+ etag,
1498
+ "cache-control": "no-cache"
1499
+ });
1500
+ return c.body(code, 200, {
1501
+ "content-type": JS_TYPE,
1502
+ etag,
1503
+ "cache-control": "no-cache"
1504
+ });
1505
+ };
1506
+ const serveFile = (file) => {
1507
+ const stat = statSync(file);
1508
+ return send(readFileSync(file, "utf8"), etagOf(stat));
1509
+ };
1510
+ if (runtime !== void 0) {
1511
+ if (!RUNTIME_FILES.includes(runtime)) return c.notFound();
1512
+ return serveFile(compiled === void 0 ? join(runtimeDir, runtime) : join(compiled, RUNTIME_DIR, runtime));
1513
+ }
1514
+ const path = servedPath(requestPath);
1515
+ if (path === void 0 || path.split("/")[0] === RUNTIME_DIR) return c.notFound();
1516
+ if (compiled !== void 0) {
1517
+ const file = fileInside(compiled, path);
1518
+ if (file !== void 0) return serveFile(file);
1519
+ if (fileInside(root, path) !== void 0) return c.text(`acrylblends: ${path} is not in ${relative(process.cwd(), compiled) || compiled}; build the client modules again (buildClientModules)`, 500);
1520
+ return c.notFound();
1521
+ }
1522
+ const file = fileInside(root, path);
1523
+ if (file === void 0) return c.notFound();
1524
+ if (!COMPILED.has(extname(file))) return serveFile(file);
1525
+ const stat = statSync(file);
1526
+ let entry = cache.get(file, stat.mtimeMs, stat.size);
1527
+ if (entry === void 0) {
1528
+ let code;
1529
+ try {
1530
+ code = compileForBrowser(file, path);
1531
+ } catch (error) {
1532
+ if (error instanceof MissingTypeScriptError) return c.text(error.message, 500);
1533
+ throw error;
1534
+ }
1535
+ entry = cache.set(file, stat, code);
1536
+ }
1537
+ return send(entry.code, entry.etag);
1538
+ });
1539
+ return app;
1540
+ }
1541
+ /** Every served file below `dir`, as paths below `root` with `/` separators; dot entries, `node_modules` and the runtime folder skipped. */
1542
+ function sourceFiles(root, dir = root) {
1543
+ const files = [];
1544
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
1545
+ if (entry.name.startsWith(".") || entry.name === "node_modules" || dir === root && entry.name === RUNTIME_DIR) continue;
1546
+ const full = join(dir, entry.name);
1547
+ if (entry.isDirectory()) files.push(...sourceFiles(root, full));
1548
+ else if (entry.isFile() && SERVED.has(extname(entry.name)) && !entry.name.endsWith(".d.ts")) files.push(relative(root, full).split(sep).join("/"));
1549
+ }
1550
+ return files.sort();
1551
+ }
1552
+ /**
1553
+ * Compile a folder of client modules once, for a production that has no `typescript`: each module is written to `outDir` at the same
1554
+ * relative path and file name (so the page's URLs stay the same), compiled exactly as {@link clientModules} compiles it, and the two
1555
+ * browser files are copied to `outDir/@acrylblends/`. Files already in `outDir` that no source produces are left there.
1556
+ * @param options - the source folder and the output folder.
1557
+ * @returns the paths written, relative to `outDir`.
1558
+ * @throws MissingTypeScriptError when the app has no `typescript`; an error when `outDir` lies inside `root`.
1559
+ */
1560
+ function buildClientModules(options) {
1561
+ const root = realpathSync(absolute(options.root));
1562
+ const outDir = absolute(options.outDir);
1563
+ const runtimeDir = absolute(options.runtimeDir ?? defaultRuntimeDir());
1564
+ if (outDir === root || outDir.startsWith(root + sep)) throw new Error(`acrylblends: the output folder ${outDir} lies inside the client folder ${root}`);
1565
+ const written = [];
1566
+ for (const path of sourceFiles(root)) {
1567
+ const source = join(root, ...path.split("/"));
1568
+ const target = join(outDir, ...path.split("/"));
1569
+ mkdirSync(dirname(target), { recursive: true });
1570
+ if (COMPILED.has(extname(path))) writeFileSync(target, compileForBrowser(source, path));
1571
+ else copyFileSync(source, target);
1572
+ written.push(path);
1573
+ }
1574
+ mkdirSync(join(outDir, RUNTIME_DIR), { recursive: true });
1575
+ for (const name of RUNTIME_FILES) {
1576
+ copyFileSync(join(runtimeDir, name), join(outDir, RUNTIME_DIR, name));
1577
+ written.push(`${RUNTIME_DIR}/${name}`);
1578
+ }
1579
+ return written;
1580
+ }
1324
1581
  //#endregion
1325
- export { RequestError, createAdaptorServer, getConnInfo, getRequestListener, serve, serveStatic, upgradeWebSocket };
1582
+ export { RequestError, buildClientModules, clientModules, createAdaptorServer, getConnInfo, getRequestListener, serve, serveStatic, upgradeWebSocket };
@@ -1,28 +1,54 @@
1
1
  import { L as MiddlewareHandler, Q as ValidationTargets, v as Env } from "./context-DS3fYyG-.js";
2
2
 
3
3
  //#region ../../plugins/acryl-router/src/helpers/schema-validator.d.ts
4
- /** The slice of the Standard Schema interface this needs; Schemastery (what Cordis uses), Zod and Valibot all implement it. */
5
- interface StandardSchema<T = unknown> {
6
- readonly '~standard': {
7
- validate(value: unknown): StandardResult<T> | Promise<StandardResult<T>>;
8
- };
4
+ /**
5
+ * The Standard Schema v1 interface (https://standardschema.dev), declared here so the framework needs no dependency for it. Schemastery
6
+ * (what Cordis uses), Zod and Valibot implement it, and a hand-written schema object of the same shape is accepted as well. Only
7
+ * `validate` is called, so `version` and `vendor` may be left out of a hand-written schema.
8
+ */
9
+ interface StandardSchema<Input = unknown, Output = Input> {
10
+ readonly '~standard': StandardSchemaProps<Input, Output>;
11
+ }
12
+ /** The `~standard` properties of a {@link StandardSchema}. */
13
+ interface StandardSchemaProps<Input = unknown, Output = Input> {
14
+ /** The version of the standard; every library sets it. */
15
+ readonly version?: 1 | undefined;
16
+ /** The name of the schema library; every library sets it. */
17
+ readonly vendor?: string | undefined;
18
+ /** Validates an unknown value. */
19
+ readonly validate: (value: unknown, options?: StandardSchemaOptions | undefined) => StandardResult<Output> | Promise<StandardResult<Output>>;
20
+ /** The input and output types, for inference only. */
21
+ readonly types?: {
22
+ readonly input: Input;
23
+ readonly output: Output;
24
+ } | undefined;
25
+ }
26
+ /** Options a caller may pass to `validate`. */
27
+ interface StandardSchemaOptions {
28
+ /** Library-specific parameters. */
29
+ readonly libraryOptions?: Record<string, unknown> | undefined;
30
+ }
31
+ /** One validation issue: a message and where in the value it applies. */
32
+ interface StandardIssue {
33
+ readonly message: string;
34
+ readonly path?: readonly (PropertyKey | {
35
+ readonly key: PropertyKey;
36
+ })[] | undefined;
9
37
  }
10
- type StandardResult<T> = {
11
- readonly value: T;
38
+ /** What `validate` answers: the parsed value, or the issues. */
39
+ type StandardResult<Output> = {
40
+ readonly value: Output;
12
41
  readonly issues?: undefined;
13
42
  } | {
14
- readonly issues: readonly {
15
- readonly message: string;
16
- readonly path?: readonly unknown[] | undefined;
17
- }[];
43
+ readonly issues: readonly StandardIssue[];
18
44
  };
19
45
  /**
20
46
  * The core `validator` driven by a Standard Schema. A failure answers 400 with `{ error, issues }`; success makes the parsed value the
21
- * handler's `c.req.valid(target)`, typed by the schema.
47
+ * handler's `c.req.valid(target)`, typed by the schema's output.
22
48
  */
23
- declare function schemaValidator<Target extends keyof ValidationTargets, Out, E extends Env = Env, P extends string = string>(target: Target, schema: StandardSchema<Out>): MiddlewareHandler<E, P, {
49
+ declare function schemaValidator<Target extends keyof ValidationTargets, Out, E extends Env = Env, P extends string = string>(target: Target, schema: StandardSchema<unknown, Out>): MiddlewareHandler<E, P, {
24
50
  in: { [K in Target]: Out };
25
51
  out: { [K in Target]: Out };
26
52
  }>;
27
53
  //#endregion
28
- export { StandardSchema, schemaValidator };
54
+ export { StandardIssue, StandardResult, StandardSchema, StandardSchemaOptions, StandardSchemaProps, schemaValidator };
@@ -1,9 +1,9 @@
1
1
  import { t as validator } from "./validator-D84mq-dO.js";
2
2
  //#region ../../plugins/acryl-router/src/helpers/schema-validator.ts
3
- const pathOf = (path) => (path ?? []).map((part) => String(typeof part === "object" && part !== null && "key" in part ? part.key : part)).join(".");
3
+ const pathOf = (path) => (path ?? []).map((part) => String(typeof part === "object" ? part.key : part)).join(".");
4
4
  /**
5
5
  * The core `validator` driven by a Standard Schema. A failure answers 400 with `{ error, issues }`; success makes the parsed value the
6
- * handler's `c.req.valid(target)`, typed by the schema.
6
+ * handler's `c.req.valid(target)`, typed by the schema's output.
7
7
  */
8
8
  function schemaValidator(target, schema) {
9
9
  return validator(target, async (value, c) => {
@@ -0,0 +1,27 @@
1
+ import { t as Acryl } from "./app-BM0rTCp7.js";
2
+ //#region ../../plugins/acryl-router/src/core/index.ts
3
+ /**
4
+ * @module
5
+ *
6
+ * Acryl - Web Framework built on Web Standards
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { Acryl } from 'acrylblends'
11
+ * const app = new Acryl()
12
+ *
13
+ * app.get('/', (c) => c.text('Acryl!'))
14
+ *
15
+ * export default app
16
+ * ```
17
+ */
18
+ //#endregion
19
+ //#region ../../plugins/acryl-router/src/deprecated.ts
20
+ /**
21
+ * The 0.4 names, kept for one release so code written against them keeps working: `App` is now `Acryl` and `AppRequest` is now `AcrylRequest`,
22
+ * the upstream names with the project renamed. Not part of the ported core; both the plugin's root and the public package's root export these.
23
+ */
24
+ /** @deprecated Use `Acryl` (`new Acryl()`); `App` is the 0.4 name and goes away in the next release. */
25
+ const App = Acryl;
26
+ //#endregion
27
+ export { App as t };