hazo_config 1.4.2 → 2.0.2

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 (58) hide show
  1. package/MIGRATION_V2.md +531 -0
  2. package/README.md +201 -10
  3. package/dist/components/app_config.d.ts +1 -1
  4. package/dist/components/app_config.d.ts.map +1 -1
  5. package/dist/components/app_config.js +123 -86
  6. package/dist/components/app_config_list_editor/app_config_list_editor.d.ts +7 -0
  7. package/dist/components/app_config_list_editor/app_config_list_editor.d.ts.map +1 -0
  8. package/dist/components/app_config_list_editor/app_config_list_editor.js +128 -0
  9. package/dist/components/app_config_list_editor/components/color_swatch_picker.d.ts +9 -0
  10. package/dist/components/app_config_list_editor/components/color_swatch_picker.d.ts.map +1 -0
  11. package/dist/components/app_config_list_editor/components/color_swatch_picker.js +15 -0
  12. package/dist/components/app_config_list_editor/components/delete_dialog.d.ts +10 -0
  13. package/dist/components/app_config_list_editor/components/delete_dialog.d.ts.map +1 -0
  14. package/dist/components/app_config_list_editor/components/delete_dialog.js +9 -0
  15. package/dist/components/app_config_list_editor/components/edit_modal.d.ts +19 -0
  16. package/dist/components/app_config_list_editor/components/edit_modal.d.ts.map +1 -0
  17. package/dist/components/app_config_list_editor/components/edit_modal.js +97 -0
  18. package/dist/components/app_config_list_editor/components/empty_state.d.ts +8 -0
  19. package/dist/components/app_config_list_editor/components/empty_state.d.ts.map +1 -0
  20. package/dist/components/app_config_list_editor/components/empty_state.js +8 -0
  21. package/dist/components/app_config_list_editor/components/list_item_row.d.ts +14 -0
  22. package/dist/components/app_config_list_editor/components/list_item_row.d.ts.map +1 -0
  23. package/dist/components/app_config_list_editor/components/list_item_row.js +14 -0
  24. package/dist/components/app_config_list_editor/components/save_status_indicator.d.ts +7 -0
  25. package/dist/components/app_config_list_editor/components/save_status_indicator.d.ts.map +1 -0
  26. package/dist/components/app_config_list_editor/components/save_status_indicator.js +25 -0
  27. package/dist/components/app_config_list_editor/components/search_bar.d.ts +10 -0
  28. package/dist/components/app_config_list_editor/components/search_bar.d.ts.map +1 -0
  29. package/dist/components/app_config_list_editor/components/search_bar.js +8 -0
  30. package/dist/components/app_config_list_editor/index.d.ts +3 -0
  31. package/dist/components/app_config_list_editor/index.d.ts.map +1 -0
  32. package/dist/components/app_config_list_editor/index.js +2 -0
  33. package/dist/components/app_config_list_editor/types.d.ts +93 -0
  34. package/dist/components/app_config_list_editor/types.d.ts.map +1 -0
  35. package/dist/components/app_config_list_editor/types.js +14 -0
  36. package/dist/components/index.d.ts +2 -0
  37. package/dist/components/index.d.ts.map +1 -1
  38. package/dist/components/index.js +2 -0
  39. package/dist/components/ui/alert-dialog.d.ts +21 -0
  40. package/dist/components/ui/alert-dialog.d.ts.map +1 -0
  41. package/dist/components/ui/alert-dialog.js +26 -0
  42. package/dist/components/ui/button.d.ts +12 -0
  43. package/dist/components/ui/button.d.ts.map +1 -0
  44. package/dist/components/ui/button.js +33 -0
  45. package/dist/components/ui/dialog.d.ts +20 -0
  46. package/dist/components/ui/dialog.d.ts.map +1 -0
  47. package/dist/components/ui/dialog.js +22 -0
  48. package/dist/components/ui/input.d.ts +4 -0
  49. package/dist/components/ui/input.d.ts.map +1 -0
  50. package/dist/components/ui/input.js +8 -0
  51. package/dist/components/use_app_config.d.ts +3 -4
  52. package/dist/components/use_app_config.d.ts.map +1 -1
  53. package/dist/components/use_app_config.js +51 -17
  54. package/dist/lib/app_config_types.d.ts +19 -17
  55. package/dist/lib/app_config_types.d.ts.map +1 -1
  56. package/dist/lib/index.d.ts +1 -1
  57. package/dist/lib/index.d.ts.map +1 -1
  58. package/package.json +6 -3
