nuxt-auto-crud 1.31.0 → 2.1.1

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.
Files changed (102) hide show
  1. package/README.md +251 -31
  2. package/dist/module.d.mts +16 -46
  3. package/dist/module.json +2 -2
  4. package/dist/module.mjs +49 -92
  5. package/dist/runtime/composables/{useAutoCrudSSE.d.ts → useNacAutoCrudSSE.d.ts} +1 -1
  6. package/dist/runtime/composables/useNacAutoCrudSSE.js +21 -0
  7. package/dist/runtime/server/api/_nac/[model]/[id].delete.js +22 -0
  8. package/dist/runtime/server/api/{[model] → _nac/[model]}/[id].get.d.ts +1 -1
  9. package/dist/runtime/server/api/_nac/[model]/[id].get.js +10 -0
  10. package/dist/runtime/server/api/{[model] → _nac/[model]}/[id].patch.d.ts +1 -1
  11. package/dist/runtime/server/api/_nac/[model]/[id].patch.js +25 -0
  12. package/dist/runtime/server/api/{_schema → _nac/[model]}/index.get.d.ts +1 -1
  13. package/dist/runtime/server/api/_nac/[model]/index.get.js +11 -0
  14. package/dist/runtime/server/api/{[model] → _nac/[model]}/index.post.d.ts +1 -1
  15. package/dist/runtime/server/api/_nac/[model]/index.post.js +25 -0
  16. package/dist/runtime/server/api/{_meta.get.d.ts → _nac/_meta.get.d.ts} +2 -11
  17. package/dist/runtime/server/api/_nac/_meta.get.js +72 -0
  18. package/dist/runtime/server/api/_nac/_schemas/[model].get.d.ts +2 -0
  19. package/dist/runtime/server/api/_nac/_schemas/[model].get.js +10 -0
  20. package/dist/runtime/server/api/_nac/_schemas/index.get.d.ts +2 -0
  21. package/dist/runtime/server/api/_nac/_schemas/index.get.js +5 -0
  22. package/dist/runtime/server/api/{sse.js → _nac/_sse.get.js} +17 -15
  23. package/dist/runtime/server/exceptions.d.ts +37 -4
  24. package/dist/runtime/server/exceptions.js +58 -6
  25. package/dist/runtime/server/middleware/nac-guard.d.ts +2 -0
  26. package/dist/runtime/server/middleware/nac-guard.js +43 -0
  27. package/dist/runtime/server/types.d.ts +4 -4
  28. package/dist/runtime/server/utils/constants.d.ts +18 -2
  29. package/dist/runtime/server/utils/constants.js +18 -22
  30. package/dist/runtime/server/utils/modelMapper.d.ts +36 -20
  31. package/dist/runtime/server/utils/modelMapper.js +99 -118
  32. package/dist/runtime/server/utils/queries.d.ts +39 -0
  33. package/dist/runtime/server/utils/queries.js +78 -0
  34. package/dist/runtime/server/utils/sse-bus.d.ts +2 -2
  35. package/dist/runtime/server/utils/sse-bus.js +18 -26
  36. package/dist/runtime/server/utils/validator.d.ts +43 -0
  37. package/dist/runtime/server/utils/validator.js +22 -0
  38. package/dist/runtime/shared/utils/helpers.d.ts +7 -0
  39. package/dist/runtime/shared/utils/helpers.js +6 -0
  40. package/dist/runtime/shared/utils/types.d.ts +13 -0
  41. package/dist/runtime/shared/utils/types.js +0 -0
  42. package/dist/runtime/types/index.d.ts +22 -0
  43. package/package.json +43 -37
  44. package/dist/runtime/auth.d.ts +0 -6
  45. package/dist/runtime/composables/useAutoCrudSSE.js +0 -13
  46. package/dist/runtime/composables/useRelationDisplay.d.ts +0 -13
  47. package/dist/runtime/composables/useRelationDisplay.js +0 -54
  48. package/dist/runtime/composables/useResourceSchemas.d.ts +0 -19
  49. package/dist/runtime/composables/useResourceSchemas.js +0 -17
  50. package/dist/runtime/server/api/[model]/[id].delete.d.ts +0 -2
  51. package/dist/runtime/server/api/[model]/[id].delete.js +0 -27
  52. package/dist/runtime/server/api/[model]/[id].get.js +0 -29
  53. package/dist/runtime/server/api/[model]/[id].patch.js +0 -49
  54. package/dist/runtime/server/api/[model]/index.get.js +0 -59
  55. package/dist/runtime/server/api/[model]/index.post.js +0 -48
  56. package/dist/runtime/server/api/_meta.get.js +0 -91
  57. package/dist/runtime/server/api/_relations.get.d.ts +0 -2
  58. package/dist/runtime/server/api/_relations.get.js +0 -7
  59. package/dist/runtime/server/api/_schema/[table].get.d.ts +0 -6
  60. package/dist/runtime/server/api/_schema/[table].get.js +0 -15
  61. package/dist/runtime/server/api/_schema/index.get.js +0 -7
  62. package/dist/runtime/server/stubs/auth.d.ts +0 -8
  63. package/dist/runtime/server/stubs/auth.js +0 -11
  64. package/dist/runtime/server/tsconfig.json +0 -3
  65. package/dist/runtime/server/utils/auth.d.ts +0 -3
  66. package/dist/runtime/server/utils/auth.js +0 -97
  67. package/dist/runtime/server/utils/config.d.ts +0 -2
  68. package/dist/runtime/server/utils/config.js +0 -4
  69. package/dist/runtime/server/utils/handler.d.ts +0 -4
  70. package/dist/runtime/server/utils/handler.js +0 -32
  71. package/dist/runtime/server/utils/jwt.d.ts +0 -2
  72. package/dist/runtime/server/utils/jwt.js +0 -19
  73. package/dist/runtime/server/utils/schema.d.ts +0 -19
  74. package/dist/runtime/server/utils/schema.js +0 -104
  75. package/src/runtime/auth.d.ts +0 -6
  76. package/src/runtime/composables/useAutoCrudSSE.ts +0 -24
  77. package/src/runtime/composables/useRelationDisplay.ts +0 -74
  78. package/src/runtime/composables/useResourceSchemas.ts +0 -42
  79. package/src/runtime/server/api/[model]/[id].delete.ts +0 -44
  80. package/src/runtime/server/api/[model]/[id].get.ts +0 -48
  81. package/src/runtime/server/api/[model]/[id].patch.ts +0 -83
  82. package/src/runtime/server/api/[model]/index.get.ts +0 -93
  83. package/src/runtime/server/api/[model]/index.post.ts +0 -72
  84. package/src/runtime/server/api/_meta.get.ts +0 -111
  85. package/src/runtime/server/api/_relations.get.ts +0 -9
  86. package/src/runtime/server/api/_schema/[table].get.ts +0 -20
  87. package/src/runtime/server/api/_schema/index.get.ts +0 -9
  88. package/src/runtime/server/api/sse.ts +0 -39
  89. package/src/runtime/server/exceptions.ts +0 -25
  90. package/src/runtime/server/stubs/auth.ts +0 -11
  91. package/src/runtime/server/tsconfig.json +0 -3
  92. package/src/runtime/server/types.ts +0 -5
  93. package/src/runtime/server/utils/auth.ts +0 -152
  94. package/src/runtime/server/utils/config.ts +0 -6
  95. package/src/runtime/server/utils/constants.ts +0 -25
  96. package/src/runtime/server/utils/handler.ts +0 -43
  97. package/src/runtime/server/utils/jwt.ts +0 -23
  98. package/src/runtime/server/utils/modelMapper.ts +0 -197
  99. package/src/runtime/server/utils/schema.ts +0 -160
  100. package/src/runtime/server/utils/sse-bus.ts +0 -44
  101. /package/dist/runtime/server/api/{[model]/index.get.d.ts → _nac/[model]/[id].delete.d.ts} +0 -0
  102. /package/dist/runtime/server/api/{sse.d.ts → _nac/_sse.get.d.ts} +0 -0
