@appweaver/cli 1.5.1 → 1.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appweaver/cli",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "Appweaver - the backend framework for AI-first development (@cli)",
5
5
  "author": "Luka Matosevic",
6
6
  "license": "MIT",
package/skill/SKILL.md CHANGED
@@ -66,10 +66,10 @@ create-weaver-app <name> [description] [options]
66
66
  | `--bun` | Use Bun as application runtime. (default is node and npm) | false |
67
67
  | `--skipInstall` | Skip all dependencies installation. | false |
68
68
  | `--noDocker` | Skip Dockerfile, Dockerfile.bun and docker-compose.yml files | false |
69
- | `--noRedis` | Skip ioredis | false |
70
- | `--noQueue` | Skip bullmq | false |
71
- | `--noMailer` | Skip nodemailer | false |
72
- | `--noCron` | Skip cron | false |
69
+ | `--noRedis` | Skip ioredis, use in-memory cache, rate limit and queue | false |
70
+ | `--noQueue` | Skip bullmq, use in-memory queue | false |
71
+ | `--noMailer` | Skip nodemailer, disable mailer (email features respond 501) | false |
72
+ | `--noCron` | Skip cron, disable scheduler | false |
73
73
 
74
74
  **Example — PostgreSQL project without queue:**
75
75
 
@@ -15,6 +15,18 @@ import { Cache } from '@appweaver/common';
15
15
  const cache = inject(Cache);
