@bloomneo/appkit 1.2.9 → 1.5.2

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 (118) hide show
  1. package/AGENTS.md +195 -0
  2. package/CHANGELOG.md +253 -0
  3. package/README.md +147 -799
  4. package/bin/commands/generate.js +7 -7
  5. package/cookbook/README.md +26 -0
  6. package/cookbook/api-key-service.ts +106 -0
  7. package/cookbook/auth-protected-crud.ts +112 -0
  8. package/cookbook/file-upload-pipeline.ts +113 -0
  9. package/cookbook/multi-tenant-saas.ts +87 -0
  10. package/cookbook/real-time-chat.ts +121 -0
  11. package/dist/auth/auth.d.ts +21 -4
  12. package/dist/auth/auth.d.ts.map +1 -1
  13. package/dist/auth/auth.js +56 -44
  14. package/dist/auth/auth.js.map +1 -1
  15. package/dist/auth/defaults.d.ts +1 -1
  16. package/dist/auth/defaults.js +35 -35
  17. package/dist/cache/cache.d.ts +29 -6
  18. package/dist/cache/cache.d.ts.map +1 -1
  19. package/dist/cache/cache.js +72 -44
  20. package/dist/cache/cache.js.map +1 -1
  21. package/dist/cache/defaults.js +29 -29
  22. package/dist/cache/index.d.ts +19 -10
  23. package/dist/cache/index.d.ts.map +1 -1
  24. package/dist/cache/index.js +21 -18
  25. package/dist/cache/index.js.map +1 -1
  26. package/dist/config/defaults.d.ts +1 -1
  27. package/dist/config/defaults.js +11 -11
  28. package/dist/config/index.d.ts +3 -3
  29. package/dist/config/index.js +4 -4
  30. package/dist/database/adapters/mongoose.d.ts +4 -4
  31. package/dist/database/adapters/mongoose.js +7 -7
  32. package/dist/database/adapters/prisma.d.ts +4 -4
  33. package/dist/database/adapters/prisma.js +7 -7
  34. package/dist/database/defaults.d.ts +1 -1
  35. package/dist/database/defaults.js +4 -4
  36. package/dist/database/index.js +2 -2
  37. package/dist/database/index.js.map +1 -1
  38. package/dist/email/defaults.js +26 -26
  39. package/dist/email/index.js +7 -7
  40. package/dist/email/strategies/resend.js +1 -1
  41. package/dist/error/defaults.d.ts +1 -1
  42. package/dist/error/defaults.js +13 -13
  43. package/dist/error/error.d.ts +12 -0
  44. package/dist/error/error.d.ts.map +1 -1
  45. package/dist/error/error.js +19 -0
  46. package/dist/error/error.js.map +1 -1
  47. package/dist/error/index.d.ts +14 -3
  48. package/dist/error/index.d.ts.map +1 -1
  49. package/dist/error/index.js +14 -3
  50. package/dist/error/index.js.map +1 -1
  51. package/dist/event/defaults.js +35 -35
  52. package/dist/event/index.js +7 -7
  53. package/dist/logger/defaults.d.ts +1 -1
  54. package/dist/logger/defaults.js +40 -40
  55. package/dist/logger/index.d.ts +1 -0
  56. package/dist/logger/index.d.ts.map +1 -1
  57. package/dist/logger/index.js.map +1 -1
  58. package/dist/logger/logger.d.ts +8 -0
  59. package/dist/logger/logger.d.ts.map +1 -1
  60. package/dist/logger/logger.js +13 -3
  61. package/dist/logger/logger.js.map +1 -1
  62. package/dist/logger/transports/console.js +2 -2
  63. package/dist/logger/transports/http.d.ts +1 -1
  64. package/dist/logger/transports/http.js +2 -2
  65. package/dist/logger/transports/webhook.d.ts +1 -1
  66. package/dist/logger/transports/webhook.js +3 -3
  67. package/dist/queue/defaults.d.ts +2 -2
  68. package/dist/queue/defaults.js +38 -38
  69. package/dist/security/defaults.d.ts +1 -1
  70. package/dist/security/defaults.js +30 -30
  71. package/dist/security/index.d.ts +1 -1
  72. package/dist/security/index.js +3 -3
  73. package/dist/security/security.d.ts +1 -1
  74. package/dist/security/security.js +4 -4
  75. package/dist/storage/defaults.js +26 -26
  76. package/dist/storage/index.js +3 -3
  77. package/dist/util/defaults.d.ts +1 -1
  78. package/dist/util/defaults.js +41 -41
  79. package/dist/util/env.d.ts +35 -0
  80. package/dist/util/env.d.ts.map +1 -0
  81. package/dist/util/env.js +50 -0
  82. package/dist/util/env.js.map +1 -0
  83. package/dist/util/errors.d.ts +52 -0
  84. package/dist/util/errors.d.ts.map +1 -0
  85. package/dist/util/errors.js +82 -0
  86. package/dist/util/errors.js.map +1 -0
  87. package/dist/util/util.js +1 -1
  88. package/examples/.env.example +80 -0
  89. package/examples/README.md +16 -0
  90. package/examples/auth.ts +228 -0
  91. package/examples/cache.ts +36 -0
  92. package/examples/config.ts +45 -0
  93. package/examples/database.ts +69 -0
  94. package/examples/email.ts +53 -0
  95. package/examples/error.ts +50 -0
  96. package/examples/event.ts +42 -0
  97. package/examples/logger.ts +41 -0
  98. package/examples/queue.ts +58 -0
  99. package/examples/security.ts +46 -0
  100. package/examples/storage.ts +44 -0
  101. package/examples/util.ts +47 -0
  102. package/llms.txt +591 -0
  103. package/package.json +19 -10
  104. package/src/auth/README.md +850 -0
  105. package/src/cache/README.md +756 -0
  106. package/src/config/README.md +604 -0
  107. package/src/database/README.md +818 -0
  108. package/src/email/README.md +759 -0
  109. package/src/error/README.md +660 -0
  110. package/src/event/README.md +729 -0
  111. package/src/logger/README.md +435 -0
  112. package/src/queue/README.md +851 -0
  113. package/src/security/README.md +612 -0
  114. package/src/storage/README.md +1008 -0
  115. package/src/util/README.md +955 -0
  116. package/bin/templates/backend/docs/APPKIT_CLI.md +0 -507
  117. package/bin/templates/backend/docs/APPKIT_COMMENTS_GUIDELINES.md +0 -61
  118. package/bin/templates/backend/docs/APPKIT_LLM_GUIDE.md +0 -2539
