@ozanarslan/corpus 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +174 -0
- package/dist/index.d.ts +758 -0
- package/dist/index.js +170513 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Tabula Corpus
|
|
2
|
+
|
|
3
|
+
A very simple typescript backend framework package to use for personal projects or simple crud applications.
|
|
4
|
+
This package is by no means a replacement for full fledged backend frameworks commonly used in production.
|
|
5
|
+
|
|
6
|
+
## Quick Start
|
|
7
|
+
|
|
8
|
+
Install the package:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
bun add @tabula/corpus
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @tabula/corpus
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Create a simple server:
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { Server, Route, Config } from "@tabula/corpus";
|
|
22
|
+
|
|
23
|
+
// Initialize server
|
|
24
|
+
const server = new Server();
|
|
25
|
+
|
|
26
|
+
// Add a route
|
|
27
|
+
new Route("/health", () => "ok");
|
|
28
|
+
|
|
29
|
+
// Start listening
|
|
30
|
+
server.listen(3000);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
That's it. Your bare bones backend is running.
|
|
34
|
+
|
|
35
|
+
## What does this library do?
|
|
36
|
+
|
|
37
|
+
- Registering routes using `Route` or `Controller`
|
|
38
|
+
- Registering static pages using `StaticRoute` or `Controller`
|
|
39
|
+
- Registering middlewares using `Middleware`
|
|
40
|
+
- Request data validation based on libraries using Standard Schema (e.g. arktype and zod)
|
|
41
|
+
- Request handling using `RouteContext` (the `(c) => {}` callback pattern)
|
|
42
|
+
- Loading env variables using `Config`
|
|
43
|
+
- Other utilities such as setting cors, global prefix, error handling etc.
|
|
44
|
+
|
|
45
|
+
## How does the routing work?
|
|
46
|
+
|
|
47
|
+
- Routes, route models and middlewares are lazily registered to their respective registries on class initialization. Router uses the registries and is created with the Server object. The Server should be created before any route, controller or middleware.
|
|
48
|
+
- Router is RegExp based and supports route parameters.
|
|
49
|
+
- The router isn't very advanced since this is my very first time working on such a project, I appreciate all feedback.
|
|
50
|
+
|
|
51
|
+
## Runtime?
|
|
52
|
+
|
|
53
|
+
Originally I wanted to support Node and Bun runtimes but to be honest, I didn't test with node at all because I almost always prefer Bun in my personal projects and this library is meant to be used for small personal projects. Maybe I'll get back to the idea later on.
|
|
54
|
+
|
|
55
|
+
## What is the pattern I had in mind?
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// You can also use something else
|
|
59
|
+
import { type } from "arktype";
|
|
60
|
+
// You can also import everything by name
|
|
61
|
+
import C from "@tabula/corpus";
|
|
62
|
+
|
|
63
|
+
// You can use schemas however you want, I just really like this.
|
|
64
|
+
export class ItemModel {
|
|
65
|
+
static entity = type({
|
|
66
|
+
id: "number",
|
|
67
|
+
createdAt: "string.date.iso",
|
|
68
|
+
name: "string",
|
|
69
|
+
});
|
|
70
|
+
static create = {
|
|
71
|
+
body: this.entity.omit("id", "createdAt"),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// This helper type could also work for similar prototypes
|
|
76
|
+
export type ItemType = C.InferModel<typeof ItemModel>;
|
|
77
|
+
|
|
78
|
+
// This is also a personal helper, all repositories get
|
|
79
|
+
// the DatabaseClientInterface in constructor args.
|
|
80
|
+
// This interface can be extended.
|
|
81
|
+
export class ItemRepository extends C.Repository {
|
|
82
|
+
// ...
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Service layer isn't included, it's just a good idea
|
|
86
|
+
export class ItemService {
|
|
87
|
+
constructor(private readonly itemRepository: ItemRepository) {}
|
|
88
|
+
|
|
89
|
+
create(body: ItemType["create"]["body"]) {
|
|
90
|
+
// ...
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Controller is an abstract class
|
|
95
|
+
export class ItemController extends C.Controller {
|
|
96
|
+
constructor(private readonly itemService: ItemService) {
|
|
97
|
+
super({ prefix: "/item" });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Helper instead of new Route()
|
|
101
|
+
create = this.route(
|
|
102
|
+
{ method: "POST", path: "/create" },
|
|
103
|
+
(c) => this.itemService.create(c.body),
|
|
104
|
+
ItemModel.create,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// Static routes can also be in the controller instead of new StaticRoute()
|
|
108
|
+
page = this
|
|
109
|
+
.staticRoute
|
|
110
|
+
// ...
|
|
111
|
+
();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Server must be created first for the router
|
|
115
|
+
const server = new C.Server();
|
|
116
|
+
// This DOES NOT apply to static routes
|
|
117
|
+
server.setGlobalPrefix("/api");
|
|
118
|
+
|
|
119
|
+
// Cors headers are applied globally if you set them this way also
|
|
120
|
+
// any request with Access-Control-Request-Method header and OPTIONS
|
|
121
|
+
// method is handled as a preflight request.
|
|
122
|
+
server.setCors({});
|
|
123
|
+
|
|
124
|
+
const db = new DatabaseClient();
|
|
125
|
+
server.setOnBeforeListen(() => db.connect());
|
|
126
|
+
server.setOnBeforeExit(() => db.disconnect());
|
|
127
|
+
|
|
128
|
+
// There isn't any automatic dependency injection because i don't like it,
|
|
129
|
+
// you can create your objects in whatever order you like
|
|
130
|
+
const itemRepository = new ItemRepository(db);
|
|
131
|
+
const itemService = new ItemService(itemRepository);
|
|
132
|
+
new ItemController(itemService);
|
|
133
|
+
new C.Route("/health", () => "ok");
|
|
134
|
+
|
|
135
|
+
// Config is a helper with a couple of methods for paths and vars.
|
|
136
|
+
server.listen(
|
|
137
|
+
C.Config.get("PORT", { parser: parseInt, fallback: 3000 }),
|
|
138
|
+
"0.0.0.0",
|
|
139
|
+
);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## What interfaces can be extended
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
declare module "@tabula/corpus" {
|
|
146
|
+
// process.env basically
|
|
147
|
+
interface Env {}
|
|
148
|
+
|
|
149
|
+
// Any data assigned to c.data anywhere
|
|
150
|
+
interface ContextDataInterface {}
|
|
151
|
+
|
|
152
|
+
// The database client class I use in my app
|
|
153
|
+
interface DatabaseClientInterface extends DatabaseClient {}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
# Closing thoughts
|
|
158
|
+
|
|
159
|
+
As I mentioned multiple times, this is not for production. It's also my first ever personal project at this scale. I am very open to suggestions (maybe even pull requests). Thank you for being here.
|
|
160
|
+
|
|
161
|
+
Very much inspired from the core ideas behind [Elysia](https://github.com/elysiajs/elysia)
|
|
162
|
+
|
|
163
|
+
# Roadmap
|
|
164
|
+
|
|
165
|
+
- [ ] Better and more memory efficient router
|
|
166
|
+
- [ ] Reduce dist size
|
|
167
|
+
- [ ] Support ArrayBuffer in custom Response wrapper object
|
|
168
|
+
- [ ] Support Blob in custom Response wrapper object
|
|
169
|
+
- [ ] Support FormData in custom Response wrapper object
|
|
170
|
+
- [ ] Support URLSearchParams in custom Response wrapper object
|
|
171
|
+
- [ ] Support ReadableStream in custom Response wrapper object
|
|
172
|
+
- [ ] Support WebSocket
|
|
173
|
+
- [ ] Compress static files in StaticRoute for caching and stuff maybe?
|
|
174
|
+
- [ ] Better everything
|