16
16
  ```
17
17
 
18
+ ### Unavailable backend
19
+
20
+ With `CACHE_SKIP_ON_ERROR` enabled (the default) the cache never fails its callers. While the backing memory (e.g.
21
+ Redis) is down or a command fails, every method returns its empty result (`get` → `null`, `has`/`set`/`evict` →
22
+ `false`, `expire` → `0`, `keys` → `[]`), so the callers fall back to the database. The outage is logged once with an
23
+ error, and the recovery once with info. Entries written before an outage may have missed invalidations made during it,
24
+ so the whole cache is cleared once the memory is available again. Calling code does not need its own `try`/`catch`
25
+ around cache access.
26
+
27
+ Set `CACHE_SKIP_ON_ERROR` to `false` to make every method throw the memory error instead, so requests that read or
28
+ write the cache fail while its backend is down. Resource cache invalidation still only logs its errors.
29
+
18
30
  #### `cache.get<T>(key)`
19
31
 
20
32
  Retrieves a cached value. Returns `null` if the key does not exist.
@@ -167,6 +179,7 @@ const key = cacheService.buildCacheKey({
167
179
  | `CACHE_EVICTION_STRATEGY` | `enum` | `'lru'` | `lru`, `lfu`, or `fifo` |
168
180
  | `CACHE_INVALIDATION_STRATEGY` | `enum` | `'expire-related'` | `expire-related`, `expire-all`, or `none` |
169
181
  | `CACHE_INVALIDATION_DEFERRED` | `bool` | `false` | Fire invalidation in the background (non-blocking) |
182
+ | `CACHE_SKIP_ON_ERROR` | `bool` | `true` | Return empty results instead of failing on errors |
170
183
 
171
184
  Switch to the in-memory implementation for local development or tests:
172
185
 
@@ -120,13 +120,14 @@ The config object is frozen with `Object.freeze()` after loading to prevent runt
120
120
 
121
121
  ### Rate limiting (RATE_LIMIT_\*)
122
122
 
123
- | Property | Type | Default | Description |
124
- |-------------------------|-----------|-----------|------------------------------------------------------------------|
125
- | `RATE_LIMIT_ENABLED` | boolean | `true` | Enable global rate limiting middleware. |
126
- | `RATE_LIMIT_MAX` | integer | `1000` | Maximum requests allowed per time window. |
127
- | `RATE_LIMIT_WINDOW` | integer | `60000` | Rate limit window in milliseconds. |
128
- | `RATE_LIMIT_ALLOW_LIST` | string[]? | - | IP addresses/patterns exempt from rate limiting. |
129
- | `RATE_LIMIT_STORE` | enum | `'redis'` | Store backend for tracking limits. Values: `redis`, `in-memory`. |
123
+ | Property | Type | Default | Description |
124
+ |----------------------------|-----------|-----------|------------------------------------------------------------------------------------------------------------|
125
+ | `RATE_LIMIT_ENABLED` | boolean | `true` | Enable global rate limiting middleware. |
126
+ | `RATE_LIMIT_MAX` | integer | `1000` | Maximum requests allowed per time window. |
127
+ | `RATE_LIMIT_WINDOW` | integer | `60000` | Rate limit window in milliseconds. |
128
+ | `RATE_LIMIT_ALLOW_LIST` | string[]? | - | IP addresses/patterns exempt from rate limiting. |
129
+ | `RATE_LIMIT_STORE` | enum | `'redis'` | Store backend for tracking limits. Values: `redis`, `in-memory`. |
130
+ | `RATE_LIMIT_SKIP_ON_ERROR` | boolean | `true` | Skip rate limiting instead of failing requests with `500` when the store errors, e.g. while Redis is down. |
130
131
 
131
132
  ### Swagger / OpenAPI (SWAGGER\_\*)
132
133
 
@@ -182,14 +183,14 @@ The config object is frozen with `Object.freeze()` after loading to prevent runt
182
183
 
183
184
  #### General
184
185
 
185
- | Property | Type | Default | Description |
186
- |--------------------------------------|----------|---------------------------------------------------------|--------------------------------------------------|
187
- | `SECURITY_ROUTE_PREFIX` | string | `'/auth'` | Base path for authentication routes. |
188
- | `SECURITY_CACHE_TTL` | integer | `300000` | Security cache TTL in milliseconds. |
189
- | `SECURITY_AUTH_OTT_TTL` | integer | `120000` | One-time token TTL for authentication (ms). |
190
- | `SECURITY_ALLOWED_REDIRECT_HOSTS` | string[] | `['*']` | Allowed hosts for post-authentication redirects. |
191
- | `SECURITY_STORE_PROVIDER` | string | `'@appweaver/core/security/store/redis-security-store'` | Security store implementation path. |
192
- | `SECURITY_STORE_KEEP_DATABASE_TABLE` | boolean | `false` | Keep database table after migrations. |
186
+ | Property | Type | Default | Description |
187
+ |--------------------------------------|----------|------------------------------------------------------------|-----------------------------------------------------------|
188
+ | `SECURITY_ROUTE_PREFIX` | string | `'/auth'` | Base path for authentication routes. |
189
+ | `SECURITY_CACHE_TTL` | integer | `300000` | Security cache TTL in milliseconds. |
190
+ | `SECURITY_AUTH_OTT_TTL` | integer | `120000` | One-time token TTL for authentication (ms). |
191
+ | `SECURITY_ALLOWED_REDIRECT_HOSTS` | string[] | `['*']` | Allowed hosts for post-authentication redirects. |
192
+ | `SECURITY_STORE_PROVIDER` | string | `'@appweaver/core/security/store/database-security-store'` | Security store implementation path. |
193
+ | `SECURITY_STORE_KEEP_DATABASE_TABLE` | boolean | `false` | Keep the one-time token table when another store is used. |
193
194
 
194
195
  #### Password policy
195
196
 
@@ -390,6 +391,12 @@ you generated yourself, or provide the team ID, key ID and `.p8` private key and
390
391
  | `REDIS_URL` | string | `'redis://localhost:6379/0'` | Redis connection URL. |
391
392
  | `REDIS_PROVIDER` | string | `'@appweaver/core/memory/redis'` | Redis provider implementation path. |
392
393
 
394
+ The application starts and keeps running while Redis is unreachable. Connections reconnect in the background, each
395
+ outage and recovery is logged once, and commands fail right away instead of waiting for the reconnection, so by default
396
+ the
397
+ cache falls back to the database (see `CACHE_SKIP_ON_ERROR`), rate limiting is skipped (see
398
+ `RATE_LIMIT_SKIP_ON_ERROR`) and queues throw (see `queue.md`).
399
+
393
400
  ### In-memory store (MEMORY\_\*)
394
401
 
395
402
  | Property | Type | Default | Description |
@@ -412,6 +419,7 @@ you generated yourself, or provide the team ID, key ID and `.p8` private key and
412
419
  | `CACHE_EVICTION_DEFERRED` | boolean | `false` | Defer eviction to a background process. |
413
420
  | `CACHE_INVALIDATION_STRATEGY` | enum | `'expire-related'` | Invalidation strategy. Values: `expire-related`, `expire-all`, `none`. |
414
421
  | `CACHE_INVALIDATION_DEFERRED` | boolean | `false` | Defer invalidation to a background process. |
422
+ | `CACHE_SKIP_ON_ERROR` | boolean | `true` | Return empty results instead of failing when the cache backend errors. |
415
423
  | `CACHE_PROVIDER` | string | `'@appweaver/core/cache/redis-cache'` | Cache provider implementation path. |
416
424
 
417
425
  ### Job queue (QUEUE\_\*)
@@ -429,10 +437,11 @@ you generated yourself, or provide the team ID, key ID and `.p8` private key and
429
437
 
430
438
  ### Scheduler (SCHEDULER\_\*)
431
439
 
432
- | Property | Type | Default | Description |
433
- |----------------------------|---------|----------------------------------------------|---------------------------------------------------|
434
- | `SCHEDULER_AUTO_START_JOB` | boolean | `true` | Auto-start scheduled jobs on application startup. |
435
- | `SCHEDULER_PROVIDER` | string | `'@appweaver/core/scheduler/cron-scheduler'` | Scheduler provider implementation path. |
440
+ | Property | Type | Default | Description |
441
+ |----------------------------|---------|----------------------------------------------|----------------------------------------------------------------------------|
442
+ | `SCHEDULER_ENABLED` | boolean | `true` | Enable the scheduler. Disable it when the `cron` package is not installed. |
443
+ | `SCHEDULER_AUTO_START_JOB` | boolean | `true` | Auto-start scheduled jobs on application startup. |
444
+ | `SCHEDULER_PROVIDER` | string | `'@appweaver/core/scheduler/cron-scheduler'` | Scheduler provider implementation path. |
436
445
 
437
446
  ### Events (EVENTS\_\*)
438
447
 
@@ -443,16 +452,17 @@ you generated yourself, or provide the team ID, key ID and `.p8` private key and
443
452
 
444
453
  ### Mailer (MAILER\_\*)
445
454
 
446
- | Property | Type | Default | Description |
447
- |-------------------------|---------|----------------------------------------|------------------------------------------|
448
- | `MAILER_SENDER_NAME` | string? | - | Default sender name for outgoing emails. |
449
- | `MAILER_SENDER_ADDRESS` | string? | - | Default sender email address. |
450
- | `MAILER_PROVIDER` | string | `'@appweaver/core/mailer/smtp-mailer'` | Mailer provider implementation path. |
451
- | `MAILER_SMTP_HOST` | string | `'127.0.0.1'` | SMTP server hostname. |
452
- | `MAILER_SMTP_PORT` | integer | `587` | SMTP server port. |
453
- | `MAILER_SMTP_SECURE` | boolean | `false` | Use TLS/SSL for SMTP connections. |
454
- | `MAILER_SMTP_USER` | string? | - | SMTP authentication username. |
455
- | `MAILER_SMTP_PASSWORD` | string? | - | SMTP authentication password. |
455
+ | Property | Type | Default | Description |
456
+ |-------------------------|---------|----------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
457
+ | `MAILER_ENABLED` | boolean | `true` | Enable the mailer. Disable it when the `nodemailer` package is not installed. Without it the email features (email verification, password reset, 2FA) respond with `501`. |
458
+ | `MAILER_SENDER_NAME` | string? | - | Default sender name for outgoing emails. |
459
+ | `MAILER_SENDER_ADDRESS` | string? | - | Default sender email address. |
460
+ | `MAILER_PROVIDER` | string | `'@appweaver/core/mailer/smtp-mailer'` | Mailer provider implementation path. |
461
+ | `MAILER_SMTP_HOST` | string | `'127.0.0.1'` | SMTP server hostname. |
462
+ | `MAILER_SMTP_PORT` | integer | `587` | SMTP server port. |
463
+ | `MAILER_SMTP_SECURE` | boolean | `false` | Use TLS/SSL for SMTP connections. |
464
+ | `MAILER_SMTP_USER` | string? | - | SMTP authentication username. |
465
+ | `MAILER_SMTP_PASSWORD` | string? | - | SMTP authentication password. |
456
466
 
457
467
  ### System (SYSTEM\_\*)
458
468
 
@@ -45,6 +45,12 @@ await queue.closeAll();
45
45
 
46
46
  Returns a `HealthCheckResult` indicating whether the underlying queue backend is reachable.
47
47
 
48
+ ### Unavailable backend
49
+
50
+ `BullQueue` needs Redis and has no fallback. The application still starts while Redis is down, but `sendJob` and
51
+ `sendBulkJobs` throw `Queue '<name>' is unavailable, Redis connection is not ready` right away instead of waiting for
52
+ the reconnection, so callers that must not fail should catch it. Workers pick jobs up again once Redis reconnects.
53
+
48
54
  ---
49
55
 
50
56
  ## `QueueProcessor` — per-queue API
@@ -76,7 +76,8 @@ if `SECURITY_JWT_SECRET` is set.
76
76
  "source": "password | apiKey | basic | oauth2Google | oauth2Facebook | oauth2X | oauth2Github | oauth2Gitlab | oauth2Linkedin | oauth2Apple | oauth2Microsoft | oauth2Custom",
77
77
  "username": "User email (e.g. admin@example.com)",
78
78
  "sub": "User ID (e.g. 123)",
79
- "iat": "Issued at timestamp (e.g. 1774623924234)"
79
+ "iat": "Issued at, in seconds (e.g. 1774623924)",
80
+ "exp": "Expiration, in seconds (e.g. 1777215924)"
80
81
  }
81
82
  ```
