@dunx/create-app 2.4.0 → 2.5.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.
@@ -360,10 +360,10 @@ var manifest = (features) => {
360
360
  dependencies,
361
361
  devDependencies: {
362
362
  "@dunx/testing": "__DUNX_VERSION__",
363
- "@types/bun": ">=1.3.0",
363
+ "@types/bun": ">=1.4.0",
364
364
  typescript: "^5.7.0"
365
365
  },
366
- engines: { bun: ">=1.3.0" }
366
+ engines: { bun: ">=1.4.0" }
367
367
  }, null, 2)}
368
368
  `;
369
369
  };
@@ -791,6 +791,3 @@ var scaffold = async (options) => {
791
791
  };
792
792
 
793
793
  export { FEATURES, featureNames, impliedBy, TEMPLATES, VERSION_PLACEHOLDER, ScaffoldError, scaffold };
794
-
795
- //# debugId=8590CB93017DD8C964756E2164756E21
796
- //# sourceMappingURL=chunk-mpa5nv1v.js.map
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  featureNames,
8
8
  impliedBy,
9
9
  scaffold
10
- } from "./chunk-mpa5nv1v.js";
10
+ } from "./chunk-nn9ekg83.js";
11
11
 
12
12
  // src/cli.ts
13
13
  import { parseArgs } from "util";
@@ -129,6 +129,3 @@ try {
129
129
  fail(error.message);
130
130
  throw error;
131
131
  }
132
-
133
- //# debugId=01853E264154F96764756E2164756E21
134
- //# sourceMappingURL=cli.js.map
package/dist/index.js CHANGED
@@ -4,13 +4,10 @@ import {
4
4
  TEMPLATES,
5
5
  VERSION_PLACEHOLDER,
6
6
  scaffold
7
- } from "./chunk-mpa5nv1v.js";
7
+ } from "./chunk-nn9ekg83.js";
8
8
  export {
9
9
  ScaffoldError,
10
10
  TEMPLATES,
11
11
  VERSION_PLACEHOLDER,
12
12
  scaffold
13
13
  };
14
-
15
- //# debugId=2DFAD801C180F0A064756E2164756E21
16
- //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dunx/create-app",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "Scaffold a new dunx application - bunx @dunx/create-app my-api",
5
5
  "keywords": [
6
6
  "bun",
@@ -58,6 +58,6 @@
58
58
  }
59
59
  },
60
60
  "engines": {
61
- "bun": ">=1.3.0"
61
+ "bun": ">=1.4.0"
62
62
  }
63
63
  }
@@ -0,0 +1,88 @@
1
+ import { Logger } from '@dunx/core';
2
+
3
+ /** Large enough that every branch below is the interesting one. */
4
+ const DOCUMENT = 'api/openapi.json';
5
+ /** Two fields. Under the 1024-byte threshold, so it is sent as it is. */
6
+ const SMALL = 'api/notes/whoami';
7
+
8
+ interface Measured {
9
+ readonly encoding: string;
10
+ readonly wire: number;
11
+ readonly decoded: number;
12
+ readonly vary: string;
13
+ }
14
+
15
+ /**
16
+ * `fetch` decodes the body itself but leaves `content-encoding` and the encoded
17
+ * `content-length` on the response, so one request measures both sides.
18
+ */
19
+ const measure = async (
20
+ url: string,
21
+ path: string,
22
+ accept: string,
23
+ ): Promise<Measured> => {
24
+ const response = await fetch(new URL(path, url), {
25
+ headers: { 'accept-encoding': accept },
26
+ });
27
+ const decoded = await response.text();
28
+ const wire = response.headers.get('content-length');
29
+ return {
30
+ encoding: response.headers.get('content-encoding') ?? 'identity',
31
+ wire: wire === null ? decoded.length : Number(wire),
32
+ decoded: decoded.length,
33
+ vary: response.headers.get('vary') ?? '-',
34
+ };
35
+ };
36
+
37
+ const ratio = (m: Measured): string =>
38
+ `${((m.wire / m.decoded) * 100).toFixed(1)}%`;
39
+
40
+ export class CompressionDemo {
41
+ constructor(private readonly logger: Logger) {}
42
+
43
+ async demonstrate(url: string): Promise<void> {
44
+ const { logger } = this;
45
+
46
+ // This folder is vendored by `@dunx/create-app`, and a scaffold that took
47
+ // `http` without `openapi` has no document to encode. Same convention as a
48
+ // part whose backing service is absent: say so and carry on.
49
+ const available = await fetch(new URL(DOCUMENT, url));
50
+ await available.body?.cancel();
51
+ if (!available.ok) {
52
+ logger.info(`skipping: no ${DOCUMENT} in this app to encode`);
53
+ return;
54
+ }
55
+
56
+ // `identity` names a coding the app does not offer, so negotiation picks
57
+ // nothing and the body goes out unencoded.
58
+ for (const accept of ['identity', 'gzip', 'zstd', 'gzip, zstd']) {
59
+ const m = await measure(url, DOCUMENT, accept);
60
+ logger.info(
61
+ `accept-encoding: ${accept.padEnd(11)} -> ${m.encoding.padEnd(8)} ` +
62
+ `${String(m.wire).padStart(6)} of ${m.decoded} bytes (${ratio(m)})`,
63
+ );
64
+ }
65
+
66
+ // Both offered at the same q, so the server's own order decides. The app
67
+ // offers `['zstd', 'gzip']`: on a body this size the two compress to within
68
+ // 0.2% of each other and zstd is the faster encoder.
69
+ const preferred = await measure(url, DOCUMENT, 'gzip, zstd');
70
+ logger.info(
71
+ `a tie in the client's q-values is broken by the server order -> ${preferred.encoding}`,
72
+ );
73
+
74
+ // An explicit q-value outranks that order.
75
+ const forced = await measure(url, DOCUMENT, 'zstd;q=0.1, gzip;q=0.9');
76
+ logger.info(`zstd;q=0.1, gzip;q=0.9 -> ${forced.encoding}`);
77
+
78
+ const small = await measure(url, SMALL, 'gzip, zstd');
79
+ logger.info(
80
+ `under the 1024-byte threshold: ${SMALL} -> ${small.encoding} ` +
81
+ `(${small.decoded} bytes, encoding it would add bytes)`,
82
+ );
83
+ logger.info(
84
+ `vary: ${small.vary} - set even when nothing was encoded, so a shared ` +
85
+ `cache does not serve one client's encoding to another`,
86
+ );
87
+ }
88
+ }
@@ -1,11 +1,39 @@
1
1
  import { Module } from '@dunx/core';
2
+ import { CompressionModule } from '@dunx/http';
3
+ import { CompressionDemo } from './compression.demo.js';
4
+ import { TraceController } from './trace.controller.js';
5
+ import { TraceDemo } from './trace.demo.js';
2
6
  import { HttpDemo } from './http.demo.js';
3
7
  import { RequestTrail, RequestTrailMiddleware } from './request-trail.js';
4
8
 
5
9
  // `use()` resolves middleware from the container, and every class self-binds - so
6
10
  // declaring them here is for the reader, not for the resolver.
7
11
  @Module({
8
- providers: [RequestTrail, RequestTrailMiddleware, HttpDemo],
9
- exports: [RequestTrail, RequestTrailMiddleware, HttpDemo],
12
+ imports: [
13
+ // Binds `Compression`; the **app** registers it, in `bootstrap.ts`, for the
14
+ // same reason `StaticFiles` is registered there. Nothing is installed by
15
+ // importing this, so an app that never calls `app.use(Compression)` has no
16
+ // branch in the request path to skip.
17
+ //
18
+ // Defaults left alone apart from the threshold, which is here to be seen: a
19
+ // body under it is sent as it is, because gzip's header and trailer alone are
20
+ // 18 bytes and a short JSON response comes out larger.
21
+ CompressionModule.forRoot({ threshold: 1024 }),
22
+ ],
23
+ controllers: [TraceController],
24
+ providers: [
25
+ RequestTrail,
26
+ RequestTrailMiddleware,
27
+ HttpDemo,
28
+ CompressionDemo,
29
+ TraceDemo,
30
+ ],
31
+ exports: [
32
+ RequestTrail,
33
+ RequestTrailMiddleware,
34
+ HttpDemo,
35
+ CompressionDemo,
36
+ TraceDemo,
37
+ ],
10
38
  })