package/AGENTS.md ADDED
@@ -0,0 +1,195 @@
1
+ # AGENTS.md — @bloomneo/appkit
2
+
3
+ > Agent instructions for `@bloomneo/appkit`. This is the **rules** file.
4
+ > For the **full API reference**, read [`llms.txt`](./llms.txt) in the same directory.
5
+ > Both files ship with the package and are accessible via
6
+ > `node_modules/@bloomneo/appkit/AGENTS.md` and `node_modules/@bloomneo/appkit/llms.txt`.
7
+
8
+ ## What this package is
9
+
10
+ `@bloomneo/appkit` is a Node.js backend toolkit with **12 integrated modules**
11
+ that share one canonical pattern: every module exports a `xxxClass` namespace
12
+ object with a `.get()` factory. There is exactly one way to obtain each module
13
+ and exactly one way to use it.
14
+
15
+ Use it for: Express/Fastify backends, JWT auth, multi-tenant database,
16
+ Redis cache, S3 storage, background queues, email, structured logging,
17
+ error handling. **Don't use it for:** frontend code, CLI tools, real-time
18
+ WebSocket-as-primary-feature, non-Node environments.
19
+
20
+ ## The one rule that matters most
21
+
22
+ ```ts
23
+ const auth = authClass.get(); // ALWAYS .get(), NEVER `new AuthClass()`
24
+ ```
25
+
26
+ Every module follows this pattern. There are no exceptions. If you find
27
+ yourself writing `new SomethingClass()` in code that imports from
28
+ `@bloomneo/appkit`, you're doing it wrong.
29
+
30
+ ## Canonical imports — pick one and stay consistent
31
+
32
+ ```ts
33
+ // Option A — flat (preferred for general code):
34
+ import { authClass, databaseClass, errorClass, loggerClass } from '@bloomneo/appkit';
35
+
36
+ // Option B — subpath (preferred when only one module is needed):
37
+ import { authClass } from '@bloomneo/appkit/auth';
38
+ import { databaseClass } from '@bloomneo/appkit/database';
39
+ ```
40
+
41
+ Both work. The subpath form is unusual for AppKit (most users want
42
+ multiple modules in the same file) but it tree-shakes slightly better.
43
+ **Don't mix the two styles in the same file.**
44
+
45
+ ## The 12 modules at a glance
46
+
47
+ | Module | Import | Purpose |
48
+ |---|---|---|
49
+ | `authClass` | `from '@bloomneo/appkit/auth'` | JWT tokens, role.level permissions, middleware |
50
+ | `databaseClass` | `from '@bloomneo/appkit/database'` | Prisma/Mongoose with multi-tenant filtering |
51
+ | `securityClass` | `from '@bloomneo/appkit/security'` | CSRF, rate limiting, encryption, sanitization |
52
+ | `errorClass` | `from '@bloomneo/appkit/error'` | HTTP errors with semantic types |
53
+ | `cacheClass` | `from '@bloomneo/appkit/cache'` | Memory → Redis auto-scaling |
54
+ | `storageClass` | `from '@bloomneo/appkit/storage'` | Local → S3/R2 auto-scaling |
55
+ | `queueClass` | `from '@bloomneo/appkit/queue'` | Memory → Redis → DB scaling |
56
+ | `emailClass` | `from '@bloomneo/appkit/email'` | Console → SMTP → Resend |
57
+ | `eventClass` | `from '@bloomneo/appkit/event'` | Memory → Redis pub/sub |
58
+ | `loggerClass` | `from '@bloomneo/appkit/logger'` | Multi-transport, auto-scaling |
59
+ | `configClass` | `from '@bloomneo/appkit/config'` | Environment-driven config |
60
+ | `utilClass` | `from '@bloomneo/appkit/util'` | Safe property access, debounce, chunk |
61
+
62
+ For full method signatures and examples, read `llms.txt` in this same directory.
63
+
64
+ ## Environment variables
65
+
66
+ AppKit reads env vars with the `BLOOM_*` prefix. There is **no** backwards
67
+ compatibility for the legacy `VOILA_*` prefix from `@voilajsx/appkit` —
68
+ that was removed entirely in 1.5.2.
69
+
70
+ Required for production:
71
+
72
+ ```bash
73
+ BLOOM_AUTH_SECRET=<min 32 chars> # JWT signing key
74
+ DATABASE_URL=postgresql://... # any Prisma-supported URL
75
+ BLOOM_SECURITY_CSRF_SECRET=<min 32 chars> # CSRF protection
76
+ BLOOM_SECURITY_ENCRYPTION_KEY=<64 hex> # AES-256-GCM key
77
+ ```
78
+
79
+ Optional (auto-scaling kicks in when set):
80
+
81
+ ```bash
82
+ REDIS_URL=redis://... # → distributed cache + queue
83
+ AWS_S3_BUCKET=... # → cloud storage
84
+ RESEND_API_KEY=re_... # → professional email
85
+ BLOOM_DB_TENANT=auto # → multi-tenant mode
86
+ ```
87
+
88
+ ## When generating code with AppKit
89
+
90
+ ### Always
91
+
92
+ - **Use `xxxClass.get()`** to obtain a module instance. Cache the result at
93
+ module scope, not inside request handlers.
94
+ - **Wrap async route handlers** in `error.asyncRoute(...)` so thrown errors
95
+ flow into the centralized error middleware.
96
+ - **Use semantic error types**: `error.badRequest('...')`, `error.unauthorized('...')`,
97
+ `error.notFound('...')`, etc. — never `throw new Error(...)` in routes.
98
+ - **Mount `error.handleErrors()` middleware last** in the Express stack.
99
+ - **Use `auth.requireLoginToken()` and `auth.requireUserRoles(['admin.tenant'])`** as
100
+ middleware, not custom token-checking code. Chain in that order:
101
+ `requireLoginToken()` first, then `requireUserRoles([...])` — never standalone,
102
+ never reversed.
103
+ - **Use `cache.getOrSet(key, fetcher, ttl)`** instead of manual cache-check-then-fetch.
104
+ - **Use `logger.get('component-name')`** so logs are tagged.
105
+
106
+ ### Never
107
+
108
+ - **Never write your own JWT helper.** `auth.generateLoginToken()`,
109
+ `auth.generateApiToken()`, and `auth.verifyToken()` cover every case.
110
+ (`signToken` is a private internal — don't reach for it.)
111
+ - **Never instantiate Prisma directly.** `databaseClass.get()` returns the
112
+ shared, tenant-aware client.
113
+ - **Never hand-roll rate limiting.** Use `security.requests(maxRequests, windowMs)`.
114
+ - **Never write a custom file-upload-to-S3 wrapper.** `storage.put()` /
115
+ `storage.get()` / `storage.url()` handle local + S3 + R2 with the same API.
116
+ - **Never read `process.env.X` directly** in business code. Go through
117
+ `config.get('section.key')` so the value is validated and typed.
118
+ - **Never `throw new Error(...)` in a route handler.** Use `error.badRequest(...)`,
119
+ `error.unauthorized(...)`, etc. — they include the right HTTP status code.
120
+
121
+ ## Canonical pattern — protected endpoint with database + logger
122
+
123
+ This is the shape ~80% of AppKit endpoints follow. Generate this as the
124
+ default starting point unless told otherwise:
125
+
126
+ ```ts
127
+ import { authClass, databaseClass, errorClass, loggerClass } from '@bloomneo/appkit';
128
+
129
+ const auth = authClass.get();
130
+ const database = await databaseClass.get();
131
+ const error = errorClass.get();
132
+ const logger = loggerClass.get('users');
133
+
134
+ app.post(
135
+ '/api/users',
136
+ auth.requireLoginToken(), // 1. authenticate the user
137
+ auth.requireUserRoles(['admin.tenant']), // 2. check the role (always chained)
138
+ error.asyncRoute(async (req, res) => {
139
+ if (!req.body?.email) {
140
+ throw error.badRequest('Email required');
141
+ }
142
+
143
+ const newUser = await database.user.create({ data: req.body });
144
+ logger.info('User created', { userId: newUser.id });
145
+
146
+ res.json({ user: newUser });
147
+ })
148
+ );
149
+
150
+ // Last middleware in the stack — handles every thrown semantic error.
151
+ app.use(error.handleErrors());
152
+ ```
153
+
154
+ **Critical chaining rules for the auth middleware:**
155
+ - `requireLoginToken()` MUST come first (it sets `req.user` for downstream).
156
+ - `requireUserRoles([...])` is for role-based access on USER routes — chain it
157
+ AFTER `requireLoginToken()`. Never use it standalone or with API tokens.
158
+ - `requireApiToken()` is for SERVICE routes (webhooks, integrations). Use it
159
+ alone. Never chain `requireUserRoles` after `requireApiToken` — API tokens
160
+ don't have user roles.
161
+
162
+ ## CLI
163
+
164
+ `@bloomneo/appkit` ships a CLI for scaffolding backend projects:
165
+
166
+ ```bash
167
+ appkit generate app myproject # full backend scaffold
168
+ cd myproject && npm run dev:api # → http://localhost:3000
169
+
170
+ appkit generate feature product # basic feature (route + service + types)
171
+ appkit generate feature order --db # database-enabled feature
172
+ appkit generate feature user # full auth system with 9-role hierarchy
173
+ ```
174
+
175
+ For a downstream consumer building with `@bloomneo/bloom`, the bloom CLI
176
+ handles scaffolding instead — appkit's CLI is for users who only want the
177
+ backend, no frontend.
178
+
179
+ ## Migration notes
180
+
181
+ - This package was previously published as `@voilajsx/appkit` (frozen at
182
+ 1.2.8). Run a project-wide find-and-replace of `@voilajsx/appkit` →
183
+ `@bloomneo/appkit`. The API is identical.
184
+ - **BREAKING (1.5.2):** the legacy `VOILA_*` env var prefix is gone.
185
+ Rename every `VOILA_FOO` in your `.env` files to `BLOOM_FOO`. There
186
+ is no fallback, no deprecation warning, no compatibility shim — the
187
+ rebrand is a clean break and consumers upgrading from earlier versions
188
+ must rename in one go.
189
+
190
+ ## Where to look next
191
+
192
+ - **Full API reference**: [`llms.txt`](./llms.txt) (in this directory)
193
+ - **Module source code**: `node_modules/@bloomneo/appkit/dist/` (TypeScript types)
194
+ - **CHANGELOG**: [`CHANGELOG.md`](./CHANGELOG.md) for release history
195
+ - **Issues**: https://github.com/bloomneo/appkit/issues
package/CHANGELOG.md ADDED
@@ -0,0 +1,253 @@
1
+ # Changelog
2
+
3
+ All notable changes to AppKit will be documented in this file.
4
+
5
+ ## [1.5.2] - 2026-04-11
6
+
7
+ ### Behavior fix — `auth.can()` permission resolution
8
+
9
+ **Breaking semantic change in the auth module.** The `permissions` field on
10
+ the JWT payload now correctly **replaces** the role's default permissions
11
+ instead of supplementing them. This matches AWS IAM, Casbin, OPA, Auth0
12
+ RBAC, and every mainstream permission system: explicit permissions are the
13
+ truth, defaults are the fallback.
14
+
15
+ **Old (buggy) behavior:**
16
+ ```ts
17
+ const user = auth.generateLoginToken({
18
+ userId: 1, role: 'admin', level: 'tenant',
19
+ permissions: ['view:own'], // expected: user is restricted to view:own
20
+ });
21
+ auth.can(user, 'manage:tenant'); // returned TRUE (additive — bug)
22
+ // because admin.tenant defaults included it
23
+ ```
24
+
25
+ **New (fixed) behavior:**
26
+ ```ts
27
+ const user = auth.generateLoginToken({
28
+ userId: 1, role: 'admin', level: 'tenant',
29
+ permissions: ['view:own'], // explicit permissions REPLACE role defaults
30
+ });
31
+ auth.can(user, 'manage:tenant'); // now FALSE — defaults are not consulted
32
+ auth.can(user, 'view:own'); // TRUE — exact match
33
+ ```
34
+
35
+ **Resolution rule:**
36
+ - If `user.permissions` is present (any array, including `[]`), it is the
37
+ COMPLETE permission set. Role defaults are NOT consulted.
38
+ - If `user.permissions` is absent, the role.level's default permissions
39
+ from the configured RolePermissionConfig apply.
40
+
41
+ **Action inheritance still works** within whichever set is in scope:
42
+ `manage:scope` grants `view`, `create`, `edit`, `delete` for that scope.
43
+ **No upward inheritance**: `edit:scope` does NOT grant `manage:scope`.
44
+
45
+ **Why this is a fix, not a feature:**
46
+ - The original JSDoc on `can()` said `auth.hasRole('edit:tenant', 'manage:tenant') → FALSE`
47
+ meaning the author intended no upward inheritance. The implementation
48
+ silently bypassed this via the additive fallback. The fix aligns the code
49
+ with its own documented intent.
50
+ - Documentation alone could not fix the consumer trap because the bug was
51
+ silent — devs writing `permissions: ['view:own']` thought they were
52
+ restricting the user, but the user could still do `manage:tenant`. That's
53
+ the worst class of security bug.
54
+ - No deployed consumers were on `@bloomneo/appkit@1.5.1` at the time of this
55
+ fix, so the breaking change has zero blast radius.
56
+
57
+ **Migration:** anyone who was relying on the additive behavior (passing
58
+ `permissions: [...]` expecting it to extend role defaults) needs to either:
59
+ 1. Remove the explicit `permissions` array entirely (defaults will apply)
60
+ 2. Add the role's defaults to the explicit array manually if you want the union
61
+
62
+ **Tests:** `src/auth/auth.test.ts` now has 8 `can()` tests covering
63
+ explicit replacement, no upward inheritance, empty-array downgrade, and
64
+ no-explicit-permissions fallback. 55/55 vitest passing.
65
+
66
+ ### error / logger / database / config module audit
67
+
68
+ - **error**: Added `tooMany()` (429) and `internal()` (500 alias for `serverError()`) as real
69
+ methods on `ErrorClass` and as shortcuts on `errorClass`. Both were previously in example
70
+ comments only — examples referenced them as if callable.
71
+ - **logger**: Added `fatal(message, meta?)` to the `Logger` interface and `LoggerClass`.
72
+ Delegates to `error()` with `{ fatal: true }` in meta. Was in `examples/logger.ts` line 24
73
+ but not in the interface — would throw "not a function" at runtime.
74
+ - **config**: Fixed `examples/config.ts` — `config.isDevelopment()` / `config.isProduction()`
75
+ do NOT exist on the `ConfigClass` instance; they live on `configClass` (the module-level
76
+ object). Fixed to `configClass.isDevelopment()` with a comment explaining the distinction.
77
+ Also fixed `config.getNumber()` / `config.getBoolean()` — neither exist on `ConfigClass`;
78
+ examples now use `Number(config.get(...))` / `config.get(...) === 'true'`.
79
+ - **database**: `disconnect()` error prefix changed to `[@bloomneo/appkit/database]` for
80
+ consistency with other modules.
81
+ - Added test files: `src/error/error.test.ts` (31 tests), `src/logger/logger.test.ts`
82
+ (25 tests), `src/config/config.test.ts` (31 tests), `src/database/database.test.ts`
83
+ (9 tests). **Total: 216/216 vitest passing.**
84
+
85
+ ### Cache module audit
86
+
87
+ - Fixed `examples/cache.ts`: `cache.del()` → `cache.delete()` (wrong name), removed
88
+ `cache.has()` (internal `CacheStrategy` method, not on the public `Cache` interface)
89
+ - Fixed `src/cache/README.md` testing section: `cacheClass.clear()` (disconnects all
90
+ instances) → `cacheClass.flushAll()` (clears cached data — the right call between tests)
91
+ - Added `src/cache/cache.test.ts` (49 vitest tests, full public API coverage, drift-check
92
+ section asserting hallucinated method names `del` and `has` don't exist on the public
93
+ `Cache` interface)
94
+ - Score block added to `src/cache/README.md`: **75.3/100 🟡 Solid** (no cap).
95
+ - Added `CacheError` class (exported from `@bloomneo/appkit/cache`) — all cache
96
+ operations now throw `CacheError` instead of swallowing errors via `console.error`.
97
+ Use `instanceof CacheError` to distinguish infrastructure failures from your own
98
+ errors and decide whether to fall back or re-throw. Error codes: `CACHE_GET_FAILED`,
99
+ `CACHE_SET_FAILED`, `CACHE_DELETE_FAILED`, `CACHE_CLEAR_FAILED`, `CACHE_CONNECT_FAILED`,
100
+ `CACHE_INVALID_KEY`, `CACHE_INVALID_VALUE`.
101
+ - `cacheClass.clear()` renamed to `cacheClass.disconnectAll()` — eliminates the naming
102
+ collision with `cache.clear()` (which clears data in a namespace). The two methods had
103
+ opposite effects under the same name.
104
+ - Added generics to `Cache` interface: `get<T>()`, `set<T>()`, `getOrSet<T>()` — no more
105
+ `any` casts when working with typed values.
106
+ - All error messages use `[@bloomneo/appkit/cache]` prefix (consistent with auth module).
107
+
108
+ ### Other fixes (auth-only revamp)
109
+
110
+ - Added `src/auth/auth.test.ts` (55 tests, full public API coverage,
111
+ drift-check section asserting hallucinated method names don't exist)
112
+ - Added `vitest.setup.ts` for env var bootstrapping before module init
113
+ - Updated `vitest.config.js` to wire the setup file
114
+ - Fixed README hero example: `auth.requireRole` → `auth.requireLoginToken()` +
115
+ `auth.requireUserRoles([...])` (was hallucinated)
116
+ - Fixed AGENTS.md `auth.requireLogin()` / `requireRole()` / `signToken()` →
117
+ real method names
118
+ - Fixed llms.txt auth section: rewrote 12 method signatures, role hierarchy,
119
+ middleware chaining rules, and 2 worked examples to match runtime
120
+ - Fixed src/auth/README.md `service.webhook` / `api.external` examples to use
121
+ valid `admin.system` role.level (the old examples threw at runtime)
122
+ - Fixed `examples/auth.ts` to demonstrate all 12 public methods (added
123
+ `verifyTokenManually` + `permissionCheckHandler`)
124
+ - Improved auth.ts runtime errors: now `[@bloomneo/appkit/auth] message + DOCS_URL#anchor`
125
+ format so devs and AI agents can self-correct from the error alone
126
+ - Added `AGENT_DEV_SCORING_ALGORITHM.md` at repo root: 15-dimension rubric
127
+ for scoring AI-agent + dev friendliness, applied to the auth module first
128
+ - Added agent-dev friendliness score block to `src/auth/README.md`
129
+ (current: 83.6/100 uncapped, 50/100 capped due to broken cookbook files
130
+ pending repair in a follow-up release)
131
+ - Added "Which case is your app?" decision tree to `src/auth/README.md`
132
+ mapping the 9-level default hierarchy to three real-world app shapes:
133
+ Case 1 (admin + users, ~50% of apps), Case 2 (admin + orgs + users, ~30%),
134
+ Case 3 (admin + orgs + tenants, ~20%). Establishes a 3-role core
135
+ (`user.basic` + `moderator.manage` + `admin.system`) shared by all cases,
136
+ with pricing tiers (`user.pro`, `user.max`) and admin level scoping
137
+ (`admin.tenant`, `admin.org`) marked optional. Multi-tenancy is reframed
138
+ as a database concern (`BLOOM_DB_TENANT=auto`), not an auth concern.
139
+ Lifts the auth README's Reading Order score 9 → 10 and Learning Curve 7 → 9.
140
+
141
+ ### security / util / queue / storage / email / event module audit
142
+
143
+ - **security**: Fixed `examples/security.ts` — removed hallucinated `csrf()`, `requireCsrf()`,
144
+ `email()`, `url()`. Real CSRF method is `forms()` (single middleware handles both injection
145
+ and validation). Added `html()` and `escape()` examples. Noted that email/URL validation
146
+ should use zod or validator.js.
147
+ - **util**: Fixed `examples/util.ts` — removed `util.set()`, `util.omit()`, `util.throttle()`,
148
+ `util.retry()` (none exist). Added real methods: `util.unique()`, `util.clamp()`,
149
+ `util.truncate()`. Added note that `util.get()` is read-only (no `set()`), `util.pick()` is
150
+ the correct "exclude keys" approach (no `omit()`).
151
+ - **queue**: Fixed `examples/queue.ts` — `retries: 3` → `attempts: 3` in `JobOptions`.
152
+ `queue.schedule('name', '0 3 * * *', {})` (cron-style, wrong) → `queue.schedule('name', {}, delayMs)`
153
+ (delay in milliseconds — there is no built-in cron scheduler; use node-cron to call `queue.add()`).
154
+ - **storage**: Fixed `examples/storage.ts` — `storage.has(key)` → `storage.exists(key)`.
155
+ - **email**: Fixed `examples/email.ts` — `email.send({ template, data })` → `email.sendTemplate(name, data)`.
156
+ Added note that `EmailData` has no `template` or `data` fields.
157
+ - **event**: `examples/event.ts` was already correct — no changes.
158
+ - Added test files for all 6 modules: `src/security/security.test.ts`, `src/util/util.test.ts`,
159
+ `src/queue/queue.test.ts`, `src/storage/storage.test.ts`, `src/email/email.test.ts`,
160
+ `src/event/event.test.ts`. Each includes a drift-check section asserting hallucinated
161
+ method names do not exist at runtime.
162
+
163
+ ### Cookbook fixes
164
+
165
+ All 5 cookbook files corrected — the two root hallucinations that had propagated everywhere:
166
+ - `auth.requireLogin()` → `auth.requireLoginToken()` (5 occurrences across 4 files)
167
+ - `auth.requireRole('admin.tenant')` → `auth.requireUserRoles(['admin.tenant'])` (6 occurrences)
168
+ - `cache.del()` → `cache.delete()` (`multi-tenant-saas.ts`)
169
+ - `{ retries: 3 }` → `{ attempts: 3 }` + updated inline comment (`file-upload-pipeline.ts`)
170
+
171
+ ### llms.txt and AGENTS.md
172
+
173
+ - Added `AGENTS.md` (new file): concise agent rules — always/never lists, canonical patterns,
174
+ CLI reference, migration notes. Ships with the package for consumption by AI coding agents.
175
+ - Fixed `llms.txt` — 7 sections with stale or hallucinated API:
176
+ - Security: corrected to `forms()`, `html()`, `escape()`; removed `csrf()`, `requireCsrf()`,
177
+ `email()`, `url()`
178
+ - Cache: `del()` → `delete()`; removed `has()` with null-check pattern documented
179
+ - Storage: `del()` → `delete()`; `has()` → `exists()`; added `list()`, `copy()`
180
+ - Queue: `retries` → `attempts`; `schedule(name, cron, data)` → `schedule(name, data, delayMs)`
181
+ - Email: removed `template`/`data` from `EmailData`; added `sendTemplate()` signature
182
+ - Util: removed `set()`, `omit()`, `throttle()`, `retry()`; added `isEmpty()`, `unique()`,
183
+ `clamp()`, `truncate()`
184
+ - Config: split instance methods (`get`, `has`, `getRequired`, `getMany`, `getAll`) vs
185
+ module-level helpers (`configClass.isDevelopment()` / `isProduction()` / `isTest()`);
186
+ documented `getNumber()`/`getBoolean()` pattern via `Number()` / `=== 'true'`
187
+
188
+ ### VOILA_* env var prefix removed (breaking change)
189
+
190
+ The legacy `VOILA_*` env var prefix is gone entirely. Rename in your `.env` files:
191
+ - `VOILA_AUTH_SECRET` → `BLOOM_AUTH_SECRET`
192
+ - `VOILA_SECURITY_CSRF_SECRET` → `BLOOM_SECURITY_CSRF_SECRET`
193
+ - `VOILA_SECURITY_ENCRYPTION_KEY` → `BLOOM_SECURITY_ENCRYPTION_KEY`
194
+ - And so on for all other `VOILA_*` vars.
195
+
196
+ There is no fallback, no deprecation warning, no compatibility shim.
197
+
198
+ ## [1.5.1] - 2026-04-11
199
+
200
+ > **Note on version jump.** Previous releases of `@bloomneo/appkit` were `1.2.9`
201
+ > and earlier (and the package was previously published as `@voilajsx/appkit`
202
+ > at `1.2.8`). This release jumps to `1.5.1` to align with the
203
+ > bloomneo trio (`@bloomneo/uikit@1.5.1`, `@bloomneo/appkit@1.5.1`,
204
+ > `@bloomneo/bloom@1.5.1`) so consumers can install matched versions in one
205
+ > step. **No breaking changes** between 1.2.9 and 1.5.1 — every export, every
206
+ > method, every default behavior is identical. The version bump is purely for
207
+ > trio alignment.
208
+
209
+ ### Fixed
210
+
211
+ - **Stale `[VoilaJSX AppKit]` brand strings in runtime warnings.** The 1.2.9
212
+ rebrand updated the package metadata, README, and documentation but missed
213
+ ~70 hardcoded brand strings inside the source files (warning messages, log
214
+ prefixes, HTTP `User-Agent` headers, JSDoc comments). Smoke testing surfaced
215
+ these as `[VoilaJSX AppKit] Environment variable …` warnings printed to
216
+ consumer terminals. All cleaned up:
217
+ - `[VoilaJSX AppKit]` → `[Bloomneo AppKit]` (runtime warning prefix in
218
+ `cache/defaults.ts`, `util/defaults.ts`, `config/defaults.ts`)
219
+ - `[VoilaJSX Utils]` → `[Bloomneo Utils]` (in `util/util.ts`)
220
+ - HTTP `User-Agent: VoilaJSX-AppKit-Logging/1.0.0` → `Bloomneo-AppKit-Logging/1.0.0`
221
+ (in `logger/transports/http.ts` and `logger/transports/webhook.ts`)
222
+ - HTTP `User-Agent: VoilaJSX-AppKit-Email/1.0.0` → `Bloomneo-AppKit-Email/1.0.0`
223
+ (in `email/strategies/resend.ts`)
224
+ - Webhook footer string `VoilaJSX AppKit Logging` → `Bloomneo AppKit Logging`
225
+ (in `logger/transports/webhook.ts`)
226
+ - JSDoc references to `VoilaJSX framework`, `VoilaJSX standard`,
227
+ `VoilaJSX app discovery`, `VoilaJSX structure`, `VoilaJSX startup` →
228
+ all renamed to `Bloomneo` equivalents
229
+ - Module README license footers `MIT © [VoilaJSX]` → `MIT © [Bloomneo]`
230
+
231
+ ### Not changed (in 1.5.1 — see 1.5.2 for the env var rename)
232
+
233
+ - **`VOILA_*` environment variable prefix was unchanged in 1.5.1.** At the
234
+ time, AppKit still read `VOILA_AUTH_SECRET`, `VOILA_DB_URL`, etc. Renaming
235
+ the prefix was deferred from this release. The prefix was treated as a
236
+ schema convention, not a brand mention.
237
+ - **The `VOILA_*` prefix was removed entirely in the next release (1.5.2).**
238
+ See the 1.5.2 entry below for migration instructions.
239
+
240
+ ### Verification
241
+
242
+ - Final grep sweep: 0 `VoilaJSX` references in `src/`
243
+ - `npm run build` (tsc): green, all 11 sub-modules compile
244
+ - `npm pack --dry-run`: tarball name updated to `bloomneo-appkit-1.5.1.tgz`
245
+
246
+ ## [1.2.9] - 2026-04-10
247
+
248
+ Republish under the `@bloomneo` scope (was `@voilajsx/appkit`). API,
249
+ behavior, and types are identical to `@voilajsx/appkit@1.2.8`. The
250
+ `@voilajsx` npm account was lost; this release migrates the package to
251
+ the new `@bloomneo` namespace. Run `npm install @bloomneo/appkit` and
252
+ do a project-wide find-and-replace of `@voilajsx/appkit` →
253
+ `@bloomneo/appkit` to migrate.