@shadow-dev/orm 1.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/.eslintrc.json +21 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +37 -0
- package/.github/dependabot.yml +34 -0
- package/.github/workflows/pr-checks.yml +139 -0
- package/.github/workflows/publish.yml +31 -0
- package/.github/workflows/release-please.yml +23 -0
- package/.github/workflows/setup-project.yml +31 -0
- package/.github/workflows/sonarcloud-analysis +26 -0
- package/LICENSE +1 -0
- package/README.md +118 -0
- package/eslint.config.mjs +39 -0
- package/jest.config.js +5 -0
- package/package.json +52 -0
- package/src/core/Database.ts +24 -0
- package/src/core/Model.ts +18 -0
- package/src/core/Repository.ts +81 -0
- package/src/core/index.ts +3 -0
- package/src/index.ts +2 -0
- package/src/utils/getNextId.ts +24 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/syncSchema.ts +37 -0
- package/tests/sample.test.ts +5 -0
- package/tsconfig.json +13 -0
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"root": true,
|
|
3
|
+
"parser": "@typescript-eslint/parser",
|
|
4
|
+
"plugins": ["@typescript-eslint"],
|
|
5
|
+
"extends": [
|
|
6
|
+
"eslint:recommended",
|
|
7
|
+
"plugin:@typescript-eslint/recommended"
|
|
8
|
+
],
|
|
9
|
+
"env": {
|
|
10
|
+
"node": true,
|
|
11
|
+
"es2021": true
|
|
12
|
+
},
|
|
13
|
+
"parserOptions": {
|
|
14
|
+
"ecmaVersion": 2021,
|
|
15
|
+
"sourceType": "module"
|
|
16
|
+
},
|
|
17
|
+
"rules": {
|
|
18
|
+
"@typescript-eslint/no-unused-vars": ["warn"],
|
|
19
|
+
"@typescript-eslint/no-explicit-any": "off"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
## 📝 Description
|
|
2
|
+
<!-- Describe your changes in detail. Explain the problem and how your PR fixes it. -->
|
|
3
|
+
|
|
4
|
+
## 🔍 Related Issues
|
|
5
|
+
<!-- Link any relevant issues (e.g., "Closes #123") -->
|
|
6
|
+
|
|
7
|
+
## 🚀 Changes Made
|
|
8
|
+
- [ ] Bug fix 🐛
|
|
9
|
+
- [ ] New feature ✨
|
|
10
|
+
- [ ] Code refactor 🔧
|
|
11
|
+
- [ ] Documentation update 📖
|
|
12
|
+
- [ ] Security improvement 🔒
|
|
13
|
+
- [ ] Other (please specify): ____________
|
|
14
|
+
|
|
15
|
+
## ✅ Testing Steps
|
|
16
|
+
<!-- Provide instructions for testing your changes. Include commands, expected behavior, and test cases if applicable. -->
|
|
17
|
+
1.
|
|
18
|
+
2.
|
|
19
|
+
3.
|
|
20
|
+
|
|
21
|
+
## 🔒 Security Considerations
|
|
22
|
+
<!-- Any security implications? Dependencies updated? New permissions required? -->
|
|
23
|
+
|
|
24
|
+
## 📸 Screenshots / Logs (if applicable)
|
|
25
|
+
<!-- Add screenshots or logs if they help understand the change. -->
|
|
26
|
+
|
|
27
|
+
## ⏳ Checklist
|
|
28
|
+
- [ ] My code follows the project coding style.
|
|
29
|
+
- [ ] I have run tests to verify my changes.
|
|
30
|
+
- [ ] I have updated the documentation if needed.
|
|
31
|
+
- [ ] My changes do not introduce new vulnerabilities.
|
|
32
|
+
- [ ] This PR is ready for review.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
### 🚀 Additional Notes
|
|
37
|
+
<!-- Anything else reviewers should know? -->
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# To get started with Dependabot version updates, you'll need to specify which
|
|
2
|
+
# package ecosystems to update and where the package manifests are located.
|
|
3
|
+
# Please see the documentation for all configuration options:
|
|
4
|
+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
|
5
|
+
|
|
6
|
+
version: 2
|
|
7
|
+
updates:
|
|
8
|
+
# 📦 Keep npm dependencies up to date
|
|
9
|
+
- package-ecosystem: "npm"
|
|
10
|
+
directory: "/" # Root directory (where package.json is)
|
|
11
|
+
schedule:
|
|
12
|
+
interval: "daily"
|
|
13
|
+
open-pull-requests-limit: 5
|
|
14
|
+
versioning-strategy: increase
|
|
15
|
+
labels:
|
|
16
|
+
- "dependencies"
|
|
17
|
+
- "npm"
|
|
18
|
+
reviewers:
|
|
19
|
+
- "Shadows-Development" # Add yourself or contributors as reviewers
|
|
20
|
+
commit-message:
|
|
21
|
+
prefix: "⬆️ Bump"
|
|
22
|
+
include: "scope" # Includes dependency name in commit message
|
|
23
|
+
|
|
24
|
+
# 🔒 Check for security updates in GitHub Actions (workflow dependencies)
|
|
25
|
+
- package-ecosystem: "github-actions"
|
|
26
|
+
directory: "/" # Root directory (where workflows are)
|
|
27
|
+
schedule:
|
|
28
|
+
interval: "daily"
|
|
29
|
+
labels:
|
|
30
|
+
- "security"
|
|
31
|
+
- "github-actions"
|
|
32
|
+
commit-message:
|
|
33
|
+
prefix: "🔄 Update"
|
|
34
|
+
include: "scope"
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
name: PR Checks
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request: # Runs on every commit in an open PR
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
build:
|
|
8
|
+
name: 🏗 Build & Type Check
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- name: Checkout repository
|
|
12
|
+
uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- name: Setup Node.js
|
|
15
|
+
uses: actions/setup-node@v4
|
|
16
|
+
with:
|
|
17
|
+
node-version: 20
|
|
18
|
+
cache: 'npm' # Speeds up installs
|
|
19
|
+
|
|
20
|
+
- name: Restore dependency cache
|
|
21
|
+
uses: actions/cache@v4
|
|
22
|
+
with:
|
|
23
|
+
path: ~/.npm
|
|
24
|
+
key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
|
|
25
|
+
restore-keys: |
|
|
26
|
+
npm-${{ runner.os }}-
|
|
27
|
+
|
|
28
|
+
- name: Install dependencies
|
|
29
|
+
run: |
|
|
30
|
+
if [ -f package-lock.json ]; then
|
|
31
|
+
npm ci
|
|
32
|
+
else
|
|
33
|
+
npm install
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
- name: TypeScript type checking
|
|
37
|
+
run: npm run type-check
|
|
38
|
+
|
|
39
|
+
- name: Build project
|
|
40
|
+
run: npm run build
|
|
41
|
+
|
|
42
|
+
lint:
|
|
43
|
+
name: 🎨 Lint Code
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
needs: build # Runs only if "build" passes
|
|
46
|
+
steps:
|
|
47
|
+
- name: Checkout repository
|
|
48
|
+
uses: actions/checkout@v4
|
|
49
|
+
|
|
50
|
+
- name: Setup Node.js
|
|
51
|
+
uses: actions/setup-node@v4
|
|
52
|
+
with:
|
|
53
|
+
node-version: 20
|
|
54
|
+
cache: 'npm'
|
|
55
|
+
|
|
56
|
+
- name: Restore dependency cache
|
|
57
|
+
uses: actions/cache@v4
|
|
58
|
+
with:
|
|
59
|
+
path: ~/.npm
|
|
60
|
+
key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
|
|
61
|
+
restore-keys: |
|
|
62
|
+
npm-${{ runner.os }}-
|
|
63
|
+
|
|
64
|
+
- name: Install dependencies
|
|
65
|
+
run: |
|
|
66
|
+
if [ -f package-lock.json ]; then
|
|
67
|
+
npm ci
|
|
68
|
+
else
|
|
69
|
+
npm install
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
- name: Run ESLint
|
|
73
|
+
run: npm run lint
|
|
74
|
+
|
|
75
|
+
security:
|
|
76
|
+
name: 🔒 Security Audit
|
|
77
|
+
runs-on: ubuntu-latest
|
|
78
|
+
needs: lint # Runs only if "lint" passes
|
|
79
|
+
steps:
|
|
80
|
+
- name: Checkout repository
|
|
81
|
+
uses: actions/checkout@v4
|
|
82
|
+
|
|
83
|
+
- name: Setup Node.js
|
|
84
|
+
uses: actions/setup-node@v4
|
|
85
|
+
with:
|
|
86
|
+
node-version: 20
|
|
87
|
+
cache: 'npm'
|
|
88
|
+
|
|
89
|
+
- name: Restore dependency cache
|
|
90
|
+
uses: actions/cache@v4
|
|
91
|
+
with:
|
|
92
|
+
path: ~/.npm
|
|
93
|
+
key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
|
|
94
|
+
restore-keys: |
|
|
95
|
+
npm-${{ runner.os }}-
|
|
96
|
+
|
|
97
|
+
- name: Install dependencies
|
|
98
|
+
run: |
|
|
99
|
+
if [ -f package-lock.json ]; then
|
|
100
|
+
npm ci
|
|
101
|
+
else
|
|
102
|
+
npm install
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
- name: Run npm audit
|
|
106
|
+
run: npm audit --audit-level=high || exit 1
|
|
107
|
+
|
|
108
|
+
tests:
|
|
109
|
+
name: 🧪 Run Tests
|
|
110
|
+
runs-on: ubuntu-latest
|
|
111
|
+
needs: security # Runs only if "security" passes
|
|
112
|
+
steps:
|
|
113
|
+
- name: Checkout repository
|
|
114
|
+
uses: actions/checkout@v4
|
|
115
|
+
|
|
116
|
+
- name: Setup Node.js
|
|
117
|
+
uses: actions/setup-node@v4
|
|
118
|
+
with:
|
|
119
|
+
node-version: 20
|
|
120
|
+
cache: 'npm'
|
|
121
|
+
|
|
122
|
+
- name: Restore dependency cache
|
|
123
|
+
uses: actions/cache@v4
|
|
124
|
+
with:
|
|
125
|
+
path: ~/.npm
|
|
126
|
+
key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
|
|
127
|
+
restore-keys: |
|
|
128
|
+
npm-${{ runner.os }}-
|
|
129
|
+
|
|
130
|
+
- name: Install dependencies
|
|
131
|
+
run: |
|
|
132
|
+
if [ -f package-lock.json ]; then
|
|
133
|
+
npm ci
|
|
134
|
+
else
|
|
135
|
+
npm install
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
- name: Run tests
|
|
139
|
+
run: npm test
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*" # Triggers when you push a tag like "v1.0.0"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout Repository
|
|
14
|
+
uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Setup Node.js
|
|
17
|
+
uses: actions/setup-node@v4
|
|
18
|
+
with:
|
|
19
|
+
node-version: 18
|
|
20
|
+
registry-url: "https://registry.npmjs.org/"
|
|
21
|
+
|
|
22
|
+
- name: Install Dependencies
|
|
23
|
+
run: npm install
|
|
24
|
+
|
|
25
|
+
- name: Build Package
|
|
26
|
+
run: npm run build # Skip if no build step
|
|
27
|
+
|
|
28
|
+
- name: Publish to npm
|
|
29
|
+
run: npm publish --access public
|
|
30
|
+
env:
|
|
31
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: Release Please
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
pull-requests: write
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
release:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout Repo
|
|
18
|
+
uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Run Release Please
|
|
21
|
+
uses: googleapis/release-please-action@v4
|
|
22
|
+
with:
|
|
23
|
+
release-type: node
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Setup Project
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
contents: write
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
setup:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout code
|
|
14
|
+
uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Setup Node.js
|
|
17
|
+
uses: actions/setup-node@v4
|
|
18
|
+
with:
|
|
19
|
+
node-version: 18
|
|
20
|
+
|
|
21
|
+
- name: Run setup script
|
|
22
|
+
run: node scripts/setup.js
|
|
23
|
+
|
|
24
|
+
- name: Push updated files
|
|
25
|
+
run: |
|
|
26
|
+
git config user.name "ShadowBot"
|
|
27
|
+
git config user.email "bot@shadowdev.local"
|
|
28
|
+
git remote set-url origin https://x-access-token:${{ secrets.PAT_TOKEN }}@github.com/${{ github.repository }}
|
|
29
|
+
git add .
|
|
30
|
+
git commit -m "chore: initialize project metadata from setup script" || echo "No changes"
|
|
31
|
+
git push
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: SonarCloud Analysis
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches:
|
|
5
|
+
- main
|
|
6
|
+
pull_request:
|
|
7
|
+
branches:
|
|
8
|
+
- main
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
sonarcloud:
|
|
12
|
+
name: SonarCloud Scan
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout code
|
|
17
|
+
uses: actions/checkout@v2
|
|
18
|
+
|
|
19
|
+
- name: Set up SonarCloud
|
|
20
|
+
uses: SonarSource/sonarcloud-github-action@v1.1
|
|
21
|
+
|
|
22
|
+
- name: Run SonarCloud analysis
|
|
23
|
+
env:
|
|
24
|
+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
25
|
+
run: |
|
|
26
|
+
sonar-scanner
|
package/LICENSE
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
MIT License © Shadows Development
|
package/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# ShadowORM
|
|
2
|
+
|
|
3
|
+
> 🧩 Lightweight, type-safe MySQL ORM for ShadowCore projects.
|
|
4
|
+
> ShadowORM is built for **modularity**, **security**, and **runtime schema sync** ΓÇö perfect for bots, services, or web apps using ShadowCore.
|
|
5
|
+
|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 🔍 Overview
|
|
12
|
+
|
|
13
|
+
ShadowORM is a minimalist ORM that offers:
|
|
14
|
+
|
|
15
|
+
- ✅ **Type-safe models** using generics
|
|
16
|
+
- ✅ **Automatic schema synchronization** (no migration needed)
|
|
17
|
+
- ✅ **JSON + Date normalization**
|
|
18
|
+
- ✅ **Relational support** with foreign keys
|
|
19
|
+
- ✅ **No decorators, no reflection, no magic**
|
|
20
|
+
|
|
21
|
+
ItΓÇÖs designed to work cleanly alongside ShadowCore but can also be used standalone in any Node.js TypeScript project.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 📦 Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @shadow-dev/orm mysql2
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 🛠 Usage Example
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { Model, Repository, initDatabase, registerModel } from "@shadow-dev/orm";
|
|
37
|
+
|
|
38
|
+
const Ticket = new Model<{
|
|
39
|
+
id: string;
|
|
40
|
+
type: "support" | "report";
|
|
41
|
+
data: { message: string };
|
|
42
|
+
createdAt: Date;
|
|
43
|
+
}>("tickets", {
|
|
44
|
+
id: "string",
|
|
45
|
+
type: "string",
|
|
46
|
+
data: "json",
|
|
47
|
+
createdAt: "datetime"
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
initDatabase({
|
|
51
|
+
host: "localhost",
|
|
52
|
+
user: "root",
|
|
53
|
+
password: "password",
|
|
54
|
+
database: "mydb"
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
registerModel(Ticket);
|
|
58
|
+
|
|
59
|
+
// Auto-create table on startup
|
|
60
|
+
await syncSchema();
|
|
61
|
+
|
|
62
|
+
const tickets = new Repository(Ticket);
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
// Use it
|
|
66
|
+
await tickets.create({
|
|
67
|
+
id: "ticket-001",
|
|
68
|
+
type: "support",
|
|
69
|
+
data: { message: "Help me!" },
|
|
70
|
+
createdAt: new Date()
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 🧠 Schema Types
|
|
77
|
+
|
|
78
|
+
ShadowORM supports:
|
|
79
|
+
|
|
80
|
+
| Type | SQL Equivalent |
|
|
81
|
+
|---------------------------|-------------------|
|
|
82
|
+
| `string` | `VARCHAR(255)` |
|
|
83
|
+
| `number` | `INT` |
|
|
84
|
+
| `boolean` | `BOOLEAN` |
|
|
85
|
+
| `json` | `LONGTEXT` |
|
|
86
|
+
| `datetime` | `DATETIME` |
|
|
87
|
+
| `FOREIGN_KEY:<tbl.col>` | Foreign key ref |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 🧱 Roadmap
|
|
92
|
+
|
|
93
|
+
- [x] CRUD repository
|
|
94
|
+
- [x] Relational schema support
|
|
95
|
+
- [x] Automatic schema sync
|
|
96
|
+
- [ ] CLI (optional)
|
|
97
|
+
- [ ] Migrations (optional)
|
|
98
|
+
- [ ] Postgres support (maybe)
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 📖 Documentation
|
|
103
|
+
|
|
104
|
+
📚 Docs are coming soon and will be available on the ShadowCore documentation site:
|
|
105
|
+
➡️ [docs.shadowdevelopment.net](https://docs.shadowdevelopment.net)
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 🏢 Project Ownership
|
|
110
|
+
|
|
111
|
+
ShadowORM is officially developed and maintained under [Shadow Development LLC](https://shadowdevelopment.net).
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 📜 License
|
|
116
|
+
|
|
117
|
+
Licensed under the **GNU General Public License v3.0**
|
|
118
|
+
See the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
import tseslint from "typescript-eslint";
|
|
4
|
+
import json from "@eslint/json";
|
|
5
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
// export default defineConfig([
|
|
9
|
+
// { files: ["**/*.{js,mjs,cjs,ts}"], plugins: { js }, extends: ["js/recommended"] },
|
|
10
|
+
// { files: ["**/*.js"], languageOptions: { sourceType: "script" } },
|
|
11
|
+
// { files: ["**/*.{js,mjs,cjs,ts}"], languageOptions: { globals: {...globals.browser, ...globals.node} } },
|
|
12
|
+
// tseslint.configs.recommended,
|
|
13
|
+
// ]);
|
|
14
|
+
export default defineConfig([
|
|
15
|
+
{ files: ["**/*.{js,mjs,cjs,ts}"] },
|
|
16
|
+
{
|
|
17
|
+
files: ["**/*.{js,mjs,cjs,ts}"],
|
|
18
|
+
languageOptions: { globals: { ...globals.browser, ...globals.node } },
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
files: ["**/*.{js,mjs,cjs,ts}"],
|
|
22
|
+
plugins: { js },
|
|
23
|
+
extends: ["js/recommended"],
|
|
24
|
+
},
|
|
25
|
+
{ files: ["**/*.json"], plugins: { json }, language: "json/json", extends: ["json/recommended"] },
|
|
26
|
+
tseslint.configs.recommended,
|
|
27
|
+
{
|
|
28
|
+
rules: {
|
|
29
|
+
"@typescript-eslint/explicit-module-boundary-types": "off",
|
|
30
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
31
|
+
"@typescript-eslint/no-require-imports": "off",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
globalIgnores([
|
|
35
|
+
"node_modules/*", // ignore its content
|
|
36
|
+
"dist/*",
|
|
37
|
+
"package-lock.json",
|
|
38
|
+
]),
|
|
39
|
+
]);
|
package/jest.config.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shadow-dev/orm",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Lightweight dynamic MySQL ORM designed for ShadowCore and modular apps.",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"require": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"dev": "ts-node src/index.ts",
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"lint": "eslint . --ext .ts",
|
|
18
|
+
"type-check": "tsc --noEmit",
|
|
19
|
+
"setup": "node scripts/setup.js",
|
|
20
|
+
"test": "jest"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/Shadows-Development/ShadowORM.git"
|
|
25
|
+
},
|
|
26
|
+
"author": "Shadow Development LLC",
|
|
27
|
+
"license": "LGPL-3.0-or-later",
|
|
28
|
+
"keywords": [
|
|
29
|
+
"orm",
|
|
30
|
+
"mysql",
|
|
31
|
+
"typescript",
|
|
32
|
+
"lightweight",
|
|
33
|
+
"shadowcore",
|
|
34
|
+
"shadow-dev"
|
|
35
|
+
],
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@eslint/json": "^0.13.1",
|
|
38
|
+
"@types/jest": "^30.0.0",
|
|
39
|
+
"@types/node": "^24.1.0",
|
|
40
|
+
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
|
41
|
+
"@typescript-eslint/parser": "^8.38.0",
|
|
42
|
+
"eslint": "^9.32.0",
|
|
43
|
+
"jest": "^30.0.5",
|
|
44
|
+
"ts-jest": "^29.4.0",
|
|
45
|
+
"ts-node": "^10.9.2",
|
|
46
|
+
"typescript": "^5.8.3",
|
|
47
|
+
"typescript-eslint": "^8.38.0"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"mysql2": "^3.14.3"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Database.ts
|
|
2
|
+
import mysql from "mysql2/promise";
|
|
3
|
+
import { Model } from "./Model";
|
|
4
|
+
|
|
5
|
+
let pool: mysql.Pool;
|
|
6
|
+
const modelRegistry = new Map<string, Model<any>>();
|
|
7
|
+
|
|
8
|
+
export function initDatabase(config: mysql.PoolOptions) {
|
|
9
|
+
pool = mysql.createPool(config);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getPool() {
|
|
13
|
+
if (!pool) throw new Error("Database not initialized");
|
|
14
|
+
return pool;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// @ts-expect-error wierd generic errors
|
|
18
|
+
export function registerModel<T>(model: Model<T>) {
|
|
19
|
+
modelRegistry.set(model.name, model);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getAllModels() {
|
|
23
|
+
return modelRegistry;
|
|
24
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface BaseSchema {
|
|
2
|
+
id: string;
|
|
3
|
+
data?: any;
|
|
4
|
+
createdAt: Date;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface ForeignKeyDefinition {
|
|
8
|
+
column: string;
|
|
9
|
+
reference: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class Model<T extends Partial<BaseSchema> = BaseSchema> {
|
|
13
|
+
constructor(
|
|
14
|
+
public readonly name: string,
|
|
15
|
+
public readonly schema: Record<keyof T, string>,
|
|
16
|
+
public readonly foreignKeys: ForeignKeyDefinition[] = []
|
|
17
|
+
) {}
|
|
18
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Repository.ts
|
|
2
|
+
import { getPool } from "./Database";
|
|
3
|
+
import { Model } from "./Model";
|
|
4
|
+
|
|
5
|
+
export class Repository<T extends object> {
|
|
6
|
+
constructor(public readonly model: Model<T>) {}
|
|
7
|
+
|
|
8
|
+
async create(data: T): Promise<void> {
|
|
9
|
+
const keys = Object.keys(data);
|
|
10
|
+
const sql = `INSERT INTO \`${this.model.name}\` (${keys.join(",")}) VALUES (${keys.map(() => "?").join(",")})`;
|
|
11
|
+
const values = keys.map((key) => this.normalizeValue((data as any)[key]));
|
|
12
|
+
|
|
13
|
+
await getPool().execute(sql, values);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async find(where: Partial<T> = {}): Promise<T[]> {
|
|
18
|
+
const { sql, params } = this.buildWhereClause(where);
|
|
19
|
+
const query = `SELECT * FROM \`${this.model.name}\` ${sql}`;
|
|
20
|
+
const [rows] = await getPool().execute(query, params.map(this.normalizeValue));
|
|
21
|
+
return rows as T[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async findOne(where: Partial<T>): Promise<T | null> {
|
|
25
|
+
const { sql, params } = this.buildWhereClause(where);
|
|
26
|
+
const query = `SELECT * FROM \`${this.model.name}\` ${sql} LIMIT 1`;
|
|
27
|
+
const [rows] = await getPool().execute(query, params.map(this.normalizeValue));
|
|
28
|
+
const results = rows as T[];
|
|
29
|
+
return results.length > 0 ? results[0] : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async update(where: Partial<T>, data: Partial<T>): Promise<void> {
|
|
33
|
+
const setKeys = Object.keys(data);
|
|
34
|
+
const setClause = setKeys.map(k => `${k} = ?`).join(", ");
|
|
35
|
+
const setValues = setKeys.map(k => this.normalizeValue((data as any)[k]));
|
|
36
|
+
|
|
37
|
+
const { sql: whereClause, params: whereValues } = this.buildWhereClause(where);
|
|
38
|
+
const query = `UPDATE \`${this.model.name}\` SET ${setClause} ${whereClause}`;
|
|
39
|
+
await getPool().execute(query, [...setValues, ...whereValues.map(this.normalizeValue)]);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async delete(where: Partial<T>): Promise<void> {
|
|
43
|
+
const { sql, params } = this.buildWhereClause(where);
|
|
44
|
+
const query = `DELETE FROM \`${this.model.name}\` ${sql}`;
|
|
45
|
+
await getPool().execute(query, params.map(this.normalizeValue));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async count(where: Partial<T> = {}): Promise<number> {
|
|
49
|
+
const { sql, params } = this.buildWhereClause(where);
|
|
50
|
+
const query = `SELECT COUNT(*) as count FROM \`${this.model.name}\` ${sql}`;
|
|
51
|
+
const [rows] = await getPool().execute(query, params.map(this.normalizeValue));
|
|
52
|
+
return (rows as any[])[0].count || 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async exists(where: Partial<T>): Promise<boolean> {
|
|
56
|
+
const count = await this.count(where);
|
|
57
|
+
return count > 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private normalizeValue(value: any): any {
|
|
61
|
+
if (value instanceof Date) {
|
|
62
|
+
return value.toISOString().slice(0, 19).replace("T", " ");
|
|
63
|
+
}
|
|
64
|
+
if (typeof value === "object" && value !== null) {
|
|
65
|
+
return JSON.stringify(value);
|
|
66
|
+
}
|
|
67
|
+
return value ?? null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private buildWhereClause(where: Partial<T>): { sql: string; params: any[] } {
|
|
71
|
+
const keys = Object.keys(where);
|
|
72
|
+
if (keys.length === 0) return { sql: "", params: [] };
|
|
73
|
+
|
|
74
|
+
const conditions = keys.map(k => `${k} = ?`).join(" AND ");
|
|
75
|
+
const values = keys.map(k => (where as any)[k]);
|
|
76
|
+
return {
|
|
77
|
+
sql: `WHERE ${conditions}`,
|
|
78
|
+
params: values,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { getPool } from "../core";
|
|
2
|
+
|
|
3
|
+
export async function getNextId(prefix: string): Promise<string> {
|
|
4
|
+
const pool = getPool();
|
|
5
|
+
|
|
6
|
+
await pool.execute(`
|
|
7
|
+
CREATE TABLE IF NOT EXISTS _id_counters (
|
|
8
|
+
prefix VARCHAR(255) PRIMARY KEY,
|
|
9
|
+
count INT NOT NULL
|
|
10
|
+
)
|
|
11
|
+
`);
|
|
12
|
+
|
|
13
|
+
const [rows] = await pool.query(`SELECT count FROM _id_counters WHERE prefix = ?`, [prefix]);
|
|
14
|
+
let count = 1;
|
|
15
|
+
|
|
16
|
+
if ((rows as any[]).length > 0) {
|
|
17
|
+
count = (rows as any)[0].count + 1;
|
|
18
|
+
await pool.execute(`UPDATE _id_counters SET count = ? WHERE prefix = ?`, [count, prefix]);
|
|
19
|
+
} else {
|
|
20
|
+
await pool.execute(`INSERT INTO _id_counters (prefix, count) VALUES (?, ?)`, [prefix, count]);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return `${prefix}-${String(count).padStart(3, "0")}`;
|
|
24
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {getAllModels, getPool} from '../core'
|
|
2
|
+
|
|
3
|
+
export async function syncSchema() {
|
|
4
|
+
const models = getAllModels();
|
|
5
|
+
const pool = getPool();
|
|
6
|
+
|
|
7
|
+
for (const [name, model] of models.entries()) {
|
|
8
|
+
const columns: string[] = [];
|
|
9
|
+
const foreignKeys: string[] = [];
|
|
10
|
+
|
|
11
|
+
for (const [key, type] of Object.entries(model.schema)) {
|
|
12
|
+
columns.push(`\`${key}\` ${mapType(type)}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
for (const fk of model.foreignKeys) {
|
|
16
|
+
foreignKeys.push(`FOREIGN KEY (\`${fk.column}\`) REFERENCES ${fk.reference}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const columnDefs = [...columns, ...foreignKeys].join(",\n ");
|
|
20
|
+
const sql = `CREATE TABLE IF NOT EXISTS \`${name}\` (\n ${columnDefs}\n);`;
|
|
21
|
+
|
|
22
|
+
await pool.execute(sql);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log("✅ Schema synchronized.");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function mapType(type: string): string {
|
|
29
|
+
switch (type.toLowerCase()) {
|
|
30
|
+
case "string": return "VARCHAR(255)";
|
|
31
|
+
case "json": return "JSON";
|
|
32
|
+
case "datetime": return "DATETIME";
|
|
33
|
+
case "number": return "INT";
|
|
34
|
+
case "boolean": return "BOOLEAN";
|
|
35
|
+
default: return type; // fallback for raw SQL types if provided
|
|
36
|
+
}
|
|
37
|
+
}
|
package/tsconfig.json
ADDED