@kyro-cms/core 0.1.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 +241 -0
- package/dist/base-CQkFzqQl.d.ts +62 -0
- package/dist/base-DlhVlwnN.d.cts +62 -0
- package/dist/chunk-3Q3FS5J4.cjs +273 -0
- package/dist/chunk-3Q3FS5J4.cjs.map +1 -0
- package/dist/chunk-3TPQ2BU6.js +423 -0
- package/dist/chunk-3TPQ2BU6.js.map +1 -0
- package/dist/chunk-3VZCX4DF.cjs +384 -0
- package/dist/chunk-3VZCX4DF.cjs.map +1 -0
- package/dist/chunk-BXMWDUED.js +115 -0
- package/dist/chunk-BXMWDUED.js.map +1 -0
- package/dist/chunk-DIC236EW.js +290 -0
- package/dist/chunk-DIC236EW.js.map +1 -0
- package/dist/chunk-DKSMFC3L.js +268 -0
- package/dist/chunk-DKSMFC3L.js.map +1 -0
- package/dist/chunk-DVD5P72E.cjs +428 -0
- package/dist/chunk-DVD5P72E.cjs.map +1 -0
- package/dist/chunk-HT6VE4NW.cjs +293 -0
- package/dist/chunk-HT6VE4NW.cjs.map +1 -0
- package/dist/chunk-K7QF2QCM.cjs +311 -0
- package/dist/chunk-K7QF2QCM.cjs.map +1 -0
- package/dist/chunk-OG3KX56O.js +308 -0
- package/dist/chunk-OG3KX56O.js.map +1 -0
- package/dist/chunk-R3XIBBAW.cjs +34 -0
- package/dist/chunk-R3XIBBAW.cjs.map +1 -0
- package/dist/chunk-RLTG4YZM.cjs +117 -0
- package/dist/chunk-RLTG4YZM.cjs.map +1 -0
- package/dist/chunk-SDMNUYVU.js +30 -0
- package/dist/chunk-SDMNUYVU.js.map +1 -0
- package/dist/chunk-UEG7KMKC.cjs +228 -0
- package/dist/chunk-UEG7KMKC.cjs.map +1 -0
- package/dist/chunk-UEYC46RL.js +374 -0
- package/dist/chunk-UEYC46RL.js.map +1 -0
- package/dist/chunk-YPAFJ7EV.js +225 -0
- package/dist/chunk-YPAFJ7EV.js.map +1 -0
- package/dist/cli/index.cjs +306 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +303 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/drizzle/index.cjs +25 -0
- package/dist/drizzle/index.cjs.map +1 -0
- package/dist/drizzle/index.d.cts +49 -0
- package/dist/drizzle/index.d.ts +49 -0
- package/dist/drizzle/index.js +4 -0
- package/dist/drizzle/index.js.map +1 -0
- package/dist/graphql/index.cjs +16 -0
- package/dist/graphql/index.cjs.map +1 -0
- package/dist/graphql/index.d.cts +20 -0
- package/dist/graphql/index.d.ts +20 -0
- package/dist/graphql/index.js +3 -0
- package/dist/graphql/index.js.map +1 -0
- package/dist/index-4fJKLFK2.d.ts +63 -0
- package/dist/index-DI0DRPNv.d.cts +63 -0
- package/dist/index.cjs +2506 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +525 -0
- package/dist/index.d.ts +525 -0
- package/dist/index.js +2334 -0
- package/dist/index.js.map +1 -0
- package/dist/mongodb/index.cjs +17 -0
- package/dist/mongodb/index.cjs.map +1 -0
- package/dist/mongodb/index.d.cts +49 -0
- package/dist/mongodb/index.d.ts +49 -0
- package/dist/mongodb/index.js +4 -0
- package/dist/mongodb/index.js.map +1 -0
- package/dist/rest/index.cjs +17 -0
- package/dist/rest/index.cjs.map +1 -0
- package/dist/rest/index.d.cts +28 -0
- package/dist/rest/index.d.ts +28 -0
- package/dist/rest/index.js +4 -0
- package/dist/rest/index.js.map +1 -0
- package/dist/trpc/index.cjs +45 -0
- package/dist/trpc/index.cjs.map +1 -0
- package/dist/trpc/index.d.cts +130 -0
- package/dist/trpc/index.d.ts +130 -0
- package/dist/trpc/index.js +4 -0
- package/dist/trpc/index.js.map +1 -0
- package/dist/types-BGM5MV_K.d.cts +589 -0
- package/dist/types-BGM5MV_K.d.ts +589 -0
- package/dist/ws/index.cjs +24 -0
- package/dist/ws/index.cjs.map +1 -0
- package/dist/ws/index.d.cts +88 -0
- package/dist/ws/index.d.ts +88 -0
- package/dist/ws/index.js +3 -0
- package/dist/ws/index.js.map +1 -0
- package/package.json +120 -0
package/README.md
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# Kyro CMS
|
|
2
|
+
|
|
3
|
+
**Astro-Native Headless CMS with Multi-Database Adapters, Multi-Protocol APIs, and Multi-Vendor Support**
|
|
4
|
+
|
|
5
|
+
Kyro is an open-source, TypeScript-native headless CMS inspired by Payload CMS but architected specifically for Astro JS. It provides zero-inference tRPC, SQL-sovereign Drizzle schemas, and built-in multi-tenancy.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
### Local-First with SQLite
|
|
12
|
+
```typescript
|
|
13
|
+
import { createKyro, LocalAdapter } from '@kyro-cms/core';
|
|
14
|
+
|
|
15
|
+
const kyro = createKyro({
|
|
16
|
+
adapter: new LocalAdapter({ path: './data.db' }),
|
|
17
|
+
collections: [productsCollection],
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
Works out of the box - no external database required.
|
|
21
|
+
|
|
22
|
+
### Deploy Anywhere with Adapters
|
|
23
|
+
- **Local/SQLite**: Zero-config local development
|
|
24
|
+
- **PostgreSQL/MySQL/SQLite**: Via Drizzle ORM
|
|
25
|
+
- **MongoDB**: NoSQL flexibility
|
|
26
|
+
|
|
27
|
+
### E-Commerce Ready
|
|
28
|
+
Pre-built collections for:
|
|
29
|
+
- Products with variants, pricing, inventory
|
|
30
|
+
- Customers with addresses, orders
|
|
31
|
+
- Orders with items, shipping, payments
|
|
32
|
+
- Coupons and promotions
|
|
33
|
+
- Inventory tracking
|
|
34
|
+
|
|
35
|
+
### Multi-Protocol API
|
|
36
|
+
- **REST**: Hono-based with query params
|
|
37
|
+
- **GraphQL**: Dynamic schema
|
|
38
|
+
- **tRPC**: Type-safe RPC (zero inference)
|
|
39
|
+
- **WebSocket**: Real-time pub/sub
|
|
40
|
+
|
|
41
|
+
### Multi-Vendor Ready
|
|
42
|
+
Row-level access control with tenant scoping built-in.
|
|
43
|
+
|
|
44
|
+
### Plugin System
|
|
45
|
+
Extend with:
|
|
46
|
+
- SEO (sitemaps, structured data)
|
|
47
|
+
- Analytics
|
|
48
|
+
- Reviews & Comments
|
|
49
|
+
- Wishlists
|
|
50
|
+
|
|
51
|
+
### Any Styling System
|
|
52
|
+
- Plain CSS
|
|
53
|
+
- Tailwind CSS
|
|
54
|
+
- CSS-in-JS
|
|
55
|
+
- Styled Components
|
|
56
|
+
- Vanilla Extract
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Quick Start
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm install @kyro-cms/core
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Basic Setup
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { createKyro, LocalAdapter } from '@kyro-cms/core';
|
|
70
|
+
|
|
71
|
+
const kyro = createKyro({
|
|
72
|
+
adapter: new LocalAdapter(),
|
|
73
|
+
collections: [{
|
|
74
|
+
slug: 'posts',
|
|
75
|
+
fields: [
|
|
76
|
+
{ name: 'title', type: 'text', required: true },
|
|
77
|
+
{ name: 'content', type: 'richtext' },
|
|
78
|
+
],
|
|
79
|
+
}],
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await kyro.init();
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### REST API
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
curl http://localhost:3000/api/posts
|
|
89
|
+
curl -X POST http://localhost:3000/api/posts \
|
|
90
|
+
-H "Content-Type: application/json" \
|
|
91
|
+
-d '{"title":"Hello World"}'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### GraphQL
|
|
95
|
+
|
|
96
|
+
```graphql
|
|
97
|
+
query { postsFind { docs { id title } totalDocs } }
|
|
98
|
+
mutation { postsCreate(data: { title: "New Post" }) { doc { id } } }
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## E-Commerce Example
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import { createKyro, LocalAdapter } from '@kyro-cms/core';
|
|
107
|
+
import {
|
|
108
|
+
productsCollection,
|
|
109
|
+
ordersCollection,
|
|
110
|
+
customersCollection,
|
|
111
|
+
ecommerceCollections
|
|
112
|
+
} from '@kyro-cms/core/examples/ecommerce';
|
|
113
|
+
|
|
114
|
+
const kyro = createKyro({
|
|
115
|
+
adapter: new LocalAdapter({ path: './store.db' }),
|
|
116
|
+
collections: ecommerceCollections,
|
|
117
|
+
tenantScoped: true, // Multi-vendor
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
await kyro.init();
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Plugin System
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { createKyro, SEOPLugin, ReviewsPlugin } from '@kyro-cms/core';
|
|
129
|
+
|
|
130
|
+
const kyro = createKyro({
|
|
131
|
+
adapter: new LocalAdapter(),
|
|
132
|
+
collections: [...],
|
|
133
|
+
plugins: [
|
|
134
|
+
new SEOPLugin(),
|
|
135
|
+
new ReviewsPlugin(),
|
|
136
|
+
],
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Styling
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import { ecommerce2026Theme, generateCSSVariables } from '@kyro-cms/core';
|
|
146
|
+
|
|
147
|
+
// Generate CSS variables
|
|
148
|
+
const css = generateCSSVariables(ecommerce2026Theme);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Architecture
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
157
|
+
│ Your Config │
|
|
158
|
+
│ (CollectionConfig[]) │
|
|
159
|
+
└───────────────────────────┬──────────────────────────────────┘
|
|
160
|
+
│
|
|
161
|
+
▼
|
|
162
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
163
|
+
│ Registry │
|
|
164
|
+
│ • Stores configs │
|
|
165
|
+
│ • Generates Zod schemas │
|
|
166
|
+
│ • Validates config │
|
|
167
|
+
└───────────────────────────┬──────────────────────────────────┘
|
|
168
|
+
│
|
|
169
|
+
┌────────────────┼────────────────┐
|
|
170
|
+
▼ ▼ ▼
|
|
171
|
+
┌─────────────────┐ ┌─────────────┐ ┌─────────────────┐
|
|
172
|
+
│ Local/SQLite │ │ Drizzle │ │ MongoDB │
|
|
173
|
+
│ (Local-first) │ │ (SQL) │ │ (NoSQL) │
|
|
174
|
+
└─────────────────┘ └─────────────┘ └─────────────────┘
|
|
175
|
+
│
|
|
176
|
+
▼
|
|
177
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
178
|
+
│ Multi-Protocol API Gateway │
|
|
179
|
+
│ • REST (Hono) • GraphQL • tRPC • WebSocket │
|
|
180
|
+
└──────────────────────────────────────────────────────────────┘
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Project Structure
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
kyro-cms/
|
|
189
|
+
├── src/
|
|
190
|
+
│ ├── index.ts # Main exports
|
|
191
|
+
│ ├── registry/ # Configuration engine
|
|
192
|
+
│ ├── fields/ # 21 field types
|
|
193
|
+
│ ├── database/ # Adapters (Local, Drizzle, MongoDB)
|
|
194
|
+
│ ├── api/ # REST, GraphQL, tRPC, WebSocket
|
|
195
|
+
│ ├── plugins/ # Plugin system
|
|
196
|
+
│ ├── styling/ # Theming & CSS generation
|
|
197
|
+
│ └── cli/ # CLI tools
|
|
198
|
+
├── admin/ # Admin dashboard
|
|
199
|
+
├── examples/
|
|
200
|
+
│ ├── basic.config.ts # Blog example
|
|
201
|
+
│ └── ecommerce.config.ts # E-commerce example
|
|
202
|
+
└── docs/ # Documentation
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## CLI Commands
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
kyro generate # Generate TypeScript types
|
|
211
|
+
kyro migrate # Run migrations
|
|
212
|
+
kyro health # Check system health
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Documentation
|
|
218
|
+
|
|
219
|
+
- [Getting Started](docs/getting-started.md)
|
|
220
|
+
- [API Reference](docs/api.md)
|
|
221
|
+
- [E-Commerce Guide](docs/ecommerce.md)
|
|
222
|
+
- [Plugin Development](docs/plugins.md)
|
|
223
|
+
- [Styling Guide](docs/styling.md)
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## License
|
|
228
|
+
|
|
229
|
+
MIT
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Roadmap
|
|
234
|
+
|
|
235
|
+
- [x] Local-first SQLite adapter
|
|
236
|
+
- [x] E-commerce collections
|
|
237
|
+
- [x] Plugin system
|
|
238
|
+
- [x] Styling abstraction
|
|
239
|
+
- [ ] Full admin dashboard
|
|
240
|
+
- [ ] Authentication
|
|
241
|
+
- [ ] Version history
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { B as BaseAdapter, C as CollectionConfig, G as GlobalConfig, a as FindArgs, b as FindResult, c as FindByIDArgs, d as CreateArgs, U as UpdateArgs, D as DeleteArgs, F as Field, R as RelationshipField, e as UploadField } from './types-BGM5MV_K.js';
|
|
2
|
+
|
|
3
|
+
declare abstract class AbstractBaseAdapter implements BaseAdapter {
|
|
4
|
+
protected collections: Map<string, CollectionConfig>;
|
|
5
|
+
protected globals: Map<string, GlobalConfig>;
|
|
6
|
+
protected connected: boolean;
|
|
7
|
+
abstract connect(): Promise<void>;
|
|
8
|
+
abstract disconnect(): Promise<void>;
|
|
9
|
+
init(collections: CollectionConfig[], globals?: GlobalConfig[]): Promise<void>;
|
|
10
|
+
abstract find<T>(args: FindArgs): Promise<FindResult<T>>;
|
|
11
|
+
abstract findByID<T>(args: FindByIDArgs): Promise<T | null>;
|
|
12
|
+
abstract create<T>(args: CreateArgs): Promise<T>;
|
|
13
|
+
abstract update<T>(args: UpdateArgs): Promise<T>;
|
|
14
|
+
abstract delete<T>(args: DeleteArgs): Promise<T>;
|
|
15
|
+
abstract count(args: {
|
|
16
|
+
collection: string;
|
|
17
|
+
where?: Record<string, any>;
|
|
18
|
+
tenantID?: string;
|
|
19
|
+
}): Promise<number>;
|
|
20
|
+
abstract findOne(args: {
|
|
21
|
+
collection: string;
|
|
22
|
+
where: Record<string, any>;
|
|
23
|
+
tenantID?: string;
|
|
24
|
+
}): Promise<any>;
|
|
25
|
+
abstract findVersions(args: FindArgs): Promise<FindResult<any>>;
|
|
26
|
+
abstract findVersionByID(args: FindByIDArgs): Promise<any>;
|
|
27
|
+
abstract createVersion(args: CreateArgs): Promise<any>;
|
|
28
|
+
abstract deleteVersions(args: {
|
|
29
|
+
collection: string;
|
|
30
|
+
where: Record<string, any>;
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
migrate?(): Promise<void>;
|
|
33
|
+
rollback?(): Promise<void>;
|
|
34
|
+
transaction?<T>(fn: (tx: any) => Promise<T>): Promise<T>;
|
|
35
|
+
protected getCollection(slug: string): CollectionConfig;
|
|
36
|
+
protected applyTenantFilter(where?: Record<string, any>, tenantID?: string): Record<string, any>;
|
|
37
|
+
protected getTableName(slug: string): string;
|
|
38
|
+
protected prepareData(data: Record<string, any>, collection: CollectionConfig): Record<string, any>;
|
|
39
|
+
protected processRelationships(data: Record<string, any>, fields: Field[], depth: number): Record<string, any>;
|
|
40
|
+
protected parseSort(sort?: string): {
|
|
41
|
+
field: string;
|
|
42
|
+
direction: 'asc' | 'desc';
|
|
43
|
+
};
|
|
44
|
+
protected calculatePagination(page: number, limit: number, totalDocs: number): {
|
|
45
|
+
totalDocs: number;
|
|
46
|
+
limit: number;
|
|
47
|
+
totalPages: number;
|
|
48
|
+
page: number;
|
|
49
|
+
pagingCounter: number;
|
|
50
|
+
hasPrevPage: boolean;
|
|
51
|
+
hasNextPage: boolean;
|
|
52
|
+
prevPage: number | null;
|
|
53
|
+
nextPage: number | null;
|
|
54
|
+
};
|
|
55
|
+
protected selectFields(data: Record<string, any>, select?: string[]): Record<string, any>;
|
|
56
|
+
protected isRelationshipField(field: Field): field is RelationshipField;
|
|
57
|
+
protected isUploadField(field: Field): field is UploadField;
|
|
58
|
+
protected getRelationshipFields(fields: Field[]): RelationshipField[];
|
|
59
|
+
protected getUploadFields(fields: Field[]): UploadField[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { AbstractBaseAdapter as A };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { B as BaseAdapter, C as CollectionConfig, G as GlobalConfig, a as FindArgs, b as FindResult, c as FindByIDArgs, d as CreateArgs, U as UpdateArgs, D as DeleteArgs, F as Field, R as RelationshipField, e as UploadField } from './types-BGM5MV_K.cjs';
|
|
2
|
+
|
|
3
|
+
declare abstract class AbstractBaseAdapter implements BaseAdapter {
|
|
4
|
+
protected collections: Map<string, CollectionConfig>;
|
|
5
|
+
protected globals: Map<string, GlobalConfig>;
|
|
6
|
+
protected connected: boolean;
|
|
7
|
+
abstract connect(): Promise<void>;
|
|
8
|
+
abstract disconnect(): Promise<void>;
|
|
9
|
+
init(collections: CollectionConfig[], globals?: GlobalConfig[]): Promise<void>;
|
|
10
|
+
abstract find<T>(args: FindArgs): Promise<FindResult<T>>;
|
|
11
|
+
abstract findByID<T>(args: FindByIDArgs): Promise<T | null>;
|
|
12
|
+
abstract create<T>(args: CreateArgs): Promise<T>;
|
|
13
|
+
abstract update<T>(args: UpdateArgs): Promise<T>;
|
|
14
|
+
abstract delete<T>(args: DeleteArgs): Promise<T>;
|
|
15
|
+
abstract count(args: {
|
|
16
|
+
collection: string;
|
|
17
|
+
where?: Record<string, any>;
|
|
18
|
+
tenantID?: string;
|
|
19
|
+
}): Promise<number>;
|
|
20
|
+
abstract findOne(args: {
|
|
21
|
+
collection: string;
|
|
22
|
+
where: Record<string, any>;
|
|
23
|
+
tenantID?: string;
|
|
24
|
+
}): Promise<any>;
|
|
25
|
+
abstract findVersions(args: FindArgs): Promise<FindResult<any>>;
|
|
26
|
+
abstract findVersionByID(args: FindByIDArgs): Promise<any>;
|
|
27
|
+
abstract createVersion(args: CreateArgs): Promise<any>;
|
|
28
|
+
abstract deleteVersions(args: {
|
|
29
|
+
collection: string;
|
|
30
|
+
where: Record<string, any>;
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
migrate?(): Promise<void>;
|
|
33
|
+
rollback?(): Promise<void>;
|
|
34
|
+
transaction?<T>(fn: (tx: any) => Promise<T>): Promise<T>;
|
|
35
|
+
protected getCollection(slug: string): CollectionConfig;
|
|
36
|
+
protected applyTenantFilter(where?: Record<string, any>, tenantID?: string): Record<string, any>;
|
|
37
|
+
protected getTableName(slug: string): string;
|
|
38
|
+
protected prepareData(data: Record<string, any>, collection: CollectionConfig): Record<string, any>;
|
|
39
|
+
protected processRelationships(data: Record<string, any>, fields: Field[], depth: number): Record<string, any>;
|
|
40
|
+
protected parseSort(sort?: string): {
|
|
41
|
+
field: string;
|
|
42
|
+
direction: 'asc' | 'desc';
|
|
43
|
+
};
|
|
44
|
+
protected calculatePagination(page: number, limit: number, totalDocs: number): {
|
|
45
|
+
totalDocs: number;
|
|
46
|
+
limit: number;
|
|
47
|
+
totalPages: number;
|
|
48
|
+
page: number;
|
|
49
|
+
pagingCounter: number;
|
|
50
|
+
hasPrevPage: boolean;
|
|
51
|
+
hasNextPage: boolean;
|
|
52
|
+
prevPage: number | null;
|
|
53
|
+
nextPage: number | null;
|
|
54
|
+
};
|
|
55
|
+
protected selectFields(data: Record<string, any>, select?: string[]): Record<string, any>;
|
|
56
|
+
protected isRelationshipField(field: Field): field is RelationshipField;
|
|
57
|
+
protected isUploadField(field: Field): field is UploadField;
|
|
58
|
+
protected getRelationshipFields(fields: Field[]): RelationshipField[];
|
|
59
|
+
protected getUploadFields(fields: Field[]): UploadField[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { AbstractBaseAdapter as A };
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkRLTG4YZM_cjs = require('./chunk-RLTG4YZM.cjs');
|
|
4
|
+
|
|
5
|
+
// src/database/drizzle/adapter.ts
|
|
6
|
+
function fieldToDrizzleType(field, dialect = "postgres") {
|
|
7
|
+
switch (field.type) {
|
|
8
|
+
case "text":
|
|
9
|
+
case "email":
|
|
10
|
+
case "password":
|
|
11
|
+
case "textarea":
|
|
12
|
+
case "color":
|
|
13
|
+
case "code":
|
|
14
|
+
case "markdown":
|
|
15
|
+
return dialect === "sqlite" ? "text" : "varchar";
|
|
16
|
+
case "number":
|
|
17
|
+
return field.integer ? "integer" : "decimal";
|
|
18
|
+
case "checkbox":
|
|
19
|
+
return "boolean";
|
|
20
|
+
case "date":
|
|
21
|
+
return "timestamp";
|
|
22
|
+
case "select":
|
|
23
|
+
case "radio":
|
|
24
|
+
return dialect === "sqlite" ? "text" : "varchar";
|
|
25
|
+
case "richtext":
|
|
26
|
+
case "json":
|
|
27
|
+
case "array":
|
|
28
|
+
case "group":
|
|
29
|
+
case "blocks":
|
|
30
|
+
case "row":
|
|
31
|
+
case "collapsible":
|
|
32
|
+
case "tabs":
|
|
33
|
+
return "jsonb";
|
|
34
|
+
case "relationship":
|
|
35
|
+
return dialect === "sqlite" ? "text" : "varchar";
|
|
36
|
+
case "upload":
|
|
37
|
+
return dialect === "sqlite" ? "text" : "varchar";
|
|
38
|
+
default:
|
|
39
|
+
return "jsonb";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function collectionToDrizzleSchema(collection, dialect = "postgres") {
|
|
43
|
+
const tableName = collection.slug.replace(/-/g, "_");
|
|
44
|
+
const lines = [];
|
|
45
|
+
lines.push(`export const ${tableName} = ${dialect === "mysql" ? "mysqlTable" : "pgTable"}('${tableName}', {`);
|
|
46
|
+
lines.push(` id: ${dialect === "mysql" ? "varchar" : "uuid"}('${dialect === "mysql" ? "id" : "id"}').${dialect === "mysql" ? "primaryKey().default(sql`UUID()`)" : "primaryKey().defaultRandom()"},`);
|
|
47
|
+
for (const field of collection.fields) {
|
|
48
|
+
if (field.name === "id") continue;
|
|
49
|
+
const dbType = fieldToDrizzleType(field, dialect);
|
|
50
|
+
const isRequired = field.required;
|
|
51
|
+
let fieldDef = ` ${field.name}: ${dialect === "mysql" ? "mysql" : "pg"}.${dbType}('${field.name}')`;
|
|
52
|
+
if (field.unique) fieldDef += ".unique()";
|
|
53
|
+
if (!isRequired) fieldDef += ".nullable()";
|
|
54
|
+
if (field.defaultValue !== void 0) {
|
|
55
|
+
if (typeof field.defaultValue === "string") {
|
|
56
|
+
fieldDef += `.default('${field.defaultValue}')`;
|
|
57
|
+
} else if (typeof field.defaultValue === "boolean") {
|
|
58
|
+
fieldDef += `.default(${field.defaultValue})`;
|
|
59
|
+
} else {
|
|
60
|
+
fieldDef += `.default(sql\`${JSON.stringify(field.defaultValue)}\`)`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
fieldDef += ",";
|
|
64
|
+
lines.push(fieldDef);
|
|
65
|
+
}
|
|
66
|
+
if (collection.timestamps) {
|
|
67
|
+
lines.push(` createdAt: ${dialect === "mysql" ? "mysql" : "pg"}.timestamp('created_at').defaultNow(),`);
|
|
68
|
+
lines.push(` updatedAt: ${dialect === "mysql" ? "mysql" : "pg"}.timestamp('updated_at').defaultNow(),`);
|
|
69
|
+
}
|
|
70
|
+
if (collection.tenantScoped) {
|
|
71
|
+
lines.push(` tenantId: ${dialect === "mysql" ? "mysql" : "pg"}.varchar('tenant_id'),`);
|
|
72
|
+
}
|
|
73
|
+
lines.push("});");
|
|
74
|
+
return lines.join("\n");
|
|
75
|
+
}
|
|
76
|
+
var DrizzleAdapter = class extends chunkRLTG4YZM_cjs.AbstractBaseAdapter {
|
|
77
|
+
client;
|
|
78
|
+
schema;
|
|
79
|
+
dialect;
|
|
80
|
+
constructor(options) {
|
|
81
|
+
super();
|
|
82
|
+
this.client = options.client;
|
|
83
|
+
this.schema = options.schema || {};
|
|
84
|
+
this.dialect = options.type;
|
|
85
|
+
}
|
|
86
|
+
async connect() {
|
|
87
|
+
this.connected = true;
|
|
88
|
+
console.log(`[DrizzleAdapter] Connected to ${this.dialect}`);
|
|
89
|
+
}
|
|
90
|
+
async disconnect() {
|
|
91
|
+
this.connected = false;
|
|
92
|
+
console.log(`[DrizzleAdapter] Disconnected from ${this.dialect}`);
|
|
93
|
+
}
|
|
94
|
+
async find(args) {
|
|
95
|
+
const { collection: slug, where = {}, sort, limit = 10, page = 1, tenantID, select } = args;
|
|
96
|
+
const config = this.getCollection(slug);
|
|
97
|
+
const table = this.getTable(slug);
|
|
98
|
+
const filters = this.buildWhereClause(where, config, tenantID);
|
|
99
|
+
const sortOption = this.parseSort(sort);
|
|
100
|
+
const totalDocs = await this.count({ collection: slug, where, tenantID });
|
|
101
|
+
const offset = (page - 1) * limit;
|
|
102
|
+
let docs = [];
|
|
103
|
+
try {
|
|
104
|
+
const results = await this.client.select().from(table).where(filters).orderBy(sortOption.direction === "asc" ? table[sortOption.field] : void 0).limit(limit).offset(offset);
|
|
105
|
+
docs = results.map((doc) => this.processResult(doc, config));
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error(`[DrizzleAdapter] Query error:`, error);
|
|
108
|
+
docs = [];
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
docs,
|
|
112
|
+
...this.calculatePagination(page, limit, totalDocs)
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
async findByID(args) {
|
|
116
|
+
const { collection: slug, id, tenantID } = args;
|
|
117
|
+
const config = this.getCollection(slug);
|
|
118
|
+
const table = this.getTable(slug);
|
|
119
|
+
let query = this.client.select().from(table).where(table.id.equals(id));
|
|
120
|
+
if (tenantID) {
|
|
121
|
+
query = query.where(table.tenantId.equals(tenantID));
|
|
122
|
+
}
|
|
123
|
+
const results = await query.limit(1);
|
|
124
|
+
if (results.length === 0) return null;
|
|
125
|
+
return this.processResult(results[0], config);
|
|
126
|
+
}
|
|
127
|
+
async create(args) {
|
|
128
|
+
const { collection: slug, data, tenantID } = args;
|
|
129
|
+
const config = this.getCollection(slug);
|
|
130
|
+
const table = this.getTable(slug);
|
|
131
|
+
const insertData = this.prepareData(data, config);
|
|
132
|
+
if (tenantID) {
|
|
133
|
+
insertData.tenantId = tenantID;
|
|
134
|
+
}
|
|
135
|
+
const result = await this.client.insert(table).values(insertData).returning();
|
|
136
|
+
return this.processResult(result[0], config);
|
|
137
|
+
}
|
|
138
|
+
async update(args) {
|
|
139
|
+
const { collection: slug, id, data, tenantID } = args;
|
|
140
|
+
const config = this.getCollection(slug);
|
|
141
|
+
const table = this.getTable(slug);
|
|
142
|
+
const updateData = this.prepareData(data, config);
|
|
143
|
+
if (tenantID) {
|
|
144
|
+
updateData.tenantId = tenantID;
|
|
145
|
+
}
|
|
146
|
+
const result = await this.client.update(table).set(updateData).where(table.id.equals(id)).returning();
|
|
147
|
+
return this.processResult(result[0], config);
|
|
148
|
+
}
|
|
149
|
+
async delete(args) {
|
|
150
|
+
const { collection: slug, id, tenantID } = args;
|
|
151
|
+
const config = this.getCollection(slug);
|
|
152
|
+
const table = this.getTable(slug);
|
|
153
|
+
let query = this.client.delete(table).where(table.id.equals(id)).returning();
|
|
154
|
+
if (tenantID) {
|
|
155
|
+
query = query.where(table.tenantId.equals(tenantID));
|
|
156
|
+
}
|
|
157
|
+
const result = await query;
|
|
158
|
+
if (result.length === 0) {
|
|
159
|
+
throw new Error(`Document not found: ${slug}/${id}`);
|
|
160
|
+
}
|
|
161
|
+
return this.processResult(result[0], config);
|
|
162
|
+
}
|
|
163
|
+
async count(args) {
|
|
164
|
+
const { collection: slug, where = {}, tenantID } = args;
|
|
165
|
+
const config = this.getCollection(slug);
|
|
166
|
+
const table = this.getTable(slug);
|
|
167
|
+
const filters = this.buildWhereClause(where, config, tenantID);
|
|
168
|
+
try {
|
|
169
|
+
const result = await this.client.select({ count: `count(*)` }).from(table).where(filters);
|
|
170
|
+
return parseInt(result[0]?.count || "0");
|
|
171
|
+
} catch {
|
|
172
|
+
return 0;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async findOne(args) {
|
|
176
|
+
const { collection: slug, where = {}, tenantID } = args;
|
|
177
|
+
const config = this.getCollection(slug);
|
|
178
|
+
const table = this.getTable(slug);
|
|
179
|
+
const filters = this.buildWhereClause(where, config, tenantID);
|
|
180
|
+
const results = await this.client.select().from(table).where(filters).limit(1);
|
|
181
|
+
if (results.length === 0) return null;
|
|
182
|
+
return this.processResult(results[0], config);
|
|
183
|
+
}
|
|
184
|
+
async findVersions(args) {
|
|
185
|
+
const { collection: slug, where = {}, sort, limit = 10, page = 1 } = args;
|
|
186
|
+
return {
|
|
187
|
+
docs: [],
|
|
188
|
+
...this.calculatePagination(page, limit, 0)
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
async findVersionByID(args) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
async createVersion(args) {
|
|
195
|
+
return args.data;
|
|
196
|
+
}
|
|
197
|
+
async deleteVersions(args) {
|
|
198
|
+
}
|
|
199
|
+
// ========================================================================
|
|
200
|
+
// Helper Methods
|
|
201
|
+
// ========================================================================
|
|
202
|
+
getTable(slug) {
|
|
203
|
+
const tableName = this.getTableName(slug);
|
|
204
|
+
const table = this.schema[tableName];
|
|
205
|
+
if (!table) {
|
|
206
|
+
throw new Error(`Table "${tableName}" not found in schema`);
|
|
207
|
+
}
|
|
208
|
+
return table;
|
|
209
|
+
}
|
|
210
|
+
buildWhereClause(where, config, tenantID) {
|
|
211
|
+
const conditions = [];
|
|
212
|
+
if (tenantID && config.tenantScoped) {
|
|
213
|
+
conditions.push({ tenantId: tenantID });
|
|
214
|
+
}
|
|
215
|
+
for (const [key, value] of Object.entries(where)) {
|
|
216
|
+
if (key === "AND" && Array.isArray(value)) {
|
|
217
|
+
for (const subCondition of value) {
|
|
218
|
+
conditions.push(...Object.entries(subCondition).map(([k, v]) => ({ [k]: v })));
|
|
219
|
+
}
|
|
220
|
+
} else if (key === "OR" && Array.isArray(value)) {
|
|
221
|
+
conditions.push(...value.flatMap(
|
|
222
|
+
(v) => Object.entries(v).map(([k, val]) => ({ [k]: val }))
|
|
223
|
+
));
|
|
224
|
+
} else if (typeof value === "object" && value !== null) {
|
|
225
|
+
if (value.equals !== void 0) conditions.push({ [key]: value.equals });
|
|
226
|
+
if (value.not_equals !== void 0) conditions.push({ [key]: { not: value.not_equals } });
|
|
227
|
+
if (value.in) conditions.push({ [key]: { in: value.in } });
|
|
228
|
+
if (value.like) conditions.push({ [key]: { like: value.like } });
|
|
229
|
+
if (value.greater_than !== void 0) conditions.push({ [key]: { gt: value.greater_than } });
|
|
230
|
+
if (value.greater_than_equal !== void 0) conditions.push({ [key]: { gte: value.greater_than_equal } });
|
|
231
|
+
if (value.less_than !== void 0) conditions.push({ [key]: { lt: value.less_than } });
|
|
232
|
+
if (value.less_than_equal !== void 0) conditions.push({ [key]: { lte: value.less_than_equal } });
|
|
233
|
+
} else {
|
|
234
|
+
conditions.push({ [key]: value });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return conditions.length > 0 ? conditions : void 0;
|
|
238
|
+
}
|
|
239
|
+
processResult(data, config) {
|
|
240
|
+
if (!data) return null;
|
|
241
|
+
const result = { ...data };
|
|
242
|
+
if (data.id) {
|
|
243
|
+
result.id = String(data.id);
|
|
244
|
+
}
|
|
245
|
+
for (const field of config.fields) {
|
|
246
|
+
if (field.type === "json" || field.type === "richtext" || field.type === "array" || field.type === "group" || field.type === "blocks") {
|
|
247
|
+
if (result[field.name] && typeof result[field.name] === "string") {
|
|
248
|
+
try {
|
|
249
|
+
result[field.name] = JSON.parse(result[field.name]);
|
|
250
|
+
} catch {
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (result.createdAt) {
|
|
256
|
+
result.createdAt = new Date(result.createdAt).toISOString();
|
|
257
|
+
}
|
|
258
|
+
if (result.updatedAt) {
|
|
259
|
+
result.updatedAt = new Date(result.updatedAt).toISOString();
|
|
260
|
+
}
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
function createDrizzleAdapter(options) {
|
|
265
|
+
return new DrizzleAdapter(options);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
exports.DrizzleAdapter = DrizzleAdapter;
|
|
269
|
+
exports.collectionToDrizzleSchema = collectionToDrizzleSchema;
|
|
270
|
+
exports.createDrizzleAdapter = createDrizzleAdapter;
|
|
271
|
+
exports.fieldToDrizzleType = fieldToDrizzleType;
|
|
272
|
+
//# sourceMappingURL=chunk-3Q3FS5J4.cjs.map
|
|
273
|
+
//# sourceMappingURL=chunk-3Q3FS5J4.cjs.map
|