@kosmojs/api 0.0.20 → 0.0.21

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-present, Slee Woo and KosmoJS contributors.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@kosmojs/api",
4
- "version": "0.0.20",
4
+ "version": "0.0.21",
5
5
  "author": "Slee Woo",
6
6
  "license": "MIT",
7
7
  "publishConfig": {
@@ -12,40 +12,33 @@
12
12
  ],
13
13
  "exports": {
14
14
  ".": {
15
- "types": "./pkg/src/index.d.ts",
15
+ "types": "./pkg/index.d.ts",
16
16
  "default": "./pkg/index.js"
17
17
  },
18
- "./bodyparser": {
19
- "types": "./pkg/src/bodyparser/index.d.ts",
20
- "default": "./pkg/bodyparser/index.js"
21
- },
22
- "./queryparser": {
23
- "types": "./pkg/src/queryparser/index.d.ts",
24
- "default": "./pkg/queryparser/index.js"
25
- },
26
18
  "./errors": {
27
- "types": "./pkg/src/errors/index.d.ts",
19
+ "types": "./pkg/errors/index.d.ts",
28
20
  "default": "./pkg/errors/index.js"
29
21
  }
30
22
  },
31
23
  "dependencies": {
32
- "@koa/router": "^15.0.0",
24
+ "@koa/router": "^15.3.1",
33
25
  "formidable": "^3.5.4",
34
- "koa": "^3.1.1",
35
- "qs": "^6.14.0",
26
+ "koa": "^3.1.2",
27
+ "qs": "^6.15.0",
36
28
  "raw-body": "^3.0.2",
37
- "string-width": "^8.1.0"
29
+ "string-width": "^8.2.0"
38
30
  },
39
31
  "devDependencies": {
40
- "@types/formidable": "^3.4.6",
32
+ "@types/bun": "^1.3.10",
33
+ "@types/formidable": "^3.5.0",
41
34
  "@types/koa": "^3.0.1",
42
35
  "@types/koa-compose": "^3.2.9",
43
36
  "@types/picomatch": "^4.0.2",
44
- "@types/qs": "^6.14.0",
37
+ "@types/qs": "^6.15.0",
45
38
  "koa-compose": "^4.1.0"
46
39
  },
47
40
  "scripts": {
48
- "build": "esbuilder src/index.ts src/bodyparser/index.ts src/queryparser/index.ts src/errors/index.ts",
41
+ "build": "esbuilder src/index.ts src/errors/index.ts",
49
42
  "test": "vitest --root ../../.. --project core/api"
50
43
  }
51
44
  }
package/pkg/debug.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import type { HTTPMethod, Route, UseOptions } from "./types";
2
+ export declare const debugRouteEntry: <MiddlewareT>(entry: {
3
+ name: string;
4
+ path: string;
5
+ file: string;
6
+ methods: Array<string>;
7
+ middleware: Array<{
8
+ middleware: Array<MiddlewareT>;
9
+ options?: UseOptions | undefined;
10
+ }>;
11
+ handler: {
12
+ kind: "handler";
13
+ middleware: Array<MiddlewareT>;
14
+ method: HTTPMethod;
15
+ };
16
+ }) => Route<MiddlewareT>["debug"];
@@ -0,0 +1,21 @@
1
+ import type { ValidationTarget } from "../types";
2
+ import type { ValidationErrorData, ValidationErrorEntry } from "./types";
3
+ export * from "./types";
4
+ /**
5
+ * Standardized error wrapper used by validation generators.
6
+ *
7
+ * Instances of this class are thrown whenever validation fails,
8
+ * carrying both the validation target and the list of validation error details.
9
+ * */
10
+ export declare class ValidationError extends Error {
11
+ target: ValidationTarget;
12
+ errors: Array<ValidationErrorEntry>;
13
+ errorMessage: string;
14
+ errorSummary: string;
15
+ route: string;
16
+ data: unknown;
17
+ constructor([target, { errors, errorMessage, errorSummary, route, data }]: [
18
+ ValidationTarget,
19
+ ValidationErrorData
20
+ ]);
21
+ }
@@ -1,16 +1,20 @@
1
1
  // src/errors/index.ts
