@nifrajs/core 1.3.1 → 1.4.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/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/reflection.d.ts +54 -0
- package/dist/reflection.d.ts.map +1 -0
- package/dist/reflection.js +153 -0
- package/dist/reflection.js.map +1 -0
- package/package.json +6 -1
- package/src/index.ts +2 -1
- package/src/reflection.ts +193 -0
package/dist/index.d.ts
CHANGED
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
* (it shipped at "0.0.0" through 1.0.0 before those guards existed). Kept narrow (`as const`) so
|
|
12
12
|
* consumers can pin it at the type level — see `test/version.test-d.ts`.
|
|
13
13
|
*/
|
|
14
|
-
export declare const VERSION: "1.
|
|
14
|
+
export declare const VERSION: "1.4.0";
|
|
15
15
|
export type Version = typeof VERSION;
|
|
16
16
|
export { FrameworkError, RouteConfigError, type RouteConfigErrorCode } from "./errors.js";
|
|
17
17
|
export { FRAMEWORK_NAME, type FrameworkName } from "./internal/brand.js";
|
|
18
|
+
export * from "./reflection.js";
|
|
18
19
|
export { METHODS, type Method, Router, type RouterMatch } from "./router/router.js";
|
|
19
20
|
export type { InferInput, InferOutput, StandardIssue, StandardResult, StandardSchemaV1, StandardTypes, ValidationOutcome, } from "./schema/standard.js";
|
|
20
21
|
export { validateStandard } from "./schema/standard.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAG,OAAgB,CAAA;AAEvC,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAA;AAEpC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAA;AACzF,OAAO,EAAE,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACxE,OAAO,EAAE,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACnF,YAAY,EACV,UAAU,EACV,WAAW,EACX,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAClB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,MAAM,EACN,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,OAAO,GACR,MAAM,UAAU,CAAA;AACjB,YAAY,EACV,OAAO,EACP,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,gBAAgB,EAChB,WAAW,GACZ,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,cAAc,EACd,KAAK,WAAW,EAChB,SAAS,EACT,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,WAAW,GACjB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,KAAK,aAAa,EAClB,YAAY,EACZ,eAAe,EACf,SAAS,EACT,WAAW,GACZ,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,oBAAoB,EACpB,UAAU,EACV,KAAK,SAAS,EACd,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,eAAe,EACf,YAAY,GACb,MAAM,oBAAoB,CAAA;AAC3B,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAC/D,OAAO,EACL,KAAK,SAAS,EACd,KAAK,0BAA0B,EAC/B,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,EAClB,KAAK,gBAAgB,EACrB,KAAK,OAAO,EACZ,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,MAAM,EACN,KAAK,aAAa,EAClB,MAAM,EACN,KAAK,eAAe,EACpB,cAAc,GACf,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,KAAK,UAAU,EACf,KAAK,OAAO,EACZ,KAAK,UAAU,EACf,KAAK,SAAS,EACd,GAAG,GACJ,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,aAAa,GACnB,MAAM,qBAAqB,CAAA;AAC5B,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAG,OAAgB,CAAA;AAEvC,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAA;AAEpC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAA;AACzF,OAAO,EAAE,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACxE,cAAc,iBAAiB,CAAA;AAC/B,OAAO,EAAE,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACnF,YAAY,EACV,UAAU,EACV,WAAW,EACX,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAClB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,MAAM,EACN,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,OAAO,GACR,MAAM,UAAU,CAAA;AACjB,YAAY,EACV,OAAO,EACP,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,gBAAgB,EAChB,WAAW,GACZ,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,cAAc,EACd,KAAK,WAAW,EAChB,SAAS,EACT,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,WAAW,GACjB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,KAAK,aAAa,EAClB,YAAY,EACZ,eAAe,EACf,SAAS,EACT,WAAW,GACZ,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,oBAAoB,EACpB,UAAU,EACV,KAAK,SAAS,EACd,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,eAAe,EACf,YAAY,GACb,MAAM,oBAAoB,CAAA;AAC3B,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAC/D,OAAO,EACL,KAAK,SAAS,EACd,KAAK,0BAA0B,EAC/B,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,EAClB,KAAK,gBAAgB,EACrB,KAAK,OAAO,EACZ,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,MAAM,EACN,KAAK,aAAa,EAClB,MAAM,EACN,KAAK,eAAe,EACpB,cAAc,GACf,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,KAAK,UAAU,EACf,KAAK,OAAO,EACZ,KAAK,UAAU,EACf,KAAK,SAAS,EACd,GAAG,GACJ,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,aAAa,GACnB,MAAM,qBAAqB,CAAA;AAC5B,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
* (it shipped at "0.0.0" through 1.0.0 before those guards existed). Kept narrow (`as const`) so
|
|
12
12
|
* consumers can pin it at the type level — see `test/version.test-d.ts`.
|
|
13
13
|
*/
|
|
14
|
-
export const VERSION = "1.
|
|
14
|
+
export const VERSION = "1.4.0";
|
|
15
15
|
export { FrameworkError, RouteConfigError } from "./errors.js";
|
|
16
16
|
export { FRAMEWORK_NAME } from "./internal/brand.js";
|
|
17
|
+
export * from "./reflection.js";
|
|
17
18
|
export { METHODS, Router } from "./router/router.js";
|
|
18
19
|
export { validateStandard } from "./schema/standard.js";
|
|
19
20
|
export { robots, sitemap, } from "./seo.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,OAAgB,CAAA;AAIvC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAA6B,MAAM,aAAa,CAAA;AACzF,OAAO,EAAE,cAAc,EAAsB,MAAM,qBAAqB,CAAA;AACxE,OAAO,EAAE,OAAO,EAAe,MAAM,EAAoB,MAAM,oBAAoB,CAAA;AAUnF,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAGL,MAAM,EAIN,OAAO,GACR,MAAM,UAAU,CAAA;AASjB,OAAO,EAGL,cAAc,EAEd,SAAS,GAKV,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAEL,YAAY,EACZ,eAAe,EACf,SAAS,EACT,WAAW,GACZ,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,oBAAoB,EACpB,UAAU,EAIV,eAAe,EACf,YAAY,GACb,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EAGL,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,EAgBlB,MAAM,EAEN,MAAM,EAEN,cAAc,GACf,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAKL,GAAG,GACJ,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAGL,aAAa,GAId,MAAM,qBAAqB,CAAA;AAS5B,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,OAAgB,CAAA;AAIvC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAA6B,MAAM,aAAa,CAAA;AACzF,OAAO,EAAE,cAAc,EAAsB,MAAM,qBAAqB,CAAA;AACxE,cAAc,iBAAiB,CAAA;AAC/B,OAAO,EAAE,OAAO,EAAe,MAAM,EAAoB,MAAM,oBAAoB,CAAA;AAUnF,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAGL,MAAM,EAIN,OAAO,GACR,MAAM,UAAU,CAAA;AASjB,OAAO,EAGL,cAAc,EAEd,SAAS,GAKV,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAEL,YAAY,EACZ,eAAe,EACf,SAAS,EACT,WAAW,GACZ,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,oBAAoB,EACpB,UAAU,EAIV,eAAe,EACf,YAAY,GACb,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EAGL,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,EAgBlB,MAAM,EAEN,MAAM,EAEN,cAAc,GACf,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAKL,GAAG,GACJ,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAGL,aAAa,GAId,MAAM,qBAAqB,CAAA;AAS5B,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime reflection for schemas and registered routes.
|
|
3
|
+
*
|
|
4
|
+
* Standard Schema deliberately standardizes validation, not introspection. This module makes that
|
|
5
|
+
* distinction explicit: `standard` is present when a value can validate, while `jsonSchema` and
|
|
6
|
+
* `fields` are present only when the value carries inspectable JSON Schema metadata. The implementation
|
|
7
|
+
* recognizes Nifra/TypeBox carriers and raw JSON Schema without depending on a validator package.
|
|
8
|
+
*/
|
|
9
|
+
import type { StandardSchemaV1 } from "./schema/standard.js";
|
|
10
|
+
import type { ToolAnnotations } from "./server/server.js";
|
|
11
|
+
/** JSON Schema permits either a schema object or the boolean schemas `true` and `false`. */
|
|
12
|
+
export type JsonSchema = boolean | Readonly<Record<string, unknown>>;
|
|
13
|
+
/** One top-level property of an introspectable object schema. */
|
|
14
|
+
export interface ReflectedSchemaField {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly required: boolean;
|
|
17
|
+
readonly schema: JsonSchema;
|
|
18
|
+
}
|
|
19
|
+
/** Validation and introspection capabilities discovered for one schema-like value. */
|
|
20
|
+
export interface SchemaReflection {
|
|
21
|
+
/** The Standard Schema validator, when the value implements Standard Schema v1. */
|
|
22
|
+
readonly standard: StandardSchemaV1 | undefined;
|
|
23
|
+
/** Raw JSON Schema metadata, when the value exposes it or is itself a raw JSON Schema. */
|
|
24
|
+
readonly jsonSchema: JsonSchema | undefined;
|
|
25
|
+
/** Top-level object fields, or `undefined` when the JSON Schema is absent/non-object. */
|
|
26
|
+
readonly fields: readonly ReflectedSchemaField[] | undefined;
|
|
27
|
+
}
|
|
28
|
+
export interface ReflectedRouteSchema {
|
|
29
|
+
readonly body?: SchemaReflection;
|
|
30
|
+
readonly query?: SchemaReflection;
|
|
31
|
+
readonly response?: SchemaReflection;
|
|
32
|
+
readonly errors?: Readonly<Record<string, SchemaReflection>>;
|
|
33
|
+
}
|
|
34
|
+
export interface ReflectedRoute {
|
|
35
|
+
readonly method: string;
|
|
36
|
+
readonly path: string;
|
|
37
|
+
readonly schema?: ReflectedRouteSchema;
|
|
38
|
+
readonly tool?: {
|
|
39
|
+
readonly name: string;
|
|
40
|
+
readonly description: string;
|
|
41
|
+
readonly annotations?: ToolAnnotations;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Reflect a Standard Schema, Nifra/TypeBox schema carrier, or raw JSON Schema. Never throws.
|
|
46
|
+
* Validation-only schemas have `standard` but no `jsonSchema`; raw JSON Schema has the reverse.
|
|
47
|
+
*/
|
|
48
|
+
export declare function reflectSchema(value: unknown): SchemaReflection;
|
|
49
|
+
/**
|
|
50
|
+
* Safely enumerate and normalize route descriptors from an app or descriptor array. Invalid entries
|
|
51
|
+
* are ignored; a missing/throwing `routes()` method yields an empty array.
|
|
52
|
+
*/
|
|
53
|
+
export declare function reflectRoutes(source: unknown): readonly ReflectedRoute[];
|
|
54
|
+
//# sourceMappingURL=reflection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reflection.d.ts","sourceRoot":"","sources":["../src/reflection.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEzD,4FAA4F;AAC5F,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAEpE,iEAAiE;AACjE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;CAC5B;AAED,sFAAsF;AACtF,MAAM,WAAW,gBAAgB;IAC/B,mFAAmF;IACnF,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,GAAG,SAAS,CAAA;IAC/C,0FAA0F;IAC1F,QAAQ,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,CAAA;IAC3C,yFAAyF;IACzF,QAAQ,CAAC,MAAM,EAAE,SAAS,oBAAoB,EAAE,GAAG,SAAS,CAAA;CAC7D;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,CAAC,EAAE,gBAAgB,CAAA;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAA;IACjC,QAAQ,CAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAA;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAA;CAC7D;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,oBAAoB,CAAA;IACtC,QAAQ,CAAC,IAAI,CAAC,EAAE;QACd,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;QACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;QAC5B,QAAQ,CAAC,WAAW,CAAC,EAAE,eAAe,CAAA;KACvC,CAAA;CACF;AAyED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAI9D;AA4CD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,cAAc,EAAE,CAexE"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime reflection for schemas and registered routes.
|
|
3
|
+
*
|
|
4
|
+
* Standard Schema deliberately standardizes validation, not introspection. This module makes that
|
|
5
|
+
* distinction explicit: `standard` is present when a value can validate, while `jsonSchema` and
|
|
6
|
+
* `fields` are present only when the value carries inspectable JSON Schema metadata. The implementation
|
|
7
|
+
* recognizes Nifra/TypeBox carriers and raw JSON Schema without depending on a validator package.
|
|
8
|
+
*/
|
|
9
|
+
const recordOf = (value) => value !== null && typeof value === "object"
|
|
10
|
+
? value
|
|
11
|
+
: undefined;
|
|
12
|
+
const standardOf = (value) => {
|
|
13
|
+
const record = recordOf(value);
|
|
14
|
+
const standard = recordOf(record?.["~standard"]);
|
|
15
|
+
return standard?.version === 1 && typeof standard.validate === "function"
|
|
16
|
+
? value
|
|
17
|
+
: undefined;
|
|
18
|
+
};
|
|
19
|
+
const jsonSchemaOf = (value, standard) => {
|
|
20
|
+
const record = recordOf(value);
|
|
21
|
+
if (record !== undefined && "jsonSchema" in record) {
|
|
22
|
+
const carried = record.jsonSchema;
|
|
23
|
+
return normalizeJsonSchema(carried);
|
|
24
|
+
}
|
|
25
|
+
// Some Standard Schema implementations (including TypeBox-shaped adapters) expose an object
|
|
26
|
+
// schema directly rather than through `.jsonSchema`. Only treat an inspectable object shape as
|
|
27
|
+
// metadata here; validation-library internals are otherwise intentionally opaque.
|
|
28
|
+
if (standard !== undefined) {
|
|
29
|
+
return recordOf(record?.properties) !== undefined ? normalizeJsonSchema(record) : undefined;
|
|
30
|
+
}
|
|
31
|
+
// A malformed/opaque Standard Schema marker is not raw JSON Schema metadata.
|
|
32
|
+
if (record !== undefined && "~standard" in record)
|
|
33
|
+
return undefined;
|
|
34
|
+
if (typeof value === "boolean" || record !== undefined)
|
|
35
|
+
return normalizeJsonSchema(value);
|
|
36
|
+
return undefined;
|
|
37
|
+
};
|
|
38
|
+
/** Strip symbol keys and other non-JSON metadata carried by schema-builder objects. */
|
|
39
|
+
const normalizeJsonSchema = (value) => {
|
|
40
|
+
if (typeof value === "boolean")
|
|
41
|
+
return value;
|
|
42
|
+
if (recordOf(value) === undefined)
|
|
43
|
+
return undefined;
|
|
44
|
+
try {
|
|
45
|
+
const normalized = JSON.parse(JSON.stringify(value));
|
|
46
|
+
if (typeof normalized === "boolean")
|
|
47
|
+
return normalized;
|
|
48
|
+
const record = recordOf(normalized);
|
|
49
|
+
if (record === undefined)
|
|
50
|
+
return undefined;
|
|
51
|
+
// `~standard` is a Standard Schema *validation* marker, never valid JSON Schema. A TypeBox-shaped
|
|
52
|
+
// carrier (a JSON Schema object that also carries `~standard`, with no separate `.jsonSchema`) would
|
|
53
|
+
// otherwise leak the marker into emitted OpenAPI / mock / form output.
|
|
54
|
+
if (!("~standard" in record))
|
|
55
|
+
return record;
|
|
56
|
+
const { ["~standard"]: _omitted, ...rest } = record;
|
|
57
|
+
return rest;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const fieldsOf = (schema) => {
|
|
64
|
+
const record = recordOf(schema);
|
|
65
|
+
const properties = recordOf(record?.properties);
|
|
66
|
+
if (properties === undefined)
|
|
67
|
+
return undefined;
|
|
68
|
+
const required = new Set(Array.isArray(record?.required)
|
|
69
|
+
? record.required.filter((name) => typeof name === "string")
|
|
70
|
+
: []);
|
|
71
|
+
const fields = [];
|
|
72
|
+
for (const [name, property] of Object.entries(properties)) {
|
|
73
|
+
if (typeof property !== "boolean" && recordOf(property) === undefined)
|
|
74
|
+
continue;
|
|
75
|
+
fields.push({ name, required: required.has(name), schema: property });
|
|
76
|
+
}
|
|
77
|
+
return fields;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Reflect a Standard Schema, Nifra/TypeBox schema carrier, or raw JSON Schema. Never throws.
|
|
81
|
+
* Validation-only schemas have `standard` but no `jsonSchema`; raw JSON Schema has the reverse.
|
|
82
|
+
*/
|
|
83
|
+
export function reflectSchema(value) {
|
|
84
|
+
const standard = standardOf(value);
|
|
85
|
+
const jsonSchema = jsonSchemaOf(value, standard);
|
|
86
|
+
return { standard, jsonSchema, fields: fieldsOf(jsonSchema) };
|
|
87
|
+
}
|
|
88
|
+
const routeCandidates = (source) => {
|
|
89
|
+
if (Array.isArray(source))
|
|
90
|
+
return source;
|
|
91
|
+
const record = recordOf(source);
|
|
92
|
+
if (typeof record?.routes !== "function")
|
|
93
|
+
return [];
|
|
94
|
+
try {
|
|
95
|
+
const routes = record.routes();
|
|
96
|
+
return Array.isArray(routes) ? routes : [];
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const reflectedRouteSchema = (value) => {
|
|
103
|
+
const schema = recordOf(value);
|
|
104
|
+
if (schema === undefined)
|
|
105
|
+
return undefined;
|
|
106
|
+
const errors = recordOf(schema.errors);
|
|
107
|
+
const reflectedErrors = {};
|
|
108
|
+
if (errors !== undefined) {
|
|
109
|
+
for (const [status, errorSchema] of Object.entries(errors)) {
|
|
110
|
+
reflectedErrors[status] = reflectSchema(errorSchema);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
...(schema.body !== undefined ? { body: reflectSchema(schema.body) } : {}),
|
|
115
|
+
...(schema.query !== undefined ? { query: reflectSchema(schema.query) } : {}),
|
|
116
|
+
...(schema.response !== undefined ? { response: reflectSchema(schema.response) } : {}),
|
|
117
|
+
...(Object.keys(reflectedErrors).length > 0 ? { errors: reflectedErrors } : {}),
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
const reflectedTool = (value) => {
|
|
121
|
+
const tool = recordOf(value);
|
|
122
|
+
if (typeof tool?.name !== "string" || typeof tool.description !== "string")
|
|
123
|
+
return undefined;
|
|
124
|
+
return {
|
|
125
|
+
name: tool.name,
|
|
126
|
+
description: tool.description,
|
|
127
|
+
...(recordOf(tool.annotations) !== undefined
|
|
128
|
+
? { annotations: tool.annotations }
|
|
129
|
+
: {}),
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Safely enumerate and normalize route descriptors from an app or descriptor array. Invalid entries
|
|
134
|
+
* are ignored; a missing/throwing `routes()` method yields an empty array.
|
|
135
|
+
*/
|
|
136
|
+
export function reflectRoutes(source) {
|
|
137
|
+
const reflected = [];
|
|
138
|
+
for (const candidate of routeCandidates(source)) {
|
|
139
|
+
const route = recordOf(candidate);
|
|
140
|
+
if (typeof route?.method !== "string" || typeof route.path !== "string")
|
|
141
|
+
continue;
|
|
142
|
+
const schema = reflectedRouteSchema(route.schema);
|
|
143
|
+
const tool = reflectedTool(route.tool);
|
|
144
|
+
reflected.push({
|
|
145
|
+
method: route.method.toUpperCase(),
|
|
146
|
+
path: route.path,
|
|
147
|
+
...(schema !== undefined ? { schema } : {}),
|
|
148
|
+
...(tool !== undefined ? { tool } : {}),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return reflected;
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=reflection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reflection.js","sourceRoot":"","sources":["../src/reflection.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Bun-native, contract-first HTTP framework — the router, server, and route descriptor.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -31,6 +31,11 @@
|
|
|
31
31
|
"bun": "./src/ws.ts",
|
|
32
32
|
"types": "./dist/ws.d.ts",
|
|
33
33
|
"default": "./dist/ws.js"
|
|
34
|
+
},
|
|
35
|
+
"./reflection": {
|
|
36
|
+
"bun": "./src/reflection.ts",
|
|
37
|
+
"types": "./dist/reflection.d.ts",
|
|
38
|
+
"default": "./dist/reflection.js"
|
|
34
39
|
}
|
|
35
40
|
},
|
|
36
41
|
"files": [
|
package/src/index.ts
CHANGED
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
* (it shipped at "0.0.0" through 1.0.0 before those guards existed). Kept narrow (`as const`) so
|
|
13
13
|
* consumers can pin it at the type level — see `test/version.test-d.ts`.
|
|
14
14
|
*/
|
|
15
|
-
export const VERSION = "1.
|
|
15
|
+
export const VERSION = "1.4.0" as const
|
|
16
16
|
|
|
17
17
|
export type Version = typeof VERSION
|
|
18
18
|
|
|
19
19
|
export { FrameworkError, RouteConfigError, type RouteConfigErrorCode } from "./errors.ts"
|
|
20
20
|
export { FRAMEWORK_NAME, type FrameworkName } from "./internal/brand.ts"
|
|
21
|
+
export * from "./reflection.ts"
|
|
21
22
|
export { METHODS, type Method, Router, type RouterMatch } from "./router/router.ts"
|
|
22
23
|
export type {
|
|
23
24
|
InferInput,
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime reflection for schemas and registered routes.
|
|
3
|
+
*
|
|
4
|
+
* Standard Schema deliberately standardizes validation, not introspection. This module makes that
|
|
5
|
+
* distinction explicit: `standard` is present when a value can validate, while `jsonSchema` and
|
|
6
|
+
* `fields` are present only when the value carries inspectable JSON Schema metadata. The implementation
|
|
7
|
+
* recognizes Nifra/TypeBox carriers and raw JSON Schema without depending on a validator package.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { StandardSchemaV1 } from "./schema/standard.ts"
|
|
11
|
+
import type { ToolAnnotations } from "./server/server.ts"
|
|
12
|
+
|
|
13
|
+
/** JSON Schema permits either a schema object or the boolean schemas `true` and `false`. */
|
|
14
|
+
export type JsonSchema = boolean | Readonly<Record<string, unknown>>
|
|
15
|
+
|
|
16
|
+
/** One top-level property of an introspectable object schema. */
|
|
17
|
+
export interface ReflectedSchemaField {
|
|
18
|
+
readonly name: string
|
|
19
|
+
readonly required: boolean
|
|
20
|
+
readonly schema: JsonSchema
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Validation and introspection capabilities discovered for one schema-like value. */
|
|
24
|
+
export interface SchemaReflection {
|
|
25
|
+
/** The Standard Schema validator, when the value implements Standard Schema v1. */
|
|
26
|
+
readonly standard: StandardSchemaV1 | undefined
|
|
27
|
+
/** Raw JSON Schema metadata, when the value exposes it or is itself a raw JSON Schema. */
|
|
28
|
+
readonly jsonSchema: JsonSchema | undefined
|
|
29
|
+
/** Top-level object fields, or `undefined` when the JSON Schema is absent/non-object. */
|
|
30
|
+
readonly fields: readonly ReflectedSchemaField[] | undefined
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ReflectedRouteSchema {
|
|
34
|
+
readonly body?: SchemaReflection
|
|
35
|
+
readonly query?: SchemaReflection
|
|
36
|
+
readonly response?: SchemaReflection
|
|
37
|
+
readonly errors?: Readonly<Record<string, SchemaReflection>>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ReflectedRoute {
|
|
41
|
+
readonly method: string
|
|
42
|
+
readonly path: string
|
|
43
|
+
readonly schema?: ReflectedRouteSchema
|
|
44
|
+
readonly tool?: {
|
|
45
|
+
readonly name: string
|
|
46
|
+
readonly description: string
|
|
47
|
+
readonly annotations?: ToolAnnotations
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const recordOf = (value: unknown): Readonly<Record<string, unknown>> | undefined =>
|
|
52
|
+
value !== null && typeof value === "object"
|
|
53
|
+
? (value as Readonly<Record<string, unknown>>)
|
|
54
|
+
: undefined
|
|
55
|
+
|
|
56
|
+
const standardOf = (value: unknown): StandardSchemaV1 | undefined => {
|
|
57
|
+
const record = recordOf(value)
|
|
58
|
+
const standard = recordOf(record?.["~standard"])
|
|
59
|
+
return standard?.version === 1 && typeof standard.validate === "function"
|
|
60
|
+
? (value as StandardSchemaV1)
|
|
61
|
+
: undefined
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const jsonSchemaOf = (
|
|
65
|
+
value: unknown,
|
|
66
|
+
standard: StandardSchemaV1 | undefined,
|
|
67
|
+
): JsonSchema | undefined => {
|
|
68
|
+
const record = recordOf(value)
|
|
69
|
+
if (record !== undefined && "jsonSchema" in record) {
|
|
70
|
+
const carried = record.jsonSchema
|
|
71
|
+
return normalizeJsonSchema(carried)
|
|
72
|
+
}
|
|
73
|
+
// Some Standard Schema implementations (including TypeBox-shaped adapters) expose an object
|
|
74
|
+
// schema directly rather than through `.jsonSchema`. Only treat an inspectable object shape as
|
|
75
|
+
// metadata here; validation-library internals are otherwise intentionally opaque.
|
|
76
|
+
if (standard !== undefined) {
|
|
77
|
+
return recordOf(record?.properties) !== undefined ? normalizeJsonSchema(record) : undefined
|
|
78
|
+
}
|
|
79
|
+
// A malformed/opaque Standard Schema marker is not raw JSON Schema metadata.
|
|
80
|
+
if (record !== undefined && "~standard" in record) return undefined
|
|
81
|
+
if (typeof value === "boolean" || record !== undefined) return normalizeJsonSchema(value)
|
|
82
|
+
return undefined
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Strip symbol keys and other non-JSON metadata carried by schema-builder objects. */
|
|
86
|
+
const normalizeJsonSchema = (value: unknown): JsonSchema | undefined => {
|
|
87
|
+
if (typeof value === "boolean") return value
|
|
88
|
+
if (recordOf(value) === undefined) return undefined
|
|
89
|
+
try {
|
|
90
|
+
const normalized: unknown = JSON.parse(JSON.stringify(value))
|
|
91
|
+
if (typeof normalized === "boolean") return normalized
|
|
92
|
+
const record = recordOf(normalized)
|
|
93
|
+
if (record === undefined) return undefined
|
|
94
|
+
// `~standard` is a Standard Schema *validation* marker, never valid JSON Schema. A TypeBox-shaped
|
|
95
|
+
// carrier (a JSON Schema object that also carries `~standard`, with no separate `.jsonSchema`) would
|
|
96
|
+
// otherwise leak the marker into emitted OpenAPI / mock / form output.
|
|
97
|
+
if (!("~standard" in record)) return record
|
|
98
|
+
const { ["~standard"]: _omitted, ...rest } = record
|
|
99
|
+
return rest
|
|
100
|
+
} catch {
|
|
101
|
+
return undefined
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const fieldsOf = (schema: JsonSchema | undefined): readonly ReflectedSchemaField[] | undefined => {
|
|
106
|
+
const record = recordOf(schema)
|
|
107
|
+
const properties = recordOf(record?.properties)
|
|
108
|
+
if (properties === undefined) return undefined
|
|
109
|
+
const required = new Set(
|
|
110
|
+
Array.isArray(record?.required)
|
|
111
|
+
? record.required.filter((name): name is string => typeof name === "string")
|
|
112
|
+
: [],
|
|
113
|
+
)
|
|
114
|
+
const fields: ReflectedSchemaField[] = []
|
|
115
|
+
for (const [name, property] of Object.entries(properties)) {
|
|
116
|
+
if (typeof property !== "boolean" && recordOf(property) === undefined) continue
|
|
117
|
+
fields.push({ name, required: required.has(name), schema: property as JsonSchema })
|
|
118
|
+
}
|
|
119
|
+
return fields
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Reflect a Standard Schema, Nifra/TypeBox schema carrier, or raw JSON Schema. Never throws.
|
|
124
|
+
* Validation-only schemas have `standard` but no `jsonSchema`; raw JSON Schema has the reverse.
|
|
125
|
+
*/
|
|
126
|
+
export function reflectSchema(value: unknown): SchemaReflection {
|
|
127
|
+
const standard = standardOf(value)
|
|
128
|
+
const jsonSchema = jsonSchemaOf(value, standard)
|
|
129
|
+
return { standard, jsonSchema, fields: fieldsOf(jsonSchema) }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const routeCandidates = (source: unknown): readonly unknown[] => {
|
|
133
|
+
if (Array.isArray(source)) return source
|
|
134
|
+
const record = recordOf(source)
|
|
135
|
+
if (typeof record?.routes !== "function") return []
|
|
136
|
+
try {
|
|
137
|
+
const routes = record.routes()
|
|
138
|
+
return Array.isArray(routes) ? routes : []
|
|
139
|
+
} catch {
|
|
140
|
+
return []
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const reflectedRouteSchema = (value: unknown): ReflectedRouteSchema | undefined => {
|
|
145
|
+
const schema = recordOf(value)
|
|
146
|
+
if (schema === undefined) return undefined
|
|
147
|
+
const errors = recordOf(schema.errors)
|
|
148
|
+
const reflectedErrors: Record<string, SchemaReflection> = {}
|
|
149
|
+
if (errors !== undefined) {
|
|
150
|
+
for (const [status, errorSchema] of Object.entries(errors)) {
|
|
151
|
+
reflectedErrors[status] = reflectSchema(errorSchema)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
...(schema.body !== undefined ? { body: reflectSchema(schema.body) } : {}),
|
|
156
|
+
...(schema.query !== undefined ? { query: reflectSchema(schema.query) } : {}),
|
|
157
|
+
...(schema.response !== undefined ? { response: reflectSchema(schema.response) } : {}),
|
|
158
|
+
...(Object.keys(reflectedErrors).length > 0 ? { errors: reflectedErrors } : {}),
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const reflectedTool = (value: unknown): ReflectedRoute["tool"] => {
|
|
163
|
+
const tool = recordOf(value)
|
|
164
|
+
if (typeof tool?.name !== "string" || typeof tool.description !== "string") return undefined
|
|
165
|
+
return {
|
|
166
|
+
name: tool.name,
|
|
167
|
+
description: tool.description,
|
|
168
|
+
...(recordOf(tool.annotations) !== undefined
|
|
169
|
+
? { annotations: tool.annotations as ToolAnnotations }
|
|
170
|
+
: {}),
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Safely enumerate and normalize route descriptors from an app or descriptor array. Invalid entries
|
|
176
|
+
* are ignored; a missing/throwing `routes()` method yields an empty array.
|
|
177
|
+
*/
|
|
178
|
+
export function reflectRoutes(source: unknown): readonly ReflectedRoute[] {
|
|
179
|
+
const reflected: ReflectedRoute[] = []
|
|
180
|
+
for (const candidate of routeCandidates(source)) {
|
|
181
|
+
const route = recordOf(candidate)
|
|
182
|
+
if (typeof route?.method !== "string" || typeof route.path !== "string") continue
|
|
183
|
+
const schema = reflectedRouteSchema(route.schema)
|
|
184
|
+
const tool = reflectedTool(route.tool)
|
|
185
|
+
reflected.push({
|
|
186
|
+
method: route.method.toUpperCase(),
|
|
187
|
+
path: route.path,
|
|
188
|
+
...(schema !== undefined ? { schema } : {}),
|
|
189
|
+
...(tool !== undefined ? { tool } : {}),
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
return reflected
|
|
193
|
+
}
|