qdadm 0.28.0 → 0.29.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/package.json +4 -2
- package/src/gen/FieldMapper.js +116 -0
- package/src/gen/StorageProfileFactory.js +109 -0
- package/src/gen/connectors/BaseConnector.js +142 -0
- package/src/gen/connectors/ManualConnector.js +385 -0
- package/src/gen/connectors/ManualConnector.test.js +499 -0
- package/src/gen/connectors/OpenAPIConnector.js +568 -0
- package/src/gen/connectors/OpenAPIConnector.test.js +737 -0
- package/src/gen/connectors/__fixtures__/sample-openapi.json +311 -0
- package/src/gen/connectors/index.js +11 -0
- package/src/gen/createManagers.js +224 -0
- package/src/gen/decorators.js +129 -0
- package/src/gen/generateManagers.js +266 -0
- package/src/gen/generateManagers.test.js +358 -0
- package/src/gen/index.js +45 -0
- package/src/gen/schema.js +221 -0
- package/src/gen/vite-plugin.js +105 -0
- package/src/generated/managers/testManager.js +45 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Schema Types
|
|
3
|
+
*
|
|
4
|
+
* Core contracts that abstract differences between OpenAPI, Pydantic, and manual schema sources.
|
|
5
|
+
* These types provide a common format for entity and field definitions that can be used
|
|
6
|
+
* by connectors (schema sources) and generators (code output).
|
|
7
|
+
*
|
|
8
|
+
* @module gen/schema
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Field Type Enumeration
|
|
13
|
+
*
|
|
14
|
+
* Unified field types that abstract source-specific types (OpenAPI string/integer,
|
|
15
|
+
* Pydantic str/int, etc.) into a common set.
|
|
16
|
+
*
|
|
17
|
+
* @typedef {'text' | 'number' | 'boolean' | 'date' | 'datetime' | 'email' | 'url' | 'uuid' | 'array' | 'object'} UnifiedFieldType
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Reference Definition
|
|
22
|
+
*
|
|
23
|
+
* Describes a relation to another entity for foreign key fields.
|
|
24
|
+
*
|
|
25
|
+
* @typedef {object} UnifiedFieldReference
|
|
26
|
+
* @property {string} entity - Target entity name (e.g., 'users', 'categories')
|
|
27
|
+
* @property {string} [labelField] - Field to display as label (e.g., 'name', 'title')
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Unified Field Schema
|
|
32
|
+
*
|
|
33
|
+
* Common field definition that abstracts OpenAPI property, Pydantic field, or manual definition.
|
|
34
|
+
* Connectors transform their source-specific formats into this common shape.
|
|
35
|
+
*
|
|
36
|
+
* @typedef {object} UnifiedFieldSchema
|
|
37
|
+
* @property {string} name - Field name (e.g., 'email', 'created_at')
|
|
38
|
+
* @property {UnifiedFieldType} type - Unified field type
|
|
39
|
+
* @property {string} [label] - Human-readable label (e.g., 'Email Address')
|
|
40
|
+
* @property {boolean} [required] - Whether field is required (default: false)
|
|
41
|
+
* @property {boolean} [readOnly] - Whether field is read-only (default: false)
|
|
42
|
+
* @property {boolean} [hidden] - Whether field should be hidden in UI (default: false)
|
|
43
|
+
* @property {string} [format] - Original format hint from source (e.g., 'date-time', 'uri', 'email')
|
|
44
|
+
* @property {string[]} [enum] - Allowed values for enumeration fields
|
|
45
|
+
* @property {*} [default] - Default value for the field
|
|
46
|
+
* @property {UnifiedFieldReference} [reference] - Relation to another entity
|
|
47
|
+
* @property {Record<string, *>} [extensions] - Project-specific extensions
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* // Simple text field
|
|
51
|
+
* const nameField = {
|
|
52
|
+
* name: 'name',
|
|
53
|
+
* type: 'text',
|
|
54
|
+
* label: 'Full Name',
|
|
55
|
+
* required: true
|
|
56
|
+
* }
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* // Enum field with options
|
|
60
|
+
* const statusField = {
|
|
61
|
+
* name: 'status',
|
|
62
|
+
* type: 'text',
|
|
63
|
+
* label: 'Status',
|
|
64
|
+
* enum: ['draft', 'published', 'archived'],
|
|
65
|
+
* default: 'draft'
|
|
66
|
+
* }
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* // Foreign key with reference
|
|
70
|
+
* const authorField = {
|
|
71
|
+
* name: 'author_id',
|
|
72
|
+
* type: 'number',
|
|
73
|
+
* label: 'Author',
|
|
74
|
+
* reference: {
|
|
75
|
+
* entity: 'users',
|
|
76
|
+
* labelField: 'username'
|
|
77
|
+
* }
|
|
78
|
+
* }
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Unified Entity Schema
|
|
83
|
+
*
|
|
84
|
+
* Common entity definition that abstracts OpenAPI path/schema, Pydantic model, or manual definition.
|
|
85
|
+
* This is the primary contract between schema sources (connectors) and consumers (generators, runtime).
|
|
86
|
+
*
|
|
87
|
+
* @typedef {object} UnifiedEntitySchema
|
|
88
|
+
* @property {string} name - Entity name, typically plural lowercase (e.g., 'users', 'blog_posts')
|
|
89
|
+
* @property {string} endpoint - API endpoint path (e.g., '/users', '/api/v1/posts')
|
|
90
|
+
* @property {string} [label] - Human-readable singular label (e.g., 'User', 'Blog Post')
|
|
91
|
+
* @property {string} [labelPlural] - Human-readable plural label (e.g., 'Users', 'Blog Posts')
|
|
92
|
+
* @property {string} [labelField] - Field used as display label (e.g., 'name', 'title')
|
|
93
|
+
* @property {string} [routePrefix] - Route prefix for admin UI (e.g., 'user', 'blog-post')
|
|
94
|
+
* @property {string} [idField] - Primary key field name (default: 'id')
|
|
95
|
+
* @property {boolean} [readOnly] - Whether entity is read-only (no create/update/delete)
|
|
96
|
+
* @property {Record<string, UnifiedFieldSchema>} fields - Field definitions keyed by field name
|
|
97
|
+
* @property {Record<string, *>} [extensions] - Project-specific extensions
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* // Complete entity schema
|
|
101
|
+
* const usersSchema = {
|
|
102
|
+
* name: 'users',
|
|
103
|
+
* endpoint: '/api/users',
|
|
104
|
+
* label: 'User',
|
|
105
|
+
* labelPlural: 'Users',
|
|
106
|
+
* labelField: 'username',
|
|
107
|
+
* routePrefix: 'user',
|
|
108
|
+
* idField: 'id',
|
|
109
|
+
* readOnly: false,
|
|
110
|
+
* fields: {
|
|
111
|
+
* id: { name: 'id', type: 'number', readOnly: true },
|
|
112
|
+
* username: { name: 'username', type: 'text', required: true },
|
|
113
|
+
* email: { name: 'email', type: 'email', required: true },
|
|
114
|
+
* created_at: { name: 'created_at', type: 'datetime', readOnly: true }
|
|
115
|
+
* }
|
|
116
|
+
* }
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* // Read-only entity (external API)
|
|
120
|
+
* const countriesSchema = {
|
|
121
|
+
* name: 'countries',
|
|
122
|
+
* endpoint: 'https://restcountries.com/v3.1/all',
|
|
123
|
+
* label: 'Country',
|
|
124
|
+
* labelPlural: 'Countries',
|
|
125
|
+
* labelField: 'name',
|
|
126
|
+
* idField: 'cca3',
|
|
127
|
+
* readOnly: true,
|
|
128
|
+
* fields: {
|
|
129
|
+
* cca3: { name: 'cca3', type: 'text', label: 'Code' },
|
|
130
|
+
* name: { name: 'name', type: 'text', label: 'Name' }
|
|
131
|
+
* }
|
|
132
|
+
* }
|
|
133
|
+
*/
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Valid field types for UnifiedFieldSchema
|
|
137
|
+
*
|
|
138
|
+
* Used for validation and documentation. Maps to common UI input types:
|
|
139
|
+
* - text: Single-line text input
|
|
140
|
+
* - number: Numeric input
|
|
141
|
+
* - boolean: Checkbox/toggle
|
|
142
|
+
* - date: Date picker (date only)
|
|
143
|
+
* - datetime: Date-time picker
|
|
144
|
+
* - email: Email input with validation
|
|
145
|
+
* - url: URL input with validation
|
|
146
|
+
* - uuid: UUID text input
|
|
147
|
+
* - array: Multi-value field (tags, list)
|
|
148
|
+
* - object: Nested object (JSON editor or subform)
|
|
149
|
+
*
|
|
150
|
+
* @type {readonly UnifiedFieldType[]}
|
|
151
|
+
*/
|
|
152
|
+
export const UNIFIED_FIELD_TYPES = Object.freeze([
|
|
153
|
+
'text',
|
|
154
|
+
'number',
|
|
155
|
+
'boolean',
|
|
156
|
+
'date',
|
|
157
|
+
'datetime',
|
|
158
|
+
'email',
|
|
159
|
+
'url',
|
|
160
|
+
'uuid',
|
|
161
|
+
'array',
|
|
162
|
+
'object'
|
|
163
|
+
])
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Check if a type is a valid UnifiedFieldType
|
|
167
|
+
*
|
|
168
|
+
* @param {string} type - Type to validate
|
|
169
|
+
* @returns {type is UnifiedFieldType} - True if valid
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* isValidFieldType('text') // true
|
|
173
|
+
* isValidFieldType('string') // false (OpenAPI type, not unified)
|
|
174
|
+
*/
|
|
175
|
+
export function isValidFieldType(type) {
|
|
176
|
+
return UNIFIED_FIELD_TYPES.includes(type)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Create a minimal field schema with defaults
|
|
181
|
+
*
|
|
182
|
+
* @param {string} name - Field name
|
|
183
|
+
* @param {UnifiedFieldType} type - Field type
|
|
184
|
+
* @param {Partial<UnifiedFieldSchema>} [overrides] - Additional field properties
|
|
185
|
+
* @returns {UnifiedFieldSchema} - Complete field schema
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* const field = createFieldSchema('email', 'email', { required: true })
|
|
189
|
+
* // { name: 'email', type: 'email', required: true }
|
|
190
|
+
*/
|
|
191
|
+
export function createFieldSchema(name, type, overrides = {}) {
|
|
192
|
+
return {
|
|
193
|
+
name,
|
|
194
|
+
type,
|
|
195
|
+
...overrides
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Create a minimal entity schema with defaults
|
|
201
|
+
*
|
|
202
|
+
* @param {string} name - Entity name
|
|
203
|
+
* @param {string} endpoint - API endpoint
|
|
204
|
+
* @param {Record<string, UnifiedFieldSchema>} fields - Field definitions
|
|
205
|
+
* @param {Partial<UnifiedEntitySchema>} [overrides] - Additional entity properties
|
|
206
|
+
* @returns {UnifiedEntitySchema} - Complete entity schema
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* const schema = createEntitySchema('users', '/api/users', {
|
|
210
|
+
* id: createFieldSchema('id', 'number', { readOnly: true }),
|
|
211
|
+
* name: createFieldSchema('name', 'text', { required: true })
|
|
212
|
+
* })
|
|
213
|
+
*/
|
|
214
|
+
export function createEntitySchema(name, endpoint, fields, overrides = {}) {
|
|
215
|
+
return {
|
|
216
|
+
name,
|
|
217
|
+
endpoint,
|
|
218
|
+
fields,
|
|
219
|
+
...overrides
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite Plugin for qdadm EntityManager Generation
|
|
3
|
+
*
|
|
4
|
+
* Integrates generateManagers with Vite's build pipeline, triggering
|
|
5
|
+
* entity manager file generation during the buildStart hook.
|
|
6
|
+
*
|
|
7
|
+
* @module gen/vite-plugin
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { generateManagers } from './generateManagers.js'
|
|
11
|
+
import { pathToFileURL } from 'node:url'
|
|
12
|
+
import { resolve } from 'node:path'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Plugin options for qdadmGen
|
|
16
|
+
*
|
|
17
|
+
* @typedef {object} QdadmGenOptions
|
|
18
|
+
* @property {string} [config] - Path to qdadm config file (default: 'qdadm.config.js')
|
|
19
|
+
* @property {string} [output] - Override output directory for generated files
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Vite plugin for generating EntityManager files at build time
|
|
24
|
+
*
|
|
25
|
+
* Loads the qdadm configuration file and calls generateManagers during
|
|
26
|
+
* Vite's buildStart hook. Supports both ESM and CommonJS config files.
|
|
27
|
+
*
|
|
28
|
+
* @param {QdadmGenOptions} [options={}] - Plugin options
|
|
29
|
+
* @returns {import('vite').Plugin} Vite plugin object
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* // vite.config.js
|
|
33
|
+
* import { defineConfig } from 'vite'
|
|
34
|
+
* import { qdadmGen } from 'qdadm/gen/vite-plugin'
|
|
35
|
+
*
|
|
36
|
+
* export default defineConfig({
|
|
37
|
+
* plugins: [
|
|
38
|
+
* qdadmGen({
|
|
39
|
+
* config: './qdadm.config.js',
|
|
40
|
+
* output: 'src/generated/managers/'
|
|
41
|
+
* })
|
|
42
|
+
* ]
|
|
43
|
+
* })
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* // qdadm.config.js
|
|
47
|
+
* export default {
|
|
48
|
+
* output: 'src/generated/managers/',
|
|
49
|
+
* entities: {
|
|
50
|
+
* users: {
|
|
51
|
+
* schema: { name: 'users', fields: { id: { type: 'integer' } } },
|
|
52
|
+
* endpoint: '/api/users',
|
|
53
|
+
* storageImport: 'qdadm',
|
|
54
|
+
* storageClass: 'ApiStorage'
|
|
55
|
+
* }
|
|
56
|
+
* }
|
|
57
|
+
* }
|
|
58
|
+
*/
|
|
59
|
+
export function qdadmGen(options = {}) {
|
|
60
|
+
const configPath = options.config || 'qdadm.config.js'
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
name: 'qdadm-gen',
|
|
64
|
+
|
|
65
|
+
async buildStart() {
|
|
66
|
+
try {
|
|
67
|
+
// Resolve config path relative to cwd
|
|
68
|
+
const resolvedConfigPath = resolve(process.cwd(), configPath)
|
|
69
|
+
|
|
70
|
+
// Load config file dynamically
|
|
71
|
+
const configUrl = pathToFileURL(resolvedConfigPath).href
|
|
72
|
+
const configModule = await import(configUrl)
|
|
73
|
+
const loadedConfig = configModule.default || configModule
|
|
74
|
+
|
|
75
|
+
// Validate loaded config
|
|
76
|
+
if (!loadedConfig || typeof loadedConfig !== 'object') {
|
|
77
|
+
throw new Error(`Invalid qdadm config at '${configPath}': must export an object`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!loadedConfig.entities) {
|
|
81
|
+
throw new Error(`Invalid qdadm config at '${configPath}': missing 'entities' property`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Merge plugin options with config (plugin options take precedence)
|
|
85
|
+
const mergedConfig = {
|
|
86
|
+
...loadedConfig,
|
|
87
|
+
...(options.output && { output: options.output })
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Generate managers
|
|
91
|
+
const generatedFiles = await generateManagers(mergedConfig)
|
|
92
|
+
|
|
93
|
+
// Log results
|
|
94
|
+
console.log(`[qdadm-gen] Generated ${generatedFiles.length} manager file(s)`)
|
|
95
|
+
for (const file of generatedFiles) {
|
|
96
|
+
console.log(` - ${file}`)
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
// Wrap error with plugin context for clearer messages
|
|
100
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
101
|
+
throw new Error(`[qdadm-gen] Failed to generate managers: ${message}`)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TestManager - Auto-generated EntityManager
|
|
3
|
+
*
|
|
4
|
+
* Generated by qdadm/gen/generateManagers
|
|
5
|
+
* DO NOT EDIT MANUALLY - Changes will be overwritten
|
|
6
|
+
*
|
|
7
|
+
* Entity: test
|
|
8
|
+
* Endpoint: /test
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { EntityManager } from 'qdadm'
|
|
12
|
+
import { ApiStorage } from 'qdadm'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Schema definition for test
|
|
16
|
+
* @type {import('qdadm/gen').UnifiedEntitySchema}
|
|
17
|
+
*/
|
|
18
|
+
export const testSchema = {
|
|
19
|
+
name: "test",
|
|
20
|
+
endpoint: "/test",
|
|
21
|
+
fields: {}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Storage options for test
|
|
26
|
+
*/
|
|
27
|
+
const storageOptions = {
|
|
28
|
+
endpoint: "/test"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* TestManager instance
|
|
33
|
+
*
|
|
34
|
+
* Provides CRUD operations for test entity.
|
|
35
|
+
*
|
|
36
|
+
* @type {EntityManager}
|
|
37
|
+
*/
|
|
38
|
+
export const testManager = new EntityManager({
|
|
39
|
+
...{
|
|
40
|
+
name: "test",
|
|
41
|
+
idField: "id",
|
|
42
|
+
fields: {}
|
|
43
|
+
},
|
|
44
|
+
storage: new ApiStorage(storageOptions)
|
|
45
|
+
})
|