honertia 0.1.41 → 0.1.43
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 +52 -0
- package/dist/effect/error-catalog.d.ts +1 -0
- package/dist/effect/error-catalog.d.ts.map +1 -1
- package/dist/effect/error-catalog.js +17 -1
- package/dist/effect/index.d.ts +1 -1
- package/dist/effect/index.d.ts.map +1 -1
- package/dist/effect/routing.d.ts +4 -1
- package/dist/effect/routing.d.ts.map +1 -1
- package/dist/effect/routing.js +4 -1
- package/dist/effect/validation.d.ts +43 -0
- package/dist/effect/validation.d.ts.map +1 -1
- package/dist/effect/validation.js +114 -15
- package/dist/schema.d.ts +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1295,6 +1295,30 @@ const input = yield* validateRequest(CreateEventSchema, {
|
|
|
1295
1295
|
})
|
|
1296
1296
|
```
|
|
1297
1297
|
|
|
1298
|
+
### Request Input Sources
|
|
1299
|
+
|
|
1300
|
+
`validateRequest(schema)` uses the legacy merge order by default:
|
|
1301
|
+
`params -> query -> body` (later sources win).
|
|
1302
|
+
|
|
1303
|
+
Use `options.request` to switch behavior:
|
|
1304
|
+
|
|
1305
|
+
```typescript
|
|
1306
|
+
// Laravel-style input (query + body; route params excluded)
|
|
1307
|
+
const input = yield* validateRequest(Schema, {
|
|
1308
|
+
request: 'laravel',
|
|
1309
|
+
})
|
|
1310
|
+
|
|
1311
|
+
// Custom merge order + conflict policy
|
|
1312
|
+
const input = yield* validateRequest(Schema, {
|
|
1313
|
+
request: {
|
|
1314
|
+
order: ['params', 'query', 'body'],
|
|
1315
|
+
onConflict: 'error', // 'last-wins' | 'first-wins' | 'error'
|
|
1316
|
+
},
|
|
1317
|
+
})
|
|
1318
|
+
```
|
|
1319
|
+
|
|
1320
|
+
If both `profile` and `order` are provided, `order` takes precedence.
|
|
1321
|
+
|
|
1298
1322
|
---
|
|
1299
1323
|
|
|
1300
1324
|
## Route Model Binding Examples
|
|
@@ -1581,6 +1605,34 @@ effectRoutes(app)
|
|
|
1581
1605
|
- `.middleware()` adds Hono middleware that runs *before* the Effect handler (can redirect/short-circuit)
|
|
1582
1606
|
- `.provide()` adds Effect layers that run *within* the Effect computation (dependency injection)
|
|
1583
1607
|
|
|
1608
|
+
### `provide()` Layer Patterns
|
|
1609
|
+
|
|
1610
|
+
`provide()` supports two layer styles:
|
|
1611
|
+
|
|
1612
|
+
1. **Self-contained layer** (`R = never`)
|
|
1613
|
+
```typescript
|
|
1614
|
+
effectRoutes(app).provide(Layer.succeed(RequestIdService, { id: 'req-123' }))
|
|
1615
|
+
```
|
|
1616
|
+
2. **Context-aware layer** (consumes route context services like `DatabaseService`, `HonertiaService`, or previously provided services)
|
|
1617
|
+
```typescript
|
|
1618
|
+
const SharePropsLayer = Layer.effectDiscard(
|
|
1619
|
+
Effect.gen(function* () {
|
|
1620
|
+
const db = yield* DatabaseService
|
|
1621
|
+
const honertia = yield* HonertiaService
|
|
1622
|
+
const organizations = yield* Effect.tryPromise(() =>
|
|
1623
|
+
db.query.organizations.findMany()
|
|
1624
|
+
)
|
|
1625
|
+
honertia.share('organizations', organizations)
|
|
1626
|
+
honertia.share('authUserAvatarSeed', 'seed-123')
|
|
1627
|
+
})
|
|
1628
|
+
)
|
|
1629
|
+
|
|
1630
|
+
effectRoutes(app)
|
|
1631
|
+
.provide(RequireAuthLayer)
|
|
1632
|
+
.provide(SharePropsLayer)
|
|
1633
|
+
.get('/dashboard', showDashboard)
|
|
1634
|
+
```
|
|
1635
|
+
|
|
1584
1636
|
| Level | Method | Scope |
|
|
1585
1637
|
|-------|--------|-------|
|
|
1586
1638
|
| `app.use('*', ...)` | Global | All routes |
|
|
@@ -27,6 +27,7 @@ export declare const ErrorCodes: {
|
|
|
27
27
|
readonly VAL_003_BODY_PARSE_FAILED: "HON_VAL_003_BODY_PARSE_FAILED";
|
|
28
28
|
readonly VAL_004_SCHEMA_MISMATCH: "HON_VAL_004_SCHEMA_MISMATCH";
|
|
29
29
|
readonly VAL_005_TYPE_COERCION_FAILED: "HON_VAL_005_TYPE_COERCION_FAILED";
|
|
30
|
+
readonly VAL_006_SOURCE_CONFLICT: "HON_VAL_006_SOURCE_CONFLICT";
|
|
30
31
|
readonly AUTH_100_UNAUTHENTICATED: "HON_AUTH_100_UNAUTHENTICATED";
|
|
31
32
|
readonly AUTH_101_SESSION_EXPIRED: "HON_AUTH_101_SESSION_EXPIRED";
|
|
32
33
|
readonly AUTH_102_FORBIDDEN: "HON_AUTH_102_FORBIDDEN";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-catalog.d.ts","sourceRoot":"","sources":["../../src/effect/error-catalog.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,eAAe,EAGf,uBAAuB,EACxB,MAAM,kBAAkB,CAAA;AAEzB;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,UAAU
|
|
1
|
+
{"version":3,"file":"error-catalog.d.ts","sourceRoot":"","sources":["../../src/effect/error-catalog.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,eAAe,EAGf,uBAAuB,EACxB,MAAM,kBAAkB,CAAA;AAEzB;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsDb,CAAA;AAEV,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,OAAO,UAAU,CAAC,CAAA;AA8JpE;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,SAAS,EAAE,eAAe,CAmc3D,CAAA;AAOD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,SAAS,GAAG,MAAM,EACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,EAAE,YAAY,GACpB,uBAAuB,CAwCzB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,GAAG,eAAe,GAAG,SAAS,CAE/E;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,aAAa,GAAG,SAAS,EAAE,CAIxE;AAWD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,MAAM,GACf,SAAS,CAUX"}
|
|
@@ -27,6 +27,7 @@ export const ErrorCodes = {
|
|
|
27
27
|
VAL_003_BODY_PARSE_FAILED: 'HON_VAL_003_BODY_PARSE_FAILED',
|
|
28
28
|
VAL_004_SCHEMA_MISMATCH: 'HON_VAL_004_SCHEMA_MISMATCH',
|
|
29
29
|
VAL_005_TYPE_COERCION_FAILED: 'HON_VAL_005_TYPE_COERCION_FAILED',
|
|
30
|
+
VAL_006_SOURCE_CONFLICT: 'HON_VAL_006_SOURCE_CONFLICT',
|
|
30
31
|
// Auth Errors (AUTH)
|
|
31
32
|
AUTH_100_UNAUTHENTICATED: 'HON_AUTH_100_UNAUTHENTICATED',
|
|
32
33
|
AUTH_101_SESSION_EXPIRED: 'HON_AUTH_101_SESSION_EXPIRED',
|
|
@@ -256,7 +257,11 @@ export const ErrorCatalog = {
|
|
|
256
257
|
httpStatus: 422,
|
|
257
258
|
defaultFixes: [],
|
|
258
259
|
docsPath: '/errors/validation/schema-mismatch',
|
|
259
|
-
related: [
|
|
260
|
+
related: [
|
|
261
|
+
ErrorCodes.VAL_001_FIELD_REQUIRED,
|
|
262
|
+
ErrorCodes.VAL_002_FIELD_INVALID,
|
|
263
|
+
ErrorCodes.VAL_006_SOURCE_CONFLICT,
|
|
264
|
+
],
|
|
260
265
|
},
|
|
261
266
|
[ErrorCodes.VAL_005_TYPE_COERCION_FAILED]: {
|
|
262
267
|
code: ErrorCodes.VAL_005_TYPE_COERCION_FAILED,
|
|
@@ -269,6 +274,17 @@ export const ErrorCatalog = {
|
|
|
269
274
|
docsPath: '/errors/validation/type-coercion-failed',
|
|
270
275
|
related: [ErrorCodes.VAL_002_FIELD_INVALID],
|
|
271
276
|
},
|
|
277
|
+
[ErrorCodes.VAL_006_SOURCE_CONFLICT]: {
|
|
278
|
+
code: ErrorCodes.VAL_006_SOURCE_CONFLICT,
|
|
279
|
+
tag: 'ValidationError',
|
|
280
|
+
category: 'validation',
|
|
281
|
+
title: 'Request Source Conflict',
|
|
282
|
+
messageTemplate: 'Conflicting values were provided for "{field}" across request sources.',
|
|
283
|
+
httpStatus: 422,
|
|
284
|
+
defaultFixes: [],
|
|
285
|
+
docsPath: '/errors/validation/source-conflict',
|
|
286
|
+
related: [ErrorCodes.VAL_004_SCHEMA_MISMATCH],
|
|
287
|
+
},
|
|
272
288
|
// Auth Errors
|
|
273
289
|
[ErrorCodes.AUTH_100_UNAUTHENTICATED]: {
|
|
274
290
|
code: ErrorCodes.AUTH_100_UNAUTHENTICATED,
|
package/dist/effect/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export { ErrorCodes, ErrorCatalog, createStructuredError, getConfigErrorCode, ge
|
|
|
10
10
|
export { JsonErrorFormatter, TerminalErrorFormatter, InertiaErrorFormatter, detectOutputFormat, createFormatter, type ErrorFormatter, type JsonFormatterOptions, type TerminalFormatterOptions, type InertiaFormatterOptions, type OutputFormat, type FormatDetectionContext, } from './error-formatter.js';
|
|
11
11
|
export { captureErrorContext, captureEnhancedContext, parseStackTrace, findUserFrame, createSourceLocation, createCodeSnippet, withHandlerContext, withServiceContext, mergeContexts, emptyContext, type StackFrame, type EnhancedErrorContext, } from './error-context.js';
|
|
12
12
|
export * from './schema.js';
|
|
13
|
-
export { getValidationData, formatSchemaErrors, formatSchemaErrorsWithDetails, createBodyParseValidationError, validate, validateUnknown, validateRequest, asValidated, asTrusted, type Validated, type Trusted, type FormattedSchemaErrors, } from './validation.js';
|
|
13
|
+
export { getValidationData, formatSchemaErrors, formatSchemaErrorsWithDetails, createBodyParseValidationError, validate, validateUnknown, validateRequest, asValidated, asTrusted, type Validated, type Trusted, type FormattedSchemaErrors, type RequestValidationSource, type RequestValidationProfile, type RequestValidationConflict, type RequestValidationOptions, type RequestValidationConfig, } from './validation.js';
|
|
14
14
|
export { ValidatedBodyService, ValidatedQueryService, validatedBody, validatedQuery, } from './validated-services.js';
|
|
15
15
|
export { effectBridge, buildContextLayer, getEffectRuntime, getEffectSchema, type EffectBridgeConfig, } from './bridge.js';
|
|
16
16
|
export { effectHandler, effect, handle, errorToResponse, getStructuredFromThrown, } from './handler.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/effect/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,eAAe,EACf,WAAW,EACX,eAAe,EACf,YAAY,EACZ,eAAe,EACf,cAAc,EACd,sBAAsB,EACtB,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,uBAAuB,EACvB,SAAS,EACT,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,KAAK,YAAY,GAClB,MAAM,eAAe,CAAA;AAGtB,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,SAAS,EACT,uBAAuB,EACvB,0BAA0B,EAC1B,QAAQ,EACR,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,QAAQ,EACb,KAAK,sBAAsB,GAC5B,MAAM,aAAa,CAAA;AAGpB,YAAY,EACV,aAAa,EACb,YAAY,EACZ,cAAc,EACd,WAAW,EACX,YAAY,EACZ,cAAc,EACd,cAAc,IAAI,mBAAmB,EACrC,cAAc,EACd,OAAO,EACP,WAAW,EACX,YAAY,EACZ,UAAU,EACV,aAAa,EACb,SAAS,EACT,uBAAuB,EACvB,UAAU,EACV,mBAAmB,EACnB,sBAAsB,EACtB,gBAAgB,EAChB,eAAe,EACf,YAAY,GACb,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EACL,UAAU,EACV,YAAY,EACZ,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,SAAS,GACf,MAAM,oBAAoB,CAAA;AAG3B,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EAClB,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EACjB,KAAK,sBAAsB,GAC5B,MAAM,sBAAsB,CAAA;AAG7B,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,oBAAoB,GAC1B,MAAM,oBAAoB,CAAA;AAG3B,cAAc,aAAa,CAAA;AAG3B,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,6BAA6B,EAC7B,8BAA8B,EAC9B,QAAQ,EACR,eAAe,EACf,eAAe,EACf,WAAW,EACX,SAAS,EACT,KAAK,SAAS,EACd,KAAK,OAAO,EACZ,KAAK,qBAAqB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/effect/index.ts"],"names":[],"mappings":"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"}
|
package/dist/effect/routing.d.ts
CHANGED
|
@@ -90,9 +90,12 @@ export declare class EffectRouteBuilder<E extends Env, ProvidedServices = never,
|
|
|
90
90
|
constructor(app: Hono<E>, layers?: Layer.Layer<any, never, never>[], pathPrefix?: string, bridgeConfig?: EffectBridgeConfig<E, CustomServices> | undefined, registry?: RouteRegistry, middlewares?: MiddlewareHandler<E>[]);
|
|
91
91
|
/**
|
|
92
92
|
* Add a layer to provide services to all routes in this builder.
|
|
93
|
+
* The layer may be self-contained (`R = never`) or consume services already
|
|
94
|
+
* available in this route context (`BaseServices`, previously provided services,
|
|
95
|
+
* and bridge-level custom services).
|
|
93
96
|
* The layer's error type must be handled by the effect bridge (AppError or subtype).
|
|
94
97
|
*/
|
|
95
|
-
provide<S, LayerErr extends AppError>(layer: Layer.Layer<S, LayerErr,
|
|
98
|
+
provide<S, LayerErr extends AppError, LayerReq extends BaseServices | ProvidedServices | CustomServices>(layer: Layer.Layer<S, LayerErr, LayerReq>): EffectRouteBuilder<E, ProvidedServices | S, CustomServices>;
|
|
96
99
|
/**
|
|
97
100
|
* Add Hono middleware that runs before the Effect handler.
|
|
98
101
|
* Use this for middleware that needs to redirect or short-circuit requests
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/effect/routing.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAQ,KAAK,EAAU,MAAM,IAAI,CAAC,EAAE,MAAM,QAAQ,CAAA;AACjE,OAAO,KAAK,EAA0B,IAAI,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,MAAM,CAAA;AAEhF,OAAO,EAKL,KAAK,kBAAkB,EACxB,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,KAAK,QAAQ,EACb,QAAQ,EAET,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,eAAe,EACf,WAAW,EACX,eAAe,EACf,cAAc,EACd,sBAAsB,EACtB,eAAe,EAChB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAA;AAErF,OAAO,EAKL,WAAW,EAGZ,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,aAAa,EAId,MAAM,qBAAqB,CAAA;AAE5B;;;GAGG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,SAAS,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,CACjG,QAAQ,GAAG,QAAQ,EACnB,CAAC,EACD,CAAC,CACF,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,YAAY,GACpB,cAAc,GACd,sBAAsB,GACtB,eAAe,GACf,eAAe,GACf,WAAW,GACX,eAAe,GACf,WAAW,GACX,oBAAoB,GACpB,qBAAqB,CAAA;AAEzB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAA;IACrB;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAA;IACnB;;;OAGG;IACH,KAAK,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAA;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAA;IACvB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AA8BD;;GAEG;AACH,qBAAa,kBAAkB,CAC7B,CAAC,SAAS,GAAG,EACb,gBAAgB,GAAG,KAAK,EACxB,cAAc,GAAG,KAAK;IAGpB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,WAAW;gBALX,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,EACZ,MAAM,GAAE,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,EAAO,EAC7C,UAAU,GAAE,MAAW,EACvB,YAAY,CAAC,EAAE,kBAAkB,CAAC,CAAC,EAAE,cAAc,CAAC,YAAA,EACpD,QAAQ,GAAE,aAAmC,EAC7C,WAAW,GAAE,iBAAiB,CAAC,CAAC,CAAC,EAAO;IAG3D
|
|
1
|
+
{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/effect/routing.ts"],"names":[],"mappings":"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"}
|
package/dist/effect/routing.js
CHANGED
|
@@ -55,6 +55,9 @@ export class EffectRouteBuilder {
|
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
57
57
|
* Add a layer to provide services to all routes in this builder.
|
|
58
|
+
* The layer may be self-contained (`R = never`) or consume services already
|
|
59
|
+
* available in this route context (`BaseServices`, previously provided services,
|
|
60
|
+
* and bridge-level custom services).
|
|
58
61
|
* The layer's error type must be handled by the effect bridge (AppError or subtype).
|
|
59
62
|
*/
|
|
60
63
|
provide(layer) {
|
|
@@ -243,7 +246,7 @@ export class EffectRouteBuilder {
|
|
|
243
246
|
fullLayer = Layer.merge(fullLayer, Layer.succeed(ValidatedQueryService, validatedQuery));
|
|
244
247
|
}
|
|
245
248
|
for (const layer of layers) {
|
|
246
|
-
fullLayer = Layer.
|
|
249
|
+
fullLayer = Layer.provideMerge(layer, fullLayer);
|
|
247
250
|
}
|
|
248
251
|
// Run the effect with the combined layer
|
|
249
252
|
const program = effect.pipe(Effect.provide(fullLayer));
|
|
@@ -7,6 +7,43 @@ import { Effect, Schema as S, ParseResult } from 'effect';
|
|
|
7
7
|
import { RequestService } from './services.js';
|
|
8
8
|
import { ValidationError } from './errors.js';
|
|
9
9
|
import type { FieldError } from './error-types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Request data source for validateRequest input extraction.
|
|
12
|
+
*/
|
|
13
|
+
export type RequestValidationSource = 'params' | 'query' | 'body';
|
|
14
|
+
/**
|
|
15
|
+
* Built-in request extraction profiles.
|
|
16
|
+
*/
|
|
17
|
+
export type RequestValidationProfile = 'legacy' | 'laravel';
|
|
18
|
+
/**
|
|
19
|
+
* Conflict handling when multiple request sources provide the same key.
|
|
20
|
+
*/
|
|
21
|
+
export type RequestValidationConflict = 'last-wins' | 'first-wins' | 'error';
|
|
22
|
+
/**
|
|
23
|
+
* Advanced request extraction options for validateRequest.
|
|
24
|
+
*/
|
|
25
|
+
export interface RequestValidationOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Built-in extraction behavior profile.
|
|
28
|
+
* - legacy: params -> query -> body
|
|
29
|
+
* - laravel: query -> body
|
|
30
|
+
*/
|
|
31
|
+
profile?: RequestValidationProfile;
|
|
32
|
+
/**
|
|
33
|
+
* Merge order for request sources.
|
|
34
|
+
* Later sources override earlier ones when `onConflict` is `last-wins`.
|
|
35
|
+
*/
|
|
36
|
+
order?: ReadonlyArray<RequestValidationSource>;
|
|
37
|
+
/**
|
|
38
|
+
* How to resolve duplicate keys across sources.
|
|
39
|
+
*/
|
|
40
|
+
onConflict?: RequestValidationConflict;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Request extraction config accepted by validateRequest.
|
|
44
|
+
* Pass a profile string for quick setup or an object for full control.
|
|
45
|
+
*/
|
|
46
|
+
export type RequestValidationConfig = RequestValidationProfile | RequestValidationOptions;
|
|
10
47
|
/**
|
|
11
48
|
* Extract validation data from the request.
|
|
12
49
|
* Merges route params, query params, and body (body takes precedence).
|
|
@@ -86,6 +123,12 @@ export interface ValidateOptions {
|
|
|
86
123
|
* 'Projects/Create'
|
|
87
124
|
*/
|
|
88
125
|
errorComponent?: string;
|
|
126
|
+
/**
|
|
127
|
+
* Request extraction behavior for validateRequest.
|
|
128
|
+
* Pass a profile string ('legacy' | 'laravel') or an object
|
|
129
|
+
* with merge order and conflict policy controls.
|
|
130
|
+
*/
|
|
131
|
+
request?: RequestValidationConfig;
|
|
89
132
|
}
|
|
90
133
|
/**
|
|
91
134
|
* Build a parse error with concrete guidance for malformed request bodies.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/effect/validation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAA;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/effect/validation.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,23 +7,122 @@ import { Effect, Schema as S, ParseResult } from 'effect';
|
|
|
7
7
|
import { RequestService } from './services.js';
|
|
8
8
|
import { ValidationError } from './errors.js';
|
|
9
9
|
import { ErrorCodes } from './error-catalog.js';
|
|
10
|
+
const BODYLESS_METHODS = new Set(['GET', 'HEAD']);
|
|
11
|
+
function getProfileOrder(profile) {
|
|
12
|
+
return profile === 'laravel'
|
|
13
|
+
? ['query', 'body']
|
|
14
|
+
: ['params', 'query', 'body'];
|
|
15
|
+
}
|
|
16
|
+
function normalizeRequestValidationOptions(config) {
|
|
17
|
+
const objectConfig = typeof config === 'string'
|
|
18
|
+
? { profile: config }
|
|
19
|
+
: (config ?? {});
|
|
20
|
+
const profile = objectConfig.profile ?? 'legacy';
|
|
21
|
+
const profileOrder = getProfileOrder(profile);
|
|
22
|
+
const seen = new Set();
|
|
23
|
+
const order = (objectConfig.order ?? profileOrder).filter((source) => {
|
|
24
|
+
if (seen.has(source))
|
|
25
|
+
return false;
|
|
26
|
+
seen.add(source);
|
|
27
|
+
return true;
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
order,
|
|
31
|
+
onConflict: objectConfig.onConflict ?? 'last-wins',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function isJsonContentType(contentType) {
|
|
35
|
+
const normalized = contentType.toLowerCase();
|
|
36
|
+
const mediaType = normalized.split(';')[0].trim();
|
|
37
|
+
return mediaType.endsWith('/json') || mediaType.endsWith('+json');
|
|
38
|
+
}
|
|
39
|
+
function createSourceConflictValidationError(conflicts, component) {
|
|
40
|
+
const errors = {};
|
|
41
|
+
const fieldDetails = {};
|
|
42
|
+
for (const conflict of conflicts) {
|
|
43
|
+
const message = `Conflicting values for ${conflict.field} from ${conflict.existingSource} and ${conflict.incomingSource}.`;
|
|
44
|
+
errors[conflict.field] = message;
|
|
45
|
+
fieldDetails[conflict.field] = {
|
|
46
|
+
value: {
|
|
47
|
+
[conflict.existingSource]: conflict.existingValue,
|
|
48
|
+
[conflict.incomingSource]: conflict.incomingValue,
|
|
49
|
+
},
|
|
50
|
+
expected: 'a single unambiguous value from request input',
|
|
51
|
+
message,
|
|
52
|
+
path: conflict.field.split('.'),
|
|
53
|
+
schemaType: 'RequestInput',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return new ValidationError({
|
|
57
|
+
errors,
|
|
58
|
+
fieldDetails,
|
|
59
|
+
component,
|
|
60
|
+
code: ErrorCodes.VAL_006_SOURCE_CONFLICT,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function mergeRequestSources(orderedSources, onConflict, component) {
|
|
64
|
+
const merged = {};
|
|
65
|
+
const sourceOfKey = new Map();
|
|
66
|
+
const conflicts = [];
|
|
67
|
+
for (const { source, data } of orderedSources) {
|
|
68
|
+
for (const [key, value] of Object.entries(data)) {
|
|
69
|
+
if (!(key in merged)) {
|
|
70
|
+
merged[key] = value;
|
|
71
|
+
sourceOfKey.set(key, source);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (onConflict === 'first-wins') {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (onConflict === 'last-wins') {
|
|
78
|
+
merged[key] = value;
|
|
79
|
+
sourceOfKey.set(key, source);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const existingValue = merged[key];
|
|
83
|
+
if (Object.is(existingValue, value)) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
conflicts.push({
|
|
87
|
+
field: key,
|
|
88
|
+
existingSource: sourceOfKey.get(key) ?? source,
|
|
89
|
+
incomingSource: source,
|
|
90
|
+
existingValue,
|
|
91
|
+
incomingValue: value,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (conflicts.length > 0) {
|
|
96
|
+
return Effect.fail(createSourceConflictValidationError(conflicts, component));
|
|
97
|
+
}
|
|
98
|
+
return Effect.succeed(merged);
|
|
99
|
+
}
|
|
100
|
+
function getValidationDataWithOptions(config, errorComponent) {
|
|
101
|
+
return Effect.gen(function* () {
|
|
102
|
+
const request = yield* RequestService;
|
|
103
|
+
const { order, onConflict } = normalizeRequestValidationOptions(config);
|
|
104
|
+
const routeParams = order.includes('params') ? request.params() : {};
|
|
105
|
+
const queryParams = order.includes('query') ? request.query() : {};
|
|
106
|
+
let body = {};
|
|
107
|
+
if (order.includes('body') &&
|
|
108
|
+
!BODYLESS_METHODS.has(request.method.toUpperCase())) {
|
|
109
|
+
const contentType = request.header('Content-Type') ?? '';
|
|
110
|
+
const isJson = isJsonContentType(contentType);
|
|
111
|
+
body = yield* Effect.tryPromise(() => isJson ? request.json() : request.parseBody()).pipe(Effect.mapError((error) => createBodyParseValidationError(error, contentType)));
|
|
112
|
+
}
|
|
113
|
+
const sourceData = {
|
|
114
|
+
params: routeParams,
|
|
115
|
+
query: queryParams,
|
|
116
|
+
body,
|
|
117
|
+
};
|
|
118
|
+
return yield* mergeRequestSources(order.map((source) => ({ source, data: sourceData[source] })), onConflict, errorComponent);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
10
121
|
/**
|
|
11
122
|
* Extract validation data from the request.
|
|
12
123
|
* Merges route params, query params, and body (body takes precedence).
|
|
13
124
|
*/
|
|
14
|
-
export const getValidationData =
|
|
15
|
-
const request = yield* RequestService;
|
|
16
|
-
const routeParams = request.params();
|
|
17
|
-
const queryParams = request.query();
|
|
18
|
-
// Only parse body for methods that typically have one
|
|
19
|
-
if (['GET', 'HEAD'].includes(request.method.toUpperCase())) {
|
|
20
|
-
return { ...routeParams, ...queryParams };
|
|
21
|
-
}
|
|
22
|
-
const contentType = request.header('Content-Type') ?? '';
|
|
23
|
-
const isJson = contentType.includes('application/json');
|
|
24
|
-
const body = yield* Effect.tryPromise(() => isJson ? request.json() : request.parseBody()).pipe(Effect.mapError((error) => createBodyParseValidationError(error, contentType)));
|
|
25
|
-
return { ...routeParams, ...queryParams, ...body };
|
|
26
|
-
});
|
|
125
|
+
export const getValidationData = getValidationDataWithOptions('legacy');
|
|
27
126
|
/**
|
|
28
127
|
* Format Effect Schema parse errors into field-level validation errors.
|
|
29
128
|
* Returns both simple errors and detailed field information.
|
|
@@ -116,7 +215,7 @@ export const asTrusted = (input) => input;
|
|
|
116
215
|
* Build a parse error with concrete guidance for malformed request bodies.
|
|
117
216
|
*/
|
|
118
217
|
export function createBodyParseValidationError(error, contentType) {
|
|
119
|
-
const isJson = contentType
|
|
218
|
+
const isJson = isJsonContentType(contentType);
|
|
120
219
|
const reason = error instanceof Error ? error.message : String(error);
|
|
121
220
|
const base = isJson ? 'Invalid JSON body' : 'Could not parse request body';
|
|
122
221
|
const hint = isJson
|
|
@@ -176,7 +275,7 @@ export function validateUnknown(schema, data, options = {}) {
|
|
|
176
275
|
*/
|
|
177
276
|
export function validateRequest(schema, options = {}) {
|
|
178
277
|
return Effect.gen(function* () {
|
|
179
|
-
const data = yield*
|
|
278
|
+
const data = yield* getValidationDataWithOptions(options.request, options.errorComponent);
|
|
180
279
|
return yield* validateUnknown(schema, data, options);
|
|
181
280
|
});
|
|
182
281
|
}
|
package/dist/schema.d.ts
CHANGED
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* Import from 'honertia/schema' for validation functionality.
|
|
6
6
|
*/
|
|
7
7
|
export { S, trimmed, nullableString, optionalString, requiredString, required, alpha, alphaDash, alphaNum, startsWith, endsWith, lowercase, uppercase, coercedNumber, positiveInt, nonNegativeInt, parsePositiveInt, between, digits, digitsBetween, gt, gte, lt, lte, multipleOf, coercedBoolean, checkbox, accepted, declined, coercedDate, nullableDate, after, afterOrEqual, before, beforeOrEqual, ensureArray, distinct, minItems, maxItems, inArray, notIn, email, nullableEmail, url, nullableUrl, uuid, nullableUuid, ip, ipv4, ipv6, macAddress, jsonString, confirmed, size, min, max, password, nullable, filled, excludeIf, } from './effect/schema.js';
|
|
8
|
-
export { getValidationData, formatSchemaErrors, createBodyParseValidationError, validate, validateUnknown, validateRequest, asValidated, asTrusted, type Validated, type Trusted, } from './effect/validation.js';
|
|
8
|
+
export { getValidationData, formatSchemaErrors, createBodyParseValidationError, validate, validateUnknown, validateRequest, asValidated, asTrusted, type Validated, type Trusted, type RequestValidationSource, type RequestValidationProfile, type RequestValidationConflict, type RequestValidationOptions, type RequestValidationConfig, } from './effect/validation.js';
|
|
9
9
|
//# sourceMappingURL=schema.d.ts.map
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAEL,CAAC,EAED,OAAO,EACP,cAAc,EACd,cAAc,EACd,cAAc,EACd,QAAQ,EACR,KAAK,EACL,SAAS,EACT,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,SAAS,EACT,SAAS,EAET,aAAa,EACb,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,OAAO,EACP,MAAM,EACN,aAAa,EACb,EAAE,EACF,GAAG,EACH,EAAE,EACF,GAAG,EACH,UAAU,EAEV,cAAc,EACd,QAAQ,EACR,QAAQ,EACR,QAAQ,EAER,WAAW,EACX,YAAY,EACZ,KAAK,EACL,YAAY,EACZ,MAAM,EACN,aAAa,EAEb,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,QAAQ,EAER,OAAO,EACP,KAAK,EAEL,KAAK,EACL,aAAa,EACb,GAAG,EACH,WAAW,EACX,IAAI,EACJ,YAAY,EACZ,EAAE,EACF,IAAI,EACJ,IAAI,EACJ,UAAU,EACV,UAAU,EAEV,SAAS,EAET,IAAI,EACJ,GAAG,EACH,GAAG,EAEH,QAAQ,EAER,QAAQ,EACR,MAAM,EACN,SAAS,GACV,MAAM,oBAAoB,CAAA;AAG3B,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,8BAA8B,EAC9B,QAAQ,EACR,eAAe,EACf,eAAe,EACf,WAAW,EACX,SAAS,EACT,KAAK,SAAS,EACd,KAAK,OAAO,
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAEL,CAAC,EAED,OAAO,EACP,cAAc,EACd,cAAc,EACd,cAAc,EACd,QAAQ,EACR,KAAK,EACL,SAAS,EACT,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,SAAS,EACT,SAAS,EAET,aAAa,EACb,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,OAAO,EACP,MAAM,EACN,aAAa,EACb,EAAE,EACF,GAAG,EACH,EAAE,EACF,GAAG,EACH,UAAU,EAEV,cAAc,EACd,QAAQ,EACR,QAAQ,EACR,QAAQ,EAER,WAAW,EACX,YAAY,EACZ,KAAK,EACL,YAAY,EACZ,MAAM,EACN,aAAa,EAEb,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,QAAQ,EAER,OAAO,EACP,KAAK,EAEL,KAAK,EACL,aAAa,EACb,GAAG,EACH,WAAW,EACX,IAAI,EACJ,YAAY,EACZ,EAAE,EACF,IAAI,EACJ,IAAI,EACJ,UAAU,EACV,UAAU,EAEV,SAAS,EAET,IAAI,EACJ,GAAG,EACH,GAAG,EAEH,QAAQ,EAER,QAAQ,EACR,MAAM,EACN,SAAS,GACV,MAAM,oBAAoB,CAAA;AAG3B,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,8BAA8B,EAC9B,QAAQ,EACR,eAAe,EACf,eAAe,EACf,WAAW,EACX,SAAS,EACT,KAAK,SAAS,EACd,KAAK,OAAO,EACZ,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,GAC7B,MAAM,wBAAwB,CAAA"}
|