package/README.md CHANGED
@@ -1,44 +1,264 @@
1
- # Nuxt Auto CRUD
1
+ # nuxt-auto-crud (nac 2.x)
2
2
 
3
- **Nuxt Auto CRUD is a headless, zero-codegen CRUD engine that transforms Drizzle ORM schemas into fully functional RESTful APIs for Nuxt 4.**
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
- | Specification | Details |
6
- | :--- | :--- |
7
- | **Runtime** | Nuxt 4 (`app/` directory), Nitro |
8
- | **Persistence** | SQLite / libSQL (Optimized for Cloudflare D1) |
9
- | **ORM & SSOT** | Drizzle ORM (Schema-driven) |
10
- | **Validation** | `drizzle-zod` (Dynamic derivation) |
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
+ ## Installation Guide
15
+
16
+ ```bash
17
+ bun create nuxt@latest my-app
18
+ npx nuxi module add hub
19
+ bun add drizzle-orm@beta @libsql/client nuxt-auto-crud
20
+ bun add -D drizzle-kit@beta
21
+
22
+ ```
23
+
24
+ ### Configuration
25
+
26
+ Update `nuxt.config.ts`:
27
+
28
+ ```typescript
29
+ export default defineNuxtConfig({
30
+ modules: [
31
+ '@nuxthub/core',
32
+ 'nuxt-auto-crud'
33
+ ],
34
+ hub: {
35
+ db: 'sqlite'
36
+ }
37
+ })
38
+
39
+ ```
40
+
41
+ ### Schema Definition
42
+
43
+ Define your schema in `server/database/schema.ts`:
44
+
45
+ ```typescript
46
+ import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
47
+
48
+ export const users = sqliteTable('users', {
49
+ id: integer().primaryKey({ autoIncrement: true }),
50
+ name: text().notNull(),
51
+ email: text().notNull().unique(),
52
+ password: text().notNull(),
53
+ avatar: text().notNull(),
54
+ createdAt: integer({ mode: 'timestamp' }).notNull().$defaultFn(() => new Date()),
55
+ })
56
+
57
+ ```
58
+
59
+ ### Initialize and Run
60
+
61
+ ```bash
62
+ npx nuxi generate
63
+ npx nuxi dev
64
+
65
+ ```
66
+
67
+ ---
68
+
69
+ ## 🌐 Dynamic RESTful CRUD endpoints
70
+
71
+ Nb: Endpoints follow the pattern `/api/_nac/:model`.
72
+
73
+ | Method | Endpoint | Action |
74
+ | --- | --- | --- |
75
+ | **GET** | `/:model` | List records (supports filtering & pagination) |
76
+ | **POST** | `/:model` | Create record with Zod validation |
77
+ | **GET** | `/:model/:id` | Fetch single record |
78
+ | **PATCH** | `/:model/:id` | Partial update with validation |
79
+ | **DELETE** | `/:model/:id` | Hard delete |
80
+
81
+ **Example (`users` table):**
82
+
83
+ | Action | HTTP Method | Endpoint | Example Result |
84
+ | --- | --- | --- | --- |
85
+ | **Fetch All** | `GET` | `/api/_nac/users` | List of all users (paginated) |
86
+ | **Create** | `POST` | `/api/_nac/users` | New user record added |
87
+ | **Fetch One** | `GET` | `/api/_nac/users/1` | Details of user with `id: 1` |
88
+ | **Update** | `PATCH` | `/api/_nac/users/1` | Partial update to user `1` |
89
+ | **Delete** | `DELETE` | `/api/_nac/users/1` | User `1` hard deleted from DB |
90
+
91
+ ---
92
+
93
+ ## 🛠 Frontend Integration APIs
94
+
95
+ In addition to CRUD endpoints, **nac** provides metadata APIs to power dynamic forms and tables in your frontend.
96
+
97
+ * **List Resources**: `GET /api/_nac/_schemas` returns all tables (excluding system-protected tables).
98
+ * **Resource Metadata**: `GET /api/_nac/_schemas/:resource` returns the field definitions, validation rules, and relationship data for a specific table.
99
+
100
+ ---
101
+
102
+ ### Schema Interface
103
+
104
+ ```typescript
105
+ export interface Field {
106
+ name: string
107
+ type: string
108
+ required?: boolean
109
+ selectOptions?: string[]
110
+ references?: string
111
+ isReadOnly?: boolean
112
+ }
113
+
114
+ export interface SchemaDefinition {
115
+ resource: string
116
+ labelField: string
117
+ fields: Field[]
118
+ }
11
119
 
12
- ## 🛠 Architectural Logic: Zero-Codegen
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.
120
+ ```
14
121
 
