@rationalbloks/frontblok-crud 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 +268 -0
- package/dist/api.d.ts +115 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +227 -0
- package/dist/api.js.map +1 -0
- package/dist/generator.d.ts +15 -0
- package/dist/generator.d.ts.map +1 -0
- package/dist/generator.js +222 -0
- package/dist/generator.js.map +1 -0
- package/dist/hooks.d.ts +149 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +327 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +75 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +122 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +45 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +93 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +241 -0
- package/dist/utils.js.map +1 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# @rationalbloks/frontblok-crud
|
|
2
|
+
|
|
3
|
+
**Universal CRUD API Layer for RationalBloks Frontends**
|
|
4
|
+
|
|
5
|
+
The TypeScript equivalent of `builderblok` - a single, generic API layer that works with ANY entity schema.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🎯 Philosophy
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
13
|
+
│ THE BUILDERBLOK PATTERN │
|
|
14
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
15
|
+
│ │
|
|
16
|
+
│ BACKEND (builderblok) FRONTEND (frontblok-crud) │
|
|
17
|
+
│ ═══════════════════ ════════════════════════ │
|
|
18
|
+
│ │
|
|
19
|
+
│ JSON Schema JSON Schema │
|
|
20
|
+
│ │ │ │
|
|
21
|
+
│ ▼ ▼ │
|
|
22
|
+
│ entities.py ◄── ONLY GENERATED entities.ts ◄── ONLY GENERATED │
|
|
23
|
+
│ │ │ │
|
|
24
|
+
│ ▼ ▼ │
|
|
25
|
+
│ models.py ─┐ api.ts ──────┐ │
|
|
26
|
+
│ crud.py ───┼── 100% GENERIC hooks.ts ────┼── 100% GENERIC │
|
|
27
|
+
│ routes.py ─┘ utils.ts ────┘ │
|
|
28
|
+
│ │
|
|
29
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Only ONE file is generated** (`entities.ts`). Everything else is generic and works with ANY schema.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 📦 Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install @rationalbloks/frontblok-crud
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Requires peer dependencies:
|
|
43
|
+
```bash
|
|
44
|
+
npm install react @rationalbloks/frontblok-auth
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 🚀 Quick Start
|
|
50
|
+
|
|
51
|
+
### 1. Initialize API (once at app startup)
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// main.tsx or App.tsx
|
|
55
|
+
import { initApi } from '@rationalbloks/frontblok-crud';
|
|
56
|
+
|
|
57
|
+
initApi(import.meta.env.VITE_DATABASE_API_URL);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 2. Use Hooks in Components
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// TaskList.tsx
|
|
64
|
+
import { useEntityList, useMutation } from '@rationalbloks/frontblok-crud';
|
|
65
|
+
import type { Task } from './entities'; // Generated file
|
|
66
|
+
|
|
67
|
+
function TaskList() {
|
|
68
|
+
const { items, loading, error, refetch } = useEntityList<Task>('tasks');
|
|
69
|
+
const { mutate } = useMutation<Task>('tasks');
|
|
70
|
+
|
|
71
|
+
const handleDelete = async (id: string) => {
|
|
72
|
+
await mutate.remove(id);
|
|
73
|
+
refetch();
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
if (loading) return <CircularProgress />;
|
|
77
|
+
if (error) return <Alert severity="error">{error.message}</Alert>;
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<ul>
|
|
81
|
+
{items.map(task => (
|
|
82
|
+
<li key={task.id}>
|
|
83
|
+
{task.title}
|
|
84
|
+
<button onClick={() => handleDelete(task.id)}>Delete</button>
|
|
85
|
+
</li>
|
|
86
|
+
))}
|
|
87
|
+
</ul>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 3. Use Direct API Access
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { getApi } from '@rationalbloks/frontblok-crud';
|
|
96
|
+
import type { Task } from './entities';
|
|
97
|
+
|
|
98
|
+
// Get all tasks
|
|
99
|
+
const tasks = await getApi().getAll<Task>('tasks');
|
|
100
|
+
|
|
101
|
+
// Get single task
|
|
102
|
+
const task = await getApi().getOne<Task>('tasks', 'uuid-here');
|
|
103
|
+
|
|
104
|
+
// Create task
|
|
105
|
+
const newTask = await getApi().create<Task>('tasks', { title: 'New Task' });
|
|
106
|
+
|
|
107
|
+
// Update task
|
|
108
|
+
const updated = await getApi().update<Task>('tasks', 'uuid-here', { status: 'done' });
|
|
109
|
+
|
|
110
|
+
// Delete task
|
|
111
|
+
await getApi().remove('tasks', 'uuid-here');
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 📚 API Reference
|
|
117
|
+
|
|
118
|
+
### Initialization
|
|
119
|
+
|
|
120
|
+
| Function | Description |
|
|
121
|
+
|----------|-------------|
|
|
122
|
+
| `initApi(baseUrl, getToken?)` | Initialize the global API instance |
|
|
123
|
+
| `getApi()` | Get the global API instance (throws if not initialized) |
|
|
124
|
+
| `isApiInitialized()` | Check if API has been initialized |
|
|
125
|
+
| `resetApi()` | Reset the API instance (for testing) |
|
|
126
|
+
|
|
127
|
+
### Hooks
|
|
128
|
+
|
|
129
|
+
| Hook | Description |
|
|
130
|
+
|------|-------------|
|
|
131
|
+
| `useEntityList<T>(entityName, options?)` | Fetch list of entities |
|
|
132
|
+
| `useEntity<T>(entityName, id?)` | Fetch single entity by ID |
|
|
133
|
+
| `useMutation<T>(entityName)` | Create, update, delete entities |
|
|
134
|
+
| `useEntityForm<T>(entityName, id?)` | Combined entity + mutation for forms |
|
|
135
|
+
|
|
136
|
+
### Generator (MCP use only)
|
|
137
|
+
|
|
138
|
+
| Function | Description |
|
|
139
|
+
|----------|-------------|
|
|
140
|
+
| `generateEntitiesTs(schema)` | Generate complete entities.ts from schema |
|
|
141
|
+
| `validateSchema(schema)` | Validate schema before generation |
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 📁 Generated entities.ts Example
|
|
146
|
+
|
|
147
|
+
Given this schema:
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"tasks": {
|
|
151
|
+
"title": { "type": "string", "max_length": 200, "required": true },
|
|
152
|
+
"status": { "type": "string", "max_length": 50, "enum": ["pending", "done"] },
|
|
153
|
+
"due_date": { "type": "date" }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The generator creates:
|
|
159
|
+
```typescript
|
|
160
|
+
// entities.ts - Generated by RationalBloks MCP
|
|
161
|
+
// DO NOT EDIT MANUALLY
|
|
162
|
+
|
|
163
|
+
import type { BaseEntity, CreateInput, UpdateInput, EntityRegistry } from '@rationalbloks/frontblok-crud';
|
|
164
|
+
|
|
165
|
+
// ----------------------------------------------------------------------------
|
|
166
|
+
// Task
|
|
167
|
+
// ----------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
export interface Task extends BaseEntity {
|
|
170
|
+
title: string;
|
|
171
|
+
status?: 'pending' | 'done';
|
|
172
|
+
due_date?: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export type CreateTaskInput = CreateInput<Task>;
|
|
176
|
+
export type UpdateTaskInput = UpdateInput<Task>;
|
|
177
|
+
|
|
178
|
+
// ============================================================================
|
|
179
|
+
// ENTITY REGISTRY
|
|
180
|
+
// ============================================================================
|
|
181
|
+
|
|
182
|
+
export const entities = {
|
|
183
|
+
tasks: {
|
|
184
|
+
singular: 'task',
|
|
185
|
+
plural: 'tasks',
|
|
186
|
+
fields: {
|
|
187
|
+
title: { type: 'string', required: true, max_length: 200 },
|
|
188
|
+
status: { type: 'string', enum: ['pending', 'done'] },
|
|
189
|
+
due_date: { type: 'date' }
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
} as const satisfies EntityRegistry;
|
|
193
|
+
|
|
194
|
+
export type Entities = typeof entities;
|
|
195
|
+
export type EntityName = keyof Entities;
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 🏗️ Architecture
|
|
201
|
+
|
|
202
|
+
### Files in this package:
|
|
203
|
+
|
|
204
|
+
| File | Purpose | Generated? |
|
|
205
|
+
|------|---------|------------|
|
|
206
|
+
| `api.ts` | Universal CRUD API client | ❌ NO (generic) |
|
|
207
|
+
| `hooks.ts` | React hooks for data fetching | ❌ NO (generic) |
|
|
208
|
+
| `types.ts` | TypeScript type definitions | ❌ NO (generic) |
|
|
209
|
+
| `utils.ts` | Helper functions | ❌ NO (generic) |
|
|
210
|
+
| `generator.ts` | Generates entities.ts | ❌ NO (used by MCP) |
|
|
211
|
+
|
|
212
|
+
### What gets generated in user's project:
|
|
213
|
+
|
|
214
|
+
| File | Purpose | Generated? |
|
|
215
|
+
|------|---------|------------|
|
|
216
|
+
| `entities.ts` | TypeScript interfaces + entity registry | ✅ YES |
|
|
217
|
+
|
|
218
|
+
**That's it. ONE file. ~50 lines.**
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## 🔗 Integration with frontblok-auth
|
|
223
|
+
|
|
224
|
+
This package builds on top of `@rationalbloks/frontblok-auth`:
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
228
|
+
│ USER'S PROJECT │
|
|
229
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
230
|
+
│ │
|
|
231
|
+
│ ┌───────────────────────────────────────────────────────────────────┐ │
|
|
232
|
+
│ │ UI LAYER (user's choice) │ │
|
|
233
|
+
│ │ - React components │ │
|
|
234
|
+
│ │ - Routing (react-router) │ │
|
|
235
|
+
│ │ - Styling (MUI, Tailwind, etc.) │ │
|
|
236
|
+
│ └───────────────────────────────────────────────────────────────────┘ │
|
|
237
|
+
│ │ │
|
|
238
|
+
│ ▼ │
|
|
239
|
+
│ ┌───────────────────────────────────────────────────────────────────┐ │
|
|
240
|
+
│ │ @rationalbloks/frontblok-crud │ │
|
|
241
|
+
│ │ - entities.ts (GENERATED - types + metadata) │ │
|
|
242
|
+
│ │ - api.ts (GENERIC - universal CRUD) │ │
|
|
243
|
+
│ │ - hooks.ts (GENERIC - React data hooks) │ │
|
|
244
|
+
│ └───────────────────────────────────────────────────────────────────┘ │
|
|
245
|
+
│ │ │
|
|
246
|
+
│ ▼ │
|
|
247
|
+
│ ┌───────────────────────────────────────────────────────────────────┐ │
|
|
248
|
+
│ │ @rationalbloks/frontblok-auth │ │
|
|
249
|
+
│ │ - BaseApi (HTTP client with auth) │ │
|
|
250
|
+
│ │ - Auth hooks │ │
|
|
251
|
+
│ │ - Token management │ │
|
|
252
|
+
│ └───────────────────────────────────────────────────────────────────┘ │
|
|
253
|
+
│ │ │
|
|
254
|
+
└───────────────────────────────┼─────────────────────────────────────────┘
|
|
255
|
+
│
|
|
256
|
+
▼
|
|
257
|
+
┌──────────────────────┐
|
|
258
|
+
│ RationalBloks API │
|
|
259
|
+
│ (generated by │
|
|
260
|
+
│ builderblok) │
|
|
261
|
+
└──────────────────────┘
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## 📝 License
|
|
267
|
+
|
|
268
|
+
MIT © RationalBloks Team
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { BaseEntity } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal interface for the auth API client.
|
|
4
|
+
* Matches frontblok-auth's BaseApi.request() signature.
|
|
5
|
+
*/
|
|
6
|
+
export interface AuthApiClient {
|
|
7
|
+
request<T>(endpoint: string, options?: RequestInit): Promise<T>;
|
|
8
|
+
}
|
|
9
|
+
export declare class UniversalApi {
|
|
10
|
+
private authApi;
|
|
11
|
+
constructor(authApi: AuthApiClient);
|
|
12
|
+
/**
|
|
13
|
+
* Get all entities of a type.
|
|
14
|
+
*
|
|
15
|
+
* @param entityName - The entity/table name (e.g., 'tasks', 'projects')
|
|
16
|
+
* @param options - Optional query parameters
|
|
17
|
+
* @returns Array of entities
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* const tasks = await api.getAll<Task>('tasks');
|
|
21
|
+
* const activeTasks = await api.getAll<Task>('tasks', { status: 'active' });
|
|
22
|
+
*/
|
|
23
|
+
getAll<T extends BaseEntity>(entityName: string, options?: {
|
|
24
|
+
skip?: number;
|
|
25
|
+
limit?: number;
|
|
26
|
+
[key: string]: unknown;
|
|
27
|
+
}): Promise<T[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Get a single entity by ID.
|
|
30
|
+
*
|
|
31
|
+
* @param entityName - The entity/table name
|
|
32
|
+
* @param id - Entity UUID
|
|
33
|
+
* @returns The entity or throws if not found
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const task = await api.getOne<Task>('tasks', 'uuid-here');
|
|
37
|
+
*/
|
|
38
|
+
getOne<T extends BaseEntity>(entityName: string, id: string): Promise<T>;
|
|
39
|
+
/**
|
|
40
|
+
* Create a new entity.
|
|
41
|
+
*
|
|
42
|
+
* @param entityName - The entity/table name
|
|
43
|
+
* @param data - Entity data (without id, created_at, updated_at)
|
|
44
|
+
* @returns The created entity with all fields
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* const task = await api.create<Task>('tasks', { title: 'New Task', status: 'pending' });
|
|
48
|
+
*/
|
|
49
|
+
create<T extends BaseEntity>(entityName: string, data: Partial<Omit<T, 'id' | 'created_at' | 'updated_at'>>): Promise<T>;
|
|
50
|
+
/**
|
|
51
|
+
* Update an existing entity.
|
|
52
|
+
*
|
|
53
|
+
* @param entityName - The entity/table name
|
|
54
|
+
* @param id - Entity UUID
|
|
55
|
+
* @param data - Partial entity data to update
|
|
56
|
+
* @returns The updated entity
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* const task = await api.update<Task>('tasks', 'uuid-here', { status: 'done' });
|
|
60
|
+
*/
|
|
61
|
+
update<T extends BaseEntity>(entityName: string, id: string, data: Partial<Omit<T, 'id' | 'created_at' | 'updated_at'>>): Promise<T>;
|
|
62
|
+
/**
|
|
63
|
+
* Delete an entity.
|
|
64
|
+
*
|
|
65
|
+
* @param entityName - The entity/table name
|
|
66
|
+
* @param id - Entity UUID
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* await api.remove('tasks', 'uuid-here');
|
|
70
|
+
*/
|
|
71
|
+
remove(entityName: string, id: string): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Smart pluralization that matches builderblok's routes.py.
|
|
74
|
+
* Mirrors the Python `pluralize()` function exactly.
|
|
75
|
+
*/
|
|
76
|
+
private pluralize;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Initialize the global CRUD API instance.
|
|
80
|
+
* Call this once at app startup (e.g., in main.tsx).
|
|
81
|
+
*
|
|
82
|
+
* @param authApi - The frontblok-auth BaseApi instance (from createAuthApi)
|
|
83
|
+
* @returns The initialized UniversalApi instance
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* // In main.tsx or App.tsx
|
|
87
|
+
* import { createAuthApi } from '@rationalbloks/frontblok-auth';
|
|
88
|
+
* import { initApi } from '@rationalbloks/frontblok-crud';
|
|
89
|
+
*
|
|
90
|
+
* const authApi = createAuthApi(import.meta.env.VITE_DATABASE_API_URL);
|
|
91
|
+
* initApi(authApi);
|
|
92
|
+
*/
|
|
93
|
+
export declare function initApi(authApi: AuthApiClient): UniversalApi;
|
|
94
|
+
/**
|
|
95
|
+
* Get the global CRUD API instance.
|
|
96
|
+
* Throws if initApi hasn't been called.
|
|
97
|
+
*
|
|
98
|
+
* @returns The UniversalApi instance
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* import { getApi } from '@rationalbloks/frontblok-crud';
|
|
102
|
+
*
|
|
103
|
+
* const tasks = await getApi().getAll<Task>('tasks';
|
|
104
|
+
*/
|
|
105
|
+
export declare function getApi(): UniversalApi;
|
|
106
|
+
/**
|
|
107
|
+
* Check if the API has been initialized.
|
|
108
|
+
* Useful for conditional rendering or lazy initialization.
|
|
109
|
+
*/
|
|
110
|
+
export declare function isApiInitialized(): boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Reset the API instance (useful for testing).
|
|
113
|
+
*/
|
|
114
|
+
export declare function resetApi(): void;
|
|
115
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAQ7C;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACjE;AAQD,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAgB;gBAEnB,OAAO,EAAE,aAAa;IAUlC;;;;;;;;;;OAUG;IACG,MAAM,CAAC,CAAC,SAAS,UAAU,EAC/B,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,GAClE,OAAO,CAAC,CAAC,EAAE,CAAC;IAmBf;;;;;;;;;OASG;IACG,MAAM,CAAC,CAAC,SAAS,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAI9E;;;;;;;;;OASG;IACG,MAAM,CAAC,CAAC,SAAS,UAAU,EAC/B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,GAAG,YAAY,GAAG,YAAY,CAAC,CAAC,GACzD,OAAO,CAAC,CAAC,CAAC;IAOb;;;;;;;;;;OAUG;IACG,MAAM,CAAC,CAAC,SAAS,UAAU,EAC/B,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,GAAG,YAAY,GAAG,YAAY,CAAC,CAAC,GACzD,OAAO,CAAC,CAAC,CAAC;IAOb;;;;;;;;OAQG;IACG,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU3D;;;OAGG;IACH,OAAO,CAAC,SAAS;CA2BlB;AAUD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,aAAa,GAAG,YAAY,CAG5D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,MAAM,IAAI,YAAY,CAYrC;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAED;;GAEG;AACH,wBAAgB,QAAQ,IAAI,IAAI,CAE/B"}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// FRONTBLOK-CRUD - UNIVERSAL CRUD API CLIENT
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Single class that handles ALL CRUD operations for ANY entity type.
|
|
5
|
+
// Uses frontblok-auth's BaseApi for HTTP requests - NO DUPLICATE LOGIC!
|
|
6
|
+
//
|
|
7
|
+
// ARCHITECTURE (THE ONE WAY):
|
|
8
|
+
// ┌─────────────────────────────────────────────────────────────────────────┐
|
|
9
|
+
// │ frontblok-auth.BaseApi → Auth + HTTP (the ONLY HTTP layer) │
|
|
10
|
+
// │ frontblok-crud.UniversalApi → CRUD methods using BaseApi │
|
|
11
|
+
// └─────────────────────────────────────────────────────────────────────────┘
|
|
12
|
+
//
|
|
13
|
+
// CHAIN OF EVENTS MANTRA:
|
|
14
|
+
// - Errors propagate up to the caller (hooks handle them)
|
|
15
|
+
// - No try/catch here - let errors bubble for consistent handling
|
|
16
|
+
// - Single path, single way, the right way
|
|
17
|
+
// - ONE WAY to make HTTP calls: through frontblok-auth's BaseApi
|
|
18
|
+
//
|
|
19
|
+
// USAGE:
|
|
20
|
+
// ```typescript
|
|
21
|
+
// import { createAuthApi } from '@rationalbloks/frontblok-auth';
|
|
22
|
+
// import { initApi, getApi } from '@rationalbloks/frontblok-crud';
|
|
23
|
+
//
|
|
24
|
+
// // Initialize once at app startup
|
|
25
|
+
// const authApi = createAuthApi(import.meta.env.VITE_DATABASE_API_URL);
|
|
26
|
+
// initApi(authApi);
|
|
27
|
+
//
|
|
28
|
+
// // Use anywhere
|
|
29
|
+
// const tasks = await getApi().getAll<Task>('tasks');
|
|
30
|
+
// const task = await getApi().create<Task>('tasks', { title: 'New Task' });
|
|
31
|
+
// ```
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// UNIVERSAL CRUD API CLASS
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// Single class that handles ALL CRUD operations for ANY entity type.
|
|
37
|
+
// Delegates HTTP to frontblok-auth's BaseApi - NO DUPLICATE HTTP LOGIC!
|
|
38
|
+
export class UniversalApi {
|
|
39
|
+
constructor(authApi) {
|
|
40
|
+
this.authApi = authApi;
|
|
41
|
+
}
|
|
42
|
+
// ==========================================================================
|
|
43
|
+
// UNIVERSAL CRUD OPERATIONS
|
|
44
|
+
// ==========================================================================
|
|
45
|
+
// These methods work with ANY entity - just pass the entity name.
|
|
46
|
+
// Mirrors builderblok's UniversalCRUD class methods.
|
|
47
|
+
/**
|
|
48
|
+
* Get all entities of a type.
|
|
49
|
+
*
|
|
50
|
+
* @param entityName - The entity/table name (e.g., 'tasks', 'projects')
|
|
51
|
+
* @param options - Optional query parameters
|
|
52
|
+
* @returns Array of entities
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* const tasks = await api.getAll<Task>('tasks');
|
|
56
|
+
* const activeTasks = await api.getAll<Task>('tasks', { status: 'active' });
|
|
57
|
+
*/
|
|
58
|
+
async getAll(entityName, options) {
|
|
59
|
+
// Build query string from options
|
|
60
|
+
let query = '';
|
|
61
|
+
if (options) {
|
|
62
|
+
const params = new URLSearchParams();
|
|
63
|
+
for (const [key, value] of Object.entries(options)) {
|
|
64
|
+
if (value !== undefined && value !== null) {
|
|
65
|
+
params.append(key, String(value));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const queryString = params.toString();
|
|
69
|
+
if (queryString) {
|
|
70
|
+
query = `?${queryString}`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return this.authApi.request(`/api/${this.pluralize(entityName)}/${query}`);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get a single entity by ID.
|
|
77
|
+
*
|
|
78
|
+
* @param entityName - The entity/table name
|
|
79
|
+
* @param id - Entity UUID
|
|
80
|
+
* @returns The entity or throws if not found
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* const task = await api.getOne<Task>('tasks', 'uuid-here');
|
|
84
|
+
*/
|
|
85
|
+
async getOne(entityName, id) {
|
|
86
|
+
return this.authApi.request(`/api/${this.pluralize(entityName)}/${id}`);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Create a new entity.
|
|
90
|
+
*
|
|
91
|
+
* @param entityName - The entity/table name
|
|
92
|
+
* @param data - Entity data (without id, created_at, updated_at)
|
|
93
|
+
* @returns The created entity with all fields
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* const task = await api.create<Task>('tasks', { title: 'New Task', status: 'pending' });
|
|
97
|
+
*/
|
|
98
|
+
async create(entityName, data) {
|
|
99
|
+
return this.authApi.request(`/api/${this.pluralize(entityName)}/`, {
|
|
100
|
+
method: 'POST',
|
|
101
|
+
body: JSON.stringify(data),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Update an existing entity.
|
|
106
|
+
*
|
|
107
|
+
* @param entityName - The entity/table name
|
|
108
|
+
* @param id - Entity UUID
|
|
109
|
+
* @param data - Partial entity data to update
|
|
110
|
+
* @returns The updated entity
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* const task = await api.update<Task>('tasks', 'uuid-here', { status: 'done' });
|
|
114
|
+
*/
|
|
115
|
+
async update(entityName, id, data) {
|
|
116
|
+
return this.authApi.request(`/api/${this.pluralize(entityName)}/${id}`, {
|
|
117
|
+
method: 'PATCH',
|
|
118
|
+
body: JSON.stringify(data),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Delete an entity.
|
|
123
|
+
*
|
|
124
|
+
* @param entityName - The entity/table name
|
|
125
|
+
* @param id - Entity UUID
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* await api.remove('tasks', 'uuid-here');
|
|
129
|
+
*/
|
|
130
|
+
async remove(entityName, id) {
|
|
131
|
+
await this.authApi.request(`/api/${this.pluralize(entityName)}/${id}`, {
|
|
132
|
+
method: 'DELETE',
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// ==========================================================================
|
|
136
|
+
// UTILITY METHODS
|
|
137
|
+
// ==========================================================================
|
|
138
|
+
/**
|
|
139
|
+
* Smart pluralization that matches builderblok's routes.py.
|
|
140
|
+
* Mirrors the Python `pluralize()` function exactly.
|
|
141
|
+
*/
|
|
142
|
+
pluralize(word) {
|
|
143
|
+
if (!word)
|
|
144
|
+
return word;
|
|
145
|
+
// Already ends with 's'? Don't add another one
|
|
146
|
+
if (word.toLowerCase().endsWith('s')) {
|
|
147
|
+
return word;
|
|
148
|
+
}
|
|
149
|
+
// Ends with 'y' preceded by consonant? Replace 'y' with 'ies'
|
|
150
|
+
if (word.length > 1 && word.slice(-1).toLowerCase() === 'y') {
|
|
151
|
+
const secondLast = word.slice(-2, -1).toLowerCase();
|
|
152
|
+
if (!'aeiou'.includes(secondLast)) {
|
|
153
|
+
return word.slice(0, -1) + 'ies';
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Ends with 'ch', 'sh', 'x', 'z', 'o'? Add 'es'
|
|
157
|
+
const esEndings = ['ch', 'sh', 'x', 'z', 'o'];
|
|
158
|
+
for (const ending of esEndings) {
|
|
159
|
+
if (word.toLowerCase().endsWith(ending)) {
|
|
160
|
+
return word + 'es';
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Default: just add 's'
|
|
164
|
+
return word + 's';
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// ============================================================================
|
|
168
|
+
// SINGLETON INSTANCE MANAGEMENT
|
|
169
|
+
// ============================================================================
|
|
170
|
+
// Global API instance - initialized once, used everywhere.
|
|
171
|
+
// THE ONE WAY: initApi(authApi) with frontblok-auth's BaseApi instance.
|
|
172
|
+
let apiInstance = null;
|
|
173
|
+
/**
|
|
174
|
+
* Initialize the global CRUD API instance.
|
|
175
|
+
* Call this once at app startup (e.g., in main.tsx).
|
|
176
|
+
*
|
|
177
|
+
* @param authApi - The frontblok-auth BaseApi instance (from createAuthApi)
|
|
178
|
+
* @returns The initialized UniversalApi instance
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* // In main.tsx or App.tsx
|
|
182
|
+
* import { createAuthApi } from '@rationalbloks/frontblok-auth';
|
|
183
|
+
* import { initApi } from '@rationalbloks/frontblok-crud';
|
|
184
|
+
*
|
|
185
|
+
* const authApi = createAuthApi(import.meta.env.VITE_DATABASE_API_URL);
|
|
186
|
+
* initApi(authApi);
|
|
187
|
+
*/
|
|
188
|
+
export function initApi(authApi) {
|
|
189
|
+
apiInstance = new UniversalApi(authApi);
|
|
190
|
+
return apiInstance;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Get the global CRUD API instance.
|
|
194
|
+
* Throws if initApi hasn't been called.
|
|
195
|
+
*
|
|
196
|
+
* @returns The UniversalApi instance
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* import { getApi } from '@rationalbloks/frontblok-crud';
|
|
200
|
+
*
|
|
201
|
+
* const tasks = await getApi().getAll<Task>('tasks';
|
|
202
|
+
*/
|
|
203
|
+
export function getApi() {
|
|
204
|
+
if (!apiInstance) {
|
|
205
|
+
throw new Error('API not initialized. Call initApi(authApi) first, typically in main.tsx or App.tsx.\n' +
|
|
206
|
+
'Example:\n' +
|
|
207
|
+
' import { createAuthApi } from "@rationalbloks/frontblok-auth";\n' +
|
|
208
|
+
' import { initApi } from "@rationalbloks/frontblok-crud";\n' +
|
|
209
|
+
' const authApi = createAuthApi(API_URL);\n' +
|
|
210
|
+
' initApi(authApi);');
|
|
211
|
+
}
|
|
212
|
+
return apiInstance;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Check if the API has been initialized.
|
|
216
|
+
* Useful for conditional rendering or lazy initialization.
|
|
217
|
+
*/
|
|
218
|
+
export function isApiInitialized() {
|
|
219
|
+
return apiInstance !== null;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Reset the API instance (useful for testing).
|
|
223
|
+
*/
|
|
224
|
+
export function resetApi() {
|
|
225
|
+
apiInstance = null;
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Schema } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generate complete entities.ts content from a schema.
|
|
4
|
+
* This is THE ONLY generation function needed.
|
|
5
|
+
*
|
|
6
|
+
* @param schema - The database schema in FLAT format
|
|
7
|
+
* @returns Complete TypeScript file content
|
|
8
|
+
*/
|
|
9
|
+
export declare function generateEntitiesTs(schema: Schema): string;
|
|
10
|
+
/**
|
|
11
|
+
* Validate a schema before generation.
|
|
12
|
+
* Returns array of error messages (empty if valid).
|
|
13
|
+
*/
|
|
14
|
+
export declare function validateSchema(schema: Schema): string[];
|
|
15
|
+
//# sourceMappingURL=generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,MAAM,EAAwC,MAAM,YAAY,CAAC;AAQ/E;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAyBzD;AA4ID;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAuDvD"}
|