@bakery-framework/core 1.1.0 → 1.2.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 +1 -1
- package/src/cache/shared-db.ts +69 -14
- package/src/client/utils.ts +11 -1
- package/src/compiler/tsconfig-sync.ts +261 -0
- package/src/core/config.ts +7 -0
- package/src/core/define-route.ts +66 -0
- package/src/core/index.ts +35 -13
- package/src/core/port.ts +55 -0
- package/src/global.d.ts +23 -2
- package/src/handlers/core/$middleware.ts +33 -4
- package/src/logger/serve-log.ts +11 -0
- package/src/plugins/types.ts +59 -0
- package/src/router.ts +22 -0
- package/src/utils/http/cors.ts +170 -0
- package/src/utils/http/index.ts +1 -0
- package/src/utils/http/sse.ts +200 -0
- package/src/utils/http/validate.ts +106 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request-body validation for route modules.
|
|
3
|
+
*
|
|
4
|
+
* `defineRoute<{ title: string }>` declares a body shape and enforces nothing —
|
|
5
|
+
* the scaffolder's own template says so twice: *"declares the contract — it
|
|
6
|
+
* does not validate it. The body is still client input."* Every route was left
|
|
7
|
+
* to hand-check or not, and most did not.
|
|
8
|
+
*
|
|
9
|
+
* **No schema library is bundled, and none is depended on.** `@bakery-framework/
|
|
10
|
+
* core` has zero runtime dependencies and that is worth keeping, so validation
|
|
11
|
+
* accepts two shapes it can consume without knowing who produced them:
|
|
12
|
+
*
|
|
13
|
+
* - **Standard Schema** (`~standard`) — the shared interface zod, valibot and
|
|
14
|
+
* arktype all implement. Bring your own library; Bakery never imports it.
|
|
15
|
+
* - **A plain function** that returns the parsed value or throws.
|
|
16
|
+
*
|
|
17
|
+
* The second exists because the first is overkill for `body => { if (!body.id)
|
|
18
|
+
* throw new Error('id required'); return body }`, and a framework that forces a
|
|
19
|
+
* dependency for that has made the common case worse.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** The subset of Standard Schema v1 that validation needs. */
|
|
23
|
+
export interface StandardSchemaLike<T> {
|
|
24
|
+
'~standard': {
|
|
25
|
+
version: 1
|
|
26
|
+
vendor: string
|
|
27
|
+
validate(value: unknown): StandardResult<T> | Promise<StandardResult<T>>
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type StandardResult<T> =
|
|
32
|
+
| { value: T; issues?: undefined }
|
|
33
|
+
| { issues: readonly StandardIssue[] }
|
|
34
|
+
|
|
35
|
+
interface StandardIssue {
|
|
36
|
+
message: string
|
|
37
|
+
path?: readonly (PropertyKey | { key: PropertyKey })[]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** A function validator: return the parsed value, or throw to reject. */
|
|
41
|
+
export type FunctionValidator<T> = (value: unknown) => T | Promise<T>
|
|
42
|
+
|
|
43
|
+
export type Validator<T> = StandardSchemaLike<T> | FunctionValidator<T>
|
|
44
|
+
|
|
45
|
+
/** One human-readable problem, with the field path when the schema gave one. */
|
|
46
|
+
export interface ValidationIssue {
|
|
47
|
+
path: string
|
|
48
|
+
message: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type ValidationResult<T> =
|
|
52
|
+
| { ok: true; value: T }
|
|
53
|
+
| { ok: false; issues: ValidationIssue[] }
|
|
54
|
+
|
|
55
|
+
function isStandardSchema<T>(v: Validator<T>): v is StandardSchemaLike<T> {
|
|
56
|
+
return typeof v === 'object' && v !== null && '~standard' in v
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Render a Standard Schema path as dotted notation.
|
|
61
|
+
*
|
|
62
|
+
* Segments may be plain keys or `{ key }` objects — the spec allows both, and a
|
|
63
|
+
* library that uses the object form would otherwise render as `[object Object]`
|
|
64
|
+
* in the very message meant to tell someone which field is wrong.
|
|
65
|
+
*/
|
|
66
|
+
function renderPath(path: StandardIssue['path']): string {
|
|
67
|
+
if (!path?.length) return ''
|
|
68
|
+
return path
|
|
69
|
+
.map(seg => (typeof seg === 'object' && seg !== null ? seg.key : seg))
|
|
70
|
+
.join('.')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Run a validator, never throwing.
|
|
75
|
+
*
|
|
76
|
+
* A function validator that throws is a *rejection*, not a crash: that is the
|
|
77
|
+
* whole idiom for the plain-function form. Its message becomes the issue, so
|
|
78
|
+
* `throw new Error('id must be a number')` reaches the client as written.
|
|
79
|
+
*/
|
|
80
|
+
export async function validate<T>(
|
|
81
|
+
validator: Validator<T>,
|
|
82
|
+
value: unknown,
|
|
83
|
+
): Promise<ValidationResult<T>> {
|
|
84
|
+
if (isStandardSchema(validator)) {
|
|
85
|
+
const result = await validator['~standard'].validate(value)
|
|
86
|
+
if (result.issues) {
|
|
87
|
+
return {
|
|
88
|
+
ok: false,
|
|
89
|
+
issues: result.issues.map(issue => ({
|
|
90
|
+
path: renderPath(issue.path),
|
|
91
|
+
message: issue.message,
|
|
92
|
+
})),
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { ok: true, value: result.value }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
return { ok: true, value: await validator(value) }
|
|
100
|
+
} catch (error: any) {
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
issues: [{ path: '', message: error?.message ?? 'Invalid request body' }],
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|