11
39
  export class HttpModule {}
@@ -0,0 +1,30 @@
1
+ import { RequestContext } from '@dunx/core';
2
+ import { Controller, Get, type Input, type RouteSchemas } from '@dunx/http';
3
+
4
+ /**
5
+ * What the request's W3C trace looks like from inside a handler.
6
+ *
7
+ * `RequestContext` is bound by `@dunx/core` whatever else the app imports, and
8
+ * `requestLogging: { trace: true }` is what puts the trace fields into it. Every
9
+ * log line this request writes carries the same three values.
10
+ */
11
+ @Controller('trace')
12
+ export class TraceController {
13
+ constructor(private readonly context: RequestContext) {}
14
+
15
+ @Get('/')
16
+ current(input: Input<RouteSchemas>): {
17
+ traceId: string | undefined;
18
+ spanId: string | undefined;
19
+ parentSpanId: string | undefined;
20
+ inbound: string | null;
21
+ } {
22
+ const { traceId, spanId, parentSpanId } = this.context.getContext();
23
+ return {
24
+ traceId: traceId as string | undefined,
25
+ spanId: spanId as string | undefined,
26
+ parentSpanId: parentSpanId as string | undefined,
27
+ inbound: input.req.headers.get('traceparent'),
28
+ };
29
+ }
30
+ }
@@ -0,0 +1,58 @@
1
+ import { Logger } from '@dunx/core';
2
+
3
+ interface Seen {
4
+ readonly traceId: string | undefined;
5
+ readonly spanId: string | undefined;
6
+ readonly parentSpanId: string | undefined;
7
+ }
8
+
9
+ const UPSTREAM_TRACE = '4bf92f3577b34da6a3ce929d0e0e4736';
10
+ const UPSTREAM_SPAN = '00f067aa0ba902b7';
11
+
12
+ const ask = async (url: string, traceparent?: string): Promise<Seen> => {
13
+ const response = await fetch(new URL('api/trace', url), {
14
+ headers: traceparent === undefined ? {} : { traceparent },
15
+ });
16
+ return (await response.json()) as Seen;
17
+ };
18
+
19
+ export class TraceDemo {
20
+ constructor(private readonly logger: Logger) {}
21
+
22
+ async demonstrate(url: string): Promise<void> {
23
+ const { logger } = this;
24
+
25
+ const fresh = await ask(url);
26
+ logger.info(
27
+ `no traceparent in: trace ${fresh.traceId} span ${fresh.spanId} ` +
28
+ `(no parent - this service started the trace)`,
29
+ );
30
+
31
+ const continued = await ask(
32
+ url,
33
+ `00-${UPSTREAM_TRACE}-${UPSTREAM_SPAN}-01`,
34
+ );
35
+ logger.info(
36
+ `traceparent in: trace ${continued.traceId} span ${continued.spanId} ` +
37
+ `parent ${continued.parentSpanId}`,
38
+ );
39
+ logger.info(
40
+ `the caller's trace id survived: ${continued.traceId === UPSTREAM_TRACE}, ` +
41
+ `and its span became this one's parent: ${continued.parentSpanId === UPSTREAM_SPAN}`,
42
+ );
43
+
44
+ // Discarded rather than repaired. An unparseable header means the caller's
45
+ // trace is unknown, so this request starts one of its own.
46
+ const broken = await ask(url, '00-not-a-trace-id-01');
47
+ logger.info(
48
+ `malformed traceparent in: trace ${broken.traceId} ` +
49
+ `(a fresh one, not the caller's)`,
50
+ );
51
+
52
+ // Two requests are two traces, and two spans within one.
53
+ const second = await ask(url);
54
+ logger.info(
55
+ `each request gets its own span: ${fresh.spanId !== second.spanId}`,
56
+ );
57
+ }
58
+ }
@@ -17,11 +17,19 @@ export class UsersController {
17
17
  // `Input<typeof listUsers>` has to be written out - a standard method decorator
18
18
  // can check a parameter's type but cannot contextually type an unannotated one.
19
19
  // Every field type still comes from the schema, so nothing is declared twice.
20
+ //
21
+ // The return type is checked too, against `listUsers.response[200]`: a handler
22
+ // that answers with a shape the document does not describe is a TS1241 here
23
+ // rather than a surprise for whoever read the document. `readonly User[]` is
24
+ // accepted against a schema inferring `User[]` - mutability does not survive
25
+ // serialisation.
20
26
  @Get('/', listUsers)
21
27
  list(input: Input<typeof listUsers>): Promise<readonly User[]> {
22
28
  return this.users.findAll(input.query.limit, input.query.q);
23
29
  }
24
30
 
31
+ // Only the success status is checked - the 404 in `oneUser.response` leaves via
32
+ // a thrown HttpError, which no return type can describe.
25
33
  @Get('/:id', oneUser)
26
34
  async one(input: Input<typeof oneUser>): Promise<User> {
27
35
  // Already a number: the params schema coerced it before this ran.
@@ -50,14 +50,19 @@ export const ListUsers = z
50
50
 
51
51
  /**
52
52
  * The response side. Same Standard Schema contract as a request, so it hoists into
53
- * `components/schemas` the same way - but it is **never validated**: it documents
54
- * what comes back, and the handler's return type is what checks it.
53
+ * `components/schemas` the same way - and it is **never validated at runtime**:
54
+ * the verb decorator holds the handler's return type to it instead, so a route
55
+ * declaring this cannot answer with anything else and still compile.
56
+ *
57
+ * That check is what caught this schema declaring a `tags: string[]` the `users`
58
+ * table has no column for. Every user response advertised an array no handler
59
+ * returned. `tags` stays on {@link CreateUser}, the request side, where it is
60
+ * read - a documented response is a view of the row, and this one is the row.
55
61
  */
56
62
  export const User = z
57
63
  .object({
58
64
  id: z.number().int(),
59
65
  name: z.string(),
60
- tags: z.array(z.string()),
61
66
  })
62
67
  .meta({ id: 'User', description: 'A stored user' });
63
68
 
@@ -1,12 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/features.ts", "../src/scaffold.ts", "../src/generate.ts"],
4
- "sourcesContent": [
5
- "/**\n * The features a generated app can be composed from, each one a directory of\n * `examples/full` - the example CI boots and tours on every push.\n *\n * That is the whole point of sourcing them there rather than writing starter code\n * here: a template nobody runs rots, and this repo already runs `examples/full`\n * end to end. `bun run sync:templates` copies the directories in and\n * `features.test.ts` fails if a copy drifts, so what gets scaffolded is what CI\n * proved works.\n *\n * What is **not** copied is the wiring: `app.module.ts`, `config.ts`,\n * `bootstrap.ts` and `main.ts` in the full example name every feature at once, so\n * they are generated from the selection instead. See `generate.ts`.\n */\nexport interface Feature {\n /** Flag name, and the directory under `templates/features/`. */\n readonly name: string;\n /** The directory in `examples/full/src` this mirrors. */\n readonly source: string;\n readonly summary: string;\n /** Features this one imports from, pulled in automatically. */\n readonly requires: readonly string[];\n /** The module class to import, and the file it comes from. */\n readonly module: { readonly klass: string; readonly from: string };\n /** Runtime dependencies this feature adds to the generated manifest. */\n readonly dependencies: readonly string[];\n /** Config groups this feature reads, contributed to the generated config. */\n readonly config: readonly string[];\n /**\n * A service that has to be running for the feature to do anything. Named so the\n * prompt can say so and the generated README can list it, rather than the app\n * failing in a way the reader has to diagnose.\n */\n readonly service?: string;\n}\n\n/**\n * Config groups, keyed by the name a feature asks for. `env` is what lands in\n * `.env.example`, `schema` the zod line, `field` the `AppConfig` member and `map`\n * how the flat variable becomes the shaped one - the four things\n * `examples/full/src/config.ts` states for every group at once, split so a\n * selection can state only its own.\n */\nexport interface ConfigGroup {\n readonly schema: readonly string[];\n readonly field: string;\n readonly map: string;\n readonly env: readonly { readonly name: string; readonly value: string }[];\n}\n\nexport const CONFIG_GROUPS: Readonly<Record<string, ConfigGroup>> =\n Object.freeze({\n port: {\n schema: [\n 'PORT: z.coerce.number().int().min(0).max(65535).default(3000),',\n ],\n field: 'readonly port: number;',\n map: 'port: value.PORT,',\n env: [{ name: 'PORT', value: '3000' }],\n },\n appName: {\n schema: [],\n field: 'readonly appName: string;',\n map: \"appName: '__DUNX_APP_NAME__',\",\n env: [],\n },\n log: {\n schema: [\n 'LOG_LEVEL: z.enum(LogLevel).default(LogLevel.INFO),',\n '/** Unset means console only. Set it to also append JSON to a rotating file. */',\n 'LOG_FILE: z.string().optional(),',\n ],\n field:\n 'readonly log: { readonly level: LogLevel; readonly file: string | undefined };',\n map: 'log: { level: value.LOG_LEVEL, file: value.LOG_FILE },',\n env: [{ name: 'LOG_LEVEL', value: 'info' }],\n },\n corsOrigin: {\n schema: [\"CORS_ORIGIN: z.string().default('https://example.com'),\"],\n field: 'readonly corsOrigin: string;',\n map: 'corsOrigin: value.CORS_ORIGIN,',\n env: [{ name: 'CORS_ORIGIN', value: 'https://example.com' }],\n },\n database: {\n schema: [\n '/** `:memory:` needs no server and leaves nothing behind, so restarts are clean. */',\n \"DATABASE_FILE: z.string().default(':memory:'),\",\n ],\n field: 'readonly database: { readonly file: string };',\n map: 'database: { file: value.DATABASE_FILE },',\n env: [{ name: 'DATABASE_FILE', value: ':memory:' }],\n },\n redis: {\n schema: [\n '/** Absent is fine: the cache routes report themselves degraded instead of failing. */',\n 'REDIS_URL: z.string().optional(),',\n ],\n field: 'readonly redis: { readonly url: string | undefined };',\n map: 'redis: { url: value.REDIS_URL },',\n env: [{ name: 'REDIS_URL', value: 'redis://localhost:6379' }],\n },\n images: {\n schema: [\n 'IMAGE_QUALITY: z.coerce.number().int().min(1).max(100).default(82),',\n ],\n field: 'readonly images: { readonly quality: number };',\n map: 'images: { quality: value.IMAGE_QUALITY },',\n env: [{ name: 'IMAGE_QUALITY', value: '82' }],\n },\n auth: {\n schema: [\n '/** better-auth signs session cookies with this. 32 characters is its own minimum. */',\n \"AUTH_SECRET: z.string().min(32).default('dunx-development-secret-not-for-production'),\",\n ],\n field: 'readonly auth: { readonly secret: string };',\n map: 'auth: { secret: value.AUTH_SECRET },',\n env: [\n {\n name: 'AUTH_SECRET',\n value: 'change-me-to-at-least-32-characters-long',\n },\n ],\n },\n seedUsers: {\n schema: [],\n field: 'readonly seedUsers: readonly string[];',\n map: \"seedUsers: ['ada', 'grace'],\",\n env: [],\n },\n authorization: {\n schema: [],\n field: 'readonly authorization: { readonly enabled: boolean };',\n map: 'authorization: { enabled: true },',\n env: [],\n },\n throttle: {\n schema: [\n '/** The app-wide limit. Generous, so a per-route `@Throttle` is the interesting half. */',\n 'THROTTLE_LIMIT: z.coerce.number().int().min(1).default(1000),',\n 'THROTTLE_WINDOW_SECONDS: z.coerce.number().int().min(1).default(60),',\n ],\n field:\n 'readonly throttle: { readonly limit: number; readonly windowSeconds: number };',\n map: 'throttle: { limit: value.THROTTLE_LIMIT, windowSeconds: value.THROTTLE_WINDOW_SECONDS },',\n env: [\n { name: 'THROTTLE_LIMIT', value: '1000' },\n { name: 'THROTTLE_WINDOW_SECONDS', value: '60' },\n ],\n },\n schedule: {\n schema: [\n '/** A `@Cron` that names no zone of its own runs in this one. */',\n \"SCHEDULE_TZ: z.string().default('UTC'),\",\n ],\n field: 'readonly schedule: { readonly tz: string };',\n map: 'schedule: { tz: value.SCHEDULE_TZ },',\n env: [{ name: 'SCHEDULE_TZ', value: 'UTC' }],\n },\n upstream: {\n schema: [\n '/** Per-call budget for the outbound client. */',\n 'UPSTREAM_TIMEOUT_MS: z.coerce.number().int().min(1).default(5000),',\n ],\n field: 'readonly upstream: { readonly timeoutMs: number };',\n map: 'upstream: { timeoutMs: value.UPSTREAM_TIMEOUT_MS },',\n env: [{ name: 'UPSTREAM_TIMEOUT_MS', value: '5000' }],\n },\n });\n\n/** Always present, whatever is selected: the port and the logger need them. */\nexport const BASE_CONFIG: readonly string[] = ['appName', 'port', 'log'];\n\nexport const FEATURES: readonly Feature[] = [\n {\n name: 'notes',\n source: 'notes',\n summary: 'CRUD routes with zod validation. The smallest real feature.',\n requires: [],\n module: { klass: 'NotesModule', from: './notes/notes.module.js' },\n dependencies: ['@dunx/openapi', 'zod'],\n config: [],\n },\n {\n name: 'openapi',\n source: 'docs',\n summary:\n 'OpenAPI 3.1 from the routes own schemas, plus the Swagger UI page.',\n requires: [],\n module: { klass: 'DocsModule', from: './docs/docs.module.js' },\n // No `swagger-ui-dist` here: it is a hard dependency of `@dunx/openapi`, so\n // it arrives transitively and a scaffolded app never names it.\n dependencies: ['@dunx/openapi', 'zod'],\n config: [],\n },\n {\n name: 'http',\n source: 'http',\n summary:\n 'CORS, a middleware of your own on the response, and error mapping.',\n requires: [],\n module: { klass: 'HttpModule', from: './http/http.module.js' },\n dependencies: [],\n config: ['corsOrigin'],\n },\n {\n name: 'guards',\n source: 'guards',\n summary:\n 'Route guards with @Roles and @Public, and a protected controller.',\n requires: [],\n module: { klass: 'GuardsModule', from: './guards/guards.module.js' },\n dependencies: ['zod'],\n config: ['authorization'],\n },\n {\n name: 'database',\n source: 'database',\n summary: 'drizzle over bun:sqlite, with a schema, seeds and migrations.',\n requires: [],\n module: { klass: 'DatabaseModule', from: './database/database.module.js' },\n dependencies: ['@dunx/infra', 'drizzle-orm', 'zod'],\n config: ['database'],\n },\n {\n name: 'users',\n source: 'users',\n summary: 'A repository, a service and validated routes over the database.',\n requires: ['database'],\n module: { klass: 'UsersModule', from: './users/users.module.js' },\n dependencies: ['@dunx/infra', 'drizzle-orm', 'zod'],\n config: ['appName', 'seedUsers'],\n },\n {\n name: 'auth',\n source: 'auth',\n summary: 'better-auth mounted, with SessionGuard and an audit trail.',\n requires: ['database'],\n module: { klass: 'AccountsModule', from: './auth/auth.module.js' },\n dependencies: ['@dunx/auth', 'better-auth', 'drizzle-orm'],\n config: ['auth', 'port'],\n },\n {\n name: 'cache',\n source: 'cache',\n summary: 'Bun.RedisClient behind a session store, degrading when absent.',\n requires: [],\n module: { klass: 'CacheModule', from: './cache/cache.module.js' },\n dependencies: ['@dunx/infra', 'zod'],\n config: ['redis'],\n service: 'Redis or Valkey',\n },\n {\n name: 'websockets',\n source: 'chat',\n summary: 'A @Gateway with @OnMessage events, PubSub and a Redis relay.',\n // `cache` joined this list for the same reason `files` joined health's: the gateway\n // injects `RedisConnection` for cross-process fan-out, and a module now has to\n // import the one that provides it. The summary already said \"and a Redis relay\".\n requires: ['cache'],\n module: { klass: 'ChatModule', from: './chat/chat.module.js' },\n dependencies: ['@dunx/infra'],\n config: [],\n service: 'Redis or Valkey, for multi-node fan-out only',\n },\n {\n name: 'images',\n source: 'pictures',\n summary: 'Bun.Image resizing and format conversion behind a route.',\n requires: [],\n module: { klass: 'PicturesModule', from: './pictures/pictures.module.js' },\n dependencies: ['@dunx/infra', 'zod'],\n config: ['images'],\n },\n {\n name: 'files',\n source: 'storage',\n summary: 'Uploads and downloads on Bun.file, with a workspace root.',\n requires: [],\n module: { klass: 'StorageModule', from: './storage/storage.module.js' },\n dependencies: ['@dunx/infra', 'zod'],\n config: [],\n },\n {\n name: 'jobs',\n source: 'jobs',\n summary: 'bullmq queues and a worker, over Bun.RedisClient.',\n requires: ['images'],\n module: { klass: 'JobsModule', from: './jobs/jobs.module.js' },\n dependencies: ['@dunx/infra', 'bullmq', 'ioredis', 'zod'],\n config: ['redis'],\n service: 'Redis or Valkey',\n },\n {\n name: 'health',\n source: 'health',\n summary:\n \"`HealthModule`'s liveness and readiness probes, wired to this app's own indicators.\",\n // Each one supplies an indicator: `cache` the Redis connection, `database` the\n // connection and the `Ledger` the custom check queries, `files` the `Workspace`\n // whose directory the disk check measures. Selecting health without them used\n // to typecheck and fail at boot.\n requires: ['cache', 'database', 'files'],\n module: { klass: 'ProbesModule', from: './health/health.module.js' },\n dependencies: ['@dunx/infra'],\n config: ['appName'],\n },\n {\n name: 'throttle',\n source: 'throttle',\n summary:\n 'A fixed-window rate limit, with the counter in Redis and per-route overrides.',\n // `cache` for the `RedisConnection` the shared counter writes to. The\n // in-process default needs nothing, but it is per replica, so the example\n // shows the one that survives a second pod.\n requires: ['cache'],\n module: { klass: 'LimitsModule', from: './throttle/throttle.module.js' },\n dependencies: ['@dunx/infra'],\n config: ['appName', 'throttle'],\n service: 'Redis or Valkey',\n },\n {\n name: 'schedule',\n source: 'schedule',\n summary:\n '@Cron, @Interval and @OnceOnBoot on Bun.cron, armed at boot and triggerable.',\n requires: [],\n module: {\n klass: 'MaintenanceModule',\n from: './schedule/schedule.module.js',\n },\n dependencies: ['@dunx/infra'],\n config: ['schedule'],\n },\n {\n name: 'assets',\n source: 'assets',\n summary:\n 'A static directory on Bun.file, with a short max-age and an immutable rule.',\n requires: [],\n module: { klass: 'AssetsModule', from: './assets/assets.module.js' },\n dependencies: [],\n config: [],\n },\n {\n name: 'client',\n source: 'upstream',\n summary:\n 'The outbound half of @dunx/http: retry, backoff and a typed FetchError.',\n requires: [],\n module: { klass: 'UpstreamModule', from: './upstream/upstream.module.js' },\n dependencies: [],\n config: ['appName', 'upstream'],\n },\n];\n\nexport const featureNames: readonly string[] = FEATURES.map(\n (feature) => feature.name,\n);\n\nconst byName = new Map(FEATURES.map((feature) => [feature.name, feature]));\n\nexport class UnknownFeatureError extends Error {\n override readonly name = 'UnknownFeatureError';\n}\n\n/**\n * The selection plus everything it requires, in **import order** - which is\n * construction order, and shutdown runs in reverse. A feature is emitted after\n * everything it requires, so the database outlives the features reading it, the\n * same ordering `examples/full/src/app.module.ts` states by hand.\n *\n * Depth-first over `requires`, with a visited set, so a diamond resolves once and\n * the result is stable whatever order the caller asked in.\n */\nexport const resolveFeatures = (\n requested: readonly string[],\n): readonly Feature[] => {\n const unknown = requested.filter((name) => !byName.has(name));\n if (unknown.length > 0) {\n throw new UnknownFeatureError(\n `Unknown feature${unknown.length === 1 ? '' : 's'}: ${unknown.join(', ')}. ` +\n `Available: ${featureNames.join(', ')}.`,\n );\n }\n\n const ordered: Feature[] = [];\n const seen = new Set<string>();\n const rank = new Map(FEATURES.map((feature, at) => [feature.name, at]));\n\n const visit = (name: string): void => {\n if (seen.has(name)) return;\n seen.add(name);\n const feature = byName.get(name);\n if (!feature) return;\n // Requirements in **registry order**, not in the order this feature happens to\n // list them: two independent requirements would otherwise come out in the\n // order they were typed, which is not a statement about construction order and\n // would make `requires: ['cache', 'database']` build the cache first.\n for (const required of [...feature.requires].sort(\n (left, right) => (rank.get(left) ?? 0) - (rank.get(right) ?? 0),\n )) {\n visit(required);\n }\n ordered.push(feature);\n };\n\n // Registry order, not request order, so two runs asking for the same set in a\n // different order generate byte-identical files.\n for (const feature of FEATURES) {\n if (requested.includes(feature.name)) visit(feature.name);\n }\n\n return ordered;\n};\n\n/** Which of the resolved features the caller did not ask for. */\nexport const impliedBy = (\n requested: readonly string[],\n resolved: readonly Feature[],\n): readonly string[] =>\n resolved\n .map((feature) => feature.name)\n .filter((name) => !requested.includes(name));\n",
6
- "import { existsSync, readdirSync } from 'node:fs';\nimport { basename, dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { Glob } from 'bun';\nimport { resolveFeatures, type Feature } from './features.js';\nimport {\n appModule,\n bootstrap,\n config,\n configGroupsFor,\n envExample,\n main,\n manifest,\n readme,\n worker,\n} from './generate.js';\n\n/** The templates that ship with the package, as `templates/<name>/`. */\nexport const TEMPLATES = Object.freeze(['minimal'] as const);\nexport type TemplateName = (typeof TEMPLATES)[number];\n\n/**\n * Every `@dunx/*` version in a template manifest is this placeholder. Versioning\n * is lockstep, so the right version to install is whatever version of\n * `@dunx/create-app` is doing the scaffolding - resolved at run time rather than\n * written into the template, which would go stale on the next release.\n */\nexport const VERSION_PLACEHOLDER = '__DUNX_VERSION__';\n\n/**\n * Names a package cannot ship as-is, so they ship prefixed and are renamed on write.\n *\n * `.gitignore` is the known one: npm renames a published copy to `.npmignore`.\n *\n * **`bunfig.toml` is the one that was silently missing.** It is stripped from the\n * tarball entirely - presumably so a dependency cannot hijack the installing\n * project's Bun config - and it is the single file dunx asks an app to have. Every\n * app scaffolded from a published `@dunx/create-app` therefore had no\n * `@dunx/transform/preload`, and failed at boot with the very error the guide\n * describes. Measured with `bun pm pack`, and `pack.test.ts` now measures it on\n * every run rather than trusting this comment.\n */\nconst RENAMED = Object.freeze({\n _gitignore: '.gitignore',\n '_bunfig.toml': 'bunfig.toml',\n});\n\n/**\n * Entries that do not make a directory non-empty for scaffolding purposes.\n *\n * `.git` is the one that matters: `git init` then scaffold into the repo is the\n * documented way to start, and refusing it blocks the flow outright. `.gitkeep`\n * exists only so git can track an otherwise empty directory, so it *means* empty.\n * `.DS_Store` appears from merely opening the folder in Finder. `LICENSE` is what\n * GitHub's create-a-repository flow leaves in a fresh clone.\n *\n * The list is deliberately short, and the test for it is whether the template\n * writes that name. It does not write any of these four, so ignoring them can\n * never destroy anything. `.gitignore` and `README.md` are excluded for exactly\n * that reason: the template writes both, and silently overwriting a user's copy\n * is what `--force` exists to gate.\n */\nconst IGNORED_WHEN_EMPTY: ReadonlySet<string> = new Set([\n '.DS_Store',\n '.git',\n '.gitkeep',\n 'LICENSE',\n]);\n\nexport interface ScaffoldOptions {\n /** Directory to create. Relative paths resolve against `cwd`. */\n readonly target: string;\n /** Package name for the generated app. Defaults to the target's basename. */\n readonly name?: string;\n readonly template?: TemplateName;\n /**\n * Features to compose the app from, by name. Anything they require is pulled in.\n *\n * Passing any switches from copying a fixed template to generating the wiring\n * around the chosen feature directories - see `generate.ts`. An empty list, or\n * none at all, scaffolds `template` unchanged, so the default behaviour is exactly\n * what it was.\n */\n readonly features?: readonly string[];\n /** Write into a directory that already has files in it. */\n readonly force?: boolean;\n readonly cwd?: string;\n /** Overrides the version written into the generated manifest. */\n readonly version?: string;\n}\n\nexport interface ScaffoldResult {\n readonly directory: string;\n readonly name: string;\n readonly template: TemplateName | 'composed';\n /** Resolved feature names, in import order. Empty for a fixed template. */\n readonly features: readonly string[];\n readonly files: readonly string[];\n}\n\nexport class ScaffoldError extends Error {\n override readonly name = 'ScaffoldError';\n}\n\n/**\n * `dist/index.js` and `dist/cli.js` both sit one level under the package root, so\n * `../templates` resolves the same from either. In the source tree it resolves\n * from `src/`, which is the same depth - so tests exercise the real path rather\n * than a special case.\n *\n * `fileURLToPath`, not `new URL(...).pathname`: the latter stays percent-encoded,\n * so an install under a directory with a space in it looks for `space%20test/`\n * and reports the template missing. On Windows it is worse - it yields a\n * leading-slash, drive-lettered path that resolves nowhere.\n */\nconst templatesRoot = (): string =>\n resolve(dirname(fileURLToPath(import.meta.url)), '..', 'templates');\n\n/**\n * npm forbids uppercase and a leading dot or underscore, and a scope is legal.\n * Checked here because the failure would otherwise surface as a confusing\n * `bun install` error inside a directory the user just created.\n */\nconst isValidPackageName = (name: string): boolean =>\n /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(name);\n\nconst readPackageVersion = async (): Promise<string> => {\n const file = Bun.file(join(templatesRoot(), '..', 'package.json'));\n const json = (await file.json()) as { version?: string };\n return json.version ?? '0.0.0';\n};\n\n/** Placeholders are substituted in every written file, generated or copied. */\nconst fill = (contents: string, name: string, version: string): string =>\n contents\n .replaceAll(VERSION_PLACEHOLDER, version)\n .replaceAll('__DUNX_APP_NAME__', name);\n\n/**\n * The four files a subset of features cannot copy, because the full example states\n * every feature at once in each of them.\n */\nconst generated = (\n name: string,\n features: readonly Feature[],\n): Readonly<Record<string, string>> => {\n const groups = configGroupsFor(features);\n const files: Record<string, string> = {\n 'package.json': manifest(features),\n 'README.md': readme(name, features),\n '.env.example': envExample(groups),\n 'src/main.ts': main(name, features),\n 'src/bootstrap.ts': bootstrap(name, features),\n 'src/app.module.ts': appModule(name, features),\n 'src/config.ts': config(name, groups),\n };\n if (features.some((feature) => feature.name === 'jobs')) {\n files['src/worker.ts'] = worker(name);\n }\n return files;\n};\n\nexport const scaffold = async (\n options: ScaffoldOptions,\n): Promise<ScaffoldResult> => {\n const template = options.template ?? 'minimal';\n if (!TEMPLATES.includes(template)) {\n throw new ScaffoldError(\n `Unknown template \"${template}\". Available: ${TEMPLATES.join(', ')}.`,\n );\n }\n\n // Resolved before anything is written, so an unknown feature name fails with the\n // list of real ones rather than half a directory.\n const requested = options.features ?? [];\n let features: readonly Feature[] = [];\n try {\n features = resolveFeatures(requested);\n } catch (error) {\n throw new ScaffoldError(\n error instanceof Error ? error.message : String(error),\n );\n }\n const composing = features.length > 0;\n\n const directory = resolve(options.cwd ?? process.cwd(), options.target);\n const name = options.name ?? basename(directory);\n\n if (!isValidPackageName(name)) {\n throw new ScaffoldError(\n `\"${name}\" is not a usable package name. Pass --name to choose one.`,\n );\n }\n\n if (existsSync(directory) && options.force !== true) {\n const blocking = readdirSync(directory).filter(\n (entry) => !IGNORED_WHEN_EMPTY.has(entry),\n );\n if (blocking.length > 0) {\n // Naming what blocked it, because `.git` used to block it and the message\n // gave no way to tell that from a directory of real work.\n const shown = blocking.sort().slice(0, 3).join(', ');\n const rest = blocking.length > 3 ? `, +${blocking.length - 3} more` : '';\n throw new ScaffoldError(\n `${directory} is not empty (${shown}${rest}). ` +\n `Pass --force to write into it anyway.`,\n );\n }\n }\n\n const version = options.version ?? `^${await readPackageVersion()}`;\n const written: string[] = [];\n\n /** Copies a directory of the package's own templates into the new app. */\n const copyTree = async (from: string, into: string): Promise<void> => {\n // `**/*` with `dot: true` so a template can carry a dotfile that npm did not\n // rename; the explicit `_gitignore` mapping covers the one that it does.\n for await (const relative of new Glob('**/*').scan({\n cwd: from,\n dot: true,\n onlyFiles: true,\n })) {\n const base = relative.split('/').at(-1) ?? relative;\n const renamed = (RENAMED as Record<string, string | undefined>)[base];\n const target = join(\n into,\n renamed === undefined ? relative : join(dirname(relative), renamed),\n );\n\n const contents = await Bun.file(join(from, relative)).text();\n // `Bun.write` creates parent directories, so there is no mkdir pass.\n await Bun.write(join(directory, target), fill(contents, name, version));\n written.push(target);\n }\n };\n\n if (!composing) {\n const source = join(templatesRoot(), template);\n if (!existsSync(source)) {\n throw new ScaffoldError(\n `Template \"${template}\" is missing from ${source}.`,\n );\n }\n await copyTree(source, '.');\n return {\n directory,\n name,\n template,\n features: [],\n files: written.sort(),\n };\n }\n\n // The base carries what every composed app needs and no feature owns: the\n // tsconfig, the transform preload, and the gitignore.\n const base = join(templatesRoot(), 'base');\n if (!existsSync(base)) {\n throw new ScaffoldError(\n `The base template is missing from ${base}. Run \\`bun run sync:templates\\`.`,\n );\n }\n await copyTree(base, '.');\n\n for (const feature of features) {\n const from = join(templatesRoot(), 'features', feature.source);\n if (!existsSync(from)) {\n throw new ScaffoldError(\n `Feature \"${feature.name}\" is missing from ${from}. ` +\n 'Run `bun run sync:templates`.',\n );\n }\n await copyTree(from, join('src', feature.source));\n }\n\n for (const [target, contents] of Object.entries(generated(name, features))) {\n await Bun.write(join(directory, target), fill(contents, name, version));\n written.push(target);\n }\n\n return {\n directory,\n name,\n template: 'composed',\n features: features.map((feature) => feature.name),\n files: written.sort(),\n };\n};\n",
7
- "import { BASE_CONFIG, CONFIG_GROUPS, type Feature } from './features.js';\n\n/**\n * The wiring, generated from a feature selection.\n *\n * The full example states every feature at once in four files - `app.module.ts`,\n * `config.ts`, `bootstrap.ts` and `main.ts` - so those are the ones a subset cannot\n * copy. Everything else is the feature's own directory, copied verbatim.\n *\n * Generated rather than assembled by editing a copy on purpose: an edited copy\n * cannot be checked against the example it came from, and the byte-for-byte parity\n * test is what stops the vendored features drifting from the app CI actually boots.\n */\nconst HEADER = (name: string): string =>\n `// Generated by @dunx/create-app for ${name}. Yours to edit.\\n`;\n\nconst uniq = (values: readonly string[]): string[] => [...new Set(values)];\n\n/** Every config group the selection needs, base first, in a stable order. */\nexport const configGroupsFor = (\n features: readonly Feature[],\n): readonly string[] => {\n const wanted = uniq([\n ...BASE_CONFIG,\n ...features.flatMap((feature) => feature.config),\n ]);\n return Object.keys(CONFIG_GROUPS).filter((group) => wanted.includes(group));\n};\n\nexport const dependenciesFor = (\n features: readonly Feature[],\n): readonly string[] =>\n uniq([\n '@dunx/core',\n '@dunx/http',\n '@dunx/transform',\n '@dunx/infra',\n ...features.flatMap((feature) => feature.dependencies),\n ]).sort();\n\nconst DUNX = /^@dunx\\//;\n\nexport const manifest = (features: readonly Feature[]): string => {\n const deps = dependenciesFor(features);\n const dependencies: Record<string, string> = {};\n for (const dep of deps) {\n dependencies[dep] = DUNX.test(dep) ? '__DUNX_VERSION__' : versionOf(dep);\n }\n\n const scripts: Record<string, string> = {\n start: 'bun src/main.ts',\n test: 'bun test',\n typecheck: 'tsc --noEmit',\n };\n // A queue needs a process to drain it, and it is not the web one.\n if (features.some((feature) => feature.name === 'jobs')) {\n scripts['worker'] = 'bun src/worker.ts';\n }\n\n return `${JSON.stringify(\n {\n name: '__DUNX_APP_NAME__',\n version: '0.1.0',\n private: true,\n type: 'module',\n scripts,\n dependencies,\n devDependencies: {\n '@dunx/testing': '__DUNX_VERSION__',\n '@types/bun': '>=1.3.0',\n typescript: '^5.7.0',\n },\n engines: { bun: '>=1.3.0' },\n },\n null,\n 2,\n )}\\n`;\n};\n\n/**\n * Third-party ranges, pinned here rather than read off `examples/full` at run time:\n * the generated app installs from npm and the example installs from the workspace,\n * so the example's manifest is not a statement about what a consumer should take.\n * `features.test.ts` checks these against the example's, which is what stops them\n * silently diverging from a version combination that is actually exercised.\n */\nexport const THIRD_PARTY: Readonly<Record<string, string>> = Object.freeze({\n zod: '^4.4.3',\n 'drizzle-orm': '^0.45.2',\n 'better-auth': '^1.6.25',\n bullmq: '^6.0.5',\n ioredis: '^6.0.0',\n});\n\nconst versionOf = (dep: string): string => THIRD_PARTY[dep] ?? 'latest';\n\nexport const appModule = (\n name: string,\n features: readonly Feature[],\n): string => {\n const needsLogger = true;\n // `OpenApiModule` wraps this module rather than being imported by it, so its\n // factory resolves `Auth` from here - see `bootstrap`.\n const documentsAuth = has(features, 'openapi') && has(features, 'auth');\n const imports = [\n ...(documentsAuth ? [\"import { Auth } from '@dunx/auth';\"] : []),\n \"import { ConfigModule, Module } from '@dunx/core';\",\n ...(needsLogger\n ? [\"import { LoggerModule } from '@dunx/infra/logger';\"]\n : []),\n \"import { AppConfigService, validate } from './config.js';\",\n ...features.map(\n (feature) =>\n `import { ${feature.module.klass} } from '${feature.module.from}';`,\n ),\n ];\n\n const moduleImports = [\n 'ConfigModule.forRoot({ validate, as: AppConfigService }),',\n '// The level comes from the validated config, which is the one thing a',\n '// zero-argument `forRoot` function cannot reach.',\n 'LoggerModule.forRootAsync(',\n ' {',\n ' useFactory: (config: AppConfigService) => ({',\n \" name: config.get('appName'),\",\n \" level: config.get('log').level,\",\n ' }),',\n ' inject: [AppConfigService] as const,',\n ' },',\n ' { captureGlobalErrors: true },',\n '),',\n ...features.map((feature) => `${feature.module.klass},`),\n ];\n\n return `${HEADER(name)}${imports.join('\\n')}\n\n/**\n * Import order is construction order, and shutdown runs in reverse - so config and\n * the logger are built first and torn down last, and anything a feature depends on\n * outlives it.\n */\n@Module({\n imports: [\n${moduleImports.map((line) => ` ${line}`).join('\\n')}\n ],${\n documentsAuth\n ? `\n // Better Auth serves its own routes, so the document is the only place they\n // appear - and \\`betterAuthDocument\\` needs the instance.\n exports: [Auth],`\n : ''\n }\n})\nexport class AppModule {}\n`;\n};\n\nexport const config = (name: string, groups: readonly string[]): string => {\n const chosen = groups\n .map((group) => [group, CONFIG_GROUPS[group]] as const)\n .filter(\n (entry): entry is [string, (typeof CONFIG_GROUPS)[string]] =>\n entry[1] !== undefined,\n );\n\n const schema = chosen.flatMap(([, group]) => group.schema);\n const needsLogLevel = groups.includes('log');\n\n return `${HEADER(name)}import { ConfigService, type ConfigSource${\n needsLogLevel ? ', LogLevel' : ''\n } } from '@dunx/core';\nimport { z } from 'zod';\n\n/**\n * One validation function, which is the whole \\`ConfigModule\\` contract. dunx does\n * not pick the library - this is zod because the routes already use it, and a\n * hand-written function that throws would work identically.\n *\n * \\`.default()\\` is where a value comes from when the variable is unset, so a clean\n * checkout boots with no \\`.env\\` at all. Bun loads \\`.env\\` and \\`.env.local\\` itself,\n * so there is nothing here that reads a file.\n */\nconst envSchema = z.object({\n${schema.map((line) => ` ${line}`).join('\\n')}\n});\n\nexport interface AppConfig {\n${chosen.map(([, group]) => ` ${group.field}`).join('\\n')}\n}\n\n/**\n * One name for the typed config everywhere. A subclass rather than\n * \\`ConfigService<AppConfig>\\` at each site because a factory's \\`inject: [...]\\`\n * carries no type argument - the class does, and it is a real runtime value, so it\n * is both a precise token and a usable constructor annotation.\n */\nexport class AppConfigService extends ConfigService<AppConfig> {}\n\n/** The one broker channel the websocket relay carries every topic on. */\nexport const RELAY_CHANNEL = '__DUNX_APP_NAME__:ws';\n\n/** Flat variables in, a shaped object out. Nothing downstream reads \\`Bun.env\\`. */\nexport const validate = (env: ConfigSource): AppConfig => {\n const parsed = envSchema.safeParse(env);\n if (!parsed.success) {\n const issues = parsed.error.issues\n .map((issue) => \\`\\${issue.path.join('.') || '(root)'}: \\${issue.message}\\`)\n .join('\\\\n - ');\n throw new Error(\\`Configuration is invalid:\\\\n - \\${issues}\\`);\n }\n const value = parsed.data;\n\n return {\n${chosen.map(([, group]) => ` ${group.map}`).join('\\n')}\n };\n};\n`;\n};\n\nconst has = (features: readonly Feature[], name: string): boolean =>\n features.some((feature) => feature.name === name);\n\nexport const bootstrap = (\n name: string,\n features: readonly Feature[],\n): string => {\n const openapi = has(features, 'openapi');\n const websockets = has(features, 'websockets');\n const http = has(features, 'http');\n const documentsAuth = openapi && has(features, 'auth');\n // Both bind a middleware class that the **app** registers rather than the module:\n // position in the chain is the app's decision, so it is generated here.\n const assets = has(features, 'assets');\n const throttle = has(features, 'throttle');\n\n const imports = [\n ...(documentsAuth\n ? [\"import { Auth, betterAuthDocument } from '@dunx/auth';\"]\n : []),\n `import { ${[\n 'HttpFactory',\n ...(websockets ? ['RedisRelay'] : []),\n ...(assets ? ['StaticFiles'] : []),\n ...(throttle ? ['ThrottleGuard'] : []),\n 'type HttpApp',\n ].join(', ')} } from '@dunx/http';`,\n ...(openapi ? [\"import { OpenApiModule } from '@dunx/openapi';\"] : []),\n \"import { AppModule } from './app.module.js';\",\n `import { ${[\n ...(http ? ['AppConfigService'] : []),\n ...(websockets ? ['RELAY_CHANNEL'] : []),\n ].join(', ')} } from './config.js';`,\n ...(http\n ? [\"import { RequestTrailMiddleware } from './http/request-trail.js';\"]\n : []),\n ].filter((line) => !line.includes('{ }'));\n\n /**\n * `forRootAsync` once Better Auth is in: it answers `/api/auth/*` from its own\n * handler, so route discovery sees none of it and `contribute` is what puts its\n * paths in the document. The factory needs the instance, and there is no\n * container while the module graph is still being described.\n */\n const root = documentsAuth\n ? `OpenApiModule.forRootAsync({\n root: AppModule,\n inject: [Auth] as const,\n useFactory: (auth: Auth) => ({\n title: '__DUNX_APP_NAME__',\n version: '0.1.0',\n contribute: [\n betterAuthDocument(auth, { basePath: '/api/auth', tag: 'Auth' }),\n ],\n }),\n })`\n : openapi\n ? `OpenApiModule.forRoot({\n title: '__DUNX_APP_NAME__',\n version: '0.1.0',\n root: AppModule,\n })`\n : 'AppModule';\n\n const options = websockets\n ? [\n '// Multi-node websocket fan-out on `Bun.RedisClient`, so it costs no',\n '// dependency. With no Redis running this degrades to single-process',\n '// behaviour, logs one warning, and the app still boots.',\n 'websocket: { idleTimeout: 30 },',\n 'relay: new RedisRelay({ connectionTimeout: 500 }),',\n 'relayChannel: RELAY_CHANNEL,',\n ]\n : [];\n\n /**\n * Everything between `create()` and `listen()`. The prefix is set whatever is\n * selected, because the copied controllers declare paths under it and the URLs\n * `main.ts` prints assume it.\n *\n * `app.use` takes the middleware **class**, not an instance: the container\n * constructs it, which is what lets it have dependencies of its own.\n */\n const shaping = [\n \"app.setGlobalPrefix('api');\",\n // Assets first, and outside the prefix: a page pulling twenty hashed bundles\n // must not spend a caller's request budget, and middleware never gets the\n // prefix because it is not a discovered route.\n ...(assets ? ['app.use(StaticFiles);'] : []),\n ...(http\n ? [\n 'app.use(RequestTrailMiddleware);',\n \"app.set('trust proxy', true);\",\n 'app.enableCors({',\n \" origin: app.get(AppConfigService).get('corsOrigin'),\",\n ' credentials: true,',\n ' maxAge: 600,',\n '});',\n ]\n : []),\n // After anything that establishes who is calling, because the subject the\n // limit counts by is what that decides.\n ...(throttle ? ['app.use(ThrottleGuard);'] : []),\n ];\n\n return `${HEADER(name)}${imports.join('\\n')}\n\n/**\n * One app, built the same way for \\`bun start\\` and for the tests - so what the\n * tests exercise is what actually serves.\n *\n * \\`create()\\` boots the container and discovers routes and gateways; \\`listen()\\` is\n * what builds the \\`Bun.serve\\` route table. Everything between the two still gets to\n * shape it, and after \\`listen()\\` every one of those throws.\n */\nexport const createApp = async (): Promise<HttpApp> => {\n const app = await HttpFactory.create(\n ${root}${\n options.length === 0\n ? ',\\n'\n : `,\n {\n${options.map((line) => ` ${line}`).join('\\n')}\n },\n`\n } );\n\n${shaping.map((line) => ` ${line}`).join('\\n')}\n\n return app;\n};\n`;\n};\n\nexport const main = (name: string, features: readonly Feature[]): string => {\n const health = has(features, 'health');\n const openapi = has(features, 'openapi');\n\n const lines = [\n ...(openapi\n ? [\n \"logger.info(`docs ${new URL('api/docs', url).href}`);\",\n \"logger.info(`openapi ${new URL('api/openapi.json', url).href}`);\",\n ]\n : []),\n ...(health\n ? [\"logger.info(`health ${new URL('api/health', url).href}`);\"]\n : []),\n ];\n\n return `${HEADER(name)}import { Logger } from '@dunx/core';\nimport { createApp } from './bootstrap.js';\nimport { AppConfigService } from './config.js';\n\nasync function bootstrap(): Promise<void> {\n const app = await createApp();\n app.enableShutdownHooks();\n\n const config = app.get(AppConfigService);\n const logger = app.get(Logger);\n const url = await app.listen(config.get('port'));\n\n logger.info(\\`listening on \\${url}\\`);\n${lines.map((line) => ` ${line}`).join('\\n')}${lines.length > 0 ? '\\n' : ''}\n // Nothing else to do: the server holds the process open, and the shutdown hooks\n // resolve this once a signal arrives.\n await app.closed;\n}\n\nbootstrap().catch((error: unknown) => {\n console.error('failed to start', error);\n process.exit(1);\n});\n`;\n};\n\n/** The queue worker, only when a queue was asked for. */\nexport const worker = (name: string): string =>\n `${HEADER(name)}import { AppFactory } from '@dunx/core';\nimport { AppModule } from './app.module.js';\n\n/**\n * A queue needs a process to drain it, and it is deliberately not the web one: a\n * worker that shares the server's event loop competes with request handling.\n */\nconst app = await AppFactory.create(AppModule);\napp.enableShutdownHooks();\nawait app.closed;\n`;\n\nexport const envExample = (groups: readonly string[]): string => {\n const lines = groups\n .flatMap((group) => CONFIG_GROUPS[group]?.env ?? [])\n .map((entry) => `${entry.name}=${entry.value}`);\n\n return lines.length === 0\n ? '# Every variable has a default, so this file is optional.\\n'\n : `# Every variable here has a default, so the app boots with no .env at all.\\n${lines.join('\\n')}\\n`;\n};\n\nexport const readme = (name: string, features: readonly Feature[]): string => {\n const services = features.filter((feature) => feature.service !== undefined);\n\n return `# ${name}\n\nScaffolded with \\`bunx @dunx/create-app\\`.\n\n\\`\\`\\`bash\nbun install\nbun run start\n\\`\\`\\`\n\n## What is wired up\n\n${\n features.length === 0\n ? 'Nothing beyond the base app.'\n : features\n .map((feature) => `- **${feature.name}** - ${feature.summary}`)\n .join('\\n')\n}\n\n${\n services.length === 0\n ? ''\n : `## Services\n\nThese features need something running. Each one degrades rather than failing the\nboot, so the app still starts without them.\n\n${services.map((feature) => `- **${feature.name}** needs ${feature.service}`).join('\\n')}\n\n`\n}## Layout\n\n- \\`src/main.ts\\` - the entry point\n- \\`src/bootstrap.ts\\` - builds the app; shared by \\`start\\` and the tests\n- \\`src/app.module.ts\\` - the root module, importing every feature\n- \\`src/config.ts\\` - one validation function, flat env in and a shaped object out\n${features.map((feature) => `- \\`src/${feature.source}/\\` - ${feature.name}`).join('\\n')}\n\n\\`main.ts\\`, \\`bootstrap.ts\\`, \\`app.module.ts\\` and \\`config.ts\\` were generated for the\nfeatures you chose; everything else is copied from dunx's \\`examples/full\\`, which is\nrun and toured in CI on every push. The \\`*.demo.ts\\` files are that example's\nscripted walkthroughs - delete one and its \\`providers\\` entry when you do not want it.\n\n## Constructor injection\n\n\\`bunfig.toml\\` preloads \\`@dunx/transform\\`, which records each class's constructor\nparameter types so the container can resolve them. Without that line providers are\nbuilt with no arguments and boot fails saying so.\n`;\n};\n"
8
- ],
9
- "mappings": "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",
10
- "debugId": "8590CB93017DD8C964756E2164756E21",
11
- "names": []
12
- }
package/dist/cli.js.map DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/cli.ts"],
4
- "sourcesContent": [
5
- "#!/usr/bin/env bun\nimport { parseArgs } from 'node:util';\nimport { relative } from 'node:path';\nimport { FEATURES, featureNames, impliedBy } from './features.js';\nimport { scaffold, ScaffoldError, TEMPLATES } from './scaffold.js';\nimport type { TemplateName } from './scaffold.js';\n\nconst USAGE = `Scaffold a new dunx application.\n\n bunx @dunx/create-app <directory> [options]\n\nOptions:\n --name <name> package name for the app (default: the directory name)\n --with <a,b,c> features to compose the app from (see --list)\n --all every feature\n --template <name> ${TEMPLATES.join(' | ')} (default: minimal, when no --with)\n --list print the features and exit\n --force write into a directory that already has files in it\n --yes, -y take the default selection without prompting\n --help print this\n\nWith no --with and no prompt, you get the minimal template: five files, one route.\nWith features, the wiring is generated around them and each feature's directory is\ncopied from dunx's own examples/full, which CI runs and tours on every push.\n`;\n\nconst featureList = (): string =>\n FEATURES.map((feature) => {\n const needs =\n feature.requires.length === 0\n ? ''\n : ` (pulls in ${feature.requires.join(', ')})`;\n const service =\n feature.service === undefined ? '' : ` [needs ${feature.service}]`;\n return ` ${feature.name.padEnd(12)}${feature.summary}${needs}${service}`;\n }).join('\\n');\n\n// A declaration, not a `const` arrow: control-flow analysis only narrows past a\n// never-returning call when the callee is declared this way, so `target` stays\n// `string | undefined` below if this is an arrow.\nfunction fail(message: string): never {\n console.error(message);\n process.exit(1);\n}\n\nconst { values, positionals } = parseArgs({\n args: Bun.argv.slice(2),\n allowPositionals: true,\n options: {\n name: { type: 'string' },\n template: { type: 'string' },\n with: { type: 'string' },\n all: { type: 'boolean', default: false },\n list: { type: 'boolean', default: false },\n force: { type: 'boolean', default: false },\n yes: { type: 'boolean', default: false, short: 'y' },\n help: { type: 'boolean', default: false, short: 'h' },\n },\n});\n\nif (values.help === true) {\n console.log(USAGE);\n process.exit(0);\n}\n\nif (values.list === true) {\n console.log(`Features:\\n${featureList()}`);\n process.exit(0);\n}\n\nconst target = positionals[0];\nif (target === undefined) {\n fail(`Missing the target directory.\\n\\n${USAGE}`);\n}\n\nconst split = (value: string): string[] =>\n value\n .split(',')\n .map((part) => part.trim())\n .filter((part) => part !== '');\n\n/**\n * One line of stdin, not a raw-mode menu.\n *\n * A full-screen selector means owning cursor movement, terminal restore on signal\n * and a fallback for every terminal that does not do what it claims - which is a\n * library's job, and taking one would put a dependency in the package whose whole\n * appeal is that `bunx @dunx/create-app` resolves almost nothing. A numbered list\n * plus one readline needs neither, and pasting `--with` skips it entirely.\n *\n * Only when stdin is a TTY: in CI there is nothing to answer with, and a scaffolder\n * that blocks on a prompt there hangs the job.\n */\nconst ask = async (): Promise<readonly string[]> => {\n if (values.all === true) return featureNames;\n if (values.with !== undefined) return split(values.with);\n if (values.yes === true || !process.stdin.isTTY) return [];\n\n console.log(`Features (empty for the minimal template):\\n${featureList()}\\n`);\n console.log('Names or numbers, comma separated. `all` for everything.');\n process.stdout.write('> ');\n\n const line = (await console[Symbol.asyncIterator]().next()).value;\n const answer = typeof line === 'string' ? line.trim() : '';\n if (answer === '') return [];\n if (answer === 'all') return featureNames;\n\n return split(answer).map((part) => {\n const index = Number(part);\n // Numbers are 1-based because the printed list reads that way to a human.\n return Number.isInteger(index) && index >= 1 && index <= FEATURES.length\n ? (FEATURES[index - 1]?.name ?? part)\n : part;\n });\n};\n\ntry {\n const requested = await ask();\n const result = await scaffold({\n target,\n ...(values.name === undefined ? {} : { name: values.name }),\n ...(values.template === undefined\n ? {}\n : { template: values.template as TemplateName }),\n features: requested,\n force: values.force === true,\n });\n\n // Empty when the target resolved to the directory the process is already in, in\n // which case `in ./` and `cd .` are both noise to a reader standing there.\n const where = relative(process.cwd(), result.directory);\n console.log(\n where === ''\n ? `Created ${result.name} here`\n : `Created ${result.name} in ${where}/`,\n );\n\n if (result.features.length === 0) {\n console.log(\n ` ${result.files.length} files from the ${result.template} template\\n`,\n );\n } else {\n const implied = impliedBy(\n requested,\n FEATURES.filter((feature) => result.features.includes(feature.name)),\n );\n console.log(\n ` ${result.files.length} files, ${result.features.length} features: ` +\n `${result.features.join(', ')}`,\n );\n if (implied.length > 0) {\n console.log(` ${implied.join(', ')} came along as requirements`);\n }\n const services = FEATURES.filter(\n (feature) =>\n result.features.includes(feature.name) && feature.service !== undefined,\n );\n for (const feature of services) {\n console.log(` ${feature.name} needs ${feature.service} to do anything`);\n }\n console.log('');\n }\n\n console.log('Next:');\n if (where !== '') console.log(` cd ${where}`);\n console.log(' bun install');\n console.log(' bun run start');\n} catch (error) {\n if (error instanceof ScaffoldError) fail(error.message);\n throw error;\n}\n"
6
- ],
7
- "mappings": "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",
8
- "debugId": "01853E264154F96764756E2164756E21",
9
- "names": []
10
- }
package/dist/index.js.map DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": [],
4
- "sourcesContent": [
5
- ],
6
- "mappings": "",
7
- "debugId": "2DFAD801C180F0A064756E2164756E21",
8
- "names": []
9
- }