@quazardous/qdadm 1.22.0 → 2.1.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/README.md CHANGED
@@ -31,7 +31,7 @@ import { SecurityChecker, PermissionMatcher, PermissionRegistry } from '@quazard
31
31
  import { PersistableRoleProvider, createLocalStorageRolesProvider } from '@quazardous/qdadm/security'
32
32
 
33
33
  // Composables
34
- import { useForm, useBareForm, useListPage } from '@quazardous/qdadm/composables'
34
+ import { useBareForm, useListPage } from '@quazardous/qdadm/composables'
35
35
 
36
36
  // Components
37
37
  import { ListPage, PageLayout, AppLayout, FormField, FormActions } from '@quazardous/qdadm/components'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quazardous/qdadm",
3
- "version": "1.22.0",
3
+ "version": "2.1.0",
4
4
  "description": "Vue 3 framework for admin dashboards with PrimeVue",
5
5
  "author": "quazardous",
6
6
  "license": "MIT",
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Kernel-level default API client (qdadm #1198)
3
+ *
4
+ * Apps already build an axios-compatible HttpClient (base URL + auth
5
+ * interceptors) for their ApiStorages. Registering it once on the kernel
6
+ * makes it available to composables/widgets that need ad-hoc API calls
7
+ * (useOptionsLookup endpoint mode, ScopeEditor), so relative endpoints
8
+ * get base URL + auth applied without per-call wiring:
9
+ *
10
+ * new Kernel({ apiClient: myAxios })
11
+ */
12
+ import { inject } from 'vue'
13
+ import type { HttpClient } from '../entity/storage/ApiStorage'
14
+
15
+ /** Injection key under which the kernel provides the default API client. */
16
+ export const API_CLIENT_INJECTION_KEY = 'qdadmApiClient'
17
+
18
+ /** Accepted forms of the kernel `apiClient` option. */
19
+ export type ApiClientSource = HttpClient | (() => HttpClient)
20
+
21
+ /** Resolve an ApiClientSource (direct instance or factory) to a client. */
22
+ export function resolveApiClient(source: ApiClientSource | null | undefined): HttpClient | null {
23
+ if (!source) return null
24
+ return typeof source === 'function' ? source() : source
25
+ }
26
+
27
+ /**
28
+ * Inject the kernel-registered default API client, or null when none is
29
+ * registered. Must be called from a setup context (like any inject).
30
+ */
31
+ export function useApiClient(): HttpClient | null {
32
+ const source = inject<ApiClientSource | null>(API_CLIENT_INJECTION_KEY, null)
33
+ return resolveApiClient(source)
34
+ }
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, computed, onMounted, watch, inject } from 'vue'
3
+ import type { HttpClient } from '../../entity/storage/ApiStorage'
3
4
  import AutoComplete from 'primevue/autocomplete'
4
5
  import Select from 'primevue/select'
5
6
  import Button from 'primevue/button'
@@ -43,8 +44,10 @@ const props = withDefaults(defineProps<Props>(), {
43
44
  defaultActions: null
44
45
  })
45
46
 
46
- // Get API adapter (optional)
47
+ // Get API adapter (optional); the kernel default client (Kernel({ apiClient }))
48
+ // serves as fallback so scope endpoints get base URL + auth without app wiring.
47
49
  const api = inject<ApiAdapter | null>('apiAdapter', null)
50
+ const kernelApiClient = inject<HttpClient | (() => HttpClient) | null>('qdadmApiClient', null)
48
51
 
49
52
  // Get global scope config (optional) - allows app-level configuration
50
53
  const globalScopeConfig = inject<ScopeConfig>('scopeConfig', {})
