@gravito/mass 3.0.0 → 3.0.2
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 +84 -76
- package/README.zh-TW.md +120 -11
- package/dist/coercion.d.ts +233 -0
- package/dist/coercion.d.ts.map +1 -0
- package/dist/errors.d.ts +169 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/formats.d.ts +98 -0
- package/dist/formats.d.ts.map +1 -0
- package/dist/index.d.ts +81 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +526 -5
- package/dist/openapi.d.ts +215 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/types.d.ts +53 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils.d.ts +23 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/validator.d.ts +47 -0
- package/dist/validator.d.ts.map +1 -0
- package/package.json +9 -7
- package/dist/mass/src/index.d.ts +0 -30
- package/dist/mass/src/index.d.ts.map +0 -1
- package/dist/mass/src/validator.d.ts +0 -25
- package/dist/mass/src/validator.d.ts.map +0 -1
- package/dist/photon/src/index.d.ts +0 -3
- package/dist/photon/src/index.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,15 +1,536 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// src/index.ts
|
|
3
|
-
import { tbValidator as
|
|
3
|
+
import { tbValidator as tbValidator3 } from "@hono/typebox-validator";
|
|
4
4
|
import * as Schema from "@sinclair/typebox";
|
|
5
5
|
|
|
6
|
-
// src/
|
|
6
|
+
// src/coercion.ts
|
|
7
7
|
import { tbValidator } from "@hono/typebox-validator";
|
|
8
|
+
import { Type } from "@sinclair/typebox";
|
|
9
|
+
function coerceNumber(value) {
|
|
10
|
+
const num = Number(value);
|
|
11
|
+
return Number.isNaN(num) ? NaN : num;
|
|
12
|
+
}
|
|
13
|
+
function coerceInteger(value) {
|
|
14
|
+
const num = Number.parseInt(value, 10);
|
|
15
|
+
return Number.isNaN(num) ? NaN : num;
|
|
16
|
+
}
|
|
17
|
+
function coerceBoolean(value) {
|
|
18
|
+
const lowerValue = value.toLowerCase().trim();
|
|
19
|
+
return ["true", "1", "yes", "on"].includes(lowerValue);
|
|
20
|
+
}
|
|
21
|
+
function coerceDate(value) {
|
|
22
|
+
return new Date(value);
|
|
23
|
+
}
|
|
24
|
+
function CoercibleNumber(options) {
|
|
25
|
+
return Type.Number({
|
|
26
|
+
...options,
|
|
27
|
+
[Symbol.for("TypeBox.Transform")]: coerceNumber
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function CoercibleInteger(options) {
|
|
31
|
+
return Type.Integer({
|
|
32
|
+
...options,
|
|
33
|
+
[Symbol.for("TypeBox.Transform")]: coerceInteger
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function CoercibleBoolean(options) {
|
|
37
|
+
return Type.Boolean({
|
|
38
|
+
...options,
|
|
39
|
+
[Symbol.for("TypeBox.Transform")]: coerceBoolean
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function coerceData(data, schema) {
|
|
43
|
+
if (typeof data !== "object" || data === null) {
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
46
|
+
const dataObj = data;
|
|
47
|
+
const result = {};
|
|
48
|
+
if (schema.type !== "object" || !schema.properties) {
|
|
49
|
+
return data;
|
|
50
|
+
}
|
|
51
|
+
for (const [key, value] of Object.entries(dataObj)) {
|
|
52
|
+
const propertySchema = schema.properties[key];
|
|
53
|
+
if (!propertySchema) {
|
|
54
|
+
result[key] = value;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (typeof value === "string") {
|
|
58
|
+
if (propertySchema.type === "number" || propertySchema.type === "integer") {
|
|
59
|
+
result[key] = propertySchema.type === "integer" ? coerceInteger(value) : coerceNumber(value);
|
|
60
|
+
} else if (propertySchema.type === "boolean") {
|
|
61
|
+
result[key] = coerceBoolean(value);
|
|
62
|
+
} else {
|
|
63
|
+
result[key] = value;
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
result[key] = value;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
function validateWithCoercion(source, schema, hook) {
|
|
72
|
+
return async (c, next) => {
|
|
73
|
+
let data;
|
|
74
|
+
switch (source) {
|
|
75
|
+
case "json":
|
|
76
|
+
data = await c.req.json();
|
|
77
|
+
break;
|
|
78
|
+
case "query":
|
|
79
|
+
data = c.req.query();
|
|
80
|
+
break;
|
|
81
|
+
case "param":
|
|
82
|
+
data = c.req.param();
|
|
83
|
+
break;
|
|
84
|
+
case "form":
|
|
85
|
+
data = await c.req.parseBody();
|
|
86
|
+
break;
|
|
87
|
+
default:
|
|
88
|
+
data = {};
|
|
89
|
+
}
|
|
90
|
+
if (source === "query" || source === "param") {
|
|
91
|
+
data = coerceData(data, schema);
|
|
92
|
+
}
|
|
93
|
+
const validator = tbValidator(source, schema, hook);
|
|
94
|
+
const originalQueryMethod = c.req.query;
|
|
95
|
+
const originalParamMethod = c.req.param;
|
|
96
|
+
if (source === "query") {
|
|
97
|
+
c.req.query = () => data;
|
|
98
|
+
} else if (source === "param") {
|
|
99
|
+
c.req.param = () => data;
|
|
100
|
+
}
|
|
101
|
+
await validator(c, next);
|
|
102
|
+
if (source === "query") {
|
|
103
|
+
c.req.query = originalQueryMethod;
|
|
104
|
+
} else if (source === "param") {
|
|
105
|
+
c.req.param = originalParamMethod;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// src/errors.ts
|
|
110
|
+
var ERROR_MESSAGES_ZH_TW = {
|
|
111
|
+
REQUIRED: "\u6B64\u6B04\u4F4D\u70BA\u5FC5\u586B",
|
|
112
|
+
INVALID_TYPE: "\u8CC7\u6599\u578B\u5225\u4E0D\u6B63\u78BA",
|
|
113
|
+
INVALID_EMAIL: "\u96FB\u5B50\u90F5\u4EF6\u683C\u5F0F\u4E0D\u6B63\u78BA",
|
|
114
|
+
INVALID_URL: "\u7DB2\u5740\u683C\u5F0F\u4E0D\u6B63\u78BA",
|
|
115
|
+
INVALID_UUID: "UUID \u683C\u5F0F\u4E0D\u6B63\u78BA",
|
|
116
|
+
INVALID_DATE: "\u65E5\u671F\u683C\u5F0F\u4E0D\u6B63\u78BA",
|
|
117
|
+
TOO_SHORT: "\u9577\u5EA6\u904E\u77ED",
|
|
118
|
+
TOO_LONG: "\u9577\u5EA6\u904E\u9577",
|
|
119
|
+
TOO_SMALL: "\u6578\u503C\u904E\u5C0F",
|
|
120
|
+
TOO_LARGE: "\u6578\u503C\u904E\u5927",
|
|
121
|
+
INVALID_PATTERN: "\u683C\u5F0F\u4E0D\u7B26\u5408\u898F\u5247",
|
|
122
|
+
INVALID_FORMAT: "\u683C\u5F0F\u4E0D\u6B63\u78BA"
|
|
123
|
+
};
|
|
124
|
+
var ERROR_MESSAGES_EN = {
|
|
125
|
+
REQUIRED: "This field is required",
|
|
126
|
+
INVALID_TYPE: "Invalid data type",
|
|
127
|
+
INVALID_EMAIL: "Invalid email format",
|
|
128
|
+
INVALID_URL: "Invalid URL format",
|
|
129
|
+
INVALID_UUID: "Invalid UUID format",
|
|
130
|
+
INVALID_DATE: "Invalid date format",
|
|
131
|
+
TOO_SHORT: "Too short",
|
|
132
|
+
TOO_LONG: "Too long",
|
|
133
|
+
TOO_SMALL: "Value too small",
|
|
134
|
+
TOO_LARGE: "Value too large",
|
|
135
|
+
INVALID_PATTERN: "Does not match required pattern",
|
|
136
|
+
INVALID_FORMAT: "Invalid format"
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
class MassValidationError extends Error {
|
|
140
|
+
source;
|
|
141
|
+
errors;
|
|
142
|
+
constructor(source, errors) {
|
|
143
|
+
super(`Validation failed for ${source}`);
|
|
144
|
+
this.source = source;
|
|
145
|
+
this.errors = errors;
|
|
146
|
+
this.name = "MassValidationError";
|
|
147
|
+
}
|
|
148
|
+
toJSON() {
|
|
149
|
+
return {
|
|
150
|
+
error: "ValidationError",
|
|
151
|
+
source: this.source,
|
|
152
|
+
details: this.errors
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function enhanceError(error, locale = "zh-TW") {
|
|
157
|
+
const messages = locale === "zh-TW" ? ERROR_MESSAGES_ZH_TW : ERROR_MESSAGES_EN;
|
|
158
|
+
const { path, message, schema, value } = error;
|
|
159
|
+
let enhancedMessage = message;
|
|
160
|
+
if (message.includes("required property")) {
|
|
161
|
+
enhancedMessage = messages.REQUIRED;
|
|
162
|
+
} else if (message.includes("email")) {
|
|
163
|
+
enhancedMessage = messages.INVALID_EMAIL;
|
|
164
|
+
} else if (message.includes("url")) {
|
|
165
|
+
enhancedMessage = messages.INVALID_URL;
|
|
166
|
+
} else if (message.includes("uuid")) {
|
|
167
|
+
enhancedMessage = messages.INVALID_UUID;
|
|
168
|
+
} else if (message.includes("date-time") || message.includes("date")) {
|
|
169
|
+
enhancedMessage = messages.INVALID_DATE;
|
|
170
|
+
} else if (message.includes("length greater or equal")) {
|
|
171
|
+
const minLength = schema.minLength;
|
|
172
|
+
enhancedMessage = locale === "zh-TW" ? `${messages.TOO_SHORT}\uFF08\u6700\u5C11 ${minLength} \u500B\u5B57\u5143\uFF09` : `${messages.TOO_SHORT} (minimum ${minLength} characters)`;
|
|
173
|
+
} else if (message.includes("length less or equal")) {
|
|
174
|
+
const maxLength = schema.maxLength;
|
|
175
|
+
enhancedMessage = locale === "zh-TW" ? `${messages.TOO_LONG}\uFF08\u6700\u591A ${maxLength} \u500B\u5B57\u5143\uFF09` : `${messages.TOO_LONG} (maximum ${maxLength} characters)`;
|
|
176
|
+
} else if (message.includes("greater or equal")) {
|
|
177
|
+
const minimum = schema.minimum;
|
|
178
|
+
enhancedMessage = locale === "zh-TW" ? `${messages.TOO_SMALL}\uFF08\u6700\u5C0F\u503C\uFF1A${minimum}\uFF09` : `${messages.TOO_SMALL} (minimum: ${minimum})`;
|
|
179
|
+
} else if (message.includes("less or equal")) {
|
|
180
|
+
const maximum = schema.maximum;
|
|
181
|
+
enhancedMessage = locale === "zh-TW" ? `${messages.TOO_LARGE}\uFF08\u6700\u5927\u503C\uFF1A${maximum}\uFF09` : `${messages.TOO_LARGE} (maximum: ${maximum})`;
|
|
182
|
+
} else if (message.includes("to match")) {
|
|
183
|
+
enhancedMessage = messages.INVALID_PATTERN;
|
|
184
|
+
} else if (message.includes("format")) {
|
|
185
|
+
enhancedMessage = messages.INVALID_FORMAT;
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
path,
|
|
189
|
+
message: enhancedMessage,
|
|
190
|
+
expected: schema.type ? String(schema.type) : undefined,
|
|
191
|
+
received: typeof value
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function createErrorFormatter(formatter) {
|
|
195
|
+
return (error) => {
|
|
196
|
+
const formatted = formatter(error);
|
|
197
|
+
return {
|
|
198
|
+
path: formatted.path,
|
|
199
|
+
message: formatted.message,
|
|
200
|
+
expected: formatted.expected,
|
|
201
|
+
received: formatted.received
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function formatErrors(errors) {
|
|
206
|
+
const fields = {};
|
|
207
|
+
for (const err of errors) {
|
|
208
|
+
const fieldName = err.path.replace(/^\//, "").replace(/\//g, ".");
|
|
209
|
+
if (!fields[fieldName]) {
|
|
210
|
+
fields[fieldName] = [];
|
|
211
|
+
}
|
|
212
|
+
fields[fieldName].push(err.message);
|
|
213
|
+
}
|
|
214
|
+
return { fields };
|
|
215
|
+
}
|
|
216
|
+
// src/formats.ts
|
|
217
|
+
import { FormatRegistry } from "@sinclair/typebox";
|
|
218
|
+
var REGISTERED_FORMATS = [
|
|
219
|
+
"email",
|
|
220
|
+
"uri",
|
|
221
|
+
"uri-reference",
|
|
222
|
+
"uuid",
|
|
223
|
+
"date-time",
|
|
224
|
+
"date",
|
|
225
|
+
"time",
|
|
226
|
+
"ipv4",
|
|
227
|
+
"ipv6"
|
|
228
|
+
];
|
|
229
|
+
var EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
|
|
230
|
+
var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
231
|
+
var URL_REGEX = /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_+.~#?&/=]*)$/;
|
|
232
|
+
var URI_REFERENCE_REGEX = /^(?:[a-z][a-z0-9+\-.]*:)?(?:\/\/)?[^\s]*$/i;
|
|
233
|
+
var DATE_TIME_REGEX = /^\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}(?:\.\d{3})?(?:Z|[+-]\d{2}:\d{2})?$/;
|
|
234
|
+
var DATE_REGEX = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
|
|
235
|
+
var TIME_REGEX = /^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)(?:\.\d{3})?$/;
|
|
236
|
+
var IPV4_REGEX = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
237
|
+
var IPV6_REGEX = /^(?:(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:))$/;
|
|
238
|
+
var FORMAT_VALIDATORS = {
|
|
239
|
+
email: (value) => EMAIL_REGEX.test(value),
|
|
240
|
+
uri: (value) => URL_REGEX.test(value),
|
|
241
|
+
"uri-reference": (value) => URI_REFERENCE_REGEX.test(value),
|
|
242
|
+
uuid: (value) => UUID_REGEX.test(value),
|
|
243
|
+
"date-time": (value) => DATE_TIME_REGEX.test(value),
|
|
244
|
+
date: (value) => DATE_REGEX.test(value),
|
|
245
|
+
time: (value) => TIME_REGEX.test(value),
|
|
246
|
+
ipv4: (value) => IPV4_REGEX.test(value),
|
|
247
|
+
ipv6: (value) => IPV6_REGEX.test(value)
|
|
248
|
+
};
|
|
249
|
+
function registerFormat(name, validator) {
|
|
250
|
+
FormatRegistry.Set(name, validator);
|
|
251
|
+
}
|
|
252
|
+
function registerAllFormats() {
|
|
253
|
+
for (const [name, validator] of Object.entries(FORMAT_VALIDATORS)) {
|
|
254
|
+
FormatRegistry.Set(name, validator);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function isFormatRegistered(name) {
|
|
258
|
+
return FormatRegistry.Has(name);
|
|
259
|
+
}
|
|
260
|
+
function getFormatValidator(name) {
|
|
261
|
+
return FormatRegistry.Has(name) ? FormatRegistry.Get(name) : undefined;
|
|
262
|
+
}
|
|
263
|
+
function unregisterFormat(name) {
|
|
264
|
+
FormatRegistry.Delete(name);
|
|
265
|
+
}
|
|
266
|
+
// src/openapi.ts
|
|
267
|
+
import { Kind } from "@sinclair/typebox";
|
|
268
|
+
function typeboxToOpenApi(schema) {
|
|
269
|
+
const result = {};
|
|
270
|
+
if (schema.description) {
|
|
271
|
+
result.description = schema.description;
|
|
272
|
+
}
|
|
273
|
+
if (schema.default !== undefined) {
|
|
274
|
+
result.default = schema.default;
|
|
275
|
+
}
|
|
276
|
+
if (schema.examples && Array.isArray(schema.examples) && schema.examples.length > 0) {
|
|
277
|
+
result.example = schema.examples[0];
|
|
278
|
+
}
|
|
279
|
+
switch (schema[Kind]) {
|
|
280
|
+
case "String": {
|
|
281
|
+
const stringSchema = schema;
|
|
282
|
+
result.type = "string";
|
|
283
|
+
if (stringSchema.format) {
|
|
284
|
+
result.format = stringSchema.format;
|
|
285
|
+
}
|
|
286
|
+
if (stringSchema.minLength !== undefined) {
|
|
287
|
+
result.minLength = stringSchema.minLength;
|
|
288
|
+
}
|
|
289
|
+
if (stringSchema.maxLength !== undefined) {
|
|
290
|
+
result.maxLength = stringSchema.maxLength;
|
|
291
|
+
}
|
|
292
|
+
if (stringSchema.pattern) {
|
|
293
|
+
result.pattern = stringSchema.pattern;
|
|
294
|
+
}
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
case "Number":
|
|
298
|
+
case "Integer": {
|
|
299
|
+
const numberSchema = schema;
|
|
300
|
+
result.type = schema[Kind] === "Integer" ? "integer" : "number";
|
|
301
|
+
if (numberSchema.minimum !== undefined) {
|
|
302
|
+
result.minimum = numberSchema.minimum;
|
|
303
|
+
}
|
|
304
|
+
if (numberSchema.maximum !== undefined) {
|
|
305
|
+
result.maximum = numberSchema.maximum;
|
|
306
|
+
}
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
case "Boolean": {
|
|
310
|
+
result.type = "boolean";
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
case "Array": {
|
|
314
|
+
const arraySchema = schema;
|
|
315
|
+
result.type = "array";
|
|
316
|
+
if (arraySchema.items) {
|
|
317
|
+
result.items = typeboxToOpenApi(arraySchema.items);
|
|
318
|
+
}
|
|
319
|
+
if (arraySchema.minItems !== undefined) {
|
|
320
|
+
result.minItems = arraySchema.minItems;
|
|
321
|
+
}
|
|
322
|
+
if (arraySchema.maxItems !== undefined) {
|
|
323
|
+
result.maxItems = arraySchema.maxItems;
|
|
324
|
+
}
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
case "Object": {
|
|
328
|
+
const objectSchema = schema;
|
|
329
|
+
result.type = "object";
|
|
330
|
+
if (objectSchema.properties) {
|
|
331
|
+
result.properties = {};
|
|
332
|
+
const required = [];
|
|
333
|
+
for (const [key, value] of Object.entries(objectSchema.properties)) {
|
|
334
|
+
const optionalSymbol = Symbol.for("TypeBox.Optional");
|
|
335
|
+
const isOptional = value[optionalSymbol] === "Optional";
|
|
336
|
+
if (isOptional) {
|
|
337
|
+
result.properties[key] = typeboxToOpenApi(value);
|
|
338
|
+
} else {
|
|
339
|
+
result.properties[key] = typeboxToOpenApi(value);
|
|
340
|
+
required.push(key);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (required.length > 0) {
|
|
344
|
+
result.required = required;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (objectSchema.additionalProperties !== undefined) {
|
|
348
|
+
if (typeof objectSchema.additionalProperties === "boolean") {
|
|
349
|
+
result.additionalProperties = objectSchema.additionalProperties;
|
|
350
|
+
} else {
|
|
351
|
+
result.additionalProperties = typeboxToOpenApi(objectSchema.additionalProperties);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
case "Union": {
|
|
357
|
+
if (schema.anyOf && Array.isArray(schema.anyOf)) {
|
|
358
|
+
result.anyOf = schema.anyOf.map((s) => typeboxToOpenApi(s));
|
|
359
|
+
}
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
case "Intersect": {
|
|
363
|
+
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
364
|
+
result.allOf = schema.allOf.map((s) => typeboxToOpenApi(s));
|
|
365
|
+
}
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
case "Literal": {
|
|
369
|
+
if (schema.const !== undefined) {
|
|
370
|
+
result.enum = [schema.const];
|
|
371
|
+
result.type = typeof schema.const;
|
|
372
|
+
}
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
case "Enum": {
|
|
376
|
+
if (schema.anyOf && Array.isArray(schema.anyOf)) {
|
|
377
|
+
result.enum = schema.anyOf.map((s) => s.const);
|
|
378
|
+
if (result.enum.length > 0) {
|
|
379
|
+
result.type = typeof result.enum[0];
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
case "Null": {
|
|
385
|
+
result.type = "null";
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
case "Optional": {
|
|
389
|
+
if (schema[Kind] === "Optional" && schema.anyOf && Array.isArray(schema.anyOf)) {
|
|
390
|
+
const innerSchema = schema.anyOf[0];
|
|
391
|
+
return typeboxToOpenApi(innerSchema);
|
|
392
|
+
}
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
default: {
|
|
396
|
+
if (schema.type) {
|
|
397
|
+
result.type = schema.type;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return result;
|
|
402
|
+
}
|
|
403
|
+
function createAstralResource(options) {
|
|
404
|
+
const {
|
|
405
|
+
path,
|
|
406
|
+
method,
|
|
407
|
+
summary,
|
|
408
|
+
description,
|
|
409
|
+
tags,
|
|
410
|
+
requestSchema,
|
|
411
|
+
requestDescription,
|
|
412
|
+
requestRequired = true,
|
|
413
|
+
responseSchema,
|
|
414
|
+
responseDescription = "Successful response",
|
|
415
|
+
responseStatusCode = 200
|
|
416
|
+
} = options;
|
|
417
|
+
const resource = {
|
|
418
|
+
path,
|
|
419
|
+
method: method.toUpperCase(),
|
|
420
|
+
responses: {
|
|
421
|
+
[responseStatusCode]: {
|
|
422
|
+
description: responseDescription,
|
|
423
|
+
content: {
|
|
424
|
+
"application/json": {
|
|
425
|
+
schema: typeboxToOpenApi(responseSchema)
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
if (summary) {
|
|
432
|
+
resource.summary = summary;
|
|
433
|
+
}
|
|
434
|
+
if (description) {
|
|
435
|
+
resource.description = description;
|
|
436
|
+
}
|
|
437
|
+
if (tags && tags.length > 0) {
|
|
438
|
+
resource.tags = tags;
|
|
439
|
+
}
|
|
440
|
+
if (requestSchema && ["POST", "PUT", "PATCH"].includes(method.toUpperCase())) {
|
|
441
|
+
resource.requestBody = {
|
|
442
|
+
description: requestDescription,
|
|
443
|
+
required: requestRequired,
|
|
444
|
+
content: {
|
|
445
|
+
"application/json": {
|
|
446
|
+
schema: typeboxToOpenApi(requestSchema)
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
return resource;
|
|
452
|
+
}
|
|
453
|
+
function createCrudResources(options) {
|
|
454
|
+
const { resourceName, basePath, tags = [resourceName], schemas } = options;
|
|
455
|
+
const capitalizedName = resourceName.charAt(0).toUpperCase() + resourceName.slice(1);
|
|
456
|
+
return {
|
|
457
|
+
list: createAstralResource({
|
|
458
|
+
path: basePath,
|
|
459
|
+
method: "GET",
|
|
460
|
+
summary: `Get ${capitalizedName} List`,
|
|
461
|
+
tags,
|
|
462
|
+
responseSchema: schemas.list || schemas.item
|
|
463
|
+
}),
|
|
464
|
+
get: createAstralResource({
|
|
465
|
+
path: `${basePath}/:id`,
|
|
466
|
+
method: "GET",
|
|
467
|
+
summary: `Get Single ${capitalizedName}`,
|
|
468
|
+
tags,
|
|
469
|
+
responseSchema: schemas.item
|
|
470
|
+
}),
|
|
471
|
+
create: createAstralResource({
|
|
472
|
+
path: basePath,
|
|
473
|
+
method: "POST",
|
|
474
|
+
summary: `Create ${capitalizedName}`,
|
|
475
|
+
tags,
|
|
476
|
+
requestSchema: schemas.create,
|
|
477
|
+
responseSchema: schemas.item,
|
|
478
|
+
responseStatusCode: 201
|
|
479
|
+
}),
|
|
480
|
+
update: createAstralResource({
|
|
481
|
+
path: `${basePath}/:id`,
|
|
482
|
+
method: "PATCH",
|
|
483
|
+
summary: `Update ${capitalizedName}`,
|
|
484
|
+
tags,
|
|
485
|
+
requestSchema: schemas.update,
|
|
486
|
+
responseSchema: schemas.item
|
|
487
|
+
}),
|
|
488
|
+
delete: createAstralResource({
|
|
489
|
+
path: `${basePath}/:id`,
|
|
490
|
+
method: "DELETE",
|
|
491
|
+
summary: `Delete ${capitalizedName}`,
|
|
492
|
+
tags,
|
|
493
|
+
responseSchema: schemas.item
|
|
494
|
+
})
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
// src/utils.ts
|
|
498
|
+
import { Type as Type2 } from "@sinclair/typebox";
|
|
499
|
+
function partial(schema) {
|
|
500
|
+
return Type2.Partial(schema);
|
|
501
|
+
}
|
|
502
|
+
// src/validator.ts
|
|
503
|
+
import { tbValidator as tbValidator2 } from "@hono/typebox-validator";
|
|
8
504
|
function validate(source, schema, hook) {
|
|
9
|
-
return
|
|
505
|
+
return tbValidator2(source, schema, hook);
|
|
10
506
|
}
|
|
11
507
|
export {
|
|
12
|
-
|
|
508
|
+
tbValidator3 as validator,
|
|
509
|
+
validateWithCoercion,
|
|
13
510
|
validate,
|
|
14
|
-
|
|
511
|
+
unregisterFormat,
|
|
512
|
+
typeboxToOpenApi,
|
|
513
|
+
registerFormat,
|
|
514
|
+
registerAllFormats,
|
|
515
|
+
partial,
|
|
516
|
+
isFormatRegistered,
|
|
517
|
+
getFormatValidator,
|
|
518
|
+
formatErrors,
|
|
519
|
+
enhanceError,
|
|
520
|
+
createErrorFormatter,
|
|
521
|
+
createCrudResources,
|
|
522
|
+
createAstralResource,
|
|
523
|
+
coerceNumber,
|
|
524
|
+
coerceInteger,
|
|
525
|
+
coerceDate,
|
|
526
|
+
coerceData,
|
|
527
|
+
coerceBoolean,
|
|
528
|
+
Schema,
|
|
529
|
+
REGISTERED_FORMATS,
|
|
530
|
+
MassValidationError,
|
|
531
|
+
ERROR_MESSAGES_ZH_TW,
|
|
532
|
+
ERROR_MESSAGES_EN,
|
|
533
|
+
CoercibleNumber,
|
|
534
|
+
CoercibleInteger,
|
|
535
|
+
CoercibleBoolean
|
|
15
536
|
};
|