@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 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
@@ -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"}