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.
@@ -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
+ })