@cosmneo/onion-lasagna-typebox 0.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/dist/index.cjs ADDED
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ Type: () => import_typebox.Type,
24
+ pagination: () => pagination,
25
+ typeboxSchema: () => typeboxSchema
26
+ });
27
+ module.exports = __toCommonJS(src_exports);
28
+
29
+ // src/typebox.adapter.ts
30
+ var import_value = require("@sinclair/typebox/value");
31
+ var import_typebox = require("@sinclair/typebox");
32
+ function safeStringify(value) {
33
+ try {
34
+ return JSON.stringify(value);
35
+ } catch {
36
+ if (typeof value === "object" && value !== null) {
37
+ return "[Complex Object]";
38
+ }
39
+ if (typeof value === "bigint") {
40
+ return String(value);
41
+ }
42
+ return String(value);
43
+ }
44
+ }
45
+ function typeboxSchema(schema) {
46
+ return {
47
+ validate(data) {
48
+ if (!import_value.Value.Check(schema, data)) {
49
+ const errors = [...import_value.Value.Errors(schema, data)];
50
+ return {
51
+ success: false,
52
+ issues: errors.map((error) => ({
53
+ // TypeBox paths are like '/property/nested' - convert to array
54
+ path: error.path.split("/").filter(Boolean),
55
+ message: error.message,
56
+ code: error.type ? String(error.type) : void 0,
57
+ // Use safeStringify to handle circular references and non-serializable values
58
+ expected: error.schema ? safeStringify(error.schema) : void 0,
59
+ received: error.value !== void 0 ? safeStringify(error.value) : void 0
60
+ }))
61
+ };
62
+ }
63
+ try {
64
+ const decoded = import_value.Value.Decode(schema, data);
65
+ return { success: true, data: decoded };
66
+ } catch (error) {
67
+ return {
68
+ success: false,
69
+ issues: [
70
+ {
71
+ path: [],
72
+ message: error instanceof Error ? error.message : "Transform decode failed",
73
+ code: "transform_error"
74
+ }
75
+ ]
76
+ };
77
+ }
78
+ },
79
+ toJsonSchema(_options) {
80
+ return structuredClone(schema);
81
+ },
82
+ _output: void 0,
83
+ _input: void 0,
84
+ _schema: schema
85
+ };
86
+ }
87
+
88
+ // src/schemas/pagination.ts
89
+ var import_typebox2 = require("@sinclair/typebox");
90
+ var pagination = {
91
+ /**
92
+ * Query params schema for paginated list requests.
93
+ *
94
+ * Uses JSON Schema `default` annotations. Coercion from query string
95
+ * values is handled by the framework (e.g., Fastify).
96
+ *
97
+ * @example
98
+ * ```typescript
99
+ * // Direct use
100
+ * typeboxSchema(pagination.input)
101
+ *
102
+ * // Extended with filters
103
+ * typeboxSchema(Type.Intersect([
104
+ * pagination.input,
105
+ * Type.Object({ searchTerm: Type.Optional(Type.String()) }),
106
+ * ]))
107
+ * ```
108
+ */
109
+ input: import_typebox2.Type.Object({
110
+ page: import_typebox2.Type.Optional(import_typebox2.Type.Integer({ minimum: 1, default: 1 })),
111
+ pageSize: import_typebox2.Type.Optional(import_typebox2.Type.Integer({ minimum: 1, maximum: 100, default: 10 }))
112
+ }),
113
+ /**
114
+ * Factory for paginated response schemas.
115
+ *
116
+ * @param itemSchema - TypeBox schema for individual items in the list
117
+ * @returns TypeBox schema for `{ items: T[], total: number }`
118
+ */
119
+ response: (itemSchema) => import_typebox2.Type.Object({
120
+ items: import_typebox2.Type.Array(itemSchema),
121
+ total: import_typebox2.Type.Integer({ minimum: 0 })
122
+ })
123
+ };
124
+ // Annotate the CommonJS export names for ESM import in node:
125
+ 0 && (module.exports = {
126
+ Type,
127
+ pagination,
128
+ typeboxSchema
129
+ });
130
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/typebox.adapter.ts","../src/schemas/pagination.ts"],"sourcesContent":["export { typeboxSchema, Type } from './typebox.adapter';\nexport { pagination } from './schemas/pagination';\n","/**\n * @fileoverview TypeBox schema adapter for the unified route system.\n *\n * TypeBox is unique among validation libraries because its schemas ARE\n * JSON Schema. This makes it ideal for OpenAPI generation with zero\n * conversion overhead.\n *\n * @module unified/schema/adapters/typebox\n */\n\nimport type { Static, StaticEncode, TSchema } from '@sinclair/typebox';\nimport { Value } from '@sinclair/typebox/value';\nimport type {\n JsonSchema,\n JsonSchemaOptions,\n SchemaAdapter,\n ValidationResult,\n} from '@cosmneo/onion-lasagna/http/schema/types';\n\n/**\n * Safely stringifies a value for error reporting.\n * Returns undefined if stringification fails (e.g., circular references, BigInt).\n */\nfunction safeStringify(value: unknown): string | undefined {\n try {\n return JSON.stringify(value);\n } catch {\n // Handle circular references, BigInt, or other non-serializable values\n if (typeof value === 'object' && value !== null) {\n return '[Complex Object]';\n }\n if (typeof value === 'bigint') {\n return String(value);\n }\n return String(value);\n }\n}\n\n/**\n * Creates a SchemaAdapter from a TypeBox schema.\n *\n * TypeBox schemas are JSON Schema, so `toJsonSchema()` simply returns\n * the schema itself (with optional cleanup). This makes TypeBox the most\n * efficient choice when OpenAPI generation is a priority.\n *\n * @typeParam T - A TypeBox schema type\n *\n * @param schema - A TypeBox schema to wrap\n * @returns A SchemaAdapter that validates using TypeBox and returns the schema as JSON Schema\n *\n * @example Basic usage\n * ```typescript\n * import { Type } from '@sinclair/typebox';\n * import { typeboxSchema } from '@cosmneo/onion-lasagna-typebox';\n *\n * const userSchema = typeboxSchema(Type.Object({\n * name: Type.String({ minLength: 1, maxLength: 100 }),\n * email: Type.String({ format: 'email' }),\n * age: Type.Optional(Type.Integer({ minimum: 0, maximum: 150 })),\n * }));\n *\n * // Validate data\n * const result = userSchema.validate({\n * name: 'John Doe',\n * email: 'john@example.com',\n * });\n *\n * if (result.success) {\n * console.log(result.data); // { name: 'John Doe', email: 'john@example.com' }\n * }\n *\n * // Get JSON Schema (this IS the TypeBox schema!)\n * const jsonSchema = userSchema.toJsonSchema();\n * ```\n *\n * @example Complex types\n * ```typescript\n * const orderSchema = typeboxSchema(Type.Object({\n * id: Type.String({ format: 'uuid' }),\n * items: Type.Array(Type.Object({\n * productId: Type.String(),\n * quantity: Type.Integer({ minimum: 1 }),\n * price: Type.Number({ minimum: 0 }),\n * })),\n * status: Type.Union([\n * Type.Literal('pending'),\n * Type.Literal('processing'),\n * Type.Literal('shipped'),\n * Type.Literal('delivered'),\n * ]),\n * createdAt: Type.String({ format: 'date-time' }),\n * }));\n * ```\n */\nexport function typeboxSchema<T extends TSchema>(\n schema: T,\n): SchemaAdapter<Static<T>, StaticEncode<T>> {\n return {\n validate(data: unknown): ValidationResult<Static<T>> {\n if (!Value.Check(schema, data)) {\n // Collect all validation errors\n const errors = [...Value.Errors(schema, data)];\n\n return {\n success: false,\n issues: errors.map((error) => ({\n // TypeBox paths are like '/property/nested' - convert to array\n path: error.path.split('/').filter(Boolean),\n message: error.message,\n code: error.type ? String(error.type) : undefined,\n // Use safeStringify to handle circular references and non-serializable values\n expected: error.schema ? safeStringify(error.schema) : undefined,\n received: error.value !== undefined ? safeStringify(error.value) : undefined,\n })),\n };\n }\n\n // Apply transforms if any (no-op when schema has no transforms)\n try {\n const decoded = Value.Decode(schema, data);\n return { success: true, data: decoded };\n } catch (error) {\n return {\n success: false,\n issues: [\n {\n path: [],\n message: error instanceof Error ? error.message : 'Transform decode failed',\n code: 'transform_error',\n },\n ],\n };\n }\n },\n\n toJsonSchema(_options?: JsonSchemaOptions): JsonSchema {\n // Deep clone to prevent mutation and strip TypeBox-specific\n // Symbol-keyed metadata ([Kind], etc.), producing clean JSON Schema\n return structuredClone(schema) as JsonSchema;\n },\n\n _output: undefined as Static<T>,\n _input: undefined as StaticEncode<T>,\n _schema: schema,\n };\n}\n\n/**\n * Re-export TypeBox Type for convenience.\n * Users can import TypeBox functionality from this module.\n */\nexport { Type } from '@sinclair/typebox';\n","/**\n * @fileoverview Pre-built TypeBox pagination schemas.\n *\n * Provides reusable schemas for paginated list endpoints,\n * matching the core `PaginationInput` and `PaginatedData<T>` types.\n *\n * TypeBox uses JSON Schema `default` annotations for default values.\n * Frameworks like Fastify handle coercion from strings natively via JSON Schema.\n *\n * @module schemas/pagination\n */\n\nimport { Type, type TSchema } from '@sinclair/typebox';\n\nexport const pagination = {\n /**\n * Query params schema for paginated list requests.\n *\n * Uses JSON Schema `default` annotations. Coercion from query string\n * values is handled by the framework (e.g., Fastify).\n *\n * @example\n * ```typescript\n * // Direct use\n * typeboxSchema(pagination.input)\n *\n * // Extended with filters\n * typeboxSchema(Type.Intersect([\n * pagination.input,\n * Type.Object({ searchTerm: Type.Optional(Type.String()) }),\n * ]))\n * ```\n */\n input: Type.Object({\n page: Type.Optional(Type.Integer({ minimum: 1, default: 1 })),\n pageSize: Type.Optional(Type.Integer({ minimum: 1, maximum: 100, default: 10 })),\n }),\n\n /**\n * Factory for paginated response schemas.\n *\n * @param itemSchema - TypeBox schema for individual items in the list\n * @returns TypeBox schema for `{ items: T[], total: number }`\n */\n response: <T extends TSchema>(itemSchema: T) =>\n Type.Object({\n items: Type.Array(itemSchema),\n total: Type.Integer({ minimum: 0 }),\n }),\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACWA,mBAAsB;AA4ItB,qBAAqB;AAhIrB,SAAS,cAAc,OAAoC;AACzD,MAAI;AACF,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B,QAAQ;AAEN,QAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,aAAO;AAAA,IACT;AACA,QAAI,OAAO,UAAU,UAAU;AAC7B,aAAO,OAAO,KAAK;AAAA,IACrB;AACA,WAAO,OAAO,KAAK;AAAA,EACrB;AACF;AA0DO,SAAS,cACd,QAC2C;AAC3C,SAAO;AAAA,IACL,SAAS,MAA4C;AACnD,UAAI,CAAC,mBAAM,MAAM,QAAQ,IAAI,GAAG;AAE9B,cAAM,SAAS,CAAC,GAAG,mBAAM,OAAO,QAAQ,IAAI,CAAC;AAE7C,eAAO;AAAA,UACL,SAAS;AAAA,UACT,QAAQ,OAAO,IAAI,CAAC,WAAW;AAAA;AAAA,YAE7B,MAAM,MAAM,KAAK,MAAM,GAAG,EAAE,OAAO,OAAO;AAAA,YAC1C,SAAS,MAAM;AAAA,YACf,MAAM,MAAM,OAAO,OAAO,MAAM,IAAI,IAAI;AAAA;AAAA,YAExC,UAAU,MAAM,SAAS,cAAc,MAAM,MAAM,IAAI;AAAA,YACvD,UAAU,MAAM,UAAU,SAAY,cAAc,MAAM,KAAK,IAAI;AAAA,UACrE,EAAE;AAAA,QACJ;AAAA,MACF;AAGA,UAAI;AACF,cAAM,UAAU,mBAAM,OAAO,QAAQ,IAAI;AACzC,eAAO,EAAE,SAAS,MAAM,MAAM,QAAQ;AAAA,MACxC,SAAS,OAAO;AACd,eAAO;AAAA,UACL,SAAS;AAAA,UACT,QAAQ;AAAA,YACN;AAAA,cACE,MAAM,CAAC;AAAA,cACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU;AAAA,cAClD,MAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IAEA,aAAa,UAA0C;AAGrD,aAAO,gBAAgB,MAAM;AAAA,IAC/B;AAAA,IAEA,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,SAAS;AAAA,EACX;AACF;;;ACrIA,IAAAA,kBAAmC;AAE5B,IAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBxB,OAAO,qBAAK,OAAO;AAAA,IACjB,MAAM,qBAAK,SAAS,qBAAK,QAAQ,EAAE,SAAS,GAAG,SAAS,EAAE,CAAC,CAAC;AAAA,IAC5D,UAAU,qBAAK,SAAS,qBAAK,QAAQ,EAAE,SAAS,GAAG,SAAS,KAAK,SAAS,GAAG,CAAC,CAAC;AAAA,EACjF,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQD,UAAU,CAAoB,eAC5B,qBAAK,OAAO;AAAA,IACV,OAAO,qBAAK,MAAM,UAAU;AAAA,IAC5B,OAAO,qBAAK,QAAQ,EAAE,SAAS,EAAE,CAAC;AAAA,EACpC,CAAC;AACL;","names":["import_typebox"]}
@@ -0,0 +1,109 @@
1
+ import * as _sinclair_typebox from '@sinclair/typebox';
2
+ import { TSchema, Static, StaticEncode } from '@sinclair/typebox';
3
+ export { Type } from '@sinclair/typebox';
4
+ import { SchemaAdapter } from '@cosmneo/onion-lasagna/http/schema/types';
5
+
6
+ /**
7
+ * @fileoverview TypeBox schema adapter for the unified route system.
8
+ *
9
+ * TypeBox is unique among validation libraries because its schemas ARE
10
+ * JSON Schema. This makes it ideal for OpenAPI generation with zero
11
+ * conversion overhead.
12
+ *
13
+ * @module unified/schema/adapters/typebox
14
+ */
15
+
16
+ /**
17
+ * Creates a SchemaAdapter from a TypeBox schema.
18
+ *
19
+ * TypeBox schemas are JSON Schema, so `toJsonSchema()` simply returns
20
+ * the schema itself (with optional cleanup). This makes TypeBox the most
21
+ * efficient choice when OpenAPI generation is a priority.
22
+ *
23
+ * @typeParam T - A TypeBox schema type
24
+ *
25
+ * @param schema - A TypeBox schema to wrap
26
+ * @returns A SchemaAdapter that validates using TypeBox and returns the schema as JSON Schema
27
+ *
28
+ * @example Basic usage
29
+ * ```typescript
30
+ * import { Type } from '@sinclair/typebox';
31
+ * import { typeboxSchema } from '@cosmneo/onion-lasagna-typebox';
32
+ *
33
+ * const userSchema = typeboxSchema(Type.Object({
34
+ * name: Type.String({ minLength: 1, maxLength: 100 }),
35
+ * email: Type.String({ format: 'email' }),
36
+ * age: Type.Optional(Type.Integer({ minimum: 0, maximum: 150 })),
37
+ * }));
38
+ *
39
+ * // Validate data
40
+ * const result = userSchema.validate({
41
+ * name: 'John Doe',
42
+ * email: 'john@example.com',
43
+ * });
44
+ *
45
+ * if (result.success) {
46
+ * console.log(result.data); // { name: 'John Doe', email: 'john@example.com' }
47
+ * }
48
+ *
49
+ * // Get JSON Schema (this IS the TypeBox schema!)
50
+ * const jsonSchema = userSchema.toJsonSchema();
51
+ * ```
52
+ *
53
+ * @example Complex types
54
+ * ```typescript
55
+ * const orderSchema = typeboxSchema(Type.Object({
56
+ * id: Type.String({ format: 'uuid' }),
57
+ * items: Type.Array(Type.Object({
58
+ * productId: Type.String(),
59
+ * quantity: Type.Integer({ minimum: 1 }),
60
+ * price: Type.Number({ minimum: 0 }),
61
+ * })),
62
+ * status: Type.Union([
63
+ * Type.Literal('pending'),
64
+ * Type.Literal('processing'),
65
+ * Type.Literal('shipped'),
66
+ * Type.Literal('delivered'),
67
+ * ]),
68
+ * createdAt: Type.String({ format: 'date-time' }),
69
+ * }));
70
+ * ```
71
+ */
72
+ declare function typeboxSchema<T extends TSchema>(schema: T): SchemaAdapter<Static<T>, StaticEncode<T>>;
73
+
74
+ declare const pagination: {
75
+ /**
76
+ * Query params schema for paginated list requests.
77
+ *
78
+ * Uses JSON Schema `default` annotations. Coercion from query string
79
+ * values is handled by the framework (e.g., Fastify).
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * // Direct use
84
+ * typeboxSchema(pagination.input)
85
+ *
86
+ * // Extended with filters
87
+ * typeboxSchema(Type.Intersect([
88
+ * pagination.input,
89
+ * Type.Object({ searchTerm: Type.Optional(Type.String()) }),
90
+ * ]))
91
+ * ```
92
+ */
93
+ input: _sinclair_typebox.TObject<{
94
+ page: _sinclair_typebox.TOptional<_sinclair_typebox.TInteger>;
95
+ pageSize: _sinclair_typebox.TOptional<_sinclair_typebox.TInteger>;
96
+ }>;
97
+ /**
98
+ * Factory for paginated response schemas.
99
+ *
100
+ * @param itemSchema - TypeBox schema for individual items in the list
101
+ * @returns TypeBox schema for `{ items: T[], total: number }`
102
+ */
103
+ response: <T extends TSchema>(itemSchema: T) => _sinclair_typebox.TObject<{
104
+ items: _sinclair_typebox.TArray<T>;
105
+ total: _sinclair_typebox.TInteger;
106
+ }>;
107
+ };
108
+
109
+ export { pagination, typeboxSchema };
@@ -0,0 +1,109 @@
1
+ import * as _sinclair_typebox from '@sinclair/typebox';
2
+ import { TSchema, Static, StaticEncode } from '@sinclair/typebox';
3
+ export { Type } from '@sinclair/typebox';
4
+ import { SchemaAdapter } from '@cosmneo/onion-lasagna/http/schema/types';
5
+
6
+ /**
7
+ * @fileoverview TypeBox schema adapter for the unified route system.
8
+ *
9
+ * TypeBox is unique among validation libraries because its schemas ARE
10
+ * JSON Schema. This makes it ideal for OpenAPI generation with zero
11
+ * conversion overhead.
12
+ *
13
+ * @module unified/schema/adapters/typebox
14
+ */
15
+
16
+ /**
17
+ * Creates a SchemaAdapter from a TypeBox schema.
18
+ *
19
+ * TypeBox schemas are JSON Schema, so `toJsonSchema()` simply returns
20
+ * the schema itself (with optional cleanup). This makes TypeBox the most
21
+ * efficient choice when OpenAPI generation is a priority.
22
+ *
23
+ * @typeParam T - A TypeBox schema type
24
+ *
25
+ * @param schema - A TypeBox schema to wrap
26
+ * @returns A SchemaAdapter that validates using TypeBox and returns the schema as JSON Schema
27
+ *
28
+ * @example Basic usage
29
+ * ```typescript
30
+ * import { Type } from '@sinclair/typebox';
31
+ * import { typeboxSchema } from '@cosmneo/onion-lasagna-typebox';
32
+ *
33
+ * const userSchema = typeboxSchema(Type.Object({
34
+ * name: Type.String({ minLength: 1, maxLength: 100 }),
35
+ * email: Type.String({ format: 'email' }),
36
+ * age: Type.Optional(Type.Integer({ minimum: 0, maximum: 150 })),
37
+ * }));
38
+ *
39
+ * // Validate data
40
+ * const result = userSchema.validate({
41
+ * name: 'John Doe',
42
+ * email: 'john@example.com',
43
+ * });
44
+ *
45
+ * if (result.success) {
46
+ * console.log(result.data); // { name: 'John Doe', email: 'john@example.com' }
47
+ * }
48
+ *
49
+ * // Get JSON Schema (this IS the TypeBox schema!)
50
+ * const jsonSchema = userSchema.toJsonSchema();
51
+ * ```
52
+ *
53
+ * @example Complex types
54
+ * ```typescript
55
+ * const orderSchema = typeboxSchema(Type.Object({
56
+ * id: Type.String({ format: 'uuid' }),
57
+ * items: Type.Array(Type.Object({
58
+ * productId: Type.String(),
59
+ * quantity: Type.Integer({ minimum: 1 }),
60
+ * price: Type.Number({ minimum: 0 }),
61
+ * })),
62
+ * status: Type.Union([
63
+ * Type.Literal('pending'),
64
+ * Type.Literal('processing'),
65
+ * Type.Literal('shipped'),
66
+ * Type.Literal('delivered'),
67
+ * ]),
68
+ * createdAt: Type.String({ format: 'date-time' }),
69
+ * }));
70
+ * ```
71
+ */
72
+ declare function typeboxSchema<T extends TSchema>(schema: T): SchemaAdapter<Static<T>, StaticEncode<T>>;
73
+
74
+ declare const pagination: {
75
+ /**
76
+ * Query params schema for paginated list requests.
77
+ *
78
+ * Uses JSON Schema `default` annotations. Coercion from query string
79
+ * values is handled by the framework (e.g., Fastify).
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * // Direct use
84
+ * typeboxSchema(pagination.input)
85
+ *
86
+ * // Extended with filters
87
+ * typeboxSchema(Type.Intersect([
88
+ * pagination.input,
89
+ * Type.Object({ searchTerm: Type.Optional(Type.String()) }),
90
+ * ]))
91
+ * ```
92
+ */
93
+ input: _sinclair_typebox.TObject<{
94
+ page: _sinclair_typebox.TOptional<_sinclair_typebox.TInteger>;
95
+ pageSize: _sinclair_typebox.TOptional<_sinclair_typebox.TInteger>;
96
+ }>;
97
+ /**
98
+ * Factory for paginated response schemas.
99
+ *
100
+ * @param itemSchema - TypeBox schema for individual items in the list
101
+ * @returns TypeBox schema for `{ items: T[], total: number }`
102
+ */
103
+ response: <T extends TSchema>(itemSchema: T) => _sinclair_typebox.TObject<{
104
+ items: _sinclair_typebox.TArray<T>;
105
+ total: _sinclair_typebox.TInteger;
106
+ }>;
107
+ };
108
+
109
+ export { pagination, typeboxSchema };
package/dist/index.js ADDED
@@ -0,0 +1,101 @@
1
+ // src/typebox.adapter.ts
2
+ import { Value } from "@sinclair/typebox/value";
3
+ import { Type } from "@sinclair/typebox";
4
+ function safeStringify(value) {
5
+ try {
6
+ return JSON.stringify(value);
7
+ } catch {
8
+ if (typeof value === "object" && value !== null) {
9
+ return "[Complex Object]";
10
+ }
11
+ if (typeof value === "bigint") {
12
+ return String(value);
13
+ }
14
+ return String(value);
15
+ }
16
+ }
17
+ function typeboxSchema(schema) {
18
+ return {
19
+ validate(data) {
20
+ if (!Value.Check(schema, data)) {
21
+ const errors = [...Value.Errors(schema, data)];
22
+ return {
23
+ success: false,
24
+ issues: errors.map((error) => ({
25
+ // TypeBox paths are like '/property/nested' - convert to array
26
+ path: error.path.split("/").filter(Boolean),
27
+ message: error.message,
28
+ code: error.type ? String(error.type) : void 0,
29
+ // Use safeStringify to handle circular references and non-serializable values
30
+ expected: error.schema ? safeStringify(error.schema) : void 0,
31
+ received: error.value !== void 0 ? safeStringify(error.value) : void 0
32
+ }))
33
+ };
34
+ }
35
+ try {
36
+ const decoded = Value.Decode(schema, data);
37
+ return { success: true, data: decoded };
38
+ } catch (error) {
39
+ return {
40
+ success: false,
41
+ issues: [
42
+ {
43
+ path: [],
44
+ message: error instanceof Error ? error.message : "Transform decode failed",
45
+ code: "transform_error"
46
+ }
47
+ ]
48
+ };
49
+ }
50
+ },
51
+ toJsonSchema(_options) {
52
+ return structuredClone(schema);
53
+ },
54
+ _output: void 0,
55
+ _input: void 0,
56
+ _schema: schema
57
+ };
58
+ }
59
+
60
+ // src/schemas/pagination.ts
61
+ import { Type as Type2 } from "@sinclair/typebox";
62
+ var pagination = {
63
+ /**
64
+ * Query params schema for paginated list requests.
65
+ *
66
+ * Uses JSON Schema `default` annotations. Coercion from query string
67
+ * values is handled by the framework (e.g., Fastify).
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * // Direct use
72
+ * typeboxSchema(pagination.input)
73
+ *
74
+ * // Extended with filters
75
+ * typeboxSchema(Type.Intersect([
76
+ * pagination.input,
77
+ * Type.Object({ searchTerm: Type.Optional(Type.String()) }),
78
+ * ]))
79
+ * ```
80
+ */
81
+ input: Type2.Object({
82
+ page: Type2.Optional(Type2.Integer({ minimum: 1, default: 1 })),
83
+ pageSize: Type2.Optional(Type2.Integer({ minimum: 1, maximum: 100, default: 10 }))
84
+ }),
85
+ /**
86
+ * Factory for paginated response schemas.
87
+ *
88
+ * @param itemSchema - TypeBox schema for individual items in the list
89
+ * @returns TypeBox schema for `{ items: T[], total: number }`
90
+ */
91
+ response: (itemSchema) => Type2.Object({
92
+ items: Type2.Array(itemSchema),
93
+ total: Type2.Integer({ minimum: 0 })
94
+ })
95
+ };
96
+ export {
97
+ Type,
98
+ pagination,
99
+ typeboxSchema
100
+ };
101
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/typebox.adapter.ts","../src/schemas/pagination.ts"],"sourcesContent":["/**\n * @fileoverview TypeBox schema adapter for the unified route system.\n *\n * TypeBox is unique among validation libraries because its schemas ARE\n * JSON Schema. This makes it ideal for OpenAPI generation with zero\n * conversion overhead.\n *\n * @module unified/schema/adapters/typebox\n */\n\nimport type { Static, StaticEncode, TSchema } from '@sinclair/typebox';\nimport { Value } from '@sinclair/typebox/value';\nimport type {\n JsonSchema,\n JsonSchemaOptions,\n SchemaAdapter,\n ValidationResult,\n} from '@cosmneo/onion-lasagna/http/schema/types';\n\n/**\n * Safely stringifies a value for error reporting.\n * Returns undefined if stringification fails (e.g., circular references, BigInt).\n */\nfunction safeStringify(value: unknown): string | undefined {\n try {\n return JSON.stringify(value);\n } catch {\n // Handle circular references, BigInt, or other non-serializable values\n if (typeof value === 'object' && value !== null) {\n return '[Complex Object]';\n }\n if (typeof value === 'bigint') {\n return String(value);\n }\n return String(value);\n }\n}\n\n/**\n * Creates a SchemaAdapter from a TypeBox schema.\n *\n * TypeBox schemas are JSON Schema, so `toJsonSchema()` simply returns\n * the schema itself (with optional cleanup). This makes TypeBox the most\n * efficient choice when OpenAPI generation is a priority.\n *\n * @typeParam T - A TypeBox schema type\n *\n * @param schema - A TypeBox schema to wrap\n * @returns A SchemaAdapter that validates using TypeBox and returns the schema as JSON Schema\n *\n * @example Basic usage\n * ```typescript\n * import { Type } from '@sinclair/typebox';\n * import { typeboxSchema } from '@cosmneo/onion-lasagna-typebox';\n *\n * const userSchema = typeboxSchema(Type.Object({\n * name: Type.String({ minLength: 1, maxLength: 100 }),\n * email: Type.String({ format: 'email' }),\n * age: Type.Optional(Type.Integer({ minimum: 0, maximum: 150 })),\n * }));\n *\n * // Validate data\n * const result = userSchema.validate({\n * name: 'John Doe',\n * email: 'john@example.com',\n * });\n *\n * if (result.success) {\n * console.log(result.data); // { name: 'John Doe', email: 'john@example.com' }\n * }\n *\n * // Get JSON Schema (this IS the TypeBox schema!)\n * const jsonSchema = userSchema.toJsonSchema();\n * ```\n *\n * @example Complex types\n * ```typescript\n * const orderSchema = typeboxSchema(Type.Object({\n * id: Type.String({ format: 'uuid' }),\n * items: Type.Array(Type.Object({\n * productId: Type.String(),\n * quantity: Type.Integer({ minimum: 1 }),\n * price: Type.Number({ minimum: 0 }),\n * })),\n * status: Type.Union([\n * Type.Literal('pending'),\n * Type.Literal('processing'),\n * Type.Literal('shipped'),\n * Type.Literal('delivered'),\n * ]),\n * createdAt: Type.String({ format: 'date-time' }),\n * }));\n * ```\n */\nexport function typeboxSchema<T extends TSchema>(\n schema: T,\n): SchemaAdapter<Static<T>, StaticEncode<T>> {\n return {\n validate(data: unknown): ValidationResult<Static<T>> {\n if (!Value.Check(schema, data)) {\n // Collect all validation errors\n const errors = [...Value.Errors(schema, data)];\n\n return {\n success: false,\n issues: errors.map((error) => ({\n // TypeBox paths are like '/property/nested' - convert to array\n path: error.path.split('/').filter(Boolean),\n message: error.message,\n code: error.type ? String(error.type) : undefined,\n // Use safeStringify to handle circular references and non-serializable values\n expected: error.schema ? safeStringify(error.schema) : undefined,\n received: error.value !== undefined ? safeStringify(error.value) : undefined,\n })),\n };\n }\n\n // Apply transforms if any (no-op when schema has no transforms)\n try {\n const decoded = Value.Decode(schema, data);\n return { success: true, data: decoded };\n } catch (error) {\n return {\n success: false,\n issues: [\n {\n path: [],\n message: error instanceof Error ? error.message : 'Transform decode failed',\n code: 'transform_error',\n },\n ],\n };\n }\n },\n\n toJsonSchema(_options?: JsonSchemaOptions): JsonSchema {\n // Deep clone to prevent mutation and strip TypeBox-specific\n // Symbol-keyed metadata ([Kind], etc.), producing clean JSON Schema\n return structuredClone(schema) as JsonSchema;\n },\n\n _output: undefined as Static<T>,\n _input: undefined as StaticEncode<T>,\n _schema: schema,\n };\n}\n\n/**\n * Re-export TypeBox Type for convenience.\n * Users can import TypeBox functionality from this module.\n */\nexport { Type } from '@sinclair/typebox';\n","/**\n * @fileoverview Pre-built TypeBox pagination schemas.\n *\n * Provides reusable schemas for paginated list endpoints,\n * matching the core `PaginationInput` and `PaginatedData<T>` types.\n *\n * TypeBox uses JSON Schema `default` annotations for default values.\n * Frameworks like Fastify handle coercion from strings natively via JSON Schema.\n *\n * @module schemas/pagination\n */\n\nimport { Type, type TSchema } from '@sinclair/typebox';\n\nexport const pagination = {\n /**\n * Query params schema for paginated list requests.\n *\n * Uses JSON Schema `default` annotations. Coercion from query string\n * values is handled by the framework (e.g., Fastify).\n *\n * @example\n * ```typescript\n * // Direct use\n * typeboxSchema(pagination.input)\n *\n * // Extended with filters\n * typeboxSchema(Type.Intersect([\n * pagination.input,\n * Type.Object({ searchTerm: Type.Optional(Type.String()) }),\n * ]))\n * ```\n */\n input: Type.Object({\n page: Type.Optional(Type.Integer({ minimum: 1, default: 1 })),\n pageSize: Type.Optional(Type.Integer({ minimum: 1, maximum: 100, default: 10 })),\n }),\n\n /**\n * Factory for paginated response schemas.\n *\n * @param itemSchema - TypeBox schema for individual items in the list\n * @returns TypeBox schema for `{ items: T[], total: number }`\n */\n response: <T extends TSchema>(itemSchema: T) =>\n Type.Object({\n items: Type.Array(itemSchema),\n total: Type.Integer({ minimum: 0 }),\n }),\n};\n"],"mappings":";AAWA,SAAS,aAAa;AA4ItB,SAAS,YAAY;AAhIrB,SAAS,cAAc,OAAoC;AACzD,MAAI;AACF,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B,QAAQ;AAEN,QAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,aAAO;AAAA,IACT;AACA,QAAI,OAAO,UAAU,UAAU;AAC7B,aAAO,OAAO,KAAK;AAAA,IACrB;AACA,WAAO,OAAO,KAAK;AAAA,EACrB;AACF;AA0DO,SAAS,cACd,QAC2C;AAC3C,SAAO;AAAA,IACL,SAAS,MAA4C;AACnD,UAAI,CAAC,MAAM,MAAM,QAAQ,IAAI,GAAG;AAE9B,cAAM,SAAS,CAAC,GAAG,MAAM,OAAO,QAAQ,IAAI,CAAC;AAE7C,eAAO;AAAA,UACL,SAAS;AAAA,UACT,QAAQ,OAAO,IAAI,CAAC,WAAW;AAAA;AAAA,YAE7B,MAAM,MAAM,KAAK,MAAM,GAAG,EAAE,OAAO,OAAO;AAAA,YAC1C,SAAS,MAAM;AAAA,YACf,MAAM,MAAM,OAAO,OAAO,MAAM,IAAI,IAAI;AAAA;AAAA,YAExC,UAAU,MAAM,SAAS,cAAc,MAAM,MAAM,IAAI;AAAA,YACvD,UAAU,MAAM,UAAU,SAAY,cAAc,MAAM,KAAK,IAAI;AAAA,UACrE,EAAE;AAAA,QACJ;AAAA,MACF;AAGA,UAAI;AACF,cAAM,UAAU,MAAM,OAAO,QAAQ,IAAI;AACzC,eAAO,EAAE,SAAS,MAAM,MAAM,QAAQ;AAAA,MACxC,SAAS,OAAO;AACd,eAAO;AAAA,UACL,SAAS;AAAA,UACT,QAAQ;AAAA,YACN;AAAA,cACE,MAAM,CAAC;AAAA,cACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU;AAAA,cAClD,MAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IAEA,aAAa,UAA0C;AAGrD,aAAO,gBAAgB,MAAM;AAAA,IAC/B;AAAA,IAEA,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,SAAS;AAAA,EACX;AACF;;;ACrIA,SAAS,QAAAA,aAA0B;AAE5B,IAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBxB,OAAOA,MAAK,OAAO;AAAA,IACjB,MAAMA,MAAK,SAASA,MAAK,QAAQ,EAAE,SAAS,GAAG,SAAS,EAAE,CAAC,CAAC;AAAA,IAC5D,UAAUA,MAAK,SAASA,MAAK,QAAQ,EAAE,SAAS,GAAG,SAAS,KAAK,SAAS,GAAG,CAAC,CAAC;AAAA,EACjF,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQD,UAAU,CAAoB,eAC5BA,MAAK,OAAO;AAAA,IACV,OAAOA,MAAK,MAAM,UAAU;AAAA,IAC5B,OAAOA,MAAK,QAAQ,EAAE,SAAS,EAAE,CAAC;AAAA,EACpC,CAAC;AACL;","names":["Type"]}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@cosmneo/onion-lasagna-typebox",
3
+ "version": "0.2.0",
4
+ "description": "TypeBox schema adapter for onion-lasagna",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Cosmneo",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/Cosmneo/onion-lasagna.git",
11
+ "directory": "packages/schemas/onion-lasagna-typebox"
12
+ },
13
+ "engines": {
14
+ "node": ">=18.0.0"
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "sideEffects": false,
20
+ "devDependencies": {
21
+ "@sinclair/typebox": "^0.34.41",
22
+ "@cosmneo/onion-lasagna": "workspace:*",
23
+ "tsup": "^8.5.1",
24
+ "vitest": "^4.0.16"
25
+ },
26
+ "peerDependencies": {
27
+ "@sinclair/typebox": "^0.34.41",
28
+ "@cosmneo/onion-lasagna": ">=0.2.0"
29
+ },
30
+ "scripts": {
31
+ "build": "tsup",
32
+ "dev": "tsup --watch",
33
+ "test": "vitest",
34
+ "test:run": "vitest run",
35
+ "prepublishOnly": "bun run build"
36
+ },
37
+ "exports": {
38
+ ".": {
39
+ "types": "./dist/index.d.ts",
40
+ "import": "./dist/index.js",
41
+ "require": "./dist/index.cjs",
42
+ "default": "./dist/index.js"
43
+ }
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ }
48
+ }