82
83
 
@@ -85,9 +86,11 @@ if `SECURITY_JWT_SECRET` is set.
85
86
  On every authenticated request, the server:
86
87
 
87
88
  1. Verifies the JWT signature
88
- 2. Loads the user from the database by `sub` (user ID)
89
+ 2. Loads the user by `sub` (user ID), from the cache when possible. Every update or delete of the auth user, through
90
+ the auth service (logout, password change or reset) or the resource routes, evicts the cached user regardless of
91
+ the cache invalidation strategy
89
92
  3. Checks that the user is enabled
90
- 4. Validates `logoutAt` is before the token's `iat` (tokens issued before logout are rejected)
93
+ 4. Rejects tokens issued before the user's `logoutAt` (compared in whole seconds)
91
94
  5. Checks that the token scope allows access to the requested URL
92
95
 
93
96
  ### Auth routes
@@ -428,7 +431,7 @@ update: {
428
431
  On every authenticated request:
429
432
 
430
433
  1. Verify user exists and is enabled
431
- 2. Verify `logoutAt` is before token `iat`
434
+ 2. Verify the token was not issued before `logoutAt`
432
435
  3. Verify JWT scope allows access to the URL
433
436
  4. Verify the user has required roles (if configured)
434
437
  5. Verify the user has required permissions (if configured)
@@ -629,10 +632,24 @@ time-limited.
629
632
 
630
633
  ### Storage
631
634
 
632
- OTTs are stored in the configured security store:
635
+ OTTs are stored in the security store set by `SECURITY_STORE_PROVIDER`. Only a hash of each token is stored.
633
636
 
634
- - **Redis** (default): `@appweaver/core/security/store/redis-security-store`
635
- - **Database**: `@appweaver/core/security/store/database-security-store`
637
+ - **Database** (default): `@appweaver/core/security/store/database-security-store`. Stores the tokens in the
638
+ `OneTimeToken` model, which is added to the schema only while this store is configured (or
639
+ `SECURITY_STORE_KEEP_DATABASE_TABLE` is set). The model has no routes.
640
+ - **Redis**: `@appweaver/core/security/store/redis-security-store`. Stores the tokens as Redis keys with a TTL, so the
641
+ one-time token flows (OAuth2 login, 2FA, email verification, password reset) fail while Redis is unavailable.
642
+
643
+ Switching the store adds or removes the `OneTimeToken` model, so run `weaver generate` and create a migration
644
+ afterwards.
645
+
646
+ Both stores behave the same way:
647
+
648
+ - A token is consumed by its first successful use. Of concurrent uses of the same token only one succeeds, the others
649
+ are rejected with `401`.
650
+ - A token failing the content validation (e.g. a mistyped 2FA code) is kept, so it can be used again until it expires.
651
+ - Expired tokens are rejected. The database store deletes them whenever a new token is created, Redis expires them on
652
+ its own.
636
653
 
637
654
  ---
638
655