@appweaver/create-weaver-app 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +7 -0
  3. package/create-weaver-app.d.ts +2 -0
  4. package/create-weaver-app.js +266 -0
  5. package/package.json +37 -0
  6. package/skill/GUIDELINES.md +298 -0
  7. package/skill/SKILL.md +593 -0
  8. package/skill/references/cache.md +207 -0
  9. package/skill/references/cli.md +213 -0
  10. package/skill/references/client.md +507 -0
  11. package/skill/references/configuration.md +402 -0
  12. package/skill/references/database.md +134 -0
  13. package/skill/references/dependency-injection.md +214 -0
  14. package/skill/references/events.md +152 -0
  15. package/skill/references/mailer.md +235 -0
  16. package/skill/references/queue.md +196 -0
  17. package/skill/references/resources.md +961 -0
  18. package/skill/references/scheduler.md +184 -0
  19. package/skill/references/security.md +694 -0
  20. package/skill/references/storage.md +251 -0
  21. package/templates/default/.dockerignore +5 -0
  22. package/templates/default/.env.tpl +1 -0
  23. package/templates/default/.prettierignore +3 -0
  24. package/templates/default/.prettierrc +7 -0
  25. package/templates/default/Dockerfile +56 -0
  26. package/templates/default/Dockerfile.bun +56 -0
  27. package/templates/default/README.md.tpl +7 -0
  28. package/templates/default/appweaver.dev.json.tpl +9 -0
  29. package/templates/default/appweaver.json.bun.tpl +17 -0
  30. package/templates/default/appweaver.json.tpl +16 -0
  31. package/templates/default/appweaver.test.json.tpl +30 -0
  32. package/templates/default/bunfig.toml.bun +8 -0
  33. package/templates/default/database/client.ts.tpl +7 -0
  34. package/templates/default/database/schema.prisma +13 -0
  35. package/templates/default/database/seeders/001-create-admin-user.ts.tpl +39 -0
  36. package/templates/default/eslint.config.mjs +55 -0
  37. package/templates/default/eslint.config.mjs.bun +53 -0
  38. package/templates/default/jest.config.json.node +23 -0
  39. package/templates/default/package.json.bun.tpl +39 -0
  40. package/templates/default/package.json.tpl +44 -0
  41. package/templates/default/prisma.config.ts.tpl +14 -0
  42. package/templates/default/public/favicon.ico +0 -0
  43. package/templates/default/public/robots.txt +2 -0
  44. package/templates/default/src/features/index.ts.tpl +0 -0
  45. package/templates/default/src/main.ts.tpl +7 -0
  46. package/templates/default/src/resources/user/model.ts.tpl +28 -0
  47. package/templates/default/src/resources/user/policy.ts.tpl +3 -0
  48. package/templates/default/src/resources/user/routes.ts.tpl +3 -0
  49. package/templates/default/src/resources/user/service.ts.tpl +16 -0
  50. package/templates/default/src/types/generated.ts.tpl +1 -0
  51. package/templates/default/src/types/index.ts.tpl +1 -0
  52. package/templates/default/start.sh +26 -0
  53. package/templates/default/start.sh.bun +26 -0
  54. package/templates/default/swc.config.json.node +13 -0
  55. package/templates/default/test/e2e/jest.e2e-config.json.node +22 -0
  56. package/templates/default/test/e2e/main.test.ts.tpl +24 -0
  57. package/templates/default/test/e2e/support/each.ts.tpl +13 -0
  58. package/templates/default/test/e2e/support/preload.ts.bun +13 -0
  59. package/templates/default/test/e2e/support/setup.ts.tpl +13 -0
  60. package/templates/default/test/e2e/support/teardown.ts.tpl +13 -0
  61. package/templates/default/test/unit/sample.test.ts.tpl +5 -0
  62. package/templates/default/tsconfig.build.json +10 -0
  63. package/templates/default/tsconfig.json +27 -0
  64. package/templates/default/tsconfig.json.bun +28 -0
