semola 0.6.0 → 0.6.1

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.
Files changed (43) hide show
  1. package/README.md +17 -5
  2. package/dist/lib/api/index.cjs +1 -537
  3. package/dist/lib/api/index.d.cts +1 -271
  4. package/dist/lib/api/index.d.mts +80 -84
  5. package/dist/lib/api/index.mjs +1 -535
  6. package/dist/lib/cache/index.cjs +1 -99
  7. package/dist/lib/cache/index.d.cts +1 -27
  8. package/dist/lib/cache/index.mjs +1 -98
  9. package/dist/lib/cron/index.cjs +1 -735
  10. package/dist/lib/cron/index.d.cts +1 -146
  11. package/dist/lib/cron/index.d.mts +99 -36
  12. package/dist/lib/cron/index.mjs +1 -726
  13. package/dist/lib/errors/index.cjs +1 -30
  14. package/dist/lib/errors/index.d.cts +1 -13
  15. package/dist/lib/errors/index.mjs +1 -26
  16. package/dist/lib/i18n/index.cjs +1 -42
  17. package/dist/lib/i18n/index.d.cts +1 -28
  18. package/dist/lib/i18n/index.mjs +1 -41
  19. package/dist/lib/logging/index.cjs +1 -388
  20. package/dist/lib/logging/index.d.cts +1 -108
  21. package/dist/lib/logging/index.mjs +1 -374
  22. package/dist/lib/orm/index.cjs +1 -1642
  23. package/dist/lib/orm/index.d.cts +1 -402
  24. package/dist/lib/orm/index.d.mts +4 -2
  25. package/dist/lib/orm/index.mjs +1 -1630
  26. package/dist/lib/policy/index.cjs +1 -285
  27. package/dist/lib/policy/index.d.cts +1 -77
  28. package/dist/lib/policy/index.mjs +1 -265
  29. package/dist/lib/prompts/index.cjs +6 -769
  30. package/dist/lib/prompts/index.d.cts +1 -75
  31. package/dist/lib/prompts/index.mjs +6 -762
  32. package/dist/lib/pubsub/index.cjs +1 -141
  33. package/dist/lib/pubsub/index.d.cts +1 -26
  34. package/dist/lib/pubsub/index.mjs +1 -140
  35. package/dist/lib/queue/index.cjs +1 -212
  36. package/dist/lib/queue/index.d.cts +1 -81
  37. package/dist/lib/queue/index.mjs +1 -209
  38. package/dist/lib/workflow/index.cjs +1 -537
  39. package/dist/lib/workflow/index.d.cts +1 -150
  40. package/dist/lib/workflow/index.mjs +1 -527
  41. package/dist/rolldown-runtime-CMqjfN_6.cjs +1 -0
  42. package/package.json +7 -5
  43. package/dist/chunk-CKQMccvm.cjs +0 -28
package/README.md CHANGED
@@ -26,7 +26,7 @@ Type-safe APIs, Redis queues, pub/sub, i18n, caching & auth with tree-shakeable
26
26
  | **🔐 Policy** | Policy-based authorization with type-safe guards | `semola/policy` |
27
27
  | **🌍 i18n** | Compile-time validated internationalization | `semola/i18n` |
28
28
  | **💾 Cache** | Redis cache wrapper with TTL & automatic serialization | `semola/cache` |
29
- | **⏰ Cron** | In-memory cron scheduler for periodic task execution | `semola/cron` |
29
+ | **⏰ Cron** | In-memory and OS cron scheduler for periodic task execution | `semola/cron` |
30
30
  | **🔁 Workflow** | Durable resumable workflows with retries and hooks | `semola/workflow` |
31
31
  | **⚠️ Errors** | Result-based error handling without try/catch | `semola/errors` |
32
32
  | **📃 Logging** | A simple logging utility | `semola/logging` |
@@ -153,18 +153,30 @@ console.log(user);
153
153
  ### Schedule Recurring Tasks
154
154
 
155
155
  ```typescript
156
- import { Cron } from "semola/cron";
156
+ import { Cron, RetryCronJob } from "semola/cron";
157
157
 
158
158
  const cleanup = new Cron({
159
159
  name: "daily-cleanup",
160
- schedule: "0 0 * * *", // Daily at midnight
160
+ schedule: "@daily",
161
161
  handler: async () => {
162
162
  await deleteOldLogs();
163
163
  await archiveInactiveUsers();
164
164
  },
165
+ retry: new RetryCronJob({
166
+ maxAttempts: 2,
167
+ onError: (err) => console.log(`An error: ${err.error.message}`),
168
+ onFailedAttempt: async ({ attemptNumber, delay, error, retriesLeft }) => {
169
+ console.log(
170
+ `Attempt ${attemptNumber} failed. Retrying in ${delay}ms. ${retriesLeft} retries left.`,
171
+ );
172
+
173
+ await recover();
174
+ },
175
+ }),
165
176
  });
166
177
 
167
- cleanup.start();
178
+ cleanup.run();
179
+
168
180
  ```
169
181
 
170
182
  ### Query a Database
@@ -317,7 +329,7 @@ _Higher is better for req/sec, lower is better for latency._
317
329
  - [API Framework](./docs/api.md) - Type-safe REST API framework with OpenAPI
318
330
  - [Queue](./docs/queue.md) - Redis-backed job queue with timeouts & concurrency
319
331
  - [PubSub](./docs/pubsub.md) - Type-safe Redis pub/sub
