@appweaver/create-weaver-app 1.0.25 → 1.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/create-weaver-app.js +4 -5
- package/package.json +1 -1
- package/skill/GUIDELINES.md +4 -0
- package/skill/SKILL.md +15 -10
- package/skill/references/configuration.md +5 -3
- package/skill/references/resources.md +22 -11
- package/skill/references/security.md +47 -4
- package/skill/references/storage.md +32 -3
package/create-weaver-app.js
CHANGED
|
@@ -180,11 +180,10 @@ program
|
|
|
180
180
|
: `[Appweaver framework guidelines](${guidelinesPath})`;
|
|
181
181
|
const guidelinesContent = `# ${variables.NAME}\n\n` +
|
|
182
182
|
`${description}\n\n` +
|
|
183
|
-
`This is an [Appweaver](https://github.com/lmatosevic/appweaver)
|
|
184
|
-
`Follow the framework conventions, architecture
|
|
185
|
-
|
|
186
|
-
`<!-- Add your own project-specific instructions below this line.
|
|
187
|
-
`This file is not modified by \`weaver update\`. -->\n`;
|
|
183
|
+
`This is an [Appweaver](https://github.com/lmatosevic/appweaver) ` +
|
|
184
|
+
`project. Follow the framework conventions, architecture,\n and ` +
|
|
185
|
+
`usage documented in the guidelines:\n\n${guidelinesReference}\n\n` +
|
|
186
|
+
`<!-- Add your own project-specific instructions below this line. -->\n`;
|
|
188
187
|
await promises_1.default.writeFile(guidelinesFilePath, guidelinesContent, {
|
|
189
188
|
encoding: 'utf8'
|
|
190
189
|
});
|
package/package.json
CHANGED
package/skill/GUIDELINES.md
CHANGED
|
@@ -141,6 +141,10 @@ Use `createAuthModel` and `createAuthService` for authenticatable users. They mu
|
|
|
141
141
|
`createAuthModel` adds: `email`, `passwordHash`, `verifiedEmail`, `twoFactorAuth`, `enabled`, `logoutAt` scalars; a
|
|
142
142
|
virtual `password` field; a `roles` relation; and optional `apiKeys` relation.
|
|
143
143
|
|
|
144
|
+
`createAuthService` supports an optional `registrationData` callback to customize the registration payload and an
|
|
145
|
+
optional `checkOAuth2User` callback to allow or reject OAuth2 registrations/logins (return nothing to proceed, or a
|
|
146
|
+
string/`Error` to abort).
|
|
147
|
+
|
|
144
148
|
```ts
|
|
145
149
|
// src/resources/user/model.ts
|
|
146
150
|
import { createAuthModel } from '@appweaver/core';
|
package/skill/SKILL.md
CHANGED
|
@@ -78,7 +78,7 @@ create-weaver-app MyBlogAPI "My own CMS for blogging" --database postgresql --no
|
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
This creates a `./my-blog-api` directory, installs all dependencies, and runs the initial schema and type generation.
|
|
81
|
-
|
|
81
|
+
The default test runner is `jest` with `swc` transpiler.
|
|
82
82
|
|
|
83
83
|
**Example — Bun project with Sqlite:**
|
|
84
84
|
|
|
@@ -86,8 +86,8 @@ Default test runner is `jest` with `swc` transpiler.
|
|
|
86
86
|
create-weaver-app BunApp "Bun application with simple API" --bun --database sqlite
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
This creates a `./bun-app` directory, installs all dependencies using bun package manager, and runs the initial
|
|
90
|
-
|
|
89
|
+
This creates a `./bun-app` directory, installs all dependencies using bun package manager, and runs the initial schema
|
|
90
|
+
and type generation. The default test runner is `bun`.
|
|
91
91
|
|
|
92
92
|
After the application is scaffolded, the following commands need to be run to finish the application setup:
|
|
93
93
|
|
|
@@ -314,11 +314,14 @@ Use `createAuthModel` and `createAuthService` instead of `createModel`/`createSe
|
|
|
314
314
|
authenticatable user. They cannot be used independently! If an auth model is created, then also auth service must exist.
|
|
315
315
|
|
|
316
316
|
`createAuthModel` extends the config with: `email`, `passwordHash`, `verifiedEmail`, `twoFactorAuth`, `enabled`,
|
|
317
|
-
`logoutAt` scalars; a virtual `password` field (write-only); a `roles` relation; and an optional `apiKeys` relation
|
|
318
|
-
when `SECURITY_API_KEY_ENABLED` is set).
|
|
317
|
+
`logoutAt` scalars; a virtual `password` field (write-only); a `roles` relation; and an optional `apiKeys` relation
|
|
318
|
+
(when `SECURITY_API_KEY_ENABLED` is set).
|
|
319
319
|
|
|
320
|
-
`createAuthService` extends the config with automatic password hashing on create/update
|
|
321
|
-
`registrationData` callback to customize registration payload
|
|
320
|
+
`createAuthService` extends the config with automatic password hashing on create/update, an optional
|
|
321
|
+
`registrationData` callback to customize registration payload (for OAuth2 logins its `additionalData` argument includes
|
|
322
|
+
`firstName`, `lastName`, `avatarUrl`, and — when `SECURITY_OAUTH2_FETCH_AVATAR_ENABLED` is set — a downloaded
|
|
323
|
+
`avatarFile`), and an optional `checkOAuth2User` callback invoked before a user is registered or authenticated via
|
|
324
|
+
OAuth2 (return nothing to proceed, or a string/`Error`/`HttpError` to abort the login with an error).
|
|
322
325
|
|
|
323
326
|
```ts
|
|
324
327
|
// src/resources/user/model.ts
|
|
@@ -356,7 +359,9 @@ export default createAuthService({
|
|
|
356
359
|
|
|
357
360
|
Use `registerRoute` to register a custom [Fastify route](https://fastify.dev/docs/latest/Reference/Routes/) handler. The
|
|
358
361
|
handler is a Fastify plugin function that defines one or more routes. An optional config object controls authentication,
|
|
359
|
-
caching, and reCAPTCHA behavior.
|
|
362
|
+
caching, and reCAPTCHA behavior. When a custom route's 2xx response schema references resource output models (`<Name>`,
|
|
363
|
+
`<Name>Single` or `<Name>Multiple` — directly or nested inside custom schemas), virtual field values (e.g. `File.url`)
|
|
364
|
+
are projected onto the response payload automatically before serialization.
|
|
360
365
|
|
|
361
366
|
```ts
|
|
362
367
|
// src/plugins/custom-route.ts
|
|
@@ -422,8 +427,8 @@ registerPlugin('audit-log', async (server) => {
|
|
|
422
427
|
|
|
423
428
|
### Dependency injection
|
|
424
429
|
|
|
425
|
-
Use `define` to register a value or class in the app context, and `inject` to retrieve it. Class constructors are
|
|
426
|
-
|
|
430
|
+
Use `define` to register a value or class in the app context, and `inject` to retrieve it. Class constructors are lazily
|
|
431
|
+
instantiated as singletons on the first injection.
|
|
427
432
|
|
|
428
433
|
```ts
|
|
429
434
|
import { Cache } from '@appweaver/common';
|
|
@@ -256,9 +256,11 @@ The config object is frozen with `Object.freeze()` after loading to prevent runt
|
|
|
256
256
|
|
|
257
257
|
#### OAuth2 general
|
|
258
258
|
|
|
259
|
-
| Property
|
|
260
|
-
|
|
261
|
-
| `SECURITY_OAUTH2_STATE_TTL`
|
|
259
|
+
| Property | Type | Default | Description |
|
|
260
|
+
|----------------------------------------|---------|----------|-----------------------------------------------------------------------------------------------------------------------------------|
|
|
261
|
+
| `SECURITY_OAUTH2_STATE_TTL` | integer | `600000` | OAuth2 state parameter TTL in milliseconds (default 10 min). |
|
|
262
|
+
| `SECURITY_OAUTH2_REGISTRATION_ENABLED` | boolean | `true` | Allow registering new users via OAuth2 login. When `false`, only already existing users (matched by email) can log in via OAuth2. |
|
|
263
|
+
| `SECURITY_OAUTH2_FETCH_AVATAR_ENABLED` | boolean | `false` | Download the user's avatar from the OAuth2 provider during registration and pass it as `avatarFile` to `registrationData`. |
|
|
262
264
|
|
|
263
265
|
#### OAuth2 Google
|
|
264
266
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Resources
|
|
2
2
|
|
|
3
|
-
Resources are the core building blocks of an Appweaver application. There are four resource types that form a
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Resources are the core building blocks of an Appweaver application. There are four resource types that form a dependency
|
|
4
|
+
chain: **model** → **service** → **routes** → **policy**. Each resource type is created using a corresponding factory
|
|
5
|
+
function and autoloaded from `src/resources/*/` on application start. Source directory and resources pattern could be
|
|
6
|
+
changed with `APP_SOURCE_PATH` and `RESOURCE_{MODEL,SERVICE,...}_PATTERN` config variables.
|
|
7
7
|
|
|
8
8
|
- A **model** is always required.
|
|
9
9
|
- A **service** requires a model.
|
|
@@ -14,9 +14,9 @@ resources pattern could be changed with `APP_SOURCE_PATH` and `RESOURCE_{MODEL,S
|
|
|
14
14
|
|
|
15
15
|
## createModel
|
|
16
16
|
|
|
17
|
-
Creates a resource model definition. The model defines database fields, relations, files, virtual fields, DTOs for
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
Creates a resource model definition. The model defines database fields, relations, files, virtual fields, DTOs for CRUD
|
|
18
|
+
operations, and index configuration. It is used to generate Prisma schema, TypeScript types, and route request/response
|
|
19
|
+
schemas.
|
|
20
20
|
|
|
21
21
|
```ts
|
|
22
22
|
import { createModel } from '@appweaver/core';
|
|
@@ -505,8 +505,8 @@ are passed through unchanged.
|
|
|
505
505
|
| `maxHeight` | number | Maximum height. Only downscales if the image exceeds this dimension. |
|
|
506
506
|
| `fit` | ImageFit | How the image fits the target dimensions: `'inside'` (default), `'contain'`, `'cover'`, `'fill'`, `'outside'`. |
|
|
507
507
|
|
|
508
|
-
`width`/`height` take precedence over `maxWidth`/`maxHeight`. When using `maxWidth`/`maxHeight`, images smaller than
|
|
509
|
-
|
|
508
|
+
`width`/`height` take precedence over `maxWidth`/`maxHeight`. When using `maxWidth`/`maxHeight`, images smaller than the
|
|
509
|
+
specified dimensions are not enlarged.
|
|
510
510
|
|
|
511
511
|
```ts
|
|
512
512
|
// Compress and limit dimensions
|
|
@@ -566,6 +566,17 @@ const config = {
|
|
|
566
566
|
| `output.type` | `'always'` \| `'single'` \| `'multiple'` \| `'none'` | When the virtual field appears in output. |
|
|
567
567
|
| `output.value` | primitive \| function | Computed value or transformer for output. |
|
|
568
568
|
|
|
569
|
+
Virtual output values are applied automatically to responses of resource CRUD routes (including nested relation and file
|
|
570
|
+
objects) and to responses of custom `registerRoute` routes whose 2xx response schemas reference resource output models.
|
|
571
|
+
To apply them manually on a raw resource object (e.g. one fetched directly through a Prisma client), use the
|
|
572
|
+
`projectVirtualFields` helper:
|
|
573
|
+
|
|
574
|
+
```ts
|
|
575
|
+
import { projectVirtualFields } from '@appweaver/core';
|
|
576
|
+
|
|
577
|
+
const projected = projectVirtualFields(post, 'Post'); // sets virtual values, recursing into relations and files
|
|
578
|
+
```
|
|
579
|
+
|
|
569
580
|
### Operation config (read, create, update)
|
|
570
581
|
|
|
571
582
|
Control which fields appear in each DTO. Use `pick` for an allowlist or `omit` for a deny-list.
|
|
@@ -988,8 +999,8 @@ registerModel(
|
|
|
988
999
|
|
|
989
1000
|
## registerPlugin
|
|
990
1001
|
|
|
991
|
-
Registers a custom Fastify plugin. Plugins are wrapped with `fastify-plugin` so their decorators and hooks are scoped
|
|
992
|
-
|
|
1002
|
+
Registers a custom Fastify plugin. Plugins are wrapped with `fastify-plugin` so their decorators and hooks are scoped to
|
|
1003
|
+
the entire server instance.
|
|
993
1004
|
|
|
994
1005
|
```ts
|
|
995
1006
|
import { registerPlugin } from '@appweaver/core';
|
|
@@ -212,7 +212,9 @@ follow the same flow pattern.
|
|
|
212
212
|
6. Server verifies the state token (one-time use)
|
|
213
213
|
7. Server exchanges the code for an access token with the provider
|
|
214
214
|
8. Server fetches user info from the provider
|
|
215
|
-
9. Server
|
|
215
|
+
9. Server finds the user by email and invokes the optional checkOAuth2User callback
|
|
216
|
+
(aborts with an error when the callback returns a string or an Error).
|
|
217
|
+
New users are registered unless SECURITY_OAUTH2_REGISTRATION_ENABLED=false
|
|
216
218
|
10. Server generates an authentication OTT
|
|
217
219
|
11. Server redirects to the original URL with the token:
|
|
218
220
|
-> https://myapp.com/dashboard?token={ott}
|
|
@@ -259,7 +261,7 @@ SECURITY_OAUTH2_GOOGLE_CLIENT_SECRET=your-google-client-secret
|
|
|
259
261
|
|
|
260
262
|
**Scopes**: `profile`, `email`
|
|
261
263
|
|
|
262
|
-
**User info extracted**: `email`, `given_name` (firstName), `family_name` (lastName)
|
|
264
|
+
**User info extracted**: `email`, `given_name` (firstName), `family_name` (lastName), `picture` (avatarUrl)
|
|
263
265
|
|
|
264
266
|
**Google Cloud Console setup:**
|
|
265
267
|
|
|
@@ -304,7 +306,7 @@ SECURITY_OAUTH2_FACEBOOK_CLIENT_SECRET=your-facebook-app-secret
|
|
|
304
306
|
|
|
305
307
|
**Scopes**: `public_profile`, `email`
|
|
306
308
|
|
|
307
|
-
**User info extracted**: `email`, `name` (split into firstName/lastName)
|
|
309
|
+
**User info extracted**: `email`, `name` (split into firstName/lastName), `picture` (avatarUrl)
|
|
308
310
|
|
|
309
311
|
**Facebook Developer Console setup:**
|
|
310
312
|
|
|
@@ -346,7 +348,48 @@ For any OpenID Connect-compatible provider (Keycloak, Auth0, etc.).
|
|
|
346
348
|
|
|
347
349
|
**User info endpoint**: `{issuer}/protocol/openid-connect/userinfo`
|
|
348
350
|
|
|
349
|
-
**Standard claims expected**: `sub`, `email`, `given_name`, `family_name`
|
|
351
|
+
**Standard claims expected**: `sub`, `email`, `given_name`, `family_name`, `picture` (optional, avatarUrl)
|
|
352
|
+
|
|
353
|
+
### OAuth2 registration control and hooks
|
|
354
|
+
|
|
355
|
+
**Disable OAuth2 registration** — set `SECURITY_OAUTH2_REGISTRATION_ENABLED=false` (JSON:
|
|
356
|
+
`security.oauth2.registrationEnabled`) to prevent new users from being created during OAuth2 login. Only users that
|
|
357
|
+
already exist in the database (matched by email) can then log in via OAuth2; unknown emails receive a 403 error.
|
|
358
|
+
|
|
359
|
+
**`checkOAuth2User` callback** — an optional callback on `createAuthService` invoked on every OAuth2 login, before a
|
|
360
|
+
user is registered or authenticated. It receives the auth source, the user info extracted from the provider, and the
|
|
361
|
+
existing auth user (or `null` when the user would be newly registered). Return nothing to proceed, or return a string,
|
|
362
|
+
`Error`, or `HttpError` to abort the flow (a 403 error is thrown, or the `HttpError` as-is):
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
// src/resources/user/service.ts
|
|
366
|
+
import { AuthSource } from '@appweaver/common';
|
|
367
|
+
import { createAuthService, HttpError } from '@appweaver/core';
|
|
368
|
+
|
|
369
|
+
export default createAuthService({
|
|
370
|
+
modelName: 'User',
|
|
371
|
+
checkOAuth2User: (source, userInfo, authUser) => {
|
|
372
|
+
if (!userInfo.email.endsWith('@mycompany.com')) {
|
|
373
|
+
return new HttpError('Only company accounts are allowed', 403);
|
|
374
|
+
}
|
|
375
|
+
if (!authUser && source === AuthSource.OAuth2Facebook) {
|
|
376
|
+
return 'New accounts cannot be created via Facebook';
|
|
377
|
+
}
|
|
378
|
+
// Return nothing to proceed with registration/login
|
|
379
|
+
},
|
|
380
|
+
registrationData: (source, email, password, additionalData) => ({
|
|
381
|
+
email,
|
|
382
|
+
password,
|
|
383
|
+
name: `${additionalData?.firstName} ${additionalData?.lastName}`
|
|
384
|
+
})
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**User avatar** — the provider's avatar/picture URL is passed to `registrationData` as `additionalData.avatarUrl`.
|
|
389
|
+
When `SECURITY_OAUTH2_FETCH_AVATAR_ENABLED=true` (JSON: `security.oauth2.fetchAvatarEnabled`), the avatar image is
|
|
390
|
+
also downloaded during registration and passed as `additionalData.avatarFile`
|
|
391
|
+
(`{ name, mimeType, size, data: Buffer }`), so it can be mapped to a model field or stored via the file service. The
|
|
392
|
+
download is best-effort: failures are logged and registration proceeds without the file.
|
|
350
393
|
|
|
351
394
|
### Client-side OAuth2 integration example
|
|
352
395
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Storage
|
|
2
2
|
|
|
3
|
-
The storage module handles file persistence: storing, streaming, and deleting binary content. The default
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
The storage module handles file persistence: storing, streaming, and deleting binary content. The default implementation
|
|
4
|
+
(`FilesystemStorage`) writes files to a local directory using a configurable name pattern. The module supports
|
|
5
|
+
range-based streaming for efficient large-file delivery (e.g., video, audio).
|
|
6
6
|
|
|
7
7
|
## Injecting Storage
|
|
8
8
|
|
|
@@ -221,6 +221,35 @@ export class PostService {
|
|
|
221
221
|
}
|
|
222
222
|
```
|
|
223
223
|
|
|
224
|
+
### File integrity (checksum)
|
|
225
|
+
|
|
226
|
+
When a file is uploaded through `FileService.saveFile()` (or the resource file upload routes), a **SHA-256 checksum**
|
|
227
|
+
(hex-encoded) of the stored content is calculated during the upload and persisted on the `File` record in the
|
|
228
|
+
`checksum` field. The checksum is calculated over the exact bytes written to storage (i.e., after any configured image
|
|
229
|
+
processing), so it can be used at any later point to verify that the file on disk has not been modified or corrupted.
|
|
230
|
+
|
|
231
|
+
The checksum is included in file API responses, so clients can verify downloaded content against it.
|
|
232
|
+
|
|
233
|
+
To verify a file's integrity, recalculate the checksum with the `makeHash` utility from `@appweaver/common` and
|
|
234
|
+
compare it with the stored value:
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
import { createReadStream } from 'node:fs';
|
|
238
|
+
import { makeHash } from '@appweaver/common';
|
|
239
|
+
|
|
240
|
+
// From a readable stream (no memory buffering, works for large files) — returns a promise
|
|
241
|
+
const checksum = await makeHash(createReadStream('/path/to/stored/file'));
|
|
242
|
+
|
|
243
|
+
// Or from a Buffer / string — returns the hash synchronously
|
|
244
|
+
// const checksum = makeHash(downloadedBuffer);
|
|
245
|
+
|
|
246
|
+
if (checksum !== file.checksum) {
|
|
247
|
+
throw new Error(`File ${file.name} has been modified or corrupted`);
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Stored file checksums always use the defaults: **`sha256` + `hex`**.
|
|
252
|
+
|
|
224
253
|
### Streaming a file
|
|
225
254
|
|
|
226
255
|
`FileService.stream()` handles authorization checks (public, private, or protected) and range-based requests
|