2
2
  var ValidationError = class extends Error {
3
- scope;
3
+ target;
4
4
  errors = [];
5
5
  errorMessage;
6
6
  errorSummary;
7
- constructor([scope, { errors, errorMessage, errorSummary }]) {
7
+ route;
8
+ data;
9
+ constructor([target, { errors, errorMessage, errorSummary, route, data }]) {
8
10
  super(JSON.stringify(errors, null, 2));
9
- this.name = `${scope}ValidationError`;
10
- this.scope = scope;
11
+ this.name = `${target}ValidationError`;
12
+ this.target = target;
11
13
  this.errors = errors;
12
14
  this.errorMessage = errorMessage;
13
15
  this.errorSummary = errorSummary;
16
+ this.route = route;
17
+ this.data = data;
14
18
  }
15
19
  };
16
20
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/errors/index.ts"],
4
- "sourcesContent": ["import type {\n ValidationErrorData,\n ValidationErrorEntry,\n ValidationErrorScope,\n} from \"@/types\";\n\n/**\n * Standardized error wrapper used by validation generators.\n *\n * Instances of this class are thrown whenever validation fails,\n * carrying both the error scope (e.g. `\"params\"`, `\"payload\"`)\n * and the list of validation error details.\n * */\nexport class ValidationError extends Error {\n public scope: ValidationErrorScope;\n public errors: Array<ValidationErrorEntry> = [];\n public errorMessage: string;\n public errorSummary: string;\n\n constructor([scope, { errors, errorMessage, errorSummary }]: [\n ValidationErrorScope,\n ValidationErrorData,\n ]) {\n super(JSON.stringify(errors, null, 2));\n this.name = `${scope}ValidationError`;\n this.scope = scope;\n this.errors = errors;\n this.errorMessage = errorMessage;\n this.errorSummary = errorSummary;\n }\n}\n"],
5
- "mappings": ";AAaO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EAClC;AAAA,EACA,SAAsC,CAAC;AAAA,EACvC;AAAA,EACA;AAAA,EAEP,YAAY,CAAC,OAAO,EAAE,QAAQ,cAAc,aAAa,CAAC,GAGvD;AACD,UAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AACrC,SAAK,OAAO,GAAG,KAAK;AACpB,SAAK,QAAQ;AACb,SAAK,SAAS;AACd,SAAK,eAAe;AACpB,SAAK,eAAe;AAAA,EACtB;AACF;",
4
+ "sourcesContent": ["import type { ValidationTarget } from \"../types\";\nimport type { ValidationErrorData, ValidationErrorEntry } from \"./types\";\n\nexport * from \"./types\";\n\n/**\n * Standardized error wrapper used by validation generators.\n *\n * Instances of this class are thrown whenever validation fails,\n * carrying both the validation target and the list of validation error details.\n * */\nexport class ValidationError extends Error {\n public target: ValidationTarget;\n public errors: Array<ValidationErrorEntry> = [];\n public errorMessage: string;\n public errorSummary: string;\n public route: string;\n public data: unknown;\n\n constructor([target, { errors, errorMessage, errorSummary, route, data }]: [\n ValidationTarget,\n ValidationErrorData,\n ]) {\n super(JSON.stringify(errors, null, 2));\n this.name = `${target}ValidationError`;\n this.target = target;\n this.errors = errors;\n this.errorMessage = errorMessage;\n this.errorSummary = errorSummary;\n this.route = route;\n this.data = data;\n }\n}\n"],
5
+ "mappings": ";AAWO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EAClC;AAAA,EACA,SAAsC,CAAC;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEP,YAAY,CAAC,QAAQ,EAAE,QAAQ,cAAc,cAAc,OAAO,KAAK,CAAC,GAGrE;AACD,UAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AACrC,SAAK,OAAO,GAAG,MAAM;AACrB,SAAK,SAAS;AACd,SAAK,SAAS;AACd,SAAK,eAAe;AACpB,SAAK,eAAe;AACpB,SAAK,QAAQ;AACb,SAAK,OAAO;AAAA,EACd;AACF;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Shape of individual validation errors emitted by generators.
3
+ * */
4
+ export type ValidationErrorEntry = {
5
+ /**
6
+ * JSON Schema keyword that triggered the error
7
+ * (e.g. `format`, `maxItems`, `maxLength`).
8
+ * */
9
+ keyword: string;
10
+ /**
11
+ * JSON Pointer–style path to the invalid field
12
+ * (matches JSON Schema `instancePath`).
13
+ * */
14
+ path: string;
15
+ /**
16
+ * Human-readable error message.
17
+ * */
18
+ message: string;
19
+ /**
20
+ * Constraint parameters (e.g. `{ limit: 5 }`, `{ format: "email" }`).
21
+ * */
22
+ params?: Record<string, unknown>;
23
+ /**
24
+ * Optional error code for i18n/l10n or custom handling.
25
+ * */
26
+ code?: string;
27
+ };
28
+ export type ValidationErrorData = {
29
+ errors: Array<ValidationErrorEntry>;
30
+ /**
31
+ * Formats errors into a single human-readable message.
32
+ * @example: Validation failed: user: missing required properties:
33
+ * "email", "name"; password: must be at least 8 characters long
34
+ * */
35
+ errorMessage: string;
36
+ /**
37
+ * Gets a simple error summary for quick feedback.
38
+ * @example: 2 validation errors found across 2 fields
39
+ * */
40
+ errorSummary: string;
41
+ route: string;
42
+ data: unknown;
43
+ };
package/pkg/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./debug";
2
+ export * from "./routes";
3
+ export * from "./types";
package/pkg/index.js CHANGED
@@ -1,64 +1,3 @@
1
- // src/app.ts
2
- import Koa from "koa";
3
-
4
- // src/queryparser/index.ts
5
- import { parse, stringify } from "qs";
6
- var queryparser_default = (app, _parseOptions = {}, _stringifyOptions = {}) => {
7
- const parseOptions = {
8
- ignoreQueryPrefix: true,
9
- parseArrays: true,
10
- arrayLimit: 100,
11
- parameterLimit: 100,
12
- depth: 5,
13
- ..._parseOptions
14
- };
15
- const stringifyOptions = {
16
- encodeValuesOnly: true,
17
- arrayFormat: "brackets",
18
- ..._stringifyOptions
19
- };
20
- const obj = {
21
- get query() {
22
- return parse(this.querystring || "", parseOptions);
23
- },
24
- set query(obj2) {
25
- this.querystring = stringify(obj2, stringifyOptions);
26
- }
27
- };
28
- const entries = Object.getOwnPropertyNames(obj).map((name) => [
29
- name,
30
- Object.getOwnPropertyDescriptor(obj, name)
31
- ]);
32
- for (const [name, desc] of entries) {
33
- Object.defineProperty(app.request, name, desc);
34
- }
35
- return app;
36
- };
37
-
38
- // src/app.ts
39
- var createApp = (options) => {
40
- return queryparser_default(new Koa(options));
41
- };
42
-
43
- // src/errors/index.ts
44
- var ValidationError = class extends Error {
45
- scope;
46
- errors = [];
47
- errorMessage;
48
- errorSummary;
49
- constructor([scope, { errors, errorMessage, errorSummary }]) {
50
- super(JSON.stringify(errors, null, 2));
51
- this.name = `${scope}ValidationError`;
52
- this.scope = scope;
53
- this.errors = errors;
54
- this.errorMessage = errorMessage;
55
- this.errorSummary = errorSummary;
56
- }
57
- };
58
-
59
- // src/router.ts
60
- import Router from "@koa/router";
61
-
62
1
  // src/debug.ts
63
2
  import { styleText } from "node:util";
64
3
  import stringWidth from "string-width";
@@ -73,7 +12,7 @@ var colorizeMethod = (method) => {
73
12
  }[method];
74
13
  return color ? styleText(color, method) : method;
75
14
  };