15
- * **Dynamic Routing**: Automatically maps `GET|POST|PATCH|DELETE` to your Drizzle tables.
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.
122
+ ### Example Response
18
123
 
19
- ## 🔐 RBAC & Permissions
20
- Integrates with `nuxt-authorization` for database-driven Role-Based Access Control.
21
- * **Ownership Logic**: Supports `update_own` and `delete_own` via `createdBy` column reflection.
22
- * **Granular Scopes**: Fine-grained control over `list` vs `list_all` (drafts/soft-deleted).
124
+ `GET /api/_nac/_schemas/users`
23
125
 
24
- ## 🌐 Endpoints
25
- | Method | Endpoint | Description |
26
- | :--- | :--- | :--- |
27
- | `GET` | `/api/:model` | List with filtering/paging |
28
- | `POST` | `/api/:model` | Validated creation |
29
- | `GET` | `/api/:model/:id` | Single record retrieval |
30
- | `PATCH` | `/api/:model/:id` | Partial validated update |
31
- | `DELETE` | `/api/:model/:id` | Soft/Hard deletion |
126
+ ```json
127
+ {
128
+ "resource": "users",
129
+ "labelField": "name",
130
+ "fields": [
131
+ { "name": "id", "type": "string", "required": true, "isReadOnly": true },
132
+ { "name": "name", "type": "string", "required": true, "isReadOnly": false },
133
+ { "name": "email", "type": "string", "required": true, "isReadOnly": false }
134
+ ]
135
+ }
32
136
 
137
+ ```
33
138
  ---
