@usequark/quark-core 2.6.0 → 2.6.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/README.md CHANGED
@@ -4,15 +4,21 @@ Shared infrastructure for the Quark platform - authentication, job queues, error
4
4
 
5
5
  ## What's Included
6
6
 
7
- - **Authentication** - Auth.js-compatible config helpers (`createAuthConfig`, `requireAuth`, password hashing) plus framework-agnostic session guards (`requireSession`, `requireSessionRole`, `createAuthMiddleware`)
8
- - **Database** - Prisma client singleton (`createPrismaClient`), connection-string/pool helpers, and a `pingDatabase()` health check
9
- - **Admin** - Prisma schema introspection (`parseSchema`, `getModels`, `getEnums`, `coerceId`) for auto-generated admin UIs
10
- - **Job Queue** - BullMQ integration (`createQueue`, `createWorker`, `addJob`)
11
- - **Storage** - Local filesystem built in; S3/R2/MinIO via optional AWS SDK install in the app
12
- - **Errors** - Standardized error types (`ValidationError`, `NotFoundError`, `UnauthorizedError`, etc.)
13
- - **Utilities** - `retryAsync`, `deepMerge`, `randomString`, `sanitizeId`, `measureTime`, `memoize`
7
+ - **Authentication** - Auth.js-compatible config helpers (`createAuthConfig`, `requireAuth`, password hashing) plus framework-agnostic session guards (`requireSession`, `requireSessionRole`, `createAuthMiddleware`), and role checks (`authorization.js`)
8
+ - **Database** - `createPrismaClient(ClientClass)` builds a client from your generated Prisma class, plus connection-string/pool helpers, `pingDatabase()`, and `db-instrumentation`
9
+ - **Job Queue** - BullMQ integration (`createQueue`, `createWorker`, `addJob`, queue definitions)
10
+ - **Email** - Provider registry (`EmailProvider`, `registerEmailProvider`), `createEmailService`, and built-in templates
11
+ - **Storage** - Local filesystem built in; S3 and any S3-compatible endpoint (R2, MinIO) via optional AWS SDK install in the app
12
+ - **Metrics** - Prometheus-style registry (`createMetrics`, `metrics`, `httpRequestsTotal`, `jobQueueDepth`, `jobDuration`, and more)
13
+ - **Health** - `runHealthChecks`, `createDefaultProbes`, `checkStorage`, `checkQueues`, `logHealthReport`
14
+ - **Security** - CSRF tokens, rate limiting (in-memory with Redis fallback), file and multipart validation, request logging, error reporting
15
+ - **Errors** - Standardized error types (`ValidationError`, `NotFoundError`, `UnauthorizedError`, `AppError`, and others)
16
+ - **Utilities** - `retryAsync`, `deepMerge`, `randomString`, `sanitizeId`, `measureTime`, `memoize`, query builder, pagination, cache, locale helpers
14
17
  - **Validation** - Zod-based request body validation
18
+ - **Payments and messaging** - Stripe client and webhook helpers, SMS provider
19
+ - **Schema introspection** - `parseSchema`, `getModels`, `getEnums`, `getModelByName`, `coerceId`, `resetSchemaCache`
15
20
  - **Redis / Mail** - Connection helpers for Redis and local mail (Mailpit)
21
+ - **Testing** - factories, mocks, and helpers under `@usequark/quark-core/testing`
16
22
 
17
23
  ## Usage
18
24
 
@@ -34,18 +40,30 @@ All modules are re-exported from the package root. See JSDoc comments on each fu
34
40
 
35
41
  Prefer a subpath when you want a smaller import graph or the browser-safe logger:
36
42
 
43
+ Every subpath in `packages/core/package.json` `exports`, verified against the file:
44
+
37
45
  | Subpath | Contents |
38
46
  |---|---|
47
+ | `@usequark/quark-core` | everything below, re-exported from the root |
39
48
  | `@usequark/quark-core/core` | errors, logger, validation, utils, pagination, csrf, rate limiter, file validation, db |
40
- | `@usequark/quark-core/db` | Prisma singleton, connection string, pool config, `pingDatabase()` |
41
- | `@usequark/quark-core/admin` | Prisma schema introspection helpers |
42
49
  | `@usequark/quark-core/auth` | auth config helpers + session guards |
43
50
  | `@usequark/quark-core/auth/middleware` | session guards only |
51
+ | `@usequark/quark-core/db` | `createPrismaClient`, connection string, pool config, `pingDatabase()` |
52
+ | `@usequark/quark-core/queue` | BullMQ queue and worker helpers |
53
+ | `@usequark/quark-core/email` | `EmailProvider`, `registerEmailProvider`, `createEmailService` |
54
+ | `@usequark/quark-core/errors` | error classes only |
55
+ | `@usequark/quark-core/health` | health check runners and default probes |
56
+ | `@usequark/quark-core/metrics` | metrics registry and default instruments |
44
57
  | `@usequark/quark-core/storage` | local adapter, storage factory, key/URL helpers, `createS3Storage` |
45
58
  | `@usequark/quark-core/storage/s3` | S3 adapter only |
46
- | `@usequark/quark-core/logger` | browser-safe logger |
47
- | `@usequark/quark-core/locale` | locale helpers |
59
+ | `@usequark/quark-core/sms` | SMS provider |
48
60
  | `@usequark/quark-core/stripe` | Stripe client + webhook helpers |
61
+ | `@usequark/quark-core/admin` | Prisma schema introspection helpers |
62
+ | `@usequark/quark-core/locale` | locale helpers |
63
+ | `@usequark/quark-core/logger` | browser-safe logger |
64
+ | `@usequark/quark-core/testing` | test factories, mocks, helpers |
65
+
66
+ `createPrismaClient` is a factory, not a singleton: it takes your generated Prisma `Client` class as its argument so core never depends on your generated client path.
49
67
 