76
- var debug_default = (entry) => {
15
+ var debugRouteEntry = (entry) => {
77
16
  const { path, file } = entry;
78
17
  const methodLines = entry.methods.flatMap((method) => {
79
18
  const coloredMethod = colorizeMethod(method);
@@ -83,13 +22,12 @@ var debug_default = (entry) => {
83
22
  const lines = [];
84
23
  if (options?.slot) {
85
24
  lines.push(
86
- `${styleText("dim", "slot:")} ${styleText("blue", options.slot)};`
25
+ `${styleText("dim", "slot:")} ${styleText("blue", options.slot)}`
87
26
  );
88
27
  }
89
- const funcNames = middleware2.map((fn) => {
90
- return styleText("magenta", funcName(fn));
91
- });
92
- lines.push(`${styleText("dim", "exec:")} ${funcNames.join("; ")}`);
28
+ lines.push(
29
+ styleText("dim", middleware2.map(funcName).join("; "))
30
+ );
93
31
  return lines.join(" ");
94
32
  }).join(`
95
33
  ${Array(12).fill(" ").join("")}`);
@@ -133,107 +71,35 @@ var funcName = (fn) => {
133
71
  return fn.name || fn.toString().split("\n")[0].slice(0, 30);
134
72
  };
135
73
 
136
- // src/use.ts
137
- var use = (middleware, options) => {
138
- return {
139
- kind: "middleware",
140
- middleware: [middleware].flat(),
141
- options
142
- };
143
- };
144
-
145
- // src/router.ts
146
- var defineRoute = (factory) => {
147
- return factory({
148
- use(middleware, options) {
149
- return {
150
- kind: "middleware",
151
- middleware: [middleware].flat(),
152
- options
153
- };
154
- },
155
- HEAD(middleware) {
156
- return {
157
- kind: "handler",
158
- middleware: [middleware].flat(),
159
- method: "HEAD"
160
- };
161
- },
162
- OPTIONS(middleware) {
163
- return {
164
- kind: "handler",
165
- middleware: [middleware].flat(),
166
- method: "OPTIONS"
167
- };
168
- },
169
- GET(middleware) {
170
- return {
171
- kind: "handler",
172
- middleware: [middleware].flat(),
173
- method: "GET"
174
- };
175
- },
176
- POST(middleware) {
177
- return {
178
- kind: "handler",
179
- middleware: [middleware].flat(),
180
- method: "POST"
181
- };
182
- },
183
- PUT(middleware) {
184
- return {
185
- kind: "handler",
186
- middleware: [middleware].flat(),
187
- method: "PUT"
188
- };
189
- },
190
- PATCH(middleware) {
191
- return {
192
- kind: "handler",
193
- middleware: [middleware].flat(),
194
- method: "PATCH"
195
- };
196
- },
197
- DELETE(middleware) {
198
- return {
199
- kind: "handler",
200
- middleware: [middleware].flat(),
201
- method: "DELETE"
202
- };
203
- }
204
- });
205
- };
206
- var createRouter = (options) => {
207
- return new Router(options);
208
- };
209
- var createRouterRoutes = (routeSources, {
210
- // Global middleware applied to every route (e.g., logging)
211
- coreMiddleware
74
+ // src/routes.ts
75
+ var createRoutes = (routeSources, {
76
+ globalMiddleware,
77
+ createRouteMiddleware
212
78
  }) => {
213
79
  const prioritizedSlots = [
214
80
  "errorHandler",
215
- "params",
216
- "validateParams",
81
+ "extendContext",
217
82
  "bodyparser",
218
- "payload",
219
- "validatePayload",
220
- "validateResponse"
83
+ "validate:params",
84
+ "validate:query",
85
+ "validate:headers",
86
+ "validate:cookies",
87
+ "validate:json",
88
+ "validate:form",
89
+ "validate:raw",
90
+ "validate:response"
221
91
  ];
222
92
  const stack = [];
223
- for (const { name, path, file, ...rest } of routeSources) {
93
+ for (const routeSource of routeSources) {
94
+ const { name, path, file } = routeSource;
224
95
  const definitionItems = [
225
- ...rest.useWrappers,
226
- ...rest.definitionItems
96
+ ...routeSource.cascadingMiddleware,
97
+ ...routeSource.definitionItems
227
98
  ].flat();
228
- const routeMiddleware = definitionItems.filter(
229
- (e) => e.kind === "middleware"
230
- );
99
+ const routeMiddleware = definitionItems.filter((e) => e.kind === "middleware");
231
100
  const middlewareStack = [
232
- ...createParamsMiddleware(rest.params, rest.numericParams),
233
- ...createValidationMiddleware(rest.validationSchemas),
234
- // core middleware overrides builtin middleware (of same slot)
235
- ...coreMiddleware,
236
- // route middleware overrides core middleware (of same slot)
101
+ ...createRouteMiddleware(routeSource),
102
+ ...globalMiddleware,
237
103
  ...routeMiddleware
238
104
  ];
239
105
  const routeStack = [
@@ -245,7 +111,7 @@ var createRouterRoutes = (routeSources, {
245
111
  );
246
112
  return middleware ? [middleware] : [];
247
113
  }),
248
- ...coreMiddleware.flatMap((entry) => {
114
+ ...globalMiddleware.flatMap((entry) => {
249
115
  if (!entry.options?.slot) {
250
116
  return [entry];
251
117
  }
@@ -265,7 +131,7 @@ var createRouterRoutes = (routeSources, {
265
131
  if (prioritizedSlots.includes(slot)) {
266
132
  return [];
267
133
  }
268
- if (coreMiddleware.some((e) => e.options?.slot === slot)) {
134
+ if (globalMiddleware.some((e) => e.options?.slot === slot)) {
269
135
  return [];
270
136
  }
271
137
  }
@@ -289,7 +155,7 @@ var createRouterRoutes = (routeSources, {
289
155
  ...middleware.flatMap((e) => e.middleware),
290
156
  ...entry.middleware
291
157
  ],
292
- debug: debug_default({
158
+ debug: debugRouteEntry({
293
159
  name,
294
160
  path,
295
161
  file,
@@ -303,63 +169,6 @@ var createRouterRoutes = (routeSources, {
303
169
  }
304
170
  return stack;
305
171
  };
306
- var createParamsMiddleware = (params, numericParams) => [
307
- use(
308
- function useParams(ctx, next) {
309
- ctx.typedParams = params.reduce(
310
- (map, [name, isRest]) => {
311
- const value = ctx.params[name];
312
- if (value) {
313
- if (isRest) {
314
- map[name] = numericParams.includes(name) ? value.split("/").map(Number) : value.split("/");
315
- } else {
316
- map[name] = numericParams.includes(name) ? Number(value) : value;
317
- }
318
- } else {
319
- map[name] = value;
320
- }
321
- return map;
322
- },
323
- {}
324
- );
325
- return next();
326
- },
327
- { slot: "params" }
328
- )
329
- ];
330
- var createValidationMiddleware = (validationSchemas) => [
331
- use(
332
- function useValidateParams(ctx, next) {
333
- validationSchemas.params?.validate(ctx.typedParams);
334
- return next();
335
- },
336
- { slot: "validateParams" }
337
- ),
338
- use(
339
- function useValidatePayload(ctx, next) {
340
- validationSchemas.payload?.[ctx.method]?.validate(ctx.payload);
341
- return next();
342
- },
343
- {
344
- slot: "validatePayload",
345
- on: Object.keys(validationSchemas.payload || {})
346
- }
347
- ),
348
- use(
349
- async function useValidateResponse(ctx, next) {
350
- if (validationSchemas.response?.[ctx.method]) {
351
- await next();
352
- validationSchemas.response?.[ctx.method]?.validate(ctx.body);
353
- } else {
354
- return next();
355
- }
356
- },
357
- {
358
- slot: "validateResponse",
359
- on: Object.keys(validationSchemas.response || {})
360
- }
361
- )
362
- ];
363
172
 
364
173
  // src/types.ts
365
174
  var HTTPMethods = /* @__PURE__ */ ((HTTPMethods2) => {
@@ -372,13 +181,28 @@ var HTTPMethods = /* @__PURE__ */ ((HTTPMethods2) => {
372
181
  HTTPMethods2["DELETE"] = "DELETE";
373
182
  return HTTPMethods2;
374
183
  })(HTTPMethods || {});
184
+ var RequestMetadataTargets = {
185
+ query: "URL query parameters",
186
+ headers: "HTTP request headers",
187
+ cookies: "HTTP cookies"
188
+ };
189
+ var RequestBodyTargets = {
190
+ json: "JSON request body",
191
+ form: "URL-encoded or Multipart form",
192
+ raw: "Raw body format (string/Buffer/ArrayBuffer/Blob)"
193
+ };
194
+ var RequestValidationTargets = {
195
+ ...RequestMetadataTargets,
196
+ ...RequestBodyTargets
197
+ };
198
+ var StateKey = /* @__PURE__ */ Symbol("kosmo.state");
375
199
  export {
376
200
  HTTPMethods,
377
- ValidationError,
378
- createApp,
379
- createRouter,
380
- createRouterRoutes,
381
- defineRoute,
382
- use
201
+ RequestBodyTargets,
202
+ RequestMetadataTargets,
203
+ RequestValidationTargets,
204
+ StateKey,
205
+ createRoutes,
206
+ debugRouteEntry
383
207
  };
384
208
  //# sourceMappingURL=index.js.map