34
139
 
35
- ## Installation
36
- It is highly recommended to use the [Template](https://auto-crud.clifland.in/docs/auto-crud) for new installations.
140
+ ## 🛡 Security & Configuration
37
141
 
38
- If you are adding it to an existing application, refer to the [Manual Installation](https://auto-crud.clifland.in/docs/manual-installation) guide.
142
+ Enabling `authentication` in the `autoCrud` config protects all **nac** routes (`/api/_nac/*`), except those explicitly defined in `publicResources`.
39
143
 
40
- [YouTube Walkthrough](https://www.youtube.com/watch?v=_o0cddJUU50&list=PLnbvxcojhIixqM1J08Tnm7vmMdx2wsy4B)
144
+ ### 🔒 Access Control & Data Safety
41
145
 
42
- [NPM Package](https://www.npmjs.com/package/nuxt-auto-crud)
146
+ * **`apiHiddenFields`**: Globally hides sensitive columns from all API responses. Default: `['password', 'secret', 'token', 'reset_token', 'reset_expires', 'github_id', 'google_id']`.
147
+ * **`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.
148
+ * **Response Scrubbing**: 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`.
43
149
 
44
- [Creator: Clifland](https://www.clifland.in/)
150
+ ---
151
+
152
+ ### ⚙️ Configuration Reference
153
+
154
+ | Key | Default | Description |
155
+ | --- | --- | --- |
156
+ | `realtime` | `false` | Enables/disables real-time capabilities. |
157
+ | `auth.authentication` | `true` | Requires a valid session for all NAC routes. |
158
+ | `auth.authorization` | `true` | Enables role/owner-based access checks. |
159
+ | `auth.ownerKey` | `'ownerId'` | The column name used to identify the record creator. |
160
+ | `publicResources` | `{}` | Defines tables and specific columns accessible without auth. |
161
+ | `nacEndpointPrefix` | `'/api/_nac'` | The base path for NAC routes. Access via `useRuntimeConfig().public.autoCrud`. |
162
+ | `schemaPath` | `'server/db/schema'` | Location of your Drizzle schema files. |
163
+
164
+ ### Example `nuxt.config.ts`
165
+
166
+ ```typescript
167
+ autoCrud: {
168
+ realtime: false,
169
+ auth: {
170
+ authentication: true,
171
+ authorization: true,
172
+ ownerKey: 'ownerId',
173
+ },
174
+ publicResources: {
175
+ users: ['id', 'name', 'email'],
176
+ },
177
+ apiHiddenFields: ['password'],
178
+ agenticToken: process.env.NAC_AGENTIC_TOKEN,
179
+ formHiddenFields: [],
180
+ nacEndpointPrefix: '/api/_nac',
181
+ schemaPath: 'server/db/schema',
182
+ }
183
+
184
+ ```
185
+
186
+ > **Note**: Modify `nacEndpointPrefix` or `schemaPath` only if the Nuxt/Nitro conventions change.
187
+ ---
188
+
189
+ ## 🛡 Filtering & Performance Optimization
190
+
191
+ ### Automatic Status Filtering
192
+
193
+ 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.
194
+
195
+ ### Ownership & Permissions
196
+
197
+ While the implementing app handles the authentication layer, **nac** provides a standardized way to enforce record ownership and granular access.
198
+
199
+ If your middleware populates `event.context.nac` with `resourcePermissions`, **nac** automatically injects the necessary SQL filters.
200
+
201
+ **Example: Restricting users to their own records**
202
+ If the permissions array includes `'list_own'`, **nac** appends a filter where `ownerCol === userId`.
203
+
204
+ ```typescript
205
+ // Example: Setting context in your Auth Middleware
206
+ event.context.nac = {
207
+ userId: user.id,
208
+ resourcePermissions: user.permissions[model], // e.g., ['list_own', 'list']
209
+ record: null, // Optional: Pre-fetched record to prevent double-hitting the DB
210
+ }
211
+
212
+ ```
213
+
214
+ ### Optimization: Skip Redundant Fetches
215
+
216
+ If your middleware has already fetched the record, pass it to `event.context.nac.record` (as shown above). **nac** will use this object instead of executing an additional database query.
217
+
218
+ ---
219
+
220
+ ## 📡 Real-time Synchronization (SSE)
221
+
222
+ When `realtime` is enabled, all `create`, `update`, and `delete` operations are automatically broadcasted:
223
+
224
+ ```typescript
225
+ if (realtime) {
226
+ void broadcast({
227
+ table: model,
228
+ action: 'create',
229
+ primaryKey: newRecord.id,
230
+ data: newRecord,
231
+ })
232
+ }
233
+
234
+ ```
235
+
236
+ ### Frontend Usage
237
+
238
+ NAC provides a `useNacAutoCrudSSE` composable to listen for these changes in your frontend:
239
+
240
+ ```typescript
241
+ useNacAutoCrudSSE(({ table, action, data: sseData, primaryKey }) => {
242
+ // Optional: Filter by specific table
243
+ // if (table !== currentTable.value) return
244
+
245
+ if (action === 'update') {
246
+ // updateRow(primaryKey, sseData)
247
+ }
248
+
249
+ if (action === 'create') {
250
+ // addRow(sseData)
251
+ }
252
+
253
+ if (action === 'delete') {
254
+ // removeRow(primaryKey)
255
+ }
256
+ })
257
+
258
+ ```
259
+ ---
260
+
261
+ ## ⚠️ Limitations
262
+ **Database Support:** Currently optimized for SQLite/libSQL only.
263
+
264
+ ---
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
- * Path to the database schema file
6
- * @default 'server/database/schema'
7
- */
8
- schemaPath?: string;
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
- * Fields that should be automatically hashed before storage
22
- * @default ['password']
23
- */
24
- hashedFields?: string[];
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: RuntimeModuleOptions;
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
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "nuxt-auto-crud",
3
3
  "configKey": "autoCrud",
4
- "version": "1.31.0",
4
+ "version": "2.1.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
- "unbuild": "3.6.1"
7
+ "unbuild": "unknown"
8
8
  }
9
9
  }
package/dist/module.mjs CHANGED
@@ -1,4 +1,5 @@
1
- import { defineNuxtModule, createResolver, addImportsDir, hasNuxtModule, addServerImports, addServerHandler, addServerImportsDir } from '@nuxt/kit';
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
- auth: false
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
- const schemaPath = resolver.resolve(
15
- nuxt.options.rootDir,
16
- options.schemaPath
17
- );
18
- nuxt.options.alias["#site/schema"] = schemaPath;
19
- addImportsDir(resolver.resolve(nuxt.options.rootDir, "shared/utils"));
20
- const stubsPath = resolver.resolve("./runtime/server/stubs/auth");
21
- if (!hasNuxtModule("nuxt-auth-utils")) {
22
- addServerImports([
23
- { name: "requireUserSession", from: stubsPath },
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
- route: "/api/:model/:id",
102
- method: "delete",
103
- handler: resolver.resolve(apiDir, "[model]/[id].delete")
40
+ middleware: true,
41
+ handler: resolver.resolve("./runtime/server/middleware/nac-guard")
104
42
  });
105
- addServerImportsDir(resolver.resolve("./runtime/server/utils"));
106
- addImportsDir(resolver.resolve("./runtime/composables"));
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" },
47
+ { path: "/:model", method: "post", handler: "[model]/index.post" },
48
+ { path: "/:model/:id", method: "get", handler: "[model]/[id].get" },
49
+ { path: "/:model/:id", method: "patch", handler: "[model]/[id].patch" },
50
+ { path: "/:model/:id", method: "delete", handler: "[model]/[id].delete" },
51
+ // System Endpoints
52
+ { path: "/_schemas", method: "get", handler: "_schemas/index.get" },
53
+ { path: "/_schemas/:model", method: "get", handler: "_schemas/[model].get" },
54
+ { path: "/_meta", method: "get", handler: "_meta.get" },
55
+ { path: "/_sse", method: "get", handler: "_sse.get" }
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
 
@@ -4,4 +4,4 @@ export interface AutoCrudEvent {
4
4
  data: Record<string, unknown>;
5
5
  primaryKey: string | number;
6
6
  }
7
- export declare function useAutoCrudSSE(onEvent: (e: AutoCrudEvent) => void): void;
7
+ export declare function useNacAutoCrudSSE(onEvent: (e: AutoCrudEvent) => void): void;
@@ -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<Record<string, unknown>>>;
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<Record<string, unknown>>>;
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<Record<string, any>>>;
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<Record<string, unknown>>>;
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
2
2
  export default _default;