@quazardous/qdadm 1.19.3
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/LICENSE +21 -0
- package/README.md +111 -0
- package/package.json +101 -0
- package/src/assets/logo.svg +6 -0
- package/src/auth/SessionAuthAdapter.ts +459 -0
- package/src/auth/index.ts +15 -0
- package/src/chain/ActiveStack.ts +154 -0
- package/src/chain/StackHydrator.ts +292 -0
- package/src/chain/index.ts +25 -0
- package/src/chain/useActiveStack.ts +120 -0
- package/src/chain/useStackHydrator.ts +155 -0
- package/src/components/BoolCell.vue +34 -0
- package/src/components/InfoBanner.vue +73 -0
- package/src/components/QdadmRoot.vue +52 -0
- package/src/components/SeverityTag.vue +86 -0
- package/src/components/dialogs/BulkStatusDialog.vue +60 -0
- package/src/components/dialogs/MultiStepDialog.vue +331 -0
- package/src/components/dialogs/SimpleDialog.vue +122 -0
- package/src/components/dialogs/UnsavedChangesDialog.vue +80 -0
- package/src/components/display/BannerZone.vue +65 -0
- package/src/components/display/CardsGrid.vue +72 -0
- package/src/components/display/CopyableId.vue +84 -0
- package/src/components/display/EmptyState.vue +62 -0
- package/src/components/display/IntensityBar.vue +190 -0
- package/src/components/display/RichCardsGrid.vue +101 -0
- package/src/components/edit/FormActions.vue +78 -0
- package/src/components/edit/FormField.vue +82 -0
- package/src/components/edit/FormInput.vue +158 -0
- package/src/components/edit/FormPage.vue +260 -0
- package/src/components/edit/FormTab.vue +217 -0
- package/src/components/edit/FormTabs.vue +120 -0
- package/src/components/edit/LookupField.vue +406 -0
- package/src/components/edit/LookupPickerDialog.vue +230 -0
- package/src/components/editors/JsonEditorFoldable.vue +470 -0
- package/src/components/editors/JsonStructuredField.vue +264 -0
- package/src/components/editors/JsonViewer.vue +51 -0
- package/src/components/editors/KeyValueEditor.vue +314 -0
- package/src/components/editors/LanguageEditor.vue +259 -0
- package/src/components/editors/PermissionEditor.vue +558 -0
- package/src/components/editors/ScopeEditor.vue +372 -0
- package/src/components/editors/VanillaJsonEditor.vue +183 -0
- package/src/components/index.ts +83 -0
- package/src/components/item/FieldGroups.vue +392 -0
- package/src/components/layout/AppLayout.vue +1032 -0
- package/src/components/layout/BaseLayout.vue +190 -0
- package/src/components/layout/Breadcrumb.vue +55 -0
- package/src/components/layout/DashboardLayout.vue +104 -0
- package/src/components/layout/FormLayout.vue +256 -0
- package/src/components/layout/ListLayout.vue +278 -0
- package/src/components/layout/PageHeader.vue +156 -0
- package/src/components/layout/PageLayout.vue +126 -0
- package/src/components/layout/PageNav.vue +192 -0
- package/src/components/layout/SidebarBox.vue +87 -0
- package/src/components/layout/Zone.vue +182 -0
- package/src/components/layout/defaults/DefaultBreadcrumb.vue +149 -0
- package/src/components/layout/defaults/DefaultFooter.vue +89 -0
- package/src/components/layout/defaults/DefaultFormActions.vue +67 -0
- package/src/components/layout/defaults/DefaultHeader.vue +78 -0
- package/src/components/layout/defaults/DefaultMenu.vue +188 -0
- package/src/components/layout/defaults/DefaultPagination.vue +75 -0
- package/src/components/layout/defaults/DefaultTable.vue +137 -0
- package/src/components/layout/defaults/DefaultUserInfo.vue +111 -0
- package/src/components/layout/defaults/index.ts +16 -0
- package/src/components/lists/ActionButtons.vue +49 -0
- package/src/components/lists/ActionColumn.vue +40 -0
- package/src/components/lists/FilterBar.vue +54 -0
- package/src/components/lists/ListPage.vue +467 -0
- package/src/components/pages/LoginPage.vue +334 -0
- package/src/components/pages/NotFoundPage.vue +98 -0
- package/src/components/show/ShowDisplay.vue +343 -0
- package/src/components/show/ShowField.vue +67 -0
- package/src/components/show/ShowPage.vue +431 -0
- package/src/composables/index.ts +155 -0
- package/src/composables/useApp.ts +67 -0
- package/src/composables/useAuth.ts +98 -0
- package/src/composables/useBareForm.ts +319 -0
- package/src/composables/useBreadcrumb.ts +252 -0
- package/src/composables/useChildPage.ts +113 -0
- package/src/composables/useCurrentEntity.ts +60 -0
- package/src/composables/useDeferred.ts +94 -0
- package/src/composables/useDirtyState.ts +138 -0
- package/src/composables/useEntityItemFormPage.ts +942 -0
- package/src/composables/useEntityItemFormPage.types.ts +451 -0
- package/src/composables/useEntityItemPage.ts +438 -0
- package/src/composables/useEntityItemShowPage.ts +722 -0
- package/src/composables/useFieldManager.ts +692 -0
- package/src/composables/useForm.ts +544 -0
- package/src/composables/useGuardStore.ts +53 -0
- package/src/composables/useHooks.ts +56 -0
- package/src/composables/useInfoBanner.ts +217 -0
- package/src/composables/useJsonSyntax.ts +122 -0
- package/src/composables/useLayoutResolver.ts +329 -0
- package/src/composables/useListPage.ts +1535 -0
- package/src/composables/useListPage.types.ts +437 -0
- package/src/composables/useListPage.utils.ts +101 -0
- package/src/composables/useNavContext.ts +439 -0
- package/src/composables/useNavigation.ts +216 -0
- package/src/composables/useOptionsLookup.ts +323 -0
- package/src/composables/usePageTitle.ts +84 -0
- package/src/composables/useSSEBridge.ts +142 -0
- package/src/composables/useSemanticBreadcrumb.ts +222 -0
- package/src/composables/useSidebarState.ts +24 -0
- package/src/composables/useSignals.ts +52 -0
- package/src/composables/useUnsavedChangesGuard.ts +155 -0
- package/src/composables/useUserImpersonator.ts +317 -0
- package/src/composables/useZoneRegistry.ts +252 -0
- package/src/core/bundles.ts +558 -0
- package/src/core/decorator.ts +420 -0
- package/src/core/extension.ts +470 -0
- package/src/core/index.ts +59 -0
- package/src/deferred/DeferredRegistry.ts +353 -0
- package/src/deferred/index.ts +15 -0
- package/src/editors/index.ts +12 -0
- package/src/entity/EntityManager.cache.ts +530 -0
- package/src/entity/EntityManager.crud.ts +615 -0
- package/src/entity/EntityManager.interface.ts +78 -0
- package/src/entity/EntityManager.query.ts +221 -0
- package/src/entity/EntityManager.relations.ts +151 -0
- package/src/entity/EntityManager.ts +944 -0
- package/src/entity/EntityManager.types.ts +288 -0
- package/src/entity/auth/CompositeAuthAdapter.ts +194 -0
- package/src/entity/auth/EntityAuthAdapter.ts +119 -0
- package/src/entity/auth/PermissiveAdapter.ts +43 -0
- package/src/entity/auth/RoleHierarchy.ts +128 -0
- package/src/entity/auth/SecurityChecker.ts +202 -0
- package/src/entity/auth/factory.ts +203 -0
- package/src/entity/auth/index.ts +47 -0
- package/src/entity/factory.test.js +189 -0
- package/src/entity/factory.ts +146 -0
- package/src/entity/index.ts +49 -0
- package/src/entity/storage/ApiStorage.ts +228 -0
- package/src/entity/storage/IStorage.ts +100 -0
- package/src/entity/storage/LocalStorage.ts +200 -0
- package/src/entity/storage/MemoryStorage.ts +184 -0
- package/src/entity/storage/MockApiStorage.ts +318 -0
- package/src/entity/storage/SdkStorage.ts +377 -0
- package/src/entity/storage/factory.test.js +159 -0
- package/src/entity/storage/factory.ts +209 -0
- package/src/entity/storage/index.ts +75 -0
- package/src/gen/FieldMapper.ts +122 -0
- package/src/gen/StorageProfileFactory.ts +93 -0
- package/src/gen/connectors/BaseConnector.ts +150 -0
- package/src/gen/connectors/ManualConnector.test.js +499 -0
- package/src/gen/connectors/ManualConnector.ts +414 -0
- package/src/gen/connectors/OpenAPIConnector.test.js +836 -0
- package/src/gen/connectors/OpenAPIConnector.ts +693 -0
- package/src/gen/connectors/__fixtures__/sample-openapi.json +311 -0
- package/src/gen/connectors/index.ts +27 -0
- package/src/gen/createManagers.ts +245 -0
- package/src/gen/decorators.ts +134 -0
- package/src/gen/generateManagers.test.js +540 -0
- package/src/gen/generateManagers.ts +398 -0
- package/src/gen/index.ts +66 -0
- package/src/gen/schema.ts +248 -0
- package/src/gen/vite-plugin.ts +114 -0
- package/src/generated/managers/testManager.js +45 -0
- package/src/generated/managers/testManager.ts +46 -0
- package/src/hooks/HookRegistry.ts +16 -0
- package/src/hooks/index.ts +15 -0
- package/src/i18n/I18n.ts +344 -0
- package/src/i18n/IncrementalDomainProvider.ts +153 -0
- package/src/i18n/InlineTranslationProvider.ts +4 -0
- package/src/i18n/LazyTranslationProvider.ts +102 -0
- package/src/i18n/MessagesRegistry.ts +4 -0
- package/src/i18n/Resolver.ts +4 -0
- package/src/i18n/__tests__/I18n.test.ts +169 -0
- package/src/i18n/__tests__/IncrementalDomainProvider.test.ts +146 -0
- package/src/i18n/__tests__/LazyTranslationProvider.test.ts +100 -0
- package/src/i18n/__tests__/Resolver.test.ts +271 -0
- package/src/i18n/defaults/DefaultCoreProvider.ts +28 -0
- package/src/i18n/defaults/core.en.yml +55 -0
- package/src/i18n/defaults/core.fr.yml +55 -0
- package/src/i18n/index.ts +55 -0
- package/src/i18n/loaders/raw-modules.d.ts +15 -0
- package/src/i18n/loaders/yaml.ts +35 -0
- package/src/i18n/strategies.ts +4 -0
- package/src/i18n/types.ts +34 -0
- package/src/i18n/useI18n.ts +34 -0
- package/src/index.ts +453 -0
- package/src/kernel/EventRouter.ts +23 -0
- package/src/kernel/Kernel.api.ts +79 -0
- package/src/kernel/Kernel.auth.ts +120 -0
- package/src/kernel/Kernel.i18n.ts +29 -0
- package/src/kernel/Kernel.modules.ts +170 -0
- package/src/kernel/Kernel.registries.ts +237 -0
- package/src/kernel/Kernel.routing.ts +334 -0
- package/src/kernel/Kernel.ts +460 -0
- package/src/kernel/Kernel.types.ts +263 -0
- package/src/kernel/Kernel.vue.ts +304 -0
- package/src/kernel/KernelContext.entities.ts +80 -0
- package/src/kernel/KernelContext.events.ts +57 -0
- package/src/kernel/KernelContext.i18n.ts +37 -0
- package/src/kernel/KernelContext.permissions.ts +38 -0
- package/src/kernel/KernelContext.routing.ts +280 -0
- package/src/kernel/KernelContext.ts +252 -0
- package/src/kernel/KernelContext.types.ts +173 -0
- package/src/kernel/KernelContext.zones.ts +54 -0
- package/src/kernel/Module.ts +169 -0
- package/src/kernel/ModuleLoader.ts +645 -0
- package/src/kernel/SSEBridge.ts +11 -0
- package/src/kernel/SignalBus.ts +48 -0
- package/src/kernel/index.ts +56 -0
- package/src/module/index.ts +33 -0
- package/src/module/moduleRegistry.ts +492 -0
- package/src/modules/debug/AuthCollector.ts +501 -0
- package/src/modules/debug/Collector.ts +20 -0
- package/src/modules/debug/DebugBridge.ts +14 -0
- package/src/modules/debug/DebugModule.ts +220 -0
- package/src/modules/debug/EntitiesCollector.ts +839 -0
- package/src/modules/debug/ErrorCollector.ts +4 -0
- package/src/modules/debug/I18nCollector.ts +9 -0
- package/src/modules/debug/LocalStorageAdapter.ts +7 -0
- package/src/modules/debug/RouterCollector.ts +449 -0
- package/src/modules/debug/SignalCollector.ts +4 -0
- package/src/modules/debug/ToastCollector.ts +4 -0
- package/src/modules/debug/ZonesCollector.ts +447 -0
- package/src/modules/debug/components/DebugBar.vue +31 -0
- package/src/modules/debug/components/adminPanelsConfig.ts +42 -0
- package/src/modules/debug/components/index.ts +9 -0
- package/src/modules/debug/components/panels/AuthPanel.vue +267 -0
- package/src/modules/debug/components/panels/EntitiesPanel.vue +572 -0
- package/src/modules/debug/components/panels/I18nPanel.vue +738 -0
- package/src/modules/debug/components/panels/RouterPanel.vue +440 -0
- package/src/modules/debug/components/panels/ZonesPanel.vue +133 -0
- package/src/modules/debug/components/panels/index.ts +16 -0
- package/src/modules/debug/index.ts +58 -0
- package/src/modules/debug/styles.scss +2671 -0
- package/src/notifications/NotificationBadge.vue +35 -0
- package/src/notifications/NotificationListener.vue +71 -0
- package/src/notifications/NotificationModule.ts +76 -0
- package/src/notifications/NotificationPanel.vue +478 -0
- package/src/notifications/NotificationStore.ts +288 -0
- package/src/notifications/index.ts +42 -0
- package/src/notifications/styles.scss +32 -0
- package/src/orchestrator/Orchestrator.ts +427 -0
- package/src/orchestrator/index.ts +8 -0
- package/src/orchestrator/useOrchestrator.ts +80 -0
- package/src/plugin.ts +251 -0
- package/src/query/FilterQuery.ts +306 -0
- package/src/query/QueryExecutor.ts +341 -0
- package/src/query/index.ts +16 -0
- package/src/security/EntityRolesProvider.ts +385 -0
- package/src/security/PermissionMatcher.ts +152 -0
- package/src/security/PermissionRegistry.ts +273 -0
- package/src/security/PersistableRolesProvider.ts +584 -0
- package/src/security/RolesManager.ts +136 -0
- package/src/security/RolesProvider.ts +167 -0
- package/src/security/RolesProviderStorage.ts +211 -0
- package/src/security/SecurityModule.ts +94 -0
- package/src/security/StaticRolesProvider.ts +117 -0
- package/src/security/UsersManager.ts +163 -0
- package/src/security/index.ts +77 -0
- package/src/security/pages/RoleForm.vue +276 -0
- package/src/security/pages/RoleList.vue +123 -0
- package/src/styles/_alerts.scss +95 -0
- package/src/styles/_buttons.scss +391 -0
- package/src/styles/_cards.scss +241 -0
- package/src/styles/_code.scss +78 -0
- package/src/styles/_dialogs.scss +55 -0
- package/src/styles/_filter-bar.scss +70 -0
- package/src/styles/_forms.scss +434 -0
- package/src/styles/_helpers.scss +153 -0
- package/src/styles/_lists.scss +225 -0
- package/src/styles/_main.scss +356 -0
- package/src/styles/_markdown.scss +88 -0
- package/src/styles/_responsive.scss +255 -0
- package/src/styles/_show-pages.scss +186 -0
- package/src/styles/_sidebar.scss +380 -0
- package/src/styles/_states.scss +115 -0
- package/src/styles/_stats.scss +47 -0
- package/src/styles/_variables.scss +224 -0
- package/src/styles/index.scss +38 -0
- package/src/styles/theme/_components.scss +331 -0
- package/src/styles/theme/_index.scss +10 -0
- package/src/styles/theme/_tokens.scss +147 -0
- package/src/styles/theme/_utilities.scss +175 -0
- package/src/toast/ToastBridgeModule.ts +68 -0
- package/src/toast/ToastListener.vue +55 -0
- package/src/toast/index.ts +26 -0
- package/src/toast/useSignalToast.ts +154 -0
- package/src/types/index.ts +285 -0
- package/src/types/pluralize.d.ts +19 -0
- package/src/types/quarkernel.d.ts +50 -0
- package/src/utils/formatters.ts +174 -0
- package/src/utils/index.ts +35 -0
- package/src/utils/transformers.ts +131 -0
- package/src/vite/qdadmDebugPlugin.ts +401 -0
- package/src/vite-env.d.ts +50 -0
- package/src/zones/ZoneRegistry.ts +934 -0
- package/src/zones/index.ts +29 -0
- package/src/zones/zones.ts +191 -0
|
@@ -0,0 +1,114 @@
|
|
|
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, type GenerateManagersConfig } from './generateManagers'
|
|
11
|
+
import { pathToFileURL } from 'node:url'
|
|
12
|
+
import { resolve } from 'node:path'
|
|
13
|
+
import type { Plugin } from 'vite'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Plugin options for qdadmGen
|
|
17
|
+
*/
|
|
18
|
+
export interface QdadmGenOptions {
|
|
19
|
+
/** Path to qdadm config file (default: 'qdadm.config.js') */
|
|
20
|
+
config?: string
|
|
21
|
+
/** Override output directory for generated files */
|
|
22
|
+
output?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Vite plugin for generating EntityManager files at build time
|
|
27
|
+
*
|
|
28
|
+
* Loads the qdadm configuration file and calls generateManagers during
|
|
29
|
+
* Vite's buildStart hook. Supports both ESM and CommonJS config files.
|
|
30
|
+
*
|
|
31
|
+
* @param options - Plugin options
|
|
32
|
+
* @returns Vite plugin object
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* // vite.config.js
|
|
37
|
+
* import { defineConfig } from 'vite'
|
|
38
|
+
* import { qdadmGen } from '@quazardous/qdadm/gen/vite-plugin'
|
|
39
|
+
*
|
|
40
|
+
* export default defineConfig({
|
|
41
|
+
* plugins: [
|
|
42
|
+
* qdadmGen({
|
|
43
|
+
* config: './qdadm.config.js',
|
|
44
|
+
* output: 'src/generated/managers/'
|
|
45
|
+
* })
|
|
46
|
+
* ]
|
|
47
|
+
* })
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* // qdadm.config.js
|
|
53
|
+
* export default {
|
|
54
|
+
* output: 'src/generated/managers/',
|
|
55
|
+
* entities: {
|
|
56
|
+
* users: {
|
|
57
|
+
* schema: { name: 'users', fields: { id: { type: 'integer' } } },
|
|
58
|
+
* endpoint: '/api/users',
|
|
59
|
+
* storageImport: '@quazardous/qdadm',
|
|
60
|
+
* storageClass: 'ApiStorage'
|
|
61
|
+
* }
|
|
62
|
+
* }
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export function qdadmGen(options: QdadmGenOptions = {}): Plugin {
|
|
67
|
+
const configPath = options.config || 'qdadm.config.js'
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
name: 'qdadm-gen',
|
|
71
|
+
|
|
72
|
+
async buildStart() {
|
|
73
|
+
try {
|
|
74
|
+
// Resolve config path relative to cwd
|
|
75
|
+
const resolvedConfigPath = resolve(process.cwd(), configPath)
|
|
76
|
+
|
|
77
|
+
// Load config file dynamically
|
|
78
|
+
const configUrl = pathToFileURL(resolvedConfigPath).href
|
|
79
|
+
const configModule = (await import(configUrl)) as {
|
|
80
|
+
default?: GenerateManagersConfig
|
|
81
|
+
} & GenerateManagersConfig
|
|
82
|
+
const loadedConfig = configModule.default || configModule
|
|
83
|
+
|
|
84
|
+
// Validate loaded config
|
|
85
|
+
if (!loadedConfig || typeof loadedConfig !== 'object') {
|
|
86
|
+
throw new Error(`Invalid qdadm config at '${configPath}': must export an object`)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!loadedConfig.entities) {
|
|
90
|
+
throw new Error(`Invalid qdadm config at '${configPath}': missing 'entities' property`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Merge plugin options with config (plugin options take precedence)
|
|
94
|
+
const mergedConfig: GenerateManagersConfig = {
|
|
95
|
+
...loadedConfig,
|
|
96
|
+
...(options.output && { output: options.output }),
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Generate managers
|
|
100
|
+
const generatedFiles = await generateManagers(mergedConfig)
|
|
101
|
+
|
|
102
|
+
// Log results
|
|
103
|
+
console.log(`[qdadm-gen] Generated ${generatedFiles.length} manager file(s)`)
|
|
104
|
+
for (const file of generatedFiles) {
|
|
105
|
+
console.log(` - ${file}`)
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
// Wrap error with plugin context for clearer messages
|
|
109
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
110
|
+
throw new Error(`[qdadm-gen] Failed to generate managers: ${message}`)
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -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 '@quazardous/qdadm'
|
|
12
|
+
import { ApiStorage } from '@quazardous/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
|
+
})
|
|
@@ -0,0 +1,46 @@
|
|
|
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 '@quazardous/qdadm'
|
|
12
|
+
import type { EntityRecord } from '@quazardous/qdadm'
|
|
13
|
+
import { ApiStorage } from '@quazardous/qdadm'
|
|
14
|
+
|
|
15
|
+
export interface TestEntity extends EntityRecord {
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Schema definition for test
|
|
20
|
+
*/
|
|
21
|
+
export const testSchema = {
|
|
22
|
+
name: "test",
|
|
23
|
+
endpoint: "/test",
|
|
24
|
+
fields: {}
|
|
25
|
+
} as const
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Storage options for test
|
|
29
|
+
*/
|
|
30
|
+
const storageOptions = {
|
|
31
|
+
endpoint: "/test"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* TestManager instance
|
|
36
|
+
*
|
|
37
|
+
* Provides CRUD operations for test entity.
|
|
38
|
+
*/
|
|
39
|
+
export const testManager = new EntityManager<TestEntity>({
|
|
40
|
+
...{
|
|
41
|
+
name: "test",
|
|
42
|
+
idField: "id",
|
|
43
|
+
fields: {}
|
|
44
|
+
},
|
|
45
|
+
storage: new ApiStorage<TestEntity>(storageOptions)
|
|
46
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HookRegistry — qdadm re-export.
|
|
3
|
+
*
|
|
4
|
+
* The actual implementation now lives in `@quazardous/qdcore` so it can be
|
|
5
|
+
* shared with qdcms. This module re-exports the public API to preserve
|
|
6
|
+
* existing qdadm imports (`from '../hooks/HookRegistry'`).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { HookRegistry, createHookRegistry, HOOK_PRIORITY } from '@quazardous/qdcore'
|
|
10
|
+
export type {
|
|
11
|
+
HookHandler,
|
|
12
|
+
HookRegistrationOptions,
|
|
13
|
+
HookRegistryOptions,
|
|
14
|
+
InvokeOptions,
|
|
15
|
+
AlterOptions,
|
|
16
|
+
} from '@quazardous/qdcore'
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hooks Module
|
|
3
|
+
*
|
|
4
|
+
* Drupal-inspired hook system for extensibility.
|
|
5
|
+
* Built on QuarKernel signal bus.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { HookRegistry, createHookRegistry, HOOK_PRIORITY } from './HookRegistry'
|
|
9
|
+
export type {
|
|
10
|
+
HookHandler,
|
|
11
|
+
HookRegistrationOptions,
|
|
12
|
+
HookRegistryOptions,
|
|
13
|
+
InvokeOptions,
|
|
14
|
+
AlterOptions,
|
|
15
|
+
} from './HookRegistry'
|
package/src/i18n/I18n.ts
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* I18n - Top-level orchestrator (exposed as `kernel.i18n`).
|
|
3
|
+
*
|
|
4
|
+
* Owns:
|
|
5
|
+
* - the active locale (reactive ref)
|
|
6
|
+
* - the MessagesRegistry
|
|
7
|
+
* - registered TranslationProviders
|
|
8
|
+
* - the active strategy's pattern aliases
|
|
9
|
+
* - the Resolver
|
|
10
|
+
*
|
|
11
|
+
* Public API:
|
|
12
|
+
* - t(key, params?)
|
|
13
|
+
* - locale (ref)
|
|
14
|
+
* - availableLocales
|
|
15
|
+
* - resolve(key) (debug trace)
|
|
16
|
+
* - dump(locale) (export merged bundle)
|
|
17
|
+
* - changeLocale(locale) (programmatic; the public way is signals.emit('locale:change', loc))
|
|
18
|
+
* - addProvider(p)
|
|
19
|
+
* - addMessages(locale, bundle)
|
|
20
|
+
* - addAliases(patterns)
|
|
21
|
+
* - registerEntityModule(entity, module) (for the 'module' strategy)
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { ref, type Ref } from 'vue'
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
MessagesRegistry,
|
|
28
|
+
Resolver,
|
|
29
|
+
InlineTranslationProvider,
|
|
30
|
+
resolveStrategy,
|
|
31
|
+
} from '@quazardous/qdcore'
|
|
32
|
+
import type {
|
|
33
|
+
AliasPattern,
|
|
34
|
+
MessagesBundle,
|
|
35
|
+
ResolveTrace,
|
|
36
|
+
TranslateParams,
|
|
37
|
+
TranslationProvider,
|
|
38
|
+
} from '@quazardous/qdcore'
|
|
39
|
+
import { createDefaultCoreProvider } from './defaults/DefaultCoreProvider'
|
|
40
|
+
import { isDomainAwareProvider } from './IncrementalDomainProvider'
|
|
41
|
+
import type { I18nOptions } from './types'
|
|
42
|
+
|
|
43
|
+
export interface I18nDeps {
|
|
44
|
+
/** SignalBus instance for emitting locale:changed and i18n:missing. */
|
|
45
|
+
signals?: {
|
|
46
|
+
emit: (signal: string, payload?: unknown) => void | Promise<void>
|
|
47
|
+
on: (signal: string, handler: (e: { data: unknown }) => void) => () => void
|
|
48
|
+
} | null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class I18n {
|
|
52
|
+
readonly inline: InlineTranslationProvider
|
|
53
|
+
readonly locale: Ref<string>
|
|
54
|
+
|
|
55
|
+
private _registry: MessagesRegistry
|
|
56
|
+
private _resolver: Resolver
|
|
57
|
+
private _providers: TranslationProvider[]
|
|
58
|
+
private _strategyName: string | string[]
|
|
59
|
+
private _appAliases: AliasPattern[]
|
|
60
|
+
private _entityToModule: Map<string, string> = new Map()
|
|
61
|
+
private _signals: I18nDeps['signals']
|
|
62
|
+
private _defaultLocale: string
|
|
63
|
+
private _fallbackLocale: string
|
|
64
|
+
private _loadedLocales: Set<string> = new Set()
|
|
65
|
+
// Track which (locale::domain) pairs have already been resolved through a
|
|
66
|
+
// domain-aware provider, so we don't re-trigger an async load on every
|
|
67
|
+
// subsequent miss for the same domain.
|
|
68
|
+
private _loadedDomains: Set<string> = new Set()
|
|
69
|
+
// Concurrent domain loads — share one in-flight promise per (locale, domain)
|
|
70
|
+
// pair across providers and t() callers.
|
|
71
|
+
private _inflightDomains: Map<string, Promise<void>> = new Map()
|
|
72
|
+
|
|
73
|
+
constructor(options: I18nOptions = {}, deps: I18nDeps = {}) {
|
|
74
|
+
this._defaultLocale = options.defaultLocale ?? 'en'
|
|
75
|
+
this._fallbackLocale = options.fallbackLocale ?? this._defaultLocale
|
|
76
|
+
this._strategyName = options.keyStrategy ?? 'global'
|
|
77
|
+
this._appAliases = options.aliases ? [...options.aliases] : []
|
|
78
|
+
this._signals = deps.signals ?? null
|
|
79
|
+
|
|
80
|
+
this._registry = new MessagesRegistry()
|
|
81
|
+
this.inline = new InlineTranslationProvider()
|
|
82
|
+
|
|
83
|
+
// Provider chain: framework defaults (lazy) → inline (ctx.messages) → user.
|
|
84
|
+
// _loadLocale merges providers in order, so later entries override earlier
|
|
85
|
+
// ones. The default core is first so app-level overrides always win.
|
|
86
|
+
const defaultCoreProviders: TranslationProvider[] = options.disableDefaultCoreBundle
|
|
87
|
+
? []
|
|
88
|
+
: [createDefaultCoreProvider()]
|
|
89
|
+
this._providers = [
|
|
90
|
+
...defaultCoreProviders,
|
|
91
|
+
this.inline,
|
|
92
|
+
...(options.providers ?? []),
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
this.locale = ref(this._defaultLocale)
|
|
96
|
+
|
|
97
|
+
this._resolver = new Resolver(this._registry, {
|
|
98
|
+
defaultLocale: this._defaultLocale,
|
|
99
|
+
fallbackLocale: this._fallbackLocale,
|
|
100
|
+
globalAliases: this._currentStrategyAliases(),
|
|
101
|
+
onMissing: (key, locale) => {
|
|
102
|
+
this._signals?.emit('i18n:missing', { key, locale })
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// App-level messages from kernel options.
|
|
107
|
+
if (options.messages) {
|
|
108
|
+
for (const [loc, bundle] of Object.entries(options.messages)) {
|
|
109
|
+
this.inline.push(loc, bundle)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Listen for locale:change requests on the bus, if present.
|
|
114
|
+
if (this._signals) {
|
|
115
|
+
this._signals.on('locale:change', (event) => {
|
|
116
|
+
const loc = typeof event.data === 'string' ? event.data : null
|
|
117
|
+
if (loc) void this.changeLocale(loc)
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// Public API
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Translate a convention-derived key.
|
|
128
|
+
*
|
|
129
|
+
* On a miss whose top-level segment matches a domain known to one of the
|
|
130
|
+
* registered domain-aware providers (e.g. `IncrementalDomainProvider`),
|
|
131
|
+
* triggers a lazy load for that (locale, domain) pair in the background
|
|
132
|
+
* and emits `i18n:domain-loaded` once merged. The current call still
|
|
133
|
+
* returns the raw key — callers that want to react to the late arrival
|
|
134
|
+
* should listen to `i18n:domain-loaded`.
|
|
135
|
+
*/
|
|
136
|
+
t(key: string, params?: TranslateParams): string {
|
|
137
|
+
const trace = this._resolver.resolve(key, this.locale.value, params)
|
|
138
|
+
if (!trace.hit) {
|
|
139
|
+
this._maybeEnsureDomain(key, this.locale.value)
|
|
140
|
+
}
|
|
141
|
+
return trace.result
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Explicitly request a domain to be loaded for a locale, ahead of any
|
|
146
|
+
* `t()` miss that would have triggered it. Useful when a screen is about
|
|
147
|
+
* to need a rare domain and the dev wants to avoid the placeholder flash.
|
|
148
|
+
*
|
|
149
|
+
* Resolves once the domain is merged into the registry (or rejects if no
|
|
150
|
+
* domain-aware provider knows it).
|
|
151
|
+
*/
|
|
152
|
+
loadDomain(domain: string, locale: string = this.locale.value): Promise<void> {
|
|
153
|
+
return this._ensureDomain(locale, domain)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Return the resolution trace (for debugging).
|
|
158
|
+
*/
|
|
159
|
+
resolve(key: string, params?: TranslateParams): ResolveTrace {
|
|
160
|
+
return this._resolver.resolve(key, this.locale.value, params)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Union of available locales across registered providers.
|
|
165
|
+
*/
|
|
166
|
+
async availableLocales(): Promise<string[]> {
|
|
167
|
+
const set = new Set<string>()
|
|
168
|
+
for (const p of this._providers) {
|
|
169
|
+
if (typeof p.availableLocales === 'function') {
|
|
170
|
+
const locs = await p.availableLocales()
|
|
171
|
+
for (const l of locs) set.add(l)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return Array.from(set)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Switch to a new locale. Loads bundles from providers if not yet cached.
|
|
179
|
+
* Public callers should prefer `signals.emit('locale:change', loc)`.
|
|
180
|
+
*/
|
|
181
|
+
async changeLocale(locale: string): Promise<void> {
|
|
182
|
+
if (this.locale.value === locale && this._loadedLocales.has(locale)) return
|
|
183
|
+
await this._loadLocale(locale)
|
|
184
|
+
this.locale.value = locale
|
|
185
|
+
this._signals?.emit('locale:changed', locale)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Initial bootstrap: load default + fallback locales from providers.
|
|
190
|
+
* Called by the kernel during warmup.
|
|
191
|
+
*/
|
|
192
|
+
async bootstrap(): Promise<void> {
|
|
193
|
+
const targets = new Set<string>([this._defaultLocale, this._fallbackLocale])
|
|
194
|
+
for (const loc of targets) {
|
|
195
|
+
await this._loadLocale(loc)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Register an additional provider after construction.
|
|
201
|
+
*/
|
|
202
|
+
addProvider(provider: TranslationProvider): void {
|
|
203
|
+
this._providers.push(provider)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Push messages into the inline provider (used by ctx.messages()).
|
|
208
|
+
* Bundles merge into the registry on next load. If the locale is already
|
|
209
|
+
* loaded, the bundle is also merged immediately for hot updates.
|
|
210
|
+
*/
|
|
211
|
+
addMessages(locale: string, bundle: MessagesBundle): void {
|
|
212
|
+
this.inline.push(locale, bundle)
|
|
213
|
+
if (this._loadedLocales.has(locale)) {
|
|
214
|
+
this._registry.merge(locale, bundle)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Append app/module-level pattern aliases to the active strategy.
|
|
220
|
+
*/
|
|
221
|
+
addAliases(patterns: AliasPattern[]): void {
|
|
222
|
+
this._appAliases.push(...patterns)
|
|
223
|
+
this._resolver.setOptions({ globalAliases: this._currentStrategyAliases() })
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Track which module owns which entity. Used to materialize the 'module'
|
|
228
|
+
* key strategy at runtime (entities.<entity>.* → modules.<module>.*).
|
|
229
|
+
*/
|
|
230
|
+
registerEntityModule(entity: string, module: string): void {
|
|
231
|
+
this._entityToModule.set(entity, module)
|
|
232
|
+
if (this._strategyIncludes('module')) {
|
|
233
|
+
this._resolver.setOptions({ globalAliases: this._currentStrategyAliases() })
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Export the currently merged bundle for a locale (for translator workflows).
|
|
239
|
+
*/
|
|
240
|
+
dump(locale: string): MessagesBundle {
|
|
241
|
+
return this.inline.load(locale)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
// Internals
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
|
|
248
|
+
private async _loadLocale(locale: string): Promise<void> {
|
|
249
|
+
for (const p of this._providers) {
|
|
250
|
+
const bundle = await p.load(locale)
|
|
251
|
+
if (bundle && Object.keys(bundle).length > 0) {
|
|
252
|
+
this._registry.merge(locale, bundle)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
this._loadedLocales.add(locale)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Fire-and-forget: invoked from t() on a miss to opportunistically pull in
|
|
260
|
+
* a domain-aware provider's bundle for the missed top-level segment.
|
|
261
|
+
* Errors are swallowed (logged as a missing-domain signal) — the current
|
|
262
|
+
* t() call has already returned.
|
|
263
|
+
*/
|
|
264
|
+
private _maybeEnsureDomain(key: string, locale: string): void {
|
|
265
|
+
const dot = key.indexOf('.')
|
|
266
|
+
const domain = dot === -1 ? key : key.slice(0, dot)
|
|
267
|
+
if (!domain) return
|
|
268
|
+
if (this._loadedDomains.has(`${locale}::${domain}`)) return
|
|
269
|
+
if (!this._providers.some((p) => isDomainAwareProvider(p) && p.knowsDomain(domain))) {
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
void this._ensureDomain(locale, domain).catch(() => {
|
|
273
|
+
// Already surfaced via i18n:missing on the original miss; silent here.
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Resolves once a domain has been pulled from any domain-aware provider
|
|
279
|
+
* that knows it. Safe to call concurrently — loads are deduped per
|
|
280
|
+
* (locale, domain) pair. Emits `i18n:domain-loaded` after the merge.
|
|
281
|
+
*/
|
|
282
|
+
private _ensureDomain(locale: string, domain: string): Promise<void> {
|
|
283
|
+
const cacheKey = `${locale}::${domain}`
|
|
284
|
+
if (this._loadedDomains.has(cacheKey)) return Promise.resolve()
|
|
285
|
+
const existing = this._inflightDomains.get(cacheKey)
|
|
286
|
+
if (existing) return existing
|
|
287
|
+
|
|
288
|
+
const promise = (async () => {
|
|
289
|
+
let merged = false
|
|
290
|
+
for (const p of this._providers) {
|
|
291
|
+
if (!isDomainAwareProvider(p) || !p.knowsDomain(domain)) continue
|
|
292
|
+
const bundle = await p.loadDomain(locale, domain)
|
|
293
|
+
if (bundle && Object.keys(bundle).length > 0) {
|
|
294
|
+
this._registry.merge(locale, bundle)
|
|
295
|
+
merged = true
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
this._loadedDomains.add(cacheKey)
|
|
299
|
+
this._inflightDomains.delete(cacheKey)
|
|
300
|
+
if (merged) {
|
|
301
|
+
this._signals?.emit('i18n:domain-loaded', { locale, domain })
|
|
302
|
+
}
|
|
303
|
+
})()
|
|
304
|
+
|
|
305
|
+
this._inflightDomains.set(cacheKey, promise)
|
|
306
|
+
return promise
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private _currentStrategyAliases(): AliasPattern[] {
|
|
310
|
+
const fromStrategy = resolveStrategy(this._strategyName)
|
|
311
|
+
const fromModuleStrategy = this._strategyIncludes('module')
|
|
312
|
+
? this._materializeModuleAliases()
|
|
313
|
+
: []
|
|
314
|
+
return [...fromStrategy, ...fromModuleStrategy, ...this._appAliases]
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private _strategyIncludes(name: string): boolean {
|
|
318
|
+
if (this._strategyName === name) return true
|
|
319
|
+
if (Array.isArray(this._strategyName)) return this._strategyName.includes(name)
|
|
320
|
+
return false
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* For each known entity → module mapping, emit two pattern aliases:
|
|
325
|
+
* entities.<entity>.actions.* → modules.<module>.actions.$1
|
|
326
|
+
* entities.<entity>.errors.* → modules.<module>.errors.$1
|
|
327
|
+
* (plus a more specific actions/errors per-module fallback to core.*.)
|
|
328
|
+
*/
|
|
329
|
+
private _materializeModuleAliases(): AliasPattern[] {
|
|
330
|
+
const out: AliasPattern[] = []
|
|
331
|
+
for (const [entity, module] of this._entityToModule) {
|
|
332
|
+
out.push(
|
|
333
|
+
{ pattern: `entities.${entity}.actions.*`, target: `modules.${module}.actions.$1` },
|
|
334
|
+
{ pattern: `entities.${entity}.errors.*`, target: `modules.${module}.errors.$1` }
|
|
335
|
+
)
|
|
336
|
+
}
|
|
337
|
+
// Module-level fallbacks to core.
|
|
338
|
+
out.push(
|
|
339
|
+
{ pattern: 'modules.*.actions.*', target: 'core.actions.$2' },
|
|
340
|
+
{ pattern: 'modules.*.errors.*', target: 'core.errors.$2' }
|
|
341
|
+
)
|
|
342
|
+
return out
|
|
343
|
+
}
|
|
344
|
+
}
|