nuxt-auto-crud 1.31.0 → 2.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 +197 -31
- package/dist/module.d.mts +16 -46
- package/dist/module.json +2 -2
- package/dist/module.mjs +49 -92
- package/dist/runtime/composables/{useAutoCrudSSE.d.ts → useNacAutoCrudSSE.d.ts} +1 -1
- package/dist/runtime/composables/useNacAutoCrudSSE.js +21 -0
- package/dist/runtime/server/api/_nac/[model]/[id].delete.js +22 -0
- package/dist/runtime/server/api/{[model] → _nac/[model]}/[id].get.d.ts +1 -1
- package/dist/runtime/server/api/_nac/[model]/[id].get.js +10 -0
- package/dist/runtime/server/api/{[model] → _nac/[model]}/[id].patch.d.ts +1 -1
- package/dist/runtime/server/api/_nac/[model]/[id].patch.js +25 -0
- package/dist/runtime/server/api/{_schema → _nac/[model]}/index.get.d.ts +1 -1
- package/dist/runtime/server/api/_nac/[model]/index.get.js +11 -0
- package/dist/runtime/server/api/{[model] → _nac/[model]}/index.post.d.ts +1 -1
- package/dist/runtime/server/api/_nac/[model]/index.post.js +25 -0
- package/dist/runtime/server/api/{_meta.get.d.ts → _nac/_meta.get.d.ts} +4 -4
- package/dist/runtime/server/api/_nac/_meta.get.js +72 -0
- package/dist/runtime/server/api/_nac/_schemas/[model].get.d.ts +2 -0
- package/dist/runtime/server/api/_nac/_schemas/[model].get.js +10 -0
- package/dist/runtime/server/api/_nac/_schemas/index.get.d.ts +2 -0
- package/dist/runtime/server/api/_nac/_schemas/index.get.js +5 -0
- package/dist/runtime/server/api/{sse.js → _nac/_sse.get.js} +17 -15
- package/dist/runtime/server/exceptions.d.ts +37 -4
- package/dist/runtime/server/exceptions.js +58 -6
- package/dist/runtime/server/middleware/nac-guard.d.ts +2 -0
- package/dist/runtime/server/middleware/nac-guard.js +43 -0
- package/dist/runtime/server/types.d.ts +4 -4
- package/dist/runtime/server/utils/constants.d.ts +18 -2
- package/dist/runtime/server/utils/constants.js +18 -22
- package/dist/runtime/server/utils/modelMapper.d.ts +36 -20
- package/dist/runtime/server/utils/modelMapper.js +99 -118
- package/dist/runtime/server/utils/queries.d.ts +39 -0
- package/dist/runtime/server/utils/queries.js +78 -0
- package/dist/runtime/server/utils/sse-bus.d.ts +2 -2
- package/dist/runtime/server/utils/sse-bus.js +18 -26
- package/dist/runtime/server/utils/validator.d.ts +43 -0
- package/dist/runtime/server/utils/validator.js +22 -0
- package/dist/runtime/shared/utils/helpers.d.ts +7 -0
- package/dist/runtime/shared/utils/helpers.js +6 -0
- package/dist/runtime/shared/utils/types.d.ts +13 -0
- package/dist/runtime/shared/utils/types.js +0 -0
- package/dist/runtime/types/index.d.ts +22 -0
- package/package.json +44 -38
- package/dist/runtime/auth.d.ts +0 -6
- package/dist/runtime/composables/useAutoCrudSSE.js +0 -13
- package/dist/runtime/composables/useRelationDisplay.d.ts +0 -13
- package/dist/runtime/composables/useRelationDisplay.js +0 -54
- package/dist/runtime/composables/useResourceSchemas.d.ts +0 -19
- package/dist/runtime/composables/useResourceSchemas.js +0 -17
- package/dist/runtime/server/api/[model]/[id].delete.d.ts +0 -2
- package/dist/runtime/server/api/[model]/[id].delete.js +0 -27
- package/dist/runtime/server/api/[model]/[id].get.js +0 -29
- package/dist/runtime/server/api/[model]/[id].patch.js +0 -49
- package/dist/runtime/server/api/[model]/index.get.js +0 -59
- package/dist/runtime/server/api/[model]/index.post.js +0 -48
- package/dist/runtime/server/api/_meta.get.js +0 -91
- package/dist/runtime/server/api/_relations.get.d.ts +0 -2
- package/dist/runtime/server/api/_relations.get.js +0 -7
- package/dist/runtime/server/api/_schema/[table].get.d.ts +0 -6
- package/dist/runtime/server/api/_schema/[table].get.js +0 -15
- package/dist/runtime/server/api/_schema/index.get.js +0 -7
- package/dist/runtime/server/stubs/auth.d.ts +0 -8
- package/dist/runtime/server/stubs/auth.js +0 -11
- package/dist/runtime/server/tsconfig.json +0 -3
- package/dist/runtime/server/utils/auth.d.ts +0 -3
- package/dist/runtime/server/utils/auth.js +0 -97
- package/dist/runtime/server/utils/config.d.ts +0 -2
- package/dist/runtime/server/utils/config.js +0 -4
- package/dist/runtime/server/utils/handler.d.ts +0 -4
- package/dist/runtime/server/utils/handler.js +0 -32
- package/dist/runtime/server/utils/jwt.d.ts +0 -2
- package/dist/runtime/server/utils/jwt.js +0 -19
- package/dist/runtime/server/utils/schema.d.ts +0 -19
- package/dist/runtime/server/utils/schema.js +0 -104
- package/src/runtime/auth.d.ts +0 -6
- package/src/runtime/composables/useAutoCrudSSE.ts +0 -24
- package/src/runtime/composables/useRelationDisplay.ts +0 -74
- package/src/runtime/composables/useResourceSchemas.ts +0 -42
- package/src/runtime/server/api/[model]/[id].delete.ts +0 -44
- package/src/runtime/server/api/[model]/[id].get.ts +0 -48
- package/src/runtime/server/api/[model]/[id].patch.ts +0 -83
- package/src/runtime/server/api/[model]/index.get.ts +0 -93
- package/src/runtime/server/api/[model]/index.post.ts +0 -72
- package/src/runtime/server/api/_meta.get.ts +0 -111
- package/src/runtime/server/api/_relations.get.ts +0 -9
- package/src/runtime/server/api/_schema/[table].get.ts +0 -20
- package/src/runtime/server/api/_schema/index.get.ts +0 -9
- package/src/runtime/server/api/sse.ts +0 -39
- package/src/runtime/server/exceptions.ts +0 -25
- package/src/runtime/server/stubs/auth.ts +0 -11
- package/src/runtime/server/tsconfig.json +0 -3
- package/src/runtime/server/types.ts +0 -5
- package/src/runtime/server/utils/auth.ts +0 -152
- package/src/runtime/server/utils/config.ts +0 -6
- package/src/runtime/server/utils/constants.ts +0 -25
- package/src/runtime/server/utils/handler.ts +0 -43
- package/src/runtime/server/utils/jwt.ts +0 -23
- package/src/runtime/server/utils/modelMapper.ts +0 -197
- package/src/runtime/server/utils/schema.ts +0 -160
- package/src/runtime/server/utils/sse-bus.ts +0 -44
- /package/dist/runtime/server/api/{[model]/index.get.d.ts → _nac/[model]/[id].delete.d.ts} +0 -0
- /package/dist/runtime/server/api/{sse.d.ts → _nac/_sse.get.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,44 +1,210 @@
|
|
|
1
|
-
|
|
1
|
+
## nuxt-auto-crud (nac 2.0)
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Zero-Codegen Dynamic RESTful CRUD APIs** derived directly from schemas. It eliminates the need to manually write or generate boilerplate for CRUD operations.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
### 🚀 Core Features
|
|
8
|
+
|
|
9
|
+
* **Zero-Codegen Dynamic RESTful CRUD APIs**: nuxt-auto-crud leverages Drizzle ORM, Zod, Nuxt, and Nitro to eliminate the need for manual CRUD coding.
|
|
10
|
+
* **Single Source of Truth (SSOT)**: Your Drizzle schemas (`schema/db/schema`) define the entire API structure and validation.
|
|
11
|
+
* **Constant Bundle Size**: Since no code is generated, the bundle size remains virtually identical whether you have one table or one hundred (scaling only with your schema definitions).
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
### 🌐 Dynamic RESTful CRUD endpoints
|
|
16
|
+
|
|
17
|
+
Nb: Endpoints follow the pattern `/api/_nac/:model`.
|
|
18
|
+
|
|
19
|
+
| Method | Endpoint | Action |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| **GET** | `/:model` | List records (supports filtering & pagination) |
|
|
22
|
+
| **POST** | `/:model` | Create record with Zod validation |
|
|
23
|
+
| **GET** | `/:model/:id` | Fetch single record |
|
|
24
|
+
| **PATCH** | `/:model/:id` | Partial update with validation |
|
|
25
|
+
| **DELETE** | `/:model/:id` | Hard delete |
|
|
26
|
+
|
|
27
|
+
**Example (`users` table):**
|
|
28
|
+
|
|
29
|
+
| Action | HTTP Method | Endpoint | Example Result |
|
|
30
|
+
| --- | --- | --- | --- |
|
|
31
|
+
| **Fetch All** | `GET` | `/api/_nac/users` | List of all users (paginated) |
|
|
32
|
+
| **Create** | `POST` | `/api/_nac/users` | New user record added |
|
|
33
|
+
| **Fetch One** | `GET` | `/api/_nac/users/1` | Details of user with `id: 1` |
|
|
34
|
+
| **Update** | `PATCH` | `/api/_nac/users/1` | Partial update to user `1` |
|
|
35
|
+
| **Delete** | `DELETE` | `/api/_nac/users/1` | User `1` hard deleted from DB |
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
### 🛠 Frontend Integration APIs
|
|
40
|
+
|
|
41
|
+
In addition to CRUD endpoints, **nac** provides metadata APIs to power dynamic forms and tables in your frontend.
|
|
42
|
+
|
|
43
|
+
* **List Resources**: `GET /api/_nac/_schemas` returns all tables (excluding system-protected tables).
|
|
44
|
+
* **Resource Metadata**: `GET /api/_nac/_schemas/:resource` returns the field definitions, validation rules, and relationship data for a specific table.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
### Schema Interface
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
export interface Field {
|
|
52
|
+
name: string
|
|
53
|
+
type: string
|
|
54
|
+
required?: boolean
|
|
55
|
+
selectOptions?: string[]
|
|
56
|
+
references?: string
|
|
57
|
+
isReadOnly?: boolean
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface SchemaDefinition {
|
|
61
|
+
resource: string
|
|
62
|
+
labelField: string
|
|
63
|
+
fields: Field[]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Example Response
|
|
69
|
+
|
|
70
|
+
`GET /api/_nac/_schemas/users`
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"resource": "users",
|
|
75
|
+
"labelField": "name",
|
|
76
|
+
"fields": [
|
|
77
|
+
{ "name": "id", "type": "string", "required": true, "isReadOnly": true },
|
|
78
|
+
{ "name": "name", "type": "string", "required": true, "isReadOnly": false },
|
|
79
|
+
{ "name": "email", "type": "string", "required": true, "isReadOnly": false }
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
### 🛡 Security & Configuration
|
|
11
87
|
|
|
12
|
-
|
|
13
|
-
NAC treats your Drizzle schema as the **Single Source of Truth (SSOT)**. Unlike traditional scaffolds, it does not generate physical files; it mounts dynamic Nitro handlers at runtime.
|
|
88
|
+
Enabling `authentication` in the `autoCrud` config protects all **nac** routes (`/api/_nac/*`), except those explicitly defined in `publicResources`.
|
|
14
89
|
|
|
15
|
-
|
|
16
|
-
* **Real-time Sync**: Built-in SSE broadcasting for `create`, `update`, and `delete` events.
|
|
17
|
-
* **Agentic Compatibility**: Built with an MCP-friendly structure to allow AI Agents to interact directly with the schema-driven API.
|
|
90
|
+
#### 🔒 Access Control & Data Safety
|
|
18
91
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* **
|
|
22
|
-
* **Granular Scopes**: Fine-grained control over `list` vs `list_all` (drafts/soft-deleted).
|
|
92
|
+
* **`apiHiddenFields`**: Globally hides sensitive columns from all API responses. Default: `['password', 'secret', 'token', 'reset_token', 'reset_expires', 'github_id', 'google_id']`.
|
|
93
|
+
* **`formHiddenFields`**: Columns excluded from the frontend schema metadata to prevent user input. Defaults to `apiHiddenFields` plus system-managed fields like `id`, `uuid`, `createdAt`, `updatedAt`, `createdBy`, etc.
|
|
94
|
+
* **Validation Logic**: If a field is in `apiHiddenFields` or does not exist in the schema, it is silently stripped from the response even if listed in `publicResources`.
|
|
23
95
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
#### ⚙️ Configuration Reference
|
|
99
|
+
|
|
100
|
+
| Key | Default | Description |
|
|
101
|
+
| --- | --- | --- |
|
|
102
|
+
| `realtime` | `false` | Enables/disables real-time capabilities. |
|
|
103
|
+
| `auth.authentication` | `true` | Requires a valid session for all NAC routes. |
|
|
104
|
+
| `auth.authorization` | `true` | Enables role/owner-based access checks. |
|
|
105
|
+
| `auth.ownerKey` | `'ownerId'` | The column name used to identify the record creator. |
|
|
106
|
+
| `publicResources` | `{}` | Defines tables and specific columns accessible without auth. |
|
|
107
|
+
| `nacEndpointPrefix` | `'/api/_nac'` | The base path for NAC routes. Access via `useRuntimeConfig().public.autoCrud`. |
|
|
108
|
+
| `schemaPath` | `'server/db/schema'` | Location of your Drizzle schema files. |
|
|
109
|
+
|
|
110
|
+
#### Example `nuxt.config.ts`
|
|
32
111
|
|
|
112
|
+
```typescript
|
|
113
|
+
autoCrud: {
|
|
114
|
+
realtime: false,
|
|
115
|
+
auth: {
|
|
116
|
+
authentication: true,
|
|
117
|
+
authorization: true,
|
|
118
|
+
ownerKey: 'ownerId',
|
|
119
|
+
},
|
|
120
|
+
publicResources: {
|
|
121
|
+
users: ['id', 'name', 'email'],
|
|
122
|
+
},
|
|
123
|
+
apiHiddenFields: ['password'],
|
|
124
|
+
agenticToken: process.env.NAC_AGENTIC_TOKEN,
|
|
125
|
+
formHiddenFields: [],
|
|
126
|
+
nacEndpointPrefix: '/api/_nac',
|
|
127
|
+
schemaPath: 'server/db/schema',
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
> **Note**: Modify `nacEndpointPrefix` or `schemaPath` only if the Nuxt/Nitro conventions change or you use a non-standard directory structure.
|
|
33
133
|
---
|
|
134
|
+
### 🛡 Filtering & Performance Optimization
|
|
135
|
+
|
|
136
|
+
#### Automatic Status Filtering
|
|
34
137
|
|
|
35
|
-
|
|
36
|
-
It is highly recommended to use the [Template](https://auto-crud.clifland.in/docs/auto-crud) for new installations.
|
|
138
|
+
To align with standard application behavior, **nac** automatically filters records if a `status` column exists. By default, it will only return **active** records, reducing boilerplate for soft-state management.
|
|
37
139
|
|
|
38
|
-
|
|
140
|
+
#### Ownership & Permissions
|
|
39
141
|
|
|
40
|
-
|
|
142
|
+
While the implementing app handles the authentication layer, **nac** provides a standardized way to enforce record ownership and granular access.
|
|
41
143
|
|
|
42
|
-
|
|
144
|
+
If your middleware populates `event.context.nac` with `resourcePermissions`, **nac** automatically injects the necessary SQL filters.
|
|
145
|
+
|
|
146
|
+
**Example: Restricting users to their own records**
|
|
147
|
+
If the permissions array includes `'list_own'`, **nac** appends a filter where `ownerCol === userId`.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// Example: Setting context in your Auth Middleware
|
|
151
|
+
event.context.nac = {
|
|
152
|
+
userId: user.id,
|
|
153
|
+
resourcePermissions: user.permissions[model], // e.g., ['list_own', 'list']
|
|
154
|
+
record: null, // Optional: Pre-fetched record to prevent double-hitting the DB
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
#### Performance: The `record` Context
|
|
160
|
+
|
|
161
|
+
For `UPDATE`, `DELETE`, or `GET` (by ID) operations, **nac** must verify ownership.
|
|
162
|
+
|
|
163
|
+
* **Standard**: **nac** fetches the record to check the owner ID.
|
|
164
|
+
* **Optimized**: If your middleware has already fetched the record for validation, pass it to `event.context.nac.record`. **nac** will use this object instead of executing an additional database query.
|
|
165
|
+
---
|
|
43
166
|
|
|
44
|
-
|
|
167
|
+
### 📡 Real-time Synchronization (SSE)
|
|
168
|
+
|
|
169
|
+
When `realtime` is enabled, all `create`, `update`, and `delete` operations are automatically broadcasted:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
if (realtime) {
|
|
173
|
+
void broadcast({
|
|
174
|
+
table: model,
|
|
175
|
+
action: 'create',
|
|
176
|
+
primaryKey: newRecord.id,
|
|
177
|
+
data: newRecord,
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### Frontend Usage
|
|
184
|
+
|
|
185
|
+
NAC provides a `useNacAutoCrudSSE` composable to listen for these changes in your frontend:
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
useNacAutoCrudSSE(({ table, action, data: sseData, primaryKey }) => {
|
|
189
|
+
// Optional: Filter by specific table
|
|
190
|
+
// if (table !== currentTable.value) return
|
|
191
|
+
|
|
192
|
+
if (action === 'update') {
|
|
193
|
+
// updateRow(primaryKey, sseData)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (action === 'create') {
|
|
197
|
+
// addRow(sseData)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (action === 'delete') {
|
|
201
|
+
// removeRow(primaryKey)
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
---
|
|
207
|
+
## ⚠️ Limitations
|
|
208
|
+
**Database Support:** Currently optimized for SQLite/libSQL only.
|
|
209
|
+
|
|
210
|
+
---
|
package/dist/module.d.mts
CHANGED
|
@@ -1,58 +1,28 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
2
|
|
|
3
3
|
interface ModuleOptions {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* Authentication configuration
|
|
11
|
-
*/
|
|
12
|
-
auth?: boolean | AuthOptions;
|
|
13
|
-
/**
|
|
14
|
-
* Resource-specific configuration
|
|
15
|
-
* Define column visibility for unauthenticated users
|
|
16
|
-
*/
|
|
17
|
-
resources?: {
|
|
18
|
-
[modelName: string]: string[];
|
|
4
|
+
realtime: boolean;
|
|
5
|
+
schemaPath: string;
|
|
6
|
+
auth: {
|
|
7
|
+
authentication: boolean;
|
|
8
|
+
authorization: boolean;
|
|
9
|
+
ownerKey: string;
|
|
19
10
|
};
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
11
|
+
apiHiddenFields: string[]; /** Sensitive: Never leaves the server */
|
|
12
|
+
agenticToken: string;
|
|
13
|
+
publicResources: Record<string, string[]>; /** Allowed fields for public apis */
|
|
14
|
+
nacEndpointPrefix: string;
|
|
15
|
+
formHiddenFields: string[]; /** UI: Hidden from forms */
|
|
25
16
|
}
|
|
26
|
-
interface AuthOptions {
|
|
27
|
-
/**
|
|
28
|
-
* Authentication type
|
|
29
|
-
* @default 'session'
|
|
30
|
-
*/
|
|
31
|
-
type?: 'session' | 'jwt';
|
|
32
|
-
/**
|
|
33
|
-
* JWT Secret (required if type is 'jwt')
|
|
34
|
-
*/
|
|
35
|
-
jwtSecret?: string;
|
|
36
|
-
/**
|
|
37
|
-
* Enable authentication checks (requires nuxt-auth-utils for session)
|
|
38
|
-
* @default false
|
|
39
|
-
*/
|
|
40
|
-
authentication: boolean;
|
|
41
|
-
/**
|
|
42
|
-
* Enable authorization checks (requires nuxt-authorization)
|
|
43
|
-
* @default false
|
|
44
|
-
*/
|
|
45
|
-
authorization?: boolean;
|
|
46
|
-
}
|
|
47
|
-
interface RuntimeModuleOptions extends Omit<ModuleOptions, 'auth'> {
|
|
48
|
-
auth: AuthOptions;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
17
|
declare module '@nuxt/schema' {
|
|
52
18
|
interface RuntimeConfig {
|
|
53
|
-
autoCrud:
|
|
19
|
+
autoCrud: Omit<ModuleOptions, 'nacEndpointPrefix' | 'formHiddenFields'>;
|
|
20
|
+
}
|
|
21
|
+
interface PublicRuntimeConfig {
|
|
22
|
+
autoCrud: Pick<ModuleOptions, 'nacEndpointPrefix' | 'formHiddenFields'>;
|
|
54
23
|
}
|
|
55
24
|
}
|
|
25
|
+
|
|
56
26
|
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
57
27
|
|
|
58
28
|
export { _default as default };
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { defineNuxtModule, createResolver, addImportsDir,
|
|
1
|
+
import { defineNuxtModule, createResolver, addImportsDir, addServerImportsDir, addServerHandler } from '@nuxt/kit';
|
|
2
|
+
import { NAC_FORM_HIDDEN_FIELDS, NAC_API_HIDDEN_FIELDS } from '../dist/runtime/server/utils/constants.js';
|
|
2
3
|
|
|
3
4
|
const module$1 = defineNuxtModule({
|
|
4
5
|
meta: {
|
|
@@ -6,104 +7,60 @@ const module$1 = defineNuxtModule({
|
|
|
6
7
|
configKey: "autoCrud"
|
|
7
8
|
},
|
|
8
9
|
defaults: {
|
|
10
|
+
// Private config
|
|
11
|
+
realtime: false,
|
|
12
|
+
auth: {
|
|
13
|
+
authentication: false,
|
|
14
|
+
authorization: false,
|
|
15
|
+
ownerKey: "createdBy"
|
|
16
|
+
},
|
|
17
|
+
publicResources: {},
|
|
18
|
+
apiHiddenFields: NAC_API_HIDDEN_FIELDS,
|
|
19
|
+
agenticToken: "",
|
|
9
20
|
schemaPath: "server/db/schema",
|
|
10
|
-
|
|
21
|
+
// Public config
|
|
22
|
+
formHiddenFields: NAC_FORM_HIDDEN_FIELDS,
|
|
23
|
+
nacEndpointPrefix: "/api/_nac"
|
|
11
24
|
},
|
|
12
25
|
async setup(options, nuxt) {
|
|
26
|
+
const prefix = options.nacEndpointPrefix || "/api/_nac";
|
|
13
27
|
const resolver = createResolver(import.meta.url);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
nuxt.options.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
{ name: "getUserSession", from: stubsPath },
|
|
25
|
-
{ name: "hashPassword", from: stubsPath }
|
|
26
|
-
]);
|
|
27
|
-
}
|
|
28
|
-
if (!hasNuxtModule("nuxt-authorization")) {
|
|
29
|
-
addServerImports([
|
|
30
|
-
{ name: "allows", from: stubsPath },
|
|
31
|
-
{ name: "abilities", from: stubsPath },
|
|
32
|
-
{ name: "abilityLogic", from: stubsPath }
|
|
33
|
-
]);
|
|
34
|
-
}
|
|
35
|
-
nuxt.options.alias["#authorization"] ||= "nuxt-authorization/utils";
|
|
36
|
-
const mergedAuth = options.auth === false ? { authentication: false, authorization: false, type: "session" } : {
|
|
37
|
-
authentication: true,
|
|
38
|
-
authorization: options.auth === true,
|
|
39
|
-
type: "session",
|
|
40
|
-
...typeof options.auth === "object" ? options.auth : {}
|
|
41
|
-
};
|
|
42
|
-
nuxt.options.runtimeConfig.autoCrud = {
|
|
43
|
-
auth: {
|
|
44
|
-
authentication: mergedAuth.authentication ?? false,
|
|
45
|
-
authorization: mergedAuth.authorization ?? false,
|
|
46
|
-
type: mergedAuth.type ?? "session",
|
|
47
|
-
jwtSecret: mergedAuth.jwtSecret
|
|
48
|
-
},
|
|
49
|
-
resources: {
|
|
50
|
-
...options.resources
|
|
51
|
-
},
|
|
52
|
-
hashedFields: options.hashedFields ?? ["password"]
|
|
53
|
-
};
|
|
54
|
-
const apiDir = resolver.resolve("./runtime/server/api");
|
|
55
|
-
addServerHandler({
|
|
56
|
-
route: "/api/_schema",
|
|
57
|
-
method: "get",
|
|
58
|
-
handler: resolver.resolve(apiDir, "_schema/index.get")
|
|
59
|
-
});
|
|
60
|
-
addServerHandler({
|
|
61
|
-
route: "/api/_schema/:table",
|
|
62
|
-
method: "get",
|
|
63
|
-
handler: resolver.resolve(apiDir, "_schema/[table].get")
|
|
64
|
-
});
|
|
65
|
-
addServerHandler({
|
|
66
|
-
route: "/api/_relations",
|
|
67
|
-
method: "get",
|
|
68
|
-
handler: resolver.resolve(apiDir, "_relations.get")
|
|
69
|
-
});
|
|
70
|
-
addServerHandler({
|
|
71
|
-
route: "/api/_meta",
|
|
72
|
-
method: "get",
|
|
73
|
-
handler: resolver.resolve(apiDir, "_meta.get")
|
|
74
|
-
});
|
|
75
|
-
addServerHandler({
|
|
76
|
-
route: "/api/sse",
|
|
77
|
-
method: "get",
|
|
78
|
-
handler: resolver.resolve(apiDir, "sse")
|
|
79
|
-
});
|
|
80
|
-
addServerHandler({
|
|
81
|
-
route: "/api/:model",
|
|
82
|
-
method: "get",
|
|
83
|
-
handler: resolver.resolve(apiDir, "[model]/index.get")
|
|
84
|
-
});
|
|
85
|
-
addServerHandler({
|
|
86
|
-
route: "/api/:model",
|
|
87
|
-
method: "post",
|
|
88
|
-
handler: resolver.resolve(apiDir, "[model]/index.post")
|
|
89
|
-
});
|
|
90
|
-
addServerHandler({
|
|
91
|
-
route: "/api/:model/:id",
|
|
92
|
-
method: "get",
|
|
93
|
-
handler: resolver.resolve(apiDir, "[model]/[id].get")
|
|
94
|
-
});
|
|
95
|
-
addServerHandler({
|
|
96
|
-
route: "/api/:model/:id",
|
|
97
|
-
method: "patch",
|
|
98
|
-
handler: resolver.resolve(apiDir, "[model]/[id].patch")
|
|
28
|
+
nuxt.options.alias["#nac/shared"] = resolver.resolve("./runtime/shared");
|
|
29
|
+
nuxt.options.alias["#nac/types"] = resolver.resolve("./runtime/server/types");
|
|
30
|
+
nuxt.options.alias["#nac/schema"] = resolver.resolve(nuxt.options.rootDir, options.schemaPath);
|
|
31
|
+
const { formHiddenFields, nacEndpointPrefix, ...privateOptions } = options;
|
|
32
|
+
nuxt.options.runtimeConfig.autoCrud = privateOptions;
|
|
33
|
+
nuxt.options.runtimeConfig.public.autoCrud = { formHiddenFields, nacEndpointPrefix };
|
|
34
|
+
addImportsDir(resolver.resolve("./runtime/composables"));
|
|
35
|
+
addServerImportsDir(resolver.resolve("./runtime/server/utils"));
|
|
36
|
+
nuxt.hook("prepare:types", ({ references }) => {
|
|
37
|
+
references.push({ path: resolver.resolve("./runtime/types/index.d.ts") });
|
|
99
38
|
});
|
|
100
39
|
addServerHandler({
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
handler: resolver.resolve(apiDir, "[model]/[id].delete")
|
|
40
|
+
middleware: true,
|
|
41
|
+
handler: resolver.resolve("./runtime/server/middleware/nac-guard.ts")
|
|
104
42
|
});
|
|
105
|
-
|
|
106
|
-
|
|
43
|
+
const apiDir = resolver.resolve("./runtime/server/api/_nac");
|
|
44
|
+
const routes = [
|
|
45
|
+
// Dynamic CRUD Endpoints
|
|
46
|
+
{ path: "/:model", method: "get", handler: "[model]/index.get.ts" },
|
|
47
|
+
{ path: "/:model", method: "post", handler: "[model]/index.post.ts" },
|
|
48
|
+
{ path: "/:model/:id", method: "get", handler: "[model]/[id].get.ts" },
|
|
49
|
+
{ path: "/:model/:id", method: "patch", handler: "[model]/[id].patch.ts" },
|
|
50
|
+
{ path: "/:model/:id", method: "delete", handler: "[model]/[id].delete.ts" },
|
|
51
|
+
// System Endpoints
|
|
52
|
+
{ path: "/_schemas", method: "get", handler: "_schemas/index.get.ts" },
|
|
53
|
+
{ path: "/_schemas/:model", method: "get", handler: "_schemas/[model].get.ts" },
|
|
54
|
+
{ path: "/_meta", method: "get", handler: "_meta.get.ts" },
|
|
55
|
+
{ path: "/_sse", method: "get", handler: "_sse.get.ts" }
|
|
56
|
+
];
|
|
57
|
+
for (const route of routes) {
|
|
58
|
+
addServerHandler({
|
|
59
|
+
route: `${prefix}${route.path}`,
|
|
60
|
+
method: route.method,
|
|
61
|
+
handler: resolver.resolve(apiDir, route.handler)
|
|
62
|
+
});
|
|
63
|
+
}
|
|
107
64
|
}
|
|
108
65
|
});
|
|
109
66
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function useNacAutoCrudSSE(onEvent) {
|
|
2
|
+
let source = null;
|
|
3
|
+
onMounted(() => {
|
|
4
|
+
if (typeof window === "undefined" || !("EventSource" in window)) return;
|
|
5
|
+
source = new EventSource(`/api/_nac/_sse`);
|
|
6
|
+
source.onerror = (err) => {
|
|
7
|
+
console.error("[NAC] SSE Connection Error:", err);
|
|
8
|
+
};
|
|
9
|
+
source.addEventListener("crud", (e) => {
|
|
10
|
+
try {
|
|
11
|
+
const payload = JSON.parse(e.data);
|
|
12
|
+
onEvent(payload);
|
|
13
|
+
} catch (err) {
|
|
14
|
+
console.error("[NAC] SSE Parse Error:", err);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
onBeforeUnmount(() => {
|
|
19
|
+
source?.close();
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { eventHandler, getRouterParams } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
import { modelTableMap } from "../../../utils/modelMapper.js";
|
|
4
|
+
import { nacDeleteRow } from "../../../utils/queries.js";
|
|
5
|
+
import { broadcast } from "../../../utils/sse-bus.js";
|
|
6
|
+
import { ResourceNotFoundError } from "../../../exceptions.js";
|
|
7
|
+
export default eventHandler(async (event) => {
|
|
8
|
+
const { model, id } = getRouterParams(event);
|
|
9
|
+
const table = modelTableMap[model];
|
|
10
|
+
if (!table) throw new ResourceNotFoundError(model);
|
|
11
|
+
const deletedRecord = await nacDeleteRow(table, id);
|
|
12
|
+
const { realtime } = useRuntimeConfig().autoCrud;
|
|
13
|
+
if (realtime) {
|
|
14
|
+
void broadcast({
|
|
15
|
+
table: model,
|
|
16
|
+
action: "delete",
|
|
17
|
+
primaryKey: deletedRecord.id,
|
|
18
|
+
data: deletedRecord
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return deletedRecord;
|
|
22
|
+
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<
|
|
1
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
|
|
2
2
|
export default _default;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { eventHandler, getRouterParams } from "h3";
|
|
2
|
+
import { modelTableMap } from "../../../utils/modelMapper.js";
|
|
3
|
+
import { nacGetRow } from "../../../utils/queries.js";
|
|
4
|
+
import { ResourceNotFoundError } from "../../../exceptions.js";
|
|
5
|
+
export default eventHandler(async (event) => {
|
|
6
|
+
const { model, id } = getRouterParams(event);
|
|
7
|
+
const table = modelTableMap[model];
|
|
8
|
+
if (!table) throw new ResourceNotFoundError(model);
|
|
9
|
+
return await nacGetRow(table, id, event.context.nac || {});
|
|
10
|
+
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<
|
|
1
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
|
|
2
2
|
export default _default;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { eventHandler, getRouterParams, readBody } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
import { modelTableMap } from "../../../utils/modelMapper.js";
|
|
4
|
+
import { resolveValidatedSchema } from "../../../utils/validator.js";
|
|
5
|
+
import { nacUpdateRow } from "../../../utils/queries.js";
|
|
6
|
+
import { broadcast } from "../../../utils/sse-bus.js";
|
|
7
|
+
import { ResourceNotFoundError } from "../../../exceptions.js";
|
|
8
|
+
export default eventHandler(async (event) => {
|
|
9
|
+
const { model, id } = getRouterParams(event);
|
|
10
|
+
const body = await readBody(event);
|
|
11
|
+
const table = modelTableMap[model];
|
|
12
|
+
if (!table) throw new ResourceNotFoundError(model);
|
|
13
|
+
const validatedData = await resolveValidatedSchema(table, "patch").parseAsync(body);
|
|
14
|
+
const updatedRecord = await nacUpdateRow(table, id, validatedData, event.context.nac || {});
|
|
15
|
+
const { realtime } = useRuntimeConfig().autoCrud;
|
|
16
|
+
if (realtime) {
|
|
17
|
+
void broadcast({
|
|
18
|
+
table: model,
|
|
19
|
+
action: "update",
|
|
20
|
+
primaryKey: updatedRecord.id,
|
|
21
|
+
data: updatedRecord
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return updatedRecord;
|
|
25
|
+
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<
|
|
1
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
|
|
2
2
|
export default _default;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { eventHandler, getRouterParams } from "h3";
|
|
2
|
+
import { modelTableMap } from "../../../utils/modelMapper.js";
|
|
3
|
+
import { nacGetRows } from "../../../utils/queries.js";
|
|
4
|
+
import { ResourceNotFoundError } from "../../../exceptions.js";
|
|
5
|
+
export default eventHandler(async (event) => {
|
|
6
|
+
const { model } = getRouterParams(event);
|
|
7
|
+
const table = modelTableMap[model];
|
|
8
|
+
if (!table) throw new ResourceNotFoundError(model);
|
|
9
|
+
const results = await nacGetRows(table, event.context.nac || {});
|
|
10
|
+
return results;
|
|
11
|
+
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<
|
|
1
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
|
|
2
2
|
export default _default;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { eventHandler, getRouterParams, readBody } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
import { modelTableMap } from "../../../utils/modelMapper.js";
|
|
4
|
+
import { resolveValidatedSchema } from "../../../utils/validator.js";
|
|
5
|
+
import { nacCreateRow } from "../../../utils/queries.js";
|
|
6
|
+
import { broadcast } from "../../../utils/sse-bus.js";
|
|
7
|
+
import { ResourceNotFoundError } from "../../../exceptions.js";
|
|
8
|
+
export default eventHandler(async (event) => {
|
|
9
|
+
const { model } = getRouterParams(event);
|
|
10
|
+
const body = await readBody(event);
|
|
11
|
+
const table = modelTableMap[model];
|
|
12
|
+
if (!table) throw new ResourceNotFoundError(model);
|
|
13
|
+
const validatedData = await resolveValidatedSchema(table, "insert").parseAsync(body);
|
|
14
|
+
const newRecord = await nacCreateRow(table, validatedData, event.context.nac || {});
|
|
15
|
+
const { realtime } = useRuntimeConfig().autoCrud;
|
|
16
|
+
if (realtime) {
|
|
17
|
+
void broadcast({
|
|
18
|
+
table: model,
|
|
19
|
+
action: "create",
|
|
20
|
+
primaryKey: newRecord.id,
|
|
21
|
+
data: newRecord
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return newRecord;
|
|
25
|
+
});
|
|
@@ -8,11 +8,11 @@ declare const _default: import("h3").EventHandler<import("h3").EventHandlerReque
|
|
|
8
8
|
methods: string[];
|
|
9
9
|
fields: {
|
|
10
10
|
name: string;
|
|
11
|
-
type:
|
|
12
|
-
required: boolean;
|
|
11
|
+
type: string;
|
|
12
|
+
required: boolean | undefined;
|
|
13
13
|
isEnum: boolean;
|
|
14
|
-
options:
|
|
15
|
-
references:
|
|
14
|
+
options: string[] | null;
|
|
15
|
+
references: string | null;
|
|
16
16
|
isRelation: boolean;
|
|
17
17
|
isReadOnly: boolean;
|
|
18
18
|
}[];
|