50
68
  `@aws-sdk/client-s3`, `@aws-sdk/s3-request-presigner`, `pg`, and `stripe` are optional peer dependencies. They are only loaded when the matching feature is used, so apps that do not use S3, `pingDatabase()`, or payments do not need them installed.
51
69
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usequark/quark-core",
3
- "version": "2.6.0",
3
+ "version": "2.6.1",
4
4
  "description": "Core utilities for Quark apps: auth, BullMQ queues, Redis, email, storage, rate limiting, logging, and error handling",
5
5
  "keywords": [
6
6
  "quark",
package/src/csrf.js CHANGED
@@ -96,12 +96,29 @@ export function requireCsrfToken(request) {
96
96
  /**
97
97
  * Creates a CSRF-protected API route handler.
98
98
  * Wraps your handler and automatically validates CSRF tokens.
99
+ *
100
+ * A failed check produces a `401` Response rather than a thrown error. The check
101
+ * runs in this wrapper, outside the route handler's own try/catch, so a throw here
102
+ * escapes `handleError` entirely: the client got an unhandled rejection instead of
103
+ * the status code its fetch handling expects. Returning the Response here keeps the
104
+ * rejection path reserved for genuine faults.
105
+ *
99
106
  * @param {Function} handler - Your API route handler
100
107
  * @returns {Function} Wrapped handler with CSRF protection
101
108
  */
102
109
  export function withCsrfProtection(handler) {
103
110
  return async (request, ...args) => {
104
- requireCsrfToken(request);
111
+ try {
112
+ requireCsrfToken(request);
113
+ } catch (error) {
114
+ if (error instanceof UnauthorizedError) {
115
+ return Response.json(error.toJSON(), {
116
+ status: error.statusCode,
117
+ headers: { "Cache-Control": "no-store" },
118
+ });
119
+ }
120
+ throw error;
121
+ }
105
122
  return handler(request, ...args);
106
123
  };
107
124
  }
package/src/csrf.test.js CHANGED
@@ -166,4 +166,80 @@ test("CSRF Module", async (t) => {
166
166
  assert.strictEqual(result.body, "success");
167
167
  assert.strictEqual(result.request, request);
168
168
  });
169
+
170
+ await t.test(
171
+ "withCsrfProtection returns 401 instead of throwing",
172
+ async () => {
173
+ // The check runs in the wrapper, outside the route handler's own
174
+ // try/catch, so a throw here escapes `handleError` and reaches the client
175
+ // as an unhandled rejection. The status code is what fetch handling expects.
176
+ const request = mockRequest({
177
+ method: "POST",
178
+ url: "http://localhost/api/posts",
179
+ headers: { "x-csrf-token": generateCsrfToken() },
180
+ });
181
+
182
+ let handlerCalled = false;
183
+ const protectedHandler = withCsrfProtection(async () => {
184
+ handlerCalled = true;
185
+ return { unreachable: true };
186
+ });
187
+
188
+ const response = await protectedHandler(request);
189
+
190
+ assert(response instanceof Response);
191
+ assert.strictEqual(response.status, 401);
192
+ assert.strictEqual(response.headers.get("Cache-Control"), "no-store");
193
+
194
+ const body = await response.json();
195
+ assert.strictEqual(body.code, "UNAUTHORIZED");
196
+ assert.strictEqual(body.statusCode, 401);
197
+ // The handler must not run on a rejected token.
198
+ assert.strictEqual(handlerCalled, false);
199
+ },
200
+ );
201
+
202
+ await t.test(
203
+ "withCsrfProtection returns 401 for a mismatched token",
204
+ async () => {
205
+ const token = generateCsrfToken();
206
+ const request = mockRequest({
207
+ method: "POST",
208
+ url: "http://localhost/api/posts",
209
+ headers: {
210
+ "x-csrf-token": "wrong-token-of-same-len",
211
+ cookie: `csrf_token=${encodeURIComponent(token)}`,
212
+ },
213
+ });
214
+
215
+ const protectedHandler = withCsrfProtection(async () => ({
216
+ unreachable: true,
217
+ }));
218
+ const response = await protectedHandler(request);
219
+
220
+ assert.strictEqual(response.status, 401);
221
+ },
222
+ );
223
+
224
+ await t.test(
225
+ "withCsrfProtection still rejects for a non-CSRF error",
226
+ async () => {
227
+ // Only UnauthorizedError is converted. Anything else from the check is a
228
+ // genuine fault and must keep propagating rather than being reported to the
229
+ // client as an authentication problem.
230
+ const request = mockRequest({ method: "POST" });
231
+ request.headers.get = () => {
232
+ throw new TypeError("headers.get exploded");
233
+ };
234
+
235
+ const protectedHandler = withCsrfProtection(async () => ({
236
+ unreachable: true,
237
+ }));
238
+
239
+ await assert.rejects(
240
+ () => protectedHandler(request),
241
+ /headers.get exploded/,
242
+ );
243
+ },
244
+ );
169
245
  });
package/src/db.js CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Shared Prisma client singleton, connection-string builder, pool config,
5
5
  * and a lightweight health-check ping. These replace the per-project
6
- * re-implementations in Regina / Pawdora / Trenchmate.
6
+ * re-implementations that every app ends up writing by hand.
7
7
  *
8
8
  * Usage:
9
9
  * import { createPrismaClient, getConnectionString, pingDatabase } from "@usequark/quark-core/db";