@@ -0,0 +1,531 @@
1
+ # Migration Guide: hazo_config v1 → v2
2
+
3
+ ## Breaking Changes Summary
4
+
5
+ Version 2.0.0 introduces a major schema change to support JSON configuration values and replace `org_id` with `scope_id`. This is a **BREAKING CHANGE** requiring code updates and database migration.
6
+
7
+ ## What Changed
8
+
9
+ ### 1. Database Schema
10
+
11
+ **Old Schema:**
12
+ ```sql
13
+ CREATE TABLE hazo_app_config (
14
+ id TEXT PRIMARY KEY,
15
+ org_id TEXT,
16
+ user_id TEXT,
17
+ config_section TEXT NOT NULL,
18
+ config_name TEXT NOT NULL,
19
+ config_value TEXT NOT NULL,
20
+ created_at TEXT,
21
+ changed_at TEXT
22
+ )
23
+ ```
24
+
25
+ **New Schema:**
26
+ ```sql
27
+ CREATE TABLE hazo_app_config (
28
+ id TEXT PRIMARY KEY,
29
+ scope_id TEXT, -- ← CHANGED: org_id → scope_id
30
+ user_id TEXT,
31
+ config_section TEXT NOT NULL,
32
+ config_name TEXT NOT NULL,
33
+ config_value_text TEXT, -- ← NEW: for general type
34
+ config_value_json TEXT, -- ← NEW: for json type (JSONB in PostgreSQL)
35
+ config_type TEXT NOT NULL, -- ← NEW: 'general' or 'json'
36
+ created_at TEXT,
37
+ changed_at TEXT,
38
+ CHECK (
39
+ (config_type = 'general' AND config_value_text IS NOT NULL AND config_value_json IS NULL) OR
40
+ (config_type = 'json' AND config_value_json IS NOT NULL AND config_value_text IS NULL)
41
+ )
42
+ )
43
+ ```
44
+
45
+ ### 2. Type Definitions
46
+
47
+ **Removed:**
48
+ - `ConfigLevel` type ('org' | 'user')
49
+
50
+ **Added:**
51
+ - `ConfigType` type ('json' | 'general')
52
+
53
+ **Changed Interface:**
54
+ ```typescript
55
+ // OLD
56
+ interface AppConfigItem {
57
+ org_id: string | null
58
+ config_value: string
59
+ }
60
+
61
+ // NEW
62
+ interface AppConfigItem {
63
+ scope_id: string | null // ← org_id renamed
64
+ config_value_text: string // ← new field
65
+ config_value_json: object // ← new field
66
+ config_type: ConfigType // ← new field
67
+ }
68
+
69
+ // OLD
70
+ interface AppConfigContext {
71
+ org_id: string
72
+ user_id?: string
73
+ }
74
+
75
+ // NEW
76
+ interface AppConfigContext {
77
+ scope_id: string // ← org_id renamed
78
+ user_id?: string
79
+ }
80
+ ```
81
+
82
+ ### 3. Component API Changes
83
+
84
+ **AppConfig Component:**
85
+ ```typescript
86
+ // OLD
87
+ <AppConfig
88
+ level="org" // ← REMOVED
89
+ context={{ org_id: '...' }}
90
+ fetch_config={fetch_config}
91
+ save_config={save_config}
92
+ delete_config={delete_config}
93
+ />
94
+
95
+ // NEW
96
+ <AppConfig
97
+ title="Scope Settings" // ← Now required (no default from level)
98
+ context={{ scope_id: '...' }}
99
+ fetch_config={fetch_config}
100
+ save_config={save_config}
101
+ delete_config={delete_config}
102
+ />
103
+ ```
104
+
105
+ **Hook Signature:**
106
+ ```typescript
107
+ // OLD
108
+ useAppConfig(
109
+ level: ConfigLevel,
110
+ context: AppConfigContext,
111
+ fetch_config: (level, context) => Promise<AppConfigItem[]>,
112
+ save_config: (item) => Promise<void>,
113
+ delete_config: (section, name, level, context) => Promise<void>
114
+ )
115
+
116
+ // NEW
117
+ useAppConfig(
118
+ context: AppConfigContext,
119
+ fetch_config: (context) => Promise<AppConfigItem[]>,
120
+ save_config: (item) => Promise<void>,
121
+ delete_config: (section, name, context) => Promise<void>
122
+ )
123
+ ```
124
+
125
+ **Hook Return Value:**
126
+ ```typescript
127
+ // OLD
128
+ sections: Record<string, Record<string, string>>
129
+ set_value: (section, key, value: string) => Promise<void>
130
+
131
+ // NEW
132
+ sections: Record<string, Record<string, AppConfigItem>> // Full items
133
+ set_value: (section, key, value: string | object, type: ConfigType) => Promise<void>
134
+ change_type: (section, key, new_type: ConfigType) => Promise<void> // NEW
135
+ ```
136
+
137
+ ### 4. Server Action Changes
138
+
139
+ **Fetch Config:**
140
+ ```typescript
141
+ // OLD
142
+ async function fetch_config(
143
+ level: ConfigLevel,
144
+ context: AppConfigContext
145
+ ): Promise<AppConfigItem[]>
146
+
147
+ // NEW
148
+ async function fetch_config(
149
+ context: AppConfigContext
150
+ ): Promise<AppConfigItem[]>
151
+ ```
152
+
153
+ **Save Config:**
154
+ ```typescript
155
+ // OLD
156
+ const item = {
157
+ org_id: context.org_id,
158
+ user_id: level === 'user' ? context.user_id : null,
159
+ config_value: value
160
+ }
161
+
162
+ // NEW
163
+ const item = {
164
+ scope_id: context.scope_id,
165
+ user_id: context.user_id ?? null,
166
+ config_value_text: type === 'general' ? value : '',
167
+ config_value_json: type === 'json' ? value : {},
168
+ config_type: type
169
+ }
170
+ ```
171
+
172
+ **Delete Config:**
173
+ ```typescript
174
+ // OLD
175
+ async function delete_config(
176
+ section: string,
177
+ name: string,
178
+ level: ConfigLevel,
179
+ context: AppConfigContext
180
+ )
181
+
182
+ // NEW
183
+ async function delete_config(
184
+ section: string,
185
+ name: string,
186
+ context: AppConfigContext
187
+ )
188
+ ```
189
+
190
+ ## Migration Steps
191
+
192
+ ### 1. Database Migration
193
+
194
+ **PostgreSQL:**
195
+ ```sql
196
+ -- Add new columns
197
+ ALTER TABLE hazo_app_config
198
+ ADD COLUMN scope_id TEXT,
199
+ ADD COLUMN config_value_text TEXT,
200
+ ADD COLUMN config_value_json JSONB,
201
+ ADD COLUMN config_type TEXT;
202
+
203
+ -- Migrate existing data
204
+ UPDATE hazo_app_config SET
205
+ scope_id = org_id,
206
+ config_value_text = config_value,
207
+ config_value_json = '{}'::jsonb,
208
+ config_type = 'general';
209
+
210
+ -- Drop old columns
211
+ ALTER TABLE hazo_app_config
212
+ DROP COLUMN org_id,
213
+ DROP COLUMN config_value;
214
+
215
+ -- Add constraints
216
+ ALTER TABLE hazo_app_config
217
+ ALTER COLUMN config_type SET NOT NULL,
218
+ ADD CONSTRAINT check_type_values CHECK (config_type IN ('json', 'general')),
219
+ ADD CONSTRAINT check_value_exclusivity CHECK (
220
+ (config_type = 'general' AND config_value_text IS NOT NULL AND config_value_json IS NULL) OR
221
+ (config_type = 'json' AND config_value_json IS NOT NULL AND config_value_text IS NULL)
222
+ );
223
+
224
+ -- Update indexes
225
+ DROP INDEX IF EXISTS idx_hazo_app_config_org;
226
+ DROP INDEX IF EXISTS idx_hazo_app_config_unique;
227
+
228
+ CREATE INDEX idx_hazo_app_config_scope ON hazo_app_config(scope_id);
229
+ CREATE UNIQUE INDEX idx_hazo_app_config_unique ON hazo_app_config(scope_id, user_id, config_section, config_name);
230
+ ```
231
+
232
+ **SQLite:**
233
+ ```sql
234
+ -- SQLite doesn't support dropping columns, so recreate the table
235
+ CREATE TABLE hazo_app_config_new (
236
+ id TEXT PRIMARY KEY,
237
+ scope_id TEXT,
238
+ user_id TEXT,
239
+ config_section TEXT NOT NULL,
240
+ config_name TEXT NOT NULL,
241
+ config_value_text TEXT,
242
+ config_value_json TEXT,
243
+ config_type TEXT NOT NULL CHECK (config_type IN ('json', 'general')),
244
+ created_at TEXT DEFAULT (datetime('now')),
245
+ changed_at TEXT,
246
+ CHECK (
247
+ (config_type = 'general' AND config_value_text IS NOT NULL AND config_value_json IS NULL) OR
248
+ (config_type = 'json' AND config_value_json IS NOT NULL AND config_value_text IS NULL)
249
+ )
250
+ );
251
+
252
+ -- Migrate data
253
+ INSERT INTO hazo_app_config_new (id, scope_id, user_id, config_section, config_name, config_value_text, config_value_json, config_type, created_at, changed_at)
254
+ SELECT id, org_id, user_id, config_section, config_name, config_value, NULL, 'general', created_at, changed_at
255
+ FROM hazo_app_config;
256
+
257
+ -- Replace old table
258
+ DROP TABLE hazo_app_config;
259
+ ALTER TABLE hazo_app_config_new RENAME TO hazo_app_config;
260
+
261
+ -- Recreate indexes
262
+ CREATE INDEX idx_hazo_app_config_scope ON hazo_app_config(scope_id);
263
+ CREATE INDEX idx_hazo_app_config_user ON hazo_app_config(user_id);
264
+ CREATE UNIQUE INDEX idx_hazo_app_config_unique ON hazo_app_config(scope_id, user_id, config_section, config_name);
265
+ ```
266
+
267
+ ### 2. Code Migration
268
+
269
+ **Update Component Usage:**
270
+ ```typescript
271
+ // OLD
272
+ import { AppConfig } from 'hazo_config/components'
273
+
274
+ <AppConfig
275
+ level="org"
276
+ context={{ org_id: DEMO_ORG_ID }}
277
+ fetch_config={fetch_config}
278
+ save_config={save_config}
279
+ delete_config={delete_config}
280
+ />
281
+
282
+ // NEW
283
+ import { AppConfig } from 'hazo_config/components'
284
+
285
+ <AppConfig
286
+ title="Scope Settings"
287
+ context={{ scope_id: DEMO_SCOPE_ID }}
288
+ fetch_config={fetch_config}
289
+ save_config={save_config}
290
+ delete_config={delete_config}
291
+ />
292
+ ```
293
+
294
+ **Update Server Actions:**
295
+ ```typescript
296
+ // OLD
297
+ export async function fetch_config(
298
+ level: ConfigLevel,
299
+ context: AppConfigContext
300
+ ): Promise<AppConfigItem[]> {
301
+ const query = new QueryBuilder()
302
+ .from('hazo_app_config')
303
+ .where('org_id', 'eq', context.org_id)
304
+
305
+ if (level === 'org') {
306
+ query.where('user_id', 'is', null)
307
+ } else {
308
+ query.where('user_id', 'eq', context.user_id)
309
+ }
310
+
311
+ return await hazo.query(query)
312
+ }
313
+
314
+ // NEW
315
+ export async function fetch_config(
316
+ context: AppConfigContext
317
+ ): Promise<AppConfigItem[]> {
318
+ const query = new QueryBuilder()
319
+ .from('hazo_app_config')
320
+ .where('scope_id', 'eq', context.scope_id)
321
+
322
+ if (context.user_id) {
323
+ query.where('user_id', 'eq', context.user_id)
324
+ } else {
325
+ query.where('user_id', 'is', null)
326
+ }
327
+
328
+ const result = await hazo.query(query)
329
+
330
+ // Parse JSON for SQLite (stored as TEXT)
331
+ return result.map(item => ({
332
+ ...item,
333
+ config_value_json: item.config_value_json
334
+ ? JSON.parse(item.config_value_json)
335
+ : {}
336
+ }))
337
+ }
338
+
339
+ export async function save_config(
340
+ item: Omit<AppConfigItem, 'id' | 'created_at' | 'changed_at'>
341
+ ): Promise<void> {
342
+ // Validate mutual exclusivity
343
+ if (item.config_type === 'general' && !item.config_value_text) {
344
+ throw new Error('General type requires config_value_text')
345
+ }
346
+ if (item.config_type === 'json' && !item.config_value_json) {
347
+ throw new Error('JSON type requires config_value_json')
348
+ }
349
+
350
+ // For SQLite: stringify JSON
351
+ const db_data = {
352
+ config_value_text: item.config_type === 'general' ? item.config_value_text : null,
353
+ config_value_json: item.config_type === 'json'
354
+ ? JSON.stringify(item.config_value_json)
355
+ : null,
356
+ config_type: item.config_type,
357
+ }
358
+
359
+ // ... rest of save logic
360
+ }
361
+
362
+ export async function delete_config(
363
+ section: string,
364
+ name: string,
365
+ context: AppConfigContext
366
+ ): Promise<void> {
367
+ const query = new QueryBuilder()
368
+ .from('hazo_app_config')
369
+ .where('scope_id', 'eq', context.scope_id)
370
+ .where('config_section', 'eq', section)
371
+ .where('config_name', 'eq', name)
372
+
373
+ if (context.user_id) {
374
+ query.where('user_id', 'eq', context.user_id)
375
+ } else {
376
+ query.where('user_id', 'is', null)
377
+ }
378
+
379
+ await hazo.query(query, 'DELETE')
380
+ }
381
+ ```
382
+
383
+ **Update Hook Usage:**
384
+ ```typescript
385
+ // OLD
386
+ const {
387
+ sections,
388
+ set_value,
389
+ delete_value
390
+ } = useAppConfig('org', context, fetch_config, save_config, delete_config)
391
+
392
+ // Access value
393
+ const value = sections.general.company_name // string
394
+
395
+ // Set value
396
+ await set_value('general', 'company_name', 'New Name')
397
+
398
+ // NEW
399
+ const {
400
+ sections,
401
+ set_value,
402
+ delete_value,
403
+ change_type
404
+ } = useAppConfig(context, fetch_config, save_config, delete_config)
405
+
406
+ // Access value
407
+ const item = sections.general.company_name // AppConfigItem
408
+ const value = item.config_type === 'json'
409
+ ? item.config_value_json
410
+ : item.config_value_text
411
+
412
+ // Set general value
413
+ await set_value('general', 'company_name', 'New Name', 'general')
414
+
415
+ // Set JSON value
416
+ await set_value('features', 'flags', { beta: true }, 'json')
417
+
418
+ // Change type
419
+ await change_type('general', 'company_name', 'json')
420
+ ```
421
+
422
+ ### 3. Import Updates
423
+
424
+ **Type Imports:**
425
+ ```typescript
426
+ // OLD
427
+ import type { ConfigLevel, AppConfigItem } from 'hazo_config'
428
+
429
+ // NEW
430
+ import type { ConfigType, AppConfigItem } from 'hazo_config'
431
+ ```
432
+
433
+ ## New Features in v2
434
+
435
+ ### 1. JSON Configuration Values
436
+
437
+ You can now store structured configuration as JSON:
438
+
439
+ ```typescript
440
+ await set_value('features', 'enabled_features', {
441
+ analytics: true,
442
+ notifications: false,
443
+ api_access: true
444
+ }, 'json')
445
+
446
+ await set_value('branding', 'theme_config', {
447
+ dark_mode: true,
448
+ accent_color: '#3B82F6',
449
+ font: 'Inter'
450
+ }, 'json')
451
+ ```
452
+
453
+ ### 2. Type Conversion
454
+
455
+ Convert between general and JSON types:
456
+
457
+ ```typescript
458
+ // Convert text to JSON
459
+ await change_type('general', 'settings', 'json')
460
+ // Will try JSON.parse(), fallback to { value: text }
461
+
462
+ // Convert JSON to text
463
+ await change_type('features', 'flags', 'general')
464
+ // Will JSON.stringify() the object
465
+ ```
466
+
467
+ ### 3. UI Enhancements
468
+
469
+ - Type badge showing 'json' or 'general'
470
+ - JSON editor with syntax validation
471
+ - Type change button with confirmation
472
+ - Type selector when adding new keys
473
+
474
+ ## Testing Migration
475
+
476
+ Before migrating production:
477
+
478
+ 1. **Backup your database**
479
+ 2. **Test migration on a copy:**
480
+ ```bash
481
+ # PostgreSQL
482
+ pg_dump production_db > backup.sql
483
+ createdb test_db
484
+ psql test_db < backup.sql
485
+ # Run migration scripts on test_db
486
+ ```
487
+
488
+ 3. **Update and test your application:**
489
+ ```bash
490
+ npm install hazo_config@^2.0.0
491
+ npm run build
492
+ npm test
493
+ ```
494
+
495
+ 4. **Verify data integrity:**
496
+ - All `config_value` migrated to `config_value_text`
497
+ - All items have `config_type = 'general'`
498
+ - All items have `scope_id` matching old `org_id`
499
+ - Unique constraints still enforced
500
+
501
+ ## Rollback Plan
502
+
503
+ If you need to rollback:
504
+
505
+ ```sql
506
+ -- PostgreSQL
507
+ ALTER TABLE hazo_app_config
508
+ ADD COLUMN org_id TEXT,
509
+ ADD COLUMN config_value TEXT;
510
+
511
+ UPDATE hazo_app_config SET
512
+ org_id = scope_id,
513
+ config_value = COALESCE(config_value_text, config_value_json::text);
514
+
515
+ ALTER TABLE hazo_app_config
516
+ DROP COLUMN scope_id,
517
+ DROP COLUMN config_value_text,
518
+ DROP COLUMN config_value_json,
519
+ DROP COLUMN config_type;
520
+ ```
521
+
522
+ ## Support
523
+
524
+ For migration assistance:
525
+ - File an issue: https://github.com/pub12/hazo_config/issues
526
+ - Email: support@example.com
527
+
528
+ ## Version History
529
+
530
+ - **v2.0.0**: Schema migration (scope_id, JSON support)
531
+ - **v1.4.2**: Last version before breaking changes