320
- - [Cron](./docs/cron.md) - In-memory cron scheduler for periodic task execution
332
+ - [Cron](./docs/cron.md) - In-memory and OS cron scheduler for periodic task execution
321
333
  - [Workflow](./docs/workflow.md) - Durable and resumable workflows with retries and hooks
322
334
  - [Policy](./docs/policy.md) - Policy-based authorization
323
335
  - [i18n](./docs/i18n.md) - Type-safe internationalization
@@ -1,537 +1 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_lib_errors_index = require("../errors/index.cjs");
3
- //#region src/lib/api/openapi/index.ts
4
- const toOpenAPISchema = (schema, io = "input") => ({ schema: schema["~standard"].jsonSchema[io]({ target: "draft-2020-12" }) });
5
- const getSchemaDescription = (schema) => {
6
- const metadata = schema["~standard"];
7
- if (!metadata) return "";
8
- if ("description" in metadata && typeof metadata.description === "string") return metadata.description;
9
- return "";
10
- };
11
- const requestFields = [
12
- "body",
13
- "query",
14
- "headers",
15
- "cookies",
16
- "params"
17
- ];
18
- const mergeRequestSchemas = (schemas) => {
19
- const merged = {};
20
- for (const schema of schemas) {
21
- if (!schema) continue;
22
- for (const field of requestFields) if (schema[field]) merged[field] = schema[field];
23
- }
24
- return merged;
25
- };
26
- const mergeResponseSchemas = (schemas) => {
27
- const merged = {};
28
- for (const schema of schemas) {
29
- if (!schema) continue;
30
- for (const status in schema) {
31
- const statusCode = Number(status);
32
- const responseSchema = schema[statusCode];
33
- if (responseSchema) merged[statusCode] = responseSchema;
34
- }
35
- }
36
- return Object.keys(merged).length > 0 ? merged : void 0;
37
- };
38
- const convertSchemaToOpenApi = async (schema, io = "input") => {
39
- const result = toOpenAPISchema(schema, io);
40
- const { schema: jsonSchema } = result;
41
- const schemaId = jsonSchema.id;
42
- if (schemaId && typeof schemaId === "string") {
43
- const schemaWithoutId = { ...jsonSchema };
44
- delete schemaWithoutId.id;
45
- delete schemaWithoutId.$schema;
46
- return {
47
- schema: { $ref: `#/components/schemas/${schemaId}` },
48
- components: { schemas: { [schemaId]: schemaWithoutId } }
49
- };
50
- }
51
- if (jsonSchema.$schema) {
52
- const schemaWithoutMeta = { ...jsonSchema };
53
- delete schemaWithoutMeta.$schema;
54
- return {
55
- schema: schemaWithoutMeta,
56
- components: void 0
57
- };
58
- }
59
- return result;
60
- };
61
- const convertSchemaToInlineOpenApi = async (schema, io = "input") => {
62
- const { schema: jsonSchema } = toOpenAPISchema(schema, io);
63
- const cleanSchema = { ...jsonSchema };
64
- delete cleanSchema.$schema;
65
- delete cleanSchema.id;
66
- return {
67
- schema: cleanSchema,
68
- components: void 0
69
- };
70
- };
71
- const extractParametersFromSchema = async (schema, location) => {
72
- const { schema: jsonSchema, components } = await convertSchemaToInlineOpenApi(schema);
73
- if (jsonSchema.type !== "object") return {
74
- parameters: [],
75
- components
76
- };
77
- if (!jsonSchema.properties) return {
78
- parameters: [],
79
- components
80
- };
81
- const parameters = [];
82
- const requiredFields = jsonSchema.required ?? [];
83
- for (const name in jsonSchema.properties) {
84
- const propertySchema = jsonSchema.properties[name];
85
- const isRequired = requiredFields.includes(name);
86
- parameters.push({
87
- name,
88
- in: location,
89
- required: isRequired,
90
- schema: propertySchema
91
- });
92
- }
93
- return {
94
- parameters,
95
- components
96
- };
97
- };
98
- const normalizePathForOpenAPI = (path) => {
99
- return path.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, "{$1}");
100
- };
101
- const extractPathParameters = (path) => {
102
- const matches = path.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g);
103
- if (!matches) return [];
104
- return matches.map((match) => match.slice(1));
105
- };
106
- const paramSources = [
107
- ["query", "query"],
108
- ["headers", "header"],
109
- ["cookies", "cookie"]
110
- ];
111
- const createParameters = async (request, path) => {
112
- const parameters = [];
113
- const allComponents = [];
114
- for (const [field, location] of paramSources) if (request[field]) {
115
- const { parameters: params, components } = await extractParametersFromSchema(request[field], location);
116
- parameters.push(...params);
117
- if (components) allComponents.push(components);
118
- }
119
- const pathParamNames = extractPathParameters(path);
120
- if (pathParamNames.length > 0 && request.params) {
121
- const { schema: jsonSchema, components } = await convertSchemaToInlineOpenApi(request.params);
122
- if (components) allComponents.push(components);
123
- if (jsonSchema.type === "object" && jsonSchema.properties) for (const name of pathParamNames) {
124
- const propertySchema = jsonSchema.properties[name];
125
- if (propertySchema) parameters.push({
126
- name,
127
- in: "path",
128
- required: true,
129
- schema: propertySchema
130
- });
131
- }
132
- }
133
- return {
134
- parameters,
135
- components: allComponents
136
- };
137
- };
138
- const createRequestBody = async (bodySchema) => {
139
- const { schema, components } = await convertSchemaToOpenApi(bodySchema);
140
- return {
141
- components,
142
- requestBody: {
143
- required: true,
144
- content: { "application/json": { schema } }
145
- }
146
- };
147
- };
148
- const createResponses = async (response) => {
149
- const responses = {};
150
- const allComponents = [];
151
- if (!response) return {
152
- responses,
153
- components: allComponents
154
- };
155
- for (const status in response) {
156
- const statusCode = String(status);
157
- const schema = response[Number(status)];
158
- if (!schema) continue;
159
- const description = getSchemaDescription(schema);
160
- const { schema: jsonSchema, components } = await convertSchemaToOpenApi(schema, "output");
161
- if (components) allComponents.push(components);
162
- responses[statusCode] = {
163
- description: description || `Response with status ${statusCode}`,
164
- content: { "application/json": { schema: jsonSchema } }
165
- };
166
- }
167
- return {
168
- responses,
169
- components: allComponents
170
- };
171
- };
172
- const createOperation = async (route, globalMiddlewares, prefix) => {
173
- const allMiddlewares = [...globalMiddlewares ?? [], ...route.middlewares ?? []];
174
- const requestSchemas = [];
175
- const responseSchemas = [];
176
- for (const middleware of allMiddlewares) {
177
- requestSchemas.push(middleware.options.request);
178
- responseSchemas.push(middleware.options.response);
179
- }
180
- requestSchemas.push(route.request);
181
- responseSchemas.push(route.response);
182
- const mergedRequest = mergeRequestSchemas(requestSchemas);
183
- const mergedResponse = mergeResponseSchemas(responseSchemas);
184
- const { parameters, components: parameterComponents } = await createParameters(mergedRequest, prefix ? prefix + route.path : route.path);
185
- const { responses, components: responseComponents } = await createResponses(mergedResponse);
186
- const operation = { responses };
187
- const allComponents = [];
188
- allComponents.push(...responseComponents);
189
- allComponents.push(...parameterComponents);
190
- for (const field of [
191
- "summary",
192
- "description",
193
- "operationId"
194
- ]) if (route[field]) operation[field] = route[field];
195
- if (route.tags && route.tags.length > 0) operation.tags = route.tags;
196
- if (parameters.length > 0) operation.parameters = parameters;
197
- const bodySchema = mergedRequest.body;
198
- if (bodySchema) {
199
- const { requestBody, components: bodyComponents } = await createRequestBody(bodySchema);
200
- operation.requestBody = requestBody;
201
- if (bodyComponents) allComponents.push(bodyComponents);
202
- }
203
- return {
204
- operation,
205
- components: allComponents
206
- };
207
- };
208
- const componentKeys = [
209
- "schemas",
210
- "responses",
211
- "parameters",
212
- "requestBodies"
213
- ];
214
- const mergeComponents = (componentsArray) => {
215
- const merged = {};
216
- for (const components of componentsArray) {
217
- if (!components) continue;
218
- for (const key of componentKeys) if (components[key]) merged[key] = {
219
- ...merged[key],
220
- ...components[key]
221
- };
222
- }
223
- return merged;
224
- };
225
- const generateOpenApiSpec = async (options) => {
226
- const spec = {
227
- openapi: "3.1.0",
228
- info: {
229
- title: options.title,
230
- description: options.description,
231
- version: options.version
232
- },
233
- paths: {}
234
- };
235
- if (options.servers && options.servers.length > 0) spec.servers = options.servers;
236
- if (options.securitySchemes) spec.components = { securitySchemes: options.securitySchemes };
237
- const allRouteComponents = [];
238
- for (const route of options.routes) {
239
- const openApiPath = normalizePathForOpenAPI(options.prefix ? options.prefix + route.path : route.path);
240
- const method = route.method.toLowerCase();
241
- if (!spec.paths[openApiPath]) spec.paths[openApiPath] = {};
242
- const { operation, components } = await createOperation(route, options.globalMiddlewares ?? [], options.prefix);
243
- spec.paths[openApiPath][method] = operation;
244
- allRouteComponents.push(...components);
245
- }
246
- const mergedComponents = mergeComponents(allRouteComponents);
247
- if (!spec.components) spec.components = {};
248
- if (options.securitySchemes) spec.components.securitySchemes = options.securitySchemes;
249
- for (const key of componentKeys) {
250
- const value = mergedComponents[key];
251
- if (value && Object.keys(value).length > 0) spec.components[key] = value;
252
- }
253
- return spec;
254
- };
255
- //#endregion
256
- //#region src/lib/api/errors.ts
257
- var ParseError = class extends Error {
258
- constructor(message) {
259
- super(message);
260
- this.name = "ParseError";
261
- }
262
- };
263
- var ValidationError = class extends Error {
264
- constructor(message) {
265
- super(message);
266
- this.name = "ValidationError";
267
- }
268
- };
269
- //#endregion
270
- //#region src/lib/api/validation/index.ts
271
- const validateSchema = async (schema, data) => {
272
- const result = await schema["~standard"].validate(data);
273
- if (!result.issues) return result.value;
274
- throw new ValidationError(result.issues.map((issue) => {
275
- let path = "unknown";
276
- if (Array.isArray(issue.path)) path = issue.path.map(String).join(".");
277
- return `${path}: ${issue.message ?? "validation failed"}`;
278
- }).join(", "));
279
- };
280
- const validateBody = async (req, bodySchema, bodyCache) => {
281
- if (!bodySchema) return true;
282
- if (!(req.headers.get("content-type") ?? "").includes("application/json")) return;
283
- if (bodyCache?.parsed) return validateSchema(bodySchema, bodyCache.value);
284
- const [parseError, parsedBody] = await require_lib_errors_index.mightThrow(req.json());
285
- if (parseError) throw new ParseError("Invalid JSON body");
286
- if (bodyCache) {
287
- bodyCache.parsed = true;
288
- bodyCache.value = parsedBody;
289
- }
290
- return validateSchema(bodySchema, parsedBody);
291
- };
292
- const validateQuery = async (req, querySchema) => {
293
- if (!querySchema) return true;
294
- const qIndex = req.url.indexOf("?");
295
- if (qIndex === -1) return validateSchema(querySchema, {});
296
- const hashIndex = req.url.indexOf("#", qIndex + 1);
297
- const queryString = hashIndex === -1 ? req.url.slice(qIndex + 1) : req.url.slice(qIndex + 1, hashIndex);
298
- const searchParams = new URLSearchParams(queryString);
299
- const queryParams = {};
300
- for (const key of searchParams.keys()) {
301
- const values = searchParams.getAll(key);
302
- const [firstValue] = values;
303
- if (values.length === 1) queryParams[key] = firstValue;
304
- else queryParams[key] = values;
305
- }
306
- return validateSchema(querySchema, queryParams);
307
- };
308
- const validateHeaders = async (req, headersSchema) => {
309
- if (!headersSchema) return true;
310
- const headers = {};
311
- req.headers.forEach((value, key) => {
312
- headers[key] = value;
313
- });
314
- return validateSchema(headersSchema, headers);
315
- };
316
- const validateCookies = async (req, cookiesSchema) => {
317
- if (!cookiesSchema) return true;
318
- const cookieHeader = req.headers.get("cookie") ?? "";
319
- const cookieMap = new Bun.CookieMap(cookieHeader);
320
- return validateSchema(cookiesSchema, Object.fromEntries(cookieMap));
321
- };
322
- const validateParams = async (req, paramsSchema) => {
323
- if (!paramsSchema) return true;
324
- return validateSchema(paramsSchema, req.params);
325
- };
326
- //#endregion
327
- //#region src/lib/api/core/index.ts
328
- const defaultValidated = Object.freeze({
329
- body: void 0,
330
- query: void 0,
331
- headers: void 0,
332
- cookies: void 0,
333
- params: void 0
334
- });
335
- const responseHelpers = {
336
- json: (status, data) => Response.json(data, { status }),
337
- text: (status, text) => new Response(text, { status }),
338
- html: (status, html) => new Response(html, {
339
- status,
340
- headers: { "Content-Type": "text/html" }
341
- }),
342
- redirect: (status, url) => Response.redirect(url, status)
343
- };
344
- const noopGet = () => void 0;
345
- const stripTrailingSlash = (path) => {
346
- if (path !== "/" && path.endsWith("/")) return path.slice(0, -1);
347
- return path;
348
- };
349
- const hasSchemas = (schema) => schema && (schema.body || schema.query || schema.headers || schema.cookies || schema.params);
350
- const needsBodyCache = (schema) => schema?.body !== void 0;
351
- const shouldCreateBodyCache = (hasMiddlewares, allMiddlewares, request) => {
352
- if (needsBodyCache(request)) return true;
353
- if (!hasMiddlewares) return false;
354
- return allMiddlewares.some((mw) => needsBodyCache(mw.options.request));
355
- };
356
- const resolveValidation = (v) => {
357
- if (v === void 0 || v === true) return {
358
- input: true,
359
- output: true
360
- };
361
- if (v === false) return {
362
- input: false,
363
- output: false
364
- };
365
- return {
366
- input: v.input !== false,
367
- output: v.output !== false
368
- };
369
- };
370
- var Api = class {
371
- options;
372
- routes = [];
373
- constructor(options = {}) {
374
- this.options = options;
375
- }
376
- getFullPath(path) {
377
- const normalizedPath = stripTrailingSlash(path) || "/";
378
- if (!this.options.prefix) return normalizedPath;
379
- const normalizedPrefix = stripTrailingSlash(this.options.prefix);
380
- if (normalizedPrefix === "/") return normalizedPath;
381
- if (normalizedPath === "/") return normalizedPrefix;
382
- return normalizedPrefix + normalizedPath;
383
- }
384
- async validateRequestSchema(req, schema, bodyCache) {
385
- if (!schema) return {
386
- success: true,
387
- data: {}
388
- };
389
- const v = {};
390
- if (schema.body) {
391
- const [err, val] = await require_lib_errors_index.mightThrow(validateBody(req, schema.body, bodyCache));
392
- if (err) return {
393
- success: false,
394
- error: err
395
- };
396
- v.body = val;
397
- }
398
- if (schema.query) {
399
- const [err, val] = await require_lib_errors_index.mightThrow(validateQuery(req, schema.query));
400
- if (err) return {
401
- success: false,
402
- error: err
403
- };
404
- v.query = val;
405
- }
406
- if (schema.headers) {
407
- const [err, val] = await require_lib_errors_index.mightThrow(validateHeaders(req, schema.headers));
408
- if (err) return {
409
- success: false,
410
- error: err
411
- };
412
- v.headers = val;
413
- }
414
- if (schema.cookies) {
415
- const [err, val] = await require_lib_errors_index.mightThrow(validateCookies(req, schema.cookies));
416
- if (err) return {
417
- success: false,
418
- error: err
419
- };
420
- v.cookies = val;
421
- }
422
- if (schema.params) {
423
- const [err, val] = await require_lib_errors_index.mightThrow(validateParams(req, schema.params));
424
- if (err) return {
425
- success: false,
426
- error: err
427
- };
428
- v.params = val;
429
- }
430
- return {
431
- success: true,
432
- data: v
433
- };
434
- }
435
- createContext(req, validated, extensions) {
436
- return {
437
- raw: req,
438
- req: validated,
439
- ...responseHelpers,
440
- get: (key) => extensions[key]
441
- };
442
- }
443
- async validateResponseBody(response, schema) {
444
- if (!schema) return response;
445
- const statusSchema = schema[response.status];
446
- if (!statusSchema) return response;
447
- if (!(response.headers.get("content-type") ?? "").includes("application/json")) return response;
448
- const [parseError, body] = await require_lib_errors_index.mightThrow(response.clone().json());
449
- if (parseError) return responseHelpers.json(400, { message: "Invalid response body" });
450
- const [validationError] = await require_lib_errors_index.mightThrow(validateSchema(statusSchema, body));
451
- if (validationError) return responseHelpers.json(400, { message: validationError.message });
452
- return response;
453
- }
454
- buildBunRoutes() {
455
- const bunRoutes = {};
456
- const validationConfig = resolveValidation(this.options.validation);
457
- for (const route of this.routes) {
458
- const { path, method, handler, request, response, middlewares } = route;
459
- const fullPath = this.getFullPath(path);
460
- if (!bunRoutes[fullPath]) bunRoutes[fullPath] = {};
461
- const allMiddlewares = [...this.options.middlewares ?? [], ...middlewares ?? []];
462
- const hasMiddlewares = allMiddlewares.length > 0;
463
- const hasRouteSchemas = hasSchemas(request);
464
- const effectiveOutputValidation = validationConfig.output && !!response;
465
- if (!hasMiddlewares && !(validationConfig.input && hasRouteSchemas) && !effectiveOutputValidation) bunRoutes[fullPath][method] = (req) => {
466
- const context = Object.create(responseHelpers);
467
- context.raw = req;
468
- context.req = defaultValidated;
469
- context.get = noopGet;
470
- return handler(context);
471
- };
472
- else bunRoutes[fullPath][method] = async (req) => {
473
- const extensions = {};
474
- const bodyCache = validationConfig.input && shouldCreateBodyCache(hasMiddlewares, allMiddlewares, request) ? {
475
- parsed: false,
476
- value: void 0
477
- } : void 0;
478
- for (const mw of allMiddlewares) {
479
- const { request: reqSchema, handler: mwHandler } = mw.options;
480
- let validated = defaultValidated;
481
- if (validationConfig.input && hasSchemas(reqSchema)) {
482
- const result = await this.validateRequestSchema(req, reqSchema, bodyCache);
483
- if (!result.success) return responseHelpers.json(400, { message: result.error?.message });
484
- validated = result.data;
485
- }
486
- const mwResult = await mwHandler(this.createContext(req, validated, extensions));
487
- if (mwResult instanceof Response) return mwResult;
488
- if (mwResult) Object.assign(extensions, mwResult);
489
- }
490
- let routeValidated = defaultValidated;
491
- if (validationConfig.input && hasRouteSchemas) {
492
- const result = await this.validateRequestSchema(req, request, bodyCache);
493
- if (!result.success) return responseHelpers.json(400, { message: result.error?.message });
494
- routeValidated = result.data;
495
- }
496
- const handlerResponse = await handler(this.createContext(req, routeValidated, extensions));
497
- if (effectiveOutputValidation) return this.validateResponseBody(handlerResponse, response);
498
- return handlerResponse;
499
- };
500
- }
501
- return bunRoutes;
502
- }
503
- defineRoute(config) {
504
- this.routes.push(config);
505
- }
506
- getOpenApiSpec() {
507
- return generateOpenApiSpec({
508
- title: this.options.openapi?.title ?? "API",
509
- description: this.options.openapi?.description,
510
- version: this.options.openapi?.version ?? "1.0.0",
511
- prefix: this.options.prefix,
512
- servers: this.options.openapi?.servers,
513
- securitySchemes: this.options.openapi?.securitySchemes,
514
- routes: this.routes,
515
- globalMiddlewares: this.options.middlewares
516
- });
517
- }
518
- serve(port, callback) {
519
- const bunRoutes = this.buildBunRoutes();
520
- const server = Bun.serve({
521
- port,
522
- routes: bunRoutes,
523
- fetch: () => new Response("Not found", { status: 404 })
524
- });
525
- if (callback) callback(server);
526
- }
527
- };
528
- //#endregion
529
- //#region src/lib/api/middleware/index.ts
530
- var Middleware = class {
531
- constructor(options) {
532
- this.options = options;
533
- }
534
- };
535
- //#endregion
536
- exports.Api = Api;
537
- exports.Middleware = Middleware;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e={status:404},t=(e,t)=>{let n=e.split(`/`).slice(1),r=[],i=0;for(let e of n){if(e.startsWith(`:`)){r.push({value:``,paramName:e.slice(1)}),i++;continue}r.push({value:e})}return{segments:r,methods:t,paramStarts:Array(i),paramEnds:Array(i)}},n=(e,t)=>{let n=1,r=0;if(t===`/`)return e.segments.length===0;for(let i of e.segments){let a=t.indexOf(`/`,n),o=t.length;if(a!==-1&&(o=a),i.paramName){if(o===n)return!1;e.paramStarts[r]=n,e.paramEnds[r]=o,r++}else{let e=o-n;if(i.value.length!==e||!t.startsWith(i.value,n))return!1}n=a===-1?t.length:a+1}return n===t.length},r=(e,t)=>{let n={},r=0;for(let i of e.segments)i.paramName&&(n[i.paramName]=t.slice(e.paramStarts[r],e.paramEnds[r]),r++);return n},i=e=>{let t=e.indexOf(`://`),n=e.indexOf(`/`,t+3);if(n===-1)return`/`;let r=e.indexOf(`?`,n),i;return i=r===-1?e.slice(n):e.slice(n,r),i.length>1&&i.endsWith(`/`)?i.slice(0,-1):i},a=a=>{let o=Object.create(null),s=[],c=[];for(let[e,n]of Object.entries(a)){let r=n;if(e.includes(`*`)){c.push({pattern:new URLPattern({pathname:e}),methods:r});continue}if(e.includes(`:`)){s.push(t(e,r));continue}o[e]=r}return t=>{let a=i(t.url),l=t.method,u=o[a]?.[l];if(u)return u(t);for(let e of s){if(!n(e,a))continue;let i=e.methods[l];if(i)return t.params=r(e,a),i(t)}for(let e of c){let n=e.pattern.exec({pathname:a});if(!n)continue;let r=e.methods[l];if(r)return t.params=n.pathname.groups,r(t)}return new Response(`Not found`,e)}},o=e=>{if(Array.isArray(e))return e.map(o);if(typeof e!=`object`||!e)return e;let t=e;if(typeof t.$ref==`string`&&t.$ref.startsWith(`#/$defs/`))return{$ref:`#/components/schemas/${t.$ref.slice(8)}`};let n={};for(let e in t)n[e]=o(t[e]);return n},s=e=>{let t=e.$defs;if(!t||typeof t!=`object`)return{schema:e,components:void 0};let n=o({...e});delete n.$defs;let r=t,i={};for(let e in r)i[e]=o(r[e]);return{schema:n,components:{schemas:i}}},c=(e,t=`input`)=>{let n=e[`~standard`].jsonSchema[t]({target:`draft-2020-12`});return s(n)},l=e=>{let t=e[`~standard`];return t&&`description`in t&&typeof t.description==`string`?t.description:``},u=e=>{let t=e.meta;if(typeof t!=`function`)return;let n=t();if(typeof n==`object`&&n&&typeof n.id==`string`)return n.id},d=(e,t)=>typeof t.id==`string`?t.id:u(e),f=[`body`,`query`,`headers`,`cookies`,`params`],p=(e,t=`input`)=>{let n=c(e,t),{schema:r,components:i}=n,a=d(e,r);if(a){let e={...r};return delete e.id,delete e.$schema,{schema:{$ref:`#/components/schemas/${a}`},components:{...i,schemas:{...i?.schemas,[a]:e}}}}if(r.$schema){let e={...r};return delete e.$schema,{schema:e,components:i}}return n},m=(e,t=`input`)=>{let{schema:n,components:r}=c(e,t),i={...n};return delete i.$schema,delete i.id,{schema:i,components:r}},h=(e,t)=>{let{schema:n,components:r}=m(e);if(n.type!==`object`||!n.properties)return{parameters:[],components:r};let i=[],a=n.required??[];for(let e in n.properties){let r=n.properties[e],o=a.includes(e);i.push({name:e,in:t,required:o,schema:r})}return{parameters:i,components:r}},g=e=>e.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g,`{$1}`),_=e=>{let t=e.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g);return t?t.map(e=>e.slice(1)):[]},ee=[[`query`,`query`],[`headers`,`header`],[`cookies`,`cookie`]],te=(e,t)=>{let n=[],r=[];for(let[t,i]of ee)if(e[t]){let{parameters:a,components:o}=h(e[t],i);n.push(...a),o&&r.push(o)}let i=_(t);if(i.length>0&&e.params){let{schema:t,components:a}=m(e.params);if(a&&r.push(a),t.type===`object`&&t.properties)for(let e of i){let r=t.properties[e];r&&n.push({name:e,in:`path`,required:!0,schema:r})}}return{parameters:n,components:r}},ne=e=>{let{schema:t,components:n}=p(e);return{components:n,requestBody:{required:!0,content:{"application/json":{schema:t}}}}},re=e=>{let t={},n=[];if(!e)return{responses:t,components:n};for(let r in e){let i=String(r),a=e[Number(r)];if(!a)continue;let o=l(a),{schema:s,components:c}=p(a,`output`);c&&n.push(c),t[i]={description:o||`Response with status ${i}`,content:{"application/json":{schema:s}}}}return{responses:t,components:n}},v=(e,t,n)=>{let{request:r,response:i}=y(e,t),a=e.path;n&&(a=n+e.path);let{parameters:o,components:s}=te(r,a),{responses:c,components:l}=re(i),u={responses:c},d=[];d.push(...l),d.push(...s);for(let t of[`summary`,`description`,`operationId`])e[t]&&(u[t]=e[t]);e.tags&&e.tags.length>0&&(u.tags=e.tags),o.length>0&&(u.parameters=o);let f=r.body;if(f){let{requestBody:e,components:t}=ne(f);u.requestBody=e,t&&d.push(t)}return{operation:u,components:d}},y=(e,t)=>{let n={},r={};for(let i of[...t,...e.middlewares??[]])b(n,i.options.request),x(r,i.options.response);b(n,e.request),x(r,e.response);let i;return Object.keys(r).length>0&&(i=r),{request:n,response:i}},b=(e,t)=>{if(t)for(let n of f)t[n]&&(e[n]=t[n])},x=(e,t)=>{if(t)for(let n in t){let r=Number(n),i=t[r];i&&(e[r]=i)}},S=e=>{let t={openapi:`3.1.0`,info:{title:e.title,description:e.description,version:e.version},paths:{}};e.servers&&e.servers.length>0&&(t.servers=e.servers);let n={};for(let r of e.routes){let i=r.path;e.prefix&&(i=e.prefix+r.path);let a=g(i),o=r.method.toLowerCase();t.paths[a]??={};let{operation:s,components:c}=v(r,e.globalMiddlewares??[],e.prefix);t.paths[a][o]=s,c.forEach(e=>{Object.assign(n,e.schemas??{})})}let r=Object.keys(n).length>0;return(e.securitySchemes||r)&&(t.components={},e.securitySchemes&&(t.components.securitySchemes=e.securitySchemes),r&&(t.components.schemas=n)),t};var C=class extends Error{constructor(e){super(e),this.name=`ParseError`}},w=class extends Error{constructor(e){super(e),this.name=`ValidationError`}},T=class extends Error{constructor(e){super(e),this.name=`SchemaConfigError`}};function ie(e){if(e.path?.length){let t=``;for(let n of e.path){let e=typeof n==`object`?n.key:n;if(typeof e==`string`||typeof e==`number`)t?t+=`.${e}`:t+=e;else return null}return t}return null}const ae=e=>ie(e)??`unknown`,E=e=>e.map(e=>`${ae(e)}: ${e.message??`validation failed`}`).join(`, `),D=e=>!(!e.body||e.query||e.headers||e.cookies||e.params),O=e=>{let t=e[`~standard`].validate;return async e=>{let n=e.headers.get(`content-type`)??``,r;if(n.startsWith(`application/json`))try{r=await e.json()}catch{throw new C(`Invalid JSON body`)}else r=await e.text();let i=t(r);if(i instanceof Promise)throw new T(`Async schema validation is not supported`);if(i.issues)throw new w(E(i.issues))}},k=e=>{if(!e.issues)return e.value;throw new w(E(e.issues))},A=(e,t=!1)=>{let n=e;t&&e.includes(`+`)&&(n=e.replaceAll(`+`,` `));try{return decodeURIComponent(n)}catch{return n}},j=(e,t,n)=>{let r=e[t];if(r===void 0){e[t]=n;return}if(Array.isArray(r)){r.push(n);return}e[t]=[r,n]},M=(e,t)=>{let n=e[`~standard`].validate(t);if(n instanceof Promise)throw new T(`Async schema validation is not supported`);return k(n)},N=async(e,t,n)=>{if(!t)return!0;if(n?.parsed)return M(t,n.value);let r=e.headers.get(`content-type`)??``,i;if(r.startsWith(`application/json`))try{i=await e.json()}catch{throw new C(`Invalid JSON body`)}else i=await e.text();return n&&(n.parsed=!0,n.value=i),M(t,i)},P=(e,t)=>{if(!t)return!0;let n=e.url.indexOf(`?`);if(n===-1)return M(t,{});let r=e.url.indexOf(`#`,n+1),i=e.url.length;r!==-1&&(i=r);let a={},o=n+1;for(;o<=i;){let t=e.url.indexOf(`&`,o),n=i;if(t!==-1&&t<i&&(n=t),n>o){let t=e.url.indexOf(`=`,o),r=t!==-1&&t<n,i=e.url.slice(o,n),s=``;r&&(i=e.url.slice(o,t),s=e.url.slice(t+1,n)),j(a,A(i,!0),A(s,!0))}if(t===-1||t>=i)break;o=t+1}return M(t,a)},F=(e,t)=>{if(!t)return!0;let n={};for(let[t,r]of e.headers)n[t]=r;return M(t,n)},I=(e,t)=>{if(!t)return!0;let n=e.headers.get(`cookie`)??``,r={},i=0;for(;i<n.length;){let e=n.indexOf(`;`,i),t=n.length;e!==-1&&(t=e);let a=n.indexOf(`=`,i);if(a!==-1&&a<t){let e=n.slice(i,a).trim();if(e){let i=n.slice(a+1,t).trim();r[e]=A(i)}}if(e===-1)break;i=e+1}return M(t,r)},L=(e,t)=>t?M(t,e.params??{}):!0,R=async(e,t)=>{let n=e.schema;if(n)try{if(n.body){let r=await N(e.req,n.body,e.bodyCache);t&&(t.body=r)}if(n.query){let r=P(e.req,n.query);t&&(t.query=r)}if(n.headers){let r=F(e.req,n.headers);t&&(t.headers=r)}if(n.cookies){let r=I(e.req,n.cookies);t&&(t.cookies=r)}if(n.params){let r=L(e.req,n.params);t&&(t.params=r)}}catch(e){return e}},z=e=>{if(e){if(D(e)){let t=e.body;if(!t)return;let n=O(t);return async e=>{try{await n(e)}catch(e){return e}}}return(t,n)=>R({req:t,schema:e,bodyCache:n})}},B={"Content-Type":`text/html`},V={status:400},H=(e,t)=>e===200?Response.json(t):Response.json(t,{status:e}),U=(e,t)=>e===200?new Response(t):new Response(t,{status:e}),W=(e,t)=>new Response(t,{status:e,headers:B}),G=(e,t)=>Response.redirect(t,e),K=e=>Response.json({message:e},V),q=e=>{if(e instanceof w||e instanceof C)return K(e.message);throw e},oe=e=>(t,n)=>{let r=e[t];if(!r)return H(t,n);try{M(r,n)}catch(e){return q(e)}return H(t,n)},J=Object.freeze({body:void 0,query:void 0,headers:void 0,cookies:void 0,params:void 0}),se={req:J,get:()=>{},json:H,text:U,html:W,redirect:G},Y=(e,t,n,r)=>{let i=Object.create(se);return i.raw=e,t&&(i.req=t),n&&(i.get=n),r&&(i.json=r),i},X=e=>e!==`/`&&e.endsWith(`/`)?e.slice(0,-1):e,ce=e=>{let t=X(e.path)||`/`;if(!e.prefix)return t;let n=X(e.prefix);return n===`/`?t:t===`/`?n:n+t},le=e=>{let t=0;e.request?.body!==void 0&&t++;for(let n of e.middlewares)if(n.options.request?.body!==void 0&&(t++,t>1))return!0;return!1},ue=e=>e===void 0||e===!0?{input:!0,output:!0}:e===!1?{input:!1,output:!1}:{input:e.input!==!1,output:e.output!==!1},de=[],Z=e=>e instanceof Response?e:typeof e==`string`?new Response(e):Response.json(e),Q=async(e,t)=>{let n=Z(e),r=t?.[n.status];if(!r)return n;let i=e;e instanceof Response&&(i=await n.clone().json());try{M(r,i)}catch(e){return q(e)}return n},fe=(e,t)=>{if(e instanceof Response)return Q(e,t);let n=Z(e),r=t?.[n.status];if(!r)return n;try{M(r,e)}catch(e){return q(e)}return n},pe=(e,t,n,r=!1,i=!1)=>{let a=i?n:void 0,o;if(r&&t&&D(t)){let e=t.body;e&&(o=O(e))}let s=r&&!o?z(t):void 0,c=e();if(c instanceof Promise)return async t=>{if(o)try{await o(t)}catch(e){return q(e)}if(s){let e=await s(t);if(e)return q(e)}let n=await e();return Q(n,a)};let l=fe(c,a);if(!o&&!s)return l instanceof Promise?async()=>l:()=>l;if(o)return async e=>{try{await o(e)}catch(e){return q(e)}return l};let u=s;return u?async e=>{let t=await u(e);return t?q(t):l}:l instanceof Promise?async()=>l:()=>l},me=e=>t=>{let n=e(Y(t));return n instanceof Promise?n.then(e=>e instanceof Response?e:Z(e)):n instanceof Response?n:Z(n)},he=(e,t)=>e.middlewares?.length?t.length===0?e.middlewares:[...t,...e.middlewares]:t,ge=(e,t,n)=>{if(!e.input)return!1;if(t)return!0;for(let e of n)if(e.options.request)return!0;return!1},$=async(e,t)=>{let n,r,i;t.validateInput&&le({middlewares:t.middlewares,request:t.routeRequest})&&(i={parsed:!1,value:void 0});let a;t.validateOutput&&t.routeResponse&&(a=oe(t.routeResponse)),t.middlewares.length>0&&(r=e=>n?.[e]);for(let a of t.middlewares){let{request:o,handler:s}=a.options,c=J;if(t.validateInput&&o){let t={},n=await R({req:e,schema:o,bodyCache:i},t);if(n)return q(n);c=t}let l=await s(Y(e,c,r));if(l instanceof Response)return l;l&&(n||={},Object.assign(n,l))}let o=J;if(t.validateInput&&t.routeRequest){let n={},r=await R({req:e,schema:t.routeRequest,bodyCache:i},n);if(r)return q(r);o=n}let s=Y(e,o,r,a);return t.handler(s)},_e=(e,t,n)=>{let r=he(e,t),i=e.handler,a=r.length>0,o=ge(n,e.request,r),s=n.output&&!!e.response;if(!a&&typeof i==`function`&&i.length===0)return pe(i,e.request,e.response,o,s);if(!a&&typeof i==`function`&&i.length===1&&!o&&!s)return me(i);let c={middlewares:r,routeRequest:e.request,routeResponse:e.response,validateInput:o,validateOutput:s,handler:i};return e=>$(e,c)},ve=(e,t,n,r)=>{let i={};for(let a of e){let e=ce({prefix:t,path:a.path});i[e]||(i[e]={}),i[e][a.method]=_e(a,n,r)}return i};var ye=class{options;routes=[];routesDirty=!0;compiled;constructor(e={}){this.options=e}defineRoute(e){this.routes.push(e),this.routesDirty=!0}fetch=e=>this.ensureCompiled().fetch(e);getRouteHandlers(){return this.ensureCompiled().routes}getOpenApiSpec(){return S({title:this.options.openapi?.title??`API`,description:this.options.openapi?.description,version:this.options.openapi?.version??`1.0.0`,prefix:this.options.prefix,servers:this.options.openapi?.servers,securitySchemes:this.options.openapi?.securitySchemes,routes:this.routes,globalMiddlewares:this.options.middlewares})}serve(e,t){let n=Bun.serve({port:e,routes:this.getRouteHandlers(),fetch:()=>new Response(`Not found`,{status:404})});t&&t(n)}ensureCompiled(){if(!this.routesDirty&&this.compiled)return this.compiled;let e=ve(this.routes,this.options.prefix,this.options.middlewares??de,ue(this.options.validation));return this.compiled={routes:e,fetch:a(e)},this.routesDirty=!1,this.compiled}},be=class{options;constructor(e){this.options=e}};exports.Api=ye,exports.Middleware=be;