dynara 0.0.1 → 0.0.3

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 CHANGED
@@ -1,6 +1,6 @@
1
- # Marci
1
+ # Dynara
2
2
 
3
- [![NPM version](https://img.shields.io/npm/v/%40den59k%2Fmarci)](https://www.npmjs.com/package/@den59k/marci)
3
+ [![NPM version](https://img.shields.io/npm/v/dynara)](https://www.npmjs.com/package/dynara)
4
4
 
5
5
  An extremely simple HTTP framework for Bun — practically a typed wrapper around `Bun.serve`, with Fastify-style routing and fast schema validation. Made for people switching over from Fastify.
6
6
 
@@ -11,15 +11,15 @@ An extremely simple HTTP framework for Bun — practically a typed wrapper aroun
11
11
  ## Install
12
12
 
13
13
  ```sh
14
- bun add @den59k/marci
14
+ bun add dynara
15
15
  ```
16
16
 
17
17
  ## Quick start
18
18
 
19
19
  ```ts
20
- import { MarciApp } from '@den59k/marci'
20
+ import { Router } from 'dynara'
21
21
 
22
- const app = new MarciApp()
22
+ const app = new Router()
23
23
 
24
24
  app.get('/', () => {
25
25
  return { hello: 'world' }
@@ -59,8 +59,16 @@ A few conveniences:
59
59
 
60
60
  - **Array params** are comma-split: `GET /items/1,2,3` with `{ itemIds: { type: 'array', items: 'number' } }` yields `[1, 2, 3]`.
61
61
  - **Query booleans** accept `?flag=true` or a bare `?flag`.
62
+ - **Dates**: a `"date"` field accepts an ISO-8601 string (`"2020-01-02"`, optionally with a time part) or epoch milliseconds, and is decoded into a JS `Date` before your handler runs — `req.body.startsAt instanceof Date`. Works in bodies, query strings, and params; `"date?"` / `"date??"` behave like every other type.
62
63
  - `req.raw` exposes the underlying `BunRequest`, and `req.server` the Bun `Server`.
63
64
 
65
+ ```ts
66
+ const event = schema({ title: 'string', startsAt: 'date', endsAt: 'date??' })
67
+ app.post('/events', { body: event }, (req) => {
68
+ return { day: req.body.startsAt.getDay() } // startsAt is a Date
69
+ })
70
+ ```
71
+
64
72
  ## Hooks
65
73
 
66
74
  ```ts
@@ -80,22 +88,22 @@ app.addHook('onListen', (server) => {
80
88
  ```ts
81
89
  type Ctx = { user: { id: number } }
82
90
 
83
- app.register((users: MarciApp<Ctx>) => {
91
+ app.register((users: Router<Ctx>) => {
84
92
  users.addHook('onRequest', (req) => { req.user = { id: 1 } })
85
93
  users.get('/me', (req) => req.user)
86
94
  }, { prefix: '/users' })
87
95
  ```
88
96
 
89
- For composable, reusable plugins there is the `marci()` builder. `use` adds plugins, `routes` adds handlers, and the result can be passed to `register`:
97
+ For composable, reusable plugins there is the `dynara()` builder. `use` adds plugins, `routes` adds handlers, and the result can be passed to `register`:
90
98
 
91
99
  ```ts
92
- import { marci, MarciApp } from '@den59k/marci'
100
+ import { dynara, Router } from 'dynara'
93
101
 
94
- const useAuth = (app: MarciApp<Ctx>) => {
102
+ const useAuth = (app: Router<Ctx>) => {
95
103
  app.addHook('onRequest', (req) => { req.user = { id: 1 } })
96
104
  }
97
105
 
98
- const users = marci<Ctx>()
106
+ const users = dynara<Ctx>()
99
107
  .use(useAuth)
100
108
  .routes((app) => {
101
109
  app.get('/me', (req) => req.user)
@@ -109,7 +117,7 @@ app.register(users, { prefix: '/users' })
109
117
  Throw `HTTPError` to send an explicit status code; validation failures are turned into `400` responses automatically.
110
118
 
111
119
  ```ts
112
- import { HTTPError } from '@den59k/marci'
120
+ import { HTTPError } from 'dynara'
113
121
 
114
122
  app.get('/secret', () => {
115
123
  throw new HTTPError('Forbidden', 403) // text body
package/dist/index.d.ts CHANGED
@@ -15,21 +15,21 @@ type GetRouteOptions = {
15
15
  params?: SchemaItem
16
16
  query?: SchemaItem
17
17
  };
18
- interface MarciContext {}
19
- type MarciRequest<
18
+ interface DynaraContext {}
19
+ type DynaraRequest<
20
20
  R extends object = {},
21
21
  T extends RouteOptions = {}
22
- > = MarciContext & R & {
22
+ > = DynaraContext & R & {
23
23
  params: T["params"] extends object ? SchemaType<T["params"]> : unknown
24
24
  query: T["query"] extends object ? SchemaType<T["query"]> : unknown
25
25
  body: T["body"] extends object ? SchemaType<T["body"]> : unknown
26
26
  raw: BunRequest
27
27
  server: Server
28
28
  };
29
- type GetMarciRequest<
29
+ type GetDynaraRequest<
30
30
  R extends object = {},
31
31
  T extends GetRouteOptions = {}
32
- > = MarciContext & R & {
32
+ > = DynaraContext & R & {
33
33
  params: T["params"] extends object ? SchemaType<T["params"]> : unknown
34
34
  query: T["query"] extends object ? SchemaType<T["query"]> : unknown
35
35
  raw: BunRequest
@@ -38,11 +38,11 @@ type GetMarciRequest<
38
38
  type RouteAction<
39
39
  T extends RouteOptions,
40
40
  R extends object = {}
41
- > = (req: MarciRequest<R, T>) => (any | Promise<any>);
41
+ > = (req: DynaraRequest<R, T>) => (any | Promise<any>);
42
42
  type GetRouteAction<
43
43
  T extends GetRouteOptions,
44
44
  R extends object = {}
45
- > = (req: GetMarciRequest<R, T>) => (any | Promise<any>);
45
+ > = (req: GetDynaraRequest<R, T>) => (any | Promise<any>);
46
46
  type RegisterPluginOptions = {
47
47
  prefix?: string
48
48
  };
@@ -67,7 +67,7 @@ type PostOptionsFromSchemaList<T extends readonly SchemaItem2[]> = T extends [Sc
67
67
  params: T[0]
68
68
  body: T[1]
69
69
  } : {};
70
- declare class MarciApp<R extends object = {}> {
70
+ declare class Router<R extends object = {}> {
71
71
  private routes;
72
72
  private promises;
73
73
  private prefix;
@@ -77,7 +77,7 @@ declare class MarciApp<R extends object = {}> {
77
77
  private onRequestHooks;
78
78
  private add;
79
79
  addHook(where: "onListen", callback: (server: Bun.Server2) => void): void;
80
- addHook(where: "onRequest", callback: (ctx: MarciRequest<R>) => void): void;
80
+ addHook(where: "onRequest", callback: (ctx: DynaraRequest<R>) => void): void;
81
81
  get(path: string, callback: GetRouteAction<{}, R>): void;
82
82
  get<T extends GetRouteOptions>(path: string, options: T, callback: GetRouteAction<T, R>): void;
83
83
  get<T extends readonly SchemaItem3[]>(path: string, schemas: [...T], callback: GetRouteAction<GetOptionsFromSchemaList<T>, R>): void;
@@ -93,7 +93,7 @@ declare class MarciApp<R extends object = {}> {
93
93
  delete(path: string, callback: RouteAction<{}, R>): void;
94
94
  delete<T extends RouteOptions>(path: string, options: T, callback: RouteAction<T, R>): void;
95
95
  delete<T extends readonly SchemaItem3[]>(path: string, schemas: [...T], callback: RouteAction<PostOptionsFromSchemaList<T>, R>): void;
96
- register(plugin: (app: MarciApp<any>) => void | Promise<void>, options?: RegisterPluginOptions): void;
96
+ register(plugin: (app: Router<any>) => void | Promise<void>, options?: RegisterPluginOptions): void;
97
97
  private websocket?;
98
98
  private websocketPath?;
99
99
  private websocketFetch?;
@@ -108,12 +108,12 @@ declare class MarciApp<R extends object = {}> {
108
108
  /**\\n\\t\\n\\t* Dispatches a request through the app's routes in-process — no server, no\\n\\t\\n\\t* socket. Matches the route the same way Bun would, then runs the onRequest\\n\\t\\n\\t* hooks, validation, handler, and error mapping, returning the Response.\\n\\t\\n\\t* Intended for integration testing.\\n\\t\\n\\t*\\n\\t\\n\\t* @example\\n\\t\\n\\t* const res = await app.inject("/users/1")\\n\\t\\n\\t* const res = await app.inject({ method: "POST", url: "/users", body: { name: "Alice" } })\\n\\t\\n\\t* expect(res.status).toBe(200)\\n\\t\\n\\t* expect(await res.json()).toEqual({ ... })\\n\\t\\n\\t*/
109
109
  inject(options: string | InjectOptions): Promise<Response>;
110
110
  }
111
- type MarciSyntax<R extends object = {}> = ((app: MarciApp<any>) => Promise<void>) & {
111
+ type Plugin<R extends object = {}> = ((app: Router<any>) => Promise<void>) & {
112
112
  use<
113
113
  S extends object = {},
114
114
  A extends any[] = []
115
- >(plugin: (app: MarciApp<R & S>, ...args: A) => Promise<void> | void, ...args: A): MarciSyntax<R & S>
116
- routes(app: (app: MarciApp<R>) => Promise<void> | void): MarciSyntax<R>
115
+ >(plugin: (app: Router<R & S>, ...args: A) => Promise<void> | void, ...args: A): Plugin<R & S>
116
+ routes(app: (app: Router<R>) => Promise<void> | void): Plugin<R>
117
117
  };
118
- declare const marci: <R extends object = {}>() => MarciSyntax<R>;
119
- export { marci, MarciSyntax, MarciRequest, MarciApp, InjectOptions, HTTPError };
118
+ declare const dynara: <R extends object = {}>() => Plugin<R>;
119
+ export { dynara, Router, Plugin, InjectOptions, HTTPError, DynaraRequest };
package/dist/index.js CHANGED
@@ -28,8 +28,8 @@ class ValidationError extends Error {
28
28
  }
29
29
  }
30
30
 
31
- // src/marci-syntax.ts
32
- var marci = () => {
31
+ // src/plugin.ts
32
+ var dynara = () => {
33
33
  const plugins = [];
34
34
  const handlers = [];
35
35
  const app = Object.assign(async (app2) => {
@@ -51,7 +51,7 @@ var marci = () => {
51
51
  });
52
52
  return app;
53
53
  };
54
- // src/MarciApp.ts
54
+ // src/Router.ts
55
55
  import { unfoldTypeBoxSchema } from "compact-json-schema";
56
56
  import { TypeBoxError } from "@sinclair/typebox";
57
57
  import { TypeCompiler } from "@sinclair/typebox/compiler";
@@ -59,7 +59,7 @@ import { TypeCompiler } from "@sinclair/typebox/compiler";
59
59
  // src/request.ts
60
60
  import { Value } from "@sinclair/typebox/value";
61
61
 
62
- class MarciRequestInternal {
62
+ class DynaraRequestInternal {
63
63
  server;
64
64
  raw;
65
65
  params;
@@ -131,6 +131,8 @@ var getValidationError = (obj, step) => {
131
131
  return `{"cause":"Validation error","fields":{"${obj.path.slice(1)}":{"message":"${obj.message}"}}}`;
132
132
  }
133
133
  };
134
+ var DATE_PATTERN = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d{1,6})?)?(Z|[+-]\d{2}:\d{2})?)?$/;
135
+ var dateType = (options) => Type.Transform(Type.Union([Type.String({ pattern: DATE_PATTERN.source }), Type.Number()], options)).Decode((value) => new Date(value)).Encode((value) => value instanceof Date ? value.toISOString() : value);
134
136
  provideTypeBoxMap({
135
137
  string: Type.String,
136
138
  boolean: Type.Boolean,
@@ -139,6 +141,7 @@ provideTypeBoxMap({
139
141
  object: Type.Object,
140
142
  array: Type.Array,
141
143
  bigint: Type.BigInt,
144
+ date: dateType,
142
145
  union: Type.Union,
143
146
  null: Type.Null,
144
147
  literal: Type.Literal,
@@ -165,8 +168,9 @@ var parseBody = (schema, req) => {
165
168
  const error = schema.Errors(resp).First();
166
169
  const err = new ValidationError(error, "body");
167
170
  rej(err);
171
+ return;
168
172
  }
169
- res(resp);
173
+ res(schema.Decode(resp));
170
174
  });
171
175
  });
172
176
  };
@@ -220,8 +224,8 @@ var matchRoute = (routes, method, pathname) => {
220
224
  return best;
221
225
  };
222
226
 
223
- // src/MarciApp.ts
224
- class MarciApp {
227
+ // src/Router.ts
228
+ class Router {
225
229
  routes = {};
226
230
  promises = [];
227
231
  prefix = "";
@@ -245,7 +249,7 @@ class MarciApp {
245
249
  }
246
250
  }
247
251
  this.routes[fullPath][method] = async (req) => {
248
- const request = new MarciRequestInternal(req, this.root?.server ?? this.server, paramsSchema, querySchema);
252
+ const request = new DynaraRequestInternal(req, this.root?.server ?? this.server, paramsSchema, querySchema);
249
253
  for (const callback2 of this.onRequestHooks) {
250
254
  await callback2(request);
251
255
  }
@@ -304,7 +308,7 @@ class MarciApp {
304
308
  }
305
309
  }
306
310
  register(plugin, options = {}) {
307
- const app = new MarciApp;
311
+ const app = new Router;
308
312
  app.root = this.root ?? this;
309
313
  app.routes = this.routes;
310
314
  app.onListenHooks = this.onListenHooks;
@@ -420,7 +424,7 @@ class MarciApp {
420
424
  }
421
425
  }
422
426
  export {
423
- marci,
424
- MarciApp,
427
+ dynara,
428
+ Router,
425
429
  HTTPError
426
430
  };
package/package.json CHANGED
@@ -11,12 +11,12 @@
11
11
  }
12
12
  },
13
13
  "license": "MIT",
14
- "version": "0.0.1",
14
+ "version": "0.0.3",
15
15
  "description": "Simple HTTP framework powered by Bun",
16
16
  "repository": {
17
17
  "type": "git",
18
- "url": "https://github.com/den59k/marci.git",
19
- "directory": "packages/marci"
18
+ "url": "https://github.com/den59k/dynara.git",
19
+ "directory": "packages/dynara"
20
20
  },
21
21
  "scripts": {
22
22
  "build": "bunx bunup src/index.ts --format esm",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@sinclair/typebox": "^0.34.37",
36
- "compact-json-schema": "^0.1.4"
36
+ "compact-json-schema": "^0.1.6"
37
37
  },
38
38
  "files": [
39
39
  "dist"