@@ -178,8 +181,17 @@ function isRowComplete(row: ScopeRow): boolean {
178
181
  async function loadScopeDefinition(): Promise<void> {
179
182
  loading.value = true
180
183
  try {
181
- if (!api) {
182
- // No API adapter, use defaults
184
+ let data: ScopeDefinition | null = null
185
+ if (api) {
186
+ data = await api.request('GET', config.value.endpoint)
187
+ } else if (kernelApiClient) {
188
+ // Fallback: kernel default API client (base URL + auth applied)
189
+ const client = typeof kernelApiClient === 'function' ? kernelApiClient() : kernelApiClient
190
+ data = (await client.get<ScopeDefinition>(config.value.endpoint)).data
191
+ }
192
+
193
+ if (!data) {
194
+ // No API layer available, use defaults
183
195
  scopeDefinition.value = {
184
196
  resources: [...config.value.resources],
185
197
  actions: [...config.value.actions],
@@ -187,7 +199,6 @@ async function loadScopeDefinition(): Promise<void> {
187
199
  return
188
200
  }
189
201
 
190
- const data = await api.request('GET', config.value.endpoint)
191
202
  scopeDefinition.value = {
192
203
  resources: data.resources || [...config.value.resources],
193
204
  actions: data.actions || [...config.value.actions],
@@ -27,8 +27,6 @@ export { default as FormPage } from './edit/FormPage.vue'
27
27
  export { default as FormField } from './edit/FormField.vue'
28
28
  export { default as FormInput } from './edit/FormInput.vue'
29
29
  export { default as FormActions } from './edit/FormActions.vue'
30
- export { default as FormTabs } from './edit/FormTabs.vue' // Deprecated: use FieldGroups with layout="tabs"
31
- export { default as FormTab } from './edit/FormTab.vue' // Deprecated: use FieldGroups with layout="tabs"
32
30
  export { default as LookupField } from './edit/LookupField.vue'
33
31
  export { default as LookupPickerDialog } from './edit/LookupPickerDialog.vue'
34
32
 
@@ -41,7 +39,6 @@ export { default as ParentCard } from './show/ParentCard.vue'
41
39
  // Lists
42
40
  export { default as ListPage } from './lists/ListPage.vue'
43
41
  export { default as ActionButtons } from './lists/ActionButtons.vue'
44
- export { default as ActionColumn } from './lists/ActionColumn.vue'
45
42
  export { default as FilterBar } from './lists/FilterBar.vue'
46
43
 
47
44
  // Editors (vanilla-jsoneditor free)
@@ -50,7 +50,7 @@ interface UserData {
50
50
  const features = inject<FeaturesConfig>('qdadmFeatures', { poweredBy: true, breadcrumb: true })
51
51
  const homeRoute = inject<string>('qdadmHomeRoute', 'home')
52
52
 
53
- // Guard dialog from shared store (registered by useBareForm/useForm when a form is active)
53
+ // Guard dialog from shared store (registered by useBareForm/useEntityItemFormPage when a form is active)
54
54
  const guardDialog = useGuardDialog()
55
55
 
56
56
  const router = useRouter()
@@ -238,7 +238,7 @@ watch(() => route.fullPath, () => {
238
238
  })
239
239
 
240
240
  // Navigation context (breadcrumb + navlinks from route config)
241
- // Entity data comes from activeStack (populated by useEntityItemPage/useForm)
241
+ // Entity data comes from activeStack (populated by useEntityItemPage/useEntityItemFormPage)
242
242
  const { breadcrumb: defaultBreadcrumb, navlinks: defaultNavlinks } = useNavContext()
243
243
 
244
244
  // Allow child pages to override breadcrumb/navlinks via provide/inject
@@ -50,7 +50,7 @@ import DefaultBreadcrumb from './defaults/DefaultBreadcrumb.vue'
50
50
  const slots = useSlots()
51
51
  const hasMainSlot: boolean = !!slots.main
52
52
 
53
- // Guard dialog from shared store (registered by useBareForm/useForm when a form is active)
53
+ // Guard dialog from shared store (registered by useBareForm/useEntityItemFormPage when a form is active)
54
54
  const guardDialog = useGuardDialog()
55
55
 
56
56
  // Provide breadcrumb/navlinks override mechanism for child pages
@@ -47,7 +47,7 @@ const props = withDefaults(defineProps<Props>(), {
47
47
  badges: () => []
48
48
  })
49
49
 
50
- // Auto-injected title from useForm (if available)
50
+ // Auto-injected title from the form composables (if available)
51
51
  const injectedTitleParts = inject<Ref<TitleParts | null> | null>('qdadmPageTitleParts', null)
52
52
 
53
53
  // Use props first, then injected, then fallback
@@ -28,6 +28,7 @@
28
28
  import { computed, type PropType } from 'vue'
29
29
  import { RouterLink, type RouteLocationRaw } from 'vue-router'
30
30
  import Tag from 'primevue/tag'
31
+ import { formatDate, formatNumber, formatCurrency } from '../../utils/formatters'
31
32
 
32
33
  type DisplayType =
33
34
  | 'text'
@@ -101,13 +102,12 @@ const textValue = computed(() => {
101
102
  return String(props.value)
102
103
  })
103
104
 
104
- // Format number value
105
+ // Format number value (browser locale by default — utils/formatters policy)
105
106
  const numberValue = computed(() => {
106
107
  if (isEmpty.value) return '-'
107
108
  const num = Number(props.value)
108
109
  if (isNaN(num)) return String(props.value)
109
- const locale = props.field.locale || 'en-US'
110
- return new Intl.NumberFormat(locale).format(num)
110
+ return formatNumber(num, {}, props.field.locale)
111
111
  })
112
112
 
113
113
  // Format currency value
@@ -115,9 +115,7 @@ const currencyValue = computed(() => {
115
115
  if (isEmpty.value) return '-'
116
116
  const num = Number(props.value)
117
117
  if (isNaN(num)) return String(props.value)
118
- const locale = props.field.locale || 'en-US'
119
- const currency = props.field.currencyCode || 'USD'
120
- return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(num)
118
+ return formatCurrency(num, props.field.currencyCode || 'USD', props.field.locale)
121
119
  })
122
120
 
123
121
  // Format boolean value
@@ -143,9 +141,8 @@ const dateValue = computed(() => {
143
141
  const v = props.value as unknown
144
142
  const date = (typeof v === 'object' && v instanceof Date) ? v : new Date(String(v))
145
143
  if (isNaN(date.getTime())) return String(props.value)
146
- const locale = props.field.locale || 'en-US'
147
144
  const format = props.field.dateFormat || 'short'
148
- return new Intl.DateTimeFormat(locale, { dateStyle: format as 'short' | 'medium' | 'long' | 'full' }).format(date)
145
+ return formatDate(date, { dateStyle: format as 'short' | 'medium' | 'long' | 'full' }, props.field.locale)
149
146
  })
150
147
 
151
148
  // Format datetime value
@@ -154,8 +151,7 @@ const datetimeValue = computed(() => {
154
151
  const v = props.value as unknown
155
152
  const date = (typeof v === 'object' && v instanceof Date) ? v : new Date(String(v))
156
153
  if (isNaN(date.getTime())) return String(props.value)
157
- const locale = props.field.locale || 'en-US'
158
- return new Intl.DateTimeFormat(locale, { dateStyle: 'short', timeStyle: 'short' }).format(date)
154
+ return formatDate(date, { dateStyle: 'short', timeStyle: 'short' }, props.field.locale)
159
155
  })
160
156
 
161
157
  // Format select value (lookup label from options)
@@ -15,12 +15,6 @@ export {
15
15
  type SemanticBreadcrumbItem,
16
16
  } from './useSemanticBreadcrumb'
17
17
  export { useDirtyState, type UseDirtyStateReturn } from './useDirtyState'
18
- export {
19
- useForm,
20
- type UseFormOptions,
21
- type UseFormReturn,
22
- type FormAlterConfig,
23
- } from './useForm'
24
18
  export {
25
19
  useEntityItemFormPage,
26
20
  useEntityItemFormPage as useFormPageBuilder,
@@ -132,7 +126,6 @@ export {
132
126
  type UseUserImpersonatorReturn,
133
127
  type UserRecord,
134
128
  } from './useUserImpersonator'
135
- export { useCurrentEntity, type UseCurrentEntityReturn } from './useCurrentEntity'
136
129
  export {
137
130
  useOptionsLookup,
138
131
  type UseOptionsLookupConfig,
@@ -35,6 +35,7 @@ import { useEntityItemPage, type ParentConfig, type UseEntityItemPageReturn } fr
35
35
  import { useActiveStack } from '../chain/useActiveStack.js'
36
36
  import { FilterQuery } from '../query/FilterQuery'
37
37
  import { useI18n } from '../i18n/useI18n'
38
+ import { formatDateOnly } from '../utils/formatters'
38
39
 
39
40
  // Import types from dedicated types file
40
41
  import type {
@@ -1307,18 +1308,12 @@ export function useListPage<T = unknown>(config: UseListPageOptions<T>): UseList
1307
1308
  })
1308
1309
 
1309
1310
  // ============ UTILITIES ============
1311
+ // Browser-locale by default (unified policy — see utils/formatters).
1310
1312
  function formatDate(
1311
1313
  dateStr: string | null | undefined,
1312
1314
  options: Intl.DateTimeFormatOptions = {}
1313
1315
  ): string {
1314
- if (!dateStr) return '-'
1315
- const defaultOptions: Intl.DateTimeFormatOptions = {
1316
- day: '2-digit',
1317
- month: '2-digit',
1318
- year: 'numeric',
1319
- ...options,
1320
- }
1321
- return new Date(dateStr).toLocaleDateString('fr-FR', defaultOptions)
1316
+ return formatDateOnly(dateStr, options)
1322
1317
  }
1323
1318
 
1324
1319
  // ============ STANDARD ACTIONS ============
@@ -2,7 +2,7 @@
2
2
  * useNavContext - Route-aware navigation context for breadcrumb and navlinks
3
3
  *
4
4
  * Uses semantic breadcrumb as the source of truth for navigation structure.
5
- * Entity data comes from activeStack (set by useEntityItemPage/useForm).
5
+ * Entity data comes from activeStack (set by useEntityItemPage/useEntityItemFormPage).
6
6
  *
7
7
  * Semantic breadcrumb kinds:
8
8
  * - entity-list: Entity collection (e.g., /books)
@@ -42,6 +42,7 @@
42
42
  */
43
43
  import { ref, onMounted, type Ref } from 'vue'
44
44
  import { useOrchestrator } from '../orchestrator/useOrchestrator.js'
45
+ import { useApiClient } from '../api/apiClient'
45
46
 
46
47
  export interface OptionsLookupItem {
47
48
  label: string
@@ -66,11 +67,27 @@ export interface UseOptionsLookupConfig {
66
67
  entity?: string
67
68
  /** Extract distinct values from a specific field (entity mode only) */
68
69
  field?: string
69
- /** Fetch options from a raw API endpoint */
70
+ /**
71
+ * Fetch options from an API endpoint.
72
+ *
73
+ * Routing (#1198): a **relative** endpoint goes through the kernel's
74
+ * default API client when one is registered (`Kernel({ apiClient })`) —
75
+ * base URL + auth applied, no per-call wiring. An **absolute** URL is
76
+ * fetched raw (escape hatch for third-party APIs, combine with `headers`).
77
+ * A relative endpoint with no registered client falls back to a raw fetch
78
+ * (legacy) with a console warning.
79
+ */
70
80
  endpoint?: string
81
+ /**
82
+ * Route the endpoint request through the named entity's storage
83
+ * (`storage.request('GET', endpoint)`) instead of the kernel client —
84
+ * explicit refinement for multi-API apps. Takes precedence over the
85
+ * kernel client.
86
+ */
87
+ via?: string
71
88
  /** When using endpoint: pick a sub-key from the response data */
72
89
  pick?: string
73
- /** Extra headers for endpoint fetch (e.g. Authorization) */
90
+ /** Extra headers for the raw-fetch paths (e.g. Authorization) */
74
91
  headers?: Record<string, string> | (() => Record<string, string>)
75
92
  /** Static options (no fetch needed) */
76
93
  static?: unknown[]
@@ -164,10 +181,16 @@ export function useOptionsLookup(config: UseOptionsLookupConfig): UseOptionsLook
164
181
  : defaultDecode
165
182
  )
166
183
 
167
- // Resolve orchestrator lazily (only if entity mode)
168
- let getManager: ((name: string) => { list: (params?: Record<string, unknown>) => Promise<{ items: unknown[] }> }) | null = null
184
+ // Resolve orchestrator lazily (entity mode, or via:'entityName' routing)
185
+ type LookupManager = {
186
+ list: (params?: Record<string, unknown>) => Promise<{ items: unknown[] }>
187
+ storage?: {
188
+ request?: (method: string, path: string, options?: Record<string, unknown>) => Promise<unknown>
189
+ }
190
+ }
191
+ let getManager: ((name: string) => LookupManager) | null = null
169
192
 
170
- if (config.entity) {
193
+ if (config.entity || config.via) {
171
194
  try {
172
195
  const orc = useOrchestrator()
173
196
  // Generic-vs-non-generic: getManager is parameterised by an
@@ -180,6 +203,105 @@ export function useOptionsLookup(config: UseOptionsLookupConfig): UseOptionsLook
180
203
  }
181
204
  }
182
205
 
206
+ // Kernel default API client (Kernel({ apiClient })) — used for relative
207
+ // endpoints so base URL + auth apply without per-call wiring (#1198).
208
+ let apiClient: ReturnType<typeof useApiClient> = null
209
+ if (config.endpoint) {
210
+ try {
211
+ apiClient = useApiClient()
212
+ } catch {
213
+ // Outside a setup/injection context
214
+ }
215
+ }
216
+
217
+ // Bare-relative warning: once per composable instance, not per load
218
+ let warnedBareRelative = false
219
+
220
+ function warnLookup(msg: string): void {
221
+ console.warn(`[qdadm] useOptionsLookup(endpoint: '${config.endpoint}'): ${msg}`)
222
+ }
223
+
224
+ /** Shared normalization for every endpoint path: unwrap data, pick, array-check. */
225
+ function normalizeEndpointPayload(json: unknown): unknown[] {
226
+ let data: unknown = (json as { data?: unknown })?.data ?? json
227
+ if (config.pick && typeof data === 'object' && data !== null && !Array.isArray(data)) {
228
+ data = (data as Record<string, unknown>)[config.pick]
229
+ }
230
+ if (!Array.isArray(data)) {
231
+ warnLookup('response contained no usable array (after data/pick unwrap) — check the endpoint payload shape')
232
+ return []
233
+ }
234
+ return data
235
+ }
236
+
237
+ /** Endpoint routing (#1198): via storage > relative→kernel client > raw fetch. */
238
+ async function loadFromEndpoint(): Promise<unknown[]> {
239
+ const endpoint = config.endpoint!
240
+ const isAbsolute = /^https?:\/\//i.test(endpoint)
241
+
242
+ // 1. via:'entityName' → that entity's storage (explicit refinement)
243
+ if (config.via && getManager) {
244
+ const storage = getManager(config.via)?.storage
245
+ if (storage?.request) {
246
+ try {
247
+ const json = await storage.request('GET', endpoint)
248
+ return normalizeEndpointPayload(json)
249
+ } catch (err) {
250
+ warnLookup(`request via '${config.via}' storage failed: ${err instanceof Error ? err.message : err}`)
251
+ throw err
252
+ }
253
+ }
254
+ warnLookup(`via: '${config.via}' has no storage.request() — falling back to the default routing`)
255
+ }
256
+
257
+ // 2. relative endpoint → kernel default API client (base URL + auth)
258
+ if (!isAbsolute && apiClient) {
259
+ try {
260
+ const { data } = await apiClient.get(endpoint)
261
+ return normalizeEndpointPayload(data)
262
+ } catch (err) {
263
+ warnLookup(`request through the kernel apiClient failed: ${err instanceof Error ? err.message : err}`)
264
+ throw err
265
+ }
266
+ }
267
+
268
+ // 3. raw fetch — absolute URL (escape hatch) or legacy relative call
269
+ if (!isAbsolute && !warnedBareRelative) {
270
+ warnedBareRelative = true
271
+ warnLookup(
272
+ 'relative endpoint fetched bare — no API base URL or auth header applied. ' +
273
+ 'Register Kernel({ apiClient }) (recommended), use via: \'entityName\', or pass an absolute URL + headers.'
274
+ )
275
+ }
276
+ const extraHeaders = typeof config.headers === 'function' ? config.headers() : config.headers
277
+ const response = await fetch(endpoint, {
278
+ credentials: 'include',
279
+ headers: { 'Accept': 'application/json', ...extraHeaders },
280
+ })
281
+ if (!response.ok) {
282
+ const authHint = response.status === 401 || response.status === 403
283
+ ? ' — missing auth? register Kernel({ apiClient }) or pass headers'
284
+ : ''
285
+ warnLookup(`HTTP ${response.status}${authHint}`)
286
+ throw new Error(`HTTP ${response.status}`)
287
+ }
288
+ const contentType = response.headers.get('content-type') || ''
289
+ if (!contentType.includes('json')) {
290
+ warnLookup(
291
+ `response is not JSON (content-type: '${contentType || 'unknown'}') — the relative URL likely resolved ` +
292
+ 'to the app origin (HTML page). Register Kernel({ apiClient }) or use an absolute URL.'
293
+ )
294
+ }
295
+ let json: unknown
296
+ try {
297
+ json = await response.json()
298
+ } catch (err) {
299
+ warnLookup(`response body is not parseable JSON: ${err instanceof Error ? err.message : err}`)
300
+ throw err
301
+ }
302
+ return normalizeEndpointPayload(json)
303
+ }
304
+
183
305
  // Encoded strings cache for mapped mode (rebuilt on processItems)
184
306
  let encodedStrings: string[] = []
185
307
 
@@ -255,21 +377,7 @@ export function useOptionsLookup(config: UseOptionsLookupConfig): UseOptionsLook
255
377
  items = rawItems
256
378
  }
257
379
  } else if (config.endpoint) {
258
- const extraHeaders = typeof config.headers === 'function' ? config.headers() : config.headers
259
- const response = await fetch(config.endpoint, {
260
- credentials: 'include',
261
- headers: { 'Accept': 'application/json', ...extraHeaders },
262
- })
263
- if (!response.ok) throw new Error(`HTTP ${response.status}`)
264
- const json = await response.json()
265
-
266
- let data = json.data ?? json
267
-
268
- if (config.pick && typeof data === 'object' && !Array.isArray(data)) {
269
- data = (data as Record<string, unknown>)[config.pick]
270
- }
271
-
272
- items = Array.isArray(data) ? data : []
380
+ items = await loadFromEndpoint()
273
381
  }
274
382
 
275
383
  processItems(items)
@@ -2,7 +2,7 @@
2
2
  * usePageTitle - Provide custom page title for PageHeader
3
3
  *
4
4
  * Use this composable in custom pages to set the title displayed in PageHeader.
5
- * For standard CRUD pages, useForm handles this automatically.
5
+ * For standard CRUD pages, useEntityItemFormPage handles this automatically.
6
6
  *
7
7
  * Usage:
8
8
  * ```ts
@@ -77,7 +77,7 @@ export function usePageTitle(
77
77
  }
78
78
  }
79
79
 
80
- // Provide to PageHeader via same key as useForm
80
+ // Provide to PageHeader via same key as the form composables
81
81
  provide('qdadmPageTitleParts', titleParts)
82
82
 
83
83
  return { titleParts, setTitle }
@@ -137,7 +137,7 @@ export function applyCrudMethods(EntityManagerClass: { prototype: any }): void {
137
137
  const apiResponse = (await storage.request('GET', endpoint, {
138
138
  params: requestParams,
139
139
  context,
140
- })) as { data?: unknown }
140
+ })) as { data?: unknown; total?: number; pagination?: { total?: number } }
141
141
  // Normalize response
142
142
  const data = apiResponse.data ?? apiResponse
143
143
  const dataObj = data as {
@@ -150,9 +150,14 @@ export function applyCrudMethods(EntityManagerClass: { prototype: any }): void {
150
150
  items: Array.isArray(data)
151
151
  ? (data as any[])
152
152
  : dataObj.items || dataObj.data || [],
153
+ // Wrapper shapes like { data: [...], pagination: { total } } or
154
+ // { data: [...], total } carry the total on apiResponse, not on the
155
+ // unwrapped array — read both levels before falling back to page length.
153
156
  total:
154
157
  dataObj.total ??
155
158
  dataObj.pagination?.total ??
159
+ apiResponse.total ??
160
+ apiResponse.pagination?.total ??
156
161
  (Array.isArray(data) ? (data as any[]).length : 0),
157
162
  }
158
163
  } else {
@@ -57,11 +57,6 @@ export class ApiStorage<T extends EntityRecord = EntityRecord> extends IStorage<
57
57
  supportsCaching: true,
58
58
  }
59
59
 
60
- /** @deprecated Use static ApiStorage.capabilities.supportsCaching instead */
61
- get supportsCaching(): boolean {
62
- return ApiStorage.capabilities.supportsCaching
63
- }
64
-
65
60
  readonly endpoint: string
66
61
  readonly responseItemsKey: string
67
62
  readonly responseTotalKey: string
@@ -27,11 +27,6 @@ export class LocalStorage<T extends EntityRecord = EntityRecord> extends IStorag
27
27
  supportsCaching: false,
28
28
  }
29
29
 
30
- /** @deprecated Use static LocalStorage.capabilities.supportsCaching instead */
31
- get supportsCaching(): boolean {
32
- return LocalStorage.capabilities.supportsCaching
33
- }
34
-
35
30
  readonly key: string
36
31
  readonly idField: string
37
32
  readonly generateId: () => string
@@ -38,11 +38,6 @@ export class MemoryStorage<T extends EntityRecord = EntityRecord> extends IStora
38
38
  supportsCaching: false,
39
39
  }
40
40
 
41
- /** @deprecated Use static MemoryStorage.capabilities.supportsCaching instead */
42
- get supportsCaching(): boolean {
43
- return MemoryStorage.capabilities.supportsCaching
44
- }
45
-
46
41
  readonly idField: string
47
42
  readonly generateId: () => string
48
43
  protected _data: Map<string, T>
@@ -35,11 +35,6 @@ export class MockApiStorage<T extends EntityRecord = EntityRecord> extends IStor
35
35
  supportsCaching: false,
36
36
  }
37
37
 
38
- /** @deprecated Use static MockApiStorage.capabilities.supportsCaching instead */
39
- get supportsCaching(): boolean {
40
- return MockApiStorage.capabilities.supportsCaching
41
- }
42
-
43
38
  /**
44
39
  * Instance capabilities getter.
45
40
  * Merges static capabilities with instance-specific ones like requiresAuth.
@@ -91,11 +91,6 @@ export class SdkStorage<T extends EntityRecord = EntityRecord> extends IStorage<
91
91
  supportsCaching: true,
92
92
  }
93
93
 
94
- /** @deprecated Use SdkStorage.capabilities.supportsCaching instead */
95
- get supportsCaching(): boolean {
96
- return SdkStorage.capabilities.supportsCaching
97
- }
98
-
99
94
  readonly clientSidePagination: boolean
100
95
 
101
96
  protected _sdk: SdkInstance | null
@@ -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
+ })
package/src/index.ts CHANGED
@@ -166,12 +166,6 @@ export {
166
166
  type SemanticBreadcrumbItem,
167
167
  } from './composables/useSemanticBreadcrumb'
168
168
  export { useDirtyState, type UseDirtyStateReturn } from './composables/useDirtyState'
169
- export {
170
- useForm,
171
- type UseFormOptions,
172
- type UseFormReturn,
173
- type FormAlterConfig,
174
- } from './composables/useForm'
175
169
  export {
176
170
  useEntityItemFormPage,
177
171
  useEntityItemFormPage as useFormPageBuilder,
@@ -292,7 +286,6 @@ export {
292
286
  type UseUserImpersonatorReturn,
293
287
  type UserRecord,
294
288
  } from './composables/useUserImpersonator'
295
- export { useCurrentEntity, type UseCurrentEntityReturn } from './composables/useCurrentEntity'
296
289
  export {
297
290
  useOptionsLookup,
298
291
  type UseOptionsLookupConfig,
@@ -300,6 +293,12 @@ export {
300
293
  type OptionsLookupItem,
301
294
  type OptionsDisplayMode,
302
295
  } from './composables/useOptionsLookup'
296
+ export {
297
+ useApiClient,
298
+ resolveApiClient,
299
+ API_CLIENT_INJECTION_KEY,
300
+ type ApiClientSource,
301
+ } from './api/apiClient'
303
302
  export { useChildPage, type UseChildPageReturn } from './composables/useChildPage'
304
303
  export { useSidebarState, SIDEBAR_COLLAPSED_KEY } from './composables/useSidebarState'
305
304