@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.
- package/LICENSE +1 -0
- package/README.md +7 -0
- package/create-weaver-app.d.ts +2 -0
- package/create-weaver-app.js +266 -0
- package/package.json +37 -0
- package/skill/GUIDELINES.md +298 -0
- package/skill/SKILL.md +593 -0
- package/skill/references/cache.md +207 -0
- package/skill/references/cli.md +213 -0
- package/skill/references/client.md +507 -0
- package/skill/references/configuration.md +402 -0
- package/skill/references/database.md +134 -0
- package/skill/references/dependency-injection.md +214 -0
- package/skill/references/events.md +152 -0
- package/skill/references/mailer.md +235 -0
- package/skill/references/queue.md +196 -0
- package/skill/references/resources.md +961 -0
- package/skill/references/scheduler.md +184 -0
- package/skill/references/security.md +694 -0
- package/skill/references/storage.md +251 -0
- package/templates/default/.dockerignore +5 -0
- package/templates/default/.env.tpl +1 -0
- package/templates/default/.prettierignore +3 -0
- package/templates/default/.prettierrc +7 -0
- package/templates/default/Dockerfile +56 -0
- package/templates/default/Dockerfile.bun +56 -0
- package/templates/default/README.md.tpl +7 -0
- package/templates/default/appweaver.dev.json.tpl +9 -0
- package/templates/default/appweaver.json.bun.tpl +17 -0
- package/templates/default/appweaver.json.tpl +16 -0
- package/templates/default/appweaver.test.json.tpl +30 -0
- package/templates/default/bunfig.toml.bun +8 -0
- package/templates/default/database/client.ts.tpl +7 -0
- package/templates/default/database/schema.prisma +13 -0
- package/templates/default/database/seeders/001-create-admin-user.ts.tpl +39 -0
- package/templates/default/eslint.config.mjs +55 -0
- package/templates/default/eslint.config.mjs.bun +53 -0
- package/templates/default/jest.config.json.node +23 -0
- package/templates/default/package.json.bun.tpl +39 -0
- package/templates/default/package.json.tpl +44 -0
- package/templates/default/prisma.config.ts.tpl +14 -0
- package/templates/default/public/favicon.ico +0 -0
- package/templates/default/public/robots.txt +2 -0
- package/templates/default/src/features/index.ts.tpl +0 -0
- package/templates/default/src/main.ts.tpl +7 -0
- package/templates/default/src/resources/user/model.ts.tpl +28 -0
- package/templates/default/src/resources/user/policy.ts.tpl +3 -0
- package/templates/default/src/resources/user/routes.ts.tpl +3 -0
- package/templates/default/src/resources/user/service.ts.tpl +16 -0
- package/templates/default/src/types/generated.ts.tpl +1 -0
- package/templates/default/src/types/index.ts.tpl +1 -0
- package/templates/default/start.sh +26 -0
- package/templates/default/start.sh.bun +26 -0
- package/templates/default/swc.config.json.node +13 -0
- package/templates/default/test/e2e/jest.e2e-config.json.node +22 -0
- package/templates/default/test/e2e/main.test.ts.tpl +24 -0
- package/templates/default/test/e2e/support/each.ts.tpl +13 -0
- package/templates/default/test/e2e/support/preload.ts.bun +13 -0
- package/templates/default/test/e2e/support/setup.ts.tpl +13 -0
- package/templates/default/test/e2e/support/teardown.ts.tpl +13 -0
- package/templates/default/test/unit/sample.test.ts.tpl +5 -0
- package/templates/default/tsconfig.build.json +10 -0
- package/templates/default/tsconfig.json +27 -0
- package/templates/default/tsconfig.json.bun +28 -0
|
@@ -0,0 +1,961 @@
|
|
|
1
|
+
# Resources
|
|
2
|
+
|
|
3
|
+
Resources are the core building blocks of an Appweaver application. There are four resource types that form a
|
|
4
|
+
dependency chain: **model** → **service** → **routes** → **policy**. Each resource type is created using a
|
|
5
|
+
corresponding factory function and autoloaded from `src/resources/*/` on application start. Source directory and
|
|
6
|
+
resources pattern could be changed with `APP_SOURCE_PATH` and `RESOURCE_{MODEL,SERVICE,...}_PATTERN` config variables.
|
|
7
|
+
|
|
8
|
+
- A **model** is always required.
|
|
9
|
+
- A **service** requires a model.
|
|
10
|
+
- The **Routes** require a service.
|
|
11
|
+
- A **policy** is optional and independent of the chain.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## createModel
|
|
16
|
+
|
|
17
|
+
Creates a resource model definition. The model defines database fields, relations, files, virtual fields, DTOs for
|
|
18
|
+
CRUD operations, and index configuration. It is used to generate Prisma schema, TypeScript types, and route
|
|
19
|
+
request/response schemas.
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { createModel } from '@appweaver/core';
|
|
23
|
+
|
|
24
|
+
export default createModel({
|
|
25
|
+
name: 'Product',
|
|
26
|
+
// ... configuration
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Configuration
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
function createModel(config: ResourceModelConfig, override ?: Partial<ResourceModelConfig>) {
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
| Property | Type | Required | Default | Description |
|
|
38
|
+
|------------------|--------------------------------|----------|-----------------------|---------------------------------------------------------------------|
|
|
39
|
+
| `name` | string | yes | - | Model name (PascalCase). Used as database table name and type name. |
|
|
40
|
+
| `tableName` | string | no | (model name) | Custom database table name override. |
|
|
41
|
+
| `generateTypes` | boolean | no | `true` | Generate TypeScript types for this model. |
|
|
42
|
+
| `generateSchema` | boolean | no | `true` | Generate Prisma schema for this model. |
|
|
43
|
+
| `id` | IdField | no | Autoincrement integer | ID field configuration. |
|
|
44
|
+
| `audit` | AuditFields | no | All included | Audit timestamps and creator tracking fields. |
|
|
45
|
+
| `scalars` | Record\<string, ScalarField> | no | - | Scalar fields (database columns). |
|
|
46
|
+
| `relations` | Record\<string, RelationField> | no | - | Relations to other models. |
|
|
47
|
+
| `files` | Record\<string, FileField> | no | - | File upload fields. |
|
|
48
|
+
| `virtual` | Record\<string, VirtualField> | no | - | Computed/virtual fields not stored in database. |
|
|
49
|
+
| `read` | OperationConfig | no | - | Pick/omit fields for the read DTO. |
|
|
50
|
+
| `create` | OperationConfig | no | - | Pick/omit fields for the create DTO. |
|
|
51
|
+
| `update` | OperationConfig | no | - | Pick/omit fields for the update DTO. |
|
|
52
|
+
| `export` | Record\<string, ExportField> | no | - | CSV export field configuration. |
|
|
53
|
+
| `index` | string[] \| string[][] | no | - | Database index definitions. |
|
|
54
|
+
|
|
55
|
+
### ID field
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const config = {
|
|
59
|
+
// Integer ID with autoincrement (default)
|
|
60
|
+
id: {
|
|
61
|
+
type: 'int',
|
|
62
|
+
generator: 'autoincrement()'
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// String ID with generator
|
|
66
|
+
id: {
|
|
67
|
+
type: 'string',
|
|
68
|
+
generator: 'uuid()'
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
| Property | Type | Default | Description |
|
|
74
|
+
|-------------|-------------------------------------------------------------------------------|---------------------|-------------------------------------------------------------------------------|
|
|
75
|
+
| `type` | `'string'` \| `'int'` \| `'bigInt'` | `'int'` | ID field data type. |
|
|
76
|
+
| `generator` | `'uuid()'` \| `'uuid(7)'` \| `'cuid()'` \| `'cuid(2)'` \| `'autoincrement()'` | `'autoincrement()'` | Value generator. String types use UUID/CUID, integer types use autoincrement. |
|
|
77
|
+
|
|
78
|
+
### Audit fields
|
|
79
|
+
|
|
80
|
+
It is recommended to always use all audit fields for all resource models, unless specified otherwise. In the usual
|
|
81
|
+
scenario audit should be left out (including all fields by default).
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
const config = {
|
|
85
|
+
// By default all audit fields are included
|
|
86
|
+
audit: {
|
|
87
|
+
createdAt: true,
|
|
88
|
+
updatedAt: true,
|
|
89
|
+
createdById: true
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
| Property | Type | Default | Description |
|
|
95
|
+
|---------------|---------|---------|-------------------------------------------------|
|
|
96
|
+
| `createdAt` | boolean | `true` | Add `createdAt` timestamp field. |
|
|
97
|
+
| `updatedAt` | boolean | `true` | Add `updatedAt` timestamp field. |
|
|
98
|
+
| `createdById` | boolean | `true` | Add `createdById` foreign key to the auth user. |
|
|
99
|
+
|
|
100
|
+
### Scalar field types
|
|
101
|
+
|
|
102
|
+
All scalar fields share these common properties:
|
|
103
|
+
|
|
104
|
+
| Property | Type | Default | Description |
|
|
105
|
+
|---------------------|-----------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------|
|
|
106
|
+
| `required` | boolean | `true` | Whether the field is required. |
|
|
107
|
+
| `unique` | boolean | `false` | Add a unique constraint. |
|
|
108
|
+
| `hidden` | boolean | `false` | Hide from API output (e.g. password hashes). |
|
|
109
|
+
| `default` | varies | - | Default static value. |
|
|
110
|
+
| `defaultGenerator` | string | - | Default is generated by function (e.g. uuid(), cuid(), autoincrement(), now(), ...). |
|
|
111
|
+
| `defaultExpression` | string | - | Default is generated by database expression in supported database syntax (e.g. concat('token_', gen_random_uuid()))::TEXT). |
|
|
112
|
+
| `array` | boolean | `false` | Store as array (supported on string, int, float). |
|
|
113
|
+
| `example` | string \| number \| boolean | - | Example value for OpenAPI (Swagger) schema documentation. |
|
|
114
|
+
|
|
115
|
+
#### String
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const config = {
|
|
119
|
+
title: {
|
|
120
|
+
type: 'string',
|
|
121
|
+
minLength: 1,
|
|
122
|
+
maxLength: 200,
|
|
123
|
+
default: 'No title'
|
|
124
|
+
},
|
|
125
|
+
email: {
|
|
126
|
+
type: 'string',
|
|
127
|
+
format: 'email'
|
|
128
|
+
},
|
|
129
|
+
slug: {
|
|
130
|
+
type: 'string',
|
|
131
|
+
pattern: '^[a-z0-9-]+$'
|
|
132
|
+
},
|
|
133
|
+
code: {
|
|
134
|
+
type: 'string',
|
|
135
|
+
defaultGenerator: 'uuid()'
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
| Property | Type | Description |
|
|
141
|
+
|-------------|---------------------------------------------------------------------------------------|--------------------------------------|
|
|
142
|
+
| `type` | `'string'` | String field type. |
|
|
143
|
+
| `minLength` | number | Minimum string length. |
|
|
144
|
+
| `maxLength` | number | Maximum string length. |
|
|
145
|
+
| `format` | `'email'` \| `'hostname'` \| `'ipv4'` \| `'ipv6'` \| `'uri'` \| `'uuid'` \| `'regex'` | Built-in format validation. |
|
|
146
|
+
| `pattern` | string | Custom regex pattern for validation. |
|
|
147
|
+
|
|
148
|
+
String defaults can also be ID generators: `'uuid()'`, `'uuid(7)'`, `'cuid()'`, `'cuid(2)'`.
|
|
149
|
+
|
|
150
|
+
#### Number (int, bigInt, float)
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
const config = {
|
|
154
|
+
price: {
|
|
155
|
+
type: 'float',
|
|
156
|
+
minimum: 0
|
|
157
|
+
},
|
|
158
|
+
quantity: {
|
|
159
|
+
type: 'int',
|
|
160
|
+
minimum: 0,
|
|
161
|
+
maximum: 10000
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
| Property | Type | Description |
|
|
167
|
+
|-----------|------------------------------------|--------------------|
|
|
168
|
+
| `type` | `'int'` \| `'bigInt'` \| `'float'` | Number field type. |
|
|
169
|
+
| `minimum` | number | Minimum value. |
|
|
170
|
+
| `maximum` | number | Maximum value. |
|
|
171
|
+
|
|
172
|
+
Integer defaults can be `'autoincrement()'`.
|
|
173
|
+
|
|
174
|
+
#### Boolean
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
const config = {
|
|
178
|
+
enabled: {
|
|
179
|
+
type: 'boolean',
|
|
180
|
+
default: true
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
| Property | Type | Description |
|
|
186
|
+
|----------|-------------|---------------------|
|
|
187
|
+
| `type` | `'boolean'` | Boolean field type. |
|
|
188
|
+
|
|
189
|
+
#### DateTime
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
const config = {
|
|
193
|
+
publishedAt: {
|
|
194
|
+
type: 'dateTime',
|
|
195
|
+
defaultGenerator: 'now()'
|
|
196
|
+
},
|
|
197
|
+
eventDate: {
|
|
198
|
+
type: 'dateTime',
|
|
199
|
+
format: 'date'
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
| Property | Type | Description |
|
|
205
|
+
|----------|---------------------------------------|----------------------|
|
|
206
|
+
| `type` | `'dateTime'` | DateTime field type. |
|
|
207
|
+
| `format` | `'date-time'` \| `'time'` \| `'date'` | DateTime format. |
|
|
208
|
+
|
|
209
|
+
Default can be `'now()'` for current timestamp.
|
|
210
|
+
|
|
211
|
+
#### JSON
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
const config = {
|
|
215
|
+
metadata: {
|
|
216
|
+
type: 'json',
|
|
217
|
+
default: {}
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
| Property | Type | Description |
|
|
223
|
+
|----------|----------|------------------------------------------------------|
|
|
224
|
+
| `type` | `'json'` | JSON field type. Stores arbitrary objects or arrays. |
|
|
225
|
+
|
|
226
|
+
#### Enum
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
const config = {
|
|
230
|
+
status: {
|
|
231
|
+
type: 'enum',
|
|
232
|
+
values: ['Draft', 'Active', 'Sold'],
|
|
233
|
+
default: 'Draft'
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
| Property | Type | Description |
|
|
239
|
+
|----------|----------|---------------------------------|
|
|
240
|
+
| `type` | `'enum'` | Enum field type. |
|
|
241
|
+
| `values` | string[] | Allowed enum values (required). |
|
|
242
|
+
|
|
243
|
+
### Relations
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
// src/resources/product/model.ts
|
|
247
|
+
const config = {
|
|
248
|
+
relations: {
|
|
249
|
+
category: {
|
|
250
|
+
model: 'Category',
|
|
251
|
+
mappedBy: 'products',
|
|
252
|
+
owner: true,
|
|
253
|
+
output: {
|
|
254
|
+
type: 'always'
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
reviews: {
|
|
258
|
+
model: 'Review',
|
|
259
|
+
mappedBy: 'product',
|
|
260
|
+
array: true,
|
|
261
|
+
output: {
|
|
262
|
+
type: 'single',
|
|
263
|
+
count: true
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
// src/resources/category/model.ts
|
|
272
|
+
const config = {
|
|
273
|
+
relations: {
|
|
274
|
+
products: {
|
|
275
|
+
model: 'Product',
|
|
276
|
+
mappedBy: 'category',
|
|
277
|
+
array: true,
|
|
278
|
+
output: {
|
|
279
|
+
type: 'single'
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
// src/resources/review/model.ts
|
|
288
|
+
const config = {
|
|
289
|
+
relations: {
|
|
290
|
+
product: {
|
|
291
|
+
model: 'Product',
|
|
292
|
+
mappedBy: 'reviews',
|
|
293
|
+
owner: true,
|
|
294
|
+
input: {
|
|
295
|
+
type: 'none'
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
| Property | Type | Default | Description |
|
|
303
|
+
|---------------------|-------------------|--------------|--------------------------------------------------------------------------|
|
|
304
|
+
| `model` | string | **required** | Target model name. |
|
|
305
|
+
| `owner` | boolean | `false` | This side owns the foreign key (only one side should be owner). |
|
|
306
|
+
| `mappedBy` | string | - | Name of the inverse relation on the target model. |
|
|
307
|
+
| `array` | boolean | `false` | One-to-many value or many-to-many relation. |
|
|
308
|
+
| `unique` | boolean | `false` | One-to-one relation (unique foreign key). |
|
|
309
|
+
| `required` | boolean | `true` | Whether the relation is required (nullable foreign key if not required). |
|
|
310
|
+
| `minItems` | number | - | Minimum items for array relations. |
|
|
311
|
+
| `createIfNotExists` | boolean | `false` | Auto-create related record if it doesn't exist. |
|
|
312
|
+
| `orphanRemoval` | boolean | `false` | Delete orphaned records when parent is deleted. |
|
|
313
|
+
| `onDelete` | ReferentialAction | - | Foreign key action on delete. |
|
|
314
|
+
| `onUpdate` | ReferentialAction | - | Foreign key action on update. |
|
|
315
|
+
| `input` | RelationInput | - | Input DTO configuration. |
|
|
316
|
+
| `output` | RelationOutput | - | Output DTO configuration. |
|
|
317
|
+
|
|
318
|
+
**ReferentialAction values**: `'cascade'`, `'restrict'`, `'noAction'`, `'setNull'`, `'setDefault'`
|
|
319
|
+
|
|
320
|
+
#### Relationship types
|
|
321
|
+
|
|
322
|
+
The combination of `owner`, `array`, and `unique` properties determines the relationship type:
|
|
323
|
+
|
|
324
|
+
**One-to-One**: One side has `owner: true` and `unique: true`, the other side has `owner: false` (default) and
|
|
325
|
+
`unique: false` (default).
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
// User model
|
|
329
|
+
const config = {
|
|
330
|
+
relations: {
|
|
331
|
+
profile: {
|
|
332
|
+
model: 'Profile',
|
|
333
|
+
mappedBy: 'user',
|
|
334
|
+
owner: true,
|
|
335
|
+
unique: true,
|
|
336
|
+
required: false // otherwise the Profile DTO must be sent when creating the user resource
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
// Profile model
|
|
344
|
+
const config = {
|
|
345
|
+
relations: {
|
|
346
|
+
user: {
|
|
347
|
+
model: 'User',
|
|
348
|
+
mappedBy: 'profile'
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
**One-to-Many**: The "one" side has `owner: false` (default) and `array: true`, the "many" side has `owner: true` and
|
|
355
|
+
`array: false` (default).
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
// Category model (one)
|
|
359
|
+
const config = {
|
|
360
|
+
relations: {
|
|
361
|
+
products: {
|
|
362
|
+
model: 'Product',
|
|
363
|
+
mappedBy: 'category',
|
|
364
|
+
array: true
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
// Product model (many)
|
|
372
|
+
const config = {
|
|
373
|
+
relations: {
|
|
374
|
+
category: {
|
|
375
|
+
model: 'Category',
|
|
376
|
+
mappedBy: 'products',
|
|
377
|
+
owner: true
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**Many-to-Many**: Both sides have `array: true`. Only one side should have `owner: true` (determines which creates the
|
|
384
|
+
join table).
|
|
385
|
+
|
|
386
|
+
```ts
|
|
387
|
+
// Post model
|
|
388
|
+
const config = {
|
|
389
|
+
relations: {
|
|
390
|
+
tags: {
|
|
391
|
+
model: 'Tag',
|
|
392
|
+
mappedBy: 'posts',
|
|
393
|
+
owner: true,
|
|
394
|
+
array: true
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
```ts
|
|
401
|
+
// Tag model
|
|
402
|
+
const config = {
|
|
403
|
+
relations: {
|
|
404
|
+
posts: {
|
|
405
|
+
model: 'Post',
|
|
406
|
+
mappedBy: 'tags',
|
|
407
|
+
array: true
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
#### Relation input
|
|
414
|
+
|
|
415
|
+
| Property | Type | Description |
|
|
416
|
+
|-------------|-------------------------------------------------|---------------------------------------------------|
|
|
417
|
+
| `type` | `'all'` \| `'create'` \| `'update'` \| `'none'` | When the relation field is available as input. |
|
|
418
|
+
| `uniqueKey` | string | Use a unique key field instead of ID for linking. |
|
|
419
|
+
| `fullModel` | boolean | Accept full nested model object as input. |
|
|
420
|
+
|
|
421
|
+
#### Relation output
|
|
422
|
+
|
|
423
|
+
| Property | Type | Description |
|
|
424
|
+
|-----------|------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------|
|
|
425
|
+
| `type` | `'always'` \| `'single'` \| `'multiple'` \| `'none'` | When to include the relation in output. `always` = all reads, `single` = single record reads, `multiple` = list reads, `none` = never. |
|
|
426
|
+
| `include` | Record\<string, RelationOutput> | Nested relation output configuration. |
|
|
427
|
+
| `count` | boolean | Include a count of related records. |
|
|
428
|
+
|
|
429
|
+
### File fields
|
|
430
|
+
|
|
431
|
+
```ts
|
|
432
|
+
const config = {
|
|
433
|
+
files: {
|
|
434
|
+
photo: {
|
|
435
|
+
mimeType: 'image/*',
|
|
436
|
+
namePattern: 'photos/{userId}-{name}-{hash}.{extension}',
|
|
437
|
+
maxSize: '2 MB'
|
|
438
|
+
},
|
|
439
|
+
documents: {
|
|
440
|
+
mimeType: 'application/pdf',
|
|
441
|
+
array: true,
|
|
442
|
+
maxCount: 5
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
| Property | Type | Description |
|
|
449
|
+
|---------------|--------------------|-------------------------------------------------------------------------|
|
|
450
|
+
| `mimeType` | string \| RegExp | Allowed MIME types (glob patterns like `'image/*'` supported). |
|
|
451
|
+
| `namePattern` | string \| function | File naming pattern or function (available variables are listed below). |
|
|
452
|
+
| `array` | boolean | Allow multiple files. |
|
|
453
|
+
| `maxSize` | number \| string | Maximum file size (e.g. `'2 MB'`, `5242880`). |
|
|
454
|
+
| `maxCount` | number | Maximum number of files (for array fields). |
|
|
455
|
+
| `output` | RelationOutput | When to include file info in output. |
|
|
456
|
+
|
|
457
|
+
#### Available namePattern variables
|
|
458
|
+
|
|
459
|
+
Default pattern is: `{name}-{hash}.{extension}`.
|
|
460
|
+
|
|
461
|
+
| Variable | Type | Description |
|
|
462
|
+
|-----------------|--------|---------------------------------------------|
|
|
463
|
+
| `name` | string | Original filename without extension. |
|
|
464
|
+
| `extension` | string | Original file extension. |
|
|
465
|
+
| `resourceField` | string | Field name the file is assigned to. |
|
|
466
|
+
| `resourceName` | string | Resource model name. |
|
|
467
|
+
| `resourceId` | string | Resource ID. |
|
|
468
|
+
| `userId` | string | Authenticated user ID. |
|
|
469
|
+
| `userEmail` | string | Authenticated user email. |
|
|
470
|
+
| `year` | number | Current UTC year. |
|
|
471
|
+
| `month` | number | Current UTC month (1-12). |
|
|
472
|
+
| `day` | number | Current UTC day of month. |
|
|
473
|
+
| `weekDay` | number | Current UTC day of week (0-6, Sunday is 0). |
|
|
474
|
+
| `yearWeek` | number | ISO week number. |
|
|
475
|
+
| `yearDay` | number | Day of year (1-366). |
|
|
476
|
+
| `hours` | number | Current UTC hours. |
|
|
477
|
+
| `minutes` | number | Current UTC minutes. |
|
|
478
|
+
| `seconds` | number | Current UTC seconds. |
|
|
479
|
+
| `milliseconds` | number | Current UTC milliseconds. |
|
|
480
|
+
| `timestamp` | number | Unix timestamp in milliseconds. |
|
|
481
|
+
| `date` | string | Current date in ISO 8601 format. |
|
|
482
|
+
| `uuid` | string | Generated random UUID. |
|
|
483
|
+
| `hash` | string | Generated random hash (32 bytes). |
|
|
484
|
+
|
|
485
|
+
### Virtual fields
|
|
486
|
+
|
|
487
|
+
Virtual fields are computed values not stored in the database. They can appear in input DTOs (to receive data) and/or
|
|
488
|
+
output DTOs (to return computed values).
|
|
489
|
+
|
|
490
|
+
```ts
|
|
491
|
+
const config = {
|
|
492
|
+
virtual: {
|
|
493
|
+
displayName: {
|
|
494
|
+
type: 'string',
|
|
495
|
+
output: {
|
|
496
|
+
type: 'always',
|
|
497
|
+
value: (resource) => `${resource.firstName} ${resource.lastName}`
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
inviteCode: {
|
|
501
|
+
type: 'string',
|
|
502
|
+
input: {
|
|
503
|
+
type: 'create'
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
| Property | Type | Description |
|
|
511
|
+
|------------------|------------------------------------------------------|------------------------------------------------------------|
|
|
512
|
+
| *(scalar props)* | - | All scalar field properties (type, minLength, etc.) apply. |
|
|
513
|
+
| `input.type` | `'all'` \| `'create'` \| `'update'` \| `'none'` | When the virtual field accepts input. |
|
|
514
|
+
| `input.value` | primitive \| function | Default value or transformer for input. |
|
|
515
|
+
| `output.type` | `'always'` \| `'single'` \| `'multiple'` \| `'none'` | When the virtual field appears in output. |
|
|
516
|
+
| `output.value` | primitive \| function | Computed value or transformer for output. |
|
|
517
|
+
|
|
518
|
+
### Operation config (read, create, update)
|
|
519
|
+
|
|
520
|
+
Control which fields appear in each DTO. Use `pick` for an allowlist or `omit` for a deny-list.
|
|
521
|
+
|
|
522
|
+
```ts
|
|
523
|
+
const config = {
|
|
524
|
+
create: {
|
|
525
|
+
omit: ['status'] // All fields except status
|
|
526
|
+
},
|
|
527
|
+
update: {
|
|
528
|
+
pick: ['title', 'price'] // Only title and price
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
| Property | Type | Description |
|
|
534
|
+
|----------|----------|------------------------------------------------|
|
|
535
|
+
| `omit` | string[] | Fields to exclude from the DTO. |
|
|
536
|
+
| `pick` | string[] | Fields to include in the DTO (overrides omit). |
|
|
537
|
+
|
|
538
|
+
### Export config
|
|
539
|
+
|
|
540
|
+
Configure CSV export behavior per field:
|
|
541
|
+
|
|
542
|
+
```ts
|
|
543
|
+
const config = {
|
|
544
|
+
export: {
|
|
545
|
+
price: {
|
|
546
|
+
headerName: 'Product Price',
|
|
547
|
+
mapValue: 'price'
|
|
548
|
+
},
|
|
549
|
+
passwordHash: {
|
|
550
|
+
exclude: true
|
|
551
|
+
},
|
|
552
|
+
status: {
|
|
553
|
+
mapValue: (val) => val.toUpperCase()
|
|
554
|
+
},
|
|
555
|
+
author: {
|
|
556
|
+
firstName: {
|
|
557
|
+
headerName: 'Given Name'
|
|
558
|
+
},
|
|
559
|
+
lastName: {
|
|
560
|
+
headerName: 'Family Name'
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
| Property | Type | Description |
|
|
568
|
+
|--------------|--------------------|------------------------------------|
|
|
569
|
+
| `headerName` | string | Custom CSV column header name. |
|
|
570
|
+
| `exclude` | boolean | Exclude this field from exports. |
|
|
571
|
+
| `mapValue` | string \| function | Transform the value during export. |
|
|
572
|
+
|
|
573
|
+
### Index config
|
|
574
|
+
|
|
575
|
+
Define database indexes as a flat array (single-field indexes) or nested arrays (composite indexes):
|
|
576
|
+
|
|
577
|
+
```ts
|
|
578
|
+
index: ['title'] // Single-field index on title
|
|
579
|
+
index: [['status', 'categoryId']] // Composite index on status + categoryId
|
|
580
|
+
index: ['email', ['status', 'createdAt']] // Both single and composite
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### Generated models
|
|
584
|
+
|
|
585
|
+
`createModel` produces the following TypeBox schema models used internally by routes and services:
|
|
586
|
+
|
|
587
|
+
| Model | Purpose |
|
|
588
|
+
|-------------------|------------------------------------|
|
|
589
|
+
| `readModel` | Full model with all visible fields |
|
|
590
|
+
| `createModel` | Request body for create operations |
|
|
591
|
+
| `updateModel` | Request body for update operations |
|
|
592
|
+
| `relationsModel` | Relations-only subset |
|
|
593
|
+
| `virtualModel` | Virtual fields-only subset |
|
|
594
|
+
| `filesModel` | File fields-only subset |
|
|
595
|
+
| `readOneModel` | Response for single-item reads |
|
|
596
|
+
| `readManyModel` | Response for list reads |
|
|
597
|
+
| `createOneModel` | Request for create endpoint |
|
|
598
|
+
| `updateOneModel` | Request for update endpoint |
|
|
599
|
+
| `fileUploadModel` | Request for file upload endpoint |
|
|
600
|
+
| `fileDeleteModel` | Request for file delete endpoint |
|
|
601
|
+
|
|
602
|
+
---
|
|
603
|
+
|
|
604
|
+
## createService
|
|
605
|
+
|
|
606
|
+
Creates a resource service with lifecycle hooks and business logic. The service handles all database operations for a
|
|
607
|
+
model and triggers hooks on each CRUD operation before/after.
|
|
608
|
+
|
|
609
|
+
```ts
|
|
610
|
+
import { createService } from '@appweaver/core';
|
|
611
|
+
|
|
612
|
+
export default createService({
|
|
613
|
+
modelName: 'Product',
|
|
614
|
+
afterCreate: (resource) => {
|
|
615
|
+
logger.info(`Product created: ${resource.id}`);
|
|
616
|
+
},
|
|
617
|
+
textSearch: {
|
|
618
|
+
title: { contains: '{input}', mode: 'insensitive' }
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### Configuration
|
|
624
|
+
|
|
625
|
+
```ts
|
|
626
|
+
function createService(config: ResourceServiceConfig, override ?: Partial<ResourceServiceConfig>) {
|
|
627
|
+
}
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
| Property | Type | Description |
|
|
631
|
+
|-------------------|--------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------|
|
|
632
|
+
| `modelName` | string | Model name to bind this service to (required). |
|
|
633
|
+
| `beforeFind` | `(id) => void` | Hook called before finding a single resource. |
|
|
634
|
+
| `beforeQuery` | `(filter, page, size, sort) => void` | Hook called before querying resources. |
|
|
635
|
+
| `beforeAggregate` | `(filter, select, dateField, from?, to?, step?, safeIncrement?) => void` | Hook called before aggregation. |
|
|
636
|
+
| `beforeCreate` | `(data) => void` | Hook called before creating a resource. Mutate `data` to modify input. |
|
|
637
|
+
| `beforeUpdate` | `(id, data) => void` | Hook called before updating a resource. |
|
|
638
|
+
| `beforeDelete` | `(id) => void` | Hook called before deleting a resource. |
|
|
639
|
+
| `afterFind` | `(resource) => void` | Hook called after finding a resource. |
|
|
640
|
+
| `afterQuery` | `(response) => void` | Hook called after querying resources. |
|
|
641
|
+
| `afterAggregate` | `(response) => void` | Hook called after aggregation. |
|
|
642
|
+
| `afterCreate` | `(resource) => void` | Hook called after creating a resource. |
|
|
643
|
+
| `afterUpdate` | `(resource) => void` | Hook called after updating a resource. |
|
|
644
|
+
| `afterDelete` | `(resource) => void` | Hook called after deleting a resource. |
|
|
645
|
+
| `textSearch` | object \| function | Prisma filter object or function `(input: string) => filter` for text search. Use `'{input}'` as placeholder in filter objects. |
|
|
646
|
+
|
|
647
|
+
All hooks can be synchronous or return a `Promise`.
|
|
648
|
+
|
|
649
|
+
### Service methods
|
|
650
|
+
|
|
651
|
+
The created service exposes the following methods:
|
|
652
|
+
|
|
653
|
+
| Method | Signature | Description |
|
|
654
|
+
|-------------|---------------------------------------------------------------------------------------------------|----------------------------------------------------------|
|
|
655
|
+
| `find` | `(id) => Promise<ReadOne>` | Find a single resource by ID. |
|
|
656
|
+
| `query` | `(filter?, page?, size?, sort?) => Promise<QueryResponse>` | Query resources with filtering, pagination, and sorting. |
|
|
657
|
+
| `aggregate` | `(filter?, select?, dateField?, from?, to?, step?, safeIncrement?) => Promise<AggregateResponse>` | Aggregate resources with time-series grouping. |
|
|
658
|
+
| `create` | `(data) => Promise<ReadOne>` | Create a new resource. |
|
|
659
|
+
| `update` | `(id, data) => Promise<ReadOne>` | Update an existing resource. |
|
|
660
|
+
| `delete` | `(id) => Promise<ReadOne>` | Delete a resource. |
|
|
661
|
+
|
|
662
|
+
### Query response
|
|
663
|
+
|
|
664
|
+
```ts
|
|
665
|
+
const config = {
|
|
666
|
+
resultCount: 123, // Items in this page
|
|
667
|
+
totalCount: 123, // Total items matching filter
|
|
668
|
+
items: [] // Page data
|
|
669
|
+
};
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
### Aggregate response
|
|
673
|
+
|
|
674
|
+
```ts
|
|
675
|
+
const resp = {
|
|
676
|
+
total: AggregateValue, // Overall aggregation
|
|
677
|
+
items: Arrray<AggregateResult> // Per-period results
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
// Each AggregateResult:
|
|
681
|
+
const result = {
|
|
682
|
+
date: 'Date',
|
|
683
|
+
result: {
|
|
684
|
+
[field]:
|
|
685
|
+
{
|
|
686
|
+
count: 123,
|
|
687
|
+
min: 123,
|
|
688
|
+
max: 123,
|
|
689
|
+
avg: 123,
|
|
690
|
+
sum: 123,
|
|
691
|
+
first: 123,
|
|
692
|
+
last: 123
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
### Text search example
|
|
699
|
+
|
|
700
|
+
Object form with placeholder:
|
|
701
|
+
|
|
702
|
+
```ts
|
|
703
|
+
const config = {
|
|
704
|
+
textSearch: {
|
|
705
|
+
title: {
|
|
706
|
+
contains: '{input}', mode:
|
|
707
|
+
'insensitive'
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
Function form for complex queries:
|
|
714
|
+
|
|
715
|
+
```ts
|
|
716
|
+
const config = {
|
|
717
|
+
textSearch: (input) => ({
|
|
718
|
+
OR: [
|
|
719
|
+
{ title: { contains: input, mode: 'insensitive' } },
|
|
720
|
+
{ description: { contains: input, mode: 'insensitive' } }
|
|
721
|
+
]
|
|
722
|
+
})
|
|
723
|
+
};
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
---
|
|
727
|
+
|
|
728
|
+
## createRoutes
|
|
729
|
+
|
|
730
|
+
Creates CRUD route definitions for a resource. Routes are automatically registered with Fastify and derive their
|
|
731
|
+
request/response schemas from the resource model.
|
|
732
|
+
|
|
733
|
+
```ts
|
|
734
|
+
import { createRoutes } from '@appweaver/core';
|
|
735
|
+
|
|
736
|
+
export default createRoutes({
|
|
737
|
+
modelName: 'Product',
|
|
738
|
+
path: '/products',
|
|
739
|
+
find: { roles: ['Admin', 'User'], rateLimit: { max: 100 } },
|
|
740
|
+
query: { cache: true, cacheTTL: 5000 },
|
|
741
|
+
create: { permissions: ['product:create'] },
|
|
742
|
+
delete: { exclude: true }
|
|
743
|
+
});
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
### Configuration
|
|
747
|
+
|
|
748
|
+
```ts
|
|
749
|
+
function createRoutes(config: ResourceRoutesConfig, override ?: Partial<ResourceRoutesConfig>) {
|
|
750
|
+
}
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
| Property | Type | Description |
|
|
754
|
+
|--------------|-----------------|----------------------------------------------------------|
|
|
755
|
+
| `modelName` | string | Model name to bind routes to (required). |
|
|
756
|
+
| `path` | string | Custom base URL path (default: derived from model name). |
|
|
757
|
+
| `find` | ReadRouteConfig | `GET /:id` - Find single resource by ID. |
|
|
758
|
+
| `query` | ReadRouteConfig | `POST /query` - Query resources with filters. |
|
|
759
|
+
| `aggregate` | ReadRouteConfig | `POST /aggregate` - Aggregate resources. |
|
|
760
|
+
| `create` | RouteConfig | `POST /` - Create a new resource. |
|
|
761
|
+
| `update` | RouteConfig | `PUT /:id` - Update a resource. |
|
|
762
|
+
| `delete` | RouteConfig | `DELETE /:id` - Delete a resource. |
|
|
763
|
+
| `export` | RouteConfig | `POST /export` - Export resources to CSV. |
|
|
764
|
+
| `fileUpload` | RouteConfig | `POST /:id/files` - Upload files to a resource. |
|
|
765
|
+
| `fileDelete` | RouteConfig | `POST /:id/delete-files` - Delete files from a resource. |
|
|
766
|
+
|
|
767
|
+
### Route config (all operations)
|
|
768
|
+
|
|
769
|
+
| Property | Type | Default | Description |
|
|
770
|
+
|-------------------|--------------------------|---------|---------------------------------------------------------------|
|
|
771
|
+
| `exclude` | boolean | `false` | Exclude this operation entirely. |
|
|
772
|
+
| `public` | boolean | `false` | No authentication required. |
|
|
773
|
+
| `roles` | string[] | - | Required roles (OR logic by default). |
|
|
774
|
+
| `permissions` | string[] | - | Required permissions (OR logic by default). |
|
|
775
|
+
| `auth` | AuthType[] | - | Allowed authentication types: `'jwt'`, `'apiKey'`, `'basic'`. |
|
|
776
|
+
| `rateLimit` | RateLimitConfig \| false | - | Per-operation rate limiting. `false` disables. |
|
|
777
|
+
| `recaptcha` | boolean | `false` | Require reCAPTCHA verification. |
|
|
778
|
+
| `recaptchaAction` | string | - | Expected reCAPTCHA action name for score validation. |
|
|
779
|
+
|
|
780
|
+
### Read route config (find, query, aggregate)
|
|
781
|
+
|
|
782
|
+
Extends RouteConfig with caching options:
|
|
783
|
+
|
|
784
|
+
| Property | Type | Default | Description |
|
|
785
|
+
|-------------------------|--------------------|---------|----------------------------------------------------------------|
|
|
786
|
+
| `cache` | boolean | `false` | Enable response caching. |
|
|
787
|
+
| `cacheKey` | string \| function | - | Custom cache key. Function signature: `(req, user) => string`. |
|
|
788
|
+
| `cacheTTL` | number | - | Cache TTL in milliseconds (overrides global default). |
|
|
789
|
+
| `cacheSkipInvalidation` | boolean | `false` | Skip automatic cache invalidation on writes. |
|
|
790
|
+
|
|
791
|
+
### Rate limit config
|
|
792
|
+
|
|
793
|
+
```ts
|
|
794
|
+
const config = {
|
|
795
|
+
rateLimit: {
|
|
796
|
+
max: 100,
|
|
797
|
+
timeWindow: 60000,
|
|
798
|
+
allowList: ['127.0.0.1'],
|
|
799
|
+
keyGenerator: (req) => req.ip
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
| Property | Type | Description |
|
|
805
|
+
|----------------|------------------------------|---------------------------------------------------------------------|
|
|
806
|
+
| `max` | number \| function | Maximum requests per time window. Function: `(req, key) => number`. |
|
|
807
|
+
| `timeWindow` | number \| string \| function | Window duration in ms. Function: `(req, key) => number`. |
|
|
808
|
+
| `allowList` | string[] \| function | IPs exempt from limiting. Function: `(req, key) => boolean`. |
|
|
809
|
+
| `keyGenerator` | function | Custom key generator. Signature: `(req) => string \| number`. |
|
|
810
|
+
|
|
811
|
+
---
|
|
812
|
+
|
|
813
|
+
## createPolicy
|
|
814
|
+
|
|
815
|
+
Creates row-level security policies for a resource. The service layer evaluates the policy on every CRUD operation to
|
|
816
|
+
enforce fine-grained authorization beyond static role/permission checks.
|
|
817
|
+
|
|
818
|
+
```ts
|
|
819
|
+
import { createPolicy } from '@appweaver/core';
|
|
820
|
+
|
|
821
|
+
export default createPolicy({
|
|
822
|
+
modelName: 'Product',
|
|
823
|
+
checkAccess: (action, resource) => resource.status === 'Draft',
|
|
824
|
+
readRestrictions: (action, resource) => ({
|
|
825
|
+
enabled: true
|
|
826
|
+
}),
|
|
827
|
+
files: {
|
|
828
|
+
photo: { accessType: 'public' }
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
### Configuration
|
|
834
|
+
|
|
835
|
+
```ts
|
|
836
|
+
function createPolicy(config: ResourcePolicyConfig, override ?: Partial<ResourcePolicyConfig>) {
|
|
837
|
+
}
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
| Property | Type | Description |
|
|
841
|
+
|---------------------|---------------------------------|---------------------------------------------------------------------------------------------------------------------------|
|
|
842
|
+
| `modelName` | string | Model name to bind this policy to (required). |
|
|
843
|
+
| `checkAccess` | `(action, resource) => boolean` | Dynamic access check against a resource instance. Return `true` to allow, `false` to deny. |
|
|
844
|
+
| `readRestrictions` | `(action, resource) => filter` | Returns a Prisma filter object applied to all read queries (find, query, aggregate). Restricts which records are visible. |
|
|
845
|
+
| `writeRestrictions` | `(action, resource) => data` | Returns data to merge or validate on create/update operations. |
|
|
846
|
+
| `files` | Record\<string, FilePolicy> | Per-file field access policy. |
|
|
847
|
+
|
|
848
|
+
**Action types**: `'find'`, `'query'`, `'aggregate'`, `'create'`, `'update'`, `'delete'`
|
|
849
|
+
|
|
850
|
+
### File policy
|
|
851
|
+
|
|
852
|
+
| Property | Type | Default | Description |
|
|
853
|
+
|--------------|--------------------------------------------|---------------|--------------------------------------------------------------------------------------------------|
|
|
854
|
+
| `accessType` | `'public'` \| `'protected'` \| `'private'` | `'protected'` | File access level. `public` = anyone, `protected` = authenticated users, `private` = owner only. |
|
|
855
|
+
| `canAccess` | `(user, resource, file) => boolean` | - | Custom access check for reading files. |
|
|
856
|
+
| `canCreate` | `(user, resource, file) => boolean` | - | Custom access check for uploading files. |
|
|
857
|
+
| `canDelete` | `(user, resource, file) => boolean` | - | Custom access check for deleting files. |
|
|
858
|
+
|
|
859
|
+
---
|
|
860
|
+
|
|
861
|
+
## registerRoute
|
|
862
|
+
|
|
863
|
+
Registers a custom Fastify route handler outside the resource system. Use this for endpoints that don't map to a
|
|
864
|
+
standard CRUD resource.
|
|
865
|
+
|
|
866
|
+
```ts
|
|
867
|
+
import { registerRoute, Router } from '@appweaver/core';
|
|
868
|
+
import { Type } from '@sinclair/typebox';
|
|
869
|
+
|
|
870
|
+
registerRoute(
|
|
871
|
+
async function (router: Router) {
|
|
872
|
+
router.get('/search-result', {
|
|
873
|
+
schema: {
|
|
874
|
+
summary: 'Sample search result response route',
|
|
875
|
+
response: { 200: Type.Ref('SearchResult') }
|
|
876
|
+
},
|
|
877
|
+
handler: async () => {
|
|
878
|
+
return { message: 'Hello, world!' };
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
},
|
|
882
|
+
{ public: true, cacheTTL: 15000 }
|
|
883
|
+
);
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
### Config options
|
|
887
|
+
|
|
888
|
+
| Property | Type | Description |
|
|
889
|
+
|-------------------------|--------------------------|---------------------------------------------|
|
|
890
|
+
| `exclude` | boolean | Skip registration of this route. |
|
|
891
|
+
| `public` | boolean | No authentication required. |
|
|
892
|
+
| `roles` | string[] | Required roles. |
|
|
893
|
+
| `permissions` | string[] | Required permissions. |
|
|
894
|
+
| `auth` | AuthType[] | Allowed authentication types. |
|
|
895
|
+
| `rateLimit` | RateLimitConfig \| false | Rate limiting configuration. |
|
|
896
|
+
| `recaptcha` | boolean | Require reCAPTCHA verification. |
|
|
897
|
+
| `recaptchaAction` | string | Expected reCAPTCHA action. |
|
|
898
|
+
| `cache` | boolean | Enable response caching. |
|
|
899
|
+
| `cacheKey` | string \| function | Custom cache key. |
|
|
900
|
+
| `cacheTTL` | number | Cache TTL in milliseconds. |
|
|
901
|
+
| `cacheSkipInvalidation` | boolean | Skip automatic cache invalidation. |
|
|
902
|
+
| `cacheModelName` | string | Model name for cache invalidation tracking. |
|
|
903
|
+
| `cacheRelations` | string[] | Related model names for cache invalidation. |
|
|
904
|
+
|
|
905
|
+
---
|
|
906
|
+
|
|
907
|
+
## registerModel
|
|
908
|
+
|
|
909
|
+
Registers a custom TypeBox schema as a named model in the schema registry. Registered models can be referenced using
|
|
910
|
+
`Type.Ref('ModelName')` in route schemas.
|
|
911
|
+
|
|
912
|
+
```ts
|
|
913
|
+
import { registerModel } from '@appweaver/core';
|
|
914
|
+
import { Nullable } from '@appweaver/common';
|
|
915
|
+
import { Type } from '@sinclair/typebox';
|
|
916
|
+
|
|
917
|
+
registerModel(
|
|
918
|
+
Type.Object(
|
|
919
|
+
{
|
|
920
|
+
id: Type.Integer(),
|
|
921
|
+
title: Type.String({ example: 'My Title' }),
|
|
922
|
+
description: Nullable(Type.String({ maxLength: 512 })),
|
|
923
|
+
score: Type.Number({ minimum: 0, maximum: 1 })
|
|
924
|
+
},
|
|
925
|
+
{ $id: 'SearchResult' } // The prefered way for naming the model
|
|
926
|
+
),
|
|
927
|
+
'SearchResult' // Model name can be overriden as a second optional argument
|
|
928
|
+
);
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
| Parameter | Type | Description |
|
|
932
|
+
|-----------|---------|--------------------------------------------------------------|
|
|
933
|
+
| `schema` | TObject | TypeBox object schema definition. |
|
|
934
|
+
| `name` | string? | Override schema name identifier for `Type.Ref()` references. |
|
|
935
|
+
|
|
936
|
+
---
|
|
937
|
+
|
|
938
|
+
## registerPlugin
|
|
939
|
+
|
|
940
|
+
Registers a custom Fastify plugin. Plugins are wrapped with `fastify-plugin` so their decorators and hooks are scoped
|
|
941
|
+
to the entire server instance.
|
|
942
|
+
|
|
943
|
+
```ts
|
|
944
|
+
import { registerPlugin } from '@appweaver/core';
|
|
945
|
+
|
|
946
|
+
registerPlugin(
|
|
947
|
+
'audit-log',
|
|
948
|
+
async (server) => {
|
|
949
|
+
server.addHook('onResponse', async (request, reply) => {
|
|
950
|
+
logger.info(`${request.method} ${request.url} -> ${reply.statusCode}`);
|
|
951
|
+
});
|
|
952
|
+
},
|
|
953
|
+
['other-plugin'] // optional dependencies
|
|
954
|
+
);
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
| Parameter | Type | Description |
|
|
958
|
+
|----------------|-------------------------------------|-------------------------------------------------------|
|
|
959
|
+
| `name` | string | Plugin name (used for dependency resolution). |
|
|
960
|
+
| `plugin` | `(server) => void \| Promise<void>` | Fastify plugin function. |
|
|
961
|
+
| `dependencies` | string[] | Optional list of plugin names this plugin depends on. |
|