hola-server 2.0.1 → 3.0.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 +318 -132
- package/dist/config/index.d.ts +46 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +55 -0
- package/dist/config/index.js.map +1 -0
- package/dist/core/array.d.ts +27 -0
- package/dist/core/array.d.ts.map +1 -0
- package/dist/core/array.js +66 -0
- package/dist/core/array.js.map +1 -0
- package/dist/core/bash.d.ts +51 -0
- package/dist/core/bash.d.ts.map +1 -0
- package/dist/core/bash.js +161 -0
- package/dist/core/bash.js.map +1 -0
- package/dist/core/chart.d.ts +11 -0
- package/dist/core/chart.d.ts.map +1 -0
- package/dist/core/chart.js +35 -0
- package/dist/core/chart.js.map +1 -0
- package/dist/core/date.d.ts +11 -0
- package/dist/core/date.d.ts.map +1 -0
- package/dist/core/date.js +18 -0
- package/dist/core/date.js.map +1 -0
- package/dist/core/encrypt.d.ts +18 -0
- package/dist/core/encrypt.d.ts.map +1 -0
- package/dist/core/encrypt.js +50 -0
- package/dist/core/encrypt.js.map +1 -0
- package/dist/core/file.d.ts +22 -0
- package/dist/core/file.d.ts.map +1 -0
- package/dist/core/file.js +21 -0
- package/dist/core/file.js.map +1 -0
- package/dist/core/lhs.d.ts +17 -0
- package/dist/core/lhs.d.ts.map +1 -0
- package/dist/core/lhs.js +30 -0
- package/dist/core/lhs.js.map +1 -0
- package/dist/core/meta.d.ts +200 -0
- package/dist/core/meta.d.ts.map +1 -0
- package/dist/core/meta.js +336 -0
- package/dist/core/meta.js.map +1 -0
- package/dist/core/number.d.ts +37 -0
- package/dist/core/number.d.ts.map +1 -0
- package/dist/core/number.js +99 -0
- package/dist/core/number.js.map +1 -0
- package/dist/core/obj.d.ts +9 -0
- package/dist/core/obj.d.ts.map +1 -0
- package/dist/core/obj.js +15 -0
- package/dist/core/obj.js.map +1 -0
- package/dist/core/random.d.ts +7 -0
- package/dist/core/random.d.ts.map +1 -0
- package/dist/core/random.js +7 -0
- package/dist/core/random.js.map +1 -0
- package/dist/core/role.d.ts +42 -0
- package/dist/core/role.d.ts.map +1 -0
- package/dist/core/role.js +81 -0
- package/dist/core/role.js.map +1 -0
- package/dist/core/thread.d.ts +7 -0
- package/dist/core/thread.d.ts.map +1 -0
- package/dist/core/thread.js +7 -0
- package/dist/core/thread.js.map +1 -0
- package/dist/core/type.d.ts +46 -0
- package/dist/core/type.d.ts.map +1 -0
- package/dist/core/type.js +281 -0
- package/dist/core/type.js.map +1 -0
- package/dist/core/url.d.ts +20 -0
- package/dist/core/url.d.ts.map +1 -0
- package/dist/core/url.js +24 -0
- package/dist/core/url.js.map +1 -0
- package/dist/core/validate.d.ts +11 -0
- package/dist/core/validate.d.ts.map +1 -0
- package/dist/core/validate.js +19 -0
- package/dist/core/validate.js.map +1 -0
- package/dist/db/db.d.ts +72 -0
- package/dist/db/db.d.ts.map +1 -0
- package/dist/db/db.js +225 -0
- package/dist/db/db.js.map +1 -0
- package/dist/db/entity.d.ts +77 -0
- package/dist/db/entity.d.ts.map +1 -0
- package/dist/db/entity.js +671 -0
- package/dist/db/entity.js.map +1 -0
- package/dist/db/gridfs.d.ts +29 -0
- package/dist/db/gridfs.d.ts.map +1 -0
- package/dist/db/gridfs.js +125 -0
- package/dist/db/gridfs.js.map +1 -0
- package/dist/db/index.d.ts +8 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +8 -0
- package/dist/db/index.js.map +1 -0
- package/dist/errors/auth.d.ts +15 -0
- package/dist/errors/auth.d.ts.map +1 -0
- package/dist/errors/auth.js +21 -0
- package/dist/errors/auth.js.map +1 -0
- package/dist/errors/http.d.ts +15 -0
- package/dist/errors/http.d.ts.map +1 -0
- package/dist/errors/http.js +21 -0
- package/dist/errors/http.js.map +1 -0
- package/dist/errors/index.d.ts +18 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +18 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/validation.d.ts +11 -0
- package/dist/errors/validation.d.ts.map +1 -0
- package/dist/errors/validation.js +15 -0
- package/dist/errors/validation.js.map +1 -0
- package/dist/http/code.d.ts +21 -0
- package/dist/http/code.d.ts.map +1 -0
- package/dist/http/code.js +27 -0
- package/dist/http/code.js.map +1 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/meta/index.d.ts +9 -0
- package/dist/meta/index.d.ts.map +1 -0
- package/dist/meta/index.js +11 -0
- package/dist/meta/index.js.map +1 -0
- package/dist/meta/router.d.ts +26 -0
- package/dist/meta/router.d.ts.map +1 -0
- package/dist/meta/router.js +258 -0
- package/dist/meta/router.js.map +1 -0
- package/dist/meta/schema.d.ts +41 -0
- package/dist/meta/schema.d.ts.map +1 -0
- package/dist/meta/schema.js +69 -0
- package/dist/meta/schema.js.map +1 -0
- package/dist/plugins/auth.d.ts +248 -0
- package/dist/plugins/auth.d.ts.map +1 -0
- package/dist/plugins/auth.js +121 -0
- package/dist/plugins/auth.js.map +1 -0
- package/dist/plugins/body.d.ts +47 -0
- package/dist/plugins/body.d.ts.map +1 -0
- package/dist/plugins/body.js +36 -0
- package/dist/plugins/body.js.map +1 -0
- package/dist/plugins/cors.d.ts +62 -0
- package/dist/plugins/cors.d.ts.map +1 -0
- package/dist/plugins/cors.js +17 -0
- package/dist/plugins/cors.js.map +1 -0
- package/dist/plugins/error.d.ts +51 -0
- package/dist/plugins/error.d.ts.map +1 -0
- package/dist/plugins/error.js +51 -0
- package/dist/plugins/error.js.map +1 -0
- package/dist/plugins/index.d.ts +9 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +9 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/setting.d.ts +66 -0
- package/dist/setting.d.ts.map +1 -0
- package/dist/setting.js +27 -0
- package/dist/setting.js.map +1 -0
- package/dist/tool/gen_i18n.d.ts +10 -0
- package/dist/tool/gen_i18n.d.ts.map +1 -0
- package/{tool → dist/tool}/gen_i18n.js +9 -22
- package/dist/tool/gen_i18n.js.map +1 -0
- package/dist/tool/vector_store.d.ts +72 -0
- package/dist/tool/vector_store.d.ts.map +1 -0
- package/dist/tool/vector_store.js +203 -0
- package/dist/tool/vector_store.js.map +1 -0
- package/package.json +37 -22
- package/core/array.js +0 -124
- package/core/bash.js +0 -294
- package/core/chart.js +0 -46
- package/core/cron.js +0 -21
- package/core/date.js +0 -26
- package/core/encrypt.js +0 -26
- package/core/file.js +0 -51
- package/core/lhs.js +0 -53
- package/core/meta.js +0 -283
- package/core/msg.js +0 -24
- package/core/number.js +0 -181
- package/core/obj.js +0 -25
- package/core/random.js +0 -12
- package/core/role.js +0 -108
- package/core/thread.js +0 -13
- package/core/type.js +0 -368
- package/core/url.js +0 -30
- package/core/validate.js +0 -35
- package/db/db.js +0 -446
- package/db/entity.js +0 -920
- package/db/gridfs.js +0 -175
- package/design/add_default_field_attr.md +0 -56
- package/http/code.js +0 -18
- package/http/context.js +0 -31
- package/http/cors.js +0 -32
- package/http/error.js +0 -39
- package/http/express.js +0 -104
- package/http/params.js +0 -85
- package/http/router.js +0 -83
- package/http/session.js +0 -73
- package/index.js +0 -112
- package/router/clone.js +0 -65
- package/router/create.js +0 -54
- package/router/delete.js +0 -49
- package/router/read.js +0 -191
- package/router/update.js +0 -89
- package/setting.js +0 -67
- package/skills/array.md +0 -155
- package/skills/bash.md +0 -91
- package/skills/chart.md +0 -54
- package/skills/code.md +0 -422
- package/skills/context.md +0 -177
- package/skills/date.md +0 -58
- package/skills/express.md +0 -255
- package/skills/file.md +0 -60
- package/skills/lhs.md +0 -54
- package/skills/meta.md +0 -1023
- package/skills/msg.md +0 -30
- package/skills/number.md +0 -88
- package/skills/obj.md +0 -36
- package/skills/params.md +0 -206
- package/skills/random.md +0 -22
- package/skills/role.md +0 -59
- package/skills/session.md +0 -281
- package/skills/storage.md +0 -743
- package/skills/thread.md +0 -22
- package/skills/type.md +0 -547
- package/skills/url.md +0 -34
- package/skills/validate.md +0 -48
- package/test/cleanup/close-db.js +0 -5
- package/test/core/array.js +0 -226
- package/test/core/chart.js +0 -51
- package/test/core/date.js +0 -37
- package/test/core/encrypt.js +0 -14
- package/test/core/file.js +0 -59
- package/test/core/lhs.js +0 -44
- package/test/core/meta.js +0 -594
- package/test/core/number.js +0 -172
- package/test/core/obj.js +0 -47
- package/test/core/random.js +0 -24
- package/test/core/thread.js +0 -20
- package/test/core/type.js +0 -216
- package/test/core/validate.js +0 -67
- package/test/db/db-ops.js +0 -99
- package/test/db/db.js +0 -72
- package/test/db/pipe_test.txt +0 -0
- package/test/db/test_case_design.md +0 -528
- package/test/db/test_db_class.js +0 -613
- package/test/db/test_entity_class.js +0 -414
- package/test/db/test_gridfs_class.js +0 -234
- package/test/entity/create.js +0 -442
- package/test/entity/delete-mixed.js +0 -156
- package/test/entity/delete.js +0 -480
- package/test/entity/read.js +0 -285
- package/test/entity/ref-filter.js +0 -63
- package/test/entity/update.js +0 -252
- package/test/router/role.js +0 -15
- package/tool/test.json +0 -25
package/README.md
CHANGED
|
@@ -1,196 +1,382 @@
|
|
|
1
1
|
# Hola Server
|
|
2
2
|
|
|
3
|
-
A meta-programming framework for building
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **Meta-Driven CRUD
|
|
8
|
-
- **Role-Based Access Control
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
|
|
17
|
-
|
|
3
|
+
A meta-programming framework for building RESTful APIs with **Bun + Elysia + MongoDB**. Define entity schemas declaratively and get fully-featured CRUD APIs with built-in authentication, role-based access control, file handling, and vector search — all out of the box.
|
|
4
|
+
|
|
5
|
+
## ✨ Features
|
|
6
|
+
|
|
7
|
+
- **Meta-Driven CRUD** — Define entity schemas with metadata, get full RESTful APIs automatically
|
|
8
|
+
- **Role-Based Access Control** — Fine-grained permissions per entity and operation (`admin:*`, `user:r`, `editor:cru`)
|
|
9
|
+
- **JWT Authentication** — Access + refresh token flow with hybrid delivery (cookies & Authorization header)
|
|
10
|
+
- **Type System** — 20+ built-in types with extensible validation and conversion
|
|
11
|
+
- **Schema Validation** — Auto-generated TypeBox schemas for request body validation
|
|
12
|
+
- **File Handling** — Built-in GridFS integration for file uploads and streaming
|
|
13
|
+
- **Vector Search** — SQLite-based vector similarity search via `sqlite-vec` for AI applications
|
|
14
|
+
- **Entity Relationships** — References with cascade/keep delete behavior and ref validation
|
|
15
|
+
- **Lifecycle Hooks** — Before/after hooks for create, update, clone, delete, and read operations
|
|
16
|
+
- **Query Building** — Advanced search, filtering, comparison operators, and pagination
|
|
17
|
+
- **Environment Config** — Configuration loader with environment-specific files (dev/test/prod)
|
|
18
|
+
- **Comprehensive Testing** — 25 test files covering core, database, entity, and cleanup
|
|
19
|
+
|
|
20
|
+
## 📦 Tech Stack
|
|
21
|
+
|
|
22
|
+
| Layer | Technology |
|
|
23
|
+
|-------|-----------|
|
|
24
|
+
| Runtime | [Bun](https://bun.sh) |
|
|
25
|
+
| HTTP Framework | [Elysia](https://elysiajs.com) v1.2+ |
|
|
26
|
+
| Database | [MongoDB](https://www.mongodb.com) 6.x |
|
|
27
|
+
| File Storage | MongoDB GridFS |
|
|
28
|
+
| Vector Search | [sqlite-vec](https://github.com/asg017/sqlite-vec) + better-sqlite3 |
|
|
29
|
+
| Auth | [@elysiajs/jwt](https://www.npmjs.com/package/@elysiajs/jwt) |
|
|
30
|
+
| Language | TypeScript 5.7+ |
|
|
31
|
+
|
|
32
|
+
## 🚀 Quick Start
|
|
33
|
+
|
|
34
|
+
### Installation
|
|
18
35
|
|
|
19
36
|
```bash
|
|
20
|
-
|
|
37
|
+
bun install
|
|
21
38
|
```
|
|
22
39
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
### 1. Configure Settings
|
|
40
|
+
### 1. Define an Entity
|
|
26
41
|
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
init_settings({
|
|
31
|
-
mongo: {
|
|
32
|
-
url: "mongodb://localhost/myapp",
|
|
33
|
-
pool: 10,
|
|
34
|
-
},
|
|
35
|
-
server: {
|
|
36
|
-
service_port: 8088,
|
|
37
|
-
client_web_url: ["http://localhost:3000"],
|
|
38
|
-
session: {
|
|
39
|
-
secret: "your-secret-key",
|
|
40
|
-
cookie_max_age: 86400000, // 1 day
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
roles: [{ name: "admin", root: true }, { name: "user" }],
|
|
44
|
-
});
|
|
45
|
-
```
|
|
42
|
+
```typescript
|
|
43
|
+
import { init_router } from "hola-server";
|
|
46
44
|
|
|
47
|
-
|
|
45
|
+
export const userRouter = init_router({
|
|
46
|
+
collection: "user",
|
|
47
|
+
primary_keys: ["email"],
|
|
48
|
+
ref_label: "name",
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
creatable: true,
|
|
51
|
+
readable: true,
|
|
52
|
+
updatable: true,
|
|
53
|
+
deleteable: true,
|
|
51
54
|
|
|
52
|
-
const user_meta = {
|
|
53
|
-
collection: "user",
|
|
54
|
-
mode: "crud",
|
|
55
55
|
fields: [
|
|
56
56
|
{ name: "name", type: "string", required: true },
|
|
57
|
-
{ name: "email", type: "string", required: true
|
|
57
|
+
{ name: "email", type: "string", required: true },
|
|
58
58
|
{ name: "age", type: "uint" },
|
|
59
|
-
{ name: "role",
|
|
59
|
+
{ name: "role", ref: "role" },
|
|
60
|
+
{ name: "avatar", type: "file" },
|
|
60
61
|
],
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
|
|
63
|
+
roles: ["admin:*", "user:r", "editor:cru"],
|
|
64
|
+
});
|
|
63
65
|
```
|
|
64
66
|
|
|
65
|
-
###
|
|
67
|
+
### 2. Start the Server
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { Elysia } from "elysia";
|
|
71
|
+
import { plugins, db, meta } from "hola-server";
|
|
72
|
+
import { userRouter } from "./router/user.js";
|
|
73
|
+
|
|
74
|
+
const app = new Elysia()
|
|
75
|
+
.use(plugins.holaCors({ origin: ["http://localhost:5173"] }))
|
|
76
|
+
.use(plugins.holaBody({ limit: "10mb" }))
|
|
77
|
+
.use(plugins.holaAuth({ secret: process.env.JWT_SECRET! }))
|
|
78
|
+
.use(plugins.holaError())
|
|
79
|
+
.use(userRouter)
|
|
80
|
+
.onStart(async () => {
|
|
81
|
+
await db.get_db();
|
|
82
|
+
meta.validate_all_metas();
|
|
83
|
+
console.log("✓ Server ready on port 3000");
|
|
84
|
+
})
|
|
85
|
+
.listen(3000);
|
|
86
|
+
```
|
|
66
87
|
|
|
67
|
-
|
|
68
|
-
|
|
88
|
+
## 📡 API Endpoints
|
|
89
|
+
|
|
90
|
+
Each entity router automatically generates these endpoints:
|
|
91
|
+
|
|
92
|
+
| Method | Path | Description |
|
|
93
|
+
|--------|------|-------------|
|
|
94
|
+
| `GET` | `/{entity}` | Simple list with query params |
|
|
95
|
+
| `POST` | `/{entity}/list` | List entities (full query: filter, sort, paginate) |
|
|
96
|
+
| `GET` | `/{entity}/:id` | Get single entity by ID |
|
|
97
|
+
| `POST` | `/{entity}` | Create entity |
|
|
98
|
+
| `PUT` | `/{entity}/:id` | Update entity |
|
|
99
|
+
| `DELETE` | `/{entity}/:id` | Delete entity |
|
|
100
|
+
| `GET` | `/{entity}/meta` | Get entity metadata and permissions |
|
|
101
|
+
| `GET` | `/{entity}/ref` | Get reference labels |
|
|
102
|
+
| `POST` | `/{entity}/:id/clone` | Clone entity (if cloneable) |
|
|
103
|
+
|
|
104
|
+
## 🏗 Entity Metadata
|
|
105
|
+
|
|
106
|
+
### Field Definition
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
interface FieldDefinition {
|
|
110
|
+
name: string; // Field name
|
|
111
|
+
type?: string; // Type for validation/conversion (default: "string")
|
|
112
|
+
required?: boolean; // Required on create
|
|
113
|
+
default?: FieldValue;// Default value
|
|
114
|
+
ref?: string; // Reference to another entity (collection name)
|
|
115
|
+
link?: string; // Link to another entity
|
|
116
|
+
delete?: "keep" | "cascade"; // Ref delete behavior
|
|
117
|
+
create?: boolean; // Include in create operation
|
|
118
|
+
list?: boolean; // Include in list response
|
|
119
|
+
search?: boolean; // Enable as search field
|
|
120
|
+
update?: boolean; // Include in update operation
|
|
121
|
+
clone?: boolean; // Include in clone operation
|
|
122
|
+
sys?: boolean; // System field (auto-managed)
|
|
123
|
+
secure?: boolean; // Exclude from client responses
|
|
124
|
+
view?: string; // View-based visibility
|
|
125
|
+
role?: string | string[]; // Role-based field visibility
|
|
126
|
+
}
|
|
127
|
+
```
|
|
69
128
|
|
|
70
|
-
|
|
71
|
-
|
|
129
|
+
### Built-in Types
|
|
130
|
+
|
|
131
|
+
| Category | Types |
|
|
132
|
+
|----------|-------|
|
|
133
|
+
| **String** | `string`, `lstr`, `text`, `enum`, `email`, `url`, `ip` |
|
|
134
|
+
| **Numeric** | `number`, `int`, `uint`, `float`, `ufloat`, `decimal`, `percentage`, `currency` |
|
|
135
|
+
| **Boolean** | `boolean` |
|
|
136
|
+
| **Date/Time** | `date`, `datetime` |
|
|
137
|
+
| **Security** | `password` (bcrypt hashed), `secret` (AES-256 encrypted) |
|
|
138
|
+
| **Other** | `file`, `array`, `obj`, `json`, `log_category` |
|
|
139
|
+
|
|
140
|
+
Custom types can be registered via `register_type()`.
|
|
141
|
+
|
|
142
|
+
### Operation Flags
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
{
|
|
146
|
+
creatable: true, // Enable POST endpoint
|
|
147
|
+
readable: true, // Enable GET endpoints
|
|
148
|
+
updatable: true, // Enable PUT endpoint
|
|
149
|
+
deleteable: true, // Enable DELETE endpoint
|
|
150
|
+
cloneable: true, // Enable clone endpoint
|
|
151
|
+
importable: true, // Enable bulk import
|
|
152
|
+
}
|
|
72
153
|
```
|
|
73
154
|
|
|
74
|
-
###
|
|
155
|
+
### Role Configuration
|
|
156
|
+
|
|
157
|
+
Format: `role_name:permissions` or `role_name:permissions:view`
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
roles: [
|
|
161
|
+
"admin:*", // Admin can do everything
|
|
162
|
+
"user:r", // User can only read
|
|
163
|
+
"editor:cru", // Editor can create/read/update
|
|
164
|
+
"viewer:r:basic" // Viewer can read with "basic" view only
|
|
165
|
+
]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Lifecycle Hooks
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
init_router({
|
|
172
|
+
collection: "order",
|
|
173
|
+
// ...fields and options...
|
|
174
|
+
|
|
175
|
+
before_create: async ({ entity, data }) => { /* validate before insert */ },
|
|
176
|
+
create: async ({ entity, data }) => { /* custom create logic */ },
|
|
177
|
+
after_create: async ({ entity, data }) => { /* post-create side effects */ },
|
|
75
178
|
|
|
76
|
-
|
|
77
|
-
|
|
179
|
+
before_update: async ({ id, entity, data }) => { /* pre-update checks */ },
|
|
180
|
+
update: async ({ id, entity, data }) => { /* custom update logic */ },
|
|
181
|
+
after_update: async ({ id, entity, data }) => { /* post-update actions */ },
|
|
78
182
|
|
|
79
|
-
|
|
80
|
-
|
|
183
|
+
before_delete: async ({ entity, ids }) => { /* pre-delete validation */ },
|
|
184
|
+
after_delete: async ({ entity, ids }) => { /* cleanup after delete */ },
|
|
185
|
+
|
|
186
|
+
after_read: async ({ id, entity, result }) => { /* transform read results */ },
|
|
187
|
+
list_query: async ({ entity, query }) => { /* modify list queries */ },
|
|
188
|
+
|
|
189
|
+
route: (router, meta) => { /* add custom routes */ },
|
|
81
190
|
});
|
|
82
191
|
```
|
|
83
192
|
|
|
84
|
-
##
|
|
193
|
+
## 🔌 Plugins
|
|
85
194
|
|
|
86
|
-
|
|
195
|
+
```typescript
|
|
196
|
+
import { plugins } from "hola-server";
|
|
87
197
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
- `uint` - Unsigned integer
|
|
91
|
-
- `float` - Float with 2 decimal places
|
|
92
|
-
- `ufloat` - Unsigned float
|
|
93
|
-
- `number` - Any number
|
|
94
|
-
- `boolean` - Boolean
|
|
95
|
-
- `password` - Encrypted password
|
|
96
|
-
- `array` - Array type
|
|
97
|
-
- `obj` - Object/Reference type
|
|
98
|
-
- `file` - File upload
|
|
198
|
+
// CORS with allowed origins
|
|
199
|
+
app.use(plugins.holaCors({ origin: ["http://localhost:5173"] }));
|
|
99
200
|
|
|
100
|
-
|
|
201
|
+
// Request body parsing with size limit
|
|
202
|
+
app.use(plugins.holaBody({ limit: "10mb" }));
|
|
101
203
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
204
|
+
// JWT auth with access + refresh tokens
|
|
205
|
+
app.use(plugins.holaAuth({
|
|
206
|
+
secret: "your-secret",
|
|
207
|
+
accessExpiry: "15m", // default
|
|
208
|
+
refreshExpiry: "7d", // default
|
|
209
|
+
excludeUrls: ["/health", /^\/public/],
|
|
210
|
+
}));
|
|
109
211
|
|
|
110
|
-
|
|
212
|
+
// Auth routes (POST /auth/refresh, POST /auth/logout)
|
|
213
|
+
app.use(plugins.holaAuthRoutes());
|
|
111
214
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
- `d` - Delete
|
|
116
|
-
- `o` - Clone
|
|
117
|
-
- `crud` - All operations
|
|
215
|
+
// Global error handling
|
|
216
|
+
app.use(plugins.holaError());
|
|
217
|
+
```
|
|
118
218
|
|
|
119
|
-
|
|
219
|
+
## 🗄 Database
|
|
120
220
|
|
|
121
|
-
|
|
221
|
+
```typescript
|
|
222
|
+
import { db } from "hola-server";
|
|
122
223
|
|
|
123
|
-
|
|
224
|
+
// Direct MongoDB access
|
|
225
|
+
const database = await db.get_db();
|
|
226
|
+
const users = await database.collection("user").find({}).toArray();
|
|
124
227
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
];
|
|
228
|
+
// Entity-level operations
|
|
229
|
+
const entity = new db.Entity("user");
|
|
230
|
+
await entity.create_entity(data, "*");
|
|
231
|
+
await entity.read_entity(id, "*", "*");
|
|
232
|
+
await entity.update_entity(id, updates, "*");
|
|
233
|
+
await entity.delete_entity([id]);
|
|
234
|
+
await entity.list_entity(queryParams, query, searchParams, role);
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## 📁 File Storage (GridFS)
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import { db } from "hola-server";
|
|
241
|
+
|
|
242
|
+
// Save file from buffer (FormData uploads)
|
|
243
|
+
await db.save_file_from_buffer("avatars", "user_123.png", buffer);
|
|
244
|
+
|
|
245
|
+
// Read file
|
|
246
|
+
const fileBuffer = await db.read_file("avatars", "user_123.png");
|
|
247
|
+
|
|
248
|
+
// Stream file
|
|
249
|
+
const stream = await db.read_file_stream("avatars", "user_123.png");
|
|
250
|
+
|
|
251
|
+
// Delete file
|
|
252
|
+
await db.delete_file("avatars", "user_123.png");
|
|
131
253
|
```
|
|
132
254
|
|
|
133
|
-
##
|
|
255
|
+
## 🔍 Vector Search
|
|
134
256
|
|
|
135
|
-
|
|
257
|
+
SQLite-based vector similarity search for AI/embedding applications:
|
|
136
258
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
- `POST /{entity}/count` - Count entities
|
|
140
|
-
- `POST /{entity}/update` - Update entity
|
|
141
|
-
- `POST /{entity}/delete` - Delete entity
|
|
142
|
-
- `POST /{entity}/clone` - Clone entity (if cloneable)
|
|
259
|
+
```typescript
|
|
260
|
+
import { VectorStore, initVectorStore } from "hola-server";
|
|
143
261
|
|
|
144
|
-
|
|
262
|
+
const store = await initVectorStore({
|
|
263
|
+
dbPath: "./data/vectors.db",
|
|
264
|
+
dimensions: 1536,
|
|
265
|
+
tableName: "embeddings",
|
|
266
|
+
});
|
|
145
267
|
|
|
146
|
-
|
|
268
|
+
// Insert vectors with metadata
|
|
269
|
+
await store.insert("doc_1", embedding, { category: "article" });
|
|
270
|
+
await store.insertBatch(records);
|
|
147
271
|
|
|
148
|
-
|
|
149
|
-
const
|
|
272
|
+
// Search similar vectors
|
|
273
|
+
const results = await store.search(queryEmbedding, 10, { category: "article" });
|
|
274
|
+
// => [{ id, distance, score, metadata }]
|
|
150
275
|
|
|
151
|
-
|
|
152
|
-
|
|
276
|
+
// Manage vectors
|
|
277
|
+
await store.delete("doc_1");
|
|
278
|
+
await store.deleteByFilter({ category: "old" });
|
|
279
|
+
const count = await store.count();
|
|
153
280
|
```
|
|
154
281
|
|
|
155
|
-
|
|
282
|
+
## ⚙️ Configuration
|
|
283
|
+
|
|
284
|
+
### Environment-Based Config
|
|
156
285
|
|
|
157
|
-
```
|
|
158
|
-
|
|
286
|
+
```typescript
|
|
287
|
+
import { config } from "hola-server";
|
|
159
288
|
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
await entity.find_entity(query);
|
|
163
|
-
await entity.update_entity(id, updates);
|
|
164
|
-
await entity.delete_entity(id);
|
|
289
|
+
const appConfig = await config.load_config(__dirname + "/config");
|
|
290
|
+
// Loads config/dev.ts, config/test.ts, or config/prod.ts based on NODE_ENV
|
|
165
291
|
```
|
|
166
292
|
|
|
167
|
-
###
|
|
293
|
+
### Settings
|
|
168
294
|
|
|
169
|
-
```
|
|
170
|
-
|
|
295
|
+
```typescript
|
|
296
|
+
import { init_settings } from "hola-server";
|
|
171
297
|
|
|
172
|
-
|
|
173
|
-
|
|
298
|
+
init_settings({
|
|
299
|
+
mongo: { url: "mongodb://localhost:27017/myapp", pool: 10 },
|
|
300
|
+
encrypt: { key: "my-encryption-key" },
|
|
301
|
+
roles: [{ name: "admin", root: true }, { name: "user" }],
|
|
302
|
+
server: {
|
|
303
|
+
service_port: 8088,
|
|
304
|
+
client_web_url: ["http://localhost:5173"],
|
|
305
|
+
keep_session: true,
|
|
306
|
+
check_user: true,
|
|
307
|
+
exclude_urls: ["/"],
|
|
308
|
+
session: { cookie_max_age: 86400000, secret: "session-secret" },
|
|
309
|
+
threshold: { max_download_size: 5000, body_limit: "10mb", default_list_limit: 1000 },
|
|
310
|
+
routes: ["router"],
|
|
311
|
+
},
|
|
312
|
+
axios: { retry: 3, retry_delay: 1000, proxy: null },
|
|
313
|
+
log: { col_log: "log", log_level: 0, save_db: true },
|
|
314
|
+
});
|
|
174
315
|
```
|
|
175
316
|
|
|
176
|
-
##
|
|
317
|
+
## 🧰 Core Utilities
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
import { array, date, validate, encrypt, random, bash, file, obj, number, lhs } from "hola-server";
|
|
321
|
+
|
|
322
|
+
// Array utilities
|
|
323
|
+
array.unique([1, 2, 2, 3]); // [1, 2, 3]
|
|
324
|
+
|
|
325
|
+
// Encryption
|
|
326
|
+
const hashed = encrypt_pwd("password");
|
|
327
|
+
const encrypted = encrypt_secret("api-key");
|
|
328
|
+
const decrypted = decrypt_secret(encrypted);
|
|
329
|
+
|
|
330
|
+
// And more: date formatting, validation, bash execution, file I/O, etc.
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## 🧪 Testing
|
|
177
334
|
|
|
178
335
|
```bash
|
|
179
|
-
|
|
336
|
+
bun test
|
|
180
337
|
```
|
|
181
338
|
|
|
182
|
-
|
|
339
|
+
25 test files covering:
|
|
340
|
+
- **Core**: array, chart, date, encrypt, file, lhs, meta, number, obj, random, thread, type, validate
|
|
341
|
+
- **Database**: connection, operations, entity class, GridFS
|
|
342
|
+
- **Entity CRUD**: create, read, update, delete, clone, ref-filter
|
|
343
|
+
|
|
344
|
+
## 📂 Project Structure
|
|
183
345
|
|
|
184
346
|
```
|
|
185
347
|
hola-server/
|
|
186
|
-
├──
|
|
187
|
-
├──
|
|
188
|
-
├──
|
|
189
|
-
├──
|
|
190
|
-
├──
|
|
191
|
-
|
|
348
|
+
├── src/
|
|
349
|
+
│ ├── index.ts # Public API entry point
|
|
350
|
+
│ ├── setting.ts # Application settings
|
|
351
|
+
│ ├── config/ # Environment-based config loader
|
|
352
|
+
│ ├── core/ # Core utilities (16 modules)
|
|
353
|
+
│ │ ├── meta.ts # Entity metadata & lifecycle hooks
|
|
354
|
+
│ │ ├── type.ts # Type system & validation
|
|
355
|
+
│ │ ├── role.ts # Role-based access control
|
|
356
|
+
│ │ ├── encrypt.ts # Password hashing & AES encryption
|
|
357
|
+
│ │ └── ... # array, bash, chart, date, file, etc.
|
|
358
|
+
│ ├── db/ # Database layer
|
|
359
|
+
│ │ ├── db.ts # MongoDB connection & helpers
|
|
360
|
+
│ │ ├── entity.ts # Entity CRUD operations
|
|
361
|
+
│ │ └── gridfs.ts # GridFS file storage
|
|
362
|
+
│ ├── meta/ # Meta programming
|
|
363
|
+
│ │ ├── router.ts # Auto RESTful route generation
|
|
364
|
+
│ │ └── schema.ts # TypeBox schema generation
|
|
365
|
+
│ ├── plugins/ # Elysia plugins
|
|
366
|
+
│ │ ├── auth.ts # JWT authentication
|
|
367
|
+
│ │ ├── cors.ts # CORS configuration
|
|
368
|
+
│ │ ├── body.ts # Body parser
|
|
369
|
+
│ │ └── error.ts # Error handling
|
|
370
|
+
│ ├── errors/ # Error classes (auth, http, validation)
|
|
371
|
+
│ ├── http/ # HTTP status codes
|
|
372
|
+
│ └── tool/ # Tools
|
|
373
|
+
│ ├── gen_i18n.ts # i18n key generation
|
|
374
|
+
│ └── vector_store.ts # Vector similarity search
|
|
375
|
+
├── test/ # Test suite (25 test files)
|
|
376
|
+
├── package.json
|
|
377
|
+
└── tsconfig.json
|
|
192
378
|
```
|
|
193
379
|
|
|
194
|
-
## License
|
|
380
|
+
## 📄 License
|
|
195
381
|
|
|
196
382
|
ISC
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration loader for environment-based settings.
|
|
3
|
+
* @module config
|
|
4
|
+
*/
|
|
5
|
+
export interface AppConfig {
|
|
6
|
+
/** Server port */
|
|
7
|
+
port: number;
|
|
8
|
+
/** JWT configuration */
|
|
9
|
+
jwt: {
|
|
10
|
+
secret: string;
|
|
11
|
+
accessExpiry: string;
|
|
12
|
+
refreshExpiry: string;
|
|
13
|
+
};
|
|
14
|
+
/** CORS configuration */
|
|
15
|
+
cors: {
|
|
16
|
+
origin: string[] | true;
|
|
17
|
+
credentials?: boolean;
|
|
18
|
+
};
|
|
19
|
+
/** Body parser configuration */
|
|
20
|
+
body: {
|
|
21
|
+
limit: string | number;
|
|
22
|
+
};
|
|
23
|
+
/** Database configuration */
|
|
24
|
+
db: {
|
|
25
|
+
url: string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Load configuration from environment-specific file.
|
|
30
|
+
* Looks for config files in the specified directory based on NODE_ENV.
|
|
31
|
+
*
|
|
32
|
+
* @param config_dir Directory containing config files (dev.ts, test.ts, prod.ts)
|
|
33
|
+
* @returns Promise resolving to AppConfig
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* const config = await load_config(__dirname + '/config');
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare const load_config: (config_dir: string) => Promise<AppConfig>;
|
|
41
|
+
/**
|
|
42
|
+
* Create a base config with common defaults.
|
|
43
|
+
* Use this as a starting point for environment configs.
|
|
44
|
+
*/
|
|
45
|
+
export declare const create_base_config: (overrides?: Partial<AppConfig>) => AppConfig;
|
|
46
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,SAAS;IACtB,kBAAkB;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,GAAG,EAAE;QACD,MAAM,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,yBAAyB;IACzB,IAAI,EAAE;QACF,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QACxB,WAAW,CAAC,EAAE,OAAO,CAAC;KACzB,CAAC;IACF,gCAAgC;IAChC,IAAI,EAAE;QACF,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;KAC1B,CAAC;IACF,6BAA6B;IAC7B,EAAE,EAAE;QACA,GAAG,EAAE,MAAM,CAAC;KACf,CAAC;CACL;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW,GAAU,YAAY,MAAM,KAAG,OAAO,CAAC,SAAS,CAUvE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,YAAW,OAAO,CAAC,SAAS,CAAM,KAAG,SAsBtE,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration loader for environment-based settings.
|
|
3
|
+
* @module config
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Load configuration from environment-specific file.
|
|
7
|
+
* Looks for config files in the specified directory based on NODE_ENV.
|
|
8
|
+
*
|
|
9
|
+
* @param config_dir Directory containing config files (dev.ts, test.ts, prod.ts)
|
|
10
|
+
* @returns Promise resolving to AppConfig
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const config = await load_config(__dirname + '/config');
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export const load_config = async (config_dir) => {
|
|
18
|
+
const env = process.env.NODE_ENV || 'dev';
|
|
19
|
+
const config_path = `${config_dir}/${env}`;
|
|
20
|
+
try {
|
|
21
|
+
const module = await import(config_path);
|
|
22
|
+
return module.default;
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
throw new Error(`Failed to load config for environment '${env}' from ${config_path}: ${error}`);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Create a base config with common defaults.
|
|
30
|
+
* Use this as a starting point for environment configs.
|
|
31
|
+
*/
|
|
32
|
+
export const create_base_config = (overrides = {}) => ({
|
|
33
|
+
port: 3000,
|
|
34
|
+
jwt: {
|
|
35
|
+
secret: 'change-this-in-production',
|
|
36
|
+
accessExpiry: '15m',
|
|
37
|
+
refreshExpiry: '7d',
|
|
38
|
+
...overrides.jwt
|
|
39
|
+
},
|
|
40
|
+
cors: {
|
|
41
|
+
origin: ['http://localhost:5173'],
|
|
42
|
+
credentials: true,
|
|
43
|
+
...overrides.cors
|
|
44
|
+
},
|
|
45
|
+
body: {
|
|
46
|
+
limit: '10mb',
|
|
47
|
+
...overrides.body
|
|
48
|
+
},
|
|
49
|
+
db: {
|
|
50
|
+
url: 'mongodb://localhost:27017/hola',
|
|
51
|
+
...overrides.db
|
|
52
|
+
},
|
|
53
|
+
...overrides
|
|
54
|
+
});
|
|
55
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA0BH;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAAE,UAAkB,EAAsB,EAAE;IACxE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,KAAK,CAAC;IAC1C,MAAM,WAAW,GAAG,GAAG,UAAU,IAAI,GAAG,EAAE,CAAC;IAE3C,IAAI,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;QACzC,OAAO,MAAM,CAAC,OAAoB,CAAC;IACvC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,0CAA0C,GAAG,UAAU,WAAW,KAAK,KAAK,EAAE,CAAC,CAAC;IACpG,CAAC;AACL,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,YAAgC,EAAE,EAAa,EAAE,CAAC,CAAC;IAClF,IAAI,EAAE,IAAI;IACV,GAAG,EAAE;QACD,MAAM,EAAE,2BAA2B;QACnC,YAAY,EAAE,KAAK;QACnB,aAAa,EAAE,IAAI;QACnB,GAAG,SAAS,CAAC,GAAG;KACnB;IACD,IAAI,EAAE;QACF,MAAM,EAAE,CAAC,uBAAuB,CAAC;QACjC,WAAW,EAAE,IAAI;QACjB,GAAG,SAAS,CAAC,IAAI;KACpB;IACD,IAAI,EAAE;QACF,KAAK,EAAE,MAAM;QACb,GAAG,SAAS,CAAC,IAAI;KACpB;IACD,EAAE,EAAE;QACA,GAAG,EAAE,gCAAgC;QACrC,GAAG,SAAS,CAAC,EAAE;KAClB;IACD,GAAG,SAAS;CACf,CAAC,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Array manipulation utility functions.
|
|
3
|
+
* @module core/array
|
|
4
|
+
*/
|
|
5
|
+
/** Shuffle array elements randomly in place. */
|
|
6
|
+
export declare const shuffle: <T>(arr: T[]) => void;
|
|
7
|
+
/** Remove elements from array by matching field value. */
|
|
8
|
+
export declare const remove_element: <T, K extends keyof T>(array: T[], field: K, value: T[K]) => void;
|
|
9
|
+
export declare const pop_n: <T>(array: T[], n: number) => T[] | undefined;
|
|
10
|
+
export declare const shift_n: <T>(array: T[], n: number) => T[] | undefined;
|
|
11
|
+
/** Calculate sum of number array. */
|
|
12
|
+
export declare const sum: (arr: number[]) => number;
|
|
13
|
+
/** Calculate average of number array. */
|
|
14
|
+
export declare const avg: (arr: number[]) => number;
|
|
15
|
+
/** Convert array of objects to key-value object. */
|
|
16
|
+
export declare const map_array_to_obj: <T, K extends keyof T, V extends keyof T>(arr: T[], key_attr: K, value_attr: V) => Record<string, T[V]>;
|
|
17
|
+
/** Sort array of objects by attribute. */
|
|
18
|
+
export declare const sort_by_attr: <T extends Record<string, number>>(arr: T[], attr: keyof T, desc?: boolean) => T[];
|
|
19
|
+
export declare const sort_desc: <T extends Record<string, number>>(arr: T[], attr: keyof T) => T[];
|
|
20
|
+
export declare const sort_asc: <T extends Record<string, number>>(arr: T[], attr: keyof T) => T[];
|
|
21
|
+
/** Sort array by predefined key sequence. */
|
|
22
|
+
export declare const sort_by_key_seq: <T, K extends keyof T>(arr: T[], attr: K, keys: T[K][]) => T[];
|
|
23
|
+
/** Create cartesian product of two object arrays. */
|
|
24
|
+
export declare const combine: <T extends object, U extends object>(arr1: T[], arr2: U[]) => (T & U)[];
|
|
25
|
+
/** Remove duplicate elements from array. */
|
|
26
|
+
export declare const unique: <T>(array: T[]) => T[];
|
|
27
|
+
//# sourceMappingURL=array.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"array.d.ts","sourceRoot":"","sources":["../../src/core/array.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,gDAAgD;AAChD,eAAO,MAAM,OAAO,GAAI,CAAC,EAAE,KAAK,CAAC,EAAE,KAAG,IAKrC,CAAC;AAEF,0DAA0D;AAC1D,eAAO,MAAM,cAAc,GAAI,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,KAAG,IAIxF,CAAC;AAaF,eAAO,MAAM,KAAK,GAAI,CAAC,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,MAAM,KAAG,CAAC,EAAE,GAAG,SAAuC,CAAC;AAC/F,eAAO,MAAM,OAAO,GAAI,CAAC,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,MAAM,KAAG,CAAC,EAAE,GAAG,SAAyC,CAAC;AAEnG,qCAAqC;AACrC,eAAO,MAAM,GAAG,GAAI,KAAK,MAAM,EAAE,KAAG,MAAiE,CAAC;AAEtG,yCAAyC;AACzC,eAAO,MAAM,GAAG,GAAI,KAAK,MAAM,EAAE,KAAG,MAAuE,CAAC;AAE5G,oDAAoD;AACpD,eAAO,MAAM,gBAAgB,GAAI,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,UAAU,CAAC,EAAE,YAAY,CAAC,KAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAEnI,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,YAAY,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,MAAM,MAAM,CAAC,EAAE,OAAM,OAAe,KAAG,CAAC,EAGhH,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,MAAM,MAAM,CAAC,KAAG,CAAC,EAAmC,CAAC;AAC3H,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,MAAM,MAAM,CAAC,KAAG,CAAC,EAAoC,CAAC;AAE3H,6CAA6C;AAC7C,eAAO,MAAM,eAAe,GAAI,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,KAAG,CAAC,EAGxF,CAAC;AAEF,qDAAqD;AACrD,eAAO,MAAM,OAAO,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,KAAG,CAAC,CAAC,GAAG,CAAC,CAAC,EAEzF,CAAC;AAEF,4CAA4C;AAC5C,eAAO,MAAM,MAAM,GAAI,CAAC,EAAE,OAAO,CAAC,EAAE,KAAG,CAAC,EAMvC,CAAC"}
|