@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
@@ -0,0 +1,41 @@
1
+ /**
2
+ * CANONICAL PATTERN — structured logging with component tagging.
3
+ *
4
+ * Copy this file when you need logging. The pattern: get a component-tagged
5
+ * logger at module scope, then call .info() / .warn() / .error() with a
6
+ * message + meta object. Logs are structured JSON in production, pretty
7
+ * console output in development.
8
+ *
9
+ * Set BLOOM_LOGGER_FILE_PATH to also write to a file.
10
+ * Set BLOOM_LOGGER_HTTP_URL to ship to a centralized log collector.
11
+ * Same code works in all three modes.
12
+ */
13
+
14
+ import { loggerClass } from '@bloomneo/appkit';
15
+
16
+ // Component-tagged logger — use one per file or per logical area.
17
+ const logger = loggerClass.get('users');
18
+
19
+ export function exampleLogging() {
20
+ logger.debug('Lookup started', { userId: 42 }); // dev only
21
+ logger.info('User created', { userId: 42, email: 'a@b' });
22
+ logger.warn('Slow query', { ms: 3200, table: 'users' });
23
+ logger.error('Database connection lost', { host: 'db1' });
24
+ logger.fatal('OOM, exiting', { rss: process.memoryUsage().rss });
25
+ }
26
+
27
+ // ── Inside a route handler ──────────────────────────────────────────
28
+ import { errorClass } from '@bloomneo/appkit';
29
+ const error = errorClass.get();
30
+
31
+ export const createUserRoute = error.asyncRoute(async (req, res) => {
32
+ logger.info('Creating user', { ip: req.ip, email: req.body.email });
33
+ try {
34
+ // ...create logic
35
+ logger.info('User created successfully', { userId: 123 });
36
+ res.json({ ok: true });
37
+ } catch (e) {
38
+ logger.error('User creation failed', { error: (e as Error).message });
39
+ throw e;
40
+ }
41
+ });
@@ -0,0 +1,58 @@
1
+ /**
2
+ * CANONICAL PATTERN — background job queue with auto-scaling backend.
3
+ *
4
+ * Copy this file when you need background jobs. Default backend is in-process
5
+ * memory (good for dev). Set REDIS_URL to upgrade to distributed Redis.
6
+ *
7
+ * Same code works for all backends — no changes needed.
8
+ *
9
+ * queue.add(jobType, data, options) — enqueue immediately or with a fixed delay
10
+ * queue.schedule(jobType, data, delayMs) — alias: enqueue with a delay in ms
11
+ * queue.process(jobType, handler) — register a worker for that job type
12
+ *
13
+ * For cron-style recurring jobs: use a cron library (node-cron, etc.) to call
14
+ * queue.add() at the right interval — there is no built-in cron scheduler.
15
+ */
16
+
17
+ import { queueClass, loggerClass, errorClass } from '@bloomneo/appkit';
18
+
19
+ const queue = queueClass.get();
20
+ const logger = loggerClass.get('queue');
21
+ const error = errorClass.get();
22
+
23
+ // ── Producer: enqueue jobs from a route ─────────────────────────────
24
+ export const enqueueEmailRoute = error.asyncRoute(async (req, res) => {
25
+ const { to, subject, body } = req.body;
26
+
27
+ await queue.add(
28
+ 'send-email',
29
+ { to, subject, body },
30
+ {
31
+ delay: 0, // run immediately (ms)
32
+ attempts: 3, // retry up to 3 times on failure (not "retries")
33
+ priority: 1, // lower number = higher priority
34
+ },
35
+ );
36
+
37
+ res.json({ queued: true });
38
+ });
39
+
40
+ // ── Consumer: process jobs (run in a worker process or main app) ────
41
+ queue.process('send-email', async (data) => {
42
+ const { to, subject, body } = data as { to: string; subject: string; body: string };
43
+ logger.info('Email sent', { to, subject });
44
+ return { sent: true };
45
+ });
46
+
47
+ // ── Delayed job (run once after N ms, not cron-style) ───────────────
48
+ // To run cleanup every day at 03:00, use node-cron to call queue.add() at the
49
+ // right time — queue.schedule() accepts a delay in milliseconds, not a cron expression.
50
+ await queue.schedule(
51
+ 'cleanup-expired-tokens',
52
+ {},
53
+ 8 * 60 * 60 * 1000, // run once, 8 hours from now
54
+ );
55
+
56
+ queue.process('cleanup-expired-tokens', async () => {
57
+ logger.info('Token cleanup ran');
58
+ });
@@ -0,0 +1,46 @@
1
+ /**
2
+ * CANONICAL PATTERN — rate limiting, CSRF, encryption, input sanitization.
3
+ *
4
+ * Copy this file when you need security middleware. AppKit's security module
5
+ * covers four common needs from one xxxClass.get() instance.
6
+ *
7
+ * Required env: BLOOM_SECURITY_CSRF_SECRET, BLOOM_SECURITY_ENCRYPTION_KEY
8
+ */
9
+
10
+ import { securityClass, errorClass } from '@bloomneo/appkit';
11
+
12
+ const security = securityClass.get();
13
+ const error = errorClass.get();
14
+
15
+ // ── Rate limiting middleware ────────────────────────────────────────
16
+ // 100 requests per 15-minute window per IP
17
+ export const apiRateLimit = security.requests(100, 15 * 60 * 1000);
18
+
19
+ // 5 requests per minute (e.g. for a login endpoint)
20
+ export const loginRateLimit = security.requests(5, 60 * 1000);
21
+
22
+ // ── CSRF protection ─────────────────────────────────────────────────
23
+ // security.forms() is a single middleware that handles BOTH:
24
+ // - Injecting a CSRF token into the response (GET requests)
25
+ // - Validating the token on state-changing requests (POST/PUT/DELETE)
26
+ // Mount it once globally — no separate "require" middleware needed.
27
+ export const csrfMiddleware = security.forms();
28
+
29
+ // ── Encryption (AES-256-GCM) ────────────────────────────────────────
30
+ export function encryptApiKey(plaintext: string): string {
31
+ return security.encrypt(plaintext); // → opaque ciphertext string
32
+ }
33
+
34
+ export function decryptApiKey(ciphertext: string): string {
35
+ return security.decrypt(ciphertext);
36
+ }
37
+
38
+ // ── Input sanitization ──────────────────────────────────────────────
39
+ // security.input() strips XSS payloads and control characters from free-form text.
40
+ // For email/URL validation, use a dedicated validation library (e.g. zod, validator.js).
41
+ export const submitRoute = error.asyncRoute(async (req, res) => {
42
+ const safeMessage = security.input(req.body.message); // strip XSS
43
+ const safeHtml = security.html(req.body.bio); // allow safe HTML tags only
44
+
45
+ res.json({ ok: true, sanitized: { message: safeMessage, bio: safeHtml } });
46
+ });
@@ -0,0 +1,44 @@
1
+ /**
2
+ * CANONICAL PATTERN — file upload + retrieval with auto-scaling backend.
3
+ *
4
+ * Copy this file when you need to store files. The same code works against
5
+ * local disk (default), AWS S3 (set AWS_S3_BUCKET), or Cloudflare R2
6
+ * (set R2_BUCKET). No code changes needed — just .env.
7
+ */
8
+
9
+ import { storageClass, errorClass, securityClass } from '@bloomneo/appkit';
10
+ import type { Request, Response } from 'express';
11
+
12
+ const storage = storageClass.get();
13
+ const error = errorClass.get();
14
+ const security = securityClass.get();
15
+
16
+ // ── Upload (with rate limit + filename sanitization) ────────────────
17
+ export const uploadRoute = [
18
+ security.requests(10, 60_000), // max 10 uploads per minute per IP
19
+ error.asyncRoute(async (req: Request & { file: any }, res: Response) => {
20
+ if (!req.file) throw error.badRequest('File required (use multer middleware)');
21
+
22
+ // Sanitize the filename so a malicious user can't write to ../../../etc/...
23
+ const safeName = security.input(req.file.originalname);
24
+ const key = `uploads/${Date.now()}-${safeName}`;
25
+
26
+ await storage.put(key, req.file.buffer, {
27
+ contentType: req.file.mimetype,
28
+ });
29
+
30
+ res.json({
31
+ key,
32
+ url: storage.url(key), // public URL (or signed URL for private buckets)
33
+ });
34
+ }),
35
+ ];
36
+
37
+ // ── Download (signed URL — works with private S3 buckets) ───────────
38
+ export const downloadRoute = error.asyncRoute(async (req, res) => {
39
+ const key = req.params.key;
40
+ if (!(await storage.exists(key))) throw error.notFound('File not found');
41
+
42
+ const signedUrl = await storage.signedUrl(key, 3600); // valid for 1 hour
43
+ res.json({ url: signedUrl });
44
+ });
@@ -0,0 +1,47 @@
1
+ /**
2
+ * CANONICAL PATTERN — small zero-dependency utility helpers.
3
+ *
4
+ * Copy this file when you need any of the util methods. These are the
5
+ * common Node.js helpers that get reinvented in every project — appkit
6
+ * ships them once with consistent semantics.
7
+ *
8
+ * Public methods: get, isEmpty, slugify, chunk, debounce, pick,
9
+ * unique, clamp, formatBytes, truncate, sleep, uuid
10
+ */
11
+
12
+ import { utilClass } from '@bloomneo/appkit';
13
+
14
+ const util = utilClass.get();
15
+
16
+ // ── Safe deep property access (read-only) ───────────────────────────
17
+ const obj = { a: { b: { c: 42 } } };
18
+ const value = util.get(obj, 'a.b.c', 0); // → 42
19
+ const missing = util.get(obj, 'a.b.x.y', 'default'); // → 'default' (safe)
20
+ // Note: there is no util.set() — use plain assignment for mutation.
21
+
22
+ // ── Subset ──────────────────────────────────────────────────────────
23
+ const user = { id: 1, email: 'a@b', password: 'secret', role: 'admin' };
24
+ const minimal = util.pick(user, ['id', 'email']); // → { id, email }
25
+ // Note: there is no util.omit() — use util.pick() with the keys you want to keep.
26
+
27
+ // ── Array helpers ───────────────────────────────────────────────────
28
+ const items = [1, 2, 3, 4, 5, 6, 7, 8, 9];
29
+ const batches = util.chunk(items, 3); // → [[1,2,3], [4,5,6], [7,8,9]]
30
+ const deduped = util.unique([1, 2, 2, 3, 3]); // → [1, 2, 3]
31
+
32
+ // ── Function helpers ────────────────────────────────────────────────
33
+ const debouncedSearch = util.debounce((q: string) => {
34
+ // ...api call
35
+ }, 300);
36
+ // Note: there is no util.throttle() — use util.debounce() for rate-limiting calls.
37
+
38
+ // ── Misc ────────────────────────────────────────────────────────────
39
+ const id = util.uuid(); // → 'a1b2c3d4-...'
40
+ const slug = util.slugify('My Awesome Post!'); // → 'my-awesome-post'
41
+ const size = util.formatBytes(1_572_864); // → '1.5 MB'
42
+ const clamped = util.clamp(150, 0, 100); // → 100
43
+ const short = util.truncate('A very long string', 10); // → 'A very lon...'
44
+
45
+ await util.sleep(100); // → wait 100ms
46
+
47
+ export { value, missing, minimal, batches, deduped, id, slug, size, clamped, short };