package/LICENSE ADDED
@@ -0,0 +1 @@
1
+ UNLICENSED
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # Appweaver - Create Weaver App
2
+
3
+ > Simple, fast, and reliable web application builder tool.
4
+
5
+ ## License
6
+
7
+ UNLICENSED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,266 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const promises_1 = __importDefault(require("node:fs/promises"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const node_child_process_1 = require("node:child_process");
11
+ const commander_1 = require("commander");
12
+ const glob_1 = require("glob");
13
+ const pkg = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(__dirname, './package.json'), 'utf8'));
14
+ const prismaVersion = '7.8.0';
15
+ const dbTypes = ['sqlite', 'postgresql', 'mysql', 'sqlserver'];
16
+ const agentTypes = [
17
+ 'claude',
18
+ 'codex',
19
+ 'junie',
20
+ 'cursor',
21
+ 'copilot',
22
+ 'opencode',
23
+ 'kiro',
24
+ 'pi',
25
+ 'none'
26
+ ];
27
+ const program = new commander_1.Command();
28
+ program
29
+ .name('create-weaver-app')
30
+ .description('Create Weaver App - Bootstrap new Appweaver project')
31
+ .version(pkg.version, '-v, --version', 'Output the current version.')
32
+ .helpOption('-h, --help', 'Output usage information.')
33
+ .usage('<name> [description] [options]')
34
+ .argument('<name>', 'Name of the new project')
35
+ .argument('[description]', 'Description of the new project', 'Appweaver project')
36
+ .option('-o, --outputDir [outputDir]', 'Directory where to generate new project. (default: name of project)')
37
+ .option('--database [database]', `Type of SQL database (${dbTypes.join(', ')}).`, parseDatabaseType, 'sqlite')
38
+ .option('--port [port]', 'Port number where the application server will listen.', parsePortNumber, 5000)
39
+ .option('--host [host]', 'Hostname or IP address where the application server will bind.', parseHostname, '0.0.0.0')
40
+ .option('--agent [agent]', `The AI agent for which to configure guidelines and skill files (${agentTypes.join(', ')}).`, parseAgentType, 'claude')
41
+ .option('--bun', 'Use Bun as application runtime.')
42
+ .option('--skipInstall', 'Skip all dependencies installation.')
43
+ .option('--noRedis', 'Skip IoRedis package installation.')
44
+ .option('--noQueue', 'Skip BullQueue package installation.')
45
+ .option('--noMailer', 'Skip Nodemailer package installation.')
46
+ .option('--noCron', 'Skip Cron package installation.')
47
+ .action(async (name, description, _, command) => {
48
+ const directory = command.getOptionValue('outputDir');
49
+ const runtime = command.getOptionValue('bun') ? 'bun' : 'node';
50
+ const packageManager = runtime === 'bun' ? 'bun' : 'npm';
51
+ // Check if bun runtime is installed on this machine
52
+ if (runtime === 'bun') {
53
+ const status = await runProcess('bun', ['--version'], { quiet: true });
54
+ if (status !== 0) {
55
+ console.error('Bun runtime is not installed on this machine.');
56
+ process.exit(1);
57
+ }
58
+ }
59
+ // Sanitize and create a new directory
60
+ const sanitizedName = name
61
+ .replace(/\s+/g, '-')
62
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
63
+ .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
64
+ .toLowerCase();
65
+ const projectDir = directory ?? sanitizedName;
66
+ const destDir = node_path_1.default.join(process.cwd(), projectDir);
67
+ // Initialize new project directory
68
+ try {
69
+ await promises_1.default.access(destDir, node_fs_1.default.constants.F_OK);
70
+ console.log(`Using existing directory: ${node_path_1.default.dirname(destDir)}\n`);
71
+ }
72
+ catch (e) {
73
+ await promises_1.default.mkdir(destDir, { recursive: true });
74
+ console.log(`Created new directory: ${projectDir}\n`);
75
+ }
76
+ console.log('Generating application files...');
77
+ // Copy template contents into a new directory
78
+ const templateDir = node_path_1.default.join(__dirname, './templates/default');
79
+ await promises_1.default.cp(templateDir, destDir, { recursive: true });
80
+ // Define all variables used in template files with .tpl extension
81
+ const variables = {
82
+ NAME: name.charAt(0).toUpperCase() + name.slice(1),
83
+ LOWER_NAME: sanitizedName,
84
+ DESCRIPTION: description,
85
+ HOST: command.getOptionValue('host'),
86
+ PORT: command.getOptionValue('port'),
87
+ DEPENDENCIES: getNodeDependencies(command, runtime).join(',\n'),
88
+ DATABASE_URL: getDatabaseUrl(command, sanitizedName, 'dev'),
89
+ DATABASE_TEST_URL: getDatabaseUrl(command, sanitizedName, 'test'),
90
+ VERSION: pkg.version
91
+ };
92
+ // Process .tpl files: replace variables and remove .tpl extension
93
+ const templateFiles = await (0, glob_1.glob)('**/*.tpl', {
94
+ cwd: destDir,
95
+ absolute: true,
96
+ dot: true
97
+ });
98
+ for (const templateFile of templateFiles) {
99
+ let content = await promises_1.default.readFile(templateFile, 'utf8');
100
+ // Replace variables with values
101
+ for (const [key, value] of Object.entries(variables)) {
102
+ content = content.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value);
103
+ }
104
+ // Save output to new file and remove template file
105
+ const outputFile = templateFile.replace(/\.tpl$/, '');
106
+ await promises_1.default.writeFile(outputFile, content, 'utf8');
107
+ await promises_1.default.unlink(templateFile);
108
+ }
109
+ // Find all runtime-specific files and keep only those for current runtime
110
+ const runtimeFiles = await (0, glob_1.glob)(`**/*.{node,bun}`, {
111
+ cwd: destDir,
112
+ absolute: true,
113
+ dot: true
114
+ });
115
+ for (const runtimeFile of runtimeFiles) {
116
+ if (runtimeFile.endsWith(`.${runtime}`)) {
117
+ const outputFile = runtimeFile.replace(`.${runtime}`, '');
118
+ await promises_1.default.cp(runtimeFile, outputFile);
119
+ }
120
+ await promises_1.default.unlink(runtimeFile);
121
+ }
122
+ // Create test reports directory
123
+ await promises_1.default.mkdir(node_path_1.default.join(destDir, 'reports'));
124
+ // Add instructions for AI Agents and skill files
125
+ const agent = command.getOptionValue('agent');
126
+ if (agent !== 'none') {
127
+ let agentsDir;
128
+ if (['claude', 'junie', 'kiro', 'pi', 'opencode'].includes(agent)) {
129
+ agentsDir = `.${agent}`;
130
+ }
131
+ else if (agent === 'copilot') {
132
+ agentsDir = '.github';
133
+ }
134
+ else {
135
+ agentsDir = '.agents';
136
+ }
137
+ const guidelinesFileName = agent === 'claude' ? 'CLAUDE.md' : 'AGENTS.md';
138
+ // Copy skill and referenced files
139
+ const skillDir = node_path_1.default.join(__dirname, 'skill');
140
+ const projectSkillPath = node_path_1.default.join(agentsDir, 'skills', 'appweaver');
141
+ const projectSkillDir = node_path_1.default.join(destDir, projectSkillPath);
142
+ await promises_1.default.cp(skillDir, projectSkillDir, {
143
+ recursive: true
144
+ });
145
+ // Move the project guidelines file to the project root
146
+ const guidelinesSkillPath = node_path_1.default.join(projectSkillDir, 'GUIDELINES.md');
147
+ const guidelinesFilePath = node_path_1.default.join(destDir, guidelinesFileName);
148
+ await promises_1.default.rename(guidelinesSkillPath, guidelinesFilePath);
149
+ // Update project guidelines reference paths to point in skills directory
150
+ const guidelinesContents = await promises_1.default.readFile(guidelinesFilePath, 'utf8');
151
+ const referencesPath = node_path_1.default
152
+ .join(projectSkillPath, 'references')
153
+ .replace(/\\/g, '/');
154
+ const updatedGuidelinesContent = guidelinesContents.replace(/(\[.+]\()references\/(.+\))/g, `$1${referencesPath}/$2`);
155
+ await promises_1.default.writeFile(guidelinesFilePath, updatedGuidelinesContent, {
156
+ encoding: 'utf8'
157
+ });
158
+ }
159
+ console.log(`Done\n`);
160
+ if (command.getOptionValue('skipInstall')) {
161
+ console.log(`${name} created successfully!`);
162
+ return;
163
+ }
164
+ console.log(`Installing dependencies...`);
165
+ await runProcess(packageManager, ['install', '--no-audit', '--no-fund', '--loglevel=error'], { destDir });
166
+ console.log(`Done\n`);
167
+ console.log(`Configuring application...`);
168
+ await runProcess(packageManager, ['run', 'generate'], { destDir });
169
+ console.log(`Done\n`);
170
+ console.log(`${name} created successfully!`);
171
+ })
172
+ .parse();
173
+ function getNodeDependencies(command, runtime) {
174
+ const dependencies = [];
175
+ const adapters = {
176
+ sqlite: runtime === 'bun'
177
+ ? `"@prisma/adapter-libsql": "${prismaVersion}"`
178
+ : `"@prisma/adapter-better-sqlite3": "${prismaVersion}"`,
179
+ postgresql: `"@prisma/adapter-pg": "${prismaVersion}"`,
180
+ mysql: `"@prisma/adapter-mariadb": "${prismaVersion}"`,
181
+ sqlserver: `"@prisma/adapter-mssql": "${prismaVersion}"`
182
+ };
183
+ const database = command.getOptionValue('database');
184
+ const databaseDependency = adapters[database.toLowerCase()];
185
+ if (!databaseDependency) {
186
+ console.error(`Invalid database type: ${database}`);
187
+ process.exit(1);
188
+ }
189
+ dependencies.push(databaseDependency);
190
+ dependencies.push(`"@prisma/client": "${prismaVersion}"`);
191
+ dependencies.push(`"prisma": "${prismaVersion}"`);
192
+ if (!command.getOptionValue('noQueue')) {
193
+ dependencies.push('"bullmq": "5.70.1"');
194
+ }
195
+ if (!command.getOptionValue('noCron')) {
196
+ dependencies.push('"cron": "4.4.0"');
197
+ }
198
+ if (!command.getOptionValue('noRedis')) {
199
+ dependencies.push('"ioredis": "5.9.3"');
200
+ }
201
+ if (!command.getOptionValue('noMailer')) {
202
+ dependencies.push('"nodemailer": "8.0.5"');
203
+ }
204
+ return dependencies.sort().map((d) => ` ${d}`);
205
+ }
206
+ function getDatabaseUrl(command, name, mode) {
207
+ const dbName = mode === 'test' ? `${name}-test` : name;
208
+ const urls = {
209
+ sqlite: `file:./${mode === 'test' ? 'temp/' : ''}${dbName}.db`,
210
+ postgresql: `postgresql://${name}:${name}@localhost:5432/${dbName}?schema=public`,
211
+ mysql: `mysql://${name}:${name}@localhost:3306/${dbName}`,
212
+ sqlserver: `sqlserver://localhost:1433;database=${dbName};user=${name};password=${name};trustServerCertificate=true`
213
+ };
214
+ const database = command.getOptionValue('database');
215
+ const databaseUrl = urls[database.toLowerCase()];
216
+ if (!databaseUrl) {
217
+ console.error(`Invalid database type: ${database}`);
218
+ process.exit(1);
219
+ }
220
+ return databaseUrl;
221
+ }
222
+ function runProcess(cmd, args = [], params = {}) {
223
+ return new Promise((resolve, reject) => {
224
+ const { destDir, quiet } = params;
225
+ const command = args.length > 0 ? `${cmd} ${args.join(' ')}` : cmd;
226
+ const child = (0, node_child_process_1.spawn)(command, {
227
+ stdio: quiet ? 'ignore' : 'inherit',
228
+ shell: true,
229
+ cwd: destDir
230
+ });
231
+ child.on('error', reject);
232
+ child.on('close', (code) => {
233
+ resolve(code);
234
+ });
235
+ });
236
+ }
237
+ function parseDatabaseType(value) {
238
+ const lowerDbType = value.toLowerCase();
239
+ if (!dbTypes.includes(lowerDbType)) {
240
+ throw new commander_1.InvalidOptionArgumentError(`Must be one of following: ${dbTypes.join(', ')}.`);
241
+ }
242
+ return lowerDbType;
243
+ }
244
+ function parsePortNumber(value) {
245
+ const int = parseInt(value, 10);
246
+ if (isNaN(int) || int < 0 || int > 65535) {
247
+ throw new commander_1.InvalidOptionArgumentError('Must be an integer between 0 and 65535.');
248
+ }
249
+ return int;
250
+ }
251
+ function parseHostname(value) {
252
+ try {
253
+ new URL(`http://${value}`);
254
+ }
255
+ catch {
256
+ throw new commander_1.InvalidOptionArgumentError('Must be a valid hostname or IP address.');
257
+ }
258
+ return value;
259
+ }
260
+ function parseAgentType(value) {
261
+ const lowerAgentType = value.toLowerCase();
262
+ if (!agentTypes.includes(lowerAgentType)) {
263
+ throw new commander_1.InvalidOptionArgumentError(`Must be one of following: ${agentTypes.join(', ')}.`);
264
+ }
265
+ return lowerAgentType;
266
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@appweaver/create-weaver-app",
3
+ "version": "1.0.0",
4
+ "description": "Appweaver - simple, fast, and reliable web application builder tool (@create-weaver-app)",
5
+ "author": "Luka Matosevic",
6
+ "license": "UNLICENSED",
7
+ "main": "./create-weaver-app.js",
8
+ "types": "./create-weaver-app.d.ts",
9
+ "bin": {
10
+ "create-weaver-app": "./create-weaver-app.js"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/lmatosevic/appweaver.git",
15
+ "directory": "packages/create-weaver-app"
16
+ },
17
+ "maintainers": [
18
+ {
19
+ "name": "Luka Matosevic",
20
+ "email": "lukamatosevic5@gmail.com",
21
+ "web": "https://lukamatosevic.com"
22
+ }
23
+ ],
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "engines": {
28
+ "node": ">= 20"
29
+ },
30
+ "scripts": {
31
+ "create-weaver-app": "node ./dist/create-weaver-app.js"
32
+ },
33
+ "dependencies": {
34
+ "commander": "14.0.3",
35
+ "glob": "13.0.1"
36
+ }
37
+ }
@@ -0,0 +1,298 @@
1
+ # Appweaver Guidelines
2
+
3
+ Appweaver is a TypeScript/Node.js library for building web applications. Built on Fastify (HTTP) and Prisma (ORM), it
4
+ provides factory methods for creating resource models, services, policies, and routes with sensible defaults.
5
+
6
+ ## Project structure
7
+
8
+ - `database/` - migrations, seeders, generated Prisma client
9
+ - `dist/` - transpiled JavaScript output
10
+ - `public/` - static files (if enabled)
11
+ - `src/features/` - application logic (vertical slice architecture)
12
+ - `src/resources/` - resources (models, services, policies, routes)
13
+ - `src/types/` - generated and manual types
14
+ - `src/main.ts` - application entrypoint
15
+ - `test/e2e/` - end-to-end tests
16
+ - `test/unit/` - unit tests
17
+ - `.env` / `.env.{env}` - environment variable overrides (optional)
18
+ - `appweaver.json` / `appweaver.{env}.json` - central configuration
19
+ - `Dockerfile` - Docker image definition
20
+
21
+ **IMPORTANT:** `{env}` is controlled by `NODE_ENV` evironment variable.
22
+
23
+ ## Application entrypoint
24
+
25
+ ```ts
26
+ // src/main.ts
27
+ import { createApp } from '@appweaver/core';
28
+ import { logger } from '@appweaver/common';
29
+
30
+ createApp().catch((err) => logger.error(err));
31
+ ```
32
+
33
+ ## Creating resources
34
+
35
+ Resources are the core building blocks. There are four types: **model**, **service**, **routes**, and **policy**.
36
+ Exported resources are loaded automatically on application start.
37
+
38
+ Dependency chain: **model** → **service** → **routes** → **policy**
39
+
40
+ Only a model is required. If a service exists, a model must exist. If routes exist, a service must exist. Policy is
41
+ independent.
42
+
43
+ **DOS:**
44
+
45
+ - Use default configuration values whenever possible
46
+ - Rely on library defaults for `omit`/`pick`, and `input`/`output` settings
47
+ - Use default `mimeType` and `namePattern` patterns in file configurations unless specifically requested
48
+ - Prefer storing configuration in JSON file (`appweaver.json`) over environment (`.env`) file, but prefer it for secrets
49
+ - Always create all four resource configs (model, service, routes, and policy) unless specified otherwise
50
+
51
+ **DON'TS:**
52
+
53
+ - Don't explicitly set default values in configuration unless specifically requested
54
+ - Don't override `omit`/`pick` for `read`, `create` and `update` settings unnecessarily
55
+ - Don't specify `input`/`output` configurations if defaults suffice
56
+ - Don't modify file's `mimeType` and `namePattern` patterns unless specifically instructed
57
+ - Don't customize index arrays without an explicit requirement
58
+
59
+ ### Model
60
+
61
+ ```ts
62
+ // src/resources/product/model.ts
63
+ import { createModel } from '@appweaver/core';
64
+
65
+ export default createModel({
66
+ name: 'Product',
67
+ scalars: {
68
+ title: { type: 'string', minLength: 1, maxLength: 200 },
69
+ price: { type: 'float', minimum: 0 },
70
+ status: { type: 'enum', default: 'Draft', values: ['Draft', 'Active', 'Sold'] },
71
+ description: { type: 'string', required: false },
72
+ lastViewedAt: { type: 'dateTime', defaultGenerator: 'now()' },
73
+ enabled: { type: 'boolean', default: true }
74
+ },
75
+ relations: {
76
+ category: { model: 'Category', mappedBy: 'products', owner: true, output: { type: 'always' } }
77
+ },
78
+ files: {
79
+ photo: { mimeType: 'image/*', maxSize: '2 MB' }
80
+ },
81
+ create: { omit: ['status'] },
82
+ update: { pick: ['title', 'price', 'status', 'description'] },
83
+ index: ['title']
84
+ });
85
+ ```
86
+
87
+ ### Service
88
+
89
+ ```ts
90
+ // src/resources/product/service.ts
91
+ import { createService } from '@appweaver/core';
92
+
93
+ export default createService({
94
+ modelName: 'Product',
95
+ afterCreate: (resource) => {
96
+ console.log('Product created:', resource.id);
97
+ },
98
+ textSearch: {
99
+ title: { contains: '{input}', mode: 'insensitive' }
100
+ }
101
+ });
102
+ ```
103
+
104
+ ### Routes
105
+
106
+ ```ts
107
+ // src/resources/product/routes.ts
108
+ import { createRoutes } from '@appweaver/core';
109
+
110
+ export default createRoutes({
111
+ modelName: 'Product',
112
+ find: { cache: true, roles: ['Admin', 'User'], rateLimit: { max: 100 } },
113
+ query: { cacheTTL: 5000 },
114
+ create: { permissions: ['product:create'] },
115
+ delete: { exclude: true }
116
+ });
117
+ ```
118
+
119
+ ### Policy
120
+
121
+ ```ts
122
+ // src/resources/product/policy.ts
123
+ import { createPolicy } from '@appweaver/core';
124
+
125
+ export default createPolicy({
126
+ modelName: 'Product',
127
+ checkAccess: (action, resource) => resource.status === 'Draft',
128
+ readRestrictions: (action, resource) => {
129
+ enabled: true;
130
+ },
131
+ files: {
132
+ photo: { accessType: 'public' }
133
+ }
134
+ });
135
+ ```
136
+
137
+ ### Auth model and service
138
+
139
+ Use `createAuthModel` and `createAuthService` for authenticatable users. They must be used together.
140
+
141
+ `createAuthModel` adds: `email`, `passwordHash`, `verifiedEmail`, `twoFactorAuth`, `enabled`, `logoutAt` scalars; a
142
+ virtual `password` field; a `roles` relation; and optional `apiKeys` relation.
143
+
144
+ ```ts
145
+ // src/resources/user/model.ts
146
+ import { createAuthModel } from '@appweaver/core';
147
+
148
+ export default createAuthModel({
149
+ name: 'User',
150
+ scalars: { name: { type: 'string', maxLength: 100 } },
151
+ files: { avatar: { mimeType: 'image/(png|jpeg|gif)', maxSize: '2 MB' } }
152
+ });
153
+ ```
154
+
155
+ ```ts
156
+ // src/resources/user/service.ts
157
+ import { createAuthService } from '@appweaver/core';
158
+
159
+ export default createAuthService({
160
+ modelName: 'User',
161
+ registrationData: (_, email, password) => ({ email, password, roles: [1, 2] })
162
+ });
163
+ ```
164
+
165
+ ## Custom routes, models, and plugins
166
+
167
+ ### Custom route
168
+
169
+ ```ts
170
+ // src/features/custom-route.ts
171
+ import { registerRoute, Router } from '@appweaver/core';
172
+ import { Type } from '@sinclair/typebox';
173
+
174
+ registerRoute(
175
+ async function (router: Router) {
176
+ router.get('/search-result', {
177
+ schema: { summary: 'Search result', response: { 200: Type.Ref('SearchResult') } },
178
+ handler: async () => ({ message: 'Hello, world!' })
179
+ });
180
+ },
181
+ { public: true, cacheTTL: 15000 }
182
+ );
183
+ ```
184
+
185
+ ### Custom model
186
+
187
+ ```ts
188
+ // src/features/custom-model.ts
189
+ import { registerModel } from '@appweaver/core';
190
+ import { Type } from '@sinclair/typebox';
191
+
192
+ registerModel(
193
+ Type.Object(
194
+ { id: Type.Number(), title: Type.String(), score: Type.Number({ minimum: 0, maximum: 1 }) },
195
+ { $id: 'SearchResult' }
196
+ )
197
+ );
198
+ ```
199
+
200
+ ### Plugin
201
+
202
+ ```ts
203
+ // src/plugins/audit-log.ts
204
+ import { registerPlugin } from '@appweaver/core';
205
+
206
+ registerPlugin('audit-log', async (server) => {
207
+ server.addHook('onResponse', async (request, reply) => {
208
+ console.log(`${request.method} ${request.url} → ${reply.statusCode}`);
209
+ });
210
+ });
211
+ ```
212
+
213
+ ## Dependency injection
214
+
215
+ ```ts
216
+ import { Cache } from '@appweaver/common';
217
+ import { define, inject, loadProvider } from '@appweaver/core';
218
+
219
+ define(RedisCacheService, Cache); // register class under abstract token
220
+ define('https://api.example.com', 'ApiBaseUrl'); // register plain value
221
+
222
+ const cache = inject(Cache); // resolve singleton
223
+ const url = inject<string>('ApiBaseUrl'); // resolve by string token
224
+
225
+ // Dynamic provider loading (typical in main.ts)
226
+ loadProvider(__dirname, config.CACHE_PROVIDER, Cache);
227
+ loadProvider(__dirname, config.MAILER_PROVIDER, Mailer, false); // optional
228
+ ```
229
+
230
+ ## Seeders
231
+
232
+ Seeder files export async functions and run in alphabetical order. Prefix filenames with ordinal numbers.
233
+
234
+ ```ts
235
+ // database/seeders/001-create-admin-user.ts
236
+ import { hashPassword } from '@appweaver/core';
237
+ import { db } from '@db/client';
238
+
239
+ export async function createAdminUser(): Promise<void> {
240
+ await db.user.create({
241
+ data: {
242
+ firstName: 'Admin',
243
+ lastName: 'Admin',
244
+ email: 'admin@appweaver.com',
245
+ roles: {
246
+ connectOrCreate: [
247
+ {
248
+ where: { name: 'Admin' },
249
+ create: {
250
+ name: 'Admin',
251
+ permissions: {
252
+ connectOrCreate: [
253
+ { where: { name: '*.read' }, create: { name: '*.read' } },
254
+ { where: { name: '*.write' }, create: { name: '*.write' } }
255
+ ]
256
+ }
257
+ }
258
+ }
259
+ ]
260
+ }
261
+ }
262
+ });
263
+ }
264
+ ```
265
+
266
+ ## Common commands
267
+
268
+ | Command | Description |
269
+ |-------------------------------|-------------------------------------------|
270
+ | `npm run generate` | Generate TypeScript types + Prisma schema |
271
+ | `npm run build` | Build the application |
272
+ | `npm run start` | Start in production mode |
273
+ | `npm run dev` | Start in development (watch) mode |
274
+ | `npm run seed` | Seed the database |
275
+ | `npm run migrate` | Apply pending database migrations |
276
+ | `npm run test` | Run unit tests |
277
+ | `npm run e2e` | Run end-to-end tests |
278
+ | `npm run format` | Format code with Prettier |
279
+ | `npm run lint` | Lint code with ESLint |
280
+ | `weaver migration new <name>` | Create a new database migration |
281
+ | `weaver update` | Update all @appweaver/* packages |
282
+ | `weaver openapi` | Generate OpenAPI specification |
283
+
284
+ ## References
285
+
286
+ - Application CLI (weaver): [cli.md](references/cli.md)
287
+ - Application configuration: [configuration.md](references/configuration.md)
288
+ - Application resources: [resources.md](references/resources.md)
289
+ - Dependency injection: [dependency-injection.md](references/dependency-injection.md)
290
+ - Security details: [security.md](references/security.md)
291
+ - Storage & File management: [storage.md](references/storage.md)
292
+ - Database & Migrations: [database.md](references/database.md)
293
+ - Events & Hooks: [events.md](references/events.md)
294
+ - Cache management: [cache.md](references/cache.md)
295
+ - Queue jobs: [queue.md](references/queue.md)
296
+ - Scheduling jobs: [scheduler.md](references/scheduler.md)
297
+ - Sending emails: [mailer.md](references/mailer.md)
298
+ - Generating an HTTP client for using API: [client.md](references/client.md)