@dunx/create-app 0.1.0 → 0.1.1
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.
- package/README.md +6 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/scaffold.d.ts +1 -1
- package/package.json +3 -3
- package/templates/minimal/README.md +4 -4
- package/templates/minimal/src/app.module.ts +1 -1
- package/templates/minimal/src/app.test.ts +1 -1
- package/templates/minimal/src/greetings.controller.ts +2 -2
- package/templates/minimal/src/greetings.service.ts +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ bunx @dunx/create-app my-api
|
|
|
8
8
|
|
|
9
9
|
> `bun create dunx-app` does **not** work, and deliberately is not advertised:
|
|
10
10
|
> `bun create <template>` resolves the unscoped npm package
|
|
11
|
-
> `create-<template>`, which this package
|
|
11
|
+
> `create-<template>`, which this package - being scoped - is not.
|
|
12
12
|
|
|
13
13
|
```
|
|
14
14
|
cd my-api
|
|
@@ -32,8 +32,7 @@ a directory you just made.
|
|
|
32
32
|
## What it generates
|
|
33
33
|
|
|
34
34
|
The `minimal` template, which is the same app as
|
|
35
|
-
[`examples/minimal`](https://github.com/petarzarkov/dunx/tree/main/examples/minimal)
|
|
36
|
-
— a service, a controller, a module, `HttpFactory.create`, one test against a real
|
|
35
|
+
[`examples/minimal`](https://github.com/petarzarkov/dunx/tree/main/examples/minimal) a service, a controller, a module, `HttpFactory.create`, one test against a real
|
|
37
36
|
server, and the `bunfig.toml` preload line that makes constructor injection work.
|
|
38
37
|
|
|
39
38
|
Its `src/` is a **byte-for-byte copy** of that example, and a test in this package
|
|
@@ -44,8 +43,8 @@ identical is what makes the template trustworthy rather than merely plausible.
|
|
|
44
43
|
|
|
45
44
|
**Versions are resolved at run time, not written into the template.** Every
|
|
46
45
|
`@dunx/*` range in the template manifest is `__DUNX_VERSION__`, replaced with a
|
|
47
|
-
caret range on this package's own version. dunx versions in lockstep
|
|
48
|
-
package shares one number and ships together
|
|
46
|
+
caret range on this package's own version. dunx versions in lockstep - every
|
|
47
|
+
package shares one number and ships together - so the version doing the
|
|
49
48
|
scaffolding is by definition a set that works together. Writing versions into the
|
|
50
49
|
template would go stale on the next release.
|
|
51
50
|
|
|
@@ -64,6 +63,6 @@ const { directory, files } = await scaffold({
|
|
|
64
63
|
});
|
|
65
64
|
```
|
|
66
65
|
|
|
67
|
-
`scaffold` throws `ScaffoldError` for anything the caller can fix
|
|
68
|
-
template, an unusable package name, a non-empty target without `force`
|
|
66
|
+
`scaffold` throws `ScaffoldError` for anything the caller can fix - an unknown
|
|
67
|
+
template, an unusable package name, a non-empty target without `force` - and lets
|
|
69
68
|
everything else propagate.
|
package/dist/cli.js.map
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"sources": ["../src/cli.ts", "../src/scaffold.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"#!/usr/bin/env bun\nimport { parseArgs } from 'node:util';\nimport { relative } from 'node:path';\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 --template <name> ${TEMPLATES.join(' | ')} (default: minimal)\n --force write into a directory that already has files in it\n --help print this\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 force: { type: 'boolean', default: false },\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\nconst target = positionals[0];\nif (target === undefined) {\n fail(`Missing the target directory.\\n\\n${USAGE}`);\n}\n\ntry {\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 force: values.force === true,\n });\n\n const where = relative(process.cwd(), result.directory) || '.';\n console.log(`Created ${result.name} in ${where}/`);\n console.log(\n ` ${result.files.length} files from the ${result.template} template\\n`,\n );\n console.log('Next:');\n 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
|
-
"import { existsSync, readdirSync } from 'node:fs';\nimport { basename, dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { Glob } from 'bun';\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
|
|
6
|
+
"import { existsSync, readdirSync } from 'node:fs';\nimport { basename, dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { Glob } from 'bun';\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/** npm renames a published `.gitignore` to `.npmignore`, so it ships prefixed. */\nconst RENAMED = Object.freeze({ _gitignore: '.gitignore' });\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 /** 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;\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\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 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 (\n existsSync(directory) &&\n readdirSync(directory).length > 0 &&\n options.force !== true\n ) {\n throw new ScaffoldError(\n `${directory} is not empty. Pass --force to write into it anyway.`,\n );\n }\n\n const source = join(templatesRoot(), template);\n if (!existsSync(source)) {\n throw new ScaffoldError(\n `Template \"${template}\" is missing from ${source}.`,\n );\n }\n\n const version = options.version ?? `^${await readPackageVersion()}`;\n const written: string[] = [];\n\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: source,\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 =\n renamed === undefined ? relative : join(dirname(relative), renamed);\n\n const contents = await Bun.file(join(source, relative)).text();\n // `Bun.write` creates parent directories, so there is no mkdir pass.\n await Bun.write(\n join(directory, target),\n contents\n .replaceAll(VERSION_PLACEHOLDER, version)\n .replaceAll('__DUNX_APP_NAME__', name),\n );\n written.push(target);\n }\n\n return { directory, name, template, files: written.sort() };\n};\n"
|
|
7
7
|
],
|
|
8
8
|
"mappings": "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",
|
|
9
9
|
"debugId": "E45CB9A6B97979B864756E2164756E21",
|
package/dist/index.js.map
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/scaffold.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import { existsSync, readdirSync } from 'node:fs';\nimport { basename, dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { Glob } from 'bun';\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
|
|
5
|
+
"import { existsSync, readdirSync } from 'node:fs';\nimport { basename, dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { Glob } from 'bun';\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/** npm renames a published `.gitignore` to `.npmignore`, so it ships prefixed. */\nconst RENAMED = Object.freeze({ _gitignore: '.gitignore' });\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 /** 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;\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\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 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 (\n existsSync(directory) &&\n readdirSync(directory).length > 0 &&\n options.force !== true\n ) {\n throw new ScaffoldError(\n `${directory} is not empty. Pass --force to write into it anyway.`,\n );\n }\n\n const source = join(templatesRoot(), template);\n if (!existsSync(source)) {\n throw new ScaffoldError(\n `Template \"${template}\" is missing from ${source}.`,\n );\n }\n\n const version = options.version ?? `^${await readPackageVersion()}`;\n const written: string[] = [];\n\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: source,\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 =\n renamed === undefined ? relative : join(dirname(relative), renamed);\n\n const contents = await Bun.file(join(source, relative)).text();\n // `Bun.write` creates parent directories, so there is no mkdir pass.\n await Bun.write(\n join(directory, target),\n contents\n .replaceAll(VERSION_PLACEHOLDER, version)\n .replaceAll('__DUNX_APP_NAME__', name),\n );\n written.push(target);\n }\n\n return { directory, name, template, files: written.sort() };\n};\n"
|
|
6
6
|
],
|
|
7
7
|
"mappings": ";;AAAA;AACA;AACA;AACA;AAGO,IAAM,YAAY,OAAO,OAAO,CAAC,SAAS,CAAU;AASpD,IAAM,sBAAsB;AAGnC,IAAM,UAAU,OAAO,OAAO,EAAE,YAAY,aAAa,CAAC;AAAA;AAsBnD,MAAM,sBAAsB,MAAM;AAAA,EACrB,OAAO;AAC3B;AAaA,IAAM,gBAAgB,MACpB,QAAQ,QAAQ,cAAc,YAAY,GAAG,CAAC,GAAG,MAAM,WAAW;AAOpE,IAAM,qBAAqB,CAAC,SAC1B,6DAA6D,KAAK,IAAI;AAExE,IAAM,qBAAqB,YAA6B;AAAA,EACtD,MAAM,OAAO,IAAI,KAAK,KAAK,cAAc,GAAG,MAAM,cAAc,CAAC;AAAA,EACjE,MAAM,OAAQ,MAAM,KAAK,KAAK;AAAA,EAC9B,OAAO,KAAK,WAAW;AAAA;AAGlB,IAAM,WAAW,OACtB,YAC4B;AAAA,EAC5B,MAAM,WAAW,QAAQ,YAAY;AAAA,EACrC,IAAI,CAAC,UAAU,SAAS,QAAQ,GAAG;AAAA,IACjC,MAAM,IAAI,cACR,qBAAqB,yBAAyB,UAAU,KAAK,IAAI,IACnE;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,QAAQ,QAAQ,OAAO,QAAQ,IAAI,GAAG,QAAQ,MAAM;AAAA,EACtE,MAAM,OAAO,QAAQ,QAAQ,SAAS,SAAS;AAAA,EAE/C,IAAI,CAAC,mBAAmB,IAAI,GAAG;AAAA,IAC7B,MAAM,IAAI,cACR,IAAI,gEACN;AAAA,EACF;AAAA,EAEA,IACE,WAAW,SAAS,KACpB,YAAY,SAAS,EAAE,SAAS,KAChC,QAAQ,UAAU,MAClB;AAAA,IACA,MAAM,IAAI,cACR,GAAG,+DACL;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,KAAK,cAAc,GAAG,QAAQ;AAAA,EAC7C,IAAI,CAAC,WAAW,MAAM,GAAG;AAAA,IACvB,MAAM,IAAI,cACR,aAAa,6BAA6B,SAC5C;AAAA,EACF;AAAA,EAEA,MAAM,UAAU,QAAQ,WAAW,IAAI,MAAM,mBAAmB;AAAA,EAChE,MAAM,UAAoB,CAAC;AAAA,EAI3B,iBAAiB,YAAY,IAAI,KAAK,MAAM,EAAE,KAAK;AAAA,IACjD,KAAK;AAAA,IACL,KAAK;AAAA,IACL,WAAW;AAAA,EACb,CAAC,GAAG;AAAA,IACF,MAAM,OAAO,SAAS,MAAM,GAAG,EAAE,GAAG,EAAE,KAAK;AAAA,IAC3C,MAAM,UAAW,QAA+C;AAAA,IAChE,MAAM,SACJ,YAAY,YAAY,WAAW,KAAK,QAAQ,QAAQ,GAAG,OAAO;AAAA,IAEpE,MAAM,WAAW,MAAM,IAAI,KAAK,KAAK,QAAQ,QAAQ,CAAC,EAAE,KAAK;AAAA,IAE7D,MAAM,IAAI,MACR,KAAK,WAAW,MAAM,GACtB,SACG,WAAW,qBAAqB,OAAO,EACvC,WAAW,qBAAqB,IAAI,CACzC;AAAA,IACA,QAAQ,KAAK,MAAM;AAAA,EACrB;AAAA,EAEA,OAAO,EAAE,WAAW,MAAM,UAAU,OAAO,QAAQ,KAAK,EAAE;AAAA;",
|
|
8
8
|
"debugId": "87D57428F30FA51264756E2164756E21",
|
package/dist/scaffold.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export type TemplateName = (typeof TEMPLATES)[number];
|
|
|
4
4
|
/**
|
|
5
5
|
* Every `@dunx/*` version in a template manifest is this placeholder. Versioning
|
|
6
6
|
* is lockstep, so the right version to install is whatever version of
|
|
7
|
-
* `@dunx/create-app` is doing the scaffolding
|
|
7
|
+
* `@dunx/create-app` is doing the scaffolding - resolved at run time rather than
|
|
8
8
|
* written into the template, which would go stale on the next release.
|
|
9
9
|
*/
|
|
10
10
|
export declare const VERSION_PLACEHOLDER = "__DUNX_VERSION__";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dunx/create-app",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Scaffold a new dunx application
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Scaffold a new dunx application - bunx @dunx/create-app my-api",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
7
7
|
"create",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"starter"
|
|
11
11
|
],
|
|
12
12
|
"homepage": "https://github.com/petarzarkov/dunx/tree/main/packages/create-app#readme",
|
|
13
|
-
"license": "
|
|
13
|
+
"license": "Apache-2.0",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Petar Zarkov",
|
|
16
16
|
"email": "pzarko1@gmail.com",
|
|
@@ -13,7 +13,7 @@ bun test
|
|
|
13
13
|
| File | Why |
|
|
14
14
|
| --------------------------- | --------------------------------------------------------------- |
|
|
15
15
|
| `src/main.ts` | Builds the container, discovers routes, starts `Bun.serve` |
|
|
16
|
-
| `src/app.module.ts` | The root module
|
|
16
|
+
| `src/app.module.ts` | The root module - `controllers` get routes, `providers` do not |
|
|
17
17
|
| `src/greetings.service.ts` | A provider, injected by constructor type with no annotation |
|
|
18
18
|
| `src/greetings.controller.ts` | Routes, returning plain objects |
|
|
19
19
|
| `src/app.test.ts` | The whole app behind a real server on port 0 |
|
|
@@ -27,7 +27,7 @@ preload = ["@dunx/transform/preload"]
|
|
|
27
27
|
|
|
28
28
|
`@dunx/transform` reads each class's constructor parameter types when the file
|
|
29
29
|
loads and records them, so the container can resolve them before calling `new`.
|
|
30
|
-
Without it, providers are constructed with no arguments and boot fails saying so
|
|
30
|
+
Without it, providers are constructed with no arguments and boot fails saying so -
|
|
31
31
|
it is not a silent `undefined`.
|
|
32
32
|
|
|
33
33
|
That is also why there is no `@Injectable()` and no `@Inject()`. Being listed in a
|
|
@@ -37,9 +37,9 @@ have no parameter decorators, so `@Inject()` does not exist.
|
|
|
37
37
|
## Next
|
|
38
38
|
|
|
39
39
|
- Add validation: give a route a schema and the body arrives typed and coerced.
|
|
40
|
-
Any Standard Schema validator works
|
|
40
|
+
Any Standard Schema validator works - zod, Valibot, ArkType.
|
|
41
41
|
- Add a database: `@dunx/infra/db` is drizzle over `bun:sqlite` and `Bun.SQL`.
|
|
42
42
|
- Serve an OpenAPI document and a page that can call your routes: `@dunx/openapi`.
|
|
43
43
|
|
|
44
44
|
The [examples](https://github.com/petarzarkov/dunx/tree/main/examples) go in that
|
|
45
|
-
order
|
|
45
|
+
order - `minimal`, then `databases` and `testing`, then `full`.
|
|
@@ -5,7 +5,7 @@ import { GreetingsService } from './greetings.service.js';
|
|
|
5
5
|
/**
|
|
6
6
|
* The root module. `controllers` are discovered for routes, `providers` are
|
|
7
7
|
* everything else. Import order is construction order, and shutdown runs in
|
|
8
|
-
* reverse
|
|
8
|
+
* reverse - which matters once there is a database to close.
|
|
9
9
|
*/
|
|
10
10
|
@Module({
|
|
11
11
|
controllers: [GreetingsController],
|
|
@@ -4,7 +4,7 @@ import { AppModule } from './app.module.js';
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* The whole app behind a real `Bun.serve` on port 0. This is also what CI runs to
|
|
7
|
-
* prove the example still boots
|
|
7
|
+
* prove the example still boots - see `examples/testing` for overrides and the
|
|
8
8
|
* rest of `@dunx/testing`.
|
|
9
9
|
*/
|
|
10
10
|
describe('minimal', () => {
|
|
@@ -5,7 +5,7 @@ import { GreetingsService } from './greetings.service.js';
|
|
|
5
5
|
* A controller is a provider with routes on it. `GreetingsService` in the
|
|
6
6
|
* constructor is resolved the same way the service's own `Logger` was.
|
|
7
7
|
*
|
|
8
|
-
* Returning a plain object is enough
|
|
8
|
+
* Returning a plain object is enough - `@dunx/http` serialises it. There is no
|
|
9
9
|
* `Response.json()` to remember and no `res` to forget to send.
|
|
10
10
|
*/
|
|
11
11
|
@Controller('greetings')
|
|
@@ -19,7 +19,7 @@ export class GreetingsController {
|
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* No schemas are declared, so a path param stays on `input.req.params` as a
|
|
22
|
-
* string. Declaring a `params` schema is what makes it typed and coerced
|
|
22
|
+
* string. Declaring a `params` schema is what makes it typed and coerced -
|
|
23
23
|
* `examples/full` does that; this one is showing the shape, not validation.
|
|
24
24
|
*/
|
|
25
25
|
@Get('/:name')
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Logger, type OnInit } from '@dunx/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* A provider. No decorator, no registration boilerplate
|
|
4
|
+
* A provider. No decorator, no registration boilerplate - being listed in a
|
|
5
5
|
* module's `providers` is what makes it injectable.
|
|
6
6
|
*
|
|
7
7
|
* `Logger` in the constructor is the whole dependency injection story: the
|